@mapmap/maps 0.1.0 → 0.3.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,4 +1,259 @@
1
- import maplibregl, { StyleSpecification, Map, MapOptions } from 'maplibre-gl';
1
+ import maplibregl, { CustomLayerInterface, Map, StyleSpecification, MapOptions, ExpressionSpecification } from 'maplibre-gl';
2
+
3
+ /**
4
+ * Shared public types for @mapmap/maps.
5
+ *
6
+ * These are runtime-free (type-only) so the pure logic modules that import
7
+ * them stay loadable in Node without a browser or maplibre-gl.
8
+ */
9
+ /** A longitude/latitude pair, accepted in the three common shapes. */
10
+ type LngLatLike = [number, number] | {
11
+ lng: number;
12
+ lat: number;
13
+ } | {
14
+ lon: number;
15
+ lat: number;
16
+ };
17
+ /** OSRM routing profile. MapMap ships `driving`, `walking` and `truck`. */
18
+ type RouteProfile = "driving" | "walking" | "truck" | (string & {});
19
+ /**
20
+ * Truck / ADR vehicle parameters forwarded to the gateway's OSRM truck
21
+ * vendor extensions. All optional; only `truck`-profile requests honour them.
22
+ */
23
+ interface TruckParams {
24
+ /** Vehicle height, metres. */
25
+ heightM?: number;
26
+ /** Vehicle width, metres. */
27
+ widthM?: number;
28
+ /** Vehicle length, metres. */
29
+ lengthM?: number;
30
+ /** Gross weight, tonnes. */
31
+ weightT?: number;
32
+ /** Carrying dangerous goods (ADR). */
33
+ hazmat?: boolean;
34
+ /**
35
+ * ADR 8.6.4 tunnel restriction code, e.g. `"C"` or `"B/D"`. The slash is
36
+ * URL-encoded automatically.
37
+ */
38
+ tunnelCode?: string;
39
+ }
40
+ /** Options for a single route request. */
41
+ interface RouteOptions {
42
+ /** Routing profile. Defaults to `"driving"`. */
43
+ profile?: RouteProfile;
44
+ /** Truck / ADR parameters (only meaningful with `profile: "truck"`). */
45
+ truck?: TruckParams;
46
+ /** Request spoken `voiceInstructions` on each step (see `guidance.ts`). */
47
+ voice?: boolean;
48
+ /** Request visual `bannerInstructions` (and lane data) on each step. */
49
+ banner?: boolean;
50
+ /** BCP 47 narration language, e.g. `"en-GB"`. */
51
+ language?: string;
52
+ }
53
+ /** A parsed GeoJSON LineString geometry (`[lng, lat]` positions). */
54
+ interface RouteGeometry {
55
+ type: "LineString";
56
+ coordinates: [number, number][];
57
+ }
58
+ /** A parsed OSRM route, normalised to the fields callers actually use. */
59
+ interface ParsedRoute {
60
+ /** Total distance, metres. */
61
+ distanceM: number;
62
+ /** Total duration, seconds. */
63
+ durationS: number;
64
+ /** Route line as GeoJSON, ready to hand to a MapLibre source. */
65
+ geometry: RouteGeometry;
66
+ /** The raw OSRM `routes[0]` object, for callers that need leg/step detail. */
67
+ raw: Record<string, unknown>;
68
+ }
69
+ /** The ADR category of a tunnel, `"A"` (least restrictive) … `"E"`. */
70
+ type AdrTunnelCategory = "A" | "B" | "C" | "D" | "E";
71
+ /**
72
+ * Physical truck dimensions for the ADR vehicle profile (mirrors
73
+ * `sn_adr::TruckDimensions`). All optional; unset fields default to the EU
74
+ * 96/53/EC maximum-authorised articulated vehicle (4.0 m high, 2.55 m wide,
75
+ * 16.5 m long, 40 t gross), matching the Rust `Default`.
76
+ */
77
+ interface AdrDimensions {
78
+ /** Vehicle height, metres. Defaults to `4.0`. */
79
+ heightM?: number;
80
+ /** Vehicle width, metres. Defaults to `2.55`. */
81
+ widthM?: number;
82
+ /** Vehicle length, metres. Defaults to `16.5`. */
83
+ lengthM?: number;
84
+ /** Gross combination weight, tonnes. Defaults to `40`. */
85
+ grossWeightT?: number;
86
+ /** Heaviest single-axle load, tonnes, if known. */
87
+ axleLoadT?: number;
88
+ /** Number of axles, if known. */
89
+ axleCount?: number;
90
+ }
91
+ /**
92
+ * Input for the gateway's `POST /adr/check` compliance endpoint: the
93
+ * vehicle's ADR profile plus the category of the tunnel to check. Serialised
94
+ * on the wire as `{"adr": <AdrVehicleProfile>, "tunnel_category": "A".."E"}`
95
+ * (see `sn-gateway` `routes/adr.rs`).
96
+ */
97
+ interface AdrCheckRequest {
98
+ /** Carrying dangerous goods. */
99
+ hazmat: boolean;
100
+ /**
101
+ * The *vehicle load's* ADR 8.6.4 tunnel restriction code, e.g. `"C"` or
102
+ * `"C/E"`. Omit when carrying dangerous goods of unknown code (the
103
+ * gateway then conservatively treats the load as code `B`). This is not
104
+ * the tunnel's category — that goes in {@link tunnelCategory}.
105
+ */
106
+ tunnelCode?: string;
107
+ /** Vehicle dimensions. Defaults to the EU standard artic maximums. */
108
+ dimensions?: AdrDimensions;
109
+ /** The ADR category of the *tunnel* to check, `"A"` … `"E"`. */
110
+ tunnelCategory: AdrTunnelCategory;
111
+ }
112
+ /** Parsed response from `POST /adr/check`. */
113
+ interface AdrCheckResult {
114
+ /** `"allowed"` or `"blocked"`. */
115
+ status: string;
116
+ /** Human-readable justification citing ADR 8.6.4 where blocked. */
117
+ reason?: string;
118
+ /** The raw response body. */
119
+ raw: Record<string, unknown>;
120
+ }
121
+
122
+ /**
123
+ * Effects engine - named, geometry-anchored visual effects rendered through
124
+ * MapLibre's stable `CustomLayerInterface` (first-party GLSL, no runtime
125
+ * dependencies). The first effect is `"flow"`: an animated energy ribbon
126
+ * along the active route line, drawn as a triangle strip with a flowing
127
+ * pulse gradient driven by a time uniform.
128
+ *
129
+ * Deliberately NOT full-map post-processing: MapLibre has no official
130
+ * post-process hook, so every effect here anchors to geometry and rides the
131
+ * supported custom-layer API. New effects register in {@link ROUTE_EFFECTS}.
132
+ *
133
+ * Accessibility and resilience:
134
+ * - `prefers-reduced-motion: reduce` freezes the ribbon to a static
135
+ * gradient (no animation loop, no repaints).
136
+ * - If WebGL setup for the custom layer fails (shader compile/link, lost
137
+ * context), the effect silently disables itself - the ordinary route
138
+ * line underneath is untouched - and logs a single console warning.
139
+ *
140
+ * The tessellation and colour helpers are pure and unit-tested in Node;
141
+ * only the layer object itself touches WebGL.
142
+ */
143
+
144
+ /** The names the effects registry knows. Grows as effects land. */
145
+ type RouteEffectName = "flow";
146
+ /** Options for the `"flow"` route effect (all optional). */
147
+ interface RouteFlowOptions {
148
+ /** Ribbon colour (hex or rgb()/rgba()). Defaults to MapMap signal blue. */
149
+ color?: string;
150
+ /** Ribbon width in CSS pixels. Defaults to `10`. */
151
+ width?: number;
152
+ /** Flow speed in pulse cycles per second. Defaults to `0.6`. */
153
+ speed?: number;
154
+ /** MapLibre layer id. Defaults to `"mapmap-route-effect"`. */
155
+ id?: string;
156
+ }
157
+ /** Floats per ribbon vertex: x, y (mercator), nx, ny (unit normal), progress. */
158
+ declare const RIBBON_FLOATS_PER_VERTEX = 5;
159
+ /** A tessellated route ribbon, ready for a `TRIANGLE_STRIP` draw. */
160
+ interface RibbonMesh {
161
+ /**
162
+ * Interleaved vertex data, {@link RIBBON_FLOATS_PER_VERTEX} floats per
163
+ * vertex: mercator position, the unit extrusion normal (the shader
164
+ * extrudes `pos + normal * halfWidth`, so width stays zoom-correct
165
+ * without re-tessellating), and progress along the route (0 at the
166
+ * start, 1 at the end - the flowing gradient's coordinate).
167
+ */
168
+ vertices: Float32Array;
169
+ /** Number of vertices (two per route point kept). */
170
+ vertexCount: number;
171
+ }
172
+ /**
173
+ * Project a `[lng, lat]` to web-mercator world coordinates in [0, 1]
174
+ * (MapLibre's `MercatorCoordinate` convention: x east, y south).
175
+ */
176
+ declare function lngLatToMercator(lngLat: [number, number]): [number, number];
177
+ /**
178
+ * Tessellate a route line into a centreline triangle strip: two vertices
179
+ * per point, each carrying the point's mercator position, a unit extrusion
180
+ * normal (averaged between adjacent segments at joins - a bevel-free miter
181
+ * that slightly thins very sharp corners, which is invisible on a soft
182
+ * glow ribbon), and normalised progress along the line.
183
+ *
184
+ * Pure: no WebGL, no map. Consecutive duplicate points are dropped; fewer
185
+ * than two distinct points yield an empty mesh.
186
+ */
187
+ declare function tessellateRouteRibbon(coordinates: [number, number][]): RibbonMesh;
188
+ /**
189
+ * Parse a CSS colour to premultiplication-ready `[r, g, b, a]` (0-1
190
+ * channels). Supports the hex forms and `rgb()`/`rgba()`; anything else
191
+ * (named colours, `hsl()`) returns `undefined` and the caller falls back
192
+ * to the default - this runs where no canvas is guaranteed, so no
193
+ * browser colour parser is available.
194
+ */
195
+ declare function parseCssColour(colour: string): [number, number, number, number] | undefined;
196
+ /** Whether the user has asked for reduced motion. Safe without a DOM. */
197
+ declare function prefersReducedMotion(): boolean;
198
+ /** Default flow parameters (exported so docs/tests state one truth). */
199
+ declare const FLOW_DEFAULTS: {
200
+ readonly color: "#3a86ff";
201
+ readonly width: 10;
202
+ readonly speed: 0.6;
203
+ };
204
+ /**
205
+ * A MapLibre custom layer (`CustomLayerInterface`) drawing the flowing
206
+ * route ribbon. Construct via {@link createRouteEffect} (the registry) or
207
+ * use `MapMapMap.setRouteEffect("flow")`, which manages the lifecycle.
208
+ */
209
+ declare class FlowRouteEffectLayer implements CustomLayerInterface {
210
+ readonly id: string;
211
+ readonly type: "custom";
212
+ readonly renderingMode: "2d";
213
+ private geometry;
214
+ private readonly colour;
215
+ private readonly widthPx;
216
+ private readonly speed;
217
+ private readonly animate;
218
+ private mapRef;
219
+ private glRef;
220
+ private program;
221
+ private buffer;
222
+ private vertexCount;
223
+ private repeats;
224
+ private startedAt;
225
+ private failed;
226
+ private aPos;
227
+ private aNormal;
228
+ private aProgress;
229
+ private uMatrix;
230
+ private uHalfWidth;
231
+ private uColor;
232
+ private uPhase;
233
+ private uRepeats;
234
+ constructor(geometry: RouteGeometry, options?: RouteFlowOptions);
235
+ /** Swap the ribbon onto a new route line (e.g. after a reroute). */
236
+ setGeometry(geometry: RouteGeometry): void;
237
+ onAdd(map: Map, gl: WebGLRenderingContext | WebGL2RenderingContext): void;
238
+ onRemove(_map: Map, gl: WebGLRenderingContext | WebGL2RenderingContext): void;
239
+ render(gl: WebGLRenderingContext | WebGL2RenderingContext, args: unknown): void;
240
+ private upload;
241
+ }
242
+ /** A route effect layer: a custom layer that can retarget its geometry. */
243
+ interface RouteEffectLayer extends CustomLayerInterface {
244
+ setGeometry(geometry: RouteGeometry): void;
245
+ }
246
+ /**
247
+ * The effect registry: name -> layer factory. New effects (ambient life,
248
+ * seasonal snow, fog-of-exploration) add an entry here and a name to
249
+ * {@link RouteEffectName}.
250
+ */
251
+ declare const ROUTE_EFFECTS: Record<RouteEffectName, (geometry: RouteGeometry, options?: RouteFlowOptions) => RouteEffectLayer>;
252
+ /**
253
+ * Create a named route effect layer. Throws an agent-readable error for
254
+ * unknown names (the accepted list included).
255
+ */
256
+ declare function createRouteEffect(name: RouteEffectName, geometry: RouteGeometry, options?: RouteFlowOptions): RouteEffectLayer;
2
257
 
