Skip to content

50,000 free calls a month, card-free. Get an API key →

Point clouds

Point clouds inside the map, not floating over it.

@mapmap/points draws lidar and photogrammetry clouds in the map’s own WebGL context: one buffer, one draw call, compositing with the basemap, the terrain and every other layer you already render. Ten bytes a point on the wire, your class bytes kept as written, and the same snk_ key that runs the rest of the platform.

50,000 free calls a month, card-free.

Point cloud: Stadt Wien - data.wien.gv.at, CC BY 4.0.

@mapmap/pointsESM · Node 18+
Wire format10 bytes a point
Draw callsone, in the map's context
Classes per layerup to 16, your byte values
Colour modesrgb · class · height
Package@mapmap/points v0.3.0
What the layer does

One draw call, ten bytes a point, your class table.

The package installs from npm today and renders payloads you host yourself, against the hosted gateway at api.mapmap.ai or your own self-hosted deployment.

One draw call

Inside the map's WebGL context

A MapLibre CustomLayerInterface sharing the map's WebGL context. The cloud is depth-correct against terrain and 3D buildings, moves with the camera exactly as the basemap does, and costs one draw call. With terrain on, terrainRelative rebuilds the eye position from the terrain surface, so point sizes and fog distances are computed from where the camera actually is.

Wire format

Ten bytes a point

Three Uint16 positions and four Uint8 bytes of colour, class in the alpha byte. About 48 bytes a point becomes 10, and both blocks reach the GPU untouched: decoding is two typed-array views over the downloaded ArrayBuffer, with no copy and no per-point loop. The shader dequantises.

Class table

Your classes, your colours

The SDK has no opinion about what a class byte means. Pass your table and your byte values survive: 40 stays 40. Colour by the payload's own RGB, flat per class, or a height ramp, and build the legend from getClasses().

From a baked payload to a layer

Bake it offline, then add one layer at runtime.

Step 1

Bake to the 10-byte format, offline.

Production baking happens offline, in Python or Rust, and produces one JSON sidecar and one binary blob. encodePointCloud exists for small clouds built in the browser. The sidecar carries anchor as [lng, lat], quant in metres per position unit, origin, count, the local bounding box, the class table and terrainRelative. validateMeta fails a sidecar whose extent will not fit the format rather than letting a cloud fold back on itself: at the default 2 cm quantisation a Uint16 axis spans 1.3 km.

Step 2

One draw call.

Positions and colours are uploaded as two interleaved buffers and drawn as a single THREE.Points call inside the map’s context. There is no level-of-detail tree and no paging: everything is resident, which keeps the layer simple and puts a hard ceiling on how big a cloud can be.

Step 3

The class table is an input.

FieldMeaning
valueYour class byte, 0 to 255, preserved as written
labelWhat the legend shows
colour#rrggbb or an [r, g, b] triple in 0 to 1, optional
visibleInitial visibility, optional

Up to 16 classes per layer, a WebGL1 uniform-budget limit. Duplicate bytes, out-of-range values and oversized tables throw at load. Omit the table entirely and you get RGB and height colouring, and setColourMode("class") throws rather than inventing a taxonomy.

The MapMap lidar viewer over the Pool of London with the cloud coloured by class rather than height: ground pale grey, buildings slate, vegetation green and objects orange, with a Point classes key naming the four in the top-right corner
Step 4

Colour modes and runtime controls.

ModeWhat it shows
rgbThe payload's own colours, the default
classFlat per-class colours from your table
heightA ramp over local height

Drive it from your own UI: setColourMode, setClassVisible, setAllClassesVisible, setPointSize in metres, getFps for a rolling one-second average, getClasses for the resolved table. A point is 0.15 m in the world by default and capped at 10 device pixels on screen, which you raise for offline video bakes.

The MapMap lidar viewer over the Pool of London with the Layers panel open: rows for Ground, Buildings, Vegetation and Objects with their point counts, Vegetation switched off and shown as a hollow swatch, and the control bar reading 2,777,908 of the scene's 3,602,663 points drawn

Contains Environment Agency data. © Environment Agency and database right. Open Government Licence v3.0.

Two fetches to a rendered cloud

Install it, then add the layer.

bash
npm install @mapmap/points three maplibre-gl
html
<div id="map" style="height: 480px"></div>
ts
import { classesFromMeta, createPointCloudLayer } from "@mapmap/points";

const [meta, raw] = await Promise.all([
  fetch("/cloud.json").then((r) => r.json()),
  fetch("/cloud.bin").then((r) => r.arrayBuffer()),
]);

map.on("style.load", () =>
  map.addLayer(
    createPointCloudLayer({ meta, raw }, { classes: classesFromMeta(meta.classes ?? {}) }),
  ),
);

three and maplibre-gl are peer dependencies, and the app must provide a single shared copy of each: two MapLibre instances on one page break the WebGL context, and two copies of three give you two incompatible sets of classes. @mapmap/points is v0.3.0, ESM only, and needs Node 18 or later to build. See the full SDK reference.

What it is not

A renderer, not a platform.

You should know where it stops before you spend a day on a trial.

One COPC per layer. openCopc streams a single file over HTTP range requests. Multi-file mosaics and EPT sources are not in 0.3.0, and a plain LAZ is not a COPC: pdal translate converts one in a single command.

The baked path is still not an LOD system. A 10-byte payload is fetched whole and stays resident, which works comfortably to a few million points on a modern GPU. Past that, bake to COPC and stream it, or split the area into several clouds and swap layers by viewport.

CRS support has edges. A streamed COPC is placed from its own WKT record: geographic, Web Mercator, Transverse Mercator (every UTM zone, BNG, MTM) and Lambert Conformal Conic 2SP. Anything else throws UnsupportedCrsError naming the CRS, and a toWgs84 hook takes you past it. The baked path reprojects nothing at all: the sidecar's anchor is trusted as lng/lat, so check an uncertain dataset first with validate_geodata or POST /geodata/validate. Neither path performs a datum shift.

No measurement, annotation or share links. None of the three exist in the SDK today.

What meters, and what does not

The points are not a metered event.

You host the payloads and the package renders them, so drawing the points never hits our meter. What meters is the map underneath: the same snk_ key as the rest of the platform, issued card-free with 50,000 free calls a month, or nothing at all once the stack is running on your own infrastructure.

If the cloud is not baked yet, that is a fixed-price package rather than a subscription. A site or corridor bake is £3,600: we take your survey delivery, bake it to the wire format, agree the class table as you number it, check the declared CRS against the coordinates and stand a viewer up on the deployment you will actually use, then hand over the payload and its sidecar for you to serve. A complex bake, where the classification is mixed or undocumented or the CRS has to be reconciled before anything can be trusted, is £6,000. Larger programmes are scoped per project. After handover there is nothing recurring: no per-point charge, no per-payload charge, no charge for looking.

If you would rather we hosted and served the payloads too, that is live, self-serve and priced. Storage is £1 per GB a month with a £10 monthly minimum, billed monthly against measured stored bytes, pro-rata to the byte. Streaming meters as standard calls on the same snk_ key as everything else: one standard call per five payload requests, counted in started 4 MiB pieces, so a whole-file download meters exactly as fetching it in chunks would. Payloads are baked and staged by us rather than uploaded self-serve, and rendering payloads you host yourself stays free and unmetered: hosting on MapMap is optional, never required.

Issue a key and put a cloud on the map.

Card-free, with 50,000 free calls a month. Bake a corridor, fetch two files, add a layer.