Esmaeilkianii commited on
Commit
a814910
·
verified ·
1 Parent(s): d446480

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1611 -686
app.py CHANGED
@@ -1,39 +1,565 @@
1
  import streamlit as st
2
- import ee
3
- import folium
4
  import pandas as pd
5
  import numpy as np
6
- import datetime
7
- import requests
8
- import json
9
- import os
10
  from streamlit_folium import folium_static
11
- import matplotlib.pyplot as plt
 
 
 
 
12
  import plotly.express as px
13
  import plotly.graph_objects as go
14
- from datetime import datetime, timedelta
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- # Set page configuration
17
  st.set_page_config(
18
- page_title="Sugarcane Monitoring System",
19
  page_icon="🌿",
20
  layout="wide",
21
  initial_sidebar_state="expanded"
22
  )
23
 
24
- # App title and description
25
- st.title("Sugarcane Monitoring System")
26
- st.markdown("### Monitoring sugarcane farms in Khuzestan, Iran")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
- # Load service account credentials for Earth Engine
 
 
 
 
 
 
 
 
29
  @st.cache_resource
30
- def initialize_ee():
31
- service_account = 'dehkhodamap-e9f0da4ce9f6514021@ee-esmaeilkiani13877.iam.gserviceaccount.com'
32
-
33
- # Create a temporary credentials file
34
- credentials_path = 'credentials.json'
35
- with open(credentials_path, 'w') as f:
36
- json_content = {
37
  "type": "service_account",
38
  "project_id": "ee-esmaeilkiani13877",
39
  "private_key_id": "cfdea6eaf4115cb6462626743e4b15df85fd0c7f",
@@ -46,737 +572,1136 @@ def initialize_ee():
46
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/dehkhodamap-e9f0da4ce9f6514021%40ee-esmaeilkiani13877.iam.gserviceaccount.com",
47
  "universe_domain": "googleapis.com"
48
  }
49
- json.dump(json_content, f)
50
-
51
- # Authenticate and initialize
52
- credentials = ee.ServiceAccountCredentials(service_account, credentials_path)
53
- ee.Initialize(credentials)
54
-
55
- # Remove the temporary file after initialization
56
- os.remove(credentials_path)
57
-
58
- return True
59
 
60
- # Download CSV data from URLs
61
- @st.cache_data
62
- def load_farm_data():
63
- farm_coords_url = "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/farm_coordinates-TTksVyH860XeyfKUCGMMkq9pYMChZj.csv"
64
- farm_db_url = "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/%D9%BE%D8%A7%DB%8C%DA%AF%D8%A7%D9%87%20%D8%AF%D8%A7%D8%AF%D9%87%20%281%29-5Aq8TzJrbK3y5AtUVjrU0bwZD1SUHL.csv"
65
-
66
- # Load farm coordinates
67
- farm_coords = pd.read_csv(farm_coords_url)
68
-
69
- # Load farm database
70
- farm_db = pd.read_csv(farm_db_url)
71
-
72
- return farm_coords, farm_db
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
- # Process satellite imagery using GEE
75
- @st.cache_data(ttl=3600)
76
- def get_satellite_indices(lat, lon, date_range):
77
- # Define point of interest
78
- poi = ee.Geometry.Point([lon, lat])
79
-
80
- # Define region around the point
81
- region = poi.buffer(500) # 500m buffer around the point
82
-
83
- # Load Sentinel-2 collection
84
- s2 = ee.ImageCollection('COPERNICUS/S2_SR') \
85
- .filterBounds(region) \
86
- .filterDate(date_range[0], date_range[1]) \
87
- .sort('CLOUDY_PIXEL_PERCENTAGE') \
88
- .first()
89
-
90
- if s2 is None:
91
- return None, None, None, None
92
-
93
- # Calculate indices
94
- ndvi = s2.normalizedDifference(['B8', 'B4']).rename('NDVI')
95
- ndwi = s2.normalizedDifference(['B3', 'B8']).rename('NDWI')
96
-
97
- # LAI calculation (Leaf Area Index)
98
- # Using simplified model: LAI = 3.618 * NDVI - 0.118
99
- lai = ndvi.multiply(3.618).subtract(0.118).rename('LAI')
100
-
101
- # Chlorophyll content (CHL)
102
- # Using ratio of bands B8/B5
103
- chl = s2.select('B8').divide(s2.select('B5')).rename('CHL')
104
-
105
- # Create visualization parameters
106
- ndvi_vis = {
107
- 'min': 0,
108
- 'max': 1,
109
- 'palette': ['red', 'yellow', 'green']
110
- }
111
-
112
- ndwi_vis = {
113
- 'min': -1,
114
- 'max': 1,
115
- 'palette': ['red', 'white', 'blue']
116
- }
117
-
118
- lai_vis = {
119
- 'min': 0,
120
- 'max': 5,
121
- 'palette': ['white', 'lightgreen', 'darkgreen']
122
- }
123
-
124
- chl_vis = {
125
- 'min': 1,
126
- 'max': 3,
127
- 'palette': ['white', 'yellow', 'green']
128
- }
129
-
130
- # Get NDVI map tile URL
131
- ndvi_mapid = ndvi.getMapId(ndvi_vis)
132
- ndvi_url = ndvi_mapid['tile_fetcher'].url_format
133
-
134
- # Get NDWI map tile URL
135
- ndwi_mapid = ndwi.getMapId(ndwi_vis)
136
- ndwi_url = ndwi_mapid['tile_fetcher'].url_format
137
-
138
- # Get LAI map tile URL
139
- lai_mapid = lai.getMapId(lai_vis)
140
- lai_url = lai_mapid['tile_fetcher'].url_format
141
-
142
- # Get CHL map tile URL
143
- chl_mapid = chl.getMapId(chl_vis)
144
- chl_url = chl_mapid['tile_fetcher'].url_format
145
-
146
- # Get values at point
147
- ndvi_val = ndvi.reduceRegion(
148
- reducer=ee.Reducer.mean(),
149
- geometry=poi,
150
- scale=10
151
- ).getInfo()['NDVI']
152
-
153
- ndwi_val = ndwi.reduceRegion(
154
- reducer=ee.Reducer.mean(),
155
- geometry=poi,
156
- scale=10
157
- ).getInfo()['NDWI']
158
-
159
- lai_val = lai.reduceRegion(
160
- reducer=ee.Reducer.mean(),
161
- geometry=poi,
162
- scale=10
163
- ).getInfo()['LAI']
164
-
165
- chl_val = chl.reduceRegion(
166
- reducer=ee.Reducer.mean(),
167
- geometry=poi,
168
- scale=10
169
- ).getInfo()['CHL']
170
-
171
- return {
172
- 'urls': {
173
- 'ndvi': ndvi_url,
174
- 'ndwi': ndwi_url,
175
- 'lai': lai_url,
176
- 'chl': chl_url
177
- },
178
- 'values': {
179
- 'ndvi': ndvi_val,
180
- 'ndwi': ndwi_val,
181
- 'lai': lai_val,
182
- 'chl': chl_val
183
  }
184
- }
185
-
186
- # Get time series data for indices
187
- @st.cache_data(ttl=3600)
188
- def get_time_series(lat, lon, start_date, end_date):
189
- # Define point of interest
190
- poi = ee.Geometry.Point([lon, lat])
191
-
192
- # Define region around the point
193
- region = poi.buffer(500) # 500m buffer around the point
194
-
195
- # Load Sentinel-2 collection for the time period
196
- s2_collection = ee.ImageCollection('COPERNICUS/S2_SR') \
197
- .filterBounds(region) \
198
- .filterDate(start_date, end_date) \
199
- .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20))
200
-
201
- # Create a function to calculate indices for each image
202
- def add_indices(image):
203
- ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI')
204
- ndwi = image.normalizedDifference(['B3', 'B8']).rename('NDWI')
205
- lai = ndvi.multiply(3.618).subtract(0.118).rename('LAI')
206
- chl = image.select('B8').divide(image.select('B5')).rename('CHL')
207
-
208
- # Get the timestamp
209
- date = ee.Date(image.get('system:time_start'))
210
-
211
- # Get values at the point
212
- ndvi_val = ndvi.reduceRegion(
213
- reducer=ee.Reducer.mean(),
214
- geometry=poi,
215
- scale=10
216
- ).get('NDVI')
217
-
218
- ndwi_val = ndwi.reduceRegion(
219
- reducer=ee.Reducer.mean(),
220
- geometry=poi,
221
- scale=10
222
- ).get('NDWI')
223
-
224
- lai_val = lai.reduceRegion(
225
- reducer=ee.Reducer.mean(),
226
- geometry=poi,
227
- scale=10
228
- ).get('LAI')
229
-
230
- chl_val = chl.reduceRegion(
231
- reducer=ee.Reducer.mean(),
232
- geometry=poi,
233
- scale=10
234
- ).get('CHL')
235
-
236
- # Return a feature with these properties
237
- return ee.Feature(None, {
238
- 'date': date.format('YYYY-MM-dd'),
239
- 'ndvi': ndvi_val,
240
- 'ndwi': ndwi_val,
241
- 'lai': lai_val,
242
- 'chl': chl_val
243
- })
244
-
245
- # Map the function over the collection
246
- indices_fc = s2_collection.map(add_indices)
247
-
248
- # Get the data as a list of dictionaries
249
- indices_data = indices_fc.getInfo()['features']
250
-
251
- # Convert to pandas DataFrame
252
- if indices_data:
253
- data_list = [{'date': feature['properties']['date'],
254
- 'ndvi': feature['properties']['ndvi'],
255
- 'ndwi': feature['properties']['ndwi'],
256
- 'lai': feature['properties']['lai'],
257
- 'chl': feature['properties']['chl']}
258
- for feature in indices_data]
259
-
260
- df = pd.DataFrame(data_list)
261
- df['date'] = pd.to_datetime(df['date'])
262
- return df.sort_values('date')
263
- else:
264
- return pd.DataFrame(columns=['date', 'ndvi', 'ndwi', 'lai', 'chl'])
265
 
266
- # Get weather data from OpenWeather API
267
- @st.cache_data(ttl=3600)
268
- def get_weather_data(lat, lon):
269
- api_key = "ed47316a45379e2221a75f813229fb46"
270
- url = f"https://api.openweathermap.org/data/2.5/onecall?lat={lat}&lon={lon}&exclude=minutely,hourly,alerts&appid={api_key}&units=metric"
271
-
272
- response = requests.get(url)
273
-
274
- if response.status_code == 200:
275
- data = response.json()
276
-
277
- # Current weather
278
- current = data.get('current', {})
279
- current_weather = {
280
- 'temp': current.get('temp'),
281
- 'humidity': current.get('humidity'),
282
- 'wind_speed': current.get('wind_speed'),
283
- 'description': current.get('weather', [{}])[0].get('description', '')
284
  }
 
 
 
 
 
 
 
285
 
286
- # Daily forecast for the next 7 days
287
- daily = data.get('daily', [])
288
- daily_forecast = []
289
-
290
- for day in daily:
291
- date = datetime.fromtimestamp(day.get('dt')).strftime('%Y-%m-%d')
292
- daily_forecast.append({
293
- 'date': date,
294
- 'temp_min': day.get('temp', {}).get('min'),
295
- 'temp_max': day.get('temp', {}).get('max'),
296
- 'humidity': day.get('humidity'),
297
- 'wind_speed': day.get('wind_speed')
298
- })
299
-
300
- return {
301
- 'current': current_weather,
302
- 'forecast': daily_forecast
303
  }
304
- else:
305
- st.error(f"Failed to fetch weather data: {response.status_code}")
306
- return None
 
 
307
 
308
- # Display folium map with satellite data
309
- def display_satellite_map(lat, lon, tile_url, layer_name):
310
- # Create map centered on the farm
311
- m = folium.Map(location=[lat, lon], zoom_start=15)
312
-
313
- # Add base tiles
314
- folium.TileLayer('OpenStreetMap').add_to(m)
315
- folium.TileLayer('Stamen Terrain').add_to(m)
316
-
317
- # Add satellite data tile layer
318
- folium.TileLayer(
319
- tiles=tile_url,
320
- attr='Google Earth Engine',
321
- name=layer_name,
322
- overlay=True,
323
- opacity=0.7
324
- ).add_to(m)
325
-
326
- # Add marker for the farm
327
- folium.Marker(
328
- [lat, lon],
329
- popup=f"Farm Location\nLat: {lat}\nLon: {lon}"
330
- ).add_to(m)
331
-
332
- # Add layer control
333
- folium.LayerControl().add_to(m)
334
-
335
- return m
336
 
337
- # Initialize Earth Engine
338
- ee_initialized = initialize_ee()
 
 
339
 
340
- if ee_initialized:
341
- # Load farm data
342
- farm_coords, farm_db = load_farm_data()
343
-
344
- # Create sidebar for selection
345
- st.sidebar.title("Farm Selection")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
 
347
- # Select farm from dropdown
348
- farm_list = farm_coords['name'].unique().tolist()
349
- selected_farm = st.sidebar.selectbox("Select Farm", farm_list)
 
 
350
 
351
- # Get the selected farm data
352
- farm_row = farm_coords[farm_coords['name'] == selected_farm].iloc[0]
353
- farm_lat = farm_row['latitude']
354
- farm_lon = farm_row['longitude']
355
- farm_age = farm_row['age']
356
- farm_variety = farm_row['variety']
357
 
