diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,3174 +1,693 @@ import streamlit as st import pandas as pd import numpy as np -import folium# +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 datetime import datetime, timedelta +import json +import os +import ee from PIL import Image import base64 from io import BytesIO 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 time +from sklearn.ensemble import RandomForestRegressor +from sklearn.model_selection import train_test_split +from sklearn.metrics import mean_squared_error, r2_score -# 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 +# تنظیمات CSS سفارشی st.markdown(""" """, 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"]) - - 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}' - -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 - } - - color = colors.get(status.lower(), "#757575") # Default gray - - 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) - - credentials = ee.ServiceAccountCredentials(service_account, credentials_file) - ee.Initialize(credentials) - - os.remove(credentials_file) - - 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() +# تنظیمات مسیرهای فایل +DATA_FILES = { + 'CROP_LOG': "کراپ لاگ اپ.csv", + 'FARM_COORDS': "farm_coordinates.csv", + 'FARM_DB': "پایگاه داده (1).csv", + 'GEE_CREDENTIALS': "ee-esmaeilkiani13877-cfdea6eaf411 (1).json" +} -# Load animation JSON -@st.cache_data -def load_lottie_url(url: str): +# تابع برای بارگذاری انیمیشن‌های Lottie +def load_lottieurl(url): 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 +# انیمیشن‌های Lottie +lottie_farm = load_lottieurl("https://assets5.lottiefiles.com/packages/lf20_ystsffqy.json") +lottie_analysis = load_lottieurl("https://assets3.lottiefiles.com/packages/lf20_qp1q7mct.json") +lottie_report = load_lottieurl("https://assets9.lottiefiles.com/packages/lf20_vnikrcia.json") + +# کلاس مدیریت Google Earth Engine +class GEEHandler: + def __init__(self): + self.initialized = False + self.initialize_gee() - 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) - ] - } + def initialize_gee(self): + try: + with open(DATA_FILES['GEE_CREDENTIALS']) as f: + credentials = json.load(f) + credentials = ee.ServiceAccountCredentials( + credentials['client_email'], + key_data=credentials['private_key'] + ) + ee.Initialize(credentials) + self.initialized = True + except Exception as e: + st.error(f"خطا در اتصال به Google Earth Engine: {str(e)}") - 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() + def get_satellite_imagery(self, geometry, start_date, end_date, max_cloud_cover=20): + if not self.initialized: + return None - # 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() + try: + # دریافت تصاویر Sentinel-2 + s2_collection = (ee.ImageCollection('COPERNICUS/S2_SR') + .filterBounds(geometry) + .filterDate(start_date, end_date) + .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', max_cloud_cover))) + + if s2_collection.size().getInfo() == 0: + st.warning("هیچ تصویری برای محدوده زمانی و مکانی انتخاب شده یافت نشد.") + return None + + return s2_collection + + except Exception as e: + st.error(f"خطا در دریافت تصاویر ماهواره‌ای: {str(e)}") + return None + + def calculate_indices(self, image_collection, geometry): + if not self.initialized or image_collection is None: + return None - # 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"), + try: + # تابع محاسبه شاخص‌ها + def add_indices(image): + # NDVI (شاخص پوشش گیاهی) + ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI') + + # NDWI (شاخص آب گیاه) + ndwi = image.normalizedDifference(['B3', 'B8']).rename('NDWI') + + # EVI (شاخص تقویت‌شده پوشش گیاهی) + evi = 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') + + # NDMI (شاخص رطوبت خاک) + ndmi = image.normalizedDifference(['B8', 'B11']).rename('NDMI') + + # LAI (شاخص سطح برگ) - تقریبی بر اساس NDVI + lai = ndvi.multiply(3).rename('LAI') + + # CHL (شاخص کلروفیل) - تقریبی با استفاده از باندهای Red-Edge + chl = image.normalizedDifference(['B7', 'B5']).rename('CHL') + + return image.addBands([ndvi, ndwi, evi, ndmi, lai, chl]) + + # اعمال محاسبات روی تمام تصاویر + images_with_indices = image_collection.map(add_indices) + + # محاسبه میانگین شاخص‌ها در محدوده زمانی + mean_indices = images_with_indices.select(['NDVI', 'NDWI', 'EVI', 'NDMI', 'LAI', 'CHL']).mean() + + # استخراج آمار برای هر شاخص + stats = {} + for index in ['NDVI', 'NDWI', 'EVI', 'NDMI', 'LAI', 'CHL']: + stats[index] = mean_indices.select(index).reduceRegion( + reducer=ee.Reducer.mean(), + geometry=geometry, + scale=10 + ).getInfo() + + return { + 'mean_indices': mean_indices, + 'stats': stats, + 'collection': images_with_indices } - # 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"]) - - # Add forecast to weather data - weather["forecast"] = forecast - - # 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 - - # Cold stress indicator (if min temp < 15°C for sugarcane) - weather["cold_stress"] = weather["temp_min"] < 15 - - # 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) - - return weather - - # If we get here, something went wrong with the API calls - st.warning("Weather data API returned an error. Using fallback data.") - - 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) - ] - } + except Exception as e: + st.error(f"خطا در محاسبه شاخص‌های گیاهی: {str(e)}") + return None -# 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"] +# کلاس مدیریت نقشه +class MapUtils: + @staticmethod + def create_base_map(center=[31.59, 48.77], zoom=12): + m = folium.Map( + location=center, + zoom_start=zoom, + tiles='CartoDB positron' + ) - # Get weather data - weather_data = get_weather_data(lat, lon, os.environ.get("OPENWEATHER_API_KEY", "")) + # اضافه کردن کنترل لایه‌ها + folium.LayerControl().add_to(m) - # 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 + # اضافه کردن مقیاس و موقعیت ماوس + folium.plugins.MousePosition().add_to(m) + folium.plugins.MeasureControl(position='topleft').add_to(m) - # Get NDVI data as a proxy for crop development - ndvi_stats = calculate_farm_stats(farm_id, "NDVI") - ndvi_value = ndvi_stats["mean_value"] + return m + + @staticmethod + def add_farm_markers(m, farm_coords, crop_log=None): + # ایجاد گروه‌های مختلف برای مزارع + farm_groups = {} + + for age in farm_coords['age'].unique(): + farm_groups[age] = folium.FeatureGroup(name=f'مزارع {age}') + + for idx, row in farm_coords.iterrows(): + # دریافت آخرین اطلاعات مزرعه + farm_data = None + if crop_log is not None: + farm_data = crop_log[crop_log['مزرعه'] == row['name']].iloc[-1] if len(crop_log[crop_log['مزرعه'] == row['name']]) > 0 else None + + # تعیین رنگ مارکر بر اساس ارتفاع + color = 'green' + if farm_data is not None and 'ارتفاع' in farm_data: + if farm_data['ارتفاع'] < 250: + color = 'red' + elif farm_data['ارتفاع'] < 300: + color = 'orange' + + # ایجاد پاپ‌آپ با اطلاعات کامل + popup_html = f""" +
+

