cockpit-server: OSM tile material — WebMercator z/x/y ↔ HHTL + tile source#73
Conversation
…ource
The Geo-domain (0x0F) map material for the /OSM cockpit: where OSM maps come
from, and how a slippy-tile address becomes an HHTL key.
- OSM_TILE_URL — the canonical slippy-tile source
(tile.openstreetmap.org/{z}/{x}/{y}.png); the client fetches the raster, the
cockpit only computes the address (no network on the request path).
- lonlat_to_tile — WebMercator (EPSG:3857) forward, no PROJ
(asinh(tan φ) = ln(tan+sec)).
- morton_interleave / tile_to_hhtl — z/x/y quadtree → 48-bit Morton → the three
16-bit HHTL tiers (HEEL/HIP/TWIG), coarse zoom left-aligned into HEEL
(tier = level>>3). The map pyramid and the semantic cascade are ONE address
(D-BOTHCASC), per docs/MERCATOR-HHTL-HELIX-MAP.md.
- Routes: GET /api/osm/locate?lon=&lat=&z= and /api/osm/tile/:z/:x/:y — return
tile address + source URL + HHTL key.
8 unit tests (null-island center tile, Berlin=(8802,5373), east→+x/south→+y,
Morton round-trip, HHTL round-trip at native depth, coarse-zoom-in-HEEL,
adjacent-tiles-share-HEEL).
Verification note: the tile MATH is verified standalone via rustc (all
assertions pass). The full `cargo test -p cockpit-server` cannot run in this
sandbox — the crate's pre-existing `v8` transitive dep fails its build.rs
(needs gn/ninja tooling not present here); this is orthogonal to this change,
which adds only std+serde+axum code. The unit tests run under CI where the v8
toolchain is available.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYvNjD8M8LMNYbRy3gq2FP
📝 WalkthroughWalkthroughAdds a new ChangesOSM Tile Locate and Metadata Feature
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant Router
participant OsmTilesHandler
participant TileMath
Client->>Router: GET /api/osm/locate?lon&lat&z
Router->>OsmTilesHandler: osm_locate_handler(LocateQuery)
OsmTilesHandler->>TileMath: lonlat_to_tile(lon, lat, z)
TileMath-->>OsmTilesHandler: (x, y)
OsmTilesHandler->>TileMath: tile_to_hhtl(z, x, y)
TileMath-->>OsmTilesHandler: Hhtl
OsmTilesHandler-->>Client: JSON {tile_url, source, hhtl, geo_domain}
Client->>Router: GET /api/osm/tile/:z/:x/:y
Router->>OsmTilesHandler: osm_tile_meta_handler(Path)
OsmTilesHandler->>TileMath: resolved_tile(z, x, y)
TileMath-->>OsmTilesHandler: (z', x', y')
OsmTilesHandler-->>Client: JSON tile metadata with resolved ancestor
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
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 |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 742b540. Configure here.
| heel: (code >> 32) as u16, | ||
| hip: (code >> 16) as u16, | ||
| twig: code as u16, | ||
| } |
There was a problem hiding this comment.
Deep zoom HHTL uses wrong bits
Medium Severity
For slippy requests with z above HHTL_DEPTH, tile_to_hhtl only caps z to 24 and sets the left-align shift to zero, leaving x and y at the finer zoom scale. Morton encoding then uses the low 24 bits instead of the quadtree prefix, so /api/osm/tile/:z/:x/:y can return an hhtl key that does not match the requested tile.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 742b540. Configure here.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 742b540c77
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let z = z.min(HHTL_DEPTH); | ||
| let shift = HHTL_DEPTH - z; | ||
| let code = morton_interleave(x << shift, y << shift); |
There was a problem hiding this comment.
Normalize coordinates when clamping zoom
In the /api/osm/tile path, this clamps only z before encoding but leaves the over-depth x/y unchanged. For a valid slippy address with z > 24, the HHTL key should either be rejected or represent the z=24 parent tile (for example, z=25 x=2 has parent x=1), but this encodes x=2 and drops any bits above the 24-bit lane, so the returned hhtl no longer identifies the tile URL in the same response and high-zoom tiles can alias incorrectly.
Useful? React with 👍 / 👎.
Review fix (Bugbot Medium + codex P2 on #73): for z > 24, tile_to_hhtl only capped z and set the left-align shift to 0, leaving x/y at the finer scale so Morton encoded the LOW 24 bits — the HHTL key didn't match the requested tile and deep tiles could alias. Now a zoom deeper than the native depth resolves to its z=24 ANCESTOR: the excess low bits of x/y are dropped (x >> (z-24)), so the key is always a valid prefix of the tile and two children of one z=24 tile share it. New `resolved_tile()` exposes the ancestor z/x/y, and /api/osm/tile/:z/:x/:y now returns a `resolved` field so the key and address never silently describe different tiles. Locate is unaffected (its z is already clamped ≤ 24). Test `over_depth_zoom_folds_to_its_native_ancestor` locks it (verified standalone via rustc: hhtl(25,2,3) == hhtl(24,1,1), ≠ the old low-bit value). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYvNjD8M8LMNYbRy3gq2FP
The Geo-domain (0x0F) sibling of the FMA body-helix cockpit. A self-contained `/osm` page (the /mri page pattern — one inline HTML string, no build step, no external JS): - pan/zoom OSM raster tiles fetched directly from the standard source (tile.openstreetmap.org), with attribution; - click any point → GET /api/osm/locate → the tile's z/x/y + source URL + the three HHTL tiers (HEEL/HIP/TWIG) shown live; - the JS slippy math mirrors cockpit-server::osm_tiles; the HHTL key is resolved server-side so the map pyramid and the cascade address are one source of truth. Route: GET /osm. Verification note: the embedded JS is validated via `node --check` (3.1 KB, OK) and the Rust raw-string delimiter is confirmed collision-free. The full `cargo test -p cockpit-server` cannot run in this sandbox (pre-existing `v8` transitive dep fails its build.rs — no gn/ninja toolchain); the page handler is a trivial `Html<String>` const mirroring the existing `mri_page_handler`, wired like every other cockpit route. Stacks on #73 (the osm_tiles material it calls). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYvNjD8M8LMNYbRy3gq2FP
… on click Review fixes (Bugbot + codex on #74): - Pan-end `mouseup` fired a `click` that ran /api/osm/locate for the release point; `if(drag) return` never applied because `mouseup` nulls `drag` first. Track a `moved` flag (set on real drag) and suppress the trailing click. - The zoom +/− controls live inside #map, so their clicks bubbled to the map's click→locate handler. `e.stopPropagation()` on both. - The click path sent `x2lon(fx)` without wrapping `fx` into [0,2^z); at low zoom / across ±180° that yields an out-of-range longitude the server clamps to an edge tile. Wrap `fx` modulo 2^z before converting. Embedded JS re-validated via `node --check`. (osm_tiles over-depth fix rides in from #73, which this branch now rebases onto.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYvNjD8M8LMNYbRy3gq2FP
|
Review fix pushed ( Generated by Claude Code |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
crates/cockpit-server/src/osm_tiles.rs (1)
25-28: 🧹 Nitpick | 🔵 TrivialConfirm OSM tile usage-policy compliance for downstream clients.
The server only computes addresses, but the returned
source/tile_urlpoint clients attile.openstreetmap.org, whose usage policy requires a valid identifyingUser-Agent/Referer, mandatory attribution, and no bulk/heavy downloading. Since this endpoint effectively directs client traffic there, ensure the consuming client honors those terms (or route through a self-hosted/commercial tile provider) to avoid getting blocked.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/cockpit-server/src/osm_tiles.rs` around lines 25 - 28, The OSM tile URL constant in OSM_TILE_URL is fine as an address template, but the returned source/tile_url must only be used by clients that comply with OpenStreetMap’s usage policy. Update the code path around OSM_TILE_URL and any consumer of the returned tile source so downstream clients send a valid identifying User-Agent/Referer, include required attribution, and avoid bulk/heavy requests; if that cannot be guaranteed, switch to a self-hosted or commercial tile provider instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/cockpit-server/src/osm_tiles.rs`:
- Around line 163-176: The osm_tile_meta_handler currently accepts raw Path
coordinates and can emit misleading metadata for out-of-range x/y values. Add a
bounds check in osm_tile_meta_handler (before calling tile_to_hhtl,
resolved_tile, and tile_url) to reject x or y outside 0..2^z, or the equivalent
resolved 0..2^24 range, and return a 400 response instead of JSON for invalid
tile coordinates.
- Around line 121-128: `resolved_tile` can panic or compute the wrong parent
when `z - HHTL_DEPTH` is 32 or more, so update the shift logic to use a
checked/saturating right shift before returning the ancestor tile. Make the fix
inside `resolved_tile(z, x, y)` and ensure large zoom values safely clamp to the
HHTL_DEPTH ancestor with x/y resolving to 0 when the shift is too wide. Add a
regression test for the `resolved_tile` case with very large inputs, including
`resolved_tile(56, u32::MAX, u32::MAX)`, to verify it returns the expected
clamped tile.
---
Nitpick comments:
In `@crates/cockpit-server/src/osm_tiles.rs`:
- Around line 25-28: The OSM tile URL constant in OSM_TILE_URL is fine as an
address template, but the returned source/tile_url must only be used by clients
that comply with OpenStreetMap’s usage policy. Update the code path around
OSM_TILE_URL and any consumer of the returned tile source so downstream clients
send a valid identifying User-Agent/Referer, include required attribution, and
avoid bulk/heavy requests; if that cannot be guaranteed, switch to a self-hosted
or commercial tile provider instead.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: b3dc5b7f-af88-45db-97c7-7d74bc697c2b
📒 Files selected for processing (2)
crates/cockpit-server/src/main.rscrates/cockpit-server/src/osm_tiles.rs
| pub fn resolved_tile(z: u32, x: u32, y: u32) -> (u32, u32, u32) { | ||
| if z > HHTL_DEPTH { | ||
| let excess = z - HHTL_DEPTH; | ||
| (HHTL_DEPTH, x >> excess, y >> excess) | ||
| } else { | ||
| (z, x, y) | ||
| } | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the relevant file and inspect the surrounding implementation.
git ls-files 'crates/cockpit-server/src/osm_tiles.rs'
echo '---'
wc -l crates/cockpit-server/src/osm_tiles.rs
echo '---'
sed -n '1,260p' crates/cockpit-server/src/osm_tiles.rsRepository: AdaWorldAPI/q2
Length of output: 10125
🏁 Script executed:
#!/bin/bash
set -euo pipefail
cat > /tmp/shift_check.rs <<'RS'
fn main() {
let x = 1u32;
let s = 32u32;
println!("{}", x >> s);
}
RS
echo "rustc version:"
rustc --version || true
echo '--- debug build ---'
rustc /tmp/shift_check.rs -o /tmp/shift_check_debug 2>/tmp/shift_check_debug.err && /tmp/shift_check_debug || { code=$?; cat /tmp/shift_check_debug.err; echo "exit=$code"; }
echo '--- release-like build ---'
rustc -O /tmp/shift_check.rs -o /tmp/shift_check_release 2>/tmp/shift_check_release.err && /tmp/shift_check_release || { code=$?; cat /tmp/shift_check_release.err; echo "exit=$code"; }Repository: AdaWorldAPI/q2
Length of output: 352
Guard resolved_tile against over-wide shifts
z > 24 can make excess >= 32, so x >> excess / y >> excess may panic in debug builds and produce the wrong ancestor on GET /api/osm/tile/:z/:x/:y. Use a checked/saturating shift and add a regression test for resolved_tile(56, u32::MAX, u32::MAX) == (24, 0, 0).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/cockpit-server/src/osm_tiles.rs` around lines 121 - 128,
`resolved_tile` can panic or compute the wrong parent when `z - HHTL_DEPTH` is
32 or more, so update the shift logic to use a checked/saturating right shift
before returning the ancestor tile. Make the fix inside `resolved_tile(z, x, y)`
and ensure large zoom values safely clamp to the HHTL_DEPTH ancestor with x/y
resolving to 0 when the shift is too wide. Add a regression test for the
`resolved_tile` case with very large inputs, including `resolved_tile(56,
u32::MAX, u32::MAX)`, to verify it returns the expected clamped tile.
Source: Coding guidelines
| pub async fn osm_tile_meta_handler( | ||
| Path((z, x, y)): Path<(u32, u32, u32)>, | ||
| ) -> Json<serde_json::Value> { | ||
| let hhtl = tile_to_hhtl(z, x, y); | ||
| let (rz, rx, ry) = resolved_tile(z, x, y); | ||
| Json(serde_json::json!({ | ||
| "z": z, "x": x, "y": y, | ||
| "tile_url": tile_url(z, x, y), | ||
| "source": OSM_TILE_URL, | ||
| "hhtl": hhtl, | ||
| "resolved": { "z": rz, "x": rx, "y": ry }, | ||
| "geo_domain": "0x0F", | ||
| })) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Validate x/y against 2^z in the tile-meta handler.
z, x, and y are taken raw from the path with no range check, so out-of-range coordinates (e.g. /api/osm/tile/2/999/1) flow into tile_to_hhtl, where x << shift truncates the high bits and yields a garbage HHTL key / tile_url that silently describes a different tile. Reject coordinates outside 0..2^z (or 0..2^24 after resolving) with a 400 instead of returning misleading metadata. (The z bound is covered by the resolved_tile fix above.)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/cockpit-server/src/osm_tiles.rs` around lines 163 - 176, The
osm_tile_meta_handler currently accepts raw Path coordinates and can emit
misleading metadata for out-of-range x/y values. Add a bounds check in
osm_tile_meta_handler (before calling tile_to_hhtl, resolved_tile, and tile_url)
to reject x or y outside 0..2^z, or the equivalent resolved 0..2^24 range, and
return a 400 response instead of JSON for invalid tile coordinates.


What
The Geo-domain (
0x0F) map material for the/OSMcockpit — where OSMmaps come from, and how a slippy-tile address becomes an HHTL key. A new
self-contained
cockpit-server::osm_tilesmodule.The two halves (per
MERCATOR-HHTL-HELIX-MAP.md)OSM_TILE_URL= the standard slippy-tile server(
tile.openstreetmap.org/{z}/{x}/{y}.png). The client fetches the raster;the cockpit only computes the address — no network on the request path.
z/x/yis the WebMercator quadtree; a quadtree is acascade, so it Morton-interleaves into HHTL's three 16-bit tiers
(
3 × 256×256= 48 bits). Coarse zooms left-align into HEEL(
tier = level >> 3), fine into TWIG — the map pyramid and the semanticcascade are one address (D-BOTHCASC).
Surface
lonlat_to_tilemorton_interleave/morton_deinterleavetile_to_hhtlz/x/y→{heel, hip, twig}tile_urlRoutes:
GET /api/osm/locate?lon=&lat=&z=andGET /api/osm/tile/:z/:x/:y→tile address + source URL + HHTL key.
Tests (8)
null-island center tile, Berlin =
(8802, 5373), east→+x / south→+y,Morton round-trip, HHTL round-trip at native depth, coarse-zoom-lands-in-HEEL,
adjacent-tiles-share-HEEL.
Verification note (please read)
The tile math is verified standalone via
rustc— all assertions pass(
Berlin=(8802,5373),hhtl(2,3,1)=(28672,0,0)). The fullcargo test -p cockpit-servercannot run in my sandbox: the crate'spre-existing
v8 v149.4.0transitive dep fails itsbuild.rs(needsgn/ninja tooling not present here). This is orthogonal to this change — it adds
only
std+serde+axumcode and standard-pattern handlers. The unittests run under CI where the v8 toolchain is available. Flagging per q2's
"say so explicitly if you can't verify end-to-end" rule.
Next
The
/OSMcockpit page consumes these endpoints — it's the stackedfollow-up PR.
🤖 Generated with Claude Code
https://claude.ai/code/session_01EYvNjD8M8LMNYbRy3gq2FP
Generated by Claude Code
Summary by CodeRabbit
New Features
Tests