hussain2010 commited on
Commit
620c262
·
verified ·
1 Parent(s): 88b31a3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +375 -150
app.py CHANGED
@@ -41,36 +41,84 @@ try:
41
 
42
  selected_country = st.selectbox("Choose a country", countries)
43
 
44
- # Load data for selected country only
45
  gdf = gpd.read_file(dataset_path, layer=0, where=f"NAME_0 = '{selected_country}'")
46
  gdf = gdf[gdf.is_valid].to_crs("EPSG:4326")
47
-
48
- # Initialize dictionary to store selected regions for each level
49
- selected_regions = {}
50
 
 
 
 
 
51
  # Special handling for Pakistan
52
  if selected_country == "Pakistan":
53
  # Define administrative levels for Pakistan
54
  admin_levels = {
55
- "Provinces": ["Punjab", "Sindh", "Khyber Pakhtunkhwa", "Balochistan"],
56
- "Territories": ["Islamabad Capital Territory", "Gilgit-Baltistan", "Azad Kashmir"]
57
  }
58
-
59
- # Add other administrative levels
60
  for i in range(2, 6):
61
  col_name = f"NAME_{i}"
62
  if col_name in gdf.columns and not gdf[col_name].isna().all():
63
- admin_levels[f"Administrative Level {i}"] = sorted(gdf[col_name].dropna().unique())
 
64
 