358
- # Get farm information from database
359
- farm_info = farm_db[farm_db['مزرعه'] == selected_farm]
 
 
 
 
360
 
361
- # Select date range
362
- st.sidebar.subheader("Date Selection")
 
 
 
 
363
 
364
- # Default to last 30 days
365
- end_date = datetime.now()
366
- start_date = end_date - timedelta(days=30)
367
 
368
- start_date_input = st.sidebar.date_input("Start Date", start_date)
369
- end_date_input = st.sidebar.date_input("End Date", end_date)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
 
371
- # Convert to string format for Earth Engine
372
- start_date_str = start_date_input.strftime('%Y-%m-%d')
373
- end_date_str = end_date_input.strftime('%Y-%m-%d')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
 
375
- # Get days in database for the selected farm
376
- if not farm_info.empty:
377
- days = farm_info['روز'].unique().tolist()
378
- selected_day = st.sidebar.selectbox("Select Day", days)
379
 
380
- # Filter farm info by selected day
381
- day_info = farm_info[farm_info['روز'] == selected_day]
 
 
 
 
 
382
 
383
- if not day_info.empty:
384
- # Display farm details
385
- with st.expander("Farm Details", expanded=True):
386
- col1, col2, col3, col4 = st.columns(4)
387
-
388
- with col1:
389
- st.metric("Farm", selected_farm)
390
-
391
- with col2:
392
- st.metric("Age", farm_age)
393
-
394
- with col3:
395
- st.metric("Variety", farm_variety)
396
-
397
- with col4:
398
- st.metric("Area", f"{day_info.iloc[0]['مساحت زیرمجموعه']} ha")
399
-
400
- # Fetch satellite data
401
- with st.spinner("Fetching satellite data..."):
402
- indices_data = get_satellite_indices(
403
- farm_lat,
404
- farm_lon,
405
- [start_date_str, end_date_str]
406
- )
407
 
