| |
| |
|
|
| """ |
| وحدة الخريطة التفاعلية مع عرض التضاريس ثلاثي الأبعاد |
| تتيح هذه الوحدة عرض مواقع المشاريع على خريطة تفاعلية مع إمكانية رؤية التضاريس بشكل ثلاثي الأبعاد |
| """ |
|
|
| 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.experimental_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": "موقع مخصص", |
| "project_id": "custom" |
| } |
| |
| st.success("تم جلب بيانات التضاريس بنجاح!") |
| st.experimental_rerun() |
| except Exception as e: |
| st.error(f"حدث خطأ أثناء جلب بيانات التضاريس: {str(e)}") |
| |
| return |
| |
| |
| location = st.session_state.selected_location |
| st.markdown(f"### عرض تضاريس موقع: {location['name']}") |
| st.markdown(f"**الإحداثيات:** {location['latitude']:.6f}, {location['longitude']:.6f}") |
| |
| |
| if st.session_state.terrain_data is None: |
| |
| try: |
| terrain_data = self._fetch_terrain_data( |
| location["latitude"], |
| location["longitude"] |
| ) |
| |
| |
| st.session_state.terrain_data = terrain_data |
| except Exception as e: |
| st.error(f"حدث خطأ أثناء جلب بيانات التضاريس: {str(e)}") |
| return |
| |
| |
| terrain_data = st.session_state.terrain_data |
| |
| |
| col1, col2, col3 = st.columns(3) |
| |
| with col1: |
| elevation_scale = st.slider( |
| "مقياس الارتفاع", |
| min_value=1, |
| max_value=50, |
| value=15, |
| key="elevation_scale" |
| ) |
| |
| with col2: |
| radius = st.slider( |
| "نطاق العرض (كم)", |
| min_value=1, |
| max_value=20, |
| value=5, |
| key="terrain_radius" |
| ) |
| |
| with col3: |
| color_scheme = st.selectbox( |
| "نظام الألوان", |
| options=["terrain", "elevation", "custom"], |
| key="color_scheme" |
| ) |
| |
| |
| try: |
| |
| terrain_df = pd.DataFrame(terrain_data) |
| |
| |
| cell_size = radius * 100 |
| |
| |
| terrain_layer = pdk.Layer( |
| "TerrainLayer", |
| data=None, |
| elevation_decoder={ |
| "elevations": "elevation", |
| "bounds": terrain_df["bounds"].iloc[0] |
| }, |
| texture=None, |
| elevation_data=terrain_df["terrain"].iloc[0], |
| elevation_scale=elevation_scale, |
| color_map=self._get_color_map(color_scheme), |
| wireframe=True, |
| pickable=True |
| ) |
| |
| |
| point_layer = pdk.Layer( |
| "ScatterplotLayer", |
| data=[{ |
| "position": [location["longitude"], location["latitude"]], |
| "name": location["name"] |
| }], |
| get_position="position", |
| get_radius=100, |
| get_fill_color=[255, 0, 0, 200], |
| pickable=True |
| ) |
| |
| |
| INITIAL_VIEW_STATE = pdk.ViewState( |
| longitude=location["longitude"], |
| latitude=location["latitude"], |
| zoom=12, |
| max_zoom=20, |
| pitch=45, |
| bearing=0 |
| ) |
| |
| deck = pdk.Deck( |
| map_style="mapbox://styles/mapbox/satellite-v9", |
| initial_view_state=INITIAL_VIEW_STATE, |
| api_keys={"mapbox": self.mapbox_token} if self.mapbox_token else None, |
| layers=[terrain_layer, point_layer], |
| tooltip={ |
| "html": "<b>{name}</b>", |
| "style": { |
| "backgroundColor": "steelblue", |
| "color": "white" |
| } |
| } |
| ) |
| |
| |
| st.pydeck_chart(deck) |
| |
| |
| if "elevation_stats" in terrain_df: |
| elevation_stats = terrain_df["elevation_stats"].iloc[0] |
| |
| st.markdown("### تحليل التضاريس") |
| |
| stats_col1, stats_col2, stats_col3, stats_col4 = st.columns(4) |
| |
| with stats_col1: |
| st.metric("أدنى ارتفاع", f"{elevation_stats['min']:.1f} م") |
| |
| with stats_col2: |
| st.metric("أعلى ارتفاع", f"{elevation_stats['max']:.1f} م") |
| |
| with stats_col3: |
| st.metric("متوسط الارتفاع", f"{elevation_stats['mean']:.1f} م") |
| |
| with stats_col4: |
| st.metric("فرق الارتفاع", f"{elevation_stats['range']:.1f} م") |
| |
| |
| if "elevation_profile" in terrain_df: |
| elevation_profile = terrain_df["elevation_profile"].iloc[0] |
| |
| |
| profile_df = pd.DataFrame(elevation_profile) |
| |
| |
| st.markdown("### مقطع الارتفاع") |
| |
| |
| import plotly.express as px |
| |
| fig = px.line( |
| profile_df, |
| x="distance", |
| y="elevation", |
| title="مقطع الارتفاع عبر الموقع", |
| labels={"distance": "المسافة (كم)", "elevation": "الارتفاع (م)"} |
| ) |
| |
| fig.update_layout( |
| title_font_size=20, |
| font_family="Arial", |
| font_size=14, |
| height=400 |
| ) |
| |
| st.plotly_chart(fig, use_container_width=True) |
| |
| |
| col1, col2 = st.columns(2) |
| |
| with col1: |
| if styled_button("إعادة تحميل بيانات التضاريس", key="reload_terrain", type="primary", icon="🔄"): |
| |
| st.session_state.terrain_data = None |
| st.experimental_rerun() |
| |
| with col2: |
| if styled_button("العودة للخريطة التفاعلية", key="back_to_map", type="secondary", icon="🗺️"): |
| |
| st.session_state.selected_location = None |
| st.session_state.terrain_data = None |
| st.experimental_rerun() |
| |
| except Exception as e: |
| st.error(f"حدث خطأ أثناء عرض التضاريس ثلاثي الأبعاد: {str(e)}") |
| |
| 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 not st.session_state.project_locations: |
| st.warning("لا توجد مواقع مشاريع متاحة. يرجى إضافة مواقع من تبويب 'إدارة المواقع'.") |
| return |
| |
| |
| locations_df = pd.DataFrame(st.session_state.project_locations) |
| |
| |
| st.markdown("### توزيع المشاريع حسب المدينة") |
| |
| city_counts = locations_df["city"].value_counts().reset_index() |
| city_counts.columns = ["المدينة", "عدد المشاريع"] |
| |
| |
| import plotly.express as px |
| |
| fig = px.bar( |
| city_counts, |
| x="المدينة", |
| y="عدد المشاريع", |
| title="توزيع المشاريع حسب المدينة", |
| color="عدد المشاريع", |
| color_continuous_scale="Viridis" |
| ) |
| |
| fig.update_layout( |
| title_font_size=20, |
| font_family="Arial", |
| font_size=14, |
| height=400 |
| ) |
| |
| st.plotly_chart(fig, use_container_width=True) |
| |
| |
| st.markdown("### توزيع المشاريع حسب الحالة") |
| |
| status_counts = locations_df["status"].value_counts().reset_index() |
| status_counts.columns = ["الحالة", "عدد المشاريع"] |
| |
| |
| fig2 = px.pie( |
| status_counts, |
| values="عدد المشاريع", |
| names="الحالة", |
| title="توزيع المشاريع حسب الحالة", |
| color_discrete_sequence=px.colors.qualitative.Set3 |
| ) |
| |
| fig2.update_layout( |
| title_font_size=20, |
| font_family="Arial", |
| font_size=14, |
| height=400 |
| ) |
| |
| st.plotly_chart(fig2, use_container_width=True) |
| |
| |
| st.markdown("### تحليل المسافات بين المشاريع") |
| |
| |
| if len(locations_df) > 1: |
| |
| reference_project = st.selectbox( |
| "اختر مشروعًا كنقطة مرجعية", |
| options=locations_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="reference_project" |
| ) |
| |
| |
| ref_project_data = locations_df[locations_df["project_id"] == reference_project].iloc[0] |
| |
| |
| distances = [] |
| for _, project in locations_df.iterrows(): |
| if project["project_id"] != reference_project: |
| distance = self._calculate_distance( |
| ref_project_data["latitude"], ref_project_data["longitude"], |
| project["latitude"], project["longitude"] |
| ) |
| |
| distances.append({ |
| "project_id": project["project_id"], |
| "name": project["name"], |
| "city": project["city"], |
| "distance": distance |
| }) |
| |
| |
| distances_df = pd.DataFrame(distances) |
| |
| |
| distances_df = distances_df.sort_values("distance") |
| |
| |
| st.markdown(f"المسافات من مشروع: **{ref_project_data['name']}**") |
| |
| |
| distances_df = distances_df.rename(columns={ |
| "name": "اسم المشروع", |
| "city": "المدينة", |
| "distance": "المسافة (كم)" |
| }) |
| |
| |
| distances_df["المسافة (كم)"] = distances_df["المسافة (كم)"].round(2) |
| |
| |
| st.dataframe(distances_df[["اسم المشروع", "المدينة", "المسافة (كم)"]], width=800) |
| |
| |
| fig3 = px.bar( |
| distances_df, |
| x="اسم المشروع", |
| y="المسافة (كم)", |
| title=f"المسافات من مشروع {ref_project_data['name']}", |
| color="المسافة (كم)", |
| color_continuous_scale="Viridis" |
| ) |
| |
| fig3.update_layout( |
| title_font_size=20, |
| font_family="Arial", |
| font_size=14, |
| height=400 |
| ) |
| |
| st.plotly_chart(fig3, use_container_width=True) |
| |
| |
| st.markdown("### المشاريع القريبة على الخريطة") |
| |
| |
| m2 = folium.Map( |
| location=[ref_project_data["latitude"], ref_project_data["longitude"]], |
| zoom_start=8, |
| tiles="OpenStreetMap", |
| attr='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' |
| ) |
| |
| |
| folium.Marker( |
| location=[ref_project_data["latitude"], ref_project_data["longitude"]], |
| popup=ref_project_data["name"], |
| tooltip=ref_project_data["name"], |
| icon=folium.Icon(color='red', icon='star') |
| ).add_to(m2) |
| |
| |
| folium.Circle( |
| location=[ref_project_data["latitude"], ref_project_data["longitude"]], |
| radius=50000, |
| color='red', |
| fill=True, |
| fill_opacity=0.1, |
| popup="50 كم" |
| ).add_to(m2) |
| |
| folium.Circle( |
| location=[ref_project_data["latitude"], ref_project_data["longitude"]], |
| radius=100000, |
| color='orange', |
| fill=True, |
| fill_opacity=0.1, |
| popup="100 كم" |
| ).add_to(m2) |
| |
| folium.Circle( |
| location=[ref_project_data["latitude"], ref_project_data["longitude"]], |
| radius=200000, |
| color='blue', |
| fill=True, |
| fill_opacity=0.1, |
| popup="200 كم" |
| ).add_to(m2) |
| |
| |
| for _, project in distances_df.iterrows(): |
| project_data = locations_df[locations_df["project_id"] == project["project_id"]].iloc[0] |
| |
| folium.Marker( |
| location=[project_data["latitude"], project_data["longitude"]], |
| popup=f"{project_data['name']} - {project['المسافة (كم)']} كم", |
| tooltip=project_data["name"], |
| icon=folium.Icon(color='green', icon='info-sign') |
| ).add_to(m2) |
| |
| |
| folium.PolyLine( |
| locations=[ |
| [ref_project_data["latitude"], ref_project_data["longitude"]], |
| [project_data["latitude"], project_data["longitude"]] |
| ], |
| color='gray', |
| weight=2, |
| opacity=0.5, |
| popup=f"{project['المسافة (كم)']} كم" |
| ).add_to(m2) |
| |
| |
| folium_static(m2, width=800, height=500) |
| else: |
| st.info("يجب وجود أكثر من مشروع واحد لحساب المسافات.") |
| |
| def _render_location_management(self): |
| """عرض إدارة المواقع""" |
| st.markdown(""" |
| <div class='custom-box info-box'> |
| <h3>⚙️ إدارة المواقع</h3> |
| <p>إضافة وتحرير وحذف مواقع المشاريع.</p> |
| <p>يمكنك إضافة مواقع جديدة أو تحديث المواقع الموجودة.</p> |
| </div> |
| """, unsafe_allow_html=True) |
| |
| |
| management_tabs = st.tabs(["إضافة موقع جديد", "تحرير المواقع الموجودة", "استيراد وتصدير المواقع"]) |
| |
| |
| with management_tabs[0]: |
| self._render_add_location() |
| |
| |
| with management_tabs[1]: |
| self._render_edit_locations() |
| |
| |
| with management_tabs[2]: |
| self._render_import_export_locations() |
| |
| def _render_add_location(self): |
| """عرض نموذج إضافة موقع جديد""" |
| st.markdown("### إضافة موقع مشروع جديد") |
| |
| |
| project_name = st.text_input("اسم المشروع", key="new_project_name") |
| project_desc = st.text_area("وصف المشروع", key="new_project_desc") |
| |
| col1, col2 = st.columns(2) |
| |
| with col1: |
| project_city = st.text_input("المدينة", key="new_project_city") |
| project_status = st.selectbox( |
| "حالة المشروع", |
| options=["جديد", "قيد التنفيذ", "متوقف", "مكتمل"], |
| key="new_project_status" |
| ) |
| |
| with col2: |
| project_id = st.text_input("معرف المشروع (اختياري)", key="new_project_id", placeholder="سيتم إنشاؤه تلقائيًا إذا تُرك فارغًا") |
| |
| |
| st.markdown("#### إحداثيات الموقع") |
| location_method = st.radio( |
| "طريقة تحديد الموقع", |
| options=["إدخال يدوي", "اختيار من الخريطة"], |
| key="new_location_method" |
| ) |
| |
| |
| if location_method == "إدخال يدوي": |
| loc_col1, loc_col2 = st.columns(2) |
| |
| with loc_col1: |
| latitude = st.number_input("خط العرض", value=24.7136, step=0.0001, format="%.6f", key="new_latitude") |
| |
| with loc_col2: |
| 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) |
| folium.Marker(location=[latitude, longitude], tooltip="الموقع المحدد").add_to(mini_map) |
| folium_static(mini_map, width=700, height=300) |
| else: |
| st.markdown("#### اختر الموقع من الخريطة") |
| st.info("انقر على الخريطة لتحديد الموقع.") |
| |
| |
| m = folium.Map(location=[24.7136, 46.6753], zoom_start=6) |
| |
| |
| m.add_child(folium.ClickForMarker(popup="الموقع المحدد")) |
| |
| |
| map_data = folium_static(m, width=700, height=400) |
| |
| |
| st.warning("ملاحظة: خاصية النقر على الخريطة غير مدعومة حاليًا في Streamlit. يرجى استخدام الإدخال اليدوي.") |
| |
| latitude = st.number_input("خط العرض", value=24.7136, step=0.0001, format="%.6f", key="map_latitude") |
| longitude = st.number_input("خط الطول", value=46.6753, step=0.0001, format="%.6f", key="map_longitude") |
| |
| |
| if styled_button("إضافة الموقع", key="add_location", type="primary", icon="➕"): |
| if not project_name or not project_desc or not project_city: |
| st.error("يرجى تعبئة جميع الحقول المطلوبة.") |
| else: |
| |
| if not project_id: |
| project_id = f"PRJ-{len(st.session_state.project_locations) + 1:04d}" |
| |
| |
| new_location = { |
| "name": project_name, |
| "description": project_desc, |
| "city": project_city, |
| "status": project_status, |
| "latitude": latitude, |
| "longitude": longitude, |
| "project_id": project_id, |
| "created_at": pd.Timestamp.now().strftime("%Y-%m-%d %H:%M:%S"), |
| "updated_at": pd.Timestamp.now().strftime("%Y-%m-%d %H:%M:%S") |
| } |
| |
| |
| st.session_state.project_locations.append(new_location) |
| |
| |
| self._save_locations_data() |
| |
| st.success(f"تمت إضافة موقع المشروع '{project_name}' بنجاح!") |
| st.balloons() |
| |
| def _render_edit_locations(self): |
| """عرض واجهة تحرير المواقع الموجودة""" |
| st.markdown("### تحرير أو حذف مواقع المشاريع") |
| |
| |
| if not st.session_state.project_locations: |
| st.warning("لا توجد مواقع مشاريع متاحة. يرجى إضافة مواقع أولاً.") |
| return |
| |
| |
| selected_project_id = st.selectbox( |
| "اختر مشروعًا للتحرير", |
| options=[p["project_id"] for p in st.session_state.project_locations], |
| format_func=lambda x: next((p["name"] for p in st.session_state.project_locations if p["project_id"] == x), x), |
| key="edit_project_select" |
| ) |
| |
| |
| 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']}") |
| |
| |
| project_name = st.text_input("اسم المشروع", value=selected_project["name"], key="edit_project_name") |
| project_desc = st.text_area("وصف المشروع", value=selected_project["description"], key="edit_project_desc") |
| |
| col1, col2 = st.columns(2) |
| |
| with col1: |
| project_city = st.text_input("المدينة", value=selected_project["city"], key="edit_project_city") |
| project_status = st.selectbox( |
| "حالة المشروع", |
| options=["جديد", "قيد التنفيذ", "متوقف", "مكتمل"], |
| index=["جديد", "قيد التنفيذ", "متوقف", "مكتمل"].index(selected_project["status"]), |
| key="edit_project_status" |
| ) |
| |
| with col2: |
| st.text_input("معرف المشروع", value=selected_project["project_id"], disabled=True, key="edit_project_id") |
| |
| |
| st.markdown("#### إحداثيات الموقع") |
| |
| |
| loc_col1, loc_col2 = st.columns(2) |
| |
| with loc_col1: |
| latitude = st.number_input( |
| "خط العرض", |
| value=selected_project["latitude"], |
| step=0.0001, |
| format="%.6f", |
| key="edit_latitude" |
| ) |
| |
| with loc_col2: |
| 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) |
| 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: |
| if styled_button("حفظ التغييرات", key="save_location_changes", type="primary", icon="💾"): |
| if not project_name or not project_desc or not project_city: |
| st.error("يرجى تعبئة جميع الحقول المطلوبة.") |
| else: |
| |
| selected_project["name"] = project_name |
| selected_project["description"] = project_desc |
| selected_project["city"] = project_city |
| selected_project["status"] = project_status |
| selected_project["latitude"] = latitude |
| selected_project["longitude"] = longitude |
| selected_project["updated_at"] = pd.Timestamp.now().strftime("%Y-%m-%d %H:%M:%S") |
| |
| |
| self._save_locations_data() |
| |
| st.success(f"تم تحديث بيانات المشروع '{project_name}' بنجاح!") |
| st.experimental_rerun() |
| |
| with col2: |
| if styled_button("حذف المشروع", key="delete_location", type="danger", icon="🗑️"): |
| |
| st.warning(f"هل أنت متأكد من حذف المشروع '{selected_project['name']}'؟") |
| |
| confirm_col1, confirm_col2 = st.columns(2) |
| |
| with confirm_col1: |
| if styled_button("تأكيد الحذف", key="confirm_delete", type="danger", icon="✓"): |
| |
| st.session_state.project_locations.remove(selected_project) |
| |
| |
| self._save_locations_data() |
| |
| st.success(f"تم حذف المشروع '{selected_project['name']}' بنجاح!") |
| st.experimental_rerun() |
| |
| with confirm_col2: |
| if styled_button("إلغاء", key="cancel_delete", type="secondary", icon="❌"): |
| st.experimental_rerun() |
| |
| def _render_import_export_locations(self): |
| """عرض واجهة استيراد وتصدير المواقع""" |
| st.markdown("### استيراد وتصدير مواقع المشاريع") |
| |
| col1, col2 = st.columns(2) |
| |
| with col1: |
| st.markdown("#### تصدير المواقع") |
| |
| export_format = st.selectbox( |
| "صيغة التصدير", |
| options=["CSV", "JSON", "GeoJSON"], |
| key="export_format" |
| ) |
| |
| if styled_button("تصدير المواقع", key="export_locations", type="primary", icon="📤"): |
| self._export_locations(export_format) |
| |
| with col2: |
| st.markdown("#### استيراد المواقع") |
| |
| import_format = st.selectbox( |
| "صيغة الاستيراد", |
| options=["CSV", "JSON", "GeoJSON"], |
| key="import_format" |
| ) |
| |
| uploaded_file = st.file_uploader( |
| "اختر ملف للاستيراد", |
| type=["csv", "json", "geojson"], |
| key="import_locations_file" |
| ) |
| |
| if uploaded_file is not None: |
| if styled_button("استيراد المواقع", key="import_locations", type="success", icon="📥"): |
| self._import_locations(uploaded_file, import_format) |
| |
| |
| st.markdown("### إحصائيات البيانات") |
| |
| stats_col1, stats_col2, stats_col3 = st.columns(3) |
| |
| with stats_col1: |
| st.metric("عدد المشاريع", len(st.session_state.project_locations)) |
| |
| with stats_col2: |
| cities = set(p["city"] for p in st.session_state.project_locations) |
| st.metric("عدد المدن", len(cities)) |
| |
| with stats_col3: |
| statuses = {} |
| for p in st.session_state.project_locations: |
| statuses[p["status"]] = statuses.get(p["status"], 0) + 1 |
| |
| status_str = ", ".join([f"{k}: {v}" for k, v in statuses.items()]) |
| st.metric("توزيع الحالات", status_str if statuses else "لا توجد بيانات") |
| |
| |
| with st.expander("خيارات متقدمة"): |
| if styled_button("حذف جميع المواقع", key="clear_locations", type="danger", icon="🗑️"): |
| |
| st.warning("هل أنت متأكد من حذف جميع مواقع المشاريع؟ لا يمكن التراجع عن هذا الإجراء.") |
| |
| confirm_col1, confirm_col2 = st.columns(2) |
| |
| with confirm_col1: |
| if styled_button("تأكيد الحذف", key="confirm_clear", type="danger", icon="✓"): |
| |
| st.session_state.project_locations = [] |
| |
| |
| self._save_locations_data() |
| |
| st.success("تم حذف جميع مواقع المشاريع بنجاح!") |
| st.experimental_rerun() |
| |
| with confirm_col2: |
| if styled_button("إلغاء", key="cancel_clear", type="secondary", icon="❌"): |
| st.experimental_rerun() |
| |
| def _fetch_terrain_data(self, latitude, longitude, radius_km=5): |
| """جلب بيانات التضاريس من واجهة برمجة التطبيقات""" |
| try: |
| |
| import plotly.express as px |
| |
| |
| center_lat, center_lon = latitude, longitude |
| |
| |
| radius_deg = radius_km / 111.0 |
| |
| |
| min_lat = center_lat - radius_deg |
| max_lat = center_lat + radius_deg |
| min_lon = center_lon - radius_deg |
| max_lon = center_lon + radius_deg |
| |
| |
| resolution = 50 |
| lats = np.linspace(min_lat, max_lat, resolution) |
| lons = np.linspace(min_lon, max_lon, resolution) |
| |
| |
| grid_lats, grid_lons = np.meshgrid(lats, lons) |
| |
| |
| points = [] |
| for i in range(grid_lats.shape[0]): |
| for j in range(grid_lats.shape[1]): |
| points.append((grid_lats[i, j], grid_lons[i, j])) |
| |
| |
| batch_size = 100 |
| batches = [points[i:i + batch_size] for i in range(0, len(points), batch_size)] |
| |
| |
| elevation_data = np.zeros((len(lats), len(lons))) |
| |
| |
| for batch_idx, batch in enumerate(batches): |
| |
| |
| for point_idx, (lat, lon) in enumerate(batch): |
| |
| lat_idx = np.abs(lats - lat).argmin() |
| lon_idx = np.abs(lons - lon).argmin() |
| |
| |
| |
| dist_from_center = np.sqrt( |
| (lat - center_lat) ** 2 + (lon - center_lon) ** 2 |
| ) |
| |
| |
| elevation = 500 + 200 * np.sin(dist_from_center * 100) + 100 * np.cos(lat * 30) + 150 * np.sin(lon * 40) |
| |
| |
| elevation += np.random.normal(0, 30) |
| |
| |
| elevation_data[lat_idx, lon_idx] = elevation |
| |
| |
| elevation_stats = { |
| "min": float(np.min(elevation_data)), |
| "max": float(np.max(elevation_data)), |
| "mean": float(np.mean(elevation_data)), |
| "range": float(np.max(elevation_data) - np.min(elevation_data)) |
| } |
| |
| |
| center_lon_idx = np.abs(lons - center_lon).argmin() |
| ns_profile = [] |
| for i, lat in enumerate(lats): |
| ns_profile.append({ |
| "distance": (lat - min_lat) * 111.0, |
| "elevation": float(elevation_data[i, center_lon_idx]) |
| }) |
| |
| |
| center_lat_idx = np.abs(lats - center_lat).argmin() |
| ew_profile = [] |
| for i, lon in enumerate(lons): |
| ew_profile.append({ |
| "distance": (lon - min_lon) * 111.0 * np.cos(np.radians(center_lat)), |
| "elevation": float(elevation_data[center_lat_idx, i]) |
| }) |
| |
| |
| elevation_profile = ns_profile + ew_profile |
| |
| |
| bounds = [min_lon, min_lat, max_lon, max_lat] |
| |
| |
| terrain_array = elevation_data.astype(np.float32) |
| |
| |
| terrain_data = [{ |
| "bounds": bounds, |
| "terrain": terrain_array.tolist(), |
| "elevation_stats": elevation_stats, |
| "elevation_profile": elevation_profile |
| }] |
| |
| return terrain_data |
| |
| except Exception as e: |
| st.error(f"حدث خطأ أثناء جلب بيانات التضاريس: {str(e)}") |
| raise e |
| |
| def _calculate_distance(self, lat1, lon1, lat2, lon2): |
| """حساب المسافة بين نقطتين بالكيلومترات باستخدام صيغة هافرساين""" |
| import math |
| |
| |
| lat1 = math.radians(lat1) |
| lon1 = math.radians(lon1) |
| lat2 = math.radians(lat2) |
| lon2 = math.radians(lon2) |
| |
| |
| dlon = lon2 - lon1 |
| dlat = lat2 - lat1 |
| a = math.sin(dlat/2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon/2)**2 |
| c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a)) |
| distance = 6371 * c |
| |
| return distance |
| |
| def _get_color_map(self, scheme): |
| """الحصول على خريطة الألوان حسب النظام المختار""" |
| if scheme == "terrain": |
| return [ |
| [0, (0, 50, 0)], |
| [0.1, (0, 100, 0)], |
| [0.25, (0, 150, 0)], |
| [0.4, (200, 170, 0)], |
| [0.6, (150, 100, 0)], |
| [0.8, (100, 50, 0)], |
| [1, (200, 200, 200)] |
| ] |
| elif scheme == "elevation": |
| return [ |
| [0, (0, 0, 100)], |
| [0.2, (0, 100, 150)], |
| [0.4, (0, 150, 50)], |
| [0.6, (150, 150, 0)], |
| [0.8, (150, 50, 0)], |
| [1, (100, 0, 0)] |
| ] |
| else: |
| return [ |
| [0, (30, 100, 200)], |
| [0.3, (60, 170, 250)], |
| [0.5, (200, 220, 150)], |
| [0.7, (180, 120, 60)], |
| [0.9, (110, 60, 30)], |
| [1, (80, 30, 10)] |
| ] |
| |
| def _export_locations(self, format): |
| """تصدير مواقع المشاريع إلى ملف""" |
| try: |
| if not st.session_state.project_locations: |
| st.error("لا توجد مواقع مشاريع للتصدير.") |
| return |
| |
| if format == "CSV": |
| |
| df = pd.DataFrame(st.session_state.project_locations) |
| |
| csv_data = df.to_csv(index=False) |
| |
| st.download_button( |
| label="تنزيل ملف CSV", |
| data=csv_data, |
| file_name="project_locations.csv", |
| mime="text/csv" |
| ) |
| |
| elif format == "JSON": |
| |
| json_data = json.dumps(st.session_state.project_locations, ensure_ascii=False, indent=2) |
| |
| st.download_button( |
| label="تنزيل ملف JSON", |
| data=json_data, |
| file_name="project_locations.json", |
| mime="application/json" |
| ) |
| |
| elif format == "GeoJSON": |
| |
| features = [] |
| |
| for location in st.session_state.project_locations: |
| feature = { |
| "type": "Feature", |
| "geometry": { |
| "type": "Point", |
| "coordinates": [location["longitude"], location["latitude"]] |
| }, |
| "properties": { |
| "name": location["name"], |
| "description": location["description"], |
| "city": location["city"], |
| "status": location["status"], |
| "project_id": location["project_id"], |
| "created_at": location.get("created_at", ""), |
| "updated_at": location.get("updated_at", "") |
| } |
| } |
| |
| features.append(feature) |
| |
| geojson = { |
| "type": "FeatureCollection", |
| "features": features |
| } |
| |
| geojson_data = json.dumps(geojson, ensure_ascii=False, indent=2) |
| |
| st.download_button( |
| label="تنزيل ملف GeoJSON", |
| data=geojson_data, |
| file_name="project_locations.geojson", |
| mime="application/geo+json" |
| ) |
| |
| st.success(f"تم تصدير {len(st.session_state.project_locations)} موقع بنجاح!") |
| |
| except Exception as e: |
| st.error(f"حدث خطأ أثناء تصدير المواقع: {str(e)}") |
| |
| def _import_locations(self, uploaded_file, format): |
| """استيراد مواقع المشاريع من ملف""" |
| try: |
| if format == "CSV": |
| |
| df = pd.read_csv(uploaded_file) |
| |
| |
| required_columns = ["name", "latitude", "longitude"] |
| missing_columns = [col for col in required_columns if col not in df.columns] |
| |
| if missing_columns: |
| st.error(f"الملف لا يحتوي على الأعمدة التالية: {', '.join(missing_columns)}") |
| return |
| |
| |
| imported_locations = df.to_dict("records") |
| |
| elif format == "JSON": |
| |
| imported_locations = json.loads(uploaded_file.read()) |
| |
| elif format == "GeoJSON": |
| |
| geojson = json.loads(uploaded_file.read()) |
| |
| |
| if "type" not in geojson or geojson["type"] != "FeatureCollection" or "features" not in geojson: |
| st.error("تنسيق GeoJSON غير صحيح.") |
| return |
| |
| |
| imported_locations = [] |
| |
| for feature in geojson["features"]: |
| if feature["type"] == "Feature" and feature["geometry"]["type"] == "Point": |
| coords = feature["geometry"]["coordinates"] |
| properties = feature["properties"] |
| |
| location = { |
| "name": properties.get("name", ""), |
| "description": properties.get("description", ""), |
| "city": properties.get("city", ""), |
| "status": properties.get("status", "جديد"), |
| "longitude": coords[0], |
| "latitude": coords[1], |
| "project_id": properties.get("project_id", f"PRJ-{len(imported_locations)+1:04d}"), |
| "created_at": properties.get("created_at", pd.Timestamp.now().strftime("%Y-%m-%d %H:%M:%S")), |
| "updated_at": properties.get("updated_at", pd.Timestamp.now().strftime("%Y-%m-%d %H:%M:%S")) |
| } |
| |
| imported_locations.append(location) |
| |
| |
| valid_locations = [] |
| for location in imported_locations: |
| |
| if "name" not in location or "latitude" not in location or "longitude" not in location: |
| continue |
| |
| |
| if "description" not in location: |
| location["description"] = "" |
| |
| if "city" not in location: |
| location["city"] = "" |
| |
| if "status" not in location: |
| location["status"] = "جديد" |
| |
| if "project_id" not in location: |
| location["project_id"] = f"PRJ-{len(valid_locations)+1:04d}" |
| |
| if "created_at" not in location: |
| location["created_at"] = pd.Timestamp.now().strftime("%Y-%m-%d %H:%M:%S") |
| |
| if "updated_at" not in location: |
| location["updated_at"] = pd.Timestamp.now().strftime("%Y-%m-%d %H:%M:%S") |
| |
| valid_locations.append(location) |
| |
| if not valid_locations: |
| st.error("لم يتم العثور على مواقع صالحة في الملف.") |
| return |
| |
| |
| import_mode = st.radio( |
| "كيفية الاستيراد", |
| options=["إضافة إلى المواقع الموجودة", "استبدال المواقع الموجودة"], |
| key="import_mode" |
| ) |
| |
| if styled_button("تأكيد الاستيراد", key="confirm_import", type="success", icon="✓"): |
| if import_mode == "إضافة إلى المواقع الموجودة": |
| |
| st.session_state.project_locations.extend(valid_locations) |
| else: |
| |
| st.session_state.project_locations = valid_locations |
| |
| |
| self._save_locations_data() |
| |
| st.success(f"تم استيراد {len(valid_locations)} موقع بنجاح!") |
| st.experimental_rerun() |
| |
| except Exception as e: |
| st.error(f"حدث خطأ أثناء استيراد المواقع: {str(e)}") |
| |
| def _save_locations_data(self): |
| """حفظ بيانات المواقع""" |
| try: |
| |
| os.makedirs(self.data_dir, exist_ok=True) |
| |
| |
| locations_file = os.path.join(self.data_dir, "project_locations.json") |
| |
| with open(locations_file, 'w', encoding='utf-8') as f: |
| json.dump(st.session_state.project_locations, f, ensure_ascii=False, indent=2) |
| except Exception as e: |
| st.error(f"حدث خطأ أثناء حفظ بيانات المواقع: {str(e)}") |
| |
| def _load_locations_data(self): |
| """تحميل بيانات المواقع""" |
| try: |
| |
| locations_file = os.path.join(self.data_dir, "project_locations.json") |
| |
| if os.path.exists(locations_file): |
| with open(locations_file, 'r', encoding='utf-8') as f: |
| locations = json.load(f) |
| |
| |
| st.session_state.project_locations = locations |
| except Exception as e: |
| st.error(f"حدث خطأ أثناء تحميل بيانات المواقع: {str(e)}") |
| |
| def _initialize_sample_projects(self): |
| """تهيئة بيانات اختبارية للمشاريع""" |
| |
| locations_file = os.path.join(self.data_dir, "project_locations.json") |
| |
| if os.path.exists(locations_file): |
| |
| self._load_locations_data() |
| return |
| |
| |
| sample_projects = [ |
| { |
| "name": "تطوير شبكة الطرق في منطقة الرياض", |
| "description": "مشروع تطوير وتوسعة شبكة الطرق الرئيسية في منطقة الرياض", |
| "city": "الرياض", |
| "status": "قيد التنفيذ", |
| "latitude": 24.7136, |
| "longitude": 46.6753, |
| "project_id": "PRJ-0001", |
| "created_at": "2025-01-15 10:30:00", |
| "updated_at": "2025-01-15 10:30:00" |
| }, |
| { |
| "name": "إنشاء سد وادي حنيفة", |
| "description": "مشروع إنشاء سد لحجز مياه الأمطار في وادي حنيفة", |
| "city": "الرياض", |
| "status": "جديد", |
| "latitude": 24.6748, |
| "longitude": 46.5831, |
| "project_id": "PRJ-0002", |
| "created_at": "2025-02-01 14:45:00", |
| "updated_at": "2025-02-01 14:45:00" |
| }, |
| { |
| "name": "تطوير ميناء جدة الإسلامي", |
| "description": "مشروع تطوير وتوسعة ميناء جدة الإسلامي لزيادة الطاقة الاستيعابية", |
| "city": "جدة", |
| "status": "قيد التنفيذ", |
| "latitude": 21.4858, |
| "longitude": 39.1925, |
| "project_id": "PRJ-0003", |
| "created_at": "2024-11-20 09:15:00", |
| "updated_at": "2024-11-20 09:15:00" |
| }, |
| { |
| "name": "إنشاء مطار الدمام الجديد", |
| "description": "مشروع إنشاء مطار جديد في مدينة الدمام لتلبية الطلب المتزايد", |
| "city": "الدمام", |
| "status": "متوقف", |
| "latitude": 26.4207, |
| "longitude": 50.0888, |
| "project_id": "PRJ-0004", |
| "created_at": "2024-10-05 11:30:00", |
| "updated_at": "2024-10-05 11:30:00" |
| }, |
| { |
| "name": "توسعة جامعة الملك فهد للبترول والمعادن", |
| "description": "مشروع توسعة مباني ومرافق جامعة الملك فهد للبترول والمعادن", |
| "city": "الظهران", |
| "status": "قيد التنفيذ", |
| "latitude": 26.3927, |
| "longitude": 50.1150, |
| "project_id": "PRJ-0005", |
| "created_at": "2025-01-10 08:00:00", |
| "updated_at": "2025-01-10 08:00:00" |
| }, |
| { |
| "name": "إنشاء محطة تحلية مياه القنفذة", |
| "description": "مشروع إنشاء محطة تحلية مياه جديدة في محافظة القنفذة", |
| "city": "القنفذة", |
| "status": "جديد", |
| "latitude": 19.1299, |
| "longitude": 41.0825, |
| "project_id": "PRJ-0006", |
| "created_at": "2025-02-20 15:20:00", |
| "updated_at": "2025-02-20 15:20:00" |
| }, |
| { |
| "name": "تطوير مجمع حكومي في حائل", |
| "description": "مشروع إنشاء وتطوير مجمع للدوائر الحكومية في مدينة حائل", |
| "city": "حائل", |
| "status": "مكتمل", |
| "latitude": 27.5114, |
| "longitude": 41.7208, |
| "project_id": "PRJ-0007", |
| "created_at": "2024-06-15 10:00:00", |
| "updated_at": "2024-12-10 14:30:00" |
| }, |
| { |
| "name": "إنشاء مستشفى الإحساء العام", |
| "description": "مشروع إنشاء مستشفى عام جديد في محافظة الإحساء بسعة 500 سرير", |
| "city": "الإحساء", |
| "status": "قيد التنفيذ", |
| "latitude": 25.3753, |
| "longitude": 49.5873, |
| "project_id": "PRJ-0008", |
| "created_at": "2024-09-01 09:45:00", |
| "updated_at": "2024-09-01 09:45:00" |
| }, |
| { |
| "name": "تطوير شبكة الصرف الصحي في أبها", |
| "description": "مشروع تطوير وتوسعة شبكة الصرف الصحي في مدينة أبها", |
| "city": "أبها", |
| "status": "جديد", |
| "latitude": 18.2164, |
| "longitude": 42.5053, |
| "project_id": "PRJ-0009", |
| "created_at": "2025-02-25 11:15:00", |
| "updated_at": "2025-02-25 11:15:00" |
| }, |
| { |
| "name": "إنشاء مدينة صناعية في سكاكا", |
| "description": "مشروع إنشاء مدينة صناعية جديدة في منطقة سكاكا", |
| "city": "سكاكا", |
| "status": "متوقف", |
| "latitude": 29.9720, |
| "longitude": 40.2006, |
| "project_id": "PRJ-0010", |
| "created_at": "2024-07-20 13:30:00", |
| "updated_at": "2024-07-20 13:30:00" |
| } |
| ] |
| |
| |
| st.session_state.project_locations = sample_projects |
| |
| |
| self._save_locations_data() |
|
|
|
|
| |
| class folium_static: |
| """فئة لعرض خرائط Folium في Streamlit""" |
| |
| def __init__(self, fig, width=700, height=500): |
| """عرض خريطة Folium في Streamlit""" |
| import streamlit.components.v1 as components |
| |
| |
| fig_html = fig._repr_html_() |
| |
| |
| components.html(fig_html, width=width, height=height) |
|
|
|
|
| |
| def main(): |
| """تشغيل وحدة الخريطة التفاعلية مع عرض التضاريس ثلاثي الأبعاد بشكل مستقل""" |
| |
| st.set_page_config( |
| page_title="الخريطة التفاعلية | WAHBi AI", |
| page_icon="🗺️", |
| layout="wide", |
| initial_sidebar_state="expanded", |
| menu_items={ |
| 'Get Help': 'mailto:support@wahbi-ai.com', |
| 'Report a bug': 'mailto:support@wahbi-ai.com', |
| 'About': 'وحدة الخريطة التفاعلية مع عرض التضاريس ثلاثي الأبعاد - جزء من نظام WAHBi AI لتحليل المناقصات' |
| } |
| ) |
| |
| |
| interactive_map = InteractiveMap() |
| |
| |
| interactive_map.render() |
|
|
| |
| if __name__ == "__main__": |
| main() |