3
258
  /**
4
259
  * The MapMap logo control: a small wordmark on the map, the same
@@ -104,6 +359,23 @@ interface LayerOverride {
104
359
  minzoom?: number;
105
360
  maxzoom?: number;
106
361
  }
362
+ /**
363
+ * The optional visual-effects block, mirroring `sn_style::Effects`. Unlike
364
+ * `extra`, this IS carried into the compiled style — as
365
+ * `metadata["mapmap:effects"]` — so any consumer of a compiled `style.json`
366
+ * (hosted style URL, baked territory package) can honour it without the
367
+ * theme document. Renderers that do not know the effects engine ignore the
368
+ * metadata and render the style unchanged.
369
+ */
370
+ interface ThemeEffects {
371
+ /** Route-line effect: `"flow"` (animated energy ribbon) or `"none"`. */
372
+ route?: "flow" | "none";
373
+ /**
374
+ * Effect parameters, validated per effect. For `flow`: `color` (CSS
375
+ * colour), `width` (px, 0.5-40), `speed` (cycles/s, 0-10).
376
+ */
377
+ params?: Record<string, unknown>;
378
+ }
107
379
  /**
108
380
  * A MapMap Studio theme: the unit users and agents edit, store and publish.
109
381
  * Mirrors `sn_style::Theme` (all fields carry serde defaults, so everything
@@ -146,22 +418,38 @@ interface Theme {
146
418
  * source-layer.
147
419
  */
148
420
  extra_layers?: JsonObject[];
421
+ /**
422
+ * Optional visual effects (route ribbon etc.). Compiled into
423
+ * `metadata["mapmap:effects"]` on the style — see {@link ThemeEffects}.
424
+ * Mirrors `Theme::effects`.
425
+ */
426
+ effects?: ThemeEffects;
149
427
  /**
150
428
  * Non-style extras carried by the THEME DOCUMENT, not the compiled
151
429
  * style. Studio stores its navigation design block here (`extra.nav`;
152
- * see `nav-design.ts`). {@link buildStyle} ignores it entirely - the
430
+ * see `nav-design.ts`) and its POI category design (`extra.poi`; see
431
+ * `poi-design.ts`). {@link buildStyle} ignores it entirely - the
153
432
  * compiled `style.json` is byte-identical with or without it. Mirrors
154
433
  * `Theme::extra` in the canonical Rust crate: the hosted styles API
155
434
  * stores the block verbatim (bounded at 256 KB serialised) and serves it
156
435
  * back from `GET /styles/{id}/theme`, so a Studio design survives
157
436
  * publishing - consume it via `createMap` with a theme document or
158
- * `navDesignFromThemeUrl` with the hosted theme URL.
437
+ * `navDesignFromThemeUrl` / `poiDesignFromThemeUrl` with the hosted
438
+ * theme URL.
159
439
  */
160
440
  extra?: {
161
441
  nav?: unknown;
442
+ poi?: unknown;
162
443
  };
163
444
  }
