Tile Geometry and Topology - krcenov/RNS510-Map-Viewer GitHub Wiki

Tile Geometry and Topology

Status: 🟑 Partially cracked. Road geometry (the actual polylines) is βœ… cracked and validated at scale. Point-to-point connectivity (topology/adjacency) is 🟑 cracked for most features, with real, characterized failure modes. This page covers what's inside a decompressed mp0/mg1–mg4 tile β€” see MAP_COMPRESSED Container Format for how to get a decompressed tile in the first place.

Reader: research/map_compressed_reader.py.

Tile header

  • 0x0000: magic 0x0146 (constant across every tile sampled)
  • 0x0002–0x0017: small header fields, uint16 values never exceed ~2000 β€” likely local tile-relative extents/counts, not decoded.
  • 0x0018 onward: a spatial sub-index of individual uint16 LE words (each either declen, used as an "empty cell" sentinel, or 0, a second pad marker). Ends at a variable but heavily-clustered position: byte 326 for ~83% of mg2/mg3/mg4 tiles, byte 24 for sparse/degenerate tiles. mp0's sub-index is deeper/differently shaped.
  • Some header fields are now separately decoded via decode_tile_header() (e.g. an exact "this feature's own topology-graph node count" field) β€” see the Topology section below.

Coordinate geometry β€” βœ… cracked, validated at scale

The coordinate region is a sequence of self-delimiting feature blocks packed back-to-back with no padding:

+0  uint16 LE  point_count (N)  -- deltas in THIS block only
+2  int32  LE  anchor longitude (degrees = value / 100000)
+6  int32  LE  anchor latitude  (degrees = value / 100000)
+10 N * (int16 LE dx, int16 LE dy)  -- cumulative from the anchor

Block size = 10 + 4*N bytes; the next feature's block starts exactly there. Blocks are read until a candidate anchor fails a geographic plausibility check β€” that's where the coordinate region ends and the topology table begins.

Validated at scale: exact-integer matching every decoded vertex of every decoded feature (across the 80 largest mg4 tiles) against eeu.rd's own int32 coordinate fields β€” same rigor as a bit-exact match, no float rounding β€” found tiles with 2+ features each independently landing an exact hit on a different real named road (i.e. genuinely separate polylines, not one chain that happens to pass near several roads). Cleanest example: a tile decodes to exactly 2 features, a 392-point chain matching 10 distinct named roads near IaΘ™i, Romania, and an independent 154-point chain matching 2 more. Also confirmed on mp0 (one tile near Geneva exact-matches 24 named streets).

Known decode caveats

  • Trailing "phantom" blocks: the greedy plausibility stop can occasionally accept a few small (0–6 point) trailing blocks with no corresponding eeu.rd record before it finally hits real topology-table bytes and stops. Doesn't affect the large, validated features, but means "number of blocks" isn't a fully trustworthy feature count on its own.
  • Oscillating overrun (trim_oscillation, opt-in, default off): some tiles contain a still-unidentified fixed-stride structure partway through a declared block that isn't real delta data β€” decoded naively, it produces huge (24–33km) single-step jumps oscillating between two fixed latitude bands. A detector (_find_oscillation_clusters()) finds and splits out these clusters, but tile-local statistics alone cannot reliably tell a real corruption cluster apart from a legitimate long road doubling back on itself β€” this was proven, not just suspected, with concrete counter-examples in both directions. The map viewer therefore defaults this filter off: turning it on was directly shown to delete real named roads (including Sofia's own ring road) in dense urban areas, a worse outcome than leaving the rare corruption visible as a few stray points.

Topology / adjacency table

Past the coordinate region, each feature (not each tile) gets its own table of exactly point_count + 1 fixed 10-byte records (record 0 is a non-node "header" record):

+0        2-byte tag   -- low byte selects how many of the 4 fields are populated
+2,4,6,8  up to 4 x uint16 LE fields (A, B, C, D)

Finding the table β€” 🟑 mostly solved

The table's start position isn't at a fixed offset from the feature's own block_end β€” it's located by searching forward from the end of the tile's whole coordinate region for a run of point_count + 1 records whose fields all stay below a plausibility cap (record 0 exempted, since it's a distinct "header" record with much larger values by design). On a multi-feature tile, features are resolved tightest-cap-first, each one's found byte range reserved against every other feature's search β€” this stops a looser search from aliasing onto an already-claimed table.

