Spaces:
Configuration error
Configuration error
Update app.py
Browse files
app.py
CHANGED
@@ -19,21 +19,13 @@ 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.ensemble import RandomForestRegressor
|
31 |
from sklearn.cluster import KMeans
|
32 |
from tensorflow.keras.models import Sequential
|
33 |
from tensorflow.keras.layers import LSTM, Dense
|
34 |
from sklearn.preprocessing import MinMaxScaler
|
35 |
|
36 |
-
#
|
37 |
st.set_page_config(
|
38 |
page_title="سامانه هوشمند پایش مزارع نیشکر دهخدا",
|
39 |
page_icon="🌿",
|
@@ -41,125 +33,40 @@ st.set_page_config(
|
|
41 |
initial_sidebar_state="expanded"
|
42 |
)
|
43 |
|
44 |
-
# Custom CSS
|
45 |
st.markdown("""
|
46 |
<style>
|
47 |
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@100;200;300;400;500;600;700;800;900&display=swap');
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
}
|
53 |
-
|
54 |
-
.
|
55 |
-
|
56 |
-
}
|
57 |
-
|
58 |
-
.
|
59 |
-
|
60 |
-
|
61 |
-
}
|
62 |
-
|
63 |
-
.
|
64 |
-
|
65 |
-
|
66 |
-
border-radius: 12px;
|
67 |
-
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
68 |
-
margin-bottom: 2rem;
|
69 |
-
position: relative;
|
70 |
-
overflow: hidden;
|
71 |
-
animation: header-glow 3s infinite alternate;
|
72 |
-
}
|
73 |
-
|
74 |
-
@keyframes header-glow {
|
75 |
-
0% { box-shadow: 0 8px 32px rgba(26, 135, 84, 0.1); }
|
76 |
-
100% { box-shadow: 0 8px 32px rgba(26, 135, 84, 0.3); }
|
77 |
-
}
|
78 |
-
|
79 |
-
.main-header h1, .main-header p {
|
80 |
-
color: white;
|
81 |
-
z-index: 1;
|
82 |
-
position: relative;
|
83 |
-
}
|
84 |
-
|
85 |
-
.stcard {
|
86 |
-
border-radius: 12px;
|
87 |
-
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
88 |
-
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
89 |
-
}
|
90 |
-
|
91 |
-
.stcard:hover {
|
92 |
-
transform: translateY(-5px);
|
93 |
-
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.1);
|
94 |
-
}
|
95 |
-
|
96 |
-
.dark-theme .stcard {
|
97 |
-
background: #34495e;
|
98 |
-
color: #ecf0f1;
|
99 |
-
}
|
100 |
-
|
101 |
-
.stButton>button {
|
102 |
-
border-radius: 50px;
|
103 |
-
padding: 0.5rem 1.5rem;
|
104 |
-
font-weight: 600;
|
105 |
-
transition: all 0.3s ease;
|
106 |
-
}
|
107 |
-
|
108 |
-
.primary-btn {
|
109 |
-
background: linear-gradient(90deg, #1a8754 0%, #115740 100%);
|
110 |
-
color: white;
|
111 |
-
}
|
112 |
-
|
113 |
-
.dark-theme .primary-btn {
|
114 |
-
background: linear-gradient(90deg, #27ae60 0%, #219653 100%);
|
115 |
-
}
|
116 |
-
|
117 |
-
.metric-card {
|
118 |
-
background: white;
|
119 |
-
border-radius: 12px;
|
120 |
-
padding: 1.5rem;
|
121 |
-
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
122 |
-
text-align: center;
|
123 |
-
}
|
124 |
-
|
125 |
-
.dark-theme .metric-card {
|
126 |
-
background: #34495e;
|
127 |
-
color: #ecf0f1;
|
128 |
-
}
|
129 |
-
|
130 |
-
.map-container {
|
131 |
-
border-radius: 12px;
|
132 |
-
overflow: hidden;
|
133 |
-
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
134 |
-
}
|
135 |
-
|
136 |
-
.animate-load {
|
137 |
-
animation: fadeIn 0.5s ease forwards;
|
138 |
-
}
|
139 |
-
|
140 |
-
@keyframes fadeIn {
|
141 |
-
0% { opacity: 0; transform: translateY(20px); }
|
142 |
-
100% { opacity: 1; transform: translateY(0); }
|
143 |
-
}
|
144 |
-
|
145 |
-
.rtl {
|
146 |
-
direction: rtl;
|
147 |
-
text-align: right;
|
148 |
-
}
|
149 |
</style>
|
150 |
""", unsafe_allow_html=True)
|
151 |
|
152 |
-
# Theme
|
153 |
if 'theme' not in st.session_state:
|
154 |
st.session_state.theme = 'light'
|
155 |
-
|
156 |
theme = st.sidebar.selectbox("انتخاب تم", ["روشن", "تیره"], index=0 if st.session_state.theme == 'light' else 1)
|
157 |
st.session_state.theme = 'light' if theme == "روشن" else 'dark'
|
158 |
theme_class = "dark-theme" if st.session_state.theme == 'dark' else ""
|
159 |
-
|
160 |
st.markdown(f'<body class="{theme_class}">', unsafe_allow_html=True)
|
161 |
|
162 |
-
#
|
163 |
@st.cache_resource
|
164 |
def initialize_earth_engine():
|
165 |
try:
|
@@ -177,661 +84,337 @@ def initialize_earth_engine():
|
|
177 |
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/dehkhodamap-e9f0da4ce9f6514021%40ee-esmaeilkiani13877.iam.gserviceaccount.com",
|
178 |
"universe_domain": "googleapis.com"
|
179 |
}
|
180 |
-
|
181 |
-
credentials_file = 'ee-esmaeilkiani13877-cfdea6eaf411.json'
|
182 |
-
with open(credentials_file, 'w') as f:
|
183 |
json.dump(credentials_dict, f)
|
184 |
-
|
185 |
-
credentials = ee.ServiceAccountCredentials(service_account, credentials_file)
|
186 |
ee.Initialize(credentials)
|
187 |
-
|
188 |
-
os.remove(credentials_file)
|
189 |
return True
|
190 |
except Exception as e:
|
191 |
st.error(f"خطا در اتصال به Earth Engine: {e}")
|
192 |
return False
|
193 |
|
194 |
-
# Load data
|
195 |
@st.cache_data
|
196 |
-
def
|
197 |
try:
|
198 |
-
|
199 |
-
return df
|
200 |
except Exception as e:
|
201 |
-
st.error(f"
|
202 |
return pd.DataFrame()
|
203 |
|
204 |
@st.cache_data
|
205 |
-
def
|
206 |
-
try:
|
207 |
-
df = pd.read_csv("farm_coordinates.csv")
|
208 |
-
return df
|
209 |
-
except Exception as e:
|
210 |
-
st.error(f"خطا در بارگذاری دادههای مختصات: {e}")
|
211 |
-
return pd.DataFrame()
|
212 |
-
|
213 |
-
# Load animation JSON
|
214 |
-
@st.cache_data
|
215 |
-
def load_lottie_url(url: str):
|
216 |
r = requests.get(url)
|
217 |
-
if r.status_code
|
218 |
-
return None
|
219 |
-
return r.json()
|
220 |
|
221 |
-
|
222 |
-
def get_weather_data(lat, lon, api_key):
|
223 |
url = f"http://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={api_key}&units=metric"
|
224 |
response = requests.get(url)
|
225 |
-
if response.status_code == 200
|
226 |
-
return response.json()
|
227 |
-
return None
|
228 |
|
229 |
-
|
230 |
-
def estimate_water_requirement(farm_id, date_str):
|
231 |
-
api_key = "ed47316a45379e2221a75f813229fb46"
|
232 |
farm_row = coordinates_df[coordinates_df['مزرعه'] == farm_id].iloc[0]
|
233 |
-
|
234 |
-
weather_data = get_weather_data(lat, lon, api_key)
|
235 |
if weather_data:
|
236 |
temperature = weather_data['main']['temp']
|
237 |
humidity = weather_data['main']['humidity']
|
238 |
-
|
239 |
-
return max(0, water_requirement)
|
240 |
return None
|
241 |
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
s2
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
elif layer_type == "NDWI":
|
278 |
-
index = s2.normalizedDifference(['B3', 'B8']).rename('NDWI')
|
279 |
-
viz_params = {'min': -0.5, 'max': 0.5, 'palette': ['#00008b', '#00b7eb', '#add8e6', '#fdae61', '#d73027']}
|
280 |
-
legend_title = 'شاخص آب (NDWI)'
|
281 |
-
elif layer_type == "SoilMoisture":
|
282 |
-
s1 = ee.ImageCollection('COPERNICUS/S1_GRD') \
|
283 |
-
.filterDate(start_date, end_date) \
|
284 |
-
.filterBounds(region) \
|
285 |
-
.sort('system:time_start') \
|
286 |
-
.first()
|
287 |
-
index = s1.select('VV').rename('SoilMoisture')
|
288 |
-
viz_params = {'min': -25, 'max': -5, 'palette': ['#00008b', '#add8e6', '#ffffff']}
|
289 |
-
legend_title = 'رطوبت خاک (Soil Moisture)'
|
290 |
-
|
291 |
-
if heatmap:
|
292 |
-
map_id_dict = ee.Image(index).getMapId({'min': viz_params['min'], 'max': viz_params['max'], 'palette': viz_params['palette'], 'opacity': 0.7})
|
293 |
-
folium.TileLayer(
|
294 |
-
tiles=map_id_dict['tile_fetcher'].url_format,
|
295 |
-
attr='Google Earth Engine Heatmap',
|
296 |
-
name=f'{layer_type} Heatmap',
|
297 |
-
overlay=True,
|
298 |
-
control=True
|
299 |
-
).add_to(m)
|
300 |
-
else:
|
301 |
-
map_id_dict = ee.Image(index).getMapId(viz_params)
|
302 |
-
folium.TileLayer(
|
303 |
-
tiles=map_id_dict['tile_fetcher'].url_format,
|
304 |
-
attr='Google Earth Engine',
|
305 |
-
name=layer_type,
|
306 |
-
overlay=True,
|
307 |
-
control=True
|
308 |
-
).add_to(m)
|
309 |
-
|
310 |
-
folium.Marker([lat, lon], popup=f'مزرعه {farm_id}', tooltip=f'مزرعه {farm_id}', icon=folium.Icon(color='green', icon='leaf')).add_to(m)
|
311 |
-
folium.Circle([lat, lon], radius=1500, color='green', fill=True, fill_color='green', fill_opacity=0.1).add_to(m)
|
312 |
-
folium.LayerControl().add_to(m)
|
313 |
-
|
314 |
-
legend_html = f'''
|
315 |
-
<div style="position: fixed; bottom: 50px; right: 50px; border: 2px solid grey; z-index: 9999; background-color: white; padding: 10px; border-radius: 5px; direction: rtl;">
|
316 |
-
<div style="font-size: 14px; font-weight: bold; margin-bottom: 5px;">{legend_title}</div>
|
317 |
-
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
318 |
-
<div style="background: {viz_params['palette'][0]}; width: 20px; height: 20px; margin-left: 5px;"></div>
|
319 |
-
<span>کم</span>
|
320 |
-
</div>
|
321 |
-
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
322 |
-
<div style="background: {viz_params['palette'][2]}; width: 20px; height: 20px; margin-left: 5px;"></div>
|
323 |
-
<span>متوسط</span>
|
324 |
-
</div>
|
325 |
-
<div style="display: flex; align-items: center;">
|
326 |
-
<div style="background: {viz_params['palette'][-1]}; width: 20px; height: 20px; margin-left: 5px;"></div>
|
327 |
-
<span>زیاد</span>
|
328 |
-
</div>
|
329 |
-
</div>
|
330 |
-
'''
|
331 |
-
m.get_root().html.add_child(folium.Element(legend_html))
|
332 |
-
|
333 |
-
return m
|
334 |
-
except Exception as e:
|
335 |
-
st.error(f"خطا در ایجاد نقشه: {e}")
|
336 |
-
return None
|
337 |
|
338 |
-
# Generate mock growth data
|
339 |
def generate_mock_growth_data(farm_data, selected_variety="all", selected_age="all"):
|
340 |
weeks = list(range(1, 23))
|
341 |
-
filtered_farms = farm_data
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
growth_rate = np.random.uniform(5, 15)
|
351 |
-
growth_data = {
|
352 |
-
'farm_id': farm['مزرعه'],
|
353 |
-
'variety': farm['واریته'],
|
354 |
-
'age': farm['سن'],
|
355 |
-
'weeks': weeks,
|
356 |
-
'heights': [round(base_height + growth_rate * week) for week in weeks]
|
357 |
-
}
|
358 |
-
farm_growth_data.append(growth_data)
|
359 |
-
|
360 |
-
if farm_growth_data:
|
361 |
-
avg_heights = [round(sum(farm['heights'][week-1] for farm in farm_growth_data) / len(farm_growth_data)) for week in weeks]
|
362 |
-
avg_growth_data = {
|
363 |
-
'farm_id': 'میانگین',
|
364 |
-
'variety': 'همه',
|
365 |
-
'age': 'همه',
|
366 |
-
'weeks': weeks,
|
367 |
-
'heights': avg_heights
|
368 |
-
}
|
369 |
-
return {'individual': farm_growth_data, 'average': avg_growth_data}
|
370 |
-
return {
|
371 |
-
'individual': [],
|
372 |
-
'average': {'farm_id': 'میانگین', 'variety': 'همه', 'age': 'همه', 'weeks': weeks, 'heights': [0] * len(weeks)}
|
373 |
-
}
|
374 |
|
375 |
-
# Calculate farm statistics and detect stress
|
376 |
def calculate_farm_stats(farm_id, layer_type="NDVI"):
|
377 |
-
|
378 |
-
|
379 |
-
|
380 |
-
|
381 |
-
|
382 |
-
|
383 |
-
mean = round(np.random.uniform(0.3, 0.5), 2)
|
384 |
-
min_val = round(mean - np.random.uniform(0.2, 0.3), 2)
|
385 |
-
max_val = round(mean + np.random.uniform(0.1, 0.2), 2)
|
386 |
-
std_dev = round(np.random.uniform(0.05, 0.15), 2)
|
387 |
-
elif layer_type == "EVI":
|
388 |
-
mean = round(np.random.uniform(0.4, 0.6), 2)
|
389 |
-
min_val = round(mean - np.random.uniform(0.2, 0.3), 2)
|
390 |
-
max_val = round(mean + np.random.uniform(0.1, 0.2), 2)
|
391 |
-
std_dev = round(np.random.uniform(0.05, 0.15), 2)
|
392 |
-
elif layer_type == "NDWI":
|
393 |
-
mean = round(np.random.uniform(-0.1, 0.1), 2)
|
394 |
-
min_val = round(mean - np.random.uniform(0.2, 0.3), 2)
|
395 |
-
max_val = round(mean + np.random.uniform(0.1, 0.2), 2)
|
396 |
-
std_dev = round(np.random.uniform(0.05, 0.15), 2)
|
397 |
-
elif layer_type == "SoilMoisture":
|
398 |
-
mean = round(np.random.uniform(-20, -10), 2)
|
399 |
-
min_val = round(mean - np.random.uniform(5, 10), 2)
|
400 |
-
max_val = round(mean + np.random.uniform(5, 10), 2)
|
401 |
-
std_dev = round(np.random.uniform(2, 5), 2)
|
402 |
-
|
403 |
-
hist_data = np.random.normal(mean, std_dev, 1000)
|
404 |
-
hist_data = np.clip(hist_data, min_val, max_val)
|
405 |
-
|
406 |
-
stress_alerts = []
|
407 |
-
if layer_type == "NDVI" and mean < 0.3:
|
408 |
-
stress_alerts.append("تنش سلامتی: NDVI پایین نشاندهنده پوشش گیاهی ضعیف است.")
|
409 |
-
if layer_type == "NDMI" and mean < 0.1:
|
410 |
-
stress_alerts.append("تنش آبی: NDMI پایین نشاندهنده کمبود رطوبت است.")
|
411 |
-
if layer_type == "SoilMoisture" and mean < -20:
|
412 |
-
stress_alerts.append("تنش خاک: رطوبت خاک بسیار پایین است.")
|
413 |
-
|
414 |
-
return {
|
415 |
-
'mean': mean, 'min': min_val, 'max': max_val, 'std_dev': std_dev,
|
416 |
-
'histogram_data': hist_data, 'stress_alerts': stress_alerts
|
417 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
418 |
|
419 |
-
# Time-series data generation
|
420 |
def generate_time_series_data(farm_id, layer_type, period="weekly"):
|
421 |
-
|
422 |
-
|
423 |
-
|
424 |
-
|
425 |
-
|
426 |
-
elif layer_type == "EVI":
|
427 |
-
values = np.random.normal(0.5, 0.1, len(dates))
|
428 |
-
elif layer_type == "NDWI":
|
429 |
-
values = np.random.normal(0, 0.1, len(dates))
|
430 |
-
elif layer_type == "SoilMoisture":
|
431 |
-
values = np.random.normal(-15, 5, len(dates))
|
432 |
-
return pd.DataFrame({'Date': dates, layer_type: values})
|
433 |
|
434 |
-
|
435 |
-
def predict_growth_advanced(farm_id, historical_data):
|
436 |
weeks = historical_data['Week'].values.reshape(-1, 1)
|
437 |
heights = historical_data['Height'].values
|
438 |
-
|
439 |
-
# Random Forest
|
440 |
-
rf_model = RandomForestRegressor(n_estimators=100, random_state=42)
|
441 |
rf_model.fit(weeks, heights)
|
442 |
future_weeks = np.array(range(16, 23)).reshape(-1, 1)
|
443 |
rf_predictions = rf_model.predict(future_weeks)
|
444 |
|
445 |
-
# LSTM
|
446 |
scaler = MinMaxScaler()
|
447 |
scaled_heights = scaler.fit_transform(heights.reshape(-1, 1))
|
448 |
-
X
|
449 |
-
|
450 |
-
|
451 |
-
y.append(scaled_heights[i])
|
452 |
-
X, y = np.array(X), np.array(y)
|
453 |
-
|
454 |
-
lstm_model = Sequential([
|
455 |
-
LSTM(50, activation='relu', input_shape=(3, 1), return_sequences=True),
|
456 |
-
LSTM(50, activation='relu'),
|
457 |
-
Dense(1)
|
458 |
-
])
|
459 |
lstm_model.compile(optimizer='adam', loss='mse')
|
460 |
-
lstm_model.fit(X, y, epochs=
|
461 |
-
|
462 |
lstm_input = scaled_heights[-3:].reshape((1, 3, 1))
|
463 |
-
lstm_predictions = []
|
464 |
-
for _ in range(7):
|
465 |
-
pred = lstm_model.predict(lstm_input, verbose=0)
|
466 |
-
lstm_predictions.append(pred[0, 0])
|
467 |
-
lstm_input = np.append(lstm_input[:, 1:, :], pred.reshape(1, 1, 1), axis=1)
|
468 |
-
lstm_predictions = scaler.inverse_transform(np.array(lstm_predictions).reshape(-1, 1)).flatten()
|
469 |
-
|
470 |
return rf_predictions, lstm_predictions
|
471 |
|
472 |
-
# Cluster farms
|
473 |
def cluster_farms(farm_data):
|
474 |
-
|
475 |
-
|
|
|
476 |
farm_data['Cluster'] = kmeans.fit_predict(features)
|
477 |
return farm_data, kmeans.cluster_centers_
|
478 |
|
479 |
-
#
|
480 |
ee_initialized = initialize_earth_engine()
|
481 |
-
farm_df =
|
482 |
-
coordinates_df =
|
483 |
-
|
484 |
-
# Load animations
|
485 |
lottie_farm = load_lottie_url('https://assets5.lottiefiles.com/packages/lf20_ystsffqy.json')
|
486 |
lottie_analysis = load_lottie_url('https://assets3.lottiefiles.com/packages/lf20_qp1q7mct.json')
|
487 |
lottie_report = load_lottie_url('https://assets9.lottiefiles.com/packages/lf20_vwcugezu.json')
|
488 |
-
|
489 |
-
# Session state
|
490 |
if 'heights_df' not in st.session_state:
|
491 |
st.session_state.heights_df = pd.DataFrame(columns=['Farm_ID', 'Week', 'Measurement_Date', 'Height', 'Station1', 'Station2', 'Station3', 'Station4', 'Station5', 'Groundwater1', 'Groundwater2', 'Sheath_Moisture', 'Nitrogen', 'Variety', 'Age', 'Area', 'Channel', 'Administration'])
|
492 |
|
493 |
-
# Main
|
494 |
-
st.markdown('<div class="main-header animate-load">', unsafe_allow_html=True)
|
495 |
-
st.markdown('<h1>سامانه هوشمند پایش مزارع نیشکر دهخدا</h1>', unsafe_allow_html=True)
|
496 |
-
st.markdown('<p>پلتفرم جامع مدیریت، پایش و تحلیل دادههای مزارع نیشکر با استفاده از تصاویر ماهوارهای و هوش مصنوعی</p>', unsafe_allow_html=True)
|
497 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
498 |
-
|
499 |
-
# Navigation menu
|
500 |
selected = option_menu(
|
501 |
-
menu_title=None,
|
502 |
-
|
503 |
-
|
504 |
-
|
505 |
-
orientation="horizontal",
|
506 |
-
styles={
|
507 |
-
"container": {"padding": "0!important", "background-color": "transparent", "margin-bottom": "20px"},
|
508 |
-
"icon": {"color": "#1a8754", "font-size": "18px"},
|
509 |
-
"nav-link": {"font-size": "16px", "text-align": "center", "margin":"0px", "--hover-color": "#e9f7ef", "border-radius": "10px"},
|
510 |
-
"nav-link-selected": {"background-color": "#1a8754", "color": "white", "font-weight": "600"},
|
511 |
-
}
|
512 |
)
|
513 |
|
514 |
-
# Dashboard
|
515 |
if selected == "داشبورد":
|
516 |
st.markdown('<div class="animate-load">', unsafe_allow_html=True)
|
517 |
col1, col2, col3, col4 = st.columns(4)
|
|
|
|
|
518 |
|
519 |
-
|
520 |
-
st.markdown('<div class="metric-card">', unsafe_allow_html=True)
|
521 |
-
st.markdown(f'<div class="metric-value">{len(farm_df)}</div>', unsafe_allow_html=True)
|
522 |
-
st.markdown('<div class="metric-label">تعداد مزارع</div>', unsafe_allow_html=True)
|
523 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
524 |
-
|
525 |
-
with col2:
|
526 |
-
active_farms = int(len(farm_df) * 0.85)
|
527 |
-
st.markdown('<div class="metric-card">', unsafe_allow_html=True)
|
528 |
-
st.markdown(f'<div class="metric-value">{active_farms}</div>', unsafe_allow_html=True)
|
529 |
-
st.markdown('<div class="metric-label">مزارع فعال</div>', unsafe_allow_html=True)
|
530 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
531 |
-
|
532 |
-
with col3:
|
533 |
-
avg_height = 175
|
534 |
-
st.markdown('<div class="metric-card">', unsafe_allow_html=True)
|
535 |
-
st.markdown(f'<div class="metric-value">{avg_height} cm</div>', unsafe_allow_html=True)
|
536 |
-
st.markdown('<div class="metric-label">میانگین ارتفاع</div>', unsafe_allow_html=True)
|
537 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
538 |
-
|
539 |
-
with col4:
|
540 |
-
avg_moisture = 68
|
541 |
-
st.markdown('<div class="metric-card">', unsafe_allow_html=True)
|
542 |
-
st.markdown(f'<div class="metric-value">{avg_moisture}%</div>', unsafe_allow_html=True)
|
543 |
-
st.markdown('<div class="metric-label">میانگین رطوبت</div>', unsafe_allow_html=True)
|
544 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
545 |
-
|
546 |
-
# Interactive Dashboard Filters
|
547 |
-
st.markdown("### فیلترهای داشبورد")
|
548 |
col_filter1, col_filter2, col_filter3 = st.columns(3)
|
549 |
-
|
550 |
-
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
|
555 |
-
|
556 |
-
filtered_df = farm_df
|
557 |
-
if "همه" not in variety_filter:
|
558 |
-
filtered_df = filtered_df[filtered_df['واریته'].isin(variety_filter)]
|
559 |
-
if "همه" not in age_filter:
|
560 |
-
filtered_df = filtered_df[filtered_df['سن'].isin(age_filter)]
|
561 |
-
if "همه" not in channel_filter:
|
562 |
-
filtered_df = filtered_df[filtered_df['کانال'].isin(channel_filter)]
|
563 |
|
564 |
tab1, tab2, tab3, tab4 = st.tabs(["نمای کلی", "نقشه مزارع", "نمودارها", "دادهها"])
|
565 |
-
|
566 |
with tab1:
|
567 |
-
st.markdown("### توزیع واریتهها و سن محصول")
|
568 |
col1, col2 = st.columns(2)
|
569 |
-
with col1:
|
570 |
-
|
571 |
-
variety_counts.columns = ['واریته', 'تعداد']
|
572 |
-
fig = px.pie(variety_counts, values='تعداد', names='واریته', title='توزیع واریتهها', color_discrete_sequence=px.colors.sequential.Greens_r)
|
573 |
-
st.plotly_chart(fig, use_container_width=True)
|
574 |
-
with col2:
|
575 |
-
age_counts = filtered_df['سن'].value_counts().reset_index()
|
576 |
-
age_counts.columns = ['سن', 'تعداد']
|
577 |
-
fig = px.pie(age_counts, values='تعداد', names='سن', title='توزیع سن محصول', color_discrete_sequence=px.colors.sequential.Blues_r)
|
578 |
-
st.plotly_chart(fig, use_container_width=True)
|
579 |
-
|
580 |
-
st.markdown("### اطلاعات کلی مزارع")
|
581 |
-
total_area = filtered_df['مساحت'].astype(float).sum()
|
582 |
col1, col2, col3 = st.columns(3)
|
583 |
-
col1.metric("تعداد
|
584 |
-
col2.metric("مساحت کل (هکتار)", f"{
|
585 |
-
col3.metric("تعداد کانالها",
|
586 |
-
st_lottie(lottie_farm, height=300
|
587 |
-
|
588 |
with tab2:
|
589 |
-
st.markdown("### نقشه مزارع")
|
590 |
if not coordinates_df.empty:
|
591 |
m = folium.Map(location=[31.45, 48.72], zoom_start=12, tiles='CartoDB positron')
|
592 |
for _, farm in coordinates_df.iterrows():
|
593 |
-
|
594 |
-
|
595 |
-
|
596 |
-
|
597 |
-
popup_text = f"""
|
598 |
-
<div style="direction: rtl;">
|
599 |
-
<h4>مزرعه {name}</h4>
|
600 |
-
<p>واریته: {farm_info['واریته'].iloc[0]}</p>
|
601 |
-
<p>سن: {farm_info['سن'].iloc[0]}</p>
|
602 |
-
<p>مساحت: {farm_info['مساحت'].iloc[0]} هکتار</p>
|
603 |
-
</div>
|
604 |
-
"""
|
605 |
-
folium.Marker([lat, lon], popup=folium.Popup(popup_text, max_width=300), tooltip=f"مزرعه {name}", icon=folium.Icon(color='green', icon='leaf')).add_to(m)
|
606 |
st.markdown('<div class="map-container">', unsafe_allow_html=True)
|
607 |
folium_static(m, width=1000, height=600)
|
608 |
st.markdown('</div>', unsafe_allow_html=True)
|
609 |
-
|
610 |
with tab3:
|
611 |
-
st.markdown("### نمودار رشد هفتگی")
|
612 |
growth_data = generate_mock_growth_data(filtered_df)
|
613 |
chart_tab1, chart_tab2 = st.tabs(["میانگین رشد", "رشد مزارع فردی"])
|
614 |
-
with chart_tab1:
|
615 |
-
|
616 |
-
fig.
|
617 |
-
fig.update_layout(title='
|
618 |
-
st.plotly_chart(fig, use_container_width=True)
|
619 |
-
with chart_tab2:
|
620 |
-
fig = go.Figure()
|
621 |
-
for i, farm_data in enumerate(growth_data['individual'][:5]):
|
622 |
-
fig.add_trace(go.Scatter(x=farm_data['weeks'], y=farm_data['heights'], mode='lines+markers', name=f"مزرعه {farm_data['farm_id']}"))
|
623 |
-
fig.update_layout(title='رشد هفتگی مزارع فردی', xaxis_title='هفته', yaxis_title='ارتفاع (سانتیمتر)', font=dict(family='Vazirmatn'))
|
624 |
st.plotly_chart(fig, use_container_width=True)
|
625 |
-
|
626 |
-
with tab4:
|
627 |
-
st.markdown("### دادههای مزارع")
|
628 |
-
st.dataframe(filtered_df, use_container_width=True, height=400, hide_index=True)
|
629 |
st.markdown('</div>', unsafe_allow_html=True)
|
630 |
|
631 |
-
# Map
|
632 |
elif selected == "نقشه مزارع":
|
633 |
st.markdown('<div class="animate-load">', unsafe_allow_html=True)
|
634 |
st.markdown("## نقشه مزارع با شاخصهای ماهوارهای")
|
635 |
col1, col2 = st.columns([1, 3])
|
636 |
with col1:
|
637 |
-
st.markdown('<div class="glass-card">', unsafe_allow_html=True)
|
638 |
selected_farm = st.selectbox("انتخاب مزرعه", options=coordinates_df['مزرعه'].tolist(), format_func=lambda x: f"مزرعه {x}")
|
639 |
selected_date = st.date_input("انتخاب تاریخ", value=datetime.now())
|
640 |
-
selected_layer = st.selectbox("انتخاب شاخص", options=["NDVI", "NDMI", "EVI", "NDWI", "SoilMoisture"], format_func=lambda x: {"NDVI": "
|
641 |
-
heatmap_toggle = st.checkbox("نمایش نقشه حرارتی"
|
642 |
-
generate_map = st.button("تولید نقشه", type="primary"
|
643 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
644 |
-
|
645 |
with col2:
|
646 |
map_tab, stats_tab = st.tabs(["نقشه", "آمار و تحلیل"])
|
647 |
with map_tab:
|
648 |
if generate_map or 'last_map' not in st.session_state:
|
649 |
with st.spinner('در حال تولید نقشه...'):
|
650 |
-
m = create_ee_map(selected_farm, selected_date.strftime('%Y-%m-%d'), selected_layer,
|
651 |
if m:
|
652 |
st.session_state.last_map = m
|
653 |
folium_static(m, width=800, height=600)
|
654 |
-
st.success(f"نقشه {selected_layer}
|
655 |
elif 'last_map' in st.session_state:
|
656 |
folium_static(st.session_state.last_map, width=800, height=600)
|
657 |
-
|
658 |
with stats_tab:
|
659 |
if 'last_map' in st.session_state:
|
660 |
stats = calculate_farm_stats(selected_farm, selected_layer)
|
661 |
col1, col2, col3, col4 = st.columns(4)
|
662 |
-
|
663 |
-
|
664 |
-
|
665 |
-
|
666 |
-
|
667 |
-
if stats['stress_alerts']:
|
668 |
-
for alert in stats['stress_alerts']:
|
669 |
-
st.warning(alert)
|
670 |
-
|
671 |
-
fig = px.histogram(x=stats["histogram_data"], nbins=20, title=f"توزیع مقادیر {selected_layer}", labels={"x": f"مقدار {selected_layer}", "y": "فراوانی"})
|
672 |
-
st.plotly_chart(fig, use_container_width=True)
|
673 |
-
|
674 |
st.markdown("### تحلیل سری زمانی")
|
675 |
period = st.selectbox("بازه زمانی", ["weekly", "monthly", "seasonal"], format_func=lambda x: {"weekly": "هفتگی", "monthly": "ماهانه", "seasonal": "فصلی"}[x])
|
676 |
ts_data = generate_time_series_data(selected_farm, selected_layer, period)
|
677 |
-
|
678 |
-
st.plotly_chart(fig, use_container_width=True)
|
679 |
st.markdown('</div>', unsafe_allow_html=True)
|
680 |
|
681 |
-
# Data Entry
|
682 |
elif selected == "ورود اطلاعات":
|
683 |
st.markdown('<div class="animate-load">', unsafe_allow_html=True)
|
684 |
-
st.markdown("## ورود اطلاعات روزانه
|
685 |
tab1, tab2 = st.tabs(["ورود دستی", "آپلود فایل"])
|
686 |
with tab1:
|
687 |
col1, col2 = st.columns(2)
|
688 |
-
|
689 |
-
selected_week = st.selectbox("انتخاب هفته", [str(i) for i in range(1, 23)], format_func=lambda x: f"هفته {x}")
|
690 |
-
with col2:
|
691 |
-
selected_day = st.selectbox("انتخاب روز", ["شنبه", "یکشنبه", "دوشنبه", "سهشنبه", "چهارشنبه", "پنجشنبه"])
|
692 |
-
|
693 |
filtered_farms = farm_df[farm_df['روز'] == selected_day]
|
694 |
data_key = f"data_{selected_week}_{selected_day}"
|
695 |
if data_key not in st.session_state:
|
696 |
-
st.session_state[data_key] = pd.DataFrame({
|
697 |
-
'مزرعه': filtered_farms['مزرعه'],
|
698 |
-
'ایستگاه 1': [0] * len(filtered_farms),
|
699 |
-
'ایستگاه 2': [0] * len(filtered_farms),
|
700 |
-
'ایستگاه 3': [0] * len(filtered_farms),
|
701 |
-
'ایستگاه 4': [0] * len(filtered_farms),
|
702 |
-
'ایستگاه 5': [0] * len(filtered_farms),
|
703 |
-
'چاهک 1': [0] * len(filtered_farms),
|
704 |
-
'چاهک 2': [0] * len(filtered_farms),
|
705 |
-
'رطوبت غلاف': [0] * len(filtered_farms),
|
706 |
-
'نیتروژن': [0] * len(filtered_farms),
|
707 |
-
'میانگین ارتفاع': [0] * len(filtered_farms)
|
708 |
-
})
|
709 |
-
|
710 |
edited_df = st.data_editor(st.session_state[data_key], use_container_width=True)
|
711 |
-
for
|
712 |
-
stations = [edited_df.iloc[i][f'ایستگاه {j}'] for j in range(1, 6)]
|
713 |
-
valid_stations = [s for s in stations if s > 0]
|
714 |
-
if valid_stations:
|
715 |
-
edited_df.iloc[i, edited_df.columns.get_loc('میانگین ارتفاع')] = round(sum(valid_stations) / len(valid_stations), 1)
|
716 |
st.session_state[data_key] = edited_df
|
717 |
-
|
718 |
if st.button("ذخیره اطلاعات", type="primary"):
|
719 |
new_data = edited_df.copy()
|
720 |
-
new_data['Farm_ID'] = new_data['مزرعه']
|
721 |
-
new_data['Week'] = int(selected_week)
|
722 |
-
new_data['Measurement_Date'] = (datetime.now() - timedelta(weeks=(22 - int(selected_week)))).strftime('%Y-%m-%d')
|
723 |
-
new_data['Height'] = new_data['میانگین ارتفاع']
|
724 |
new_data = new_data.merge(farm_df[['مزرعه', 'واریته', 'سن', 'مساحت', 'کانال', 'اداره']], left_on='Farm_ID', right_on='مزرعه', how='left')
|
725 |
st.session_state.heights_df = pd.concat([st.session_state.heights_df, new_data], ignore_index=True)
|
726 |
st.success(f"دادههای هفته {selected_week} ذخیره شدند.")
|
727 |
with tab2:
|
728 |
-
uploaded_file = st.file_uploader("فایل اکسل
|
729 |
if uploaded_file:
|
730 |
df = pd.read_csv(uploaded_file) if uploaded_file.name.endswith('.csv') else pd.read_excel(uploaded_file)
|
731 |
st.dataframe(df)
|
732 |
if st.button("ذخیره فایل"): st.success("فایل ذخیره شد.")
|
733 |
st.markdown('</div>', unsafe_allow_html=True)
|
734 |
|
735 |
-
# Data Analysis
|
736 |
elif selected == "تحلیل دادهها":
|
737 |
st.markdown('<div class="animate-load">', unsafe_allow_html=True)
|
738 |
st.markdown("## تحلیل هوشمند دادهها")
|
739 |
tab1, tab2, tab3, tab4 = st.tabs(["تحلیل رشد", "مقایسه واریتهها", "تحلیل رطوبت", "پیشبینی"])
|
740 |
-
|
741 |
with tab1:
|
742 |
growth_data = generate_mock_growth_data(farm_df)
|
743 |
-
|
744 |
-
|
745 |
-
for i, week in enumerate(farm_data['weeks']):
|
746 |
-
chart_data.append({'Farm': farm_data['farm_id'], 'Week': week, 'Height': farm_data['heights'][i]})
|
747 |
-
chart_df = pd.DataFrame(chart_data)
|
748 |
-
chart = alt.Chart(chart_df).mark_line(point=True).encode(x='Week:Q', y='Height:Q', color='Farm:N').interactive()
|
749 |
-
st.altair_chart(chart, use_container_width=True)
|
750 |
-
|
751 |
with tab2:
|
752 |
variety_heights = {variety: np.random.normal(150, 20, 100) for variety in farm_df['واریته'].unique()}
|
753 |
-
fig = go.Figure()
|
754 |
-
|
755 |
-
fig.add_trace(go.Box(y=variety_heights[variety], name=variety))
|
756 |
-
fig.update_layout(title='مقایسه ارتفاع بر اساس واریته', yaxis_title='ارتفاع (سانتیمتر)', font=dict(family="Vazirmatn"))
|
757 |
st.plotly_chart(fig, use_container_width=True)
|
758 |
-
|
759 |
with tab3:
|
760 |
-
|
761 |
-
|
762 |
-
|
763 |
-
|
764 |
-
fig = px.line(moisture_df, x='Date', y='Moisture', color='Farm', title='روند رطوبت مزارع')
|
765 |
-
|
766 |
-
# Correlation analysis
|
767 |
-
correlation_data = [{'Moisture': m, 'Height': 100 + m * 1.5 + np.random.normal(0, 20)} for m in moisture_df['Moisture']]
|
768 |
-
corr_df = pd.DataFrame(correlation_data)
|
769 |
-
fig_corr = px.scatter(corr_df, x='Moisture', y='Height', title='همبستگی رطوبت و ارتفاع', trendline='ols')
|
770 |
-
st.plotly_chart(fig, use_container_width=True)
|
771 |
-
st.plotly_chart(fig_corr, use_container_width=True)
|
772 |
st.info(f"ضریب همبستگی: {corr_df['Moisture'].corr(corr_df['Height']):.2f}")
|
773 |
-
|
774 |
with tab4:
|
775 |
selected_farm = st.selectbox("انتخاب مزرعه", options=farm_df['مزرعه'].tolist(), format_func=lambda x: f"مزرعه {x}")
|
776 |
-
|
777 |
-
|
778 |
-
|
779 |
-
|
780 |
-
|
781 |
-
|
782 |
-
fig = go.Figure()
|
783 |
-
fig.add_trace(go.Scatter(x=weeks, y=heights, mode='lines+markers', name='دادههای تاریخی'))
|
784 |
-
fig.add_trace(go.Scatter(x=future_weeks, y=rf_preds, mode='lines+markers', name='پیشبینی Random Forest', line=dict(dash='dash')))
|
785 |
-
fig.add_trace(go.Scatter(x=future_weeks, y=lstm_preds, mode='lines+markers', name='پیشبینی LSTM', line=dict(dash='dot')))
|
786 |
-
fig.update_layout(title=f'پیشبینی رشد مزرعه {selected_farm}', xaxis_title='هفته', yaxis_title='ارتفاع (سانتیمتر)', font=dict(family='Vazirmatn'))
|
787 |
st.plotly_chart(fig, use_container_width=True)
|
788 |
-
|
789 |
-
# Clustering
|
790 |
clustered_df, centers = cluster_farms(farm_df)
|
791 |
-
|
792 |
-
st.plotly_chart(fig, use_container_width=True)
|
793 |
st.markdown('</div>', unsafe_allow_html=True)
|
794 |
|
795 |
-
# Reporting
|
796 |
elif selected == "گزارشگیری":
|
797 |
st.markdown('<div class="animate-load">', unsafe_allow_html=True)
|
798 |
st.markdown("## گزارشگیری پیشرفته")
|
799 |
-
start_date = st.date_input("تاریخ شروع", value=datetime.now() - timedelta(days=30))
|
800 |
-
end_date = st.date_input("تاریخ پایان", value=datetime.now())
|
801 |
report_type = st.selectbox("نوع گزارش", ["گزارش کلی", "گزارش رشد", "گزارش رطوبت", "گزارش مقایسهای واریتهها"])
|
802 |
if st.button("تولید گزارش"):
|
803 |
with st.spinner('در حال تولید گزارش...'):
|
804 |
-
time.sleep(
|
805 |
if report_type == "گزارش کلی":
|
806 |
-
st.markdown("### گزارش کلی
|
807 |
col1, col2 = st.columns(2)
|
808 |
-
with col1:
|
809 |
-
|
810 |
-
st.plotly_chart(fig)
|
811 |
-
with col2:
|
812 |
-
weeks = list(range(1, 23))
|
813 |
-
heights = [100 + i * 5 + np.random.normal(0, 10) for i in weeks]
|
814 |
-
fig = px.line(x=weeks, y=heights, title='روند رشد کلی مزارع', labels={'x': 'هفته', 'y': 'ارتفاع (سانتیمتر)'})
|
815 |
-
st.plotly_chart(fig)
|
816 |
st.markdown('</div>', unsafe_allow_html=True)
|
817 |
|
818 |
-
# Settings
|
819 |
elif selected == "تنظیمات":
|
820 |
st.markdown('<div class="animate-load">', unsafe_allow_html=True)
|
821 |
-
st.markdown("## تنظیمات
|
822 |
tab1, tab2 = st.tabs(["تنظیمات کاربری", "تنظیمات سیستم"])
|
823 |
-
with tab1:
|
824 |
-
|
825 |
-
user_email = st.text_input("ایمیل", value="[email protected]")
|
826 |
-
with tab2:
|
827 |
-
system_language = st.selectbox("زبان سیستم", ["فارسی", "English", "العربية"])
|
828 |
-
date_format = st.selectbox("فرمت تاریخ", ["YYYY/MM/DD", "DD/MM/YYYY", "MM/DD/YYYY"])
|
829 |
st.markdown('</div>', unsafe_allow_html=True)
|
830 |
|
831 |
-
# Footer
|
832 |
st.markdown("""
|
833 |
<footer style="position: fixed; left: 0; bottom: 0; width: 100%; background-color: #1a8754; color: white; text-align: center; padding: 10px 0;">
|
834 |
-
<p>© سامانه هوشمند پایش مزارع نیشکر
|
835 |
</footer>
|
836 |
""", unsafe_allow_html=True)
|
837 |
st.markdown('</body>', unsafe_allow_html=True)
|
|
|
19 |
from streamlit_option_menu import option_menu
|
20 |
from streamlit_lottie import st_lottie
|
21 |
import requests
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
from sklearn.ensemble import RandomForestRegressor
|
23 |
from sklearn.cluster import KMeans
|
24 |
from tensorflow.keras.models import Sequential
|
25 |
from tensorflow.keras.layers import LSTM, Dense
|
26 |
from sklearn.preprocessing import MinMaxScaler
|
27 |
|
28 |
+
# --- Configuration ---
|
29 |
st.set_page_config(
|
30 |
page_title="سامانه هوشمند پایش مزارع نیشکر دهخدا",
|
31 |
page_icon="🌿",
|
|
|
33 |
initial_sidebar_state="expanded"
|
34 |
)
|
35 |
|
36 |
+
# --- Custom CSS ---
|
37 |
st.markdown("""
|
38 |
<style>
|
39 |
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@100;200;300;400;500;600;700;800;900&display=swap');
|
40 |
+
* { font-family: 'Vazirmatn', sans-serif !important; transition: all 0.3s ease; }
|
41 |
+
.main { background: linear-gradient(135deg, #f5f7fa 0%, #e4e9f2 100%); }
|
42 |
+
.dark-theme .main { background: linear-gradient(135deg, #2c3e50 0%, #1a252f 100%); color: #ecf0f1; }
|
43 |
+
.main-header { background: linear-gradient(90deg, #1a8754 0%, #115740 100%); padding: 1.5rem; border-radius: 12px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); margin-bottom: 2rem; animation: header-glow 3s infinite alternate; }
|
44 |
+
@keyframes header-glow { 0% { box-shadow: 0 8px 32px rgba(26, 135, 84, 0.1); } 100% { box-shadow: 0 8px 32px rgba(26, 135, 84, 0.3); } }
|
45 |
+
.main-header h1, .main-header p { color: white; }
|
46 |
+
.stcard { border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); transition: transform 0.3s ease, box-shadow 0.3s ease; }
|
47 |
+
.stcard:hover { transform: translateY(-5px); box-shadow: 0 8px 30px rgba(0, 0, 0, 0.1); }
|
48 |
+
.dark-theme .stcard { background: #34495e; color: #ecf0f1; }
|
49 |
+
.stButton>button { border-radius: 50px; padding: 0.5rem 1.5rem; font-weight: 600; transition: all 0.3s ease; }
|
50 |
+
.primary-btn { background: linear-gradient(90deg, #1a8754 0%, #115740 100%); color: white; }
|
51 |
+
.dark-theme .primary-btn { background: linear-gradient(90deg, #27ae60 0%, #219653 100%); }
|
52 |
+
.metric-card { background: white; border-radius: 12px; padding: 1.5rem; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); text-align: center; }
|
53 |
+
.dark-theme .metric-card { background: #34495e; color: #ecf0f1; }
|
54 |
+
.map-container { border-radius: 12px; overflow: hidden; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); }
|
55 |
+
.animate-load { animation: fadeIn 0.5s ease forwards; }
|
56 |
+
@keyframes fadeIn { 0% { opacity: 0; transform: translateY(20px); } 100% { opacity: 1; transform: translateY(0); } }
|
57 |
+
.rtl { direction: rtl; text-align: right; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
</style>
|
59 |
""", unsafe_allow_html=True)
|
60 |
|
61 |
+
# --- Theme Selection ---
|
62 |
if 'theme' not in st.session_state:
|
63 |
st.session_state.theme = 'light'
|
|
|
64 |
theme = st.sidebar.selectbox("انتخاب تم", ["روشن", "تیره"], index=0 if st.session_state.theme == 'light' else 1)
|
65 |
st.session_state.theme = 'light' if theme == "روشن" else 'dark'
|
66 |
theme_class = "dark-theme" if st.session_state.theme == 'dark' else ""
|
|
|
67 |
st.markdown(f'<body class="{theme_class}">', unsafe_allow_html=True)
|
68 |
|
69 |
+
# --- Utility Functions ---
|
70 |
@st.cache_resource
|
71 |
def initialize_earth_engine():
|
72 |
try:
|
|
|
84 |
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/dehkhodamap-e9f0da4ce9f6514021%40ee-esmaeilkiani13877.iam.gserviceaccount.com",
|
85 |
"universe_domain": "googleapis.com"
|
86 |
}
|
87 |
+
with open('ee_credentials.json', 'w') as f:
|
|
|
|
|
88 |
json.dump(credentials_dict, f)
|
89 |
+
credentials = ee.ServiceAccountCredentials(service_account, 'ee_credentials.json')
|
|
|
90 |
ee.Initialize(credentials)
|
91 |
+
os.remove('ee_credentials.json')
|
|
|
92 |
return True
|
93 |
except Exception as e:
|
94 |
st.error(f"خطا در اتصال به Earth Engine: {e}")
|
95 |
return False
|
96 |
|
|
|
97 |
@st.cache_data
|
98 |
+
def load_data(file_name, error_message):
|
99 |
try:
|
100 |
+
return pd.read_csv(file_name)
|
|
|
101 |
except Exception as e:
|
102 |
+
st.error(f"{error_message}: {e}")
|
103 |
return pd.DataFrame()
|
104 |
|
105 |
@st.cache_data
|
106 |
+
def load_lottie_url(url):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
107 |
r = requests.get(url)
|
108 |
+
return r.json() if r.status_code == 200 else None
|
|
|
|
|
109 |
|
110 |
+
def get_weather_data(lat, lon, api_key="ed47316a45379e2221a75f813229fb46"):
|
|
|
111 |
url = f"http://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={api_key}&units=metric"
|
112 |
response = requests.get(url)
|
113 |
+
return response.json() if response.status_code == 200 else None
|
|
|
|
|
114 |
|
115 |
+
def estimate_water_requirement(farm_id, date_str, coordinates_df):
|
|
|
|
|
116 |
farm_row = coordinates_df[coordinates_df['مزرعه'] == farm_id].iloc[0]
|
117 |
+
weather_data = get_weather_data(farm_row['عرض جغرافیایی'], farm_row['طول جغرافیایی'])
|
|
|
118 |
if weather_data:
|
119 |
temperature = weather_data['main']['temp']
|
120 |
humidity = weather_data['main']['humidity']
|
121 |
+
return max(0, (temperature - 20) * 0.5 + (100 - humidity) * 0.1)
|
|
|
122 |
return None
|
123 |
|
124 |
+
def create_ee_map(farm_id, date_str, coordinates_df, layer_type="NDVI", heatmap=False):
|
125 |
+
farm_row = coordinates_df[coordinates_df['مزرعه'] == farm_id].iloc[0]
|
126 |
+
lat, lon = farm_row['عرض جغرافیایی'], farm_row['طول جغرافیایی']
|
127 |
+
m = folium.Map(location=[lat, lon], zoom_start=14, tiles='CartoDB positron')
|
128 |
+
date_obj = datetime.strptime(date_str, '%Y-%m-%d')
|
129 |
+
start_date = (date_obj - timedelta(days=5)).strftime('%Y-%m-%d')
|
130 |
+
end_date = (date_obj + timedelta(days=5)).strftime('%Y-%m-%d')
|
131 |
+
region = ee.Geometry.Point([lon, lat]).buffer(1500)
|
132 |
+
s2 = ee.ImageCollection('COPERNICUS/S2_SR').filterDate(start_date, end_date).filterBounds(region).sort('CLOUDY_PIXEL_PERCENTAGE').first()
|
133 |
+
|
134 |
+
layers = {
|
135 |
+
"NDVI": (s2.normalizedDifference(['B8', 'B4']).rename('NDVI'), {'min': -0.2, 'max': 0.8, 'palette': ['#ff0000', '#ff4500', '#ffd700', '#32cd32', '#006400']}, 'شاخص پوشش گیاهی (NDVI)'),
|
136 |
+
"NDMI": (s2.normalizedDifference(['B8', 'B11']).rename('NDMI'), {'min': -0.5, 'max': 0.5, 'palette': ['#8b0000', '#ff8c00', '#00ced1', '#00b7eb', '#00008b']}, 'شاخص رطوبت (NDMI)'),
|
137 |
+
"EVI": (s2.normalizedDifference(['B8', 'B4']).multiply(2.5).divide(s2.select('B8').add(s2.select('B4').multiply(6)).subtract(s2.select('B2').multiply(7.5)).add(1)).rename('EVI'), {'min': 0, 'max': 1, 'palette': ['#d73027', '#f46d43', '#fdae61', '#fee08b', '#4caf50']}, 'شاخص پیشرفته گیاهی (EVI)'),
|
138 |
+
"NDWI": (s2.normalizedDifference(['B3', 'B8']).rename('NDWI'), {'min': -0.5, 'max': 0.5, 'palette': ['#00008b', '#00b7eb', '#add8e6', '#fdae61', '#d73027']}, 'شاخص آب (NDWI)'),
|
139 |
+
"SoilMoisture": (ee.ImageCollection('COPERNICUS/S1_GRD').filterDate(start_date, end_date).filterBounds(region).sort('system:time_start').first().select('VV').rename('SoilMoisture'), {'min': -25, 'max': -5, 'palette': ['#00008b', '#add8e6', '#ffffff']}, 'رطوبت خاک (Soil Moisture)')
|
140 |
+
}
|
141 |
+
|
142 |
+
index, viz_params, legend_title = layers[layer_type]
|
143 |
+
map_id_dict = ee.Image(index).getMapId(viz_params if not heatmap else {**viz_params, 'opacity': 0.7})
|
144 |
+
folium.TileLayer(tiles=map_id_dict['tile_fetcher'].url_format, attr='Google Earth Engine', name=layer_type, overlay=True, control=True).add_to(m)
|
145 |
+
folium.Marker([lat, lon], popup=f'مزرعه {farm_id}', tooltip=f'مزرعه {farm_id}', icon=folium.Icon(color='green', icon='leaf')).add_to(m)
|
146 |
+
folium.Circle([lat, lon], radius=1500, color='green', fill=True, fill_opacity=0.1).add_to(m)
|
147 |
+
folium.LayerControl().add_to(m)
|
148 |
+
|
149 |
+
legend_html = f'''
|
150 |
+
<div style="position: fixed; bottom: 50px; right: 50px; border: 2px solid grey; z-index: 9999; background-color: white; padding: 10px; border-radius: 5px; direction: rtl;">
|
151 |
+
<div style="font-size: 14px; font-weight: bold; margin-bottom: 5px;">{legend_title}</div>
|
152 |
+
<div style="display: flex; align-items: center; margin-bottom: 5px;"><div style="background: {viz_params['palette'][0]}; width: 20px; height: 20px; margin-left: 5px;"></div><span>کم</span></div>
|
153 |
+
<div style="display: flex; align-items: center; margin-bottom: 5px;"><div style="background: {viz_params['palette'][2]}; width: 20px; height: 20px; margin-left: 5px;"></div><span>متوسط</span></div>
|
154 |
+
<div style="display: flex; align-items: center;"><div style="background: {viz_params['palette'][-1]}; width: 20px; height: 20px; margin-left: 5px;"></div><span>زیاد</span></div>
|
155 |
+
</div>
|
156 |
+
'''
|
157 |
+
m.get_root().html.add_child(folium.Element(legend_html))
|
158 |
+
return m
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
159 |
|
|
|
160 |
def generate_mock_growth_data(farm_data, selected_variety="all", selected_age="all"):
|
161 |
weeks = list(range(1, 23))
|
162 |
+
filtered_farms = farm_data[(farm_data['واریته'] == selected_variety) if selected_variety != "all" else farm_data.index] \
|
163 |
+
[(farm_data['سن'] == selected_age) if selected_age != "all" else farm_data.index]
|
164 |
+
farm_growth_data = [
|
165 |
+
{'farm_id': farm['مزرعه'], 'variety': farm['واریته'], 'age': farm['سن'], 'weeks': weeks,
|
166 |
+
'heights': [round(np.random.uniform(50, 100) + np.random.uniform(5, 15) * week) for week in weeks]}
|
167 |
+
for _, farm in filtered_farms.iterrows()
|
168 |
+
]
|
169 |
+
avg_heights = [round(np.mean([farm['heights'][week-1] for farm in farm_growth_data])) if farm_growth_data else 0 for week in weeks]
|
170 |
+
return {'individual': farm_growth_data, 'average': {'farm_id': 'میانگین', 'variety': 'همه', 'age': 'همه', 'weeks': weeks, 'heights': avg_heights}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
171 |
|
|
|
172 |
def calculate_farm_stats(farm_id, layer_type="NDVI"):
|
173 |
+
stats_ranges = {
|
174 |
+
"NDVI": (0.6, 0.8, 0.2, 0.3, 0.1, 0.2, 0.05, 0.15, 0.3, "تنش سلامتی: NDVI پایین"),
|
175 |
+
"NDMI": (0.3, 0.5, 0.2, 0.3, 0.1, 0.2, 0.05, 0.15, 0.1, "تنش آبی: NDMI پایین"),
|
176 |
+
"EVI": (0.4, 0.6, 0.2, 0.3, 0.1, 0.2, 0.05, 0.15, None, None),
|
177 |
+
"NDWI": (-0.1, 0.1, 0.2, 0.3, 0.1, 0.2, 0.05, 0.15, None, None),
|
178 |
+
"SoilMoisture": (-20, -10, 5, 10, 5, 10, 2, 5, -20, "تنش خاک: رطوبت پایین")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
179 |
}
|
180 |
+
mean_range, min_adj1, min_adj2, max_adj1, max_adj2, std_range = stats_ranges[layer_type][:6]
|
181 |
+
mean = round(np.random.uniform(*mean_range), 2)
|
182 |
+
min_val = round(mean - np.random.uniform(min_adj1, min_adj2), 2)
|
183 |
+
max_val = round(mean + np.random.uniform(max_adj1, max_adj2), 2)
|
184 |
+
std_dev = round(np.random.uniform(*std_range), 2)
|
185 |
+
hist_data = np.clip(np.random.normal(mean, std_dev, 1000), min_val, max_val)
|
186 |
+
stress_threshold, stress_message = stats_ranges[layer_type][8:]
|
187 |
+
stress_alerts = [stress_message] if stress_threshold and mean < stress_threshold else []
|
188 |
+
return {'mean': mean, 'min': min_val, 'max': max_val, 'std_dev': std_dev, 'histogram_data': hist_data, 'stress_alerts': stress_alerts}
|
189 |
|
|
|
190 |
def generate_time_series_data(farm_id, layer_type, period="weekly"):
|
191 |
+
periods = {"weekly": 30, "monthly": 90, "seasonal": 365}
|
192 |
+
freqs = {"weekly": 'W', "monthly": 'M', "seasonal": 'Q'}
|
193 |
+
dates = pd.date_range(end=datetime.now(), periods=periods[period], freq=freqs[period])
|
194 |
+
ranges = {"NDVI": (0.7, 0.1), "NDMI": (0.4, 0.1), "EVI": (0.5, 0.1), "NDWI": (0, 0.1), "SoilMoisture": (-15, 5)}
|
195 |
+
return pd.DataFrame({'Date': dates, layer_type: np.random.normal(*ranges[layer_type], len(dates))})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
196 |
|
197 |
+
def predict_growth_advanced(historical_data):
|
|
|
198 |
weeks = historical_data['Week'].values.reshape(-1, 1)
|
199 |
heights = historical_data['Height'].values
|
200 |
+
rf_model = RandomForestRegressor(n_estimators=50, random_state=42) # Reduced trees for speed
|
|
|
|
|
201 |
rf_model.fit(weeks, heights)
|
202 |
future_weeks = np.array(range(16, 23)).reshape(-1, 1)
|
203 |
rf_predictions = rf_model.predict(future_weeks)
|
204 |
|
|
|
205 |
scaler = MinMaxScaler()
|
206 |
scaled_heights = scaler.fit_transform(heights.reshape(-1, 1))
|
207 |
+
X = np.array([scaled_heights[i-3:i] for i in range(3, len(scaled_heights))])
|
208 |
+
y = scaled_heights[3:]
|
209 |
+
lstm_model = Sequential([LSTM(20, activation='relu', input_shape=(3, 1)), Dense(1)]) # Simplified LSTM
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
210 |
lstm_model.compile(optimizer='adam', loss='mse')
|
211 |
+
lstm_model.fit(X, y, epochs=20, batch_size=1, verbose=0) # Reduced epochs
|
|
|
212 |
lstm_input = scaled_heights[-3:].reshape((1, 3, 1))
|
213 |
+
lstm_predictions = [scaler.inverse_transform(lstm_model.predict(lstm_input, verbose=0)).flatten()[0] for _ in range(7)]
|
|
|
|
|
|
|
|
|
|
|
|
|
214 |
return rf_predictions, lstm_predictions
|
215 |
|
|
|
216 |
def cluster_farms(farm_data):
|
217 |
+
# Clean data: Convert to numeric, replace invalid values with NaN, then fill with median
|
218 |
+
features = farm_data[['مساحت', 'سن']].apply(pd.to_numeric, errors='coerce').fillna({'مساحت': farm_data['مساحت'].median(), 'سن': farm_data['سن'].median()})
|
219 |
+
kmeans = KMeans(n_clusters=3, random_state=42, n_init=10) # Added n_init for explicit control
|
220 |
farm_data['Cluster'] = kmeans.fit_predict(features)
|
221 |
return farm_data, kmeans.cluster_centers_
|
222 |
|
223 |
+
# --- Initialization ---
|
224 |
ee_initialized = initialize_earth_engine()
|
225 |
+
farm_df = load_data("پایگاه داده (1).csv", "خطا در بارگذاری دادههای مزارع")
|
226 |
+
coordinates_df = load_data("farm_coordinates.csv", "خطا در بارگذاری دادههای مختصات")
|
|
|
|
|
227 |
lottie_farm = load_lottie_url('https://assets5.lottiefiles.com/packages/lf20_ystsffqy.json')
|
228 |
lottie_analysis = load_lottie_url('https://assets3.lottiefiles.com/packages/lf20_qp1q7mct.json')
|
229 |
lottie_report = load_lottie_url('https://assets9.lottiefiles.com/packages/lf20_vwcugezu.json')
|
|
|
|
|
230 |
if 'heights_df' not in st.session_state:
|
231 |
st.session_state.heights_df = pd.DataFrame(columns=['Farm_ID', 'Week', 'Measurement_Date', 'Height', 'Station1', 'Station2', 'Station3', 'Station4', 'Station5', 'Groundwater1', 'Groundwater2', 'Sheath_Moisture', 'Nitrogen', 'Variety', 'Age', 'Area', 'Channel', 'Administration'])
|
232 |
|
233 |
+
# --- Main UI ---
|
234 |
+
st.markdown('<div class="main-header animate-load"><h1>سامانه هوشمند پایش مزارع نیشکر دهخدا</h1><p>پلتفرم جامع مدیریت و پایش مزارع نیشکر</p></div>', unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
235 |
selected = option_menu(
|
236 |
+
menu_title=None, options=["داشبورد", "نقشه مزارع", "ورود اطلاعات", "تحلیل دادهها", "گزارشگیری", "تنظیمات"],
|
237 |
+
icons=["speedometer2", "map", "pencil-square", "graph-up", "file-earmark-text", "gear"], default_index=0, orientation="horizontal",
|
238 |
+
styles={"container": {"padding": "0!important", "background-color": "transparent", "margin-bottom": "20px"}, "icon": {"color": "#1a8754", "font-size": "18px"},
|
239 |
+
"nav-link": {"font-size": "16px", "text-align": "center", "margin": "0px", "--hover-color": "#e9f7ef", "border-radius": "10px"}, "nav-link-selected": {"background-color": "#1a8754", "color": "white", "font-weight": "600"}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
240 |
)
|
241 |
|
242 |
+
# --- Dashboard ---
|
243 |
if selected == "داشبورد":
|
244 |
st.markdown('<div class="animate-load">', unsafe_allow_html=True)
|
245 |
col1, col2, col3, col4 = st.columns(4)
|
246 |
+
for col, value, label in [(col1, len(farm_df), "تعداد مزارع"), (col2, int(len(farm_df) * 0.85), "مزارع فعال"), (col3, 175, "میانگین ارتفاع (cm)"), (col4, 68, "میانگین رطوبت (%)")]:
|
247 |
+
col.markdown(f'<div class="metric-card"><div class="metric-value">{value}</div><div class="metric-label">{label}</div></div>', unsafe_allow_html=True)
|
248 |
|
249 |
+
st.markdown("### فیلترها")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
250 |
col_filter1, col_filter2, col_filter3 = st.columns(3)
|
251 |
+
filters = [col_filter1.multiselect("واریته", ["همه"] + list(farm_df['واریته'].unique()), default="همه"),
|
252 |
+
col_filter2.multiselect("سن", ["همه"] + list(farm_df['سن'].unique()), default="همه"),
|
253 |
+
col_filter3.multiselect("کانال", ["همه"] + list(farm_df['کانال'].unique()), default="همه")]
|
254 |
+
filtered_df = farm_df[([(v in filters[0]) for v in farm_df['واریته']] if "همه" not in filters[0] else farm_df.index) &
|
255 |
+
([(a in filters[1]) for a in farm_df['سن']] if "همه" not in filters[1] else farm_df.index) &
|
256 |
+
([(c in filters[2]) for c in farm_df['کانال']] if "همه" not in filters[2] else farm_df.index)]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
257 |
|
258 |
tab1, tab2, tab3, tab4 = st.tabs(["نمای کلی", "نقشه مزارع", "نمودارها", "دادهها"])
|
|
|
259 |
with tab1:
|
|
|
260 |
col1, col2 = st.columns(2)
|
261 |
+
with col1: st.plotly_chart(px.pie(filtered_df['واریته'].value_counts().reset_index(), values='count', names='واریته', title='توزیع واریتهها', color_discrete_sequence=px.colors.sequential.Greens_r), use_container_width=True)
|
262 |
+
with col2: st.plotly_chart(px.pie(filtered_df['سن'].value_counts().reset_index(), values='count', names='سن', title='توزیع سن محصول', color_discrete_sequence=px.colors.sequential.Blues_r), use_container_width=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
263 |
col1, col2, col3 = st.columns(3)
|
264 |
+
col1.metric("تعداد مزارع", len(filtered_df))
|
265 |
+
col2.metric("مساحت کل (هکتار)", f"{filtered_df['مساحت'].astype(float).sum():.2f}")
|
266 |
+
col3.metric("تعداد کانالها", filtered_df['کانال'].nunique())
|
267 |
+
st_lottie(lottie_farm, height=300)
|
|
|
268 |
with tab2:
|
|
|
269 |
if not coordinates_df.empty:
|
270 |
m = folium.Map(location=[31.45, 48.72], zoom_start=12, tiles='CartoDB positron')
|
271 |
for _, farm in coordinates_df.iterrows():
|
272 |
+
if farm['مزرعه'] in filtered_df['مزرعه'].values:
|
273 |
+
farm_info = filtered_df[filtered_df['مزرعه'] == farm['مزرعه']].iloc[0]
|
274 |
+
popup_text = f"<div style='direction: rtl;'><h4>مزرعه {farm['مزرعه']}</h4><p>واریته: {farm_info['واریته']}</p><p>سن: {farm_info['سن']}</p><p>مساحت: {farm_info['مساحت']} هکتار</p></div>"
|
275 |
+
folium.Marker([farm['عرض جغرافیایی'], farm['طول جغرافیایی']], popup=folium.Popup(popup_text, max_width=300), tooltip=f"مزرعه {farm['مزرعه']}", icon=folium.Icon(color='green', icon='leaf')).add_to(m)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
276 |
st.markdown('<div class="map-container">', unsafe_allow_html=True)
|
277 |
folium_static(m, width=1000, height=600)
|
278 |
st.markdown('</div>', unsafe_allow_html=True)
|
|
|
279 |
with tab3:
|
|
|
280 |
growth_data = generate_mock_growth_data(filtered_df)
|
281 |
chart_tab1, chart_tab2 = st.tabs(["میانگین رشد", "رشد مزارع فردی"])
|
282 |
+
with chart_tab1: st.plotly_chart(go.Figure(go.Scatter(x=growth_data['average']['weeks'], y=growth_data['average']['heights'], mode='lines+markers', name='میانگین رشد', line=dict(color='#1a8754', width=3))).update_layout(title='میانگین رشد هفتگی', xaxis_title='هفته', yaxis_title='ارتفاع (cm)', font=dict(family='Vazirmatn')), use_container_width=True)
|
283 |
+
with chart_tab2:
|
284 |
+
fig = go.Figure([go.Scatter(x=farm_data['weeks'], y=farm_data['heights'], mode='lines+markers', name=f"مزرعه {farm_data['farm_id']}") for farm_data in growth_data['individual'][:5]])
|
285 |
+
fig.update_layout(title='رشد هفتگی مزارع فردی', xaxis_title='هفته', yaxis_title='ارتفاع (cm)', font=dict(family='Vazirmatn'))
|
|
|
|
|
|
|
|
|
|
|
|
|
286 |
st.plotly_chart(fig, use_container_width=True)
|
287 |
+
with tab4: st.dataframe(filtered_df, use_container_width=True, height=400, hide_index=True)
|
|
|
|
|
|
|
288 |
st.markdown('</div>', unsafe_allow_html=True)
|
289 |
|
290 |
+
# --- Map ---
|
291 |
elif selected == "نقشه مزارع":
|
292 |
st.markdown('<div class="animate-load">', unsafe_allow_html=True)
|
293 |
st.markdown("## نقشه مزارع با شاخصهای ماهوارهای")
|
294 |
col1, col2 = st.columns([1, 3])
|
295 |
with col1:
|
|
|
296 |
selected_farm = st.selectbox("انتخاب مزرعه", options=coordinates_df['مزرعه'].tolist(), format_func=lambda x: f"مزرعه {x}")
|
297 |
selected_date = st.date_input("انتخاب تاریخ", value=datetime.now())
|
298 |
+
selected_layer = st.selectbox("انتخاب شاخص", options=["NDVI", "NDMI", "EVI", "NDWI", "SoilMoisture"], format_func=lambda x: {"NDVI": "NDVI", "NDMI": "NDMI", "EVI": "EVI", "NDWI": "NDWI", "SoilMoisture": "رطوبت خاک"}[x])
|
299 |
+
heatmap_toggle = st.checkbox("نمایش نقشه حرارتی")
|
300 |
+
generate_map = st.button("تولید نقشه", type="primary")
|
|
|
|
|
301 |
with col2:
|
302 |
map_tab, stats_tab = st.tabs(["نقشه", "آمار و تحلیل"])
|
303 |
with map_tab:
|
304 |
if generate_map or 'last_map' not in st.session_state:
|
305 |
with st.spinner('در حال تولید نقشه...'):
|
306 |
+
m = create_ee_map(selected_farm, selected_date.strftime('%Y-%m-%d'), coordinates_df, selected_layer, heatmap_toggle)
|
307 |
if m:
|
308 |
st.session_state.last_map = m
|
309 |
folium_static(m, width=800, height=600)
|
310 |
+
st.success(f"نقشه {selected_layer} تولید شد.")
|
311 |
elif 'last_map' in st.session_state:
|
312 |
folium_static(st.session_state.last_map, width=800, height=600)
|
|
|
313 |
with stats_tab:
|
314 |
if 'last_map' in st.session_state:
|
315 |
stats = calculate_farm_stats(selected_farm, selected_layer)
|
316 |
col1, col2, col3, col4 = st.columns(4)
|
317 |
+
for col, label, value in [(col1, f"میانگین {selected_layer}", stats["mean"]), (col2, f"حداکثر {selected_layer}", stats["max"]), (col3, f"حداقل {selected_layer}", stats["min"]), (col4, "انحراف معیار", stats["std_dev"])]:
|
318 |
+
col.metric(label, value)
|
319 |
+
for alert in stats['stress_alerts']: st.warning(alert)
|
320 |
+
st.plotly_chart(px.histogram(x=stats["histogram_data"], nbins=20, title=f"توزیع {selected_layer}", labels={"x": selected_layer, "y": "فراوانی"}), use_container_width=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
321 |
st.markdown("### تحلیل سری زمانی")
|
322 |
period = st.selectbox("بازه زمانی", ["weekly", "monthly", "seasonal"], format_func=lambda x: {"weekly": "هفتگی", "monthly": "ماهانه", "seasonal": "فصلی"}[x])
|
323 |
ts_data = generate_time_series_data(selected_farm, selected_layer, period)
|
324 |
+
st.plotly_chart(px.line(ts_data, x='Date', y=selected_layer, title=f"روند {selected_layer}"), use_container_width=True)
|
|
|
325 |
st.markdown('</div>', unsafe_allow_html=True)
|
326 |
|
327 |
+
# --- Data Entry ---
|
328 |
elif selected == "ورود اطلاعات":
|
329 |
st.markdown('<div class="animate-load">', unsafe_allow_html=True)
|
330 |
+
st.markdown("## ورود اطلاعات روزانه")
|
331 |
tab1, tab2 = st.tabs(["ورود دستی", "آپلود فایل"])
|
332 |
with tab1:
|
333 |
col1, col2 = st.columns(2)
|
334 |
+
selected_week, selected_day = col1.selectbox("انتخاب هفته", [str(i) for i in range(1, 23)], format_func=lambda x: f"هفته {x}"), col2.selectbox("انتخاب روز", ["شنبه", "یکشنبه", "دوشنبه", "سهشنبه", "چهارشنبه", "پنجشنبه"])
|
|
|
|
|
|
|
|
|
335 |
filtered_farms = farm_df[farm_df['روز'] == selected_day]
|
336 |
data_key = f"data_{selected_week}_{selected_day}"
|
337 |
if data_key not in st.session_state:
|
338 |
+
st.session_state[data_key] = pd.DataFrame({col: [0 if col != 'مزرعه' else farm for farm in filtered_farms['مزرعه']] for col in ['مزرعه', 'ایستگاه 1', 'ایستگاه 2', 'ایستگاه 3', 'ایستگاه 4', 'ایستگاه 5', 'چاهک 1', 'چاهک 2', 'رطوبت غلاف', 'نیتروژن', 'میانگین ارتفاع']})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
339 |
edited_df = st.data_editor(st.session_state[data_key], use_container_width=True)
|
340 |
+
edited_df['میانگین ارتفاع'] = [round(np.mean([s for s in row[1:6] if s > 0])) if any(s > 0 for s in row[1:6]) else 0 for row in edited_df.iloc[:, 1:6].values]
|
|
|
|
|
|
|
|
|
341 |
st.session_state[data_key] = edited_df
|
|
|
342 |
if st.button("ذخیره اطلاعات", type="primary"):
|
343 |
new_data = edited_df.copy()
|
344 |
+
new_data['Farm_ID'], new_data['Week'], new_data['Measurement_Date'], new_data['Height'] = new_data['مزرعه'], int(selected_week), (datetime.now() - timedelta(weeks=(22 - int(selected_week)))).strftime('%Y-%m-%d'), new_data['میانگین ارتفاع']
|
|
|
|
|
|
|
345 |
new_data = new_data.merge(farm_df[['مزرعه', 'واریته', 'سن', 'مساحت', 'کانال', 'اداره']], left_on='Farm_ID', right_on='مزرعه', how='left')
|
346 |
st.session_state.heights_df = pd.concat([st.session_state.heights_df, new_data], ignore_index=True)
|
347 |
st.success(f"دادههای هفته {selected_week} ذخیره شدند.")
|
348 |
with tab2:
|
349 |
+
uploaded_file = st.file_uploader("فایل اکسل یا CSV", type=["xlsx", "xls", "csv"])
|
350 |
if uploaded_file:
|
351 |
df = pd.read_csv(uploaded_file) if uploaded_file.name.endswith('.csv') else pd.read_excel(uploaded_file)
|
352 |
st.dataframe(df)
|
353 |
if st.button("ذخیره فایل"): st.success("فایل ذخیره شد.")
|
354 |
st.markdown('</div>', unsafe_allow_html=True)
|
355 |
|
356 |
+
# --- Data Analysis ---
|
357 |
elif selected == "تحلیل دادهها":
|
358 |
st.markdown('<div class="animate-load">', unsafe_allow_html=True)
|
359 |
st.markdown("## تحلیل هوشمند دادهها")
|
360 |
tab1, tab2, tab3, tab4 = st.tabs(["تحلیل رشد", "مقایسه واریتهها", "تحلیل رطوبت", "پیشبینی"])
|
|
|
361 |
with tab1:
|
362 |
growth_data = generate_mock_growth_data(farm_df)
|
363 |
+
chart_df = pd.DataFrame([{'Farm': farm['farm_id'], 'Week': week, 'Height': height} for farm in growth_data['individual'] for week, height in zip(farm['weeks'], farm['heights'])])
|
364 |
+
st.altair_chart(alt.Chart(chart_df).mark_line(point=True).encode(x='Week:Q', y='Height:Q', color='Farm:N').interactive(), use_container_width=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
365 |
with tab2:
|
366 |
variety_heights = {variety: np.random.normal(150, 20, 100) for variety in farm_df['واریته'].unique()}
|
367 |
+
fig = go.Figure([go.Box(y=heights, name=variety) for variety, heights in variety_heights.items()])
|
368 |
+
fig.update_layout(title='مقایسه ارتفاع بر اساس واریته', yaxis_title='ارتفاع (cm)', font=dict(family="Vazirmatn"))
|
|
|
|
|
369 |
st.plotly_chart(fig, use_container_width=True)
|
|
|
370 |
with tab3:
|
371 |
+
moisture_df = pd.DataFrame([{'Farm': farm, 'Date': date, 'Moisture': max(0, min(100, np.random.uniform(50, 80) + np.random.normal(0, 5)))} for farm in farm_df['مزرعه'].unique()[:10] for date in pd.date_range(end=datetime.now(), periods=30)])
|
372 |
+
st.plotly_chart(px.line(moisture_df, x='Date', y='Moisture', color='Farm', title='روند رطوبت مزارع'), use_container_width=True)
|
373 |
+
corr_df = pd.DataFrame({'Moisture': moisture_df['Moisture'], 'Height': moisture_df['Moisture'] * 1.5 + np.random.normal(0, 20, len(moisture_df))})
|
374 |
+
st.plotly_chart(px.scatter(corr_df, x='Moisture', y='Height', title='همبستگی رطوبت و ارتفاع', trendline='ols'), use_container_width=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
375 |
st.info(f"ضریب همبستگی: {corr_df['Moisture'].corr(corr_df['Height']):.2f}")
|
|
|
376 |
with tab4:
|
377 |
selected_farm = st.selectbox("انتخاب مزرعه", options=farm_df['مزرعه'].tolist(), format_func=lambda x: f"مزرعه {x}")
|
378 |
+
historical_df = pd.DataFrame({'Week': range(1, 16), 'Height': [50 + i * 10 + np.random.normal(0, 5) for i in range(15)]})
|
379 |
+
rf_preds, lstm_preds = predict_growth_advanced(historical_df)
|
380 |
+
fig = go.Figure([go.Scatter(x=historical_df['Week'], y=historical_df['Height'], mode='lines+markers', name='دادههای تاریخی'),
|
381 |
+
go.Scatter(x=range(16, 23), y=rf_preds, mode='lines+markers', name='پیشبینی RF', line=dict(dash='dash')),
|
382 |
+
go.Scatter(x=range(16, 23), y=lstm_preds, mode='lines+markers', name='پیشبینی LSTM', line=dict(dash='dot'))])
|
383 |
+
fig.update_layout(title=f'پیشبینی رشد مزرعه {selected_farm}', xaxis_title='هفته', yaxis_title='ارتفاع (cm)', font=dict(family='Vazirmatn'))
|
|
|
|
|
|
|
|
|
|
|
384 |
st.plotly_chart(fig, use_container_width=True)
|
|
|
|
|
385 |
clustered_df, centers = cluster_farms(farm_df)
|
386 |
+
st.plotly_chart(px.scatter(clustered_df, x='مساحت', y='سن', color='Cluster', title='گروهبندی مزارع', labels={'مساحت': 'مساحت (هکتار)', 'سن': 'سن (سال)'}), use_container_width=True)
|
|
|
387 |
st.markdown('</div>', unsafe_allow_html=True)
|
388 |
|
389 |
+
# --- Reporting ---
|
390 |
elif selected == "گزارشگیری":
|
391 |
st.markdown('<div class="animate-load">', unsafe_allow_html=True)
|
392 |
st.markdown("## گزارشگیری پیشرفته")
|
393 |
+
start_date, end_date = st.date_input("تاریخ شروع", value=datetime.now() - timedelta(days=30)), st.date_input("تاریخ پایان", value=datetime.now())
|
|
|
394 |
report_type = st.selectbox("نوع گزارش", ["گزارش کلی", "گزارش رشد", "گزارش رطوبت", "گزارش مقایسهای واریتهها"])
|
395 |
if st.button("تولید گزارش"):
|
396 |
with st.spinner('در حال تولید گزارش...'):
|
397 |
+
time.sleep(1) # Reduced for speed
|
398 |
if report_type == "گزارش کلی":
|
399 |
+
st.markdown("### گزارش کلی")
|
400 |
col1, col2 = st.columns(2)
|
401 |
+
with col1: st.plotly_chart(px.pie(values=farm_df['اداره'].value_counts().values, names=farm_df['اداره'].value_counts().index, title='توزیع مزارع بر اساس اداره'))
|
402 |
+
with col2: st.plotly_chart(px.line(x=range(1, 23), y=[100 + i * 5 + np.random.normal(0, 10) for i in range(22)], title='روند رشد کلی', labels={'x': 'هفته', 'y': 'ارتفاع (cm)'}))
|
|
|
|
|
|
|
|
|
|
|
|
|
403 |
st.markdown('</div>', unsafe_allow_html=True)
|
404 |
|
405 |
+
# --- Settings ---
|
406 |
elif selected == "تنظیمات":
|
407 |
st.markdown('<div class="animate-load">', unsafe_allow_html=True)
|
408 |
+
st.markdown("## تنظیمات")
|
409 |
tab1, tab2 = st.tabs(["تنظیمات کاربری", "تنظیمات سیستم"])
|
410 |
+
with tab1: st.text_input("نام کاربری", value="کاربر نمونه"), st.text_input("ایمیل", value="[email protected]")
|
411 |
+
with tab2: st.selectbox("زبان سیستم", ["فارسی", "English", "العربية"]), st.selectbox("فرمت تاریخ", ["YYYY/MM/DD", "DD/MM/YYYY", "MM/DD/YYYY"])
|
|
|
|
|
|
|
|
|
412 |
st.markdown('</div>', unsafe_allow_html=True)
|
413 |
|
414 |
+
# --- Footer ---
|
415 |
st.markdown("""
|
416 |
<footer style="position: fixed; left: 0; bottom: 0; width: 100%; background-color: #1a8754; color: white; text-align: center; padding: 10px 0;">
|
417 |
+
<p>© سامانه هوشمند پایش مزارع نیشکر دهخدا | طراحی شده توسط تیم مطالعات کاربردی توسعه</p>
|
418 |
</footer>
|
419 |
""", unsafe_allow_html=True)
|
420 |
st.markdown('</body>', unsafe_allow_html=True)
|