| |
| |
|
|
| """ |
| وحدة الخريطة التفاعلية مع عرض التضاريس ثلاثي الأبعاد |
| تتيح هذه الوحدة عرض مواقع المشاريع على خريطة تفاعلية مع إمكانية رؤية التضاريس بشكل ثلاثي الأبعاد |
| """ |
|
|
| 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() |