Short answer
Vector tiles let a geological map stay interactive and restyleable at any scale by chopping the data into a pyramid of small, simplified, attribute-carrying tiles (the Mapbox Vector Tile / MVT format). The usual path: export the geology to GeoJSON in WGS84, build tiles with tippecanoe (or ST_AsMVT from PostGIS for live serving), then point MapLibre GL at the tile source and drive lithology colors, fault dash patterns, and click popups from the per-feature attributes. The advantage over GeoJSON is that the browser only loads the features in view at a sensible detail for the current zoom, so a national geological dataset stays smooth.
Why vector tiles fit geological data
Geological maps are awkward on the web: polygons are large and irregular, contacts and faults are detailed lines, and a single sheet can carry tens of thousands of vertices. Shipping that as raw GeoJSON forces the browser to download and parse everything before the first paint.
Vector tiles solve this with three properties:
- Tiling. Data is cut into a quadtree of 256/512-px tiles per zoom level. The client fetches only the tiles covering the viewport, so payload scales with screen size, not dataset size.
- Per-zoom generalization. Geometry is simplified at low zoom (you do not need every vertex of a contact when the whole country fits on screen) and full-detail at high zoom.
- Attributes survive. Unlike a pre-rendered raster tile, each feature keeps its properties —
lithology,age,unit_code,confidence— so styling and interaction happen on the client. The same tiles can be themed by age one day and by rock type the next, with no re-rendering.
That last point is the real win for geology: raster tiles bake in one legend; vector tiles let the legend be a style choice.
Building the tiles with tippecanoe
tippecanoe is the standard offline builder. First get the geology into GeoJSON in EPSG:4326 (vector tiles are defined in Web Mercator, and tippecanoe expects WGS84 input):
ogr2ogr -t_srs EPSG:4326 -f GeoJSON geology.geojson geology_utm.gpkg
Then build, controlling the zoom range to match the source scale and protecting attributes:
tippecanoe -o geology.mbtiles \
--minimum-zoom=6 --maximum-zoom=14 \
--layer=units \
--drop-densest-as-needed \
--no-tiny-polygon-reduction \
--coalesce-densest-as-needed \
--simplification=4 \
geology.geojson
Key flags for geological data:
--maximum-zoomshould reflect the source map scale. A 1:50,000 sheet carries no real information past ~z15; building beyond that fakes precision.--drop-densest-as-neededthins features only where a tile would exceed the size limit, rather than uniformly — important so sparse units survive at low zoom.--no-tiny-polygon-reductionstops tippecanoe from deleting small but geologically real polygons (dykes, inliers) at coarse zoom; weigh this against tile size.--simplificationcontrols Douglas-Peucker aggressiveness; lower keeps contact detail, higher shrinks tiles.
Build faults/contacts as a separate layer (-L) from polygons so you can style and simplify them independently — lines and fills want different generalization.
For a database-backed live service, serve tiles directly from PostGIS instead of pre-building:
SELECT ST_AsMVT(t.*, 'units', 4096, 'geom') FROM (
SELECT unit_code, lithology, age,
ST_AsMVTGeom(ST_Transform(geom, 3857), ST_TileEnvelope(z, x, y)) AS geom
FROM geology
WHERE geom && ST_Transform(ST_TileEnvelope(z, x, y), <source_srid>)
) t WHERE geom IS NOT NULL;
ST_TileEnvelope(z,x,y) builds the tile bounds, ST_AsMVTGeom clips and quantizes geometry into the tile grid, and ST_AsMVT packs it. This pairs with a thin tile endpoint and is ideal when the geology updates and you do not want to rebuild an mbtiles each time.
Styling in MapLibre GL
Add the source and style the layers from attributes. Lithology by color, faults by dash:
{
"sources": {
"geology": { "type": "vector", "url": "https://tiles.example.com/geology.json" }
},
"layers": [
{
"id": "units-fill",
"type": "fill",
"source": "geology",
"source-layer": "units",
"paint": {
"fill-color": [
"match", ["get", "lithology"],
"sandstone", "#e7c98a",
"shale", "#7a8a99",
"limestone", "#9fc6d6",
"granite", "#d9a7b0",
"#cccccc"
],
"fill-opacity": 0.7
}
},
{
"id": "faults",
"type": "line",
"source": "geology",
"source-layer": "faults",
"paint": { "line-color": "#b00", "line-width": 1.3, "line-dasharray": [3, 2] }
}
]
}
Because attributes are in the tile, a click handler can read feature.properties.unit_code and lithology for a popup, and you can add a "color by age" toggle that swaps the fill-color expression to read age — no server round-trip. For very large datasets or GPU-heavy styling, deck.gl's MVTLayer consumes the same tiles and binds getFillColor/getLineColor accessors against the same fields.
Worked example: a national 1:50,000 dataset
Suppose you have a merged national 1:50,000 bedrock layer (~120,000 polygons) plus a fault layer. Plan:
- Reproject both to EPSG:4326, export to GeoJSON.
- tippecanoe at z6-z15, units and faults as separate layers,
--drop-densest-as-needed,--simplification=3. - Keep
unit_code,lithology,age,confidence,source_sheetas attributes; drop bulky free-text description fields to keep tiles small (re-join descriptions client-side from a small lookup if needed). - Inspect tile sizes — aim to keep individual tiles under ~500 KB; if any blow past, raise simplification or drop attributes, not the whole zoom level.
- Serve
.mbtilesvia a tile server or unpack to a static.pbfdirectory behind a CDN.
Common pitfalls and why they happen
- Building to z18 from a 1:50,000 source. Users zoom in and see jagged digitizing artefacts presented as crisp detail. Cap
--maximum-zoomto the source scale. - Tiny polygons vanishing. Default tiny-polygon reduction deletes small units at low zoom; a key inlier or dyke disappears. Use
--no-tiny-polygon-reductionor set--minimum-zoomhigher for that layer. - Lost attributes. tippecanoe can drop or coalesce attributes under size pressure; a popup then shows
undefined. Verify the fields survived (decode a sample tile) and trim unneeded fields before tiling. - Fat tiles from over-detailed contacts. Lines with millions of vertices bloat tiles. Pre-simplify in PostGIS (
ST_SimplifyPreserveTopology) or raise--simplification. - CRS confusion. Feeding UTM GeoJSON to tippecanoe misplaces everything; MVT geometry is Web Mercator built from WGS84 input. Always reproject to 4326 first.
Validation checks
- Decode a representative tile (
tippecanoe-decodeor a tile inspector) and confirm the expected layers, zoom range, and attributes are present. - Overlay the tiled layer against the original GeoPackage in QGIS at several zooms; generalization should be invisible at the intended scales and contacts should still register on a trusted basemap.
- Click features across zoom levels and confirm
lithology/unit_codereturn correctly — silent attribute loss only shows up under interaction. - State the source scale and the tile zoom range in the layer metadata so consumers do not over-zoom into false precision.
Bathyl perspective
We tile geological data to the scale it was actually mapped at, keep the legend-driving attributes intact, and serve faults and units as separate styleable layers. The result is a national-scale map that stays interactive on a phone while still telling the reader what each polygon is and how far to trust it.
Related reading
- Vector Tiles for Web Maps
- Uncertainty Display in Geological Maps
- WebGL for Earth Data Visualization
- Preparing GIS Data for Web Visualization
- Mapping systems