mirix commited on
Commit
5380330
Β·
verified Β·
1 Parent(s): 20b42a7

Upload 2 files

Browse files
Files changed (2) hide show
  1. Russia_protected_areas_simplified.geoparquet +2 -2
  2. app.py +181 -121
Russia_protected_areas_simplified.geoparquet CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:aab10cc608c079b112df3655efb9694f891b6a6040a1fc11a91f0876b1af2c60
3
- size 7376561
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:700d2be037950da938063c0c1f9fe3f4ecbfd872713c0f2f2e45e41cbe5d16e3
3
+ size 3698919
app.py CHANGED
@@ -3,133 +3,190 @@ import geopandas as gpd
3
  import plotly.express as px
4
  import plotly.graph_objects as go
5
  import pandas as pd
 
6
 
7
- # IUCN Category information (descriptions only, colors from Plotly)
 
 
8
  IUCN_CATEGORIES = {
9
- 'Ia': {
10
- 'name': 'Strict Nature Reserve',
11
- 'description': 'Strictly protected for biodiversity, geological/geomorphological features, and scientific research.'
 
 
 
12
  },
13
- 'Ib': {
14
- 'name': 'Wilderness Area',
15
- 'description': 'Large, unmodified areas retaining natural character, without permanent habitation, managed to preserve natural condition.'
 
 
 
16
  },
17
- 'II': {
18
- 'name': 'National Park',
19
- 'description': 'Large natural/near-natural areas protecting large-scale ecological processes and species, while allowing compatible spiritual/recreational use.'
 
 
 
 
20
  },
21
- 'III': {
22
- 'name': 'Natural Monument/Feature',
23
- 'description': 'Set aside to protect a specific natural monument, landform, sea mount, or geological feature.'
 
 
 
24
  },
25
- 'IV': {
26
- 'name': 'Habitat/Species Management Area',
27
- 'description': 'Protects particular species or habitats, often requiring regular, active management interventions.'
 
 
 
 
 
 
 
 
 
 
28
  },
29
- 'V': {
30
- 'name': 'Protected Landscape/Seascape',
31
- 'description': 'Protects areas where the interaction of people and nature over time has produced a distinct character.'
 
 
 
 
32
  },
33
- 'VI': {
34
- 'name': 'Sustainable Use Area',
35
- 'description': 'Conserves ecosystems and habitats together with associated cultural values and traditional natural resource management systems.'
36
  },
37
- 'Not Applicable': {
38
- 'name': 'Not Applicable',
39
- 'description': 'IUCN category is not applicable to this protected area.'
40
  },
41
- 'Not Assigned': {
42
- 'name': 'Not Assigned',
43
- 'description': 'IUCN category has not been assigned.'
44
  },
45
- 'Not Reported': {
46
- 'name': 'Not Reported',
47
- 'description': 'IUCN category has not been reported.'
48
- }
49
  }
50
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  # Load data
 
52
  try:
53
- # Load protected areas
54
- protected_gdf = gpd.read_parquet('Russia_protected_areas_simplified.geoparquet')
 
55
  protected_gdf = protected_gdf.to_crs(epsg=4326)
56
  print(f"Loaded {len(protected_gdf)} protected areas")
57
-
58
- # Ensure IUCN_CAT is string type and handle missing values
59
- if 'IUCN_CAT' in protected_gdf.columns:
60
- protected_gdf['IUCN_CAT'] = protected_gdf['IUCN_CAT'].fillna('Not Reported').astype(str)
61
- protected_gdf['IUCN_CAT'] = protected_gdf['IUCN_CAT'].apply(
62
- lambda x: x if x in IUCN_CATEGORIES else 'Not Reported'
 
 
 
 
 
63
  )
64
-
65
  # Keep only needed columns
66
- columns_to_keep = ['NAME', 'DESIG', 'DESIG_ENG', 'DESIG_TYPE', 'IUCN_CAT', 'geometry']
67
- available_columns = [col for col in columns_to_keep if col in protected_gdf.columns]
 
 
 
 
68
  protected_gdf = protected_gdf[available_columns].copy()
69
-
70
  print(f"Columns retained: {', '.join(protected_gdf.columns.tolist())}")
