Skip to content

Instantly share code, notes, and snippets.

@jsmorph
Last active July 27, 2026 13:20
Show Gist options
  • Select an option

  • Save jsmorph/b57845e26d6be8d5181c743e8f36a129 to your computer and use it in GitHub Desktop.

Select an option

Save jsmorph/b57845e26d6be8d5181c743e8f36a129 to your computer and use it in GitHub Desktop.
Geoterrain features
I want you to implement design.md. It should be utterly perfect, visually beautiful, with every single thing done at high quality.

Fan out sub-agents and have sub-agents tackle each one individually so that the app is utterly perfect. You should /loop on each item and have a separate sub-agent check it visually to ensure it looks triple A. That separate sub-agent should be a really harsh critic, and if it doesn't work very well, it should keep going.

Don't stop until each sub-agent is utterly wowed with the quality. It should literally compare them side by side blind and say which one looks better. /loop until it's utterly perfect. Fan out sub-agents and ultracode.

Keep me updated as you make progress but do not stop.

GeoTerrain features and internals

GeoTerrain draws geologic maps over shaded terrain from one local process. A single Go executable serves the compiled web client, tile data, metadata, a remote-source proxy, and a disk cache; the browser talks only to that process. This document describes what the application does and how it is built, with attention to the parts whose implementation is not obvious from the outside.

The source is about 23,000 lines of Go, TypeScript, and CSS across fourteen internal Go packages. Every Go package with logic worth testing has tests, and all ten test packages pass.

Contents

Section Subject
Capabilities What a user can do
Architecture How the pieces fit
Source selection Choosing which map to draw
Rendering Drape, relief, line hierarchy, glyphs
Tile storage MBTiles, disk cache, proxy
Import pipelines Building offline packs
Elevation Sampling, slope, and profiles
Honesty about precision Where the application declines to guess
Client State, permalinks, panels
Security Operating modes and LAN access
Visual QA The screenshot harness
Upstream defects What is worked around

Capabilities

The map fills the viewport under a globe projection, with three-dimensional terrain from raster DEM tiles and geologic polygons draped over shaded relief. A left panel controls sources and layers and carries the legend; a right panel inspects the selected unit; both collapse. A status bar shows position, elevation, active source, scale, zoom, and attribution.

Control Behaviour
2D and 3D Switches between plan view and pitched terrain without moving the ground under the cursor
Geology opacity Adjusts polygon opacity while relief stays legible beneath
Vertical exaggeration 0 to 3 times, with 1× marked as true scale and ticks positioned by value
Source selection Automatic, or a specific published map pinned by hand
Geologic layers Units, contacts, faults, and unit labels, each independent
Reference layers Hillshade, water, towns, summits, roads, county and state lines, pack coverage
Measure Distance with per-segment bearing, and elevation profile with slope
Coordinate display Decimal degrees, degrees-minutes-seconds, or UTM
Search Formations, place names, and typed coordinates in one field
Permalink Encodes camera, mode, source, layers, opacity, exaggeration, coordinate format, theme, selection, and panel state
Attribution Permanently visible, with complete provenance in the inspector

Clicking a polygon fills the inspector with the unit's name, symbol, age with numeric bounds, lithology, description, source map, published scale, publication date, provider, licence, attribution, and a formatted citation with a copy control.

Search merges three kinds of result in one list. A typed coordinate is recognised in all three display formats and takes the first slot, because it is unambiguous. Formation names come from a full-text index over unit names, symbols, ages, and lithologies. Place names come from Nominatim, rate-limited to its usage policy and cached with everything else.

Architecture

cmd/geoterrain/      serve, import-geology, import-terrain
internal/catalog/    dataset and unit metadata, search, source ranking
internal/config/     configuration loading and validation
internal/elevation/  point elevation, terrain slope, profiles
internal/fonts/      glyph ranges compiled in so map text works offline
internal/httpapi/    JSON endpoints, tile routes, middleware
internal/importer/   geology and terrain import pipelines
internal/mbtiles/    MBTiles reader and writer
internal/packs/      pack discovery, validation, and pruning
internal/proxy/      remote proxy, allowlist, request coalescing
internal/tilecache/  disk cache with a size-limited LRU index
internal/tiles/      Web Mercator tile arithmetic
internal/tileserve/  resolves a dataset and coordinate to tile bytes
web/src/             TypeScript client, no framework
tools/visualqa/      deterministic screenshot harness

