Concept
RTC + DSFUN precision.
Why a city street still renders sharply when the camera is zoomed to building-detail levels — even though the GPU vertex shader only handles 32-bit floats.
The problem
Web Mercator (EPSG:3857) coordinates measure in meters from the
equator and prime meridian. A point in Seoul lands around
(14130000, 4520000).
Single-precision floats can represent that magnitude — but the
precision drops to roughly 1.5 m per step
at those values. Move the camera to a Seoul building (zoom 18),
ask the GPU to subtract the camera position from each vertex,
and you lose every sub-meter detail to f32 cancellation. The
result on screen: jittering polygons and mis-aligned strokes.
X-GIS solves this with two coordinated tricks: RTC (relative-to-center coordinates) and DSFUN (double-single function math) — together they recover f64-equivalent precision inside an f32 GPU pipeline.
Coordinate spaces
Each stage of the X-GIS pipeline operates in a defined space. Crossing a stage boundary always means converting; staying inside means staying in the same units.
| Code | Space | Units | Stage |
|---|---|---|---|
| LL | WGS84 lon/lat | degrees | GeoJSON input, bbox-reject |
| MM | Global Web Mercator | meters (~±2×10⁷) | All clipping, simplification, line arc length |
| DLM | DSFUN tile-local Mercator | meters, hi/lo f32 pair | Output vertices, GPU consumption |
| SP | Screen / NDC | pixels / clip-space | Camera projection only |
DSFUN: f64-equivalent in f32 hardware
Each tile vertex is stored as two f32 values per axis —
high and low —
such that h + l reproduces the
original f64 within ~1 µm. The vertex shader subtracts the camera
position the same way:
// CPU side, once per tile:
let camRel = camera_merc - tile_origin_merc // f64 subtraction
let cam_h = f32(camRel)
let cam_l = f32(camRel - cam_h) // residual
// GPU vertex shader, once per vertex:
let pos_relative_to_camera =
(pos_h - cam_h) + (pos_l - cam_l) // f32 cancellation
The (pos_h - cam_h) subtraction
cancels the city-magnitude component to a small residual. The
(pos_l - cam_l) term carries
the sub-meter detail. The sum is small enough to live comfortably
in f32 — every downstream calculation stays precise.
Cross-path invariants
Because polygons and lines flow through different code paths —
triangulation vs SDF segment generation — X-GIS enforces explicit
invariants between them, all checked in
tile-cross-path-invariants.test.ts:
- Polygon fill and stroke clip in the same space (MM). Pre-2026-04-20 the stroke ran in MM and the fill in LL — endpoints diverged up to 27 km at z=8 boundary tiles.
- Fill triangulation boundary == stroke outline endpoints, within 1 m in DLM units.
- Batch
compileGeoJSONToTilesand on-demandcompileSingleTileproduce geometrically equivalent tiles. - Sub-tile area conservation: four DSFUN children sum to the parent's triangle-area.
- DSFUN reconstruction is exact:
h + lrecovers the original f64 within 1 µm.
Source
The full coordinate convention with the developer-facing invariants and stage-by-stage table lives in docs/COORDINATES.md in the repo. New contributors should read it before adding any clip / simplify / sub-tile logic.