Shared deck.gl + MapLibre map utilities, extracted across runsascoded map projects so each stops hand-rolling the same pieces.
Status: proof-of-concept (
0.0.x). Only the lowest-risk, genuinely duplicated pieces are here so far — see Roadmap.
Four sibling projects render interactive maps; three are deck.gl:
| Project | Engine | deck.gl | 3D | Choropleth layer | Basemap |
|---|---|---|---|---|---|
jc-taxes |
deck.gl + MapLibre | 9.x | yes | GeoJsonLayer extruded |
MapLibre / CARTO vector |
crashes |
deck.gl + MapLibre | 8.9 | yes | ColumnLayer (H3 hexes) |
MapLibre / Stadia raster |
household-vehicles |
deck.gl | 9.x | yes | GeoJsonLayer extruded |
deck.gl TileLayer / Stadia |
ctbk |
Leaflet | — | no | Circle / Polyline |
Leaflet / Stadia raster |
They're divergent enough that a single <ChoroplethMap> component would be a
leaky abstraction — extruded-polygon vs. H3-column maps are different shapes.
So this is a toolkit of small generic pieces, not one mega-component.
The clearest evidence it's worth extracting: useTouchPitch is currently
byte-identical in jc-taxes and crashes.
pnpm add @rdub/deck-mapreact is an (optional) peer dependency — only useTouchPitch needs it.
Two-finger vertical-drag → pitch gesture for deck.gl on touch devices, working
around deck.gl#4853 (pinch beats two-finger-pan). Operates on a controlled
viewState. Takes a plain (next) => void setter — so it works with React
useState and with non-updater setters like use-prms' useUrlState.
import { useTouchPitch } from '@rdub/deck-map'
const isPitchingRef = useTouchPitch({ viewState, setViewState, maxPitch: 85 })
<DeckGL
viewState={viewState}
onViewStateChange={({ viewState: vs }) => {
if (isPitchingRef.current) return // ignore deck.gl echoes during pitch
setViewState(vs)
}}
controller={{ touchRotate: true }}
/>Theme-aware ('light' | 'dark') basemap config — stop copy-pasting
alidade_smooth[_dark] URLs.
import { stadiaTileUrl, stadiaRasterStyle, cartoVectorStyleUrl } from '@rdub/deck-map'
stadiaTileUrl('dark') // deck.gl TileLayer `data`
stadiaRasterStyle('dark') // MapLibre <Map mapStyle={...}>
cartoVectorStyleUrl('light') // CARTO vector style URLPhased, low-risk first:
- P1 (in progress) —
useTouchPitch, basemap presets. Next: camera helpers (fitBounds,metersPerPixel), a resizable-map container hook (drag-resize +sessionStorageheight + reset), a pitch-slider widget. - P2 — a thin
<DeckMap>shell:<DeckGL>+ MapLibre basemap + controlledviewState+useTouchPitchwired in; consumers pass their ownlayers. Migratehousehold-vehiclesandjc-taxes. - P3 — upgrade
crashesfrom deck.gl 8.9 → 9.x, then migrate it. - P4 (optional) —
ctbkoff Leaflet onto deck.gl — a rewrite, separate call.
Camera URL-param sync is intentionally out of scope — use-prms's
viewStateParam already covers it; consumers should adopt that directly.
pnpm install
pnpm build # tsup → dist/ (ESM + CJS + d.ts)
pnpm test # vitest
pnpm typecheck # tsc --noEmit