The client is TypeScript and the DOM, bundled by esbuild into web/dist and compiled into the executable by embed.FS. A released binary needs nothing but a writable data directory. The only runtime dependency outside the standard library is modernc.org/sqlite, a cgo-free driver, which keeps the build a single static executable.

Routing uses the standard library's method-and-pattern ServeMux. One consequence is worth recording: a wildcard must occupy a whole path segment, so /tiles/geology/{dataset}/{z}/{x}/{y}.pbf is not a legal pattern and panics at registration. The final segment matches whole and the handler splits the extension, validating it against the set the route accepts.

Tile responses

A tile the source does not cover answers 204, not 404. MapLibre treats 404 as an error worth retrying and logging, while 204 means the tile is legitimately empty, which is the ordinary case at the edge of a pack. Pack tiles carry an ETag built from the dataset name and coordinate, which is sound because a pack is immutable for as long as it is installed.

Source selection

The design's four inputs are geographic coverage, published scale, priority, and user selection. Coverage and scale come from the catalog; priority is set during import from the map scale, so a 1:24,000 survey outranks a national compilation; user selection pins a source and disables switching.

Two refinements are not obvious.

Hysteresis. The current dataset is held while it remains plausible, using a 0.6 zoom band beyond its usable range and a 4 percent geographic band beyond its bounds. Without it, panning across a pack boundary or nudging the wheel near a threshold swaps datasets repeatedly, which reads as flicker and discards the tiles just fetched. A pack still takes over the instant the view enters its coverage, because the hold is broken by a strictly higher priority.

Terrain is chosen by area, not by point. MapLibre draws one terrain mesh. A pack that covers part of the view leaves the rest flat and puts a straight cliff along the pack boundary, which is worse than using a coarser global source for the whole scene. Selection therefore prefers a dataset that contains the entire viewport; when none does, it ranks by the fraction of the view each covers, so the global source wins and the cliff never appears. The same reasoning applies to an elevation profile, where the source covering most of the line is used for all of it: changing source partway would put a step in the profile where the two disagree.

Ranking also prefers an installed pack over a remote source at equal depth, since a pack is prepared for its own ground from a named survey while a global mosaic is whatever covered that ground worldwide.

Rendering

Layers draw bottom to top: neutral background, hillshade, geologic polygons, faint relief over the polygons, contacts, faults, unit labels, reference features, then water and place labels.

Relief over the drape. A geologic overlay opaque enough to read its own colours also hides the relief beneath it. A second, much fainter hillshade is drawn over the polygons, which restores the shading without shifting hue. MapLibre has no layer blend modes, so the strength lives in the alpha of the shadow and highlight colours. Measured inside one polygon, this raises the tonal range from 0.071 to 0.133 in light and 0.055 to 0.184 in dark, while mean hue moves under half a degree and saturation falls about four percent.

Water above geology. Water draws over the polygons rather than under them. A national compilation that maps a lake as a rock unit would otherwise hide it, and a geologic sheet prints water over the units anyway.

Line hierarchy. Faults carry more weight than contacts and a warmer black, and their opacity stays below solid because a dense fault swarm at full strength turns into black hatching. A trace the survey recorded as approximate, inferred, or concealed draws dashed.

Labels. Place and summit labels are a dark glyph on a light halo in both themes, because they sit on geologic colour rather than on the theme's background, and that colour is light to mid tone once the drape is applied. Unit labels start at a zoom derived from the source's published scale, so a 1:500,000 compilation does not begin naming units at the zoom a 1:24,000 quadrangle can support; polygon size is handled by collision, so a label whose polygon cannot hold it is dropped rather than spilling across a contact.

