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

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
@@ -850,98 +850,6 @@ function cloneJson(value) {
850
850
  return JSON.parse(JSON.stringify(value));
851
851
  }
852
852
 
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
853
  // src/cartography/layer-style.ts
946
854
  import { Color as Color2 } from "@maplibre/maplibre-gl-style-spec";
947
855
 
@@ -1169,11 +1077,11 @@ var zoom = {
1169
1077
  };
1170
1078
  function expression(value) {
1171
1079
  validateExpressionArray(value, "expression");
1172
- return { kind: "expression", value: cloneJson3(value) };
1080
+ return { kind: "expression", value: cloneJson2(value) };
1173
1081
  }
1174
1082
  function toMapLibreStyleValue(value) {
1175
1083
  if (isTileflowExpression(value)) {
1176
- return cloneJson3(value.value);
1084
+ return cloneJson2(value.value);
1177
1085
  }
1178
1086
  if (isTileflowZoomValue(value)) {
1179
1087
  if (value.interpolation === "step") {
@@ -1181,8 +1089,8 @@ function toMapLibreStyleValue(value) {
1181
1089
  return [
1182
1090
  "step",
1183
1091
  ["zoom"],
1184
- cloneJson3(firstValue),
1185
- ...rest.flatMap(([stop, stopValue]) => [stop, cloneJson3(stopValue)])
1092
+ cloneJson2(firstValue),
1093
+ ...rest.flatMap(([stop, stopValue]) => [stop, cloneJson2(stopValue)])
1186
1094
  ];
1187
1095
  }
1188
1096
  const interpolation = value.interpolation === "exponential" ? ["exponential", value.base] : [value.interpolation];
@@ -1190,13 +1098,13 @@ function toMapLibreStyleValue(value) {
1190
1098
  "interpolate",
1191
1099
  interpolation,
1192
1100
  ["zoom"],
1193
- ...value.stops.flatMap(([stop, stopValue]) => [stop, cloneJson3(stopValue)])
1101
+ ...value.stops.flatMap(([stop, stopValue]) => [stop, cloneJson2(stopValue)])
1194
1102
  ];
1195
1103
  }
1196
1104
  if (isTileflowThemeValueNode(value)) {
1197
1105
  throw new Error("Tileflow theme values must be resolved before MapLibre style compilation.");
1198
1106
  }
1199
- return cloneJson3(value);
1107
+ return cloneJson2(value);
1200
1108
  }
1201
1109
  function isTileflowExpression(value) {
1202
1110
  return isRecord(value) && value.kind === "expression" && Array.isArray(value.value);
@@ -1233,7 +1141,7 @@ function createZoomValue(interpolation, stops, base) {
1233
1141
  throw new Error("Tileflow zoom stops must be strictly increasing.");
1234
1142
  }
1235
1143
  previous = stop;
1236
- return [stop, cloneJson3(value)];
1144
+ return [stop, cloneJson2(value)];
1237
1145
  });
