Esmaeilkiani commited on
Commit
7ba07c5
·
verified ·
1 Parent(s): f3f6e6c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +132 -177
app.py CHANGED
@@ -9,29 +9,33 @@ from datetime import datetime, timedelta
9
  # --- پیکربندی ---
10
  SERVICE_ACCOUNT_FILE = 'ee-esmaeilkiani13877-cfdea6eaf411 (4).json' # مسیر فایل کلید خود را وارد کنید
11
  # KHUZESTAN_ROI = ee.Geometry.Rectangle([47.5, 30.0, 50.5, 32.5]) # دیگر استفاده نمی‌شود اگر محاسبات بر اساس هندسه مزرعه است
12
-
13
- # --- مقداردهی اولیه GEE ---
14
  @st.cache_resource
15
  def initialize_gee():
16
  try:
17
- if 'ee_initialized' not in st.session_state:
 
18
  if not os.path.exists(SERVICE_ACCOUNT_FILE):
19
  st.error(f"خطا: فایل Service Account در مسیر '{SERVICE_ACCOUNT_FILE}' یافت نشد.")
20
- st.stop()
 
 
 
21
  credentials = ee.ServiceAccountCredentials(None, key_file=SERVICE_ACCOUNT_FILE)
22
  ee.Initialize(credentials=credentials, opt_url='https://earthengine-highvolume.googleapis.com')
23
  st.session_state.ee_initialized = True
24
  st.success("اتصال به Google Earth Engine با موفقیت انجام شد.")
25
- return True
 
 
26
  except Exception as e:
27
  st.error(f"خطا در اتصال به GEE: {e}")
28
- st.session_state.ee_initialized = False # اطمینان از تنظیم وضعیت در صورت خطا
29
  return False
30
 
31
- # --- بارگذاری داده‌های مزارع از GEE ---
32
  @st.cache_data(show_spinner="در حال بارگذاری داده‌های مزارع از GEE...")
33
  def load_farm_data_from_gee():
34
- if not st.session_state.get('ee_initialized'):
35
  st.warning("GEE مقداردهی اولیه نشده است.")
36
  return pd.DataFrame()
37
 
@@ -53,7 +57,7 @@ def load_farm_data_from_gee():
53
  try:
54
  centroid = ee_geom.centroid(maxError=1).getInfo()['coordinates']
55
  centroid_lon, centroid_lat = centroid[0], centroid[1]
56
- except Exception: # خطاهای محاسبه مرکز معمولاً بحرانی نیستند
57
  pass
58
 
59
  area_ha = None
@@ -64,7 +68,7 @@ def load_farm_data_from_gee():
64
  area_ha = area_m2 / 10000.0
65
  except Exception:
66
  area_ha = props.get('Area')
67
- else: # اگر ee_geom وجود ندارد، سعی کنید از پراپرتی 'Area' استفاده کنید
68
  area_ha = props.get('Area')
69
 
70
 
@@ -81,7 +85,6 @@ def load_farm_data_from_gee():
81
  'مرکز عرض جغرافیایی': centroid_lat,
82
  })
83
  df = pd.DataFrame(farm_records)
84
- # تبدیل مساحت به عددی و مدیریت خطاهای احتمالی
85
  df['مساحت (هکتار)'] = pd.to_numeric(df['مساحت (هکتار)'], errors='coerce')
86
  return df
87
  except Exception as e:
@@ -92,55 +95,86 @@ def load_farm_data_from_gee():
92
  # --- توابع واکشی داده از GEE ---
93
  @st.cache_data(show_spinner="در حال واکشی داده‌های تبخیر و تعرق از GEE...")
94
  def get_et_data(_farm_geometry, start_date_str, end_date_str):
95
- if not st.session_state.get('ee_initialized') or not _farm_geometry:
96
- return None, None, None # farm_actual_et_mm_day, reference_et0_mm_day, image_for_map
97
  try:
98
  et_collection = ee.ImageCollection('MODIS/061/MOD16A2GF').select('ET', 'PET')
99
  filtered_et = et_collection.filterDate(start_date_str, end_date_str)
 
 
 
 
 
100
 
101
- # تصویر میانگین برای نمایش روی نقشه (برش خورده به مرز مزرعه)
102
  mean_et_image_clipped = filtered_et.mean().clip(_farm_geometry)
103
 
104
- # محاسبه میانگین مقادیر ET و PET در محدوده مزرعه
105
  stats = mean_et_image_clipped.reduceRegion(
106
  reducer=ee.Reducer.mean(),
107
- geometry=_farm_geometry, # استفاده از هندسه دقیق مزرعه
108
- scale=500, # رزولوشن MODIS
109
  maxPixels=1e9
110
  ).getInfo()
111
 
112
  et_kg_m2_8day = stats.get('ET')
113
- pet_kg_m2_8day = stats.get('PET') # این PET به عنوان ET0 استفاده می‌شود
114
 
115
- # تبدیل به mm/day (1 kg/m^2 = 1 mm)
116
  farm_actual_et_mm_day = (et_kg_m2_8day / 8) if et_kg_m2_8day is not None else None
117
  reference_et0_mm_day = (pet_kg_m2_8day / 8) if pet_kg_m2_8day is not None else None
118
 
119
- return farm_actual_et_mm_day, reference_et0_mm_day, mean_et_image_clipped.select('ET') # فقط باند ET برای نقشه ET واقعی
120
- except Exception as e:
121
- st.error(f"خطا در واکشی داده‌های ET از GEE: {e}")
 
 
 
 
 
 
 
122
  return None, None, None
123
 
 
124
  @st.cache_data(show_spinner="در حال واکشی داده‌های رطوبت خاک از GEE...")
125
  def get_soil_moisture_data(_farm_geometry, date_str):
126
- if not st.session_state.get('ee_initialized') or not _farm_geometry:
 
127
  return None
128
  try:
129
  sm_collection = ee.ImageCollection('NASA_USDA/HSL/SMAP_soil_moisture').select(['ssm', 'susm'])
130
- image = sm_collection.filterDate(ee.Date(date_str).advance(-2, 'day'), ee.Date(date_str).advance(2, 'day')) \
131
- .sort('system:time_start', False) \
132
- .first()
 
 
 
 
 
 
 