65
- # Create multiselect for each administrative level
66
- for level, regions in admin_levels.items():
67
- selected = st.multiselect(
68
- f"Select {level}",
69
- regions,
70
- default=regions if level in ["Provinces", "Territories"] else []
71
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  if selected:
73
- selected_regions[level] = selected
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
  else:
76
  # Handle other countries
@@ -79,45 +127,58 @@ try:
79
  col_name = f"NAME_{i}"
80
  if col_name in gdf.columns and not gdf[col_name].isna().all():
81
  level_name = f"Administrative Level {i}"
82
- admin_levels[level_name] = sorted(gdf[col_name].dropna().unique())
83
 
84
- # Create multiselect for each administrative level
85
- for level, regions in admin_levels.items():
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  selected = st.multiselect(
87
  f"Select {level} regions",
88
- regions,
89
- default=regions if level == "Administrative Level 1" else []
90
  )
 
91
  if selected:
92
- selected_regions[level] = selected
 
 
93
 
94
- if selected_regions:
95
  # Area Customization
96
  st.subheader("🎨 Area Customization")
97
 
98
  # Create columns for color pickers
99
  num_cols = 3
100
  cols = st.columns(num_cols)
101
- colors = {}
 
102
 
103
- # Create color pickers for each level and its regions
104
- col_idx = 0
105
- for level, regions in selected_regions.items():
106
- with cols[col_idx % num_cols]:
107
- st.markdown(f"### {level}")
108
- colors[level] = {
109
- "border": st.color_picker(
110
- f"Border color for {level}",
111
- "#000000",
112
- key=f"border_{level}"
113
- ),
114
- "fill": st.color_picker(
115
- f"Fill color for {level}",
116
- f"#{hash(level) % 0xFFFFFF:06x}",
117
- key=f"fill_{level}"
118
- )
119
- }
120
- col_idx += 1
121
 
122
  # Legend Customization
123
  st.subheader("📌 Legend Customization")
@@ -142,43 +203,49 @@ try:
142
 
143
  def create_2d_map():
144
  fig, ax = plt.subplots(figsize=(15, 10))
145
- patches = []
146
- legend_labels = []
147
 
148
- def plot_geometry(geom, level):
149
- if isinstance(geom, MultiPolygon):
150
- for poly in geom.geoms:
 
151
  x, y = poly.exterior.xy
152
- ax.fill(x, y, color=colors[level]["fill"], alpha=0.5)
153
- ax.plot(x, y, color=colors[level]["border"], linewidth=1)
154
- elif isinstance(geom, Polygon):
155
- x, y = geom.exterior.xy
156
- ax.fill(x, y, color=colors[level]["fill"], alpha=0.5)
157
- ax.plot(x, y, color=colors[level]["border"], linewidth=1)
158
-
159
- # Plot each level
160
- for level, regions in selected_regions.items():
161
- level_gdf = None
162
-
163
- if level == "Provinces":
164
- level_gdf = gdf[gdf["NAME_1"].isin(regions)]
165
- elif level == "Territories":
166
- level_gdf = gdf[gdf["NAME_1"].isin(regions)]
167
  else:
168
- col_name = f"NAME_{level.split()[-1]}"
169
- level_gdf = gdf[gdf[col_name].isin(regions)]
170
-
171
- if level_gdf is not None and not level_gdf.empty:
172
- for _, row in level_gdf.iterrows():
173
- plot_geometry(row["geometry"], level)
174
-
175
- if level not in legend_labels:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  patch = plt.Rectangle((0, 0), 1, 1,
177
- fc=colors[level]["fill"],
178
- ec=colors[level]["border"],
179
  alpha=0.5)
180
  patches.append(patch)
181
- legend_labels.append(level)
182
 
183
  if patches:
184
  ax.legend(patches, legend_labels,
@@ -217,8 +284,8 @@ try:
217
  f"image/{format_option.lower()}"
218
  )
219
 
220
- # 3D Map Visualization
221
- st.subheader("🌐 3D Map Visualization")
222
 
223
  st.markdown("""
224
  <style>
@@ -232,78 +299,91 @@ try:
232
  fig_3d = go.Figure()
233
  added_to_legend = set()
234
 
235
- def create_3d_trace(geom, level, show_in_legend):
236
- traces = []
237
- if isinstance(geom, MultiPolygon):
238
- for poly in geom.geoms:
239
  x, y = poly.exterior.xy
240
- traces.append(
241
- go.Scatter3d(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  x=list(x), y=list(y), z=[0]*len(x),
243
  mode='lines+markers',
244
- line=dict(color=colors[level]["border"], width=2),
245
- marker=dict(
246
- size=1,
247
- color=colors[level]["fill"],
248
- opacity=0.5
249
- ),
250
- name=level,
251
- showlegend=show_in_legend and len(traces) == 0
252
- )
253
- )
254
- traces.append(
255
- go.Mesh3d(
256
  x=list(x), y=list(y), z=[0]*len(x),
257
- color=colors[level]["fill"],
258
- opacity=0.3,
 
 
259
  showlegend=False
260
- )
261
- )
262
- elif isinstance(geom, Polygon):
263
- x, y = geom.exterior.xy
264
- traces.append(
265
- go.Scatter3d(
266
- x=list(x), y=list(y), z=[0]*len(x),
267
- mode='lines+markers',
268
- line=dict(color=colors[level]["border"], width=2),
269
- marker=dict(
270
- size=1,
271
- color=colors[level]["fill"],
272
- opacity=0.5
273
- ),
274
- name=level,
275
- showlegend=show_in_legend
276
- )
277
- )
278
- traces.append(
279
- go.Mesh3d(
280
  x=list(x), y=list(y), z=[0]*len(x),
281
- color=colors[level]["fill"],
282
  opacity=0.3,
283
  showlegend=False
284
- )
285
- )
286
- return traces
287
-
288
- # Plot each level
289
- for level, regions in selected_regions.items():
290
- level_gdf = None
291
-
292
- if level == "Provinces":
293
- level_gdf = gdf[gdf["NAME_1"].isin(regions)]
294
- elif level == "Territories":
295
- level_gdf = gdf[gdf["NAME_1"].isin(regions)]
296
- else:
297
- col_name = f"NAME_{level.split()[-1]}"
298
- level_gdf = gdf[gdf[col_name].isin(regions)]
299
-
300
- if level_gdf is not None and not level_gdf.empty:
301
- for _, row in level_gdf.iterrows():
302
- first_occurrence = level not in added_to_legend
303
- traces = create_3d_trace(row["geometry"], level, first_occurrence)
304
- for trace in traces:
305
- fig_3d.add_trace(trace)
306
- added_to_legend.add(level)
307
 
308
  fig_3d.update_layout(
309
  scene=dict(
@@ -340,20 +420,165 @@ try:
340
  )
341
  return fig_3d
342
 
343
- # Create and display 3D map
344
  fig_3d = create_3d_map()
345
  st.plotly_chart(fig_3d, use_container_width=True)
346
 
347
- # Add download button for 3D map
348
- if st.button("Download 3D Map (HTML)"):
349
- html_buffer = io.StringIO()
350
- fig_3d.write_html(html_buffer)
351
- st.download_button(
352
- label="Click to Download 3D Map",
353
- data=html_buffer.getvalue(),
354
- file_name=f"{selected_country.lower()}_map_3d.html",
355
- mime="text/html"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
 
358
  except Exception as e:
359
  st.error(f"⚠️ Error: {e}")
 
41
 
42
  selected_country = st.selectbox("Choose a country", countries)
43
 
44
+ # Load data for selected country and world data
45
  gdf = gpd.read_file(dataset_path, layer=0, where=f"NAME_0 = '{selected_country}'")
46
  gdf = gdf[gdf.is_valid].to_crs("EPSG:4326")
 
 
 
47
 
48
+ # Load world data for the third visualization
49
+ world_gdf = gpd.read_file(dataset_path, layer=0)
50
+ world_gdf = world_gdf[world_gdf.is_valid].to_crs("EPSG:4326")
51
+
52
  # Special handling for Pakistan
53
  if selected_country == "Pakistan":
54
  # Define administrative levels for Pakistan
55
  admin_levels = {
56
+ "Provinces and Territories": "NAME_1",
 
57
  }
58
+ # Add other administrative levels if available
 
59
  for i in range(2, 6):
60
  col_name = f"NAME_{i}"
61
  if col_name in gdf.columns and not gdf[col_name].isna().all():
62
+ level_name = f"Administrative Level {i}"
63
+ admin_levels[level_name] = col_name
64
 
65
+ # Define Pakistan-specific regions
66
+ pakistan_regions = {
67
+ "Provinces": ["Punjab", "Sindh", "Khyber Pakhtunkhwa", "Balochistan"],
68
+ "Territories": ["Islamabad Capital Territory", "Gilgit-Baltistan", "Azad Kashmir"]
69
+ }
70
+
71
+ # Selection for Pakistan's administrative levels
72
+ selected_admin_levels = st.multiselect(
73
+ "Select Administrative Levels to Display",
74
+ list(admin_levels.keys()),
75
+ default=["Provinces and Territories"]
76
+ )
77
+
78
+ # Initialize selected_gdf and selected_areas
79
+ selected_gdf = gpd.GeoDataFrame(columns=gdf.columns, geometry='geometry', crs=gdf.crs)
80
+ selected_areas = []
81
+
82
+ # Handle selection for Pakistan's provinces and territories
83
+ if "Provinces and Territories" in selected_admin_levels:
84
+ col1, col2 = st.columns(2)
85
+
86
+ with col1:
87
+ selected_provinces = st.multiselect(
88
+ "Select Provinces",
89
+ pakistan_regions["Provinces"],
90
+ default=pakistan_regions["Provinces"]
91
+ )
92
+
93
+ with col2:
94
+ selected_territories = st.multiselect(
95
+ "Select Territories",
96
+ pakistan_regions["Territories"],
97
+ default=pakistan_regions["Territories"]
98
+ )
99
+
100
+ # Combine provinces and territories selections
101
+ selected = selected_provinces + selected_territories
102
  if selected:
103
+ level_gdf = gdf[gdf["NAME_1"].isin(selected)]
104
+ selected_gdf = pd.concat([selected_gdf, level_gdf], ignore_index=True)
105
+ selected_areas.extend(selected)
106
+
107
+ # Handle other administrative levels
108
+ for level in selected_admin_levels:
109
+ if level != "Provinces and Territories":
110
+ name_column = admin_levels[level]
111
+ available_areas = sorted(gdf[name_column].dropna().unique())
112
+ selected = st.multiselect(
113
+ f"Select {level} regions",
114
+ available_areas,
115
+ key=f"select_{level}"
116
+ )
117
+
118
+ if selected:
119
+ level_gdf = gdf[gdf[name_column].isin(selected)]
120
+ selected_gdf = pd.concat([selected_gdf, level_gdf], ignore_index=True)
121
+ selected_areas.extend(selected)
122
 
123
  else:
124
  # Handle other countries
 
127
  col_name = f"NAME_{i}"
128
  if col_name in gdf.columns and not gdf[col_name].isna().all():
129
  level_name = f"Administrative Level {i}"
130
+ admin_levels[level_name] = col_name
131
 
132
+ # Select administrative levels to display
133
+ selected_admin_levels = st.multiselect(
134
+ "Select Administrative Levels to Display",
135
+ list(admin_levels.keys()),
136
+ default=[list(admin_levels.keys())[0]] if admin_levels else []
137
+ )
138
+
139
+ # Initialize selected_gdf and selected_areas
140
+ selected_gdf = gpd.GeoDataFrame(columns=gdf.columns, geometry='geometry', crs=gdf.crs)
141
+ selected_areas = []
142
+
143
+ # Handle selection for each administrative level
144
+ for level in selected_admin_levels:
145
+ name_column = admin_levels[level]
146
+ available_areas = sorted(gdf[name_column].dropna().unique())
147
  selected = st.multiselect(
148
  f"Select {level} regions",
149
+ available_areas,
150
+ key=f"select_{level}"
151
  )
152
+
153
  if selected:
154
+ level_gdf = gdf[gdf[name_column].isin(selected)]
155
+ selected_gdf = pd.concat([selected_gdf, level_gdf], ignore_index=True)
156
+ selected_areas.extend(selected)
157
 
158
+ if not selected_gdf.empty and selected_areas:
159
  # Area Customization
160
  st.subheader("🎨 Area Customization")
161
 
162
  # Create columns for color pickers
163
  num_cols = 3
164
  cols = st.columns(num_cols)
165
+ border_colors = {}
166
+ fill_colors = {}
167
 
168
+ # Create color pickers for each area
169
+ for idx, area in enumerate(selected_areas):
170
+ with cols[idx % num_cols]:
171
+ st.markdown(f"### {area}")
172
+ border_colors[area] = st.color_picker(
173
+ f"Border color for {area}",
174
+ "#000000",
175
+ key=f"border_{area}"
176
+ )
177
+ fill_colors[area] = st.color_picker(
178
+ f"Fill color for {area}",
179
+ f"#{hash(area) % 0xFFFFFF:06x}",
180
+ key=f"fill_{area}"
181
+ )
 
 
 
 
182
 
183
  # Legend Customization
184
  st.subheader("📌 Legend Customization")
 
203
 
204
  def create_2d_map():
205
  fig, ax = plt.subplots(figsize=(15, 10))
 
 
206
 
207
+ # Plot all regions first with a light gray color
208
+ for _, row in gdf.iterrows():
209
+ if isinstance(row.geometry, MultiPolygon):
210
+ for poly in row.geometry.geoms:
211
  x, y = poly.exterior.xy
212
+ ax.fill(x, y, color='#EEEEEE', alpha=0.3)
213
+ ax.plot(x, y, color='#CCCCCC', linewidth=0.5)
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  else:
215
+ x, y = row.geometry.exterior.xy
216
+ ax.fill(x, y, color='#EEEEEE', alpha=0.3)
217
+ ax.plot(x, y, color='#CCCCCC', linewidth=0.5)
218
+
219
+ patches = []
220
+ legend_labels = []
221
+
222
+ # Plot selected areas with their chosen colors
223
+ for _, row in selected_gdf.iterrows():
224
+ area_name = None
225
+ for level in selected_admin_levels:
226
+ col_name = admin_levels[level]
227
+ if row[col_name] in selected_areas:
228
+ area_name = row[col_name]
229
+ break
230
+
231
+ if area_name and area_name in border_colors:
232
+ if isinstance(row.geometry, MultiPolygon):
233
+ for poly in row.geometry.geoms:
234
+ x, y = poly.exterior.xy
235
+ ax.fill(x, y, color=fill_colors[area_name], alpha=0.5)
236
+ ax.plot(x, y, color=border_colors[area_name], linewidth=1)
237
+ else:
238
+ x, y = row.geometry.exterior.xy
239
+ ax.fill(x, y, color=fill_colors[area_name], alpha=0.5)
240
+ ax.plot(x, y, color=border_colors[area_name], linewidth=1)
241
+
242
+ if area_name not in legend_labels:
243
  patch = plt.Rectangle((0, 0), 1, 1,
244
+ fc=fill_colors[area_name],
245
+ ec=border_colors[area_name],
246
  alpha=0.5)
247
  patches.append(patch)
248
+ legend_labels.append(area_name)
249
 
250
  if patches:
251
  ax.legend(patches, legend_labels,
 
284
  f"image/{format_option.lower()}"
285
  )
286
 
287
+ # 3D Map Visualization (Current Version)
288
+ st.subheader("🌐 3D Map Visualization (Current Version)")
289
 
290
  st.markdown("""
291
  <style>
 
299
  fig_3d = go.Figure()
300
  added_to_legend = set()
301
 
302
+ # First plot all regions in light gray
303
+ for _, row in gdf.iterrows():
304
+ if isinstance(row.geometry, MultiPolygon):
305
+ for poly in row.geometry.geoms:
306
  x, y = poly.exterior.xy
307
+ fig_3d.add_trace(go.Scatter3d(
308
+ x=list(x), y=list(y), z=[0]*len(x),
309
+ mode='lines',
310
+ line=dict(color='#CCCCCC', width=1),
311
+ showlegend=False
312
+ ))
313
+ else:
314
+ x, y = row.geometry.exterior.xy
315
+ fig_3d.add_trace(go.Scatter3d(
316
+ x=list(x), y=list(y), z=[0]*len(x),
317
+ mode='lines',
318
+ line=dict(color='#CCCCCC', width=1),
319
+ showlegend=False
320
+ ))
321
+
322
+ # Then plot selected areas with their colors
323
+ for _, row in selected_gdf.iterrows():
324
+ area_name = None
325
+ for level in selected_admin_levels:
326
+ col_name = admin_levels[level]
327
+ if row[col_name] in selected_areas:
328
+ area_name = row[col_name]
329
+ break
330
+
331
+ if area_name and area_name in border_colors:
332
+ if isinstance(row.geometry, MultiPolygon):
333
+ for poly in row.geometry.geoms:
334
+ x, y = poly.exterior.xy
335
+ if area_name not in added_to_legend:
336
+ fig_3d.add_trace(go.Scatter3d(
337
+ x=list(x), y=list(y), z=[0]*len(x),
338
+ mode='lines+markers',
339
+ line=dict(color=border_colors[area_name], width=2),
340
+ marker=dict(size=1, color=fill_colors[area_name], opacity=0.5),
341
+ name=area_name,
342
+ showlegend=True
343
+ ))
344
+ added_to_legend.add(area_name)
345
+ else:
346
+ fig_3d.add_trace(go.Scatter3d(
347
+ x=list(x), y=list(y), z=[0]*len(x),
348
+ mode='lines+markers',
349
+ line=dict(color=border_colors[area_name], width=2),
350
+ marker=dict(size=1, color=fill_colors[area_name], opacity=0.5),
351
+ name=area_name,
352
+ showlegend=False
353
+ ))
354
+ fig_3d.add_trace(go.Mesh3d(
355
+ x=list(x), y=list(y), z=[0]*len(x),
356
+ color=fill_colors[area_name],
357
+ opacity=0.3,
358
+ showlegend=False
359
+ ))
360
+ else:
361
+ x, y = row.geometry.exterior.xy
362
+ if area_name not in added_to_legend:
363
+ fig_3d.add_trace(go.Scatter3d(
364
  x=list(x), y=list(y), z=[0]*len(x),
365
  mode='lines+markers',
366
+ line=dict(color=border_colors[area_name], width=2),
367
+ marker=dict(size=1, color=fill_colors[area_name], opacity=0.5),
368
+ name=area_name,
369
+ showlegend=True
370
+ ))
371
+ added_to_legend.add(area_name)
372
+ else:
373
+ fig_3d.add_trace(go.Scatter3d(
 
 
 
 
374
  x=list(x), y=list(y), z=[0]*len(x),
375
+ mode='lines+markers',
376
+ line=dict(color=border_colors[area_name], width=2),
377
+ marker=dict(size=1, color=fill_colors[area_name], opacity=0.5),
378
+ name=area_name,
379
  showlegend=False
380
+ ))
381
+ fig_3d.add_trace(go.Mesh3d(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
  x=list(x), y=list(y), z=[0]*len(x),
383
+ color=fill_colors[area_name],
384
  opacity=0.3,
385
  showlegend=False
386
+ ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
387
 
388
  fig_3d.update_layout(
389
  scene=dict(
 
420
  )
421
  return fig_3d
422
 
423
+ # Create and display 3D map (Current Version)
424
  fig_3d = create_3d_map()
425
  st.plotly_chart(fig_3d, use_container_width=True)
426
 
427
+ # 3D Map Visualization (World Map Version)
428
+ st.subheader("🌎 3D Map Visualization (World Map)")
429
+
430
+ def create_3d_world_map():
431
+ fig_world = go.Figure()
432
+ added_to_legend = set()
433
+
434
+ # Plot all countries in light gray
435
+ for _, row in world_gdf.iterrows():
436
+ if isinstance(row.geometry, MultiPolygon):
437
+ for poly in row.geometry.geoms:
438
+ x, y = poly.exterior.xy
439
+ fig_world.add_trace(go.Scatter3d(
440
+ x=list(x), y=list(y), z=[0]*len(x),
441
+ mode='lines',
442
+ line=dict(color='#CCCCCC', width=1),
443
+ showlegend=False
444
+ ))
445
+ else:
446
+ x, y = row.geometry.exterior.xy
447
+ fig_world.add_trace(go.Scatter3d(
448
+ x=list(x), y=list(y), z=[0]*len(x),
449
+ mode='lines',
450
+ line=dict(color='#CCCCCC', width=1),
451
+ showlegend=False
452
+ ))
453
+
454
+ # Plot selected areas with their colors
455
+ for _, row in selected_gdf.iterrows():
456
+ area_name = None
457
+ for level in selected_admin_levels:
458
+ col_name = admin_levels[level]
459
+ if row[col_name] in selected_areas:
460
+ area_name = row[col_name]
461
+ break
462
+
463
+ if area_name and area_name in border_colors:
464
+ if isinstance(row.geometry, MultiPolygon):
465
+ for poly in row.geometry.geoms:
466
+ x, y = poly.exterior.xy
467
+ if area_name not in added_to_legend:
468
+ fig_world.add_trace(go.Scatter3d(
469
+ x=list(x), y=list(y), z=[0]*len(x),
470
+ mode='lines+markers',
471
+ line=dict(color=border_colors[area_name], width=2),
472
+ marker=dict(size=1, color=fill_colors[area_name], opacity=0.5),
473
+ name=area_name,
474
+ showlegend=True
475
+ ))
476
+ added_to_legend.add(area_name)
477
+ else:
478
+ fig_world.add_trace(go.Scatter3d(
479
+ x=list(x), y=list(y), z=[0]*len(x),
480
+ mode='lines+markers',
481
+ line=dict(color=border_colors[area_name], width=2),
482
+ marker=dict(size=1, color=fill_colors[area_name], opacity=0.5),
483
+ name=area_name,
484
+ showlegend=False
485
+ ))
486
+ fig_world.add_trace(go.Mesh3d(
487
+ x=list(x), y=list(y), z=[0]*len(x),
488
+ color=fill_colors[area_name],
489
+ opacity=0.3,
490
+ showlegend=False
491
+ ))
492
+ else:
493
+ x, y = row.geometry.exterior.xy
494
+ if area_name not in added_to_legend:
495
+ fig_world.add_trace(go.Scatter3d(
496
+ x=list(x), y=list(y), z=[0]*len(x),
497
+ mode='lines+markers',
498
+ line=dict(color=border_colors[area_name], width=2),
499
+ marker=dict(size=1, color=fill_colors[area_name], opacity=0.5),
500
+ name=area_name,
501
+ showlegend=True
502
+ ))
503
+ added_to_legend.add(area_name)
504
+ else:
505
+ fig_world.add_trace(go.Scatter3d(
506
+ x=list(x), y=list(y), z=[0]*len(x),
507
+ mode='lines+markers',
508
+ line=dict(color=border_colors[area_name], width=2),
509
+ marker=dict(size=1, color=fill_colors[area_name], opacity=0.5),
510
+ name=area_name,
511
+ showlegend=False
512
+ ))
513
+ fig_world.add_trace(go.Mesh3d(
514
+ x=list(x), y=list(y), z=[0]*len(x),
515
+ color=fill_colors[area_name],
516
+ opacity=0.3,
517
+ showlegend=False
518
+ ))
519
+
520
+ fig_world.update_layout(
521
+ scene=dict(
522
+ aspectmode='data',
523
+ xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
524
+ yaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
525
+ zaxis=dict(showgrid=False, zeroline=False, showticklabels=False, range=[-1, 1])
526
+ ),
527
+ margin=dict(t=30, b=0, l=0, r=0),
528
+ legend=dict(
529
+ font=dict(
530
+ size=legend_font_size,
531
+ weight=legend_font_weight.lower()
532
+ ),
533
+ bgcolor=legend_bg_color,
534
+ bordercolor=legend_border_color,
535
+ borderwidth=legend_border_width,
536
+ orientation="h" if legend_columns > 1 else "v",
537
+ yanchor="bottom",
538
+ y=1.02,
539
+ xanchor="right",
540
+ x=1,
541
+ title=dict(
542
+ text=legend_title,
543
+ font=dict(
544
+ size=legend_font_size + 2
545
+ )
546
+ ),
547
+ itemsizing='constant',
548
+ traceorder='normal' if legend_columns == 1 else 'grouped'
549
+ ),
550
+ showlegend=True,
551
+ height=800
552
  )
553
+ return fig_world
554
+
555
+ # Create and display 3D world map
556
+ fig_world = create_3d_world_map()
557
+ st.plotly_chart(fig_world, use_container_width=True)
558
+
559
+ # Add download buttons for both 3D maps
560
+ col1, col2 = st.columns(2)
561
+ with col1:
562
+ if st.button("Download 3D Map (Current Version)"):
563
+ html_buffer = io.StringIO()
564
+ fig_3d.write_html(html_buffer)
565
+ st.download_button(
566
+ label="Click to Download 3D Map (Current Version)",
567
+ data=html_buffer.getvalue(),
568
+ file_name=f"{selected_country.lower()}_map_3d.html",
569
+ mime="text/html"
570
+ )
571
+
572
+ with col2:
573
+ if st.button("Download 3D World Map"):
574
+ html_buffer = io.StringIO()
575
+ fig_world.write_html(html_buffer)
576
+ st.download_button(
577
+ label="Click to Download 3D World Map",
578
+ data=html_buffer.getvalue(),
579
+ file_name=f"{selected_country.lower()}_world_map_3d.html",
580
+ mime="text/html"
581
+ )
582
 
583
  except Exception as e:
584
  st.error(f"⚠️ Error: {e}")