@mapmap/maps 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import maplibregl, { CustomLayerInterface, Map, StyleSpecification, MapOptions, ExpressionSpecification } from 'maplibre-gl';
2
- export { DirectionIconName, DirectionIconStep, directionIconSvg, directionIcons, iconNameForManeuver, iconNameForStep } from './direction-icons.js';
2
+ export { DirectionIconName, DirectionIconStep, directionIconSvg, directionIcons, iconNameForManeuver, iconNameForStep, iconNamesForSteps } from './direction-icons.js';
3
3
 
4
4
  /**
5
5
  * Shared public types for @mapmap/maps.
@@ -258,9 +258,10 @@ declare function createRouteEffect(name: RouteEffectName, geometry: RouteGeometr
258
258
 
259
259
  /**
260
260
  * The MapMap logo control: a small wordmark on the map, the same
261
- * convention as Google Maps' and Mapbox's marks. On by default in
262
- * {@link MapMapMap}; positionable, and removable with `logo: false`
263
- * (attribution itself is separate and never removable).
261
+ * convention as Google Maps' and Mapbox's marks. Always on in
262
+ * {@link MapMapMap} and positionable with `logo: { position }`; it cannot
263
+ * be removed (carrying the mark is a condition of the SDK licence), and
264
+ * the OpenStreetMap attribution is a separate, equally permanent control.
264
265
  *
265
266
  * The mark is an inline SVG (no network fetch, works offline and under
266
267
  * strict CSPs): the MapMap brand lockup — the three-colour route mark
@@ -271,7 +272,15 @@ declare function createRouteEffect(name: RouteEffectName, geometry: RouteGeometr
271
272
  type LogoPosition = "bottom-right" | "bottom-left" | "top-right" | "top-left";
272
273
  /** Options for {@link LogoControl}. */