71
  print("\nIUCN Category distribution:")
72
- print(protected_gdf['IUCN_CAT'].value_counts().sort_index())
73
-
74
  except Exception as e:
75
  print(f"Error loading protected areas: {e}")
76
  protected_gdf = None
77
 
 
 
 
 
78
  def format_value(value):
79
- """Format value for tooltip"""
80
- if pd.isna(value) or value == '' or value == 'None':
81
  return "N/A"
82
  return str(value)
83
 
 
84
  def create_legend_table():
85
- """Create a DataFrame for the IUCN category legend"""
86
- legend_data = []
87
- for cat, info in IUCN_CATEGORIES.items():
88
- legend_data.append({
89
- 'Category': cat,
90
- 'Name': info['name'],
91
- 'Primary Objective': info['description']
92
- })
93
- return pd.DataFrame(legend_data)
 
94
 
 
 
 
 
95
  def create_map():
96
- """Create the interactive map with protected areas using satellite style"""
97
-
98
  if protected_gdf is None:
99
  fig = go.Figure()
100
  fig.add_annotation(
101
  text="Error: Could not load data files",
102
  xref="paper", yref="paper",
103
- x=0.5, y=0.5, showarrow=False
104
  )
105
  return fig
106
-
107
- # Build customdata columns directly in the dataframe
108
- # so that px.choropleth_map keeps them aligned per-trace
109
- protected_gdf_plot = protected_gdf.copy()
110
- protected_gdf_plot['tooltip_name'] = protected_gdf_plot['NAME'].apply(format_value)
111
- protected_gdf_plot['tooltip_desig'] = protected_gdf_plot['DESIG'].apply(format_value)
112
- protected_gdf_plot['tooltip_desig_eng'] = protected_gdf_plot['DESIG_ENG'].apply(format_value)
113
- protected_gdf_plot['tooltip_desig_type'] = protected_gdf_plot['DESIG_TYPE'].apply(format_value)
114
- protected_gdf_plot['tooltip_iucn'] = protected_gdf_plot['IUCN_CAT'].apply(format_value)
115
-
116
- # Use hover_data to let Plotly handle per-trace customdata alignment
117
  fig = px.choropleth_map(
118
- protected_gdf_plot,
119
- geojson=protected_gdf_plot.geometry,
120
- locations=protected_gdf_plot.index,
121
- color='IUCN_CAT',
122
  color_discrete_sequence=px.colors.qualitative.Prism,
123
- custom_data=['tooltip_name', 'tooltip_desig', 'tooltip_desig_eng',
124
- 'tooltip_desig_type', 'tooltip_iucn'],
 
 
125
  map_style="satellite-streets",
126
  zoom=2,
127
  center={"lat": 60, "lon": 90},
128
  opacity=0.65,
129
- labels={'IUCN_CAT': 'IUCN Category'}
130
  )
131
-
132
- # Now update_traces is safe because Plotly already split customdata per trace
133
  fig.update_traces(
134
  hovertemplate=(
135
  "<b>Name:</b> %{customdata[0]}<br>"
@@ -139,84 +196,87 @@ def create_map():
139
  "<b>IUCN Category:</b> %{customdata[4]}"
140
  "<extra></extra>"
141
  ),
142
- marker_line_width=0
143
  )
144
-
145
  fig.update_layout(
146
  margin={"r": 0, "t": 0, "l": 0, "b": 0},
147
  height=800,
148
  legend=dict(
149
- title=dict(text='IUCN Category'),
150
- yanchor="top",
151
- y=0.99,
152
- xanchor="left",
153
- x=0.01,
154
- bgcolor='rgba(255, 255, 255, 0.9)',
155
- bordercolor='rgba(0, 0, 0, 0.2)',
156
- borderwidth=1
157
  ),
158
- uirevision='constant'
159
  )
160
-
161
  return fig
162
 
163
- # Build Gradio UI
 
 
 
164
  with gr.Blocks(title="Russia Protected Areas") as demo:
165
  gr.Markdown("# Protected Areas of Russia")
166
- gr.Markdown("Explore protected areas in Russia colored by their IUCN conservation category.")
167
-
 
 
 
168
  with gr.Row():