Summits. Named peaks draw with their elevation. The marker is the text glyph U+25B2 rather than a sprite icon: the basemap sprite's peak icons carry no sdf flag, so their colour cannot be reached from the style, and a marker fixed in one baked colour competes with the geology in one theme or the other. The source layer carries saddles and passes alongside summits, so the filter admits only peaks and volcanoes, and the admitted rank rises with zoom. No layer draws an icon, so the style names no sprite at all.

Worker-safe URLs. MapLibre fetches vector tiles and glyphs inside a worker, which has no document base to resolve a path against. The server emits absolute URLs built from the request's own host.

Declared extents. Each source declares its bounds in the style. Without that, a raster-DEM source receives empty responses at the edge of a pack, builds a zero-size elevation grid, and fails to backfill against its neighbours.

Glyphs

MapLibre draws every label from signed-distance-field glyph ranges fetched over HTTP, and one style names one glyph URL for the whole map. Pointing that URL at the basemap provider means an offline run loses unit labels, place names, and the summit marker along with the basemap, so seven codepoint ranges for Noto Sans Regular and Bold are compiled into the executable: Latin through Latin Extended-B, combining diacriticals and Greek, Cyrillic, General Punctuation, and the Geometric Shapes range that holds U+25B2. That is fourteen files and 1.4 MB, taking the executable from 17 MB to 18.3 MB.

The bundled files come from the openmaptiles fonts release, which is what OpenFreeMap itself serves, and Noto Sans Regular/0-255.pbf is byte-identical between the two. A bundled range and a proxied range therefore come from the same generation and their metrics agree. Building the distance fields here instead would mean a rasterizer, a distance transform, and a set of parameters to get wrong for no gain.

A range outside the bundled set still comes from the provider while the network is up. With no network it answers a valid glyph message holding one named, empty fontstack: MapLibre reports a 404 as a failure for text it could not have drawn either way, so an empty range says the same thing without the noise, and a short cache lifetime lets it retry. A style may also name several fonts for one layer, which arrives as a comma-separated stack, so each name is tried in turn the way a glyph server composes a stack from the fonts it holds.

Tile storage and caching

MBTiles

The reader handles both the flat tiles table and the deduplicated map plus images schema where tiles is a view. MBTiles rows are in TMS order and the application is in XYZ, so every read and write flips Y exactly once. The reader is read-only, safe for concurrent use, and reports whether a tile's stored bytes carry the gzip magic number, which the HTTP layer turns into Content-Encoding.

The writer deduplicates by content hash into images with map referencing it, which matters because a terrain pack contains many identical flat tiles. The unique index on images(tile_id) exists during the bulk load because INSERT OR IGNORE needs it to collapse duplicates; deferring it would write every duplicate to disk. The map index is built after the load. Writes batch at 2,048 tiles or 16 MiB, and the archive is written with synchronous off and the journal in memory, treating it as a regenerable build artefact.

Disk cache

Blobs live in a two-level sharded tree named by a hash of the key, so no directory holds an unbounded number of files. A SQLite index records size, content type, encoding, ETag, upstream status, fetch time, and last access.

Three details are worth naming. Locks are striped per key, shared for reads and exclusive for writes and evictions, so a reader never observes an index row without its blob and a concurrent eviction is not misreported as corruption. Access times are batched in memory and flushed on a timer, at a pending threshold, and before eviction selects victims, which keeps LRU order correct at the moment it is used without putting readers behind the writer. Removal deletes the blob before the row, so a crash mid-removal leaves a row the read path repairs rather than bytes nothing reclaims.

Writes rename a completed temporary file into place, so a crash never leaves a truncated blob a later read would trust. A 404 stores a row with no blob under a separate, shorter TTL, which stops repeated requests for a tile the upstream does not have from hammering the network.

Proxy

The browser never contacts an upstream provider. Every remote request passes through the proxy and is subject to a hostname allowlist enforced at request time and again on redirect, so a redirect cannot walk off the list.

