Documentation menu
@mapmap/points reference
Generated from the published @mapmap/points 0.6.0 type declarations by
website/scripts/sdk-reference.mjs. Do not edit by hand: change the JSDoc in the package, or the examples inwebsite/content/sdk-reference/points.examples.md, and regenerate.
Every public export of the point-cloud SDK, as declared in the package. The guide, with the format, budgets and the streaming walkthrough, is Point clouds in SDKs & installation.
npm install @mapmap/points@0.6.0 @mapmap/maps@0.25.1 maplibre-gl three
Other SDK references: @mapmap/maps, @mapmap/core, @mapmap/tile, @mapmap/react-native, @mapmap/voice, MapMapKit, ai.mapmap:core.
101 exports across 2 entry points: 2 classes, 30 functions, 39 interfaces, 20 types, 10 constants.
How to read this page
Each entry gives the declaration as published, the JSDoc description, and a table of parameters or properties with their types. The Default column quotes the default the source states (an @default tag or a "Default ..." sentence in the JSDoc). "required" means the property must be given; "not stated" means the source gives no default, so none is claimed here. Classes, factories and the main entry functions each carry a minimal example; examples marked from the source are the package's own @example.
Contents
@mapmap/points: 99 exports@mapmap/points/worker: 2 exports
@mapmap/points
import { /* … */ } from "@mapmap/points";
Classes (@mapmap/points)
CopcTransportError, UnsupportedCrsError
CopcTransportError
Class
class CopcTransportError extends Error { … }
Every error this layer throws, so a caller can catch one type.
Extends Error.
| Property | Type | Default | Description |
|---|---|---|---|
readonly code | CopcTransportErrorCode | ||
readonly status | number | null | HTTP status, when there was one. | |
readonly url | string | null | The URL being read, when there was one. |
Constructor and methods
constructor(code: CopcTransportErrorCode, message: string, options?: { status?: number | null; url?: string | null; cause?: unknown; })
Example
import { CopcTransportError, isAbortError, openCopc } from "@mapmap/points";
try {
await openCopc("https://example.com/scan.copc.laz");
} catch (err) {
if (isAbortError(err)) {
// cancelled, not failed
} else if (err instanceof CopcTransportError && err.code === "range-unsupported") {
console.error("The server ignores Range requests:", err.url);
} else {
throw err;
}
}
UnsupportedCrsError
Class
class UnsupportedCrsError extends Error { … }
Thrown when a CRS is outside the v1 set and no crs hook was supplied.
Extends Error.
| Property | Type | Default | Description |
|---|---|---|---|
readonly crsName | string | ||
readonly epsg | number | undefined | ||
readonly family | string | undefined |
Constructor and methods
constructor(message: string, crsName: string, epsg?: number, family?: string)
Example
import { UnsupportedCrsError, openCopc } from "@mapmap/points";
try {
await openCopc("https://example.com/scan.copc.laz");
} catch (err) {
if (err instanceof UnsupportedCrsError) {
// Supply a crs hook (proj4 satisfies it) for this CRS.
console.error(err.crsName, err.epsg);
} else {
throw err;
}
}
Functions (@mapmap/points)
absoluteHeightsFromMeta, asprsClassLabel, autoFogRange, cacheBytesForBudget, clampHeightRange, classesFromMeta, classTableProblems, createPointCloudLayer, decodePointCloud, defaultHeightRange, deviceMemoryGb, encodePointCloud, eyePosition, formatClearance, formatLateral, isAbortError, isStreamingSource, localOffset, metresPerPixel, openCopc, payloadBytes, pointBudgetForDevice, pointSizeScale, reportToGeoJson, resolveClasses, stickyElevation, validateMeta, visiblePointCount
absoluteHeightsFromMeta
Function
function absoluteHeightsFromMeta(meta: Pick<PointCloudMeta, "absoluteHeights" | "terrainRelative">): boolean;
Resolve a sidecar's height datum to the one boolean the layer needs.
absoluteHeights is the field; terrainRelative is the 0.1.0 spelling of
the same thing, kept as an alias so an existing sidecar renders
identically. They share a polarity, terrainRelative: true always
meant absolute heights, whatever its docstring claimed, so the alias
needs no inversion, only a rename. See #678.
Absent from both is true: that is what 0.1.0 did with an unset field,
and it is the right default anyway, since a cloud georeferenced well
enough to sit on a map is nearly always in absolute metres.
The deprecation warning fires once per process rather than per call: the layer resolves this inside its render loop, and a warning on every frame would bury the console it is trying to be seen in.
| Parameter | Type | Default | Description |
|---|---|---|---|
meta | Pick<PointCloudMeta, "absoluteHeights" | "terrainRelative"> | required |
Options and types: PointCloudMeta.
asprsClassLabel
Function
function asprsClassLabel(value: number): string | undefined;
A label for a class byte, or undefined when the standard does not name
one. Never invents a name for a user-definable byte.
| Parameter | Type | Default | Description |
|---|---|---|---|
value | number | required |
autoFogRange
Function
function autoFogRange(box: LocalBox): FogRange;
Fog distances derived from a cloud's horizontal extent.
nearM is 0.6 of the horizontal diagonal, floored at 120 m so a tiny
cloud is not fogged from the doorstep; farM is three times that, which
keeps the far end beyond the point where a viewer has pulled back far
enough to see the whole cloud. The vertical extent is deliberately not
in the calculation: fog is about how far away things are on the ground,
and a single tall mast should not push a street scene's fog out by 80 m.
| Parameter | Type | Default | Description |
|---|---|---|---|
box | LocalBox | required |
Options and types: LocalBox.
cacheBytesForBudget
Function
function cacheBytesForBudget(pointBudget: number, override?: number): number;
Decoded-cache byte budget: 50 % headroom over the point budget, capped.
The headroom is what stops a small camera wobble evicting and immediately refetching; the cap stops a 32 GB desktop filling a quarter of a gigabyte for no visible benefit.
| Parameter | Type | Default | Description |
|---|---|---|---|
pointBudget | number | required | |
override? | number | not stated |
clampHeightRange
Function
function clampHeightRange(minM: number, maxM: number): MetreRange;
Put a caller's height range into a state the shader can divide by.
Throws on a non-finite bound, because there is no sane clamp for NaN
and silently substituting one hides the caller's own arithmetic bug.
An inverted or degenerate range is repaired rather than thrown: the
caller is usually a slider, and a slider whose handles meet should not
take the viewer's cloud down with it.
| Parameter | Type | Default | Description |
|---|---|---|---|
minM | number | required | |
maxM | number | required |
classesFromMeta
Function
function classesFromMeta(table: MetaClassTable | null | undefined): PointClass[];
Build a class table from a sidecar's classes, in either shape.
Two shapes are read because two shapes exist in the wild. A map of byte →
label is the compact one; a list of { byte, label, colour } is what most
bakers emit, because a list survives a round trip through a language whose
object keys are not strings. Accepting only the map meant the list decoded
to NaN-valued rows that threw at first render, a long way from the sidecar
that caused it.
- Map: ordered by class byte, since a JSON object has no order worth trusting. Non-numeric keys are dropped rather than coerced to NaN.
- List: kept in the producer's own order, because they chose it and
it drives the default palette.
colourandcolorare both read.
Your byte values survive as your byte values in both: 40 stays 40.
A table longer than MAX_CLASSES is truncated with a warning
rather than thrown, so a viewer handed a 20-class sidecar still draws
something, resolveClasses would refuse it outright.
Anything that is neither shape throws, naming both.
| Parameter | Type | Default | Description |
|---|---|---|---|
table | MetaClassTable | null | undefined | required |
Options and types: MetaClassTable.
classTableProblems
Function
function classTableProblems(classes: unknown): string[];
Check a classes value against both accepted shapes.
Returns the problems rather than throwing, so validateMeta can
report them alongside everything else it found. A wrong shape used to
surface as a NaN-keyed class table and a throw at first render, which is
a long way from the sidecar that caused it.
| Parameter | Type | Default | Description |
|---|---|---|---|
classes | unknown | required |
createPointCloudLayer
Function
function createPointCloudLayer(source: PointCloudSource, options?: PointCloudLayerOptions): PointCloudLayer;
Create the layer. Add it with map.addLayer(...) once the style is loaded.
source is either a decoded cloud or a { meta, raw } pair, which is
decoded (and validated) here.
| Parameter | Type | Default | Description |
|---|---|---|---|
source | PointCloudSource | required | |
options? | PointCloudLayerOptions | not stated |
Options and types: PointCloudSource, PointCloudLayerOptions.
Example
import "maplibre-gl/dist/maplibre-gl.css";
import { createMap } from "@mapmap/maps";
import { createPointCloudLayer, openCopc } from "@mapmap/points";
const map = createMap({ container: "map", apiKey: "snk_…", center: [-0.1276, 51.5072], zoom: 16 });
const source = await openCopc("https://example.com/scan.copc.laz");
const layer = createPointCloudLayer(source, { colourMode: "rgb", pointSizeM: 0.15 });
map.map.on("load", () => map.map.addLayer(layer));
decodePointCloud
Function
function decodePointCloud(meta: PointCloudMeta, raw: ArrayBuffer): DecodedPointCloud;
Point two typed arrays at a payload, after checking it is the right size.
Throws rather than returning a partial view: a truncated download that silently renders its first 300 000 points is far harder to diagnose than one that refuses.
| Parameter | Type | Default | Description |
|---|---|---|---|
meta | PointCloudMeta | required | |
raw | ArrayBuffer | required |
Options and types: PointCloudMeta.
defaultHeightRange
Function
function defaultHeightRange(box: LocalBox): MetreRange;
The height range a cloud gets when the caller does not steer it: its own vertical extent, widened if that extent is flatter than the ramp can use.
| Parameter | Type | Default | Description |
|---|---|---|---|
box | LocalBox | required |
Options and types: LocalBox.
deviceMemoryGb
Function
function deviceMemoryGb(): number | undefined;
Read navigator.deviceMemory without assuming a DOM.
encodePointCloud
Function
function encodePointCloud(input: {
anchor: [number, number];
xyz: Float64Array | Float32Array | number[];
rgb: Uint8Array | number[];
classes?: Uint8Array | number[];
quant?: number;
sensor?: string;
}): {
buffer: ArrayBuffer;
meta: PointCloudMeta;
};
Encode points into the wire format, for producers written in TypeScript.
xyz is metres in the local frame; rgb is 0–255; classes is one byte
per point. Returns the buffer plus the origin, quant and bboxLocal
to write into the sidecar, so the two cannot disagree.
Most production baking happens offline in Python or Rust, this exists so a small cloud can be built in the browser, and so the decoder has an encoder to be tested against.
| Parameter | Type | Default | Description |
|---|---|---|---|
input | { anchor: [number, number]; xyz: Float64Array | Float32Array | number[]; rgb: Uint8Array | number[]; classes?: Uint8Array | number[]; quant?: number; sensor?: string; } | required |
eyePosition
Function
function eyePosition(view: CameraView, centre: LocalCentre): EyePosition;
Where the eye is, in the cloud's local frame.
The camera sits distanceM behind the map centre, opposite the bearing,
lifted by the pitch, and, when terrain is on, raised by the terrain
height at the centre (see 1 in the module docs).
| Parameter | Type | Default | Description |
|---|---|---|---|
view | CameraView | required | |
centre | LocalCentre | required |
Options and types: CameraView, LocalCentre.
formatClearance
Function
function formatClearance(c: MeasuredClearance): ClearanceText;
Format a measured clearance as "4.62 m measured (safe bound 4.48 m, surveyed Jan 2026)".
The bound is not optional, not appended by a caller and not dropped when the string is short on space. If a surface has no room for the full string it needs a shorter design, not a shorter truth.
A non-structure overhead class is named in the text, because a figure
limited by vegetation or by a wire behaves differently from one limited
by concrete: foliage is seasonal and compressible, and a wire is a thin
target measured from few returns. Both are still real obstructions, so
the figure stands; the reader is simply told what it is a figure about.
| Parameter | Type | Default | Description |
|---|---|---|---|
c | MeasuredClearance | required |
Options and types: MeasuredClearance.
Throws: if any figure is non-finite, if a bound is negative, or if the safe bound exceeds the measured value. Each of those means the arithmetic upstream is wrong, and every one of them would otherwise render as a confident, plausible label.
formatLateral
Function
function formatLateral(l: MeasuredLateral): ClearanceText;
Format a measured lateral clearance as "2.90 m clear width measured (safe bound 2.71 m, surveyed Jan 2026)".
A separate function rather than a flag on formatClearance,
because it takes a separate type. There is no argument anybody can get
wrong: a MeasuredLateral has no headroom to caption, and a
MeasuredClearance has no clear width.
The wording says "clear width" every time, including for a structure, where the vertical formatter stays silent about the class. A figure with no unit noun beside it defaults in a reader's head to headroom, because that is what a clearance report is mostly about.
| Parameter | Type | Default | Description |
|---|---|---|---|
l | MeasuredLateral | required |
Options and types: MeasuredLateral.
Throws: on the same three arithmetic impossibilities.
isAbortError
Function
function isAbortError(error: unknown): boolean;
True for the DOM AbortError and for our own aborted-code error.
| Parameter | Type | Default | Description |
|---|---|---|---|
error | unknown | required |
isStreamingSource
Function
function isStreamingSource(source: unknown): source is StreamingSource;
Narrow a layer source to a streamed one.
| Parameter | Type | Default | Description |
|---|---|---|---|
source | unknown | required |
localOffset
Function
function localOffset(anchor: [number, number], lngLat: [number, number]): {
x: number;
z: number;
};
Local offset in metres (x east, z south) of a lng/lat from an anchor.
| Parameter | Type | Default | Description |
|---|---|---|---|
anchor | [number, number] | required | |
lngLat | [number, number] | required |
metresPerPixel
Function
function metresPerPixel(lat: number, zoom: number): number;
Ground metres one CSS pixel covers at a given latitude and zoom.
512 is MapLibre's tile size in CSS pixels, so this is a CSS-pixel scale
and multiplying it by a device-pixel height is wrong by the device pixel
ratio. camera.test.ts pins it against MapLibre's own
transform.cameraToCenterDistance, which is likewise in CSS pixels.
| Parameter | Type | Default | Description |
|---|---|---|---|
lat | number | required | |
zoom | number | required |
openCopc
Function
function openCopc(source: string | RangeGetter, options?: CopcOpenOptions): Promise<CopcSource>;
Read a COPC's header and hierarchy root. Fetches no points.
| Parameter | Type | Default | Description |
|---|---|---|---|
source | string | RangeGetter | required | a URL, or a RangeGetter to read bytes yourself. |
options? | CopcOpenOptions | not stated |
Options and types: RangeGetter, CopcOpenOptions.
Example
import { openCopc } from "@mapmap/points";
const source = await openCopc("https://example.com/scan.copc.laz", {
onWarn: (message) => console.warn(message),
});
console.log(source.meta.totalPoints, source.channels);
source.dispose(); // a source owns a worker pool: always dispose of it
payloadBytes
Function
function payloadBytes(count: number): number;
Byte length a payload with count points must have.
| Parameter | Type | Default | Description |
|---|---|---|---|
count | number | required |
pointBudgetForDevice
Function
function pointBudgetForDevice(deviceMemoryGb?: number, override?: number): number;
Resident point budget for a device.
At 10 bytes a point the three tiers are 120 / 60 / 30 MB of GPU buffer, which leaves ample room beside a MapLibre basemap with terrain on the weakest device in each tier.
navigator.deviceMemory is Chromium-only, so undefined takes the
conservative tier rather than the optimistic one. An app that knows
better passes override, and an explicit override always wins, including
over a device that reports plenty of memory.
| Parameter | Type | Default | Description |
|---|---|---|---|
deviceMemoryGb? | number | not stated | |
override? | number | not stated |
pointSizeScale
Function
function pointSizeScale(worldSizeM: number, bufferHeightPx: number, fovDeg?: number): number;
Device pixels a point of worldSizeM metres subtends at one metre.
The shader divides this by the distance to the point, which is what makes points shrink with depth like real surfaces instead of staying a fixed screen size and turning distant geometry into a solid slab.
| Parameter | Type | Default | Description |
|---|---|---|---|
worldSizeM | number | required | |
bufferHeightPx | number | required | |
fovDeg? | number | not stated |
reportToGeoJson
Function
function reportToGeoJson(report: RouteClearanceReport): ClearanceGeoJson;
Turn a route clearance report into map geometry.
The collection contains, in order:
- the route ribbon, one LineString decoded from
route.geometry_polyline6, statused by the height verdict. Omitted when the report carries no drawable shape; - coverage gaps,
not_surveyedtheninsufficient_data, for the height axis and then the width axis; - advisory runs;
- measurement points, height axis then width axis.
The width axis contributes nothing when it reports not_assessed: there
is no lateral geometry to draw, and the reason belongs in the panel
beside the report rather than on the map as an unplaced label.
| Parameter | Type | Default | Description |
|---|---|---|---|
report | RouteClearanceReport | required |
Options and types: RouteClearanceReport.
Throws: if a coverage or advisory section carries fewer than two positions, or if the encoded route or section geometry is malformed.
resolveClasses
Function
function resolveClasses(classes: readonly PointClass[]): ResolvedClasses;
Turn a caller's class table into the flat arrays the shader wants.
Throws on a table the shader cannot represent, duplicate class bytes, a
byte outside 0–255, or more classes than MAX_CLASSES. These are
all producer mistakes that would otherwise render as points quietly
taking a neighbour's colour.
| Parameter | Type | Default | Description |
|---|---|---|---|
classes | readonly PointClass[] | required |
Options and types: PointClass.
stickyElevation
Function
function stickyElevation(sampled: number | null | undefined, remembered: number | null): number | null;
Carry the last known terrain elevation across frames the DEM has not answered for yet.
map.queryTerrainElevation returns null until the DEM tile covering
the point has loaded, on the first frames after terrain is switched on,
and again after a pan into un-cached tiles. Defaulting those frames to 0
puts the eye at sea level, so on a hill the point sizes and the fog
distance jump the moment the tile arrives: a first-paint pop with no
visible cause. Holding the previous value is wrong by however far the
camera has moved since, which is a metre or two, instead of by the height
of the hill.
Returns null only when nothing has ever been sampled, there is no real
elevation to hold yet, and the caller falls back to 0 for those frames
alone. Once a real value has been seen this never returns to null, so
the eye never snaps back to sea level mid-session.
Pure, so the layer's memory is one variable it passes in and stores back.
| Parameter | Type | Default | Description |
|---|---|---|---|
sampled | number | null | undefined | required | |
remembered | number | null | required |
validateMeta
Function
function validateMeta(meta: PointCloudMeta): string[];
Validate a sidecar. Returns the problems; an empty array means usable.
Separate from decodePointCloud so a build pipeline can check a
sidecar it has just written without holding the (large) binary.
| Parameter | Type | Default | Description |
|---|---|---|---|
meta | PointCloudMeta | required |
Options and types: PointCloudMeta.
visiblePointCount
Function
function visiblePointCount(classes: ResolvedClasses, classCounts: Record<string, number> | undefined): number | null;
How many points the currently visible classes account for, or null if
that cannot be known from the sidecar.
null rather than a guess, and rather than a count of our own: the only
other way to answer is to walk the class byte of every point in the
payload, which is a multi-megabyte scan on the main thread for a number
a producer could have written into classCounts at bake time. A readout
that shows nothing is honest; one that stalls a viewer for a second per
toggle, or quietly reports the wrong figure, is not.
A visible class the producer did not count contributes nothing, so a
partial classCounts under-reports rather than throwing. Non-finite and
negative counts are ignored for the same reason.
| Parameter | Type | Default | Description |
|---|---|---|---|
classes | ResolvedClasses | required | |
classCounts | Record<string, number> | undefined | required |
Options and types: ResolvedClasses.
Interfaces (@mapmap/points)
AdaptiveBudget, CameraView, ChangeEvent, ChangeReport, ClearanceEnforcement, ClearanceFieldSidecar, ClearanceGeoJson, CopcCloudMeta, CopcDataStats, CopcOpenOptions, CopcSource, DatumBlock, DecodedPointCloud, EpochRegistration, EyePosition, FogRange, LateralPoint, LimitingPoint, LocalBox, LocalCentre, MeasuredClearance, MeasuredLateral, MetaClassEntry, MetreRange, ObservedClass, OsmDraftEdit, PointClass, PointCloudLayer, PointCloudLayerOptions, PointCloudMeta, ResolvedClasses, RouteClearanceReport, RouteSection, StreamCloudMeta, StreamingSource, StreamStats, TagAuditReport, TagComparison, VehicleQuery
AdaptiveBudget
Interface
interface AdaptiveBudget
Turn the adaptive-budget option on, and bound it.
See PointCloudLayerOptions.adaptiveBudget. Off by default: a library
must not govern a caller's frame budget silently. true is a safe,
PROTECTIVE default, the budget may shrink to a floor to hold the frame rate
but never grows above where it started (allowGrow: false), so turning it on
can only ever make a struggling machine smoother, never fetch more than the
app already asked for. The object form is the real control: a floor, a hard
ceiling and an optional target frame rate. GROW is enabled only when the
ceiling is above the starting budget, that is the one gate that keeps a phone
(which the demo hands max === its tier budget) protective-only while a
desktop (max well above its start) may earn more detail.
| Property | Type | Default | Description |
|---|---|---|---|
min | number | required | Floor. The budget never shrinks below this. |
max | number | required | Hard ceiling. The budget never grows above this, whatever the frame rate. |
targetFps? | number | DEFAULT_TARGET_FPS (~50) | Frame rate to hold. Default DEFAULT_TARGET_FPS (~50). |
CameraView
Interface
interface CameraView
The parts of a MapLibre map this module reads.
| Property | Type | Default | Description |
|---|---|---|---|
centre | { lng: number; lat: number; } | required | Map centre in degrees. |
zoom | number | required | |
bearing | number | required | Degrees, clockwise from north. |
pitch | number | required | Degrees from vertical. |
fovDeg? | number | DEFAULT_FOV_DEG | Vertical field of view in degrees. Defaults to DEFAULT_FOV_DEG. |
bufferHeightPx | number | required | Drawing-buffer height in DEVICE pixels, not the CSS height. |
cssHeightPx? | number | bufferHeightPx, which is right on a 1x display and is what every caller before 0.3.0 effectively passed | Canvas height in CSS pixels, for the eye distance. Two heights, and they are not interchangeable. pointSizeScale wants DEVICE pixels, because gl_PointSize is in device pixels. eyePosition wants CSS pixels, because the metres-per-pixel it multiplies by is MapLibre's own 512-CSS-pixel tile scale. Passing the device height to both is the trap, and it is silent: it puts the eye at exactly devicePixelRatio times its true distance, which on a 2x display doubles every fog depth and halves every point's screen size. Defaults to bufferHeightPx, which is right on a 1x display and is what every caller before 0.3.0 effectively passed. |
centreElevationM? | number | not stated | Terrain elevation at the centre in metres, or 0 when terrain is off. |
ChangeEvent
Interface
interface ChangeEvent
One clustered difference between two epochs.
| Property | Type | Default | Description |
|---|---|---|---|
kind | ChangeKind | required | |
location | { lng: number; lat: number; } | required | |
footprint_m2 | number | required | |
dz_m | number | null | required | Height change, metres, or null where the change is not a height. |
threshold_m | number | required | The threshold actually applied: both cells' sigmas combined with the registration residual. Reported so a reader can tell a real change from one the alignment could not have resolved. |
overhead_class | OverheadClass | required | |
view_url | string | required |
ChangeReport
Interface
interface ChangeReport
A whole-corridor comparison between two epochs.
| Property | Type | Default | Description |
|---|---|---|---|
registration | EpochRegistration | required | |
events | ChangeEvent[] | required | |
compared_fraction | number | required | Fraction of the corridor both epochs actually covered. |
explanation | string | required |
ClearanceEnforcement
Interface
interface ClearanceEnforcement
The enforcement block, present on every response including, especially, the clear one, because the clear answer is the one that reads as a certificate.
It mirrors the tunnel enforcement block the routing API already ships on
every truck route, and for the same reason: basis and caveat are
constants defined once in the sn-clearance crate, so the gateway, the
MCP tools and the docs cannot describe the same guarantee three different
ways. This SDK deliberately does not define its own copy of the
caveat text. A second definition is a second thing to drift; the literal
types below pin the fields that must not vary, and the prose arrives
from the one place that owns it.
Asymmetry with the Rust side, stated so nobody assumes otherwise.
There, route_certified is a type with exactly one inhabitant and
basis is a literal tag, so true is not writable and deserialising a
report that claims otherwise fails at the boundary. Here the literal
types below give the same guarantee at compile time and none at all at
runtime: TypeScript types are erased, so a JSON document parsed into
one of these can carry route_certified: true and nothing will notice.
Anything consuming an enforcement block from the wire, rather than from
code that typechecked, has to check it itself.
| Property | Type | Default | Description |
|---|---|---|---|
basis | "surveyed_pointcloud_within_survey_difference" | required | |
route_certified | false | required | Always false, and typed as the literal so it cannot be written otherwise in code. A clearance report is a measurement with a bound, never a certificate about a route. |
vertical_datum | string | required | The vertical frame the figures live in, spelled out for a reader. |
caveat | string | required | What this answer does and does not prove, in one paragraph. |
ClearanceFieldSidecar
Interface
interface ClearanceFieldSidecar
The .clr.json sidecar written beside the binary field.
sources exists for staleness: the dataset manifest holds the same
hashes, and a field baked from a source that has since been republished
is served with stale: true and can never carry a pass.
| Property | Type | Default | Description |
|---|---|---|---|
schema_version | 1 | required | |
dataset | string | required | The dataset id, as the point-cloud manifest spells it. |
datum | DatumBlock | required | |
cell_size_m | number | required | |
grid | { min_e: number; min_n: number; max_e: number; max_n: number; cols: number; rows: number; } | required | |
survey | { captured_from: string; captured_to: string; sensor: string; trajectory_sigma_source: "per_epoch_sd" | "declared_constant"; sigma_rel_georef_m: number; passes: "per_point" | "unavailable"; } | required | |
classes | { source: "standard_las" | "custom_dimension" | "none"; dimension?: string; map_file?: string; } | required | Where the class prior came from. custom_dimension is the San Jose case, where semantics live in a Type extra-byte dimension and the standard LAS classification is all zeros. The standard ASPRS table must never be applied to those codes; map_file carries the provenance of the decode that was used instead. |
sources | { file: string; sha256: string; }[] | required | Content hashes of the source tiles, for staleness detection. |
footprint? | Polygon | not stated | WGS84 survey footprint, where the bake emitted one. |
bake | { tool_version: string; parameters: Record<string, number | string | boolean>; qa: { cells_total: number; cells_road: number; cells_overhead: number; road_z_mad_m: number; synthetic_checks_passed: boolean; }; } | required |
ClearanceGeoJson
Interface
interface ClearanceGeoJson
What reportToGeoJson returns: a FeatureCollection, narrowed so
a consumer gets the property names checked instead of any.
Assignable to GeoJSON's own FeatureCollection, which invariants.ts asserts
at compile time, so anything expecting the standard type still takes it.
| Property | Type | Default | Description |
|---|---|---|---|
type | "FeatureCollection" | required | |
features | ClearanceFeature[] | required |
CopcCloudMeta
Interface
interface CopcCloudMeta extends StreamCloudMeta
Everything a COPC file tells us about itself, in PointCloudMeta shape.
Extends StreamCloudMeta.
| Property | Type | Default | Description |
|---|---|---|---|
streaming | true | required | Always true. How a consumer tells a streamed source from a baked one. |
totalPoints | number | required | Points in the file, from the LAS header. count mirrors this. |
maxLevel | number | required | Deepest level present in the hierarchy loaded so far. Live. |
spacingM | number | required | Root point spacing, metres. The geometric error at level 0. |
pdrf | Pdrf | required | LAS point data record format: 6, 7 or 8. |
hasRgb | boolean | required | True when the PDRF carries RGB (7 or 8). |
hasNir | boolean | required | True when the PDRF carries NIR (8). |
wkt? | string | not stated | The file's CRS, verbatim from the LASF_Projection WKT VLR. |
projection | "geographic" | "webMercator" | "transverseMercator" | "lambertCC" | "caller" | required | How the SDK is reprojecting: which family, or "caller" for a hook. |
CopcDataStats
Interface
interface CopcDataStats
Counters for getStats() and for a proof run's request ledger.
| Property | Type | Default | Description |
|---|---|---|---|
requests | number | required | Range requests actually issued, node data and hierarchy pages alike. |
nodeRequests | number | required | |
pageRequests | number | required | |
bytes | number | required | Compressed bytes pulled over the wire. |
wastedBytes | number | required | Bytes inside a coalesced span that nobody asked for. |
nodesDecoded | number | required | |
nodesFailed | number | required | |
cacheHits | number | required | Nodes served from the decoded LRU with no request at all. |
decodedCacheBytes | number | required | |
compressedCacheBytes | number | required | |
requestsInFlight | number | required | Coalesced requests on the wire right now, and the most there have ever been. The high-water mark is the one number that says whether admission is actually reaching its cap, the failure this pair was added to make visible was a 10-slot budget running at an effective two, which no byte or request total shows. |
maxObservedRequestsInFlight | number | required | |
pendingDecodeBytes | number | required | Compressed bytes fetched and waiting for a decode worker. |
maxObservedPendingDecodeBytes | number | required | Its high-water mark, against maxPendingDecodeBytes. |
CopcOpenOptions
Interface
interface CopcOpenOptions
| Property | Type | Default | Description |
|---|---|---|---|
anchor? | [number, number] | the info VLR's cube centre, reprojected | Override the anchor. Default: the info VLR's cube centre, reprojected. |
crs? | CrsHook | not stated | Reproject from the file's CRS yourself. Supply this when the CRS is outside the built-in set (geographic, Web Mercator, Transverse Mercator, Lambert Conformal Conic 2SP), or when you need a datum shift the SDK deliberately does not do. proj4 fits this signature directly: ts const from = proj4("EPSG:2056"); openCopc(url, { crs: { toWgs84: (x, y) => from.inverse([x, y]) } }); One consequence worth knowing: a hook is a function, so it cannot cross into a Web Worker. Nodes whose affine fit fails its residual gate keep the fitted affine instead of falling back to the exact path, and the SDK warns once naming the residual. This affects the coarsest two or three levels only, and the error shrinks fourfold per level. |
heightOffsetM? | number | not stated | Metres added to every Z. For a geoid or datum offset the file omits. |
channel? | CopcChannel | "rgb" if the PDRF has it, else "intensity" | Default: "rgb" if the PDRF has it, else "intensity". |
workers? | number | clamp(hardwareConcurrency - 2, 2, 4) | Worker count. Default: clamp(hardwareConcurrency - 2, 2, 4). |
createWorker? | (index: number) => PoolWorker | The default is new Worker(new URL("./worker.js", import.meta.url), { type: "module" }), which every bundler this SDK supports understands | Build the decode workers yourself. The default is new Worker(new URL("./worker.js", import.meta.url), { type: "module" }), which every bundler this SDK supports understands. Pass your own when yours does not, or to run the pool outside a browser. |
lazPerfWasmUrl? | string | Emscripten resolves it next to its own glue by default, which is right from node_modules and wrong from a bundle: a build that emits the decode worker as an asset leaves the glue looking for a sibling .wasm that was never copied there, and the whole decode path then fails on every node with a module-resolution error | URL of the laz-perf .wasm. Emscripten resolves it next to its own glue by default, which is right from node_modules and wrong from a bundle: a build that emits the decode worker as an asset leaves the glue looking for a sibling .wasm that was never copied there, and the whole decode path then fails on every node with a module-resolution error. Serve the file (it is laz-perf/lib/worker/laz-perf.wasm, 214 KB, unmodified) and name it here. |
cacheBytes? | number | derived from the point budget | Decoded-cache byte budget. Default: derived from the point budget. |
compressedCacheBytes? | number | not stated | Compressed-node cache, which makes a channel switch a re-decode. |
coalesceGapBytes? | number | 32 KB (§16) | Largest gap two wanted byte ranges may have and still be joined into one request. Default 32 KB (§16). Raise it behind a fast link, lower it where bandwidth costs more than latency. |
coalesceMaxBytes? | number | 4 MB (§16), which caps how long a single request can monopolise a connection | Largest span one coalesced request may cover. Default 4 MB (§16), which caps how long a single request can monopolise a connection. A single range wider than this is still issued whole: a node is one complete LAZ chunk and a prefix of it decodes to nothing. |
requestInit? | RequestInit | not stated | Passed to every fetch: auth headers, credentials, a cache mode. |
requestStallMs? | number | 15 s (REQUEST_STALL_MS) | How long one attempt may deliver nothing at all before it is abandoned and retried. Default 15 s (REQUEST_STALL_MS). A SILENCE deadline, re-armed on every chunk of body, so a slow download is never killed for being slow. It exists because fetch has none of its own: a request black-holed by a cellular handover never rejects, and a promise that never settles holds a scheduler slot for the life of the page. Only ever set this to 0 to disable it in a test that means to hold a request open. |
maxConcurrent? | number | 6 | Total concurrent range requests to the file's origin. Default 6. Six is the browsers' per-origin connection cap for HTTP/1.1, and plain S3 is HTTP/1.1: asking for more just queues the surplus inside the browser, where the SDK can neither see it nor reprioritise it (design §8.3). Behind an HTTP/2 origin, a CDN, or a proxy that terminates h2, that cap is lifted, one connection multiplexes many streams, and raising this is safe and worthwhile: on a dense city COPC a refine to street level is round-trip-bound, and more concurrency fills it faster. This is the one lever a library cannot pick a good default for, because it is a fact about the caller's transport, not about the file. So the default stays at the conservative HTTP/1.1 figure and an app that knows its origin speaks h2 opts in. Two of the total stay reserved for hierarchy pages however high this goes (RESERVED_PAGE_SLOTS), so the node lane and the COPC data source's own in-flight cap both track maxConcurrent - 2. Values below 3 leave the node lane with no room and are rejected. Ignored when you supply your own getRange. |
getRange? | RangeGetter | not stated | Replace the transport entirely. This is the seam the tests use. |
getPageRange? | RangeGetter | getRange | Hierarchy pages, if they want their own lane. Defaults to getRange. |
source? | string | "COPC <url>" | What meta.source says. Default: "COPC <url>". Put the credit here. |
sensor? | string | not stated | Override meta.sensor, which otherwise comes from the LAS header. |
onWarn? | (message: string) => void | console.warn, once per distinct message | Where warnings go. Default: console.warn, once per distinct message. |
signal? | AbortSignal | not stated | Aborts the two opening reads. |
CopcSource
Interface
interface CopcSource extends StreamingSource
A streamed COPC, as createPointCloudLayer consumes it.
Extends StreamingSource.
| Property | Type | Default | Description |
|---|---|---|---|
readonly kind | "copc" | required | |
readonly url | string | required | |
readonly meta | CopcCloudMeta | required | |
readonly channels | readonly CopcChannel[] | required | What the file can be coloured by, given its PDRF. |
readonly channel | CopcChannel | required | Which of them is in the colour bytes right now. |
readonly stats | CopcDataStats | required | Request and cache counters, for a readout or a proof run. |
readonly header | LasHeader | required | The parsed LAS public header, for anything this meta does not carry. |
readonly info | CopcInfo | required | The COPC info VLR: cube, spacing, root page. |
readonly descriptor | ProjectionDescriptor | required | The §6.2 descriptor, including the CRS's own name. |
Methods
setChannel(channel: CopcChannel): Promise<void>: Switch it. Re-decodes; no new range requests with the cache warm.dispose(): void: Release workers, abort in-flight requests, drop caches.
DatumBlock
Interface
interface DatumBlock
The mandatory datum block. A bake without one must not exist.
crs.wkt_source records where the CRS came from, and the ordering is a
quality ranking rather than a preference: a CRS read from the data's own
WKT VLR is a fact, one from a sidecar is a claim, and one declared by an
operator is a hope. It is never taken from a filename. The Montreal
delivery that was MTM and looked like UTM is why.
| Property | Type | Default | Description |
|---|---|---|---|
crs | { epsg: number; wkt_source: "las_vlr" | "sidecar" | "declared"; } | required | CRS as read from the data (WKT VLR), never from a filename. |
vertical_basis | VerticalBasis | required | |
within_survey_differences | true | required | The load-bearing invariant, restated where a reader will see it: headroom values are WITHIN-SURVEY differences, so the absolute basis above never enters the clearance arithmetic. Typed as the literal true because there is no other permissible value: an artefact whose numbers are not within-survey differences is not a clearance field. |
DecodedPointCloud
Interface
interface DecodedPointCloud
Two zero-copy views over one payload, ready to hand to the GPU.
| Property | Type | Default | Description |
|---|---|---|---|
positions | Uint16Array | required | count * 3 integers; multiply by quant and add origin for metres. |
colours | Uint8Array | required | count * 4 bytes: R, G, B, class. |
meta | PointCloudMeta | required |
EpochRegistration
Interface
interface EpochRegistration
The alignment applied to one epoch before it can be compared to another.
| Property | Type | Default | Description |
|---|---|---|---|
from_epoch | string | required | |
to_epoch | string | required | |
transform | { bearing_deg: number; t_e: number; t_n: number; t_z: number; } | required | Four degrees of freedom: a bearing about the vertical plus a translation, applied to the from epoch. The bearing is grid north. |
residual | { median_m: number; p90_m: number; method: "ncc_icp_4dof"; } | required | |
claim | "fit_not_survey" | required | Always fit_not_survey. An alignment is not a measurement. |
EyePosition
Interface
interface EyePosition
Eye position in the cloud's local east/up/south frame, in metres.
| Property | Type | Default | Description |
|---|---|---|---|
x | number | required | |
y | number | required | |
z | number | required | |
distanceM | number | required | Ground distance from eye to map centre, metres. |
FogRange
Interface
interface FogRange
Fog distances in metres from the eye.
| Property | Type | Default | Description |
|---|---|---|---|
nearM | number | required | Where fog starts. |
farM | number | required | Where fog is total. |
LateralPoint
Interface
interface LateralPoint
A place on the route where a lateral measurement constrains the answer.
The measurement is on lateral, not clearance. Two field names for
two quantities: code that reaches for .clearance on one of these does
not compile, which is the point.
| Property | Type | Default | Description |
|---|---|---|---|
location | { lng: number; lat: number; } | required | |
route_distance_m | number | required | |
lateral | MeasuredLateral | required | |
dataset | string | required | |
view_url | string | required |
LimitingPoint
Interface
interface LimitingPoint
A place on the route where an overhead measurement constrains the answer.
| Property | Type | Default | Description |
|---|---|---|---|
location | { lng: number; lat: number; } | required | |
route_distance_m | number | required | |
clearance | MeasuredClearance | required | |
dataset | string | required | Which survey the figure came from. |
view_url | string | required | Deep link into the viewer, posed at this exact spot. |
LocalBox
Interface
interface LocalBox
Local bounding box in metres, in the cloud's own east/up/south frame.
| Property | Type | Default | Description |
|---|---|---|---|
x | [number, number] | required | East, metres from the anchor. |
y | [number, number] | required | Up, metres. |
z | [number, number] | required | South, metres from the anchor. |
LocalCentre
Interface
interface LocalCentre
Where the centre of the map sits in the cloud's local frame.
| Property | Type | Default | Description |
|---|---|---|---|
x | number | required | East, metres. |
z | number | required | South, metres. |
MeasuredClearance
Interface
interface MeasuredClearance
One measured clearance. The uncertainty is not optional, and that is the point of the type.
A headroom figure without an error bound is the number somebody drives a
lorry at a bridge on. Making sigma_m and sampling_gap_m required
means the bound cannot be dropped in transit, and pairing that with a
single formatter means it cannot be dropped in display either.
The two bounds are different animals and are kept apart deliberately:
sigma_mis the combined fit residual of the road estimate, the overhead estimate and the differential georeferencing between them. It is a spread.sampling_gap_mis a one-sided bias bound, not a sigma. A sample spacing of g on a structure's underside means the true low point can hang up to about g below the lowest sample that was actually caught. No amount of averaging recovers it, so it is subtracted rather than combined in quadrature.
safe_headroom_m is the verdict input: headroom_m less a multiple of
sigma_m less sampling_gap_m. Both figures are reported, because a
caller is entitled to see what was measured as well as what it is safe
to conclude.
| Property | Type | Default | Description |
|---|---|---|---|
headroom_m | number | required | overhead_z - road_z, a within-survey difference. |
sigma_m | number | required | |
sampling_gap_m | number | required | One-sided bias bound from the sample spacing on the overhead surface. |
safe_headroom_m | number | required | headroom_m - K*sigma_m - sampling_gap_m; the verdict input. |
overhead_class | OverheadClass | required | |
support_m2 | number | required | Connected-component area of the overhead cluster, the noise gate. |
surveyed_on | string | required | ISO date the constraining geometry was captured. |
MeasuredLateral
Interface
interface MeasuredLateral
One measured lateral clearance. A separate type from
MeasuredClearance, with no headroom field anywhere on it.
The obvious alternative was one measurement type with an axis
discriminant, and it is worse: a discriminant is something a reader has
to remember to check, and the whole discipline of this module is that
dangerous states are unrepresentable rather than merely detectable. A
clear width of 2.90 m sitting in a field called headroom_m is a
mislabelled safety figure one careless line away from a bridge strike,
so the field does not exist to be misread. The Rust crate makes the same
choice and asserts that a serialised width answer contains the string
"headroom" nowhere; invariants.ts asserts the same against the type,
and format.test.ts and geojson.test.ts against the values, so the
guarantee holds for every width answer rather than for the one a test
happened to build.
The uncertainty is derived laterally rather than borrowed from the vertical, because it comes from different geometry:
sigma_missqrt(2) * sigma_rel_georef_m. Both corridor edges are measured, and a width is the difference between them, so the differential georeferencing term enters twice.sampling_gap_mis2 * cell_size, one cell of ambiguity at each edge. It is a one-sided bias bound like its vertical counterpart: the true edge can sit up to a cell inboard of the last sample that caught it, and no averaging recovers that.
| Property | Type | Default | Description |
|---|---|---|---|
clear_width_m | number | required | Free width between the surveyed corridor edges, metres. |
sigma_m | number | required | sqrt(2) * sigma_rel_georef_m: both edges carry the term. |
sampling_gap_m | number | required | 2 * cell_size: one cell of one-sided bias at each edge. |
safe_clear_width_m | number | required | clear_width_m - K*sigma_m - sampling_gap_m; the verdict input. |
limited_by | ObstructionClass | required | What the narrower edge was: a wall, a hedge, a pole. |
support_m2 | number | required | Connected-component area of the limiting cluster, the noise gate. |
surveyed_on | string | required | ISO date the constraining geometry was captured. |
MetaClassEntry
Interface
interface MetaClassEntry
One class as an array-shaped sidecar writes it.
Bakers that emit a list rather than a map tend to spell the field byte
and, being written in Python or against a US style guide, color. Both
spellings are accepted; neither is preferred, and colour wins if a
producer somehow writes both.
| Property | Type | Default | Description |
|---|---|---|---|
byte | number | required | The class byte written in the alpha channel. Integer 0–255. |
label | string | required | Label for your own UI. |
colour? | string | [number, number, number] | not stated | Optional colour, #rgb, #rrggbb or [r, g, b] in 0–1. |
color? | string | [number, number, number] | not stated | US spelling of MetaClassEntry.colour, accepted verbatim. |
MetreRange
Interface
interface MetreRange
A closed range in metres.
| Property | Type | Default | Description |
|---|---|---|---|
minM | number | required | |
maxM | number | required |
ObservedClass
Interface
interface ObservedClass
One class byte and how many resident points carry it.
| Property | Type | Default | Description |
|---|---|---|---|
value | number | required | The classification byte, unmasked, exactly as the producer wrote it. |
count | number | required | Points of this class in the resident set, right now. |
OsmDraftEdit
Interface
interface OsmDraftEdit
A proposed OSM edit, in draft.
set is typed to the single tag this product is ever allowed to draft.
Widening it is not a refactor: maxheight is a legal claim and we do not
hold the evidence to make one.
| Property | Type | Default | Description |
|---|---|---|---|
element | "way" | "node" | required | |
id | number | required | |
set | { "maxheight:physical": string; } | required | The only tag this product ever drafts. |
provenance | { survey: string; surveyed_on: string; sigma_m: number; } | required | |
status | "draft" | required | Always draft. A human reviews and submits; nothing auto-uploads. |
PointClass
Interface
interface PointClass
One entry in the class table.
| Property | Type | Default | Description |
|---|---|---|---|
value | number | required | The byte written in the alpha channel for points of this class. |
label | string | required | Label for your own UI. The SDK renders no text. |
colour? | string | [number, number, number] | the palette entry for this class's position in the table | Colour in class-colour mode, as #rrggbb or [r, g, b] in 0–1. Defaults to the palette entry for this class's position in the table. |
visible? | boolean | true | Whether it starts visible. Defaults to true. |
PointCloudLayer
Interface
interface PointCloudLayer extends CustomLayerInterface
Extends CustomLayerInterface.
| Property | Type | Default | Description |
|---|---|---|---|
readonly meta | PointCloudMeta | required |
Methods
setClassVisible(value: number, visible: boolean): void: Show or hide one class, by its byte value.setAllClassesVisible(visible: boolean): void: Show or hide every class at once.setColourMode(mode: ColourMode): voidsetPointSize(metres: number, maxPx?: number): void: World size in metres;maxPxoptionally lifts the screen-size cap.setHeightRange(minM: number, maxM: number): void: Steer the"height"colour ramp to a band of local metres, live. The bounds are sorted if they arrive the wrong way round, and a collapsed range is widened rather than dividing by zero in the shader. Non-finite bounds throw. Takes effect on the next frame whatever the current colour mode.resetHeightRange(): void: Put the ramp back to the cloud's own vertical extent.getHeightRange(): MetreRange: The band the ramp currently spans, for a slider to start from.getFps(): number: Frames per second, averaged over the last second.getClasses(): ResolvedClasses | null: The class table as resolved, for building a legend.visibleis live: it is the array the shader itself reads, so it always reflects the lastPointCloudLayer.setClassVisibleorPointCloudLayer.setAllClassesVisiblecall rather than the state the table declared at construction. A legend can therefore read its checked state off the layer instead of shadowing it.nullwhen the layer was created without aclassestable.getVisiblePointCount(): number | null: How many points are currently drawn, for a stat readout.meta.countwhen the layer has no class table, since nothing is being culled. Otherwise the sum ofmeta.classCountsover the visible classes, ornullwhen the sidecar carries noclassCounts: the only other way to answer is to walk the class byte of every point in the payload on the main thread, which is too expensive to do per toggle for a number the producer could have baked in.getStats(): StreamStats | null: Streaming counters, for a loading readout.nullfor a static payload.residentPoints / totalPointsis what the loading UI shows. It never reaches 1 and it should not pretend to: the honest label is "2.1M of 940M points drawn", not a progress bar to 100 %.observedClasses(): ObservedClass[]: Classes actually seen in the resident set, by descending count. Empty for a static payload, whose class table is declared rather than discovered. For a streamed source this is a sample and a viewer should say so: it describes the nodes decoded so far, not the file. COPC carries no class histogram and LAS 1.4 defines no statistics VLR, so the alternative is not a better number, it is no number at all.setClasses(classes: readonly PointClass[]): void: Replace the class table live. Streamed sources only. This is what makesPointCloudLayer.observedClassesusable rather than merely interesting: discover which bytes the file actually uses, then name and colour them. Throws aboveMAX_CLASSES, and throws for a static payload, whose shader was compiled for exactly the table it was given. The table is read, never mutated, so areadonlyarray such as the exportedASPRS_LAS14_CLASSESis accepted as it stands:cloud.setClasses(ASPRS_LAS14_CLASSES)typechecks under strict TypeScript with no cast.setChannel(channel: string): Promise<void>: Switch the scalar in the colour bytes. Streamed sources only. Re-decodes the resident set, and issues no new range requests while the compressed-node cache still holds the bytes. Resolves once the re-decode is done; the map repaints as nodes arrive, so the scene never blanks.setTargetSse(px: number): void: Change the target screen-space error live, for a quality slider.setPointBudget(points: number): void: Change the resident point budget live.
PointCloudLayerOptions
Interface
interface PointCloudLayerOptions
| Property | Type | Default | Description |
|---|---|---|---|
id? | string | "mapmap-point-cloud" | Layer id. Defaults to "mapmap-point-cloud". |
classes? | readonly PointClass[] | not stated | The classes this cloud declares. Omit to colour by RGB and height only; setColourMode("class") then throws rather than inventing a taxonomy. Read, never mutated, and typed readonly so a frozen table such as ASPRS_LAS14_CLASSES passes without a cast under strict TypeScript. |
colourMode? | ColourMode | "rgb" | Starting colour mode. Defaults to "rgb". |
pointSizeM? | number | 0.15 | World size of one point in metres before perspective scaling. Default 0.15. |
maxPointPx? | number | 10 | Screen-size cap in device pixels. Default 10. Raise it for offline video bakes: at the default, near-field points separate into distinct marbles that a video encoder reads as noise and spends its whole bitrate on. A higher cap lets them overlap into a surface, which encodes cleanly. |
fog? | { colour?: string; nearM?: number; farM?: number; } | not stated | Distance fog. Set colour to your basemap's fog colour so they agree. nearM and farM are metres from the eye, and each defaults on its own to a distance derived from the cloud's horizontal extent (0.6 of the diagonal, floored at 120 m, with farM three times nearM), so { colour } alone still gets extent-scaled distances, and anything you pass explicitly wins. Fixed defaults suit an aerial capture and fog a 340 m street to flat grey the moment the camera pulls back, which presents as a broken renderer rather than as fog. |
heightRange? | { minM: number; maxM: number; } | the cloud's own vertical extent | Height range the "height" ramp spans, in local metres. Defaults to the cloud's own vertical extent; override it when a handful of below-datum returns or one tall roof would otherwise squeeze the interesting band into a slice of the ramp. Steerable later with PointCloudLayer.setHeightRange. |
pointBudget? | number | a navigator.deviceMemory tier: 12M / 6M / 3M points at >= 8 GB / >= 4 GB / below or unknown | Resident point budget. Streamed sources only. Defaults to a navigator.deviceMemory tier: 12M / 6M / 3M points at >= 8 GB / >= 4 GB / below or unknown. deviceMemory is Chromium-only, so an unknown device takes the conservative tier and an app that knows better passes this. |
cacheBytes? | number | not stated | Decoded-cache byte budget. Streamed sources only. |
targetSsePx? | number | 2.0 | Target screen-space error in device pixels. Default 2.0. |
budgetRamp? | boolean | Off by default | Open a large streamed scene through a budget ramp, for a fast first view. Streamed sources only. With this set, a scene whose resolved budget is large (a desktop tier) opens at a smaller budget so the near field sharpens in a second or two, then grows to the full budget once that near field is resident. The destination is unchanged; only the time to a usable near view improves. The policy, the size gate and the start budget, lives in resolveBudgetRamp, so a budget below the gate (every phone and mid tier) ignores the flag and keeps today's single-budget path. Off by default. |
adaptiveBudget? | AdaptiveBudget | boolean | Off by default, a library must not govern a caller's frame budget silently | Adaptive, fps-governed resident budget. Streamed sources only. Off by default, a library must not govern a caller's frame budget silently. With this set, the layer samples the real frame cadence (rAF deltas) and scales the resident budget within the given bounds to hold a target frame rate: a capable machine earns more detail, a weak one settles lower, and both stay smooth. It governs only on steady-state RENDER cost, foreground, camera-still, not-loading, post-settle frames, and moves the budget slowly, in modest multiplicative steps behind a wide deadband, a sustained-signal requirement and a cooldown, so a user never sees points hunting in and out. It converges to a stable budget and then stops. true is a PROTECTIVE default: the budget may shrink to hold the frame rate but never grows above where it started. The object form, { min, max, targetFps? }, is the real control; GROW is enabled only when max is above the starting budget, which is how a phone (handed max === its tier budget) stays protective-only while a desktop may earn more. Composes with budgetRamp: the governor stays dormant until the ramp has grown, then takes over the steady-state budget. See AdaptiveBudget. |
PointCloudMeta
Interface
interface PointCloudMeta
The sidecar that describes a cloud. Written alongside the binary, usually
as <name>.json next to <name>.bin.
Deliberately small: it carries what is needed to draw the cloud, plus
two free-form provenance strings (source, sensor) that the SDK never
renders. Licence terms, survey dates and processing history belong in
your own metadata, not in a rendering payload.
| Property | Type | Default | Description |
|---|---|---|---|
anchor | [number, number] | required | [lng, lat] the local frame is measured from. |
quant | number | required | Metres per position unit. 0.02 (2 cm) is the usual choice. |
origin | [number, number, number] | required | Local-frame offset in metres added after dequantisation. |
count | number | required | Number of points in the binary. |
bboxLocal | LocalBox | required | Local extent, used for the height colour ramp and for framing. |
bboxWgs? | [number, number, number, number] | not stated | [west, south, east, north] in degrees, for fitBounds. |
classes? | MetaClassTable | not stated | The cloud's class table, in either accepted shape: a map of class byte → label, e.g. { "0": "ground", "1": "building" }, or a list of { byte, label, colour } entries. The labels are yours: this format has no opinion about a classification scheme, and does not assume any particular standard's numbering. |
classCounts? | Record<string, number> | not stated | Points per class, if the producer counted them. |
absoluteHeights? | boolean | true | Whether the cloud's y values are absolute metres. Defaults to true. This flag moves the camera, never the points. Nothing in this SDK displaces a cloud: the vertex shader draws each point at position * quant + origin and that is the end of it. What the flag decides is the altitude the eye is reconstructed at, see #678, which is where the field got its proper name. - true (the default): the eye's altitude includes the terrain elevation at the map centre, because the cloud's own heights are absolute and MapLibre's terrain is displaced by absolute metres too. Leave it alone unless you know otherwise. - false: the eye sits at altitude 0. Correct only for a cloud on a flat z = 0 datum with terrain off. Either way this affects perspective point sizing and fog distance only, because those are the two things that need a real eye position. It cannot rescue a payload whose heights are wrong, see PointCloudMeta.terrainRelative. |
terrainRelative? | boolean | not stated | |
source? | string | not stated | Free-form attribution string for the data. Not rendered by the SDK. |
sensor? | string | not stated | What captured this cloud, free-form, e.g. "Ouster OS-1-256-RGB @ 2048x10". Structured enough to put in a viewer's info panel without parsing it back out of PointCloudMeta.source, free-form enough that the SDK never interprets it: it is carried through encode, decode and validation untouched, and nothing is rendered from it. |
ResolvedClasses
Interface
interface ResolvedClasses
A class table normalised for the shader: dense arrays, resolved colours.
| Property | Type | Default | Description |
|---|---|---|---|
values | number[] | required | Class byte for each table row, in table order. |
labels | string[] | required | |
colours | number[] | required | Flat rgb triples in 0–1, values.length * 3 long. |
visible | number[] | required | 1 or 0 per row, and the layer's single source of truth for what is drawn: the shader's uClassOn uniform is this very array, not a copy of it, so what a layer returns from getClasses() is always what it is currently rendering. Steer it through the layer's setClassVisible, which also asks the map to repaint; writing into it directly changes the next frame the map happens to draw, but does not schedule one. |
lookup | Int8Array | required | Row index for a class byte, or -1. Length 256. |
RouteClearanceReport
Interface
interface RouteClearanceReport
The full report for one route and one vehicle.
width is either a verdict of its own or an explicit refusal. Lateral
obstruction distance can only be measured where both corridor edges have
adequate return density, and where they do not the width axis says
not_assessed while the height axis still answers. It does not guess.
| Property | Type | Default | Description |
|---|---|---|---|
basis | "surveyed_geometry_not_signage" | required | |
clearance_enforcement | ClearanceEnforcement | required | |
route | { distance_m: number; geometry_polyline6: string; } | required | |
vehicle | VehicleQuery | required | |
height | RouteVerdict | required | |
width | WidthVerdict | { verdict: "not_assessed"; reason: string; } | required | |
datasets | { dataset: string; survey_dates: string; stale: boolean; }[] | required | |
advisories | { section: RouteSection; note: string; }[] | required | Vegetation age, seasonal compressibility and the like. |
explanation | string | required | One-line honest summary: what was measured, when, with what bound, and what the answer stops short of concluding. |
RouteSection
Interface
interface RouteSection
A contiguous run of route stations, by distance along the route.
The gateway sends the run's shape as polyline6, a six-digit encoded
polyline (latitude first, like route.geometry_polyline6), and for one
release also as the older polyline array of [lng, lat] pairs, which
it has marked deprecated. A report from a gateway older than
polyline6 carries only the array. reportToGeoJson reads
polyline6 when it is present and non-empty and falls back to
polyline otherwise, so both shapes draw the same line.
| Property | Type | Default | Description |
|---|---|---|---|
start_m | number | required | |
end_m | number | required | |
polyline6? | string | not stated | The run's shape, encoded at precision 6, latitude first. |
polyline? | [number, number][] | not stated | The same run as [lng, lat] pairs (GeoJSON order). |
StreamCloudMeta
Interface
interface StreamCloudMeta extends PointCloudMeta
Metadata a streamed source synthesises. Task E fills this from the file.
Extends PointCloudMeta.
| Property | Type | Default | Description |
|---|---|---|---|
streaming | true | required | Always true. How a consumer tells a streamed source from a baked one. |
totalPoints | number | required | Points in the file, from the LAS header. count mirrors it. |
spacingM | number | required | Root point spacing in metres: the geometric error at level 0. |
maxLevel | number | required | Deepest level present in the hierarchy. |
StreamingSource
Interface
interface StreamingSource
A streamed point cloud, as createPointCloudLayer consumes it.
| Property | Type | Default | Description |
|---|---|---|---|
readonly kind | "stream" | "copc" | required | "copc" for a COPC file over HTTP range requests, which is the only container v1 reads. "stream" is the generic spelling, kept because this interface is the concept, an octree plus a way to get its nodes, and EPT (the v2 item) will be a second container behind the same one. Anything the layer does here is true of both. |
readonly meta | StreamCloudMeta | required | |
readonly octree | OctreeView | required | The node store, adapted into local metres. |
readonly data | NodeDataSource | required | The I/O side. |
readonly spacingPerQuant | number | required | 65534 * spacing / (2 * halfsize). One constant for the whole file, and the reason the shader can derive a per-node point size from length(modelMatrix[0].xyz) alone. |
readonly channels? | readonly string[] | not stated | Which scalars this container can put in the colour bytes. A channel is what is in the three colour bytes and changing it means re-decoding the resident set; a colour mode is how the shader paints them and changing it is a uniform write. Keeping the two apart is the split the existing viewer already got right, and it survives here. |
Methods
setChannel?(channel: string): Promise<void>: Switch the channel. Absent when the container has only one.
StreamStats
Interface
interface StreamStats
Counters a loading readout can show without lying.
| Property | Type | Default | Description |
|---|---|---|---|
residentPoints | number | required | Drawn right now. |
residentNodes | number | required | |
selectedNodes | number | required | Including nodes selected but not yet resident. |
totalPoints | number | required | The file's own count, from the LAS header. |
pendingRequests | number | required | |
cacheBytes | number | required | |
cacheBudgetBytes | number | required | |
failedNodes | number | required | |
maxLevelResident | number | required | |
busy | boolean | required | True while anything is outstanding: the repaint condition. |
pointBudget | number | required | The resident point budget in force right now. Constant unless the adaptive governor is on, in which case this is the live, fps-scaled figure, so a "pts" readout can report the budget the scene is actually being held to rather than the number it was created with. |
TagAuditReport
Interface
interface TagAuditReport
The audit over a dataset, or over one route corridor.
| Property | Type | Default | Description |
|---|---|---|---|
basis | "surveyed_geometry_not_signage" | required | |
clearance_enforcement | ClearanceEnforcement | required | |
area | { dataset: string; } | { route_polyline6: string; } | required | |
comparisons | TagComparison[] | required | |
osm_drafts | OsmDraftEdit[] | required | |
explanation | string | required |
TagComparison
Interface
interface TagComparison
One structure's tagged height against its measured clearance.
osm.value_raw is kept verbatim, and osm.value_m is null when the
value could not be parsed. That pairing is the same doctrine the
dimension parser already applies elsewhere in the stack: a tag reading
13'6" normalises cleanly, a tag reading about 4m does not, and the
second one is reported as unparseable rather than turned into a number
somebody might act on.
| Property | Type | Default | Description |
|---|---|---|---|
osm | { element: "way" | "node"; id: number; tag: "maxheight" | "maxheight:physical"; value_raw: string; value_m: number | null; } | required | |
measured | MeasuredClearance | required | |
location | { lng: number; lat: number; } | required | |
finding | "consistent" | "tag_understates" | "tag_overstates" | "untagged_structure" | required | What the comparison found, about the tag: - consistent, within the combined tolerance. - tag_understates, tagged below the measured physical clearance, so the map is over-restrictive and the router is turning away vehicles that would fit. - tag_overstates, tagged above the measured physical clearance. The dangerous direction. For a legal maxheight this is a signage and authority matter, not an edit. - untagged_structure, the survey found a constraining structure the map says nothing about. |
tolerance_m | number | required | The tolerance actually applied, metres. |
view_url | string | required | Deep link into the viewer at this structure. |
VehicleQuery
Interface
interface VehicleQuery
The vehicle the question was asked about.
| Property | Type | Default | Description |
|---|---|---|---|
height_m | number | required | |
width_m? | number | not stated | The width axis is assessed only when a width was given. |
margin_m | number | 0 and is echoed back: we surface both the measured figure and the safe bound rather than baking anybody's compliance policy into the arithmetic | The caller's operating margin, added to the height before comparison. Defaults to 0 and is echoed back: we surface both the measured figure and the safe bound rather than baking anybody's compliance policy into the arithmetic. |
Types (@mapmap/points)
ChangeKind, ClearanceFeature, ClearanceLineProperties, ClearancePointProperties, ClearanceStatus, ClearanceText, ColourMode, CopcChannel, CopcTransportErrorCode, Coverage, IndeterminateReason, LateralPointProperties, MetaClassTable, ObstructionClass, OverheadClass, PointCloudSource, RangeGetter, RouteVerdict, VerticalBasis, WidthVerdict
ChangeKind
Type
type ChangeKind = "appeared" | "vanished" | "moved" | "grew" | "shrank";
What happened to a cluster of cells between two epochs.
ClearanceFeature
Type
type ClearanceFeature = {
type: "Feature";
geometry: LineString;
properties: ClearanceLineProperties;
} | {
type: "Feature";
geometry: Point;
properties: ClearancePointProperties;
} | {
type: "Feature";
geometry: Point;
properties: LateralPointProperties;
};
One feature of a clearance collection.
ClearanceLineProperties
Type
type ClearanceLineProperties = {
kind: "route" | "coverage_gap" | "advisory";
axis: "height" | "width" | null;
status: ClearanceStatus;
start_m: number;
end_m: number;
note: string | null;
};
Properties on the line features: the route ribbon, coverage gaps and advisory runs.
Written as a type alias rather than an interface so it keeps its
implicit index signature and stays assignable to GeoJSON's
GeoJsonProperties. An interface here compiles until the moment someone
passes the collection to a typed GeoJSON API, and then does not.
| Property | Type | Default | Description |
|---|---|---|---|
kind | "route" | "coverage_gap" | "advisory" | required | |
axis | "height" | "width" | null | required | The axis this run reports on; null on the whole-route ribbon. |
status | ClearanceStatus | required | |
start_m | number | required | |
end_m | number | required | |
note | string | null | required | Advisory text, verbatim from the report. null on other kinds. |
ClearancePointProperties
Type
type ClearancePointProperties = MeasurementPointCommon & {
axis: "height";
headroom_m: number;
safe_headroom_m: number;
overhead_class: ObstructionClass;
};
Properties on an overhead measurement point.
axis is still here, and still useful: a style expression filters on it
to give the two axes different symbols. What it is no longer doing is
telling a reader which of two possible meanings headroom_m has, because
the width features do not have a headroom_m to disambiguate.
| Property | Type | Default | Description |
|---|---|---|---|
axis | "height" | required | |
headroom_m | number | required | |
safe_headroom_m | number | required | |
overhead_class | ObstructionClass | required |
ClearanceStatus
Type
type ClearanceStatus = "pass" | "fail" | "indeterminate" | "no_verdict" | "not_surveyed" | "insufficient_data" | "advisory";
What a style expression keys colour off.
not_surveyed and insufficient_data are separate from every verdict
on purpose. They are not weaker verdicts; they are the absence of one,
and a palette that renders them as a paler version of pass has
reintroduced by styling exactly the confusion the report types spend
their whole existence preventing.
ClearanceText
Type
type ClearanceText = string & {
readonly __clearanceText: "a measured value and its bound, together";
};
Display text for a clearance measurement, carrying its bound.
A branded string: it is a string and can be assigned to one, passed
to a DOM API or put in a GeoJSON property, but a plain string cannot be
assigned back to it. That asymmetry is the whole point. Produced only by
formatClearance and formatLateral.
| Property | Type | Default | Description |
|---|---|---|---|
readonly __clearanceText | "a measured value and its bound, together" | required |
ColourMode
Type
type ColourMode = "rgb" | "class" | "height";
How points are coloured.
CopcChannel
Type
type CopcChannel = "rgb" | "intensity";
Which scalar lands in the three colour bytes.
CopcTransportErrorCode
Type
type CopcTransportErrorCode = "range-unsupported" | "network" | "stalled" | "http-status" | "retries-exhausted" | "aborted" | "laz-perf-missing" | "invalid-request";
Machine-readable discriminator on CopcTransportError.
Coverage
Type
type Coverage = {
kind: "complete";
assessed_m: number;
} | {
kind: "partial";
assessed_m: number;
not_surveyed: RouteSection[];
insufficient_data: RouteSection[];
};
How much of the route was actually assessed.
The two variants are distinct shapes on purpose. complete has no field
for gaps, so a consumer holding a complete coverage cannot ask about
unsurveyed ground and get a plausible-looking empty array back; it has
to narrow first, and narrowing is where it learns that gaps exist.
Open sky is not a gap. A station over a cell with adequate road density
and no validated overhead cluster is a measurement of no obstruction,
and it counts toward assessed_m. A station outside every field, or
over cells flagged NO_ROAD, is the absence of a measurement and lands
in not_surveyed.
IndeterminateReason
Type
type IndeterminateReason = "inside_uncertainty_band" | "vegetation_age_exceeded" | "artefact_stale" | "artefact_freshness_unchecked";
Why an answer stopped short of a verdict, as a machine-readable set.
It travels beside the human sentence, not instead of it. A caller that wants to branch on the cause should not be parsing prose, and a person reading the answer should not be handed an enum variant. The list is sorted and de-duplicated by the producer, so two identical answers serialise identically.
inside_uncertainty_band, the margin landed between the measured value and the safe bound. The measurement is fine; the question is too close to call.vegetation_age_exceeded, the limiting return is vegetation on a survey old enough that foliage has had time to move.artefact_stale, the clearance field is behind the sources it was baked from. A pass is refused rather than served with a warning.artefact_freshness_unchecked, staleness could not be established either way, which is not the same as fresh.
LateralPointProperties
Type
type LateralPointProperties = MeasurementPointCommon & {
axis: "width";
clear_width_m: number;
safe_clear_width_m: number;
limited_by: ObstructionClass;
};
Properties on a lateral measurement point.
No headroom field, matching MeasuredLateral. The Rust crate asserts
that a serialised width answer contains the string "headroom" nowhere;
geojson.test.ts asserts the same of this collection, so the guarantee
survives the trip through the SDK and onto the map.
| Property | Type | Default | Description |
|---|---|---|---|
axis | "width" | required | |
clear_width_m | number | required | |
safe_clear_width_m | number | required | |
limited_by | ObstructionClass | required |
MetaClassTable
Type
type MetaClassTable = Record<string, string> | MetaClassEntry[];
The two class-table shapes a sidecar may use.
A map keyed by the class byte is the compact form; a list of entries is
what most bakers produce, because it survives a round trip through a
language whose object keys are not strings. Both mean the same thing, so
both are read, see classesFromMeta.
ObstructionClass
Type
type ObstructionClass = "structure" | "vegetation" | "wire" | "unknown";
What the survey found in the way, as a class prior rather than a certainty.
vegetation and wire are labelled, never dropped: a branch across a
lane is a real obstruction, and a catenary is a real obstruction to a
high load. What the label buys is honesty about what the figure will do
next, vegetation is compressible and seasonal, and a wire is a thin
target measured from few returns.
This taxonomy IS shared between the two axes, deliberately, and that is not a contradiction of the rule that keeps their measurements apart. A headroom metre and a clear-width metre are different physical quantities; "vegetation" is the same material whether it is above the vehicle or beside it.
OverheadClass
Type
type OverheadClass = ObstructionClass;
The obstruction class at its vertical use site, where "overhead" reads
correctly. An alias, not a copy: one definition, two names, so the two
axes cannot drift apart on what vegetation means.
PointCloudSource
Type
type PointCloudSource = DecodedPointCloud | {
meta: PointCloudMeta;
raw: ArrayBuffer;
} | StreamingSource;
Where a layer's points come from.
The first two are the existing signature, so adding the third is a widening and not a break.
RangeGetter
Type
type RangeGetter = (begin: number, end: number, signal?: AbortSignal) => Promise<Uint8Array>;
The byte-range reader every COPC read goes through. The whole I/O seam.
end is exclusive, matching Uint8Array.prototype.subarray and the
public API in the streaming design §11.2. HTTP's Range header is
inclusive at both ends, so the fetch implementation subtracts one; that
conversion happens in exactly one place (:transport/http).
RouteVerdict
Type
type RouteVerdict = {
verdict: "pass";
coverage: Extract<Coverage, {
kind: "complete";
}>;
tightest?: LimitingPoint;
} | {
verdict: "fail";
coverage: Coverage;
limiting: LimitingPoint;
all_failures: LimitingPoint[];
} | {
verdict: "indeterminate";
coverage: Coverage;
contested: LimitingPoint[];
reasons: IndeterminateReason[];
resolution_hint: string;
} | {
verdict: "no_verdict";
coverage: Extract<Coverage, {
kind: "partial";
}>;
reason: "no_survey_coverage" | "insufficient_data";
};
The verdict on the vehicle's height.
A passing route CANNOT carry coverage gaps: pass narrows Coverage to
its complete variant, and no_verdict narrows it to partial. Those
two Extracts are the invariant. Everything else about the honesty of a
clearance answer is prose that somebody can ignore; this is arithmetic
the compiler does.
tightest is optional, and that is not a loosening. A fully
surveyed stretch of open road has no tightest point, because nothing
overhead was ever measured on it: overhead_z is NaN across the whole
corridor, which is a measurement of clear sky rather than a very large
headroom. The alternatives were both worse than an absent field, fabricate a number for a safety report, or refuse to pass the safest
possible route. Where a constraint does exist it is always named,
because "yes" is not an answer a dispatcher can act on and "yes, with
0.11 m to spare under the railway bridge at 4.2 km" is.
VerticalBasis
Type
type VerticalBasis = {
kind: "ellipsoidal";
ellipsoid: "WGS84";
undulation_applied_m: number | null;
} | {
kind: "orthometric";
geoid: string;
} | {
kind: "dem_referenced";
dem: "terrarium_z14";
};
How a survey's heights relate to a physical vertical datum.
Recorded so a reader can say what the absolute numbers mean, never so the
clearance arithmetic can use them. dem_referenced is listed because
reheighted payloads exist and their basis has to be stateable; it is not
a clearance source, and a bake reading one is a bug rather than a
degraded mode.
WidthVerdict
Type
type WidthVerdict = {
verdict: "pass";
coverage: Extract<Coverage, {
kind: "complete";
}>;
tightest?: LateralPoint;
} | {
verdict: "fail";
coverage: Coverage;
limiting: LateralPoint;
all_failures: LateralPoint[];
} | {
verdict: "indeterminate";
coverage: Coverage;
contested: LateralPoint[];
reasons: IndeterminateReason[];
resolution_hint: string;
} | {
verdict: "no_verdict";
coverage: Extract<Coverage, {
kind: "partial";
}>;
reason: "no_survey_coverage" | "insufficient_data";
};
The verdict on the vehicle's width. Structurally the shape of
RouteVerdict, deliberately not the same type.
It carries LateralPoints, which carry MeasuredLaterals,
which have no headroom field. Sharing one verdict type between the axes
would have put a clear width inside a MeasuredClearance and undone the
separation at the only level that matters.
The same coverage invariant applies, for the same reason: a width axis that passes over ground with no surveyed corridor edges is exactly as dangerous as a height axis that does.
Constants (@mapmap/points)
ASPRS_LAS14_CLASS_LABELS, ASPRS_LAS14_CLASSES, BYTES_PER_POINT, DEFAULT_FOV_DEG, DEFAULT_PALETTE, FOG_FAR_MULTIPLE, FOG_NEAR_DIAGONAL_FRACTION, MAX_CLASSES, MIN_FOG_NEAR_M, MIN_HEIGHT_SPAN_M
ASPRS_LAS14_CLASS_LABELS
Constant
const ASPRS_LAS14_CLASS_LABELS: Readonly<Record<number, string>>;
Every ASPRS name for PDRF 6–10, by class byte (LAS 1.4 R15 Table 17).
For labelling a byte ASPRS_LAS14_CLASSES leaves out, which is
what observedClasses() will hand you the moment a file uses one. Bytes
23–63 are reserved and 64–255 are user-definable, so anything absent from
this map is the producer's to name, not ours.
ASPRS_LAS14_CLASSES
Constant
const ASPRS_LAS14_CLASSES: readonly PointClass[];
The curated 14. Colours are chosen to read as a landscape rather than as a palette: ground is earth, vegetation greens by height, buildings are the one saturated colour, noise is a warning red, wires are pale.
BYTES_PER_POINT
Constant
const BYTES_PER_POINT = 10;
Bytes one point occupies: 3 * Uint16 + 4 * Uint8.
DEFAULT_FOV_DEG
Constant
const DEFAULT_FOV_DEG = 36.87;
MapLibre's default vertical field of view, in degrees.
DEFAULT_PALETTE
Constant
const DEFAULT_PALETTE: readonly string[];
Distinguishable, colour-blind-aware default palette.
Ordered so that the first few stay separable for viewers with the common forms of colour-vision deficiency: the early entries differ in lightness as well as hue, which is what survives a deuteranopic simulation.
FOG_FAR_MULTIPLE
Constant
const FOG_FAR_MULTIPLE = 3;
Fog is total at this multiple of where it starts.
FOG_NEAR_DIAGONAL_FRACTION
Constant
const FOG_NEAR_DIAGONAL_FRACTION = 0.6;
Fraction of the horizontal diagonal at which fog starts.
MAX_CLASSES
Constant
const MAX_CLASSES = 16;
Most classes any one cloud may declare.
The cap is a uniform-budget limit, not an arbitrary round number. The
shader carries a vec3 colour, a visibility float and the class byte per
class in uniform arrays, and WebGL1 only guarantees 128 vertex uniform
vectors, array elements are not reliably packed, so each one can cost a
full slot. Sixteen classes is 48 slots, which leaves comfortable room for
everything else the vertex shader needs on the weakest conforming device.
It is also more than a legend can usefully hold: past roughly a dozen flat colours a viewer cannot tell them apart anyway.
MIN_FOG_NEAR_M
Constant
const MIN_FOG_NEAR_M = 120;
Fog never starts closer than this, however small the cloud.
MIN_HEIGHT_SPAN_M
Constant
const MIN_HEIGHT_SPAN_M = 8;
Shortest height range the ramp is allowed to span.
A zero-height range divides by zero in the ramp and paints every point the same colour, so a flat cloud (or a caller asking for min === max) is widened to this instead. Eight metres is roughly two storeys: enough that the ramp still says something about a nominally flat scene.
@mapmap/points/worker
import { /* … */ } from "@mapmap/points/worker";
Functions (@mapmap/points/worker)
onDecodeMessage, setLazPerfImporter
onDecodeMessage
Function
function onDecodeMessage(scope: WorkerScope, data: unknown): Promise<void>;
Handle one message. Exported so a test can drive it without a Worker.
| Parameter | Type | Default | Description |
|---|---|---|---|
scope | WorkerScope | required | |
data | unknown | required |
setLazPerfImporter
Function
function setLazPerfImporter(fn: (() => Promise<unknown>) | null): void;
Hand the loader your own import() of laz-perf.
LoadLazPerfOptions.importModule says the same thing, and cannot be used
where it is most needed: a worker is constructed from a URL and given no
options, so there is no way to pass a function into one. This setter is
that channel, called from inside the worker's own module scope, which is
why worker.ts re-exports it.
The case it exists for is real and is not hypothetical. A bundler that
processes this package's pre-built dist/worker.js may fail to map the
bare specifier in its dynamic import, and the failure mode is the worst
kind: the import neither resolves nor rejects, so every decode hangs, no
error is ever logged, and the scene stays empty while the network, the
octree and the georeferencing all work perfectly. An app-owned worker
module can resolve laz-perf in its own graph, where its bundler knows
what node_modules is, and hand the result over here.
// the app's own worker entry
import { setLazPerfImporter } from "@mapmap/points/worker";
setLazPerfImporter(() => import("laz-perf/lib/worker/index.js"));
Must be called before the first decode message is handled. Module scope in the worker entry is exactly that moment.
| Parameter | Type | Default | Description |
|---|---|---|---|
fn | (() => Promise<unknown>) | null | required |