Short answer
deck.gl is a WebGL (and increasingly WebGPU) framework for drawing large geospatial datasets in the browser by pushing geometry onto the GPU as typed-array buffers. Where Leaflet markers or even MapLibre vector layers strain at tens of thousands of features, deck.gl renders millions of points, lines, arcs, or polygons interactively. You rarely use it alone: it interleaves with MapLibre through MapboxOverlay, so the basemap and ordinary vector tiles stay in MapLibre while deck.gl handles the heavy, custom, or 3D layer. The performance lever is data format and tiling — binary buffers and on-demand tiles — far more than the choice of layer class.
The deck.gl model
deck.gl is layer-based and reactive. You declare a list of layer instances; deck.gl diffs them on each render and updates only what changed. Each layer is a GPU program: it uploads your data once as attribute buffers and redraws every frame from the GPU, which is why pan/zoom stay smooth even with huge datasets.
Core layers you will reach for in Earth-data work:
- ScatterplotLayer — points (sample sites, occurrences) with per-feature radius and colour.
- PathLayer / LineLayer / ArcLayer — traverses, drill traces, flow/connectivity.
- PolygonLayer / GeoJsonLayer — geology polygons, catchments (GeoJsonLayer dispatches to point/line/polygon sublayers).
- ColumnLayer / HexagonLayer / GridLayer — 3D extrusions and spatial aggregation.
- HeatmapLayer / ScreenGridLayer — density when individual features are not needed.
- MVTLayer — vector tiles, loading only the current viewport.
- TerrainLayer — a mesh from a terrain (RGB-encoded) DEM, for 3D relief.
- PointCloudLayer — LiDAR-style point clouds.
Layers accept accessors (getPosition, getFillColor, getRadius) that can be constants or functions reading from your data — and crucially can read directly from binary buffers.
Interleaving with MapLibre
The mature integration is MapboxOverlay (works with MapLibre GL JS) in interleaved mode:
import { MapboxOverlay } from '@deck.gl/mapbox';
import { ScatterplotLayer } from '@deck.gl/layers';
const overlay = new MapboxOverlay({
interleaved: true,
layers: [
new ScatterplotLayer({
id: 'samples',
data: '/api/samples.bin', // binary, not GeoJSON
getPosition: d => d.position,
getRadius: 30,
getFillColor: [200, 80, 40, 180],
}),
],
});
map.addControl(overlay); // map is a maplibre-gl Map
interleaved: true shares MapLibre's WebGL context, so deck.gl layers can sit between MapLibre layers (e.g. above the fill basemap but below labels) and respect depth for 3D. interleaved: false (overlaid) draws deck.gl in a separate canvas on top — simpler, but it cannot interleave by z-order. Both share MapLibre's camera, so bearing/pitch/zoom stay synchronized.
Making it fast: data format and tiling
The biggest performance wins are upstream of the layer choice.
-
Use binary, not GeoJSON objects. Parsing a 200 MB GeoJSON into JS objects is slow and memory-heavy. Supply typed arrays (Float32 positions, Uint8 colors) via the binary
dataform, so deck.gl uploads them to the GPU with no per-feature JS iteration.@loaders.glcan parse Arrow/GeoParquet/FlatGeobuf into this shape. -
Tile large datasets. For continental or city-scale vector data, render an
MVTLayerso only the tiles in view (and at the current zoom) are fetched and drawn — pair it with the sameST_AsMVTPostGIS endpoint a MapLibre map would use. For rasters,TileLayerloads image tiles on demand. -
Aggregate when you do not need every feature. A million points as individual dots is visual noise and GPU load;
HexagonLayer,ScreenGridLayer, orHeatmapLayerbin them into a readable density surface and draw far less geometry. -
Cap update churn. deck.gl re-uploads a buffer when the
datareference changes. Keep data references stable and useupdateTriggersto invalidate only the accessor that actually changed, rather than recreating the whole dataset each render.
Worked example: a million-point geochemical layer
Goal: an interactive map of ~1.2 M soil-geochem samples coloured by an element value, over a MapLibre basemap.
- Prepare data for the web. Reproject to lon/lat (EPSG:4326), drop unused columns, and export to GeoParquet or a packed binary. Pre-compute the colour-driving value so the browser does not.
- Decide: points or density. At zoom-out, 1.2 M dots overplot uselessly — use a HexagonLayer (aggregate mean value per hexbin) for the overview and switch to a ScatterplotLayer fed from an MVTLayer when the user zooms past, say, z12.
- Bind colour to data via a quantized colour scale in
getFillColor, with the breakpoints documented in the legend. - Keep uncertainty visible. Encode detection-limit or below-LOD samples distinctly (e.g. hollow symbols), and expose the value, units, and sample date in the tooltip — do not let a smooth gradient imply false precision.
- Test with the full 1.2 M, not a 1 k sample, on a mid-range laptop. Performance problems only appear at real volume.
When to reach past deck.gl
- Globe / planet-scale 3D, streaming meshes, huge point clouds: Cesium with 3D Tiles is purpose-built for streaming photogrammetry, BIM, and massive LiDAR over an ellipsoidal globe. deck.gl does 3D well at city-to-regional scale but is not a 3D-tiles streaming engine (though deck.gl can consume 3D Tiles via
Tile3DLayerfor many cases). - Simple maps: if the dataset is small, plain MapLibre vector layers or even Leaflet are less machinery.
- Heavy cartographic styling/labelling: MapLibre's style spec and label engine are stronger than deck.gl's text handling.
Common pitfalls and why they happen
- Feeding raw GeoJSON at scale. The cost is JSON parsing and per-feature JS, not GPU draw. Large GeoJSON freezes the main thread; binary buffers and tiling avoid it.
- Loading everything, then filtering client-side. Without tiling, the browser downloads and holds the whole dataset. Use
MVTLayer/TileLayerso the viewport drives loading. - Overlaid when you needed interleaved (or vice versa). Overlaid mode can't z-order deck.gl between MapLibre layers and breaks 3D depth against terrain; interleaved fixes ordering but shares context constraints.
- 3D for its own sake. Pitch and extrusion impress but often hurt comparison and quantitative reading. Add 3D only when elevation or height is the message.
- Hiding uncertainty behind a gradient. A polished GPU heatmap implies precision the data may not have; keep source, units, and confidence in tooltips and notes.
QA and validation
- Confirm coordinates are lon/lat (EPSG:4326) before tiling/upload; a wrong CRS misplaces everything silently.
- Verify aggregation layers (hexbin/grid) report the statistic you intend (mean vs sum vs count) and that empty bins are handled.
- Test on representative hardware at full data volume and watch frame time and memory.
- Check tooltips expose source, value, units, and date; confirm legend breakpoints match the colour accessor.
Bathyl perspective
We use deck.gl to keep large Earth-data layers inspectable in the browser without dumbing them down: binary, tiled data over a MapLibre basemap, aggregation at small scales and full detail on zoom-in, and uncertainty kept visible in tooltips and legends. The frontend library is the last 10% — the work is the data strategy of tiling, formatting, and honest encoding behind it.
Related reading
- When to Use MapLibre Instead of Leaflet
- Vector Tiles for Geological Maps
- Preparing GIS Data for Web Visualization
- Mapping systems