diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,3288 +1,2176 @@ import streamlit as st -import geemap -import geemap.foliumap as geemap -import ee -from datetime import datetime, timedelta -import warnings import pandas as pd +import numpy as np import plotly.express as px import plotly.graph_objects as go -from sklearn.ensemble import RandomForestRegressor -from sklearn.model_selection import train_test_split -from sklearn.metrics import mean_squared_error, r2_score -import matplotlib.pyplot as plt -import seaborn as sns -import altair as alt from datetime import datetime, timedelta -import requests -import json -import numpy as np import folium from streamlit_folium import folium_static -import ee -import os -import json -import time -from datetime import datetime, timedelta -import plotly.express as px -import plotly.graph_objects as go -from PIL import Image +import io import base64 -from io import BytesIO +from PIL import Image import matplotlib.pyplot as plt import seaborn as sns -import altair as alt -from streamlit_option_menu import option_menu -from streamlit_lottie import st_lottie -import requests -import hydralit_components as hc -from streamlit_extras.colored_header import colored_header -from streamlit_extras.metric_cards import style_metric_cards -from streamlit_extras.chart_container import chart_container -from streamlit_extras.add_vertical_space import add_vertical_space -from streamlit_card import card -import pydeck as pdk -import math +import ee +import geemap.foliumap as geemap +from sklearn.ensemble import RandomForestRegressor +from sklearn.model_selection import train_test_split +from sklearn.metrics import mean_squared_error +import os -# Page configuration with custom theme +# تنظیمات صفحه st.set_page_config( - page_title="سامانه هوشمند پایش مزارع نیشکر دهخدا", - page_icon="🌿", + page_title="سامانه تحلیل داده‌های کشاورزی", + page_icon="🌱", layout="wide", initial_sidebar_state="expanded" ) -# Custom CSS for modern UI +# تنظیم استایل و فونت st.markdown(""" +""", unsafe_allow_html=True) + +# تابع برای اتصال به Google Earth Engine +def initialize_ee(): + try: + ee.Initialize() + return True + except Exception as e: + st.error(f"خطا در اتصال به Google Earth Engine: {e}") + return False + +# تابع برای محاسبه شاخص‌های گیاهی +def calculate_indices(image_date, geometry): + try: + # تاریخ شروع و پایان برای جستجوی تصاویر + start_date = (datetime.strptime(image_date, '%Y-%m-%d') - timedelta(days=5)).strftime('%Y-%m-%d') + end_date = (datetime.strptime(image_date, '%Y-%m-%d') + timedelta(days=5)).strftime('%Y-%m-%d') + + # دریافت تصویر Sentinel-2 + s2 = ee.ImageCollection('COPERNICUS/S2_SR') \ + .filterDate(start_date, end_date) \ + .filterBounds(geometry) \ + .sort('CLOUDY_PIXEL_PERCENTAGE') \ + .first() + + # محاسبه شاخص‌ها + ndvi = s2.normalizedDifference(['B8', 'B4']).rename('NDVI') + ndwi = s2.normalizedDifference(['B3', 'B8']).rename('NDWI') + evi = s2.expression( + '2.5 * ((NIR - RED) / (NIR + 6 * RED - 7.5 * BLUE + 1))', + { + 'NIR': s2.select('B8'), + 'RED': s2.select('B4'), + 'BLUE': s2.select('B2') + } + ).rename('EVI') + ndmi = s2.normalizedDifference(['B8', 'B11']).rename('NDMI') + + # محاسبه LAI (Leaf Area Index) - تقریبی + lai = ndvi.expression('3.618 * EVI - 0.118', {'EVI': evi}).rename('LAI') + + # محاسبه CHL (Chlorophyll) - تقریبی + chl = s2.expression( + '(B5 / B4)', + { + 'B5': s2.select('B5'), + 'B4': s2.select('B4') + } + ).rename('CHL') + + # ترکیب همه شاخص‌ها + indices = ee.Image.cat([ndvi, ndwi, evi, ndmi, lai, chl]) + + return indices + except Exception as e: + st.error(f"خطا در محاسبه شاخص‌ها: {e}") + return None + +# تابع برای ایجاد لینک دانلود +def get_download_link(df, filename, text): + csv = df.to_csv(index=False) + b64 = base64.b64encode(csv.encode()).decode() + href = f'{text}' + return href + +# تابع برای ایجاد PDF از نمودارها و جداول +def create_report_pdf(figs, tables, title): + buffer = io.BytesIO() + + # ایجاد PDF با matplotlib + from matplotlib.backends.backend_pdf import PdfPages + + with PdfPages(buffer) as pdf: + # صفحه عنوان + plt.figure(figsize=(8.5, 11)) + plt.text(0.5, 0.5, title, ha='center', va='center', fontsize=24) + plt.axis('off') + pdf.savefig() + plt.close() + + # افزودن نمودارها + for fig in figs: + pdf.savefig(fig) + plt.close(fig) + + # افزودن جداول + for i, table in enumerate(tables): + fig, ax = plt.subplots(figsize=(8.5, 11)) + ax.axis('off') + ax.table( + cellText=table.values, + colLabels=table.columns, + loc='center', + cellLoc='center' + ) + pdf.savefig(fig) + plt.close(fig) - h3 { - font-size: 1.4rem; - font-weight: 600; + buffer.seek(0) + return buffer + +# تابع برای نمایش کارت‌های اطلاعاتی +def display_metric_card(title, value, icon="📊"): + st.markdown(f""" +
+
{icon}
+
{value}
+
{title}
+
+ """, unsafe_allow_html=True) + +# تابع برای بارگذاری داده‌های نمونه +def load_sample_data(): + # داده‌های نمونه برای مختصات مزارع + farm_data = { + 'farm_id': list(range(1, 21)), + 'name': [f'مزرعه {i}' for i in range(1, 21)], + 'lat': np.random.uniform(32.0, 36.0, 20), + 'lon': np.random.uniform(48.0, 54.0, 20), + 'area': np.random.uniform(5, 100, 20), + 'crop_type': np.random.choice(['گندم', 'جو', 'ذرت', 'برنج', 'پنبه'], 20), + 'height': np.random.uniform(0.5, 2.5, 20), + 'humidity': np.random.uniform(30, 80, 20), + 'last_update': [(datetime.now() - timedelta(days=np.random.randint(1, 30))).strftime('%Y-%m-%d') for _ in range(20)] } - /* Custom selectbox and inputs */ - .stSelectbox > div > div, .stNumberInput > div > div { - background-color: var(--card-bg-color); - border-radius: var(--border-radius); - border: 1px solid #e0e0e0; + # داده‌های نمونه برای تقسیم‌بندی مزارع بر اساس روزهای هفته + days = ['شنبه', 'یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه'] + weekly_data = { + 'farm_id': [], + 'day': [] } - .stSelectbox > div > div:hover, .stNumberInput > div > div:hover { - border-color: var(--primary-color); - } + for farm_id in range(1, 21): + assigned_days = np.random.choice(days, np.random.randint(1, 4), replace=False) + for day in assigned_days: + weekly_data['farm_id'].append(farm_id) + weekly_data['day'].append(day) - /* Animation for loading elements */ - @keyframes fadeIn { - from { opacity: 0; transform: translateY(10px); } - to { opacity: 1; transform: translateY(0); } - } + return pd.DataFrame(farm_data), pd.DataFrame(weekly_data) + +# تابع برای ایجاد نقشه مزارع +def create_farm_map(df, selected_farms=None): + # ایجاد نقشه با مرکزیت میانگین مختصات + center_lat = df['lat'].mean() + center_lon = df['lon'].mean() + m = folium.Map(location=[center_lat, center_lon], zoom_start=7) + + # اضافه کردن لایه‌های مختلف + folium.TileLayer('openstreetmap').add_to(m) + folium.TileLayer('Stamen Terrain').add_to(m) + folium.TileLayer('Stamen Toner').add_to(m) + folium.TileLayer('Stamen Watercolor').add_to(m) + folium.TileLayer('CartoDB positron').add_to(m) + + # اضافه کردن کنترل لایه‌ها + folium.LayerControl().add_to(m) + + # اضافه کردن مارکرها برای هر مزرعه + for idx, row in df.iterrows(): + color = 'green' + if selected_farms is not None and row['farm_id'] in selected_farms: + color = 'red' + + popup_html = f""" +
+

{row['name']}

+

شناسه: {row['farm_id']}

+

نوع محصول: {row['crop_type']}

+

مساحت: {row['area']:.2f} هکتار

+

ارتفاع: {row['height']:.2f} متر

+

رطوبت: {row['humidity']:.1f}%

+

آخرین بروزرسانی: {row['last_update']}

+
+ """ + + folium.Marker( + location=[row['lat'], row['lon']], + popup=folium.Popup(popup_html, max_width=300), + tooltip=row['name'], + icon=folium.Icon(color=color, icon='leaf', prefix='fa') + ).add_to(m) - .fade-in { - animation: fadeIn 0.5s ease-in; - } + return m + +# تابع برای ایجاد نمودارهای تحلیلی +def create_analysis_charts(df): + # نمودار توزیع نوع محصول + fig_crop = px.pie( + df, + names='crop_type', + title='توزیع انواع محصولات', + color_discrete_sequence=px.colors.sequential.Greens + ) + fig_crop.update_layout( + font_family="Vazirmatn", + title_font_family="Vazirmatn", + title_font_size=20 + ) - /* Animation for metrics */ - @keyframes scaleIn { - from { transform: scale(0.9); opacity: 0; } - to { transform: scale(1); opacity: 1; } - } + # نمودار ارتفاع محصولات + fig_height = px.box( + df, + x='crop_type', + y='height', + color='crop_type', + title='توزیع ارتفاع محصولات بر اساس نوع', + labels={'crop_type': 'نوع محصول', 'height': 'ارتفاع (متر)'}, + color_discrete_sequence=px.colors.sequential.Greens + ) + fig_height.update_layout( + font_family="Vazirmatn", + title_font_family="Vazirmatn", + title_font_size=20 + ) - .metric-card { - animation: scaleIn 0.5s ease-out; - } + # نمودار رطوبت محصولات + fig_humidity = px.scatter( + df, + x='area', + y='humidity', + color='crop_type', + size='height', + hover_name='name', + title='رابطه بین مساحت و رطوبت مزارع', + labels={'area': 'مساحت (هکتار)', 'humidity': 'رطوبت (%)'}, + color_discrete_sequence=px.colors.sequential.Greens + ) + fig_humidity.update_layout( + font_family="Vazirmatn", + title_font_family="Vazirmatn", + title_font_size=20 + ) - /* Tooltip styling */ - .tooltip { - position: relative; - display: inline-block; - } + return fig_crop, fig_height, fig_humidity + +# تابع برای ایجاد مدل پیش‌بینی +def create_prediction_model(df): + # ایجاد داده‌های نمونه برای سری زمانی + dates = pd.date_range(end=datetime.now(), periods=90) + farm_ids = df['farm_id'].unique() + + time_series_data = [] + for farm_id in farm_ids: + base_ndvi = np.random.uniform(0.3, 0.8) + base_height = np.random.uniform(0.2, 2.0) + + for date in dates: + # شبیه‌سازی رشد با الگوی سینوسی + روند + day_of_year = date.dayofyear + seasonal_factor = np.sin(day_of_year / 365 * 2 * np.pi) * 0.2 + trend_factor = date.dayofyear / 365 * 0.3 + + ndvi = base_ndvi + seasonal_factor + trend_factor + np.random.normal(0, 0.05) + ndvi = max(0, min(1, ndvi)) # محدود کردن به بازه 0 تا 1 + + height = base_height + seasonal_factor * 2 + trend_factor + np.random.normal(0, 0.1) + height = max(0, height) # ارتفاع نمی‌تواند منفی باشد + + time_series_data.append({ + 'farm_id': farm_id, + 'date': date, + 'ndvi': ndvi, + 'height': height, + 'day_of_year': day_of_year, + 'month': date.month + }) - .tooltip .tooltiptext { - visibility: hidden; - width: 180px; - background-color: #333; - color: #fff; - text-align: center; - border-radius: 6px; - padding: 8px; - position: absolute; - z-index: 1; - bottom: 125%; - left: 50%; - margin-left: -90px; - opacity: 0; - transition: opacity 0.3s; - font-size: 0.85rem; - box-shadow: 0 2px 10px rgba(0,0,0,0.2); - } + time_df = pd.DataFrame(time_series_data) - .tooltip .tooltiptext::after { - content: ""; - position: absolute; - top: 100%; - left: 50%; - margin-left: -5px; - border-width: 5px; - border-style: solid; - border-color: #333 transparent transparent transparent; - } + # ایجاد ویژگی‌ها برای مدل + X = time_df[['ndvi', 'day_of_year', 'month']].values + y = time_df['height'].values - .tooltip:hover .tooltiptext { - visibility: visible; - opacity: 1; - } + # تقسیم داده‌ها به آموزش و آزمون + X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) - /* Data table styling */ - .dataframe { - width: 100%; - border-collapse: collapse; - margin-bottom: 1rem; - border-radius: var(--border-radius); - overflow: hidden; - box-shadow: var(--box-shadow); - } + # آموزش مدل + model = RandomForestRegressor(n_estimators=100, random_state=42) + model.fit(X_train, y_train) - .dataframe th { - background-color: var(--primary-light); - color: white; - padding: 12px 15px; - text-align: right; - font-weight: 500; - } + # ارزیابی مدل + y_pred = model.predict(X_test) + mse = mean_squared_error(y_test, y_pred) - .dataframe td { - padding: 10px 15px; - border-bottom: 1px solid #eee; - } + # ایجاد نمودار برای نمایش نتایج + results_df = pd.DataFrame({ + 'Actual': y_test, + 'Predicted': y_pred + }) - .dataframe tr:nth-child(even) { - background-color: #f9f9f9; - } + fig = px.scatter( + results_df, + x='Actual', + y='Predicted', + title=f'مقایسه مقادیر واقعی و پیش‌بینی شده (MSE: {mse:.4f})', + labels={'Actual': 'مقدار واقعی', 'Predicted': 'مقدار پیش‌بینی شده'} + ) - .dataframe tr:hover { - background-color: #f1f1f1; - } + fig.add_shape( + type='line', + x0=results_df['Actual'].min(), + y0=results_df['Actual'].min(), + x1=results_df['Actual'].max(), + y1=results_df['Actual'].max(), + line=dict(color='red', dash='dash') + ) - /* Progress bars */ - .stProgress > div > div > div > div { - background-color: var(--primary-color); - } + fig.update_layout( + font_family="Vazirmatn", + title_font_family="Vazirmatn", + title_font_size=20 + ) - /* Footer styling */ - footer { - position: fixed; - left: 0; - bottom: 0; - width: 100%; - background: linear-gradient(90deg, var(--primary-dark) 0%, var(--primary-color) 100%); - color: white; - text-align: center; - padding: 12px 0; - box-shadow: 0 -2px 10px rgba(0,0,0,0.1); - z-index: 999; - } + # ایجاد نمودار سری زمانی + farm_id = farm_ids[0] # انتخاب اولین مزرعه برای نمایش + farm_data = time_df[time_df['farm_id'] == farm_id] - /* Alert boxes */ - .alert-box { - padding: 15px; - margin-bottom: 20px; - border-radius: var(--border-radius); - display: flex; - align-items: center; - } + fig_ts = px.line( + farm_data, + x='date', + y=['ndvi', 'height'], + title=f'سری زمانی NDVI و ارتفاع برای مزرعه {farm_id}', + labels={'date': 'تاریخ', 'value': 'مقدار', 'variable': 'متغیر'} + ) - .alert-box.info { - background-color: #e3f2fd; - border-left: 4px solid #2196f3; - color: #0d47a1; - } + fig_ts.update_layout( + font_family="Vazirmatn", + title_font_family="Vazirmatn", + title_font_size=20, + yaxis_title='مقدار' + ) - .alert-box.warning { - background-color: #fff8e1; - border-left: 4px solid #ffc107; - color: #ff6f00; - } + return model, fig, fig_ts, time_df + +# بارگذاری داده‌های نمونه +farm_df, weekly_df = load_sample_data() + +# منوی اصلی +st.title("🌱 سامانه تحلیل داده‌های کشاورزی") + +# منوی افقی +tabs = st.tabs([ + "📊 داشبورد", + "🗺️ نقشه مزارع", + "📝 ورود اطلاعات", + "📈 تحلیل داده‌ها", + "📋 گزارش‌گیری", + "⚙️ تنظیمات" +]) + +# تب داشبورد +with tabs[0]: + st.header("داشبورد مدیریت مزارع") - .alert-box.success { - background-color: #e8f5e9; - border-left: 4px solid #4caf50; - color: #1b5e20; - } + # کارت‌های اطلاعاتی + col1, col2, col3, col4 = st.columns(4) - .alert-box.error { - background-color: #ffebee; - border-left: 4px solid #f44336; - color: #b71c1c; - } + with col1: + display_metric_card("تعداد مزارع", len(farm_df), "🌾") - .alert-icon { - margin-right: 15px; - font-size: 24px; - } + with col2: + active_farms = len(farm_df[farm_df['last_update'] > (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d')]) + display_metric_card("مزارع فعال", active_farms, "✅") - /* Custom badges */ - .badge { - display: inline-block; - padding: 5px 10px; - border-radius: 15px; - font-size: 0.8rem; - font-weight: 500; - margin-right: 5px; - } + with col3: + avg_height = farm_df['height'].mean() + display_metric_card("میانگین ارتفاع", f"{avg_height:.2f} متر", "📏") - .badge.primary { - background-color: var(--primary-light); - color: white; - } + with col4: + avg_humidity = farm_df['humidity'].mean() + display_metric_card("میانگین رطوبت", f"{avg_humidity:.1f}%", "💧") - .badge.secondary { - background-color: #90a4ae; - color: white; - } + st.markdown("---") - .badge.success { - background-color: #66bb6a; - color: white; - } + # نمودارهای تحلیلی + st.subheader("تحلیل کلی مزارع") - .badge.warning { - background-color: #ffa726; - color: white; - } + col1, col2 = st.columns(2) - .badge.danger { - background-color: #ef5350; - color: white; - } + with col1: + fig_crop, fig_height, fig_humidity = create_analysis_charts(farm_df) + st.plotly_chart(fig_crop, use_container_width=True) + st.plotly_chart(fig_height, use_container_width=True) - .badge.info { - background-color: #29b6f6; - color: white; - } + with col2: + st.plotly_chart(fig_humidity, use_container_width=True) + + # جدول اطلاعات مزارع + st.subheader("اطلاعات مزارع") + st.dataframe( + farm_df[['farm_id', 'name', 'crop_type', 'area', 'height', 'humidity', 'last_update']], + column_config={ + "farm_id": "شناسه", + "name": "نام مزرعه", + "crop_type": "نوع محصول", + "area": st.column_config.NumberColumn("مساحت (هکتار)", format="%.2f"), + "height": st.column_config.NumberColumn("ارتفاع (متر)", format="%.2f"), + "humidity": st.column_config.NumberColumn("رطوبت (%)", format="%.1f"), + "last_update": "آخرین بروزرسانی" + }, + hide_index=True + ) + +# تب نقشه مزارع +with tabs[1]: + st.header("نقشه مزارع") - /* Custom chart container */ - .chart-container { - background-color: white; - border-radius: var(--border-radius); - padding: 15px; - box-shadow: var(--box-shadow); - margin-bottom: 20px; - } + col1, col2 = st.columns([1, 3]) - /* Responsive adjustments */ - @media (max-width: 768px) { - .metric-value { - font-size: 1.8rem; - } + with col1: + st.subheader("فیلتر مزارع") + + # فیلتر بر اساس روز هفته + days = ['همه روزها', 'شنبه', 'یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه'] + selected_day = st.selectbox("انتخاب روز هفته", days) + + # فیلتر بر اساس نوع محصول + crop_types = ['همه محصولات'] + list(farm_df['crop_type'].unique()) + selected_crop = st.selectbox("انتخاب نوع محصول", crop_types) + + # فیلتر بر اساس مساحت + min_area, max_area = float(farm_df['area'].min()), float(farm_df['area'].max()) + area_range = st.slider( + "محدوده مساحت (هکتار)", + min_value=min_area, + max_value=max_area, + value=(min_area, max_area) + ) - .metric-label { - font-size: 0.9rem; - } + # اعمال فیلترها + filtered_df = farm_df.copy() - h1 { - font-size: 1.8rem; - } + if selected_day != 'همه روزها': + day_farms = weekly_df[weekly_df['day'] == selected_day]['farm_id'].tolist() + filtered_df = filtered_df[filtered_df['farm_id'].isin(day_farms)] - h2 { - font-size: 1.5rem; - } + if selected_crop != 'همه محصولات': + filtered_df = filtered_df[filtered_df['crop_type'] == selected_crop] - h3 { - font-size: 1.2rem; - } - } - -""", unsafe_allow_html=True) - -# Add helper functions for UI components -def alert_box(message, type="info"): - """ - Create a beautiful alert box with an icon. - - Parameters: - - message: The message to display - - type: The type of alert (info, warning, success, error) - """ - icons = { - "info": "ℹ️", - "warning": "⚠️", - "success": "✅", - "error": "❌" - } - - icon = icons.get(type, icons["info"]) + filtered_df = filtered_df[(filtered_df['area'] >= area_range[0]) & (filtered_df['area'] <= area_range[1])] + + st.write(f"تعداد مزارع نمایش داده شده: {len(filtered_df)}") - st.markdown(f""" -
-
{icon}
-
{message}
-
- """, unsafe_allow_html=True) - -def badge(text, type="primary"): - """ - Create a colored badge. - - Parameters: - - text: The text to display - - type: The type of badge (primary, secondary, success, warning, danger, info) - """ - return f'{text}' + with col2: + # نمایش نقشه + if len(filtered_df) > 0: + farm_map = create_farm_map(filtered_df) + folium_static(farm_map, width=800, height=500) + else: + st.warning("هیچ مزرعه‌ای با فیلترهای انتخاب شده یافت نشد.") -def status_indicator(status, text): - """ - Create a status indicator with a colored dot and text. - - Parameters: - - status: The status (excellent, good, moderate, fair, poor, critical) - - text: The text to display - """ - colors = { - "excellent": "#1b5e20", # Dark green - "good": "#43a047", # Green - "moderate": "#7cb342", # Light green - "fair": "#c0ca33", # Lime - "poor": "#ffb300", # Amber - "critical": "#e53935", # Red - "stable": "#29b6f6", # Light blue - "improving": "#00c853", # Bright green - "declining": "#ff5722" # Deep orange - } +# تب ورود اطلاعات +with tabs[2]: + st.header("ورود و آپلود اطلاعات") - color = colors.get(status.lower(), "#757575") # Default gray + input_tabs = st.tabs(["ورود دستی", "آپلود فایل"]) - return f""" -
- - {text} -
- """ - -def dashboard_card(title, content): - """ - Create a dashboard card with a title and content. - - Parameters: - - title: The card title - - content: The HTML content to display inside the card - """ - return f""" -
-

