@tileflow/core 0.1.0-alpha.30 → 0.1.0-alpha.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -1
- package/dist/browser.d.ts +747 -15
- package/dist/browser.js +1015 -53
- package/dist/build.d.ts +62 -55
- package/dist/build.js +100 -53
- package/dist/{capture-scene-D6soWztE.d.ts → capture-scene-BaAJjGq7.d.ts} +11 -1
- package/dist/capture.d.ts +1 -1
- package/dist/capture.js +12 -0
- package/dist/config-reference.json +1411 -1367
- package/dist/{icon-composition-BiOTwrvD.d.ts → icon-composition-Befmn-zZ.d.ts} +40 -32
- package/dist/index.d.ts +13 -11
- package/dist/index.js +108 -53
- package/dist/{manifest-types-CTT2eLbp.d.ts → manifest-types-E7m23eXL.d.ts} +18 -1
- package/dist/manifest.d.ts +2 -2
- package/dist/native-profile.d.ts +25 -11
- package/dist/native-profile.js +21 -1
- package/dist/native.d.ts +134 -1
- package/dist/native.js +691 -1
- package/dist/runtime.d.ts +1 -1
- package/docs/authoring-model.md +20 -4
- package/docs/capture-scenes.md +1 -1
- package/docs/compiled-style-performance.md +4 -1
- package/docs/data-is-separate-from-design.md +103 -9
- package/docs/dynamic-style-images.md +100 -0
- package/docs/map-emphasis.md +89 -0
- package/docs/native-artifact-profile.md +5 -3
- package/docs/shared-visual-primitives.md +20 -0
- package/docs/style-states.md +73 -0
- package/docs/theme-transitions-and-blends.md +100 -5
- package/package.json +1 -1
package/dist/browser.js
CHANGED
|
@@ -2042,10 +2042,231 @@ var init_dist = __esm({
|
|
|
2042
2042
|
}
|
|
2043
2043
|
});
|
|
2044
2044
|
|
|
2045
|
+
// src/dynamic-style-images.ts
|
|
2046
|
+
var owners = /* @__PURE__ */ new WeakMap();
|
|
2047
|
+
function isRestoredCopy(image, previous) {
|
|
2048
|
+
if (!image || !previous) return false;
|
|
2049
|
+
const a = image, b2 = previous;
|
|
2050
|
+
if (!a.data || !b2.data || a.data.width !== b2.data.width || a.data.height !== b2.data.height || a.pixelRatio !== b2.pixelRatio || a.sdf !== b2.sdf || a.content !== b2.content || a.stretchX !== b2.stretchX || a.stretchY !== b2.stretchY || a.data.data.length !== b2.data.data.length)
|
|
2051
|
+
return false;
|
|
2052
|
+
return a.data.data.every((byte, index) => byte === b2.data.data[index]);
|
|
2053
|
+
}
|
|
2054
|
+
function createTileflowDynamicImageRegistry(input, context, beforeRestore) {
|
|
2055
|
+
for (const method of [
|
|
2056
|
+
"addImage",
|
|
2057
|
+
"getImage",
|
|
2058
|
+
"getStyle",
|
|
2059
|
+
"removeImage",
|
|
2060
|
+
"updateImage"
|
|
2061
|
+
]) {
|
|
2062
|
+
if (typeof input[method] !== "function") {
|
|
2063
|
+
throw new TypeError(`Dynamic images require a map with ${method}().`);
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
const map = input;
|
|
2067
|
+
const names = owners.get(map) ?? /* @__PURE__ */ new Set();
|
|
2068
|
+
owners.set(map, names);
|
|
2069
|
+
const entries = /* @__PURE__ */ new Map();
|
|
2070
|
+
const view = typeof map.getCanvas === "function" ? map.getCanvas().ownerDocument?.defaultView : void 0;
|
|
2071
|
+
let cancelFrame;
|
|
2072
|
+
let disposed = false;
|
|
2073
|
+
let suspended = false;
|
|
2074
|
+
const imageContext = (entry, current) => Object.freeze({
|
|
2075
|
+
...current,
|
|
2076
|
+
width: entry.pixels.width,
|
|
2077
|
+
height: entry.pixels.height,
|
|
2078
|
+
pixelRatio: entry.pixelRatio
|
|
2079
|
+
});
|
|
2080
|
+
const keyOf = (entry, current) => JSON.stringify([
|
|
2081
|
+
current.theme,
|
|
2082
|
+
current.blend,
|
|
2083
|
+
(entry.definition.states ?? []).map((name) => [
|
|
2084
|
+
name,
|
|
2085
|
+
typeof current.states[name],
|
|
2086
|
+
current.states[name]
|
|
2087
|
+
])
|
|
2088
|
+
]);
|
|
2089
|
+
const report = (entry, cause) => {
|
|
2090
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
2091
|
+
if (entry.definition.onError) entry.definition.onError(error);
|
|
2092
|
+
else console.error(`Tileflow dynamic image "${entry.name}":`, error);
|
|
2093
|
+
};
|
|
2094
|
+
function render(entry, current) {
|
|
2095
|
+
const output = entry.definition.render(imageContext(entry, current));
|
|
2096
|
+
if (output === void 0) return false;
|
|
2097
|
+
if (!(output instanceof Uint8Array || output instanceof Uint8ClampedArray) || output.byteLength !== entry.pixels.width * entry.pixels.height * 4) {
|
|
2098
|
+
throw new TypeError(
|
|
2099
|
+
`Dynamic image "${entry.name}" must return width \xD7 height \xD7 4 RGBA bytes.`
|
|
2100
|
+
);
|
|
2101
|
+
}
|
|
2102
|
+
entry.pixels = { ...entry.pixels, data: Uint8Array.from(output) };
|
|
2103
|
+
return true;
|
|
2104
|
+
}
|
|
2105
|
+
function install(entry, changed = false) {
|
|
2106
|
+
if (suspended) return;
|
|
2107
|
+
const existing = map.getImage(entry.name);
|
|
2108
|
+
if (!existing && !map.getStyle()) return;
|
|
2109
|
+
if (existing && existing !== entry.installed && !isRestoredCopy(existing, entry.recoveryImage)) {
|
|
2110
|
+
throw new Error(`Dynamic image "${entry.name}" conflicts with an existing image owner.`);
|
|
2111
|
+
}
|
|
2112
|
+
if (!existing) map.addImage(entry.name, entry.pixels, { pixelRatio: entry.pixelRatio });
|
|
2113
|
+
else if (changed) map.updateImage(entry.name, entry.pixels);
|
|
2114
|
+
entry.installed = map.getImage(entry.name);
|
|
2115
|
+
entry.recoveryImage = void 0;
|
|
2116
|
+
}
|
|
2117
|
+
function flush() {
|
|
2118
|
+
cancelFrame = void 0;
|
|
2119
|
+
if (disposed || suspended) return;
|
|
2120
|
+
const current = context();
|
|
2121
|
+
for (const entry of entries.values()) {
|
|
2122
|
+
if (!entry.dirty) continue;
|
|
2123
|
+
entry.dirty = false;
|
|
2124
|
+
entry.key = keyOf(entry, current);
|
|
2125
|
+
try {
|
|
2126
|
+
const changed = render(entry, current);
|
|
2127
|
+
install(entry, changed);
|
|
2128
|
+
} catch (error) {
|
|
2129
|
+
report(entry, error);
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
function schedule() {
|
|
2134
|
+
if (cancelFrame || disposed || suspended) return;
|
|
2135
|
+
if (view?.requestAnimationFrame) {
|
|
2136
|
+
const id = view.requestAnimationFrame(flush);
|
|
2137
|
+
cancelFrame = () => view.cancelAnimationFrame(id);
|
|
2138
|
+
} else {
|
|
2139
|
+
let cancelled = false;
|
|
2140
|
+
cancelFrame = () => {
|
|
2141
|
+
cancelled = true;
|
|
2142
|
+
};
|
|
2143
|
+
queueMicrotask(() => {
|
|
2144
|
+
if (!cancelled) flush();
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
function restore() {
|
|
2149
|
+
if (disposed || suspended) return;
|
|
2150
|
+
beforeRestore();
|
|
2151
|
+
for (const entry of entries.values()) {
|
|
2152
|
+
try {
|
|
2153
|
+
install(entry, true);
|
|
2154
|
+
} catch (error) {
|
|
2155
|
+
report(entry, error);
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
if ([...entries.values()].some((entry) => entry.dirty)) schedule();
|
|
2159
|
+
}
|
|
2160
|
+
function loseContext() {
|
|
2161
|
+
suspended = true;
|
|
2162
|
+
for (const entry of entries.values()) entry.recoveryImage = entry.installed;
|
|
2163
|
+
cancelFrame?.();
|
|
2164
|
+
cancelFrame = void 0;
|
|
2165
|
+
}
|
|
2166
|
+
function restoreContext() {
|
|
2167
|
+
suspended = false;
|
|
2168
|
+
restore();
|
|
2169
|
+
}
|
|
2170
|
+
function release(entry, remove) {
|
|
2171
|
+
if (entries.get(entry.name) !== entry) return;
|
|
2172
|
+
entries.delete(entry.name);
|
|
2173
|
+
names.delete(entry.name);
|
|
2174
|
+
if (remove && entry.installed && map.getImage(entry.name) === entry.installed) {
|
|
2175
|
+
map.removeImage(entry.name);
|
|
2176
|
+
}
|
|
2177
|
+
if (!entries.size) {
|
|
2178
|
+
cancelFrame?.();
|
|
2179
|
+
cancelFrame = void 0;
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
function dispose(remove = true) {
|
|
2183
|
+
if (disposed) return;
|
|
2184
|
+
disposed = true;
|
|
2185
|
+
cancelFrame?.();
|
|
2186
|
+
cancelFrame = void 0;
|
|
2187
|
+
for (const entry of [...entries.values()]) release(entry, remove);
|
|
2188
|
+
map.off("style.load", restore);
|
|
2189
|
+
map.off("webglcontextlost", loseContext);
|
|
2190
|
+
map.off("webglcontextrestored", restoreContext);
|
|
2191
|
+
map.off("remove", removed);
|
|
2192
|
+
}
|
|
2193
|
+
const removed = () => dispose(false);
|
|
2194
|
+
map.on("style.load", restore);
|
|
2195
|
+
map.on("webglcontextlost", loseContext);
|
|
2196
|
+
map.on("webglcontextrestored", restoreContext);
|
|
2197
|
+
map.on("remove", removed);
|
|
2198
|
+
return {
|
|
2199
|
+
dispose,
|
|
2200
|
+
assertBlend(images) {
|
|
2201
|
+
for (const image of images) {
|
|
2202
|
+
if (image.names.some((name) => entries.has(name))) {
|
|
2203
|
+
throw new TypeError("Dynamic image names must remain identical across blended themes.");
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
},
|
|
2207
|
+
invalidate(state) {
|
|
2208
|
+
if (disposed) return;
|
|
2209
|
+
const current = context();
|
|
2210
|
+
for (const entry of entries.values()) {
|
|
2211
|
+
if (state !== void 0 && !entry.definition.states?.includes(state)) continue;
|
|
2212
|
+
if (keyOf(entry, current) === entry.key) continue;
|
|
2213
|
+
entry.dirty = true;
|
|
2214
|
+
}
|
|
2215
|
+
if ([...entries.values()].some((entry) => entry.dirty)) schedule();
|
|
2216
|
+
},
|
|
2217
|
+
register(name, definition) {
|
|
2218
|
+
if (disposed) throw new Error("Dynamic image registry is disposed.");
|
|
2219
|
+
if (typeof name !== "string" || !name.trim())
|
|
2220
|
+
throw new TypeError("Dynamic image names must be non-empty.");
|
|
2221
|
+
if (names.has(name))
|
|
2222
|
+
throw new Error(`Dynamic image "${name}" already has a registered owner.`);
|
|
2223
|
+
if (map.getImage(name)) throw new Error(`Dynamic image "${name}" already exists.`);
|
|
2224
|
+
const { width, height, pixelRatio = 1 } = definition;
|
|
2225
|
+
if (![width, height].every((value) => Number.isInteger(value) && value > 0 && value <= 2048) || !Number.isFinite(pixelRatio) || pixelRatio <= 0 || typeof definition.render !== "function" || definition.states !== void 0 && (!Array.isArray(definition.states) || definition.states.some(
|
|
2226
|
+
(state) => typeof state !== "string" || !state.trim() || state !== state.trim()
|
|
2227
|
+
))) {
|
|
2228
|
+
throw new TypeError(
|
|
2229
|
+
"Dynamic images require fixed dimensions, a positive pixel ratio, valid state names and a render function."
|
|
2230
|
+
);
|
|
2231
|
+
}
|
|
2232
|
+
const entry = {
|
|
2233
|
+
name,
|
|
2234
|
+
definition: { ...definition, states: definition.states?.slice() },
|
|
2235
|
+
pixelRatio,
|
|
2236
|
+
pixels: { width, height, data: new Uint8Array(width * height * 4) },
|
|
2237
|
+
dirty: false,
|
|
2238
|
+
key: ""
|
|
2239
|
+
};
|
|
2240
|
+
names.add(name);
|
|
2241
|
+
try {
|
|
2242
|
+
const current = context();
|
|
2243
|
+
entry.key = keyOf(entry, current);
|
|
2244
|
+
render(entry, current);
|
|
2245
|
+
install(entry);
|
|
2246
|
+
entries.set(name, entry);
|
|
2247
|
+
} catch (error) {
|
|
2248
|
+
names.delete(name);
|
|
2249
|
+
throw error;
|
|
2250
|
+
}
|
|
2251
|
+
return {
|
|
2252
|
+
refresh() {
|
|
2253
|
+
if (disposed || entries.get(name) !== entry) return;
|
|
2254
|
+
entry.dirty = true;
|
|
2255
|
+
schedule();
|
|
2256
|
+
},
|
|
2257
|
+
dispose() {
|
|
2258
|
+
release(entry, !disposed);
|
|
2259
|
+
}
|
|
2260
|
+
};
|
|
2261
|
+
}
|
|
2262
|
+
};
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2045
2265
|
// src/browser.ts
|
|
2046
2266
|
init_portable_identity_rules();
|
|
2047
2267
|
|
|
2048
2268
|
// src/runtime.ts
|
|
2269
|
+
init_portable_identity_rules();
|
|
2049
2270
|
var defaultTileflowRuntimeView = Object.freeze({
|
|
2050
2271
|
bearing: 0,
|
|
2051
2272
|
center: [0, 20],
|
|
@@ -2066,6 +2287,11 @@ var maximumManifestBytes = 1024 * 1024;
|
|
|
2066
2287
|
var maximumSessionGrantBytes = 64 * 1024;
|
|
2067
2288
|
var manifestCache = new globalThis.Map();
|
|
2068
2289
|
var tileflowThemeBlendLimits = Object.freeze({ maximumThemes: 8, minimumThemes: 2 });
|
|
2290
|
+
function validateTileflowThemeBlendSelection(value) {
|
|
2291
|
+
if (!value || typeof value !== "object") return false;
|
|
2292
|
+
const { position, themes } = value;
|
|
2293
|
+
return Array.isArray(themes) && themes.length >= tileflowThemeBlendLimits.minimumThemes && themes.length <= tileflowThemeBlendLimits.maximumThemes && themes.every((theme) => isTileflowThemeName(theme)) && typeof position === "number" && Number.isFinite(position) && position >= 0 && position <= themes.length - 1;
|
|
2294
|
+
}
|
|
2069
2295
|
function getTileflowStyleFontFaces(style) {
|
|
2070
2296
|
const input = style.metadata?.[tileflowStyleFontFacesMetadataKey];
|
|
2071
2297
|
if (input === void 0) return [];
|
|
@@ -2238,17 +2464,21 @@ var featureOperators = /* @__PURE__ */ new Set([
|
|
|
2238
2464
|
"properties"
|
|
2239
2465
|
]);
|
|
2240
2466
|
var maximumBranches = 24;
|
|
2241
|
-
function planTileflowThemeBlend(styles) {
|
|
2467
|
+
function planTileflowThemeBlend(styles, options = {}) {
|
|
2242
2468
|
if (styles.length < 2)
|
|
2243
2469
|
throw new TileflowThemeBlendError("A theme blend needs two or more themes.");
|
|
2244
2470
|
const [base] = styles;
|
|
2245
2471
|
assertSameStructure(styles, base);
|
|
2472
|
+
const mixedImages = new Set(imageProperties);
|
|
2473
|
+
if (options.iconImages === "switch") mixedImages.delete("icon-image");
|
|
2246
2474
|
const paints = [];
|
|
2247
2475
|
const switches = [];
|
|
2248
|
-
const imageGroups = collectImageNames(styles, base);
|
|
2476
|
+
const imageGroups = collectImageNames(styles, base, mixedImages);
|
|
2477
|
+
const symbolLayers = /* @__PURE__ */ new Set();
|
|
2249
2478
|
const images = /* @__PURE__ */ new Map();
|
|
2250
2479
|
let splitLayers = 0;
|
|
2251
2480
|
let copiedLayers = 0;
|
|
2481
|
+
let switchedLayers = 0;
|
|
2252
2482
|
const layers = [];
|
|
2253
2483
|
for (const [index, layer] of base.layers.entries()) {
|
|
2254
2484
|
const variants = styles.map((style) => style.layers[index]);
|
|
@@ -2262,6 +2492,7 @@ function planTileflowThemeBlend(styles) {
|
|
|
2262
2492
|
}
|
|
2263
2493
|
const sky = differingRoot(styles, "sky");
|
|
2264
2494
|
const light = differingRoot(styles, "light");
|
|
2495
|
+
const metadata = alignInteractionManifest(base.metadata, layers);
|
|
2265
2496
|
return {
|
|
2266
2497
|
themes: styles.length,
|
|
2267
2498
|
paints,
|
|
@@ -2269,14 +2500,21 @@ function planTileflowThemeBlend(styles) {
|
|
|
2269
2500
|
switches,
|
|
2270
2501
|
...sky === void 0 ? {} : { sky },
|
|
2271
2502
|
...light === void 0 ? {} : { light },
|
|
2272
|
-
summary: { splitLayers, copiedLayers, switchedValues: switches.length },
|
|
2503
|
+
summary: { splitLayers, copiedLayers, switchedLayers, switchedValues: switches.length },
|
|
2273
2504
|
styleAt(position) {
|
|
2274
2505
|
const at = clampPosition(position, styles.length);
|
|
2275
2506
|
const dominant = Math.round(at);
|
|
2276
2507
|
const style = {
|
|
2277
2508
|
...base,
|
|
2509
|
+
...metadata === void 0 ? {} : { metadata },
|
|
2510
|
+
// Symbols show the nearest theme, as the label cross-fade leaves them between changes.
|
|
2278
2511
|
layers: layers.map(
|
|
2279
|
-
(layer) => fillLayer(
|
|
2512
|
+
(layer) => fillLayer(
|
|
2513
|
+
layer,
|
|
2514
|
+
symbolLayers.has(String(layer.id)) ? dominant : at,
|
|
2515
|
+
dominant,
|
|
2516
|
+
switchesByLayer.get(String(layer.id))
|
|
2517
|
+
)
|
|
2280
2518
|
)
|
|
2281
2519
|
};
|
|
2282
2520
|
if (sky !== void 0) style.sky = fillTemplate(sky, at);
|
|
@@ -2301,7 +2539,7 @@ function planTileflowThemeBlend(styles) {
|
|
|
2301
2539
|
(variant) => variant[group]?.[property]
|
|
2302
2540
|
);
|
|
2303
2541
|
if (values.every((value) => same(value, values[0]))) continue;
|
|
2304
|
-
if (
|
|
2542
|
+
if (mixedImages.has(property)) {
|
|
2305
2543
|
const tuples = imageTuples(values);
|
|
2306
2544
|
if (tuples && tuples.every((names) => imageGroups.get(names[0])?.size === 1)) {
|
|
2307
2545
|
for (const names of tuples) {
|
|
@@ -2345,17 +2583,46 @@ function planTileflowThemeBlend(styles) {
|
|
|
2345
2583
|
values: variants.map((variant) => paintOf(variant)[property])
|
|
2346
2584
|
});
|
|
2347
2585
|
}
|
|
2586
|
+
if (options.featureSwitches === "layers" && results.length === 1 && ownSwitches.some((entry) => entry.values.some(isDataDriven)))
|
|
2587
|
+
results = switchLayer();
|
|
2348
2588
|
for (const result of results) {
|
|
2349
2589
|
const resultId = String(result.layer.id);
|
|
2590
|
+
if (type === "symbol") symbolLayers.add(resultId);
|
|
2350
2591
|
const overridden = new Set(result.extra.map((entry) => entry.property));
|
|
2351
2592
|
for (const entry of [
|
|
2352
2593
|
...ownPaints.filter((own) => !overridden.has(own.property)),
|
|
2353
2594
|
...result.extra
|
|
2354
2595
|
])
|
|
2355
2596
|
paints.push({ layer: resultId, property: entry.property, template: entry.template, follows });
|
|
2356
|
-
|
|
2597
|
+
if (result.visibility)
|
|
2598
|
+
switches.push({
|
|
2599
|
+
layer: resultId,
|
|
2600
|
+
group: "layout",
|
|
2601
|
+
property: "visibility",
|
|
2602
|
+
values: result.visibility
|
|
2603
|
+
});
|
|
2604
|
+
else for (const entry of ownSwitches) switches.push({ layer: resultId, ...entry });
|
|
2357
2605
|
}
|
|
2358
2606
|
return results.map((result) => result.layer);
|
|
2607
|
+
function switchLayer() {
|
|
2608
|
+
switchedLayers += variants.length;
|
|
2609
|
+
return variants.map((_2, theme) => {
|
|
2610
|
+
const copyPaint = { ...paint };
|
|
2611
|
+
const copyLayout = { ...layout };
|
|
2612
|
+
for (const entry of ownSwitches) {
|
|
2613
|
+
const target = entry.group === "paint" ? copyPaint : copyLayout;
|
|
2614
|
+
const value = entry.values[theme];
|
|
2615
|
+
if (value === void 0) delete target[entry.property];
|
|
2616
|
+
else target[entry.property] = value;
|
|
2617
|
+
}
|
|
2618
|
+
const shown = copyLayout.visibility === "none" ? "none" : "visible";
|
|
2619
|
+
return {
|
|
2620
|
+
layer: { ...planned, id: `${id}::theme-${theme}`, paint: copyPaint, layout: copyLayout },
|
|
2621
|
+
extra: [],
|
|
2622
|
+
visibility: variants.map((__, nearest) => nearest === theme ? shown : "none")
|
|
2623
|
+
};
|
|
2624
|
+
});
|
|
2625
|
+
}
|
|
2359
2626
|
function splitLayer() {
|
|
2360
2627
|
if (isLegacyFilter(layer.filter)) return void 0;
|
|
2361
2628
|
const branches = splitBranches(
|
|
@@ -2420,6 +2687,15 @@ function planTileflowThemeBlend(styles) {
|
|
|
2420
2687
|
function fillTileflowThemeBlendTemplate(template, position) {
|
|
2421
2688
|
return fillTemplate(template, position);
|
|
2422
2689
|
}
|
|
2690
|
+
function mixTileflowColours(from, to, t) {
|
|
2691
|
+
const a = oklab(from);
|
|
2692
|
+
const b2 = oklab(to);
|
|
2693
|
+
if (!a || !b2) return void 0;
|
|
2694
|
+
return colourFromOklab(a.map((value, channel) => value + (b2[channel] - value) * t));
|
|
2695
|
+
}
|
|
2696
|
+
function readsTileflowFeatureData(value) {
|
|
2697
|
+
return isDataDriven(value) || reads(value, "within") || reads(value, "distance");
|
|
2698
|
+
}
|
|
2423
2699
|
function fillLayer(layer, position, dominant, layerSwitches) {
|
|
2424
2700
|
const filled = { ...layer };
|
|
2425
2701
|
for (const group of ["layout", "paint"]) {
|
|
@@ -2504,11 +2780,11 @@ function differingRoot(styles, key) {
|
|
|
2504
2780
|
if (values.every((value) => same(value, values[0]))) return void 0;
|
|
2505
2781
|
return tryMixTemplate(values);
|
|
2506
2782
|
}
|
|
2507
|
-
function collectImageNames(styles, base) {
|
|
2783
|
+
function collectImageNames(styles, base, properties) {
|
|
2508
2784
|
const uses = /* @__PURE__ */ new Map();
|
|
2509
2785
|
for (const [index] of base.layers.entries()) {
|
|
2510
2786
|
for (const group of ["layout", "paint"]) {
|
|
2511
|
-
for (const property of
|
|
2787
|
+
for (const property of properties) {
|
|
2512
2788
|
const values = styles.map(
|
|
2513
2789
|
(style) => style.layers[index]?.[group]?.[property]
|
|
2514
2790
|
);
|
|
@@ -2523,6 +2799,51 @@ function collectImageNames(styles, base) {
|
|
|
2523
2799
|
}
|
|
2524
2800
|
return uses;
|
|
2525
2801
|
}
|
|
2802
|
+
function alignInteractionManifest(metadata, layers) {
|
|
2803
|
+
if (!isRecord2(metadata)) return void 0;
|
|
2804
|
+
const planned = /* @__PURE__ */ new Map();
|
|
2805
|
+
for (const [index, layer] of layers.entries()) {
|
|
2806
|
+
const id = String(layer.id);
|
|
2807
|
+
const original = /^(.+)::(?:branch|theme)-\d+$/u.exec(id)?.[1] ?? id;
|
|
2808
|
+
planned.set(original, [...planned.get(original) ?? [], { id, index }]);
|
|
2809
|
+
}
|
|
2810
|
+
let aligned;
|
|
2811
|
+
const interactions = metadata["tileflow:interaction-manifest"];
|
|
2812
|
+
const domains = isRecord2(interactions) ? interactions.domains : void 0;
|
|
2813
|
+
if (isRecord2(interactions) && isRecord2(domains) && isRecord2(domains.poi)) {
|
|
2814
|
+
const poi = domains.poi;
|
|
2815
|
+
if (Array.isArray(poi.layers)) {
|
|
2816
|
+
const poiLayers = poi.layers.flatMap((entry) => {
|
|
2817
|
+
if (!isRecord2(entry) || typeof entry.layerId !== "string") return [entry];
|
|
2818
|
+
const copies = planned.get(entry.layerId);
|
|
2819
|
+
if (!copies) return [entry];
|
|
2820
|
+
return copies.map(({ id, index }) => ({ ...entry, layerId: id, priority: index }));
|
|
2821
|
+
});
|
|
2822
|
+
aligned = {
|
|
2823
|
+
...metadata,
|
|
2824
|
+
"tileflow:interaction-manifest": {
|
|
2825
|
+
...interactions,
|
|
2826
|
+
domains: { ...domains, poi: { ...poi, layers: poiLayers } }
|
|
2827
|
+
}
|
|
2828
|
+
};
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
const overlays = metadata["tileflow:overlay-placement-manifest"];
|
|
2832
|
+
if (isRecord2(overlays) && isRecord2(overlays.anchors)) {
|
|
2833
|
+
const anchors = Object.fromEntries(
|
|
2834
|
+
Object.entries(overlays.anchors).map(([placement, anchor]) => [
|
|
2835
|
+
placement,
|
|
2836
|
+
typeof anchor === "string" ? planned.get(anchor)?.[0]?.id ?? anchor : anchor
|
|
2837
|
+
])
|
|
2838
|
+
);
|
|
2839
|
+
aligned = {
|
|
2840
|
+
...aligned ?? metadata,
|
|
2841
|
+
"tileflow:overlay-placement-manifest": { ...overlays, anchors }
|
|
2842
|
+
};
|
|
2843
|
+
}
|
|
2844
|
+
return aligned;
|
|
2845
|
+
}
|
|
2846
|
+
var isRecord2 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2526
2847
|
function imageTuples(values, out = []) {
|
|
2527
2848
|
const [first] = values;
|
|
2528
2849
|
if (values.every((value) => typeof value === "string")) {
|
|
@@ -2789,6 +3110,196 @@ function parseUnit(part, scale) {
|
|
|
2789
3110
|
return percentage ? value / 100 * scale : value;
|
|
2790
3111
|
}
|
|
2791
3112
|
|
|
3113
|
+
// src/layer-domain.ts
|
|
3114
|
+
var tileflowLayerDomainMetadataKey = "tileflow:domain";
|
|
3115
|
+
|
|
3116
|
+
// src/theme-emphasis.ts
|
|
3117
|
+
var colourProperties = {
|
|
3118
|
+
circle: ["circle-color", "circle-stroke-color"],
|
|
3119
|
+
fill: ["fill-color", "fill-outline-color"],
|
|
3120
|
+
"fill-extrusion": ["fill-extrusion-color"],
|
|
3121
|
+
line: ["line-color"]
|
|
3122
|
+
};
|
|
3123
|
+
var patternProperties = {
|
|
3124
|
+
fill: "fill-pattern",
|
|
3125
|
+
"fill-extrusion": "fill-extrusion-pattern",
|
|
3126
|
+
line: "line-pattern"
|
|
3127
|
+
};
|
|
3128
|
+
var opacityProperties2 = {
|
|
3129
|
+
circle: "circle-opacity",
|
|
3130
|
+
fill: "fill-opacity",
|
|
3131
|
+
"fill-extrusion": "fill-extrusion-opacity",
|
|
3132
|
+
heatmap: "heatmap-opacity",
|
|
3133
|
+
line: "line-opacity",
|
|
3134
|
+
raster: "raster-opacity"
|
|
3135
|
+
};
|
|
3136
|
+
var labelProperties = ["text-opacity", "icon-opacity"];
|
|
3137
|
+
var maximumAreaPositions = 4096;
|
|
3138
|
+
function validateTileflowEmphasis(value) {
|
|
3139
|
+
if (!isRecord3(value)) return new TypeError("A Tileflow emphasis must be an object.");
|
|
3140
|
+
for (const key of Object.keys(value)) {
|
|
3141
|
+
if (key !== "recede" && key !== "labels" && key !== "keep") {
|
|
3142
|
+
return new TypeError(`A Tileflow emphasis has no ${JSON.stringify(key)} option.`);
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
if (value.recede !== void 0) {
|
|
3146
|
+
if (!isRecord3(value.recede)) {
|
|
3147
|
+
return new TypeError("A Tileflow emphasis recede must map module names to amounts.");
|
|
3148
|
+
}
|
|
3149
|
+
for (const [module, amount] of Object.entries(value.recede)) {
|
|
3150
|
+
if (!/^[a-z][a-z0-9-]*$/u.test(module) || !isUnit(amount)) {
|
|
3151
|
+
return new TypeError(
|
|
3152
|
+
`A Tileflow emphasis recede amount for ${JSON.stringify(module)} must be a number from 0 to 1.`
|
|
3153
|
+
);
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
if (value.labels !== void 0 && !isUnit(value.labels)) {
|
|
3158
|
+
return new TypeError("A Tileflow emphasis labels share must be a number from 0 to 1.");
|
|
3159
|
+
}
|
|
3160
|
+
if (value.keep !== void 0) {
|
|
3161
|
+
const positions = areaPositions(value.keep);
|
|
3162
|
+
if (positions === void 0) {
|
|
3163
|
+
return new TypeError(
|
|
3164
|
+
"A Tileflow emphasis keep area must be a GeoJSON Polygon or MultiPolygon."
|
|
3165
|
+
);
|
|
3166
|
+
}
|
|
3167
|
+
if (positions > maximumAreaPositions) {
|
|
3168
|
+
return new TypeError(
|
|
3169
|
+
`A Tileflow emphasis keep area may hold at most ${maximumAreaPositions} positions.`
|
|
3170
|
+
);
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
return void 0;
|
|
3174
|
+
}
|
|
3175
|
+
function planTileflowEmphasis(layers, emphasis) {
|
|
3176
|
+
const targets = [];
|
|
3177
|
+
const fadeLabels = emphasis.labels === void 0 ? 0 : 1 - emphasis.labels;
|
|
3178
|
+
for (const layer of layers) {
|
|
3179
|
+
if (!isRecord3(layer) || typeof layer.id !== "string" || typeof layer.type !== "string")
|
|
3180
|
+
continue;
|
|
3181
|
+
const domain = isRecord3(layer.metadata) ? layer.metadata[tileflowLayerDomainMetadataKey] : void 0;
|
|
3182
|
+
if (typeof domain !== "string") continue;
|
|
3183
|
+
const paint = isRecord3(layer.paint) ? layer.paint : {};
|
|
3184
|
+
const add = (property, kind, amount2) => targets.push({
|
|
3185
|
+
amount: amount2,
|
|
3186
|
+
featureData: readsTileflowFeatureData(paint[property]) || kind === "label" && emphasis.keep !== void 0,
|
|
3187
|
+
kind,
|
|
3188
|
+
layer: layer.id,
|
|
3189
|
+
property
|
|
3190
|
+
});
|
|
3191
|
+
if (layer.type === "symbol") {
|
|
3192
|
+
if (fadeLabels > 0)
|
|
3193
|
+
for (const property of labelProperties) add(property, "label", fadeLabels);
|
|
3194
|
+
continue;
|
|
3195
|
+
}
|
|
3196
|
+
if (layer.type === "background") continue;
|
|
3197
|
+
const amount = emphasis.recede?.[domain] ?? 0;
|
|
3198
|
+
if (amount <= 0) continue;
|
|
3199
|
+
const pattern = patternProperties[layer.type];
|
|
3200
|
+
const opacity = opacityProperties2[layer.type];
|
|
3201
|
+
if (pattern && paint[pattern] !== void 0) {
|
|
3202
|
+
if (opacity) add(opacity, "fade", amount);
|
|
3203
|
+
continue;
|
|
3204
|
+
}
|
|
3205
|
+
const colours = (colourProperties[layer.type] ?? []).filter(
|
|
3206
|
+
(property) => paint[property] !== void 0
|
|
3207
|
+
);
|
|
3208
|
+
if (colours.length > 0) for (const property of colours) add(property, "colour", amount);
|
|
3209
|
+
else if (opacity) add(opacity, "fade", amount);
|
|
3210
|
+
}
|
|
3211
|
+
return targets;
|
|
3212
|
+
}
|
|
3213
|
+
function emphasizeTileflowValue(target, value, options) {
|
|
3214
|
+
const amount = target.amount * Math.min(1, Math.max(0, options.strength));
|
|
3215
|
+
if (target.kind === "label") {
|
|
3216
|
+
const keep = options.keep;
|
|
3217
|
+
if (amount <= 0 && !keep) return value;
|
|
3218
|
+
const near = keep ? (original, scaled) => ["case", ["within", keep], original, scaled] : void 0;
|
|
3219
|
+
return scaleOpacity(value, 1 - amount, near);
|
|
3220
|
+
}
|
|
3221
|
+
if (amount <= 0) return value;
|
|
3222
|
+
if (target.kind === "fade") return scaleOpacity(value, 1 - amount);
|
|
3223
|
+
if (options.ground === void 0) return value;
|
|
3224
|
+
return mixColours(value, options.ground, amount);
|
|
3225
|
+
}
|
|
3226
|
+
function tileflowEmphasisGround(layers) {
|
|
3227
|
+
for (const layer of layers) {
|
|
3228
|
+
if (!isRecord3(layer) || layer.type !== "background") continue;
|
|
3229
|
+
const colour = isRecord3(layer.paint) ? layer.paint["background-color"] : void 0;
|
|
3230
|
+
return firstColour(colour);
|
|
3231
|
+
}
|
|
3232
|
+
return void 0;
|
|
3233
|
+
}
|
|
3234
|
+
function firstColour(value) {
|
|
3235
|
+
if (typeof value === "string") {
|
|
3236
|
+
return mixTileflowColours(value, value, 0) === void 0 ? void 0 : value;
|
|
3237
|
+
}
|
|
3238
|
+
if (Array.isArray(value)) {
|
|
3239
|
+
for (const item of value.slice(1)) {
|
|
3240
|
+
const found = firstColour(item);
|
|
3241
|
+
if (found !== void 0) return found;
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
return void 0;
|
|
3245
|
+
}
|
|
3246
|
+
function mixColours(value, toward, amount) {
|
|
3247
|
+
if (typeof value === "string") return mixTileflowColours(value, toward, amount) ?? value;
|
|
3248
|
+
if (Array.isArray(value)) {
|
|
3249
|
+
if (value[0] === "literal") return value;
|
|
3250
|
+
return value.map((item, index) => index === 0 ? item : mixColours(item, toward, amount));
|
|
3251
|
+
}
|
|
3252
|
+
return value;
|
|
3253
|
+
}
|
|
3254
|
+
var isZoomInput = (input) => Array.isArray(input) && input[0] === "zoom";
|
|
3255
|
+
function scaleOpacity(value, factor, near = (_original, scaled) => scaled) {
|
|
3256
|
+
const scale = (item) => scaleOpacity(item, factor, near);
|
|
3257
|
+
if (value === void 0 || value === null) return near(1, factor);
|
|
3258
|
+
if (typeof value === "number") return near(value, round(value * factor));
|
|
3259
|
+
if (Array.isArray(value)) {
|
|
3260
|
+
const operator = value[0];
|
|
3261
|
+
if (typeof operator === "string" && operator.startsWith("interpolate") && isZoomInput(value[2])) {
|
|
3262
|
+
return [
|
|
3263
|
+
...value.slice(0, 3),
|
|
3264
|
+
...value.slice(3).map((item, index) => index % 2 === 1 ? scale(item) : item)
|
|
3265
|
+
];
|
|
3266
|
+
}
|
|
3267
|
+
if (operator === "step" && isZoomInput(value[1])) {
|
|
3268
|
+
return [
|
|
3269
|
+
"step",
|
|
3270
|
+
value[1],
|
|
3271
|
+
scale(value[2]),
|
|
3272
|
+
...value.slice(3).map((item, index) => index % 2 === 1 ? scale(item) : item)
|
|
3273
|
+
];
|
|
3274
|
+
}
|
|
3275
|
+
if (operator === "let") return [...value.slice(0, -1), scale(value.at(-1))];
|
|
3276
|
+
}
|
|
3277
|
+
return near(value, ["*", value, factor]);
|
|
3278
|
+
}
|
|
3279
|
+
var round = (value) => Math.round(value * 1e4) / 1e4;
|
|
3280
|
+
function areaPositions(value) {
|
|
3281
|
+
if (!isRecord3(value) || !Array.isArray(value.coordinates)) return void 0;
|
|
3282
|
+
const polygons = value.type === "Polygon" ? [value.coordinates] : value.type === "MultiPolygon" ? value.coordinates : void 0;
|
|
3283
|
+
if (!polygons) return void 0;
|
|
3284
|
+
let positions = 0;
|
|
3285
|
+
for (const polygon of polygons) {
|
|
3286
|
+
if (!Array.isArray(polygon) || polygon.length === 0) return void 0;
|
|
3287
|
+
for (const ring of polygon) {
|
|
3288
|
+
if (!Array.isArray(ring) || ring.length < 4) return void 0;
|
|
3289
|
+
for (const position of ring) {
|
|
3290
|
+
if (!Array.isArray(position) || position.length < 2 || !position.every(
|
|
3291
|
+
(coordinate) => typeof coordinate === "number" && Number.isFinite(coordinate)
|
|
3292
|
+
))
|
|
3293
|
+
return void 0;
|
|
3294
|
+
positions += 1;
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
return positions;
|
|
3299
|
+
}
|
|
3300
|
+
var isUnit = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
3301
|
+
var isRecord3 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3302
|
+
|
|
2792
3303
|
// src/theme-motion-browser.ts
|
|
2793
3304
|
var settleFrames = 3;
|
|
2794
3305
|
var settleTimeoutMs = 1e4;
|
|
@@ -2896,18 +3407,27 @@ function createTileflowThemeBlender(input, plan, options) {
|
|
|
2896
3407
|
const positionPaints = plan.paints.filter((paint) => paint.follows === "position");
|
|
2897
3408
|
const dominantPaints = plan.paints.filter((paint) => paint.follows === "dominant");
|
|
2898
3409
|
const pixels = createImageMixer(map, plan.images);
|
|
3410
|
+
const mixesArtwork = plan.images.some((image) => image.follows === "position");
|
|
3411
|
+
const relayoutAt = relayoutPositions(plan);
|
|
2899
3412
|
const shownValues = /* @__PURE__ */ new Map();
|
|
3413
|
+
const baseValues = /* @__PURE__ */ new Map();
|
|
3414
|
+
const valueKey = (layer, property) => `${layer}\0${property}`;
|
|
3415
|
+
const transformed = (layer, property, value) => options.transform ? options.transform(layer, property, value) : value;
|
|
2900
3416
|
let position = clamp(options.position);
|
|
2901
3417
|
let dominant = Math.round(position);
|
|
2902
3418
|
let label = dominant;
|
|
2903
3419
|
let fade;
|
|
2904
3420
|
let frameRequest;
|
|
2905
3421
|
let disposed = false;
|
|
3422
|
+
let holding = false;
|
|
3423
|
+
let artworkWaits = false;
|
|
2906
3424
|
pixels.paint("position", position);
|
|
2907
3425
|
pixels.paint("dominant", label);
|
|
2908
3426
|
const drive = (paints, at) => {
|
|
2909
3427
|
for (const paint of paints) {
|
|
2910
|
-
const
|
|
3428
|
+
const base = fillTileflowThemeBlendTemplate(paint.template, at);
|
|
3429
|
+
baseValues.set(valueKey(paint.layer, paint.property), base);
|
|
3430
|
+
const value = transformed(paint.layer, paint.property, base);
|
|
2911
3431
|
const key = JSON.stringify(value);
|
|
2912
3432
|
if (shownValues.get(paint) === key) continue;
|
|
2913
3433
|
shownValues.set(paint, key);
|
|
@@ -2932,18 +3452,53 @@ function createTileflowThemeBlender(input, plan, options) {
|
|
|
2932
3452
|
const applySwitches = (theme) => {
|
|
2933
3453
|
for (const entry of plan.switches) {
|
|
2934
3454
|
const value = entry.values[theme];
|
|
2935
|
-
if (entry.group === "paint")
|
|
2936
|
-
|
|
2937
|
-
|
|
3455
|
+
if (entry.group === "paint") {
|
|
3456
|
+
baseValues.set(valueKey(entry.layer, entry.property), value);
|
|
3457
|
+
map.setPaintProperty(
|
|
3458
|
+
entry.layer,
|
|
3459
|
+
entry.property,
|
|
3460
|
+
transformed(entry.layer, entry.property, value),
|
|
3461
|
+
{ validate: false }
|
|
3462
|
+
);
|
|
3463
|
+
} else map.setLayoutProperty(entry.layer, entry.property, value, { validate: false });
|
|
2938
3464
|
}
|
|
2939
3465
|
};
|
|
3466
|
+
const switchedPaints = plan.switches.filter((entry) => entry.group === "paint");
|
|
2940
3467
|
return {
|
|
3468
|
+
baseValue(layer, property) {
|
|
3469
|
+
return baseValues.get(valueKey(layer, property));
|
|
3470
|
+
},
|
|
2941
3471
|
get dominant() {
|
|
2942
3472
|
return dominant;
|
|
2943
3473
|
},
|
|
3474
|
+
get held() {
|
|
3475
|
+
return holding && (artworkWaits || Math.round(position) !== dominant);
|
|
3476
|
+
},
|
|
2944
3477
|
get position() {
|
|
2945
3478
|
return position;
|
|
2946
3479
|
},
|
|
3480
|
+
relayoutAt,
|
|
3481
|
+
refresh(include) {
|
|
3482
|
+
if (disposed) return;
|
|
3483
|
+
drive(
|
|
3484
|
+
positionPaints.filter((paint) => include(paint.layer, paint.property)),
|
|
3485
|
+
position
|
|
3486
|
+
);
|
|
3487
|
+
drive(
|
|
3488
|
+
dominantPaints.filter((paint) => include(paint.layer, paint.property)),
|
|
3489
|
+
label
|
|
3490
|
+
);
|
|
3491
|
+
for (const entry of switchedPaints) {
|
|
3492
|
+
if (!include(entry.layer, entry.property)) continue;
|
|
3493
|
+
const value = entry.values[dominant];
|
|
3494
|
+
map.setPaintProperty(
|
|
3495
|
+
entry.layer,
|
|
3496
|
+
entry.property,
|
|
3497
|
+
transformed(entry.layer, entry.property, value),
|
|
3498
|
+
{ validate: false }
|
|
3499
|
+
);
|
|
3500
|
+
}
|
|
3501
|
+
},
|
|
2947
3502
|
dispose() {
|
|
2948
3503
|
disposed = true;
|
|
2949
3504
|
if (frameRequest !== void 0 && view) {
|
|
@@ -2953,21 +3508,30 @@ function createTileflowThemeBlender(input, plan, options) {
|
|
|
2953
3508
|
}
|
|
2954
3509
|
frameRequest = void 0;
|
|
2955
3510
|
},
|
|
2956
|
-
set(next) {
|
|
3511
|
+
set(next, setOptions) {
|
|
2957
3512
|
if (disposed) return;
|
|
3513
|
+
const hold = setOptions?.hold === true;
|
|
3514
|
+
if (hold) holding = true;
|
|
2958
3515
|
const target = clamp(next);
|
|
2959
3516
|
const reachesTheme = target % 1 === 0 && target !== position;
|
|
2960
3517
|
if (Math.abs(target - position) >= 25e-4 || reachesTheme) {
|
|
2961
3518
|
position = target;
|
|
2962
3519
|
drive(positionPaints, position);
|
|
2963
|
-
|
|
3520
|
+
if (holding) artworkWaits ||= mixesArtwork;
|
|
3521
|
+
else pixels.paint("position", position);
|
|
2964
3522
|
if (plan.sky !== void 0 && typeof map.setSky === "function")
|
|
2965
3523
|
map.setSky(fillTileflowThemeBlendTemplate(plan.sky, position), { validate: false });
|
|
2966
3524
|
if (plan.light !== void 0 && typeof map.setLight === "function")
|
|
2967
3525
|
map.setLight(fillTileflowThemeBlendTemplate(plan.light, position), { validate: false });
|
|
3526
|
+
options.onPosition?.();
|
|
3527
|
+
}
|
|
3528
|
+
if (!hold && holding) {
|
|
3529
|
+
holding = false;
|
|
3530
|
+
if (artworkWaits) pixels.paint("position", position);
|
|
3531
|
+
artworkWaits = false;
|
|
2968
3532
|
}
|
|
2969
3533
|
const nextDominant = Math.round(target);
|
|
2970
|
-
if (nextDominant === dominant) return;
|
|
3534
|
+
if (nextDominant === dominant || holding) return;
|
|
2971
3535
|
dominant = nextDominant;
|
|
2972
3536
|
if (plan.switches.length > 0) {
|
|
2973
3537
|
const change = () => applySwitches(nextDominant);
|
|
@@ -2984,20 +3548,34 @@ function createImageMixer(map, images) {
|
|
|
2984
3548
|
const art = /* @__PURE__ */ new Map();
|
|
2985
3549
|
const shown = /* @__PURE__ */ new Map();
|
|
2986
3550
|
const unusable = /* @__PURE__ */ new Set();
|
|
3551
|
+
const names = /* @__PURE__ */ new Map();
|
|
3552
|
+
const alike = /* @__PURE__ */ new Map();
|
|
2987
3553
|
const artOf = (name) => {
|
|
2988
3554
|
if (!art.has(name)) {
|
|
2989
3555
|
const image = map.getImage(name)?.data;
|
|
2990
3556
|
if (!image) return void 0;
|
|
2991
|
-
|
|
3557
|
+
const copy = { data: Uint8Array.from(image.data), height: image.height, width: image.width };
|
|
3558
|
+
art.set(name, copy);
|
|
3559
|
+
names.set(copy, name);
|
|
2992
3560
|
}
|
|
2993
3561
|
return art.get(name);
|
|
2994
3562
|
};
|
|
3563
|
+
const same2 = (from, to) => {
|
|
3564
|
+
if (from === to) return true;
|
|
3565
|
+
const key = `${names.get(from)}\0${names.get(to)}`;
|
|
3566
|
+
if (!alike.has(key)) {
|
|
3567
|
+
let equal = from.data.length === to.data.length;
|
|
3568
|
+
for (let byte = 0; equal && byte < from.data.length; byte += 1)
|
|
3569
|
+
equal = from.data[byte] === to.data[byte];
|
|
3570
|
+
alike.set(key, equal);
|
|
3571
|
+
}
|
|
3572
|
+
return alike.get(key);
|
|
3573
|
+
};
|
|
2995
3574
|
return {
|
|
2996
3575
|
paint(follows, position) {
|
|
2997
3576
|
const quantized = Math.round(position * 32) / 32;
|
|
2998
3577
|
for (const image of images) {
|
|
2999
|
-
if (image.follows !== follows || unusable.has(image)
|
|
3000
|
-
continue;
|
|
3578
|
+
if (image.follows !== follows || unusable.has(image)) continue;
|
|
3001
3579
|
const sources = image.names.map(artOf);
|
|
3002
3580
|
if (sources.some((source) => !source)) continue;
|
|
3003
3581
|
const [first] = sources;
|
|
@@ -3005,14 +3583,21 @@ function createImageMixer(map, images) {
|
|
|
3005
3583
|
unusable.add(image);
|
|
3006
3584
|
continue;
|
|
3007
3585
|
}
|
|
3008
|
-
shown.set(image,
|
|
3586
|
+
if (!shown.has(image)) shown.set(image, first);
|
|
3009
3587
|
const index = Math.min(sources.length - 2, Math.floor(quantized));
|
|
3010
3588
|
const t = quantized - index;
|
|
3011
3589
|
const from = sources[index];
|
|
3012
3590
|
const to = sources[index + 1];
|
|
3013
|
-
let
|
|
3014
|
-
if (t >= 1)
|
|
3015
|
-
else if (t > 0 && from
|
|
3591
|
+
let content = from;
|
|
3592
|
+
if (t >= 1) content = to;
|
|
3593
|
+
else if (t > 0 && !same2(from, to)) content = `${index}:${t}`;
|
|
3594
|
+
const before = shown.get(image);
|
|
3595
|
+
if (before === content || typeof before !== "string" && typeof content !== "string" && same2(before, content))
|
|
3596
|
+
continue;
|
|
3597
|
+
shown.set(image, content);
|
|
3598
|
+
let data;
|
|
3599
|
+
if (typeof content !== "string") data = content.data;
|
|
3600
|
+
else {
|
|
3016
3601
|
data = new Uint8Array(from.data.length);
|
|
3017
3602
|
for (let byte = 0; byte < data.length; byte += 1)
|
|
3018
3603
|
data[byte] = from.data[byte] + (to.data[byte] - from.data[byte]) * t;
|
|
@@ -3022,6 +3607,20 @@ function createImageMixer(map, images) {
|
|
|
3022
3607
|
}
|
|
3023
3608
|
};
|
|
3024
3609
|
}
|
|
3610
|
+
function relayoutPositions(plan) {
|
|
3611
|
+
const positions = [];
|
|
3612
|
+
const relays = (entry) => entry.group === "layout" || entry.property.endsWith("-pattern") || entry.values.some(readsTileflowFeatureData);
|
|
3613
|
+
for (let index = 0; index + 1 < plan.themes; index += 1) {
|
|
3614
|
+
const switched = plan.switches.some(
|
|
3615
|
+
(entry) => relays(entry) && JSON.stringify(entry.values[index]) !== JSON.stringify(entry.values[index + 1])
|
|
3616
|
+
);
|
|
3617
|
+
const icons = plan.images.some(
|
|
3618
|
+
(image) => image.follows === "dominant" && image.names[index] !== image.names[index + 1]
|
|
3619
|
+
);
|
|
3620
|
+
if (switched || icons) positions.push(index + 0.5);
|
|
3621
|
+
}
|
|
3622
|
+
return positions;
|
|
3623
|
+
}
|
|
3025
3624
|
function isCrossfadeMap(map) {
|
|
3026
3625
|
return typeof map.getCanvas === "function" && typeof map.redraw === "function" && typeof map.project === "function" && typeof map.unproject === "function" && typeof map.isStyleLoaded === "function" && typeof map.areTilesLoaded === "function";
|
|
3027
3626
|
}
|
|
@@ -4724,6 +5323,7 @@ l(H, "PMTiles");
|
|
|
4724
5323
|
var w = H;
|
|
4725
5324
|
|
|
4726
5325
|
// src/pmtiles-browser.ts
|
|
5326
|
+
var missingRasterTile = null;
|
|
4727
5327
|
var registeredPmtilesProtocols = /* @__PURE__ */ new WeakSet();
|
|
4728
5328
|
var maximumSectionBytes = 16 * 1024 * 1024;
|
|
4729
5329
|
var maximumParserBytes = 32 * 1024 * 1024;
|
|
@@ -4751,7 +5351,9 @@ var TileflowPmtilesProtocol = class {
|
|
|
4751
5351
|
const archive2 = this.#archive(parseArchiveTarget(request.url));
|
|
4752
5352
|
const header2 = await archive2.getHeader();
|
|
4753
5353
|
const tileJson = await archive2.getTileJson(request.url);
|
|
4754
|
-
|
|
5354
|
+
if (!isRecord4(tileJson))
|
|
5355
|
+
throw new TypeError("A PMTiles archive described no TileJSON object.");
|
|
5356
|
+
const data = header2.tileType === O.Mlt ? { ...tileJson, encoding: "mlt" } : tileJson;
|
|
4755
5357
|
abortController.signal.throwIfAborted();
|
|
4756
5358
|
return { data };
|
|
4757
5359
|
}
|
|
@@ -4768,7 +5370,7 @@ var TileflowPmtilesProtocol = class {
|
|
|
4768
5370
|
};
|
|
4769
5371
|
}
|
|
4770
5372
|
return {
|
|
4771
|
-
data: header.tileType === O.Mvt || header.tileType === O.Mlt ? new Uint8Array() :
|
|
5373
|
+
data: header.tileType === O.Mvt || header.tileType === O.Mlt ? new Uint8Array() : missingRasterTile
|
|
4772
5374
|
};
|
|
4773
5375
|
};
|
|
4774
5376
|
tile = protocolCompatibility(this.tilev4);
|
|
@@ -4889,7 +5491,7 @@ function normalizeMaximumDirectoryDepth(value) {
|
|
|
4889
5491
|
}
|
|
4890
5492
|
return depth;
|
|
4891
5493
|
}
|
|
4892
|
-
function
|
|
5494
|
+
function isRecord4(value) {
|
|
4893
5495
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4894
5496
|
}
|
|
4895
5497
|
function protocolCompatibility(handler) {
|
|
@@ -4952,10 +5554,22 @@ function handleSystemColorSchemeChange() {
|
|
|
4952
5554
|
systemColorScheme = next;
|
|
4953
5555
|
for (const listener of systemColorSchemeListeners) listener(next);
|
|
4954
5556
|
}
|
|
5557
|
+
function validateTileflowBrowserThemeBlendSelection(value) {
|
|
5558
|
+
if (!validateTileflowThemeBlendSelection(value)) return false;
|
|
5559
|
+
const { hold } = value;
|
|
5560
|
+
return hold === void 0 || typeof hold === "boolean";
|
|
5561
|
+
}
|
|
4955
5562
|
var maximumBlendThemes = 8;
|
|
4956
5563
|
var maximumTransitionMs = 5e3;
|
|
4957
5564
|
var blendLabelFadeMs = 450;
|
|
4958
5565
|
var maximumTileflowBlendStyleBytes = 16 * 1024 * 1024;
|
|
5566
|
+
function validateStyleState(name, value) {
|
|
5567
|
+
if (typeof name !== "string" || !name.trim())
|
|
5568
|
+
return new TypeError("Style state names must be non-empty strings.");
|
|
5569
|
+
if (value === null || typeof value === "boolean" || typeof value === "string" || typeof value === "number" && Number.isFinite(value))
|
|
5570
|
+
return void 0;
|
|
5571
|
+
return new TypeError("Style state values must be booleans, finite numbers, strings, or null.");
|
|
5572
|
+
}
|
|
4959
5573
|
function createTileflowThemeController(options) {
|
|
4960
5574
|
assertConcreteRuntimeTheme(options.initial, "initial");
|
|
4961
5575
|
const loadFonts = options.loadFonts ?? ((runtimeStyle) => loadTileflowStyleFonts(runtimeStyle.style, { fontFaces: runtimeStyle.fontFaces }));
|
|
@@ -4971,22 +5585,98 @@ function createTileflowThemeController(options) {
|
|
|
4971
5585
|
let disposed = false;
|
|
4972
5586
|
let requestId = 0;
|
|
4973
5587
|
let applyQueue = Promise.resolve();
|
|
5588
|
+
let shownLayers = typeof options.initial.style === "string" ? void 0 : options.initial.style.layers;
|
|
5589
|
+
let emphasis;
|
|
5590
|
+
let emphasisShown;
|
|
5591
|
+
let emphasisStrength = 0;
|
|
5592
|
+
let emphasisGoal = 0;
|
|
5593
|
+
let emphasisTargets = /* @__PURE__ */ new Map();
|
|
5594
|
+
let emphasisBase = /* @__PURE__ */ new Map();
|
|
5595
|
+
let emphasisBlended = /* @__PURE__ */ new Set();
|
|
5596
|
+
let emphasisGroundTemplate;
|
|
5597
|
+
let emphasisStaticGround;
|
|
5598
|
+
const emphasisWritten = /* @__PURE__ */ new Map();
|
|
5599
|
+
let emphasisRun = 0;
|
|
5600
|
+
let emphasisFrame;
|
|
5601
|
+
let emphasisSettle;
|
|
5602
|
+
const states = /* @__PURE__ */ new Map();
|
|
5603
|
+
let images;
|
|
5604
|
+
function imageContext() {
|
|
5605
|
+
return {
|
|
5606
|
+
theme: current.theme,
|
|
5607
|
+
blend: activeBlend ? Object.freeze({
|
|
5608
|
+
position: activeBlend.blender.position,
|
|
5609
|
+
themes: Object.freeze(activeBlend.themes.map((theme) => theme.theme))
|
|
5610
|
+
}) : void 0,
|
|
5611
|
+
states: Object.freeze(
|
|
5612
|
+
Object.fromEntries([...states].map(([name, state]) => [name, state.shown]))
|
|
5613
|
+
)
|
|
5614
|
+
};
|
|
5615
|
+
}
|
|
4974
5616
|
return {
|
|
4975
5617
|
dispose() {
|
|
4976
5618
|
disposed = true;
|
|
5619
|
+
images?.dispose();
|
|
4977
5620
|
requestId += 1;
|
|
4978
5621
|
activeBlend?.blender.dispose();
|
|
5622
|
+
stopEmphasisMotion({ status: "superseded", theme: current.theme });
|
|
5623
|
+
for (const state of states.values())
|
|
5624
|
+
stopStateMotion(state, { status: "superseded", theme: current.theme });
|
|
4979
5625
|
},
|
|
4980
5626
|
getBlend() {
|
|
4981
5627
|
if (!activeBlend) return void 0;
|
|
4982
5628
|
return {
|
|
5629
|
+
held: activeBlend.blender.held,
|
|
4983
5630
|
position: activeBlend.blender.position,
|
|
5631
|
+
relayoutAt: activeBlend.blender.relayoutAt,
|
|
4984
5632
|
themes: activeBlend.themes.map((theme) => theme.theme)
|
|
4985
5633
|
};
|
|
4986
5634
|
},
|
|
5635
|
+
registerImage(name, definition) {
|
|
5636
|
+
if (disposed) throw new Error("Tileflow theme controller is disposed.");
|
|
5637
|
+
if (activeBlend?.plan.images.some((image) => image.names.includes(name))) {
|
|
5638
|
+
throw new TypeError("Dynamic image names must remain identical across blended themes.");
|
|
5639
|
+
}
|
|
5640
|
+
images ??= createTileflowDynamicImageRegistry(options.map, imageContext, resetStates);
|
|
5641
|
+
return images.register(name, definition);
|
|
5642
|
+
},
|
|
4987
5643
|
getCurrent() {
|
|
4988
5644
|
return current;
|
|
4989
5645
|
},
|
|
5646
|
+
getEmphasis() {
|
|
5647
|
+
return emphasis;
|
|
5648
|
+
},
|
|
5649
|
+
getState(name) {
|
|
5650
|
+
return typeof name === "string" ? states.get(name.trim())?.value : void 0;
|
|
5651
|
+
},
|
|
5652
|
+
setState(name, value, changeOptions) {
|
|
5653
|
+
const invalid = validateStyleState(name, value);
|
|
5654
|
+
if (invalid) return Promise.resolve({ error: invalid, status: "failed", theme: current.theme });
|
|
5655
|
+
if (disposed) return Promise.resolve(disposedResult(current.theme));
|
|
5656
|
+
if (typeof options.map.setGlobalStateProperty !== "function") {
|
|
5657
|
+
return Promise.resolve({
|
|
5658
|
+
error: new TypeError("Style states need a MapLibre map with global state (GL JS 5.6+)."),
|
|
5659
|
+
status: "failed",
|
|
5660
|
+
theme: current.theme
|
|
5661
|
+
});
|
|
5662
|
+
}
|
|
5663
|
+
const transition = transitionOf(changeOptions);
|
|
5664
|
+
if (transition instanceof TypeError)
|
|
5665
|
+
return Promise.resolve({ error: transition, status: "failed", theme: current.theme });
|
|
5666
|
+
return changeState(name.trim(), value, reducedMotion() ? 0 : transition);
|
|
5667
|
+
},
|
|
5668
|
+
setEmphasis(request, changeOptions) {
|
|
5669
|
+
if (request !== void 0) {
|
|
5670
|
+
const invalid = validateTileflowEmphasis(request);
|
|
5671
|
+
if (invalid)
|
|
5672
|
+
return Promise.resolve({ error: invalid, status: "failed", theme: current.theme });
|
|
5673
|
+
}
|
|
5674
|
+
if (disposed) return Promise.resolve(disposedResult(current.theme));
|
|
5675
|
+
const transition = transitionOf(changeOptions);
|
|
5676
|
+
if (transition instanceof TypeError)
|
|
5677
|
+
return Promise.resolve({ error: transition, status: "failed", theme: current.theme });
|
|
5678
|
+
return changeEmphasis(request, reducedMotion() ? 0 : transition);
|
|
5679
|
+
},
|
|
4990
5680
|
setBlend(request, changeOptions) {
|
|
4991
5681
|
const targetTheme = nearestTheme(request);
|
|
4992
5682
|
const invalid = validateBlendRequest(request);
|
|
@@ -4998,8 +5688,9 @@ function createTileflowThemeController(options) {
|
|
|
4998
5688
|
const key = blendKey(request.themes);
|
|
4999
5689
|
if (activeBlend?.key === key) {
|
|
5000
5690
|
requestId += 1;
|
|
5001
|
-
activeBlend.blender.set(request.position);
|
|
5691
|
+
activeBlend.blender.set(request.position, { hold: request.hold === true });
|
|
5002
5692
|
current = activeBlend.themes[activeBlend.blender.dominant];
|
|
5693
|
+
images?.invalidate();
|
|
5003
5694
|
return Promise.resolve({ status: "applied", theme: current.theme });
|
|
5004
5695
|
}
|
|
5005
5696
|
if (pendingBlend?.key === key) {
|
|
@@ -5087,7 +5778,9 @@ function createTileflowThemeController(options) {
|
|
|
5087
5778
|
),
|
|
5088
5779
|
Promise.all(themes.map((theme) => loadFonts(theme)))
|
|
5089
5780
|
]);
|
|
5090
|
-
|
|
5781
|
+
const plan = planTileflowThemeBlend(styles);
|
|
5782
|
+
images?.assertBlend(plan.images);
|
|
5783
|
+
return plan;
|
|
5091
5784
|
}
|
|
5092
5785
|
function enqueue(runId, target) {
|
|
5093
5786
|
const operation = applyQueue.then(
|
|
@@ -5106,6 +5799,8 @@ function createTileflowThemeController(options) {
|
|
|
5106
5799
|
async function applyTarget(runId, target) {
|
|
5107
5800
|
const targetTheme = targetThemeOf(target);
|
|
5108
5801
|
if (disposed || runId !== requestId) return { status: "superseded", theme: targetTheme };
|
|
5802
|
+
await whenContextAvailable(options.map);
|
|
5803
|
+
if (disposed || runId !== requestId) return { status: "superseded", theme: targetTheme };
|
|
5109
5804
|
const previous = { current, shown };
|
|
5110
5805
|
options.onTransition?.({ currentTheme: previous.current.theme, phase: "applying", targetTheme });
|
|
5111
5806
|
const cover = beginTileflowStyleCrossfade(options.map, target.transition);
|
|
@@ -5113,6 +5808,7 @@ function createTileflowThemeController(options) {
|
|
|
5113
5808
|
activeBlend = void 0;
|
|
5114
5809
|
const styleInput = target.kind === "theme" ? target.style.style : target.plan.styleAt(target.position());
|
|
5115
5810
|
try {
|
|
5811
|
+
if (target.kind === "blend") images?.assertBlend(target.plan.images);
|
|
5116
5812
|
await applyMapStyle(options.map, styleInput, timeoutMs);
|
|
5117
5813
|
if (disposed) {
|
|
5118
5814
|
cover?.cancel();
|
|
@@ -5136,6 +5832,10 @@ function createTileflowThemeController(options) {
|
|
|
5136
5832
|
current = target.themes[blender.dominant];
|
|
5137
5833
|
if (pendingBlend?.key === target.key) pendingBlend = void 0;
|
|
5138
5834
|
}
|
|
5835
|
+
shownLayers = typeof styleInput === "string" ? void 0 : styleInput.layers;
|
|
5836
|
+
resetEmphasis();
|
|
5837
|
+
resetStates();
|
|
5838
|
+
images?.invalidate();
|
|
5139
5839
|
await cover?.finish({ stop: () => disposed || runId !== requestId });
|
|
5140
5840
|
options.onTransition?.({
|
|
5141
5841
|
currentTheme: current.theme,
|
|
@@ -5168,10 +5868,16 @@ function createTileflowThemeController(options) {
|
|
|
5168
5868
|
void fade?.finish();
|
|
5169
5869
|
},
|
|
5170
5870
|
labelFadeMs: reducedMotion() ? 0 : blendLabelFadeMs,
|
|
5171
|
-
|
|
5871
|
+
// The ground colour moves with the blend, and colours that recede follow it.
|
|
5872
|
+
onPosition: () => {
|
|
5873
|
+
if (emphasisShown) writeEmphasis(false);
|
|
5874
|
+
},
|
|
5875
|
+
position,
|
|
5876
|
+
transform: (layer, property, value) => emphasisValue(layer, property, value)
|
|
5172
5877
|
});
|
|
5173
5878
|
}
|
|
5174
5879
|
async function restore(previous) {
|
|
5880
|
+
await whenContextAvailable(options.map);
|
|
5175
5881
|
const blend = previous.shown.blend;
|
|
5176
5882
|
if (blend) {
|
|
5177
5883
|
const position = blend.blender.position;
|
|
@@ -5179,11 +5885,260 @@ function createTileflowThemeController(options) {
|
|
|
5179
5885
|
await applyMapStyle(options.map, style, timeoutMs);
|
|
5180
5886
|
activeBlend = { ...blend, blender: createBlender(blend.plan, position) };
|
|
5181
5887
|
shown = { blend: activeBlend, style };
|
|
5888
|
+
shownLayers = style.layers;
|
|
5182
5889
|
} else {
|
|
5183
5890
|
await applyMapStyle(options.map, previous.shown.style, timeoutMs);
|
|
5184
5891
|
shown = previous.shown;
|
|
5892
|
+
shownLayers = typeof shown.style === "string" ? void 0 : shown.style.layers;
|
|
5185
5893
|
}
|
|
5186
5894
|
current = previous.current;
|
|
5895
|
+
resetEmphasis();
|
|
5896
|
+
resetStates();
|
|
5897
|
+
images?.invalidate();
|
|
5898
|
+
}
|
|
5899
|
+
function writeState(name, value) {
|
|
5900
|
+
const set = options.map.setGlobalStateProperty;
|
|
5901
|
+
try {
|
|
5902
|
+
set?.call(options.map, name, value);
|
|
5903
|
+
} catch {
|
|
5904
|
+
}
|
|
5905
|
+
images?.invalidate(name);
|
|
5906
|
+
}
|
|
5907
|
+
function resetStates() {
|
|
5908
|
+
for (const [name, state] of states) {
|
|
5909
|
+
if (state.shown !== void 0) writeState(name, state.shown);
|
|
5910
|
+
}
|
|
5911
|
+
}
|
|
5912
|
+
function stopStateMotion(state, result) {
|
|
5913
|
+
state.stop?.();
|
|
5914
|
+
state.stop = void 0;
|
|
5915
|
+
const settle = state.settle;
|
|
5916
|
+
state.settle = void 0;
|
|
5917
|
+
if (result) settle?.(result);
|
|
5918
|
+
}
|
|
5919
|
+
function changeState(name, value, duration) {
|
|
5920
|
+
const state = states.get(name) ?? { run: 0, shown: void 0, value };
|
|
5921
|
+
states.set(name, state);
|
|
5922
|
+
stopStateMotion(state, { status: "superseded", theme: current.theme });
|
|
5923
|
+
const run = ++state.run;
|
|
5924
|
+
const from = state.shown;
|
|
5925
|
+
state.value = value;
|
|
5926
|
+
const view = tileflowThemeMotionView(options.map);
|
|
5927
|
+
if (duration <= 0 || !view || typeof from !== "number" || typeof value !== "number" || from === value) {
|
|
5928
|
+
state.shown = value;
|
|
5929
|
+
writeState(name, value);
|
|
5930
|
+
return Promise.resolve({ status: "applied", theme: current.theme });
|
|
5931
|
+
}
|
|
5932
|
+
return new Promise((resolve) => {
|
|
5933
|
+
state.settle = resolve;
|
|
5934
|
+
const now = () => view.performance?.now?.() ?? Date.now();
|
|
5935
|
+
const start = now();
|
|
5936
|
+
const least = Math.abs(value - from) / 100;
|
|
5937
|
+
let cancelled = false;
|
|
5938
|
+
let frame;
|
|
5939
|
+
const step = () => {
|
|
5940
|
+
frame = void 0;
|
|
5941
|
+
if (cancelled || disposed || run !== state.run) return;
|
|
5942
|
+
const t = Math.min(1, (now() - start) / duration);
|
|
5943
|
+
const eased = t * t * (3 - 2 * t);
|
|
5944
|
+
const shown2 = t >= 1 ? value : from + (value - from) * eased;
|
|
5945
|
+
if (t >= 1 || Math.abs(shown2 - state.shown) >= least) {
|
|
5946
|
+
state.shown = shown2;
|
|
5947
|
+
writeState(name, shown2);
|
|
5948
|
+
}
|
|
5949
|
+
if (t >= 1) {
|
|
5950
|
+
state.stop = void 0;
|
|
5951
|
+
state.settle = void 0;
|
|
5952
|
+
resolve({ status: "applied", theme: current.theme });
|
|
5953
|
+
return;
|
|
5954
|
+
}
|
|
5955
|
+
frame = view.requestAnimationFrame(step);
|
|
5956
|
+
};
|
|
5957
|
+
state.stop = () => {
|
|
5958
|
+
cancelled = true;
|
|
5959
|
+
if (frame !== void 0) view.cancelAnimationFrame(frame);
|
|
5960
|
+
};
|
|
5961
|
+
frame = view.requestAnimationFrame(step);
|
|
5962
|
+
});
|
|
5963
|
+
}
|
|
5964
|
+
function valueKey(layer, property) {
|
|
5965
|
+
return `${layer}\0${property}`;
|
|
5966
|
+
}
|
|
5967
|
+
function layersShown() {
|
|
5968
|
+
if (shownLayers) return shownLayers;
|
|
5969
|
+
const getStyle = options.map.getStyle;
|
|
5970
|
+
if (typeof getStyle !== "function") return [];
|
|
5971
|
+
const style = getStyle.call(options.map);
|
|
5972
|
+
shownLayers = Array.isArray(style?.layers) ? style.layers : [];
|
|
5973
|
+
return shownLayers;
|
|
5974
|
+
}
|
|
5975
|
+
function planEmphasis(onMap = /* @__PURE__ */ new Map()) {
|
|
5976
|
+
const layers = layersShown();
|
|
5977
|
+
const targets = emphasisShown ? planTileflowEmphasis(layers, emphasisShown) : [];
|
|
5978
|
+
emphasisTargets = new Map(
|
|
5979
|
+
targets.map((target) => [valueKey(target.layer, target.property), target])
|
|
5980
|
+
);
|
|
5981
|
+
const paintOf = /* @__PURE__ */ new Map();
|
|
5982
|
+
for (const layer of layers) {
|
|
5983
|
+
if (layer && typeof layer === "object" && typeof layer.id === "string") {
|
|
5984
|
+
const paint = layer.paint;
|
|
5985
|
+
paintOf.set(
|
|
5986
|
+
layer.id,
|
|
5987
|
+
paint && typeof paint === "object" ? paint : {}
|
|
5988
|
+
);
|
|
5989
|
+
}
|
|
5990
|
+
}
|
|
5991
|
+
emphasisBase = new Map(
|
|
5992
|
+
targets.map((target) => [
|
|
5993
|
+
valueKey(target.layer, target.property),
|
|
5994
|
+
paintOf.get(target.layer)?.[target.property]
|
|
5995
|
+
])
|
|
5996
|
+
);
|
|
5997
|
+
emphasisStaticGround = tileflowEmphasisGround(layers);
|
|
5998
|
+
emphasisBlended = new Set(
|
|
5999
|
+
activeBlend?.plan.paints.map((paint) => valueKey(paint.layer, paint.property)) ?? []
|
|
6000
|
+
);
|
|
6001
|
+
const background = layers.find(
|
|
6002
|
+
(layer) => layer && typeof layer === "object" && layer.type === "background"
|
|
6003
|
+
);
|
|
6004
|
+
emphasisGroundTemplate = activeBlend?.plan.paints.find(
|
|
6005
|
+
(paint) => paint.layer === background?.id && paint.property === "background-color"
|
|
6006
|
+
)?.template;
|
|
6007
|
+
emphasisWritten.clear();
|
|
6008
|
+
for (const [key, base] of emphasisBase) {
|
|
6009
|
+
emphasisWritten.set(key, onMap.get(key) ?? JSON.stringify(base ?? null));
|
|
6010
|
+
}
|
|
6011
|
+
}
|
|
6012
|
+
function resetEmphasis() {
|
|
6013
|
+
if (!emphasisShown) return;
|
|
6014
|
+
planEmphasis();
|
|
6015
|
+
writeEmphasis(true);
|
|
6016
|
+
}
|
|
6017
|
+
function emphasisGround() {
|
|
6018
|
+
if (activeBlend && emphasisGroundTemplate !== void 0) {
|
|
6019
|
+
return firstColour(
|
|
6020
|
+
fillTileflowThemeBlendTemplate(emphasisGroundTemplate, activeBlend.blender.position)
|
|
6021
|
+
);
|
|
6022
|
+
}
|
|
6023
|
+
return emphasisStaticGround;
|
|
6024
|
+
}
|
|
6025
|
+
function emphasisValue(layer, property, value) {
|
|
6026
|
+
const target = emphasisShown ? emphasisTargets.get(valueKey(layer, property)) : void 0;
|
|
6027
|
+
if (!target) return value;
|
|
6028
|
+
const strength = target.featureData ? emphasisGoal : emphasisStrength;
|
|
6029
|
+
return emphasizeTileflowValue(target, value, {
|
|
6030
|
+
ground: emphasisGround(),
|
|
6031
|
+
keep: strength > 0 ? emphasisShown?.keep : void 0,
|
|
6032
|
+
strength
|
|
6033
|
+
});
|
|
6034
|
+
}
|
|
6035
|
+
function writeEmphasis(blended) {
|
|
6036
|
+
if (blended && activeBlend && emphasisTargets.size > 0) {
|
|
6037
|
+
activeBlend.blender.refresh(
|
|
6038
|
+
(layer, property) => emphasisTargets.has(valueKey(layer, property))
|
|
6039
|
+
);
|
|
6040
|
+
}
|
|
6041
|
+
const setPaint = options.map.setPaintProperty;
|
|
6042
|
+
const getLayer = options.map.getLayer;
|
|
6043
|
+
if (typeof setPaint !== "function") return;
|
|
6044
|
+
for (const [key, target] of emphasisTargets) {
|
|
6045
|
+
if (emphasisBlended.has(key)) continue;
|
|
6046
|
+
const value = emphasisValue(target.layer, target.property, emphasisBase.get(key));
|
|
6047
|
+
const serialized = JSON.stringify(value ?? null);
|
|
6048
|
+
if (emphasisWritten.get(key) === serialized) continue;
|
|
6049
|
+
if (typeof getLayer === "function" && !getLayer.call(options.map, target.layer)) continue;
|
|
6050
|
+
emphasisWritten.set(key, serialized);
|
|
6051
|
+
setPaint.call(options.map, target.layer, target.property, value, { validate: false });
|
|
6052
|
+
}
|
|
6053
|
+
}
|
|
6054
|
+
function stopEmphasisMotion(result) {
|
|
6055
|
+
emphasisFrame?.();
|
|
6056
|
+
emphasisFrame = void 0;
|
|
6057
|
+
const settle = emphasisSettle;
|
|
6058
|
+
emphasisSettle = void 0;
|
|
6059
|
+
if (result) settle?.(result);
|
|
6060
|
+
}
|
|
6061
|
+
function changeEmphasis(request, duration) {
|
|
6062
|
+
stopEmphasisMotion({ status: "superseded", theme: current.theme });
|
|
6063
|
+
const run = ++emphasisRun;
|
|
6064
|
+
emphasis = request;
|
|
6065
|
+
const before = emphasisTargets;
|
|
6066
|
+
if (request) {
|
|
6067
|
+
const leaving = new Map(emphasisWritten);
|
|
6068
|
+
const previous = emphasisShown;
|
|
6069
|
+
emphasisShown = request;
|
|
6070
|
+
planEmphasis(leaving);
|
|
6071
|
+
if (previous) {
|
|
6072
|
+
const setPaint = options.map.setPaintProperty;
|
|
6073
|
+
for (const [key, target] of before) {
|
|
6074
|
+
if (emphasisTargets.has(key) || typeof setPaint !== "function") continue;
|
|
6075
|
+
const base = layerPaint(target.layer, target.property);
|
|
6076
|
+
setPaint.call(options.map, target.layer, target.property, base, { validate: false });
|
|
6077
|
+
}
|
|
6078
|
+
}
|
|
6079
|
+
}
|
|
6080
|
+
if (!emphasisShown) return Promise.resolve({ status: "applied", theme: current.theme });
|
|
6081
|
+
emphasisGoal = request ? 1 : 0;
|
|
6082
|
+
const from = emphasisStrength;
|
|
6083
|
+
const to = emphasisGoal;
|
|
6084
|
+
const switches = [...emphasisTargets.values()].some((target) => target.featureData);
|
|
6085
|
+
const cover = switches ? beginTileflowStyleCrossfade(options.map, duration) : void 0;
|
|
6086
|
+
const view = tileflowThemeMotionView(options.map);
|
|
6087
|
+
return new Promise((resolve) => {
|
|
6088
|
+
emphasisSettle = resolve;
|
|
6089
|
+
const finish = () => {
|
|
6090
|
+
emphasisFrame = void 0;
|
|
6091
|
+
emphasisStrength = to;
|
|
6092
|
+
writeEmphasis(true);
|
|
6093
|
+
if (to === 0) {
|
|
6094
|
+
emphasisShown = void 0;
|
|
6095
|
+
emphasisTargets = /* @__PURE__ */ new Map();
|
|
6096
|
+
emphasisWritten.clear();
|
|
6097
|
+
}
|
|
6098
|
+
emphasisSettle = void 0;
|
|
6099
|
+
resolve({ status: "applied", theme: current.theme });
|
|
6100
|
+
};
|
|
6101
|
+
writeEmphasis(true);
|
|
6102
|
+
void cover?.finish({ stop: () => disposed || run !== emphasisRun });
|
|
6103
|
+
if (duration <= 0 || !view || from === to) {
|
|
6104
|
+
finish();
|
|
6105
|
+
return;
|
|
6106
|
+
}
|
|
6107
|
+
const now = () => view.performance?.now?.() ?? Date.now();
|
|
6108
|
+
const start = now();
|
|
6109
|
+
let cancelled = false;
|
|
6110
|
+
let frame;
|
|
6111
|
+
const step = () => {
|
|
6112
|
+
frame = void 0;
|
|
6113
|
+
if (cancelled || disposed || run !== emphasisRun) return;
|
|
6114
|
+
const t = Math.min(1, (now() - start) / duration);
|
|
6115
|
+
const eased = t * t * (3 - 2 * t);
|
|
6116
|
+
const strength = from + (to - from) * eased;
|
|
6117
|
+
if (t >= 1) return finish();
|
|
6118
|
+
if (Math.abs(strength - emphasisStrength) >= 0.01) {
|
|
6119
|
+
emphasisStrength = strength;
|
|
6120
|
+
writeEmphasis(true);
|
|
6121
|
+
}
|
|
6122
|
+
frame = view.requestAnimationFrame(step);
|
|
6123
|
+
};
|
|
6124
|
+
emphasisFrame = () => {
|
|
6125
|
+
cancelled = true;
|
|
6126
|
+
if (frame !== void 0) view.cancelAnimationFrame(frame);
|
|
6127
|
+
};
|
|
6128
|
+
frame = view.requestAnimationFrame(step);
|
|
6129
|
+
});
|
|
6130
|
+
}
|
|
6131
|
+
function layerPaint(layer, property) {
|
|
6132
|
+
const key = valueKey(layer, property);
|
|
6133
|
+
if (activeBlend && emphasisBlended.has(key))
|
|
6134
|
+
return activeBlend.blender.baseValue(layer, property);
|
|
6135
|
+
for (const entry of layersShown()) {
|
|
6136
|
+
if (entry && typeof entry === "object" && entry.id === layer) {
|
|
6137
|
+
const paint = entry.paint;
|
|
6138
|
+
return paint?.[property];
|
|
6139
|
+
}
|
|
6140
|
+
}
|
|
6141
|
+
return void 0;
|
|
5187
6142
|
}
|
|
5188
6143
|
function reducedMotion() {
|
|
5189
6144
|
const view = tileflowThemeMotionView(options.map);
|
|
@@ -5265,6 +6220,26 @@ function validateConcreteRuntimeTheme(style, role) {
|
|
|
5265
6220
|
`Tileflow theme controller ${role} style requires a concrete portable theme name; received ${JSON.stringify(style.theme)}.`
|
|
5266
6221
|
);
|
|
5267
6222
|
}
|
|
6223
|
+
function whenContextAvailable(map) {
|
|
6224
|
+
if (!isContextLost(map)) return Promise.resolve();
|
|
6225
|
+
return new Promise((resolve) => {
|
|
6226
|
+
const restored = () => {
|
|
6227
|
+
map.off("webglcontextrestored", restored);
|
|
6228
|
+
resolve();
|
|
6229
|
+
};
|
|
6230
|
+
map.on("webglcontextrestored", restored);
|
|
6231
|
+
});
|
|
6232
|
+
}
|
|
6233
|
+
function isContextLost(map) {
|
|
6234
|
+
if (typeof map.getCanvas !== "function") return false;
|
|
6235
|
+
try {
|
|
6236
|
+
const canvas = map.getCanvas.call(map);
|
|
6237
|
+
const context = canvas?.getContext?.("webgl2") ?? canvas?.getContext?.("webgl");
|
|
6238
|
+
return context?.isContextLost?.() === true;
|
|
6239
|
+
} catch {
|
|
6240
|
+
return false;
|
|
6241
|
+
}
|
|
6242
|
+
}
|
|
5268
6243
|
function applyMapStyle(map, style, timeoutMs) {
|
|
5269
6244
|
return new Promise((resolve, reject) => {
|
|
5270
6245
|
let settled = false;
|
|
@@ -5574,37 +6549,21 @@ function attachTileflowMapLifecycle(options) {
|
|
|
5574
6549
|
};
|
|
5575
6550
|
}
|
|
5576
6551
|
function createTileflowTransformRequest(options) {
|
|
5577
|
-
if (!options.always && !options.transformRequest && !options.worldRequestBridge) {
|
|
5578
|
-
const analytics = options.getAnalytics();
|
|
5579
|
-
const requiresCommercialSession = Boolean(options.sessionController && analytics?.mapId);
|
|
5580
|
-
if (!requiresCommercialSession && (!analytics || analytics.enabled === false)) {
|
|
5581
|
-
return void 0;
|
|
5582
|
-
}
|
|
5583
|
-
}
|
|
5584
|
-
const asyncAnalyticsTiming = options.asyncAnalyticsTiming ?? "request";
|
|
5585
6552
|
return (url, resourceType) => {
|
|
5586
6553
|
const request = options.transformRequest?.(url, resourceType);
|
|
5587
|
-
const
|
|
5588
|
-
|
|
5589
|
-
return request.then(
|
|
5590
|
-
(resolvedRequest) => applyTileflowRequest(
|
|
5591
|
-
url,
|
|
5592
|
-
resolvedRequest,
|
|
5593
|
-
asyncAnalyticsTiming === "resolution" ? options.getAnalytics() : analyticsAtRequest,
|
|
5594
|
-
options.sessionId,
|
|
5595
|
-
options.sessionController,
|
|
5596
|
-
options.worldRequestBridge
|
|
5597
|
-
) ?? resolvedRequest
|
|
5598
|
-
);
|
|
5599
|
-
}
|
|
5600
|
-
return applyTileflowRequest(
|
|
6554
|
+
const analytics = options.getAnalytics();
|
|
6555
|
+
const apply = (resolvedRequest) => applyTileflowRequest(
|
|
5601
6556
|
url,
|
|
5602
|
-
|
|
5603
|
-
|
|
6557
|
+
resolvedRequest,
|
|
6558
|
+
analytics,
|
|
5604
6559
|
options.sessionId,
|
|
5605
6560
|
options.sessionController,
|
|
5606
6561
|
options.worldRequestBridge
|
|
5607
6562
|
);
|
|
6563
|
+
if (isPromiseLike(request)) {
|
|
6564
|
+
return request.then((resolvedRequest) => apply(resolvedRequest) ?? resolvedRequest);
|
|
6565
|
+
}
|
|
6566
|
+
return apply(request);
|
|
5608
6567
|
};
|
|
5609
6568
|
}
|
|
5610
6569
|
function applyTileflowRequest(url, request, analytics, sessionId, sessionController, worldRequestBridge) {
|
|
@@ -5670,9 +6629,12 @@ export {
|
|
|
5670
6629
|
registerTileflowWorldRequestBridge,
|
|
5671
6630
|
subscribeTileflowSystemColorScheme,
|
|
5672
6631
|
tileflowFairUseNoticeMessage,
|
|
6632
|
+
tileflowLayerDomainMetadataKey,
|
|
5673
6633
|
tileflowMaplibreContourVersion,
|
|
5674
6634
|
tileflowPmtilesMaximumDirectoryDepth,
|
|
5675
|
-
tileflowPmtilesProtocol
|
|
6635
|
+
tileflowPmtilesProtocol,
|
|
6636
|
+
validateTileflowBrowserThemeBlendSelection,
|
|
6637
|
+
validateTileflowEmphasis
|
|
5676
6638
|
};
|
|
5677
6639
|
/*! Bundled license information:
|
|
5678
6640
|
|