Leaflet + WebGPU? Skip WebGPU? What the GPU actually buys here (Hydrant Analysis)

**Note** from Bead: Hydrant Analysis · [canonical source](https://redfish.acequia.io/guerin/.agents/6e958310-6390-4634-95bf-dbb9a52d2666/2026-07-07/notes/leaflet-vs-webgpu-advice.md) · session 2026-07-07 · discussion: Talk: Hydrant Analysis

*2026-07-08, bead [6e958310](https://redfish.acequia.io/guerin/.agents/6e958310-6390-4634-95bf-dbb9a52d2666/about.md). Answering Stephen's question after the five sketches: "is this better to just do with webgpu in leaflet? and further, should we skip webgpu? what's the performance benefit?" Numbers below are measured from the deployed sketches unless marked as estimates.*

## The measured performance picture | Task | CPU JS (flatbush-indexed, estimate) | GEOS / PostGIS (upstream) | WebGPU (measured in sketches) | |---|---|---|---| | Spatial join, 110k points → 262 NTAs | ~50–150 ms, exact | seconds (incl. DB round-trip), exact | 49–220 ms incl. ID-raster rebuild + readback; dispatch itself ~1–2 ms; quantized | | Coverage (100m buffer + union + %), Manhattan | geos-wasm worker-parallel ~0.5–1 s; pure-JS clipping: seconds | 1–5 s single-thread | 30–300 ms full rerun, cheap enough to bind to a slider | | Rendering 110k points | Canvas 2D: ~10 fps; SVG/DOM: unusable | n/a | WebGL or WebGPU: 60 fps easily | | Data load | the real win lives here: native EPSG:2263 → linear scale, typed-array binaries, zero parse | | | Three honest conclusions fall out: 1. **For counting and joining at this data size, WebGPU buys nothing.** Readback latency eats the dispatch win; an indexed CPU join is the same speed, exact instead of quantized, and simpler. (It also settles sketch 4's exactness question for free: CPU is the arbiter.) 2. **For the coverage instrument, WebGPU is a 10–100× win and changes the interaction class.** Buffer-union goes from a batch query you run once (GEOS, seconds) to a parameter you scrub (30–300 ms, and most of that is the tally readback; the splat itself is per-frame cheap). This is the only piece of the current work the GPU is irreplaceable for. 3. **The biggest performance lever so far wasn't compute at all.** It was the source-format find (state-plane feet already in the data → one multiply to meters) and preprocessing to GPU/CPU-shared typed arrays. Data layout beat engine choice.

## "WebGPU in Leaflet" — yes, and the sketches are already shaped for it Every analysis pass in the sketches (ID raster, join, splat, tally) runs **offscreen**; none of it depends on taos-engine or on being on-screen. So the compute layer ports to a Leaflet page as-is: - Numeric results (counts, densities, coverage %) → DOM tables, same as now. - Coverage / choropleth textures → colorize compute → `ImageBitmap` → `L.imageOverlay`, regenerated per slider move. Leaflet is perfectly happy re-pinning a bitmap at ~100 ms cadence. - 110k points → NOT vanilla Leaflet markers (SVG/DOM dies; Canvas renderer strains). Use Leaflet.glify / a deck.gl-leaflet interop layer, or accept clustering. - CRS: Leaflet is EPSG:3857; we already carry lon/lat per hydrant (`hydrants-lonlat.f32`), so no new preprocessing. What Leaflet buys: basemaps, pan/zoom/mobile UX, attribution, popups, an API the existing Redfish/Simtable apps already use (cross-app consistency — this is Debbie's argument, and it's a real one). What it costs: a second rendering world beside taos, and the 110k-point layer needs a WebGL plugin anyway. **MapLibre GL is the variant worth weighing**: GPU vector basemap, native 100k+ point layers, and it aligns with sketch 5's "vector tiles" option. Pick Leaflet for consistency with the existing app family; pick MapLibre if the basemap itself should be GPU-vector and restyleable.

## "Should we skip WebGPU?" — mostly, but keep one piece At 110k points / 262 polygons you could ship the whole analysis with **zero WebGPU**: flatbush CPU join (exact, fast enough), geos-wasm in workers for coverage (sub-second, though not scrubable), WebGL for point rendering (WebGL ≠ WebGPU; WebGL is universal and sufficient for rendering at this scale). You'd give up two things: - the **live coverage instrument** (the radius slider, and the path to time-sweeps and multi-parameter scrubbing), which was the strongest demo of the whole exercise; - **headroom**: citywide-all-layers or multi-city (1M+ points) keeps millisecond latencies on GPU while CPU joins creep into seconds. Stephen's own answer ("flat until multi-city, then revisit") suggests headroom matters eventually, not now. Browser support (early 2026): Chrome/Edge solid, Firefox shipped on Windows, Safari recent. Fine for internal instruments, still worth a fallback for anything public.

## Recommended shape - **Analysis map = Leaflet (or MapLibre), not taos.** Basemap, panning, familiar UX for free; consistent with existing apps. - **Joins/counts = CPU** (flatbush + typed arrays). Exact, simple, no GPU dependency, same speed at this scale. - **Coverage instrument = WebGPU offscreen → image overlay**, behind a `navigator.gpu` check with a geos-wasm "compute once" fallback. This is the only WebGPU that earns its keep. - **taos-engine = the 3D/terrain presentation shell only** (sketch 5's role: fly-through, drape, ECEF), not the 2D analysis surface. - Shared substrate for all of it: the preprocessed typed-array binaries (already both CPU- and GPU-consumable).

## Open questions for Stephen - Leaflet (app-family consistency) or MapLibre (GPU vector basemap, vector-tiles direction)? - Does the coverage instrument justify its WebGPU dependency in the product, or is "compute once per parameter change in a worker" (geos-wasm, ~1 s) actually acceptable UX? - When multi-city arrives, does the GPU join come back (headroom), or does the exact CPU path scale out with workers instead?