rapsoj commited on
Commit
e4955ba
·
verified ·
1 Parent(s): ca2c43d

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +253 -42
  2. requirements.txt +3 -1
app.py CHANGED
@@ -6,30 +6,84 @@ import geopandas as gpd
6
  import urllib.request
7
  import rasterio
8
  import rasterio.merge
 
 
9
  from bs4 import BeautifulSoup
10
  from datetime import datetime
 
11
 
12
- def generate_raster(iso: str, rp: int):
13
- iso = iso.upper()
14
 
15
- # --- Get geometry from UNHCR API ---
16
- geometry_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}'",
19
  "outFields": "*",
20
  "outSR": "4326",
21
- "f": "geojson"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  }
23
- response = requests.get(geometry_url, params=params)
24
- if response.status_code != 200:
25
- return None, f"❌ Failed to fetch geometry for {iso}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
- data = response.json()
28
- if "features" not in data or not data["features"]:
29
- return None, f"❌ No features found for {iso}"
 
 
 
 
 
 
30
 
31
- gdf = gpd.GeoDataFrame.from_features(data["features"])
32
- bounds = gdf.total_bounds # [left, bottom, right, top]
33
 
34
  # --- Determine tiles to download ---
35
  top = math.ceil(bounds[3] / 10) * 10
@@ -66,52 +120,151 @@ def generate_raster(iso: str, rp: int):
66
  local_paths.append(local)
67
 
68
  sources = [rasterio.open(p) for p in local_paths]
69
- mosaic, transform = rasterio.merge.merge(sources)
70
 
71
- out_meta = sources[0].meta.copy()
72
- out_meta.update({
 
 
 
73
  "driver": "GTiff",
74
  "height": mosaic.shape[1],
75
  "width": mosaic.shape[2],
76
- "transform": transform
77
  })
78
 
79
- timestamp = datetime.utcnow().strftime("%Y%m%d%H%M%S")
80
- output_path = f"./merged_raster_{iso}_{rp}_{timestamp}.tif"
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
- with rasterio.open(output_path, "w", **out_meta) as dest:
83
- dest.write(mosaic)
84
 
 
 
 
 
85
  for src in sources:
86
  src.close()
87
  for f in local_paths:
88
  os.remove(f)
 
 
89
 
90
  return output_path, f"✅ Raster generated for {iso} (RP {rp}). Click to download below."
91
 
92
 
93
- def download_hotosm_populated_places(iso3: str, save_dir: str = "./"):
 
 
 
 
 
 
 
 
 
 
 
 
94
  iso3_upper = iso3.upper()
95
  iso3_lower = iso3.lower()
96
 
97
- url = f"https://s3.dualstack.us-east-1.amazonaws.com/production-raw-data-api/ISO3/{iso3_upper}/populated_places/points/hotosm_{iso3_lower}_populated_places_points_shp.zip"
98
- local_filename = os.path.join(save_dir, f"hotosm_{iso3_lower}_populated_places_points_shp.zip")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
- response = requests.get(url, stream=True)
101
- if response.status_code != 200:
102
- print(f"Failed to download HOTOSM data for {iso3}. HTTP status: {response.status_code}")
103
  return None
104
 
105
- with open(local_filename, 'wb') as f:
106
- for chunk in response.iter_content(chunk_size=8192):
107
- f.write(chunk)
108
 
109
- return local_filename
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
 
112
  # Selection options
113
  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')]
114
  rp_options = [10, 20, 50, 75, 100, 200, 500]
 
115
 
116
  with gr.Blocks(title="Flood Raster Downloader") as demo:
117
  gr.Markdown("## E-VCA Flood Raster Downloader\nSelect a country and return period to generate the GloFAS flood hazard raster for that region.")
@@ -119,27 +272,85 @@ with gr.Blocks(title="Flood Raster Downloader") as demo:
119
  with gr.Row():
120
  iso_input = gr.Dropdown(choices=iso_options, label="Country", interactive=True, value=None)
121
  rp_input = gr.Dropdown(choices=rp_options, label="Return Period (years)", value=10)
 
 
 