164
445
  interface BuildStyleOptions {
446
+ /**
447
+ * Label language: an ISO 639 code selects `name:<code>` with a
448
+ * local-name fallback; the literal `"local"` selects the local name
449
+ * outright; omitted keeps the default (English-first). The tiles carry
450
+ * the OSM `name:*` set, so switching involves no tile rebuild.
451
+ */
452
+ labelLanguage?: string;
165
453
  /**
166
454
  * Theme to build: a built-in name (`"light"` / `"dark"`) or a full
167
455
  * {@link Theme} document. Defaults to `"light"`.
@@ -175,6 +463,22 @@ interface BuildStyleOptions {
175
463
  }
176
464
  /** Ensure a tiles URL carries the `pmtiles://` protocol prefix. */
177
465
  declare function toPmtilesUrl(url: string): string;
466
+ /**
467
+ * The `metadata` key a compiled style carries its effects block under.
468
+ * Shared with `sn-style` (compile.rs) and read back by `MapMapMap` to
469
+ * auto-enable the route effect for styles that request one.
470
+ */
471
+ declare const EFFECTS_METADATA_KEY = "mapmap:effects";
472
+ /**
473
+ * The effects block from a compiled style's `metadata`, if the style
474
+ * carries a valid one (`{ route: "flow", params? }`). Lenient by design —
475
+ * it reads FOREIGN styles (hosted URLs, baked packages), so anything
476
+ * malformed returns `undefined` rather than throwing.
477
+ */
478
+ declare function effectsFromStyleMetadata(metadata: unknown): {
479
+ route: "flow";
480
+ params?: Record<string, unknown>;
481
+ } | undefined;
178
482
  declare function buildStyle(options?: BuildStyleOptions): StyleSpecification;
179
483
 
180
484
  /**
@@ -331,6 +635,128 @@ declare function navDesignFromTheme(theme: Theme | undefined | null): NavDesign
331
635
  */
332
636
  declare function navDesignFromThemeUrl(url: string, fetchImpl?: typeof fetch): Promise<NavDesign | undefined>;
333
637
 
638
+ /**
639
+ * Studio POI category-design block (`extra.poi`) - types, defaults, parser
640
+ * and appliers.
641
+ *
642
+ * MapMap Studio's POIs panel colours the ~8 POI categories - the coloured
643
+ * category dot drawn under a POI label, and the `poi-labels` text colour -
644
+ * and stores the result as a small design-token block under `extra.poi` in
645
+ * the theme JSON it downloads/copies/publishes, exactly like the navigation
646
+ * design under `extra.nav` (see nav-design.ts). This module reads that
647
+ * block and applies the label-text part to a live MapLibre map; the dot
648
+ * images themselves are runtime-rasterised by their host (the MapMap
649
+ * website today - sprite-based icons arrive with marker packs).
650
+ *
651
+ * LOCKSTEP: this file mirrors `website/lib/studio-poi.ts` (the Studio
652
+ * reference parser) - identical category ids, built-in colours, leniency
653
+ * and expression building. The class->category mapping mirrors
654
+ * `website/lib/poi-icons.ts` (`POI_CLASS_CATEGORIES`). Keep them in sync so
655
+ * a theme previews in Studio exactly as the SDK applies it. Not imported
656
+ * from the website because the packages do not depend on each other.
657
+ *
658
+ * PUBLISH NOTE: the block travels with the THEME DOCUMENT, never with the
659
+ * compiled `style.json`. The canonical `sn_style` crate stores `extra`
660
+ * verbatim (bounded at 256 KB serialised) and the gateway serves it back
661
+ * from `GET /styles/{id}/theme`, so a design survives Studio's publish
662
+ * flow. Read it from a theme file you pass to `createMap` / `buildStyle`,
663
+ * or fetch it from a hosted style with {@link poiDesignFromThemeUrl} - a
664
+ * compiled style URL alone never carries it.
665
+ */
666
+
667
+ /** One category's overrides. Both fields optional: absent = built-in. */
668
+ interface PoiCategoryDesign {
669
+ /** Dot fill colour (replaces the built-in category brand colour). */
670
+ color?: string;
671
+ /** Label text colour for this category (replaces `textSecondary`). */
672
+ textColor?: string;
673
+ }
674
+ /** The `extra.poi` block: POI category design. */
675
+ interface PoiDesign {
676
+ /** Block schema version; currently always 1. */
677
+ version: 1;
678
+ /** Overrides by category id; empty = all built-in. */
679
+ categories: Record<string, PoiCategoryDesign>;
680
+ }
681
+ /**
682
+ * The valid category ids with their built-in dot colours, in presentation
683
+ * order. LOCKSTEP: mirrors `POI_CATEGORIES` in `website/lib/poi-icons.ts`.
684
+ */
685
+ declare const POI_CATEGORY_COLORS: readonly (readonly [string, string])[];
686
+ /** The valid category ids, in presentation order. */
687
+ declare const POI_CATEGORY_IDS: readonly string[];
688
+ /**
689
+ * OpenMapTiles `poi` layer `class` values grouped into the categories
690
+ * above; every class not listed falls through to `services`. LOCKSTEP:
691
+ * mirrors `POI_CLASS_CATEGORIES` in `website/lib/poi-icons.ts`.
692
+ */
693
+ declare const POI_CLASS_CATEGORIES: Record<string, string[]>;
694
+ /** The built-in dot colour for a category id. */
695
+ declare function builtInPoiColor(categoryId: string): string | undefined;
696
+ /** No overrides: every category keeps its built-in dot and label colours. */
697
+ declare function defaultPoiDesign(): PoiDesign;
698
+ /** True when the design changes nothing. */
699
+ declare function poiDesignIsDefault(design: PoiDesign): boolean;
700
+ /**
701
+ * Lenient parse of an `extra.poi` value: unknown category ids and invalid
702
+ * colours are dropped rather than failing, so an imported theme never
703
+ * breaks on its POI block. LOCKSTEP: identical semantics to
704
+ * `parsePoiDesign` in `website/lib/studio-poi.ts`.
705
+ */
706
+ declare function parsePoiDesign(value: unknown): PoiDesign;
707
+ /**
708
+ * Reads and parses the `extra.poi` block from a Studio theme document.
709
+ * Returns `undefined` when the theme carries no poi block at all (so
710
+ * callers can tell "no design" apart from "default design"); a present but
711
+ * malformed block parses leniently to the defaults. Same contract as
712
+ * `navDesignFromTheme`.
713
+ */
714
+ declare function poiDesignFromTheme(theme: Theme | undefined | null): PoiDesign | undefined;
715
+ /**
716
+ * Fetches a hosted theme document and reads its `extra.poi` block - point
717
+ * it at the gateway's public, never-cached theme endpoint, e.g.
718
+ * `https://api.mapmap.ai/styles/midnight-fleet-a1b2c3/theme`. Returns
719
+ * `undefined` when the theme carries no poi block; throws on network
720
+ * failure, a non-2xx response or a non-JSON body. Same contract as
721
+ * {@link navDesignFromThemeUrl}.
722
+ *
723
+ * @param fetchImpl Optional `fetch` replacement (tests, Node polyfills).
724
+ */
725
+ declare function poiDesignFromThemeUrl(url: string, fetchImpl?: typeof fetch): Promise<PoiDesign | undefined>;
726
+ /**
727
+ * The `poi-labels` `text-color` value for a design: the plain fallback
728
+ * colour when no category overrides label text, otherwise a `match`
729
+ * expression keyed on the tile's `class` property. Every non-services
730
+ * category always gets an arm (override or fallback) so unmatched classes
731
+ * - the `services` fallback arm - never inherit another category's colour.
732
+ * LOCKSTEP: mirrors `poiTextColorExpression` in `website/lib/studio-poi.ts`.
733
+ */
734
+ declare function poiTextColorExpression(design: PoiDesign, fallback: string): unknown;
735
+ /** The subset of the MapLibre map surface {@link applyPoiDesign} touches. */
736
+ interface PoiPaintMap {
737
+ getStyle(): {
738
+ layers?: {
739
+ id: string;
740
+ type?: string;
741
+ }[];
742
+ } | undefined;
743
+ setPaintProperty(layerId: string, name: string, value: unknown): unknown;
744
+ }
745
+ /**
746
+ * Applies a design's per-category label text colours to every `poi-labels`
747
+ * layer on a live map (including per-territory clones named
748
+ * `poi-labels@<territory>`). `fallbackTextColor` is the colour
749
+ * non-overridden categories keep - pass the style's `poi-labels` text
750
+ * colour (the theme's `textSecondary` slot; defaults to the light-base
751
+ * value). A default design restores the plain fallback, so this is safe to
752
+ * call on every design change.
753
+ *
754
+ * NOTE: runtime paint changes do not survive a later `setStyle`; re-apply
755
+ * after style swaps (the MapMap website bakes the expression into the
756
+ * style object instead for exactly this reason).
757
+ */
758
+ declare function applyPoiDesign(map: PoiPaintMap, design: PoiDesign, fallbackTextColor?: string): void;
759
+
334
760
  /**
335
761
  * MapMapMap - the branded MapLibre GL map.
336
762
  *
@@ -378,6 +804,15 @@ interface MapMapOptions {
378
804
  */
379
805
  mapOptions?: Partial<Omit<MapOptions, "container" | "style">>;
380
806
  }
807
+ /** Options for {@link MapMapMap.setRouteEffect}. */
808
+ interface SetRouteEffectOptions extends RouteFlowOptions {
809
+ /**
810
+ * The route line to attach the effect to. Optional: when omitted the
811
+ * effect is ARMED and applies as soon as a `RouteLayer` draws a route
812
+ * (RouteLayer feeds every drawn geometry to the map automatically).
813
+ */
814
+ geometry?: RouteGeometry;
815
+ }
381
816
  /** The MapMap map: a branded, tiles-and-routing-ready MapLibre map. */
