Modlarised app
Browse files- README.md +40 -1
- app.py +34 -277
- config.py +18 -0
- data/__pycache__/ckan.cpython-311.pyc +0 -0
- data/__pycache__/geometry.cpython-311.pyc +0 -0
- data/__pycache__/hotosm.cpython-311.pyc +0 -0
- data/__pycache__/options.cpython-311.pyc +0 -0
- data/ckan.py +57 -0
- data/geometry.py +57 -0
- data/hotosm.py +41 -0
- data/options.py +2 -0
- downloads/lso_admpop_adm0_2022.csv +2 -0
- downloads/lso_admpop_adm1_2022.csv +11 -0
- hazards/__pycache__/flood.cpython-311.pyc +0 -0
- hazards/flood.py +103 -0
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 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 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 |
-
|
| 35 |
-
|
| 36 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
|
| 286 |
-
|
|
|
|
|
|
|
|
|
|
| 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)
|
| 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="")
|
| 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."
|