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

Update app.py

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