Spaces:
Running
Running
import streamlit as st | |
import pandas as pd | |
import numpy as np | |
import matplotlib.pyplot as plt | |
import plotly.express as px | |
import plotly.graph_objects as go | |
import folium | |
from folium.plugins import HeatMap | |
from streamlit_folium import folium_static | |
import json | |
import os | |
import ee | |
import geemap.foliumap as geemap | |
from datetime import datetime, timedelta | |
import io | |
import base64 | |
from reportlab.pdfgen import canvas | |
from reportlab.lib.pagesizes import letter | |
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle | |
from reportlab.lib import colors | |
from sklearn.ensemble import RandomForestRegressor | |
from streamlit_lottie import st_lottie | |
import requests | |
import seaborn as sns | |
from PIL import Image | |
import streamlit_authenticator as stauth | |
import pickle | |
from pathlib import Path | |
import time | |
import statsmodels.api as sm | |
from statsmodels.tsa.arima.model import ARIMA | |
# تنظیمات صفحه | |
st.set_page_config( | |
page_title="سامانه هوشمند پایش مزارع نیشکر", | |
page_icon="🌱", | |
layout="wide", | |
initial_sidebar_state="expanded" | |
) | |
# تنظیمات CSS سفارشی | |
with open("style.css") as f: | |
st.markdown(f'<style>{f.read()}</style>', unsafe_allow_html=True) | |
# تنظیمات فونت فارسی | |
st.markdown( | |
""" | |
<style> | |
@font-face { | |
font-family: 'Vazirmatn'; | |
src: url('https://cdn.jsdelivr.net/gh/rastikerdar/[email protected]/fonts/webfonts/Vazirmatn-Regular.woff2') format('woff2'); | |
font-weight: 400; | |
font-style: normal; | |
} | |
* { | |
font-family: 'Vazirmatn', sans-serif; | |
direction: rtl; | |
text-align: right; | |
} | |
</style> | |
""", | |
unsafe_allow_html=True | |
) | |
# توابع برای لود انیمیشنهای Lottie | |
def load_lottieurl(url): | |
r = requests.get(url) | |
if r.status_code != 200: | |
return None | |
return r.json() | |
# بارگذاری انیمیشنهای Lottie | |
lottie_farm = load_lottieurl("https://assets4.lottiefiles.com/packages/lf20_qbkoyp6j.json") | |
lottie_analytics = load_lottieurl("https://assets2.lottiefiles.com/private_files/lf30_ajzyv37m.json") | |
lottie_report = load_lottieurl("https://assets9.lottiefiles.com/packages/lf20_vfcu9yyj.json") | |
# اتصال به Google Earth Engine | |
def initialize_gee(): | |
try: | |
# مسیر فایل اعتبارسنجی سرویس اکانت | |
service_account = '[email protected]' | |
credentials_path = r"ee-esmaeilkiani13877-cfdea6eaf411.json" | |
credentials = ee.ServiceAccountCredentials(service_account, credentials_path) | |
ee.Initialize(credentials) | |
return True | |
except Exception as e: | |
st.error(f"خطا در اتصال به Google Earth Engine: {e}") | |
return False | |
# بارگذاری و پردازش دادهها | |
def load_farm_data(): | |
try: | |
# بارگذاری دادههای مزارع | |
farm_data = pd.read_csv('پایگاه داده (1).csv') | |
farm_coordinates = pd.read_csv('farm_coordinates.csv') | |
# پیشپردازش و یکپارچهسازی دادهها | |
# تبدیل ستونهای تاریخ به فرمت مناسب و غیره | |
return farm_data, farm_coordinates | |
except Exception as e: | |
st.error(f"خطا در بارگذاری دادهها: {e}") | |
return None, None | |
# توابع محاسبه شاخصهای گیاهی | |
def calculate_ndvi(geometry, date): | |
"""محاسبه شاخص NDVI برای یک منطقه و تاریخ مشخص""" | |
start_date = (datetime.strptime(date, '%Y-%m-%d') - timedelta(days=5)).strftime('%Y-%m-%d') | |
end_date = (datetime.strptime(date, '%Y-%m-%d') + timedelta(days=5)).strftime('%Y-%m-%d') | |
# استفاده از تصاویر Sentinel-2 | |
sentinel = ee.ImageCollection('COPERNICUS/S2_SR') \ | |
.filterDate(start_date, end_date) \ | |
.filterBounds(geometry) \ | |
.sort('CLOUDY_PIXEL_PERCENTAGE') \ | |
.first() | |
# محاسبه NDVI | |
ndvi = sentinel.normalizedDifference(['B8', 'B4']).rename('NDVI') | |
# گرفتن آمار NDVI برای منطقه | |
ndvi_stats = ndvi.reduceRegion( | |
reducer=ee.Reducer.mean().combine(ee.Reducer.min(), None, True).combine(ee.Reducer.max(), None, True), | |
geometry=geometry, | |
scale=10, | |
maxPixels=1e9 | |
).getInfo() | |
return ndvi, ndvi_stats | |
def calculate_ndwi(geometry, date): | |
"""محاسبه شاخص NDWI برای یک منطقه و تاریخ مشخص""" | |
start_date = (datetime.strptime(date, '%Y-%m-%d') - timedelta(days=5)).strftime('%Y-%m-%d') | |
end_date = (datetime.strptime(date, '%Y-%m-%d') + timedelta(days=5)).strftime('%Y-%m-%d') | |
# استفاده از تصاویر Sentinel-2 | |
sentinel = ee.ImageCollection('COPERNICUS/S2_SR') \ | |
.filterDate(start_date, end_date) \ | |
.filterBounds(geometry) \ | |
.sort('CLOUDY_PIXEL_PERCENTAGE') \ | |
.first() | |
# محاسبه NDWI (استفاده از باندهای NIR و SWIR) | |
ndwi = sentinel.normalizedDifference(['B8', 'B11']).rename('NDWI') | |
# گرفتن آمار NDWI برای منطقه | |
ndwi_stats = ndwi.reduceRegion( | |
reducer=ee.Reducer.mean().combine(ee.Reducer.min(), None, True).combine(ee.Reducer.max(), None, True), | |
geometry=geometry, | |
scale=10, | |
maxPixels=1e9 | |
).getInfo() | |
return ndwi, ndwi_stats | |
def calculate_evi(geometry, date): | |
"""محاسبه شاخص EVI برای یک منطقه و تاریخ مشخص""" | |
start_date = (datetime.strptime(date, '%Y-%m-%d') - timedelta(days=5)).strftime('%Y-%m-%d') | |
end_date = (datetime.strptime(date, '%Y-%m-%d') + timedelta(days=5)).strftime('%Y-%m-%d') | |
# استفاده از تصاویر Sentinel-2 | |
sentinel = ee.ImageCollection('COPERNICUS/S2_SR') \ | |
.filterDate(start_date, end_date) \ | |
.filterBounds(geometry) \ | |
.sort('CLOUDY_PIXEL_PERCENTAGE') \ | |
.first() | |
# محاسبه EVI | |
# EVI = 2.5 * ((NIR - RED) / (NIR + 6 * RED - 7.5 * BLUE + 1)) | |
nir = sentinel.select('B8') | |
red = sentinel.select('B4') | |
blue = sentinel.select('B2') | |
evi = nir.subtract(red).multiply(2.5).divide( | |
nir.add(red.multiply(6)).subtract(blue.multiply(7.5)).add(1) | |
).rename('EVI') | |
# گرفتن آمار EVI برای منطقه | |
evi_stats = evi.reduceRegion( | |
reducer=ee.Reducer.mean().combine(ee.Reducer.min(), None, True).combine(ee.Reducer.max(), None, True), | |
geometry=geometry, | |
scale=10, | |
maxPixels=1e9 | |
).getInfo() | |
return evi, evi_stats | |
def calculate_ndmi(geometry, date): | |
"""محاسبه شاخص NDMI برای یک منطقه و تاریخ مشخص""" | |
start_date = (datetime.strptime(date, '%Y-%m-%d') - timedelta(days=5)).strftime('%Y-%m-%d') | |
end_date = (datetime.strptime(date, '%Y-%m-%d') + timedelta(days=5)).strftime('%Y-%m-%d') | |
# استفاده از تصاویر Sentinel-2 | |
sentinel = ee.ImageCollection('COPERNICUS/S2_SR') \ | |
.filterDate(start_date, end_date) \ | |
.filterBounds(geometry) \ | |
.sort('CLOUDY_PIXEL_PERCENTAGE') \ | |
.first() | |
# محاسبه NDMI (استفاده از باندهای NIR و SWIR) | |
ndmi = sentinel.normalizedDifference(['B8', 'B11']).rename('NDMI') | |
# گرفتن آمار NDMI برای منطقه | |
ndmi_stats = ndmi.reduceRegion( | |
reducer=ee.Reducer.mean().combine(ee.Reducer.min(), None, True).combine(ee.Reducer.max(), None, True), | |
geometry=geometry, | |
scale=10, | |
maxPixels=1e9 | |
).getInfo() | |
return ndmi, ndmi_stats | |
def calculate_lai(geometry, date): | |
"""محاسبه شاخص LAI (سطح برگ) برای یک منطقه و تاریخ مشخص""" | |
start_date = (datetime.strptime(date, '%Y-%m-%d') - timedelta(days=5)).strftime('%Y-%m-%d') | |
end_date = (datetime.strptime(date, '%Y-%m-%d') + timedelta(days=5)).strftime('%Y-%m-%d') | |
# استفاده از تصاویر Sentinel-2 | |
sentinel = ee.ImageCollection('COPERNICUS/S2_SR') \ | |
.filterDate(start_date, end_date) \ | |
.filterBounds(geometry) \ | |
.sort('CLOUDY_PIXEL_PERCENTAGE') \ | |
.first() | |
# محاسبه LAI (تقریبی بر اساس NDVI) | |
ndvi = sentinel.normalizedDifference(['B8', 'B4']) | |
lai = ndvi.multiply(3.618).exp().subtract(0.118).rename('LAI') | |
# گرفتن آمار LAI برای منطقه | |
lai_stats = lai.reduceRegion( | |
reducer=ee.Reducer.mean().combine(ee.Reducer.min(), None, True).combine(ee.Reducer.max(), None, True), | |
geometry=geometry, | |
scale=10, | |
maxPixels=1e9 | |
).getInfo() | |
return lai, lai_stats | |
def calculate_chl(geometry, date): | |
"""محاسبه شاخص کلروفیل (CHL) برای یک منطقه و تاریخ مشخص""" | |
start_date = (datetime.strptime(date, '%Y-%m-%d') - timedelta(days=5)).strftime('%Y-%m-%d') | |
end_date = (datetime.strptime(date, '%Y-%m-%d') + timedelta(days=5)).strftime('%Y-%m-%d') | |
# استفاده از تصاویر Sentinel-2 | |
sentinel = ee.ImageCollection('COPERNICUS/S2_SR') \ | |
.filterDate(start_date, end_date) \ | |
.filterBounds(geometry) \ | |
.sort('CLOUDY_PIXEL_PERCENTAGE') \ | |
.first() | |
# محاسبه شاخص کلروفیل با استفاده از نسبت باندها | |
# استفاده از باندهای قرمز لبه (Red Edge) و نزدیک مادون قرمز (NIR) | |
re1 = sentinel.select('B5') # Red Edge 1 | |
re2 = sentinel.select('B6') # Red Edge 2 | |
chl = re2.divide(re1).rename('CHL') | |
# گرفتن آمار CHL برای منطقه | |
chl_stats = chl.reduceRegion( | |
reducer=ee.Reducer.mean().combine(ee.Reducer.min(), None, True).combine(ee.Reducer.max(), None, True), | |
geometry=geometry, | |
scale=10, | |
maxPixels=1e9 | |
).getInfo() | |
return chl, chl_stats | |
# توابع ایجاد نمودارها و نقشهها | |
def create_variety_pie_chart(farm_data): | |
"""ایجاد نمودار دایرهای برای نمایش توزیع واریتههای نیشکر""" | |
variety_counts = farm_data['واریته'].value_counts() | |
fig = px.pie( | |
values=variety_counts.values, | |
names=variety_counts.index, | |
title="توزیع واریتههای نیشکر", | |
color_discrete_sequence=px.colors.sequential.Greens_r | |
) | |
fig.update_layout( | |
legend_title="واریته", | |
font=dict(family="Vazirmatn"), | |
hoverlabel=dict(font_family="Vazirmatn") | |
) | |
return fig | |
def create_age_pie_chart(farm_data): | |
"""ایجاد نمودار دایرهای برای نمایش توزیع سن مزارع نیشکر""" | |
age_counts = farm_data['سن'].value_counts() | |
fig = px.pie( | |
values=age_counts.values, | |
names=age_counts.index, | |
title="توزیع سن مزارع نیشکر", | |
color_discrete_sequence=px.colors.sequential.Greens_r | |
) | |
fig.update_layout( | |
legend_title="سن", | |
font=dict(family="Vazirmatn"), | |
hoverlabel=dict(font_family="Vazirmatn") | |
) | |
return fig | |
def create_farm_map(farm_coordinates, selected_index=None, index_data=None): | |
"""ایجاد نقشه تعاملی مزارع با Folium""" | |
# ایجاد نقشه با مرکزیت میانگین مختصات مزارع | |
center_lat = farm_coordinates['lat'].mean() | |
center_lon = farm_coordinates['lon'].mean() | |
m = folium.Map(location=[center_lat, center_lon], zoom_start=10, control_scale=True) | |
# اضافه کردن لایههای مختلف به نقشه | |
folium.TileLayer('openstreetmap').add_to(m) | |
folium.TileLayer('Stamen Terrain').add_to(m) | |
folium.TileLayer('Stamen Toner').add_to(m) | |
folium.TileLayer('Stamen Watercolor').add_to(m) | |
folium.TileLayer('CartoDB positron').add_to(m) | |
# اگر دادههای شاخص برای نمایش ارائه شده باشد | |
if selected_index and index_data: | |
# اضافه کردن لایه حرارتی بر اساس شاخص انتخاب شده | |
heat_data = [] | |
for _, row in farm_coordinates.iterrows(): | |
farm_id = row['farm_id'] | |
if farm_id in index_data: | |
heat_data.append([row['lat'], row['lon'], index_data[farm_id]]) | |
HeatMap(heat_data, radius=15, max_zoom=13, blur=10).add_to(m) | |
# اضافه کردن نشانگرها برای هر مزرعه | |
for _, row in farm_coordinates.iterrows(): | |
popup_text = f""" | |
<div dir="rtl" style="font-family: 'Vazirmatn', sans-serif;"> | |
<b>شناسه مزرعه:</b> {row['farm_id']}<br> | |
<b>واریته:</b> {row['variety']}<br> | |
<b>سن:</b> {row['age']} سال<br> | |
<b>مساحت:</b> {row['area']} هکتار | |
</div> | |
""" | |
folium.Marker( | |
location=[row['lat'], row['lon']], | |
popup=folium.Popup(popup_text, max_width=300), | |
tooltip=f"مزرعه {row['farm_id']}", | |
icon=folium.Icon(color='green', icon='leaf', prefix='fa') | |
).add_to(m) | |
# اضافه کردن کنترل لایهها | |
folium.LayerControl().add_to(m) | |
return m | |
def create_growth_chart(farm_data, farm_id, index_type='NDVI'): | |
"""ایجاد نمودار خطی برای نمایش روند رشد مزرعه بر اساس شاخص انتخابی""" | |
# فیلتر دادهها برای مزرعه مورد نظر | |
farm_history = farm_data[farm_data['farm_id'] == farm_id].sort_values('date') | |
# ایجاد نمودار خطی | |
fig = px.line( | |
farm_history, | |
x='date', | |
y=index_type, | |
title=f"روند شاخص {index_type} برای مزرعه {farm_id}", | |
markers=True | |
) | |
fig.update_layout( | |
xaxis_title="تاریخ", | |
yaxis_title=f"شاخص {index_type}", | |
font=dict(family="Vazirmatn"), | |
hoverlabel=dict(font_family="Vazirmatn") | |
) | |
return fig | |
def create_index_distribution(farm_data, index_type='NDVI'): | |
"""ایجاد نمودار توزیع شاخص انتخابی برای تمام مزارع""" | |
fig = px.histogram( | |
farm_data, | |
x=index_type, | |
title=f"توزیع شاخص {index_type} در مزارع", | |
color_discrete_sequence=['green'] | |
) | |
fig.update_layout( | |
xaxis_title=f"شاخص {index_type}", | |
yaxis_title="تعداد مزارع", | |
font=dict(family="Vazirmatn"), | |
hoverlabel=dict(font_family="Vazirmatn") | |
) | |
return fig | |
def create_correlation_heatmap(farm_data): | |
"""ایجاد نقشه حرارتی همبستگی بین شاخصهای مختلف""" | |
# انتخاب ستونهای شاخص | |
index_columns = ['NDVI', 'NDWI', 'EVI', 'NDMI', 'LAI', 'CHL'] | |
correlation_data = farm_data[index_columns].corr() | |
fig = px.imshow( | |
correlation_data, | |
text_auto=True, | |
color_continuous_scale='Viridis', | |
title="همبستگی بین شاخصهای گیاهی" | |
) | |
fig.update_layout( | |
font=dict(family="Vazirmatn"), | |
hoverlabel=dict(font_family="Vazirmatn") | |
) | |
return fig | |
# توابع مربوط به یادگیری ماشین و تحلیل داده | |
def train_growth_prediction_model(farm_data, target_index='NDVI'): | |
"""آموزش مدل پیشبینی رشد با استفاده از Random Forest""" | |
# آمادهسازی دادهها | |
X = farm_data[['سن', 'رطوبت', 'دما', 'بارش']] # ویژگیهای ورودی | |
y = farm_data[target_index] # هدف پیشبینی | |
# آموزش مدل | |
model = RandomForestRegressor(n_estimators=100, random_state=42) | |
model.fit(X, y) | |
return model | |
def predict_growth(model, new_data): | |
"""پیشبینی رشد با استفاده از مدل آموزش دیده""" | |
predictions = model.predict(new_data) | |
return predictions | |
def detect_stress(farm_data, ndvi_threshold=0.4, ndwi_threshold=0.2): | |
"""شناسایی مزارع با تنش آبی یا بیماری بر اساس آستانههای شاخص""" | |
# شناسایی مزارع با NDVI پایین (تنش یا بیماری) | |
low_ndvi_farms = farm_data[farm_data['NDVI'] < ndvi_threshold] | |
# شناسایی مزارع با NDWI پایین (تنش آبی) | |
low_water_farms = farm_data[farm_data['NDWI'] < ndwi_threshold] | |
return low_ndvi_farms, low_water_farms | |
# توابع مربوط به گزارشگیری | |
def generate_pdf_report(farm_data, report_type, start_date, end_date): | |
"""تولید گزارش PDF بر اساس نوع گزارش و بازه زمانی""" | |
buffer = io.BytesIO() | |
doc = SimpleDocTemplate(buffer, pagesize=letter) | |
elements = [] | |
# افزودن عنوان گزارش | |
title = f"گزارش {report_type} مزارع نیشکر از {start_date} تا {end_date}" | |
elements.append(Paragraph(title, getSampleStyleSheet()['Heading1'])) | |
# افزودن جدول دادهها | |
table_data = [list(farm_data.columns)] | |
for _, row in farm_data.iterrows(): | |
table_data.append(list(row.values)) | |
t = Table(table_data) | |
t.setStyle(TableStyle([ | |
('BACKGROUND', (0, 0), (-1, 0), colors.green), | |
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke), | |
('ALIGN', (0, 0), (-1, -1), 'CENTER'), | |
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), | |
('BOTTOMPADDING', (0, 0), (-1, 0), 12), | |
('BACKGROUND', (0, 1), (-1, -1), colors.beige), | |
('GRID', (0, 0), (-1, -1), 1, colors.black) | |
])) | |
elements.append(t) | |
# ساخت PDF | |
doc.build(elements) | |
buffer.seek(0) | |
return buffer | |
def download_button(object_to_download, download_filename, button_text): | |
"""ایجاد دکمه دانلود برای فایل""" | |
b64 = base64.b64encode(object_to_download.getvalue()).decode() | |
button_uuid = str(uuid.uuid4()).replace('-', '') | |
button_id = re.sub('\d+', '', button_uuid) | |
custom_css = f""" | |
<style> | |
#{button_id} {{ | |
background-color: #4CAF50; | |
color: white; | |
padding: 10px 20px; | |
border: none; | |
border-radius: 4px; | |
font-family: 'Vazirmatn', sans-serif; | |
cursor: pointer; | |
}} | |
#{button_id}:hover {{ | |
background-color: #45a049; | |
}} | |
</style> | |
""" | |
dl_link = custom_css + f'<a download="{download_filename}" id="{button_id}" href="data:application/octet-stream;base64,{b64}">{button_text}</a><br><br>' | |
return st.markdown(dl_link, unsafe_allow_html=True) | |
# بخش اصلی برنامه | |
def main(): | |
# شروع مقدارسازی Google Earth Engine | |
gee_initialized = initialize_gee() | |
# بارگذاری دادههای مزارع | |
farm_data, farm_coordinates = load_farm_data() | |
# منوی افقی | |
st.markdown( | |
""" | |
<div class="horizontal-menu"> | |
<a href="#" class="menu-item active" id="dashboard-btn">داشبورد</a> | |
<a href="#" class="menu-item" id="map-btn">نقشه مزارع</a> | |
<a href="#" class="menu-item" id="input-btn">ورود اطلاعات</a> | |
<a href="#" class="menu-item" id="analysis-btn">تحلیل دادهها</a> | |
<a href="#" class="menu-item" id="report-btn">گزارشگیری</a> | |
<a href="#" class="menu-item" id="settings-btn">تنظیمات</a> | |
</div> | |
""", | |
unsafe_allow_html=True | |
) | |
# باکس سمت راست برای انتخاب گزینهها | |
with st.sidebar: | |
st.markdown("<h2 style='text-align: right;'>سامانه هوشمند پایش مزارع نیشکر</h2>", unsafe_allow_html=True) | |
st_lottie(lottie_farm, height=200) | |
st.markdown("<h3 style='text-align: right;'>تنظیمات</h3>", unsafe_allow_html=True) | |
# انتخاب تاریخ | |
selected_date = st.date_input( | |
"تاریخ مورد نظر", | |
datetime.now().date(), | |
format="YYYY/MM/DD" | |
) | |
# انتخاب مزرعه | |
if farm_coordinates is not None: | |
farm_options = farm_coordinates['farm_id'].unique() | |
selected_farm = st.selectbox("انتخاب مزرعه", farm_options, index=0) | |
# انتخاب شاخص | |
index_options = ['NDVI', 'NDWI', 'EVI', 'NDMI', 'LAI', 'CHL'] | |
selected_index = st.selectbox("انتخاب شاخص", index_options, index=0) | |
# دکمه اعمال تغییرات | |
apply_btn = st.button("اعمال تغییرات", type="primary") | |
# نمایش داشبورد (صفحه اصلی) | |
tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs(["داشبورد", "نقشه مزارع", "ورود اطلاعات", "تحلیل دادهها", "گزارشگیری", "تنظیمات"]) | |
with tab1: | |
st.markdown("<h1 style='text-align: right;'>داشبورد مدیریت مزارع نیشکر</h1>", unsafe_allow_html=True) | |
# کارتهای اطلاعات کلیدی | |
col1, col2, col3, col4 = st.columns(4) | |
with col1: | |
st.markdown( | |
""" | |
<div class="metric-card green-gradient"> | |
<div class="metric-icon">🌱</div> | |
<div class="metric-content"> | |
<div class="metric-title">تعداد کل مزارع</div> | |
<div class="metric-value">125</div> | |
</div> | |
</div> | |
""", | |
unsafe_allow_html=True | |
) | |
with col2: | |
st.markdown( | |
""" | |
<div class="metric-card green-gradient"> | |
<div class="metric-icon">🚜</div> | |
<div class="metric-content">""" | |
) |