Evidence review · 15 October 2026 · KL atlas research ↗
This is a public-data evidence twin: live observations, published models and dated reference geometry. It has no calibrated physics solver, complete asset registry or official operational mandate. New surface heat, soil moisture and rainfall browse layers are in 2D Layers; transport trip comparisons are in Economy. The 3D and 2D views retain one briefing shell.
Load the searchable source register, or download its dated JSON. Official discovery: data.gov.my, DBKL, NASA GIBS.
What the source review cannot establish
No exhaustive census of public data is possible from a catalogue search. DBKL ponds, pump houses and gazetted green areas need coordinate, licence and vintage checks before map ingestion. Building heights do not establish flood clearance. Hospital points do not establish capacity. A rainfall or wind overlay does not identify a flood or smoke source. Fine-looking pixels do not increase the underlying sensor resolution.
A Public Data Atlas
of the Federation
13 states and 3 federal territories. 160 district records. 32.4 million people in Census 2020. Selected public signals on one map, with historical reference layers underneath. Coverage and age vary by feed. Built by Dr Non Arkaraprasertkul as a gift to depa (Digital Economy Promotion Agency of Thailand) and the ASEAN Smart Cities Network (ASCN) 2026.
01Dr Non Arkaraprasertkul
Most important thing first: this is a gift.
Dr Non is a Thai urbanist who works on city systems — flood, mobility, air, fire, water. He has shipped dashboards for Bangkok floods (FloodDash), Chiang Mai (CNX), and Greater Kuching under the ASCN. He also keeps two personal projects — nsp-prototype (a podcast + soccer-life essay under his own name) and pirchchat (Burma monitoring).
The Malaysia project extends three boards he has already shipped. Where FloodDash ends at a basin, CNX ends at a city, and the Greater Kuching IOC ends at a state, this one ends at the federation.
"A good dashboard tells you what the room is doing right now. A great dashboard tells you what the room cannot tell you. This one does both, in three languages."
— Dr Non Arkaraprasertkul, project notes, September 2026
02Historical display examples
Historical display examples recorded in the project notes for 25 September 2026. Original response archives are not attached, so these values are not independently reproducible measurements or a verified weekly aggregate. For current values and their age, open the dashboard.
What the news has been
The press panel requests Google News in EN / BM / 中文 every 10 minutes. Headlines are linked to their publishers and retain publication dates. The earlier narrative of regional stories and GDP figures had no attached sources; it is withdrawn as evidence. Read the linked reporting for current news.
An optional Facebook relay is written but not deployed — see § 06 — but it is not connected to the dashboard press panel. The panel currently uses Google News. Credentialed relays require operator authorization and a rate-limit binding.
03Mathematics
Five small maths. Nothing exotic. The dashboard earns its keep by not lying when the maths is uncertain.
3.1 Great-circle distance
The Haversine formula describes great-circle distance between latitude/longitude points. The reach bake uses an approximate local kilometre projection instead; see §3.2.
a = sin²(Δφ / 2) + cos(φ₁) · cos(φ₂) · sin²(Δλ / 2) c = 2 · atan2(√a, √(1−a)) d = R · c R = 6,371 km
Real journeys by road or river are longer. The headline always says "straight line".
3.2 Reach analysis
The formula below is conceptual. scripts/bake/analysis.py actually queries a cKDTree using an approximate local kilometre projection (longitude × 111.32 × cos latitude; latitude × 110.57), not a road network or an exact geodesic solver. It uses a 0.01° population grid, about 1.1 km at the equator. Water exposure uses a separate 0.0025° grid, about 280 m. Meta HRSL is an estimated population surface; Census 2020 district totals provide historical context, not observed people in each cell.
For every ~1 km² cell where Meta HRSL says n people live:
for each facility f ∈
{hospitals, clinics, schools}:
km[c, f] = haversine(c, f)
nearest[c] = argmin_f km[c, f]
if km[c, nearest_hospital] > 20:
people_>20km += n
total_>20km = Σ people_>20km
3.3 Freshness grading
Observation feeds carry the reading or reference-period time. Event lists carry the upstream source-check time. These meanings are labelled separately. Age against cadence decides status:
grade(f) = loading if !f.fetched error if f.error nokey if error matches /no .*key/ stale if f.obs > now + 5m stale if age > 4 · cadence delayed if age > cadence live otherwise
A 200 OK with yesterday's numbers is not "live".
3.4 Trilingual interpolation
Bahasa forecast strings are matched against a small phrase table in feeds.js and replaced with EN or 中文 equivalents. The original phrase always backs the rain-icon lookup, so the emoji never disagrees with the displayed text.
3.5 Air quality
PM2.5 for the board’s 21 cities comes from Open-Meteo CAMS model estimates in µg/m³. The concentration display uses legacy bands at 12 / 35 / 55 / 150 µg/m³; it is not a current EPA AQI calculation. DOE MyEQMS separately supplies observed Malaysian API/IPU station indices, checked with the shared three-hour currency rule. These signals have different scales and cannot be subtracted.
3.6 Reach map (visual)
04Data pipeline & diagram
Every feed runs through the same shape: fetch → normalise → redraw → grade → notify. Polling lives in core.js → poll(id, fn). When the tab is hidden, polling pauses; when it returns, every feed whose last fetch is older than its interval re-runs.
fetch(url, { headers, signal: timeout(10s) })
↓
JSON.parse → feed-specific normalise()
↓
D.{feed} → layer redraw (Leaflet)
↓
mark(id, ok, newestObservationTime)
↓
grade(f) → status: live | delayed | stale | error | nokey | loading
↓
regrade() → re-render verdict, KPIs, tab, status pill
Selected legacy feed cadences · source register above covers additional products
| Feed | Endpoint | ↻ | ok ≤ |
|---|---|---|---|
| JPS river gauges | publicinfobanjir.water.gov.my | 120 s | 45 m |
| MET warnings | api.data.gov.my | 300 s | 15 m |
| MET forecast | api.data.gov.my | 30 m | 36 h |
| USGS + MET earthquakes | earthquake.usgs.gov | 300 s | 60 m |
| GTFS-Realtime (Prasarana, myBAS, KTMB) | developer.data.gov.my | 20 s | 5 m |
| Aircraft (adsb.lol / OpenSky) | adsb.lol | 30 s | 5 m |
| AIS ships (Axiom Overwatch) | axiomoverwatch.io | 300 s | 45 m |
| Airport arrivals | opensky-network.org | 1 h | 36 h |
| Open-Meteo air quality (CAMS) | open-meteo.com | 600 s | 2 h |
| RainViewer radar | rainviewer.com | 300 s | 30 m |
| NASA FIRMS fire hotspots | firms.modaps.eosdis.nasa.gov | 30 m | 24 h |
| Blitzortung lightning (PocketWorld) | pocketworld.org | 60 s | 5 m |
| Bank Negara rates | apikijangportal.bnm.gov.my | 30 m | 4 d |
| OpenDOSM CPI | open.dosm.gov.my | 6 h | 100 d |
| JAKIM e-Solat prayer times | e-solat.gov.my | 1 h | 36 h |
| Google News (EN / BM / 中文) | news.google.com | 10 m | 24 h |
| Facebook Page posts | graph.facebook.com | — | — |
Reference groups include baked reach analysis, Census 2020, HIES 2019 and the separate HIES 2024 snapshot, facilities and boundaries. Their vintages remain separate. The Facebook Page endpoint is separate from the UI and requires operator authorization and rate limiting when credentials are configured. It reads content only when both FACEBOOK_PAGE_ID and FACEBOOK_PAGE_ACCESS_TOKEN are set on the server; otherwise it returns an empty array.
Pipeline diagram (text + flow)
Feed lifecycle state diagram (text representation)
STATES: loading → error/nokey → stale → delayed → live → (grace period) → stale
Transitions happen on fetch, observation, and interval. Each feed runs in parallel.
05Facebook integration
An optional server adapter exists in api/facebook.js but is not deployed: the Pages adapter does not route /api/facebook (it returns 404), and nothing on the board calls it. Re-enabling it means adding it back to the adapter's handler list. Its legacy Graph API version is v18.0, and compatibility must be verified before configuring it. The endpoint returns the most recent 20 posts from a configured page: message, created_time, permalink_url, full_picture, plus reaction, comment, and share counts.
Configuration is two environment variables on the Cloudflare Pages project:
FACEBOOK_PAGE_ID = 123456789 FACEBOOK_PAGE_ACCESS_TOKEN = EAAxxxxxx...
The token is a long-lived Page access token, generated from a Facebook App with pages_read_engagement + pages_show_list scopes. Without it the endpoint returns { posts: [], note: "..." } and the dashboard continues to use Google News alone.
The front-end uses Google News independently. A successful configured Facebook response has a five-minute cache; this page does not establish that credentials, permissions or the legacy API version currently work.
Example wire shape
GET /api/facebook
200 OK
{
"source": "Facebook Graph API",
"configured": true,
"posts": [
{
"id": "123_456",
"message": "Today at 14:32 MYT...",
"time": "2026-09-25T06:32:00+0000",
"url": "https://facebook.com/...",
"image": "https://scontent...",
"reactions": 432,
"comments": 41,
"shares": 18
}
]
}
Failure mode
If authorized access is allowed but the token has expired or the page is unpublished, the relay returns 424 (Failed Dependency) with error set. The press tab uses Google News independently.
06Safety & limitations
"This board is decision support, not an official warning."
The dashboard never claims it can establish that an area is safe. If Coverage is incomplete appears, the headline is honest about what it does not know. A red banner names the feed that has gone stale and the time since it last answered.
What the dashboard does not claim
- Hospital locations are reference data. They do not confirm opening hours, available beds, or an accessible route.
- Reach distances are straight line. Real journeys by road or river are longer.
- Census 2020 and HIES 2019 are historical.
- CAMS air quality is modelled. DOE API/IPU is observed at stations; neither establishes conditions at every address.
- Ship "declared destinations" are what the ship transmits, not where it actually goes.
- Rain radar is 10-minute frames.
- Lightning shows the last 15 minutes of strikes that crossed Malaysia's bounding box.
- The optional Facebook adapter requests public Page posts and aggregate reaction/comment counts, not DMs or comment bodies. It is not a current dashboard source.
What it does claim
- If a river gauge reads DANGER within the last 45 minutes, that is the operator-reported category for that station.
- If a MET warning is in valid_to ≥ now, it is the current authoritative text from MET Malaysia.
- If the verdict says Not enough data, coverage is incomplete or a reading cannot be used; upstream failure and dashboard defects are both possible.
07Action queue and join logic
The question: a board that lists 1,306 gauges, 5 warnings, 293 fires and 8 KPIs has given the reader homework, not help. Nobody under a flood warning needs more numbers. They need the three places to look first, in order, with the reason attached to each.
So the briefing tab now carries an action queue. Cards still summarise one feed each; the queue joins feeds. Five joins, recomputed on every poll, each gated by the same 45-minute freshness rule as the verdict:
- Flood action queue. Every elevated, current gauge is scored by converging evidence — its JPS category, its trend word, rain at the station today, rain in the state forecast, a MET warning naming the state, and how many people live on low ground in its district. Top five, worst first.
- Climb memory. The flood loader keeps the previous poll's water levels, so a gauge climbing ≥ 0.3 m/h earns urgency even before JPS moves its category. Rise rate is measured, not guessed.
- Evacuation-hard zones. Districts far from help (≥ 20% of people over 20 km from a hospital, or ≥ 15 km average) while a hazard is live in their state. If this floods, help is far — that district needs local review of access and official evacuation instructions.
- Silent zones. Districts with ≥ 5,000 people on low ground and no live gauge watching them. The unknown, quantified, in the gaps card.
- Rain on high rivers. Rain falling now (capital-city precipitation ≥ 2 mm/h) onto a state whose gauges are already elevated. The model publishes a 15-minute total, so the test runs on its hourly equivalent; the board prints the total and names the window.
The scoring, in full
score = category DANGER 100 · WARNING 60 · ALERT 30
+ rising trend +25
+ climbing ≥ 0.3 m/h +15
+ rain at the station +10
+ rain in state forecast +10
+ MET warning in state +15
+ low-ground people nearby +min(20, pop / 2000)
A receding DANGER keeps its category but gains no rise bonus, and carries the line falling, but still above the line — verify before standing down. Stale, disabled, invalid and UNKNOWN readings never enter the queue; they stay grey on the map.
What the queue refuses
It never claims a gauge crossed a threshold — one poll cannot see motion, only climb memory across polls can. It never forecasts an address: a nearby gauge is not a flood forecast for your street. And a silent district is reported as unknown, never as safe.
Worked example: the morning of 27 September 2026
At 11:00 MYT the relay held 1,306 stations with 503 current readings (newest 12 minutes old). Five MET warnings were in force — strong winds and rough seas naming Kelantan, Pahang, Perak, Terengganu, Sabah and Sarawak, plus thunderstorms over Kelantan, Sabah, Sarawak and Labuan. The queue ranked:
| # | Gauge | Evidence |
|---|---|---|
| 1 | Sg. Bidor di Changkat Jong, Hilir Perak — DANGER 3.92 m against a 3.5 m line | rising · 1 mm at station · MET warning in Perak · 38,278 on low ground nearby |
| 2 | Sg. Bernam di Jambatan SKC, Hulu Selangor — DANGER 19.21 m against 19.1 m | receding · reading 42 min old · verify before standing down |
| 3 | Sg. Luit di Kg. Subuh, Maran, Pahang — WARNING, steady | MET warning in Pahang |
| 4 | Sg. Galas di Limau Kasturi, Gua Musang, Kelantan — ALERT, receding | 25 mm at station · MET warning in Kelantan |
Hilir Perak holds 141,959 people (Census 2020), 38,278 of them on low ground by rivers — and 0% live over 20 km from a hospital. So the morning's answer was specific: the danger in Perak was exposure, not distance to care. A different district with the same gauge would have ranked an evacuation-hard zone alongside it. That distinction — what kind of danger, not just where — is the whole point of the joins.
"Raw data tells you what the room is doing. Joins tell you what to do about it. If an insight cannot change someone's next hour, it does not belong on this board."
— Dr Non Arkaraprasertkul, insight-layer notes, September 2026
Method and authorship
Project notes, September 2026. The worked example is historical and its raw response archive is not supplied. Formulations are implemented in public/js/insights.js, rendered trilingually in the briefing tab, and held to account by tests/insights.test.mjs — nine tests covering ranking, evidence convergence, climb memory, zone logic, Bahasa forecast parsing, and trilingual copy coverage.
08Reach analysis (detailed)
The reach analysis is a static feed — it never changes on the interval. It identifies historical population estimates far from mapped services. It does not establish travel time, hospital capacity or personal safety.
Reach calculation details
Inputs are Meta HRSL population estimates aggregated to the baked grid and Overture facility reference locations. Census 2020 supplies the historical district comparison; it does not supply population per HRSL cell. The implemented nearest-service calculations cover hospitals, clinics and schools.
for each HRSL cell c:
for each facility f in [hospitals, clinics, schools]:
km[c, f] = haversine(c, f)
nearest[c] = argmin_f km[c, f]
if km[c, nearest_hospital] > 20:
people_>20km += population(c)
total_>20km = Σ people_>20km
Real travel by road or river is longer than straight line. The headline says straight line because it is the fastest estimate and conservative (if even the straight line is too long, road travel is definitely too long). That conservativism is the whole point of the metric.
Visualization: the reach map
Reach analysis metrics
- People within 20 km: Σ population(c) for all cells where the nearest hospital ≤20 km
- People beyond 20 km: Σ population(c) for all cells where the nearest hospital >20 km
- Percent beyond 20 km: total_beyond / total_population × 100
- Average distance to nearest hospital: Σ population(c) × km[c, nearest[c]] / Σ population(c) (population-weighted)
- District reach score: no composite score is calculated; distance and population exposure remain separate
Applications in evacuation planning
The briefing flags districts with a hospital-distance gap when a current hazard names their state. This is a review priority, not an evacuation order or route assessment. Straight-line distance does not establish whether a hospital can be reached during a flood.
Data sources validation
- HRSL grid cells: no road-density validation study is supplied; the former R² claim is unsupported.
- Facility locations: Overture reference records; the tests check plausible counts and the Semporna location correction, not a complete official-register audit.
- Population totals: tests require the national baked-grid total to fall within 3% of Census 2020. That reconciliation does not validate each cell or census tract.
Limitations
- Static data ignores seasonal migration (students, workers)
- Road network distances not calculated (approximate local kilometre projection)
- Facility accessibility (road condition, traffic, language) not considered
- Weather-dependent routes not factored
09Monitoring, alerts and notifications
The dashboard does not act on alerts; it displays them for human decision-making.
Alert channels
- Verdict banner: dangerous/warning/alert level at the top of the dashboard, updates every poll
- Warnings tab: detailed MET warning text, locations, timing
- Action queue: ranked list of what to look at first (see §08)
- Stations list: flood gauge status, colored by current level
- Notifications: toast on copy (sitrep), share link, error messages
Alert triggers
Alert appears when:
- ≥1 gauge reports DANGER or WARNING and is current (≤45 minutes old)
- ≥1 MET warning has started and remains within its validity period
- Historical exposure alone never raises a current national hazard verdict.
Alert escalation
When an alert appears, the following additional visualizations activate:
- Gauge markers and MET warning areas are shown; they are not modelled inundation extents.
- Warnings tab detailed text
- Action queue highlights affected areas
- Status pills update to show affected gauge count
Current categories require current readings; MET notices expire by their validity window. Missing or aged readings produce a coverage gap, not an all-clear.
10Credits & acknowledgments
Built by Dr Non Arkaraprasertkul as a gift to depa (Digital Economy Promotion Agency of Thailand) and the ASEAN Smart Cities Network (ASCN) 2026. Fonts: Jost by indestructible type*; Noto Sans SC. Independent demonstration. Not an official product of the Government of Malaysia.
Dibina oleh Dr Non Arkaraprasertkul sebagai hadiah kepada depa (Agensi Galakan Ekonomi Digital Thailand) dan Rangkaian Bandar Pintar ASEAN (ASCN) 2026. Fon: Jost oleh indestructible type*; Noto Sans SC. Demonstrasi bebas. Bukan produk rasmi Kerajaan Malaysia.
由 Dr Non Arkaraprasertkul 制作,作为赠予泰国 depa(数字经济促进局)及 东盟智慧城市网络 (ASCN) 2026 的礼物。字体:Jost(indestructible type*);Noto Sans SC。独立演示项目,并非马来西亚政府官方产品。
Data attribution
- Weather warnings, forecast, earthquakes: MET Malaysia via api.data.gov.my.
- River gauges: Jabatan Pengairan dan Saliran (JPS) / DID via publicinfobanjir.water.gov.my.
- Census 2020 + HIES 2019 + boundaries: DOSM via github.com/dosm-malaysia/data-open (CC BY 4.0).
- Population grid: Meta HRSL (CC BY 4.0).
- Facilities: Overture Maps 2026-08 (CC BY 4.0)
- Terrain: AWS Terrarium tiles, derived from Copernicus / SRTM.
- Waterways: Overture Maps 2026-08 (CC BY 4.0)
- Buses + trains: Prasarana Malaysia, myBAS Johor, KTMB via GTFS-Realtime.
- Aircraft: adsb.lol and OpenSky Network.
- Arrivals: OpenSky Network or AirLabs.
- Ships: Axiom Overwatch (CC-BY 4.0) + VesselFinder.
- Fire hotspots: NASA FIRMS (VIIRS, public)
- Satellite: NASA GIBS, JMA / NASA GIBS (Himawari-9), JRC, Copernicus Sentinel-1/-2, ECMWF via Open-Meteo.
- Lightning: Blitzortung via PocketWorld.
- Rates: Bank Negara Malaysia.
- CPI, unemployment: OpenDOSM.
- Prayer times: JAKIM e-Solat.
- News: Google News RSS (EN / BM / 中文) · optional Facebook adapter remains separate and unverified
License
Application code: MIT. Fonts: OFL (Jost, Noto Sans SC). Data layers retain product-specific licences and attribution. Public access alone does not establish a redistribution licence; consult the source register and provider terms. The Malaysia state outline comes from DOSM's data-open repository. This repository is private.
— end —