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
- Pass 1: Scan the layer, collect keys, values, and feature byte-ranges
- 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:
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.- 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.
- 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. - Descent stops at
covering_zoom_level()(further clamped by the source's max zoom) and the result is capped atMAX_VISIBLE_TILES = 512, which bounds the set a near-horizontal view would otherwise let run away. - 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
- Place labels in priority order (style layer order, then by feature rank)
- Test each candidate against a spatial grid of already-placed labels
- Rejected labels with
text-variable-anchorretry with alternative anchor positions - 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.wgslshader 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 = 24pxShapedText::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.