At-scale characterization (a real ~11kmΓ—11km box around Sofia, Bulgaria, 257 features across all 5 layers): 194/257 (75.5%) resolve at high confidence, 1 at low confidence, 62 (24.1%) not found at all β€” concentrated in dense street-level mp0 tiles (54/63 of the non-high cases), not correlated with feature size. A "found": False result means exactly that: no table could be located for that specific feature in this tile, honestly reported rather than fabricated.

Node-id ↔ coordinate mapping β€” βœ… cracked (the "shared link-id" mechanism)

A record does not reference a neighbor's point index directly. Instead, two really-adjacent points' records share one common field value (a per-edge "link id"); which table record belongs to which point index is offset by a small per-feature shift that must be discovered per feature (not a fixed constant). resolve_topology_adjacency() scores candidate shifts by the median real-world distance of the edges they imply β€” the correct shift produces short, road-like edges; a wrong one produces long, implausible ones.

Validated against two independent human-verified ground-truth tiles: 16/16 and 16/18 real, human-confirmed edges recovered exactly (the 2 misses on the second tile both touch one point, most likely a near-duplicate junction vertex, not a mechanism failure). A broader 75.5%-high-confidence characterization (above) followed from fixing two real bugs in the table-location search this uncovered.

Per-edge false-positive filter

Even within a feature whose overall confidence is "high", a small number of individual edges can be wrong: two points sharing the literal link-id value 0 get wrongly wired together, because 0 is almost certainly a padding/unset-field sentinel (the same role plain 0 plays elsewhere in this tile format), not a genuine edge id β€” so any two points that both happen to have an unused field slot land in the same bucket and get spuriously connected. Found via 5 real user-reported examples (two "connected" points 692m–2,917m apart). Fixed with an independent per-edge distance sanity filter (max_edge_m, default 200m) applied after shift selection β€” re-validated to leave both ground-truth ties unchanged while dropping the 5 reported false edges and a number of other implausible ones.

