@tileflow/dom 0.0.0-stage → 0.1.0-alpha.1
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 +201 -0
- package/README.md +110 -2
- package/REFERENCE.md +91 -0
- package/dist/index.d.ts +198 -0
- package/dist/index.js +1951 -0
- package/package.json +57 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,1951 @@
|
|
|
1
|
+
// src/map.ts
|
|
2
|
+
import {
|
|
3
|
+
attachTileflowAtmosphere,
|
|
4
|
+
attachTileflowBranding,
|
|
5
|
+
attachTileflowFairUseNotice,
|
|
6
|
+
attachTileflowHorizonQueries,
|
|
7
|
+
attachTileflowLabelPlacement,
|
|
8
|
+
attachTileflowMap3D,
|
|
9
|
+
attachTileflowMapLifecycle,
|
|
10
|
+
attachTileflowNauticalLights,
|
|
11
|
+
attachTileflowNotices,
|
|
12
|
+
createTileflowAppearanceBinding,
|
|
13
|
+
createTileflowContourRequestScope,
|
|
14
|
+
createTileflowNavigationControlBinding,
|
|
15
|
+
createTileflowSessionStarter,
|
|
16
|
+
createTileflowThemeController,
|
|
17
|
+
createTileflowTransformRequest,
|
|
18
|
+
getTileflowSystemColorScheme,
|
|
19
|
+
loadTileflowRuntimeStyle,
|
|
20
|
+
loadTileflowStyleFonts,
|
|
21
|
+
registerTileflowContourProtocol,
|
|
22
|
+
registerTileflowPathGroundProtocol,
|
|
23
|
+
registerTileflowPmtilesProtocol,
|
|
24
|
+
registerTileflowWorldRequestBridge,
|
|
25
|
+
reportTileflowMapError,
|
|
26
|
+
resolveTileflowCameraFit,
|
|
27
|
+
subscribeTileflowSystemColorScheme,
|
|
28
|
+
tileflowCanvasContextAttributes,
|
|
29
|
+
tileflowMaxPitch
|
|
30
|
+
} from "@tileflow/core/browser";
|
|
31
|
+
import { normalizeTileflowCaptureId } from "@tileflow/core/capture";
|
|
32
|
+
import {
|
|
33
|
+
createTileflowSessionController,
|
|
34
|
+
defaultTileflowManifestUrl,
|
|
35
|
+
defaultTileflowRuntimeView,
|
|
36
|
+
loadTileflowManifest,
|
|
37
|
+
mergeTileflowAnalytics,
|
|
38
|
+
nearestTileflowBlendTheme,
|
|
39
|
+
normalizeTileflowRuntimeCenter,
|
|
40
|
+
normalizeTileflowStaticImageSize,
|
|
41
|
+
resolveTileflowManifestMap,
|
|
42
|
+
resolveTileflowRuntimeStyle,
|
|
43
|
+
resolveTileflowRuntimeTheme,
|
|
44
|
+
resolveTileflowRuntimeThemeBlend,
|
|
45
|
+
resolveTileflowRuntimeView,
|
|
46
|
+
resolveTileflowStaticImageUrl
|
|
47
|
+
} from "@tileflow/core/runtime";
|
|
48
|
+
import {
|
|
49
|
+
tileflowInteractionStateSchema as tileflowInteractionStateSchema2,
|
|
50
|
+
unusedTileflowRendererDiagnostics
|
|
51
|
+
} from "@tileflow/interactions";
|
|
52
|
+
import {
|
|
53
|
+
createTileflowMapLibreDataRuntime,
|
|
54
|
+
createTileflowMapLibreDomRuntime,
|
|
55
|
+
createTileflowMapLibreInteractionCoordinator,
|
|
56
|
+
createTileflowMapLibreSemanticDomRuntime
|
|
57
|
+
} from "@tileflow/interactions/maplibre";
|
|
58
|
+
|
|
59
|
+
// src/inputs.ts
|
|
60
|
+
import { validateTileflowBrowserThemeBlendSelection } from "@tileflow/core/browser";
|
|
61
|
+
import {
|
|
62
|
+
isTileflowTimeOfDay,
|
|
63
|
+
validateTileflowRuntimeSource,
|
|
64
|
+
validateTileflowThemeSelection
|
|
65
|
+
} from "@tileflow/core/runtime";
|
|
66
|
+
import {
|
|
67
|
+
initialTileflowInteractionState,
|
|
68
|
+
tileflowInteractionStateSchema,
|
|
69
|
+
validateTileflowAnnotations,
|
|
70
|
+
validateTileflowInteractionBindings
|
|
71
|
+
} from "@tileflow/interactions";
|
|
72
|
+
function prepareTileflowAnnotations(annotations) {
|
|
73
|
+
const candidates = annotations ?? [];
|
|
74
|
+
const titles = new Map(candidates.map((annotation) => [annotation.id, annotation.ariaLabel]));
|
|
75
|
+
const validation = validateTileflowAnnotations(candidates);
|
|
76
|
+
if (!validation.ok) {
|
|
77
|
+
return { annotations: [], diagnostics: validation.diagnostics, ok: false, titles };
|
|
78
|
+
}
|
|
79
|
+
return { annotations: candidates, diagnostics: [], ok: true, titles };
|
|
80
|
+
}
|
|
81
|
+
function prepareTileflowInteractionBindings(interactions) {
|
|
82
|
+
const validation = validateTileflowInteractionBindings(interactions ?? []);
|
|
83
|
+
return validation.ok ? { bindings: validation.bindings, diagnostics: [], ok: true } : { diagnostics: validation.diagnostics, ok: false };
|
|
84
|
+
}
|
|
85
|
+
function prepareTileflowInteractionState(interactionState, defaultInteractionState, controlledOwnership = interactionState !== void 0) {
|
|
86
|
+
const controlled = interactionState !== void 0;
|
|
87
|
+
if (controlled && defaultInteractionState !== void 0) {
|
|
88
|
+
return invalidState(
|
|
89
|
+
controlled,
|
|
90
|
+
"Tileflow Map accepts interactionState or defaultInteractionState, not both."
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (controlled !== controlledOwnership) {
|
|
94
|
+
return invalidState(
|
|
95
|
+
controlled,
|
|
96
|
+
"Tileflow interaction state ownership cannot switch between controlled and uncontrolled."
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
const statePath = controlled ? "/interactionState" : "/defaultInteractionState";
|
|
100
|
+
const parsed = tileflowInteractionStateSchema.safeParse(
|
|
101
|
+
interactionState ?? defaultInteractionState ?? initialTileflowInteractionState
|
|
102
|
+
);
|
|
103
|
+
if (parsed.success) return { controlled, diagnostics: [], ok: true, state: parsed.data };
|
|
104
|
+
return {
|
|
105
|
+
controlled,
|
|
106
|
+
diagnostics: parsed.error.issues.map((issue) => ({
|
|
107
|
+
code: "INVALID_DOCUMENT",
|
|
108
|
+
level: "error",
|
|
109
|
+
message: issue.message,
|
|
110
|
+
path: `${statePath}${issue.path.map((segment) => String(segment).replaceAll("~", "~0").replaceAll("/", "~1")).map((segment) => `/${segment}`).join("")}`
|
|
111
|
+
})),
|
|
112
|
+
ok: false,
|
|
113
|
+
state: initialTileflowInteractionState
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function invalidState(controlled, message) {
|
|
117
|
+
return {
|
|
118
|
+
controlled,
|
|
119
|
+
diagnostics: [{ code: "INVALID_DOCUMENT", level: "error", message, path: "" }],
|
|
120
|
+
ok: false,
|
|
121
|
+
state: initialTileflowInteractionState
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function assertTileflowMapStyleOptions(input) {
|
|
125
|
+
const error = styleOptionsError(input);
|
|
126
|
+
if (error) throw new TypeError(`Invalid Tileflow map options: ${error}`);
|
|
127
|
+
}
|
|
128
|
+
function styleOptionsError(input) {
|
|
129
|
+
const sourceValidation = validateTileflowRuntimeSource(input.source);
|
|
130
|
+
if (!sourceValidation.ok) return sourceValidation.error;
|
|
131
|
+
if (input.theme !== void 0 && !validateTileflowThemeSelection(input.theme)) {
|
|
132
|
+
return 'theme must be a concrete portable theme name or "system"';
|
|
133
|
+
}
|
|
134
|
+
if (input.timeOfDay !== void 0 && !isTileflowTimeOfDay(input.timeOfDay)) {
|
|
135
|
+
return "timeOfDay must be day, dawn, dusk, or night";
|
|
136
|
+
}
|
|
137
|
+
if (input.themeBlend !== void 0 && !validateTileflowBrowserThemeBlendSelection(input.themeBlend)) {
|
|
138
|
+
return "themeBlend must name two to eight concrete themes, a position from 0 to themes.length - 1, and an optional boolean hold";
|
|
139
|
+
}
|
|
140
|
+
if (input.themeTransition !== void 0 && !isThemeTransition(input.themeTransition)) {
|
|
141
|
+
return "themeTransition.duration must be a number of milliseconds from 0 to 5000";
|
|
142
|
+
}
|
|
143
|
+
return void 0;
|
|
144
|
+
}
|
|
145
|
+
function isThemeTransition(value) {
|
|
146
|
+
if (!value || typeof value !== "object") return false;
|
|
147
|
+
const { duration } = value;
|
|
148
|
+
return duration === void 0 || typeof duration === "number" && Number.isFinite(duration) && duration >= 0 && duration <= 5e3;
|
|
149
|
+
}
|
|
150
|
+
function createOverlayFailureDiagnostic(message) {
|
|
151
|
+
return { code: "OVERLAY_FAILURE", level: "error", message, path: "" };
|
|
152
|
+
}
|
|
153
|
+
function tileflowInteractionDiagnosticKey(diagnostic) {
|
|
154
|
+
return [
|
|
155
|
+
diagnostic.code,
|
|
156
|
+
diagnostic.level,
|
|
157
|
+
diagnostic.path ?? "",
|
|
158
|
+
diagnostic.target ? JSON.stringify(diagnostic.target) : "",
|
|
159
|
+
diagnostic.message
|
|
160
|
+
].join("\0");
|
|
161
|
+
}
|
|
162
|
+
function imageModeAnnotationDiagnostic(annotationCount) {
|
|
163
|
+
return annotationCount > 0 ? {
|
|
164
|
+
code: "UNSUPPORTED_MODE",
|
|
165
|
+
level: "error",
|
|
166
|
+
message: 'Tileflow annotations require mode="interactive"; image mode cannot render interactive overlays.',
|
|
167
|
+
path: ""
|
|
168
|
+
} : void 0;
|
|
169
|
+
}
|
|
170
|
+
function imageModeMapInteractionDiagnostic(annotationCount, bindingCount, hasAdditionalInteractionConfiguration = false) {
|
|
171
|
+
if (bindingCount === 0 && !hasAdditionalInteractionConfiguration) {
|
|
172
|
+
return imageModeAnnotationDiagnostic(annotationCount);
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
code: "UNSUPPORTED_MODE",
|
|
176
|
+
level: "error",
|
|
177
|
+
message: annotationCount > 0 && bindingCount > 0 ? 'Tileflow annotations and interactions require mode="interactive"; image mode cannot render interactive overlays.' : annotationCount > 0 ? 'Tileflow annotations require mode="interactive"; image mode cannot render interactive overlays.' : bindingCount > 0 ? 'Tileflow interactions require mode="interactive"; image mode cannot render interactive overlays.' : 'Tileflow interaction state, callbacks, and renderers require mode="interactive"; image mode cannot render interactive overlays.',
|
|
178
|
+
path: ""
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/maplibre.ts
|
|
183
|
+
var bundlerWorkerUrlKey = /* @__PURE__ */ Symbol.for("tileflow.maplibre.workerUrl");
|
|
184
|
+
var importMapLibre = () => import("maplibre-gl");
|
|
185
|
+
var loadMapLibreModule = importMapLibre;
|
|
186
|
+
var mapLibrePromise;
|
|
187
|
+
var mapLibre;
|
|
188
|
+
var workerUrl;
|
|
189
|
+
var appliedWorkerUrl;
|
|
190
|
+
function configureTileflowMapLibre(configuration) {
|
|
191
|
+
const nextWorkerUrl = configuration.workerUrl;
|
|
192
|
+
if (!isWorkerUrl(nextWorkerUrl)) {
|
|
193
|
+
throw new TypeError("Tileflow MapLibre workerUrl must be a non-empty string.");
|
|
194
|
+
}
|
|
195
|
+
if (workerUrl !== void 0 && workerUrl !== nextWorkerUrl) {
|
|
196
|
+
throw new Error("Tileflow MapLibre workerUrl is already configured.");
|
|
197
|
+
}
|
|
198
|
+
if (appliedWorkerUrl !== void 0 && appliedWorkerUrl !== nextWorkerUrl) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
"Tileflow MapLibre workerUrl is already configured: a map started with the bundler-provided worker. Configure the worker before the first map."
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
workerUrl = nextWorkerUrl;
|
|
204
|
+
if (mapLibre) applyWorkerUrl(mapLibre);
|
|
205
|
+
}
|
|
206
|
+
function loadTileflowMapLibre() {
|
|
207
|
+
mapLibrePromise ??= loadMapLibreModule().then(resolveMapLibreModule).then(configureTileflowMapLibreModule).catch((error) => {
|
|
208
|
+
mapLibrePromise = void 0;
|
|
209
|
+
throw error;
|
|
210
|
+
});
|
|
211
|
+
return mapLibrePromise;
|
|
212
|
+
}
|
|
213
|
+
function resolveMapLibreModule(module) {
|
|
214
|
+
const defaultExport = module.default;
|
|
215
|
+
return defaultExport ?? module;
|
|
216
|
+
}
|
|
217
|
+
function configureTileflowMapLibreModule(maplibregl) {
|
|
218
|
+
applyWorkerUrl(maplibregl);
|
|
219
|
+
mapLibre = maplibregl;
|
|
220
|
+
return maplibregl;
|
|
221
|
+
}
|
|
222
|
+
function applyWorkerUrl(maplibregl) {
|
|
223
|
+
const nextWorkerUrl = workerUrl ?? readBundlerWorkerUrl();
|
|
224
|
+
if (nextWorkerUrl === void 0 || nextWorkerUrl === appliedWorkerUrl) return;
|
|
225
|
+
maplibregl.setWorkerUrl(nextWorkerUrl);
|
|
226
|
+
appliedWorkerUrl = nextWorkerUrl;
|
|
227
|
+
}
|
|
228
|
+
function readBundlerWorkerUrl() {
|
|
229
|
+
const value = globalThis[bundlerWorkerUrlKey];
|
|
230
|
+
return isWorkerUrl(value) ? value : void 0;
|
|
231
|
+
}
|
|
232
|
+
function isWorkerUrl(value) {
|
|
233
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/reconcile.ts
|
|
237
|
+
function createTileflowReconciler() {
|
|
238
|
+
const memos = /* @__PURE__ */ new Map();
|
|
239
|
+
const effects = /* @__PURE__ */ new Map();
|
|
240
|
+
const stableValues = /* @__PURE__ */ new Map();
|
|
241
|
+
let pending = [];
|
|
242
|
+
return {
|
|
243
|
+
commit() {
|
|
244
|
+
const batch = pending;
|
|
245
|
+
pending = [];
|
|
246
|
+
const errors = [];
|
|
247
|
+
for (const { key } of batch) {
|
|
248
|
+
const slot = effects.get(key);
|
|
249
|
+
const cleanup = slot?.cleanup;
|
|
250
|
+
if (slot) slot.cleanup = void 0;
|
|
251
|
+
if (!cleanup) continue;
|
|
252
|
+
try {
|
|
253
|
+
cleanup();
|
|
254
|
+
} catch (error) {
|
|
255
|
+
errors.push(error);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
for (const { key, run } of batch) {
|
|
259
|
+
const slot = effects.get(key);
|
|
260
|
+
if (!slot) continue;
|
|
261
|
+
try {
|
|
262
|
+
const cleanup = run();
|
|
263
|
+
slot.cleanup = typeof cleanup === "function" ? cleanup : void 0;
|
|
264
|
+
} catch (error) {
|
|
265
|
+
errors.push(error);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
throwCollected(errors, "Tileflow map update failed.");
|
|
269
|
+
},
|
|
270
|
+
dispose() {
|
|
271
|
+
pending = [];
|
|
272
|
+
const cleanups = [...effects.values()].map((slot) => slot.cleanup).filter((cleanup) => cleanup !== void 0).reverse();
|
|
273
|
+
effects.clear();
|
|
274
|
+
memos.clear();
|
|
275
|
+
stableValues.clear();
|
|
276
|
+
runTileflowCleanups(cleanups);
|
|
277
|
+
},
|
|
278
|
+
effect(key, deps, run) {
|
|
279
|
+
const slot = effects.get(key);
|
|
280
|
+
if (slot && sameDependencies(slot.deps, deps)) return;
|
|
281
|
+
if (slot) slot.deps = deps;
|
|
282
|
+
else effects.set(key, { deps });
|
|
283
|
+
pending = pending.filter((entry) => entry.key !== key);
|
|
284
|
+
pending.push({ key, run });
|
|
285
|
+
},
|
|
286
|
+
memo(key, deps, compute) {
|
|
287
|
+
const slot = memos.get(key);
|
|
288
|
+
if (slot && sameDependencies(slot.deps, deps)) return slot.value;
|
|
289
|
+
const value = compute();
|
|
290
|
+
memos.set(key, { deps, value });
|
|
291
|
+
return value;
|
|
292
|
+
},
|
|
293
|
+
stable(key, value) {
|
|
294
|
+
if (stableValues.has(key)) {
|
|
295
|
+
const previous = stableValues.get(key);
|
|
296
|
+
if (areEquivalentTileflowOptionValues(previous, value)) return previous;
|
|
297
|
+
}
|
|
298
|
+
stableValues.set(key, value);
|
|
299
|
+
return value;
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function sameDependencies(left, right) {
|
|
304
|
+
return left.length === right.length && left.every((value, index) => Object.is(value, right[index]));
|
|
305
|
+
}
|
|
306
|
+
function areEquivalentTileflowOptionValues(left, right, seen = /* @__PURE__ */ new WeakMap()) {
|
|
307
|
+
if (Object.is(left, right)) return true;
|
|
308
|
+
if (!left || !right || typeof left !== "object" || typeof right !== "object") return false;
|
|
309
|
+
const seenRight = seen.get(left);
|
|
310
|
+
if (seenRight) return seenRight === right;
|
|
311
|
+
seen.set(left, right);
|
|
312
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
313
|
+
return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => areEquivalentTileflowOptionValues(value, right[index], seen));
|
|
314
|
+
}
|
|
315
|
+
if (!isPlainObject(left) || !isPlainObject(right)) return false;
|
|
316
|
+
const leftKeys = Object.keys(left);
|
|
317
|
+
const rightKeys = Object.keys(right);
|
|
318
|
+
return leftKeys.length === rightKeys.length && leftKeys.every(
|
|
319
|
+
(key) => Object.hasOwn(right, key) && areEquivalentTileflowOptionValues(left[key], right[key], seen)
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
function isPlainObject(value) {
|
|
323
|
+
const prototype = Object.getPrototypeOf(value);
|
|
324
|
+
return prototype === Object.prototype || prototype === null;
|
|
325
|
+
}
|
|
326
|
+
function runTileflowCleanups(cleanups) {
|
|
327
|
+
const errors = [];
|
|
328
|
+
for (const cleanup of cleanups) {
|
|
329
|
+
try {
|
|
330
|
+
cleanup();
|
|
331
|
+
} catch (error) {
|
|
332
|
+
errors.push(error);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
throwCollected(errors, "Tileflow map cleanup failed.");
|
|
336
|
+
}
|
|
337
|
+
function throwCollected(errors, message) {
|
|
338
|
+
if (errors.length === 1) throw errors[0];
|
|
339
|
+
if (errors.length > 1) throw new AggregateError(errors, message);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// src/scope.ts
|
|
343
|
+
function createTileflowResourceScope() {
|
|
344
|
+
const cleanups = [];
|
|
345
|
+
let disposed = false;
|
|
346
|
+
const scope = {
|
|
347
|
+
add(cleanup) {
|
|
348
|
+
if (disposed) runTileflowCleanups([cleanup]);
|
|
349
|
+
else cleanups.push(cleanup);
|
|
350
|
+
},
|
|
351
|
+
child() {
|
|
352
|
+
const child = createTileflowResourceScope();
|
|
353
|
+
scope.add(() => child.dispose());
|
|
354
|
+
return child;
|
|
355
|
+
},
|
|
356
|
+
dispose() {
|
|
357
|
+
if (disposed) return;
|
|
358
|
+
disposed = true;
|
|
359
|
+
runTileflowCleanups(cleanups.splice(0).reverse());
|
|
360
|
+
},
|
|
361
|
+
get disposed() {
|
|
362
|
+
return disposed;
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
return scope;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/views.ts
|
|
369
|
+
function tileflowAnnotationViewContext(target, state) {
|
|
370
|
+
const { annotation } = target;
|
|
371
|
+
const content = target.kind === "marker" ? annotation.marker?.content : target.kind === "tooltip" ? annotation.tooltip?.content : annotation.popup?.content;
|
|
372
|
+
return {
|
|
373
|
+
annotation,
|
|
374
|
+
close: target.close,
|
|
375
|
+
...content ? { content } : {},
|
|
376
|
+
target: { annotation, coordinate: annotation.coordinate, kind: "annotation" },
|
|
377
|
+
...content?.kind === "view" ? { viewName: content.name } : {},
|
|
378
|
+
...target.kind === "marker" && state ? { state } : {}
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function tileflowSemanticViewContext(target) {
|
|
382
|
+
return {
|
|
383
|
+
close: target.close,
|
|
384
|
+
content: target.content,
|
|
385
|
+
target: target.target,
|
|
386
|
+
...target.viewName ? { viewName: target.viewName } : {}
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
function createTileflowMapViewHost(options) {
|
|
390
|
+
const mounted = /* @__PURE__ */ new Map();
|
|
391
|
+
let signature = "[]";
|
|
392
|
+
let settledSignature = "[]";
|
|
393
|
+
let frames = [];
|
|
394
|
+
let disposed = false;
|
|
395
|
+
const cancelFrames = () => {
|
|
396
|
+
for (const frame of frames) options.window.cancelAnimationFrame(frame);
|
|
397
|
+
frames = [];
|
|
398
|
+
};
|
|
399
|
+
const draw = (view, context) => {
|
|
400
|
+
view.context = context;
|
|
401
|
+
try {
|
|
402
|
+
if (view.instance && view.instance.update) {
|
|
403
|
+
view.instance.update(context);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
view.instance?.destroy?.();
|
|
407
|
+
view.element.replaceChildren();
|
|
408
|
+
view.instance = view.renderer(view.element, context);
|
|
409
|
+
} catch (error) {
|
|
410
|
+
view.instance = void 0;
|
|
411
|
+
options.onError(error);
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
const unmount = (view) => {
|
|
415
|
+
view.detachPointer?.();
|
|
416
|
+
try {
|
|
417
|
+
view.instance?.destroy?.();
|
|
418
|
+
} catch (error) {
|
|
419
|
+
options.onError(error);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
const markerContext = (view) => tileflowAnnotationViewContext(view.target, {
|
|
423
|
+
...view.pointer,
|
|
424
|
+
selected: view.selected
|
|
425
|
+
});
|
|
426
|
+
const watchPointer = (view) => {
|
|
427
|
+
const element = view.element.closest('[data-tileflow-interaction="marker"]');
|
|
428
|
+
if (!element) return void 0;
|
|
429
|
+
const set = (patch) => {
|
|
430
|
+
const next = { ...view.pointer, ...patch };
|
|
431
|
+
if (next.focused === view.pointer.focused && next.hovered === view.pointer.hovered) return;
|
|
432
|
+
view.pointer = next;
|
|
433
|
+
draw(view, markerContext(view));
|
|
434
|
+
};
|
|
435
|
+
const enter = () => set({ hovered: true });
|
|
436
|
+
const leave = () => set({ hovered: false });
|
|
437
|
+
const focus = () => set({ focused: true });
|
|
438
|
+
const blur = () => set({ focused: false });
|
|
439
|
+
element.addEventListener("pointerenter", enter);
|
|
440
|
+
element.addEventListener("pointerleave", leave);
|
|
441
|
+
element.addEventListener("focusin", focus);
|
|
442
|
+
element.addEventListener("focusout", blur);
|
|
443
|
+
return () => {
|
|
444
|
+
element.removeEventListener("pointerenter", enter);
|
|
445
|
+
element.removeEventListener("pointerleave", leave);
|
|
446
|
+
element.removeEventListener("focusin", focus);
|
|
447
|
+
element.removeEventListener("focusout", blur);
|
|
448
|
+
};
|
|
449
|
+
};
|
|
450
|
+
return {
|
|
451
|
+
dispose() {
|
|
452
|
+
disposed = true;
|
|
453
|
+
cancelFrames();
|
|
454
|
+
for (const view of mounted.values()) unmount(view);
|
|
455
|
+
mounted.clear();
|
|
456
|
+
},
|
|
457
|
+
isSettled: () => settledSignature === signature,
|
|
458
|
+
sync(targets, views) {
|
|
459
|
+
if (disposed) return;
|
|
460
|
+
const selected = new Set(
|
|
461
|
+
targets.annotations.filter((target) => target.kind === "popup").map((target) => target.annotation.id)
|
|
462
|
+
);
|
|
463
|
+
const next = /* @__PURE__ */ new Map();
|
|
464
|
+
for (const target of targets.annotations) {
|
|
465
|
+
next.set(`annotation:${target.key}`, { kind: "annotation", target });
|
|
466
|
+
}
|
|
467
|
+
for (const target of targets.semantic) {
|
|
468
|
+
next.set(`semantic:${target.key}`, { kind: "semantic", target });
|
|
469
|
+
}
|
|
470
|
+
for (const [key, view] of mounted) {
|
|
471
|
+
if (next.has(key)) continue;
|
|
472
|
+
unmount(view);
|
|
473
|
+
mounted.delete(key);
|
|
474
|
+
}
|
|
475
|
+
for (const [key, entry] of next) {
|
|
476
|
+
const viewKind = entry.target.kind;
|
|
477
|
+
const renderer = views[viewKind];
|
|
478
|
+
const isMarker = entry.kind === "annotation" && viewKind === "marker";
|
|
479
|
+
const isSelected = isMarker && selected.has(
|
|
480
|
+
entry.target.annotation.id
|
|
481
|
+
);
|
|
482
|
+
const existing = mounted.get(key);
|
|
483
|
+
if (!renderer) {
|
|
484
|
+
if (existing) {
|
|
485
|
+
unmount(existing);
|
|
486
|
+
mounted.delete(key);
|
|
487
|
+
}
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
if (existing && existing.renderer === renderer && existing.target === entry.target && existing.selected === isSelected) {
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
if (existing && existing.renderer !== renderer) {
|
|
494
|
+
unmount(existing);
|
|
495
|
+
mounted.delete(key);
|
|
496
|
+
}
|
|
497
|
+
const view = mounted.get(key) ?? {
|
|
498
|
+
context: void 0,
|
|
499
|
+
element: entry.target.container,
|
|
500
|
+
instance: void 0,
|
|
501
|
+
pointer: { focused: false, hovered: false },
|
|
502
|
+
renderer,
|
|
503
|
+
selected: isSelected,
|
|
504
|
+
target: entry.target
|
|
505
|
+
};
|
|
506
|
+
view.element = entry.target.container;
|
|
507
|
+
view.selected = isSelected;
|
|
508
|
+
view.target = entry.target;
|
|
509
|
+
if (!mounted.has(key)) {
|
|
510
|
+
mounted.set(key, view);
|
|
511
|
+
if (isMarker) view.detachPointer = watchPointer(view);
|
|
512
|
+
}
|
|
513
|
+
draw(
|
|
514
|
+
view,
|
|
515
|
+
entry.kind === "semantic" ? tileflowSemanticViewContext(entry.target) : isMarker ? markerContext(view) : tileflowAnnotationViewContext(
|
|
516
|
+
entry.target
|
|
517
|
+
)
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
const nextSignature = JSON.stringify([...mounted.keys()].sort());
|
|
521
|
+
if (nextSignature === signature) return;
|
|
522
|
+
signature = nextSignature;
|
|
523
|
+
cancelFrames();
|
|
524
|
+
const first = options.window.requestAnimationFrame(() => {
|
|
525
|
+
const second = options.window.requestAnimationFrame(() => {
|
|
526
|
+
frames = [];
|
|
527
|
+
if (disposed) return;
|
|
528
|
+
settledSignature = nextSignature;
|
|
529
|
+
options.onChange();
|
|
530
|
+
});
|
|
531
|
+
frames = [second];
|
|
532
|
+
});
|
|
533
|
+
frames = [first];
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// src/map.ts
|
|
539
|
+
var noInteractions = [];
|
|
540
|
+
var statusAttributes = [
|
|
541
|
+
"data-tileflow-capture-id",
|
|
542
|
+
"data-tileflow-map",
|
|
543
|
+
"data-tileflow-state",
|
|
544
|
+
"data-tileflow-theme"
|
|
545
|
+
];
|
|
546
|
+
function createTileflowMap(container, initialOptions, createOptions = {}) {
|
|
547
|
+
if (!container || typeof container !== "object" || !container.ownerDocument) {
|
|
548
|
+
throw new TypeError("createTileflowMap needs an HTML element to mount the map in.");
|
|
549
|
+
}
|
|
550
|
+
const ownerWindow = container.ownerDocument.defaultView;
|
|
551
|
+
if (!ownerWindow) throw new Error("A Tileflow map needs a browser document.");
|
|
552
|
+
const browserWindow = ownerWindow;
|
|
553
|
+
assertTileflowMapStyleOptions(initialOptions);
|
|
554
|
+
if (initialOptions.views && initialOptions.renderTargets) {
|
|
555
|
+
throw new TypeError("A Tileflow map takes views or renderTargets, not both.");
|
|
556
|
+
}
|
|
557
|
+
const sessionSource = createOptions.sessionSource ?? "dom";
|
|
558
|
+
const statusElement = createOptions.statusElement === void 0 ? container : createOptions.statusElement;
|
|
559
|
+
const captureId = normalizeTileflowCaptureId(createOptions.captureId);
|
|
560
|
+
const controlledInteractionOwnership = initialOptions.interactionState !== void 0;
|
|
561
|
+
let options = { ...initialOptions };
|
|
562
|
+
let destroyed = false;
|
|
563
|
+
let reconciling = false;
|
|
564
|
+
let dirty = false;
|
|
565
|
+
let scheduled = false;
|
|
566
|
+
let manifestResolution = { key: "", map: null, state: "loading" };
|
|
567
|
+
let systemColorScheme = "light";
|
|
568
|
+
let captureState = "loading";
|
|
569
|
+
let themeTransitionState = "idle";
|
|
570
|
+
let activeRuntimeResource;
|
|
571
|
+
let annotationRenderTargets = [];
|
|
572
|
+
let semanticRenderTargets = [];
|
|
573
|
+
let annotationBridgeDiagnostic;
|
|
574
|
+
let semanticBridgeDiagnostic;
|
|
575
|
+
let stateBridgeDiagnostic;
|
|
576
|
+
let rendererBridgeDiagnostic;
|
|
577
|
+
let viewBridgeDiagnostic;
|
|
578
|
+
let dataBridgeDiagnostic;
|
|
579
|
+
let interactionStartDiagnostic;
|
|
580
|
+
let annotationDiagnostics = [];
|
|
581
|
+
let semanticDiagnostics = [];
|
|
582
|
+
let reportedDeclarativeDiagnosticKeys = /* @__PURE__ */ new Set();
|
|
583
|
+
let imageSize = null;
|
|
584
|
+
let managedImage = null;
|
|
585
|
+
let imageReadinessRun = 0;
|
|
586
|
+
let themeMapIdentity;
|
|
587
|
+
let appliedTheme;
|
|
588
|
+
let failedTheme;
|
|
589
|
+
let validThemeSelection;
|
|
590
|
+
let map = null;
|
|
591
|
+
let mounted;
|
|
592
|
+
let scene;
|
|
593
|
+
let themeController;
|
|
594
|
+
let appearanceBinding;
|
|
595
|
+
let navigationBinding;
|
|
596
|
+
let interactionRuntime;
|
|
597
|
+
let semanticRuntime;
|
|
598
|
+
let interactionCoordinator;
|
|
599
|
+
let dataRuntime;
|
|
600
|
+
let appliedCameraFitKey;
|
|
601
|
+
let lastStatus;
|
|
602
|
+
let current;
|
|
603
|
+
let annotationsForRuntime = [];
|
|
604
|
+
let annotationTitles = /* @__PURE__ */ new Map();
|
|
605
|
+
let coordinatedInteractionState = prepareTileflowInteractionState(
|
|
606
|
+
initialOptions.interactionState,
|
|
607
|
+
initialOptions.defaultInteractionState
|
|
608
|
+
).state;
|
|
609
|
+
let dataInput = {};
|
|
610
|
+
let resolveReady;
|
|
611
|
+
let rejectReady;
|
|
612
|
+
let readySettled = false;
|
|
613
|
+
const ready = new Promise((resolve, reject) => {
|
|
614
|
+
resolveReady = resolve;
|
|
615
|
+
rejectReady = reject;
|
|
616
|
+
});
|
|
617
|
+
ready.catch(() => void 0);
|
|
618
|
+
const settleReady = (outcome) => {
|
|
619
|
+
if (readySettled) return;
|
|
620
|
+
readySettled = true;
|
|
621
|
+
if ("map" in outcome) resolveReady(outcome.map);
|
|
622
|
+
else rejectReady(outcome.error);
|
|
623
|
+
};
|
|
624
|
+
const reconciler = createTileflowReconciler();
|
|
625
|
+
const reportError = (code, cause) => {
|
|
626
|
+
reportTileflowMapError(options.onError, code, cause);
|
|
627
|
+
if (code === "manifest" || code === "runtime") {
|
|
628
|
+
settleReady({ error: cause instanceof Error ? cause : new Error(String(cause)) });
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
const reportDiagnostic = (diagnostic) => options.onInteractionDiagnostic?.(diagnostic);
|
|
632
|
+
const viewHost = createTileflowMapViewHost({
|
|
633
|
+
onChange: () => invalidate(),
|
|
634
|
+
onError(error) {
|
|
635
|
+
console.error("Failed to render a Tileflow map view", error);
|
|
636
|
+
viewBridgeDiagnostic = createOverlayFailureDiagnostic(
|
|
637
|
+
"A Tileflow map view could not be rendered."
|
|
638
|
+
);
|
|
639
|
+
reportDiagnostic(viewBridgeDiagnostic);
|
|
640
|
+
invalidate();
|
|
641
|
+
},
|
|
642
|
+
window: browserWindow
|
|
643
|
+
});
|
|
644
|
+
function invalidate() {
|
|
645
|
+
if (destroyed) return;
|
|
646
|
+
if (reconciling) {
|
|
647
|
+
dirty = true;
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (scheduled) return;
|
|
651
|
+
scheduled = true;
|
|
652
|
+
queueMicrotask(() => {
|
|
653
|
+
scheduled = false;
|
|
654
|
+
reconcile();
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
function derive() {
|
|
658
|
+
const memo = reconciler.memo;
|
|
659
|
+
const stable = reconciler.stable;
|
|
660
|
+
const {
|
|
661
|
+
annotationClusters,
|
|
662
|
+
annotations,
|
|
663
|
+
bounds,
|
|
664
|
+
dataOverlays,
|
|
665
|
+
dataSources,
|
|
666
|
+
defaultInteractionState,
|
|
667
|
+
fitAnnotations,
|
|
668
|
+
fitBoundsOptions,
|
|
669
|
+
interactionState,
|
|
670
|
+
source,
|
|
671
|
+
theme,
|
|
672
|
+
themeBlend,
|
|
673
|
+
timeOfDay
|
|
674
|
+
} = options;
|
|
675
|
+
const interactions = options.interactions ?? noInteractions;
|
|
676
|
+
const runtimeSource = memo("runtimeSource", [source.manifestUrl, source.map], () => ({
|
|
677
|
+
manifestUrl: source.manifestUrl,
|
|
678
|
+
map: source.map
|
|
679
|
+
}));
|
|
680
|
+
const mapName = runtimeSource.map;
|
|
681
|
+
const manifestUrl = runtimeSource.manifestUrl ?? defaultTileflowManifestUrl;
|
|
682
|
+
const runtimeMapIdentity = `${manifestUrl}\0${mapName}`;
|
|
683
|
+
if (themeMapIdentity !== runtimeMapIdentity) {
|
|
684
|
+
themeMapIdentity = runtimeMapIdentity;
|
|
685
|
+
appliedTheme = void 0;
|
|
686
|
+
failedTheme = void 0;
|
|
687
|
+
validThemeSelection = void 0;
|
|
688
|
+
}
|
|
689
|
+
const preparedAnnotations = memo(
|
|
690
|
+
"annotations",
|
|
691
|
+
[annotations],
|
|
692
|
+
() => prepareTileflowAnnotations(annotations)
|
|
693
|
+
);
|
|
694
|
+
const preparedInteractions = memo(
|
|
695
|
+
"interactions",
|
|
696
|
+
[interactions],
|
|
697
|
+
() => prepareTileflowInteractionBindings(interactions)
|
|
698
|
+
);
|
|
699
|
+
const preparedInteractionState = memo(
|
|
700
|
+
"interactionState",
|
|
701
|
+
[defaultInteractionState, interactionState],
|
|
702
|
+
() => prepareTileflowInteractionState(
|
|
703
|
+
interactionState,
|
|
704
|
+
defaultInteractionState,
|
|
705
|
+
controlledInteractionOwnership
|
|
706
|
+
)
|
|
707
|
+
);
|
|
708
|
+
const cameraFit = memo(
|
|
709
|
+
"cameraFit",
|
|
710
|
+
[annotations, bounds, fitAnnotations, fitBoundsOptions],
|
|
711
|
+
() => resolveTileflowCameraFit({
|
|
712
|
+
annotations: annotations ?? [],
|
|
713
|
+
bounds,
|
|
714
|
+
fitAnnotations,
|
|
715
|
+
fitBoundsOptions
|
|
716
|
+
})
|
|
717
|
+
);
|
|
718
|
+
const mapOptions = stable("mapOptions", options.mapOptions);
|
|
719
|
+
const manifestRequestKey = JSON.stringify([manifestUrl, mapName]);
|
|
720
|
+
const currentManifest = manifestResolution.key === manifestRequestKey ? manifestResolution : { key: manifestRequestKey, map: null, state: "loading" };
|
|
721
|
+
const manifestMap = currentManifest.state === "ready" ? currentManifest.map : null;
|
|
722
|
+
const themeBlendKey = themeBlend ? JSON.stringify(themeBlend.themes) : void 0;
|
|
723
|
+
const blendThemeName = themeBlend ? nearestTileflowBlendTheme(themeBlend) : void 0;
|
|
724
|
+
const resolveSelection = (selection) => {
|
|
725
|
+
if (!manifestMap) {
|
|
726
|
+
return {
|
|
727
|
+
error: false,
|
|
728
|
+
name: selection && selection !== "system" ? selection : void 0
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
try {
|
|
732
|
+
if (themeBlendKey) {
|
|
733
|
+
for (const name of JSON.parse(themeBlendKey)) {
|
|
734
|
+
resolveTileflowRuntimeTheme(manifestMap, name);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return {
|
|
738
|
+
error: false,
|
|
739
|
+
name: resolveTileflowRuntimeTheme(
|
|
740
|
+
manifestMap,
|
|
741
|
+
selection,
|
|
742
|
+
systemColorScheme,
|
|
743
|
+
themeBlendKey ? void 0 : timeOfDay
|
|
744
|
+
).name
|
|
745
|
+
};
|
|
746
|
+
} catch (cause) {
|
|
747
|
+
return { cause, error: true, name: void 0 };
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
const themeResolution = memo(
|
|
751
|
+
"themeResolution",
|
|
752
|
+
[
|
|
753
|
+
blendThemeName,
|
|
754
|
+
manifestMap,
|
|
755
|
+
(blendThemeName ?? theme) === "system" ? systemColorScheme : void 0,
|
|
756
|
+
theme,
|
|
757
|
+
themeBlendKey,
|
|
758
|
+
timeOfDay
|
|
759
|
+
],
|
|
760
|
+
() => resolveSelection(blendThemeName ?? theme)
|
|
761
|
+
);
|
|
762
|
+
const themeRequestFailed = failedTheme !== void 0 && failedTheme.selection === theme && failedTheme.theme === themeResolution.name;
|
|
763
|
+
const themeChoice = memo(
|
|
764
|
+
"themeChoice",
|
|
765
|
+
[
|
|
766
|
+
failedTheme,
|
|
767
|
+
manifestMap,
|
|
768
|
+
systemColorScheme,
|
|
769
|
+
themeBlendKey,
|
|
770
|
+
themeRequestFailed,
|
|
771
|
+
themeResolution,
|
|
772
|
+
timeOfDay,
|
|
773
|
+
validThemeSelection
|
|
774
|
+
],
|
|
775
|
+
() => {
|
|
776
|
+
if (blendThemeName) return { resolution: themeResolution, selection: theme };
|
|
777
|
+
if (themeRequestFailed && failedTheme) {
|
|
778
|
+
const { fallback } = failedTheme;
|
|
779
|
+
const resolution = resolveSelection(fallback.selection);
|
|
780
|
+
return resolution.error || resolution.name === failedTheme.theme ? { resolution: { error: false, name: fallback.theme }, selection: fallback.theme } : { resolution, selection: fallback.selection };
|
|
781
|
+
}
|
|
782
|
+
if (themeResolution.error) {
|
|
783
|
+
return {
|
|
784
|
+
resolution: resolveSelection(validThemeSelection),
|
|
785
|
+
selection: validThemeSelection
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
return { resolution: themeResolution, selection: theme };
|
|
789
|
+
}
|
|
790
|
+
);
|
|
791
|
+
const themeSelection = themeChoice.selection;
|
|
792
|
+
const shownThemeResolution = themeChoice.resolution;
|
|
793
|
+
const manifestView = resolveTileflowRuntimeView({ manifestMap });
|
|
794
|
+
const manifestCenter = stable(
|
|
795
|
+
"manifestCenter",
|
|
796
|
+
normalizeTileflowRuntimeCenter(manifestView.center)
|
|
797
|
+
);
|
|
798
|
+
const resolvedCenter = stable(
|
|
799
|
+
"center",
|
|
800
|
+
options.center ?? mapOptions?.center ?? manifestCenter
|
|
801
|
+
);
|
|
802
|
+
const resolvedZoom = options.zoom ?? mapOptions?.zoom ?? manifestView.zoom ?? defaultTileflowRuntimeView.zoom;
|
|
803
|
+
const resolvedBearing = mapOptions?.bearing ?? manifestView.bearing;
|
|
804
|
+
const resolvedPitch = mapOptions?.pitch ?? manifestView.pitch;
|
|
805
|
+
const resolvedInteractive = options.interactive ?? mapOptions?.interactive ?? true;
|
|
806
|
+
const isImageMode = options.mode === "image";
|
|
807
|
+
const renderers = tileflowMapRenderers(options);
|
|
808
|
+
const { hasMarkerRenderer, hasPopupRenderer, hasTooltipRenderer } = renderers;
|
|
809
|
+
const hasInteractionConfiguration = hasTileflowMapInteractionConfiguration(options, renderers);
|
|
810
|
+
const declarativeDiagnostics = memo(
|
|
811
|
+
"declarativeDiagnostics",
|
|
812
|
+
[
|
|
813
|
+
hasInteractionConfiguration,
|
|
814
|
+
hasPopupRenderer,
|
|
815
|
+
hasTooltipRenderer,
|
|
816
|
+
interactions,
|
|
817
|
+
isImageMode,
|
|
818
|
+
preparedAnnotations,
|
|
819
|
+
preparedInteractions,
|
|
820
|
+
preparedInteractionState
|
|
821
|
+
],
|
|
822
|
+
() => tileflowMapDeclarativeDiagnostics({
|
|
823
|
+
hasInteractionConfiguration,
|
|
824
|
+
hasPopupRenderer,
|
|
825
|
+
hasTooltipRenderer,
|
|
826
|
+
interactions,
|
|
827
|
+
isImageMode,
|
|
828
|
+
preparedAnnotations,
|
|
829
|
+
preparedInteractions,
|
|
830
|
+
preparedInteractionState
|
|
831
|
+
})
|
|
832
|
+
);
|
|
833
|
+
const runtimeStyle = stable(
|
|
834
|
+
"runtimeStyle",
|
|
835
|
+
memo(
|
|
836
|
+
"runtimeStyle",
|
|
837
|
+
[isImageMode, manifestMap, runtimeSource, systemColorScheme, shownThemeResolution],
|
|
838
|
+
() => isImageMode || shownThemeResolution.error ? null : resolveTileflowRuntimeStyle({
|
|
839
|
+
colorScheme: systemColorScheme,
|
|
840
|
+
manifestMap,
|
|
841
|
+
source: runtimeSource,
|
|
842
|
+
theme: shownThemeResolution.name
|
|
843
|
+
})
|
|
844
|
+
)
|
|
845
|
+
);
|
|
846
|
+
const blendResolution = memo(
|
|
847
|
+
"blendResolution",
|
|
848
|
+
[isImageMode, manifestMap, runtimeSource, themeBlendKey],
|
|
849
|
+
() => {
|
|
850
|
+
if (isImageMode || !manifestMap || !themeBlendKey) return { styles: null };
|
|
851
|
+
try {
|
|
852
|
+
return {
|
|
853
|
+
styles: resolveTileflowRuntimeThemeBlend({
|
|
854
|
+
blend: { position: 0, themes: JSON.parse(themeBlendKey) },
|
|
855
|
+
manifestMap,
|
|
856
|
+
source: runtimeSource
|
|
857
|
+
})
|
|
858
|
+
};
|
|
859
|
+
} catch (cause) {
|
|
860
|
+
return { cause, styles: null };
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
);
|
|
864
|
+
const resolvedAnalytics = memo(
|
|
865
|
+
"analytics",
|
|
866
|
+
[options.analytics, runtimeStyle],
|
|
867
|
+
() => mergeTileflowAnalytics(options.analytics, runtimeStyle?.analytics)
|
|
868
|
+
);
|
|
869
|
+
const staticImageCenter = stable(
|
|
870
|
+
"staticImageCenter",
|
|
871
|
+
normalizeTileflowRuntimeCenter(options.center ?? mapOptions?.center, manifestCenter)
|
|
872
|
+
);
|
|
873
|
+
const runtimeImageUrl = memo(
|
|
874
|
+
"runtimeImageUrl",
|
|
875
|
+
[
|
|
876
|
+
imageSize,
|
|
877
|
+
options.imageUrl,
|
|
878
|
+
isImageMode,
|
|
879
|
+
manifestMap,
|
|
880
|
+
staticImageCenter,
|
|
881
|
+
resolvedZoom,
|
|
882
|
+
systemColorScheme,
|
|
883
|
+
themeResolution
|
|
884
|
+
],
|
|
885
|
+
() => options.imageUrl ?? (isImageMode && !themeResolution.error ? resolveTileflowStaticImageUrl({
|
|
886
|
+
center: staticImageCenter,
|
|
887
|
+
colorScheme: systemColorScheme,
|
|
888
|
+
imageSize,
|
|
889
|
+
manifestMap,
|
|
890
|
+
theme: themeResolution.name,
|
|
891
|
+
zoom: resolvedZoom
|
|
892
|
+
}) : void 0)
|
|
893
|
+
);
|
|
894
|
+
const runtimeResolutionState = currentManifest.state !== "ready" ? currentManifest.state === "error" ? "error" : "loading" : themeResolution.error || themeRequestFailed ? "error" : isImageMode ? runtimeImageUrl ? "idle" : options.imageUrl === void 0 && imageSize === null ? "loading" : "error" : runtimeStyle ? "idle" : "error";
|
|
895
|
+
return {
|
|
896
|
+
annotationClusters,
|
|
897
|
+
blendResolution,
|
|
898
|
+
isImageMode,
|
|
899
|
+
runtimeImageUrl,
|
|
900
|
+
cameraFit,
|
|
901
|
+
dataOverlays,
|
|
902
|
+
dataSources,
|
|
903
|
+
declarativeDiagnostics,
|
|
904
|
+
hasMarkerRenderer,
|
|
905
|
+
hasPopupRenderer,
|
|
906
|
+
hasTooltipRenderer,
|
|
907
|
+
manifestRequestKey,
|
|
908
|
+
manifestUrl,
|
|
909
|
+
mapName,
|
|
910
|
+
mapOptions,
|
|
911
|
+
preparedAnnotations,
|
|
912
|
+
preparedInteractions,
|
|
913
|
+
preparedInteractionState,
|
|
914
|
+
resolvedAnalytics,
|
|
915
|
+
resolvedBearing,
|
|
916
|
+
resolvedCenter,
|
|
917
|
+
resolvedInteractive,
|
|
918
|
+
resolvedPitch,
|
|
919
|
+
resolvedZoom,
|
|
920
|
+
runtimeMapIdentity,
|
|
921
|
+
runtimeResolutionState,
|
|
922
|
+
runtimeSource,
|
|
923
|
+
manifestReady: manifestMap !== null,
|
|
924
|
+
runtimeStyle,
|
|
925
|
+
shownThemeResolution,
|
|
926
|
+
themeRequestFailed,
|
|
927
|
+
themeSelection,
|
|
928
|
+
themeBlendHold: themeBlend?.hold === true,
|
|
929
|
+
themeBlendKey,
|
|
930
|
+
themeBlendPosition: themeBlend?.position,
|
|
931
|
+
themeResolution
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
function reconcile() {
|
|
935
|
+
if (destroyed) return;
|
|
936
|
+
reconciling = true;
|
|
937
|
+
try {
|
|
938
|
+
do {
|
|
939
|
+
dirty = false;
|
|
940
|
+
pass();
|
|
941
|
+
} while (dirty && !destroyed);
|
|
942
|
+
} finally {
|
|
943
|
+
reconciling = false;
|
|
944
|
+
}
|
|
945
|
+
emitStatus();
|
|
946
|
+
}
|
|
947
|
+
function pass() {
|
|
948
|
+
const effect = reconciler.effect;
|
|
949
|
+
const d = derive();
|
|
950
|
+
current = d;
|
|
951
|
+
if (d.preparedAnnotations.ok) {
|
|
952
|
+
annotationsForRuntime = d.preparedAnnotations.annotations;
|
|
953
|
+
annotationTitles = d.preparedAnnotations.titles;
|
|
954
|
+
}
|
|
955
|
+
dataInput = {
|
|
956
|
+
annotationClusters: d.annotationClusters,
|
|
957
|
+
annotations: d.preparedAnnotations.annotations,
|
|
958
|
+
dataOverlays: d.dataOverlays,
|
|
959
|
+
dataSources: d.dataSources
|
|
960
|
+
};
|
|
961
|
+
effect("scene", [options.vegetation, options.viewMode ?? "3d"], () => {
|
|
962
|
+
scene?.setVegetation(options.vegetation);
|
|
963
|
+
scene?.setViewMode(options.viewMode ?? "3d");
|
|
964
|
+
});
|
|
965
|
+
effect("imageSize", [d.isImageMode, options.imageUrl], () => {
|
|
966
|
+
imageSize = null;
|
|
967
|
+
invalidate();
|
|
968
|
+
if (!d.isImageMode || options.imageUrl) return;
|
|
969
|
+
const updateImageSize = () => {
|
|
970
|
+
const next = normalizeTileflowStaticImageSize({
|
|
971
|
+
height: container.clientHeight,
|
|
972
|
+
width: container.clientWidth
|
|
973
|
+
});
|
|
974
|
+
if (imageSize?.height === next.height && imageSize.width === next.width) return;
|
|
975
|
+
imageSize = next;
|
|
976
|
+
invalidate();
|
|
977
|
+
};
|
|
978
|
+
const resizeObserver = new ResizeObserver(updateImageSize);
|
|
979
|
+
resizeObserver.observe(container);
|
|
980
|
+
updateImageSize();
|
|
981
|
+
return () => resizeObserver.disconnect();
|
|
982
|
+
});
|
|
983
|
+
const ownsImage = d.isImageMode && options.image === void 0;
|
|
984
|
+
effect("managedImage", [ownsImage], () => {
|
|
985
|
+
if (!ownsImage) return;
|
|
986
|
+
const image = container.ownerDocument.createElement("img");
|
|
987
|
+
image.decoding = "async";
|
|
988
|
+
image.style.cssText = "display: block; height: 100%; object-fit: cover; width: 100%;";
|
|
989
|
+
managedImage = image;
|
|
990
|
+
invalidate();
|
|
991
|
+
return () => {
|
|
992
|
+
image.remove();
|
|
993
|
+
if (managedImage === image) managedImage = null;
|
|
994
|
+
};
|
|
995
|
+
});
|
|
996
|
+
const imageElement = ownsImage ? managedImage : options.image ?? null;
|
|
997
|
+
const alt = options.alt ?? "";
|
|
998
|
+
const imageLoading = options.imageLoading ?? "eager";
|
|
999
|
+
effect("managedImageAttributes", [managedImage, d.runtimeImageUrl, alt, imageLoading], () => {
|
|
1000
|
+
const image = managedImage;
|
|
1001
|
+
if (!image) return;
|
|
1002
|
+
image.alt = alt;
|
|
1003
|
+
image.loading = imageLoading;
|
|
1004
|
+
if (!d.runtimeImageUrl) {
|
|
1005
|
+
image.remove();
|
|
1006
|
+
image.removeAttribute("src");
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
if (image.getAttribute("src") !== d.runtimeImageUrl) image.src = d.runtimeImageUrl;
|
|
1010
|
+
if (!image.isConnected) container.append(image);
|
|
1011
|
+
});
|
|
1012
|
+
effect("imageReadiness", [d.isImageMode, d.runtimeImageUrl, imageElement], () => {
|
|
1013
|
+
if (!d.isImageMode) return;
|
|
1014
|
+
imageReadinessRun += 1;
|
|
1015
|
+
activeRuntimeResource = d.runtimeImageUrl;
|
|
1016
|
+
captureState = "loading";
|
|
1017
|
+
invalidate();
|
|
1018
|
+
const image = imageElement;
|
|
1019
|
+
if (!image) return;
|
|
1020
|
+
const markReady = async () => {
|
|
1021
|
+
const run = ++imageReadinessRun;
|
|
1022
|
+
try {
|
|
1023
|
+
if (typeof image.decode === "function") await image.decode();
|
|
1024
|
+
} catch {
|
|
1025
|
+
if (!image.complete || image.naturalWidth === 0) {
|
|
1026
|
+
if (imageReadinessRun === run) {
|
|
1027
|
+
captureState = "error";
|
|
1028
|
+
invalidate();
|
|
1029
|
+
}
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
if (imageReadinessRun !== run) return;
|
|
1034
|
+
captureState = "idle";
|
|
1035
|
+
invalidate();
|
|
1036
|
+
};
|
|
1037
|
+
const fail = () => {
|
|
1038
|
+
imageReadinessRun += 1;
|
|
1039
|
+
captureState = "error";
|
|
1040
|
+
invalidate();
|
|
1041
|
+
};
|
|
1042
|
+
const load = () => void markReady();
|
|
1043
|
+
image.addEventListener("load", load);
|
|
1044
|
+
image.addEventListener("error", fail);
|
|
1045
|
+
if (image.complete && image.getAttribute("src")) {
|
|
1046
|
+
if (image.naturalWidth === 0) fail();
|
|
1047
|
+
else void markReady();
|
|
1048
|
+
}
|
|
1049
|
+
return () => {
|
|
1050
|
+
image.removeEventListener("load", load);
|
|
1051
|
+
image.removeEventListener("error", fail);
|
|
1052
|
+
};
|
|
1053
|
+
});
|
|
1054
|
+
if (d.manifestReady && !d.themeResolution.error && !d.themeRequestFailed) {
|
|
1055
|
+
validThemeSelection = options.theme;
|
|
1056
|
+
}
|
|
1057
|
+
if (failedTheme && (failedTheme.selection !== options.theme || failedTheme.theme !== d.themeResolution.name)) {
|
|
1058
|
+
failedTheme = void 0;
|
|
1059
|
+
}
|
|
1060
|
+
effect("systemColorScheme", [d.themeSelection], () => {
|
|
1061
|
+
if (d.themeSelection !== "system") return;
|
|
1062
|
+
systemColorScheme = getTileflowSystemColorScheme();
|
|
1063
|
+
invalidate();
|
|
1064
|
+
return subscribeTileflowSystemColorScheme((scheme) => {
|
|
1065
|
+
systemColorScheme = scheme;
|
|
1066
|
+
invalidate();
|
|
1067
|
+
});
|
|
1068
|
+
});
|
|
1069
|
+
effect("declarativeDiagnostics", [d.declarativeDiagnostics], () => {
|
|
1070
|
+
const previousKeys = reportedDeclarativeDiagnosticKeys;
|
|
1071
|
+
const nextKeys = /* @__PURE__ */ new Set();
|
|
1072
|
+
for (const diagnostic of d.declarativeDiagnostics) {
|
|
1073
|
+
const key = tileflowInteractionDiagnosticKey(diagnostic);
|
|
1074
|
+
nextKeys.add(key);
|
|
1075
|
+
if (!previousKeys.has(key)) reportDiagnostic(diagnostic);
|
|
1076
|
+
}
|
|
1077
|
+
reportedDeclarativeDiagnosticKeys = nextKeys;
|
|
1078
|
+
});
|
|
1079
|
+
effect("manifest", [d.manifestRequestKey, d.manifestUrl, d.mapName], () => {
|
|
1080
|
+
let cancelled = false;
|
|
1081
|
+
const { manifestRequestKey, manifestUrl, mapName } = d;
|
|
1082
|
+
manifestResolution = { key: manifestRequestKey, map: null, state: "loading" };
|
|
1083
|
+
invalidate();
|
|
1084
|
+
loadTileflowManifest(manifestUrl).then((manifest) => {
|
|
1085
|
+
if (cancelled) return;
|
|
1086
|
+
const resolvedMap = manifest && mapName ? resolveTileflowManifestMap(manifest, mapName) : null;
|
|
1087
|
+
if (!resolvedMap) {
|
|
1088
|
+
reportError(
|
|
1089
|
+
"manifest",
|
|
1090
|
+
new Error(
|
|
1091
|
+
manifest ? `The Tileflow manifest at ${manifestUrl} has no map named "${mapName}".` : `No Tileflow manifest was found at ${manifestUrl}.`
|
|
1092
|
+
)
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
manifestResolution = {
|
|
1096
|
+
key: manifestRequestKey,
|
|
1097
|
+
map: resolvedMap,
|
|
1098
|
+
state: resolvedMap ? "ready" : "error"
|
|
1099
|
+
};
|
|
1100
|
+
invalidate();
|
|
1101
|
+
}).catch((error) => {
|
|
1102
|
+
if (cancelled) return;
|
|
1103
|
+
reportError("manifest", error);
|
|
1104
|
+
manifestResolution = { key: manifestRequestKey, map: null, state: "error" };
|
|
1105
|
+
invalidate();
|
|
1106
|
+
});
|
|
1107
|
+
return () => {
|
|
1108
|
+
cancelled = true;
|
|
1109
|
+
};
|
|
1110
|
+
});
|
|
1111
|
+
effect("camera", [d.resolvedBearing, d.resolvedCenter, d.resolvedPitch, d.resolvedZoom], () => {
|
|
1112
|
+
map?.jumpTo({
|
|
1113
|
+
bearing: d.resolvedBearing,
|
|
1114
|
+
center: d.resolvedCenter,
|
|
1115
|
+
pitch: d.resolvedPitch,
|
|
1116
|
+
zoom: d.resolvedZoom
|
|
1117
|
+
});
|
|
1118
|
+
});
|
|
1119
|
+
effect("navigationControl", [options.navigationControl, d.resolvedInteractive], () => {
|
|
1120
|
+
navigationBinding?.update(d.resolvedInteractive ? options.navigationControl : false);
|
|
1121
|
+
});
|
|
1122
|
+
effect("cameraFit", [d.cameraFit], () => {
|
|
1123
|
+
if (!map || appliedCameraFitKey === d.cameraFit.key) return;
|
|
1124
|
+
appliedCameraFitKey = d.cameraFit.key;
|
|
1125
|
+
if (d.cameraFit.error) reportError("camera", new Error(d.cameraFit.error));
|
|
1126
|
+
if (d.cameraFit.fit) fitCameraTo(map, d.cameraFit.fit);
|
|
1127
|
+
});
|
|
1128
|
+
effect("reconcileAnnotations", [d.preparedAnnotations], () => {
|
|
1129
|
+
if (!interactionRuntime || !d.preparedAnnotations.ok) return;
|
|
1130
|
+
reconcileAnnotations(interactionRuntime, d.preparedAnnotations.annotations);
|
|
1131
|
+
});
|
|
1132
|
+
effect(
|
|
1133
|
+
"reconcileData",
|
|
1134
|
+
[d.annotationClusters, d.dataOverlays, d.dataSources, d.preparedAnnotations],
|
|
1135
|
+
() => {
|
|
1136
|
+
if (dataRuntime) reconcileData(dataRuntime);
|
|
1137
|
+
}
|
|
1138
|
+
);
|
|
1139
|
+
effect(
|
|
1140
|
+
"restartInteractions",
|
|
1141
|
+
[
|
|
1142
|
+
d.annotationClusters,
|
|
1143
|
+
d.dataOverlays,
|
|
1144
|
+
d.dataSources,
|
|
1145
|
+
d.hasMarkerRenderer,
|
|
1146
|
+
d.hasPopupRenderer,
|
|
1147
|
+
d.hasTooltipRenderer,
|
|
1148
|
+
d.preparedAnnotations,
|
|
1149
|
+
d.preparedInteractions
|
|
1150
|
+
],
|
|
1151
|
+
() => {
|
|
1152
|
+
if (interactionStartDiagnostic && mounted && !mounted.scope.disposed) {
|
|
1153
|
+
attachInteractions(mounted);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
);
|
|
1157
|
+
effect("reconcileInteractions", [d.preparedInteractions], () => {
|
|
1158
|
+
if (!semanticRuntime || !d.preparedInteractions.ok) return;
|
|
1159
|
+
reconcileInteractions(semanticRuntime, d.preparedInteractions.bindings);
|
|
1160
|
+
});
|
|
1161
|
+
effect("controlledInteractionState", [d.preparedInteractionState], () => {
|
|
1162
|
+
const prepared = d.preparedInteractionState;
|
|
1163
|
+
if (!prepared.controlled || !prepared.ok) return;
|
|
1164
|
+
try {
|
|
1165
|
+
stateBridgeDiagnostic = void 0;
|
|
1166
|
+
interactionCoordinator?.setInteractionState(prepared.state);
|
|
1167
|
+
coordinatedInteractionState = prepared.state;
|
|
1168
|
+
} catch (error) {
|
|
1169
|
+
console.error("Failed to update Tileflow interaction state", error);
|
|
1170
|
+
stateBridgeDiagnostic = createOverlayFailureDiagnostic(
|
|
1171
|
+
"Tileflow interaction state could not be updated."
|
|
1172
|
+
);
|
|
1173
|
+
reportDiagnostic(stateBridgeDiagnostic);
|
|
1174
|
+
}
|
|
1175
|
+
invalidate();
|
|
1176
|
+
});
|
|
1177
|
+
effect(
|
|
1178
|
+
"customRenderers",
|
|
1179
|
+
[d.hasMarkerRenderer, d.hasPopupRenderer, d.hasTooltipRenderer],
|
|
1180
|
+
() => {
|
|
1181
|
+
if (!interactionRuntime && !semanticRuntime) return;
|
|
1182
|
+
try {
|
|
1183
|
+
rendererBridgeDiagnostic = void 0;
|
|
1184
|
+
interactionRuntime?.setCustomRenderers({
|
|
1185
|
+
marker: d.hasMarkerRenderer,
|
|
1186
|
+
popup: d.hasPopupRenderer,
|
|
1187
|
+
tooltip: d.hasTooltipRenderer
|
|
1188
|
+
});
|
|
1189
|
+
semanticRuntime?.setCustomRenderers({
|
|
1190
|
+
popup: d.hasPopupRenderer,
|
|
1191
|
+
tooltip: d.hasTooltipRenderer
|
|
1192
|
+
});
|
|
1193
|
+
} catch (error) {
|
|
1194
|
+
console.error("Failed to update Tileflow interaction renderers", error);
|
|
1195
|
+
rendererBridgeDiagnostic = createOverlayFailureDiagnostic(
|
|
1196
|
+
"Tileflow interaction renderers could not be updated."
|
|
1197
|
+
);
|
|
1198
|
+
reportDiagnostic(rendererBridgeDiagnostic);
|
|
1199
|
+
}
|
|
1200
|
+
invalidate();
|
|
1201
|
+
}
|
|
1202
|
+
);
|
|
1203
|
+
effect(
|
|
1204
|
+
"mount",
|
|
1205
|
+
[
|
|
1206
|
+
d.isImageMode,
|
|
1207
|
+
d.resolvedInteractive,
|
|
1208
|
+
d.runtimeMapIdentity,
|
|
1209
|
+
d.runtimeStyle !== null || d.themeResolution.error && map !== null,
|
|
1210
|
+
d.mapOptions
|
|
1211
|
+
],
|
|
1212
|
+
() => d.isImageMode ? void 0 : mountMap()
|
|
1213
|
+
);
|
|
1214
|
+
effect("theme", [d.runtimeStyle, d.themeBlendKey], () => {
|
|
1215
|
+
const controller = themeController;
|
|
1216
|
+
if (!controller || d.themeBlendKey) return;
|
|
1217
|
+
if (!d.runtimeStyle || controller.getCurrent() === d.runtimeStyle && !controller.getBlend()) {
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
requestTheme(controller, d.runtimeStyle, d.themeSelection);
|
|
1221
|
+
});
|
|
1222
|
+
const themeResolutionCause = d.themeResolution.error ? d.themeResolution.cause : void 0;
|
|
1223
|
+
effect("themeError", [d.themeResolution.error, themeResolutionCause], () => {
|
|
1224
|
+
if (d.themeResolution.error) reportError("theme", themeResolutionCause);
|
|
1225
|
+
});
|
|
1226
|
+
effect("blendError", [d.blendResolution.cause], () => {
|
|
1227
|
+
if (d.blendResolution.cause !== void 0) reportError("blend", d.blendResolution.cause);
|
|
1228
|
+
});
|
|
1229
|
+
effect("appearance", [options.colors, options.emphasis], () => {
|
|
1230
|
+
appearanceBinding?.update(
|
|
1231
|
+
{ colors: options.colors, emphasis: options.emphasis },
|
|
1232
|
+
{ transition: options.themeTransition }
|
|
1233
|
+
);
|
|
1234
|
+
});
|
|
1235
|
+
effect(
|
|
1236
|
+
"blend",
|
|
1237
|
+
[d.blendResolution.styles, d.runtimeStyle, d.themeBlendHold, d.themeBlendPosition],
|
|
1238
|
+
() => {
|
|
1239
|
+
const controller = themeController;
|
|
1240
|
+
const styles = d.blendResolution.styles;
|
|
1241
|
+
const position = d.themeBlendPosition;
|
|
1242
|
+
if (!controller || !styles || position === void 0) return;
|
|
1243
|
+
if (d.runtimeStyle) {
|
|
1244
|
+
activeRuntimeResource = d.runtimeStyle;
|
|
1245
|
+
invalidate();
|
|
1246
|
+
}
|
|
1247
|
+
void controller.setBlend(
|
|
1248
|
+
{ hold: d.themeBlendHold, position, themes: styles },
|
|
1249
|
+
{ transition: options.themeTransition }
|
|
1250
|
+
).then((result) => {
|
|
1251
|
+
if (result.status === "failed") {
|
|
1252
|
+
reportError(
|
|
1253
|
+
"blend",
|
|
1254
|
+
result.error ?? new Error("The map themes could not be blended.")
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
);
|
|
1260
|
+
reconciler.commit();
|
|
1261
|
+
syncViews();
|
|
1262
|
+
}
|
|
1263
|
+
function reconcileAnnotations(runtime, annotations) {
|
|
1264
|
+
try {
|
|
1265
|
+
annotationBridgeDiagnostic = void 0;
|
|
1266
|
+
runtime.reconcile(annotations);
|
|
1267
|
+
} catch (error) {
|
|
1268
|
+
console.error("Failed to reconcile Tileflow annotations", error);
|
|
1269
|
+
annotationBridgeDiagnostic = createOverlayFailureDiagnostic(
|
|
1270
|
+
"Tileflow annotations could not be reconciled."
|
|
1271
|
+
);
|
|
1272
|
+
reportDiagnostic(annotationBridgeDiagnostic);
|
|
1273
|
+
}
|
|
1274
|
+
invalidate();
|
|
1275
|
+
}
|
|
1276
|
+
function reconcileData(runtime) {
|
|
1277
|
+
try {
|
|
1278
|
+
dataBridgeDiagnostic = void 0;
|
|
1279
|
+
runtime.reconcile(dataInput);
|
|
1280
|
+
} catch (error) {
|
|
1281
|
+
console.error("Failed to reconcile Tileflow data layers", error);
|
|
1282
|
+
dataBridgeDiagnostic = createOverlayFailureDiagnostic(
|
|
1283
|
+
"Tileflow data layers could not be reconciled."
|
|
1284
|
+
);
|
|
1285
|
+
reportDiagnostic(dataBridgeDiagnostic);
|
|
1286
|
+
}
|
|
1287
|
+
invalidate();
|
|
1288
|
+
}
|
|
1289
|
+
function reconcileInteractions(runtime, bindings) {
|
|
1290
|
+
try {
|
|
1291
|
+
semanticBridgeDiagnostic = void 0;
|
|
1292
|
+
runtime.reconcile(bindings);
|
|
1293
|
+
} catch (error) {
|
|
1294
|
+
console.error("Failed to reconcile Tileflow semantic interactions", error);
|
|
1295
|
+
semanticBridgeDiagnostic = createOverlayFailureDiagnostic(
|
|
1296
|
+
"Tileflow semantic interactions could not be reconciled."
|
|
1297
|
+
);
|
|
1298
|
+
reportDiagnostic(semanticBridgeDiagnostic);
|
|
1299
|
+
}
|
|
1300
|
+
invalidate();
|
|
1301
|
+
}
|
|
1302
|
+
let lastRenderTargets = {
|
|
1303
|
+
annotations: annotationRenderTargets,
|
|
1304
|
+
semantic: semanticRenderTargets
|
|
1305
|
+
};
|
|
1306
|
+
function syncViews() {
|
|
1307
|
+
if (lastRenderTargets.annotations !== annotationRenderTargets || lastRenderTargets.semantic !== semanticRenderTargets) {
|
|
1308
|
+
lastRenderTargets = { annotations: annotationRenderTargets, semantic: semanticRenderTargets };
|
|
1309
|
+
options.renderTargets?.onChange(lastRenderTargets);
|
|
1310
|
+
}
|
|
1311
|
+
viewHost.sync(lastRenderTargets, options.renderTargets ? {} : options.views ?? {});
|
|
1312
|
+
}
|
|
1313
|
+
function mountMap() {
|
|
1314
|
+
themeTransitionState = "idle";
|
|
1315
|
+
invalidate();
|
|
1316
|
+
const initialRuntimeStyle = current.runtimeStyle;
|
|
1317
|
+
if (!initialRuntimeStyle) return void 0;
|
|
1318
|
+
activeRuntimeResource = initialRuntimeStyle;
|
|
1319
|
+
captureState = "loading";
|
|
1320
|
+
const scope = createTileflowResourceScope();
|
|
1321
|
+
const { mapOptions } = current;
|
|
1322
|
+
const resourceLifetime = new AbortController();
|
|
1323
|
+
scope.add(() => resourceLifetime.abort());
|
|
1324
|
+
const session = createTileflowSessionController({ source: sessionSource });
|
|
1325
|
+
const resourceTransform = createTileflowTransformRequest({
|
|
1326
|
+
getAnalytics: () => current.resolvedAnalytics,
|
|
1327
|
+
sessionController: session,
|
|
1328
|
+
sessionId: session.sessionId,
|
|
1329
|
+
transformRequest: mapOptions?.transformRequest ?? void 0
|
|
1330
|
+
});
|
|
1331
|
+
const styleLoadOptions = {
|
|
1332
|
+
signal: resourceLifetime.signal,
|
|
1333
|
+
transformRequest: (url, type) => resourceTransform(
|
|
1334
|
+
url,
|
|
1335
|
+
type
|
|
1336
|
+
)
|
|
1337
|
+
};
|
|
1338
|
+
void Promise.all([
|
|
1339
|
+
loadTileflowMapLibre(),
|
|
1340
|
+
loadTileflowRuntimeStyle(initialRuntimeStyle, styleLoadOptions).then(async (prepared) => {
|
|
1341
|
+
await loadTileflowStyleFonts(prepared.style, { fontFaces: prepared.fontFaces });
|
|
1342
|
+
return prepared;
|
|
1343
|
+
})
|
|
1344
|
+
]).then(([maplibregl, prepared]) => {
|
|
1345
|
+
if (scope.disposed || destroyed) return;
|
|
1346
|
+
createMap(scope, maplibregl, prepared, { initialRuntimeStyle, session, styleLoadOptions });
|
|
1347
|
+
}).catch((error) => {
|
|
1348
|
+
const cancelled = scope.disposed || destroyed;
|
|
1349
|
+
try {
|
|
1350
|
+
scope.dispose();
|
|
1351
|
+
} catch (cleanupError) {
|
|
1352
|
+
console.error("Failed to release a Tileflow map that could not start", cleanupError);
|
|
1353
|
+
}
|
|
1354
|
+
if (cancelled) return;
|
|
1355
|
+
reportError("runtime", error);
|
|
1356
|
+
captureState = "error";
|
|
1357
|
+
invalidate();
|
|
1358
|
+
});
|
|
1359
|
+
return () => scope.dispose();
|
|
1360
|
+
}
|
|
1361
|
+
function createMap(scope, maplibregl, prepared, context) {
|
|
1362
|
+
const { initialRuntimeStyle, session, styleLoadOptions } = context;
|
|
1363
|
+
const { mapOptions, resolvedInteractive } = current;
|
|
1364
|
+
scope.add(() => {
|
|
1365
|
+
map = null;
|
|
1366
|
+
mounted = void 0;
|
|
1367
|
+
appliedTheme = void 0;
|
|
1368
|
+
invalidate();
|
|
1369
|
+
});
|
|
1370
|
+
const sessionStarter = createTileflowSessionStarter({
|
|
1371
|
+
getSessionId: () => session.sessionId,
|
|
1372
|
+
sessionId: session.sessionId,
|
|
1373
|
+
source: sessionSource
|
|
1374
|
+
});
|
|
1375
|
+
const fairUseNotice = attachTileflowFairUseNotice(container);
|
|
1376
|
+
scope.add(() => fairUseNotice.dispose());
|
|
1377
|
+
registerTileflowContourProtocol({ addProtocol: maplibregl.addProtocol });
|
|
1378
|
+
registerTileflowPathGroundProtocol({ addProtocol: maplibregl.addProtocol });
|
|
1379
|
+
registerTileflowPmtilesProtocol({ addProtocol: maplibregl.addProtocol });
|
|
1380
|
+
const worldRequestBridge = registerTileflowWorldRequestBridge({
|
|
1381
|
+
addProtocol: maplibregl.addProtocol,
|
|
1382
|
+
onNotice: fairUseNotice.update
|
|
1383
|
+
});
|
|
1384
|
+
scope.add(() => worldRequestBridge.dispose());
|
|
1385
|
+
const contourRequestScope = createTileflowContourRequestScope({
|
|
1386
|
+
transformRequest: (url, resourceType) => transformRequest(
|
|
1387
|
+
url,
|
|
1388
|
+
resourceType
|
|
1389
|
+
)
|
|
1390
|
+
});
|
|
1391
|
+
scope.add(() => contourRequestScope.dispose());
|
|
1392
|
+
const transformRequest = createTileflowTransformRequest({
|
|
1393
|
+
contourRequestScope,
|
|
1394
|
+
getAnalytics: () => current.resolvedAnalytics,
|
|
1395
|
+
sessionController: session,
|
|
1396
|
+
sessionId: session.sessionId,
|
|
1397
|
+
transformRequest: mapOptions?.transformRequest ?? void 0,
|
|
1398
|
+
worldRequestBridge
|
|
1399
|
+
});
|
|
1400
|
+
const mapLibreMap = new maplibregl.Map({
|
|
1401
|
+
...mapOptions,
|
|
1402
|
+
canvasContextAttributes: tileflowCanvasContextAttributes(mapOptions?.canvasContextAttributes),
|
|
1403
|
+
container,
|
|
1404
|
+
style: prepared.style,
|
|
1405
|
+
bearing: current.resolvedBearing,
|
|
1406
|
+
center: current.resolvedCenter,
|
|
1407
|
+
pitch: current.resolvedPitch,
|
|
1408
|
+
zoom: current.resolvedZoom,
|
|
1409
|
+
interactive: resolvedInteractive,
|
|
1410
|
+
attributionControl: mapOptions?.attributionControl ?? { compact: true },
|
|
1411
|
+
// Tileflow's compiled styles are validated when they are built.
|
|
1412
|
+
validateStyle: mapOptions?.validateStyle ?? false,
|
|
1413
|
+
maxPitch: mapOptions?.maxPitch ?? tileflowMaxPitch,
|
|
1414
|
+
transformRequest
|
|
1415
|
+
});
|
|
1416
|
+
map = mapLibreMap;
|
|
1417
|
+
let removedByMapLibre = false;
|
|
1418
|
+
scope.add(() => {
|
|
1419
|
+
if (!removedByMapLibre) mapLibreMap.remove();
|
|
1420
|
+
});
|
|
1421
|
+
const handleRemove = () => {
|
|
1422
|
+
removedByMapLibre = true;
|
|
1423
|
+
if (map !== mapLibreMap) return;
|
|
1424
|
+
map = null;
|
|
1425
|
+
queueMicrotask(teardown);
|
|
1426
|
+
};
|
|
1427
|
+
mapLibreMap.on("remove", handleRemove);
|
|
1428
|
+
scope.add(() => mapLibreMap.off("remove", handleRemove));
|
|
1429
|
+
scope.add(attachTileflowAtmosphere(mapLibreMap));
|
|
1430
|
+
scope.add(attachTileflowBranding(mapLibreMap));
|
|
1431
|
+
scope.add(attachTileflowNotices(mapLibreMap));
|
|
1432
|
+
scope.add(attachTileflowNauticalLights(mapLibreMap));
|
|
1433
|
+
scope.add(attachTileflowLabelPlacement(mapLibreMap));
|
|
1434
|
+
scope.add(attachTileflowHorizonQueries(mapLibreMap));
|
|
1435
|
+
let lifecycle;
|
|
1436
|
+
const controller = initialRuntimeStyle.theme === void 0 ? void 0 : createTileflowThemeController({
|
|
1437
|
+
initial: prepared.family ? prepared : initialRuntimeStyle,
|
|
1438
|
+
initialStyle: prepared.style,
|
|
1439
|
+
styleLoadOptions,
|
|
1440
|
+
map: mapLibreMap,
|
|
1441
|
+
onTransition(transition) {
|
|
1442
|
+
if (themeController !== controller) return;
|
|
1443
|
+
if (transition.phase === "ready") {
|
|
1444
|
+
lifecycle?.invalidate("loading");
|
|
1445
|
+
mapLibreMap.triggerRepaint();
|
|
1446
|
+
}
|
|
1447
|
+
options.onThemeChange?.(transition);
|
|
1448
|
+
themeTransitionState = transition.phase === "preloading" || transition.phase === "applying" ? "loading" : transition.phase === "error" ? "error" : "idle";
|
|
1449
|
+
invalidate();
|
|
1450
|
+
}
|
|
1451
|
+
});
|
|
1452
|
+
if (controller) {
|
|
1453
|
+
themeController = controller;
|
|
1454
|
+
scope.add(() => {
|
|
1455
|
+
controller.dispose();
|
|
1456
|
+
if (themeController === controller) themeController = void 0;
|
|
1457
|
+
});
|
|
1458
|
+
appliedTheme = { selection: current.themeSelection, theme: initialRuntimeStyle.theme };
|
|
1459
|
+
const appearance = createTileflowAppearanceBinding({
|
|
1460
|
+
controller,
|
|
1461
|
+
// CSS variables in `colors` are read where the map sits.
|
|
1462
|
+
element: container,
|
|
1463
|
+
map: mapLibreMap,
|
|
1464
|
+
onError: (error, part) => {
|
|
1465
|
+
if (appearanceBinding === appearance) reportError(part, error);
|
|
1466
|
+
}
|
|
1467
|
+
});
|
|
1468
|
+
appearanceBinding = appearance;
|
|
1469
|
+
scope.add(() => {
|
|
1470
|
+
appearance.dispose();
|
|
1471
|
+
if (appearanceBinding === appearance) appearanceBinding = void 0;
|
|
1472
|
+
});
|
|
1473
|
+
appearance.update(
|
|
1474
|
+
{ colors: options.colors, emphasis: options.emphasis },
|
|
1475
|
+
{ transition: { duration: 0 } }
|
|
1476
|
+
);
|
|
1477
|
+
}
|
|
1478
|
+
const mapScene = attachTileflowMap3D({
|
|
1479
|
+
map: mapLibreMap,
|
|
1480
|
+
...controller ? { controller } : {},
|
|
1481
|
+
vegetation: options.vegetation,
|
|
1482
|
+
viewMode: options.viewMode ?? "3d",
|
|
1483
|
+
load: () => import("@tileflow/core/3d"),
|
|
1484
|
+
onError: (error) => {
|
|
1485
|
+
if (scene === mapScene) reportError("scene", error);
|
|
1486
|
+
},
|
|
1487
|
+
// Direct tile readers use HTTP, not MapLibre's custom World protocol.
|
|
1488
|
+
transformRequest: createTileflowTransformRequest({
|
|
1489
|
+
getAnalytics: () => current.resolvedAnalytics,
|
|
1490
|
+
sessionController: session,
|
|
1491
|
+
sessionId: session.sessionId,
|
|
1492
|
+
transformRequest: (url) => mapOptions?.transformRequest?.(
|
|
1493
|
+
url,
|
|
1494
|
+
"Tile"
|
|
1495
|
+
)
|
|
1496
|
+
})
|
|
1497
|
+
});
|
|
1498
|
+
scene = mapScene;
|
|
1499
|
+
scope.add(() => {
|
|
1500
|
+
mapScene.dispose();
|
|
1501
|
+
if (scene === mapScene) scene = void 0;
|
|
1502
|
+
});
|
|
1503
|
+
mounted = { map: mapLibreMap, maplibregl, scope };
|
|
1504
|
+
attachInteractions(mounted);
|
|
1505
|
+
const navigation = createTileflowNavigationControlBinding({
|
|
1506
|
+
createControl: (controlOptions) => new maplibregl.NavigationControl(controlOptions),
|
|
1507
|
+
map: mapLibreMap
|
|
1508
|
+
});
|
|
1509
|
+
navigationBinding = navigation;
|
|
1510
|
+
scope.add(() => {
|
|
1511
|
+
navigation.dispose();
|
|
1512
|
+
if (navigationBinding === navigation) navigationBinding = void 0;
|
|
1513
|
+
});
|
|
1514
|
+
navigation.update(current.resolvedInteractive ? options.navigationControl : false);
|
|
1515
|
+
const initialFit = current.cameraFit;
|
|
1516
|
+
appliedCameraFitKey = initialFit.key;
|
|
1517
|
+
if (initialFit.error) reportError("camera", new Error(initialFit.error));
|
|
1518
|
+
if (initialFit.fit) fitCameraTo(mapLibreMap, initialFit.fit, 0);
|
|
1519
|
+
const resizeObserver = new ResizeObserver(() => mapLibreMap.resize());
|
|
1520
|
+
scope.add(() => resizeObserver.disconnect());
|
|
1521
|
+
resizeObserver.observe(container);
|
|
1522
|
+
const mapLifecycle = attachTileflowMapLifecycle({
|
|
1523
|
+
getSession: () => {
|
|
1524
|
+
const analyticsForLoad = current.resolvedAnalytics;
|
|
1525
|
+
return {
|
|
1526
|
+
analytics: analyticsForLoad,
|
|
1527
|
+
styleId: analyticsForLoad?.styleId ?? (typeof initialRuntimeStyle.style === "string" ? initialRuntimeStyle.style : current.mapName)
|
|
1528
|
+
};
|
|
1529
|
+
},
|
|
1530
|
+
map: mapLibreMap,
|
|
1531
|
+
onLoad: (loadedMap) => {
|
|
1532
|
+
settleReady({ map: loadedMap });
|
|
1533
|
+
options.onLoad?.(loadedMap);
|
|
1534
|
+
},
|
|
1535
|
+
scheduler: {
|
|
1536
|
+
cancelFrame: (frame) => browserWindow.cancelAnimationFrame(frame),
|
|
1537
|
+
requestFrame: (callback) => browserWindow.requestAnimationFrame(callback)
|
|
1538
|
+
},
|
|
1539
|
+
sessionStarter,
|
|
1540
|
+
setState: (state) => {
|
|
1541
|
+
if (map !== mapLibreMap) return;
|
|
1542
|
+
captureState = state;
|
|
1543
|
+
invalidate();
|
|
1544
|
+
},
|
|
1545
|
+
subscribe: (subscribedMap, event, listener) => {
|
|
1546
|
+
const subscription = subscribedMap.on(event, listener);
|
|
1547
|
+
return () => subscription.unsubscribe();
|
|
1548
|
+
}
|
|
1549
|
+
});
|
|
1550
|
+
lifecycle = mapLifecycle;
|
|
1551
|
+
scope.add(() => {
|
|
1552
|
+
lifecycle = void 0;
|
|
1553
|
+
mapLifecycle.dispose();
|
|
1554
|
+
});
|
|
1555
|
+
const latestRuntimeStyle = current.runtimeStyle;
|
|
1556
|
+
const latestBlend = current.blendResolution.styles && current.themeBlendPosition !== void 0 ? {
|
|
1557
|
+
hold: current.themeBlendHold,
|
|
1558
|
+
position: current.themeBlendPosition,
|
|
1559
|
+
themes: current.blendResolution.styles
|
|
1560
|
+
} : null;
|
|
1561
|
+
if (controller && latestBlend) {
|
|
1562
|
+
if (latestRuntimeStyle) activeRuntimeResource = latestRuntimeStyle;
|
|
1563
|
+
void controller.setBlend(latestBlend, { transition: options.themeTransition });
|
|
1564
|
+
} else if (controller && latestRuntimeStyle && latestRuntimeStyle !== initialRuntimeStyle) {
|
|
1565
|
+
requestTheme(controller, latestRuntimeStyle, current.themeSelection);
|
|
1566
|
+
}
|
|
1567
|
+
invalidate();
|
|
1568
|
+
}
|
|
1569
|
+
function attachInteractions(target) {
|
|
1570
|
+
const { map: mapLibreMap, maplibregl } = target;
|
|
1571
|
+
const scope = target.scope.child();
|
|
1572
|
+
scope.add(() => {
|
|
1573
|
+
interactionRuntime = void 0;
|
|
1574
|
+
semanticRuntime = void 0;
|
|
1575
|
+
interactionCoordinator = void 0;
|
|
1576
|
+
dataRuntime = void 0;
|
|
1577
|
+
annotationDiagnostics = [];
|
|
1578
|
+
annotationRenderTargets = [];
|
|
1579
|
+
annotationBridgeDiagnostic = void 0;
|
|
1580
|
+
semanticDiagnostics = [];
|
|
1581
|
+
semanticRenderTargets = [];
|
|
1582
|
+
semanticBridgeDiagnostic = void 0;
|
|
1583
|
+
stateBridgeDiagnostic = void 0;
|
|
1584
|
+
rendererBridgeDiagnostic = void 0;
|
|
1585
|
+
dataBridgeDiagnostic = void 0;
|
|
1586
|
+
invalidate();
|
|
1587
|
+
});
|
|
1588
|
+
try {
|
|
1589
|
+
startInteractions(scope, mapLibreMap, maplibregl);
|
|
1590
|
+
interactionStartDiagnostic = void 0;
|
|
1591
|
+
} catch (error) {
|
|
1592
|
+
console.error("Failed to start Tileflow map interactions", error);
|
|
1593
|
+
try {
|
|
1594
|
+
scope.dispose();
|
|
1595
|
+
} catch (cleanupError) {
|
|
1596
|
+
console.error("Failed to release Tileflow map interactions", cleanupError);
|
|
1597
|
+
}
|
|
1598
|
+
interactionStartDiagnostic = createOverlayFailureDiagnostic(
|
|
1599
|
+
"Tileflow map interactions could not start."
|
|
1600
|
+
);
|
|
1601
|
+
reportDiagnostic(interactionStartDiagnostic);
|
|
1602
|
+
}
|
|
1603
|
+
invalidate();
|
|
1604
|
+
}
|
|
1605
|
+
function startInteractions(scope, mapLibreMap, maplibregl) {
|
|
1606
|
+
const coordinator = createTileflowMapLibreInteractionCoordinator({
|
|
1607
|
+
...controlledInteractionOwnership ? { interactionState: coordinatedInteractionState } : { defaultInteractionState: coordinatedInteractionState },
|
|
1608
|
+
onInteractionStateChange(state) {
|
|
1609
|
+
if (interactionCoordinator !== coordinator) return;
|
|
1610
|
+
if (!controlledInteractionOwnership) coordinatedInteractionState = state;
|
|
1611
|
+
options.onInteractionStateChange?.(state);
|
|
1612
|
+
}
|
|
1613
|
+
});
|
|
1614
|
+
interactionCoordinator = coordinator;
|
|
1615
|
+
scope.add(() => coordinator.dispose());
|
|
1616
|
+
const createOverlay = ({
|
|
1617
|
+
container: overlayContainer,
|
|
1618
|
+
kind
|
|
1619
|
+
}) => wrapMapLibrePositioned(
|
|
1620
|
+
new maplibregl.Popup({
|
|
1621
|
+
closeButton: false,
|
|
1622
|
+
closeOnClick: false,
|
|
1623
|
+
closeOnMove: false,
|
|
1624
|
+
focusAfterOpen: false,
|
|
1625
|
+
offset: kind === "popup" ? 24 : 18
|
|
1626
|
+
}).setDOMContent(overlayContainer)
|
|
1627
|
+
);
|
|
1628
|
+
const annotationRuntime = createTileflowMapLibreDomRuntime({
|
|
1629
|
+
createMarker({ annotation, element }) {
|
|
1630
|
+
const marker = new maplibregl.Marker({ element });
|
|
1631
|
+
element.title = annotationTitles.get(annotation.id) ?? annotation.ariaLabel;
|
|
1632
|
+
return wrapMapLibrePositioned(marker);
|
|
1633
|
+
},
|
|
1634
|
+
createOverlay,
|
|
1635
|
+
customMarker: current.hasMarkerRenderer,
|
|
1636
|
+
customPopup: current.hasPopupRenderer,
|
|
1637
|
+
customTooltip: current.hasTooltipRenderer,
|
|
1638
|
+
document: container.ownerDocument,
|
|
1639
|
+
interactionState: coordinator.getInteractionState(),
|
|
1640
|
+
map: mapLibreMap,
|
|
1641
|
+
onInteractionStateChange: coordinator.requestInteractionState,
|
|
1642
|
+
onDiagnostic: reportDiagnostic,
|
|
1643
|
+
updateMarker(_marker, { annotation, element }) {
|
|
1644
|
+
element.title = annotationTitles.get(annotation.id) ?? annotation.ariaLabel;
|
|
1645
|
+
}
|
|
1646
|
+
});
|
|
1647
|
+
interactionRuntime = annotationRuntime;
|
|
1648
|
+
scope.add(() => annotationRuntime.dispose());
|
|
1649
|
+
const mapDataRuntime = createTileflowMapLibreDataRuntime({
|
|
1650
|
+
createMarker: ({ element }) => wrapMapLibrePositioned(new maplibregl.Marker({ element })),
|
|
1651
|
+
document: container.ownerDocument,
|
|
1652
|
+
map: mapLibreMap,
|
|
1653
|
+
onDiagnostic: reportDiagnostic,
|
|
1654
|
+
onInteractionEvent(event) {
|
|
1655
|
+
options.onInteractionEvent?.(event);
|
|
1656
|
+
},
|
|
1657
|
+
setHiddenAnnotations: annotationRuntime.setHiddenAnnotations
|
|
1658
|
+
});
|
|
1659
|
+
dataRuntime = mapDataRuntime;
|
|
1660
|
+
scope.add(() => mapDataRuntime.dispose());
|
|
1661
|
+
const featureRuntime = createTileflowMapLibreSemanticDomRuntime({
|
|
1662
|
+
cancelFrame: (frame) => browserWindow.cancelAnimationFrame(frame),
|
|
1663
|
+
createOverlay,
|
|
1664
|
+
customPopup: current.hasPopupRenderer,
|
|
1665
|
+
customTooltip: current.hasTooltipRenderer,
|
|
1666
|
+
document: container.ownerDocument,
|
|
1667
|
+
interactionState: coordinator.getInteractionState(),
|
|
1668
|
+
map: mapLibreMap,
|
|
1669
|
+
onDiagnostic: reportDiagnostic,
|
|
1670
|
+
onInteractionStateChange: coordinator.requestInteractionState,
|
|
1671
|
+
poiMap: createTileflowMapLibrePoiMap(mapLibreMap),
|
|
1672
|
+
requestFrame: (callback) => browserWindow.requestAnimationFrame(() => callback())
|
|
1673
|
+
});
|
|
1674
|
+
semanticRuntime = featureRuntime;
|
|
1675
|
+
scope.add(() => featureRuntime.dispose());
|
|
1676
|
+
scope.add(coordinator.attach("annotation", annotationRuntime));
|
|
1677
|
+
scope.add(coordinator.attach("semantic", featureRuntime));
|
|
1678
|
+
scope.add(annotationRuntime.subscribeEvents((event) => options.onInteractionEvent?.(event)));
|
|
1679
|
+
scope.add(
|
|
1680
|
+
annotationRuntime.subscribeDiagnostics((diagnostics) => {
|
|
1681
|
+
annotationDiagnostics = diagnostics;
|
|
1682
|
+
invalidate();
|
|
1683
|
+
})
|
|
1684
|
+
);
|
|
1685
|
+
scope.add(
|
|
1686
|
+
annotationRuntime.subscribeRenderTargets((targets) => {
|
|
1687
|
+
annotationRenderTargets = targets;
|
|
1688
|
+
invalidate();
|
|
1689
|
+
})
|
|
1690
|
+
);
|
|
1691
|
+
scope.add(
|
|
1692
|
+
featureRuntime.subscribeEvents((event) => {
|
|
1693
|
+
if (event.target.kind === "annotation") return;
|
|
1694
|
+
options.onInteractionEvent?.({ ...event, target: event.target });
|
|
1695
|
+
})
|
|
1696
|
+
);
|
|
1697
|
+
scope.add(
|
|
1698
|
+
featureRuntime.subscribeDiagnostics((diagnostics) => {
|
|
1699
|
+
semanticDiagnostics = diagnostics;
|
|
1700
|
+
invalidate();
|
|
1701
|
+
})
|
|
1702
|
+
);
|
|
1703
|
+
scope.add(
|
|
1704
|
+
featureRuntime.subscribeRenderTargets((targets) => {
|
|
1705
|
+
semanticRenderTargets = targets;
|
|
1706
|
+
invalidate();
|
|
1707
|
+
})
|
|
1708
|
+
);
|
|
1709
|
+
annotationDiagnostics = annotationRuntime.getDiagnostics();
|
|
1710
|
+
annotationRenderTargets = annotationRuntime.getRenderTargets();
|
|
1711
|
+
semanticDiagnostics = featureRuntime.getDiagnostics();
|
|
1712
|
+
semanticRenderTargets = featureRuntime.getRenderTargets();
|
|
1713
|
+
reconcileAnnotations(annotationRuntime, annotationsForRuntime);
|
|
1714
|
+
reconcileData(mapDataRuntime);
|
|
1715
|
+
const initialInteractions = current.preparedInteractions;
|
|
1716
|
+
if (initialInteractions.ok) reconcileInteractions(featureRuntime, initialInteractions.bindings);
|
|
1717
|
+
}
|
|
1718
|
+
function requestTheme(controller, style, selection) {
|
|
1719
|
+
const requested = { selection: options.theme, theme: current.themeResolution.name };
|
|
1720
|
+
const standingIn = current.themeRequestFailed || current.themeResolution.error;
|
|
1721
|
+
activeRuntimeResource = style;
|
|
1722
|
+
invalidate();
|
|
1723
|
+
void controller.setTheme(style, { transition: options.themeTransition }).then((result) => {
|
|
1724
|
+
if (destroyed || themeController !== controller) return;
|
|
1725
|
+
if (result.status === "applied") {
|
|
1726
|
+
appliedTheme = { selection, theme: result.theme };
|
|
1727
|
+
if (!standingIn) failedTheme = void 0;
|
|
1728
|
+
} else if (result.status === "failed") {
|
|
1729
|
+
const shown = controller.getCurrent().theme;
|
|
1730
|
+
const applied = appliedTheme ?? { selection: shown, theme: shown };
|
|
1731
|
+
failedTheme = {
|
|
1732
|
+
...requested,
|
|
1733
|
+
fallback: standingIn ? { selection: applied.theme, theme: applied.theme } : applied
|
|
1734
|
+
};
|
|
1735
|
+
reportError("theme", result.error ?? new Error("The map theme could not be changed."));
|
|
1736
|
+
}
|
|
1737
|
+
invalidate();
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
function computeStatus() {
|
|
1741
|
+
const d = current;
|
|
1742
|
+
const diagnostics = [
|
|
1743
|
+
...d.declarativeDiagnostics,
|
|
1744
|
+
...annotationBridgeDiagnostic ? [annotationBridgeDiagnostic] : [],
|
|
1745
|
+
...semanticBridgeDiagnostic ? [semanticBridgeDiagnostic] : [],
|
|
1746
|
+
...stateBridgeDiagnostic ? [stateBridgeDiagnostic] : [],
|
|
1747
|
+
...rendererBridgeDiagnostic ? [rendererBridgeDiagnostic] : [],
|
|
1748
|
+
...viewBridgeDiagnostic ? [viewBridgeDiagnostic] : [],
|
|
1749
|
+
...dataBridgeDiagnostic ? [dataBridgeDiagnostic] : [],
|
|
1750
|
+
...interactionStartDiagnostic ? [interactionStartDiagnostic] : [],
|
|
1751
|
+
...annotationDiagnostics,
|
|
1752
|
+
...semanticDiagnostics
|
|
1753
|
+
];
|
|
1754
|
+
const interactionReadiness = diagnostics.some(
|
|
1755
|
+
(diagnostic) => diagnostic.level === "error"
|
|
1756
|
+
) ? "error" : viewHost.isSettled() ? "idle" : "loading";
|
|
1757
|
+
const currentCaptureState = activeRuntimeResource === (d.isImageMode ? d.runtimeImageUrl : d.runtimeStyle) ? captureState : "loading";
|
|
1758
|
+
const parts = [
|
|
1759
|
+
currentCaptureState,
|
|
1760
|
+
interactionReadiness,
|
|
1761
|
+
d.runtimeResolutionState,
|
|
1762
|
+
themeTransitionState
|
|
1763
|
+
];
|
|
1764
|
+
return {
|
|
1765
|
+
diagnostics,
|
|
1766
|
+
readiness: parts.includes("error") ? "error" : parts.every((part) => part === "idle") ? "idle" : "loading",
|
|
1767
|
+
theme: d.shownThemeResolution.name,
|
|
1768
|
+
imageUrl: d.isImageMode ? d.runtimeImageUrl : void 0
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
function emitStatus() {
|
|
1772
|
+
if (destroyed) return;
|
|
1773
|
+
const status = computeStatus();
|
|
1774
|
+
if (lastStatus && lastStatus.readiness === status.readiness && lastStatus.theme === status.theme && lastStatus.imageUrl === status.imageUrl && lastStatus.diagnostics.length === status.diagnostics.length && lastStatus.diagnostics.every((diagnostic, index) => diagnostic === status.diagnostics[index])) {
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
lastStatus = status;
|
|
1778
|
+
if (current.isImageMode && status.readiness === "idle") settleReady({ map: null });
|
|
1779
|
+
if (statusElement) {
|
|
1780
|
+
setAttribute(statusElement, "data-tileflow-capture-id", captureId);
|
|
1781
|
+
setAttribute(statusElement, "data-tileflow-map", current.mapName);
|
|
1782
|
+
setAttribute(statusElement, "data-tileflow-theme", status.theme);
|
|
1783
|
+
setAttribute(statusElement, "data-tileflow-state", status.readiness);
|
|
1784
|
+
}
|
|
1785
|
+
options.onStatusChange?.(status);
|
|
1786
|
+
}
|
|
1787
|
+
function update(next) {
|
|
1788
|
+
if (destroyed) return;
|
|
1789
|
+
const merged = { ...options, ...next };
|
|
1790
|
+
assertTileflowMapStyleOptions(merged);
|
|
1791
|
+
if (merged.views && merged.renderTargets) {
|
|
1792
|
+
throw new TypeError("A Tileflow map takes views or renderTargets, not both.");
|
|
1793
|
+
}
|
|
1794
|
+
options = merged;
|
|
1795
|
+
if (reconciling) {
|
|
1796
|
+
dirty = true;
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1799
|
+
reconcile();
|
|
1800
|
+
}
|
|
1801
|
+
function teardown() {
|
|
1802
|
+
if (destroyed) return;
|
|
1803
|
+
destroyed = true;
|
|
1804
|
+
settleReady({ error: new DOMException("The Tileflow map was destroyed.", "AbortError") });
|
|
1805
|
+
try {
|
|
1806
|
+
runTileflowCleanups([() => viewHost.dispose(), () => reconciler.dispose()]);
|
|
1807
|
+
} catch (error) {
|
|
1808
|
+
console.error("Failed to release a Tileflow map", error);
|
|
1809
|
+
} finally {
|
|
1810
|
+
map = null;
|
|
1811
|
+
mounted = void 0;
|
|
1812
|
+
if (statusElement) {
|
|
1813
|
+
for (const attribute of statusAttributes) statusElement.removeAttribute(attribute);
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
reconcile();
|
|
1818
|
+
return {
|
|
1819
|
+
container,
|
|
1820
|
+
destroy: teardown,
|
|
1821
|
+
getInteractionState: () => interactionCoordinator?.getInteractionState() ?? coordinatedInteractionState,
|
|
1822
|
+
getMap: () => map,
|
|
1823
|
+
getRenderTargets: () => lastRenderTargets,
|
|
1824
|
+
getStatus: () => lastStatus ?? computeStatus(),
|
|
1825
|
+
getThemeController: () => map ? themeController ?? null : null,
|
|
1826
|
+
ready,
|
|
1827
|
+
setAnnotations: (annotations) => update({ annotations }),
|
|
1828
|
+
setBounds: (bounds, fitBoundsOptions) => update(fitBoundsOptions === void 0 ? { bounds } : { bounds, fitBoundsOptions }),
|
|
1829
|
+
setColors: (colors) => update({ colors }),
|
|
1830
|
+
setEmphasis: (emphasis) => update({ emphasis }),
|
|
1831
|
+
setInteractions: (interactions) => update({ interactions }),
|
|
1832
|
+
setInteractionState(state) {
|
|
1833
|
+
if (controlledInteractionOwnership) {
|
|
1834
|
+
update({ interactionState: state });
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
const parsed = tileflowInteractionStateSchema2.parse(state);
|
|
1838
|
+
coordinatedInteractionState = parsed;
|
|
1839
|
+
interactionCoordinator?.setInteractionState(parsed);
|
|
1840
|
+
},
|
|
1841
|
+
setTheme: (theme) => update({ theme }),
|
|
1842
|
+
setThemeBlend: (themeBlend) => update({ themeBlend }),
|
|
1843
|
+
setView: (view) => update(view),
|
|
1844
|
+
setViews: (views) => update({ views }),
|
|
1845
|
+
update
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
function tileflowMapRenderers(options) {
|
|
1849
|
+
const external = options.renderTargets;
|
|
1850
|
+
return external ? {
|
|
1851
|
+
hasMarkerRenderer: external.marker === true,
|
|
1852
|
+
hasPopupRenderer: external.popup === true,
|
|
1853
|
+
hasTooltipRenderer: external.tooltip === true
|
|
1854
|
+
} : {
|
|
1855
|
+
hasMarkerRenderer: options.views?.marker !== void 0,
|
|
1856
|
+
hasPopupRenderer: options.views?.popup !== void 0,
|
|
1857
|
+
hasTooltipRenderer: options.views?.tooltip !== void 0
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
function hasTileflowMapInteractionConfiguration(options, renderers) {
|
|
1861
|
+
return options.annotationClusters !== void 0 || options.annotations !== void 0 || options.dataOverlays !== void 0 || options.dataSources !== void 0 || options.defaultInteractionState !== void 0 || options.interactionState !== void 0 || options.interactions !== void 0 || options.onInteractionDiagnostic !== void 0 || options.onInteractionEvent !== void 0 || options.onInteractionStateChange !== void 0 || renderers.hasMarkerRenderer || renderers.hasPopupRenderer || renderers.hasTooltipRenderer;
|
|
1862
|
+
}
|
|
1863
|
+
function tileflowMapDeclarativeDiagnostics(input) {
|
|
1864
|
+
const modeDiagnostic = input.isImageMode ? imageModeMapInteractionDiagnostic(
|
|
1865
|
+
input.preparedAnnotations.annotations.length,
|
|
1866
|
+
input.interactions.length,
|
|
1867
|
+
input.hasInteractionConfiguration
|
|
1868
|
+
) : void 0;
|
|
1869
|
+
return [
|
|
1870
|
+
...modeDiagnostic ? [modeDiagnostic] : [],
|
|
1871
|
+
...input.preparedAnnotations.diagnostics,
|
|
1872
|
+
...input.preparedInteractions.diagnostics,
|
|
1873
|
+
...input.preparedInteractionState.diagnostics,
|
|
1874
|
+
...input.isImageMode ? [] : unusedTileflowRendererDiagnostics({
|
|
1875
|
+
annotations: input.preparedAnnotations.annotations,
|
|
1876
|
+
bindings: input.interactions,
|
|
1877
|
+
hasPopupRenderer: input.hasPopupRenderer,
|
|
1878
|
+
hasTooltipRenderer: input.hasTooltipRenderer
|
|
1879
|
+
})
|
|
1880
|
+
];
|
|
1881
|
+
}
|
|
1882
|
+
function getTileflowMapInitialStatus(options, controlledInteractionOwnership = options.interactionState !== void 0) {
|
|
1883
|
+
const interactions = options.interactions ?? noInteractions;
|
|
1884
|
+
const renderers = tileflowMapRenderers(options);
|
|
1885
|
+
const isImageMode = options.mode === "image";
|
|
1886
|
+
const diagnostics = tileflowMapDeclarativeDiagnostics({
|
|
1887
|
+
hasInteractionConfiguration: hasTileflowMapInteractionConfiguration(options, renderers),
|
|
1888
|
+
hasPopupRenderer: renderers.hasPopupRenderer,
|
|
1889
|
+
hasTooltipRenderer: renderers.hasTooltipRenderer,
|
|
1890
|
+
interactions,
|
|
1891
|
+
isImageMode,
|
|
1892
|
+
preparedAnnotations: prepareTileflowAnnotations(options.annotations),
|
|
1893
|
+
preparedInteractions: prepareTileflowInteractionBindings(interactions),
|
|
1894
|
+
preparedInteractionState: prepareTileflowInteractionState(
|
|
1895
|
+
options.interactionState,
|
|
1896
|
+
options.defaultInteractionState,
|
|
1897
|
+
controlledInteractionOwnership
|
|
1898
|
+
)
|
|
1899
|
+
});
|
|
1900
|
+
const selection = options.themeBlend ? nearestTileflowBlendTheme(options.themeBlend) : options.theme;
|
|
1901
|
+
return {
|
|
1902
|
+
diagnostics,
|
|
1903
|
+
imageUrl: isImageMode ? options.imageUrl : void 0,
|
|
1904
|
+
readiness: diagnostics.some((diagnostic) => diagnostic.level === "error") ? "error" : "loading",
|
|
1905
|
+
theme: selection && selection !== "system" ? selection : void 0
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
function setAttribute(element, name, value) {
|
|
1909
|
+
if (value === void 0) element.removeAttribute(name);
|
|
1910
|
+
else if (element.getAttribute(name) !== value) element.setAttribute(name, value);
|
|
1911
|
+
}
|
|
1912
|
+
function fitCameraTo(map, fit, duration) {
|
|
1913
|
+
map.fitBounds(fit.bounds, {
|
|
1914
|
+
...fit.options.maxZoom === void 0 ? {} : { maxZoom: fit.options.maxZoom },
|
|
1915
|
+
...fit.options.padding === void 0 ? {} : { padding: fit.options.padding },
|
|
1916
|
+
...duration !== void 0 ? { duration } : fit.options.duration === void 0 ? {} : { duration: fit.options.duration }
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
function wrapMapLibrePositioned(positioned) {
|
|
1920
|
+
return {
|
|
1921
|
+
addTo(map) {
|
|
1922
|
+
positioned.addTo(map);
|
|
1923
|
+
},
|
|
1924
|
+
remove() {
|
|
1925
|
+
positioned.remove();
|
|
1926
|
+
},
|
|
1927
|
+
setLngLat(coordinate) {
|
|
1928
|
+
positioned.setLngLat([coordinate[0], coordinate[1]]);
|
|
1929
|
+
}
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
function createTileflowMapLibrePoiMap(map) {
|
|
1933
|
+
const poiMap = map;
|
|
1934
|
+
return {
|
|
1935
|
+
getStyle: () => map.getStyle(),
|
|
1936
|
+
on(event, listener) {
|
|
1937
|
+
return poiMap.on(event, listener);
|
|
1938
|
+
},
|
|
1939
|
+
queryRenderedFeatures(point, queryOptions) {
|
|
1940
|
+
return map.queryRenderedFeatures(point, {
|
|
1941
|
+
layers: [...queryOptions.layers]
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1944
|
+
};
|
|
1945
|
+
}
|
|
1946
|
+
export {
|
|
1947
|
+
configureTileflowMapLibre,
|
|
1948
|
+
createTileflowMap,
|
|
1949
|
+
getTileflowMapInitialStatus,
|
|
1950
|
+
loadTileflowMapLibre
|
|
1951
|
+
};
|