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"""
+
', 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('