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