rapsoj commited on
Commit
a6ec964
·
verified ·
1 Parent(s): cb63921

Modlarised app

Browse files
README.md CHANGED
@@ -9,4 +9,43 @@ app_file: app.py
9
  pinned: false
10
  license: mit
11
  short_description: Download and process hazard layers for Red Cross VCAs.
12
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  pinned: false
10
  license: mit
11
  short_description: Download and process hazard layers for Red Cross VCAs.
12
+ ---
13
+
14
+ # eVCA Hazard and Exposure Data Downloader 🌍
15
+
16
+ This Gradio app enables users to download GloFAS flood hazard rasters and relevant exposure data for vulnerability and capacity assessments (VCAs).
17
+
18
+ ## 🚀 Features
19
+
20
+ - Select a country and return period
21
+ - Fetch hazard rasters and exposure data
22
+ - CKAN and HOTOSM integrations
23
+ - Modular hazard support
24
+
25
+ ## 🧪 How to Use
26
+
27
+ 1. Select a country from the dropdown
28
+ 2. Choose a return period and hazard (e.g. Flood)
29
+ 3. Select subdivision if prompted
30
+ 4. Click **Generate Raster**
31
+ 5. Download your files below
32
+
33
+ ## 📁 Output
34
+
35
+ - `.tif` raster files (hazards)
36
+ - `.geojson` files (exposure layers)
37
+ - Bundled `.zip` for downloads
38
+
39
+ ## 🧩 Modular Design
40
+
41
+ - `hazards/`: Hazard-specific logic (flood, wildfire, etc.)
42
+ - `data/`: HOTOSM and CKAN fetchers
43
+ - `app.py`: UI and orchestration logic
44
+
45
+ ## 🛠 Run Locally
46
+
47
+ ```bash
48
+ git clone https://huggingface.co/spaces/your-username/evca
49
+ cd evca
50
+ pip install -r requirements.txt
51
+ python app.py
app.py CHANGED
@@ -1,289 +1,51 @@
1
  import gradio as gr
 
2
  import os
3
- import requests
4
- import math
5
- import geopandas as gpd
6
- import urllib.request
7
- import rasterio
8
- import rasterio.merge
9
- from rasterio.mask import mask
10
- from rasterio.transform import array_bounds
11
- from bs4 import BeautifulSoup
12
- from datetime import datetime
13
- import pandas as pd
14
 
 
 
 
 
15
 
16
- def has_associated_features(iso_code, url="https://gis.unhcr.org/arcgis/rest/services/core_v2/wrl_polbnd_adm1_a_unhcr/MapServer/0/query"):
17
- params = {
18
- "where": f"iso3 = '{iso_code}'",
19
- "outFields": "*",
20
- "outSR": "4326",
21
- "f": "json"
22
- }
23
- try:
24
- response = requests.get(url, params=params, timeout=10)
25
- if response.status_code == 200 and response.text.strip():
26
- data = response.json()
27
- if 'features' in data:
28
- return len(data['features']) > 0, data
29
- return False, None
30
- except Exception as e:
31
- return False, None
32
 
 
33
 
34
- def fetch_gis_subdivisions(iso_code, url="https://gis.unhcr.org/arcgis/rest/services/core_v2/wrl_polbnd_adm1_a_unhcr/MapServer/0/query"):
35
- params = {
36
- "where": f"iso3 = '{iso_code}'",
37
- "outFields": "iso3,gis_name",
38
- "returnGeometry": "false",
39
- "f": "json"
40
- }
41
- response = requests.get(url, params=params)
42
- if response.status_code == 200:
43
- data = response.json()
44
- if "features" in data:
45
- return [f["attributes"]["gis_name"] for f in data["features"]]
46
- return []
47
-
48
-
49
- def get_geometry(iso_code, gis_name=None, url="https://gis.unhcr.org/arcgis/rest/services/core_v2/wrl_polbnd_adm1_a_unhcr/MapServer/0/query"):
50
- if gis_name:
51
- params = {
52
- "where": f"iso3 = '{iso_code}' AND gis_name = '{gis_name}'",
53
- "outFields": "*",
54
- "outSR": "4326",
55
- "f": "geojson"
56
- }
57
- else:
58
- params = {
59
- "where": f"iso3 = '{iso_code}'",
60
- "outFields": "*",
61
- "outSR": "4326",
62
- "f": "geojson"
63
- }
64
- response = requests.get(url, params=params)
65
- if response.status_code == 200:
66
- geojson_data = response.json()
67
- if "features" in geojson_data and geojson_data["features"]:
68
- return gpd.GeoDataFrame.from_features(geojson_data["features"])
69
- return None
70
-
71
-
72
- def generate_raster(iso: str, rp: int, gis_name: str = None):
73
- iso = iso.upper()
74
- base_url = "https://gis.unhcr.org/arcgis/rest/services/core_v2/wrl_polbnd_adm1_a_unhcr/MapServer/0/query"
75
-
76
- has_features, _ = has_associated_features(iso, base_url)
77
- if has_features:
78
- gdf = get_geometry(iso, url=base_url)
79
- else:
80
- if not gis_name:
81
- return None, f"❌ Subdivision required for {iso}."
82
- gdf = get_geometry(iso, gis_name)
83
- if gdf is None or gdf.empty:
84
- return None, f"❌ No geometry found for {gis_name} in {iso}."
85
-
86
- bounds = gdf.total_bounds # left, bottom, right, top
87
-
88
- # --- Determine tiles to download ---
89
- top = math.ceil(bounds[3] / 10) * 10
90
- left = (int(bounds[0]) // 10) * 10
91
- right = math.ceil(bounds[2] / 10) * 10
92
- bottom = (int(bounds[1]) // 10) * 10
93
-
94
- url_base = f"https://jeodpp.jrc.ec.europa.eu/ftp/jrc-opendata/CEMS-GLOFAS/flood_hazard/RP{rp}/"
95
- page = requests.get(url_base)
96
- soup = BeautifulSoup(page.text, "html.parser")
97
- all_links = [a['href'] for a in soup.find_all('a') if a['href'].endswith('.tif')]
98
-
99
- tif_paths = []
100
- col = left
101
- while col < right:
102
- row = top
103
- while row > bottom:
104
- prefix = f"{'N' if row >= 0 else 'S'}{abs(row)}_{'E' if col >= 0 else 'W'}{abs(col)}"
105
- match = [l for l in all_links if prefix in l]
106
- if match:
107
- tif_paths.append(match[0])
108
- row -= 10
109
- col += 10
110
-
111
- if not tif_paths:
112
- return None, "❌ No tiles found for selected region."
113
-
114
- # --- Download and merge ---
115
- local_paths = []
116
- for tif in tif_paths:
117
- remote = url_base + tif
118
- local = f"/tmp/{tif}"
119
- urllib.request.urlretrieve(remote, local)
120
- local_paths.append(local)
121
-
122
- sources = [rasterio.open(p) for p in local_paths]
123
-
124
- mosaic, merge_transform = rasterio.merge.merge(sources)
125
-
126
- temp_merged = "/tmp/temp_merged.tif"
127
- temp_meta = sources[0].meta.copy()
128
- temp_meta.update({
129
- "driver": "GTiff",
130
- "height": mosaic.shape[1],
131
- "width": mosaic.shape[2],
132
- "transform": merge_transform
133
- })
134
-
135
- with rasterio.open(temp_merged, "w", **temp_meta) as temp_dst:
136
- temp_dst.write(mosaic)
137
-
138
- # --- Crop to geometry ---
139
- geoms = [feature["geometry"] for feature in gdf.__geo_interface__["features"]]
140
- with rasterio.open(temp_merged) as src:
141
- out_image, out_transform = mask(src, geoms, crop=True, nodata=-9999)
142
- out_meta = src.meta.copy()
143
- out_meta.update({
144
- "driver": "GTiff",
145
- "height": out_image.shape[1],
146
- "width": out_image.shape[2],
147
- "transform": out_transform,
148
- "nodata": -9999
149
- })
150
-
151
- timestamp = datetime.utcnow().strftime("%Y%m%d%H%M%S")
152
- output_path = f"./merged_raster_{iso}_{rp}_{timestamp}.tif"
153
-
154
- with rasterio.open(output_path, "w", **out_meta) as dest:
155
- dest.write(out_image)
156
-
157
- # Cleanup
158
- for src in sources:
159
- src.close()
160
- for f in local_paths:
161
- os.remove(f)
162
- if os.path.exists(temp_merged):
163
- os.remove(temp_merged)
164
-
165
- return output_path, f"✅ Raster generated for {iso} (RP {rp}). Click to download below."
166
-
167
-
168
- def download_hotosm_data(iso3: str, feature_type: str = "populated_places", geometry: str = "points", save_dir: str = "./"):
169
- """
170
- Download HOTOSM data (e.g., populated_places, roads, etc.) as SHP zip.
171
-
172
- Args:
173
- iso3 (str): ISO3 country code (e.g., 'SSD').
174
- feature_type (str): HOTOSM feature type (e.g., 'populated_places', 'roads').
175
- geometry (str): Geometry type ('points', 'lines', 'polygons').
176
- save_dir (str): Directory to save the downloaded file.
177
-
178
- Returns:
179
- str or None: Path to saved file, or None if failed.
180
- """
181
- iso3_upper = iso3.upper()
182
- iso3_lower = iso3.lower()
183
-
184
- # Build filename
185
- filename = f"hotosm_{iso3_lower}_{feature_type}_{geometry}_shp.zip"
186
- url = f"https://s3.dualstack.us-east-1.amazonaws.com/production-raw-data-api/ISO3/{iso3_upper}/{feature_type}/{geometry}/{filename}"
187
- local_filename = os.path.join(save_dir, filename)
188
-
189
- try:
190
- response = requests.get(url, stream=True)
191
- if response.status_code != 200:
192
- print(f"[{iso3_upper}] Failed to download HOTOSM {feature_type}/{geometry}. HTTP {response.status_code}")
193
- return None
194
-
195
- os.makedirs(save_dir, exist_ok=True)
196
- with open(local_filename, 'wb') as f:
197
- for chunk in response.iter_content(chunk_size=8192):
198
- f.write(chunk)
199
-
200
- print(f"[{iso3_upper}] Downloaded: {local_filename}")
201
- return local_filename
202
-
203
- except Exception as e:
204
- print(f"[{iso3_upper}] Error: {e}")
205
- return None
206
-
207
-
208
- def download_resource_ckan(iso3_code, dataset_prefix="cod-ab", download_dir="downloads"):
209
- iso3 = iso3_code.lower()
210
- dataset_id = f"{dataset_prefix}-{iso3}"
211
- api_url = f"https://data.humdata.org/api/3/action/package_show?id={dataset_id}"
212
- headers = {'User-Agent': 'Mozilla/5.0'}
213
-
214
- # Match logic
215
- if dataset_prefix == "cod-ps":
216
- keyword_match = lambda name: name.startswith(f"{iso3}_admpop_adm") and name.endswith(".csv")
217
- else:
218
- keyword_match = lambda name: "_SHP.zip" in name
219
-
220
- try:
221
- response = requests.get(api_url, headers=headers)
222
- if response.status_code != 200:
223
- print(f"[{iso3.upper()}] Dataset not found (HTTP {response.status_code}): {dataset_id}")
224
- return []
225
-
226
- data = response.json()
227
- if not data.get("success") or "result" not in data:
228
- print(f"[{iso3.upper()}] API returned malformed response.")
229
- return []
230
-
231
- # Find and download all matching resources
232
- os.makedirs(download_dir, exist_ok=True)
233
- downloaded_files = []
234
-
235
- for resource in data["result"].get("resources", []):
236
- name = resource.get("name", "")
237
- if keyword_match(name):
238
- url = resource.get("url")
239
- if not url:
240
- continue
241
-
242
- filepath = os.path.join(download_dir, name)
243
- try:
244
- with requests.get(url, stream=True) as r:
245
- r.raise_for_status()
246
- with open(filepath, 'wb') as f:
247
- for chunk in r.iter_content(chunk_size=8192):
248
- f.write(chunk)
249
- print(f"[{iso3.upper()}] Downloaded: {filepath}")
250
- downloaded_files.append(filepath)
251
- except Exception as e:
252
- print(f"[{iso3.upper()}] Failed to download {name}: {e}")
253
-
254
- if not downloaded_files:
255
- print(f"[{iso3.upper()}] No matching files found in dataset '{dataset_id}'.")
256
-
257
- return downloaded_files
258
-
259
- except Exception as e:
260
- print(f"[{iso3.upper()}] Error: {e}")
261
- return []
262
-
263
-
264
- # Selection options
265
- iso_options = [('Abyei', 'XAB'), ('Afghanistan', 'AFG'), ('Aksai Chin (Sovereignty unsettled)', 'XAC'), ('Aland Islands (FIN)', 'ALA'), ('Albania', 'ALB'), ('Algeria', 'DZA'), ('American Samoa', 'ASM'), ('Andorra', 'AND'), ('Angola', 'AGO'), ('Anguilla (GBR)', 'AIA'), ('Antarctica', 'ATA'), ('Antigua and Barbuda', 'ATG'), ('Argentina', 'ARG'), ('Armenia', 'ARM'), ('Aruba (K. of the Netherlands)', 'ABW'), ('Arunachal Pradesh (IND)', 'XAP'), ('Australia', 'AUS'), ('Austria', 'AUT'), ('Azerbaijan', 'AZE'), ('Bahamas', 'BHS'), ('Bahrain', 'BHR'), ('Bangladesh', 'BGD'), ('Barbados', 'BRB'), ('Belarus', 'BLR'), ('Belgium', 'BEL'), ('Belize', 'BLZ'), ('Benin', 'BEN'), ('Bermuda', 'BMU'), ('Bhutan', 'BTN'), ('Bolivarian Republic of Venezuela', 'VEN'), ('Bonaire Sint Eustatius and Saba', 'BES'), ('Bosnia and Herzegovina', 'BIH'), ('Botswana', 'BWA'), ('Bouvet Island', 'BVT'), ('Brazil', 'BRA'), ('British Indian Ocean Territory', 'IOT'), ('British Virgin Islands (GBR)', 'VGB'), ('Brunei Darussalam', 'BRN'), ('Bulgaria', 'BGR'), ('Burkina Faso', 'BFA'), ('Burundi', 'BDI'), ('Cambodia', 'KHM'), ('Cameroon', 'CMR'), ('Canada', 'CAN'), ('Cape Verde', 'CPV'), ('Cayman Islands (GBR)', 'CYM'), ('Central African Republic', 'CAF'), ('Chile', 'CHL'), ('China', 'CHN'), ('China/India (Sovereignty unsettled)', 'XCI'), ('Christmas Island (AUS)', 'CXR'), ('Cocos (Keeling) Islands', 'CCK'), ('Colombia', 'COL'), ('Comoros', 'COM'), ('Cook Islands', 'COK'), ('Costa Rica', 'CRI'), ('Croatia', 'HRV'), ('Cuba', 'CUB'), ('Curacao (K. of Netherlands)', 'CUW'), ('Cyprus', 'CYP'), ('Czech Republic', 'CZE'), ("Côte d'Ivoire", 'CIV'), ("Democratic Poeple's Rep. of Korea", 'PRK'), ('Democratic Republic of the Congo', 'COD'), ('Denmark', 'DNK'), ('Djibouti', 'DJI'), ('Dominica', 'DMA'), ('Dominican Republic', 'DOM'), ('Ecuador', 'ECU'), ('Egypt', 'EGY'), ('El Salvador', 'SLV'), ('Equatorial Guinea', 'GNQ'), ('Eritrea', 'ERI'), ('Estonia', 'EST'), ('Eswatini', 'SWZ'), ('Ethiopia', 'ETH'), ('Falkland Islands (Malvinas)', 'FLK'), ('Faroe Islands (DNK)', 'FRO'), ('Federated States of Micronesia', 'FSM'), ('Fiji', 'FJI'), ('Finland', 'FIN'), ('France', 'FRA'), ('French Guiana (FRA)', 'GUF'), ('French New Caledonia (FRA)', 'NCL'), ('French Polynesia', 'PYF'), ('French Southern and Antarctic Territories', 'ATF'), ('Gabon', 'GAB'), ('Gambia', 'GMB'), ('Georgia', 'GEO'), ('Germany', 'DEU'), ('Ghana', 'GHA'), ('Gibraltar', 'GIB'), ('Greece', 'GRC'), ('Greenland (DNK)', 'GRL'), ('Grenada', 'GRD'), ('Guadeloupe (FRA)', 'GLP'), ('Guam', 'GUM'), ('Guatemala', 'GTM'), ('Guernsey (GBR)', 'GGY'), ('Guinea', 'GIN'), ('Guinea-Bissau', 'GNB'), ('Guyana', 'GUY'), ('Haiti', 'HTI'), ("Hala'ib triangle (SDN)", 'XHT'), ('Heard Island and McDonald Islands (AUS)', 'HMD'), ('Holy See', 'VAT'), ('Honduras', 'HND'), ('Hong Kong', 'HKG'), ('Hungary', 'HUN'), ('Iceland', 'ISL'), ('Ilemi Triangle (SSD)', 'XIT'), ('India', 'IND'), ('Indonesia', 'IDN'), ('Iraq', 'IRQ'), ('Ireland', 'IRL'), ('Islamic Republic of Iran', 'IRN'), ('Isle of Man', 'IMN'), ('Israel', 'ISR'), ('Italy', 'ITA'), ('Jamaica', 'JAM'), ('Jammu and Kashmir', 'XJK'), ('Jan Mayen Island (NOR)', 'SJM'), ('Japan', 'JPN'), ('Jersey (GBR)', 'JEY'), ('Johnston Atoll', 'JTN'), ('Jordan', 'JOR'), ('Kazakhstan', 'KAZ'), ('Kenya', 'KEN'), ('Kiribati', 'KIR'), ('Kosovo (SRB)', 'KOS'), ('Kuril Islands (RUS)', 'XKI'), ('Kuwait', 'KWT'), ('Kyrgyzstan', 'KGZ'), ("Lao People's Democratic Republic", 'LAO'), ('Latvia', 'LVA'), ('Lebanon', 'LBN'), ('Lesotho', 'LSO'), ('Liberia', 'LBR'), ('Libya', 'LBY'), ('Liechtenstein', 'LIE'), ('Lithuania', 'LTU'), ('Luxembourg', 'LUX'), ("Ma'tan al-Sarra (EGY)", 'XMS'), ('Macao (CHN)', 'MAC'), ('Madagascar', 'MDG'), ('Malawi', 'MWI'), ('Malaysia', 'MYS'), ('Maldives', 'MDV'), ('Mali', 'MLI'), ('Malta', 'MLT'), ('Marshall Islands', 'MHL'), ('Martinique (FRA)', 'MTQ'), ('Mauritania', 'MRT'), ('Mauritius', 'MUS'), ('Mayotte (FRA)', 'MYT'), ('Mexico', 'MEX'), ('Midway Islands', 'MID'), ('Monaco', 'MCO'), ('Mongolia', 'MNG'), ('Montenegro', 'MNE'), ('Montserrat', 'MSR'), ('Morocco', 'MAR'), ('Mozambique', 'MOZ'), ('Myanmar', 'MMR'), ('Namibia', 'NAM'), ('Nauru', 'NRU'), ('Nepal', 'NPL'), ('Netherlands (Kingdom of the)', 'NLD'), ('New Zealand', 'NZL'), ('Nicaragua', 'NIC'), ('Niger', 'NER'), ('Nigeria', 'NGA'), ('Niue', 'NIU'), ('No code (ISO user specified)', 'AAA'), ('Norfolk Island (AUS)', 'NFK'), ('North Macedonia', 'MKD'), ('Northern Mariana Islands (USA)', 'MNP'), ('Norway', 'NOR'), ('Oman', 'OMN'), ('Pakistan', 'PAK'), ('Palau', 'PLW'), ('Panama', 'PAN'), ('Papua New Guinea', 'PNG'), ('Paracel Islands (Sovereignty unsettled)', 'XPI'), ('Paraguay', 'PRY'), ('Peru', 'PER'), ('Philippines', 'PHL'), ('Pitcairn Islands', 'PCN'), ('Plurinational State of Bolivia', 'BOL'), ('Poland', 'POL'), ('Portugal', 'PRT'), ('Puerto Rico', 'PRI'), ('Qatar', 'QAT'), ('Rep. of Chad', 'TCD'), ('Republic of Korea', 'KOR'), ('Republic of Moldova', 'MDA'), ('Republic of the Congo', 'COG'), ('Reunion (FRA)', 'REU'), ('Romania', 'ROU'), ('Russian Federation', 'RUS'), ('Rwanda', 'RWA'), ('Saint Barthelemy (FRA)', 'BLM'), ('Saint Helena', 'SHN'), ('Saint Kitts and Nevis', 'KNA'), ('Saint Lucia', 'LCA'), ('Saint Martin (FRA)', 'MAF'), ('Saint Pierre et Miquelon (FRA)', 'SPM'), ('Saint Vincent and the Grenadines', 'VCT'), ('Samoa', 'WSM'), ('San Marino', 'SMR'), ('Sao Tome and Principe', 'STP'), ('Sark (GBR)', 'XSA'), ('Saudi Arabia', 'SAU'), ('Scarborough Reef (Sovereignty unsettled)', 'XSR'), ('Senegal', 'SEN'), ('Senkaku Islands (Sovereignty unsettled)', 'XSI'), ('Serbia', 'SRB'), ('Seychelles', 'SYC'), ('Sierra Leone', 'SLE'), ('Singapore', 'SGP'), ('Sint Maarten (K. of Netherlands)', 'SXM'), ('Slovakia', 'SVK'), ('Slovenia', 'SVN'), ('Solomon Islands', 'SLB'), ('Somalia', 'SOM'), ('South Africa', 'ZAF'), ('South Georgia and the South Sandwich Islands (GBR)', 'SGS'), ('South Sudan', 'SSD'), ('Spain', 'ESP'), ('Spratly Islands (Sovereignty unsettled)', 'XSP'), ('Sri Lanka', 'LKA'), ('State of Palestine', 'PSE'), ('Sudan', 'SDN'), ('Suriname', 'SUR'), ('Sweden', 'SWE'), ('Switzerland', 'CHE'), ('Syrian Arab Republic', 'SYR'), ('Taiwan (CHN)', 'TWN'), ('Tajikistan', 'TJK'), ('Thailand', 'THA'), ('Timor-Leste', 'TLS'), ('Togo', 'TGO'), ('Tokelau', 'TKL'), ('Tonga', 'TON'), ('Trinidad and Tobago', 'TTO'), ('Tunisia', 'TUN'), ('Turkey', 'TUR'), ('Turkmenistan', 'TKM'), ('Turks and Caicos Islands (GBR)', 'TCA'), ('Tuvalu', 'TUV'), ('Uganda', 'UGA'), ('Ukraine', 'UKR'), ('United Arab Emirates', 'ARE'), ('United Kingdom of Great Britain and Northern Ireland', 'GBR'), ('United Republic of Tanzania', 'TZA'), ('United States Minor Outlying Islands', 'UMI'), ('United States Virgin Islands (USA)', 'VIR'), ('United States of America', 'USA'), ('Uruguay', 'URY'), ('Uzbekistan', 'UZB'), ('Vanuatu', 'VUT'), ('Viet Nam', 'VNM'), ('Wake Island', 'WAK'), ('Wallis and Futuna', 'WLF'), ('Western Sahara', 'ESH'), ('Yemen', 'YEM'), ('Zambia', 'ZMB'), ('Zimbabwe', 'ZWE')]
266
- rp_options = [10, 20, 50, 75, 100, 200, 500]
267
- subdivision_input = gr.Dropdown(choices=[], label="Subdivision", interactive=True, visible=False)
268
-
269
- with gr.Blocks(title="Flood Raster Downloader") as demo:
270
- gr.Markdown("## eVCA Hazard and Exposure Data Downloader\nSelect a country and return period to generate the GloFAS flood hazard raster for that region and supporting exposure data.")
271
 
272
  with gr.Row():
 
273
  iso_input = gr.Dropdown(choices=iso_options, label="Country", interactive=True, value=None)
274
  rp_input = gr.Dropdown(choices=rp_options, label="Return Period (years)", value=10)
275
-
276
- # Subdivision dropdown, initially hidden
277
  subdivision_input = gr.Dropdown(choices=[], label="Subdivision", interactive=True, visible=False)
278
 
279
  generate_btn = gr.Button("Generate Raster", visible=False)
280
  status_text = gr.Markdown("")
281
  file_output = gr.Files(label="Downloads", interactive=False)
282
 
283
- def wrapper(iso, rp, subdivision):
284
- raster_path, msg = generate_raster(iso, rp, subdivision)
 
 
 
 
 
285
 
286
- iso3 = iso.split(",")[-1].strip() if "," in iso else iso # Handle format like "South Sudan, SSD"
 
 
 
287
 
288
  # HOTOSM Downloads
289
  pp_path = download_hotosm_data(iso3, feature_type="populated_places", geometry="points")
@@ -306,7 +68,7 @@ with gr.Blocks(title="Flood Raster Downloader") as demo:
306
  if admin_path:
307
  files.extend(admin_path)
308
  if pop_paths:
309
- files.extend(pop_paths) # not append!
310
  if not files:
311
  msg += " ⚠️ No data could be downloaded."
312
 
@@ -325,14 +87,9 @@ with gr.Blocks(title="Flood Raster Downloader") as demo:
325
  return (
326
  gr.update(visible=False),
327
  gr.update(visible=True),
328
- gr.update(value="") # Clear status if subdivisions aren't needed
329
  )
330
 
331
- def on_subdivision_selected(subdivision):
332
- if subdivision:
333
- return gr.update(visible=True), gr.update(visible=True)
334
- return gr.update(visible=False), gr.update(visible=False)
335
-
336
  iso_input.change(
337
  fn=lambda iso: gr.update(value="🔄 Loading subdivisions..."),
338
  inputs=iso_input,
@@ -351,7 +108,7 @@ with gr.Blocks(title="Flood Raster Downloader") as demo:
351
 
352
  generate_btn.click(
353
  fn=wrapper,
354
- inputs=[iso_input, rp_input, subdivision_input],
355
  outputs=[file_output, status_text]
356
  )
357
 
 
1
  import gradio as gr
2
+ import importlib
3
  import os
 
 
 
 
 
 
 
 
 
 
 
4
 
5
+ from data.options import iso_options, rp_options
6
+ from data.geometry import fetch_gis_subdivisions, has_associated_features
7
+ from data.hotosm import download_hotosm_data
8
+ from data.ckan import download_resource_ckan
9
 
10
+ # Discover available hazard modules dynamically
11
+ def get_available_hazards():
12
+ hazard_dir = "hazards"
13
+ files = os.listdir(hazard_dir)
14
+ hazards = [
15
+ f.replace(".py", "") for f in files
16
+ if f.endswith(".py") and f != "__init__.py"
17
+ ]
18
+ return sorted([(h.capitalize(), h) for h in hazards])
 
 
 
 
 
 
 
19
 
20
+ hazard_options = get_available_hazards()
21
 
22
+ # Gradio app
23
+ with gr.Blocks(title="Hazard Raster Downloader") as demo:
24
+ gr.Markdown("## eVCA Hazard and Exposure Data Downloader\nSelect a country, hazard, and return period to generate hazard rasters and supporting exposure data.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
  with gr.Row():
27
+ hazard_input = gr.Dropdown(choices=hazard_options, label="Hazard", value="flood", interactive=True)
28
  iso_input = gr.Dropdown(choices=iso_options, label="Country", interactive=True, value=None)
29
  rp_input = gr.Dropdown(choices=rp_options, label="Return Period (years)", value=10)
30
+
 
31
  subdivision_input = gr.Dropdown(choices=[], label="Subdivision", interactive=True, visible=False)
32
 
33
  generate_btn = gr.Button("Generate Raster", visible=False)
34
  status_text = gr.Markdown("")
35
  file_output = gr.Files(label="Downloads", interactive=False)
36
 
37
+ def wrapper(hazard, iso, rp, subdivision):
38
+ # Dynamic import of selected hazard module
39
+ try:
40
+ hazard_module = importlib.import_module(f"hazards.{hazard}")
41
+ generate_func = getattr(hazard_module, f"generate_{hazard}_raster")
42
+ except (ModuleNotFoundError, AttributeError):
43
+ return [], f"❌ Error: No raster generation function found for hazard '{hazard}'."
44
 
45
+ # Generate raster
46
+ raster_path, msg = generate_func(iso, rp, subdivision)
47
+
48
+ iso3 = iso.split(",")[-1].strip() if "," in iso else iso
49
 
50
  # HOTOSM Downloads
51
  pp_path = download_hotosm_data(iso3, feature_type="populated_places", geometry="points")
 
68
  if admin_path:
69
  files.extend(admin_path)
70
  if pop_paths:
71
+ files.extend(pop_paths)
72
  if not files:
73
  msg += " ⚠️ No data could be downloaded."
74
 
 
87
  return (
88
  gr.update(visible=False),
89
  gr.update(visible=True),
90
+ gr.update(value="")
91
  )
92
 
 
 
 
 
 
93
  iso_input.change(
94
  fn=lambda iso: gr.update(value="🔄 Loading subdivisions..."),
95
  inputs=iso_input,
 
108
 
109
  generate_btn.click(
110
  fn=wrapper,
111
+ inputs=[hazard_input, iso_input, rp_input, subdivision_input],
112
  outputs=[file_output, status_text]
113
  )
114
 
config.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from hazards.flood import generate_flood_raster
2
+
3
+ class HazardSource:
4
+ def __init__(self, name, rp_options, generate_fn, data_layers):
5
+ self.name = name
6
+ self.rp_options = rp_options
7
+ self.generate_fn = generate_fn
8
+ self.data_layers = data_layers
9
+
10
+ hazard_registry = {
11
+ "Flood": HazardSource(
12
+ name="Flood",
13
+ rp_options=[10, 20, 50, 75, 100, 200, 500],
14
+ generate_fn=generate_flood_raster,
15
+ data_layers=["populated_places", "roads", "buildings"]
16
+ ),
17
+ # Future: "Drought": HazardSource(...)
18
+ }
data/__pycache__/ckan.cpython-311.pyc ADDED
Binary file (4.19 kB). View file
 
data/__pycache__/geometry.cpython-311.pyc ADDED
Binary file (2.71 kB). View file
 
data/__pycache__/hotosm.cpython-311.pyc ADDED
Binary file (2.66 kB). View file
 
data/__pycache__/options.cpython-311.pyc ADDED
Binary file (6.16 kB). View file
 
data/ckan.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import requests
3
+
4
+ def download_resource_ckan(iso3_code, dataset_prefix="cod-ab", download_dir="downloads"):
5
+ iso3 = iso3_code.lower()
6
+ dataset_id = f"{dataset_prefix}-{iso3}"
7
+ api_url = f"https://data.humdata.org/api/3/action/package_show?id={dataset_id}"
8
+ headers = {'User-Agent': 'Mozilla/5.0'}
9
+
10
+ # Match logic
11
+ if dataset_prefix == "cod-ps":
12
+ keyword_match = lambda name: name.startswith(f"{iso3}_admpop_adm") and name.endswith(".csv")
13
+ else:
14
+ keyword_match = lambda name: "_SHP.zip" in name
15
+
16
+ try:
17
+ response = requests.get(api_url, headers=headers)
18
+ if response.status_code != 200:
19
+ print(f"[{iso3.upper()}] Dataset not found (HTTP {response.status_code}): {dataset_id}")
20
+ return []
21
+
22
+ data = response.json()
23
+ if not data.get("success") or "result" not in data:
24
+ print(f"[{iso3.upper()}] API returned malformed response.")
25
+ return []
26
+
27
+ # Find and download all matching resources
28
+ os.makedirs(download_dir, exist_ok=True)
29
+ downloaded_files = []
30
+
31
+ for resource in data["result"].get("resources", []):
32
+ name = resource.get("name", "")
33
+ if keyword_match(name):
34
+ url = resource.get("url")
35
+ if not url:
36
+ continue
37
+
38
+ filepath = os.path.join(download_dir, name)
39
+ try:
40
+ with requests.get(url, stream=True) as r:
41
+ r.raise_for_status()
42
+ with open(filepath, 'wb') as f:
43
+ for chunk in r.iter_content(chunk_size=8192):
44
+ f.write(chunk)
45
+ print(f"[{iso3.upper()}] Downloaded: {filepath}")
46
+ downloaded_files.append(filepath)
47
+ except Exception as e:
48
+ print(f"[{iso3.upper()}] Failed to download {name}: {e}")
49
+
50
+ if not downloaded_files:
51
+ print(f"[{iso3.upper()}] No matching files found in dataset '{dataset_id}'.")
52
+
53
+ return downloaded_files
54
+
55
+ except Exception as e:
56
+ print(f"[{iso3.upper()}] Error: {e}")
57
+ return []
data/geometry.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import geopandas as gpd
3
+
4
+ def has_associated_features(iso_code, url="https://gis.unhcr.org/arcgis/rest/services/core_v2/wrl_polbnd_adm1_a_unhcr/MapServer/0/query"):
5
+ params = {
6
+ "where": f"iso3 = '{iso_code}'",
7
+ "outFields": "*",
8
+ "outSR": "4326",
9
+ "f": "json"
10
+ }
11
+ try:
12
+ response = requests.get(url, params=params, timeout=10)
13
+ if response.status_code == 200 and response.text.strip():
14
+ data = response.json()
15
+ if 'features' in data:
16
+ return len(data['features']) > 0, data
17
+ return False, None
18
+ except Exception as e:
19
+ return False, None
20
+
21
+
22
+ def fetch_gis_subdivisions(iso_code, url="https://gis.unhcr.org/arcgis/rest/services/core_v2/wrl_polbnd_adm1_a_unhcr/MapServer/0/query"):
23
+ params = {
24
+ "where": f"iso3 = '{iso_code}'",
25
+ "outFields": "iso3,gis_name",
26
+ "returnGeometry": "false",
27
+ "f": "json"
28
+ }
29
+ response = requests.get(url, params=params)
30
+ if response.status_code == 200:
31
+ data = response.json()
32
+ if "features" in data:
33
+ return [f["attributes"]["gis_name"] for f in data["features"]]
34
+ return []
35
+
36
+
37
+ def get_geometry(iso_code, gis_name=None, url="https://gis.unhcr.org/arcgis/rest/services/core_v2/wrl_polbnd_adm1_a_unhcr/MapServer/0/query"):
38
+ if gis_name:
39
+ params = {
40
+ "where": f"iso3 = '{iso_code}' AND gis_name = '{gis_name}'",
41
+ "outFields": "*",
42
+ "outSR": "4326",
43
+ "f": "geojson"
44
+ }
45
+ else:
46
+ params = {
47
+ "where": f"iso3 = '{iso_code}'",
48
+ "outFields": "*",
49
+ "outSR": "4326",
50
+ "f": "geojson"
51
+ }
52
+ response = requests.get(url, params=params)
53
+ if response.status_code == 200:
54
+ geojson_data = response.json()
55
+ if "features" in geojson_data and geojson_data["features"]:
56
+ return gpd.GeoDataFrame.from_features(geojson_data["features"])
57
+ return None
data/hotosm.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import requests
3
+
4
+ def download_hotosm_data(iso3: str, feature_type: str = "populated_places", geometry: str = "points", save_dir: str = "./"):
5
+ """
6
+ Download HOTOSM data (e.g., populated_places, roads, etc.) as SHP zip.
7
+
8
+ Args:
9
+ iso3 (str): ISO3 country code (e.g., 'SSD').
10
+ feature_type (str): HOTOSM feature type (e.g., 'populated_places', 'roads').
11
+ geometry (str): Geometry type ('points', 'lines', 'polygons').
12
+ save_dir (str): Directory to save the downloaded file.
13
+
14
+ Returns:
15
+ str or None: Path to saved file, or None if failed.
16
+ """
17
+ iso3_upper = iso3.upper()
18
+ iso3_lower = iso3.lower()
19
+
20
+ # Build filename
21
+ filename = f"hotosm_{iso3_lower}_{feature_type}_{geometry}_shp.zip"
22
+ url = f"https://s3.dualstack.us-east-1.amazonaws.com/production-raw-data-api/ISO3/{iso3_upper}/{feature_type}/{geometry}/{filename}"
23
+ local_filename = os.path.join(save_dir, filename)
24
+
25
+ try:
26
+ response = requests.get(url, stream=True)
27
+ if response.status_code != 200:
28
+ print(f"[{iso3_upper}] Failed to download HOTOSM {feature_type}/{geometry}. HTTP {response.status_code}")
29
+ return None
30
+
31
+ os.makedirs(save_dir, exist_ok=True)
32
+ with open(local_filename, 'wb') as f:
33
+ for chunk in response.iter_content(chunk_size=8192):
34
+ f.write(chunk)
35
+
36
+ print(f"[{iso3_upper}] Downloaded: {local_filename}")
37
+ return local_filename
38
+
39
+ except Exception as e:
40
+ print(f"[{iso3_upper}] Error: {e}")
41
+ return None
data/options.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ iso_options = [('Abyei', 'XAB'), ('Afghanistan', 'AFG'), ('Aksai Chin (Sovereignty unsettled)', 'XAC'), ('Aland Islands (FIN)', 'ALA'), ('Albania', 'ALB'), ('Algeria', 'DZA'), ('American Samoa', 'ASM'), ('Andorra', 'AND'), ('Angola', 'AGO'), ('Anguilla (GBR)', 'AIA'), ('Antarctica', 'ATA'), ('Antigua and Barbuda', 'ATG'), ('Argentina', 'ARG'), ('Armenia', 'ARM'), ('Aruba (K. of the Netherlands)', 'ABW'), ('Arunachal Pradesh (IND)', 'XAP'), ('Australia', 'AUS'), ('Austria', 'AUT'), ('Azerbaijan', 'AZE'), ('Bahamas', 'BHS'), ('Bahrain', 'BHR'), ('Bangladesh', 'BGD'), ('Barbados', 'BRB'), ('Belarus', 'BLR'), ('Belgium', 'BEL'), ('Belize', 'BLZ'), ('Benin', 'BEN'), ('Bermuda', 'BMU'), ('Bhutan', 'BTN'), ('Bolivarian Republic of Venezuela', 'VEN'), ('Bonaire Sint Eustatius and Saba', 'BES'), ('Bosnia and Herzegovina', 'BIH'), ('Botswana', 'BWA'), ('Bouvet Island', 'BVT'), ('Brazil', 'BRA'), ('British Indian Ocean Territory', 'IOT'), ('British Virgin Islands (GBR)', 'VGB'), ('Brunei Darussalam', 'BRN'), ('Bulgaria', 'BGR'), ('Burkina Faso', 'BFA'), ('Burundi', 'BDI'), ('Cambodia', 'KHM'), ('Cameroon', 'CMR'), ('Canada', 'CAN'), ('Cape Verde', 'CPV'), ('Cayman Islands (GBR)', 'CYM'), ('Central African Republic', 'CAF'), ('Chile', 'CHL'), ('China', 'CHN'), ('China/India (Sovereignty unsettled)', 'XCI'), ('Christmas Island (AUS)', 'CXR'), ('Cocos (Keeling) Islands', 'CCK'), ('Colombia', 'COL'), ('Comoros', 'COM'), ('Cook Islands', 'COK'), ('Costa Rica', 'CRI'), ('Croatia', 'HRV'), ('Cuba', 'CUB'), ('Curacao (K. of Netherlands)', 'CUW'), ('Cyprus', 'CYP'), ('Czech Republic', 'CZE'), ("Côte d'Ivoire", 'CIV'), ("Democratic Poeple's Rep. of Korea", 'PRK'), ('Democratic Republic of the Congo', 'COD'), ('Denmark', 'DNK'), ('Djibouti', 'DJI'), ('Dominica', 'DMA'), ('Dominican Republic', 'DOM'), ('Ecuador', 'ECU'), ('Egypt', 'EGY'), ('El Salvador', 'SLV'), ('Equatorial Guinea', 'GNQ'), ('Eritrea', 'ERI'), ('Estonia', 'EST'), ('Eswatini', 'SWZ'), ('Ethiopia', 'ETH'), ('Falkland Islands (Malvinas)', 'FLK'), ('Faroe Islands (DNK)', 'FRO'), ('Federated States of Micronesia', 'FSM'), ('Fiji', 'FJI'), ('Finland', 'FIN'), ('France', 'FRA'), ('French Guiana (FRA)', 'GUF'), ('French New Caledonia (FRA)', 'NCL'), ('French Polynesia', 'PYF'), ('French Southern and Antarctic Territories', 'ATF'), ('Gabon', 'GAB'), ('Gambia', 'GMB'), ('Georgia', 'GEO'), ('Germany', 'DEU'), ('Ghana', 'GHA'), ('Gibraltar', 'GIB'), ('Greece', 'GRC'), ('Greenland (DNK)', 'GRL'), ('Grenada', 'GRD'), ('Guadeloupe (FRA)', 'GLP'), ('Guam', 'GUM'), ('Guatemala', 'GTM'), ('Guernsey (GBR)', 'GGY'), ('Guinea', 'GIN'), ('Guinea-Bissau', 'GNB'), ('Guyana', 'GUY'), ('Haiti', 'HTI'), ("Hala'ib triangle (SDN)", 'XHT'), ('Heard Island and McDonald Islands (AUS)', 'HMD'), ('Holy See', 'VAT'), ('Honduras', 'HND'), ('Hong Kong', 'HKG'), ('Hungary', 'HUN'), ('Iceland', 'ISL'), ('Ilemi Triangle (SSD)', 'XIT'), ('India', 'IND'), ('Indonesia', 'IDN'), ('Iraq', 'IRQ'), ('Ireland', 'IRL'), ('Islamic Republic of Iran', 'IRN'), ('Isle of Man', 'IMN'), ('Israel', 'ISR'), ('Italy', 'ITA'), ('Jamaica', 'JAM'), ('Jammu and Kashmir', 'XJK'), ('Jan Mayen Island (NOR)', 'SJM'), ('Japan', 'JPN'), ('Jersey (GBR)', 'JEY'), ('Johnston Atoll', 'JTN'), ('Jordan', 'JOR'), ('Kazakhstan', 'KAZ'), ('Kenya', 'KEN'), ('Kiribati', 'KIR'), ('Kosovo (SRB)', 'KOS'), ('Kuril Islands (RUS)', 'XKI'), ('Kuwait', 'KWT'), ('Kyrgyzstan', 'KGZ'), ("Lao People's Democratic Republic", 'LAO'), ('Latvia', 'LVA'), ('Lebanon', 'LBN'), ('Lesotho', 'LSO'), ('Liberia', 'LBR'), ('Libya', 'LBY'), ('Liechtenstein', 'LIE'), ('Lithuania', 'LTU'), ('Luxembourg', 'LUX'), ("Ma'tan al-Sarra (EGY)", 'XMS'), ('Macao (CHN)', 'MAC'), ('Madagascar', 'MDG'), ('Malawi', 'MWI'), ('Malaysia', 'MYS'), ('Maldives', 'MDV'), ('Mali', 'MLI'), ('Malta', 'MLT'), ('Marshall Islands', 'MHL'), ('Martinique (FRA)', 'MTQ'), ('Mauritania', 'MRT'), ('Mauritius', 'MUS'), ('Mayotte (FRA)', 'MYT'), ('Mexico', 'MEX'), ('Midway Islands', 'MID'), ('Monaco', 'MCO'), ('Mongolia', 'MNG'), ('Montenegro', 'MNE'), ('Montserrat', 'MSR'), ('Morocco', 'MAR'), ('Mozambique', 'MOZ'), ('Myanmar', 'MMR'), ('Namibia', 'NAM'), ('Nauru', 'NRU'), ('Nepal', 'NPL'), ('Netherlands (Kingdom of the)', 'NLD'), ('New Zealand', 'NZL'), ('Nicaragua', 'NIC'), ('Niger', 'NER'), ('Nigeria', 'NGA'), ('Niue', 'NIU'), ('No code (ISO user specified)', 'AAA'), ('Norfolk Island (AUS)', 'NFK'), ('North Macedonia', 'MKD'), ('Northern Mariana Islands (USA)', 'MNP'), ('Norway', 'NOR'), ('Oman', 'OMN'), ('Pakistan', 'PAK'), ('Palau', 'PLW'), ('Panama', 'PAN'), ('Papua New Guinea', 'PNG'), ('Paracel Islands (Sovereignty unsettled)', 'XPI'), ('Paraguay', 'PRY'), ('Peru', 'PER'), ('Philippines', 'PHL'), ('Pitcairn Islands', 'PCN'), ('Plurinational State of Bolivia', 'BOL'), ('Poland', 'POL'), ('Portugal', 'PRT'), ('Puerto Rico', 'PRI'), ('Qatar', 'QAT'), ('Rep. of Chad', 'TCD'), ('Republic of Korea', 'KOR'), ('Republic of Moldova', 'MDA'), ('Republic of the Congo', 'COG'), ('Reunion (FRA)', 'REU'), ('Romania', 'ROU'), ('Russian Federation', 'RUS'), ('Rwanda', 'RWA'), ('Saint Barthelemy (FRA)', 'BLM'), ('Saint Helena', 'SHN'), ('Saint Kitts and Nevis', 'KNA'), ('Saint Lucia', 'LCA'), ('Saint Martin (FRA)', 'MAF'), ('Saint Pierre et Miquelon (FRA)', 'SPM'), ('Saint Vincent and the Grenadines', 'VCT'), ('Samoa', 'WSM'), ('San Marino', 'SMR'), ('Sao Tome and Principe', 'STP'), ('Sark (GBR)', 'XSA'), ('Saudi Arabia', 'SAU'), ('Scarborough Reef (Sovereignty unsettled)', 'XSR'), ('Senegal', 'SEN'), ('Senkaku Islands (Sovereignty unsettled)', 'XSI'), ('Serbia', 'SRB'), ('Seychelles', 'SYC'), ('Sierra Leone', 'SLE'), ('Singapore', 'SGP'), ('Sint Maarten (K. of Netherlands)', 'SXM'), ('Slovakia', 'SVK'), ('Slovenia', 'SVN'), ('Solomon Islands', 'SLB'), ('Somalia', 'SOM'), ('South Africa', 'ZAF'), ('South Georgia and the South Sandwich Islands (GBR)', 'SGS'), ('South Sudan', 'SSD'), ('Spain', 'ESP'), ('Spratly Islands (Sovereignty unsettled)', 'XSP'), ('Sri Lanka', 'LKA'), ('State of Palestine', 'PSE'), ('Sudan', 'SDN'), ('Suriname', 'SUR'), ('Sweden', 'SWE'), ('Switzerland', 'CHE'), ('Syrian Arab Republic', 'SYR'), ('Taiwan (CHN)', 'TWN'), ('Tajikistan', 'TJK'), ('Thailand', 'THA'), ('Timor-Leste', 'TLS'), ('Togo', 'TGO'), ('Tokelau', 'TKL'), ('Tonga', 'TON'), ('Trinidad and Tobago', 'TTO'), ('Tunisia', 'TUN'), ('Turkey', 'TUR'), ('Turkmenistan', 'TKM'), ('Turks and Caicos Islands (GBR)', 'TCA'), ('Tuvalu', 'TUV'), ('Uganda', 'UGA'), ('Ukraine', 'UKR'), ('United Arab Emirates', 'ARE'), ('United Kingdom of Great Britain and Northern Ireland', 'GBR'), ('United Republic of Tanzania', 'TZA'), ('United States Minor Outlying Islands', 'UMI'), ('United States Virgin Islands (USA)', 'VIR'), ('United States of America', 'USA'), ('Uruguay', 'URY'), ('Uzbekistan', 'UZB'), ('Vanuatu', 'VUT'), ('Viet Nam', 'VNM'), ('Wake Island', 'WAK'), ('Wallis and Futuna', 'WLF'), ('Western Sahara', 'ESH'), ('Yemen', 'YEM'), ('Zambia', 'ZMB'), ('Zimbabwe', 'ZWE')]
2
+ rp_options = [10, 20, 50, 75, 100, 200, 500]
downloads/lso_admpop_adm0_2022.csv ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ year,ISO3,ADM0_NAME,ADM0_PCODE,F_TL,M_TL,T_TL,F_00_04,F_05_09,F_10_14,F_15_19,F_20_24,F_25_29,F_30_34,F_35_39,F_40_44,F_45_49,F_50_54,F_55_59,F_60_64,F_65_69,F_70Plus,M_00_04,M_05_09,M_10_14,M_15_19,M_20_24,M_25_29,M_30_34,M_35_39,M_40_44,M_45_49,M_50_54,M_55_59,M_60_64,M_65_69,M_70Plus,T_00_04,T_05_09,T_10_14,T_15_19,T_20_24,T_25_29,T_30_34,T_35_39,T_40_44,T_45_49,T_50_54,T_55_59,T_60_64,T_65_69,T_70Plus
2
+ 2022,LSO,Lesotho,LS,1063959,1026523,2090482,98065,115410,106271,94988,95857,99667,95379,71416,50038,38260,41243,38695,33335,24212,61128,99891,111849,105833,99020,95298,103720,104286,82950,54370,37745,33659,27326,23384,16539,30654,197954,227257,212102,194008,191156,203389,199664,154367,104407,76005,74901,66022,56716,40752,91782
downloads/lso_admpop_adm1_2022.csv ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ year,ISO3,ADM0_NAME,ADM0_PCODE,ADM1_NAME,ADM1_PCODE,F_TL,M_TL,T_TL,F_00_04,F_05_09,F_10_14,F_15_19,F_20_24,F_25_29,F_30_34,F_35_39,F_40_44,F_45_49,F_50_54,F_55_59,F_60_64,F_65_69,F_70Plus,M_00_04,M_05_09,M_10_14,M_15_19,M_20_24,M_25_29,M_30_34,M_35_39,M_40_44,M_45_49,M_50_54,M_55_59,M_60_64,M_65_69,M_70Plus,T_00_04,T_05_09,T_10_14,T_15_19,T_20_24,T_25_29,T_30_34,T_35_39,T_40_44,T_45_49,T_50_54,T_55_59,T_60_64,T_65_69,T_70Plus
2
+ 2022,LSO,Lesotho,LS,Maseru,LSA,297553,278876,576429,27469,29033,26160,25828,31021,31084,29565,23230,15768,11339,10810,9497,7851,5910,12988,27868,28374,26283,25278,28186,29440,29552,24529,16496,11554,9135,6691,5529,3688,6274,55337,57407,52443,51106,59207,60524,59117,47759,32264,22893,19945,16188,13379,9598,19263
3
+ 2022,LSO,Lesotho,LS,Butha-Buthe,LSB,63594,59708,123302,5969,7606,6518,5438,5374,5699,5576,4046,2804,2155,2529,2341,2110,1510,3919,5872,6846,6169,5064,5017,5939,6248,4923,3199,2108,2003,1784,1442,1078,2016,11842,14452,12686,10501,10391,11639,11824,8969,6003,4263,4532,4125,3552,2588,5935
4
+ 2022,LSO,Lesotho,LS,Leribe,LSC,187240,179120,366361,17117,20129,18102,16527,17201,18313,17197,12716,8983,6947,7249,6998,5740,4150,9872,17402,19684,17483,17310,16548,18454,18673,14295,9382,6737,6099,4944,4098,2826,5185,34518,39812,35585,33837,33749,36767,35870,27011,18365,13685,13348,11942,9838,6977,15057
5
+ 2022,LSO,Lesotho,LS,Berea,LSD,137375,133147,270523,12686,14600,12874,11898,11522,12491,12651,9357,6619,5659,5943,5633,4596,3143,7703,12934,14067,12886,12455,12203,13273,13541,10921,7023,5110,4901,4250,3422,2074,4086,25619,28667,25760,24354,23725,25764,26192,20279,13642,10769,10844,9884,8018,5217,11789
6
+ 2022,LSO,Lesotho,LS,Mafeteng,LSE,83727,84517,168244,7391,9041,8503,7091,6147,7173,7224,5330,3761,2819,3505,3752,3599,2461,5933,7819,8774,8808,7880,7088,8908,8631,6858,4382,2776,2943,2442,2155,2076,2978,15210,17815,17310,14971,13235,16080,15855,12188,8143,5595,6447,6194,5753,4537,8911
7
+ 2022,LSO,Lesotho,LS,Mohale's Hoek,LSF,80114,77569,157683,6735,8908,8579,6686,6600,7114,6721,4597,3348,2837,3487,3332,2891,2217,6062,7112,8635,8304,7482,6746,7793,8081,5808,3770,2545,2476,2332,2187,1507,2791,13846,17543,16883,14168,13346,14908,14802,10405,7118,5381,5964,5664,5078,3724,8852
8
+ 2022,LSO,Lesotho,LS,Quthing,LSG,55080,54426,109506,5175,6191,5752,5016,5110,5182,4569,2952,2013,1644,2074,2102,1914,1309,4077,5247,5936,6074,5640,5249,5831,5629,3902,2429,1638,1542,1283,1296,876,1856,10422,12126,11826,10655,10359,11013,10198,6854,4441,3282,3616,3385,3210,2185,5933
9
+ 2022,LSO,Lesotho,LS,Qacha's Nek,LSH,39031,38607,77637,3771,4412,4470,3809,3179,3464,3312,2287,1577,1247,1437,1379,1175,911,2600,3817,4417,4484,4398,3519,3559,3695,2919,1673,1209,1222,1032,846,607,1211,7588,8829,8954,8207,6698,7023,7007,5206,3250,2456,2659,2411,2021,1518,3811
10
+ 2022,LSO,Lesotho,LS,Mokhotlong,LSJ,51161,50905,102066,4676,6462,6915,5774,4288,3834,3698,2990,2179,1490,1771,1454,1470,1087,3074,4947,6456,6728,5502,4530,4545,4456,3837,2549,1596,1367,1052,1007,784,1548,9623,12918,13643,11276,8818,8380,8154,6827,4728,3086,3137,2506,2477,1871,4622
11
+ 2022,LSO,Lesotho,LS,Thaba-Tseka,LSK,69084,69648,138731,7076,9028,8398,6921,5415,5313,4866,3911,2986,2123,2438,2207,1989,1514,4900,6873,8660,8614,8011,6212,5978,5780,4958,3467,2472,1971,1516,1402,1023,2709,13949,17688,17012,14933,11628,11291,10645,8869,6453,4595,4409,3723,3390,2537,7609
hazards/__pycache__/flood.cpython-311.pyc ADDED
Binary file (6.7 kB). View file
 
hazards/flood.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from data.geometry import get_geometry, has_associated_features
2
+ import math, os, rasterio, urllib.request
3
+ from rasterio.merge import merge
4
+ from rasterio.mask import mask
5
+ from bs4 import BeautifulSoup
6
+ from datetime import datetime
7
+ import requests
8
+
9
+
10
+ def generate_flood_raster(iso: str, rp: int, gis_name: str = None):
11
+ iso = iso.upper()
12
+ base_url = "https://gis.unhcr.org/arcgis/rest/services/core_v2/wrl_polbnd_adm1_a_unhcr/MapServer/0/query"
13
+
14
+ has_features, _ = has_associated_features(iso, base_url)
15
+ if has_features:
16
+ gdf = get_geometry(iso, url=base_url)
17
+ else:
18
+ if not gis_name:
19
+ return None, f"❌ Subdivision required for {iso}."
20
+ gdf = get_geometry(iso, gis_name)
21
+ if gdf is None or gdf.empty:
22
+ return None, f"❌ No geometry found for {gis_name} in {iso}."
23
+
24
+ bounds = gdf.total_bounds # left, bottom, right, top
25
+
26
+ # --- Determine tiles to download ---
27
+ top = math.ceil(bounds[3] / 10) * 10
28
+ left = (int(bounds[0]) // 10) * 10
29
+ right = math.ceil(bounds[2] / 10) * 10
30
+ bottom = (int(bounds[1]) // 10) * 10
31
+
32
+ url_base = f"https://jeodpp.jrc.ec.europa.eu/ftp/jrc-opendata/CEMS-GLOFAS/flood_hazard/RP{rp}/"
33
+ page = requests.get(url_base)
34
+ soup = BeautifulSoup(page.text, "html.parser")
35
+ all_links = [a['href'] for a in soup.find_all('a') if a['href'].endswith('.tif')]
36
+
37
+ tif_paths = []
38
+ col = left
39
+ while col < right:
40
+ row = top
41
+ while row > bottom:
42
+ prefix = f"{'N' if row >= 0 else 'S'}{abs(row)}_{'E' if col >= 0 else 'W'}{abs(col)}"
43
+ match = [l for l in all_links if prefix in l]
44
+ if match:
45
+ tif_paths.append(match[0])
46
+ row -= 10
47
+ col += 10
48
+
49
+ if not tif_paths:
50
+ return None, "❌ No tiles found for selected region."
51
+
52
+ # --- Download and merge ---
53
+ local_paths = []
54
+ for tif in tif_paths:
55
+ remote = url_base + tif
56
+ local = f"/tmp/{tif}"
57
+ urllib.request.urlretrieve(remote, local)
58
+ local_paths.append(local)
59
+
60
+ sources = [rasterio.open(p) for p in local_paths]
61
+
62
+ mosaic, merge_transform = rasterio.merge.merge(sources)
63
+
64
+ temp_merged = "/tmp/temp_merged.tif"
65
+ temp_meta = sources[0].meta.copy()
66
+ temp_meta.update({
67
+ "driver": "GTiff",
68
+ "height": mosaic.shape[1],
69
+ "width": mosaic.shape[2],
70
+ "transform": merge_transform
71
+ })
72
+
73
+ with rasterio.open(temp_merged, "w", **temp_meta) as temp_dst:
74
+ temp_dst.write(mosaic)
75
+
76
+ # --- Crop to geometry ---
77
+ geoms = [feature["geometry"] for feature in gdf.__geo_interface__["features"]]
78
+ with rasterio.open(temp_merged) as src:
79
+ out_image, out_transform = mask(src, geoms, crop=True, nodata=-9999)
80
+ out_meta = src.meta.copy()
81
+ out_meta.update({
82
+ "driver": "GTiff",
83
+ "height": out_image.shape[1],
84
+ "width": out_image.shape[2],
85
+ "transform": out_transform,
86
+ "nodata": -9999
87
+ })
88
+
89
+ timestamp = datetime.utcnow().strftime("%Y%m%d%H%M%S")
90
+ output_path = f"./merged_raster_{iso}_{rp}_{timestamp}.tif"
91
+
92
+ with rasterio.open(output_path, "w", **out_meta) as dest:
93
+ dest.write(out_image)
94
+
95
+ # Cleanup
96
+ for src in sources:
97
+ src.close()
98
+ for f in local_paths:
99
+ os.remove(f)
100
+ if os.path.exists(temp_merged):
101
+ os.remove(temp_merged)
102
+
103
+ return output_path, f"✅ Raster generated for {iso} (RP {rp}). Click to download below."