169
  map_plot = gr.Plot(label="Interactive Map", value=create_map())
170
-
171
  with gr.Row():
172
  gr.Markdown("## IUCN Protected Area Categories")
173
-
174
  with gr.Row():
175
  legend_table = gr.DataFrame(
176
  value=create_legend_table(),
177
  label="Category Definitions",
178
  interactive=False,
179
- wrap=True
180
  )
181
-
182
  # Statistics section
183
  if protected_gdf is not None:
184
  with gr.Row():
185
  gr.Markdown("## Statistics")
186
-
187
  with gr.Row():
188
  total_areas = len(protected_gdf)
189
-
190
  stats_data = []
191
- for cat in list(IUCN_CATEGORIES.keys()):
192
- count = len(protected_gdf[protected_gdf['IUCN_CAT'] == cat])
193
  if count > 0:
194
  percentage = (count / total_areas) * 100
195
- stats_data.append({
196
- 'IUCN Category': cat,
197
- 'Number of Areas': f"{count:,}",
198
- 'Percentage': f"{percentage:.1f}%"
199
- })
200
-
201
- stats_df = pd.DataFrame(stats_data)
202
-
203
  gr.DataFrame(
204
- value=stats_df,
205
  label=f"Distribution of {total_areas:,} Protected Areas",
206
- interactive=False
207
  )
208
-
209
- gr.Markdown("""
 
210
  ---
211
  ### Data Sources & Attribution
 
 
212
 
213
- **Protected Areas Data:** [Protected Planet - WDPA](https://www.protectedplanet.net/)
214
- **Administrative Boundaries:** Satellite map streets layer
215
  **IUCN Categories:** [IUCN Protected Area Categories System](https://www.iucn.org/theme/protected-areas/about/protected-area-categories)
216
-
217
- ---
218
- *Note: The map shows protected area polygons only. Points and lines from the WDPA dataset are not included.*
219
- """)
220
 
221
  if __name__ == "__main__":
222
  demo.launch()
 
3
  import plotly.express as px
4
  import plotly.graph_objects as go
5
  import pandas as pd
6
+ from shapely.geometry import MultiPolygon
7
 
8
+ # ──────────────────────────────────────────────────────────────
9
+ # IUCN Category reference information
10
+ # ──────────────────────────────────────────────────────────────
11
  IUCN_CATEGORIES = {
12
+ "Ia": {
13
+ "name": "Strict Nature Reserve",
14
+ "description": (
15
+ "Strictly protected for biodiversity, geological/geomorphological"
16
+ " features, and scientific research."
17
+ ),
18
  },
19
+ "Ib": {
20
+ "name": "Wilderness Area",
21
+ "description": (
22
+ "Large, unmodified areas retaining natural character, without"
23
+ " permanent habitation, managed to preserve natural condition."
24
+ ),
25
  },
26
+ "II": {
27
+ "name": "National Park",
28
+ "description": (
29
+ "Large natural/near-natural areas protecting large-scale ecological"
30
+ " processes and species, while allowing compatible spiritual/"
31
+ "recreational use."
32
+ ),
33
  },
34
+ "III": {
35
+ "name": "Natural Monument/Feature",
36
+ "description": (
37
+ "Set aside to protect a specific natural monument, landform, sea"
38
+ " mount, or geological feature."
39
+ ),
40
  },
41
+ "IV": {
42
+ "name": "Habitat/Species Management Area",
43
+ "description": (
44
+ "Protects particular species or habitats, often requiring regular,"
45
+ " active management interventions."
46
+ ),
47
+ },
48
+ "V": {
49
+ "name": "Protected Landscape/Seascape",
50
+ "description": (
51
+ "Protects areas where the interaction of people and nature over"
52
+ " time has produced a distinct character."
53
+ ),
54
  },
55
+ "VI": {
56
+ "name": "Sustainable Use Area",
57
+ "description": (
58
+ "Conserves ecosystems and habitats together with associated"
59
+ " cultural values and traditional natural resource management"
60
+ " systems."
61
+ ),
62
  },
63
+ "Not Applicable": {
64
+ "name": "Not Applicable",
65
+ "description": "IUCN category is not applicable to this protected area.",
66
  },
67
+ "Not Assigned": {
68
+ "name": "Not Assigned",
69
+ "description": "IUCN category has not been assigned.",
70
  },
71
+ "Not Reported": {
72
+ "name": "Not Reported",
73
+ "description": "IUCN category has not been reported.",
74
  },
 
 
 
 
75
  }
