osm cockpit: draw the basemap from the bake, not from a tile CDN - #117
Conversation
The map was someone else's raster with our data sprinkled on top: OSM_TILE_URL pointed at tile.openstreetmap.org and SAT_TILE_URL at Esri, and the 1.42 GB Berlin bake supplied only the feature dots plus the shape of the one feature you clicked. "The map pyramid and the slab's row key are one and the same address" was true as arithmetic while every pixel was rented — and a deployed host on the OSMF tile server also runs against its usage policy. The decode half already existed (.chains holds every way's z=32 vertex chain; query_geometry reads one by row index). This serves them per tile and draws them client-side in the SVG layer #116 added. Server: - ShapeClass + class_for_tags: the semantic category is derived from the tags the bake already stored and served per shape. Shipping every shape's tags would multiply the payload by the tag fan-out for data a viewer wants about one shape at a time. FeatureGeometryOut carries the same field, so the click path and the basemap share ONE classification in Rust — the JS classFor(tags) rule is deleted, not duplicated. - simplify_cells: drops vertices landing on the same (sub)pixel, as an integer compare on the codec's own z32 grid — the display's resolution IS the tolerance, so there is no constant to tune. First/last always survive, so a ring that survives stays closed. - geometry_row_budget + query_tile_geometry, reusing overview_sample so the cascade-cell selection keeps isolated features. Reports total/sampled/returned/malformed separately: a thin basemap must read as LOD, and a corrupt sidecar must not look like an empty neighbourhood. Client: - vector is the DEFAULT basemap; the raster skins stay behind the toggle as the reference to check our render against. - The layer is retained across pan frames and rebuilt only on zoom. A pan changes the transform, not world-pixel coordinates, so rebuilding per frame would be the same n^2 append the drawnCells comment warns about. - The tile-source readout names the source actually in use. Mobile (bundled per operator request): the page was unusable on a phone. grid-template-columns:1fr 320px left the map 70px wide on a 390px viewport, and drag was bound to mousedown/mousemove with no touch-action, so the map could not be panned by finger at all. Stacked layout under a 720px breakpoint, pointer events with a tracked pointerId, and touch-action:none. Measured before/after in headless Chromium against the shipped page, with the bake endpoints stubbed (the container has no slab): before after phone map 70x844 (18% of vp) 390x490 (100%) external req 141-277 to tile.osm.org 0 touch pan does not move the map moves the map raster <img> 18-42 0 Tests: 95 passed, 0 failed. The five new ones are two-sided — class precedence is checked in both tag orders (a first-match implementation returns Green for one of them), and simplification is pinned at z4/z22/z23 so it tracks pixel_shift's real arithmetic rather than "fewer points when zoomed out". NOT verified against the real bake: this container lost the slab on restart and AWS_S3_BUCKET_NAME is unset, so the render path over real Berlin data is unverified until the deploy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NMeiLmtDKhomJNSo2ecbJw
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_0b9dc9bc-cf31-40c6-961f-9e4912c8cbdb) |
Closes gap #1 from #116 ("the target is an area-fill base layer"). The map was someone else's raster with our data sprinkled on top —
OSM_TILE_URL→tile.openstreetmap.org,SAT_TILE_URL→ Esri — and the 1.42 GB Berlin bake supplied only the feature dots plus the shape of the one feature you clicked. "The map pyramid and the slab's row key are one and the same address" was true as arithmetic while every pixel was rented; a deployed host on the OSMF tile server also runs against its usage policy.The decode half already existed (
.chainsholds every way's z=32 vertex chain). This serves them per tile and draws them client-side in the SVG layer #116 added.Measured, before → after
Headless Chromium against the shipped page, bake endpoints stubbed (this container has no slab). Identical harness run against
mainfor the baseline, so the numbers are two-sided rather than self-reported:main)tile.openstreetmap.org<img>Server
ShapeClass+class_for_tags— the semantic category is derived from tags the bake already stored, and served per shape. Shipping every shape's tags would multiply the payload by the tag fan-out for data a viewer wants about one shape at a time (the argumentFeatureOut::idxalready makes).FeatureGeometryOutcarries the same field, so the click path and the basemap share one classification in Rust — the JSclassFor(tags)rule is deleted rather than duplicated. The client keeps only the style table.simplify_cells— drops vertices landing on the same (sub)pixel, as an integer compare on the codec's own z32 grid: a tile is 256px, so a world pixel at zoomzis the cell shifted right by32 - (z + 8). The display's resolution is the tolerance, so there is no constant to tune. First and last vertices always survive, so a ring that survives stays closed.query_tile_geometry— reusesoverview_sample, so the cascade-cell selection that keeps isolated features applies here too. Reportstotal/sampled/returned/malformedseparately: a thin basemap must read as LOD rather than missing data, and a corrupt sidecar must not masquerade as an empty neighbourhood.GET /api/osm/geometry/tile/:z/:x/:y, registered after the:idxform.Client
vectoris the default basemap; the raster skins stay behind the toggle as the reference to check our render against, now an explicit opt-in.drawnCellscomment warns about.tile.openstreetmap.orgwhile nothing was being fetched from it — the exact confusion this view had to stop causing).Mobile (bundled per request)
grid-template-columns:1fr 320pxleft the map 70px wide on a 390px viewport, and drag was bound tomousedown/mousemovewith notouch-action, so the map could not be panned by finger at all. Stacked layout under a 720px width breakpoint (width, not device class — a narrow desktop window has the same problem), pointer events with a trackedpointerIdso a second finger can't fight the first, andtouch-action:none.Tests
95 passed, 0 failed. The five new ones are two-sided by construction: class precedence is asserted in both tag orders (a first-match implementation returns Green for one of them), and simplification is pinned at z4/z22/z23 so it tracks
pixel_shift's real arithmetic rather than a vague "fewer points when zoomed out". One of them failed first and was my expectation that was wrong — z22 has a 1-bit shift, so 201 of 400 is correct and full precision starts at z23.Not verified
The render path over real Berlin data is unverified. This container lost the slab on restart and
AWS_S3_BUCKET_NAMEis unset, soensure_slab_localcannot hydrate; every measurement above uses synthetic shapes through the real client code. Worth watching on the deploy: whether the overview zoom looks too sparse, since the budget is spent on rows and rows that are nodes carry no chain. If it does, the next rung is recorded in the plan (over-sample, then re-spread the chain-bearing survivors throughoverview_samplea second time).Plan:
claude-notes/plans/2026-08-11-osm-vector-basemap.md🤖 Generated with Claude Code
https://claude.ai/code/session_01NMeiLmtDKhomJNSo2ecbJw
Generated by Claude Code