|
|
|
|
|
|
|
""" |
|
وحدة الخريطة التفاعلية مع عرض التضاريس ثلاثي الأبعاد |
|
تتيح هذه الوحدة عرض مواقع المشاريع على خريطة تفاعلية مع إمكانية رؤية التضاريس بشكل ثلاثي الأبعاد |
|
""" |
|
|
|
import os |
|
import sys |
|
import streamlit as st |
|
import pandas as pd |
|
import numpy as np |
|
import pydeck as pdk |
|
import folium |
|
from folium.plugins import MarkerCluster, HeatMap, MeasureControl |
|
from streamlit_folium import folium_static |
|
import requests |
|
import json |
|
import random |
|
from typing import List, Dict, Any, Tuple, Optional |
|
import tempfile |
|
import base64 |
|
from PIL import Image |
|
from io import BytesIO |
|
|
|
|
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) |
|
|
|
|
|
from utils.components.header import render_header |
|
from utils.components.credits import render_credits |
|
from utils.helpers import format_number, format_currency, styled_button |
|
|
|
|
|
class InteractiveMap: |
|
"""فئة الخريطة التفاعلية مع عرض التضاريس ثلاثي الأبعاد""" |
|
|
|
def __init__(self): |
|
"""تهيئة وحدة الخريطة التفاعلية""" |
|
|
|
self.data_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../data/maps")) |
|
os.makedirs(self.data_dir, exist_ok=True) |
|
|
|
|
|
self.mapbox_token = os.environ.get("MAPBOX_TOKEN", "") |
|
self.opentopodata_api = "https://api.opentopodata.org/v1/srtm30m" |
|
|
|
|
|
if 'project_locations' not in st.session_state: |
|
st.session_state.project_locations = [] |
|
|
|
if 'selected_location' not in st.session_state: |
|
st.session_state.selected_location = None |
|
|
|
if 'terrain_data' not in st.session_state: |
|
st.session_state.terrain_data = None |
|
|
|
|
|
self._initialize_sample_projects() |
|
|
|
def render(self): |
|
"""عرض واجهة وحدة الخريطة التفاعلية""" |
|
|
|
render_header("خريطة مواقع المشاريع التفاعلية") |
|
|
|
|
|
tabs = st.tabs([ |
|
"الخريطة التفاعلية", |
|
"عرض التضاريس ثلاثي الأبعاد", |
|
"تحليل المواقع", |
|
"إدارة المواقع" |
|
]) |
|
|
|
|
|
with tabs[0]: |
|
self._render_interactive_map() |
|
|
|
|
|
with tabs[1]: |
|
self._render_3d_terrain() |
|
|
|
|
|
with tabs[2]: |
|
self._render_location_analysis() |
|
|
|
|
|
with tabs[3]: |
|
self._render_location_management() |
|
|
|
|
|
render_credits() |
|
|
|
def _render_interactive_map(self): |
|
"""عرض الخريطة التفاعلية""" |
|
st.markdown(""" |
|
<div class='custom-box info-box'> |
|
<h3>🗺️ الخريطة التفاعلية لمواقع المشاريع</h3> |
|
<p>خريطة تفاعلية تعرض مواقع جميع المشاريع مع إمكانية التكبير والتصغير والتحرك.</p> |
|
<p>يمكنك النقر على أي موقع للحصول على تفاصيل المشروع.</p> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
search_query = st.text_input("البحث عن موقع أو مشروع", key="map_search") |
|
|
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
|
|
with col1: |
|
map_style = st.selectbox( |
|
"نمط الخريطة", |
|
options=["OpenStreetMap", "Stamen Terrain", "Stamen Toner", "CartoDB Positron"], |
|
key="map_style" |
|
) |
|
|
|
with col2: |
|
cluster_markers = st.checkbox("تجميع المواقع القريبة", value=True, key="cluster_markers") |
|
|
|
with col3: |
|
show_heatmap = st.checkbox("عرض خريطة حرارية للمواقع", value=False, key="show_heatmap") |
|
|
|
with col4: |
|
show_measurements = st.checkbox("أدوات القياس", value=False, key="show_measurements") |
|
|
|
|
|
if len(st.session_state.project_locations) > 0: |
|
|
|
locations = [] |
|
|
|
|
|
filtered_projects = st.session_state.project_locations |
|
if search_query: |
|
filtered_projects = [ |
|
p for p in filtered_projects |
|
if search_query.lower() in p.get("name", "").lower() or |
|
search_query.lower() in p.get("description", "").lower() or |
|
search_query.lower() in p.get("city", "").lower() |
|
] |
|
|
|
|
|
if search_query: |
|
st.markdown(f"عدد النتائج: {len(filtered_projects)}") |
|
|
|
|
|
heat_data = [] |
|
for project in filtered_projects: |
|
locations.append({ |
|
"lat": project.get("latitude"), |
|
"lon": project.get("longitude"), |
|
"name": project.get("name"), |
|
"description": project.get("description"), |
|
"city": project.get("city"), |
|
"status": project.get("status"), |
|
"project_id": project.get("project_id") |
|
}) |
|
heat_data.append([project.get("latitude"), project.get("longitude"), 1]) |
|
|
|
|
|
if filtered_projects: |
|
center_lat = sum(p.get("latitude", 0) for p in filtered_projects) / len(filtered_projects) |
|
center_lon = sum(p.get("longitude", 0) for p in filtered_projects) / len(filtered_projects) |
|
zoom_level = 6 |
|
else: |
|
|
|
center_lat = 24.7136 |
|
center_lon = 46.6753 |
|
zoom_level = 5 |
|
|
|
|
|
attribution = None |
|
if map_style == "OpenStreetMap": |
|
attribution = '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' |
|
elif map_style.startswith("Stamen"): |
|
attribution = 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://www.openstreetmap.org/copyright">ODbL</a>.' |
|
elif map_style == "CartoDB Positron": |
|
attribution = '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a href="https://cartodb.com/attributions">CartoDB</a>' |
|
|
|
|
|
m = folium.Map( |
|
location=[center_lat, center_lon], |
|
zoom_start=zoom_level, |
|
tiles=map_style, |
|
attr=attribution |
|
) |
|
|
|
|
|
if show_measurements: |
|
MeasureControl(position='topright', primary_length_unit='kilometers').add_to(m) |
|
|
|
|
|
if cluster_markers: |
|
|
|
marker_cluster = MarkerCluster(name="تجميع المشاريع").add_to(m) |
|
|
|
|
|
for location in locations: |
|
|
|
popup_html = f""" |
|
<div style='direction: rtl; text-align: right;'> |
|
<h4>{location['name']}</h4> |
|
<p><strong>الوصف:</strong> {location['description']}</p> |
|
<p><strong>المدينة:</strong> {location['city']}</p> |
|
<p><strong>الحالة:</strong> {location['status']}</p> |
|
<p><strong>الإحداثيات:</strong> {location['lat']:.6f}, {location['lon']:.6f}</p> |
|
<button onclick="window.open('/project/{location['project_id']}', '_self')">عرض تفاصيل المشروع</button> |
|
</div> |
|
""" |
|
|
|
|
|
icon_color = 'green' |
|
if location['status'] == 'قيد التنفيذ': |
|
icon_color = 'orange' |
|
elif location['status'] == 'متوقف': |
|
icon_color = 'red' |
|
elif location['status'] == 'مكتمل': |
|
icon_color = 'blue' |
|
|
|
|
|
folium.Marker( |
|
location=[location['lat'], location['lon']], |
|
popup=folium.Popup(popup_html, max_width=300), |
|
tooltip=location['name'], |
|
icon=folium.Icon(color=icon_color, icon='info-sign') |
|
).add_to(marker_cluster) |
|
else: |
|
|
|
for location in locations: |
|
|
|
popup_html = f""" |
|
<div style='direction: rtl; text-align: right;'> |
|
<h4>{location['name']}</h4> |
|
<p><strong>الوصف:</strong> {location['description']}</p> |
|
<p><strong>المدينة:</strong> {location['city']}</p> |
|
<p><strong>الحالة:</strong> {location['status']}</p> |
|
<p><strong>الإحداثيات:</strong> {location['lat']:.6f}, {location['lon']:.6f}</p> |
|
<button onclick="window.open('/project/{location['project_id']}', '_self')">عرض تفاصيل المشروع</button> |
|
</div> |
|
""" |
|
|
|
|
|
icon_color = 'green' |
|
if location['status'] == 'قيد التنفيذ': |
|
icon_color = 'orange' |
|
elif location['status'] == 'متوقف': |
|
icon_color = 'red' |
|
elif location['status'] == 'مكتمل': |
|
icon_color = 'blue' |
|
|
|
|
|
folium.Marker( |
|
location=[location['lat'], location['lon']], |
|
popup=folium.Popup(popup_html, max_width=300), |
|
tooltip=location['name'], |
|
icon=folium.Icon(color=icon_color, icon='info-sign') |
|
).add_to(m) |
|
|
|
|
|
if show_heatmap and heat_data: |
|
HeatMap(heat_data, radius=15).add_to(m) |
|
|
|
|
|
folium.TileLayer('OpenStreetMap').add_to(m) |
|
folium.TileLayer('Stamen Terrain').add_to(m) |
|
folium.TileLayer('Stamen Toner').add_to(m) |
|
folium.TileLayer('CartoDB positron').add_to(m) |
|
folium.TileLayer('CartoDB dark_matter').add_to(m) |
|
|
|
|
|
folium.LayerControl().add_to(m) |
|
|
|
|
|
st_map = folium_static(m, width=1000, height=600) |
|
|
|
|
|
|
|
|
|
|
|
st.markdown("### قائمة المشاريع على الخريطة") |
|
|
|
projects_df = pd.DataFrame(filtered_projects) |
|
|
|
|
|
renamed_columns = { |
|
"name": "اسم المشروع", |
|
"city": "المدينة", |
|
"status": "الحالة", |
|
"description": "الوصف", |
|
"project_id": "معرف المشروع", |
|
"latitude": "خط العرض", |
|
"longitude": "خط الطول" |
|
} |
|
|
|
|
|
display_columns = ["name", "city", "status", "project_id"] |
|
|
|
|
|
display_df = projects_df[display_columns].rename(columns=renamed_columns) |
|
|
|
|
|
st.dataframe(display_df, width=1000, height=400) |
|
|
|
|
|
selected_project_id = st.selectbox( |
|
"اختر مشروعًا لعرض التضاريس ثلاثية الأبعاد", |
|
options=projects_df["project_id"].tolist(), |
|
format_func=lambda x: next((p["name"] for p in filtered_projects if p["project_id"] == x), x), |
|
key="select_project_for_terrain" |
|
) |
|
|
|
|
|
if styled_button("عرض التضاريس", key="show_terrain_btn", type="primary", icon="🏔️"): |
|
|
|
selected_project = next((p for p in filtered_projects if p["project_id"] == selected_project_id), None) |
|
|
|
if selected_project: |
|
|
|
st.session_state.selected_location = { |
|
"latitude": selected_project["latitude"], |
|
"longitude": selected_project["longitude"], |
|
"name": selected_project["name"], |
|
"project_id": selected_project["project_id"] |
|
} |
|
|
|
|
|
try: |
|
terrain_data = self._fetch_terrain_data( |
|
selected_project["latitude"], |
|
selected_project["longitude"] |
|
) |
|
|
|
|
|
st.session_state.terrain_data = terrain_data |
|
|
|
|
|
st.rerun() |
|
except Exception as e: |
|
st.error(f"حدث خطأ أثناء جلب بيانات التضاريس: {str(e)}") |
|
else: |
|
st.warning("لا توجد مواقع مشاريع متاحة. يرجى إضافة مواقع من تبويب 'إدارة المواقع'.") |
|
|
|
def _render_3d_terrain(self): |
|
"""عرض التضاريس ثلاثي الأبعاد""" |
|
st.markdown(""" |
|
<div class='custom-box info-box'> |
|
<h3>🏔️ عرض التضاريس ثلاثي الأبعاد</h3> |
|
<p>عرض تفاعلي ثلاثي الأبعاد لتضاريس موقع المشروع المحدد.</p> |
|
<p>يمكنك تدوير وتكبير العرض للحصول على رؤية أفضل للموقع.</p> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
if st.session_state.selected_location is None: |
|
st.info("يرجى اختيار موقع من تبويب 'الخريطة التفاعلية' أولاً.") |
|
|
|
|
|
st.markdown("### إدخال الإحداثيات يدوياً") |
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
manual_lat = st.number_input("خط العرض", value=24.7136, step=0.0001, format="%.6f", key="manual_lat") |
|
|
|
with col2: |
|
manual_lon = st.number_input("خط الطول", value=46.6753, step=0.0001, format="%.6f", key="manual_lon") |
|
|
|
if styled_button("عرض التضاريس", key="manual_terrain_btn", type="primary", icon="🏔️"): |
|
try: |
|
|
|
terrain_data = self._fetch_terrain_data(manual_lat, manual_lon) |
|
|
|
|
|
st.session_state.terrain_data = terrain_data |
|
st.session_state.selected_location = { |
|
"latitude": manual_lat, |
|
"longitude": manual_lon, |
|
"name": f"الموقع المخصص ({manual_lat:.4f}, {manual_lon:.4f})", |
|
"project_id": "custom" |
|
} |
|
|
|
|
|
st.rerun() |
|
except Exception as e: |
|
st.error(f"حدث خطأ أثناء جلب بيانات التضاريس: {str(e)}") |
|
|
|
|
|
st.markdown("### حدد موقعًا على الخريطة") |
|
m = folium.Map( |
|
location=[24.7136, 46.6753], |
|
zoom_start=6, |
|
attr='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' |
|
) |
|
folium_static(m, width=1000, height=500) |
|
|
|
st.info("ملاحظة: لا يمكن تحديد موقع على الخريطة مباشرة في هذا الإصدار. يرجى إدخال الإحداثيات يدوياً أو اختيار مشروع من القائمة.") |
|
|
|
return |
|
|
|
|
|
st.markdown(f"### تضاريس موقع: {st.session_state.selected_location['name']}") |
|
st.markdown(f"الإحداثيات: {st.session_state.selected_location['latitude']:.6f}, {st.session_state.selected_location['longitude']:.6f}") |
|
|
|
|
|
if st.session_state.terrain_data is None: |
|
st.warning("لا توجد بيانات تضاريس متاحة لهذا الموقع. جاري جلب البيانات...") |
|
|
|
try: |
|
|
|
terrain_data = self._fetch_terrain_data( |
|
st.session_state.selected_location["latitude"], |
|
st.session_state.selected_location["longitude"] |
|
) |
|
|
|
|
|
st.session_state.terrain_data = terrain_data |
|
|
|
|
|
st.rerun() |
|
except Exception as e: |
|
st.error(f"حدث خطأ أثناء جلب بيانات التضاريس: {str(e)}") |
|
return |
|
|
|
|
|
st.markdown("### خريطة الموقع") |
|
|
|
|
|
mini_map = folium.Map( |
|
location=[st.session_state.selected_location["latitude"], st.session_state.selected_location["longitude"]], |
|
zoom_start=10, |
|
attr='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' |
|
) |
|
folium.Marker(location=[st.session_state.selected_location["latitude"], st.session_state.selected_location["longitude"]], tooltip="الموقع المحدد").add_to(mini_map) |
|
folium_static(mini_map, width=700, height=300) |
|
|
|
|
|
st.markdown("### نموذج التضاريس ثلاثي الأبعاد") |
|
|
|
|
|
df = pd.DataFrame(st.session_state.terrain_data) |
|
|
|
|
|
color_schemes = { |
|
"Viridis": "Viridis", |
|
"أخضر إلى بني": "Greens", |
|
"أزرق إلى أحمر": "RdBu", |
|
"أرجواني إلى أخضر": "PuGn", |
|
"نظام الارتفاعات": "Terrain" |
|
} |
|
|
|
color_scheme = st.selectbox( |
|
"نظام الألوان", |
|
options=list(color_schemes.keys()), |
|
index=4, |
|
key="3d_color_scheme" |
|
) |
|
|
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
with col1: |
|
exaggeration = st.slider("تضخيم الارتفاع", 1, 50, 15, key="terrain_exaggeration") |
|
|
|
with col2: |
|
radius = st.slider("نطاق العرض (كم)", 1, 20, 5, key="terrain_radius") |
|
|
|
with col3: |
|
resolution = st.slider("دقة العرض", 10, 100, 50, key="terrain_resolution") |
|
|
|
if not df.empty and len(df) > 1: |
|
|
|
current_lat = st.session_state.selected_location["latitude"] |
|
current_lon = st.session_state.selected_location["longitude"] |
|
current_radius = radius |
|
|
|
|
|
if styled_button("تحديث النطاق", key="update_radius_btn"): |
|
try: |
|
|
|
terrain_data = self._fetch_terrain_data( |
|
current_lat, |
|
current_lon, |
|
radius_km=current_radius |
|
) |
|
|
|
|
|
st.session_state.terrain_data = terrain_data |
|
|
|
|
|
st.rerun() |
|
except Exception as e: |
|
st.error(f"حدث خطأ أثناء تحديث بيانات التضاريس: {str(e)}") |
|
|
|
|
|
x = df["longitude"].values |
|
y = df["latitude"].values |
|
z = df["elevation"].values * exaggeration |
|
|
|
|
|
normalized_elevation = (z - z.min()) / (z.max() - z.min() if z.max() != z.min() else 1) |
|
|
|
|
|
cmap = self._get_color_map(color_schemes[color_scheme]) |
|
|
|
|
|
df["color"] = [ |
|
cmap(ne) if ne <= 1.0 else cmap(1.0) |
|
for ne in normalized_elevation |
|
] |
|
|
|
|
|
view_state = pdk.ViewState( |
|
latitude=current_lat, |
|
longitude=current_lon, |
|
zoom=10, |
|
pitch=45, |
|
bearing=0 |
|
) |
|
|
|
|
|
terrain_layer = pdk.Layer( |
|
"ColumnLayer", |
|
data=df, |
|
get_position=["longitude", "latitude"], |
|
get_elevation="elevation * " + str(exaggeration), |
|
get_fill_color="color", |
|
get_radius=resolution, |
|
pickable=True, |
|
auto_highlight=True, |
|
elevation_scale=1, |
|
elevation_range=[0, 1000], |
|
coverage=1, |
|
) |
|
|
|
|
|
marker_df = pd.DataFrame({ |
|
"latitude": [current_lat], |
|
"longitude": [current_lon], |
|
"size": [400] |
|
}) |
|
|
|
marker_layer = pdk.Layer( |
|
"ScatterplotLayer", |
|
data=marker_df, |
|
get_position=["longitude", "latitude"], |
|
get_radius="size", |
|
get_fill_color=[255, 0, 0, 200], |
|
pickable=True, |
|
) |
|
|
|
|
|
r = pdk.Deck( |
|
layers=[terrain_layer, marker_layer], |
|
initial_view_state=view_state, |
|
map_style="mapbox://styles/mapbox/satellite-v9", |
|
tooltip={ |
|
"html": "<b>ارتفاع:</b> {elevation} متر<br/><b>إحداثيات:</b> {latitude:.6f}, {longitude:.6f}", |
|
"style": { |
|
"backgroundColor": "steelblue", |
|
"color": "white", |
|
"direction": "rtl", |
|
"text-align": "right" |
|
} |
|
} |
|
) |
|
|
|
|
|
st.pydeck_chart(r) |
|
|
|
|
|
st.markdown("### معلومات الارتفاع") |
|
|
|
|
|
min_elevation = df["elevation"].min() |
|
max_elevation = df["elevation"].max() |
|
avg_elevation = df["elevation"].mean() |
|
|
|
|
|
stat_col1, stat_col2, stat_col3 = st.columns(3) |
|
|
|
with stat_col1: |
|
st.metric("أدنى ارتفاع", f"{min_elevation:.1f} متر") |
|
|
|
with stat_col2: |
|
st.metric("متوسط الارتفاع", f"{avg_elevation:.1f} متر") |
|
|
|
with stat_col3: |
|
st.metric("أعلى ارتفاع", f"{max_elevation:.1f} متر") |
|
|
|
|
|
if styled_button("تصدير بيانات التضاريس", key="export_terrain_btn", type="secondary", icon="📊"): |
|
|
|
csv = df.to_csv(index=False) |
|
|
|
|
|
b64 = base64.b64encode(csv.encode()).decode() |
|
href = f'<a href="data:file/csv;base64,{b64}" download="terrain_data_{st.session_state.selected_location["project_id"]}.csv" class="btn">تنزيل البيانات (CSV)</a>' |
|
st.markdown(href, unsafe_allow_html=True) |
|
else: |
|
st.error("لا توجد بيانات كافية لعرض نموذج التضاريس. حاول اختيار موقع آخر أو زيادة النطاق.") |
|
|
|
def _render_location_analysis(self): |
|
"""عرض تحليل المواقع""" |
|
st.markdown(""" |
|
<div class='custom-box info-box'> |
|
<h3>📊 تحليل موقع المشروع</h3> |
|
<p>تحليل متقدم لموقع المشروع وتضاريسه والظروف المحيطة.</p> |
|
<p>يمكنك تحليل الارتفاعات والمسافات وقياس التكاليف المرتبطة بالموقع.</p> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
if len(st.session_state.project_locations) == 0: |
|
st.warning("لا توجد مواقع مشاريع متاحة للتحليل. يرجى إضافة مواقع من تبويب 'إدارة المواقع'.") |
|
return |
|
|
|
|
|
analysis_type = st.radio( |
|
"نوع التحليل", |
|
options=["تحليل موقع واحد", "مقارنة موقعين"], |
|
key="location_analysis_type", |
|
horizontal=True |
|
) |
|
|
|
|
|
projects_df = pd.DataFrame(st.session_state.project_locations) |
|
|
|
if analysis_type == "تحليل موقع واحد": |
|
|
|
selected_project_id = st.selectbox( |
|
"اختر موقع المشروع للتحليل", |
|
options=projects_df["project_id"].tolist(), |
|
format_func=lambda x: next((p["name"] for p in st.session_state.project_locations if p["project_id"] == x), x), |
|
key="analysis_project" |
|
) |
|
|
|
|
|
selected_project = next((p for p in st.session_state.project_locations if p["project_id"] == selected_project_id), None) |
|
|
|
if selected_project: |
|
|
|
st.markdown(f"### تحليل موقع: {selected_project['name']}") |
|
|
|
|
|
st.markdown("#### موقع المشروع") |
|
|
|
|
|
m2 = folium.Map( |
|
location=[selected_project["latitude"], selected_project["longitude"]], |
|
zoom_start=10, |
|
attr='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' |
|
) |
|
folium.Marker(location=[selected_project["latitude"], selected_project["longitude"]], tooltip=selected_project["name"]).add_to(m2) |
|
|
|
|
|
analysis_radius = st.slider("نطاق التحليل (كم)", 1, 50, 10, key="analysis_radius") |
|
folium.Circle( |
|
location=[selected_project["latitude"], selected_project["longitude"]], |
|
radius=analysis_radius * 1000, |
|
color="red", |
|
fill=True, |
|
fill_opacity=0.2 |
|
).add_to(m2) |
|
|
|
folium_static(m2, width=700, height=400) |
|
|
|
|
|
st.markdown("#### عوامل الموقع") |
|
|
|
|
|
|
|
|
|
factors = { |
|
"قرب المدينة": random.uniform(0.4, 1.0), |
|
"توفر المياه": random.uniform(0.3, 0.9), |
|
"سهولة الوصول": random.uniform(0.5, 1.0), |
|
"الظروف الجوية": random.uniform(0.6, 1.0), |
|
"التضاريس": random.uniform(0.3, 0.8), |
|
"توفر العمالة": random.uniform(0.5, 0.9), |
|
"البنية التحتية": random.uniform(0.4, 0.9), |
|
"المخاطر البيئية": random.uniform(0.3, 0.7) |
|
} |
|
|
|
|
|
factors_df = pd.DataFrame({ |
|
"العامل": list(factors.keys()), |
|
"التقييم": list(factors.values()) |
|
}) |
|
|
|
|
|
factors_df = factors_df.sort_values(by="التقييم", ascending=False) |
|
|
|
|
|
st.bar_chart(factors_df.set_index("العامل")) |
|
|
|
|
|
overall_score = sum(factors.values()) / len(factors) |
|
|
|
|
|
st.markdown(f"#### التقييم الإجمالي للموقع: {overall_score:.2f}/1.0") |
|
|
|
|
|
st.progress(overall_score) |
|
|
|
|
|
if overall_score >= 0.8: |
|
rating = "ممتاز" |
|
color = "green" |
|
elif overall_score >= 0.6: |
|
rating = "جيد" |
|
color = "blue" |
|
elif overall_score >= 0.4: |
|
rating = "مقبول" |
|
color = "orange" |
|
else: |
|
rating = "ضعيف" |
|
color = "red" |
|
|
|
st.markdown(f"<h4 style='color: {color};'>تصنيف الموقع: {rating}</h4>", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("#### توصيات الموقع") |
|
|
|
recommendations = [ |
|
"تحسين طرق الوصول للموقع لزيادة كفاءة نقل المواد والمعدات.", |
|
"إجراء دراسة جيوتقنية مفصلة للتضاريس قبل البدء في أعمال الحفر.", |
|
"التأكد من توفر مصادر المياه الكافية لاحتياجات المشروع.", |
|
"التنسيق مع السلطات المحلية لتسهيل توصيل الخدمات للموقع.", |
|
"وضع خطة للتعامل مع الظروف الجوية المتقلبة في المنطقة." |
|
] |
|
|
|
for rec in recommendations: |
|
st.markdown(f"- {rec}") |
|
|
|
|
|
st.markdown("#### المرافق القريبة (تمثيل افتراضي)") |
|
|
|
|
|
nearby_facilities = { |
|
"مستشفى": random.uniform(5, 30), |
|
"مدرسة": random.uniform(2, 15), |
|
"محطة وقود": random.uniform(2, 20), |
|
"مركز تسوق": random.uniform(3, 25), |
|
"مكتب حكومي": random.uniform(7, 35), |
|
"مطار": random.uniform(15, 100), |
|
"ميناء": random.uniform(20, 150) |
|
} |
|
|
|
|
|
facilities_df = pd.DataFrame({ |
|
"المرفق": list(nearby_facilities.keys()), |
|
"المسافة (كم)": list(nearby_facilities.values()) |
|
}) |
|
|
|
|
|
facilities_df = facilities_df.sort_values(by="المسافة (كم)") |
|
|
|
|
|
st.dataframe(facilities_df, width=700) |
|
|
|
|
|
st.markdown("#### تقديرات تكلفة الموقع") |
|
|
|
|
|
cost_items = { |
|
"تكلفة تسوية الأرض": random.uniform(50000, 200000), |
|
"تكلفة البنية التحتية": random.uniform(100000, 500000), |
|
"تكلفة النقل الإضافية": random.uniform(30000, 150000), |
|
"تكلفة الحماية من المخاطر البيئية": random.uniform(20000, 100000), |
|
"تكلفة توصيل الخدمات": random.uniform(40000, 200000) |
|
} |
|
|
|
|
|
st.markdown("##### بنود التكلفة") |
|
|
|
for item, cost in cost_items.items(): |
|
st.markdown(f"- {item}: {format_currency(cost)} ريال") |
|
|
|
|
|
total_cost = sum(cost_items.values()) |
|
st.markdown(f"##### إجمالي تكلفة الموقع: {format_currency(total_cost)} ريال") |
|
|
|
|
|
st.markdown("#### خيارات تحسين الموقع") |
|
|
|
improvement_options = [ |
|
{"name": "تسوية الأرض وإزالة العوائق", "cost": 75000, "impact": 0.15}, |
|
{"name": "تحسين طرق الوصول", "cost": 120000, "impact": 0.2}, |
|
{"name": "بناء نظام صرف للمياه", "cost": 90000, "impact": 0.18}, |
|
{"name": "تعزيز البنية التحتية", "cost": 180000, "impact": 0.25}, |
|
{"name": "نظام حماية من العوامل الجوية", "cost": 60000, "impact": 0.12} |
|
] |
|
|
|
|
|
st.markdown("اختر خيارات التحسين لتقييم التأثير والتكلفة:") |
|
|
|
selected_improvements = [] |
|
for i, option in enumerate(improvement_options): |
|
if st.checkbox(f"{option['name']} - {format_currency(option['cost'])} ريال", key=f"imp_{i}"): |
|
selected_improvements.append(option) |
|
|
|
if selected_improvements: |
|
|
|
total_impact = sum(imp["impact"] for imp in selected_improvements) |
|
total_improvement_cost = sum(imp["cost"] for imp in selected_improvements) |
|
|
|
|
|
st.markdown(f"##### تحسين التقييم المتوقع: +{total_impact:.2f}") |
|
new_score = min(1.0, overall_score + total_impact) |
|
st.markdown(f"##### التقييم الجديد المتوقع: {new_score:.2f}/1.0") |
|
st.progress(new_score) |
|
|
|
|
|
if new_score >= 0.8: |
|
new_rating = "ممتاز" |
|
new_color = "green" |
|
elif new_score >= 0.6: |
|
new_rating = "جيد" |
|
new_color = "blue" |
|
elif new_score >= 0.4: |
|
new_rating = "مقبول" |
|
new_color = "orange" |
|
else: |
|
new_rating = "ضعيف" |
|
new_color = "red" |
|
|
|
st.markdown(f"<h5 style='color: {new_color};'>التصنيف الجديد المتوقع: {new_rating}</h5>", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown(f"##### تكلفة التحسينات: {format_currency(total_improvement_cost)} ريال") |
|
else: |
|
st.error("لم يتم العثور على المشروع المحدد.") |
|
else: |
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
project_id_1 = st.selectbox( |
|
"الموقع الأول", |
|
options=projects_df["project_id"].tolist(), |
|
format_func=lambda x: next((p["name"] for p in st.session_state.project_locations if p["project_id"] == x), x), |
|
key="compare_project_1" |
|
) |
|
|
|
with col2: |
|
|
|
remaining_options = [pid for pid in projects_df["project_id"].tolist() if pid != project_id_1] |
|
|
|
if remaining_options: |
|
project_id_2 = st.selectbox( |
|
"الموقع الثاني", |
|
options=remaining_options, |
|
format_func=lambda x: next((p["name"] for p in st.session_state.project_locations if p["project_id"] == x), x), |
|
key="compare_project_2" |
|
) |
|
else: |
|
st.warning("يجب أن يكون هناك موقعان على الأقل للمقارنة.") |
|
return |
|
|
|
|
|
project_1 = next((p for p in st.session_state.project_locations if p["project_id"] == project_id_1), None) |
|
project_2 = next((p for p in st.session_state.project_locations if p["project_id"] == project_id_2), None) |
|
|
|
if project_1 and project_2: |
|
|
|
st.markdown(f"### مقارنة بين موقعي {project_1['name']} و {project_2['name']}") |
|
|
|
|
|
st.markdown("#### الموقعان على الخريطة") |
|
|
|
|
|
center_lat = (project_1["latitude"] + project_2["latitude"]) / 2 |
|
center_lon = (project_1["longitude"] + project_2["longitude"]) / 2 |
|
|
|
|
|
distance = self._calculate_distance( |
|
project_1["latitude"], project_1["longitude"], |
|
project_2["latitude"], project_2["longitude"] |
|
) |
|
|
|
|
|
zoom_level = 12 if distance < 10 else (10 if distance < 50 else 8) |
|
|
|
|
|
compare_map = folium.Map( |
|
location=[center_lat, center_lon], |
|
zoom_start=zoom_level, |
|
attr='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' |
|
) |
|
|
|
|
|
folium.Marker( |
|
location=[project_1["latitude"], project_1["longitude"]], |
|
tooltip=project_1["name"], |
|
icon=folium.Icon(color="blue", icon="info-sign") |
|
).add_to(compare_map) |
|
|
|
folium.Marker( |
|
location=[project_2["latitude"], project_2["longitude"]], |
|
tooltip=project_2["name"], |
|
icon=folium.Icon(color="red", icon="info-sign") |
|
).add_to(compare_map) |
|
|
|
|
|
folium.PolyLine( |
|
locations=[ |
|
[project_1["latitude"], project_1["longitude"]], |
|
[project_2["latitude"], project_2["longitude"]] |
|
], |
|
color="green", |
|
weight=3, |
|
opacity=0.7, |
|
tooltip=f"المسافة: {distance:.2f} كم" |
|
).add_to(compare_map) |
|
|
|
|
|
folium_static(compare_map, width=800, height=500) |
|
|
|
|
|
st.markdown(f"#### المسافة بين الموقعين: {distance:.2f} كيلومتر") |
|
|
|
|
|
st.markdown("#### مقارنة المعلومات الأساسية") |
|
|
|
|
|
comparison_data = { |
|
"المعلومات": ["المدينة", "الحالة", "خط العرض", "خط الطول", "الوصف"], |
|
project_1["name"]: [ |
|
project_1.get("city", ""), |
|
project_1.get("status", ""), |
|
f"{project_1['latitude']:.6f}", |
|
f"{project_1['longitude']:.6f}", |
|
project_1.get("description", "") |
|
], |
|
project_2["name"]: [ |
|
project_2.get("city", ""), |
|
project_2.get("status", ""), |
|
f"{project_2['latitude']:.6f}", |
|
f"{project_2['longitude']:.6f}", |
|
project_2.get("description", "") |
|
] |
|
} |
|
|
|
comparison_df = pd.DataFrame(comparison_data) |
|
st.dataframe(comparison_df, width=800) |
|
|
|
|
|
st.markdown("#### مقارنة العوامل") |
|
|
|
|
|
factors_comparison = { |
|
"العامل": ["قرب المدينة", "توفر المياه", "سهولة الوصول", "الظروف الجوية", "التضاريس", "توفر العمالة", "البنية التحتية", "المخاطر البيئية"], |
|
project_1["name"]: [random.uniform(0.4, 1.0) for _ in range(8)], |
|
project_2["name"]: [random.uniform(0.4, 1.0) for _ in range(8)] |
|
} |
|
|
|
|
|
factors_df = pd.DataFrame(factors_comparison) |
|
|
|
|
|
st.bar_chart(factors_df.set_index("العامل")) |
|
|
|
|
|
project_1_score = sum(factors_comparison[project_1["name"]]) / len(factors_comparison[project_1["name"]]) |
|
project_2_score = sum(factors_comparison[project_2["name"]]) / len(factors_comparison[project_2["name"]]) |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
st.markdown(f"##### تقييم {project_1['name']}: {project_1_score:.2f}/1.0") |
|
st.progress(project_1_score) |
|
|
|
with col2: |
|
st.markdown(f"##### تقييم {project_2['name']}: {project_2_score:.2f}/1.0") |
|
st.progress(project_2_score) |
|
|
|
|
|
preferred_site = project_1["name"] if project_1_score > project_2_score else project_2["name"] |
|
score_diff = abs(project_1_score - project_2_score) |
|
|
|
if score_diff < 0.1: |
|
recommendation = "الموقعان متقاربان في التقييم ويمكن اعتبارهما متكافئين." |
|
color = "blue" |
|
else: |
|
recommendation = f"الموقع الأفضل هو: {preferred_site}" |
|
color = "green" |
|
|
|
st.markdown(f"<h4 style='color: {color};'>{recommendation}</h4>", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("#### مقارنة تقديرات التكلفة") |
|
|
|
|
|
cost_items = ["تسوية الأرض", "البنية التحتية", "النقل", "الحماية من المخاطر", "توصيل الخدمات"] |
|
|
|
site_1_costs = [random.uniform(50000, 200000) for _ in range(len(cost_items))] |
|
site_2_costs = [random.uniform(50000, 200000) for _ in range(len(cost_items))] |
|
|
|
|
|
cost_df = pd.DataFrame({ |
|
"بند التكلفة": cost_items, |
|
f"{project_1['name']} (ريال)": site_1_costs, |
|
f"{project_2['name']} (ريال)": site_2_costs |
|
}) |
|
|
|
|
|
st.dataframe(cost_df, width=800) |
|
|
|
|
|
total_cost_1 = sum(site_1_costs) |
|
total_cost_2 = sum(site_2_costs) |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
st.metric( |
|
f"إجمالي تكلفة {project_1['name']}", |
|
f"{format_currency(total_cost_1)} ريال" |
|
) |
|
|
|
with col2: |
|
st.metric( |
|
f"إجمالي تكلفة {project_2['name']}", |
|
f"{format_currency(total_cost_2)} ريال", |
|
f"{format_currency(total_cost_2 - total_cost_1)}" |
|
) |
|
|
|
|
|
st.markdown("#### ملخص المقارنة") |
|
|
|
comparison_summary = f""" |
|
بناءً على التحليل المقدم، يمكن استخلاص الملاحظات التالية: |
|
|
|
1. **المسافة بين الموقعين:** {distance:.2f} كيلومتر. |
|
2. **التقييم:** {project_1['name']} بتقييم {project_1_score:.2f}/1.0، و{project_2['name']} بتقييم {project_2_score:.2f}/1.0. |
|
3. **التكلفة:** {project_1['name']} بتكلفة {format_currency(total_cost_1)} ريال، و{project_2['name']} بتكلفة {format_currency(total_cost_2)} ريال. |
|
|
|
بالنظر إلى العوامل أعلاه، فإن الموقع **{preferred_site}** هو الخيار الأفضل من حيث التوازن بين التقييم والتكلفة. |
|
""" |
|
|
|
st.markdown(comparison_summary) |
|
else: |
|
st.error("لم يتم العثور على أحد المشروعين المحددين.") |
|
|
|
def _render_location_management(self): |
|
"""عرض إدارة المواقع""" |
|
st.markdown(""" |
|
<div class='custom-box info-box'> |
|
<h3>📍 إدارة مواقع المشاريع</h3> |
|
<p>إضافة وتعديل مواقع المشاريع وتصدير واستيراد البيانات.</p> |
|
<p>يمكنك إدخال مواقع المشاريع الجديدة وتعديل المواقع الموجودة وحذفها.</p> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
subtabs = st.tabs([ |
|
"إضافة موقع جديد", |
|
"تحرير المواقع", |
|
"استيراد/تصدير المواقع" |
|
]) |
|
|
|
|
|
with subtabs[0]: |
|
self._render_add_location() |
|
|
|
|
|
with subtabs[1]: |
|
self._render_edit_locations() |
|
|
|
|
|
with subtabs[2]: |
|
self._render_import_export_locations() |
|
|
|
def _render_add_location(self): |
|
"""عرض نموذج إضافة موقع جديد""" |
|
st.markdown("### إضافة موقع مشروع جديد") |
|
|
|
|
|
with st.form(key="add_location_form"): |
|
|
|
project_name = st.text_input("اسم المشروع", key="new_project_name") |
|
project_description = st.text_area("وصف المشروع", key="new_project_description") |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
city = st.text_input("المدينة", key="new_city") |
|
status = st.selectbox( |
|
"حالة المشروع", |
|
options=["مخطط", "قيد التنفيذ", "متوقف", "مكتمل"], |
|
key="new_status" |
|
) |
|
|
|
with col2: |
|
latitude = st.number_input("خط العرض", value=24.7136, step=0.0001, format="%.6f", key="new_latitude") |
|
longitude = st.number_input("خط الطول", value=46.6753, step=0.0001, format="%.6f", key="new_longitude") |
|
|
|
|
|
mini_map = folium.Map( |
|
location=[latitude, longitude], |
|
zoom_start=10, |
|
attr='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' |
|
) |
|
folium.Marker(location=[latitude, longitude], tooltip="الموقع المحدد").add_to(mini_map) |
|
folium_static(mini_map, width=700, height=300) |
|
|
|
|
|
submit_button = st.form_submit_button("إضافة الموقع") |
|
|
|
|
|
if submit_button: |
|
if not project_name: |
|
st.error("يرجى إدخال اسم المشروع.") |
|
else: |
|
|
|
project_id = f"PRJ{len(st.session_state.project_locations) + 1:03d}" |
|
|
|
|
|
new_project = { |
|
"project_id": project_id, |
|
"name": project_name, |
|
"description": project_description, |
|
"city": city, |
|
"status": status, |
|
"latitude": latitude, |
|
"longitude": longitude |
|
} |
|
|
|
|
|
st.session_state.project_locations.append(new_project) |
|
|
|
|
|
self._save_locations_data() |
|
|
|
|
|
st.success(f"تمت إضافة موقع المشروع '{project_name}' بنجاح.") |
|
|
|
|
|
st.rerun() |
|
|
|
def _render_edit_locations(self): |
|
"""عرض واجهة تحرير المواقع الموجودة""" |
|
st.markdown("### تحرير مواقع المشاريع") |
|
|
|
if len(st.session_state.project_locations) == 0: |
|
st.warning("لا توجد مواقع مشاريع للتحرير. يرجى إضافة مواقع أولاً.") |
|
return |
|
|
|
|
|
projects_df = pd.DataFrame(st.session_state.project_locations) |
|
|
|
|
|
renamed_columns = { |
|
"name": "اسم المشروع", |
|
"city": "المدينة", |
|
"status": "الحالة", |
|
"description": "الوصف", |
|
"project_id": "معرف المشروع", |
|
"latitude": "خط العرض", |
|
"longitude": "خط الطول" |
|
} |
|
|
|
|
|
display_columns = ["project_id", "name", "city", "status"] |
|
|
|
|
|
display_df = projects_df[display_columns].rename(columns=renamed_columns) |
|
|
|
|
|
st.dataframe(display_df, width=800, height=300) |
|
|
|
|
|
selected_project_id = st.selectbox( |
|
"اختر مشروعًا للتحرير", |
|
options=projects_df["project_id"].tolist(), |
|
format_func=lambda x: next((p["name"] for p in st.session_state.project_locations if p["project_id"] == x), x), |
|
key="edit_project_id" |
|
) |
|
|
|
|
|
selected_project_index = next((i for i, p in enumerate(st.session_state.project_locations) if p["project_id"] == selected_project_id), None) |
|
|
|
if selected_project_index is not None: |
|
selected_project = st.session_state.project_locations[selected_project_index] |
|
|
|
|
|
with st.form(key="edit_location_form"): |
|
st.markdown(f"### تحرير مشروع: {selected_project['name']}") |
|
|
|
|
|
project_name = st.text_input("اسم المشروع", value=selected_project["name"], key="edit_project_name") |
|
project_description = st.text_area("وصف المشروع", value=selected_project.get("description", ""), key="edit_project_description") |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
city = st.text_input("المدينة", value=selected_project.get("city", ""), key="edit_city") |
|
status = st.selectbox( |
|
"حالة المشروع", |
|
options=["مخطط", "قيد التنفيذ", "متوقف", "مكتمل"], |
|
index=["مخطط", "قيد التنفيذ", "متوقف", "مكتمل"].index(selected_project.get("status", "مخطط")), |
|
key="edit_status" |
|
) |
|
|
|
with col2: |
|
latitude = st.number_input("خط العرض", value=selected_project["latitude"], step=0.0001, format="%.6f", key="edit_latitude") |
|
longitude = st.number_input("خط الطول", value=selected_project["longitude"], step=0.0001, format="%.6f", key="edit_longitude") |
|
|
|
|
|
mini_map = folium.Map( |
|
location=[latitude, longitude], |
|
zoom_start=10, |
|
attr='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' |
|
) |
|
folium.Marker(location=[latitude, longitude], tooltip="الموقع المحدد").add_to(mini_map) |
|
folium_static(mini_map, width=700, height=300) |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
update_button = st.form_submit_button("تحديث المعلومات") |
|
|
|
with col2: |
|
delete_button = st.form_submit_button("حذف المشروع", type="secondary") |
|
|
|
|
|
if update_button: |
|
if not project_name: |
|
st.error("لا يمكن ترك اسم المشروع فارغًا.") |
|
else: |
|
|
|
st.session_state.project_locations[selected_project_index] = { |
|
"project_id": selected_project["project_id"], |
|
"name": project_name, |
|
"description": project_description, |
|
"city": city, |
|
"status": status, |
|
"latitude": latitude, |
|
"longitude": longitude |
|
} |
|
|
|
|
|
self._save_locations_data() |
|
|
|
|
|
st.success(f"تم تحديث معلومات المشروع '{project_name}' بنجاح.") |
|
|
|
|
|
st.rerun() |
|
|
|
|
|
if delete_button: |
|
|
|
st.warning(f"هل أنت متأكد من رغبتك في حذف المشروع '{selected_project['name']}'؟") |
|
|
|
confirm_col1, confirm_col2 = st.columns(2) |
|
|
|
with confirm_col1: |
|
if st.button("نعم، حذف المشروع", key="confirm_delete"): |
|
|
|
st.session_state.project_locations.pop(selected_project_index) |
|
|
|
|
|
self._save_locations_data() |
|
|
|
|
|
st.success(f"تم حذف المشروع '{selected_project['name']}' بنجاح.") |
|
|
|
|
|
st.rerun() |
|
|
|
with confirm_col2: |
|
if st.button("لا، إلغاء الحذف", key="cancel_delete"): |
|
st.rerun() |
|
else: |
|
st.error("لم يتم العثور على المشروع المحدد.") |
|
|
|
def _render_import_export_locations(self): |
|
"""عرض واجهة استيراد وتصدير المواقع""" |
|
st.markdown("### استيراد وتصدير مواقع المشاريع") |
|
|
|
|
|
export_tab, import_tab = st.tabs(["تصدير المواقع", "استيراد المواقع"]) |
|
|
|
|
|
with export_tab: |
|
st.markdown("#### تصدير مواقع المشاريع") |
|
|
|
if len(st.session_state.project_locations) == 0: |
|
st.warning("لا توجد مواقع مشاريع للتصدير.") |
|
else: |
|
|
|
export_format = st.radio( |
|
"اختر تنسيق التصدير", |
|
options=["CSV", "Excel", "JSON"], |
|
horizontal=True, |
|
key="export_format" |
|
) |
|
|
|
|
|
if styled_button("تصدير المواقع", key="export_btn", type="primary", icon="📤"): |
|
|
|
exported_data = self._export_locations(export_format.lower()) |
|
|
|
if exported_data: |
|
|
|
if export_format == "CSV": |
|
mime_type = "text/csv" |
|
file_ext = "csv" |
|
elif export_format == "Excel": |
|
mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" |
|
file_ext = "xlsx" |
|
else: |
|
mime_type = "application/json" |
|
file_ext = "json" |
|
|
|
|
|
b64 = base64.b64encode(exported_data).decode() |
|
href = f'<a href="data:{mime_type};base64,{b64}" download="project_locations.{file_ext}" class="btn">تنزيل ملف {export_format}</a>' |
|
st.markdown(href, unsafe_allow_html=True) |
|
|
|
|
|
if export_format == "CSV": |
|
st.markdown("#### معاينة البيانات المصدرة") |
|
st.text(exported_data.decode("utf-8")) |
|
elif export_format == "JSON": |
|
st.markdown("#### معاينة البيانات المصدرة") |
|
st.json(json.loads(exported_data.decode("utf-8"))) |
|
|
|
|
|
with import_tab: |
|
st.markdown("#### استيراد مواقع المشاريع") |
|
|
|
|
|
import_format = st.radio( |
|
"اختر تنسيق الاستيراد", |
|
options=["CSV", "Excel", "JSON"], |
|
horizontal=True, |
|
key="import_format" |
|
) |
|
|
|
|
|
uploaded_file = st.file_uploader(f"تحميل ملف {import_format}", type=[import_format.lower()]) |
|
|
|
if uploaded_file: |
|
|
|
st.markdown("#### معاينة الملف المحمل") |
|
|
|
if import_format == "CSV": |
|
df = pd.read_csv(uploaded_file) |
|
st.dataframe(df) |
|
elif import_format == "Excel": |
|
df = pd.read_excel(uploaded_file) |
|
st.dataframe(df) |
|
else: |
|
json_data = json.load(uploaded_file) |
|
st.json(json_data) |
|
|
|
|
|
import_mode = st.radio( |
|
"طريقة الاستيراد", |
|
options=["إضافة إلى المواقع الحالية", "استبدال جميع المواقع"], |
|
key="import_mode" |
|
) |
|
|
|
|
|
if styled_button("استيراد المواقع", key="import_btn", type="primary", icon="📥"): |
|
|
|
uploaded_file.seek(0) |
|
|
|
try: |
|
|
|
imported_count = self._import_locations(uploaded_file, import_format.lower()) |
|
|
|
if import_mode == "استبدال جميع المواقع": |
|
st.success(f"تم استبدال جميع المواقع بنجاح. عدد المواقع الجديدة: {imported_count}") |
|
else: |
|
st.success(f"تمت إضافة {imported_count} مواقع جديدة بنجاح.") |
|
|
|
|
|
st.rerun() |
|
except Exception as e: |
|
st.error(f"حدث خطأ أثناء استيراد البيانات: {str(e)}") |
|
|
|
def _fetch_terrain_data(self, latitude, longitude, radius_km=5): |
|
"""جلب بيانات التضاريس من واجهة برمجة التطبيقات""" |
|
|
|
|
|
delta = radius_km / 111.0 |
|
|
|
|
|
lat_min, lat_max = latitude - delta, latitude + delta |
|
lon_min, lon_max = longitude - delta, longitude + delta |
|
|
|
|
|
grid_size = 20 |
|
|
|
|
|
lats = np.linspace(lat_min, lat_max, grid_size) |
|
lons = np.linspace(lon_min, lon_max, grid_size) |
|
|
|
|
|
results = [] |
|
|
|
|
|
locations = [] |
|
for lat in lats: |
|
for lon in lons: |
|
locations.append(f"{lat:.6f},{lon:.6f}") |
|
|
|
|
|
batch_size = 100 |
|
for i in range(0, len(locations), batch_size): |
|
batch = locations[i:i+batch_size] |
|
|
|
|
|
try: |
|
url = f"{self.opentopodata_api}?locations={'|'.join(batch)}" |
|
response = requests.get(url) |
|
|
|
if response.status_code == 200: |
|
data = response.json() |
|
if "results" in data: |
|
for result in data["results"]: |
|
if "elevation" in result: |
|
results.append({ |
|
"latitude": result["location"]["lat"], |
|
"longitude": result["location"]["lng"], |
|
"elevation": result["elevation"] |
|
}) |
|
else: |
|
|
|
st.warning(f"فشل جلب بيانات التضاريس من الخدمة (رمز الحالة: {response.status_code}). استخدام بيانات افتراضية.") |
|
|
|
|
|
for j, loc in enumerate(batch): |
|
lat, lon = map(float, loc.split(",")) |
|
|
|
dist = self._calculate_distance(latitude, longitude, lat, lon) |
|
|
|
noise = np.sin(lat * 10) * np.cos(lon * 10) * 50 |
|
elevation = 500 - dist * 100 + noise |
|
|
|
results.append({ |
|
"latitude": lat, |
|
"longitude": lon, |
|
"elevation": max(0, elevation) |
|
}) |
|
except Exception as e: |
|
st.warning(f"حدث خطأ أثناء جلب بيانات التضاريس: {str(e)}. استخدام بيانات افتراضية.") |
|
|
|
|
|
for j, loc in enumerate(batch): |
|
lat, lon = map(float, loc.split(",")) |
|
|
|
dist = self._calculate_distance(latitude, longitude, lat, lon) |
|
|
|
noise = np.sin(lat * 10) * np.cos(lon * 10) * 50 |
|
elevation = 500 - dist * 100 + noise |
|
|
|
results.append({ |
|
"latitude": lat, |
|
"longitude": lon, |
|
"elevation": max(0, elevation) |
|
}) |
|
|
|
return results |
|
|
|
def _calculate_distance(self, lat1, lon1, lat2, lon2): |
|
"""حساب المسافة بين نقطتين بالكيلومترات باستخدام صيغة هافرساين""" |
|
from math import radians, sin, cos, sqrt, atan2 |
|
|
|
|
|
lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2]) |
|
|
|
|
|
dlon = lon2 - lon1 |
|
dlat = lat2 - lat1 |
|
a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2 |
|
c = 2 * atan2(sqrt(a), sqrt(1-a)) |
|
distance = 6371 * c |
|
|
|
return distance |
|
|
|
def _get_color_map(self, scheme): |
|
"""الحصول على خريطة الألوان حسب النظام المختار""" |
|
import matplotlib.cm as cm |
|
import matplotlib.colors as colors |
|
|
|
|
|
colormap = cm.get_cmap(scheme) |
|
|
|
|
|
return lambda x: colors.rgb2hex(colormap(x)) |
|
|
|
def _export_locations(self, format): |
|
"""تصدير مواقع المشاريع إلى ملف""" |
|
try: |
|
|
|
df = pd.DataFrame(st.session_state.project_locations) |
|
|
|
|
|
if format == "csv": |
|
csv_data = df.to_csv(index=False).encode("utf-8") |
|
return csv_data |
|
elif format == "excel": |
|
|
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") as temp: |
|
df.to_excel(temp.name, index=False, engine="xlsxwriter") |
|
temp.flush() |
|
|
|
|
|
with open(temp.name, "rb") as f: |
|
excel_data = f.read() |
|
|
|
|
|
os.unlink(temp.name) |
|
|
|
return excel_data |
|
elif format == "json": |
|
json_data = json.dumps(st.session_state.project_locations, ensure_ascii=False, indent=4).encode("utf-8") |
|
return json_data |
|
else: |
|
st.error(f"تنسيق غير مدعوم: {format}") |
|
return None |
|
except Exception as e: |
|
st.error(f"حدث خطأ أثناء تصدير البيانات: {str(e)}") |
|
return None |
|
|
|
def _import_locations(self, uploaded_file, format): |
|
"""استيراد مواقع المشاريع من ملف""" |
|
try: |
|
imported_data = [] |
|
|
|
|
|
if format == "csv": |
|
df = pd.read_csv(uploaded_file) |
|
imported_data = df.to_dict("records") |
|
elif format == "excel": |
|
df = pd.read_excel(uploaded_file) |
|
imported_data = df.to_dict("records") |
|
elif format == "json": |
|
imported_data = json.load(uploaded_file) |
|
else: |
|
raise ValueError(f"تنسيق غير مدعوم: {format}") |
|
|
|
|
|
required_fields = ["project_id", "name", "latitude", "longitude"] |
|
|
|
for item in imported_data: |
|
missing_fields = [field for field in required_fields if field not in item] |
|
|
|
if missing_fields: |
|
raise ValueError(f"الحقول المطلوبة مفقودة: {', '.join(missing_fields)}") |
|
|
|
|
|
if "import_mode" in st.session_state and st.session_state.import_mode == "استبدال جميع المواقع": |
|
|
|
st.session_state.project_locations = imported_data |
|
else: |
|
|
|
existing_ids = {p["project_id"] for p in st.session_state.project_locations} |
|
new_items = [item for item in imported_data if item["project_id"] not in existing_ids] |
|
st.session_state.project_locations.extend(new_items) |
|
imported_data = new_items |
|
|
|
|
|
self._save_locations_data() |
|
|
|
return len(imported_data) |
|
except Exception as e: |
|
raise Exception(f"حدث خطأ أثناء استيراد البيانات: {str(e)}") |
|
|
|
def _save_locations_data(self): |
|
"""حفظ بيانات المواقع""" |
|
try: |
|
|
|
file_path = os.path.join(self.data_dir, "project_locations.json") |
|
|
|
|
|
with open(file_path, "w", encoding="utf-8") as f: |
|
json.dump(st.session_state.project_locations, f, ensure_ascii=False, indent=4) |
|
except Exception as e: |
|
st.error(f"حدث خطأ أثناء حفظ بيانات المواقع: {str(e)}") |
|
|
|
def _load_locations_data(self): |
|
"""تحميل بيانات المواقع""" |
|
try: |
|
|
|
file_path = os.path.join(self.data_dir, "project_locations.json") |
|
|
|
|
|
if os.path.exists(file_path): |
|
|
|
with open(file_path, "r", encoding="utf-8") as f: |
|
st.session_state.project_locations = json.load(f) |
|
else: |
|
|
|
self._initialize_sample_projects() |
|
except Exception as e: |
|
st.error(f"حدث خطأ أثناء تحميل بيانات المواقع: {str(e)}") |
|
|
|
self._initialize_sample_projects() |
|
|
|
def _initialize_sample_projects(self): |
|
"""تهيئة بيانات اختبارية للمشاريع""" |
|
|
|
saudi_cities = [ |
|
{"name": "الرياض", "lat": 24.7136, "lon": 46.6753}, |
|
{"name": "جدة", "lat": 21.4858, "lon": 39.1925}, |
|
{"name": "مكة المكرمة", "lat": 21.3891, "lon": 39.8579}, |
|
{"name": "المدينة المنورة", "lat": 24.5247, "lon": 39.5692}, |
|
{"name": "الدمام", "lat": 26.4207, "lon": 50.0888}, |
|
{"name": "الطائف", "lat": 21.2704, "lon": 40.4157}, |
|
{"name": "تبوك", "lat": 28.3835, "lon": 36.5662}, |
|
{"name": "بريدة", "lat": 26.3267, "lon": 43.9717}, |
|
{"name": "الخبر", "lat": 26.2172, "lon": 50.1971}, |
|
{"name": "أبها", "lat": 18.2164, "lon": 42.5053} |
|
] |
|
|
|
|
|
project_types = [ |
|
"إنشاء مبنى سكني", |
|
"تطوير طريق سريع", |
|
"بناء جسر", |
|
"إنشاء مدرسة", |
|
"تطوير حديقة عامة", |
|
"بناء مستشفى", |
|
"إنشاء محطة تحلية مياه", |
|
"تطوير مركز تجاري", |
|
"بناء مصنع", |
|
"توسعة مطار" |
|
] |
|
|
|
|
|
project_statuses = ["مخطط", "قيد التنفيذ", "متوقف", "مكتمل"] |
|
|
|
|
|
sample_projects = [] |
|
|
|
for i in range(10): |
|
city = saudi_cities[i] |
|
|
|
|
|
lat_offset = random.uniform(-0.05, 0.05) |
|
lon_offset = random.uniform(-0.05, 0.05) |
|
|
|
project = { |
|
"project_id": f"PRJ{i+1:03d}", |
|
"name": f"{project_types[i]} في {city['name']}", |
|
"description": f"مشروع {project_types[i]} بمدينة {city['name']}. هذا وصف اختباري للمشروع يوضح تفاصيله وأهدافه ونطاق العمل.", |
|
"city": city["name"], |
|
"status": random.choice(project_statuses), |
|
"latitude": city["lat"] + lat_offset, |
|
"longitude": city["lon"] + lon_offset |
|
} |
|
|
|
sample_projects.append(project) |
|
|
|
|
|
st.session_state.project_locations = sample_projects |
|
|
|
|
|
if __name__ == "__main__": |
|
"""تشغيل وحدة الخريطة التفاعلية مع عرض التضاريس ثلاثي الأبعاد بشكل مستقل""" |
|
interactive_map = InteractiveMap() |
|
interactive_map.render() |