@mapmap/maps 0.1.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.
@@ -0,0 +1,1180 @@
1
+ import maplibregl, { StyleSpecification, Map, MapOptions } from 'maplibre-gl';
2
+
3
+ /**
4
+ * The MapMap logo control: a small wordmark on the map, the same
5
+ * convention as Google Maps' and Mapbox's marks. On by default in
6
+ * {@link MapMapMap}; positionable, and removable with `logo: false`
7
+ * (attribution itself is separate and never removable).
8
+ *
9
+ * The mark is an inline SVG (no network fetch, works offline and under
10
+ * strict CSPs): the MapMap brand lockup — the three-colour route mark
11
+ * plus the lowercase wordmark. Height stays at 20px so existing layouts
12
+ * hold.
13
+ */
14
+ /** Corner positions accepted by the logo control. */
15
+ type LogoPosition = "bottom-right" | "bottom-left" | "top-right" | "top-left";
16
+ /** Options for {@link LogoControl}. */
17
+ interface LogoOptions {
18
+ /** Corner to render in. Default `bottom-right`, above the attribution. */
19
+ position?: LogoPosition;
20
+ /** Link target when clicked. Default the MapMap site. */
21
+ href?: string;
22
+ }
23
+ /**
24
+ * The inline wordmark (height 20px): the outline-light lockup — the
25
+ * line-art route mark plus the dark wordmark — over a white halo stroke
26
+ * so it stays legible on any base map.
27
+ */
28
+ declare const LOGO_SVG: string;
29
+ /**
30
+ * A MapLibre `IControl` rendering the MapMap wordmark. Typed structurally
31
+ * (`onAdd`/`onRemove`) so it needs no maplibre-gl import and works with
32
+ * any instance the host app bundles.
33
+ */
34
+ declare class LogoControl {
35
+ private container;
36
+ private readonly href;
37
+ constructor(options?: LogoOptions);
38
+ onAdd(): HTMLElement;
39
+ onRemove(): void;
40
+ }
41
+
42
+ /**
43
+ * MapMap style assembly - pure, browser-free port of the `sn-style` crate.
44
+ *
45
+ * This module mirrors `crates/sn-style` (theme.rs + compile.rs), the
46
+ * canonical MapMap Studio theme engine, so client-side and build-side
47
+ * styles compile identically. A {@link Theme} is a small, agent-friendly
48
+ * JSON document (named palette slots + per-layer overrides) that
49
+ * {@link buildStyle} turns into a full MapLibre style-spec v8 style over
50
+ * the MapMap territory tiles (OpenMapTiles schema, as emitted by
51
+ * `sn-factory`'s Planetiler stage and served as PMTiles).
52
+ *
53
+ * The `pmtiles://` URL prefix is resolved by the pmtiles protocol handler
54
+ * that {@link registerPmtilesProtocol} installs on the MapLibre instance.
55
+ *
56
+ * Attribution: "© OpenStreetMap contributors" (ODbL) and "© OpenMapTiles"
57
+ * (CC-BY 4.0 schema) are legally required and stamped on every compiled
58
+ * style's tile source. There is no theme field that can remove them.
59
+ */
60
+
61
+ /** A loose JSON object - skeleton layers are assembled as plain JSON. */
62
+ type JsonObject = Record<string, unknown>;
63
+ /** Which built-in palette a theme starts from. */
64
+ type MapMapTheme = "light" | "dark";
65
+ /** The OSM attribution string. Legally required (ODbL) - never removed. */
66
+ declare const OSM_ATTRIBUTION = "\u00A9 OpenStreetMap contributors";
67
+ /** The OpenMapTiles schema attribution (CC-BY 4.0). */
68
+ declare const OPENMAPTILES_ATTRIBUTION = "\u00A9 OpenMapTiles";
69
+ /**
70
+ * Combined attribution stamped on every compiled style's tile source.
71
+ * Legally required (ODbL + CC-BY) - enforced, not themable: no theme
72
+ * field can remove it.
73
+ */
74
+ declare const FULL_ATTRIBUTION = "\u00A9 OpenStreetMap contributors \u00A9 OpenMapTiles";
75
+ /** Default glyph endpoint (PBF fontstacks), mirroring `sn_style::DEFAULT_GLYPHS_URL`. */
76
+ declare const DEFAULT_GLYPHS_URL = "https://fonts.mapmap.ai/{fontstack}/{range}.pbf";
77
+ /** A sensible default territory tiles URL (overridable per map). */
78
+ declare const DEFAULT_TERRITORY_TILES_URL = "pmtiles://https://tiles.mapmap.ai/territories/planet.pmtiles";
79
+ /**
80
+ * Every source-layer the Planetiler (OpenMapTiles-schema) tiles emit.
81
+ * The style skeleton covers all of them; extra layers referencing anything
82
+ * else are rejected. Mirrors `sn_style::SOURCE_LAYERS`.
83
+ */
84
+ declare const SOURCE_LAYERS: readonly string[];
85
+ /**
86
+ * The named palette slots as `[slot, lightDefault, darkDefault]`, mirroring
87
+ * `sn_style::palette_slots()`. Order is stable and documentation-worthy: it
88
+ * is the order a Studio UI should present them in.
89
+ */
90
+ declare const PALETTE_SLOTS: readonly (readonly [string, string, string])[];
91
+ /**
92
+ * Per-layer override: merged onto the skeleton layer with the same id.
93
+ * Mirrors `sn_style::LayerOverride`.
94
+ */
95
+ interface LayerOverride {
96
+ /** Hide the layer entirely (`layout.visibility = "none"`). */
97
+ visible?: boolean;
98
+ /** Paint properties merged over the skeleton's (per-key, override wins). */
99
+ paint?: Record<string, unknown>;
100
+ /** Layout properties merged over the skeleton's. */
101
+ layout?: Record<string, unknown>;
102
+ /** Replace the skeleton's filter (per-attribute styling hook). */
103
+ filter?: unknown;
104
+ minzoom?: number;
105
+ maxzoom?: number;
106
+ }
107
+ /**
108
+ * A MapMap Studio theme: the unit users and agents edit, store and publish.
109
+ * Mirrors `sn_style::Theme` (all fields carry serde defaults, so everything
110
+ * is optional here).
111
+ */
112
+ interface Theme {
113
+ /** Human-readable theme name, e.g. `"midnight-fleet"`. */
114
+ name?: string;
115
+ /** Built-in palette to start from. Defaults to `"light"`. */
116
+ base?: MapMapTheme;
117
+ /** Palette overrides by slot name (see {@link PALETTE_SLOTS}). */
118
+ palette?: Record<string, string>;
119
+ /** Per-layer overrides by skeleton layer id. */
120
+ layers?: Record<string, LayerOverride>;
121
+ /**
122
+ * Render buildings as 3D extrusions. Adds a `building-3d` fill-extrusion
123
+ * layer (zoom 15+) driven by the tiles' `render_height` /
124
+ * `render_min_height` attributes, and caps the flat `building` footprint
125
+ * layer at that zoom so the two hand off cleanly (the extrusion rises
126
+ * from flat as you zoom in). Web-safe; native navigation SDKs should
127
+ * leave this off by default until MapLibre Native's fill-extrusion
128
+ * memory use at street zooms is fixed (maplibre-native#4107). Mirrors
129
+ * `Theme::buildings_3d`.
130
+ */
131
+ buildings_3d?: boolean;
132
+ /**
133
+ * Label fonts. Every fontstack must exist on the glyph server; until that
134
+ * ships (P2), leave this at its default (`"Noto Sans Regular"`).
135
+ */
136
+ fonts?: {
137
+ regular?: string;
138
+ };
139
+ /** Override the glyph endpoint (defaults to {@link DEFAULT_GLYPHS_URL}). */
140
+ glyphs?: string;
141
+ /** Optional sprite URL (icons/shields - P2, no default sprite exists). */
142
+ sprite?: string;
143
+ /**
144
+ * Extra fully-formed MapLibre layers appended above the skeleton (below
145
+ * labels). Each must reference the `territory` source and a known
146
+ * source-layer.
147
+ */
148
+ extra_layers?: JsonObject[];
149
+ /**
150
+ * Non-style extras carried by the THEME DOCUMENT, not the compiled
151
+ * style. Studio stores its navigation design block here (`extra.nav`;
152
+ * see `nav-design.ts`). {@link buildStyle} ignores it entirely - the
153
+ * compiled `style.json` is byte-identical with or without it. Mirrors
154
+ * `Theme::extra` in the canonical Rust crate: the hosted styles API
155
+ * stores the block verbatim (bounded at 256 KB serialised) and serves it
156
+ * back from `GET /styles/{id}/theme`, so a Studio design survives
157
+ * publishing - consume it via `createMap` with a theme document or
158
+ * `navDesignFromThemeUrl` with the hosted theme URL.
159
+ */
160
+ extra?: {
161
+ nav?: unknown;
162
+ };
163
+ }
164
+ interface BuildStyleOptions {
165
+ /**
166
+ * Theme to build: a built-in name (`"light"` / `"dark"`) or a full
167
+ * {@link Theme} document. Defaults to `"light"`.
168
+ */
169
+ theme?: MapMapTheme | Theme;
170
+ /**
171
+ * PMTiles URL for the territory tiles. Accepts either a `pmtiles://…` URL
172
+ * or a plain `https://…` URL (the `pmtiles://` prefix is added for you).
173
+ */
174
+ territoryTilesUrl?: string;
175
+ }
176
+ /** Ensure a tiles URL carries the `pmtiles://` protocol prefix. */
177
+ declare function toPmtilesUrl(url: string): string;
178
+ declare function buildStyle(options?: BuildStyleOptions): StyleSpecification;
179
+
180
+ /**
181
+ * Studio navigation-design block (`extra.nav`) - types, defaults and parser.
182
+ *
183
+ * MapMap Studio's Navigation panel designs the turn-by-turn look - route
184
+ * line, current-position puck and banner instruction - and stores the result
185
+ * as a small design-token block under `extra.nav` in the theme JSON it
186
+ * downloads/copies. This module reads that block so the same theme file
187
+ * styles real navigation UI: `RouteLayer`, `PositionPuck` and
188
+ * `GuidanceBanner` all accept a design from it.
189
+ *
190
+ * LOCKSTEP: this file mirrors `website/lib/studio-nav.ts` (the Studio
191
+ * reference parser) - identical interfaces, defaults, clamps and URL rules.
192
+ * Keep the two in sync so a theme previews in Studio exactly as the SDK
193
+ * renders it. Not imported from the website because the packages do not
194
+ * depend on each other.
195
+ *
196
+ * PUBLISH NOTE: the block travels with the THEME DOCUMENT, never with the
197
+ * compiled `style.json`. That includes hosted publishing: the canonical
198
+ * `sn_style` crate stores `extra` verbatim (bounded at 256 KB serialised)
199
+ * and the gateway serves it back from `GET /styles/{id}/theme`, so a design
200
+ * survives Studio's publish flow too. Read it from a theme file you pass to
201
+ * `createMap` / `buildStyle`, or fetch it from a hosted style with
202
+ * {@link navDesignFromThemeUrl} — a compiled style URL alone never carries
203
+ * it.
204
+ */
205
+
206
+ /** Route-line tokens (defaults match {@link RouteLayer}'s built-in look). */
207
+ interface NavRouteDesign {
208
+ /** Line colour. Default is MapMap signal blue (`SIGNAL_BLUE`). */
209
+ color: string;
210
+ /** Line width in px at mid zoom. */
211
+ width: number;
212
+ /** Line opacity, 0-1. */
213
+ opacity: number;
214
+ /** Casing (outline) colour drawn under the line. */
215
+ casingColor: string;
216
+ }
217
+ /** Current-position puck tokens. */
218
+ interface NavPuckDesign {
219
+ /** Puck fill colour. */
220
+ color: string;
221
+ /** Puck diameter in px. */
222
+ size: number;
223
+ /** Whether the heading arrow is drawn on the puck. */
224
+ headingArrow: boolean;
225
+ /**
226
+ * Optional custom puck image (`https:` or `data:` URI, PNG/SVG). When set
227
+ * it replaces the built-in dot + arrow; `size` still scales it. Optional
228
+ * since v1 - themes saved without it load unchanged.
229
+ */
230
+ imageUrl?: string;
231
+ }
232
+ /** Banner-instruction tokens (defaults match {@link GuidanceBanner}). */
233
+ interface NavBannerDesign {
234
+ /** Banner background colour. */
235
+ background: string;
236
+ /** Banner text (and arrow) colour. */
237
+ textColor: string;
238
+ /** Primary text size in px. */
239
+ fontSize: number;
240
+ /** Whether the lane diagram row is shown when lanes are present. */
241
+ showLanes: boolean;
242
+ /** Inner padding in px, 6-24. Optional since v1; default 10. */
243
+ padding?: number;
244
+ /** Corner radius in px, 0-24. Optional since v1; default 10. */
245
+ cornerRadius?: number;
246
+ /** Maximum banner width in px, 200-520. Optional since v1; default 340. */
247
+ maxWidth?: number;
248
+ /**
249
+ * Banner height in px, 40-120. Optional since v1; unset means auto -
250
+ * the banner takes its natural content height (old themes keep theirs).
251
+ */
252
+ height?: number;
253
+ }
254
+ /**
255
+ * Drive-camera tokens: how the chase cam frames a turn-by-turn drive.
256
+ * The whole block is optional since v1 - themes saved without it load
257
+ * unchanged and the SDK keeps its built-in framing. Every field is itself
258
+ * optional (absent = the documented default). `NavigationCamera` reads
259
+ * `pitch`/`zoom` as its option defaults; `speedMps` drives Studio's demo
260
+ * drive (and any route simulator) - a camera following real GPS fixes
261
+ * ignores it.
262
+ */
263
+ interface NavCameraDesign {
264
+ /** Chase-cam tilt in degrees, 0-85. Default 60. */
265
+ pitch?: number;
266
+ /** Chase-cam zoom, 14-20. Default 17.5. */
267
+ zoom?: number;
268
+ /** Drive speed in metres per second, 2-40. Default 12 (~43 km/h). */
269
+ speedMps?: number;
270
+ }
271
+ /**
272
+ * Defaults for the optional `camera` block. LOCKSTEP: mirrors
273
+ * `NAV_CAMERA_DEFAULTS` in `website/lib/studio-nav.ts`.
274
+ */
275
+ declare const NAV_CAMERA_DEFAULTS: {
276
+ readonly pitch: 60;
277
+ readonly zoom: 17.5;
278
+ readonly speedMps: 12;
279
+ };
280
+ /** The `extra.nav` block: navigation design tokens for the SDKs. */
281
+ interface NavDesign {
282
+ /** Block schema version; currently always 1. */
283
+ version: 1;
284
+ route: NavRouteDesign;
285
+ puck: NavPuckDesign;
286
+ banner: NavBannerDesign;
287
+ /**
288
+ * Optional since v1: drive-camera framing (pitch/zoom) and speed. Absent
289
+ * means "SDK built-ins" - the block stays `version: 1` either way.
290
+ */
291
+ camera?: NavCameraDesign;
292
+ }
293
+ /**
294
+ * Defaults mirror what the SDK draws today with no design: `RouteLayer`'s
295
+ * signal-blue line over a dark casing, and `GuidanceBanner`'s dark pill.
296
+ */
297
+ declare function defaultNavDesign(): NavDesign;
298
+ /**
299
+ * Lenient parse of an `extra.nav` value: unknown or malformed fields fall
300
+ * back to the defaults, so an imported theme never fails on its nav block.
301
+ * LOCKSTEP: identical semantics to `parseNavDesign` in
302
+ * `website/lib/studio-nav.ts` (same clamps, same fallbacks).
303
+ */
304
+ declare function parseNavDesign(value: unknown): NavDesign;
305
+ /**
306
+ * Reads and parses the `extra.nav` block from a Studio theme document.
307
+ * Returns `undefined` when the theme carries no nav block at all (so
308
+ * callers can tell "no design" apart from "default design"); a present but
309
+ * malformed block parses leniently to the defaults.
310
+ */
311
+ declare function navDesignFromTheme(theme: Theme | undefined | null): NavDesign | undefined;
312
+ /**
313
+ * Fetches a hosted theme document and reads its `extra.nav` block.
314
+ *
315
+ * Point it at the gateway's theme endpoint - the design a Studio publish
316
+ * stores alongside the compiled style:
317
+ *
318
+ * ```ts
319
+ * const design = await navDesignFromThemeUrl(
320
+ * "https://api.mapmap.ai/styles/midnight-fleet-a1b2c3/theme",
321
+ * );
322
+ * ```
323
+ *
324
+ * The endpoint is public (no API key) and never cached, so this always
325
+ * reflects the latest published version. Returns `undefined` when the
326
+ * theme carries no nav block (same contract as {@link navDesignFromTheme});
327
+ * throws on network failure, a non-2xx response or a non-JSON body.
328
+ *
329
+ * @param fetchImpl Optional `fetch` replacement (tests, Node polyfills).
330
+ * Defaults to the global `fetch`.
331
+ */
332
+ declare function navDesignFromThemeUrl(url: string, fetchImpl?: typeof fetch): Promise<NavDesign | undefined>;
333
+
334
+ /**
335
+ * MapMapMap - the branded MapLibre GL map.
336
+ *
337
+ * A thin wrapper over `maplibregl.Map`. MapLibre does all the rendering; this
338
+ * class only wires in the MapMap default style, registers the pmtiles
339
+ * protocol, and keeps the gateway `apiKey`/`baseUrl` handy so routing and ADR
340
+ * helpers can reuse them.
341
+ */
342
+
343
+ /**
344
+ * Register the `pmtiles://` protocol on a MapLibre instance. Idempotent per
345
+ * maplibre-gl instance, and safe to call before constructing any map.
346
+ * Exposed for apps that build their own `maplibregl.Map` (possibly from
347
+ * their own bundled maplibre-gl copy) but still want to read MapMap PMTiles.
348
+ */
349
+ declare function registerPmtilesProtocol(gl?: typeof maplibregl): void;
350
+ interface MapMapOptions {
351
+ /**
352
+ * The MapMap wordmark on the map (like the Google Maps / Mapbox marks).
353
+ * Default on at bottom-right, above the attribution; `false` removes
354
+ * it, or pass `{ position, href }` to customise.
355
+ */
356
+ logo?: boolean | LogoOptions;
357
+ /** The container element or its id. */
358
+ container: string | HTMLElement;
359
+ /** Gateway API key (`snk_…`), used by routing and ADR helpers. */
360
+ apiKey?: string;
361
+ /** Gateway base URL for routing/ADR. Defaults to `https://api.mapmap.ai`. */
362
+ baseUrl?: string;
363
+ /**
364
+ * Style to load. A theme name (`"light"` / `"dark"`), a MapMap Studio
365
+ * `Theme` document, a full MapLibre `StyleSpecification`, or a style URL.
366
+ * Defaults to `"light"`.
367
+ */
368
+ style?: MapMapTheme | Theme | StyleSpecification | string;
369
+ /** Override the territory PMTiles URL used by the default styles. */
370
+ territoryTilesUrl?: string;
371
+ /** Initial centre as `[lng, lat]`. Defaults to the UK. */
372
+ center?: [number, number];
373
+ /** Initial zoom. Defaults to `5`. */
374
+ zoom?: number;
375
+ /**
376
+ * Extra MapLibre `MapOptions` merged last (escape hatch for hash, bearing,
377
+ * maxBounds, etc.). `container` and `style` here are ignored.
378
+ */
379
+ mapOptions?: Partial<Omit<MapOptions, "container" | "style">>;
380
+ }
381
+ /** The MapMap map: a branded, tiles-and-routing-ready MapLibre map. */
382
+ declare class MapMapMap {
383
+ /** The underlying MapLibre map. Use it for any native MapLibre call. */
384
+ readonly map: Map;
385
+ /** Gateway API key, if provided. */
386
+ readonly apiKey: string | undefined;
387
+ /** Gateway base URL (no trailing slash). */
388
+ readonly baseUrl: string;
389
+ /**
390
+ * The Studio navigation design (`extra.nav`) parsed from the theme passed
391
+ * as `style`, when it carried one. `RouteLayer`, `PositionPuck` and
392
+ * `GuidanceBanner` pick this up as their default look; `undefined` means
393
+ * the theme had no nav block (or `style` was not a theme document).
394
+ */
395
+ readonly navDesign: NavDesign | undefined;
396
+ constructor(options: MapMapOptions);
397
+ /**
398
+ * Toggle 3D building extrusions at runtime — the live equivalent of
399
+ * compiling the style with `buildings_3d: true` (see `Theme`): adds or
400
+ * removes the `building-3d` fill-extrusion layer and hands the flat
401
+ * `building` footprint layer off at zoom 15. The extrusion takes its
402
+ * colour from the current `building` fill, so it follows the theme.
403
+ * Idempotent; safe to call before the style has loaded (it applies on
404
+ * load). Requires the MapMap skeleton's `building` layer — on a custom
405
+ * style without one this throws rather than guessing.
406
+ *
407
+ * Mobile note: leave 3D off in native navigation views for now
408
+ * (maplibre-native#4107 — fill-extrusion memory at street zooms).
409
+ */
410
+ setBuildings3d(enabled: boolean): void;
411
+ /** Resolves once the style and first tiles are loaded. */
412
+ whenReady(): Promise<void>;
413
+ /** Remove the map and release its WebGL context. */
414
+ destroy(): void;
415
+ }
416
+ /** Functional alias for {@link MapMapMap}, mirroring the Mapbox `new Map` feel. */
417
+ declare function createMap(options: MapMapOptions): MapMapMap;
418
+
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
+ /**
539
+ * RouteLayer - fetch an OSRM route from the MapMap gateway and draw it.
540
+ *
541
+ * The route request/parse logic lives in the pure `osrm` module; this class
542
+ * owns the network call and the MapLibre source/layers (a signal-blue line
543
+ * with a darker casing, matching the brand).
544
+ */
545
+
546
+ /** MapMap brand signal blue (see website `--signal`). */
547
+ declare const SIGNAL_BLUE = "#3a86ff";
548
+ interface RouteLayerOptions {
549
+ /** Gateway base URL. Defaults to the map's `baseUrl` when given a map. */
550
+ baseUrl?: string;
551
+ /** Gateway API key. Defaults to the map's `apiKey` when given a map. */
552
+ apiKey?: string;
553
+ /** Unique id prefix for the source/layers. Defaults to `"mapmap-route"`. */
554
+ id?: string;
555
+ /**
556
+ * Studio route design (`extra.nav` `route` block). Defaults to the map's
557
+ * `navDesign.route` when given a `MapMapMap` whose theme carried one;
558
+ * without a design the layer keeps its built-in signal-blue look.
559
+ */
560
+ design?: NavRouteDesign;
561
+ }
562
+ /** Draws MapMap routes on a MapLibre map. */
563
+ declare class RouteLayer {
564
+ private readonly map;
565
+ private readonly baseUrl;
566
+ private readonly apiKey;
567
+ private readonly sourceId;
568
+ private readonly casingLayerId;
569
+ private readonly lineLayerId;
570
+ private readonly design;
571
+ private lastRoute;
572
+ constructor(map: MapMapMap | Map, options?: RouteLayerOptions);
573
+ private readonly handleStyleLoad;
574
+ /**
575
+ * Route from `from` to `to` (with optional intermediate `via` points passed
576
+ * through `options` is not supported here - pass a coordinate list to
577
+ * {@link routePath} for that), draw the line, and return the parsed route.
578
+ */
579
+ route(from: LngLatLike, to: LngLatLike, options?: RouteOptions): Promise<ParsedRoute>;
580
+ /** Route through an ordered list of coordinates and draw the result. */
581
+ routePath(points: LngLatLike[], options?: RouteOptions): Promise<ParsedRoute>;
582
+ /** The most recently drawn route, if any. */
583
+ get current(): ParsedRoute | undefined;
584
+ /**
585
+ * Draw (or update) a parsed route's line on the map. Safe to call before
586
+ * the style has loaded: the route is stored and installed on the next
587
+ * `style.load` instead of throwing MapLibre's "Style is not done loading".
588
+ */
589
+ draw(route: ParsedRoute): void;
590
+ /** Add-or-update the source and layers for a route on the current style. */
591
+ private install;
592
+ /** Remove the route's layers and source from the map. */
593
+ clear(): void;
594
+ /**
595
+ * Remove the route and detach the layer's `style.load` listener. Call
596
+ * when disposing of the layer (the map itself is left untouched).
597
+ */
598
+ destroy(): void;
599
+ }
600
+
601
+ /** Maximum accepted `data:` puck-image payload (matches Studio's upload cap). */
602
+ declare const MAX_PUCK_IMAGE_BYTES: number;
603
+ /** A current-position puck marker for MapMap maps. */
604
+ declare class PositionPuck {
605
+ /** The puck's root DOM element (the Marker element). */
606
+ readonly element: HTMLElement;
607
+ private readonly map;
608
+ private readonly marker;
609
+ private readonly design;
610
+ private added;
611
+ /**
612
+ * Creates the puck (not yet on the map - it appears on the first
613
+ * {@link setLocation}). The design defaults to the map's
614
+ * `navDesign.puck` when given a `MapMapMap` whose theme carried an
615
+ * `extra.nav` block, then to the built-in blue puck.
616
+ */
617
+ constructor(map: MapMapMap | Map, design?: NavPuckDesign);
618
+ /**
619
+ * Moves the puck (adding it to the map on the first call). `headingDeg`
620
+ * rotates the whole element - arrow or custom image - clockwise from
621
+ * north; omit it to keep the previous heading.
622
+ */
623
+ setLocation(location: {
624
+ lat: number;
625
+ lon: number;
626
+ }, headingDeg?: number): void;
627
+ /** Removes the puck from the map. `setLocation` re-adds it. */
628
+ remove(): void;
629
+ }
630
+
631
+ /** One place to show on the map - a store, depot, branch, POI. */
632
+ interface Place {
633
+ /** Stable unique id, e.g. your store number. */
634
+ id: string;
635
+ /** Display name. */
636
+ name: string;
637
+ /** Latitude, degrees. */
638
+ lat: number;
639
+ /** Longitude, degrees. */
640
+ lon: number;
641
+ /**
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`.
645
+ */
646
+ properties?: Record<string, unknown>;
647
+ }
648
+ /** A GeoJSON Point feature, as accepted in a {@link PlacesFeatureCollection}. */
649
+ interface PlacePointFeature {
650
+ type: "Feature";
651
+ id?: string | number;
652
+ geometry: {
653
+ type: "Point";
654
+ coordinates: number[];
655
+ };
656
+ properties?: Record<string, unknown> | null;
657
+ }
658
+ /**
659
+ * A plain GeoJSON FeatureCollection of Points, accepted anywhere a `Place[]`
660
+ * is (structurally compatible with `@types/geojson`'s
661
+ * `FeatureCollection<Point>`). See {@link placesFromGeoJSON} for how ids and
662
+ * names are derived.
663
+ */
664
+ interface PlacesFeatureCollection {
665
+ type: "FeatureCollection";
666
+ features: PlacePointFeature[];
667
+ }
668
+ /** Either input shape for {@link PlacesLayer} / `setPlaces`. */
669
+ type PlacesInput = Place[] | PlacesFeatureCollection;
670
+ /** A place with its straight-line distance attached (see `nearest`). */
671
+ type PlaceWithDistance = Place & {
672
+ /** Straight-line (haversine) distance from the origin, metres. */
673
+ distanceM: number;
674
+ };
675
+ /** A place with gateway drive time attached (see `nearestByDriveTime`). */
676
+ type PlaceWithDriveTime = Place & {
677
+ /** Drive time from the origin, seconds. */
678
+ durationS: number;
679
+ /** Driven distance from the origin, metres (`null` when not returned). */
680
+ distanceM: number | null;
681
+ };
682
+ /** A custom pin image for unclustered places. */
683
+ interface PlacesIcon {
684
+ /** Image URL (any format `Map#loadImage` accepts, e.g. PNG). */
685
+ url: string;
686
+ /** MapLibre `icon-size` scale factor. Defaults to `1`. */
687
+ size?: number;
688
+ }
689
+ /** Options for {@link PlacesLayer.nearestByDriveTime}. */
690
+ interface NearestByDriveTimeOptions {
691
+ /** How many places to return. Defaults to `1`. */
692
+ n?: number;
693
+ /**
694
+ * Gateway costing model, e.g. `"auto"` (default) or `"truck"` (Premium
695
+ * price class - see `POST /matrix` in the API docs).
696
+ */
697
+ costing?: string;
698
+ /**
699
+ * Valhalla-style costing options forwarded verbatim, e.g.
700
+ * `{ truck: { height: 4.0 } }`.
701
+ */
702
+ costingOptions?: Record<string, unknown>;
703
+ }
704
+ interface PlacesLayerOptions {
705
+ /** Initial places. Also settable later via {@link PlacesLayer.setPlaces}. */
706
+ places?: PlacesInput;
707
+ /** Cluster nearby places into count badges. Defaults to `true`. */
708
+ cluster?: boolean;
709
+ /** Cluster radius in pixels. Defaults to MapLibre's `50`. */
710
+ clusterRadius?: number;
711
+ /** Max zoom to cluster at. Defaults to MapLibre's `14`. */
712
+ clusterMaxZoom?: number;
713
+ /** Pin and cluster colour. Defaults to MapMap signal blue (`#3a86ff`). */
714
+ color?: string;
715
+ /**
716
+ * Custom pin image for unclustered places (a symbol layer instead of the
717
+ * default circle). If the image fails to load, the circle look is used.
718
+ */
719
+ icon?: PlacesIcon;
720
+ /**
721
+ * Fit the map to the places the first time they are set. Defaults to
722
+ * `false` (the map view is left alone).
723
+ */
724
+ fitBounds?: boolean;
725
+ /** Unique id prefix for the source/layers. Defaults to `"mapmap-places"`. */
726
+ id?: string;
727
+ /** Gateway base URL. Defaults to the map's `baseUrl` when given a map. */
728
+ baseUrl?: string;
729
+ /** Gateway API key. Defaults to the map's `apiKey` when given a map. */
730
+ apiKey?: string;
731
+ /** Called when an individual (unclustered) place is clicked. */
732
+ onPlaceClick?: (place: Place, lngLat: {
733
+ lng: number;
734
+ lat: number;
735
+ }) => void;
736
+ /**
737
+ * Built-in popup: return HTML (a string is set with `setHTML`, an element
738
+ * with `setDOMContent`) and a `maplibregl.Popup` opens at the place on
739
+ * click. Omit for no popup.
740
+ */
741
+ popup?: (place: Place) => string | HTMLElement;
742
+ }
743
+ /**
744
+ * Normalise a GeoJSON FeatureCollection of Points to `Place[]`. The id comes
745
+ * from `feature.id`, then `properties.id`, then the feature index; the name
746
+ * from `properties.name` (else `""`). Non-Point features are skipped.
747
+ */
748
+ declare function placesFromGeoJSON(collection: PlacesFeatureCollection): Place[];
749
+ /** Straight-line (haversine) distance between two points, metres. */
750
+ declare function haversineDistanceM(a: {
751
+ lat: number;
752
+ lon: number;
753
+ }, b: {
754
+ lat: number;
755
+ lon: number;
756
+ }): number;
757
+ /** Draws customer places on a MapLibre map, with clustering and popups. */
758
+ declare class PlacesLayer {
759
+ private readonly map;
760
+ private readonly baseUrl;
761
+ private readonly apiKey;
762
+ private readonly sourceId;
763
+ private readonly clustersLayerId;
764
+ private readonly clusterCountLayerId;
765
+ private readonly pointsLayerId;
766
+ private readonly iconImageId;
767
+ private readonly cluster;
768
+ private readonly clusterRadius;
769
+ private readonly clusterMaxZoom;
770
+ private readonly color;
771
+ private readonly icon;
772
+ private readonly wantFitBounds;
773
+ private readonly onPlaceClick;
774
+ private readonly popupFn;
775
+ private places;
776
+ private data;
777
+ private popup;
778
+ private fitted;
779
+ private destroyed;
780
+ constructor(map: MapMapMap | Map, options?: PlacesLayerOptions);
781
+ private readonly handleStyleLoad;
782
+ private readonly handleMouseEnter;
783
+ private readonly handleMouseLeave;
784
+ private readonly handlePlaceClick;
785
+ private readonly handleClusterClick;
786
+ /**
787
+ * 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.
791
+ */
792
+ setPlaces(places: PlacesInput): void;
793
+ /** The layer's current places (normalised to `Place[]`). */
794
+ get current(): Place[];
795
+ /**
796
+ * The nearest `n` places (default `1`) to `origin` by straight-line
797
+ * (haversine) distance, each with `distanceM` attached. Pure and instant -
798
+ * no network. For road-network answers use {@link nearestByDriveTime}.
799
+ */
800
+ nearest(origin: {
801
+ lat: number;
802
+ lon: number;
803
+ }, n?: number): PlaceWithDistance[];
804
+ /**
805
+ * The nearest `n` places (default `1`) to `origin` by drive time, via the
806
+ * gateway's `POST /matrix` (one source, all places as targets). Resolves
807
+ * to places sorted by `durationS` ascending, with the driven `distanceM`
808
+ * attached; unreachable places (`null` duration) are dropped. Needs the
809
+ * gateway `baseUrl`/`apiKey` - picked up from a `MapMapMap` automatically,
810
+ * or pass them as layer options.
811
+ */
812
+ nearestByDriveTime(origin: {
813
+ lat: number;
814
+ lon: number;
815
+ }, options?: NearestByDriveTimeOptions): Promise<PlaceWithDriveTime[]>;
816
+ /** Add-or-update the source and layers on the current style. */
817
+ private install;
818
+ /** Cluster circles (sized by count) and the count badge on top. */
819
+ private installClusterLayers;
820
+ /**
821
+ * The unclustered-places layer: a circle pin by default, or a symbol
822
+ * layer once the custom `icon` image has loaded (falling back to the
823
+ * circle look if it fails, like PositionPuck's image fallback).
824
+ */
825
+ private installPointsLayer;
826
+ private addCirclePointsLayer;
827
+ private addIconPointsLayer;
828
+ /** Open the built-in popup for a place, replacing any previous one. */
829
+ private openPopup;
830
+ /** Remove the places' layers, source, image and popup from the map. */
831
+ clear(): void;
832
+ /**
833
+ * Remove the places and detach every listener (style.load, clicks,
834
+ * hovers). Call when disposing of the layer (the map is left untouched).
835
+ */
836
+ destroy(): void;
837
+ }
838
+
839
+ /**
840
+ * NavigationCamera - the turnkey chase-cam for turn-by-turn navigation.
841
+ *
842
+ * Packages the Google-Maps-style follow camera on top of MapLibre's native
843
+ * camera: per-fix `easeTo` with linear easing (continuous glide, no
844
+ * rubber-banding), course-up bearing, a tilted pitch preset, and a
845
+ * screen-offset that anchors the puck low-centre so the camera looks ahead.
846
+ * User gestures (drag, rotate, pitch, zoom) switch the camera to `"free"`
847
+ * mode; it auto-recentres after a configurable idle period. An `overview()`
848
+ * mode fits the whole route top-down, and `resume()` returns to the chase
849
+ * cam at the last known fix.
850
+ *
851
+ * Like `RouteLayer`, the class holds only a structural handle on the map
852
+ * (`easeTo`, `fitBounds`, `stop`, events, container) so it is unit-testable
853
+ * against a fake without a WebGL context.
854
+ *
855
+ * Globe projection note: {@link NavigationCamera.isSupported} returns
856
+ * `false` when the map runs the globe (or vertical-perspective) projection.
857
+ * At navigation zooms globe renders much like mercator, but the pixel-offset
858
+ * anchor maths and the top-down overview framing assume a mercator camera;
859
+ * switch the map to mercator (`map.setProjection({ type: "mercator" })`)
860
+ * before navigating, or accept an unanchored follow.
861
+ */
862
+
863
+ /** A GPS fix to follow. */
864
+ interface CameraFix {
865
+ lat: number;
866
+ lon: number;
867
+ }
868
+ /** The camera's current behaviour. */
869
+ type NavigationCameraMode = "follow" | "overview" | "free";
870
+ interface NavigationCameraOptions {
871
+ /**
872
+ * Camera tilt in degrees, clamped to 0-85. Default `60` - MapLibre's
873
+ * non-experimental ceiling; values above 60 may show rendering artefacts
874
+ * and flatten DOM-marker pucks. When the requested pitch exceeds the
875
+ * map's `maxPitch`, the limit is raised to match.
876
+ *
877
+ * Precedence: this option > the Studio design's `camera.pitch` (when the
878
+ * map is a `MapMapMap` whose theme carried `extra.nav.camera`) > the
879
+ * built-in default.
880
+ */
881
+ pitch?: number;
882
+ /**
883
+ * Follow-mode zoom. Default `17`. Same precedence as `pitch`: option >
884
+ * Studio design `camera.zoom` > built-in default.
885
+ */
886
+ zoom?: number;
887
+ /**
888
+ * Where the fix (and puck) sits vertically on screen, as a fraction of
889
+ * the container height from the top, clamped to 0-1. Default `0.72` -
890
+ * the puck rides low so the camera looks up the road ahead.
891
+ */
892
+ anchorY?: number;
893
+ /**
894
+ * Ease duration per {@link NavigationCamera.follow} call, milliseconds.
895
+ * Default `900`, and always capped by the observed fix interval so a
896
+ * fast fix cadence never queues up lagging animations.
897
+ */
898
+ easeMs?: number;
899
+ /**
900
+ * Idle time after the last user gesture before the camera recentres
901
+ * itself back to follow mode, milliseconds. Default `6000`; `0` disables
902
+ * auto-recentre (the app must call {@link NavigationCamera.resume}).
903
+ */
904
+ autoRecentreMs?: number;
905
+ }
906
+ /** Anything with a `PositionPuck`-shaped `setLocation` can be co-driven. */
907
+ type CoDrivenPuck = Pick<PositionPuck, "setLocation">;
908
+ /** A follow-mode chase camera for MapMap maps. */
909
+ declare class NavigationCamera {
910
+ private readonly m;
911
+ private readonly pitch;
912
+ private readonly zoom;
913
+ private readonly anchorY;
914
+ private readonly easeMs;
915
+ private readonly autoRecentreMs;
916
+ private currentMode;
917
+ private puck;
918
+ private lastFix;
919
+ private lastCourse;
920
+ private lastFixAt;
921
+ private lastRoute;
922
+ private recentreTimer;
923
+ private destroyed;
924
+ private readonly onInteraction;
925
+ /**
926
+ * `false` when the map runs the globe (or vertical-perspective)
927
+ * projection, whose camera geometry breaks the low-anchor offset maths
928
+ * and top-down overview framing. Switch to mercator for navigation.
929
+ */
930
+ static isSupported(map: MapMapMap | Map): boolean;
931
+ constructor(map: MapMapMap | Map, options?: NavigationCameraOptions);
932
+ /** The camera's current behaviour: `"follow"`, `"overview"` or `"free"`. */
933
+ get mode(): NavigationCameraMode;
934
+ /**
935
+ * Co-drive a {@link PositionPuck}: every {@link follow} call also
936
+ * `setLocation`s the puck, so camera and puck never diverge.
937
+ */
938
+ attachPuck(puck: CoDrivenPuck): void;
939
+ /**
940
+ * Glide the camera to a fix, course-up when `courseDeg` is given
941
+ * (bearing is left unchanged when it is `undefined`). Each call eases
942
+ * with linear timing over `easeMs` capped by the observed fix interval,
943
+ * and interrupts the previous ease (no queue buildup). The fix and an
944
+ * attached puck are always updated, but the camera only moves in
945
+ * `"follow"` mode - `"free"` and `"overview"` record the fix for
946
+ * {@link resume}.
947
+ */
948
+ follow(fix: CameraFix, courseDeg?: number): void;
949
+ /**
950
+ * Fit the route top-down (pitch 0, north-up). Pass the route geometry on
951
+ * the first call; later calls reuse the last geometry given. A no-op
952
+ * when no geometry has ever been provided.
953
+ */
954
+ overview(routeGeometry?: RouteGeometry): void;
955
+ /**
956
+ * Return to follow mode, gliding back to the last fix (with its last
957
+ * known course) when one has been recorded.
958
+ */
959
+ resume(): void;
960
+ /** Remove the map listeners and cancel any pending auto-recentre. */
961
+ destroy(): void;
962
+ private easeToFix;
963
+ /**
964
+ * The pixel offset that renders the followed fix at `anchorY` of the
965
+ * container height: `(anchorY - 0.5) * height` (0.5 is the natural
966
+ * centre; positive y moves the puck down the screen).
967
+ */
968
+ private anchorOffsetY;
969
+ private scheduleRecentre;
970
+ private clearRecentreTimer;
971
+ }
972
+
973
+ /**
974
+ * AdrCheck - optional helper for the gateway's `POST /adr/check` endpoint.
975
+ *
976
+ * The ADR 8.6.4 tunnel compliance engine, exposed directly. No MapLibre
977
+ * dependency; just a typed fetch wrapper.
978
+ */
979
+
980
+ /**
981
+ * Builds the exact wire body the gateway deserialises
982
+ * (`sn-gateway` `routes/adr.rs` → `sn_adr::AdrVehicleProfile`):
983
+ * `{"adr": {"dimensions": {…}, "tunnel_code"?, "hazmat"}, "tunnel_category"}`.
984
+ * Unset dimensions default to the EU 96/53/EC artic maximums, mirroring
985
+ * `TruckDimensions::default()`. Exported for contract tests.
986
+ */
987
+ declare function buildAdrCheckBody(request: AdrCheckRequest): Record<string, unknown>;
988
+ interface AdrCheckOptions {
989
+ /** Gateway base URL, e.g. `https://api.mapmap.ai`. */
990
+ baseUrl: string;
991
+ /** Gateway API key (`snk_…`). */
992
+ apiKey?: string;
993
+ }
994
+ /** Calls `POST /adr/check` and returns the parsed compliance decision. */
995
+ declare class AdrCheck {
996
+ private readonly baseUrl;
997
+ private readonly apiKey;
998
+ constructor(options: AdrCheckOptions);
999
+ check(request: AdrCheckRequest): Promise<AdrCheckResult>;
1000
+ }
1001
+
1002
+ /** Guidance-related query options. */
1003
+ interface GuidanceQuery {
1004
+ voice?: boolean;
1005
+ banner?: boolean;
1006
+ language?: string;
1007
+ }
1008
+ /** Build the OSRM query string (without the leading `?`) for a request. */
1009
+ declare function buildRouteQuery(truck?: TruckParams, guidance?: GuidanceQuery): string;
1010
+ /**
1011
+ * Build the full OSRM route URL. `baseUrl` is the gateway origin (trailing
1012
+ * slashes are trimmed); `points` is the ordered coordinate path.
1013
+ */
1014
+ declare function buildRouteUrl(baseUrl: string, profile: RouteProfile, points: LngLatLike[], truck?: TruckParams, guidance?: GuidanceQuery): string;
1015
+ /**
1016
+ * Parse an OSRM route response into a {@link ParsedRoute}. Throws with the
1017
+ * OSRM `code`/`message` when the response is not `Ok` or carries no route.
1018
+ */
1019
+ declare function parseOsrmRoute(body: unknown): ParsedRoute;
1020
+
1021
+ /**
1022
+ * Coordinate helpers - pure, browser-free logic.
1023
+ *
1024
+ * The gateway speaks the OSRM HTTP API: coordinates are `lon,lat` pairs
1025
+ * separated by `;`. MapLibre and GeoJSON use `[lng, lat]` order, so the one
1026
+ * job here is to normalise the three accepted input shapes into that wire
1027
+ * format without ever swapping the axes by accident.
1028
+ */
1029
+
1030
+ /** Normalise any accepted shape to a `[lng, lat]` tuple. */
1031
+ declare function toLngLat(point: LngLatLike): [number, number];
1032
+ /** Format one point as an OSRM `lon,lat` coordinate. */
1033
+ declare function formatCoord(point: LngLatLike): string;
1034
+ /**
1035
+ * Format an ordered list of points as an OSRM coordinate path
1036
+ * (`lon,lat;lon,lat;…`). At least two points are required, matching the
1037
+ * gateway, which rejects single-coordinate requests.
1038
+ */
1039
+ declare function formatCoords(points: LngLatLike[]): string;
1040
+
1041
+ /**
1042
+ * Turn-by-turn guidance helpers: voice + banner instructions and lanes.
1043
+ *
1044
+ * The gateway's OSRM endpoint returns Mapbox-shaped `voiceInstructions`
1045
+ * (plain + SSML announcements with distance triggers) and
1046
+ * `bannerInstructions` (primary line with a maneuver glyph, lane
1047
+ * sub-banners) on each step when requested with
1048
+ * `voice: true` / `banner: true` route options. This module extracts them
1049
+ * from a parsed route, renders a banner as a DOM element, and speaks
1050
+ * announcements through the browser's `SpeechSynthesis`.
1051
+ *
1052
+ * Everything except {@link GuidanceBanner} and {@link speak} is pure and
1053
+ * covered by unit tests.
1054
+ */
1055
+
1056
+ /** A spoken instruction with its trigger distance before the maneuver. */
1057
+ interface VoiceInstruction {
1058
+ /** Metres before the end of the step at which to announce. */
1059
+ distanceAlongGeometry: number;
1060
+ /** Plain text for speech synthesis. */
1061
+ announcement: string;
1062
+ /** SSML form, preferred by engines that support it. */
1063
+ ssmlAnnouncement?: string;
1064
+ }
1065
+ /** One lane entry of a banner lane diagram. */
1066
+ interface LaneIndication {
1067
+ /** Turn directions this lane allows (OSRM indication strings). */
1068
+ directions: string[];
1069
+ /**
1070
+ * Whether the lane can be used for the maneuver. `undefined` means
1071
+ * unknown, not unusable — banner components omit the flag for
1072
+ * non-recommended lanes (see {@link bannerLanes}).
1073
+ */
1074
+ valid?: boolean;
1075
+ /** Whether guidance recommends this lane. */
1076
+ active?: boolean;
1077
+ }
1078
+ /** A typed fragment of banner content. */
1079
+ interface BannerComponent {
1080
+ type: string;
1081
+ text: string;
1082
+ directions?: string[];
1083
+ active?: boolean;
1084
+ }
1085
+ /** One banner line. */
1086
+ interface BannerContent {
1087
+ text: string;
1088
+ type?: string;
1089
+ modifier?: string;
1090
+ components?: BannerComponent[];
1091
+ }
1092
+ /** The visual banner shown while travelling a step. */
1093
+ interface BannerInstruction {
1094
+ distanceAlongGeometry: number;
1095
+ primary: BannerContent;
1096
+ secondary?: BannerContent;
1097
+ sub?: BannerContent;
1098
+ }
1099
+ /** Guidance data for one step of the route. */
1100
+ interface StepGuidance {
1101
+ /** Step index within the flattened route. */
1102
+ stepIndex: number;
1103
+ /** Step length in metres. */
1104
+ distanceM: number;
1105
+ /**
1106
+ * Written instruction for the step's upcoming maneuver. Taken from
1107
+ * `maneuver.instruction` when a server emits it (Mapbox-style); the
1108
+ * MapMap gateway does not, so it falls back to the step's final voice
1109
+ * announcement (the pre-transition instruction) and then to the banner
1110
+ * primary text. Undefined only when the step carries no guidance at all.
1111
+ */
1112
+ instruction?: string;
1113
+ /** Spoken announcements, ordered by descending trigger distance. */
1114
+ voice: VoiceInstruction[];
1115
+ /** Visual banners for the upcoming maneuver. */
1116
+ banners: BannerInstruction[];
1117
+ }
1118
+ /**
1119
+ * Extracts per-step guidance from a parsed route (requires the route to
1120
+ * have been requested with `steps: true` and voice/banner enabled).
1121
+ * Steps without instructions yield empty arrays, never holes.
1122
+ */
1123
+ declare function extractGuidance(route: ParsedRoute): StepGuidance[];
1124
+ /**
1125
+ * Strips SSML tags down to speakable plain text. Entities are decoded in a
1126
+ * single pass (never re-scanning already-decoded output), so text whose
1127
+ * literal content looks like an entity — e.g. `&amp;lt;`, the gateway's
1128
+ * escape of a literal `&lt;` — round-trips correctly. Decodes the five
1129
+ * named XML entities plus decimal numeric references (`&#39;`).
1130
+ */
1131
+ declare function ssmlToText(ssml: string): string;
1132
+ /**
1133
+ * Lane entries of a banner's sub line, if it carries a lane diagram.
1134
+ *
1135
+ * Banner lane components only carry `active` (the recommended lane) — the
1136
+ * gateway drops the per-lane `valid` flag from banners, keeping it in the
1137
+ * raw route's `intersections[].lanes`. An active lane is by definition
1138
+ * usable, so `valid` is `true` for active lanes and *unknown* (undefined,
1139
+ * never `false`) for the rest: a non-recommended lane may still be legal.
1140
+ * Read `route.raw` `legs[].steps[].intersections[].lanes` for the full
1141
+ * `valid` set.
1142
+ */
1143
+ declare function bannerLanes(banner: BannerInstruction): LaneIndication[];
1144
+ /**
1145
+ * Unicode arrow for a lane/maneuver direction (OSRM indication strings).
1146
+ * A text-only fallback; sprite-based lane icons ship with the map assets.
1147
+ */
1148
+ declare function directionArrow(direction: string): string;
1149
+ /** Options for {@link speak}. */
1150
+ interface SpeakOptions {
1151
+ /** BCP 47 language for the utterance, e.g. `en-GB`. */
1152
+ lang?: string;
1153
+ /** Speech rate (0.1–10, default 1). */
1154
+ rate?: number;
1155
+ }
1156
+ /**
1157
+ * Speaks a voice instruction through the browser's `SpeechSynthesis`.
1158
+ * Prefers the SSML body's text (browsers do not accept raw SSML). Returns
1159
+ * false when speech synthesis is unavailable (e.g. non-browser contexts).
1160
+ * De-duplication is the caller's responsibility: speak each instruction
1161
+ * once as its trigger distance is crossed.
1162
+ */
1163
+ declare function speak(instruction: VoiceInstruction, options?: SpeakOptions): boolean;
1164
+ /**
1165
+ * A minimal DOM banner: primary text with a maneuver arrow, optional lane
1166
+ * row. Style with the `mapmap-banner` / `mapmap-banner-lanes` classes, pass
1167
+ * a Studio design (`extra.nav` `banner` block, e.g. `map.navDesign?.banner`)
1168
+ * to match a Studio theme, or read {@link StepGuidance} yourself for fully
1169
+ * custom UI.
1170
+ */
1171
+ declare class GuidanceBanner {
1172
+ /** The banner root element; attach it wherever your UI needs it. */
1173
+ readonly element: HTMLElement;
1174
+ private readonly design;
1175
+ constructor(container?: HTMLElement, design?: NavBannerDesign);
1176
+ /** Renders a banner instruction (or hides the element with `null`). */
1177
+ update(banner: BannerInstruction | null): void;
1178
+ }
1179
+
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 };