Skip to content
Documentation menu
docs / points-reference · raw .md

@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 in website/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.

bash
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

ts
import { /* … */ } from "@mapmap/points";

Classes (@mapmap/points)

CopcTransportError, UnsupportedCrsError

CopcTransportError

Class

ts
class CopcTransportError extends Error { … }

Every error this layer throws, so a caller can catch one type.

Extends Error.

PropertyTypeDefaultDescription
readonly codeCopcTransportErrorCode
readonly statusnumber | nullHTTP status, when there was one.
readonly urlstring | nullThe 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

ts
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

ts
class UnsupportedCrsError extends Error { … }

Thrown when a CRS is outside the v1 set and no crs hook was supplied.

Extends Error.

PropertyTypeDefaultDescription
readonly crsNamestring
readonly epsgnumber | undefined
readonly familystring | undefined

Constructor and methods

  • constructor(message: string, crsName: string, epsg?: number, family?: string)

Example

ts
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

ts
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.

ParameterTypeDefaultDescription
metaPick<PointCloudMeta, "absoluteHeights" | "terrainRelative">required

Options and types: PointCloudMeta.

asprsClassLabel

Function

ts
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.

ParameterTypeDefaultDescription
valuenumberrequired

autoFogRange

Function

ts
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.

ParameterTypeDefaultDescription
boxLocalBoxrequired

Options and types: LocalBox.

cacheBytesForBudget

Function

ts
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.

ParameterTypeDefaultDescription
pointBudgetnumberrequired
override?numbernot stated

clampHeightRange

Function

ts
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.

ParameterTypeDefaultDescription
minMnumberrequired
maxMnumberrequired

classesFromMeta

Function

ts
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. colour and color are 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.

ParameterTypeDefaultDescription
tableMetaClassTable | null | undefinedrequired

Options and types: MetaClassTable.

classTableProblems

Function

ts
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.

ParameterTypeDefaultDescription
classesunknownrequired

createPointCloudLayer

Function

ts
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.

ParameterTypeDefaultDescription
sourcePointCloudSourcerequired
options?PointCloudLayerOptionsnot stated

Options and types: PointCloudSource, PointCloudLayerOptions.

Example

ts
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

ts
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.

ParameterTypeDefaultDescription
metaPointCloudMetarequired
rawArrayBufferrequired

Options and types: PointCloudMeta.

defaultHeightRange

Function

ts
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.

ParameterTypeDefaultDescription
boxLocalBoxrequired

Options and types: LocalBox.

deviceMemoryGb

Function

ts
function deviceMemoryGb(): number | undefined;

Read navigator.deviceMemory without assuming a DOM.

encodePointCloud

Function

ts
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.

ParameterTypeDefaultDescription
input{ anchor: [number, number]; xyz: Float64Array | Float32Array | number[]; rgb: Uint8Array | number[]; classes?: Uint8Array | number[]; quant?: number; sensor?: string; }required

eyePosition

Function

ts
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).

ParameterTypeDefaultDescription
viewCameraViewrequired
centreLocalCentrerequired

Options and types: CameraView, LocalCentre.

formatClearance

Function

ts
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.

ParameterTypeDefaultDescription
cMeasuredClearancerequired

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

ts
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.

ParameterTypeDefaultDescription
lMeasuredLateralrequired

Options and types: MeasuredLateral.

Throws: on the same three arithmetic impossibilities.

isAbortError

Function

ts
function isAbortError(error: unknown): boolean;

True for the DOM AbortError and for our own aborted-code error.

ParameterTypeDefaultDescription
errorunknownrequired

isStreamingSource

Function

ts
function isStreamingSource(source: unknown): source is StreamingSource;

Narrow a layer source to a streamed one.

ParameterTypeDefaultDescription
sourceunknownrequired

localOffset

Function

ts
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.

ParameterTypeDefaultDescription
anchor[number, number]required
lngLat[number, number]required

metresPerPixel

Function

ts
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.

ParameterTypeDefaultDescription
latnumberrequired
zoomnumberrequired

openCopc

Function

ts
function openCopc(source: string | RangeGetter, options?: CopcOpenOptions): Promise<CopcSource>;