133
 
134
- if image.bandNames().size().getInfo() == 0:
135
- st.warning(f"داده رطوبت خاک برای تاریخ نزدیک به {date_str} یافت نشد.")
 
 
 
 
 
 
 
136
  return None
137
- return image.clip(_farm_geometry) # برش به هندسه مزرعه
 
 
 
 
 
 
 
138
  except Exception as e:
139
- st.error(f"خطا در واکشی داده‌های رطوبت خاک از GEE: {e}")
140
  return None
141
 
142
-
143
- # --- محاسبه نیاز آبی ---
144
  def calculate_water_needs(et0_mm_day, kc, area_ha, irrigation_efficiency=0.7):
145
  if et0_mm_day is None or kc is None or area_ha is None or pd.isna(area_ha):
146
  return None, None, None
@@ -148,40 +182,24 @@ def calculate_water_needs(et0_mm_day, kc, area_ha, irrigation_efficiency=0.7):
148
  etc_mm_day = kc * et0_mm_day
149
  nir_mm_day = etc_mm_day
150
  gir_mm_day = nir_mm_day / irrigation_efficiency if irrigation_efficiency > 0 else nir_mm_day
151
- daily_volume_m3 = gir_mm_day * area_ha * 10 # 1mm آب در 1 هکتار = 10 متر مکعب
152
  return etc_mm_day, gir_mm_day, daily_volume_m3
153
 
154
- # --- برنامه زمان‌بندی آبیاری ---
155
  def get_irrigation_parameters(farm_info, et0_mm_day_for_kc_estimation):
156
- """
157
- تعیین ضریب گیاهی (Kc) بر اساس اطلاعات مزرعه.
158
- این تابع Kc پیشنهادی را برمی‌گرداند.
159
- """
160
  age_months_str = farm_info.get('سن')
161
- # تبدیل سن به عدد، با فرض اینکه سن به ماه است و ممکن است به صورت رشته ذخیره شده باشد
162
  try:
163
  age_months = float(age_months_str) if age_months_str else 0
164
  except ValueError:
165
- age_months = 0 # مقدار پیش‌فرض اگر تبدیل ناموفق بود
166
-
167
- kc_sugarcane = 0.4 # مقدار پیش فرض کلی
168
- # این مقادیر برای نیشکر مثالی هستند و باید بر اساس داده‌های محلی و دقیق تنظیم شوند.
169
- if 0 <= age_months <= 1: # کاشت تا استقرار اولیه
170
- kc_sugarcane = 0.35
171
- elif 1 < age_months <= 3: # مرحله اولیه و شروع پنجه زنی
172
- kc_sugarcane = 0.5
173
- elif 3 < age_months <= 5: # پنجه زنی کامل و شروع رشد سریع ساقه
174
- kc_sugarcane = 0.85
175
- elif 5 < age_months <= 9: # مرحله رشد سریع ساقه و توسعه پوشش گیاهی (اوج نیاز)
176
- kc_sugarcane = 1.20 # این مقدار می‌تواند تا 1.25 یا بیشتر هم باشد
177
- elif 9 < age_months <= 11: # شروع مرحله رسیدگی
178
- kc_sugarcane = 1.00
179
- elif 11 < age_months <= 13: # رسیدگی کامل تا قبل از برداشت (نیاز کاهش می‌یابد)
180
- kc_sugarcane = 0.75
181
- # برای کشت مجدد (ratoon)، این مراحل ممکن است کمی متفاوت باشند.
182
- # واریته‌های مختلف نیز ممکن است ضرایب متفاوتی داشته باشند.
183
-
184
- # st.caption(f"ضریب گیاهی ($K_c$) پیشنهادی برای واریته '{farm_info.get('واریته')}' با سن حدود {age_months:.1f} ماه: {kc_sugarcane}")
185
  return kc_sugarcane
186
 
187
  def get_irrigation_schedule_text(etc_mm_day, soil_water_holding_capacity_mm=150, mad_percentage=0.5, irrigation_efficiency=0.7):
