loubnabnl HF staff commited on
Commit
5c8a315
1 Parent(s): bdfe087

Refactor the code (#12)

Browse files

- Refactor the code (aac840ec39f6e7ecc41ad69182f3dae1b8f76f06)
- Refactor the code (20177bef474994c6cf0223f8e6995969aa414d4b)
- Update app.py (bb85d5ef8c32addc70e4e22087c7d6fc7be449ec)
- Update app.py (daf947da499a1a23d33806e6e15dc5cb22d07b37)
- Create utils.py (86a34e404b88f883ed1ea99b5b29fd01e8175b3d)
- Create utils.py (defc66410ce3f9e6b6f997681ecf570ef965a731)
- Create map_utils.py (6eb804cdd61dbe1ca7d643f85d8569aaf1997170)
- Create map_utils.py (90f672f6310b0da75193aa3ea3b19d70892e5e62)
- Delete utils.py (cdc69ba5946fcbf8087dd01f8d8315525a5b8ec0)
- Delete utils.py (0190569eadf661f25c0dacd338fc5ad0aa2ab105)
- fix imports (736e6b255b0ba1d4e9042ed3eb742872cd3a3be6)
- fix imports (942c123c4317552bc3f6a640ebdc90449cd6dcaa)
- Merge commit 'refs/pr/12' of https://huggingface.co/spaces/nt3awnou/Nt3awnou-rescue-map into pr/12 (98c1362f2dc59e25772d81057b95d4790b1d6b23)
- split review text and remove print for parsing error (d585a22732abed051065171c588c6b0f66e16b9b)
- Update src/text_content.py (a4eeff8e3b1105f7c58b7cb8f84daeef6dc9af4b)

Files changed (4) hide show
  1. app.py +182 -339
  2. utils.py → src/map_utils.py +0 -0
  3. src/text_content.py +82 -0
  4. src/utils.py +110 -0
app.py CHANGED
@@ -1,180 +1,206 @@
1
  import os
2
  import time
3
- import requests
 
4
  import folium
5
  import pandas as pd
6
- from datetime import datetime
7
  import streamlit as st
8
- from streamlit_folium import st_folium
9
- from utils import legend_macro
10
- from huggingface_hub import HfApi
11
  from folium import plugins
 
 
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  TOKEN = os.environ.get("HF_TOKEN", None)
15
- api = HfApi(token=TOKEN)
 
 
 
 
 
16
  st.set_page_config(layout="wide", initial_sidebar_state="collapsed")
17
 
 
18
  if "sleep_time" not in st.session_state:
19
  st.session_state.sleep_time = 2
20
-
21
  if "auto_refresh" not in st.session_state:
22
  st.session_state.auto_refresh = False
23
 
24
- auto_refresh = st.sidebar.checkbox("Auto Refresh?", st.session_state.auto_refresh)
 
25
 
 
26
  if auto_refresh:
27
- number = st.sidebar.number_input("Refresh rate in seconds", value=st.session_state.sleep_time)
28
- st.session_state.sleep_time = number
29
-
30
- #Logo
31
- st.markdown(
32
- """
33
- <div style="text-align: center;">
34
- <img src="https://storage.googleapis.com/storage-annarabic/Nt3awnou(1).png" width="200" height="200">
35
- </div>
36
- """,
37
- unsafe_allow_html=True,
38
- )
39
-
40
- st.title("Nt3awnou نتعاونو ")
41
-
42
- # middle slogan
43
- st.markdown(
44
- """
45
- <div style="text-align: center;">
46
- <h4>وَمَنْ أَحْيَاهَا فَكَأَنَّمَا أَحْيَا النَّاسَ جَمِيعاً</h4>
47
- </div>
48
- """,
49
- unsafe_allow_html=True,
50
- )
51
-
52
- # Have different streamlit tabs for each language
53
- st.sidebar.title("Language / اللغة")
54
- tab_ar, tab_en, tab_fr = st.tabs(["العربية", "English", "Français"])
55
-
56
- with tab_en:
57
- st.markdown("""
58
- <div style="text-align: left;">
59
- Nt3awnou نتعاونو is a collaborative platform dedicated to aiding individuals impacted by the recent earthquake in Morocco. Our core mission is to streamline and coordinate timely assistance for everyone affected. How do we achieve this? We assist those in need by allowing them to communicate their location and the specific aid they require, either by completing a form or sending a voice message via WhatsApp to a designated number. Once we receive and process this information, it can be viewed in our dashboard, which allows NGOs to organize and precisely target their interventions, ensuring swift assistance reaches those in need. Any organization that has taken initiative in a particular area can notify us by completing a dedicated form. This data is also incorporated into the dashboard so that other NGOs can help affected areas that still haven't received help.
60
- Warning : There are still rocks falling down the mountains, making the roads to the affected areas very dangerous. We advise volunteers to donate directly to specialized NGOs.
61
-
62
- ✉️ You can contact us via email at [email protected] <br>
63
- 📝 Help us report more people in need by filling this form: https://forms.gle/nZNCUVog9ka2Vdqu6 <br>
64
- 📝 NGOs can report their interventions by filling this form: https://forms.gle/PsNSuHHjTTgwQMmVA
65
- </div>
66
- """,
67
- unsafe_allow_html=True,
68
- )
69
- with tab_ar:
70
- st.markdown(
71
- """
72
- <div style="text-align: right;">
73
-
74
- نتعاونو هي منصة تعاونية لمساعدة الأفراد المتضررين من الزلزال الأخير في المغرب. مهمتنا هي تسهيل تقديم المساعدة في الوقت المناسب و بفاعلية و تنظيم لجميع المتضررين. كيفاش؟ كنعاونو الناس لي محتاجين للمساعدة إعلمونا بمكانهم و نوع المساعدة لي محتاجين ليها سواء عن طريق ملأ الاستمارة أو عن طريق إرسال تسجيل صوتي عبر واتساب إلى رقم مخصص. بعد معالجة هاد المعلومات، كنجمعوهم فخريطة كتمكن الجمعيات من تنظيم و استهداف تدخلاتهم بدقة باش توصل المساعدة للناس لي محتاجين في وقت أسرع. و كل جمعية قامت باللازم في منطقة معينة تقدر تعلمنا عن طريق ملأ استمارة مخصصة لهاد الأمر. هاد المعلومات كذلك ك��ضاف للخريطة باش باقي الجمعيات يتاجهو لمناطق أخرى مازال ماوصلاتهم مساعدة.
75
- تحذير : نظرا لخطورة الطرقان بسبب الحجر اللي كيطيح من الجبال، ننصح المتطوعين اللي بغاو يساعدو المناطق المتضررة يتبرعو عن طريق الجمعيات المختصة.
76
-
77
- [email protected] المتطوعين ليبغاو يعاونوا يقدرو يتصلوا معنا عبر البريد ✉️
78
- https://forms.gle/nZNCUVog9ka2Vdqu6 : ساعدونا نبلغو الناس ليمحتاجين فهاد الاستمارة 📝<br>
79
- https://forms.gle/PsNSuHHjTTgwQMmVA : الجمعيات لي عندهم تدخلات يقدرو يبلغونا عبر هاد الاستمار ة📝
80
- </div>
81
- """,
82
- unsafe_allow_html=True,
83
  )
84
-
85
- with tab_fr:
86
- st.markdown("""
87
- <div style="text-align: left;">
88
- Nt3awnou نتعاونو est une plateforme collaborative dédiée à l'aide aux personnes touchées par le récent tremblement de terre au Maroc. Notre mission principale est de rationaliser et de coordonner une assistance rapide pour toutes les personnes touchées. Comment y parvenons-nous ? Nous aidons les personnes dans le besoin en leur permettant de communiquer leur localisation et l'aide spécifique dont elles ont besoin, soit en remplissant un formulaire, soit en envoyant un message vocal via WhatsApp à un numéro désigné. Une fois reçues et traitées, ces informations peuvent être consultées dans notre tableau de bord, qui permet aux associations d'organiser et de cibler précisément leurs interventions, afin que l'aide parvienne rapidement à ceux qui en ont besoin. Toute organisation ayant pris une initiative dans une zone particulière peut nous en informer en remplissant un formulaire prévu à cet effet. Ces données sont également intégrées au tableau de bord afin que d'autres associations puissent aider les zones touchées qui n'ont pas encore reçu d'aide.
89
- Avertissement : Il y a encore des chutes de pierres dans les montagnes, ce qui rend les routes vers les zones touchées très dangereuses. Nous conseillons aux volontaires de faire des dons directement aux associations spécialisées.
90
-
91
- ✉️ Vous pouvez nous contacter par courrier électronique à l'adresse suivante : [email protected] <br>
92
- 📝 Aidez-nous à signaler plus de personnes dans le besoin en remplissant ce formulaire : https://forms.gle/nZNCUVog9ka2Vdqu6 <br>
93
- 📝 Les associations peuvent signaler leurs interventions en remplissant ce formulaire : https://forms.gle/PsNSuHHjTTgwQMmVA
94
- </div>
95
- """
96
- , unsafe_allow_html=True)
97
 
98
 
99
- session = requests.Session()
100
  @st.cache_data(persist=True)
101
  def parse_latlng_from_link(url):
102
  try:
103
- # extract latitude and longitude from gmaps link
104
  if "@" not in url:
105
- # We first need to get the redirect URL
106
  resp = session.head(url, allow_redirects=True)
107
  url = resp.url
108
- latlng = url.split('@')[1].split(',')[0:2]
109
  return [float(latlng[0]), float(latlng[1])]
110
  except Exception as e:
111
- print(f"Error parsing latlng from link: {e}")
112
  return None
113
-
 
114
  def parse_gg_sheet_interventions(url):
115
  url = url.replace("edit#gid=", "export?format=csv&gid=")
116
  print(url)
117
- df = pd.read_csv(url, on_bad_lines='skip')
118
  return df.assign(latlng=df.iloc[:, 3].apply(parse_latlng_from_link))
119
 
120
- def parse_gg_sheet(url):
121
- url = url.replace("edit#gid=", "export?format=csv&gid=")
122
- print(url)
123
- df = pd.read_csv(url, on_bad_lines='warn')
124
-
125
- # parse latlng (column 4) to [lat, lng]
126
- def parse_latlng(latlng):
127
- try:
128
- lat, lng = latlng.split(",")
129
- return [float(lat), float(lng)]
130
- except Exception as e:
131
- print(f"Error parsing latlng: {e}")
132
- return None
133
- if df.shape[1] > 4:
134
- df = df.assign(latlng=df.iloc[:, 4].apply(parse_latlng))
135
- return df
136
-
137
- df = parse_gg_sheet(
138
- "https://docs.google.com/spreadsheets/d/1gYoBBiBo1L18IVakHkf3t1fOGvHWb23loadyFZUeHJs/edit#gid=966953708"
139
- )
140
- interventions_df = parse_gg_sheet_interventions(
141
- "https://docs.google.com/spreadsheets/d/1eXOTqunOWWP8FRdENPs4cU9ulISm4XZWYJJNR1-SrwY/edit#gid=2089222765"
142
- )
143
 
144
- # select requests
145
- with tab_ar:
146
- headers_mapping = {
147
- "إغاثة": "Rescue/إغاثة",
148
- "مساعدة طبية": "Medical Assistance/مساعدة طبية",
149
- "مأوى": "Shelter/مأوى",
150
- "طعام وماء": "Food & Water/طعام وماء",
151
- "مخاطر (تسرب الغاز، تلف في الخدمات العامة...)": "Danger/مخاطر (تسرب الغاز، تلف في الخدمات العامة...)",
152
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  with tab_en:
154
- headers_mapping = {
155
- "إغاثة" : "Rescue | إغاثة | Secours",
156
- "مساعدة طبية": "Medical Assistance | مساعدة طبية | Assistance médicale",
157
- "مأوى": "Shelter | مأوى | Abri",
158
- "طعام وماء": "Food & Water | طعام وماء | Nourriture et eau",
159
- "مخاطر (تسرب الغاز، تلف في الخدمات العامة...)": "Danger | مخاطر (تسرب الغاز، تلف في الخدمات العامة...) | Danger",
160
- }
161
-
162
-
163
- colors_mapping = {
164
- "إغاثة": "red",
165
- "مساعدة طبية": "orange",
166
- "مأوى": "beige",
167
- "طعام وماء": "blue",
168
- "مخاطر (تسرب الغاز، تلف في الخدمات العامة...)": "gray",
169
- }
170
- icon_mapping = {
171
- "إغاثة": "bell", # life ring icon for rescue
172
- "مساعدة طبية": "heart", # medical kit for medical assistance
173
- "مأوى": "home", # home icon for shelter
174
- "طعام وماء": "cutlery", # cutlery (fork and knife) for food & water
175
- "مخاطر (تسرب الغاز، تلف في الخدمات العامة...)": "Warning" # warning triangle for dangers
176
- }
177
- options = ["إغاثة", "مساعدة طبية", "مأوى", "طعام وماء", "مخاطر (تسرب الغاز، تلف في الخدمات العامة...)"]
178
  selected_options = []
179
 
180
  with tab_en:
@@ -188,223 +214,40 @@ col1, col2, col3, col4, col5 = st.columns([2, 3, 2, 3, 4])
188
  cols = [col1, col2, col3, col4, col5]
189
 
190
  for i, option in enumerate(options):
191
- checked = cols[i].checkbox(headers_mapping[option], value=True)
192
  if checked:
193
  selected_options.append(option)
194
 
195
- df['id'] = df.index
196
- filtered_df = df[df['ما هي احتياجاتك؟ (أضفها إذا لم يتم ذكرها)'].isin(selected_options)]
197
- selected_headers = [headers_mapping[request] for request in selected_options]
198
-
199
- # select interventions
200
- show_interventions = st.checkbox("Display Interventions | عرض عمليات المساعدة | Afficher les interventions", value=True)
201
-
202
- m = folium.Map(
203
- location=[31.228674, -7.992047],
204
- zoom_start=8.5,
205
- min_zoom=8.5,
206
- max_lat=35.628674,
207
- min_lat=29.628674,
208
- max_lon=-4.992047,
209
- min_lon=-10.992047,
210
- max_bounds=True,
211
- )
212
 
213
- # Add a search bar to the map
214
- plugins.Geocoder(
215
- collapsed=False,
216
- position="topright",
217
- placeholder="Search | البحث",
218
- ).add_to(m)
219
-
220
- # Add Fullscreen button to the map
221
- plugins.Fullscreen(
222
- position='topright',
223
- title='Expand me | تكبير الخريطة',
224
- title_cancel='Exit me | تصغير الخريطة',
225
- force_separate_button=True
226
- ).add_to(m)
227
-
228
- #Satellite View from Mapbox
229
- tileurl = 'https://api.mapbox.com/styles/v1/phd2020/clmer2mra01d001pbgjkictpt/tiles/256/{z}/{x}/{y}@2x?access_token=pk.eyJ1IjoicGhkMjAyMCIsImEiOiJja29lZzFwZmUwNHkzMm5wMjZnYjVvcGltIn0.tE0ritrelQOyLdKUH6hgOw'
230
-
231
- folium.TileLayer(
232
- tiles = tileurl,
233
- attr = 'Satellite View',
234
- name = 'Satellite View | عرض القمر الصناعي',
235
- overlay = False,
236
- control = True
237
- ).add_to(m)
238
-
239
- # Marker for the epicentre
240
- icon_epicentre = folium.plugins.BeautifyIcon(
241
- icon='spinner',
242
- spin='true',
243
- border_color='#b3334f',
244
- background_color = '#b3334f',
245
- text_color = 'white'
246
  )
247
- folium.Marker(location=[31.12210171476489, -8.42945837915193], popup="Epicenter زلزال المغرب 2023 (مركز الزلزال)", icon=icon_epicentre).add_to(m)
248
-
249
- # Danger Distances Feature
250
- Danger_Distances_group = folium.FeatureGroup(name='Danger distances - earthquake magnitude 7 | مسافات الخطر - قوة الزلازل 7').add_to(m)
251
-
252
- radius = 100000 # in meters
253
- folium.Circle(
254
- location=[31.12210171476489, -8.42945837915193],
255
- radius=radius,
256
- color="black",
257
- weight=1,
258
- fill_opacity=0.1,
259
- opacity=0.1,
260
- fill_color="yellow",
261
- tooltip="50 to 100 km -Moderate risk area | منطقة خطر معتدلة",
262
- ).add_to(Danger_Distances_group)
263
-
264
-
265
- radius = 50000 # in meters
266
- folium.Circle(
267
- location=[31.12210171476489, -8.42945837915193],
268
- radius=radius,
269
- color="black",
270
- weight=1,
271
- fill_opacity=0.1,
272
- opacity=0.1,
273
- fill_color="orange",
274
-
275
-
276
- tooltip="30 to 50 km - High risk zone | منطقة عالية المخاطر",
277
- ).add_to(Danger_Distances_group)
278
-
279
-
280
- radius = 30000 # in meters
281
- folium.Circle(
282
- location=[31.12210171476489, -8.42945837915193],
283
- radius=radius,
284
- color="black",
285
- weight=1,
286
- fill_opacity=0.2,
287
- opacity=0.2,
288
- fill_color="#FF0000",
289
-
290
- tooltip="10 to 30 km - Very high risk zone | منطقة شديدة الخطورة ",
291
- ).add_to(Danger_Distances_group)
292
-
293
- radius = 10000 # in meters
294
- folium.Circle(
295
- location=[31.12210171476489, -8.42945837915193],
296
- radius=radius,
297
- color="black",
298
- weight=0.2,
299
- fill_opacity=0.2,
300
- opacity=0.2,
301
- fill_color="#8B0000",
302
-
303
- tooltip="0 to 10km - direct impact zone | منطقة التأثير المباشر",
304
- ).add_to(Danger_Distances_group)
305
-
306
-
307
- # Add a LayerControl to the map to toggle between layers (Satellite View and Default One)
308
- folium.LayerControl().add_to(m)
309
-
310
 
311
  if show_interventions:
312
- for index, row in interventions_df.iterrows():
313
- status = "Done ✅" if row[interventions_df.columns[5]]!="Intervention prévue dans le futur / Planned future intervention" else "Planned ⌛"
314
- color_mk = "green" if row[interventions_df.columns[5]]!="Intervention prévue dans le futur / Planned future intervention" else "pink"
315
- intervention_type = row[interventions_df.columns[6]].split("/")[0].strip()
316
- org = row[interventions_df.columns[1]]
317
- city = row[interventions_df.columns[9]]
318
- date = row[interventions_df.columns[4]]
319
- intervention_info = f"<b>Status:</b> {status}<br><b>Org:</b> {org}<br><b>Intervention:</b> {intervention_type}<br><b>📅 Date:</b> {date}"
320
- if row["latlng"] is None:
321
- continue
322
- folium.Marker(
323
- location=row["latlng"],
324
- tooltip=city,
325
- popup=folium.Popup(intervention_info, max_width=300),
326
- icon=folium.Icon(color=color_mk)
327
- ).add_to(m)
328
-
329
- for index, row in filtered_df.iterrows():
330
- request_type = row['ما هي احتياجاتك؟ (أضفها إذا لم يتم ذكرها)']
331
- long_lat = row["هل يمكنك تقديم الإحداثيات الدقيقة للموقع؟ (ادا كنت لا توجد بعين المكان) متلاً \n31.01837503440344, -6.781405948842175"]
332
- maps_url = f"https://maps.google.com/?q={long_lat}"
333
- display_text = f'<b>Request Type:</b> {request_type}<br><b>Id:</b> {row["id"]}<br><a href="{maps_url}" target="_blank" rel="noopener noreferrer"><b>Google Maps</b></a>'
334
- icon_name = icon_mapping.get(request_type, 'info-sign')
335
- if row["latlng"] is None:
336
- continue
337
 
338
- folium.Marker(
339
- location=row["latlng"],
340
- tooltip=row[' لأي جماعة / قيادة / دوار تنتمون ؟'] if not pd.isna(row[' لأي جماعة / قيادة / دوار تنتمون ؟']) else None,
341
- popup=folium.Popup(display_text, max_width=300),
342
- icon=folium.Icon(color=colors_mapping.get(request_type, "blue"), icon=icon_name)
343
- ).add_to(m)
344
-
345
-
346
- # Macro to add legend
347
- m.get_root().add_child(legend_macro)
348
 
349
  st_data = st_folium(m, use_container_width=True)
350
 
351
- # Google Sheet Table
352
- st.subheader("📝 **Table of requests / جدول الطلبات** https://forms.gle/nZNCUVog9ka2Vdqu6")
353
- st.markdown(
354
- """
355
- <iframe src="https://docs.google.com/spreadsheets/d/1gYoBBiBo1L18IVakHkf3t1fOGvHWb23loadyFZUeHJs/" width="100%" height="600px"></iframe>
356
- <br>
357
- """,
358
- unsafe_allow_html=True,
359
- )
360
-
361
- # Google Sheet Table
362
- st.subheader("📝 **Table of interventions / جدول عمليات المساعدة** https://forms.gle/PsNSuHHjTTgwQMmVA")
363
- st.markdown(
364
- """
365
- <iframe src="https://docs.google.com/spreadsheets/d/1eXOTqunOWWP8FRdENPs4cU9ulISm4XZWYJJNR1-SrwY/?single=true&gid=419657423&range=f2:g6&widget=false&chrome=false" width="100%" height="600px"></iframe>
366
- <br>
367
- """,
368
- unsafe_allow_html=True,
369
- )
370
- # chat.sendMessage(`https://www.google.com/maps/search/?api=1&query=${location.latitude},${location.longitude}`);
371
 
372
  # Submit an id for review
373
- st.subheader("🔍 Review of requests")
374
- st.markdown("If you intervened to solve a request, please fill this [form](https://docs.google.com/forms/d/e/1FAIpQLSe8D6T__DJDTVGMrIWMT-H-hQ0qDUWVOncKnrSXgv4NbwHCrQ/viewform)")
375
- st.markdown("إذا تدخلت لحل طلب، يرجى ملء هذا [النموذج](https://docs.google.com/forms/d/e/1FAIpQLSe8D6T__DJDTVGMrIWMT-H-hQ0qDUWVOncKnrSXgv4NbwHCrQ/viewform)")
376
- st.markdown("**If a request should be reviewed or dropped submit its id here/ إذا كان يجب مراجعة أو حذف طلب، أدخل رقمه هنا:**")
377
-
378
- id_to_review = st.number_input("Enter id / أدخل الرقم", min_value=0, max_value=len(df), value=0, step=1)
379
- reason_for_review = st.text_area("Explain why / أدخل سبب المراجعة")
380
- if st.button("Submit / أرسل"):
381
- if reason_for_review == "":
382
- st.error("Please enter a reason / الرجاء إدخال سبب")
383
- else:
384
- filename = f"review_id_{id_to_review}_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.txt"
385
- with open(filename, "w") as f:
386
- f.write(f"id: {id_to_review}, explanation: {reason_for_review}\n")
387
- api.upload_file(
388
- path_or_fileobj=filename,
389
- path_in_repo=filename,
390
- repo_id="nt3awnou/review_requests",
391
- repo_type="dataset",
392
- )
393
- st.success("Submitted at https://huggingface.co/datasets/nt3awnou/review_requests/ تم الإرسال")
394
-
395
 
396
 
397
  # Credits
398
  st.markdown(
399
- """
400
- <hr>
401
- <div style="text-align: center;">
402
- <p>By <b>Moroccans</b> for <b>Moroccans</b> 🤝</p>
403
- <p>Bot powered by <a href="https://www.annarabic.com/">Annarabic</a></p>
404
- <p>Collaboration made possible thanks to <a href="https://summerschool.morocco.ai/">AI Summer School</a></p>
405
- """,
406
  unsafe_allow_html=True,
407
  )
408
  if auto_refresh:
409
  time.sleep(number)
410
- st.experimental_rerun()
 
1
  import os
2
  import time
3
+ from datetime import datetime
4
+
5
  import folium
6
  import pandas as pd
7
+ import requests
8
  import streamlit as st
 
 
 
9
  from folium import plugins
10
+ from huggingface_hub import HfApi
11
+ from streamlit_folium import st_folium
12
 
13
+ from src.text_content import (
14
+ COLOR_MAPPING,
15
+ CREDITS_TEXT,
16
+ HEADERS_MAPPING,
17
+ ICON_MAPPING,
18
+ INTRO_TEXT_AR,
19
+ INTRO_TEXT_EN,
20
+ INTRO_TEXT_FR,
21
+ LOGO,
22
+ REVIEW_TEXT,
23
+ REVIEW_TEXT_2,
24
+ SLOGAN,
25
+ )
26
+ from src.utils import init_map, parse_gg_sheet
27
 
28
  TOKEN = os.environ.get("HF_TOKEN", None)
29
+ REQUESTS_URL = "https://docs.google.com/spreadsheets/d/1gYoBBiBo1L18IVakHkf3t1fOGvHWb23loadyFZUeHJs/edit#gid=966953708"
30
+ INTERVENTIONS_URL = "https://docs.google.com/spreadsheets/d/1eXOTqunOWWP8FRdENPs4cU9ulISm4XZWYJJNR1-SrwY/edit#gid=2089222765"
31
+ api = HfApi(TOKEN)
32
+
33
+
34
+ # Initialize Streamlit Config
35
  st.set_page_config(layout="wide", initial_sidebar_state="collapsed")
36
 
37
+ # Initialize States
38
  if "sleep_time" not in st.session_state:
39
  st.session_state.sleep_time = 2
 
40
  if "auto_refresh" not in st.session_state:
41
  st.session_state.auto_refresh = False
42
 
43
+ # Session for Requests
44
+ session = requests.Session()
45
 
46
+ auto_refresh = st.sidebar.checkbox("Auto Refresh?", st.session_state.auto_refresh)
47
  if auto_refresh:
48
+ number = st.sidebar.number_input(
49
+ "Refresh rate in seconds", value=st.session_state.sleep_time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  )
51
+ st.session_state.sleep_time = number
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
 
54
+ # Utility functions
55
  @st.cache_data(persist=True)
56
  def parse_latlng_from_link(url):
57
  try:
58
+ # extract latitude and longitude from gmaps link
59
  if "@" not in url:
 
60
  resp = session.head(url, allow_redirects=True)
61
  url = resp.url
62
+ latlng = url.split("@")[1].split(",")[0:2]
63
  return [float(latlng[0]), float(latlng[1])]
64
  except Exception as e:
 
65
  return None
66
+
67
+
68
  def parse_gg_sheet_interventions(url):
69
  url = url.replace("edit#gid=", "export?format=csv&gid=")
70
  print(url)
71
+ df = pd.read_csv(url, on_bad_lines="skip")
72
  return df.assign(latlng=df.iloc[:, 3].apply(parse_latlng_from_link))
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
+ # Streamlit functions
76
+ def display_interventions(interventions_df, m):
77
+ """Display NGO interventions on the map"""
78
+ for index, row in interventions_df.iterrows():
79
+ status = (
80
+ "Done "
81
+ if row[interventions_df.columns[5]]
82
+ != "Intervention prévue dans le futur / Planned future intervention"
83
+ else "Planned ⌛"
84
+ )
85
+ color_mk = (
86
+ "green"
87
+ if row[interventions_df.columns[5]]
88
+ != "Intervention prévue dans le futur / Planned future intervention"
89
+ else "pink"
90
+ )
91
+ intervention_type = row[interventions_df.columns[6]].split("/")[0].strip()
92
+ org = row[interventions_df.columns[1]]
93
+ city = row[interventions_df.columns[9]]
94
+ date = row[interventions_df.columns[4]]
95
+ intervention_info = f"<b>Status:</b> {status}<br><b>Org:</b> {org}<br><b>Intervention:</b> {intervention_type}<br><b>📅 Date:</b> {date}"
96
+ if row["latlng"] is None:
97
+ continue
98
+ folium.Marker(
99
+ location=row["latlng"],
100
+ tooltip=city,
101
+ popup=folium.Popup(intervention_info, max_width=300),
102
+ icon=folium.Icon(color=color_mk),
103
+ ).add_to(m)
104
+
105
+
106
+ def show_requests(filtered_df, m):
107
+ """Display victim requests on the map"""
108
+ for index, row in filtered_df.iterrows():
109
+ request_type = row["ما هي احتياجاتك؟ (أضفها إذا لم يتم ذكرها)"]
110
+ long_lat = row[
111
+ "هل يمكنك تقديم الإحداثيات الدقيقة للموقع؟ (ادا كنت لا توجد بعين المكان) متلاً \n31.01837503440344, -6.781405948842175"
112
+ ]
113
+ maps_url = f"https://maps.google.com/?q={long_lat}"
114
+ display_text = f'<b>Request Type:</b> {request_type}<br><b>Id:</b> {row["id"]}<br><a href="{maps_url}" target="_blank" rel="noopener noreferrer"><b>Google Maps</b></a>'
115
+ icon_name = ICON_MAPPING.get(request_type, "info-sign")
116
+ if row["latlng"] is None:
117
+ continue
118
+
119
+ folium.Marker(
120
+ location=row["latlng"],
121
+ tooltip=row[" لأي جماعة / قيادة / دوار تنتمون ؟"]
122
+ if not pd.isna(row[" لأي جماعة / قيادة / دوار تنتمون ؟"])
123
+ else None,
124
+ popup=folium.Popup(display_text, max_width=300),
125
+ icon=folium.Icon(
126
+ color=COLOR_MAPPING.get(request_type, "blue"), icon=icon_name
127
+ ),
128
+ ).add_to(m)
129
+
130
+
131
+ def display_google_sheet_tables():
132
+ """Display the google sheet tables for requests and interventions"""
133
+ st.subheader("📝 **Table of requests / جدول الطلبات**")
134
+ st.markdown(
135
+ f"""<iframe src="{REQUESTS_URL}" width="100%" height="600px"></iframe>""",
136
+ unsafe_allow_html=True,
137
+ )
138
+
139
+ st.subheader("📝 **Table of interventions / جدول التدخلات**")
140
+ st.markdown(
141
+ f"""<iframe src="{INTERVENTIONS_URL}" width="100%" height="600px"></iframe>""",
142
+ unsafe_allow_html=True,
143
+ )
144
+
145
+
146
+ def id_review_submission():
147
+ """Id review submission form"""
148
+ st.subheader("🔍 Review of requests")
149
+ st.markdown(REVIEW_TEXT)
150
+ st.markdown(REVIEW_TEXT_2)
151
+
152
+ id_to_review = st.number_input(
153
+ "Enter id / أدخل الرقم", min_value=0, max_value=len(df), value=0, step=1
154
+ )
155
+ reason_for_review = st.text_area("Explain why / أدخل سبب المراجعة")
156
+ if st.button("Submit / أرسل"):
157
+ if reason_for_review == "":
158
+ st.error("Please enter a reason / الرجاء إدخال سبب")
159
+ else:
160
+ filename = f"review_id_{id_to_review}_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.txt"
161
+ with open(filename, "w") as f:
162
+ f.write(f"id: {id_to_review}, explanation: {reason_for_review}\n")
163
+ api.upload_file(
164
+ path_or_fileobj=filename,
165
+ path_in_repo=filename,
166
+ repo_id="nt3awnou/review_requests",
167
+ repo_type="dataset",
168
+ )
169
+ st.success(
170
+ "Submitted at https://huggingface.co/datasets/nt3awnou/review_requests/ تم الإرسال"
171
+ )
172
+
173
+
174
+ # Logo and Title
175
+ st.markdown(LOGO, unsafe_allow_html=True)
176
+ st.title("Nt3awnou نتعاونو ")
177
+ st.markdown(SLOGAN, unsafe_allow_html=True)
178
+
179
+ # Language tabs
180
+ st.sidebar.title("Language / اللغة")
181
+ tab_ar, tab_en, tab_fr = st.tabs(["العربية", "English", "Français"])
182
+
183
  with tab_en:
184
+ st.markdown(INTRO_TEXT_EN, unsafe_allow_html=True)
185
+ with tab_ar:
186
+ st.markdown(INTRO_TEXT_AR, unsafe_allow_html=True)
187
+ with tab_fr:
188
+ st.markdown(INTRO_TEXT_FR, unsafe_allow_html=True)
189
+
190
+
191
+ # Load data and initialize map with plugins
192
+ df = parse_gg_sheet(REQUESTS_URL)
193
+ interventions_df = parse_gg_sheet_interventions(INTERVENTIONS_URL)
194
+ m = init_map()
195
+
196
+ # Selection of requests
197
+ options = [
198
+ "إغاثة",
199
+ "مساعدة طبية",
200
+ "مأوى",
201
+ "طعام وماء",
202
+ "مخاطر (تسرب الغاز، تلف في الخدمات العامة...)",
203
+ ]
 
 
 
 
204
  selected_options = []
205
 
206
  with tab_en:
 
214
  cols = [col1, col2, col3, col4, col5]
215
 
216
  for i, option in enumerate(options):
217
+ checked = cols[i].checkbox(HEADERS_MAPPING[option], value=True)
218
  if checked:
219
  selected_options.append(option)
220
 
221
+ df["id"] = df.index
222
+ filtered_df = df[df["ما هي احتياجاتك؟ (أضفها إذا لم يتم ذكرها)"].isin(selected_options)]
223
+ selected_headers = [HEADERS_MAPPING[request] for request in selected_options]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
+ # Selection of interventions
226
+ show_interventions = st.checkbox(
227
+ "Display Interventions | عرض عمليات المساعدة | Afficher les interventions",
228
+ value=True,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
 
231
  if show_interventions:
232
+ display_interventions(interventions_df, m)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
 
234
+ # Show requests
235
+ show_requests(filtered_df, m)
 
 
 
 
 
 
 
 
236
 
237
  st_data = st_folium(m, use_container_width=True)
238
 
239
+ # Google Sheet Tables
240
+ display_google_sheet_tables()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
 
242
  # Submit an id for review
243
+ id_review_submission()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
 
245
 
246
  # Credits
247
  st.markdown(
248
+ CREDITS_TEXT,
 
 
 
 
 
 
249
  unsafe_allow_html=True,
250
  )
251
  if auto_refresh:
252
  time.sleep(number)
253
+ st.experimental_rerun()
utils.py → src/map_utils.py RENAMED
File without changes
src/text_content.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ INTRO_TEXT_EN = """
2
+ <div style="text-align: left;">
3
+ Nt3awnou نتعاونو is a collaborative platform dedicated to aiding individuals impacted by the recent earthquake in Morocco. Our core mission is to streamline and coordinate timely assistance for everyone affected. How do we achieve this? We assist those in need by allowing them to communicate their location and the specific aid they require, either by completing a form or sending a voice message via WhatsApp to a designated number. Once we receive and process this information, it can be viewed in our dashboard, which allows NGOs to organize and precisely target their interventions, ensuring swift assistance reaches those in need. Any organization that has taken initiative in a particular area can notify us by completing a dedicated form. This data is also incorporated into the dashboard so that other NGOs can help affected areas that still haven't received help.
4
+ Warning : There are still rocks falling down the mountains, making the roads to the affected areas very dangerous. We advise volunteers to donate directly to specialized NGOs.
5
+ ✉️ You can contact us via email at [email protected] <br>
6
+ 📝 Help us report more people in need by filling this form: https://forms.gle/nZNCUVog9ka2Vdqu6 <br>
7
+ 📝 NGOs can report their interventions by filling this form: https://forms.gle/PsNSuHHjTTgwQMmVA
8
+ </div>
9
+ """
10
+
11
+ INTRO_TEXT_AR = """
12
+ <div style="text-align: right;">
13
+
14
+ نتعاونو هي منصة تعاونية لمساعدة الأفراد المتضررين من الزلزال الأخير في المغرب. مهمتنا هي تسهيل تقديم المساعدة في الوقت المناسب و بفاعلية و تنظيم لجميع المتضررين. كيفاش؟ كنعاونو الناس لي محتاجين للمساعدة إعلمونا بمكانهم و نوع المساعدة لي محتاجين ليها سواء عن طريق ملأ الاستمارة أو عن طريق إرسال تسجيل صوتي عبر واتساب إلى رقم مخصص. بعد معالجة هاد المعلومات، كنجمعوهم فخريطة كتمكن الجمعيات من تنظيم و استهداف تدخلاتهم بدقة باش توصل المساعدة للناس لي محتاجين في وقت أسرع. و كل جمعية قامت باللازم في منطقة معينة تقدر تعلمنا عن طريق ملأ استمارة مخصصة لهاد الأمر. هاد المعلومات كذلك كتضاف للخريطة باش باقي الجمعيات يتاجهو لمناطق أخرى مازال ماوصلاتهم مساعدة.
15
+ تحذير : نظرا لخطورة الطرقان بسبب الحجر اللي كيطيح من الجبال، ننصح المتطوعين اللي بغاو يساعدو المناطق المتضررة يتبرعو عن طريق الجمعيات المختصة.
16
+
17
+ [email protected] المتطوعين ليبغاو يعاونوا يقدرو يتصلوا معنا عبر البريد ✉️
18
+ https://forms.gle/nZNCUVog9ka2Vdqu6 : ساعدونا نبلغو الناس ليمحتاجين فهاد الاستمارة 📝<br>
19
+ https://forms.gle/PsNSuHHjTTgwQMmVA : الجمعيات لي عندهم تدخلات يقدرو يبلغونا عبر هاد الاستمار ة📝
20
+ </div>
21
+ """
22
+
23
+ INTRO_TEXT_FR = """
24
+ <div style="text-align: left;">
25
+ Nt3awnou نتعاونو est une plateforme collaborative dédiée à l'aide aux personnes touchées par le récent tremblement de terre au Maroc. Notre mission principale est de rationaliser et de coordonner une assistance rapide pour toutes les personnes touchées. Comment y parvenons-nous ? Nous aidons les personnes dans le besoin en leur permettant de communiquer leur localisation et l'aide spécifique dont elles ont besoin, soit en remplissant un formulaire, soit en envoyant un message vocal via WhatsApp à un numéro désigné. Une fois reçues et traitées, ces informations peuvent être consultées dans notre tableau de bord, qui permet aux associations d'organiser et de cibler précisément leurs interventions, afin que l'aide parvienne rapidement à ceux qui en ont besoin. Toute organisation ayant pris une initiative dans une zone particulière peut nous en informer en remplissant un formulaire prévu à cet effet. Ces données sont également intégrées au tableau de bord afin que d'autres associations puissent aider les zones touchées qui n'ont pas encore reçu d'aide.
26
+ Avertissement : Il y a encore des chutes de pierres dans les montagnes, ce qui rend les routes vers les zones touchées très dangereuses. Nous conseillons aux volontaires de faire des dons directement aux associations spécialisées.
27
+
28
+ Vous pouvez nous contacter par courrier électronique à l'adresse suivante : [email protected]
29
+ Aidez-nous à signaler plus de personnes dans le besoin en remplissant ce formulaire : https://forms.gle/nZNCUVog9ka2Vdqu6
30
+ Les associations peuvent signaler leurs interventions en remplissant ce formulaire : https://forms.gle/PsNSuHHjTTgwQMmVA
31
+ </div>
32
+ """
33
+
34
+ SLOGAN = """
35
+ <div style="text-align: center;">
36
+ <h4>وَمَنْ أَحْيَاهَا فَكَأَنَّمَا أَحْيَا النَّاسَ جَمِيعاً</h4>
37
+ </div>
38
+ """
39
+
40
+ HEADERS_MAPPING = {
41
+ "إغاثة" : "Rescue | إغاثة | Secours",
42
+ "مساعدة طبية": "Medical Assistance | مساعدة طبية | Assistance médicale",
43
+ "مأوى": "Shelter | مأوى | Abri",
44
+ "طعام وماء": "Food & Water | طعام وماء | Nourriture et eau",
45
+ "مخاطر (تسرب الغاز، تلف في الخدمات العامة...)": "Danger | مخاطر (تسرب الغاز، تلف في الخدمات العامة...) | Danger",
46
+ }
47
+
48
+ COLOR_MAPPING = {
49
+ "إغاثة": "red",
50
+ "مساعدة طبية": "orange",
51
+ "مأوى": "beige",
52
+ "طعام وماء": "blue",
53
+ "مخاطر (تسرب الغاز، تلف في الخدمات العامة...)": "gray",
54
+ }
55
+
56
+ ICON_MAPPING = {
57
+ "إغاثة": "bell", # life ring icon for rescue
58
+ "مساعدة طبية": "heart", # medical kit for medical assistance
59
+ "مأوى": "home", # home icon for shelter
60
+ "طعام وماء": "cutlery", # cutlery (fork and knife) for food & water
61
+ "مخاطر (تسرب الغاز، تلف في الخدمات العامة...)": "Warning" # warning triangle for dangers
62
+ }
63
+
64
+ CREDITS_TEXT = """
65
+ <hr>
66
+ <div style="text-align: center;">
67
+ <p>By <b>Moroccans</b> for <b>Moroccans</b> 🤝</p>
68
+ <p>Bot powered by <a href="https://www.annarabic.com/">Annarabic</a></p>
69
+ <p>Collaboration made possible thanks to <a href="https://summerschool.morocco.ai/">AI Summer School</a></p>
70
+ """
71
+
72
+ LOGO = """
73
+ <div style="text-align: center;">
74
+ <img src="https://storage.googleapis.com/storage-annarabic/Nt3awnou(1).png" width="200" height="200">
75
+ </div>
76
+ """
77
+
78
+ REVIEW_TEXT = """
79
+ If you intervened to solve a request, please fill this [form](https://docs.google.com/forms/d/e/1FAIpQLSe8D6T__DJDTVGMrIWMT-H-hQ0qDUWVOncKnrSXgv4NbwHCrQ/viewform)\n\n
80
+ إذا تدخلت لحل طلب، يرجى ملء هذا [النموذج](https://docs.google.com/forms/d/e/1FAIpQLSe8D6T__DJDTVGMrIWMT-H-hQ0qDUWVOncKnrSXgv4NbwHCrQ/viewform)
81
+ """
82
+ REVIEW_TEXT_2 = """**If a request should be reviewed or dropped submit its id here/ إذا كان يجب مراجعة أو حذف طلب، أدخل رقمه هنا:**"""
src/utils.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import folium
2
+ import pandas as pd
3
+ from folium import plugins
4
+ from src.map_utils import legend_macro
5
+
6
+
7
+ EPICENTER_LOCATION = [31.12210171476489, -8.42945837915193]
8
+ BORDER_COLOR = "black"
9
+
10
+ def parse_gg_sheet(url):
11
+ url = url.replace("edit#gid=", "export?format=csv&gid=")
12
+ print(url)
13
+ df = pd.read_csv(url, on_bad_lines="warn")
14
+
15
+ # parse latlng (column 4) to [lat, lng]
16
+ def parse_latlng(latlng):
17
+ try:
18
+ lat, lng = latlng.split(",")
19
+ return [float(lat), float(lng)]
20
+ except Exception as e:
21
+ print(f"Error parsing latlng: {e}")
22
+ return None
23
+
24
+ if df.shape[1] > 4:
25
+ df = df.assign(latlng=df.iloc[:, 4].apply(parse_latlng))
26
+ return df
27
+
28
+
29
+ def add_epicentre_to_map(map_obj):
30
+ icon_epicentre = folium.plugins.BeautifyIcon(
31
+ icon='spinner',
32
+ spin=True,
33
+ border_color='#b3334f',
34
+ background_color='#b3334f',
35
+ text_color='white'
36
+ )
37
+ folium.Marker(location=EPICENTER_LOCATION,
38
+ popup="Epicenter مركز الزلزال",
39
+ icon=icon_epicentre).add_to(map_obj)
40
+
41
+
42
+ def add_danger_distances_to_map(map_obj):
43
+ Danger_Distances_group = folium.FeatureGroup(name='Danger distances - earthquake magnitude 7 | مسافات الخطر - قوة الزلازل 7').add_to(map_obj)
44
+
45
+ zones = [
46
+ {"radius": 100000, "fill_opacity": 0.1, "weight": 1, "fill_color": "yellow", "tooltip": "50 to 100 km - Moderate risk area | منطقة خطر معتدلة"},
47
+ {"radius": 50000, "fill_opacity": 0.1, "weight": 1, "fill_color": "orange", "tooltip": "30 to 50 km - High risk zone | منطقة عالية المخاطر"},
48
+ {"radius": 30000, "fill_opacity": 0.2, "weight": 1, "fill_color": "#FF0000", "tooltip": "10 to 30 km - Very high risk zone | منطقة شديدة الخطورة"},
49
+ {"radius": 10000, "fill_opacity": 0.2, "weight": 0.2, "fill_color": "#8B0000", "tooltip": "0 to 10km - direct impact zone | منطقة التأثير المباشر"}
50
+ ]
51
+
52
+ for zone in zones:
53
+ folium.Circle(
54
+ location=EPICENTER_LOCATION,
55
+ radius=zone["radius"],
56
+ color=BORDER_COLOR,
57
+ weight=zone["weight"],
58
+ fill_opacity=zone["fill_opacity"],
59
+ opacity=zone["fill_opacity"], # Assuming border opacity should match fill_opacity
60
+ fill_color=zone["fill_color"],
61
+ tooltip=zone["tooltip"],
62
+ ).add_to(Danger_Distances_group)
63
+
64
+
65
+ def init_map():
66
+ m = folium.Map(
67
+ location=[31.228674, -7.992047],
68
+ zoom_start=8.5,
69
+ min_zoom=8.5,
70
+ max_lat=35.628674,
71
+ min_lat=29.628674,
72
+ max_lon=-4.992047,
73
+ min_lon=-10.992047,
74
+ max_bounds=True,
75
+ )
76
+ # Add a search bar to the map
77
+ plugins.Geocoder(
78
+ collapsed=False,
79
+ position="topright",
80
+ placeholder="Search | البحث",
81
+ ).add_to(m)
82
+
83
+ # Add Fullscreen button to the map
84
+ plugins.Fullscreen(
85
+ position="topright",
86
+ title="Expand me | تكبير الخريطة",
87
+ title_cancel="Exit me | تصغير الخريطة",
88
+ force_separate_button=True,
89
+ ).add_to(m)
90
+
91
+ # Satellite View from Mapbox
92
+ tileurl = "https://api.mapbox.com/styles/v1/phd2020/clmer2mra01d001pbgjkictpt/tiles/256/{z}/{x}/{y}@2x?access_token=pk.eyJ1IjoicGhkMjAyMCIsImEiOiJja29lZzFwZmUwNHkzMm5wMjZnYjVvcGltIn0.tE0ritrelQOyLdKUH6hgOw"
93
+ folium.TileLayer(
94
+ tiles=tileurl,
95
+ attr="Satellite View",
96
+ name="Satellite View | عرض القمر الصناعي",
97
+ overlay=False,
98
+ control=True,
99
+ ).add_to(m)
100
+
101
+ # Add danger zones
102
+ add_epicentre_to_map(m)
103
+ add_danger_distances_to_map(m)
104
+
105
+ # Add a LayerControl to the map to toggle between layers (Satellite View and Default One)
106
+ folium.LayerControl().add_to(m)
107
+
108
+ # Macro to add legend
109
+ m.get_root().add_child(legend_macro)
110
+ return m