1238
1146
  return {
1239
1147
  kind: "zoom",
@@ -1249,9 +1157,9 @@ function validateExpressionArray(value, name) {
1249
1157
  if (!isMapLibreExpressionOperator(value[0])) {
1250
1158
  throw new Error(`Unknown MapLibre expression operator: ${value[0]}`);
1251
1159
  }
1252
- cloneJson3(value);
1160
+ cloneJson2(value);
1253
1161
  }
1254
- function cloneJson3(value) {
1162
+ function cloneJson2(value) {
1255
1163
  const serialized = JSON.stringify(value);
1256
1164
  if (serialized === void 0) {
1257
1165
  throw new Error("Tileflow style values must be JSON-serializable.");
@@ -1571,30 +1479,30 @@ function asRecord(value) {
1571
1479
 
1572
1480
  // src/cartography/merge.ts
1573
1481
  function mergeTileflowDesign(base, ...overlays) {
1574
- let resolved = cloneJson4(base);
1482
+ let resolved = cloneJson3(base);
1575
1483
  for (const overlay of overlays) {
1576
1484
  resolved = mergeValue(resolved, overlay);
1577
1485
  }
1578
1486
  return resolved;
1579
1487
  }
1580
1488
  function mergeValue(base, overlay) {
1581
- if (overlay === void 0) return cloneJson4(base);
1489
+ if (overlay === void 0) return cloneJson3(base);
1582
1490
  if (Array.isArray(overlay) || isTileflowExpression(overlay) || isTileflowThemeValueNode(overlay) || isTileflowZoomValue(overlay) || !isRecord2(overlay)) {
1583
- return cloneJson4(overlay);
1491
+ return cloneJson3(overlay);
1584
1492
  }
1585
1493
  const baseRecord = isRecord2(base) ? base : {};
1586
1494
  const result = {};
1587
1495
  for (const key of /* @__PURE__ */ new Set([...Object.keys(baseRecord), ...Object.keys(overlay)])) {
1588
1496
  const overlayValue = overlay[key];
1589
1497
  if (overlayValue === void 0) {
1590
- if (key in baseRecord) result[key] = cloneJson4(baseRecord[key]);
1498
+ if (key in baseRecord) result[key] = cloneJson3(baseRecord[key]);
1591
1499
  continue;
1592
1500
  }
1593
1501
  result[key] = mergeValue(baseRecord[key], overlayValue);
1594
1502
  }
1595
1503
  return result;
1596
1504
  }
1597
- function cloneJson4(value) {
1505
+ function cloneJson3(value) {
1598
1506
  if (value === void 0) return value;
1599
1507
  return JSON.parse(JSON.stringify(value));
1600
1508
  }
@@ -1602,6 +1510,402 @@ function isRecord2(value) {
1602
1510
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
1603
1511
  }
1604
1512
 
1513
+ // src/trees-3d.ts
1514
+ var tileflowWorldTreeFields = Object.freeze({
1515
+ height: "height",
1516
+ diameterCrown: "diameter_crown",
1517
+ circumference: "circumference",
1518
+ genus: "genus",
1519
+ species: "species",
1520
+ leafType: "leaf_type",
1521
+ taxon: "taxon",
1522
+ canopyRank: "rank",
1523
+ canopyClearance: "clearance_px_z15",
1524
+ canopyCover: "cover_pct",
1525
+ canopyHeight: "height_m"
1526
+ });
1527
+ var tileflowDefaultUnknownTreeForms = Object.freeze({
1528
+ round: 1,
1529
+ open: 1,
1530
+ tall: 1
1531
+ });
1532
+ var broadleaves = Object.freeze({
1533
+ ...Object.fromEntries(
1534
+ [
1535
+ "acacia",
1536
+ "albizia",
1537
+ "arbutus",
1538
+ "bursera",
1539
+ "ceratonia",
1540
+ "delonix",
1541
+ "ficus",
1542
+ "gleditsia",
1543
+ "jacaranda",
1544
+ "olea",
1545
+ "quercus",
1546
+ "robinia",
1547
+ "schinus",
1548
+ "sophora",
1549
+ "styphnolobium",
1550
+ "tamarix"
1551
+ ].map((genus) => [genus, "open"])
1552
+ ),
1553
+ ...Object.fromEntries(
1554
+ [
1555
+ "betula",
1556
+ "celtis",
1557
+ "fraxinus",
1558
+ "ginkgo",
1559
+ "liquidambar",
1560
+ "liriodendron",
1561
+ "magnolia",
1562
+ "platanus",
1563
+ "populus",
1564
+ "pyrus",
1565
+ "ulmus",
1566
+ "zelkova"
1567
+ ].map((genus) => [genus, "tall"])
1568
+ ),
1569
+ ...Object.fromEntries(
1570
+ [
1571
+ "acer",
1572
+ "aesculus",
1573
+ "carpinus",
1574
+ "castanea",
1575
+ "catalpa",
1576
+ "cercis",
1577
+ "citrus",
1578
+ "fagus",
1579
+ "lagerstroemia",
1580
+ "malus",
1581
+ "melia",
1582
+ "morus",
1583
+ "prunus",
1584
+ "sorbus",
1585
+ "tilia"
1586
+ ].map((genus) => [genus, "round"])
1587
+ )
1588
+ });
1589
+ var tileflowTreeModelIdPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
1590
+ var tileflowVegetationMetadataKey = "tileflow:vegetation";
1591
+ function tileflowVegetationMetadata(vegetation2) {
1592
+ return { version: 1, ...vegetation2 };
1593
+ }
1594
+
1595
+ // src/modules/vegetation/compiler.ts
1596
+ function compileVegetation(request, context) {
1597
+ const minZoom = request?.flat?.minZoom ?? 16;
1598
+ const mode = request?.mode ?? "3d";
1599
+ const config = mergeTileflowDesign(
1600
+ {
1601
+ type: "vegetation",
1602
+ enabled: true,
1603
+ flat: {
1604
+ color: context.colors.landcover.wood,
1605
+ minZoom,
1606
+ opacity: mode === "3d" ? 0.82 : 0.9,
1607
+ pitchAlignment: "map",
1608
+ pitchScale: "map",
1609
+ radius: minZoom < 24 ? zoom.linear([
1610
+ [minZoom, 2.2],
1611
+ [minZoom < 20 ? 20 : 24, 8]
1612
+ ]) : 2.2,
1613
+ strokeColor: context.colors.park,
1614
+ strokeOpacity: 0.55,
1615
+ strokeWidth: 0.8
1616
+ },
1617
+ mode,
1618
+ threeDimensional: {
1619
+ barkColor: context.colors.vegetation.tree.bark,
1620
+ broadleafColors: context.colors.vegetation.tree.broadleaf,
1621
+ coniferColors: context.colors.vegetation.tree.conifer,
1622
+ palmColors: context.colors.vegetation.tree.palm,
1623
+ crownScale: 1,
1624
+ heightScale: 1
1625
+ }
1626
+ },
1627
+ request
1628
+ );
1629
+ if (config.enabled === false || !context.data.schema.layers.tree) return [];
1630
+ const resolvedMode = config.mode ?? "3d";
1631
+ return [
1632
+ {
1633
+ kind: "layer",
1634
+ layer: applyCircleStyle(
1635
+ {
1636
+ id: "tileflow-vegetation-trees",
1637
+ type: "circle",
1638
+ source: context.data.sourceId,
1639
+ "source-layer": context.data.schema.layers.tree,
1640
+ // The layer also holds rows of trees as lines; a circle on each vertex would misdraw them.
1641
+ filter: ["==", ["geometry-type"], "Point"],
1642
+ metadata: {
1643
+ "tileflow:vegetation-mode": resolvedMode,
1644
+ ...resolvedMode === "3d" ? { "tileflow:vegetation-fallback": "flat-circle" } : {}
1645
+ }
1646
+ },
1647
+ config.flat ?? {}
1648
+ ),
1649
+ localOrder: 1,
1650
+ owner: "vegetation",
1651
+ slot: "vegetation",
1652
+ target: "vegetation.trees"
1653
+ }
1654
+ ];
1655
+ }
1656
+ function tileflowVegetationDescription(request, context) {
1657
+ const { schema } = context.data;
1658
+ if (request?.enabled === false || (request?.mode ?? "3d") !== "3d" || !schema.layers.tree)
1659
+ return void 0;
1660
+ const tree = context.colors.vegetation.tree;
1661
+ const style = request?.threeDimensional ?? {};
1662
+ const colours = (value, fallback) => Array.isArray(value) && value.length > 0 && value.every((entry) => typeof entry === "string") ? value : [...fallback];
1663
+ const number = (value) => {
1664
+ const resolved = value === void 0 ? void 0 : toMapLibreStyleValue(value);
1665
+ return typeof resolved === "number" && resolved > 0 ? resolved : 1;
1666
+ };
1667
+ const field2 = (name, fallback) => schema.fields[name] ?? fallback;
1668
+ return {
1669
+ source: context.data.sourceId,
1670
+ layers: {
1671
+ tree: schema.layers.tree,
1672
+ ...schema.layers.canopy ? { canopy: schema.layers.canopy } : {}
1673
+ },
1674
+ fields: {
1675
+ ...tileflowWorldTreeFields,
1676
+ height: field2("height", tileflowWorldTreeFields.height),
1677
+ diameterCrown: field2("diameterCrown", tileflowWorldTreeFields.diameterCrown),
1678
+ genus: field2("genus", tileflowWorldTreeFields.genus),
1679
+ species: field2("species", tileflowWorldTreeFields.species),
1680
+ leafType: field2("leafType", tileflowWorldTreeFields.leafType),
1681
+ canopyRank: field2("canopyRank", tileflowWorldTreeFields.canopyRank),
1682
+ canopyClearance: field2("canopyClearanceAtZoom15", tileflowWorldTreeFields.canopyClearance),
1683
+ canopyCover: field2("canopyCoverPercent", tileflowWorldTreeFields.canopyCover)
1684
+ },
1685
+ colors: {
1686
+ bark: typeof style.barkColor === "string" ? style.barkColor : tree.bark,
1687
+ broadleaf: colours(style.broadleafColors, tree.broadleaf),
1688
+ conifer: colours(style.coniferColors, tree.conifer),
1689
+ palm: colours(style.palmColors, tree.palm)
1690
+ },
1691
+ heightScale: number(style.heightScale),
1692
+ crownScale: number(style.crownScale),
1693
+ forms: Object.fromEntries(
1694
+ Object.entries(style.forms ?? {}).map(([form, own]) => [
1695
+ form,
1696
+ {
1697
+ heightScale: number(own?.heightScale),
1698
+ crownScale: number(own?.crownScale),
1699
+ ...own?.models?.length ? { models: [...own.models] } : {}
1700
+ }
1701
+ ])
1702
+ ),
1703
+ // Filled by the compiler from the map's prepared models.
1704
+ models: {},
1705
+ finish: style.finish ?? "faceted",
1706
+ edges: (() => {
1707
+ const edges = toMapLibreStyleValue(style.edges ?? 0);
1708
+ return typeof edges === "number" ? edges : 0;
1709
+ })(),
1710
+ ...(() => {
1711
+ const edgeColor = style.edgeColor === void 0 ? void 0 : toMapLibreStyleValue(style.edgeColor);
1712
+ return typeof edgeColor === "string" ? { edgeColor } : {};
1713
+ })(),
1714
+ unknownForms: style.unknownForms ?? tileflowDefaultUnknownTreeForms,
1715
+ regions: style.regions ?? [],
1716
+ // Filled by the compiler once the style's layers are final.
1717
+ flatLayers: []
1718
+ };
1719
+ }
1720
+
1721
+ // src/buildings-3d-design.ts
1722
+ var tileflowBuildingsMetadataKey = "tileflow:buildings";
1723
+ var tileflowDefaultBuildings3DDesign = Object.freeze({
1724
+ source: "tileflow",
1725
+ layers: Object.freeze({ building: "building", places: "poi" }),
1726
+ color: "#d8d0c0",
1727
+ edges: 1,
1728
+ nightLight: 1,
1729
+ windows: false,
1730
+ shadows: true,
1731
+ occlusion: true
1732
+ });
1733
+ function tileflowBuildingsMetadata(design) {
1734
+ return { version: 1, ...design };
1735
+ }
1736
+
1737
+ // src/street-lights.ts
1738
+ var tileflowStreetLightsMetadataKey = "tileflow:street-lights";
1739
+ var tileflowDefaultStreetLightsDesign = Object.freeze({
1740
+ source: "tileflow",
1741
+ layers: Object.freeze({
1742
+ streetFurniture: "street_furniture",
1743
+ transportation: "transportation",
1744
+ sidewalk: "sidewalk"
1745
+ }),
1746
+ estimate: "lit",
1747
+ color: "#ffcf8f",
1748
+ intensity: 1
1749
+ });
1750
+ function tileflowStreetLightsMetadata(design) {
1751
+ return { version: 1, ...design };
1752
+ }
1753
+
1754
+ // src/cartography/scene-3d-metadata.ts
1755
+ var colourSources = [
1756
+ ["tileflow-buildings-render-extrusion", "fill-extrusion-color"],
1757
+ ["tileflow-buildings-3d", "fill-extrusion-color"],
1758
+ ["tileflow-buildings-fill", "fill-color"]
1759
+ ];
1760
+ function tileflowBuildings3DDesignOf(module, context, layers) {
1761
+ const config = isRecord3(module) ? module : {};
1762
+ if (config.enabled === false) return void 0;
1763
+ const style = config.threeDimensional ?? {};
1764
+ let color2;
1765
+ for (const [id2, property] of colourSources) {
1766
+ const value = layers.find((layer) => layer.id === id2)?.paint?.[property];
1767
+ if (typeof value === "string" || Array.isArray(value)) {
1768
+ color2 = value;
1769
+ break;
1770
+ }
1771
+ }
1772
+ const number = (value, fallback) => {
1773
+ const resolved = value === void 0 ? void 0 : toMapLibreStyleValue(value);
1774
+ return typeof resolved === "number" ? resolved : fallback;
1775
+ };
1776
+ const edgeColor = style.edgeColor === void 0 ? void 0 : toMapLibreStyleValue(style.edgeColor);
1777
+ return {
1778
+ source: context.data.sourceId,
1779
+ layers: {
1780
+ building: context.data.schema.layers.building,
1781
+ ...context.data.schema.layers.poi ? { places: context.data.schema.layers.poi } : {}
1782
+ },
1783
+ color: color2 ?? context.colors.buildings.generic,
1784
+ edges: number(style.edges, 1),
1785
+ ...typeof edgeColor === "string" ? { edgeColor } : {},
1786
+ nightLight: number(style.nightLight, 1),
1787
+ windows: style.windows === true,
1788
+ shadows: style.shadows !== false,
1789
+ occlusion: style.occlusion !== false
1790
+ };
1791
+ }
1792
+ function tileflowStreetLightsDesignOf(module, context) {
1793
+ const config = isRecord3(module) ? module : {};
1794
+ const layers = context.data.schema.layers;
1795
+ const style = config.streetLights ?? {};
1796
+ if (config.enabled === false || style.enabled === false || !layers.streetFurniture)
1797
+ return void 0;
1798
+ const color2 = style.color === void 0 ? void 0 : toMapLibreStyleValue(style.color);
1799
+ const intensity = style.intensity === void 0 ? void 0 : toMapLibreStyleValue(style.intensity);
1800
+ const estimate = style.estimate;
1801
+ return {
1802
+ source: context.data.sourceId,
1803
+ layers: {
1804
+ streetFurniture: layers.streetFurniture,
1805
+ transportation: layers.road,
1806
+ ...layers.sidewalk ? { sidewalk: layers.sidewalk } : {}
1807
+ },
1808
+ estimate: estimate === "streets" || estimate === "none" || estimate === "lit" ? estimate : "lit",
1809
+ color: typeof color2 === "string" ? color2 : tileflowDefaultStreetLightsDesign.color,
1810
+ intensity: typeof intensity === "number" ? intensity : 1
1811
+ };
1812
+ }
1813
+ function isRecord3(value) {
1814
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1815
+ }
1816
+
1817
+ // src/cartography/contributions.ts
1818
+ var tileflowLayerSlots = [
1819
+ "background",
1820
+ "land",
1821
+ "terrain",
1822
+ "hydro",
1823
+ "building-areas",
1824
+ "transport-areas",
1825
+ "transport-tunnel-shadow",
1826
+ "transport-tunnel-casing",
1827
+ "transport-tunnel-fill",
1828
+ "transport-pedestrian-areas",
1829
+ "aeroways",
1830
+ "transport-surface-shadow",
1831
+ "transport-surface-casing",
1832
+ "transport-surface-fill",
1833
+ "transport-bridge-shadow",
1834
+ "transport-bridge-casing",
1835
+ "transport-bridge-fill",
1836
+ "transport-symbols",
1837
+ "boundaries",
1838
+ "buildings",
1839
+ "vegetation",
1840
+ "symbols"
1841
+ ];
1842
+ var tileflowLayerBands = [
1843
+ "land",
1844
+ "relief",
1845
+ "water",
1846
+ "roads",
1847
+ "boundaries",
1848
+ "buildings",
1849
+ "vegetation",
1850
+ "labels"
1851
+ ];
1852
+ var bandSlots = {
1853
+ land: ["background", "land"],
1854
+ relief: ["terrain", "terrain"],
1855
+ water: ["hydro", "hydro"],
1856
+ roads: ["transport-areas", "transport-symbols"],
1857
+ boundaries: ["boundaries", "boundaries"],
1858
+ buildings: ["buildings", "buildings"],
1859
+ vegetation: ["vegetation", "vegetation"],
1860
+ labels: ["symbols", "symbols"]
1861
+ };
1862
+ var tileflowBandPlacements = tileflowLayerBands.flatMap(
1863
+ (band) => [`below-${band}`, `above-${band}`]
1864
+ );
1865
+ function tileflowBandPlacementSlot(placement) {
1866
+ const [side, band] = splitBandPlacement(placement);
1867
+ return bandSlots[band][side === "below" ? 0 : 1];
1868
+ }
1869
+ function tileflowBandPlacementIndex(slotRanks, placement) {
1870
+ const [side, band] = splitBandPlacement(placement);
1871
+ const [first, last] = bandSlots[band];
1872
+ const threshold = side === "below" ? tileflowLayerSlots.indexOf(first) : tileflowLayerSlots.indexOf(last) + 1;
1873
+ const index = slotRanks.findIndex((rank) => rank !== void 0 && rank >= threshold);
1874
+ return index < 0 ? slotRanks.length : index;
1875
+ }
1876
+ function splitBandPlacement(placement) {
1877
+ const separator = placement.indexOf("-");
1878
+ return [
1879
+ placement.slice(0, separator),
1880
+ placement.slice(separator + 1)
1881
+ ];
1882
+ }
1883
+ var tileflowSemanticTargetPattern = /^[a-z][a-z0-9-]*(?:\.[A-Za-z0-9_-]+)*$/u;
1884
+ var tileflowRenderStackOperationNamePattern = /^[a-z][A-Za-z0-9_-]*$/u;
1885
+ var tileflowCompilerMetadataKeys = Object.freeze({
1886
+ owner: "tileflow:compiler-owner",
1887
+ slot: "tileflow:compiler-slot",
1888
+ target: "tileflow:compiler-target"
1889
+ });
1890
+
1891
+ // src/modules/addresses/index.ts
1892
+ function addresses(options = {}) {
1893
+ return { type: "addresses", ...cloneJson4(options) };
1894
+ }
1895
+ function cloneJson4(value) {
1896
+ return JSON.parse(JSON.stringify(value));
1897
+ }
1898
+
1899
+ // src/cartography/context.ts
1900
+ function typographyTextStyle(typography) {
1901
+ return {
1902
+ font: typography.font,
1903
+ ...typography.fallbacks ? { fallbacks: typography.fallbacks } : {},
1904
+ ...typography.letterSpacing === void 0 ? {} : { letterSpacing: typography.letterSpacing },
1905
+ ...typography.transform === void 0 ? {} : { transform: typography.transform }
1906
+ };
1907
+ }
1908
+
1605
1909
  // src/modules/addresses/compiler.ts
1606
1910
  function compileAddresses(request, context) {
1607
1911
  if (request?.enabled === false) return [];
@@ -4463,7 +4767,7 @@ function auditTileflowThemeValues(value, path = "value", context = inferAuditCon
4463
4767
  return diagnostics.sort(compareAuditDiagnostics);
4464
4768
  }
4465
4769
  function auditTileflowMapThemeValues(map) {
4466
- const modules = isRecord3(map.modules) ? map.modules : void 0;
4770
+ const modules = isRecord4(map.modules) ? map.modules : void 0;
4467
4771
  const moduleDiagnostics = modules ? Object.keys(modules).sort(compareCodeUnits2).flatMap(
4468
4772
  (owner) => auditTileflowThemeValues(modules[owner], `modules.${owner}`, {
4469
4773
  owner,
@@ -4566,7 +4870,7 @@ function isThemePrimitiveForCategory(value, category) {
4566
4870
  return category === "number" ? typeof value === "number" && Number.isFinite(value) || Array.isArray(value) && value.every((entry) => typeof entry === "number" && Number.isFinite(entry)) : typeof value === "string" && value.length > 0;
4567
4871
  }
4568
4872
  function isThemeValueCandidate(value) {
4569
- return isRecord3(value) && (value.kind === "theme-token" || value.kind === "theme-fixed" || value.kind === "theme-color");
4873
+ return isRecord4(value) && (value.kind === "theme-token" || value.kind === "theme-fixed" || value.kind === "theme-color");
4570
4874
  }
4571
4875
  function visitAuditableValue(value, path, category, state, intentRequired = false) {
4572
4876
  if (isThemeStructuralPath(path)) category = void 0;
@@ -4582,7 +4886,7 @@ function visitAuditableValue(value, path, category, state, intentRequired = fals
4582
4886
  visitAuditableArray(value, path, category, state, false, intentRequired);
4583
4887
  return;
4584
4888
  }
4585
- if (!isRecord3(value)) return;
4889
+ if (!isRecord4(value)) return;
4586
4890
  if (value.kind === "expression" && Array.isArray(value.value)) {
4587
4891
  visitAuditableArray(value.value, `${path}.value`, category, state, true, intentRequired);
4588
4892
  return;
@@ -4897,7 +5201,7 @@ var ThemeValueResolver = class {
4897
5201
  if (Array.isArray(value)) {
4898
5202
  return value.map((entry, index) => this.resolveTree(entry, `${path}[${index}]`, category));
4899
5203
  }
4900
- if (!isRecord3(value)) return value;
5204
+ if (!isRecord4(value)) return value;
4901
5205
  if (value.kind === "expression" && Array.isArray(value.value)) {
4902
5206
  return {
4903
5207
  ...value,
@@ -5209,7 +5513,7 @@ function assertThemeLighting(value, path) {
5209
5513
  }
5210
5514
  }
5211
5515
  function assertThemeCategoryValue(value, expectedCategory, path) {
5212
- if (!isRecord3(value)) {
5516
+ if (!isRecord4(value)) {
5213
5517
  assertTokenPrimitive(expectedCategory, value, path);
5214
5518
  return;
5215
5519
  }
@@ -5561,11 +5865,11 @@ function cloneJson14(value) {
5561
5865
  if (serialized === void 0) throw new Error("Tileflow themes must be JSON-serializable.");
5562
5866
  return JSON.parse(serialized);
5563
5867
  }
5564
- function isRecord3(value) {
5868
+ function isRecord4(value) {
5565
5869
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
5566
5870
  }
5567
5871
  function isPlainRecord(value) {
5568
- if (!isRecord3(value)) return false;
5872
+ if (!isRecord4(value)) return false;
5569
5873
  const prototype = Object.getPrototypeOf(value);
5570
5874
  return prototype === Object.prototype || prototype === null;
5571
5875
  }
@@ -5757,7 +6061,8 @@ function resolveColors(overrides = {}, defaults4 = defaultColors, moduleOverride
5757
6061
  resolved.landcover.grass,
5758
6062
  resolved.landcover.meadow
5759
6063
  ],
5760
- conifer: [resolved.landcover.protected, resolved.landcover.scrub, resolved.landcover.wood]
6064
+ conifer: [resolved.landcover.protected, resolved.landcover.scrub, resolved.landcover.wood],
6065
+ palm: [resolved.landcover.urbanPark, resolved.landcover.grass, resolved.landcover.meadow]
5761
6066
  }
5762
6067
  }
5763
6068
  };
@@ -5924,6 +6229,11 @@ function resolveThemeColors(theme) {
5924
6229
  optionalColor("vegetation.tree.conifer.a", colors.vegetation.tree.conifer[0]),
5925
6230
  optionalColor("vegetation.tree.conifer.b", colors.vegetation.tree.conifer[1]),
5926
6231
  optionalColor("vegetation.tree.conifer.c", colors.vegetation.tree.conifer[2])
6232
+ ],
6233
+ palm: [
6234
+ optionalColor("vegetation.tree.palm.a", colors.vegetation.tree.palm[0]),
6235
+ optionalColor("vegetation.tree.palm.b", colors.vegetation.tree.palm[1]),
6236
+ optionalColor("vegetation.tree.palm.c", colors.vegetation.tree.palm[2])
5927
6237
  ]
5928
6238
  }
5929
6239
  }
@@ -7272,76 +7582,6 @@ function cloneJson16(value) {
7272
7582
  return JSON.parse(JSON.stringify(value));
7273
7583
  }
7274
7584
 
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;
7311
- 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
- }
7342
- ];
7343
- }
7344
-
7345
7585
  // src/modules/water/index.ts
7346
7586
  function water(options = {}) {
7347
7587
  return { type: "water", ...cloneJson17(options) };
@@ -9054,21 +9294,21 @@ function bindSemanticValue(value, schema, sourceId) {
9054
9294
  }
9055
9295
  if (isDataSourceReference(value)) return sourceId;
9056
9296
  if (Array.isArray(value)) return value.map((item) => bindSemanticValue(item, schema, sourceId));
9057
- if (!isRecord4(value)) return value;
9297
+ if (!isRecord5(value)) return value;
9058
9298
  return Object.fromEntries(
9059
9299
  Object.entries(value).map(([key, item]) => [key, bindSemanticValue(item, schema, sourceId)])
9060
9300
  );
9061
9301
  }
9062
9302
  function isDataFieldReference2(value) {
9063
- return isRecord4(value) && value.kind === "tileflow-data-field" && typeof value.name === "string";
9303
+ return isRecord5(value) && value.kind === "tileflow-data-field" && typeof value.name === "string";
9064
9304
  }
9065
9305
  function isDataLayerReference(value) {
9066
- return isRecord4(value) && value.kind === "tileflow-data-layer" && typeof value.name === "string";
9306
+ return isRecord5(value) && value.kind === "tileflow-data-layer" && typeof value.name === "string";
9067
9307
  }
9068
9308
  function isDataSourceReference(value) {
9069
- return isRecord4(value) && value.kind === "tileflow-data-source" && value.name === "primary";
9309
+ return isRecord5(value) && value.kind === "tileflow-data-source" && value.name === "primary";
9070
9310
  }
9071
- function isRecord4(value) {
9311
+ function isRecord5(value) {
9072
9312
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
9073
9313
  }
9074
9314
  function compareCodeUnits5(left, right) {
@@ -9172,8 +9412,8 @@ function createTileflowLayerTemplateIR(layer, semanticKey) {
9172
9412
  renderer: type,
9173
9413
  ...filter === void 0 ? {} : { selector: cloneJson20(filter) },
9174
9414
  style: Object.freeze({
9175
- ...isRecord5(layout) ? { placement: Object.freeze(cloneJson20(layout)) } : {},
9176
- ...isRecord5(paint) ? { appearance: Object.freeze(cloneJson20(paint)) } : {}
9415
+ ...isRecord6(layout) ? { placement: Object.freeze(cloneJson20(layout)) } : {},
9416
+ ...isRecord6(paint) ? { appearance: Object.freeze(cloneJson20(paint)) } : {}
9177
9417
  })
9178
9418
  });
9179
9419
  }
@@ -9413,21 +9653,21 @@ function visitReferences(value, counts, visited = /* @__PURE__ */ new WeakSet())
9413
9653
  }
9414
9654
  }
9415
9655
  function isSemanticFieldReference(value) {
9416
- return isRecord5(value) && value.kind === "tileflow-data-field";
9656
+ return isRecord6(value) && value.kind === "tileflow-data-field";
9417
9657
  }
9418
9658
  function isSemanticLayerReference(value) {
9419
- return isRecord5(value) && value.kind === "tileflow-data-layer";
9659
+ return isRecord6(value) && value.kind === "tileflow-data-layer";
9420
9660
  }
9421
9661
  function isSemanticSourceReference(value) {
9422
- return isRecord5(value) && value.kind === "tileflow-data-source";
9662
+ return isRecord6(value) && value.kind === "tileflow-data-source";
9423
9663
  }
9424
9664
  function cloneJson20(value) {
9425
9665
  return JSON.parse(JSON.stringify(value));
9426
9666
  }
9427
9667
  function asRecord3(value) {
9428
- return isRecord5(value) ? value : {};
9668
+ return isRecord6(value) ? value : {};
9429
9669
  }
9430
- function isRecord5(value) {
9670
+ function isRecord6(value) {
9431
9671
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
9432
9672
  }
9433
9673
 
@@ -9479,7 +9719,7 @@ function validateTileflowRenderSelectorConstraints(input) {
9479
9719
  };
9480
9720
  while (pending.length > 0) {
9481
9721
  const current = pending.pop();
9482
- if (!isRecord6(current.selector) || typeof current.selector.kind !== "string") {
9722
+ if (!isRecord7(current.selector) || typeof current.selector.kind !== "string") {
9483
9723
  addIssue(
9484
9724
  "invalid-selector",
9485
9725
  "a render selector must be a semantic selector node",
@@ -9540,7 +9780,7 @@ function validateTileflowRenderSelectorConstraints(input) {
9540
9780
  }
9541
9781
  for (const [index, branch] of current.selector.branches.entries()) {
9542
9782
  const branchPath = [...branchesPath, index];
9543
- if (!isRecord6(branch)) {
9783
+ if (!isRecord7(branch)) {
9544
9784
  addIssue("invalid-selector", "match selector branch must be an object", branchPath);
9545
9785
  continue;
9546
9786
  }
@@ -9571,14 +9811,14 @@ function validateTileflowRenderSelectorConstraints(input) {
9571
9811
  for (let index = current.selector.stops.length - 1; index >= 0; index -= 1) {
9572
9812
  const stop = current.selector.stops[index];
9573
9813
  const stopPath = [...stopsPath, index];
9574
- if (!isRecord6(stop)) {
9814
+ if (!isRecord7(stop)) {
9575
9815
  addIssue("invalid-selector", "step selector stop must be an object", stopPath);
9576
9816
  continue;
9577
9817
  }
9578
9818
  pushChild(stop.selector, current.depth + 1, [...stopPath, "selector"]);
9579
9819
  }
9580
9820
  for (const [index, stop] of current.selector.stops.entries()) {
9581
- if (!isRecord6(stop)) continue;
9821
+ if (!isRecord7(stop)) continue;
9582
9822
  const zoom3 = stop.zoom;
9583
9823
  const zoomPath = [...stopsPath, index, "zoom"];
9584
9824
  if (typeof zoom3 !== "number" || !Number.isFinite(zoom3) || zoom3 < 0 || zoom3 > 24) {
@@ -9903,7 +10143,7 @@ function applyRendererStyle(layer, renderer, style) {
9903
10143
  };
9904
10144
  }
9905
10145
  function lowerSelector(selector, data) {
9906
- if (!isRecord6(selector) || typeof selector.kind !== "string") {
10146
+ if (!isRecord7(selector) || typeof selector.kind !== "string") {
9907
10147
  throw renderStackError(
9908
10148
  "invalid-selector",
9909
10149
  "A render selector must be a semantic selector node."
@@ -10106,7 +10346,7 @@ function requireRenderer(value) {
10106
10346
  function requirePlacement(value) {
10107
10347
  if (typeof value === "string") {
10108
10348
  if (tileflowBandPlacements.includes(value)) return;
10109
- } else if (isRecord6(value)) {
10349
+ } else if (isRecord7(value)) {
10110
10350
  const keys = Object.keys(value);
10111
10351
  if (keys.length === 1 && (keys[0] === "above" || keys[0] === "below")) {
10112
10352
  requirePortableTarget(value[keys[0]], "render placement target");
@@ -10133,7 +10373,7 @@ function validateRefinementShape(operation) {
10133
10373
  validateStyle(operation.renderer, operation.style);
10134
10374
  }
10135
10375
  function validateStyle(renderer, style) {
10136
- if (!isRecord6(style)) {
10376
+ if (!isRecord7(style)) {
10137
10377
  throw renderStackError(
10138
10378
  "invalid-style",
10139
10379
  `A ${renderer} render operation requires a style object.`
@@ -10235,7 +10475,7 @@ function normalizeRequirements(requirements) {
10235
10475
  return [...requirements];
10236
10476
  }
10237
10477
  function requireRenderStackEntries(stack) {
10238
- if (!isRecord6(stack)) {
10478
+ if (!isRecord7(stack)) {
10239
10479
  throw renderStackError("invalid-stack", "A render stack must be a named operation record.");
10240
10480
  }
10241
10481
  const entries = Object.entries(stack);
@@ -10382,9 +10622,9 @@ function cloneJson21(value) {
10382
10622
  return JSON.parse(JSON.stringify(value));
10383
10623
  }
10384
10624
  function asRecord4(value) {
10385
- return isRecord6(value) ? value : {};
10625
+ return isRecord7(value) ? value : {};
10386
10626
  }
10387
- function isRecord6(value) {
10627
+ function isRecord7(value) {
10388
10628
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
10389
10629
  }
10390
10630
 
@@ -11408,6 +11648,17 @@ var waterModuleSchema = z5.object({
11408
11648
  stream: lineStyleSchema.optional()
11409
11649
  }).strict().optional()
11410
11650
  }).strict();
11651
+ function boundedThemeNumberSchema(min, max) {
11652
+ return z5.union([
11653
+ z5.number().finite().min(min).max(max),
11654
+ themeNumberReferenceSchema,
11655
+ z5.object({
11656
+ kind: z5.literal("theme-fixed"),
11657
+ reason: z5.string().trim().min(1),
11658
+ value: z5.number().finite().min(min).max(max)
11659
+ }).strict()
11660
+ ]);
11661
+ }
11411
11662
  var buildingsModuleSchema = z5.object({
11412
11663
  type: z5.literal("buildings"),
11413
11664
  businessCorridor: areaStyleSchema.optional(),
@@ -11415,7 +11666,15 @@ var buildingsModuleSchema = z5.object({
11415
11666
  extrusion: extrusionStyleSchema.optional(),
11416
11667
  flat: areaStyleSchema.optional(),
11417
11668
  mode: z5.enum(["3d", "flat"]).optional(),
11418
- renderStack: renderStackSchema.optional()
11669
+ renderStack: renderStackSchema.optional(),
11670
+ threeDimensional: z5.object({
11671
+ edges: boundedThemeNumberSchema(0, 1).optional(),
11672
+ edgeColor: themeColorValueSchema.optional(),
11673
+ nightLight: boundedThemeNumberSchema(0, 2).optional(),
11674
+ occlusion: z5.boolean().optional(),
11675
+ shadows: z5.boolean().optional(),
11676
+ windows: z5.boolean().optional()
11677
+ }).strict().optional()
11419
11678
  }).strict();
11420
11679
  var addressesModuleSchema = z5.object({
11421
11680
  type: z5.literal("addresses"),
@@ -11528,6 +11787,12 @@ var roadsModuleSchema = z5.object({
11528
11787
  pattern: fillStyleSchema.optional(),
11529
11788
  surface: fillStyleSchema.optional()
11530
11789
  }).strict().optional(),
11790
+ streetLights: z5.object({
11791
+ color: themeColorValueSchema.optional(),
11792
+ enabled: z5.boolean().optional(),
11793
+ estimate: z5.enum(["lit", "streets", "none"]).optional(),
11794
+ intensity: boundedThemeNumberSchema(0, 2).optional()
11795
+ }).strict().optional(),
11531
11796
  structures: roadStructureMapSchema.optional(),
11532
11797
  weight: z5.enum(["thin", "regular", "bold"]).optional(),
11533
11798
  widthScale: z5.partialRecord(roadClassSchema, themeNumberValueSchema).optional()
@@ -11616,6 +11881,16 @@ var nauticalModuleSchema = z5.object({
11616
11881
  wreckAreas: areaStyleSchema.optional(),
11617
11882
  wrecks: markerSymbolStyleSchema.optional()
11618
11883
  }).strict();
11884
+ var treeFormMixSchema = z5.object({
11885
+ round: z5.number().finite().min(0).optional(),
11886
+ open: z5.number().finite().min(0).optional(),
11887
+ tall: z5.number().finite().min(0).optional(),
11888
+ conifer: z5.number().finite().min(0).optional(),
11889
+ columnar: z5.number().finite().min(0).optional(),
11890
+ palm: z5.number().finite().min(0).optional()
11891
+ }).strict().refine((mix2) => Object.values(mix2).some((weight) => (weight ?? 0) > 0), {
11892
+ message: "Expected at least one tree form with a weight above 0"
11893
+ });
11619
11894
  var modulesSchema = z5.object({
11620
11895
  addresses: addressesModuleSchema.optional(),
11621
11896
  aeroways: aerowaysModuleSchema.optional(),
@@ -11638,6 +11913,39 @@ var modulesSchema = z5.object({
11638
11913
  barkColor: themeColorValueSchema.optional(),
11639
11914
  broadleafColors: z5.array(themeColorValueSchema).min(1).max(8).optional(),
11640
11915
  coniferColors: z5.array(themeColorValueSchema).min(1).max(8).optional(),
11916
+ palmColors: z5.array(themeColorValueSchema).min(1).max(8).optional(),
11917
+ finish: z5.enum(["faceted", "smooth", "toon"]).optional(),
11918
+ edges: boundedThemeNumberSchema(0, 1).optional(),
11919
+ edgeColor: themeColorValueSchema.optional(),
11920
+ forms: z5.partialRecord(
11921
+ z5.enum(["round", "open", "tall", "conifer", "columnar", "palm"]),
11922
+ z5.object({
11923
+ crownScale: boundedThemeNumberSchema(0.1, 10).optional(),
11924
+ heightScale: boundedThemeNumberSchema(0.1, 10).optional(),
11925
+ models: z5.array(
11926
+ z5.string().max(64).regex(
11927
+ tileflowTreeModelIdPattern,
11928
+ "Expected a model id: its .glb file name in lowercase letters, digits and hyphens"
11929
+ )
11930
+ ).min(1).max(4).refine((ids) => new Set(ids).size === ids.length, {
11931
+ message: "Expected each model once"
11932
+ }).optional()
11933
+ }).strict()
11934
+ ).optional(),
11935
+ unknownForms: treeFormMixSchema.optional(),
11936
+ regions: z5.array(
11937
+ z5.object({
11938
+ bounds: z5.tuple([
11939
+ z5.number().finite().min(-180).max(180),
11940
+ z5.number().finite().min(-90).max(90),
11941
+ z5.number().finite().min(-180).max(180),
11942
+ z5.number().finite().min(-90).max(90)
11943
+ ]).refine(([, south, , north]) => south <= north, {
11944
+ message: "Expected bounds as [west, south, east, north] with south below north"
11945
+ }),
11946
+ unknownForms: treeFormMixSchema
11947
+ }).strict()
11948
+ ).max(16).optional(),
11641
11949
  crownScale: z5.union([
11642
11950
  z5.number().finite().positive().max(10),
11643
11951
  themeNumberReferenceSchema,
@@ -11970,6 +12278,7 @@ var iconDirectoriesSchema = z5.array(
11970
12278
  }
11971
12279
  });
11972
12280
  var fontDirectoriesSchema = z5.array(assetDirectorySchema).max(16);
12281
+ var modelDirectoriesSchema = z5.array(assetDirectorySchema).max(16);
11973
12282
  var fontStacksSchema = z5.array(
11974
12283
  z5.string().min(1).max(200).refine(
11975
12284
  (value) => value === value.trim() && value === value.normalize("NFC") && !/[\p{Cc}\\/]/u.test(value),
@@ -12191,6 +12500,7 @@ var resolvedTileflowMapSchema = z5.object({
12191
12500
  icons: iconDirectoriesSchema.optional(),
12192
12501
  id: tileflowMapIdSchema,
12193
12502
  marine: marineSchema.optional(),
12503
+ models: modelDirectoriesSchema.optional(),
12194
12504
  modules: modulesSchema.optional(),
12195
12505
  name: z5.string().trim().min(1),
12196
12506
  overlays: overlaysSchema.optional(),
@@ -13099,11 +13409,11 @@ function isTileflowPoiCategory(value) {
13099
13409
  return typeof value === "string" && tileflowPoiCategories.includes(value);
13100
13410
  }
13101
13411
  function semanticTarget(layer) {
13102
- if (!isRecord7(layer.metadata)) return void 0;
13412
+ if (!isRecord8(layer.metadata)) return void 0;
13103
13413
  const value = layer.metadata[tileflowCompilerMetadataKeys.target];
13104
13414
  return typeof value === "string" ? value : void 0;
13105
13415
  }
13106
- function isRecord7(value) {
13416
+ function isRecord8(value) {
13107
13417
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
13108
13418
  }
13109
13419
  function isSafeArtifactName(value) {
@@ -14127,8 +14437,19 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
14127
14437
  "domains",
14128
14438
  () => compileSemanticDomains(modules, context)
14129
14439
  );
14440
+ const vegetation3D = runCompilationPhase(
14441
+ "domains",
14442
+ () => withPreparedTreeModels(
14443
+ tileflowVegetationDescription(
14444
+ modules.vegetation,
14445
+ context
14446
+ ),
14447
+ config,
14448
+ options.preparedAssets
14449
+ )
14450
+ );
14130
14451
  const renderStackModules = Object.values(modules).filter(
14131
- (module) => isRecord8(module) && module.enabled !== false && "renderStack" in module && isRecord8(module.renderStack)
14452
+ (module) => isRecord9(module) && module.enabled !== false && "renderStack" in module && isRecord9(module.renderStack)
14132
14453
  );
14133
14454
  const renderOperations = runCompilationPhase(
14134
14455
  "render-stack",
@@ -14288,6 +14609,15 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
14288
14609
  () => createTileflowOverlayPlacementManifest(plannedLayers)
14289
14610
  );
14290
14611
  const layers = runCompilationPhase("finalization", () => finalizeTileflowLayers(plannedLayers));
14612
+ const buildings3D = tileflowBuildings3DDesignOf(
14613
+ modules.buildings,
14614
+ context,
14615
+ layers
14616
+ );
14617
+ const streetLights = tileflowStreetLightsDesignOf(
14618
+ modules.roads,
14619
+ context
14620
+ );
14291
14621
  runCompilationPhase(
14292
14622
  "finalization",
14293
14623
  () => assertTileflowInteractionManifestLayers(interactionManifest, layers)
@@ -14360,13 +14690,33 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
14360
14690
  "tileflow:colorScheme": resolvedTheme.colorScheme,
14361
14691
  [tileflowRouteMetadataKey]: tileflowRouteMetadata(resolveTileflowRouteInks(resolvedTheme)),
14362
14692
  ...resolvedTheme.sun ? { [tileflowSunMetadataKey]: tileflowSunMetadata(resolvedTheme.sun) } : {},
14693
+ ...buildings3D ? {
14694
+ [tileflowBuildingsMetadataKey]: tileflowBuildingsMetadata(
14695
+ bindSemanticReferences(buildings3D, semanticData)
14696
+ )
14697
+ } : {},
14698
+ ...streetLights ? {
14699
+ [tileflowStreetLightsMetadataKey]: tileflowStreetLightsMetadata(
14700
+ bindSemanticReferences(streetLights, semanticData)
14701
+ )
14702
+ } : {},
14703
+ ...vegetation3D ? {
14704
+ [tileflowVegetationMetadataKey]: tileflowVegetationMetadata({
14705
+ ...bindSemanticReferences(vegetation3D, semanticData),
14706
+ // The layers that draw the trees flat: the module's own and its render passes.
14707
+ flatLayers: layers.filter((layer) => {
14708
+ const metadata = layer.metadata;
14709
+ return metadata?.["tileflow:vegetation-mode"] !== void 0 || metadata?.["tileflow:domain"] === "vegetation";
14710
+ }).map((layer) => String(layer.id))
14711
+ })
14712
+ } : {},
14363
14713
  "tileflow:data": data.identity,
14364
14714
  "tileflow:sources": sourceIdentities,
14365
14715
  ...Object.keys(loweredOverlays.identities).length > 0 ? { "tileflow:overlays": loweredOverlays.identities } : {},
14366
14716
  "tileflow:sourceRequirements": sourceRequirements,
14367
14717
  ...interactionManifest ? { [tileflowInteractionManifestMetadataKey]: interactionManifest } : {},
14368
14718
  [tileflowOverlayPlacementManifestMetadataKey]: overlayPlacementManifest,
14369
- "tileflow:modules": Object.entries(modules).filter(([, module]) => !isRecord8(module) || module.enabled !== false).map(([name]) => name).sort(),
14719
+ "tileflow:modules": Object.entries(modules).filter(([, module]) => !isRecord9(module) || module.enabled !== false).map(([name]) => name).sort(),
14370
14720
  ...config.view ? { "tileflow:view": config.view } : {}
14371
14721
  }
14372
14722
  };
@@ -14418,7 +14768,7 @@ function finalizeTileflowLayers(input) {
14418
14768
  if (typeof minimum2 === "number" && typeof maximum2 === "number" && minimum2 > maximum2) {
14419
14769
  throw new Error(`Compiled Tileflow layer ${id2} requires minzoom <= maxzoom.`);
14420
14770
  }
14421
- const metadata = isRecord8(layer.metadata) ? { ...layer.metadata } : void 0;
14771
+ const metadata = isRecord9(layer.metadata) ? { ...layer.metadata } : void 0;
14422
14772
  if (metadata) {
14423
14773
  const owner = metadata[tileflowCompilerMetadataKeys.owner];
14424
14774
  delete metadata[tileflowCompilerProvenanceMetadataKey];
@@ -14452,13 +14802,32 @@ function assertMapLibreStyle(style, mapId) {
14452
14802
  }
14453
14803
  );
14454
14804
  }
14455
- function isRecord8(value) {
14805
+ function isRecord9(value) {
14456
14806
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
14457
14807
  }
14458
14808
  function resolveGlyphs(config) {
14459
14809
  if (config.fonts !== void 0) return void 0;
14460
14810
  return config.glyphs?.url;
14461
14811
  }
14812
+ function withPreparedTreeModels(vegetation2, config, prepared) {
14813
+ if (!vegetation2) return void 0;
14814
+ const referenced = [
14815
+ ...new Set(Object.values(vegetation2.forms).flatMap((form) => form?.models ?? []))
14816
+ ].sort();
14817
+ if (referenced.length === 0) return vegetation2;
14818
+ const available = prepared?.models ?? {};
14819
+ const missing = referenced.filter((id2) => !Object.hasOwn(available, id2));
14820
+ if (missing.length > 0) {
14821
+ 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.";
14822
+ throw new Error(
14823
+ `Tileflow map "${config.id}" draws missing 3D models: ${missing.join(", ")}. ${authoring}`
14824
+ );
14825
+ }
14826
+ return {
14827
+ ...vegetation2,
14828
+ models: Object.fromEntries(referenced.map((id2) => [id2, available[id2]]))
14829
+ };
14830
+ }
14462
14831
  function assertPreparedIconReferences(style, config, prepared) {
14463
14832
  const references = collectStyleImageReferences(style);
14464
14833
  if (references.size === 0) return;
@@ -14473,8 +14842,8 @@ function assertPreparedIconReferences(style, config, prepared) {
14473
14842
  function collectStyleImageReferences(style) {
14474
14843
  const result = /* @__PURE__ */ new Set();
14475
14844
  for (const layer of style.layers) {
14476
- const layout = isRecord8(layer.layout) ? layer.layout : {};
14477
- const paint = isRecord8(layer.paint) ? layer.paint : {};
14845
+ const layout = isRecord9(layer.layout) ? layer.layout : {};
14846
+ const paint = isRecord9(layer.paint) ? layer.paint : {};
14478
14847
  const layerId = typeof layer.id === "string" ? layer.id : "<unknown>";
14479
14848
  for (const [property, value] of [
14480
14849
  ["icon-image", layout["icon-image"]],
@@ -14563,7 +14932,7 @@ function assertGlyphFontStacks(style, config) {
14563
14932
  const declared = new Set(config.glyphs.fontStacks);
14564
14933
  const missing = /* @__PURE__ */ new Set();
14565
14934
  for (const layer of style.layers) {
14566
- const layout = isRecord8(layer.layout) ? layer.layout : void 0;
14935
+ const layout = isRecord9(layer.layout) ? layer.layout : void 0;
14567
14936
  const font = layout?.["text-font"];
14568
14937
  if (!Array.isArray(font) || !font.every((entry) => typeof entry === "string")) continue;
14569
14938
  const stack = font.join(",");
@@ -14579,7 +14948,7 @@ function assertGlyphFontStacks(style, config) {
14579
14948
  function assertTextAssets(style, config) {
14580
14949
  const textLayers = [];
14581
14950
  for (const layer of style.layers) {
14582
- const layout = isRecord8(layer.layout) ? layer.layout : void 0;
14951
+ const layout = isRecord9(layer.layout) ? layer.layout : void 0;
14583
14952
  if (layout?.["text-field"] === void 0) continue;
14584
14953
  textLayers.push(typeof layer.id === "string" ? layer.id : "<unknown>");
14585
14954
  const font = layout["text-font"];
@@ -14592,7 +14961,7 @@ function assertTextAssets(style, config) {
14592
14961
  if (textLayers.length === 0) return;
14593
14962
  if (config.fonts !== void 0) {
14594
14963
  const backed = style.layers.find((layer) => {
14595
- const font = isRecord8(layer.layout) ? layer.layout["text-font"] : void 0;
14964
+ const font = isRecord9(layer.layout) ? layer.layout["text-font"] : void 0;
14596
14965
  return Array.isArray(font) && font.length === 1 && typeof font[0] === "string" && parseTileflowLineBackgroundFontStack(font[0]) !== void 0;
14597
14966
  });
14598
14967
  if (backed) {
@@ -14618,7 +14987,7 @@ function runCompilationPhase(phase, operation) {
14618
14987
  }
14619
14988
  }
14620
14989
  function withCompilationPhase(error, phase) {
14621
- const record3 = isRecord8(error) ? error : {};
14990
+ const record3 = isRecord9(error) ? error : {};
14622
14991
  if (typeof record3.phase === "string") return error;
14623
14992
  if (error instanceof Error && Object.isExtensible(error)) {
14624
14993
  Object.defineProperty(error, "phase", { configurable: true, enumerable: true, value: phase });
@@ -15178,6 +15547,7 @@ var tileflowMapMergeStrategies = {
15178
15547
  icons: "icons",
15179
15548
  id: "identity",
15180
15549
  marine: "atomic",
15550
+ models: "atomic",
15181
15551
  modules: "modules",
15182
15552
  name: "identity",
15183
15553
  overlays: "keyed-resources",
@@ -15523,11 +15893,11 @@ function cloneDesign(value) {
15523
15893
  function describeMap(value) {
15524
15894
  return typeof value.id === "string" && value.id ? value.id : "<anonymous>";
15525
15895
  }
15526
- function isRecord9(value) {
15896
+ function isRecord10(value) {
15527
15897
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
15528
15898
  }
15529
15899
  function isPlainRecord4(value) {
15530
- if (!isRecord9(value)) return false;
15900
+ if (!isRecord10(value)) return false;
15531
15901
  const prototype = Object.getPrototypeOf(value);
15532
15902
  return prototype === Object.prototype || prototype === null;
15533
15903
  }
@@ -16045,6 +16415,7 @@ async function hashTileflowMapRevision(input, sourceAssets) {
16045
16415
  const {
16046
16416
  fonts: _fonts,
16047
16417
  icons: _icons,
16418
+ models: _models,
16048
16419
  id: _id,
16049
16420
  name: _name,
16050
16421
  version: _version,
@@ -16140,10 +16511,26 @@ function normalizeSourceAssets(input) {
16140
16511
  if (new Set(fonts.map(fontIdentity)).size !== fonts.length) {
16141
16512
  throw new Error("Effective Tileflow font source identities must have unique faces.");
16142
16513
  }
16514
+ const models = [...input.models ?? []].map((model) => {
16515
+ assertSha256(model.sha256, `model ${model.id}`);
16516
+ if (model.level !== "near" && model.level !== "far")
16517
+ throw new Error(`Tileflow model ${model.id} is near or far.`);
16518
+ return { id: model.id, level: model.level, sha256: model.sha256 };
16519
+ }).sort(
16520
+ (left, right) => compareCodeUnits8(`${left.id}\0${left.level}`, `${right.id}\0${right.level}`)
16521
+ );
16522
+ if (new Set(models.map((model) => `${model.id}\0${model.level}`)).size !== models.length) {
16523
+ throw new Error("Effective Tileflow model source identities must be unique.");
16524
+ }
16143
16525
  const iconComposition = input.iconComposition === void 0 ? void 0 : parseTileflowIconComposition(input.iconComposition);
16144
16526
  if (icons.some((icon) => icon.kind === "rendered-icon") && !iconComposition)
16145
16527
  throw new Error("Rendered icon identities require an explicit composition receipt");
16146
- return { fonts, icons, ...iconComposition ? { iconComposition } : {} };
16528
+ return {
16529
+ fonts,
16530
+ icons,
16531
+ ...iconComposition ? { iconComposition } : {},
16532
+ ...models.length > 0 ? { models } : {}
16533
+ };
16147
16534
  }
16148
16535
  function fontIdentity(font) {
16149
16536
  return `${font.family}\0${font.style}\0${font.weight}`;