Spaces:
Configuration error
Configuration error
Update app.py
Browse files
app.py
CHANGED
@@ -18,10 +18,12 @@ import os
|
|
18 |
import json
|
19 |
from PIL import Image
|
20 |
import time
|
|
|
|
|
21 |
|
22 |
# Set page configuration
|
23 |
st.set_page_config(
|
24 |
-
page_title="
|
25 |
page_icon="🌱",
|
26 |
layout="wide",
|
27 |
initial_sidebar_state="collapsed"
|
@@ -30,19 +32,36 @@ st.set_page_config(
|
|
30 |
# Custom CSS for styling
|
31 |
st.markdown("""
|
32 |
<style>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
.main {
|
34 |
-
background-color: #
|
35 |
color: white;
|
|
|
|
|
36 |
}
|
|
|
37 |
.stApp {
|
38 |
-
background-image: linear-gradient(to bottom, #
|
39 |
}
|
|
|
40 |
.css-18e3th9 {
|
41 |
padding-top: 0;
|
42 |
}
|
|
|
43 |
.css-1d391kg {
|
44 |
padding-top: 1rem;
|
45 |
}
|
|
|
46 |
.stButton>button {
|
47 |
background-color: #2EC4B6;
|
48 |
color: white;
|
@@ -51,83 +70,118 @@ st.markdown("""
|
|
51 |
padding: 10px 20px;
|
52 |
transition: all 0.3s;
|
53 |
}
|
|
|
54 |
.stButton>button:hover {
|
55 |
background-color: #1A9E8F;
|
56 |
transform: scale(1.05);
|
57 |
}
|
|
|
58 |
.metric-card {
|
59 |
-
background-color: rgba(
|
60 |
border-radius: 10px;
|
61 |
padding: 15px;
|
62 |
border: 1px solid #2EC4B6;
|
63 |
text-align: center;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
64 |
}
|
|
|
65 |
.metric-value {
|
66 |
-
font-size:
|
67 |
font-weight: bold;
|
68 |
color: #2EC4B6;
|
|
|
69 |
}
|
|
|
70 |
.metric-label {
|
71 |
-
font-size:
|
72 |
color: #CCC;
|
|
|
73 |
}
|
|
|
74 |
.chart-container {
|
75 |
-
background-color: rgba(
|
76 |
border-radius: 10px;
|
77 |
padding: 15px;
|
78 |
border: 1px solid #2EC4B6;
|
|
|
79 |
}
|
|
|
80 |
.table-container {
|
81 |
-
background-color: rgba(
|
82 |
border-radius: 10px;
|
83 |
padding: 15px;
|
84 |
border: 1px solid #2EC4B6;
|
|
|
85 |
}
|
|
|
86 |
.compass {
|
87 |
position: relative;
|
88 |
width: 200px;
|
89 |
height: 200px;
|
90 |
margin: 0 auto;
|
|
|
91 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
.compass-img {
|
93 |
width: 100%;
|
94 |
height: 100%;
|
95 |
}
|
|
|
96 |
.stDataFrame {
|
97 |
-
background-color: rgba(
|
98 |
}
|
|
|
99 |
div[data-testid="stDataFrameResizable"] {
|
100 |
-
background-color: rgba(
|
101 |
}
|
|
|
102 |
div[data-testid="stDataFrameResizable"] > div {
|
103 |
-
background-color: rgba(
|
104 |
}
|
|
|
105 |
div[data-testid="stDataFrameResizable"] td {
|
106 |
-
background-color: rgba(
|
107 |
color: white !important;
|
108 |
}
|
|
|
109 |
div[data-testid="stDataFrameResizable"] th {
|
110 |
background-color: rgba(46, 196, 182, 0.3) !important;
|
111 |
color: white !important;
|
112 |
}
|
|
|
113 |
.stTabs [data-baseweb="tab-list"] {
|
114 |
gap: 10px;
|
115 |
}
|
|
|
116 |
.stTabs [data-baseweb="tab"] {
|
117 |
-
background-color: rgba(
|
118 |
border-radius: 4px 4px 0px 0px;
|
119 |
padding: 10px 20px;
|
120 |
color: white;
|
121 |
}
|
|
|
122 |
.stTabs [aria-selected="true"] {
|
123 |
background-color: #2EC4B6 !important;
|
124 |
color: white !important;
|
125 |
}
|
|
|
126 |
.gauge-chart {
|
127 |
margin: 0 auto;
|
128 |
width: 200px;
|
129 |
height: 200px;
|
130 |
}
|
|
|
131 |
@keyframes pulse {
|
132 |
0% {
|
133 |
box-shadow: 0 0 0 0 rgba(46, 196, 182, 0.7);
|
@@ -139,18 +193,357 @@ st.markdown("""
|
|
139 |
box-shadow: 0 0 0 0 rgba(46, 196, 182, 0);
|
140 |
}
|
141 |
}
|
|
|
142 |
.pulsing {
|
143 |
animation: pulse 2s infinite;
|
144 |
}
|
|
|
145 |
.logo-container {
|
146 |
text-align: center;
|
147 |
margin-bottom: 20px;
|
148 |
}
|
|
|
149 |
.logo-text {
|
150 |
font-size: 24px;
|
151 |
font-weight: bold;
|
152 |
color: #2EC4B6;
|
153 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
154 |
</style>
|
155 |
""", unsafe_allow_html=True)
|
156 |
|
@@ -168,7 +561,7 @@ def initialize_ee():
|
|
168 |
ee.Initialize(credentials)
|
169 |
return True
|
170 |
else:
|
171 |
-
st.error("GEE
|
172 |
return False
|
173 |
|
174 |
# Load farm coordinates
|
@@ -176,16 +569,18 @@ def initialize_ee():
|
|
176 |
def load_farm_data():
|
177 |
try:
|
178 |
farm_data = pd.read_csv('farm_coordinates.csv')
|
|
|
|
|
179 |
return farm_data
|
180 |
except Exception as e:
|
181 |
-
st.error(f"
|
182 |
# Create sample data if file not found
|
183 |
sample_data = pd.DataFrame({
|
184 |
-
'
|
185 |
-
'
|
186 |
-
'
|
187 |
-
'
|
188 |
-
'
|
189 |
})
|
190 |
return sample_data
|
191 |
|
@@ -194,14 +589,17 @@ def load_farm_data():
|
|
194 |
def load_day_schedule():
|
195 |
try:
|
196 |
schedule_data = pd.read_csv('پایگاه داده (1).csv')
|
|
|
|
|
|
|
197 |
return schedule_data
|
198 |
except Exception as e:
|
199 |
-
st.error(f"
|
200 |
# Create sample data if file not found
|
201 |
-
days = ['
|
202 |
sample_data = pd.DataFrame({
|
203 |
-
'
|
204 |
-
'
|
205 |
})
|
206 |
return sample_data
|
207 |
|
@@ -226,34 +624,32 @@ def calculate_indices(image):
|
|
226 |
# NDRE = (NIR - Red Edge) / (NIR + Red Edge)
|
227 |
ndre = image.normalizedDifference(['B8', 'B5']).rename('NDRE')
|
228 |
|
229 |
-
# LAI calculation (
|
230 |
-
# LAI =
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
'2.5 * ((NIR - RED) / (NIR + 6 * RED - 7.5 * BLUE + 1))',
|
236 |
-
{'NIR': nir, 'RED': red, 'BLUE': blue}
|
237 |
-
).rename('EVI')
|
238 |
-
lai = evi.multiply(3.618).subtract(0.118).rename('LAI')
|
239 |
|
240 |
# Chlorophyll index
|
|
|
|
|
241 |
chlorophyll = image.expression(
|
242 |
'(NIR / RED) - 1',
|
243 |
{'NIR': nir, 'RED': red}
|
244 |
).rename('CHL')
|
245 |
|
246 |
# Add all indices to the image
|
247 |
-
return image.addBands([ndvi, ndre, lai,
|
248 |
|
249 |
# Function to create LSTM model
|
250 |
def create_lstm_model(input_shape):
|
251 |
model = Sequential()
|
252 |
-
model.add(LSTM(
|
253 |
model.add(Dropout(0.2))
|
254 |
-
model.add(LSTM(
|
255 |
model.add(Dropout(0.2))
|
256 |
-
model.add(Dense(
|
257 |
model.add(Dense(3)) # Predict LAI, NDVI, NDRE
|
258 |
|
259 |
model.compile(optimizer='adam', loss='mse')
|
@@ -323,7 +719,7 @@ def create_gauge_chart(value, title, min_val=0, max_val=1):
|
|
323 |
gauge={
|
324 |
'axis': {'range': [min_val, max_val], 'tickcolor': 'white'},
|
325 |
'bar': {'color': "#2EC4B6"},
|
326 |
-
'bgcolor': "rgba(
|
327 |
'borderwidth': 2,
|
328 |
'bordercolor': "#2EC4B6",
|
329 |
'steps': [
|
@@ -348,20 +744,22 @@ def create_gauge_chart(value, title, min_val=0, max_val=1):
|
|
348 |
def create_line_chart(data, x_col, y_cols, title):
|
349 |
fig = go.Figure()
|
350 |
|
351 |
-
|
|
|
|
|
352 |
fig.add_trace(go.Scatter(
|
353 |
x=data[x_col],
|
354 |
y=data[col],
|
355 |
mode='lines+markers',
|
356 |
name=col,
|
357 |
-
line=dict(width=3),
|
358 |
-
marker=dict(size=8)
|
359 |
))
|
360 |
|
361 |
fig.update_layout(
|
362 |
title=title,
|
363 |
-
xaxis_title='
|
364 |
-
yaxis_title='
|
365 |
legend=dict(
|
366 |
orientation="h",
|
367 |
yanchor="bottom",
|
@@ -370,7 +768,7 @@ def create_line_chart(data, x_col, y_cols, title):
|
|
370 |
x=1
|
371 |
),
|
372 |
template="plotly_dark",
|
373 |
-
plot_bgcolor='rgba(
|
374 |
paper_bgcolor='rgba(0,0,0,0)',
|
375 |
font=dict(color='white'),
|
376 |
margin=dict(l=20, r=20, t=50, b=20),
|
@@ -386,7 +784,10 @@ def create_bar_chart(data, x_col, y_col, title):
|
|
386 |
fig.add_trace(go.Bar(
|
387 |
x=data[x_col],
|
388 |
y=data[y_col],
|
389 |
-
marker_color='#2EC4B6'
|
|
|
|
|
|
|
390 |
))
|
391 |
|
392 |
fig.update_layout(
|
@@ -394,7 +795,7 @@ def create_bar_chart(data, x_col, y_col, title):
|
|
394 |
xaxis_title=x_col,
|
395 |
yaxis_title=y_col,
|
396 |
template="plotly_dark",
|
397 |
-
plot_bgcolor='rgba(
|
398 |
paper_bgcolor='rgba(0,0,0,0)',
|
399 |
font=dict(color='white'),
|
400 |
margin=dict(l=20, r=20, t=50, b=20),
|
@@ -421,7 +822,7 @@ def create_area_chart(data, x_col, y_col, title):
|
|
421 |
xaxis_title=x_col,
|
422 |
yaxis_title=y_col,
|
423 |
template="plotly_dark",
|
424 |
-
plot_bgcolor='rgba(
|
425 |
paper_bgcolor='rgba(0,0,0,0)',
|
426 |
font=dict(color='white'),
|
427 |
margin=dict(l=20, r=20, t=50, b=20),
|
@@ -430,64 +831,118 @@ def create_area_chart(data, x_col, y_col, title):
|
|
430 |
|
431 |
return fig
|
432 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
433 |
# Main application
|
434 |
def main():
|
435 |
# Initialize Earth Engine
|
436 |
ee_initialized = initialize_ee()
|
437 |
if not ee_initialized:
|
438 |
-
st.warning("Earth Engine
|
439 |
|
440 |
# Load data
|
441 |
farm_data = load_farm_data()
|
442 |
day_schedule = load_day_schedule()
|
443 |
|
444 |
# Header with logo
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
|
|
|
|
|
|
|
449 |
<div style="font-size: 40px; color: #2EC4B6;">✦</div>
|
450 |
-
<div
|
451 |
-
<div style="color: #CCC; font-size:
|
452 |
</div>
|
453 |
-
|
454 |
-
|
455 |
-
with col2:
|
456 |
-
st.markdown("""
|
457 |
-
<h1 style="text-align: center; color: #2EC4B6; margin-bottom: 20px;">
|
458 |
-
<span style="margin-right: 10px;">��</span> Smart Sugarcane Farm Monitoring <span style="margin-left: 10px;">🌱</span>
|
459 |
-
</h1>
|
460 |
-
""", unsafe_allow_html=True)
|
461 |
|
462 |
# Date selection
|
463 |
today = datetime.now()
|
464 |
start_date = today - timedelta(days=30)
|
465 |
end_date = today
|
466 |
|
467 |
-
|
|
|
|
|
468 |
with col1:
|
469 |
-
start_date = st.date_input("
|
470 |
with col2:
|
471 |
-
|
|
|
|
|
|
|
|
|
472 |
|
473 |
-
|
474 |
-
days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
|
475 |
-
selected_day = st.selectbox("Select Day", days)
|
476 |
|
477 |
# Filter farms for selected day
|
478 |
-
day_farms = day_schedule[day_schedule['
|
479 |
-
selected_farms = farm_data[farm_data['
|
480 |
|
481 |
if selected_farms.empty:
|
482 |
-
st.warning(f"
|
483 |
else:
|
484 |
# Create tabs for different sections
|
485 |
-
tabs = st.tabs(["
|
486 |
|
487 |
# Dashboard Tab
|
488 |
with tabs[0]:
|
|
|
|
|
489 |
# Main metrics row
|
490 |
-
st.markdown("
|
|
|
|
|
|
|
491 |
metric_cols = st.columns(5)
|
492 |
|
493 |
# Generate sample metrics for demonstration
|
@@ -501,7 +956,7 @@ def main():
|
|
501 |
st.markdown(f"""
|
502 |
<div class="metric-card">
|
503 |
<div class="metric-value">{avg_ndvi}</div>
|
504 |
-
<div class="metric-label"
|
505 |
</div>
|
506 |
""", unsafe_allow_html=True)
|
507 |
|
@@ -509,7 +964,7 @@ def main():
|
|
509 |
st.markdown(f"""
|
510 |
<div class="metric-card">
|
511 |
<div class="metric-value">{avg_lai}</div>
|
512 |
-
<div class="metric-label"
|
513 |
</div>
|
514 |
""", unsafe_allow_html=True)
|
515 |
|
@@ -517,7 +972,7 @@ def main():
|
|
517 |
st.markdown(f"""
|
518 |
<div class="metric-card">
|
519 |
<div class="metric-value">{avg_ndre}</div>
|
520 |
-
<div class="metric-label"
|
521 |
</div>
|
522 |
""", unsafe_allow_html=True)
|
523 |
|
@@ -525,7 +980,7 @@ def main():
|
|
525 |
st.markdown(f"""
|
526 |
<div class="metric-card">
|
527 |
<div class="metric-value">{avg_chl}</div>
|
528 |
-
<div class="metric-label"
|
529 |
</div>
|
530 |
""", unsafe_allow_html=True)
|
531 |
|
@@ -533,19 +988,25 @@ def main():
|
|
533 |
st.markdown(f"""
|
534 |
<div class="metric-card pulsing">
|
535 |
<div class="metric-value">{avg_growth}%</div>
|
536 |
-
<div class="metric-label"
|
537 |
</div>
|
538 |
""", unsafe_allow_html=True)
|
539 |
|
|
|
|
|
540 |
# Map and compass row
|
|
|
|
|
541 |
map_col, compass_col = st.columns([3, 1])
|
542 |
|
543 |
with map_col:
|
544 |
-
st.markdown("
|
|
|
|
|
545 |
|
546 |
# Create a map centered at the mean of farm coordinates
|
547 |
-
center_lat = selected_farms['
|
548 |
-
center_lon = selected_farms['
|
549 |
|
550 |
m = geemap.Map(center=[center_lat, center_lon], zoom=12)
|
551 |
|
@@ -554,7 +1015,7 @@ def main():
|
|
554 |
|
555 |
# Create features for farms
|
556 |
for _, farm in selected_farms.iterrows():
|
557 |
-
point = ee.Geometry.Point([farm['
|
558 |
buffer = point.buffer(500) # 500m buffer around point
|
559 |
|
560 |
# Get Sentinel imagery
|
@@ -575,16 +1036,16 @@ def main():
|
|
575 |
'max': 1,
|
576 |
'palette': ['#d73027', '#f46d43', '#fdae61', '#fee08b', '#d9ef8b', '#a6d96a', '#66bd63', '#1a9850']
|
577 |
}
|
578 |
-
m.addLayer(image_with_indices.select('NDVI'), ndvi_vis, f'NDVI - {farm["
|
579 |
|
580 |
# Add farm marker with pulsing effect
|
581 |
folium.Marker(
|
582 |
-
location=[farm['
|
583 |
popup=f"""
|
584 |
-
<div style='width: 200px'>
|
585 |
-
<h4>{farm['
|
586 |
-
<p
|
587 |
-
<p
|
588 |
</div>
|
589 |
""",
|
590 |
icon=folium.Icon(color='green', icon='leaf')
|
@@ -594,20 +1055,30 @@ def main():
|
|
594 |
folium_static(m)
|
595 |
|
596 |
with compass_col:
|
597 |
-
st.markdown("
|
598 |
-
st.markdown(""
|
599 |
-
|
600 |
-
|
601 |
-
|
602 |
-
|
603 |
|
604 |
# Add a gauge chart for overall farm health
|
605 |
-
st.markdown("
|
606 |
-
|
|
|
|
|
|
|
607 |
st.plotly_chart(gauge_fig, use_container_width=True)
|
608 |
|
|
|
|
|
609 |
# Charts row
|
610 |
-
st.markdown("
|
|
|
|
|
|
|
|
|
|
|
611 |
chart_col1, chart_col2 = st.columns(2)
|
612 |
|
613 |
with chart_col1:
|
@@ -622,43 +1093,58 @@ def main():
|
|
622 |
ndvi_values = np.clip(ndvi_values, 0, 1)
|
623 |
|
624 |
ts_data = pd.DataFrame({
|
625 |
-
'
|
626 |
'NDVI': ndvi_values,
|
627 |
'NDRE': ndvi_values * 0.7 + np.random.normal(0, 0.05, size=len(dates)),
|
628 |
'LAI': ndvi_values * 5 + np.random.normal(0, 0.3, size=len(dates))
|
629 |
})
|
630 |
|
631 |
# Create and display line chart
|
632 |
-
line_fig = create_line_chart(ts_data, '
|
633 |
st.plotly_chart(line_fig, use_container_width=True)
|
634 |
|
635 |
with chart_col2:
|
636 |
# Create sample area chart data
|
637 |
area_data = pd.DataFrame({
|
638 |
-
'
|
639 |
-
'
|
640 |
})
|
641 |
|
642 |
# Create and display area chart
|
643 |
-
area_fig = create_area_chart(area_data, '
|
644 |
st.plotly_chart(area_fig, use_container_width=True)
|
645 |
|
|
|
|
|
646 |
# Farm ranking row
|
647 |
-
st.markdown("
|
|
|
|
|
|
|
|
|
|
|
648 |
rank_col1, rank_col2 = st.columns(2)
|
649 |
|
650 |
with rank_col1:
|
651 |
-
# Create sample farm ranking data
|
652 |
-
farm_names = selected_farms['
|
653 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
654 |
|
655 |
rank_data = pd.DataFrame({
|
656 |
-
'
|
657 |
-
'
|
658 |
-
}).sort_values('
|
659 |
|
660 |
# Create and display bar chart
|
661 |
-
bar_fig = create_bar_chart(rank_data, '
|
662 |
st.plotly_chart(bar_fig, use_container_width=True)
|
663 |
|
664 |
with rank_col2:
|
@@ -666,78 +1152,139 @@ def main():
|
|
666 |
st.markdown('<div class="table-container">', unsafe_allow_html=True)
|
667 |
st.dataframe(
|
668 |
rank_data.reset_index(drop=True).style.background_gradient(
|
669 |
-
cmap='Greens', subset=['
|
670 |
),
|
671 |
use_container_width=True
|
672 |
)
|
673 |
st.markdown('</div>', unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
674 |
|
675 |
# Farm Analysis Tab
|
676 |
with tabs[1]:
|
677 |
-
st.markdown("
|
|
|
|
|
|
|
|
|
678 |
|
679 |
# Farm selection
|
680 |
-
selected_farm = st.selectbox("
|
681 |
-
farm_row = selected_farms[selected_farms['
|
682 |
|
683 |
# Farm details
|
684 |
st.markdown(f"""
|
685 |
<div class="metric-card" style="margin-bottom: 20px;">
|
686 |
<h3>{selected_farm}</h3>
|
687 |
-
<p
|
688 |
-
<p
|
689 |
</div>
|
690 |
""", unsafe_allow_html=True)
|
691 |
|
|
|
|
|
692 |
# Create tabs for different analysis views
|
693 |
-
analysis_tabs = st.tabs(["
|
694 |
|
695 |
# Vegetation Indices tab
|
696 |
with analysis_tabs[0]:
|
|
|
|
|
697 |
# Generate sample time series data for the selected farm
|
698 |
dates = pd.date_range(start=start_date, end=end_date, freq='D')
|
699 |
|
700 |
# Base values with some randomness
|
701 |
base_ndvi = np.random.uniform(0.6, 0.8)
|
702 |
base_ndre = np.random.uniform(0.4, 0.6)
|
703 |
-
base_lai = np.random.uniform(3.0, 4.5)
|
704 |
|
705 |
# Add daily variations
|
706 |
ndvi_values = base_ndvi + np.random.normal(0, 0.05, size=len(dates))
|
707 |
ndre_values = base_ndre + np.random.normal(0, 0.04, size=len(dates))
|
708 |
-
lai_values = base_lai + np.random.normal(0, 0.3, size=len(dates))
|
709 |
|
710 |
# Add trend
|
711 |
trend = np.linspace(0, 0.15, len(dates))
|
712 |
ndvi_values += trend
|
713 |
ndre_values += trend * 0.8
|
714 |
-
lai_values += trend * 2
|
715 |
|
716 |
# Clip to valid ranges
|
717 |
ndvi_values = np.clip(ndvi_values, 0, 1)
|
718 |
ndre_values = np.clip(ndre_values, 0, 1)
|
719 |
-
|
|
|
|
|
720 |
|
721 |
# Create dataframe
|
722 |
farm_ts_data = pd.DataFrame({
|
723 |
-
'
|
724 |
'NDVI': ndvi_values,
|
725 |
'NDRE': ndre_values,
|
726 |
'LAI': lai_values
|
727 |
})
|
728 |
|
729 |
# Display charts
|
730 |
-
st.markdown("
|
|
|
|
|
731 |
|
732 |
# Create line chart for all indices
|
733 |
vi_fig = create_line_chart(
|
734 |
-
farm_ts_data, '
|
735 |
-
f'
|
736 |
)
|
737 |
st.plotly_chart(vi_fig, use_container_width=True)
|
738 |
|
739 |
# Display individual metrics with small charts
|
740 |
-
st.markdown("
|
|
|
|
|
|
|
741 |
vi_cols = st.columns(3)
|
742 |
|
743 |
with vi_cols[0]:
|
@@ -752,11 +1299,15 @@ def main():
|
|
752 |
|
753 |
with vi_cols[2]:
|
754 |
current_lai = lai_values[-1]
|
755 |
-
lai_gauge = create_gauge_chart(current_lai, "LAI", 0,
|
756 |
st.plotly_chart(lai_gauge, use_container_width=True)
|
|
|
|
|
757 |
|
758 |
# Growth Trends tab
|
759 |
with analysis_tabs[1]:
|
|
|
|
|
760 |
# Generate sample growth data
|
761 |
growth_dates = pd.date_range(start=start_date, end=end_date, freq='D')
|
762 |
|
@@ -773,32 +1324,37 @@ def main():
|
|
773 |
|
774 |
# Create dataframe
|
775 |
growth_data = pd.DataFrame({
|
776 |
-
'
|
777 |
-
'
|
778 |
-
'
|
779 |
})
|
780 |
|
781 |
# Display charts
|
782 |
-
st.markdown("
|
|
|
|
|
783 |
|
784 |
-
growth_tabs = st.tabs(["
|
785 |
|
786 |
with growth_tabs[0]:
|
787 |
daily_fig = create_bar_chart(
|
788 |
-
growth_data, '
|
789 |
-
f'
|
790 |
)
|
791 |
st.plotly_chart(daily_fig, use_container_width=True)
|
792 |
|
793 |
with growth_tabs[1]:
|
794 |
cumulative_fig = create_area_chart(
|
795 |
-
growth_data, '
|
796 |
-
f'
|
797 |
)
|
798 |
st.plotly_chart(cumulative_fig, use_container_width=True)
|
799 |
|
800 |
# Growth statistics
|
801 |
-
st.markdown("
|
|
|
|
|
|
|
802 |
stats_cols = st.columns(3)
|
803 |
|
804 |
with stats_cols[0]:
|
@@ -806,7 +1362,7 @@ def main():
|
|
806 |
st.markdown(f"""
|
807 |
<div class="metric-card">
|
808 |
<div class="metric-value">{avg_growth:.3f}</div>
|
809 |
-
<div class="metric-label"
|
810 |
</div>
|
811 |
""", unsafe_allow_html=True)
|
812 |
|
@@ -816,7 +1372,7 @@ def main():
|
|
816 |
st.markdown(f"""
|
817 |
<div class="metric-card">
|
818 |
<div class="metric-value">{max_growth:.3f}</div>
|
819 |
-
<div class="metric-label"
|
820 |
</div>
|
821 |
""", unsafe_allow_html=True)
|
822 |
|
@@ -825,12 +1381,16 @@ def main():
|
|
825 |
st.markdown(f"""
|
826 |
<div class="metric-card">
|
827 |
<div class="metric-value">{total_growth:.2f}</div>
|
828 |
-
<div class="metric-label"
|
829 |
</div>
|
830 |
""", unsafe_allow_html=True)
|
|
|
|
|
831 |
|
832 |
# Environmental Factors tab
|
833 |
with analysis_tabs[2]:
|
|
|
|
|
834 |
# Generate sample environmental data
|
835 |
env_dates = pd.date_range(start=start_date, end=end_date, freq='D')
|
836 |
|
@@ -850,52 +1410,56 @@ def main():
|
|
850 |
|
851 |
# Create dataframe
|
852 |
env_data = pd.DataFrame({
|
853 |
-
'
|
854 |
-
'
|
855 |
-
'
|
856 |
-
'
|
857 |
})
|
858 |
|
859 |
# Display charts
|
860 |
-
st.markdown("
|
|
|
|
|
861 |
|
862 |
-
env_tabs = st.tabs(["
|
863 |
|
864 |
with env_tabs[0]:
|
865 |
temp_fig = create_line_chart(
|
866 |
-
env_data, '
|
867 |
-
f'
|
868 |
)
|
869 |
st.plotly_chart(temp_fig, use_container_width=True)
|
870 |
|
871 |
with env_tabs[1]:
|
872 |
humidity_fig = create_line_chart(
|
873 |
-
env_data, '
|
874 |
-
f'
|
875 |
)
|
876 |
st.plotly_chart(humidity_fig, use_container_width=True)
|
877 |
|
878 |
with env_tabs[2]:
|
879 |
rainfall_fig = create_bar_chart(
|
880 |
-
env_data, '
|
881 |
-
f'
|
882 |
)
|
883 |
st.plotly_chart(rainfall_fig, use_container_width=True)
|
884 |
|
885 |
# Correlation analysis
|
886 |
-
st.markdown("
|
|
|
|
|
887 |
|
888 |
# Merge environmental and growth data
|
889 |
merged_data = pd.merge(
|
890 |
env_data,
|
891 |
-
growth_data[['
|
892 |
-
on='
|
893 |
)
|
894 |
|
895 |
# Calculate correlations
|
896 |
-
corr_temp = np.corrcoef(merged_data['
|
897 |
-
corr_humidity = np.corrcoef(merged_data['
|
898 |
-
corr_rainfall = np.corrcoef(merged_data['
|
899 |
|
900 |
# Display correlation metrics
|
901 |
corr_cols = st.columns(3)
|
@@ -904,7 +1468,7 @@ def main():
|
|
904 |
st.markdown(f"""
|
905 |
<div class="metric-card">
|
906 |
<div class="metric-value">{corr_temp:.2f}</div>
|
907 |
-
<div class="metric-label"
|
908 |
</div>
|
909 |
""", unsafe_allow_html=True)
|
910 |
|
@@ -912,7 +1476,7 @@ def main():
|
|
912 |
st.markdown(f"""
|
913 |
<div class="metric-card">
|
914 |
<div class="metric-value">{corr_humidity:.2f}</div>
|
915 |
-
<div class="metric-label"
|
916 |
</div>
|
917 |
""", unsafe_allow_html=True)
|
918 |
|
@@ -920,16 +1484,24 @@ def main():
|
|
920 |
st.markdown(f"""
|
921 |
<div class="metric-card">
|
922 |
<div class="metric-value">{corr_rainfall:.2f}</div>
|
923 |
-
<div class="metric-label"
|
924 |
</div>
|
925 |
""", unsafe_allow_html=True)
|
|
|
|
|
926 |
|
927 |
# Growth Prediction Tab
|
928 |
with tabs[2]:
|
929 |
-
st.markdown("
|
|
|
|
|
|
|
|
|
930 |
|
931 |
# Farm selection for prediction
|
932 |
-
pred_farm = st.selectbox("
|
|
|
|
|
933 |
|
934 |
# Generate sample historical data for LSTM
|
935 |
hist_dates = pd.date_range(start=start_date - timedelta(days=60), end=end_date, freq='D')
|
@@ -956,57 +1528,67 @@ def main():
|
|
956 |
rainfall[rain_days] = np.random.exponential(5, size=len(rain_days))
|
957 |
|
958 |
# Age (constant for a farm)
|
959 |
-
farm_age = selected_farms[selected_farms['
|
960 |
age = np.ones(len(hist_dates)) * farm_age
|
961 |
|
962 |
# Variety (encoded as 0, 1, 2)
|
963 |
-
variety_map = {'
|
964 |
-
farm_variety = selected_farms[selected_farms['
|
965 |
variety_encoded = np.ones(len(hist_dates)) * variety_map.get(farm_variety, 0)
|
966 |
|
967 |
# Target variables with relationship to features
|
968 |
-
#
|
969 |
-
|
970 |
-
|
971 |
-
lai = np.clip(lai, 0, 7)
|
972 |
-
|
973 |
-
# NDVI follows similar pattern but scaled
|
974 |
-
ndvi = 0.5 + 0.07 * lai / 7 + np.random.normal(0, 0.03, len(hist_dates))
|
975 |
ndvi = np.clip(ndvi, 0, 1)
|
976 |
|
977 |
# NDRE correlated with NDVI but lower values
|
978 |
ndre = 0.7 * ndvi + np.random.normal(0, 0.02, len(hist_dates))
|
979 |
ndre = np.clip(ndre, 0, 1)
|
980 |
|
|
|
|
|
|
|
981 |
# Create dataframe
|
982 |
hist_data = pd.DataFrame({
|
983 |
-
'
|
984 |
-
'
|
985 |
-
'
|
986 |
-
'
|
987 |
-
'
|
988 |
-
'
|
989 |
'LAI': lai,
|
990 |
'NDVI': ndvi,
|
991 |
'NDRE': ndre
|
992 |
})
|
993 |
|
994 |
# Display historical data
|
995 |
-
st.markdown("
|
|
|
|
|
|
|
|
|
|
|
996 |
hist_fig = create_line_chart(
|
997 |
-
hist_data, '
|
998 |
-
f'
|
999 |
)
|
1000 |
st.plotly_chart(hist_fig, use_container_width=True)
|
1001 |
|
|
|
|
|
1002 |
# LSTM Model Training
|
1003 |
-
st.markdown("
|
|
|
|
|
|
|
|
|
1004 |
|
1005 |
# Button to trigger training
|
1006 |
-
if st.button("
|
1007 |
-
with st.spinner("
|
1008 |
# Prepare data for LSTM
|
1009 |
-
features = hist_data[['
|
1010 |
targets = hist_data[['LAI', 'NDVI', 'NDRE']].values
|
1011 |
|
1012 |
# Combine features and targets
|
@@ -1026,7 +1608,7 @@ def main():
|
|
1026 |
|
1027 |
# Create prediction dataframe
|
1028 |
pred_data = pd.DataFrame({
|
1029 |
-
'
|
1030 |
'LAI': predictions[:, 0],
|
1031 |
'NDVI': predictions[:, 1],
|
1032 |
'NDRE': predictions[:, 2]
|
@@ -1034,35 +1616,37 @@ def main():
|
|
1034 |
|
1035 |
# Combine historical and prediction data
|
1036 |
combined_data = pd.concat([
|
1037 |
-
hist_data[['
|
1038 |
pred_data
|
1039 |
])
|
1040 |
|
1041 |
# Display prediction results
|
1042 |
-
st.markdown("
|
|
|
|
|
1043 |
|
1044 |
# Create line chart with historical and predicted values
|
1045 |
pred_fig = go.Figure()
|
1046 |
|
1047 |
# Historical data (solid lines)
|
1048 |
-
for col, color in zip(['LAI', 'NDVI', 'NDRE'], ['#
|
1049 |
-
hist_part = combined_data[combined_data['
|
1050 |
pred_fig.add_trace(go.Scatter(
|
1051 |
-
x=hist_part['
|
1052 |
y=hist_part[col],
|
1053 |
mode='lines',
|
1054 |
-
name=f'
|
1055 |
line=dict(color=color)
|
1056 |
))
|
1057 |
|
1058 |
# Predicted data (dashed lines)
|
1059 |
-
for col, color in zip(['LAI', 'NDVI', 'NDRE'], ['#
|
1060 |
-
pred_part = combined_data[combined_data['
|
1061 |
pred_fig.add_trace(go.Scatter(
|
1062 |
-
x=pred_part['
|
1063 |
y=pred_part[col],
|
1064 |
mode='lines+markers',
|
1065 |
-
name=f'
|
1066 |
line=dict(color=color, dash='dash'),
|
1067 |
marker=dict(size=8)
|
1068 |
))
|
@@ -1073,14 +1657,14 @@ def main():
|
|
1073 |
line_width=2,
|
1074 |
line_dash="dash",
|
1075 |
line_color="red",
|
1076 |
-
annotation_text="
|
1077 |
annotation_position="top right"
|
1078 |
)
|
1079 |
|
1080 |
pred_fig.update_layout(
|
1081 |
-
title=f'7
|
1082 |
-
xaxis_title='
|
1083 |
-
yaxis_title='
|
1084 |
legend=dict(
|
1085 |
orientation="h",
|
1086 |
yanchor="bottom",
|
@@ -1089,7 +1673,7 @@ def main():
|
|
1089 |
x=1
|
1090 |
),
|
1091 |
template="plotly_dark",
|
1092 |
-
plot_bgcolor='rgba(
|
1093 |
paper_bgcolor='rgba(0,0,0,0)',
|
1094 |
font=dict(color='white'),
|
1095 |
height=500
|
@@ -1098,7 +1682,10 @@ def main():
|
|
1098 |
st.plotly_chart(pred_fig, use_container_width=True)
|
1099 |
|
1100 |
# Display prediction table
|
1101 |
-
st.markdown("
|
|
|
|
|
|
|
1102 |
st.dataframe(
|
1103 |
pred_data.style.background_gradient(
|
1104 |
cmap='Greens', subset=['LAI', 'NDVI', 'NDRE']
|
@@ -1107,7 +1694,9 @@ def main():
|
|
1107 |
)
|
1108 |
|
1109 |
# Growth summary
|
1110 |
-
st.markdown("
|
|
|
|
|
1111 |
|
1112 |
# Calculate growth rates
|
1113 |
lai_growth = (pred_data['LAI'].iloc[-1] - hist_data['LAI'].iloc[-1]) / hist_data['LAI'].iloc[-1] * 100
|
@@ -1120,7 +1709,7 @@ def main():
|
|
1120 |
st.markdown(f"""
|
1121 |
<div class="metric-card">
|
1122 |
<div class="metric-value">{lai_growth:.1f}%</div>
|
1123 |
-
<div class="metric-label"
|
1124 |
</div>
|
1125 |
""", unsafe_allow_html=True)
|
1126 |
|
@@ -1128,7 +1717,7 @@ def main():
|
|
1128 |
st.markdown(f"""
|
1129 |
<div class="metric-card">
|
1130 |
<div class="metric-value">{ndvi_growth:.1f}%</div>
|
1131 |
-
<div class="metric-label"
|
1132 |
</div>
|
1133 |
""", unsafe_allow_html=True)
|
1134 |
|
@@ -1136,22 +1725,31 @@ def main():
|
|
1136 |
st.markdown(f"""
|
1137 |
<div class="metric-card">
|
1138 |
<div class="metric-value">{ndre_growth:.1f}%</div>
|
1139 |
-
<div class="metric-label"
|
1140 |
</div>
|
1141 |
""", unsafe_allow_html=True)
|
1142 |
else:
|
1143 |
-
st.info("
|
|
|
|
|
1144 |
|
1145 |
# Data Tables Tab
|
1146 |
with tabs[3]:
|
1147 |
-
st.markdown("
|
|
|
|
|
|
|
|
|
1148 |
|
1149 |
# Create tabs for different data tables
|
1150 |
-
data_tabs = st.tabs(["
|
1151 |
|
1152 |
# Farm Information tab
|
1153 |
with data_tabs[0]:
|
1154 |
-
st.markdown("
|
|
|
|
|
|
|
1155 |
st.dataframe(
|
1156 |
selected_farms,
|
1157 |
use_container_width=True
|
@@ -1159,26 +1757,30 @@ def main():
|
|
1159 |
|
1160 |
# Vegetation Indices tab
|
1161 |
with data_tabs[1]:
|
1162 |
-
st.markdown("
|
|
|
|
|
1163 |
|
1164 |
# Generate sample vegetation indices for all farms
|
1165 |
vi_data = []
|
1166 |
|
1167 |
for _, farm in selected_farms.iterrows():
|
1168 |
# Generate random indices with some correlation to farm age
|
1169 |
-
farm_age = farm['
|
1170 |
base_ndvi = min(0.5 + 0.08 * farm_age + np.random.normal(0, 0.05), 0.95)
|
1171 |
base_ndre = base_ndvi * 0.7 + np.random.normal(0, 0.03)
|
1172 |
-
|
|
|
|
|
1173 |
|
1174 |
vi_data.append({
|
1175 |
-
'
|
1176 |
-
'
|
1177 |
-
'
|
1178 |
'NDVI': round(base_ndvi, 2),
|
1179 |
'NDRE': round(base_ndre, 2),
|
1180 |
'LAI': round(base_lai, 1),
|
1181 |
-
'
|
1182 |
})
|
1183 |
|
1184 |
vi_df = pd.DataFrame(vi_data)
|
@@ -1186,16 +1788,18 @@ def main():
|
|
1186 |
# Display the data
|
1187 |
st.dataframe(
|
1188 |
vi_df.style.background_gradient(
|
1189 |
-
cmap='Greens', subset=['NDVI', 'NDRE', 'LAI', '
|
1190 |
),
|
1191 |
use_container_width=True
|
1192 |
)
|
1193 |
|
1194 |
# Growth Rankings tab
|
1195 |
with data_tabs[2]:
|
1196 |
-
st.markdown("
|
|
|
|
|
1197 |
|
1198 |
-
# Generate sample growth data for all farms
|
1199 |
growth_data = []
|
1200 |
|
1201 |
for _, farm in selected_farms.iterrows():
|
@@ -1203,25 +1807,25 @@ def main():
|
|
1203 |
base_growth = np.random.uniform(2.0, 5.0)
|
1204 |
|
1205 |
# Adjust based on farm age (younger farms grow faster)
|
1206 |
-
age_factor = max(1.0, 6 - farm['
|
1207 |
|
1208 |
growth_data.append({
|
1209 |
-
'
|
1210 |
-
'
|
1211 |
-
'
|
1212 |
-
'
|
1213 |
-
'
|
1214 |
-
'
|
1215 |
})
|
1216 |
|
1217 |
-
growth_df = pd.DataFrame(growth_data).sort_values('
|
1218 |
|
1219 |
# Display the data
|
1220 |
st.dataframe(
|
1221 |
growth_df.style.background_gradient(
|
1222 |
-
cmap='Greens', subset=['
|
1223 |
).background_gradient(
|
1224 |
-
cmap='Reds_r', subset=['
|
1225 |
),
|
1226 |
use_container_width=True
|
1227 |
)
|
@@ -1230,34 +1834,41 @@ def main():
|
|
1230 |
col1, col2 = st.columns(2)
|
1231 |
|
1232 |
with col1:
|
1233 |
-
st.markdown("
|
|
|
|
|
|
|
1234 |
top_farms = growth_df.head(3)
|
1235 |
|
1236 |
for i, (_, farm) in enumerate(top_farms.iterrows()):
|
1237 |
st.markdown(f"""
|
1238 |
<div class="metric-card" style="margin-bottom: 10px;">
|
1239 |
-
<h4>#{i+1}: {farm['
|
1240 |
-
<div class="metric-value">{farm['
|
1241 |
-
<div class="metric-label"
|
1242 |
-
<p
|
1243 |
</div>
|
1244 |
""", unsafe_allow_html=True)
|
1245 |
|
1246 |
with col2:
|
1247 |
-
st.markdown("
|
|
|
|
|
|
|
1248 |
bottom_farms = growth_df.tail(3).iloc[::-1]
|
1249 |
|
1250 |
for i, (_, farm) in enumerate(bottom_farms.iterrows()):
|
1251 |
st.markdown(f"""
|
1252 |
<div class="metric-card" style="margin-bottom: 10px; border-color: #ff6b6b;">
|
1253 |
-
<h4>#{i+1}: {farm['
|
1254 |
-
<div class="metric-value" style="color: #ff6b6b;">{farm['
|
1255 |
-
<div class="metric-label"
|
1256 |
-
<p
|
1257 |
</div>
|
1258 |
""", unsafe_allow_html=True)
|
|
|
|
|
1259 |
|
1260 |
# Run the app
|
1261 |
if __name__ == "__main__":
|
1262 |
-
main()
|
1263 |
-
|
|
|
18 |
import json
|
19 |
from PIL import Image
|
20 |
import time
|
21 |
+
import base64
|
22 |
+
from io import BytesIO
|
23 |
|
24 |
# Set page configuration
|
25 |
st.set_page_config(
|
26 |
+
page_title="سامانه هوشمند مانیتورینگ مزارع نیشکر",
|
27 |
page_icon="🌱",
|
28 |
layout="wide",
|
29 |
initial_sidebar_state="collapsed"
|
|
|
32 |
# Custom CSS for styling
|
33 |
st.markdown("""
|
34 |
<style>
|
35 |
+
@font-face {
|
36 |
+
font-family: 'Vazir';
|
37 |
+
src: url('https://cdn.jsdelivr.net/gh/rastikerdar/[email protected]/dist/Vazir.woff2') format('woff2');
|
38 |
+
font-weight: normal;
|
39 |
+
font-style: normal;
|
40 |
+
}
|
41 |
+
|
42 |
+
* {
|
43 |
+
font-family: 'Vazir', sans-serif !important;
|
44 |
+
}
|
45 |
+
|
46 |
.main {
|
47 |
+
background-color: #0F1923;
|
48 |
color: white;
|
49 |
+
direction: rtl;
|
50 |
+
text-align: right;
|
51 |
}
|
52 |
+
|
53 |
.stApp {
|
54 |
+
background-image: linear-gradient(to bottom, #0F1923, #1A2634);
|
55 |
}
|
56 |
+
|
57 |
.css-18e3th9 {
|
58 |
padding-top: 0;
|
59 |
}
|
60 |
+
|
61 |
.css-1d391kg {
|
62 |
padding-top: 1rem;
|
63 |
}
|
64 |
+
|
65 |
.stButton>button {
|
66 |
background-color: #2EC4B6;
|
67 |
color: white;
|
|
|
70 |
padding: 10px 20px;
|
71 |
transition: all 0.3s;
|
72 |
}
|
73 |
+
|
74 |
.stButton>button:hover {
|
75 |
background-color: #1A9E8F;
|
76 |
transform: scale(1.05);
|
77 |
}
|
78 |
+
|
79 |
.metric-card {
|
80 |
+
background-color: rgba(15, 25, 35, 0.8);
|
81 |
border-radius: 10px;
|
82 |
padding: 15px;
|
83 |
border: 1px solid #2EC4B6;
|
84 |
text-align: center;
|
85 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
86 |
+
transition: all 0.3s ease;
|
87 |
+
}
|
88 |
+
|
89 |
+
.metric-card:hover {
|
90 |
+
transform: translateY(-5px);
|
91 |
+
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2);
|
92 |
}
|
93 |
+
|
94 |
.metric-value {
|
95 |
+
font-size: 28px;
|
96 |
font-weight: bold;
|
97 |
color: #2EC4B6;
|
98 |
+
text-shadow: 0 0 10px rgba(46, 196, 182, 0.5);
|
99 |
}
|
100 |
+
|
101 |
.metric-label {
|
102 |
+
font-size: 16px;
|
103 |
color: #CCC;
|
104 |
+
margin-top: 5px;
|
105 |
}
|
106 |
+
|
107 |
.chart-container {
|
108 |
+
background-color: rgba(15, 25, 35, 0.8);
|
109 |
border-radius: 10px;
|
110 |
padding: 15px;
|
111 |
border: 1px solid #2EC4B6;
|
112 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
113 |
}
|
114 |
+
|
115 |
.table-container {
|
116 |
+
background-color: rgba(15, 25, 35, 0.8);
|
117 |
border-radius: 10px;
|
118 |
padding: 15px;
|
119 |
border: 1px solid #2EC4B6;
|
120 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
121 |
}
|
122 |
+
|
123 |
.compass {
|
124 |
position: relative;
|
125 |
width: 200px;
|
126 |
height: 200px;
|
127 |
margin: 0 auto;
|
128 |
+
animation: rotate 60s linear infinite;
|
129 |
}
|
130 |
+
|
131 |
+
@keyframes rotate {
|
132 |
+
from { transform: rotate(0deg); }
|
133 |
+
to { transform: rotate(360deg); }
|
134 |
+
}
|
135 |
+
|
136 |
.compass-img {
|
137 |
width: 100%;
|
138 |
height: 100%;
|
139 |
}
|
140 |
+
|
141 |
.stDataFrame {
|
142 |
+
background-color: rgba(15, 25, 35, 0.8) !important;
|
143 |
}
|
144 |
+
|
145 |
div[data-testid="stDataFrameResizable"] {
|
146 |
+
background-color: rgba(15, 25, 35, 0.8) !important;
|
147 |
}
|
148 |
+
|
149 |
div[data-testid="stDataFrameResizable"] > div {
|
150 |
+
background-color: rgba(15, 25, 35, 0.8) !important;
|
151 |
}
|
152 |
+
|
153 |
div[data-testid="stDataFrameResizable"] td {
|
154 |
+
background-color: rgba(15, 25, 35, 0.8) !important;
|
155 |
color: white !important;
|
156 |
}
|
157 |
+
|
158 |
div[data-testid="stDataFrameResizable"] th {
|
159 |
background-color: rgba(46, 196, 182, 0.3) !important;
|
160 |
color: white !important;
|
161 |
}
|
162 |
+
|
163 |
.stTabs [data-baseweb="tab-list"] {
|
164 |
gap: 10px;
|
165 |
}
|
166 |
+
|
167 |
.stTabs [data-baseweb="tab"] {
|
168 |
+
background-color: rgba(15, 25, 35, 0.8);
|
169 |
border-radius: 4px 4px 0px 0px;
|
170 |
padding: 10px 20px;
|
171 |
color: white;
|
172 |
}
|
173 |
+
|
174 |
.stTabs [aria-selected="true"] {
|
175 |
background-color: #2EC4B6 !important;
|
176 |
color: white !important;
|
177 |
}
|
178 |
+
|
179 |
.gauge-chart {
|
180 |
margin: 0 auto;
|
181 |
width: 200px;
|
182 |
height: 200px;
|
183 |
}
|
184 |
+
|
185 |
@keyframes pulse {
|
186 |
0% {
|
187 |
box-shadow: 0 0 0 0 rgba(46, 196, 182, 0.7);
|
|
|
193 |
box-shadow: 0 0 0 0 rgba(46, 196, 182, 0);
|
194 |
}
|
195 |
}
|
196 |
+
|
197 |
.pulsing {
|
198 |
animation: pulse 2s infinite;
|
199 |
}
|
200 |
+
|
201 |
.logo-container {
|
202 |
text-align: center;
|
203 |
margin-bottom: 20px;
|
204 |
}
|
205 |
+
|
206 |
.logo-text {
|
207 |
font-size: 24px;
|
208 |
font-weight: bold;
|
209 |
color: #2EC4B6;
|
210 |
}
|
211 |
+
|
212 |
+
.dashboard-container {
|
213 |
+
background-color: rgba(15, 25, 35, 0.7);
|
214 |
+
border-radius: 15px;
|
215 |
+
padding: 20px;
|
216 |
+
border: 1px solid #2EC4B6;
|
217 |
+
box-shadow: 0 0 20px rgba(46, 196, 182, 0.2);
|
218 |
+
margin-bottom: 20px;
|
219 |
+
}
|
220 |
+
|
221 |
+
.dashboard-header {
|
222 |
+
display: flex;
|
223 |
+
justify-content: space-between;
|
224 |
+
align-items: center;
|
225 |
+
margin-bottom: 20px;
|
226 |
+
border-bottom: 1px solid rgba(46, 196, 182, 0.3);
|
227 |
+
padding-bottom: 10px;
|
228 |
+
}
|
229 |
+
|
230 |
+
.dashboard-title {
|
231 |
+
color: #2EC4B6;
|
232 |
+
font-size: 24px;
|
233 |
+
font-weight: bold;
|
234 |
+
margin: 0;
|
235 |
+
}
|
236 |
+
|
237 |
+
.dashboard-subtitle {
|
238 |
+
color: #CCC;
|
239 |
+
font-size: 14px;
|
240 |
+
margin: 0;
|
241 |
+
}
|
242 |
+
|
243 |
+
.circular-button {
|
244 |
+
width: 60px;
|
245 |
+
height: 60px;
|
246 |
+
border-radius: 50%;
|
247 |
+
background-color: rgba(46, 196, 182, 0.2);
|
248 |
+
display: flex;
|
249 |
+
justify-content: center;
|
250 |
+
align-items: center;
|
251 |
+
color: #2EC4B6;
|
252 |
+
font-size: 24px;
|
253 |
+
border: 2px solid #2EC4B6;
|
254 |
+
cursor: pointer;
|
255 |
+
transition: all 0.3s;
|
256 |
+
margin: 0 auto;
|
257 |
+
box-shadow: 0 0 10px rgba(46, 196, 182, 0.5);
|
258 |
+
}
|
259 |
+
|
260 |
+
.circular-button:hover {
|
261 |
+
background-color: rgba(46, 196, 182, 0.4);
|
262 |
+
transform: scale(1.1);
|
263 |
+
}
|
264 |
+
|
265 |
+
.circular-value {
|
266 |
+
font-size: 18px;
|
267 |
+
font-weight: bold;
|
268 |
+
color: #2EC4B6;
|
269 |
+
text-align: center;
|
270 |
+
margin-top: 10px;
|
271 |
+
}
|
272 |
+
|
273 |
+
.circular-label {
|
274 |
+
font-size: 14px;
|
275 |
+
color: #CCC;
|
276 |
+
text-align: center;
|
277 |
+
}
|
278 |
+
|
279 |
+
.blinking {
|
280 |
+
animation: blink 1.5s infinite;
|
281 |
+
}
|
282 |
+
|
283 |
+
@keyframes blink {
|
284 |
+
0% { opacity: 1; }
|
285 |
+
50% { opacity: 0.3; }
|
286 |
+
100% { opacity: 1; }
|
287 |
+
}
|
288 |
+
|
289 |
+
.rotating {
|
290 |
+
animation: rotate 10s linear infinite;
|
291 |
+
}
|
292 |
+
|
293 |
+
.glowing {
|
294 |
+
box-shadow: 0 0 10px #2EC4B6, 0 0 20px #2EC4B6, 0 0 30px #2EC4B6;
|
295 |
+
animation: glow 2s infinite alternate;
|
296 |
+
}
|
297 |
+
|
298 |
+
@keyframes glow {
|
299 |
+
from { box-shadow: 0 0 10px #2EC4B6, 0 0 20px #2EC4B6; }
|
300 |
+
to { box-shadow: 0 0 20px #2EC4B6, 0 0 30px #2EC4B6, 0 0 40px #2EC4B6; }
|
301 |
+
}
|
302 |
+
|
303 |
+
.stSelectbox [data-baseweb=select] {
|
304 |
+
background-color: rgba(15, 25, 35, 0.8);
|
305 |
+
border-color: #2EC4B6;
|
306 |
+
}
|
307 |
+
|
308 |
+
.stSelectbox [data-baseweb=select] > div {
|
309 |
+
background-color: rgba(15, 25, 35, 0.8);
|
310 |
+
color: white;
|
311 |
+
}
|
312 |
+
|
313 |
+
.stDateInput > div > div {
|
314 |
+
background-color: rgba(15, 25, 35, 0.8);
|
315 |
+
border-color: #2EC4B6;
|
316 |
+
color: white;
|
317 |
+
}
|
318 |
+
|
319 |
+
.stDateInput input {
|
320 |
+
color: white;
|
321 |
+
}
|
322 |
+
|
323 |
+
/* Custom scrollbar */
|
324 |
+
::-webkit-scrollbar {
|
325 |
+
width: 8px;
|
326 |
+
height: 8px;
|
327 |
+
}
|
328 |
+
|
329 |
+
::-webkit-scrollbar-track {
|
330 |
+
background: rgba(15, 25, 35, 0.8);
|
331 |
+
}
|
332 |
+
|
333 |
+
::-webkit-scrollbar-thumb {
|
334 |
+
background: #2EC4B6;
|
335 |
+
border-radius: 4px;
|
336 |
+
}
|
337 |
+
|
338 |
+
::-webkit-scrollbar-thumb:hover {
|
339 |
+
background: #1A9E8F;
|
340 |
+
}
|
341 |
+
|
342 |
+
/* Animated buttons */
|
343 |
+
.animated-button {
|
344 |
+
background-color: rgba(46, 196, 182, 0.2);
|
345 |
+
color: #2EC4B6;
|
346 |
+
border: 2px solid #2EC4B6;
|
347 |
+
border-radius: 10px;
|
348 |
+
padding: 10px 15px;
|
349 |
+
margin: 5px;
|
350 |
+
transition: all 0.3s;
|
351 |
+
cursor: pointer;
|
352 |
+
text-align: center;
|
353 |
+
font-weight: bold;
|
354 |
+
box-shadow: 0 0 10px rgba(46, 196, 182, 0.3);
|
355 |
+
}
|
356 |
+
|
357 |
+
.animated-button:hover {
|
358 |
+
background-color: rgba(46, 196, 182, 0.4);
|
359 |
+
transform: translateY(-3px);
|
360 |
+
box-shadow: 0 5px 15px rgba(46, 196, 182, 0.5);
|
361 |
+
}
|
362 |
+
|
363 |
+
/* Tooltip styling */
|
364 |
+
.tooltip {
|
365 |
+
position: relative;
|
366 |
+
display: inline-block;
|
367 |
+
}
|
368 |
+
|
369 |
+
.tooltip .tooltiptext {
|
370 |
+
visibility: hidden;
|
371 |
+
width: 120px;
|
372 |
+
background-color: rgba(15, 25, 35, 0.9);
|
373 |
+
color: #fff;
|
374 |
+
text-align: center;
|
375 |
+
border-radius: 6px;
|
376 |
+
padding: 5px;
|
377 |
+
position: absolute;
|
378 |
+
z-index: 1;
|
379 |
+
bottom: 125%;
|
380 |
+
left: 50%;
|
381 |
+
margin-left: -60px;
|
382 |
+
opacity: 0;
|
383 |
+
transition: opacity 0.3s;
|
384 |
+
border: 1px solid #2EC4B6;
|
385 |
+
}
|
386 |
+
|
387 |
+
.tooltip:hover .tooltiptext {
|
388 |
+
visibility: visible;
|
389 |
+
opacity: 1;
|
390 |
+
}
|
391 |
+
|
392 |
+
/* Progress bar styling */
|
393 |
+
.stProgress > div > div > div {
|
394 |
+
background-color: #2EC4B6;
|
395 |
+
}
|
396 |
+
|
397 |
+
/* Header styling */
|
398 |
+
.header-container {
|
399 |
+
display: flex;
|
400 |
+
justify-content: space-between;
|
401 |
+
align-items: center;
|
402 |
+
padding: 10px 20px;
|
403 |
+
background-color: rgba(15, 25, 35, 0.8);
|
404 |
+
border-radius: 10px;
|
405 |
+
margin-bottom: 20px;
|
406 |
+
border-bottom: 2px solid #2EC4B6;
|
407 |
+
}
|
408 |
+
|
409 |
+
.header-title {
|
410 |
+
color: #2EC4B6;
|
411 |
+
font-size: 28px;
|
412 |
+
font-weight: bold;
|
413 |
+
margin: 0;
|
414 |
+
text-shadow: 0 0 10px rgba(46, 196, 182, 0.5);
|
415 |
+
}
|
416 |
+
|
417 |
+
.header-subtitle {
|
418 |
+
color: #CCC;
|
419 |
+
font-size: 16px;
|
420 |
+
margin: 0;
|
421 |
+
}
|
422 |
+
|
423 |
+
/* Card styling */
|
424 |
+
.card {
|
425 |
+
background-color: rgba(15, 25, 35, 0.8);
|
426 |
+
border-radius: 10px;
|
427 |
+
padding: 15px;
|
428 |
+
border: 1px solid #2EC4B6;
|
429 |
+
margin-bottom: 15px;
|
430 |
+
transition: all 0.3s;
|
431 |
+
}
|
432 |
+
|
433 |
+
.card:hover {
|
434 |
+
transform: translateY(-5px);
|
435 |
+
box-shadow: 0 5px 15px rgba(46, 196, 182, 0.3);
|
436 |
+
}
|
437 |
+
|
438 |
+
.card-title {
|
439 |
+
color: #2EC4B6;
|
440 |
+
font-size: 18px;
|
441 |
+
font-weight: bold;
|
442 |
+
margin-bottom: 10px;
|
443 |
+
border-bottom: 1px solid rgba(46, 196, 182, 0.3);
|
444 |
+
padding-bottom: 5px;
|
445 |
+
}
|
446 |
+
|
447 |
+
/* Weather icon styling */
|
448 |
+
.weather-icon {
|
449 |
+
font-size: 36px;
|
450 |
+
color: #2EC4B6;
|
451 |
+
text-align: center;
|
452 |
+
margin-bottom: 10px;
|
453 |
+
}
|
454 |
+
|
455 |
+
/* Farm status indicators */
|
456 |
+
.status-indicator {
|
457 |
+
width: 15px;
|
458 |
+
height: 15px;
|
459 |
+
border-radius: 50%;
|
460 |
+
display: inline-block;
|
461 |
+
margin-right: 5px;
|
462 |
+
}
|
463 |
+
|
464 |
+
.status-good {
|
465 |
+
background-color: #4CAF50;
|
466 |
+
box-shadow: 0 0 5px #4CAF50;
|
467 |
+
}
|
468 |
+
|
469 |
+
.status-warning {
|
470 |
+
background-color: #FFC107;
|
471 |
+
box-shadow: 0 0 5px #FFC107;
|
472 |
+
}
|
473 |
+
|
474 |
+
.status-alert {
|
475 |
+
background-color: #F44336;
|
476 |
+
box-shadow: 0 0 5px #F44336;
|
477 |
+
}
|
478 |
+
|
479 |
+
/* Custom radio buttons */
|
480 |
+
.stRadio [data-testid="stMarkdownContainer"] > div {
|
481 |
+
display: flex;
|
482 |
+
flex-direction: row;
|
483 |
+
}
|
484 |
+
|
485 |
+
.stRadio label {
|
486 |
+
background-color: rgba(15, 25, 35, 0.8);
|
487 |
+
border: 1px solid #2EC4B6;
|
488 |
+
border-radius: 5px;
|
489 |
+
padding: 10px 15px;
|
490 |
+
margin: 0 5px;
|
491 |
+
transition: all 0.3s;
|
492 |
+
}
|
493 |
+
|
494 |
+
.stRadio label:hover {
|
495 |
+
background-color: rgba(46, 196, 182, 0.2);
|
496 |
+
}
|
497 |
+
|
498 |
+
.stRadio label[data-checked="true"] {
|
499 |
+
background-color: #2EC4B6;
|
500 |
+
color: white;
|
501 |
+
}
|
502 |
+
|
503 |
+
/* Animated loading spinner */
|
504 |
+
@keyframes spinner {
|
505 |
+
0% { transform: rotate(0deg); }
|
506 |
+
100% { transform: rotate(360deg); }
|
507 |
+
}
|
508 |
+
|
509 |
+
.loading-spinner {
|
510 |
+
width: 40px;
|
511 |
+
height: 40px;
|
512 |
+
border: 4px solid rgba(46, 196, 182, 0.3);
|
513 |
+
border-radius: 50%;
|
514 |
+
border-top: 4px solid #2EC4B6;
|
515 |
+
animation: spinner 1s linear infinite;
|
516 |
+
margin: 20px auto;
|
517 |
+
}
|
518 |
+
|
519 |
+
/* Custom slider */
|
520 |
+
.stSlider [data-baseweb="slider"] {
|
521 |
+
margin-top: 10px;
|
522 |
+
}
|
523 |
+
|
524 |
+
.stSlider [data-baseweb="slider"] [data-testid="stThumbValue"] {
|
525 |
+
background-color: #2EC4B6;
|
526 |
+
color: white;
|
527 |
+
}
|
528 |
+
|
529 |
+
.stSlider [data-baseweb="slider"] [data-testid="stTrack"] > div {
|
530 |
+
background-color: #2EC4B6;
|
531 |
+
}
|
532 |
+
|
533 |
+
/* Responsive design adjustments */
|
534 |
+
@media (max-width: 768px) {
|
535 |
+
.metric-value {
|
536 |
+
font-size: 20px;
|
537 |
+
}
|
538 |
+
|
539 |
+
.metric-label {
|
540 |
+
font-size: 12px;
|
541 |
+
}
|
542 |
+
|
543 |
+
.dashboard-title {
|
544 |
+
font-size: 20px;
|
545 |
+
}
|
546 |
+
}
|
547 |
</style>
|
548 |
""", unsafe_allow_html=True)
|
549 |
|
|
|
561 |
ee.Initialize(credentials)
|
562 |
return True
|
563 |
else:
|
564 |
+
st.error("فایل اعتبارنامه GEE یافت نشد. لطفاً فایل اعتبارنامه را آپلود کنید.")
|
565 |
return False
|
566 |
|
567 |
# Load farm coordinates
|
|
|
569 |
def load_farm_data():
|
570 |
try:
|
571 |
farm_data = pd.read_csv('farm_coordinates.csv')
|
572 |
+
# Rename columns to Persian
|
573 |
+
farm_data.columns = ['نام مزرعه', 'سن', 'تنوع', 'طول جغرافیایی', 'عرض جغرافیایی']
|
574 |
return farm_data
|
575 |
except Exception as e:
|
576 |
+
st.error(f"خطا در بارگذاری مختصات مزارع: {e}")
|
577 |
# Create sample data if file not found
|
578 |
sample_data = pd.DataFrame({
|
579 |
+
'نام مزرعه': [f'مزرعه_{i}' for i in range(1, 11)],
|
580 |
+
'سن': np.random.randint(1, 6, 10),
|
581 |
+
'تنوع': np.random.choice(['نوع A', 'نوع B', 'نوع C'], 10),
|
582 |
+
'طول جغرافیایی': np.random.uniform(51.0, 51.5, 10),
|
583 |
+
'عرض جغرافیایی': np.random.uniform(32.0, 32.5, 10)
|
584 |
})
|
585 |
return sample_data
|
586 |
|
|
|
589 |
def load_day_schedule():
|
590 |
try:
|
591 |
schedule_data = pd.read_csv('پایگاه داده (1).csv')
|
592 |
+
# Ensure the column names are in Persian
|
593 |
+
if 'day' in schedule_data.columns and 'farm_name' in schedule_data.columns:
|
594 |
+
schedule_data.columns = ['روز', 'نام مزرعه']
|
595 |
return schedule_data
|
596 |
except Exception as e:
|
597 |
+
st.error(f"خطا در بارگذاری برنامه روزانه: {e}")
|
598 |
# Create sample data if file not found
|
599 |
+
days = ['دوشنبه', 'سهشنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه', 'شنبه', 'یکشنبه']
|
600 |
sample_data = pd.DataFrame({
|
601 |
+
'روز': np.random.choice(days, 10),
|
602 |
+
'نام مزرعه': [f'مزرعه_{i}' for i in range(1, 11)]
|
603 |
})
|
604 |
return sample_data
|
605 |
|
|
|
624 |
# NDRE = (NIR - Red Edge) / (NIR + Red Edge)
|
625 |
ndre = image.normalizedDifference(['B8', 'B5']).rename('NDRE')
|
626 |
|
627 |
+
# LAI calculation (more realistic model based on NDVI)
|
628 |
+
# LAI = 0.57 * exp(2.33 * NDVI)
|
629 |
+
lai = ndvi.expression(
|
630 |
+
'0.57 * exp(2.33 * NDVI)',
|
631 |
+
{'NDVI': ndvi}
|
632 |
+
).rename('LAI')
|
|
|
|
|
|
|
|
|
633 |
|
634 |
# Chlorophyll index
|
635 |
+
nir = image.select('B8')
|
636 |
+
red = image.select('B4')
|
637 |
chlorophyll = image.expression(
|
638 |
'(NIR / RED) - 1',
|
639 |
{'NIR': nir, 'RED': red}
|
640 |
).rename('CHL')
|
641 |
|
642 |
# Add all indices to the image
|
643 |
+
return image.addBands([ndvi, ndre, lai, chlorophyll])
|
644 |
|
645 |
# Function to create LSTM model
|
646 |
def create_lstm_model(input_shape):
|
647 |
model = Sequential()
|
648 |
+
model.add(LSTM(64, return_sequences=True, input_shape=input_shape))
|
649 |
model.add(Dropout(0.2))
|
650 |
+
model.add(LSTM(64, return_sequences=False))
|
651 |
model.add(Dropout(0.2))
|
652 |
+
model.add(Dense(32))
|
653 |
model.add(Dense(3)) # Predict LAI, NDVI, NDRE
|
654 |
|
655 |
model.compile(optimizer='adam', loss='mse')
|
|
|
719 |
gauge={
|
720 |
'axis': {'range': [min_val, max_val], 'tickcolor': 'white'},
|
721 |
'bar': {'color': "#2EC4B6"},
|
722 |
+
'bgcolor': "rgba(15, 25, 35, 0.8)",
|
723 |
'borderwidth': 2,
|
724 |
'bordercolor': "#2EC4B6",
|
725 |
'steps': [
|
|
|
744 |
def create_line_chart(data, x_col, y_cols, title):
|
745 |
fig = go.Figure()
|
746 |
|
747 |
+
colors = ['#2EC4B6', '#FF9F1C', '#E71D36', '#4361EE', '#7209B7']
|
748 |
+
|
749 |
+
for i, col in enumerate(y_cols):
|
750 |
fig.add_trace(go.Scatter(
|
751 |
x=data[x_col],
|
752 |
y=data[col],
|
753 |
mode='lines+markers',
|
754 |
name=col,
|
755 |
+
line=dict(width=3, color=colors[i % len(colors)]),
|
756 |
+
marker=dict(size=8, color=colors[i % len(colors)])
|
757 |
))
|
758 |
|
759 |
fig.update_layout(
|
760 |
title=title,
|
761 |
+
xaxis_title='تاریخ',
|
762 |
+
yaxis_title='مقدار',
|
763 |
legend=dict(
|
764 |
orientation="h",
|
765 |
yanchor="bottom",
|
|
|
768 |
x=1
|
769 |
),
|
770 |
template="plotly_dark",
|
771 |
+
plot_bgcolor='rgba(15, 25, 35, 0.8)',
|
772 |
paper_bgcolor='rgba(0,0,0,0)',
|
773 |
font=dict(color='white'),
|
774 |
margin=dict(l=20, r=20, t=50, b=20),
|
|
|
784 |
fig.add_trace(go.Bar(
|
785 |
x=data[x_col],
|
786 |
y=data[y_col],
|
787 |
+
marker_color='#2EC4B6',
|
788 |
+
marker_line_color='#1A9E8F',
|
789 |
+
marker_line_width=1.5,
|
790 |
+
opacity=0.8
|
791 |
))
|
792 |
|
793 |
fig.update_layout(
|
|
|
795 |
xaxis_title=x_col,
|
796 |
yaxis_title=y_col,
|
797 |
template="plotly_dark",
|
798 |
+
plot_bgcolor='rgba(15, 25, 35, 0.8)',
|
799 |
paper_bgcolor='rgba(0,0,0,0)',
|
800 |
font=dict(color='white'),
|
801 |
margin=dict(l=20, r=20, t=50, b=20),
|
|
|
822 |
xaxis_title=x_col,
|
823 |
yaxis_title=y_col,
|
824 |
template="plotly_dark",
|
825 |
+
plot_bgcolor='rgba(15, 25, 35, 0.8)',
|
826 |
paper_bgcolor='rgba(0,0,0,0)',
|
827 |
font=dict(color='white'),
|
828 |
margin=dict(l=20, r=20, t=50, b=20),
|
|
|
831 |
|
832 |
return fig
|
833 |
|
834 |
+
# Create a compass image
|
835 |
+
def get_compass_image():
|
836 |
+
# Create a base64 encoded compass image
|
837 |
+
compass_html = f"""
|
838 |
+
<div style="width:200px; height:200px; margin:0 auto; position:relative;">
|
839 |
+
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" class="rotating">
|
840 |
+
<circle cx="100" cy="100" r="90" fill="rgba(15, 25, 35, 0.8)" stroke="#2EC4B6" stroke-width="2"/>
|
841 |
+
<circle cx="100" cy="100" r="5" fill="#2EC4B6"/>
|
842 |
+
<path d="M100,10 L100,30" stroke="#2EC4B6" stroke-width="2" transform="rotate(0 100 100)"/>
|
843 |
+
<path d="M100,10 L100,30" stroke="#2EC4B6" stroke-width="2" transform="rotate(90 100 100)"/>
|
844 |
+
<path d="M100,10 L100,30" stroke="#2EC4B6" stroke-width="2" transform="rotate(180 100 100)"/>
|
845 |
+
<path d="M100,10 L100,30" stroke="#2EC4B6" stroke-width="2" transform="rotate(270 100 100)"/>
|
846 |
+
<path d="M100,10 L100,20" stroke="#2EC4B6" stroke-width="1" transform="rotate(30 100 100)"/>
|
847 |
+
<path d="M100,10 L100,20" stroke="#2EC4B6" stroke-width="1" transform="rotate(60 100 100)"/>
|
848 |
+
<path d="M100,10 L100,20" stroke="#2EC4B6" stroke-width="1" transform="rotate(120 100 100)"/>
|
849 |
+
<path d="M100,10 L100,20" stroke="#2EC4B6" stroke-width="1" transform="rotate(150 100 100)"/>
|
850 |
+
<path d="M100,10 L100,20" stroke="#2EC4B6" stroke-width="1" transform="rotate(210 100 100)"/>
|
851 |
+
<path d="M100,10 L100,20" stroke="#2EC4B6" stroke-width="1" transform="rotate(240 100 100)"/>
|
852 |
+
<path d="M100,10 L100,20" stroke="#2EC4B6" stroke-width="1" transform="rotate(300 100 100)"/>
|
853 |
+
<path d="M100,10 L100,20" stroke="#2EC4B6" stroke-width="1" transform="rotate(330 100 100)"/>
|
854 |
+
<text x="100" y="45" text-anchor="middle" fill="#2EC4B6" font-size="12">N</text>
|
855 |
+
<text x="155" y="105" text-anchor="middle" fill="#2EC4B6" font-size="12">E</text>
|
856 |
+
<text x="100" y="165" text-anchor="middle" fill="#2EC4B6" font-size="12">S</text>
|
857 |
+
<text x="45" y="105" text-anchor="middle" fill="#2EC4B6" font-size="12">W</text>
|
858 |
+
<path d="M100,40 L90,90 L100,80 L110,90 Z" fill="#E71D36" stroke="#2EC4B6" stroke-width="1"/>
|
859 |
+
<path d="M100,160 L90,110 L100,120 L110,110 Z" fill="#2EC4B6" stroke="#2EC4B6" stroke-width="1"/>
|
860 |
+
</svg>
|
861 |
+
</div>
|
862 |
+
"""
|
863 |
+
return compass_html
|
864 |
+
|
865 |
+
# Create a circular metric
|
866 |
+
def create_circular_metric(value, label, icon, min_val=0, max_val=100):
|
867 |
+
# Calculate percentage for the circular progress
|
868 |
+
percentage = min(max(value / max_val, 0), 1) * 100
|
869 |
+
|
870 |
+
# Create the HTML for the circular metric
|
871 |
+
html = f"""
|
872 |
+
<div style="text-align: center; margin-bottom: 20px;">
|
873 |
+
<div class="circular-button" style="background: conic-gradient(#2EC4B6 {percentage}%, rgba(15, 25, 35, 0.8) 0%);">
|
874 |
+
{icon}
|
875 |
+
</div>
|
876 |
+
<div class="circular-value">{value}</div>
|
877 |
+
<div class="circular-label">{label}</div>
|
878 |
+
</div>
|
879 |
+
"""
|
880 |
+
return html
|
881 |
+
|
882 |
# Main application
|
883 |
def main():
|
884 |
# Initialize Earth Engine
|
885 |
ee_initialized = initialize_ee()
|
886 |
if not ee_initialized:
|
887 |
+
st.warning("مقداردهی اولیه Earth Engine با مشکل مواجه شد. برخی از ویژگیها ممکن است کار نکنند.")
|
888 |
|
889 |
# Load data
|
890 |
farm_data = load_farm_data()
|
891 |
day_schedule = load_day_schedule()
|
892 |
|
893 |
# Header with logo
|
894 |
+
st.markdown("""
|
895 |
+
<div class="header-container">
|
896 |
+
<div>
|
897 |
+
<h1 class="header-title">سامانه هوشمند مانیتورینگ مزارع نیشکر</h1>
|
898 |
+
<p class="header-subtitle">پایش و پیشبینی رشد با استفاده از تصاویر ماهوارهای و هوش مصنوعی</p>
|
899 |
+
</div>
|
900 |
+
<div style="text-align: center;">
|
901 |
<div style="font-size: 40px; color: #2EC4B6;">✦</div>
|
902 |
+
<div style="font-size: 18px; font-weight: bold; color: #2EC4B6;">SMART<br>Sugarcane</div>
|
903 |
+
<div style="color: #CCC; font-size: 14px;">FARM</div>
|
904 |
</div>
|
905 |
+
</div>
|
906 |
+
""", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
907 |
|
908 |
# Date selection
|
909 |
today = datetime.now()
|
910 |
start_date = today - timedelta(days=30)
|
911 |
end_date = today
|
912 |
|
913 |
+
st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
|
914 |
+
|
915 |
+
col1, col2, col3 = st.columns([2, 1, 2])
|
916 |
with col1:
|
917 |
+
start_date = st.date_input("تاریخ شروع", start_date)
|
918 |
with col2:
|
919 |
+
# Day selection
|
920 |
+
days_persian = ['دوشنبه', 'سهشنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه', 'شنبه', 'یکشنبه']
|
921 |
+
selected_day = st.selectbox("انتخاب روز", days_persian)
|
922 |
+
with col3:
|
923 |
+
end_date = st.date_input("تاریخ پایان", end_date)
|
924 |
|
925 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
|
|
|
|
926 |
|
927 |
# Filter farms for selected day
|
928 |
+
day_farms = day_schedule[day_schedule['روز'] == selected_day]['نام مزرعه'].tolist()
|
929 |
+
selected_farms = farm_data[farm_data['نام مزرعه'].isin(day_farms)]
|
930 |
|
931 |
if selected_farms.empty:
|
932 |
+
st.warning(f"هیچ مزرعهای برای روز {selected_day} برنامهریزی نشده است. لطفاً روز دیگری را انتخاب کنید.")
|
933 |
else:
|
934 |
# Create tabs for different sections
|
935 |
+
tabs = st.tabs(["داشبورد", "تحلیل مزرعه", "پیشبینی رشد", "جداول داده"])
|
936 |
|
937 |
# Dashboard Tab
|
938 |
with tabs[0]:
|
939 |
+
st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
|
940 |
+
|
941 |
# Main metrics row
|
942 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
943 |
+
st.markdown('<h2 class="dashboard-title">شاخصهای فعلی مزارع</h2>', unsafe_allow_html=True)
|
944 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
945 |
+
|
946 |
metric_cols = st.columns(5)
|
947 |
|
948 |
# Generate sample metrics for demonstration
|
|
|
956 |
st.markdown(f"""
|
957 |
<div class="metric-card">
|
958 |
<div class="metric-value">{avg_ndvi}</div>
|
959 |
+
<div class="metric-label">میانگین NDVI</div>
|
960 |
</div>
|
961 |
""", unsafe_allow_html=True)
|
962 |
|
|
|
964 |
st.markdown(f"""
|
965 |
<div class="metric-card">
|
966 |
<div class="metric-value">{avg_lai}</div>
|
967 |
+
<div class="metric-label">میانگین LAI</div>
|
968 |
</div>
|
969 |
""", unsafe_allow_html=True)
|
970 |
|
|
|
972 |
st.markdown(f"""
|
973 |
<div class="metric-card">
|
974 |
<div class="metric-value">{avg_ndre}</div>
|
975 |
+
<div class="metric-label">میانگین NDRE</div>
|
976 |
</div>
|
977 |
""", unsafe_allow_html=True)
|
978 |
|
|
|
980 |
st.markdown(f"""
|
981 |
<div class="metric-card">
|
982 |
<div class="metric-value">{avg_chl}</div>
|
983 |
+
<div class="metric-label">شاخص کلروفیل</div>
|
984 |
</div>
|
985 |
""", unsafe_allow_html=True)
|
986 |
|
|
|
988 |
st.markdown(f"""
|
989 |
<div class="metric-card pulsing">
|
990 |
<div class="metric-value">{avg_growth}%</div>
|
991 |
+
<div class="metric-label">نرخ رشد</div>
|
992 |
</div>
|
993 |
""", unsafe_allow_html=True)
|
994 |
|
995 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
996 |
+
|
997 |
# Map and compass row
|
998 |
+
st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
|
999 |
+
|
1000 |
map_col, compass_col = st.columns([3, 1])
|
1001 |
|
1002 |
with map_col:
|
1003 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1004 |
+
st.markdown('<h2 class="dashboard-title">نقشه مزارع (NDVI)</h2>', unsafe_allow_html=True)
|
1005 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1006 |
|
1007 |
# Create a map centered at the mean of farm coordinates
|
1008 |
+
center_lat = selected_farms['عرض جغرافیایی'].mean()
|
1009 |
+
center_lon = selected_farms['طول جغرافیایی'].mean()
|
1010 |
|
1011 |
m = geemap.Map(center=[center_lat, center_lon], zoom=12)
|
1012 |
|
|
|
1015 |
|
1016 |
# Create features for farms
|
1017 |
for _, farm in selected_farms.iterrows():
|
1018 |
+
point = ee.Geometry.Point([farm['طول جغرافیایی'], farm['عرض جغرافیایی']])
|
1019 |
buffer = point.buffer(500) # 500m buffer around point
|
1020 |
|
1021 |
# Get Sentinel imagery
|
|
|
1036 |
'max': 1,
|
1037 |
'palette': ['#d73027', '#f46d43', '#fdae61', '#fee08b', '#d9ef8b', '#a6d96a', '#66bd63', '#1a9850']
|
1038 |
}
|
1039 |
+
m.addLayer(image_with_indices.select('NDVI'), ndvi_vis, f'NDVI - {farm["نام مزرعه"]}')
|
1040 |
|
1041 |
# Add farm marker with pulsing effect
|
1042 |
folium.Marker(
|
1043 |
+
location=[farm['عرض جغرافیایی'], farm['طول جغرافیایی']],
|
1044 |
popup=f"""
|
1045 |
+
<div style='width: 200px; direction: rtl; text-align: right;'>
|
1046 |
+
<h4>{farm['نام مزرعه']}</h4>
|
1047 |
+
<p>سن: {farm['سن']} سال</p>
|
1048 |
+
<p>تنوع: {farm['تنوع']}</p>
|
1049 |
</div>
|
1050 |
""",
|
1051 |
icon=folium.Icon(color='green', icon='leaf')
|
|
|
1055 |
folium_static(m)
|
1056 |
|
1057 |
with compass_col:
|
1058 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1059 |
+
st.markdown('<h2 class="dashboard-title">ناوبری</h2>', unsafe_allow_html=True)
|
1060 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1061 |
+
|
1062 |
+
# Display compass
|
1063 |
+
st.markdown(get_compass_image(), unsafe_allow_html=True)
|
1064 |
|
1065 |
# Add a gauge chart for overall farm health
|
1066 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1067 |
+
st.markdown('<h2 class="dashboard-title">سلامت مزرعه</h2>', unsafe_allow_html=True)
|
1068 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1069 |
+
|
1070 |
+
gauge_fig = create_gauge_chart(avg_growth/100, "شاخص سلامت", 0, 1)
|
1071 |
st.plotly_chart(gauge_fig, use_container_width=True)
|
1072 |
|
1073 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1074 |
+
|
1075 |
# Charts row
|
1076 |
+
st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
|
1077 |
+
|
1078 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1079 |
+
st.markdown('<h2 class="dashboard-title">تحلیل مزارع</h2>', unsafe_allow_html=True)
|
1080 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1081 |
+
|
1082 |
chart_col1, chart_col2 = st.columns(2)
|
1083 |
|
1084 |
with chart_col1:
|
|
|
1093 |
ndvi_values = np.clip(ndvi_values, 0, 1)
|
1094 |
|
1095 |
ts_data = pd.DataFrame({
|
1096 |
+
'تاریخ': dates,
|
1097 |
'NDVI': ndvi_values,
|
1098 |
'NDRE': ndvi_values * 0.7 + np.random.normal(0, 0.05, size=len(dates)),
|
1099 |
'LAI': ndvi_values * 5 + np.random.normal(0, 0.3, size=len(dates))
|
1100 |
})
|
1101 |
|
1102 |
# Create and display line chart
|
1103 |
+
line_fig = create_line_chart(ts_data, 'تاریخ', ['NDVI', 'NDRE'], 'شاخصهای گیاهی در طول زمان')
|
1104 |
st.plotly_chart(line_fig, use_container_width=True)
|
1105 |
|
1106 |
with chart_col2:
|
1107 |
# Create sample area chart data
|
1108 |
area_data = pd.DataFrame({
|
1109 |
+
'تاریخ': dates,
|
1110 |
+
'رشد': np.cumsum(np.random.normal(0.1, 0.03, size=len(dates)))
|
1111 |
})
|
1112 |
|
1113 |
# Create and display area chart
|
1114 |
+
area_fig = create_area_chart(area_data, 'تاریخ', 'رشد', 'رشد تجمعی')
|
1115 |
st.plotly_chart(area_fig, use_container_width=True)
|
1116 |
|
1117 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1118 |
+
|
1119 |
# Farm ranking row
|
1120 |
+
st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
|
1121 |
+
|
1122 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1123 |
+
st.markdown('<h2 class="dashboard-title">رتبهبندی مزارع</h2>', unsafe_allow_html=True)
|
1124 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1125 |
+
|
1126 |
rank_col1, rank_col2 = st.columns(2)
|
1127 |
|
1128 |
with rank_col1:
|
1129 |
+
# Create sample farm ranking data for the last 7 days
|
1130 |
+
farm_names = selected_farms['نام مزرعه'].tolist()
|
1131 |
+
|
1132 |
+
# Calculate LAI for each farm based on realistic formula
|
1133 |
+
farm_lai_values = []
|
1134 |
+
for _, farm in selected_farms.iterrows():
|
1135 |
+
# Base NDVI with some randomness related to farm age
|
1136 |
+
base_ndvi = min(0.5 + 0.08 * farm['سن'] + np.random.normal(0, 0.05), 0.95)
|
1137 |
+
# LAI calculation: LAI = 0.57 * exp(2.33 * NDVI)
|
1138 |
+
lai_value = 0.57 * np.exp(2.33 * base_ndvi)
|
1139 |
+
farm_lai_values.append(round(lai_value, 2))
|
1140 |
|
1141 |
rank_data = pd.DataFrame({
|
1142 |
+
'مزرعه': farm_names,
|
1143 |
+
'شاخص سطح برگ': farm_lai_values
|
1144 |
+
}).sort_values('شاخص سطح برگ', ascending=False)
|
1145 |
|
1146 |
# Create and display bar chart
|
1147 |
+
bar_fig = create_bar_chart(rank_data, 'مزرعه', 'شاخص سطح برگ', 'مزارع بر اساس شاخص سطح برگ')
|
1148 |
st.plotly_chart(bar_fig, use_container_width=True)
|
1149 |
|
1150 |
with rank_col2:
|
|
|
1152 |
st.markdown('<div class="table-container">', unsafe_allow_html=True)
|
1153 |
st.dataframe(
|
1154 |
rank_data.reset_index(drop=True).style.background_gradient(
|
1155 |
+
cmap='Greens', subset=['شاخص سطح برگ']
|
1156 |
),
|
1157 |
use_container_width=True
|
1158 |
)
|
1159 |
st.markdown('</div>', unsafe_allow_html=True)
|
1160 |
+
|
1161 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1162 |
+
|
1163 |
+
# Circular metrics row
|
1164 |
+
st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
|
1165 |
+
|
1166 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1167 |
+
st.markdown('<h2 class="dashboard-title">شاخصهای کلیدی</h2>', unsafe_allow_html=True)
|
1168 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1169 |
+
|
1170 |
+
circ_cols = st.columns(4)
|
1171 |
+
|
1172 |
+
with circ_cols[0]:
|
1173 |
+
st.markdown(create_circular_metric(
|
1174 |
+
round(np.random.uniform(20, 30), 1),
|
1175 |
+
"دما (°C)",
|
1176 |
+
"🌡️",
|
1177 |
+
0,
|
1178 |
+
50
|
1179 |
+
), unsafe_allow_html=True)
|
1180 |
+
|
1181 |
+
with circ_cols[1]:
|
1182 |
+
st.markdown(create_circular_metric(
|
1183 |
+
round(np.random.uniform(40, 80), 1),
|
1184 |
+
"رطوبت (%)",
|
1185 |
+
"💧",
|
1186 |
+
0,
|
1187 |
+
100
|
1188 |
+
), unsafe_allow_html=True)
|
1189 |
+
|
1190 |
+
with circ_cols[2]:
|
1191 |
+
st.markdown(create_circular_metric(
|
1192 |
+
round(np.random.uniform(3, 8), 1),
|
1193 |
+
"بریکس",
|
1194 |
+
"🍯",
|
1195 |
+
0,
|
1196 |
+
15
|
1197 |
+
), unsafe_allow_html=True)
|
1198 |
+
|
1199 |
+
with circ_cols[3]:
|
1200 |
+
st.markdown(create_circular_metric(
|
1201 |
+
round(np.random.uniform(70, 95), 1),
|
1202 |
+
"خلوص (%)",
|
1203 |
+
"✨",
|
1204 |
+
0,
|
1205 |
+
100
|
1206 |
+
), unsafe_allow_html=True)
|
1207 |
+
|
1208 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1209 |
|
1210 |
# Farm Analysis Tab
|
1211 |
with tabs[1]:
|
1212 |
+
st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
|
1213 |
+
|
1214 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1215 |
+
st.markdown('<h2 class="dashboard-title">تحلیل تفصیلی مزرعه</h2>', unsafe_allow_html=True)
|
1216 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1217 |
|
1218 |
# Farm selection
|
1219 |
+
selected_farm = st.selectbox("انتخاب مزرعه برای تحلیل", selected_farms['نام مزرعه'].tolist())
|
1220 |
+
farm_row = selected_farms[selected_farms['نام مزرعه'] == selected_farm].iloc[0]
|
1221 |
|
1222 |
# Farm details
|
1223 |
st.markdown(f"""
|
1224 |
<div class="metric-card" style="margin-bottom: 20px;">
|
1225 |
<h3>{selected_farm}</h3>
|
1226 |
+
<p>سن: {farm_row['سن']} سال | تنوع: {farm_row['تنوع']}</p>
|
1227 |
+
<p>موقعیت: {farm_row['عرض جغرافیایی']:.4f}, {farm_row['طول جغرافیایی']:.4f}</p>
|
1228 |
</div>
|
1229 |
""", unsafe_allow_html=True)
|
1230 |
|
1231 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1232 |
+
|
1233 |
# Create tabs for different analysis views
|
1234 |
+
analysis_tabs = st.tabs(["شاخصهای گیاهی", "روند رشد", "عوامل محیطی"])
|
1235 |
|
1236 |
# Vegetation Indices tab
|
1237 |
with analysis_tabs[0]:
|
1238 |
+
st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
|
1239 |
+
|
1240 |
# Generate sample time series data for the selected farm
|
1241 |
dates = pd.date_range(start=start_date, end=end_date, freq='D')
|
1242 |
|
1243 |
# Base values with some randomness
|
1244 |
base_ndvi = np.random.uniform(0.6, 0.8)
|
1245 |
base_ndre = np.random.uniform(0.4, 0.6)
|
|
|
1246 |
|
1247 |
# Add daily variations
|
1248 |
ndvi_values = base_ndvi + np.random.normal(0, 0.05, size=len(dates))
|
1249 |
ndre_values = base_ndre + np.random.normal(0, 0.04, size=len(dates))
|
|
|
1250 |
|
1251 |
# Add trend
|
1252 |
trend = np.linspace(0, 0.15, len(dates))
|
1253 |
ndvi_values += trend
|
1254 |
ndre_values += trend * 0.8
|
|
|
1255 |
|
1256 |
# Clip to valid ranges
|
1257 |
ndvi_values = np.clip(ndvi_values, 0, 1)
|
1258 |
ndre_values = np.clip(ndre_values, 0, 1)
|
1259 |
+
|
1260 |
+
# Calculate LAI using the realistic formula: LAI = 0.57 * exp(2.33 * NDVI)
|
1261 |
+
lai_values = 0.57 * np.exp(2.33 * ndvi_values)
|
1262 |
|
1263 |
# Create dataframe
|
1264 |
farm_ts_data = pd.DataFrame({
|
1265 |
+
'تاریخ': dates,
|
1266 |
'NDVI': ndvi_values,
|
1267 |
'NDRE': ndre_values,
|
1268 |
'LAI': lai_values
|
1269 |
})
|
1270 |
|
1271 |
# Display charts
|
1272 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1273 |
+
st.markdown('<h2 class="dashboard-title">شاخصهای گیاهی در طول زمان</h2>', unsafe_allow_html=True)
|
1274 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1275 |
|
1276 |
# Create line chart for all indices
|
1277 |
vi_fig = create_line_chart(
|
1278 |
+
farm_ts_data, 'تاریخ', ['NDVI', 'NDRE', 'LAI'],
|
1279 |
+
f'شاخصهای گیاهی برای {selected_farm}'
|
1280 |
)
|
1281 |
st.plotly_chart(vi_fig, use_container_width=True)
|
1282 |
|
1283 |
# Display individual metrics with small charts
|
1284 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1285 |
+
st.markdown('<h2 class="dashboard-title">وضعیت فعلی گیاه</h2>', unsafe_allow_html=True)
|
1286 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1287 |
+
|
1288 |
vi_cols = st.columns(3)
|
1289 |
|
1290 |
with vi_cols[0]:
|
|
|
1299 |
|
1300 |
with vi_cols[2]:
|
1301 |
current_lai = lai_values[-1]
|
1302 |
+
lai_gauge = create_gauge_chart(current_lai, "LAI", 0, 7)
|
1303 |
st.plotly_chart(lai_gauge, use_container_width=True)
|
1304 |
+
|
1305 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1306 |
|
1307 |
# Growth Trends tab
|
1308 |
with analysis_tabs[1]:
|
1309 |
+
st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
|
1310 |
+
|
1311 |
# Generate sample growth data
|
1312 |
growth_dates = pd.date_range(start=start_date, end=end_date, freq='D')
|
1313 |
|
|
|
1324 |
|
1325 |
# Create dataframe
|
1326 |
growth_data = pd.DataFrame({
|
1327 |
+
'تاریخ': growth_dates,
|
1328 |
+
'رشد روزانه': growth_values,
|
1329 |
+
'رشد تجمعی': cumulative_growth
|
1330 |
})
|
1331 |
|
1332 |
# Display charts
|
1333 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1334 |
+
st.markdown('<h2 class="dashboard-title">الگوهای رشد</h2>', unsafe_allow_html=True)
|
1335 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1336 |
|
1337 |
+
growth_tabs = st.tabs(["رشد روزانه", "رشد تجمعی"])
|
1338 |
|
1339 |
with growth_tabs[0]:
|
1340 |
daily_fig = create_bar_chart(
|
1341 |
+
growth_data, 'تاریخ', 'رشد روزانه',
|
1342 |
+
f'نرخ رشد روزانه برای {selected_farm}'
|
1343 |
)
|
1344 |
st.plotly_chart(daily_fig, use_container_width=True)
|
1345 |
|
1346 |
with growth_tabs[1]:
|
1347 |
cumulative_fig = create_area_chart(
|
1348 |
+
growth_data, 'تاریخ', 'رشد تجمعی',
|
1349 |
+
f'رشد تجمعی برای {selected_farm}'
|
1350 |
)
|
1351 |
st.plotly_chart(cumulative_fig, use_container_width=True)
|
1352 |
|
1353 |
# Growth statistics
|
1354 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1355 |
+
st.markdown('<h2 class="dashboard-title">آمار رشد</h2>', unsafe_allow_html=True)
|
1356 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1357 |
+
|
1358 |
stats_cols = st.columns(3)
|
1359 |
|
1360 |
with stats_cols[0]:
|
|
|
1362 |
st.markdown(f"""
|
1363 |
<div class="metric-card">
|
1364 |
<div class="metric-value">{avg_growth:.3f}</div>
|
1365 |
+
<div class="metric-label">میانگین رشد روزانه</div>
|
1366 |
</div>
|
1367 |
""", unsafe_allow_html=True)
|
1368 |
|
|
|
1372 |
st.markdown(f"""
|
1373 |
<div class="metric-card">
|
1374 |
<div class="metric-value">{max_growth:.3f}</div>
|
1375 |
+
<div class="metric-label">حداکثر رشد ({max_day})</div>
|
1376 |
</div>
|
1377 |
""", unsafe_allow_html=True)
|
1378 |
|
|
|
1381 |
st.markdown(f"""
|
1382 |
<div class="metric-card">
|
1383 |
<div class="metric-value">{total_growth:.2f}</div>
|
1384 |
+
<div class="metric-label">رشد کل</div>
|
1385 |
</div>
|
1386 |
""", unsafe_allow_html=True)
|
1387 |
+
|
1388 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1389 |
|
1390 |
# Environmental Factors tab
|
1391 |
with analysis_tabs[2]:
|
1392 |
+
st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
|
1393 |
+
|
1394 |
# Generate sample environmental data
|
1395 |
env_dates = pd.date_range(start=start_date, end=end_date, freq='D')
|
1396 |
|
|
|
1410 |
|
1411 |
# Create dataframe
|
1412 |
env_data = pd.DataFrame({
|
1413 |
+
'تاریخ': env_dates,
|
1414 |
+
'دما (°C)': temp_values,
|
1415 |
+
'رطوبت (%)': humidity_values,
|
1416 |
+
'بارندگی (mm)': rainfall_values
|
1417 |
})
|
1418 |
|
1419 |
# Display charts
|
1420 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1421 |
+
st.markdown('<h2 class="dashboard-title">شرایط محیطی</h2>', unsafe_allow_html=True)
|
1422 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1423 |
|
1424 |
+
env_tabs = st.tabs(["دما", "رطوبت", "بارندگی"])
|
1425 |
|
1426 |
with env_tabs[0]:
|
1427 |
temp_fig = create_line_chart(
|
1428 |
+
env_data, 'تاریخ', ['دما (°C)'],
|
1429 |
+
f'دما برای {selected_farm}'
|
1430 |
)
|
1431 |
st.plotly_chart(temp_fig, use_container_width=True)
|
1432 |
|
1433 |
with env_tabs[1]:
|
1434 |
humidity_fig = create_line_chart(
|
1435 |
+
env_data, 'تاریخ', ['رطوبت (%)'],
|
1436 |
+
f'رطوبت برای {selected_farm}'
|
1437 |
)
|
1438 |
st.plotly_chart(humidity_fig, use_container_width=True)
|
1439 |
|
1440 |
with env_tabs[2]:
|
1441 |
rainfall_fig = create_bar_chart(
|
1442 |
+
env_data, 'تاریخ', 'بارندگی (mm)',
|
1443 |
+
f'بارندگی برای {selected_farm}'
|
1444 |
)
|
1445 |
st.plotly_chart(rainfall_fig, use_container_width=True)
|
1446 |
|
1447 |
# Correlation analysis
|
1448 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1449 |
+
st.markdown('<h2 class="dashboard-title">تأثیر محیط بر رشد</h2>', unsafe_allow_html=True)
|
1450 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1451 |
|
1452 |
# Merge environmental and growth data
|
1453 |
merged_data = pd.merge(
|
1454 |
env_data,
|
1455 |
+
growth_data[['تاریخ', 'رشد روزانه']],
|
1456 |
+
on='تاریخ'
|
1457 |
)
|
1458 |
|
1459 |
# Calculate correlations
|
1460 |
+
corr_temp = np.corrcoef(merged_data['دما (°C)'], merged_data['رشد روزانه'])[0, 1]
|
1461 |
+
corr_humidity = np.corrcoef(merged_data['رطوبت (%)'], merged_data['رشد روزانه'])[0, 1]
|
1462 |
+
corr_rainfall = np.corrcoef(merged_data['بارندگی (mm)'], merged_data['رشد روزانه'])[0, 1]
|
1463 |
|
1464 |
# Display correlation metrics
|
1465 |
corr_cols = st.columns(3)
|
|
|
1468 |
st.markdown(f"""
|
1469 |
<div class="metric-card">
|
1470 |
<div class="metric-value">{corr_temp:.2f}</div>
|
1471 |
+
<div class="metric-label">همبستگی دما</div>
|
1472 |
</div>
|
1473 |
""", unsafe_allow_html=True)
|
1474 |
|
|
|
1476 |
st.markdown(f"""
|
1477 |
<div class="metric-card">
|
1478 |
<div class="metric-value">{corr_humidity:.2f}</div>
|
1479 |
+
<div class="metric-label">همبستگی رطوبت</div>
|
1480 |
</div>
|
1481 |
""", unsafe_allow_html=True)
|
1482 |
|
|
|
1484 |
st.markdown(f"""
|
1485 |
<div class="metric-card">
|
1486 |
<div class="metric-value">{corr_rainfall:.2f}</div>
|
1487 |
+
<div class="metric-label">همبستگی بارندگی</div>
|
1488 |
</div>
|
1489 |
""", unsafe_allow_html=True)
|
1490 |
+
|
1491 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1492 |
|
1493 |
# Growth Prediction Tab
|
1494 |
with tabs[2]:
|
1495 |
+
st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
|
1496 |
+
|
1497 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1498 |
+
st.markdown('<h2 class="dashboard-title">پیشب��نی رشد با LSTM</h2>', unsafe_allow_html=True)
|
1499 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1500 |
|
1501 |
# Farm selection for prediction
|
1502 |
+
pred_farm = st.selectbox("انتخاب مزرعه برای پیشبینی", selected_farms['نام مزرعه'].tolist(), key="pred_farm")
|
1503 |
+
|
1504 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1505 |
|
1506 |
# Generate sample historical data for LSTM
|
1507 |
hist_dates = pd.date_range(start=start_date - timedelta(days=60), end=end_date, freq='D')
|
|
|
1528 |
rainfall[rain_days] = np.random.exponential(5, size=len(rain_days))
|
1529 |
|
1530 |
# Age (constant for a farm)
|
1531 |
+
farm_age = selected_farms[selected_farms['نام مزرعه'] == pred_farm]['سن'].values[0]
|
1532 |
age = np.ones(len(hist_dates)) * farm_age
|
1533 |
|
1534 |
# Variety (encoded as 0, 1, 2)
|
1535 |
+
variety_map = {'نوع A': 0, 'نوع B': 1, 'نوع C': 2}
|
1536 |
+
farm_variety = selected_farms[selected_farms['نام مزرعه'] == pred_farm]['تنوع'].values[0]
|
1537 |
variety_encoded = np.ones(len(hist_dates)) * variety_map.get(farm_variety, 0)
|
1538 |
|
1539 |
# Target variables with relationship to features
|
1540 |
+
# NDVI increases with rainfall and temperature within optimal range
|
1541 |
+
ndvi_base = 0.01 * (temp - 15) / 15 + 0.02 * rainfall / 5 - 0.005 * np.abs(temp - 28) / 10
|
1542 |
+
ndvi = 0.6 + ndvi_base + 0.001 * np.arange(len(hist_dates)) + np.random.normal(0, 0.02, len(hist_dates))
|
|
|
|
|
|
|
|
|
1543 |
ndvi = np.clip(ndvi, 0, 1)
|
1544 |
|
1545 |
# NDRE correlated with NDVI but lower values
|
1546 |
ndre = 0.7 * ndvi + np.random.normal(0, 0.02, len(hist_dates))
|
1547 |
ndre = np.clip(ndre, 0, 1)
|
1548 |
|
1549 |
+
# LAI calculation using the realistic formula: LAI = 0.57 * exp(2.33 * NDVI)
|
1550 |
+
lai = 0.57 * np.exp(2.33 * ndvi)
|
1551 |
+
|
1552 |
# Create dataframe
|
1553 |
hist_data = pd.DataFrame({
|
1554 |
+
'تاریخ': hist_dates,
|
1555 |
+
'دما': temp,
|
1556 |
+
'رطوبت': humidity,
|
1557 |
+
'بارندگی': rainfall,
|
1558 |
+
'سن': age,
|
1559 |
+
'تنوع': variety_encoded,
|
1560 |
'LAI': lai,
|
1561 |
'NDVI': ndvi,
|
1562 |
'NDRE': ndre
|
1563 |
})
|
1564 |
|
1565 |
# Display historical data
|
1566 |
+
st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
|
1567 |
+
|
1568 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1569 |
+
st.markdown('<h2 class="dashboard-title">دادههای تاریخی</h2>', unsafe_allow_html=True)
|
1570 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1571 |
+
|
1572 |
hist_fig = create_line_chart(
|
1573 |
+
hist_data, 'تاریخ', ['LAI', 'NDVI', 'NDRE'],
|
1574 |
+
f'شاخصهای گیاهی تاریخی برای {pred_farm}'
|
1575 |
)
|
1576 |
st.plotly_chart(hist_fig, use_container_width=True)
|
1577 |
|
1578 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1579 |
+
|
1580 |
# LSTM Model Training
|
1581 |
+
st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
|
1582 |
+
|
1583 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1584 |
+
st.markdown('<h2 class="dashboard-title">آموزش مدل LSTM</h2>', unsafe_allow_html=True)
|
1585 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1586 |
|
1587 |
# Button to trigger training
|
1588 |
+
if st.button("آموزش مدل LSTM و پیشبینی"):
|
1589 |
+
with st.spinner("در حال آموزش مدل LSTM..."):
|
1590 |
# Prepare data for LSTM
|
1591 |
+
features = hist_data[['دما', 'رطوبت', 'بارندگی', 'سن', 'تنوع']].values
|
1592 |
targets = hist_data[['LAI', 'NDVI', 'NDRE']].values
|
1593 |
|
1594 |
# Combine features and targets
|
|
|
1608 |
|
1609 |
# Create prediction dataframe
|
1610 |
pred_data = pd.DataFrame({
|
1611 |
+
'تاریخ': pred_dates,
|
1612 |
'LAI': predictions[:, 0],
|
1613 |
'NDVI': predictions[:, 1],
|
1614 |
'NDRE': predictions[:, 2]
|
|
|
1616 |
|
1617 |
# Combine historical and prediction data
|
1618 |
combined_data = pd.concat([
|
1619 |
+
hist_data[['تاریخ', 'LAI', 'NDVI', 'NDRE']].tail(30), # Last 30 days of historical data
|
1620 |
pred_data
|
1621 |
])
|
1622 |
|
1623 |
# Display prediction results
|
1624 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1625 |
+
st.markdown('<h2 class="dashboard-title">نتایج پیشبینی</h2>', unsafe_allow_html=True)
|
1626 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1627 |
|
1628 |
# Create line chart with historical and predicted values
|
1629 |
pred_fig = go.Figure()
|
1630 |
|
1631 |
# Historical data (solid lines)
|
1632 |
+
for col, color in zip(['LAI', 'NDVI', 'NDRE'], ['#2EC4B6', '#FF9F1C', '#E71D36']):
|
1633 |
+
hist_part = combined_data[combined_data['تاریخ'] <= end_date]
|
1634 |
pred_fig.add_trace(go.Scatter(
|
1635 |
+
x=hist_part['تاریخ'],
|
1636 |
y=hist_part[col],
|
1637 |
mode='lines',
|
1638 |
+
name=f'تاریخی {col}',
|
1639 |
line=dict(color=color)
|
1640 |
))
|
1641 |
|
1642 |
# Predicted data (dashed lines)
|
1643 |
+
for col, color in zip(['LAI', 'NDVI', 'NDRE'], ['#2EC4B6', '#FF9F1C', '#E71D36']):
|
1644 |
+
pred_part = combined_data[combined_data['تاریخ'] > end_date]
|
1645 |
pred_fig.add_trace(go.Scatter(
|
1646 |
+
x=pred_part['تاریخ'],
|
1647 |
y=pred_part[col],
|
1648 |
mode='lines+markers',
|
1649 |
+
name=f'پیشبینی {col}',
|
1650 |
line=dict(color=color, dash='dash'),
|
1651 |
marker=dict(size=8)
|
1652 |
))
|
|
|
1657 |
line_width=2,
|
1658 |
line_dash="dash",
|
1659 |
line_color="red",
|
1660 |
+
annotation_text="شروع پیشبینی",
|
1661 |
annotation_position="top right"
|
1662 |
)
|
1663 |
|
1664 |
pred_fig.update_layout(
|
1665 |
+
title=f'پیشبینی رشد 7 روزه برای {pred_farm}',
|
1666 |
+
xaxis_title='تاریخ',
|
1667 |
+
yaxis_title='مقدار',
|
1668 |
legend=dict(
|
1669 |
orientation="h",
|
1670 |
yanchor="bottom",
|
|
|
1673 |
x=1
|
1674 |
),
|
1675 |
template="plotly_dark",
|
1676 |
+
plot_bgcolor='rgba(15, 25, 35, 0.8)',
|
1677 |
paper_bgcolor='rgba(0,0,0,0)',
|
1678 |
font=dict(color='white'),
|
1679 |
height=500
|
|
|
1682 |
st.plotly_chart(pred_fig, use_container_width=True)
|
1683 |
|
1684 |
# Display prediction table
|
1685 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1686 |
+
st.markdown('<h2 class="dashboard-title">مقادیر پیشبینی شده</h2>', unsafe_allow_html=True)
|
1687 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1688 |
+
|
1689 |
st.dataframe(
|
1690 |
pred_data.style.background_gradient(
|
1691 |
cmap='Greens', subset=['LAI', 'NDVI', 'NDRE']
|
|
|
1694 |
)
|
1695 |
|
1696 |
# Growth summary
|
1697 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1698 |
+
st.markdown('<h2 class="dashboard-title">خلاصه رشد</h2>', unsafe_allow_html=True)
|
1699 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1700 |
|
1701 |
# Calculate growth rates
|
1702 |
lai_growth = (pred_data['LAI'].iloc[-1] - hist_data['LAI'].iloc[-1]) / hist_data['LAI'].iloc[-1] * 100
|
|
|
1709 |
st.markdown(f"""
|
1710 |
<div class="metric-card">
|
1711 |
<div class="metric-value">{lai_growth:.1f}%</div>
|
1712 |
+
<div class="metric-label">رشد LAI (7 روز)</div>
|
1713 |
</div>
|
1714 |
""", unsafe_allow_html=True)
|
1715 |
|
|
|
1717 |
st.markdown(f"""
|
1718 |
<div class="metric-card">
|
1719 |
<div class="metric-value">{ndvi_growth:.1f}%</div>
|
1720 |
+
<div class="metric-label">رشد NDVI (7 روز)</div>
|
1721 |
</div>
|
1722 |
""", unsafe_allow_html=True)
|
1723 |
|
|
|
1725 |
st.markdown(f"""
|
1726 |
<div class="metric-card">
|
1727 |
<div class="metric-value">{ndre_growth:.1f}%</div>
|
1728 |
+
<div class="metric-label">رشد NDRE (7 روز)</div>
|
1729 |
</div>
|
1730 |
""", unsafe_allow_html=True)
|
1731 |
else:
|
1732 |
+
st.info("برای آموزش مدل LSTM و تولید پیشبینیها، روی دکمه بالا کلیک کنید.")
|
1733 |
+
|
1734 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1735 |
|
1736 |
# Data Tables Tab
|
1737 |
with tabs[3]:
|
1738 |
+
st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
|
1739 |
+
|
1740 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1741 |
+
st.markdown('<h2 class="dashboard-title">جداول داده مزارع</h2>', unsafe_allow_html=True)
|
1742 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1743 |
|
1744 |
# Create tabs for different data tables
|
1745 |
+
data_tabs = st.tabs(["اطلاعات مزرعه", "شاخصهای گیاهی", "رتبهبندی رشد"])
|
1746 |
|
1747 |
# Farm Information tab
|
1748 |
with data_tabs[0]:
|
1749 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1750 |
+
st.markdown('<h2 class="dashboard-title">جزئیات مزرعه</h2>', unsafe_allow_html=True)
|
1751 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1752 |
+
|
1753 |
st.dataframe(
|
1754 |
selected_farms,
|
1755 |
use_container_width=True
|
|
|
1757 |
|
1758 |
# Vegetation Indices tab
|
1759 |
with data_tabs[1]:
|
1760 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1761 |
+
st.markdown('<h2 class="dashboard-title">شاخصهای گیاهی بر اساس مزرعه</h2>', unsafe_allow_html=True)
|
1762 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1763 |
|
1764 |
# Generate sample vegetation indices for all farms
|
1765 |
vi_data = []
|
1766 |
|
1767 |
for _, farm in selected_farms.iterrows():
|
1768 |
# Generate random indices with some correlation to farm age
|
1769 |
+
farm_age = farm['سن']
|
1770 |
base_ndvi = min(0.5 + 0.08 * farm_age + np.random.normal(0, 0.05), 0.95)
|
1771 |
base_ndre = base_ndvi * 0.7 + np.random.normal(0, 0.03)
|
1772 |
+
|
1773 |
+
# Calculate LAI using the realistic formula: LAI = 0.57 * exp(2.33 * NDVI)
|
1774 |
+
base_lai = 0.57 * np.exp(2.33 * base_ndvi)
|
1775 |
|
1776 |
vi_data.append({
|
1777 |
+
'مزرعه': farm['نام مزرعه'],
|
1778 |
+
'سن': farm_age,
|
1779 |
+
'تنوع': farm['تنوع'],
|
1780 |
'NDVI': round(base_ndvi, 2),
|
1781 |
'NDRE': round(base_ndre, 2),
|
1782 |
'LAI': round(base_lai, 1),
|
1783 |
+
'شاخص کلروفیل': round(base_ndvi * 4 + np.random.normal(0, 0.2), 1)
|
1784 |
})
|
1785 |
|
1786 |
vi_df = pd.DataFrame(vi_data)
|
|
|
1788 |
# Display the data
|
1789 |
st.dataframe(
|
1790 |
vi_df.style.background_gradient(
|
1791 |
+
cmap='Greens', subset=['NDVI', 'NDRE', 'LAI', 'شاخص کلروفیل']
|
1792 |
),
|
1793 |
use_container_width=True
|
1794 |
)
|
1795 |
|
1796 |
# Growth Rankings tab
|
1797 |
with data_tabs[2]:
|
1798 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1799 |
+
st.markdown('<h2 class="dashboard-title">رتبهبندی رشد مزارع</h2>', unsafe_allow_html=True)
|
1800 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1801 |
|
1802 |
+
# Generate sample growth data for all farms for the last 7 days
|
1803 |
growth_data = []
|
1804 |
|
1805 |
for _, farm in selected_farms.iterrows():
|
|
|
1807 |
base_growth = np.random.uniform(2.0, 5.0)
|
1808 |
|
1809 |
# Adjust based on farm age (younger farms grow faster)
|
1810 |
+
age_factor = max(1.0, 6 - farm['سن']) / 3
|
1811 |
|
1812 |
growth_data.append({
|
1813 |
+
'مزرعه': farm['نام مزرعه'],
|
1814 |
+
'سن': farm['سن'],
|
1815 |
+
'تنوع': farm['تنوع'],
|
1816 |
+
'نرخ رشد (%)': round(base_growth * age_factor, 1),
|
1817 |
+
'شاخص سلامت': round(np.random.uniform(0.7, 1.0), 2),
|
1818 |
+
'سطح استرس': round(np.random.uniform(0.0, 0.3), 2)
|
1819 |
})
|
1820 |
|
1821 |
+
growth_df = pd.DataFrame(growth_data).sort_values('نرخ رشد (%)', ascending=False)
|
1822 |
|
1823 |
# Display the data
|
1824 |
st.dataframe(
|
1825 |
growth_df.style.background_gradient(
|
1826 |
+
cmap='Greens', subset=['نرخ رشد (%)', 'شاخص سلامت']
|
1827 |
).background_gradient(
|
1828 |
+
cmap='Reds_r', subset=['سطح استرس']
|
1829 |
),
|
1830 |
use_container_width=True
|
1831 |
)
|
|
|
1834 |
col1, col2 = st.columns(2)
|
1835 |
|
1836 |
with col1:
|
1837 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1838 |
+
st.markdown('<h2 class="dashboard-title">بهترین عملکردها</h2>', unsafe_allow_html=True)
|
1839 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1840 |
+
|
1841 |
top_farms = growth_df.head(3)
|
1842 |
|
1843 |
for i, (_, farm) in enumerate(top_farms.iterrows()):
|
1844 |
st.markdown(f"""
|
1845 |
<div class="metric-card" style="margin-bottom: 10px;">
|
1846 |
+
<h4>#{i+1}: {farm['مزرعه']}</h4>
|
1847 |
+
<div class="metric-value">{farm['نرخ رشد (%)']}%</div>
|
1848 |
+
<div class="metric-label">نرخ رشد</div>
|
1849 |
+
<p>شاخص سلامت: {farm['شاخص سلامت']} | استرس: {farm['سطح استرس']}</p>
|
1850 |
</div>
|
1851 |
""", unsafe_allow_html=True)
|
1852 |
|
1853 |
with col2:
|
1854 |
+
st.markdown('<div class="dashboard-header">', unsafe_allow_html=True)
|
1855 |
+
st.markdown('<h2 class="dashboard-title">نیازمند توجه</h2>', unsafe_allow_html=True)
|
1856 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1857 |
+
|
1858 |
bottom_farms = growth_df.tail(3).iloc[::-1]
|
1859 |
|
1860 |
for i, (_, farm) in enumerate(bottom_farms.iterrows()):
|
1861 |
st.markdown(f"""
|
1862 |
<div class="metric-card" style="margin-bottom: 10px; border-color: #ff6b6b;">
|
1863 |
+
<h4>#{i+1}: {farm['مزرعه']}</h4>
|
1864 |
+
<div class="metric-value" style="color: #ff6b6b;">{farm['نرخ رشد (%)']}%</div>
|
1865 |
+
<div class="metric-label">نرخ رشد</div>
|
1866 |
+
<p>شاخص سلامت: {farm['شاخص سلامت']} | استرس: {farm['سطح استرس']}</p>
|
1867 |
</div>
|
1868 |
""", unsafe_allow_html=True)
|
1869 |
+
|
1870 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1871 |
|
1872 |
# Run the app
|
1873 |
if __name__ == "__main__":
|
1874 |
+
main()
|
|