@tileflow/core 0.1.0-alpha.33 → 0.1.0-alpha.35

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/dist/build.js CHANGED
@@ -45,6 +45,25 @@ function compileTileflowSky(options) {
45
45
  };
46
46
  }
47
47
 
48
+ // src/buildings-3d-design.ts
49
+ var tileflowBuildingsMetadataKey = "tileflow:buildings";
50
+ var tileflowDefaultBuildings3DDesign = Object.freeze({
51
+ source: "tileflow",
52
+ layers: Object.freeze({ building: "building", places: "poi" }),
53
+ color: "#d8d0c0",
54
+ edges: 0,
55
+ lighting: "style",
56
+ colorVariation: 0,
57
+ verticalGradient: true,
58
+ nightLight: 0,
59
+ windows: false,
60
+ shadows: false,
61
+ occlusion: false
62
+ });
63
+ function tileflowBuildingsMetadata(design) {
64
+ return { version: 1, ...design };
65
+ }
66
+
48
67
  // src/data/world-generation.ts
49
68
  var tileflowWorldGeneration = "v1";
50
69
 
@@ -135,6 +154,7 @@ var canonicalFields = {
135
154
  markings: "markings",
136
155
  minHeight: "min_height",
137
156
  minZoom: "min_zoom",
157
+ mode: "mode",
138
158
  mtbScale: "mtb_scale",
139
159
  name: "name",
140
160
  nameEnglish: "name:en",
@@ -635,7 +655,16 @@ var tileflowBathymetrySourceId = "tileflow-bathymetry";
635
655
  var tileflowBathymetryDemSourceId = "tileflow-bathymetry-dem";
636
656
  var tileflowNauticalSourceId = "tileflow-nautical";
637
657
  var tileflowBathymetryV1Schema = Object.freeze({
638
- fields: Object.freeze({ minDepth: "min_depth", sortKey: "sort_key" }),
658
+ fields: Object.freeze({
659
+ class: "class",
660
+ depth: "depth",
661
+ id: "id",
662
+ maxZoom: "max_zoom",
663
+ minDepth: "min_depth",
664
+ minZoom: "min_zoom",
665
+ name: "name",
666
+ sortKey: "sort_key"
667
+ }),
639
668
  layer: "bathymetry",
640
669
  layers: Object.freeze({
641
670
  bands: "bathymetry",
@@ -700,6 +729,7 @@ var tileflowNauticalV1Schema = Object.freeze({
700
729
  product: "nautical-v1",
701
730
  schemaVersion: 1
702
731
  });
732
+ var tileflowBathymetryNameLayerLimits = Object.freeze({ maxZoomRanges: 128 });
703
733
  function resolveMarine(marine, apiBaseUrl = "https://api.tileflow.dev") {
704
734
  if (marine === void 0) return void 0;
705
735
  if (typeof marine === "string") {
@@ -739,6 +769,7 @@ function resolveBathymetry(input, apiBaseUrl) {
739
769
  ...advanced?.contours === void 0 ? {} : { contours: advanced.contours },
740
770
  display,
741
771
  ...advanced?.labels === void 0 ? {} : { labels: advanced.labels },
772
+ ...advanced?.vectorLayers === void 0 ? {} : { vectorLayers: cloneJson(advanced.vectorLayers) },
742
773
  ...reliefVisible ? { relief: resolveBathymetryRelief(reliefConfig ?? {}, apiBaseUrl) } : {},
743
774
  ...usesVector ? {
744
775
  vector: resolveMarineSource({
@@ -850,98 +881,6 @@ function cloneJson(value) {
850
881
  return JSON.parse(JSON.stringify(value));
851
882
  }
852
883
 
853
- // src/cartography/contributions.ts
854
- var tileflowLayerSlots = [
855
- "background",
856
- "land",
857
- "terrain",
858
- "hydro",
859
- "building-areas",
860
- "transport-areas",
861
- "transport-tunnel-shadow",
862
- "transport-tunnel-casing",
863
- "transport-tunnel-fill",
864
- "transport-pedestrian-areas",
865
- "aeroways",
866
- "transport-surface-shadow",
867
- "transport-surface-casing",
868
- "transport-surface-fill",
869
- "transport-bridge-shadow",
870
- "transport-bridge-casing",
871
- "transport-bridge-fill",
872
- "transport-symbols",
873
- "boundaries",
874
- "buildings",
875
- "vegetation",
876
- "symbols"
877
- ];
878
- var tileflowLayerBands = [
879
- "land",
880
- "relief",
881
- "water",
882
- "roads",
883
- "boundaries",
884
- "buildings",
885
- "vegetation",
886
- "labels"
887
- ];
888
- var bandSlots = {
889
- land: ["background", "land"],
890
- relief: ["terrain", "terrain"],
891
- water: ["hydro", "hydro"],
892
- roads: ["transport-areas", "transport-symbols"],
893
- boundaries: ["boundaries", "boundaries"],
894
- buildings: ["buildings", "buildings"],
895
- vegetation: ["vegetation", "vegetation"],
896
- labels: ["symbols", "symbols"]
897
- };
898
- var tileflowBandPlacements = tileflowLayerBands.flatMap(
899
- (band) => [`below-${band}`, `above-${band}`]
900
- );
901
- function tileflowBandPlacementSlot(placement) {
902
- const [side, band] = splitBandPlacement(placement);
903
- return bandSlots[band][side === "below" ? 0 : 1];
904
- }
905
- function tileflowBandPlacementIndex(slotRanks, placement) {
906
- const [side, band] = splitBandPlacement(placement);
907
- const [first, last] = bandSlots[band];
908
- const threshold = side === "below" ? tileflowLayerSlots.indexOf(first) : tileflowLayerSlots.indexOf(last) + 1;
909
- const index = slotRanks.findIndex((rank) => rank !== void 0 && rank >= threshold);
910
- return index < 0 ? slotRanks.length : index;
911
- }
912
- function splitBandPlacement(placement) {
913
- const separator = placement.indexOf("-");
914
- return [
915
- placement.slice(0, separator),
916
- placement.slice(separator + 1)
917
- ];
918
- }
919
- var tileflowSemanticTargetPattern = /^[a-z][a-z0-9-]*(?:\.[A-Za-z0-9_-]+)*$/u;
920
- var tileflowRenderStackOperationNamePattern = /^[a-z][A-Za-z0-9_-]*$/u;
921
- var tileflowCompilerMetadataKeys = Object.freeze({
922
- owner: "tileflow:compiler-owner",
923
- slot: "tileflow:compiler-slot",
924
- target: "tileflow:compiler-target"
925
- });
926
-
927
- // src/modules/addresses/index.ts
928
- function addresses(options = {}) {
929
- return { type: "addresses", ...cloneJson2(options) };
930
- }
931
- function cloneJson2(value) {
932
- return JSON.parse(JSON.stringify(value));
933
- }
934
-
935
- // src/cartography/context.ts
936
- function typographyTextStyle(typography) {
937
- return {
938
- font: typography.font,
939
- ...typography.fallbacks ? { fallbacks: typography.fallbacks } : {},
940
- ...typography.letterSpacing === void 0 ? {} : { letterSpacing: typography.letterSpacing },
941
- ...typography.transform === void 0 ? {} : { transform: typography.transform }
942
- };
943
- }
944
-
945
884
  // src/cartography/layer-style.ts
946
885
  import { Color as Color2 } from "@maplibre/maplibre-gl-style-spec";
947
886
 
@@ -997,20 +936,20 @@ function createTileflowLineBackgroundFontStack(source, metrics = {}) {
997
936
  return stack;
998
937
  }
999
938
  function parseTileflowLineBackgroundFontStack(stack) {
1000
- const match2 = STACK_PATTERN.exec(stack);
1001
- if (!match2 || stack.length > MAX_STACK_LENGTH || !SOURCE_PATTERN.test(match2[1]))
939
+ const match3 = STACK_PATTERN.exec(stack);
940
+ if (!match3 || stack.length > MAX_STACK_LENGTH || !SOURCE_PATTERN.test(match3[1]))
1002
941
  return void 0;
1003
942
  const metrics = {
1004
- letterSpacing: Number(match2[2]) / 1e3,
1005
- lineHeight: Number(match2[3]) / 1e3,
1006
- padding: Number(match2[4]) / 1e3
943
+ letterSpacing: Number(match3[2]) / 1e3,
944
+ lineHeight: Number(match3[3]) / 1e3,
945
+ padding: Number(match3[4]) / 1e3
1007
946
  };
1008
947
  try {
1009
948
  const normalized = normalizeTileflowLineBackgroundMetrics(metrics);
1010
- if (`lines-v1-t${thousandths(normalized.letterSpacing)}-l${thousandths(normalized.lineHeight)}-p${thousandths(normalized.padding)}` !== stack.slice(match2[1].length + 1)) {
949
+ if (`lines-v1-t${thousandths(normalized.letterSpacing)}-l${thousandths(normalized.lineHeight)}-p${thousandths(normalized.padding)}` !== stack.slice(match3[1].length + 1)) {
1011
950
  return void 0;
1012
951
  }
1013
- return { metrics: normalized, source: match2[1] };
952
+ return { metrics: normalized, source: match3[1] };
1014
953
  } catch {
1015
954
  return void 0;
1016
955
  }
@@ -1169,11 +1108,11 @@ var zoom = {
1169
1108
  };
1170
1109
  function expression(value) {
1171
1110
  validateExpressionArray(value, "expression");
1172
- return { kind: "expression", value: cloneJson3(value) };
1111
+ return { kind: "expression", value: cloneJson2(value) };
1173
1112
  }
1174
1113
  function toMapLibreStyleValue(value) {
1175
1114
  if (isTileflowExpression(value)) {
1176
- return cloneJson3(value.value);
1115
+ return cloneJson2(value.value);
1177
1116
  }
1178
1117
  if (isTileflowZoomValue(value)) {
1179
1118
  if (value.interpolation === "step") {
@@ -1181,8 +1120,8 @@ function toMapLibreStyleValue(value) {
1181
1120
  return [
1182
1121
  "step",
1183
1122
  ["zoom"],
1184
- cloneJson3(firstValue),
1185
- ...rest.flatMap(([stop, stopValue]) => [stop, cloneJson3(stopValue)])
1123
+ cloneJson2(firstValue),
1124
+ ...rest.flatMap(([stop, stopValue]) => [stop, cloneJson2(stopValue)])
1186
1125
  ];
1187
1126
  }
1188
1127
  const interpolation = value.interpolation === "exponential" ? ["exponential", value.base] : [value.interpolation];
@@ -1190,13 +1129,13 @@ function toMapLibreStyleValue(value) {
1190
1129
  "interpolate",
1191
1130
  interpolation,
1192
1131
  ["zoom"],
1193
- ...value.stops.flatMap(([stop, stopValue]) => [stop, cloneJson3(stopValue)])
1132
+ ...value.stops.flatMap(([stop, stopValue]) => [stop, cloneJson2(stopValue)])
1194
1133
  ];
1195
1134
  }
1196
1135
  if (isTileflowThemeValueNode(value)) {
1197
1136
  throw new Error("Tileflow theme values must be resolved before MapLibre style compilation.");
1198
1137
  }
1199
- return cloneJson3(value);
1138
+ return cloneJson2(value);
1200
1139
  }
1201
1140
  function isTileflowExpression(value) {
1202
1141
  return isRecord(value) && value.kind === "expression" && Array.isArray(value.value);
@@ -1233,7 +1172,7 @@ function createZoomValue(interpolation, stops, base) {
1233
1172
  throw new Error("Tileflow zoom stops must be strictly increasing.");
1234
1173
  }
1235
1174
  previous = stop;
1236
- return [stop, cloneJson3(value)];
1175
+ return [stop, cloneJson2(value)];
1237
1176
  });
1238
1177
  return {
1239
1178
  kind: "zoom",
@@ -1249,9 +1188,9 @@ function validateExpressionArray(value, name) {
1249
1188
  if (!isMapLibreExpressionOperator(value[0])) {
1250
1189
  throw new Error(`Unknown MapLibre expression operator: ${value[0]}`);
1251
1190
  }
1252
- cloneJson3(value);
1191
+ cloneJson2(value);
1253
1192
  }
1254
- function cloneJson3(value) {
1193
+ function cloneJson2(value) {
1255
1194
  const serialized = JSON.stringify(value);
1256
1195
  if (serialized === void 0) {
1257
1196
  throw new Error("Tileflow style values must be JSON-serializable.");
@@ -1571,30 +1510,30 @@ function asRecord(value) {
1571
1510
 
1572
1511
  // src/cartography/merge.ts
1573
1512
  function mergeTileflowDesign(base, ...overlays) {
1574
- let resolved = cloneJson4(base);
1513
+ let resolved = cloneJson3(base);
1575
1514
  for (const overlay of overlays) {
1576
1515
  resolved = mergeValue(resolved, overlay);
1577
1516
  }
1578
1517
  return resolved;
1579
1518
  }
1580
1519
  function mergeValue(base, overlay) {
1581
- if (overlay === void 0) return cloneJson4(base);
1520
+ if (overlay === void 0) return cloneJson3(base);
1582
1521
  if (Array.isArray(overlay) || isTileflowExpression(overlay) || isTileflowThemeValueNode(overlay) || isTileflowZoomValue(overlay) || !isRecord2(overlay)) {
1583
- return cloneJson4(overlay);
1522
+ return cloneJson3(overlay);
1584
1523
  }
1585
1524
  const baseRecord = isRecord2(base) ? base : {};
1586
1525
  const result = {};
1587
1526
  for (const key of /* @__PURE__ */ new Set([...Object.keys(baseRecord), ...Object.keys(overlay)])) {
1588
1527
  const overlayValue = overlay[key];
1589
1528
  if (overlayValue === void 0) {
1590
- if (key in baseRecord) result[key] = cloneJson4(baseRecord[key]);
1529
+ if (key in baseRecord) result[key] = cloneJson3(baseRecord[key]);
1591
1530
  continue;
1592
1531
  }
1593
1532
  result[key] = mergeValue(baseRecord[key], overlayValue);
1594
1533
  }
1595
1534
  return result;
1596
1535
  }
1597
- function cloneJson4(value) {
1536
+ function cloneJson3(value) {
1598
1537
  if (value === void 0) return value;
1599
1538
  return JSON.parse(JSON.stringify(value));
1600
1539
  }
@@ -1602,6 +1541,306 @@ function isRecord2(value) {
1602
1541
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
1603
1542
  }
1604
1543
 
1544
+ // src/trees-3d.ts
1545
+ var tileflowWorldTreeFields = Object.freeze({
1546
+ height: "height",
1547
+ diameterCrown: "diameter_crown",
1548
+ circumference: "circumference",
1549
+ genus: "genus",
1550
+ species: "species",
1551
+ leafType: "leaf_type",
1552
+ taxon: "taxon",
1553
+ canopyRank: "rank",
1554
+ canopyClearance: "clearance_px_z15",
1555
+ canopyCover: "cover_pct",
1556
+ canopyHeight: "height_m"
1557
+ });
1558
+ var tileflowDefaultUnknownTreeForms = Object.freeze({
1559
+ round: 1,
1560
+ open: 1,
1561
+ tall: 1
1562
+ });
1563
+ var broadleaves = Object.freeze({
1564
+ ...Object.fromEntries(
1565
+ [
1566
+ "acacia",
1567
+ "albizia",
1568
+ "arbutus",
1569
+ "bursera",
1570
+ "ceratonia",
1571
+ "delonix",
1572
+ "ficus",
1573
+ "gleditsia",
1574
+ "jacaranda",
1575
+ "olea",
1576
+ "quercus",
1577
+ "robinia",
1578
+ "schinus",
1579
+ "sophora",
1580
+ "styphnolobium",
1581
+ "tamarix"
1582
+ ].map((genus) => [genus, "open"])
1583
+ ),
1584
+ ...Object.fromEntries(
1585
+ [
1586
+ "betula",
1587
+ "celtis",
1588
+ "fraxinus",
1589
+ "ginkgo",
1590
+ "liquidambar",
1591
+ "liriodendron",
1592
+ "magnolia",
1593
+ "platanus",
1594
+ "populus",
1595
+ "pyrus",
1596
+ "ulmus",
1597
+ "zelkova"
1598
+ ].map((genus) => [genus, "tall"])
1599
+ ),
1600
+ ...Object.fromEntries(
1601
+ [
1602
+ "acer",
1603
+ "aesculus",
1604
+ "carpinus",
1605
+ "castanea",
1606
+ "catalpa",
1607
+ "cercis",
1608
+ "citrus",
1609
+ "fagus",
1610
+ "lagerstroemia",
1611
+ "malus",
1612
+ "melia",
1613
+ "morus",
1614
+ "prunus",
1615
+ "sorbus",
1616
+ "tilia"
1617
+ ].map((genus) => [genus, "round"])
1618
+ )
1619
+ });
1620
+ var tileflowTreeModelIdPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
1621
+ var tileflowVegetationMetadataKey = "tileflow:vegetation";
1622
+ function tileflowVegetationMetadata(vegetation2) {
1623
+ return { version: 1, ...vegetation2 };
1624
+ }
1625
+
1626
+ // src/modules/vegetation/compiler.ts
1627
+ function compileVegetation(request, context) {
1628
+ const minZoom = request?.flat?.minZoom ?? 16;
1629
+ const mode = request?.mode ?? "3d";
1630
+ const config = mergeTileflowDesign(
1631
+ {
1632
+ type: "vegetation",
1633
+ enabled: true,
1634
+ flat: {
1635
+ color: context.colors.landcover.wood,
1636
+ minZoom,
1637
+ opacity: mode === "3d" ? 0.82 : 0.9,
1638
+ pitchAlignment: "map",
1639
+ pitchScale: "map",
1640
+ radius: minZoom < 24 ? zoom.linear([
1641
+ [minZoom, 2.2],
1642
+ [minZoom < 20 ? 20 : 24, 8]
1643
+ ]) : 2.2,
1644
+ strokeColor: context.colors.park,
1645
+ strokeOpacity: 0.55,
1646
+ strokeWidth: 0.8
1647
+ },
1648
+ mode,
1649
+ threeDimensional: {
1650
+ barkColor: context.colors.vegetation.tree.bark,
1651
+ broadleafColors: context.colors.vegetation.tree.broadleaf,
1652
+ coniferColors: context.colors.vegetation.tree.conifer,
1653
+ palmColors: context.colors.vegetation.tree.palm,
1654
+ crownScale: 1,
1655
+ heightScale: 1
1656
+ }
1657
+ },
1658
+ request
1659
+ );
1660
+ if (config.enabled === false || !context.data.schema.layers.tree) return [];
1661
+ const resolvedMode = config.mode ?? "3d";
1662
+ return [
1663
+ {
1664
+ kind: "layer",
1665
+ layer: applyCircleStyle(
1666
+ {
1667
+ id: "tileflow-vegetation-trees",
1668
+ type: "circle",
1669
+ source: context.data.sourceId,
1670
+ "source-layer": context.data.schema.layers.tree,
1671
+ // The layer also holds rows of trees as lines; a circle on each vertex would misdraw them.
1672
+ filter: ["==", ["geometry-type"], "Point"],
1673
+ metadata: {
1674
+ "tileflow:vegetation-mode": resolvedMode,
1675
+ ...resolvedMode === "3d" ? { "tileflow:vegetation-fallback": "flat-circle" } : {}
1676
+ }
1677
+ },
1678
+ config.flat ?? {}
1679
+ ),
1680
+ localOrder: 1,
1681
+ owner: "vegetation",
1682
+ slot: "vegetation",
1683
+ target: "vegetation.trees"
1684
+ }
1685
+ ];
1686
+ }
1687
+ function tileflowVegetationDescription(request, context) {
1688
+ const { schema } = context.data;
1689
+ if (request?.enabled === false || request?.mode === "flat" || !schema.layers.tree)
1690
+ return void 0;
1691
+ const tree = context.colors.vegetation.tree;
1692
+ const style = request?.threeDimensional ?? {};
1693
+ const colours = (value, fallback) => Array.isArray(value) && value.length > 0 && value.every((entry) => typeof entry === "string") ? value : [...fallback];
1694
+ const number = (value) => {
1695
+ const resolved = value === void 0 ? void 0 : toMapLibreStyleValue(value);
1696
+ return typeof resolved === "number" && resolved > 0 ? resolved : 1;
1697
+ };
1698
+ const field2 = (name, fallback) => schema.fields[name] ?? fallback;
1699
+ return {
1700
+ source: context.data.sourceId,
1701
+ layers: {
1702
+ tree: schema.layers.tree,
1703
+ ...schema.layers.canopy ? { canopy: schema.layers.canopy } : {}
1704
+ },
1705
+ fields: {
1706
+ ...tileflowWorldTreeFields,
1707
+ height: field2("height", tileflowWorldTreeFields.height),
1708
+ diameterCrown: field2("diameterCrown", tileflowWorldTreeFields.diameterCrown),
1709
+ genus: field2("genus", tileflowWorldTreeFields.genus),
1710
+ species: field2("species", tileflowWorldTreeFields.species),
1711
+ leafType: field2("leafType", tileflowWorldTreeFields.leafType),
1712
+ canopyRank: field2("canopyRank", tileflowWorldTreeFields.canopyRank),
1713
+ canopyClearance: field2("canopyClearanceAtZoom15", tileflowWorldTreeFields.canopyClearance),
1714
+ canopyCover: field2("canopyCoverPercent", tileflowWorldTreeFields.canopyCover)
1715
+ },
1716
+ colors: {
1717
+ bark: typeof style.barkColor === "string" ? style.barkColor : tree.bark,
1718
+ broadleaf: colours(style.broadleafColors, tree.broadleaf),
1719
+ conifer: colours(style.coniferColors, tree.conifer),
1720
+ palm: colours(style.palmColors, tree.palm)
1721
+ },
1722
+ heightScale: number(style.heightScale),
1723
+ crownScale: number(style.crownScale),
1724
+ forms: Object.fromEntries(
1725
+ Object.entries(style.forms ?? {}).map(([form, own]) => [
1726
+ form,
1727
+ {
1728
+ heightScale: number(own?.heightScale),
1729
+ crownScale: number(own?.crownScale),
1730
+ ...own?.models?.length ? { models: [...own.models] } : {}
1731
+ }
1732
+ ])
1733
+ ),
1734
+ // Filled by the compiler from the map's prepared models.
1735
+ models: {},
1736
+ finish: style.finish ?? "faceted",
1737
+ edges: (() => {
1738
+ const edges = toMapLibreStyleValue(style.edges ?? 0);
1739
+ return typeof edges === "number" ? edges : 0;
1740
+ })(),
1741
+ ...(() => {
1742
+ const edgeColor = style.edgeColor === void 0 ? void 0 : toMapLibreStyleValue(style.edgeColor);
1743
+ return typeof edgeColor === "string" ? { edgeColor } : {};
1744
+ })(),
1745
+ unknownForms: style.unknownForms ?? tileflowDefaultUnknownTreeForms,
1746
+ regions: style.regions ?? [],
1747
+ // Filled by the compiler once the style's layers are final.
1748
+ flatLayers: []
1749
+ };
1750
+ }
1751
+
1752
+ // src/cartography/contributions.ts
1753
+ var tileflowLayerSlots = [
1754
+ "background",
1755
+ "land",
1756
+ "terrain",
1757
+ "hydro",
1758
+ "building-areas",
1759
+ "transport-areas",
1760
+ "transport-tunnel-shadow",
1761
+ "transport-tunnel-casing",
1762
+ "transport-tunnel-fill",
1763
+ "transport-pedestrian-areas",
1764
+ "aeroways",
1765
+ "transport-surface-shadow",
1766
+ "transport-surface-casing",
1767
+ "transport-surface-fill",
1768
+ "transport-bridge-shadow",
1769
+ "transport-bridge-casing",
1770
+ "transport-bridge-fill",
1771
+ "transport-symbols",
1772
+ "boundaries",
1773
+ "buildings",
1774
+ "vegetation",
1775
+ "symbols"
1776
+ ];
1777
+ var tileflowLayerBands = [
1778
+ "land",
1779
+ "relief",
1780
+ "water",
1781
+ "roads",
1782
+ "boundaries",
1783
+ "buildings",
1784
+ "vegetation",
1785
+ "labels"
1786
+ ];
1787
+ var bandSlots = {
1788
+ land: ["background", "land"],
1789
+ relief: ["terrain", "terrain"],
1790
+ water: ["hydro", "hydro"],
1791
+ roads: ["transport-areas", "transport-symbols"],
1792
+ boundaries: ["boundaries", "boundaries"],
1793
+ buildings: ["buildings", "buildings"],
1794
+ vegetation: ["vegetation", "vegetation"],
1795
+ labels: ["symbols", "symbols"]
1796
+ };
1797
+ var tileflowBandPlacements = tileflowLayerBands.flatMap(
1798
+ (band) => [`below-${band}`, `above-${band}`]
1799
+ );
1800
+ function tileflowBandPlacementSlot(placement) {
1801
+ const [side, band] = splitBandPlacement(placement);
1802
+ return bandSlots[band][side === "below" ? 0 : 1];
1803
+ }
1804
+ function tileflowBandPlacementIndex(slotRanks, placement) {
1805
+ const [side, band] = splitBandPlacement(placement);
1806
+ const [first, last] = bandSlots[band];
1807
+ const threshold = side === "below" ? tileflowLayerSlots.indexOf(first) : tileflowLayerSlots.indexOf(last) + 1;
1808
+ const index = slotRanks.findIndex((rank) => rank !== void 0 && rank >= threshold);
1809
+ return index < 0 ? slotRanks.length : index;
1810
+ }
1811
+ function splitBandPlacement(placement) {
1812
+ const separator = placement.indexOf("-");
1813
+ return [
1814
+ placement.slice(0, separator),
1815
+ placement.slice(separator + 1)
1816
+ ];
1817
+ }
1818
+ var tileflowSemanticTargetPattern = /^[a-z][a-z0-9-]*(?:\.[A-Za-z0-9_-]+)*$/u;
1819
+ var tileflowRenderStackOperationNamePattern = /^[a-z][A-Za-z0-9_-]*$/u;
1820
+ var tileflowCompilerMetadataKeys = Object.freeze({
1821
+ owner: "tileflow:compiler-owner",
1822
+ slot: "tileflow:compiler-slot",
1823
+ target: "tileflow:compiler-target"
1824
+ });
1825
+
1826
+ // src/modules/addresses/index.ts
1827
+ function addresses(options = {}) {
1828
+ return { type: "addresses", ...cloneJson4(options) };
1829
+ }
1830
+ function cloneJson4(value) {
1831
+ return JSON.parse(JSON.stringify(value));
1832
+ }
1833
+
1834
+ // src/cartography/context.ts
1835
+ function typographyTextStyle(typography) {
1836
+ return {
1837
+ font: typography.font,
1838
+ ...typography.fallbacks ? { fallbacks: typography.fallbacks } : {},
1839
+ ...typography.letterSpacing === void 0 ? {} : { letterSpacing: typography.letterSpacing },
1840
+ ...typography.transform === void 0 ? {} : { transform: typography.transform }
1841
+ };
1842
+ }
1843
+
1605
1844
  // src/modules/addresses/compiler.ts
1606
1845
  function compileAddresses(request, context) {
1607
1846
  if (request?.enabled === false) return [];
@@ -2068,7 +2307,21 @@ function buildingColorExpression(context) {
2068
2307
  colors.destination,
2069
2308
  ["==", ["coalesce", ["get", fields.buildingTone], ""], "commercial"],
2070
2309
  colors.commercial,
2071
- colors.generic
2310
+ // Without a tone, World's use class: housing, shops and offices, public buildings and works
2311
+ // each take their theme colour; a shed, a garage or an unknown use the generic one.
2312
+ [
2313
+ "match",
2314
+ ["coalesce", ["get", fields.class], ""],
2315
+ "residential",
2316
+ colors.residential,
2317
+ "commercial",
2318
+ colors.commercial,
2319
+ ["civic", "education", "entertainment", "medical", "military", "religious", "transportation"],
2320
+ colors.civic,
2321
+ ["agricultural", "industrial", "service"],
2322
+ colors.industrial,
2323
+ colors.generic
2324
+ ]
2072
2325
  ];
2073
2326
  }
2074
2327
  function buildingVisibilityFilter() {
@@ -2332,6 +2585,9 @@ function cloneJson9(value) {
2332
2585
  function labelField(language, context) {
2333
2586
  return expression(labelFieldExpression(language, context));
2334
2587
  }
2588
+ function labelNameFields(language, context) {
2589
+ return labelFieldExpression(language, context).slice(1).map((entry) => entry[1]);
2590
+ }
2335
2591
  function labelFieldExpression(language, context) {
2336
2592
  const fields = context.data.schema.fields;
2337
2593
  if (language === "local") {
@@ -3189,12 +3445,12 @@ function compileLand(request, context) {
3189
3445
  if (name === "recreation") continue;
3190
3446
  appendLanduse(name, classes);
3191
3447
  }
3192
- for (const [name, match2] of landcoverClasses) {
3448
+ for (const [name, match3] of landcoverClasses) {
3193
3449
  const style = config.landcover?.[name];
3194
3450
  if (!style) continue;
3195
3451
  const protectedSource = name === "protected";
3196
3452
  const sourceLayer = protectedSource ? schema.layers.park : schema.layers.landcover;
3197
- const filter = protectedSource && schema.semantics.parkLayer === "protected-only" ? void 0 : landcoverFilter(classField, subclassField, match2);
3453
+ const filter = protectedSource && schema.semantics.parkLayer === "protected-only" ? void 0 : landcoverFilter(classField, subclassField, match3);
3198
3454
  for (const area of createAreaLayers(
3199
3455
  {
3200
3456
  id: `tileflow-landcover-${name}`,
@@ -3250,11 +3506,11 @@ function compileLand(request, context) {
3250
3506
  function classFilter2(field2, classes) {
3251
3507
  return ["match", ["get", field2], classes, true, false];
3252
3508
  }
3253
- function landcoverFilter(classField, subclassField, match2) {
3254
- const filter = classFilter2(classField, match2.class);
3255
- if (match2.subclass) return ["all", filter, classFilter2(subclassField, match2.subclass)];
3256
- if (match2.excludeSubclass) {
3257
- return ["all", filter, ["!", classFilter2(subclassField, match2.excludeSubclass)]];
3509
+ function landcoverFilter(classField, subclassField, match3) {
3510
+ const filter = classFilter2(classField, match3.class);
3511
+ if (match3.subclass) return ["all", filter, classFilter2(subclassField, match3.subclass)];
3512
+ if (match3.excludeSubclass) {
3513
+ return ["all", filter, ["!", classFilter2(subclassField, match3.excludeSubclass)]];
3258
3514
  }
3259
3515
  return filter;
3260
3516
  }
@@ -3963,149 +4219,557 @@ var tileflowPoiImageRoles = Object.freeze({
3963
4219
  transport: { fallback: "major-transit", token: "poi.transport" },
3964
4220
  "visitor-amenity": { fallback: "services", token: "poi.visitor-amenity" }
3965
4221
  });
4222
+ var tileflowPoiIconOnlyTypes = Object.freeze([
4223
+ "parking",
4224
+ "parking_garage",
4225
+ "parking_lot",
4226
+ "public_restroom"
4227
+ ]);
3966
4228
  function poi(options = {}) {
3967
4229
  return { type: "poi", ...cloneJson13(options) };
3968
4230
  }
3969
- function resolvePoi(request) {
3970
- return {
3971
- categories: [...new Set(request?.categories ?? tileflowPoiCategories)],
3972
- color: request?.color ?? "uniform",
3973
- density: request?.density ?? 3,
3974
- icons: request?.icons ?? true,
3975
- labels: request?.labels ?? true,
3976
- minZoom: request?.minZoom ?? 0,
3977
- placement: {
3978
- coupleIconAndLabel: request?.placement?.coupleIconAndLabel ?? false,
3979
- iconPadding: request?.placement?.iconPadding ?? 2,
3980
- textPadding: request?.placement?.textPadding ?? 2
4231
+ function resolvePoi(request) {
4232
+ return {
4233
+ categories: [...new Set(request?.categories ?? tileflowPoiCategories)],
4234
+ color: request?.color ?? "uniform",
4235
+ density: request?.density ?? 3,
4236
+ icons: request?.icons ?? true,
4237
+ labels: request?.labels ?? true,
4238
+ minZoom: request?.minZoom ?? 0,
4239
+ placement: {
4240
+ coupleIconAndLabel: request?.placement?.coupleIconAndLabel ?? false,
4241
+ iconOnlyTypes: [...request?.placement?.iconOnlyTypes ?? tileflowPoiIconOnlyTypes],
4242
+ iconPadding: request?.placement?.iconPadding ?? 2,
4243
+ order: request?.placement?.order ?? "importance",
4244
+ ...request?.placement?.prominentRank === void 0 ? {} : { prominentRank: request.placement.prominentRank },
4245
+ textPadding: request?.placement?.textPadding ?? 2
4246
+ }
4247
+ };
4248
+ }
4249
+ function cloneJson13(value) {
4250
+ return JSON.parse(JSON.stringify(value));
4251
+ }
4252
+
4253
+ // src/modules/poi/placement.ts
4254
+ var tileflowPoiFeatureVaryingProperties = Object.freeze({
4255
+ circle: Object.freeze({
4256
+ "circle-blur": 0,
4257
+ "circle-color": "#000000",
4258
+ "circle-opacity": 1,
4259
+ "circle-radius": 5,
4260
+ "circle-sort-key": void 0,
4261
+ "circle-stroke-color": "#000000",
4262
+ "circle-stroke-opacity": 1,
4263
+ "circle-stroke-width": 0
4264
+ }),
4265
+ symbol: Object.freeze({
4266
+ "icon-anchor": "center",
4267
+ "icon-color": "#000000",
4268
+ "icon-halo-blur": 0,
4269
+ "icon-halo-color": "rgba(0, 0, 0, 0)",
4270
+ "icon-halo-width": 0,
4271
+ // An empty image draws no icon, so a category without one stays text-only.
4272
+ "icon-image": "",
4273
+ "icon-offset": [0, 0],
4274
+ "icon-opacity": 1,
4275
+ "icon-padding": [2],
4276
+ "icon-rotate": 0,
4277
+ "icon-size": 1,
4278
+ "symbol-sort-key": void 0,
4279
+ "text-anchor": "center",
4280
+ "text-color": "#000000",
4281
+ "text-field": "",
4282
+ // MapLibre can vary `text-font` per feature, but Tileflow prepares glyphs for a static font
4283
+ // stack, so the font stays a layer-wide property.
4284
+ "text-halo-blur": 0,
4285
+ "text-halo-color": "rgba(0, 0, 0, 0)",
4286
+ "text-halo-width": 0,
4287
+ "text-justify": "center",
4288
+ "text-letter-spacing": 0,
4289
+ "text-max-width": 10,
4290
+ "text-offset": [0, 0],
4291
+ "text-opacity": 1,
4292
+ "text-radial-offset": 0,
4293
+ "text-rotate": 0,
4294
+ "text-size": 16,
4295
+ "text-transform": "none",
4296
+ "text-variable-anchor-offset": void 0
4297
+ })
4298
+ });
4299
+ var tileflowPoiLayerPropertyDefaults = Object.freeze({
4300
+ "circle-pitch-alignment": "viewport",
4301
+ "circle-pitch-scale": "map",
4302
+ "circle-translate": [0, 0],
4303
+ "circle-translate-anchor": "map",
4304
+ "icon-allow-overlap": false,
4305
+ "icon-ignore-placement": false,
4306
+ "icon-keep-upright": false,
4307
+ "icon-optional": false,
4308
+ "icon-pitch-alignment": "auto",
4309
+ "icon-rotation-alignment": "auto",
4310
+ "icon-text-fit": "none",
4311
+ "icon-text-fit-padding": [0, 0, 0, 0],
4312
+ "symbol-avoid-edges": false,
4313
+ "symbol-placement": "point",
4314
+ "symbol-spacing": 250,
4315
+ "symbol-z-order": "auto",
4316
+ "text-allow-overlap": false,
4317
+ "text-ignore-placement": false,
4318
+ "text-keep-upright": true,
4319
+ "text-line-height": 1.2,
4320
+ "text-max-angle": 45,
4321
+ "text-optional": false,
4322
+ "text-padding": 2,
4323
+ "text-pitch-alignment": "auto",
4324
+ "text-rotation-alignment": "auto",
4325
+ visibility: "visible"
4326
+ });
4327
+ var TileflowPoiPlacementError = class extends Error {
4328
+ code = "TILEFLOW_POI_PLACEMENT_CONFLICT";
4329
+ constructor(message) {
4330
+ super(message);
4331
+ this.name = "TileflowPoiPlacementError";
4332
+ }
4333
+ };
4334
+ function mergePoiCategoryLayers(entries, options) {
4335
+ const [first] = entries;
4336
+ if (!first) throw new TileflowPoiPlacementError("A POI placement layer needs a category.");
4337
+ const kind = first.layer.type === "circle" ? "circle" : "symbol";
4338
+ const varying = tileflowPoiFeatureVaryingProperties[kind];
4339
+ const categories = entries.map(({ category }) => category);
4340
+ for (const property of ["type", "source", "source-layer", "minzoom", "maxzoom"]) {
4341
+ requireShared(entries, property, (layer) => layer[property]);
4342
+ }
4343
+ const result = {
4344
+ ...first.layer,
4345
+ id: options.id,
4346
+ filter: options.filter
4347
+ };
4348
+ for (const part of ["layout", "paint"]) {
4349
+ const records = entries.map(({ layer }) => asRecord2(layer[part]));
4350
+ const properties2 = [...new Set(records.flatMap((record3) => Object.keys(record3)))].sort();
4351
+ const merged = {};
4352
+ for (const property of properties2) {
4353
+ const fallback = Object.hasOwn(varying, property) ? varying[property] : tileflowPoiLayerPropertyDefaults[property];
4354
+ const values = records.map(
4355
+ (record3) => Object.hasOwn(record3, property) ? record3[property] : fallback
4356
+ );
4357
+ if (values.every((value) => sameJson(value, values[0]))) {
4358
+ if (values[0] !== void 0) merged[property] = values[0];
4359
+ continue;
4360
+ }
4361
+ if (!Object.hasOwn(varying, property)) {
4362
+ throw conflict(entries, property, (layer) => asRecord2(layer[part])[property]);
4363
+ }
4364
+ if (values.some((value) => value === void 0)) {
4365
+ throw new TileflowPoiPlacementError(
4366
+ `POI categories ${categories.join(", ")} set ${property} for only some categories, and MapLibre has no default to fill in for the rest. Set it for every category.`
4367
+ );
4368
+ }
4369
+ merged[property] = matchByCategory(
4370
+ property,
4371
+ categories.map((category, index) => [category, values[index]]),
4372
+ ["get", options.categoryField]
4373
+ );
4374
+ }
4375
+ if (Object.keys(merged).length) result[part] = merged;
4376
+ else delete result[part];
4377
+ }
4378
+ return result;
4379
+ }
4380
+ function requireShared(entries, property, read) {
4381
+ const value = read(entries[0].layer);
4382
+ if (entries.every(({ layer }) => sameJson(read(layer), value))) return;
4383
+ throw conflict(entries, property, read);
4384
+ }
4385
+ function conflict(entries, property, read) {
4386
+ const described = entries.map(({ category, layer }) => `${category}: ${JSON.stringify(read(layer)) ?? "unset"}`).join("; ");
4387
+ return new TileflowPoiPlacementError(
4388
+ `POI categories need different ${property} values (${described}), but MapLibre keeps ${property} constant within one layer. Give them the same ${property}, or set placement.order to 'categories' to place each category in its own layer.`
4389
+ );
4390
+ }
4391
+ function matchByCategory(property, values, input) {
4392
+ const curves = values.map(([, value]) => zoomCurve(value));
4393
+ const present = curves.filter((curve) => curve !== void 0);
4394
+ if (present.length === 0) return match(input, values);
4395
+ const reference = present[0];
4396
+ for (const curve of present) {
4397
+ if (curve.kind !== reference.kind || curve.kind === "interpolate" && reference.kind === "interpolate" && (curve.operator !== reference.operator || !sameJson(curve.interpolation, reference.interpolation))) {
4398
+ throw new TileflowPoiPlacementError(
4399
+ `POI categories use different kinds of zoom curve for ${property}. Use one kind of curve (linear, the same exponential base, or step) for every category, or set placement.order to 'categories'.`
4400
+ );
3981
4401
  }
3982
- };
4402
+ }
4403
+ const stops = [...new Set(present.flatMap((curve) => curve.stops.map(([zoom3]) => zoom3)))].sort(
4404
+ (left, right) => left - right
4405
+ );
4406
+ const at = (zoom3) => match(
4407
+ input,
4408
+ values.map(([category, value], index) => [
4409
+ category,
4410
+ curves[index] ? evaluateCurve(property, curves[index], zoom3) : value
4411
+ ])
4412
+ );
4413
+ if (reference.kind === "step") {
4414
+ const base = match(
4415
+ input,
4416
+ values.map(([category, value], index) => {
4417
+ const curve = curves[index];
4418
+ return [category, curve?.kind === "step" ? curve.base : value];
4419
+ })
4420
+ );
4421
+ return ["step", ["zoom"], base, ...stops.flatMap((zoom3) => [zoom3, at(zoom3)])];
4422
+ }
4423
+ return [
4424
+ reference.operator,
4425
+ reference.interpolation,
4426
+ ["zoom"],
4427
+ ...stops.flatMap((zoom3) => [zoom3, at(zoom3)])
4428
+ ];
3983
4429
  }
3984
- function cloneJson13(value) {
3985
- return JSON.parse(JSON.stringify(value));
4430
+ function match(input, values) {
4431
+ const groups = [];
4432
+ for (const [category, value] of values) {
4433
+ const group = groups.find((candidate) => sameJson(candidate.value, value));
4434
+ if (group) group.categories.push(category);
4435
+ else groups.push({ categories: [category], value });
4436
+ }
4437
+ if (groups.length === 1) return groups[0].value;
4438
+ const fallback = groups.reduce(
4439
+ (largest, group) => group.categories.length > largest.categories.length ? group : largest
4440
+ );
4441
+ return [
4442
+ "match",
4443
+ input,
4444
+ ...groups.filter((group) => group !== fallback).flatMap((group) => [
4445
+ group.categories.length === 1 ? group.categories[0] : group.categories,
4446
+ expressionValue(group.value)
4447
+ ]),
4448
+ expressionValue(fallback.value)
4449
+ ];
4450
+ }
4451
+ function zoomCurve(value) {
4452
+ if (!Array.isArray(value)) return void 0;
4453
+ const [operator] = value;
4454
+ if ((operator === "interpolate" || operator === "interpolate-hcl" || operator === "interpolate-lab") && isZoomInput3(value[2])) {
4455
+ return {
4456
+ kind: "interpolate",
4457
+ operator,
4458
+ interpolation: value[1],
4459
+ stops: pairs(value.slice(3))
4460
+ };
4461
+ }
4462
+ if (operator === "step" && isZoomInput3(value[1])) {
4463
+ return { kind: "step", base: value[2], stops: pairs(value.slice(3)) };
4464
+ }
4465
+ return void 0;
4466
+ }
4467
+ function evaluateCurve(property, curve, zoom3) {
4468
+ const { stops } = curve;
4469
+ if (curve.kind === "step") {
4470
+ let value2 = curve.base;
4471
+ for (const [stop, output] of stops) if (stop <= zoom3) value2 = output;
4472
+ return value2;
4473
+ }
4474
+ const exact = stops.find(([stop]) => stop === zoom3);
4475
+ if (exact) return exact[1];
4476
+ if (zoom3 < stops[0][0]) return stops[0][1];
4477
+ if (zoom3 > stops.at(-1)[0]) return stops.at(-1)[1];
4478
+ const upperIndex = stops.findIndex(([stop]) => stop > zoom3);
4479
+ const [lowerZoom, lower] = stops[upperIndex - 1];
4480
+ const [upperZoom, upper] = stops[upperIndex];
4481
+ const t = interpolationFactor(curve.interpolation, zoom3, lowerZoom, upperZoom);
4482
+ const value = interpolateOutput(lower, upper, t);
4483
+ if (value === void 0) {
4484
+ throw new TileflowPoiPlacementError(
4485
+ `POI categories interpolate ${property} between values Tileflow cannot evaluate at zoom ${zoom3}. Give every category the same zoom stops for ${property}, or set placement.order to 'categories'.`
4486
+ );
4487
+ }
4488
+ return value;
4489
+ }
4490
+ function interpolationFactor(interpolation, zoom3, lower, upper) {
4491
+ const progress = zoom3 - lower;
4492
+ const range2 = upper - lower;
4493
+ if (Array.isArray(interpolation) && interpolation[0] === "exponential") {
4494
+ const base = Number(interpolation[1]);
4495
+ if (base !== 1) return (base ** progress - 1) / (base ** range2 - 1);
4496
+ }
4497
+ return progress / range2;
4498
+ }
4499
+ function interpolateOutput(lower, upper, t) {
4500
+ if (typeof lower === "number" && typeof upper === "number") {
4501
+ return roundOutput(lower + (upper - lower) * t);
4502
+ }
4503
+ const lowerArray = numericArray(lower);
4504
+ const upperArray = numericArray(upper);
4505
+ if (lowerArray && upperArray && lowerArray.length === upperArray.length) {
4506
+ return [
4507
+ "literal",
4508
+ lowerArray.map((value, index) => roundOutput(value + (upperArray[index] - value) * t))
4509
+ ];
4510
+ }
4511
+ if (isNumericOutput(lower) && isNumericOutput(upper)) {
4512
+ return ["+", lower, ["*", roundOutput(t), ["-", upper, lower]]];
4513
+ }
4514
+ return void 0;
4515
+ }
4516
+ function isNumericOutput(value) {
4517
+ if (typeof value === "number") return true;
4518
+ if (!Array.isArray(value) || typeof value[0] !== "string") return false;
4519
+ return isMapLibreExpressionOperator(value[0]) && !["literal", "array"].includes(value[0]);
4520
+ }
4521
+ function numericArray(value) {
4522
+ const array = Array.isArray(value) && value[0] === "literal" && Array.isArray(value[1]) ? value[1] : value;
4523
+ return Array.isArray(array) && array.every((entry) => typeof entry === "number") ? array : void 0;
4524
+ }
4525
+ function roundOutput(value) {
4526
+ return Math.round(value * 1e6) / 1e6;
4527
+ }
4528
+ function expressionValue(value) {
4529
+ if (!Array.isArray(value)) return value;
4530
+ if (typeof value[0] === "string" && isMapLibreExpressionOperator(value[0])) return value;
4531
+ return ["literal", value];
4532
+ }
4533
+ function pairs(values) {
4534
+ const result = [];
4535
+ for (let index = 0; index + 1 < values.length; index += 2) {
4536
+ result.push([values[index], values[index + 1]]);
4537
+ }
4538
+ return result;
4539
+ }
4540
+ function isZoomInput3(value) {
4541
+ return Array.isArray(value) && value.length === 1 && value[0] === "zoom";
4542
+ }
4543
+ function sameJson(left, right) {
4544
+ return JSON.stringify(left) === JSON.stringify(right);
4545
+ }
4546
+ function asRecord2(value) {
4547
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
3986
4548
  }
3987
4549
 
3988
4550
  // src/modules/poi/compiler.ts
4551
+ var representationOrder = {
4552
+ marker: 0,
4553
+ combined: 1,
4554
+ icon: 1,
4555
+ label: 2
4556
+ };
3989
4557
  function compilePoi(request, context, language = "auto") {
3990
4558
  if (request?.enabled === false) return [];
3991
4559
  const semantics = resolvePoi(request);
3992
- const result = [];
4560
+ const selection2 = createPoiSelection(semantics, context, language);
4561
+ const parts = semantics.categories.flatMap(
4562
+ (category) => compileCategory(category, request, semantics, context, language, selection2)
4563
+ );
4564
+ if (semantics.placement.order === "categories") {
4565
+ return parts.map((part) => {
4566
+ const rank = semantics.categories.length - 1 - semantics.categories.indexOf(part.category);
4567
+ const suffix = part.representation === "combined" ? "" : `.${part.representation}`;
4568
+ return poiContribution(
4569
+ `${part.category}${suffix}`,
4570
+ rank * 4 + representationOrder[part.representation],
4571
+ {
4572
+ ...part.layer,
4573
+ id: `tileflow-poi-${part.category}${suffix.replace(".", "-")}`,
4574
+ filter: selection2.filter([part.category], part.representation)
4575
+ }
4576
+ );
4577
+ });
4578
+ }
4579
+ const { prominentRank } = semantics.placement;
4580
+ const filterRank = numericPoiField(context.data.schema.fields.poiFilterRank, 6);
4581
+ return ["marker", "combined", "icon", "label"].flatMap((representation) => {
4582
+ const entries = parts.filter((part) => part.representation === representation);
4583
+ if (entries.length === 0) return [];
4584
+ const suffix = representation === "combined" ? "" : `.${representation}`;
4585
+ const filter = selection2.filter(
4586
+ entries.map(({ category }) => category),
4587
+ representation
4588
+ );
4589
+ const layer = mergePoiCategoryLayers(entries, {
4590
+ categoryField: context.data.schema.fields.poiCategory,
4591
+ filter,
4592
+ id: `tileflow-poi${suffix.replace(".", "-")}`
4593
+ });
4594
+ if (prominentRank === void 0 || representation === "marker") {
4595
+ return [poiContribution(suffix.slice(1), representationOrder[representation], layer)];
4596
+ }
4597
+ return [
4598
+ poiContribution(suffix.slice(1), representationOrder[representation], {
4599
+ ...layer,
4600
+ filter: [...filter, [">", filterRank, prominentRank]]
4601
+ }),
4602
+ poiContribution(`prominent${suffix}`, 400 + representationOrder[representation], {
4603
+ ...layer,
4604
+ id: `tileflow-poi-prominent${suffix.replace(".", "-")}`,
4605
+ filter: [...filter, ["<=", filterRank, prominentRank]]
4606
+ })
4607
+ ];
4608
+ });
4609
+ }
4610
+ function compileCategory(category, request, semantics, context, language, selection2) {
3993
4611
  const { sourceId: source, schema } = context.data;
3994
- for (const [index, category] of semantics.categories.entries()) {
3995
- const requestedCategoryStyle = request?.styles?.[category];
3996
- const categoryDensity = requestedCategoryStyle?.density ?? semantics.density;
3997
- const { density: _density, ...categoryStyle } = requestedCategoryStyle ?? {};
3998
- const categoryColor = semantics.color === "category" ? context.colors.poi[category] ?? context.colors.poi.label : context.colors.poi.label;
3999
- const defaults4 = {
4612
+ const coupled = semantics.placement.coupleIconAndLabel;
4613
+ const categoryColor = semantics.color === "category" ? context.colors.poi[category] ?? context.colors.poi.label : context.colors.poi.label;
4614
+ const defaults4 = {
4615
+ icon: {
4616
+ optional: !coupled,
4617
+ padding: semantics.placement.iconPadding,
4618
+ size: semantics.minZoom < 17 ? zoom.linear([
4619
+ [semantics.minZoom, 0.85],
4620
+ [17, 1]
4621
+ ]) : 1
4622
+ },
4623
+ text: poiTextStyle(context, {
4624
+ color: categoryColor,
4625
+ field: labelField(language, context),
4626
+ // A coupled POI is its pictogram and its name: it is placed only when both fit.
4627
+ optional: !coupled,
4628
+ padding: semantics.placement.textPadding,
4629
+ size: semantics.minZoom < 17 ? zoom.linear([
4630
+ [semantics.minZoom, 10],
4631
+ [17, 12]
4632
+ ]) : 12
4633
+ })
4634
+ };
4635
+ let style = mergeTileflowDesign(defaults4, request?.styles?.[category] ?? {});
4636
+ const markerStyle = style.marker ? resolveMarkerStyle(style) : void 0;
4637
+ const hidden = style.visible === false;
4638
+ const showText = !hidden && semantics.labels && style.text.visible !== false;
4639
+ const showIcon = !hidden && semantics.icons && style.icon.visible !== false;
4640
+ style = {
4641
+ ...withoutVisibility(style),
4642
+ icon: withoutVisibility(style.icon),
4643
+ text: withoutVisibility(style.text)
4644
+ };
4645
+ if (showIcon && style.icon.image === void 0) {
4646
+ style = {
4647
+ ...style,
4000
4648
  icon: {
4001
- optional: !semantics.placement.coupleIconAndLabel,
4002
- padding: semantics.placement.iconPadding,
4003
- size: semantics.minZoom < 17 ? zoom.linear([
4004
- [semantics.minZoom, 0.85],
4005
- [17, 1]
4006
- ]) : 1
4007
- },
4008
- text: poiTextStyle(context, {
4009
- color: categoryColor,
4010
- field: labelField(language, context),
4011
- padding: semantics.placement.textPadding,
4012
- size: semantics.minZoom < 17 ? zoom.linear([
4013
- [semantics.minZoom, 10],
4014
- [17, 12]
4015
- ]) : 12
4016
- })
4649
+ ...style.icon,
4650
+ image: resolvePoiImage(category, context, schema.fields.poiIcon)
4651
+ }
4017
4652
  };
4018
- let style = mergeTileflowDesign(defaults4, categoryStyle);
4019
- const markerStyle = style.marker ? resolveMarkerStyle(style) : void 0;
4020
- const showText = semantics.labels && style.text.visible !== false;
4021
- const showIcon = semantics.icons && style.icon.visible !== false;
4022
- if (showIcon && style.icon.image === void 0) {
4023
- style = {
4024
- ...style,
4025
- icon: {
4026
- ...style.icon,
4027
- image: resolvePoiImage(category, context, schema.fields.poiIcon)
4028
- }
4029
- };
4030
- }
4031
- style = withPoiTextClearance(style, showText && showIcon);
4032
- const symbolStyle2 = withoutMarker2(style);
4033
- if (markerStyle && markerStyle.visible !== false) {
4034
- const base = createPoiLayer({
4035
- category,
4036
- categoryField: schema.fields.poiCategory,
4037
- density: categoryDensity,
4038
- filterRankField: schema.fields.poiFilterRank,
4039
- id: `tileflow-poi-${category}-marker`,
4040
- minZoom: semantics.minZoom,
4041
- sizeRankField: schema.fields.poiSizeRank,
4042
- source,
4043
- sourceLayer: schema.layers.poi
4044
- });
4045
- const markerLayer = applyCircleStyle(
4653
+ }
4654
+ style = withPoiTextClearance(style, showText && showIcon);
4655
+ const symbolStyle2 = withoutMarker2(style);
4656
+ const base = () => ({
4657
+ id: `tileflow-poi-${category}`,
4658
+ type: "symbol",
4659
+ source,
4660
+ "source-layer": schema.layers.poi,
4661
+ minzoom: semantics.minZoom,
4662
+ layout: { "symbol-sort-key": poiSortKey(schema.fields.poiFilterRank, schema.fields.poiSizeRank) }
4663
+ });
4664
+ const parts = [];
4665
+ selection2.register(category, { icon: showIcon, text: showText });
4666
+ if (!hidden && markerStyle && markerStyle.visible !== false) {
4667
+ const markerBase = base();
4668
+ parts.push({
4669
+ category,
4670
+ representation: "marker",
4671
+ layer: applyCircleStyle(
4046
4672
  {
4047
- ...base,
4673
+ ...markerBase,
4048
4674
  type: "circle",
4049
4675
  layout: {
4050
4676
  "circle-sort-key": poiSortKey(schema.fields.poiFilterRank, schema.fields.poiSizeRank)
4051
4677
  }
4052
4678
  },
4053
4679
  markerStyle
4054
- );
4055
- result.push(poiContribution(`${category}.marker`, index * 4, markerLayer));
4056
- }
4057
- if (semantics.placement.coupleIconAndLabel && showText && showIcon) {
4058
- let layer = createPoiLayer({
4059
- category,
4060
- categoryField: schema.fields.poiCategory,
4061
- density: categoryDensity,
4062
- filterRankField: schema.fields.poiFilterRank,
4063
- id: `tileflow-poi-${category}`,
4064
- minZoom: semantics.minZoom,
4065
- sizeRankField: schema.fields.poiSizeRank,
4066
- source,
4067
- sourceLayer: schema.layers.poi
4068
- });
4069
- layer = applySymbolStyle(layer, symbolStyle2);
4070
- result.push(poiContribution(category, index * 4 + 1, layer));
4071
- continue;
4072
- }
4073
- if (showIcon) {
4074
- const layer = applySymbolStyle(
4075
- createPoiLayer({
4076
- category,
4077
- categoryField: schema.fields.poiCategory,
4078
- density: categoryDensity,
4079
- filterRankField: schema.fields.poiFilterRank,
4080
- id: `tileflow-poi-${category}-icon`,
4081
- minZoom: semantics.minZoom,
4082
- sizeRankField: schema.fields.poiSizeRank,
4083
- source,
4084
- sourceLayer: schema.layers.poi
4085
- }),
4086
- { ...symbolStyle2, text: void 0 }
4087
- );
4088
- result.push(poiContribution(`${category}.icon`, index * 4 + 1, layer));
4089
- }
4090
- if (showText) {
4091
- const layer = applySymbolStyle(
4092
- createPoiLayer({
4093
- category,
4094
- categoryField: schema.fields.poiCategory,
4095
- density: categoryDensity,
4096
- filterRankField: schema.fields.poiFilterRank,
4097
- id: `tileflow-poi-${category}-label`,
4098
- minZoom: semantics.minZoom,
4099
- sizeRankField: schema.fields.poiSizeRank,
4100
- source,
4101
- sourceLayer: schema.layers.poi
4102
- }),
4103
- { ...symbolStyle2, icon: void 0 }
4104
- );
4105
- result.push(poiContribution(`${category}.label`, index * 4 + 2, layer));
4106
- }
4680
+ )
4681
+ });
4107
4682
  }
4108
- return result;
4683
+ const combined = coupled && (semantics.placement.order === "importance" ? semantics.icons && semantics.labels && (showIcon || showText) : showIcon && showText);
4684
+ if (combined) {
4685
+ const combinedStyle = {
4686
+ ...symbolStyle2,
4687
+ icon: showIcon ? symbolStyle2.icon : { image: "" },
4688
+ text: showText ? { ...symbolStyle2.text, field: selection2.textField(symbolStyle2.text.field) } : { field: "" }
4689
+ };
4690
+ parts.push({
4691
+ category,
4692
+ representation: "combined",
4693
+ layer: applySymbolStyle(base(), combinedStyle)
4694
+ });
4695
+ return parts;
4696
+ }
4697
+ if (showIcon) {
4698
+ parts.push({
4699
+ category,
4700
+ representation: "icon",
4701
+ layer: applySymbolStyle(base(), { ...symbolStyle2, text: void 0 })
4702
+ });
4703
+ }
4704
+ if (showText) {
4705
+ parts.push({
4706
+ category,
4707
+ representation: "label",
4708
+ layer: applySymbolStyle(base(), { ...symbolStyle2, icon: void 0 })
4709
+ });
4710
+ }
4711
+ return parts;
4712
+ }
4713
+ function createPoiSelection(semantics, context, language) {
4714
+ const { fields } = context.data.schema;
4715
+ const shown = /* @__PURE__ */ new Map();
4716
+ const iconOnlyTypes = [...new Set(semantics.placement.iconOnlyTypes)];
4717
+ const iconOnly = () => iconOnlyTypes.length ? ["match", ["get", fields.poiType], iconOnlyTypes, true, false] : ["literal", false];
4718
+ const inCategories = (categories) => categories.length === 1 ? ["==", ["get", fields.poiCategory], categories[0]] : ["match", ["get", fields.poiCategory], [...categories], true, false];
4719
+ const named = () => [
4720
+ "any",
4721
+ ...labelNameFields(language, context).map((field2) => ["has", field2])
4722
+ ];
4723
+ return {
4724
+ register(category, parts) {
4725
+ shown.set(category, parts);
4726
+ },
4727
+ textField(field2) {
4728
+ if (!iconOnlyTypes.length || field2 === void 0) return field2;
4729
+ return expression([
4730
+ "match",
4731
+ ["get", fields.poiType],
4732
+ iconOnlyTypes,
4733
+ "",
4734
+ fieldValue(field2)
4735
+ ]);
4736
+ },
4737
+ filter(categories, representation) {
4738
+ const filterRank = numericPoiField(fields.poiFilterRank, 6);
4739
+ const sizeRank = numericPoiField(fields.poiSizeRank, 17);
4740
+ const result = [
4741
+ "all",
4742
+ inCategories(categories),
4743
+ ["has", fields.poiFilterRank],
4744
+ [">=", filterRank, 0],
4745
+ ["<=", filterRank, poiRankThreshold(semantics.density)],
4746
+ ["has", fields.poiSizeRank],
4747
+ [">=", sizeRank, 0],
4748
+ ["<=", sizeRank, 16]
4749
+ ];
4750
+ if (!semantics.icons || !semantics.labels) return result;
4751
+ const textless = categories.filter((category) => !shown.get(category)?.text);
4752
+ const iconless = categories.filter((category) => !shown.get(category)?.icon);
4753
+ if (representation === "label") {
4754
+ if (iconless.length === categories.length || !iconOnlyTypes.length) return result;
4755
+ const keep = ["!", iconOnly()];
4756
+ return [...result, iconless.length ? ["any", inCategories(iconless), keep] : keep];
4757
+ }
4758
+ if (textless.length === categories.length) return result;
4759
+ return [
4760
+ ...result,
4761
+ [
4762
+ "any",
4763
+ named(),
4764
+ ...iconOnlyTypes.length ? [iconOnly()] : [],
4765
+ ...textless.length ? [inCategories(textless)] : []
4766
+ ]
4767
+ ];
4768
+ }
4769
+ };
4770
+ }
4771
+ function fieldValue(field2) {
4772
+ return typeof field2 === "object" && field2 !== null && "kind" in field2 && field2.kind === "expression" ? field2.value : field2;
4109
4773
  }
4110
4774
  function resolvePoiImage(category, context, iconField) {
4111
4775
  const role = `poi.${category}`;
@@ -4128,28 +4792,6 @@ function resolveMarkerStyle(style) {
4128
4792
  style.marker
4129
4793
  );
4130
4794
  }
4131
- function createPoiLayer(options) {
4132
- const filterRank = numericPoiField(options.filterRankField, 6);
4133
- const sizeRank = numericPoiField(options.sizeRankField, 17);
4134
- return {
4135
- id: options.id,
4136
- type: "symbol",
4137
- source: options.source,
4138
- "source-layer": options.sourceLayer,
4139
- minzoom: options.minZoom,
4140
- filter: [
4141
- "all",
4142
- ["==", ["get", options.categoryField], options.category],
4143
- ["has", options.filterRankField],
4144
- [">=", filterRank, 0],
4145
- ["<=", filterRank, poiRankThreshold(options.density)],
4146
- ["has", options.sizeRankField],
4147
- [">=", sizeRank, 0],
4148
- ["<=", sizeRank, 16]
4149
- ],
4150
- layout: { "symbol-sort-key": poiSortKey(options.filterRankField, options.sizeRankField) }
4151
- };
4152
- }
4153
4795
  function poiRankThreshold(density) {
4154
4796
  return ["+", ["step", ["zoom"], 0, 16, 1, 17, 2], density];
4155
4797
  }
@@ -4166,7 +4808,7 @@ function poiContribution(target, localOrder, layer) {
4166
4808
  localOrder: 1e3 + localOrder,
4167
4809
  owner: "poi",
4168
4810
  slot: "symbols",
4169
- target: `poi.${target}`
4811
+ target: target ? `poi.${target}` : "poi"
4170
4812
  };
4171
4813
  }
4172
4814
  function poiTextStyle(context, overrides) {
@@ -4176,8 +4818,7 @@ function poiTextStyle(context, overrides) {
4176
4818
  color: context.colors.poi.label,
4177
4819
  ...typographyTextStyle(context.typography.poi),
4178
4820
  haloColor: context.colors.poi.halo,
4179
- haloWidth: 1,
4180
- optional: true
4821
+ haloWidth: 1
4181
4822
  },
4182
4823
  overrides
4183
4824
  );
@@ -4200,6 +4841,10 @@ function withoutMarker2(style) {
4200
4841
  delete result.marker;
4201
4842
  return result;
4202
4843
  }
4844
+ function withoutVisibility(value) {
4845
+ const { visible: _visible, ...rest } = value;
4846
+ return rest;
4847
+ }
4203
4848
 
4204
4849
  // src/portable-identity-rules.ts
4205
4850
  var tileflowPortableIdMaximumLength = 64;
@@ -4211,6 +4856,56 @@ function isTileflowThemeName(value) {
4211
4856
  return isTileflowPortableId(value) && value !== "system";
4212
4857
  }
4213
4858
 
4859
+ // src/time-of-day.ts
4860
+ var tileflowTimesOfDay = ["day", "dawn", "dusk", "night"];
4861
+ function isTileflowTimeOfDay(value) {
4862
+ return typeof value === "string" && tileflowTimesOfDay.includes(value);
4863
+ }
4864
+ function validateTileflowTimeOfDayVariants(variants, themes) {
4865
+ const issues = [];
4866
+ const owners = /* @__PURE__ */ new Map();
4867
+ const families = Object.entries(variants);
4868
+ for (const [family, moments] of families) {
4869
+ if (!isTileflowThemeName(family) || !Object.hasOwn(themes, family)) {
4870
+ issues.push({ path: [family], message: "Time-of-day family must name an existing theme" });
4871
+ }
4872
+ if (!Object.values(moments).includes(family)) {
4873
+ issues.push({ path: [family], message: "A family must include its own theme in its presets" });
4874
+ }
4875
+ for (const [moment, name] of Object.entries(moments)) {
4876
+ if (name === void 0) continue;
4877
+ if (!isTileflowTimeOfDay(moment) || !isTileflowThemeName(name) || !Object.hasOwn(themes, name)) {
4878
+ issues.push({
4879
+ path: [family, moment],
4880
+ message: "Expected a supported moment and an existing theme"
4881
+ });
4882
+ }
4883
+ const owner = owners.get(name);
4884
+ if (owner !== void 0 && owner !== family) {
4885
+ issues.push({
4886
+ path: [family, moment],
4887
+ message: `Theme "${name}" already belongs to family "${owner}"`
4888
+ });
4889
+ }
4890
+ owners.set(name, family);
4891
+ }
4892
+ }
4893
+ return issues;
4894
+ }
4895
+ function resolveTileflowTimeOfDayTheme(variants, theme, timeOfDay) {
4896
+ if (timeOfDay === void 0 || variants === void 0) return theme;
4897
+ if (!isTileflowTimeOfDay(timeOfDay)) throw new TypeError("Expected day, dawn, dusk, or night");
4898
+ const family = Object.hasOwn(variants, theme) ? variants[theme] : Object.values(variants).find((moments) => Object.values(moments).includes(theme));
4899
+ if (!family) return theme;
4900
+ const result = family[timeOfDay];
4901
+ if (result === void 0) {
4902
+ throw new Error(
4903
+ `Theme "${theme}" does not offer timeOfDay="${timeOfDay}". Available: ${Object.keys(family).join(", ")}.`
4904
+ );
4905
+ }
4906
+ return result;
4907
+ }
4908
+
4214
4909
  // src/sun.ts
4215
4910
  var tileflowSunMetadataKey = "tileflow:sun";
4216
4911
  var tileflowDefaultSunPreset = Object.freeze({
@@ -4433,7 +5128,7 @@ function resolveThemeValues(value, theme, path = "value") {
4433
5128
  if (errors.length > 0) throw new TileflowThemeAuditError(errors);
4434
5129
  return resolved;
4435
5130
  }
4436
- function resolveThemeSelection(map, requested) {
5131
+ function resolveThemeSelection(map, requested, timeOfDay) {
4437
5132
  const name = requested ?? map.defaultTheme;
4438
5133
  if (name === "system") {
4439
5134
  throw new Error(
@@ -4450,7 +5145,8 @@ function resolveThemeSelection(map, requested) {
4450
5145
  const available = Object.keys(map.themes).sort().join(", ") || "<none>";
4451
5146
  throw new Error(`Unknown Tileflow theme "${name}"; available themes: ${available}.`);
4452
5147
  }
4453
- return { name, theme };
5148
+ const resolvedName = resolveTileflowTimeOfDayTheme(map.timeOfDay, name, timeOfDay);
5149
+ return { name: resolvedName, theme: map.themes[resolvedName] };
4454
5150
  }
4455
5151
  function auditTileflowThemeValues(value, path = "value", context = inferAuditContext(path)) {
4456
5152
  const diagnostics = [];
@@ -5757,7 +6453,8 @@ function resolveColors(overrides = {}, defaults4 = defaultColors, moduleOverride
5757
6453
  resolved.landcover.grass,
5758
6454
  resolved.landcover.meadow
5759
6455
  ],
5760
- conifer: [resolved.landcover.protected, resolved.landcover.scrub, resolved.landcover.wood]
6456
+ conifer: [resolved.landcover.protected, resolved.landcover.scrub, resolved.landcover.wood],
6457
+ palm: [resolved.landcover.urbanPark, resolved.landcover.grass, resolved.landcover.meadow]
5761
6458
  }
5762
6459
  }
5763
6460
  };
@@ -5924,6 +6621,11 @@ function resolveThemeColors(theme) {
5924
6621
  optionalColor("vegetation.tree.conifer.a", colors.vegetation.tree.conifer[0]),
5925
6622
  optionalColor("vegetation.tree.conifer.b", colors.vegetation.tree.conifer[1]),
5926
6623
  optionalColor("vegetation.tree.conifer.c", colors.vegetation.tree.conifer[2])
6624
+ ],
6625
+ palm: [
6626
+ optionalColor("vegetation.tree.palm.a", colors.vegetation.tree.palm[0]),
6627
+ optionalColor("vegetation.tree.palm.b", colors.vegetation.tree.palm[1]),
6628
+ optionalColor("vegetation.tree.palm.c", colors.vegetation.tree.palm[2])
5927
6629
  ]
5928
6630
  }
5929
6631
  }
@@ -6513,9 +7215,10 @@ function compileRoundabouts(request, context, contributions) {
6513
7215
  contributions.push({
6514
7216
  kind: "layer",
6515
7217
  layer: applyCircleStyle({ ...base, id: "tileflow-road-circular-casing" }, config.casing),
6516
- localOrder: 10,
7218
+ localOrder: 900,
6517
7219
  owner: "roads",
6518
- slot: "transport-symbols",
7220
+ // Approach fills must cover the circular casing where the roads meet.
7221
+ slot: "transport-surface-casing",
6519
7222
  target: "roads.roundabouts.casing"
6520
7223
  });
6521
7224
  }
@@ -6523,9 +7226,9 @@ function compileRoundabouts(request, context, contributions) {
6523
7226
  contributions.push({
6524
7227
  kind: "layer",
6525
7228
  layer: applyCircleStyle({ ...base, id: "tileflow-road-circular-fill" }, config.fill),
6526
- localOrder: 20,
7229
+ localOrder: 900,
6527
7230
  owner: "roads",
6528
- slot: "transport-symbols",
7231
+ slot: "transport-surface-fill",
6529
7232
  target: "roads.roundabouts.fill"
6530
7233
  });
6531
7234
  }
@@ -6590,7 +7293,7 @@ var circularRoadBorderTotalWidth = 1;
6590
7293
  function circularRoadInnerRadius(fields, casing) {
6591
7294
  const values = circularRoadValues(fields);
6592
7295
  const stops = [];
6593
- for (let level = 15; level <= 22; level += 1) {
7296
+ for (let level = 15; level <= 24; level += 1) {
6594
7297
  const centerlineRadius = level === 15 ? values.radiusAtZoom15 : ["*", values.radiusAtZoom15, 2 ** (level - 15)];
6595
7298
  const physicalInnerRadius = [
6596
7299
  "*",
@@ -6607,13 +7310,13 @@ function circularRoadInnerRadius(fields, casing) {
6607
7310
  casing ? ["-", innerRadius, circularRoadBorderTotalWidth / 2] : innerRadius
6608
7311
  ]);
6609
7312
  }
6610
- return ["interpolate", ["linear"], ["zoom"], ...stops];
7313
+ return ["interpolate", ["exponential", 2], ["zoom"], ...stops];
6611
7314
  }
6612
7315
  function circularRoadStrokeWidth(fields, casing) {
6613
7316
  const values = circularRoadValues(fields);
6614
7317
  const baseWidth = circularRoadBaseWidth(fields.class);
6615
7318
  const stops = [];
6616
- for (let level = 15; level <= 22; level += 1) {
7319
+ for (let level = 15; level <= 24; level += 1) {
6617
7320
  const centerlineRadius = level === 15 ? values.radiusAtZoom15 : ["*", values.radiusAtZoom15, 2 ** (level - 15)];
6618
7321
  const physicalWidth = [
6619
7322
  "*",
@@ -6628,7 +7331,7 @@ function circularRoadStrokeWidth(fields, casing) {
6628
7331
  casing ? ["+", fallbackWidth, circularRoadBorderTotalWidth] : fallbackWidth
6629
7332
  ]);
6630
7333
  }
6631
- return ["interpolate", ["linear"], ["zoom"], ...stops];
7334
+ return ["interpolate", ["exponential", 2], ["zoom"], ...stops];
6632
7335
  }
6633
7336
  function circularRoadBaseWidth(classField) {
6634
7337
  return [
@@ -6792,6 +7495,10 @@ function applyRoadTreatments(base, treatments, structure, phase, fallbackColor)
6792
7495
  return value === void 0 ? [] : [{ condition, resolve: () => expressionOutput(value) }];
6793
7496
  });
6794
7497
  if (branches.length > 0) {
7498
+ if (property === "dash" && isSolidRoadDash(result[property] ?? treatmentPaintDefaults.dash) && branches.every(({ resolve }) => isSolidRoadDash(resolve()))) {
7499
+ delete result[property];
7500
+ continue;
7501
+ }
6795
7502
  result[property] = conditionalStyleValue(
6796
7503
  result[property],
6797
7504
  branches,
@@ -6808,9 +7515,21 @@ function applyRoadTreatments(base, treatments, structure, phase, fallbackColor)
6808
7515
  widthScales,
6809
7516
  treatmentPaintDefaults.width
6810
7517
  );
7518
+ if (result.gapWidth !== void 0 && result.gapWidth !== 0) {
7519
+ result.gapWidth = conditionalWidthScaleValue(
7520
+ result.gapWidth,
7521
+ widthScales,
7522
+ treatmentPaintDefaults.gapWidth
7523
+ );
7524
+ }
6811
7525
  }
6812
7526
  return result;
6813
7527
  }
7528
+ function isSolidRoadDash(value) {
7529
+ const output = expressionOutput(value);
7530
+ const dash = Array.isArray(output) && output[0] === "literal" ? output[1] : output;
7531
+ return Array.isArray(dash) && dash.length === 2 && dash[0] === 1 && dash[1] === 0;
7532
+ }
6814
7533
  function roadTreatmentPhaseConditions(treatments, structure, phase) {
6815
7534
  return treatments.flatMap(
6816
7535
  ({ condition, style }) => style[structure]?.[phase] === void 0 ? [] : [condition]
@@ -6895,10 +7614,10 @@ function isExpressionOutput(value) {
6895
7614
  }
6896
7615
  function rewriteZoomOutputs(value, rewrite) {
6897
7616
  if (!Array.isArray(value)) return rewrite(value);
6898
- if (value[0] === "interpolate" && isZoomInput3(value[2])) {
7617
+ if (value[0] === "interpolate" && isZoomInput4(value[2])) {
6899
7618
  return value.map((entry, index) => index >= 4 && index % 2 === 0 ? rewrite(entry) : entry);
6900
7619
  }
6901
- if (value[0] === "step" && isZoomInput3(value[1])) {
7620
+ if (value[0] === "step" && isZoomInput4(value[1])) {
6902
7621
  return value.map(
6903
7622
  (entry, index) => index === 2 || index >= 4 && index % 2 === 0 ? rewrite(entry) : entry
6904
7623
  );
@@ -6908,7 +7627,7 @@ function rewriteZoomOutputs(value, rewrite) {
6908
7627
  function expressionOutput(value) {
6909
7628
  return Array.isArray(value) && typeof value[0] !== "string" ? ["literal", value] : value;
6910
7629
  }
6911
- function isZoomInput3(value) {
7630
+ function isZoomInput4(value) {
6912
7631
  return Array.isArray(value) && value.length === 1 && value[0] === "zoom";
6913
7632
  }
6914
7633
  function restrictedAccessFilter(field2) {
@@ -7243,105 +7962,35 @@ function railHatchingStyle(color2, opacity) {
7243
7962
  };
7244
7963
  }
7245
7964
  function serviceRailStyle(color2, opacity) {
7246
- return {
7247
- color: color2,
7248
- minZoom: 12,
7249
- opacity,
7250
- width: zoom.linear([
7251
- [12, 0.35],
7252
- [16, 1.4]
7253
- ])
7254
- };
7255
- }
7256
- function structureFilter2(field2, structure) {
7257
- return structure === "surface" ? ["match", ["get", field2], ["tunnel", "bridge"], false, true] : ["==", ["get", field2], structure];
7258
- }
7259
- function modalFilter(classField, subclassField, modes) {
7260
- return [
7261
- "any",
7262
- ["match", ["get", classField], modes, true, false],
7263
- ["match", ["get", subclassField], modes, true, false]
7264
- ];
7265
- }
7266
-
7267
- // src/modules/vegetation/index.ts
7268
- function vegetation(options = {}) {
7269
- return { type: "vegetation", ...cloneJson16(options) };
7270
- }
7271
- function cloneJson16(value) {
7272
- return JSON.parse(JSON.stringify(value));
7273
- }
7274
-
7275
- // src/modules/vegetation/compiler.ts
7276
- function compileVegetation(request, context) {
7277
- const minZoom = request?.flat?.minZoom ?? 16;
7278
- const mode = request?.mode ?? "3d";
7279
- const config = mergeTileflowDesign(
7280
- {
7281
- type: "vegetation",
7282
- enabled: true,
7283
- flat: {
7284
- color: context.colors.landcover.wood,
7285
- minZoom,
7286
- opacity: mode === "3d" ? 0.82 : 0.9,
7287
- pitchAlignment: "map",
7288
- pitchScale: "map",
7289
- radius: minZoom < 24 ? zoom.linear([
7290
- [minZoom, 2.2],
7291
- [minZoom < 20 ? 20 : 24, 8]
7292
- ]) : 2.2,
7293
- strokeColor: context.colors.park,
7294
- strokeOpacity: 0.55,
7295
- strokeWidth: 0.8
7296
- },
7297
- mode,
7298
- threeDimensional: {
7299
- barkColor: context.colors.vegetation.tree.bark,
7300
- broadleafColors: context.colors.vegetation.tree.broadleaf,
7301
- coniferColors: context.colors.vegetation.tree.conifer,
7302
- crownScale: 1,
7303
- heightScale: 1
7304
- }
7305
- },
7306
- request
7307
- );
7308
- if (config.enabled === false || !context.data.schema.layers.tree) return [];
7309
- const resolvedMode = config.mode ?? "3d";
7310
- const threeDimensional = config.threeDimensional;
7965
+ return {
7966
+ color: color2,
7967
+ minZoom: 12,
7968
+ opacity,
7969
+ width: zoom.linear([
7970
+ [12, 0.35],
7971
+ [16, 1.4]
7972
+ ])
7973
+ };
7974
+ }
7975
+ function structureFilter2(field2, structure) {
7976
+ return structure === "surface" ? ["match", ["get", field2], ["tunnel", "bridge"], false, true] : ["==", ["get", field2], structure];
7977
+ }
7978
+ function modalFilter(classField, subclassField, modes) {
7311
7979
  return [
7312
- {
7313
- kind: "layer",
7314
- layer: applyCircleStyle(
7315
- {
7316
- id: "tileflow-vegetation-trees",
7317
- type: "circle",
7318
- source: context.data.sourceId,
7319
- "source-layer": context.data.schema.layers.tree,
7320
- metadata: {
7321
- "tileflow:vegetation-mode": resolvedMode,
7322
- ...resolvedMode === "3d" ? { "tileflow:vegetation-fallback": "flat-circle" } : {},
7323
- "tileflow:tree-bark-color": threeDimensional.barkColor,
7324
- "tileflow:tree-broadleaf-colors": threeDimensional.broadleafColors,
7325
- "tileflow:tree-conifer-colors": threeDimensional.coniferColors,
7326
- "tileflow:tree-crown-field": context.data.schema.fields.diameterCrown,
7327
- "tileflow:tree-crown-scale": threeDimensional.crownScale,
7328
- "tileflow:tree-genus-field": context.data.schema.fields.genus,
7329
- "tileflow:tree-height-field": context.data.schema.fields.height,
7330
- "tileflow:tree-height-scale": threeDimensional.heightScale,
7331
- "tileflow:tree-leaf-type-field": context.data.schema.fields.leafType,
7332
- "tileflow:tree-species-field": context.data.schema.fields.species
7333
- }
7334
- },
7335
- config.flat ?? {}
7336
- ),
7337
- localOrder: 1,
7338
- owner: "vegetation",
7339
- slot: "vegetation",
7340
- target: "vegetation.trees"
7341
- }
7980
+ "any",
7981
+ ["match", ["get", classField], modes, true, false],
7982
+ ["match", ["get", subclassField], modes, true, false]
7342
7983
  ];
7343
7984
  }
7344
7985
 
7986
+ // src/modules/vegetation/index.ts
7987
+ function vegetation(options = {}) {
7988
+ return { type: "vegetation", ...cloneJson16(options) };
7989
+ }
7990
+ function cloneJson16(value) {
7991
+ return JSON.parse(JSON.stringify(value));
7992
+ }
7993
+
7345
7994
  // src/modules/water/index.ts
7346
7995
  function water(options = {}) {
7347
7996
  return { type: "water", ...cloneJson17(options) };
@@ -7357,7 +8006,7 @@ var waterwayClasses = {
7357
8006
  river: ["river"],
7358
8007
  stream: ["stream"]
7359
8008
  };
7360
- function compileWater(request, context) {
8009
+ function compileWater(request, context, language = "auto") {
7361
8010
  const config = mergeTileflowDesign(
7362
8011
  {
7363
8012
  type: "water",
@@ -7509,8 +8158,11 @@ function compileWater(request, context) {
7509
8158
  appendBathymetryRelief(contributions, bathymetryProduct.relief, context);
7510
8159
  }
7511
8160
  const productContours = bathymetryProduct?.contours;
8161
+ const nativeContours = sidecarBathymetry && bathymetryProduct?.vectorLayers?.contours === true;
8162
+ const contourLayer = nativeContours ? tileflowBathymetryV1Schema.layers.contours : bathymetryLayer;
8163
+ const contourDepthField = nativeContours ? tileflowBathymetryV1Schema.fields.depth : bathymetryDepthField;
7512
8164
  const contourRequest = productContours === false ? void 0 : productContours === void 0 ? request?.bathymetryContours : mergeTileflowDesign(productContours, request?.bathymetryContours);
7513
- if (contourRequest !== void 0 && bathymetryLayer && bathymetryDepthField) {
8165
+ if (contourRequest !== void 0 && contourLayer && contourDepthField) {
7514
8166
  const bathymetryContours = mergeTileflowDesign(
7515
8167
  {
7516
8168
  color: context.colors.hydro.label,
@@ -7533,12 +8185,12 @@ function compileWater(request, context) {
7533
8185
  id: "tileflow-bathymetry-contours",
7534
8186
  type: "line",
7535
8187
  source: bathymetrySource,
7536
- "source-layer": bathymetryLayer,
8188
+ "source-layer": contourLayer,
7537
8189
  filter: [
7538
8190
  "all",
7539
- ["==", ["geometry-type"], "Polygon"],
7540
- ["has", bathymetryDepthField],
7541
- ["<", ["to-number", ["get", bathymetryDepthField], 0], 0]
8191
+ ["==", ["geometry-type"], nativeContours ? "LineString" : "Polygon"],
8192
+ ["has", contourDepthField],
8193
+ ["<", ["to-number", ["get", contourDepthField], 0], 0]
7542
8194
  ]
7543
8195
  },
7544
8196
  bathymetryContours
@@ -7552,19 +8204,19 @@ function compileWater(request, context) {
7552
8204
  }
7553
8205
  const productLabels = bathymetryProduct?.labels;
7554
8206
  const labelRequest = productLabels === false ? void 0 : productLabels === void 0 ? request?.bathymetryLabels : mergeTileflowDesign(productLabels, request?.bathymetryLabels);
7555
- if (labelRequest !== void 0 && bathymetryLayer && bathymetryDepthField) {
8207
+ if (labelRequest !== void 0 && contourLayer && contourDepthField) {
7556
8208
  const bathymetryLabels = mergeTileflowDesign(
7557
8209
  {
7558
8210
  maxZoom: 10,
7559
8211
  minZoom: 3,
7560
- placement: "point",
8212
+ placement: nativeContours ? "line" : "point",
7561
8213
  priority: 5,
7562
8214
  text: {
7563
8215
  allowOverlap: false,
7564
8216
  color: context.colors.hydro.label,
7565
8217
  field: expression([
7566
8218
  "to-string",
7567
- ["abs", ["to-number", ["get", bathymetryDepthField], 0]]
8219
+ ["abs", ["to-number", ["get", contourDepthField], 0]]
7568
8220
  ]),
7569
8221
  ...typographyTextStyle(context.typography.water),
7570
8222
  haloColor: context.colors.hydro.water,
@@ -7585,8 +8237,13 @@ function compileWater(request, context) {
7585
8237
  id: "tileflow-bathymetry-labels",
7586
8238
  type: "symbol",
7587
8239
  source: bathymetrySource,
7588
- "source-layer": bathymetryLayer,
7589
- filter: ["all", ["==", ["geometry-type"], "Polygon"], ["has", bathymetryDepthField]]
8240
+ "source-layer": contourLayer,
8241
+ filter: [
8242
+ "all",
8243
+ ["==", ["geometry-type"], nativeContours ? "LineString" : "Polygon"],
8244
+ ["has", contourDepthField],
8245
+ ...nativeContours ? [["<", ["to-number", ["get", contourDepthField], 0], 0]] : []
8246
+ ]
7590
8247
  },
7591
8248
  bathymetryLabels
7592
8249
  ),
@@ -7597,6 +8254,37 @@ function compileWater(request, context) {
7597
8254
  });
7598
8255
  }
7599
8256
  }
8257
+ if (sidecarBathymetry) {
8258
+ const { layers: marineLayers } = tileflowBathymetryV1Schema;
8259
+ const landforms2 = bathymetryProduct?.vectorLayers?.landforms;
8260
+ const waterNames = bathymetryProduct?.vectorLayers?.waterNames;
8261
+ if (landforms2) {
8262
+ appendBathymetryNames(contributions, {
8263
+ availability: landforms2,
8264
+ context,
8265
+ language,
8266
+ localOrder: -300,
8267
+ source: sidecarBathymetry.sourceId,
8268
+ sourceLayer: marineLayers.landforms,
8269
+ style: config.bathymetryLandforms,
8270
+ target: "water.bathymetryLandforms",
8271
+ id: "tileflow-bathymetry-landforms"
8272
+ });
8273
+ }
8274
+ if (waterNames) {
8275
+ appendBathymetryNames(contributions, {
8276
+ availability: waterNames,
8277
+ context,
8278
+ language,
8279
+ localOrder: -150,
8280
+ source: sidecarBathymetry.sourceId,
8281
+ sourceLayer: marineLayers.waterNames,
8282
+ style: config.bathymetryWaterNames,
8283
+ target: "water.bathymetryWaterNames",
8284
+ id: "tileflow-bathymetry-water-names"
8285
+ });
8286
+ }
8287
+ }
7600
8288
  let localOrder = 10;
7601
8289
  for (const [name, classes] of Object.entries(waterwayClasses)) {
7602
8290
  const style = config.waterways?.[name];
@@ -7660,6 +8348,99 @@ function compileWater(request, context) {
7660
8348
  }
7661
8349
  return contributions;
7662
8350
  }
8351
+ function appendBathymetryNames(contributions, options) {
8352
+ const { context, language } = options;
8353
+ const { fields } = tileflowBathymetryV1Schema;
8354
+ const localName = ["to-string", ["coalesce", ["get", fields.name], ""]];
8355
+ const name = language === "auto" || language === "local" ? localName : [
8356
+ "case",
8357
+ ["!=", ["to-string", ["coalesce", ["get", `name:${language}`], ""]], ""],
8358
+ ["to-string", ["get", `name:${language}`]],
8359
+ localName
8360
+ ];
8361
+ const style = mergeTileflowDesign(
8362
+ {
8363
+ minZoom: 0,
8364
+ placement: "point",
8365
+ priority: expression(["to-number", ["get", fields.sortKey], 0]),
8366
+ priorityOrder: "lower-first",
8367
+ text: {
8368
+ ...typographyTextStyle(context.typography.water),
8369
+ allowOverlap: false,
8370
+ ignorePlacement: false,
8371
+ color: context.colors.hydro.label,
8372
+ field: expression(name),
8373
+ haloColor: context.colors.hydro.water,
8374
+ haloWidth: 1,
8375
+ optional: false,
8376
+ padding: 12,
8377
+ size: 11
8378
+ }
8379
+ },
8380
+ options.style
8381
+ );
8382
+ if (style.visible === false || style.text?.visible === false) return;
8383
+ const layer = applySymbolStyle(
8384
+ {
8385
+ id: options.id,
8386
+ type: "symbol",
8387
+ source: options.source,
8388
+ "source-layer": options.sourceLayer,
8389
+ filter: [
8390
+ "all",
8391
+ ["match", ["geometry-type"], ["Point", "LineString", "Polygon"], true, false],
8392
+ ["!=", name, ""]
8393
+ ]
8394
+ },
8395
+ style
8396
+ );
8397
+ const sourceMinimum = ["to-number", ["coalesce", ["get", fields.minZoom], 0], 0];
8398
+ const sourceMaximum = ["to-number", ["coalesce", ["get", fields.maxZoom], 24], 24];
8399
+ const baseFilter = layer.filter;
8400
+ const append = (result, target, order = 0) => {
8401
+ contributions.push({
8402
+ kind: "layer",
8403
+ layer: result,
8404
+ localOrder: options.localOrder + order,
8405
+ owner: "water",
8406
+ slot: "symbols",
8407
+ target
8408
+ });
8409
+ };
8410
+ if (options.availability === true) {
8411
+ append(
8412
+ {
8413
+ ...layer,
8414
+ filter: [...baseFilter, [">=", ["zoom"], sourceMinimum], ["<", ["zoom"], sourceMaximum]]
8415
+ },
8416
+ options.target
8417
+ );
8418
+ return;
8419
+ }
8420
+ const ranges = [...options.availability.zoomRanges].sort(
8421
+ (left, right) => left.minZoom - right.minZoom || left.maxZoom - right.maxZoom
8422
+ );
8423
+ for (const [index, range2] of ranges.entries()) {
8424
+ const minzoom = Math.max(Number(layer.minzoom ?? 0), range2.minZoom);
8425
+ const maxzoom = Math.min(Number(layer.maxzoom ?? 24), range2.maxZoom);
8426
+ if (minzoom >= maxzoom) continue;
8427
+ append(
8428
+ {
8429
+ ...layer,
8430
+ id: `${options.id}-cohort-${index}`,
8431
+ minzoom,
8432
+ maxzoom,
8433
+ filter: [
8434
+ ...baseFilter,
8435
+ ["==", sourceMinimum, range2.minZoom],
8436
+ ["==", sourceMaximum, range2.maxZoom]
8437
+ ]
8438
+ },
8439
+ `${options.target}.cohort${index}`,
8440
+ index
8441
+ );
8442
+ }
8443
+ }
7663
8444
  function appendBathymetryRelief(contributions, relief, context) {
7664
8445
  const { sourceId, style } = relief;
7665
8446
  const opacity = resolvedNumber(style.opacity, 0.22, "bathymetry relief opacity");
@@ -7868,12 +8649,6 @@ var tileflowSemanticDomainRegistry = Object.freeze([
7868
8649
  dependencies: {},
7869
8650
  name: "land"
7870
8651
  }),
7871
- defineDomain({
7872
- compile: ({ context, module }) => compileWater(module, context),
7873
- defaultModule: water(),
7874
- dependencies: {},
7875
- name: "water"
7876
- }),
7877
8652
  defineDomain({
7878
8653
  compile: ({ context, module }) => compileNautical(module, context),
7879
8654
  defaultModule: nautical(),
@@ -7962,6 +8737,12 @@ var tileflowSemanticDomainRegistry = Object.freeze([
7962
8737
  provides: ["language"],
7963
8738
  resolveServices: ({ module }) => ({ language: resolveLabels(module).language })
7964
8739
  }),
8740
+ defineDomain({
8741
+ compile: ({ context, dependencies, module }) => compileWater(module, context, dependencies.service("language")),
8742
+ defaultModule: water(),
8743
+ dependencies: { services: ["language"] },
8744
+ name: "water"
8745
+ }),
7965
8746
  defineDomain({
7966
8747
  compile: ({ context, dependencies, module }) => compileLandforms(module, dependencies.service("language"), context),
7967
8748
  defaultModule: landforms(),
@@ -8080,7 +8861,7 @@ function createTileflowCompilerProvenance(owner, slot, target) {
8080
8861
  return [{ operations: [], owner, slot, target }];
8081
8862
  }
8082
8863
  function readTileflowCompilerProvenance(layer) {
8083
- const metadata = asRecord2(layer.metadata);
8864
+ const metadata = asRecord3(layer.metadata);
8084
8865
  const raw = metadata[tileflowCompilerProvenanceMetadataKey];
8085
8866
  if (Array.isArray(raw)) {
8086
8867
  const parsed = raw.flatMap((value) => {
@@ -8108,7 +8889,7 @@ function createTileflowStyleInspection(map, theme, layers) {
8108
8889
  };
8109
8890
  }
8110
8891
  function parseContribution(value) {
8111
- const input = asRecord2(value);
8892
+ const input = asRecord3(value);
8112
8893
  const owner = requireLayerDomain(input.owner);
8113
8894
  const slot = requireLayerSlot(input.slot);
8114
8895
  const target = input.target;
@@ -8122,7 +8903,7 @@ function parseContribution(value) {
8122
8903
  return { operations, owner, slot, target };
8123
8904
  }
8124
8905
  function parseRenderOperation(value) {
8125
- const input = asRecord2(value);
8906
+ const input = asRecord3(value);
8126
8907
  const owner = requireLayerDomain(input.owner);
8127
8908
  const target = input.target;
8128
8909
  if (input.kind !== "pass" && input.kind !== "refinement" || !owner || typeof target !== "string" || !tileflowSemanticTargetPattern.test(target)) {
@@ -8136,7 +8917,7 @@ function requireLayerDomain(value) {
8136
8917
  function requireLayerSlot(value) {
8137
8918
  return typeof value === "string" && tileflowLayerSlots.includes(value) ? value : void 0;
8138
8919
  }
8139
- function asRecord2(value) {
8920
+ function asRecord3(value) {
8140
8921
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
8141
8922
  }
8142
8923
 
@@ -8183,6 +8964,7 @@ var classicManifestSchemaOperations = {
8183
8964
  return z3.enum(values);
8184
8965
  },
8185
8966
  literal: (value) => z3.literal(value),
8967
+ union: (left, right) => z3.union([classic(left), classic(right)]),
8186
8968
  tuplePair(left, right) {
8187
8969
  return z3.tuple([classic(left), classic(right)]);
8188
8970
  },
@@ -8505,7 +9287,7 @@ function not(value) {
8505
9287
  function has(reference) {
8506
9288
  return make(["has", reference]);
8507
9289
  }
8508
- function match(input, branches, fallback) {
9290
+ function match2(input, branches, fallback) {
8509
9291
  validateBranchCount(branches.length, "expr.match");
8510
9292
  validateMatchLabels(branches);
8511
9293
  return make([
@@ -8603,7 +9385,7 @@ function limitError(message) {
8603
9385
  return new Error(site ? `${message} Called at ${site}.` : message);
8604
9386
  }
8605
9387
  function authoringCallSite(stack) {
8606
- const locations = (stack ?? "").split("\n").slice(1).map((line) => /\(?([^()\s]+?):(\d+):(\d+)\)?$/.exec(line.trim())).filter((match2) => match2 !== null).map(([, file, line, column]) => ({ file: file.replace(/^file:\/\//, ""), line, column }));
9388
+ const locations = (stack ?? "").split("\n").slice(1).map((line) => /\(?([^()\s]+?):(\d+):(\d+)\)?$/.exec(line.trim())).filter((match3) => match3 !== null).map(([, file, line, column]) => ({ file: file.replace(/^file:\/\//, ""), line, column }));
8607
9389
  const own = locations[0]?.file;
8608
9390
  const site = locations.find(({ file }) => file !== own && !file.startsWith("node:"));
8609
9391
  return site ? `${site.file}:${site.line}:${site.column}` : void 0;
@@ -8988,7 +9770,7 @@ var expr = Object.freeze({
8988
9770
  literal,
8989
9771
  lt: (left, right) => compare("<", left, right),
8990
9772
  lte: (left, right) => compare("<=", left, right),
8991
- match,
9773
+ match: match2,
8992
9774
  max: maximum,
8993
9775
  min: minimum,
8994
9776
  modulo,
@@ -9012,6 +9794,12 @@ var tileflowSemanticFieldNames = Object.freeze(
9012
9794
  var tileflowSemanticLayerNames = Object.freeze(
9013
9795
  Object.keys(completeSemanticDataContract.layers).sort(compareCodeUnits5)
9014
9796
  );
9797
+ var auxiliaryFieldBindings = Object.freeze({
9798
+ bathymetryContourDepth: tileflowBathymetryV1Schema.fields.depth
9799
+ });
9800
+ var tileflowSemanticExpressionFieldNames = Object.freeze(
9801
+ [...tileflowSemanticFieldNames, ...Object.keys(auxiliaryFieldBindings)].sort(compareCodeUnits5)
9802
+ );
9015
9803
  function field(name) {
9016
9804
  return Object.freeze({ kind: "tileflow-data-field", name });
9017
9805
  }
@@ -9043,6 +9831,9 @@ function bindSemanticReferences(value, data) {
9043
9831
  }
9044
9832
  function bindSemanticValue(value, schema, sourceId) {
9045
9833
  if (isDataFieldReference2(value)) {
9834
+ if (Object.hasOwn(auxiliaryFieldBindings, value.name)) {
9835
+ return auxiliaryFieldBindings[value.name];
9836
+ }
9046
9837
  const binding = schema.fields[value.name];
9047
9838
  if (!binding) throw new Error(`Tileflow data schema does not provide field ${value.name}.`);
9048
9839
  return binding;
@@ -9247,72 +10038,77 @@ function emitLayer(family, resolvePhysicalId) {
9247
10038
  function physicalLayerIdForSemanticKey(semanticKey) {
9248
10039
  const exact = exactPhysicalLayerIds[semanticKey];
9249
10040
  if (exact) return exact;
9250
- let match2 = /^([A-Za-z0-9_-]+)\.render\.([A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)$/u.exec(
10041
+ let match3 = /^([A-Za-z0-9_-]+)\.render\.([A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)$/u.exec(
9251
10042
  semanticKey
9252
10043
  );
9253
- if (match2) return `tileflow-${match2[1]}-render-${match2[2].replaceAll(".", "-")}`;
9254
- match2 = /^roads\.classes\.([^.]+)\.(tunnel|surface|bridge)\.(shadow|casing|fill|hatch)$/u.exec(
10044
+ if (match3) return `tileflow-${match3[1]}-render-${match3[2].replaceAll(".", "-")}`;
10045
+ match3 = /^roads\.classes\.([^.]+)\.(tunnel|surface|bridge)\.(shadow|casing|fill|hatch)$/u.exec(
9255
10046
  semanticKey
9256
10047
  );
9257
- if (match2) return `tileflow-road-${match2[2]}-${match2[1]}-${match2[3]}`;
9258
- match2 = /^roads\.cohorts\.(tunnel|surface|bridge)\.([^.]+)\.(shadow|casing|fill)$/u.exec(
10048
+ if (match3) return `tileflow-road-${match3[2]}-${match3[1]}-${match3[3]}`;
10049
+ match3 = /^roads\.cohorts\.(tunnel|surface|bridge)\.([^.]+)\.(shadow|casing|fill)$/u.exec(
9259
10050
  semanticKey
9260
10051
  );
9261
- if (match2) return `tileflow-road-${match2[1]}-highzoom-${match2[2]}-${match2[3]}`;
9262
- match2 = /^roads\.cohorts\.(tunnel|surface|bridge)\.hatch$/u.exec(semanticKey);
9263
- if (match2) return `tileflow-road-${match2[1]}-hatch`;
9264
- match2 = /^roads\.areas\.(road|pedestrian|pier)\.(fill|outline)$/u.exec(semanticKey);
9265
- if (match2) {
9266
- const base = match2[1] === "road" ? "tileflow-road-area" : `tileflow-road-${match2[1]}-area`;
9267
- return match2[2] === "fill" ? base : `${base}-outline`;
9268
- }
9269
- match2 = /^land\.(landcover|landuse)\.([^.]+)\.(fill|outline)$/u.exec(semanticKey);
9270
- if (match2) {
9271
- const base = `tileflow-${match2[1]}-${match2[2]}`;
9272
- return match2[3] === "fill" ? base : `${base}-outline`;
9273
- }
9274
- match2 = /^land\.compatibility\.legacyPark\.(fill|outline)$/u.exec(semanticKey);
9275
- if (match2) {
9276
- return match2[1] === "fill" ? "tileflow-landcover-legacy-park" : "tileflow-landcover-legacy-park-outline";
9277
- }
9278
- match2 = /^land\.cohorts\.(landcover|landuse)(?:\.cohort(\d+))?$/u.exec(semanticKey);
9279
- if (match2) return `tileflow-${match2[1]}${match2[2] === void 0 ? "" : `-${match2[2]}`}`;
9280
- match2 = /^water\.(intermittent\.)?bodies\.(fill|outline)$/u.exec(semanticKey);
9281
- if (match2) {
9282
- const base = `tileflow-water${match2[1] ? "-intermittent" : ""}`;
9283
- return match2[2] === "fill" ? base : `${base}-outline`;
9284
- }
9285
- match2 = /^water\.(intermittent\.)?waterways\.([^.]+)$/u.exec(semanticKey);
9286
- if (match2) return `tileflow-waterway-${match2[2]}${match2[1] ? "-intermittent" : ""}`;
9287
- match2 = /^buildings\.(businessCorridor|flat)\.(fill|outline)$/u.exec(semanticKey);
9288
- if (match2) {
9289
- const base = match2[1] === "businessCorridor" ? "tileflow-business-corridor" : "tileflow-buildings-fill";
9290
- return match2[2] === "fill" ? base : `${base}-outline`;
9291
- }
9292
- match2 = /^aeroways\.area\.(fill|outline)$/u.exec(semanticKey);
9293
- if (match2) return match2[1] === "fill" ? "tileflow-aeroway-area" : "tileflow-aeroway-area-outline";
9294
- match2 = /^aeroways\.(taxiway|runway)\.(shadow|casing|fill)$/u.exec(semanticKey);
9295
- if (match2) return `tileflow-aeroway-${match2[1]}-${match2[2]}`;
9296
- match2 = /^transit\.([A-Za-z0-9_-]+)(?:\.(tunnel|surface|bridge))?$/u.exec(semanticKey);
9297
- if (match2) return `tileflow-transit-${match2[1]}${match2[2] ? `-${match2[2]}` : ""}`;
9298
- match2 = /^boundaries\.([A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)$/u.exec(semanticKey);
9299
- if (match2) return `tileflow-boundary-${match2[1].replaceAll(".", "-")}`;
9300
- match2 = /^labels\.places\.([^.]+)$/u.exec(semanticKey);
9301
- if (match2) return `tileflow-label-place-${match2[1]}`;
9302
- match2 = /^labels\.roads\.([^.]+)$/u.exec(semanticKey);
9303
- if (match2) return `tileflow-label-road-${match2[1]}`;
9304
- match2 = /^labels\.shields\.([^.]+)$/u.exec(semanticKey);
9305
- if (match2) return `tileflow-label-road-shield-${match2[1]}`;
9306
- match2 = /^labels\.water\.([^.]+)$/u.exec(semanticKey);
9307
- if (match2) return `tileflow-label-water-${match2[1]}`;
9308
- match2 = /^labels\.cohorts\.roads\.(major|local)$/u.exec(semanticKey);
9309
- if (match2) return `tileflow-label-road-${match2[1]}`;
9310
- match2 = /^labels\.cohorts\.roads\.cohort(\d+)$/u.exec(semanticKey);
9311
- if (match2) return `tileflow-label-road-cohort-${match2[1]}`;
9312
- match2 = /^landforms\.classes\.([^.]+)$/u.exec(semanticKey);
9313
- if (match2) return `tileflow-landform-${match2[1]}`;
9314
- match2 = /^poi\.([A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)$/u.exec(semanticKey);
9315
- if (match2) return `tileflow-poi-${match2[1].replaceAll(".", "-")}`;
10052
+ if (match3) return `tileflow-road-${match3[1]}-highzoom-${match3[2]}-${match3[3]}`;
10053
+ match3 = /^roads\.cohorts\.(tunnel|surface|bridge)\.hatch$/u.exec(semanticKey);
10054
+ if (match3) return `tileflow-road-${match3[1]}-hatch`;
10055
+ match3 = /^roads\.areas\.(road|pedestrian|pier)\.(fill|outline)$/u.exec(semanticKey);
10056
+ if (match3) {
10057
+ const base = match3[1] === "road" ? "tileflow-road-area" : `tileflow-road-${match3[1]}-area`;
10058
+ return match3[2] === "fill" ? base : `${base}-outline`;
10059
+ }
10060
+ match3 = /^land\.(landcover|landuse)\.([^.]+)\.(fill|outline)$/u.exec(semanticKey);
10061
+ if (match3) {
10062
+ const base = `tileflow-${match3[1]}-${match3[2]}`;
10063
+ return match3[3] === "fill" ? base : `${base}-outline`;
10064
+ }
10065
+ match3 = /^land\.compatibility\.legacyPark\.(fill|outline)$/u.exec(semanticKey);
10066
+ if (match3) {
10067
+ return match3[1] === "fill" ? "tileflow-landcover-legacy-park" : "tileflow-landcover-legacy-park-outline";
10068
+ }
10069
+ match3 = /^land\.cohorts\.(landcover|landuse)(?:\.cohort(\d+))?$/u.exec(semanticKey);
10070
+ if (match3) return `tileflow-${match3[1]}${match3[2] === void 0 ? "" : `-${match3[2]}`}`;
10071
+ match3 = /^water\.(intermittent\.)?bodies\.(fill|outline)$/u.exec(semanticKey);
10072
+ if (match3) {
10073
+ const base = `tileflow-water${match3[1] ? "-intermittent" : ""}`;
10074
+ return match3[2] === "fill" ? base : `${base}-outline`;
10075
+ }
10076
+ match3 = /^water\.(intermittent\.)?waterways\.([^.]+)$/u.exec(semanticKey);
10077
+ if (match3) return `tileflow-waterway-${match3[2]}${match3[1] ? "-intermittent" : ""}`;
10078
+ match3 = /^buildings\.(businessCorridor|flat)\.(fill|outline)$/u.exec(semanticKey);
10079
+ if (match3) {
10080
+ const base = match3[1] === "businessCorridor" ? "tileflow-business-corridor" : "tileflow-buildings-fill";
10081
+ return match3[2] === "fill" ? base : `${base}-outline`;
10082
+ }
10083
+ match3 = /^aeroways\.area\.(fill|outline)$/u.exec(semanticKey);
10084
+ if (match3) return match3[1] === "fill" ? "tileflow-aeroway-area" : "tileflow-aeroway-area-outline";
10085
+ match3 = /^aeroways\.(taxiway|runway)\.(shadow|casing|fill)$/u.exec(semanticKey);
10086
+ if (match3) return `tileflow-aeroway-${match3[1]}-${match3[2]}`;
10087
+ match3 = /^transit\.([A-Za-z0-9_-]+)(?:\.(tunnel|surface|bridge))?$/u.exec(semanticKey);
10088
+ if (match3) return `tileflow-transit-${match3[1]}${match3[2] ? `-${match3[2]}` : ""}`;
10089
+ match3 = /^boundaries\.([A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)$/u.exec(semanticKey);
10090
+ if (match3) return `tileflow-boundary-${match3[1].replaceAll(".", "-")}`;
10091
+ match3 = /^labels\.places\.([^.]+)$/u.exec(semanticKey);
10092
+ if (match3) return `tileflow-label-place-${match3[1]}`;
10093
+ match3 = /^labels\.roads\.([^.]+)$/u.exec(semanticKey);
10094
+ if (match3) return `tileflow-label-road-${match3[1]}`;
10095
+ match3 = /^labels\.shields\.([^.]+)$/u.exec(semanticKey);
10096
+ if (match3) return `tileflow-label-road-shield-${match3[1]}`;
10097
+ match3 = /^labels\.water\.([^.]+)$/u.exec(semanticKey);
10098
+ if (match3) return `tileflow-label-water-${match3[1]}`;
10099
+ match3 = /^labels\.cohorts\.roads\.(major|local)$/u.exec(semanticKey);
10100
+ if (match3) return `tileflow-label-road-${match3[1]}`;
10101
+ match3 = /^labels\.cohorts\.roads\.cohort(\d+)$/u.exec(semanticKey);
10102
+ if (match3) return `tileflow-label-road-cohort-${match3[1]}`;
10103
+ match3 = /^landforms\.classes\.([^.]+)$/u.exec(semanticKey);
10104
+ if (match3) return `tileflow-landform-${match3[1]}`;
10105
+ match3 = /^water\.(bathymetryLandforms|bathymetryWaterNames)\.cohort(\d+)$/u.exec(semanticKey);
10106
+ if (match3) {
10107
+ const base = match3[1] === "bathymetryLandforms" ? "landforms" : "water-names";
10108
+ return `tileflow-bathymetry-${base}-cohort-${match3[2]}`;
10109
+ }
10110
+ match3 = /^poi\.([A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)$/u.exec(semanticKey);
10111
+ if (match3) return `tileflow-poi-${match3[1].replaceAll(".", "-")}`;
9316
10112
  const nautical2 = nauticalPhysicalLayerId(semanticKey);
9317
10113
  if (nautical2) return nautical2;
9318
10114
  throw new TileflowDomainIRError(
@@ -9329,6 +10125,7 @@ var exactPhysicalLayerIds = Object.freeze({
9329
10125
  "labels.junctions": "tileflow-label-road-junction",
9330
10126
  "land.background": "tileflow-background",
9331
10127
  "land.globalLandcover": "tileflow-global-landcover",
10128
+ poi: "tileflow-poi",
9332
10129
  "landforms.classes": "tileflow-landforms",
9333
10130
  "roads.crossings": "tileflow-road-crossing",
9334
10131
  "roads.oneWayMarkers": "tileflow-road-oneway",
@@ -9345,6 +10142,8 @@ var exactPhysicalLayerIds = Object.freeze({
9345
10142
  "water.bathymetry": "tileflow-bathymetry",
9346
10143
  "water.bathymetryContours": "tileflow-bathymetry-contours",
9347
10144
  "water.bathymetryLabels": "tileflow-bathymetry-labels",
10145
+ "water.bathymetryLandforms": "tileflow-bathymetry-landforms",
10146
+ "water.bathymetryWaterNames": "tileflow-bathymetry-water-names",
9348
10147
  "water.bathymetryRelief.color": "tileflow-bathymetry-color-relief",
9349
10148
  "water.bathymetryRelief.hillshade": "tileflow-bathymetry-relief"
9350
10149
  });
@@ -9369,15 +10168,15 @@ var nauticalPhysicalBases = Object.freeze({
9369
10168
  function nauticalPhysicalLayerId(semanticKey) {
9370
10169
  const direct = nauticalPhysicalBases[semanticKey];
9371
10170
  if (direct) return direct;
9372
- const match2 = /^(nautical\.[A-Za-z0-9_.-]+)\.(fill|outline|marker|symbol)$/u.exec(semanticKey);
9373
- if (!match2) return void 0;
9374
- const base = nauticalPhysicalBases[match2[1]];
10171
+ const match3 = /^(nautical\.[A-Za-z0-9_.-]+)\.(fill|outline|marker|symbol)$/u.exec(semanticKey);
10172
+ if (!match3) return void 0;
10173
+ const base = nauticalPhysicalBases[match3[1]];
9375
10174
  if (!base) return void 0;
9376
- if (match2[2] === "fill" || match2[2] === "symbol") return base;
9377
- return `${base}-${match2[2]}`;
10175
+ if (match3[2] === "fill" || match3[2] === "symbol") return base;
10176
+ return `${base}-${match3[2]}`;
9378
10177
  }
9379
10178
  function publicMetadata(value) {
9380
- const metadata = { ...asRecord3(value) };
10179
+ const metadata = { ...asRecord4(value) };
9381
10180
  delete metadata[tileflowCompilerProvenanceMetadataKey];
9382
10181
  for (const key of Object.values(tileflowCompilerMetadataKeys)) delete metadata[key];
9383
10182
  return Object.keys(metadata).length > 0 ? cloneJson20(metadata) : void 0;
@@ -9424,7 +10223,7 @@ function isSemanticSourceReference(value) {
9424
10223
  function cloneJson20(value) {
9425
10224
  return JSON.parse(JSON.stringify(value));
9426
10225
  }
9427
- function asRecord3(value) {
10226
+ function asRecord4(value) {
9428
10227
  return isRecord5(value) ? value : {};
9429
10228
  }
9430
10229
  function isRecord5(value) {
@@ -9898,8 +10697,8 @@ function applyRendererStyle(layer, renderer, style) {
9898
10697
  if (!visibilityGroup) return result;
9899
10698
  return {
9900
10699
  ...result,
9901
- layout: { ...asRecord4(result.layout), visibility: "none" },
9902
- metadata: { ...asRecord4(result.metadata), "tileflow:3d-toggle": visibilityGroup }
10700
+ layout: { ...asRecord5(result.layout), visibility: "none" },
10701
+ metadata: { ...asRecord5(result.metadata), "tileflow:3d-toggle": visibilityGroup }
9903
10702
  };
9904
10703
  }
9905
10704
  function lowerSelector(selector, data) {
@@ -10381,7 +11180,7 @@ function compareCodeUnits6(left, right) {
10381
11180
  function cloneJson21(value) {
10382
11181
  return JSON.parse(JSON.stringify(value));
10383
11182
  }
10384
- function asRecord4(value) {
11183
+ function asRecord5(value) {
10385
11184
  return isRecord6(value) ? value : {};
10386
11185
  }
10387
11186
  function isRecord6(value) {
@@ -10579,6 +11378,19 @@ function isLoopbackHostname(hostname) {
10579
11378
  return normalized === "localhost" || normalized === "::1" || normalized === "0:0:0:0:0:0:0:1" || /^127(?:\.\d{1,3}){3}$/u.test(normalized);
10580
11379
  }
10581
11380
 
11381
+ // src/time-of-day-schema.ts
11382
+ function createTileflowTimeOfDaySchema(operations, themeName) {
11383
+ return operations.record(
11384
+ themeName,
11385
+ operations.object({
11386
+ day: operations.optional(themeName),
11387
+ dawn: operations.optional(themeName),
11388
+ dusk: operations.optional(themeName),
11389
+ night: operations.optional(themeName)
11390
+ })
11391
+ );
11392
+ }
11393
+
10582
11394
  // src/resolved-map-schema.ts
10583
11395
  var TileflowResolvedMapValidationError = class extends Error {
10584
11396
  code = "CONFIG_INVALID";
@@ -10726,7 +11538,7 @@ function validateExpressionFieldNames(value, context, path) {
10726
11538
  }
10727
11539
  if (!value || typeof value !== "object") return;
10728
11540
  const candidate = value;
10729
- if (candidate.kind === "tileflow-data-field" && (typeof candidate.name !== "string" || !Object.hasOwn(fieldBindingsSchema.shape, candidate.name))) {
11541
+ if (candidate.kind === "tileflow-data-field" && (typeof candidate.name !== "string" || !tileflowSemanticExpressionFieldNames.some((name) => name === candidate.name))) {
10730
11542
  context.addIssue({
10731
11543
  code: "custom",
10732
11544
  message: "Expected a registered semantic field name.",
@@ -10880,12 +11692,12 @@ var hexColorSchema = z5.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a
10880
11692
  message: "Expected a hex color like #F6F7F3 or #F6F7F3FF"
10881
11693
  });
10882
11694
  var rgbColorSchema = z5.string().refine((value) => {
10883
- const match2 = value.match(
11695
+ const match3 = value.match(
10884
11696
  /^rgba?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)(?:\s*,\s*(\d+(?:\.\d+)?))?\s*\)$/iu
10885
11697
  );
10886
- if (!match2) return false;
10887
- const channels = match2.slice(1, 4).map(Number);
10888
- const alpha2 = Number(match2[4] ?? 1);
11698
+ if (!match3) return false;
11699
+ const channels = match3.slice(1, 4).map(Number);
11700
+ const alpha2 = Number(match3[4] ?? 1);
10889
11701
  return channels.every((channel) => channel >= 0 && channel <= 255) && alpha2 >= 0 && alpha2 <= 1;
10890
11702
  }, "Expected a Tileflow hex, rgb(), or rgba() color");
10891
11703
  var terrainColorLiteralSchema = z5.union([hexColorSchema, rgbColorSchema]);
@@ -11162,9 +11974,7 @@ var symbolStyleSchema = z5.object({
11162
11974
  text: textStyleSchema.optional()
11163
11975
  }).strict();
11164
11976
  var markerSymbolStyleSchema = symbolStyleSchema.extend({ marker: circleStyleSchema.optional() }).strict();
11165
- var poiCategoryStyleSchema = markerSymbolStyleSchema.omit({ priority: true, priorityOrder: true }).extend({
11166
- density: z5.union([z5.literal(1), z5.literal(2), z5.literal(3), z5.literal(4), z5.literal(5)]).optional()
11167
- }).strict();
11977
+ var poiCategoryStyleSchema = markerSymbolStyleSchema.omit({ priority: true, priorityOrder: true }).strict();
11168
11978
  var renderScalarSchema = z5.union([z5.boolean(), z5.number().finite(), z5.string()]);
11169
11979
  var renderTargetSchema = z5.string().regex(tileflowSemanticTargetPattern, "Expected a semantic target");
11170
11980
  var renderStackOperationNameSchema = z5.string().regex(
@@ -11397,6 +12207,8 @@ var waterModuleSchema = z5.object({
11397
12207
  bathymetry: fillStyleSchema.optional(),
11398
12208
  bathymetryContours: lineStyleSchema.optional(),
11399
12209
  bathymetryLabels: symbolStyleSchema.optional(),
12210
+ bathymetryLandforms: symbolStyleSchema.optional(),
12211
+ bathymetryWaterNames: symbolStyleSchema.optional(),
11400
12212
  bodies: areaStyleSchema.optional(),
11401
12213
  enabled: z5.boolean().optional(),
11402
12214
  intermittent: z5.object({ bodies: areaStyleSchema.optional(), waterways: lineStyleSchema.optional() }).strict().optional(),
@@ -11408,6 +12220,17 @@ var waterModuleSchema = z5.object({
11408
12220
  stream: lineStyleSchema.optional()
11409
12221
  }).strict().optional()
11410
12222
  }).strict();
12223
+ function boundedThemeNumberSchema(min, max) {
12224
+ return z5.union([
12225
+ z5.number().finite().min(min).max(max),
12226
+ themeNumberReferenceSchema,
12227
+ z5.object({
12228
+ kind: z5.literal("theme-fixed"),
12229
+ reason: z5.string().trim().min(1),
12230
+ value: z5.number().finite().min(min).max(max)
12231
+ }).strict()
12232
+ ]);
12233
+ }
11411
12234
  var buildingsModuleSchema = z5.object({
11412
12235
  type: z5.literal("buildings"),
11413
12236
  businessCorridor: areaStyleSchema.optional(),
@@ -11415,7 +12238,46 @@ var buildingsModuleSchema = z5.object({
11415
12238
  extrusion: extrusionStyleSchema.optional(),
11416
12239
  flat: areaStyleSchema.optional(),
11417
12240
  mode: z5.enum(["3d", "flat"]).optional(),
11418
- renderStack: renderStackSchema.optional()
12241
+ renderStack: renderStackSchema.optional(),
12242
+ threeDimensional: z5.object({
12243
+ lighting: z5.enum(["style", "sun"]).optional(),
12244
+ colorVariation: boundedThemeNumberSchema(0, 1).optional(),
12245
+ edges: boundedThemeNumberSchema(0, 1).optional(),
12246
+ edgeColor: themeColorValueSchema.optional(),
12247
+ nightLight: boundedThemeNumberSchema(0, 2).optional(),
12248
+ occlusion: z5.boolean().optional(),
12249
+ shadows: z5.boolean().optional(),
12250
+ windows: z5.boolean().optional(),
12251
+ models: z5.array(
12252
+ z5.object({
12253
+ model: z5.string().max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u),
12254
+ at: z5.tuple([z5.number().min(-180).max(180), z5.number().min(-85).max(85)]),
12255
+ heading: z5.number().finite().optional(),
12256
+ url: z5.url().max(2048).refine((value) => new URL(value).protocol === "https:", {
12257
+ message: "A model served elsewhere has an HTTPS address."
12258
+ }).optional()
12259
+ }).strict()
12260
+ ).max(256).optional(),
12261
+ modelColors: z5.object({
12262
+ surface: themeColorValueSchema.optional(),
12263
+ secondary: themeColorValueSchema.optional(),
12264
+ glass: themeColorValueSchema.optional(),
12265
+ accent: themeColorValueSchema.optional(),
12266
+ roof: themeColorValueSchema.optional()
12267
+ }).strict().optional(),
12268
+ landmarks: z5.union([
12269
+ z5.boolean(),
12270
+ z5.object({
12271
+ url: z5.url().max(2048).refine(
12272
+ (value) => {
12273
+ const url = new URL(value);
12274
+ return url.protocol === "https:" || url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
12275
+ },
12276
+ { message: "A landmarks tileset has an HTTPS address, or a local one." }
12277
+ )
12278
+ }).strict()
12279
+ ]).optional()
12280
+ }).strict().optional()
11419
12281
  }).strict();
11420
12282
  var addressesModuleSchema = z5.object({
11421
12283
  type: z5.literal("addresses"),
@@ -11528,6 +12390,12 @@ var roadsModuleSchema = z5.object({
11528
12390
  pattern: fillStyleSchema.optional(),
11529
12391
  surface: fillStyleSchema.optional()
11530
12392
  }).strict().optional(),
12393
+ streetLights: z5.object({
12394
+ color: themeColorValueSchema.optional(),
12395
+ enabled: z5.boolean().optional(),
12396
+ estimate: z5.enum(["lit", "streets", "none"]).optional(),
12397
+ intensity: boundedThemeNumberSchema(0, 2).optional()
12398
+ }).strict().optional(),
11531
12399
  structures: roadStructureMapSchema.optional(),
11532
12400
  weight: z5.enum(["thin", "regular", "bold"]).optional(),
11533
12401
  widthScale: z5.partialRecord(roadClassSchema, themeNumberValueSchema).optional()
@@ -11587,7 +12455,10 @@ var poiModuleSchema = z5.object({
11587
12455
  minZoom: zoomNumberSchema.optional(),
11588
12456
  placement: z5.object({
11589
12457
  coupleIconAndLabel: z5.boolean().optional(),
12458
+ iconOnlyTypes: z5.array(z5.string().regex(/^[a-z0-9]+(?:_[a-z0-9]+)*$/u, "Expected a World POI type")).max(64).optional(),
11590
12459
  iconPadding: z5.number().finite().min(0).optional(),
12460
+ order: z5.enum(["categories", "importance"]).optional(),
12461
+ prominentRank: z5.number().int().min(0).max(5).optional(),
11591
12462
  textPadding: z5.number().finite().min(0).optional()
11592
12463
  }).strict().optional(),
11593
12464
  renderStack: renderStackSchema.optional(),
@@ -11616,6 +12487,16 @@ var nauticalModuleSchema = z5.object({
11616
12487
  wreckAreas: areaStyleSchema.optional(),
11617
12488
  wrecks: markerSymbolStyleSchema.optional()
11618
12489
  }).strict();
12490
+ var treeFormMixSchema = z5.object({
12491
+ round: z5.number().finite().min(0).optional(),
12492
+ open: z5.number().finite().min(0).optional(),
12493
+ tall: z5.number().finite().min(0).optional(),
12494
+ conifer: z5.number().finite().min(0).optional(),
12495
+ columnar: z5.number().finite().min(0).optional(),
12496
+ palm: z5.number().finite().min(0).optional()
12497
+ }).strict().refine((mix2) => Object.values(mix2).some((weight) => (weight ?? 0) > 0), {
12498
+ message: "Expected at least one tree form with a weight above 0"
12499
+ });
11619
12500
  var modulesSchema = z5.object({
11620
12501
  addresses: addressesModuleSchema.optional(),
11621
12502
  aeroways: aerowaysModuleSchema.optional(),
@@ -11632,12 +12513,47 @@ var modulesSchema = z5.object({
11632
12513
  type: z5.literal("vegetation"),
11633
12514
  enabled: z5.boolean().optional(),
11634
12515
  flat: circleStyleSchema.optional(),
11635
- mode: z5.enum(["3d", "flat"]).optional(),
12516
+ mode: z5.enum(["3d", "flat", "auto"]).optional(),
12517
+ fallback: z5.enum(["flat", "none"]).optional(),
12518
+ visible: z5.boolean().optional(),
11636
12519
  renderStack: renderStackSchema.optional(),
11637
12520
  threeDimensional: z5.object({
11638
12521
  barkColor: themeColorValueSchema.optional(),
11639
12522
  broadleafColors: z5.array(themeColorValueSchema).min(1).max(8).optional(),
11640
12523
  coniferColors: z5.array(themeColorValueSchema).min(1).max(8).optional(),
12524
+ palmColors: z5.array(themeColorValueSchema).min(1).max(8).optional(),
12525
+ finish: z5.enum(["faceted", "smooth", "toon"]).optional(),
12526
+ edges: boundedThemeNumberSchema(0, 1).optional(),
12527
+ edgeColor: themeColorValueSchema.optional(),
12528
+ forms: z5.partialRecord(
12529
+ z5.enum(["round", "open", "tall", "conifer", "columnar", "palm"]),
12530
+ z5.object({
12531
+ crownScale: boundedThemeNumberSchema(0.1, 10).optional(),
12532
+ heightScale: boundedThemeNumberSchema(0.1, 10).optional(),
12533
+ models: z5.array(
12534
+ z5.string().max(64).regex(
12535
+ tileflowTreeModelIdPattern,
12536
+ "Expected a model id: its .glb file name in lowercase letters, digits and hyphens"
12537
+ )
12538
+ ).min(1).max(4).refine((ids) => new Set(ids).size === ids.length, {
12539
+ message: "Expected each model once"
12540
+ }).optional()
12541
+ }).strict()
12542
+ ).optional(),
12543
+ unknownForms: treeFormMixSchema.optional(),
12544
+ regions: z5.array(
12545
+ z5.object({
12546
+ bounds: z5.tuple([
12547
+ z5.number().finite().min(-180).max(180),
12548
+ z5.number().finite().min(-90).max(90),
12549
+ z5.number().finite().min(-180).max(180),
12550
+ z5.number().finite().min(-90).max(90)
12551
+ ]).refine(([, south, , north]) => south <= north, {
12552
+ message: "Expected bounds as [west, south, east, north] with south below north"
12553
+ }),
12554
+ unknownForms: treeFormMixSchema
12555
+ }).strict()
12556
+ ).max(16).optional(),
11641
12557
  crownScale: z5.union([
11642
12558
  z5.number().finite().positive().max(10),
11643
12559
  themeNumberReferenceSchema,
@@ -11736,6 +12652,7 @@ var fieldBindingsSchema = z5.object({
11736
12652
  markings: z5.string().trim().min(1),
11737
12653
  minHeight: z5.string().trim().min(1),
11738
12654
  minZoom: z5.string().trim().min(1),
12655
+ mode: z5.string().trim().min(1),
11739
12656
  mtbScale: z5.string().trim().min(1),
11740
12657
  name: z5.string().trim().min(1),
11741
12658
  nameEnglish: z5.string().trim().min(1),
@@ -11839,6 +12756,32 @@ var bathymetryReliefSchema = z5.object({
11839
12756
  });
11840
12757
  }
11841
12758
  });
12759
+ var bathymetryNameLayerSchema = z5.union([
12760
+ z5.boolean(),
12761
+ z5.object({
12762
+ zoomRanges: z5.array(z5.object({ minZoom: zoomNumberSchema, maxZoom: zoomNumberSchema }).strict()).min(1).max(tileflowBathymetryNameLayerLimits.maxZoomRanges).superRefine((ranges, context) => {
12763
+ const seen = /* @__PURE__ */ new Set();
12764
+ for (const [index, range2] of ranges.entries()) {
12765
+ if (range2.minZoom >= range2.maxZoom) {
12766
+ context.addIssue({
12767
+ code: "custom",
12768
+ message: "Expected minZoom < maxZoom",
12769
+ path: [index, "maxZoom"]
12770
+ });
12771
+ }
12772
+ const key = `${range2.minZoom}/${range2.maxZoom}`;
12773
+ if (seen.has(key)) {
12774
+ context.addIssue({
12775
+ code: "custom",
12776
+ message: "Expected unique nominal zoom ranges",
12777
+ path: [index]
12778
+ });
12779
+ }
12780
+ seen.add(key);
12781
+ }
12782
+ })
12783
+ }).strict()
12784
+ ]);
11842
12785
  var bathymetryConfigSchema = z5.object({
11843
12786
  ...marineSourceSchema.shape,
11844
12787
  bands: z5.union([z5.literal(false), fillStyleSchema]).optional(),
@@ -11846,7 +12789,12 @@ var bathymetryConfigSchema = z5.object({
11846
12789
  display: z5.enum(["bands", "relief", "hybrid"]).optional(),
11847
12790
  labels: z5.union([z5.literal(false), symbolStyleSchema]).optional(),
11848
12791
  relief: z5.union([z5.literal(false), bathymetryReliefSchema]).optional(),
11849
- type: z5.literal("bathymetry")
12792
+ type: z5.literal("bathymetry"),
12793
+ vectorLayers: z5.object({
12794
+ contours: z5.boolean().optional(),
12795
+ landforms: bathymetryNameLayerSchema.optional(),
12796
+ waterNames: bathymetryNameLayerSchema.optional()
12797
+ }).strict().optional()
11850
12798
  }).strict();
11851
12799
  var marineSchema = z5.union([
11852
12800
  z5.enum(["none", "bathymetry", "nautical", "chart"]),
@@ -11970,6 +12918,7 @@ var iconDirectoriesSchema = z5.array(
11970
12918
  }
11971
12919
  });
11972
12920
  var fontDirectoriesSchema = z5.array(assetDirectorySchema).max(16);
12921
+ var modelDirectoriesSchema = z5.array(assetDirectorySchema).max(16);
11973
12922
  var fontStacksSchema = z5.array(
11974
12923
  z5.string().min(1).max(200).refine(
11975
12924
  (value) => value === value.trim() && value === value.normalize("NFC") && !/[\p{Cc}\\/]/u.test(value),
@@ -12191,6 +13140,7 @@ var resolvedTileflowMapSchema = z5.object({
12191
13140
  icons: iconDirectoriesSchema.optional(),
12192
13141
  id: tileflowMapIdSchema,
12193
13142
  marine: marineSchema.optional(),
13143
+ models: modelDirectoriesSchema.optional(),
12194
13144
  modules: modulesSchema.optional(),
12195
13145
  name: z5.string().trim().min(1),
12196
13146
  overlays: overlaysSchema.optional(),
@@ -12199,6 +13149,10 @@ var resolvedTileflowMapSchema = z5.object({
12199
13149
  systemThemes: systemThemesSchema.optional(),
12200
13150
  terrain: terrainSchema.optional(),
12201
13151
  themes: themesSchema,
13152
+ timeOfDay: createTileflowTimeOfDaySchema(
13153
+ classicManifestSchemaOperations,
13154
+ tileflowThemeNameSchema
13155
+ ).optional(),
12202
13156
  version: z5.number().int().positive(),
12203
13157
  view: viewSchema.optional()
12204
13158
  }).strict().superRefine((map, context) => {
@@ -12223,6 +13177,15 @@ var resolvedTileflowMapSchema = z5.object({
12223
13177
  path: ["defaultTheme"]
12224
13178
  });
12225
13179
  }
13180
+ if (map.timeOfDay) {
13181
+ for (const issue of validateTileflowTimeOfDayVariants(map.timeOfDay, map.themes)) {
13182
+ context.addIssue({
13183
+ code: "custom",
13184
+ message: issue.message,
13185
+ path: ["timeOfDay", ...issue.path]
13186
+ });
13187
+ }
13188
+ }
12226
13189
  const themeEntries = Object.entries(map.themes).sort(
12227
13190
  ([left], [right]) => left < right ? -1 : left > right ? 1 : 0
12228
13191
  );
@@ -12445,6 +13408,23 @@ function tileflowRouteMetadata(inks) {
12445
13408
  return Object.freeze({ version: 1, ...inks });
12446
13409
  }
12447
13410
 
13411
+ // src/street-lights.ts
13412
+ var tileflowStreetLightsMetadataKey = "tileflow:street-lights";
13413
+ var tileflowDefaultStreetLightsDesign = Object.freeze({
13414
+ source: "tileflow",
13415
+ layers: Object.freeze({
13416
+ streetFurniture: "street_furniture",
13417
+ transportation: "transportation",
13418
+ sidewalk: "sidewalk"
13419
+ }),
13420
+ estimate: "lit",
13421
+ color: "#ffcf8f",
13422
+ intensity: 1
13423
+ });
13424
+ function tileflowStreetLightsMetadata(design) {
13425
+ return { version: 1, ...design };
13426
+ }
13427
+
12448
13428
  // src/terrain/index.ts
12449
13429
  function resolveTerrain(terrain, apiBaseUrl) {
12450
13430
  if (!terrain || terrain === "none") return void 0;
@@ -12623,7 +13603,7 @@ function terrainContribution(target, localOrder, layer) {
12623
13603
  };
12624
13604
  }
12625
13605
  function applyLayerRange2(layer, range2) {
12626
- const layout = { ...asRecord5(layer.layout) };
13606
+ const layout = { ...asRecord6(layer.layout) };
12627
13607
  if (range2.visible !== void 0) layout.visibility = range2.visible ? "visible" : "none";
12628
13608
  return {
12629
13609
  ...layer,
@@ -12632,10 +13612,13 @@ function applyLayerRange2(layer, range2) {
12632
13612
  ...Object.keys(layout).length === 0 ? {} : { layout }
12633
13613
  };
12634
13614
  }
12635
- function asRecord5(value) {
13615
+ function asRecord6(value) {
12636
13616
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
12637
13617
  }
12638
13618
 
13619
+ // src/vegetation-display.ts
13620
+ var tileflowVegetationDisplayMetadataKey = "tileflow:vegetation-display";
13621
+
12639
13622
  // src/cartography/compilation-report.ts
12640
13623
  var tileflowCompilationReportSchemaVersion = 1;
12641
13624
  var tileflowCompilationPhases = Object.freeze([
@@ -13007,14 +13990,16 @@ function defaultPaint(image, key, names, appearances, ids) {
13007
13990
 
13008
13991
  // src/cartography/interaction-manifest.ts
13009
13992
  var tileflowInteractionManifestMetadataKey = "tileflow:interaction-manifest";
13010
- var tileflowInteractionManifestVersion = 2;
13993
+ var tileflowInteractionManifestVersion = 3;
13011
13994
  function createTileflowInteractionManifest(layers, fields) {
13012
13995
  const poiLayers = layers.flatMap((layer, priority) => {
13013
13996
  const id2 = typeof layer.id === "string" ? layer.id : void 0;
13014
13997
  const source = typeof layer.source === "string" ? layer.source : void 0;
13015
13998
  const sourceLayer = typeof layer["source-layer"] === "string" ? layer["source-layer"] : void 0;
13016
13999
  const target = semanticTarget(layer);
13017
- if (!id2 || !source || !sourceLayer || !target?.startsWith("poi.")) return [];
14000
+ if (!id2 || !source || !sourceLayer || target !== "poi" && !target?.startsWith("poi.")) {
14001
+ return [];
14002
+ }
13018
14003
  const parsed = parsePoiTarget(target);
13019
14004
  if (!parsed) return [];
13020
14005
  return [
@@ -13062,7 +14047,7 @@ function assertTileflowInteractionManifestLayers(manifest, layers) {
13062
14047
  );
13063
14048
  }
13064
14049
  for (const layer of poiLayers) {
13065
- if (!isTileflowPoiCategory(layer.category) || !isSafeArtifactName(layer.layerId) || !isSafeArtifactName(layer.source) || !isSafeArtifactName(layer.sourceLayer)) {
14050
+ if (!isSafeArtifactName(layer.layerId) || !isSafeArtifactName(layer.source) || !isSafeArtifactName(layer.sourceLayer)) {
13066
14051
  throw new Error("Tileflow interaction manifest contains an invalid physical POI lookup.");
13067
14052
  }
13068
14053
  const finalized = layersById.get(layer.layerId);
@@ -13085,15 +14070,16 @@ function assertTileflowInteractionManifestLayers(manifest, layers) {
13085
14070
  }
13086
14071
  function parsePoiTarget(target) {
13087
14072
  const segments = target.split(".");
13088
- if (segments[0] !== "poi" || segments.length < 2 || segments.length > 3) return void 0;
13089
- const category = segments[1];
13090
- if (!isTileflowPoiCategory(category)) return void 0;
13091
- const suffix = segments[2];
13092
- if (suffix === void 0) return { category, representation: "combined" };
13093
- if (suffix === "icon" || suffix === "label" || suffix === "marker") {
13094
- return { category, representation: suffix };
13095
- }
13096
- return void 0;
14073
+ if (segments[0] !== "poi" || segments.length > 3) return void 0;
14074
+ const [, first, second] = segments;
14075
+ if (first === void 0) return { representation: "combined" };
14076
+ if (segments.length === 2 && isRepresentationPart(first)) return { representation: first };
14077
+ if (first !== "prominent" && !isTileflowPoiCategory(first)) return void 0;
14078
+ if (second === void 0) return { representation: "combined" };
14079
+ return isRepresentationPart(second) ? { representation: second } : void 0;
14080
+ }
14081
+ function isRepresentationPart(value) {
14082
+ return value === "icon" || value === "label" || value === "marker";
13097
14083
  }
13098
14084
  function isTileflowPoiCategory(value) {
13099
14085
  return typeof value === "string" && tileflowPoiCategories.includes(value);
@@ -13827,7 +14813,7 @@ function conditionalExpression(entries, fallback) {
13827
14813
  ];
13828
14814
  }
13829
14815
  const classMatches = entries.map(({ condition }) => splitClassMatch(condition));
13830
- if (classMatches.every((match2) => match2 !== void 0) && allEqual(classMatches.map((match2) => match2?.field)) && allEqual(classMatches.map((match2) => match2?.remainder)) && classMatchLabelsAreDisjoint(classMatches.map((match2) => match2.labels))) {
14816
+ if (classMatches.every((match3) => match3 !== void 0) && allEqual(classMatches.map((match3) => match3?.field)) && allEqual(classMatches.map((match3) => match3?.remainder)) && classMatchLabelsAreDisjoint(classMatches.map((match3) => match3.labels))) {
13831
14817
  return [
13832
14818
  "match",
13833
14819
  ["get", classMatches[0].field],
@@ -13991,6 +14977,114 @@ function stableSerialize(value) {
13991
14977
  return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => `${JSON.stringify(key)}:${stableSerialize(entry)}`).join(",")}}`;
13992
14978
  }
13993
14979
 
14980
+ // src/cartography/scene-3d-metadata.ts
14981
+ var colourSources = [
14982
+ ["tileflow-buildings-render-extrusion", "fill-extrusion-color"],
14983
+ ["tileflow-buildings-3d", "fill-extrusion-color"],
14984
+ ["tileflow-buildings-fill", "fill-color"]
14985
+ ];
14986
+ function tileflowBuildings3DDesignOf(module, context, layers, buildingModels, tileflowLandmarks) {
14987
+ const config = isRecord8(module) ? module : {};
14988
+ if (config.enabled === false) return void 0;
14989
+ const style = config.threeDimensional ?? {};
14990
+ let color2;
14991
+ let verticalGradient = true;
14992
+ for (const [id2, property] of colourSources) {
14993
+ const value = layers.find((layer) => layer.id === id2)?.paint?.[property];
14994
+ if (typeof value === "string" || Array.isArray(value)) {
14995
+ color2 = value;
14996
+ verticalGradient = layers.find((layer) => layer.id === id2)?.paint?.["fill-extrusion-vertical-gradient"] !== false;
14997
+ break;
14998
+ }
14999
+ }
15000
+ const number = (value, fallback) => {
15001
+ const resolved = value === void 0 ? void 0 : toMapLibreStyleValue(value);
15002
+ return typeof resolved === "number" ? resolved : fallback;
15003
+ };
15004
+ const edgeColor = style.edgeColor === void 0 ? void 0 : toMapLibreStyleValue(style.edgeColor);
15005
+ return {
15006
+ source: context.data.sourceId,
15007
+ layers: {
15008
+ building: context.data.schema.layers.building,
15009
+ ...context.data.schema.layers.poi ? { places: context.data.schema.layers.poi } : {}
15010
+ },
15011
+ color: color2 ?? context.colors.buildings.generic,
15012
+ lighting: style.lighting === "sun" ? "sun" : "style",
15013
+ colorVariation: number(style.colorVariation, 0),
15014
+ verticalGradient,
15015
+ edges: number(style.edges, 0),
15016
+ ...typeof edgeColor === "string" ? { edgeColor } : {},
15017
+ nightLight: number(style.nightLight, 0),
15018
+ windows: style.windows === true,
15019
+ shadows: style.shadows === true,
15020
+ occlusion: style.occlusion === true,
15021
+ ...placedModels(style.models, buildingModels),
15022
+ ...modelColours(style.modelColors),
15023
+ ...landmarksOf(style.landmarks, tileflowLandmarks)
15024
+ };
15025
+ }
15026
+ function landmarksOf(value, tileflowLandmarks) {
15027
+ if (value === false) return {};
15028
+ if (isRecord8(value) && typeof value.url === "string") return { landmarks: { url: value.url } };
15029
+ return tileflowLandmarks ? { landmarks: { url: tileflowLandmarks } } : {};
15030
+ }
15031
+ function placedModels(value, served) {
15032
+ if (!Array.isArray(value) || value.length === 0) return {};
15033
+ const placements = value;
15034
+ const missing = [
15035
+ ...new Set(
15036
+ placements.filter((entry) => !entry.url).map((entry) => entry.model).filter((id2) => !served?.[id2])
15037
+ )
15038
+ ].sort();
15039
+ if (missing.length > 0)
15040
+ throw new Error(
15041
+ `The buildings module places missing 3D models: ${missing.join(", ")}. Add ${missing.map((id2) => `${id2}.glb`).join(
15042
+ ", "
15043
+ )} to a map.models directory, with materials named surface, secondary, glass, accent or roof, and run the Node build so the directories are prepared.`
15044
+ );
15045
+ return {
15046
+ models: placements.map((entry) => ({
15047
+ model: entry.model,
15048
+ url: entry.url ?? served[entry.model],
15049
+ at: [entry.at[0], entry.at[1]],
15050
+ heading: ((entry.heading ?? 0) % 360 + 360) % 360
15051
+ }))
15052
+ };
15053
+ }
15054
+ function modelColours(value) {
15055
+ if (!isRecord8(value)) return {};
15056
+ const colours = {};
15057
+ for (const [part, colour] of Object.entries(value)) {
15058
+ const resolved = colour === void 0 ? void 0 : toMapLibreStyleValue(colour);
15059
+ if (typeof resolved === "string") colours[part] = resolved;
15060
+ }
15061
+ return Object.keys(colours).length > 0 ? { modelColors: colours } : {};
15062
+ }
15063
+ function tileflowStreetLightsDesignOf(module, context) {
15064
+ const config = isRecord8(module) ? module : {};
15065
+ const layers = context.data.schema.layers;
15066
+ const style = config.streetLights ?? {};
15067
+ if (config.enabled === false || style.enabled === false || !layers.streetFurniture)
15068
+ return void 0;
15069
+ const color2 = style.color === void 0 ? void 0 : toMapLibreStyleValue(style.color);
15070
+ const intensity = style.intensity === void 0 ? void 0 : toMapLibreStyleValue(style.intensity);
15071
+ const estimate = style.estimate;
15072
+ return {
15073
+ source: context.data.sourceId,
15074
+ layers: {
15075
+ streetFurniture: layers.streetFurniture,
15076
+ transportation: layers.road,
15077
+ ...layers.sidewalk ? { sidewalk: layers.sidewalk } : {}
15078
+ },
15079
+ estimate: estimate === "streets" || estimate === "none" || estimate === "lit" ? estimate : "lit",
15080
+ color: typeof color2 === "string" ? color2 : tileflowDefaultStreetLightsDesign.color,
15081
+ intensity: typeof intensity === "number" ? intensity : 1
15082
+ };
15083
+ }
15084
+ function isRecord8(value) {
15085
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15086
+ }
15087
+
13994
15088
  // src/cartography/semantic-compiler.ts
13995
15089
  var tileflowSemanticCompilerIdentity = Object.freeze({
13996
15090
  name: "tileflow-semantic",
@@ -14021,7 +15115,10 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
14021
15115
  suggestion,
14022
15116
  ...target ? { target } : {}
14023
15117
  }));
14024
- const selected = runCompilationPhase("theme", () => resolveThemeSelection(input, options.theme));
15118
+ const selected = runCompilationPhase(
15119
+ "theme",
15120
+ () => resolveThemeSelection(input, options.theme, options.timeOfDay)
15121
+ );
14025
15122
  const resolvedTheme = runCompilationPhase("theme", () => resolveTileflowTheme(selected.theme));
14026
15123
  const { overlays, themes: _themes, ...themeableConfig } = input;
14027
15124
  const resolvedThemeConfig = runCompilationPhase(
@@ -14127,8 +15224,19 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
14127
15224
  "domains",
14128
15225
  () => compileSemanticDomains(modules, context)
14129
15226
  );
15227
+ const vegetation3D = runCompilationPhase(
15228
+ "domains",
15229
+ () => withPreparedTreeModels(
15230
+ tileflowVegetationDescription(
15231
+ modules.vegetation,
15232
+ context
15233
+ ),
15234
+ config,
15235
+ options.preparedAssets
15236
+ )
15237
+ );
14130
15238
  const renderStackModules = Object.values(modules).filter(
14131
- (module) => isRecord8(module) && module.enabled !== false && "renderStack" in module && isRecord8(module.renderStack)
15239
+ (module) => isRecord9(module) && module.enabled !== false && "renderStack" in module && isRecord9(module.renderStack)
14132
15240
  );
14133
15241
  const renderOperations = runCompilationPhase(
14134
15242
  "render-stack",
@@ -14288,6 +15396,17 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
14288
15396
  () => createTileflowOverlayPlacementManifest(plannedLayers)
14289
15397
  );
14290
15398
  const layers = runCompilationPhase("finalization", () => finalizeTileflowLayers(plannedLayers));
15399
+ const buildings3D = tileflowBuildings3DDesignOf(
15400
+ modules.buildings,
15401
+ context,
15402
+ layers,
15403
+ options.preparedAssets?.buildingModels,
15404
+ data.kind === "tileflow-world" ? `${apiBaseUrl}/landmarks/tiles.json` : void 0
15405
+ );
15406
+ const streetLights = tileflowStreetLightsDesignOf(
15407
+ modules.roads,
15408
+ context
15409
+ );
14291
15410
  runCompilationPhase(
14292
15411
  "finalization",
14293
15412
  () => assertTileflowInteractionManifestLayers(interactionManifest, layers)
@@ -14335,6 +15454,24 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
14335
15454
  layers
14336
15455
  })
14337
15456
  );
15457
+ const flatVegetationLayers = layers.filter((layer) => {
15458
+ const metadata = layer.metadata;
15459
+ return metadata?.["tileflow:vegetation-mode"] !== void 0 || metadata?.["tileflow:domain"] === "vegetation";
15460
+ });
15461
+ const vegetationDisplay = flatVegetationLayers.length || vegetation3D ? {
15462
+ version: 1,
15463
+ mode: modules.vegetation?.mode ?? "3d",
15464
+ fallback: modules.vegetation?.fallback ?? "flat",
15465
+ visible: modules.vegetation?.visible ?? true,
15466
+ flatLayers: flatVegetationLayers.map((layer) => ({
15467
+ id: String(layer.id),
15468
+ visible: layer.layout?.visibility !== "none"
15469
+ }))
15470
+ } : void 0;
15471
+ if (vegetationDisplay && (!vegetationDisplay.visible || vegetationDisplay.mode === "3d" && vegetationDisplay.fallback === "none")) {
15472
+ for (const layer of flatVegetationLayers)
15473
+ layer.layout = { ...layer.layout, visibility: "none" };
15474
+ }
14338
15475
  const atmosphere = resolveTileflowAtmosphere(config.atmosphere);
14339
15476
  const style = {
14340
15477
  ...atmosphere ? { sky: compileTileflowSky(atmosphere) } : {},
@@ -14360,13 +15497,31 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
14360
15497
  "tileflow:colorScheme": resolvedTheme.colorScheme,
14361
15498
  [tileflowRouteMetadataKey]: tileflowRouteMetadata(resolveTileflowRouteInks(resolvedTheme)),
14362
15499
  ...resolvedTheme.sun ? { [tileflowSunMetadataKey]: tileflowSunMetadata(resolvedTheme.sun) } : {},
15500
+ ...buildings3D ? {
15501
+ [tileflowBuildingsMetadataKey]: tileflowBuildingsMetadata(
15502
+ bindSemanticReferences(buildings3D, semanticData)
15503
+ )
15504
+ } : {},
15505
+ ...streetLights ? {
15506
+ [tileflowStreetLightsMetadataKey]: tileflowStreetLightsMetadata(
15507
+ bindSemanticReferences(streetLights, semanticData)
15508
+ )
15509
+ } : {},
15510
+ ...vegetation3D ? {
15511
+ [tileflowVegetationMetadataKey]: tileflowVegetationMetadata({
15512
+ ...bindSemanticReferences(vegetation3D, semanticData),
15513
+ // The layers that draw the trees flat: the module's own and its render passes.
15514
+ flatLayers: flatVegetationLayers.map((layer) => String(layer.id))
15515
+ })
15516
+ } : {},
15517
+ ...vegetationDisplay ? { [tileflowVegetationDisplayMetadataKey]: vegetationDisplay } : {},
14363
15518
  "tileflow:data": data.identity,
14364
15519
  "tileflow:sources": sourceIdentities,
14365
15520
  ...Object.keys(loweredOverlays.identities).length > 0 ? { "tileflow:overlays": loweredOverlays.identities } : {},
14366
15521
  "tileflow:sourceRequirements": sourceRequirements,
14367
15522
  ...interactionManifest ? { [tileflowInteractionManifestMetadataKey]: interactionManifest } : {},
14368
15523
  [tileflowOverlayPlacementManifestMetadataKey]: overlayPlacementManifest,
14369
- "tileflow:modules": Object.entries(modules).filter(([, module]) => !isRecord8(module) || module.enabled !== false).map(([name]) => name).sort(),
15524
+ "tileflow:modules": Object.entries(modules).filter(([, module]) => !isRecord9(module) || module.enabled !== false).map(([name]) => name).sort(),
14370
15525
  ...config.view ? { "tileflow:view": config.view } : {}
14371
15526
  }
14372
15527
  };
@@ -14418,7 +15573,7 @@ function finalizeTileflowLayers(input) {
14418
15573
  if (typeof minimum2 === "number" && typeof maximum2 === "number" && minimum2 > maximum2) {
14419
15574
  throw new Error(`Compiled Tileflow layer ${id2} requires minzoom <= maxzoom.`);
14420
15575
  }
14421
- const metadata = isRecord8(layer.metadata) ? { ...layer.metadata } : void 0;
15576
+ const metadata = isRecord9(layer.metadata) ? { ...layer.metadata } : void 0;
14422
15577
  if (metadata) {
14423
15578
  const owner = metadata[tileflowCompilerMetadataKeys.owner];
14424
15579
  delete metadata[tileflowCompilerProvenanceMetadataKey];
@@ -14452,13 +15607,32 @@ function assertMapLibreStyle(style, mapId) {
14452
15607
  }
14453
15608
  );
14454
15609
  }
14455
- function isRecord8(value) {
15610
+ function isRecord9(value) {
14456
15611
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
14457
15612
  }
14458
15613
  function resolveGlyphs(config) {
14459
15614
  if (config.fonts !== void 0) return void 0;
14460
15615
  return config.glyphs?.url;
14461
15616
  }
15617
+ function withPreparedTreeModels(vegetation2, config, prepared) {
15618
+ if (!vegetation2) return void 0;
15619
+ const referenced = [
15620
+ ...new Set(Object.values(vegetation2.forms).flatMap((form) => form?.models ?? []))
15621
+ ].sort();
15622
+ if (referenced.length === 0) return vegetation2;
15623
+ const available = prepared?.models ?? {};
15624
+ const missing = referenced.filter((id2) => !Object.hasOwn(available, id2));
15625
+ if (missing.length > 0) {
15626
+ const authoring = config.models?.length ? `Add ${missing.map((id2) => `${id2}.glb`).join(", ")} to a map.models directory, or run the Node build so the directories are prepared.` : "Declare map.models with directories containing those .glb files.";
15627
+ throw new Error(
15628
+ `Tileflow map "${config.id}" draws missing 3D models: ${missing.join(", ")}. ${authoring}`
15629
+ );
15630
+ }
15631
+ return {
15632
+ ...vegetation2,
15633
+ models: Object.fromEntries(referenced.map((id2) => [id2, available[id2]]))
15634
+ };
15635
+ }
14462
15636
  function assertPreparedIconReferences(style, config, prepared) {
14463
15637
  const references = collectStyleImageReferences(style);
14464
15638
  if (references.size === 0) return;
@@ -14473,8 +15647,8 @@ function assertPreparedIconReferences(style, config, prepared) {
14473
15647
  function collectStyleImageReferences(style) {
14474
15648
  const result = /* @__PURE__ */ new Set();
14475
15649
  for (const layer of style.layers) {
14476
- const layout = isRecord8(layer.layout) ? layer.layout : {};
14477
- const paint = isRecord8(layer.paint) ? layer.paint : {};
15650
+ const layout = isRecord9(layer.layout) ? layer.layout : {};
15651
+ const paint = isRecord9(layer.paint) ? layer.paint : {};
14478
15652
  const layerId = typeof layer.id === "string" ? layer.id : "<unknown>";
14479
15653
  for (const [property, value] of [
14480
15654
  ["icon-image", layout["icon-image"]],
@@ -14563,7 +15737,7 @@ function assertGlyphFontStacks(style, config) {
14563
15737
  const declared = new Set(config.glyphs.fontStacks);
14564
15738
  const missing = /* @__PURE__ */ new Set();
14565
15739
  for (const layer of style.layers) {
14566
- const layout = isRecord8(layer.layout) ? layer.layout : void 0;
15740
+ const layout = isRecord9(layer.layout) ? layer.layout : void 0;
14567
15741
  const font = layout?.["text-font"];
14568
15742
  if (!Array.isArray(font) || !font.every((entry) => typeof entry === "string")) continue;
14569
15743
  const stack = font.join(",");
@@ -14579,7 +15753,7 @@ function assertGlyphFontStacks(style, config) {
14579
15753
  function assertTextAssets(style, config) {
14580
15754
  const textLayers = [];
14581
15755
  for (const layer of style.layers) {
14582
- const layout = isRecord8(layer.layout) ? layer.layout : void 0;
15756
+ const layout = isRecord9(layer.layout) ? layer.layout : void 0;
14583
15757
  if (layout?.["text-field"] === void 0) continue;
14584
15758
  textLayers.push(typeof layer.id === "string" ? layer.id : "<unknown>");
14585
15759
  const font = layout["text-font"];
@@ -14592,7 +15766,7 @@ function assertTextAssets(style, config) {
14592
15766
  if (textLayers.length === 0) return;
14593
15767
  if (config.fonts !== void 0) {
14594
15768
  const backed = style.layers.find((layer) => {
14595
- const font = isRecord8(layer.layout) ? layer.layout["text-font"] : void 0;
15769
+ const font = isRecord9(layer.layout) ? layer.layout["text-font"] : void 0;
14596
15770
  return Array.isArray(font) && font.length === 1 && typeof font[0] === "string" && parseTileflowLineBackgroundFontStack(font[0]) !== void 0;
14597
15771
  });
14598
15772
  if (backed) {
@@ -14618,7 +15792,7 @@ function runCompilationPhase(phase, operation) {
14618
15792
  }
14619
15793
  }
14620
15794
  function withCompilationPhase(error, phase) {
14621
- const record3 = isRecord8(error) ? error : {};
15795
+ const record3 = isRecord9(error) ? error : {};
14622
15796
  if (typeof record3.phase === "string") return error;
14623
15797
  if (error instanceof Error && Object.isExtensible(error)) {
14624
15798
  Object.defineProperty(error, "phase", { configurable: true, enumerable: true, value: phase });
@@ -14721,12 +15895,24 @@ function createTileflowRuntimeManifestSchema(primitives, operations, identities
14721
15895
  })
14722
15896
  ),
14723
15897
  themes: operations.record(tileflowThemeNameSchema2, manifestThemeSchema),
15898
+ timeOfDay: operations.optional(
15899
+ createTileflowTimeOfDaySchema(operations, tileflowThemeNameSchema2)
15900
+ ),
14724
15901
  usageMode: operations.optional(operations.literal("session")),
14725
15902
  view: operations.optional(viewSchema3),
14726
15903
  worldGeneration: operations.optional(operations.literal("v1"))
14727
15904
  }),
14728
15905
  (entry, context) => {
14729
15906
  const names = Object.keys(entry.themes);
15907
+ if (entry.timeOfDay) {
15908
+ for (const issue of validateTileflowTimeOfDayVariants(entry.timeOfDay, entry.themes)) {
15909
+ context.addIssue({
15910
+ code: "custom",
15911
+ message: issue.message,
15912
+ path: ["timeOfDay", ...issue.path]
15913
+ });
15914
+ }
15915
+ }
14730
15916
  if (names.length === 0) {
14731
15917
  context.addIssue({
14732
15918
  code: "custom",
@@ -14780,10 +15966,23 @@ function createTileflowRuntimeManifestSchema(primitives, operations, identities
14780
15966
  operations.object({
14781
15967
  apiUrl: operations.optional(apiOriginSchema),
14782
15968
  maps: operations.record(tileflowPortableIdSchema2, manifestMapSchema),
14783
- version: operations.literal(tileflowRuntimeManifestVersion)
15969
+ version: operations.union(
15970
+ operations.literal(tileflowRuntimeManifestVersion),
15971
+ operations.literal(2)
15972
+ )
14784
15973
  }),
14785
15974
  (manifest, context) => {
14786
15975
  const mapCount = Object.keys(manifest.maps).length;
15976
+ if (manifest.version === 1) {
15977
+ for (const [name, map] of Object.entries(manifest.maps)) {
15978
+ if (map.timeOfDay !== void 0)
15979
+ context.addIssue({
15980
+ code: "custom",
15981
+ message: "timeOfDay requires manifest version 2",
15982
+ path: ["maps", name, "timeOfDay"]
15983
+ });
15984
+ }
15985
+ }
14787
15986
  if (mapCount === 0) {
14788
15987
  context.addIssue({ code: "custom", message: "Expected at least one map", path: ["maps"] });
14789
15988
  }
@@ -15178,6 +16377,7 @@ var tileflowMapMergeStrategies = {
15178
16377
  icons: "icons",
15179
16378
  id: "identity",
15180
16379
  marine: "atomic",
16380
+ models: "atomic",
15181
16381
  modules: "modules",
15182
16382
  name: "identity",
15183
16383
  overlays: "keyed-resources",
@@ -15188,6 +16388,7 @@ var tileflowMapMergeStrategies = {
15188
16388
  systemThemes: "atomic",
15189
16389
  terrain: "atomic",
15190
16390
  themes: "atomic",
16391
+ timeOfDay: "atomic",
15191
16392
  version: "identity",
15192
16393
  view: "deep"
15193
16394
  };
@@ -15202,7 +16403,10 @@ function resolveMap(map, options = {}) {
15202
16403
  for (let index = lineage.length - 1; index >= 0; index -= 1) {
15203
16404
  const current = lineage[index];
15204
16405
  const currentId = current.id;
15205
- if (current.themes !== void 0) delete design.systemThemes;
16406
+ if (current.themes !== void 0) {
16407
+ delete design.systemThemes;
16408
+ delete design.timeOfDay;
16409
+ }
15206
16410
  for (const key of Object.keys(current)) {
15207
16411
  const value = current[key];
15208
16412
  const strategy = tileflowMapMergeStrategies[key];
@@ -15523,11 +16727,11 @@ function cloneDesign(value) {
15523
16727
  function describeMap(value) {
15524
16728
  return typeof value.id === "string" && value.id ? value.id : "<anonymous>";
15525
16729
  }
15526
- function isRecord9(value) {
16730
+ function isRecord10(value) {
15527
16731
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
15528
16732
  }
15529
16733
  function isPlainRecord4(value) {
15530
- if (!isRecord9(value)) return false;
16734
+ if (!isRecord10(value)) return false;
15531
16735
  const prototype = Object.getPrototypeOf(value);
15532
16736
  return prototype === Object.prototype || prototype === null;
15533
16737
  }
@@ -15980,6 +17184,7 @@ async function createTileflowMapBuildManifest(maps, options = {}) {
15980
17184
  semanticCompiler: { ...tileflowSemanticCompilerIdentity },
15981
17185
  sourceAssets,
15982
17186
  ...map.systemThemes ? { systemThemes: { ...map.systemThemes } } : {},
17187
+ ...map.timeOfDay && Object.keys(map.timeOfDay).length ? { timeOfDay: map.timeOfDay } : {},
15983
17188
  themes
15984
17189
  }
15985
17190
  ];
@@ -16045,6 +17250,7 @@ async function hashTileflowMapRevision(input, sourceAssets) {
16045
17250
  const {
16046
17251
  fonts: _fonts,
16047
17252
  icons: _icons,
17253
+ models: _models,
16048
17254
  id: _id,
16049
17255
  name: _name,
16050
17256
  version: _version,
@@ -16140,10 +17346,26 @@ function normalizeSourceAssets(input) {
16140
17346
  if (new Set(fonts.map(fontIdentity)).size !== fonts.length) {
16141
17347
  throw new Error("Effective Tileflow font source identities must have unique faces.");
16142
17348
  }
17349
+ const models = [...input.models ?? []].map((model) => {
17350
+ assertSha256(model.sha256, `model ${model.id}`);
17351
+ if (model.level !== "near" && model.level !== "mid" && model.level !== "far")
17352
+ throw new Error(`Tileflow model ${model.id} is near, mid or far.`);
17353
+ return { id: model.id, level: model.level, sha256: model.sha256 };
17354
+ }).sort(
17355
+ (left, right) => compareCodeUnits8(`${left.id}\0${left.level}`, `${right.id}\0${right.level}`)
17356
+ );
17357
+ if (new Set(models.map((model) => `${model.id}\0${model.level}`)).size !== models.length) {
17358
+ throw new Error("Effective Tileflow model source identities must be unique.");
17359
+ }
16143
17360
  const iconComposition = input.iconComposition === void 0 ? void 0 : parseTileflowIconComposition(input.iconComposition);
16144
17361
  if (icons.some((icon) => icon.kind === "rendered-icon") && !iconComposition)
16145
17362
  throw new Error("Rendered icon identities require an explicit composition receipt");
16146
- return { fonts, icons, ...iconComposition ? { iconComposition } : {} };
17363
+ return {
17364
+ fonts,
17365
+ icons,
17366
+ ...iconComposition ? { iconComposition } : {},
17367
+ ...models.length > 0 ? { models } : {}
17368
+ };
16147
17369
  }
16148
17370
  function fontIdentity(font) {
16149
17371
  return `${font.family}\0${font.style}\0${font.weight}`;
@@ -24909,7 +26131,7 @@ function createNativeUrlPolicy(parse) {
24909
26131
  const resourcePath = resolved.href.slice(resolved.origin.length);
24910
26132
  const suffix = resourcePath.replace(
24911
26133
  new RegExp(`${prefix}(\\d+)__`, "gu"),
24912
- (match2, index) => placeholders[Number(index)] ?? match2
26134
+ (match3, index) => placeholders[Number(index)] ?? match3
24913
26135
  );
24914
26136
  const result = resolved.origin + suffix;
24915
26137
  assertUrlText(result, "resourceUrl");
@@ -24927,8 +26149,8 @@ function createNativeUrlPolicy(parse) {
24927
26149
  if (value === void 0) return void 0;
24928
26150
  try {
24929
26151
  assertUrlText(value, "developmentOrigin");
24930
- const match2 = /^(http:\/\/[^/?#]+)\/?$/iu.exec(value);
24931
- if (!match2 || /[{}@*]/u.test(value)) throw new Error();
26152
+ const match3 = /^(http:\/\/[^/?#]+)\/?$/iu.exec(value);
26153
+ if (!match3 || /[{}@*]/u.test(value)) throw new Error();
24932
26154
  const url = parseUrl(value, "developmentOrigin");
24933
26155
  if (url.protocol !== "http:" || url.pathname !== "/" || url.search || url.hash || url.hostname.includes("*")) {
24934
26156
  throw new Error();
@@ -25339,11 +26561,11 @@ function parserIssuePointer(key, style) {
25339
26561
  let path = "";
25340
26562
  while (rest) {
25341
26563
  if (Array.isArray(value)) {
25342
- const match2 = /^\[(\d+)\]/u.exec(rest);
25343
- if (!match2 || Number(match2[1]) >= value.length) return path;
25344
- path = nativePointer(path, match2[1]);
25345
- value = value[Number(match2[1])];
25346
- rest = rest.slice(match2[0].length).replace(/^\./u, "");
26564
+ const match3 = /^\[(\d+)\]/u.exec(rest);
26565
+ if (!match3 || Number(match3[1]) >= value.length) return path;
26566
+ path = nativePointer(path, match3[1]);
26567
+ value = value[Number(match3[1])];
26568
+ rest = rest.slice(match3[0].length).replace(/^\./u, "");
25347
26569
  } else if (record2(value)) {
25348
26570
  const matches = Object.keys(value).filter(
25349
26571
  (name2) => rest === name2 || rest.startsWith(`${name2}.`) || rest.startsWith(`${name2}[`)
@@ -25436,7 +26658,11 @@ var mapBuildSchema = z15.object({
25436
26658
  semanticCompiler: z15.object({ name: z15.literal("tileflow-semantic"), version: z15.literal(1) }).strict(),
25437
26659
  sourceAssets: sourceAssetsSchema,
25438
26660
  systemThemes: z15.object({ dark: tileflowThemeNameSchema, light: tileflowThemeNameSchema }).strict().optional(),
25439
- themes: z15.record(tileflowThemeNameSchema, themeBuildSchema)
26661
+ themes: z15.record(tileflowThemeNameSchema, themeBuildSchema),
26662
+ timeOfDay: createTileflowTimeOfDaySchema(
26663
+ classicManifestSchemaOperations,
26664
+ tileflowThemeNameSchema
26665
+ ).optional()
25440
26666
  }).strict();
25441
26667
  var buildManifestSchema = z15.object({
25442
26668
  schemaVersion: z15.literal(1),
@@ -25498,6 +26724,8 @@ var tileflowRendererDeploymentArtifactSchema = z15.object({
25498
26724
  const themes = Object.keys(map.themes).sort();
25499
26725
  if (!themes.length || themes.length > tileflowRendererDeploymentLimits.maximumThemes || !equal(themes, Object.keys(family.styles).sort()) || !themes.includes(map.defaultTheme) || map.lineage[0]?.id !== artifact.mapId || map.lineage[0]?.mapVersion !== map.mapVersion || Boolean(map.sourceAssets.iconComposition) !== (map.mapRevisionSchemaVersion === 2))
25500
26726
  fail();
26727
+ if (map.timeOfDay && validateTileflowTimeOfDayVariants(map.timeOfDay, map.themes).length)
26728
+ fail();
25501
26729
  for (const scheme of ["light", "dark"]) {
25502
26730
  if (map.systemThemes && map.themes[map.systemThemes[scheme]]?.colorScheme !== scheme)
25503
26731
  fail();
@@ -25613,7 +26841,7 @@ var artifactName = z15.string().min(1).max(256).refine(
25613
26841
  })
25614
26842
  );
25615
26843
  var semanticSchema = z15.object({
25616
- version: z15.literal(2),
26844
+ version: z15.literal(3),
25617
26845
  domains: z15.object({
25618
26846
  poi: z15.object({
25619
26847
  deduplication: z15.object({
@@ -25645,7 +26873,6 @@ var semanticSchema = z15.object({
25645
26873
  layers: z15.array(
25646
26874
  z15.object({
25647
26875
  anchor: z15.literal("pointer-coordinate"),
25648
- category: z15.enum(tileflowPoiCategories),
25649
26876
  layerId: artifactName,
25650
26877
  priority: z15.number().int().min(0).max(4095),
25651
26878
  representation: z15.enum(["combined", "icon", "label", "marker"]),
@@ -25817,6 +27044,7 @@ function createManifest(catalog, options = {}) {
25817
27044
  defaultTheme: resolved.defaultTheme,
25818
27045
  ...resolved.systemThemes ? { systemThemes: resolved.systemThemes } : {},
25819
27046
  themes,
27047
+ ...options.manifestVersion !== 1 && resolved.timeOfDay && Object.keys(resolved.timeOfDay).length ? { timeOfDay: resolved.timeOfDay } : {},
25820
27048
  ...resolved.view ? { view: resolved.view } : {}
25821
27049
  }
25822
27050
  ];
@@ -25824,7 +27052,7 @@ function createManifest(catalog, options = {}) {
25824
27052
  );
25825
27053
  const manifest = {
25826
27054
  maps,
25827
- version: tileflowRuntimeManifestVersion
27055
+ version: options.manifestVersion ?? (Object.values(maps).some((map) => map.timeOfDay) ? 2 : tileflowRuntimeManifestVersion)
25828
27056
  };
25829
27057
  return parseTileflowRuntimeManifest(manifest);
25830
27058
  }