Sugarcane / app.py
Esmaeilkianii's picture
Update app.py
14a7f80 verified
raw
history blame
30.1 kB
import streamlit as st
import pandas as pd
import numpy as np
import folium
from streamlit_folium import folium_static
import ee
import os
import json
import time
from datetime import datetime, timedelta
import plotly.express as px
import plotly.graph_objects as go
from PIL import Image
import 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
from sklearn.linear_model import LinearRegression
# Page configuration
st.set_page_config(
page_title="سامانه هوشمند پایش مزارع نیشکر دهخدا",
page_icon="🌿",
layout="wide",
initial_sidebar_state="expanded"
)
# Custom CSS with cascading panels and eye-catching reflexes
st.markdown("""
<style>
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@100;200;300;400;500;600;700;800;900&display=swap');
* {
font-family: 'Vazirmatn', sans-serif !important;
}
/* Main container styling */
.main {
background: linear-gradient(135deg, #f0f4f8 0%, #d9e2ec 100%);
}
/* Header styling */
.main-header {
background: linear-gradient(90deg, #2ecc71 0%, #27ae60 100%);
padding: 2rem;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
margin-bottom: 2rem;
position: relative;
overflow: hidden;
animation: headerPulse 3s infinite ease-in-out;
}
@keyframes headerPulse {
0% { box-shadow: 0 10px 30px rgba(46, 204, 113, 0.2); }
50% { box-shadow: 0 15px 40px rgba(46, 204, 113, 0.4); }
100% { box-shadow: 0 10px 30px rgba(46, 204, 113, 0.2); }
}
.main-header h1 {
color: white;
font-weight: 700;
margin: 0;
text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2);
}
.main-header p {
color: rgba(255, 255, 255, 0.9);
margin: 0;
font-size: 1.1rem;
}
/* Metric card styling */
.metric-container {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
gap: 25px;
padding: 20px;
}
.metric-card {
background: linear-gradient(135deg, #3498db 0%, #2980b9 100%);
border-radius: 20px;
padding: 25px;
width: 240px;
text-align: center;
color: white;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
transition: all 0.4s ease;
position: relative;
overflow: hidden;
}
.metric-card:hover {
transform: translateY(-10px) scale(1.05);
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.25);
}
.metric-card::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: rgba(255, 255, 255, 0.1);
transform: rotate(30deg);
transition: all 0.4s ease;
}
.metric-card:hover::before {
top: 100%;
left: 100%;
}
.metric-icon { font-size: 2.8rem; margin-bottom: 15px; animation: iconBounce 2s infinite; }
.metric-value { font-size: 2.2rem; font-weight: 700; text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.2); }
.metric-label { font-size: 1.1rem; opacity: 0.9; }
@keyframes iconBounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
/* Dropdown menu styling */
.stSelectbox {
background: white;
border-radius: 12px;
padding: 10px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.stSelectbox:hover {
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
}
/* Navigation menu styling */
.st-emotion-cache-1lcbz7b {
background-color: transparent !important;
padding: 0 !important;
margin-bottom: 20px !important;
}
.st-emotion-cache-1j7d69d {
--hover-color: #e9f7ef !important;
border-radius: 12px !important;
font-size: 16px !important;
text-align: center !important;
margin: 0 !important;
transition: all 0.3s ease;
}
.st-emotion-cache-1j7d69d:hover {
background-color: #e9f7ef !important;
transform: translateY(-2px);
}
.st-emotion-cache-1j7d69d[data-selected="true"] {
background-color: #2ecc71 !important;
color: white !important;
font-weight: 600 !important;
box-shadow: 0 5px 15px rgba(46, 204, 113, 0.3);
}
/* Button styling */
.stButton>button {
border-radius: 50px;
padding: 0.7rem 2rem;
font-weight: 600;
background: linear-gradient(90deg, #2ecc71 0%, #27ae60 100%);
color: white;
border: none;
transition: all 0.3s ease;
}
.stButton>button:hover {
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(46, 204, 113, 0.3);
}
/* Tabs styling */
.stTabs [data-baseweb="tab-list"] {
gap: 10px;
}
.stTabs [data-baseweb="tab"] {
border-radius: 8px 8px 0 0;
padding: 12px 20px;
background-color: #f8f9fa;
transition: all 0.3s ease;
}
.stTabs [aria-selected="true"] {
background-color: #2ecc71 !important;
color: white !important;
box-shadow: 0 5px 15px rgba(46, 204, 113, 0.2);
}
/* Footer styling */
footer {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
background: linear-gradient(90deg, #2ecc71 0%, #27ae60 100%);
color: white;
text-align: center;
padding: 15px 0;
box-shadow: 0 -5px 15px rgba(0, 0, 0, 0.1);
}
</style>
""", unsafe_allow_html=True)
# Load real farm data from CSV
@st.cache_data
def load_farm_data():
try:
df = pd.read_csv("کراپ لاگ کلی (1).csv")
df.columns = [col.strip() for col in df.columns]
df.rename(columns={
'سال': 'Year', 'هفته': 'Week', 'مزرعه': 'Farm_ID', 'کانال': 'Channel', 'اداره': 'Administration',
'مساحت': 'Area', 'مساحت زیر مجموعه': 'SubArea', 'رقم': 'Variety', 'سن': 'Age',
'ایستگاه 1': 'Station1', 'ایستگاه 2': 'Station2', 'ایستگاه 3': 'Station3',
'ایستگاه 4': 'Station4', 'ایستگاه 5': 'Station5', 'ارتفاع هفته جاری مزرعه': 'CurrentHeight',
'ارتفاع هفته گذشته مزرعه': 'PreviousHeight', 'رشد هفته جاری': 'CurrentGrowth',
'رشد هفته گذشته': 'PreviousGrowth', 'نیتروژن فعلی': 'CurrentNitrogen',
'نیتروژن استاندارد فعلی': 'StandardNitrogen', 'نیتروژن قبلی': 'PreviousNitrogen',
'نیتروژن استاندارد قبلی': 'PreviousStandardNitrogen', 'رطوبت غلاف فعلی': 'CurrentMoisture',
'رطوبت استاندارد فعلی': 'StandardMoisture', 'رطوبت غلاف قبلی': 'PreviousMoisture',
'رطوبت استاندارد قبلی': 'PreviousStandardMoisture', 'چاهک 1': 'Well1', 'تاریخ قرائت': 'Well1Date',
'چاهک 2': 'Well2', 'تاریخ قرائت.1': 'Well2Date'
}, inplace=True)
numeric_cols = ['Area', 'CurrentHeight', 'PreviousHeight', 'CurrentGrowth', 'PreviousGrowth',
'CurrentNitrogen', 'PreviousNitrogen', 'CurrentMoisture', 'PreviousMoisture',
'Station1', 'Station2', 'Station3', 'Station4', 'Station5', 'Well1', 'Well2']
for col in numeric_cols:
if col in df.columns:
df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0)
return df
except Exception as e:
st.error(f"خطا در بارگذاری داده‌های مزارع: {e}")
return pd.DataFrame()
@st.cache_data
def load_coordinates_data():
try:
coords_df = pd.read_csv("farm_coordinates.csv")
coords_df.rename(columns={
'مزرعه': 'Farm_ID', 'عرض جغرافیایی': 'Latitude', 'طول جغرافیایی': 'Longitude'
}, inplace=True)
return coords_df
except Exception as e:
st.error(f"خطا در بارگذاری داده‌های مختصات: {e}")
return pd.DataFrame()
@st.cache_data
def load_day_data():
try:
day_df = pd.read_csv("پایگاه داده (1).csv")
day_df.rename(columns={'مزرعه': 'Farm_ID', 'روز': 'Day'}, inplace=True)
return day_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()
# 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
# 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'] == farm_id].iloc[0]
lat, lon = farm_row['Latitude'], farm_row['Longitude']
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']}
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']}
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']}
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']}
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}', icon=folium.Icon(color='green', icon='leaf')).add_to(m)
folium.LayerControl().add_to(m)
return m
except Exception as e:
st.error(f"خطا در ایجاد نقشه: {e}")
return None
# Generate real growth data
def generate_real_growth_data(selected_variety="all", selected_age="all"):
filtered_farms = farm_df
if selected_variety != "all":
filtered_farms = filtered_farms[filtered_farms['Variety'] == selected_variety]
if selected_age != "all":
filtered_farms = filtered_farms[filtered_farms['Age'] == selected_age]
farm_growth_data = []
weeks = filtered_farms['Week'].unique()
for farm_id in filtered_farms['Farm_ID'].unique():
farm_data = filtered_farms[filtered_farms['Farm_ID'] == farm_id]
growth_data = {
'farm_id': farm_id,
'variety': farm_data['Variety'].iloc[0] if not farm_data.empty else 'Unknown',
'age': farm_data['Age'].iloc[0] if not farm_data.empty else 'Unknown',
'weeks': weeks,
'heights': [farm_data[farm_data['Week'] == week]['CurrentHeight'].mean() if not farm_data[farm_data['Week'] == week].empty else 0 for week in weeks]
}
farm_growth_data.append(growth_data)
if farm_growth_data:
avg_heights = []
for week in weeks:
week_heights = [farm['heights'][list(weeks).index(week)] for farm in farm_growth_data if farm['heights'][list(weeks).index(week)] > 0]
avg_heights.append(round(sum(week_heights) / len(week_heights)) if week_heights else 0)
avg_growth_data = {'farm_id': 'میانگین', 'variety': 'همه', 'age': 'همه', 'weeks': weeks, 'heights': avg_heights}
return {'individual': farm_growth_data, 'average': avg_growth_data}
return {
'individual': [],
'average': {'farm_id': 'میانگین', 'variety': 'همه', 'age': 'همه', 'weeks': weeks, 'heights': [0] * len(weeks)}
}
# Initialize Earth Engine and load data
ee_initialized = initialize_earth_engine()
farm_df = load_farm_data()
coordinates_df = load_coordinates_data()
day_df = load_day_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 = farm_df.copy()
# Main header
st.markdown('<div class="main-header">', unsafe_allow_html=True)
st.markdown('<h1>سامانه هوشمند پایش مزارع نیشکر دهخدا</h1>', unsafe_allow_html=True)
st.markdown('<p>پلتفرم جامع مدیریت، پایش و تحلیل داده‌های مزارع نیشکر با فناوری پیشرفته</p>', unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
# 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": "#2ecc71", "font-size": "18px"},
"nav-link": {"font-size": "16px", "text-align": "center", "margin":"0px", "--hover-color": "#e9f7ef", "border-radius": "12px"},
"nav-link-selected": {"background-color": "#2ecc71", "color": "white", "font-weight": "600"},
}
)
# Dashboard
if selected == "داشبورد":
st.markdown('<div class="metric-container">', unsafe_allow_html=True)
st.markdown("""
<div class="metric-card">
<div class="metric-icon">🌾</div>
<div class="metric-value">{}</div>
<div class="metric-label">تعداد مزارع</div>
</div>
""".format(int(len(farm_df["Farm_ID"].unique()))), unsafe_allow_html=True)
st.markdown("""
<div class="metric-card">
<div class="metric-icon">📏</div>
<div class="metric-value">{:.0f} ha</div>
<div class="metric-label">مساحت کل</div>
</div>
""".format(farm_df["Area"].sum()), unsafe_allow_html=True)
st.markdown("""
<div class="metric-card">
<div class="metric-icon">📈</div>
<div class="metric-value">{:.1f} cm</div>
<div class="metric-label">میانگین ارتفاع</div>
</div>
""".format(farm_df["CurrentHeight"].mean()), unsafe_allow_html=True)
st.markdown("""
<div class="metric-card">
<div class="metric-icon">💧</div>
<div class="metric-value">{:.1f}%</div>
<div class="metric-label">میانگین رطوبت</div>
</div>
""".format(farm_df["CurrentMoisture"].mean()), unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
tab1, tab2, tab3, tab4 = st.tabs(["نمای کلی", "نقشه مزارع", "نمودارها", "داده‌ها"])
with tab1:
st.subheader("توزیع واریته‌ها و سن محصول")
col1, col2 = st.columns(2)
with col1:
fig = px.pie(farm_df['Variety'].value_counts().reset_index(), values='count', names='Variety', title='توزیع واریته‌ها')
st.plotly_chart(fig, use_container_width=True)
with col2:
fig = px.pie(farm_df['Age'].value_counts().reset_index(), values='count', names='Age', title='توزیع سن محصول')
st.plotly_chart(fig, use_container_width=True)
st_lottie(lottie_farm, height=200, key="farm_animation")
with tab2:
if not coordinates_df.empty:
m = folium.Map(location=[31.45, 48.72], zoom_start=12)
for _, farm in coordinates_df.iterrows():
folium.Marker([farm['Latitude'], farm['Longitude']], popup=f'مزرعه {farm["Farm_ID"]}').add_to(m)
folium_static(m, width=1000, height=600)
with tab3:
st.subheader("نمودار رشد")
col1, col2 = st.columns(2)
with col1:
variety = st.selectbox("انتخاب واریته", ["all"] + list(farm_df['Variety'].unique()), key="variety_chart")
with col2:
age = st.selectbox("انتخاب سن", ["all"] + list(farm_df['Age'].unique()), key="age_chart")
growth_data = generate_real_growth_data(variety, age)
fig = go.Figure()
for farm_data in 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']}"))
fig.update_layout(title='رشد هفتگی مزارع', xaxis_title='هفته', yaxis_title='ارتفاع (سانتی‌متر)')
st.plotly_chart(fig, use_container_width=True)
with tab4:
st.subheader("داده‌های مزارع")
st.dataframe(farm_df, use_container_width=True)
# Map Page
elif selected == "نقشه مزارع":
st.markdown("## نقشه مزارع با شاخص‌های ماهواره‌ای")
col1, col2 = st.columns([1, 3])
with col1:
farm_id = st.selectbox("انتخاب مزرعه", coordinates_df['Farm_ID'].tolist())
date = st.date_input("انتخاب تاریخ", datetime.now())
layer_type = st.selectbox("انتخاب شاخص", ["NDVI", "NDMI", "EVI", "NDWI"])
if st.button("تولید نقشه"):
with st.spinner('در حال تولید نقشه...'):
m = create_ee_map(farm_id, date.strftime('%Y-%m-%d'), layer_type)
if m:
st.session_state['last_map'] = m
st.success(f"نقشه {layer_type} تولید شد.")
with col2:
if 'last_map' in st.session_state:
folium_static(st.session_state['last_map'], width=800, height=600)
# Data Entry Page
elif selected == "ورود اطلاعات":
st.markdown("## ورود اطلاعات روزانه مزارع")
tab1, tab2 = st.tabs(["ورود دستی", "آپلود فایل"])
with tab1:
col1, col2 = st.columns(2)
with col1:
week = st.selectbox("انتخاب هفته", [str(i) for i in range(1, 23)], key="week_entry")
with col2:
day = st.selectbox("انتخاب روز", day_df['Day'].unique(), key="day_entry")
filtered_farms = farm_df[farm_df['Week'] == int(week)]
if not filtered_farms.empty:
data_key = f"data_{week}_{day}"
if data_key not in st.session_state:
st.session_state[data_key] = pd.DataFrame({
'Farm_ID': filtered_farms['Farm_ID'],
'Station1': [0] * len(filtered_farms),
'Station2': [0] * len(filtered_farms),
'Station3': [0] * len(filtered_farms),
'Station4': [0] * len(filtered_farms),
'Station5': [0] * len(filtered_farms),
'CurrentHeight': [0.0] * len(filtered_farms),
'CurrentMoisture': [0.0] * len(filtered_farms)
})
edited_df = st.data_editor(st.session_state[data_key], use_container_width=True)
if st.button("ذخیره اطلاعات"):
st.session_state.heights_df = pd.concat([st.session_state.heights_df, edited_df], ignore_index=True)
st.success("داده‌ها ذخیره شدند.")
with tab2:
uploaded_file = st.file_uploader("آپلود فایل", type=["csv", "xlsx"])
if uploaded_file:
try:
df = pd.read_csv(uploaded_file) if uploaded_file.name.endswith('.csv') else pd.read_excel(uploaded_file)
numeric_cols = ['CurrentHeight', 'CurrentMoisture', 'Station1', 'Station2', 'Station3', 'Station4', 'Station5']
for col in numeric_cols:
if col in df.columns:
df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0)
st.dataframe(df)
if st.button("ذخیره فایل"):
st.session_state.heights_df = pd.concat([st.session_state.heights_df, df], ignore_index=True)
st.success("فایل ذخیره شد.")
except Exception as e:
st.error(f"خطا در خواندن فایل: {e}")
# Data Analysis Page
elif selected == "تحلیل داده‌ها":
st.markdown("## تحلیل هوشمند داده‌ها")
tab1, tab2, tab3, tab4 = st.tabs(["تحلیل رشد", "مقایسه واریته‌ها", "تحلیل رطوبت", "پیش‌بینی"])
with tab1:
col1, col2 = st.columns(2)
with col1:
variety = st.selectbox("انتخاب واریته", ["all"] + list(farm_df['Variety'].unique()), key="variety_analysis")
with col2:
age = st.selectbox("انتخاب سن", ["all"] + list(farm_df['Age'].unique()), key="age_analysis")
growth_data = generate_real_growth_data(variety, age)
fig = go.Figure()
for farm_data in 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']}"))
fig.update_layout(title='رشد هفتگی', xaxis_title='هفته', yaxis_title='ارتفاع (سانتی‌متر)')
st.plotly_chart(fig, use_container_width=True)
with tab2:
fig = px.box(farm_df, x='Variety', y='CurrentHeight', title='مقایسه ارتفاع بر اساس واریته')
st.plotly_chart(fig, use_container_width=True)
with tab3:
fig = px.scatter(farm_df, x='CurrentMoisture', y='CurrentHeight', color='Farm_ID', title='همبستگی رطوبت و ارتفاع')
st.plotly_chart(fig, use_container_width=True)
with tab4:
farm_id = st.selectbox("انتخاب مزرعه", farm_df['Farm_ID'].tolist(), key="predict_farm")
farm_data = farm_df[farm_df['Farm_ID'] == farm_id]
if len(farm_data) > 1:
model = LinearRegression()
model.fit(farm_data['Week'].values.reshape(-1, 1), farm_data['CurrentHeight'].values)
future_weeks = np.array(range(max(farm_data['Week']) + 1, 30)).reshape(-1, 1)
future_heights = model.predict(future_weeks)
fig = go.Figure()
fig.add_trace(go.Scatter(x=farm_data['Week'], y=farm_data['CurrentHeight'], mode='lines+markers', name='داده‌های واقعی'))
fig.add_trace(go.Scatter(x=future_weeks.flatten(), y=future_heights, mode='lines', name='پیش‌بینی'))
fig.update_layout(title=f'پیش‌بینی رشد مزرعه {farm_id}', xaxis_title='هفته', yaxis_title='ارتفاع (سانتی‌متر)')
st.plotly_chart(fig, use_container_width=True)
# Report Generation Page
elif selected == "گزارش‌گیری":
st.markdown("## گزارش‌گیری")
report_week = st.selectbox("انتخاب هفته", [str(i) for i in range(1, 23)], key="report_week")
report_day = st.selectbox("انتخاب روز", day_df['Day'].unique(), key="report_day")
report_df = st.session_state.heights_df[
(st.session_state.heights_df['Week'] == int(report_week)) &
(st.session_state.heights_df['Farm_ID'].isin(day_df[day_df['Day'] == report_day]['Farm_ID']))
]
if not report_df.empty:
st.dataframe(report_df)
csv = report_df.to_csv(index=False).encode('utf-8')
st.download_button(label="دانلود گزارش", data=csv, file_name=f"report_week_{report_week}_day_{report_day}.csv")
else:
st.warning(f"داده‌ای برای هفته {report_week} و روز {report_day} یافت نشد.")
st_lottie(lottie_report, height=200, key="report_animation")
# Settings Page
elif selected == "تنظیمات":
st.markdown("## تنظیمات سامانه")
if st.button("بارگذاری مجدد داده‌ها"):
st.session_state.heights_df = load_farm_data()
st.success("داده‌ها به‌روزرسانی شدند.")
theme = st.selectbox("انتخاب تم", ["سبز (پیش‌فرض)", "آبی"], key="theme_select")
if theme == "آبی":
st.markdown("""
<style>
.main-header {background: linear-gradient(90deg, #3498db 0%, #2980b9 100%);}
.metric-card {background: linear-gradient(135deg, #3498db 0%, #2980b9 100%);}
.stButton>button {background: linear-gradient(90deg, #3498db 0%, #2980b9 100%);}
footer {background: linear-gradient(90deg, #3498db 0%, #2980b9 100%);}
</style>
""", unsafe_allow_html=True)
# Footer
st.markdown("""
<footer>
<p>© 2025 سامانه هوشمند پایش مزارع نیشکر دهخدا. تمامی حقوق محفوظ است.</p>
</footer>
""", unsafe_allow_html=True)