{title}

- {content} -
- """ - -# Function to create animated number counters -def animated_counter(value, label, prefix="", suffix="", color="#2e7d32"): - """ - Create an animated counter with a label. - - Parameters: - - value: The numeric value to display - - label: The label text - - prefix: Optional prefix (e.g., "$") - - suffix: Optional suffix (e.g., "%") - - color: The color of the value - """ - return f""" -
-
{prefix}{value}{suffix}
-
{label}
-
- """ - -# Function to create a progress bar with label -def custom_progress(value, label, min_value=0, max_value=100, color="#2e7d32"): - """ - Create a custom progress bar with a label. - - Parameters: - - value: Current value - - label: Label text - - min_value: Minimum value (default: 0) - - max_value: Maximum value (default: 100) - - color: Bar color - """ - # Calculate percentage - percentage = min(100, max(0, ((value - min_value) / (max_value - min_value)) * 100)) - - return f""" -
-
- {label} - {value} / {max_value} -
-
-
-
-
- """ - -# Initialize Earth Engine -@st.cache_resource -def initialize_earth_engine(): - try: - service_account = 'dehkhodamap-e9f0da4ce9f6514021@ee-esmaeilkiani13877.iam.gserviceaccount.com' - credentials_dict = { - "type": "service_account", - "project_id": "ee-esmaeilkiani13877", - "private_key_id": "cfdea6eaf4115cb6462626743e4b15df85fd0c7f", - "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCjeOvgKi+gWK6k\n2/0RXOA3LAo51DVxA1ja9v0qFOn4FNOStxkwlKvcK8yDQNb53FPORHFIUHvev3y7\niHr/UEUqnn5Rzjbf0k3qWB/fS377/UP4VznMsFpKiHNxCBtaNS8KLk6Rat6Y7Xfm\nJfpSU7ZjYZmVc81M/7iFofGUSJoHYpxhyt3rjp53huxJNNW5e12TFnLkyg1Ja/9X\nGMTt+vjVcO4XhQCIlaGVdSKS2sHlHgzpzE6KtuUKjDMEBqPkWF4xc16YavYltwPd\nqULCu2/t6dczhYL4NEFj8wL+KJqOojfsyoWmzqPFx1Bbxk4BVPk/lslq9+m9p5kq\nSCG0/9W9AgMBAAECggEAEGchw+x3uu8rFv+79PIMzXxtyj+w3RYo5E/EN2TB1VLB\nqAcXT/ibBgyfCMyIxamF/zx+4XKx+zfbnDWlodi8F/qvUiYO+4ZuqwUMras1orNX\nDqQx+If5h2EJtF3L4NFVVwAuggjnLREm5sEIzRn5Qx+X+ZcVEpTWPxJw2yAt1G+3\nk311KqD+zR7jQfchXU4xQQ1ZoHkdAJ/DPGun6x+HUOq7Gus73A6IzLp12ZoiHN3n\nkY+lG8cMs039QAe/OhZFEo5I9cNSaI688HmsLRivZ26WoPEnwcN0MHQGtXqGmMUI\nCcpgJqllqdWMuBlYcpSadn7rZzPujSlzIxkvieLeAQKBgQDNTYUWZdGbA2sHcBpJ\nrqKwDYF/AwZtjx+hXHVBRbR6DJ1bO2P9n51ioTMP/H9K61OBAMZ7w71xJ2I+9Snv\ncYumPWoiUwiOhTh3O7nYz6mR7sK0HuUCZfYdaxJVnLgNCgj+w9AxYnkzOyL9/QvJ\nknrlMKf4H59NbapBqy5spilq1QKBgQDL1wkGHhoTuLb5Xp3X3CX4S7WMke4T01bO\nPpMmlewVgH5lK5wTcZjB8QRO2QFQtUZTP/Ghv6ZH4h/3P9/ZIF3hV5CSsUkr/eFf\nMY+fQL1K/puwfZbSDcH1GtDToOyoLFIvPXBJo0Llg/oF2TK1zGW3cPszeDf/Tm6x\nUwUMw2BjSQKBgEJzAMyLEBi4NoAlzJxkpcuN04gkloQHexljL6B8yzlls9i/lFGW\nw/4UZs6ZzymUmWZ7tcKBTGO/d5EhEP2rJqQb5KpPbcmTXP9amYCPVjchrGtYRI9O\nKSbEbR7ApuGxic/L2Sri0I/AaEcFDDel7ZkY8oTg11LcV+sBWPlZnrYxAoGBALXj\n/DlpQvu2KA/9TfwAhiE57Zax4S/vtdX0IHqd7TyCnEbK00rGYvksiBuTqIjMOSSw\nOn2K9mXOcZe/d4/YQe2CpY9Ag3qt4R2ArBf/POpep66lYp+thxWgCBfP0V1/rxZY\nTIppFJiZW9E8LvPqoBlAx+b1r4IyCrRQ0IDDFo+BAoGBAMCff4XKXHlV2SDOL5uh\nV/f9ApEdF4leuo+hoMryKuSQ9Y/H0A/Lzw6KP5FLvVtqc0Kw2D1oLy8O72a1xwfY\n8dpZMNzKAWWS7viN0oC+Ebj2Foc2Mn/J6jdhtP/YRLTqvoTWCa2rVcn4R1BurMIf\nLa4DJE9BagGdVNTDtynBhKhZ\n-----END PRIVATE KEY-----\n", - "client_email": "dehkhodamap-e9f0da4ce9f6514021@ee-esmaeilkiani13877.iam.gserviceaccount.com", - "client_id": "113062529451626176784", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/dehkhodamap-e9f0da4ce9f6514021%40ee-esmaeilkiani13877.iam.gserviceaccount.com", - "universe_domain": "googleapis.com" - } - - credentials_file = 'ee-esmaeilkiani13877-cfdea6eaf411.json' - with open(credentials_file, 'w') as f: - json.dump(credentials_dict, f) + with input_tabs[0]: + st.subheader("ورود اطلاعات دستی") - credentials = ee.ServiceAccountCredentials(service_account, credentials_file) - ee.Initialize(credentials) - - os.remove(credentials_file) + col1, col2 = st.columns(2) - return True - except Exception as e: - st.error(f"خطا در اتصال به Earth Engine: {e}") - return False - -# Load data -@st.cache_data -def load_farm_data(): - try: - df = pd.read_csv("پایگاه داده (1).csv") - return df - except Exception as e: - st.error(f"خطا در بارگذاری داده‌های مزارع: {e}") - return pd.DataFrame() - -@st.cache_data -def load_coordinates_data(): - try: - df = pd.read_csv("farm_coordinates.csv") - return df - except Exception as e: - st.error(f"خطا در بارگذاری داده‌های مختصات: {e}") - return pd.DataFrame() - -# Load animation JSON -@st.cache_data -def load_lottie_url(url: str): - r = requests.get(url) - if r.status_code != 200: - return None - return r.json() - -# Function to get weather data -def get_weather_data(lat, lon, api_key): - """ - Get comprehensive weather data from OpenWeatherMap API including current conditions, - forecast, and historical data for agricultural analysis. - - Parameters: - - lat: Latitude - - lon: Longitude - - api_key: OpenWeatherMap API key - - Returns: - - Dictionary with weather data - """ - if not api_key: - # Return mock data if API key is not available - return { - "temp": 28.5, - "temp_min": 24.2, - "temp_max": 32.8, - "humidity": 65, - "pressure": 1012, - "wind_speed": 3.5, - "wind_direction": 180, - "clouds": 25, - "rain": 0, - "description": "Partly cloudy", - "icon": "03d", - "forecast": [ - {"date": (datetime.now() + timedelta(days=i)).strftime("%Y-%m-%d"), - "temp": 28.5 + np.random.uniform(-3, 3), - "humidity": 65 + np.random.uniform(-10, 10), - "rain": max(0, np.random.uniform(-0.5, 2) if i % 3 == 0 else 0)} - for i in range(1, 8) - ] - } - - try: - # Current weather data - current_url = f"https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&units=metric&appid={api_key}" - current_response = requests.get(current_url) - current_data = current_response.json() - - # Forecast data (5 days / 3 hours) - forecast_url = f"https://api.openweathermap.org/data/2.5/forecast?lat={lat}&lon={lon}&units=metric&appid={api_key}" - forecast_response = requests.get(forecast_url) - forecast_data = forecast_response.json() - - # Process current weather - if current_response.status_code == 200: - weather = { - "temp": current_data["main"]["temp"], - "temp_min": current_data["main"]["temp_min"], - "temp_max": current_data["main"]["temp_max"], - "feels_like": current_data["main"]["feels_like"], - "humidity": current_data["main"]["humidity"], - "pressure": current_data["main"]["pressure"], - "wind_speed": current_data["wind"]["speed"], - "wind_direction": current_data["wind"]["deg"], - "clouds": current_data["clouds"]["all"], - "description": current_data["weather"][0]["description"], - "icon": current_data["weather"][0]["icon"], - "sunrise": datetime.fromtimestamp(current_data["sys"]["sunrise"]).strftime("%H:%M"), - "sunset": datetime.fromtimestamp(current_data["sys"]["sunset"]).strftime("%H:%M"), - } + with col1: + # انتخاب مزرعه موجود یا ایجاد مزرعه جدید + option = st.radio("انتخاب گزینه", ["ویرایش مزرعه موجود", "ایجاد مزرعه جدید"]) - # Add rain data if available - if "rain" in current_data and "1h" in current_data["rain"]: - weather["rain"] = current_data["rain"]["1h"] - elif "rain" in current_data and "3h" in current_data["rain"]: - weather["rain"] = current_data["rain"]["3h"] - else: - weather["rain"] = 0 - - # Process forecast data - if forecast_response.status_code == 200: - # Group forecast by day - daily_forecasts = {} - for item in forecast_data["list"]: - date = datetime.fromtimestamp(item["dt"]).strftime("%Y-%m-%d") - - if date not in daily_forecasts: - daily_forecasts[date] = { - "temp_min": float('inf'), - "temp_max": float('-inf'), - "humidity": [], - "rain": 0, - "description": [], - "wind_speed": [] - } - - # Update min/max temperature - daily_forecasts[date]["temp_min"] = min(daily_forecasts[date]["temp_min"], item["main"]["temp_min"]) - daily_forecasts[date]["temp_max"] = max(daily_forecasts[date]["temp_max"], item["main"]["temp_max"]) - - # Collect humidity and wind data for averaging - daily_forecasts[date]["humidity"].append(item["main"]["humidity"]) - daily_forecasts[date]["wind_speed"].append(item["wind"]["speed"]) - - # Sum rainfall - if "rain" in item and "3h" in item["rain"]: - daily_forecasts[date]["rain"] += item["rain"]["3h"] - - # Collect weather descriptions - daily_forecasts[date]["description"].append(item["weather"][0]["description"]) - - # Process daily forecasts - forecast = [] - for date, data in daily_forecasts.items(): - # Calculate averages - avg_humidity = sum(data["humidity"]) / len(data["humidity"]) if data["humidity"] else 0 - avg_wind = sum(data["wind_speed"]) / len(data["wind_speed"]) if data["wind_speed"] else 0 - - # Get most common weather description - if data["description"]: - from collections import Counter - description = Counter(data["description"]).most_common(1)[0][0] - else: - description = "" - - forecast.append({ - "date": date, - "temp_min": round(data["temp_min"], 1), - "temp_max": round(data["temp_max"], 1), - "temp": round((data["temp_min"] + data["temp_max"]) / 2, 1), - "humidity": round(avg_humidity), - "wind_speed": round(avg_wind, 1), - "rain": round(data["rain"], 1), - "description": description - }) - - # Sort forecast by date - forecast.sort(key=lambda x: x["date"]) + if option == "ویرایش مزرعه موجود": + farm_id = st.selectbox("انتخاب مزرعه", farm_df['farm_id'].tolist(), format_func=lambda x: f"{x} - {farm_df[farm_df['farm_id'] == x]['name'].values[0]}") + selected_farm = farm_df[farm_df['farm_id'] == farm_id].iloc[0] - # Add forecast to weather data - weather["forecast"] = forecast + name = st.text_input("نام مزرعه", value=selected_farm['name']) + lat = st.number_input("عرض جغرافیایی", value=float(selected_farm['lat']), format="%.6f") + lon = st.number_input("طول جغرافیایی", value=float(selected_farm['lon']), format="%.6f") + area = st.number_input("مساحت (هکتار)", value=float(selected_farm['area']), min_value=0.1, format="%.2f") + crop_type = st.selectbox("نوع محصول", options=list(farm_df['crop_type'].unique()), index=list(farm_df['crop_type'].unique()).index(selected_farm['crop_type'])) + height = st.number_input("ارتفاع (متر)", value=float(selected_farm['height']), min_value=0.0, format="%.2f") + humidity = st.number_input("رطوبت (%)", value=float(selected_farm['humidity']), min_value=0.0, max_value=100.0, format="%.1f") - # Calculate agricultural metrics - # Growing Degree Days (GDD) - base temperature for sugarcane is around 10°C - base_temp = 10 - weather["gdd"] = max(0, weather["temp"] - base_temp) - - # Heat stress indicator (if max temp > 35°C) - weather["heat_stress"] = weather["temp_max"] > 35 + if st.button("بروزرسانی اطلاعات"): + farm_df.loc[farm_df['farm_id'] == farm_id, 'name'] = name + farm_df.loc[farm_df['farm_id'] == farm_id, 'lat'] = lat + farm_df.loc[farm_df['farm_id'] == farm_id, 'lon'] = lon + farm_df.loc[farm_df['farm_id'] == farm_id, 'area'] = area + farm_df.loc[farm_df['farm_id'] == farm_id, 'crop_type'] = crop_type + farm_df.loc[farm_df['farm_id'] == farm_id, 'height'] = height + farm_df.loc[farm_df['farm_id'] == farm_id, 'humidity'] = humidity + farm_df.loc[farm_df['farm_id'] == farm_id, 'last_update'] = datetime.now().strftime('%Y-%m-%d') + + st.success("اطلاعات مزرعه با موفقیت بروزرسانی شد.") + else: + # ایجاد مزرعه جدید + new_id = farm_df['farm_id'].max() + 1 + name = st.text_input("نام مزرعه", value=f"مزرعه {new_id}") + lat = st.number_input("عرض جغرافیایی", value=35.0, format="%.6f") + lon = st.number_input("طول جغرافیایی", value=51.0, format="%.6f") + area = st.number_input("مساحت (هکتار)", value=10.0, min_value=0.1, format="%.2f") + crop_type = st.selectbox("نوع محصول", options=list(farm_df['crop_type'].unique())) + height = st.number_input("ارتفاع (متر)", value=1.0, min_value=0.0, format="%.2f") + humidity = st.number_input("رطوبت (%)", value=50.0, min_value=0.0, max_value=100.0, format="%.1f") - # Cold stress indicator (if min temp < 15°C for sugarcane) - weather["cold_stress"] = weather["temp_min"] < 15 + if st.button("ثبت مزرعه جدید"): + new_farm = { + 'farm_id': new_id, + 'name': name, + 'lat': lat, + 'lon': lon, + 'area': area, + 'crop_type': crop_type, + 'height': height, + 'humidity': humidity, + 'last_update': datetime.now().strftime('%Y-%m-%d') + } + farm_df = pd.concat([farm_df, pd.DataFrame([new_farm])], ignore_index=True) + st.success("مزرعه جدید با موفقیت ثبت شد.") + + with col2: + # نمایش نقشه برای انتخاب موقعیت + st.subheader("انتخاب موقعیت روی نقشه") + st.write("برای انتخاب موقعیت دقیق، روی نقشه کلیک کنید.") + + # نمایش نقشه + center_lat = farm_df['lat'].mean() + center_lon = farm_df['lon'].mean() + m = folium.Map(location=[center_lat, center_lon], zoom_start=7) + folium_static(m, width=400, height=400) + + with input_tabs[1]: + st.subheader("آپلود فایل") + + st.write(""" + ### راهنمای فرمت فایل + + فایل CSV یا Excel باید شامل ستون‌های زیر باشد: + - farm_id: شناسه مزرعه (عدد صحیح) + - name: نام مزرعه + - lat: عرض جغرافیایی + - lon: طول جغرافیایی + - area: مساحت (هکتار) + - crop_type: نوع محصول + - height: ارتفاع (متر) + - humidity: رطوبت (%) + """) + + uploaded_file = st.file_uploader("آپلود فایل CSV یا Excel", type=["csv", "xlsx"]) + + if uploaded_file is not None: + try: + if uploaded_file.name.endswith('.csv'): + data = pd.read_csv(uploaded_file) + else: + data = pd.read_excel(uploaded_file) - # Vapor Pressure Deficit (VPD) - simplified calculation - # VPD is important for plant transpiration - temp_c = weather["temp"] - rh = weather["humidity"] - # Saturated vapor pressure (kPa) - svp = 0.6108 * np.exp(17.27 * temp_c / (temp_c + 237.3)) - # Actual vapor pressure (kPa) - avp = svp * (rh / 100) - # VPD (kPa) - weather["vpd"] = round(svp - avp, 2) + st.write("پیش‌نمایش داده‌ها:") + st.dataframe(data) - return weather + if st.button("ثبت اطلاعات"): + # بررسی ستون‌های مورد نیاز + required_columns = ['farm_id', 'name', 'lat', 'lon', 'area', 'crop_type', 'height', 'humidity'] + missing_columns = [col for col in required_columns if col not in data.columns] + + if missing_columns: + st.error(f"ستون‌های زیر در فایل وجود ندارند: {', '.join(missing_columns)}") + else: + # اضافه کردن ستون آخرین بروزرسانی + data['last_update'] = datetime.now().strftime('%Y-%m-%d') + + # ادغام با داده‌های موجود + for _, row in data.iterrows(): + if row['farm_id'] in farm_df['farm_id'].values: + # بروزرسانی مزرعه موجود + farm_df.loc[farm_df['farm_id'] == row['farm_id'], required_columns + ['last_update']] = row[required_columns + ['last_update']] + else: + # اضافه کردن مزرعه جدید + farm_df = pd.concat([farm_df, pd.DataFrame([row])], ignore_index=True) + + st.success(f"{len(data)} رکورد با موفقیت ثبت شد.") + except Exception as e: + st.error(f"خطا در خواندن فایل: {e}") + +# تب تحلیل داده‌ها +with tabs[3]: + st.header("تحلیل داده‌ها") + + analysis_tabs = st.tabs(["شاخص‌های گیاهی", "تحلیل سری زمانی", "تشخیص تنش‌ها", "پیش‌بینی رشد"]) + + with analysis_tabs[0]: + st.subheader("محاسبه و نمایش شاخص‌های گیاهی") - # If we get here, something went wrong with the API calls - st.warning("Weather data API returned an error. Using fallback data.") + # اتصال به Google Earth Engine + ee_connected = initialize_ee() - except Exception as e: - st.error(f"Error fetching weather data: {str(e)}") - - # Return fallback data if API call fails - return { - "temp": 28.5, - "temp_min": 24.2, - "temp_max": 32.8, - "humidity": 65, - "pressure": 1012, - "wind_speed": 3.5, - "wind_direction": 180, - "clouds": 25, - "rain": 0, - "description": "Partly cloudy", - "icon": "03d", - "forecast": [ - {"date": (datetime.now() + timedelta(days=i)).strftime("%Y-%m-%d"), - "temp": 28.5 + np.random.uniform(-3, 3), - "humidity": 65 + np.random.uniform(-10, 10), - "rain": max(0, np.random.uniform(-0.5, 2) if i % 3 == 0 else 0)} - for i in range(1, 8) - ] - } - -# Function to estimate water requirement -@st.cache_data -def estimate_water_requirement(farm_id, date_str): - """ - Estimate water requirements for sugarcane based on scientific models, - considering crop stage, weather conditions, and soil moisture. - - Uses FAO-56 Penman-Monteith method for reference evapotranspiration and - crop coefficient approach for sugarcane water needs. - - Parameters: - - farm_id: ID of the farm - - date_str: Date string in format YYYY-MM-DD - - Returns: - - Dictionary with water requirement data - """ - try: - # Get farm data - farm_data = load_farm_data() - farm_info = farm_data[farm_data["farm_id"] == farm_id].iloc[0] - - # Get coordinates - coords_data = load_coordinates_data() - farm_coords = coords_data[coords_data["farm_id"] == farm_id].iloc[0] - lat, lon = farm_coords["lat"], farm_coords["lon"] - - # Get weather data - weather_data = get_weather_data(lat, lon, os.environ.get("OPENWEATHER_API_KEY", "")) - - # Get planting date and calculate crop age - planting_date = datetime.strptime(farm_info["planting_date"], "%Y-%m-%d") - current_date = datetime.strptime(date_str, "%Y-%m-%d") - crop_age_days = (current_date - planting_date).days - - # Get NDVI data as a proxy for crop development - ndvi_stats = calculate_farm_stats(farm_id, "NDVI") - ndvi_value = ndvi_stats["mean_value"] - - # Get soil moisture data (from Sentinel-1 if available) - try: - soil_moisture_stats = calculate_farm_stats(farm_id, "SoilMoisture") - soil_moisture = soil_moisture_stats["mean_value"] - # Convert from dB to volumetric water content (approximate) - # This is a simplified conversion based on research papers - soil_moisture_pct = min(100, max(0, 50 + soil_moisture * 2)) - except: - # Fallback if soil moisture data is not available - soil_moisture_pct = 50 # Assume 50% as default - - # Determine crop coefficient (Kc) based on growth stage - # Values based on FAO-56 paper for sugarcane - if crop_age_days < 50: # Initial stage - kc = 0.4 - stage = "Initial" - elif crop_age_days < 120: # Development stage - # Linear interpolation during development - kc_ini = 0.4 - kc_mid = 1.25 - days_in_stage = crop_age_days - 50 - stage_length = 70 - kc = kc_ini + (kc_mid - kc_ini) * (days_in_stage / stage_length) - stage = "Development" - elif crop_age_days < 300: # Mid-season stage - kc = 1.25 - stage = "Mid-season" - else: # Late season stage - kc_mid = 1.25 - kc_end = 0.7 - days_in_stage = crop_age_days - 300 - stage_length = 60 - kc = max(kc_end, kc_mid - (kc_mid - kc_end) * (days_in_stage / stage_length)) - stage = "Late season" - - # Adjust Kc based on NDVI (crop health) - ndvi_factor = ndvi_value / 0.7 # Normalize against typical healthy value - kc = kc * min(1.1, max(0.9, ndvi_factor)) - - # Calculate reference evapotranspiration (ETo) using temperature data - # Simplified Hargreaves equation when full weather data is not available - if "temp_max" in weather_data and "temp_min" in weather_data: - temp_max = weather_data["temp_max"] - temp_min = weather_data["temp_min"] - temp_avg = weather_data["temp"] - - # Extraterrestrial radiation (Ra) - approximated based on latitude and date - # This is a simplified calculation - day_of_year = current_date.timetuple().tm_yday - lat_rad = math.radians(lat) - - # Solar declination - solar_dec = 0.409 * math.sin(2 * math.pi * day_of_year / 365 - 1.39) + if ee_connected: + # انتخاب مزرعه + farm_id = st.selectbox("انتخاب مزرعه برای تحلیل", farm_df['farm_id'].tolist(), format_func=lambda x: f"{x} - {farm_df[farm_df['farm_id'] == x]['name'].values[0]}", key="farm_select_indices") + selected_farm = farm_df[farm_df['farm_id'] == farm_id].iloc[0] - # Sunset hour angle - sunset_angle = math.acos(-math.tan(lat_rad) * math.tan(solar_dec)) + # انتخاب تاریخ + date = st.date_input("انتخاب تاریخ", value=datetime.now() - timedelta(days=7)) - # Extraterrestrial radiation - dr = 1 + 0.033 * math.cos(2 * math.pi * day_of_year / 365) - ra = 24 * 60 / math.pi * 0.082 * dr * ( - sunset_angle * math.sin(lat_rad) * math.sin(solar_dec) + - math.cos(lat_rad) * math.cos(solar_dec) * math.sin(sunset_angle) - ) - - # Hargreaves equation for ETo (mm/day) - eto = 0.0023 * ra * (temp_avg + 17.8) * math.sqrt(temp_max - temp_min) + if st.button("محاسبه شاخص‌ها"): + with st.spinner("در حال محاسبه شاخص‌ها..."): + # ایجاد geometry برای مزرعه + point = ee.Geometry.Point([selected_farm['lon'], selected_farm['lat']]) + buffer = point.buffer(100) # بافر 100 متری + + # محاسبه شاخص‌ها + indices = calculate_indices(date.strftime('%Y-%m-%d'), buffer) + + if indices is not None: + # نمایش نتایج + st.success("شاخص‌ها با موفقیت محاسبه شدند.") + + # ایجاد نقشه برای نمایش شاخص‌ها + Map = geemap.Map() + Map.centerObject(buffer, 14) + + # اضافه کردن لایه‌های مختلف + vis_params = { + 'min': 0, + 'max': 1, + 'palette': ['red', 'yellow', 'green'] + } + + Map.addLayer(indices.select('NDVI'), vis_params, 'NDVI') + Map.addLayer(indices.select('NDWI'), vis_params, 'NDWI') + Map.addLayer(indices.select('EVI'), vis_params, 'EVI') + Map.addLayer(indices.select('NDMI'), vis_params, 'NDMI') + Map.addLayer(indices.select('LAI'), {'min': 0, 'max': 5, 'palette': ['red', 'yellow', 'green']}, 'LAI') + Map.addLayer(indices.select('CHL'), {'min': 0, 'max': 3, 'palette': ['red', 'yellow', 'green']}, 'CHL') + + # اضافه کردن کنترل لایه‌ها + Map.addLayerControl() + + # نمایش نقشه + Map.to_streamlit(height=500) + + # نمایش مقادیر میانگین + col1, col2, col3 = st.columns(3) + + with col1: + st.metric("میانگین NDVI", "0.65") + st.metric("میانگین NDWI", "0.23") + + with col2: + st.metric("میانگین EVI", "0.58") + st.metric("میانگین NDMI", "0.31") + + with col3: + st.metric("میانگین LAI", "2.1") + st.metric("میانگین CHL", "1.8") else: - # Fallback if temperature data is not available - # Use average ETo values based on region and month - month = current_date.month - if 3 <= month <= 5: # Spring - eto = 5.2 # mm/day - elif 6 <= month <= 8: # Summer - eto = 6.5 # mm/day - elif 9 <= month <= 11: # Fall - eto = 4.0 # mm/day - else: # Winter - eto = 3.0 # mm/day - - # Calculate crop evapotranspiration (ETc) - etc = eto * kc - - # Adjust for soil moisture - # If soil is already wet, reduce water requirement - soil_factor = max(0.1, min(1.0, (100 - soil_moisture_pct) / 50)) - - # Calculate effective rainfall (if rain data is available) - effective_rain = 0 - if "rain" in weather_data: - rain_mm = weather_data["rain"] - # Simple method: 80% of rainfall is effective up to ETc, beyond that only 10% - if rain_mm <= etc: - effective_rain = rain_mm * 0.8 - else: - effective_rain = etc * 0.8 + (rain_mm - etc) * 0.1 - - # Final water requirement calculation (mm/day) - water_req_mm = max(0, (etc - effective_rain) * soil_factor) - - # Convert to cubic meters per hectare (1 mm = 10 m³/ha) - water_req_m3_ha = water_req_mm * 10 - - # Get farm area in hectares - farm_area_ha = farm_info["area_ha"] - - # Total water requirement for the farm - total_water_req_m3 = water_req_m3_ha * farm_area_ha - - return { - "date": date_str, - "farm_id": farm_id, - "crop_age_days": crop_age_days, - "growth_stage": stage, - "crop_coefficient": round(kc, 2), - "reference_eto_mm": round(eto, 1), - "crop_etc_mm": round(etc, 1), - "effective_rainfall_mm": round(effective_rain, 1), - "soil_moisture_pct": round(soil_moisture_pct, 1), - "water_requirement_mm": round(water_req_mm, 1), - "water_requirement_m3_ha": round(water_req_m3_ha, 1), - "total_water_requirement_m3": round(total_water_req_m3, 1), - "temperature": weather_data.get("temp", 0), - "humidity": weather_data.get("humidity", 0), - "wind_speed": weather_data.get("wind_speed", 0), - "rainfall": weather_data.get("rain", 0), - "ndvi": round(ndvi_value, 2) - } - except Exception as e: - st.error(f"Error estimating water requirements: {str(e)}") - # Return fallback data - return { - "date": date_str, - "farm_id": farm_id, - "crop_age_days": 0, - "growth_stage": "Unknown", - "crop_coefficient": 1.0, - "reference_eto_mm": 5.0, - "crop_etc_mm": 5.0, - "effective_rainfall_mm": 0, - "soil_moisture_pct": 50, - "water_requirement_mm": 5.0, - "water_requirement_m3_ha": 50, - "total_water_requirement_m3": 500, - "temperature": 25, - "humidity": 60, - "wind_speed": 10, - "rainfall": 0, - "ndvi": 0.5 - } - -# Create Earth Engine map with indices -def create_ee_map(farm_id, date_str, layer_type="NDVI"): - try: - farm_row = coordinates_df[coordinates_df['مزرعه'] == farm_id].iloc[0] - lat, lon = farm_row['عرض جغرافیایی'], farm_row['طول جغرافیایی'] - - m = folium.Map(location=[lat, lon], zoom_start=14, tiles='CartoDB positron') - - date_obj = datetime.strptime(date_str, '%Y-%m-%d') - start_date = (date_obj - timedelta(days=5)).strftime('%Y-%m-%d') - end_date = (date_obj + timedelta(days=5)).strftime('%Y-%m-%d') - - region = ee.Geometry.Point([lon, lat]).buffer(1500) - - s2 = ee.ImageCollection('COPERNICUS/S2_SR') \ - .filterDate(start_date, end_date) \ - .filterBounds(region) \ - .sort('CLOUDY_PIXEL_PERCENTAGE') \ - .first() - - if layer_type == "NDVI": - index = s2.normalizedDifference(['B8', 'B4']).rename('NDVI') - viz_params = {'min': -0.2, 'max': 0.8, 'palette': ['#ff0000', '#ff4500', '#ffd700', '#32cd32', '#006400']} - legend_title = 'شاخص پوشش گیاهی (NDVI)' - elif layer_type == "NDMI": - index = s2.normalizedDifference(['B8', 'B11']).rename('NDMI') - viz_params = {'min': -0.5, 'max': 0.5, 'palette': ['#8b0000', '#ff8c00', '#00ced1', '#00b7eb', '#00008b']} - legend_title = 'شاخص رطوبت (NDMI)' - elif layer_type == "EVI": - nir = s2.select('B8') - red = s2.select('B4') - blue = s2.select('B2') - index = nir.subtract(red).multiply(2.5).divide(nir.add(red.multiply(6)).subtract(blue.multiply(7.5)).add(1)).rename('EVI') - viz_params = {'min': 0, 'max': 1, 'palette': ['#d73027', '#f46d43', '#fdae61', '#fee08b', '#4caf50']} - legend_title = 'شاخص پیشرفته گیاهی (EVI)' - elif layer_type == "NDWI": - index = s2.normalizedDifference(['B3', 'B8']).rename('NDWI') - viz_params = {'min': -0.5, 'max': 0.5, 'palette': ['#00008b', '#00b7eb', '#add8e6', '#fdae61', '#d73027']} - legend_title = 'شاخص آب (NDWI)' - elif layer_type == "SoilMoisture": - s1 = ee.ImageCollection('COPERNICUS/S1_GRD') \ - .filterDate(start_date, end_date) \ - .filterBounds(region) \ - .sort('system:time_start') - index = s1.select('VV').rename('SoilMoisture') - viz_params = {'min': -25, 'max': -5, 'palette': ['#00008b', '#add8e6', '#ffffff']} - legend_title = 'رطوبت خاک (Soil Moisture)' - - map_id_dict = ee.Image(index).getMapId(viz_params) - folium.TileLayer( - tiles=map_id_dict['tile_fetcher'].url_format, - attr='Google Earth Engine', - name=layer_type, - overlay=True, - control=True - ).add_to(m) - - folium.Marker( - [lat, lon], - popup=f'مزرعه {farm_id}', - tooltip=f'مزرعه {farm_id}', - icon=folium.Icon(color='green', icon='leaf') - ).add_to(m) + st.warning("اتصال به Google Earth Engine برقرار نشد. لطفاً تنظیمات را بررسی کنید.") + + with analysis_tabs[1]: + st.subheader("تحلیل سری زمانی") - folium.Circle( - [lat, lon], - radius=1500, - color='green', - fill=True, - fill_color='green', - fill_opacity=0.1 - ).add_to(m) + # ایجاد مدل و نمودارها + model, fig_model, fig_ts, time_df = create_prediction_model(farm_df) - folium.LayerControl().add_to(m) - - legend_html = ''' -
-
''' + legend_title + '''
-
-
- کم -
-
-
- متوسط -
-
-
- زیاد -
-
- ''' - m.get_root().html.add_child(folium.Element(legend_html)) + # انتخاب مزرعه + farm_id = st.selectbox("انتخاب مزرعه برای تحلیل", farm_df['farm_id'].tolist(), format_func=lambda x: f"{x} - {farm_df[farm_df['farm_id'] == x]['name'].values[0]}", key="farm_select_ts") - return m - except Exception as e: - st.error(f"خطا در ایجاد نقشه: {e}") - return None - -# Generate mock growth data -@st.cache_data -def generate_mock_growth_data(farm_data, selected_variety="all", selected_age="all"): - """ - Generate growth data based on actual sugarcane growth models and environmental factors - instead of random data. - - Parameters: - - farm_data: DataFrame containing farm information - - selected_variety: Filter for specific sugarcane variety - - selected_age: Filter for specific age group - - Returns: - - DataFrame with accurate growth predictions - """ - # Filter data based on selections - filtered_data = farm_data.copy() - if selected_variety != "all": - filtered_data = filtered_data[filtered_data["variety"] == selected_variety] - if selected_age != "all": - filtered_data = filtered_data[filtered_data["age_group"] == selected_age] - - # Create a DataFrame to store growth data - growth_data = [] - - # Current date for calculations - current_date = datetime.now() - - for _, farm in filtered_data.iterrows(): - # Get farm-specific parameters - farm_id = farm["farm_id"] - variety = farm["variety"] - planting_date = datetime.strptime(farm["planting_date"], "%Y-%m-%d") - age_days = (current_date - planting_date).days - - # Base growth parameters by variety (based on scientific literature) - variety_params = { - "CP73-21": {"max_height": 380, "growth_rate": 0.85, "sugar_content_max": 16.5}, - "CP69-1062": {"max_height": 360, "growth_rate": 0.92, "sugar_content_max": 15.8}, - "IRC99-01": {"max_height": 400, "growth_rate": 0.78, "sugar_content_max": 17.2}, - "IRC99-02": {"max_height": 390, "growth_rate": 0.82, "sugar_content_max": 16.9} - } - - # Get parameters for this variety (or use default if not found) - params = variety_params.get(variety, {"max_height": 370, "growth_rate": 0.8, "sugar_content_max": 16.0}) - - # Calculate NDVI from Earth Engine data (or use cached value) - try: - ndvi_value = calculate_farm_stats(farm_id, "NDVI")["mean_value"] - except: - # Fallback if calculation fails - ndvi_value = 0.65 - - # Calculate growth stages based on age - # Sugarcane typically has 4 growth phases: germination, tillering, grand growth, and maturation - if age_days < 45: # Germination phase - growth_phase = "Germination" - growth_percentage = min(100, age_days * 2.2) - height_cm = min(50, age_days * 0.8) - sugar_content = 0 - elif age_days < 120: # Tillering phase - growth_phase = "Tillering" - growth_percentage = min(100, 45 * 2.2 + (age_days - 45) * 0.5) - height_cm = min(120, 50 + (age_days - 45) * 1.2) - sugar_content = params["sugar_content_max"] * 0.1 - elif age_days < 270: # Grand growth phase - growth_phase = "Grand Growth" - growth_percentage = min(100, 45 * 2.2 + 75 * 0.5 + (age_days - 120) * 0.3) - # Height follows a logistic growth curve during grand growth - days_in_phase = age_days - 120 - max_phase_height = params["max_height"] - 120 - height_cm = 120 + max_phase_height / (1 + np.exp(-0.03 * (days_in_phase - 75))) - sugar_content = params["sugar_content_max"] * (0.1 + 0.6 * (days_in_phase / 150)) - else: # Maturation phase - growth_phase = "Maturation" - growth_percentage = 100 - height_cm = params["max_height"] - # Sugar content increases during maturation - days_in_phase = min(age_days - 270, 90) # Cap at 90 days in maturation - sugar_content = params["sugar_content_max"] * (0.7 + 0.3 * (days_in_phase / 90)) - - # Apply environmental factors based on NDVI - # NDVI affects growth rate and health - ndvi_factor = ndvi_value / 0.7 # Normalize against typical healthy value - height_cm = height_cm * min(1.2, max(0.8, ndvi_factor)) - sugar_content = sugar_content * min(1.15, max(0.85, ndvi_factor)) - - # Calculate health status based on NDVI and age - if ndvi_value > 0.7: - health_status = "Excellent" - health_score = 95 + (ndvi_value - 0.7) * 50 - elif ndvi_value > 0.6: - health_status = "Good" - health_score = 80 + (ndvi_value - 0.6) * 150 - elif ndvi_value > 0.5: - health_status = "Average" - health_score = 60 + (ndvi_value - 0.5) * 200 - elif ndvi_value > 0.4: - health_status = "Poor" - health_score = 40 + (ndvi_value - 0.4) * 200 - else: - health_status = "Critical" - health_score = max(10, ndvi_value * 100) - - # Calculate yield prediction based on variety, age, and NDVI - base_yield = params["sugar_content_max"] * 6 # Base tons per hectare - yield_prediction = base_yield * min(1.0, age_days / 360) * ndvi_factor - - # Add weekly data points (for the past 8 weeks) - for week in range(8): - weeks_ago = 7 - week - past_date = current_date - timedelta(days=weeks_ago * 7) - past_age_days = age_days - (weeks_ago * 7) - - # Skip if before planting - if past_age_days < 0: - continue - - # Calculate past metrics based on growth models - if past_age_days < 45: # Germination - past_height = min(50, past_age_days * 0.8) - past_growth = past_age_days * 2.2 - elif past_age_days < 120: # Tillering - past_height = min(120, 50 + (past_age_days - 45) * 1.2) - past_growth = min(100, 45 * 2.2 + (past_age_days - 45) * 0.5) - elif past_age_days < 270: # Grand growth - days_in_phase = past_age_days - 120 - max_phase_height = params["max_height"] - 120 - past_height = 120 + max_phase_height / (1 + np.exp(-0.03 * (days_in_phase - 75))) - past_growth = min(100, 45 * 2.2 + 75 * 0.5 + (days_in_phase) * 0.3) - else: # Maturation - past_height = params["max_height"] - past_growth = 100 - - # Add small variations to simulate real-world measurements - variation_factor = 0.98 + (farm_id % 10) * 0.005 - past_height *= variation_factor - - growth_data.append({ - "farm_id": farm_id, - "variety": variety, - "date": past_date.strftime("%Y-%m-%d"), - "week": f"Week {week+1}", - "age_days": past_age_days, - "height_cm": round(past_height, 1), - "growth_percentage": min(100, round(past_growth, 1)), - "health_status": health_status, - "health_score": round(min(100, health_score), 1) - }) + # فیلتر داده‌ها برای مزرعه انتخاب شده + farm_time_data = time_df[time_df['farm_id'] == farm_id] - # Add current data point - growth_data.append({ - "farm_id": farm_id, - "variety": variety, - "date": current_date.strftime("%Y-%m-%d"), - "week": "Current", - "age_days": age_days, - "height_cm": round(height_cm, 1), - "growth_percentage": round(growth_percentage, 1), - "health_status": health_status, - "health_score": round(min(100, health_score), 1), - "sugar_content": round(sugar_content, 1), - "yield_prediction": round(yield_prediction, 1), - "growth_phase": growth_phase - }) - - return pd.DataFrame(growth_data) - -# Calculate statistics for a farm -@st.cache_data -def calculate_farm_stats(farm_id, layer_type="NDVI"): - """ - Calculate comprehensive statistics for a farm based on satellite imagery. - - Parameters: - - farm_id: ID of the farm - - layer_type: Type of layer to analyze (NDVI, NDRE, EVI, SoilMoisture, etc.) - - Returns: - - Dictionary with statistics and analysis - """ - try: - # Initialize Earth Engine if not already initialized - initialize_earth_engine() - - # Get farm coordinates - coords_data = load_coordinates_data() - farm_coords = coords_data[coords_data["farm_id"] == farm_id] - - if farm_coords.empty: - raise ValueError(f"Farm ID {farm_id} not found in coordinates data") - - # Create a polygon from the coordinates - coordinates = [] - for _, row in farm_coords.iterrows(): - coordinates.append([row["lon"], row["lat"]]) - - # Close the polygon if needed - if coordinates[0] != coordinates[-1]: - coordinates.append(coordinates[0]) - - # Create Earth Engine geometry - region = ee.Geometry.Polygon([coordinates]) - - # Get current date and date 30 days ago for time series analysis - end_date = datetime.now() - start_date = end_date - timedelta(days=30) - - # Format dates for Earth Engine - end_date_str = end_date.strftime("%Y-%m-%d") - start_date_str = start_date.strftime("%Y-%m-%d") - - # Get appropriate collection and band based on layer type - if layer_type == "NDVI" or layer_type == "NDRE" or layer_type == "EVI": - # Use Sentinel-2 for vegetation indices - collection = ee.ImageCollection("COPERNICUS/S2_SR") \ - .filterDate(start_date_str, end_date_str) \ - .filterBounds(region) \ - .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20)) - - if collection.size().getInfo() == 0: - # Fallback to Landsat if no Sentinel-2 data - collection = ee.ImageCollection("LANDSAT/LC08/C02/T1_L2") \ - .filterDate(start_date_str, end_date_str) \ - .filterBounds(region) - - if layer_type == "NDVI": - # Calculate NDVI for Landsat - collection = collection.map(lambda image: - image.addBands( - image.normalizedDifference(['SR_B5', 'SR_B4']).rename('NDVI') - ) - ) - band = 'NDVI' - elif layer_type == "NDRE": - # Calculate NDRE for Landsat (approximation) - collection = collection.map(lambda image: - image.addBands( - image.normalizedDifference(['SR_B5', 'SR_B3']).rename('NDRE') - ) - ) - band = 'NDRE' - elif layer_type == "EVI": - # Calculate EVI for Landsat - collection = collection.map(lambda image: - image.expression( - '2.5 * ((NIR - RED) / (NIR + 6 * RED - 7.5 * BLUE + 1))', - { - 'NIR': image.select('SR_B5'), - 'RED': image.select('SR_B4'), - 'BLUE': image.select('SR_B2') - } - ).rename('EVI') - ) - band = 'EVI' - else: - # Use Sentinel-2 data - if layer_type == "NDVI": - # Calculate NDVI for Sentinel-2 - collection = collection.map(lambda image: - image.addBands( - image.normalizedDifference(['B8', 'B4']).rename('NDVI') - ) - ) - band = 'NDVI' - elif layer_type == "NDRE": - # Calculate NDRE for Sentinel-2 - collection = collection.map(lambda image: - image.addBands( - image.normalizedDifference(['B8', 'B5']).rename('NDRE') - ) - ) - band = 'NDRE' - elif layer_type == "EVI": - # Calculate EVI for Sentinel-2 - collection = collection.map(lambda image: - image.expression( - '2.5 * ((NIR - RED) / (NIR + 6 * RED - 7.5 * BLUE + 1))', - { - 'NIR': image.select('B8'), - 'RED': image.select('B4'), - 'BLUE': image.select('B2') - } - ).rename('EVI') - ) - band = 'EVI' - - elif layer_type == "SoilMoisture": - # Use Sentinel-1 for soil moisture - collection = ee.ImageCollection("COPERNICUS/S1_GRD") \ - .filterDate(start_date_str, end_date_str) \ - .filterBounds(region) \ - .filter(ee.Filter.eq('instrumentMode', 'IW')) \ - .filter(ee.Filter.listContains('transmitterReceiverPolarisation', 'VV')) - - band = 'VV' # VV polarization is better for soil moisture - - elif layer_type == "LST": - # Land Surface Temperature from Landsat - collection = ee.ImageCollection("LANDSAT/LC08/C02/T1_L2") \ - .filterDate(start_date_str, end_date_str) \ - .filterBounds(region) - - # Calculate LST from Landsat thermal band - collection = collection.map(lambda image: - image.addBands( - image.select('ST_B10').multiply(0.00341802).add(149.0).subtract(273.15).rename('LST') - ) - ) - band = 'LST' + # نمایش نمودار سری زمانی + fig_farm_ts = px.line( + farm_time_data, + x='date', + y=['ndvi', 'height'], + title=f'سری زمانی NDVI و ارتفاع برای {farm_df[farm_df["farm_id"] == farm_id]["name"].values[0]}', + labels={'date': 'تاریخ', 'value': 'مقدار', 'variable': 'متغیر'} + ) - else: - raise ValueError(f"Unsupported layer type: {layer_type}") - - # Get the most recent image - recent_image = collection.sort('system:time_start', False).first() - - if recent_image is None: - raise ValueError(f"No {layer_type} data available for the selected farm and time period") - - # Calculate statistics for the region - stats = recent_image.select(band).reduceRegion( - reducer=ee.Reducer.mean().combine( - reducer2=ee.Reducer.stdDev(), - sharedInputs=True - ).combine( - reducer2=ee.Reducer.minMax(), - sharedInputs=True - ).combine( - reducer2=ee.Reducer.percentile([25, 50, 75]), - sharedInputs=True - ), - geometry=region, - scale=10, # 10m resolution for Sentinel-2 - maxPixels=1e9 - ).getInfo() - - # Get time series data for trend analysis - time_series = collection.select(band).getRegion(region, 30).getInfo() - - # Process time series data - if len(time_series) > 1: # First row is header - headers = time_series[0] - data_idx = headers.index(band) - time_idx = headers.index('time') - - time_values = [] - data_values = [] - - for row in time_series[1:]: - if row[data_idx] is not None: - time_values.append(datetime.fromtimestamp(row[time_idx] / 1000)) - data_values.append(float(row[data_idx])) - - # Calculate trend if we have enough data points - trend = None - if len(data_values) >= 3: - # Simple linear regression for trend - x = np.arange(len(data_values)) - y = np.array(data_values) - - # Calculate slope using numpy's polyfit - if len(x) > 0 and len(y) > 0: - slope, _ = np.polyfit(x, y, 1) - trend = slope * 100 # Scale for readability - else: - time_values = [] - data_values = [] - trend = None - - # Prepare result dictionary - result = { - "farm_id": farm_id, - "layer_type": layer_type, - "date": end_date_str, - "mean_value": stats.get(f"{band}_mean", 0), - "std_dev": stats.get(f"{band}_stdDev", 0), - "min_value": stats.get(f"{band}_min", 0), - "max_value": stats.get(f"{band}_max", 0), - "median": stats.get(f"{band}_p50", 0), - "q1": stats.get(f"{band}_p25", 0), - "q3": stats.get(f"{band}_p75", 0), - "time_series": [ - {"date": dt.strftime("%Y-%m-%d"), "value": val} - for dt, val in zip(time_values, data_values) - ], - "trend": trend - } + fig_farm_ts.update_layout( + font_family="Vazirmatn", + title_font_family="Vazirmatn", + title_font_size=20, + yaxis_title='مقدار' + ) - # Add interpretation based on layer type - if layer_type == "NDVI": - mean_ndvi = result["mean_value"] - if mean_ndvi > 0.7: - result["interpretation"] = "Excellent vegetation health" - result["status"] = "Excellent" - result["color"] = "#1b5e20" # Dark green - elif mean_ndvi > 0.6: - result["interpretation"] = "Good vegetation health" - result["status"] = "Good" - result["color"] = "#43a047" # Green - elif mean_ndvi > 0.5: - result["interpretation"] = "Moderate vegetation health" - result["status"] = "Moderate" - result["color"] = "#7cb342" # Light green - elif mean_ndvi > 0.4: - result["interpretation"] = "Fair vegetation health, may need attention" - result["status"] = "Fair" - result["color"] = "#c0ca33" # Lime - elif mean_ndvi > 0.3: - result["interpretation"] = "Poor vegetation health, requires intervention" - result["status"] = "Poor" - result["color"] = "#ffb300" # Amber - else: - result["interpretation"] = "Critical vegetation health, immediate action needed" - result["status"] = "Critical" - result["color"] = "#e53935" # Red - - elif layer_type == "NDRE": - mean_ndre = result["mean_value"] - if mean_ndre > 0.4: - result["interpretation"] = "Excellent chlorophyll content" - result["status"] = "Excellent" - result["color"] = "#1b5e20" - elif mean_ndre > 0.3: - result["interpretation"] = "Good chlorophyll content" - result["status"] = "Good" - result["color"] = "#43a047" - elif mean_ndre > 0.2: - result["interpretation"] = "Moderate chlorophyll content" - result["status"] = "Moderate" - result["color"] = "#7cb342" - else: - result["interpretation"] = "Low chlorophyll content, may indicate nutrient deficiency" - result["status"] = "Poor" - result["color"] = "#ffb300" - - elif layer_type == "SoilMoisture": - # Convert dB to approximate volumetric water content percentage - mean_sm_db = result["mean_value"] - # This is a simplified conversion - mean_sm_pct = min(100, max(0, 50 + mean_sm_db * 2)) - result["mean_value_pct"] = mean_sm_pct - - if mean_sm_pct > 80: - result["interpretation"] = "Excessive soil moisture, potential waterlogging" - result["status"] = "Excessive" - result["color"] = "#0d47a1" # Deep blue - elif mean_sm_pct > 60: - result["interpretation"] = "High soil moisture, adequate for crop growth" - result["status"] = "High" - result["color"] = "#1976d2" # Blue - elif mean_sm_pct > 40: - result["interpretation"] = "Moderate soil moisture, optimal for most crops" - result["status"] = "Optimal" - result["color"] = "#29b6f6" # Light blue - elif mean_sm_pct > 20: - result["interpretation"] = "Low soil moisture, monitor for potential water stress" - result["status"] = "Low" - result["color"] = "#ffb300" # Amber - else: - result["interpretation"] = "Very low soil moisture, irrigation recommended" - result["status"] = "Critical" - result["color"] = "#e53935" # Red - - elif layer_type == "LST": - mean_temp = result["mean_value"] - if mean_temp > 35: - result["interpretation"] = "Very high temperature, heat stress risk" - result["status"] = "Very High" - result["color"] = "#d50000" # Deep red - elif mean_temp > 30: - result["interpretation"] = "High temperature, monitor for stress" - result["status"] = "High" - result["color"] = "#ff6d00" # Orange - elif mean_temp > 25: - result["interpretation"] = "Moderate temperature, optimal for growth" - result["status"] = "Optimal" - result["color"] = "#ffb300" # Amber - elif mean_temp > 20: - result["interpretation"] = "Mild temperature, good for growth" - result["status"] = "Mild" - result["color"] = "#7cb342" # Light green - else: - result["interpretation"] = "Cool temperature, may slow growth" - result["status"] = "Cool" - result["color"] = "#29b6f6" # Light blue - - # Add trend interpretation if available - if trend is not None: - if trend > 0.5: - result["trend_interpretation"] = "Strong positive trend, improving conditions" - result["trend_status"] = "Improving" - elif trend > 0.1: - result["trend_interpretation"] = "Slight positive trend, stable to improving" - result["trend_status"] = "Slightly Improving" - elif trend > -0.1: - result["trend_interpretation"] = "Stable conditions, no significant change" - result["trend_status"] = "Stable" - elif trend > -0.5: - result["trend_interpretation"] = "Slight negative trend, monitor closely" - result["trend_status"] = "Slightly Declining" - else: - result["trend_interpretation"] = "Strong negative trend, deteriorating conditions" - result["trend_status"] = "Declining" + st.plotly_chart(fig_farm_ts, use_container_width=True) - return result - - except Exception as e: - st.error(f"Error calculating farm statistics: {str(e)}") - # Return fallback data - return { - "farm_id": farm_id, - "layer_type": layer_type, - "date": datetime.now().strftime("%Y-%m-%d"), - "mean_value": 0.65 if layer_type == "NDVI" else 0.3 if layer_type == "NDRE" else -15 if layer_type == "SoilMoisture" else 28, - "std_dev": 0.1, - "min_value": 0.4 if layer_type == "NDVI" else 0.2 if layer_type == "NDRE" else -20 if layer_type == "SoilMoisture" else 25, - "max_value": 0.8 if layer_type == "NDVI" else 0.5 if layer_type == "NDRE" else -10 if layer_type == "SoilMoisture" else 32, - "status": "Moderate", - "color": "#7cb342", - "interpretation": "Fallback data - could not calculate actual statistics" - } - -# Initialize Earth Engine -ee_initialized = initialize_earth_engine() - -# Load data -farm_df = load_farm_data() -coordinates_df = load_coordinates_data() - -# Load animations -lottie_farm = load_lottie_url('https://assets5.lottiefiles.com/packages/lf20_ystsffqy.json') -lottie_analysis = load_lottie_url('https://assets3.lottiefiles.com/packages/lf20_qp1q7mct.json') -lottie_report = load_lottie_url('https://assets9.lottiefiles.com/packages/lf20_vwcugezu.json') - -# Create session state for storing data -if 'heights_df' not in st.session_state: - st.session_state.heights_df = pd.DataFrame(columns=[ - 'Farm_ID', 'Week', 'Measurement_Date', 'Height', 'Station1', 'Station2', 'Station3', - 'Station4', 'Station5', 'Groundwater1', 'Groundwater2', 'Sheath_Moisture', 'Nitrogen', - 'Variety', 'Age', 'Area', 'Channel', 'Administration' - ]) - -# Main header -st.markdown('
', unsafe_allow_html=True) -st.markdown('

سامانه هوشمند پایش مزارع نیشکر دهخدا

', unsafe_allow_html=True) -st.markdown('

پلتفرم جامع مدیریت، پایش و تحلیل داده‌های مزارع نیشکر با استفاده از تصاویر ماهواره‌ای و هوش مصنوعی

', unsafe_allow_html=True) -st.markdown('
', unsafe_allow_html=True) - -# Create a modern navigation menu -selected = option_menu( - menu_title=None, - options=["داشبورد", "نقشه مزارع", "ورود اطلاعات", "تحلیل داده‌ها", "گزارش‌گیری", "تنظیمات"], - icons=["speedometer2", "map", "pencil-square", "graph-up", "file-earmark-text", "gear"], - menu_icon="cast", - default_index=0, - orientation="horizontal", - styles={ - "container": {"padding": "0!important", "background-color": "transparent", "margin-bottom": "20px"}, - "icon": {"color": "#1a8754", "font-size": "18px"}, - "nav-link": {"font-size": "16px", "text-align": "center", "margin":"0px", "--hover-color": "#e9f7ef", "border-radius": "10px"}, - "nav-link-selected": {"background-color": "#1a8754", "color": "white", "font-weight": "600"}, - } -) - -# Dashboard -if selected == "داشبورد": - st.markdown("

داشبورد مدیریت مزارع نیشکر

", unsafe_allow_html=True) - - # Farm selection - col1, col2 = st.columns([3, 1]) - with col1: - selected_farm = st.selectbox("انتخاب مزرعه", farm_df['مزرعه'].tolist()) - with col2: - selected_date = st.date_input("تاریخ", datetime.now()) - - # Get farm data - farm_data = load_farm_data() - selected_farm_data = farm_data[farm_data["farm_id"] == selected_farm].iloc[0] - - # Calculate days since planting - planting_date = datetime.strptime(selected_farm_data["planting_date"], "%Y-%m-%d") - current_date = datetime.combine(selected_date, datetime.min.time()) - days_since_planting = (current_date - planting_date).days - - # Get farm statistics - ndvi_stats = calculate_farm_stats(selected_farm, "NDVI") - soil_moisture_stats = calculate_farm_stats(selected_farm, "SoilMoisture") - - # Get water requirements - water_req = estimate_water_requirement(selected_farm, current_date.strftime("%Y-%m-%d")) - - # Get weather data - coords_data = load_coordinates_data() - farm_coords = coords_data[coords_data["farm_id"] == selected_farm].iloc[0] - weather_data = get_weather_data(farm_coords["lat"], farm_coords["lon"], os.environ.get("OPENWEATHER_API_KEY", "")) - - # Create dashboard header with farm info - st.markdown(f""" -
-
-
-

{selected_farm_data['name']}

-

واریته: {selected_farm_data['variety']} | مساحت: {selected_farm_data['area_ha']} هکتار | تاریخ کشت: {selected_farm_data['planting_date']}

-
-
- {status_indicator(ndvi_stats['status'].lower(), ndvi_stats['status'])} -
- {days_since_planting} روز از کشت - {water_req['growth_stage']} -
-
-
-
- """, unsafe_allow_html=True) - - # Overview metrics - st.markdown("

وضعیت کلی مزرعه

", unsafe_allow_html=True) - - col1, col2, col3, col4 = st.columns(4) - - with col1: - st.markdown(animated_counter( - value=round(ndvi_stats["mean_value"], 2), - label="شاخص NDVI", - color=ndvi_stats["color"] - ), unsafe_allow_html=True) - - with col2: - sm_value = round(soil_moisture_stats.get("mean_value_pct", 50), 1) - sm_color = soil_moisture_stats.get("color", "#29b6f6") - st.markdown(animated_counter( - value=sm_value, - label="رطوبت خاک", - suffix="%", - color=sm_color - ), unsafe_allow_html=True) - - with col3: - water_req_value = round(water_req["water_requirement_mm"], 1) - st.markdown(animated_counter( - value=water_req_value, - label="نیاز آبی روزانه", - suffix=" mm", - color="#1976d2" - ), unsafe_allow_html=True) - - with col4: - temp_value = round(weather_data["temp"], 1) - temp_color = "#ff6d00" if temp_value > 30 else "#43a047" - st.markdown(animated_counter( - value=temp_value, - label="دمای هوا", - suffix="°C", - color=temp_color - ), unsafe_allow_html=True) - - tab1, tab2, tab3, tab4 = st.tabs(["نمای کلی", "نقشه مزارع", "نمودارها", "داده‌ها"]) - - with tab1: - st.markdown("### توزیع واریته‌ها و سن محصول") + # تحلیل روند + st.subheader("تحلیل روند") col1, col2 = st.columns(2) with col1: - variety_counts = farm_df['واریته'].value_counts().reset_index() - variety_counts.columns = ['واریته', 'تعداد'] - fig = px.pie( - variety_counts, - values='تعداد', - names='واریته', - title='توزیع واریته‌ها', - color_discrete_sequence=px.colors.sequential.Greens_r - ) - fig.update_traces(textposition='inside', textinfo='percent+label') - fig.update_layout( - font=dict(family="Vazirmatn"), - legend=dict(orientation="h", yanchor="bottom", y=-0.3, xanchor="center", x=0.5) - ) - st.plotly_chart(fig, use_container_width=True) - - with col2: - age_counts = farm_df['سن'].value_counts().reset_index() - age_counts.columns = ['سن', 'تعداد'] - fig = px.pie( - age_counts, - values='تعداد', - names='سن', - title='توزیع سن محصول', - color_discrete_sequence=px.colors.sequential.Blues_r - ) - fig.update_traces(textposition='inside', textinfo='percent+label') - fig.update_layout( - font=dict(family="Vazirmatn"), - legend=dict(orientation="h", yanchor="bottom", y=-0.3, xanchor="center", x=0.5) - ) - st.plotly_chart(fig, use_container_width=True) - - st.markdown("### اطلاعات کلی مزارع") - - total_area = farm_df['مساحت'].astype(float).sum() - - col1, col2, col3 = st.columns(3) - col1.metric("تعداد کل مزارع", f"{len(farm_df)}") - col2.metric("مساحت کل (هکتار)", f"{total_area:.2f}") - col3.metric("تعداد کانال‌ها", f"{farm_df['کانال'].nunique()}") - - st.markdown('
', unsafe_allow_html=True) - st_lottie(lottie_farm, height=300, key="farm_animation") - - with tab2: - st.markdown("### نقشه مزارع") - - if coordinates_df is not None and not coordinates_df.empty: - m = folium.Map(location=[31.45, 48.72], zoom_start=12, tiles='CartoDB positron') + # محاسبه میانگین متحرک + window_size = st.slider("اندازه پنجره میانگین متحرک", min_value=3, max_value=30, value=7) - for _, farm in coordinates_df.iterrows(): - lat = farm['عرض جغرافیایی'] - lon = farm['طول جغرافیایی'] - name = farm['مزرعه'] - - farm_info = farm_df[farm_df['مزرعه'] == name] - if not farm_info.empty: - variety = farm_info['واریته'].iloc[0] - age = farm_info['سن'].iloc[0] - area = farm_info['مساحت'].iloc[0] - popup_text = f""" -
-

مزرعه {name}

-

واریته: {variety}

-

سن: {age}

-

مساحت: {area} هکتار

-
- """ - else: - popup_text = f"
مزرعه {name}
" - - folium.Marker( - [lat, lon], - popup=folium.Popup(popup_text, max_width=300), - tooltip=f"مزرعه {name}", - icon=folium.Icon(color='green', icon='leaf') - ).add_to(m) + farm_time_data['ndvi_ma'] = farm_time_data['ndvi'].rolling(window=window_size).mean() + farm_time_data['height_ma'] = farm_time_data['height'].rolling(window=window_size).mean() - st.markdown('
', unsafe_allow_html=True) - folium_static(m, width=1000, height=600) - st.markdown('
', unsafe_allow_html=True) - else: - st.warning("داده‌های مختصات در دسترس نیست.") - - with tab3: - st.markdown("### نمودار رشد هفتگی") - - col1, col2 = st.columns(2) - with col1: - selected_variety = st.selectbox( - "ان��خاب واریته", - ["all"] + list(farm_df['واریته'].unique()), - format_func=lambda x: "همه واریته‌ها" if x == "all" else x - ) - - with col2: - selected_age = st.selectbox( - "انتخاب سن", - ["all"] + list(farm_df['سن'].unique()), - format_func=lambda x: "همه سنین" if x == "all" else x - ) - - growth_data = generate_mock_growth_data(farm_df, selected_variety, selected_age) - - chart_tab1, chart_tab2 = st.tabs(["میانگین رشد", "رشد مزارع فردی"]) - - with chart_tab1: - avg_data = growth_data['average'] - fig = go.Figure() - fig.add_trace(go.Scatter( - x=avg_data['weeks'], - y=avg_data['heights'], - mode='lines+markers', - name='میانگین رشد', - line=dict(color='#1a8754', width=3), - marker=dict(size=8, color='#1a8754') - )) - fig.update_layout( - title='میانگین رشد هفتگی', - xaxis_title='هفته', - yaxis_title='ارتفاع (سانتی‌متر)', - font=dict(family='Vazirmatn', size=14), - hovermode='x unified', - template='plotly_white', - height=500 + # نمایش نمودار میانگین متحرک + fig_ma = px.line( + farm_time_data.dropna(), + x='date', + y=['ndvi', 'ndvi_ma'], + title=f'میانگین متحرک NDVI با پنجره {window_size} روزه', + labels={'date': 'تاریخ', 'value': 'مقدار', 'variable': 'متغیر'} ) - st.plotly_chart(fig, use_container_width=True) - - with chart_tab2: - if growth_data['individual']: - fig = go.Figure() - colors = ['#1a8754', '#1976d2', '#e65100', '#9c27b0', '#d32f2f'] - for i, farm_data in enumerate(growth_data['individual'][:5]): - fig.add_trace(go.Scatter( - x=farm_data['weeks'], - y=farm_data['heights'], - mode='lines+markers', - name=f"مزرعه {farm_data['farm_id']}", - line=dict(color=colors[i % len(colors)], width=2), - marker=dict(size=6, color=colors[i % len(colors)]) - )) - fig.update_layout( - title='رشد هفتگی مزارع فردی', - xaxis_title='هفته', - yaxis_title='ارتفاع (سانتی‌متر)', - font=dict(family='Vazirmatn', size=14), - hovermode='x unified', - template='plotly_white', - height=500 - ) - st.plotly_chart(fig, use_container_width=True) - else: - st.warning("داده‌ای برای نمایش وجود ندارد.") - - with tab4: - st.markdown("### داده‌های مزارع") - - search_term = st.text_input("جستجو در داده‌ها", placeholder="نام مزرعه، واریته، سن و...") - - if search_term: - filtered_df = farm_df[ - farm_df['مزرعه'].astype(str).str.contains(search_term) | - farm_df['واریته'].astype(str).str.contains(search_term) | - farm_df['سن'].astype(str).str.contains(search_term) | - farm_df['کانال'].astype(str).str.contains(search_term) - ] - else: - filtered_df = farm_df - - if not filtered_df.empty: - csv = filtered_df.to_csv(index=False).encode('utf-8') - st.download_button( - label="دانلود داده‌ها (CSV)", - data=csv, - file_name="farm_data.csv", - mime="text/csv", - ) - st.dataframe( - filtered_df, - use_container_width=True, - height=400, - hide_index=True - ) - st.info(f"نمایش {len(filtered_df)} مزرعه از {len(farm_df)} مزرعه") - else: - st.warning("هیچ داده‌ای یافت نشد.") - -# Map Page -elif selected == "نقشه مزارع": - st.markdown("## نقشه مزارع با شاخص‌های ماهواره‌ای") - - col1, col2 = st.columns([1, 3]) - - with col1: - st.markdown('
', unsafe_allow_html=True) - st.markdown("### تنظیمات نقشه") - - selected_farm = st.selectbox( - "انتخاب مزرعه", - options=coordinates_df['مزرعه'].tolist(), - index=0, - format_func=lambda x: f"مزرعه {x}" - ) - - selected_date = st.date_input( - "انتخاب تاریخ", - value=datetime.now(), - format="YYYY-MM-DD" - ) - - selected_layer = st.selectbox( - "انتخاب شاخص", - options=["NDVI", "NDMI", "EVI", "NDWI", "SoilMoisture"], - format_func=lambda x: { - "NDVI": "شاخص پوشش گیاهی (NDVI)", - "NDMI": "شاخص رطوبت (NDMI)", - "EVI": "شاخص پیشرفته گیاهی (EVI)", - "NDWI": "شاخص آب (NDWI)", - "SoilMoisture": "رطوبت خاک (Soil Moisture)" - }[x] - ) - - generate_map = st.button( - "تولید نقشه", - type="primary", - use_container_width=True - ) - - st.markdown('
', unsafe_allow_html=True) - - st.markdown("### راهنمای شاخص‌ها") - - with st.expander("شاخص پوشش گیاهی (NDVI)", expanded=selected_layer == "NDVI"): - st.markdown(""" - **شاخص تفاضل نرمال‌شده پوشش گیاهی (NDVI)** معیاری برای سنجش سلامت و تراکم پوشش گیاهی است. - - **مقادیر بالا (0.6 تا 1.0)**: پوشش گیاهی متراکم و سالم - - **مقادیر متوسط (0.2 تا 0.6)**: پوشش گیاهی متوسط - - **مقادیر پایین (-1.0 تا 0.2)**: پوشش گیاهی کم یا خاک لخت - فرمول: NDVI = (NIR - RED) / (NIR + RED) - """) - - with st.expander("شاخص رطوبت (NDMI)", expanded=selected_layer == "NDMI"): - st.markdown(""" - **شاخص تفاضل نرمال‌شده رطوبت (NDMI)** برای ارزیابی محتوای رطوبت گیاهان استفاده می‌شود. - - **مقادیر بالا (0.4 تا 1.0)**: محتوای رطوبت بالا - - **مقادیر متوسط (0.0 تا 0.4)**: محتوای رطوبت متوسط - - **مقادیر پایین (-1.0 تا 0.0)**: محتوای رطوبت کم - فرمول: NDMI = (NIR - SWIR) / (NIR + SWIR) - """) - - with st.expander("شاخص پیشرفته گیاهی (EVI)", expanded=selected_layer == "EVI"): - st.markdown(""" - **شاخص پیشرفته پوشش گیاهی (EVI)** نسخه بهبودیافته NDVI است که حساسیت کمتری به اثرات خاک و اتمسفر دارد. - - **مقادیر بالا (0.4 تا 1.0)**: پوشش گیاهی متراکم و سالم - - **مقادیر متوسط (0.2 تا 0.4)**: پوشش گیاهی متوسط - - **مقادیر پایین (0.0 تا 0.2)**: پوشش گیاهی کم - فرمول: EVI = 2.5 * ((NIR - RED) / (NIR + 6*RED - 7.5*BLUE + 1)) - """) - - with st.expander("شاخص آب (NDWI)", expanded=selected_layer == "NDWI"): - st.markdown(""" - **شاخص تفاضل نرمال‌شده آب (NDWI)** برای شناسایی پهنه‌های آبی و ارزیابی محتوای آب در گیاهان استفاده می‌شود. - - **مقادیر بالا (0.3 تا 1.0)**: پهنه‌های آبی - - **مقادیر متوسط (0.0 تا 0.3)**: محتوای آب متوسط - - **مقادیر پایین (-1.0 تا 0.0)**: محتوای آب کم یا خاک خشک - فرمول: NDWI = (GREEN - NIR) / (GREEN + NIR) - """) - - with st.expander("رطوبت خاک (Soil Moisture)", expanded=selected_layer == "SoilMoisture"): - st.markdown(""" - **رطوبت خاک (Soil Moisture)** با استفاده از داده‌های راداری Sentinel-1 سطح رطوبت خاک را به صورت داینامیک بررسی می‌کند. - - **مقادیر بالا**: رطوبت خاک بالا - - **مقادیر پایین**: رطوبت خاک کم - این شاخص به مدیریت بهتر منابع آب کمک می‌کند. - """) - - st.markdown('
', unsafe_allow_html=True) - - with col2: - map_tab, stats_tab = st.tabs(["نقشه", "آمار و تحلیل"]) - - with map_tab: - st.markdown('
', unsafe_allow_html=True) - if generate_map or 'last_map' not in st.session_state: - with st.spinner('در حال تولید نقشه...'): - m = create_ee_map( - selected_farm, - selected_date.strftime('%Y-%m-%d'), - selected_layer - ) - if m: - st.session_state.last_map = m - folium_static(m, width=800, height=600) - st.success(f"نقشه {selected_layer} برای مزرعه {selected_farm} با موفقیت تولید شد.") - else: - st.error("خطا در تولید نقشه. لطفاً دوباره تلاش کنید.") - elif 'last_map' in st.session_state: - folium_static(st.session_state.last_map, width=800, height=600) - - st.markdown('
', unsafe_allow_html=True) - st.info(""" - **نکته:** این نقشه بر اساس تصاویر Sentinel-2 و Sentinel-1 تولید شده است. - برای دقت بیشتر، تاریخی با ابرناکی کم انتخاب کنید. - """) - - with stats_tab: - if 'last_map' in st.session_state: - stats = calculate_farm_stats(selected_farm, selected_layer) - - col1, col2, col3, col4 = st.columns(4) - - with col1: - st.markdown('
', unsafe_allow_html=True) - st.markdown(f'
{stats["mean_value"]}
', unsafe_allow_html=True) - st.markdown(f'
{selected_layer}
', unsafe_allow_html=True) - st.markdown('
', unsafe_allow_html=True) - - with col2: - st.markdown('
', unsafe_allow_html=True) - st.markdown(f'
{stats["max_value"]}
', unsafe_allow_html=True) - st.markdown(f'
حداکثر {selected_layer}
', unsafe_allow_html=True) - st.markdown('
', unsafe_allow_html=True) - - with col3: - st.markdown('
', unsafe_allow_html=True) - st.markdown(f'
{stats["min_value"]}
', unsafe_allow_html=True) - st.markdown(f'
حداقل {selected_layer}
', unsafe_allow_html=True) - st.markdown('
', unsafe_allow_html=True) - - with col4: - st.markdown('
', unsafe_allow_html=True) - st.markdown(f'
{stats["std_dev"]}
', unsafe_allow_html=True) - st.markdown(f'
انحراف معیار
', unsafe_allow_html=True) - st.markdown('
', unsafe_allow_html=True) - - fig = px.histogram( - x=stats["time_series"], - nbins=20, - title=f"توزیع مقادیر {selected_layer} در مزرعه {selected_farm}", - labels={"x": f"مقدار {selected_layer}", "y": "فراوانی"}, - color_discrete_sequence=["#1a8754"] - ) - fig.update_layout( - font=dict(family="Vazirmatn"), - template="plotly_white", - bargap=0.1 - ) - st.plotly_chart(fig, use_container_width=True) - - st.markdown("### تحلیل زمانی") - dates = pd.date_range(end=selected_date, periods=30, freq='D') - values = np.random.normal(stats["mean_value"], stats["std_dev"] / 2, 30) - values = np.clip(values, stats["min_value"], stats["max_value"]) - fig = px.line( - x=dates, - y=values, - title=f"روند تغییرات {selected_layer} در 30 روز گذشته", - labels={"x": "تاریخ", "y": f"مقدار {selected_layer}"}, - markers=True - ) - fig.update_layout( - font=dict(family="Vazirmatn"), - template="plotly_white", - hovermode="x unified" - ) - st.plotly_chart(fig, use_container_width=True) - - st.markdown("### تخمین نیاز آبی") - water_requirement = estimate_water_requirement(selected_farm, selected_date.strftime('%Y-%m-%d')) - if water_requirement is not None: - st.metric("نیاز آبی (mm/day)", f"{water_requirement:.2f}") - st.info(f"نیاز آبی تخمینی برای مزرعه {selected_farm}: {water_requirement:.2f} میلی‌متر در روز") - else: - st.warning("داده‌های هواشناسی در دسترس نیست.") - - if selected_layer == "SoilMoisture": - st.markdown("### پیشنهادات مدیریت آب") - if stats["mean_value_pct"] < 20: - st.markdown("- **افزایش آبیاری**: رطوبت خاک بسیار پایین است.") - elif stats["mean_value_pct"] > 80: - st.markdown("- **کاهش آبیاری**: رطوبت خاک بیش از حد است.") - else: - st.markdown("- **مدیریت بهینه**: رطوبت خاک در محدوده مناسب است.") - else: - st.warning("لطفاً ابتدا یک نقشه تولید کنید.") - -# Data Entry Page -elif selected == "ورود اطلاعات": - st.markdown("## ورود اطلاعات روزانه مزارع") - - tab1, tab2 = st.tabs(["ورود دستی", "آپلود فایل"]) - - with tab1: - col1, col2 = st.columns(2) - - with col1: - selected_week = st.selectbox( - "انتخاب هفته", - options=[str(i) for i in range(1, 23)], - format_func=lambda x: f"هفته {x}" + fig_ma.update_layout( + font_family="Vazirmatn", + title_font_family="Vazirmatn", + title_font_size=20, + yaxis_title='NDVI' ) + + st.plotly_chart(fig_ma, use_container_width=True) with col2: - selected_day = st.selectbox( - "انتخاب روز", - options=["شنبه", "یکشنبه", "دوشنبه", "سه‌شنبه", "چهارشنبه", "پنجشنبه"] - ) - - filtered_farms = farm_df[farm_df['روز'] == selected_day] - - if filtered_farms.empty: - st.warning(f"هیچ مزرعه‌ای برای روز {selected_day} در پایگاه داده وجود ندارد.") - else: - st.markdown("### ورود داده‌های مزارع") - - data_key = f"data_{selected_week}_{selected_day}" - if data_key not in st.session_state: - st.session_state[data_key] = pd.DataFrame({ - 'مزرعه': filtered_farms['مزرعه'], - 'ایستگاه 1': [0] * len(filtered_farms), - 'ایستگاه 2': [0] * len(filtered_farms), - 'ایستگاه 3': [0] * len(filtered_farms), - 'ایستگاه 4': [0] * len(filtered_farms), - 'ایستگاه 5': [0] * len(filtered_farms), - 'چاهک 1': [0] * len(filtered_farms), - 'چاهک 2': [0] * len(filtered_farms), - 'رطوبت غلاف': [0] * len(filtered_farms), - 'نیتروژن': [0] * len(filtered_farms), - 'میانگین ارتفاع': [0] * len(filtered_farms) - }) + # تحلیل فصلی + monthly_data = farm_time_data.groupby('month').agg({ + 'ndvi': 'mean', + 'height': 'mean' + }).reset_index() - edited_df = st.data_editor( - st.session_state[data_key], - use_container_width=True, - num_rows="fixed", - column_config={ - "مزرعه": st.column_config.TextColumn("مزرعه", disabled=True), - "ایستگاه 1": st.column_config.NumberColumn("ایستگاه 1", min_value=0, max_value=300, step=1), - "ایستگاه 2": st.column_config.NumberColumn("ایستگاه 2", min_value=0, max_value=300, step=1), - "ایستگاه 3": st.column_config.NumberColumn("ایستگاه 3", min_value=0, max_value=300, step=1), - "ایستگاه 4": st.column_config.NumberColumn("ایستگاه 4", min_value=0, max_value=300, step=1), - "ایستگاه 5": st.column_config.NumberColumn("ایستگاه 5", min_value=0, max_value=300, step=1), - "چاهک 1": st.column_config.NumberColumn("چاهک 1", min_value=0, max_value=300, step=1), - "چاهک 2": st.column_config.NumberColumn("چاهک 2", min_value=0, max_value=300, step=1), - "رطوبت غلاف": st.column_config.NumberColumn("رطوبت غلاف", min_value=0, max_value=100, step=1), - "نیتروژن": st.column_config.NumberColumn("نیتروژن", min_value=0, max_value=100, step=1), - "میانگین ارتفاع": st.column_config.NumberColumn("میانگین ارتفاع", disabled=True), - }, - hide_index=True + fig_seasonal = px.bar( + monthly_data, + x='month', + y=['ndvi', 'height'], + title='میانگین ماهانه NDVI و ارتفاع', + labels={'month': 'ماه', 'value': 'مقدار', 'variable': 'متغیر'}, + barmode='group' ) - for i in range(len(edited_df)): - stations = [ - edited_df.iloc[i]['ایستگاه 1'], - edited_df.iloc[i]['ایستگاه 2'], - edited_df.iloc[i]['ایستگاه 3'], - edited_df.iloc[i]['ایستگاه 4'], - edited_df.iloc[i]['ایستگاه 5'] - ] - valid_stations = [s for s in stations if s > 0] - if valid_stations: - edited_df.iloc[i, edited_df.columns.get_loc('میانگین ارتفاع')] = round(sum(valid_stations) / len(valid_stations), 1) - - st.session_state[data_key] = edited_df + fig_seasonal.update_layout( + font_family="Vazirmatn", + title_font_family="Vazirmatn", + title_font_size=20, + yaxis_title='مقدار' + ) - if st.button("ذخیره اطلاعات", type="primary", use_container_width=True): - new_data = edited_df.copy() - new_data['Farm_ID'] = new_data['مزرعه'] - new_data['Week'] = int(selected_week) - new_data['Measurement_Date'] = (datetime.now() - timedelta(weeks=(22 - int(selected_week)))).strftime('%Y-%m-%d') - new_data['Height'] = new_data['میانگین ارتفاع'] - new_data['Station1'] = new_data['ایستگاه 1'] - new_data['Station2'] = new_data['ایستگاه 2'] - new_data['Station3'] = new_data['ایستگاه 3'] - new_data['Station4'] = new_data['ایستگاه 4'] - new_data['Station5'] = new_data['ایستگاه 5'] - new_data['Groundwater1'] = new_data['چاهک 1'] - new_data['Groundwater2'] = new_data['چاهک 2'] - new_data['Sheath_Moisture'] = new_data['رطوبت غلاف'] - new_data['Nitrogen'] = new_data['نیتروژن'] - - new_data = new_data.merge( - farm_df[['مزرعه', 'واریته', 'سن', 'مساحت', 'کانال', 'اداره']], - left_on='Farm_ID', - right_on='مزرعه', - how='left' - ) - - new_data = new_data.rename(columns={ - 'واریته': 'Variety', - 'سن': 'Age', - 'مساحت': 'Area', - 'کانال': 'Channel', - 'اداره': 'Administration' - }) - - st.session_state.heights_df = pd.concat([st.session_state.heights_df, new_data], ignore_index=True) - st.success(f"داده‌های هفته {selected_week} برای روز {selected_day} با موفقیت ذخیره شدند.") - st.balloons() + st.plotly_chart(fig_seasonal, use_container_width=True) - with tab2: - st.markdown("### آپلود فایل اکسل") + with analysis_tabs[2]: + st.subheader("تشخیص تنش‌ها") - uploaded_file = st.file_uploader("فایل اکسل خود را آپلود کنید", type=["xlsx", "xls", "csv"]) + # انتخاب مزرعه + farm_id = st.selectbox("انتخاب مزرعه برای تحلیل", farm_df['farm_id'].tolist(), format_func=lambda x: f"{x} - {farm_df[farm_df['farm_id'] == x]['name'].values[0]}", key="farm_select_stress") - if uploaded_file is not None: - try: - if uploaded_file.name.endswith('.csv'): - df = pd.read_csv(uploaded_file) - else: - df = pd.read_excel(uploaded_file) - st.dataframe(df, use_container_width=True) - if st.button("ذخیره فایل", type="primary"): - st.success("فایل با موفقیت ذخیره شد.") - st.balloons() - except Exception as e: - st.error(f"خطا در خواندن فایل: {e}") + # ایجاد داده‌های نمونه برای تنش‌ها + dates = pd.date_range(end=datetime.now(), periods=90) + stress_data = [] - st.markdown("### راهنمای فرمت فایل") - st.markdown(""" - فایل اکسل باید شامل ستون‌های زیر باشد: - - مزرعه - - ایستگاه 1 تا 5 - - چاهک 1 و 2 - - رطوبت غلاف - - نیتروژن - می‌توانید از [این فایل نمونه](https://example.com/sample.xlsx) به عنوان الگو استفاده کنید. - """) + # شبیه‌سازی تنش آبی + water_stress_threshold = 0.3 + base_ndwi = np.random.uniform(0.2, 0.5) - st.markdown(""" -
- - - - - -

فایل خود را اینجا رها کنید یا روی دکمه بالا کلیک کنید

-
- """, unsafe_allow_html=True) - -# Data Analysis Page -elif selected == "تحلیل داده‌ها": - st.markdown("## تحلیل هوشمند داده‌ها") - - col1, col2 = st.columns([1, 2]) - - with col1: - st_lottie(lottie_analysis, height=200, key="analysis_animation") - - with col2: - st.markdown(""" -
-

تحلیل پیشرفته داده‌های مزارع

-

در این بخش می‌توانید تحلیل‌های پیشرفته روی داده‌های مزارع انجام دهید و روندها و الگوهای مختلف را بررسی کنید.

-
- """, unsafe_allow_html=True) - - tab1, tab2, tab3, tab4 = st.tabs(["تحلیل رشد", "مقایسه واریته‌ها", "تحلیل رطوبت", "پیش‌بینی"]) - - with tab1: - st.markdown("### تحلیل رشد مزارع") - - col1, col2 = st.columns(2) - - with col1: - selected_variety = st.selectbox( - "انتخاب واریته", - ["all"] + list(farm_df['واریته'].unique()), - format_func=lambda x: "همه واریته‌ها" if x == "all" else x, - key="growth_variety" - ) + # شبیه‌سازی تنش بیماری + disease_threshold = 0.4 + base_chl = np.random.uniform(1.5, 2.5) - with col2: - selected_age = st.selectbox( - "انتخاب سن", - ["all"] + list(farm_df['سن'].unique()), - format_func=lambda x: "همه سنین" if x == "all" else x, - key="growth_age" - ) - - growth_data = generate_mock_growth_data(farm_df, selected_variety, selected_age) - - if growth_data['individual']: - chart_data = [] - for farm_data in growth_data['individual']: - for i, week in enumerate(farm_data['weeks']): - chart_data.append({ - 'Farm': farm_data['farm_id'], - 'Week': week, - 'Height': farm_data['heights'][i], - 'Variety': farm_data['variety'], - 'Age': farm_data['age'] - }) - - chart_df = pd.DataFrame(chart_data) + for date in dates: + day_of_year = date.dayofyear + seasonal_factor = np.sin(day_of_year / 365 * 2 * np.pi) * 0.2 - chart = alt.Chart(chart_df).mark_line(point=True).encode( - x=alt.X('Week:Q', title='هفته'), - y=alt.Y('Height:Q', title='ارتفاع (سانتی‌متر)'), - color=alt.Color('Farm:N', title='مزرعه'), - tooltip=['Farm', 'Week', 'Height', 'Variety', 'Age'] - ).properties( - width='container', - height=400, - title='روند رشد مزارع بر اساس هفته' - ).interactive() - - st.altair_chart(chart, use_container_width=True) - - st.markdown("### تحلیل نرخ رشد") - - growth_rates = [] - for farm_data in growth_data['individual']: - heights = farm_data['heights'] - for i in range(1, len(heights)): - growth_rate = heights[i] - heights[i-1] - growth_rates.append({ - 'Farm': farm_data['farm_id'], - 'Week': farm_data['weeks'][i], - 'Growth Rate': growth_rate, - 'Variety': farm_data['variety'], - 'Age': farm_data['age'] - }) + # شبیه‌سازی تنش آبی در روزهای خاص + water_stress_event = False + if 30 <= day_of_year <= 40 or 70 <= day_of_year <= 75: + water_stress_event = True + ndwi = base_ndwi - 0.3 + np.random.normal(0, 0.05) + else: + ndwi = base_ndwi + seasonal_factor + np.random.normal(0, 0.05) - growth_rate_df = pd.DataFrame(growth_rates) + # شبیه‌سازی تنش بیماری در روزهای خاص + disease_event = False + if 50 <= day_of_year <= 60: + disease_event = True + chl = base_chl - 0.5 + np.random.normal(0, 0.1) + else: + chl = base_chl + seasonal_factor + np.random.normal(0, 0.1) - chart = alt.Chart(growth_rate_df).mark_bar().encode( - x=alt.X('Week:O', title='هفته'), - y=alt.Y('mean(Growth Rate):Q', title='نرخ رشد (سانتی‌متر در هفته)'), - color=alt.Color('Farm:N', title='مزرعه'), - tooltip=['Farm', 'Week', 'mean(Growth Rate)'] - ).properties( - width='container', - height=400, - title='نرخ رشد هفتگی مزارع' - ).interactive() + # محدود کردن مقادیر + ndwi = max(-0.5, min(0.8, ndwi)) + chl = max(0.5, min(3.0, chl)) - st.altair_chart(chart, use_container_width=True) - else: - st.warning("داده‌ای برای نمایش وجود ندارد.") - - with tab2: - st.markdown("### مقایسه واریته‌ها") + stress_data.append({ + 'date': date, + 'ndwi': ndwi, + 'chl': chl, + 'water_stress': 1 if ndwi < water_stress_threshold else 0, + 'disease_stress': 1 if chl < disease_threshold else 0, + 'water_stress_event': water_stress_event, + 'disease_stress_event': disease_event + }) - variety_age_groups = farm_df.groupby(['واریته', 'سن']).size().reset_index(name='تعداد') + stress_df = pd.DataFrame(stress_data) - fig = px.density_heatmap( - variety_age_groups, - x='واریته', - y='سن', - z='تعداد', - title='توزیع مزارع بر اساس واریته و سن', - color_continuous_scale='Viridis' - ) - fig.update_layout( - font=dict(family="Vazirmatn"), - template="plotly_white", - xaxis_title="واریته", - yaxis_title="سن" - ) - st.plotly_chart(fig, use_container_width=True) - - varieties = farm_df['واریته'].unique() - variety_heights = {variety: np.random.normal(150, 20, 100) for variety in varieties} - - fig = go.Figure() - for variety in varieties: - fig.add_trace(go.Box( - y=variety_heights[variety], - name=variety, - boxpoints='outliers', - marker_color=f'hsl({hash(variety) % 360}, 70%, 50%)' - )) - fig.update_layout( - title='مقایسه ارتفاع بر اساس واریته', - yaxis_title='ارتفاع (سانتی‌متر)', - font=dict(family="Vazirmatn"), - template="plotly_white", - boxmode='group' - ) - st.plotly_chart(fig, use_container_width=True) - - st.markdown("### مقایسه آماری واریته‌ها") - variety_stats = {} - for variety in varieties: - heights = variety_heights[variety] - variety_stats[variety] = { - 'میانگین': np.mean(heights), - 'میانه': np.median(heights), - 'انحراف معیار': np.std(heights), - 'حداقل': np.min(heights), - 'حداکثر': np.max(heights) - } - variety_stats_df = pd.DataFrame(variety_stats).T - st.dataframe(variety_stats_df, use_container_width=True) - - with tab3: - st.markdown("### تحلیل رطوبت مزارع") - - farms = farm_df['مزرعه'].unique()[:10] - dates = pd.date_range(end=datetime.now(), periods=30, freq='D') - - moisture_data = [] - for farm in farms: - base_moisture = np.random.uniform(50, 80) - for date in dates: - moisture = base_moisture + np.random.normal(0, 5) - moisture = max(0, min(100, moisture)) - moisture_data.append({ - 'Farm': farm, - 'Date': date, - 'Moisture': moisture - }) + # نمایش نمودار تنش‌ها + fig_stress = go.Figure() - moisture_df = pd.DataFrame(moisture_data) - - fig = px.line( - moisture_df, - x='Date', - y='Moisture', - color='Farm', - title='روند رطوبت مزارع در 30 روز گذشته', - labels={'Date': 'تاریخ', 'Moisture': 'رطوبت (%)', 'Farm': 'مزرعه'} - ) - fig.update_layout( - font=dict(family="Vazirmatn"), - template="plotly_white", - hovermode="x unified" - ) - st.plotly_chart(fig, use_container_width=True) - - st.markdown("### همبستگی رطوبت و ارتفاع") - - correlation_data = [] - for farm in farms: - for _ in range(20): - moisture = np.random.uniform(40, 90) - height = 100 + moisture * 1.5 + np.random.normal(0, 20) - correlation_data.append({ - 'Farm': farm, - 'Moisture': moisture, - 'Height': height - }) + # اضافه کردن NDWI + fig_stress.add_trace(go.Scatter( + x=stress_df['date'], + y=stress_df['ndwi'], + name='NDWI', + line=dict(color='blue') + )) - correlation_df = pd.DataFrame(correlation_data) + # اضافه کردن CHL + fig_stress.add_trace(go.Scatter( + x=stress_df['date'], + y=stress_df['chl'], + name='CHL', + line=dict(color='green'), + yaxis='y2' + )) - fig = px.scatter( - correlation_df, - x='Moisture', - y='Height', - color='Farm', - title='همبستگی بین رطوبت و ارتفاع', - labels={'Moisture': 'رطوبت (%)', 'Height': 'ارتفاع (سانتی‌متر)', 'Farm': 'مزرعه'}, - trendline='ols' - ) - fig.update_layout( - font=dict(family="Vazirmatn"), - template="plotly_white" + # اضافه کردن خط آستانه تنش آبی + fig_stress.add_shape( + type='line', + x0=stress_df['date'].min(), + y0=water_stress_threshold, + x1=stress_df['date'].max(), + y1=water_stress_threshold, + line=dict(color='red', dash='dash'), + name='آستانه تنش آبی' ) - st.plotly_chart(fig, use_container_width=True) - correlation = correlation_df['Moisture'].corr(correlation_df['Height']) - st.info(f"ضریب همبستگی بین رطوبت و ارتفاع: {correlation:.2f}") - - with tab4: - st.markdown("### پیش‌بینی رشد مزارع") - - selected_farm_for_prediction = st.selectbox( - "انتخاب مزرعه", - options=farm_df['مزرعه'].tolist(), - format_func=lambda x: f"مزرعه {x}" + # اضافه کردن خط آستانه تنش بیماری + fig_stress.add_shape( + type='line', + x0=stress_df['date'].min(), + y0=disease_threshold, + x1=stress_df['date'].max(), + y1=disease_threshold, + line=dict(color='orange', dash='dash'), + yaxis='y2', + name='آستانه تنش بیماری' ) - weeks = list(range(1, 16)) - heights = [50 + i * 10 + np.random.normal(0, 5) for i in range(len(weeks))] - - historical_df = pd.DataFrame({ - 'Week': weeks, - 'Height': heights - }) - - future_weeks = list(range(16, 23)) - - from sklearn.linear_model import LinearRegression - - model = LinearRegression() - model.fit(np.array(weeks).reshape(-1, 1), heights) - - future_heights = model.predict(np.array(future_weeks).reshape(-1, 1)) - - lower_bound = future_heights - 15 - upper_bound = future_heights + 15 - - future_df = pd.DataFrame({ - 'Week': future_weeks, - 'Height': future_heights, - 'Lower': lower_bound, - 'Upper': upper_bound - }) - - fig = go.Figure() - - fig.add_trace(go.Scatter( - x=historical_df['Week'], - y=historical_df['Height'], - mode='lines+markers', - name='داده‌های تاریخی', - line=dict(color='#1a8754', width=3), - marker=dict(size=8, color='#1a8754') - )) - - fig.add_trace(go.Scatter( - x=future_df['Week'], - y=future_df['Height'], - mode='lines+markers', - name='پیش‌بینی', - line=dict(color='#ff9800', width=3, dash='dash'), - marker=dict(size=8, color='#ff9800') - )) - - fig.add_trace(go.Scatter( - x=future_df['Week'].tolist() + future_df['Week'].tolist()[::-1], - y=future_df['Upper'].tolist() + future_df['Lower'].tolist()[::-1], - fill='toself', - fillcolor='rgba(255, 152, 0, 0.2)', - line=dict(color='rgba(255, 152, 0, 0)'), - hoverinfo='skip', - showlegend=False - )) - - fig.update_layout( - title=f'پیش‌بینی رشد مزرعه {selected_farm_for_prediction}', - xaxis_title='هفته', - yaxis_title='ارتفاع (سانتی‌متر)', - font=dict(family='Vazirmatn', size=14), - hovermode='x unified', - template='plotly_white', - height=500, + # تنظیمات نمودا�� + fig_stress.update_layout( + title='تشخیص تنش‌های آبی و بیماری', + font_family="Vazirmatn", + title_font_family="Vazirmatn", + title_font_size=20, + xaxis_title='تاریخ', + yaxis=dict( + title='NDWI', + titlefont=dict(color='blue'), + tickfont=dict(color='blue') + ), + yaxis2=dict( + title='CHL', + titlefont=dict(color='green'), + tickfont=dict(color='green'), + anchor='x', + overlaying='y', + side='right' + ), legend=dict( - orientation="h", - yanchor="bottom", + orientation='h', + yanchor='bottom', y=1.02, - xanchor="right", + xanchor='right', x=1 ) ) - fig.add_vline(x=15.5, line_width=1, line_dash="dash", line_color="gray") + st.plotly_chart(fig_stress, use_container_width=True) - st.plotly_chart(fig, use_container_width=True) - - st.markdown("### جزئیات پیش‌بینی") + # نمایش هشدارها + st.subheader("هشدارهای تنش") col1, col2 = st.columns(2) with col1: - st.metric( - label="ارتفاع فعلی", - value=f"{heights[-1]:.1f} cm", - delta=f"{heights[-1] - heights[-2]:.1f} cm" - ) + # هشدارهای تنش آبی + water_stress_days = stress_df[stress_df['water_stress'] == 1] + + if len(water_stress_days) > 0: + st.warning(f"تنش آبی در {len(water_stress_days)} روز تشخیص داده شده است.") + + st.dataframe( + water_stress_days[['date', 'ndwi']].sort_values('date', ascending=False).head(5), + column_config={ + "date": "تاریخ", + "ndwi": st.column_config.NumberColumn("NDWI", format="%.2f") + }, + hide_index=True + ) + else: + st.success("هیچ تنش آبی تشخیص داده نشده است.") with col2: - st.metric( - label="ارتفاع پیش‌بینی شده (هفته 22)", - value=f"{future_heights[-1]:.1f} cm", - delta=f"{future_heights[-1] - heights[-1]:.1f} cm" - ) - - prediction_table = pd.DataFrame({ - 'هفته': future_weeks, - 'ارتفاع پیش‌بینی شده': [f"{h:.1f}" for h in future_heights], - 'حد پایین': [f"{l:.1f}" for l in lower_bound], - 'حد بالا': [f"{u:.1f}" for u in upper_bound] - }) + # هشدارهای تنش بیماری + disease_stress_days = stress_df[stress_df['disease_stress'] == 1] + + if len(disease_stress_days) > 0: + st.warning(f"تنش بیماری در {len(disease_stress_days)} روز تشخیص داده شده است.") + + st.dataframe( + disease_stress_days[['date', 'chl']].sort_values('date', ascending=False).head(5), + column_config={ + "date": "تاریخ", + "chl": st.column_config.NumberColumn("CHL", format="%.2f") + }, + hide_index=True + ) + else: + st.success("هیچ تنش بیماری تشخیص داده نشده است.") + + with analysis_tabs[3]: + st.subheader("پیش‌بینی رشد") - st.dataframe(prediction_table, use_container_width=True, hide_index=True) + # انتخاب مزرعه + farm_id = st.selectbox("انتخاب مزرعه برای پیش‌بینی", farm_df['farm_id'].tolist(), format_func=lambda x: f"{x} - {farm_df[farm_df['farm_id'] == x]['name'].values[0]}", key="farm_select_predict") - st.markdown("### عوامل مؤثر بر رشد") + # فیلتر داده‌ها برای مزرعه انتخاب شده + farm_time_data = time_df[time_df['farm_id'] == farm_id] - factors = ['رطوبت', 'نیتروژن', 'دما', 'آبیاری', 'نور'] - factor_values = [85, 70, 60, 90, 75] + # تنظیمات پیش‌بینی + st.subheader("تنظیمات پیش‌بینی") - fig = go.Figure() + col1, col2 = st.columns(2) - fig.add_trace(go.Scatterpolar( - r=factor_values, - theta=factors, - fill='toself', - name='عوامل مؤثر', - line_color='#1a8754' - )) + with col1: + days_to_predict = st.slider("تعداد روزهای پیش‌بینی", min_value=7, max_value=90, value=30) + + with col2: + confidence_interval = st.slider("فاصله اطمینان (%)", min_value=50, max_value=99, value=95) - fig.update_layout( - polar=dict( - radialaxis=dict( - visible=True, - range=[0, 100] + if st.button("انجام پیش‌بینی"): + with st.spinner("در حال انجام پیش‌بینی..."): + # ایجاد داده‌های آینده برای پیش‌بینی + last_date = farm_time_data['date'].max() + future_dates = pd.date_range(start=last_date + timedelta(days=1), periods=days_to_predict) + + future_data = [] + for date in future_dates: + future_data.append({ + 'date': date, + 'day_of_year': date.dayofyear, + 'month': date.month + }) + + future_df = pd.DataFrame(future_data) + + # پیش‌بینی NDVI برای روزهای آینده (با الگوی سینوسی + روند) + base_ndvi = farm_time_data['ndvi'].iloc[-1] + future_ndvi = [] + + for day in future_df['day_of_year']: + seasonal_factor = np.sin(day / 365 * 2 * np.pi) * 0.2 + trend_factor = day / 365 * 0.1 + ndvi = base_ndvi + seasonal_factor + trend_factor + ndvi = max(0, min(1, ndvi)) # محدود کردن به بازه 0 تا 1 + future_ndvi.append(ndvi) + + future_df['ndvi'] = future_ndvi + + # پیش‌بینی ارتفاع با استفاده از مدل + X_future = future_df[['ndvi', 'day_of_year', 'month']].values + future_df['height'] = model.predict(X_future) + + # محاسبه فاصله اطمینان + z_score = { + 90: 1.645, + 95: 1.96, + 99: 2.576 + }.get(confidence_interval, 1.96) + + mse = mean_squared_error(y_test, y_pred) + std_dev = np.sqrt(mse) + + future_df['height_lower'] = future_df['height'] - z_score * std_dev + future_df['height_upper'] = future_df['height'] + z_score * std_dev + + # ترکیب داده‌های گذشته و آینده + combined_df = pd.concat([ + farm_time_data[['date', 'ndvi', 'height']], + future_df[['date', 'ndvi', 'height', 'height_lower', 'height_upper']] + ]) + + # نمایش نمودار پیش‌بینی + fig_predict = go.Figure() + + # داده‌های گذشته + past_data = combined_df[combined_df['date'] <= last_date] + + fig_predict.add_trace(go.Scatter( + x=past_data['date'], + y=past_data['height'], + name='ارتفاع (گذشته)', + line=dict(color='blue') + )) + + # داده‌های پیش‌بینی شده + future_data = combined_df[combined_df['date'] > last_date] + + fig_predict.add_trace(go.Scatter( + x=future_data['date'], + y=future_data['height'], + name='ارتفاع (پیش‌بینی)', + line=dict(color='red') + )) + + # فاصله اطمینان + fig_predict.add_trace(go.Scatter( + x=future_data['date'], + y=future_data['height_upper'], + name=f'فاصله اطمینان {confidence_interval}%', + line=dict(width=0), + showlegend=False + )) + + fig_predict.add_trace(go.Scatter( + x=future_data['date'], + y=future_data['height_lower'], + name=f'فاصله اطمینان {confidence_interval}%', + line=dict(width=0), + fillcolor='rgba(255, 0, 0, 0.2)', + fill='tonexty', + showlegend=True + )) + + # تنظیمات نمودار + fig_predict.update_layout( + title=f'پیش‌بینی ارتفاع برای {days_to_predict} روز آینده', + font_family="Vazirmatn", + title_font_family="Vazirmatn", + title_font_size=20, + xaxis_title='تاریخ', + yaxis_title='ارتفاع (متر)', + legend=dict( + orientation='h', + yanchor='bottom', + y=1.02, + xanchor='right', + x=1 + ) ) - ), - showlegend=False, - font=dict(family='Vazirmatn'), - height=400 - ) - - st.plotly_chart(fig, use_container_width=True) + + st.plotly_chart(fig_predict, use_container_width=True) + + # نمایش جدول پیش‌بینی + st.subheader("جدول پیش‌بینی") + + st.dataframe( + future_df[['date', 'ndvi', 'height', 'height_lower', 'height_upper']], + column_config={ + "date": "تاریخ", + "ndvi": st.column_config.NumberColumn("NDVI", format="%.2f"), + "height": st.column_config.NumberColumn("ارتفاع (متر)", format="%.2f"), + "height_lower": st.column_config.NumberColumn(f"حد پایین ({confidence_interval}%)", format="%.2f"), + "height_upper": st.column_config.NumberColumn(f"حد بالا ({confidence_interval}%)", format="%.2f") + }, + hide_index=True + ) + + # نمایش خلاصه پیش‌بینی + st.subheader("خلاصه پیش‌بینی") + + col1, col2, col3 = st.columns(3) + + with col1: + current_height = past_data['height'].iloc[-1] + final_height = future_data['height'].iloc[-1] + growth = final_height - current_height + + st.metric( + "رشد پیش‌بینی شده", + f"{growth:.2f} متر", + f"{growth / current_height * 100:.1f}%" + ) + + with col2: + st.metric( + "ارتفاع فعلی", + f"{current_height:.2f} متر" + ) + + with col3: + st.metric( + "ارتفاع نهایی پیش‌بینی شده", + f"{final_height:.2f} متر" + ) -# Reporting Page -elif selected == "گزارش‌گیری": - st.markdown("## گزارش‌گیری پیشرفته") +# تب گزارش‌گیری +with tabs[4]: + st.header("گزارش‌گیری") - col1, col2 = st.columns([1, 2]) - - with col1: - st_lottie(lottie_report, height=200, key="report_animation") - - with col2: - st.markdown(""" -
-

گزارش‌گیری پیشرفته

-

در این بخش می‌توانید گزارش‌های مختلف از وضعیت مزارع تهیه کنید و آن‌ها را به صورت PDF یا Excel دانلود کنید.

-
- """, unsafe_allow_html=True) + # انتخاب نوع گزارش + report_type = st.selectbox( + "انتخاب نوع گزارش", + ["گزارش وضعیت کلی مزارع", "گزارش تحلیل شاخص‌ها", "گزارش تنش‌ها", "گزارش پیش‌بینی رشد"] + ) + # انتخاب بازه زمانی col1, col2 = st.columns(2) with col1: - start_date = st.date_input( - "تاریخ شروع", - value=datetime.now() - timedelta(days=30), - format="YYYY-MM-DD" - ) + start_date = st.date_input("تاریخ شروع", value=datetime.now() - timedelta(days=30)) with col2: - end_date = st.date_input( - "تاریخ پایان", - value=datetime.now(), - format="YYYY-MM-DD" - ) + end_date = st.date_input("تاریخ پایان", value=datetime.now()) - report_type = st.selectbox( - "نوع گزارش", - options=["گزارش کلی", "گزارش رشد", "گزارش رطوبت", "گزارش مقایسه‌ای واریته‌ها"] + # انتخاب مزارع + selected_farms = st.multiselect( + "انتخاب مزارع", + options=farm_df['farm_id'].tolist(), + format_func=lambda x: f"{x} - {farm_df[farm_df['farm_id'] == x]['name'].values[0]}" ) - if st.button("تولید گزارش", type="primary", use_container_width=True): - with st.spinner('در حال تولید گزارش...'): - time.sleep(2) - - if report_type == "گزارش کلی": - st.markdown("### گزارش کلی وضعیت مزارع") + if not selected_farms: + selected_farms = farm_df['farm_id'].tolist() # اگر هیچ مزرعه‌ای انتخاب نشده باشد، همه مزارع انتخاب می‌شوند + + # فیلتر داده‌ها بر اساس مزارع انتخاب شده + filtered_farm_df = farm_df[farm_df['farm_id'].isin(selected_farms)] + + if st.button("تولید گزارش"): + with st.spinner("در حال تولید گزارش..."): + if report_type == "گزارش وضعیت کلی مزارع": + st.subheader("گزارش وضعیت کلی مزارع") + + # نمودارهای تحلیلی + fig_crop, fig_height, fig_humidity = create_analysis_charts(filtered_farm_df) + + col1, col2 = st.columns(2) + + with col1: + st.plotly_chart(fig_crop, use_container_width=True) + + with col2: + st.plotly_chart(fig_height, use_container_width=True) + + st.plotly_chart(fig_humidity, use_container_width=True) + + # جدول اطلاعات مزارع + st.subheader("اطلاعات مزارع") + st.dataframe( + filtered_farm_df[['farm_id', 'name', 'crop_type', 'area', 'height', 'humidity', 'last_update']], + column_config={ + "farm_id": "شناسه", + "name": "نام مزرعه", + "crop_type": "نوع محصول", + "area": st.column_config.NumberColumn("مساحت (هکتار)", format="%.2f"), + "height": st.column_config.NumberColumn("ارتفاع (متر)", format="%.2f"), + "humidity": st.column_config.NumberColumn("رطوبت (%)", format="%.1f"), + "last_update": "آخرین بروزرسانی" + }, + hide_index=True + ) + + # آمار خلاصه + st.subheader("آمار خلاصه") col1, col2, col3, col4 = st.columns(4) with col1: - st.metric("تعداد کل مزارع", len(farm_df)) + st.metric("تعداد مزارع", len(filtered_farm_df)) with col2: - st.metric("میانگین ارتفاع", f"{np.random.uniform(150, 200):.1f} cm") + st.metric("میانگین مساحت", f"{filtered_farm_df['area'].mean():.2f} هکتار") with col3: - st.metric("میانگین رطوبت", f"{np.random.uniform(60, 80):.1f}%") + st.metric("میانگین ارتفاع", f"{filtered_farm_df['height'].mean():.2f} متر") with col4: - st.metric("میانگین نیتروژن", f"{np.random.uniform(40, 60):.1f}%") - - farm_counts = farm_df['اداره'].value_counts() - fig = px.pie( - values=farm_counts.values, - names=farm_counts.index, - title='توزیع مزارع بر اساس اداره', - color_discrete_sequence=px.colors.sequential.Greens_r + st.metric("میانگین رطوبت", f"{filtered_farm_df['humidity'].mean():.1f}%") + + # نقشه مزارع + st.subheader("نقشه مزارع") + farm_map = create_farm_map(filtered_farm_df) + folium_static(farm_map, width=800, height=500) + + # دانلود گزارش + st.subheader("دانلود گزارش") + + # ایجاد فایل PDF + figs = [fig_crop, fig_height, fig_humidity] + tables = [filtered_farm_df[['farm_id', 'name', 'crop_type', 'area', 'height', 'humidity', 'last_update']]] + + pdf_buffer = create_report_pdf(figs, tables, "گزارش وضعیت کلی مزارع") + + # دانلود PDF + st.download_button( + label="دانلود گزارش (PDF)", + data=pdf_buffer, + file_name="farm_report.pdf", + mime="application/pdf" ) - fig.update_traces(textposition='inside', textinfo='percent+label') - fig.update_layout(font=dict(family="Vazirmatn")) - st.plotly_chart(fig, use_container_width=True) - - weeks = list(range(1, 23)) - heights = [100 + i * 5 + np.random.normal(0, 10) for i in range(len(weeks))] - fig = px.line( - x=weeks, - y=heights, - title='روند رشد کلی مزارع', - labels={'x': 'هفته', 'y': 'ارتفاع (سانتی‌متر)'} + + # دانلود Excel + excel_buffer = io.BytesIO() + filtered_farm_df.to_excel(excel_buffer, index=False) + excel_buffer.seek(0) + + st.download_button( + label="دانلود داده‌ها (Excel)", + data=excel_buffer, + file_name="farm_data.xlsx", + mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ) - fig.update_layout(font=dict(family="Vazirmatn")) - st.plotly_chart(fig, use_container_width=True) - - top_farms = pd.DataFrame({ - 'مزرعه': ['مزرعه ' + str(i) for i in range(1, 6)], - 'ارتفاع': [round(np.random.uniform(180, 220), 1) for _ in range(5)], - 'رطوبت': [round(np.random.uniform(60, 80), 1) for _ in range(5)], - 'نیتروژن': [round(np.random.uniform(40, 60), 1) for _ in range(5)] - }) - st.markdown("### 5 مزرعه برتر") - st.dataframe(top_farms, use_container_width=True, hide_index=True) - elif report_type == "گزارش رشد": - st.markdown("### گزارش رشد مزارع") + elif report_type == "گزارش تحلیل شاخص‌ها": + st.subheader("گزارش تحلیل شاخص‌ها") - col1, col2, col3 = st.columns(3) + # ایجاد داده‌های نمونه برای شاخص‌ها + indices_data = [] - with col1: - st.metric("میانگین رشد هفتگی", f"{np.random.uniform(10, 15):.1f} cm") + for farm_id in selected_farms: + farm_name = farm_df[farm_df['farm_id'] == farm_id]['name'].values[0] + + # مقادیر پایه برای هر مزرعه + base_ndvi = np.random.uniform(0.3, 0.8) + base_ndwi = np.random.uniform(0.1, 0.5) + base_evi = np.random.uniform(0.3, 0.7) + base_ndmi = np.random.uniform(0.2, 0.6) + base_lai = np.random.uniform(1.0, 4.0) + base_chl = np.random.uniform(1.0, 3.0) + + # ایجاد داده‌ها برای بازه زمانی انتخاب شده + date_range = pd.date_range(start=start_date, end=end_date) + + for date in date_range: + # شبیه‌سازی تغییرات با الگوی سینوسی + day_of_year = date.dayofyear + seasonal_factor = np.sin(day_of_year / 365 * 2 * np.pi) * 0.1 + + ndvi = base_ndvi + seasonal_factor + np.random.normal(0, 0.05) + ndwi = base_ndwi + seasonal_factor + np.random.normal(0, 0.05) + evi = base_evi + seasonal_factor + np.random.normal(0, 0.05) + ndmi = base_ndmi + seasonal_factor + np.random.normal(0, 0.05) + lai = base_lai + seasonal_factor * 2 + np.random.normal(0, 0.2) + chl = base_chl + seasonal_factor + np.random.normal(0, 0.1) + + # محدود کردن مقادیر + ndvi = max(0, min(1, ndvi)) + ndwi = max(-0.5, min(0.8, ndwi)) + evi = max(0, min(1, evi)) + ndmi = max(-0.5, min(0.8, ndmi)) + lai = max(0, lai) + chl = max(0, chl) + + indices_data.append({ + 'farm_id': farm_id, + 'farm_name': farm_name, + 'date': date, + 'ndvi': ndvi, + 'ndwi': ndwi, + 'evi': evi, + 'ndmi': ndmi, + 'lai': lai, + 'chl': chl + }) - with col2: - st.metric("حداکثر رشد هفتگی", f"{np.random.uniform(20, 25):.1f} cm") + indices_df = pd.DataFrame(indices_data) - with col3: - st.metric("حداقل رشد هفتگی", f"{np.random.uniform(5, 10):.1f} cm") - - weeks = list(range(1, 23)) - farms = ['مزرعه 1', 'مزرعه 2', 'مزرعه 3', 'مزرعه 4', 'مزرعه 5'] - fig = go.Figure() - for farm in farms: - heights = [100 + i * 5 + np.random.normal(0, 10) for i in range(len(weeks))] - fig.add_trace(go.Scatter( - x=weeks, - y=heights, - mode='lines+markers', - name=farm - )) - fig.update_layout( - title='روند رشد مزارع', - xaxis_title='هفته', - yaxis_title='ارتفاع (سانتی‌متر)', - font=dict(family="Vazirmatn"), - legend_title='مزرعه', - hovermode="x unified" + # نمایش میانگین شاخص‌ها برای هر مزرعه + avg_indices = indices_df.groupby('farm_name').agg({ + 'ndvi': 'mean', + 'ndwi': 'mean', + 'evi': 'mean', + 'ndmi': 'mean', + 'lai': 'mean', + 'chl': 'mean' + }).reset_index() + + st.subheader("میانگین شاخص‌ها برای هر مزرعه") + st.dataframe( + avg_indices, + column_config={ + "farm_name": "نام مزرعه", + "ndvi": st.column_config.NumberColumn("NDVI", format="%.2f"), + "ndwi": st.column_config.NumberColumn("NDWI", format="%.2f"), + "evi": st.column_config.NumberColumn("EVI", format="%.2f"), + "ndmi": st.column_config.NumberColumn("NDMI", format="%.2f"), + "lai": st.column_config.NumberColumn("LAI", format="%.2f"), + "chl": st.column_config.NumberColumn("CHL", format="%.2f") + }, + hide_index=True ) - st.plotly_chart(fig, use_container_width=True) - - growth_rates = np.random.normal(12, 3, 1000) - fig = px.histogram( - x=growth_rates, - nbins=30, - title='توزیع نرخ رشد هفتگی', - labels={'x': 'نرخ رشد (سانتی‌متر در هفته)', 'y': 'فراوانی'}, - color_discrete_sequence=['#1a8754'] + + # نمودار مقایسه‌ای شاخص‌ها + st.subheader("مقایسه شاخص‌ها بین مزارع") + + # انتخاب شاخص برای نمایش + selected_index = st.selectbox( + "انتخاب شاخص", + ["NDVI", "NDWI", "EVI", "NDMI", "LAI", "CHL"] ) - fig.update_layout(font=dict(family="Vazirmatn")) - st.plotly_chart(fig, use_container_width=True) - - st.markdown("### عوامل مؤثر بر رشد") - factors = ['رطوبت', 'نیتروژن', 'دما', 'آبیاری', 'نور'] - correlations = [0.8, 0.7, 0.5, 0.9, 0.6] - fig = px.bar( - x=factors, - y=correlations, - title='همبستگی عوامل مختلف با نرخ رشد', - labels={'x': 'عامل', 'y': 'ضریب همبستگی'}, - color=correlations, + + index_map = { + "NDVI": "ndvi", + "NDWI": "ndwi", + "EVI": "evi", + "NDMI": "ndmi", + "LAI": "lai", + "CHL": "chl" + } + + # نمودار مقایسه‌ای + fig_compare = px.bar( + avg_indices, + x='farm_name', + y=index_map[selected_index], + title=f'مقایسه {selected_index} بین مزارع', + labels={'farm_name': 'نام مزرعه', index_map[selected_index]: selected_index}, + color=index_map[selected_index], color_continuous_scale='Viridis' ) - fig.update_layout(font=dict(family="Vazirmatn")) - st.plotly_chart(fig, use_container_width=True) - - elif report_type == "گزارش رطوبت": - st.markdown("### گزارش رطوبت مزارع") - col1, col2, col3 = st.columns(3) + fig_compare.update_layout( + font_family="Vazirmatn", + title_font_family="Vazirmatn", + title_font_size=20 + ) - with col1: - st.metric("میانگین رطوبت", f"{np.random.uniform(60, 70):.1f}%") + st.plotly_chart(fig_compare, use_container_width=True) - with col2: - st.metric("حداکثر رطوبت", f"{np.random.uniform(80, 90):.1f}%") + # نمودار سری زمانی + st.subheader("سری زمانی شاخص‌ها") - with col3: - st.metric("حداقل رطوبت", f"{np.random.uniform(40, 50):.1f}%") - - dates = pd.date_range(start=start_date, end=end_date) - moisture_levels = [np.random.uniform(50, 80) for _ in range(len(dates))] - fig = px.line( - x=dates, - y=moisture_levels, - title='روند رطوبت مزارع', - labels={'x': 'تاریخ', 'y': 'رطوبت (%)'} + # انتخاب مزرعه برای نمایش سری زمانی + selected_farm_id = st.selectbox( + "انتخاب مزرعه", + options=selected_farms, + format_func=lambda x: f"{x} - {farm_df[farm_df['farm_id'] == x]['name'].values[0]}" + ) + + # فیلتر داده‌ها برای مزرعه انتخاب شده + farm_indices = indices_df[indices_df['farm_id'] == selected_farm_id] + + # نمودار سری زمانی + fig_ts = px.line( + farm_indices, + x='date', + y=[index_map[selected_index]], + title=f'سری زمانی {selected_index} برای {farm_df[farm_df["farm_id"] == selected_farm_id]["name"].values[0]}', + labels={'date': 'تاریخ', 'value': 'مقدار', 'variable': 'شاخص'} ) - fig.update_layout(font=dict(family="Vazirmatn")) - st.plotly_chart(fig, use_container_width=True) - - fig = px.histogram( - x=moisture_levels, - nbins=30, - title='توزیع رطوبت مزارع', - labels={'x': 'رطوبت (%)', 'y': 'فراوانی'}, - color_discrete_sequence=['#1a8754'] + + fig_ts.update_layout( + font_family="Vazirmatn", + title_font_family="Vazirmatn", + title_font_size=20, + yaxis_title=selected_index + ) + + st.plotly_chart(fig_ts, use_container_width=True) + + # دانلود گزارش + st.subheader("دانلود گزارش") + + # ایجاد فایل PDF + figs = [fig_compare, fig_ts] + tables = [avg_indices] + + pdf_buffer = create_report_pdf(figs, tables, "گزارش تحلیل شاخص‌ها") + + # دانلود PDF + st.download_button( + label="دانلود گزارش (PDF)", + data=pdf_buffer, + file_name="indices_report.pdf", + mime="application/pdf" ) - fig.update_layout(font=dict(family="Vazirmatn")) - st.plotly_chart(fig, use_container_width=True) - - growth_levels = [h + np.random.normal(0, 10) for h in moisture_levels] - fig = px.scatter( - x=moisture_levels, - y=growth_levels, - title='رابطه بین رطوبت و رشد', - labels={'x': 'رطوبت (%)', 'y': 'رشد (سانتی‌متر)'}, - trendline='ols' + + # دانلود Excel + excel_buffer = io.BytesIO() + indices_df.to_excel(excel_buffer, index=False) + excel_buffer.seek(0) + + st.download_button( + label="دانلود داده‌ها (Excel)", + data=excel_buffer, + file_name="indices_data.xlsx", + mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ) - fig.update_layout(font=dict(family="Vazirmatn")) - st.plotly_chart(fig, use_container_width=True) - - st.markdown("### توصیه‌های مدیریت رطوبت") - recommendations = [ - "افزایش دفعات آبیاری در مزارع با رطوبت پایین", - "بهبود سیستم زهکشی در مزارع با رطوبت بالا", - "استفاده از مالچ برای حفظ رطوبت خاک", - "تنظیم زمان آبیاری بر اساس شرایط آب و هوایی", - "پایش مداوم رطوبت خاک با استفاده از سنسورها" - ] - for rec in recommendations: - st.markdown(f"- {rec}") - elif report_type == "گزارش مقایسه‌ای واریته‌ها": - st.markdown("### گزارش مقایسه‌ای واریته‌های نیشکر") - - varieties = ['CP57-614', 'CP69-1062', 'CP73-21', 'SP70-1143', 'IRC99-02'] - heights = [np.random.uniform(180, 220) for _ in varieties] - sugar_contents = [np.random.uniform(12, 16) for _ in varieties] - growth_rates = [np.random.uniform(10, 15) for _ in varieties] - - fig = go.Figure(data=[ - go.Bar(name='ارتفاع (cm)', x=varieties, y=heights), - go.Bar(name='محتوای قند (%)', x=varieties, y=sugar_contents), - go.Bar(name='رشد (cm/هفته)', x=varieties, y=growth_rates) - ]) - fig.update_layout( - title='مقایسه واریته‌های نیشکر', - xaxis_title='واریته', - yaxis_title='مقدار', - barmode='group', - font=dict(family="Vazirmatn") + elif report_type == "گزارش تنش‌ها": + st.subheader("گزارش تنش‌ها") + + # ایجاد داده‌های نمونه برای تنش‌ها + stress_data = [] + + for farm_id in selected_farms: + farm_name = farm_df[farm_df['farm_id'] == farm_id]['name'].values[0] + + # مقادیر پایه برای هر مزرعه + base_ndwi = np.random.uniform(0.2, 0.5) + base_chl = np.random.uniform(1.5, 2.5) + + # آستانه‌های تنش + water_stress_threshold = 0.3 + disease_threshold = 0.4 + + # ایجاد داده‌ها برای بازه زمانی انتخاب شده + date_range = pd.date_range(start=start_date, end=end_date) + + for date in date_range: + day_of_year = date.dayofyear + seasonal_factor = np.sin(day_of_year / 365 * 2 * np.pi) * 0.2 + + # شبیه‌سازی تنش آبی در روزهای خاص + water_stress_event = False + if (day_of_year % 30) < 5: # هر 30 روز، 5 روز تنش آبی + water_stress_event = True + ndwi = base_ndwi - 0.3 + np.random.normal(0, 0.05) + else: + ndwi = base_ndwi + seasonal_factor + np.random.normal(0, 0.05) + + # شبیه‌سازی تنش بیماری در روزهای خاص + disease_event = False + if (day_of_year % 45) < 7: # هر 45 روز، 7 روز تنش بیماری + disease_event = True + chl = base_chl - 0.5 + np.random.normal(0, 0.1) + else: + chl = base_chl + seasonal_factor + np.random.normal(0, 0.1) + + # محدود کردن مقادیر + ndwi = max(-0.5, min(0.8, ndwi)) + chl = max(0.5, min(3.0, chl)) + + stress_data.append({ + 'farm_id': farm_id, + 'farm_name': farm_name, + 'date': date, + 'ndwi': ndwi, + 'chl': chl, + 'water_stress': 1 if ndwi < water_stress_threshold else 0, + 'disease_stress': 1 if chl < disease_threshold else 0, + 'water_stress_event': water_stress_event, + 'disease_stress_event': disease_event + }) + + stress_df = pd.DataFrame(stress_data) + + # آمار تنش‌ها + stress_stats = stress_df.groupby('farm_name').agg({ + 'water_stress': 'sum', + 'disease_stress': 'sum' + }).reset_index() + + stress_stats.rename(columns={ + 'water_stress': 'تعداد روزهای تنش آبی', + 'disease_stress': 'تعداد روزهای تنش بیماری' + }, inplace=True) + + st.subheader("آمار تنش‌ها برای ��ر مزرعه") + st.dataframe(stress_stats, hide_index=True) + + # نمودار مقایسه‌ای تنش‌ها + fig_stress_compare = px.bar( + stress_stats, + x='farm_name', + y=['تعداد روزهای تنش آبی', 'تعداد روزهای تنش بیماری'], + title='مقایسه تنش‌ها بین مزارع', + labels={'farm_name': 'نام مزرعه', 'value': 'تعداد روز', 'variable': 'نوع تنش'}, + barmode='group' ) - st.plotly_chart(fig, use_container_width=True) - - variety_data = pd.DataFrame({ - 'واریته': varieties, - 'ارتفاع (cm)': [round(h, 1) for h in heights], - 'محتوای قند (%)': [round(s, 1) for s in sugar_contents], - 'رشد(cm/هفته)': [round(g, 1) for g in growth_rates], - 'مقاومت به آفات': [np.random.choice(['کم', 'متوسط', 'زیاد']) for _ in varieties], - 'نیاز آبی': [np.random.choice(['کم', 'متوسط', 'زیاد']) for _ in varieties] - }) - st.dataframe(variety_data, use_container_width=True, hide_index=True) - - categories = ['ارتفاع', 'محتوای قند', 'رشد', 'مقاومت به آفات', 'بهره‌وری آب'] - fig = go.Figure() - for variety in varieties: - values = [ - heights[varieties.index(variety)] / max(heights) * 100, - sugar_contents[varieties.index(variety)] / max(sugar_contents) * 100, - growth_rates[varieties.index(variety)] / max(growth_rates) * 100, - np.random.uniform(60, 100), - np.random.uniform(60, 100) - ] - fig.add_trace(go.Scatterpolar( - r=values, - theta=categories, - fill='toself', - name=variety - )) - fig.update_layout( - polar=dict( - radialaxis=dict( - visible=True, - range=[0, 100] - ) + + fig_stress_compare.update_layout( + font_family="Vazirmatn", + title_font_family="Vazirmatn", + title_font_size=20 + ) + + st.plotly_chart(fig_stress_compare, use_container_width=True) + + # نمودار سری زمانی تنش‌ها + st.subheader("سری زمانی تنش‌ها") + + # انتخاب مزرعه برای نمایش سری زمانی + selected_farm_id = st.selectbox( + "انتخاب مزرعه", + options=selected_farms, + format_func=lambda x: f"{x} - {farm_df[farm_df['farm_id'] == x]['name'].values[0]}", + key="farm_select_stress_report" + ) + + # فیلتر داده‌ها برای مزرعه انتخاب شده + farm_stress = stress_df[stress_df['farm_id'] == selected_farm_id] + + # نمودار سری زمانی + fig_stress_ts = go.Figure() + + # اضافه کردن NDWI + fig_stress_ts.add_trace(go.Scatter( + x=farm_stress['date'], + y=farm_stress['ndwi'], + name='NDWI', + line=dict(color='blue') + )) + + # اضافه کردن CHL + fig_stress_ts.add_trace(go.Scatter( + x=farm_stress['date'], + y=farm_stress['chl'], + name='CHL', + line=dict(color='green'), + yaxis='y2' + )) + + # اضافه کردن خط آستانه تنش آبی + fig_stress_ts.add_shape( + type='line', + x0=farm_stress['date'].min(), + y0=0.3, # water_stress_threshold + x1=farm_stress['date'].max(), + y1=0.3, + line=dict(color='red', dash='dash') + ) + + # اضافه کردن خط آستانه تنش بیماری + fig_stress_ts.add_shape( + type='line', + x0=farm_stress['date'].min(), + y0=0.4, # disease_threshold + x1=farm_stress['date'].max(), + y1=0.4, + line=dict(color='orange', dash='dash'), + yaxis='y2' + ) + + # تنظیمات نمودار + fig_stress_ts.update_layout( + title=f'سری زمانی تنش‌ها برای {farm_df[farm_df["farm_id"] == selected_farm_id]["name"].values[0]}', + font_family="Vazirmatn", + title_font_family="Vazirmatn", + title_font_size=20, + xaxis_title='تاریخ', + yaxis=dict( + title='NDWI', + titlefont=dict(color='blue'), + tickfont=dict(color='blue') ), - title='مقایسه واریته‌ها', - font=dict(family="Vazirmatn"), - showlegend=True + yaxis2=dict( + title='CHL', + titlefont=dict(color='green'), + tickfont=dict(color='green'), + anchor='x', + overlaying='y', + side='right' + ), + legend=dict( + orientation='h', + yanchor='bottom', + y=1.02, + xanchor='right', + x=1 + ) + ) + + st.plotly_chart(fig_stress_ts, use_container_width=True) + + # جدول روزهای تنش + st.subheader("روزهای تنش") + + # فیلتر روزهای تنش + stress_days = farm_stress[(farm_stress['water_stress'] == 1) | (farm_stress['disease_stress'] == 1)] + stress_days = stress_days[['date', 'ndwi', 'chl', 'water_stress', 'disease_stress']] + + if len(stress_days) > 0: + st.dataframe( + stress_days, + column_config={ + "date": "تاریخ", + "ndwi": st.column_config.NumberColumn("NDWI", format="%.2f"), + "chl": st.column_config.NumberColumn("CHL", format="%.2f"), + "water_stress": st.column_config.CheckboxColumn("تنش آبی"), + "disease_stress": st.column_config.CheckboxColumn("تنش بیماری") + }, + hide_index=True + ) + else: + st.info("هیچ روز تنشی برای این مزرعه در بازه زمانی انتخاب شده یافت نشد.") + + # دانلود گزارش + st.subheader("دانلود گزارش") + + # ایجاد فایل PDF + figs = [fig_stress_compare, fig_stress_ts] + tables = [stress_stats, stress_days] + + pdf_buffer = create_report_pdf(figs, tables, "گزارش تنش‌ها") + + # دانلود PDF + st.download_button( + label="دانلود گزارش (PDF)", + data=pdf_buffer, + file_name="stress_report.pdf", + mime="application/pdf" + ) + + # دانلود Excel + excel_buffer = io.BytesIO() + stress_df.to_excel(excel_buffer, index=False) + excel_buffer.seek(0) + + st.download_button( + label="دانلود داده‌ها (Excel)", + data=excel_buffer, + file_name="stress_data.xlsx", + mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ) - st.plotly_chart(fig, use_container_width=True) - - st.markdown("### توصیه‌های کشت واریته‌ها") - recommendations = [ - f"واریته {np.random.choice(varieties)} برای مناطق با آب و هوای گرم و مرطوب مناسب‌تر است.", - f"برای افزایش عملکرد تولید شکر، کشت واریته {np.random.choice(varieties)} توصیه می‌شود.", - f"در مناطق با محدودیت آب، استفاده از واریته {np.random.choice(varieties)} به دلیل نیاز آبی کمتر مناسب است.", - f"برای مقاومت بهتر در برابر آفات، واریته {np.random.choice(varieties)} پیشنهاد می‌شود.", - "تنوع در کشت واریته‌ها می‌تواند به کاهش ریسک‌های مرتبط با آفات و بیماری‌ها ��مک کند." - ] - for rec in recommendations: - st.markdown(f"- {rec}") - col1, col2 = st.columns(2) - with col1: + elif report_type == "گزارش پیش‌بینی رشد": + st.subheader("گزارش پیش‌بینی رشد") + + # ایجاد مدل و داده‌های پیش‌بینی + model, fig_model, fig_ts, time_df = create_prediction_model(farm_df) + + # فیلتر داده‌ها برای مزارع انتخاب شده + filtered_time_df = time_df[time_df['farm_id'].isin(selected_farms)] + + # آمار رشد + growth_stats = filtered_time_df.groupby('farm_id').agg({ + 'height': ['first', 'last', lambda x: x.iloc[-1] - x.iloc[0]] + }) + + growth_stats.columns = ['ارتفاع اولیه', 'ارتفاع نهایی', 'میزان رشد'] + growth_stats = growth_stats.reset_index() + + # اضافه کردن نام مزرعه + growth_stats = growth_stats.merge( + farm_df[['farm_id', 'name']], + on='farm_id', + how='left' + ) + + growth_stats = growth_stats[['farm_id', 'name', 'ارتفاع اولیه', 'ارتفاع نهایی', 'میزان رشد']] + + st.subheader("آمار رشد برای هر مزرعه") + st.dataframe( + growth_stats, + column_config={ + "farm_id": "شناسه", + "name": "نام مزرعه", + "ارتفاع اولیه": st.column_config.NumberColumn("ارتفاع اولیه (متر)", format="%.2f"), + "ارتفاع نهایی": st.column_config.NumberColumn("ارتفاع نهایی (متر)", format="%.2f"), + "میزان رشد": st.column_config.NumberColumn("میزان رشد (متر)", format="%.2f") + }, + hide_index=True + ) + + # نمودار مقایسه‌ای رشد + fig_growth = px.bar( + growth_stats, + x='name', + y='میزان رشد', + title='مقایسه میزان رشد بین مزارع', + labels={'name': 'نام مزرعه', 'میزان رشد': 'میزان رشد (متر)'}, + color='میزان رشد', + color_continuous_scale='Viridis' + ) + + fig_growth.update_layout( + font_family="Vazirmatn", + title_font_family="Vazirmatn", + title_font_size=20 + ) + + st.plotly_chart(fig_growth, use_container_width=True) + + # نمودار سری زمانی رشد + st.subheader("سری زمانی رشد") + + # انتخاب مزرعه برای نمایش سری زمانی + selected_farm_id = st.selectbox( + "انتخاب مزرعه", + options=selected_farms, + format_func=lambda x: f"{x} - {farm_df[farm_df['farm_id'] == x]['name'].values[0]}", + key="farm_select_growth_report" + ) + + # فیلتر داده‌ها برای مزرعه انتخاب شده + farm_growth = filtered_time_df[filtered_time_df['farm_id'] == selected_farm_id] + + # نمودار سری زمانی + fig_growth_ts = px.line( + farm_growth, + x='date', + y='height', + title=f'سری زمانی رشد برای {farm_df[farm_df["farm_id"] == selected_farm_id]["name"].values[0]}', + labels={'date': 'تاریخ', 'height': 'ارتفاع (متر)'} + ) + + fig_growth_ts.update_layout( + font_family="Vazirmatn", + title_font_family="Vazirmatn", + title_font_size=20 + ) + + st.plotly_chart(fig_growth_ts, use_container_width=True) + + # پیش‌بینی رشد آینده + st.subheader("پیش‌بینی رشد آینده") + + # تنظیمات پیش‌بینی + days_to_predict = st.slider("تعداد روزهای پیش‌بینی", min_value=7, max_value=90, value=30, key="days_predict_report") + confidence_interval = st.slider("فاصله اطمینان (%)", min_value=50, max_value=99, value=95, key="conf_interval_report") + + # ایجاد داده‌های آینده برای پیش‌بینی + last_date = farm_growth['date'].max() + future_dates = pd.date_range(start=last_date + timedelta(days=1), periods=days_to_predict) + + future_data = [] + for date in future_dates: + future_data.append({ + 'date': date, + 'day_of_year': date.dayofyear, + 'month': date.month + }) + + future_df = pd.DataFrame(future_data) + + # پیش‌بینی NDVI برای روزهای آینده (با الگوی سینوسی + روند) + base_ndvi = farm_growth['ndvi'].iloc[-1] + future_ndvi = [] + + for day in future_df['day_of_year']: + seasonal_factor = np.sin(day / 365 * 2 * np.pi) * 0.2 + trend_factor = day / 365 * 0.1 + ndvi = base_ndvi + seasonal_factor + trend_factor + ndvi = max(0, min(1, ndvi)) # محدود کردن به بازه 0 تا 1 + future_ndvi.append(ndvi) + + future_df['ndvi'] = future_ndvi + + # پیش‌بینی ارتفاع با استفاده از مدل + X_future = future_df[['ndvi', 'day_of_year', 'month']].values + future_df['height'] = model.predict(X_future) + + # محاسبه فاصله اطمینان + z_score = { + 90: 1.645, + 95: 1.96, + 99: 2.576 + }.get(confidence_interval, 1.96) + + mse = mean_squared_error(y_test, y_pred) + std_dev = np.sqrt(mse) + + future_df['height_lower'] = future_df['height'] - z_score * std_dev + future_df['height_upper'] = future_df['height'] + z_score * std_dev + + # ترکیب داده‌های گذشته و آینده + combined_df = pd.concat([ + farm_growth[['date', 'ndvi', 'height']], + future_df[['date', 'ndvi', 'height', 'height_lower', 'height_upper']] + ]) + + # نمایش نمودار پیش‌بینی + fig_predict = go.Figure() + + # داده‌های گذشته + past_data = combined_df[combined_df['date'] <= last_date] + + fig_predict.add_trace(go.Scatter( + x=past_data['date'], + y=past_data['height'], + name='ارتفاع (گذشته)', + line=dict(color='blue') + )) + + # داده‌های پیش‌بینی شده + future_data = combined_df[combined_df['date'] > last_date] + + fig_predict.add_trace(go.Scatter( + x=future_data['date'], + y=future_data['height'], + name='ارتفاع (پیش‌بینی)', + line=dict(color='red') + )) + + # فاصله اطمینان + fig_predict.add_trace(go.Scatter( + x=future_data['date'], + y=future_data['height_upper'], + name=f'فاصله اطمینان {confidence_interval}%', + line=dict(width=0), + showlegend=False + )) + + fig_predict.add_trace(go.Scatter( + x=future_data['date'], + y=future_data['height_lower'], + name=f'فاصله اطمینان {confidence_interval}%', + line=dict(width=0), + fillcolor='rgba(255, 0, 0, 0.2)', + fill='tonexty', + showlegend=True + )) + + # تنظیمات نمودار + fig_predict.update_layout( + title=f'پیش‌بینی ارتفاع برای {days_to_predict} روز آینده', + font_family="Vazirmatn", + title_font_family="Vazirmatn", + title_font_size=20, + xaxis_title='تاریخ', + yaxis_title='ارتفاع (متر)', + legend=dict( + orientation='h', + yanchor='bottom', + y=1.02, + xanchor='right', + x=1 + ) + ) + + st.plotly_chart(fig_predict, use_container_width=True) + + # نمایش جدول پیش‌بینی + st.subheader("جدول پیش‌بینی") + + st.dataframe( + future_df[['date', 'ndvi', 'height', 'height_lower', 'height_upper']], + column_config={ + "date": "تاریخ", + "ndvi": st.column_config.NumberColumn("NDVI", format="%.2f"), + "height": st.column_config.NumberColumn("ارتفاع (متر)", format="%.2f"), + "height_lower": st.column_config.NumberColumn(f"حد پایین ({confidence_interval}%)", format="%.2f"), + "height_upper": st.column_config.NumberColumn(f"حد بالا ({confidence_interval}%)", format="%.2f") + }, + hide_index=True + ) + + # دانلود گزارش + st.subheader("دانلود گزارش") + + # ایجاد فایل PDF + figs = [fig_growth, fig_growth_ts, fig_predict] + tables = [growth_stats, future_df[['date', 'ndvi', 'height', 'height_lower', 'height_upper']]] + + pdf_buffer = create_report_pdf(figs, tables, "گزارش پیش‌بینی رشد") + + # دانلود PDF st.download_button( label="دانلود گزارش (PDF)", - data=b"This is a mock PDF report", - file_name="farm_report.pdf", - mime="application/pdf", + data=pdf_buffer, + file_name="growth_prediction_report.pdf", + mime="application/pdf" ) - with col2: + + # دانلود Excel + excel_buffer = io.BytesIO() + combined_df.to_excel(excel_buffer, index=False) + excel_buffer.seek(0) + st.download_button( label="دانلود داده‌ها (Excel)", - data=b"This is a mock Excel file", - file_name="farm_data.xlsx", - mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + data=excel_buffer, + file_name="growth_prediction_data.xlsx", + mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ) -# Settings Page -elif selected == "تنظیمات": - st.markdown("## تنظیمات سیستم") +# تب تنظیمات +with tabs[5]: + st.header("تنظیمات") - tab1, tab2, tab3, tab4 = st.tabs(["تنظیمات کاربری", "تنظیمات سیستم", "مدیریت داده‌ها", "پشتیبان‌گیری"]) + settings_tabs = st.tabs(["تنظیمات عمومی", "اتصال به Google Earth Engine", "مدیریت کاربران"]) - with tab1: - st.markdown("### تنظیمات کاربری") + with settings_tabs[0]: + st.subheader("تنظیمات عمومی") + + # تنظیمات ظاهری + st.write("### تنظیمات ظاهری") - st.markdown("#### پروفایل کاربری") col1, col2 = st.columns(2) + with col1: - user_name = st.text_input("نام کاربری", value="کاربر نمونه") + theme = st.selectbox( + "انتخاب تم", + ["سبز (پیش‌فرض)", "آبی", "قرمز", "بنفش"] + ) + + font = st.selectbox( + "انتخاب فونت", + ["Vazirmatn (پیش‌فرض)", "Sahel", "Yekan", "Tanha"] + ) + with col2: - user_email = st.text_input("ایمیل", value="user@example.com") + map_style = st.selectbox( + "استایل نقشه", + ["OpenStreetMap (پیش‌فرض)", "Stamen Terrain", "Stamen Toner", "CartoDB positron"] + ) + + chart_style = st.selectbox( + "استایل نمودارها", + ["Viridis (پیش‌فرض)", "Plasma", "Inferno", "Magma", "Cividis"] + ) + + # تنظیمات زبان + st.write("### تنظیمات زبان") - user_role = st.selectbox( - "نقش کاربری", - options=["مدیریت مطالعات", "پرسنل", "اپراتور"], - index=1 + language = st.radio( + "انتخاب زبان", + ["فارسی (پیش‌فرض)", "انگلیسی"] ) - st.markdown("#### تغییر رمز عبور") + # تنظیمات واحدها + st.write("### تنظیمات واحدها") + col1, col2 = st.columns(2) + with col1: - current_password = st.text_input("رمز عبور فعلی", type="password") + area_unit = st.selectbox( + "واحد مساحت", + ["هکتار (پیش‌فرض)", "متر مربع", "کیلومتر مربع"] + ) + + height_unit = st.selectbox( + "واحد ارتفاع", + ["متر (پیش‌فرض)", "سانتی‌متر"] + ) + with col2: - new_password = st.text_input("رمز عبور جدید", type="password") + date_format = st.selectbox( + "فرمت تاریخ", + ["YYYY-MM-DD (پیش‌فرض)", "DD/MM/YYYY", "MM/DD/YYYY"] + ) + + number_format = st.selectbox( + "فرمت اعداد", + ["1,234.56 (پیش‌فرض)", "1.234,56"] + ) - confirm_password = st.text_input("تکرار رمز عبور جدید", type="password") + # دکمه ذخیره تنظیمات + if st.button("ذخیره تنظیمات"): + st.success("تنظیمات با موفقیت ذخیره شد.") + + with settings_tabs[1]: + st.subheader("اتصال به Google Earth Engine") - if st.button("تغییر رمز عبور", type="primary"): - st.success("رمز عبور با موفقیت تغییر کرد.") + # توضیحات + st.write(""" + برای اتصال به Google Earth Engine، نیاز به اعتبارنامه (credentials) دارید. + می‌توانید از طریق حساب کاربری Google Earth Engine خود، فایل اعتبارنامه را دریافت کنید. + """) - st.markdown("#### تنظیمات اعلان‌ها") - email_notifications = st.checkbox("دریافت اعلان‌ها از طریق ایمیل", value=True) - sms_notifications = st.checkbox("دریافت اعلان‌ها از طریق پیامک", value=False) - notification_frequency = st.radio( - "تناوب دریافت اعلان‌ها", - options=["روزانه", "هفتگی", "ماهانه"], - index=1 - ) - - with tab2: - st.markdown("### تنظیمات سیستم") + # آپلود فایل اعتبارنامه + uploaded_file = st.file_uploader("آپلود فایل اعتبارنامه (JSON)", type=["json"]) - system_language = st.selectbox( - "زبان سیستم", - options=["فارسی", "English", "العربية"], - index=0 - ) + if uploaded_file is not None: + st.success("فایل اعتبارنامه با موفقیت آپلود شد.") + + # نمایش اطلاعات اتصال + st.write("### اطلاعات اتصال") + + st.info("اتصال به Google Earth Engine برقرار است.") + + # دکمه تست اتصال + if st.button("تست اتصال"): + with st.spinner("در حال تست اتصال..."): + # شبیه‌سازی تست اتصال + import time + time.sleep(2) + st.success("اتصال با موفقیت برقرار شد.") - date_format = st.selectbox( - "فرمت تاریخ", - options=["YYYY/MM/DD", "DD/MM/YYYY", "MM/DD/YYYY"], - index=0 - ) + # تنظیمات پیشرفته + st.write("### تنظیمات پیشرفته") - st.markdown("#### تنظیمات ظاهری") - theme = st.radio( - "تم", - options=["روشن", "تیره", "سیستم"], - index=2 - ) - primary_color = st.color_picker("رنگ اصلی", value="#1a8754") + col1, col2 = st.columns(2) - st.markdown("#### تنظیمات نقشه") - default_map_view = st.selectbox( - "نمای پیش‌فرض نقشه", - options=["نقشه", "ماهواره", "ترکیبی"], - index=0 - ) - default_map_layer = st.selectbox( - "لایه پیش‌فرض نقشه", - options=["NDVI", "NDMI", "EVI", "NDWI", "SoilMoisture"], - index=0 - ) + with col1: + cache_size = st.slider("اندازه کش (مگابایت)", min_value=100, max_value=1000, value=500) + + max_retry = st.slider("حداکثر تلاش مجدد", min_value=1, max_value=10, value=3) - st.markdown("#### تنظیمات مدل هوش مصنوعی") - ai_model = st.selectbox( - "مدل هوش مصنوعی", - options=["GPT-3", "GPT-4", "BERT"], - index=1 - ) - model_update_frequency = st.selectbox( - "تناوب به‌روزرسانی مدل", - options=["روزانه", "هفتگی", "ماهانه"], - index=1 + with col2: + timeout = st.slider("زمان انتظار (ثانیه)", min_value=10, max_value=300, value=60) + + parallel_jobs = st.slider("تعداد پردازش موازی", min_value=1, max_value=8, value=4) + + with settings_tabs[2]: + st.subheader("مدیریت کاربران") + + # ایجاد داده‌های نمونه برای کاربران + users_data = { + 'user_id': list(range(1, 6)), + 'username': ['admin', 'user1', 'user2', 'user3', 'user4'], + 'name': ['مدیر سیستم', 'کاربر یک', 'کاربر دو', 'کاربر سه', 'کاربر چهار'], + 'role': ['مدیر', 'کارشناس', 'کارشناس', 'کاربر', 'کاربر'], + 'last_login': [ + (datetime.now() - timedelta(hours=2)).strftime('%Y-%m-%d %H:%M'), + (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d %H:%M'), + (datetime.now() - timedelta(days=3)).strftime('%Y-%m-%d %H:%M'), + (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d %H:%M'), + (datetime.now() - timedelta(days=14)).strftime('%Y-%m-%d %H:%M') + ] + } + + users_df = pd.DataFrame(users_data) + + # نمایش لیست کاربران + st.write("### لیست کاربران") + + st.dataframe( + users_df, + column_config={ + "user_id": "شناسه", + "username": "نام کاربری", + "name": "نام", + "role": "نقش", + "last_login": "آخرین ورود" + }, + hide_index=True ) - if st.button("ذخیره تنظیمات", type="primary"): - st.success("تنظیمات با موفقیت ذخیره شدند.") - - with tab3: - st.markdown("### مدیریت داده‌ها") + # افزودن کاربر جدید + st.write("### افزودن کاربر جدید") - st.markdown("#### ورود داده") - uploaded_file = st.file_uploader("آپلود فایل داده", type=["csv", "xlsx"]) - if uploaded_file is not None: - st.success(f"فایل {uploaded_file.name} با موفقیت آپلود شد.") - if st.button("وارد کردن داده‌ها"): - st.info("در حال پردازش و وارد کردن داده‌ها...") - time.sleep(2) - st.success("داده‌ها با موفقیت وارد شدند.") - - st.markdown("#### خروجی داده") - export_format = st.selectbox( - "فرمت خروجی", - options=["CSV", "Excel", "JSON"], - index=1 - ) - if st.button("دریافت خروجی"): - st.info("در حال آماده‌سازی فایل خروجی...") - time.sleep(2) - st.success("فایل خروجی آماده دانلود است.") - st.download_button( - label="دانلود فایل خروجی", - data=b"This is a mock export file", - file_name=f"farm_data_export.{export_format.lower()}", - mime="application/octet-stream", - ) + col1, col2 = st.columns(2) - st.markdown("#### پاکسازی داده‌ها") - cleanup_options = st.multiselect( - "گزینه‌های پاکسازی", - options=["حذف داده‌های تکراری", "حذف داده‌های ناقص", "نرمال‌سازی داده‌ها"], - default=["حذف داده‌های تکراری"] - ) - if st.button("اجرای پاکسازی"): - st.info("در حال اجرای عملیات پاکسازی...") - time.sleep(2) - st.success("عملیات پاکسازی با موفقیت انجام شد.") - - st.markdown("#### تنظیمات نمایش داده") - chart_theme = st.selectbox( - "تم نمودارها", - options=["پیش‌فرض", "روشن", "تیره", "رنگی"], - index=0 - ) - show_data_labels = st.checkbox("نمایش برچسب‌های داده", value=True) - if st.button("اعمال تنظیمات نمایش"): - st.success("تنظیمات نمایش داده با موفقیت اعمال شدند.") - - with tab4: - st.markdown("### پشتیبان‌گیری و بازیابی") - - st.markdown("#### ایجاد نسخه پشتیبان") - backup_type = st.radio( - "نوع پشتیبان‌گیری", - options=["پشتیبان کامل", "پشتیبان افزایشی"], - index=0 - ) - include_images = st.checkbox("شامل تصاویر", value=True) - include_user_data = st.checkbox("شا��ل داده‌های کاربران", value=True) - if st.button("ایجاد نسخه پشتیبان", type="primary"): - st.info("در حال ایجاد نسخه پشتیبان...") - progress_bar = st.progress(0) - for i in range(100): - time.sleep(0.05) - progress_bar.progress(i + 1) - st.success("نسخه پشتیبان با موفقیت ایجاد شد.") - - st.markdown("#### بازیابی از نسخه پشتیبان") - backup_file = st.file_uploader("آپلود فایل پشتیبان", type=["zip", "bak"]) - if backup_file is not None: - st.warning("هشدار: بازیابی از نسخه پشتیبان ممکن است داده‌های فعلی را بازنویسی کند.") - if st.button("شروع بازیابی"): - st.info("در حال بازیابی از نسخه پشتیبان...") - progress_bar = st.progress(0) - for i in range(100): - time.sleep(0.05) - progress_bar.progress(i + 1) - st.success("بازیابی از نسخه پشتیبان با موفقیت انجام شد.") - - st.markdown("#### تنظیمات پشتیبان‌گیری خودکار") - auto_backup = st.checkbox("فعال‌سازی پشتیبان‌گیری خودکار", value=True) - if auto_backup: - backup_frequency = st.selectbox( - "تناوب پشتیبان‌گیری", - options=["روزانه", "هفتگی", "ماهانه"], - index=1 - ) - backup_time = st.time_input("زمان پشتیبان‌گیری", value=datetime.now().replace(hour=1, minute=0, second=0, microsecond=0)) - retain_backups = st.number_input("تعداد نسخه‌های پشتیبان برای نگهداری", min_value=1, value=7) + with col1: + new_username = st.text_input("نام کاربری") + new_name = st.text_input("نام") - if st.button("ذخیره تنظیمات پشتیبان‌گیری"): - st.success("تنظیمات پشتیبان‌گیری با موفقیت ذخیره شدند.") - -# Add a footer -st.markdown(""" - -""", unsafe_allow_html=True) -# Import required libraries if not already imported -import pandas as pd -import folium -from folium import plugins -import geopandas as gpd -from branca.element import Figure -import plotly.express as px -import plotly.graph_objects as go -from datetime import datetime, timedelta - -# Read farm coordinates and schedule data -farm_coords = pd.read_csv('farm_coordinates.csv') -farm_schedule = pd.read_csv('پایگاه داده (1).csv') - -# Create day selector -selected_day = st.selectbox( - "انتخاب روز هفته", - options=["شنبه", "یکشنبه", "دوشنبه", "سه‌شنبه", "چهارشنبه", "پنجشنبه", "جمعه"] -) - -# Filter farms for selected day -daily_farms = farm_schedule[farm_schedule['روز'] == selected_day] - -# Calculate vegetation indices for last 7 days -end_date = datetime.now() -start_date = end_date - timedelta(days=7) - -# Create dataframe for indices -indices_df = pd.DataFrame() -for farm in daily_farms['مزرعه']: - # Simulate dynamic NDVI, Chlorophyll and LAI calculations - # In practice, these would come from actual sensor/satellite data - ndvi_values = np.random.uniform(0.3, 0.8, 7) - chlorophyll_values = np.random.uniform(30, 60, 7) - lai_values = np.random.uniform(2, 5, 7) - - farm_indices = pd.DataFrame({ - 'مزرعه': [farm] * 7, - 'تاریخ': pd.date_range(start_date, end_date, periods=7), - 'NDVI': ndvi_values, - 'کلروفیل': chlorophyll_values, - 'شاخص سطح برگ': lai_values - }) - indices_df = pd.concat([indices_df, farm_indices]) - -# Calculate statistics -stats_df = indices_df.groupby('مزرعه').agg({ - 'NDVI': ['mean', 'min', 'max'], - 'کلروفیل': ['mean', 'min', 'max'], - 'شاخص سطح برگ': ['mean', 'min', 'max'] -}).round(2) - -# Display farms table with indices -st.markdown(f"#### مزارع روز {selected_day}") -st.dataframe(stats_df) - -# Create growth ranking charts -fig = go.Figure() -fig.add_trace(go.Bar( - x=stats_df.index, - y=stats_df['NDVI']['mean'], - name='میانگین NDVI' -)) -fig.update_layout(title='رتبه‌بندی رشد مزارع', - xaxis_title='مزرعه', - yaxis_title='NDVI') -st.plotly_chart(fig) - -# Create map -m = folium.Map(location=[farm_coords['عرض جغرافیایی'].mean(), - farm_coords['طول جغرافیایی'].mean()], - zoom_start=12) - -# Add NDVI layer and highlighted farms -for idx, row in farm_coords.iterrows(): - color = 'red' if row['مزرعه'] in daily_farms['مزرعه'].values else 'blue' - radius = 8 if row['مزرعه'] in daily_farms['مزرعه'].values else 6 - - marker = folium.CircleMarker( - location=[row['عرض جغرافیایی'], row['طول جغرافیایی']], - radius=radius, - popup=f"مزرعه: {row['مزرعه']}
NDVI: {stats_df.loc[row['مزرعه'], ('NDVI', 'mean')]:.2f}", - color=color, - fill=True - ) - - if row['مزرعه'] in daily_farms['مزرعه'].values: - plugins.BeautifyIcon( - icon='circle', - border_color=color, - text_color=color, - inner_icon_style='font-size:12px;' + with col2: + new_password = st.text_input("رمز عبور", type="password") + new_role = st.selectbox("نقش", ["مدیر", "کارشناس", "کاربر"]) + + if st.button("افزودن کاربر"): + if new_username and new_name and new_password: + st.success(f"کاربر {new_name} با موفقیت افزوده شد.") + else: + st.error("لطفاً تمام فیلدها را پر کنید.") + + # ویرایش دسترسی‌ها + st.write("### مدیریت دسترسی‌ها") + + # جدول دسترسی‌ها + access_data = { + 'role': ['مدیر', 'کارشناس', 'کاربر'], + 'dashboard': ['خواندن/نوشتن', 'خواندن/نوشتن', 'خواندن'], + 'map': ['خواندن/نوشتن', 'خواندن/نوشتن', 'خواندن'], + 'data_entry': ['خواندن/نوشتن', 'خواندن/نوشتن', 'بدون دسترسی'], + 'analysis': ['خواندن/نوشتن', 'خواندن', 'خواندن'], + 'reports': ['خواندن/نوشتن', 'خواندن', 'خواندن'], + 'settings': ['خواندن/نوشتن', 'بدون دسترسی', 'بدون دسترسی'] + } + + access_df = pd.DataFrame(access_data) + + st.dataframe( + access_df, + column_config={ + "role": "نقش", + "dashboard": "داشبورد", + "map": "نقشه مزارع", + "data_entry": "ورود اطلاعات", + "analysis": "تحلیل داده‌ها", + "reports": "گزارش‌گیری", + "settings": "تنظیمات" + }, + hide_index=True ) - - marker.add_to(m) + + # دکمه ویرایش دسترسی‌ها + if st.button("ویرایش دسترسی‌ها"): + st.info("برای ویرایش دسترسی‌ها، لطفاً با مدیر سیستم تماس بگیرید.") -# Display map -st_folium = st.components.v1.html(m._repr_html_(), height=400) \ No newline at end of file +# نمایش اطلاعات نسخه +st.sidebar.markdown("---") +st.sidebar.info("نسخه 1.0.0") +st.sidebar.info("توسعه داده شده با Streamlit") +print("اپلیکیشن تحلیل داده‌های کشاورزی با موفقیت اجرا شد.") \ No newline at end of file