Concurrent misses for the same key are coalesced: a map pan issues dozens of tile requests at once and the client retries, so without coalescing one slow upstream tile is fetched several times over. When the upstream is unreachable and a stale copy exists, the stale copy is served, which is the design's "cached" operating mode.

The proxy sets Accept-Encoding itself so tiles can be cached and forwarded still compressed. That turns off Go's transparent decompression, which makes decoding the caller's job: tile paths forward the bytes untouched, and JSON readers call Decoded. Getting this wrong is silent — the JSON parser simply sees gzip magic.

Import pipelines

Both commands orchestrate GDAL and Tippecanoe rather than reimplementing shapefile reading, reprojection, and vector generalization. Those are build-time dependencies; the server that reads a pack is self-contained.

Geology

A zip, directory, shapefile, or GeoPackage goes in. Layers are discovered with ogrinfo -json; when the field map names no layer, the polygon and line layers are chosen by geometry type and an ambiguous choice fails with the layers listed.

Each layer runs its own ogr2ogr emitting newline-delimited GeoJSON to /vsistdout/. The stream is rewritten one feature at a time into the tile schema and written into an os.Pipe that Tippecanoe reads as -L units:/dev/fd/3 and -L lines:/dev/fd/4, passed through ExtraFiles. Nothing larger than one feature is ever held, so a dataset of any size streams from GDAL through the rewriter into the tile compiler without touching disk a second time.

Tippecanoe flags are chosen for geologic polygons rather than for point data: --detect-shared-borders so adjacent units simplify identically and develop no slivers, --coalesce-densest-as-needed rather than dropping polygons when a tile overflows, --no-tiny-polygon-reduction-at-maximum-zoom so a small outcrop stays a real shape, simplification tightened at maximum zoom, and a real tile-size ceiling rather than none.

Field maps. Surveys name their columns inconsistently and often publish the legend outside the attribute table, so the mapping is data. Each target reads a list of candidate source fields and the first non-empty one wins. Two members exist because older surveys need them. sourceCrs supplies a projection the source fails to declare; the Bureau of Economic Geology's Austin shapefiles ship no .prj at all, and reprojecting without it would place the map elsewhere on the globe. unitTable supplies names, ages, lithologies, descriptions, and colours for a layer that carries only a symbol.

Colour precedence runs from most specific to least: the source's own colour column, then the field map's stated colour, then the chronostratigraphic chart matched against the age text. Deriving a colour from age must not override an operator's explicit legend, which is what separates formations inside one epoch. The built-in palette holds 168 entries covering eons through stages of the International Chronostratigraphic Chart.

An import builds into a temporary sibling directory and renames it into place, so a failure never leaves a partial pack. Because a pack holds both geology and terrain and the two imports run separately, the install carries across whatever the other import produced, and the manifest merges from the state before the import rather than after it.

Terrain

GDAL has no Terrain-RGB output and rio-rgbify would add a Python dependency, so GDAL reprojects and the encoding happens in Go.

Inputs are mosaicked with gdalbuildvrt. For each zoom level and each row of tiles, one gdalwarp reprojects into EPSG:3857 at exactly that row's extent and pixel size, writing ENVI float32, which Go then reads and encodes as PNG. Warping per zoom rather than downsampling a single maximum-zoom raster keeps resampling honest at every level, and the row-strip layout bounds peak memory to one row of tiles regardless of pack size. Extents are formatted with strconv.FormatFloat(v, 'f', -1, 64) so GDAL's strtod recovers the exact double: a half-pixel error would show as seams in the hillshade.

Resampling compares the destination pixel's ground size to the source cell's. Above twice the source pixel the warp is decimating and uses average, the only kernel that antialiases; at or below that it uses cubicspline.

Nodata is nan with INIT_DEST=NO_DATA, because NaN is the one float32 value real elevation cannot collide with, and pixels the warp never touches start as nodata rather than as zero. A nodata pixel encodes as 0 m, the convention MapLibre reads, and a tile that is entirely nodata is not written at all.