122
 
123
- generate_btn = gr.Button("Generate Raster")
124
  status_text = gr.Markdown("")
125
  file_output = gr.Files(label="Downloads", interactive=False)
126
 
127
- def wrapper(iso, rp):
128
- raster_path, msg = generate_raster(iso, rp)
129
- hotosm_path = download_hotosm_populated_places(iso)
130
-
131
- if not hotosm_path:
132
- msg += " ⚠️ No HOTOSM populated places data found."
 
 
 
 
 
 
133
 
134
  files = []
135
  if raster_path:
136
  files.append(raster_path)
137
- if hotosm_path:
138
- files.append(hotosm_path)
 
 
 
 
 
 
 
 
139
 
140
  return files, msg
141
 
142
- generate_btn.click(fn=wrapper, inputs=[iso_input, rp_input], outputs=[file_output, status_text])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
  if __name__ == "__main__":
145
  demo.launch(share=True)
 
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
 
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("## E-VCA Flood Raster Downloader\nSelect a country and return period to generate the GloFAS flood hazard raster for that region.")
 
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")
290
+ roads_path = download_hotosm_data(iso3, feature_type="roads", geometry="lines")
291
+
292
+ # CKAN Downloads
293
+ pop_paths = download_resource_ckan(iso3, dataset_prefix="cod-ps")
294
+ admin_path = download_resource_ckan(iso3, dataset_prefix="cod-ab")
295
 
296
  files = []
297
  if raster_path:
298
  files.append(raster_path)
299
+ if pp_path:
300
+ files.append(pp_path)
301
+ if roads_path:
302
+ files.append(roads_path)
303
+ if admin_path:
304
+ files.extend(admin_path)
305
+ if pop_paths:
306
+ files.extend(pop_paths) # not append!
307
+ if not files:
308
+ msg += " ⚠️ No data could be downloaded."
309
 
310
  return files, msg
311
 
312
+ def get_subdivision_options(iso_code):
313
+ has_features, _ = has_associated_features(iso_code)
314
+ if not has_features:
315
+ subdivisions = fetch_gis_subdivisions(iso_code)
316
+ return (
317
+ gr.update(choices=subdivisions, visible=True, value=None),
318
+ gr.update(visible=False),
319
+ gr.update(value="✅ Subdivisions loaded. Please select one.")
320
+ )
321
+ else:
322
+ return (
323
+ gr.update(visible=False),
324
+ gr.update(visible=True),
325
+ gr.update(value="") # Clear status if subdivisions aren't needed
326
+ )
327
+
328
+ def on_subdivision_selected(subdivision):
329
+ if subdivision:
330
+ return gr.update(visible=True), gr.update(visible=True)
331
+ return gr.update(visible=False), gr.update(visible=False)
332
+
333
+ iso_input.change(
334
+ fn=lambda iso: gr.update(value="🔄 Loading subdivisions..."),
335
+ inputs=iso_input,
336
+ outputs=status_text
337
+ ).then(
338
+ fn=get_subdivision_options,
339
+ inputs=iso_input,
340
+ outputs=[subdivision_input, generate_btn, status_text]
341
+ )
342
+
343
+ subdivision_input.change(
344
+ fn=lambda s: gr.update(visible=True) if s else gr.update(visible=False),
345
+ inputs=subdivision_input,
346
+ outputs=generate_btn
347
+ )
348
+
349
+ generate_btn.click(
350
+ fn=wrapper,
351
+ inputs=[iso_input, rp_input, subdivision_input],
352
+ outputs=[file_output, status_text]
353
+ )
354
 
355
  if __name__ == "__main__":
356
  demo.launch(share=True)
requirements.txt CHANGED
@@ -4,6 +4,8 @@ geopandas
4
  gradio
5
  matplotlib
6
  numpy
 
7
  rasterio
8
  requests
9
- shapely
 
 
4
  gradio
5
  matplotlib
6
  numpy
7
+ pandas
8
  rasterio
9
  requests
10
+ shapely
11
+ urllib