X-GIS

Concept

Projection switching.

Seven projections ship with X-GIS. Switching from Mercator to Orthographic is a single uniform write — same GeoJSON, no re-tessellation, no fresh tile fetches.

How it works

Tile vertices are stored in global Web Mercator meters regardless of which projection is active. Reprojection happens entirely inside the WGSL vertex shader: convert MM back to lon/lat, then forward through whichever projection's CPU-mirrored math is baked into the WGSL.

Tile vertices Mercator meters stored once · never re-tiled WGSL vertex shader mercator_inverse(mm) → lon, lat project(lonlat, type) → projected x, y setProjection() → u.projection_type Clip-space position u.mvp · vec4(projected, 0, 1) 7 projections · same GeoJSON
// WGSL vertex shader (simplified):
let mm = pos_h + pos_l                             // DSFUN reconstruction
let lonlat = mercator_inverse(mm)                  // back to lon/lat
let projected = project(lonlat, u.projection_type) // forward into target
let clip = u.mvp * vec4(projected, 0.0, 1.0)

The u.projection_type uniform is a small integer; switching projections is a single write per frame. No tessellation work, no re-upload, no cache invalidation.

Why MM as the storage frame

Storing in lon/lat would mean every projection's vertex shader starts from f32(degrees) — fine for the equator but the precision cliff hits at the poles. Storing in already-projected target coordinates would invalidate every tile when the user switches projections.

MM is the compromise: precision is uniform (DSFUN handles f32 magnitude) and switching is free (just change the post-MM projection step in the shader).

Available projections

Web Mercator (EPSG:3857) — the default. Conformal, scales infinitely toward the poles (clamped at ±85.05° per spec).

equirectangular

Try this →

Plate carrée. Latitude maps directly to y; trivial inverse. Distortion grows toward the poles but no clipping.

natural_earth

Try this →

Pseudo-cylindrical, polynomial. Best balance of low distortion + recognizable continent shapes. Default for "world overview" demos.

orthographic(lon, lat)

Try this →

Globe view from infinity. Hemispherical visibility — back-face culled in WGSL. Centers the view on (lon, lat).

azimuthal_equidistant(lon, lat)

Try this →

Distances from center are exact. Used for radio range maps + polar projections.

stereographic(lon, lat)

Try this →

Conformal projection from a sphere onto a plane. Good for polar regions; everything-on-one-page world map.

oblique_mercator(lon, lat)

Try this →

Tilted Mercator centered on (lon, lat). Useful for narrow regions that span continents (e.g., the Hawaiian Islands chain).

True 3D sphere with an orbit camera — Cesium-style. Two-finger vertical drag tilts (pitch), pinch zooms, drag rotates. Pitch is a real 3D tilt, not a flattened disc like orthographic.

Switching at runtime

From JavaScript:

import { XGISMap, getProjection } from "@xgis/runtime"

const map = new XGISMap(canvas)
await map.run(source, baseUrl)

// Switch to globe view centered on Seoul:
map.setProjection(getProjection("orthographic", 127, 37.5))

Known limitations

Globe: polar caps render empty (±85.05°)

Tile data is keyed to the Web Mercator pyramid across all projections — even the 3D globe. Mercator's projection diverges at the poles and the spec clamps coverage to ±85.051129° (EPSG:3857). Tiles beyond that latitude band simply don't exist, so the globe renders a small circular "no-data" disk centred on each pole. The shader's proj_globe() function itself handles ±90° fine; only the tile pyramid truncates.

Workarounds: (a) accept the limitation — most map data is irrelevant at the geographic poles; (b) overlay a single land polygon clipped to the polar cap as a separate non-tiled draw; (c) wait for a future polar tile scheme follow-up that synthesises cap tiles at the source-compile step. The first option is recommended for typical world-map demos.

CPU/GPU consistency

Each projection has paired CPU + WGSL implementations. They're kept in lockstep by runtime/src/__tests__/projection-wgsl-consistency.test.ts, which forwards a sample grid through both and asserts they agree to f32 epsilon. Pick-correctness on a globe map relies on this — the CPU resolves the picked feature; the GPU draws it where the user clicked.

Specifications

See also

Was this page helpful?

Tell us what's missing