@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/native.js
CHANGED
|
@@ -4606,6 +4606,15 @@ function validateTileflowThemeSelection(value) {
|
|
|
4606
4606
|
return value === "system" || isTileflowThemeName(value);
|
|
4607
4607
|
}
|
|
4608
4608
|
var tileflowThemeBlendLimits = Object.freeze({ maximumThemes: 8, minimumThemes: 2 });
|
|
4609
|
+
function validateTileflowThemeBlendSelection(value) {
|
|
4610
|
+
if (!value || typeof value !== "object") return false;
|
|
4611
|
+
const { position, themes } = value;
|
|
4612
|
+
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;
|
|
4613
|
+
}
|
|
4614
|
+
function nearestTileflowBlendTheme(blend) {
|
|
4615
|
+
const index = Math.min(blend.themes.length - 1, Math.max(0, Math.round(blend.position)));
|
|
4616
|
+
return blend.themes[index];
|
|
4617
|
+
}
|
|
4609
4618
|
function resolveTileflowRuntimeTheme(map2, selection, colorScheme) {
|
|
4610
4619
|
let name;
|
|
4611
4620
|
if (selection === void 0) {
|
|
@@ -4849,14 +4858,695 @@ function snapshotView(input) {
|
|
|
4849
4858
|
...parsed.zoom === void 0 ? {} : { zoom: parsed.zoom }
|
|
4850
4859
|
};
|
|
4851
4860
|
}
|
|
4861
|
+
|
|
4862
|
+
// src/theme-blend.ts
|
|
4863
|
+
var TileflowThemeBlendError = class extends Error {
|
|
4864
|
+
constructor() {
|
|
4865
|
+
super(...arguments);
|
|
4866
|
+
__publicField(this, "name", "TileflowThemeBlendError");
|
|
4867
|
+
}
|
|
4868
|
+
};
|
|
4869
|
+
var BlendLeaf = class {
|
|
4870
|
+
constructor(values) {
|
|
4871
|
+
__publicField(this, "values", values);
|
|
4872
|
+
}
|
|
4873
|
+
};
|
|
4874
|
+
var imageProperties = /* @__PURE__ */ new Set([
|
|
4875
|
+
"icon-image",
|
|
4876
|
+
"fill-pattern",
|
|
4877
|
+
"line-pattern",
|
|
4878
|
+
"background-pattern",
|
|
4879
|
+
"fill-extrusion-pattern"
|
|
4880
|
+
]);
|
|
4881
|
+
var opacityProperties = {
|
|
4882
|
+
background: "background-opacity",
|
|
4883
|
+
circle: "circle-opacity",
|
|
4884
|
+
fill: "fill-opacity",
|
|
4885
|
+
"fill-extrusion": "fill-extrusion-opacity",
|
|
4886
|
+
heatmap: "heatmap-opacity",
|
|
4887
|
+
line: "line-opacity",
|
|
4888
|
+
raster: "raster-opacity"
|
|
4889
|
+
};
|
|
4890
|
+
var featureOperators = /* @__PURE__ */ new Set([
|
|
4891
|
+
"feature-state",
|
|
4892
|
+
"geometry-type",
|
|
4893
|
+
"get",
|
|
4894
|
+
"has",
|
|
4895
|
+
"id",
|
|
4896
|
+
"in",
|
|
4897
|
+
"properties"
|
|
4898
|
+
]);
|
|
4899
|
+
var maximumBranches = 24;
|
|
4900
|
+
function planTileflowThemeBlend(styles, options = {}) {
|
|
4901
|
+
if (styles.length < 2)
|
|
4902
|
+
throw new TileflowThemeBlendError("A theme blend needs two or more themes.");
|
|
4903
|
+
const [base2] = styles;
|
|
4904
|
+
assertSameStructure(styles, base2);
|
|
4905
|
+
const mixedImages = new Set(imageProperties);
|
|
4906
|
+
if (options.iconImages === "switch") mixedImages.delete("icon-image");
|
|
4907
|
+
const paints = [];
|
|
4908
|
+
const switches = [];
|
|
4909
|
+
const imageGroups = collectImageNames(styles, base2, mixedImages);
|
|
4910
|
+
const symbolLayers = /* @__PURE__ */ new Set();
|
|
4911
|
+
const images = /* @__PURE__ */ new Map();
|
|
4912
|
+
let splitLayers = 0;
|
|
4913
|
+
let copiedLayers = 0;
|
|
4914
|
+
let switchedLayers = 0;
|
|
4915
|
+
const layers = [];
|
|
4916
|
+
for (const [index, layer] of base2.layers.entries()) {
|
|
4917
|
+
const variants = styles.map((style) => style.layers[index]);
|
|
4918
|
+
layers.push(...planLayer(variants, String(layer.type)));
|
|
4919
|
+
}
|
|
4920
|
+
const switchesByLayer = /* @__PURE__ */ new Map();
|
|
4921
|
+
for (const entry of switches) {
|
|
4922
|
+
const list = switchesByLayer.get(entry.layer) ?? [];
|
|
4923
|
+
list.push(entry);
|
|
4924
|
+
switchesByLayer.set(entry.layer, list);
|
|
4925
|
+
}
|
|
4926
|
+
const sky = differingRoot(styles, "sky");
|
|
4927
|
+
const light = differingRoot(styles, "light");
|
|
4928
|
+
const metadata = alignInteractionManifest(base2.metadata, layers);
|
|
4929
|
+
return {
|
|
4930
|
+
themes: styles.length,
|
|
4931
|
+
paints,
|
|
4932
|
+
images: [...images.values()],
|
|
4933
|
+
switches,
|
|
4934
|
+
...sky === void 0 ? {} : { sky },
|
|
4935
|
+
...light === void 0 ? {} : { light },
|
|
4936
|
+
summary: { splitLayers, copiedLayers, switchedLayers, switchedValues: switches.length },
|
|
4937
|
+
styleAt(position) {
|
|
4938
|
+
const at = clampPosition(position, styles.length);
|
|
4939
|
+
const dominant = Math.round(at);
|
|
4940
|
+
const style = {
|
|
4941
|
+
...base2,
|
|
4942
|
+
...metadata === void 0 ? {} : { metadata },
|
|
4943
|
+
// Symbols show the nearest theme, as the label cross-fade leaves them between changes.
|
|
4944
|
+
layers: layers.map(
|
|
4945
|
+
(layer) => fillLayer(
|
|
4946
|
+
layer,
|
|
4947
|
+
symbolLayers.has(String(layer.id)) ? dominant : at,
|
|
4948
|
+
dominant,
|
|
4949
|
+
switchesByLayer.get(String(layer.id))
|
|
4950
|
+
)
|
|
4951
|
+
)
|
|
4952
|
+
};
|
|
4953
|
+
if (sky !== void 0) style.sky = fillTemplate(sky, at);
|
|
4954
|
+
if (light !== void 0) style.light = fillTemplate(light, at);
|
|
4955
|
+
return style;
|
|
4956
|
+
}
|
|
4957
|
+
};
|
|
4958
|
+
function planLayer(variants, type) {
|
|
4959
|
+
const [layer] = variants;
|
|
4960
|
+
const id = String(layer.id);
|
|
4961
|
+
const paint = { ...layer.paint };
|
|
4962
|
+
const layout = { ...layer.layout };
|
|
4963
|
+
const follows = type === "symbol" ? "dominant" : "position";
|
|
4964
|
+
const ownPaints = [];
|
|
4965
|
+
const ownSwitches = [];
|
|
4966
|
+
const driven = [];
|
|
4967
|
+
const paintOf = (variant) => variant.paint ?? {};
|
|
4968
|
+
for (const group of ["layout", "paint"]) {
|
|
4969
|
+
const target = group === "paint" ? paint : layout;
|
|
4970
|
+
for (const property of propertyNames(variants, group)) {
|
|
4971
|
+
const values = variants.map(
|
|
4972
|
+
(variant) => variant[group]?.[property]
|
|
4973
|
+
);
|
|
4974
|
+
if (values.every((value) => same(value, values[0]))) continue;
|
|
4975
|
+
if (mixedImages.has(property)) {
|
|
4976
|
+
const tuples = imageTuples(values);
|
|
4977
|
+
if (tuples && tuples.every((names) => imageGroups.get(names[0])?.size === 1)) {
|
|
4978
|
+
for (const names of tuples) {
|
|
4979
|
+
if (names.every((name) => name === names[0])) continue;
|
|
4980
|
+
images.set(JSON.stringify(names), {
|
|
4981
|
+
names,
|
|
4982
|
+
follows: property === "icon-image" ? "dominant" : "position"
|
|
4983
|
+
});
|
|
4984
|
+
}
|
|
4985
|
+
continue;
|
|
4986
|
+
}
|
|
4987
|
+
}
|
|
4988
|
+
if (group === "layout" || values.includes(void 0) || imageProperties.has(property)) {
|
|
4989
|
+
ownSwitches.push({ group, property, values });
|
|
4990
|
+
continue;
|
|
4991
|
+
}
|
|
4992
|
+
if (values.some(isDataDriven)) {
|
|
4993
|
+
driven.push(property);
|
|
4994
|
+
continue;
|
|
4995
|
+
}
|
|
4996
|
+
const template = tryMixTemplate(values);
|
|
4997
|
+
if (template === void 0) {
|
|
4998
|
+
ownSwitches.push({ group, property, values });
|
|
4999
|
+
continue;
|
|
5000
|
+
}
|
|
5001
|
+
ownPaints.push({ property, template });
|
|
5002
|
+
target[property] = template;
|
|
5003
|
+
}
|
|
5004
|
+
}
|
|
5005
|
+
const planned = { ...layer, paint, layout };
|
|
5006
|
+
let results = [{ layer: planned, extra: [] }];
|
|
5007
|
+
if (driven.length > 0) {
|
|
5008
|
+
const split = type === "symbol" ? void 0 : splitLayer();
|
|
5009
|
+
const copies = split ?? (type === "symbol" ? void 0 : copyLayer());
|
|
5010
|
+
if (copies) results = copies;
|
|
5011
|
+
else
|
|
5012
|
+
for (const property of driven)
|
|
5013
|
+
ownSwitches.push({
|
|
5014
|
+
group: "paint",
|
|
5015
|
+
property,
|
|
5016
|
+
values: variants.map((variant) => paintOf(variant)[property])
|
|
5017
|
+
});
|
|
5018
|
+
}
|
|
5019
|
+
if (options.featureSwitches === "layers" && results.length === 1 && ownSwitches.some((entry) => entry.values.some(isDataDriven)))
|
|
5020
|
+
results = switchLayer();
|
|
5021
|
+
for (const result of results) {
|
|
5022
|
+
const resultId = String(result.layer.id);
|
|
5023
|
+
if (type === "symbol") symbolLayers.add(resultId);
|
|
5024
|
+
const overridden = new Set(result.extra.map((entry) => entry.property));
|
|
5025
|
+
for (const entry of [
|
|
5026
|
+
...ownPaints.filter((own) => !overridden.has(own.property)),
|
|
5027
|
+
...result.extra
|
|
5028
|
+
])
|
|
5029
|
+
paints.push({ layer: resultId, property: entry.property, template: entry.template, follows });
|
|
5030
|
+
if (result.visibility)
|
|
5031
|
+
switches.push({
|
|
5032
|
+
layer: resultId,
|
|
5033
|
+
group: "layout",
|
|
5034
|
+
property: "visibility",
|
|
5035
|
+
values: result.visibility
|
|
5036
|
+
});
|
|
5037
|
+
else for (const entry of ownSwitches) switches.push({ layer: resultId, ...entry });
|
|
5038
|
+
}
|
|
5039
|
+
return results.map((result) => result.layer);
|
|
5040
|
+
function switchLayer() {
|
|
5041
|
+
switchedLayers += variants.length;
|
|
5042
|
+
return variants.map((_, theme) => {
|
|
5043
|
+
const copyPaint = { ...paint };
|
|
5044
|
+
const copyLayout = { ...layout };
|
|
5045
|
+
for (const entry of ownSwitches) {
|
|
5046
|
+
const target = entry.group === "paint" ? copyPaint : copyLayout;
|
|
5047
|
+
const value = entry.values[theme];
|
|
5048
|
+
if (value === void 0) delete target[entry.property];
|
|
5049
|
+
else target[entry.property] = value;
|
|
5050
|
+
}
|
|
5051
|
+
const shown = copyLayout.visibility === "none" ? "none" : "visible";
|
|
5052
|
+
return {
|
|
5053
|
+
layer: { ...planned, id: `${id}::theme-${theme}`, paint: copyPaint, layout: copyLayout },
|
|
5054
|
+
extra: [],
|
|
5055
|
+
visibility: variants.map((__, nearest) => nearest === theme ? shown : "none")
|
|
5056
|
+
};
|
|
5057
|
+
});
|
|
5058
|
+
}
|
|
5059
|
+
function splitLayer() {
|
|
5060
|
+
if (isLegacyFilter(layer.filter)) return void 0;
|
|
5061
|
+
const branches = splitBranches(
|
|
5062
|
+
Object.fromEntries(
|
|
5063
|
+
driven.map((property) => [
|
|
5064
|
+
property,
|
|
5065
|
+
variants.map((variant) => paintOf(variant)[property])
|
|
5066
|
+
])
|
|
5067
|
+
)
|
|
5068
|
+
);
|
|
5069
|
+
if (!branches) return void 0;
|
|
5070
|
+
const groups = /* @__PURE__ */ new Map();
|
|
5071
|
+
for (const branch of branches) {
|
|
5072
|
+
const key = JSON.stringify(branch.valuesByProperty);
|
|
5073
|
+
let group = groups.get(key);
|
|
5074
|
+
if (!group) {
|
|
5075
|
+
const templates = {};
|
|
5076
|
+
for (const [property, values] of Object.entries(branch.valuesByProperty)) {
|
|
5077
|
+
const template = tryMixTemplate(values);
|
|
5078
|
+
if (template === void 0) return void 0;
|
|
5079
|
+
templates[property] = template;
|
|
5080
|
+
}
|
|
5081
|
+
group = { templates, conditions: [] };
|
|
5082
|
+
groups.set(key, group);
|
|
5083
|
+
}
|
|
5084
|
+
group.conditions.push(
|
|
5085
|
+
branch.conditions.length === 1 ? branch.conditions[0] : ["all", ...branch.conditions]
|
|
5086
|
+
);
|
|
5087
|
+
}
|
|
5088
|
+
splitLayers += groups.size;
|
|
5089
|
+
return [...groups.values()].map((group, number3) => {
|
|
5090
|
+
const chosen = group.conditions.length === 1 ? group.conditions[0] : ["any", ...group.conditions];
|
|
5091
|
+
return {
|
|
5092
|
+
layer: {
|
|
5093
|
+
...planned,
|
|
5094
|
+
id: `${id}::branch-${number3}`,
|
|
5095
|
+
filter: layer.filter === void 0 ? chosen : ["all", layer.filter, chosen],
|
|
5096
|
+
paint: { ...paint, ...group.templates }
|
|
5097
|
+
},
|
|
5098
|
+
extra: Object.entries(group.templates).filter(([, template]) => containsLeaf(template)).map(([property, template]) => ({ property, template }))
|
|
5099
|
+
};
|
|
5100
|
+
});
|
|
5101
|
+
}
|
|
5102
|
+
function copyLayer() {
|
|
5103
|
+
const opacity = opacityProperties[type];
|
|
5104
|
+
if (!opacity || variants.some((variant) => isDataDriven(paintOf(variant)[opacity])))
|
|
5105
|
+
return void 0;
|
|
5106
|
+
copiedLayers += variants.length;
|
|
5107
|
+
return variants.map((variant, theme) => {
|
|
5108
|
+
const weight = new BlendLeaf(variants.map((_, other) => other === theme ? 1 : 0));
|
|
5109
|
+
const template = scaleByWeight(paint[opacity] ?? 1, weight);
|
|
5110
|
+
const copyPaint = { ...paint, [opacity]: template };
|
|
5111
|
+
for (const property of driven) copyPaint[property] = paintOf(variant)[property];
|
|
5112
|
+
return {
|
|
5113
|
+
layer: { ...planned, id: `${id}::theme-${theme}`, paint: copyPaint },
|
|
5114
|
+
extra: [{ property: opacity, template }]
|
|
5115
|
+
};
|
|
5116
|
+
});
|
|
5117
|
+
}
|
|
5118
|
+
}
|
|
5119
|
+
}
|
|
5120
|
+
function fillTileflowThemeBlendTemplate(template, position) {
|
|
5121
|
+
return fillTemplate(template, position);
|
|
5122
|
+
}
|
|
5123
|
+
function fillLayer(layer, position, dominant, layerSwitches) {
|
|
5124
|
+
const filled = { ...layer };
|
|
5125
|
+
for (const group of ["layout", "paint"]) {
|
|
5126
|
+
const values = layer[group];
|
|
5127
|
+
if (!values) continue;
|
|
5128
|
+
const out = {};
|
|
5129
|
+
for (const [key, value] of Object.entries(values)) out[key] = fillTemplate(value, position);
|
|
5130
|
+
for (const entry of layerSwitches ?? []) {
|
|
5131
|
+
if (entry.group !== group) continue;
|
|
5132
|
+
const value = entry.values[dominant];
|
|
5133
|
+
if (value === void 0) delete out[entry.property];
|
|
5134
|
+
else out[entry.property] = value;
|
|
5135
|
+
}
|
|
5136
|
+
if (Object.keys(out).length > 0) filled[group] = out;
|
|
5137
|
+
else delete filled[group];
|
|
5138
|
+
}
|
|
5139
|
+
return filled;
|
|
5140
|
+
}
|
|
5141
|
+
function fillTemplate(node, position) {
|
|
5142
|
+
if (node instanceof BlendLeaf) return mixLeaf(node.values, position);
|
|
5143
|
+
if (Array.isArray(node)) return node.map((item) => fillTemplate(item, position));
|
|
5144
|
+
if (node && typeof node === "object" && containsLeaf(node)) {
|
|
5145
|
+
return Object.fromEntries(
|
|
5146
|
+
Object.entries(node).map(([key, value]) => [key, fillTemplate(value, position)])
|
|
5147
|
+
);
|
|
5148
|
+
}
|
|
5149
|
+
return node;
|
|
5150
|
+
}
|
|
5151
|
+
function containsLeaf(node) {
|
|
5152
|
+
if (node instanceof BlendLeaf) return true;
|
|
5153
|
+
if (Array.isArray(node)) return node.some(containsLeaf);
|
|
5154
|
+
if (node && typeof node === "object") return Object.values(node).some(containsLeaf);
|
|
5155
|
+
return false;
|
|
5156
|
+
}
|
|
5157
|
+
function mixLeaf(values, position) {
|
|
5158
|
+
const at = clampPosition(position, values.length);
|
|
5159
|
+
const index = Math.min(values.length - 2, Math.floor(at));
|
|
5160
|
+
const t = at - index;
|
|
5161
|
+
const from = values[index];
|
|
5162
|
+
const to = values[index + 1];
|
|
5163
|
+
if (t <= 0 || from === to) return from;
|
|
5164
|
+
if (t >= 1) return to;
|
|
5165
|
+
if (typeof from === "number" && typeof to === "number") return from + (to - from) * t;
|
|
5166
|
+
const a = oklab(String(from));
|
|
5167
|
+
const b = oklab(String(to));
|
|
5168
|
+
if (!a || !b) return t < 0.5 ? from : to;
|
|
5169
|
+
return colourFromOklab(a.map((value, channel) => value + (b[channel] - value) * t));
|
|
5170
|
+
}
|
|
5171
|
+
function clampPosition(position, themes) {
|
|
5172
|
+
if (!Number.isFinite(position)) return 0;
|
|
5173
|
+
return Math.min(themes - 1, Math.max(0, position));
|
|
5174
|
+
}
|
|
5175
|
+
function assertSameStructure(styles, base2) {
|
|
5176
|
+
const ids = (style) => style.layers.map((layer) => layer.id);
|
|
5177
|
+
for (const style of styles) {
|
|
5178
|
+
if (!same(ids(style), ids(base2)))
|
|
5179
|
+
throw new TileflowThemeBlendError(
|
|
5180
|
+
"Blended themes must have the same layers in the same order."
|
|
5181
|
+
);
|
|
5182
|
+
if (!same(style.sources, base2.sources))
|
|
5183
|
+
throw new TileflowThemeBlendError("Blended themes must use the same sources.");
|
|
5184
|
+
if (!same(style.sprite, base2.sprite) || !same(style.glyphs, base2.glyphs))
|
|
5185
|
+
throw new TileflowThemeBlendError("Blended themes must use the same sprite and glyphs.");
|
|
5186
|
+
for (const [index, layer] of style.layers.entries()) {
|
|
5187
|
+
const other = base2.layers[index];
|
|
5188
|
+
for (const key of ["type", "source", "source-layer", "filter", "minzoom", "maxzoom"])
|
|
5189
|
+
if (!same(layer[key], other[key]))
|
|
5190
|
+
throw new TileflowThemeBlendError(
|
|
5191
|
+
`Blended themes differ in ${key} of layer ${JSON.stringify(layer.id)}.`
|
|
5192
|
+
);
|
|
5193
|
+
}
|
|
5194
|
+
}
|
|
5195
|
+
}
|
|
5196
|
+
function propertyNames(variants, group) {
|
|
5197
|
+
const names = /* @__PURE__ */ new Set();
|
|
5198
|
+
for (const variant of variants)
|
|
5199
|
+
for (const name of Object.keys(variant[group] ?? {})) names.add(name);
|
|
5200
|
+
return [...names];
|
|
5201
|
+
}
|
|
5202
|
+
function differingRoot(styles, key) {
|
|
5203
|
+
const values = styles.map((style) => style[key]);
|
|
5204
|
+
if (values.every((value) => same(value, values[0]))) return void 0;
|
|
5205
|
+
return tryMixTemplate(values);
|
|
5206
|
+
}
|
|
5207
|
+
function collectImageNames(styles, base2, properties) {
|
|
5208
|
+
const uses = /* @__PURE__ */ new Map();
|
|
5209
|
+
for (const [index] of base2.layers.entries()) {
|
|
5210
|
+
for (const group of ["layout", "paint"]) {
|
|
5211
|
+
for (const property of properties) {
|
|
5212
|
+
const values = styles.map(
|
|
5213
|
+
(style) => style.layers[index]?.[group]?.[property]
|
|
5214
|
+
);
|
|
5215
|
+
if (values[0] === void 0) continue;
|
|
5216
|
+
for (const names of imageTuples(values) ?? []) {
|
|
5217
|
+
const set = uses.get(names[0]) ?? /* @__PURE__ */ new Set();
|
|
5218
|
+
set.add(JSON.stringify(names));
|
|
5219
|
+
uses.set(names[0], set);
|
|
5220
|
+
}
|
|
5221
|
+
}
|
|
5222
|
+
}
|
|
5223
|
+
}
|
|
5224
|
+
return uses;
|
|
5225
|
+
}
|
|
5226
|
+
function alignInteractionManifest(metadata, layers) {
|
|
5227
|
+
if (!isRecord(metadata)) return void 0;
|
|
5228
|
+
const planned = /* @__PURE__ */ new Map();
|
|
5229
|
+
for (const [index, layer] of layers.entries()) {
|
|
5230
|
+
const id = String(layer.id);
|
|
5231
|
+
const original = /^(.+)::(?:branch|theme)-\d+$/u.exec(id)?.[1] ?? id;
|
|
5232
|
+
planned.set(original, [...planned.get(original) ?? [], { id, index }]);
|
|
5233
|
+
}
|
|
5234
|
+
let aligned;
|
|
5235
|
+
const interactions = metadata["tileflow:interaction-manifest"];
|
|
5236
|
+
const domains = isRecord(interactions) ? interactions.domains : void 0;
|
|
5237
|
+
if (isRecord(interactions) && isRecord(domains) && isRecord(domains.poi)) {
|
|
5238
|
+
const poi = domains.poi;
|
|
5239
|
+
if (Array.isArray(poi.layers)) {
|
|
5240
|
+
const poiLayers = poi.layers.flatMap((entry) => {
|
|
5241
|
+
if (!isRecord(entry) || typeof entry.layerId !== "string") return [entry];
|
|
5242
|
+
const copies = planned.get(entry.layerId);
|
|
5243
|
+
if (!copies) return [entry];
|
|
5244
|
+
return copies.map(({ id, index }) => ({ ...entry, layerId: id, priority: index }));
|
|
5245
|
+
});
|
|
5246
|
+
aligned = {
|
|
5247
|
+
...metadata,
|
|
5248
|
+
"tileflow:interaction-manifest": {
|
|
5249
|
+
...interactions,
|
|
5250
|
+
domains: { ...domains, poi: { ...poi, layers: poiLayers } }
|
|
5251
|
+
}
|
|
5252
|
+
};
|
|
5253
|
+
}
|
|
5254
|
+
}
|
|
5255
|
+
const overlays = metadata["tileflow:overlay-placement-manifest"];
|
|
5256
|
+
if (isRecord(overlays) && isRecord(overlays.anchors)) {
|
|
5257
|
+
const anchors = Object.fromEntries(
|
|
5258
|
+
Object.entries(overlays.anchors).map(([placement, anchor]) => [
|
|
5259
|
+
placement,
|
|
5260
|
+
typeof anchor === "string" ? planned.get(anchor)?.[0]?.id ?? anchor : anchor
|
|
5261
|
+
])
|
|
5262
|
+
);
|
|
5263
|
+
aligned = {
|
|
5264
|
+
...aligned ?? metadata,
|
|
5265
|
+
"tileflow:overlay-placement-manifest": { ...overlays, anchors }
|
|
5266
|
+
};
|
|
5267
|
+
}
|
|
5268
|
+
return aligned;
|
|
5269
|
+
}
|
|
5270
|
+
var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
5271
|
+
function imageTuples(values, out = []) {
|
|
5272
|
+
const [first] = values;
|
|
5273
|
+
if (values.every((value) => typeof value === "string")) {
|
|
5274
|
+
out.push(values);
|
|
5275
|
+
return out;
|
|
5276
|
+
}
|
|
5277
|
+
if (!Array.isArray(first) || typeof first[0] !== "string") {
|
|
5278
|
+
return values.every((value) => same(value, first)) ? out : void 0;
|
|
5279
|
+
}
|
|
5280
|
+
const operator = first[0];
|
|
5281
|
+
if (!values.every(
|
|
5282
|
+
(value) => Array.isArray(value) && value[0] === operator && value.length === first.length
|
|
5283
|
+
)) {
|
|
5284
|
+
return void 0;
|
|
5285
|
+
}
|
|
5286
|
+
const at = (index) => values.map((value) => value[index]);
|
|
5287
|
+
const sameAt = (index) => at(index).every((value) => same(value, first[index]));
|
|
5288
|
+
const outputs = [];
|
|
5289
|
+
if (operator === "literal" || operator === "image") outputs.push(1);
|
|
5290
|
+
else if (operator === "coalesce")
|
|
5291
|
+
for (let index = 1; index < first.length; index += 1) outputs.push(index);
|
|
5292
|
+
else if (operator === "match") {
|
|
5293
|
+
if (!sameAt(1)) return void 0;
|
|
5294
|
+
for (let index = 2; index < first.length - 1; index += 2) {
|
|
5295
|
+
if (!sameAt(index)) return void 0;
|
|
5296
|
+
outputs.push(index + 1);
|
|
5297
|
+
}
|
|
5298
|
+
outputs.push(first.length - 1);
|
|
5299
|
+
} else if (operator === "case") {
|
|
5300
|
+
for (let index = 1; index < first.length - 1; index += 2) {
|
|
5301
|
+
if (!sameAt(index)) return void 0;
|
|
5302
|
+
outputs.push(index + 1);
|
|
5303
|
+
}
|
|
5304
|
+
outputs.push(first.length - 1);
|
|
5305
|
+
} else if (operator === "step") {
|
|
5306
|
+
if (!sameAt(1)) return void 0;
|
|
5307
|
+
outputs.push(2);
|
|
5308
|
+
for (let index = 3; index < first.length; index += 2) {
|
|
5309
|
+
if (!sameAt(index)) return void 0;
|
|
5310
|
+
outputs.push(index + 1);
|
|
5311
|
+
}
|
|
5312
|
+
} else {
|
|
5313
|
+
return values.every((value) => same(value, first)) ? out : void 0;
|
|
5314
|
+
}
|
|
5315
|
+
for (const index of outputs) if (!imageTuples(at(index), out)) return void 0;
|
|
5316
|
+
return out;
|
|
5317
|
+
}
|
|
5318
|
+
function tryMixTemplate(values) {
|
|
5319
|
+
const [first] = values;
|
|
5320
|
+
if (values.every((value) => same(value, first))) return first;
|
|
5321
|
+
if (Array.isArray(first) && first[0] !== "literal" && values.every((value) => Array.isArray(value) && value.length === first.length)) {
|
|
5322
|
+
const items = [];
|
|
5323
|
+
for (let index = 0; index < first.length; index += 1) {
|
|
5324
|
+
const item = tryMixTemplate(values.map((value) => value[index]));
|
|
5325
|
+
if (item === void 0 && first[index] !== void 0) return void 0;
|
|
5326
|
+
items.push(item);
|
|
5327
|
+
}
|
|
5328
|
+
return items;
|
|
5329
|
+
}
|
|
5330
|
+
if (first && typeof first === "object" && !Array.isArray(first)) {
|
|
5331
|
+
const keys = Object.keys(first);
|
|
5332
|
+
if (!values.every((value) => value && typeof value === "object" && same(Object.keys(value), keys)))
|
|
5333
|
+
return void 0;
|
|
5334
|
+
const out = {};
|
|
5335
|
+
for (const key of keys) {
|
|
5336
|
+
const item = tryMixTemplate(values.map((value) => value[key]));
|
|
5337
|
+
if (item === void 0) return void 0;
|
|
5338
|
+
out[key] = item;
|
|
5339
|
+
}
|
|
5340
|
+
return out;
|
|
5341
|
+
}
|
|
5342
|
+
if (values.every((value) => typeof value === "number")) return new BlendLeaf(values);
|
|
5343
|
+
if (values.every((value) => typeof value === "string" && oklab(value)))
|
|
5344
|
+
return new BlendLeaf(values);
|
|
5345
|
+
return void 0;
|
|
5346
|
+
}
|
|
5347
|
+
function isDataDriven(value) {
|
|
5348
|
+
if (!Array.isArray(value)) return false;
|
|
5349
|
+
if (typeof value[0] === "string" && featureOperators.has(value[0])) return true;
|
|
5350
|
+
return value.some(isDataDriven);
|
|
5351
|
+
}
|
|
5352
|
+
var reads = (value, operator) => Array.isArray(value) && (value[0] === operator || value.some((item) => reads(item, operator)));
|
|
5353
|
+
function decisionOf(node) {
|
|
5354
|
+
if (!Array.isArray(node) || typeof node[0] !== "string") return void 0;
|
|
5355
|
+
const [operator] = node;
|
|
5356
|
+
if (operator === "match" && isDataDriven(node[1])) {
|
|
5357
|
+
const labels = [];
|
|
5358
|
+
const outputs = [];
|
|
5359
|
+
for (let index = 2; index < node.length - 1; index += 2) {
|
|
5360
|
+
labels.push(node[index]);
|
|
5361
|
+
outputs.push(node[index + 1]);
|
|
5362
|
+
}
|
|
5363
|
+
outputs.push(node.at(-1));
|
|
5364
|
+
return {
|
|
5365
|
+
index: ["match", node[1], ...labels.flatMap((label, index) => [label, index]), labels.length],
|
|
5366
|
+
outputs
|
|
5367
|
+
};
|
|
5368
|
+
}
|
|
5369
|
+
if (operator === "case") {
|
|
5370
|
+
const conditions = [];
|
|
5371
|
+
const outputs = [];
|
|
5372
|
+
for (let index = 1; index < node.length - 1; index += 2) {
|
|
5373
|
+
conditions.push(node[index]);
|
|
5374
|
+
outputs.push(node[index + 1]);
|
|
5375
|
+
}
|
|
5376
|
+
outputs.push(node.at(-1));
|
|
5377
|
+
if (!conditions.some(isDataDriven)) return void 0;
|
|
5378
|
+
return {
|
|
5379
|
+
index: [
|
|
5380
|
+
"case",
|
|
5381
|
+
...conditions.flatMap((condition, index) => [condition, index]),
|
|
5382
|
+
conditions.length
|
|
5383
|
+
],
|
|
5384
|
+
outputs
|
|
5385
|
+
};
|
|
5386
|
+
}
|
|
5387
|
+
if (operator === "step" && isDataDriven(node[1])) {
|
|
5388
|
+
const outputs = [node[2]];
|
|
5389
|
+
const stops = [];
|
|
5390
|
+
for (let index = 3; index < node.length; index += 2) {
|
|
5391
|
+
stops.push(node[index]);
|
|
5392
|
+
outputs.push(node[index + 1]);
|
|
5393
|
+
}
|
|
5394
|
+
return {
|
|
5395
|
+
index: ["step", node[1], 0, ...stops.flatMap((stop, index) => [stop, index + 1])],
|
|
5396
|
+
outputs
|
|
5397
|
+
};
|
|
5398
|
+
}
|
|
5399
|
+
return void 0;
|
|
5400
|
+
}
|
|
5401
|
+
function firstDecision(value) {
|
|
5402
|
+
const decision = decisionOf(value);
|
|
5403
|
+
if (decision) return decision;
|
|
5404
|
+
if (!Array.isArray(value) || value[0] === "literal") return void 0;
|
|
5405
|
+
for (const item of value) {
|
|
5406
|
+
const found = firstDecision(item);
|
|
5407
|
+
if (found) return found;
|
|
5408
|
+
}
|
|
5409
|
+
return void 0;
|
|
5410
|
+
}
|
|
5411
|
+
function choose(value, key, branch) {
|
|
5412
|
+
const decision = decisionOf(value);
|
|
5413
|
+
if (decision && JSON.stringify(decision.index) === key)
|
|
5414
|
+
return choose(decision.outputs[branch], key, branch);
|
|
5415
|
+
if (!Array.isArray(value) || value[0] === "literal") return value;
|
|
5416
|
+
return value.map((item) => choose(item, key, branch));
|
|
5417
|
+
}
|
|
5418
|
+
function splitBranches(valuesByProperty, conditions = []) {
|
|
5419
|
+
let decision;
|
|
5420
|
+
for (const values of Object.values(valuesByProperty)) {
|
|
5421
|
+
decision = firstDecision(values[0]);
|
|
5422
|
+
if (decision) break;
|
|
5423
|
+
}
|
|
5424
|
+
if (!decision) {
|
|
5425
|
+
const residual = Object.values(valuesByProperty).some((values) => values.some(isDataDriven));
|
|
5426
|
+
return residual ? void 0 : [{ conditions, valuesByProperty }];
|
|
5427
|
+
}
|
|
5428
|
+
if (reads(decision.index, "zoom") || reads(decision.index, "feature-state")) return void 0;
|
|
5429
|
+
const key = JSON.stringify(decision.index);
|
|
5430
|
+
const branches = [];
|
|
5431
|
+
for (let branch = 0; branch < decision.outputs.length; branch += 1) {
|
|
5432
|
+
const chosen = Object.fromEntries(
|
|
5433
|
+
Object.entries(valuesByProperty).map(([property, values]) => [
|
|
5434
|
+
property,
|
|
5435
|
+
values.map((value) => choose(value, key, branch))
|
|
5436
|
+
])
|
|
5437
|
+
);
|
|
5438
|
+
const nested = splitBranches(chosen, [...conditions, ["==", decision.index, branch]]);
|
|
5439
|
+
if (!nested) return void 0;
|
|
5440
|
+
branches.push(...nested);
|
|
5441
|
+
if (branches.length > maximumBranches) return void 0;
|
|
5442
|
+
}
|
|
5443
|
+
return branches;
|
|
5444
|
+
}
|
|
5445
|
+
var isLegacyFilter = (filter) => Array.isArray(filter) && typeof filter[1] === "string" && !Array.isArray(filter[1]);
|
|
5446
|
+
function scaleByWeight(opacity, weight) {
|
|
5447
|
+
const input = Array.isArray(opacity) ? opacity[0] === "interpolate" ? opacity[2] : opacity[1] : void 0;
|
|
5448
|
+
if (Array.isArray(opacity) && (opacity[0] === "interpolate" || opacity[0] === "step") && reads(input, "zoom")) {
|
|
5449
|
+
const head = opacity[0] === "interpolate" ? 3 : 2;
|
|
5450
|
+
return opacity.map(
|
|
5451
|
+
(item, index) => index >= head && (index - head) % 2 === (opacity[0] === "interpolate" ? 1 : 0) ? scaleByWeight(item, weight) : item
|
|
5452
|
+
);
|
|
5453
|
+
}
|
|
5454
|
+
if (typeof opacity === "number")
|
|
5455
|
+
return new BlendLeaf(weight.values.map((value) => opacity * Number(value)));
|
|
5456
|
+
return ["*", opacity, weight];
|
|
5457
|
+
}
|
|
5458
|
+
function same(a, b) {
|
|
5459
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
5460
|
+
}
|
|
5461
|
+
var oklabCache = /* @__PURE__ */ new Map();
|
|
5462
|
+
function oklab(colour) {
|
|
5463
|
+
const cached2 = oklabCache.get(colour);
|
|
5464
|
+
if (cached2 !== void 0) return cached2;
|
|
5465
|
+
const rgba = parseColour(colour);
|
|
5466
|
+
let result = null;
|
|
5467
|
+
if (rgba) {
|
|
5468
|
+
const [r, g, b] = rgba.slice(0, 3).map((channel) => {
|
|
5469
|
+
const value = channel / 255;
|
|
5470
|
+
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
5471
|
+
});
|
|
5472
|
+
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
5473
|
+
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
|
5474
|
+
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
5475
|
+
result = [
|
|
5476
|
+
0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
|
|
5477
|
+
1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
|
|
5478
|
+
0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
|
|
5479
|
+
rgba[3]
|
|
5480
|
+
];
|
|
5481
|
+
}
|
|
5482
|
+
if (oklabCache.size > 4096) oklabCache.clear();
|
|
5483
|
+
oklabCache.set(colour, result);
|
|
5484
|
+
return result;
|
|
5485
|
+
}
|
|
5486
|
+
function colourFromOklab([lightness, a, b, alpha]) {
|
|
5487
|
+
const l = (lightness + 0.3963377774 * a + 0.2158037573 * b) ** 3;
|
|
5488
|
+
const m = (lightness - 0.1055613458 * a - 0.0638541728 * b) ** 3;
|
|
5489
|
+
const s = (lightness - 0.0894841775 * a - 1.291485548 * b) ** 3;
|
|
5490
|
+
const [red, green, blue] = [
|
|
5491
|
+
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
|
5492
|
+
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
|
5493
|
+
-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
|
|
5494
|
+
].map((value) => {
|
|
5495
|
+
const clamped = Math.min(1, Math.max(0, value));
|
|
5496
|
+
const encoded = clamped <= 31308e-7 ? clamped * 12.92 : 1.055 * clamped ** (1 / 2.4) - 0.055;
|
|
5497
|
+
return Math.round(encoded * 255);
|
|
5498
|
+
});
|
|
5499
|
+
return `rgba(${red}, ${green}, ${blue}, ${Number(alpha.toFixed(3))})`;
|
|
5500
|
+
}
|
|
5501
|
+
function parseColour(colour) {
|
|
5502
|
+
const text = colour.trim().toLowerCase();
|
|
5503
|
+
const hex = /^#([\da-f]{3,8})$/u.exec(text)?.[1];
|
|
5504
|
+
if (hex && [3, 4, 6, 8].includes(hex.length)) {
|
|
5505
|
+
const full = hex.length <= 4 ? [...hex].map((digit) => digit + digit).join("") : hex;
|
|
5506
|
+
const channels = full.match(/../gu).map((pair) => Number.parseInt(pair, 16));
|
|
5507
|
+
return [channels[0], channels[1], channels[2], channels.length > 3 ? channels[3] / 255 : 1];
|
|
5508
|
+
}
|
|
5509
|
+
const functional = /^(rgba?|hsla?)\(([^)]+)\)$/u.exec(text);
|
|
5510
|
+
if (!functional) return null;
|
|
5511
|
+
const parts = functional[2].split(/[\s,/]+/u).filter(Boolean);
|
|
5512
|
+
if (parts.length < 3 || parts.length > 4) return null;
|
|
5513
|
+
const alpha = parts[3] === void 0 ? 1 : parseUnit(parts[3], 1);
|
|
5514
|
+
if (functional[1].startsWith("rgb")) {
|
|
5515
|
+
const [r, g, b] = parts.slice(0, 3).map((part) => parseUnit(part, 255));
|
|
5516
|
+
if ([r, g, b, alpha].some((value) => value === null)) return null;
|
|
5517
|
+
return [r, g, b, alpha];
|
|
5518
|
+
}
|
|
5519
|
+
const hue = Number.parseFloat(parts[0]);
|
|
5520
|
+
const saturation = parseUnit(parts[1], 1);
|
|
5521
|
+
const light = parseUnit(parts[2], 1);
|
|
5522
|
+
if (!Number.isFinite(hue) || saturation === null || light === null || alpha === null) return null;
|
|
5523
|
+
const chroma = (1 - Math.abs(2 * light - 1)) * saturation;
|
|
5524
|
+
const sector = (hue % 360 + 360) % 360 / 60;
|
|
5525
|
+
const x = chroma * (1 - Math.abs(sector % 2 - 1));
|
|
5526
|
+
const [r1, g1, b1] = sector < 1 ? [chroma, x, 0] : sector < 2 ? [x, chroma, 0] : sector < 3 ? [0, chroma, x] : sector < 4 ? [0, x, chroma] : sector < 5 ? [x, 0, chroma] : [chroma, 0, x];
|
|
5527
|
+
const offset = light - chroma / 2;
|
|
5528
|
+
return [(r1 + offset) * 255, (g1 + offset) * 255, (b1 + offset) * 255, alpha];
|
|
5529
|
+
}
|
|
5530
|
+
function parseUnit(part, scale) {
|
|
5531
|
+
const percentage = part.endsWith("%");
|
|
5532
|
+
const value = Number.parseFloat(part);
|
|
5533
|
+
if (!Number.isFinite(value)) return null;
|
|
5534
|
+
return percentage ? value / 100 * scale : value;
|
|
5535
|
+
}
|
|
4852
5536
|
export {
|
|
4853
5537
|
TileflowNativeSourceError,
|
|
4854
5538
|
TileflowNativeUrlError,
|
|
5539
|
+
TileflowThemeBlendError,
|
|
4855
5540
|
createTileflowNativeSourceController,
|
|
5541
|
+
fillTileflowThemeBlendTemplate,
|
|
4856
5542
|
loadTileflowNativeManifest,
|
|
5543
|
+
nearestTileflowBlendTheme,
|
|
5544
|
+
planTileflowThemeBlend,
|
|
4857
5545
|
resolveTileflowNativeInitialView,
|
|
4858
5546
|
resolveTileflowNativeManifestUrl,
|
|
4859
5547
|
resolveTileflowNativeResourceUrl,
|
|
4860
5548
|
tileflowNativeManifestLimits,
|
|
4861
|
-
tileflowNativeUrlLimits
|
|
5549
|
+
tileflowNativeUrlLimits,
|
|
5550
|
+
tileflowThemeBlendLimits,
|
|
5551
|
+
validateTileflowThemeBlendSelection
|
|
4862
5552
|
};
|