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