76
 
77
+
78
+ # ──────────────────────────────────────────────────────────────
79
+ # Helper: keep only the largest polygon from a MultiPolygon
80
+ # ──────────────────────────────────────────────────────────────
81
+ def largest_polygon(geom):
82
+ """Return the single largest polygon from a MultiPolygon."""
83
+ if isinstance(geom, MultiPolygon):
84
+ return max(geom.geoms, key=lambda g: g.area)
85
+ return geom
86
+
87
+
88
+ # ──────────────────────────────────────────────────────────────
89
  # Load data
90
+ # ───────────��──────────────────────────────────────────────────
91
  try:
92
+ protected_gdf = gpd.read_parquet(
93
+ "Russia_protected_areas_simplified.geoparquet"
94
+ )
95
  protected_gdf = protected_gdf.to_crs(epsg=4326)
96
  print(f"Loaded {len(protected_gdf)} protected areas")
97
+
98
+ # Extra safety: ensure no MultiPolygons remain at runtime
99
+ protected_gdf["geometry"] = protected_gdf.geometry.apply(largest_polygon)
100
+
101
+ # Normalise IUCN_CAT
102
+ if "IUCN_CAT" in protected_gdf.columns:
103
+ protected_gdf["IUCN_CAT"] = (
104
+ protected_gdf["IUCN_CAT"]
105
+ .fillna("Not Reported")
106
+ .astype(str)
107
+ .apply(lambda x: x if x in IUCN_CATEGORIES else "Not Reported")
108
  )
109
+
110
  # Keep only needed columns
111
+ columns_to_keep = [
112
+ "NAME", "DESIG", "DESIG_ENG", "DESIG_TYPE", "IUCN_CAT", "geometry",
113
+ ]
114
+ available_columns = [
115
+ c for c in columns_to_keep if c in protected_gdf.columns
116
+ ]
117
  protected_gdf = protected_gdf[available_columns].copy()
118
+
119
  print(f"Columns retained: {', '.join(protected_gdf.columns.tolist())}")
120
  print("\nIUCN Category distribution:")
121
+ print(protected_gdf["IUCN_CAT"].value_counts().sort_index())
122
+
123
  except Exception as e:
124
  print(f"Error loading protected areas: {e}")
125
  protected_gdf = None
126
 
127
+
128
+ # ──────────────────────────────────────────────────────────────
129
+ # Helpers
130
+ # ──────────────────────────────────────────────────────────────
131
  def format_value(value):
132
+ """Format a value for the hover tooltip."""
133
+ if pd.isna(value) or value in ("", "None"):
134
  return "N/A"
135
  return str(value)
136
 
137
+
138
  def create_legend_table():
139
+ """Return a DataFrame describing each IUCN category."""
140
+ rows = [
141
+ {
142
+ "Category": cat,
143
+ "Name": info["name"],
144
+ "Primary Objective": info["description"],
145
+ }
146
+ for cat, info in IUCN_CATEGORIES.items()
147
+ ]
148
+ return pd.DataFrame(rows)
149
 
150
+
151
+ # ──────────────────────────────────────────────────────────────
152
+ # Map
153
+ # ──────────────────────────────────────────────────────────────
154
  def create_map():
155
+ """Create the interactive Plotly choropleth map."""
156
+
157
  if protected_gdf is None:
158
  fig = go.Figure()
159
  fig.add_annotation(
160
  text="Error: Could not load data files",
161
  xref="paper", yref="paper",
162
+ x=0.5, y=0.5, showarrow=False,
163
  )
164
  return fig
165
+
166
+ plot_gdf = protected_gdf.copy()
167
+ plot_gdf["tooltip_name"] = plot_gdf["NAME"].apply(format_value)
168
+ plot_gdf["tooltip_desig"] = plot_gdf["DESIG"].apply(format_value)
169
+ plot_gdf["tooltip_desig_eng"] = plot_gdf["DESIG_ENG"].apply(format_value)
170
+ plot_gdf["tooltip_desig_type"] = plot_gdf["DESIG_TYPE"].apply(format_value)
171
+ plot_gdf["tooltip_iucn"] = plot_gdf["IUCN_CAT"].apply(format_value)
172
+
 
 
 
