Esmaeilkianii commited on
Commit
536cc28
·
verified ·
1 Parent(s): 0831686

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +136 -559
app.py CHANGED
@@ -2,24 +2,12 @@ import streamlit as st
2
  import pandas as pd
3
  import numpy as np
4
  import plotly.express as px
5
- import plotly.graph_objects as go
6
- from datetime import datetime, timedelta
7
  import folium
8
  from streamlit_folium import folium_static
9
- import ee
10
- import os
11
- import json
12
- from io import BytesIO
13
- import xlsxwriter
14
- from reportlab.lib.pagesizes import letter
15
- from reportlab.pdfgen import canvas
16
- from reportlab.lib import colors
17
- import time
18
- from sklearn.ensemble import RandomForestRegressor
19
- from sklearn.model_selection import train_test_split
20
  import jdatetime
21
 
22
- # Page configuration
23
  st.set_page_config(
24
  page_title="سامانه هوشمند پایش مزارع نیشکر دهخدا",
25
  page_icon="🌿",
@@ -27,7 +15,7 @@ st.set_page_config(
27
  initial_sidebar_state="collapsed"
28
  )
29
 
30
- # Modern and vibrant CSS with graphical menu
31
  st.markdown("""
32
  <style>
33
  @font-face {
@@ -36,93 +24,114 @@ st.markdown("""
36
  }
37
  * { font-family: 'Vazir', sans-serif !important; }
38
  body {
39
- background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
40
- color: #1a3c34;
41
  margin: 0;
42
  padding: 0;
43
  }
44
- .top-bar {
45
- background: linear-gradient(90deg, #1b5e20 0%, #4caf50 100%);
46
- padding: 20px;
47
  text-align: center;
48
  color: white;
49
- font-size: 1.2rem;
50
- box-shadow: 0 4px 15px rgba(0,0,0,0.3);
51
- border-radius: 0 0 20px 20px;
52
- animation: slideDown 0.8s ease-out;
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  }
54
- .top-menu {
55
  display: flex;
56
  justify-content: center;
57
- gap: 20px;
58
  padding: 20px;
59
- background: rgba(255, 255, 255, 0.1);
60
- backdrop-filter: blur(10px);
61
- border-radius: 20px;
62
- box-shadow: 0 8px 32px rgba(0,0,0,0.2);
63
- margin: 20px auto;
64
- max-width: 1200px;
65
  }
66
- .top-menu button {
67
- background: linear-gradient(135deg, #2e7d32 0%, #81c784 100%);
68
  color: white;
69
  border: none;
70
- border-radius: 15px;
71
- padding: 15px 25px;
72
- font-size: 1.2rem;
73
  font-weight: bold;
74
- box-shadow: 0 5px 15px rgba(0,0,0,0.3);
75
- transition: all 0.3s ease;
76
  cursor: pointer;
77
  display: flex;
78
  align-items: center;
79
- gap: 10px;
 
 
80
  }
81
- .top-menu button:hover {
82
- transform: translateY(-5px) scale(1.05);
83
- box-shadow: 0 10px 25px rgba(0,0,0,0.4);
84
- background: linear-gradient(135deg, #388e3c 0%, #a5d6a7 100%);
 
 
 
 
 
85
  }
86
- .top-menu button:active {
87
- transform: translateY(2px);
88
- box-shadow: 0 3px 10px rgba(0,0,0,0.2);
89
  }
90
- .card {
91
- background: rgba(255, 255, 255, 0.97);
92
- border-radius: 25px;
93
- padding: 2rem;
94
- box-shadow: 0 15px 35px rgba(0,0,0,0.1);
95
- margin-bottom: 2rem;
96
- animation: floatIn 0.8s ease-out;
97
- border: 1px solid #e0e0e0;
98
  }
99
- .metric-card {
100
- background: white;
101
- border-radius: 20px;
102
- padding: 1.5rem;
103
- text-align: center;
104
- box-shadow: 0 8px 25px rgba(0,0,0,0.1);
105
- transition: all 0.3s ease;
106
- border-left: 5px solid #1b5e20;
107
  }
108
- .metric-card:hover {
109
- transform: translateY(-8px);
110
- box-shadow: 0 15px 35px rgba(0,0,0,0.2);
 
 
 
 
111
  }
112
  .header {
113
- color: #1b5e20;
114
  text-align: center;
115
- font-size: 2.5rem;
116
  font-weight: bold;
117
- text-shadow: 2px 2px 5px rgba(0,0,0,0.1);
118
- animation: fadeIn 1s ease-in-out;
119
  }
120
- @keyframes slideDown {
121
- 0% { transform: translateY(-100%); opacity: 0; }
122
- 100% { transform: translateY(0); opacity: 1; }
 
 
 
 
 
 
123
  }
124
  @keyframes floatIn {
125
- 0% { transform: translateY(20px); opacity: 0; }
126
  100% { transform: translateY(0); opacity: 1; }
127
  }
128
  @keyframes fadeIn {
@@ -132,7 +141,7 @@ st.markdown("""
132
  </style>
133
  """, unsafe_allow_html=True)
134
 
135
- # Cached data loading for farm metadata
136
  @st.cache_data
137
  def load_data():
138
  try:
@@ -147,516 +156,84 @@ farms_df, coordinates_df = load_data()
147
  if farms_df is None or coordinates_df is None:
148
  st.stop()
149
 
150
- # Earth Engine initialization
151
- @st.cache_resource
152
- def initialize_earth_engine():
153
- try:
154
- credentials_path = "ee-esmaeilkiani13877-cfdea6eaf411.json"
155
- if not os.path.exists(credentials_path):
156
- credentials_dict = {
157
- "type": "service_account",
158
- "project_id": "your-project-id",
159
- "private_key": "your-private-key",
160
- "client_email": "your-service-account-email",
161
- "client_id": "your-client-id",
162
- }
163
- with open(credentials_path, 'w') as f:
164
- json.dump(credentials_dict, f)
165
- credentials = ee.ServiceAccountCredentials(
166
- "dehkhodamap-e9f0da4ce9f6514021@ee-esmaeilkiani13877.iam.gserviceaccount.com",
167
- credentials_path
168
- )
169
- ee.Initialize(credentials)
170
- return True
171
- except Exception as e:
172
- st.error(f"خطا در اتصال به Earth Engine: {e}")
173
- return False
174
-
175
- earth_engine_initialized = initialize_earth_engine()
176
-
177
- # Initialize session state for dynamic data
178
  if 'heights_df' not in st.session_state:
179
  st.session_state.heights_df = pd.DataFrame(columns=[
180
  'Farm_ID', 'Week', 'Measurement_Date', 'Height', 'Station1', 'Station2', 'Station3',
181
- 'Station4', 'Station5', 'Groundwater1', 'Groundwater2', 'Sheath_Moisture', 'Nitrogen',
182
- 'Variety', 'Age', 'Area', 'Channel', 'Administration'
183
  ])
 
 
184
 
185
- # Convert Gregorian to Jalali date and get weekday
186
  def get_jalali_date_and_day():
187
  greg_date = datetime.now()
188
  jalali_date = jdatetime.date.fromgregorian(date=greg_date)
189
- weekdays = ["دوشنبه", "سه‌شنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه", "یکشنبه"]
190
  weekday = weekdays[jalali_date.weekday()]
191
  return jalali_date.strftime('%Y/%m/%d'), weekday
192
 
193
  jalali_date, current_day = get_jalali_date_and_day()
194
- st.markdown(f'<div class="top-bar">تاریخ: {jalali_date} | روز: {current_day}</div>', unsafe_allow_html=True)
195
-
196
- # Enhanced Earth Engine Map Function
197
- @st.cache_data
198
- def create_map(farm_id, date_str, layer_type="NDVI"):
199
- if not earth_engine_initialized:
200
- return None
201
- try:
202
- farm_coords = coordinates_df[coordinates_df['نام'] == farm_id].iloc[0]
203
- lat, lon = farm_coords['عرض جغرافیایی'], farm_coords['طول جغرافیایی']
204
- m = folium.Map(location=[lat, lon], zoom_start=14, tiles='CartoDB positron')
205
-
206
- date_obj = datetime.strptime(date_str, '%Y-%m-%d')
207
- start_date = (date_obj - timedelta(days=5)).strftime('%Y-%m-%d')
208
- end_date = (date_obj + timedelta(days=5)).strftime('%Y-%m-%d')
209
-
210
- region = ee.Geometry.Point([lon, lat]).buffer(1500)
211
- s2 = ee.ImageCollection('COPERNICUS/S2_SR') \
212
- .filterDate(start_date, end_date) \
213
- .filterBounds(region) \
214
- .sort('CLOUDY_PIXEL_PERCENTAGE') \
215
- .first()
216
-
217
- if layer_type == "NDVI":
218
- index = s2.normalizedDifference(['B8', 'B4']).rename('NDVI')
219
- viz_params = {'min': -0.2, 'max': 0.8, 'palette': ['#ff0000', '#ff4500', '#ffd700', '#32cd32', '#006400']}
220
- elif layer_type == "NDMI":
221
- index = s2.normalizedDifference(['B8', 'B11']).rename('NDMI')
222
- viz_params = {'min': -0.5, 'max': 0.5, 'palette': ['#8b0000', '#ff8c00', '#00ced1', '#00b7eb', '#00008b']}
223
- elif layer_type == "EVI":
224
- nir = s2.select('B8')
225
- red = s2.select('B4')
226
- blue = s2.select('B2')
227
- index = nir.subtract(red).multiply(2.5).divide(nir.add(red.multiply(6)).subtract(blue.multiply(7.5)).add(1)).rename('EVI')
228
- viz_params = {'min': 0, 'max': 1, 'palette': ['#d73027', '#f46d43', '#fdae61', '#fee08b', '#4caf50']}
229
- elif layer_type == "NDWI":
230
- index = s2.normalizedDifference(['B3', 'B8']).rename('NDWI')
231
- viz_params = {'min': -0.5, 'max': 0.5, 'palette': ['#00008b', '#00b7eb', '#add8e6', '#fdae61', '#d73027']}
232
-
233
- map_id_dict = ee.Image(index).getMapId(viz_params)
234
- folium.TileLayer(
235
- tiles=map_id_dict['tile_fetcher'].url_format,
236
- attr='Google Earth Engine',
237
- name=layer_type,
238
- overlay=True,
239
- control=True
240
- ).add_to(m)
241
-
242
- folium.Marker([lat, lon], popup=f'مزرعه {farm_id}', icon=folium.Icon(color='green', icon='leaf')).add_to(m)
243
- folium.LayerControl().add_to(m)
244
- return m
245
- except Exception as e:
246
- st.error(f"خطا در ایجاد نقشه: {e}")
247
- return None
248
-
249
- # Cached Scoring Function
250
- @st.cache_data
251
- def calculate_farm_scores(date_str, farms):
252
- if not earth_engine_initialized:
253
- return None
254
- scoring_data = []
255
- date_obj = datetime.strptime(date_str, '%Y-%m-%d')
256
- start_date = (date_obj - timedelta(days=7)).strftime('%Y-%m-%d')
257
- end_date = (date_obj - timedelta(days=1)).strftime('%Y-%m-%d')
258
-
259
- for _, farm in farms.iterrows():
260
- farm_id = farm['نام']
261
- lat, lon = farm['عرض جغرافیایی'], farm['طول جغرافیایی']
262
- region = ee.Geometry.Point([lon, lat]).buffer(1500)
263
-
264
- s2 = ee.ImageCollection('COPERNICUS/S2_SR') \
265
- .filterDate(start_date, end_date) \
266
- .filterBounds(region) \
267
- .sort('CLOUDY_PIXEL_PERCENTAGE') \
268
- .first()
269
-
270
- if s2:
271
- ndvi = s2.normalizedDifference(['B8', 'B4']).rename('NDVI')
272
- ndvi_value = ndvi.reduceRegion(reducer=ee.Reducer.mean(), geometry=region, scale=10).get('NDVI').getInfo() or 0
273
-
274
- ndmi = s2.normalizedDifference(['B8', 'B11']).rename('NDMI')
275
- ndmi_value = ndmi.reduceRegion(reducer=ee.Reducer.mean(), geometry=region, scale=10).get('NDMI').getInfo() or 0
276
-
277
- if ndvi_value < 0.1:
278
- growth_status = "بدون پوشش گیاهی"
279
- elif ndvi_value < 0.3:
280
- growth_status = "تنش‌دار"
281
- elif ndvi_value < 0.6:
282
- growth_status = "نرمال"
283
- else:
284
- growth_status = "خوب"
285
-
286
- if ndmi_value < -0.2:
287
- moisture_status = "تنش‌دار"
288
- elif ndmi_value < 0.2:
289
- moisture_status = "نرمال"
290
- else:
291
- moisture_status = "خوب"
292
-
293
- scoring_data.append({
294
- 'مزرعه': farm_id,
295
- 'NDVI': ndvi_value,
296
- 'وضعیت رشد': growth_status,
297
- 'NDMI': ndmi_value,
298
- 'وضعیت رطوبت': moisture_status
299
- })
300
-
301
- return pd.DataFrame(scoring_data)
302
-
303
- # Machine Learning Model
304
- @st.cache_resource
305
- def train_growth_model(_heights_df):
306
- if len(_heights_df) < 2:
307
- return None
308
- X = _heights_df[['Height', 'Nitrogen', 'Sheath_Moisture', 'Week']]
309
- y = _heights_df['Height'].shift(-1).fillna(method='ffill')
310
- X_train, _, y_train, _ = train_test_split(X, y, test_size=0.2, random_state=42)
311
- model = RandomForestRegressor(n_estimators=50, random_state=42)
312
- model.fit(X_train, y_train)
313
- return model
314
-
315
- growth_model = train_growth_model(st.session_state.heights_df)
316
-
317
- # Top menu with buttons
318
- menu_options = ["📊 داشبورد", "✍️ ورود اطلاعات", "📈 گزارش‌گیری", "🔍 تحلیل داده‌ها", "🔮 پیش‌بینی رشد", "⭐ وضعیت مزارع", "⚙️ تنظیمات"]
319
  cols = st.columns(len(menu_options))
320
  for i, option in enumerate(menu_options):
321
  with cols[i]:
322
- if st.button(option, key=f"menu_{option}"):
323
- st.session_state['selected_tab'] = option.split(' ')[1]
 
324
 
325
- # Dashboard
326
- if st.session_state.get('selected_tab') == "داشبورد":
327
- st.markdown('<h1 class="header">🌿 مطالعات کاربردی کشت و صنعت دهخدا </h1>', unsafe_allow_html=True)
328
  st.markdown('<div class="card">', unsafe_allow_html=True)
329
-
330
- heights_df = st.session_state.heights_df
331
- if heights_df.empty:
332
- st.warning("هیچ داده‌ای وارد نشده است. لطفاً از بخش ورود اطلاعات استفاده کنید.")
333
- else:
334
- latest_week = heights_df['Week'].max()
335
- latest_data = heights_df[heights_df['Week'] == latest_week]
336
- col1, col2, col3 = st.columns(3)
337
- with col1:
338
- st.markdown(f'<div class="metric-card"><h3>{len(latest_data)}</h3><p>مزارع فعال</p></div>', unsafe_allow_html=True)
339
- with col2:
340
- avg_height = int(latest_data['Height'].mean()) if not latest_data.empty else 0
341
- st.markdown(f'<div class="metric-card"><h3>{avg_height} cm</h3><p>میانگین ارتفاع</p></div>', unsafe_allow_html=True)
342
- with col3:
343
- st.markdown(f'<div class="metric-card"><h3>{latest_week}</h3><p>هفته جاری</p></div>', unsafe_allow_html=True)
344
-
345
- st.subheader("📊 نمودار ارتفاع مزارع")
346
- fig = px.bar(latest_data, x='Farm_ID', y='Height', title="ارتفاع مزارع در هفته جاری",
347
- color_discrete_sequence=['#1b5e20'], height=400)
348
- fig.update_layout(font=dict(family="Vazir"), template='plotly_white', title_x=0.5)
349
- st.plotly_chart(fig, use_container_width=True)
350
-
351
- st.subheader("🗺️ نقشه پایش پیشرفته")
352
- col1, col2 = st.columns([1, 3])
353
- with col1:
354
- farm_id = st.selectbox("انتخاب مزرعه", farms_df['مزرعه'].unique(), key="map_farm")
355
- date = st.date_input("انتخاب تاریخ", datetime.now(), key="map_date")
356
- layer_type = st.selectbox("نوع لایه", ["NDVI", "NDMI", "EVI", "NDWI"], key="map_layer")
357
- with col2:
358
- if st.button("نمایش نقشه", key="show_map"):
359
- with st.spinner("در حال بارگذاری نقشه..."):
360
- m = create_map(farm_id, date.strftime('%Y-%m-%d'), layer_type)
361
- if m:
362
- folium_static(m, width=800, height=400)
363
  st.markdown('</div>', unsafe_allow_html=True)
364
 
365
- # Data Entry
366
- elif st.session_state.get('selected_tab') == "ورود اطلاعات":
367
  st.markdown('<h1 class="header">✍️ ورود اطلاعات روزانه</h1>', unsafe_allow_html=True)
368
  st.markdown('<div class="card">', unsafe_allow_html=True)
369
-
370
- weeks = [str(i) for i in range(1, 23)]
371
- selected_week = st.selectbox("انتخاب هفته", weeks, key="week_select")
372
- days = ["شنبه", "یکشنبه", "دوشنبه", "سه‌شنبه", "چهارشنبه", "پنجشنبه"]
373
- selected_day = st.selectbox("انتخاب روز", days, key="day_select")
374
-
375
- filtered_farms = farms_df[farms_df['روز'] == selected_day].copy()
376
- if filtered_farms.empty:
377
- st.warning(f"هیچ مزرعه‌ای برای روز {selected_day} در پایگاه داده وجود ندارد.")
378
- else:
379
- data_key = f"data_{selected_week}_{selected_day}"
380
- if data_key not in st.session_state:
381
- st.session_state[data_key] = pd.DataFrame({
382
- 'مزرعه': filtered_farms['مزرعه'],
383
- 'ایستگاه 1': [0] * len(filtered_farms),
384
- 'ایستگاه 2': [0] * len(filtered_farms),
385
- 'ایستگاه 3': [0] * len(filtered_farms),
386
- 'ایستگاه 4': [0] * len(filtered_farms),
387
- 'ایستگاه 5': [0] * len(filtered_farms),
388
- 'چاهک 1': [0] * len(filtered_farms),
389
- 'چاهک 2': [0] * len(filtered_farms),
390
- 'رطوبت غلاف': [0] * len(filtered_farms),
391
- 'نیتروژن': [0] * len(filtered_farms),
392
- 'میانگین ارتفاع': [0] * len(filtered_farms)
393
- })
394
-
395
- st.subheader("آپلود فایل اکسل یا کپی کردن داده‌ها")
396
- uploaded_file = st.file_uploader("فایل اکسل را آپلود کنید", type=["xlsx", "xls"])
397
- if uploaded_file:
398
- uploaded_df = pd.read_excel(uploaded_file)
399
- if set(st.session_state[data_key].columns).issubset(uploaded_df.columns):
400
- st.session_state[data_key] = uploaded_df[st.session_state[data_key].columns]
401
- st.success("فایل با موفقیت آپلود شد!")
402
- else:
403
- st.error("ساختار فایل با جدول سازگار نیست.")
404
-
405
- pasted_data = st.text_area("داده‌ها را کپی کنید (ستون‌ها با تب جدا شوند)", "")
406
- if st.button("اعمال داده‌های کپی‌شده", key="apply_paste"):
407
- if pasted_data:
408
- try:
409
- pasted_df = pd.read_csv(BytesIO(pasted_data.encode()), sep='\t')
410
- if set(st.session_state[data_key].columns).issubset(pasted_df.columns):
411
- st.session_state[data_key] = pasted_df[st.session_state[data_key].columns]
412
- st.success("داده‌ها با موفقیت اعمال شدند!")
413
- else:
414
- st.error("ساختار داده‌ها با جدول سازگار نیست.")
415
- except:
416
- st.error("خطا در پردازش داده‌های کپی‌شده.")
417
-
418
- edited_df = st.data_editor(
419
- st.session_state[data_key],
420
- num_rows="fixed",
421
- key=f"editor_{data_key}",
422
- column_config={
423
- "مزرعه": st.column_config.TextColumn("مزرعه", disabled=True),
424
- "ایستگاه 1": st.column_config.NumberColumn("ایستگاه 1", min_value=0, max_value=300, step=1),
425
- "ایستگاه 2": st.column_config.NumberColumn("ایستگاه 2", min_value=0, max_value=300, step=1),
426
- "ایستگاه 3": st.column_config.NumberColumn("ایستگاه 3", min_value=0, max_value=300, step=1),
427
- "ایستگاه 4": st.column_config.NumberColumn("ایستگاه 4", min_value=0, max_value=300, step=1),
428
- "ایستگاه 5": st.column_config.NumberColumn("ایستگاه 5", min_value=0, max_value=300, step=1),
429
- "چاهک 1": st.column_config.NumberColumn("چاهک 1", min_value=0, max_value=300, step=1),
430
- "چاهک 2": st.column_config.NumberColumn("چاهک 2", min_value=0, max_value=300, step=1),
431
- "رطوبت غلاف": st.column_config.NumberColumn("رطوبت غلاف", min_value=0, max_value=100, step=1),
432
- "نیتروژن": st.column_config.NumberColumn("نیتروژن", min_value=0, max_value=100, step=1),
433
- "میانگین ارتفاع": st.column_config.NumberColumn("میانگین ارتفاع", disabled=True)
434
- },
435
- use_container_width=True
436
- )
437
-
438
- edited_df['میانگین ارتفاع'] = edited_df[['ایستگاه 1', 'ایستگاه 2', 'ایستگاه 3', 'ایستگاه 4', 'ایستگاه 5']].mean(axis=1)
439
- st.session_state[data_key] = edited_df
440
-
441
- if st.button("💾 ذخیره و به‌روزرسانی", key="save_data"):
442
- new_data = edited_df.copy()
443
- new_data['Farm_ID'] = new_data['مزرعه']
444
- new_data['Week'] = int(selected_week)
445
- new_data['Measurement_Date'] = (datetime.now() - timedelta(weeks=(22 - int(selected_week)))).strftime('%Y-%m-%d')
446
- new_data['Height'] = new_data['میانگین ارتفاع']
447
- new_data['Station1'] = new_data['ایستگاه 1']
448
- new_data['Station2'] = new_data['ایستگاه 2']
449
- new_data['Station3'] = new_data['ایستگاه 3']
450
- new_data['Station4'] = new_data['ایستگاه 4']
451
- new_data['Station5'] = new_data['ایستگاه 5']
452
- new_data['Groundwater1'] = new_data['چاهک 1']
453
- new_data['Groundwater2'] = new_data['چاهک 2']
454
- new_data['Sheath_Moisture'] = new_data['رطوبت غلاف']
455
- new_data['Nitrogen'] = new_data['نیتروژن']
456
-
457
- available_columns = [col for col in ['مزرعه', 'واریته', 'سن', 'مساحت داشت', 'کانال', 'اداره'] if col in farms_df.columns]
458
- if not available_columns:
459
- st.error("هیچ ستون معتبری در پایگاه داده یافت نشد.")
460
- else:
461
- new_data = new_data.merge(farms_df[available_columns],
462
- left_on='Farm_ID', right_on='مزرعه', how='left')
463
- new_data = new_data.rename(columns={
464
- 'واریته': 'Variety',
465
- 'سن': 'Age',
466
- 'مساحت داشت': 'Area' if 'مساحت داشت' in available_columns else 'Area_Default',
467
- 'کانال': 'Channel',
468
- 'اداره': 'Administration'
469
- })
470
- if 'Area' not in new_data.columns:
471
- new_data['Area'] = 0
472
- st.session_state.heights_df = pd.concat([st.session_state.heights_df, new_data], ignore_index=True)
473
- st.success("داده‌ها با موفقیت ذخیره و به‌روزرسانی شدند!")
474
- st.balloons()
475
  st.markdown('</div>', unsafe_allow_html=True)
476
 
477
- # Reporting
478
- elif st.session_state.get('selected_tab') == "گزارش‌گیری":
479
- st.markdown('<h1 class="header">📊 گزارش‌گیری پیشرفته</h1>', unsafe_allow_html=True)
480
  st.markdown('<div class="card">', unsafe_allow_html=True)
481
-
482
- heights_df = st.session_state.heights_df
483
- if heights_df.empty:
484
- st.warning("هیچ داده‌ای برای گزارش وجود ندارد.")
485
  else:
486
- start_date = st.date_input("تاریخ شروع (میلادی)", datetime.now() - timedelta(days=30), key="report_start")
487
- end_date = st.date_input("تاریخ پایان (میلادی)", datetime.now(), key="report_end")
488
- start_date_jalali = to_jalali(start_date.strftime('%Y-%m-%d'))
489
- end_date_jalali = to_jalali(end_date.strftime('%Y-%m-%d'))
490
- st.write(f"بازه زمانی شمسی: {start_date_jalali} تا {end_date_jalali}")
491
-
492
- filtered_data = heights_df[
493
- (heights_df['Measurement_Date'] >= start_date.strftime('%Y-%m-%d')) &
494
- (heights_df['Measurement_Date'] <= end_date.strftime('%Y-%m-%d'))
495
- ]
496
-
497
- if filtered_data.empty:
498
- st.warning("داده‌ای در این بازه زمانی وجود ندارد.")
499
- else:
500
- filtered_data = filtered_data.sort_values(['Farm_ID', 'Week'])
501
- filtered_data['Previous_Height'] = filtered_data.groupby('Farm_ID')['Height'].shift(1)
502
- filtered_data['Weekly_Growth'] = filtered_data['Height'] - filtered_data['Previous_Height'].fillna(0)
503
-
504
- varieties = ['CP57', 'CP48', 'CP69', 'CP73', 'CP65', 'IR01-412']
505
- ages = ['P', 'R1', 'R2', 'R3', 'R4', 'R5', 'R6']
506
-
507
- report_data = {}
508
- for age in ages:
509
- age_data = filtered_data[filtered_data['Age'] == age]
510
- report_data[age] = {}
511
- for variety in varieties:
512
- var_data = age_data[age_data['Variety'] == variety]
513
- if not var_data.empty:
514
- report_data[age][variety] = {
515
- 'مساحت کل (داشت)': var_data['Area'].sum(),
516
- 'مساحت کراپ لاگ': var_data['Area'].sum(),
517
- 'درصد رطوبت': var_data['Sheath_Moisture'].mean(),
518
- 'درصد ازت': var_data['Nitrogen'].mean(),
519
- 'رشد هفتگی': var_data['Weekly_Growth'].mean(),
520
- 'ارتفاع نیشکر': var_data['Height'].mean()
521
- }
522
- else:
523
- report_data[age][variety] = {
524
- 'مساحت کل (داشت)': 0,
525
- 'مساحت کراپ لاگ': 0,
526
- 'درصد رطوبت': 0,
527
- 'درصد ازت': 0,
528
- 'رشد هفتگی': 0,
529
- 'ارتفاع نیشکر': 0
530
- }
531
-
532
- st.subheader("گزارش هفتگی کنترل محصول")
533
- for age in ages:
534
- st.write(f"**سن محصول: {'پلنت' if age == 'P' else f'بازرویی {ages.index(age) + 1}'}**")
535
- report_df = pd.DataFrame(report_data[age]).T
536
- report_df.index.name = 'واریته'
537
- st.dataframe(report_df, use_container_width=True)
538
-
539
- total_area = filtered_data['Area'].sum()
540
- total_crop_log = total_area
541
- total_height = filtered_data['Height'].mean()
542
- total_growth = filtered_data['Weekly_Growth'].mean()
543
- st.write(f"**جمع کل:** مساحت کل (هکتار): {total_area:.2f} | مساحت کراپ لاگ: {total_crop_log:.2f} | ارتفاع (سانتی‌متر): {total_height:.2f} | رشد (سانتی‌متر): {total_growth:.2f}")
544
-
545
- col1, col2 = st.columns(2)
546
- with col1:
547
- if st.button("📄 خروجی PDF", key="pdf_export"):
548
- buffer = BytesIO()
549
- c = canvas.Canvas(buffer, pagesize=letter)
550
- c.setFont("Helvetica", 12)
551
- c.setFillColor(colors.darkgreen)
552
- c.drawString(100, 750, f"گزارش پایش مزارع نیشکر دهخدا ({start_date_jalali} تا {end_date_jalali})")
553
- c.setFillColor(colors.black)
554
- y = 730
555
- for age in ages:
556
- c.drawString(50, y, f"سن محصول: {'پلنت' if age == 'P' else f'بازرویی {ages.index(age) + 1}'}")
557
- y -= 20
558
- for variety, metrics in report_data[age].items():
559
- text = f"{variety}: مساحت کل: {metrics['مساحت کل (داشت)']:.2f} | ارتفاع: {metrics['ارتفاع نیشکر']:.2f} | رشد: {metrics['رشد هفتگی']:.2f}"
560
- if y < 50:
561
- c.showPage()
562
- y = 750
563
- c.drawString(50, y, text)
564
- y -= 20
565
- c.save()
566
- st.download_button("دانلود PDF", buffer.getvalue(), "report.pdf", "application/pdf", key="pdf_download")
567
- with col2:
568
- if st.button("📑 خروجی Excel", key="excel_export"):
569
- buffer = BytesIO()
570
- with pd.ExcelWriter(buffer, engine='xlsxwriter') as writer:
571
- for age in ages:
572
- pd.DataFrame(report_data[age]).T.to_excel(writer, sheet_name=f"{'پلنت' if age == 'P' else f'بازرویی {ages.index(age) + 1}'}")
573
- st.download_button("دانلود Excel", buffer.getvalue(), "report.xlsx", "application/vnd.ms-excel", key="excel_download")
574
  st.markdown('</div>', unsafe_allow_html=True)
575
 
576
- # Data Analysis
577
- elif st.session_state.get('selected_tab') == "تحلیل داده‌ها":
578
- st.markdown('<h1 class="header">🔍 تحلیل هوشمند داده‌ها</h1>', unsafe_allow_html=True)
579
  st.markdown('<div class="card">', unsafe_allow_html=True)
580
-
581
- heights_df = st.session_state.heights_df
582
- if heights_df.empty:
583
- st.warning("هیچ داده‌ای برای تحلیل وجود ندارد.")
584
- else:
585
- fig = px.line(heights_df.groupby(['Age', 'Week'])['Height'].mean().reset_index(),
586
- x='Week', y='Height', color='Age', title="روند رشد بر اساس سن محصول",
587
- color_discrete_sequence=['#ff4500', '#32cd32', '#ffd700', '#4169e1'], markers=True)
588
- fig.update_layout(font=dict(family="Vazir"), template='plotly_white', title_x=0.5, height=500)
589
- st.plotly_chart(fig, use_container_width=True)
590
  st.markdown('</div>', unsafe_allow_html=True)
591
-
592
- # Growth Prediction
593
- elif st.session_state.get('selected_tab') == "پیش‌بینی رشد":
594
- st.markdown('<h1 class="header">🔮 پیش‌بینی رشد محصول</h1>', unsafe_allow_html=True)
595
- st.markdown('<div class="card">', unsafe_allow_html=True)
596
-
597
- heights_df = st.session_state.heights_df
598
- if heights_df.empty or len(heights_df) < 2:
599
- st.warning("داده کافی برای پیش‌بینی وجود ندارد. حداقل دو هفته داده وارد کنید.")
600
- else:
601
- farm_id = st.selectbox("انتخاب مزرعه", heights_df['Farm_ID'].unique(), key="pred_farm")
602
- if st.button("پیش‌بینی", key="predict_btn"):
603
- farm_data = heights_df[heights_df['Farm_ID'] == farm_id]
604
- pred = growth_model.predict(pd.DataFrame({
605
- 'Height': [farm_data['Height'].iloc[-1]],
606
- 'Nitrogen': [farm_data['Nitrogen'].iloc[-1]],
607
- 'Sheath_Moisture': [farm_data['Sheath_Moisture'].iloc[-1]],
608
- 'Week': [farm_data['Week'].iloc[-1] + 1]
609
- }))[0]
610
-
611
- fig = go.Figure()
612
- fig.add_trace(go.Scatter(x=farm_data['Week'], y=farm_data['Height'], mode='lines+markers', name='واقعی', line=dict(color='#1b5e20')))
613
- fig.add_trace(go.Scatter(x=[farm_data['Week'].iloc[-1], farm_data['Week'].iloc[-1] + 1],
614
- y=[farm_data['Height'].iloc[-1], pred], mode='lines', name='پیش‌بینی', line=dict(color='#ff4500', dash='dash')))
615
- fig.update_layout(title=f"پیش‌بینی رشد مزرعه {farm_id}", font=dict(family="Vazir"), template='plotly_white', height=500, title_x=0.5)
616
- st.plotly_chart(fig, use_container_width=True)
617
- st.success(f"پیش‌بینی ارتفاع هفته بعد: {pred:.1f} cm")
618
- st.markdown('</div>', unsafe_allow_html=True)
619
-
620
- # Farm Scoring
621
- elif st.session_state.get('selected_tab') == "امتیازدهی مزارع":
622
- st.markdown('<h1 class="header">⭐ امتیازدهی مزارع</h1>', unsafe_allow_html=True)
623
- st.markdown('<div class="card">', unsafe_allow_html=True)
624
-
625
- if not earth_engine_initialized:
626
- st.error("اتصال به Google Earth Engine برقرار نشد.")
627
- else:
628
- date = st.date_input("انتخاب تاریخ", datetime.now(), key="score_date")
629
- date_str = date.strftime('%Y-%m-%d')
630
-
631
- filtered_farms = coordinates_df[coordinates_df['نام'].isin(farms_df[farms_df['روز'] == current_day]['مزرعه'])]
632
- if filtered_farms.empty:
633
- st.warning(f"هیچ مزرعه‌ای برای روز {current_day} یافت نشد.")
634
- else:
635
- if st.button("محاسبه امتیازها", key="score_btn"):
636
- with st.spinner("در حال محاسبه امتیازها..."):
637
- scoring_df = calculate_farm_scores(date_str, filtered_farms)
638
- if scoring_df is not None and not scoring_df.empty:
639
- st.subheader(f"امتیازدهی مزارع روز {current_day} (هفته قبل از {to_jalali(date_str)})")
640
- st.dataframe(scoring_df, use_container_width=True)
641
-
642
- growth_counts = scoring_df['وضعیت رشد'].value_counts()
643
- moisture_counts = scoring_df['وضعیت رطوبت'].value_counts()
644
- st.write("**خلاصه وضعیت رشد:**")
645
- for status, count in growth_counts.items():
646
- st.write(f"{status}: {count} مزرعه")
647
- st.write("**خلاصه وضعیت رطوبت:**")
648
- for status, count in moisture_counts.items():
649
- st.write(f"{status}: {count} مزرعه")
650
- else:
651
- st.warning("داده‌ای برای امتیازدهی در این بازه زمانی یافت نشد.")
652
- st.markdown('</div>', unsafe_allow_html=True)
653
-
654
- # Settings
655
- elif st.session_state.get('selected_tab') == "تنظیمات":
656
- st.markdown('<h1 class="header">⚙️ تنظیمات پیشرفته</h1>', unsafe_allow_html=True)
657
- st.markdown('<div class="card">', unsafe_allow_html=True)
658
- st.write("در حال توسعه...")
659
- st.subheader("ستون‌های پایگاه داده")
660
- st.write("ستون‌های موجود در فایل `پایگاه داده (1).csv`:")
661
- st.write(farms_df.columns.tolist())
662
- st.markdown('</div>', unsafe_allow_html=True)
 
2
  import pandas as pd
3
  import numpy as np
4
  import plotly.express as px
5
+ from datetime import datetime
 
6
  import folium
7
  from streamlit_folium import folium_static
 
 
 
 
 
 
 
 
 
 
 
8
  import jdatetime
9
 
10
+ # تنظیمات صفحه
11
  st.set_page_config(
12
  page_title="سامانه هوشمند پایش مزارع نیشکر دهخدا",
13
  page_icon="🌿",
 
15
  initial_sidebar_state="collapsed"
16
  )
17
 
18
+ # استایل خلاقانه و شگفت‌انگیز
19
  st.markdown("""
20
  <style>
21
  @font-face {
 
24
  }
25
  * { font-family: 'Vazir', sans-serif !important; }
26
  body {
27
+ background: linear-gradient(120deg, #e0f7fa 0%, #b2dfdb 100%);
28
+ color: #004d40;
29
  margin: 0;
30
  padding: 0;
31
  }
32
+ .header-container {
33
+ background: linear-gradient(45deg, #00695c, #4db6ac, #b2dfdb);
34
+ padding: 25px;
35
  text-align: center;
36
  color: white;
37
+ font-size: 2rem;
38
+ font-weight: bold;
39
+ border-radius: 0 0 30px 30px;
40
+ box-shadow: 0 10px 30px rgba(0, 77, 64, 0.5);
41
+ position: relative;
42
+ overflow: hidden;
43
+ animation: wave 3s infinite ease-in-out;
44
+ }
45
+ .header-container::before {
46
+ content: '';
47
+ position: absolute;
48
+ top: -50%;
49
+ left: -50%;
50
+ width: 200%;
51
+ height: 200%;
52
+ background: radial-gradient(circle, rgba(255,255,255,0.2), transparent);
53
+ animation: glow 5s infinite;
54
  }
55
+ .menu-container {
56
  display: flex;
57
  justify-content: center;
58
+ gap: 15px;
59
  padding: 20px;
60
+ background: rgba(255, 255, 255, 0.15);
61
+ backdrop-filter: blur(12px);
62
+ border-radius: 25px;
63
+ box-shadow: 0 15px 40px rgba(0, 77, 64, 0.3);
64
+ margin: 25px auto;
65
+ max-width: 1300px;
66
  }
67
+ .menu-button {
68
+ background: linear-gradient(135deg, #00796b, #80deea);
69
  color: white;
70
  border: none;
71
+ border-radius: 20px;
72
+ padding: 15px 30px;
73
+ font-size: 1.3rem;
74
  font-weight: bold;
75
+ box-shadow: 0 8px 20px rgba(0, 77, 64, 0.4);
76
+ transition: all 0.4s ease;
77
  cursor: pointer;
78
  display: flex;
79
  align-items: center;
80
+ gap: 12px;
81
+ position: relative;
82
+ overflow: hidden;
83
  }
84
+ .menu-button::after {
85
+ content: '';
86
+ position: absolute;
87
+ width: 0;
88
+ height: 100%;
89
+ background: rgba(255, 255, 255, 0.3);
90
+ top: 0;
91
+ left: -100%;
92
+ transition: all 0.5s ease;
93
  }
94
+ .menu-button:hover::after {
95
+ width: 200%;
96
+ left: 100%;
97
  }
98
+ .menu-button:hover {
99
+ transform: translateY(-8px) scale(1.08);
100
+ box-shadow: 0 15px 35px rgba(0, 77, 64, 0.6);
101
+ background: linear-gradient(135deg, #004d40, #26a69a);
 
 
 
 
102
  }
103
+ .menu-button:active {
104
+ transform: translateY(2px);
105
+ box-shadow: 0 5px 15px rgba(0, 77, 64, 0.3);
 
 
 
 
 
106
  }
107
+ .card {
108
+ background: rgba(255, 255, 255, 0.95);
109
+ border-radius: 30px;
110
+ padding: 2.5rem;
111
+ box-shadow: 0 20px 50px rgba(0, 77, 64, 0.2);
112
+ margin-bottom: 2.5rem;
113
+ animation: floatIn 1s ease-out;
114
  }
115
  .header {
116
+ color: #00695c;
117
  text-align: center;
118
+ font-size: 3rem;
119
  font-weight: bold;
120
+ text-shadow: 3px 3px 8px rgba(0, 77, 64, 0.3);
121
+ animation: fadeIn 1.5s ease-in-out;
122
  }
123
+ @keyframes wave {
124
+ 0% { transform: translateY(0); }
125
+ 50% { transform: translateY(-10px); }
126
+ 100% { transform: translateY(0); }
127
+ }
128
+ @keyframes glow {
129
+ 0% { transform: scale(1); opacity: 0.5; }
130
+ 50% { transform: scale(1.2); opacity: 0.8; }
131
+ 100% { transform: scale(1); opacity: 0.5; }
132
  }
133
  @keyframes floatIn {
134
+ 0% { transform: translateY(30px); opacity: 0; }
135
  100% { transform: translateY(0); opacity: 1; }
136
  }
137
  @keyframes fadeIn {
 
141
  </style>
142
  """, unsafe_allow_html=True)
143
 
144
+ # بارگذاری داده‌ها
145
  @st.cache_data
146
  def load_data():
147
  try:
 
156
  if farms_df is None or coordinates_df is None:
157
  st.stop()
158
 
159
+ # مقداردهی اولیه session_state
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  if 'heights_df' not in st.session_state:
161
  st.session_state.heights_df = pd.DataFrame(columns=[
162
  'Farm_ID', 'Week', 'Measurement_Date', 'Height', 'Station1', 'Station2', 'Station3',
163
+ 'Station4', 'Station5', 'Groundwater1', 'Groundwater2', 'Sheath_Moisture', 'Nitrogen'
 
164
  ])
165
+ if 'selected_tab' not in st.session_state:
166
+ st.session_state.selected_tab = "داشبورد"
167
 
168
+ # تابع تبدیل تاریخ میلادی به شمسی و گرفتن روز هفته
169
  def get_jalali_date_and_day():
170
  greg_date = datetime.now()
171
  jalali_date = jdatetime.date.fromgregorian(date=greg_date)
172
+ weekdays = ["شنبه", "یکشنبه", "دوشنبه", "سه‌شنبه", "چهارشنبه", "پنجشنبه", "جمعه"]
173
  weekday = weekdays[jalali_date.weekday()]
174
  return jalali_date.strftime('%Y/%m/%d'), weekday
175
 
176
  jalali_date, current_day = get_jalali_date_and_day()
177
+ st.markdown(f'<div class="header-container">تاریخ شمسی: {jalali_date} | روز: {current_day}</div>', unsafe_allow_html=True)
178
+
179
+ # تابع نقشه (ساده‌سازی شده برای تست)
180
+ def create_map(farm_id, date_str):
181
+ farm_coords = coordinates_df[coordinates_df['نام'] == farm_id].iloc[0]
182
+ lat, lon = farm_coords['عرض جغرافیایی'], farm_coords['طول جغرافیایی']
183
+ m = folium.Map(location=[lat, lon], zoom_start=14, tiles='CartoDB positron')
184
+ folium.Marker([lat, lon], popup=f'مزرعه {farm_id}', icon=folium.Icon(color='green', icon='leaf')).add_to(m)
185
+ return m
186
+
187
+ # منوی اصلی
188
+ menu_options = ["📊 داشبورد", "✍️ ورود اطلاعات", "📈 گزارش‌گیری", "🗺️ نقشه"]
189
+ st.markdown('<div class="menu-container">', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  cols = st.columns(len(menu_options))
191
  for i, option in enumerate(menu_options):
192
  with cols[i]:
193
+ if st.button(option, key=f"menu_{i}"):
194
+ st.session_state.selected_tab = option.split(' ')[1]
195
+ st.markdown('</div>', unsafe_allow_html=True)
196
 
197
+ # داشبورد
198
+ if st.session_state.selected_tab == "داشبورد":
199
+ st.markdown('<h1 class="header">🌿 داشبورد هوشمند پایش</h1>', unsafe_allow_html=True)
200
  st.markdown('<div class="card">', unsafe_allow_html=True)
201
+ st.write("خوش آمدید! اینجا خلاصه وضعیت مزارع رو می‌بینید.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  st.markdown('</div>', unsafe_allow_html=True)
203
 
204
+ # ورود اطلاعات
205
+ elif st.session_state.selected_tab == "ورود اطلاعات":
206
  st.markdown('<h1 class="header">✍️ ورود اطلاعات روزانه</h1>', unsafe_allow_html=True)
207
  st.markdown('<div class="card">', unsafe_allow_html=True)
208
+ week = st.selectbox("انتخاب هفته", [str(i) for i in range(1, 23)])
209
+ farm = st.selectbox("انتخاب مزرعه", farms_df['مزرعه'].unique())
210
+ height = st.number_input("ارتفاع (سانتی‌متر)", min_value=0.0)
211
+ if st.button("ذخیره"):
212
+ new_data = pd.DataFrame({
213
+ 'Farm_ID': [farm], 'Week': [int(week)], 'Measurement_Date': [datetime.now().strftime('%Y-%m-%d')],
214
+ 'Height': [height]
215
+ })
216
+ st.session_state.heights_df = pd.concat([st.session_state.heights_df, new_data], ignore_index=True)
217
+ st.success("داده‌ها ذخیره شدند!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  st.markdown('</div>', unsafe_allow_html=True)
219
 
220
+ # گزارش‌گیری
221
+ elif st.session_state.selected_tab == "گزارش‌گیری":
222
+ st.markdown('<h1 class="header">📈 گزارش‌گیری پیشرفته</h1>', unsafe_allow_html=True)
223
  st.markdown('<div class="card">', unsafe_allow_html=True)
224
+ if st.session_state.heights_df.empty:
225
+ st.warning("داده‌ای برای گزارش وجود ندارد.")
 
 
226
  else:
227
+ st.dataframe(st.session_state.heights_df)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  st.markdown('</div>', unsafe_allow_html=True)
229
 
230
+ # نقشه
231
+ elif st.session_state.selected_tab == "نقشه":
232
+ st.markdown('<h1 class="header">🗺️ نقشه پایش مزارع</h1>', unsafe_allow_html=True)
233
  st.markdown('<div class="card">', unsafe_allow_html=True)
234
+ farm_id = st.selectbox("انتخاب مزرعه", farms_df['مزرعه'].unique())
235
+ date = st.date_input("انتخاب تاریخ", datetime.now())
236
+ if st.button("نمایش نقشه"):
237
+ m = create_map(farm_id, date.strftime('%Y-%m-%d'))
238
+ folium_static(m, width=800, height=400)
 
 
 
 
 
239
  st.markdown('</div>', unsafe_allow_html=True)