@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/LICENSE +3 -3
- package/README.md +166 -4
- package/dist/index.d.ts +1167 -133
- package/dist/index.js +1756 -76
- package/dist/index.js.map +1 -1
- package/llms-sdk.txt +131 -0
- package/package.json +4 -3
package/LICENSE
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
MapMap Maps SDK (@mapmap/maps)
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026
|
|
3
|
+
Copyright (c) 2026 Mapmap AI Ltd. All rights reserved.
|
|
4
4
|
|
|
5
|
-
This package is proprietary first-party software of
|
|
5
|
+
This package is proprietary first-party software of Mapmap AI Ltd, distributed
|
|
6
6
|
under a commercial licence. It is part of the MapMap
|
|
7
7
|
product. Use of this package requires a written commercial agreement with
|
|
8
|
-
|
|
8
|
+
Mapmap AI Ltd and a valid MapMap gateway API key; it is licensed per fleet
|
|
9
9
|
vehicle or per monthly-active-user (see docs/SDK-DISTRIBUTION.md). No licence,
|
|
10
10
|
express or implied, is granted except under that agreement.
|
|
11
11
|
|
package/README.md
CHANGED
|
@@ -22,6 +22,11 @@ shared copy (two MapLibre instances on one page break the WebGL context).
|
|
|
22
22
|
You also need a MapMap gateway API key (`snk_…`). See
|
|
23
23
|
[`docs/SDK-DISTRIBUTION.md`](../docs/SDK-DISTRIBUTION.md) for licensing.
|
|
24
24
|
|
|
25
|
+
**Building with an AI agent?** The package ships
|
|
26
|
+
[`llms-sdk.txt`](./llms-sdk.txt) - a concise, agent-facing integration
|
|
27
|
+
guide (init, key issuance, routing, places, flythrough, effects,
|
|
28
|
+
isochrones, and the classic gotchas). Point your coding agent at it.
|
|
29
|
+
|
|
25
30
|
## Quickstart
|
|
26
31
|
|
|
27
32
|
```ts
|
|
@@ -114,6 +119,12 @@ puck.remove();
|
|
|
114
119
|
|
|
115
120
|
### `class PlacesLayer` - places / store finder
|
|
116
121
|
|
|
122
|
+
> **⚠️ Give the map container an explicit height first.** MapLibre silently
|
|
123
|
+
> renders into a 0px-tall canvas when the container's height resolves to
|
|
124
|
+
> zero (a bare `<div id="map">` with no CSS) - no error, no map, no pins.
|
|
125
|
+
> Set `#map { height: 100vh; }` (or any real height) before debugging
|
|
126
|
+
> anything else.
|
|
127
|
+
|
|
117
128
|
Drop your own places (e.g. 300 store locations) onto the map: clustered
|
|
118
129
|
pins, popups, and "nearest branch" answers by straight line or by drive
|
|
119
130
|
time (via the gateway's `POST /matrix`).
|
|
@@ -128,15 +139,18 @@ const stores = new PlacesLayer(map /* MapMapMap or maplibregl.Map */, {
|
|
|
128
139
|
// …or a plain GeoJSON FeatureCollection of Points
|
|
129
140
|
],
|
|
130
141
|
cluster: true, // default; clusterRadius?, clusterMaxZoom?
|
|
131
|
-
color: "#ff6b35", // pin
|
|
142
|
+
color: "#ff6b35", // pin colour: string or expression (below)
|
|
132
143
|
fitBounds: true, // fit the view to the places on first set
|
|
133
144
|
popup: (place) => `<strong>${place.name}</strong>`,
|
|
134
145
|
onPlaceClick: (place, lngLat) => console.log(place.id, lngLat),
|
|
135
146
|
});
|
|
136
147
|
|
|
137
|
-
stores.setPlaces(nextPlaces); // replace data
|
|
148
|
+
stores.setPlaces(nextPlaces); // replace data - never dropped
|
|
149
|
+
stores.select("man-01"); // list→map sync: popup + camera
|
|
150
|
+
stores.deselect(); // close the popup
|
|
138
151
|
stores.nearest({ lat: 51.5, lon: -0.13 }, 3); // haversine, adds distanceM
|
|
139
152
|
await stores.nearestByDriveTime({ lat: 51.5, lon: -0.13 }, { n: 3, costing: "truck" });
|
|
153
|
+
stores.ids; // { source, points, clusters, clusterCounts } - see below
|
|
140
154
|
stores.clear(); // remove pins; stores.destroy() also detaches listeners
|
|
141
155
|
```
|
|
142
156
|
|
|
@@ -146,6 +160,71 @@ circle pin if it fails to load). `nearestByDriveTime` sorts by `durationS`
|
|
|
146
160
|
(seconds, driven `distanceM` attached, unreachable places dropped) and reuses
|
|
147
161
|
the map's `baseUrl`/`apiKey` - or pass them as layer options.
|
|
148
162
|
|
|
163
|
+
`setPlaces` never silently drops an update: once the source exists the data
|
|
164
|
+
is applied immediately - even mid-render, while `map.isStyleLoaded()` is
|
|
165
|
+
transiently `false` - and calls made before the style has first loaded are
|
|
166
|
+
stashed (the latest one wins) and installed on `style.load`.
|
|
167
|
+
Search-as-you-type just works.
|
|
168
|
+
|
|
169
|
+
#### Per-category pin colours
|
|
170
|
+
|
|
171
|
+
`color` also accepts a MapLibre expression, evaluated against each
|
|
172
|
+
feature's `properties`. Everything in a place's `properties` is copied
|
|
173
|
+
verbatim onto the **top level** of its GeoJSON feature's properties,
|
|
174
|
+
alongside the reserved `id`, `name` and `__mapmapIndex` keys (which win on
|
|
175
|
+
collision) - so a scalar like `category` is directly `["get", …]`-able.
|
|
176
|
+
MapLibre JSON-stringifies nested objects/arrays at render time, so keep
|
|
177
|
+
anything you want to style on as a top-level string/number/boolean:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
const stores = new PlacesLayer(map, {
|
|
181
|
+
places: [
|
|
182
|
+
{ id: "s1", name: "Corner Deli", lat: 51.5, lon: -0.1,
|
|
183
|
+
properties: { category: "food" } },
|
|
184
|
+
{ id: "s2", name: "St Pancras", lat: 51.53, lon: -0.126,
|
|
185
|
+
properties: { category: "travel" } },
|
|
186
|
+
],
|
|
187
|
+
color: [
|
|
188
|
+
"match", ["get", "category"],
|
|
189
|
+
"food", "#e63946",
|
|
190
|
+
"travel", "#457b9d",
|
|
191
|
+
"postoffice", "#d90429",
|
|
192
|
+
"childcare", "#ffb703",
|
|
193
|
+
/* fallback */ "#3a86ff",
|
|
194
|
+
],
|
|
195
|
+
clusterColor: "#3a86ff", // clusters mix categories → plain colour only
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
A cluster mixes categories, so an expression never applies to cluster
|
|
200
|
+
circles: they use `clusterColor`, which defaults to `color` when that is a
|
|
201
|
+
plain string, else to MapMap signal blue. (With a custom `icon`, pins are
|
|
202
|
+
images - `color` only styles the default circle pins.)
|
|
203
|
+
|
|
204
|
+
#### Programmatic selection (list → map)
|
|
205
|
+
|
|
206
|
+
`select(id, options?)` syncs a results list to the map: it opens the
|
|
207
|
+
configured `popup` at the place (`popup: false` to skip) and eases the
|
|
208
|
+
camera to it (`flyTo: false` to skip; `zoom` to also zoom in). It returns
|
|
209
|
+
the `Place`, or `undefined` for an unknown id (nothing happens). It does
|
|
210
|
+
**not** call `onPlaceClick` - programmatic selection is not a user click.
|
|
211
|
+
`deselect()` closes any open popup.
|
|
212
|
+
|
|
213
|
+
#### Escape-hatch styling: `ids`
|
|
214
|
+
|
|
215
|
+
The generated MapLibre ids are public API via `stores.ids` →
|
|
216
|
+
`{ source, points, clusters, clusterCounts }`, so raw MapLibre calls are
|
|
217
|
+
supported when the options don't reach far enough:
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
map.setPaintProperty(stores.ids.points, "circle-radius", 9);
|
|
221
|
+
map.queryRenderedFeatures({ layers: [stores.ids.points] });
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`clusters`/`clusterCounts` are only installed with `cluster: true` (the
|
|
225
|
+
default); `points` is a circle layer by default, or a symbol layer once a
|
|
226
|
+
custom `icon` has loaded.
|
|
227
|
+
|
|
149
228
|
### `class NavigationCamera`
|
|
150
229
|
|
|
151
230
|
The turnkey chase cam: follows each GPS fix course-up, tilted, with the
|
|
@@ -303,6 +382,89 @@ puck.setLocation({ lat: 51.5074, lon: -0.1278 }, 45); // heading in degrees
|
|
|
303
382
|
payloads capped at 64 KB) - a malformed block falls back per-field to the
|
|
304
383
|
defaults rather than failing.
|
|
305
384
|
|
|
385
|
+
## Cinematic flythrough and scrollytelling
|
|
386
|
+
|
|
387
|
+
`flythrough(map, route, options?)` replays a route as a chase-cam ride: the
|
|
388
|
+
camera glides along the line, bearing eased along the shortest arc towards
|
|
389
|
+
a look-ahead point, at a configurable pitch/zoom/speed.
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
import { flythrough, bindFlythroughToScroll } from "@mapmap/maps";
|
|
393
|
+
|
|
394
|
+
const route = await routes.route(from, to);
|
|
395
|
+
const replay = flythrough(map, route, { pitch: 60, durationMs: 15000 });
|
|
396
|
+
replay.onProgress((t) => scrubber.value = String(t));
|
|
397
|
+
replay.play(); // also: pause(), stop(), seek(0..1), speed = 2
|
|
398
|
+
|
|
399
|
+
// Scrollytelling: scrolling the story column scrubs the camera.
|
|
400
|
+
const unbind = bindFlythroughToScroll(replay, document.querySelector("#story")!);
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Options: `pitch` (default 60), `zoom` (16), `durationMs` (20 000) or
|
|
404
|
+
`speedMps` (ground speed; wins over duration), `lookAheadM` (200),
|
|
405
|
+
`bearingEase` (3). Accepts a `ParsedRoute` or a GeoJSON LineString. Nothing
|
|
406
|
+
moves until `play()`/`seek()`. `flythroughPose`, `bearingBetween` and
|
|
407
|
+
`shortestArcDelta` are exported for apps driving the camera themselves.
|
|
408
|
+
|
|
409
|
+
## Route effects (the flowing ribbon)
|
|
410
|
+
|
|
411
|
+
`map.setRouteEffect("flow")` draws an animated energy ribbon along the
|
|
412
|
+
active route line - a MapLibre custom layer with first-party GLSL, no
|
|
413
|
+
extra dependencies.
|
|
414
|
+
|
|
415
|
+
```ts
|
|
416
|
+
map.setRouteEffect("flow"); // uses the RouteLayer route
|
|
417
|
+
map.setRouteEffect("flow", { color: "#ff7a1f", width: 12, speed: 0.8 });
|
|
418
|
+
map.setRouteEffect("flow", { geometry }); // explicit geometry
|
|
419
|
+
map.setRouteEffect(null); // plain line again
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
- Attaches automatically to whatever a `RouteLayer` on the map draws
|
|
423
|
+
(current and future routes), and dies with `routes.clear()`.
|
|
424
|
+
- Themes can request it: a Studio theme with
|
|
425
|
+
`"effects": { "route": "flow", "params": { … } }` compiles to
|
|
426
|
+
`metadata["mapmap:effects"]` on the style, and the map auto-enables the
|
|
427
|
+
ribbon. An explicit `setRouteEffect(…)` call (including `null`) wins.
|
|
428
|
+
- `prefers-reduced-motion` freezes the ribbon to a static gradient.
|
|
429
|
+
- If WebGL setup for the effect fails, it falls back silently to the plain
|
|
430
|
+
route line (one console warning).
|
|
431
|
+
|
|
432
|
+
## Walkability rings (isochrones)
|
|
433
|
+
|
|
434
|
+
Reachability contours from the gateway's `POST /isochrone`:
|
|
435
|
+
|
|
436
|
+
```ts
|
|
437
|
+
import { IsochroneLayer } from "@mapmap/maps";
|
|
438
|
+
|
|
439
|
+
const rings = new IsochroneLayer(map); // baseUrl/apiKey from the map
|
|
440
|
+
await rings.showReachability({
|
|
441
|
+
origin: { lat: 51.5074, lon: -0.1278 },
|
|
442
|
+
mode: "walk", // "walk" | "cycle" | "drive" | "truck" | raw costing
|
|
443
|
+
minutes: [5, 10, 15],
|
|
444
|
+
color: "#3a86ff", // optional
|
|
445
|
+
});
|
|
446
|
+
rings.clear();
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Renders graduated-opacity fills (nearest ring strongest), contour outlines
|
|
450
|
+
and `"N min"` labels, survives theme swaps, and resolves to the raw GeoJSON
|
|
451
|
+
`FeatureCollection` (each feature carries a `contour` property in minutes).
|
|
452
|
+
`costingOptions` forwards Valhalla costing options verbatim (e.g.
|
|
453
|
+
`{ pedestrian: { use_lit: 1.0 } }`).
|
|
454
|
+
|
|
455
|
+
## Self-diagnosing errors
|
|
456
|
+
|
|
457
|
+
The map diagnoses the classic silent failures at construction and logs ONE
|
|
458
|
+
actionable `console.error` per issue per page, each with a docs link:
|
|
459
|
+
|
|
460
|
+
- `[container-zero-height]` - the 0px container (top blank-map cause)
|
|
461
|
+
- `[container-detached]` - container not in the DOM
|
|
462
|
+
- `[duplicate-maplibre]` - two maplibre-gl copies on one page
|
|
463
|
+
- `[webgl-unavailable]` - no WebGL context available
|
|
464
|
+
- `[invalid-api-key]` - a 401 from the gateway (missing/mistyped/revoked key)
|
|
465
|
+
|
|
466
|
+
`runMapDiagnostics(…)` is exported for apps wrapping a raw `maplibregl.Map`.
|
|
467
|
+
|
|
306
468
|
## The MapMap mark
|
|
307
469
|
|
|
308
470
|
Maps render a small MapMap wordmark bottom-right (the same convention as
|
|
@@ -317,7 +479,7 @@ removable.
|
|
|
317
479
|
import { GuidanceBanner, extractGuidance, speak } from "@mapmap/maps";
|
|
318
480
|
|
|
319
481
|
const route = await routes.route(from, to, {
|
|
320
|
-
|
|
482
|
+
profile: "truck",
|
|
321
483
|
voice: true,
|
|
322
484
|
banner: true,
|
|
323
485
|
language: "en-GB",
|
|
@@ -407,5 +569,5 @@ npm test # vitest (pure-logic unit tests, no browser)
|
|
|
407
569
|
|
|
408
570
|
## Licence
|
|
409
571
|
|
|
410
|
-
Proprietary - © 2026
|
|
572
|
+
Proprietary - © 2026 Mapmap AI Ltd, distributed under a commercial licence.
|
|
411
573
|
See [`LICENSE`](./LICENSE).
|