173
  fig = px.choropleth_map(
174
+ plot_gdf,
175
+ geojson=plot_gdf.geometry,
176
+ locations=plot_gdf.index,
177
+ color="IUCN_CAT",
178
  color_discrete_sequence=px.colors.qualitative.Prism,
179
+ custom_data=[
180
+ "tooltip_name", "tooltip_desig", "tooltip_desig_eng",
181
+ "tooltip_desig_type", "tooltip_iucn",
182
+ ],
183
  map_style="satellite-streets",
184
  zoom=2,
185
  center={"lat": 60, "lon": 90},
186
  opacity=0.65,
187
+ labels={"IUCN_CAT": "IUCN Category"},
188
  )
189
+
 
190
  fig.update_traces(
191
  hovertemplate=(
192
  "<b>Name:</b> %{customdata[0]}<br>"
 
196
  "<b>IUCN Category:</b> %{customdata[4]}"
197
  "<extra></extra>"
198
  ),
199
+ marker_line_width=0,
200
  )
201
+
202
  fig.update_layout(
203
  margin={"r": 0, "t": 0, "l": 0, "b": 0},
204
  height=800,
205
  legend=dict(
206
+ title=dict(text="IUCN Category"),
207
+ yanchor="top", y=0.99,
208
+ xanchor="left", x=0.01,
209
+ bgcolor="rgba(255, 255, 255, 0.9)",
210
+ bordercolor="rgba(0, 0, 0, 0.2)",
211
+ borderwidth=1,
 
 
212
  ),
213
+ uirevision="constant",
214
  )
215
+
216
  return fig
217
 
218
+
219
+ # ──────────────────────────────────────────────────────────────
220
+ # Gradio UI
221
+ # ──────────────────────────────────────────────────────────────
222
  with gr.Blocks(title="Russia Protected Areas") as demo:
223
  gr.Markdown("# Protected Areas of Russia")
224
+ gr.Markdown(
225
+ "Explore protected areas in Russia colored by their IUCN"
226
+ " conservation category."
227
+ )
228
+
229
  with gr.Row():
230
  map_plot = gr.Plot(label="Interactive Map", value=create_map())
231
+
232
  with gr.Row():
233
  gr.Markdown("## IUCN Protected Area Categories")
234
+
235
  with gr.Row():
236
  legend_table = gr.DataFrame(
237
  value=create_legend_table(),
238
  label="Category Definitions",
239
  interactive=False,
240
+ wrap=True,
241
  )
242
+
243
  # Statistics section
244
  if protected_gdf is not None:
245
  with gr.Row():
246
  gr.Markdown("## Statistics")
247
+
248
  with gr.Row():
249
  total_areas = len(protected_gdf)
 
250
  stats_data = []
251
+ for cat in IUCN_CATEGORIES:
252
+ count = len(protected_gdf[protected_gdf["IUCN_CAT"] == cat])
253
  if count > 0:
254
  percentage = (count / total_areas) * 100
255
+ stats_data.append(
256
+ {
257
+ "IUCN Category": cat,
258
+ "Number of Areas": f"{count:,}",
259
+ "Percentage": f"{percentage:.1f}%",
260
+ }
261
+ )
262
+
263
  gr.DataFrame(
264
+ value=pd.DataFrame(stats_data),
265
  label=f"Distribution of {total_areas:,} Protected Areas",
266
+ interactive=False,
267
  )
268
+
269
+ gr.Markdown(
270
+ """
271
  ---
272
  ### Data Sources & Attribution
273
+
274
+ **Protected Areas Data:** [Protected Planet - WDPA](https://www.protectedplanet.net/)
275
 
 
 
276
  **IUCN Categories:** [IUCN Protected Area Categories System](https://www.iucn.org/theme/protected-areas/about/protected-area-categories)
277
+
278
+ """
279
+ )
 
280
 
281
  if __name__ == "__main__":
282
  demo.launch()