Read a COPC's header and hierarchy root. Fetches no points.

ParameterTypeDefaultDescription
sourcestring | RangeGetterrequireda URL, or a RangeGetter to read bytes yourself.
options?CopcOpenOptionsnot stated

Options and types: RangeGetter, CopcOpenOptions.

Example

ts
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

ts
function payloadBytes(count: number): number;

Byte length a payload with count points must have.

ParameterTypeDefaultDescription
countnumberrequired

pointBudgetForDevice

Function

ts
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.

ParameterTypeDefaultDescription
deviceMemoryGb?numbernot stated
override?numbernot stated

pointSizeScale

Function

ts
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.

ParameterTypeDefaultDescription
worldSizeMnumberrequired
bufferHeightPxnumberrequired
fovDeg?numbernot stated

reportToGeoJson

Function

ts
function reportToGeoJson(report: RouteClearanceReport): ClearanceGeoJson;

Turn a route clearance report into map geometry.

The collection contains, in order:

  1. the route ribbon, one LineString decoded from route.geometry_polyline6, statused by the height verdict. Omitted when the report carries no drawable shape;
  2. coverage gaps, not_surveyed then insufficient_data, for the height axis and then the width axis;
  3. advisory runs;
  4. 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.

ParameterTypeDefaultDescription
reportRouteClearanceReportrequired

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

ts
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.

ParameterTypeDefaultDescription
classesreadonly PointClass[]required

Options and types: PointClass.

stickyElevation

Function

ts
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.

ParameterTypeDefaultDescription
samplednumber | null | undefinedrequired
rememberednumber | nullrequired

validateMeta

Function

ts
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.

ParameterTypeDefaultDescription
metaPointCloudMetarequired

Options and types: PointCloudMeta.

visiblePointCount

Function

ts
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.

ParameterTypeDefaultDescription
classesResolvedClassesrequired
classCountsRecord<string, number> | undefinedrequired

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

ts
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.

PropertyTypeDefaultDescription
minnumberrequiredFloor. The budget never shrinks below this.
maxnumberrequiredHard ceiling. The budget never grows above this, whatever the frame rate.
targetFps?numberDEFAULT_TARGET_FPS (~50)Frame rate to hold. Default DEFAULT_TARGET_FPS (~50).

CameraView

Interface

ts
interface CameraView

The parts of a MapLibre map this module reads.

PropertyTypeDefaultDescription
centre{ lng: number; lat: number; }requiredMap centre in degrees.
zoomnumberrequired
bearingnumberrequiredDegrees, clockwise from north.
pitchnumberrequiredDegrees from vertical.
fovDeg?numberDEFAULT_FOV_DEGVertical field of view in degrees. Defaults to DEFAULT_FOV_DEG.
bufferHeightPxnumberrequiredDrawing-buffer height in DEVICE pixels, not the CSS height.
cssHeightPx?numberbufferHeightPx, which is right on a 1x display and is what every caller before 0.3.0 effectively passedCanvas 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?numbernot statedTerrain elevation at the centre in metres, or 0 when terrain is off.

ChangeEvent

Interface

ts
interface ChangeEvent

One clustered difference between two epochs.

PropertyTypeDefaultDescription
kindChangeKindrequired
location{ lng: number; lat: number; }required
footprint_m2numberrequired
dz_mnumber | nullrequiredHeight change, metres, or null where the change is not a height.
threshold_mnumberrequiredThe 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_classOverheadClassrequired
view_urlstringrequired

ChangeReport

Interface

ts
interface ChangeReport

A whole-corridor comparison between two epochs.

PropertyTypeDefaultDescription
registrationEpochRegistrationrequired
eventsChangeEvent[]required
compared_fractionnumberrequiredFraction of the corridor both epochs actually covered.
explanationstringrequired

ClearanceEnforcement

Interface

ts
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.

PropertyTypeDefaultDescription
basis"surveyed_pointcloud_within_survey_difference"required
route_certifiedfalserequiredAlways 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_datumstringrequiredThe vertical frame the figures live in, spelled out for a reader.
caveatstringrequiredWhat this answer does and does not prove, in one paragraph.

ClearanceFieldSidecar

Interface

ts
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.