408
- # Get time series data for the period
409
- time_series = get_time_series(
410
- farm_lat,
411
- farm_lon,
412
- start_date_str,
413
- end_date_str
414
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
 
416
- # Fetch weather data
417
- with st.spinner("Fetching weather data..."):
418
- weather_data = get_weather_data(farm_lat, farm_lon)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
419
 
420
- # Display tabs
421
- tab1, tab2, tab3, tab4 = st.tabs([
422
- "Current Status",
423
- "Time Series Analysis",
424
- "Weather Data",
425
- "Weekly Report"
426
- ])
427
 
428
- with tab1:
429
- if indices_data:
430
- # Display satellite indices
431
- st.subheader("Vegetation Indices")
432
-
433
- col1, col2, col3, col4 = st.columns(4)
434
-
435
- with col1:
436
- ndvi_val = indices_data['values']['ndvi']
437
- ndvi_color = "green" if ndvi_val > 0.5 else "yellow" if ndvi_val > 0.2 else "red"
438
- st.metric("NDVI", f"{ndvi_val:.3f}", delta="Good" if ndvi_val > 0.5 else "Medium" if ndvi_val > 0.2 else "Poor", delta_color="normal")
439
-
440
- with col2:
441
- ndwi_val = indices_data['values']['ndwi']
442
- st.metric("NDWI", f"{ndwi_val:.3f}", delta="Good" if ndwi_val > 0 else "Low", delta_color="normal")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
443
 
444
- with col3:
445
- lai_val = indices_data['values']['lai']
446
- st.metric("LAI", f"{lai_val:.3f}", delta="Good" if lai_val > 2 else "Medium" if lai_val > 1 else "Poor", delta_color="normal")
447
 
448
- with col4:
449
- chl_val = indices_data['values']['chl']
450
- st.metric("CHL", f"{chl_val:.3f}", delta="Good" if chl_val > 2 else "Medium" if chl_val > 1.5 else "Poor", delta_color="normal")
 
 
 
451
 
452
- # Display maps
453
- st.subheader("Satellite Maps")
 
454
 
455
- map_tabs = st.tabs(["NDVI", "NDWI", "LAI", "CHL"])
 
 
 
 
 
456
 
457
- with map_tabs[0]:
458
- ndvi_map = display_satellite_map(
459
- farm_lat,
460
- farm_lon,
461
- indices_data['urls']['ndvi'],
462
- "NDVI"
463
- )
464
- st.write("NDVI (Normalized Difference Vegetation Index) - Higher values (green) indicate healthy vegetation")
465
- folium_static(ndvi_map, width=800, height=500)
466
 
467
- with map_tabs[1]:
468
- ndwi_map = display_satellite_map(
469
- farm_lat,
470
- farm_lon,
471
- indices_data['urls']['ndwi'],
472
- "NDWI"
473
- )
474
- st.write("NDWI (Normalized Difference Water Index) - Higher values (blue) indicate more water content")
475
- folium_static(ndwi_map, width=800, height=500)
476
 
477
- with map_tabs[2]:
478
- lai_map = display_satellite_map(
479
- farm_lat,
480
- farm_lon,
481
- indices_data['urls']['lai'],
482
- "LAI"
483
- )
484
- st.write("LAI (Leaf Area Index) - Higher values (darker green) indicate more leaf area")
485
- folium_static(lai_map, width=800, height=500)
486
 
487
- with map_tabs[3]:
488
- chl_map = display_satellite_map(
489
- farm_lat,
490
- farm_lon,
491
- indices_data['urls']['chl'],
492
- "CHL"
493
- )
494
- st.write("CHL (Chlorophyll Content) - Higher values (green) indicate more chlorophyll")
495
- folium_static(chl_map, width=800, height=500)
496
- else:
497
- st.warning("No satellite data available for the selected date range. Try extending the date range.")
498
 
499
- with tab2:
500
- if not time_series.empty:
501
- st.subheader("Time Series Analysis")
502
-
503
- # Plot time series
504
- ts_tabs = st.tabs(["NDVI", "NDWI", "LAI", "CHL", "Comparison"])
505
-
506
- with ts_tabs[0]:
507
- fig = px.line(
508
- time_series,
509
- x='date',
510
- y='ndvi',
511
- title=f"NDVI Time Series for {selected_farm}",
512
- labels={"date": "Date", "ndvi": "NDVI Value"},
513
- markers=True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
514
  )
515
- st.plotly_chart(fig, use_container_width=True)
516
-
517
- with ts_tabs[1]:
518
- fig = px.line(
519
- time_series,
520
- x='date',
521
- y='ndwi',
522
- title=f"NDWI Time Series for {selected_farm}",
523
- labels={"date": "Date", "ndwi": "NDWI Value"},
524
- markers=True
525
  )
526
  st.plotly_chart(fig, use_container_width=True)
527
-
528
- with ts_tabs[2]:
 
 
 
529
  fig = px.line(
530
- time_series,
531
- x='date',
532
- y='lai',
533
- title=f"LAI Time Series for {selected_farm}",
534
- labels={"date": "Date", "lai": "LAI Value"},
535
  markers=True
536
  )
537
- st.plotly_chart(fig, use_container_width=True)
538
-
539
- with ts_tabs[3]:
540
- fig = px.line(
541
- time_series,
542
- x='date',
543
- y='chl',
544
- title=f"CHL Time Series for {selected_farm}",
545
- labels={"date": "Date", "chl": "CHL Value"},
546
- markers=True
547
  )
548
  st.plotly_chart(fig, use_container_width=True)
549
-
550
- with ts_tabs[4]:
551
- # Comparison of all indices
552
- fig = go.Figure()
553
-
554
- fig.add_trace(go.Scatter(
555
- x=time_series['date'],
556
- y=time_series['ndvi'],
557
- mode='lines+markers',
558
- name='NDVI'
559
- ))
560
-
561
- fig.add_trace(go.Scatter(
562
- x=time_series['date'],
563
- y=time_series['ndwi'],
564
- mode='lines+markers',
565
- name='NDWI'
566
- ))
567
 
568
- fig.add_trace(go.Scatter(
569
- x=time_series['date'],
570
- y=time_series['lai'],
571
- mode='lines+markers',
572
- name='LAI'
573
- ))
574
-
575
- fig.add_trace(go.Scatter(
576
- x=time_series['date'],
577
- y=time_series['chl'],
578
- mode='lines+markers',
579
- name='CHL'
580
- ))
581
 
 
 
 
 
 
 
 
 
582
  fig.update_layout(
583
- title=f"Vegetation Indices Comparison for {selected_farm}",
584
- xaxis_title="Date",
585
- yaxis_title="Index Value",
586
- legend_title="Index"
587
  )
588
-
589
  st.plotly_chart(fig, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590
  else:
591
- st.warning("No time series data available for the selected date range. Try extending the date range.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
592
 
593
- with tab3:
594
- if weather_data:
595
- st.subheader("Weather Data")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
596
 
597
- # Current weather
598
- current = weather_data['current']
599
- forecast = weather_data['forecast']
600
 
601
- col1, col2, col3 = st.columns(3)
 
 
 
 
 
 
 
 
 
602
 
603
- with col1:
604
- st.metric("Temperature", f"{current['temp']}°C")
605
 
606
- with col2:
607
- st.metric("Humidity", f"{current['humidity']}%")
608
 
609
- with col3:
610
- st.metric("Wind Speed", f"{current['wind_speed']} m/s")
 
 
 
 
 
 
 
 
 
 
 
611
 
612
- st.write(f"Current conditions: {current['description'].title()}")
613
 
614
- # Weather forecast
615
- st.subheader("7-Day Forecast")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
616
 
617
- # Create forecast dataframe
618
- forecast_df = pd.DataFrame(forecast)
 
 
619
 
620
- # Plot temperature forecast
621
  fig = go.Figure()
622
-
623
  fig.add_trace(go.Scatter(
624
- x=forecast_df['date'],
625
- y=forecast_df['temp_max'],
626
  mode='lines+markers',
627
- name='Max Temp',
628
- line=dict(color='red')
 
629
  ))
630
-
631
  fig.add_trace(go.Scatter(
632
- x=forecast_df['date'],
633
- y=forecast_df['temp_min'],
634
- mode='lines+markers',
635
- name='Min Temp',
636
- line=dict(color='blue')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
637
  ))
638
-
639
  fig.update_layout(
640
- title="Temperature Forecast",
641
- xaxis_title="Date",
642
- yaxis_title="Temperature (°C)",
643
- legend_title="Temperature"
644
- )
645
-
646
- st.plotly_chart(fig, use_container_width=True)
647
-
648
- # Plot humidity forecast
649
- fig = px.line(
650
- forecast_df,
651
- x='date',
652
- y='humidity',
653
- title="Humidity Forecast",
654
- labels={"date": "Date", "humidity": "Humidity (%)"},
655
- markers=True
656
  )
657
-
658
- st.plotly_chart(fig, use_container_width=True)
659
-
660
- # Plot wind speed forecast
661
- fig = px.line(
662
- forecast_df,
663
- x='date',
664
- y='wind_speed',
665
- title="Wind Speed Forecast",
666
- labels={"date": "Date", "wind_speed": "Wind Speed (m/s)"},
667
- markers=True
668
- )
669
-
670
  st.plotly_chart(fig, use_container_width=True)
671
  else:
672
- st.warning("Weather data could not be retrieved. Please check your internet connection.")
 
 
 
 
673
 
674
- with tab4:
675
- st.subheader("Weekly Report")
676
-
677
- # Get the last 7 days of data
678
- last_7_days = datetime.now() - timedelta(days=7)
679
- last_7_days_str = last_7_days.strftime('%Y-%m-%d')
680
-
681
- with st.spinner("Generating weekly report..."):
682
- # Get weekly time series
683
- weekly_data = get_time_series(
684
- farm_lat,
685
- farm_lon,
686
- last_7_days_str,
687
- end_date_str
688
- )
689
-
690
- if not weekly_data.empty:
691
- # Weekly summary
692
- weekly_avg = weekly_data.mean()
693
-
694
- col1, col2, col3, col4 = st.columns(4)
695
-
696
- with col1:
697
- st.metric("Avg NDVI", f"{weekly_avg['ndvi']:.3f}")
698
-
699
- with col2:
700
- st.metric("Avg NDWI", f"{weekly_avg['ndwi']:.3f}")
701
-
702
- with col3:
703
- st.metric("Avg LAI", f"{weekly_avg['lai']:.3f}")
704
-
705
- with col4:
706
- st.metric("Avg CHL", f"{weekly_avg['chl']:.3f}")
707
-
708
- # Plot weekly trends
709
- st.subheader("Weekly Trends")
710
-
711
- fig = go.Figure()
712
-
713
- fig.add_trace(go.Scatter(
714
- x=weekly_data['date'],
715
- y=weekly_data['ndvi'],
716
- mode='lines+markers',
717
- name='NDVI'
718
- ))
719
-
720
- fig.add_trace(go.Scatter(
721
- x=weekly_data['date'],
722
- y=weekly_data['lai'],
723
- x=weekly_data['date'],
724
- y=weekly_data['lai'],
725
- mode='lines+markers',
726
- name='LAI'
727
- ))
728
-
729
- fig.update_layout(
730
- title=f"NDVI and LAI Weekly Trends for {selected_farm}",
731
- xaxis_title="Date",
732
- yaxis_title="Index Value",
733
- legend_title="Index"
734
- )
735
-
736
- st.plotly_chart(fig, use_container_width=True)
737
-
738
- # Weekly change
739
- if len(weekly_data) > 1:
740
- first_day = weekly_data.iloc[0]
741
- last_day = weekly_data.iloc[-1]
742
-
743
- ndvi_change = ((last_day['ndvi'] - first_day['ndvi']) / first_day['ndvi']) * 100 if first_day['ndvi'] != 0 else 0
744
- lai_change = ((last_day['lai'] - first_day['lai']) / first_day['lai']) * 100 if first_day['lai'] != 0 else 0
745
-
746
- col1, col2 = st.columns(2)
747
-
748
- with col1:
749
- st.metric("NDVI Change", f"{ndvi_change:.2f}%", delta=f"{ndvi_change:.2f}%")
750
-
751
- with col2:
752
- st.metric("LAI Change", f"{lai_change:.2f}%", delta=f"{lai_change:.2f}%")
753
-
754
- # Growth status assessment
755
- st.subheader("Growth Status Assessment")
756
-
757
- if ndvi_change > 5 and lai_change > 5:
758
- st.success("✅ Healthy Growth: The crop is showing good growth patterns with increasing vegetation indices.")
759
- elif ndvi_change > 0 and lai_change > 0:
760
- st.info("ℹ️ Moderate Growth: The crop is growing, but at a slower rate than expected.")
761
- elif ndvi_change < 0 or lai_change < 0:
762
- st.warning("⚠️ Growth Concern: The crop is showing signs of stress or declining health.")
763
-
764
- # Recommendations
765
- st.subheader("Recommendations")
766
-
767
- if ndvi_change < 0:
768
- st.warning("Consider checking for pest infestations or nutrient deficiencies.")
769
-
770
- if ndwi_val < -0.3:
771
- st.warning("Water stress detected. Consider irrigation schedule adjustments.")
772
-
773
- if lai_val < 1.5:
774
- st.warning("Low leaf area index. Investigate possible causes for poor canopy development.")
775
-
776
- if chl_val < 1.5:
777
- st.warning("Low chlorophyll content. Consider nitrogen fertilization.")
778
- else:
779
- st.warning("No data available for the last 7 days. Check your satellite data availability.")
780
- else:
781
- st.error("Failed to initialize Google Earth Engine. Please check your credentials.")
782
 
 
 
 
 
 
 
 
1
  import streamlit as st
 
 
2
  import pandas as pd
3
  import numpy as np
4
+ import folium
 
 
 
5
  from streamlit_folium import folium_static
6
+ import ee
7
+ import os
8
+ import json
9
+ import time
10
+ from datetime import datetime, timedelta
11
  import plotly.express as px
12
  import plotly.graph_objects as go
13
+ from PIL import Image
14
+ import base64
15
+ from io import BytesIO
16
+ import matplotlib.pyplot as plt
17
+ import seaborn as sns
18
+ import altair as alt
19
+ from streamlit_option_menu import option_menu
20
+ from streamlit_lottie import st_lottie
21
+ import requests
22
+ import hydralit_components as hc
23
+ from streamlit_extras.colored_header import colored_header
24
+ from streamlit_extras.metric_cards import style_metric_cards
25
+ from streamlit_extras.chart_container import chart_container
26
+ from streamlit_extras.add_vertical_space import add_vertical_space
27
+ from streamlit_card import card
28
+ import pydeck as pdk
29
+ import math
30
+ from sklearn.linear_model import LinearRegression
31
 
32
+ # Page configuration with custom theme
33
  st.set_page_config(
34
+ page_title="سامانه هوشمند پایش مزارع نیشکر دهخدا",
35
  page_icon="🌿",
36
  layout="wide",
37
  initial_sidebar_state="expanded"
38
  )
39
 
40
+ # Custom CSS with modern green design and animations
41
+ st.markdown("""
42
+ <style>
43
+ @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@100;200;300;400;500;600;700;800;900&display=swap');
44
+
45
+ * {
46
+ font-family: 'Vazirmatn', sans-serif !important;
47
+ }
48
+
49
+ /* Main container styling */
50
+ .main {
51
+ background: linear-gradient(135deg, #f5f7fa 0%, #e4e9f2 100%);
52
+ }
53
+
54
+ /* Header styling */
55
+ .main-header {
56
+ background: linear-gradient(90deg, #1a8754 0%, #115740 100%);
57
+ padding: 1.5rem;
58
+ border-radius: 12px;
59
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
60
+ margin-bottom: 2rem;
61
+ position: relative;
62
+ overflow: hidden;
63
+ animation: header-glow 3s infinite alternate;
64
+ }
65
+
66
+ @keyframes header-glow {
67
+ 0% {
68
+ box-shadow: 0 8px 32px rgba(26, 135, 84, 0.1);
69
+ }
70
+ 100% {
71
+ box-shadow: 0 8px 32px rgba(26, 135, 84, 0.3);
72
+ }
73
+ }
74
+
75
+ .main-header::before {
76
+ content: '';
77
+ position: absolute;
78
+ top: -50%;
79
+ left: -50%;
80
+ width: 200%;
81
+ height: 200%;
82
+ background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 70%);
83
+ transform: rotate(30deg);
84
+ z-index: 0;
85
+ }
86
+
87
+ .main-header h1 {
88
+ color: white;
89
+ font-weight: 700;
90
+ margin: 0;
91
+ position: relative;
92
+ z-index: 1;
93
+ }
94
+
95
+ .main-header p {
96
+ color: rgba(255, 255, 255, 0.8);
97
+ margin: 0;
98
+ position: relative;
99
+ z-index: 1;
100
+ }
101
+
102
+ /* Navigation menu styling */
103
+ .st-emotion-cache-1lcbz7b {
104
+ background-color: transparent !important;
105
+ padding: 0 !important;
106
+ margin-bottom: 20px !important;
107
+ }
108
+
109
+ .st-emotion-cache-1lcbz7b .st-emotion-cache-1j7d69d {
110
+ --hover-color: #e9f7ef !important;
111
+ border-radius: 10px !important;
112
+ font-size: 16px !important;
113
+ text-align: center !important;
114
+ margin: 0 !important;
115
+ }
116
+
117
+ .st-emotion-cache-1lcbz7b .st-emotion-cache-1j7d69d:hover {
118
+ background-color: #e9f7ef !important;
119
+ }
120
+
121
+ .st-emotion-cache-1lcbz7b .st-emotion-cache-1j7d69d[data-selected="true"] {
122
+ background-color: #1a8754 !important;
123
+ color: white !important;
124
+ font-weight: 600 !important;
125
+ }
126
+
127
+ .st-emotion-cache-1lcbz7b .st-emotion-cache-1j7d69d .st-emotion-cache-1m5q2i0 {
128
+ color: #1a8754 !important;
129
+ font-size: 18px !important;
130
+ }
131
+
132
+ /* Metric card styling */
133
+ .metric-card {
134
+ background: white;
135
+ border-radius: 12px;
136
+ padding: 1.5rem;
137
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
138
+ transition: all 0.3s ease;
139
+ text-align: center;
140
+ }
141
+
142
+ .metric-card:hover {
143
+ transform: translateY(-5px);
144
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.1);
145
+ }
146
+
147
+ .metric-card .metric-value {
148
+ font-size: 2.5rem;
149
+ font-weight: 700;
150
+ color: #1a8754;
151
+ margin-bottom: 0.5rem;
152
+ }
153
+
154
+ .metric-card .metric-label {
155
+ font-size: 1rem;
156
+ color: #6c757d;
157
+ }
158
+
159
+ /* Map container styling */
160
+ .map-container {
161
+ border-radius: 12px;
162
+ overflow: hidden;
163
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
164
+ }
165
+
166
+ /* Tabs styling */
167
+ .stTabs [data-baseweb="tab-list"] {
168
+ gap: 8px;
169
+ }
170
+
171
+ .stTabs [data-baseweb="tab"] {
172
+ border-radius: 4px 4px 0px 0px;
173
+ padding: 10px 16px;
174
+ background-color: #f8f9fa;
175
+ }
176
+
177
+ .stTabs [aria-selected="true"] {
178
+ background-color: #1a8754 !important;
179
+ color: white !important;
180
+ }
181
+
182
+ /* Sidebar styling */
183
+ [data-testid="stSidebar"] {
184
+ background-color: #ffffff;
185
+ border-right: 1px solid #e9ecef;
186
+ }
187
+
188
+ /* Animations */
189
+ @keyframes fadeIn {
190
+ 0% { opacity: 0; transform: translateY(20px); }
191
+ 100% { opacity: 1; transform: translateY(0); }
192
+ }
193
+
194
+ .animate-fadeIn {
195
+ animation: fadeIn 0.5s ease forwards;
196
+ }
197
+
198
+ /* Loading animation */
199
+ .loading-spinner {
200
+ display: flex;
201
+ justify-content: center;
202
+ align-items: center;
203
+ height: 100px;
204
+ }
205
+
206
+ .loading-spinner::after {
207
+ content: "";
208
+ width: 40px;
209
+ height: 40px;
210
+ border: 4px solid #f3f3f3;
211
+ border-top: 4px solid #1a8754;
212
+ border-radius: 50%;
213
+ animation: spin 1s linear infinite;
214
+ }
215
+
216
+ @keyframes spin {
217
+ 0% { transform: rotate(0deg); }
218
+ 100% { transform: rotate(360deg); }
219
+ }
220
+
221
+ /* RTL Support */
222
+ .rtl {
223
+ direction: rtl;
224
+ text-align: right;
225
+ }
226
+
227
+ /* Custom scrollbar */
228
+ ::-webkit-scrollbar {
229
+ width: 8px;
230
+ height: 8px;
231
+ }
232
+
233
+ ::-webkit-scrollbar-track {
234
+ background: #f1f1f1;
235
+ border-radius: 10px;
236
+ }
237
+
238
+ ::-webkit-scrollbar-thumb {
239
+ background: #1a8754;
240
+ border-radius: 10px;
241
+ }
242
+
243
+ ::-webkit-scrollbar-thumb:hover {
244
+ background: #115740;
245
+ }
246
+
247
+ /* Tooltip styling */
248
+ .tooltip {
249
+ position: relative;
250
+ display: inline-block;
251
+ }
252
+
253
+ .tooltip .tooltiptext {
254
+ visibility: hidden;
255
+ width: 120px;
256
+ background-color: #555;
257
+ color: #fff;
258
+ text-align: center;
259
+ border-radius: 6px;
260
+ padding: 5px;
261
+ position: absolute;
262
+ z-index: 1;
263
+ bottom: 125%;
264
+ left: 50%;
265
+ margin-left: -60px;
266
+ opacity: 0;
267
+ transition: opacity 0.3s;
268
+ }
269
+
270
+ .tooltip:hover .tooltiptext {
271
+ visibility: visible;
272
+ opacity: 1;
273
+ }
274
+
275
+ /* Data table styling */
276
+ .dataframe {
277
+ border-collapse: collapse;
278
+ width: 100%;
279
+ border-radius: 8px;
280
+ overflow: hidden;
281
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
282
+ }
283
+
284
+ .dataframe th {
285
+ background-color: #1a8754;
286
+ color: white;
287
+ padding: 12px;
288
+ text-align: right;
289
+ }
290
+
291
+ .dataframe td {
292
+ padding: 10px 12px;
293
+ border-bottom: 1px solid #e9ecef;
294
+ }
295
+
296
+ .dataframe tr:nth-child(even) {
297
+ background-color: #f8f9fa;
298
+ }
299
+
300
+ .dataframe tr:hover {
301
+ background-color: #e9ecef;
302
+ }
303
+
304
+ /* Progress bar styling */
305
+ .stProgress > div > div > div > div {
306
+ background-color: #1a8754;
307
+ }
308
+
309
+ /* Notification styling */
310
+ .notification {
311
+ background-color: #d1e7dd;
312
+ color: #0f5132;
313
+ padding: 1rem;
314
+ border-radius: 8px;
315
+ margin-bottom: 1rem;
316
+ display: flex;
317
+ align-items: center;
318
+ animation: slideIn 0.5s ease;
319
+ }
320
+
321
+ @keyframes slideIn {
322
+ 0% { transform: translateX(100%); opacity: 0; }
323
+ 100% { transform: translateX(0); opacity: 1; }
324
+ }
325
+
326
+ .notification-icon {
327
+ margin-right: 0.5rem;
328
+ font-size: 1.2rem;
329
+ }
330
+
331
+ /* Custom select box */
332
+ .custom-select {
333
+ background-color: white;
334
+ border-radius: 8px;
335
+ padding: 0.5rem;
336
+ border: 1px solid #ced4da;
337
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
338
+ }
339
+
340
+ /* Glassmorphism effect */
341
+ .glass-card {
342
+ background: rgba(255, 255, 255, 0.7);
343
+ backdrop-filter: blur(10px);
344
+ -webkit-backdrop-filter: blur(10px);
345
+ border-radius: 12px;
346
+ border: 1px solid rgba(255, 255, 255, 0.3);
347
+ padding: 1.5rem;
348
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
349
+ }
350
+
351
+ /* Neumorphism effect */
352
+ .neumorphic-card {
353
+ background: #f0f0f3;
354
+ border-radius: 12px;
355
+ box-shadow: 10px 10px 20px #d1d1d4, -10px -10px 20px #ffffff;
356
+ padding: 1.5rem;
357
+ }
358
+
359
+ /* Gradient text */
360
+ .gradient-text {
361
+ background: linear-gradient(90deg, #1a8754 0%, #115740 100%);
362
+ -webkit-background-clip: text;
363
+ -webkit-text-fill-color: transparent;
364
+ font-weight: 700;
365
+ }
366
+
367
+ /* Pulsing animation */
368
+ @keyframes pulse {
369
+ 0% { transform: scale(1); }
370
+ 50% { transform: scale(1.05); }
371
+ 100% { transform: scale(1); }
372
+ }
373
+
374
+ .pulse-animation {
375
+ animation: pulse 2s infinite;
376
+ }
377
+
378
+ /* Custom radio buttons */
379
+ .stRadio > div {
380
+ display: flex;
381
+ gap: 10px;
382
+ }
383
+
384
+ .stRadio label {
385
+ cursor: pointer;
386
+ background-color: #f8f9fa;
387
+ padding: 0.5rem 1rem;
388
+ border-radius: 50px;
389
+ transition: all 0.3s ease;
390
+ }
391
+
392
+ .stRadio label:hover {
393
+ background-color: #e9ecef;
394
+ }
395
+
396
+ /* Hide default radio button */
397
+ .stRadio input {
398
+ display: none;
399
+ }
400
+
401
+ /* Custom checked state */
402
+ .stRadio input:checked + label {
403
+ background-color: #1a8754;
404
+ color: white;
405
+ }
406
+
407
+ .stSelectbox, .stNumberInput {
408
+ background-color: #f0f2f6;
409
+ border-radius: 10px;
410
+ padding: 10px;
411
+ margin: 10px 0;
412
+ }
413
+
414
+ .custom-card {
415
+ background-color: white;
416
+ padding: 20px;
417
+ border-radius: 15px;
418
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
419
+ margin: 10px 0;
420
+ }
421
+
422
+ .metric-container {
423
+ display: flex;
424
+ justify-content: space-between;
425
+ flex-wrap: wrap;
426
+ }
427
+
428
+ .metric-card {
429
+ background-color: #1a8754;
430
+ color: white;
431
+ padding: 15px;
432
+ border-radius: 10px;
433
+ margin: 5px;
434
+ flex: 1;
435
+ min-width: 200px;
436
+ text-align: center;
437
+ }
438
+
439
+ /* Button styling */
440
+ .stButton>button {
441
+ border-radius: 50px;
442
+ padding: 0.5rem 1.5rem;
443
+ font-weight: 600;
444
+ transition: all 0.3s ease;
445
+ border: none;
446
+ }
447
+
448
+ .stButton>button:hover {
449
+ transform: translateY(-2px);
450
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
451
+ }
452
+
453
+ .primary-btn {
454
+ background: linear-gradient(90deg, #1a8754 0%, #115740 100%);
455
+ color: white;
456
+ }
457
+
458
+ .secondary-btn {
459
+ background: white;
460
+ color: #1a8754;
461
+ border: 1px solid #1a8754 !important;
462
+ }
463
+
464
+ /* Footer styling */
465
+ footer {
466
+ position: fixed;
467
+ left: 0;
468
+ bottom: 0;
469
+ width: 100%;
470
+ background-color: #1a8754;
471
+ color: white;
472
+ text-align: center;
473
+ padding: 10px 0;
474
+ font-family: 'Vazirmatn', sans-serif;
475
+ }
476
+ </style>
477
+ """, unsafe_allow_html=True)
478
+
479
+ # Load real farm data from CSV
480
+ @st.cache_data
481
+ def load_farm_data():
482
+ try:
483
+ df = pd.read_csv("کراپ لاگ کلی (1).csv")
484
+ # Rename columns for consistency with the program
485
+ df.rename(columns={
486
+ 'سال': 'Year',
487
+ 'هفته': 'Week',
488
+ 'مزرعه': 'Farm_ID',
489
+ 'کانال': 'Channel',
490
+ 'اداره': 'Administration',
491
+ 'مساحت': 'Area',
492
+ 'مساحت زیر مجموعه': 'SubArea',
493
+ 'رقم': 'Variety',
494
+ 'سن': 'Age',
495
+ 'ایستگاه 1': 'Station1',
496
+ 'ایستگاه 2': 'Station2',
497
+ 'ایستگاه 3': 'Station3',
498
+ 'ایستگاه 4': 'Station4',
499
+ 'ایستگاه 5': 'Station5',
500
+ 'ارتفاع هفته جاری مزرعه': 'CurrentHeight',
501
+ 'ارتفاع هفته گذشته مزرعه': 'PreviousHeight',
502
+ 'رشد هفته جاری': 'CurrentGrowth',
503
+ 'رشد هفته گذشته': 'PreviousGrowth',
504
+ 'نیتروژن فعلی': 'CurrentNitrogen',
505
+ 'نیتروژن استاندارد فعلی': 'StandardNitrogen',
506
+ 'نیتروژن قبلی': 'PreviousNitrogen',
507
+ 'نیتروژن استاندارد قبلی': 'PreviousStandardNitrogen',
508
+ 'رطوبت غلاف فعلی': 'CurrentMoisture',
509
+ 'رطوبت استاندارد فعلی': 'StandardMoisture',
510
+ 'رطوبت غلاف قبلی': 'PreviousMoisture',
511
+ 'رطوبت استاندارد قبلی': 'PreviousStandardMoisture',
512
+ 'چاهک 1': 'Well1',
513
+ 'تاریخ قرائت': 'Well1Date',
514
+ 'چاهک 2': 'Well2',
515
+ 'تاریخ قرائت.1': 'Well2Date'
516
+ }, inplace=True)
517
+ return df
518
+ except Exception as e:
519
+ st.error(f"خطا در بارگذاری داده‌های مزارع: {e}")
520
+ return pd.DataFrame()
521
+
522
+ @st.cache_data
523
+ def load_coordinates_data():
524
+ try:
525
+ coords_df = pd.read_csv("farm_coordinates.csv")
526
+ coords_df.rename(columns={
527
+ 'مزرعه': 'Farm_ID',
528
+ 'عرض جغرافیایی': 'Latitude',
529
+ 'طول جغرافیایی': 'Longitude'
530
+ }, inplace=True)
531
+ return coords_df
532
+ except Exception as e:
533
+ st.error(f"خطا در بارگذاری داده‌های مختصات: {e}")
534
+ return pd.DataFrame()
535
+
536
+ @st.cache_data
537
+ def load_day_data():
538
+ try:
539
+ day_df = pd.read_csv("پایگاه داده (1).csv")
540
+ day_df.rename(columns={
541
+ 'مزرعه': 'Farm_ID',
542
+ 'روز': 'Day'
543
+ }, inplace=True)
544
+ return day_df
545
+ except Exception as e:
546
+ st.error(f"خطا در بارگذاری داده‌های روزهای هفته: {e}")
547
+ return pd.DataFrame()
548
 
549
+ # Load animation JSON
550
+ @st.cache_data
551
+ def load_lottie_url(url: str):
552
+ r = requests.get(url)
553
+ if r.status_code != 200:
554
+ return None
555
+ return r.json()
556
+
557
+ # Initialize Earth Engine (unchanged for now, but can be used with real data)
558
  @st.cache_resource
559
+ def initialize_earth_engine():
560
+ try:
561
+ service_account = 'dehkhodamap-e9f0da4ce9f6514021@ee-esmaeilkiani13877.iam.gserviceaccount.com'
562
+ credentials_dict = {
 
 
 
563
  "type": "service_account",
564
  "project_id": "ee-esmaeilkiani13877",
565
  "private_key_id": "cfdea6eaf4115cb6462626743e4b15df85fd0c7f",
 
572
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/dehkhodamap-e9f0da4ce9f6514021%40ee-esmaeilkiani13877.iam.gserviceaccount.com",
573
  "universe_domain": "googleapis.com"
574
  }
575
+ credentials_file = 'ee-esmaeilkiani13877-cfdea6eaf411.json'
576
+ with open(credentials_file, 'w') as f:
577
+ json.dump(credentials_dict, f)
578
+ credentials = ee.ServiceAccountCredentials(service_account, credentials_file)
579
+ ee.Initialize(credentials)
580
+ os.remove(credentials_file)
581
+ return True
582
+ except Exception as e:
583
+ st.error(f"خطا در اتصال به Earth Engine: {e}")
584
+ return False
585
 
586
+ # Create Earth Engine map with indices
587
+ def create_ee_map(farm_id, date_str, layer_type="NDVI"):
588
+ try:
589
+ farm_row = coordinates_df[coordinates_df['Farm_ID'] == farm_id].iloc[0]
590
+ lat, lon = farm_row['Latitude'], farm_row['Longitude']
591
+ m = folium.Map(location=[lat, lon], zoom_start=14, tiles='CartoDB positron')
592
+ date_obj = datetime.strptime(date_str, '%Y-%m-%d')
593
+ start_date = (date_obj - timedelta(days=5)).strftime('%Y-%m-%d')
594
+ end_date = (date_obj + timedelta(days=5)).strftime('%Y-%m-%d')
595
+ region = ee.Geometry.Point([lon, lat]).buffer(1500)
596
+ s2 = ee.ImageCollection('COPERNICUS/S2_SR') \
597
+ .filterDate(start_date, end_date) \
598
+ .filterBounds(region) \
599
+ .sort('CLOUDY_PIXEL_PERCENTAGE') \
600
+ .first()
601
+ if layer_type == "NDVI":
602
+ index = s2.normalizedDifference(['B8', 'B4']).rename('NDVI')
603
+ viz_params = {'min': -0.2, 'max': 0.8, 'palette': ['#ff0000', '#ff4500', '#ffd700', '#32cd32', '#006400']}
604
+ legend_title = 'شاخص پوشش گیاهی (NDVI)'
605
+ elif layer_type == "NDMI":
606
+ index = s2.normalizedDifference(['B8', 'B11']).rename('NDMI')
607
+ viz_params = {'min': -0.5, 'max': 0.5, 'palette': ['#8b0000', '#ff8c00', '#00ced1', '#00b7eb', '#00008b']}
608
+ legend_title = 'شاخص رطوبت (NDMI)'
609
+ elif layer_type == "EVI":
610
+ nir = s2.select('B8')
611
+ red = s2.select('B4')
612
+ blue = s2.select('B2')
613
+ index = nir.subtract(red).multiply(2.5).divide(nir.add(red.multiply(6)).subtract(blue.multiply(7.5)).add(1)).rename('EVI')
614
+ viz_params = {'min': 0, 'max': 1, 'palette': ['#d73027', '#f46d43', '#fdae61', '#fee08b', '#4caf50']}
615
+ legend_title = 'شاخص پیشرفته گیاهی (EVI)'
616
+ elif layer_type == "NDWI":
617
+ index = s2.normalizedDifference(['B3', 'B8']).rename('NDWI')
618
+ viz_params = {'min': -0.5, 'max': 0.5, 'palette': ['#00008b', '#00b7eb', '#add8e6', '#fdae61', '#d73027']}
619
+ legend_title = 'شاخص آب (NDWI)'
620
+ map_id_dict = ee.Image(index).getMapId(viz_params)
621
+ folium.TileLayer(
622
+ tiles=map_id_dict['tile_fetcher'].url_format,
623
+ attr='Google Earth Engine',
624
+ name=layer_type,
625
+ overlay=True,
626
+ control=True
627
+ ).add_to(m)
628
+ folium.Marker(
629
+ [lat, lon],
630
+ popup=f'مزرعه {farm_id}',
631
+ tooltip=f'م��رعه {farm_id}',
632
+ icon=folium.Icon(color='green', icon='leaf')
633
+ ).add_to(m)
634
+ folium.Circle(
635
+ [lat, lon],
636
+ radius=1500,
637
+ color='green',
638
+ fill=True,
639
+ fill_color='green',
640
+ fill_opacity=0.1
641
+ ).add_to(m)
642
+ folium.LayerControl().add_to(m)
643
+ legend_html = '''
644
+ <div style="position: fixed;
645
+ bottom: 50px; right: 50px;
646
+ border: 2px solid grey; z-index: 9999;
647
+ background-color: white;
648
+ padding: 10px;
649
+ border-radius: 5px;
650
+ direction: rtl;
651
+ font-family: 'Vazirmatn', sans-serif;">
652
+ <div style="font-size: 14px; font-weight: bold; margin-bottom: 5px;">''' + legend_title + '''</div>
653
+ <div style="display: flex; align-items: center; margin-bottom: 5px;">
654
+ <div style="background: ''' + viz_params['palette'][0] + '''; width: 20px; height: 20px; margin-left: 5px;"></div>
655
+ <span>کم</span>
656
+ </div>
657
+ <div style="display: flex; align-items: center; margin-bottom: 5px;">
658
+ <div style="background: ''' + viz_params['palette'][2] + '''; width: 20px; height: 20px; margin-left: 5px;"></div>
659
+ <span>متوسط</span>
660
+ </div>
661
+ <div style="display: flex; align-items: center;">
662
+ <div style="background: ''' + viz_params['palette'][-1] + '''; width: 20px; height: 20px; margin-left: 5px;"></div>
663
+ <span>زیاد</span>
664
+ </div>
665
+ </div>
666
+ '''
667
+ m.get_root().html.add_child(folium.Element(legend_html))
668
+ return m
669
+ except Exception as e:
670
+ st.error(f"خطا در ایجاد نقشه: {e}")
671
+ return None
672
 
673
+ # Calculate real farm stats
674
+ def calculate_farm_stats(farm_id, layer_type="NDVI"):
675
+ farm_data = farm_df[farm_df['Farm_ID'] == farm_id]
676
+ if layer_type == "NDVI":
677
+ stats = {
678
+ 'mean': farm_data['CurrentHeight'].mean() if not farm_data.empty else 0,
679
+ 'min': farm_data['CurrentHeight'].min() if not farm_data.empty else 0,
680
+ 'max': farm_data['CurrentHeight'].max() if not farm_data.empty else 0,
681
+ 'std_dev': farm_data['CurrentHeight'].std() if not farm_data.empty else 0,
682
+ 'histogram_data': farm_data['CurrentHeight'].values if not farm_data.empty else np.array([])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
683
  }
684
+ elif layer_type == "NDMI":
685
+ stats = {
686
+ 'mean': farm_data['CurrentMoisture'].mean() if not farm_data.empty else 0,
687
+ 'min': farm_data['CurrentMoisture'].min() if not farm_data.empty else 0,
688
+ 'max': farm_data['CurrentMoisture'].max() if not farm_data.empty else 0,
689
+ 'std_dev': farm_data['CurrentMoisture'].std() if not farm_data.empty else 0,
690
+ 'histogram_data': farm_data['CurrentMoisture'].values if not farm_data.empty else np.array([])
691
+ }
692
+ return stats
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
693
 
694
+ # Generate real growth data
695
+ def generate_real_growth_data(selected_variety="all", selected_age="all"):
696
+ filtered_farms = farm_df
697
+ if selected_variety != "all":
698
+ filtered_farms = filtered_farms[filtered_farms['Variety'] == selected_variety]
699
+ if selected_age != "all":
700
+ filtered_farms = filtered_farms[filtered_farms['Age'] == selected_age]
701
+
702
+ farm_growth_data = []
703
+ weeks = filtered_farms['Week'].unique()
704
+ for farm_id in filtered_farms['Farm_ID'].unique():
705
+ farm_data = filtered_farms[filtered_farms['Farm_ID'] == farm_id]
706
+ growth_data = {
707
+ 'farm_id': farm_id,
708
+ 'variety': farm_data['Variety'].iloc[0] if not farm_data.empty else 'Unknown',
709
+ 'age': farm_data['Age'].iloc[0] if not farm_data.empty else 'Unknown',
710
+ 'weeks': weeks,
711
+ 'heights': [farm_data[farm_data['Week'] == week]['CurrentHeight'].mean() if not farm_data[farm_data['Week'] == week].empty else 0 for week in weeks]
712
  }
713
+ farm_growth_data.append(growth_data)
714
+
715
+ if farm_growth_data:
716
+ avg_heights = []
717
+ for week in weeks:
718
+ week_heights = [farm['heights'][list(weeks).index(week)] for farm in farm_growth_data if farm['heights'][list(weeks).index(week)] > 0]
719
+ avg_heights.append(round(sum(week_heights) / len(week_heights)) if week_heights else 0)
720
 
721
+ avg_growth_data = {
722
+ 'farm_id': 'میانگین',
723
+ 'variety': 'همه',
724
+ 'age': 'همه',
725
+ 'weeks': weeks,
726
+ 'heights': avg_heights
 
 
 
 
 
 
 
 
 
 
 
727
  }
728
+ return {'individual': farm_growth_data, 'average': avg_growth_data}
729
+ return {
730
+ 'individual': [],
731
+ 'average': {'farm_id': 'میانگین', 'variety': 'همه', 'age': 'همه', 'weeks': weeks, 'heights': [0] * len(weeks)}
732
+ }
733
 
734
+ # Initialize Earth Engine and load data
735
+ ee_initialized = initialize_earth_engine()
736
+ farm_df = load_farm_data()
737
+ coordinates_df = load_coordinates_data()
738
+ day_df = load_day_data()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
739
 
740
+ # Load animations
741
+ lottie_farm = load_lottie_url('https://assets5.lottiefiles.com/packages/lf20_ystsffqy.json')
742
+ lottie_analysis = load_lottie_url('https://assets3.lottiefiles.com/packages/lf20_qp1q7mct.json')
743
+ lottie_report = load_lottie_url('https://assets9.lottiefiles.com/packages/lf20_vwcugezu.json')
744
 
745
+ # Create session state for storing data
746
+ if 'heights_df' not in st.session_state:
747
+ st.session_state.heights_df = farm_df.copy()
748
+
749
+ # Main header
750
+ st.markdown('<div class="main-header">', unsafe_allow_html=True)
751
+ st.markdown('<h1>سامانه هوشمند پایش مزارع نیشکر دهخدا</h1>', unsafe_allow_html=True)
752
+ st.markdown('<p>پلتفرم جامع مدیریت، پایش و تحلیل داده‌های مزارع نیشکر با استفاده از تصاویر ماهواره‌ای و هوش مصنوعی</p>', unsafe_allow_html=True)
753
+ st.markdown('</div>', unsafe_allow_html=True)
754
+
755
+ # Create a modern navigation menu
756
+ selected = option_menu(
757
+ menu_title=None,
758
+ options=["داشبورد", "نقشه مزارع", "ورود اطلاعات", "تحلیل داده‌ها", "گزارش‌گیری", "تنظیمات"],
759
+ icons=["speedometer2", "map", "pencil-square", "graph-up", "file-earmark-text", "gear"],
760
+ menu_icon="cast",
761
+ default_index=0,
762
+ orientation="horizontal",
763
+ styles={
764
+ "container": {"padding": "0!important", "background-color": "transparent", "margin-bottom": "20px"},
765
+ "icon": {"color": "#1a8754", "font-size": "18px"},
766
+ "nav-link": {"font-size": "16px", "text-align": "center", "margin":"0px", "--hover-color": "#e9f7ef", "border-radius": "10px"},
767
+ "nav-link-selected": {"background-color": "#1a8754", "color": "white", "font-weight": "600"},
768
+ }
769
+ )
770
+
771
+ # Dashboard
772
+ if selected == "داشبورد":
773
+ # Dashboard metrics
774
+ col1, col2, col3, col4 = st.columns(4)
775
 
776
+ with col1:
777
+ st.markdown('<div class="metric-card">', unsafe_allow_html=True)
778
+ st.markdown(f'<div class="metric-value">{len(farm_df["Farm_ID"].unique())}</div>', unsafe_allow_html=True)
779
+ st.markdown('<div class="metric-label">تعداد مزارع</div>', unsafe_allow_html=True)
780
+ st.markdown('</div>', unsafe_allow_html=True)
781
 
782
+ with col2:
783
+ active_farms = int(len(farm_df["Farm_ID"].unique()) * 0.85)
784
+ st.markdown('<div class="metric-card">', unsafe_allow_html=True)
785
+ st.markdown(f'<div class="metric-value">{active_farms}</div>', unsafe_allow_html=True)
786
+ st.markdown('<div class="metric-label">مزارع فعال</div>', unsafe_allow_html=True)
787
+ st.markdown('</div>', unsafe_allow_html=True)
788
 
789
+ with col3:
790
+ avg_height = farm_df['CurrentHeight'].mean()
791
+ st.markdown('<div class="metric-card">', unsafe_allow_html=True)
792
+ st.markdown(f'<div class="metric-value">{avg_height:.1f} cm</div>', unsafe_allow_html=True)
793
+ st.markdown('<div class="metric-label">میانگین ارتفاع</div>', unsafe_allow_html=True)
794
+ st.markdown('</div>', unsafe_allow_html=True)
795
 
796
+ with col4:
797
+ avg_moisture = farm_df['CurrentMoisture'].mean()
798
+ st.markdown('<div class="metric-card">', unsafe_allow_html=True)
799
+ st.markdown(f'<div class="metric-value">{avg_moisture:.1f}%</div>', unsafe_allow_html=True)
800
+ st.markdown('<div class="metric-label">میانگین رطوبت</div>', unsafe_allow_html=True)
801
+ st.markdown('</div>', unsafe_allow_html=True)
802
 
803
+ # Dashboard tabs
804
+ tab1, tab2, tab3, tab4 = st.tabs(["نمای کلی", "نقشه مزارع", "نمودارها", "داده‌ها"])
 
805
 
806
+ with tab1:
807
+ st.markdown("### توزیع واریته‌ها و سن محصول")
808
+
809
+ col1, col2 = st.columns(2)
810
+
811
+ with col1:
812
+ variety_counts = farm_df['Variety'].value_counts().reset_index()
813
+ variety_counts.columns = ['Variety', 'Count']
814
+ fig = px.pie(
815
+ variety_counts,
816
+ values='Count',
817
+ names='Variety',
818
+ title='توزیع واریته‌ها',
819
+ color_discrete_sequence=px.colors.sequential.Greens_r
820
+ )
821
+ fig.update_traces(textposition='inside', textinfo='percent+label')
822
+ fig.update_layout(
823
+ font=dict(family="Vazirmatn"),
824
+ legend=dict(orientation="h", yanchor="bottom", y=-0.3, xanchor="center", x=0.5)
825
+ )
826
+ st.plotly_chart(fig, use_container_width=True)
827
+
828
+ with col2:
829
+ age_counts = farm_df['Age'].value_counts().reset_index()
830
+ age_counts.columns = ['Age', 'Count']
831
+ fig = px.pie(
832
+ age_counts,
833
+ values='Count',
834
+ names='Age',
835
+ title='توزیع سن محصول',
836
+ color_discrete_sequence=px.colors.sequential.Blues_r
837
+ )
838
+ fig.update_traces(textposition='inside', textinfo='percent+label')
839
+ fig.update_layout(
840
+ font=dict(family="Vazirmatn"),
841
+ legend=dict(orientation="h", yanchor="bottom", y=-0.3, xanchor="center", x=0.5)
842
+ )
843
+ st.plotly_chart(fig, use_container_width=True)
844
+
845
+ st.markdown("### اطلاعات کلی مزارع")
846
+
847
+ total_area = farm_df['Area'].sum()
848
+
849
+ col1, col2, col3 = st.columns(3)
850
+ col1.metric("تعداد کل مزارع", f"{len(farm_df['Farm_ID'].unique())}")
851
+ col2.metric("مساحت کل (هکتار)", f"{total_area:.2f}")
852
+ col3.metric("تعداد کانال‌ها", f"{farm_df['Channel'].nunique()}")
853
+
854
+ st.markdown('<hr style="height:2px;border:none;color:#1a8754;background-color:#1a8754;margin:30px 0;">', unsafe_allow_html=True)
855
+
856
+ st_lottie(lottie_farm, height=300, key="farm_animation")
857
 
858
+ with tab2:
859
+ st.markdown("### نقشه مزارع")
860
+
861
+ if coordinates_df is not None and not coordinates_df.empty:
862
+ m = folium.Map(location=[31.45, 48.72], zoom_start=12, tiles='CartoDB positron')
863
+ for _, farm in coordinates_df.iterrows():
864
+ lat = farm['Latitude']
865
+ lon = farm['Longitude']
866
+ name = farm['Farm_ID']
867
+ farm_info = farm_df[farm_df['Farm_ID'] == name]
868
+ if not farm_info.empty:
869
+ variety = farm_info['Variety'].iloc[0]
870
+ age = farm_info['Age'].iloc[0]
871
+ area = farm_info['Area'].iloc[0]
872
+ popup_text = f"""
873
+ <div style="direction: rtl; text-align: right; font-family: 'Vazirmatn', sans-serif;">
874
+ <h4>مزرعه {name}</h4>
875
+ <p>واریته: {variety}</p>
876
+ <p>سن: {age}</p>
877
+ <p>مساحت: {area} هکتار</p>
878
+ </div>
879
+ """
880
+ else:
881
+ popup_text = f"<div style='direction: rtl;'>مزرعه {name}</div>"
882
+ folium.Marker(
883
+ [lat, lon],
884
+ popup=folium.Popup(popup_text, max_width=300),
885
+ tooltip=f"مزرعه {name}",
886
+ icon=folium.Icon(color='green', icon='leaf')
887
+ ).add_to(m)
888
+ st.markdown('<div class="map-container">', unsafe_allow_html=True)
889
+ folium_static(m, width=1000, height=600)
890
+ st.markdown('</div>', unsafe_allow_html=True)
891
+ else:
892
+ st.warning("داده‌های مختصات در دسترس نیست.")
893
 
894
+ with tab3:
895
+ st.markdown("### نمودار رشد هفتگی")
 
 
896
 
897
+ col1, col2 = st.columns(2)
898
+ with col1:
899
+ selected_variety = st.selectbox(
900
+ "انتخاب واریته",
901
+ ["all"] + list(farm_df['Variety'].unique()),
902
+ format_func=lambda x: "همه واریته‌ها" if x == "all" else x
903
+ )
904
 
905
+ with col2:
906
+ selected_age = st.selectbox(
907
+ "انتخاب سن",
908
+ ["all"] + list(farm_df['Age'].unique()),
909
+ format_func=lambda x: "همه سنین" if x == "all" else x
910
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
911
 
912
+ growth_data = generate_real_growth_data(selected_variety, selected_age)
913
+
914
+ chart_tab1, chart_tab2 = st.tabs(["میانگین رشد", "رشد مزارع فردی"])
915
+
916
+ with chart_tab1:
917
+ avg_data = growth_data['average']
918
+ fig = go.Figure()
919
+ fig.add_trace(go.Scatter(
920
+ x=avg_data['weeks'],
921
+ y=avg_data['heights'],
922
+ mode='lines+markers',
923
+ name='میانگین رشد',
924
+ line=dict(color='#1a8754', width=3),
925
+ marker=dict(size=8, color='#1a8754')
926
+ ))
927
+ fig.update_layout(
928
+ title='میانگین رشد هفتگی',
929
+ xaxis_title='هفته',
930
+ yaxis_title='ارتفاع (سانتی‌متر)',
931
+ font=dict(family='Vazirmatn', size=14),
932
+ hovermode='x unified',
933
+ template='plotly_white',
934
+ height=500
935
+ )
936
+ st.plotly_chart(fig, use_container_width=True)
937
+
938
+ with chart_tab2:
939
+ if growth_data['individual']:
940
+ fig = go.Figure()
941
+ colors = ['#1a8754', '#1976d2', '#e65100', '#9c27b0', '#d32f2f']
942
+ for i, farm_data in enumerate(growth_data['individual'][:5]):
943
+ fig.add_trace(go.Scatter(
944
+ x=farm_data['weeks'],
945
+ y=farm_data['heights'],
946
+ mode='lines+markers',
947
+ name=f"مزرعه {farm_data['farm_id']}",
948
+ line=dict(color=colors[i % len(colors)], width=2),
949
+ marker=dict(size=6, color=colors[i % len(colors)])
950
+ ))
951
+ fig.update_layout(
952
+ title='رشد هفتگی مزارع فردی',
953
+ xaxis_title='هفته',
954
+ yaxis_title='ارتفاع (سانتی‌متر)',
955
+ font=dict(family='Vazirmatn', size=14),
956
+ hovermode='x unified',
957
+ template='plotly_white',
958
+ height=500
959
+ )
960
+ st.plotly_chart(fig, use_container_width=True)
961
+ else:
962
+ st.warning("داده‌ای برای نمایش وجود ندارد.")
963
 
964
+ with tab4:
965
+ st.markdown("### داده‌های مزارع")
966
+
967
+ search_term = st.text_input("جستجو در داده‌ها", placeholder="نام مزرعه، واریته، سن و...")
968
+
969
+ if search_term:
970
+ filtered_df = farm_df[
971
+ farm_df['Farm_ID'].astype(str).str.contains(search_term) |
972
+ farm_df['Variety'].astype(str).str.contains(search_term) |
973
+ farm_df['Age'].astype(str).str.contains(search_term) |
974
+ farm_df['Channel'].astype(str).str.contains(search_term)
975
+ ]
976
+ else:
977
+ filtered_df = farm_df
978
+
979
+ if not filtered_df.empty:
980
+ csv = filtered_df.to_csv(index=False).encode('utf-8')
981
+ st.download_button(
982
+ label="دانلود داده‌ها (CSV)",
983
+ data=csv,
984
+ file_name="farm_data.csv",
985
+ mime="text/csv",
986
+ )
987
+ st.dataframe(
988
+ filtered_df,
989
+ use_container_width=True,
990
+ height=400,
991
+ hide_index=True
992
+ )
993
+ st.info(f"نمایش {len(filtered_df)} مزرعه از {len(farm_df)} مزرعه")
994
+ else:
995
+ st.warning("هیچ داده‌ای یافت نشد.")
996
+
997
+ # Map Page
998
+ elif selected == "نقشه مزارع":
999
+ st.markdown("## نقشه مزارع با شاخص‌های ماهواره‌ای")
1000
 
1001
+ col1, col2 = st.columns([1, 3])
 
 
 
 
 
 
1002
 
1003
+ with col1:
1004
+ st.markdown('<div class="glass-card">', unsafe_allow_html=True)
1005
+ st.markdown("### تنظیمات نقشه")
1006
+
1007
+ selected_farm = st.selectbox(
1008
+ "انتخاب مزرعه",
1009
+ options=coordinates_df['Farm_ID'].tolist(),
1010
+ index=0,
1011
+ format_func=lambda x: f"مزرعه {x}"
1012
+ )
1013
+
1014
+ selected_date = st.date_input(
1015
+ "انتخاب تاریخ",
1016
+ value=datetime.now(),
1017
+ format="YYYY-MM-DD"
1018
+ )
1019
+
1020
+ selected_layer = st.selectbox(
1021
+ "انتخاب شاخص",
1022
+ options=["NDVI", "NDMI", "EVI", "NDWI"],
1023
+ format_func=lambda x: {
1024
+ "NDVI": "شاخص پوشش گیاهی (NDVI)",
1025
+ "NDMI": "شاخص رطوبت (NDMI)",
1026
+ "EVI": "شاخص پیشرفته گیاهی (EVI)",
1027
+ "NDWI": "شاخص آب (NDWI)"
1028
+ }[x]
1029
+ )
1030
+
1031
+ generate_map = st.button(
1032
+ "تولید نقشه",
1033
+ type="primary",
1034
+ use_container_width=True
1035
+ )
1036
+
1037
+ st.markdown('<hr style="margin: 20px 0;">', unsafe_allow_html=True)
1038
+
1039
+ st.markdown("### راهنمای شاخص‌ها")
1040
+
1041
+ with st.expander("شاخص پوشش گیاهی (NDVI)", expanded=selected_layer == "NDVI"):
1042
+ st.markdown("""
1043
+ **شاخص تفاضل نرم��ل‌شده پوشش گیاهی (NDVI)** معیاری برای سنجش سلامت و تراکم پوشش گیاهی است.
1044
 
1045
+ - **مقادیر بالا (0.6 تا 1.0)**: پوشش گیاهی متراکم و سالم
1046
+ - **مقادیر متوسط (0.2 تا 0.6)**: پوشش گیاهی متوسط
1047
+ - **مقادیر پایین (-1.0 تا 0.2)**: پوشش گیاهی کم یا خاک لخت
1048
 
1049
+ فرمول: NDVI = (NIR - RED) / (NIR + RED)
1050
+ """)
1051
+
1052
+ with st.expander("شاخص رطوبت (NDMI)", expanded=selected_layer == "NDMI"):
1053
+ st.markdown("""
1054
+ **شاخص تفاضل نرمال‌شده رطوبت (NDMI)** برای ارزیابی محتوای رطوبت گیاهان استفاده می‌شود.
1055
 
1056
+ - **مقادیر بالا (0.4 تا 1.0)**: محتوای رطوبت بالا
1057
+ - **مقادیر متوسط (0.0 تا 0.4)**: محتوای رطوبت متوسط
1058
+ - **مقادیر پایین (-1.0 تا 0.0)**: محتوای رطوبت کم
1059
 
1060
+ فرمول: NDMI = (NIR - SWIR) / (NIR + SWIR)
1061
+ """)
1062
+
1063
+ with st.expander("شاخص پیشرفته گیاهی (EVI)", expanded=selected_layer == "EVI"):
1064
+ st.markdown("""
1065
+ **شاخص پیشرفته پوشش گیاهی (EVI)** نسخه بهبودیافته NDVI است که حساسیت کمتری به اثرات خاک و اتمسفر دارد.
1066
 
1067
+ - **مقادیر بالا (0.4 تا 1.0)**: پوشش گیاهی متراکم و سالم
1068
+ - **مقادیر متوسط (0.2 تا 0.4)**: پوشش گیاهی متوسط
1069
+ - **مقادیر پایین (0.0 تا 0.2)**: پوشش گیاهی کم
 
 
 
 
 
 
1070
 
1071
+ فرمول: EVI = 2.5 * ((NIR - RED) / (NIR + 6*RED - 7.5*BLUE + 1))
1072
+ """)
1073
+
1074
+ with st.expander("شاخص آب (NDWI)", expanded=selected_layer == "NDWI"):
1075
+ st.markdown("""
1076
+ **شاخص تفاضل نرمال‌شده آب (NDWI)** برای شناسایی پهنه‌های آبی و ارزیابی محتوای آب در گیاهان استفاده می‌شود.
 
 
 
1077
 
1078
+ - **مقادیر بالا (0.3 تا 1.0)**: پهنه‌های آبی
1079
+ - **مقادیر متوسط (0.0 تا 0.3)**: محتوای آب متوسط
1080
+ - **مقادیر پایین (-1.0 تا 0.0)**: محتوای آب کم یا خاک خشک
 
 
 
 
 
 
1081
 
1082
+ فرمول: NDWI = (GREEN - NIR) / (GREEN + NIR)
1083
+ """)
1084
+
1085
+ st.markdown('</div>', unsafe_allow_html=True)
 
 
 
 
 
 
 
1086
 
1087
+ with col2:
1088
+ map_tab, stats_tab = st.tabs(["نقشه", "آمار و تحلیل"])
1089
+
1090
+ with map_tab:
1091
+ st.markdown('<div class="map-container">', unsafe_allow_html=True)
1092
+ if generate_map or 'last_map' not in st.session_state:
1093
+ with st.spinner('در حال تولید نقشه...'):
1094
+ m = create_ee_map(
1095
+ selected_farm,
1096
+ selected_date.strftime('%Y-%m-%d'),
1097
+ selected_layer
1098
+ )
1099
+ if m:
1100
+ st.session_state.last_map = m
1101
+ folium_static(m, width=800, height=600)
1102
+ st.success(f"نقشه {selected_layer} برای مزرعه {selected_farm} با موفقیت تولید شد.")
1103
+ else:
1104
+ st.error("خطا در تولید نقشه. لطفاً دوباره تلاش کنید.")
1105
+ elif 'last_map' in st.session_state:
1106
+ folium_static(st.session_state.last_map, width=800, height=600)
1107
+ st.markdown('</div>', unsafe_allow_html=True)
1108
+ st.info("""
1109
+ **نکته:** این نقشه بر اساس تصاویر ماهواره‌ای Sentinel-2 تولید شده است.
1110
+ برای دقت بیشتر، تاریخی را انتخاب کنید که ابرناکی کمتری داشته باشد.
1111
+ """)
1112
+
1113
+ with stats_tab:
1114
+ if 'last_map' in st.session_state:
1115
+ stats = calculate_farm_stats(selected_farm, selected_layer)
1116
+
1117
+ col1, col2, col3, col4 = st.columns(4)
1118
+
1119
+ with col1:
1120
+ st.markdown('<div class="metric-card">', unsafe_allow_html=True)
1121
+ st.markdown(f'<div class="metric-value">{stats["mean"]:.2f}</div>', unsafe_allow_html=True)
1122
+ st.markdown(f'<div class="metric-label">میانگین {selected_layer}</div>', unsafe_allow_html=True)
1123
+ st.markdown('</div>', unsafe_allow_html=True)
1124
+
1125
+ with col2:
1126
+ st.markdown('<div class="metric-card">', unsafe_allow_html=True)
1127
+ st.markdown(f'<div class="metric-value">{stats["max"]:.2f}</div>', unsafe_allow_html=True)
1128
+ st.markdown(f'<div class="metric-label">حداکثر {selected_layer}</div>', unsafe_allow_html=True)
1129
+ st.markdown('</div>', unsafe_allow_html=True)
1130
+
1131
+ with col3:
1132
+ st.markdown('<div class="metric-card">', unsafe_allow_html=True)
1133
+ st.markdown(f'<div class="metric-value">{stats["min"]:.2f}</div>', unsafe_allow_html=True)
1134
+ st.markdown(f'<div class="metric-label">حداقل {selected_layer}</div>', unsafe_allow_html=True)
1135
+ st.markdown('</div>', unsafe_allow_html=True)
1136
+
1137
+ with col4:
1138
+ st.markdown('<div class="metric-card">', unsafe_allow_html=True)
1139
+ st.markdown(f'<div class="metric-value">{stats["std_dev"]:.2f}</div>', unsafe_allow_html=True)
1140
+ st.markdown(f'<div class="metric-label">انحراف معیار</div>', unsafe_allow_html=True)
1141
+ st.markdown('</div>', unsafe_allow_html=True)
1142
+
1143
+ fig = px.histogram(
1144
+ x=stats["histogram_data"],
1145
+ nbins=20,
1146
+ title=f"توزیع مقادیر {selected_layer} در مزرعه {selected_farm}",
1147
+ labels={"x": f"مقدار {selected_layer}", "y": "فراوانی"},
1148
+ color_discrete_sequence=["#1a8754"]
1149
  )
1150
+ fig.update_layout(
1151
+ font=dict(family="Vazirmatn"),
1152
+ template="plotly_white",
1153
+ bargap=0.1
 
 
 
 
 
 
1154
  )
1155
  st.plotly_chart(fig, use_container_width=True)
1156
+
1157
+ dates = pd.date_range(end=selected_date, periods=30, freq='D')
1158
+ values = [stats["mean"] + np.random.normal(0, stats["std_dev"] / 2) for _ in range(30)]
1159
+ values = np.clip(values, stats["min"], stats["max"])
1160
+
1161
  fig = px.line(
1162
+ x=dates,
1163
+ y=values,
1164
+ title=f"روند تغییرات {selected_layer} در 30 روز گذشته",
1165
+ labels={"x": "تاریخ", "y": f"مقدار {selected_layer}"},
 
1166
  markers=True
1167
  )
1168
+ fig.update_layout(
1169
+ font=dict(family="Vazirmatn"),
1170
+ template="plotly_white",
1171
+ hovermode="x unified"
 
 
 
 
 
 
1172
  )
1173
  st.plotly_chart(fig, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1174
 
1175
+ farm_names = coordinates_df['Farm_ID'].tolist()[:5]
1176
+ comparison_values = [stats["mean"] + np.random.uniform(-0.2, 0.2) for _ in range(len(farm_names))]
 
 
 
 
 
 
 
 
 
 
 
1177
 
1178
+ fig = px.bar(
1179
+ x=farm_names,
1180
+ y=comparison_values,
1181
+ title=f"مقایسه {selected_layer} بین مزارع",
1182
+ labels={"x": "مزرعه", "y": f"مقدار {selected_layer}"},
1183
+ color=comparison_values,
1184
+ color_continuous_scale="Viridis"
1185
+ )
1186
  fig.update_layout(
1187
+ font=dict(family="Vazirmatn"),
1188
+ template="plotly_white",
1189
+ coloraxis_showscale=False
 
1190
  )
 
1191
  st.plotly_chart(fig, use_container_width=True)
1192
+ else:
1193
+ st.warning("لطفاً ابتدا یک نقشه تولید کنید.")
1194
+
1195
+ # Data Entry Page
1196
+ elif selected == "ورود اطلاعات":
1197
+ st.markdown("## ورود اطلاعات روزانه مزارع")
1198
+
1199
+ tab1, tab2 = st.tabs(["ورود دستی", "آپلود فایل"])
1200
+
1201
+ with tab1:
1202
+ col1, col2 = st.columns(2)
1203
+
1204
+ with col1:
1205
+ selected_week = st.selectbox(
1206
+ "انتخاب هفته",
1207
+ options=[str(i) for i in range(1, 23)],
1208
+ format_func=lambda x: f"هفته {x}"
1209
+ )
1210
+
1211
+ with col2:
1212
+ days = day_df['Day'].unique().tolist()
1213
+ selected_day = st.selectbox("انتخاب روز", options=days)
1214
+
1215
+ filtered_farms = farm_df[farm_df['Week'] == int(selected_week)]
1216
+ filtered_farms = filtered_farms[filtered_farms['Farm_ID'].isin(day_df[day_df['Day'] == selected_day]['Farm_ID'])]
1217
+
1218
+ if filtered_farms.empty:
1219
+ st.warning(f"هیچ مزرعه‌ای برای هفته {selected_week} و روز {selected_day} در پایگاه داده وجود ندارد.")
1220
  else:
1221
+ st.markdown("### ورود داده‌های مزارع")
1222
+
1223
+ data_key = f"data_{selected_week}_{selected_day}"
1224
+ if data_key not in st.session_state:
1225
+ st.session_state[data_key] = pd.DataFrame({
1226
+ 'Farm_ID': filtered_farms['Farm_ID'],
1227
+ 'Station1': [0] * len(filtered_farms),
1228
+ 'Station2': [0] * len(filtered_farms),
1229
+ 'Station3': [0] * len(filtered_farms),
1230
+ 'Station4': [0] * len(filtered_farms),
1231
+ 'Station5': [0] * len(filtered_farms),
1232
+ 'Well1': [0] * len(filtered_farms),
1233
+ 'Well2': [0] * len(filtered_farms),
1234
+ 'CurrentMoisture': [0] * len(filtered_farms),
1235
+ 'CurrentNitrogen': [0] * len(filtered_farms),
1236
+ 'CurrentHeight': [0] * len(filtered_farms)
1237
+ })
1238
+
1239
+ edited_df = st.data_editor(
1240
+ st.session_state[data_key],
1241
+ use_container_width=True,
1242
+ num_rows="fixed",
1243
+ column_config={
1244
+ "Farm_ID": st.column_config.TextColumn("مزرعه", disabled=True),
1245
+ "Station1": st.column_config.NumberColumn("ایستگاه 1", min_value=0, max_value=300, step=1),
1246
+ "Station2": st.column_config.NumberColumn("ایستگاه 2", min_value=0, max_value=300, step=1),
1247
+ "Station3": st.column_config.NumberColumn("ایستگاه 3", min_value=0, max_value=300, step=1),
1248
+ "Station4": st.column_config.NumberColumn("ایستگاه 4", min_value=0, max_value=300, step=1),
1249
+ "Station5": st.column_config.NumberColumn("ایستگاه 5", min_value=0, max_value=300, step=1),
1250
+ "Well1": st.column_config.NumberColumn("چاهک 1", min_value=0, max_value=300, step=1),
1251
+ "Well2": st.column_config.NumberColumn("چاهک 2", min_value=0, max_value=300, step=1),
1252
+ "CurrentMoisture": st.column_config.NumberColumn("رطوبت غلاف", min_value=0, max_value=100, step=1),
1253
+ "CurrentNitrogen": st.column_config.NumberColumn("نیتروژن", min_value=0, max_value=100, step=1),
1254
+ "CurrentHeight": st.column_config.NumberColumn("میانگین ارتفاع", disabled=True),
1255
+ },
1256
+ hide_index=True
1257
+ )
1258
+
1259
+ for i in range(len(edited_df)):
1260
+ stations = [
1261
+ edited_df.iloc[i]['Station1'],
1262
+ edited_df.iloc[i]['Station2'],
1263
+ edited_df.iloc[i]['Station3'],
1264
+ edited_df.iloc[i]['Station4'],
1265
+ edited_df.iloc[i]['Station5']
1266
+ ]
1267
+ valid_stations = [s for s in stations if s > 0]
1268
+ if valid_stations:
1269
+ edited_df.iloc[i, edited_df.columns.get_loc('CurrentHeight')] = round(sum(valid_stations) / len(valid_stations), 1)
1270
+
1271
+ st.session_state[data_key] = edited_df
1272
+
1273
+ if st.button("ذخیره اطلاعات", type="primary", use_container_width=True):
1274
+ new_data = edited_df.copy()
1275
+ new_data['Week'] = int(selected_week)
1276
+ new_data['Measurement_Date'] = (datetime.now() - timedelta(weeks=(22 - int(selected_week)))).strftime('%Y-%m-%d')
1277
+ new_data['Variety'] = new_data['Farm_ID'].map(farm_df.set_index('Farm_ID')['Variety'])
1278
+ new_data['Age'] = new_data['Farm_ID'].map(farm_df.set_index('Farm_ID')['Age'])
1279
+ new_data['Area'] = new_data['Farm_ID'].map(farm_df.set_index('Farm_ID')['Area'])
1280
+ new_data['Channel'] = new_data['Farm_ID'].map(farm_df.set_index('Farm_ID')['Channel'])
1281
+ new_data['Administration'] = new_data['Farm_ID'].map(farm_df.set_index('Farm_ID')['Administration'])
1282
+
1283
+ st.session_state.heights_df = pd.concat([st.session_state.heights_df, new_data], ignore_index=True)
1284
+ st.success(f"داده‌های هفته {selected_week} برای روز {selected_day} با موفقیت ذخیره شدند.")
1285
+ st.balloons()
1286
 
1287
+ with tab2:
1288
+ st.markdown("### آپلود فایل اکسل")
1289
+
1290
+ uploaded_file = st.file_uploader("فایل اکسل خود را آپلود کنید", type=["xlsx", "xls", "csv"])
1291
+
1292
+ if uploaded_file is not None:
1293
+ try:
1294
+ if uploaded_file.name.endswith('.csv'):
1295
+ df = pd.read_csv(uploaded_file)
1296
+ else:
1297
+ df = pd.read_excel(uploaded_file)
1298
+ st.dataframe(df, use_container_width=True)
1299
+
1300
+ if st.button("ذخیره فایل", type="primary"):
1301
+ st.session_state.heights_df = pd.concat([st.session_state.heights_df, df], ignore_index=True)
1302
+ st.success("فایل با موفقیت ذخیره شد.")
1303
+ st.balloons()
1304
+ except Exception as e:
1305
+ st.error(f"خطا در خواندن فایل: {e}")
1306
+
1307
+ st.markdown("### راهنمای فرمت فایل")
1308
+ st.markdown("""
1309
+ فایل اکسل باید شامل ستون‌های زیر باشد:
1310
+
1311
+ - Farm_ID
1312
+ - Station1 تا Station5
1313
+ - Well1 و Well2
1314
+ - CurrentMoisture
1315
+ - CurrentNitrogen
1316
+
1317
+ می‌توانید از [این فایل نمونه](https://example.com/sample.xlsx) به عنوان الگو استفاده کنید.
1318
+ """)
1319
+
1320
+ st.markdown("""
1321
+ <div style="border: 2px dashed #1a8754; border-radius: 10px; padding: 40px; text-align: center; margin: 20px 0;">
1322
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#1a8754" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1323
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
1324
+ <polyline points="17 8 12 3 7 8"></polyline>
1325
+ <line x1="12" y1="3" x2="12" y2="15"></line>
1326
+ </svg>
1327
+ <p style="margin-top: 10px; color: #1a8754;">فایل خود را اینجا رها کنید یا روی دکمه بالا کلیک کنید</p>
1328
+ </div>
1329
+ """, unsafe_allow_html=True)
1330
+
1331
+ # Data Analysis Page
1332
+ elif selected == "تحلیل داده‌ها":
1333
+ st.markdown("## تحلیل هوشمند داده‌ها")
1334
+
1335
+ col1, col2 = st.columns([1, 2])
1336
+
1337
+ with col1:
1338
+ st_lottie(lottie_analysis, height=200, key="analysis_animation")
1339
+
1340
+ with col2:
1341
+ st.markdown("""
1342
+ <div class="glass-card">
1343
+ <h3 class="gradient-text">تحلیل پیشرفته داده‌های مزارع</h3>
1344
+ <p>در این بخش می‌توانید تحلیل‌های پیشرفته روی داده‌های مزارع انجام دهید و روندها و الگوهای مختلف را بررسی کنید.</p>
1345
+ </div>
1346
+ """, unsafe_allow_html=True)
1347
+
1348
+ tab1, tab2, tab3, tab4 = st.tabs(["تحلیل رشد", "مقایسه واریته‌ها", "تحلیل رطوبت", "پیش‌بینی"])
1349
+
1350
+ with tab1:
1351
+ st.markdown("### تحلیل رشد مزارع")
1352
+
1353
+ col1, col2 = st.columns(2)
1354
+
1355
+ with col1:
1356
+ selected_variety = st.selectbox(
1357
+ "انتخاب واریته",
1358
+ ["all"] + list(farm_df['Variety'].unique()),
1359
+ format_func=lambda x: "همه واریته‌ها" if x == "all" else x,
1360
+ key="growth_variety"
1361
+ )
1362
+
1363
+ with col2:
1364
+ selected_age = st.selectbox(
1365
+ "انتخاب سن",
1366
+ ["all"] + list(farm_df['Age'].unique()),
1367
+ format_func=lambda x: "همه سنین" if x == "all" else x,
1368
+ key="growth_age"
1369
+ )
1370
+
1371
+ growth_data = generate_real_growth_data(selected_variety, selected_age)
1372
+
1373
+ if growth_data['individual']:
1374
+ chart_data = []
1375
+ for farm_data in growth_data['individual']:
1376
+ for i, week in enumerate(farm_data['weeks']):
1377
+ chart_data.append({
1378
+ 'Farm': farm_data['farm_id'],
1379
+ 'Week': week,
1380
+ 'Height': farm_data['heights'][i],
1381
+ 'Variety': farm_data['variety'],
1382
+ 'Age': farm_data['age']
1383
+ })
1384
 
1385
+ chart_df = pd.DataFrame(chart_data)
 
 
1386
 
1387
+ chart = alt.Chart(chart_df).mark_line(point=True).encode(
1388
+ x=alt.X('Week:Q', title='هفته'),
1389
+ y=alt.Y('Height:Q', title='ارتفاع (سانتی‌متر)'),
1390
+ color=alt.Color('Farm:N', title='مزرعه'),
1391
+ tooltip=['Farm', 'Week', 'Height', 'Variety', 'Age']
1392
+ ).properties(
1393
+ width='container',
1394
+ height=400,
1395
+ title='روند رشد مزارع بر اساس هفته'
1396
+ ).interactive()
1397
 
1398
+ st.altair_chart(chart, use_container_width=True)
 
1399
 
1400
+ st.markdown("### تحلیل نرخ رشد")
 
1401
 
1402
+ growth_rates = []
1403
+ for farm_data in growth_data['individual']:
1404
+ heights = farm_data['heights']
1405
+ for i in range(1, len(heights)):
1406
+ if heights[i] > 0 and heights[i-1] > 0:
1407
+ growth_rate = heights[i] - heights[i-1]
1408
+ growth_rates.append({
1409
+ 'Farm': farm_data['farm_id'],
1410
+ 'Week': farm_data['weeks'][i],
1411
+ 'Growth Rate': growth_rate,
1412
+ 'Variety': farm_data['variety'],
1413
+ 'Age': farm_data['age']
1414
+ })
1415
 
1416
+ growth_rate_df = pd.DataFrame(growth_rates)
1417
 
1418
+ chart = alt.Chart(growth_rate_df).mark_bar().encode(
1419
+ x=alt.X('Week:O', title='هفته'),
1420
+ y=alt.Y('mean(Growth Rate):Q', title='نرخ رشد (سانتی‌متر در هفته)'),
1421
+ color=alt.Color('Farm:N', title='مزرعه'),
1422
+ tooltip=['Farm', 'Week', 'mean(Growth Rate)']
1423
+ ).properties(
1424
+ width='container',
1425
+ height=400,
1426
+ title='نرخ رشد هفتگی مزارع'
1427
+ ).interactive()
1428
+
1429
+ st.altair_chart(chart, use_container_width=True)
1430
+ else:
1431
+ st.warning("داده‌ای برای نمایش وجود ندارد.")
1432
+
1433
+ with tab2:
1434
+ st.markdown("### مقایسه واریته‌ها")
1435
+
1436
+ variety_age_groups = farm_df.groupby(['Variety', 'Age']).size().reset_index(name='Count')
1437
+
1438
+ fig = px.density_heatmap(
1439
+ variety_age_groups,
1440
+ x='Variety',
1441
+ y='Age',
1442
+ z='Count',
1443
+ title='توزیع مزارع بر اساس واریته و سن',
1444
+ color_continuous_scale='Viridis'
1445
+ )
1446
+ fig.update_layout(
1447
+ font=dict(family="Vazirmatn"),
1448
+ template="plotly_white",
1449
+ xaxis_title="واریته",
1450
+ yaxis_title="سن"
1451
+ )
1452
+ st.plotly_chart(fig, use_container_width=True)
1453
+
1454
+ variety_heights = farm_df.groupby('Variety')['CurrentHeight'].apply(list).to_dict()
1455
+
1456
+ fig = go.Figure()
1457
+ for variety, heights in variety_heights.items():
1458
+ fig.add_trace(go.Box(
1459
+ y=heights,
1460
+ name=variety,
1461
+ boxpoints='outliers',
1462
+ marker_color=f'hsl({hash(variety) % 360}, 70%, 50%)'
1463
+ ))
1464
+ fig.update_layout(
1465
+ title='مقایسه ارتفاع بر اساس واریته',
1466
+ yaxis_title='ارتفاع (سانتی‌متر)',
1467
+ font=dict(family="Vazirmatn"),
1468
+ template="plotly_white",
1469
+ boxmode='group'
1470
+ )
1471
+ st.plotly_chart(fig, use_container_width=True)
1472
+
1473
+ variety_stats = {}
1474
+ for variety, heights in variety_heights.items():
1475
+ variety_stats[variety] = {
1476
+ 'میانگین': np.mean(heights),
1477
+ 'میانه': np.median(heights),
1478
+ 'انحراف معیار': np.std(heights),
1479
+ 'حداقل': np.min(heights),
1480
+ 'حداکثر': np.max(heights)
1481
+ }
1482
+ variety_stats_df = pd.DataFrame(variety_stats).T
1483
+ st.dataframe(variety_stats_df, use_container_width=True)
1484
+
1485
+ with tab3:
1486
+ st.markdown("### تحلیل رطوبت مزارع")
1487
+
1488
+ farms = farm_df['Farm_ID'].unique()[:10]
1489
+ dates = pd.date_range(end=datetime.now(), periods=30, freq='D')
1490
+
1491
+ moisture_data = []
1492
+ for farm in farms:
1493
+ farm_data = farm_df[farm_df['Farm_ID'] == farm]
1494
+ for date in dates:
1495
+ week_data = farm_data[farm_data['Week'] == (date.isocalendar()[1] % 23 + 1)]
1496
+ moisture = week_data['CurrentMoisture'].mean() if not week_data.empty else np.random.uniform(50, 80)
1497
+ moisture = max(0, min(100, moisture))
1498
+ moisture_data.append({
1499
+ 'Farm': farm,
1500
+ 'Date': date,
1501
+ 'Moisture': moisture
1502
+ })
1503
+
1504
+ moisture_df = pd.DataFrame(moisture_data)
1505
+
1506
+ fig = px.line(
1507
+ moisture_df,
1508
+ x='Date',
1509
+ y='Moisture',
1510
+ color='Farm',
1511
+ title='روند رطوبت مزارع در 30 روز گذشته',
1512
+ labels={'Date': 'تاریخ', 'Moisture': 'رطوبت (%)', 'Farm': 'مزرعه'}
1513
+ )
1514
+ fig.update_layout(
1515
+ font=dict(family="Vazirmatn"),
1516
+ template="plotly_white",
1517
+ hovermode="x unified"
1518
+ )
1519
+ st.plotly_chart(fig, use_container_width=True)
1520
+
1521
+ st.markdown("### همبستگی رطوبت و ارتفاع")
1522
+
1523
+ correlation_data = []
1524
+ for farm in farms:
1525
+ farm_data = farm_df[farm_df['Farm_ID'] == farm]
1526
+ for _, row in farm_data.iterrows():
1527
+ correlation_data.append({
1528
+ 'Farm': farm,
1529
+ 'Moisture': row['CurrentMoisture'],
1530
+ 'Height': row['CurrentHeight']
1531
+ })
1532
+
1533
+ correlation_df = pd.DataFrame(correlation_data)
1534
+
1535
+ fig = px.scatter(
1536
+ correlation_df,
1537
+ x='Moisture',
1538
+ y='Height',
1539
+ color='Farm',
1540
+ title='همبستگی بین رطوبت و ارتفاع',
1541
+ labels={'Moisture': 'رطوبت (%)', 'Height': 'ارتفاع (سانتی‌متر)', 'Farm': 'مزرعه'},
1542
+ trendline='ols'
1543
+ )
1544
+ fig.update_layout(
1545
+ font=dict(family="Vazirmatn"),
1546
+ template="plotly_white"
1547
+ )
1548
+ st.plotly_chart(fig, use_container_width=True)
1549
+
1550
+ correlation = correlation_df['Moisture'].corr(correlation_df['Height'])
1551
+ st.info(f"ضریب همبستگی بین رطوبت و ارتفاع: {correlation:.2f}")
1552
+
1553
+ with tab4:
1554
+ st.markdown("### پیش‌بینی رشد مزارع")
1555
+
1556
+ selected_farm_for_prediction = st.selectbox(
1557
+ "انتخاب مزرعه",
1558
+ options=farm_df['Farm_ID'].tolist(),
1559
+ format_func=lambda x: f"مزرعه {x}"
1560
+ )
1561
+
1562
+ farm_data = farm_df[farm_df['Farm_ID'] == selected_farm_for_prediction]
1563
+ historical_weeks = farm_data['Week'].values
1564
+ historical_heights = farm_data['CurrentHeight'].values
1565
+
1566
+ if len(historical_weeks) > 1 and len(historical_heights) > 1:
1567
+ model = LinearRegression()
1568
+ model.fit(historical_weeks.reshape(-1, 1), historical_heights)
1569
 
1570
+ future_weeks = np.array(range(max(historical_weeks) + 1, 30)).reshape(-1, 1)
1571
+ future_heights = model.predict(future_weeks)
1572
+ lower_bound = future_heights - 15
1573
+ upper_bound = future_heights + 15
1574
 
 
1575
  fig = go.Figure()
 
1576
  fig.add_trace(go.Scatter(
1577
+ x=historical_weeks,
1578
+ y=historical_heights,
1579
  mode='lines+markers',
1580
+ name='داده‌های تاریخی',
1581
+ line=dict(color='#1a8754', width=3),
1582
+ marker=dict(size=8, color='#1a8754')
1583
  ))
 
1584
  fig.add_trace(go.Scatter(
1585
+ x=future_weeks.flatten(),
1586
+ y=future_heights,
1587
+ mode='lines',
1588
+ name='پیش‌بینی',
1589
+ line=dict(color='#1976d2', width=3, dash='dash')
1590
+ ))
1591
+ fig.add_trace(go.Scatter(
1592
+ x=future_weeks.flatten(),
1593
+ y=lower_bound,
1594
+ mode='lines',
1595
+ name='حد پایین',
1596
+ line=dict(color='#d32f2f', width=1, dash='dot'),
1597
+ showlegend=True
1598
+ ))
1599
+ fig.add_trace(go.Scatter(
1600
+ x=future_weeks.flatten(),
1601
+ y=upper_bound,
1602
+ mode='lines',
1603
+ name='حد بالا',
1604
+ line=dict(color='#d32f2f', width=1, dash='dot'),
1605
+ fill='tonexty',
1606
+ showlegend=True
1607
  ))
 
1608
  fig.update_layout(
1609
+ title=f'پیش‌بینی رشد مزرعه {selected_farm_for_prediction}',
1610
+ xaxis_title='هفته',
1611
+ yaxis_title='ارتفاع (سانتی‌متر)',
1612
+ font=dict(family='Vazirmatn', size=14),
1613
+ hovermode='x unified',
1614
+ template='plotly_white',
1615
+ height=500
 
 
 
 
 
 
 
 
 
1616
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
1617
  st.plotly_chart(fig, use_container_width=True)
1618
  else:
1619
+ st.warning("داده‌های کافی برای پیش‌بینی وجود ندارد.")
1620
+
1621
+ # Report Generation Page
1622
+ elif selected == "گزارش‌گیری":
1623
+ st.markdown("## گزارش‌گیری")
1624
 
1625
+ report_week = st.selectbox("انتخاب هفته برای گزارش", options=[str(i) for i in range(1, 23)])
1626
+ report_day = st.selectbox("انتخاب روز برای گزارش", options=day_df['Day'].unique().tolist())
1627
+
1628
+ report_df = st.session_state.heights_df[
1629
+ (st.session_state.heights_df['Week'] == int(report_week)) &
1630
+ (st.session_state.heights_df['Farm_ID'].isin(day_df[day_df['Day'] == report_day]['Farm_ID']))
1631
+ ]
1632
+
1633
+ if not report_df.empty:
1634
+ st.markdown(f"### گزارش هفته {report_week} - روز {report_day}")
1635
+ st.dataframe(report_df, use_container_width=True)
1636
+
1637
+ csv = report_df.to_csv(index=False).encode('utf-8')
1638
+ st.download_button(
1639
+ label="دانلود گزارش (CSV)",
1640
+ data=csv,
1641
+ file_name=f"report_week_{report_week}_day_{report_day}.csv",
1642
+ mime="text/csv",
1643
+ )
1644
+
1645
+ st_lottie(lottie_report, height=200, key="report_animation")
1646
+ else:
1647
+ st.warning(f"داده‌ای برای هفته {report_week} و روز {report_day} یافت نشد.")
1648
+
1649
+ # Settings Page
1650
+ elif selected == "تنظیمات":
1651
+ st.markdown("## تنظیمات سامانه")
1652
+
1653
+ st.markdown("""
1654
+ <div class="glass-card">
1655
+ <h3 class="gradient-text">تنظیمات پیشرفته</h3>
1656
+ <p>در این بخش می‌توانید تنظیمات کلی سامانه، از جمله به‌روزرسانی داده‌ها و پیکربندی‌های پیشرفته را مدیریت کنید.</p>
1657
+ </div>
1658
+ """, unsafe_allow_html=True)
1659
+
1660
+ st.markdown("### به‌روزرسانی داده‌ها")
1661
+
1662
+ if st.button("بارگذاری مجدد داده‌ها", type="primary", use_container_width=True):
1663
+ st.session_state.heights_df = load_farm_data()
1664
+ st.success("داده‌ها با موفقیت به‌روزرسانی شدند.")
1665
+
1666
+ st.markdown("### تنظیمات ظاهری")
1667
+
1668
+ theme = st.radio(
1669
+ "انتخاب تم",
1670
+ options=["سبز (پیش‌فرض)", "آبی", "سفید"],
1671
+ format_func=lambda x: x
1672
+ )
1673
+
1674
+ if theme == "آبی":
1675
+ st.markdown("""
1676
+ <style>
1677
+ .main-header {background: linear-gradient(90deg, #1976d2 0%, #0d47a1 100%);}
1678
+ .metric-card .metric-value {color: #1976d2;}
1679
+ .stButton>button {background: linear-gradient(90deg, #1976d2 0%, #0d47a1 100%);}
1680
+ .stProgress > div > div > div > div {background-color: #1976d2;}
1681
+ </style>
1682
+ """, unsafe_allow_html=True)
1683
+ elif theme == "سفید":
1684
+ st.markdown("""
1685
+ <style>
1686
+ .main-header {background: linear-gradient(90deg, #ffffff 0%, #f5f5f5 100%);}
1687
+ .metric-card .metric-value {color: #333333;}
1688
+ .stButton>button {background: linear-gradient(90deg, #ffffff 0%, #f5f5f5 100%); color: #333333;}
1689
+ .stProgress > div > div > div > div {background-color: #333333;}
1690
+ </style>
1691
+ """, unsafe_allow_html=True)
1692
+
1693
+ st.markdown("### اطلاعات تماس")
1694
+ st.markdown("""
1695
+ <div class="neumorphic-card">
1696
+ <p>برای پشتیبانی یا مشکلات فنی، با ما تماس بگیرید:</p>
1697
+ <p>ایمیل: support@dehkhoda.com</p>
1698
+ <p>تلفن: +98 21 12345678</p>
1699
+ </div>
1700
+ """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1701
 
1702
+ # Footer
1703
+ st.markdown("""
1704
+ <footer>
1705
+ <p>&copy; 2025 سامانه هوشمند پایش مزارع نیشکر دهخدا. تمامی حقوق محفوظ است.</p>
1706
+ </footer>
1707
+ """, unsafe_allow_html=True)