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"""

{text}

""", 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()