Demo: area map
A minimal template that exercises the whole pipeline: it receives an admin area from the app, draws it with lonboard, and writes a small results table.
Source
# --- Reserved parameters (injected by the Notebook Factory; defaults keep the notebook runnable standalone) ---
run_id = ""
inputs_dir = ""
output_dir = "outputs"
area = {} # {id, code, name, level, country, country_name, bbox}
area_geojson = "" # FeatureCollection with the target area
subdivisions_geojson = "" # FeatureCollection with the aggregation subdivisions
event = {} # Montandon STAC item (dict)
event_json = ""
montandon_stac_url = "https://montandon-eoapi-stage.ifrc.org/stac"
hazard = ""
# --- User parameters ---
label = "Hello from the Notebook Factory"
buffer_km = 5
show_subdivisions = TrueSource
# Parameters
run_id = "9a0edb8c-62e3-449b-be9f-15967d00ca46"
inputs_dir = "/app/data/work/9a0edb8c-62e3-449b-be9f-15967d00ca46/inputs"
output_dir = "/app/data/work/9a0edb8c-62e3-449b-be9f-15967d00ca46/outputs"
area = {"id": 2, "code": "NPL.1_1", "name": "Central", "level": 1, "country": "NPL", "country_name": "Nepal", "bbox": [83.9218, 26.5684, 86.5752, 28.3869]}
area_geojson = "/app/data/work/9a0edb8c-62e3-449b-be9f-15967d00ca46/inputs/area.geojson"
subdivisions_geojson = "/app/data/work/9a0edb8c-62e3-449b-be9f-15967d00ca46/inputs/subdivisions.geojson"
event = {}
event_json = ""
montandon_stac_url = "https://montandon-eoapi-stage.ifrc.org/stac"
hazard = ""
label = "post-review 2"
buffer_km = 5
show_subdivisions = True
Source
import json, math, os, warnings
from pathlib import Path
import numpy as np
import pandas as pd
import geopandas as gpd
import shapely
from shapely.geometry import box
warnings.filterwarnings("ignore", message=".*geographic CRS.*")
Path(output_dir).mkdir(parents=True, exist_ok=True)
def centroids(geoms):
"""Centroids computed in an equal-area projection (avoids the geographic-CRS warning)."""
return geoms.to_crs(6933).centroid.to_crs(4326)
def load_area_and_subdivisions():
"""Read the boundaries prepared by the app, or build a small demo area when run standalone."""
if area_geojson and Path(area_geojson).exists():
area_gdf = gpd.read_file(area_geojson)
subs = gpd.read_file(subdivisions_geojson) if subdivisions_geojson and Path(subdivisions_geojson).exists() else area_gdf.copy()
return area_gdf, subs
demo = gpd.GeoDataFrame({"name": ["Demo area"], "code": ["DEMO"], "level": [1]}, geometry=[box(83.5, 26.5, 88.0, 30.5)], crs=4326)
cells, names = [], []
minx, miny, maxx, maxy = demo.total_bounds
for i in range(3):
for j in range(3):
cells.append(box(minx + i * (maxx - minx) / 3, miny + j * (maxy - miny) / 3, minx + (i + 1) * (maxx - minx) / 3, miny + (j + 1) * (maxy - miny) / 3))
names.append(f"Demo district {i * 3 + j + 1}")
subs = gpd.GeoDataFrame({"name": names, "code": [f"DEMO.{k}" for k in range(9)], "level": [2] * 9}, geometry=cells, crs=4326)
return demo, subs
area_gdf, subdivisions = load_area_and_subdivisions()
area_name = area.get("name") or area_gdf.iloc[0].get("name", "Demo area")
print(f"Area: {area_name} · {len(subdivisions)} subdivisions")
subdivisions[["code", "name", "level"]].head()Area: Central · 3 subdivisions
Loading...
Map¶
Source
# Visualisation: lonboard for maps, manywidgets for charts, controls and stats.
# All of these are anywidgets, so they stay interactive in the published (kernel-free) page.
from matplotlib import colormaps
from lonboard import Map, PolygonLayer, ScatterplotLayer
from manywidgets import Chart, Column, Legend, Row, Slider, Stat
from manywidgets.lonboard import LayerToggle
def colors_for(values, cmap="OrRd", vmin=None, vmax=None):
v = np.asarray(values, dtype=float)
vmin = np.nanmin(v) if vmin is None else vmin
vmax = np.nanmax(v) if vmax is None else vmax
norm = (v - vmin) / (vmax - vmin) if vmax > vmin else np.zeros_like(v)
rgba = colormaps[cmap](np.nan_to_num(norm))
return (rgba * 255).astype(np.uint8)
def legend_for(cmap, vmin, vmax, title="", n=5, fmt="{:.0%}"):
stops = np.linspace(vmin, vmax, n)
rgb = colors_for(stops, cmap, vmin, vmax)[:, :3]
return Legend(entries=[[[int(c) for c in rgb[i]], fmt.format(stops[i])] for i in range(n)], title=title)
def bar_chart(names, values, title, y_label, color="#E4572E"):
chart = Chart(chart_type="bar", title=title, y_label=y_label, height=max(280, 22 * len(names) + 120), legend_enabled=False)
chart.set_options(scales={"x": {"type": "category", "ticks": {"autoSkip": False, "maxRotation": 60, "minRotation": 30}}})
chart.add_series(x=[str(n) for n in names], y=[float(v) for v in values], name=y_label, series_type="bar", color=color)
return chart
def fmt_int(v):
return f"{float(v):,.0f}"
subs = subdivisions.copy()
subs["area_km2"] = subs.to_crs(6933).area / 1e6
Row(Stat(label="Area", value=area_name), Stat(label="Subdivisions", value=len(subs)), Stat(label="Total area", value=fmt_int(subs["area_km2"].sum()), unit="km²"), gap="16px")Source
outline = area_gdf.to_crs(3857).buffer(buffer_km * 1000).to_crs(4326)
outline_layer = PolygonLayer.from_geopandas(gpd.GeoDataFrame(geometry=outline, crs=4326), get_fill_color=[228, 87, 46, 40], get_line_color=[194, 67, 31], line_width_min_pixels=2)
subs_layer = PolygonLayer.from_geopandas(subs[["name", "area_km2", "geometry"]], get_fill_color=colors_for(subs["area_km2"], "Blues"), get_line_color=[80, 80, 80], line_width_min_pixels=1, opacity=0.6, visible=show_subdivisions)
Map([outline_layer, subs_layer])The controls below are live in the published page (no kernel): toggle layers, move the slider.
Source
from ipywidgets import jsdlink
slider = Slider(label="Buffer (km) — linked to the stat on the right", min=0, max=100, step=1, value=float(buffer_km))
stat = Stat(label="Slider value", value=float(buffer_km), unit="km")
jsdlink((slider, "value"), (stat, "value"))
Column(Row(LayerToggle(layer=subs_layer, label="Subdivisions"), LayerToggle(layer=outline_layer, label="Buffered outline"), gap="16px"), Row(slider, stat, gap="16px"), gap="12px")Source
print(label)
table = subs[["code", "name", "area_km2"]].copy()
table["area_km2"] = table["area_km2"].round(1)
table.to_csv(Path(output_dir) / "subdivisions.csv", index=False)
tablepost-review 2
Loading...