--dry-run reports the tile count per zoom and the estimated size before anything is written.

Elevation

Point elevation samples the terrain tile bilinearly across tile boundaries, fetching neighbouring tiles as needed and caching decoded grids in an LRU of 256 tiles, which covers an interactive profile drag without redecoding PNGs. Nodata comes from full transparency rather than from the height, because zero metres is a real elevation in both encodings.

Slope and aspect use Horn's method over the eight neighbours, the standard estimator, which behaves better on lidar-derived DEMs than a two-point difference.

Profiles sample a polyline at a count derived from the line length and the terrain resolution, so a short line is not oversampled beyond what the DEM supports. Gain and loss accumulate only past a threshold set from the source's vertical error.

The threshold does not make cumulative gain interval-independent, and nothing can: gain rises as the sampling interval shrinks, the way a coastline lengthens as the ruler shortens. Measured over one 40 km transect it ran 237 m at 16 samples, 882 m at 128, and 1,171 m at 1,024, flattening by two percent over the last doubling — mostly real dissection being resolved rather than noise. The interface therefore states the threshold next to the figure and says outright that the figure depends on the sampling interval, because the number means nothing without both.

Honesty about precision

The design requires that the application not imply more precision than a source supports. That principle shows up in several places.

Macrostrat records a Burwell scale class rather than a denominator, so the class is reported as itself and a numeric scale is parsed out of the source's own reference only when one is stated. A source that states no scale shows "not stated by the source" rather than a fabricated number.

Coordinate precision follows the ground size of a pixel: decimal places come from the zoom, seconds carry a decimal only where a pixel is finer than one second, and UTM rounds to the pixel rather than the metre. Four decimals over a globe would claim eleven metres where one pixel spans twenty-five kilometres.

A single scale bar describes one scale, which a globe does not have, so the bar is hidden below zoom 5 and reads "at centre" on a pitched view.

The national layer blends published maps of many scales. Where a more detailed quadrangle sits inside the view it appears as a rectangle of denser mapping, which reads as a rendering fault. Selecting a feature now outlines the map it came from and the inspector names it, so the rectangle explains itself.

Elevation rounds to ten metres once the sample's ground resolution exceeds 100 metres, because at that point the DEM tile is overzoomed and one sample stands for a wide patch of ground.

Client

State is a small observable record. Listeners receive the set of keys that changed, so a view can skip work it does not need — the map engine in particular must not rebuild its style because the coordinate format changed. The engine rebuilds only on a theme or dataset change and otherwise mutates paint and layout properties in place, because a style rebuild discards every loaded tile.

Permalinks encode the full view state. Layer flags pack into one string of letters, recording whichever of the on or off list is shorter; recording only the difference from the default would break if the defaults changed. Updates replace the history entry rather than pushing, so panning does not fill the back button.

Two layout details caused real defects and are worth recording. MapLibre measures its container at construction and falls back to 400 by 300 for a detached element, so the workspace is attached to the document before the map is built. And the initial source resolution happens before the first render; leaving it to the map's first moveend showed every panel an empty source.

The status bar reserves a per-format minimum width in ch units for the coordinate readout, and its own width for elevation and zoom, so the bar does not shift as the cursor moves or as digits change.

The elevation profile chart is hand-drawn SVG with no charting library. Tick intervals come from the 1, 2, 5 ladder chosen against a minimum pixel gap expressed in data units, because deriving a count by integer division lost labels at some widths. The y range never starts at zero unless the data reach it, and the chart states the vertical exaggeration its aspect ratio implies, since a profile that silently exaggerates relief misleads. Null elevations break the path rather than interpolating across a gap.

The measure panel sizes itself against the distance to the map centre rather than against the map column, because the canvas runs under the side panels and so the centre sits at half the viewport regardless of them. Its head degrades under a container query so the panel can narrow without its contents overflowing.

Operating modes and security

