Skip to content

Internals

Deep dives into the major subsystems.

MVT Decoder

src/mvt.rs contains a hand-rolled protobuf decoder (no prost dependency) that parses Mapbox Vector Tiles.

Two-Pass Parsing

  1. Pass 1: Scan the layer, collect keys, values, and feature byte-ranges
  2. Pass 2: Parse features using the collected keys/values

This avoids allocating intermediate structures for the entire tile. Feature properties are stored as HashMap<String, serde_json::Value>.

Geometry Types

ID Type Decoded as
1 Point Single [x, y] coordinate
2 LineString Vec of coordinate pairs
3 Polygon Vec of rings (outer + holes)

Tile coordinates (0–4096 extent) are converted to world coordinates during decoding. The tile is gzip-compressed (flate2 for native, browser DecompressionStream for WASM).

Varint Handling

read_varint uses u64 internally (not usize) to prevent shift-left overflow on wasm32 where usize is 32 bits.

Tile Loading

src/tile_loader/ implements an async tile fetcher with an LRU cache.

State Machine

Each tile request progresses through: Requested → Loading → Loaded | Failed

Visible Tile Calculation

covering_tiles() walks the tile pyramid from z0 and keeps what the camera can see:

  1. CameraTransform::ground_quad() gives the patch of ground the viewport covers, as four Mercator corners. Turned, it is not axis aligned; tilted, the far edge is wider than the near one, so a plain rectangle will not do.
  2. A tile is culled unless its bounds overlap that quad, tested with the separating axis theorem over the box axes and the quad's own edge normals.
  3. A surviving tile is split into its four children while it would be drawn wider than TILE_SCREEN_PX. The projected width uses the distance to the tile's nearest corner, taken from the w row of the view-projection, so distant ground resolves coarser on its own: a tilted view naturally mixes zoom levels, and a flat one comes out at a single level, matching what the old rectangle walk produced.
  4. Descent stops at covering_zoom_level() (further clamped by the source's max zoom) and the result is capped at MAX_VISIBLE_TILES = 512, which bounds the set a near-horizontal view would otherwise let run away.
  5. Longitude wraps and latitude does not: a column past the antimeridian is the column on the other side of the world, and a duplicate tile at very low zoom is kept once.

Globe mode does not use this walk. The visible region there is a spherical cap, so get_visible_tiles_globe() scans the grid at one zoom and keeps the tiles that project onto the front hemisphere and land on screen.

Over/Dezoom Fallback

When a tile hasn't loaded yet:

  • Overzoom: find_loaded_ancestor() walks up 3 zoom levels for a coarser tile
  • Dezoom: find_loaded_descendants() BFS walks down 3 zoom levels for finer tiles

Ancestor tiles are clipped to the target tile's bounds via clip_bounds in the per-tile storage buffer.

LRU Cache

Tiles are stored as Rc<VectorTile> in an LRU cache. get_tile() returns Option<Rc<VectorTile>> for O(1) clone.

Tile Fade-In

New tiles fade in over 300ms. Opacity is tracked via a tile_first_seen HashMap and interpolated per-frame.

Symbol Collision

src/collision.rs (51 KB) handles label placement to prevent overlapping text and icons.

Algorithm

  1. Place labels in priority order (style layer order, then by feature rank)
  2. Test each candidate against a spatial grid of already-placed labels
  3. Rejected labels with text-variable-anchor retry with alternative anchor positions
  4. Accepted labels fade in, rejected labels fade out

Smart Collision Skipping

The renderer tracks camera center and zoom at last collision resolve. If the camera has moved less than 2px since the last resolve, full collision is skipped — only fade animations advance. This avoids expensive O(n log n) collision work during smooth panning.

Per-Label Opacity

Label opacity is stored in a GPU storage buffer (binding 2, group 1), separate from vertex data. The shader reads label_opacity[label_index] * tile_opacity. This means only a small ~2–4 KB array is uploaded when collision state or fades change, rather than rebuilding the entire vertex buffer.

Text & Glyph Rendering

Glyph Atlas (src/glyph_atlas.rs)

  • Parses PBF protobuf glyph data (SDF bitmaps)
  • Packs glyphs into a dynamic shelf-packed texture atlas (1024x1024, grows by doubling)
  • SDF byte stored in the alpha channel — reuses the icon.wgsl shader with zero shader changes
  • Basic Latin (0–255) loaded on style load; additional Unicode ranges loaded lazily

Text Shaper (src/text_shaper.rs)

  • SimplePbfShaper: 1:1 codepoint-to-glyph mapping at PBF_BASE_SIZE = 24px
  • ShapedText::wrap(max_width_px, line_height): word wraps at spaces, forced break if no space

SDF Thresholds

The is_sdf vertex attribute encodes both mode and halo information:

Value Mode
0.0 RGBA (no SDF)
1.0 Sprite SDF
2.0 + halo_buff PBF glyph SDF

Fill: buff = 192/256 = 0.75. Halo: buff = (6.0 - halo_width/fontScale) / 8.0. Both use smoothstep(buff - gamma, buff + gamma, dist) with gamma = 0.105.

Tessellation Cache

src/tessellation_cache.rs caches polygon triangulation results (earcutr output) keyed by tile coordinate + feature identity. This avoids re-tessellating the same geometry when tiles are re-extracted due to camera changes. The cache uses get_or_insert_with() — no clones needed.

Raster Tiles

src/raster.rs implements a raster tile rendering pipeline for satellite imagery. Raster tiles are fetched, decoded (PNG/JPEG/WebP via the image crate), and rendered as textured quads below vector geometry using raster.wgsl.