Terrain Viewer
Dev

State reference (URL & storage)

Every URL parameter the app reads, generated from the source at build time — state parameters mirrored by nuqs, instruction parameters read once on load, and the settings kept in localStorage

The tables on this page are generated from the app's source by docs/scripts/build-url-params.mjs, run before every docs build, so the deployed pages cannot drift (the tracked copies in git are refreshed by hand, pnpm run url-params, and may lag between releases). For how to compose a link or an iframe, see Embedding & URL parameters; for an interactive form over the same parameters, see URL as an API.

State parameters

Read and written by nuqs from QUERY_STATE_PARSERS in components/TerrainViewer.tsx. Booleans are true/false, lists are comma-separated, colours are hex, and a parameter at its default is omitted from the address bar. In the URL the terrain source keys are terrainSourceA…terrainSourceH; the same fields are sourceA…sourceH inside the code and inside a project preset's initialState (see lib/url-keys.ts), and the old URL spelling is still accepted.

ParameterTypeDefaultNote
projectstring""Embed/project convenience params: `project` looks up a named preset in lib/projects.json (see lib/project-config.ts); terrainUrl/basemapUrl let an embedder point straight at a raw tile/COG URL withou…
terrainUrlstring""
basemapUrlstring""
terrainTypestring""Explicit source type for terrainUrl/basemapUrl when it's a raw URL (not an existing source id) — type can't always be inferred from the URL shape alone (e.g.
basemapTypestring""
viewModeone of 2d | globe | 3d"3d"
appModeone of terrain | historical"terrain"Which sidebar layout the ModePicker (TerrainControlPanel.tsx) shows — "historical" strips it down to just basemap browsing.
splitStyleone of off | overlay | side-by-side"off"Master split control — replaces the old plain splitScreen boolean.
gridLayoutone of 2x1 | 3x1 | 4x1 | 1x2 | 2x2 | 3x2 | 4x2"2x1"Only meaningful once splitStyle !== "off"; "overlay" always behaves as "2x1" regardless of this value (see the splitContainer render below).
splitBlendModestring"difference""overlay"-only: pane B's CSS mix-blend-mode + opacity against pane A.
splitBlendModeEnabledbooleantrueLets a picked blend mode be toggled off (falls back to plain "normal") without losing/resetting it — a quick way to flip back and forth between a blended and a plain overlay comparison for the same p…
overlayOpacitynumber1.0
splitRationumber0.5Split gutter position (overlay clip boundary / side-by-side divider) as a 0-1 ratio of the available width.
matchColorsToAbooleanfalse"overlay"-only: "Match Colors (View A as reference)" — live-samples both panes' CURRENTLY RENDERED canvases (whatever tiles are actually loaded in the viewport, re-sampled at most once/second) and re…
matchColorsColorSpacestring"rgb"Which color space the matching above is computed/applied in — "rgb" (default) is a live CSS filter; the others are a real per-pixel JS conversion, capped to a low working resolution (see HistogramMat…
showCaptureDatePillone of auto | off | date | source-date" as const"auto"Compare and Blend's "Show Capture Date" toggle — a small per-view pill for whichever views are actually on a dated historical source.
terrainSourceCstring"aws"C-H only ever matter for gridLayout "2x2"/"3x1"/"3x2"/"4x1"/"4x2" — same shape as sourceA/B, just extra slots so bigger grids need no schema change.
terrainSourceDstring"mapterhorn"
terrainSourceEstring"maptiler"
terrainSourceFstring"aws"
terrainSourceGstring"mapterhorn"
terrainSourceHstring"maptiler"
basemapPerViewbooleantrue
basemapSourceAstring"esri"
basemapSourceBstring"google"
basemapSourceCstring"esri"
basemapSourceDstring"google"
basemapSourceEstring"esri"
basemapSourceFstring"google"
basemapSourceGstring"esri"
basemapSourceHstring"google"
overlayBasemapIdslist of string (comma-separated)[]'overlay'-role custom basemap sources currently stacked on top of the active basemap (see basemap-byod-section.tsx's checkbox list) — shared across A/B, only meaningful in split-or-radio basemap mode…
dateinteger0The ONE scrubbed date (epoch ms) for whichever concrete historical source is active on this side (historicalActiveSource(A/B) below) — Wayback/HLS/GE-Historical/Planet/EOX-S2/Bing all share this sing…
dateAinteger0
dateBinteger0
dateCinteger0
dateDinteger0
dateEinteger0
dateFinteger0
dateGinteger0
dateHinteger0
historicalActiveSourceone of wayback | hls | ge-historical | planet | eox-s2" as const"wayback"Which concrete underlying source (wayback/hls/ge-historical/planet/ eox-s2) actually renders when basemapSource(A/B) === "historical" — the sidebar only ever exposes one combined "Historical Imagery"…
historicalActiveSourceAone of wayback | hls | ge-historical | planet | eox-s2" as const"wayback"
historicalActiveSourceBone of wayback | hls | ge-historical | planet | eox-s2" as const"wayback"
historicalActiveSourceCone of wayback | hls | ge-historical | planet | eox-s2" as const"wayback"
historicalActiveSourceDone of wayback | hls | ge-historical | planet | eox-s2" as const"wayback"
historicalActiveSourceEone of wayback | hls | ge-historical | planet | eox-s2" as const"wayback"
historicalActiveSourceFone of wayback | hls | ge-historical | planet | eox-s2" as const"wayback"
historicalActiveSourceGone of wayback | hls | ge-historical | planet | eox-s2" as const"wayback"
historicalActiveSourceHone of wayback | hls | ge-historical | planet | eox-s2" as const"wayback"
timelineSourceslist of string (comma-separated)["wayback", "ge-historical", "bing"]Which historical sources' ticks are aggregated onto the shared timeline (pill toggles in historical-timeline-panel.tsx) — independent of which single source is actually "active"/rendered on the map.
timelineSourcesAlist of string (comma-separated)["wayback", "ge-historical", "bing"]Per-side variants of timelineSources above — only meaningful when both basemapPerView AND splitStyle!=="off" are on (dualMode) AND the timeline's sync toggle is off, letting each active view aggregat…
timelineSourcesBlist of string (comma-separated)["wayback", "ge-historical", "bing"]
timelineSourcesClist of string (comma-separated)["wayback", "ge-historical", "bing"]
timelineSourcesDlist of string (comma-separated)["wayback", "ge-historical", "bing"]
timelineSourcesElist of string (comma-separated)["wayback", "ge-historical", "bing"]
timelineSourcesFlist of string (comma-separated)["wayback", "ge-historical", "bing"]
timelineSourcesGlist of string (comma-separated)["wayback", "ge-historical", "bing"]
timelineSourcesHlist of string (comma-separated)["wayback", "ge-historical", "bing"]
historicalTimelineCollapsedbooleanfalseWhether the historical timeline's full bar (header + track) is collapsed down to just the small floating clock-icon toggle button.
sidebarCollapsedbooleanfalseThe side panel, folded.
coverageOverlayslist of string (comma-separated)Coverage footprints drawn on the map (Source Info -> Coverage overlays).
drawingUrllist of string (comma-separated)[]Remote vector data in the Drawing tool, one layer per URL (comma- separated; nuqs encodes commas inside an item).
historicalControlsExpandedbooleantrueWhether the timeline's title/source-pills/resolution-chips header row is shown (true) or the panel is in its minimal, track-only mode with just a small floating cog+collapse chip (false).
resolutionClasseslist of string (comma-separated)["vhr", "medium"]Resolution-class filter alongside the source pills above — "vhr" (Wayback/GE Historical/Bing, sub-meter-ish) vs "medium" (HLS's 10-30m Landsat/Sentinel-2, Planet's ~4.7m monthly mosaic) — see SOURCE_…
colorRampstring"mby"colorRamp: parseAsString.withDefault("mby"),
customStopsramp-stopsDEFAULT_SLOPE_CUSTOM_STOPS
customStopsDiscretebooleanfalse
showHillshadebooleantrueNative MapLibre Hillshade — its own independent viz mode, entirely separate from "Lighting Effects" (Matcap/Phong) below.
hillshadeOpacitynumber1.0
showLightingEffectsbooleanfalse"Lighting Effects" viz mode — master toggle/opacity, housing two sub-modes: Matcap first, Phong second (see lighting-effects-options-section.tsx).
lightingEffectsOpacitynumber1.0
showMatcapbooleanfalse"Matcap" sub-mode (lib/matcap-protocol.ts) — a plain raster overlay (draped over 3D terrain the same automatic way the raster basemap is) that looks up color from a material-capture image using the s…
matcapOpacitynumber1.0
matcapTextureIdstringDEFAULT_MATCAP_ID
matcapRotationDegnumber0"Sphere Rotation" — spins the matcap lookup independently of the map's own bearing (a raster tile is baked once per z/x/y, so it can't track live bearing the way a real-time GPU shader could).
matcapRendererone of raster | live" as const"live""raster" (default): lib/matcap-protocol.ts's plain raster-tile pipeline — drapes correctly over 3D terrain exaggeration AND globe, but every rotation/exaggeration change costs a real tile refetch.
matcapLightRelativeToCamerabooleantrue"Light Anchor", ported from Phong's phongLightRelativeToCamera — only meaningful in "live" (2D Fast): off (Absolute) samples the material by the tile-space normal, pinned to compass directions and id…
showPhongbooleantrue"Phong" sub-mode (lib/phong-protocol.ts) — a plain raster overlay doing real ambient+diffuse+specular shading from a compass-fixed light (state.illuminationDir/illuminationAlt below — the same fields…
phongOpacitynumber1.0
phongDiffuseStrengthnumber0.8
phongSpecularStrengthnumber0.2
phongLightRelativeToCamerabooleantrueOff (default — briefly flipped to Camera on 2026-08-20, reverted the same day on the user's follow-up): illuminationDir is a compass azimuth, fixed to the world, matching maplibre's own hillshade ill…
phongRendererone of raster | live" as const"live""raster" (default): lib/phong-protocol.ts's plain raster-tile pipeline — drapes correctly over 3D terrain exaggeration AND globe, but every light/strength/exaggeration change costs a real tile refetc…
showShadowsbooleanfalse"Shadows" sub-mode (lib/shadow-protocol.ts) — a plain binary raster mask: for each pixel, marches toward the sun's actual azimuth (state.illuminationDir/illuminationAlt below — same shared light as H…
shadowOpacitynumber0.6
shadowRadiusPxnumber32"Search Radius" — same convention as SVF/Openness: how many same-zoom pixels the single ray marches outward looking for an obstruction.
lightUseDatetimebooleanfalse"Datetime-based" light: when on, illuminationDir/illuminationAlt are driven from a physically-plausible sun position (see lib/solar-position.ts) computed from the viewport-center lat/lng + these day-…
lightTimeModeone of local | utc" as const"local"Which clock convention lightTimeOfDay is expressed in — "local": the real civil wall-clock time at the viewport lat/lng, including that location's actual DST rules (see lib/timezone.ts); "utc": UTC d…
showColorReliefbooleanfalse
colorReliefOpacitynumber0.35
showTerrainAnalysisbooleanfalseMaster toggles for what used to be one merged "Slope and More" viz mode, now split into Terrain Analysis (surface derivatives + neighborhood statistics: Slope/Aspect/Curvature/Det Hessian/Blobness/TP…
terrainAnalysisOpacitynumber1.0Master opacity for Terrain Analysis — composites (multiplies) with each sub-mode's own opacity below, rather than replacing it.
showReliefVisualizationbooleanfalse
reliefVisualizationOpacitynumber1.0
showSlopebooleantrue
slopeOpacitynumber1.0
slopeColorRampstring"slope-plantopo"
slopeSourceModeone of plantopo | client"client"
slopeMinDegreesnumber0
slopeMaxDegreesnumber55
slopeInvertColorRampbooleanfalse
slopeCustomStopsramp-stopsDEFAULT_SLOPE_CUSTOM_STOPSOnly read when slopeColorRamp === "custom" — see computeColorReliefPaint's dedicated branch for that ramp id in MapLayers.tsx.
slopeCustomStopsDiscretebooleanfalseWhen the custom ramp is selected, render its stops as hard discrete bands (each color holds until the next stop) instead of a continuous gradient.
showAspectbooleanfalse
aspectOpacitynumber0.5
aspectColorRampstring"aspect-compass"
aspectMinDegreesnumber0
aspectMaxDegreesnumber360
aspectShiftDegreesnumber0
aspectInvertColorRampbooleanfalse
aspectCustomStopsramp-stopsDEFAULT_SLOPE_CUSTOM_STOPS
aspectCustomStopsDiscretebooleanfalse
showTribooleanfalse
triOpacitynumber1.0
triColorRampstring"tri-default"
triMinnumber0
triMaxnumber50
triInvertColorRampbooleanfalse
triCustomStopsramp-stopsDEFAULT_SLOPE_CUSTOM_STOPS
triCustomStopsDiscretebooleanfalse
showCurvaturebooleanfalse
curvatureOpacitynumber1.0
curvatureModeone of combined | profile | plan | det-hessian | casorati | shape-index"combined"
curvatureColorRampstring"curvature-diverging"
curvatureMinnumber-20
curvatureMaxnumber20
curvatureInvertColorRampbooleanfalse
curvatureSymmetricbooleantrue
curvatureCustomStopsramp-stopsDEFAULT_SLOPE_CUSTOM_STOPS
curvatureCustomStopsDiscretebooleanfalse
showTpibooleanfalse
tpiOpacitynumber1.0
tpiColorRampstring"tpi-diverging"
tpiMinnumber-20
tpiMaxnumber20
tpiInvertColorRampbooleanfalse
tpiSymmetricbooleantrue
tpiCustomStopsramp-stopsDEFAULT_SLOPE_CUSTOM_STOPS
tpiCustomStopsDiscretebooleanfalse
showLrmbooleantrue
lrmOpacitynumber1.0
lrmColorRampstring"lrm-diverging"
lrmMinnumber-20
lrmMaxnumber20
lrmInvertColorRampbooleanfalse
lrmSymmetricbooleantrue
lrmRadiusnumber16
lrmCustomStopsramp-stopsDEFAULT_SLOPE_CUSTOM_STOPS
lrmCustomStopsDiscretebooleanfalse
showRoughnessbooleanfalse
roughnessOpacitynumber1.0
roughnessColorRampstring"roughness-default"
roughnessMinnumber0
roughnessMaxnumber50
roughnessInvertColorRampbooleanfalse
roughnessCustomStopsramp-stopsDEFAULT_SLOPE_CUSTOM_STOPS
roughnessCustomStopsDiscretebooleanfalse
showShapeIndexbooleanfalse
shapeIndexOpacitynumber1.0
shapeIndexColorRampstring"custom"
shapeIndexMinnumber-1
shapeIndexMaxnumber1
shapeIndexInvertColorRampbooleanfalse
shapeIndexSymmetricbooleantrue
shapeIndexCustomStopsramp-stopsDEFAULT_SHAPE_INDEX_CUSTOM_STOPS
shapeIndexCustomStopsDiscretebooleantrue
showBlobnessbooleanfalse
blobnessOpacitynumber1.0
blobnessColorRampstring"blobness-default"
blobnessMinnumber0
blobnessMaxnumber50
blobnessInvertColorRampbooleanfalse
blobnessCustomStopsramp-stopsDEFAULT_SLOPE_CUSTOM_STOPS
blobnessCustomStopsDiscretebooleanfalse
showEigenRatiobooleanfalse
eigenRatioOpacitynumber1.0
eigenRatioColorRampstring"eigen-ratio-default"
eigenRatioMinnumber0
eigenRatioMaxnumber100
eigenRatioInvertColorRampbooleanfalse
eigenRatioCustomStopsramp-stopsDEFAULT_SLOPE_CUSTOM_STOPS
eigenRatioCustomStopsDiscretebooleanfalse
showOrientationbooleanfalse
orientationOpacitynumber1.0
orientationColorRampstring"orientation-default"
orientationMinnumber0
orientationMaxnumber180
orientationInvertColorRampbooleanfalse
orientationCustomStopsramp-stopsDEFAULT_SLOPE_CUSTOM_STOPS
orientationCustomStopsDiscretebooleanfalse
showSvfbooleanfalse
svfOpacitynumber1.0
svfColorRampstring"svf-default"
svfMinnumber0
svfMaxnumber100
svfInvertColorRampbooleanfalse
svfRadiusnumber8
svfPrecisionone of precise | fast"fast"
svfCustomStopsramp-stopsDEFAULT_SLOPE_CUSTOM_STOPS
svfCustomStopsDiscretebooleanfalse
showOpennessbooleanfalse
opennessOpacitynumber1.0
opennessColorRampstring"openness-default"
opennessMinnumber-15
opennessMaxnumber15
opennessInvertColorRampbooleanfalse
opennessSymmetricbooleantrue
opennessRadiusnumber8
opennessModeone of positive | negative"positive"
opennessPrecisionone of precise | fast"fast"
opennessCustomStopsramp-stopsDEFAULT_SLOPE_CUSTOM_STOPS
opennessCustomStopsDiscretebooleanfalse
showLocalDominancebooleanfalseLocal Dominance (Hesse 2016) — Relief Visualization mode, see lib/local-dominance-protocol.ts.
localDominanceOpacitynumber1.0
localDominanceColorRampstring"local-dominance-default"
localDominanceMinnumber-5
localDominanceMaxnumber15
localDominanceInvertColorRampbooleanfalse
localDominanceSymmetricbooleanfalse
localDominanceMinRadiusnumber8
localDominanceMaxRadiusnumber32
localDominanceCustomStopsramp-stopsDEFAULT_SLOPE_CUSTOM_STOPS
localDominanceCustomStopsDiscretebooleanfalse
showPlaneSlicerbooleanfalsePlane Slicer — Tools: Elevation Picker sub-section.
planeSlicerReferenceModeone of absolute | lrm"absolute"
planeSlicerValuenumber0Absolute and LRM keep independent threshold values — the two reference frames have wildly different natural ranges (metres of real elevation vs.
planeSlicerValueLrmnumber0
planeSlicerSideone of above | below"below"
planeSlicerColorstring"#3388ff"
planeSlicerOpacitynumber0.6
elevationPickerReferenceModeone of absolute | lrm"absolute"Same Absolute/LRM reference as Plane Slicer, for the Elevation Picker's point/profile sampling itself (lib/elevation-query.ts) — "LRM" here has no 3D-mesh equivalent (map.queryTerrainElevation() only…
tellsBetabooleanfalseExperimental — opt-in via Settings (or ?tellsBeta=true directly) so it doesn't clutter Visualization Modes for everyone by default.
sunShadowBetabooleantrueSame opt-in-beta gate as tellsBeta above, for Tools: Sun Shadow Calculator.
historicalBetabooleantrueSame opt-in-beta gate as tellsBeta above, for the historical-imagery basemaps (Wayback/HLS/GE Historical/Planet) + bottom timeline panel.
showTellsDetectorbooleanfalseMaster on/off (Visualization Modes' "Tells (Mound Detector)" checkbox) — gates the sidebar's Mound Candidates section as well as the map layer.
tellsMarkersVisiblebooleantrueMound Candidates section's own "Show mound candidates" checkbox — a pure paint-visibility toggle for markers already being computed, independent of showTellsDetector so it can't also collapse the sec…
tellsStyleone of outline | byBlobness | byPlan | byDetHessian | byLrm"outline"
tellsOutlineColorcolor"#ef4444"
tellsScaleMarkersbooleantrueOnly meaningful with tellMeasureScale on: draw each marker at tellsScaleMultiplier x its measured diameter (real-world meters, zoom-scaled) instead of fixed px.
tellsScaleMultipliernumberTELLS_MEASURED_SCALE_MULTIPLIER_DEFAULT
tellSizenumber100
tellRadiusnumber4
tellMinReliefnumber1.5
tellBlobnessMinnumber0
tellPlanMinnumber0
tellDetHessianMinnumber0
tellMeasureScalebooleantrue
tellVetoResolutionone of fine | coarse"coarse"
tellsPolarityone of mounds | pits | both"mounds"Which extrema the detector reports: mounds (local maxima of the relief, the archaeological tell), pits (minima: quarries, sinkholes, craters, cisterns, robbed-out tombs) or both.
tellsPitColorcolor"#3b82f6"
tellsFrozenbooleanfalse"Frozen" pins the currently-computed candidates in place (snapshotted into a plain geojson source, see the tellsFrozenSnapshot state and TellsSource's frozen/frozenSnapshot props below) so panning/zo…
showContoursAndGraticulesbooleanfalse
showContoursbooleantrue
showContourLabelsbooleantrue
showGraticulesbooleanfalse
showRasterBasemapbooleanfalse
showBackgroundbooleanfalse
matchThemeColorsbooleantrueSky/horizon/fog colors for the Background + Fog/Sky mode (background- options-section.tsx) — URL-shareable state like every other viz-mode field, not a localStorage preference (was previously a plain…
skyColorcolor"#80ccff"
skyHorizonBlendnumber0.5
horizonColorcolor"#ccddff"
horizonFogBlendnumber0.5
fogColorcolor"#fcf0dd"
fogGroundBlendnumber0.2
backgroundLayerActivebooleantrue
rasterBasemapOpacitynumber1.0Viz-mode master opacity (the "Raster Basemap" checkbox's own slider) — composites (multiplies) with basemapSourceOpacity below for the single/split basemap layer, same pattern as Slope-and-More's mas…
basemapSourceOpacitynumber1.0Basemap-solo opacity (the "Basemap Opacity" slider inside the Basemap Source section) — only affects the single/split basemap layer, not overlays.
exaggerationnumber1
latnumber21.4208Whole-earth start, the same view handleGoHome resets to and historical mode already used.
lngnumber0
zoomnumber1.52
pitchnumber0-- try getting out of pitch 0 loop in 3d pitch: parseAsFloat.withDefault(60.001),
bearingnumber0
hillshadeMethodone of standard | combined | igor | basic | aspect-multidir | multidir-colors"combined"--
illuminationDirnumber315
illuminationAltnumber45
shadowColorcolor"#000000"shadowColor: parseAsString.withDefault("#000000"), highlightColor: parseAsString.withDefault("#FFFFFF"), accentColor: parseAsString.withDefault("#808080"),
highlightColorcolor"#FFFFFF"
accentColorcolor"#808080"
hillshadeExagnumber1.0graticuleColor: parseAsString.withDefault("#000"), graticuleColor: parseAsString, // don't use default to sync with theme
contourReferenceModeone of absolute | lrm"absolute"Absolute ("real altitude") vs LRM ("height above/below the local neighborhood mean") — same reference concept Plane Slicer introduced (PLANE_SLICER_REFERENCE_MODES above), applied to what the contour…
contourMinornumber50
contourMajornumber200
contourMinorLrmnumber5Absolute and LRM keep independent interval values, same reasoning as planeSlicerValue/planeSlicerValueLrm — LRM's natural range (roughly ±100m of local relief) is much narrower than real elevation's,…
contourMajorLrmnumber20
contourWeightnumber1Multiplies both major and minor contour line-width — default (1) keeps today's major-vs-minor ratio, 2/4 make both proportionally bolder.
contourColorstring""Contour line / graticule colors — empty string means "auto" (theme-adaptive: the contour lines fall back to translucent black/white by theme, the grid to themeAntiColor).
customHypsoMinMaxbooleanfalse
hypsoSymmetricbooleanfalseMin/Max mirrored around 0 (one magnitude), like the curvature ramp: for height-above-ground or elevation-change grids (DSM − DTM, dh).
minElevationnumber0
maxElevationnumber8100
hypsoSliderMinBoundnumber-8000
hypsoSliderMaxBoundnumber5000
graticuleWidthnumber1.0
graticuleColorstring""
showGraticuleLabelsbooleanfalse
graticuleDensitynumber0
minimapMinimizedbooleantrue
invertColorRampbooleanfalseKeyframe/360 animation state (animDuration, animLoopMode, animSmoothCamera, animPlaying, animPlaying360, animPose1, animPose2Delta) lives in its own nuqs hook inside CameraUtilities.tsx, not in this …
maxBoundsModeone of none | terrain | raster | union | custom"terrain"Max map bounds (Settings > Map Bounds) — constrains pan/zoom rather than a one-shot camera fly like the smart-zoom/fit-to-bounds features above.
maxBoundsBuffernumber0
maxBoundsWestnumber-180
maxBoundsSouthnumber-85
maxBoundsEastnumber180
maxBoundsNorthnumber85

306 parameters read from the app source at build time; 5 multi-line declarations not parsed.

Instruction parameters

Read once on load, never written by the app, left in place in the URL. They either load something or apply a one-time setting.

ParameterTypeEffect
projectstringA named preset from lib/projects.json: hidden panels, initial state, bundled sources.
terrainUrlstringView A's terrain as a URL, registered under a fixed id (older form of terrainSourceA=https://…).
basemapUrlstringView A's basemap as a URL (older form of basemapSource=https://…).
terrainTypestringType of URL terrain sources (cog, terrarium, terrainrgb, wms-raw, tilejson …) instead of the {z} guess.
basemapTypestringType of URL basemap sources (cog, tms, wms, wmts, tilejson) instead of the {z} guess.
viaTitilerbooleanRoute URL COGs through titiler (files not in Web Mercator).
addSourceslist of string (comma-separated)Library entries (by id) made available in the BYOD lists without being selected.
addTerrainUrllist of string (repeat the parameter)URL terrain sources made available without being selected (repeat the parameter).
addBasemapUrllist of string (repeat the parameter)URL basemaps made available without being selected (repeat the parameter).
addOverlayUrllist of string (repeat the parameter)URL overlays made available; overlayBasemapIds (state) turns overlays on.
openSectionslist of string (comma-separated)Sidebar sections expanded on load (general, terrainSource, hillshade, drawing …).
closeSectionslist of string (comma-separated)Sidebar sections collapsed on load.
scrollTostringSection key (or element id) the side panel opens and scrolls to.
startTourbooleanStarts the product walkthrough.
bookmarksGallerybooleanOpens the Bookmarks gallery modal on arrival.
openLibrarystringterrain | basemap - opens that Library modal (the curated dataset list) on arrival.
bookmarksUrlstringA bookmarks JSON (the Bookmarks section's export format) fetched and merged into the visitor's bookmarks; pair with openSections=bookmarks.

Stored settings (localStorage)

jotai atomWithStorage atoms: API keys, beta flags, panel folds, export preferences, the BYOD source lists. None of these is in the URL; a project export carries a curated subset.

localStorage keyDefaultNoteDefined in
mapboxKeyimport.meta.env.VITE_MAPBOX_ACCESS_TOKEN ?? ""These were hardcoded literals committed here — all moved to local, gitignored VITE_*_API_KEY / VITE_MAPBOX_ACCESS_TOKEN vars (see .env) instead, same pattern as hereKeyAtom below.lib/settings-atoms.ts
googleKeyimport.meta.env.VITE_GOOGLE_API_KEY ?? ""lib/settings-atoms.ts
mapzenKey"mapzen-xxxxxxx"lib/settings-atoms.ts
maptilerKeyimport.meta.env.VITE_MAPTILER_API_KEY ?? ""lib/settings-atoms.ts
hereKeyimport.meta.env.VITE_HERE_API_KEY ?? ""Unlike mapbox/maptiler/google above, no public demo key is committed here — HERE requires a paid account, so its default comes from a local, gitignored VITE_HERE_API_KEY (see .env) instead of a hardc…lib/settings-atoms.ts
planetKeyimport.meta.env.VITE_PLANET_API_KEY ?? ""Same gating pattern as hereKeyAtom — Planet's monthly mosaics require a paid account, so "planet" only appears as a Basemap option (raster-basemap- section.tsx) once a real key is set, from a local V…lib/settings-atoms.ts
cesiumIonKeyimport.meta.env.VITE_CESIUM_ION_TOKEN ?? ""Cesium ion access token.lib/settings-atoms.ts
cesiumDetailOffset1Extra quantized-mesh levels to fetch per tile.lib/settings-atoms.ts
disabledStacPresets[]STAC catalogs the visitor has switched OFF in the Library's Catalogs section: stored as the exclusions rather than the inclusions so a preset added in a later release is on by default instead of sile…lib/settings-atoms.ts
savedStacCatalogs[]lib/settings-atoms.ts
titilerEndpoint"https://titiler.xyz"lib/settings-atoms.ts
customBasemapLastType"qms"lib/settings-atoms.ts
osmBuildings3dtruelib/settings-atoms.ts
customTerrainLastType"cog"lib/settings-atoms.ts
maxResolution4096lib/settings-atoms.ts
exportResolutionMode"screen"lib/settings-atoms.ts
exportValueFormat"raw"lib/settings-atoms.ts
exportImageFormat"tiff"lib/settings-atoms.ts
useCogProtocolVsTitilertruelib/settings-atoms.ts
useClientExporttrueDTM export mode: client-side (browser range-reads/tile-mosaic, no titiler, no server-side size limit) vs the original titiler-based export — see lib/client-export.ts.lib/settings-atoms.ts
colorRampType'classic'lib/settings-atoms.ts
licenseFilter'open-distribute' lib/settings-atoms.ts
highResTerraintruelib/settings-atoms.ts
cacheVizTilestrueGates lib/tile-result-cache.ts — the LRU of finished viz-mode tile bytes that makes re-toggling a mode instant instead of recomputing every visible tile.lib/settings-atoms.ts
terrainAnalysisAdvancedtrueBasic/Advanced toggle, one per section (Terrain Analysis / Relief Visualization — terrain-analysis-section.tsx / relief-visualization-section.tsx): in "basic" mode each sub-mode collapses to just its…lib/settings-atoms.ts
reliefVisualizationAdvancedtruelib/settings-atoms.ts
lightingEffectsAdvancedtruelib/settings-atoms.ts
hasSeenTourfalseGuided product tour (components/TerrainControlPanel/product-tour.tsx).lib/settings-atoms.ts
customTerrainSources[]getOnInit: true reads localStorage synchronously on first render instead of the jotai default (hardcoded `[]` on first paint, real value applied post-mount via onMount).lib/settings-atoms.ts
isByodOpentruelib/settings-atoms.ts
customThemes[]lib/settings-atoms.ts
customBasemapSources[]lib/settings-atoms.ts
isBasemapByodOpentruelib/settings-atoms.ts
isHillshadeXYPadOpentruelib/settings-atoms.ts
bookmarksListHeightnullUser-dragged height (px) of the Bookmarks list's scroll area (bookmarks-section.tsx's drag handle below it) — null means "use the default max-h-64 clamp".lib/settings-atoms.ts
collapsedBookmarkGroups[]Ids of "project" (root) bookmarks whose children are currently folded away — bookmarks-section.tsx's file-tree-style collapse.lib/settings-atoms.ts
bookmarksViewMode"list"Sidebar bookmarks list presentation — "list" (default: the original tree of project rows with children indented beneath) or "grid" (flattened thumbnail tiles, a compact in-panel cousin of the gallery…lib/settings-atoms.ts
bookmarksGalleryViewMode"grid"Gallery MODAL presentation — "grid" (default: the original thumbnail cards) or "list" (compact rows: small thumbnail left, project + view name right).lib/settings-atoms.ts
vizModePinnedtruePins Visualization Modes open through "Fold all sections" (TerrainControlPanel.tsx) — that's the master on/off switchboard for every viz layer, so folding it away along with everything else hides the…lib/settings-atoms.ts
appMode"terrain"lib/settings-atoms.ts
isTransparentUitruelib/settings-atoms.ts
isExportSettingsOpenfalseDownload and Snapshot's own "Export settings" fold (see download-section.tsx).lib/settings-atoms.ts
snapshotIncludeTimelinefalseSnapshot / Copy / Share: keep the historical timeline in the picture, or (default) crop the strip it covers off the bottom - see captureMapScreenshot.lib/settings-atoms.ts
anim-render-quality'normal'lib/settings-atoms.ts
anim-fps60lib/settings-atoms.ts
anim-target-size-mb''lib/settings-atoms.ts
lastSeenChangelogAt"2026-08-01"ISO date ("YYYY-MM-DD") of the newest changelog entry's `<!-- released -->` marker the user has already seen — see lib/changelog.ts.lib/settings-atoms.ts
changelogView"changes""changes" (just what's new since last visit) vs "full" (every entry) — always defaults to "changes", regardless of whether there's currently anything unseen.lib/settings-atoms.ts
betaEnabled{ tells: false, sunShadow: true, historical: true, stacSear…Mirrors of TerrainViewer's tellsBeta/sunShadowBeta/historicalBeta nuqs fields (the actual gates the app reads) — those live in the URL so a `?tellsBeta=true` link still works, but with no localStorag…lib/settings-atoms.ts
galleryFlattenGroupstrueBookmarks gallery modal: on (default) flattens every group's cards into one continuous grid (each card's label prefixed with its project name) so nothing but the real bookmark count determines how mu…lib/settings-atoms.ts
galleryShowFeaturedtrueBookmarks gallery modal: whether the read-only Featured/preset strip (see lib/preset-bookmarks.ts) shows as its own section at the top of the gallery, alongside the visitor's own saved bookmarks.lib/settings-atoms.ts
splitRatio0.5Draggable A/B split-screen divider — persisted (like isSidebarOpenAtom) since it's a user layout preference, not per-session UI state.lib/layout-constants.ts
colorizeMapBordersfalseOutlines each active map pane in its resolved side color (SIDE_COLORS, overridden by sideColorOverridesAtom) — purely cosmetic, off by default so it doesn't surprise anyone not using the comparison/g…lib/layout-constants.ts
colorizeMapBordersInsettrueWhether that border sits inset 3px from the pane edge (default, reads as a frame) or flush against it — dropping the inset doubles the stroke width (border-2 -> border-4) so a flush border doesn't re…lib/layout-constants.ts
isComparisonMixAdvancedOpenfalseCompare and Blend section's own "Advanced" (capture date / colorize borders / side colors) collapsible — persisted like every other section-local collapse toggle (isBasemapByodOpenAtom etc.lib/layout-constants.ts
isSidebarOpentrue--- Persisted state ---components/TerrainControlPanel/TerrainControlPanel.tsx
sectionOpenDEFAULT_OPEN_STATEcomponents/TerrainControlPanel/TerrainControlPanel.tsx
sidebarScroll0components/TerrainControlPanel/TerrainControlPanel.tsx
drawingExportPerLayerfalseexportGeoJSON toggle (TerraDrawActions below): off (default) exports every layer's features flattened into one FeatureCollection — the existing behavior, and still the right default for "keep drawing…components/TerrainControlPanel/TerraDrawSystem.tsx
drawingExportScopenullcomponents/TerrainControlPanel/TerraDrawSystem.tsx
drawingLayers[DEFAULT_LAYER]Layer metadata (id/name/color/stroke width) is small enough to persist directly in localStorage — this doubles as the list of layer ids whose *geometry* gets hydrated from OPFS on mount (see hydrateP…components/TerrainControlPanel/TerraDrawSystem.tsx
activeLayerIdDEFAULT_LAYER.idcomponents/TerrainControlPanel/TerraDrawSystem.tsx
persistVectorLayerstruecomponents/TerrainControlPanel/TerraDrawSystem.tsx

63 stored settings read from the app source at build time. None is in the URL; a project export carries a curated subset.

On this page