Spaces:
Running
Running
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 | |
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() | |
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() | |
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 | |
def load_lottie_url(url: str): | |
r = requests.get(url) | |
if r.status_code != 200: | |
return None | |
return r.json() | |
# Initialize Earth Engine | |
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) |