@tileflow/core 0.1.0-alpha.32 → 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 [];
@@ -4211,6 +4515,38 @@ function isTileflowThemeName(value) {
4211
4515
  return isTileflowPortableId(value) && value !== "system";
4212
4516
  }
4213
4517
 
4518
+ // src/sun.ts
4519
+ var tileflowSunMetadataKey = "tileflow:sun";
4520
+ var tileflowDefaultSunPreset = Object.freeze({
4521
+ azimuth: 210,
4522
+ altitude: 60,
4523
+ color: "#ffffff",
4524
+ intensity: 0.5,
4525
+ shadowIntensity: 1,
4526
+ ambient: Object.freeze({ color: "#ffffff", intensity: 0.5 })
4527
+ });
4528
+ var degrees = Math.PI / 180;
4529
+ function normalizeTileflowAzimuth(azimuth) {
4530
+ const turned = azimuth % 360;
4531
+ return turned < 0 ? turned + 360 : turned === 0 ? 0 : turned;
4532
+ }
4533
+ function tileflowSunMetadata(preset) {
4534
+ return Object.freeze({ version: 1, ...preset });
4535
+ }
4536
+ function tileflowSunLight(direction, base) {
4537
+ const radial = base?.position?.[0] ?? 1.15;
4538
+ return {
4539
+ ...base ?? {},
4540
+ anchor: "map",
4541
+ position: [
4542
+ radial,
4543
+ Number(normalizeTileflowAzimuth(direction.azimuth).toFixed(3)),
4544
+ // MapLibre lights the extrusions from above the horizon only.
4545
+ Number(Math.min(90, Math.max(0, 90 - direction.altitude)).toFixed(3))
4546
+ ]
4547
+ };
4548
+ }
4549
+
4214
4550
  // src/themes/visual-semantics.ts