PropertyTypeDefaultDescription
schema_version1required
datasetstringrequiredThe dataset id, as the point-cloud manifest spells it.
datumDatumBlockrequired
cell_size_mnumberrequired
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; }requiredWhere 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; }[]requiredContent hashes of the source tiles, for staleness detection.
footprint?Polygonnot statedWGS84 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

ts
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.

PropertyTypeDefaultDescription
type"FeatureCollection"required
featuresClearanceFeature[]required

CopcCloudMeta

Interface

ts
interface CopcCloudMeta extends StreamCloudMeta

Everything a COPC file tells us about itself, in PointCloudMeta shape.

Extends StreamCloudMeta.

PropertyTypeDefaultDescription
streamingtruerequiredAlways true. How a consumer tells a streamed source from a baked one.
totalPointsnumberrequiredPoints in the file, from the LAS header. count mirrors this.
maxLevelnumberrequiredDeepest level present in the hierarchy loaded so far. Live.
spacingMnumberrequiredRoot point spacing, metres. The geometric error at level 0.
pdrfPdrfrequiredLAS point data record format: 6, 7 or 8.
hasRgbbooleanrequiredTrue when the PDRF carries RGB (7 or 8).
hasNirbooleanrequiredTrue when the PDRF carries NIR (8).
wkt?stringnot statedThe file's CRS, verbatim from the LASF_Projection WKT VLR.
projection"geographic" | "webMercator" | "transverseMercator" | "lambertCC" | "caller"requiredHow the SDK is reprojecting: which family, or "caller" for a hook.

CopcDataStats

Interface

ts
interface CopcDataStats

Counters for getStats() and for a proof run's request ledger.

PropertyTypeDefaultDescription
requestsnumberrequiredRange requests actually issued, node data and hierarchy pages alike.
nodeRequestsnumberrequired
pageRequestsnumberrequired
bytesnumberrequiredCompressed bytes pulled over the wire.
wastedBytesnumberrequiredBytes inside a coalesced span that nobody asked for.
nodesDecodednumberrequired
nodesFailednumberrequired
cacheHitsnumberrequiredNodes served from the decoded LRU with no request at all.
decodedCacheBytesnumberrequired
compressedCacheBytesnumberrequired
requestsInFlightnumberrequiredCoalesced 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.
maxObservedRequestsInFlightnumberrequired
pendingDecodeBytesnumberrequiredCompressed bytes fetched and waiting for a decode worker.
maxObservedPendingDecodeBytesnumberrequiredIts high-water mark, against maxPendingDecodeBytes.

CopcOpenOptions

Interface

ts
interface CopcOpenOptions
PropertyTypeDefaultDescription
anchor?[number, number]the info VLR's cube centre, reprojectedOverride the anchor. Default: the info VLR's cube centre, reprojected.
crs?CrsHooknot statedReproject 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?numbernot statedMetres 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?numberclamp(hardwareConcurrency - 2, 2, 4)Worker count. Default: clamp(hardwareConcurrency - 2, 2, 4).
createWorker?(index: number) => PoolWorkerThe default is new Worker(new URL("./worker.js", import.meta.url), { type: "module" }), which every bundler this SDK supports understandsBuild 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?stringEmscripten 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 errorURL 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?numberderived from the point budgetDecoded-cache byte budget. Default: derived from the point budget.
compressedCacheBytes?numbernot statedCompressed-node cache, which makes a channel switch a re-decode.
coalesceGapBytes?number32 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?number4 MB (§16), which caps how long a single request can monopolise a connectionLargest 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?RequestInitnot statedPassed to every fetch: auth headers, credentials, a cache mode.
requestStallMs?number15 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?number6Total 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?RangeGetternot statedReplace the transport entirely. This is the seam the tests use.
getPageRange?RangeGettergetRangeHierarchy 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?stringnot statedOverride meta.sensor, which otherwise comes from the LAS header.
onWarn?(message: string) => voidconsole.warn, once per distinct messageWhere warnings go. Default: console.warn, once per distinct message.
signal?AbortSignalnot statedAborts the two opening reads.

CopcSource

Interface

ts
interface CopcSource extends StreamingSource

