feat(dashboard): CMU's 2024 flood marks as a map layer
CI / Format & lint (push) Successful in 44s
CI / Test suite (push) Successful in 49s
Docs / Validate documentation (push) Successful in 18s

CMU Water Center marked the October 2024 high-water line at ~4,900 places in
Chiang Mai and Lamphun (depth above the road while P.1 stood at 5.30 m).

- src/static/flood-marks-2024.json (position and depth, 110 KB), built by
  scripts/build_flood_marks.py from CMU's GeoJSON.
- "Flood marks 2024" layer: canvas dots from zoom 12 in CMU's five depth
  classes, legend row, CMU credit; turning it on below zoom 12 flies to the
  city. A mark's popup gives the depth and its flood zone.
- Zone and marker popups name the nearest mark within 250 m (loaded with the
  zones).
- No depth estimate for other P.1 levels: the marks do not scale one-to-one
  with P.1 (marks in the 5.30 m zone stood a median 65 cm deep in 2024).
- The layer bar wraps on narrow maps; popups pan clear of it.
- Docs: DATA_SOURCES 5.1, DASHBOARD_REDESIGN, README.
This commit is contained in:
grabowski committed 2026-10-26 14:30:35 +07:00
1 parent 701bb87d34
commit a610abcdb8
6 files changed
+191 -10

No files matched your search

+71
View File
@@ -0,0 +1,71 @@
"""Build src/static/flood-marks-2024.json from CMU Water Center's flood marks.
After the October 2024 flood (P.1 peaked at 5.30 m on 5 October), CMU Water
Center marked the high-water line at about 5,000 places in Chiang Mai and
Lamphun (CMFlood, "Flood marks 2024"): the depth above the road surface, in
cm. This keeps position and depth only, in a compact array form:
uv run python scripts/build_flood_marks.py # fetch from CMU
uv run python scripts/build_flood_marks.py --geojson F # or a saved copy
"""
import argparse
import json
import urllib.request
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
OUT = REPO / "src" / "static" / "flood-marks-2024.json"
URL = "https://watercenter.cmu.ac.th/data/New_floodMark_2025_fixed_like_pole.geojson"
SOURCE = (
"CMU Water Center (ศูนย์วิชาการสนับสนุนการบริหารจัดการน้ำ มหาวิทยาลัยเชียงใหม่), "
"flood marks of October 2024, "
"https://watercenter.cmu.ac.th/chiangmai/cmflood/warning/floodmark"
)
def load(path: Path | None) -> dict:
if path:
return json.loads(path.read_text(encoding="utf-8"))
req = urllib.request.Request(URL, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(req, timeout=120) as r:
return json.loads(r.read().decode("utf-8"))
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
ap.add_argument("--geojson", type=Path, help="read CMU's GeoJSON from here")
args = ap.parse_args()
marks, skipped = [], 0
for f in load(args.geojson)["features"]:
p = f.get("properties") or {}
lon, lat, depth = p.get("longitude"), p.get("latitude"), p.get("water_level")
if lon is None or lat is None or depth is None:
skipped += 1
continue
# 18-19.5 N, 98.5-99.5 E: a swapped or empty coordinate is skipped
if not (18.0 < lat < 19.5 and 98.5 < lon < 99.5) or depth <= 0:
skipped += 1
continue
marks.append([round(lon, 5), round(lat, 5), round(depth)])
marks.sort(key=lambda m: (m[1], m[0]))
doc = {
"source": SOURCE,
"p1_level": 5.30,
"date": "2024-10-05",
"fields": ["lon", "lat", "depth_cm_above_road"],
"marks": marks,
}
OUT.write_text(
json.dumps(doc, ensure_ascii=False, separators=(",", ":")) + "\n",
encoding="utf-8",
)
print(
f"wrote {OUT.relative_to(REPO)}: {len(marks)} marks, {skipped} skipped, "
f"{OUT.stat().st_size / 1024:.0f} KB"
)
if __name__ == "__main__":
main()