import re from typing import Union import folium import pandas as pd from folium import plugins import streamlit as st EPICENTER_LOCATION = [31.12210171476489, -8.42945837915193] BORDER_COLOR = "black" # @st.cache_resource def parse_gg_sheet(url): print("Parsing Google Sheet:", url) url = url.replace("edit#gid=", "export?format=csv&gid=") df = pd.read_csv(url, on_bad_lines="warn") return df @st.cache_resource def parse_json_file(url): df = pd.read_json(url) df = pd.json_normalize(df.douars) return df def is_request_in_list(request, selection_list, options): if isinstance(request, float): # Check if the input is a float (like NaN) return False if "," in request: all_requests = [r.strip() for r in request.split(",")] else: all_requests = [request] # If at least one of the requests is not in the options or in the selection list, return True for r in all_requests: if r not in options: return True if r in selection_list: return True return False def marker_request(request): # in case of multiple requests we use the first one for the marker's icon # requests are already sorted by priority from the form try: displayed_request = request.split(',')[0] except: displayed_request = request return displayed_request def add_latlng_col(df, process_column: Union[str, int]): """Add a latlng column to the dataframe""" if isinstance(process_column, str): df["latlng"] = df[process_column].apply(parse_latlng) elif isinstance(process_column, int): df["latlng"] = df.iloc[:, process_column].apply(parse_latlng) else: raise ValueError(f"process_column should be a string or an integer, got {type(process_column)}") return df # parse latlng (column 4) to [lat, lng] def parse_latlng(latlng): if pd.isna(latlng): return None try: # case where there more than one comma 30,98 , -7,10 if latlng.count(',') > 2: d1, d2, d3, d4 = latlng.split(",")[:4] return [float(".".join([d1, d2])), float(".".join([d3, d4]))] # case of more than one dot 30.98. -7.10 if latlng.count('.') > 2: d1, d2, d3, d4 = latlng.split(".")[:4] return [float(".".join([d1, d2])), float(".".join([d3, d4]))] # case where there is only one comma 30,98 , -7,10 lat, lng = latlng.split(",")[:2] # remove anything that is not a digit or a dot or a minus sign lat = re.sub(r"[^\d\.\-]", "", lat) lng = re.sub(r"[^\d\.\-]", "", lng) return [float(lat), float(lng)] except Exception as e: print(f"Error parsing latlng: {latlng} Reason: {e}") return None def add_epicentre_to_map(fg): # Removed the spinner to not confuse the users as the map is already loaded icon_epicentre = folium.plugins.BeautifyIcon( icon='star', border_color='#b3334f', background_color='#b3334f', text_color='white' ) fg.add_child(folium.Marker(location=EPICENTER_LOCATION, # popup="Epicenter مركز الزلزال", tooltip="Epicenter مركز الزلزال", icon=icon_epicentre)) def add_danger_distances_to_map(map_obj): Danger_Distances_group = folium.FeatureGroup(name='Danger distances - earthquake magnitude 7 | مسافات الخطر - قوة الزلازل 7').add_to(map_obj) zones = [ {"radius": 100000, "fill_opacity": 0.1, "weight": 1, "fill_color": "yellow", "tooltip": "50 to 100 km - Moderate risk area | منطقة خطر معتدلة"}, {"radius": 50000, "fill_opacity": 0.1, "weight": 1, "fill_color": "orange", "tooltip": "30 to 50 km - High risk zone | منطقة عالية المخاطر"}, {"radius": 30000, "fill_opacity": 0.2, "weight": 1, "fill_color": "#FF0000", "tooltip": "10 to 30 km - Very high risk zone | منطقة شديدة الخطورة"}, {"radius": 10000, "fill_opacity": 0.2, "weight": 0.2, "fill_color": "#8B0000", "tooltip": "0 to 10km - direct impact zone | منطقة التأثير المباشر"} ] for zone in zones: folium.Circle( location=EPICENTER_LOCATION, radius=zone["radius"], color=BORDER_COLOR, weight=zone["weight"], fill_opacity=zone["fill_opacity"], opacity=zone["fill_opacity"], # Assuming border opacity should match fill_opacity fill_color=zone["fill_color"], # tooltip=zone["tooltip"], ).add_to(Danger_Distances_group) def add_village_names(douar_df, map_obj): village_fgroup = folium.FeatureGroup(name='🔵 All the Villages / Tous les villages / جميع القرى', show=False).add_to(map_obj) for _, row in douar_df.iterrows(): lat = row['lat'] lng = row['lng'] lat_lng = (lat, lng) dour_name = row['name'].capitalize() maps_url = f"https://maps.google.com/?q={lat_lng}" display_text = f'
⛰️ Douar: {dour_name}
🧭 Google Maps' folium.CircleMarker( location=[lat, lng], radius=0.1, tooltip = dour_name, # we might remove the tooltip to avoid crowding the map popup=folium.Popup(display_text, max_width=200), color= "#0046C8", opacity = 0.7 ).add_to(village_fgroup) def init_intervention_fgs(m): intervention_fgs = {} fg_done = folium.FeatureGroup(name="Done ✅", show=True).add_to(m) fg_planned = folium.FeatureGroup(name="Planned ⏳", show=True).add_to(m) fg_partial = folium.FeatureGroup(name="Partial 📝", show=True).add_to(m) intervention_fgs["Done ✅"] = fg_done intervention_fgs["Planned ⌛"] = fg_planned intervention_fgs["Partial 📝"] = fg_partial return intervention_fgs def init_emergency_fgs(m): emergency_fgs = {} fg_high = folium.FeatureGroup(name="High 🔴", show=True).add_to(m) fg_medium = folium.FeatureGroup(name="Medium 🟠", show=True).add_to(m) fg_low = folium.FeatureGroup(name="Low 🟡", show=True).add_to(m) emergency_fgs["High"] = fg_high emergency_fgs["Medium"] = fg_medium emergency_fgs["Low"] = fg_low return emergency_fgs def init_map(): m = folium.Map( location=[31.228674, -7.992047], zoom_start=8.5, min_zoom=8.5, max_lat=35.628674, min_lat=29.628674, max_lon=-4.992047, min_lon=-10.992047, max_bounds=True, ) # Add a search bar to the map geocoder = plugins.Geocoder( collapsed=False, position="topright", placeholder="Search | البحث", ) m.add_child(geocoder) # Add Fullscreen button to the map fullscreen = plugins.Fullscreen( position="topright", title="Expand me | تكبير الخريطة", title_cancel="Exit me | تصغير الخريطة", force_separate_button=True, ) m.add_child(fullscreen) # Satellite View from Mapbox tileurl = "https://marocmap.ikiker.com/maroc/{z}/{x}/{y}.png" folium.TileLayer( tiles=tileurl, attr="Maroc Map", name="Maroc Map", overlay=False, control=False, ).add_to(m) # Add danger zones add_epicentre_to_map(m) add_danger_distances_to_map(m) emergency_fgs = init_emergency_fgs(m) intervention_fgs = init_intervention_fgs(m) # Add a LayerControl to the map to toggle between layers (Satellite View and Default One) folium.LayerControl().add_to(m) # Add detect location button plugins.LocateControl( position="topleft", drawCircle=False, flyTo=True, strings={"title": "My location | موقعي", "popup": "My location | موقعي"}, ).add_to(m) return m, emergency_fgs, intervention_fgs