A streamed COPC, as createPointCloudLayer consumes it.

Extends StreamingSource.

PropertyTypeDefaultDescription
readonly kind"copc"required
readonly urlstringrequired
readonly metaCopcCloudMetarequired
readonly channelsreadonly CopcChannel[]requiredWhat the file can be coloured by, given its PDRF.
readonly channelCopcChannelrequiredWhich of them is in the colour bytes right now.
readonly statsCopcDataStatsrequiredRequest and cache counters, for a readout or a proof run.
readonly headerLasHeaderrequiredThe parsed LAS public header, for anything this meta does not carry.
readonly infoCopcInforequiredThe COPC info VLR: cube, spacing, root page.
readonly descriptorProjectionDescriptorrequiredThe §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

ts
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.

PropertyTypeDefaultDescription
crs{ epsg: number; wkt_source: "las_vlr" | "sidecar" | "declared"; }requiredCRS as read from the data (WKT VLR), never from a filename.
vertical_basisVerticalBasisrequired
within_survey_differencestruerequiredThe 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

ts
interface DecodedPointCloud

Two zero-copy views over one payload, ready to hand to the GPU.

PropertyTypeDefaultDescription
positionsUint16Arrayrequiredcount * 3 integers; multiply by quant and add origin for metres.
coloursUint8Arrayrequiredcount * 4 bytes: R, G, B, class.
metaPointCloudMetarequired

EpochRegistration

Interface

ts
interface EpochRegistration

The alignment applied to one epoch before it can be compared to another.

PropertyTypeDefaultDescription
from_epochstringrequired
to_epochstringrequired
transform{ bearing_deg: number; t_e: number; t_n: number; t_z: number; }requiredFour 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"requiredAlways fit_not_survey. An alignment is not a measurement.

EyePosition

Interface

ts
interface EyePosition

Eye position in the cloud's local east/up/south frame, in metres.

PropertyTypeDefaultDescription
xnumberrequired
ynumberrequired
znumberrequired
distanceMnumberrequiredGround distance from eye to map centre, metres.

FogRange

Interface

ts
interface FogRange

Fog distances in metres from the eye.

PropertyTypeDefaultDescription
nearMnumberrequiredWhere fog starts.
farMnumberrequiredWhere fog is total.

LateralPoint

Interface

ts
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.

PropertyTypeDefaultDescription
location{ lng: number; lat: number; }required
route_distance_mnumberrequired
lateralMeasuredLateralrequired
datasetstringrequired
view_urlstringrequired

LimitingPoint

Interface

ts
interface LimitingPoint

A place on the route where an overhead measurement constrains the answer.

PropertyTypeDefaultDescription
location{ lng: number; lat: number; }required
route_distance_mnumberrequired
clearanceMeasuredClearancerequired
datasetstringrequiredWhich survey the figure came from.
view_urlstringrequiredDeep link into the viewer, posed at this exact spot.

LocalBox

Interface

ts
interface LocalBox

Local bounding box in metres, in the cloud's own east/up/south frame.

PropertyTypeDefaultDescription
x[number, number]requiredEast, metres from the anchor.
y[number, number]requiredUp, metres.
z[number, number]requiredSouth, metres from the anchor.

LocalCentre

Interface

ts
interface LocalCentre

Where the centre of the map sits in the cloud's local frame.

PropertyTypeDefaultDescription
xnumberrequiredEast, metres.
znumberrequiredSouth, metres.

MeasuredClearance

Interface

ts
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_m is the combined fit residual of the road estimate, the overhead estimate and the differential georeferencing between them. It is a spread.
  • sampling_gap_m is 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.

PropertyTypeDefaultDescription
headroom_mnumberrequiredoverhead_z - road_z, a within-survey difference.
sigma_mnumberrequired
sampling_gap_mnumberrequiredOne-sided bias bound from the sample spacing on the overhead surface.
safe_headroom_mnumberrequiredheadroom_m - K*sigma_m - sampling_gap_m; the verdict input.
overhead_classOverheadClassrequired
support_m2numberrequiredConnected-component area of the overhead cluster, the noise gate.
surveyed_onstringrequiredISO date the constraining geometry was captured.

MeasuredLateral

Interface

