Terrain
Loading terrain…
ETOPO1 elevations become a shaded relief mesh, with contours every 500 metres. Exaggeration 40 makes the Alps readable; projection defaults use 1 for true-scale relief.
The site’s terrain is a baked ETOPO1 pyramid (zoom 0–4, plus curated regional tiles). Below it, /maps and the demos on these pages stream the Mapzen terrain tiles hosted on AWS (terrarium encoding, about 30 m over most land) down to zoom 12, from s3.amazonaws.com/elevation-tiles-prod, and credit them in the map attribution. A streamed tile that fails to load falls back to the baked pyramid.
Elevation sources
Section titled “Elevation sources”map.addLayer({ type: "raster", source: provider, classifier: GlyphMapClassifiers.etopo1V1, colors: terrainColors,});A raster source is a GlyphMapGeoTile or GlyphMapProvider. The widget selects detail from degrees per glyph cell and keeps coarser terrain while finer tiles load. One tile pyramid serves every projection.
| Source | Required data |
|---|---|
GlyphMapGeoTile | bounds, cols, rows, elevation: Float32Array, source, sampler |
GlyphMapProvider | id, zooms, bounds(z, x, y), async loadTile(z, x, y) |
A tile’s grid is (cols + 1) × (rows + 1), row-major, north first; elevations are metres. Both sources accept attribution. Split tiles crossing east > 180 with splitGlyphMapGeoTileAtAntimeridian before projecting.
Classifiers
Section titled “Classifiers”import { GlyphMapClassifiers, glyphMapBreaks, glyphMapQuantile, glyphMapEqualInterval, glyphMapLog,} from "@glyphcss/maps";
GlyphMapClassifiers.etopo1V1;glyphMapBreaks([0, 250, 800, 1500, 3000]);glyphMapQuantile(9);glyphMapEqualInterval(9, [-8000, 6000]);glyphMapLog(9);Supply one colour per band through colors. Quantile and log classifiers reject categorical fields. Classifier choice changes colour bands; projection exaggeration changes relief height.
The terrain elevation window
Section titled “The terrain elevation window”map.addLayer({ type: "raster", source: provider, minElevation: 0 });map.addLayer({ type: "contour", source: provider, levels: { interval: 500 }, minElevation: 0 });minElevation / maxElevation are optional metre limits. Raster positions outside the window flatten to its edge; colours still read original elevations. Contours have their own window: match its floor to avoid burying lines under flattened terrain.
Where the ground comes from
Section titled “Where the ground comes from”createGlyphMap(host, { view, projection, groundElevation: (lon, lat) => myDem.sample(lon, lat) ?? null, layers: [],});Roads, draped fills, extrusions, and point layers normally follow mounted terrain, using the finest available tiles. groundElevation overrides that lookup; null or non-finite values use the datum. Without either source, features sit on the datum.
A custom lookup is read live, but cannot announce changes: move the camera or re-add the layer after loading data. Contours need a field/provider; a point lookup cannot supply them.
Lighting
Section titled “Lighting”Loading terrain…
map.setSun({ mode: "manual", date: new Date("2026-06-21T12:00:00Z") });map.setSun({ mode: "realtime" });map.setSun({ mode: "off" });map.setKeyLight("headlight"); // camera-following lightSun lighting overrides the key light; headlight overrides your scene direction. getKeyLightDirection() reports the active direction. getSunDirection() and getSubsolarPoint() expose the sun position. Turning the sun off leaves the last light direction in place.
| Sun option | Default |
|---|---|
mode | "off" |
date | Now; used in "manual" mode |
tickMs | 30000; realtime updates |
twilightDeg | 6 |
nightOpacity / nightColor | 0.72 / "#000000" |
nightLevels | 4 |
On sheets, day/night shading needs useColors: true. keyLight defaults to "fixed"; headlight preserves relief while lighting the visible face. Set light intensity and ambient fill through scene.directionalLight / scene.ambientLight.
Cast shadows
Section titled “Cast shadows”map.setShadow({ color: "#000000", opacity: 0.25 });map.setShadow(null); // off, the defaultfill-extrusionandmodelcast; those plusraster,fill, andheatmapreceive.- Terrain never casts; stamped lines and contours do not receive shadows.
- Direction follows the active scene light; headlight produces no visible shadows.
liftdefaults to0; large extra bias erases short-building shadows. Low sun angles can dither edges.
Baking a static map
Section titled “Baking a static map”import { loadGlyphMapSource } from "@glyphcss/maps/node";import { glyphMapBounds, sampleGlyphMapField, classifyGlyphMapField, compileGlyphMap, GlyphMapClassifiers,} from "@glyphcss/maps";
const src = await loadGlyphMapSource({ path: "ETOPO1.asc.gz", id: "etopo1-2009" });const view = glyphMapBounds({ west: 84.5, east: 86.5, south: 27.6, north: 28.8, cols: 140, rows: 48 });const field = await sampleGlyphMapField(src, view, { sampler: "max" });const bands = classifyGlyphMapField(field, GlyphMapClassifiers.etopo1V1);const { html, css } = compileGlyphMap(bands, { ramp: " .:-=+*#%@", water: "~" });This produces a frozen map without browser JavaScript. "max" preserves peaks; "mean" smooths them; "nearest" can alias. For a mesh without the widget, glyphMapPolygons(tile, projection, options) returns Polygon[]; keep resolution consistent across each tile level to avoid seams.