مزرعه {row['name']}

+

واریته: {row['variety']}

+

سن: {row['age']}

+ """ + + if farm_data is not None: + popup_html += f""" +

ارتفاع: {farm_data.get('ارتفاع', 'نامشخص')} سانتی‌متر

+

رطوبت: {farm_data.get('رطوبت', 'نامشخص')}%

+ """ + + popup_html += "
" + + # ایجاد مارکر + folium.Marker( + location=[row['lat'], row['lon']], + popup=folium.Popup(popup_html, max_width=300), + tooltip=f"مزرعه {row['name']}", + icon=folium.Icon(color=color, icon='leaf', prefix='fa') + ).add_to(farm_groups[row['age']]) + + # اضافه کردن گروه‌ها به نقشه + for group in farm_groups.values(): + group.add_to(m) - # Get soil moisture data (from Sentinel-1 if available) + return m + + @staticmethod + def add_ee_layer(m, ee_image, vis_params, name): + map_id_dict = ee.Image(ee_image).getMapId(vis_params) + folium.raster_layers.TileLayer( + tiles=map_id_dict['tile_fetcher'].url_format, + attr='Google Earth Engine', + name=name, + overlay=True, + control=True + ).add_to(m) + +# کلاس مدیریت داده‌ها +class DataHandler: + def __init__(self): + self.farm_db = None + self.farm_coords = None + self.crop_log = None + self.load_data() + + def load_data(self): 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) - - # Sunset hour angle - sunset_angle = math.acos(-math.tan(lat_rad) * math.tan(solar_dec)) + # بارگذاری داده‌های مزارع + if os.path.exists(DATA_FILES['FARM_DB']): + self.farm_db = pd.read_csv(DATA_FILES['FARM_DB']) + else: + st.warning(f"فایل {DATA_FILES['FARM_DB']} یافت نشد.") - # 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) - ) + # بارگذاری مختصات مزارع + if os.path.exists(DATA_FILES['FARM_COORDS']): + self.farm_coords = pd.read_csv(DATA_FILES['FARM_COORDS']) + else: + st.warning(f"فایل {DATA_FILES['FARM_COORDS']} یافت نشد.") - # Hargreaves equation for ETo (mm/day) - eto = 0.0023 * ra * (temp_avg + 17.8) * math.sqrt(temp_max - temp_min) - 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 + # بارگذاری لاگ محصول + if os.path.exists(DATA_FILES['CROP_LOG']): + self.crop_log = pd.read_csv(DATA_FILES['CROP_LOG']) + else: + st.warning(f"فایل {DATA_FILES['CROP_LOG']} یافت نشد.") + + except Exception as e: + st.error(f"خطا در بارگذاری داده‌ها: {str(e)}") + + def get_active_farms(self, selected_date): + if self.farm_db is None: + return pd.DataFrame() + + # فیلتر کردن مزارع فعال در تاریخ انتخاب شده + active_farms = self.farm_db[self.farm_db['تاریخ'] == selected_date] + return active_farms + + def get_farm_stats(self, selected_date=None): + if self.farm_db is None or self.crop_log is None: + return { + 'total_farms': 0, + 'active_farms': 0, + 'avg_height': 0, + 'avg_moisture': 0 + } - # 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)) + # اگر تاریخ انتخاب نشده باشد، آخرین تاریخ را استفاده کن + if selected_date is None: + selected_date = self.farm_db['تاریخ'].max() - # 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 + active_farms = self.get_active_farms(selected_date) - # Final water requirement calculation (mm/day) - water_req_mm = max(0, (etc - effective_rain) * soil_factor) + # محاسبه آمار + total_farms = len(self.farm_coords) if self.farm_coords is not None else 0 + active_count = len(active_farms) - # Convert to cubic meters per hectare (1 mm = 10 m³/ha) - water_req_m3_ha = water_req_mm * 10 + avg_height = 0 + avg_moisture = 0 - # Get farm area in hectares - farm_area_ha = farm_info["area_ha"] + if 'ارتفاع' in self.crop_log.columns and len(self.crop_log) > 0: + avg_height = self.crop_log['ارتفاع'].mean() - # Total water requirement for the farm - total_water_req_m3 = water_req_m3_ha * farm_area_ha + if 'رطوبت' in self.crop_log.columns and len(self.crop_log) > 0: + avg_moisture = self.crop_log['رطوبت'].mean() 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) + 'total_farms': total_farms, + 'active_farms': active_count, + 'avg_height': avg_height, + 'avg_moisture': avg_moisture } - except Exception as e: - st.error(f"Error estimating water requirements: {str(e)}") - # Return fallback data + +# کلاس تحلیل‌های پیشرفته +class AdvancedAnalytics: + def __init__(self, data_handler): + self.data_handler = data_handler + self.models = {} + + def train_growth_prediction_model(self): + if self.data_handler.crop_log is None: + return None + + try: + # آماده‌سازی داده‌ها + df = self.data_handler.crop_log.copy() + + # تبدیل تاریخ به ویژگی‌های عددی + if 'تاریخ' in df.columns: + df['تاریخ'] = pd.to_datetime(df['تاریخ']) + df['month'] = df['تاریخ'].dt.month + df['day'] = df['تاریخ'].dt.day + + # انتخاب ویژگی‌ها و هدف + features = ['month', 'day', 'رطوبت'] + target = 'ارتفاع' + + if all(col in df.columns for col in features + [target]): + X = df[features] + y = df[target] + + # تقسیم داده‌ها + X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) + + # آموزش مدل + model = RandomForestRegressor(n_estimators=100, random_state=42) + model.fit(X_train, y_train) + + # ارزیابی مدل + y_pred = model.predict(X_test) + mse = mean_squared_error(y_test, y_pred) + r2 = r2_score(y_test, y_pred) + + self.models['growth_prediction'] = { + 'model': model, + 'features': features, + 'metrics': { + 'mse': mse, + 'r2': r2 + } + } + + return self.models['growth_prediction'] + else: + st.warning("داده‌های کافی برای آموزش مدل وجود ندارد.") + return None + + except Exception as e: + st.error(f"خطا در آموزش مدل پیش‌بینی رشد: {str(e)}") + return None + + def predict_growth(self, moisture, date): + if 'growth_prediction' not in self.models: + self.train_growth_prediction_model() + + if 'growth_prediction' not in self.models: + return None + + try: + # تبدیل تاریخ به ویژگی‌های عددی + date_obj = pd.to_datetime(date) + month = date_obj.month + day = date_obj.day + + # پیش‌بینی + features = self.models['growth_prediction']['features'] + model = self.models['growth_prediction']['model'] + + prediction = model.predict([[month, day, moisture]])[0] + return prediction + + except Exception as e: + st.error(f"خطا در پیش‌بینی رشد: {str(e)}") + return None + + def detect_stress(self, ndvi_value, ndmi_value): + # تشخیص تنش بر اساس آستانه‌های شاخص‌ها + stress_level = "سالم" + stress_cause = [] + + if ndvi_value < 0.4: + stress_level = "تنش شدید" + stress_cause.append("کاهش پوشش گیاهی") + elif ndvi_value < 0.6: + stress_level = "تنش متوسط" + stress_cause.append("کاهش نسبی پوشش گیاهی") + + if ndmi_value < 0.1: + stress_level = "تنش شدید" if stress_level == "تنش شدید" else "تنش متوسط" + stress_cause.append("کمبود رطوبت") + elif ndmi_value < 0.3: + if stress_level != "تنش شدید": + stress_level = "تنش متوسط" + stress_cause.append("کمبود نسبی رطوبت") + 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 + 'level': stress_level, + 'causes': stress_cause } -# 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) +# تابع اصلی برنامه +def main(): + # مقداردهی اولیه کلاس‌ها + data_handler = DataHandler() + gee_handler = GEEHandler() + analytics = AdvancedAnalytics(data_handler) + + # عنوان برنامه + st.title("🌱 سیستم پایش هوشمند مزارع نیشکر") + + # منوی افقی + st.markdown(""" +
+ + +
ورود اطلاعات
+ + + +
+ """, unsafe_allow_html=True) + + # انتخاب بخش فعال + tabs = ["داشبورد", "نقشه مزارع", "ورود اطلاعات", "تحلیل داده‌ها", "گزارش‌گیری", "تنظیمات"] + active_tab = st.radio("بخش", tabs, label_visibility="collapsed") + + # بخش داشبورد + if active_tab == "داشبورد": + st.header("📊 داشبورد مدیریت مزارع") - folium.Marker( - [lat, lon], - popup=f'مزرعه {farm_id}', - tooltip=f'مزرعه {farm_id}', - icon=folium.Icon(color='green', icon='leaf') - ).add_to(m) + # انتخاب تاریخ + available_dates = data_handler.farm_db['تاریخ'].unique() if data_handler.farm_db is not None else [] + if len(available_dates) > 0: + selected_date = st.selectbox("انتخاب تاریخ", available_dates, index=len(available_dates)-1) + else: + selected_date = datetime.now().strftime("%Y-%m-%d") + st.warning("هیچ تاریخی در داده‌ها یافت نشد. از تاریخ امروز استفاده می‌شود.") - folium.Circle( - [lat, lon], - radius=1500, - color='green', - fill=True, - fill_color='green', - fill_opacity=0.1 - ).add_to(m) + # آمار کلی + stats = data_handler.get_farm_stats(selected_date) - folium.LayerControl().add_to(m) + # نمایش کارت‌های آماری + col1, col2, col3, col4 = st.columns(4) - legend_html = ''' -
-
''' + legend_title + '''
-
-
- کم + with col1: + st.markdown(""" +
+
تعداد کل مزارع
+
{}
-
-
- متوسط + """.format(stats['total_farms']), unsafe_allow_html=True) + + with col2: + st.markdown(""" +
+
مزارع فعال
+
{}
-
-
- زیاد + """.format(stats['active_farms']), unsafe_allow_html=True) + + with col3: + st.markdown(""" +
+
میانگین ارتفاع
+
{:.1f}
+
سانتی‌متر
-
- ''' - m.get_root().html.add_child(folium.Element(legend_html)) - - 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) - }) - - # 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' - - 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 = [] + """.format(stats['avg_height']), unsafe_allow_html=True) - 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 - } - - # 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" - - 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']} -
+ with col4: + st.markdown(""" +
+
میانگین رطوبت
+
{:.1f}
+
درصد
-
-
- """, 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("### توزیع واریته‌ها و سن محصول") + """.format(stats['avg_moisture']), unsafe_allow_html=True) + # نمودارها و نقشه 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') - - 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) - - 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 - ) - 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.subheader("توزیع واریته‌های نیشکر") + if data_handler.farm_coords is not None and 'variety' in data_handler.farm_coords.columns: + variety_counts = data_handler.farm_coords['variety'].value_counts() + fig = px.pie( + values=variety_counts.values, + names=variety_counts.index, + title="توزیع واریته‌های نیشکر", + color_discrete_sequence=px.colors.sequential.Greens ) + fig.update_traces(textposition='inside', textinfo='percent+label') 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("### تنظیمات نقشه") + st.info("داده‌ای برای نمایش توزیع واریته‌ها وجود ندارد.") - 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" + with col2: + st.subheader("توزیع سن مزارع") + if data_handler.farm_coords is not None and 'age' in data_handler.farm_coords.columns: + age_counts = data_handler.farm_coords['age'].value_counts() + fig = px.bar( + x=age_counts.index, + y=age_counts.values, + title="توزیع سن مزارع", + labels={'x': 'سن', 'y': 'تعداد مزارع'}, + color=age_counts.values, + color_continuous_scale='Greens' ) 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}" - ) - - with col2: - selected_day = st.selectbox( - "انتخاب روز", - options=["شنبه", "یکشنبه", "دوشنبه", "سه‌شنبه", "چهارشنبه", "پنجشنبه"] - ) - - filtered_farms = farm_df[farm_df['روز'] == selected_day] - - if filtered_farms.empty: - st.warning(f"هیچ مزرعه‌ای برای روز {selected_day} در پایگاه داده وجود ندارد.") + st.info("داده‌ای برای نمایش توزیع سن مزارع وجود ندارد.") + + # نقشه مزارع + st.subheader("نقشه مزارع") + if data_handler.farm_coords is not None: + m = MapUtils.create_base_map() + m = MapUtils.add_farm_markers(m, data_handler.farm_coords, data_handler.crop_log) + folium_static(m, width=1200, height=500) 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) - }) - - 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 - ) - - 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 - - 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() - - with tab2: - st.markdown("### آپلود فایل اکسل") - - uploaded_file = st.file_uploader("فایل اکسل خود را آپلود کنید", type=["xlsx", "xls", "csv"]) - - 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}") - - st.markdown("### راهنمای فرمت فایل") - st.markdown(""" - فایل اکسل باید شامل ستون‌های زیر باشد: - - مزرعه - - ایستگاه 1 تا 5 - - چاهک 1 و 2 - - رطوبت غلاف - - نیتروژن - می‌توانید از [این فایل نمونه](https://example.com/sample.xlsx) به عنوان الگو استفاده کنید. - """) - - 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" - ) - - 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) - - 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'] - }) - - growth_rate_df = pd.DataFrame(growth_rates) - - 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() - - st.altair_chart(chart, use_container_width=True) + st.info("داده‌ای برای نمایش نقشه مزارع وجود ندارد.") + + # جدول داده‌ها + st.subheader("جدول اطلاعات مزارع") + if data_handler.farm_db is not None: + active_farms = data_handler.get_active_farms(selected_date) + if len(active_farms) > 0: + st.dataframe(active_farms, use_container_width=True) + else: + st.info(f"هیچ مزرعه فعالی در تاریخ {selected_date} یافت نشد.") else: - st.warning("داده‌ای برای نمایش وجود ندارد.") - - with tab2: - st.markdown("### مقایسه واریته‌ها") - - variety_age_groups = farm_df.groupby(['واریته', 'سن']).size().reset_index(name='تعداد') - - 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 - }) - - 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 - }) - - correlation_df = pd.DataFrame(correlation_data) - - 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" - ) - st.plotly_chart(fig, use_container_width=True) - - correlation = correlation_df['Moisture'].corr(correlation_df['Height']) - st.info(f"ضریب همبستگی بین رطوبت و ارتفاع: {correlation:.2f}") + st.info("داده‌ای برای نمایش جدول مزارع وجود ندارد.") - with tab4: - st.markdown("### پیش‌بینی رشد مزارع") + # بخش نقشه مزارع + elif active_tab == "نقشه مزارع": + st.header("🗺️ نقشه تعاملی مزارع") - selected_farm_for_prediction = st.selectbox( - "انتخاب مزرعه", - options=farm_df['مزرعه'].tolist(), - format_func=lambda x: f"مزرعه {x}" - ) - - 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, - legend=dict( - orientation="h", - yanchor="bottom", - y=1.02, - xanchor="right", - x=1 - ) - ) - - fig.add_vline(x=15.5, line_width=1, line_dash="dash", line_color="gray") - - st.plotly_chart(fig, use_container_width=True) - - st.markdown("### جزئیات پیش‌بینی") - - col1, col2 = st.columns(2) + col1, col2 = st.columns([1, 3]) with col1: - st.metric( - label="ارتفاع فعلی", - value=f"{heights[-1]:.1f} cm", - delta=f"{heights[-1] - heights[-2]:.1f} cm" - ) - - 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] - }) - - st.dataframe(prediction_table, use_container_width=True, hide_index=True) - - st.markdown("### عوامل مؤثر بر رشد") - - factors = ['رطوبت', 'نیتروژن', 'دما', 'آبیاری', 'نور'] - factor_values = [85, 70, 60, 90, 75] - - fig = go.Figure() - - fig.add_trace(go.Scatterpolar( - r=factor_values, - theta=factors, - fill='toself', - name='عوامل مؤثر', - line_color='#1a8754' - )) - - fig.update_layout( - polar=dict( - radialaxis=dict( - visible=True, - range=[0, 100] - ) - ), - showlegend=False, - font=dict(family='Vazirmatn'), - height=400 - ) - - st.plotly_chart(fig, use_container_width=True) - -# Reporting Page -elif selected == "گزارش‌گیری": - st.markdown("## گزارش‌گیری پیشرفته") - - 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) - - col1, col2 = st.columns(2) - - with col1: - start_date = st.date_input( - "تاریخ شروع", - value=datetime.now() - timedelta(days=30), - format="YYYY-MM-DD" - ) - - with col2: - end_date = st.date_input( - "تاریخ پایان", - value=datetime.now(), - format="YYYY-MM-DD" - ) - - report_type = st.selectbox( - "نوع گزارش", - options=["گزارش کلی", "گزارش رشد", "گزارش رطوبت", "گزارش مقایسه‌ای واریته‌ها"] - ) - - if st.button("تولید گزارش", type="primary", use_container_width=True): - with st.spinner('در حال تولید گزارش...'): - time.sleep(2) + st.subheader("تنظیمات نقشه") - if report_type == "گزارش کلی": - st.markdown("### گزارش کلی وضعیت مزارع") - - col1, col2, col3, col4 = st.columns(4) - - with col1: - st.metric("تعداد کل مزارع", len(farm_df)) - - with col2: - st.metric("میانگین ارتفاع", f"{np.random.uniform(150, 200):.1f} cm") - - with col3: - st.metric("میانگین رطوبت", f"{np.random.uniform(60, 80):.1f}%") - - 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 - ) - 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': 'ارتفاع (سانتی‌متر)'} - ) - 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("### گزارش رشد مزارع") - - col1, col2, col3 = st.columns(3) - - with col1: - st.metric("میانگین رشد هفتگی", f"{np.random.uniform(10, 15):.1f} cm") - - with col2: - st.metric("حداکثر رشد هفتگی", f"{np.random.uniform(20, 25):.1f} cm") - - 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" - ) - 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'] - ) - 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, - 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) - - with col1: - st.metric("میانگین رطوبت", f"{np.random.uniform(60, 70):.1f}%") - - with col2: - st.metric("حداکثر رطوبت", f"{np.random.uniform(80, 90):.1f}%") - - 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': 'رطوبت (%)'} - ) - 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.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' - ) - 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") - ) - 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] - ) - ), - title='مقایسه واریته‌ها', - font=dict(family="Vazirmatn"), - showlegend=True - ) - 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: - st.download_button( - label="دانلود گزارش (PDF)", - data=b"This is a mock PDF report", - file_name="farm_report.pdf", - mime="application/pdf", - ) - with col2: - 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", - ) + # انتخاب مزرعه -# Settings Page -elif selected == "تنظیمات": - st.markdown("## تنظیمات سیستم") - - tab1, tab2, tab3, tab4 = st.tabs(["تنظیمات کاربری", "تنظیمات سیستم", "مدیریت داده‌ها", "پشتیبان‌گیری"]) - - with tab1: - st.markdown("### تنظیمات کاربری") - - st.markdown("#### پروفایل کاربری") - col1, col2 = st.columns(2) - with col1: - user_name = st.text_input("نام کاربری", value="کاربر نمونه") - with col2: - user_email = st.text_input("ایمیل", value="user@example.com") - - user_role = st.selectbox( - "نقش کاربری", - options=["مدیریت مطالعات", "پرسنل", "اپراتور"], - index=1 - ) - - st.markdown("#### تغییر رمز عبور") - col1, col2 = st.columns(2) - with col1: - current_password = st.text_input("رمز عبور فعلی", type="password") - with col2: - new_password = st.text_input("رمز عبور جدید", type="password") - - confirm_password = st.text_input("تکرار رمز عبور جدید", type="password") - - if st.button("تغییر رمز عبور", type="primary"): - st.success("رمز عبور با موفقیت تغییر کرد.") - - 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("### تنظیمات سیستم") - - system_language = st.selectbox( - "زبان سیستم", - options=["فارسی", "English", "العربية"], - index=0 - ) - - date_format = st.selectbox( - "فرمت تاریخ", - options=["YYYY/MM/DD", "DD/MM/YYYY", "MM/DD/YYYY"], - index=0 - ) - - st.markdown("#### تنظیمات ظاهری") - theme = st.radio( - "تم", - options=["روشن", "تیره", "سیستم"], - index=2 - ) - primary_color = st.color_picker("رنگ اصلی", value="#1a8754") - - st.markdown("#### تنظیمات نقشه") - default_map_view = st.selectbox( - "نمای پیش‌فرض نقشه", - options=["نقشه", "ماهواره", "ترکیبی"], - index=0 - ) - default_map_layer = st.selectbox( - "لایه پیش‌فرض نقشه", - options=["NDVI", "NDMI", "EVI", "NDWI", "SoilMoisture"], - index=0 - ) - - st.markdown("#### تنظیمات مدل هوش مصنوعی") - ai_model = st.selectbox( - "مدل هوش مصنوعی", - options=["GPT-3", "GPT-4", "BERT"], - index=1 - ) - model_update_frequency = st.selectbox( - "تناوب به‌روزرسانی مدل", - options=["روزانه", "هفتگی", "ماهانه"], - index=1 - ) - - if st.button("ذخیره تنظیمات", type="primary"): - st.success("تنظیمات با موفقیت ذخیره شدند.") - - with tab3: - st.markdown("### مدیریت داده‌ها") - - 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", - ) - - 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) - - if st.button("ذخیره تنظیمات پشتیبان‌گیری"): - st.success("تنظیمات پشتیبان‌گیری با موفقیت ذخیره شدند.") - -# Add a footer -st.markdown(""" - -""", unsafe_allow_html=True) + if data_handler.farm_coords is not None: + farm_names = data_handler.farm_coords['name'].unique() + selected_farm = st.selectbox("انتخاب مزرعه", ["همه مزارع"] + list(farm_names)) + + # فیلتر بر اساس سن مزرعه + age_options = ["همه"] + list(data_handler.farm_coords['age'].unique()) + selected_age = st.selectbox("فیلتر بر اساس سن", age_options) + + # فیلتر بر اساس واریته + variety_options = ["همه"] + list(data_handler.farm_coords['variety'].unique()) + selected_variety = st.selectbox("فیلتر بر اساس واریته", variety_options) + + # دکمه اعمال فیلترها + apply_filters = st.button("اعمال فیلترها") + + # نمایش اطلاعات مزرعه انتخاب شده + if selected_farm != "همه مزارع": + farm_info = data_handler.farm_coords[data_handler.farm_coords['name'] == selected_farm].iloc[0] + st.subheader(f"اطلاعات مزرعه {selected_farm}") + st.write(f"واریته: {farm_info['variety']}") + st.write(f"سن: {farm_info['age']}") + + # نمایش آخرین وضعیت مزرعه از کراپ لاگ + if data_handler.crop_log is not None: + farm_log = data_handler.crop_log[data_handler.crop_log['مزرعه'] == selected_farm] + if len(farm_log) > 0: + last_log = farm_log.iloc[-1] + st.write(f"آخرین ارتفاع ثبت شده: {last_log['ارتفاع']} سانتی‌متر") + st.write(f"تاریخ آخرین بازدید: {last_log['تاریخ']}") + else: + st.info("داده‌ای برای نمایش مزارع وجود ندارد.")