Mode Behaviour
Hybrid Remote national tiles are proxied and cached; installed packs override them
Cached Previously viewed areas remain available without a connection
Offline pack Terrain, geology, metadata, legends, and map text are all local
LAN Explicit binding to a local-network address with an access token

The default binds loopback only. A non-loopback host without --lan is refused, --lan without a token is refused, and the proxy contacts only allowlisted hosts. Offline mode removes the remote datasets from the catalog so the client is not offered sources it cannot reach.

A pack manifest may name its tile file, but the resolved path must stay inside the pack directory, so a mistaken or malicious manifest cannot make the server read elsewhere. Pack discovery also prunes catalog rows whose pack is gone; the catalog outlives any single run, and without pruning a removed pack leaves a dataset the client still offers and whose every tile request fails.

LAN sessions. A token in the query string authorises only the request carrying it, and a browser does not append it to the stylesheet and script the page links. The first request presenting a valid token therefore receives an HttpOnly cookie and the rest of the session travels on that. The client also carries the token itself, as a header on its own requests and in the URL for the tiles and glyphs MapLibre fetches from workers where no header can be set, and only for requests to this server.

Visual QA

tools/visualqa/shoot.mjs drives the system Chromium through Playwright, loads the client at named application states expressed as real permalinks, and writes one PNG per state with a manifest recording viewport, theme, capture time, size, and a SHA-256 of the bytes. It starts and stops its own server, and exits non-zero if any view logs a console error, so a loop can gate on it.

Determinism is the point, and it is pursued from outside the client. The browser runs with a fixed viewport, device scale factor, locale, timezone, reduced motion, sRGB profile, and font flags disabling hinting and subpixel positioning. An init script seeds Math.random and crypto.randomUUID, injects a stylesheet zeroing every animation and transition, and fixes the wall clock while letting elapsed time advance from performance.now — a fully frozen clock breaks interval arithmetic, including MapLibre's tile expiry. Before each capture the harness waits for load, network idle, document.fonts.ready, no animation frame for 600 ms, and finally three consecutive byte-identical samples of the viewport, which is what proves the WebGL canvas has settled.

Each view gets a fresh browser, because the software renderer does not release a WebGL context when its page closes and one browser across a whole run exhausts it partway through. Errors logged after the image exists are ignored, since closing the page aborts in-flight tile requests that did not affect the capture.

--compare composes side-by-side images from two runs, labelled only "A" and "B" with no filename visible, for blind review. Three rounds of adversarial critique were run this way; devnotes.md records the measurements and the defects they found.

Residual nondeterminism is recorded rather than hidden: remote tiles can change upstream, tile arrival order can move a label near a boundary, Playwright init scripts do not reach web workers, and SwiftShader is deterministic per build and CPU but not across machines.

Upstream defects worked around

Macrostrat has disabled its single-scale tile layers. /medium/... and /large/... answer 404 while /carto/... serves. The design's zoom-to-scale table depends on the disabled ones, so the application proxies carto and derives each feature's published scale from its own source_id instead of from the zoom. The per-scale routes remain implemented and will work when upstream re-enables them.

The documented /defs/sources route on the production v2 API returns an empty array for every query, including the examples in its own documentation. The same query answers on the development host, and the PostgREST table at /api/pg/sources answers in production, so the resolver reads that table. It holds a few hundred rows, small enough to load whole once and cache, which removes a per-feature round trip from every click. The cache key carries a digest of the request, so widening the field list does not go on serving an older response that lacks the new field.

Attribution

National geology comes from Macrostrat under CC BY 4.0, which requires citing Macrostrat and the original data providers; the inspector resolves each feature to the survey that published it. Elevation comes from the USGS 3D Elevation Program, directly for packs and through AWS Terrain Tiles for the national layer. Reference features come from OpenFreeMap over OpenMapTiles and OpenStreetMap. Place search uses Nominatim. Map text is drawn with Noto Sans, whose glyph ranges are compiled in under the SIL Open Font License and whose licence text is served at /fonts/OFL.txt. Each installed pack carries its own citation file so it stays attributable if copied away from the catalog that installed it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment