@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/dist/index.js
CHANGED
|
@@ -3,6 +3,456 @@ 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 || /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/coords.ts
|
|
100
|
+
function toLngLat(point) {
|
|
101
|
+
if (Array.isArray(point)) {
|
|
102
|
+
const [lng2, lat2] = point;
|
|
103
|
+
assertFinite(lng2, lat2);
|
|
104
|
+
return [lng2, lat2];
|
|
105
|
+
}
|
|
106
|
+
const lng = "lng" in point ? point.lng : point.lon;
|
|
107
|
+
const lat = point.lat;
|
|
108
|
+
assertFinite(lng, lat);
|
|
109
|
+
return [lng, lat];
|
|
110
|
+
}
|
|
111
|
+
function formatCoord(point) {
|
|
112
|
+
const [lng, lat] = toLngLat(point);
|
|
113
|
+
return `${lng},${lat}`;
|
|
114
|
+
}
|
|
115
|
+
function formatCoords(points) {
|
|
116
|
+
if (points.length < 2) {
|
|
117
|
+
throw new Error("at least two coordinates are required for a route");
|
|
118
|
+
}
|
|
119
|
+
return points.map(formatCoord).join(";");
|
|
120
|
+
}
|
|
121
|
+
function unwrapLngs(coordinates) {
|
|
122
|
+
const out = [];
|
|
123
|
+
for (const [lng, lat] of coordinates) {
|
|
124
|
+
const prev = out[out.length - 1];
|
|
125
|
+
let unwrapped = lng;
|
|
126
|
+
if (prev) {
|
|
127
|
+
while (unwrapped - prev[0] > 180) unwrapped -= 360;
|
|
128
|
+
while (unwrapped - prev[0] < -180) unwrapped += 360;
|
|
129
|
+
}
|
|
130
|
+
out.push([unwrapped, lat]);
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
function assertFinite(lng, lat) {
|
|
135
|
+
if (!Number.isFinite(lng) || !Number.isFinite(lat)) {
|
|
136
|
+
throw new Error(`invalid coordinate: lng=${lng}, lat=${lat}`);
|
|
137
|
+
}
|
|
138
|
+
if (lng < -180 || lng > 180 || lat < -90 || lat > 90) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`coordinate out of range: lng=${lng} (\xB1180), lat=${lat} (\xB190) - check lng/lat order`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/effects.ts
|
|
146
|
+
var FLOW_COLOUR = "#3a86ff";
|
|
147
|
+
var EARTH_RADIUS_M = 63710088e-1;
|
|
148
|
+
function haversineM(a, b) {
|
|
149
|
+
const rad = (deg) => deg * Math.PI / 180;
|
|
150
|
+
const dLat = rad(b[1] - a[1]);
|
|
151
|
+
const dLon = rad(b[0] - a[0]);
|
|
152
|
+
const h = Math.sin(dLat / 2) ** 2 + Math.cos(rad(a[1])) * Math.cos(rad(b[1])) * Math.sin(dLon / 2) ** 2;
|
|
153
|
+
return 2 * EARTH_RADIUS_M * Math.asin(Math.sqrt(h));
|
|
154
|
+
}
|
|
155
|
+
var RIBBON_FLOATS_PER_VERTEX = 5;
|
|
156
|
+
function lngLatToMercator(lngLat) {
|
|
157
|
+
const [lng, lat] = lngLat;
|
|
158
|
+
const x = (lng + 180) / 360;
|
|
159
|
+
const y = (1 - Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360)) / Math.PI) / 2;
|
|
160
|
+
return [x, y];
|
|
161
|
+
}
|
|
162
|
+
function tessellateRouteRibbon(coordinates) {
|
|
163
|
+
const points = [];
|
|
164
|
+
for (const lngLat of unwrapLngs(coordinates)) {
|
|
165
|
+
const p = lngLatToMercator(lngLat);
|
|
166
|
+
const last = points[points.length - 1];
|
|
167
|
+
if (!last || last[0] !== p[0] || last[1] !== p[1]) points.push(p);
|
|
168
|
+
}
|
|
169
|
+
if (points.length < 2) {
|
|
170
|
+
return { vertices: new Float32Array(0), vertexCount: 0 };
|
|
171
|
+
}
|
|
172
|
+
const cumulative = [0];
|
|
173
|
+
for (let i = 1; i < points.length; i++) {
|
|
174
|
+
const dx = points[i][0] - points[i - 1][0];
|
|
175
|
+
const dy = points[i][1] - points[i - 1][1];
|
|
176
|
+
cumulative.push(cumulative[i - 1] + Math.hypot(dx, dy));
|
|
177
|
+
}
|
|
178
|
+
const total = cumulative[cumulative.length - 1];
|
|
179
|
+
const vertices = new Float32Array(
|
|
180
|
+
points.length * 2 * RIBBON_FLOATS_PER_VERTEX
|
|
181
|
+
);
|
|
182
|
+
let out = 0;
|
|
183
|
+
for (let i = 0; i < points.length; i++) {
|
|
184
|
+
const prev = points[i - 1];
|
|
185
|
+
const here = points[i];
|
|
186
|
+
const next = points[i + 1];
|
|
187
|
+
let dirX = 0;
|
|
188
|
+
let dirY = 0;
|
|
189
|
+
if (prev) {
|
|
190
|
+
const len = Math.hypot(here[0] - prev[0], here[1] - prev[1]);
|
|
191
|
+
dirX += (here[0] - prev[0]) / len;
|
|
192
|
+
dirY += (here[1] - prev[1]) / len;
|
|
193
|
+
}
|
|
194
|
+
if (next) {
|
|
195
|
+
const len = Math.hypot(next[0] - here[0], next[1] - here[1]);
|
|
196
|
+
dirX += (next[0] - here[0]) / len;
|
|
197
|
+
dirY += (next[1] - here[1]) / len;
|
|
198
|
+
}
|
|
199
|
+
const dirLen = Math.hypot(dirX, dirY);
|
|
200
|
+
if (dirLen < 1e-12 && prev) {
|
|
201
|
+
const len = Math.hypot(here[0] - prev[0], here[1] - prev[1]);
|
|
202
|
+
dirX = (here[0] - prev[0]) / len;
|
|
203
|
+
dirY = (here[1] - prev[1]) / len;
|
|
204
|
+
} else {
|
|
205
|
+
dirX /= dirLen;
|
|
206
|
+
dirY /= dirLen;
|
|
207
|
+
}
|
|
208
|
+
const nx = -dirY;
|
|
209
|
+
const ny = dirX;
|
|
210
|
+
const progress = total > 0 ? cumulative[i] / total : 0;
|
|
211
|
+
for (const side of [1, -1]) {
|
|
212
|
+
vertices[out++] = here[0];
|
|
213
|
+
vertices[out++] = here[1];
|
|
214
|
+
vertices[out++] = nx * side;
|
|
215
|
+
vertices[out++] = ny * side;
|
|
216
|
+
vertices[out++] = progress;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return { vertices, vertexCount: points.length * 2 };
|
|
220
|
+
}
|
|
221
|
+
function parseCssColour(colour2) {
|
|
222
|
+
const v = colour2.trim();
|
|
223
|
+
if (v.startsWith("#")) {
|
|
224
|
+
const hex = v.slice(1);
|
|
225
|
+
if (!/^[0-9a-fA-F]+$/.test(hex)) return void 0;
|
|
226
|
+
if (hex.length === 3 || hex.length === 4) {
|
|
227
|
+
const parts = hex.split("").map((c) => parseInt(c + c, 16) / 255);
|
|
228
|
+
const [r, g, b, a] = parts;
|
|
229
|
+
if (r === void 0 || g === void 0 || b === void 0) return void 0;
|
|
230
|
+
return [r, g, b, a ?? 1];
|
|
231
|
+
}
|
|
232
|
+
if (hex.length === 6 || hex.length === 8) {
|
|
233
|
+
const chan = (i) => parseInt(hex.slice(i, i + 2), 16) / 255;
|
|
234
|
+
return [chan(0), chan(2), chan(4), hex.length === 8 ? chan(6) : 1];
|
|
235
|
+
}
|
|
236
|
+
return void 0;
|
|
237
|
+
}
|
|
238
|
+
const fn = /^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*(?:,\s*([\d.]+)\s*)?\)$/.exec(v);
|
|
239
|
+
if (fn) {
|
|
240
|
+
const r = Number(fn[1]) / 255;
|
|
241
|
+
const g = Number(fn[2]) / 255;
|
|
242
|
+
const b = Number(fn[3]) / 255;
|
|
243
|
+
const a = fn[4] !== void 0 ? Number(fn[4]) : 1;
|
|
244
|
+
if ([r, g, b, a].some((c) => !Number.isFinite(c) || c < 0 || c > 1)) {
|
|
245
|
+
return void 0;
|
|
246
|
+
}
|
|
247
|
+
return [r, g, b, a];
|
|
248
|
+
}
|
|
249
|
+
return void 0;
|
|
250
|
+
}
|
|
251
|
+
function prefersReducedMotion() {
|
|
252
|
+
if (typeof matchMedia !== "function") return false;
|
|
253
|
+
try {
|
|
254
|
+
return matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
255
|
+
} catch {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
var VERTEX_SHADER = `
|
|
260
|
+
uniform mat4 u_matrix;
|
|
261
|
+
uniform float u_half_width;
|
|
262
|
+
attribute vec2 a_pos;
|
|
263
|
+
attribute vec2 a_normal;
|
|
264
|
+
attribute float a_progress;
|
|
265
|
+
varying float v_progress;
|
|
266
|
+
void main() {
|
|
267
|
+
v_progress = a_progress;
|
|
268
|
+
gl_Position = u_matrix * vec4(a_pos + a_normal * u_half_width, 0.0, 1.0);
|
|
269
|
+
}
|
|
270
|
+
`;
|
|
271
|
+
var FRAGMENT_SHADER = `
|
|
272
|
+
precision mediump float;
|
|
273
|
+
uniform vec4 u_color;
|
|
274
|
+
uniform float u_phase;
|
|
275
|
+
uniform float u_repeats;
|
|
276
|
+
varying float v_progress;
|
|
277
|
+
void main() {
|
|
278
|
+
float t = fract(v_progress * u_repeats - u_phase);
|
|
279
|
+
// A soft comet: rises quickly, decays slowly, never fully dark so the
|
|
280
|
+
// ribbon always traces the route.
|
|
281
|
+
float pulse = smoothstep(0.0, 0.25, t) * (1.0 - smoothstep(0.35, 1.0, t));
|
|
282
|
+
float energy = 0.25 + 0.75 * pulse;
|
|
283
|
+
float alpha = u_color.a * energy;
|
|
284
|
+
// Premultiplied alpha, matching MapLibre's framebuffer convention.
|
|
285
|
+
gl_FragColor = vec4(u_color.rgb * alpha, alpha);
|
|
286
|
+
}
|
|
287
|
+
`;
|
|
288
|
+
var warnedWebglFailure = false;
|
|
289
|
+
var FLOW_DEFAULTS = {
|
|
290
|
+
color: FLOW_COLOUR,
|
|
291
|
+
width: 10,
|
|
292
|
+
speed: 0.6
|
|
293
|
+
};
|
|
294
|
+
function mercatorUnitsPerPixel(zoom) {
|
|
295
|
+
return 1 / (512 * 2 ** zoom);
|
|
296
|
+
}
|
|
297
|
+
function routeLengthM(coordinates) {
|
|
298
|
+
let length = 0;
|
|
299
|
+
for (let i = 1; i < coordinates.length; i++) {
|
|
300
|
+
length += haversineM(coordinates[i - 1], coordinates[i]);
|
|
301
|
+
}
|
|
302
|
+
return length;
|
|
303
|
+
}
|
|
304
|
+
var FlowRouteEffectLayer = class {
|
|
305
|
+
constructor(geometry, options = {}) {
|
|
306
|
+
this.type = "custom";
|
|
307
|
+
this.renderingMode = "2d";
|
|
308
|
+
this.program = null;
|
|
309
|
+
this.buffer = null;
|
|
310
|
+
this.vertexCount = 0;
|
|
311
|
+
this.repeats = 8;
|
|
312
|
+
this.startedAt = 0;
|
|
313
|
+
this.failed = false;
|
|
314
|
+
this.aPos = 0;
|
|
315
|
+
this.aNormal = 0;
|
|
316
|
+
this.aProgress = 0;
|
|
317
|
+
this.uMatrix = null;
|
|
318
|
+
this.uHalfWidth = null;
|
|
319
|
+
this.uColor = null;
|
|
320
|
+
this.uPhase = null;
|
|
321
|
+
this.uRepeats = null;
|
|
322
|
+
this.id = options.id ?? "mapmap-route-effect";
|
|
323
|
+
this.geometry = geometry;
|
|
324
|
+
this.colour = (options.color !== void 0 ? parseCssColour(options.color) : void 0) ?? parseCssColour(FLOW_DEFAULTS.color);
|
|
325
|
+
this.widthPx = options.width ?? FLOW_DEFAULTS.width;
|
|
326
|
+
this.speed = options.speed ?? FLOW_DEFAULTS.speed;
|
|
327
|
+
this.animate = !prefersReducedMotion();
|
|
328
|
+
}
|
|
329
|
+
/** Swap the ribbon onto a new route line (e.g. after a reroute). */
|
|
330
|
+
setGeometry(geometry) {
|
|
331
|
+
this.geometry = geometry;
|
|
332
|
+
if (this.glRef && !this.failed) this.upload(this.glRef);
|
|
333
|
+
this.mapRef?.triggerRepaint();
|
|
334
|
+
}
|
|
335
|
+
onAdd(map, gl) {
|
|
336
|
+
this.mapRef = map;
|
|
337
|
+
this.glRef = gl;
|
|
338
|
+
this.startedAt = now();
|
|
339
|
+
try {
|
|
340
|
+
const program = gl.createProgram();
|
|
341
|
+
if (!program) throw new Error("createProgram returned null");
|
|
342
|
+
for (const [kind, source] of [
|
|
343
|
+
[gl.VERTEX_SHADER, VERTEX_SHADER],
|
|
344
|
+
[gl.FRAGMENT_SHADER, FRAGMENT_SHADER]
|
|
345
|
+
]) {
|
|
346
|
+
const shader = gl.createShader(kind);
|
|
347
|
+
if (!shader) throw new Error("createShader returned null");
|
|
348
|
+
gl.shaderSource(shader, source);
|
|
349
|
+
gl.compileShader(shader);
|
|
350
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
351
|
+
throw new Error(gl.getShaderInfoLog(shader) ?? "shader compile failed");
|
|
352
|
+
}
|
|
353
|
+
gl.attachShader(program, shader);
|
|
354
|
+
}
|
|
355
|
+
gl.linkProgram(program);
|
|
356
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
357
|
+
throw new Error(gl.getProgramInfoLog(program) ?? "program link failed");
|
|
358
|
+
}
|
|
359
|
+
this.program = program;
|
|
360
|
+
this.aPos = gl.getAttribLocation(program, "a_pos");
|
|
361
|
+
this.aNormal = gl.getAttribLocation(program, "a_normal");
|
|
362
|
+
this.aProgress = gl.getAttribLocation(program, "a_progress");
|
|
363
|
+
this.uMatrix = gl.getUniformLocation(program, "u_matrix");
|
|
364
|
+
this.uHalfWidth = gl.getUniformLocation(program, "u_half_width");
|
|
365
|
+
this.uColor = gl.getUniformLocation(program, "u_color");
|
|
366
|
+
this.uPhase = gl.getUniformLocation(program, "u_phase");
|
|
367
|
+
this.uRepeats = gl.getUniformLocation(program, "u_repeats");
|
|
368
|
+
this.upload(gl);
|
|
369
|
+
} catch (error) {
|
|
370
|
+
this.failed = true;
|
|
371
|
+
if (!warnedWebglFailure) {
|
|
372
|
+
warnedWebglFailure = true;
|
|
373
|
+
console.warn(
|
|
374
|
+
`MapMap: route effect "flow" disabled (WebGL setup failed: ${error instanceof Error ? error.message : String(error)}). Falling back to the plain route line.`
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
onRemove(_map, gl) {
|
|
380
|
+
if (this.buffer) gl.deleteBuffer(this.buffer);
|
|
381
|
+
if (this.program) gl.deleteProgram(this.program);
|
|
382
|
+
this.buffer = null;
|
|
383
|
+
this.program = null;
|
|
384
|
+
this.mapRef = void 0;
|
|
385
|
+
this.glRef = void 0;
|
|
386
|
+
}
|
|
387
|
+
render(gl, args) {
|
|
388
|
+
if (this.failed || !this.program || !this.buffer || this.vertexCount < 3) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const matrix = projectionMatrixOf(args);
|
|
392
|
+
if (!matrix || !this.mapRef) return;
|
|
393
|
+
gl.useProgram(this.program);
|
|
394
|
+
gl.uniformMatrix4fv(this.uMatrix, false, matrix);
|
|
395
|
+
const halfWidth = this.widthPx / 2 * mercatorUnitsPerPixel(this.mapRef.getZoom());
|
|
396
|
+
gl.uniform1f(this.uHalfWidth, halfWidth);
|
|
397
|
+
gl.uniform4f(this.uColor, ...this.colour);
|
|
398
|
+
const phase = this.animate ? (now() - this.startedAt) / 1e3 * this.speed % 1 : 0;
|
|
399
|
+
gl.uniform1f(this.uPhase, phase);
|
|
400
|
+
gl.uniform1f(this.uRepeats, this.repeats);
|
|
401
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
402
|
+
const stride = RIBBON_FLOATS_PER_VERTEX * 4;
|
|
403
|
+
gl.enableVertexAttribArray(this.aPos);
|
|
404
|
+
gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, stride, 0);
|
|
405
|
+
gl.enableVertexAttribArray(this.aNormal);
|
|
406
|
+
gl.vertexAttribPointer(this.aNormal, 2, gl.FLOAT, false, stride, 8);
|
|
407
|
+
gl.enableVertexAttribArray(this.aProgress);
|
|
408
|
+
gl.vertexAttribPointer(this.aProgress, 1, gl.FLOAT, false, stride, 16);
|
|
409
|
+
gl.enable(gl.BLEND);
|
|
410
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
411
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, this.vertexCount);
|
|
412
|
+
if (this.animate) this.mapRef.triggerRepaint();
|
|
413
|
+
}
|
|
414
|
+
upload(gl) {
|
|
415
|
+
const mesh = tessellateRouteRibbon(this.geometry.coordinates);
|
|
416
|
+
this.vertexCount = mesh.vertexCount;
|
|
417
|
+
this.repeats = Math.min(
|
|
418
|
+
80,
|
|
419
|
+
Math.max(2, Math.round(routeLengthM(this.geometry.coordinates) / 500))
|
|
420
|
+
);
|
|
421
|
+
if (!this.buffer) this.buffer = gl.createBuffer();
|
|
422
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
423
|
+
gl.bufferData(gl.ARRAY_BUFFER, mesh.vertices, gl.STATIC_DRAW);
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
function now() {
|
|
427
|
+
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
428
|
+
}
|
|
429
|
+
function projectionMatrixOf(args) {
|
|
430
|
+
if (Array.isArray(args)) return args;
|
|
431
|
+
if (args instanceof Float32Array || args instanceof Float64Array) {
|
|
432
|
+
return args instanceof Float64Array ? new Float32Array(args) : args;
|
|
433
|
+
}
|
|
434
|
+
if (typeof args === "object" && args !== null) {
|
|
435
|
+
const data = args.defaultProjectionData;
|
|
436
|
+
const main = data?.mainMatrix;
|
|
437
|
+
if (Array.isArray(main)) return main;
|
|
438
|
+
if (main instanceof Float32Array) return main;
|
|
439
|
+
if (main instanceof Float64Array) return new Float32Array(main);
|
|
440
|
+
}
|
|
441
|
+
return void 0;
|
|
442
|
+
}
|
|
443
|
+
var ROUTE_EFFECTS = {
|
|
444
|
+
flow: (geometry, options) => new FlowRouteEffectLayer(geometry, options)
|
|
445
|
+
};
|
|
446
|
+
function createRouteEffect(name, geometry, options) {
|
|
447
|
+
const factory = ROUTE_EFFECTS[name];
|
|
448
|
+
if (!factory) {
|
|
449
|
+
throw new Error(
|
|
450
|
+
`unknown route effect ${JSON.stringify(name)}; accepted effects: ` + Object.keys(ROUTE_EFFECTS).join(", ")
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
return factory(geometry, options);
|
|
454
|
+
}
|
|
455
|
+
|
|
6
456
|
// src/logo.ts
|
|
7
457
|
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
458
|
var DEFAULT_HREF = "https://mapmap.ai";
|
|
@@ -132,6 +582,172 @@ async function navDesignFromThemeUrl(url, fetchImpl) {
|
|
|
132
582
|
return navDesignFromTheme(theme);
|
|
133
583
|
}
|
|
134
584
|
|
|
585
|
+
// src/poi-design.ts
|
|
586
|
+
var POI_CATEGORY_COLORS = [
|
|
587
|
+
["food_drink", "#e8734a"],
|
|
588
|
+
["shopping", "#4a90d9"],
|
|
589
|
+
["transport", "#3a86ff"],
|
|
590
|
+
["lodging", "#9b6dd6"],
|
|
591
|
+
["health", "#e05c6c"],
|
|
592
|
+
["culture_leisure", "#3aa675"],
|
|
593
|
+
["education", "#d9a13a"],
|
|
594
|
+
["services", "#7a8699"]
|
|
595
|
+
];
|
|
596
|
+
var POI_CATEGORY_IDS = POI_CATEGORY_COLORS.map(([id]) => id);
|
|
597
|
+
var POI_CLASS_CATEGORIES = {
|
|
598
|
+
food_drink: [
|
|
599
|
+
"restaurant",
|
|
600
|
+
"fast_food",
|
|
601
|
+
"cafe",
|
|
602
|
+
"bar",
|
|
603
|
+
"pub",
|
|
604
|
+
"biergarten",
|
|
605
|
+
"food_court",
|
|
606
|
+
"alcohol_shop"
|
|
607
|
+
],
|
|
608
|
+
shopping: [
|
|
609
|
+
"shop",
|
|
610
|
+
"grocery",
|
|
611
|
+
"supermarket",
|
|
612
|
+
"clothing_store",
|
|
613
|
+
"mall",
|
|
614
|
+
"department_store",
|
|
615
|
+
"convenience",
|
|
616
|
+
"bakery",
|
|
617
|
+
"marketplace"
|
|
618
|
+
],
|
|
619
|
+
transport: [
|
|
620
|
+
"railway",
|
|
621
|
+
"railway_station",
|
|
622
|
+
"subway",
|
|
623
|
+
"bus",
|
|
624
|
+
"bus_station",
|
|
625
|
+
"ferry_terminal",
|
|
626
|
+
"aerodrome",
|
|
627
|
+
"airport",
|
|
628
|
+
"airfield"
|
|
629
|
+
],
|
|
630
|
+
lodging: [
|
|
631
|
+
"lodging",
|
|
632
|
+
"hotel",
|
|
633
|
+
"motel",
|
|
634
|
+
"hostel",
|
|
635
|
+
"guest_house",
|
|
636
|
+
"camp_site",
|
|
637
|
+
"caravan_site",
|
|
638
|
+
"alpine_hut"
|
|
639
|
+
],
|
|
640
|
+
health: ["hospital", "pharmacy", "doctors", "dentist", "veterinary", "clinic"],
|
|
641
|
+
culture_leisure: [
|
|
642
|
+
"museum",
|
|
643
|
+
"theatre",
|
|
644
|
+
"cinema",
|
|
645
|
+
"attraction",
|
|
646
|
+
"park",
|
|
647
|
+
"stadium",
|
|
648
|
+
"art_gallery",
|
|
649
|
+
"zoo",
|
|
650
|
+
"swimming_pool",
|
|
651
|
+
"golf",
|
|
652
|
+
"playground",
|
|
653
|
+
"cemetery",
|
|
654
|
+
"garden",
|
|
655
|
+
"picnic_site",
|
|
656
|
+
"viewpoint"
|
|
657
|
+
],
|
|
658
|
+
education: ["school", "college", "university", "library", "kindergarten"],
|
|
659
|
+
services: [
|
|
660
|
+
"bank",
|
|
661
|
+
"post",
|
|
662
|
+
"police",
|
|
663
|
+
"town_hall",
|
|
664
|
+
"place_of_worship",
|
|
665
|
+
"courthouse",
|
|
666
|
+
"embassy",
|
|
667
|
+
"fire_station",
|
|
668
|
+
"community_centre",
|
|
669
|
+
"toilet",
|
|
670
|
+
"telephone",
|
|
671
|
+
"atm"
|
|
672
|
+
]
|
|
673
|
+
};
|
|
674
|
+
function builtInPoiColor(categoryId) {
|
|
675
|
+
return POI_CATEGORY_COLORS.find(([id]) => id === categoryId)?.[1];
|
|
676
|
+
}
|
|
677
|
+
function defaultPoiDesign() {
|
|
678
|
+
return { version: 1, categories: {} };
|
|
679
|
+
}
|
|
680
|
+
function poiDesignIsDefault(design) {
|
|
681
|
+
return Object.keys(design.categories).length === 0;
|
|
682
|
+
}
|
|
683
|
+
function isRecord2(v) {
|
|
684
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
685
|
+
}
|
|
686
|
+
function colour(v) {
|
|
687
|
+
if (typeof v !== "string") return void 0;
|
|
688
|
+
const t = v.trim();
|
|
689
|
+
if (t.startsWith("#")) {
|
|
690
|
+
const hex = t.slice(1);
|
|
691
|
+
return [3, 4, 6, 8].includes(hex.length) && /^[0-9a-fA-F]+$/.test(hex) ? t : void 0;
|
|
692
|
+
}
|
|
693
|
+
for (const prefix of ["rgb(", "rgba(", "hsl(", "hsla("]) {
|
|
694
|
+
if (t.startsWith(prefix) && t.endsWith(")")) return t;
|
|
695
|
+
}
|
|
696
|
+
return void 0;
|
|
697
|
+
}
|
|
698
|
+
function parsePoiDesign(value) {
|
|
699
|
+
const design = defaultPoiDesign();
|
|
700
|
+
if (!isRecord2(value) || !isRecord2(value.categories)) return design;
|
|
701
|
+
for (const id of POI_CATEGORY_IDS) {
|
|
702
|
+
const raw = value.categories[id];
|
|
703
|
+
if (!isRecord2(raw)) continue;
|
|
704
|
+
const category = {};
|
|
705
|
+
const color = colour(raw.color);
|
|
706
|
+
const textColor = colour(raw.textColor);
|
|
707
|
+
if (color !== void 0) category.color = color;
|
|
708
|
+
if (textColor !== void 0) category.textColor = textColor;
|
|
709
|
+
if (Object.keys(category).length > 0) design.categories[id] = category;
|
|
710
|
+
}
|
|
711
|
+
return design;
|
|
712
|
+
}
|
|
713
|
+
function poiDesignFromTheme(theme) {
|
|
714
|
+
const extra = theme?.extra;
|
|
715
|
+
if (typeof extra !== "object" || extra === null || !("poi" in extra)) {
|
|
716
|
+
return void 0;
|
|
717
|
+
}
|
|
718
|
+
return parsePoiDesign(extra.poi);
|
|
719
|
+
}
|
|
720
|
+
async function poiDesignFromThemeUrl(url, fetchImpl) {
|
|
721
|
+
const doFetch = fetchImpl ?? ((input) => globalThis.fetch(input));
|
|
722
|
+
const response = await doFetch(url);
|
|
723
|
+
if (!response.ok) {
|
|
724
|
+
throw new Error(`fetching theme ${url}: HTTP ${response.status}`);
|
|
725
|
+
}
|
|
726
|
+
const theme = await response.json();
|
|
727
|
+
return poiDesignFromTheme(theme);
|
|
728
|
+
}
|
|
729
|
+
function poiTextColorExpression(design, fallback) {
|
|
730
|
+
if (!POI_CATEGORY_IDS.some((id) => design.categories[id]?.textColor)) return fallback;
|
|
731
|
+
const arms = POI_CATEGORY_IDS.filter((id) => id !== "services").flatMap((id) => [
|
|
732
|
+
POI_CLASS_CATEGORIES[id],
|
|
733
|
+
design.categories[id]?.textColor ?? fallback
|
|
734
|
+
]);
|
|
735
|
+
return [
|
|
736
|
+
"match",
|
|
737
|
+
["get", "class"],
|
|
738
|
+
...arms,
|
|
739
|
+
design.categories.services?.textColor ?? fallback
|
|
740
|
+
];
|
|
741
|
+
}
|
|
742
|
+
function applyPoiDesign(map, design, fallbackTextColor = "#6b6b6b") {
|
|
743
|
+
const value = poiTextColorExpression(design, fallbackTextColor);
|
|
744
|
+
for (const layer of map.getStyle()?.layers ?? []) {
|
|
745
|
+
if (layer.id === "poi-labels" || layer.id.startsWith("poi-labels@")) {
|
|
746
|
+
map.setPaintProperty(layer.id, "text-color", value);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
135
751
|
// src/style.ts
|
|
136
752
|
var OSM_ATTRIBUTION = "\xA9 OpenStreetMap contributors";
|
|
137
753
|
var OPENMAPTILES_ATTRIBUTION = "\xA9 OpenMapTiles";
|
|
@@ -175,6 +791,46 @@ var PALETTE_SLOTS = [
|
|
|
175
791
|
["textSecondary", "#6b6b6b", "#9aa3ad"],
|
|
176
792
|
["textHalo", "#ffffff", "#12161c"]
|
|
177
793
|
];
|
|
794
|
+
var FLOW_PARAM_KEYS = ["color", "width", "speed"];
|
|
795
|
+
function validateEffects(effects) {
|
|
796
|
+
const route = effects.route ?? "none";
|
|
797
|
+
if (route !== "flow" && route !== "none") {
|
|
798
|
+
throw new Error(
|
|
799
|
+
`invalid effects block: unknown route effect ${JSON.stringify(route)}; accepted: "flow", "none"`
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
if (!effects.params) return;
|
|
803
|
+
if (route === "none") {
|
|
804
|
+
throw new Error(
|
|
805
|
+
'invalid effects block: "params" requires "route" to name an effect (e.g. "flow")'
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
for (const [key, value] of Object.entries(effects.params)) {
|
|
809
|
+
if (key === "color") {
|
|
810
|
+
if (typeof value !== "string" || !isCssColour(value)) {
|
|
811
|
+
throw new Error(
|
|
812
|
+
`invalid effects block: params.color has invalid colour ${JSON.stringify(value)}; use #rgb/#rrggbb/#rrggbbaa or rgb()/rgba()/hsl()/hsla()`
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
} else if (key === "width") {
|
|
816
|
+
if (typeof value !== "number" || value < 0.5 || value > 40) {
|
|
817
|
+
throw new Error(
|
|
818
|
+
`invalid effects block: params.width must be a number between 0.5 and 40 (pixels), got ${JSON.stringify(value)}`
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
} else if (key === "speed") {
|
|
822
|
+
if (typeof value !== "number" || value < 0 || value > 10) {
|
|
823
|
+
throw new Error(
|
|
824
|
+
`invalid effects block: params.speed must be a number between 0 and 10 (cycles per second), got ${JSON.stringify(value)}`
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
} else {
|
|
828
|
+
throw new Error(
|
|
829
|
+
`invalid effects block: unknown params key ${JSON.stringify(key)}; accepted keys: ${FLOW_PARAM_KEYS.join(", ")}`
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
178
834
|
function toPmtilesUrl(url) {
|
|
179
835
|
return url.startsWith("pmtiles://") ? url : `pmtiles://${url}`;
|
|
180
836
|
}
|
|
@@ -212,18 +868,18 @@ function resolvePalette(theme) {
|
|
|
212
868
|
}
|
|
213
869
|
return palette;
|
|
214
870
|
}
|
|
215
|
-
function fill(id, sourceLayer,
|
|
871
|
+
function fill(id, sourceLayer, colour2, opacity, minzoom) {
|
|
216
872
|
const layer = {
|
|
217
873
|
id,
|
|
218
874
|
type: "fill",
|
|
219
875
|
source: "territory",
|
|
220
876
|
"source-layer": sourceLayer,
|
|
221
|
-
paint: { "fill-color":
|
|
877
|
+
paint: { "fill-color": colour2, "fill-opacity": opacity }
|
|
222
878
|
};
|
|
223
879
|
if (minzoom !== void 0) layer["minzoom"] = minzoom;
|
|
224
880
|
return layer;
|
|
225
881
|
}
|
|
226
|
-
function symbol(id, sourceLayer, textField, font, textSize,
|
|
882
|
+
function symbol(id, sourceLayer, textField, font, textSize, colour2, halo, minzoom, extraLayout) {
|
|
227
883
|
const layout = {
|
|
228
884
|
"text-field": textField,
|
|
229
885
|
"text-font": font,
|
|
@@ -237,7 +893,7 @@ function symbol(id, sourceLayer, textField, font, textSize, colour, halo, minzoo
|
|
|
237
893
|
"source-layer": sourceLayer,
|
|
238
894
|
layout,
|
|
239
895
|
paint: {
|
|
240
|
-
"text-color":
|
|
896
|
+
"text-color": colour2,
|
|
241
897
|
"text-halo-color": halo,
|
|
242
898
|
"text-halo-width": 1.2
|
|
243
899
|
}
|
|
@@ -295,10 +951,20 @@ var THEME_KEYS = [
|
|
|
295
951
|
"sprite",
|
|
296
952
|
"extra_layers",
|
|
297
953
|
"extra",
|
|
298
|
-
"buildings_3d"
|
|
954
|
+
"buildings_3d",
|
|
955
|
+
"effects"
|
|
299
956
|
];
|
|
957
|
+
var EFFECTS_METADATA_KEY = "mapmap:effects";
|
|
958
|
+
function effectsFromStyleMetadata(metadata) {
|
|
959
|
+
if (typeof metadata !== "object" || metadata === null) return void 0;
|
|
960
|
+
const block = metadata[EFFECTS_METADATA_KEY];
|
|
961
|
+
if (typeof block !== "object" || block === null) return void 0;
|
|
962
|
+
const { route, params } = block;
|
|
963
|
+
if (route !== "flow") return void 0;
|
|
964
|
+
return typeof params === "object" && params !== null ? { route, params } : { route };
|
|
965
|
+
}
|
|
300
966
|
var BUILDINGS_3D_MINZOOM = 15;
|
|
301
|
-
function buildings3dLayer(
|
|
967
|
+
function buildings3dLayer(colour2) {
|
|
302
968
|
return {
|
|
303
969
|
id: "building-3d",
|
|
304
970
|
type: "fill-extrusion",
|
|
@@ -306,7 +972,7 @@ function buildings3dLayer(colour) {
|
|
|
306
972
|
"source-layer": "building",
|
|
307
973
|
minzoom: BUILDINGS_3D_MINZOOM,
|
|
308
974
|
paint: {
|
|
309
|
-
"fill-extrusion-color":
|
|
975
|
+
"fill-extrusion-color": colour2,
|
|
310
976
|
"fill-extrusion-height": [
|
|
311
977
|
"interpolate",
|
|
312
978
|
["linear"],
|
|
@@ -330,6 +996,11 @@ function buildings3dLayer(colour) {
|
|
|
330
996
|
}
|
|
331
997
|
};
|
|
332
998
|
}
|
|
999
|
+
function nameFieldFor(labelLanguage) {
|
|
1000
|
+
if (labelLanguage === "local") return ["get", "name"];
|
|
1001
|
+
if (labelLanguage) return ["coalesce", ["get", `name:${labelLanguage}`], ["get", "name"]];
|
|
1002
|
+
return ["coalesce", ["get", "name:en"], ["get", "name"]];
|
|
1003
|
+
}
|
|
333
1004
|
function buildStyle(options = {}) {
|
|
334
1005
|
const input = options.theme ?? "light";
|
|
335
1006
|
if (typeof input === "object") {
|
|
@@ -345,7 +1016,7 @@ function buildStyle(options = {}) {
|
|
|
345
1016
|
const palette = resolvePalette(theme);
|
|
346
1017
|
const p = (slot) => palette[slot];
|
|
347
1018
|
const font = [theme.fonts?.regular ?? "Noto Sans Regular"];
|
|
348
|
-
const nameField =
|
|
1019
|
+
const nameField = nameFieldFor(options.labelLanguage);
|
|
349
1020
|
const tilesUrl = toPmtilesUrl(
|
|
350
1021
|
options.territoryTilesUrl ?? DEFAULT_TERRITORY_TILES_URL
|
|
351
1022
|
);
|
|
@@ -536,15 +1207,40 @@ function buildStyle(options = {}) {
|
|
|
536
1207
|
p("textHalo"),
|
|
537
1208
|
10
|
|
538
1209
|
),
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
1210
|
+
// Label hierarchy: settlements and regions wait until z4 so world
|
|
1211
|
+
// zooms aren't a flat democracy of states, cities and villages;
|
|
1212
|
+
// continent labels are dropped entirely as clutter.
|
|
1213
|
+
{
|
|
1214
|
+
...symbol(
|
|
1215
|
+
"place-labels",
|
|
1216
|
+
"place",
|
|
1217
|
+
nameField,
|
|
1218
|
+
font,
|
|
1219
|
+
["interpolate", ["linear"], ["zoom"], 4, 10.5, 12, 16],
|
|
1220
|
+
p("textPrimary"),
|
|
1221
|
+
p("textHalo"),
|
|
1222
|
+
4
|
|
1223
|
+
),
|
|
1224
|
+
filter: ["match", ["get", "class"], ["country", "continent"], false, true]
|
|
1225
|
+
},
|
|
1226
|
+
// Countries: the most prominent text on the map at world zooms —
|
|
1227
|
+
// larger, letter-spaced, visible from z1, ceding to the regional
|
|
1228
|
+
// hierarchy from z10. Drawn last = wins label collisions.
|
|
1229
|
+
{
|
|
1230
|
+
...symbol(
|
|
1231
|
+
"country-labels",
|
|
1232
|
+
"place",
|
|
1233
|
+
nameField,
|
|
1234
|
+
font,
|
|
1235
|
+
["interpolate", ["linear"], ["zoom"], 1, 11, 3, 13.5, 6, 18],
|
|
1236
|
+
p("textPrimary"),
|
|
1237
|
+
p("textHalo"),
|
|
1238
|
+
1,
|
|
1239
|
+
{ "text-letter-spacing": 0.08, "text-max-width": 7 }
|
|
1240
|
+
),
|
|
1241
|
+
filter: ["==", ["get", "class"], "country"],
|
|
1242
|
+
maxzoom: 10
|
|
1243
|
+
}
|
|
548
1244
|
);
|
|
549
1245
|
const seenIds = /* @__PURE__ */ new Set();
|
|
550
1246
|
for (const layer of layers) {
|
|
@@ -580,6 +1276,14 @@ function buildStyle(options = {}) {
|
|
|
580
1276
|
layers
|
|
581
1277
|
};
|
|
582
1278
|
if (theme.sprite !== void 0) style["sprite"] = theme.sprite;
|
|
1279
|
+
if (theme.effects) {
|
|
1280
|
+
validateEffects(theme.effects);
|
|
1281
|
+
if ((theme.effects.route ?? "none") !== "none") {
|
|
1282
|
+
const block = { route: theme.effects.route };
|
|
1283
|
+
if (theme.effects.params) block["params"] = theme.effects.params;
|
|
1284
|
+
style["metadata"] = { [EFFECTS_METADATA_KEY]: block };
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
583
1287
|
return style;
|
|
584
1288
|
}
|
|
585
1289
|
|
|
@@ -594,12 +1298,37 @@ function registerPmtilesProtocol(gl = maplibregl) {
|
|
|
594
1298
|
var DEFAULT_BASE_URL = "https://api.mapmap.ai";
|
|
595
1299
|
var DEFAULT_CENTER = [-1.5, 52.6];
|
|
596
1300
|
var DEFAULT_ZOOM = 5;
|
|
1301
|
+
var DEFAULT_EFFECT_LAYER_ID = "mapmap-route-effect";
|
|
1302
|
+
function flowOptionsFromParams(params) {
|
|
1303
|
+
const options = {};
|
|
1304
|
+
if (typeof params["color"] === "string") options.color = params["color"];
|
|
1305
|
+
if (typeof params["width"] === "number") options.width = params["width"];
|
|
1306
|
+
if (typeof params["speed"] === "number") options.speed = params["speed"];
|
|
1307
|
+
return options;
|
|
1308
|
+
}
|
|
597
1309
|
var MapMapMap = class {
|
|
598
1310
|
constructor(options) {
|
|
1311
|
+
// Route-effect state (see setRouteEffect / effects.ts). `explicit`
|
|
1312
|
+
// records an app-level decision, which always wins over a style's
|
|
1313
|
+
// metadata auto-enable.
|
|
1314
|
+
this.effectName = null;
|
|
1315
|
+
this.effectOptions = {};
|
|
1316
|
+
this.effectExplicit = false;
|
|
1317
|
+
this.handleStyleLoadForEffects = () => {
|
|
1318
|
+
if (!this.effectExplicit) {
|
|
1319
|
+
const block = effectsFromStyleMetadata(this.styleMetadata());
|
|
1320
|
+
this.effectName = block?.route ?? null;
|
|
1321
|
+
this.effectOptions = block?.params ? flowOptionsFromParams(block.params) : {};
|
|
1322
|
+
}
|
|
1323
|
+
this.effectLayer = void 0;
|
|
1324
|
+
queueMicrotask(() => this.applyRouteEffect());
|
|
1325
|
+
};
|
|
599
1326
|
registerPmtilesProtocol();
|
|
600
1327
|
this.apiKey = options.apiKey;
|
|
601
1328
|
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
602
|
-
|
|
1329
|
+
const theme = themeOf(options.style);
|
|
1330
|
+
this.navDesign = navDesignFromTheme(theme);
|
|
1331
|
+
this.poiDesign = poiDesignFromTheme(theme);
|
|
603
1332
|
const style = resolveStyle(options.style, options.territoryTilesUrl);
|
|
604
1333
|
this.map = new maplibregl.Map({
|
|
605
1334
|
container: options.container,
|
|
@@ -616,6 +1345,102 @@ var MapMapMap = class {
|
|
|
616
1345
|
logoOptions.position ?? "bottom-right"
|
|
617
1346
|
);
|
|
618
1347
|
}
|
|
1348
|
+
this.map.on("style.load", this.handleStyleLoadForEffects);
|
|
1349
|
+
runMapDiagnostics({
|
|
1350
|
+
container: options.container,
|
|
1351
|
+
map: this.map,
|
|
1352
|
+
maplibre: maplibregl,
|
|
1353
|
+
apiKey: this.apiKey
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
/** The current style's `metadata`, if it can be read yet. */
|
|
1357
|
+
styleMetadata() {
|
|
1358
|
+
try {
|
|
1359
|
+
return this.map.getStyle?.()?.metadata;
|
|
1360
|
+
} catch {
|
|
1361
|
+
return void 0;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Set (or clear) the visual effect on the active route line - the
|
|
1366
|
+
* effects-engine entry point (see `effects.ts`). The first effect is
|
|
1367
|
+
* `"flow"`: an animated energy ribbon flowing along the route, drawn in
|
|
1368
|
+
* a MapLibre custom layer with first-party GLSL.
|
|
1369
|
+
*
|
|
1370
|
+
* ```ts
|
|
1371
|
+
* const route = await routes.route(from, to);
|
|
1372
|
+
* map.setRouteEffect("flow"); // uses the drawn route
|
|
1373
|
+
* map.setRouteEffect("flow", { color: "#ff7a1f" }); // options
|
|
1374
|
+
* map.setRouteEffect(null); // back to the plain line
|
|
1375
|
+
* ```
|
|
1376
|
+
*
|
|
1377
|
+
* Geometry: pass `{ geometry }` explicitly, or omit it and the effect
|
|
1378
|
+
* attaches to whatever route a `RouteLayer` on this map draws (current
|
|
1379
|
+
* and future - RouteLayer reports every drawn line via
|
|
1380
|
+
* {@link setRouteEffectGeometry}).
|
|
1381
|
+
*
|
|
1382
|
+
* Styles whose theme carried an `effects` block auto-enable their effect
|
|
1383
|
+
* from the compiled style's metadata; calling this method (with any
|
|
1384
|
+
* value, including `null`) overrides the style's wish for the lifetime
|
|
1385
|
+
* of this map.
|
|
1386
|
+
*
|
|
1387
|
+
* Accessibility/resilience (see effects.ts): honours
|
|
1388
|
+
* `prefers-reduced-motion` (static gradient, no animation) and falls
|
|
1389
|
+
* back to the plain route line with a single console warning if WebGL
|
|
1390
|
+
* setup fails.
|
|
1391
|
+
*/
|
|
1392
|
+
setRouteEffect(effect, options = {}) {
|
|
1393
|
+
this.effectExplicit = true;
|
|
1394
|
+
const { geometry, ...flowOptions } = options;
|
|
1395
|
+
this.effectName = effect;
|
|
1396
|
+
this.effectOptions = flowOptions;
|
|
1397
|
+
if (geometry) this.effectGeometry = geometry;
|
|
1398
|
+
this.removeEffectLayer();
|
|
1399
|
+
this.applyRouteEffect();
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Attach the active route effect to a route line (or detach it with
|
|
1403
|
+
* `null`). `RouteLayer` calls this on every `draw()`/`clear()`, so apps
|
|
1404
|
+
* normally never do - pass `geometry` to {@link setRouteEffect} for
|
|
1405
|
+
* routes drawn outside a RouteLayer.
|
|
1406
|
+
*/
|
|
1407
|
+
setRouteEffectGeometry(geometry) {
|
|
1408
|
+
this.effectGeometry = geometry ?? void 0;
|
|
1409
|
+
if (geometry && this.effectLayer && this.map.getLayer(this.effectLayerId())) {
|
|
1410
|
+
this.effectLayer.setGeometry(geometry);
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
this.applyRouteEffect();
|
|
1414
|
+
}
|
|
1415
|
+
effectLayerId() {
|
|
1416
|
+
return this.effectOptions.id ?? DEFAULT_EFFECT_LAYER_ID;
|
|
1417
|
+
}
|
|
1418
|
+
removeEffectLayer() {
|
|
1419
|
+
const id = this.effectLayerId();
|
|
1420
|
+
if (this.map.getLayer(id)) this.map.removeLayer(id);
|
|
1421
|
+
this.effectLayer = void 0;
|
|
1422
|
+
}
|
|
1423
|
+
/** Reconcile the effect state with the map: install, update or remove. */
|
|
1424
|
+
applyRouteEffect() {
|
|
1425
|
+
const id = this.effectLayerId();
|
|
1426
|
+
if (this.effectName === null || this.effectGeometry === void 0) {
|
|
1427
|
+
this.removeEffectLayer();
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
if (!this.map.isStyleLoaded()) {
|
|
1431
|
+
this.map.once("load", () => this.applyRouteEffect());
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
if (this.effectLayer && this.map.getLayer(id)) {
|
|
1435
|
+
this.effectLayer.setGeometry(this.effectGeometry);
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
if (this.map.getLayer(id)) this.map.removeLayer(id);
|
|
1439
|
+
this.effectLayer = createRouteEffect(this.effectName, this.effectGeometry, {
|
|
1440
|
+
...this.effectOptions,
|
|
1441
|
+
id
|
|
1442
|
+
});
|
|
1443
|
+
this.map.addLayer(this.effectLayer);
|
|
619
1444
|
}
|
|
620
1445
|
/**
|
|
621
1446
|
* Toggle 3D building extrusions at runtime — the live equivalent of
|
|
@@ -644,9 +1469,9 @@ var MapMapMap = class {
|
|
|
644
1469
|
}
|
|
645
1470
|
const minzoom = building.minzoom ?? 13;
|
|
646
1471
|
if (enabled) {
|
|
647
|
-
const
|
|
1472
|
+
const colour2 = this.map.getPaintProperty("building", "fill-color") ?? "#e2ddd4";
|
|
648
1473
|
this.map.addLayer(
|
|
649
|
-
buildings3dLayer(
|
|
1474
|
+
buildings3dLayer(colour2),
|
|
650
1475
|
buildings3dBeforeId(this.map)
|
|
651
1476
|
);
|
|
652
1477
|
this.map.setLayerZoomRange("building", minzoom, BUILDINGS_3D_MINZOOM);
|
|
@@ -702,39 +1527,6 @@ function resolveStyle(style, territoryTilesUrl) {
|
|
|
702
1527
|
return style;
|
|
703
1528
|
}
|
|
704
1529
|
|
|
705
|
-
// src/coords.ts
|
|
706
|
-
function toLngLat(point) {
|
|
707
|
-
if (Array.isArray(point)) {
|
|
708
|
-
const [lng2, lat2] = point;
|
|
709
|
-
assertFinite(lng2, lat2);
|
|
710
|
-
return [lng2, lat2];
|
|
711
|
-
}
|
|
712
|
-
const lng = "lng" in point ? point.lng : point.lon;
|
|
713
|
-
const lat = point.lat;
|
|
714
|
-
assertFinite(lng, lat);
|
|
715
|
-
return [lng, lat];
|
|
716
|
-
}
|
|
717
|
-
function formatCoord(point) {
|
|
718
|
-
const [lng, lat] = toLngLat(point);
|
|
719
|
-
return `${lng},${lat}`;
|
|
720
|
-
}
|
|
721
|
-
function formatCoords(points) {
|
|
722
|
-
if (points.length < 2) {
|
|
723
|
-
throw new Error("at least two coordinates are required for a route");
|
|
724
|
-
}
|
|
725
|
-
return points.map(formatCoord).join(";");
|
|
726
|
-
}
|
|
727
|
-
function assertFinite(lng, lat) {
|
|
728
|
-
if (!Number.isFinite(lng) || !Number.isFinite(lat)) {
|
|
729
|
-
throw new Error(`invalid coordinate: lng=${lng}, lat=${lat}`);
|
|
730
|
-
}
|
|
731
|
-
if (lng < -180 || lng > 180 || lat < -90 || lat > 90) {
|
|
732
|
-
throw new Error(
|
|
733
|
-
`coordinate out of range: lng=${lng} (\xB1180), lat=${lat} (\xB190) - check lng/lat order`
|
|
734
|
-
);
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
1530
|
// src/osrm.ts
|
|
739
1531
|
function buildRouteQuery(truck, guidance) {
|
|
740
1532
|
const params = new URLSearchParams({
|
|
@@ -808,13 +1600,42 @@ function numberOr(value, fallback) {
|
|
|
808
1600
|
// src/route.ts
|
|
809
1601
|
var SIGNAL_BLUE = "#3a86ff";
|
|
810
1602
|
var CASING_COLOR = "#1f438a";
|
|
1603
|
+
var PROGRESS_COLOR = "#b0b0b0";
|
|
1604
|
+
function arrowImage() {
|
|
1605
|
+
const size = 24;
|
|
1606
|
+
const data = new Uint8Array(size * size * 4);
|
|
1607
|
+
const head = 13;
|
|
1608
|
+
const put = (x, y, edge) => {
|
|
1609
|
+
const i = (y * size + x) * 4;
|
|
1610
|
+
const v = edge ? 31 : 255;
|
|
1611
|
+
data[i] = v;
|
|
1612
|
+
data[i + 1] = v;
|
|
1613
|
+
data[i + 2] = edge ? 58 : 255;
|
|
1614
|
+
data[i + 3] = 255;
|
|
1615
|
+
};
|
|
1616
|
+
for (let y = 2; y < head; y++) {
|
|
1617
|
+
const half = Math.round((y - 2) / (head - 3) * 9);
|
|
1618
|
+
for (let x = 11 - half; x <= 12 + half; x++) {
|
|
1619
|
+
put(x, y, x === 11 - half || x === 12 + half || y === 2);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
for (let y = head; y < 22; y++) {
|
|
1623
|
+
for (let x = 9; x <= 14; x++) {
|
|
1624
|
+
put(x, y, x === 9 || x === 14 || y === 21);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
return { width: size, height: size, data };
|
|
1628
|
+
}
|
|
811
1629
|
var RouteLayer = class {
|
|
812
1630
|
constructor(map, options = {}) {
|
|
1631
|
+
this.progress = 0;
|
|
813
1632
|
this.handleStyleLoad = () => {
|
|
814
1633
|
if (this.lastRoute) this.install(this.lastRoute);
|
|
1634
|
+
else this.installManeuver();
|
|
815
1635
|
};
|
|
816
1636
|
if (map instanceof MapMapMap) {
|
|
817
1637
|
this.map = map.map;
|
|
1638
|
+
this.owner = map;
|
|
818
1639
|
this.baseUrl = (options.baseUrl ?? map.baseUrl).replace(/\/+$/, "");
|
|
819
1640
|
this.apiKey = options.apiKey ?? map.apiKey;
|
|
820
1641
|
this.design = options.design ?? map.navDesign?.route;
|
|
@@ -831,6 +1652,10 @@ var RouteLayer = class {
|
|
|
831
1652
|
this.sourceId = `${id}-src`;
|
|
832
1653
|
this.casingLayerId = `${id}-casing`;
|
|
833
1654
|
this.lineLayerId = `${id}-line`;
|
|
1655
|
+
this.maneuverSourceId = `${id}-maneuver-src`;
|
|
1656
|
+
this.maneuverLayerId = `${id}-maneuver`;
|
|
1657
|
+
this.arrowImageId = `${id}-arrow`;
|
|
1658
|
+
this.progressColor = options.progressColor ?? PROGRESS_COLOR;
|
|
834
1659
|
this.map.on("style.load", this.handleStyleLoad);
|
|
835
1660
|
}
|
|
836
1661
|
/**
|
|
@@ -878,8 +1703,8 @@ var RouteLayer = class {
|
|
|
878
1703
|
*/
|
|
879
1704
|
draw(route) {
|
|
880
1705
|
this.lastRoute = route;
|
|
881
|
-
if (
|
|
882
|
-
this.
|
|
1706
|
+
if (this.map.isStyleLoaded()) this.install(route);
|
|
1707
|
+
this.owner?.setRouteEffectGeometry(route.geometry);
|
|
883
1708
|
}
|
|
884
1709
|
/** Add-or-update the source and layers for a route on the current style. */
|
|
885
1710
|
install(route) {
|
|
@@ -892,9 +1717,11 @@ var RouteLayer = class {
|
|
|
892
1717
|
if (existing) {
|
|
893
1718
|
existing.setData(data);
|
|
894
1719
|
} else {
|
|
895
|
-
this.map.addSource(this.sourceId, { type: "geojson", data });
|
|
1720
|
+
this.map.addSource(this.sourceId, { type: "geojson", data, lineMetrics: true });
|
|
896
1721
|
}
|
|
897
1722
|
if (this.map.getLayer(this.casingLayerId) && this.map.getLayer(this.lineLayerId)) {
|
|
1723
|
+
this.applyProgress();
|
|
1724
|
+
this.installManeuver();
|
|
898
1725
|
return;
|
|
899
1726
|
}
|
|
900
1727
|
const casing = {
|
|
@@ -927,14 +1754,86 @@ var RouteLayer = class {
|
|
|
927
1754
|
};
|
|
928
1755
|
if (!this.map.getLayer(this.casingLayerId)) this.map.addLayer(casing);
|
|
929
1756
|
if (!this.map.getLayer(this.lineLayerId)) this.map.addLayer(line);
|
|
1757
|
+
this.applyProgress();
|
|
1758
|
+
this.installManeuver();
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Sets how much of the route has been travelled, as a fraction in `[0, 1]`
|
|
1762
|
+
* of the line's length. The travelled part dims to `progressColor` (the
|
|
1763
|
+
* "vanishing route line"); `0` restores the untinted line. The value is
|
|
1764
|
+
* remembered across {@link draw} calls and style swaps. Pair with the
|
|
1765
|
+
* guidance module's distance-remaining to derive the fraction.
|
|
1766
|
+
*/
|
|
1767
|
+
setProgress(fraction) {
|
|
1768
|
+
this.progress = Math.min(1, Math.max(0, fraction));
|
|
1769
|
+
if (this.map.getLayer(this.lineLayerId)) this.applyProgress();
|
|
1770
|
+
}
|
|
1771
|
+
/** Applies the current progress fraction to the line layer's gradient. */
|
|
1772
|
+
applyProgress() {
|
|
1773
|
+
const routeColor = this.design?.color ?? SIGNAL_BLUE;
|
|
1774
|
+
const gradient = this.progress > 0 ? ["step", ["line-progress"], this.progressColor, this.progress, routeColor] : void 0;
|
|
1775
|
+
this.map.setPaintProperty(this.lineLayerId, "line-gradient", gradient);
|
|
1776
|
+
}
|
|
1777
|
+
/**
|
|
1778
|
+
* Shows (or moves) the upcoming-manoeuvre arrow: a small map-aligned
|
|
1779
|
+
* arrow at `lngLat` rotated to `bearingDeg` (clockwise from north).
|
|
1780
|
+
* Survives style swaps until {@link clearManeuver}.
|
|
1781
|
+
*/
|
|
1782
|
+
setManeuver(lngLat, bearingDeg) {
|
|
1783
|
+
this.maneuver = { lngLat, bearingDeg };
|
|
1784
|
+
if (this.map.isStyleLoaded()) this.installManeuver();
|
|
1785
|
+
}
|
|
1786
|
+
/** Hides the manoeuvre arrow. */
|
|
1787
|
+
clearManeuver() {
|
|
1788
|
+
this.maneuver = void 0;
|
|
1789
|
+
if (this.map.getLayer(this.maneuverLayerId)) this.map.removeLayer(this.maneuverLayerId);
|
|
1790
|
+
if (this.map.getSource(this.maneuverSourceId)) this.map.removeSource(this.maneuverSourceId);
|
|
1791
|
+
}
|
|
1792
|
+
/** Add-or-update the manoeuvre arrow source/layer for the current style. */
|
|
1793
|
+
installManeuver() {
|
|
1794
|
+
if (!this.maneuver) return;
|
|
1795
|
+
const data = {
|
|
1796
|
+
type: "Feature",
|
|
1797
|
+
properties: { bearing: this.maneuver.bearingDeg },
|
|
1798
|
+
geometry: { type: "Point", coordinates: this.maneuver.lngLat }
|
|
1799
|
+
};
|
|
1800
|
+
const source = this.map.getSource(this.maneuverSourceId);
|
|
1801
|
+
if (source) {
|
|
1802
|
+
source.setData(data);
|
|
1803
|
+
} else {
|
|
1804
|
+
this.map.addSource(this.maneuverSourceId, { type: "geojson", data });
|
|
1805
|
+
}
|
|
1806
|
+
if (!this.map.hasImage(this.arrowImageId)) {
|
|
1807
|
+
this.map.addImage(this.arrowImageId, arrowImage());
|
|
1808
|
+
}
|
|
1809
|
+
if (!this.map.getLayer(this.maneuverLayerId)) {
|
|
1810
|
+
this.map.addLayer({
|
|
1811
|
+
id: this.maneuverLayerId,
|
|
1812
|
+
type: "symbol",
|
|
1813
|
+
source: this.maneuverSourceId,
|
|
1814
|
+
layout: {
|
|
1815
|
+
"icon-image": this.arrowImageId,
|
|
1816
|
+
"icon-rotate": ["get", "bearing"],
|
|
1817
|
+
"icon-rotation-alignment": "map",
|
|
1818
|
+
"icon-allow-overlap": true,
|
|
1819
|
+
"icon-ignore-placement": true,
|
|
1820
|
+
"icon-size": ["interpolate", ["linear"], ["zoom"], 12, 0.7, 18, 1.4]
|
|
1821
|
+
}
|
|
1822
|
+
});
|
|
1823
|
+
} else {
|
|
1824
|
+
this.map.setLayoutProperty(this.maneuverLayerId, "icon-rotate", ["get", "bearing"]);
|
|
1825
|
+
}
|
|
930
1826
|
}
|
|
931
1827
|
/** Remove the route's layers and source from the map. */
|
|
932
1828
|
clear() {
|
|
1829
|
+
this.clearManeuver();
|
|
933
1830
|
for (const layerId of [this.lineLayerId, this.casingLayerId]) {
|
|
934
1831
|
if (this.map.getLayer(layerId)) this.map.removeLayer(layerId);
|
|
935
1832
|
}
|
|
936
1833
|
if (this.map.getSource(this.sourceId)) this.map.removeSource(this.sourceId);
|
|
937
1834
|
this.lastRoute = void 0;
|
|
1835
|
+
this.owner?.setRouteEffectGeometry(null);
|
|
1836
|
+
this.progress = 0;
|
|
938
1837
|
}
|
|
939
1838
|
/**
|
|
940
1839
|
* Remove the route and detach the layer's `style.load` listener. Call
|
|
@@ -960,6 +1859,12 @@ function effectiveImageUrl(design) {
|
|
|
960
1859
|
}
|
|
961
1860
|
return url;
|
|
962
1861
|
}
|
|
1862
|
+
function shortestArcDeg(fromDeg, toDeg) {
|
|
1863
|
+
const raw = ((toDeg - fromDeg) % 360 + 360) % 360;
|
|
1864
|
+
return raw > 180 ? raw - 360 : raw;
|
|
1865
|
+
}
|
|
1866
|
+
var MAX_TWEEN_MS = 900;
|
|
1867
|
+
var MIN_TWEEN_MS = 100;
|
|
963
1868
|
var PositionPuck = class {
|
|
964
1869
|
/**
|
|
965
1870
|
* Creates the puck (not yet on the map - it appears on the first
|
|
@@ -967,12 +1872,17 @@ var PositionPuck = class {
|
|
|
967
1872
|
* `navDesign.puck` when given a `MapMapMap` whose theme carried an
|
|
968
1873
|
* `extra.nav` block, then to the built-in blue puck.
|
|
969
1874
|
*/
|
|
970
|
-
constructor(map, design) {
|
|
1875
|
+
constructor(map, design, options) {
|
|
971
1876
|
this.added = false;
|
|
972
1877
|
this.map = map instanceof MapMapMap ? map.map : map;
|
|
973
1878
|
this.design = design ?? (map instanceof MapMapMap ? map.navDesign?.puck : void 0) ?? defaultNavDesign().puck;
|
|
974
1879
|
this.element = createPuckElement();
|
|
975
1880
|
stylePuckElement(this.element, this.design);
|
|
1881
|
+
this.interpolate = options?.interpolate ?? true;
|
|
1882
|
+
this.now = options?.now ?? Date.now;
|
|
1883
|
+
const g = globalThis;
|
|
1884
|
+
this.requestFrame = options?.requestFrame ?? (g.requestAnimationFrame ? (cb) => g.requestAnimationFrame(() => cb()) : void 0);
|
|
1885
|
+
this.cancelFrame = options?.cancelFrame ?? (g.cancelAnimationFrame ? (h) => g.cancelAnimationFrame(h) : void 0);
|
|
976
1886
|
this.marker = new maplibregl.Marker({
|
|
977
1887
|
element: this.element,
|
|
978
1888
|
rotationAlignment: "map",
|
|
@@ -983,20 +1893,72 @@ var PositionPuck = class {
|
|
|
983
1893
|
* Moves the puck (adding it to the map on the first call). `headingDeg`
|
|
984
1894
|
* rotates the whole element - arrow or custom image - clockwise from
|
|
985
1895
|
* north; omit it to keep the previous heading.
|
|
1896
|
+
*
|
|
1897
|
+
* With interpolation on (the default) every call after the first glides
|
|
1898
|
+
* from the currently rendered position - a fix arriving mid-tween
|
|
1899
|
+
* retargets smoothly rather than jumping.
|
|
986
1900
|
*/
|
|
987
1901
|
setLocation(location, headingDeg) {
|
|
988
|
-
this.
|
|
989
|
-
|
|
990
|
-
|
|
1902
|
+
const first = !this.added;
|
|
1903
|
+
const nowMs = this.now();
|
|
1904
|
+
const interval = this.lastFixAt === void 0 ? MAX_TWEEN_MS : nowMs - this.lastFixAt;
|
|
1905
|
+
this.lastFixAt = nowMs;
|
|
1906
|
+
this.cancelTween();
|
|
1907
|
+
const from = this.rendered;
|
|
1908
|
+
const target = {
|
|
1909
|
+
lat: location.lat,
|
|
1910
|
+
lon: location.lon,
|
|
1911
|
+
heading: headingDeg ?? from?.heading ?? 0
|
|
1912
|
+
};
|
|
1913
|
+
const duration = Math.min(MAX_TWEEN_MS, interval);
|
|
1914
|
+
if (first || !this.interpolate || !this.requestFrame || !from || duration < MIN_TWEEN_MS) {
|
|
1915
|
+
this.render(target);
|
|
1916
|
+
} else {
|
|
1917
|
+
this.tween(from, target, nowMs, duration);
|
|
1918
|
+
}
|
|
1919
|
+
if (first) {
|
|
991
1920
|
this.marker.addTo(this.map);
|
|
992
1921
|
this.added = true;
|
|
993
1922
|
}
|
|
994
1923
|
}
|
|
995
|
-
/** Removes the puck from the map. `setLocation` re-adds it. */
|
|
1924
|
+
/** Removes the puck from the map (cancelling any tween). `setLocation` re-adds it. */
|
|
996
1925
|
remove() {
|
|
1926
|
+
this.cancelTween();
|
|
997
1927
|
this.marker.remove();
|
|
998
1928
|
this.added = false;
|
|
999
1929
|
}
|
|
1930
|
+
/** Applies a position/heading to the marker immediately. */
|
|
1931
|
+
render(state) {
|
|
1932
|
+
this.marker.setLngLat([state.lon, state.lat]);
|
|
1933
|
+
this.marker.setRotation(state.heading);
|
|
1934
|
+
this.rendered = state;
|
|
1935
|
+
}
|
|
1936
|
+
/** Runs a linear position lerp + shortest-arc heading tween via rAF. */
|
|
1937
|
+
tween(from, to, startedAt, duration) {
|
|
1938
|
+
const headingDelta = shortestArcDeg(from.heading, to.heading);
|
|
1939
|
+
const step = () => {
|
|
1940
|
+
const t = Math.min(1, (this.now() - startedAt) / duration);
|
|
1941
|
+
this.render({
|
|
1942
|
+
lat: from.lat + (to.lat - from.lat) * t,
|
|
1943
|
+
lon: from.lon + (to.lon - from.lon) * t,
|
|
1944
|
+
heading: from.heading + headingDelta * t
|
|
1945
|
+
});
|
|
1946
|
+
if (t < 1) {
|
|
1947
|
+
this.frameHandle = this.requestFrame(step);
|
|
1948
|
+
} else {
|
|
1949
|
+
this.frameHandle = void 0;
|
|
1950
|
+
this.render(to);
|
|
1951
|
+
}
|
|
1952
|
+
};
|
|
1953
|
+
this.frameHandle = this.requestFrame(step);
|
|
1954
|
+
}
|
|
1955
|
+
/** Cancels an in-flight tween, leaving the marker where it rendered last. */
|
|
1956
|
+
cancelTween() {
|
|
1957
|
+
if (this.frameHandle !== void 0) {
|
|
1958
|
+
this.cancelFrame?.(this.frameHandle);
|
|
1959
|
+
this.frameHandle = void 0;
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1000
1962
|
};
|
|
1001
1963
|
function createPuckElement() {
|
|
1002
1964
|
const doc = globalThis.document;
|
|
@@ -1045,7 +2007,187 @@ function stylePuckElement(el, puck) {
|
|
|
1045
2007
|
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
2008
|
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
2009
|
}
|
|
1048
|
-
|
|
2010
|
+
|
|
2011
|
+
// src/daynight.ts
|
|
2012
|
+
var DEG = Math.PI / 180;
|
|
2013
|
+
var DAY_MS = 864e5;
|
|
2014
|
+
var JULIAN_EPOCH = 24405875e-1;
|
|
2015
|
+
var J2000 = 2451545;
|
|
2016
|
+
function fromJulian(julian) {
|
|
2017
|
+
return new Date((julian - JULIAN_EPOCH) * DAY_MS);
|
|
2018
|
+
}
|
|
2019
|
+
function sunTimes(date, lat, lng) {
|
|
2020
|
+
const julianDay = Math.ceil(date.getTime() / DAY_MS + JULIAN_EPOCH - J2000 - 9e-4 + lng / 360);
|
|
2021
|
+
const meanSolarTime = julianDay + 9e-4 - lng / 360;
|
|
2022
|
+
const meanAnomalyDeg = (357.5291 + 0.98560028 * meanSolarTime) % 360;
|
|
2023
|
+
const m = meanAnomalyDeg * DEG;
|
|
2024
|
+
const centreDeg = 1.9148 * Math.sin(m) + 0.02 * Math.sin(2 * m) + 3e-4 * Math.sin(3 * m);
|
|
2025
|
+
const eclipticLngDeg = (meanAnomalyDeg + centreDeg + 180 + 102.9372) % 360;
|
|
2026
|
+
const l = eclipticLngDeg * DEG;
|
|
2027
|
+
const transit = J2000 + meanSolarTime + 53e-4 * Math.sin(m) - 69e-4 * Math.sin(2 * l);
|
|
2028
|
+
const sinDeclination = Math.sin(l) * Math.sin(23.4397 * DEG);
|
|
2029
|
+
const cosDeclination = Math.cos(Math.asin(sinDeclination));
|
|
2030
|
+
const cosHourAngle = (Math.sin(-0.833 * DEG) - Math.sin(lat * DEG) * sinDeclination) / (Math.cos(lat * DEG) * cosDeclination);
|
|
2031
|
+
if (cosHourAngle < -1) return "polarDay";
|
|
2032
|
+
if (cosHourAngle > 1) return "polarNight";
|
|
2033
|
+
const hourAngleDeg = Math.acos(cosHourAngle) / DEG;
|
|
2034
|
+
return {
|
|
2035
|
+
sunrise: fromJulian(transit - hourAngleDeg / 360),
|
|
2036
|
+
sunset: fromJulian(transit + hourAngleDeg / 360)
|
|
2037
|
+
};
|
|
2038
|
+
}
|
|
2039
|
+
function resolveTheme(date, lat, lng) {
|
|
2040
|
+
const times = sunTimes(date, lat, lng);
|
|
2041
|
+
if (times === "polarDay") return "light";
|
|
2042
|
+
if (times === "polarNight") return "dark";
|
|
2043
|
+
return date >= times.sunrise && date < times.sunset ? "light" : "dark";
|
|
2044
|
+
}
|
|
2045
|
+
var POLAR_RECHECK_MS = 6 * 36e5;
|
|
2046
|
+
var BOUNDARY_MARGIN_MS = 1e3;
|
|
2047
|
+
var ThemeScheduler = class {
|
|
2048
|
+
constructor(options) {
|
|
2049
|
+
this.disposed = false;
|
|
2050
|
+
this.lat = options.lat;
|
|
2051
|
+
this.lng = options.lng;
|
|
2052
|
+
this.onLight = options.onLight;
|
|
2053
|
+
this.onDark = options.onDark;
|
|
2054
|
+
this.now = options.now ?? Date.now;
|
|
2055
|
+
this.setTimeoutFn = options.setTimeoutFn ?? ((cb, ms) => setTimeout(cb, ms));
|
|
2056
|
+
this.clearTimeoutFn = options.clearTimeoutFn ?? ((h) => clearTimeout(h));
|
|
2057
|
+
this.evaluate();
|
|
2058
|
+
}
|
|
2059
|
+
/** The theme most recently applied, if any. */
|
|
2060
|
+
get current() {
|
|
2061
|
+
return this.applied;
|
|
2062
|
+
}
|
|
2063
|
+
/** Moves the observer (e.g. a new GPS fix region) and re-evaluates. */
|
|
2064
|
+
setPosition(lat, lng) {
|
|
2065
|
+
this.lat = lat;
|
|
2066
|
+
this.lng = lng;
|
|
2067
|
+
this.evaluate();
|
|
2068
|
+
}
|
|
2069
|
+
/** Stops all future flips. */
|
|
2070
|
+
dispose() {
|
|
2071
|
+
this.disposed = true;
|
|
2072
|
+
if (this.handle !== void 0) this.clearTimeoutFn(this.handle);
|
|
2073
|
+
this.handle = void 0;
|
|
2074
|
+
}
|
|
2075
|
+
/** Applies the theme for now and arms the timer for the next boundary. */
|
|
2076
|
+
evaluate() {
|
|
2077
|
+
if (this.disposed) return;
|
|
2078
|
+
if (this.handle !== void 0) {
|
|
2079
|
+
this.clearTimeoutFn(this.handle);
|
|
2080
|
+
this.handle = void 0;
|
|
2081
|
+
}
|
|
2082
|
+
const nowDate = new Date(this.now());
|
|
2083
|
+
const theme = resolveTheme(nowDate, this.lat, this.lng);
|
|
2084
|
+
if (theme !== this.applied) {
|
|
2085
|
+
this.applied = theme;
|
|
2086
|
+
(theme === "light" ? this.onLight : this.onDark)();
|
|
2087
|
+
}
|
|
2088
|
+
const delay = this.nextBoundaryDelay(nowDate);
|
|
2089
|
+
this.handle = this.setTimeoutFn(() => this.evaluate(), delay);
|
|
2090
|
+
}
|
|
2091
|
+
/** Milliseconds until the next sunrise/sunset (or the polar re-check). */
|
|
2092
|
+
nextBoundaryDelay(nowDate) {
|
|
2093
|
+
const nowMs = nowDate.getTime();
|
|
2094
|
+
for (const dayOffset of [0, 1]) {
|
|
2095
|
+
const times = sunTimes(new Date(nowMs + dayOffset * DAY_MS), this.lat, this.lng);
|
|
2096
|
+
if (times === "polarDay" || times === "polarNight") continue;
|
|
2097
|
+
for (const event of [times.sunrise, times.sunset]) {
|
|
2098
|
+
const delta = event.getTime() - nowMs;
|
|
2099
|
+
if (delta > 0) return delta + BOUNDARY_MARGIN_MS;
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
return POLAR_RECHECK_MS;
|
|
2103
|
+
}
|
|
2104
|
+
};
|
|
2105
|
+
|
|
2106
|
+
// src/language.ts
|
|
2107
|
+
var LANGUAGE_TAG = /^[a-z]{2,3}(-[A-Za-z0-9]{2,4})?$/;
|
|
2108
|
+
function languageTextField(language) {
|
|
2109
|
+
if (language === null) {
|
|
2110
|
+
return ["coalesce", ["get", "name:en"], ["get", "name"]];
|
|
2111
|
+
}
|
|
2112
|
+
return [
|
|
2113
|
+
"coalesce",
|
|
2114
|
+
["get", `name:${language}`],
|
|
2115
|
+
["get", "name:latin"],
|
|
2116
|
+
["get", "name"]
|
|
2117
|
+
];
|
|
2118
|
+
}
|
|
2119
|
+
function isNameTextField(textField) {
|
|
2120
|
+
if (typeof textField === "string") {
|
|
2121
|
+
return /\{name(?::[A-Za-z0-9-]+)?\}/.test(textField);
|
|
2122
|
+
}
|
|
2123
|
+
if (Array.isArray(textField)) {
|
|
2124
|
+
if (textField.length === 2 && textField[0] === "get" && typeof textField[1] === "string") {
|
|
2125
|
+
return textField[1] === "name" || textField[1].startsWith("name:");
|
|
2126
|
+
}
|
|
2127
|
+
return textField.some((part) => isNameTextField(part));
|
|
2128
|
+
}
|
|
2129
|
+
return false;
|
|
2130
|
+
}
|
|
2131
|
+
function setMapLanguage(map, language) {
|
|
2132
|
+
if (language !== null && !LANGUAGE_TAG.test(language)) {
|
|
2133
|
+
throw new Error(
|
|
2134
|
+
`invalid language tag ${JSON.stringify(language)}; use e.g. "de", "pt-BR", "zh-Hans"`
|
|
2135
|
+
);
|
|
2136
|
+
}
|
|
2137
|
+
const ml = map instanceof MapMapMap ? map.map : map;
|
|
2138
|
+
const layers = ml.getStyle()?.layers ?? [];
|
|
2139
|
+
const changed = [];
|
|
2140
|
+
for (const layer of layers) {
|
|
2141
|
+
if (layer.type !== "symbol") continue;
|
|
2142
|
+
const textField = layer.layout?.["text-field"];
|
|
2143
|
+
if (textField === void 0 || !isNameTextField(textField)) continue;
|
|
2144
|
+
ml.setLayoutProperty(layer.id, "text-field", languageTextField(language));
|
|
2145
|
+
changed.push(layer.id);
|
|
2146
|
+
}
|
|
2147
|
+
return changed;
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
// src/probe.ts
|
|
2151
|
+
function buildProbeUrl(baseUrl) {
|
|
2152
|
+
return `${baseUrl.replace(/\/+$/, "")}/v1/probe`;
|
|
2153
|
+
}
|
|
2154
|
+
async function uploadProbeBatch(baseUrl, apiKey, body, options = {}) {
|
|
2155
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
2156
|
+
const maxAttempts = options.maxAttempts ?? 4;
|
|
2157
|
+
const backoffMs = options.backoffMs ?? ((attempt) => attempt * 1e3);
|
|
2158
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
2159
|
+
const url = buildProbeUrl(baseUrl);
|
|
2160
|
+
for (let attempt = 1; ; attempt++) {
|
|
2161
|
+
let outcome;
|
|
2162
|
+
try {
|
|
2163
|
+
const response = await doFetch(url, {
|
|
2164
|
+
method: "POST",
|
|
2165
|
+
headers: {
|
|
2166
|
+
Authorization: `Bearer ${apiKey}`,
|
|
2167
|
+
"Content-Type": "application/json"
|
|
2168
|
+
},
|
|
2169
|
+
body,
|
|
2170
|
+
signal: options.signal
|
|
2171
|
+
});
|
|
2172
|
+
outcome = classifyStatus(response.status);
|
|
2173
|
+
} catch {
|
|
2174
|
+
outcome = "gaveUp";
|
|
2175
|
+
}
|
|
2176
|
+
if (outcome === "gaveUp" && attempt < maxAttempts) {
|
|
2177
|
+
await sleep(backoffMs(attempt));
|
|
2178
|
+
continue;
|
|
2179
|
+
}
|
|
2180
|
+
return outcome;
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
function classifyStatus(status) {
|
|
2184
|
+
if (status === 202) return "accepted";
|
|
2185
|
+
if (status === 403) return "refused";
|
|
2186
|
+
if (status === 501) return "notEnabled";
|
|
2187
|
+
if (status >= 500 && status <= 599) return "gaveUp";
|
|
2188
|
+
return "rejected";
|
|
2189
|
+
}
|
|
2190
|
+
var EARTH_RADIUS_M2 = 63710088e-1;
|
|
1049
2191
|
var CLUSTER_TEXT_FONT = "Noto Sans Regular";
|
|
1050
2192
|
function placesFromGeoJSON(collection) {
|
|
1051
2193
|
const places = [];
|
|
@@ -1073,7 +2215,7 @@ function haversineDistanceM(a, b) {
|
|
|
1073
2215
|
const dLat = rad(b.lat - a.lat);
|
|
1074
2216
|
const dLon = rad(b.lon - a.lon);
|
|
1075
2217
|
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 *
|
|
2218
|
+
return 2 * EARTH_RADIUS_M2 * Math.asin(Math.sqrt(h));
|
|
1077
2219
|
}
|
|
1078
2220
|
var PlacesLayer = class {
|
|
1079
2221
|
constructor(map, options = {}) {
|
|
@@ -1134,6 +2276,7 @@ var PlacesLayer = class {
|
|
|
1134
2276
|
this.clusterRadius = options.clusterRadius;
|
|
1135
2277
|
this.clusterMaxZoom = options.clusterMaxZoom;
|
|
1136
2278
|
this.color = options.color ?? SIGNAL_BLUE;
|
|
2279
|
+
this.clusterColor = options.clusterColor ?? (typeof this.color === "string" ? this.color : SIGNAL_BLUE);
|
|
1137
2280
|
this.icon = options.icon;
|
|
1138
2281
|
this.wantFitBounds = options.fitBounds ?? false;
|
|
1139
2282
|
this.onPlaceClick = options.onPlaceClick;
|
|
@@ -1151,9 +2294,12 @@ var PlacesLayer = class {
|
|
|
1151
2294
|
}
|
|
1152
2295
|
/**
|
|
1153
2296
|
* Replace the layer's places - a `Place[]` or a GeoJSON FeatureCollection
|
|
1154
|
-
* of Points.
|
|
1155
|
-
*
|
|
1156
|
-
* `
|
|
2297
|
+
* of Points. Never silently drops an update: once the source exists the
|
|
2298
|
+
* data is applied immediately, even while `isStyleLoaded()` is transiently
|
|
2299
|
+
* `false` mid-render (search-as-you-type just works); calls made before
|
|
2300
|
+
* the source has first been installed are stashed - the latest one wins -
|
|
2301
|
+
* and installed on the next `style.load`. With `fitBounds: true` the
|
|
2302
|
+
* first non-empty set also fits the map view.
|
|
1157
2303
|
*/
|
|
1158
2304
|
setPlaces(places) {
|
|
1159
2305
|
this.places = Array.isArray(places) ? places : placesFromGeoJSON(places);
|
|
@@ -1174,13 +2320,59 @@ var PlacesLayer = class {
|
|
|
1174
2320
|
this.fitted = true;
|
|
1175
2321
|
this.map.fitBounds(boundsOf(this.places), { padding: 48, maxZoom: 15 });
|
|
1176
2322
|
}
|
|
1177
|
-
|
|
1178
|
-
|
|
2323
|
+
const source = this.map.getSource(this.sourceId);
|
|
2324
|
+
if (source) {
|
|
2325
|
+
source.setData(this.data);
|
|
2326
|
+
} else if (this.map.isStyleLoaded()) {
|
|
2327
|
+
this.install(this.data);
|
|
2328
|
+
}
|
|
1179
2329
|
}
|
|
1180
2330
|
/** The layer's current places (normalised to `Place[]`). */
|
|
1181
2331
|
get current() {
|
|
1182
2332
|
return this.places;
|
|
1183
2333
|
}
|
|
2334
|
+
/**
|
|
2335
|
+
* The generated MapLibre source/layer ids - public API for escape-hatch
|
|
2336
|
+
* styling (`map.setPaintProperty`, `queryRenderedFeatures`, …) beyond the
|
|
2337
|
+
* layer's options. Stable for the layer's lifetime, derived from the `id`
|
|
2338
|
+
* option (default `"mapmap-places"`). The cluster ids are only installed
|
|
2339
|
+
* on the map with `cluster: true` (the default), and `points` is a circle
|
|
2340
|
+
* layer by default or a symbol layer once a custom `icon` has loaded.
|
|
2341
|
+
*/
|
|
2342
|
+
get ids() {
|
|
2343
|
+
return {
|
|
2344
|
+
source: this.sourceId,
|
|
2345
|
+
points: this.pointsLayerId,
|
|
2346
|
+
clusters: this.clustersLayerId,
|
|
2347
|
+
clusterCounts: this.clusterCountLayerId
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
/**
|
|
2351
|
+
* Programmatically select a place by id - list-to-map sync for a store
|
|
2352
|
+
* finder's results list. Opens the layer's configured `popup` at the
|
|
2353
|
+
* place (`popup: false` to skip, no-op without a `popup` option) and
|
|
2354
|
+
* eases the camera to it (`flyTo: false` to skip; `zoom` to also zoom).
|
|
2355
|
+
* Returns the selected place, or `undefined` for an unknown id (in which
|
|
2356
|
+
* case nothing happens). Does NOT invoke `onPlaceClick` - a programmatic
|
|
2357
|
+
* selection is not a user click.
|
|
2358
|
+
*/
|
|
2359
|
+
select(id, options = {}) {
|
|
2360
|
+
const place = this.places.find((p) => p.id === id);
|
|
2361
|
+
if (!place) return void 0;
|
|
2362
|
+
if (options.flyTo ?? true) {
|
|
2363
|
+
this.map.easeTo({
|
|
2364
|
+
center: [place.lon, place.lat],
|
|
2365
|
+
...options.zoom !== void 0 ? { zoom: options.zoom } : {}
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
if (options.popup ?? true) this.openPopup(place);
|
|
2369
|
+
return place;
|
|
2370
|
+
}
|
|
2371
|
+
/** Close the popup opened by {@link select} or a place click, if any. */
|
|
2372
|
+
deselect() {
|
|
2373
|
+
this.popup?.remove();
|
|
2374
|
+
this.popup = void 0;
|
|
2375
|
+
}
|
|
1184
2376
|
/**
|
|
1185
2377
|
* The nearest `n` places (default `1`) to `origin` by straight-line
|
|
1186
2378
|
* (haversine) distance, each with `distanceM` attached. Pure and instant -
|
|
@@ -1269,7 +2461,7 @@ var PlacesLayer = class {
|
|
|
1269
2461
|
source: this.sourceId,
|
|
1270
2462
|
filter: ["has", "point_count"],
|
|
1271
2463
|
paint: {
|
|
1272
|
-
"circle-color": this.
|
|
2464
|
+
"circle-color": this.clusterColor,
|
|
1273
2465
|
"circle-opacity": 0.9,
|
|
1274
2466
|
"circle-radius": ["step", ["get", "point_count"], 16, 25, 20, 100, 26],
|
|
1275
2467
|
"circle-stroke-color": "#ffffff",
|
|
@@ -1507,9 +2699,9 @@ var NavigationCamera = class {
|
|
|
1507
2699
|
* {@link resume}.
|
|
1508
2700
|
*/
|
|
1509
2701
|
follow(fix, courseDeg) {
|
|
1510
|
-
const
|
|
1511
|
-
const interval = this.lastFixAt === void 0 ? this.easeMs : Math.max(0,
|
|
1512
|
-
this.lastFixAt =
|
|
2702
|
+
const now2 = Date.now();
|
|
2703
|
+
const interval = this.lastFixAt === void 0 ? this.easeMs : Math.max(0, now2 - this.lastFixAt);
|
|
2704
|
+
this.lastFixAt = now2;
|
|
1513
2705
|
this.lastFix = fix;
|
|
1514
2706
|
if (courseDeg !== void 0) this.lastCourse = courseDeg;
|
|
1515
2707
|
this.puck?.setLocation(fix, courseDeg);
|
|
@@ -1609,6 +2801,188 @@ function boundsOf2(coordinates) {
|
|
|
1609
2801
|
[maxLng, maxLat]
|
|
1610
2802
|
];
|
|
1611
2803
|
}
|
|
2804
|
+
var FLYTHROUGH_EARTH_RADIUS_M = 63710088e-1;
|
|
2805
|
+
function flythroughDistanceM(a, b) {
|
|
2806
|
+
const rad = (deg) => deg * Math.PI / 180;
|
|
2807
|
+
const dLat = rad(b[1] - a[1]);
|
|
2808
|
+
const dLon = rad(b[0] - a[0]);
|
|
2809
|
+
const h = Math.sin(dLat / 2) ** 2 + Math.cos(rad(a[1])) * Math.cos(rad(b[1])) * Math.sin(dLon / 2) ** 2;
|
|
2810
|
+
return 2 * FLYTHROUGH_EARTH_RADIUS_M * Math.asin(Math.sqrt(h));
|
|
2811
|
+
}
|
|
2812
|
+
function bearingBetween(a, b) {
|
|
2813
|
+
const rad = (deg2) => deg2 * Math.PI / 180;
|
|
2814
|
+
const dLon = rad(b[0] - a[0]);
|
|
2815
|
+
const latA = rad(a[1]);
|
|
2816
|
+
const latB = rad(b[1]);
|
|
2817
|
+
const y = Math.sin(dLon) * Math.cos(latB);
|
|
2818
|
+
const x = Math.cos(latA) * Math.sin(latB) - Math.sin(latA) * Math.cos(latB) * Math.cos(dLon);
|
|
2819
|
+
const deg = Math.atan2(y, x) * 180 / Math.PI;
|
|
2820
|
+
return (deg + 360) % 360;
|
|
2821
|
+
}
|
|
2822
|
+
function shortestArcDelta(from, to) {
|
|
2823
|
+
const delta = ((to - from) % 360 + 540) % 360 - 180;
|
|
2824
|
+
return delta === -180 ? 180 : delta;
|
|
2825
|
+
}
|
|
2826
|
+
var PoseSampler = class {
|
|
2827
|
+
constructor(coordinates) {
|
|
2828
|
+
const unwrapped = unwrapLngs(coordinates);
|
|
2829
|
+
this.points = unwrapped.filter(
|
|
2830
|
+
(p, i) => i === 0 || p[0] !== unwrapped[i - 1][0] || p[1] !== unwrapped[i - 1][1]
|
|
2831
|
+
);
|
|
2832
|
+
if (this.points.length < 2) {
|
|
2833
|
+
throw new Error(
|
|
2834
|
+
"flythrough needs a route with at least two distinct coordinates"
|
|
2835
|
+
);
|
|
2836
|
+
}
|
|
2837
|
+
this.cumulative = [0];
|
|
2838
|
+
for (let i = 1; i < this.points.length; i++) {
|
|
2839
|
+
this.cumulative.push(
|
|
2840
|
+
this.cumulative[i - 1] + flythroughDistanceM(this.points[i - 1], this.points[i])
|
|
2841
|
+
);
|
|
2842
|
+
}
|
|
2843
|
+
this.totalM = this.cumulative[this.cumulative.length - 1];
|
|
2844
|
+
}
|
|
2845
|
+
/** The interpolated point `distanceM` along the route (clamped). */
|
|
2846
|
+
pointAt(distanceM) {
|
|
2847
|
+
const d = Math.min(this.totalM, Math.max(0, distanceM));
|
|
2848
|
+
let i = 1;
|
|
2849
|
+
while (i < this.cumulative.length - 1 && this.cumulative[i] < d) i++;
|
|
2850
|
+
const before = this.cumulative[i - 1];
|
|
2851
|
+
const span = this.cumulative[i] - before;
|
|
2852
|
+
const f = span > 0 ? (d - before) / span : 0;
|
|
2853
|
+
const a = this.points[i - 1];
|
|
2854
|
+
const b = this.points[i];
|
|
2855
|
+
return [a[0] + (b[0] - a[0]) * f, a[1] + (b[1] - a[1]) * f];
|
|
2856
|
+
}
|
|
2857
|
+
/** The pose at progress `t` (clamped 0-1), chasing `lookAheadM` ahead. */
|
|
2858
|
+
poseAt(t, lookAheadM) {
|
|
2859
|
+
const d = Math.min(1, Math.max(0, t)) * this.totalM;
|
|
2860
|
+
const center = this.pointAt(d);
|
|
2861
|
+
let target = this.pointAt(d + Math.max(1, lookAheadM));
|
|
2862
|
+
if (target[0] === center[0] && target[1] === center[1]) {
|
|
2863
|
+
const last = this.points[this.points.length - 1];
|
|
2864
|
+
const prev = this.points[this.points.length - 2];
|
|
2865
|
+
return { center, bearing: bearingBetween(prev, last) };
|
|
2866
|
+
}
|
|
2867
|
+
return { center, bearing: bearingBetween(center, target) };
|
|
2868
|
+
}
|
|
2869
|
+
};
|
|
2870
|
+
function flythroughPose(coordinates, t, lookAheadM = 200) {
|
|
2871
|
+
return new PoseSampler(coordinates).poseAt(t, lookAheadM);
|
|
2872
|
+
}
|
|
2873
|
+
function scheduleFrame(cb) {
|
|
2874
|
+
if (typeof requestAnimationFrame === "function") {
|
|
2875
|
+
const handle2 = requestAnimationFrame(cb);
|
|
2876
|
+
return () => cancelAnimationFrame(handle2);
|
|
2877
|
+
}
|
|
2878
|
+
const handle = setTimeout(() => cb(Date.now()), 16);
|
|
2879
|
+
return () => clearTimeout(handle);
|
|
2880
|
+
}
|
|
2881
|
+
function flythrough(map, route, options = {}) {
|
|
2882
|
+
const m = map instanceof MapMapMap ? map.map : map;
|
|
2883
|
+
const geometry = "geometry" in route ? route.geometry : route;
|
|
2884
|
+
const sampler = new PoseSampler(geometry.coordinates);
|
|
2885
|
+
const pitch = clamp(options.pitch ?? 60, 0, 85);
|
|
2886
|
+
const zoom = options.zoom ?? 16;
|
|
2887
|
+
const lookAheadM = options.lookAheadM ?? 200;
|
|
2888
|
+
const bearingEase = Math.max(0, options.bearingEase ?? 3);
|
|
2889
|
+
const durationMs = options.speedMps !== void 0 && options.speedMps > 0 ? sampler.totalM / options.speedMps * 1e3 : Math.max(1, options.durationMs ?? 2e4);
|
|
2890
|
+
let t = 0;
|
|
2891
|
+
let rate = 1;
|
|
2892
|
+
let playing = false;
|
|
2893
|
+
let destroyed = false;
|
|
2894
|
+
let bearing;
|
|
2895
|
+
let lastFrameTs;
|
|
2896
|
+
let cancelFrame;
|
|
2897
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2898
|
+
const applyPose = (dtS) => {
|
|
2899
|
+
const pose = sampler.poseAt(t, lookAheadM);
|
|
2900
|
+
bearing = bearing === void 0 ? pose.bearing : bearing + shortestArcDelta(bearing, pose.bearing) * Math.min(1, bearingEase * dtS);
|
|
2901
|
+
m.jumpTo({ center: pose.center, bearing, pitch, zoom });
|
|
2902
|
+
for (const listener of listeners) listener(t);
|
|
2903
|
+
};
|
|
2904
|
+
const frame = (timestampMs) => {
|
|
2905
|
+
if (!playing || destroyed) return;
|
|
2906
|
+
const dtS = lastFrameTs === void 0 ? 0 : Math.max(0, timestampMs - lastFrameTs) / 1e3;
|
|
2907
|
+
lastFrameTs = timestampMs;
|
|
2908
|
+
t = Math.min(1, t + dtS * 1e3 * rate / durationMs);
|
|
2909
|
+
applyPose(dtS || 1 / 60);
|
|
2910
|
+
if (t >= 1) {
|
|
2911
|
+
playing = false;
|
|
2912
|
+
return;
|
|
2913
|
+
}
|
|
2914
|
+
cancelFrame = scheduleFrame(frame);
|
|
2915
|
+
};
|
|
2916
|
+
const pause = () => {
|
|
2917
|
+
playing = false;
|
|
2918
|
+
cancelFrame?.();
|
|
2919
|
+
cancelFrame = void 0;
|
|
2920
|
+
lastFrameTs = void 0;
|
|
2921
|
+
};
|
|
2922
|
+
return {
|
|
2923
|
+
play() {
|
|
2924
|
+
if (destroyed || playing) return;
|
|
2925
|
+
if (t >= 1) t = 0;
|
|
2926
|
+
playing = true;
|
|
2927
|
+
lastFrameTs = void 0;
|
|
2928
|
+
cancelFrame = scheduleFrame(frame);
|
|
2929
|
+
},
|
|
2930
|
+
pause,
|
|
2931
|
+
stop() {
|
|
2932
|
+
if (destroyed) return;
|
|
2933
|
+
pause();
|
|
2934
|
+
t = 0;
|
|
2935
|
+
bearing = void 0;
|
|
2936
|
+
applyPose(1 / 60);
|
|
2937
|
+
},
|
|
2938
|
+
seek(to) {
|
|
2939
|
+
if (destroyed) return;
|
|
2940
|
+
t = Math.min(1, Math.max(0, to));
|
|
2941
|
+
bearing = void 0;
|
|
2942
|
+
applyPose(1 / 60);
|
|
2943
|
+
},
|
|
2944
|
+
get speed() {
|
|
2945
|
+
return rate;
|
|
2946
|
+
},
|
|
2947
|
+
set speed(value) {
|
|
2948
|
+
if (Number.isFinite(value) && value > 0) rate = value;
|
|
2949
|
+
},
|
|
2950
|
+
get progress() {
|
|
2951
|
+
return t;
|
|
2952
|
+
},
|
|
2953
|
+
get playing() {
|
|
2954
|
+
return playing;
|
|
2955
|
+
},
|
|
2956
|
+
onProgress(listener) {
|
|
2957
|
+
listeners.add(listener);
|
|
2958
|
+
return () => listeners.delete(listener);
|
|
2959
|
+
},
|
|
2960
|
+
destroy() {
|
|
2961
|
+
destroyed = true;
|
|
2962
|
+
pause();
|
|
2963
|
+
listeners.clear();
|
|
2964
|
+
}
|
|
2965
|
+
};
|
|
2966
|
+
}
|
|
2967
|
+
function bindFlythroughToScroll(controller, target = window) {
|
|
2968
|
+
const t = target;
|
|
2969
|
+
const fraction = () => {
|
|
2970
|
+
if (typeof t.scrollTop === "number") {
|
|
2971
|
+
const max2 = (t.scrollHeight ?? 0) - (t.clientHeight ?? 0);
|
|
2972
|
+
return max2 > 0 ? t.scrollTop / max2 : 0;
|
|
2973
|
+
}
|
|
2974
|
+
const doc = t.document?.documentElement;
|
|
2975
|
+
const max = (doc?.scrollHeight ?? 0) - (t.innerHeight ?? 0);
|
|
2976
|
+
return max > 0 ? (t.scrollY ?? 0) / max : 0;
|
|
2977
|
+
};
|
|
2978
|
+
const onScroll = () => {
|
|
2979
|
+
controller.pause();
|
|
2980
|
+
controller.seek(Math.min(1, Math.max(0, fraction())));
|
|
2981
|
+
};
|
|
2982
|
+
t.addEventListener("scroll", onScroll, { passive: true });
|
|
2983
|
+
onScroll();
|
|
2984
|
+
return () => t.removeEventListener("scroll", onScroll);
|
|
2985
|
+
}
|
|
1612
2986
|
function usesGlobe(projection) {
|
|
1613
2987
|
if (projection === null || projection === void 0) return false;
|
|
1614
2988
|
if (typeof projection === "string") {
|
|
@@ -1621,6 +2995,207 @@ function usesGlobe(projection) {
|
|
|
1621
2995
|
return false;
|
|
1622
2996
|
}
|
|
1623
2997
|
|
|
2998
|
+
// src/isochrone.ts
|
|
2999
|
+
var MODE_TO_COSTING = {
|
|
3000
|
+
walk: "pedestrian",
|
|
3001
|
+
cycle: "bicycle",
|
|
3002
|
+
drive: "auto"
|
|
3003
|
+
};
|
|
3004
|
+
var IsochroneLayer = class {
|
|
3005
|
+
constructor(map, options = {}) {
|
|
3006
|
+
this.lastMinutes = [];
|
|
3007
|
+
this.lastColor = SIGNAL_BLUE;
|
|
3008
|
+
this.handleStyleLoad = () => {
|
|
3009
|
+
if (this.lastData) this.install(this.lastData);
|
|
3010
|
+
};
|
|
3011
|
+
if (map instanceof MapMapMap) {
|
|
3012
|
+
this.map = map.map;
|
|
3013
|
+
this.baseUrl = (options.baseUrl ?? map.baseUrl).replace(/\/+$/, "");
|
|
3014
|
+
this.apiKey = options.apiKey ?? map.apiKey;
|
|
3015
|
+
} else {
|
|
3016
|
+
this.map = map;
|
|
3017
|
+
if (!options.baseUrl) {
|
|
3018
|
+
throw new Error(
|
|
3019
|
+
"IsochroneLayer needs a baseUrl when given a raw MapLibre map"
|
|
3020
|
+
);
|
|
3021
|
+
}
|
|
3022
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
3023
|
+
this.apiKey = options.apiKey;
|
|
3024
|
+
}
|
|
3025
|
+
const id = options.id ?? "mapmap-isochrone";
|
|
3026
|
+
this.sourceId = `${id}-src`;
|
|
3027
|
+
this.fillLayerId = `${id}-fill`;
|
|
3028
|
+
this.lineLayerId = `${id}-line`;
|
|
3029
|
+
this.labelLayerId = `${id}-label`;
|
|
3030
|
+
this.map.on("style.load", this.handleStyleLoad);
|
|
3031
|
+
}
|
|
3032
|
+
/**
|
|
3033
|
+
* Fetch and render reachability rings. Returns the GeoJSON
|
|
3034
|
+
* FeatureCollection the gateway produced (each feature carries a
|
|
3035
|
+
* `contour` property in minutes), for callers who also want the raw
|
|
3036
|
+
* shapes. Replaces any rings already shown by this layer.
|
|
3037
|
+
*/
|
|
3038
|
+
async showReachability(options) {
|
|
3039
|
+
if (options.minutes.length === 0) {
|
|
3040
|
+
throw new Error(
|
|
3041
|
+
"showReachability needs at least one contour in minutes, e.g. { minutes: [5, 10, 15] }"
|
|
3042
|
+
);
|
|
3043
|
+
}
|
|
3044
|
+
const [lon, lat] = toLngLat(options.origin);
|
|
3045
|
+
const mode = options.mode ?? "walk";
|
|
3046
|
+
const body = {
|
|
3047
|
+
locations: [{ lat, lon }],
|
|
3048
|
+
costing: MODE_TO_COSTING[mode] ?? mode,
|
|
3049
|
+
// Largest first so smaller (nearer) rings paint on top.
|
|
3050
|
+
contours: [...options.minutes].sort((a, b) => b - a).map((time) => ({ time })),
|
|
3051
|
+
polygons: true
|
|
3052
|
+
};
|
|
3053
|
+
if (options.costingOptions) body["costing_options"] = options.costingOptions;
|
|
3054
|
+
const headers = {
|
|
3055
|
+
"Content-Type": "application/json",
|
|
3056
|
+
Accept: "application/json"
|
|
3057
|
+
};
|
|
3058
|
+
if (this.apiKey) headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
3059
|
+
const response = await fetch(`${this.baseUrl}/isochrone`, {
|
|
3060
|
+
method: "POST",
|
|
3061
|
+
headers,
|
|
3062
|
+
body: JSON.stringify(body)
|
|
3063
|
+
});
|
|
3064
|
+
const payload = await response.json().catch(() => null);
|
|
3065
|
+
if (!response.ok) {
|
|
3066
|
+
const problem = typeof payload === "object" && payload !== null ? payload : void 0;
|
|
3067
|
+
const context = [problem?.["title"], problem?.["detail"]].filter((v) => typeof v === "string" && v.length > 0).join(" - ");
|
|
3068
|
+
throw new Error(
|
|
3069
|
+
`isochrone request failed: HTTP ${response.status}${context ? ` (${context})` : ""}`
|
|
3070
|
+
);
|
|
3071
|
+
}
|
|
3072
|
+
if (typeof payload !== "object" || payload === null || payload["type"] !== "FeatureCollection") {
|
|
3073
|
+
throw new Error("isochrone returned no FeatureCollection");
|
|
3074
|
+
}
|
|
3075
|
+
const collection = payload;
|
|
3076
|
+
this.lastMinutes = [...options.minutes].sort((a, b) => a - b);
|
|
3077
|
+
this.lastColor = options.color ?? SIGNAL_BLUE;
|
|
3078
|
+
this.draw(collection);
|
|
3079
|
+
return collection;
|
|
3080
|
+
}
|
|
3081
|
+
/** The most recently drawn rings, if any. */
|
|
3082
|
+
get current() {
|
|
3083
|
+
return this.lastData;
|
|
3084
|
+
}
|
|
3085
|
+
/** Draw (or update) a ring collection. Safe before the style has loaded. */
|
|
3086
|
+
draw(collection) {
|
|
3087
|
+
this.lastData = collection;
|
|
3088
|
+
if (!this.map.isStyleLoaded()) return;
|
|
3089
|
+
this.install(collection);
|
|
3090
|
+
}
|
|
3091
|
+
/** Add-or-update the source and ring layers on the current style. */
|
|
3092
|
+
install(collection) {
|
|
3093
|
+
const existing = this.map.getSource(this.sourceId);
|
|
3094
|
+
if (existing) {
|
|
3095
|
+
existing.setData(collection);
|
|
3096
|
+
} else {
|
|
3097
|
+
this.map.addSource(this.sourceId, {
|
|
3098
|
+
type: "geojson",
|
|
3099
|
+
data: collection
|
|
3100
|
+
});
|
|
3101
|
+
}
|
|
3102
|
+
if (this.map.getLayer(this.fillLayerId)) {
|
|
3103
|
+
this.map.setPaintProperty(
|
|
3104
|
+
this.fillLayerId,
|
|
3105
|
+
"fill-color",
|
|
3106
|
+
this.lastColor
|
|
3107
|
+
);
|
|
3108
|
+
this.map.setPaintProperty(
|
|
3109
|
+
this.fillLayerId,
|
|
3110
|
+
"fill-opacity",
|
|
3111
|
+
this.fillOpacity()
|
|
3112
|
+
);
|
|
3113
|
+
this.map.setPaintProperty(this.lineLayerId, "line-color", this.lastColor);
|
|
3114
|
+
this.map.setPaintProperty(this.labelLayerId, "text-color", this.lastColor);
|
|
3115
|
+
return;
|
|
3116
|
+
}
|
|
3117
|
+
const fill2 = {
|
|
3118
|
+
id: this.fillLayerId,
|
|
3119
|
+
type: "fill",
|
|
3120
|
+
source: this.sourceId,
|
|
3121
|
+
paint: {
|
|
3122
|
+
"fill-color": this.lastColor,
|
|
3123
|
+
"fill-opacity": this.fillOpacity()
|
|
3124
|
+
}
|
|
3125
|
+
};
|
|
3126
|
+
const line = {
|
|
3127
|
+
id: this.lineLayerId,
|
|
3128
|
+
type: "line",
|
|
3129
|
+
source: this.sourceId,
|
|
3130
|
+
paint: {
|
|
3131
|
+
"line-color": this.lastColor,
|
|
3132
|
+
"line-width": 1.5,
|
|
3133
|
+
"line-opacity": 0.8
|
|
3134
|
+
}
|
|
3135
|
+
};
|
|
3136
|
+
const label = {
|
|
3137
|
+
id: this.labelLayerId,
|
|
3138
|
+
type: "symbol",
|
|
3139
|
+
source: this.sourceId,
|
|
3140
|
+
layout: {
|
|
3141
|
+
"symbol-placement": "line",
|
|
3142
|
+
"text-field": [
|
|
3143
|
+
"concat",
|
|
3144
|
+
["to-string", ["get", "contour"]],
|
|
3145
|
+
" min"
|
|
3146
|
+
],
|
|
3147
|
+
// Ships with the MapMap default styles (see places.ts).
|
|
3148
|
+
"text-font": ["Noto Sans Regular"],
|
|
3149
|
+
"text-size": 11
|
|
3150
|
+
},
|
|
3151
|
+
paint: {
|
|
3152
|
+
"text-color": this.lastColor,
|
|
3153
|
+
"text-halo-color": "#ffffff",
|
|
3154
|
+
"text-halo-width": 1.2
|
|
3155
|
+
}
|
|
3156
|
+
};
|
|
3157
|
+
this.map.addLayer(fill2);
|
|
3158
|
+
this.map.addLayer(line);
|
|
3159
|
+
this.map.addLayer(label);
|
|
3160
|
+
}
|
|
3161
|
+
/**
|
|
3162
|
+
* Graduated fill opacity: the nearest contour (fewest minutes) is the
|
|
3163
|
+
* most opaque, the farthest the faintest, interpolated on each
|
|
3164
|
+
* feature's `contour` property. With one contour it is a constant.
|
|
3165
|
+
*/
|
|
3166
|
+
fillOpacity() {
|
|
3167
|
+
const min = this.lastMinutes[0] ?? 0;
|
|
3168
|
+
const max = this.lastMinutes[this.lastMinutes.length - 1] ?? 0;
|
|
3169
|
+
if (this.lastMinutes.length < 2 || min === max) return 0.18;
|
|
3170
|
+
return [
|
|
3171
|
+
"interpolate",
|
|
3172
|
+
["linear"],
|
|
3173
|
+
["get", "contour"],
|
|
3174
|
+
min,
|
|
3175
|
+
0.28,
|
|
3176
|
+
max,
|
|
3177
|
+
0.08
|
|
3178
|
+
];
|
|
3179
|
+
}
|
|
3180
|
+
/** Remove the rings' layers and source from the map. */
|
|
3181
|
+
clear() {
|
|
3182
|
+
for (const layerId of [
|
|
3183
|
+
this.labelLayerId,
|
|
3184
|
+
this.lineLayerId,
|
|
3185
|
+
this.fillLayerId
|
|
3186
|
+
]) {
|
|
3187
|
+
if (this.map.getLayer(layerId)) this.map.removeLayer(layerId);
|
|
3188
|
+
}
|
|
3189
|
+
if (this.map.getSource(this.sourceId)) this.map.removeSource(this.sourceId);
|
|
3190
|
+
this.lastData = void 0;
|
|
3191
|
+
}
|
|
3192
|
+
/** Remove the rings and detach the layer's `style.load` listener. */
|
|
3193
|
+
destroy() {
|
|
3194
|
+
this.map.off("style.load", this.handleStyleLoad);
|
|
3195
|
+
this.clear();
|
|
3196
|
+
}
|
|
3197
|
+
};
|
|
3198
|
+
|
|
1624
3199
|
// src/adr.ts
|
|
1625
3200
|
function buildAdrCheckBody(request) {
|
|
1626
3201
|
const dims = request.dimensions ?? {};
|
|
@@ -1763,14 +3338,14 @@ function directionArrow(direction) {
|
|
|
1763
3338
|
}
|
|
1764
3339
|
}
|
|
1765
3340
|
function speak(instruction, options = {}) {
|
|
1766
|
-
const
|
|
3341
|
+
const synth2 = globalThis.speechSynthesis;
|
|
1767
3342
|
const Utterance = globalThis.SpeechSynthesisUtterance;
|
|
1768
|
-
if (!
|
|
3343
|
+
if (!synth2 || !Utterance) return false;
|
|
1769
3344
|
const text = instruction.ssmlAnnouncement ? ssmlToText(instruction.ssmlAnnouncement) : instruction.announcement;
|
|
1770
3345
|
const utterance = new Utterance(text);
|
|
1771
3346
|
if (options.lang) utterance.lang = options.lang;
|
|
1772
3347
|
if (options.rate) utterance.rate = options.rate;
|
|
1773
|
-
|
|
3348
|
+
synth2.speak(utterance);
|
|
1774
3349
|
return true;
|
|
1775
3350
|
}
|
|
1776
3351
|
var GuidanceBanner = class {
|
|
@@ -1822,6 +3397,111 @@ var GuidanceBanner = class {
|
|
|
1822
3397
|
}
|
|
1823
3398
|
};
|
|
1824
3399
|
|
|
1825
|
-
|
|
3400
|
+
// src/voice.ts
|
|
3401
|
+
function severityProsody(severity, baseRate = 1) {
|
|
3402
|
+
switch (severity) {
|
|
3403
|
+
case "critical":
|
|
3404
|
+
return { rate: baseRate * 1.1, pitch: 1.2, interrupt: true };
|
|
3405
|
+
case "warning":
|
|
3406
|
+
return { rate: baseRate, pitch: 1.1, interrupt: false };
|
|
3407
|
+
default:
|
|
3408
|
+
return { rate: baseRate, pitch: 1, interrupt: false };
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
var EARCON_SEVERITIES = ["warning", "critical"];
|
|
3412
|
+
var VoiceGuidance = class {
|
|
3413
|
+
constructor(options = {}) {
|
|
3414
|
+
this.offRouteAnnounced = false;
|
|
3415
|
+
this.options = options;
|
|
3416
|
+
this.volume = clampVolume(options.volume ?? 1);
|
|
3417
|
+
this.mutedState = options.muted ?? false;
|
|
3418
|
+
}
|
|
3419
|
+
/** Whether this environment can speak at all. */
|
|
3420
|
+
get available() {
|
|
3421
|
+
return synth() !== void 0 && utteranceCtor() !== void 0;
|
|
3422
|
+
}
|
|
3423
|
+
/** Whether announcements are currently muted. */
|
|
3424
|
+
get muted() {
|
|
3425
|
+
return this.mutedState;
|
|
3426
|
+
}
|
|
3427
|
+
/** Mute announcements (also cancels anything mid-utterance). */
|
|
3428
|
+
mute() {
|
|
3429
|
+
this.mutedState = true;
|
|
3430
|
+
synth()?.cancel();
|
|
3431
|
+
}
|
|
3432
|
+
/** Unmute announcements. Prompts seen while muted are not replayed. */
|
|
3433
|
+
unmute() {
|
|
3434
|
+
this.mutedState = false;
|
|
3435
|
+
}
|
|
3436
|
+
/** Set the volume for speech and earcons (clamped to 0–1). */
|
|
3437
|
+
setVolume(volume) {
|
|
3438
|
+
this.volume = clampVolume(volume);
|
|
3439
|
+
}
|
|
3440
|
+
/**
|
|
3441
|
+
* Consume one guidance update. Speaks the update's prompt the first
|
|
3442
|
+
* time its `utteranceId` appears; plays the warning earcon once per
|
|
3443
|
+
* off-route episode. Safe to call in any environment.
|
|
3444
|
+
*/
|
|
3445
|
+
update(update) {
|
|
3446
|
+
if (update.state === "offRoute") {
|
|
3447
|
+
if (!this.offRouteAnnounced) {
|
|
3448
|
+
this.offRouteAnnounced = true;
|
|
3449
|
+
this.playEarcon("warning");
|
|
3450
|
+
}
|
|
3451
|
+
return;
|
|
3452
|
+
}
|
|
3453
|
+
this.offRouteAnnounced = false;
|
|
3454
|
+
const spoken = update.spoken;
|
|
3455
|
+
if (!spoken || spoken.utteranceId === this.lastUtteranceId) return;
|
|
3456
|
+
this.lastUtteranceId = spoken.utteranceId;
|
|
3457
|
+
this.speak(spoken, update.severity ?? "info", update.voiceLocale);
|
|
3458
|
+
}
|
|
3459
|
+
/** Cancel any speech in progress and forget the de-duplication state. */
|
|
3460
|
+
dispose() {
|
|
3461
|
+
synth()?.cancel();
|
|
3462
|
+
this.lastUtteranceId = void 0;
|
|
3463
|
+
this.offRouteAnnounced = false;
|
|
3464
|
+
}
|
|
3465
|
+
speak(prompt, severity, voiceLocale) {
|
|
3466
|
+
if (this.mutedState) return;
|
|
3467
|
+
const synthesis = synth();
|
|
3468
|
+
const Utterance = utteranceCtor();
|
|
3469
|
+
if (!synthesis || !Utterance) return;
|
|
3470
|
+
const prosody = severityProsody(severity, this.options.rate ?? 1);
|
|
3471
|
+
if (prosody.interrupt) synthesis.cancel();
|
|
3472
|
+
if (EARCON_SEVERITIES.includes(severity)) this.playEarcon(severity);
|
|
3473
|
+
const text = prompt.ssml ? ssmlToText(prompt.ssml) : prompt.text;
|
|
3474
|
+
const utterance = new Utterance(text);
|
|
3475
|
+
const lang = voiceLocale ?? this.options.lang;
|
|
3476
|
+
if (lang) utterance.lang = lang;
|
|
3477
|
+
utterance.rate = prosody.rate;
|
|
3478
|
+
utterance.pitch = prosody.pitch;
|
|
3479
|
+
utterance.volume = this.volume;
|
|
3480
|
+
synthesis.speak(utterance);
|
|
3481
|
+
}
|
|
3482
|
+
playEarcon(severity) {
|
|
3483
|
+
if (this.mutedState) return;
|
|
3484
|
+
const url = this.options.earcons?.[severity];
|
|
3485
|
+
if (!url) return;
|
|
3486
|
+
const AudioCtor = globalThis.Audio;
|
|
3487
|
+
if (!AudioCtor) return;
|
|
3488
|
+
const audio = new AudioCtor(url);
|
|
3489
|
+
audio.volume = this.volume;
|
|
3490
|
+
void audio.play()?.catch?.(() => {
|
|
3491
|
+
});
|
|
3492
|
+
}
|
|
3493
|
+
};
|
|
3494
|
+
function synth() {
|
|
3495
|
+
return globalThis.speechSynthesis;
|
|
3496
|
+
}
|
|
3497
|
+
function utteranceCtor() {
|
|
3498
|
+
return globalThis.SpeechSynthesisUtterance;
|
|
3499
|
+
}
|
|
3500
|
+
function clampVolume(volume) {
|
|
3501
|
+
if (!Number.isFinite(volume)) return 1;
|
|
3502
|
+
return Math.min(1, Math.max(0, volume));
|
|
3503
|
+
}
|
|
3504
|
+
|
|
3505
|
+
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, ThemeScheduler, VoiceGuidance, applyPoiDesign, bannerLanes, bearingBetween, bindFlythroughToScroll, buildAdrCheckBody, buildProbeUrl, buildRouteQuery, buildRouteUrl, buildStyle, builtInPoiColor, createMap, createRouteEffect, defaultNavDesign, defaultPoiDesign, directionArrow, effectsFromStyleMetadata, extractGuidance, flythrough, flythroughPose, formatCoord, formatCoords, haversineDistanceM, isNameTextField, languageTextField, lngLatToMercator, navDesignFromTheme, navDesignFromThemeUrl, parseCssColour, parseNavDesign, parseOsrmRoute, parsePoiDesign, placesFromGeoJSON, poiDesignFromTheme, poiDesignFromThemeUrl, poiDesignIsDefault, poiTextColorExpression, prefersReducedMotion, registerPmtilesProtocol, resetDiagnostics, resolveTheme, runMapDiagnostics, setMapLanguage, severityProsody, shortestArcDeg, shortestArcDelta, speak, ssmlToText, sunTimes, tessellateRouteRibbon, toLngLat, toPmtilesUrl, uploadProbeBatch };
|
|
1826
3506
|
//# sourceMappingURL=index.js.map
|
|
1827
3507
|
//# sourceMappingURL=index.js.map
|