4215
4551
  var visualNumberProperties = /* @__PURE__ */ new Set([
4216
4552
  "angle",
@@ -4369,7 +4705,9 @@ var themeTypographyKeys = [
4369
4705
  "water"
4370
4706
  ];
4371
4707
  var themeTypographyDomains = ["places", "poi", "roads", "water"];
4372
- var themeLightingKeys = ["anchor", "color", "intensity", "position"];
4708
+ var themeLightingKeys = ["ambient", "anchor", "color", "intensity", "position", "sun"];
4709
+ var themeSunKeys = ["altitude", "azimuth", "color", "intensity", "shadowIntensity"];
4710
+ var themeAmbientKeys = ["color", "intensity"];
4373
4711
  function resolveTileflowTheme(theme) {
4374
4712
  assertThemeDocument(theme);
4375
4713
  const resolver = new ThemeValueResolver(theme);
@@ -4380,11 +4718,12 @@ function resolveTileflowTheme(theme) {
4380
4718
  number: resolver.resolveCategory("number")
4381
4719
  };
4382
4720
  const typography = resolver.resolveTypography(theme.typography);
4383
- const lighting = resolver.resolveLighting(theme.lighting);
4721
+ const { light: lighting, sun } = resolver.resolveLighting(theme.lighting);
4384
4722
  return {
4385
4723
  colorScheme: theme.colorScheme,
4386
4724
  id: theme.id,
4387
4725
  lighting,
4726
+ ...sun ? { sun } : {},
4388
4727
  tokens,
4389
4728
  typography,
4390
4729
  version: theme.version
@@ -4428,7 +4767,7 @@ function auditTileflowThemeValues(value, path = "value", context = inferAuditCon
4428
4767
  return diagnostics.sort(compareAuditDiagnostics);
4429
4768
  }
4430
4769
  function auditTileflowMapThemeValues(map) {
4431
- const modules = isRecord3(map.modules) ? map.modules : void 0;
4770
+ const modules = isRecord4(map.modules) ? map.modules : void 0;
4432
4771
  const moduleDiagnostics = modules ? Object.keys(modules).sort(compareCodeUnits2).flatMap(
4433
4772
  (owner) => auditTileflowThemeValues(modules[owner], `modules.${owner}`, {
4434
4773
  owner,
@@ -4531,7 +4870,7 @@ function isThemePrimitiveForCategory(value, category) {
4531
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;
4532
4871
  }
4533
4872
  function isThemeValueCandidate(value) {
4534
- 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");
4535
4874
  }
4536
4875
  function visitAuditableValue(value, path, category, state, intentRequired = false) {
4537
4876
  if (isThemeStructuralPath(path)) category = void 0;
@@ -4547,7 +4886,7 @@ function visitAuditableValue(value, path, category, state, intentRequired = fals
4547
4886
  visitAuditableArray(value, path, category, state, false, intentRequired);
4548
4887
  return;
4549
4888
  }
4550
- if (!isRecord3(value)) return;
4889
+ if (!isRecord4(value)) return;
4551
4890
  if (value.kind === "expression" && Array.isArray(value.value)) {
4552
4891
  visitAuditableArray(value.value, `${path}.value`, category, state, true, intentRequired);
4553
4892
  return;
@@ -4862,7 +5201,7 @@ var ThemeValueResolver = class {
4862
5201
  if (Array.isArray(value)) {
4863
5202
  return value.map((entry, index) => this.resolveTree(entry, `${path}[${index}]`, category));
4864
5203
  }
4865
- if (!isRecord3(value)) return value;
5204
+ if (!isRecord4(value)) return value;
4866
5205
  if (value.kind === "expression" && Array.isArray(value.value)) {
4867
5206
  return {
4868
5207
  ...value,
@@ -4902,6 +5241,65 @@ var ThemeValueResolver = class {
4902
5241
  };
4903
5242
  }
4904
5243
  resolveLighting(lighting) {
5244
+ const { ambient, sun, ...flat } = lighting;
5245
+ const light = this.#resolveFlatLight(flat);
5246
+ if (sun === void 0 && ambient === void 0) return { light };
5247
+ const resolvedSun = this.resolveTree(sun ?? {}, "theme.lighting.sun");
5248
+ const resolvedAmbient = this.resolveTree(ambient ?? {}, "theme.lighting.ambient");
5249
+ const defaults4 = tileflowDefaultSunPreset;
5250
+ const number = (value, path, fallback, range2) => {
5251
+ if (value === void 0) return fallback;
5252
+ assertFiniteNumber(value, path);
5253
+ if (range2 && (value < range2[0] || value > range2[1])) {
5254
+ throw new Error(`Tileflow ${path} must be between ${range2[0]} and ${range2[1]}.`);
5255
+ }
5256
+ return value;
5257
+ };
5258
+ const colour = (value, path, fallback) => {
5259
+ if (value === void 0) return fallback;
5260
+ assertColor(value, path);
5261
+ return value;
5262
+ };
5263
+ const preset = {
5264
+ azimuth: normalizeTileflowAzimuth(
5265
+ number(resolvedSun.azimuth, "theme.lighting.sun.azimuth", defaults4.azimuth)
5266
+ ),
5267
+ altitude: number(
5268
+ resolvedSun.altitude,
5269
+ "theme.lighting.sun.altitude",
5270
+ defaults4.altitude,
5271
+ [-90, 90]
5272
+ ),
5273
+ color: colour(resolvedSun.color, "theme.lighting.sun.color", defaults4.color),
5274
+ intensity: number(
5275
+ resolvedSun.intensity,
5276
+ "theme.lighting.sun.intensity",
5277
+ defaults4.intensity,
5278
+ [0, 1]
5279
+ ),
5280
+ shadowIntensity: number(
5281
+ resolvedSun.shadowIntensity,
5282
+ "theme.lighting.sun.shadowIntensity",
5283
+ defaults4.shadowIntensity,
5284
+ [0, 1]
5285
+ ),
5286
+ ambient: {
5287
+ color: colour(
5288
+ resolvedAmbient.color,
5289
+ "theme.lighting.ambient.color",
5290
+ defaults4.ambient.color
5291
+ ),
5292
+ intensity: number(
5293
+ resolvedAmbient.intensity,
5294
+ "theme.lighting.ambient.intensity",
5295
+ defaults4.ambient.intensity,
5296
+ [0, 1]
5297
+ )
5298
+ }
5299
+ };
5300
+ return { light: sun === void 0 ? light : tileflowSunLight(preset, light), sun: preset };
5301
+ }
5302
+ #resolveFlatLight(lighting) {
4905
5303
  const result = this.resolveTree(lighting, "theme.lighting");
4906
5304
  if (result.anchor !== void 0 && result.anchor !== "map" && result.anchor !== "viewport") {
4907
5305
  throw themeTypeError("theme.lighting.anchor", "map or viewport", result.anchor);
@@ -5078,6 +5476,24 @@ function assertThemeTypographyStyleFields(style, path) {
5078
5476
  function assertThemeLighting(value, path) {
5079
5477
  const lighting = assertPlainThemeRecord(value, path);
5080
5478
  assertExactThemeKeys(lighting, path, themeLightingKeys);
5479
+ if (Object.hasOwn(lighting, "sun")) {
5480
+ const sun = assertPlainThemeRecord(lighting.sun, `${path}.sun`);
5481
+ assertExactThemeKeys(sun, `${path}.sun`, themeSunKeys);
5482
+ for (const key of ["altitude", "azimuth", "intensity", "shadowIntensity"]) {
5483
+ if (Object.hasOwn(sun, key))
5484
+ assertThemeCategoryValue(sun[key], "number", `${path}.sun.${key}`);
5485
+ }
5486
+ if (Object.hasOwn(sun, "color"))
5487
+ assertThemeCategoryValue(sun.color, "color", `${path}.sun.color`);
5488
+ }
5489
+ if (Object.hasOwn(lighting, "ambient")) {
5490
+ const ambient = assertPlainThemeRecord(lighting.ambient, `${path}.ambient`);
5491
+ assertExactThemeKeys(ambient, `${path}.ambient`, themeAmbientKeys);
5492
+ if (Object.hasOwn(ambient, "intensity"))
5493
+ assertThemeCategoryValue(ambient.intensity, "number", `${path}.ambient.intensity`);
5494
+ if (Object.hasOwn(ambient, "color"))
5495
+ assertThemeCategoryValue(ambient.color, "color", `${path}.ambient.color`);
5496
+ }
5081
5497
  if (Object.hasOwn(lighting, "anchor") && lighting.anchor !== "map" && lighting.anchor !== "viewport") {
5082
5498
  throw themeTypeError(`${path}.anchor`, "map or viewport", lighting.anchor);
5083
5499
  }
@@ -5097,7 +5513,7 @@ function assertThemeLighting(value, path) {
5097
5513
  }
5098
5514
  }
5099
5515
  function assertThemeCategoryValue(value, expectedCategory, path) {
5100
- if (!isRecord3(value)) {
5516
+ if (!isRecord4(value)) {
5101
5517
  assertTokenPrimitive(expectedCategory, value, path);
5102
5518
  return;
5103
5519
  }
@@ -5244,6 +5660,7 @@ function inferThemeCategoryFromPath(path) {
5244
5660
  const tokenCategory = path.match(/^theme\.tokens\.(color|font|image|number)(?:\.|$)/u)?.[1];
5245
5661
  if (isThemeTokenCategory(tokenCategory)) return tokenCategory;
5246
5662
  if (/^theme\.lighting\.position(?:\[\d+\])?$/u.test(path)) return "number";
5663
+ if (/^theme\.lighting\.sun\.(?:altitude|azimuth|shadowIntensity)$/u.test(path)) return "number";
5247
5664
  const segments = path.replace(/\[\d+\]/g, "").split(".");
5248
5665
  return [...segments].reverse().map((segment) => classifyTileflowVisualProperty(segment)).find((category) => category !== void 0);
5249
5666
  }
@@ -5448,11 +5865,11 @@ function cloneJson14(value) {
5448
5865
  if (serialized === void 0) throw new Error("Tileflow themes must be JSON-serializable.");
5449
5866
  return JSON.parse(serialized);
5450
5867
  }
5451
- function isRecord3(value) {
5868
+ function isRecord4(value) {
5452
5869
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
5453
5870
  }
5454
5871
  function isPlainRecord(value) {
5455
- if (!isRecord3(value)) return false;
5872
+ if (!isRecord4(value)) return false;
5456
5873
  const prototype = Object.getPrototypeOf(value);
5457
5874
  return prototype === Object.prototype || prototype === null;
5458
5875
  }
@@ -5644,7 +6061,8 @@ function resolveColors(overrides = {}, defaults4 = defaultColors, moduleOverride
5644
6061
  resolved.landcover.grass,
5645
6062
  resolved.landcover.meadow
5646
6063
  ],
5647
- 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]
5648
6066
  }
5649
6067
  }
5650
6068
  };
@@ -5811,6 +6229,11 @@ function resolveThemeColors(theme) {
5811
6229
  optionalColor("vegetation.tree.conifer.a", colors.vegetation.tree.conifer[0]),
5812
6230
  optionalColor("vegetation.tree.conifer.b", colors.vegetation.tree.conifer[1]),
5813
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])
5814
6237
  ]
5815
6238
  }
5816
6239
  }
@@ -6217,7 +6640,7 @@ function compileRoads(request, context) {
6217
6640
  });
6218
6641
  }
6219
6642
  }
6220
- compileSidewalks(request?.sidewalks, context, contributions);
6643
+ compileSidewalks(request?.sidewalks, areaStyles.pedestrian?.fill?.color, context, contributions);
6221
6644
  compileRoundabouts(request?.roundabouts, context, contributions);
6222
6645
  compileCrossings(request?.crossings, context, contributions);
6223
6646
  if (semantics.oneWayMarkers && visible.size > 0) {
@@ -6286,15 +6709,15 @@ function roadPhysicalCohort(roadClass) {
6286
6709
  if (roadClass === "minor" || roadClass === "service" || roadClass === "track") return "local";
6287
6710
  return "path";
6288
6711
  }
6289
- function compileSidewalks(request, context, contributions) {
6712
+ function compileSidewalks(request, pedestrianAreaColor, context, contributions) {
6290
6713
  const { fields, layers } = context.data.schema;
6291
6714
  if (!layers.sidewalk) return;
6292
6715
  const config = mergeTileflowDesign(
6293
6716
  {
6294
6717
  surface: {
6295
- color: context.colors.roads.casing,
6718
+ color: pedestrianAreaColor ?? context.colors.road,
6296
6719
  minZoom: 17,
6297
- opacity: 0.96
6720
+ opacity: 1
6298
6721
  }
6299
6722
  },
6300
6723
  request
@@ -6946,10 +7369,10 @@ function roadWidth(roadClass, scale) {
6946
7369
  [16, 3.4]
6947
7370
  ]
6948
7371
  };
6949
- return zoom.linear(stops[roadClass].map(([z15, value]) => [z15, value * scale]));
7372
+ return zoom.linear(stops[roadClass].map(([z16, value]) => [z16, value * scale]));
6950
7373
  }
6951
7374
  function widen(value, addition) {
6952
- return zoom.linear(value.stops.map(([z15, width]) => [z15, width + addition]));
7375
+ return zoom.linear(value.stops.map(([z16, width]) => [z16, width + addition]));
6953
7376
  }
6954
7377
  function structureFilter(field2, structure) {
6955
7378
  if (structure === "surface") {
@@ -7159,76 +7582,6 @@ function cloneJson16(value) {
7159
7582
  return JSON.parse(JSON.stringify(value));
7160
7583
  }
7161
7584
 
7162
- // src/modules/vegetation/compiler.ts
7163
- function compileVegetation(request, context) {
7164
- const minZoom = request?.flat?.minZoom ?? 16;
7165
- const mode = request?.mode ?? "3d";
7166
- const config = mergeTileflowDesign(
7167
- {
7168
- type: "vegetation",
7169
- enabled: true,
7170
- flat: {
7171
- color: context.colors.landcover.wood,
7172
- minZoom,
7173
- opacity: mode === "3d" ? 0.82 : 0.9,
7174
- pitchAlignment: "map",
7175
- pitchScale: "map",
7176
- radius: minZoom < 24 ? zoom.linear([
7177
- [minZoom, 2.2],
7178
- [minZoom < 20 ? 20 : 24, 8]
7179
- ]) : 2.2,
7180
- strokeColor: context.colors.park,
7181
- strokeOpacity: 0.55,
7182
- strokeWidth: 0.8
7183
- },
7184
- mode,
7185
- threeDimensional: {
7186
- barkColor: context.colors.vegetation.tree.bark,
7187
- broadleafColors: context.colors.vegetation.tree.broadleaf,
7188
- coniferColors: context.colors.vegetation.tree.conifer,
7189
- crownScale: 1,
7190
- heightScale: 1
7191
- }
7192
- },
7193
- request
7194
- );
7195
- if (config.enabled === false || !context.data.schema.layers.tree) return [];
7196
- const resolvedMode = config.mode ?? "3d";
7197
- const threeDimensional = config.threeDimensional;
7198
- return [
7199
- {
7200
- kind: "layer",
7201
- layer: applyCircleStyle(
7202
- {
7203
- id: "tileflow-vegetation-trees",
7204
- type: "circle",
7205
- source: context.data.sourceId,
7206
- "source-layer": context.data.schema.layers.tree,
7207
- metadata: {
7208
- "tileflow:vegetation-mode": resolvedMode,
7209
- ...resolvedMode === "3d" ? { "tileflow:vegetation-fallback": "flat-circle" } : {},
7210
- "tileflow:tree-bark-color": threeDimensional.barkColor,
7211
- "tileflow:tree-broadleaf-colors": threeDimensional.broadleafColors,
7212
- "tileflow:tree-conifer-colors": threeDimensional.coniferColors,
7213
- "tileflow:tree-crown-field": context.data.schema.fields.diameterCrown,
7214
- "tileflow:tree-crown-scale": threeDimensional.crownScale,
7215
- "tileflow:tree-genus-field": context.data.schema.fields.genus,
7216
- "tileflow:tree-height-field": context.data.schema.fields.height,
7217
- "tileflow:tree-height-scale": threeDimensional.heightScale,
7218
- "tileflow:tree-leaf-type-field": context.data.schema.fields.leafType,
7219
- "tileflow:tree-species-field": context.data.schema.fields.species
7220
- }
7221
- },
7222
- config.flat ?? {}
7223
- ),
7224
- localOrder: 1,
7225
- owner: "vegetation",
7226
- slot: "vegetation",
7227
- target: "vegetation.trees"
7228
- }
7229
- ];
7230
- }
7231
-
7232
7585
  // src/modules/water/index.ts
7233
7586
  function water(options = {}) {
7234
7587
  return { type: "water", ...cloneJson17(options) };
@@ -8462,24 +8815,39 @@ function validateStops(stops, api) {
8462
8815
  }
8463
8816
  }
8464
8817
  function validateOperandCount(count, api) {
8465
- if (count > tileflowDataExpressionLimits.maxOperands) {
8466
- throw new Error(
8467
- `${api} supports at most ${tileflowDataExpressionLimits.maxOperands} operands.`
8818
+ const limit = tileflowDataExpressionLimits.maxOperands;
8819
+ if (count > limit) {
8820
+ throw limitError(
8821
+ `${api} received ${count} operands; it supports at most ${limit}. Nest ${api} calls of at most ${limit} operands each.`
8468
8822
  );
8469
8823
  }
8470
8824
  }
8471
8825
  function validateBranchCount(count, api) {
8472
- if (count > tileflowDataExpressionLimits.maxBranches) {
8473
- throw new Error(
8474
- `${api} supports at most ${tileflowDataExpressionLimits.maxBranches} branches.`
8826
+ const limit = tileflowDataExpressionLimits.maxBranches;
8827
+ if (count > limit) {
8828
+ throw limitError(
8829
+ `${api} received ${count} branches; it supports at most ${limit}. Split the lookup into nested ${api} groups of at most ${limit} branches, or reduce the mapping.`
8475
8830
  );
8476
8831
  }
8477
8832
  }
8478
8833
  function validateStopCount(count, api) {
8479
- if (count > tileflowDataExpressionLimits.maxStops) {
8480
- throw new Error(`${api} supports at most ${tileflowDataExpressionLimits.maxStops} stops.`);
8834
+ const limit = tileflowDataExpressionLimits.maxStops;
8835
+ if (count > limit) {
8836
+ throw limitError(
8837
+ `${api} received ${count} stops; it supports at most ${limit}. Remove stops the curve can interpolate.`
8838
+ );
8481
8839
  }
8482
8840
  }
8841
+ function limitError(message) {
8842
+ const site = authoringCallSite(new Error().stack);
8843
+ return new Error(site ? `${message} Called at ${site}.` : message);
8844
+ }
8845
+ function authoringCallSite(stack) {
8846
+ 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 }));
8847
+ const own = locations[0]?.file;
8848
+ const site = locations.find(({ file }) => file !== own && !file.startsWith("node:"));
8849
+ return site ? `${site.file}:${site.line}:${site.column}` : void 0;
8850
+ }
8483
8851
  function node(value) {
8484
8852
  if (isTileflowExpression(value)) return cloneJson19(value.value);
8485
8853
  if (isDataFieldReference(value)) return ["get", cloneJson19(value)];
@@ -8673,7 +9041,11 @@ function visitExpression(value, path, variables, issues) {
8673
9041
  return;
8674
9042
  }
8675
9043
  if ((value.length - 2) / 2 > branchLimit) {
8676
- addExpressionIssue(issues, path, `Expression case exceeds ${branchLimit} branches.`);
9044
+ addExpressionIssue(
9045
+ issues,
9046
+ path,
9047
+ `Expression case has ${(value.length - 2) / 2} branches and exceeds ${branchLimit}.`
9048
+ );
8677
9049
  return;
8678
9050
  }
8679
9051
  for (let index = 1; index < value.length; index += 1) visit(value[index], index);
@@ -8689,7 +9061,11 @@ function visitExpression(value, path, variables, issues) {
8689
9061
  return;
8690
9062
  }
8691
9063
  if ((value.length - 3) / 2 > branchLimit) {
8692
- addExpressionIssue(issues, path, `Expression match exceeds ${branchLimit} branches.`);
9064
+ addExpressionIssue(
9065
+ issues,
9066
+ path,
9067
+ `Expression match has ${(value.length - 3) / 2} branches and exceeds ${branchLimit}.`
9068
+ );
8693
9069
  return;
8694
9070
  }
8695
9071
  visit(value[1], 1);
@@ -8918,21 +9294,21 @@ function bindSemanticValue(value, schema, sourceId) {
8918
9294
  }
8919
9295
  if (isDataSourceReference(value)) return sourceId;
8920
9296
  if (Array.isArray(value)) return value.map((item) => bindSemanticValue(item, schema, sourceId));
8921
- if (!isRecord4(value)) return value;
9297
+ if (!isRecord5(value)) return value;
8922
9298
  return Object.fromEntries(
8923
9299
  Object.entries(value).map(([key, item]) => [key, bindSemanticValue(item, schema, sourceId)])
8924
9300
  );
8925
9301
  }
8926
9302
  function isDataFieldReference2(value) {
8927
- 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";
8928
9304
  }
8929
9305
  function isDataLayerReference(value) {
8930
- 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";
8931
9307
  }
8932
9308
  function isDataSourceReference(value) {
8933
- return isRecord4(value) && value.kind === "tileflow-data-source" && value.name === "primary";
9309
+ return isRecord5(value) && value.kind === "tileflow-data-source" && value.name === "primary";
8934
9310
  }
8935
- function isRecord4(value) {
9311
+ function isRecord5(value) {
8936
9312
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
8937
9313
  }
8938
9314
  function compareCodeUnits5(left, right) {
@@ -9036,8 +9412,8 @@ function createTileflowLayerTemplateIR(layer, semanticKey) {
9036
9412
  renderer: type,
9037
9413
  ...filter === void 0 ? {} : { selector: cloneJson20(filter) },
9038
9414
  style: Object.freeze({
9039
- ...isRecord5(layout) ? { placement: Object.freeze(cloneJson20(layout)) } : {},
9040
- ...isRecord5(paint) ? { appearance: Object.freeze(cloneJson20(paint)) } : {}
9415
+ ...isRecord6(layout) ? { placement: Object.freeze(cloneJson20(layout)) } : {},
9416
+ ...isRecord6(paint) ? { appearance: Object.freeze(cloneJson20(paint)) } : {}
9041
9417
  })
9042
9418
  });
9043
9419
  }
@@ -9277,21 +9653,21 @@ function visitReferences(value, counts, visited = /* @__PURE__ */ new WeakSet())
9277
9653
  }
9278
9654
  }
9279
9655
  function isSemanticFieldReference(value) {
9280
- return isRecord5(value) && value.kind === "tileflow-data-field";
9656
+ return isRecord6(value) && value.kind === "tileflow-data-field";
9281
9657
  }
9282
9658
  function isSemanticLayerReference(value) {
9283
- return isRecord5(value) && value.kind === "tileflow-data-layer";
9659
+ return isRecord6(value) && value.kind === "tileflow-data-layer";
9284
9660
  }
9285
9661
  function isSemanticSourceReference(value) {
9286
- return isRecord5(value) && value.kind === "tileflow-data-source";
9662
+ return isRecord6(value) && value.kind === "tileflow-data-source";
9287
9663
  }
9288
9664
  function cloneJson20(value) {
9289
9665
  return JSON.parse(JSON.stringify(value));
9290
9666
  }
9291
9667
  function asRecord3(value) {
9292
- return isRecord5(value) ? value : {};
9668
+ return isRecord6(value) ? value : {};
9293
9669
  }
9294
- function isRecord5(value) {
9670
+ function isRecord6(value) {
9295
9671
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
9296
9672
  }
9297
9673
 
@@ -9343,7 +9719,7 @@ function validateTileflowRenderSelectorConstraints(input) {
9343
9719
  };
9344
9720
  while (pending.length > 0) {
9345
9721
  const current = pending.pop();
9346
- if (!isRecord6(current.selector) || typeof current.selector.kind !== "string") {
9722
+ if (!isRecord7(current.selector) || typeof current.selector.kind !== "string") {
9347
9723
  addIssue(
9348
9724
  "invalid-selector",
9349
9725
  "a render selector must be a semantic selector node",
@@ -9404,7 +9780,7 @@ function validateTileflowRenderSelectorConstraints(input) {
9404
9780
  }
9405
9781
  for (const [index, branch] of current.selector.branches.entries()) {
9406
9782
  const branchPath = [...branchesPath, index];
9407
- if (!isRecord6(branch)) {
9783
+ if (!isRecord7(branch)) {
9408
9784
  addIssue("invalid-selector", "match selector branch must be an object", branchPath);
9409
9785
  continue;
9410
9786
  }
@@ -9435,14 +9811,14 @@ function validateTileflowRenderSelectorConstraints(input) {
9435
9811
  for (let index = current.selector.stops.length - 1; index >= 0; index -= 1) {
9436
9812
  const stop = current.selector.stops[index];
9437
9813
  const stopPath = [...stopsPath, index];
9438
- if (!isRecord6(stop)) {
9814
+ if (!isRecord7(stop)) {
9439
9815
  addIssue("invalid-selector", "step selector stop must be an object", stopPath);
9440
9816
  continue;
9441
9817
  }
9442
9818
  pushChild(stop.selector, current.depth + 1, [...stopPath, "selector"]);
9443
9819
  }
9444
9820
  for (const [index, stop] of current.selector.stops.entries()) {
9445
- if (!isRecord6(stop)) continue;
9821
+ if (!isRecord7(stop)) continue;
9446
9822
  const zoom3 = stop.zoom;
9447
9823
  const zoomPath = [...stopsPath, index, "zoom"];
9448
9824
  if (typeof zoom3 !== "number" || !Number.isFinite(zoom3) || zoom3 < 0 || zoom3 > 24) {
@@ -9767,7 +10143,7 @@ function applyRendererStyle(layer, renderer, style) {
9767
10143
  };
9768
10144
  }
9769
10145
  function lowerSelector(selector, data) {
9770
- if (!isRecord6(selector) || typeof selector.kind !== "string") {
10146
+ if (!isRecord7(selector) || typeof selector.kind !== "string") {
9771
10147
  throw renderStackError(
9772
10148
  "invalid-selector",
9773
10149
  "A render selector must be a semantic selector node."
@@ -9970,7 +10346,7 @@ function requireRenderer(value) {
9970
10346
  function requirePlacement(value) {
9971
10347
  if (typeof value === "string") {
9972
10348
  if (tileflowBandPlacements.includes(value)) return;
9973
- } else if (isRecord6(value)) {
10349
+ } else if (isRecord7(value)) {
9974
10350
  const keys = Object.keys(value);
9975
10351
  if (keys.length === 1 && (keys[0] === "above" || keys[0] === "below")) {
9976
10352
  requirePortableTarget(value[keys[0]], "render placement target");
@@ -9997,7 +10373,7 @@ function validateRefinementShape(operation) {
9997
10373
  validateStyle(operation.renderer, operation.style);
9998
10374
  }
9999
10375
  function validateStyle(renderer, style) {
10000
- if (!isRecord6(style)) {
10376
+ if (!isRecord7(style)) {
10001
10377
  throw renderStackError(
10002
10378
  "invalid-style",
10003
10379
  `A ${renderer} render operation requires a style object.`
@@ -10099,7 +10475,7 @@ function normalizeRequirements(requirements) {
10099
10475
  return [...requirements];
10100
10476
  }
10101
10477
  function requireRenderStackEntries(stack) {
10102
- if (!isRecord6(stack)) {
10478
+ if (!isRecord7(stack)) {
10103
10479
  throw renderStackError("invalid-stack", "A render stack must be a named operation record.");
10104
10480
  }
10105
10481
  const entries = Object.entries(stack);
@@ -10246,9 +10622,9 @@ function cloneJson21(value) {
10246
10622
  return JSON.parse(JSON.stringify(value));
10247
10623
  }
10248
10624
  function asRecord4(value) {
10249
- return isRecord6(value) ? value : {};
10625
+ return isRecord7(value) ? value : {};
10250
10626
  }
10251
- function isRecord6(value) {
10627
+ function isRecord7(value) {
10252
10628
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
10253
10629
  }
10254
10630
 
@@ -10616,7 +10992,7 @@ function validateExpressionThemeValues(value, expectedCategory, context, path) {
10616
10992
  if (!parsed.success || parsed.data.category !== expectedCategory) {
10617
10993
  context.addIssue({
10618
10994
  code: "custom",
10619
- message: expectedCategory === void 0 ? "Structural expressions cannot contain theme-token references" : `Expected a ${expectedCategory} theme-token reference`,
10995
+ message: expectedCategory === void 0 ? `Structural expressions cannot contain theme-token references. ${structuralThemeValueHint}` : parsed.success ? `Expected a ${expectedCategory} theme-token reference, received ${parsed.data.category} token "${parsed.data.token}". ${expressionOperandHint(expectedCategory)}` : `Expected a ${expectedCategory} theme-token reference`,
10620
10996
  path
10621
10997
  });
10622
10998
  }
@@ -10625,9 +11001,11 @@ function validateExpressionThemeValues(value, expectedCategory, context, path) {
10625
11001
  if (candidate.kind === "theme-fixed") {
10626
11002
  const parsed = expectedCategory === "number" ? z5.union([fixedNumberSchema, fixedNumberArraySchema]).safeParse(candidate) : expectedCategory === "color" || expectedCategory === "image" ? fixedStringSchema.safeParse(candidate) : void 0;
10627
11003
  if (!parsed?.success) {
11004
+ const received = fixedValueKind(candidate.value);
11005
+ const literal2 = JSON.stringify(candidate.value);
10628
11006
  context.addIssue({
10629
11007
  code: "custom",
10630
- message: expectedCategory === void 0 ? "Structural expressions cannot contain theme-fixed values" : `Expected a fixed ${expectedCategory} value`,
11008
+ message: expectedCategory === void 0 ? `Structural expressions cannot contain theme-fixed values. ${structuralThemeValueHint}` : received !== void 0 && received !== expectedCategoryKind(expectedCategory) ? `Expected a fixed ${expectedCategory} value, received a fixed ${received}. ${expressionOperandHint(expectedCategory)} Write ${literal2} instead of fixed(${literal2}).` : `Expected a fixed ${expectedCategory} value`,
10631
11009
  path
10632
11010
  });
10633
11011
  }
@@ -10647,6 +11025,20 @@ function validateExpressionThemeValues(value, expectedCategory, context, path) {
10647
11025
  validateExpressionThemeValues(entry, expectedCategory, context, [...path, key]);
10648
11026
  }
10649
11027
  }
11028
+ var structuralThemeValueHint = "fixed() and theme tokens apply only to visual properties such as colours, sizes, and images; write structural values directly.";
11029
+ function expressionOperandHint(category) {
11030
+ return `In ${category === "image" ? "an" : "a"} ${category} expression, fixed() and theme tokens belong in its ${category} results; conditions and other operands take plain values.`;
11031
+ }
11032
+ function fixedValueKind(value) {
11033
+ if (typeof value === "number") return "number";
11034
+ if (typeof value === "string") return "string";
11035
+ if (typeof value === "boolean") return "boolean";
11036
+ if (Array.isArray(value)) return "array";
11037
+ return void 0;
11038
+ }
11039
+ function expectedCategoryKind(category) {
11040
+ return category === "number" ? "number" : "string";
11041
+ }
10650
11042
  var numberExpressionSchema = expressionSchemaFor("number");
10651
11043
  var colorExpressionSchema = expressionSchemaFor("color");
10652
11044
  var imageExpressionSchema = expressionSchemaFor("image");
@@ -10759,10 +11151,21 @@ var themeTypographySchema = themeTypographyStyleSchema.extend({
10759
11151
  water: themeTypographyStyleSchema.optional()
10760
11152
  });
10761
11153
  var themeLightingSchema = z5.object({
11154
+ ambient: z5.object({
11155
+ color: themeColorValueSchema.optional(),
11156
+ intensity: themeNumberValueSchema.optional()
11157
+ }).strict().optional(),
10762
11158
  anchor: z5.enum(["map", "viewport"]).optional(),
10763
11159
  color: themeColorValueSchema.optional(),
10764
11160
  intensity: themeNumberValueSchema.optional(),
10765
- position: z5.tuple([themeNumberValueSchema, themeNumberValueSchema, themeNumberValueSchema]).optional()
11161
+ position: z5.tuple([themeNumberValueSchema, themeNumberValueSchema, themeNumberValueSchema]).optional(),
11162
+ sun: z5.object({
11163
+ altitude: themeNumberValueSchema.optional(),
11164
+ azimuth: themeNumberValueSchema.optional(),
11165
+ color: themeColorValueSchema.optional(),
11166
+ intensity: themeNumberValueSchema.optional(),
11167
+ shadowIntensity: themeNumberValueSchema.optional()
11168
+ }).strict().optional()
10766
11169
  }).strict();
10767
11170
  var tileflowThemeSchema = z5.object({
10768
11171
  colorScheme: z5.enum(["light", "dark"]),
@@ -11245,6 +11648,17 @@ var waterModuleSchema = z5.object({
11245
11648
  stream: lineStyleSchema.optional()
11246
11649
  }).strict().optional()
11247
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
+ }
11248
11662
  var buildingsModuleSchema = z5.object({
11249
11663
  type: z5.literal("buildings"),
11250
11664
  businessCorridor: areaStyleSchema.optional(),
@@ -11252,7 +11666,15 @@ var buildingsModuleSchema = z5.object({
11252
11666
  extrusion: extrusionStyleSchema.optional(),
11253
11667
  flat: areaStyleSchema.optional(),
11254
11668
  mode: z5.enum(["3d", "flat"]).optional(),
11255
- 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()
11256
11678
  }).strict();
11257
11679
  var addressesModuleSchema = z5.object({
11258
11680
  type: z5.literal("addresses"),
@@ -11365,6 +11787,12 @@ var roadsModuleSchema = z5.object({
11365
11787
  pattern: fillStyleSchema.optional(),
11366
11788
  surface: fillStyleSchema.optional()
11367
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(),
11368
11796
  structures: roadStructureMapSchema.optional(),
11369
11797
  weight: z5.enum(["thin", "regular", "bold"]).optional(),
11370
11798
  widthScale: z5.partialRecord(roadClassSchema, themeNumberValueSchema).optional()
@@ -11453,6 +11881,16 @@ var nauticalModuleSchema = z5.object({
11453
11881
  wreckAreas: areaStyleSchema.optional(),
11454
11882
  wrecks: markerSymbolStyleSchema.optional()
11455
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
+ });
11456
11894
  var modulesSchema = z5.object({
11457
11895
  addresses: addressesModuleSchema.optional(),
11458
11896
  aeroways: aerowaysModuleSchema.optional(),
@@ -11475,6 +11913,39 @@ var modulesSchema = z5.object({
11475
11913
  barkColor: themeColorValueSchema.optional(),
11476
11914
  broadleafColors: z5.array(themeColorValueSchema).min(1).max(8).optional(),
11477
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(),
11478
11949
  crownScale: z5.union([
11479
11950
  z5.number().finite().positive().max(10),
11480
11951
  themeNumberReferenceSchema,
@@ -11807,6 +12278,7 @@ var iconDirectoriesSchema = z5.array(
11807
12278
  }
11808
12279
  });
11809
12280
  var fontDirectoriesSchema = z5.array(assetDirectorySchema).max(16);
12281
+ var modelDirectoriesSchema = z5.array(assetDirectorySchema).max(16);
11810
12282
  var fontStacksSchema = z5.array(
11811
12283
  z5.string().min(1).max(200).refine(
11812
12284
  (value) => value === value.trim() && value === value.normalize("NFC") && !/[\p{Cc}\\/]/u.test(value),
@@ -12028,6 +12500,7 @@ var resolvedTileflowMapSchema = z5.object({
12028
12500
  icons: iconDirectoriesSchema.optional(),
12029
12501
  id: tileflowMapIdSchema,
12030
12502
  marine: marineSchema.optional(),
12503
+ models: modelDirectoriesSchema.optional(),
12031
12504
  modules: modulesSchema.optional(),
12032
12505
  name: z5.string().trim().min(1),
12033
12506
  overlays: overlaysSchema.optional(),
@@ -12117,9 +12590,43 @@ function parseOrThrow(schema, input, name) {
12117
12590
  if (result.success) return result.data;
12118
12591
  throw new TileflowResolvedMapValidationError(
12119
12592
  name,
12120
- validationMessagesFromIssues(result.error.issues)
12593
+ validationMessagesFromIssues(expandUnionIssues(result.error.issues, [], input))
12121
12594
  );
12122
12595
  }
12596
+ function expandUnionIssues(issues, base, input) {
12597
+ return issues.flatMap((issue) => {
12598
+ const path = [...base, ...issue.path];
12599
+ if (issue.code !== "invalid_union" || issue.errors.length === 0) return [{ ...issue, path }];
12600
+ const candidates = issue.errors.filter((branch) => !branch.some(isUnionBranchMismatch));
12601
+ if (candidates.length === 0) {
12602
+ return [{ ...issue, path, message: unmatchedUnionMessage(valueAtPath(input, path)) }];
12603
+ }
12604
+ const fewest = Math.min(...candidates.map((branch) => branch.length));
12605
+ const best = candidates.filter((branch) => branch.length === fewest);
12606
+ return best.length === 1 ? expandUnionIssues(best[0], path, input) : [{ ...issue, path }];
12607
+ });
12608
+ }
12609
+ function isUnionBranchMismatch(issue) {
12610
+ if (issue.path.length === 0)
12611
+ return issue.code === "invalid_type" || issue.code === "invalid_value";
12612
+ return issue.path.length === 1 && (issue.code === "invalid_value" || issue.code === "invalid_union" && issue.errors.length === 0);
12613
+ }
12614
+ function unmatchedUnionMessage(value) {
12615
+ const kind = value !== null && typeof value === "object" ? value.kind : void 0;
12616
+ if (kind === "theme-fixed" || kind === "theme-token") {
12617
+ return `This property is structural. ${structuralThemeValueHint}`;
12618
+ }
12619
+ const received = value === null ? "null" : Array.isArray(value) ? "an array" : typeof value;
12620
+ return `Invalid input: no accepted form takes ${received === "object" ? "this object" : received}`;
12621
+ }
12622
+ function valueAtPath(input, path) {
12623
+ let value = input;
12624
+ for (const key of path) {
12625
+ if (value === null || typeof value !== "object" || !Object.hasOwn(value, key)) return void 0;
12626
+ value = value[key];
12627
+ }
12628
+ return value;
12629
+ }
12123
12630
  function validationMessagesFromIssues(issues) {
12124
12631
  return issues.flatMap((issue) => {
12125
12632
  const basePath = issue.path.join(".");
@@ -12183,6 +12690,71 @@ function findUnsafeObjectStructure(input) {
12183
12690
  return void 0;
12184
12691
  }
12185
12692
 
12693
+ // src/route-inks.ts
12694
+ var tileflowRouteMetadataKey = "tileflow:route";
12695
+ var tileflowRouteTokens = Object.freeze({
12696
+ color: Object.freeze({
12697
+ line: "route.line",
12698
+ casing: "route.casing",
12699
+ core: "route.core",
12700
+ glow: "route.glow",
12701
+ alternative: "route.alternative"
12702
+ }),
12703
+ number: Object.freeze({
12704
+ glowOpacity: "route.glow.opacity",
12705
+ coreOpacity: "route.core.opacity"
12706
+ })
12707
+ });
12708
+ var tileflowDefaultRouteInks = Object.freeze({
12709
+ light: Object.freeze({
12710
+ line: "#2563eb",
12711
+ casing: "#1e3a8a",
12712
+ core: "#dbeafe",
12713
+ glow: "#2563eb",
12714
+ alternative: "#94a3b8",
12715
+ glowOpacity: 0.16,
12716
+ coreOpacity: 0
12717
+ }),
12718
+ dark: Object.freeze({
12719
+ line: "#60a5fa",
12720
+ casing: "#0b1220",
12721
+ core: "#eff6ff",
12722
+ glow: "#60a5fa",
12723
+ alternative: "#64748b",
12724
+ glowOpacity: 0.4,
12725
+ coreOpacity: 0.7
12726
+ })
12727
+ });
12728
+ var colourKeys = Object.keys(
12729
+ tileflowRouteTokens.color
12730
+ );
12731
+ var numberKeys = Object.keys(
12732
+ tileflowRouteTokens.number
12733
+ );
12734
+ function resolveTileflowRouteInks(theme) {
12735
+ const defaults4 = tileflowDefaultRouteInks[theme.colorScheme];
12736
+ const inks = { ...defaults4 };
12737
+ for (const key of colourKeys) {
12738
+ const value = theme.tokens.color[tileflowRouteTokens.color[key]];
12739
+ if (value !== void 0) inks[key] = value;
12740
+ }
12741
+ for (const key of numberKeys) {
12742
+ const token = tileflowRouteTokens.number[key];
12743
+ const value = theme.tokens.number[token];
12744
+ if (value === void 0) continue;
12745
+ if (!(value >= 0 && value <= 1)) {
12746
+ throw new Error(
12747
+ `Tileflow theme "${theme.id}" sets number token "${token}" to ${value}; route opacities run from 0 to 1.`
12748
+ );
12749
+ }
12750
+ inks[key] = value;
12751
+ }
12752
+ return Object.freeze(inks);
12753
+ }
12754
+ function tileflowRouteMetadata(inks) {
12755
+ return Object.freeze({ version: 1, ...inks });
12756
+ }
12757
+
12186
12758
  // src/terrain/index.ts
12187
12759
  function resolveTerrain(terrain, apiBaseUrl) {
12188
12760
  if (!terrain || terrain === "none") return void 0;
@@ -12837,11 +13409,11 @@ function isTileflowPoiCategory(value) {
12837
13409
  return typeof value === "string" && tileflowPoiCategories.includes(value);
12838
13410
  }
12839
13411
  function semanticTarget(layer) {
12840
- if (!isRecord7(layer.metadata)) return void 0;
13412
+ if (!isRecord8(layer.metadata)) return void 0;
12841
13413
  const value = layer.metadata[tileflowCompilerMetadataKeys.target];
12842
13414
  return typeof value === "string" ? value : void 0;
12843
13415
  }
12844
- function isRecord7(value) {
13416
+ function isRecord8(value) {
12845
13417
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
12846
13418
  }
12847
13419
  function isSafeArtifactName(value) {
@@ -12851,6 +13423,47 @@ function isSafeArtifactName(value) {
12851
13423
  });
12852
13424
  }
12853
13425
 
13426
+ // src/cartography/layout-zoom-diagnostics.ts
13427
+ function diagnoseFractionalLayoutZoomSteps(layers) {
13428
+ const diagnostics = [];
13429
+ for (const layer of layers) {
13430
+ const layout = layer.layout;
13431
+ if (layout === null || typeof layout !== "object") continue;
13432
+ for (const [property, value] of Object.entries(layout)) {
13433
+ const stops = fractionalZoomSteps(value);
13434
+ if (stops.length === 0) continue;
13435
+ const [origin] = readTileflowCompilerProvenance(layer);
13436
+ const listed = stops.map((stop) => String(stop)).join(", ");
13437
+ diagnostics.push({
13438
+ code: "TF_LAYOUT_FRACTIONAL_ZOOM_STEP",
13439
+ ...origin ? { domain: origin.owner } : {},
13440
+ message: `${property} steps at zoom ${listed}; layout properties are evaluated at each tile's integer zoom, so the step takes effect at zoom ${stops.map(Math.ceil).join(", ")}.`,
13441
+ path: `layers.${String(layer.id)}.layout.${property}`,
13442
+ phase: "lowering",
13443
+ severity: "warning",
13444
+ suggestion: "Step at whole zooms, or move the change to a paint property or a zoom range.",
13445
+ ...origin ? { target: origin.target } : {}
13446
+ });
13447
+ }
13448
+ }
13449
+ return diagnostics;
13450
+ }
13451
+ function fractionalZoomSteps(value) {
13452
+ if (!Array.isArray(value)) return [];
13453
+ const stops = [];
13454
+ if (value[0] === "step" && isZoom(value[1])) {
13455
+ for (let index = 3; index < value.length; index += 2) {
13456
+ const stop = value[index];
13457
+ if (typeof stop === "number" && !Number.isInteger(stop)) stops.push(stop);
13458
+ }
13459
+ }
13460
+ for (const entry of value) stops.push(...fractionalZoomSteps(entry));
13461
+ return stops;
13462
+ }
13463
+ function isZoom(value) {
13464
+ return Array.isArray(value) && value.length === 1 && value[0] === "zoom";
13465
+ }
13466
+
12854
13467
  // src/cartography/overlay-placement-manifest.ts
12855
13468
  var tileflowOverlayPlacementManifestMetadataKey = "tileflow:overlay-placement-manifest";
12856
13469
  var tileflowOverlayPlacementManifestSchemaVersion = 1;
@@ -13824,8 +14437,19 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
13824
14437
  "domains",
13825
14438
  () => compileSemanticDomains(modules, context)
13826
14439
  );
14440
+ const vegetation3D = runCompilationPhase(
14441
+ "domains",
14442
+ () => withPreparedTreeModels(
14443
+ tileflowVegetationDescription(
14444
+ modules.vegetation,
14445
+ context
14446
+ ),
14447
+ config,
14448
+ options.preparedAssets
14449
+ )
14450
+ );
13827
14451
  const renderStackModules = Object.values(modules).filter(
13828
- (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)
13829
14453
  );
13830
14454
  const renderOperations = runCompilationPhase(
13831
14455
  "render-stack",
@@ -13985,6 +14609,15 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
13985
14609
  () => createTileflowOverlayPlacementManifest(plannedLayers)
13986
14610
  );
13987
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
+ );
13988
14621
  runCompilationPhase(
13989
14622
  "finalization",
13990
14623
  () => assertTileflowInteractionManifestLayers(interactionManifest, layers)
@@ -14055,13 +14688,35 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
14055
14688
  ...atmosphere ? { [tileflowAtmosphereMetadataKey]: { version: 1, ...atmosphere } } : {},
14056
14689
  "tileflow:theme": selected.name,
14057
14690
  "tileflow:colorScheme": resolvedTheme.colorScheme,
14691
+ [tileflowRouteMetadataKey]: tileflowRouteMetadata(resolveTileflowRouteInks(resolvedTheme)),
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
+ } : {},
14058
14713
  "tileflow:data": data.identity,
14059
14714
  "tileflow:sources": sourceIdentities,
14060
14715
  ...Object.keys(loweredOverlays.identities).length > 0 ? { "tileflow:overlays": loweredOverlays.identities } : {},
14061
14716
  "tileflow:sourceRequirements": sourceRequirements,
14062
14717
  ...interactionManifest ? { [tileflowInteractionManifestMetadataKey]: interactionManifest } : {},
14063
14718
  [tileflowOverlayPlacementManifestMetadataKey]: overlayPlacementManifest,
14064
- "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(),
14065
14720
  ...config.view ? { "tileflow:view": config.view } : {}
14066
14721
  }
14067
14722
  };
@@ -14081,7 +14736,16 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
14081
14736
  runCompilationPhase("assets", () => assertTextAssets(style, config));
14082
14737
  runCompilationPhase("assets", () => assertGlyphFontStacks(style, config));
14083
14738
  runCompilationPhase("validation", () => assertMapLibreStyle(style, config.id));
14084
- return { diagnostics, report, style, ...inspection ? { inspection } : {} };
14739
+ const layoutDiagnostics = runCompilationPhase(
14740
+ "validation",
14741
+ () => diagnoseFractionalLayoutZoomSteps(plannedLayers)
14742
+ );
14743
+ return {
14744
+ diagnostics: [...diagnostics, ...layoutDiagnostics],
14745
+ report,
14746
+ style,
14747
+ ...inspection ? { inspection } : {}
14748
+ };
14085
14749
  }
14086
14750
  function finalizeTileflowLayers(input) {
14087
14751
  const ids = /* @__PURE__ */ new Set();
@@ -14104,7 +14768,7 @@ function finalizeTileflowLayers(input) {
14104
14768
  if (typeof minimum2 === "number" && typeof maximum2 === "number" && minimum2 > maximum2) {
14105
14769
  throw new Error(`Compiled Tileflow layer ${id2} requires minzoom <= maxzoom.`);
14106
14770
  }
14107
- const metadata = isRecord8(layer.metadata) ? { ...layer.metadata } : void 0;
14771
+ const metadata = isRecord9(layer.metadata) ? { ...layer.metadata } : void 0;
14108
14772
  if (metadata) {
14109
14773
  const owner = metadata[tileflowCompilerMetadataKeys.owner];
14110
14774
  delete metadata[tileflowCompilerProvenanceMetadataKey];
@@ -14138,13 +14802,32 @@ function assertMapLibreStyle(style, mapId) {
14138
14802
  }
14139
14803
  );
14140
14804
  }
14141
- function isRecord8(value) {
14805
+ function isRecord9(value) {
14142
14806
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
14143
14807
  }
14144
14808
  function resolveGlyphs(config) {
14145
14809
  if (config.fonts !== void 0) return void 0;
14146
14810
  return config.glyphs?.url;
14147
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
+ }
14148
14831
  function assertPreparedIconReferences(style, config, prepared) {
14149
14832
  const references = collectStyleImageReferences(style);
14150
14833
  if (references.size === 0) return;
@@ -14159,8 +14842,8 @@ function assertPreparedIconReferences(style, config, prepared) {
14159
14842
  function collectStyleImageReferences(style) {
14160
14843
  const result = /* @__PURE__ */ new Set();
14161
14844
  for (const layer of style.layers) {
14162
- const layout = isRecord8(layer.layout) ? layer.layout : {};
14163
- const paint = isRecord8(layer.paint) ? layer.paint : {};
14845
+ const layout = isRecord9(layer.layout) ? layer.layout : {};
14846
+ const paint = isRecord9(layer.paint) ? layer.paint : {};
14164
14847
  const layerId = typeof layer.id === "string" ? layer.id : "<unknown>";
14165
14848
  for (const [property, value] of [
14166
14849
  ["icon-image", layout["icon-image"]],
@@ -14249,7 +14932,7 @@ function assertGlyphFontStacks(style, config) {
14249
14932
  const declared = new Set(config.glyphs.fontStacks);
14250
14933
  const missing = /* @__PURE__ */ new Set();
14251
14934
  for (const layer of style.layers) {
14252
- const layout = isRecord8(layer.layout) ? layer.layout : void 0;
14935
+ const layout = isRecord9(layer.layout) ? layer.layout : void 0;
14253
14936
  const font = layout?.["text-font"];
14254
14937
  if (!Array.isArray(font) || !font.every((entry) => typeof entry === "string")) continue;
14255
14938
  const stack = font.join(",");
@@ -14265,7 +14948,7 @@ function assertGlyphFontStacks(style, config) {
14265
14948
  function assertTextAssets(style, config) {
14266
14949
  const textLayers = [];
14267
14950
  for (const layer of style.layers) {
14268
- const layout = isRecord8(layer.layout) ? layer.layout : void 0;
14951
+ const layout = isRecord9(layer.layout) ? layer.layout : void 0;
14269
14952
  if (layout?.["text-field"] === void 0) continue;
14270
14953
  textLayers.push(typeof layer.id === "string" ? layer.id : "<unknown>");
14271
14954
  const font = layout["text-font"];
@@ -14278,7 +14961,7 @@ function assertTextAssets(style, config) {
14278
14961
  if (textLayers.length === 0) return;
14279
14962
  if (config.fonts !== void 0) {
14280
14963
  const backed = style.layers.find((layer) => {
14281
- const font = isRecord8(layer.layout) ? layer.layout["text-font"] : void 0;
14964
+ const font = isRecord9(layer.layout) ? layer.layout["text-font"] : void 0;
14282
14965
  return Array.isArray(font) && font.length === 1 && typeof font[0] === "string" && parseTileflowLineBackgroundFontStack(font[0]) !== void 0;
14283
14966
  });
14284
14967
  if (backed) {
@@ -14304,7 +14987,7 @@ function runCompilationPhase(phase, operation) {
14304
14987
  }
14305
14988
  }
14306
14989
  function withCompilationPhase(error, phase) {
14307
- const record3 = isRecord8(error) ? error : {};
14990
+ const record3 = isRecord9(error) ? error : {};
14308
14991
  if (typeof record3.phase === "string") return error;
14309
14992
  if (error instanceof Error && Object.isExtensible(error)) {
14310
14993
  Object.defineProperty(error, "phase", { configurable: true, enumerable: true, value: phase });
@@ -14726,6 +15409,97 @@ function hasControlCharacter(value) {
14726
15409
  return false;
14727
15410
  }
14728
15411
 
15412
+ // src/capture-shot.ts
15413
+ import { z as z9 } from "zod";
15414
+ var tileflowCaptureShotLimits = Object.freeze({
15415
+ blendThemes: Object.freeze({ maximum: 8, minimum: 2 }),
15416
+ defaultFps: 30,
15417
+ durationMs: Object.freeze({ maximum: 6e4 }),
15418
+ fps: Object.freeze([24, 25, 30, 50, 60]),
15419
+ keyframes: Object.freeze({ maximum: 64, minimum: 2 }),
15420
+ maximumFrames: 3600
15421
+ });
15422
+ var tileflowCaptureShotBlendSchema = z9.object({
15423
+ themes: z9.array(tileflowThemeNameSchema).min(tileflowCaptureShotLimits.blendThemes.minimum).max(tileflowCaptureShotLimits.blendThemes.maximum),
15424
+ position: z9.number().finite().min(0)
15425
+ }).strict().superRefine((blend, context) => {
15426
+ if (new Set(blend.themes).size !== blend.themes.length) {
15427
+ context.addIssue({ code: "custom", message: "Expected distinct themes", path: ["themes"] });
15428
+ }
15429
+ if (blend.position > blend.themes.length - 1) {
15430
+ context.addIssue({
15431
+ code: "custom",
15432
+ message: `Expected a position from 0 to ${blend.themes.length - 1}`,
15433
+ path: ["position"]
15434
+ });
15435
+ }
15436
+ });
15437
+ var tileflowCaptureShotKeyframeSchema = z9.object({
15438
+ at: z9.number().int().min(0).max(tileflowCaptureShotLimits.durationMs.maximum),
15439
+ camera: tileflowCaptureCenterCameraSchema,
15440
+ blend: tileflowCaptureShotBlendSchema.optional()
15441
+ }).strict();
15442
+ var tileflowCaptureShotFields = {
15443
+ theme: tileflowThemeNameSchema,
15444
+ viewport: tileflowCaptureViewportSchema,
15445
+ fps: z9.literal(tileflowCaptureShotLimits.fps).optional(),
15446
+ keyframes: z9.array(tileflowCaptureShotKeyframeSchema).min(tileflowCaptureShotLimits.keyframes.minimum).max(tileflowCaptureShotLimits.keyframes.maximum),
15447
+ easing: z9.enum(["smooth", "linear"]).optional()
15448
+ };
15449
+ var tileflowCaptureShotSchema = z9.object({ map: tileflowPortableIdSchema, ...tileflowCaptureShotFields }).strict().superRefine(checkShotTiming);
15450
+ var tileflowMapShotSchema = z9.object(tileflowCaptureShotFields).strict().superRefine(checkShotTiming);
15451
+ function tileflowCaptureShotFrameCount(shot) {
15452
+ const fps = shot.fps ?? tileflowCaptureShotLimits.defaultFps;
15453
+ const duration = shot.keyframes.at(-1)?.at ?? 0;
15454
+ return Math.floor(duration * fps / 1e3 + 1e-9) + 1;
15455
+ }
15456
+ function checkShotTiming(shot, context) {
15457
+ const { keyframes } = shot;
15458
+ if (keyframes[0] && keyframes[0].at !== 0) {
15459
+ context.addIssue({
15460
+ code: "custom",
15461
+ message: "Expected the first keyframe at 0 milliseconds",
15462
+ path: ["keyframes", 0, "at"]
15463
+ });
15464
+ }
15465
+ for (let index = 1; index < keyframes.length; index += 1) {
15466
+ if (keyframes[index].at <= keyframes[index - 1].at) {
15467
+ context.addIssue({
15468
+ code: "custom",
15469
+ message: "Expected a time after the previous keyframe",
15470
+ path: ["keyframes", index, "at"]
15471
+ });
15472
+ }
15473
+ }
15474
+ const blended = keyframes.filter((keyframe) => keyframe.blend !== void 0);
15475
+ if (blended.length > 0) {
15476
+ const themes = JSON.stringify(blended[0].blend.themes);
15477
+ keyframes.forEach((keyframe, index) => {
15478
+ if (!keyframe.blend) {
15479
+ context.addIssue({
15480
+ code: "custom",
15481
+ message: "Expected a blend on every keyframe when one keyframe blends themes",
15482
+ path: ["keyframes", index, "blend"]
15483
+ });
15484
+ } else if (JSON.stringify(keyframe.blend.themes) !== themes) {
15485
+ context.addIssue({
15486
+ code: "custom",
15487
+ message: "Expected the same themes, in the same order, on every keyframe",
15488
+ path: ["keyframes", index, "blend", "themes"]
15489
+ });
15490
+ }
15491
+ });
15492
+ }
15493
+ const frames = tileflowCaptureShotFrameCount(shot);
15494
+ if (frames > tileflowCaptureShotLimits.maximumFrames) {
15495
+ context.addIssue({
15496
+ code: "custom",
15497
+ message: `Expected at most ${tileflowCaptureShotLimits.maximumFrames} frames; this shot renders ${frames}`,
15498
+ path: ["keyframes", keyframes.length - 1, "at"]
15499
+ });
15500
+ }
15501
+ }
15502
+
14729
15503
  // src/maps/operations.ts
14730
15504
  var resetValue = deepFreeze({ $tileflow: "reset" });
14731
15505
  var disableValue = deepFreeze({ op: "disable" });
@@ -14773,11 +15547,13 @@ var tileflowMapMergeStrategies = {
14773
15547
  icons: "icons",
14774
15548
  id: "identity",
14775
15549
  marine: "atomic",
15550
+ models: "atomic",
14776
15551
  modules: "modules",
14777
15552
  name: "identity",
14778
15553
  overlays: "keyed-resources",
14779
15554
  projection: "atomic",
14780
15555
  scenes: "leaf",
15556
+ shots: "leaf",
14781
15557
  sources: "keyed-resources",
14782
15558
  systemThemes: "atomic",
14783
15559
  terrain: "atomic",
@@ -15117,11 +15893,11 @@ function cloneDesign(value) {
15117
15893
  function describeMap(value) {
15118
15894
  return typeof value.id === "string" && value.id ? value.id : "<anonymous>";
15119
15895
  }
15120
- function isRecord9(value) {
15896
+ function isRecord10(value) {
15121
15897
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
15122
15898
  }
15123
15899
  function isPlainRecord4(value) {
15124
- if (!isRecord9(value)) return false;
15900
+ if (!isRecord10(value)) return false;
15125
15901
  const prototype = Object.getPrototypeOf(value);
15126
15902
  return prototype === Object.prototype || prototype === null;
15127
15903
  }
@@ -15154,6 +15930,7 @@ function parseTileflowMap(map) {
15154
15930
  for (const node2 of lineage.reverse()) {
15155
15931
  const resolved = node2 === map ? resolvedLeaf : resolveMap(node2);
15156
15932
  validateMapScenes(node2, resolved.id);
15933
+ validateMapShots(node2, resolved.id);
15157
15934
  const parsed = parseResolvedTileflowMap(resolved);
15158
15935
  if (node2 === map) parsedLeaf = parsed;
15159
15936
  }
@@ -15161,40 +15938,53 @@ function parseTileflowMap(map) {
15161
15938
  return parsedLeaf;
15162
15939
  }
15163
15940
  function validateMapScenes(map, mapId) {
15164
- if (map.scenes === void 0) return;
15165
- if (!isPlainRecord5(map.scenes)) {
15166
- throw new Error(`Invalid Tileflow map "${map.id}". scenes: Expected a plain object.`);
15941
+ validateMapCaptureEntries(map, mapId, "scenes", (input) => {
15942
+ const result = tileflowCaptureSceneSchema.safeParse(input);
15943
+ return result.success ? tileflowCaptureSceneFitIssue(result.data) : result.error.issues[0];
15944
+ });
15945
+ }
15946
+ function validateMapShots(map, mapId) {
15947
+ validateMapCaptureEntries(map, mapId, "shots", (input) => {
15948
+ const result = tileflowCaptureShotSchema.safeParse(input);
15949
+ return result.success ? void 0 : result.error.issues[0];
15950
+ });
15951
+ }
15952
+ function validateMapCaptureEntries(map, mapId, key, findIssue) {
15953
+ const entries = map[key];
15954
+ if (entries === void 0) return;
15955
+ if (!isPlainRecord5(entries)) {
15956
+ throw new Error(`Invalid Tileflow map "${map.id}". ${key}: Expected a plain object.`);
15167
15957
  }
15958
+ const label = key === "scenes" ? "scene" : "shot";
15168
15959
  const namesByCaseFold = /* @__PURE__ */ new Map();
15169
- for (const [sceneName, input] of Object.entries(map.scenes)) {
15170
- const nameResult = tileflowCaptureSceneNameSchema.safeParse(sceneName);
15960
+ for (const [name, input] of Object.entries(entries)) {
15961
+ const nameResult = tileflowCaptureSceneNameSchema.safeParse(name);
15171
15962
  if (!nameResult.success) {
15172
15963
  throw new Error(
15173
- `Invalid Tileflow map "${map.id}". scenes.${sceneName}: ${nameResult.error.issues[0]?.message ?? "Invalid scene name"}`
15964
+ `Invalid Tileflow map "${map.id}". ${key}.${name}: ${nameResult.error.issues[0]?.message ?? `Invalid ${label} name`}`
15174
15965
  );
15175
15966
  }
15176
- const normalizedSceneName = nameResult.data;
15177
- const folded = normalizedSceneName.toLowerCase();
15967
+ const normalizedName = nameResult.data;
15968
+ const folded = normalizedName.toLowerCase();
15178
15969
  const existing = namesByCaseFold.get(folded);
15179
15970
  if (existing) {
15180
15971
  throw new Error(
15181
- `Invalid Tileflow map "${map.id}". scenes.${sceneName}: Expected an id that does not normalize to "${existing}".`
15972
+ `Invalid Tileflow map "${map.id}". ${key}.${name}: Expected an id that does not normalize to "${existing}".`
15182
15973
  );
15183
15974
  }
15184
- namesByCaseFold.set(folded, normalizedSceneName);
15975
+ namesByCaseFold.set(folded, normalizedName);
15185
15976
  if (!isPlainRecord5(input)) {
15186
- throw new Error(`Invalid Tileflow map "${map.id}". scenes.${sceneName}: Expected an object.`);
15977
+ throw new Error(`Invalid Tileflow map "${map.id}". ${key}.${name}: Expected an object.`);
15187
15978
  }
15188
15979
  if (Object.hasOwn(input, "map")) {
15189
15980
  throw new Error(
15190
- `Invalid Tileflow map "${map.id}". scenes.${sceneName}.map: Unrecognized key; a singular map scene inherits its map id.`
15981
+ `Invalid Tileflow map "${map.id}". ${key}.${name}.map: Unrecognized key; a singular map ${label} inherits its map id.`
15191
15982
  );
15192
15983
  }
15193
- const result = tileflowCaptureSceneSchema.safeParse({ ...input, map: mapId });
15194
- const issue = result.success ? tileflowCaptureSceneFitIssue(result.data) : result.error.issues[0];
15195
- if (!result.success || issue) {
15984
+ const issue = findIssue({ ...input, map: mapId });
15985
+ if (issue) {
15196
15986
  throw new Error(
15197
- `Invalid Tileflow map "${map.id}". scenes.${sceneName}${issue?.path.length ? `.${issue.path.join(".")}` : ""}: ${issue?.message ?? "Invalid scene"}`
15987
+ `Invalid Tileflow map "${map.id}". ${key}.${name}${issue.path.length ? `.${issue.path.join(".")}` : ""}: ${issue.message}`
15198
15988
  );
15199
15989
  }
15200
15990
  }
@@ -15206,10 +15996,10 @@ function isPlainRecord5(value) {
15206
15996
  }
15207
15997
 
15208
15998
  // src/icon-composition.ts
15209
- import { z as z10 } from "zod";
15999
+ import { z as z11 } from "zod";
15210
16000
 
15211
16001
  // src/icon-package.ts
15212
- import { z as z9 } from "zod";
16002
+ import { z as z10 } from "zod";
15213
16003
  var tileflowIconIdPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
15214
16004
  var tileflowIconPackageLimits = {
15215
16005
  decodeConcurrency: 4,
@@ -15228,19 +16018,19 @@ var tileflowHostedAlphaCompatibility = {
15228
16018
  },
15229
16019
  maxMapsPerDeploy: 20
15230
16020
  };
15231
- var sha256Schema = z9.string().regex(/^[a-f0-9]{64}$/);
16021
+ var sha256Schema = z10.string().regex(/^[a-f0-9]{64}$/);
15232
16022
  var tileflowIconPackageContentHashSchema = sha256Schema;
15233
- var tileflowIconIdSchema = z9.string().max(64).regex(tileflowIconIdPattern);
15234
- var tileflowRenderedIconManifestEntrySchema = z9.object({
16023
+ var tileflowIconIdSchema = z10.string().max(64).regex(tileflowIconIdPattern);
16024
+ var tileflowRenderedIconManifestEntrySchema = z10.object({
15235
16025
  name: tileflowIconIdSchema,
15236
16026
  appearance: tileflowIconAppearanceSchema.optional(),
15237
16027
  layout: tileflowIconLayoutSchema.optional(),
15238
- pixelSha256: z9.object({
16028
+ pixelSha256: z10.object({
15239
16029
  oneX: sha256Schema,
15240
16030
  twoX: sha256Schema
15241
16031
  }).strict()
15242
16032
  }).strict();
15243
- var tileflowIconPackageLabelSchema = z9.string().superRefine((value, context) => {
16033
+ var tileflowIconPackageLabelSchema = z10.string().superRefine((value, context) => {
15244
16034
  if (value !== value.trim() || value.length === 0) {
15245
16035
  context.addIssue({
15246
16036
  code: "custom",
@@ -15260,32 +16050,32 @@ var tileflowIconPackageLabelSchema = z9.string().superRefine((value, context) =>
15260
16050
  });
15261
16051
  }
15262
16052
  });
15263
- var spriteDimensionsSchema = z9.object({
15264
- height: z9.number().int().positive().max(tileflowIconPackageLimits.maxAtlasDimension),
15265
- pixelRatio: z9.union([z9.literal(1), z9.literal(2)]),
15266
- width: z9.number().int().positive().max(tileflowIconPackageLimits.maxAtlasDimension)
16053
+ var spriteDimensionsSchema = z10.object({
16054
+ height: z10.number().int().positive().max(tileflowIconPackageLimits.maxAtlasDimension),
16055
+ pixelRatio: z10.union([z10.literal(1), z10.literal(2)]),
16056
+ width: z10.number().int().positive().max(tileflowIconPackageLimits.maxAtlasDimension)
15267
16057
  }).strict();
15268
16058
  function iconPackageFileSchema(name, contentType) {
15269
- return z9.object({
15270
- byteLength: z9.number().int().nonnegative().max(tileflowIconPackageLimits.maxGeneratedFileBytes),
15271
- contentType: z9.literal(contentType),
15272
- name: z9.literal(name),
16059
+ return z10.object({
16060
+ byteLength: z10.number().int().nonnegative().max(tileflowIconPackageLimits.maxGeneratedFileBytes),
16061
+ contentType: z10.literal(contentType),
16062
+ name: z10.literal(name),
15273
16063
  sha256: sha256Schema
15274
16064
  }).strict();
15275
16065
  }
15276
- var tileflowIconPackageManifestSchema = z9.object({
15277
- files: z9.tuple([
16066
+ var tileflowIconPackageManifestSchema = z10.object({
16067
+ files: z10.tuple([
15278
16068
  iconPackageFileSchema("sprite.json", "application/json"),
15279
16069
  iconPackageFileSchema("sprite.png", "image/png"),
15280
16070
  iconPackageFileSchema("sprite@2x.json", "application/json"),
15281
16071
  iconPackageFileSchema("sprite@2x.png", "image/png")
15282
16072
  ]),
15283
- format: z9.enum(["tileflow-icon-package-v1", "tileflow-icon-package-v2"]),
15284
- iconNames: z9.array(tileflowIconIdSchema).min(1).max(tileflowIconPackageLimits.maxIconCount),
15285
- renderedIcons: z9.array(tileflowRenderedIconManifestEntrySchema).min(1).max(tileflowIconPackageLimits.maxIconCount),
15286
- sprites: z9.object({
15287
- oneX: spriteDimensionsSchema.extend({ pixelRatio: z9.literal(1) }),
15288
- twoX: spriteDimensionsSchema.extend({ pixelRatio: z9.literal(2) })
16073
+ format: z10.enum(["tileflow-icon-package-v1", "tileflow-icon-package-v2"]),
16074
+ iconNames: z10.array(tileflowIconIdSchema).min(1).max(tileflowIconPackageLimits.maxIconCount),
16075
+ renderedIcons: z10.array(tileflowRenderedIconManifestEntrySchema).min(1).max(tileflowIconPackageLimits.maxIconCount),
16076
+ sprites: z10.object({
16077
+ oneX: spriteDimensionsSchema.extend({ pixelRatio: z10.literal(1) }),
16078
+ twoX: spriteDimensionsSchema.extend({ pixelRatio: z10.literal(2) })
15289
16079
  }).strict()
15290
16080
  }).strict().superRefine((manifest, context) => {
15291
16081
  if (manifest.renderedIcons.some(
@@ -15384,57 +16174,57 @@ function containsControlCharacter(value) {
15384
16174
 
15385
16175
  // src/icon-composition.ts
15386
16176
  var tileflowIconCompositionMaximumBytes = 1024 * 1024;
15387
- var idsSchema = z10.array(tileflowIconIdSchema).max(tileflowIconPackageLimits.maxIconCount).refine(
16177
+ var idsSchema = z11.array(tileflowIconIdSchema).max(tileflowIconPackageLimits.maxIconCount).refine(
15388
16178
  (ids) => new Set(ids).size === ids.length && ids.every((id2, index) => index === 0 || compareCodeUnits8(ids[index - 1], id2) < 0),
15389
16179
  "Icon exports must be unique and sorted by code unit"
15390
16180
  );
15391
- var localContributorSchema = z10.object({ kind: z10.literal("local"), iconIds: idsSchema }).strict();
15392
- var packageContributorSchema = z10.object({
15393
- kind: z10.literal("package"),
15394
- package: z10.string().max(214).regex(/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u),
16181
+ var localContributorSchema = z11.object({ kind: z11.literal("local"), iconIds: idsSchema }).strict();
16182
+ var packageContributorSchema = z11.object({
16183
+ kind: z11.literal("package"),
16184
+ package: z11.string().max(214).regex(/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u),
15395
16185
  iconIds: idsSchema
15396
16186
  }).strict();
15397
- var setContributorSchema = z10.object({
15398
- kind: z10.literal("icon-set"),
16187
+ var setContributorSchema = z11.object({
16188
+ kind: z11.literal("icon-set"),
15399
16189
  reference: tileflowIconSetReferenceSchema,
15400
16190
  teamId: tileflowIconSetTeamIdSchema,
15401
16191
  setId: tileflowIconSetIdSchema,
15402
16192
  versionId: tileflowIconSetVersionIdSchema,
15403
- version: z10.number().int().positive().max(Number.MAX_SAFE_INTEGER),
16193
+ version: z11.number().int().positive().max(Number.MAX_SAFE_INTEGER),
15404
16194
  packageId: tileflowIconSetPackageIdSchema,
15405
16195
  contentHash: tileflowIconPackageContentHashSchema,
15406
16196
  iconIds: idsSchema.min(1)
15407
16197
  }).strict();
15408
- var tileflowRenderedIconIdentitySchema = z10.object({
15409
- kind: z10.literal("rendered-icon"),
16198
+ var tileflowRenderedIconIdentitySchema = z11.object({
16199
+ kind: z11.literal("rendered-icon"),
15410
16200
  id: tileflowIconIdSchema,
15411
16201
  appearance: tileflowIconAppearanceSchema.optional(),
15412
16202
  layout: tileflowIconLayoutSchema.optional(),
15413
- width: z10.number().int().positive().max(tileflowIconPackageLimits.maxAtlasDimension / 2),
15414
- height: z10.number().int().positive().max(tileflowIconPackageLimits.maxAtlasDimension / 2),
15415
- pixelSha256: z10.object({
16203
+ width: z11.number().int().positive().max(tileflowIconPackageLimits.maxAtlasDimension / 2),
16204
+ height: z11.number().int().positive().max(tileflowIconPackageLimits.maxAtlasDimension / 2),
16205
+ pixelSha256: z11.object({
15416
16206
  oneX: tileflowIconPackageContentHashSchema,
15417
16207
  twoX: tileflowIconPackageContentHashSchema
15418
16208
  }).strict()
15419
16209
  }).strict();
15420
- var tileflowIconCompositionSchema = z10.object({
16210
+ var tileflowIconCompositionSchema = z11.object({
15421
16211
  // An explicit map keeps declaration order independent of TypeScript's literal cache.
15422
- format: z10.enum({
16212
+ format: z11.enum({
15423
16213
  "tileflow-icon-composition-v1": "tileflow-icon-composition-v1",
15424
16214
  "tileflow-icon-composition-v2": "tileflow-icon-composition-v2"
15425
16215
  }),
15426
- compositionVersion: z10.union([z10.literal(1), z10.literal(2)]),
16216
+ compositionVersion: z11.union([z11.literal(1), z11.literal(2)]),
15427
16217
  packageHash: tileflowIconPackageContentHashSchema,
15428
- contributors: z10.array(
15429
- z10.discriminatedUnion("kind", [
16218
+ contributors: z11.array(
16219
+ z11.discriminatedUnion("kind", [
15430
16220
  localContributorSchema,
15431
16221
  packageContributorSchema,
15432
16222
  setContributorSchema
15433
16223
  ])
15434
16224
  ).min(1).max(tileflowIconSourceLimit),
15435
- winners: z10.array(
16225
+ winners: z11.array(
15436
16226
  tileflowRenderedIconIdentitySchema.omit({ kind: true }).extend({
15437
- contributor: z10.number().int().min(0).max(tileflowIconSourceLimit - 1)
16227
+ contributor: z11.number().int().min(0).max(tileflowIconSourceLimit - 1)
15438
16228
  })
15439
16229
  ).min(1).max(tileflowIconPackageLimits.maxIconCount)
15440
16230
  }).strict().superRefine((receipt, context) => {
@@ -15625,6 +16415,7 @@ async function hashTileflowMapRevision(input, sourceAssets) {
15625
16415
  const {
15626
16416
  fonts: _fonts,
15627
16417
  icons: _icons,
16418
+ models: _models,
15628
16419
  id: _id,
15629
16420
  name: _name,
15630
16421
  version: _version,
@@ -15720,10 +16511,26 @@ function normalizeSourceAssets(input) {
15720
16511
  if (new Set(fonts.map(fontIdentity)).size !== fonts.length) {
15721
16512
  throw new Error("Effective Tileflow font source identities must have unique faces.");
15722
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
+ }
15723
16525
  const iconComposition = input.iconComposition === void 0 ? void 0 : parseTileflowIconComposition(input.iconComposition);
15724
16526
  if (icons.some((icon) => icon.kind === "rendered-icon") && !iconComposition)
15725
16527
  throw new Error("Rendered icon identities require an explicit composition receipt");
15726
- return { fonts, icons, ...iconComposition ? { iconComposition } : {} };
16528
+ return {
16529
+ fonts,
16530
+ icons,
16531
+ ...iconComposition ? { iconComposition } : {},
16532
+ ...models.length > 0 ? { models } : {}
16533
+ };
15727
16534
  }
15728
16535
  function fontIdentity(font) {
15729
16536
  return `${font.family}\0${font.style}\0${font.weight}`;
@@ -15786,23 +16593,23 @@ function compileTileflowMapWithInspection(config, options) {
15786
16593
  }
15787
16594
 
15788
16595
  // src/renderer-deployment.ts
15789
- import { z as z14 } from "zod";
16596
+ import { z as z15 } from "zod";
15790
16597
 
15791
16598
  // src/native-build-record.ts
15792
- import { z as z12 } from "zod";
16599
+ import { z as z13 } from "zod";
15793
16600
 
15794
16601
  // src/native-profile-definition.ts
15795
- import { z as z11 } from "zod";
16602
+ import { z as z12 } from "zod";
15796
16603
  var tileflowNativeProfileSchema = strictNativeObject({
15797
- id: z11.literal("native-v1"),
15798
- renderer: z11.literal("native"),
15799
- schemaVersion: z11.literal(1),
15800
- styleVersion: z11.literal(8),
15801
- validatorStyleSpec: z11.literal("24.8.5"),
15802
- reactNativeStyleSpec: z11.literal("26.2.1"),
15803
- maplibreReactNative: z11.literal("11.3.10"),
15804
- android: z11.literal("13.2.0"),
15805
- ios: z11.literal("6.26.0")
16604
+ id: z12.literal("native-v1"),
16605
+ renderer: z12.literal("native"),
16606
+ schemaVersion: z12.literal(1),
16607
+ styleVersion: z12.literal(8),
16608
+ validatorStyleSpec: z12.literal("24.8.5"),
16609
+ reactNativeStyleSpec: z12.literal("26.2.1"),
16610
+ maplibreReactNative: z12.literal("11.3.10"),
16611
+ android: z12.literal("13.2.0"),
16612
+ ios: z12.literal("6.26.0")
15806
16613
  });
15807
16614
  var tileflowNativeProfile = Object.freeze({
15808
16615
  id: "native-v1",
@@ -15815,8 +16622,8 @@ var tileflowNativeProfile = Object.freeze({
15815
16622
  android: "13.2.0",
15816
16623
  ios: "6.26.0"
15817
16624
  });
15818
- var tileflowNativeProfileIdSchema = z11.literal("native-v1");
15819
- var tileflowRendererSchema = z11.enum(["web", "native"]);
16625
+ var tileflowNativeProfileIdSchema = z12.literal("native-v1");
16626
+ var tileflowRendererSchema = z12.enum(["web", "native"]);
15820
16627
 
15821
16628
  // src/native-profile-helpers.ts
15822
16629
  var tileflowNativeProfileLimits = Object.freeze({
@@ -15897,20 +16704,20 @@ function isBoundedNativeJson(input, stage = "input") {
15897
16704
  }
15898
16705
 
15899
16706
  // src/native-build-record.ts
15900
- var hashSchema = z12.string().regex(/^[a-f0-9]{64}$/u);
15901
- var layerCountSchema = z12.number().int().min(0).max(tileflowNativeProfileLimits.maximumLayers);
16707
+ var hashSchema = z13.string().regex(/^[a-f0-9]{64}$/u);
16708
+ var layerCountSchema = z13.number().int().min(0).max(tileflowNativeProfileLimits.maximumLayers);
15902
16709
  var layerTransformationSchema = strictNativeObject({
15903
16710
  inputLayer: layerCountSchema,
15904
16711
  outputStart: layerCountSchema,
15905
- outputCount: z12.number().int().min(1).max(32),
15906
- properties: z12.array(z12.enum(["line-cap", "line-dasharray"])).min(1).max(2).refine(
16712
+ outputCount: z13.number().int().min(1).max(32),
16713
+ properties: z13.array(z13.enum(["line-cap", "line-dasharray"])).min(1).max(2).refine(
15907
16714
  (properties2) => properties2.length === 1 || properties2[0] === "line-cap" && properties2[1] === "line-dasharray",
15908
16715
  "Expected unique, ordered properties"
15909
16716
  )
15910
16717
  });
15911
16718
  var symbolOverlapTransformationSchema = strictNativeObject({
15912
16719
  inputLayer: layerCountSchema,
15913
- properties: z12.array(z12.enum(["icon-overlap", "text-overlap"])).min(1).max(2).refine(
16720
+ properties: z13.array(z13.enum(["icon-overlap", "text-overlap"])).min(1).max(2).refine(
15914
16721
  (properties2) => properties2.length === 1 || properties2[0] === "icon-overlap" && properties2[1] === "text-overlap",
15915
16722
  "Expected unique, ordered properties"
15916
16723
  )
@@ -15922,9 +16729,9 @@ var tileflowNativeStyleTransformationSchema = strictNativeObject({
15922
16729
  loweredStyleSha256: hashSchema,
15923
16730
  inputLayers: layerCountSchema,
15924
16731
  outputLayers: layerCountSchema,
15925
- projection: z12.enum(["none", "mercator-to-implicit", "globe-to-mercator"]),
15926
- layers: z12.array(layerTransformationSchema).max(tileflowNativeProfileLimits.maximumLayers),
15927
- symbolOverlap: z12.array(symbolOverlapTransformationSchema).max(tileflowNativeProfileLimits.maximumLayers)
16732
+ projection: z13.enum(["none", "mercator-to-implicit", "globe-to-mercator"]),
16733
+ layers: z13.array(layerTransformationSchema).max(tileflowNativeProfileLimits.maximumLayers),
16734
+ symbolOverlap: z13.array(symbolOverlapTransformationSchema).max(tileflowNativeProfileLimits.maximumLayers)
15928
16735
  }).superRefine((style, context) => {
15929
16736
  let previous = -1;
15930
16737
  let added = 0;
@@ -15960,14 +16767,14 @@ var tileflowNativeStyleTransformationSchema = strictNativeObject({
15960
16767
  }
15961
16768
  });
15962
16769
  var tileflowNativeBuildRecordSchema = strictNativeObject({
15963
- schemaVersion: z12.literal(2),
15964
- renderer: z12.literal("native"),
15965
- profile: z12.literal("native-v1"),
15966
- validation: z12.literal("static-artifacts"),
15967
- preparationVersion: z12.literal("native-lowering-v1"),
15968
- engines: strictNativeObject({ android: z12.literal("13.2.0"), ios: z12.literal("6.26.0") }),
16770
+ schemaVersion: z13.literal(2),
16771
+ renderer: z13.literal("native"),
16772
+ profile: z13.literal("native-v1"),
16773
+ validation: z13.literal("static-artifacts"),
16774
+ preparationVersion: z13.literal("native-lowering-v1"),
16775
+ engines: strictNativeObject({ android: z13.literal("13.2.0"), ios: z13.literal("6.26.0") }),
15969
16776
  buildManifestSha256: hashSchema,
15970
- transformations: z12.array(tileflowNativeStyleTransformationSchema).max(4096)
16777
+ transformations: z13.array(tileflowNativeStyleTransformationSchema).max(4096)
15971
16778
  }).superRefine((record3, context) => {
15972
16779
  let previous = "";
15973
16780
  for (const [index, item] of record3.transformations.entries()) {
@@ -24423,7 +25230,7 @@ var latest_default = {
24423
25230
  };
24424
25231
 
24425
25232
  // src/native-profile.ts
24426
- import { z as z13 } from "zod";
25233
+ import { z as z14 } from "zod";
24427
25234
 
24428
25235
  // src/native-url-policy.ts
24429
25236
  var tileflowNativeUrlLimits = Object.freeze({ maximumLength: 2048 });
@@ -24578,7 +25385,7 @@ var { resolveTileflowNativeResourceUrl } = createNativeUrlPolicy(
24578
25385
  );
24579
25386
 
24580
25387
  // src/native-profile.ts
24581
- var tileflowNativeDiagnosticCodeSchema = z13.enum([
25388
+ var tileflowNativeDiagnosticCodeSchema = z14.enum([
24582
25389
  "NATIVE_UNSUPPORTED_STYLE",
24583
25390
  "NATIVE_UNSUPPORTED_SOURCE",
24584
25391
  "NATIVE_FONT_UNAVAILABLE",
@@ -24587,13 +25394,13 @@ var tileflowNativeDiagnosticCodeSchema = z13.enum([
24587
25394
  ]);
24588
25395
  var tileflowNativeDiagnosticSchema = strictNativeObject({
24589
25396
  code: tileflowNativeDiagnosticCodeSchema,
24590
- phase: z13.literal("native-compatibility"),
24591
- severity: z13.literal("error"),
24592
- path: z13.string().max(300).regex(/^(?:\/(?:[^~]|~[01])*)?$/u),
24593
- renderer: z13.literal("native"),
25397
+ phase: z14.literal("native-compatibility"),
25398
+ severity: z14.literal("error"),
25399
+ path: z14.string().max(300).regex(/^(?:\/(?:[^~]|~[01])*)?$/u),
25400
+ renderer: z14.literal("native"),
24594
25401
  profile: tileflowNativeProfileIdSchema,
24595
- message: z13.string().max(300),
24596
- suggestion: z13.string().max(300)
25402
+ message: z14.string().max(300),
25403
+ suggestion: z14.string().max(300)
24597
25404
  });
24598
25405
  var descriptions = {
24599
25406
  NATIVE_UNSUPPORTED_STYLE: [
@@ -24973,24 +25780,24 @@ var tileflowRendererDeploymentLimits = Object.freeze({
24973
25780
  maximumNativeFamilyBytes: tileflowNativeProfileLimits.maximumStyleBytes,
24974
25781
  maximumThemes: 64
24975
25782
  });
24976
- var hashSchema2 = z14.string().regex(/^[a-f0-9]{64}$/u);
24977
- var recordSchema = z14.record(z14.string().min(1).max(256), z14.unknown());
24978
- var positive = z14.number().int().positive().max(Number.MAX_SAFE_INTEGER);
24979
- var sourceAssetsSchema = z14.object({
24980
- fonts: z14.array(
24981
- z14.object({
24982
- family: z14.string().min(1).max(128),
25783
+ var hashSchema2 = z15.string().regex(/^[a-f0-9]{64}$/u);
25784
+ var recordSchema = z15.record(z15.string().min(1).max(256), z15.unknown());
25785
+ var positive = z15.number().int().positive().max(Number.MAX_SAFE_INTEGER);
25786
+ var sourceAssetsSchema = z15.object({
25787
+ fonts: z15.array(
25788
+ z15.object({
25789
+ family: z15.string().min(1).max(128),
24983
25790
  sha256: hashSchema2,
24984
- style: z14.enum(["italic", "normal", "oblique"]),
24985
- weight: z14.enum(["100", "200", "300", "400", "500", "600", "700", "800", "900"])
25791
+ style: z15.enum(["italic", "normal", "oblique"]),
25792
+ weight: z15.enum(["100", "200", "300", "400", "500", "600", "700", "800", "900"])
24986
25793
  }).strict()
24987
25794
  ).max(64),
24988
- icons: z14.array(
24989
- z14.union([
24990
- z14.object({
24991
- format: z14.enum(["jpeg", "png", "svg", "webp"]),
25795
+ icons: z15.array(
25796
+ z15.union([
25797
+ z15.object({
25798
+ format: z15.enum(["jpeg", "png", "svg", "webp"]),
24992
25799
  id: tileflowPortableIdSchema,
24993
- kind: z14.enum(["icon", "pattern"]),
25800
+ kind: z15.enum(["icon", "pattern"]),
24994
25801
  sha256: hashSchema2
24995
25802
  }).strict(),
24996
25803
  tileflowRenderedIconIdentitySchema
@@ -24998,44 +25805,44 @@ var sourceAssetsSchema = z14.object({
24998
25805
  ).max(512),
24999
25806
  iconComposition: tileflowIconCompositionSchema.optional()
25000
25807
  }).strict();
25001
- var themeBuildSchema = z14.object({
25002
- colorScheme: z14.enum(["dark", "light"]),
25808
+ var themeBuildSchema = z15.object({
25809
+ colorScheme: z15.enum(["dark", "light"]),
25003
25810
  dataRequirements: recordSchema,
25004
25811
  sourceRequirements: recordSchema,
25005
25812
  styleSha256: hashSchema2,
25006
25813
  themeId: tileflowPortableIdSchema,
25007
25814
  themeVersion: positive
25008
25815
  }).strict();
25009
- var mapBuildSchema = z14.object({
25816
+ var mapBuildSchema = z15.object({
25010
25817
  assetSetSha256: hashSchema2,
25011
25818
  defaultTheme: tileflowThemeNameSchema,
25012
- lineage: z14.array(z14.object({ id: tileflowPortableIdSchema, mapVersion: positive }).strict()).min(1).max(64),
25819
+ lineage: z15.array(z15.object({ id: tileflowPortableIdSchema, mapVersion: positive }).strict()).min(1).max(64),
25013
25820
  mapRevisionSha256: hashSchema2,
25014
- mapRevisionSchemaVersion: z14.literal(2).optional(),
25821
+ mapRevisionSchemaVersion: z15.literal(2).optional(),
25015
25822
  mapVersion: positive,
25016
- semanticCompiler: z14.object({ name: z14.literal("tileflow-semantic"), version: z14.literal(1) }).strict(),
25823
+ semanticCompiler: z15.object({ name: z15.literal("tileflow-semantic"), version: z15.literal(1) }).strict(),
25017
25824
  sourceAssets: sourceAssetsSchema,
25018
- systemThemes: z14.object({ dark: tileflowThemeNameSchema, light: tileflowThemeNameSchema }).strict().optional(),
25019
- themes: z14.record(tileflowThemeNameSchema, themeBuildSchema)
25825
+ systemThemes: z15.object({ dark: tileflowThemeNameSchema, light: tileflowThemeNameSchema }).strict().optional(),
25826
+ themes: z15.record(tileflowThemeNameSchema, themeBuildSchema)
25020
25827
  }).strict();
25021
- var buildManifestSchema = z14.object({
25022
- schemaVersion: z14.literal(1),
25023
- maps: z14.record(tileflowPortableIdSchema, mapBuildSchema),
25024
- provenance: z14.object({
25025
- schemaVersion: z14.literal(1),
25026
- lockfile: z14.object({ format: z14.enum(["bun", "npm", "pnpm", "yarn"]), sha256: hashSchema2 }).strict().optional(),
25027
- packages: z14.record(z14.string().min(1).max(128), z14.string().min(1).max(128))
25828
+ var buildManifestSchema = z15.object({
25829
+ schemaVersion: z15.literal(1),
25830
+ maps: z15.record(tileflowPortableIdSchema, mapBuildSchema),
25831
+ provenance: z15.object({
25832
+ schemaVersion: z15.literal(1),
25833
+ lockfile: z15.object({ format: z15.enum(["bun", "npm", "pnpm", "yarn"]), sha256: hashSchema2 }).strict().optional(),
25834
+ packages: z15.record(z15.string().min(1).max(128), z15.string().min(1).max(128))
25028
25835
  }).strict().optional()
25029
25836
  }).strict();
25030
25837
  var familyShape = {
25031
25838
  buildManifest: buildManifestSchema,
25032
- styles: z14.record(tileflowThemeNameSchema, recordSchema)
25839
+ styles: z15.record(tileflowThemeNameSchema, recordSchema)
25033
25840
  };
25034
- var teamSourcesSchema = z14.record(
25841
+ var teamSourcesSchema = z15.record(
25035
25842
  tileflowPortableIdSchema,
25036
- z14.object({
25037
- tileset: z14.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
25038
- type: z14.enum(["raster", "vector"])
25843
+ z15.object({
25844
+ tileset: z15.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
25845
+ type: z15.enum(["raster", "vector"])
25039
25846
  }).strict()
25040
25847
  ).superRefine((sources, context) => {
25041
25848
  const entries = Object.entries(sources);
@@ -25047,21 +25854,21 @@ var teamSourcesSchema = z14.record(
25047
25854
  tilesets.add(value.tileset);
25048
25855
  }
25049
25856
  });
25050
- var viewSchema2 = z14.object({
25051
- center: z14.tuple([z14.number().min(-180).max(180), z14.number().min(-90).max(90)]).optional(),
25052
- zoom: z14.number().min(0).max(24).optional(),
25053
- bearing: z14.number().finite().optional(),
25054
- pitch: z14.number().min(0).max(85).optional()
25857
+ var viewSchema2 = z15.object({
25858
+ center: z15.tuple([z15.number().min(-180).max(180), z15.number().min(-90).max(90)]).optional(),
25859
+ zoom: z15.number().min(0).max(24).optional(),
25860
+ bearing: z15.number().finite().optional(),
25861
+ pitch: z15.number().min(0).max(85).optional()
25055
25862
  }).strict();
25056
- var tileflowRendererDeploymentArtifactSchema = z14.object({
25057
- kind: z14.literal("tileflow-map-deployment"),
25058
- schemaVersion: z14.literal(2),
25863
+ var tileflowRendererDeploymentArtifactSchema = z15.object({
25864
+ kind: z15.literal("tileflow-map-deployment"),
25865
+ schemaVersion: z15.literal(2),
25059
25866
  mapId: tileflowPortableIdSchema,
25060
25867
  teamSources: teamSourcesSchema,
25061
25868
  view: viewSchema2.optional(),
25062
- renderers: z14.object({
25063
- web: z14.object(familyShape).strict(),
25064
- native: z14.object({ ...familyShape, buildRecord: tileflowNativeBuildRecordSchema }).strict()
25869
+ renderers: z15.object({
25870
+ web: z15.object(familyShape).strict(),
25871
+ native: z15.object({ ...familyShape, buildRecord: tileflowNativeBuildRecordSchema }).strict()
25065
25872
  }).strict()
25066
25873
  }).strict().superRefine((artifact, context) => {
25067
25874
  const fail = () => context.addIssue({
@@ -25186,30 +25993,30 @@ function object(value) {
25186
25993
  function equal(left, right) {
25187
25994
  return left === void 0 || right === void 0 ? left === right : serializeCanonicalJson(left) === serializeCanonicalJson(right);
25188
25995
  }
25189
- var artifactName = z14.string().min(1).max(256).refine(
25996
+ var artifactName = z15.string().min(1).max(256).refine(
25190
25997
  (value) => [...value].every((char) => {
25191
25998
  const code = char.codePointAt(0);
25192
25999
  return code >= 32 && code !== 127;
25193
26000
  })
25194
26001
  );
25195
- var semanticSchema = z14.object({
25196
- version: z14.literal(2),
25197
- domains: z14.object({
25198
- poi: z14.object({
25199
- deduplication: z14.object({
25200
- identity: z14.tuple([
25201
- z14.literal("source"),
25202
- z14.literal("source-layer"),
25203
- z14.literal("feature-id")
26002
+ var semanticSchema = z15.object({
26003
+ version: z15.literal(2),
26004
+ domains: z15.object({
26005
+ poi: z15.object({
26006
+ deduplication: z15.object({
26007
+ identity: z15.tuple([
26008
+ z15.literal("source"),
26009
+ z15.literal("source-layer"),
26010
+ z15.literal("feature-id")
25204
26011
  ]),
25205
- representationPriority: z14.tuple([
25206
- z14.literal("marker"),
25207
- z14.literal("icon"),
25208
- z14.literal("combined"),
25209
- z14.literal("label")
26012
+ representationPriority: z15.tuple([
26013
+ z15.literal("marker"),
26014
+ z15.literal("icon"),
26015
+ z15.literal("combined"),
26016
+ z15.literal("label")
25210
26017
  ])
25211
26018
  }).strict(),
25212
- fields: z14.object({
26019
+ fields: z15.object({
25213
26020
  category: artifactName,
25214
26021
  filterRank: artifactName,
25215
26022
  icon: artifactName,
@@ -25217,18 +26024,18 @@ var semanticSchema = z14.object({
25217
26024
  sizeRank: artifactName,
25218
26025
  type: artifactName
25219
26026
  }).strict(),
25220
- hitTesting: z14.object({
25221
- frequency: z14.literal("animation-frame"),
25222
- order: z14.literal("rendered-topmost")
26027
+ hitTesting: z15.object({
26028
+ frequency: z15.literal("animation-frame"),
26029
+ order: z15.literal("rendered-topmost")
25223
26030
  }).strict(),
25224
- identity: z14.literal("maplibre-feature-id-if-present"),
25225
- layers: z14.array(
25226
- z14.object({
25227
- anchor: z14.literal("pointer-coordinate"),
25228
- category: z14.enum(tileflowPoiCategories),
26031
+ identity: z15.literal("maplibre-feature-id-if-present"),
26032
+ layers: z15.array(
26033
+ z15.object({
26034
+ anchor: z15.literal("pointer-coordinate"),
26035
+ category: z15.enum(tileflowPoiCategories),
25229
26036
  layerId: artifactName,
25230
- priority: z14.number().int().min(0).max(4095),
25231
- representation: z14.enum(["combined", "icon", "label", "marker"]),
26037
+ priority: z15.number().int().min(0).max(4095),
26038
+ representation: z15.enum(["combined", "icon", "label", "marker"]),
25232
26039
  source: artifactName,
25233
26040
  sourceLayer: artifactName
25234
26041
  }).strict()
@@ -25254,8 +26061,8 @@ function semanticIdentity(style) {
25254
26061
  const poi2 = manifest.domains.poi;
25255
26062
  return poi2 ? { ...poi2, layers: poi2.layers.map(({ priority: _priority, ...identity }) => identity) } : manifest;
25256
26063
  }
25257
- var mapIdSchema = z14.string().regex(/^map_[A-Za-z0-9_-]{16}$/u);
25258
- var publicUrlSchema = z14.string().min(1).max(2048).refine((value) => {
26064
+ var mapIdSchema = z15.string().regex(/^map_[A-Za-z0-9_-]{16}$/u);
26065
+ var publicUrlSchema = z15.string().min(1).max(2048).refine((value) => {
25259
26066
  try {
25260
26067
  const url = new URL(value);
25261
26068
  return url.protocol === "https:" && !url.username && !url.password && !url.search && !url.hash && url.toString() === value && !/tf_(?:public|native|live)_/u.test(value);
@@ -25263,22 +26070,22 @@ var publicUrlSchema = z14.string().min(1).max(2048).refine((value) => {
25263
26070
  return false;
25264
26071
  }
25265
26072
  });
25266
- var webResponseTheme = z14.object({ styleId: z14.string().min(1).max(128).optional(), styleUrl: publicUrlSchema }).strict();
25267
- var nativeResponseTheme = z14.object({ styleUrl: publicUrlSchema, revision: hashSchema2 }).strict();
25268
- var tileflowRendererDeploymentResponseSchema = z14.object({
25269
- kind: z14.literal("tileflow-map-deployment-result"),
25270
- schemaVersion: z14.literal(2),
25271
- changed: z14.boolean(),
26073
+ var webResponseTheme = z15.object({ styleId: z15.string().min(1).max(128).optional(), styleUrl: publicUrlSchema }).strict();
26074
+ var nativeResponseTheme = z15.object({ styleUrl: publicUrlSchema, revision: hashSchema2 }).strict();
26075
+ var tileflowRendererDeploymentResponseSchema = z15.object({
26076
+ kind: z15.literal("tileflow-map-deployment-result"),
26077
+ schemaVersion: z15.literal(2),
26078
+ changed: z15.boolean(),
25272
26079
  mapId: mapIdSchema,
25273
- deploymentId: z14.string().min(1).max(128).regex(/^[A-Za-z0-9_-]+$/u),
26080
+ deploymentId: z15.string().min(1).max(128).regex(/^[A-Za-z0-9_-]+$/u),
25274
26081
  version: positive,
25275
- worldConversionId: z14.string().min(1).max(128).optional(),
25276
- renderers: z14.object({
25277
- web: z14.object({ themes: z14.record(tileflowThemeNameSchema, webResponseTheme) }).strict(),
25278
- native: z14.object({
25279
- profile: z14.literal("native-v1"),
26082
+ worldConversionId: z15.string().min(1).max(128).optional(),
26083
+ renderers: z15.object({
26084
+ web: z15.object({ themes: z15.record(tileflowThemeNameSchema, webResponseTheme) }).strict(),
26085
+ native: z15.object({
26086
+ profile: z15.literal("native-v1"),
25280
26087
  manifestUrl: publicUrlSchema,
25281
- themes: z14.record(tileflowThemeNameSchema, nativeResponseTheme)
26088
+ themes: z15.record(tileflowThemeNameSchema, nativeResponseTheme)
25282
26089
  }).strict()
25283
26090
  }).strict()
25284
26091
  }).strict().superRefine((value, context) => {