@tileflow/dev 0.1.0-alpha.34 → 0.1.0-alpha.36
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/THIRD_PARTY_NOTICES.md +2 -2
- package/dist/artifacts.js +1 -1
- package/dist/{chunk-2F7LOYQI.js → chunk-UKWAQKLH.js} +11 -4
- package/dist/devtools-browser.js +1 -1
- package/dist/{preview-assets-KGUICQHG.js → preview-assets-MT3RXZMN.js} +0 -51
- package/dist/preview.d.ts +1 -41
- package/dist/preview.js +117 -798
- package/dist/server.js +2 -2
- package/docs/bounded-vector-feature-inspection.md +1 -1
- package/docs/native-artifacts.md +3 -1
- package/package.json +3 -4
package/dist/preview.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
resolveThemeSelection
|
|
12
12
|
} from "@tileflow/core";
|
|
13
13
|
|
|
14
|
-
// src/
|
|
14
|
+
// src/bounded-response.ts
|
|
15
15
|
async function readBoundedTileflowResponse(response, maximumBytes) {
|
|
16
16
|
if (!Number.isSafeInteger(maximumBytes) || maximumBytes < 1) {
|
|
17
17
|
throw new Error("invalid response byte limit");
|
|
@@ -51,94 +51,6 @@ async function readBoundedTileflowJsonResponse(response, maximumBytes) {
|
|
|
51
51
|
const bytes = await readBoundedTileflowResponse(response, maximumBytes);
|
|
52
52
|
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
53
53
|
}
|
|
54
|
-
function normalizeTileflowLandmarkManifest(candidate, manifestUrl) {
|
|
55
|
-
const value = candidate;
|
|
56
|
-
const portableId = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
57
|
-
const sha256 = /^[a-f0-9]{64}$/;
|
|
58
|
-
const axisConventions = /* @__PURE__ */ new Set(["EUN_Y_UP", "EUS_Y_UP", "ENU_Z_UP"]);
|
|
59
|
-
if (value?.schemaVersion !== 2 || typeof value.id !== "string" || !portableId.test(value.id) || !Array.isArray(value.archives) || value.archives.length > 64 || !Array.isArray(value.landmarks) || value.landmarks.length > 4096 || !Number.isInteger(value.minzoom) || value.minzoom < 0 || value.minzoom > 24 || !Number.isInteger(value.maximumVisibleModels) || value.maximumVisibleModels < 1 || value.maximumVisibleModels > 64) {
|
|
60
|
-
throw new Error("invalid landmark manifest");
|
|
61
|
-
}
|
|
62
|
-
const minzoom = value.minzoom;
|
|
63
|
-
const maximumVisibleModels = value.maximumVisibleModels;
|
|
64
|
-
const maximumCachedModels = value.maximumCachedModels === void 0 ? Math.min(128, Math.max(maximumVisibleModels, maximumVisibleModels * 2)) : value.maximumCachedModels;
|
|
65
|
-
if (!Number.isInteger(maximumCachedModels) || maximumCachedModels < maximumVisibleModels || maximumCachedModels > 128) {
|
|
66
|
-
throw new Error("invalid landmark cache limit");
|
|
67
|
-
}
|
|
68
|
-
const archiveIds = /* @__PURE__ */ new Set();
|
|
69
|
-
const archiveMap = /* @__PURE__ */ new Map();
|
|
70
|
-
for (const input of value.archives) {
|
|
71
|
-
const archive = input;
|
|
72
|
-
if (typeof archive?.id !== "string" || !portableId.test(archive.id) || archiveIds.has(archive.id) || typeof archive.url !== "string" || archive.url.length < 1 || archive.url.length > 2048 || !Number.isSafeInteger(archive.bytes) || archive.bytes < 1 || archive.bytes > 8 * 1024 * 1024 * 1024 || typeof archive.sha256 !== "string" || !sha256.test(archive.sha256)) {
|
|
73
|
-
throw new Error("invalid landmark archive");
|
|
74
|
-
}
|
|
75
|
-
const url = new URL(archive.url, manifestUrl);
|
|
76
|
-
if (url.protocol !== "http:" && url.protocol !== "https:" || url.username !== "" || url.password !== "") {
|
|
77
|
-
throw new Error("invalid landmark archive URL");
|
|
78
|
-
}
|
|
79
|
-
const normalized = {
|
|
80
|
-
bytes: archive.bytes,
|
|
81
|
-
id: archive.id,
|
|
82
|
-
sha256: archive.sha256,
|
|
83
|
-
url: url.toString()
|
|
84
|
-
};
|
|
85
|
-
archiveIds.add(archive.id);
|
|
86
|
-
archiveMap.set(archive.id, normalized);
|
|
87
|
-
}
|
|
88
|
-
const landmarkIds = /* @__PURE__ */ new Set();
|
|
89
|
-
const landmarks = value.landmarks.map((input) => {
|
|
90
|
-
const landmark = input;
|
|
91
|
-
const center = landmark?.center;
|
|
92
|
-
const bounds = landmark?.bounds;
|
|
93
|
-
if (typeof landmark?.id !== "string" || !portableId.test(landmark.id) || landmarkIds.has(landmark.id) || !Array.isArray(center) || center.length !== 2 || !center.every(Number.isFinite) || center[0] < -180 || center[0] > 180 || center[1] < -90 || center[1] > 90 || !Array.isArray(bounds) || bounds.length !== 4 || !bounds.every(Number.isFinite) || bounds[0] < -180 || bounds[2] > 180 || bounds[1] < -90 || bounds[3] > 90 || bounds[0] > bounds[2] || bounds[1] > bounds[3] || center[0] < bounds[0] || center[0] > bounds[2] || center[1] < bounds[1] || center[1] > bounds[3] || landmark.priority !== void 0 && !Number.isFinite(landmark.priority) || !Array.isArray(landmark.models) || landmark.models.length < 1 || landmark.models.length > 8) {
|
|
94
|
-
throw new Error("invalid landmark entry");
|
|
95
|
-
}
|
|
96
|
-
landmarkIds.add(landmark.id);
|
|
97
|
-
const models = landmark.models.map((inputModel) => {
|
|
98
|
-
const model = inputModel;
|
|
99
|
-
if (!model) throw new Error("invalid landmark model");
|
|
100
|
-
const archive = typeof model.archiveId === "string" ? archiveMap.get(model.archiveId) : void 0;
|
|
101
|
-
if (!archive || !Number.isInteger(model?.minzoom) || model.minzoom < minzoom || model.minzoom > 24 || !Number.isInteger(model.z) || model.z < 0 || model.z > 26 || !Number.isInteger(model.x) || model.x < 0 || model.x >= 2 ** model.z || !Number.isInteger(model.y) || model.y < 0 || model.y >= 2 ** model.z || !Number.isSafeInteger(model.bytes) || model.bytes < 1 || model.bytes > 64 * 1024 * 1024 || typeof model.sha256 !== "string" || !sha256.test(model.sha256) || typeof model.axisConvention !== "string" || !axisConventions.has(model.axisConvention)) {
|
|
102
|
-
throw new Error("invalid landmark model");
|
|
103
|
-
}
|
|
104
|
-
return {
|
|
105
|
-
archive,
|
|
106
|
-
archiveId: model.archiveId,
|
|
107
|
-
axisConvention: model.axisConvention,
|
|
108
|
-
bytes: model.bytes,
|
|
109
|
-
key: [archive.sha256, model.z, model.x, model.y, model.sha256].join(":"),
|
|
110
|
-
minzoom: model.minzoom,
|
|
111
|
-
sha256: model.sha256,
|
|
112
|
-
x: model.x,
|
|
113
|
-
y: model.y,
|
|
114
|
-
z: model.z
|
|
115
|
-
};
|
|
116
|
-
});
|
|
117
|
-
models.sort((left, right) => left.minzoom - right.minzoom || left.key.localeCompare(right.key));
|
|
118
|
-
if (new Set(models.map((model) => model.minzoom)).size !== models.length) {
|
|
119
|
-
throw new Error("duplicate landmark LOD zoom");
|
|
120
|
-
}
|
|
121
|
-
if (models[0].minzoom !== minzoom) {
|
|
122
|
-
throw new Error("landmark requires a base model");
|
|
123
|
-
}
|
|
124
|
-
return {
|
|
125
|
-
bounds,
|
|
126
|
-
center,
|
|
127
|
-
id: landmark.id,
|
|
128
|
-
models,
|
|
129
|
-
priority: landmark.priority ?? 0
|
|
130
|
-
};
|
|
131
|
-
});
|
|
132
|
-
return {
|
|
133
|
-
archives: [...archiveMap.values()],
|
|
134
|
-
id: value.id,
|
|
135
|
-
landmarks,
|
|
136
|
-
maximumCachedModels,
|
|
137
|
-
maximumVisibleModels,
|
|
138
|
-
minzoom,
|
|
139
|
-
schemaVersion: 2
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
54
|
|
|
143
55
|
// src/preview-scene.ts
|
|
144
56
|
var previewSceneClient = String.raw`
|
|
@@ -230,6 +142,42 @@ function createPreviewSceneThemes(map, readSun) {
|
|
|
230
142
|
};
|
|
231
143
|
}
|
|
232
144
|
|
|
145
|
+
// src/preview-style-reads.ts
|
|
146
|
+
function createPreviewStyleReads(map) {
|
|
147
|
+
let snapshot;
|
|
148
|
+
map.on("style.load", () => {
|
|
149
|
+
snapshot = void 0;
|
|
150
|
+
});
|
|
151
|
+
return {
|
|
152
|
+
metadata() {
|
|
153
|
+
if (!snapshot || snapshot.style !== map.style) {
|
|
154
|
+
const style = map.getStyle();
|
|
155
|
+
if (!style) return void 0;
|
|
156
|
+
snapshot = { style: map.style, metadata: style.metadata };
|
|
157
|
+
}
|
|
158
|
+
return snapshot.metadata;
|
|
159
|
+
},
|
|
160
|
+
// Layers change between style loads, so they are read live, in the shape of the style's own
|
|
161
|
+
// layers. Custom layers have no style definition; getStyle leaves them out too.
|
|
162
|
+
layers() {
|
|
163
|
+
const layers = [];
|
|
164
|
+
for (const id of map.getLayersOrder()) {
|
|
165
|
+
const layer = map.getLayer(id);
|
|
166
|
+
if (!layer || layer.type === "custom") continue;
|
|
167
|
+
layers.push({
|
|
168
|
+
id: layer.id,
|
|
169
|
+
type: layer.type,
|
|
170
|
+
metadata: layer.metadata,
|
|
171
|
+
"source-layer": layer.sourceLayer,
|
|
172
|
+
minzoom: layer.minzoom,
|
|
173
|
+
maxzoom: layer.maxzoom
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return layers;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
233
181
|
// src/preview-html.ts
|
|
234
182
|
function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticPreview, fontFaces = [], fitViewport = false, controls) {
|
|
235
183
|
const styleUrl = preview ? `${basePath}/styles/${preview.mapName}/${preview.themeName}.json${controls ? `?${new URLSearchParams({ controls })}` : ""}` : void 0;
|
|
@@ -341,15 +289,17 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
341
289
|
<div class="badge" id="badge">Tileflow preview</div>
|
|
342
290
|
<div class="status" id="status" role="status"></div>
|
|
343
291
|
<div id="map"></div>
|
|
344
|
-
<script type="importmap">{"imports":{"
|
|
292
|
+
<script type="importmap">{"imports":{"three":"${basePath}/__runtime/three.module.js"}}</script>
|
|
345
293
|
<script type="module">
|
|
346
294
|
import * as maplibregl from "${basePath}/__runtime/maplibre-gl.mjs";
|
|
347
295
|
import {createPreviewObserver} from "${basePath}/__runtime/devtools-observer.js";
|
|
348
296
|
import {loadTileflowStyleFonts} from "${basePath}/__runtime/tileflow-browser.js";
|
|
349
297
|
import {registerTileflowContourProtocol} from "${basePath}/__runtime/tileflow-browser.js";
|
|
350
298
|
import {registerTileflowPmtilesProtocol} from "${basePath}/__runtime/tileflow-browser.js";
|
|
351
|
-
import {attachTileflowNotices} from "${basePath}/__runtime/tileflow-browser.js";
|
|
352
|
-
import {
|
|
299
|
+
import {attachTileflowNauticalLights, attachTileflowNotices} from "${basePath}/__runtime/tileflow-browser.js";
|
|
300
|
+
import {attachTileflowSky, attachTileflowSkyHaze, tileflowMaxPitch} from "${basePath}/__runtime/tileflow-browser.js";
|
|
301
|
+
import {tileflowCanvasContextAttributes} from "${basePath}/__runtime/tileflow-browser.js";
|
|
302
|
+
import {createTileflowMap3D} from "${basePath}/__runtime/tileflow-browser.js";
|
|
353
303
|
import {readTileflowSunPreset, resolveTileflowSun, tileflowDefaultSunPreset} from "${basePath}/__runtime/tileflow-browser.js";
|
|
354
304
|
|
|
355
305
|
maplibregl.setWorkerUrl("${basePath}/__runtime/maplibre-gl-worker.mjs");
|
|
@@ -359,7 +309,6 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
359
309
|
if (window.parent !== window) document.documentElement.dataset.devtoolsEmbedded = "true";
|
|
360
310
|
|
|
361
311
|
const initialStatus = ${JSON.stringify(initialStatus)};
|
|
362
|
-
const normalizeTileflowLandmarkManifest = ${normalizeTileflowLandmarkManifest.toString()};
|
|
363
312
|
const readBoundedTileflowResponse = ${readBoundedTileflowResponse.toString()};
|
|
364
313
|
const readBoundedTileflowJsonResponse = ${readBoundedTileflowJsonResponse.toString()};
|
|
365
314
|
const initialGeneration = initialStatus.generation;
|
|
@@ -391,6 +340,8 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
391
340
|
requestedMapWorkerCount <= 3
|
|
392
341
|
? requestedMapWorkerCount
|
|
393
342
|
: undefined;
|
|
343
|
+
// Benchmarks compare the multisampled canvas with a single-sampled one through this override.
|
|
344
|
+
const mapAntialias = readToggleFromUrl("mapAntialias", true);
|
|
394
345
|
const mapBenchmarkLongitude = Number(previewSearchParameters.get("lng"));
|
|
395
346
|
const mapBenchmarkLatitude = Number(previewSearchParameters.get("lat"));
|
|
396
347
|
const mapBenchmarkCenter =
|
|
@@ -409,16 +360,6 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
409
360
|
}
|
|
410
361
|
let threeDimensionalEnabled = readToggleFromUrl("buildings3d", true);
|
|
411
362
|
let treesEnabled = readToggleFromUrl("trees3d", true);
|
|
412
|
-
const landmarkState = {
|
|
413
|
-
active: [],
|
|
414
|
-
cacheLimit: undefined,
|
|
415
|
-
enabled: threeDimensionalEnabled,
|
|
416
|
-
manifestId: undefined,
|
|
417
|
-
loaded: [],
|
|
418
|
-
loading: [],
|
|
419
|
-
errors: []
|
|
420
|
-
};
|
|
421
|
-
globalThis.__tileflowLandmarkState = landmarkState;
|
|
422
363
|
const buildingWireframeMetrics = {
|
|
423
364
|
buildingCount: 0,
|
|
424
365
|
refreshMilliseconds: undefined,
|
|
@@ -427,22 +368,11 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
427
368
|
};
|
|
428
369
|
globalThis.__tileflowBuildingWireframeMetrics = buildingWireframeMetrics;
|
|
429
370
|
let THREE;
|
|
430
|
-
let GLTFLoader;
|
|
431
|
-
let DRACOLoader;
|
|
432
|
-
let MeshoptDecoder;
|
|
433
371
|
let LineMaterial;
|
|
434
372
|
let LineSegments2;
|
|
435
373
|
let LineSegmentsGeometry;
|
|
436
|
-
let PMTiles;
|
|
437
|
-
let FetchSource;
|
|
438
|
-
let landmarkDracoLoader;
|
|
439
374
|
let threeCoreRuntimePromise;
|
|
440
375
|
let buildingWireframeRuntimePromise;
|
|
441
|
-
let landmarkRuntimePromise;
|
|
442
|
-
let pmtilesRuntimePromise;
|
|
443
|
-
const landmarkManifestPromises = new Map();
|
|
444
|
-
const landmarkManifestMaximumBytes = 1024 * 1024;
|
|
445
|
-
const landmarkManifestTimeoutMs = 10000;
|
|
446
376
|
|
|
447
377
|
function loadThreeCoreRuntime() {
|
|
448
378
|
if (!threeCoreRuntimePromise) {
|
|
@@ -471,89 +401,6 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
471
401
|
return buildingWireframeRuntimePromise;
|
|
472
402
|
}
|
|
473
403
|
|
|
474
|
-
function loadLandmarkRuntime() {
|
|
475
|
-
if (!landmarkRuntimePromise) {
|
|
476
|
-
landmarkRuntimePromise = Promise.all([
|
|
477
|
-
loadThreeCoreRuntime(),
|
|
478
|
-
import("${basePath}/__runtime/three-addons/loaders/GLTFLoader.js"),
|
|
479
|
-
import("${basePath}/__runtime/three-addons/loaders/DRACOLoader.js"),
|
|
480
|
-
import("${basePath}/__runtime/three-addons/libs/meshopt_decoder.module.js"),
|
|
481
|
-
loadPmtilesRuntime()
|
|
482
|
-
]).then(([, gltfLoaderModule, dracoLoaderModule, meshoptDecoderModule, pmtilesModule]) => {
|
|
483
|
-
GLTFLoader = gltfLoaderModule.GLTFLoader;
|
|
484
|
-
DRACOLoader = dracoLoaderModule.DRACOLoader;
|
|
485
|
-
MeshoptDecoder = meshoptDecoderModule.MeshoptDecoder;
|
|
486
|
-
PMTiles = pmtilesModule.PMTiles;
|
|
487
|
-
FetchSource = pmtilesModule.FetchSource;
|
|
488
|
-
landmarkDracoLoader = new DRACOLoader()
|
|
489
|
-
.setDecoderPath("${basePath}/__runtime/three-addons/libs/draco/gltf/")
|
|
490
|
-
.setDecoderConfig({type: "wasm"});
|
|
491
|
-
landmarkDracoLoader.preload();
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
return landmarkRuntimePromise;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
function loadPmtilesRuntime() {
|
|
498
|
-
if (!pmtilesRuntimePromise) {
|
|
499
|
-
pmtilesRuntimePromise = import("${basePath}/__runtime/pmtiles.js").then((pmtilesModule) => {
|
|
500
|
-
PMTiles = pmtilesModule.PMTiles;
|
|
501
|
-
FetchSource = pmtilesModule.FetchSource;
|
|
502
|
-
return pmtilesModule;
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
return pmtilesRuntimePromise;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
function waitForLandmarkManifest(promise, signal) {
|
|
509
|
-
if (!signal) return promise;
|
|
510
|
-
if (signal.aborted) return Promise.reject(new DOMException("Aborted", "AbortError"));
|
|
511
|
-
return new Promise((resolve, reject) => {
|
|
512
|
-
const abort = () => reject(new DOMException("Aborted", "AbortError"));
|
|
513
|
-
signal.addEventListener("abort", abort, {once: true});
|
|
514
|
-
promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
function loadLandmarkManifestCandidate(manifestUrl, signal) {
|
|
519
|
-
const existing = landmarkManifestPromises.get(manifestUrl);
|
|
520
|
-
if (existing) return waitForLandmarkManifest(existing, signal);
|
|
521
|
-
const resolvedUrl = new URL(manifestUrl, location.href);
|
|
522
|
-
if (
|
|
523
|
-
!["http:", "https:"].includes(resolvedUrl.protocol) ||
|
|
524
|
-
resolvedUrl.username ||
|
|
525
|
-
resolvedUrl.password
|
|
526
|
-
) throw new Error("invalid landmark manifest URL");
|
|
527
|
-
const controller = new AbortController();
|
|
528
|
-
const timeout = setTimeout(() => controller.abort(), landmarkManifestTimeoutMs);
|
|
529
|
-
const pending = (async () => {
|
|
530
|
-
try {
|
|
531
|
-
const response = await fetch(resolvedUrl, {
|
|
532
|
-
credentials: "include",
|
|
533
|
-
signal: controller.signal
|
|
534
|
-
});
|
|
535
|
-
if (!response.ok) throw new Error("HTTP " + response.status);
|
|
536
|
-
return readBoundedTileflowJsonResponse(response, landmarkManifestMaximumBytes);
|
|
537
|
-
} finally {
|
|
538
|
-
clearTimeout(timeout);
|
|
539
|
-
}
|
|
540
|
-
})();
|
|
541
|
-
landmarkManifestPromises.set(manifestUrl, pending);
|
|
542
|
-
pending.catch(() => {
|
|
543
|
-
if (landmarkManifestPromises.get(manifestUrl) === pending) {
|
|
544
|
-
landmarkManifestPromises.delete(manifestUrl);
|
|
545
|
-
}
|
|
546
|
-
});
|
|
547
|
-
return waitForLandmarkManifest(pending, signal);
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
function loadLandmarkPrerequisites(manifestUrl, signal) {
|
|
551
|
-
return Promise.all([
|
|
552
|
-
loadLandmarkRuntime(),
|
|
553
|
-
loadLandmarkManifestCandidate(manifestUrl, signal)
|
|
554
|
-
]);
|
|
555
|
-
}
|
|
556
|
-
|
|
557
404
|
function percentile(values, percentileValue) {
|
|
558
405
|
if (values.length === 0) return undefined;
|
|
559
406
|
const sorted = [...values].sort((left, right) => left - right);
|
|
@@ -1021,7 +868,10 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
1021
868
|
}
|
|
1022
869
|
|
|
1023
870
|
function updateGlobeBackdrop(map) {
|
|
1024
|
-
|
|
871
|
+
// getProjection reads the style's projection without serializing the style.
|
|
872
|
+
const projection = map.getProjection
|
|
873
|
+
? map.getProjection()?.type
|
|
874
|
+
: map.getStyle?.()?.projection?.type;
|
|
1025
875
|
const container = map.getContainer?.();
|
|
1026
876
|
if (!container || !projection) return;
|
|
1027
877
|
if (projection !== "globe") {
|
|
@@ -1036,7 +886,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
1036
886
|
// Uses the map's declared atmosphere for the preview's space and rim, so a themed globe shows
|
|
1037
887
|
// its own colours; maps without one keep the default backdrop.
|
|
1038
888
|
function applyAtmosphereBackdrop(map, container) {
|
|
1039
|
-
const atmosphere = map.
|
|
889
|
+
const atmosphere = styleReads(map).metadata()?.["tileflow:atmosphere"];
|
|
1040
890
|
const rgba = (hex, alpha) => {
|
|
1041
891
|
const match = /^#([0-9a-f]{6})$/i.exec(String(hex ?? ""));
|
|
1042
892
|
if (!match) return undefined;
|
|
@@ -1512,530 +1362,44 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
1512
1362
|
return zoom >= minzoom && zoom < maxzoom;
|
|
1513
1363
|
}
|
|
1514
1364
|
|
|
1515
|
-
|
|
1516
|
-
const manifestUrl = configLayer.metadata?.["tileflow:landmark-manifest-url"];
|
|
1517
|
-
const {minzoom: landmarkMinimumZoom, maxzoom: landmarkMaximumZoom} =
|
|
1518
|
-
styleLayerZoomRange(configLayer);
|
|
1519
|
-
const abortController = new AbortController();
|
|
1520
|
-
const loaded = new Map();
|
|
1521
|
-
const loading = new Map();
|
|
1522
|
-
const archiveReaders = new Map();
|
|
1523
|
-
const activeLandmarkIds = new Set();
|
|
1524
|
-
let cacheClock = 0;
|
|
1525
|
-
let camera;
|
|
1526
|
-
let renderer;
|
|
1527
|
-
let scene;
|
|
1528
|
-
let group;
|
|
1529
|
-
let sceneOriginMercator;
|
|
1530
|
-
let sceneElevation = 0;
|
|
1531
|
-
let manifest;
|
|
1532
|
-
let fallbackSignature;
|
|
1533
|
-
let queuedRefresh;
|
|
1534
|
-
const mapMatrix = new THREE.Matrix4();
|
|
1535
|
-
const sceneMatrix = new THREE.Matrix4();
|
|
1536
|
-
|
|
1537
|
-
function overlapsViewport(bounds, paddingRatio = 0) {
|
|
1538
|
-
const viewport = map.getBounds();
|
|
1539
|
-
const longitudePadding = Math.abs(viewport.getEast() - viewport.getWest()) * paddingRatio;
|
|
1540
|
-
const latitudePadding = Math.abs(viewport.getNorth() - viewport.getSouth()) * paddingRatio;
|
|
1541
|
-
return !(
|
|
1542
|
-
bounds[2] < viewport.getWest() - longitudePadding ||
|
|
1543
|
-
bounds[0] > viewport.getEast() + longitudePadding ||
|
|
1544
|
-
bounds[3] < viewport.getSouth() - latitudePadding ||
|
|
1545
|
-
bounds[1] > viewport.getNorth() + latitudePadding
|
|
1546
|
-
);
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
function updateLandmarkFallbackFilters() {
|
|
1550
|
-
const readyIds = [...activeLandmarkIds]
|
|
1551
|
-
.filter((id) => loaded.has(id))
|
|
1552
|
-
.sort();
|
|
1553
|
-
const signature = JSON.stringify(readyIds);
|
|
1554
|
-
if (signature === fallbackSignature) return;
|
|
1555
|
-
fallbackSignature = signature;
|
|
1556
|
-
for (const fallback of fallbackLayers) {
|
|
1557
|
-
if (!map.getLayer(fallback.id)) continue;
|
|
1558
|
-
const spatialExclusions = (fallback.spatialFallbacks ?? [])
|
|
1559
|
-
.filter((entry) => readyIds.includes(entry.id))
|
|
1560
|
-
.map((entry) => ["!", ["within", {
|
|
1561
|
-
type: "Polygon",
|
|
1562
|
-
coordinates: [[
|
|
1563
|
-
[entry.bounds[0], entry.bounds[1]],
|
|
1564
|
-
[entry.bounds[2], entry.bounds[1]],
|
|
1565
|
-
[entry.bounds[2], entry.bounds[3]],
|
|
1566
|
-
[entry.bounds[0], entry.bounds[3]],
|
|
1567
|
-
[entry.bounds[0], entry.bounds[1]]
|
|
1568
|
-
]]
|
|
1569
|
-
}]]);
|
|
1570
|
-
map.setFilter(
|
|
1571
|
-
fallback.id,
|
|
1572
|
-
readyIds.length === 0 && spatialExclusions.length === 0
|
|
1573
|
-
? (fallback.filter ?? null)
|
|
1574
|
-
: [
|
|
1575
|
-
"all",
|
|
1576
|
-
...(fallback.filter ? [fallback.filter] : []),
|
|
1577
|
-
[
|
|
1578
|
-
"!",
|
|
1579
|
-
["in", ["get", "landmark_id"], ["literal", readyIds]]
|
|
1580
|
-
],
|
|
1581
|
-
...spatialExclusions
|
|
1582
|
-
]
|
|
1583
|
-
);
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
function queueRefresh() {
|
|
1588
|
-
if (queuedRefresh !== undefined) return;
|
|
1589
|
-
queuedRefresh = setTimeout(() => {
|
|
1590
|
-
queuedRefresh = undefined;
|
|
1591
|
-
refresh();
|
|
1592
|
-
}, 100);
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
function ensureSceneOrigin(elevation) {
|
|
1596
|
-
if (sceneOriginMercator) return;
|
|
1597
|
-
sceneElevation = elevation;
|
|
1598
|
-
sceneOriginMercator = maplibregl.MercatorCoordinate.fromLngLat(
|
|
1599
|
-
map.getCenter(),
|
|
1600
|
-
elevation
|
|
1601
|
-
);
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
function localPosition(lng, lat, elevation) {
|
|
1605
|
-
const mercator = maplibregl.MercatorCoordinate.fromLngLat([lng, lat]);
|
|
1606
|
-
const mercatorPerMeter = sceneOriginMercator.meterInMercatorCoordinateUnits();
|
|
1607
|
-
return new THREE.Vector3(
|
|
1608
|
-
(mercator.x - sceneOriginMercator.x) / mercatorPerMeter,
|
|
1609
|
-
elevation - sceneElevation,
|
|
1610
|
-
(sceneOriginMercator.y - mercator.y) / mercatorPerMeter
|
|
1611
|
-
);
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
function disposeModel(model) {
|
|
1615
|
-
const disposed = new Set();
|
|
1616
|
-
group?.remove(model);
|
|
1617
|
-
for (const texture of model.userData?.tileflowDetachedTextures ?? []) {
|
|
1618
|
-
if (!texture?.isTexture || disposed.has(texture)) continue;
|
|
1619
|
-
disposed.add(texture);
|
|
1620
|
-
texture.dispose();
|
|
1621
|
-
}
|
|
1622
|
-
for (const material of model.userData?.tileflowDetachedMaterials ?? []) {
|
|
1623
|
-
if (!material || disposed.has(material)) continue;
|
|
1624
|
-
for (const value of Object.values(material)) {
|
|
1625
|
-
if (value?.isTexture && !disposed.has(value)) {
|
|
1626
|
-
disposed.add(value);
|
|
1627
|
-
value.dispose();
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1630
|
-
disposed.add(material);
|
|
1631
|
-
material.dispose?.();
|
|
1632
|
-
}
|
|
1633
|
-
model.traverse((object) => {
|
|
1634
|
-
if (object.geometry && !disposed.has(object.geometry)) {
|
|
1635
|
-
disposed.add(object.geometry);
|
|
1636
|
-
object.geometry.dispose?.();
|
|
1637
|
-
}
|
|
1638
|
-
const materials = Array.isArray(object.material)
|
|
1639
|
-
? object.material
|
|
1640
|
-
: [object.material];
|
|
1641
|
-
for (const material of materials) {
|
|
1642
|
-
if (!material || disposed.has(material)) continue;
|
|
1643
|
-
for (const value of Object.values(material)) {
|
|
1644
|
-
if (value?.isTexture && !disposed.has(value)) {
|
|
1645
|
-
disposed.add(value);
|
|
1646
|
-
value.dispose();
|
|
1647
|
-
}
|
|
1648
|
-
}
|
|
1649
|
-
disposed.add(material);
|
|
1650
|
-
material.dispose?.();
|
|
1651
|
-
}
|
|
1652
|
-
});
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
|
-
function harmonizeLandmarkModel(model) {
|
|
1656
|
-
const detachedMaterials = new Set();
|
|
1657
|
-
const uniformMaterial = new THREE.MeshStandardMaterial({
|
|
1658
|
-
alphaTest: 0,
|
|
1659
|
-
color: "#EEE4D4",
|
|
1660
|
-
depthWrite: true,
|
|
1661
|
-
metalness: 0,
|
|
1662
|
-
opacity: 1,
|
|
1663
|
-
roughness: 0.9,
|
|
1664
|
-
side: THREE.DoubleSide,
|
|
1665
|
-
transparent: false
|
|
1666
|
-
});
|
|
1667
|
-
model.traverse((object) => {
|
|
1668
|
-
if (!object.isMesh) return;
|
|
1669
|
-
const originalMaterials = Array.isArray(object.material)
|
|
1670
|
-
? object.material
|
|
1671
|
-
: [object.material];
|
|
1672
|
-
for (const material of originalMaterials) {
|
|
1673
|
-
if (material) detachedMaterials.add(material);
|
|
1674
|
-
}
|
|
1675
|
-
object.material = Array.isArray(object.material)
|
|
1676
|
-
? object.material.map(() => uniformMaterial)
|
|
1677
|
-
: uniformMaterial;
|
|
1678
|
-
});
|
|
1679
|
-
model.userData.tileflowDetachedMaterials = [...detachedMaterials];
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
function normalizeLandmarkAxes(sourceModel, axisConvention) {
|
|
1683
|
-
if (axisConvention === "EUN_Y_UP") return sourceModel;
|
|
1684
|
-
if (axisConvention === "ENU_Z_UP") {
|
|
1685
|
-
sourceModel.rotateX(-Math.PI / 2);
|
|
1686
|
-
}
|
|
1687
|
-
const normalizedModel = new THREE.Group();
|
|
1688
|
-
normalizedModel.scale.z = -1;
|
|
1689
|
-
normalizedModel.add(sourceModel);
|
|
1690
|
-
return normalizedModel;
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
function unloadLandmark(id) {
|
|
1694
|
-
const entry = loaded.get(id);
|
|
1695
|
-
if (!entry) return;
|
|
1696
|
-
disposeModel(entry.model);
|
|
1697
|
-
loaded.delete(id);
|
|
1698
|
-
landmarkState.loaded = [...loaded.keys()];
|
|
1699
|
-
fallbackSignature = undefined;
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
|
-
function enforceCacheLimit() {
|
|
1703
|
-
if (!manifest || loaded.size <= manifest.maximumCachedModels) return;
|
|
1704
|
-
const evictionCandidates = [...loaded.entries()]
|
|
1705
|
-
.filter(([id]) => !activeLandmarkIds.has(id))
|
|
1706
|
-
.sort(
|
|
1707
|
-
([leftId, left], [rightId, right]) =>
|
|
1708
|
-
left.lastUsed - right.lastUsed || leftId.localeCompare(rightId)
|
|
1709
|
-
);
|
|
1710
|
-
while (
|
|
1711
|
-
loaded.size > manifest.maximumCachedModels &&
|
|
1712
|
-
evictionCandidates.length > 0
|
|
1713
|
-
) {
|
|
1714
|
-
unloadLandmark(evictionCandidates.shift()[0]);
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
function landmarkModelAtZoom(landmark, zoom) {
|
|
1719
|
-
let selected = landmark.models[0];
|
|
1720
|
-
for (const candidate of landmark.models) {
|
|
1721
|
-
if (candidate.minzoom > zoom) break;
|
|
1722
|
-
selected = candidate;
|
|
1723
|
-
}
|
|
1724
|
-
return selected;
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
async function sha256Hex(bytes) {
|
|
1728
|
-
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
1729
|
-
return [...new Uint8Array(digest)]
|
|
1730
|
-
.map((value) => value.toString(16).padStart(2, "0"))
|
|
1731
|
-
.join("");
|
|
1732
|
-
}
|
|
1733
|
-
|
|
1734
|
-
function archiveReader(archive) {
|
|
1735
|
-
let reader = archiveReaders.get(archive.url);
|
|
1736
|
-
if (!reader) {
|
|
1737
|
-
reader = new PMTiles(
|
|
1738
|
-
new FetchSource(archive.url, new Headers(), "include")
|
|
1739
|
-
);
|
|
1740
|
-
archiveReaders.set(archive.url, reader);
|
|
1741
|
-
}
|
|
1742
|
-
return reader;
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
async function loadLandmarkBytes(modelDefinition) {
|
|
1746
|
-
const tile = await archiveReader(modelDefinition.archive).getZxy(
|
|
1747
|
-
modelDefinition.z,
|
|
1748
|
-
modelDefinition.x,
|
|
1749
|
-
modelDefinition.y,
|
|
1750
|
-
abortController.signal
|
|
1751
|
-
);
|
|
1752
|
-
if (!tile) throw new Error("landmark entry is missing from PMTiles");
|
|
1753
|
-
if (tile.data.byteLength !== modelDefinition.bytes) {
|
|
1754
|
-
throw new Error("landmark entry has an unexpected byte length");
|
|
1755
|
-
}
|
|
1756
|
-
if ((await sha256Hex(tile.data)) !== modelDefinition.sha256) {
|
|
1757
|
-
throw new Error("landmark entry has an unexpected SHA-256");
|
|
1758
|
-
}
|
|
1759
|
-
return tile.data;
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
function loadLandmark(landmark, requestedModel) {
|
|
1763
|
-
const cached = loaded.get(landmark.id);
|
|
1764
|
-
const nextModel = cached ? requestedModel : landmark.models[0];
|
|
1765
|
-
if (cached?.modelKey === nextModel.key) {
|
|
1766
|
-
cached.lastUsed = ++cacheClock;
|
|
1767
|
-
cached.model.visible = activeLandmarkIds.has(landmark.id);
|
|
1768
|
-
return;
|
|
1769
|
-
}
|
|
1770
|
-
if (cached) {
|
|
1771
|
-
// Keep the previous LOD visible until its replacement has loaded.
|
|
1772
|
-
cached.lastUsed = ++cacheClock;
|
|
1773
|
-
cached.model.visible = activeLandmarkIds.has(landmark.id);
|
|
1774
|
-
}
|
|
1775
|
-
if (loading.has(landmark.id)) return;
|
|
1776
|
-
loading.set(landmark.id, nextModel.key);
|
|
1777
|
-
landmarkState.loading = [...loading.keys()];
|
|
1778
|
-
const loader = new GLTFLoader();
|
|
1779
|
-
loader.setMeshoptDecoder(MeshoptDecoder);
|
|
1780
|
-
loader.setDRACOLoader(landmarkDracoLoader);
|
|
1781
|
-
loader.setWithCredentials(true);
|
|
1782
|
-
const fail = (error) => {
|
|
1783
|
-
if (loading.get(landmark.id) !== nextModel.key) return;
|
|
1784
|
-
loading.delete(landmark.id);
|
|
1785
|
-
landmarkState.loading = [...loading.keys()];
|
|
1786
|
-
landmarkState.errors = [
|
|
1787
|
-
...landmarkState.errors.filter((item) => item.id !== landmark.id),
|
|
1788
|
-
{id: landmark.id, message: String(error)}
|
|
1789
|
-
];
|
|
1790
|
-
console.warn("Tileflow landmark failed to load", landmark.id, error);
|
|
1791
|
-
};
|
|
1792
|
-
void loadLandmarkBytes(nextModel).then((bytes) => {
|
|
1793
|
-
if (loading.get(landmark.id) !== nextModel.key) return;
|
|
1794
|
-
loader.parse(
|
|
1795
|
-
bytes,
|
|
1796
|
-
new URL(".", nextModel.archive.url).toString(),
|
|
1797
|
-
(gltf) => {
|
|
1798
|
-
if (loading.get(landmark.id) !== nextModel.key) {
|
|
1799
|
-
disposeModel(gltf.scene);
|
|
1800
|
-
return;
|
|
1801
|
-
}
|
|
1802
|
-
loading.delete(landmark.id);
|
|
1803
|
-
landmarkState.loading = [...loading.keys()];
|
|
1804
|
-
if (abortController.signal.aborted) {
|
|
1805
|
-
disposeModel(gltf.scene);
|
|
1806
|
-
return;
|
|
1807
|
-
}
|
|
1808
|
-
const elevation = map.queryTerrainElevation(landmark.center) ?? 0;
|
|
1809
|
-
ensureSceneOrigin(elevation);
|
|
1810
|
-
const model = normalizeLandmarkAxes(
|
|
1811
|
-
gltf.scene,
|
|
1812
|
-
nextModel.axisConvention
|
|
1813
|
-
);
|
|
1814
|
-
model.name = "tileflow-landmark-" + landmark.id;
|
|
1815
|
-
harmonizeLandmarkModel(model);
|
|
1816
|
-
model.position.copy(
|
|
1817
|
-
localPosition(landmark.center[0], landmark.center[1], elevation)
|
|
1818
|
-
);
|
|
1819
|
-
model.traverse((object) => {
|
|
1820
|
-
if (!object.isMesh) return;
|
|
1821
|
-
object.frustumCulled = true;
|
|
1822
|
-
object.castShadow = false;
|
|
1823
|
-
object.receiveShadow = false;
|
|
1824
|
-
});
|
|
1825
|
-
model.visible = activeLandmarkIds.has(landmark.id);
|
|
1826
|
-
if (loaded.has(landmark.id)) unloadLandmark(landmark.id);
|
|
1827
|
-
loaded.set(landmark.id, {
|
|
1828
|
-
lastUsed: ++cacheClock,
|
|
1829
|
-
model,
|
|
1830
|
-
modelKey: nextModel.key
|
|
1831
|
-
});
|
|
1832
|
-
landmarkState.errors = landmarkState.errors.filter(
|
|
1833
|
-
(item) => item.id !== landmark.id
|
|
1834
|
-
);
|
|
1835
|
-
landmarkState.loaded = [...loaded.keys()];
|
|
1836
|
-
group.add(model);
|
|
1837
|
-
enforceCacheLimit();
|
|
1838
|
-
fallbackSignature = undefined;
|
|
1839
|
-
refresh();
|
|
1840
|
-
},
|
|
1841
|
-
fail
|
|
1842
|
-
);
|
|
1843
|
-
}).catch(fail);
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
function refresh() {
|
|
1847
|
-
if (
|
|
1848
|
-
!threeDimensionalEnabled ||
|
|
1849
|
-
!manifest ||
|
|
1850
|
-
!styleLayerIsVisibleAtZoom(configLayer, map.getZoom())
|
|
1851
|
-
) {
|
|
1852
|
-
activeLandmarkIds.clear();
|
|
1853
|
-
landmarkState.active = [];
|
|
1854
|
-
for (const entry of loaded.values()) entry.model.visible = false;
|
|
1855
|
-
updateLandmarkFallbackFilters();
|
|
1856
|
-
return;
|
|
1857
|
-
}
|
|
1858
|
-
const zoom = map.getZoom();
|
|
1859
|
-
const visible = manifest.landmarks
|
|
1860
|
-
.filter((landmark) => overlapsViewport(landmark.bounds))
|
|
1861
|
-
.sort(
|
|
1862
|
-
(left, right) =>
|
|
1863
|
-
right.priority - left.priority || (left.id < right.id ? -1 : left.id > right.id ? 1 : 0)
|
|
1864
|
-
)
|
|
1865
|
-
.slice(0, manifest.maximumVisibleModels);
|
|
1866
|
-
activeLandmarkIds.clear();
|
|
1867
|
-
for (const landmark of visible) activeLandmarkIds.add(landmark.id);
|
|
1868
|
-
landmarkState.active = visible.map((landmark) => landmark.id);
|
|
1869
|
-
for (const [id, entry] of loaded) {
|
|
1870
|
-
entry.model.visible = activeLandmarkIds.has(id);
|
|
1871
|
-
}
|
|
1872
|
-
for (const landmark of visible) {
|
|
1873
|
-
loadLandmark(landmark, landmarkModelAtZoom(landmark, zoom));
|
|
1874
|
-
}
|
|
1875
|
-
const nearbyCacheSlots = Math.max(
|
|
1876
|
-
0,
|
|
1877
|
-
manifest.maximumCachedModels - visible.length
|
|
1878
|
-
);
|
|
1879
|
-
const nearby = manifest.landmarks
|
|
1880
|
-
.filter(
|
|
1881
|
-
(landmark) =>
|
|
1882
|
-
!activeLandmarkIds.has(landmark.id) &&
|
|
1883
|
-
overlapsViewport(landmark.bounds, 0.35)
|
|
1884
|
-
)
|
|
1885
|
-
.sort(
|
|
1886
|
-
(left, right) =>
|
|
1887
|
-
right.priority - left.priority || (left.id < right.id ? -1 : left.id > right.id ? 1 : 0)
|
|
1888
|
-
)
|
|
1889
|
-
.slice(0, nearbyCacheSlots);
|
|
1890
|
-
for (const landmark of nearby) {
|
|
1891
|
-
loadLandmark(landmark, landmark.models[0]);
|
|
1892
|
-
}
|
|
1893
|
-
enforceCacheLimit();
|
|
1894
|
-
updateLandmarkFallbackFilters();
|
|
1895
|
-
map.triggerRepaint();
|
|
1896
|
-
}
|
|
1897
|
-
|
|
1898
|
-
function normalizeManifest(candidate) {
|
|
1899
|
-
return normalizeTileflowLandmarkManifest(candidate, manifestUrl);
|
|
1900
|
-
}
|
|
1901
|
-
|
|
1902
|
-
async function loadManifest() {
|
|
1903
|
-
try {
|
|
1904
|
-
const candidate = await loadLandmarkManifestCandidate(
|
|
1905
|
-
manifestUrl,
|
|
1906
|
-
abortController.signal
|
|
1907
|
-
);
|
|
1908
|
-
if (abortController.signal.aborted) return;
|
|
1909
|
-
manifest = normalizeManifest(candidate);
|
|
1910
|
-
landmarkState.manifestId = candidate.id;
|
|
1911
|
-
landmarkState.cacheLimit = manifest.maximumCachedModels;
|
|
1912
|
-
refresh();
|
|
1913
|
-
} catch (error) {
|
|
1914
|
-
if (error?.name !== "AbortError") {
|
|
1915
|
-
console.warn("Tileflow landmark manifest failed to load", error);
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
}
|
|
1919
|
-
|
|
1920
|
-
return {
|
|
1921
|
-
id: "tileflow-landmarks-3d",
|
|
1922
|
-
type: "custom",
|
|
1923
|
-
renderingMode: "3d",
|
|
1924
|
-
minzoom: landmarkMinimumZoom,
|
|
1925
|
-
...(Number.isFinite(landmarkMaximumZoom)
|
|
1926
|
-
? {maxzoom: landmarkMaximumZoom}
|
|
1927
|
-
: {}),
|
|
1928
|
-
metadata: {"tileflow:3d-toggle": "landmark"},
|
|
1929
|
-
onAdd(_map, gl) {
|
|
1930
|
-
camera = new THREE.Camera();
|
|
1931
|
-
scene = new THREE.Scene();
|
|
1932
|
-
scene.rotateX(Math.PI / 2);
|
|
1933
|
-
scene.scale.multiply(new THREE.Vector3(1, 1, -1));
|
|
1934
|
-
group = new THREE.Group();
|
|
1935
|
-
scene.add(group);
|
|
1936
|
-
scene.add(new THREE.AmbientLight(0xffffff, 2.2));
|
|
1937
|
-
scene.add(new THREE.HemisphereLight(0xfff8e8, 0xd8cfbd, 0.8));
|
|
1938
|
-
const sun = new THREE.DirectionalLight(0xffffff, 0.45);
|
|
1939
|
-
sun.position.set(-120, 220, 100);
|
|
1940
|
-
scene.add(sun);
|
|
1941
|
-
renderer = new THREE.WebGLRenderer({
|
|
1942
|
-
canvas: map.getCanvas(),
|
|
1943
|
-
context: gl
|
|
1944
|
-
});
|
|
1945
|
-
renderer.autoClear = false;
|
|
1946
|
-
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
|
1947
|
-
map.on("move", queueRefresh);
|
|
1948
|
-
map.on("moveend", refresh);
|
|
1949
|
-
map.on("tileflow:3d-toggle", refresh);
|
|
1950
|
-
void loadManifest();
|
|
1951
|
-
},
|
|
1952
|
-
render(_gl, options) {
|
|
1953
|
-
if (
|
|
1954
|
-
!threeDimensionalEnabled ||
|
|
1955
|
-
!sceneOriginMercator ||
|
|
1956
|
-
group.children.length === 0
|
|
1957
|
-
) return;
|
|
1958
|
-
const meter = sceneOriginMercator.meterInMercatorCoordinateUnits();
|
|
1959
|
-
mapMatrix.fromArray(options.defaultProjectionData.mainMatrix);
|
|
1960
|
-
sceneMatrix
|
|
1961
|
-
.makeTranslation(
|
|
1962
|
-
sceneOriginMercator.x,
|
|
1963
|
-
sceneOriginMercator.y,
|
|
1964
|
-
sceneOriginMercator.z
|
|
1965
|
-
)
|
|
1966
|
-
.scale(new THREE.Vector3(meter, -meter, meter));
|
|
1967
|
-
camera.projectionMatrix.copy(mapMatrix).multiply(sceneMatrix);
|
|
1968
|
-
renderer.resetState();
|
|
1969
|
-
renderer.render(scene, camera);
|
|
1970
|
-
},
|
|
1971
|
-
onRemove() {
|
|
1972
|
-
abortController.abort();
|
|
1973
|
-
if (queuedRefresh !== undefined) clearTimeout(queuedRefresh);
|
|
1974
|
-
map.off("move", queueRefresh);
|
|
1975
|
-
map.off("moveend", refresh);
|
|
1976
|
-
map.off("tileflow:3d-toggle", refresh);
|
|
1977
|
-
for (const entry of loaded.values()) disposeModel(entry.model);
|
|
1978
|
-
loaded.clear();
|
|
1979
|
-
for (const fallback of fallbackLayers) {
|
|
1980
|
-
if (map.getLayer(fallback.id)) {
|
|
1981
|
-
map.setFilter(fallback.id, fallback.filter ?? null);
|
|
1982
|
-
}
|
|
1983
|
-
}
|
|
1984
|
-
renderer?.dispose();
|
|
1985
|
-
}
|
|
1986
|
-
};
|
|
1987
|
-
}
|
|
1988
|
-
|
|
1989
|
-
function addLandmarkLayerIfConfigured(map, styleLayers) {
|
|
1990
|
-
const configLayer = styleLayers.find(
|
|
1991
|
-
(layer) => layer.metadata?.["tileflow:landmark-manifest-url"]
|
|
1992
|
-
);
|
|
1993
|
-
if (!configLayer || map.getLayer("tileflow-landmarks-3d")) return;
|
|
1994
|
-
const fallbackLayers = styleLayers
|
|
1995
|
-
.filter((layer) => layer.metadata?.["tileflow:landmark-fallback"] === true)
|
|
1996
|
-
.map((layer) => ({
|
|
1997
|
-
filter: layer.filter,
|
|
1998
|
-
id: layer.id,
|
|
1999
|
-
spatialFallbacks:
|
|
2000
|
-
layer.metadata?.["tileflow:landmark-spatial-fallbacks"] ?? []
|
|
2001
|
-
}));
|
|
2002
|
-
// Landmark custom layers use Mercator world coordinates.
|
|
2003
|
-
if (map.getProjection().type !== "mercator") {
|
|
2004
|
-
map.setProjection({type: "mercator"});
|
|
2005
|
-
}
|
|
2006
|
-
const firstForegroundLabel = styleLayers.find(
|
|
2007
|
-
(layer) =>
|
|
2008
|
-
(layer.id.startsWith("tileflow-label-") &&
|
|
2009
|
-
!layer.id.startsWith("tileflow-label-road-")) ||
|
|
2010
|
-
layer.id === "tileflow-poi" ||
|
|
2011
|
-
layer.id.startsWith("tileflow-poi-")
|
|
2012
|
-
)?.id;
|
|
2013
|
-
const treeLayer = map.getLayer("tileflow-vegetation-trees")
|
|
2014
|
-
? "tileflow-vegetation-trees"
|
|
2015
|
-
: firstForegroundLabel;
|
|
2016
|
-
map.addLayer(createLandmarkLayer(map, configLayer, fallbackLayers), treeLayer);
|
|
2017
|
-
}
|
|
2018
|
-
|
|
2019
|
-
// Core's 3D layers, as an application mounts them: the map's style describes how its
|
|
1365
|
+
// Core's 3D scene, as an application mounts it: the map's style describes how its
|
|
2020
1366
|
// buildings, trees and street lights look, and the controls only show and hide them. The
|
|
2021
1367
|
// vegetation control hides all trees when off. The preview shows one theme at a
|
|
2022
1368
|
// time, so the style on screen holds the sun and the design.
|
|
2023
|
-
const coreScene = {
|
|
1369
|
+
const coreScene = {scene: undefined, shown: undefined, buildingsShown: undefined, animation: "0"};
|
|
2024
1370
|
const runtimeLayerVisibility = {};
|
|
2025
1371
|
globalThis.__tileflowPreviewScene = coreScene;
|
|
2026
1372
|
globalThis.__tileflowRuntimeVisibility = (id, visible) => {
|
|
2027
1373
|
if (!['tileflow-buildings-3d','tileflow-trees-3d','tileflow-street-lights'].includes(id)) return undefined;
|
|
2028
|
-
|
|
1374
|
+
const previewMap = globalThis.__tileflowPreviewMap;
|
|
1375
|
+
if (typeof visible === 'boolean') {
|
|
1376
|
+
runtimeLayerVisibility[id] = visible;
|
|
1377
|
+
if (previewMap) { mountCoreScene(previewMap); previewMap.triggerRepaint(); }
|
|
1378
|
+
}
|
|
2029
1379
|
return runtimeLayerVisibility[id] !== false;
|
|
2030
1380
|
};
|
|
2031
1381
|
// Explicit observation capability: controllers stay private to the preview and expose
|
|
2032
1382
|
// only their public state/statistics to the parent inspector.
|
|
2033
|
-
globalThis.__tileflowInspectModels = () => [...(coreScene.buildings?.inspectModels?.() || []), ...(coreScene.
|
|
2034
|
-
globalThis.__tileflowDescribeRuntimeLayers = () =>
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
1383
|
+
globalThis.__tileflowInspectModels = () => [...(coreScene.scene?.buildings?.inspectModels?.() || []), ...(coreScene.scene?.vegetation?.inspectModels?.() || [])];
|
|
1384
|
+
globalThis.__tileflowDescribeRuntimeLayers = () => {
|
|
1385
|
+
const {buildings, vegetation, streetLights} = coreScene.scene ?? {};
|
|
1386
|
+
return {
|
|
1387
|
+
...(buildings ? {"tileflow-buildings-3d": {controller:"Buildings 3D", state:{visible:runtimeLayerVisibility["tileflow-buildings-3d"] !== false, shown:coreScene.buildingsShown, scale:buildings.scale}, stats:buildings.stats}} : {}),
|
|
1388
|
+
...(vegetation ? {"tileflow-trees-3d": {controller:"Vegetation", state:{visible:runtimeLayerVisibility["tileflow-trees-3d"] !== false, representation:vegetation.representation}, stats:vegetation.stats}} : {}),
|
|
1389
|
+
...(streetLights ? {"tileflow-street-lights": {controller:"Street lights", state:{visible:runtimeLayerVisibility["tileflow-street-lights"] !== false, mounted:true}, stats:streetLights.stats}} : {})
|
|
1390
|
+
};
|
|
1391
|
+
};
|
|
1392
|
+
const createPreviewStyleReads = ${createPreviewStyleReads.toString()};
|
|
1393
|
+
const previewStyleReads = new WeakMap();
|
|
1394
|
+
// Style reads for code that runs on every camera move or style event.
|
|
1395
|
+
function styleReads(map) {
|
|
1396
|
+
let reads = previewStyleReads.get(map);
|
|
1397
|
+
if (!reads) {
|
|
1398
|
+
reads = createPreviewStyleReads(map);
|
|
1399
|
+
previewStyleReads.set(map, reads);
|
|
1400
|
+
}
|
|
1401
|
+
return reads;
|
|
1402
|
+
}
|
|
2039
1403
|
const createPreviewSceneThemes = ${createPreviewSceneThemes.toString()};
|
|
2040
1404
|
const coreSceneThemeControllers = new WeakMap();
|
|
2041
1405
|
function coreSceneThemes(map) {
|
|
@@ -2052,26 +1416,34 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2052
1416
|
if (mountingCoreScene) return;
|
|
2053
1417
|
mountingCoreScene = true;
|
|
2054
1418
|
try {
|
|
2055
|
-
const metadata = map.
|
|
2056
|
-
const
|
|
2057
|
-
if (!coreScene.
|
|
2058
|
-
coreScene.buildings = createTileflowBuildings3D({map, controller});
|
|
2059
|
-
}
|
|
2060
|
-
if (!coreScene.trees) {
|
|
1419
|
+
const metadata = styleReads(map).metadata() ?? {};
|
|
1420
|
+
const treesShown = () => treesEnabled && runtimeLayerVisibility["tileflow-trees-3d"] !== false;
|
|
1421
|
+
if (!coreScene.scene) {
|
|
2061
1422
|
if (!previewSearchParameters.has("trees3d")) {
|
|
2062
1423
|
treesEnabled = metadata["tileflow:vegetation-display"]?.visible ?? true;
|
|
2063
1424
|
}
|
|
2064
|
-
|
|
2065
|
-
|
|
1425
|
+
// The scene mounts what the style declares; street lights belong to the 3D view.
|
|
1426
|
+
coreScene.scene = createTileflowMap3D({map, controller: coreSceneThemes(map),
|
|
1427
|
+
viewMode: threeDimensionalEnabled ? "3d" : "2d", vegetation: treesShown(),
|
|
1428
|
+
transformRequest: previewTransformRequest});
|
|
1429
|
+
}
|
|
1430
|
+
const scene = coreScene.scene;
|
|
1431
|
+
scene.setVegetation(treesShown());
|
|
1432
|
+
const instant = {transition: {duration: 0}};
|
|
1433
|
+
const animation = previewSearchParameters.get('animation') || '0';
|
|
1434
|
+
const duration = animation === 'fade' ? 800 : Math.min(3000, Math.max(0, Number(animation) || 0));
|
|
1435
|
+
// Only a change of the controls moves the layers, so a style event does not restart them.
|
|
1436
|
+
if (coreScene.shown !== threeDimensionalEnabled || coreScene.animation !== animation) {
|
|
1437
|
+
coreScene.shown = threeDimensionalEnabled;
|
|
1438
|
+
coreScene.buildingsShown = threeDimensionalEnabled;
|
|
1439
|
+
coreScene.animation = animation;
|
|
1440
|
+
void (threeDimensionalEnabled ? scene.show({transition: {duration, mode: animation === "fade" ? "fade" : "grow"}}) : scene.hide(instant));
|
|
2066
1441
|
}
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
} else if (coreScene.lights && (!threeDimensionalEnabled || !metadata["tileflow:street-lights"])) {
|
|
2073
|
-
coreScene.lights.dispose();
|
|
2074
|
-
coreScene.lights = undefined;
|
|
1442
|
+
// The inspector can hide the buildings alone.
|
|
1443
|
+
const showBuildings = threeDimensionalEnabled && runtimeLayerVisibility["tileflow-buildings-3d"] !== false;
|
|
1444
|
+
if (scene.buildings && coreScene.buildingsShown !== showBuildings) {
|
|
1445
|
+
coreScene.buildingsShown = showBuildings;
|
|
1446
|
+
void (showBuildings ? scene.buildings.show(instant) : scene.buildings.hide(instant));
|
|
2075
1447
|
}
|
|
2076
1448
|
const lightLayer = map.getLayer("tileflow-street-lights");
|
|
2077
1449
|
const lightRenderer = lightLayer?.implementation || lightLayer;
|
|
@@ -2082,16 +1454,6 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2082
1454
|
};
|
|
2083
1455
|
lightRenderer.__previewVisibility = true;
|
|
2084
1456
|
}
|
|
2085
|
-
const showBuildings = threeDimensionalEnabled && runtimeLayerVisibility["tileflow-buildings-3d"] !== false;
|
|
2086
|
-
const instant = {transition: {duration: 0}};
|
|
2087
|
-
const animation = previewSearchParameters.get('animation') || '0';
|
|
2088
|
-
const duration = animation === 'fade' ? 800 : Math.min(3000, Math.max(0, Number(animation) || 0));
|
|
2089
|
-
// Only a change of the controls moves the layers, so a style event does not restart them.
|
|
2090
|
-
if (coreScene.buildings && (coreScene.buildingsShown !== showBuildings || coreScene.animation !== animation)) {
|
|
2091
|
-
coreScene.buildingsShown = showBuildings;
|
|
2092
|
-
coreScene.animation = animation;
|
|
2093
|
-
void (showBuildings ? coreScene.buildings.show({transition: {duration, mode: animation === "fade" ? "fade" : "grow"}}) : coreScene.buildings.hide(instant));
|
|
2094
|
-
}
|
|
2095
1457
|
} finally {mountingCoreScene = false;}
|
|
2096
1458
|
}
|
|
2097
1459
|
|
|
@@ -2126,7 +1488,6 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2126
1488
|
if (typeof enabled !== "boolean" || enabled === this.enabled) return;
|
|
2127
1489
|
this.enabled = enabled;
|
|
2128
1490
|
threeDimensionalEnabled = this.enabled;
|
|
2129
|
-
landmarkState.enabled = this.enabled;
|
|
2130
1491
|
this.update();
|
|
2131
1492
|
writeToggleStateToUrl();
|
|
2132
1493
|
this.map.fire("tileflow:3d-toggle", {enabled: this.enabled});
|
|
@@ -2135,11 +1496,12 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2135
1496
|
}
|
|
2136
1497
|
|
|
2137
1498
|
update() {
|
|
2138
|
-
|
|
1499
|
+
const reads = styleReads(this.map);
|
|
1500
|
+
for (const layer of reads.layers()) {
|
|
2139
1501
|
const toggle = layer.metadata?.["tileflow:3d-toggle"];
|
|
2140
1502
|
if (toggle !== "building" && toggle !== "landmark") continue;
|
|
2141
1503
|
// Core's 3D buildings stand in for the compiled extrusion where the style describes them.
|
|
2142
|
-
const replaced = toggle === "building" &&
|
|
1504
|
+
const replaced = toggle === "building" && reads.metadata()?.["tileflow:buildings"];
|
|
2143
1505
|
const visibility = this.enabled && !replaced ? "visible" : "none";
|
|
2144
1506
|
if (this.map.getLayoutProperty(layer.id, "visibility") !== visibility) {
|
|
2145
1507
|
this.map.setLayoutProperty(layer.id, "visibility", visibility);
|
|
@@ -2297,9 +1659,10 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2297
1659
|
...visibleLayerGroups,
|
|
2298
1660
|
].join(",");
|
|
2299
1661
|
}
|
|
2300
|
-
const
|
|
2301
|
-
|
|
2302
|
-
|
|
1662
|
+
const reads = styleReads(map);
|
|
1663
|
+
const vegetationLayers = reads.metadata()?.["tileflow:vegetation-display"]?.flatLayers;
|
|
1664
|
+
for (const layer of reads.layers()) {
|
|
1665
|
+
if (layer.type === "symbol" && map.getLayoutProperty(layer.id, "text-field") !== undefined) {
|
|
2303
1666
|
const hideLabels = !visibleLayerGroups.has("labels");
|
|
2304
1667
|
if (hideLabels && !defaultLayerTextField.has(layer.id)) {
|
|
2305
1668
|
defaultLayerTextField.set(
|
|
@@ -2333,7 +1696,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2333
1696
|
const threeDimensionalToggle = layer.metadata?.["tileflow:3d-toggle"];
|
|
2334
1697
|
const authoredVisibility = defaultLayerVisibility.get(layer.id);
|
|
2335
1698
|
if (!hideLayer) defaultLayerVisibility.delete(layer.id);
|
|
2336
|
-
const styleMetadata =
|
|
1699
|
+
const styleMetadata = reads.metadata() ?? {};
|
|
2337
1700
|
// Core's 3D buildings and trees stand in for compiled extrusions and flat trees.
|
|
2338
1701
|
const replacedByCore =
|
|
2339
1702
|
threeDimensionalToggle === "building" && styleMetadata["tileflow:buildings"];
|
|
@@ -2377,14 +1740,19 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2377
1740
|
registerTileflowContourProtocol({addProtocol: maplibregl.addProtocol});
|
|
2378
1741
|
registerTileflowPmtilesProtocol({addProtocol: maplibregl.addProtocol});
|
|
2379
1742
|
const map = new maplibregl.Map({
|
|
1743
|
+
canvasContextAttributes: tileflowCanvasContextAttributes({antialias: mapAntialias}),
|
|
2380
1744
|
collectResourceTiming: mapBenchmarkEnabled || Boolean(devtoolsObserver),
|
|
2381
1745
|
container: "map",
|
|
1746
|
+
maxPitch: tileflowMaxPitch,
|
|
2382
1747
|
style: styleUrl,
|
|
2383
1748
|
transformRequest: previewTransformRequest,
|
|
2384
1749
|
...resolveInitialMapOptions(previewMapOptions)
|
|
2385
1750
|
});
|
|
2386
1751
|
globalThis.__tileflowPreviewMap = map;
|
|
2387
1752
|
attachTileflowNotices(map);
|
|
1753
|
+
attachTileflowNauticalLights(map);
|
|
1754
|
+
attachTileflowSky(map);
|
|
1755
|
+
attachTileflowSkyHaze(map);
|
|
2388
1756
|
devtoolsObserver?.attach(map);
|
|
2389
1757
|
installMapBenchmark(map);
|
|
2390
1758
|
if (window.parent === window) map.addControl(new maplibregl.NavigationControl(), "top-right");
|
|
@@ -2408,45 +1776,17 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2408
1776
|
let ensuringThreeDimensionalLayers;
|
|
2409
1777
|
const ensureThreeDimensionalLayers = () => {
|
|
2410
1778
|
if (ensuringThreeDimensionalLayers) return ensuringThreeDimensionalLayers;
|
|
2411
|
-
const
|
|
2412
|
-
const landmarkConfigLayer = currentStyleLayers.find(
|
|
2413
|
-
(layer) => layer.metadata?.["tileflow:landmark-manifest-url"]
|
|
2414
|
-
);
|
|
2415
|
-
const buildingWireframeConfigLayer = currentStyleLayers.find(
|
|
1779
|
+
const buildingWireframeConfigLayer = styleReads(map).layers().find(
|
|
2416
1780
|
(layer) => layer.metadata?.["tileflow:building-wireframe"] === true
|
|
2417
1781
|
);
|
|
2418
|
-
const landmarkManifestUrl =
|
|
2419
|
-
landmarkConfigLayer?.metadata?.["tileflow:landmark-manifest-url"];
|
|
2420
|
-
const landmarkPreloadZoom = landmarkConfigLayer
|
|
2421
|
-
? Math.max(0, styleLayerZoomRange(landmarkConfigLayer).minzoom - 1)
|
|
2422
|
-
: Infinity;
|
|
2423
|
-
const shouldPreloadLandmarks =
|
|
2424
|
-
landmarkManifestUrl &&
|
|
2425
|
-
threeDimensionalEnabled &&
|
|
2426
|
-
map.getZoom() >= landmarkPreloadZoom;
|
|
2427
|
-
const needsLandmarks =
|
|
2428
|
-
landmarkConfigLayer &&
|
|
2429
|
-
threeDimensionalEnabled &&
|
|
2430
|
-
styleLayerIsVisibleAtZoom(landmarkConfigLayer, map.getZoom()) &&
|
|
2431
|
-
!map.getLayer("tileflow-landmarks-3d");
|
|
2432
1782
|
const needsBuildingWireframe =
|
|
2433
1783
|
buildingWireframeConfigLayer &&
|
|
2434
1784
|
threeDimensionalEnabled &&
|
|
2435
1785
|
styleLayerIsVisibleAtZoom(buildingWireframeConfigLayer, map.getZoom()) &&
|
|
2436
1786
|
!map.getLayer("tileflow-buildings-wireframe-3d");
|
|
2437
|
-
if (
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
!shouldPreloadLandmarks
|
|
2441
|
-
) return;
|
|
2442
|
-
const runtimePrerequisites = [
|
|
2443
|
-
shouldPreloadLandmarks
|
|
2444
|
-
? loadLandmarkPrerequisites(landmarkManifestUrl)
|
|
2445
|
-
: loadThreeCoreRuntime(),
|
|
2446
|
-
...(needsBuildingWireframe ? [loadBuildingWireframeRuntime()] : [])
|
|
2447
|
-
];
|
|
2448
|
-
ensuringThreeDimensionalLayers = Promise.all(runtimePrerequisites)
|
|
2449
|
-
.then(async () => {
|
|
1787
|
+
if (!needsBuildingWireframe) return;
|
|
1788
|
+
ensuringThreeDimensionalLayers = loadBuildingWireframeRuntime()
|
|
1789
|
+
.then(() => {
|
|
2450
1790
|
const styleLayers = map.getStyle()?.layers || [];
|
|
2451
1791
|
const buildingWireframeConfigLayer = styleLayers.find(
|
|
2452
1792
|
(layer) => layer.metadata?.["tileflow:building-wireframe"] === true
|
|
@@ -2456,28 +1796,8 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2456
1796
|
threeDimensionalEnabled &&
|
|
2457
1797
|
styleLayerIsVisibleAtZoom(buildingWireframeConfigLayer, map.getZoom())
|
|
2458
1798
|
) {
|
|
2459
|
-
// A tree-only load may already be in flight when buildings are
|
|
2460
|
-
// enabled. Reassert the line runtime before mounting the layer.
|
|
2461
|
-
await loadBuildingWireframeRuntime();
|
|
2462
1799
|
addBuildingWireframeLayerIfConfigured(map, styleLayers);
|
|
2463
1800
|
}
|
|
2464
|
-
const landmarkConfigLayer = styleLayers.find(
|
|
2465
|
-
(layer) => layer.metadata?.["tileflow:landmark-manifest-url"]
|
|
2466
|
-
);
|
|
2467
|
-
const shouldAddLandmarks =
|
|
2468
|
-
landmarkConfigLayer &&
|
|
2469
|
-
threeDimensionalEnabled &&
|
|
2470
|
-
styleLayerIsVisibleAtZoom(landmarkConfigLayer, map.getZoom()) &&
|
|
2471
|
-
!map.getLayer("tileflow-landmarks-3d");
|
|
2472
|
-
if (shouldAddLandmarks) {
|
|
2473
|
-
// The tree-only path may already be in flight when landmarks
|
|
2474
|
-
// are enabled. Reassert the richer runtime here so a shared
|
|
2475
|
-
// core promise can never expose an undefined GLTF loader.
|
|
2476
|
-
await loadLandmarkPrerequisites(
|
|
2477
|
-
landmarkConfigLayer.metadata["tileflow:landmark-manifest-url"]
|
|
2478
|
-
);
|
|
2479
|
-
addLandmarkLayerIfConfigured(map, styleLayers);
|
|
2480
|
-
}
|
|
2481
1801
|
})
|
|
2482
1802
|
.catch((error) => console.warn("Tileflow 3D runtime failed to load", error))
|
|
2483
1803
|
.finally(() => {
|
|
@@ -2710,7 +2030,6 @@ function resolvePreviewTheme(map, requested) {
|
|
|
2710
2030
|
}
|
|
2711
2031
|
export {
|
|
2712
2032
|
TileflowPreviewSelectionError,
|
|
2713
|
-
normalizeTileflowLandmarkManifest,
|
|
2714
2033
|
readBoundedTileflowJsonResponse,
|
|
2715
2034
|
readBoundedTileflowResponse,
|
|
2716
2035
|
renderTileflowPreviewHtml,
|