ts
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_m is sqrt(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_m is 2 * 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.
PropertyTypeDefaultDescription
clear_width_mnumberrequiredFree width between the surveyed corridor edges, metres.
sigma_mnumberrequiredsqrt(2) * sigma_rel_georef_m: both edges carry the term.
sampling_gap_mnumberrequired2 * cell_size: one cell of one-sided bias at each edge.
safe_clear_width_mnumberrequiredclear_width_m - K*sigma_m - sampling_gap_m; the verdict input.
limited_byObstructionClassrequiredWhat the narrower edge was: a wall, a hedge, a pole.
support_m2numberrequiredConnected-component area of the limiting cluster, the noise gate.
surveyed_onstringrequiredISO date the constraining geometry was captured.

MetaClassEntry

Interface

ts
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.

PropertyTypeDefaultDescription
bytenumberrequiredThe class byte written in the alpha channel. Integer 0–255.
labelstringrequiredLabel for your own UI.
colour?string | [number, number, number]not statedOptional colour, #rgb, #rrggbb or [r, g, b] in 0–1.
color?string | [number, number, number]not statedUS spelling of MetaClassEntry.colour, accepted verbatim.

MetreRange

Interface

ts
interface MetreRange

A closed range in metres.

PropertyTypeDefaultDescription
minMnumberrequired
maxMnumberrequired

ObservedClass

Interface

ts
interface ObservedClass

One class byte and how many resident points carry it.

PropertyTypeDefaultDescription
valuenumberrequiredThe classification byte, unmasked, exactly as the producer wrote it.
countnumberrequiredPoints of this class in the resident set, right now.

OsmDraftEdit

Interface

ts
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.

PropertyTypeDefaultDescription
element"way" | "node"required
idnumberrequired
set{ "maxheight:physical": string; }requiredThe only tag this product ever drafts.
provenance{ survey: string; surveyed_on: string; sigma_m: number; }required
status"draft"requiredAlways draft. A human reviews and submits; nothing auto-uploads.

PointClass

Interface

ts
interface PointClass

One entry in the class table.

PropertyTypeDefaultDescription
valuenumberrequiredThe byte written in the alpha channel for points of this class.
labelstringrequiredLabel for your own UI. The SDK renders no text.
colour?string | [number, number, number]the palette entry for this class's position in the tableColour 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?booleantrueWhether it starts visible. Defaults to true.

PointCloudLayer

Interface

ts
interface PointCloudLayer extends CustomLayerInterface

Extends CustomLayerInterface.

PropertyTypeDefaultDescription
readonly metaPointCloudMetarequired

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): void
  • setPointSize(metres: number, maxPx?: number): void: World size in metres; maxPx optionally 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. visible is live: it is the array the shader itself reads, so it always reflects the last PointCloudLayer.setClassVisible or PointCloudLayer.setAllClassesVisible call rather than the state the table declared at construction. A legend can therefore read its checked state off the layer instead of shadowing it. null when the layer was created without a classes table.
  • getVisiblePointCount(): number | null: How many points are currently drawn, for a stat readout. meta.count when the layer has no class table, since nothing is being culled. Otherwise the sum of meta.classCounts over the visible classes, or null when the sidecar carries no classCounts: 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. null for a static payload. residentPoints / totalPoints is 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 makes PointCloudLayer.observedClasses usable rather than merely interesting: discover which bytes the file actually uses, then name and colour them. Throws above MAX_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 a readonly array such as the exported ASPRS_LAS14_CLASSES is 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

ts
interface PointCloudLayerOptions
PropertyTypeDefaultDescription
id?string"mapmap-point-cloud"Layer id. Defaults to "mapmap-point-cloud".
classes?readonly PointClass[]not statedThe 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?number0.15World size of one point in metres before perspective scaling. Default 0.15.
maxPointPx?number10Screen-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 statedDistance 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 extentHeight 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?numbera navigator.deviceMemory tier: 12M / 6M / 3M points at >= 8 GB / >= 4 GB / below or unknownResident 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?numbernot statedDecoded-cache byte budget. Streamed sources only.
targetSsePx?number2.0Target screen-space error in device pixels. Default 2.0.
budgetRamp?booleanOff by defaultOpen 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 | booleanOff by default, a library must not govern a caller's frame budget silentlyAdaptive, 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

