Short answer

An interactive geological map is a web map where the geology is data, not a picture. Each polygon carries its attributes (lithology, stratigraphic unit, age, source), so a user can click it to read those values, filter the map to a single formation, toggle faults and contacts independently, restyle units on the fly, and read coordinates under the cursor. A scanned geological sheet behind zoom buttons is not interactive; it is a raster image. The difference is whether the attributes survive the trip to the browser.

In practice this means delivering geology as vector tiles rendered by a GL engine such as MapLibre, with data-driven styling that colours units by attribute rather than by a fixed legend image. That architecture is what makes a national-scale map both fast and queryable.

What "interactive" actually requires

Three capabilities separate a real interactive map from a slippy image:

  1. Identify / query. Clicking a unit returns its attributes. This only works if geometry and attributes are delivered together, which rules out pre-rendered raster tiles.
  2. Data-driven symbology. The colour of a polygon is computed from a field (e.g. stratigraphic_age) at render time, so the same data can be styled by age, by lithology, or by confidence without re-exporting anything.
  3. Independent layers and state. Faults, contacts, boreholes, structural measurements, and the geological polygons are separate sources the user can toggle, filter, and order. Application state (which layers are on, the active filter, the selected feature) lives in the app, not in the tiles.

A modern GL map cleanly separates four things: sources (where data comes from), layers (how each source is drawn), interaction logic (clicks, hovers, filters), and application state. Keeping these separate is what lets you add a "filter to Jurassic units" control without touching the data pipeline.

Choosing a delivery format

The format decision is driven by feature count and the need to query:

  • Raster (XYZ/WMTS) tiles are pre-rendered PNG/JPEG images. They are the cheapest to serve and render, ideal for a historical scanned sheet or a smooth hillshade backdrop, but they carry no attributes and the styling is frozen at render time.
  • GeoJSON ships raw geometry and attributes to the client. It is perfect up to a few thousand features (a project area, a set of boreholes) and trivial to filter in JavaScript, but a national geology layer as one GeoJSON can be hundreds of megabytes and will stall the browser.
  • Vector tiles (Mapbox Vector Tiles, the .pbf/.mvt format) clip, simplify, and pack geometry plus attributes per tile and zoom level. They stay interactive at country scale because the client only ever loads the tiles in view. This is the default for any sizeable geology layer.
  • OGC API – Features or a tile server exposes the database directly, useful when the geology changes often and you want live queries instead of a static tileset.

A concrete build

A typical pipeline for a regional geological map:

  1. Clean and load the geology into PostGIS. Store everything in one CRS (often EPSG:4326 for web delivery, reprojected from the survey CRS with ST_Transform). Validate geometry with ST_IsValid / ST_MakeValid so tile generation does not choke on self-intersections.
  2. Generate vector tiles. From a GeoJSON/FlatGeobuf export, tippecanoe -zg --drop-densest-as-needed -o geology.mbtiles geology.geojson builds a tileset with sensible zoom-dependent simplification. Alternatively, serve straight from PostGIS with Martin or pg_tileserv, which emit MVT on demand.
  3. Style with MapLibre GL. In the style JSON, a fill layer colours units with a match expression on the unit code:
{
  "id": "geology-fill",
  "type": "fill",
  "source": "geology",
  "source-layer": "units",
  "paint": {
    "fill-color": ["match", ["get", "lithology"],
      "limestone", "#7fc3e8",
      "granite", "#e8a07f",
      "#cccccc"],
    "fill-opacity": 0.7
  }
}
  1. Wire interaction. A map.on('click', 'geology-fill', ...) handler reads e.features[0].properties to populate an attribute panel; map.setFilter('geology-fill', ['==', ['get', 'period'], 'Jurassic']) implements the period filter.
  2. Add specialist layers. Faults and contacts as line layers (dashed for inferred, solid for observed via a data-driven line-dasharray), boreholes as a circle or symbol layer, and a hillshade raster source underneath for relief context.

For very large point or line datasets (millions of structural measurements, dense LiDAR-derived features), deck.gl layers render on the GPU and integrate with MapLibre, handling volumes a standard fill layer cannot.

When 3D is justified

3D adds cost and cognitive load, so reserve it for genuinely volumetric questions: borehole stratigraphy, cross-sections, 3D geological models, or draping units over exaggerated terrain to read structure. Cesium with 3D Tiles handles globe-scale terrain, point clouds, and tiled meshes; MapLibre's own terrain and fill-extrusion cover lighter cases. The common mistake is reaching for 3D for visual impact when the user actually needs to compare units and faults, a task plan-view 2D does better and faster.

Common pitfalls and why they happen

  • Shipping desktop GIS files straight to the browser. A 400 MB Shapefile or GeoPackage dumped as GeoJSON freezes the tab. The fix is a tiling step, which is a deliberate part of the pipeline, not an afterthought.
  • Baking the legend into a raster. Pre-rendered tiles look fine until someone asks to recolour by age; with no attributes you must re-render everything. Vector tiles avoid this.
  • Losing the source CRS. Reprojecting to 4326 for delivery is fine, but if the original survey CRS and scale are not recorded in metadata, no one can trace a feature back to its sheet.
  • Hiding uncertainty behind polish. Inferred contacts, low-confidence units, and generalised boundaries should be visible (dashing, opacity, a confidence field), not smoothed away by a clean interface.
  • Over-simplifying at tile-generation. Aggressive tippecanoe simplification can detach a thin dyke or merge adjacent units; check the highest zoom against the source.

QA and validation

Test with realistic data volume, not a 50-feature sample, because performance problems only appear at scale. Verify a click on any unit returns the same attributes as the database row. Confirm geometry validity before tiling (ST_IsValid), check that filters and layer toggles change state predictably, and confirm the CRS chain from source survey to delivered 4326 is documented. Inspect tile boundaries at several zooms for clipping artefacts on long faults.

Bathyl perspective

We build geology viewers so the science stays inspectable: every unit clickable, every layer toggleable, uncertainty visible, and the source sheet and CRS one click away. The interface should lower the effort of reading the map while preserving the depth that makes the data worth trusting.

Related reading

Sources