import streamlit as st
import ee
import geemap.foliumap as geemap
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
from datetime import datetime, timedelta
import os
import json
import time
from PIL import Image
import io
import base64
# Set page configuration
st.set_page_config(
page_title="داشبورد کشت و صنعت نیشکر دهخدا",
page_icon="🌱",
layout="wide",
initial_sidebar_state="expanded"
)
# Apply custom CSS for Persian font and styling
st.markdown("""
""", unsafe_allow_html=True)
# Loading animation function
def show_loading_animation(text="در حال بارگذاری..."):
with st.spinner(text):
loading_placeholder = st.empty()
loading_placeholder.markdown(f"""
""", unsafe_allow_html=True)
return loading_placeholder
# Initialize Earth Engine with service account from local file
def initialize_ee():
try:
# Path to the service account JSON file
json_key_path = "ee-esmaeilkiani13877-cfdea6eaf411.json"
# Check if file exists
if not os.path.exists(json_key_path):
st.error(f"فایل کلید JSON ({json_key_path}) یافت نشد.")
return False
# Load the service account info from the JSON file
with open(json_key_path, 'r') as f:
service_account_info = json.load(f)
# Get service account email from the JSON file
service_account = service_account_info.get('client_email')
if not service_account:
st.error("مشکل در فایل کلید JSON: client_email یافت نشد.")
return False
# Initialize Earth Engine with the credentials
credentials = ee.ServiceAccountCredentials(service_account, json_key_path)
ee.Initialize(credentials)
return True
except Exception as e:
st.error(f"خطا در اتصال به Google Earth Engine: {str(e)}")
return False
# Function to load farm data from CSV file
def load_farm_data():
try:
# Path to the farm details CSV file
csv_path = "farm_details.csv"
# Check if file exists
if not os.path.exists(csv_path):
st.error(f"فایل دادههای مزارع ({csv_path}) یافت نشد.")
return None
# Load the farm data
df = pd.read_csv(csv_path)
# Check required columns
required_columns = ['farm_name', 'longitude', 'latitude', 'days_of_week', 'variety']
missing_columns = [col for col in required_columns if col not in df.columns]
if missing_columns:
st.error(f"ستونهای زیر در فایل CSV یافت نشد: {', '.join(missing_columns)}")
return None
# Create a farm data dictionary
farm_data = {}
for _, row in df.iterrows():
# Create a small rectangle around the farm point (approx. 500m x 500m)
lon = row['longitude']
lat = row['latitude']
# Approximate conversion: 0.005 degrees ~ 500m
geometry = ee.Geometry.Rectangle([lon - 0.0025, lat - 0.0025, lon + 0.0025, lat + 0.0025])
farm_data[row['farm_name']] = {
"geometry": geometry,
"longitude": lon,
"latitude": lat,
"days_of_week": row['days_of_week'],
"variety": row['variety'],
"area_ha": np.random.randint(80, 150) # Random area between 80-150 ha for demonstration
}
return farm_data
except Exception as e:
st.error(f"خطا در بارگذاری دادههای مزارع: {str(e)}")
return None
# Function to calculate NDVI
def calculate_ndvi(image):
ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI')
return image.addBands(ndvi)
# Function to calculate NDMI (Normalized Difference Moisture Index)
def calculate_ndmi(image):
ndmi = image.normalizedDifference(['B8', 'B11']).rename('NDMI')
return image.addBands(ndmi)
# Function to calculate MSI (Moisture Stress Index)
def calculate_msi(image):
msi = image.expression(
'(B11 / B8)', {
'B11': image.select('B11'),
'B8': image.select('B8')
}
).rename('MSI')
return image.addBands(msi)
# Function to calculate CIgreen (Chlorophyll Index Green)
def calculate_cigreen(image):
cigreen = image.expression(
'(B8 / B3) - 1', {
'B8': image.select('B8'),
'B3': image.select('B3')
}
).rename('CIgreen')
return image.addBands(cigreen)
# Function to calculate NDWI (Normalized Difference Water Index)
def calculate_ndwi(image):
ndwi = image.normalizedDifference(['B3', 'B8']).rename('NDWI')
return image.addBands(ndwi)
# Function to calculate LAI (Leaf Area Index) from NDVI
def calculate_lai(ndvi_value):
if ndvi_value is None:
return None
# Simple LAI model: LAI = -ln(1 - NDVI/0.95) / 0.5
# Clipping NDVI to avoid invalid math operations
ndvi_clipped = min(max(ndvi_value, 0), 0.94)
return -np.log(1 - ndvi_clipped/0.95) / 0.5
# Function to get Sentinel-2 imagery for a date range
def get_sentinel_imagery(start_date, end_date, aoi):
# Get Sentinel-2 Level-2A data
s2 = ee.ImageCollection('COPERNICUS/S2_SR') \
.filterDate(start_date, end_date) \
.filterBounds(aoi) \
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20))
if s2.size().getInfo() == 0:
return None
# Apply all indices calculations
s2 = s2.map(calculate_ndvi) \
.map(calculate_ndmi) \
.map(calculate_msi) \
.map(calculate_cigreen) \
.map(calculate_ndwi)
# Get the median composite
median = s2.median()
return median
# Function to create a styled map
def create_map(aoi, image, center_lat, center_lon):
# Create a map centered at the specified location
m = geemap.Map()
m.centerObject(aoi, 13)
if image:
# Add NDVI layer
ndvi_vis = {
'min': 0,
'max': 0.8,
'palette': ['#d73027', '#fc8d59', '#fee08b', '#d9ef8b', '#91cf60', '#1a9850']
}
m.addLayer(image.select('NDVI'), ndvi_vis, 'NDVI')
# Add NDMI layer (hidden by default)
ndmi_vis = {
'min': -0.2,
'max': 0.4,
'palette': ['#d7191c', '#fdae61', '#ffffbf', '#abd9e9', '#2c7bb6']
}
m.addLayer(image.select('NDMI'), ndmi_vis, 'NDMI', False)
# Add MSI layer (hidden by default)
msi_vis = {
'min': 0.4,
'max': 2,
'palette': ['#1a9850', '#91cf60', '#d9ef8b', '#fee08b', '#fc8d59', '#d73027']
}
m.addLayer(image.select('MSI'), msi_vis, 'MSI (Nitrogen)', False)
# Add CIgreen layer (hidden by default)
cigreen_vis = {
'min': 1,
'max': 8,
'palette': ['#d73027', '#fc8d59', '#fee08b', '#d9ef8b', '#91cf60', '#1a9850']
}
m.addLayer(image.select('CIgreen'), cigreen_vis, 'CIgreen (Chlorophyll)', False)
# Add NDWI layer (hidden by default)
ndwi_vis = {
'min': -0.5,
'max': 0.5,
'palette': ['#d73027', '#fc8d59', '#fee08b', '#d9ef8b', '#91cf60', '#1a9850']
}
m.addLayer(image.select('NDWI'), ndwi_vis, 'NDWI', False)
# Add a legend
legend_dict = {
'NDVI (سلامت گیاه)': ['#d73027', '#fc8d59', '#fee08b', '#d9ef8b', '#91cf60', '#1a9850'],
'NDMI (رطوبت خاک)': ['#d7191c', '#fdae61', '#ffffbf', '#abd9e9', '#2c7bb6'],
'MSI (نیتروژن)': ['#1a9850', '#91cf60', '#d9ef8b', '#fee08b', '#fc8d59', '#d73027'],
'CIgreen (کلروفیل)': ['#d73027', '#fc8d59', '#fee08b', '#d9ef8b', '#91cf60', '#1a9850'],
'NDWI (آب گیاه)': ['#d73027', '#fc8d59', '#fee08b', '#d9ef8b', '#91cf60', '#1a9850']
}
m.add_legend(title="راهنمای شاخصها", legend_dict=legend_dict)
return m
# Function to get farm statistics from an image and region
def get_farm_stats(image, farm_geometry):
if not image:
return None
# Get statistics for all bands
stats = image.reduceRegion(
reducer=ee.Reducer.mean(),
geometry=farm_geometry,
scale=10,
maxPixels=1e9
).getInfo()
return stats
# Function to classify index values
def classify_index(value, index_type):
if value is None:
return "N/A", "metric-medium"
if index_type == 'NDVI':
if value < 0.2:
return "ضعیف", "metric-low"
elif value < 0.4:
return "متوسط", "metric-warning"
elif value < 0.6:
return "خوب", "metric-medium"
elif value < 0.7:
return "بسیار خوب", "metric-good"
else:
return "عالی", "metric-high"
elif index_type == 'NDMI':
if value < -0.1:
return "خشکی شدید", "metric-low"
elif value < 0:
return "خشکی نسبی", "metric-warning"
elif value < 0.1:
return "متوسط", "metric-medium"
elif value < 0.2:
return "مناسب", "metric-good"
else:
return "زیاد", "metric-high"
elif index_type == 'MSI':
if value > 1.5:
return "کمبود", "metric-low"
elif value > 1.2:
return "نیاز به بررسی", "metric-warning"
elif value > 1.0:
return "متوسط", "metric-medium"
elif value > 0.8:
return "مناسب", "metric-good"
else:
return "زیاد", "metric-high"
elif index_type == 'CIgreen':
if value < 2:
return "کم", "metric-low"
elif value < 3:
return "متوسط", "metric-warning"
elif value < 4:
return "خوب", "metric-medium"
elif value < 6:
return "بسیار خوب", "metric-good"
else:
return "عالی", "metric-high"
elif index_type == 'NDWI':
if value < -0.3:
return "خشک", "metric-low"
elif value < -0.1:
return "نسبتاً خشک", "metric-warning"
elif value < 0.1:
return "متوسط", "metric-medium"
elif value < 0.3:
return "مرطوب", "metric-good"
else:
return "بسیار مرطوب", "metric-high"
return "نامشخص", "metric-medium"
# Function to create time series data for a farm
def create_time_series(farm_geometry, days=7):
end_date = datetime.now()
start_date = end_date - timedelta(days=days)
# Format dates for Earth Engine
end_date_str = end_date.strftime('%Y-%m-%d')
start_date_str = start_date.strftime('%Y-%m-%d')
# Get Sentinel-2 collection
s2 = ee.ImageCollection('COPERNICUS/S2_SR') \
.filterDate(start_date_str, end_date_str) \
.filterBounds(farm_geometry) \
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20)) \
.map(calculate_ndvi) \
.map(calculate_ndmi) \
.map(calculate_msi) \
.map(calculate_cigreen) \
.map(calculate_ndwi)
# Get time series data
time_series = []
image_list = s2.toList(s2.size())
size = s2.size().getInfo()
for i in range(size):
image = ee.Image(image_list.get(i))
date = ee.Date(image.get('system:time_start')).format('YYYY-MM-dd').getInfo()
stats = image.reduceRegion(
reducer=ee.Reducer.mean(),
geometry=farm_geometry,
scale=10,
maxPixels=1e9
).getInfo()
time_series.append({
'date': date,
'NDVI': stats.get('NDVI'),
'NDMI': stats.get('NDMI'),
'MSI': stats.get('MSI'),
'CIgreen': stats.get('CIgreen'),
'NDWI': stats.get('NDWI'),
'LAI': calculate_lai(stats.get('NDVI', 0)) if stats.get('NDVI') else None
})
return time_series
# Function to create a time series plot
def create_time_series_plot(time_series, index_name):
if not time_series or len(time_series) == 0:
return None
df = pd.DataFrame(time_series)
fig = px.line(
df,
x='date',
y=index_name,
title=f'روند تغییرات {index_name} در بازه زمانی انتخاب شده',
markers=True
)
fig.update_layout(
plot_bgcolor='rgba(22, 28, 36, 0.8)',
paper_bgcolor='rgba(22, 28, 36, 0.8)',
font_color='white',
title_font_size=20,
title_font_family='Vazir',
xaxis_title='تاریخ',
yaxis_title=index_name,
legend_title_font_color='white',
legend_font_color='white',
xaxis=dict(
showgrid=True,
gridcolor='rgba(255, 255, 255, 0.1)'
),
yaxis=dict(
showgrid=True,
gridcolor='rgba(255, 255, 255, 0.1)'
),
hovermode='x unified',
hoverlabel=dict(
bgcolor='rgba(22, 28, 36, 0.9)',
font_size=12,
font_family='Vazir',
font_color='white'
)
)
fig.update_traces(
line=dict(width=3),
marker=dict(size=8)
)
# Add gradient fill under the line
if index_name == 'NDVI':
fig.update_traces(fill='tozeroy', fillcolor='rgba(0, 255, 136, 0.2)')
elif index_name == 'NDMI':
fig.update_traces(fill='tozeroy', fillcolor='rgba(0, 161, 255, 0.2)')
elif index_name == 'MSI':
fig.update_traces(fill='tozeroy', fillcolor='rgba(255, 152, 0, 0.2)')
elif index_name == 'CIgreen':
fig.update_traces(fill='tozeroy', fillcolor='rgba(129, 255, 0, 0.2)')
elif index_name == 'NDWI':
fig.update_traces(fill='tozeroy', fillcolor='rgba(0, 214, 255, 0.2)')
else:
fig.update_traces(fill='tozeroy', fillcolor='rgba(255, 255, 255, 0.1)')
return fig
# Function to create a comparison plot for multiple farms
def create_comparison_plot(farms_data, index_name):
if not farms_data or len(farms_data) == 0:
return None
fig = go.Figure()
colors = ['#00ff88', '#00a1ff', '#ff9800', '#ff5252', '#ae00ff', '#00e5ff']
for i, (farm_name, time_series) in enumerate(farms_data.items()):
if time_series and len(time_series) > 0:
df = pd.DataFrame(time_series)
color_idx = i % len(colors)
fig.add_trace(go.Scatter(
x=df['date'],
y=df[index_name],
mode='lines+markers',
name=farm_name,
line=dict(width=3, color=colors[color_idx]),
marker=dict(size=8, color=colors[color_idx]),
hovertemplate=f'{farm_name}: %{{y:.2f}}'
))
fig.update_layout(
title=f'مقایسه {index_name} بین مزارع',
plot_bgcolor='rgba(22, 28, 36, 0.8)',
paper_bgcolor='rgba(22, 28, 36, 0.8)',
font_color='white',
title_font_size=20,
title_font_family='Vazir',
xaxis_title='تاریخ',
yaxis_title=index_name,
legend_title_font_color='white',
legend_font_color='white',
xaxis=dict(
showgrid=True,
gridcolor='rgba(255, 255, 255, 0.1)'
),
yaxis=dict(
showgrid=True,
gridcolor='rgba(255, 255, 255, 0.1)'
),
hovermode='x unified',
hoverlabel=dict(
bgcolor='rgba(22, 28, 36, 0.9)',
font_size=12,
font_family='Vazir',
font_color='white'
),
legend=dict(
orientation='h',
y=-0.2
)
)
return fig
# Custom alert function
def custom_alert(message, alert_type="info"):
icon = "ℹ️"
if alert_type == "warning":
icon = "⚠️"
elif alert_type == "success":
icon = "✅"
elif alert_type == "error":
icon = "❌"
alert_class = f"custom-alert alert-{alert_type}"
return st.markdown(f"""
{icon}
{message}
""", unsafe_allow_html=True)
# Main function
def main():
# Display header
st.markdown('داشبورد پایش مزارع نیشکر دهخدا
', unsafe_allow_html=True)
# Initialize Earth Engine - show loading animation
loading_placeholder = show_loading_animation("در حال اتصال به Google Earth Engine...")
if not initialize_ee():
loading_placeholder.empty()
custom_alert("خطا در اتصال به Google Earth Engine. لطفاً اطمینان حاصل کنید که فایل کلید JSON در مسیر صحیح قرار دارد.", "error")
return
loading_placeholder.empty()
# Load farm data - show loading animation
loading_placeholder = show_loading_animation("در حال بارگذاری دادههای مزارع...")
farm_data = load_farm_data()
loading_placeholder.empty()
if not farm_data:
custom_alert("خطا در بارگذاری دادههای مزارع. لطفاً اطمینان حاصل کنید که فایل CSV در مسیر صحیح قرار دارد.", "error")
return
custom_alert("اتصال به Google Earth Engine و بارگذاری دادهها با موفقیت انجام شد.", "success")
# Create sidebar
with st.sidebar:
st.markdown('', unsafe_allow_html=True)
st.subheader("🔍 فیلترها و تنظیمات")
# Date selection
today = datetime.now()
end_date = st.date_input(
"تاریخ پایان",
today
)
days_back = st.slider(
"تعداد روزهای قبل",
1, 30, 7
)
start_date = end_date - timedelta(days=days_back)
# Convert to string format for Earth Engine
start_date_str = start_date.strftime('%Y-%m-%d')
end_date_str = end_date.strftime('%Y-%m-%d')
# Filter by day of week if needed
days_of_week = ['شنبه', 'یکشنبه', 'دوشنبه', 'سهشنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه']
selected_days = st.multiselect(
"روزهای هفته",
options=days_of_week,
default=days_of_week
)
# Filter farms by selected days
filtered_farms = farm_data
if selected_days and len(selected_days) < len(days_of_week):
filtered_farms = {
name: data for name, data in farm_data.items()
if any(day in data.get('days_of_week', '').split(',') for day in selected_days)
}
# Farm selection
st.subheader("🌱 انتخاب مزرعه")
selected_farms = st.multiselect(
"انتخاب مزارع",
options=list(filtered_farms.keys()),
default=[list(filtered_farms.keys())[0]] if filtered_farms else []
)
# Search for a specific farm
farm_search = st.text_input("جستجوی مزرعه")
if farm_search:
search_results = [name for name in filtered_farms.keys() if farm_search.lower() in name.lower()]
if search_results:
st.success(f"{len(search_results)} مزرعه یافت شد")
if st.button("افزودن مزارع یافت شده"):
selected_farms = list(set(selected_farms + search_results))
else:
st.warning("مزرعهای یافت نشد")
# Index selection for visualization
st.subheader("📊 انتخاب شاخص")
selected_index = st.selectbox(
"شاخص برای نمایش روی نقشه",
options=["NDVI", "NDMI", "MSI", "CIgreen", "NDWI"],
index=0
)
# Comparison options
st.subheader("🔄 مقایسه")
compare_farms = st.checkbox("مقایسه بین مزارع", value=True)
compare_index = st.selectbox(
"شاخص برای مقایسه",
options=["NDVI", "NDMI", "MSI", "CIgreen", "NDWI", "LAI"],
index=0
)
st.markdown('
', unsafe_allow_html=True)
# Main content
if not selected_farms:
custom_alert("لطفاً حداقل یک مزرعه را انتخاب کنید.", "warning")
return
# Create tabs for different views
tab1, tab2, tab3 = st.tabs(["📊 داشبورد اصلی", "🌱 جزئیات مزارع", "📈 تحلیل روند"])
with tab1:
# Create two columns for the main dashboard
col1, col2 = st.columns([2, 1])
with col1:
st.markdown('', unsafe_allow_html=True)
st.subheader("🗺️ نقشه شاخصهای کشاورزی")
# Get the first selected farm for map centering
primary_farm = selected_farms[0]
primary_geometry = farm_data[primary_farm]["geometry"]
# Create a feature collection of all selected farms
features = []
for farm_name in selected_farms:
farm_geom = farm_data[farm_name]["geometry"]
features.append(ee.Feature(farm_geom, {"name": farm_name}))
farms_fc = ee.FeatureCollection(features)
# Loading animation for map
map_loading = show_loading_animation("در حال دریافت تصاویر ماهوارهای و تولید نقشه...")
# Get Sentinel imagery
sentinel_image = get_sentinel_imagery(start_date_str, end_date_str, farms_fc)
map_loading.empty()
if sentinel_image:
# Create map
m = create_map(farms_fc, sentinel_image,
farm_data[primary_farm]["latitude"],
farm_data[primary_farm]["longitude"])
# Add farm boundaries
for i, farm_name in enumerate(selected_farms):
farm_geom = farm_data[farm_name]["geometry"]
# Use different colors for different farms
colors = ['#00ff88', '#00a1ff', '#ff9800', '#ff5252', '#ae00ff']
color_idx = i % len(colors)
m.addLayer(farm_geom, {"color": colors[color_idx]}, farm_name)
# Display the map
map_component = m.to_streamlit(height=500)
# Add download button for the map
m.to_html("temp_map.html")
with open("temp_map.html", "rb") as file:
st.download_button(
label="📥 دانلود نقشه (HTML)",
data=file,
file_name="dehkhoda_map.html",
mime="text/html"
)
else:
custom_alert("تصویر ماهوارهای برای بازه زمانی انتخاب شده یافت نشد. لطفاً بازه زمانی دیگری را انتخاب کنید.", "warning")
st.markdown('
', unsafe_allow_html=True)
with col2:
st.markdown('', unsafe_allow_html=True)
st.subheader("📊 خلاصه وضعیت مزارع")
# Get statistics for each farm
farm_stats = {}
stats_loading = show_loading_animation("در حال محاسبه شاخصهای کشاورزی...")
for farm_name in selected_farms:
farm_geom = farm_data[farm_name]["geometry"]
if sentinel_image:
stats = get_farm_stats(sentinel_image, farm_geom)
farm_stats[farm_name] = stats
stats_loading.empty()
# Display farm statistics
for farm_name, stats in farm_stats.items():
st.markdown(f"### 🌱 {farm_name}")
st.markdown(f"**مساحت:** {farm_data[farm_name]['area_ha']} هکتار")
st.markdown(f"**نوع محصول:** نیشکر")
st.markdown(f"**رقم:** {farm_data[farm_name]['variety']}")
if stats:
# Create a small progress bar for each index
def create_index_display(label, value, index_type):
if value is None:
return
status, color_class = classify_index(value, index_type)
# Calculate progress percentage based on index type and value
if index_type == 'MSI': # Lower is better for MSI
progress = max(0, min(100, (2 - value) / 2 * 100))
else: # Higher is better for others
max_val = 0.8 if index_type == 'NDVI' else 0.4 if index_type == 'NDMI' else 8 if index_type == 'CIgreen' else 0.5
progress = max(0, min(100, value / max_val * 100))
# Create HTML for the progress bar
color = '#00ff88' if color_class == 'metric-high' or color_class == 'metric-good' else \
'#ffff00' if color_class == 'metric-medium' else \
'#ff9800' if color_class == 'metric-warning' else '#ff0000'
html = f"""
{label}
{value:.2f} - {status}
"""
return st.markdown(html, unsafe_allow_html=True)
# Display indices with progress bars
create_index_display("سلامت گیاه (NDVI)", stats.get('NDVI'), 'NDVI')
create_index_display("رطوبت خاک (NDMI)", stats.get('NDMI'), 'NDMI')
create_index_display("نیتروژن (MSI)", stats.get('MSI'), 'MSI')
create_index_display("کلروفیل (CIgreen)", stats.get('CIgreen'), 'CIgreen')
create_index_display("آب غلاف (NDWI)", stats.get('NDWI'), 'NDWI')
# LAI
if stats.get('NDVI'):
lai_value = calculate_lai(stats.get('NDVI'))
st.markdown(f"**شاخص سطح برگ (LAI):** {lai_value:.2f}")
st.markdown("---")
st.markdown('
', unsafe_allow_html=True)
# Bottom row for alerts and recommendations
st.markdown('', unsafe_allow_html=True)
st.subheader("⚠️ هشدارها و توصیهها")
# Generate alerts based on farm statistics
alerts_generated = False
for farm_name, stats in farm_stats.items():
if stats:
ndvi_value = stats.get('NDVI')
ndmi_value = stats.get('NDMI')
msi_value = stats.get('MSI')
if ndvi_value and ndvi_value < 0.4:
custom_alert(f"**هشدار سلامت گیاه:** مزرعه {farm_name} دارای NDVI پایین ({ndvi_value:.2f}) است. بررسی وضعیت سلامت گیاه توصیه میشود.", "warning")
alerts_generated = True
if ndmi_value and ndmi_value < 0:
custom_alert(f"**هشدار خشکی خاک:** مزرعه {farm_name} دارای NDMI پایین ({ndmi_value:.2f}) است. آبیاری توصیه میشود.", "warning")
alerts_generated = True
if msi_value and msi_value > 1.2:
custom_alert(f"**هشدار کمبود نیتروژن:** مزرعه {farm_name} دارای MSI بالا ({msi_value:.2f}) است. کوددهی نیتروژن توصیه میشود.", "warning")
alerts_generated = True
if not alerts_generated:
custom_alert("هیچ هشدار مهمی برای مزارع انتخاب شده وجود ندارد.", "success")
st.markdown('
', unsafe_allow_html=True)
with tab2:
# Farm details tab
for farm_name in selected_farms:
st.markdown(f'', unsafe_allow_html=True)
st.subheader(f"🌱 جزئیات مزرعه {farm_name}")
farm_geom = farm_data[farm_name]["geometry"]
# Display farm details
col1, col2 = st.columns(2)
with col1:
st.markdown(f"**مساحت:** {farm_data[farm_name]['area_ha']} هکتار")
st.markdown(f"**نوع محصول:** نیشکر")
st.markdown(f"**رقم:** {farm_data[farm_name]['variety']}")
st.markdown(f"**موقعیت:** طول {farm_data[farm_name]['longitude']:.4f}، عرض {farm_data[farm_name]['latitude']:.4f}")
# Loading animation for time series
ts_loading = show_loading_animation(f"در حال استخراج سری زمانی برای مزرعه {farm_name}...")
# Get time series data for the farm
time_series = create_time_series(farm_geom, days=days_back)
ts_loading.empty()
if time_series and len(time_series) > 0:
# Create time series plot for NDVI
ndvi_fig = create_time_series_plot(time_series, 'NDVI')
if ndvi_fig:
st.plotly_chart(ndvi_fig, use_container_width=True)
# Create tabs for other indices
indices_tabs = st.tabs(['NDMI', 'MSI', 'CIgreen', 'NDWI', 'LAI'])
with indices_tabs[0]: # NDMI
ndmi_fig = create_time_series_plot(time_series, 'NDMI')
if ndmi_fig:
st.plotly_chart(ndmi_fig, use_container_width=True)
with indices_tabs[1]: # MSI
msi_fig = create_time_series_plot(time_series, 'MSI')
if msi_fig:
st.plotly_chart(msi_fig, use_container_width=True)
with indices_tabs[2]: # CIgreen
cigreen_fig = create_time_series_plot(time_series, 'CIgreen')
if cigreen_fig:
st.plotly_chart(cigreen_fig, use_container_width=True)
with indices_tabs[3]: # NDWI
ndwi_fig = create_time_series_plot(time_series, 'NDWI')
if ndwi_fig:
st.plotly_chart(ndwi_fig, use_container_width=True)
with indices_tabs[4]: # LAI
lai_values = [{'date': item['date'], 'LAI': calculate_lai(item['NDVI']) if item['NDVI'] else None} for item in time_series]
lai_df = pd.DataFrame(lai_values)
lai_fig = px.line(
lai_df,
x='date',
y='LAI',
title='روند تغییرات شاخص سطح برگ (LAI) در بازه زمانی انتخاب شده',
markers=True
)
lai_fig.update_layout(
plot_bgcolor='rgba(22, 28, 36, 0.8)',
paper_bgcolor='rgba(22, 28, 36, 0.8)',
font_color='white',
font_family='Vazir',
xaxis_title='تاریخ',
yaxis_title='LAI',
hovermode='x unified',
hoverlabel=dict(
bgcolor='rgba(22, 28, 36, 0.9)',
font_size=12,
font_family='Vazir',
font_color='white'
)
)
lai_fig.update_traces(
line=dict(width=3, color='#00ff88'),
marker=dict(size=8, color='#00ff88'),
fill='tozeroy',
fillcolor='rgba(0, 255, 136, 0.2)'
)
st.plotly_chart(lai_fig, use_container_width=True)
# Data table
with st.expander("📋 جدول دادهها"):
df = pd.DataFrame(time_series)
if 'date' in df.columns:
df = df.sort_values('date', ascending=False)
st.dataframe(df.style.format("{:.4f}"), use_container_width=True)
else:
custom_alert("دادههای سری زمانی برای این مزرعه در بازه زمانی انتخاب شده یافت نشد.", "warning")
st.markdown('
', unsafe_allow_html=True)
with tab3:
st.markdown('', unsafe_allow_html=True)
st.subheader("📈 تحلیل مقایسهای")
if compare_farms and len(selected_farms) > 1:
# Loading animation for comparison
comp_loading = show_loading_animation("در حال آمادهسازی دادههای مقایسهای...")
# Get time series data for all selected farms
farms_time_series = {}
for farm_name in selected_farms:
farm_geom = farm_data[farm_name]["geometry"]
time_series = create_time_series(farm_geom, days=days_back)
if time_series and len(time_series) > 0:
farms_time_series[farm_name] = time_series
comp_loading.empty()
if farms_time_series:
# Create comparison plot
comp_fig = create_comparison_plot(farms_time_series, compare_index)
if comp_fig:
st.plotly_chart(comp_fig, use_container_width=True)
# Calculate and display statistics
st.subheader("📊 آمار مقایسهای")
# Create a DataFrame for comparison
comparison_data = []
for farm_name, time_series in farms_time_series.items():
if time_series and len(time_series) > 0:
df = pd.DataFrame(time_series)
latest_values = df.iloc[-1] if not df.empty else None
if latest_values is not None:
comparison_data.append({
'مزرعه': farm_name,
'NDVI': latest_values.get('NDVI'),
'NDMI': latest_values.get('NDMI'),
'MSI': latest_values.get('MSI'),
'CIgreen': latest_values.get('CIgreen'),
'NDWI': latest_values.get('NDWI'),
'LAI': calculate_lai(latest_values.get('NDVI')) if latest_values.get('NDVI') else None
})
if comparison_data:
comparison_df = pd.DataFrame(comparison_data)
comparison_df = comparison_df.set_index('مزرعه')
# Display the comparison table
st.dataframe(comparison_df.style.format("{:.2f}"), use_container_width=True)
# Create a radar chart for comparing farms
if len(comparison_data) > 1 and len(comparison_data) <= 5: # Radar works best with 2-5 entities
st.subheader("🕸️ نمودار راداری مقایسه مزارع")
# Prepare data for radar chart
categories = ['NDVI', 'NDMI', 'CIgreen', 'NDWI', 'LAI']
# Normalize values between 0 and 1 for radar chart
normalized_data = comparison_df.copy()
for cat in categories:
if cat in normalized_data.columns:
if cat == 'MSI': # Lower is better
min_val = normalized_data[cat].min()
max_val = normalized_data[cat].max()
if max_val > min_val:
normalized_data[cat] = 1 - ((normalized_data[cat] - min_val) / (max_val - min_val))
else: # Higher is better
min_val = normalized_data[cat].min()
max_val = normalized_data[cat].max()
if max_val > min_val:
normalized_data[cat] = (normalized_data[cat] - min_val) / (max_val - min_val)
# Create radar chart
fig = go.Figure()
colors = ['#00ff88', '#00a1ff', '#ff9800', '#ff5252', '#ae00ff']
for i, farm in enumerate(normalized_data.index):
values = normalized_data.loc[farm, categories].fillna(0).tolist()
# Close the radar by repeating the first value
values.append(values[0])
fig.add_trace(go.Scatterpolar(
r=values,
theta=categories + [categories[0]],
fill='toself',
name=farm,
line=dict(color=colors[i % len(colors)], width=3),
fillcolor=colors[i % len(colors)] + '20' # 20 is hex for 12% opacity
))
fig.update_layout(
polar=dict(
radialaxis=dict(
visible=True,
range=[0, 1],
showticklabels=False,
gridcolor='rgba(255, 255, 255, 0.2)'
),
angularaxis=dict(
gridcolor='rgba(255, 255, 255, 0.2)'
),
bgcolor='rgba(22, 28, 36, 0.8)'
),
paper_bgcolor='rgba(22, 28, 36, 0.8)',
font_color='white',
font_family='Vazir',
legend=dict(
orientation='h',
y=-0.1
),
height=500
)
st.plotly_chart(fig, use_container_width=True)
# Find the best and worst farms for each index
st.subheader("🏆 رتبهبندی مزارع")
indices = ['NDVI', 'NDMI', 'MSI', 'CIgreen', 'NDWI', 'LAI']
for idx in indices:
if idx in comparison_df.columns and not comparison_df[idx].isna().all():
if idx == 'MSI': # Lower is better for MSI
best_farm = comparison_df[idx].idxmin()
worst_farm = comparison_df[idx].idxmax()
best_val = comparison_df.loc[best_farm, idx]
worst_val = comparison_df.loc[worst_farm, idx]
else: # Higher is better for other indices
best_farm = comparison_df[idx].idxmax()
worst_farm = comparison_df[idx].idxmin()
best_val = comparison_df.loc[best_farm, idx]
worst_val = comparison_df.loc[worst_farm, idx]
st.markdown(f"""
{idx}:
بهترین - {best_farm} ({best_val:.2f})
ضعیفترین - {worst_farm} ({worst_val:.2f})
""", unsafe_allow_html=True)
else:
custom_alert("دادههای کافی برای مقایسه مزارع یافت نشد.", "warning")
else:
custom_alert("برای مقایسه، لطفاً بیش از یک مزرعه را انتخاب کنید و گزینه 'مقایسه بین مزارع' را فعال نمایید.", "info")
st.markdown('
', unsafe_allow_html=True)
# Footer
st.markdown("""
داشبورد پایش مزارع نیشکر دهخدا | طراحی شده با ❤️
© 1403 - کلیه حقوق محفوظ است
""", unsafe_allow_html=True)
if __name__ == "__main__":
main()