ts
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.

PropertyTypeDefaultDescription
anchor[number, number]required[lng, lat] the local frame is measured from.
quantnumberrequiredMetres per position unit. 0.02 (2 cm) is the usual choice.
origin[number, number, number]requiredLocal-frame offset in metres added after dequantisation.
countnumberrequiredNumber of points in the binary.
bboxLocalLocalBoxrequiredLocal 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?MetaClassTablenot statedThe 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 statedPoints per class, if the producer counted them.
absoluteHeights?booleantrueWhether 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?booleannot stated
source?stringnot statedFree-form attribution string for the data. Not rendered by the SDK.
sensor?stringnot statedWhat 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

ts
interface ResolvedClasses

A class table normalised for the shader: dense arrays, resolved colours.

PropertyTypeDefaultDescription
valuesnumber[]requiredClass byte for each table row, in table order.
labelsstring[]required
coloursnumber[]requiredFlat rgb triples in 0–1, values.length * 3 long.
visiblenumber[]required1 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.
lookupInt8ArrayrequiredRow index for a class byte, or -1. Length 256.

RouteClearanceReport

Interface

ts
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.

PropertyTypeDefaultDescription
basis"surveyed_geometry_not_signage"required
clearance_enforcementClearanceEnforcementrequired
route{ distance_m: number; geometry_polyline6: string; }required
vehicleVehicleQueryrequired
heightRouteVerdictrequired
widthWidthVerdict | { verdict: "not_assessed"; reason: string; }required
datasets{ dataset: string; survey_dates: string; stale: boolean; }[]required
advisories{ section: RouteSection; note: string; }[]requiredVegetation age, seasonal compressibility and the like.
explanationstringrequiredOne-line honest summary: what was measured, when, with what bound, and what the answer stops short of concluding.

RouteSection

Interface

ts
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.

PropertyTypeDefaultDescription
start_mnumberrequired
end_mnumberrequired
polyline6?stringnot statedThe run's shape, encoded at precision 6, latitude first.
polyline?[number, number][]not statedThe same run as [lng, lat] pairs (GeoJSON order).

StreamCloudMeta

Interface

ts
interface StreamCloudMeta extends PointCloudMeta

Metadata a streamed source synthesises. Task E fills this from the file.

Extends PointCloudMeta.

PropertyTypeDefaultDescription
streamingtruerequiredAlways true. How a consumer tells a streamed source from a baked one.
totalPointsnumberrequiredPoints in the file, from the LAS header. count mirrors it.
spacingMnumberrequiredRoot point spacing in metres: the geometric error at level 0.
maxLevelnumberrequiredDeepest level present in the hierarchy.

StreamingSource

Interface

ts
interface StreamingSource

A streamed point cloud, as createPointCloudLayer consumes it.

PropertyTypeDefaultDescription
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 metaStreamCloudMetarequired
readonly octreeOctreeViewrequiredThe node store, adapted into local metres.
readonly dataNodeDataSourcerequiredThe I/O side.
readonly spacingPerQuantnumberrequired65534 * 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 statedWhich 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

ts
interface StreamStats

Counters a loading readout can show without lying.

PropertyTypeDefaultDescription
residentPointsnumberrequiredDrawn right now.
residentNodesnumberrequired
selectedNodesnumberrequiredIncluding nodes selected but not yet resident.
totalPointsnumberrequiredThe file's own count, from the LAS header.
pendingRequestsnumberrequired
cacheBytesnumberrequired
cacheBudgetBytesnumberrequired
failedNodesnumberrequired
maxLevelResidentnumberrequired
busybooleanrequiredTrue while anything is outstanding: the repaint condition.
pointBudgetnumberrequiredThe 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

ts
interface TagAuditReport

The audit over a dataset, or over one route corridor.

PropertyTypeDefaultDescription
basis"surveyed_geometry_not_signage"required
clearance_enforcementClearanceEnforcementrequired
area{ dataset: string; } | { route_polyline6: string; }required
comparisonsTagComparison[]required
osm_draftsOsmDraftEdit[]required
explanationstringrequired