@@ -190,11 +208,9 @@ def get_irrigation_schedule_text(etc_mm_day, soil_water_holding_capacity_mm=150,
190
 
191
  readily_available_water_mm = soil_water_holding_capacity_mm * mad_percentage
192
  irrigation_interval_days = readily_available_water_mm / etc_mm_day
193
- irrigation_interval_days = round(max(1, irrigation_interval_days)) # حداقل ۱ روز
194
 
195
- # مقدار آب مورد نیاز در هر نوبت آبیاری (خالص)
196
  net_water_per_irrigation_mm = etc_mm_day * irrigation_interval_days
197
- # مقدار آب مورد نیاز در هر نوبت آبیاری (کل، با احتساب راندمان)
198
  gross_water_per_irrigation_mm = net_water_per_irrigation_mm / irrigation_efficiency if irrigation_efficiency > 0 else net_water_per_irrigation_mm
199
 
200
  schedule_info = (
@@ -206,44 +222,38 @@ def get_irrigation_schedule_text(etc_mm_day, soil_water_holding_capacity_mm=150,
206
  return schedule_info, irrigation_interval_days, gross_water_per_irrigation_mm
207
 
208
 
209
- # --- تابع نمایش نقشه ---
210
  def create_ee_map(ee_image_object, center_lat, center_lon, farm_name, zoom_level=12, vis_params=None):
211
  if not ee_image_object:
212
- # st.warning("داده‌ای برای نمایش روی نقشه وجود ندارد.") # این هشدار در بخش مربوطه نمایش داده می‌شود
213
  return None
214
  if center_lat is None or center_lon is None:
215
  st.warning(f"مختصات مرکز برای مزرعه {farm_name} موجود نیست. نقشه نمایش داده نمی‌شود.")
216
  return None
217
 
218
- m = folium.Map(location=[center_lat, center_lon], zoom_start=zoom_level, tiles="OpenStreetMap") # تغییر کاشی پیش‌فرض
219
-
220
- default_vis_params = {'palette': ['blue', 'yellow', 'red']} # یک پالت پیش‌فرض ساده
 
 
 
221
 
 
222
  if vis_params is None:
 
223
  try:
224
- band_names = ee_image_object.bandNames().getInfo()
225
- if band_names:
226
- first_band = band_names[0]
227
- # تلاش برای گرفتن min/max از خود تصویر برای یک نمایش بهتر در محدوده هندسه آن
228
- geometry_for_stats = ee_image_object.geometry() # استفاده از هندسه خود تصویر (که باید از قبل clip شده باشد)
229
- if geometry_for_stats.coordinates().size().getInfo() > 0 : # بررسی وجود هندسه
230
- min_val_info = ee_image_object.select(first_band).reduceRegion(
231
- reducer=ee.Reducer.min(), geometry=geometry_for_stats, scale=30, maxPixels=1e9 # scale مناسب انتخاب شود
232
- ).get(first_band).getInfo()
233
- max_val_info = ee_image_object.select(first_band).reduceRegion(
234
- reducer=ee.Reducer.max(), geometry=geometry_for_stats, scale=30, maxPixels=1e9
235
- ).get(first_band).getInfo()
236
-
237
- if min_val_info is not None and max_val_info is not None and min_val_info < max_val_info:
238
- vis_params = {'min': min_val_info, 'max': max_val_info, 'palette': ['#fde725', '#5ec962', '#21918c', '#3b528b', '#440154']} # Viridis
239
- else:
240
- vis_params = default_vis_params
241
- else: # اگر هندسه تصویر معتبر نیست
242
- vis_params = default_vis_params
243
- else: # اگر باندی وجود ندارد
244
- vis_params = default_vis_params
245
- except Exception: # در صورت بروز هرگونه خطا در تعیین خودکار
246
- vis_params = default_vis_params
247
 
248
  try:
249
  tile_url = ee_image_object.getMapId(vis_params)['tile_fetcher'].url_format
@@ -257,27 +267,22 @@ def create_ee_map(ee_image_object, center_lat, center_lon, farm_name, zoom_level
257
  folium.LayerControl().add_to(m)
258
  except Exception as e:
259
  st.error(f"خطا در افزودن لایه GEE به نقشه برای {farm_name}: {e}")
260
- # st.write("پارامترهای نمایش استفاده شده:", vis_params)
261
- # if ee_image_object:
262
- # st.write("اطلاعات تصویر:", ee_image_object.getInfo()) # برای دیباگ
263
- return None # در صورت خطا، نقشه را برنگردانید
264
-
265
  return m
266
 
267
- # --- Streamlit App ---
268
  st.set_page_config(layout="wide", page_title="محاسبه و برنامه‌ریزی آبیاری مزارع")
269
 
270
  st.title("سامانه هوشمند مدیریت آبیاری مزارع")
271
  st.subheader("ویژه مزارع نیشکر شرکت دهخدا، خوزستان")
272
  st.markdown("---")
273
 
274
- # مقداردهی اولیه GEE
275
  if initialize_gee():
276
  farm_data_df = load_farm_data_from_gee()
277
 
278
  if not farm_data_df.empty:
279
  st.sidebar.header("انتخاب مزرعه")
280
- farm_names = sorted(farm_data_df['مزرعه'].unique().tolist()) # مرتب سازی نام مزارع
281
  selected_farm_name = st.sidebar.selectbox("انتخاب مزرعه:", farm_names)
282
 
283
  selected_farm_info = farm_data_df[farm_data_df['مزرعه'] == selected_farm_name].iloc[0]
@@ -288,40 +293,30 @@ if initialize_gee():
288
  farm_centroid_lat = selected_farm_info['مرکز عرض جغرافیایی']
289
  farm_centroid_lon = selected_farm_info['مرکز طول جغرافیایی']
290
 
291
-
292
  st.header(f"اطلاعات و محاسبات برای مزرعه: {selected_farm_name}")
293
  col_info1, col_info2, col_info3 = st.columns(3)
294
- with col_info1:
295
- st.metric("واریته", farm_variety if farm_variety else "نامشخص")
296
- with col_info2:
297
- st.metric("سن تقریبی (ماه)", str(farm_age) if farm_age else "نامشخص")
298
- with col_info3:
299
- st.metric("مساحت (هکتار)", f"{farm_area_ha:.2f}" if pd.notna(farm_area_ha) else "نامشخص")
300
-
301
 
302
  st.markdown("#### تنظیم پارامترهای ورودی")
303
  col_param1, col_param2, col_param3 = st.columns(3)
304
  with col_param1:
305
- # انتخاب تاریخ برای داده های ماهواره ای ET
306
  default_et_end_date = datetime.today() - timedelta(days=1)
307
- default_et_start_date = default_et_end_date - timedelta(days=7) # دوره 8 روزه MODIS
308
  start_date = st.date_input("تاریخ شروع دوره ET:", default_et_start_date, key="et_start")
309
  end_date = st.date_input("تاریخ پایان دوره ET:", default_et_end_date, key="et_end")
310
-
311
  with col_param2:
312
  sm_date = st.date_input("تاریخ برای رطوبت خاک:", datetime.today() - timedelta(days=1), key="sm_date")
313
  irrigation_efficiency = st.slider("راندمان آبیاری کل مزرعه:", 0.1, 1.0, 0.7, 0.05, key="irrig_eff")
314
-
315
  with col_param3:
316
- # Kc پیشنهادی بر اساس سن و واریته
317
- suggested_kc = get_irrigation_parameters(selected_farm_info, None) # ET0 در اینجا لازم نیست
318
  kc_value = st.number_input(
319
  f"ضریب گیاهی ($K_c$) (پیشنهادی: {suggested_kc:.2f}):",
320
  min_value=0.1, max_value=2.5, value=suggested_kc, step=0.05, key="kc_val",
321
  help=f"بر اساس سن تقریبی {farm_age} ماه و واریته {farm_variety}. در صورت نیاز اصلاح کنید."
322
  )
323
 
324
-
325
  if start_date > end_date:
326
  st.error("تاریخ شروع دوره ET نمی‌تواند بعد از تاریخ پایان آن باشد.")
327
  else:
@@ -337,47 +332,26 @@ if initialize_gee():
337
 
338
  col_metric1, col_metric2, col_metric3 = st.columns(3)
339
  if farm_actual_et_mm_day is not None:
340
- col_metric1.metric("ET واقعی مزرعه (MODIS)", f"{farm_actual_et_mm_day:.2f} mm/day",
341
- help="میانگین تبخیر و تعرق واقعی برآورد شده از تصاویر MODIS برای این مزرعه در دوره مشخص شده.")
342
- else:
343
- col_metric1.warning("ET واقعی مزرعه در دسترس نیست.")
344
 
345
  if reference_et0_mm_day is not None:
346
- col_metric2.metric(" $ET_0$ مرجع (MODIS PET)", f"{reference_et0_mm_day:.2f} mm/day",
347
- help="میانگین تبخیر و تعرق مرجع (پتانسیل) برآورد شده از تصاویر MODIS PET برای این مزرعه در دوره مشخص شده.")
348
-
349
- etc_mm_day, gir_mm_day, daily_volume_m3 = calculate_water_needs(
350
- reference_et0_mm_day,
351
- kc_value,
352
- farm_area_ha,
353
- irrigation_efficiency
354
- )
355
  if etc_mm_day is not None:
356
- col_metric3.metric("$ET_c$ محاسبه شده گیاه", f"{etc_mm_day:.2f} mm/day",
357
- help=f"$K_c \\times ET_0 = {kc_value:.2f} \\times {reference_et0_mm_day:.2f}$")
358
- st.success(
359
- f"**نیاز آبی گیاه ($ET_c$): {etc_mm_day:.2f} mm/day** | "
360
- f"**نیاز آبی کل ($GIR$): {gir_mm_day:.2f} mm/day** | "
361
- f"**حجم آب روزانه مورد نیاز: {daily_volume_m3:.1f} متر مکعب**"
362
- )
363
-
364
- # --- برنامه زمان‌بندی آبیاری ---
365
  st.markdown("#### برنامه زمان‌بندی آبیاری (پیشنهادی)")
366
  col_sched1, col_sched2 = st.columns(2)
367
- with col_sched1:
368
- soil_capacity = st.number_input("ظرفیت نگهداری آب خاک در منطقه ریشه (mm):", value=120, min_value=20, max_value=300, step=10, key="soil_cap")
369
- with col_sched2:
370
- mad_percent = st.slider("درصد تخلیه مجاز رطوبت (MAD):", 0.1, 0.8, 0.5, 0.05, key="mad", help="مثلاً 0.5 یعنی 50% تخلیه مجاز است.")
371
-
372
  schedule_text, _, _ = get_irrigation_schedule_text(etc_mm_day, soil_capacity, mad_percent, irrigation_efficiency)
373
  st.info(schedule_text)
374
- else:
375
- st.warning("امکان محاسبه $ET_c$ و نیاز آبی وجود ندارد. مقادیر $ET_0$ یا $K_c$ معتبر نیستند.")
376
  else:
377
  col_metric2.warning("$ET_0$ مرجع در دسترس نیست.")
378
  st.warning(f"داده‌های تبخیر و تعرق مرجع ($ET_0$) برای دوره انتخاب شده یافت نشد یا خطایی رخ داده است.")
379
 
380
- # --- نمایش نقشه‌ها ---
381
  st.markdown("---")
382
  st.subheader("نقشه‌های ماهواره‌ای")
383
  tab1, tab2 = st.tabs(["نقشه ET واقعی مزرعه", "نقشه رطوبت خاک"])
@@ -385,47 +359,28 @@ if initialize_gee():
385
  with tab1:
386
  st.markdown(f"**نمایش میانگین تبخیر و تعرق واقعی (ET) برای مزرعه {selected_farm_name} ({start_date_str} تا {end_date_str})**")
387
  if et_image_for_map and farm_centroid_lat and farm_centroid_lon:
388
- # پارامترهای نمایش برای ET واقعی (mm/day)
389
- et_vis_params = {'min': 0, 'max': 10, 'palette': ['#ffffcc', '#c7e9b4', '#7fcdbb', '#41b6c4', '#2c7fb8', '#253494']} # YlGnBu palette
390
- et_map = create_ee_map(et_image_for_map,
391
- farm_centroid_lat, farm_centroid_lon,
392
- selected_farm_name,
393
- zoom_level=15, # زوم بیشتر برای یک مزرعه
394
- vis_params=et_vis_params)
395
- if et_map:
396
- st_folium(et_map, width=None, height=500, returned_objects=[])
397
- else:
398
- st.warning("نقشه تبخیر و تعرق واقعی قابل نمایش نیست.")
399
- else:
400
- st.info("داده‌های ET واقعی برای نمایش روی نقشه آماده نیست یا مرکز مزرعه مشخص نیست.")
401
 
402
  with tab2:
403
  st.markdown(f"**نمایش رطوبت سطحی خاک (SSM) برای مزرعه {selected_farm_name} (نزدیک به تاریخ {sm_date_str})**")
404
- soil_moisture_image = get_soil_moisture_data(farm_ee_geometry, sm_date_str)
405
  if soil_moisture_image and farm_centroid_lat and farm_centroid_lon:
406
- # پارامترهای نمایش برای رطوبت خاک SMAP ssm (معمولاً 0-0.6 m^3/m^3 یا 0-~500 mm اگر مقیاس‌بندی شده باشد)
407
- # باند 'ssm' مجموعه داده SMAP مقادیر رطوبت حجمی را نشان می‌دهد.
408
- # برای نمایش بهتر، مقادیر در واحد mm (عمق آب) از مجموعه داده اصلی استفاده می‌شوند.
409
- sm_vis_params = {'bands': ['ssm'], 'min': 0.0, 'max': 40.0, 'palette': ['#feebe2','#fbb4b9','#f768a1','#c51b8a','#7a0177']} # PuRd palette
410
- sm_map = create_ee_map(soil_moisture_image,
411
- farm_centroid_lat, farm_centroid_lon,
412
- selected_farm_name,
413
- zoom_level=15,
414
- vis_params=sm_vis_params)
415
- if sm_map:
416
- st_folium(sm_map, width=None, height=500, returned_objects=[])
417
- else:
418
- st.warning("نقشه رطوبت خاک قابل نمایش نیست.")
419
- else:
420
- st.info(f"داده‌های رطوبت خاک برای تاریخ {sm_date_str} برای نمایش روی نقشه آماده نیست یا مرکز مزرعه مشخص نیست.")
421
  else:
422
  st.error(f"اطلاعات هندسی یا مساحت برای مزرعه {selected_farm_name} یافت نشد. امکان انجام محاسبات و نمایش نقشه وجود ندارد.")
423
-
424
- elif st.session_state.get('ee_initialized'):
425
  st.error("داده‌های مزارع بارگذاری نشدند یا خالی هستند. لطفاً FeatureCollection خود را در GEE و مسیر فایل کلید بررسی کنید.")
426
- else:
427
- st.info("منتظر اتصال به Google Earth Engine... اگر این پیام باقی ماند، مسیر فایل Service Account و اتصال اینترنت خود را بررسی کنید.")
428
 
429
  st.sidebar.markdown("---")
430
  st.sidebar.info("توسعه داده شده با Streamlit و Google Earth Engine")
431
- st.sidebar.info(f"تاریخ اجرا: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
 
9
  # --- پیکربندی ---
10
  SERVICE_ACCOUNT_FILE = 'ee-esmaeilkiani13877-cfdea6eaf411 (4).json' # مسیر فایل کلید خود را وارد کنید
11
  # KHUZESTAN_ROI = ee.Geometry.Rectangle([47.5, 30.0, 50.5, 32.5]) # دیگر استفاده نمی‌شود اگر محاسبات بر اساس هندسه مزرعه است
 
 
12
  @st.cache_resource
13
  def initialize_gee():
14
  try:
15
+ # اگر ee_initialized هنوز در وضعیت جلسه نیست یا مقدار آن False است، سعی در مقداردهی اولیه کن
16
+ if not st.session_state.get('ee_initialized', False):
17
  if not os.path.exists(SERVICE_ACCOUNT_FILE):
18
  st.error(f"خطا: فایل Service Account در مسیر '{SERVICE_ACCOUNT_FILE}' یافت نشد.")
19
+ st.session_state.ee_initialized = False
20
+ # st.stop() # در صورت نیاز به توقف کامل برنامه
21
+ return False # بازگشت وضعیت ناموفق
22
+
23
  credentials = ee.ServiceAccountCredentials(None, key_file=SERVICE_ACCOUNT_FILE)
24
  ee.Initialize(credentials=credentials, opt_url='https://earthengine-highvolume.googleapis.com')
25
  st.session_state.ee_initialized = True
26
  st.success("اتصال به Google Earth Engine با موفقیت انجام شد.")
27
+
28
+ return st.session_state.get('ee_initialized', False) # بازگشت وضعیت فعلی مقداردهی اولیه
29
+
30
  except Exception as e:
31
  st.error(f"خطا در اتصال به GEE: {e}")
32
+ st.session_state.ee_initialized = False # اطمینان از تنظیم وضعیت در صورت بروز هرگونه خطا
33
  return False
34
 
35
+ # --- بارگذاری داده‌های مزارع از GEE (بدون تغییر نسبت به نسخه قبلی شما) ---
36
  @st.cache_data(show_spinner="در حال بارگذاری داده‌های مزارع از GEE...")
37
  def load_farm_data_from_gee():
38
+ if not st.session_state.get('ee_initialized', False): # بررسی صریح مقدار True
39
  st.warning("GEE مقداردهی اولیه نشده است.")
40
  return pd.DataFrame()
41
 
 
57
  try:
58
  centroid = ee_geom.centroid(maxError=1).getInfo()['coordinates']
59
  centroid_lon, centroid_lat = centroid[0], centroid[1]
60
+ except Exception:
61
  pass
62
 
63
  area_ha = None
 
68
  area_ha = area_m2 / 10000.0
69
  except Exception:
70
  area_ha = props.get('Area')
71
+ else:
72
  area_ha = props.get('Area')
73
 
74
 
 
85
  'مرکز عرض جغرافیایی': centroid_lat,
86
  })
87
  df = pd.DataFrame(farm_records)
 
88
  df['مساحت (هکتار)'] = pd.to_numeric(df['مساحت (هکتار)'], errors='coerce')
89
  return df
90
  except Exception as e:
 
95
  # --- توابع واکشی داده از GEE ---
96
  @st.cache_data(show_spinner="در حال واکشی داده‌های تبخیر و تعرق از GEE...")
97
  def get_et_data(_farm_geometry, start_date_str, end_date_str):
98
+ if not st.session_state.get('ee_initialized', False) or not _farm_geometry:
99
+ return None, None, None
100
  try:
101
  et_collection = ee.ImageCollection('MODIS/061/MOD16A2GF').select('ET', 'PET')
102
  filtered_et = et_collection.filterDate(start_date_str, end_date_str)
103
+
104
+ # بررسی اندازه کالکشن ET قبل از .mean() و .clip()
105
+ if filtered_et.size().getInfo() == 0:
106
+ st.warning(f"داده تبخیر و تعرق (ET/PET) برای بازه {start_date_str} تا {end_date_str} یافت نشد.")
107
+ return None, None, None
108
 
 
109
  mean_et_image_clipped = filtered_et.mean().clip(_farm_geometry)
110
 
 
111
  stats = mean_et_image_clipped.reduceRegion(
112
  reducer=ee.Reducer.mean(),
113
+ geometry=_farm_geometry,
114
+ scale=500,
115
  maxPixels=1e9
116
  ).getInfo()
117
 
118
  et_kg_m2_8day = stats.get('ET')
119
+ pet_kg_m2_8day = stats.get('PET')
120
 
 
121
  farm_actual_et_mm_day = (et_kg_m2_8day / 8) if et_kg_m2_8day is not None else None
122
  reference_et0_mm_day = (pet_kg_m2_8day / 8) if pet_kg_m2_8day is not None else None
123
 
124
+ # اطمینان از وجود باندها قبل از انتخاب
125
+ if not mean_et_image_clipped.bandNames().getInfo(): # اگر هیچ باندی وجود نداشته باشد
126
+ return farm_actual_et_mm_day, reference_et0_mm_day, None
127
+
128
+ return farm_actual_et_mm_day, reference_et0_mm_day, mean_et_image_clipped.select('ET')
129
+ except ee.EEException as e: # خطاهای خاص GEE
130
+ st.error(f"خطای Google Earth Engine در واکشی داده‌های ET: {e}")
131
+ return None, None, None
132
+ except Exception as e: # سایر خطاها
133
+ st.error(f"خطای عمومی در واکشی داده‌های ET: {e}")
134
  return None, None, None
135
 
136
+ # --- تابع واکشی رطوبت خاک (اصلاح شده) ---
137
  @st.cache_data(show_spinner="در حال واکشی داده‌های رطوبت خاک از GEE...")
138
  def get_soil_moisture_data(_farm_geometry, date_str):
139
+ if not st.session_state.get('ee_initialized', False) or not _farm_geometry:
140
+ st.warning("GEE مقداردهی اولیه نشده یا هندسه مزرعه نامعتبر است (برای رطوبت خاک).")
141
  return None
142
  try:
143
  sm_collection = ee.ImageCollection('NASA_USDA/HSL/SMAP_soil_moisture').select(['ssm', 'susm'])
144
+ ee_date = ee.Date(date_str)
145
+
146
+ filtered_collection = sm_collection.filterDate(ee_date.advance(-2, 'day'), ee_date.advance(2, 'day'))
147
+
148
+ # --- بررسی کلیدی: ابتدا اندازه کالکشن فیلتر شده را بررسی کنید ---
149
+ collection_size = filtered_collection.size().getInfo()
150
+
151
+ if collection_size == 0:
152
+ st.warning(f"داده رطوبت خاک برای تاریخ نزدیک به {date_str} یافت نشد (هیچ تصویری در بازه زمانی موجود نیست).")
153
+ return None
154
 
155
+ # اگر کالکشن خالی نیست، اولین (جدیدترین) تصویر را بگیرید
156
+ image = filtered_collection.sort('system:time_start', False).first()
157
+
158
+ # در این مرحله، 'image' یک شیء ee.Image است.
159
+ # بررسی اینکه آیا تصویر واقعاً باند داده‌ای دارد (برای اطمینان بیشتر).
160
+ band_names = image.bandNames().getInfo() # .getInfo() برای اجرای سمت سرور و دریافت نتیجه
161
+
162
+ if not band_names: # اگر لیست نام باندها خالی باشد
163
+ st.warning(f"تصویر رطوبت خاک برای تاریخ {date_str} یافت شد اما فاقد باند داده است.")
164
  return None
165
+
166
+ return image.clip(_farm_geometry)
167
+
168
+ except ee.EEException as e:
169
+ st.error(f"خطای Google Earth Engine در هنگام واکشی داده‌های رطوبت خاک: {e}")
170
+ if "Parameter 'image' is required and may not be null" in str(e) or "Image.load: Image asset not found" in str(e) :
171
+ st.error("توضیح بیشتر: این خطا نشان می‌دهد که شیء تصویر مورد استفاده GEE نامعتبر یا تهی بوده است، یا داده‌ای برای تاریخ مورد نظر یافت نشده است.")
172
+ return None
173
  except Exception as e:
174
+ st.error(f"خطای عمومی (غیر GEE) در واکشی داده‌های رطوبت خاک: {e}")
175
  return None
176
 
177
+ # --- محاسبه نیاز آبی (بدون تغییر) ---
 
178
  def calculate_water_needs(et0_mm_day, kc, area_ha, irrigation_efficiency=0.7):
179
  if et0_mm_day is None or kc is None or area_ha is None or pd.isna(area_ha):
180
  return None, None, None
 
182
  etc_mm_day = kc * et0_mm_day
183
  nir_mm_day = etc_mm_day
184
  gir_mm_day = nir_mm_day / irrigation_efficiency if irrigation_efficiency > 0 else nir_mm_day
185
+ daily_volume_m3 = gir_mm_day * area_ha * 10
186
  return etc_mm_day, gir_mm_day, daily_volume_m3
187
 
188
+ # --- برنامه زمان‌بندی آبیاری (بدون تغییر) ---
189
  def get_irrigation_parameters(farm_info, et0_mm_day_for_kc_estimation):
 
 
 
 
190
  age_months_str = farm_info.get('سن')
 
191
  try:
192
  age_months = float(age_months_str) if age_months_str else 0
193
  except ValueError:
194
+ age_months = 0
195
+
196
+ kc_sugarcane = 0.4
197
+ if 0 <= age_months <= 1: kc_sugarcane = 0.35
198
+ elif 1 < age_months <= 3: kc_sugarcane = 0.5
199
+ elif 3 < age_months <= 5: kc_sugarcane = 0.85
200
+ elif 5 < age_months <= 9: kc_sugarcane = 1.20
201
+ elif 9 < age_months <= 11: kc_sugarcane = 1.00
202
+ elif 11 < age_months <= 13: kc_sugarcane = 0.75
 
 
 
 
 
 
 
 
 
 
 
203
  return kc_sugarcane
204
 
205
  def get_irrigation_schedule_text(etc_mm_day, soil_water_holding_capacity_mm=150, mad_percentage=0.5, irrigation_efficiency=0.7):
 
208
 
209
  readily_available_water_mm = soil_water_holding_capacity_mm * mad_percentage
210
  irrigation_interval_days = readily_available_water_mm / etc_mm_day
211
+ irrigation_interval_days = round(max(1, irrigation_interval_days))
212
 
 
213
  net_water_per_irrigation_mm = etc_mm_day * irrigation_interval_days
 
214
  gross_water_per_irrigation_mm = net_water_per_irrigation_mm / irrigation_efficiency if irrigation_efficiency > 0 else net_water_per_irrigation_mm
215
 
216
  schedule_info = (
 
222
  return schedule_info, irrigation_interval_days, gross_water_per_irrigation_mm
223
 
224
 
225
+ # --- تابع نمایش نقشه (بدون تغییر قابل توجه، اما بررسی vis_params مهم است) ---
226
  def create_ee_map(ee_image_object, center_lat, center_lon, farm_name, zoom_level=12, vis_params=None):
227
  if not ee_image_object:
 
228
  return None
229
  if center_lat is None or center_lon is None:
230
  st.warning(f"مختصات مرکز برای مزرعه {farm_name} موجود نیست. نقشه نمایش داده نمی‌شود.")
231
  return None
232
 
233
+ m = folium.Map(location=[center_lat, center_lon], zoom_start=zoom_level, tiles="OpenStreetMap")
234
+
235
+ # اطمینان از اینکه ee_image_object دارای باند است قبل از تلاش برای تعیین vis_params خودکار
236
+ if not ee_image_object.bandNames().getInfo():
237
+ st.warning(f"تصویر GEE برای {farm_name} فاقد باند است و قابل نمایش روی نقشه نیست.")
238
+ return None # یا یک نقشه خالی برگردانید
239
 
240
+ # استفاده از vis_params پیش‌فرض اگر چیزی ارائه نشده باشد
241
  if vis_params is None:
242
+ vis_params = {'min': 0, 'max': 1000, 'palette': ['blue', 'yellow', 'red']} # پالت بسیار عمومی
243
  try:
244
+ first_band = ee_image_object.bandNames().getInfo()[0]
245
+ geometry_for_stats = ee_image_object.geometry()
246
+ if geometry_for_stats.coordinates().size().getInfo() > 0 :
247
+ min_val_info = ee_image_object.select(first_band).reduceRegion(
248
+ reducer=ee.Reducer.min(), geometry=geometry_for_stats, scale=30, maxPixels=1e9
249
+ ).get(first_band).getInfo()
250
+ max_val_info = ee_image_object.select(first_band).reduceRegion(
251
+ reducer=ee.Reducer.max(), geometry=geometry_for_stats, scale=30, maxPixels=1e9
252
+ ).get(first_band).getInfo()
253
+ if min_val_info is not None and max_val_info is not None and min_val_info < max_val_info:
254
+ vis_params = {'min': min_val_info, 'max': max_val_info, 'palette': ['#fde725', '#5ec962', '#21918c', '#3b528b', '#440154']}
255
+ except Exception:
256
+ pass # اگر تعیین خودکار ناموفق بود، از پیش‌فرض استفاده می‌شود
 
 
 
 
 
 
 
 
 
 
257
 
258
  try:
259
  tile_url = ee_image_object.getMapId(vis_params)['tile_fetcher'].url_format
 
267
  folium.LayerControl().add_to(m)
268
  except Exception as e:
269
  st.error(f"خطا در افزودن لایه GEE به نقشه برای {farm_name}: {e}")
270
+ return None
 
 
 
 
271
  return m
272
 
273
+ # --- Streamlit App (بخش UI بدون تغییرات عمده نسبت به قبل) ---
274
  st.set_page_config(layout="wide", page_title="محاسبه و برنامه‌ریزی آبیاری مزارع")
275
 
276
  st.title("سامانه هوشمند مدیریت آبیاری مزارع")
277
  st.subheader("ویژه مزارع نیشکر شرکت دهخدا، خوزستان")
278
  st.markdown("---")
279
 
 
280
  if initialize_gee():
281
  farm_data_df = load_farm_data_from_gee()
282
 
283
  if not farm_data_df.empty:
284
  st.sidebar.header("انتخاب مزرعه")
285
+ farm_names = sorted(farm_data_df['مزرعه'].unique().tolist())
286
  selected_farm_name = st.sidebar.selectbox("انتخاب مزرعه:", farm_names)
287
 
288
  selected_farm_info = farm_data_df[farm_data_df['مزرعه'] == selected_farm_name].iloc[0]
 
293
  farm_centroid_lat = selected_farm_info['مرکز عرض جغرافیایی']
294
  farm_centroid_lon = selected_farm_info['مرکز طول جغرافیایی']
295
 
 
296
  st.header(f"اطلاعات و محاسبات برای مزرعه: {selected_farm_name}")
297
  col_info1, col_info2, col_info3 = st.columns(3)
298
+ with col_info1: st.metric("واریته", farm_variety if farm_variety else "نامشخص")
299
+ with col_info2: st.metric("سن تقریبی (ماه)", str(farm_age) if farm_age else "نامشخص")
300
+ with col_info3: st.metric("مساحت (هکتار)", f"{farm_area_ha:.2f}" if pd.notna(farm_area_ha) else "نامشخص")
 
 
 
 
301
 
302
  st.markdown("#### تنظیم پارامترهای ورودی")
303
  col_param1, col_param2, col_param3 = st.columns(3)
304
  with col_param1:
 
305
  default_et_end_date = datetime.today() - timedelta(days=1)
306
+ default_et_start_date = default_et_end_date - timedelta(days=7)
307
  start_date = st.date_input("تاریخ شروع دوره ET:", default_et_start_date, key="et_start")
308
  end_date = st.date_input("تاریخ پایان دوره ET:", default_et_end_date, key="et_end")
 
309
  with col_param2:
310
  sm_date = st.date_input("تاریخ برای رطوبت خاک:", datetime.today() - timedelta(days=1), key="sm_date")
311
  irrigation_efficiency = st.slider("راندمان آبیاری کل مزرعه:", 0.1, 1.0, 0.7, 0.05, key="irrig_eff")
 
312
  with col_param3:
313
+ suggested_kc = get_irrigation_parameters(selected_farm_info, None)
 
314
  kc_value = st.number_input(
315
  f"ضریب گیاهی ($K_c$) (پیشنهادی: {suggested_kc:.2f}):",
316
  min_value=0.1, max_value=2.5, value=suggested_kc, step=0.05, key="kc_val",
317
  help=f"بر اساس سن تقریبی {farm_age} ماه و واریته {farm_variety}. در صورت نیاز اصلاح کنید."
318
  )
319
 
 
320
  if start_date > end_date:
321
  st.error("تاریخ شروع دوره ET نمی‌تواند بعد از تاریخ پایان آن باشد.")
322
  else:
 
332
 
333
  col_metric1, col_metric2, col_metric3 = st.columns(3)
334
  if farm_actual_et_mm_day is not None:
335
+ col_metric1.metric("ET واقعی مزرعه (MODIS)", f"{farm_actual_et_mm_day:.2f} mm/day", help="میانگین تبخیر و تعرق واقعی برآورد شده از تصاویر MODIS برای این مزرعه در دوره مشخص شده.")
336
+ else: col_metric1.warning("ET واقعی مزرعه در دسترس نیست.")
 
 
337
 
338
  if reference_et0_mm_day is not None:
339
+ col_metric2.metric(" $ET_0$ مرجع (MODIS PET)", f"{reference_et0_mm_day:.2f} mm/day", help="میانگین تبخیر و تعرق مرجع (پتانسیل) برآورد شده از تصاویر MODIS PET برای این مزرعه در دوره مشخص شده.")
340
+ etc_mm_day, gir_mm_day, daily_volume_m3 = calculate_water_needs(reference_et0_mm_day, kc_value, farm_area_ha, irrigation_efficiency)
 
 
 
 
 
 
 
341
  if etc_mm_day is not None:
342
+ col_metric3.metric("$ET_c$ محاسبه شده گیاه", f"{etc_mm_day:.2f} mm/day", help=f"$K_c \\times ET_0 = {kc_value:.2f} \\times {reference_et0_mm_day:.2f}$")
343
+ st.success(f"**نیاز آبی گیاه ($ET_c$): {etc_mm_day:.2f} mm/day** | **نیاز آبی کل ($GIR$): {gir_mm_day:.2f} mm/day** | **حجم آب روزانه مورد نیاز: {daily_volume_m3:.1f} متر مکعب**")
 
 
 
 
 
 
 
344
  st.markdown("#### برنامه زمان‌بندی آبیاری (پیشنهادی)")
345
  col_sched1, col_sched2 = st.columns(2)
346
+ with col_sched1: soil_capacity = st.number_input("ظرفیت نگهداری آب خاک در منطقه ریشه (mm):", value=120, min_value=20, max_value=300, step=10, key="soil_cap")
347
+ with col_sched2: mad_percent = st.slider("درصد تخلیه مجاز رطوبت (MAD):", 0.1, 0.8, 0.5, 0.05, key="mad", help="مثلاً 0.5 یعنی 50% تخلیه مجاز است.")
 
 
 
348
  schedule_text, _, _ = get_irrigation_schedule_text(etc_mm_day, soil_capacity, mad_percent, irrigation_efficiency)
349
  st.info(schedule_text)
350
+ else: st.warning("امکان محاسبه $ET_c$ و نیاز آبی وجود ندارد. مقادیر $ET_0$ یا $K_c$ معتبر نیستند.")
 
351
  else:
352
  col_metric2.warning("$ET_0$ مرجع در دسترس نیست.")
353
  st.warning(f"داده‌های تبخیر و تعرق مرجع ($ET_0$) برای دوره انتخاب شده یافت نشد یا خطایی رخ داده است.")
354
 
 
355
  st.markdown("---")
356
  st.subheader("نقشه‌های ماهواره‌ای")
357
  tab1, tab2 = st.tabs(["نقشه ET واقعی مزرعه", "نقشه رطوبت خاک"])
 
359
  with tab1:
360
  st.markdown(f"**نمایش میانگین تبخیر و تعرق واقعی (ET) برای مزرعه {selected_farm_name} ({start_date_str} تا {end_date_str})**")
361
  if et_image_for_map and farm_centroid_lat and farm_centroid_lon:
362
+ et_vis_params = {'min': 0, 'max': 10, 'palette': ['#ffffcc', '#c7e9b4', '#7fcdbb', '#41b6c4', '#2c7fb8', '#253494']}
363
+ et_map = create_ee_map(et_image_for_map, farm_centroid_lat, farm_centroid_lon, selected_farm_name, zoom_level=15, vis_params=et_vis_params)
364
+ if et_map: st_folium(et_map, width=None, height=500, returned_objects=[])
365
+ else: st.warning("نقشه تبخیر و تعرق واقعی قابل نمایش نیست.")
366
+ else: st.info("داده‌های ET واقعی برای نمایش روی نقشه آماده نیست (ممکن است تصویری در بازه زمانی یافت نشده باشد) یا مرکز مزرعه مشخص نیست.")
 
 
 
 
 
 
 
 
367
 
368
  with tab2:
369
  st.markdown(f"**نمایش رطوبت سطحی خاک (SSM) برای مزرعه {selected_farm_name} (نزدیک به تاریخ {sm_date_str})**")
370
+ soil_moisture_image = get_soil_moisture_data(farm_ee_geometry, sm_date_str) # فراخوانی تابع اصلاح شده
371
  if soil_moisture_image and farm_centroid_lat and farm_centroid_lon:
372
+ sm_vis_params = {'bands': ['ssm'], 'min': 0.0, 'max': 40.0, 'palette': ['#feebe2','#fbb4b9','#f768a1','#c51b8a','#7a0177']}
373
+ sm_map = create_ee_map(soil_moisture_image, farm_centroid_lat, farm_centroid_lon, selected_farm_name, zoom_level=15, vis_params=sm_vis_params)
374
+ if sm_map: st_folium(sm_map, width=None, height=500, returned_objects=[])
375
+ else: st.warning("نقشه رطوبت خاک قابل نمایش نیست.")
376
+ else: st.info(f"داده‌های رطوبت خاک برای تاریخ {sm_date_str} یافت نشد یا برای نمایش روی نقشه آماده نیست یا مرکز مزرعه مشخص نیست.")
 
 
 
 
 
 
 
 
 
 
377
  else:
378
  st.error(f"اطلاعات هندسی یا مساحت برای مزرعه {selected_farm_name} یافت نشد. امکان انجام محاسبات و نمایش نقشه وجود ندارد.")
379
+ elif st.session_state.get('ee_initialized', False): # اگر GEE وصل است ولی داده مزرعه خالی است
 
380
  st.error("داده‌های مزارع بارگذاری نشدند یا خالی هستند. لطفاً FeatureCollection خود را در GEE و مسیر فایل کلید بررسی کنید.")
381
+ else: # اگر GEE مقداردهی اولیه نشده است
382
+ st.warning("اتصال به Google Earth Engine برقرار نشد. لطفاً ابتدا از صحت مسیر فایل Service Account اطمینان حاصل کرده و منتظر اتصال بمانید یا صفحه را مجدداً بارگذاری کنید.")
383
 
384
  st.sidebar.markdown("---")
385
  st.sidebar.info("توسعه داده شده با Streamlit و Google Earth Engine")
386
+ st.sidebar.info(f"تاریخ اجرا: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")