Esmaeilkianii commited on
Commit
6a8f19e
·
verified ·
1 Parent(s): ab32faf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +765 -242
app.py CHANGED
@@ -5,14 +5,35 @@ import folium
5
  from streamlit_folium import folium_static
6
  import ee
7
  import os
8
- import requests
 
9
  from datetime import datetime, timedelta
10
  import plotly.express as px
11
  import plotly.graph_objects as go
 
 
 
 
 
 
 
12
  from streamlit_lottie import st_lottie
13
- import json
14
-
15
- # تنظیمات صفحه
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  st.set_page_config(
17
  page_title="سامانه هوشمند پایش مزارع نیشکر دهخدا",
18
  page_icon="🌿",
@@ -20,295 +41,797 @@ st.set_page_config(
20
  initial_sidebar_state="expanded"
21
  )
22
 
23
- # استایل‌های سفارشی برای رابط کاربری
24
  st.markdown("""
25
  <style>
26
- @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;700&display=swap');
27
- * { font-family: 'Vazirmatn', sans-serif !important; }
28
- .main { background: linear-gradient(135deg, #f5f7fa 0%, #e4e9f2 100%); }
29
- .rtl { direction: rtl; text-align: right; }
30
- .stButton>button { background: #1a8754; color: white; border-radius: 8px; }
31
- .metric-card { background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); text-align: center; }
32
- .metric-value { font-size: 1.5rem; font-weight: bold; color: #1a8754; }
33
- .metric-label { font-size: 0.9rem; color: #666; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  </style>
35
  """, unsafe_allow_html=True)
36
 
37
- # اطلاعات اهراز هویت Google Earth Engine
38
- credentials = {
39
- "type": "service_account",
40
- "project_id": "ee-esmaeilkiani13877",
41
- "private_key_id": "cfdea6eaf4115cb6462626743e4b15df85fd0c7f",
42
- "private_key": """-----BEGIN PRIVATE KEY-----
43
- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCjeOvgKi+gWK6k
44
- 2/0RXOA3LAo51DVxA1ja9v0qFOn4FNOStxkwlKvcK8yDQNb53FPORHFIUHvev3y7
45
- iHr/UEUqnn5Rzjbf0k3qWB/fS377/UP4VznMsFpKiHNxCBtaNS8KLk6Rat6Y7Xfm
46
- JfpSU7ZjYZmVc81M/7iFofGUSJoHYpxhyt3rjp53huxJNNW5e12TFnLkyg1Ja/9X
47
- GMTt+vjVcO4XhQCIlaGVdSKS2sHlHgzpzE6KtuUKjDMEBqPkWF4xc16YavYltwPd
48
- qULCu2/t6dczhYL4NEFj8wL+KJqOojfsyoWmzqPFx1Bbxk4BVPk/lslq9+m9p5kq
49
- SCG0/9W9AgMBAAECggEAEGchw+x3uu8rFv+79PIMzXxtyj+w3RYo5E/EN2TB1VLB
50
- qAcXT/ibBgyfCMyIxamF/zx+4XKx+zfbnDWlodi8F/qvUiYO+4ZuqwUMras1orNX
51
- DqQx+If5h2EJtF3L4NFVVwAuggjnLREm5sEIzRn5Qx+X+ZcVEpTWPxJw2yAt1G+3
52
- k311KqD+zR7jQfchXU4xQQ1ZoHkdAJ/DPGun6x+HUOq7Gus73A6IzLp12ZoiHN3n
53
- kY+lG8cMs039QAe/OhZFEo5I9cNSaI688HmsLRivZ26WoPEnwcN0MHQGtXqGmMUI
54
- CcpgJqllqdWMuBlYcpSadn7rZzPujSlzIxkvieLeAQKBgQDNTYUWZdGbA2sHcBpJ
55
- rqKwDYF/AwZtjx+hXHVBRbR6DJ1bO2P9n51ioTMP/H9K61OBAMZ7w71xJ2I+9Snv
56
- cYumPWoiUwiOhTh3O7nYz6mR7sK0HuUCZfYdaxJVnLgNCgj+w9AxYnkzOyL9/QvJ
57
- knrlMKf4H59NbapBqy5spilq1QKBgQDL1wkGHhoTuLb5Xp3X3CX4S7WMke4T01bO
58
- PpMmlewVgH5lK5wTcZjB8QRO2QFQtUZTP/Ghv6ZH4h/3P9/ZIF3hV5CSsUkr/eFf
59
- MY+fQL1K/puwfZbSDcH1GtDToOyoLFIvPXBJo0Llg/oF2TK1zGW3cPszeDf/Tm6x
60
- UwUMw2BjSQKBgEJzAMyLEBi4NoAlzJxkpcuN04gkloQHexljL6B8yzlls9i/lFGW
61
- w/4UZs6ZzymUmWZ7tcKBTGO/d5EhEP2rJqQb5KpPbcmTXP9amYCPVjchrGtYRI9O
62
- KSbEbR7ApuGxic/L2Sri0I/AaEcFDDel7ZkY8oTg11LcV+sBWPlZnrYxAoGBALXj
63
- /DlpQvu2KA/9TfwAhiE57Zax4S/vtdX0IHqd7TyCnEbK00rGYvksiBuTqIjMOSSw
64
- On2K9mXOcZe/d4/YQe2CpY9Ag3qt4R2ArBf/POpep66lYp+thxWgCBfP0V1/rxZY
65
- TIppFJiZW9E8LvPqoBlAx+b1r4IyCrRQ0IDDFo+BAoGBAMCff4XKXHlV2SDOL5uh
66
- V/f9ApEdF4leuo+hoMryKuSQ9Y/H0A/Lzw6KP5FLvVtqc0Kw2D1oLy8O72a1xwfY
67
- 8dpZMNzKAWWS7viN0oC+Ebj2Foc2Mn/J6jdhtP/YRLTqvoTWCa2rVcn4R1BurMIf
68
- La4DJE9BagGdVNTDtynBhKhZ
69
- -----END PRIVATE KEY-----
70
- """,
71
- "client_email": "dehkhodamap-e9f0da4ce9f6514021@ee-esmaeilkiani13877.iam.gserviceaccount.com",
72
- "client_id": "113062529451626176784",
73
- "auth_uri": "https://accounts.google.com/o/oauth2/auth",
74
- "token_uri": "https://oauth2.googleapis.com/token",
75
- "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
76
- "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/dehkhodamap-e9f0da4ce9f6514021%40ee-esmaeilkiani13877.iam.gserviceaccount.com",
77
- "universe_domain": "googleapis.com"
78
- }
79
 
80
- # راه‌اندازی Google Earth Engine
81
  @st.cache_resource
82
  def initialize_earth_engine():
83
  try:
84
- ee.Initialize(ee.ServiceAccountCredentials(credentials["client_email"], key_data=credentials["private_key"]))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  return True
86
  except Exception as e:
87
  st.error(f"خطا در اتصال به Earth Engine: {e}")
88
  return False
89
 
90
- # تابع بارگذاری داده‌های مزارع از فایل CSV
91
  @st.cache_data
92
  def load_farm_data():
93
  try:
94
  df = pd.read_csv("پایگاه داده (1).csv")
95
- if df.empty:
96
- st.warning("فایل داده خالی است.")
97
  return df
98
- except FileNotFoundError:
99
- st.error("فایل پایگاه داده (1).csv یافت نشد.")
100
- return pd.DataFrame()
101
  except Exception as e:
102
- st.error(f"خطا در بارگذاری داده‌ها: {e}")
103
  return pd.DataFrame()
104
 
105
- # تابع بارگذاری مختصات مزارع از فایل CSV
106
  @st.cache_data
107
  def load_coordinates_data():
108
  try:
109
  df = pd.read_csv("farm_coordinates.csv")
110
- if df.empty:
111
- st.warning("فایل مختصات خالی است.")
112
  return df
113
- except FileNotFoundError:
114
- st.error("فایل farm_coordinates.csv یافت نشد.")
115
- return pd.DataFrame()
116
  except Exception as e:
117
- st.error(f"خطا در بارگذاری مختصات: {e}")
118
  return pd.DataFrame()
119
 
120
- # تابع دریافت داده‌های هواشناسی از OpenWeatherMap
121
- def get_weather_data(lat, lon, api_key="ed47316a45379e2221a75f813229fb46"):
 
 
 
 
 
 
 
 
122
  url = f"http://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={api_key}&units=metric"
123
- try:
124
- response = requests.get(url)
125
- response.raise_for_status()
126
  return response.json()
127
- except Exception as e:
128
- st.error(f"خطا در دریافت داده‌های هواشناسی: {e}")
129
- return None
130
 
131
- # تابع تخمین نیاز آبی مزرعه
132
  def estimate_water_requirement(farm_id, date_str):
133
- farm_row = coordinates_df[coordinates_df["مزرعه"] == farm_id].iloc[0]
134
- lat, lon = farm_row["عرض جغرافیایی"], farm_row["طول جغرافیایی"]
135
- weather = get_weather_data(lat, lon)
136
- if weather:
137
- temp = weather["main"]["temp"]
138
- humidity = weather["main"]["humidity"]
139
- # فرمول ساده تخمین نیاز آبی (برای دقت بیشتر از Penman-Monteith استفاده شود)
140
- water_req = max(0, (temp - 20) * 0.5 + (100 - humidity) * 0.1)
141
- return water_req
142
  return None
143
 
144
- # تابع محاسبه شاخص‌های ماهواره‌ای (NDVI، NDWI، LAI، CHL)
145
- @st.cache_data
146
- def calculate_indices(farm_id, date_str):
147
  try:
148
- farm_row = coordinates_df[coordinates_df["مزرعه"] == farm_id].iloc[0]
149
- lat, lon = farm_row["عرض جغرافیایی"], farm_row["طول جغرافیایی"]
150
- date_obj = datetime.strptime(date_str, "%Y-%m-%d")
151
- start_date = (date_obj - timedelta(days=7)).strftime("%Y-%m-%d")
152
- end_date = date_obj.strftime("%Y-%m-%d")
153
-
154
- region = ee.Geometry.Point([lon, lat]).buffer(500)
155
- s2 = (ee.ImageCollection("COPERNICUS/S2_SR")
156
- .filterDate(start_date, end_date)
157
- .filterBounds(region)
158
- .filter(ee.Filter.lt("CLOUDY_PIXEL_PERCENTAGE", 20))
159
- .sort("CLOUDY_PIXEL_PERCENTAGE")
160
- .first())
161
-
162
- if not s2.getInfo():
163
- st.warning(f"تصویری برای مزرعه {farm_id} یافت نشد.")
164
- return {"NDVI": 0, "NDWI": 0, "LAI": 0, "CHL": 0}
165
-
166
- ndvi = s2.normalizedDifference(["B8", "B4"]).rename("NDVI")
167
- ndwi = s2.normalizedDifference(["B3", "B8"]).rename("NDWI")
168
-
169
- ndvi_value = ndvi.reduceRegion(reducer=ee.Reducer.mean(), geometry=region, scale=10).get("NDVI").getInfo()
170
- ndwi_value = ndwi.reduceRegion(reducer=ee.Reducer.mean(), geometry=region, scale=10).get("NDWI").getInfo()
171
-
172
- lai_value = 6 * ndvi_value if ndvi_value else 0 # تخمین LAI
173
- chl_value = 50 * ndvi_value if ndvi_value else 0 # تخمین کلروفیل
174
-
175
- return {
176
- "NDVI": ndvi_value or 0,
177
- "NDWI": ndwi_value or 0,
178
- "LAI": min(max(lai_value, 0), 6),
179
- "CHL": chl_value
180
- }
181
- except Exception as e:
182
- st.error(f"خطا در محاسبه شاخص‌ها: {e}")
183
- return {"NDVI": 0, "NDWI": 0, "LAI": 0, "CHL": 0}
184
-
185
- # تابع ایجاد نقشه NDVI با استفاده از Earth Engine و Folium
186
- def create_ee_map(farm_id, date_str):
187
- try:
188
- farm_row = coordinates_df[coordinates_df["مزرعه"] == farm_id].iloc[0]
189
- lat, lon = farm_row["عرض جغرافیایی"], farm_row["طول جغرافیایی"]
190
- m = folium.Map(location=[lat, lon], zoom_start=14)
191
-
192
- date_obj = datetime.strptime(date_str, "%Y-%m-%d")
193
- start_date = (date_obj - timedelta(days=7)).strftime("%Y-%m-%d")
194
- end_date = date_obj.strftime("%Y-%m-%d")
195
-
196
  region = ee.Geometry.Point([lon, lat]).buffer(1500)
197
- s2 = (ee.ImageCollection("COPERNICUS/S2_SR")
198
- .filterDate(start_date, end_date)
199
- .filterBounds(region)
200
- .filter(ee.Filter.lt("CLOUDY_PIXEL_PERCENTAGE", 20))
201
- .sort("CLOUDY_PIXEL_PERCENTAGE")
202
- .first())
203
-
204
- if not s2.getInfo():
205
- st.warning(f"تصویری برای مزرعه {farm_id} یافت نشد.")
206
- return None
207
-
208
- ndvi = s2.normalizedDifference(["B8", "B4"]).rename("NDVI")
209
- viz_params = {"min": -0.2, "max": 0.8, "palette": ["#ff0000", "#ffd700", "#32cd32"]}
210
- map_id = ndvi.getMapId(viz_params)
211
-
212
- folium.TileLayer(
213
- tiles=map_id["tile_fetcher"].url_format,
214
- attr="Google Earth Engine",
215
- name="NDVI",
216
- overlay=True
217
- ).add_to(m)
218
-
219
- folium.Marker([lat, lon], popup=f"مزرعه {farm_id}").add_to(m)
220
-
221
- # افزودن راهنما به نقشه
222
- legend_html = """
223
- <div style="position: fixed; bottom: 50px; right: 50px; z-index: 1000; background: white; padding: 10px; border-radius: 5px;">
224
- <p>راهنما: شاخص NDVI</p>
225
- <p><span style="color: #ff0000;">■</span> ضعیف (NDVI 0.3)</p>
226
- <p><span style="color: #ffd700;">■</span> متوسط (0.3 < NDVI ≤ 0.6)</p>
227
- <p><span style="color: #32cd32;">■</span> سالم (NDVI > 0.6)</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  </div>
229
- """
230
  m.get_root().html.add_child(folium.Element(legend_html))
231
-
232
  return m
233
  except Exception as e:
234
  st.error(f"خطا در ایجاد نقشه: {e}")
235
  return None
236
 
237
- # بارگذاری اولیه داده‌ها و راه‌اندازی Earth Engine
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  ee_initialized = initialize_earth_engine()
239
  farm_df = load_farm_data()
240
  coordinates_df = load_coordinates_data()
241
 
242
- # منوی ناوبری در سایدبار
243
- menu = ["داشبورد", "نقشه", "ورود داده", "تحلیل", "گزارش"]
244
- selected = st.sidebar.selectbox("منو", menu)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
 
246
- # بخش داشبورد
247
  if selected == "داشبورد":
248
- st.markdown("## داشبورد مزارع نیشکر", unsafe_allow_html=True, class_="rtl")
249
- col1, col2, col3 = st.columns(3)
 
250
  with col1:
251
- st.markdown(f'<div class="metric-card"><div class="metric-value">{len(farm_df)}</div><div class="metric-label">تعداد مزارع</div></div>', unsafe_allow_html=True)
 
 
 
 
252
  with col2:
253
- st.markdown(f'<div class="metric-card"><div class="metric-value">{farm_df["مساحت"].sum():.1f}</div><div class="metric-label">مساحت کل (هکتار)</div></div>', unsafe_allow_html=True)
 
 
 
 
 
254
  with col3:
255
- st.markdown(f'<div class="metric-card"><div class="metric-value">{farm_df["کانال"].nunique()}</div><div class="metric-label">تعداد کانال‌ها</div></div>', unsafe_allow_html=True)
256
-
257
- # نمودار توزیع واریته‌ها
258
- st.plotly_chart(px.pie(farm_df, names="واریته", title="توزیع واریته‌ها"), use_container_width=True)
259
-
260
- # بخش نقشه
261
- elif selected == "نقشه":
262
- st.markdown("## نقشه مزارع", unsafe_allow_html=True, class_="rtl")
263
- farm_id = st.selectbox("انتخاب مزرعه", coordinates_df["مزرعه"])
264
- date = st.date_input("تاریخ", datetime.now())
265
- if st.button("نمایش نقشه"):
266
- with st.spinner("در حال بارگذاری نقشه..."):
267
- m = create_ee_map(farm_id, date.strftime("%Y-%m-%d"))
268
- if m:
269
- folium_static(m, width=800, height=600)
270
-
271
- # بخش ورود داده
272
- elif selected == "ورود داده":
273
- st.markdown("## ورود داده‌های روزانه", unsafe_allow_html=True, class_="rtl")
274
- week = st.number_input("هفته", min_value=1, max_value=52, step=1)
275
- day = st.selectbox("روز", ["شنبه", "یکشنبه", "دوشنبه", "سه‌شنبه", "چهارشنبه", "پنجشنبه"])
276
- farm_id = st.selectbox("مزرعه", farm_df["مزرعه"])
277
-
278
- with st.form("data_entry_form"):
279
- height = st.number_input("ارتفاع (سانتی‌متر)", min_value=0.0, max_value=300.0)
280
- moisture = st.number_input("رطوبت غلاف (%)", min_value=0.0, max_value=100.0)
281
- submit = st.form_submit_button("ذخیره")
282
- if submit:
283
- if height > 0 and moisture >= 0:
284
- st.success(f"داده‌ها برای مزرعه {farm_id} در هفته {week} ذخیر�� شد.")
285
- else:
286
- st.error("لطفاً مقادیر معتبر وارد کنید.")
287
-
288
- # بخش تحلیل داده
289
- elif selected == "تحلیل":
290
- st.markdown("## تحلیل داده‌ها", unsafe_allow_html=True, class_="rtl")
291
- farm_id = st.selectbox("مزرعه", coordinates_df["مزرعه"])
292
- date = st.date_input("تاریخ", datetime.now())
293
- if st.button("محاسبه شاخص‌ها"):
294
- with st.spinner("در حال محاسبه..."):
295
- indices = calculate_indices(farm_id, date.strftime("%Y-%m-%d"))
296
- st.write(f"NDVI: {indices['NDVI']:.2f}")
297
- st.write(f"NDWI: {indices['NDWI']:.2f}")
298
- st.write(f"LAI: {indices['LAI']:.2f}")
299
- st.write(f"CHL: {indices['CHL']:.2f}")
300
- water_req = estimate_water_requirement(farm_id, date.strftime("%Y-%m-%d"))
301
- if water_req:
302
- st.write(f"نیاز آبی: {water_req:.2f} میلی‌متر در روز")
303
-
304
- # بخش گزارش‌گیری
305
- elif selected == "گزارش":
306
- st.markdown("## گزارش‌گیری", unsafe_allow_html=True, class_="rtl")
307
- st.write("گزارش‌ها در حال توسعه هستند.")
308
-
309
- # آزمون‌های واحد (برای اجرا در محیط جداگانه)
310
- def test_calculate_indices():
311
- assert "NDVI" in calculate_indices("مزرعه 1", "2023-10-01"), "محاسبه شاخص‌ها باید NDVI را برگرداند"
312
-
313
- if __name__ == "__main__":
314
- st.write("اپلیکیشن در حال اجرا است...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  from streamlit_folium import folium_static
6
  import ee
7
  import os
8
+ import json
9
+ import time
10
  from datetime import datetime, timedelta
11
  import plotly.express as px
12
  import plotly.graph_objects as go
13
+ from PIL import Image
14
+ import base64
15
+ from io import BytesIO
16
+ import matplotlib.pyplot as plt
17
+ import seaborn as sns
18
+ import altair as alt
19
+ from streamlit_option_menu import option_menu
20
  from streamlit_lottie import st_lottie
21
+ import requests
22
+ import hydralit_components as hc
23
+ from streamlit_extras.colored_header import colored_header
24
+ from streamlit_extras.metric_cards import style_metric_cards
25
+ from streamlit_extras.chart_container import chart_container
26
+ from streamlit_extras.add_vertical_space import add_vertical_space
27
+ from streamlit_card import card
28
+ import pydeck as pdk
29
+ import math
30
+ from sklearn.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
+ # Page configuration with custom theme
37
  st.set_page_config(
38
  page_title="سامانه هوشمند پایش مزارع نیشکر دهخدا",
39
  page_icon="🌿",
 
41
  initial_sidebar_state="expanded"
42
  )
43
 
44
+ # Custom CSS with modern design, animations, and theme support
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
+ font-family: 'Vazirmatn', sans-serif !important;
51
+ transition: all 0.3s ease;
52
+ }
53
+
54
+ .main {
55
+ background: linear-gradient(135deg, #f5f7fa 0%, #e4e9f2 100%);
56
+ }
57
+
58
+ .dark-theme .main {
59
+ background: linear-gradient(135deg, #2c3e50 0%, #1a252f 100%);
60
+ color: #ecf0f1;
61
+ }
62
+
63
+ .main-header {
64
+ background: linear-gradient(90deg, #1a8754 0%, #115740 100%);
65
+ padding: 1.5rem;
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 selection
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
+ # Earth Engine Initialization
163
  @st.cache_resource
164
  def initialize_earth_engine():
165
  try:
166
+ service_account = 'dehkhodamap-e9f0da4ce9f6514021@ee-esmaeilkiani13877.iam.gserviceaccount.com'
167
+ credentials_dict = {
168
+ "type": "service_account",
169
+ "project_id": "ee-esmaeilkiani13877",
170
+ "private_key_id": "cfdea6eaf4115cb6462626743e4b15df85fd0c7f",
171
+ "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCjeOvgKi+gWK6k\n2/0RXOA3LAo51DVxA1ja9v0qFOn4FNOStxkwlKvcK8yDQNb53FPORHFIUHvev3y7\niHr/UEUqnn5Rzjbf0k3qWB/fS377/UP4VznMsFpKiHNxCBtaNS8KLk6Rat6Y7Xfm\nJfpSU7ZjYZmVc81M/7iFofGUSJoHYpxhyt3rjp53huxJNNW5e12TFnLkyg1Ja/9X\nGMTt+vjVcO4XhQCIlaGVdSKS2sHlHgzpzE6KtuUKjDMEBqPkWF4xc16YavYltwPd\nqULCu2/t6dczhYL4NEFj8wL+KJqOojfsyoWmzqPFx1Bbxk4BVPk/lslq9+m9p5kq\nSCG0/9W9AgMBAAECggEAEGchw+x3uu8rFv+79PIMzXxtyj+w3RYo5E/EN2TB1VLB\nqAcXT/ibBgyfCMyIxamF/zx+4XKx+zfbnDWlodi8F/qvUiYO+4ZuqwUMras1orNX\nDqQx+If5h2EJtF3L4NFVVwAuggjnLREm5sEIzRn5Qx+X+ZcVEpTWPxJw2yAt1G+3\nk311KqD+zR7jQfchXU4xQQ1ZoHkdAJ/DPGun6x+HUOq7Gus73A6IzLp12ZoiHN3n\nkY+lG8cMs039QAe/OhZFEo5I9cNSaI688HmsLRivZ26WoPEnwcN0MHQGtXqGmMUI\nCcpgJqllqdWMuBlYcpSadn7rZzPujSlzIxkvieLeAQKBgQDNTYUWZdGbA2sHcBpJ\nrqKwDYF/AwZtjx+hXHVBRbR6DJ1bO2P9n51ioTMP/H9K61OBAMZ7w71xJ2I+9Snv\ncYumPWoiUwiOhTh3O7nYz6mR7sK0HuUCZfYdaxJVnLgNCgj+w9AxYnkzOyL9/QvJ\nknrlMKf4H59NbapBqy5spilq1QKBgQDL1wkGHhoTuLb5Xp3X3CX4S7WMke4T01bO\nPpMmlewVgH5lK5wTcZjB8QRO2QFQtUZTP/Ghv6ZH4h/3P9/ZIF3hV5CSsUkr/eFf\nMY+fQL1K/puwfZbSDcH1GtDToOyoLFIvPXBJo0Llg/oF2TK1zGW3cPszeDf/Tm6x\nUwUMw2BjSQKBgEJzAMyLEBi4NoAlzJxkpcuN04gkloQHexljL6B8yzlls9i/lFGW\nw/4UZs6ZzymUmWZ7tcKBTGO/d5EhEP2rJqQb5KpPbcmTXP9amYCPVjchrGtYRI9O\nKSbEbR7ApuGxic/L2Sri0I/AaEcFDDel7ZkY8oTg11LcV+sBWPlZnrYxAoGBALXj\n/DlpQvu2KA/9TfwAhiE57Zax4S/vtdX0IHqd7TyCnEbK00rGYvksiBuTqIjMOSSw\nOn2K9mXOcZe/d4/YQe2CpY9Ag3qt4R2ArBf/POpep66lYp+thxWgCBfP0V1/rxZY\nTIppFJiZW9E8LvPqoBlAx+b1r4IyCrRQ0IDDFo+BAoGBAMCff4XKXHlV2SDOL5uh\nV/f9ApEdF4leuo+hoMryKuSQ9Y/H0A/Lzw6KP5FLvVtqc0Kw2D1oLy8O72a1xwfY\n8dpZMNzKAWWS7viN0oC+Ebj2Foc2Mn/J6jdhtP/YRLTqvoTWCa2rVcn4R1BurMIf\nLa4DJE9BagGdVNTDtynBhKhZ\n-----END PRIVATE KEY-----\n",
172
+ "client_email": "dehkhodamap-e9f0da4ce9f6514021@ee-esmaeilkiani13877.iam.gserviceaccount.com",
173
+ "client_id": "113062529451626176784",
174
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
175
+ "token_uri": "https://oauth2.googleapis.com/token",
176
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
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 load_farm_data():
197
  try:
198
  df = pd.read_csv("پایگاه داده (1).csv")
 
 
199
  return df
 
 
 
200
  except Exception as e:
201
+ st.error(f"خطا در بارگذاری داده‌های مزارع: {e}")
202
  return pd.DataFrame()
203
 
 
204
  @st.cache_data
205
  def load_coordinates_data():
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 != 200:
218
+ return None
219
+ return r.json()
220
+
221
+ # Weather data function
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
+ # Estimate water requirement
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
+ lat, lon = farm_row['عرض جغرافیایی'], farm_row['طول جغرافیایی']
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
+ water_requirement = (temperature - 20) * 0.5 + (100 - humidity) * 0.1
239
+ return max(0, water_requirement)
240
  return None
241
 
242
+ # Create Earth Engine map with heatmap option
243
+ def create_ee_map(farm_id, date_str, layer_type="NDVI", heatmap=False):
 
244
  try:
245
+ farm_row = coordinates_df[coordinates_df['مزرعه'] == farm_id].iloc[0]
246
+ lat, lon = farm_row['عرض جغرافیایی'], farm_row['طول جغرافیایی']
247
+
248
+ m = folium.Map(location=[lat, lon], zoom_start=14, tiles='CartoDB positron')
249
+
250
+ date_obj = datetime.strptime(date_str, '%Y-%m-%d')
251
+ start_date = (date_obj - timedelta(days=5)).strftime('%Y-%m-%d')
252
+ end_date = (date_obj + timedelta(days=5)).strftime('%Y-%m-%d')
253
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  region = ee.Geometry.Point([lon, lat]).buffer(1500)
255
+
256
+ s2 = ee.ImageCollection('COPERNICUS/S2_SR') \
257
+ .filterDate(start_date, end_date) \
258
+ .filterBounds(region) \
259
+ .sort('CLOUDY_PIXEL_PERCENTAGE') \
260
+ .first()
261
+
262
+ if layer_type == "NDVI":
263
+ index = s2.normalizedDifference(['B8', 'B4']).rename('NDVI')
264
+ viz_params = {'min': -0.2, 'max': 0.8, 'palette': ['#ff0000', '#ff4500', '#ffd700', '#32cd32', '#006400']}
265
+ legend_title = 'شاخص پوشش گیاهی (NDVI)'
266
+ elif layer_type == "NDMI":
267
+ index = s2.normalizedDifference(['B8', 'B11']).rename('NDMI')
268
+ viz_params = {'min': -0.5, 'max': 0.5, 'palette': ['#8b0000', '#ff8c00', '#00ced1', '#00b7eb', '#00008b']}
269
+ legend_title = 'شاخص رطوبت (NDMI)'
270
+ elif layer_type == "EVI":
271
+ nir = s2.select('B8')
272
+ red = s2.select('B4')
273
+ blue = s2.select('B2')
274
+ index = nir.subtract(red).multiply(2.5).divide(nir.add(red.multiply(6)).subtract(blue.multiply(7.5)).add(1)).rename('EVI')
275
+ viz_params = {'min': 0, 'max': 1, 'palette': ['#d73027', '#f46d43', '#fdae61', '#fee08b', '#4caf50']}
276
+ legend_title = 'شاخص پیشرفته گیاهی (EVI)'
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
+ if selected_variety != "all":
343
+ filtered_farms = filtered_farms[filtered_farms['واریته'] == selected_variety]
344
+ if selected_age != "all":
345
+ filtered_farms = filtered_farms[filtered_farms['سن'] == selected_age]
346
+
347
+ farm_growth_data = []
348
+ for _, farm in filtered_farms.iterrows():
349
+ base_height = np.random.uniform(50, 100)
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
+ if layer_type == "NDVI":
378
+ mean = round(np.random.uniform(0.6, 0.8), 2)
379
+ min_val = round(mean - np.random.uniform(0.2, 0.3), 2)
380
+ max_val = round(mean + np.random.uniform(0.1, 0.2), 2)
381
+ std_dev = round(np.random.uniform(0.05, 0.15), 2)
382
+ elif layer_type == "NDMI":
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
+ dates = pd.date_range(end=datetime.now(), periods=30 if period == "weekly" else 90 if period == "monthly" else 365, freq='W' if period == "weekly" else 'M' if period == "monthly" else 'Q')
422
+ if layer_type == "NDVI":
423
+ values = np.random.normal(0.7, 0.1, len(dates))
424
+ elif layer_type == "NDMI":
425
+ values = np.random.normal(0.4, 0.1, len(dates))
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
+ # Advanced prediction with Random Forest and LSTM
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, y = [], []
449
+ for i in range(3, len(scaled_heights)):
450
+ X.append(scaled_heights[i-3:i])
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=50, batch_size=1, verbose=0)
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
+ features = farm_data[['مساحت', 'سن']].astype(float)
475
+ kmeans = KMeans(n_clusters=3, random_state=42)
476
+ farm_data['Cluster'] = kmeans.fit_predict(features)
477
+ return farm_data, kmeans.cluster_centers_
478
+
479
+ # Initialize Earth Engine
480
  ee_initialized = initialize_earth_engine()
481
  farm_df = load_farm_data()
482
  coordinates_df = load_coordinates_data()
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 header
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
+ options=["داشبورد", "نقشه مزارع", "ورود اطلاعات", "تحلیل داده‌ها", "گزارش‌گیری", "تنظیمات"],
503
+ icons=["speedometer2", "map", "pencil-square", "graph-up", "file-earmark-text", "gear"],
504
+ default_index=0,
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
  with col1:
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
+ with col_filter1:
550
+ variety_filter = st.multiselect("واریته", ["همه"] + list(farm_df['واریته'].unique()), default="همه")
551
+ with col_filter2:
552
+ age_filter = st.multiselect("سن", ["همه"] + list(farm_df['سن'].unique()), default="همه")
553
+ with col_filter3:
554
+ channel_filter = st.multiselect("کانال", ["همه"] + list(farm_df['کانال'].unique()), default="همه")
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
+ variety_counts = filtered_df['واریته'].value_counts().reset_index()
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("تعداد کل مزارع", f"{len(filtered_df)}")
584
+ col2.metric("مساحت کل (هکتار)", f"{total_area:.2f}")
585
+ col3.metric("تعداد کانال‌ها", f"{filtered_df['کانال'].nunique()}")
586
+ st_lottie(lottie_farm, height=300, key="farm_animation")
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
+ lat, lon = farm['عرض جغرافیایی'], farm['طول جغرافیایی']
594
+ name = farm['مزرعه']
595
+ farm_info = filtered_df[filtered_df['مزرعه'] == name]
596
+ if not farm_info.empty:
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
+ fig = go.Figure()
616
+ fig.add_trace(go.Scatter(x=growth_data['average']['weeks'], y=growth_data['average']['heights'], mode='lines+markers', name='میانگین رشد', line=dict(color='#1a8754', width=3)))
617
+ fig.update_layout(title='میانگین رشد هفتگی', xaxis_title='هفته', yaxis_title='ارتفاع (سانتی‌متر)', font=dict(family='Vazirmatn'))
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 Page
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": "شاخص پوشش گیاهی (NDVI)", "NDMI": "شاخص رطوبت (NDMI)", "EVI": "شاخص پیشرفته گیاهی (EVI)", "NDWI": "شاخص آب (NDWI)", "SoilMoisture": "رطوبت خاک (Soil Moisture)"}[x])
641
+ heatmap_toggle = st.checkbox("نمایش نقشه حرارتی", value=False)
642
+ generate_map = st.button("تولید نقشه", type="primary", use_container_width=True)
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, heatmap=heatmap_toggle)
651
+ if m:
652
+ st.session_state.last_map = m
653
+ folium_static(m, width=800, height=600)
654
+ st.success(f"نقشه {selected_layer} برای مزرعه {selected_farm} تولید شد.")
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
+ with col1: st.metric(f"میانگین {selected_layer}", stats["mean"])
663
+ with col2: st.metric(f"حداکثر {selected_layer}", stats["max"])
664
+ with col3: st.metric(f"حداقل {selected_layer}", stats["min"])
665
+ with col4: st.metric("انحراف معیار", stats["std_dev"])
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
+ fig = px.line(ts_data, x='Date', y=selected_layer, title=f"روند تغییرات {selected_layer}")
678
+ st.plotly_chart(fig, use_container_width=True)
679
+ st.markdown('</div>', unsafe_allow_html=True)
680
+
681
+ # Data Entry Page
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
+ with col1:
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 i in range(len(edited_df)):
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("فایل اکسل خود را آپلود کنید", type=["xlsx", "xls", "csv"])
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 Page
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
+ chart_data = []
744
+ for farm_data in growth_data['individual']:
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
+ for variety in variety_heights:
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
+ farms = farm_df['مزرعه'].unique()[:10]
761
+ dates = pd.date_range(end=datetime.now(), periods=30)
762
+ moisture_data = [{'Farm': farm, 'Date': date, 'Moisture': max(0, min(100, np.random.uniform(50, 80) + np.random.normal(0, 5)))} for farm in farms for date in dates]
763
+ moisture_df = pd.DataFrame(moisture_data)
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
+ weeks = list(range(1, 16))
777
+ heights = [50 + i * 10 + np.random.normal(0, 5) for i in weeks]
778
+ historical_df = pd.DataFrame({'Week': weeks, 'Height': heights})
779
+ rf_preds, lstm_preds = predict_growth_advanced(selected_farm, historical_df)
780
+ future_weeks = list(range(16, 23))
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
+ fig = px.scatter(clustered_df, x='مساحت', y='سن', color='Cluster', title='گروه‌بندی مزارع', labels={'مساحت': 'مساحت (هکتار)', 'سن': 'سن (سال)'})
792
+ st.plotly_chart(fig, use_container_width=True)
793
+ st.markdown('</div>', unsafe_allow_html=True)
794
+
795
+ # Reporting Page
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(2)
805
+ if report_type == "گزارش کلی":
806
+ st.markdown("### گزارش کلی وضعیت مزارع")
807
+ col1, col2 = st.columns(2)
808
+ with col1:
809
+ fig = px.pie(values=farm_df['اداره'].value_counts().values, names=farm_df['اداره'].value_counts().index, title='توزیع مزارع بر اساس اداره')
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 Page
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
+ user_name = st.text_input("نام کاربری", value="کاربر نمونه")
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>© سامانه هوشمند پایش مزارع نیشکر کشت و صنعت دهخدا | طراحی شده توسط تیم مطالعات کاربردی توسعه</p>
835
+ </footer>
836
+ """, unsafe_allow_html=True)
837
+ st.markdown('</body>', unsafe_allow_html=True)