@mapmap/maps 0.1.0 → 0.2.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/README.md +165 -3
- package/dist/index.d.ts +935 -131
- package/dist/index.js +1353 -37
- package/dist/index.js.map +1 -1
- package/llms-sdk.txt +131 -0
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -3,6 +3,410 @@ import { Protocol } from 'pmtiles';
|
|
|
3
3
|
|
|
4
4
|
// src/map.ts
|
|
5
5
|
|
|
6
|
+
// src/diagnostics.ts
|
|
7
|
+
var DOCS = "https://mapmap.ai/docs";
|
|
8
|
+
var reported = /* @__PURE__ */ new Set();
|
|
9
|
+
var MAPLIBRE_REGISTRY_KEY = /* @__PURE__ */ Symbol.for("mapmap.maplibre-instances");
|
|
10
|
+
function resetDiagnostics() {
|
|
11
|
+
reported.clear();
|
|
12
|
+
const holder = globalThis;
|
|
13
|
+
delete holder[MAPLIBRE_REGISTRY_KEY];
|
|
14
|
+
}
|
|
15
|
+
function report(issue, message) {
|
|
16
|
+
if (reported.has(issue)) return;
|
|
17
|
+
reported.add(issue);
|
|
18
|
+
console.error(`MapMap [${issue}]: ${message}`);
|
|
19
|
+
}
|
|
20
|
+
function runMapDiagnostics(input) {
|
|
21
|
+
checkDuplicateMaplibre(input.maplibre);
|
|
22
|
+
checkWebgl();
|
|
23
|
+
checkContainer(input.container);
|
|
24
|
+
watchForAuthFailures(input.map, input.apiKey);
|
|
25
|
+
}
|
|
26
|
+
function checkContainer(container) {
|
|
27
|
+
if (container === void 0 || typeof document === "undefined") return;
|
|
28
|
+
let element;
|
|
29
|
+
try {
|
|
30
|
+
element = typeof container === "string" ? document.getElementById(container) : container;
|
|
31
|
+
} catch {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (!element) return;
|
|
35
|
+
if (typeof element.isConnected !== "boolean") return;
|
|
36
|
+
if (!element.isConnected) {
|
|
37
|
+
report(
|
|
38
|
+
"container-detached",
|
|
39
|
+
`the map container element is not attached to the DOM, so nothing can render. Append it to the document before constructing the map. ${DOCS}#container`
|
|
40
|
+
);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (element.clientHeight === 0) {
|
|
44
|
+
report(
|
|
45
|
+
"container-zero-height",
|
|
46
|
+
`the map container's height is 0px, so MapLibre is rendering into an invisible canvas (no error, no map). Give it a real height, e.g. #map { height: 100vh; }. If the height arrives later (CSS load, layout), you can ignore this. ${DOCS}#container-height`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function checkDuplicateMaplibre(maplibre) {
|
|
51
|
+
if (typeof maplibre !== "object" && typeof maplibre !== "function") return;
|
|
52
|
+
if (maplibre === null) return;
|
|
53
|
+
const holder = globalThis;
|
|
54
|
+
const registry = holder[MAPLIBRE_REGISTRY_KEY] ??= /* @__PURE__ */ new Set();
|
|
55
|
+
registry.add(maplibre);
|
|
56
|
+
const windowCopy = globalThis.maplibregl;
|
|
57
|
+
if (windowCopy !== void 0 && (typeof windowCopy === "object" || typeof windowCopy === "function")) {
|
|
58
|
+
registry.add(windowCopy);
|
|
59
|
+
}
|
|
60
|
+
if (registry.size > 1) {
|
|
61
|
+
report(
|
|
62
|
+
"duplicate-maplibre",
|
|
63
|
+
`two different maplibre-gl instances are loaded on this page (duplicate dependency, a CDN <script> next to the bundled copy, or micro-frontends). Maps, styles and the pmtiles protocol registration will not be shared between them. De-duplicate so the app owns a single maplibre-gl - it is a peerDependency of @mapmap/maps for exactly this reason. ${DOCS}#duplicate-maplibre`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function checkWebgl() {
|
|
68
|
+
if (typeof document === "undefined") return;
|
|
69
|
+
try {
|
|
70
|
+
const canvas = document.createElement("canvas");
|
|
71
|
+
const gl = canvas.getContext("webgl2") ?? canvas.getContext("webgl");
|
|
72
|
+
if (!gl) {
|
|
73
|
+
report(
|
|
74
|
+
"webgl-unavailable",
|
|
75
|
+
`this browser/environment reports no WebGL support, so the map cannot render. Common causes: headless browsers without --use-gl, blocked GPU/hardware acceleration, remote desktops. ${DOCS}#webgl`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function watchForAuthFailures(map, apiKey) {
|
|
82
|
+
const on = map?.on;
|
|
83
|
+
if (typeof on !== "function" || map === null || map === void 0) return;
|
|
84
|
+
try {
|
|
85
|
+
on.call(map, "error", (ev) => {
|
|
86
|
+
const status = ev?.error?.status;
|
|
87
|
+
const message = ev?.error?.message ?? "";
|
|
88
|
+
const unauthorised = status === 401 || /\b401\b|unauthori[sz]ed/i.test(message);
|
|
89
|
+
if (!unauthorised) return;
|
|
90
|
+
report(
|
|
91
|
+
"invalid-api-key",
|
|
92
|
+
apiKey ? `the gateway rejected your API key (HTTP 401). Check the \`apiKey\` for typos or revocation - keys look like "snk_\u2026". ${DOCS}#authentication` : `a request was rejected with HTTP 401 and no \`apiKey\` was configured. Pass one to the map (\`new MapMapMap({ apiKey: "snk_\u2026" })\`) - issue a free key with POST https://api.mapmap.ai/v1/keys. ${DOCS}#authentication`
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
} catch {
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/effects.ts
|
|
100
|
+
var FLOW_COLOUR = "#3a86ff";
|
|
101
|
+
var EARTH_RADIUS_M = 63710088e-1;
|
|
102
|
+
function haversineM(a, b) {
|
|
103
|
+
const rad = (deg) => deg * Math.PI / 180;
|
|
104
|
+
const dLat = rad(b[1] - a[1]);
|
|
105
|
+
const dLon = rad(b[0] - a[0]);
|
|
106
|
+
const h = Math.sin(dLat / 2) ** 2 + Math.cos(rad(a[1])) * Math.cos(rad(b[1])) * Math.sin(dLon / 2) ** 2;
|
|
107
|
+
return 2 * EARTH_RADIUS_M * Math.asin(Math.sqrt(h));
|
|
108
|
+
}
|
|
109
|
+
var RIBBON_FLOATS_PER_VERTEX = 5;
|
|
110
|
+
function lngLatToMercator(lngLat) {
|
|
111
|
+
const [lng, lat] = lngLat;
|
|
112
|
+
const x = (lng + 180) / 360;
|
|
113
|
+
const y = (1 - Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360)) / Math.PI) / 2;
|
|
114
|
+
return [x, y];
|
|
115
|
+
}
|
|
116
|
+
function tessellateRouteRibbon(coordinates) {
|
|
117
|
+
const points = [];
|
|
118
|
+
for (const lngLat of coordinates) {
|
|
119
|
+
const p = lngLatToMercator(lngLat);
|
|
120
|
+
const last = points[points.length - 1];
|
|
121
|
+
if (!last || last[0] !== p[0] || last[1] !== p[1]) points.push(p);
|
|
122
|
+
}
|
|
123
|
+
if (points.length < 2) {
|
|
124
|
+
return { vertices: new Float32Array(0), vertexCount: 0 };
|
|
125
|
+
}
|
|
126
|
+
const cumulative = [0];
|
|
127
|
+
for (let i = 1; i < points.length; i++) {
|
|
128
|
+
const dx = points[i][0] - points[i - 1][0];
|
|
129
|
+
const dy = points[i][1] - points[i - 1][1];
|
|
130
|
+
cumulative.push(cumulative[i - 1] + Math.hypot(dx, dy));
|
|
131
|
+
}
|
|
132
|
+
const total = cumulative[cumulative.length - 1];
|
|
133
|
+
const vertices = new Float32Array(
|
|
134
|
+
points.length * 2 * RIBBON_FLOATS_PER_VERTEX
|
|
135
|
+
);
|
|
136
|
+
let out = 0;
|
|
137
|
+
for (let i = 0; i < points.length; i++) {
|
|
138
|
+
const prev = points[i - 1];
|
|
139
|
+
const here = points[i];
|
|
140
|
+
const next = points[i + 1];
|
|
141
|
+
let dirX = 0;
|
|
142
|
+
let dirY = 0;
|
|
143
|
+
if (prev) {
|
|
144
|
+
const len = Math.hypot(here[0] - prev[0], here[1] - prev[1]);
|
|
145
|
+
dirX += (here[0] - prev[0]) / len;
|
|
146
|
+
dirY += (here[1] - prev[1]) / len;
|
|
147
|
+
}
|
|
148
|
+
if (next) {
|
|
149
|
+
const len = Math.hypot(next[0] - here[0], next[1] - here[1]);
|
|
150
|
+
dirX += (next[0] - here[0]) / len;
|
|
151
|
+
dirY += (next[1] - here[1]) / len;
|
|
152
|
+
}
|
|
153
|
+
const dirLen = Math.hypot(dirX, dirY);
|
|
154
|
+
if (dirLen < 1e-12 && prev) {
|
|
155
|
+
const len = Math.hypot(here[0] - prev[0], here[1] - prev[1]);
|
|
156
|
+
dirX = (here[0] - prev[0]) / len;
|
|
157
|
+
dirY = (here[1] - prev[1]) / len;
|
|
158
|
+
} else {
|
|
159
|
+
dirX /= dirLen;
|
|
160
|
+
dirY /= dirLen;
|
|
161
|
+
}
|
|
162
|
+
const nx = -dirY;
|
|
163
|
+
const ny = dirX;
|
|
164
|
+
const progress = total > 0 ? cumulative[i] / total : 0;
|
|
165
|
+
for (const side of [1, -1]) {
|
|
166
|
+
vertices[out++] = here[0];
|
|
167
|
+
vertices[out++] = here[1];
|
|
168
|
+
vertices[out++] = nx * side;
|
|
169
|
+
vertices[out++] = ny * side;
|
|
170
|
+
vertices[out++] = progress;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return { vertices, vertexCount: points.length * 2 };
|
|
174
|
+
}
|
|
175
|
+
function parseCssColour(colour2) {
|
|
176
|
+
const v = colour2.trim();
|
|
177
|
+
if (v.startsWith("#")) {
|
|
178
|
+
const hex = v.slice(1);
|
|
179
|
+
if (!/^[0-9a-fA-F]+$/.test(hex)) return void 0;
|
|
180
|
+
if (hex.length === 3 || hex.length === 4) {
|
|
181
|
+
const parts = hex.split("").map((c) => parseInt(c + c, 16) / 255);
|
|
182
|
+
const [r, g, b, a] = parts;
|
|
183
|
+
if (r === void 0 || g === void 0 || b === void 0) return void 0;
|
|
184
|
+
return [r, g, b, a ?? 1];
|
|
185
|
+
}
|
|
186
|
+
if (hex.length === 6 || hex.length === 8) {
|
|
187
|
+
const chan = (i) => parseInt(hex.slice(i, i + 2), 16) / 255;
|
|
188
|
+
return [chan(0), chan(2), chan(4), hex.length === 8 ? chan(6) : 1];
|
|
189
|
+
}
|
|
190
|
+
return void 0;
|
|
191
|
+
}
|
|
192
|
+
const fn = /^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*(?:,\s*([\d.]+)\s*)?\)$/.exec(v);
|
|
193
|
+
if (fn) {
|
|
194
|
+
const r = Number(fn[1]) / 255;
|
|
195
|
+
const g = Number(fn[2]) / 255;
|
|
196
|
+
const b = Number(fn[3]) / 255;
|
|
197
|
+
const a = fn[4] !== void 0 ? Number(fn[4]) : 1;
|
|
198
|
+
if ([r, g, b, a].some((c) => !Number.isFinite(c) || c < 0 || c > 1)) {
|
|
199
|
+
return void 0;
|
|
200
|
+
}
|
|
201
|
+
return [r, g, b, a];
|
|
202
|
+
}
|
|
203
|
+
return void 0;
|
|
204
|
+
}
|
|
205
|
+
function prefersReducedMotion() {
|
|
206
|
+
if (typeof matchMedia !== "function") return false;
|
|
207
|
+
try {
|
|
208
|
+
return matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
209
|
+
} catch {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
var VERTEX_SHADER = `
|
|
214
|
+
uniform mat4 u_matrix;
|
|
215
|
+
uniform float u_half_width;
|
|
216
|
+
attribute vec2 a_pos;
|
|
217
|
+
attribute vec2 a_normal;
|
|
218
|
+
attribute float a_progress;
|
|
219
|
+
varying float v_progress;
|
|
220
|
+
void main() {
|
|
221
|
+
v_progress = a_progress;
|
|
222
|
+
gl_Position = u_matrix * vec4(a_pos + a_normal * u_half_width, 0.0, 1.0);
|
|
223
|
+
}
|
|
224
|
+
`;
|
|
225
|
+
var FRAGMENT_SHADER = `
|
|
226
|
+
precision mediump float;
|
|
227
|
+
uniform vec4 u_color;
|
|
228
|
+
uniform float u_phase;
|
|
229
|
+
uniform float u_repeats;
|
|
230
|
+
varying float v_progress;
|
|
231
|
+
void main() {
|
|
232
|
+
float t = fract(v_progress * u_repeats - u_phase);
|
|
233
|
+
// A soft comet: rises quickly, decays slowly, never fully dark so the
|
|
234
|
+
// ribbon always traces the route.
|
|
235
|
+
float pulse = smoothstep(0.0, 0.25, t) * (1.0 - smoothstep(0.35, 1.0, t));
|
|
236
|
+
float energy = 0.25 + 0.75 * pulse;
|
|
237
|
+
float alpha = u_color.a * energy;
|
|
238
|
+
// Premultiplied alpha, matching MapLibre's framebuffer convention.
|
|
239
|
+
gl_FragColor = vec4(u_color.rgb * alpha, alpha);
|
|
240
|
+
}
|
|
241
|
+
`;
|
|
242
|
+
var warnedWebglFailure = false;
|
|
243
|
+
var FLOW_DEFAULTS = {
|
|
244
|
+
color: FLOW_COLOUR,
|
|
245
|
+
width: 10,
|
|
246
|
+
speed: 0.6
|
|
247
|
+
};
|
|
248
|
+
function mercatorUnitsPerPixel(zoom) {
|
|
249
|
+
return 1 / (512 * 2 ** zoom);
|
|
250
|
+
}
|
|
251
|
+
function routeLengthM(coordinates) {
|
|
252
|
+
let length = 0;
|
|
253
|
+
for (let i = 1; i < coordinates.length; i++) {
|
|
254
|
+
length += haversineM(coordinates[i - 1], coordinates[i]);
|
|
255
|
+
}
|
|
256
|
+
return length;
|
|
257
|
+
}
|
|
258
|
+
var FlowRouteEffectLayer = class {
|
|
259
|
+
constructor(geometry, options = {}) {
|
|
260
|
+
this.type = "custom";
|
|
261
|
+
this.renderingMode = "2d";
|
|
262
|
+
this.program = null;
|
|
263
|
+
this.buffer = null;
|
|
264
|
+
this.vertexCount = 0;
|
|
265
|
+
this.repeats = 8;
|
|
266
|
+
this.startedAt = 0;
|
|
267
|
+
this.failed = false;
|
|
268
|
+
this.aPos = 0;
|
|
269
|
+
this.aNormal = 0;
|
|
270
|
+
this.aProgress = 0;
|
|
271
|
+
this.uMatrix = null;
|
|
272
|
+
this.uHalfWidth = null;
|
|
273
|
+
this.uColor = null;
|
|
274
|
+
this.uPhase = null;
|
|
275
|
+
this.uRepeats = null;
|
|
276
|
+
this.id = options.id ?? "mapmap-route-effect";
|
|
277
|
+
this.geometry = geometry;
|
|
278
|
+
this.colour = (options.color !== void 0 ? parseCssColour(options.color) : void 0) ?? parseCssColour(FLOW_DEFAULTS.color);
|
|
279
|
+
this.widthPx = options.width ?? FLOW_DEFAULTS.width;
|
|
280
|
+
this.speed = options.speed ?? FLOW_DEFAULTS.speed;
|
|
281
|
+
this.animate = !prefersReducedMotion();
|
|
282
|
+
}
|
|
283
|
+
/** Swap the ribbon onto a new route line (e.g. after a reroute). */
|
|
284
|
+
setGeometry(geometry) {
|
|
285
|
+
this.geometry = geometry;
|
|
286
|
+
if (this.glRef && !this.failed) this.upload(this.glRef);
|
|
287
|
+
this.mapRef?.triggerRepaint();
|
|
288
|
+
}
|
|
289
|
+
onAdd(map, gl) {
|
|
290
|
+
this.mapRef = map;
|
|
291
|
+
this.glRef = gl;
|
|
292
|
+
this.startedAt = now();
|
|
293
|
+
try {
|
|
294
|
+
const program = gl.createProgram();
|
|
295
|
+
if (!program) throw new Error("createProgram returned null");
|
|
296
|
+
for (const [kind, source] of [
|
|
297
|
+
[gl.VERTEX_SHADER, VERTEX_SHADER],
|
|
298
|
+
[gl.FRAGMENT_SHADER, FRAGMENT_SHADER]
|
|
299
|
+
]) {
|
|
300
|
+
const shader = gl.createShader(kind);
|
|
301
|
+
if (!shader) throw new Error("createShader returned null");
|
|
302
|
+
gl.shaderSource(shader, source);
|
|
303
|
+
gl.compileShader(shader);
|
|
304
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
305
|
+
throw new Error(gl.getShaderInfoLog(shader) ?? "shader compile failed");
|
|
306
|
+
}
|
|
307
|
+
gl.attachShader(program, shader);
|
|
308
|
+
}
|
|
309
|
+
gl.linkProgram(program);
|
|
310
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
311
|
+
throw new Error(gl.getProgramInfoLog(program) ?? "program link failed");
|
|
312
|
+
}
|
|
313
|
+
this.program = program;
|
|
314
|
+
this.aPos = gl.getAttribLocation(program, "a_pos");
|
|
315
|
+
this.aNormal = gl.getAttribLocation(program, "a_normal");
|
|
316
|
+
this.aProgress = gl.getAttribLocation(program, "a_progress");
|
|
317
|
+
this.uMatrix = gl.getUniformLocation(program, "u_matrix");
|
|
318
|
+
this.uHalfWidth = gl.getUniformLocation(program, "u_half_width");
|
|
319
|
+
this.uColor = gl.getUniformLocation(program, "u_color");
|
|
320
|
+
this.uPhase = gl.getUniformLocation(program, "u_phase");
|
|
321
|
+
this.uRepeats = gl.getUniformLocation(program, "u_repeats");
|
|
322
|
+
this.upload(gl);
|
|
323
|
+
} catch (error) {
|
|
324
|
+
this.failed = true;
|
|
325
|
+
if (!warnedWebglFailure) {
|
|
326
|
+
warnedWebglFailure = true;
|
|
327
|
+
console.warn(
|
|
328
|
+
`MapMap: route effect "flow" disabled (WebGL setup failed: ${error instanceof Error ? error.message : String(error)}). Falling back to the plain route line.`
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
onRemove(_map, gl) {
|
|
334
|
+
if (this.buffer) gl.deleteBuffer(this.buffer);
|
|
335
|
+
if (this.program) gl.deleteProgram(this.program);
|
|
336
|
+
this.buffer = null;
|
|
337
|
+
this.program = null;
|
|
338
|
+
this.mapRef = void 0;
|
|
339
|
+
this.glRef = void 0;
|
|
340
|
+
}
|
|
341
|
+
render(gl, args) {
|
|
342
|
+
if (this.failed || !this.program || !this.buffer || this.vertexCount < 3) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const matrix = projectionMatrixOf(args);
|
|
346
|
+
if (!matrix || !this.mapRef) return;
|
|
347
|
+
gl.useProgram(this.program);
|
|
348
|
+
gl.uniformMatrix4fv(this.uMatrix, false, matrix);
|
|
349
|
+
const halfWidth = this.widthPx / 2 * mercatorUnitsPerPixel(this.mapRef.getZoom());
|
|
350
|
+
gl.uniform1f(this.uHalfWidth, halfWidth);
|
|
351
|
+
gl.uniform4f(this.uColor, ...this.colour);
|
|
352
|
+
const phase = this.animate ? (now() - this.startedAt) / 1e3 * this.speed % 1 : 0;
|
|
353
|
+
gl.uniform1f(this.uPhase, phase);
|
|
354
|
+
gl.uniform1f(this.uRepeats, this.repeats);
|
|
355
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
356
|
+
const stride = RIBBON_FLOATS_PER_VERTEX * 4;
|
|
357
|
+
gl.enableVertexAttribArray(this.aPos);
|
|
358
|
+
gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, stride, 0);
|
|
359
|
+
gl.enableVertexAttribArray(this.aNormal);
|
|
360
|
+
gl.vertexAttribPointer(this.aNormal, 2, gl.FLOAT, false, stride, 8);
|
|
361
|
+
gl.enableVertexAttribArray(this.aProgress);
|
|
362
|
+
gl.vertexAttribPointer(this.aProgress, 1, gl.FLOAT, false, stride, 16);
|
|
363
|
+
gl.enable(gl.BLEND);
|
|
364
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
365
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, this.vertexCount);
|
|
366
|
+
if (this.animate) this.mapRef.triggerRepaint();
|
|
367
|
+
}
|
|
368
|
+
upload(gl) {
|
|
369
|
+
const mesh = tessellateRouteRibbon(this.geometry.coordinates);
|
|
370
|
+
this.vertexCount = mesh.vertexCount;
|
|
371
|
+
this.repeats = Math.min(
|
|
372
|
+
80,
|
|
373
|
+
Math.max(2, Math.round(routeLengthM(this.geometry.coordinates) / 500))
|
|
374
|
+
);
|
|
375
|
+
if (!this.buffer) this.buffer = gl.createBuffer();
|
|
376
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
377
|
+
gl.bufferData(gl.ARRAY_BUFFER, mesh.vertices, gl.STATIC_DRAW);
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
function now() {
|
|
381
|
+
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
382
|
+
}
|
|
383
|
+
function projectionMatrixOf(args) {
|
|
384
|
+
if (Array.isArray(args)) return args;
|
|
385
|
+
if (args instanceof Float32Array || args instanceof Float64Array) {
|
|
386
|
+
return args instanceof Float64Array ? new Float32Array(args) : args;
|
|
387
|
+
}
|
|
388
|
+
if (typeof args === "object" && args !== null) {
|
|
389
|
+
const data = args.defaultProjectionData;
|
|
390
|
+
const main = data?.mainMatrix;
|
|
391
|
+
if (Array.isArray(main)) return main;
|
|
392
|
+
if (main instanceof Float32Array) return main;
|
|
393
|
+
if (main instanceof Float64Array) return new Float32Array(main);
|
|
394
|
+
}
|
|
395
|
+
return void 0;
|
|
396
|
+
}
|
|
397
|
+
var ROUTE_EFFECTS = {
|
|
398
|
+
flow: (geometry, options) => new FlowRouteEffectLayer(geometry, options)
|
|
399
|
+
};
|
|
400
|
+
function createRouteEffect(name, geometry, options) {
|
|
401
|
+
const factory = ROUTE_EFFECTS[name];
|
|
402
|
+
if (!factory) {
|
|
403
|
+
throw new Error(
|
|
404
|
+
`unknown route effect ${JSON.stringify(name)}; accepted effects: ` + Object.keys(ROUTE_EFFECTS).join(", ")
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
return factory(geometry, options);
|
|
408
|
+
}
|
|
409
|
+
|
|
6
410
|
// src/logo.ts
|
|
7
411
|
var LOGO_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="84" height="20" viewBox="0 0 156 37" fill="none" role="img" aria-label="MapMap"><g stroke="#ffffff" stroke-linejoin="round" stroke-linecap="round"><path d="M146.965 19.2363C146.965 17.5281 146.64 16.2469 145.989 15.3927C145.358 14.5386 144.464 14.1115 143.304 14.1115C142.227 14.1115 141.352 14.5589 140.681 15.4537C140.03 16.3282 139.705 17.5382 139.705 19.0838V19.3889C139.705 20.9751 140.03 22.2055 140.681 23.08C141.352 23.9341 142.227 24.3612 143.304 24.3612C144.382 24.3612 145.257 23.924 145.928 23.0495C146.619 22.1547 146.965 20.8836 146.965 19.2363ZM150.778 19.2363C150.778 21.8598 150.188 23.8934 149.009 25.3373C147.85 26.7609 146.324 27.4727 144.433 27.4727C142.481 27.4727 140.925 26.7304 139.766 25.2458H139.705V33.8787H135.739V11.305H139.461L139.522 13.5319H139.583C140.803 11.844 142.42 11 144.433 11C146.406 11 147.951 11.7016 149.07 13.1048C150.209 14.5081 150.778 16.5519 150.778 19.2363Z" fill="#ffffff" stroke-width="3" paint-order="stroke"/><path d="M126.399 11C128.819 11 130.558 11.4779 131.615 12.4337C132.673 13.3692 133.202 14.8945 133.202 17.0095V22.8969C133.202 24.3409 133.354 25.7644 133.659 27.1676H129.907C129.785 26.4965 129.694 25.7136 129.633 24.8188H129.572C129.002 25.6526 128.229 26.3033 127.253 26.7711C126.297 27.2388 125.25 27.4727 124.111 27.4727C122.627 27.4727 121.437 27.066 120.542 26.2525C119.668 25.4187 119.23 24.2697 119.23 22.8054C119.23 21.0361 119.973 19.6227 121.457 18.5652C122.962 17.4874 125.118 16.9485 127.924 16.9485H129.236V16.857C129.236 15.7791 129.023 15.0267 128.596 14.5996C128.168 14.1725 127.406 13.959 126.308 13.959C124.315 13.959 122.433 14.4166 120.664 15.3317L120.085 12.3727C122.017 11.4576 124.121 11 126.399 11ZM122.952 22.5004C122.952 23.1512 123.145 23.6596 123.532 24.0256C123.918 24.3917 124.447 24.5747 125.118 24.5747C126.277 24.5747 127.253 24.1985 128.046 23.446C128.84 22.6936 129.236 21.7479 129.236 20.6091V19.5719H127.924C126.318 19.5719 125.087 19.8464 124.233 20.3955C123.379 20.9243 122.952 21.6259 122.952 22.5004Z" fill="#ffffff" stroke-width="3" paint-order="stroke"/><path d="M96.1152 11.305H99.7758L99.8673 13.4404H99.9284C101.108 11.8135 102.46 11 103.986 11C104.982 11 105.806 11.2034 106.456 11.6101C107.107 12.0168 107.666 12.6778 108.134 13.5929H108.195C109.517 11.8643 111.073 11 112.862 11C114.571 11 115.811 11.4779 116.584 12.4337C117.377 13.3692 117.774 14.9555 117.774 17.1925V27.1676H113.93V18.1077C113.93 16.5214 113.757 15.4537 113.412 14.9046C113.086 14.3555 112.496 14.081 111.642 14.081C110.91 14.081 110.27 14.4674 109.72 15.2402C109.171 16.013 108.897 16.9688 108.897 18.1077V27.1676H105.053V18.1077C105.053 16.5214 104.88 15.4537 104.535 14.9046C104.209 14.3555 103.619 14.081 102.765 14.081C102.033 14.081 101.393 14.4674 100.844 15.2402C100.294 16.013 100.02 16.9688 100.02 18.1077V27.1676H96.1152V11.305Z" fill="#ffffff" stroke-width="3" paint-order="stroke"/><path d="M90.8503 19.2363C90.8503 17.5281 90.525 16.2469 89.8742 15.3927C89.2438 14.5386 88.3489 14.1115 87.1898 14.1115C86.1119 14.1115 85.2374 14.5589 84.5663 15.4537C83.9155 16.3282 83.5902 17.5382 83.5902 19.0838V19.3889C83.5902 20.9751 83.9155 22.2055 84.5663 23.08C85.2374 23.9341 86.1119 24.3612 87.1898 24.3612C88.2676 24.3612 89.1421 23.924 89.8132 23.0495C90.5046 22.1547 90.8503 20.8836 90.8503 19.2363ZM94.6635 19.2363C94.6635 21.8598 94.0737 23.8934 92.8942 25.3373C91.735 26.7609 90.2097 27.4727 88.3184 27.4727C86.3661 27.4727 84.8104 26.7304 83.6512 25.2458H83.5902V33.8787H79.6245V11.305H83.3461L83.4071 13.5319H83.4681C84.6883 11.844 86.3051 11 88.3184 11C90.2911 11 91.8367 11.7016 92.9552 13.1048C94.094 14.5081 94.6635 16.5519 94.6635 19.2363Z" fill="#ffffff" stroke-width="3" paint-order="stroke"/><path d="M70.2844 11C72.7045 11 74.4432 11.4779 75.5008 12.4337C76.5583 13.3692 77.087 14.8945 77.087 17.0095V22.8969C77.087 24.3409 77.2395 25.7644 77.5446 27.1676H73.7925C73.6705 26.4965 73.5789 25.7136 73.5179 24.8188H73.4569C72.8875 25.6526 72.1147 26.3033 71.1385 26.7711C70.1827 27.2388 69.1354 27.4727 67.9965 27.4727C66.5119 27.4727 65.3223 27.066 64.4274 26.2525C63.553 25.4187 63.1157 24.2697 63.1157 22.8054C63.1157 21.0361 63.858 19.6227 65.3426 18.5652C66.8475 17.4874 69.0032 16.9485 71.8096 16.9485H73.1214V16.857C73.1214 15.7791 72.9078 15.0267 72.4808 14.5996C72.0537 14.1725 71.2911 13.959 70.1929 13.959C68.1999 13.959 66.3187 14.4166 64.5495 15.3317L63.9699 12.3727C65.9018 11.4576 68.0067 11 70.2844 11ZM66.8373 22.5004C66.8373 23.1512 67.0305 23.6596 67.4169 24.0256C67.8033 24.3917 68.3321 24.5747 69.0032 24.5747C70.1624 24.5747 71.1385 24.1985 71.9317 23.446C72.7248 22.6936 73.1214 21.7479 73.1214 20.6091V19.5719H71.8096C70.203 19.5719 68.9727 19.8464 68.1185 20.3955C67.2644 20.9243 66.8373 21.6259 66.8373 22.5004Z" fill="#ffffff" stroke-width="3" paint-order="stroke"/><path d="M40 11.3049H43.6606L43.7521 13.4403H43.8131C44.9927 11.8133 46.345 10.9999 47.8703 10.9999C48.8668 10.9999 49.6904 11.2032 50.3412 11.61C50.992 12.0167 51.5512 12.6777 52.019 13.5928H52.08C53.4019 11.8642 54.9576 10.9999 56.7472 10.9999C58.4555 10.9999 59.6961 11.4778 60.4689 12.4336C61.262 13.3691 61.6586 14.9554 61.6586 17.1924V27.1675H57.8149V18.1075C57.8149 16.5213 57.6421 15.4536 57.2963 14.9045C56.9709 14.3554 56.3812 14.0809 55.527 14.0809C54.7949 14.0809 54.1543 14.4673 53.6052 15.2401C53.0561 16.0129 52.7816 16.9687 52.7816 18.1075V27.1675H48.938V18.1075C48.938 16.5213 48.7651 15.4536 48.4194 14.9045C48.094 14.3554 47.5042 14.0809 46.6501 14.0809C45.918 14.0809 45.2774 14.4673 44.7283 15.2401C44.1792 16.0129 43.9046 16.9687 43.9046 18.1075V27.1675H40V11.3049Z" fill="#ffffff" stroke-width="3" paint-order="stroke"/><path d="M27.3847 25.9178C28.8081 23.5868 30.5287 21.3029 30.9562 18.5589C31.9911 11.9172 26.9381 5.71447 19.956 5.30338C17.4904 5.15838 13.6449 5.12507 11.1992 5.30299C10.0552 5.38607 8.94122 5.89121 8.16887 6.70868C7.71694 7.18718 5.28183 11.1346 5.20651 11.6441C5.02777 12.8495 5.83228 13.8426 7.09035 13.9645C9.18143 14.1667 11.8763 13.9253 14.0309 13.9264C15.8964 13.9272 17.9989 13.8097 19.8339 13.9253C25.9341 14.3093 29.7128 20.5446 27.3249 25.9319L27.3847 25.9178Z" stroke-width="4.96"/><path d="M14.0302 13.9258L10.6029 19.6599C9.96327 20.8096 10.6737 22.3059 12.0613 22.4638C14.1002 22.6962 17.0133 22.4493 19.1606 22.4689C21.213 22.4877 23.197 22.146 24.8203 23.6246C26.1443 24.8308 26.5555 26.7091 25.9192 28.3472L27.3849 25.9175C29.7728 20.5302 25.9338 14.3083 19.8336 13.9246C17.9986 13.809 15.8961 13.927 14.0306 13.9258H14.0302Z" stroke-width="4.96"/><path d="M19.1603 22.469L15.6471 28.3947C15.2098 29.3278 15.7183 30.5509 16.7183 30.9134C17.4381 31.174 21.8747 31.1148 22.7354 30.9463C24.1038 30.6786 25.4299 29.6056 25.9188 28.3477C26.5548 26.7096 26.1436 24.8313 24.82 23.6251C23.1967 22.1461 21.1733 22.5542 19.1209 22.5354L19.1603 22.469Z" stroke-width="4.96"/></g><path d="M146.965 19.2363C146.965 17.5281 146.64 16.2469 145.989 15.3927C145.358 14.5386 144.464 14.1115 143.304 14.1115C142.227 14.1115 141.352 14.5589 140.681 15.4537C140.03 16.3282 139.705 17.5382 139.705 19.0838V19.3889C139.705 20.9751 140.03 22.2055 140.681 23.08C141.352 23.9341 142.227 24.3612 143.304 24.3612C144.382 24.3612 145.257 23.924 145.928 23.0495C146.619 22.1547 146.965 20.8836 146.965 19.2363ZM150.778 19.2363C150.778 21.8598 150.188 23.8934 149.009 25.3373C147.85 26.7609 146.324 27.4727 144.433 27.4727C142.481 27.4727 140.925 26.7304 139.766 25.2458H139.705V33.8787H135.739V11.305H139.461L139.522 13.5319H139.583C140.803 11.844 142.42 11 144.433 11C146.406 11 147.951 11.7016 149.07 13.1048C150.209 14.5081 150.778 16.5519 150.778 19.2363Z" fill="#26282A"/><path d="M126.399 11C128.819 11 130.558 11.4779 131.615 12.4337C132.673 13.3692 133.202 14.8945 133.202 17.0095V22.8969C133.202 24.3409 133.354 25.7644 133.659 27.1676H129.907C129.785 26.4965 129.694 25.7136 129.633 24.8188H129.572C129.002 25.6526 128.229 26.3033 127.253 26.7711C126.297 27.2388 125.25 27.4727 124.111 27.4727C122.627 27.4727 121.437 27.066 120.542 26.2525C119.668 25.4187 119.23 24.2697 119.23 22.8054C119.23 21.0361 119.973 19.6227 121.457 18.5652C122.962 17.4874 125.118 16.9485 127.924 16.9485H129.236V16.857C129.236 15.7791 129.023 15.0267 128.596 14.5996C128.168 14.1725 127.406 13.959 126.308 13.959C124.315 13.959 122.433 14.4166 120.664 15.3317L120.085 12.3727C122.017 11.4576 124.121 11 126.399 11ZM122.952 22.5004C122.952 23.1512 123.145 23.6596 123.532 24.0256C123.918 24.3917 124.447 24.5747 125.118 24.5747C126.277 24.5747 127.253 24.1985 128.046 23.446C128.84 22.6936 129.236 21.7479 129.236 20.6091V19.5719H127.924C126.318 19.5719 125.087 19.8464 124.233 20.3955C123.379 20.9243 122.952 21.6259 122.952 22.5004Z" fill="#26282A"/><path d="M96.1152 11.305H99.7758L99.8673 13.4404H99.9284C101.108 11.8135 102.46 11 103.986 11C104.982 11 105.806 11.2034 106.456 11.6101C107.107 12.0168 107.666 12.6778 108.134 13.5929H108.195C109.517 11.8643 111.073 11 112.862 11C114.571 11 115.811 11.4779 116.584 12.4337C117.377 13.3692 117.774 14.9555 117.774 17.1925V27.1676H113.93V18.1077C113.93 16.5214 113.757 15.4537 113.412 14.9046C113.086 14.3555 112.496 14.081 111.642 14.081C110.91 14.081 110.27 14.4674 109.72 15.2402C109.171 16.013 108.897 16.9688 108.897 18.1077V27.1676H105.053V18.1077C105.053 16.5214 104.88 15.4537 104.535 14.9046C104.209 14.3555 103.619 14.081 102.765 14.081C102.033 14.081 101.393 14.4674 100.844 15.2402C100.294 16.013 100.02 16.9688 100.02 18.1077V27.1676H96.1152V11.305Z" fill="#26282A"/><path d="M90.8503 19.2363C90.8503 17.5281 90.525 16.2469 89.8742 15.3927C89.2438 14.5386 88.3489 14.1115 87.1898 14.1115C86.1119 14.1115 85.2374 14.5589 84.5663 15.4537C83.9155 16.3282 83.5902 17.5382 83.5902 19.0838V19.3889C83.5902 20.9751 83.9155 22.2055 84.5663 23.08C85.2374 23.9341 86.1119 24.3612 87.1898 24.3612C88.2676 24.3612 89.1421 23.924 89.8132 23.0495C90.5046 22.1547 90.8503 20.8836 90.8503 19.2363ZM94.6635 19.2363C94.6635 21.8598 94.0737 23.8934 92.8942 25.3373C91.735 26.7609 90.2097 27.4727 88.3184 27.4727C86.3661 27.4727 84.8104 26.7304 83.6512 25.2458H83.5902V33.8787H79.6245V11.305H83.3461L83.4071 13.5319H83.4681C84.6883 11.844 86.3051 11 88.3184 11C90.2911 11 91.8367 11.7016 92.9552 13.1048C94.094 14.5081 94.6635 16.5519 94.6635 19.2363Z" fill="#26282A"/><path d="M70.2844 11C72.7045 11 74.4432 11.4779 75.5008 12.4337C76.5583 13.3692 77.087 14.8945 77.087 17.0095V22.8969C77.087 24.3409 77.2395 25.7644 77.5446 27.1676H73.7925C73.6705 26.4965 73.5789 25.7136 73.5179 24.8188H73.4569C72.8875 25.6526 72.1147 26.3033 71.1385 26.7711C70.1827 27.2388 69.1354 27.4727 67.9965 27.4727C66.5119 27.4727 65.3223 27.066 64.4274 26.2525C63.553 25.4187 63.1157 24.2697 63.1157 22.8054C63.1157 21.0361 63.858 19.6227 65.3426 18.5652C66.8475 17.4874 69.0032 16.9485 71.8096 16.9485H73.1214V16.857C73.1214 15.7791 72.9078 15.0267 72.4808 14.5996C72.0537 14.1725 71.2911 13.959 70.1929 13.959C68.1999 13.959 66.3187 14.4166 64.5495 15.3317L63.9699 12.3727C65.9018 11.4576 68.0067 11 70.2844 11ZM66.8373 22.5004C66.8373 23.1512 67.0305 23.6596 67.4169 24.0256C67.8033 24.3917 68.3321 24.5747 69.0032 24.5747C70.1624 24.5747 71.1385 24.1985 71.9317 23.446C72.7248 22.6936 73.1214 21.7479 73.1214 20.6091V19.5719H71.8096C70.203 19.5719 68.9727 19.8464 68.1185 20.3955C67.2644 20.9243 66.8373 21.6259 66.8373 22.5004Z" fill="#26282A"/><path d="M40 11.3049H43.6606L43.7521 13.4403H43.8131C44.9927 11.8133 46.345 10.9999 47.8703 10.9999C48.8668 10.9999 49.6904 11.2032 50.3412 11.61C50.992 12.0167 51.5512 12.6777 52.019 13.5928H52.08C53.4019 11.8642 54.9576 10.9999 56.7472 10.9999C58.4555 10.9999 59.6961 11.4778 60.4689 12.4336C61.262 13.3691 61.6586 14.9554 61.6586 17.1924V27.1675H57.8149V18.1075C57.8149 16.5213 57.6421 15.4536 57.2963 14.9045C56.9709 14.3554 56.3812 14.0809 55.527 14.0809C54.7949 14.0809 54.1543 14.4673 53.6052 15.2401C53.0561 16.0129 52.7816 16.9687 52.7816 18.1075V27.1675H48.938V18.1075C48.938 16.5213 48.7651 15.4536 48.4194 14.9045C48.094 14.3554 47.5042 14.0809 46.6501 14.0809C45.918 14.0809 45.2774 14.4673 44.7283 15.2401C44.1792 16.0129 43.9046 16.9687 43.9046 18.1075V27.1675H40V11.3049Z" fill="#26282A"/><path d="M27.3847 25.9178C28.8081 23.5868 30.5287 21.3029 30.9562 18.5589C31.9911 11.9172 26.9381 5.71447 19.956 5.30338C17.4904 5.15838 13.6449 5.12507 11.1992 5.30299C10.0552 5.38607 8.94122 5.89121 8.16887 6.70868C7.71694 7.18718 5.28183 11.1346 5.20651 11.6441C5.02777 12.8495 5.83228 13.8426 7.09035 13.9645C9.18143 14.1667 11.8763 13.9253 14.0309 13.9264C15.8964 13.9272 17.9989 13.8097 19.8339 13.9253C25.9341 14.3093 29.7128 20.5446 27.3249 25.9319L27.3847 25.9178Z" stroke="#3A3838" stroke-width="1.95943" stroke-miterlimit="10"/><path d="M14.0302 13.9258L10.6029 19.6599C9.96327 20.8096 10.6737 22.3059 12.0613 22.4638C14.1002 22.6962 17.0133 22.4493 19.1606 22.4689C21.213 22.4877 23.197 22.146 24.8203 23.6246C26.1443 24.8308 26.5555 26.7091 25.9192 28.3472L27.3849 25.9175C29.7728 20.5302 25.9338 14.3083 19.8336 13.9246C17.9986 13.809 15.8961 13.927 14.0306 13.9258H14.0302Z" stroke="#3A3838" stroke-width="1.95943" stroke-miterlimit="10"/><path d="M19.1603 22.469L15.6471 28.3947C15.2098 29.3278 15.7183 30.5509 16.7183 30.9134C17.4381 31.174 21.8747 31.1148 22.7354 30.9463C24.1038 30.6786 25.4299 29.6056 25.9188 28.3477C26.5548 26.7096 26.1436 24.8313 24.82 23.6251C23.1967 22.1461 21.1733 22.5542 19.1209 22.5354L19.1603 22.469Z" stroke="#3A3838" stroke-width="1.95943" stroke-miterlimit="10"/></svg>';
|
|
8
412
|
var DEFAULT_HREF = "https://mapmap.ai";
|
|
@@ -132,6 +536,172 @@ async function navDesignFromThemeUrl(url, fetchImpl) {
|
|
|
132
536
|
return navDesignFromTheme(theme);
|
|
133
537
|
}
|
|
134
538
|
|
|
539
|
+
// src/poi-design.ts
|
|
540
|
+
var POI_CATEGORY_COLORS = [
|
|
541
|
+
["food_drink", "#e8734a"],
|
|
542
|
+
["shopping", "#4a90d9"],
|
|
543
|
+
["transport", "#3a86ff"],
|
|
544
|
+
["lodging", "#9b6dd6"],
|
|
545
|
+
["health", "#e05c6c"],
|
|
546
|
+
["culture_leisure", "#3aa675"],
|
|
547
|
+
["education", "#d9a13a"],
|
|
548
|
+
["services", "#7a8699"]
|
|
549
|
+
];
|
|
550
|
+
var POI_CATEGORY_IDS = POI_CATEGORY_COLORS.map(([id]) => id);
|
|
551
|
+
var POI_CLASS_CATEGORIES = {
|
|
552
|
+
food_drink: [
|
|
553
|
+
"restaurant",
|
|
554
|
+
"fast_food",
|
|
555
|
+
"cafe",
|
|
556
|
+
"bar",
|
|
557
|
+
"pub",
|
|
558
|
+
"biergarten",
|
|
559
|
+
"food_court",
|
|
560
|
+
"alcohol_shop"
|
|
561
|
+
],
|
|
562
|
+
shopping: [
|
|
563
|
+
"shop",
|
|
564
|
+
"grocery",
|
|
565
|
+
"supermarket",
|
|
566
|
+
"clothing_store",
|
|
567
|
+
"mall",
|
|
568
|
+
"department_store",
|
|
569
|
+
"convenience",
|
|
570
|
+
"bakery",
|
|
571
|
+
"marketplace"
|
|
572
|
+
],
|
|
573
|
+
transport: [
|
|
574
|
+
"railway",
|
|
575
|
+
"railway_station",
|
|
576
|
+
"subway",
|
|
577
|
+
"bus",
|
|
578
|
+
"bus_station",
|
|
579
|
+
"ferry_terminal",
|
|
580
|
+
"aerodrome",
|
|
581
|
+
"airport",
|
|
582
|
+
"airfield"
|
|
583
|
+
],
|
|
584
|
+
lodging: [
|
|
585
|
+
"lodging",
|
|
586
|
+
"hotel",
|
|
587
|
+
"motel",
|
|
588
|
+
"hostel",
|
|
589
|
+
"guest_house",
|
|
590
|
+
"camp_site",
|
|
591
|
+
"caravan_site",
|
|
592
|
+
"alpine_hut"
|
|
593
|
+
],
|
|
594
|
+
health: ["hospital", "pharmacy", "doctors", "dentist", "veterinary", "clinic"],
|
|
595
|
+
culture_leisure: [
|
|
596
|
+
"museum",
|
|
597
|
+
"theatre",
|
|
598
|
+
"cinema",
|
|
599
|
+
"attraction",
|
|
600
|
+
"park",
|
|
601
|
+
"stadium",
|
|
602
|
+
"art_gallery",
|
|
603
|
+
"zoo",
|
|
604
|
+
"swimming_pool",
|
|
605
|
+
"golf",
|
|
606
|
+
"playground",
|
|
607
|
+
"cemetery",
|
|
608
|
+
"garden",
|
|
609
|
+
"picnic_site",
|
|
610
|
+
"viewpoint"
|
|
611
|
+
],
|
|
612
|
+
education: ["school", "college", "university", "library", "kindergarten"],
|
|
613
|
+
services: [
|
|
614
|
+
"bank",
|
|
615
|
+
"post",
|
|
616
|
+
"police",
|
|
617
|
+
"town_hall",
|
|
618
|
+
"place_of_worship",
|
|
619
|
+
"courthouse",
|
|
620
|
+
"embassy",
|
|
621
|
+
"fire_station",
|
|
622
|
+
"community_centre",
|
|
623
|
+
"toilet",
|
|
624
|
+
"telephone",
|
|
625
|
+
"atm"
|
|
626
|
+
]
|
|
627
|
+
};
|
|
628
|
+
function builtInPoiColor(categoryId) {
|
|
629
|
+
return POI_CATEGORY_COLORS.find(([id]) => id === categoryId)?.[1];
|
|
630
|
+
}
|
|
631
|
+
function defaultPoiDesign() {
|
|
632
|
+
return { version: 1, categories: {} };
|
|
633
|
+
}
|
|
634
|
+
function poiDesignIsDefault(design) {
|
|
635
|
+
return Object.keys(design.categories).length === 0;
|
|
636
|
+
}
|
|
637
|
+
function isRecord2(v) {
|
|
638
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
639
|
+
}
|
|
640
|
+
function colour(v) {
|
|
641
|
+
if (typeof v !== "string") return void 0;
|
|
642
|
+
const t = v.trim();
|
|
643
|
+
if (t.startsWith("#")) {
|
|
644
|
+
const hex = t.slice(1);
|
|
645
|
+
return [3, 4, 6, 8].includes(hex.length) && /^[0-9a-fA-F]+$/.test(hex) ? t : void 0;
|
|
646
|
+
}
|
|
647
|
+
for (const prefix of ["rgb(", "rgba(", "hsl(", "hsla("]) {
|
|
648
|
+
if (t.startsWith(prefix) && t.endsWith(")")) return t;
|
|
649
|
+
}
|
|
650
|
+
return void 0;
|
|
651
|
+
}
|
|
652
|
+
function parsePoiDesign(value) {
|
|
653
|
+
const design = defaultPoiDesign();
|
|
654
|
+
if (!isRecord2(value) || !isRecord2(value.categories)) return design;
|
|
655
|
+
for (const id of POI_CATEGORY_IDS) {
|
|
656
|
+
const raw = value.categories[id];
|
|
657
|
+
if (!isRecord2(raw)) continue;
|
|
658
|
+
const category = {};
|
|
659
|
+
const color = colour(raw.color);
|
|
660
|
+
const textColor = colour(raw.textColor);
|
|
661
|
+
if (color !== void 0) category.color = color;
|
|
662
|
+
if (textColor !== void 0) category.textColor = textColor;
|
|
663
|
+
if (Object.keys(category).length > 0) design.categories[id] = category;
|
|
664
|
+
}
|
|
665
|
+
return design;
|
|
666
|
+
}
|
|
667
|
+
function poiDesignFromTheme(theme) {
|
|
668
|
+
const extra = theme?.extra;
|
|
669
|
+
if (typeof extra !== "object" || extra === null || !("poi" in extra)) {
|
|
670
|
+
return void 0;
|
|
671
|
+
}
|
|
672
|
+
return parsePoiDesign(extra.poi);
|
|
673
|
+
}
|
|
674
|
+
async function poiDesignFromThemeUrl(url, fetchImpl) {
|
|
675
|
+
const doFetch = fetchImpl ?? ((input) => globalThis.fetch(input));
|
|
676
|
+
const response = await doFetch(url);
|
|
677
|
+
if (!response.ok) {
|
|
678
|
+
throw new Error(`fetching theme ${url}: HTTP ${response.status}`);
|
|
679
|
+
}
|
|
680
|
+
const theme = await response.json();
|
|
681
|
+
return poiDesignFromTheme(theme);
|
|
682
|
+
}
|
|
683
|
+
function poiTextColorExpression(design, fallback) {
|
|
684
|
+
if (!POI_CATEGORY_IDS.some((id) => design.categories[id]?.textColor)) return fallback;
|
|
685
|
+
const arms = POI_CATEGORY_IDS.filter((id) => id !== "services").flatMap((id) => [
|
|
686
|
+
POI_CLASS_CATEGORIES[id],
|
|
687
|
+
design.categories[id]?.textColor ?? fallback
|
|
688
|
+
]);
|
|
689
|
+
return [
|
|
690
|
+
"match",
|
|
691
|
+
["get", "class"],
|
|
692
|
+
...arms,
|
|
693
|
+
design.categories.services?.textColor ?? fallback
|
|
694
|
+
];
|
|
695
|
+
}
|
|
696
|
+
function applyPoiDesign(map, design, fallbackTextColor = "#6b6b6b") {
|
|
697
|
+
const value = poiTextColorExpression(design, fallbackTextColor);
|
|
698
|
+
for (const layer of map.getStyle()?.layers ?? []) {
|
|
699
|
+
if (layer.id === "poi-labels" || layer.id.startsWith("poi-labels@")) {
|
|
700
|
+
map.setPaintProperty(layer.id, "text-color", value);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
135
705
|
// src/style.ts
|
|
136
706
|
var OSM_ATTRIBUTION = "\xA9 OpenStreetMap contributors";
|
|
137
707
|
var OPENMAPTILES_ATTRIBUTION = "\xA9 OpenMapTiles";
|
|
@@ -175,6 +745,46 @@ var PALETTE_SLOTS = [
|
|
|
175
745
|
["textSecondary", "#6b6b6b", "#9aa3ad"],
|
|
176
746
|
["textHalo", "#ffffff", "#12161c"]
|
|
177
747
|
];
|
|
748
|
+
var FLOW_PARAM_KEYS = ["color", "width", "speed"];
|
|
749
|
+
function validateEffects(effects) {
|
|
750
|
+
const route = effects.route ?? "none";
|
|
751
|
+
if (route !== "flow" && route !== "none") {
|
|
752
|
+
throw new Error(
|
|
753
|
+
`invalid effects block: unknown route effect ${JSON.stringify(route)}; accepted: "flow", "none"`
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
if (!effects.params) return;
|
|
757
|
+
if (route === "none") {
|
|
758
|
+
throw new Error(
|
|
759
|
+
'invalid effects block: "params" requires "route" to name an effect (e.g. "flow")'
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
for (const [key, value] of Object.entries(effects.params)) {
|
|
763
|
+
if (key === "color") {
|
|
764
|
+
if (typeof value !== "string" || !isCssColour(value)) {
|
|
765
|
+
throw new Error(
|
|
766
|
+
`invalid effects block: params.color has invalid colour ${JSON.stringify(value)}; use #rgb/#rrggbb/#rrggbbaa or rgb()/rgba()/hsl()/hsla()`
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
} else if (key === "width") {
|
|
770
|
+
if (typeof value !== "number" || value < 0.5 || value > 40) {
|
|
771
|
+
throw new Error(
|
|
772
|
+
`invalid effects block: params.width must be a number between 0.5 and 40 (pixels), got ${JSON.stringify(value)}`
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
} else if (key === "speed") {
|
|
776
|
+
if (typeof value !== "number" || value < 0 || value > 10) {
|
|
777
|
+
throw new Error(
|
|
778
|
+
`invalid effects block: params.speed must be a number between 0 and 10 (cycles per second), got ${JSON.stringify(value)}`
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
} else {
|
|
782
|
+
throw new Error(
|
|
783
|
+
`invalid effects block: unknown params key ${JSON.stringify(key)}; accepted keys: ${FLOW_PARAM_KEYS.join(", ")}`
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
178
788
|
function toPmtilesUrl(url) {
|
|
179
789
|
return url.startsWith("pmtiles://") ? url : `pmtiles://${url}`;
|
|
180
790
|
}
|
|
@@ -212,18 +822,18 @@ function resolvePalette(theme) {
|
|
|
212
822
|
}
|
|
213
823
|
return palette;
|
|
214
824
|
}
|
|
215
|
-
function fill(id, sourceLayer,
|
|
825
|
+
function fill(id, sourceLayer, colour2, opacity, minzoom) {
|
|
216
826
|
const layer = {
|
|
217
827
|
id,
|
|
218
828
|
type: "fill",
|
|
219
829
|
source: "territory",
|
|
220
830
|
"source-layer": sourceLayer,
|
|
221
|
-
paint: { "fill-color":
|
|
831
|
+
paint: { "fill-color": colour2, "fill-opacity": opacity }
|
|
222
832
|
};
|
|
223
833
|
if (minzoom !== void 0) layer["minzoom"] = minzoom;
|
|
224
834
|
return layer;
|
|
225
835
|
}
|
|
226
|
-
function symbol(id, sourceLayer, textField, font, textSize,
|
|
836
|
+
function symbol(id, sourceLayer, textField, font, textSize, colour2, halo, minzoom, extraLayout) {
|
|
227
837
|
const layout = {
|
|
228
838
|
"text-field": textField,
|
|
229
839
|
"text-font": font,
|
|
@@ -237,7 +847,7 @@ function symbol(id, sourceLayer, textField, font, textSize, colour, halo, minzoo
|
|
|
237
847
|
"source-layer": sourceLayer,
|
|
238
848
|
layout,
|
|
239
849
|
paint: {
|
|
240
|
-
"text-color":
|
|
850
|
+
"text-color": colour2,
|
|
241
851
|
"text-halo-color": halo,
|
|
242
852
|
"text-halo-width": 1.2
|
|
243
853
|
}
|
|
@@ -295,10 +905,20 @@ var THEME_KEYS = [
|
|
|
295
905
|
"sprite",
|
|
296
906
|
"extra_layers",
|
|
297
907
|
"extra",
|
|
298
|
-
"buildings_3d"
|
|
908
|
+
"buildings_3d",
|
|
909
|
+
"effects"
|
|
299
910
|
];
|
|
911
|
+
var EFFECTS_METADATA_KEY = "mapmap:effects";
|
|
912
|
+
function effectsFromStyleMetadata(metadata) {
|
|
913
|
+
if (typeof metadata !== "object" || metadata === null) return void 0;
|
|
914
|
+
const block = metadata[EFFECTS_METADATA_KEY];
|
|
915
|
+
if (typeof block !== "object" || block === null) return void 0;
|
|
916
|
+
const { route, params } = block;
|
|
917
|
+
if (route !== "flow") return void 0;
|
|
918
|
+
return typeof params === "object" && params !== null ? { route, params } : { route };
|
|
919
|
+
}
|
|
300
920
|
var BUILDINGS_3D_MINZOOM = 15;
|
|
301
|
-
function buildings3dLayer(
|
|
921
|
+
function buildings3dLayer(colour2) {
|
|
302
922
|
return {
|
|
303
923
|
id: "building-3d",
|
|
304
924
|
type: "fill-extrusion",
|
|
@@ -306,7 +926,7 @@ function buildings3dLayer(colour) {
|
|
|
306
926
|
"source-layer": "building",
|
|
307
927
|
minzoom: BUILDINGS_3D_MINZOOM,
|
|
308
928
|
paint: {
|
|
309
|
-
"fill-extrusion-color":
|
|
929
|
+
"fill-extrusion-color": colour2,
|
|
310
930
|
"fill-extrusion-height": [
|
|
311
931
|
"interpolate",
|
|
312
932
|
["linear"],
|
|
@@ -330,6 +950,11 @@ function buildings3dLayer(colour) {
|
|
|
330
950
|
}
|
|
331
951
|
};
|
|
332
952
|
}
|
|
953
|
+
function nameFieldFor(labelLanguage) {
|
|
954
|
+
if (labelLanguage === "local") return ["get", "name"];
|
|
955
|
+
if (labelLanguage) return ["coalesce", ["get", `name:${labelLanguage}`], ["get", "name"]];
|
|
956
|
+
return ["coalesce", ["get", "name:en"], ["get", "name"]];
|
|
957
|
+
}
|
|
333
958
|
function buildStyle(options = {}) {
|
|
334
959
|
const input = options.theme ?? "light";
|
|
335
960
|
if (typeof input === "object") {
|
|
@@ -345,7 +970,7 @@ function buildStyle(options = {}) {
|
|
|
345
970
|
const palette = resolvePalette(theme);
|
|
346
971
|
const p = (slot) => palette[slot];
|
|
347
972
|
const font = [theme.fonts?.regular ?? "Noto Sans Regular"];
|
|
348
|
-
const nameField =
|
|
973
|
+
const nameField = nameFieldFor(options.labelLanguage);
|
|
349
974
|
const tilesUrl = toPmtilesUrl(
|
|
350
975
|
options.territoryTilesUrl ?? DEFAULT_TERRITORY_TILES_URL
|
|
351
976
|
);
|
|
@@ -536,15 +1161,40 @@ function buildStyle(options = {}) {
|
|
|
536
1161
|
p("textHalo"),
|
|
537
1162
|
10
|
|
538
1163
|
),
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
1164
|
+
// Label hierarchy: settlements and regions wait until z4 so world
|
|
1165
|
+
// zooms aren't a flat democracy of states, cities and villages;
|
|
1166
|
+
// continent labels are dropped entirely as clutter.
|
|
1167
|
+
{
|
|
1168
|
+
...symbol(
|
|
1169
|
+
"place-labels",
|
|
1170
|
+
"place",
|
|
1171
|
+
nameField,
|
|
1172
|
+
font,
|
|
1173
|
+
["interpolate", ["linear"], ["zoom"], 4, 10.5, 12, 16],
|
|
1174
|
+
p("textPrimary"),
|
|
1175
|
+
p("textHalo"),
|
|
1176
|
+
4
|
|
1177
|
+
),
|
|
1178
|
+
filter: ["match", ["get", "class"], ["country", "continent"], false, true]
|
|
1179
|
+
},
|
|
1180
|
+
// Countries: the most prominent text on the map at world zooms —
|
|
1181
|
+
// larger, letter-spaced, visible from z1, ceding to the regional
|
|
1182
|
+
// hierarchy from z10. Drawn last = wins label collisions.
|
|
1183
|
+
{
|
|
1184
|
+
...symbol(
|
|
1185
|
+
"country-labels",
|
|
1186
|
+
"place",
|
|
1187
|
+
nameField,
|
|
1188
|
+
font,
|
|
1189
|
+
["interpolate", ["linear"], ["zoom"], 1, 11, 3, 13.5, 6, 18],
|
|
1190
|
+
p("textPrimary"),
|
|
1191
|
+
p("textHalo"),
|
|
1192
|
+
1,
|
|
1193
|
+
{ "text-letter-spacing": 0.08, "text-max-width": 7 }
|
|
1194
|
+
),
|
|
1195
|
+
filter: ["==", ["get", "class"], "country"],
|
|
1196
|
+
maxzoom: 10
|
|
1197
|
+
}
|
|
548
1198
|
);
|
|
549
1199
|
const seenIds = /* @__PURE__ */ new Set();
|
|
550
1200
|
for (const layer of layers) {
|
|
@@ -580,6 +1230,14 @@ function buildStyle(options = {}) {
|
|
|
580
1230
|
layers
|
|
581
1231
|
};
|
|
582
1232
|
if (theme.sprite !== void 0) style["sprite"] = theme.sprite;
|
|
1233
|
+
if (theme.effects) {
|
|
1234
|
+
validateEffects(theme.effects);
|
|
1235
|
+
if ((theme.effects.route ?? "none") !== "none") {
|
|
1236
|
+
const block = { route: theme.effects.route };
|
|
1237
|
+
if (theme.effects.params) block["params"] = theme.effects.params;
|
|
1238
|
+
style["metadata"] = { [EFFECTS_METADATA_KEY]: block };
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
583
1241
|
return style;
|
|
584
1242
|
}
|
|
585
1243
|
|
|
@@ -594,12 +1252,37 @@ function registerPmtilesProtocol(gl = maplibregl) {
|
|
|
594
1252
|
var DEFAULT_BASE_URL = "https://api.mapmap.ai";
|
|
595
1253
|
var DEFAULT_CENTER = [-1.5, 52.6];
|
|
596
1254
|
var DEFAULT_ZOOM = 5;
|
|
1255
|
+
var DEFAULT_EFFECT_LAYER_ID = "mapmap-route-effect";
|
|
1256
|
+
function flowOptionsFromParams(params) {
|
|
1257
|
+
const options = {};
|
|
1258
|
+
if (typeof params["color"] === "string") options.color = params["color"];
|
|
1259
|
+
if (typeof params["width"] === "number") options.width = params["width"];
|
|
1260
|
+
if (typeof params["speed"] === "number") options.speed = params["speed"];
|
|
1261
|
+
return options;
|
|
1262
|
+
}
|
|
597
1263
|
var MapMapMap = class {
|
|
598
1264
|
constructor(options) {
|
|
1265
|
+
// Route-effect state (see setRouteEffect / effects.ts). `explicit`
|
|
1266
|
+
// records an app-level decision, which always wins over a style's
|
|
1267
|
+
// metadata auto-enable.
|
|
1268
|
+
this.effectName = null;
|
|
1269
|
+
this.effectOptions = {};
|
|
1270
|
+
this.effectExplicit = false;
|
|
1271
|
+
this.handleStyleLoadForEffects = () => {
|
|
1272
|
+
if (!this.effectExplicit) {
|
|
1273
|
+
const block = effectsFromStyleMetadata(this.styleMetadata());
|
|
1274
|
+
this.effectName = block?.route ?? null;
|
|
1275
|
+
this.effectOptions = block?.params ? flowOptionsFromParams(block.params) : {};
|
|
1276
|
+
}
|
|
1277
|
+
this.effectLayer = void 0;
|
|
1278
|
+
queueMicrotask(() => this.applyRouteEffect());
|
|
1279
|
+
};
|
|
599
1280
|
registerPmtilesProtocol();
|
|
600
1281
|
this.apiKey = options.apiKey;
|
|
601
1282
|
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
602
|
-
|
|
1283
|
+
const theme = themeOf(options.style);
|
|
1284
|
+
this.navDesign = navDesignFromTheme(theme);
|
|
1285
|
+
this.poiDesign = poiDesignFromTheme(theme);
|
|
603
1286
|
const style = resolveStyle(options.style, options.territoryTilesUrl);
|
|
604
1287
|
this.map = new maplibregl.Map({
|
|
605
1288
|
container: options.container,
|
|
@@ -616,6 +1299,102 @@ var MapMapMap = class {
|
|
|
616
1299
|
logoOptions.position ?? "bottom-right"
|
|
617
1300
|
);
|
|
618
1301
|
}
|
|
1302
|
+
this.map.on("style.load", this.handleStyleLoadForEffects);
|
|
1303
|
+
runMapDiagnostics({
|
|
1304
|
+
container: options.container,
|
|
1305
|
+
map: this.map,
|
|
1306
|
+
maplibre: maplibregl,
|
|
1307
|
+
apiKey: this.apiKey
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
/** The current style's `metadata`, if it can be read yet. */
|
|
1311
|
+
styleMetadata() {
|
|
1312
|
+
try {
|
|
1313
|
+
return this.map.getStyle?.()?.metadata;
|
|
1314
|
+
} catch {
|
|
1315
|
+
return void 0;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* Set (or clear) the visual effect on the active route line - the
|
|
1320
|
+
* effects-engine entry point (see `effects.ts`). The first effect is
|
|
1321
|
+
* `"flow"`: an animated energy ribbon flowing along the route, drawn in
|
|
1322
|
+
* a MapLibre custom layer with first-party GLSL.
|
|
1323
|
+
*
|
|
1324
|
+
* ```ts
|
|
1325
|
+
* const route = await routes.route(from, to);
|
|
1326
|
+
* map.setRouteEffect("flow"); // uses the drawn route
|
|
1327
|
+
* map.setRouteEffect("flow", { color: "#ff7a1f" }); // options
|
|
1328
|
+
* map.setRouteEffect(null); // back to the plain line
|
|
1329
|
+
* ```
|
|
1330
|
+
*
|
|
1331
|
+
* Geometry: pass `{ geometry }` explicitly, or omit it and the effect
|
|
1332
|
+
* attaches to whatever route a `RouteLayer` on this map draws (current
|
|
1333
|
+
* and future - RouteLayer reports every drawn line via
|
|
1334
|
+
* {@link setRouteEffectGeometry}).
|
|
1335
|
+
*
|
|
1336
|
+
* Styles whose theme carried an `effects` block auto-enable their effect
|
|
1337
|
+
* from the compiled style's metadata; calling this method (with any
|
|
1338
|
+
* value, including `null`) overrides the style's wish for the lifetime
|
|
1339
|
+
* of this map.
|
|
1340
|
+
*
|
|
1341
|
+
* Accessibility/resilience (see effects.ts): honours
|
|
1342
|
+
* `prefers-reduced-motion` (static gradient, no animation) and falls
|
|
1343
|
+
* back to the plain route line with a single console warning if WebGL
|
|
1344
|
+
* setup fails.
|
|
1345
|
+
*/
|
|
1346
|
+
setRouteEffect(effect, options = {}) {
|
|
1347
|
+
this.effectExplicit = true;
|
|
1348
|
+
const { geometry, ...flowOptions } = options;
|
|
1349
|
+
this.effectName = effect;
|
|
1350
|
+
this.effectOptions = flowOptions;
|
|
1351
|
+
if (geometry) this.effectGeometry = geometry;
|
|
1352
|
+
this.removeEffectLayer();
|
|
1353
|
+
this.applyRouteEffect();
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
1356
|
+
* Attach the active route effect to a route line (or detach it with
|
|
1357
|
+
* `null`). `RouteLayer` calls this on every `draw()`/`clear()`, so apps
|
|
1358
|
+
* normally never do - pass `geometry` to {@link setRouteEffect} for
|
|
1359
|
+
* routes drawn outside a RouteLayer.
|
|
1360
|
+
*/
|
|
1361
|
+
setRouteEffectGeometry(geometry) {
|
|
1362
|
+
this.effectGeometry = geometry ?? void 0;
|
|
1363
|
+
if (geometry && this.effectLayer && this.map.getLayer(this.effectLayerId())) {
|
|
1364
|
+
this.effectLayer.setGeometry(geometry);
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
this.applyRouteEffect();
|
|
1368
|
+
}
|
|
1369
|
+
effectLayerId() {
|
|
1370
|
+
return this.effectOptions.id ?? DEFAULT_EFFECT_LAYER_ID;
|
|
1371
|
+
}
|
|
1372
|
+
removeEffectLayer() {
|
|
1373
|
+
const id = this.effectLayerId();
|
|
1374
|
+
if (this.map.getLayer(id)) this.map.removeLayer(id);
|
|
1375
|
+
this.effectLayer = void 0;
|
|
1376
|
+
}
|
|
1377
|
+
/** Reconcile the effect state with the map: install, update or remove. */
|
|
1378
|
+
applyRouteEffect() {
|
|
1379
|
+
const id = this.effectLayerId();
|
|
1380
|
+
if (this.effectName === null || this.effectGeometry === void 0) {
|
|
1381
|
+
this.removeEffectLayer();
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
if (!this.map.isStyleLoaded()) {
|
|
1385
|
+
this.map.once("load", () => this.applyRouteEffect());
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
if (this.effectLayer && this.map.getLayer(id)) {
|
|
1389
|
+
this.effectLayer.setGeometry(this.effectGeometry);
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
if (this.map.getLayer(id)) this.map.removeLayer(id);
|
|
1393
|
+
this.effectLayer = createRouteEffect(this.effectName, this.effectGeometry, {
|
|
1394
|
+
...this.effectOptions,
|
|
1395
|
+
id
|
|
1396
|
+
});
|
|
1397
|
+
this.map.addLayer(this.effectLayer);
|
|
619
1398
|
}
|
|
620
1399
|
/**
|
|
621
1400
|
* Toggle 3D building extrusions at runtime — the live equivalent of
|
|
@@ -644,9 +1423,9 @@ var MapMapMap = class {
|
|
|
644
1423
|
}
|
|
645
1424
|
const minzoom = building.minzoom ?? 13;
|
|
646
1425
|
if (enabled) {
|
|
647
|
-
const
|
|
1426
|
+
const colour2 = this.map.getPaintProperty("building", "fill-color") ?? "#e2ddd4";
|
|
648
1427
|
this.map.addLayer(
|
|
649
|
-
buildings3dLayer(
|
|
1428
|
+
buildings3dLayer(colour2),
|
|
650
1429
|
buildings3dBeforeId(this.map)
|
|
651
1430
|
);
|
|
652
1431
|
this.map.setLayerZoomRange("building", minzoom, BUILDINGS_3D_MINZOOM);
|
|
@@ -815,6 +1594,7 @@ var RouteLayer = class {
|
|
|
815
1594
|
};
|
|
816
1595
|
if (map instanceof MapMapMap) {
|
|
817
1596
|
this.map = map.map;
|
|
1597
|
+
this.owner = map;
|
|
818
1598
|
this.baseUrl = (options.baseUrl ?? map.baseUrl).replace(/\/+$/, "");
|
|
819
1599
|
this.apiKey = options.apiKey ?? map.apiKey;
|
|
820
1600
|
this.design = options.design ?? map.navDesign?.route;
|
|
@@ -878,8 +1658,8 @@ var RouteLayer = class {
|
|
|
878
1658
|
*/
|
|
879
1659
|
draw(route) {
|
|
880
1660
|
this.lastRoute = route;
|
|
881
|
-
if (
|
|
882
|
-
this.
|
|
1661
|
+
if (this.map.isStyleLoaded()) this.install(route);
|
|
1662
|
+
this.owner?.setRouteEffectGeometry(route.geometry);
|
|
883
1663
|
}
|
|
884
1664
|
/** Add-or-update the source and layers for a route on the current style. */
|
|
885
1665
|
install(route) {
|
|
@@ -935,6 +1715,7 @@ var RouteLayer = class {
|
|
|
935
1715
|
}
|
|
936
1716
|
if (this.map.getSource(this.sourceId)) this.map.removeSource(this.sourceId);
|
|
937
1717
|
this.lastRoute = void 0;
|
|
1718
|
+
this.owner?.setRouteEffectGeometry(null);
|
|
938
1719
|
}
|
|
939
1720
|
/**
|
|
940
1721
|
* Remove the route and detach the layer's `style.load` listener. Call
|
|
@@ -1045,7 +1826,7 @@ function stylePuckElement(el, puck) {
|
|
|
1045
1826
|
dot.style.cssText = `position:absolute;inset:0;border-radius:50%;background:${puck.color};border:2px solid #ffffff;box-shadow:0 1px 6px rgba(0,0,0,0.45)`;
|
|
1046
1827
|
arrow.style.cssText = puck.headingArrow ? `position:absolute;left:50%;top:${(-s * 0.55).toFixed(1)}px;transform:translateX(-50%);width:0;height:0;border-left:${(s * 0.35).toFixed(1)}px solid transparent;border-right:${(s * 0.35).toFixed(1)}px solid transparent;border-bottom:${(s * 0.6).toFixed(1)}px solid ${puck.color}` : "display:none";
|
|
1047
1828
|
}
|
|
1048
|
-
var
|
|
1829
|
+
var EARTH_RADIUS_M2 = 63710088e-1;
|
|
1049
1830
|
var CLUSTER_TEXT_FONT = "Noto Sans Regular";
|
|
1050
1831
|
function placesFromGeoJSON(collection) {
|
|
1051
1832
|
const places = [];
|
|
@@ -1073,7 +1854,7 @@ function haversineDistanceM(a, b) {
|
|
|
1073
1854
|
const dLat = rad(b.lat - a.lat);
|
|
1074
1855
|
const dLon = rad(b.lon - a.lon);
|
|
1075
1856
|
const h = Math.sin(dLat / 2) ** 2 + Math.cos(rad(a.lat)) * Math.cos(rad(b.lat)) * Math.sin(dLon / 2) ** 2;
|
|
1076
|
-
return 2 *
|
|
1857
|
+
return 2 * EARTH_RADIUS_M2 * Math.asin(Math.sqrt(h));
|
|
1077
1858
|
}
|
|
1078
1859
|
var PlacesLayer = class {
|
|
1079
1860
|
constructor(map, options = {}) {
|
|
@@ -1134,6 +1915,7 @@ var PlacesLayer = class {
|
|
|
1134
1915
|
this.clusterRadius = options.clusterRadius;
|
|
1135
1916
|
this.clusterMaxZoom = options.clusterMaxZoom;
|
|
1136
1917
|
this.color = options.color ?? SIGNAL_BLUE;
|
|
1918
|
+
this.clusterColor = options.clusterColor ?? (typeof this.color === "string" ? this.color : SIGNAL_BLUE);
|
|
1137
1919
|
this.icon = options.icon;
|
|
1138
1920
|
this.wantFitBounds = options.fitBounds ?? false;
|
|
1139
1921
|
this.onPlaceClick = options.onPlaceClick;
|
|
@@ -1151,9 +1933,12 @@ var PlacesLayer = class {
|
|
|
1151
1933
|
}
|
|
1152
1934
|
/**
|
|
1153
1935
|
* Replace the layer's places - a `Place[]` or a GeoJSON FeatureCollection
|
|
1154
|
-
* of Points.
|
|
1155
|
-
*
|
|
1156
|
-
* `
|
|
1936
|
+
* of Points. Never silently drops an update: once the source exists the
|
|
1937
|
+
* data is applied immediately, even while `isStyleLoaded()` is transiently
|
|
1938
|
+
* `false` mid-render (search-as-you-type just works); calls made before
|
|
1939
|
+
* the source has first been installed are stashed - the latest one wins -
|
|
1940
|
+
* and installed on the next `style.load`. With `fitBounds: true` the
|
|
1941
|
+
* first non-empty set also fits the map view.
|
|
1157
1942
|
*/
|
|
1158
1943
|
setPlaces(places) {
|
|
1159
1944
|
this.places = Array.isArray(places) ? places : placesFromGeoJSON(places);
|
|
@@ -1174,13 +1959,59 @@ var PlacesLayer = class {
|
|
|
1174
1959
|
this.fitted = true;
|
|
1175
1960
|
this.map.fitBounds(boundsOf(this.places), { padding: 48, maxZoom: 15 });
|
|
1176
1961
|
}
|
|
1177
|
-
|
|
1178
|
-
|
|
1962
|
+
const source = this.map.getSource(this.sourceId);
|
|
1963
|
+
if (source) {
|
|
1964
|
+
source.setData(this.data);
|
|
1965
|
+
} else if (this.map.isStyleLoaded()) {
|
|
1966
|
+
this.install(this.data);
|
|
1967
|
+
}
|
|
1179
1968
|
}
|
|
1180
1969
|
/** The layer's current places (normalised to `Place[]`). */
|
|
1181
1970
|
get current() {
|
|
1182
1971
|
return this.places;
|
|
1183
1972
|
}
|
|
1973
|
+
/**
|
|
1974
|
+
* The generated MapLibre source/layer ids - public API for escape-hatch
|
|
1975
|
+
* styling (`map.setPaintProperty`, `queryRenderedFeatures`, …) beyond the
|
|
1976
|
+
* layer's options. Stable for the layer's lifetime, derived from the `id`
|
|
1977
|
+
* option (default `"mapmap-places"`). The cluster ids are only installed
|
|
1978
|
+
* on the map with `cluster: true` (the default), and `points` is a circle
|
|
1979
|
+
* layer by default or a symbol layer once a custom `icon` has loaded.
|
|
1980
|
+
*/
|
|
1981
|
+
get ids() {
|
|
1982
|
+
return {
|
|
1983
|
+
source: this.sourceId,
|
|
1984
|
+
points: this.pointsLayerId,
|
|
1985
|
+
clusters: this.clustersLayerId,
|
|
1986
|
+
clusterCounts: this.clusterCountLayerId
|
|
1987
|
+
};
|
|
1988
|
+
}
|
|
1989
|
+
/**
|
|
1990
|
+
* Programmatically select a place by id - list-to-map sync for a store
|
|
1991
|
+
* finder's results list. Opens the layer's configured `popup` at the
|
|
1992
|
+
* place (`popup: false` to skip, no-op without a `popup` option) and
|
|
1993
|
+
* eases the camera to it (`flyTo: false` to skip; `zoom` to also zoom).
|
|
1994
|
+
* Returns the selected place, or `undefined` for an unknown id (in which
|
|
1995
|
+
* case nothing happens). Does NOT invoke `onPlaceClick` - a programmatic
|
|
1996
|
+
* selection is not a user click.
|
|
1997
|
+
*/
|
|
1998
|
+
select(id, options = {}) {
|
|
1999
|
+
const place = this.places.find((p) => p.id === id);
|
|
2000
|
+
if (!place) return void 0;
|
|
2001
|
+
if (options.flyTo ?? true) {
|
|
2002
|
+
this.map.easeTo({
|
|
2003
|
+
center: [place.lon, place.lat],
|
|
2004
|
+
...options.zoom !== void 0 ? { zoom: options.zoom } : {}
|
|
2005
|
+
});
|
|
2006
|
+
}
|
|
2007
|
+
if (options.popup ?? true) this.openPopup(place);
|
|
2008
|
+
return place;
|
|
2009
|
+
}
|
|
2010
|
+
/** Close the popup opened by {@link select} or a place click, if any. */
|
|
2011
|
+
deselect() {
|
|
2012
|
+
this.popup?.remove();
|
|
2013
|
+
this.popup = void 0;
|
|
2014
|
+
}
|
|
1184
2015
|
/**
|
|
1185
2016
|
* The nearest `n` places (default `1`) to `origin` by straight-line
|
|
1186
2017
|
* (haversine) distance, each with `distanceM` attached. Pure and instant -
|
|
@@ -1269,7 +2100,7 @@ var PlacesLayer = class {
|
|
|
1269
2100
|
source: this.sourceId,
|
|
1270
2101
|
filter: ["has", "point_count"],
|
|
1271
2102
|
paint: {
|
|
1272
|
-
"circle-color": this.
|
|
2103
|
+
"circle-color": this.clusterColor,
|
|
1273
2104
|
"circle-opacity": 0.9,
|
|
1274
2105
|
"circle-radius": ["step", ["get", "point_count"], 16, 25, 20, 100, 26],
|
|
1275
2106
|
"circle-stroke-color": "#ffffff",
|
|
@@ -1507,9 +2338,9 @@ var NavigationCamera = class {
|
|
|
1507
2338
|
* {@link resume}.
|
|
1508
2339
|
*/
|
|
1509
2340
|
follow(fix, courseDeg) {
|
|
1510
|
-
const
|
|
1511
|
-
const interval = this.lastFixAt === void 0 ? this.easeMs : Math.max(0,
|
|
1512
|
-
this.lastFixAt =
|
|
2341
|
+
const now2 = Date.now();
|
|
2342
|
+
const interval = this.lastFixAt === void 0 ? this.easeMs : Math.max(0, now2 - this.lastFixAt);
|
|
2343
|
+
this.lastFixAt = now2;
|
|
1513
2344
|
this.lastFix = fix;
|
|
1514
2345
|
if (courseDeg !== void 0) this.lastCourse = courseDeg;
|
|
1515
2346
|
this.puck?.setLocation(fix, courseDeg);
|
|
@@ -1609,6 +2440,186 @@ function boundsOf2(coordinates) {
|
|
|
1609
2440
|
[maxLng, maxLat]
|
|
1610
2441
|
];
|
|
1611
2442
|
}
|
|
2443
|
+
var FLYTHROUGH_EARTH_RADIUS_M = 63710088e-1;
|
|
2444
|
+
function flythroughDistanceM(a, b) {
|
|
2445
|
+
const rad = (deg) => deg * Math.PI / 180;
|
|
2446
|
+
const dLat = rad(b[1] - a[1]);
|
|
2447
|
+
const dLon = rad(b[0] - a[0]);
|
|
2448
|
+
const h = Math.sin(dLat / 2) ** 2 + Math.cos(rad(a[1])) * Math.cos(rad(b[1])) * Math.sin(dLon / 2) ** 2;
|
|
2449
|
+
return 2 * FLYTHROUGH_EARTH_RADIUS_M * Math.asin(Math.sqrt(h));
|
|
2450
|
+
}
|
|
2451
|
+
function bearingBetween(a, b) {
|
|
2452
|
+
const rad = (deg2) => deg2 * Math.PI / 180;
|
|
2453
|
+
const dLon = rad(b[0] - a[0]);
|
|
2454
|
+
const latA = rad(a[1]);
|
|
2455
|
+
const latB = rad(b[1]);
|
|
2456
|
+
const y = Math.sin(dLon) * Math.cos(latB);
|
|
2457
|
+
const x = Math.cos(latA) * Math.sin(latB) - Math.sin(latA) * Math.cos(latB) * Math.cos(dLon);
|
|
2458
|
+
const deg = Math.atan2(y, x) * 180 / Math.PI;
|
|
2459
|
+
return (deg + 360) % 360;
|
|
2460
|
+
}
|
|
2461
|
+
function shortestArcDelta(from, to) {
|
|
2462
|
+
const delta = ((to - from) % 360 + 540) % 360 - 180;
|
|
2463
|
+
return delta === -180 ? 180 : delta;
|
|
2464
|
+
}
|
|
2465
|
+
var PoseSampler = class {
|
|
2466
|
+
constructor(coordinates) {
|
|
2467
|
+
this.points = coordinates.filter(
|
|
2468
|
+
(p, i) => i === 0 || p[0] !== coordinates[i - 1][0] || p[1] !== coordinates[i - 1][1]
|
|
2469
|
+
);
|
|
2470
|
+
if (this.points.length < 2) {
|
|
2471
|
+
throw new Error(
|
|
2472
|
+
"flythrough needs a route with at least two distinct coordinates"
|
|
2473
|
+
);
|
|
2474
|
+
}
|
|
2475
|
+
this.cumulative = [0];
|
|
2476
|
+
for (let i = 1; i < this.points.length; i++) {
|
|
2477
|
+
this.cumulative.push(
|
|
2478
|
+
this.cumulative[i - 1] + flythroughDistanceM(this.points[i - 1], this.points[i])
|
|
2479
|
+
);
|
|
2480
|
+
}
|
|
2481
|
+
this.totalM = this.cumulative[this.cumulative.length - 1];
|
|
2482
|
+
}
|
|
2483
|
+
/** The interpolated point `distanceM` along the route (clamped). */
|
|
2484
|
+
pointAt(distanceM) {
|
|
2485
|
+
const d = Math.min(this.totalM, Math.max(0, distanceM));
|
|
2486
|
+
let i = 1;
|
|
2487
|
+
while (i < this.cumulative.length - 1 && this.cumulative[i] < d) i++;
|
|
2488
|
+
const before = this.cumulative[i - 1];
|
|
2489
|
+
const span = this.cumulative[i] - before;
|
|
2490
|
+
const f = span > 0 ? (d - before) / span : 0;
|
|
2491
|
+
const a = this.points[i - 1];
|
|
2492
|
+
const b = this.points[i];
|
|
2493
|
+
return [a[0] + (b[0] - a[0]) * f, a[1] + (b[1] - a[1]) * f];
|
|
2494
|
+
}
|
|
2495
|
+
/** The pose at progress `t` (clamped 0-1), chasing `lookAheadM` ahead. */
|
|
2496
|
+
poseAt(t, lookAheadM) {
|
|
2497
|
+
const d = Math.min(1, Math.max(0, t)) * this.totalM;
|
|
2498
|
+
const center = this.pointAt(d);
|
|
2499
|
+
let target = this.pointAt(d + Math.max(1, lookAheadM));
|
|
2500
|
+
if (target[0] === center[0] && target[1] === center[1]) {
|
|
2501
|
+
const last = this.points[this.points.length - 1];
|
|
2502
|
+
const prev = this.points[this.points.length - 2];
|
|
2503
|
+
return { center, bearing: bearingBetween(prev, last) };
|
|
2504
|
+
}
|
|
2505
|
+
return { center, bearing: bearingBetween(center, target) };
|
|
2506
|
+
}
|
|
2507
|
+
};
|
|
2508
|
+
function flythroughPose(coordinates, t, lookAheadM = 200) {
|
|
2509
|
+
return new PoseSampler(coordinates).poseAt(t, lookAheadM);
|
|
2510
|
+
}
|
|
2511
|
+
function scheduleFrame(cb) {
|
|
2512
|
+
if (typeof requestAnimationFrame === "function") {
|
|
2513
|
+
const handle2 = requestAnimationFrame(cb);
|
|
2514
|
+
return () => cancelAnimationFrame(handle2);
|
|
2515
|
+
}
|
|
2516
|
+
const handle = setTimeout(() => cb(Date.now()), 16);
|
|
2517
|
+
return () => clearTimeout(handle);
|
|
2518
|
+
}
|
|
2519
|
+
function flythrough(map, route, options = {}) {
|
|
2520
|
+
const m = map instanceof MapMapMap ? map.map : map;
|
|
2521
|
+
const geometry = "geometry" in route ? route.geometry : route;
|
|
2522
|
+
const sampler = new PoseSampler(geometry.coordinates);
|
|
2523
|
+
const pitch = clamp(options.pitch ?? 60, 0, 85);
|
|
2524
|
+
const zoom = options.zoom ?? 16;
|
|
2525
|
+
const lookAheadM = options.lookAheadM ?? 200;
|
|
2526
|
+
const bearingEase = Math.max(0, options.bearingEase ?? 3);
|
|
2527
|
+
const durationMs = options.speedMps !== void 0 && options.speedMps > 0 ? sampler.totalM / options.speedMps * 1e3 : Math.max(1, options.durationMs ?? 2e4);
|
|
2528
|
+
let t = 0;
|
|
2529
|
+
let rate = 1;
|
|
2530
|
+
let playing = false;
|
|
2531
|
+
let destroyed = false;
|
|
2532
|
+
let bearing;
|
|
2533
|
+
let lastFrameTs;
|
|
2534
|
+
let cancelFrame;
|
|
2535
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2536
|
+
const applyPose = (dtS) => {
|
|
2537
|
+
const pose = sampler.poseAt(t, lookAheadM);
|
|
2538
|
+
bearing = bearing === void 0 ? pose.bearing : bearing + shortestArcDelta(bearing, pose.bearing) * Math.min(1, bearingEase * dtS);
|
|
2539
|
+
m.jumpTo({ center: pose.center, bearing, pitch, zoom });
|
|
2540
|
+
for (const listener of listeners) listener(t);
|
|
2541
|
+
};
|
|
2542
|
+
const frame = (timestampMs) => {
|
|
2543
|
+
if (!playing || destroyed) return;
|
|
2544
|
+
const dtS = lastFrameTs === void 0 ? 0 : Math.max(0, timestampMs - lastFrameTs) / 1e3;
|
|
2545
|
+
lastFrameTs = timestampMs;
|
|
2546
|
+
t = Math.min(1, t + dtS * 1e3 * rate / durationMs);
|
|
2547
|
+
applyPose(dtS || 1 / 60);
|
|
2548
|
+
if (t >= 1) {
|
|
2549
|
+
playing = false;
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2552
|
+
cancelFrame = scheduleFrame(frame);
|
|
2553
|
+
};
|
|
2554
|
+
const pause = () => {
|
|
2555
|
+
playing = false;
|
|
2556
|
+
cancelFrame?.();
|
|
2557
|
+
cancelFrame = void 0;
|
|
2558
|
+
lastFrameTs = void 0;
|
|
2559
|
+
};
|
|
2560
|
+
return {
|
|
2561
|
+
play() {
|
|
2562
|
+
if (destroyed || playing) return;
|
|
2563
|
+
if (t >= 1) t = 0;
|
|
2564
|
+
playing = true;
|
|
2565
|
+
lastFrameTs = void 0;
|
|
2566
|
+
cancelFrame = scheduleFrame(frame);
|
|
2567
|
+
},
|
|
2568
|
+
pause,
|
|
2569
|
+
stop() {
|
|
2570
|
+
if (destroyed) return;
|
|
2571
|
+
pause();
|
|
2572
|
+
t = 0;
|
|
2573
|
+
bearing = void 0;
|
|
2574
|
+
applyPose(1 / 60);
|
|
2575
|
+
},
|
|
2576
|
+
seek(to) {
|
|
2577
|
+
if (destroyed) return;
|
|
2578
|
+
t = Math.min(1, Math.max(0, to));
|
|
2579
|
+
applyPose(1 / 60);
|
|
2580
|
+
},
|
|
2581
|
+
get speed() {
|
|
2582
|
+
return rate;
|
|
2583
|
+
},
|
|
2584
|
+
set speed(value) {
|
|
2585
|
+
if (Number.isFinite(value) && value > 0) rate = value;
|
|
2586
|
+
},
|
|
2587
|
+
get progress() {
|
|
2588
|
+
return t;
|
|
2589
|
+
},
|
|
2590
|
+
get playing() {
|
|
2591
|
+
return playing;
|
|
2592
|
+
},
|
|
2593
|
+
onProgress(listener) {
|
|
2594
|
+
listeners.add(listener);
|
|
2595
|
+
return () => listeners.delete(listener);
|
|
2596
|
+
},
|
|
2597
|
+
destroy() {
|
|
2598
|
+
destroyed = true;
|
|
2599
|
+
pause();
|
|
2600
|
+
listeners.clear();
|
|
2601
|
+
}
|
|
2602
|
+
};
|
|
2603
|
+
}
|
|
2604
|
+
function bindFlythroughToScroll(controller, target = window) {
|
|
2605
|
+
const t = target;
|
|
2606
|
+
const fraction = () => {
|
|
2607
|
+
if (typeof t.scrollTop === "number") {
|
|
2608
|
+
const max2 = (t.scrollHeight ?? 0) - (t.clientHeight ?? 0);
|
|
2609
|
+
return max2 > 0 ? t.scrollTop / max2 : 0;
|
|
2610
|
+
}
|
|
2611
|
+
const doc = t.document?.documentElement;
|
|
2612
|
+
const max = (doc?.scrollHeight ?? 0) - (t.innerHeight ?? 0);
|
|
2613
|
+
return max > 0 ? (t.scrollY ?? 0) / max : 0;
|
|
2614
|
+
};
|
|
2615
|
+
const onScroll = () => {
|
|
2616
|
+
controller.pause();
|
|
2617
|
+
controller.seek(Math.min(1, Math.max(0, fraction())));
|
|
2618
|
+
};
|
|
2619
|
+
t.addEventListener("scroll", onScroll, { passive: true });
|
|
2620
|
+
onScroll();
|
|
2621
|
+
return () => t.removeEventListener("scroll", onScroll);
|
|
2622
|
+
}
|
|
1612
2623
|
function usesGlobe(projection) {
|
|
1613
2624
|
if (projection === null || projection === void 0) return false;
|
|
1614
2625
|
if (typeof projection === "string") {
|
|
@@ -1621,6 +2632,206 @@ function usesGlobe(projection) {
|
|
|
1621
2632
|
return false;
|
|
1622
2633
|
}
|
|
1623
2634
|
|
|
2635
|
+
// src/isochrone.ts
|
|
2636
|
+
var MODE_TO_COSTING = {
|
|
2637
|
+
walk: "pedestrian",
|
|
2638
|
+
cycle: "bicycle",
|
|
2639
|
+
drive: "auto"
|
|
2640
|
+
};
|
|
2641
|
+
var IsochroneLayer = class {
|
|
2642
|
+
constructor(map, options = {}) {
|
|
2643
|
+
this.lastMinutes = [];
|
|
2644
|
+
this.lastColor = SIGNAL_BLUE;
|
|
2645
|
+
this.handleStyleLoad = () => {
|
|
2646
|
+
if (this.lastData) this.install(this.lastData);
|
|
2647
|
+
};
|
|
2648
|
+
if (map instanceof MapMapMap) {
|
|
2649
|
+
this.map = map.map;
|
|
2650
|
+
this.baseUrl = (options.baseUrl ?? map.baseUrl).replace(/\/+$/, "");
|
|
2651
|
+
this.apiKey = options.apiKey ?? map.apiKey;
|
|
2652
|
+
} else {
|
|
2653
|
+
this.map = map;
|
|
2654
|
+
if (!options.baseUrl) {
|
|
2655
|
+
throw new Error(
|
|
2656
|
+
"IsochroneLayer needs a baseUrl when given a raw MapLibre map"
|
|
2657
|
+
);
|
|
2658
|
+
}
|
|
2659
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
2660
|
+
this.apiKey = options.apiKey;
|
|
2661
|
+
}
|
|
2662
|
+
const id = options.id ?? "mapmap-isochrone";
|
|
2663
|
+
this.sourceId = `${id}-src`;
|
|
2664
|
+
this.fillLayerId = `${id}-fill`;
|
|
2665
|
+
this.lineLayerId = `${id}-line`;
|
|
2666
|
+
this.labelLayerId = `${id}-label`;
|
|
2667
|
+
this.map.on("style.load", this.handleStyleLoad);
|
|
2668
|
+
}
|
|
2669
|
+
/**
|
|
2670
|
+
* Fetch and render reachability rings. Returns the GeoJSON
|
|
2671
|
+
* FeatureCollection the gateway produced (each feature carries a
|
|
2672
|
+
* `contour` property in minutes), for callers who also want the raw
|
|
2673
|
+
* shapes. Replaces any rings already shown by this layer.
|
|
2674
|
+
*/
|
|
2675
|
+
async showReachability(options) {
|
|
2676
|
+
if (options.minutes.length === 0) {
|
|
2677
|
+
throw new Error(
|
|
2678
|
+
"showReachability needs at least one contour in minutes, e.g. { minutes: [5, 10, 15] }"
|
|
2679
|
+
);
|
|
2680
|
+
}
|
|
2681
|
+
const [lon, lat] = toLngLat(options.origin);
|
|
2682
|
+
const mode = options.mode ?? "walk";
|
|
2683
|
+
const body = {
|
|
2684
|
+
locations: [{ lat, lon }],
|
|
2685
|
+
costing: MODE_TO_COSTING[mode] ?? mode,
|
|
2686
|
+
// Largest first so smaller (nearer) rings paint on top.
|
|
2687
|
+
contours: [...options.minutes].sort((a, b) => b - a).map((time) => ({ time })),
|
|
2688
|
+
polygons: true
|
|
2689
|
+
};
|
|
2690
|
+
if (options.costingOptions) body["costing_options"] = options.costingOptions;
|
|
2691
|
+
const headers = {
|
|
2692
|
+
"Content-Type": "application/json",
|
|
2693
|
+
Accept: "application/json"
|
|
2694
|
+
};
|
|
2695
|
+
if (this.apiKey) headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
2696
|
+
const response = await fetch(`${this.baseUrl}/isochrone`, {
|
|
2697
|
+
method: "POST",
|
|
2698
|
+
headers,
|
|
2699
|
+
body: JSON.stringify(body)
|
|
2700
|
+
});
|
|
2701
|
+
const payload = await response.json().catch(() => null);
|
|
2702
|
+
if (!response.ok) {
|
|
2703
|
+
const problem = typeof payload === "object" && payload !== null ? payload : void 0;
|
|
2704
|
+
const context = [problem?.["title"], problem?.["detail"]].filter((v) => typeof v === "string" && v.length > 0).join(" - ");
|
|
2705
|
+
throw new Error(
|
|
2706
|
+
`isochrone request failed: HTTP ${response.status}${context ? ` (${context})` : ""}`
|
|
2707
|
+
);
|
|
2708
|
+
}
|
|
2709
|
+
if (typeof payload !== "object" || payload === null || payload["type"] !== "FeatureCollection") {
|
|
2710
|
+
throw new Error("isochrone returned no FeatureCollection");
|
|
2711
|
+
}
|
|
2712
|
+
const collection = payload;
|
|
2713
|
+
this.lastMinutes = [...options.minutes].sort((a, b) => a - b);
|
|
2714
|
+
this.lastColor = options.color ?? SIGNAL_BLUE;
|
|
2715
|
+
this.draw(collection);
|
|
2716
|
+
return collection;
|
|
2717
|
+
}
|
|
2718
|
+
/** The most recently drawn rings, if any. */
|
|
2719
|
+
get current() {
|
|
2720
|
+
return this.lastData;
|
|
2721
|
+
}
|
|
2722
|
+
/** Draw (or update) a ring collection. Safe before the style has loaded. */
|
|
2723
|
+
draw(collection) {
|
|
2724
|
+
this.lastData = collection;
|
|
2725
|
+
if (!this.map.isStyleLoaded()) return;
|
|
2726
|
+
this.install(collection);
|
|
2727
|
+
}
|
|
2728
|
+
/** Add-or-update the source and ring layers on the current style. */
|
|
2729
|
+
install(collection) {
|
|
2730
|
+
const existing = this.map.getSource(this.sourceId);
|
|
2731
|
+
if (existing) {
|
|
2732
|
+
existing.setData(collection);
|
|
2733
|
+
} else {
|
|
2734
|
+
this.map.addSource(this.sourceId, {
|
|
2735
|
+
type: "geojson",
|
|
2736
|
+
data: collection
|
|
2737
|
+
});
|
|
2738
|
+
}
|
|
2739
|
+
if (this.map.getLayer(this.fillLayerId)) {
|
|
2740
|
+
this.map.setPaintProperty(
|
|
2741
|
+
this.fillLayerId,
|
|
2742
|
+
"fill-color",
|
|
2743
|
+
this.lastColor
|
|
2744
|
+
);
|
|
2745
|
+
this.map.setPaintProperty(
|
|
2746
|
+
this.fillLayerId,
|
|
2747
|
+
"fill-opacity",
|
|
2748
|
+
this.fillOpacity()
|
|
2749
|
+
);
|
|
2750
|
+
this.map.setPaintProperty(this.lineLayerId, "line-color", this.lastColor);
|
|
2751
|
+
return;
|
|
2752
|
+
}
|
|
2753
|
+
const fill2 = {
|
|
2754
|
+
id: this.fillLayerId,
|
|
2755
|
+
type: "fill",
|
|
2756
|
+
source: this.sourceId,
|
|
2757
|
+
paint: {
|
|
2758
|
+
"fill-color": this.lastColor,
|
|
2759
|
+
"fill-opacity": this.fillOpacity()
|
|
2760
|
+
}
|
|
2761
|
+
};
|
|
2762
|
+
const line = {
|
|
2763
|
+
id: this.lineLayerId,
|
|
2764
|
+
type: "line",
|
|
2765
|
+
source: this.sourceId,
|
|
2766
|
+
paint: {
|
|
2767
|
+
"line-color": this.lastColor,
|
|
2768
|
+
"line-width": 1.5,
|
|
2769
|
+
"line-opacity": 0.8
|
|
2770
|
+
}
|
|
2771
|
+
};
|
|
2772
|
+
const label = {
|
|
2773
|
+
id: this.labelLayerId,
|
|
2774
|
+
type: "symbol",
|
|
2775
|
+
source: this.sourceId,
|
|
2776
|
+
layout: {
|
|
2777
|
+
"symbol-placement": "line",
|
|
2778
|
+
"text-field": [
|
|
2779
|
+
"concat",
|
|
2780
|
+
["to-string", ["get", "contour"]],
|
|
2781
|
+
" min"
|
|
2782
|
+
],
|
|
2783
|
+
// Ships with the MapMap default styles (see places.ts).
|
|
2784
|
+
"text-font": ["Noto Sans Regular"],
|
|
2785
|
+
"text-size": 11
|
|
2786
|
+
},
|
|
2787
|
+
paint: {
|
|
2788
|
+
"text-color": this.lastColor,
|
|
2789
|
+
"text-halo-color": "#ffffff",
|
|
2790
|
+
"text-halo-width": 1.2
|
|
2791
|
+
}
|
|
2792
|
+
};
|
|
2793
|
+
this.map.addLayer(fill2);
|
|
2794
|
+
this.map.addLayer(line);
|
|
2795
|
+
this.map.addLayer(label);
|
|
2796
|
+
}
|
|
2797
|
+
/**
|
|
2798
|
+
* Graduated fill opacity: the nearest contour (fewest minutes) is the
|
|
2799
|
+
* most opaque, the farthest the faintest, interpolated on each
|
|
2800
|
+
* feature's `contour` property. With one contour it is a constant.
|
|
2801
|
+
*/
|
|
2802
|
+
fillOpacity() {
|
|
2803
|
+
const min = this.lastMinutes[0] ?? 0;
|
|
2804
|
+
const max = this.lastMinutes[this.lastMinutes.length - 1] ?? 0;
|
|
2805
|
+
if (this.lastMinutes.length < 2 || min === max) return 0.18;
|
|
2806
|
+
return [
|
|
2807
|
+
"interpolate",
|
|
2808
|
+
["linear"],
|
|
2809
|
+
["get", "contour"],
|
|
2810
|
+
min,
|
|
2811
|
+
0.28,
|
|
2812
|
+
max,
|
|
2813
|
+
0.08
|
|
2814
|
+
];
|
|
2815
|
+
}
|
|
2816
|
+
/** Remove the rings' layers and source from the map. */
|
|
2817
|
+
clear() {
|
|
2818
|
+
for (const layerId of [
|
|
2819
|
+
this.labelLayerId,
|
|
2820
|
+
this.lineLayerId,
|
|
2821
|
+
this.fillLayerId
|
|
2822
|
+
]) {
|
|
2823
|
+
if (this.map.getLayer(layerId)) this.map.removeLayer(layerId);
|
|
2824
|
+
}
|
|
2825
|
+
if (this.map.getSource(this.sourceId)) this.map.removeSource(this.sourceId);
|
|
2826
|
+
this.lastData = void 0;
|
|
2827
|
+
}
|
|
2828
|
+
/** Remove the rings and detach the layer's `style.load` listener. */
|
|
2829
|
+
destroy() {
|
|
2830
|
+
this.map.off("style.load", this.handleStyleLoad);
|
|
2831
|
+
this.clear();
|
|
2832
|
+
}
|
|
2833
|
+
};
|
|
2834
|
+
|
|
1624
2835
|
// src/adr.ts
|
|
1625
2836
|
function buildAdrCheckBody(request) {
|
|
1626
2837
|
const dims = request.dimensions ?? {};
|
|
@@ -1763,14 +2974,14 @@ function directionArrow(direction) {
|
|
|
1763
2974
|
}
|
|
1764
2975
|
}
|
|
1765
2976
|
function speak(instruction, options = {}) {
|
|
1766
|
-
const
|
|
2977
|
+
const synth2 = globalThis.speechSynthesis;
|
|
1767
2978
|
const Utterance = globalThis.SpeechSynthesisUtterance;
|
|
1768
|
-
if (!
|
|
2979
|
+
if (!synth2 || !Utterance) return false;
|
|
1769
2980
|
const text = instruction.ssmlAnnouncement ? ssmlToText(instruction.ssmlAnnouncement) : instruction.announcement;
|
|
1770
2981
|
const utterance = new Utterance(text);
|
|
1771
2982
|
if (options.lang) utterance.lang = options.lang;
|
|
1772
2983
|
if (options.rate) utterance.rate = options.rate;
|
|
1773
|
-
|
|
2984
|
+
synth2.speak(utterance);
|
|
1774
2985
|
return true;
|
|
1775
2986
|
}
|
|
1776
2987
|
var GuidanceBanner = class {
|
|
@@ -1822,6 +3033,111 @@ var GuidanceBanner = class {
|
|
|
1822
3033
|
}
|
|
1823
3034
|
};
|
|
1824
3035
|
|
|
1825
|
-
|
|
3036
|
+
// src/voice.ts
|
|
3037
|
+
function severityProsody(severity, baseRate = 1) {
|
|
3038
|
+
switch (severity) {
|
|
3039
|
+
case "critical":
|
|
3040
|
+
return { rate: baseRate * 1.1, pitch: 1.2, interrupt: true };
|
|
3041
|
+
case "warning":
|
|
3042
|
+
return { rate: baseRate, pitch: 1.1, interrupt: false };
|
|
3043
|
+
default:
|
|
3044
|
+
return { rate: baseRate, pitch: 1, interrupt: false };
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
var EARCON_SEVERITIES = ["warning", "critical"];
|
|
3048
|
+
var VoiceGuidance = class {
|
|
3049
|
+
constructor(options = {}) {
|
|
3050
|
+
this.offRouteAnnounced = false;
|
|
3051
|
+
this.options = options;
|
|
3052
|
+
this.volume = clampVolume(options.volume ?? 1);
|
|
3053
|
+
this.mutedState = options.muted ?? false;
|
|
3054
|
+
}
|
|
3055
|
+
/** Whether this environment can speak at all. */
|
|
3056
|
+
get available() {
|
|
3057
|
+
return synth() !== void 0 && utteranceCtor() !== void 0;
|
|
3058
|
+
}
|
|
3059
|
+
/** Whether announcements are currently muted. */
|
|
3060
|
+
get muted() {
|
|
3061
|
+
return this.mutedState;
|
|
3062
|
+
}
|
|
3063
|
+
/** Mute announcements (also cancels anything mid-utterance). */
|
|
3064
|
+
mute() {
|
|
3065
|
+
this.mutedState = true;
|
|
3066
|
+
synth()?.cancel();
|
|
3067
|
+
}
|
|
3068
|
+
/** Unmute announcements. Prompts seen while muted are not replayed. */
|
|
3069
|
+
unmute() {
|
|
3070
|
+
this.mutedState = false;
|
|
3071
|
+
}
|
|
3072
|
+
/** Set the volume for speech and earcons (clamped to 0–1). */
|
|
3073
|
+
setVolume(volume) {
|
|
3074
|
+
this.volume = clampVolume(volume);
|
|
3075
|
+
}
|
|
3076
|
+
/**
|
|
3077
|
+
* Consume one guidance update. Speaks the update's prompt the first
|
|
3078
|
+
* time its `utteranceId` appears; plays the warning earcon once per
|
|
3079
|
+
* off-route episode. Safe to call in any environment.
|
|
3080
|
+
*/
|
|
3081
|
+
update(update) {
|
|
3082
|
+
if (update.state === "offRoute") {
|
|
3083
|
+
if (!this.offRouteAnnounced) {
|
|
3084
|
+
this.offRouteAnnounced = true;
|
|
3085
|
+
this.playEarcon("warning");
|
|
3086
|
+
}
|
|
3087
|
+
return;
|
|
3088
|
+
}
|
|
3089
|
+
this.offRouteAnnounced = false;
|
|
3090
|
+
const spoken = update.spoken;
|
|
3091
|
+
if (!spoken || spoken.utteranceId === this.lastUtteranceId) return;
|
|
3092
|
+
this.lastUtteranceId = spoken.utteranceId;
|
|
3093
|
+
this.speak(spoken, update.severity ?? "info", update.voiceLocale);
|
|
3094
|
+
}
|
|
3095
|
+
/** Cancel any speech in progress and forget the de-duplication state. */
|
|
3096
|
+
dispose() {
|
|
3097
|
+
synth()?.cancel();
|
|
3098
|
+
this.lastUtteranceId = void 0;
|
|
3099
|
+
this.offRouteAnnounced = false;
|
|
3100
|
+
}
|
|
3101
|
+
speak(prompt, severity, voiceLocale) {
|
|
3102
|
+
if (this.mutedState) return;
|
|
3103
|
+
const synthesis = synth();
|
|
3104
|
+
const Utterance = utteranceCtor();
|
|
3105
|
+
if (!synthesis || !Utterance) return;
|
|
3106
|
+
const prosody = severityProsody(severity, this.options.rate ?? 1);
|
|
3107
|
+
if (prosody.interrupt) synthesis.cancel();
|
|
3108
|
+
if (EARCON_SEVERITIES.includes(severity)) this.playEarcon(severity);
|
|
3109
|
+
const text = prompt.ssml ? ssmlToText(prompt.ssml) : prompt.text;
|
|
3110
|
+
const utterance = new Utterance(text);
|
|
3111
|
+
const lang = voiceLocale ?? this.options.lang;
|
|
3112
|
+
if (lang) utterance.lang = lang;
|
|
3113
|
+
utterance.rate = prosody.rate;
|
|
3114
|
+
utterance.pitch = prosody.pitch;
|
|
3115
|
+
utterance.volume = this.volume;
|
|
3116
|
+
synthesis.speak(utterance);
|
|
3117
|
+
}
|
|
3118
|
+
playEarcon(severity) {
|
|
3119
|
+
if (this.mutedState) return;
|
|
3120
|
+
const url = this.options.earcons?.[severity];
|
|
3121
|
+
if (!url) return;
|
|
3122
|
+
const AudioCtor = globalThis.Audio;
|
|
3123
|
+
if (!AudioCtor) return;
|
|
3124
|
+
const audio = new AudioCtor(url);
|
|
3125
|
+
audio.volume = this.volume;
|
|
3126
|
+
void audio.play()?.catch?.(() => {
|
|
3127
|
+
});
|
|
3128
|
+
}
|
|
3129
|
+
};
|
|
3130
|
+
function synth() {
|
|
3131
|
+
return globalThis.speechSynthesis;
|
|
3132
|
+
}
|
|
3133
|
+
function utteranceCtor() {
|
|
3134
|
+
return globalThis.SpeechSynthesisUtterance;
|
|
3135
|
+
}
|
|
3136
|
+
function clampVolume(volume) {
|
|
3137
|
+
if (!Number.isFinite(volume)) return 1;
|
|
3138
|
+
return Math.min(1, Math.max(0, volume));
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
export { AdrCheck, DEFAULT_GLYPHS_URL, DEFAULT_TERRITORY_TILES_URL, EFFECTS_METADATA_KEY, FLOW_DEFAULTS, FULL_ATTRIBUTION, FlowRouteEffectLayer, GuidanceBanner, IsochroneLayer, LOGO_SVG, LogoControl, MAX_PUCK_IMAGE_BYTES, MapMapMap, NAV_CAMERA_DEFAULTS, NavigationCamera, OPENMAPTILES_ATTRIBUTION, OSM_ATTRIBUTION, PALETTE_SLOTS, POI_CATEGORY_COLORS, POI_CATEGORY_IDS, POI_CLASS_CATEGORIES, PlacesLayer, PositionPuck, RIBBON_FLOATS_PER_VERTEX, ROUTE_EFFECTS, RouteLayer, SIGNAL_BLUE, SOURCE_LAYERS, VoiceGuidance, applyPoiDesign, bannerLanes, bearingBetween, bindFlythroughToScroll, buildAdrCheckBody, buildRouteQuery, buildRouteUrl, buildStyle, builtInPoiColor, createMap, createRouteEffect, defaultNavDesign, defaultPoiDesign, directionArrow, effectsFromStyleMetadata, extractGuidance, flythrough, flythroughPose, formatCoord, formatCoords, haversineDistanceM, lngLatToMercator, navDesignFromTheme, navDesignFromThemeUrl, parseCssColour, parseNavDesign, parseOsrmRoute, parsePoiDesign, placesFromGeoJSON, poiDesignFromTheme, poiDesignFromThemeUrl, poiDesignIsDefault, poiTextColorExpression, prefersReducedMotion, registerPmtilesProtocol, resetDiagnostics, runMapDiagnostics, severityProsody, shortestArcDelta, speak, ssmlToText, tessellateRouteRibbon, toLngLat, toPmtilesUrl };
|
|
1826
3142
|
//# sourceMappingURL=index.js.map
|
|
1827
3143
|
//# sourceMappingURL=index.js.map
|