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.
// 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
mercator
Try this →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).
globe
Try this →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