382
817
  declare class MapMapMap {
383
818
  /** The underlying MapLibre map. Use it for any native MapLibre call. */
@@ -393,7 +828,62 @@ declare class MapMapMap {
393
828
  * the theme had no nav block (or `style` was not a theme document).
394
829
  */
395
830
  readonly navDesign: NavDesign | undefined;
831
+ /**
832
+ * The Studio POI category design (`extra.poi`) parsed from the theme
833
+ * passed as `style`, when it carried one. Apply its label-text part with
834
+ * `applyPoiDesign(map.map, map.poiDesign)`; `undefined` means the theme
835
+ * had no poi block (or `style` was not a theme document).
836
+ */
837
+ readonly poiDesign: PoiDesign | undefined;
838
+ private effectName;
839
+ private effectOptions;
840
+ private effectGeometry;
841
+ private effectExplicit;
842
+ private effectLayer;
396
843
  constructor(options: MapMapOptions);
844
+ private readonly handleStyleLoadForEffects;
845
+ /** The current style's `metadata`, if it can be read yet. */
846
+ private styleMetadata;
847
+ /**
848
+ * Set (or clear) the visual effect on the active route line - the
849
+ * effects-engine entry point (see `effects.ts`). The first effect is
850
+ * `"flow"`: an animated energy ribbon flowing along the route, drawn in
851
+ * a MapLibre custom layer with first-party GLSL.
852
+ *
853
+ * ```ts
854
+ * const route = await routes.route(from, to);
855
+ * map.setRouteEffect("flow"); // uses the drawn route
856
+ * map.setRouteEffect("flow", { color: "#ff7a1f" }); // options
857
+ * map.setRouteEffect(null); // back to the plain line
858
+ * ```
859
+ *
860
+ * Geometry: pass `{ geometry }` explicitly, or omit it and the effect
861
+ * attaches to whatever route a `RouteLayer` on this map draws (current
862
+ * and future - RouteLayer reports every drawn line via
863
+ * {@link setRouteEffectGeometry}).
864
+ *
865
+ * Styles whose theme carried an `effects` block auto-enable their effect
866
+ * from the compiled style's metadata; calling this method (with any
867
+ * value, including `null`) overrides the style's wish for the lifetime
868
+ * of this map.
869
+ *
870
+ * Accessibility/resilience (see effects.ts): honours
871
+ * `prefers-reduced-motion` (static gradient, no animation) and falls
872
+ * back to the plain route line with a single console warning if WebGL
873
+ * setup fails.
874
+ */
875
+ setRouteEffect(effect: RouteEffectName | null, options?: SetRouteEffectOptions): void;
876
+ /**
877
+ * Attach the active route effect to a route line (or detach it with
878
+ * `null`). `RouteLayer` calls this on every `draw()`/`clear()`, so apps
879
+ * normally never do - pass `geometry` to {@link setRouteEffect} for
880
+ * routes drawn outside a RouteLayer.
881
+ */
882
+ setRouteEffectGeometry(geometry: RouteGeometry | null): void;
883
+ private effectLayerId;
884
+ private removeEffectLayer;
885
+ /** Reconcile the effect state with the map: install, update or remove. */
886
+ private applyRouteEffect;
397
887
  /**
398
888
  * Toggle 3D building extrusions at runtime — the live equivalent of
399
889
  * compiling the style with `buildings_3d: true` (see `Theme`): adds or
@@ -416,125 +906,6 @@ declare class MapMapMap {
416
906
  /** Functional alias for {@link MapMapMap}, mirroring the Mapbox `new Map` feel. */
417
907
  declare function createMap(options: MapMapOptions): MapMapMap;
418
908
 
419
- /**
420
- * Shared public types for @mapmap/maps.
421
- *
422
- * These are runtime-free (type-only) so the pure logic modules that import
423
- * them stay loadable in Node without a browser or maplibre-gl.
424
- */
425
- /** A longitude/latitude pair, accepted in the three common shapes. */
426
- type LngLatLike = [number, number] | {
427
- lng: number;
428
- lat: number;
429
- } | {
430
- lon: number;
431
- lat: number;
432
- };
433
- /** OSRM routing profile. MapMap ships `driving`, `walking` and `truck`. */
434
- type RouteProfile = "driving" | "walking" | "truck" | (string & {});
435
- /**
436
- * Truck / ADR vehicle parameters forwarded to the gateway's OSRM truck
437
- * vendor extensions. All optional; only `truck`-profile requests honour them.
438
- */
439
- interface TruckParams {
440
- /** Vehicle height, metres. */
441
- heightM?: number;
442
- /** Vehicle width, metres. */
443
- widthM?: number;
444
- /** Vehicle length, metres. */
445
- lengthM?: number;
446
- /** Gross weight, tonnes. */
447
- weightT?: number;
448
- /** Carrying dangerous goods (ADR). */
449
- hazmat?: boolean;
450
- /**
451
- * ADR 8.6.4 tunnel restriction code, e.g. `"C"` or `"B/D"`. The slash is
452
- * URL-encoded automatically.
453
- */
454
- tunnelCode?: string;
455
- }
456
- /** Options for a single route request. */
457
- interface RouteOptions {
458
- /** Routing profile. Defaults to `"driving"`. */
459
- profile?: RouteProfile;
460
- /** Truck / ADR parameters (only meaningful with `profile: "truck"`). */
461
- truck?: TruckParams;
462
- /** Request spoken `voiceInstructions` on each step (see `guidance.ts`). */
463
- voice?: boolean;
464
- /** Request visual `bannerInstructions` (and lane data) on each step. */
465
- banner?: boolean;
466
- /** BCP 47 narration language, e.g. `"en-GB"`. */
467
- language?: string;
468
- }
469
- /** A parsed GeoJSON LineString geometry (`[lng, lat]` positions). */
470
- interface RouteGeometry {
471
- type: "LineString";
472
- coordinates: [number, number][];
473
- }
474
- /** A parsed OSRM route, normalised to the fields callers actually use. */
475
- interface ParsedRoute {
476
- /** Total distance, metres. */
477
- distanceM: number;
478
- /** Total duration, seconds. */
479
- durationS: number;
480
- /** Route line as GeoJSON, ready to hand to a MapLibre source. */
481
- geometry: RouteGeometry;
482
- /** The raw OSRM `routes[0]` object, for callers that need leg/step detail. */
483
- raw: Record<string, unknown>;
484
- }
485
- /** The ADR category of a tunnel, `"A"` (least restrictive) … `"E"`. */
486
- type AdrTunnelCategory = "A" | "B" | "C" | "D" | "E";
487
- /**
488
- * Physical truck dimensions for the ADR vehicle profile (mirrors
489
- * `sn_adr::TruckDimensions`). All optional; unset fields default to the EU
490
- * 96/53/EC maximum-authorised articulated vehicle (4.0 m high, 2.55 m wide,
491
- * 16.5 m long, 40 t gross), matching the Rust `Default`.
492
- */
493
- interface AdrDimensions {
494
- /** Vehicle height, metres. Defaults to `4.0`. */
495
- heightM?: number;
496
- /** Vehicle width, metres. Defaults to `2.55`. */
497
- widthM?: number;
498
- /** Vehicle length, metres. Defaults to `16.5`. */
499
- lengthM?: number;
500
- /** Gross combination weight, tonnes. Defaults to `40`. */
501
- grossWeightT?: number;
502
- /** Heaviest single-axle load, tonnes, if known. */
503
- axleLoadT?: number;
504
- /** Number of axles, if known. */
505
- axleCount?: number;
506
- }
507
- /**
508
- * Input for the gateway's `POST /adr/check` compliance endpoint: the
509
- * vehicle's ADR profile plus the category of the tunnel to check. Serialised
510
- * on the wire as `{"adr": <AdrVehicleProfile>, "tunnel_category": "A".."E"}`
511
- * (see `sn-gateway` `routes/adr.rs`).
512
- */
513
- interface AdrCheckRequest {
514
- /** Carrying dangerous goods. */
515
- hazmat: boolean;
516
- /**
517
- * The *vehicle load's* ADR 8.6.4 tunnel restriction code, e.g. `"C"` or
518
- * `"C/E"`. Omit when carrying dangerous goods of unknown code (the
519
- * gateway then conservatively treats the load as code `B`). This is not
520
- * the tunnel's category — that goes in {@link tunnelCategory}.
521
- */
522
- tunnelCode?: string;
523
- /** Vehicle dimensions. Defaults to the EU standard artic maximums. */
524
- dimensions?: AdrDimensions;
525
- /** The ADR category of the *tunnel* to check, `"A"` … `"E"`. */
526
- tunnelCategory: AdrTunnelCategory;
527
- }
528
- /** Parsed response from `POST /adr/check`. */
529
- interface AdrCheckResult {
530
- /** `"allowed"` or `"blocked"`. */
531
- status: string;
532
- /** Human-readable justification citing ADR 8.6.4 where blocked. */
533
- reason?: string;
534
- /** The raw response body. */
535
- raw: Record<string, unknown>;
536
- }
537
-
538
909
  /**
539
910
  * RouteLayer - fetch an OSRM route from the MapMap gateway and draw it.
540
911
  *
@@ -558,17 +929,32 @@ interface RouteLayerOptions {
558
929
  * without a design the layer keeps its built-in signal-blue look.
559
930
  */
560
931
  design?: NavRouteDesign;
932
+ /**
933
+ * Colour of the already-travelled part of the line once
934
+ * {@link RouteLayer.setProgress} is used (the Google-style "vanishing
935
+ * route line"). Defaults to a dimmed grey.
936
+ */
937
+ progressColor?: string;
561
938
  }
562
939
  /** Draws MapMap routes on a MapLibre map. */
563
940
  declare class RouteLayer {
564
941
  private readonly map;
942
+ /** The owning MapMapMap, when built from one - fed each drawn route so
943
+ * an armed route effect (`setRouteEffect`) attaches automatically. */
944
+ private readonly owner;
565
945
  private readonly baseUrl;
566
946
  private readonly apiKey;
567
947
  private readonly sourceId;
568
948
  private readonly casingLayerId;
569
949
  private readonly lineLayerId;
950
+ private readonly maneuverSourceId;
951
+ private readonly maneuverLayerId;
952
+ private readonly arrowImageId;
570
953
  private readonly design;
954
+ private readonly progressColor;
571
955
  private lastRoute;
956
+ private progress;
957
+ private maneuver;
572
958
  constructor(map: MapMapMap | Map, options?: RouteLayerOptions);
573
959
  private readonly handleStyleLoad;
574
960
  /**
@@ -589,6 +975,26 @@ declare class RouteLayer {
589
975
  draw(route: ParsedRoute): void;
590
976
  /** Add-or-update the source and layers for a route on the current style. */
591
977
  private install;
978
+ /**
979
+ * Sets how much of the route has been travelled, as a fraction in `[0, 1]`
980
+ * of the line's length. The travelled part dims to `progressColor` (the
981
+ * "vanishing route line"); `0` restores the untinted line. The value is
982
+ * remembered across {@link draw} calls and style swaps. Pair with the
983
+ * guidance module's distance-remaining to derive the fraction.
984
+ */
985
+ setProgress(fraction: number): void;
986
+ /** Applies the current progress fraction to the line layer's gradient. */
987
+ private applyProgress;
988
+ /**
989
+ * Shows (or moves) the upcoming-manoeuvre arrow: a small map-aligned
990
+ * arrow at `lngLat` rotated to `bearingDeg` (clockwise from north).
991
+ * Survives style swaps until {@link clearManeuver}.
992
+ */
993
+ setManeuver(lngLat: [number, number], bearingDeg: number): void;
994
+ /** Hides the manoeuvre arrow. */
995
+ clearManeuver(): void;
996
+ /** Add-or-update the manoeuvre arrow source/layer for the current style. */
997
+ private installManeuver;
592
998
  /** Remove the route's layers and source from the map. */
593
999
  clear(): void;
594
1000
  /**
@@ -600,6 +1006,29 @@ declare class RouteLayer {
600
1006
 
601
1007
  /** Maximum accepted `data:` puck-image payload (matches Studio's upload cap). */
602
1008
  declare const MAX_PUCK_IMAGE_BYTES: number;
1009
+ /**
1010
+ * The signed shortest-arc rotation from `fromDeg` to `toDeg`, in degrees.
1011
+ * Always in `(-180, 180]`, so a heading tween never spins the long way
1012
+ * round the compass (350° -> 10° is +20°, not -340°).
1013
+ */
1014
+ declare function shortestArcDeg(fromDeg: number, toDeg: number): number;
1015
+ /** Options for {@link PositionPuck}. */
1016
+ interface PositionPuckOptions {
1017
+ /**
1018
+ * Animate between fixes instead of snapping (default `true`). Position
1019
+ * lerps linearly and heading tweens along the shortest arc, over a
1020
+ * duration adapted to the observed fix interval (like the
1021
+ * `NavigationCamera` glide, so puck and camera arrive together). Falls
1022
+ * back to instant placement where `requestAnimationFrame` is missing.
1023
+ */
1024
+ interpolate?: boolean;
1025
+ /** Test seam: clock override, defaults to `Date.now`. */
1026
+ now?: () => number;
1027
+ /** Test seam: frame scheduler override, defaults to rAF. */
1028
+ requestFrame?: (callback: () => void) => number;
1029
+ /** Test seam: frame canceller override, defaults to cancelAnimationFrame. */
1030
+ cancelFrame?: (handle: number) => void;
1031
+ }
603
1032
  /** A current-position puck marker for MapMap maps. */
604
1033
  declare class PositionPuck {
605
1034
  /** The puck's root DOM element (the Marker element). */
@@ -608,25 +1037,200 @@ declare class PositionPuck {
608
1037
  private readonly marker;
609
1038
  private readonly design;
610
1039
  private added;
1040
+ private readonly interpolate;
1041
+ private readonly now;
1042
+ private readonly requestFrame?;
1043
+ private readonly cancelFrame?;
1044
+ private frameHandle;
1045
+ /** The position/heading currently rendered on the marker. */
1046
+ private rendered;
1047
+ private lastFixAt;
611
1048
  /**
612
1049
  * Creates the puck (not yet on the map - it appears on the first
613
1050
  * {@link setLocation}). The design defaults to the map's
614
1051
  * `navDesign.puck` when given a `MapMapMap` whose theme carried an
615
1052
  * `extra.nav` block, then to the built-in blue puck.
616
1053
  */
617
- constructor(map: MapMapMap | Map, design?: NavPuckDesign);
1054
+ constructor(map: MapMapMap | Map, design?: NavPuckDesign, options?: PositionPuckOptions);
618
1055
  /**
619
1056
  * Moves the puck (adding it to the map on the first call). `headingDeg`
620
1057
  * rotates the whole element - arrow or custom image - clockwise from
621
1058
  * north; omit it to keep the previous heading.
1059
+ *
1060
+ * With interpolation on (the default) every call after the first glides
1061
+ * from the currently rendered position - a fix arriving mid-tween
1062
+ * retargets smoothly rather than jumping.
622
1063
  */
623
1064
  setLocation(location: {
624
1065
  lat: number;
625
1066
  lon: number;
626
1067
  }, headingDeg?: number): void;
627
- /** Removes the puck from the map. `setLocation` re-adds it. */
1068
+ /** Removes the puck from the map (cancelling any tween). `setLocation` re-adds it. */
628
1069
  remove(): void;
1070
+ /** Applies a position/heading to the marker immediately. */
1071
+ private render;
1072
+ /** Runs a linear position lerp + shortest-arc heading tween via rAF. */
1073
+ private tween;
1074
+ /** Cancels an in-flight tween, leaving the marker where it rendered last. */
1075
+ private cancelTween;
1076
+ }
1077
+
1078
+ /**
1079
+ * Auto day/night theme switching from sun position.
1080
+ *
1081
+ * A dependency-free solar calculator (the standard sunrise equation, as
1082
+ * published at https://en.wikipedia.org/wiki/Sunrise_equation, itself a
1083
+ * restatement of the NOAA solar calculation details,
1084
+ * https://gml.noaa.gov/grad/solcalc/calcdetails.html) plus a small
1085
+ * scheduler that flips between light and dark at sunrise/sunset. Nothing
1086
+ * here touches a map: pair {@link ThemeScheduler} with
1087
+ * `MapMapMap.setStyle("light" | "dark")` (or any other callback) in the
1088
+ * app. Territory packages already ship paired light/dark styles, so the
1089
+ * switch works offline too.
1090
+ */
1091
+ /** Sun-times result: either both events, or a polar day/night marker. */
1092
+ type SunTimes = {
1093
+ sunrise: Date;
1094
+ sunset: Date;
1095
+ } | "polarDay" | "polarNight";
1096
+ /**
1097
+ * Sunrise and sunset (standard -0.833° horizon: refraction + solar disc)
1098
+ * for the civil day containing `date` (UTC) at `lat`/`lng`, or a polar
1099
+ * marker when the sun never crosses the horizon that day.
1100
+ */
1101
+ declare function sunTimes(date: Date, lat: number, lng: number): SunTimes;
1102
+ /** The theme the sun dictates at `date` for `lat`/`lng`. */
1103
+ declare function resolveTheme(date: Date, lat: number, lng: number): "light" | "dark";
1104
+ /** Options for {@link ThemeScheduler}. */
1105
+ interface ThemeSchedulerOptions {
1106
+ /** Latitude used for the solar calculation. */
1107
+ lat: number;
1108
+ /** Longitude used for the solar calculation. */
1109
+ lng: number;
1110
+ /** Called (immediately on construction, then at each sunrise) for light. */
1111
+ onLight: () => void;
1112
+ /** Called (immediately on construction, then at each sunset) for dark. */
1113
+ onDark: () => void;
1114
+ /** Test seam: clock override, defaults to `Date.now`. */
1115
+ now?: () => number;
1116
+ /** Test seam: timer override, defaults to `setTimeout`. */
1117
+ setTimeoutFn?: (callback: () => void, ms: number) => ReturnType<typeof setTimeout>;
1118
+ /** Test seam: timer canceller, defaults to `clearTimeout`. */
1119
+ clearTimeoutFn?: (handle: ReturnType<typeof setTimeout>) => void;
1120
+ }
1121
+ /**
1122
+ * Applies the sun-appropriate theme now and at every subsequent
1123
+ * sunrise/sunset until {@link dispose}. No OSS map SDK ships this
1124
+ * out of the box; pair with the OS dark-mode signal in the app if the
1125
+ * user's system preference should win instead.
1126
+ */
1127
+ declare class ThemeScheduler {
1128
+ private lat;
1129
+ private lng;
1130
+ private readonly onLight;
1131
+ private readonly onDark;
1132
+ private readonly now;
1133
+ private readonly setTimeoutFn;
1134
+ private readonly clearTimeoutFn;
1135
+ private handle;
1136
+ private applied;
1137
+ private disposed;
1138
+ constructor(options: ThemeSchedulerOptions);
1139
+ /** The theme most recently applied, if any. */
1140
+ get current(): "light" | "dark" | undefined;
1141
+ /** Moves the observer (e.g. a new GPS fix region) and re-evaluates. */
1142
+ setPosition(lat: number, lng: number): void;
1143
+ /** Stops all future flips. */
1144
+ dispose(): void;
1145
+ /** Applies the theme for now and arms the timer for the next boundary. */
1146
+ private evaluate;
1147
+ /** Milliseconds until the next sunrise/sunset (or the polar re-check). */
1148
+ private nextBoundaryDelay;
1149
+ }
1150
+
1151
+ /**
1152
+ * Runtime label-language switching.
1153
+ *
1154
+ * MapMap tiles carry OpenMapTiles multilingual `name:*` fields
1155
+ * (https://openmaptiles.org/schema/), so a map can switch label language
1156
+ * without new tiles: rewrite each symbol layer's `text-field` to prefer
1157
+ * `name:{language}`. `setMapLanguage(map, "de")` does exactly that for
1158
+ * every layer whose text-field reads name fields (other text — road refs,
1159
+ * house numbers — is left untouched). Pass `null` to restore the compiled
1160
+ * style's default (`name:en` first).
1161
+ *
1162
+ * Server-side, themes can bake a language in with `theme.language`; this
1163
+ * helper is the client-side equivalent for user-facing toggles. For
1164
+ * right-to-left scripts (Arabic, Hebrew) also install MapLibre's RTL text
1165
+ * plugin in the app (`maplibregl.setRTLTextPlugin(...)`) — the SDK does
1166
+ * not fetch remote scripts on your behalf.
1167
+ */
1168
+
1169
+ /**
1170
+ * The `text-field` expression for labels in `language` — the requested
1171
+ * language first, the Latin transliteration second, the local name last.
1172
+ * `null` yields the compiled default (`name:en`, then `name`).
1173
+ */
1174
+ declare function languageTextField(language: string | null): unknown;
1175
+ /**
1176
+ * True when a layer's `text-field` reads `name`/`name:*` properties —
1177
+ * i.e. it is a place/POI/road-name label whose language can switch.
1178
+ * Fields reading other properties (`ref` shields, house numbers) are not.
1179
+ */
1180
+ declare function isNameTextField(textField: unknown): boolean;
1181
+ /**
1182
+ * Switches every name-label layer of the map's current style to
1183
+ * `language` (`null` restores the style's default). Returns the ids of
1184
+ * the layers it rewrote. Throws on a malformed language tag. Reapply
1185
+ * after `setStyle` — a style swap resets label languages.
1186
+ */
1187
+ declare function setMapLanguage(map: MapMapMap | Map, language: string | null): string[];
1188
+
1189
+ /**
1190
+ * Probe batch upload - the browser side of opt-in aggregate collection.
1191
+ *
1192
+ * The wasm nav core (`@mapmap/core` `GuidanceSession`) accumulates aggregates
1193
+ * on-device and hands back the exact body to POST via `finishProbeJson()`;
1194
+ * this helper delivers it to the gateway's `POST /v1/probe`, with the same
1195
+ * status handling and retry policy as the mobile SDKs. Aggregates only - the
1196
+ * core is architecturally incapable of emitting a trajectory.
1197
+ *
1198
+ * Consent is the caller's responsibility: only upload for a key whose operator
1199
+ * has set `probe_opt_in`, and - in a browser - only with the user's opt-in
1200
+ * (ePrivacy/PECR) consent. See `docs/PROBE-SDK-INTEGRATION.md`.
1201
+ *
1202
+ * @example
1203
+ * ```ts
1204
+ * const body = session.finishProbeJson(arrived, Date.now());
1205
+ * if (body) void uploadProbeBatch("https://api.mapmap.ai", apiKey, body);
1206
+ * ```
1207
+ */
1208
+ /** The outcome of an upload attempt. */
1209
+ type ProbeUploadOutcome = "accepted" | "refused" | "notEnabled" | "rejected" | "gaveUp";
1210
+ /** Options for {@link uploadProbeBatch}. All optional. */
1211
+ interface UploadProbeOptions {
1212
+ /** Injectable fetch (defaults to the global). */
1213
+ fetch?: typeof fetch;
1214
+ /** Total attempts including the first (default 4). */
1215
+ maxAttempts?: number;
1216
+ /** Attempt number → delay before the next try, ms (default `attempt*1000`). */
1217
+ backoffMs?: (attempt: number) => number;
1218
+ /** Injectable delay, for deterministic tests (default `setTimeout`). */
1219
+ sleep?: (ms: number) => Promise<void>;
1220
+ /** Abort signal forwarded to fetch. */
1221
+ signal?: AbortSignal;
629
1222
  }
1223
+ /** The `/v1/probe` endpoint for a gateway origin (trailing slashes trimmed). */
1224
+ declare function buildProbeUrl(baseUrl: string): string;
1225
+ /**
1226
+ * POST one probe body to `{baseUrl}/v1/probe` with a bearer key. Transient
1227
+ * failures (network or `5xx`) are retried with bounded backoff; permanent ones
1228
+ * (`400`/`403`/`501`) are not. Resolves with the terminal outcome and never
1229
+ * rejects, so a fire-and-forget `void uploadProbeBatch(...)` is safe.
1230
+ *
1231
+ * @param body the string returned by `GuidanceSession.finishProbeJson()`.
1232
+ */
1233
+ declare function uploadProbeBatch(baseUrl: string, apiKey: string, body: string, options?: UploadProbeOptions): Promise<ProbeUploadOutcome>;
630
1234
 
631
1235
  /** One place to show on the map - a store, depot, branch, POI. */
632
1236
  interface Place {
@@ -639,9 +1243,14 @@ interface Place {
639
1243
  /** Longitude, degrees. */
640
1244
  lon: number;
641
1245
  /**
642
- * Arbitrary extra data (opening hours, phone, …). Copied onto the GeoJSON
643
- * feature properties, so it is available to data-driven styling and comes
644
- * back on the place handed to `onPlaceClick` / `popup`.
1246
+ * Arbitrary extra data (opening hours, phone, …). Every key is copied
1247
+ * verbatim onto the top level of the GeoJSON feature's `properties`,
1248
+ * alongside the reserved `id`, `name` and `__mapmapIndex` keys (which win
1249
+ * on collision) - so scalar values are directly addressable from a
1250
+ * data-driven `color` expression, e.g. `["get", "category"]`. MapLibre
1251
+ * JSON-stringifies nested objects/arrays at render time, so keep anything
1252
+ * you want to style on as a top-level string/number/boolean. The whole
1253
+ * object also comes back on the place handed to `onPlaceClick` / `popup`.
645
1254
  */
646
1255
  properties?: Record<string, unknown>;
647
1256
  }
@@ -710,8 +1319,22 @@ interface PlacesLayerOptions {
710
1319
  clusterRadius?: number;
711
1320
  /** Max zoom to cluster at. Defaults to MapLibre's `14`. */
712
1321
  clusterMaxZoom?: number;
713
- /** Pin and cluster colour. Defaults to MapMap signal blue (`#3a86ff`). */
714
- color?: string;
1322
+ /**
1323
+ * Pin colour: a CSS colour string, or a MapLibre expression evaluated
1324
+ * against each feature's `properties` for per-category pins, e.g.
1325
+ * `["match", ["get", "category"], "food", "#e63946", "#3a86ff"]` (see
1326
+ * {@link Place.properties} for what an expression can `get`). Defaults to
1327
+ * MapMap signal blue (`#3a86ff`). Only styles the default circle pins -
1328
+ * ignored with a custom `icon`. A cluster mixes categories, so an
1329
+ * expression never applies to clusters: they use `clusterColor`.
1330
+ */
1331
+ color?: string | ExpressionSpecification;
1332
+ /**
1333
+ * Cluster circle colour. Defaults to `color` when that is a plain string,
1334
+ * else to MapMap signal blue (a cluster mixes categories, so a
1335
+ * data-driven `color` expression cannot apply to it).
1336
+ */
1337
+ clusterColor?: string;
715
1338
  /**
716
1339
  * Custom pin image for unclustered places (a symbol layer instead of the
717
1340
  * default circle). If the image fails to load, the circle look is used.
@@ -740,6 +1363,26 @@ interface PlacesLayerOptions {
740
1363
  */
741
1364
  popup?: (place: Place) => string | HTMLElement;
742
1365
  }
1366
+ /** The generated MapLibre source/layer ids (see {@link PlacesLayer.ids}). */
1367
+ interface PlacesLayerIds {
1368
+ /** The GeoJSON source id. */
1369
+ source: string;
1370
+ /** The unclustered-points layer id (circle, or symbol with `icon`). */
1371
+ points: string;
1372
+ /** The cluster circles layer id (only installed with `cluster: true`). */
1373
+ clusters: string;
1374
+ /** The cluster count badge layer id (only installed with `cluster: true`). */
1375
+ clusterCounts: string;
1376
+ }
1377
+ /** Options for {@link PlacesLayer.select}. */
1378
+ interface PlacesSelectOptions {
1379
+ /** Open the layer's configured `popup` at the place. Defaults to `true`. */
1380
+ popup?: boolean;
1381
+ /** Ease the camera to the place. Defaults to `true`. */
1382
+ flyTo?: boolean;
1383
+ /** Zoom for the camera move (with `flyTo`). Keeps the current zoom if omitted. */
1384
+ zoom?: number;
1385
+ }
743
1386
  /**
744
1387
  * Normalise a GeoJSON FeatureCollection of Points to `Place[]`. The id comes
745
1388
  * from `feature.id`, then `properties.id`, then the feature index; the name
@@ -768,6 +1411,7 @@ declare class PlacesLayer {
768
1411
  private readonly clusterRadius;
769
1412
  private readonly clusterMaxZoom;
770
1413
  private readonly color;
1414
+ private readonly clusterColor;
771
1415
  private readonly icon;
772
1416
  private readonly wantFitBounds;
773
1417
  private readonly onPlaceClick;
@@ -785,13 +1429,37 @@ declare class PlacesLayer {
785
1429
  private readonly handleClusterClick;
786
1430
  /**
787
1431
  * Replace the layer's places - a `Place[]` or a GeoJSON FeatureCollection
788
- * of Points. Updates the existing GeoJSON source in place; safe to call
789
- * before the style has loaded (installed on the next `style.load`). With
790
- * `fitBounds: true` the first non-empty set also fits the map view.
1432
+ * of Points. Never silently drops an update: once the source exists the
1433
+ * data is applied immediately, even while `isStyleLoaded()` is transiently
1434
+ * `false` mid-render (search-as-you-type just works); calls made before
1435
+ * the source has first been installed are stashed - the latest one wins -
1436
+ * and installed on the next `style.load`. With `fitBounds: true` the
1437
+ * first non-empty set also fits the map view.
791
1438
  */
792
1439
  setPlaces(places: PlacesInput): void;
793
1440
  /** The layer's current places (normalised to `Place[]`). */
794
1441
  get current(): Place[];
1442
+ /**
1443
+ * The generated MapLibre source/layer ids - public API for escape-hatch
1444
+ * styling (`map.setPaintProperty`, `queryRenderedFeatures`, …) beyond the
1445
+ * layer's options. Stable for the layer's lifetime, derived from the `id`
1446
+ * option (default `"mapmap-places"`). The cluster ids are only installed
1447
+ * on the map with `cluster: true` (the default), and `points` is a circle
1448
+ * layer by default or a symbol layer once a custom `icon` has loaded.
1449
+ */
1450
+ get ids(): PlacesLayerIds;
1451
+ /**
1452
+ * Programmatically select a place by id - list-to-map sync for a store
1453
+ * finder's results list. Opens the layer's configured `popup` at the
1454
+ * place (`popup: false` to skip, no-op without a `popup` option) and
1455
+ * eases the camera to it (`flyTo: false` to skip; `zoom` to also zoom).
1456
+ * Returns the selected place, or `undefined` for an unknown id (in which
1457
+ * case nothing happens). Does NOT invoke `onPlaceClick` - a programmatic
1458
+ * selection is not a user click.
1459
+ */
1460
+ select(id: string, options?: PlacesSelectOptions): Place | undefined;
1461
+ /** Close the popup opened by {@link select} or a place click, if any. */
1462
+ deselect(): void;
795
1463
  /**
796
1464
  * The nearest `n` places (default `1`) to `origin` by straight-line
797
1465
  * (haversine) distance, each with `distanceM` attached. Pure and instant -
@@ -969,6 +1637,255 @@ declare class NavigationCamera {
969
1637
  private scheduleRecentre;
970
1638
  private clearRecentreTimer;
971
1639
  }
1640
+ /**
1641
+ * Initial great-circle bearing from `a` to `b`, degrees clockwise from
1642
+ * north, normalised to `[0, 360)`.
1643
+ */
1644
+ declare function bearingBetween(a: [number, number], b: [number, number]): number;
1645
+ /**
1646
+ * The signed shortest-arc rotation from bearing `from` to bearing `to`,
1647
+ * in `(-180, 180]` degrees - the slerp-style delta the flythrough eases
1648
+ * along so the chase cam never spins the long way round.
1649
+ */
1650
+ declare function shortestArcDelta(from: number, to: number): number;
1651
+ /** A camera pose along a flythrough: where to look from, and which way. */
1652
+ interface FlythroughPose {
1653
+ /** Camera target on the route, `[lng, lat]`. */
1654
+ center: [number, number];
1655
+ /** Chase-cam bearing: towards a point `lookAheadM` up the road. */
1656
+ bearing: number;
1657
+ }
1658
+ /**
1659
+ * The camera pose at progress `t` (0-1) along a route: the interpolated
1660
+ * point that fraction of the total distance along the line, with a
1661
+ * chase-cam bearing towards the point `lookAheadM` further on. Pure -
1662
+ * exported for tests and for apps that drive the camera themselves.
1663
+ */
1664
+ declare function flythroughPose(coordinates: [number, number][], t: number, lookAheadM?: number): FlythroughPose;
1665
+ interface FlythroughOptions {
1666
+ /**
1667
+ * Camera tilt in degrees, clamped 0-85. Default `60` (the cinematic
1668
+ * chase look; see {@link NavigationCameraOptions.pitch} for caveats
1669
+ * above 60).
1670
+ */
1671
+ pitch?: number;
1672
+ /** Camera zoom held through the replay. Default `16`. */
1673
+ zoom?: number;
1674
+ /**
1675
+ * Total replay duration in milliseconds. Default `20000`. Ignored when
1676
+ * `speedMps` is given.
1677
+ */
1678
+ durationMs?: number;
1679
+ /**
1680
+ * Replay ground speed in metres/second along the route - the duration
1681
+ * becomes `routeLength / speedMps`. Wins over `durationMs`.
1682
+ */
1683
+ speedMps?: number;
1684
+ /**
1685
+ * How far up the road the chase cam looks to derive its bearing,
1686
+ * metres. Default `200`; larger = calmer bearing on wiggly roads.
1687
+ */
1688
+ lookAheadM?: number;
1689
+ /**
1690
+ * Bearing smoothing rate per second (the fraction of the remaining
1691
+ * shortest-arc turn closed each second). Default `3`; higher = snappier.
1692
+ */
1693
+ bearingEase?: number;
1694
+ }
1695
+ /** The handle a {@link flythrough} returns. */
1696
+ interface FlythroughController {
1697
+ /** Start (or resume) the replay. Restarts from 0 when it had finished. */
1698
+ play(): void;
1699
+ /** Freeze the replay where it is. */
1700
+ pause(): void;
1701
+ /** Pause and rewind to the start (the camera jumps to the start pose). */
1702
+ stop(): void;
1703
+ /** Scrub to progress `t` (0-1, clamped) and apply the pose immediately. */
1704
+ seek(t: number): void;
1705
+ /** Playback-rate multiplier (`1` = real duration, `2` = double). */
1706
+ speed: number;
1707
+ /** Current progress, 0-1. */
1708
+ readonly progress: number;
1709
+ /** Whether the replay is currently advancing. */
1710
+ readonly playing: boolean;
1711
+ /** Subscribe to progress updates; returns the unsubscribe function. */
1712
+ onProgress(listener: (t: number) => void): () => void;
1713
+ /** Pause and detach everything. The controller is dead afterwards. */
1714
+ destroy(): void;
1715
+ }
1716
+ /**
1717
+ * Cinematic route replay: fly the camera along a route line with a
1718
+ * chase-cam bearing (eased along the shortest arc, so junctions glide
1719
+ * instead of spinning), at a configurable pitch/zoom/speed. Returns a
1720
+ * transport-style controller; nothing moves until `play()` or `seek()`.
1721
+ *
1722
+ * ```ts
1723
+ * const route = await routes.route(from, to);
1724
+ * const replay = flythrough(map, route, { pitch: 60, durationMs: 15000 });
1725
+ * replay.onProgress((t) => scrubber.value = String(t));
1726
+ * replay.play();
1727
+ * ```
1728
+ *
1729
+ * Drives the camera with `jumpTo` once per animation frame (the standard
1730
+ * MapLibre scrub pattern). Runs under any projection; on globe the ride is
1731
+ * unanchored but valid. Accepts a `ParsedRoute` (from `RouteLayer`) or a
1732
+ * bare GeoJSON LineString geometry.
1733
+ */
1734
+ declare function flythrough(map: MapMapMap | Map, route: RouteGeometry | ParsedRoute, options?: FlythroughOptions): FlythroughController;
1735
+ /**
1736
+ * Scrollytelling: bind a flythrough to a scroll position, so scrolling a
1737
+ * story column scrubs the camera along the route. The binder pauses the
1738
+ * controller (scroll owns the timeline) and seeks it to the scrolled
1739
+ * fraction - immediately on bind, then on every scroll. Returns the
1740
+ * unbind function.
1741
+ *
1742
+ * ```ts
1743
+ * const replay = flythrough(map, route);
1744
+ * const unbind = bindFlythroughToScroll(replay, document.querySelector("#story")!);
1745
+ * ```
1746
+ */
1747
+ declare function bindFlythroughToScroll(controller: FlythroughController, target?: HTMLElement | Window): () => void;
1748
+
1749
+ /**
1750
+ * IsochroneLayer - walkability rings (reachability contours) on a MapMap
1751
+ * map, backed by the gateway's live `POST /isochrone` endpoint (Valhalla
1752
+ * isochrone; see docs/API.md).
1753
+ *
1754
+ * One call - `showReachability({ origin, mode: "walk", minutes: [5, 10, 15] })` -
1755
+ * renders concentric reachability rings: graduated-opacity fills (nearest
1756
+ * ring strongest), contour outlines, and "N min" labels along each
1757
+ * contour. The estate-agent / store-catchment / "how walkable is this
1758
+ * flat?" view in one line.
1759
+ *
1760
+ * Follows the RouteLayer pattern: gateway base URL/key picked up from a
1761
+ * `MapMapMap` automatically, layers survive `setStyle` theme swaps via
1762
+ * re-installation on `style.load`, and `clear()`/`destroy()` tidy up.
1763
+ */
1764
+
1765
+ /**
1766
+ * Travel mode for the rings. The friendly names map onto gateway costings
1767
+ * (`walk` → `pedestrian`, `cycle` → `bicycle`, `drive` → `auto`); any
1768
+ * other string is passed through as a raw Valhalla costing (e.g.
1769
+ * `"truck"`, `"motor_scooter"`).
1770
+ */
1771
+ type ReachabilityMode = "walk" | "cycle" | "drive" | "truck" | (string & {});
1772
+ interface ShowReachabilityOptions {
1773
+ /** Where the rings radiate from. */
1774
+ origin: LngLatLike;
1775
+ /** Travel mode. Defaults to `"walk"` - these are walkability rings. */
1776
+ mode?: ReachabilityMode;
1777
+ /** Ring contours in minutes, e.g. `[5, 10, 15]`. At least one. */
1778
+ minutes: number[];
1779
+ /** Ring colour. Defaults to MapMap signal blue. */
1780
+ color?: string;
1781
+ /**
1782
+ * Valhalla-style costing options forwarded verbatim, e.g.
1783
+ * `{ pedestrian: { use_lit: 1.0 } }`.
1784
+ */
1785
+ costingOptions?: Record<string, unknown>;
1786
+ }
1787
+ interface IsochroneLayerOptions {
1788
+ /** Gateway base URL. Defaults to the map's `baseUrl` when given a map. */
1789
+ baseUrl?: string;
1790
+ /** Gateway API key. Defaults to the map's `apiKey` when given a map. */
1791
+ apiKey?: string;
1792
+ /** Unique id prefix for the source/layers. Defaults to `"mapmap-isochrone"`. */
1793
+ id?: string;
1794
+ }
1795
+ /** The GeoJSON FeatureCollection the gateway returns, passed through. */
1796
+ interface IsochroneFeatureCollection {
1797
+ type: "FeatureCollection";
1798
+ features: {
1799
+ type: "Feature";
1800
+ geometry: {
1801
+ type: string;
1802
+ coordinates: unknown;
1803
+ };
1804
+ properties?: Record<string, unknown> | null;
1805
+ }[];
1806
+ }
1807
+ /** Renders reachability rings from the gateway's `POST /isochrone`. */
1808
+ declare class IsochroneLayer {
1809
+ private readonly map;
1810
+ private readonly baseUrl;
1811
+ private readonly apiKey;
1812
+ private readonly sourceId;
1813
+ private readonly fillLayerId;
1814
+ private readonly lineLayerId;
1815
+ private readonly labelLayerId;
1816
+ private lastData;
1817
+ private lastMinutes;
1818
+ private lastColor;
1819
+ constructor(map: MapMapMap | Map, options?: IsochroneLayerOptions);
1820
+ private readonly handleStyleLoad;
1821
+ /**
1822
+ * Fetch and render reachability rings. Returns the GeoJSON
1823
+ * FeatureCollection the gateway produced (each feature carries a
1824
+ * `contour` property in minutes), for callers who also want the raw
1825
+ * shapes. Replaces any rings already shown by this layer.
1826
+ */
1827
+ showReachability(options: ShowReachabilityOptions): Promise<IsochroneFeatureCollection>;
1828
+ /** The most recently drawn rings, if any. */
1829
+ get current(): IsochroneFeatureCollection | undefined;
1830
+ /** Draw (or update) a ring collection. Safe before the style has loaded. */
1831
+ private draw;
1832
+ /** Add-or-update the source and ring layers on the current style. */
1833
+ private install;
1834
+ /**
1835
+ * Graduated fill opacity: the nearest contour (fewest minutes) is the
1836
+ * most opaque, the farthest the faintest, interpolated on each
1837
+ * feature's `contour` property. With one contour it is a constant.
1838
+ */
1839
+ private fillOpacity;
1840
+ /** Remove the rings' layers and source from the map. */
1841
+ clear(): void;
1842
+ /** Remove the rings and detach the layer's `style.load` listener. */
1843
+ destroy(): void;
1844
+ }
1845
+
1846
+ /**
1847
+ * Self-diagnosing map errors - the SDK tells you what went wrong and how
1848
+ * to fix it, instead of a silently blank map.
1849
+ *
1850
+ * `MapMapMap` runs these checks at construction (and listens for tile
1851
+ * auth failures); each detected issue produces ONE `console.error` per
1852
+ * page with an actionable message and a docs link. Everything here is
1853
+ * best-effort and defensive: no check may ever throw or require a DOM
1854
+ * (server-side rendering and Node tests just skip the DOM checks).
1855
+ *
1856
+ * Issues covered - the documented gotchas that burn real integrations:
1857
+ * - a container whose height resolves to 0px (MapLibre renders into a
1858
+ * 0-tall canvas: no error, no map)
1859
+ * - a container element that is not attached to the DOM
1860
+ * - two maplibre-gl copies on one page (duplicate dependency /
1861
+ * micro-frontends - breaks the shared WebGL context and styles)
1862
+ * - WebGL unavailable (headless browsers, blocked GPU, old devices)
1863
+ * - missing/invalid API key (a 401 from the tile or routing endpoints)
1864
+ */
1865
+ /** The distinct issues the diagnostics can report. */
1866
+ type DiagnosticIssue = "container-zero-height" | "container-detached" | "duplicate-maplibre" | "webgl-unavailable" | "invalid-api-key";
1867
+ /** Reset the once-per-page dedup - for tests (and hot-reload tooling). */
1868
+ declare function resetDiagnostics(): void;
1869
+ /** What `runMapDiagnostics` needs; everything optional and structural. */
1870
+ interface MapDiagnosticsInput {
1871
+ /** The `container` option as given: an element or an element id. */
1872
+ container?: string | HTMLElement;
1873
+ /**
1874
+ * The constructed map (or any object with a MapLibre-style `on`). Used
1875
+ * to watch for tile-request auth failures.
1876
+ */
1877
+ map?: unknown;
1878
+ /** The maplibre-gl module the SDK is using (duplicate detection). */
1879
+ maplibre?: unknown;
1880
+ /** The gateway API key, if one was configured. */
1881
+ apiKey?: string | undefined;
1882
+ }
1883
+ /**
1884
+ * Run the construction-time checks and attach the 401 watcher. Called by
1885
+ * `MapMapMap` automatically; exported for apps wrapping a raw
1886
+ * `maplibregl.Map` that still want the self-diagnosis.
1887
+ */
1888
+ declare function runMapDiagnostics(input: MapDiagnosticsInput): void;
972
1889
 
973
1890
  /**
974
1891
  * AdrCheck - optional helper for the gateway's `POST /adr/check` endpoint.
@@ -1177,4 +2094,121 @@ declare class GuidanceBanner {
1177
2094
  update(banner: BannerInstruction | null): void;
1178
2095
  }
1179
2096
 
1180
- export { AdrCheck, type AdrCheckOptions, type AdrCheckRequest, type AdrCheckResult, type AdrDimensions, type AdrTunnelCategory, type BannerComponent, type BannerContent, type BannerInstruction, type BuildStyleOptions, type CameraFix, DEFAULT_GLYPHS_URL, DEFAULT_TERRITORY_TILES_URL, FULL_ATTRIBUTION, GuidanceBanner, LOGO_SVG, type LaneIndication, type LayerOverride, type LngLatLike, LogoControl, type LogoOptions, type LogoPosition, MAX_PUCK_IMAGE_BYTES, MapMapMap, type MapMapOptions, type MapMapTheme, NAV_CAMERA_DEFAULTS, type NavBannerDesign, type NavCameraDesign, type NavDesign, type NavPuckDesign, type NavRouteDesign, NavigationCamera, type NavigationCameraMode, type NavigationCameraOptions, type NearestByDriveTimeOptions, OPENMAPTILES_ATTRIBUTION, OSM_ATTRIBUTION, PALETTE_SLOTS, type ParsedRoute, type Place, type PlacePointFeature, type PlaceWithDistance, type PlaceWithDriveTime, type PlacesFeatureCollection, type PlacesIcon, type PlacesInput, PlacesLayer, type PlacesLayerOptions, PositionPuck, type RouteGeometry, RouteLayer, type RouteLayerOptions, type RouteOptions, type RouteProfile, SIGNAL_BLUE, SOURCE_LAYERS, type SpeakOptions, type StepGuidance, type Theme, type TruckParams, type VoiceInstruction, bannerLanes, buildAdrCheckBody, buildRouteQuery, buildRouteUrl, buildStyle, createMap, defaultNavDesign, directionArrow, extractGuidance, formatCoord, formatCoords, haversineDistanceM, navDesignFromTheme, navDesignFromThemeUrl, parseNavDesign, parseOsrmRoute, placesFromGeoJSON, registerPmtilesProtocol, speak, ssmlToText, toLngLat, toPmtilesUrl };
2097
+ /**
2098
+ * Spoken turn-by-turn guidance over the browser's `SpeechSynthesis`.
2099
+ *
2100
+ * {@link VoiceGuidance} consumes the guidance updates emitted by the
2101
+ * `@mapmap/core` wasm `GuidanceSession` (`{ state: "navigating" | "arrived"
2102
+ * | "offRoute", spoken?, severity?, … }`). The core already schedules each
2103
+ * spoken prompt by its trigger distance and repeats it on every update
2104
+ * until it is superseded, so the speaking rule is simple: announce each
2105
+ * `utteranceId` exactly once, the first time it appears.
2106
+ *
2107
+ * Severity (derived in the Rust core — see `InstructionSeverity`) maps to
2108
+ * prosody and earcons:
2109
+ *
2110
+ * - `info` — normal manoeuvres: base rate/pitch, no earcon by default;
2111
+ * - `warning` — approaching a restriction (toll, ferry): slight pitch
2112
+ * lift, warning earcon when configured;
2113
+ * - `critical` — prohibitions (ADR tunnel block, gate): pitch and rate
2114
+ * lift, pending speech cancelled so the prompt is never queued behind
2115
+ * chatter, critical earcon when configured.
2116
+ *
2117
+ * Off-route updates carry no instruction; they play the warning earcon
2118
+ * once per off-route episode (the app recalculates and speech resumes on
2119
+ * the new session).
2120
+ *
2121
+ * Earcons are lazy URLs (see `map-assets/earcons/`, generated by
2122
+ * `scripts/generate-earcons.py`) — nothing is bundled with the SDK.
2123
+ * Everything no-ops safely where `speechSynthesis` is unavailable
2124
+ * (non-browser contexts, some webviews).
2125
+ */
2126
+ /** Instruction urgency, as emitted by the core (`InstructionSeverity`). */
2127
+ type GuidanceSeverity = "info" | "warning" | "critical";
2128
+ /** The spoken prompt of a core guidance update. */
2129
+ interface GuidanceSpokenPrompt {
2130
+ /** Plain text for speech synthesis. */
2131
+ text: string;
2132
+ /** SSML form; browsers reject raw SSML, so it is stripped to text. */
2133
+ ssml?: string | null;
2134
+ /** Metres before the manoeuvre at which the core released the prompt. */
2135
+ triggerDistanceM: number;
2136
+ /** Unique utterance id — the de-duplication key. */
2137
+ utteranceId: string;
2138
+ }
2139
+ /** The subset of a core guidance update that {@link VoiceGuidance} reads. */
2140
+ interface VoiceGuidanceUpdate {
2141
+ /** Guidance state, as emitted by the wasm `GuidanceSession`. */
2142
+ state: "navigating" | "arrived" | "offRoute";
2143
+ /** Spoken prompt currently due (repeated until superseded). */
2144
+ spoken?: GuidanceSpokenPrompt | null;
2145
+ /** Urgency of the current step's instruction (default `info`). */
2146
+ severity?: GuidanceSeverity;
2147
+ /** BCP 47 locale of the instruction text, when the payload carries one. */
2148
+ voiceLocale?: string;
2149
+ }
2150
+ /** Options for {@link VoiceGuidance}. */
2151
+ interface VoiceGuidanceOptions {
2152
+ /** BCP 47 language for utterances, e.g. `en-GB`. An update's
2153
+ * `voiceLocale` takes precedence when present. */
2154
+ lang?: string;
2155
+ /** Base speech rate (0.1–10, default 1). Critical prompts speak
2156
+ * slightly faster than this base. */
2157
+ rate?: number;
2158
+ /** Volume for speech and earcons, 0–1 (default 1). */
2159
+ volume?: number;
2160
+ /** Start muted. */
2161
+ muted?: boolean;
2162
+ /** Severity → earcon audio URL. Absent entries play nothing. */
2163
+ earcons?: Partial<Record<GuidanceSeverity, string>>;
2164
+ }
2165
+ /** Speech parameters for one severity level. */
2166
+ interface SeverityProsody {
2167
+ /** Utterance rate. */
2168
+ rate: number;
2169
+ /** Utterance pitch (0–2, 1 = neutral). */
2170
+ pitch: number;
2171
+ /** Whether pending speech is cancelled before this prompt. */
2172
+ interrupt: boolean;
2173
+ }
2174
+ /**
2175
+ * Prosody for a severity level at a base rate. Pure — exported for tests
2176
+ * and for custom voice layers that want the same house style.
2177
+ */
2178
+ declare function severityProsody(severity: GuidanceSeverity, baseRate?: number): SeverityProsody;
2179
+ /**
2180
+ * Speaks core guidance updates through the browser's `SpeechSynthesis`,
2181
+ * with severity-aware prosody, optional earcons, and mute/volume
2182
+ * controls. Feed every update from the wasm `GuidanceSession` to
2183
+ * {@link VoiceGuidance.update}.
2184
+ */
2185
+ declare class VoiceGuidance {
2186
+ private readonly options;
2187
+ private volume;
2188
+ private mutedState;
2189
+ private lastUtteranceId;
2190
+ private offRouteAnnounced;
2191
+ constructor(options?: VoiceGuidanceOptions);
2192
+ /** Whether this environment can speak at all. */
2193
+ get available(): boolean;
2194
+ /** Whether announcements are currently muted. */
2195
+ get muted(): boolean;
2196
+ /** Mute announcements (also cancels anything mid-utterance). */
2197
+ mute(): void;
2198
+ /** Unmute announcements. Prompts seen while muted are not replayed. */
2199
+ unmute(): void;
2200
+ /** Set the volume for speech and earcons (clamped to 0–1). */
2201
+ setVolume(volume: number): void;
2202
+ /**
2203
+ * Consume one guidance update. Speaks the update's prompt the first
2204
+ * time its `utteranceId` appears; plays the warning earcon once per
2205
+ * off-route episode. Safe to call in any environment.
2206
+ */
2207
+ update(update: VoiceGuidanceUpdate): void;
2208
+ /** Cancel any speech in progress and forget the de-duplication state. */
2209
+ dispose(): void;
2210
+ private speak;
2211
+ private playEarcon;
2212
+ }
2213
+
2214
+ export { AdrCheck, type AdrCheckOptions, type AdrCheckRequest, type AdrCheckResult, type AdrDimensions, type AdrTunnelCategory, type BannerComponent, type BannerContent, type BannerInstruction, type BuildStyleOptions, 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, MapMapMap, type MapMapOptions, type MapMapTheme, NAV_CAMERA_DEFAULTS, type NavBannerDesign, type NavCameraDesign, 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 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, applyPoiDesign, bannerLanes, bearingBetween, bindFlythroughToScroll, buildAdrCheckBody, buildProbeUrl, buildRouteQuery, buildRouteUrl, buildStyle, builtInPoiColor, createMap, createRouteEffect, defaultNavDesign, defaultPoiDesign, directionArrow, effectsFromStyleMetadata, extractGuidance, flythrough, flythroughPose, formatCoord, formatCoords, haversineDistanceM, isNameTextField, languageTextField, lngLatToMercator, navDesignFromTheme, navDesignFromThemeUrl, parseCssColour, parseNavDesign, parseOsrmRoute, parsePoiDesign, placesFromGeoJSON, poiDesignFromTheme, poiDesignFromThemeUrl, poiDesignIsDefault, poiTextColorExpression, prefersReducedMotion, registerPmtilesProtocol, resetDiagnostics, resolveTheme, runMapDiagnostics, setMapLanguage, severityProsody, shortestArcDeg, shortestArcDelta, speak, ssmlToText, sunTimes, tessellateRouteRibbon, toLngLat, toPmtilesUrl, uploadProbeBatch };