273
274
  interface LogoOptions {
274
- /** Corner to render in. Default `bottom-right`, above the attribution. */
275
+ /**
276
+ * Corner to render in. Default `bottom-right`.
277
+ *
278
+ * In a bottom corner the mark sits ABOVE the attribution, despite being
279
+ * added after it: MapLibre prepends controls in the bottom corners
280
+ * (`insertBefore(container.firstChild)`) and the corner is anchored to
281
+ * the bottom edge, so the control added last is the one that ends up
282
+ * highest. Verified against maplibre-gl 5's `addControl`.
283
+ */
275
284
  position?: LogoPosition;
276
285
  /** Link target when clicked. Default the MapMap site. */
277
286
  href?: string;
@@ -922,7 +931,8 @@ declare function navDesignFromThemeUrl(url: string, fetchImpl?: typeof fetch): P
922
931
  * and expression building. The class->category mapping mirrors
923
932
  * `website/lib/poi-icons.ts` (`POI_CLASS_CATEGORIES`). Keep them in sync so
924
933
  * a theme previews in Studio exactly as the SDK applies it. Not imported
925
- * from the website because the packages do not depend on each other.
934
+ * from the website because nothing here may depend on the website: the
935
+ * dependency runs the other way (the website installs `@mapmap/maps`).
926
936
  *
927
937
  * PUBLISH NOTE: the block travels with the THEME DOCUMENT, never with the
928
938
  * compiled `style.json`. The canonical `sn_style` crate stores `extra`
@@ -1045,8 +1055,18 @@ declare function registerPmtilesProtocol(gl?: typeof maplibregl): void;
1045
1055
  interface MapMapOptions {
1046
1056
  /**
1047
1057
  * The MapMap wordmark on the map (like the Google Maps / Mapbox marks).
1048
- * Default on at bottom-right, above the attribution; `false` removes
1049
- * it, or pass `{ position, href }` to customise.
1058
+ * Always on, at bottom-right above the attribution by default; pass
1059
+ * `{ position, href }` to move or relink it.
1060
+ *
1061
+ * The mark cannot be taken off. Every displayed MapMap map carries it,
1062
+ * which is a condition of the SDK licence, the same model as Mapbox GL
1063
+ * JS v2+. `false` (and `true`) are still accepted so existing code keeps
1064
+ * compiling: passing `false` is ignored, the mark is added anyway, and
1065
+ * the SDK logs one `console.warn` for that map.
1066
+ *
1067
+ * `false` is soft-deprecated: it does nothing and will be dropped from
1068
+ * the type in the next major. Use `{ position }` to get the mark out of
1069
+ * the way instead.
1050
1070
  */
1051
1071
  logo?: boolean | LogoOptions;
1052
1072
  /** The container element or its id. */
@@ -1070,6 +1090,13 @@ interface MapMapOptions {
1070
1090
  /**
1071
1091
  * Extra MapLibre `MapOptions` merged last (escape hatch for hash, bearing,
1072
1092
  * maxBounds, etc.). `container` and `style` here are ignored.
1093
+ *
1094
+ * `attributionControl` is honoured but never allowed to remove the
1095
+ * credit: `{ compact }` and `{ customAttribution }` work as MapLibre
1096
+ * documents them, while `attributionControl: false` is ignored (the
1097
+ * attribution stays, and the SDK logs one `console.warn` for that map).
1098
+ * The OpenStreetMap credit is a condition of the ODbL the map data is
1099
+ * served under, for MapMap and for you.
1073
1100
  */
1074
1101
  mapOptions?: Partial<Omit<MapOptions, "container" | "style">>;
1075
1102
  }
@@ -1109,7 +1136,32 @@ declare class MapMapMap {
1109
1136
  private effectGeometry;
1110
1137
  private effectExplicit;
1111
1138
  private effectLayer;
1139
+ /** True while an effect re-apply is already queued behind the style. */
1140
+ private effectWaiting;
1141
+ private destroyed;
1142
+ private readonly pendingWaits;
1143
+ private readonly notices;
1112
1144
  constructor(options: MapMapOptions);
1145
+ /**
1146
+ * Log an enforcement notice at most once for this map. A warning, not
1147
+ * an error and never a throw: `logo: false` and
1148
+ * `attributionControl: false` are published, documented options, and a
1149
+ * hard failure would break a customer's build on a patch upgrade.
1150
+ */
1151
+ private notice;
1152
+ /**
1153
+ * Run `action` as soon as the style can take layer edits, or now if it
1154
+ * already can.
1155
+ *
1156
+ * Never wait on `load` alone: MapLibre fires it exactly once per map, so
1157
+ * anything deferred after the first load is dropped for good - and
1158
+ * `isStyleLoaded()` reports false for the whole of every `style.load`
1159
+ * (the new style's sources are still loading) and whenever a source is
1160
+ * loading afterwards. Waiting for whichever of `style.load` / `idle` /
1161
+ * `load` arrives first covers the first load and every later style swap,
1162
+ * and the listeners detach as soon as one fires (or on `destroy()`).
1163
+ */
1164
+ private whenStyleReady;
1113
1165
  private readonly handleStyleLoadForEffects;
1114
1166
  /** The current style's `metadata`, if it can be read yet. */
1115
1167
  private styleMetadata;
@@ -1167,20 +1219,397 @@ declare class MapMapMap {
1167
1219
  * (maplibre-native#4107 — fill-extrusion memory at street zooms).
1168
1220
  */
1169
1221
  setBuildings3d(enabled: boolean): void;
1170
- /** Resolves once the style and first tiles are loaded. */
1222
+ /**
1223
+ * Resolves once the style and first tiles are loaded.
1224
+ *
1225
+ * Safe to call at any point in the map's life, not just before its
1226
+ * first load: `map.loaded()` goes false again whenever a source is
1227
+ * loading (a theme swap, a pan into new tiles) while `load` only ever
1228
+ * fires once, so this waits on `idle` too rather than hanging.
1229
+ */
1171
1230
  whenReady(): Promise<void>;
1172
- /** Remove the map and release its WebGL context. */
1231
+ /**
1232
+ * Remove the map and release its WebGL context. Anything still queued
1233
+ * behind a style load is dropped first, so nothing touches the map
1234
+ * after this returns.
1235
+ */
1173
1236
  destroy(): void;
1174
1237
  }
1175
1238
  /** Functional alias for {@link MapMapMap}, mirroring the Mapbox `new Map` feel. */
1176
1239
  declare function createMap(options: MapMapOptions): MapMapMap;
1177
1240
 
1241
+ /**
1242
+ * GENERATED FILE — do not edit by hand.
1243
+ *
1244
+ * LOCKSTEP: identical copies of this module are emitted to
1245
+ * web-sdk/src/marker-icons.ts
1246
+ * website/lib/marker-icons.ts
1247
+ * Source of truth: map-assets/icons/markers/*.svg
1248
+ * Regenerate with: node map-assets/icons/markers/generate.mjs
1249
+ *
1250
+ * Every glyph is 24x24 viewBox path data, drawn as a solid shape (nonzero
1251
+ * fill rule; holes are reverse-wound subpaths) that reads clearly in white
1252
+ * at 24 px. The pin body is the teardrop the coloured marker is drawn
1253
+ * with: head centre (12, 9.4), radius 9, tip exactly at (12, 24) so an
1254
+ * `icon-anchor: "bottom"` symbol anchors at the tip.
1255
+ *
1256
+ * The glyph-id list is the LOCKED `theme.extra.markers` v1 contract shared
1257
+ * with MapMap Studio and the gateway's Rust validator — order and spelling
1258
+ * must never drift.
1259
+ */
1260
+ /** The 21 marker glyph ids of the `extra.markers` v1 contract, in order. */
1261
+ declare const MARKER_GLYPH_IDS: readonly ["pin", "dot", "star", "heart", "flag", "home", "work", "food", "cafe", "bar", "shop", "hotel", "parking", "fuel", "charging", "transit", "bike", "camera", "music", "warning", "info"];
1262
+ /** One marker glyph id (`"pin"`, `"star"`, `"fuel"`, ...). */
1263
+ type MarkerGlyphId = (typeof MARKER_GLYPH_IDS)[number];
1264
+ /** Whether `value` is one of the 21 marker glyph ids. */
1265
+ declare function isMarkerGlyphId(value: unknown): value is MarkerGlyphId;
1266
+ /** 24x24 viewBox SVG path data for every marker glyph, keyed by id. */
1267
+ declare const markerGlyphPaths: Record<MarkerGlyphId, string>;
1268
+ /**
1269
+ * The coloured pin-body teardrop, 24x24 viewBox: head centre (12, 9.4),
1270
+ * radius 9, tip at (12, 24). Every glyph except `dot` is drawn in white
1271
+ * on top of this body, centred on the head.
1272
+ */
1273
+ declare const MARKER_PIN_BODY_PATH = "M12 24C7 17.2 3 13.2 3 9.4a9 9 0 0 1 18 0C21 13.2 17 17.2 12 24Z";
1274
+ /** The glyph and pin-body coordinate space (a 24x24 viewBox). */
1275
+ declare const MARKER_GLYPH_VIEWBOX = 24;
1276
+
1277
+ /**
1278
+ * MarkersLayer - custom markers & labels from a Studio theme's
1279
+ * `extra.markers` block (schema v1).
1280
+ *
1281
+ * A theme can carry up to 200 designed markers - coloured glyph pins, a
1282
+ * plain dot, or a small custom image - each with an optional text label.
1283
+ * This module renders that block on a MapLibre map: glyph markers are
1284
+ * rasterised on an offscreen canvas at 2x (the `poi-icons` pattern) and
1285
+ * registered via `map.addImage`; custom images are `data:` URIs capped at
1286
+ * 64 KB (the `puck.ts` precedent). Everything draws from ONE GeoJSON
1287
+ * source and ONE symbol layer, both id `mm-user-markers`, with per-feature
1288
+ * `icon-image`, `icon-size`, `icon-anchor`, label and text sizing.
1289
+ *
1290
+ * LOCKSTEP: the schema, glyph set, image caps and cache-key format are a
1291
+ * locked contract shared with MapMap Studio's markers panel and the
1292
+ * gateway's Rust validator. Do not change ids, defaults or key formats
1293
+ * here without coordinating all three.
1294
+ *
1295
+ * Like PlacesLayer, the layer survives `setStyle` (theme swaps) — but NOT
1296
+ * because a diffed `setStyle` leaves it alone. `Style.serialize()` includes
1297
+ * runtime-added sources and layers, so MapLibre's diff sees
1298
+ * `mm-user-markers` in the old style and not in the new one and emits
1299
+ * removeLayer/removeSource for it: EVERY theme swap tears this layer down.
1300
+ * It comes back because `Style.setState` fires `style.load` after a
1301
+ * successful diff as well as after a full reload, and this layer
1302
+ * re-installs on that event. Do not "optimise" the re-install away.
1303
+ * (Runtime IMAGES do survive a successful diff — only a full reload rebuilds
1304
+ * the image registry — so `registerImages` is written to be idempotent.)
1305
+ */
1306
+
1307
+ /** One marker size preset. */
1308
+ type MarkerSize = "s" | "m" | "l";
1309
+ /** The marker size presets, smallest first. */
1310
+ declare const MARKER_SIZES: readonly ["s", "m", "l"];
1311
+ /** CSS pixel size of each marker preset (the rasterised square canvas). */
1312
+ declare const MARKER_SIZES_PX: Record<MarkerSize, number>;
1313
+ /** Label text size in px for each marker size preset. */
1314
+ declare const MARKER_LABEL_TEXT_SIZES: Record<MarkerSize, number>;
1315
+ /** Default marker colour (MapMap marker blue). */
1316
+ declare const MARKER_DEFAULT_COLOUR = "#1a6bff";
1317
+ /** Maximum accepted `data:` marker-image payload (matches the puck's cap). */
1318
+ declare const MAX_MARKER_IMAGE_BYTES: number;
1319
+ /** Maximum markers per theme (`extra.markers` v1 contract). */
1320
+ declare const MAX_MARKER_ITEMS = 200;
1321
+ /** Maximum marker `id` length (contract). */
1322
+ declare const MAX_MARKER_ID_LENGTH = 64;
1323
+ /** Maximum marker `label` length (contract). */
1324
+ declare const MAX_MARKER_LABEL_LENGTH = 120;
1325
+ /**
1326
+ * Maximum characters drawn INSIDE a pin head by the short-text mode of
1327
+ * {@link renderMarkerImage} (numbered waypoints `1`, `2`, `3`; lettered
1328
+ * stops `A`, `B`). Anything longer belongs in a `label` below the pin.
1329
+ */
1330
+ declare const MAX_MARKER_TEXT_LENGTH = 3;
1331
+ /** The shared GeoJSON source id AND symbol layer id (contract). */
1332
+ declare const MARKERS_ID = "mm-user-markers";
1333
+ /** One parsed marker of an `extra.markers` v1 block. */
1334
+ interface MarkerItem {
1335
+ /** Stable unique id, non-empty, at most 64 characters. */
1336
+ id: string;
1337
+ /** Longitude, degrees, [-180, 180]. */
1338
+ lng: number;
1339
+ /** Latitude, degrees, [-90, 90]. */
1340
+ lat: number;
1341
+ /** Glyph drawn on the pin (or the standalone `dot`). Default `"pin"`. */
1342
+ icon: MarkerGlyphId;
1343
+ /**
1344
+ * Optional custom image (`data:` URI, png/jpeg/webp/svg+xml, at most
1345
+ * 64 KB decoded). Overrides `icon`; an unusable image falls back to the
1346
+ * default pin, never to nothing.
1347
+ */
1348
+ image?: string;
1349
+ /** Pin colour, `#rrggbb` (normalised lowercase). Default `#1a6bff`. */
1350
+ colour: string;
1351
+ /** Size preset. Default `"m"` (32 CSS px). */
1352
+ size: MarkerSize;
1353
+ /** Optional label drawn below the marker, at most 120 characters. */
1354
+ label?: string;
1355
+ }
1356
+ /** A parsed `extra.markers` block. */
1357
+ interface MarkersBlock {
1358
+ /** Block schema version; currently always 1. */
1359
+ version: 1;
1360
+ /** The valid markers, in input order, capped at 200. */
1361
+ items: MarkerItem[];
1362
+ }
1363
+ /**
1364
+ * Cut a string to `max` UNICODE SCALARS (not UTF-16 code units). The Rust
1365
+ * validator counts `chars()`, so a plain `slice` both rejected valid emoji
1366
+ * strings and could cut a surrogate pair in half — and a lone surrogate
1367
+ * fails `serde_json` with a JSON parse error instead of the friendly
1368
+ * marker message. LOCKSTEP: the same helper exists in Studio's mirror.
1369
+ */
1370
+ declare function truncateChars(text: string, max: number): string;
1371
+ /**
1372
+ * A usable custom marker image, or `undefined`: a `data:` URI of an
1373
+ * accepted image type (png/jpeg/webp/svg+xml) whose decoded payload is at
1374
+ * most 64 KB. Oversized or foreign URIs are rejected - the marker falls
1375
+ * back to its glyph pin (the `puck.ts` cap precedent).
1376
+ */
1377
+ declare function markerImage(v: unknown): string | undefined;
1378
+ /**
1379
+ * Lenient parse of an `extra.markers` value into a v1 block. A block whose
1380
+ * `version` is not 1 (including a missing one) parses to NOTHING: the Rust
1381
+ * validator requires exactly 1, so a version-less block that rendered here
1382
+ * could never be published, and a future v2 block must not be silently
1383
+ * drawn as v1 by an already-deployed SDK.
1384
+ *
1385
+ * Within a v1 block, invalid ITEMS are skipped (bad/missing id, duplicate
1386
+ * id, out-of-range coordinates); invalid FIELDS on a valid item fall back
1387
+ * to their defaults (unknown glyph -> `pin`, bad colour -> the default
1388
+ * blue, bad size -> `m`, over-long labels truncated, unusable images
1389
+ * dropped). Never throws on bad data - a hand-edited theme cannot break
1390
+ * the map. At most 200 items survive.
1391
+ */
1392
+ declare function parseMarkers(value: unknown): MarkersBlock;
1393
+ /**
1394
+ * Reads and parses the `extra.markers` block from a Studio theme document.
1395
+ * Returns `undefined` when the theme carries no markers block at all (so
1396
+ * callers can tell "no markers" apart from "an empty designed set"); a
1397
+ * present but malformed block parses leniently via {@link parseMarkers}.
1398
+ */
1399
+ declare function markersFromTheme(theme: Theme | undefined | null): MarkersBlock | undefined;
1400
+ /**
1401
+ * Fetches a hosted theme document and reads its `extra.markers` block -
1402
+ * the same endpoint contract as `navDesignFromThemeUrl`:
1403
+ *
1404
+ * ```ts
1405
+ * const markers = await markersFromThemeUrl(
1406
+ * "https://api.mapmap.ai/styles/midnight-fleet-a1b2c3/theme",
1407
+ * );
1408
+ * if (markers) new MarkersLayer(map, markers);
1409
+ * ```
1410
+ *
1411
+ * Returns `undefined` when the theme carries no markers block; throws on
1412
+ * network failure, a non-2xx response or a non-JSON body. Bad DATA never
1413
+ * throws - invalid items are skipped by the lenient parser.
1414
+ *
1415
+ * @param fetchImpl Optional `fetch` replacement (tests, Node polyfills).
1416
+ */
1417
+ declare function markersFromThemeUrl(url: string, fetchImpl?: typeof fetch): Promise<MarkersBlock | undefined>;
1418
+ /**
1419
+ * `map.addImage` cache key for a rasterised glyph marker:
1420
+ * `mm-marker-<icon>-<colour>-<size>` (contract format; the colour is the
1421
+ * normalised lowercase `#rrggbb`). Identical markers share one image.
1422
+ */
1423
+ declare function markerImageKey(icon: MarkerGlyphId, colour: string, size: MarkerSize): string;
1424
+ /**
1425
+ * Normalise a short pin text: trimmed and cut to
1426
+ * {@link MAX_MARKER_TEXT_LENGTH} characters, or `undefined` when nothing
1427
+ * printable remains. Callers use `undefined` to mean "draw the glyph".
1428
+ */
1429
+ declare function markerText(value: unknown): string | undefined;
1430
+ /**
1431
+ * `map.addImage` cache key for a rasterised SHORT-TEXT marker:
1432
+ * `mm-marker-text-<text>-<colour>-<size>`. Deliberately distinct from
1433
+ * {@link markerImageKey} so glyph pins keep their existing keys unchanged.
1434
+ */
1435
+ declare function markerTextImageKey(text: string, colour: string, size: MarkerSize): string;
1436
+ /**
1437
+ * `map.addImage` cache key for a custom-image marker: the data URI is
1438
+ * hashed (FNV-1a), so identical images share one registered copy no
1439
+ * matter how many markers use them.
1440
+ */
1441
+ declare function markerImageDataKey(dataUri: string): string;
1442
+ /** What {@link renderMarkerImage} returns, ready for `map.addImage`. */
1443
+ interface RenderedMarkerImage {
1444
+ /** The rasterised marker, drawn at 2x. */
1445
+ image: ImageData;
1446
+ /** Pass to `addImage` so the marker displays at its CSS pixel size. */
1447
+ pixelRatio: 2;
1448
+ }
1449
+ /** Options for {@link renderMarkerImage}. */
1450
+ interface RenderMarkerImageOptions {
1451
+ /** Glyph to draw. Default `"pin"`. */
1452
+ icon?: MarkerGlyphId;
1453
+ /** Pin (or dot) colour. Default the marker blue. */
1454
+ colour?: string;
1455
+ /** Size preset. Default `"m"` (32 CSS px). */
1456
+ size?: MarkerSize;
1457
+ /**
1458
+ * Short text (1-3 characters) drawn in white in the pin head INSTEAD of
1459
+ * the glyph - numbered waypoints (`"1"`, `"2"`) and lettered stops
1460
+ * (`"A"`, `"B"`). Longer strings are cut to
1461
+ * {@link MAX_MARKER_TEXT_LENGTH}; blank strings fall back to the glyph.
1462
+ * Text always draws the pin body, so `icon: "dot"` is ignored when text
1463
+ * is given (a dot has no head to write in).
1464
+ */
1465
+ text?: string;
1466
+ }
1467
+ /**
1468
+ * Rasterise one marker to `ImageData` for `map.addImage`, per the
1469
+ * rendering contract: every glyph except `dot` is the coloured pin-body
1470
+ * teardrop with the glyph drawn in white centred on the head (anchor the
1471
+ * symbol `bottom` - the tip sits exactly on the canvas's bottom edge);
1472
+ * `dot` is a coloured circle with a white outline (anchor `center`). The
1473
+ * canvas is a square of twice the preset's CSS pixel size, returned with
1474
+ * `pixelRatio: 2`.
1475
+ *
1476
+ * Passing `text` switches on the short-text mode: the same coloured pin
1477
+ * body, with 1-3 white bold characters centred in the head (auto-shrunk to
1478
+ * fit) in place of the glyph. That is what numbered waypoints and lettered
1479
+ * stops are drawn with.
1480
+ *
1481
+ * Returns `null` where canvas 2D or `Path2D` is unavailable (never
1482
+ * expected in a browser; keeps non-DOM environments safe, the
1483
+ * `poi-icons.ts` pattern).
1484
+ */
1485
+ declare function renderMarkerImage(options?: RenderMarkerImageOptions): RenderedMarkerImage | null;
1486
+ /** The generated MapLibre ids (see {@link MarkersLayer.ids}). */
1487
+ interface MarkersLayerIds {
1488
+ /** The GeoJSON source id (`mm-user-markers`). */
1489
+ source: string;
1490
+ /** The symbol layer id (`mm-user-markers`). */
1491
+ layer: string;
1492
+ }
1493
+ /** Options for {@link MarkersLayer}. */
1494
+ interface MarkersLayerOptions {
1495
+ /**
1496
+ * Called when a marker's custom image passes the schema (right media
1497
+ * type, within the 64 KB cap) but MapLibre cannot rasterise it — most
1498
+ * often an SVG with no intrinsic `width`/`height`, which `<img>` renders
1499
+ * happily and `createImageBitmap` rejects. The marker keeps its glyph
1500
+ * pin either way; this is the only way to TELL the user that the logo
1501
+ * they can see in their editor will not be on the map.
1502
+ */
1503
+ onImageError?: (image: string, error: unknown) => void;
1504
+ }
1505
+ /**
1506
+ * Draws a theme's custom markers & labels on a MapLibre map.
1507
+ *
1508
+ * ```ts
1509
+ * const markers = await markersFromThemeUrl(themeUrl);
1510
+ * if (markers) new MarkersLayer(map, markers);
1511
+ * ```
1512
+ *
1513
+ * Registers every needed marker image (rasterised glyph pins are cached by
1514
+ * `mm-marker-<icon>-<colour>-<size>`; custom images by data-URI hash),
1515
+ * builds one GeoJSON feature per marker carrying its image key, label,
1516
+ * precomputed `icon-size` ratio and anchor, and installs the single
1517
+ * `mm-user-markers` source + symbol layer. Survives `setStyle` like
1518
+ * PlacesLayer (re-installed on every `style.load`).
1519
+ */
1520
+ declare class MarkersLayer {
1521
+ private readonly map;
1522
+ private readonly options;
1523
+ private items;
1524
+ private data;
1525
+ private destroyed;
1526
+ /**
1527
+ * Bumped by {@link clear} (and {@link destroy}). An image load started
1528
+ * before the bump resolves into a cleared layer, so its handler compares
1529
+ * the generation it was started in and drops out — without this a
1530
+ * pending `loadImage` would rebuild and RE-INSTALL the layer on a map the
1531
+ * caller had already cleaned up.
1532
+ */
1533
+ private generation;
1534
+ /** Custom images by cache key, once loaded (kept across style swaps). */
1535
+ private readonly loadedImages;
1536
+ /** Custom-image cache keys that failed to load (pin fallback, once). */
1537
+ private readonly failedImages;
1538
+ /** Custom-image cache keys with a load in flight. */
1539
+ private readonly pendingImages;
1540
+ /**
1541
+ * Every `map.addImage` key this layer currently owns, so a rebuild can
1542
+ * drop the ones nothing references any more and teardown removes exactly
1543
+ * what it added (the RouteLayer endpoints pattern). Without it every
1544
+ * icon/colour/size change leaked a 2x RGBA image — 25 KB per click at
1545
+ * the 40px preset — and re-invalidated the sprite atlas.
1546
+ */
1547
+ private readonly registeredImageKeys;
1548
+ constructor(map: MapMapMap | Map, markers?: MarkersBlock, options?: MarkersLayerOptions);
1549
+ private readonly handleStyleLoad;
1550
+ /**
1551
+ * Replace the layer's markers with a parsed block (`undefined` or an
1552
+ * empty block clears the map of markers but keeps the layer alive).
1553
+ * Custom images load asynchronously: their markers render as default
1554
+ * pins until the image arrives, then swap in place; a failed or
1555
+ * oversized image keeps the pin fallback.
1556
+ */
1557
+ setMarkers(markers: MarkersBlock | undefined): void;
1558
+ /** The layer's current markers. */
1559
+ get current(): MarkerItem[];
1560
+ /**
1561
+ * The MapLibre source/layer ids - public API for escape-hatch styling.
1562
+ * Both are the contract id `mm-user-markers`.
1563
+ */
1564
+ get ids(): MarkersLayerIds;
1565
+ /**
1566
+ * Rebuild the source data from the items and (re)install. Mirrors
1567
+ * PlacesLayer's never-drop-an-update rule: once the source exists the
1568
+ * data is applied immediately (even while `isStyleLoaded()` is
1569
+ * transiently false mid-render); before first installation the latest
1570
+ * data is stashed for the next `style.load`.
1571
+ */
1572
+ private rebuild;
1573
+ /** Build one feature, kicking off the custom-image load if needed. */
1574
+ private featureFor;
1575
+ /** Load a custom `data:` image once; rebuild the features when done. */
1576
+ private loadCustomImage;
1577
+ /**
1578
+ * Register every image the current features reference and drop the ones
1579
+ * they no longer do (idempotent). Runtime images survive a diffed
1580
+ * `setStyle` and are rebuilt after a full reload, so this runs on every
1581
+ * install as well as every data rebuild.
1582
+ */
1583
+ private registerImages;
1584
+ /** Add-or-update the images, source and layer on the current style. */
1585
+ private install;
1586
+ /**
1587
+ * Remove the markers' layer, source and registered images from the map.
1588
+ * Images are removed by the keys this layer REGISTERED, not by whatever
1589
+ * `items` happens to hold now — deriving them from the items meant
1590
+ * `setMarkers(undefined)` followed by `clear()`/`destroy()` removed
1591
+ * nothing at all.
1592
+ */
1593
+ clear(): void;
1594
+ /**
1595
+ * Remove the markers and detach the style.load listener. Call when
1596
+ * disposing of the layer (the map is left untouched). Any image load
1597
+ * still in flight resolves into a no-op.
1598
+ */
1599
+ destroy(): void;
1600
+ }
1601
+
1178
1602
  /**
1179
1603
  * RouteLayer - fetch an OSRM route from the MapMap gateway and draw it.
1180
1604
  *
1181
1605
  * The route request/parse logic lives in the pure `osrm` module; this class
1182
1606
  * owns the network call and the MapLibre source/layers (a signal-blue line
1183
- * with a darker casing, matching the brand).
1607
+ * with a darker casing, matching the brand), plus the optional overlays
1608
+ * that hang off a drawn route: alternatives, ferry dashes, the manoeuvre
1609
+ * arrow, average-speed corridors, and — with the `endpoints` option — the
1610
+ * branded start/destination/waypoint markers, drawn as a symbol layer over
1611
+ * their own source so they land in canvas exports and match the native
1612
+ * SDKs.
1184
1613
  */
1185
1614
 
1186
1615
  /** MapMap brand signal blue (see website `--signal`). */
@@ -1197,6 +1626,43 @@ interface RouteLineStyle {
1197
1626
  /** Casing opacity, 0-1. Set 0 to hide the casing entirely. */
1198
1627
  casingOpacity?: number;
1199
1628
  }
1629
+ /**
1630
+ * One designed endpoint marker: a coloured pin (or dot) drawn from the
1631
+ * shared marker rasteriser, optionally with a text label underneath.
1632
+ */
1633
+ interface EndpointMarker {
1634
+ /** Glyph drawn on the pin, or `"dot"` for a plain circle. */
1635
+ icon?: MarkerGlyphId;
1636
+ /**
1637
+ * Short text (1-3 characters) drawn in the pin head INSTEAD of the
1638
+ * glyph - `"A"`, `"B"`, `"1"`. Overrides `icon`.
1639
+ */
1640
+ text?: string;
1641
+ /** Marker colour, any CSS colour the canvas accepts. */
1642
+ colour?: string;
1643
+ /** Size preset: `"s"` 24 px, `"m"` 32 px, `"l"` 40 px. */
1644
+ size?: MarkerSize;
1645
+ /** Optional caption drawn below the marker, with a white halo. */
1646
+ label?: string;
1647
+ }
1648
+ /**
1649
+ * How a route's start, end and intermediate waypoints are drawn. Every
1650
+ * field is optional; `false` switches an endpoint off entirely.
1651
+ */
1652
+ interface EndpointsDesign {
1653
+ /** The first route coordinate. Default a green `dot`, size `m`. */
1654
+ start?: EndpointMarker | false;
1655
+ /** The last route coordinate. Default a signal-blue `pin`, size `m`. */
1656
+ end?: EndpointMarker | false;
1657
+ /** Every intermediate stop. Default a signal-blue `pin`, size `s`. */
1658
+ waypoint?: EndpointMarker | false;
1659
+ /**
1660
+ * Draw each waypoint's 1-based index in its pin head (`1`, `2`, `3`)
1661
+ * instead of the glyph. Default `true`. An explicit `waypoint.text`
1662
+ * wins over the number.
1663
+ */
1664
+ numberWaypoints?: boolean;
1665
+ }
1200
1666
  interface RouteLayerOptions {
1201
1667
  /** Gateway base URL. Defaults to the map's `baseUrl` when given a map. */
1202
1668
  baseUrl?: string;
@@ -1229,6 +1695,45 @@ interface RouteLayerOptions {
1229
1695
  alternativeColor?: string;
1230
1696
  /** Colour of the ferry dashes drawn over the selected route. */
1231
1697
  ferryColor?: string;
1698
+ /**
1699
+ * Branded start/end/waypoint markers on the drawn route. Off by default:
1700
+ * a layer built without this option behaves exactly as it always has.
1701
+ *
1702
+ * `true` uses the brand defaults (green `dot` at the start, blue `pin`
1703
+ * at the end, smaller numbered blue pins at each intermediate stop); an
1704
+ * {@link EndpointsDesign} overrides any of them, and `false` on one
1705
+ * endpoint drops just that one.
1706
+ *
1707
+ * The coordinates come from the route itself: first coordinate = start,
1708
+ * last = end. Intermediate stops are remembered from
1709
+ * {@link RouteLayer.routePath}'s via points, or set explicitly with
1710
+ * {@link RouteLayer.setWaypoints} when drawing a route parsed elsewhere.
1711
+ *
1712
+ * These are symbol-layer markers, not DOM `maplibregl.Marker`s, so they
1713
+ * appear in canvas exports and match the native SDKs.
1714
+ */
1715
+ endpoints?: boolean | EndpointsDesign;
1716
+ }
1717
+ /** The MapLibre ids a {@link RouteLayer} owns (see {@link RouteLayer.ids}). */
1718
+ interface RouteLayerIds {
1719
+ /** GeoJSON source holding the route line. */
1720
+ source: string;
1721
+ /** Line layer drawn under the route (the darker casing). */
1722
+ casing: string;
1723
+ /** Line layer for the route itself. */
1724
+ line: string;
1725
+ /** GeoJSON source for the manoeuvre arrow. */
1726
+ maneuverSource: string;
1727
+ /** Symbol layer for the manoeuvre arrow. */
1728
+ maneuver: string;
1729
+ /** GeoJSON source for the average-speed corridors. */
1730
+ corridorSource: string;
1731
+ /** Line layer tinting the average-speed corridors. */
1732
+ corridor: string;
1733
+ /** GeoJSON source for the start/end/waypoint markers. */
1734
+ endpointsSource: string;
1735
+ /** Symbol layer drawing the start/end/waypoint markers. */
1736
+ endpoints: string;
1232
1737
  }
1233
1738
  /** Draws MapMap routes on a MapLibre map. */
1234
1739
  declare class RouteLayer {
@@ -1246,6 +1751,14 @@ declare class RouteLayer {
1246
1751
  private readonly arrowImageId;
1247
1752
  private readonly corridorSourceId;
1248
1753
  private readonly corridorLayerId;
1754
+ private readonly endpointsSourceId;
1755
+ private readonly endpointsLayerId;
1756
+ /** The resolved endpoint design, or `undefined` when switched off. */
1757
+ private readonly endpoints;
1758
+ /** Intermediate stops for the endpoint markers, `[lng, lat]` each. */
1759
+ private waypoints;
1760
+ /** Marker images this layer registered, so teardown can remove them. */
1761
+ private readonly endpointImageKeys;
1249
1762
  private readonly design;
1250
1763
  private alertsDesign;
1251
1764
  private readonly progressColor;
@@ -1268,15 +1781,39 @@ declare class RouteLayer {
1268
1781
  private progress;
1269
1782
  private maneuver;
1270
1783
  private corridors;
1784
+ /**
1785
+ * Monotonic id of the newest route request. A response only draws when it
1786
+ * still owns this value - see {@link RouteLayer.routePath}.
1787
+ */
1788
+ private requestSeq;
1789
+ /** Whether the alternative-line click listener is currently attached. */
1790
+ private altClickBound;
1271
1791
  constructor(map: MapMapMap | Map, options?: RouteLayerOptions);
1272
1792
  private readonly handleStyleLoad;
1793
+ /**
1794
+ * The MapLibre source and layer ids this layer owns - public API for
1795
+ * escape-hatch styling and layer ordering (`map.moveLayer`). All are
1796
+ * derived from the `id` option (default `mapmap-route`).
1797
+ */
1798
+ get ids(): RouteLayerIds;
1273
1799
  /**
1274
1800
  * Route from `from` to `to` (with optional intermediate `via` points passed
1275
1801
  * through `options` is not supported here - pass a coordinate list to
1276
1802
  * {@link routePath} for that), draw the line, and return the parsed route.
1277
1803
  */
1278
1804
  route(from: LngLatLike, to: LngLatLike, options?: RouteOptions): Promise<ParsedRoute>;
1279
- /** Route through an ordered list of coordinates and draw the result. */
1805
+ /**
1806
+ * Route through an ordered list of coordinates and draw the result.
1807
+ *
1808
+ * Concurrent calls are ordered by ISSUE, not by arrival: only the newest
1809
+ * request draws. A destination retyped while the first request is still in
1810
+ * flight used to leave the map showing whichever response happened to land
1811
+ * last, so a driver could be looking at the line to the address they had
1812
+ * already replaced. Each caller still receives its own parsed route, so a
1813
+ * caller that wants the data of a superseded request keeps getting it -
1814
+ * only the drawing is suppressed. {@link RouteLayer.clear} likewise
1815
+ * supersedes anything in flight.
1816
+ */
1280
1817
  routePath(points: LngLatLike[], options?: RouteOptions): Promise<ParsedRoute>;
1281
1818
  /** The most recently drawn route, if any. */
1282
1819
  get current(): ParsedRoute | undefined;
@@ -1288,6 +1825,33 @@ declare class RouteLayer {
1288
1825
  draw(route: ParsedRoute): void;
1289
1826
  /** Add-or-update the source and layers for a route on the current style. */
1290
1827
  private install;
1828
+ /**
1829
+ * Set the intermediate stops drawn by the endpoint markers, in order.
1830
+ * {@link routePath} fills these in from its own via points, so this is
1831
+ * only needed when drawing a route parsed elsewhere (e.g. a route fetched
1832
+ * by the app and handed to {@link draw}). An empty array clears them.
1833
+ */
1834
+ setWaypoints(waypoints: readonly LngLatLike[]): void;
1835
+ /** The intermediate stops currently drawn, `[lng, lat]` each. */
1836
+ get currentWaypoints(): [number, number][];
1837
+ /**
1838
+ * Build one Point feature per endpoint marker: start (first route
1839
+ * coordinate), each waypoint in order, then end (last coordinate). The
1840
+ * end is added last so it wins MapLibre's symbol-collision ordering when
1841
+ * markers overlap on a short route.
1842
+ */
1843
+ private endpointFeatures;
1844
+ /**
1845
+ * Register every endpoint marker image the current features need
1846
+ * (idempotent; `setStyle` wipes runtime images, so this runs on each
1847
+ * install). Keys are remembered so teardown removes exactly what this
1848
+ * layer added.
1849
+ */
1850
+ private registerEndpointImages;
1851
+ /** Add-or-update the endpoint markers' source and symbol layer. */
1852
+ private installEndpoints;
1853
+ /** Remove the endpoint markers, their source and their images. */
1854
+ private clearEndpoints;
1291
1855
  /**
1292
1856
  * Draw a bare line from coordinates, without a parsed OSRM route.
1293
1857
  *
@@ -1393,7 +1957,11 @@ declare class RouteLayer {
1393
1957
  clearAlertCorridors(): void;
1394
1958
  /** Add-or-update the corridor source/layer for the current style. */
1395
1959
  private installCorridors;
1396
- /** Remove the route's layers and source from the map. */
1960
+ /**
1961
+ * Remove the route's layers and source from the map. Any route request
1962
+ * still in flight is superseded, so a late response cannot redraw a route
1963
+ * the caller has already cleared.
1964
+ */
1397
1965
  clear(): void;
1398
1966
  /**
1399
1967
  * Remove the route and detach the layer's `style.load` listener. Call
@@ -1685,7 +2253,11 @@ interface UploadProbeOptions {
1685
2253
  backoffMs?: (attempt: number) => number;
1686
2254
  /** Injectable delay, for deterministic tests (default `setTimeout`). */
1687
2255
  sleep?: (ms: number) => Promise<void>;
1688
- /** Abort signal forwarded to fetch. */
2256
+ /**
2257
+ * Abort signal forwarded to fetch. Once it has aborted the retry loop
2258
+ * stops immediately (an abort is a caller decision, not a transient
2259
+ * failure) and the upload resolves `"gaveUp"`.
2260
+ */
1689
2261
  signal?: AbortSignal;
1690
2262
  }
1691
2263
  /** The `/v1/probe` endpoint for a gateway origin (trailing slashes trimmed). */
@@ -1693,8 +2265,9 @@ declare function buildProbeUrl(baseUrl: string): string;
1693
2265
  /**
1694
2266
  * POST one probe body to `{baseUrl}/v1/probe` with a bearer key. Transient
1695
2267
  * failures (network or `5xx`) are retried with bounded backoff; permanent ones
1696
- * (`400`/`403`/`501`) are not. Resolves with the terminal outcome and never
1697
- * rejects, so a fire-and-forget `void uploadProbeBatch(...)` is safe.
2268
+ * (`400`/`403`/`501`) are not, and neither is an aborted `signal`. Resolves
2269
+ * with the terminal outcome and never rejects, so a fire-and-forget
2270
+ * `void uploadProbeBatch(...)` is safe.
1698
2271
  *
1699
2272
  * @param body the string returned by `GuidanceSession.finishProbeJson()`.
1700
2273
  */
@@ -1763,6 +2336,33 @@ interface PlacesIcon {
1763
2336
  /** MapLibre `icon-size` scale factor. Defaults to `1`. */
1764
2337
  size?: number;
1765
2338
  }
2339
+ /**
2340
+ * Options for the per-place name labels (see `PlacesLayerOptions.label`).
2341
+ *
2342
+ * The colour keys accept BOTH spellings: `colour`/`haloColour` (British,
2343
+ * as first shipped) and `color`/`haloColor` (matching the sibling
2344
+ * `color`/`clusterColor` options on {@link PlacesLayerOptions}). A
2345
+ * dynamically built options object should not lose its labels' colours to
2346
+ * a spelling mismatch. British wins if somehow both are given.
2347
+ */
2348
+ interface PlacesLabelOptions {
2349
+ /**
2350
+ * Feature property holding the label text (see {@link Place.properties}
2351
+ * for what is addressable). Defaults to `"name"` - every place carries
2352
+ * its `name` there.
2353
+ */
2354
+ property?: string;
2355
+ /** Text size in px. Defaults to `12`. */
2356
+ size?: number;
2357
+ /** Text colour. Defaults to a dark ink (`#333333`). Alias: `color`. */
2358
+ colour?: string;
2359
+ /** Text colour, American spelling of {@link PlacesLabelOptions.colour}. */
2360
+ color?: string;
2361
+ /** Halo colour behind the text. Defaults to white. Alias: `haloColor`. */
2362
+ haloColour?: string;
2363
+ /** Halo colour, American spelling of {@link PlacesLabelOptions.haloColour}. */
2364
+ haloColor?: string;
2365
+ }
1766
2366
  /** Options for {@link PlacesLayer.nearestByDriveTime}. */
1767
2367
  interface NearestByDriveTimeOptions {
1768
2368
  /** How many places to return. Defaults to `1`. */
@@ -1806,8 +2406,29 @@ interface PlacesLayerOptions {
1806
2406
  /**
1807
2407
  * Custom pin image for unclustered places (a symbol layer instead of the
1808
2408
  * default circle). If the image fails to load, the circle look is used.
2409
+ *
2410
+ * Also accepts a record of images keyed by a feature-property value for
2411
+ * per-place icons - e.g. `{ cafe: { url: … }, fuel: { url: … } }` picks
2412
+ * an image per place from the property named by `iconProperty` (default
2413
+ * `"kind"`). Places whose property value has no entry - or whose entry's
2414
+ * image fails to load - keep the default circle look.
2415
+ */
2416
+ icon?: PlacesIcon | Record<string, PlacesIcon>;
2417
+ /**
2418
+ * Feature property that selects a per-place icon when `icon` is a
2419
+ * record (ignored for a single icon). Defaults to `"kind"`. Set place
2420
+ * `properties` values to the record's keys.
2421
+ */
2422
+ iconProperty?: string;
2423
+ /**
2424
+ * Per-place name labels beside the points. `true` labels every place
2425
+ * with its `name`; an options object customises the property, size and
2426
+ * colours (see {@link PlacesLabelOptions}). Labels are drawn in the map
2427
+ * font with a halo, hide before they collide (`text-optional`), and
2428
+ * only ever appear on unclustered points. Omit for no labels (today's
2429
+ * behaviour).
1809
2430
  */
1810
- icon?: PlacesIcon;
2431
+ label?: boolean | PlacesLabelOptions;
1811
2432
  /**
1812
2433
  * Fit the map to the places the first time they are set. Defaults to
1813
2434
  * `false` (the map view is left alone).
@@ -1841,6 +2462,13 @@ interface PlacesLayerIds {
1841
2462
  clusters: string;
1842
2463
  /** The cluster count badge layer id (only installed with `cluster: true`). */
1843
2464
  clusterCounts: string;
2465
+ /** The labels symbol layer id (only installed with a `label` option). */
2466
+ labels: string;
2467
+ /**
2468
+ * The circle layer drawn under places without a loaded per-place icon
2469
+ * (only installed when `icon` is a record of images).
2470
+ */
2471
+ pointsFallback: string;
1844
2472
  }
1845
2473
  /** Options for {@link PlacesLayer.select}. */
1846
2474
  interface PlacesSelectOptions {
@@ -1874,13 +2502,20 @@ declare class PlacesLayer {
1874
2502
  private readonly clustersLayerId;
1875
2503
  private readonly clusterCountLayerId;
1876
2504
  private readonly pointsLayerId;
2505
+ private readonly pointsFallbackLayerId;
2506
+ private readonly labelsLayerId;
1877
2507
  private readonly iconImageId;
1878
2508
  private readonly cluster;
1879
2509
  private readonly clusterRadius;
1880
2510
  private readonly clusterMaxZoom;
1881
2511
  private readonly color;
1882
2512
  private readonly clusterColor;
1883
- private readonly icon;
2513
+ private readonly singleIcon;
2514
+ private readonly iconRecord;
2515
+ private readonly iconProperty;
2516
+ private readonly label;
2517
+ /** Per-place icon keys whose image failed to load (circle fallback). */
2518
+ private readonly failedIconKeys;
1884
2519
  private readonly wantFitBounds;
1885
2520
  private readonly onPlaceClick;
1886
2521
  private readonly popupFn;
@@ -1944,6 +2579,11 @@ declare class PlacesLayer {
1944
2579
  * attached; unreachable places (`null` duration) are dropped. Needs the
1945
2580
  * gateway `baseUrl`/`apiKey` - picked up from a `MapMapMap` automatically,
1946
2581
  * or pass them as layer options.
2582
+ *
2583
+ * Answers always describe the places that were asked about: the list is
2584
+ * snapshotted at call time, so a `setPlaces` landing mid-flight (a
2585
+ * search-as-you-type filter, say) cannot mis-pair the matrix rows with a
2586
+ * different set of places.
1947
2587
  */
1948
2588
  nearestByDriveTime(origin: {
1949
2589
  lat: number;
@@ -1951,6 +2591,13 @@ declare class PlacesLayer {
1951
2591
  }, options?: NearestByDriveTimeOptions): Promise<PlaceWithDriveTime[]>;
1952
2592
  /** Add-or-update the source and layers on the current style. */
1953
2593
  private install;
2594
+ /**
2595
+ * The per-place name labels layer (only with a `label` option): the map
2596
+ * font with a halo, anchored just below each unclustered point, hidden
2597
+ * before colliding (`text-optional`). Re-installed with the rest of the
2598
+ * layers on every `style.load`.
2599
+ */
2600
+ private installLabelsLayer;
1954
2601
  /** Cluster circles (sized by count) and the count badge on top. */
1955
2602
  private installClusterLayers;
1956
2603
  /**
@@ -1959,6 +2606,17 @@ declare class PlacesLayer {
1959
2606
  * circle look if it fails, like PositionPuck's image fallback).
1960
2607
  */
1961
2608
  private installPointsLayer;
2609
+ /** The `map.addImage` id for one key of the per-place icon record. */
2610
+ private iconImageIdFor;
2611
+ /**
2612
+ * Per-place icon mode: load every record image (each key independently -
2613
+ * one bad URL never sinks the rest), then install a symbol layer whose
2614
+ * `icon-image` is a match expression on `iconProperty`, plus a circle
2615
+ * fallback layer for places whose property value has no loaded icon.
2616
+ */
2617
+ private installPerPlaceIconLayers;
2618
+ /** Install the per-place icon symbol layer + circle fallback layer. */
2619
+ private addPerPlaceLayers;
1962
2620
  private addCirclePointsLayer;
1963
2621
  private addIconPointsLayer;
1964
2622
  /** Open the built-in popup for a place, replacing any previous one. */
@@ -2295,7 +2953,15 @@ declare class IsochroneLayer {
2295
2953
  showReachability(options: ShowReachabilityOptions): Promise<IsochroneFeatureCollection>;
2296
2954
  /** The most recently drawn rings, if any. */
2297
2955
  get current(): IsochroneFeatureCollection | undefined;
2298
- /** Draw (or update) a ring collection. Safe before the style has loaded. */
2956
+ /**
2957
+ * Draw (or update) a ring collection. Safe before the style has loaded.
2958
+ * Never silently drops an update: once the source exists the data is
2959
+ * applied immediately, even while `isStyleLoaded()` is transiently
2960
+ * `false` mid-render (PlacesLayer has the same rule) - otherwise the map
2961
+ * would keep showing the previous rings until the next style swap.
2962
+ * Calls made before the source has first been installed are stashed in
2963
+ * `lastData` - the latest wins - and installed on the next `style.load`.
2964
+ */
2299
2965
  private draw;
2300
2966
  /** Add-or-update the source and ring layers on the current style. */
2301
2967
  private install;
@@ -2460,13 +3126,31 @@ interface LaneIndication {
2460
3126
  valid?: boolean;
2461
3127
  /** Whether guidance recommends this lane. */
2462
3128
  active?: boolean;
3129
+ /**
3130
+ * Which one of {@link directions} guidance actually takes from this lane,
3131
+ * when the server resolves it (the gateway's `active_direction`, set only
3132
+ * on active lanes whose mask decodes to a single indication).
3133
+ *
3134
+ * A lane may permit several movements — "straight or right" is the common
3135
+ * one — and only this field says which of them the route uses. Draw the
3136
+ * lane arrow from it, falling back to the first permitted direction: see
3137
+ * {@link laneArrowDirection}.
3138
+ */
3139
+ activeDirection?: string;
2463
3140
  }
2464
- /** A typed fragment of banner content. */
3141
+ /**
3142
+ * A typed fragment of banner content.
3143
+ *
3144
+ * Field names are the wire names, because these objects come straight off
3145
+ * `route.raw` without a mapping pass (see {@link extractGuidance}).
3146
+ */
2465
3147
  interface BannerComponent {
2466
3148
  type: string;
2467
3149
  text: string;
2468
3150
  directions?: string[];
2469
3151
  active?: boolean;
3152
+ /** Which of `directions` guidance recommends (gateway `active_direction`). */
3153
+ active_direction?: string;
2470
3154
  }
2471
3155
  /** One banner line. */
2472
3156
  interface BannerContent {
@@ -2539,6 +3223,18 @@ declare function ssmlToText(ssml: string): string;
2539
3223
  * `valid` set.
2540
3224
  */
2541
3225
  declare function bannerLanes(banner: BannerInstruction): LaneIndication[];
3226
+ /**
3227
+ * The single direction a lane's arrow should show.
3228
+ *
3229
+ * A lane that permits several movements ("straight or right") carries them
3230
+ * all in `directions`, in the gateway's bitmask order, which is NOT the
3231
+ * order the route cares about. Drawing `directions[0]` therefore shows a
3232
+ * straight-ahead arrow on the lane the driver is being told to turn right
3233
+ * from. `activeDirection` is the one the route actually takes, so it wins;
3234
+ * the first permitted direction is only the fallback for lanes where the
3235
+ * server could not resolve one.
3236
+ */
3237
+ declare function laneArrowDirection(lane: LaneIndication): string;
2542
3238
  /**
2543
3239
  * Unicode arrow for a lane/maneuver direction (OSRM indication strings).
2544
3240
  * A text-only fallback; sprite-based lane icons ship with the map assets.
@@ -2676,9 +3372,10 @@ declare function cameraKindsShownOnMap(design: NavAlertsDesign, mode?: MapDispla
2676
3372
  * floor and per-mode visibility.
2677
3373
  *
2678
3374
  * The features must carry a `kind` property holding one of
2679
- * {@link NAV_CAMERA_KINDS} (or the gateway's `redlight` spelling); an
2680
- * unrecognised value falls back to the `unknown` icon, never to a guess at
2681
- * a specific camera type.
3375
+ * {@link NAV_CAMERA_KINDS} or any wire spelling {@link navCameraKind}
3376
+ * accepts (the gateway's `redlight`, OSM's `traffic_signals`, …); an
3377
+ * unrecognised or absent value falls back to the `unknown` icon, never to a
3378
+ * guess at a specific camera type.
2682
3379
  *
2683
3380
  * @param source GeoJSON source id holding the camera features.
2684
3381
  * @param variant Sprite variant to draw: `"light"` icons read on a dark
@@ -2901,4 +3598,4 @@ declare class VoiceGuidance {
2901
3598
  private playEarcon;
2902
3599
  }
2903
3600
 
2904
- export { ALERT_CONTRAST_MIN, AdrCheck, type AdrCheckOptions, type AdrCheckRequest, type AdrCheckResult, type AdrDimensions, type AdrTunnelCategory, type AlertChipContent, type AlertContrastIssue, type AlertPresentation, type BannerComponent, type BannerContent, type BannerInstruction, type BuildStyleOptions, type CameraAlert, CameraAlertChip, type CameraFix, DEFAULT_GLYPHS_URL, DEFAULT_TERRITORY_TILES_URL, type DiagnosticIssue, EFFECTS_METADATA_KEY, FLOW_DEFAULTS, FULL_ATTRIBUTION, FlowRouteEffectLayer, type FlythroughController, type FlythroughOptions, type FlythroughPose, GuidanceBanner, type GuidanceSeverity, type GuidanceSpokenPrompt, type IsochroneFeatureCollection, IsochroneLayer, type IsochroneLayerOptions, LOGO_SVG, type LaneIndication, type LayerOverride, type LngLatLike, LogoControl, type LogoOptions, type LogoPosition, MAX_PUCK_IMAGE_BYTES, type MapDiagnosticsInput, type MapDisplayMode, MapMapMap, type MapMapOptions, type MapMapTheme, NAV_ALERT_AUDIO_MODES, NAV_ALERT_CHIP_POSITIONS, NAV_ALERT_ICON_SETS, NAV_ALERT_LEAD_DISTANCES, NAV_ALERT_LEAD_DISTANCE_M, NAV_ALERT_MODES, NAV_ALERT_SOUNDS, NAV_ALERT_SOUND_BASE_URL, NAV_ALERT_SOUND_FILES, NAV_CAMERA_DEFAULTS, NAV_CAMERA_KINDS, type NavAlertAudioMode, type NavAlertChipPosition, type NavAlertIconSet, type NavAlertKindDesign, type NavAlertLeadDistance, type NavAlertMode, type NavAlertSound, type NavAlertsDesign, type NavBannerDesign, type NavCameraDesign, type NavCameraKind, type NavDesign, type NavPuckDesign, type NavRouteDesign, NavigationCamera, type NavigationCameraMode, type NavigationCameraOptions, type NearestByDriveTimeOptions, OPENMAPTILES_ATTRIBUTION, OSM_ATTRIBUTION, PALETTE_SLOTS, POI_CATEGORY_COLORS, POI_CATEGORY_IDS, POI_CLASS_CATEGORIES, type ParsedRoute, type Place, type PlacePointFeature, type PlaceWithDistance, type PlaceWithDriveTime, type PlacesFeatureCollection, type PlacesIcon, type PlacesInput, PlacesLayer, type PlacesLayerIds, type PlacesLayerOptions, type PlacesSelectOptions, type PoiCategoryDesign, type PoiDesign, PositionPuck, type PositionPuckOptions, type ProbeUploadOutcome, RIBBON_FLOATS_PER_VERTEX, ROUTE_EFFECTS, type ReachabilityMode, type RibbonMesh, type RouteEffectLayer, type RouteEffectName, type RouteFlowOptions, type RouteGeometry, RouteLayer, type RouteLayerOptions, type RouteLineStyle, type RouteOptions, type RouteProfile, SIGNAL_BLUE, SOURCE_LAYERS, type SetRouteEffectOptions, type SeverityProsody, type ShowReachabilityOptions, type SpeakOptions, type StepGuidance, type SunTimes, type Theme, type ThemeEffects, ThemeScheduler, type ThemeSchedulerOptions, type TruckParams, type UploadProbeOptions, VoiceGuidance, type VoiceGuidanceOptions, type VoiceGuidanceUpdate, type VoiceInstruction, alertChipContent, alertContrastIssues, alertIconSvg, alertIconUrl, alertPresentation, alertSoundUrl, alertSpeedReadoutColor, applyPoiDesign, bannerLanes, bearingBetween, bindFlythroughToScroll, buildAdrCheckBody, buildProbeUrl, buildRouteQuery, buildRouteUrl, buildStyle, builtInPoiColor, cameraKindLabel, cameraKindsShownOnMap, cameraShownOnMap, cameraSpriteName, cameraSymbolLayer, contrastRatio, createMap, createRouteEffect, defaultNavAlertsDesign, defaultNavDesign, defaultPoiDesign, directionArrow, effectsFromStyleMetadata, extractGuidance, flythrough, flythroughPose, formatCoord, formatCoords, haversineDistanceM, isNameTextField, languageTextField, lngLatToMercator, navAlertLeadDistanceM, navAlertSoundUrl, navCameraKind, navDesignFromTheme, navDesignFromThemeUrl, parseCssColour, parseHexColor, parseNavAlertsDesign, parseNavDesign, parseOsrmRoute, parsePoiDesign, placesFromGeoJSON, poiDesignFromTheme, poiDesignFromThemeUrl, poiDesignIsDefault, poiTextColorExpression, prefersReducedMotion, registerPmtilesProtocol, relativeLuminance, resetDiagnostics, resolveTheme, runMapDiagnostics, setMapLanguage, severityProsody, shortestArcDeg, shortestArcDelta, speak, ssmlToText, sunTimes, tessellateRouteRibbon, toLngLat, toPmtilesUrl, uploadProbeBatch };
3601
+ export { ALERT_CONTRAST_MIN, AdrCheck, type AdrCheckOptions, type AdrCheckRequest, type AdrCheckResult, type AdrDimensions, type AdrTunnelCategory, type AlertChipContent, type AlertContrastIssue, type AlertPresentation, type BannerComponent, type BannerContent, type BannerInstruction, type BuildStyleOptions, type CameraAlert, CameraAlertChip, type CameraFix, DEFAULT_GLYPHS_URL, DEFAULT_TERRITORY_TILES_URL, type DiagnosticIssue, EFFECTS_METADATA_KEY, type EndpointMarker, type EndpointsDesign, FLOW_DEFAULTS, FULL_ATTRIBUTION, FlowRouteEffectLayer, type FlythroughController, type FlythroughOptions, type FlythroughPose, GuidanceBanner, type GuidanceSeverity, type GuidanceSpokenPrompt, type IsochroneFeatureCollection, IsochroneLayer, type IsochroneLayerOptions, LOGO_SVG, type LaneIndication, type LayerOverride, type LngLatLike, LogoControl, type LogoOptions, type LogoPosition, MARKERS_ID, MARKER_DEFAULT_COLOUR, MARKER_GLYPH_IDS, MARKER_GLYPH_VIEWBOX, MARKER_LABEL_TEXT_SIZES, MARKER_PIN_BODY_PATH, MARKER_SIZES, MARKER_SIZES_PX, MAX_MARKER_ID_LENGTH, MAX_MARKER_IMAGE_BYTES, MAX_MARKER_ITEMS, MAX_MARKER_LABEL_LENGTH, MAX_MARKER_TEXT_LENGTH, MAX_PUCK_IMAGE_BYTES, type MapDiagnosticsInput, type MapDisplayMode, MapMapMap, type MapMapOptions, type MapMapTheme, type MarkerGlyphId, type MarkerItem, type MarkerSize, type MarkersBlock, MarkersLayer, type MarkersLayerIds, type MarkersLayerOptions, NAV_ALERT_AUDIO_MODES, NAV_ALERT_CHIP_POSITIONS, NAV_ALERT_ICON_SETS, NAV_ALERT_LEAD_DISTANCES, NAV_ALERT_LEAD_DISTANCE_M, NAV_ALERT_MODES, NAV_ALERT_SOUNDS, NAV_ALERT_SOUND_BASE_URL, NAV_ALERT_SOUND_FILES, NAV_CAMERA_DEFAULTS, NAV_CAMERA_KINDS, type NavAlertAudioMode, type NavAlertChipPosition, type NavAlertIconSet, type NavAlertKindDesign, type NavAlertLeadDistance, type NavAlertMode, type NavAlertSound, type NavAlertsDesign, type NavBannerDesign, type NavCameraDesign, type NavCameraKind, type NavDesign, type NavPuckDesign, type NavRouteDesign, NavigationCamera, type NavigationCameraMode, type NavigationCameraOptions, type NearestByDriveTimeOptions, OPENMAPTILES_ATTRIBUTION, OSM_ATTRIBUTION, PALETTE_SLOTS, POI_CATEGORY_COLORS, POI_CATEGORY_IDS, POI_CLASS_CATEGORIES, type ParsedRoute, type Place, type PlacePointFeature, type PlaceWithDistance, type PlaceWithDriveTime, type PlacesFeatureCollection, type PlacesIcon, type PlacesInput, type PlacesLabelOptions, PlacesLayer, type PlacesLayerIds, type PlacesLayerOptions, type PlacesSelectOptions, type PoiCategoryDesign, type PoiDesign, PositionPuck, type PositionPuckOptions, type ProbeUploadOutcome, RIBBON_FLOATS_PER_VERTEX, ROUTE_EFFECTS, type ReachabilityMode, type RenderMarkerImageOptions, type RenderedMarkerImage, type RibbonMesh, type RouteEffectLayer, type RouteEffectName, type RouteFlowOptions, type RouteGeometry, RouteLayer, type RouteLayerIds, type RouteLayerOptions, type RouteLineStyle, type RouteOptions, type RouteProfile, SIGNAL_BLUE, SOURCE_LAYERS, type SetRouteEffectOptions, type SeverityProsody, type ShowReachabilityOptions, type SpeakOptions, type StepGuidance, type SunTimes, type Theme, type ThemeEffects, ThemeScheduler, type ThemeSchedulerOptions, type TruckParams, type UploadProbeOptions, VoiceGuidance, type VoiceGuidanceOptions, type VoiceGuidanceUpdate, type VoiceInstruction, alertChipContent, alertContrastIssues, alertIconSvg, alertIconUrl, alertPresentation, alertSoundUrl, alertSpeedReadoutColor, applyPoiDesign, bannerLanes, bearingBetween, bindFlythroughToScroll, buildAdrCheckBody, buildProbeUrl, buildRouteQuery, buildRouteUrl, buildStyle, builtInPoiColor, cameraKindLabel, cameraKindsShownOnMap, cameraShownOnMap, cameraSpriteName, cameraSymbolLayer, contrastRatio, createMap, createRouteEffect, defaultNavAlertsDesign, defaultNavDesign, defaultPoiDesign, directionArrow, effectsFromStyleMetadata, extractGuidance, flythrough, flythroughPose, formatCoord, formatCoords, haversineDistanceM, isMarkerGlyphId, isNameTextField, laneArrowDirection, languageTextField, lngLatToMercator, markerGlyphPaths, markerImage, markerImageDataKey, markerImageKey, markerText, markerTextImageKey, markersFromTheme, markersFromThemeUrl, navAlertLeadDistanceM, navAlertSoundUrl, navCameraKind, navDesignFromTheme, navDesignFromThemeUrl, parseCssColour, parseHexColor, parseMarkers, parseNavAlertsDesign, parseNavDesign, parseOsrmRoute, parsePoiDesign, placesFromGeoJSON, poiDesignFromTheme, poiDesignFromThemeUrl, poiDesignIsDefault, poiTextColorExpression, prefersReducedMotion, registerPmtilesProtocol, relativeLuminance, renderMarkerImage, resetDiagnostics, resolveTheme, runMapDiagnostics, setMapLanguage, severityProsody, shortestArcDeg, shortestArcDelta, speak, ssmlToText, sunTimes, tessellateRouteRibbon, toLngLat, toPmtilesUrl, truncateChars, uploadProbeBatch };