@tileflow/core 0.1.0-alpha.30 → 0.1.0-alpha.31
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 +3 -0
- package/dist/browser.d.ts +629 -1
- package/dist/browser.js +559 -16
- package/dist/build.js +18 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +24 -0
- package/dist/native.d.ts +134 -1
- package/dist/native.js +691 -1
- package/docs/authoring-model.md +15 -0
- package/docs/map-emphasis.md +89 -0
- package/docs/theme-transitions-and-blends.md +59 -3
- package/package.json +1 -1
package/dist/browser.js
CHANGED
|
@@ -2238,17 +2238,21 @@ var featureOperators = /* @__PURE__ */ new Set([
|
|
|
2238
2238
|
"properties"
|
|
2239
2239
|
]);
|
|
2240
2240
|
var maximumBranches = 24;
|
|
2241
|
-
function planTileflowThemeBlend(styles) {
|
|
2241
|
+
function planTileflowThemeBlend(styles, options = {}) {
|
|
2242
2242
|
if (styles.length < 2)
|
|
2243
2243
|
throw new TileflowThemeBlendError("A theme blend needs two or more themes.");
|
|
2244
2244
|
const [base] = styles;
|
|
2245
2245
|
assertSameStructure(styles, base);
|
|
2246
|
+
const mixedImages = new Set(imageProperties);
|
|
2247
|
+
if (options.iconImages === "switch") mixedImages.delete("icon-image");
|
|
2246
2248
|
const paints = [];
|
|
2247
2249
|
const switches = [];
|
|
2248
|
-
const imageGroups = collectImageNames(styles, base);
|
|
2250
|
+
const imageGroups = collectImageNames(styles, base, mixedImages);
|
|
2251
|
+
const symbolLayers = /* @__PURE__ */ new Set();
|
|
2249
2252
|
const images = /* @__PURE__ */ new Map();
|
|
2250
2253
|
let splitLayers = 0;
|
|
2251
2254
|
let copiedLayers = 0;
|
|
2255
|
+
let switchedLayers = 0;
|
|
2252
2256
|
const layers = [];
|
|
2253
2257
|
for (const [index, layer] of base.layers.entries()) {
|
|
2254
2258
|
const variants = styles.map((style) => style.layers[index]);
|
|
@@ -2262,6 +2266,7 @@ function planTileflowThemeBlend(styles) {
|
|
|
2262
2266
|
}
|
|
2263
2267
|
const sky = differingRoot(styles, "sky");
|
|
2264
2268
|
const light = differingRoot(styles, "light");
|
|
2269
|
+
const metadata = alignInteractionManifest(base.metadata, layers);
|
|
2265
2270
|
return {
|
|
2266
2271
|
themes: styles.length,
|
|
2267
2272
|
paints,
|
|
@@ -2269,14 +2274,21 @@ function planTileflowThemeBlend(styles) {
|
|
|
2269
2274
|
switches,
|
|
2270
2275
|
...sky === void 0 ? {} : { sky },
|
|
2271
2276
|
...light === void 0 ? {} : { light },
|
|
2272
|
-
summary: { splitLayers, copiedLayers, switchedValues: switches.length },
|
|
2277
|
+
summary: { splitLayers, copiedLayers, switchedLayers, switchedValues: switches.length },
|
|
2273
2278
|
styleAt(position) {
|
|
2274
2279
|
const at = clampPosition(position, styles.length);
|
|
2275
2280
|
const dominant = Math.round(at);
|
|
2276
2281
|
const style = {
|
|
2277
2282
|
...base,
|
|
2283
|
+
...metadata === void 0 ? {} : { metadata },
|
|
2284
|
+
// Symbols show the nearest theme, as the label cross-fade leaves them between changes.
|
|
2278
2285
|
layers: layers.map(
|
|
2279
|
-
(layer) => fillLayer(
|
|
2286
|
+
(layer) => fillLayer(
|
|
2287
|
+
layer,
|
|
2288
|
+
symbolLayers.has(String(layer.id)) ? dominant : at,
|
|
2289
|
+
dominant,
|
|
2290
|
+
switchesByLayer.get(String(layer.id))
|
|
2291
|
+
)
|
|
2280
2292
|
)
|
|
2281
2293
|
};
|
|
2282
2294
|
if (sky !== void 0) style.sky = fillTemplate(sky, at);
|
|
@@ -2301,7 +2313,7 @@ function planTileflowThemeBlend(styles) {
|
|
|
2301
2313
|
(variant) => variant[group]?.[property]
|
|
2302
2314
|
);
|
|
2303
2315
|
if (values.every((value) => same(value, values[0]))) continue;
|
|
2304
|
-
if (
|
|
2316
|
+
if (mixedImages.has(property)) {
|
|
2305
2317
|
const tuples = imageTuples(values);
|
|
2306
2318
|
if (tuples && tuples.every((names) => imageGroups.get(names[0])?.size === 1)) {
|
|
2307
2319
|
for (const names of tuples) {
|
|
@@ -2345,17 +2357,46 @@ function planTileflowThemeBlend(styles) {
|
|
|
2345
2357
|
values: variants.map((variant) => paintOf(variant)[property])
|
|
2346
2358
|
});
|
|
2347
2359
|
}
|
|
2360
|
+
if (options.featureSwitches === "layers" && results.length === 1 && ownSwitches.some((entry) => entry.values.some(isDataDriven)))
|
|
2361
|
+
results = switchLayer();
|
|
2348
2362
|
for (const result of results) {
|
|
2349
2363
|
const resultId = String(result.layer.id);
|
|
2364
|
+
if (type === "symbol") symbolLayers.add(resultId);
|
|
2350
2365
|
const overridden = new Set(result.extra.map((entry) => entry.property));
|
|
2351
2366
|
for (const entry of [
|
|
2352
2367
|
...ownPaints.filter((own) => !overridden.has(own.property)),
|
|
2353
2368
|
...result.extra
|
|
2354
2369
|
])
|
|
2355
2370
|
paints.push({ layer: resultId, property: entry.property, template: entry.template, follows });
|
|
2356
|
-
|
|
2371
|
+
if (result.visibility)
|
|
2372
|
+
switches.push({
|
|
2373
|
+
layer: resultId,
|
|
2374
|
+
group: "layout",
|
|
2375
|
+
property: "visibility",
|
|
2376
|
+
values: result.visibility
|
|
2377
|
+
});
|
|
2378
|
+
else for (const entry of ownSwitches) switches.push({ layer: resultId, ...entry });
|
|
2357
2379
|
}
|
|
2358
2380
|
return results.map((result) => result.layer);
|
|
2381
|
+
function switchLayer() {
|
|
2382
|
+
switchedLayers += variants.length;
|
|
2383
|
+
return variants.map((_2, theme) => {
|
|
2384
|
+
const copyPaint = { ...paint };
|
|
2385
|
+
const copyLayout = { ...layout };
|
|
2386
|
+
for (const entry of ownSwitches) {
|
|
2387
|
+
const target = entry.group === "paint" ? copyPaint : copyLayout;
|
|
2388
|
+
const value = entry.values[theme];
|
|
2389
|
+
if (value === void 0) delete target[entry.property];
|
|
2390
|
+
else target[entry.property] = value;
|
|
2391
|
+
}
|
|
2392
|
+
const shown = copyLayout.visibility === "none" ? "none" : "visible";
|
|
2393
|
+
return {
|
|
2394
|
+
layer: { ...planned, id: `${id}::theme-${theme}`, paint: copyPaint, layout: copyLayout },
|
|
2395
|
+
extra: [],
|
|
2396
|
+
visibility: variants.map((__, nearest) => nearest === theme ? shown : "none")
|
|
2397
|
+
};
|
|
2398
|
+
});
|
|
2399
|
+
}
|
|
2359
2400
|
function splitLayer() {
|
|
2360
2401
|
if (isLegacyFilter(layer.filter)) return void 0;
|
|
2361
2402
|
const branches = splitBranches(
|
|
@@ -2420,6 +2461,15 @@ function planTileflowThemeBlend(styles) {
|
|
|
2420
2461
|
function fillTileflowThemeBlendTemplate(template, position) {
|
|
2421
2462
|
return fillTemplate(template, position);
|
|
2422
2463
|
}
|
|
2464
|
+
function mixTileflowColours(from, to, t) {
|
|
2465
|
+
const a = oklab(from);
|
|
2466
|
+
const b2 = oklab(to);
|
|
2467
|
+
if (!a || !b2) return void 0;
|
|
2468
|
+
return colourFromOklab(a.map((value, channel) => value + (b2[channel] - value) * t));
|
|
2469
|
+
}
|
|
2470
|
+
function readsTileflowFeatureData(value) {
|
|
2471
|
+
return isDataDriven(value) || reads(value, "within") || reads(value, "distance");
|
|
2472
|
+
}
|
|
2423
2473
|
function fillLayer(layer, position, dominant, layerSwitches) {
|
|
2424
2474
|
const filled = { ...layer };
|
|
2425
2475
|
for (const group of ["layout", "paint"]) {
|
|
@@ -2504,11 +2554,11 @@ function differingRoot(styles, key) {
|
|
|
2504
2554
|
if (values.every((value) => same(value, values[0]))) return void 0;
|
|
2505
2555
|
return tryMixTemplate(values);
|
|
2506
2556
|
}
|
|
2507
|
-
function collectImageNames(styles, base) {
|
|
2557
|
+
function collectImageNames(styles, base, properties) {
|
|
2508
2558
|
const uses = /* @__PURE__ */ new Map();
|
|
2509
2559
|
for (const [index] of base.layers.entries()) {
|
|
2510
2560
|
for (const group of ["layout", "paint"]) {
|
|
2511
|
-
for (const property of
|
|
2561
|
+
for (const property of properties) {
|
|
2512
2562
|
const values = styles.map(
|
|
2513
2563
|
(style) => style.layers[index]?.[group]?.[property]
|
|
2514
2564
|
);
|
|
@@ -2523,6 +2573,51 @@ function collectImageNames(styles, base) {
|
|
|
2523
2573
|
}
|
|
2524
2574
|
return uses;
|
|
2525
2575
|
}
|
|
2576
|
+
function alignInteractionManifest(metadata, layers) {
|
|
2577
|
+
if (!isRecord2(metadata)) return void 0;
|
|
2578
|
+
const planned = /* @__PURE__ */ new Map();
|
|
2579
|
+
for (const [index, layer] of layers.entries()) {
|
|
2580
|
+
const id = String(layer.id);
|
|
2581
|
+
const original = /^(.+)::(?:branch|theme)-\d+$/u.exec(id)?.[1] ?? id;
|
|
2582
|
+
planned.set(original, [...planned.get(original) ?? [], { id, index }]);
|
|
2583
|
+
}
|
|
2584
|
+
let aligned;
|
|
2585
|
+
const interactions = metadata["tileflow:interaction-manifest"];
|
|
2586
|
+
const domains = isRecord2(interactions) ? interactions.domains : void 0;
|
|
2587
|
+
if (isRecord2(interactions) && isRecord2(domains) && isRecord2(domains.poi)) {
|
|
2588
|
+
const poi = domains.poi;
|
|
2589
|
+
if (Array.isArray(poi.layers)) {
|
|
2590
|
+
const poiLayers = poi.layers.flatMap((entry) => {
|
|
2591
|
+
if (!isRecord2(entry) || typeof entry.layerId !== "string") return [entry];
|
|
2592
|
+
const copies = planned.get(entry.layerId);
|
|
2593
|
+
if (!copies) return [entry];
|
|
2594
|
+
return copies.map(({ id, index }) => ({ ...entry, layerId: id, priority: index }));
|
|
2595
|
+
});
|
|
2596
|
+
aligned = {
|
|
2597
|
+
...metadata,
|
|
2598
|
+
"tileflow:interaction-manifest": {
|
|
2599
|
+
...interactions,
|
|
2600
|
+
domains: { ...domains, poi: { ...poi, layers: poiLayers } }
|
|
2601
|
+
}
|
|
2602
|
+
};
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
const overlays = metadata["tileflow:overlay-placement-manifest"];
|
|
2606
|
+
if (isRecord2(overlays) && isRecord2(overlays.anchors)) {
|
|
2607
|
+
const anchors = Object.fromEntries(
|
|
2608
|
+
Object.entries(overlays.anchors).map(([placement, anchor]) => [
|
|
2609
|
+
placement,
|
|
2610
|
+
typeof anchor === "string" ? planned.get(anchor)?.[0]?.id ?? anchor : anchor
|
|
2611
|
+
])
|
|
2612
|
+
);
|
|
2613
|
+
aligned = {
|
|
2614
|
+
...aligned ?? metadata,
|
|
2615
|
+
"tileflow:overlay-placement-manifest": { ...overlays, anchors }
|
|
2616
|
+
};
|
|
2617
|
+
}
|
|
2618
|
+
return aligned;
|
|
2619
|
+
}
|
|
2620
|
+
var isRecord2 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2526
2621
|
function imageTuples(values, out = []) {
|
|
2527
2622
|
const [first] = values;
|
|
2528
2623
|
if (values.every((value) => typeof value === "string")) {
|
|
@@ -2789,6 +2884,196 @@ function parseUnit(part, scale) {
|
|
|
2789
2884
|
return percentage ? value / 100 * scale : value;
|
|
2790
2885
|
}
|
|
2791
2886
|
|
|
2887
|
+
// src/layer-domain.ts
|
|
2888
|
+
var tileflowLayerDomainMetadataKey = "tileflow:domain";
|
|
2889
|
+
|
|
2890
|
+
// src/theme-emphasis.ts
|
|
2891
|
+
var colourProperties = {
|
|
2892
|
+
circle: ["circle-color", "circle-stroke-color"],
|
|
2893
|
+
fill: ["fill-color", "fill-outline-color"],
|
|
2894
|
+
"fill-extrusion": ["fill-extrusion-color"],
|
|
2895
|
+
line: ["line-color"]
|
|
2896
|
+
};
|
|
2897
|
+
var patternProperties = {
|
|
2898
|
+
fill: "fill-pattern",
|
|
2899
|
+
"fill-extrusion": "fill-extrusion-pattern",
|
|
2900
|
+
line: "line-pattern"
|
|
2901
|
+
};
|
|
2902
|
+
var opacityProperties2 = {
|
|
2903
|
+
circle: "circle-opacity",
|
|
2904
|
+
fill: "fill-opacity",
|
|
2905
|
+
"fill-extrusion": "fill-extrusion-opacity",
|
|
2906
|
+
heatmap: "heatmap-opacity",
|
|
2907
|
+
line: "line-opacity",
|
|
2908
|
+
raster: "raster-opacity"
|
|
2909
|
+
};
|
|
2910
|
+
var labelProperties = ["text-opacity", "icon-opacity"];
|
|
2911
|
+
var maximumAreaPositions = 4096;
|
|
2912
|
+
function validateTileflowEmphasis(value) {
|
|
2913
|
+
if (!isRecord3(value)) return new TypeError("A Tileflow emphasis must be an object.");
|
|
2914
|
+
for (const key of Object.keys(value)) {
|
|
2915
|
+
if (key !== "recede" && key !== "labels" && key !== "keep") {
|
|
2916
|
+
return new TypeError(`A Tileflow emphasis has no ${JSON.stringify(key)} option.`);
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
if (value.recede !== void 0) {
|
|
2920
|
+
if (!isRecord3(value.recede)) {
|
|
2921
|
+
return new TypeError("A Tileflow emphasis recede must map module names to amounts.");
|
|
2922
|
+
}
|
|
2923
|
+
for (const [module, amount] of Object.entries(value.recede)) {
|
|
2924
|
+
if (!/^[a-z][a-z0-9-]*$/u.test(module) || !isUnit(amount)) {
|
|
2925
|
+
return new TypeError(
|
|
2926
|
+
`A Tileflow emphasis recede amount for ${JSON.stringify(module)} must be a number from 0 to 1.`
|
|
2927
|
+
);
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
if (value.labels !== void 0 && !isUnit(value.labels)) {
|
|
2932
|
+
return new TypeError("A Tileflow emphasis labels share must be a number from 0 to 1.");
|
|
2933
|
+
}
|
|
2934
|
+
if (value.keep !== void 0) {
|
|
2935
|
+
const positions = areaPositions(value.keep);
|
|
2936
|
+
if (positions === void 0) {
|
|
2937
|
+
return new TypeError(
|
|
2938
|
+
"A Tileflow emphasis keep area must be a GeoJSON Polygon or MultiPolygon."
|
|
2939
|
+
);
|
|
2940
|
+
}
|
|
2941
|
+
if (positions > maximumAreaPositions) {
|
|
2942
|
+
return new TypeError(
|
|
2943
|
+
`A Tileflow emphasis keep area may hold at most ${maximumAreaPositions} positions.`
|
|
2944
|
+
);
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
return void 0;
|
|
2948
|
+
}
|
|
2949
|
+
function planTileflowEmphasis(layers, emphasis) {
|
|
2950
|
+
const targets = [];
|
|
2951
|
+
const fadeLabels = emphasis.labels === void 0 ? 0 : 1 - emphasis.labels;
|
|
2952
|
+
for (const layer of layers) {
|
|
2953
|
+
if (!isRecord3(layer) || typeof layer.id !== "string" || typeof layer.type !== "string")
|
|
2954
|
+
continue;
|
|
2955
|
+
const domain = isRecord3(layer.metadata) ? layer.metadata[tileflowLayerDomainMetadataKey] : void 0;
|
|
2956
|
+
if (typeof domain !== "string") continue;
|
|
2957
|
+
const paint = isRecord3(layer.paint) ? layer.paint : {};
|
|
2958
|
+
const add = (property, kind, amount2) => targets.push({
|
|
2959
|
+
amount: amount2,
|
|
2960
|
+
featureData: readsTileflowFeatureData(paint[property]) || kind === "label" && emphasis.keep !== void 0,
|
|
2961
|
+
kind,
|
|
2962
|
+
layer: layer.id,
|
|
2963
|
+
property
|
|
2964
|
+
});
|
|
2965
|
+
if (layer.type === "symbol") {
|
|
2966
|
+
if (fadeLabels > 0)
|
|
2967
|
+
for (const property of labelProperties) add(property, "label", fadeLabels);
|
|
2968
|
+
continue;
|
|
2969
|
+
}
|
|
2970
|
+
if (layer.type === "background") continue;
|
|
2971
|
+
const amount = emphasis.recede?.[domain] ?? 0;
|
|
2972
|
+
if (amount <= 0) continue;
|
|
2973
|
+
const pattern = patternProperties[layer.type];
|
|
2974
|
+
const opacity = opacityProperties2[layer.type];
|
|
2975
|
+
if (pattern && paint[pattern] !== void 0) {
|
|
2976
|
+
if (opacity) add(opacity, "fade", amount);
|
|
2977
|
+
continue;
|
|
2978
|
+
}
|
|
2979
|
+
const colours = (colourProperties[layer.type] ?? []).filter(
|
|
2980
|
+
(property) => paint[property] !== void 0
|
|
2981
|
+
);
|
|
2982
|
+
if (colours.length > 0) for (const property of colours) add(property, "colour", amount);
|
|
2983
|
+
else if (opacity) add(opacity, "fade", amount);
|
|
2984
|
+
}
|
|
2985
|
+
return targets;
|
|
2986
|
+
}
|
|
2987
|
+
function emphasizeTileflowValue(target, value, options) {
|
|
2988
|
+
const amount = target.amount * Math.min(1, Math.max(0, options.strength));
|
|
2989
|
+
if (target.kind === "label") {
|
|
2990
|
+
const keep = options.keep;
|
|
2991
|
+
if (amount <= 0 && !keep) return value;
|
|
2992
|
+
const near = keep ? (original, scaled) => ["case", ["within", keep], original, scaled] : void 0;
|
|
2993
|
+
return scaleOpacity(value, 1 - amount, near);
|
|
2994
|
+
}
|
|
2995
|
+
if (amount <= 0) return value;
|
|
2996
|
+
if (target.kind === "fade") return scaleOpacity(value, 1 - amount);
|
|
2997
|
+
if (options.ground === void 0) return value;
|
|
2998
|
+
return mixColours(value, options.ground, amount);
|
|
2999
|
+
}
|
|
3000
|
+
function tileflowEmphasisGround(layers) {
|
|
3001
|
+
for (const layer of layers) {
|
|
3002
|
+
if (!isRecord3(layer) || layer.type !== "background") continue;
|
|
3003
|
+
const colour = isRecord3(layer.paint) ? layer.paint["background-color"] : void 0;
|
|
3004
|
+
return firstColour(colour);
|
|
3005
|
+
}
|
|
3006
|
+
return void 0;
|
|
3007
|
+
}
|
|
3008
|
+
function firstColour(value) {
|
|
3009
|
+
if (typeof value === "string") {
|
|
3010
|
+
return mixTileflowColours(value, value, 0) === void 0 ? void 0 : value;
|
|
3011
|
+
}
|
|
3012
|
+
if (Array.isArray(value)) {
|
|
3013
|
+
for (const item of value.slice(1)) {
|
|
3014
|
+
const found = firstColour(item);
|
|
3015
|
+
if (found !== void 0) return found;
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
return void 0;
|
|
3019
|
+
}
|
|
3020
|
+
function mixColours(value, toward, amount) {
|
|
3021
|
+
if (typeof value === "string") return mixTileflowColours(value, toward, amount) ?? value;
|
|
3022
|
+
if (Array.isArray(value)) {
|
|
3023
|
+
if (value[0] === "literal") return value;
|
|
3024
|
+
return value.map((item, index) => index === 0 ? item : mixColours(item, toward, amount));
|
|
3025
|
+
}
|
|
3026
|
+
return value;
|
|
3027
|
+
}
|
|
3028
|
+
var isZoomInput = (input) => Array.isArray(input) && input[0] === "zoom";
|
|
3029
|
+
function scaleOpacity(value, factor, near = (_original, scaled) => scaled) {
|
|
3030
|
+
const scale = (item) => scaleOpacity(item, factor, near);
|
|
3031
|
+
if (value === void 0 || value === null) return near(1, factor);
|
|
3032
|
+
if (typeof value === "number") return near(value, round(value * factor));
|
|
3033
|
+
if (Array.isArray(value)) {
|
|
3034
|
+
const operator = value[0];
|
|
3035
|
+
if (typeof operator === "string" && operator.startsWith("interpolate") && isZoomInput(value[2])) {
|
|
3036
|
+
return [
|
|
3037
|
+
...value.slice(0, 3),
|
|
3038
|
+
...value.slice(3).map((item, index) => index % 2 === 1 ? scale(item) : item)
|
|
3039
|
+
];
|
|
3040
|
+
}
|
|
3041
|
+
if (operator === "step" && isZoomInput(value[1])) {
|
|
3042
|
+
return [
|
|
3043
|
+
"step",
|
|
3044
|
+
value[1],
|
|
3045
|
+
scale(value[2]),
|
|
3046
|
+
...value.slice(3).map((item, index) => index % 2 === 1 ? scale(item) : item)
|
|
3047
|
+
];
|
|
3048
|
+
}
|
|
3049
|
+
if (operator === "let") return [...value.slice(0, -1), scale(value.at(-1))];
|
|
3050
|
+
}
|
|
3051
|
+
return near(value, ["*", value, factor]);
|
|
3052
|
+
}
|
|
3053
|
+
var round = (value) => Math.round(value * 1e4) / 1e4;
|
|
3054
|
+
function areaPositions(value) {
|
|
3055
|
+
if (!isRecord3(value) || !Array.isArray(value.coordinates)) return void 0;
|
|
3056
|
+
const polygons = value.type === "Polygon" ? [value.coordinates] : value.type === "MultiPolygon" ? value.coordinates : void 0;
|
|
3057
|
+
if (!polygons) return void 0;
|
|
3058
|
+
let positions = 0;
|
|
3059
|
+
for (const polygon of polygons) {
|
|
3060
|
+
if (!Array.isArray(polygon) || polygon.length === 0) return void 0;
|
|
3061
|
+
for (const ring of polygon) {
|
|
3062
|
+
if (!Array.isArray(ring) || ring.length < 4) return void 0;
|
|
3063
|
+
for (const position of ring) {
|
|
3064
|
+
if (!Array.isArray(position) || position.length < 2 || !position.every(
|
|
3065
|
+
(coordinate) => typeof coordinate === "number" && Number.isFinite(coordinate)
|
|
3066
|
+
))
|
|
3067
|
+
return void 0;
|
|
3068
|
+
positions += 1;
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
return positions;
|
|
3073
|
+
}
|
|
3074
|
+
var isUnit = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
3075
|
+
var isRecord3 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3076
|
+
|
|
2792
3077
|
// src/theme-motion-browser.ts
|
|
2793
3078
|
var settleFrames = 3;
|
|
2794
3079
|
var settleTimeoutMs = 1e4;
|
|
@@ -2897,6 +3182,9 @@ function createTileflowThemeBlender(input, plan, options) {
|
|
|
2897
3182
|
const dominantPaints = plan.paints.filter((paint) => paint.follows === "dominant");
|
|
2898
3183
|
const pixels = createImageMixer(map, plan.images);
|
|
2899
3184
|
const shownValues = /* @__PURE__ */ new Map();
|
|
3185
|
+
const baseValues = /* @__PURE__ */ new Map();
|
|
3186
|
+
const valueKey = (layer, property) => `${layer}\0${property}`;
|
|
3187
|
+
const transformed = (layer, property, value) => options.transform ? options.transform(layer, property, value) : value;
|
|
2900
3188
|
let position = clamp(options.position);
|
|
2901
3189
|
let dominant = Math.round(position);
|
|
2902
3190
|
let label = dominant;
|
|
@@ -2907,7 +3195,9 @@ function createTileflowThemeBlender(input, plan, options) {
|
|
|
2907
3195
|
pixels.paint("dominant", label);
|
|
2908
3196
|
const drive = (paints, at) => {
|
|
2909
3197
|
for (const paint of paints) {
|
|
2910
|
-
const
|
|
3198
|
+
const base = fillTileflowThemeBlendTemplate(paint.template, at);
|
|
3199
|
+
baseValues.set(valueKey(paint.layer, paint.property), base);
|
|
3200
|
+
const value = transformed(paint.layer, paint.property, base);
|
|
2911
3201
|
const key = JSON.stringify(value);
|
|
2912
3202
|
if (shownValues.get(paint) === key) continue;
|
|
2913
3203
|
shownValues.set(paint, key);
|
|
@@ -2932,18 +3222,49 @@ function createTileflowThemeBlender(input, plan, options) {
|
|
|
2932
3222
|
const applySwitches = (theme) => {
|
|
2933
3223
|
for (const entry of plan.switches) {
|
|
2934
3224
|
const value = entry.values[theme];
|
|
2935
|
-
if (entry.group === "paint")
|
|
2936
|
-
|
|
2937
|
-
|
|
3225
|
+
if (entry.group === "paint") {
|
|
3226
|
+
baseValues.set(valueKey(entry.layer, entry.property), value);
|
|
3227
|
+
map.setPaintProperty(
|
|
3228
|
+
entry.layer,
|
|
3229
|
+
entry.property,
|
|
3230
|
+
transformed(entry.layer, entry.property, value),
|
|
3231
|
+
{ validate: false }
|
|
3232
|
+
);
|
|
3233
|
+
} else map.setLayoutProperty(entry.layer, entry.property, value, { validate: false });
|
|
2938
3234
|
}
|
|
2939
3235
|
};
|
|
3236
|
+
const switchedPaints = plan.switches.filter((entry) => entry.group === "paint");
|
|
2940
3237
|
return {
|
|
3238
|
+
baseValue(layer, property) {
|
|
3239
|
+
return baseValues.get(valueKey(layer, property));
|
|
3240
|
+
},
|
|
2941
3241
|
get dominant() {
|
|
2942
3242
|
return dominant;
|
|
2943
3243
|
},
|
|
2944
3244
|
get position() {
|
|
2945
3245
|
return position;
|
|
2946
3246
|
},
|
|
3247
|
+
refresh(include) {
|
|
3248
|
+
if (disposed) return;
|
|
3249
|
+
drive(
|
|
3250
|
+
positionPaints.filter((paint) => include(paint.layer, paint.property)),
|
|
3251
|
+
position
|
|
3252
|
+
);
|
|
3253
|
+
drive(
|
|
3254
|
+
dominantPaints.filter((paint) => include(paint.layer, paint.property)),
|
|
3255
|
+
label
|
|
3256
|
+
);
|
|
3257
|
+
for (const entry of switchedPaints) {
|
|
3258
|
+
if (!include(entry.layer, entry.property)) continue;
|
|
3259
|
+
const value = entry.values[dominant];
|
|
3260
|
+
map.setPaintProperty(
|
|
3261
|
+
entry.layer,
|
|
3262
|
+
entry.property,
|
|
3263
|
+
transformed(entry.layer, entry.property, value),
|
|
3264
|
+
{ validate: false }
|
|
3265
|
+
);
|
|
3266
|
+
}
|
|
3267
|
+
},
|
|
2947
3268
|
dispose() {
|
|
2948
3269
|
disposed = true;
|
|
2949
3270
|
if (frameRequest !== void 0 && view) {
|
|
@@ -2965,6 +3286,7 @@ function createTileflowThemeBlender(input, plan, options) {
|
|
|
2965
3286
|
map.setSky(fillTileflowThemeBlendTemplate(plan.sky, position), { validate: false });
|
|
2966
3287
|
if (plan.light !== void 0 && typeof map.setLight === "function")
|
|
2967
3288
|
map.setLight(fillTileflowThemeBlendTemplate(plan.light, position), { validate: false });
|
|
3289
|
+
options.onPosition?.();
|
|
2968
3290
|
}
|
|
2969
3291
|
const nextDominant = Math.round(target);
|
|
2970
3292
|
if (nextDominant === dominant) return;
|
|
@@ -4751,7 +5073,7 @@ var TileflowPmtilesProtocol = class {
|
|
|
4751
5073
|
const archive2 = this.#archive(parseArchiveTarget(request.url));
|
|
4752
5074
|
const header2 = await archive2.getHeader();
|
|
4753
5075
|
const tileJson = await archive2.getTileJson(request.url);
|
|
4754
|
-
const data = header2.tileType === O.Mlt &&
|
|
5076
|
+
const data = header2.tileType === O.Mlt && isRecord4(tileJson) ? { ...tileJson, encoding: "mlt" } : tileJson;
|
|
4755
5077
|
abortController.signal.throwIfAborted();
|
|
4756
5078
|
return { data };
|
|
4757
5079
|
}
|
|
@@ -4889,7 +5211,7 @@ function normalizeMaximumDirectoryDepth(value) {
|
|
|
4889
5211
|
}
|
|
4890
5212
|
return depth;
|
|
4891
5213
|
}
|
|
4892
|
-
function
|
|
5214
|
+
function isRecord4(value) {
|
|
4893
5215
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4894
5216
|
}
|
|
4895
5217
|
function protocolCompatibility(handler) {
|
|
@@ -4971,11 +5293,26 @@ function createTileflowThemeController(options) {
|
|
|
4971
5293
|
let disposed = false;
|
|
4972
5294
|
let requestId = 0;
|
|
4973
5295
|
let applyQueue = Promise.resolve();
|
|
5296
|
+
let shownLayers = typeof options.initial.style === "string" ? void 0 : options.initial.style.layers;
|
|
5297
|
+
let emphasis;
|
|
5298
|
+
let emphasisShown;
|
|
5299
|
+
let emphasisStrength = 0;
|
|
5300
|
+
let emphasisGoal = 0;
|
|
5301
|
+
let emphasisTargets = /* @__PURE__ */ new Map();
|
|
5302
|
+
let emphasisBase = /* @__PURE__ */ new Map();
|
|
5303
|
+
let emphasisBlended = /* @__PURE__ */ new Set();
|
|
5304
|
+
let emphasisGroundTemplate;
|
|
5305
|
+
let emphasisStaticGround;
|
|
5306
|
+
const emphasisWritten = /* @__PURE__ */ new Map();
|
|
5307
|
+
let emphasisRun = 0;
|
|
5308
|
+
let emphasisFrame;
|
|
5309
|
+
let emphasisSettle;
|
|
4974
5310
|
return {
|
|
4975
5311
|
dispose() {
|
|
4976
5312
|
disposed = true;
|
|
4977
5313
|
requestId += 1;
|
|
4978
5314
|
activeBlend?.blender.dispose();
|
|
5315
|
+
stopEmphasisMotion({ status: "superseded", theme: current.theme });
|
|
4979
5316
|
},
|
|
4980
5317
|
getBlend() {
|
|
4981
5318
|
if (!activeBlend) return void 0;
|
|
@@ -4987,6 +5324,21 @@ function createTileflowThemeController(options) {
|
|
|
4987
5324
|
getCurrent() {
|
|
4988
5325
|
return current;
|
|
4989
5326
|
},
|
|
5327
|
+
getEmphasis() {
|
|
5328
|
+
return emphasis;
|
|
5329
|
+
},
|
|
5330
|
+
setEmphasis(request, changeOptions) {
|
|
5331
|
+
if (request !== void 0) {
|
|
5332
|
+
const invalid = validateTileflowEmphasis(request);
|
|
5333
|
+
if (invalid)
|
|
5334
|
+
return Promise.resolve({ error: invalid, status: "failed", theme: current.theme });
|
|
5335
|
+
}
|
|
5336
|
+
if (disposed) return Promise.resolve(disposedResult(current.theme));
|
|
5337
|
+
const transition = transitionOf(changeOptions);
|
|
5338
|
+
if (transition instanceof TypeError)
|
|
5339
|
+
return Promise.resolve({ error: transition, status: "failed", theme: current.theme });
|
|
5340
|
+
return changeEmphasis(request, reducedMotion() ? 0 : transition);
|
|
5341
|
+
},
|
|
4990
5342
|
setBlend(request, changeOptions) {
|
|
4991
5343
|
const targetTheme = nearestTheme(request);
|
|
4992
5344
|
const invalid = validateBlendRequest(request);
|
|
@@ -5136,6 +5488,8 @@ function createTileflowThemeController(options) {
|
|
|
5136
5488
|
current = target.themes[blender.dominant];
|
|
5137
5489
|
if (pendingBlend?.key === target.key) pendingBlend = void 0;
|
|
5138
5490
|
}
|
|
5491
|
+
shownLayers = typeof styleInput === "string" ? void 0 : styleInput.layers;
|
|
5492
|
+
resetEmphasis();
|
|
5139
5493
|
await cover?.finish({ stop: () => disposed || runId !== requestId });
|
|
5140
5494
|
options.onTransition?.({
|
|
5141
5495
|
currentTheme: current.theme,
|
|
@@ -5168,7 +5522,12 @@ function createTileflowThemeController(options) {
|
|
|
5168
5522
|
void fade?.finish();
|
|
5169
5523
|
},
|
|
5170
5524
|
labelFadeMs: reducedMotion() ? 0 : blendLabelFadeMs,
|
|
5171
|
-
|
|
5525
|
+
// The ground colour moves with the blend, and colours that recede follow it.
|
|
5526
|
+
onPosition: () => {
|
|
5527
|
+
if (emphasisShown) writeEmphasis(false);
|
|
5528
|
+
},
|
|
5529
|
+
position,
|
|
5530
|
+
transform: (layer, property, value) => emphasisValue(layer, property, value)
|
|
5172
5531
|
});
|
|
5173
5532
|
}
|
|
5174
5533
|
async function restore(previous) {
|
|
@@ -5179,11 +5538,193 @@ function createTileflowThemeController(options) {
|
|
|
5179
5538
|
await applyMapStyle(options.map, style, timeoutMs);
|
|
5180
5539
|
activeBlend = { ...blend, blender: createBlender(blend.plan, position) };
|
|
5181
5540
|
shown = { blend: activeBlend, style };
|
|
5541
|
+
shownLayers = style.layers;
|
|
5182
5542
|
} else {
|
|
5183
5543
|
await applyMapStyle(options.map, previous.shown.style, timeoutMs);
|
|
5184
5544
|
shown = previous.shown;
|
|
5545
|
+
shownLayers = typeof shown.style === "string" ? void 0 : shown.style.layers;
|
|
5185
5546
|
}
|
|
5186
5547
|
current = previous.current;
|
|
5548
|
+
resetEmphasis();
|
|
5549
|
+
}
|
|
5550
|
+
function valueKey(layer, property) {
|
|
5551
|
+
return `${layer}\0${property}`;
|
|
5552
|
+
}
|
|
5553
|
+
function layersShown() {
|
|
5554
|
+
if (shownLayers) return shownLayers;
|
|
5555
|
+
const getStyle = options.map.getStyle;
|
|
5556
|
+
if (typeof getStyle !== "function") return [];
|
|
5557
|
+
const style = getStyle.call(options.map);
|
|
5558
|
+
shownLayers = Array.isArray(style?.layers) ? style.layers : [];
|
|
5559
|
+
return shownLayers;
|
|
5560
|
+
}
|
|
5561
|
+
function planEmphasis(onMap = /* @__PURE__ */ new Map()) {
|
|
5562
|
+
const layers = layersShown();
|
|
5563
|
+
const targets = emphasisShown ? planTileflowEmphasis(layers, emphasisShown) : [];
|
|
5564
|
+
emphasisTargets = new Map(
|
|
5565
|
+
targets.map((target) => [valueKey(target.layer, target.property), target])
|
|
5566
|
+
);
|
|
5567
|
+
const paintOf = /* @__PURE__ */ new Map();
|
|
5568
|
+
for (const layer of layers) {
|
|
5569
|
+
if (layer && typeof layer === "object" && typeof layer.id === "string") {
|
|
5570
|
+
const paint = layer.paint;
|
|
5571
|
+
paintOf.set(
|
|
5572
|
+
layer.id,
|
|
5573
|
+
paint && typeof paint === "object" ? paint : {}
|
|
5574
|
+
);
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
emphasisBase = new Map(
|
|
5578
|
+
targets.map((target) => [
|
|
5579
|
+
valueKey(target.layer, target.property),
|
|
5580
|
+
paintOf.get(target.layer)?.[target.property]
|
|
5581
|
+
])
|
|
5582
|
+
);
|
|
5583
|
+
emphasisStaticGround = tileflowEmphasisGround(layers);
|
|
5584
|
+
emphasisBlended = new Set(
|
|
5585
|
+
activeBlend?.plan.paints.map((paint) => valueKey(paint.layer, paint.property)) ?? []
|
|
5586
|
+
);
|
|
5587
|
+
const background = layers.find(
|
|
5588
|
+
(layer) => layer && typeof layer === "object" && layer.type === "background"
|
|
5589
|
+
);
|
|
5590
|
+
emphasisGroundTemplate = activeBlend?.plan.paints.find(
|
|
5591
|
+
(paint) => paint.layer === background?.id && paint.property === "background-color"
|
|
5592
|
+
)?.template;
|
|
5593
|
+
emphasisWritten.clear();
|
|
5594
|
+
for (const [key, base] of emphasisBase) {
|
|
5595
|
+
emphasisWritten.set(key, onMap.get(key) ?? JSON.stringify(base ?? null));
|
|
5596
|
+
}
|
|
5597
|
+
}
|
|
5598
|
+
function resetEmphasis() {
|
|
5599
|
+
if (!emphasisShown) return;
|
|
5600
|
+
planEmphasis();
|
|
5601
|
+
writeEmphasis(true);
|
|
5602
|
+
}
|
|
5603
|
+
function emphasisGround() {
|
|
5604
|
+
if (activeBlend && emphasisGroundTemplate !== void 0) {
|
|
5605
|
+
return firstColour(
|
|
5606
|
+
fillTileflowThemeBlendTemplate(emphasisGroundTemplate, activeBlend.blender.position)
|
|
5607
|
+
);
|
|
5608
|
+
}
|
|
5609
|
+
return emphasisStaticGround;
|
|
5610
|
+
}
|
|
5611
|
+
function emphasisValue(layer, property, value) {
|
|
5612
|
+
const target = emphasisShown ? emphasisTargets.get(valueKey(layer, property)) : void 0;
|
|
5613
|
+
if (!target) return value;
|
|
5614
|
+
const strength = target.featureData ? emphasisGoal : emphasisStrength;
|
|
5615
|
+
return emphasizeTileflowValue(target, value, {
|
|
5616
|
+
ground: emphasisGround(),
|
|
5617
|
+
keep: strength > 0 ? emphasisShown?.keep : void 0,
|
|
5618
|
+
strength
|
|
5619
|
+
});
|
|
5620
|
+
}
|
|
5621
|
+
function writeEmphasis(blended) {
|
|
5622
|
+
if (blended && activeBlend && emphasisTargets.size > 0) {
|
|
5623
|
+
activeBlend.blender.refresh(
|
|
5624
|
+
(layer, property) => emphasisTargets.has(valueKey(layer, property))
|
|
5625
|
+
);
|
|
5626
|
+
}
|
|
5627
|
+
const setPaint = options.map.setPaintProperty;
|
|
5628
|
+
const getLayer = options.map.getLayer;
|
|
5629
|
+
if (typeof setPaint !== "function") return;
|
|
5630
|
+
for (const [key, target] of emphasisTargets) {
|
|
5631
|
+
if (emphasisBlended.has(key)) continue;
|
|
5632
|
+
const value = emphasisValue(target.layer, target.property, emphasisBase.get(key));
|
|
5633
|
+
const serialized = JSON.stringify(value ?? null);
|
|
5634
|
+
if (emphasisWritten.get(key) === serialized) continue;
|
|
5635
|
+
if (typeof getLayer === "function" && !getLayer.call(options.map, target.layer)) continue;
|
|
5636
|
+
emphasisWritten.set(key, serialized);
|
|
5637
|
+
setPaint.call(options.map, target.layer, target.property, value, { validate: false });
|
|
5638
|
+
}
|
|
5639
|
+
}
|
|
5640
|
+
function stopEmphasisMotion(result) {
|
|
5641
|
+
emphasisFrame?.();
|
|
5642
|
+
emphasisFrame = void 0;
|
|
5643
|
+
const settle = emphasisSettle;
|
|
5644
|
+
emphasisSettle = void 0;
|
|
5645
|
+
if (result) settle?.(result);
|
|
5646
|
+
}
|
|
5647
|
+
function changeEmphasis(request, duration) {
|
|
5648
|
+
stopEmphasisMotion({ status: "superseded", theme: current.theme });
|
|
5649
|
+
const run = ++emphasisRun;
|
|
5650
|
+
emphasis = request;
|
|
5651
|
+
const before = emphasisTargets;
|
|
5652
|
+
if (request) {
|
|
5653
|
+
const leaving = new Map(emphasisWritten);
|
|
5654
|
+
const previous = emphasisShown;
|
|
5655
|
+
emphasisShown = request;
|
|
5656
|
+
planEmphasis(leaving);
|
|
5657
|
+
if (previous) {
|
|
5658
|
+
const setPaint = options.map.setPaintProperty;
|
|
5659
|
+
for (const [key, target] of before) {
|
|
5660
|
+
if (emphasisTargets.has(key) || typeof setPaint !== "function") continue;
|
|
5661
|
+
const base = layerPaint(target.layer, target.property);
|
|
5662
|
+
setPaint.call(options.map, target.layer, target.property, base, { validate: false });
|
|
5663
|
+
}
|
|
5664
|
+
}
|
|
5665
|
+
}
|
|
5666
|
+
if (!emphasisShown) return Promise.resolve({ status: "applied", theme: current.theme });
|
|
5667
|
+
emphasisGoal = request ? 1 : 0;
|
|
5668
|
+
const from = emphasisStrength;
|
|
5669
|
+
const to = emphasisGoal;
|
|
5670
|
+
const switches = [...emphasisTargets.values()].some((target) => target.featureData);
|
|
5671
|
+
const cover = switches ? beginTileflowStyleCrossfade(options.map, duration) : void 0;
|
|
5672
|
+
const view = tileflowThemeMotionView(options.map);
|
|
5673
|
+
return new Promise((resolve) => {
|
|
5674
|
+
emphasisSettle = resolve;
|
|
5675
|
+
const finish = () => {
|
|
5676
|
+
emphasisFrame = void 0;
|
|
5677
|
+
emphasisStrength = to;
|
|
5678
|
+
writeEmphasis(true);
|
|
5679
|
+
if (to === 0) {
|
|
5680
|
+
emphasisShown = void 0;
|
|
5681
|
+
emphasisTargets = /* @__PURE__ */ new Map();
|
|
5682
|
+
emphasisWritten.clear();
|
|
5683
|
+
}
|
|
5684
|
+
emphasisSettle = void 0;
|
|
5685
|
+
resolve({ status: "applied", theme: current.theme });
|
|
5686
|
+
};
|
|
5687
|
+
writeEmphasis(true);
|
|
5688
|
+
void cover?.finish({ stop: () => disposed || run !== emphasisRun });
|
|
5689
|
+
if (duration <= 0 || !view || from === to) {
|
|
5690
|
+
finish();
|
|
5691
|
+
return;
|
|
5692
|
+
}
|
|
5693
|
+
const now = () => view.performance?.now?.() ?? Date.now();
|
|
5694
|
+
const start = now();
|
|
5695
|
+
let cancelled = false;
|
|
5696
|
+
let frame;
|
|
5697
|
+
const step = () => {
|
|
5698
|
+
frame = void 0;
|
|
5699
|
+
if (cancelled || disposed || run !== emphasisRun) return;
|
|
5700
|
+
const t = Math.min(1, (now() - start) / duration);
|
|
5701
|
+
const eased = t * t * (3 - 2 * t);
|
|
5702
|
+
const strength = from + (to - from) * eased;
|
|
5703
|
+
if (t >= 1) return finish();
|
|
5704
|
+
if (Math.abs(strength - emphasisStrength) >= 0.01) {
|
|
5705
|
+
emphasisStrength = strength;
|
|
5706
|
+
writeEmphasis(true);
|
|
5707
|
+
}
|
|
5708
|
+
frame = view.requestAnimationFrame(step);
|
|
5709
|
+
};
|
|
5710
|
+
emphasisFrame = () => {
|
|
5711
|
+
cancelled = true;
|
|
5712
|
+
if (frame !== void 0) view.cancelAnimationFrame(frame);
|
|
5713
|
+
};
|
|
5714
|
+
frame = view.requestAnimationFrame(step);
|
|
5715
|
+
});
|
|
5716
|
+
}
|
|
5717
|
+
function layerPaint(layer, property) {
|
|
5718
|
+
const key = valueKey(layer, property);
|
|
5719
|
+
if (activeBlend && emphasisBlended.has(key))
|
|
5720
|
+
return activeBlend.blender.baseValue(layer, property);
|
|
5721
|
+
for (const entry of layersShown()) {
|
|
5722
|
+
if (entry && typeof entry === "object" && entry.id === layer) {
|
|
5723
|
+
const paint = entry.paint;
|
|
5724
|
+
return paint?.[property];
|
|
5725
|
+
}
|
|
5726
|
+
}
|
|
5727
|
+
return void 0;
|
|
5187
5728
|
}
|
|
5188
5729
|
function reducedMotion() {
|
|
5189
5730
|
const view = tileflowThemeMotionView(options.map);
|
|
@@ -5670,9 +6211,11 @@ export {
|
|
|
5670
6211
|
registerTileflowWorldRequestBridge,
|
|
5671
6212
|
subscribeTileflowSystemColorScheme,
|
|
5672
6213
|
tileflowFairUseNoticeMessage,
|
|
6214
|
+
tileflowLayerDomainMetadataKey,
|
|
5673
6215
|
tileflowMaplibreContourVersion,
|
|
5674
6216
|
tileflowPmtilesMaximumDirectoryDepth,
|
|
5675
|
-
tileflowPmtilesProtocol
|
|
6217
|
+
tileflowPmtilesProtocol,
|
|
6218
|
+
validateTileflowEmphasis
|
|
5676
6219
|
};
|
|
5677
6220
|
/*! Bundled license information:
|
|
5678
6221
|
|