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