TagComparison

Interface

ts
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.

PropertyTypeDefaultDescription
osm{ element: "way" | "node"; id: number; tag: "maxheight" | "maxheight:physical"; value_raw: string; value_m: number | null; }required
measuredMeasuredClearancerequired
location{ lng: number; lat: number; }required
finding"consistent" | "tag_understates" | "tag_overstates" | "untagged_structure"requiredWhat 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_mnumberrequiredThe tolerance actually applied, metres.
view_urlstringrequiredDeep link into the viewer at this structure.

VehicleQuery

Interface

ts
interface VehicleQuery

The vehicle the question was asked about.

PropertyTypeDefaultDescription
height_mnumberrequired
width_m?numbernot statedThe width axis is assessed only when a width was given.
margin_mnumber0 and is echoed back: we surface both the measured figure and the safe bound rather than baking anybody's compliance policy into the arithmeticThe 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

ts
type ChangeKind = "appeared" | "vanished" | "moved" | "grew" | "shrank";

What happened to a cluster of cells between two epochs.

ClearanceFeature

Type

ts
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

ts
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.

PropertyTypeDefaultDescription
kind"route" | "coverage_gap" | "advisory"required
axis"height" | "width" | nullrequiredThe axis this run reports on; null on the whole-route ribbon.
statusClearanceStatusrequired
start_mnumberrequired
end_mnumberrequired
notestring | nullrequiredAdvisory text, verbatim from the report. null on other kinds.

ClearancePointProperties

Type

ts
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.

PropertyTypeDefaultDescription
axis"height"required
headroom_mnumberrequired
safe_headroom_mnumberrequired
overhead_classObstructionClassrequired

ClearanceStatus

Type

ts
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

ts
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.

PropertyTypeDefaultDescription
readonly __clearanceText"a measured value and its bound, together"required

ColourMode

Type

ts
type ColourMode = "rgb" | "class" | "height";

How points are coloured.

CopcChannel

Type

ts
type CopcChannel = "rgb" | "intensity";

Which scalar lands in the three colour bytes.

CopcTransportErrorCode

Type

ts
type CopcTransportErrorCode = "range-unsupported" | "network" | "stalled" | "http-status" | "retries-exhausted" | "aborted" | "laz-perf-missing" | "invalid-request";

Machine-readable discriminator on CopcTransportError.

Coverage

Type

ts
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

ts
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

ts
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.

PropertyTypeDefaultDescription
axis"width"required
clear_width_mnumberrequired
safe_clear_width_mnumberrequired
limited_byObstructionClassrequired

MetaClassTable

Type

ts
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

ts
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

ts
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

ts
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

ts
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

ts
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

ts
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

ts
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

ts
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

ts
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

ts
const BYTES_PER_POINT = 10;

Bytes one point occupies: 3 * Uint16 + 4 * Uint8.

DEFAULT_FOV_DEG

Constant

ts
const DEFAULT_FOV_DEG = 36.87;

MapLibre's default vertical field of view, in degrees.

DEFAULT_PALETTE

Constant

ts
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

ts
const FOG_FAR_MULTIPLE = 3;

Fog is total at this multiple of where it starts.

FOG_NEAR_DIAGONAL_FRACTION

Constant

ts
const FOG_NEAR_DIAGONAL_FRACTION = 0.6;

Fraction of the horizontal diagonal at which fog starts.

MAX_CLASSES

Constant

ts
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

ts
const MIN_FOG_NEAR_M = 120;

Fog never starts closer than this, however small the cloud.

MIN_HEIGHT_SPAN_M

Constant

ts
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

ts
import { /* … */ } from "@mapmap/points/worker";

Functions (@mapmap/points/worker)

onDecodeMessage, setLazPerfImporter

onDecodeMessage

Function

ts
function onDecodeMessage(scope: WorkerScope, data: unknown): Promise<void>;

Handle one message. Exported so a test can drive it without a Worker.

ParameterTypeDefaultDescription
scopeWorkerScoperequired
dataunknownrequired

setLazPerfImporter

Function

ts
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.

ts
// 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.

ParameterTypeDefaultDescription
fn(() => Promise<unknown>) | nullrequired