A predicted-but-unobserved limitation of this same filter CONFIRMED and FIXED, a later session. The 200m default was calibrated entirely from mg2/mp0 ground truth (dense layers, every real edge under 110m) and applied uniformly to every layer β€” never validated against mg4/mg3, the coarsest layers, where 200m-2,000m between shape points is normal. User-reported: a real mg4 edge (points 72↔74, 1,518m, a genuine divided-road carriageway transition) was being silently dropped. Auditing just one feature found 28 real edges (200m-1,810m) dropped this way. Fixed at the caller level (not the function's own default, which stays correct for the layers it was validated against): the map viewer's MAX_EDGE_M_BY_LAYER now passes 5,000m for mg4/mg3, unchanged 200m for mg2/mg1/mp0. Verified with a real redraw test: rendered line pixels increased at 2 zoom levels (667β†’1,183, 1,055β†’1,460), confirmed more real edges now drawn, not a regression.

Interleaved-parallel-chains as a divided-road detector β€” tested and rejected, same session. The (72, 74) fix above is a genuine 2-chain interleaved structure. Tested whether this generalizes into a usable signal (scan for alternating gap=2 edges β†’ flag divided roads), against the same 4 human-confirmed ground-truth sites used for the seg_list work (A1 x2, Hemus, A6). A wide-window check looked promising but was a window-choice artifact; re-checked against the exact reported points' own real neighbors instead: only 1 of 3 divided sites (Hemus) shows the interleaved signature right at its reported location, the other 2 look mostly sequential, and the non-divided control isn't clean either. Real but inconsistent β€” not pursued as a detector.

Link-id value meaning β€” characterized at scale, still not fully explained

A direct follow-up to "what do the shared numeric link-id values themselves mean?" Sampled 402 clean single-feature high-confidence tiles (β‰₯30 points, all 3 layers, Bulgaria-wide) and classified every non-zero value by how many points share it (123,618 (tile, value) pairs): 74.2% shared by exactly 2 points (a clean edge β€” validates the mechanism at far larger scale than the original hand-verified ground truth), 25.4% are "orphans" (present in only 1 point's own record, no local partner β€” meaning unresolved: unused padding? a link stub that doesn't resolve locally? something else?), and 0.3% form real junction groups (3+). Also found a harmless SECOND sentinel pattern (values near the uint16 ceiling, >65,000, distinct from the already-known 0 padding value) that skews naive range stats but was confirmed to never form a spurious edge (0/597 tiles checked) β€” no code change needed. Clean tiles' value ranges are dense ([1, K], no gaps), consistent with a per-tile-authored LOCAL numbering, not an obviously external key. Whether the values (or the orphans specifically) map to anything in eeu.rd's own record layout was the natural next step β€” tested immediately after, same session, and REFUTED: built a full in-memory exact-coordinate index over all 8,809,081 eeu.rd records (~50s) and compared each matched point's eeu.rd bytes 16-20/20-24 (uint32 LE β€” confirmed to hold small plausible-looking integers on inspection, e.g. 67/43, 325/370) against its link-id value set. Across 2,963 qualifying points: only 20 matches (0.67%) β€” actually below the ~1.6% naive chance-collision rate for value sets this size. Not the external reference. Also tested eeu.rd's other 2 documented "unresolved" fields the same session: bytes[5:8] (a plausible shared-geometry pointer, noted elsewhere as "often shared across same-name records") holds values around 3.6-7.7 million β€” far outside any plausible link-id magnitude, ruled out on scale alone; bytes[1:5] (the already-ruled-out road-class candidate) also scored 0/2,963. This is now a complete sweep of every eeu.rd field documented as unresolved or candidate β€” none of them are the link-id's external reference. Its independent meaning remains genuinely open. See eeu.rd for the record layout. Full writeup: resolve_topology_adjacency()'s own docstring in research/map_compressed_reader.py.

Known remaining gaps

  • Cross-feature edges: the obvious mechanism was tested and refuted, same session. resolve_topology_adjacency() resolves adjacency entirely within one feature. A real, confirmed example exists: a user-reported connectivity chain crossed from one feature's point into a different feature in the same tile β€” even though both features individually resolve at high confidence, the resolver has never tried bridging between them. There was a structural hint this should be possible (node-ids referenced in one feature's table have been observed going as high as the tile's combined point total across all features, suggesting a tile-global, not per-feature, id space), which raised the natural hypothesis: do two geographically-close dead-end points in different features share a link-id VALUE, the same mechanism used within one feature? Tested at scale (Bulgaria-wide, 3 layers, 646 qualifying multi-feature tiles): the near-vs-far shared-value hit rate is NOT elevated for nearby pairs in any layer (mg4 0.33% near vs 0.58% far; mg2 0.30% vs 0.33%; mp0 0.54% vs 0.68%) β€” if anything slightly lower β€” and many "hits" pair points tens of kilometers apart. This rules out cross-feature value-sharing as pure numeric coincidence, not a real mechanism. Cross-feature adjacency remains genuinely unsolved; this specific approach is closed, not just untried. Full writeup: resolve_topology_adjacency()'s own docstring in research/map_compressed_reader.py.
  • An even bigger version of the same gap: CROSS-TILE edges, real example confirmed. User right-clicked 2 real mg4 points expected to connect: tile 2384 point 132 (23.45508, 42.63235) and tile 2385 point 12 (23.45595, 42.63224) β€” different tiles, ~72m apart. Confirmed the structural signature matches a genuine tile-boundary split exactly: both resolve "high" confidence locally, but each is a degree-1 dead end within its own tile (no further neighbor). The 2 points' own raw topology records share NO common field value β€” the intra-tile shared-link-id mechanism genuinely can't reach across tiles. This is the same problem the firmware string-mining found a real, NAMED pipeline for (db_vid_get_map_id_V000/db_vid_get_pcl_id_V000) but never cracked at the byte level β€” this example is real ground truth preserved for that future attempt.
  • decode_topology()'s own graph-walk hypothesis was tested and definitively refuted: a single feature's topology sub-graph is provably not graph-isomorphic to its own point-sequence path (on a reference tile, it has two connected components, one containing a cycle) β€” so recovering decode_features()'s point order from a walk of this graph isn't possible even in principle for that tile.
  • The tag's field-count-vs-real-junction correlation is a real but weak signal (found via eeu.rd name-change cross-referencing at intersections) β€” not strong enough to use as ground truth on its own.

Practical relevance for editing

The geometry format is fully readable/writable per-feature, and a valid topology entry for a newly-inserted road's own vertices can now be synthesized in principle. What (if anything) real hardware actually needs from the topology table to render/route a new road correctly is untested on real hardware β€” see the main README's "Open problems" section.

Reader code

research/map_compressed_reader.py β€” decode_features(), decode_tile_header(), decode_topology(), resolve_topology_adjacency() (see that function's own docstring for the full mechanism writeup, every validation number, and all known caveats in detail).