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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -261,16 +261,16 @@ var init_expression_operators = __esm({
261
261
  function fixed(value, options) {
262
262
  const reason = options.reason.trim();
263
263
  if (!reason) throw new Error("Tileflow fixed values require a non-empty reason.");
264
- cloneJson3(value);
264
+ cloneJson2(value);
265
265
  return { kind: "theme-fixed", reason, value };
266
266
  }
267
267
  function expression(value) {
268
268
  validateExpressionArray(value, "expression");
269
- return { kind: "expression", value: cloneJson3(value) };
269
+ return { kind: "expression", value: cloneJson2(value) };
270
270
  }
271
271
  function toMapLibreStyleValue(value) {
272
272
  if (isTileflowExpression(value)) {
273
- return cloneJson3(value.value);
273
+ return cloneJson2(value.value);
274
274
  }
275
275
  if (isTileflowZoomValue(value)) {
276
276
  if (value.interpolation === "step") {
@@ -278,8 +278,8 @@ function toMapLibreStyleValue(value) {
278
278
  return [
279
279
  "step",
280
280
  ["zoom"],
281
- cloneJson3(firstValue),
282
- ...rest.flatMap(([stop, stopValue]) => [stop, cloneJson3(stopValue)])
281
+ cloneJson2(firstValue),
282
+ ...rest.flatMap(([stop, stopValue]) => [stop, cloneJson2(stopValue)])
283
283
  ];
284
284
  }
285
285
  const interpolation = value.interpolation === "exponential" ? ["exponential", value.base] : [value.interpolation];
@@ -287,13 +287,13 @@ function toMapLibreStyleValue(value) {
287
287
  "interpolate",
288
288
  interpolation,
289
289
  ["zoom"],
290
- ...value.stops.flatMap(([stop, stopValue]) => [stop, cloneJson3(stopValue)])
290
+ ...value.stops.flatMap(([stop, stopValue]) => [stop, cloneJson2(stopValue)])
291
291
  ];
292
292
  }
293
293
  if (isTileflowThemeValueNode(value)) {
294
294
  throw new Error("Tileflow theme values must be resolved before MapLibre style compilation.");
295
295
  }
296
- return cloneJson3(value);
296
+ return cloneJson2(value);
297
297
  }
298
298
  function isTileflowExpression(value) {
299
299
  return isRecord(value) && value.kind === "expression" && Array.isArray(value.value);
@@ -330,7 +330,7 @@ function createZoomValue(interpolation, stops, base) {
330
330
  throw new Error("Tileflow zoom stops must be strictly increasing.");
331
331
  }
332
332
  previous = stop;
333
- return [stop, cloneJson3(value)];
333
+ return [stop, cloneJson2(value)];
334
334
  });
335
335
  return {
336
336
  kind: "zoom",
@@ -359,9 +359,9 @@ function validateExpressionArray(value, name) {
359
359
  if (!isMapLibreExpressionOperator(value[0])) {
360
360
  throw new Error(`Unknown MapLibre expression operator: ${value[0]}`);
361
361
  }
362
- cloneJson3(value);
362
+ cloneJson2(value);
363
363
  }
364
- function cloneJson3(value) {
364
+ function cloneJson2(value) {
365
365
  const serialized = JSON.stringify(value);
366
366
  if (serialized === void 0) {
367
367
  throw new Error("Tileflow style values must be JSON-serializable.");
@@ -403,9 +403,9 @@ var init_values = __esm({
403
403
  color = {
404
404
  alpha(value, opacity) {
405
405
  return {
406
- color: cloneJson3(value),
406
+ color: cloneJson2(value),
407
407
  kind: "theme-color",
408
- opacity: cloneJson3(opacity),
408
+ opacity: cloneJson2(opacity),
409
409
  operation: "alpha"
410
410
  };
411
411
  },
@@ -414,12 +414,12 @@ var init_values = __esm({
414
414
  throw new Error("Tileflow color mixing supports only the deterministic OKLCH color space.");
415
415
  }
416
416
  return {
417
- amount: cloneJson3(options.amount),
418
- from: cloneJson3(from),
417
+ amount: cloneJson2(options.amount),
418
+ from: cloneJson2(from),
419
419
  kind: "theme-color",
420
420
  operation: "mix",
421
421
  space: "oklch",
422
- to: cloneJson3(to)
422
+ to: cloneJson2(to)
423
423
  };
424
424
  }
425
425
  };
@@ -662,9 +662,9 @@ function tileflowSunMetadata(preset) {
662
662
  return Object.freeze({ version: 1, ...preset });
663
663
  }
664
664
  function readTileflowSunPreset(metadata) {
665
- if (!isRecord3(metadata)) return void 0;
665
+ if (!isRecord6(metadata)) return void 0;
666
666
  const value = metadata[tileflowSunMetadataKey];
667
- if (!isRecord3(value) || value.version !== 1 || !isRecord3(value.ambient)) return void 0;
667
+ if (!isRecord6(value) || value.version !== 1 || !isRecord6(value.ambient)) return void 0;
668
668
  const { azimuth, altitude, color: color3, intensity, shadowIntensity } = value;
669
669
  const ambient = value.ambient;
670
670
  if (!isFiniteNumber(azimuth) || !isFiniteNumber(altitude) || !isUnit(intensity) || !isUnit(shadowIntensity) || !isUnit(ambient.intensity) || !isColour(color3) || !isColour(ambient.color))
@@ -681,7 +681,7 @@ function readTileflowSunPreset(metadata) {
681
681
  function tileflowStyleSunPreset(style) {
682
682
  const published = readTileflowSunPreset(style?.metadata);
683
683
  if (published) return published;
684
- const light = isRecord3(style?.light) ? style.light : void 0;
684
+ const light = isRecord6(style?.light) ? style.light : void 0;
685
685
  const position = light?.position;
686
686
  if (!position) return tileflowDefaultSunPreset;
687
687
  return Object.freeze({
@@ -719,7 +719,7 @@ function smoothstep2(from, to, value) {
719
719
  function clamp01(value) {
720
720
  return Math.min(1, Math.max(0, Number.isFinite(value) ? value : 0));
721
721
  }
722
- function isRecord3(value) {
722
+ function isRecord6(value) {
723
723
  return typeof value === "object" && value !== null && !Array.isArray(value);
724
724
  }
725
725
  function isFiniteNumber(value) {
@@ -963,7 +963,7 @@ function auditTileflowThemeValues(value, path = "value", context = inferAuditCon
963
963
  return diagnostics.sort(compareAuditDiagnostics);
964
964
  }
965
965
  function auditTileflowMapThemeValues(map) {
966
- const modules = isRecord4(map.modules) ? map.modules : void 0;
966
+ const modules = isRecord7(map.modules) ? map.modules : void 0;
967
967
  const moduleDiagnostics = modules ? Object.keys(modules).sort(compareCodeUnits2).flatMap(
968
968
  (owner) => auditTileflowThemeValues(modules[owner], `modules.${owner}`, {
969
969
  owner,
@@ -1070,7 +1070,7 @@ function isThemePrimitiveForCategory(value, category) {
1070
1070
  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;
1071
1071
  }
1072
1072
  function isThemeValueCandidate(value) {
1073
- return isRecord4(value) && (value.kind === "theme-token" || value.kind === "theme-fixed" || value.kind === "theme-color");
1073
+ return isRecord7(value) && (value.kind === "theme-token" || value.kind === "theme-fixed" || value.kind === "theme-color");
1074
1074
  }
1075
1075
  function visitAuditableValue(value, path, category, state, intentRequired = false) {
1076
1076
  if (isThemeStructuralPath(path)) category = void 0;
@@ -1086,7 +1086,7 @@ function visitAuditableValue(value, path, category, state, intentRequired = fals
1086
1086
  visitAuditableArray(value, path, category, state, false, intentRequired);
1087
1087
  return;
1088
1088
  }
1089
- if (!isRecord4(value)) return;
1089
+ if (!isRecord7(value)) return;
1090
1090
  if (value.kind === "expression" && Array.isArray(value.value)) {
1091
1091
  visitAuditableArray(value.value, `${path}.value`, category, state, true, intentRequired);
1092
1092
  return;
@@ -1469,7 +1469,7 @@ function assertThemeLighting(value, path) {
1469
1469
  }
1470
1470
  }
1471
1471
  function assertThemeCategoryValue(value, expectedCategory, path) {
1472
- if (!isRecord4(value)) {
1472
+ if (!isRecord7(value)) {
1473
1473
  assertTokenPrimitive(expectedCategory, value, path);
1474
1474
  return;
1475
1475
  }
@@ -1830,11 +1830,11 @@ function cloneJson14(value) {
1830
1830
  if (serialized === void 0) throw new Error("Tileflow themes must be JSON-serializable.");
1831
1831
  return JSON.parse(serialized);
1832
1832
  }
1833
- function isRecord4(value) {
1833
+ function isRecord7(value) {
1834
1834
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
1835
1835
  }
1836
1836
  function isPlainRecord(value) {
1837
- if (!isRecord4(value)) return false;
1837
+ if (!isRecord7(value)) return false;
1838
1838
  const prototype = Object.getPrototypeOf(value);
1839
1839
  return prototype === Object.prototype || prototype === null;
1840
1840
  }
@@ -1947,7 +1947,7 @@ var init_model = __esm({
1947
1947
  if (Array.isArray(value)) {
1948
1948
  return value.map((entry, index) => this.resolveTree(entry, `${path}[${index}]`, category));
1949
1949
  }
1950
- if (!isRecord4(value)) return value;
1950
+ if (!isRecord7(value)) return value;
1951
1951
  if (value.kind === "expression" && Array.isArray(value.value)) {
1952
1952
  return {
1953
1953
  ...value,
@@ -2956,7 +2956,7 @@ var defaults = {
2956
2956
  function resolveTileflowAtmosphere(value) {
2957
2957
  if (!value) return void 0;
2958
2958
  const options = { ...defaults, ...value === true ? {} : value };
2959
- const normalize = (input) => {
2959
+ const normalize2 = (input) => {
2960
2960
  const color3 = typeof input === "string" ? Color.parse(input) : void 0;
2961
2961
  if (!color3 || color3.a !== 1)
2962
2962
  throw new Error("Atmosphere colors must resolve to opaque colors.");
@@ -2966,10 +2966,10 @@ function resolveTileflowAtmosphere(value) {
2966
2966
  };
2967
2967
  return {
2968
2968
  ...options,
2969
- skyColor: normalize(options.skyColor),
2970
- horizonColor: normalize(options.horizonColor),
2971
- fogColor: normalize(options.fogColor),
2972
- spaceColor: normalize(options.spaceColor)
2969
+ skyColor: normalize2(options.skyColor),
2970
+ horizonColor: normalize2(options.horizonColor),
2971
+ fogColor: normalize2(options.fogColor),
2972
+ spaceColor: normalize2(options.spaceColor)
2973
2973
  };
2974
2974
  }
2975
2975
  function compileTileflowSky(options) {
@@ -3480,10 +3480,10 @@ function isCanonicalOpenMapTilesSchema(schema) {
3480
3480
  ([key, value]) => schema.fields[key] === value
3481
3481
  );
3482
3482
  }
3483
- function dataIdentity(kind, schema, version, url) {
3483
+ function dataIdentity(kind, schema, version2, url) {
3484
3484
  return {
3485
- ...version.generation ? { generation: version.generation } : {},
3486
- ..."worldSelection" in version && version.worldSelection ? { worldSelection: version.worldSelection } : {},
3485
+ ...version2.generation ? { generation: version2.generation } : {},
3486
+ ..."worldSelection" in version2 && version2.worldSelection ? { worldSelection: version2.worldSelection } : {},
3487
3487
  ...kind === "vector-tiles" ? {
3488
3488
  bindings: {
3489
3489
  fields: { ...schema.fields },
@@ -3498,7 +3498,7 @@ function dataIdentity(kind, schema, version, url) {
3498
3498
  }
3499
3499
  } : {},
3500
3500
  kind,
3501
- ...version.revision ? { revision: version.revision } : {},
3501
+ ...version2.revision ? { revision: version2.revision } : {},
3502
3502
  schema: schema.type,
3503
3503
  schemaVersion: schema.contractVersion,
3504
3504
  semantics: { ...schema.semantics },
@@ -4090,98 +4090,6 @@ function cloneJson(value) {
4090
4090
  return JSON.parse(JSON.stringify(value));
4091
4091
  }
4092
4092
 
4093
- // src/cartography/contributions.ts
4094
- var tileflowLayerSlots = [
4095
- "background",
4096
- "land",
4097
- "terrain",
4098
- "hydro",
4099
- "building-areas",
4100
- "transport-areas",
4101
- "transport-tunnel-shadow",
4102
- "transport-tunnel-casing",
4103
- "transport-tunnel-fill",
4104
- "transport-pedestrian-areas",
4105
- "aeroways",
4106
- "transport-surface-shadow",
4107
- "transport-surface-casing",
4108
- "transport-surface-fill",
4109
- "transport-bridge-shadow",
4110
- "transport-bridge-casing",
4111
- "transport-bridge-fill",
4112
- "transport-symbols",
4113
- "boundaries",
4114
- "buildings",
4115
- "vegetation",
4116
- "symbols"
4117
- ];
4118
- var tileflowLayerBands = [
4119
- "land",
4120
- "relief",
4121
- "water",
4122
- "roads",
4123
- "boundaries",
4124
- "buildings",
4125
- "vegetation",
4126
- "labels"
4127
- ];
4128
- var bandSlots = {
4129
- land: ["background", "land"],
4130
- relief: ["terrain", "terrain"],
4131
- water: ["hydro", "hydro"],
4132
- roads: ["transport-areas", "transport-symbols"],
4133
- boundaries: ["boundaries", "boundaries"],
4134
- buildings: ["buildings", "buildings"],
4135
- vegetation: ["vegetation", "vegetation"],
4136
- labels: ["symbols", "symbols"]
4137
- };
4138
- var tileflowBandPlacements = tileflowLayerBands.flatMap(
4139
- (band) => [`below-${band}`, `above-${band}`]
4140
- );
4141
- function tileflowBandPlacementSlot(placement) {
4142
- const [side, band] = splitBandPlacement(placement);
4143
- return bandSlots[band][side === "below" ? 0 : 1];
4144
- }
4145
- function tileflowBandPlacementIndex(slotRanks, placement) {
4146
- const [side, band] = splitBandPlacement(placement);
4147
- const [first, last] = bandSlots[band];
4148
- const threshold = side === "below" ? tileflowLayerSlots.indexOf(first) : tileflowLayerSlots.indexOf(last) + 1;
4149
- const index = slotRanks.findIndex((rank) => rank !== void 0 && rank >= threshold);
4150
- return index < 0 ? slotRanks.length : index;
4151
- }
4152
- function splitBandPlacement(placement) {
4153
- const separator = placement.indexOf("-");
4154
- return [
4155
- placement.slice(0, separator),
4156
- placement.slice(separator + 1)
4157
- ];
4158
- }
4159
- var tileflowSemanticTargetPattern = /^[a-z][a-z0-9-]*(?:\.[A-Za-z0-9_-]+)*$/u;
4160
- var tileflowRenderStackOperationNamePattern = /^[a-z][A-Za-z0-9_-]*$/u;
4161
- var tileflowCompilerMetadataKeys = Object.freeze({
4162
- owner: "tileflow:compiler-owner",
4163
- slot: "tileflow:compiler-slot",
4164
- target: "tileflow:compiler-target"
4165
- });
4166
-
4167
- // src/modules/addresses/index.ts
4168
- function addresses(options = {}) {
4169
- return { type: "addresses", ...cloneJson2(options) };
4170
- }
4171
- function cloneJson2(value) {
4172
- return JSON.parse(JSON.stringify(value));
4173
- }
4174
-
4175
- // src/cartography/context.ts
4176
- function typographyTextStyle(typography) {
4177
- return {
4178
- font: typography.font,
4179
- ...typography.fallbacks ? { fallbacks: typography.fallbacks } : {},
4180
- ...typography.letterSpacing === void 0 ? {} : { letterSpacing: typography.letterSpacing },
4181
- ...typography.transform === void 0 ? {} : { transform: typography.transform }
4182
- };
4183
- }
4184
-
4185
4093
  // src/cartography/layer-style.ts
4186
4094
  import { Color as Color2 } from "@maplibre/maplibre-gl-style-spec";
4187
4095
 
@@ -4815,30 +4723,30 @@ function asRecord2(value) {
4815
4723
  // src/cartography/merge.ts
4816
4724
  init_values();
4817
4725
  function mergeTileflowDesign(base, ...overlays) {
4818
- let resolved = cloneJson4(base);
4726
+ let resolved = cloneJson3(base);
4819
4727
  for (const overlay of overlays) {
4820
4728
  resolved = mergeValue(resolved, overlay);
4821
4729
  }
4822
4730
  return resolved;
4823
4731
  }
4824
4732
  function mergeValue(base, overlay) {
4825
- if (overlay === void 0) return cloneJson4(base);
4733
+ if (overlay === void 0) return cloneJson3(base);
4826
4734
  if (Array.isArray(overlay) || isTileflowExpression(overlay) || isTileflowThemeValueNode(overlay) || isTileflowZoomValue(overlay) || !isRecord2(overlay)) {
4827
- return cloneJson4(overlay);
4735
+ return cloneJson3(overlay);
4828
4736
  }
4829
4737
  const baseRecord = isRecord2(base) ? base : {};
4830
4738
  const result = {};
4831
4739
  for (const key of /* @__PURE__ */ new Set([...Object.keys(baseRecord), ...Object.keys(overlay)])) {
4832
4740
  const overlayValue = overlay[key];
4833
4741
  if (overlayValue === void 0) {
4834
- if (key in baseRecord) result[key] = cloneJson4(baseRecord[key]);
4742
+ if (key in baseRecord) result[key] = cloneJson3(baseRecord[key]);
4835
4743
  continue;
4836
4744
  }
4837
4745
  result[key] = mergeValue(baseRecord[key], overlayValue);
4838
4746
  }
4839
4747
  return result;
4840
4748
  }
4841
- function cloneJson4(value) {
4749
+ function cloneJson3(value) {
4842
4750
  if (value === void 0) return value;
4843
4751
  return JSON.parse(JSON.stringify(value));
4844
4752
  }
@@ -4846,6 +4754,558 @@ function isRecord2(value) {
4846
4754
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
4847
4755
  }
4848
4756
 
4757
+ // src/modules/vegetation/compiler.ts
4758
+ init_values();
4759
+
4760
+ // src/trees-3d.ts
4761
+ var tileflowTreeForms = ["round", "open", "tall", "conifer", "columnar", "palm"];
4762
+ var tileflowTreeFinishes = ["faceted", "smooth", "toon"];
4763
+ var tileflowWorldTreeFields = Object.freeze({
4764
+ height: "height",
4765
+ diameterCrown: "diameter_crown",
4766
+ circumference: "circumference",
4767
+ genus: "genus",
4768
+ species: "species",
4769
+ leafType: "leaf_type",
4770
+ taxon: "taxon",
4771
+ canopyRank: "rank",
4772
+ canopyClearance: "clearance_px_z15",
4773
+ canopyCover: "cover_pct",
4774
+ canopyHeight: "height_m"
4775
+ });
4776
+ var tileflowDefaultUnknownTreeForms = Object.freeze({
4777
+ round: 1,
4778
+ open: 1,
4779
+ tall: 1
4780
+ });
4781
+ var broadleaves = Object.freeze({
4782
+ ...Object.fromEntries(
4783
+ [
4784
+ "acacia",
4785
+ "albizia",
4786
+ "arbutus",
4787
+ "bursera",
4788
+ "ceratonia",
4789
+ "delonix",
4790
+ "ficus",
4791
+ "gleditsia",
4792
+ "jacaranda",
4793
+ "olea",
4794
+ "quercus",
4795
+ "robinia",
4796
+ "schinus",
4797
+ "sophora",
4798
+ "styphnolobium",
4799
+ "tamarix"
4800
+ ].map((genus) => [genus, "open"])
4801
+ ),
4802
+ ...Object.fromEntries(
4803
+ [
4804
+ "betula",
4805
+ "celtis",
4806
+ "fraxinus",
4807
+ "ginkgo",
4808
+ "liquidambar",
4809
+ "liriodendron",
4810
+ "magnolia",
4811
+ "platanus",
4812
+ "populus",
4813
+ "pyrus",
4814
+ "ulmus",
4815
+ "zelkova"
4816
+ ].map((genus) => [genus, "tall"])
4817
+ ),
4818
+ ...Object.fromEntries(
4819
+ [
4820
+ "acer",
4821
+ "aesculus",
4822
+ "carpinus",
4823
+ "castanea",
4824
+ "catalpa",
4825
+ "cercis",
4826
+ "citrus",
4827
+ "fagus",
4828
+ "lagerstroemia",
4829
+ "malus",
4830
+ "melia",
4831
+ "morus",
4832
+ "prunus",
4833
+ "sorbus",
4834
+ "tilia"
4835
+ ].map((genus) => [genus, "round"])
4836
+ )
4837
+ });
4838
+ function tileflowTreeFormMixProblem(mix2) {
4839
+ if (typeof mix2 !== "object" || mix2 === null || Array.isArray(mix2))
4840
+ return "a mix of forms is an object of weights, such as {round: 1, palm: 2}";
4841
+ let total = 0;
4842
+ for (const [form, weight] of Object.entries(mix2)) {
4843
+ if (!tileflowTreeForms.includes(form))
4844
+ return `"${form}" is not a tree form; forms are ${tileflowTreeForms.join(", ")}`;
4845
+ if (typeof weight !== "number" || !Number.isFinite(weight) || weight < 0)
4846
+ return `the weight of "${form}" must be a number of 0 or more`;
4847
+ total += weight;
4848
+ }
4849
+ return total > 0 ? void 0 : "a mix of forms needs at least one weight above 0";
4850
+ }
4851
+ var tileflowTreeModelIdPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
4852
+ var tileflowVegetationMetadataKey = "tileflow:vegetation";
4853
+ function tileflowVegetationMetadata(vegetation2) {
4854
+ return { version: 1, ...vegetation2 };
4855
+ }
4856
+ function readTileflowVegetation3D(metadata) {
4857
+ if (!isRecord3(metadata)) return void 0;
4858
+ const value = metadata[tileflowVegetationMetadataKey];
4859
+ if (!isRecord3(value) || value.version !== 1) return void 0;
4860
+ const { source, layers, fields, colors, heightScale, crownScale, unknownForms, regions: regions2 } = value;
4861
+ if (typeof source !== "string" || !isRecord3(layers) || typeof layers.tree !== "string")
4862
+ return void 0;
4863
+ if (!isRecord3(fields) || !isRecord3(colors) || typeof colors.bark !== "string") return void 0;
4864
+ const palette = (list) => Array.isArray(list) && list.length > 0 && list.every((entry) => typeof entry === "string") ? list : void 0;
4865
+ const broadleaf = palette(colors.broadleaf);
4866
+ const conifer = palette(colors.conifer);
4867
+ const palm = palette(colors.palm) ?? broadleaf;
4868
+ if (!broadleaf || !conifer || !palm) return void 0;
4869
+ const scale = (number) => typeof number === "number" && number > 0 && number <= 10 ? number : 1;
4870
+ const mix2 = tileflowTreeFormMixProblem(unknownForms) === void 0 ? unknownForms : void 0;
4871
+ const models = Object.fromEntries(
4872
+ Object.entries(isRecord3(value.models) ? value.models : {}).filter(
4873
+ ([id2, model]) => tileflowTreeModelIdPattern.test(id2) && isRecord3(model) && typeof model.near === "string" && (model.far === void 0 || typeof model.far === "string")
4874
+ ).slice(0, 16).map(([id2, model]) => {
4875
+ const { near, far } = model;
4876
+ return [id2, far === void 0 ? { near } : { near, far }];
4877
+ })
4878
+ );
4879
+ const ownScales = isRecord3(value.forms) ? value.forms : {};
4880
+ const forms = Object.fromEntries(
4881
+ tileflowTreeForms.flatMap((form) => {
4882
+ const own = ownScales[form];
4883
+ if (!isRecord3(own)) return [];
4884
+ const drawn = Array.isArray(own.models) ? [...new Set(own.models)].filter((id2) => typeof id2 === "string" && id2 in models).slice(0, 4) : [];
4885
+ return [
4886
+ [
4887
+ form,
4888
+ {
4889
+ heightScale: scale(own.heightScale),
4890
+ crownScale: scale(own.crownScale),
4891
+ ...drawn.length > 0 ? { models: drawn } : {}
4892
+ }
4893
+ ]
4894
+ ];
4895
+ })
4896
+ );
4897
+ const finish = tileflowTreeFinishes.find((name) => name === value.finish) ?? "faceted";
4898
+ const edges = typeof value.edges === "number" && value.edges >= 0 && value.edges <= 1 ? value.edges : 0;
4899
+ const zones = Array.isArray(regions2) ? regions2.filter(
4900
+ (region) => isRecord3(region) && tileflowTreeRegionProblem(region) === void 0
4901
+ ) : [];
4902
+ const named = Object.fromEntries(
4903
+ Object.entries(tileflowWorldTreeFields).map(([key, fallback]) => [
4904
+ key,
4905
+ typeof fields[key] === "string" ? fields[key] : fallback
4906
+ ])
4907
+ );
4908
+ return {
4909
+ source,
4910
+ layers: {
4911
+ tree: layers.tree,
4912
+ ...typeof layers.canopy === "string" ? { canopy: layers.canopy } : {}
4913
+ },
4914
+ fields: named,
4915
+ colors: { bark: colors.bark, broadleaf, conifer, palm },
4916
+ heightScale: scale(heightScale),
4917
+ crownScale: scale(crownScale),
4918
+ forms,
4919
+ models,
4920
+ finish,
4921
+ edges,
4922
+ ...typeof value.edgeColor === "string" ? { edgeColor: value.edgeColor } : {},
4923
+ unknownForms: mix2 ?? tileflowDefaultUnknownTreeForms,
4924
+ regions: zones,
4925
+ flatLayers: Array.isArray(value.flatLayers) ? value.flatLayers.filter((id2) => typeof id2 === "string") : []
4926
+ };
4927
+ }
4928
+ function tileflowTreeRegionProblem(region) {
4929
+ if (!isRecord3(region)) return "a region is an object with bounds and unknownForms";
4930
+ const bounds = region.bounds;
4931
+ if (!Array.isArray(bounds) || bounds.length !== 4 || !bounds.every((value) => typeof value === "number" && Number.isFinite(value)))
4932
+ return "bounds are [west, south, east, north] in degrees";
4933
+ const [west, south, east, north] = bounds;
4934
+ if (Math.abs(west) > 180 || Math.abs(east) > 180 || Math.abs(south) > 90 || Math.abs(north) > 90)
4935
+ return "bounds are [west, south, east, north] in degrees, longitudes within \xB1180 and latitudes within \xB190";
4936
+ if (south > north) return "a region\u2019s south lies below its north";
4937
+ const mix2 = tileflowTreeFormMixProblem(region.unknownForms);
4938
+ return mix2 ? `unknownForms: ${mix2}` : void 0;
4939
+ }
4940
+ function isRecord3(value) {
4941
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4942
+ }
4943
+
4944
+ // src/modules/vegetation/compiler.ts
4945
+ function compileVegetation(request, context) {
4946
+ const minZoom = request?.flat?.minZoom ?? 16;
4947
+ const mode = request?.mode ?? "3d";
4948
+ const config = mergeTileflowDesign(
4949
+ {
4950
+ type: "vegetation",
4951
+ enabled: true,
4952
+ flat: {
4953
+ color: context.colors.landcover.wood,
4954
+ minZoom,
4955
+ opacity: mode === "3d" ? 0.82 : 0.9,
4956
+ pitchAlignment: "map",
4957
+ pitchScale: "map",
4958
+ radius: minZoom < 24 ? zoom.linear([
4959
+ [minZoom, 2.2],
4960
+ [minZoom < 20 ? 20 : 24, 8]
4961
+ ]) : 2.2,
4962
+ strokeColor: context.colors.park,
4963
+ strokeOpacity: 0.55,
4964
+ strokeWidth: 0.8
4965
+ },
4966
+ mode,
4967
+ threeDimensional: {
4968
+ barkColor: context.colors.vegetation.tree.bark,
4969
+ broadleafColors: context.colors.vegetation.tree.broadleaf,
4970
+ coniferColors: context.colors.vegetation.tree.conifer,
4971
+ palmColors: context.colors.vegetation.tree.palm,
4972
+ crownScale: 1,
4973
+ heightScale: 1
4974
+ }
4975
+ },
4976
+ request
4977
+ );
4978
+ if (config.enabled === false || !context.data.schema.layers.tree) return [];
4979
+ const resolvedMode = config.mode ?? "3d";
4980
+ return [
4981
+ {
4982
+ kind: "layer",
4983
+ layer: applyCircleStyle(
4984
+ {
4985
+ id: "tileflow-vegetation-trees",
4986
+ type: "circle",
4987
+ source: context.data.sourceId,
4988
+ "source-layer": context.data.schema.layers.tree,
4989
+ // The layer also holds rows of trees as lines; a circle on each vertex would misdraw them.
4990
+ filter: ["==", ["geometry-type"], "Point"],
4991
+ metadata: {
4992
+ "tileflow:vegetation-mode": resolvedMode,
4993
+ ...resolvedMode === "3d" ? { "tileflow:vegetation-fallback": "flat-circle" } : {}
4994
+ }
4995
+ },
4996
+ config.flat ?? {}
4997
+ ),
4998
+ localOrder: 1,
4999
+ owner: "vegetation",
5000
+ slot: "vegetation",
5001
+ target: "vegetation.trees"
5002
+ }
5003
+ ];
5004
+ }
5005
+ function tileflowVegetationDescription(request, context) {
5006
+ const { schema } = context.data;
5007
+ if (request?.enabled === false || (request?.mode ?? "3d") !== "3d" || !schema.layers.tree)
5008
+ return void 0;
5009
+ const tree = context.colors.vegetation.tree;
5010
+ const style = request?.threeDimensional ?? {};
5011
+ const colours = (value, fallback) => Array.isArray(value) && value.length > 0 && value.every((entry) => typeof entry === "string") ? value : [...fallback];
5012
+ const number = (value) => {
5013
+ const resolved = value === void 0 ? void 0 : toMapLibreStyleValue(value);
5014
+ return typeof resolved === "number" && resolved > 0 ? resolved : 1;
5015
+ };
5016
+ const field2 = (name, fallback) => schema.fields[name] ?? fallback;
5017
+ return {
5018
+ source: context.data.sourceId,
5019
+ layers: {
5020
+ tree: schema.layers.tree,
5021
+ ...schema.layers.canopy ? { canopy: schema.layers.canopy } : {}
5022
+ },
5023
+ fields: {
5024
+ ...tileflowWorldTreeFields,
5025
+ height: field2("height", tileflowWorldTreeFields.height),
5026
+ diameterCrown: field2("diameterCrown", tileflowWorldTreeFields.diameterCrown),
5027
+ genus: field2("genus", tileflowWorldTreeFields.genus),
5028
+ species: field2("species", tileflowWorldTreeFields.species),
5029
+ leafType: field2("leafType", tileflowWorldTreeFields.leafType),
5030
+ canopyRank: field2("canopyRank", tileflowWorldTreeFields.canopyRank),
5031
+ canopyClearance: field2("canopyClearanceAtZoom15", tileflowWorldTreeFields.canopyClearance),
5032
+ canopyCover: field2("canopyCoverPercent", tileflowWorldTreeFields.canopyCover)
5033
+ },
5034
+ colors: {
5035
+ bark: typeof style.barkColor === "string" ? style.barkColor : tree.bark,
5036
+ broadleaf: colours(style.broadleafColors, tree.broadleaf),
5037
+ conifer: colours(style.coniferColors, tree.conifer),
5038
+ palm: colours(style.palmColors, tree.palm)
5039
+ },
5040
+ heightScale: number(style.heightScale),
5041
+ crownScale: number(style.crownScale),
5042
+ forms: Object.fromEntries(
5043
+ Object.entries(style.forms ?? {}).map(([form, own]) => [
5044
+ form,
5045
+ {
5046
+ heightScale: number(own?.heightScale),
5047
+ crownScale: number(own?.crownScale),
5048
+ ...own?.models?.length ? { models: [...own.models] } : {}
5049
+ }
5050
+ ])
5051
+ ),
5052
+ // Filled by the compiler from the map's prepared models.
5053
+ models: {},
5054
+ finish: style.finish ?? "faceted",
5055
+ edges: (() => {
5056
+ const edges = toMapLibreStyleValue(style.edges ?? 0);
5057
+ return typeof edges === "number" ? edges : 0;
5058
+ })(),
5059
+ ...(() => {
5060
+ const edgeColor = style.edgeColor === void 0 ? void 0 : toMapLibreStyleValue(style.edgeColor);
5061
+ return typeof edgeColor === "string" ? { edgeColor } : {};
5062
+ })(),
5063
+ unknownForms: style.unknownForms ?? tileflowDefaultUnknownTreeForms,
5064
+ regions: style.regions ?? [],
5065
+ // Filled by the compiler once the style's layers are final.
5066
+ flatLayers: []
5067
+ };
5068
+ }
5069
+
5070
+ // src/buildings-3d-design.ts
5071
+ var tileflowBuildingsMetadataKey = "tileflow:buildings";
5072
+ var tileflowDefaultBuildings3DDesign = Object.freeze({
5073
+ source: "tileflow",
5074
+ layers: Object.freeze({ building: "building", places: "poi" }),
5075
+ color: "#d8d0c0",
5076
+ edges: 1,
5077
+ nightLight: 1,
5078
+ windows: false,
5079
+ shadows: true,
5080
+ occlusion: true
5081
+ });
5082
+ function tileflowBuildingsMetadata(design) {
5083
+ return { version: 1, ...design };
5084
+ }
5085
+ function readTileflowBuildings3D(metadata) {
5086
+ if (!isRecord4(metadata)) return void 0;
5087
+ const value = metadata[tileflowBuildingsMetadataKey];
5088
+ if (!isRecord4(value) || value.version !== 1) return void 0;
5089
+ const { source, layers, color: color3 } = value;
5090
+ if (typeof source !== "string" || !isRecord4(layers) || typeof layers.building !== "string")
5091
+ return void 0;
5092
+ if (typeof color3 !== "string" && !Array.isArray(color3)) return void 0;
5093
+ const within = (number, high, fallback) => typeof number === "number" && number >= 0 && number <= high ? number : fallback;
5094
+ return {
5095
+ source,
5096
+ layers: {
5097
+ building: layers.building,
5098
+ ...typeof layers.places === "string" ? { places: layers.places } : {}
5099
+ },
5100
+ color: color3,
5101
+ edges: within(value.edges, 1, 1),
5102
+ ...typeof value.edgeColor === "string" ? { edgeColor: value.edgeColor } : {},
5103
+ nightLight: within(value.nightLight, 2, 1),
5104
+ windows: value.windows === true,
5105
+ shadows: value.shadows !== false,
5106
+ occlusion: value.occlusion !== false
5107
+ };
5108
+ }
5109
+ function isRecord4(value) {
5110
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5111
+ }
5112
+
5113
+ // src/street-lights.ts
5114
+ init_theme_blend();
5115
+ var tileflowStreetLightsMetadataKey = "tileflow:street-lights";
5116
+ var tileflowDefaultStreetLightsDesign = Object.freeze({
5117
+ source: "tileflow",
5118
+ layers: Object.freeze({
5119
+ streetFurniture: "street_furniture",
5120
+ transportation: "transportation",
5121
+ sidewalk: "sidewalk"
5122
+ }),
5123
+ estimate: "lit",
5124
+ color: "#ffcf8f",
5125
+ intensity: 1
5126
+ });
5127
+ function tileflowStreetLightsMetadata(design) {
5128
+ return { version: 1, ...design };
5129
+ }
5130
+ function readTileflowStreetLights(metadata) {
5131
+ if (typeof metadata !== "object" || metadata === null || Array.isArray(metadata))
5132
+ return void 0;
5133
+ const value = metadata[tileflowStreetLightsMetadataKey];
5134
+ if (typeof value !== "object" || value === null || value.version !== 1) return void 0;
5135
+ const layers = value.layers;
5136
+ if (typeof value.source !== "string" || typeof layers?.streetFurniture !== "string" || typeof layers.transportation !== "string")
5137
+ return void 0;
5138
+ const estimate = ["lit", "streets", "none"].find((name) => name === value.estimate);
5139
+ const intensity = typeof value.intensity === "number" && value.intensity >= 0 && value.intensity <= 2 ? value.intensity : 1;
5140
+ return {
5141
+ source: value.source,
5142
+ layers: {
5143
+ streetFurniture: layers.streetFurniture,
5144
+ transportation: layers.transportation,
5145
+ ...typeof layers.sidewalk === "string" ? { sidewalk: layers.sidewalk } : {}
5146
+ },
5147
+ estimate: estimate ?? "lit",
5148
+ color: typeof value.color === "string" && tileflowColourChannels(value.color) ? value.color : tileflowDefaultStreetLightsDesign.color,
5149
+ intensity
5150
+ };
5151
+ }
5152
+
5153
+ // src/cartography/scene-3d-metadata.ts
5154
+ init_values();
5155
+ var colourSources = [
5156
+ ["tileflow-buildings-render-extrusion", "fill-extrusion-color"],
5157
+ ["tileflow-buildings-3d", "fill-extrusion-color"],
5158
+ ["tileflow-buildings-fill", "fill-color"]
5159
+ ];
5160
+ function tileflowBuildings3DDesignOf(module, context, layers) {
5161
+ const config = isRecord5(module) ? module : {};
5162
+ if (config.enabled === false) return void 0;
5163
+ const style = config.threeDimensional ?? {};
5164
+ let color3;
5165
+ for (const [id2, property] of colourSources) {
5166
+ const value = layers.find((layer) => layer.id === id2)?.paint?.[property];
5167
+ if (typeof value === "string" || Array.isArray(value)) {
5168
+ color3 = value;
5169
+ break;
5170
+ }
5171
+ }
5172
+ const number = (value, fallback) => {
5173
+ const resolved = value === void 0 ? void 0 : toMapLibreStyleValue(value);
5174
+ return typeof resolved === "number" ? resolved : fallback;
5175
+ };
5176
+ const edgeColor = style.edgeColor === void 0 ? void 0 : toMapLibreStyleValue(style.edgeColor);
5177
+ return {
5178
+ source: context.data.sourceId,
5179
+ layers: {
5180
+ building: context.data.schema.layers.building,
5181
+ ...context.data.schema.layers.poi ? { places: context.data.schema.layers.poi } : {}
5182
+ },
5183
+ color: color3 ?? context.colors.buildings.generic,
5184
+ edges: number(style.edges, 1),
5185
+ ...typeof edgeColor === "string" ? { edgeColor } : {},
5186
+ nightLight: number(style.nightLight, 1),
5187
+ windows: style.windows === true,
5188
+ shadows: style.shadows !== false,
5189
+ occlusion: style.occlusion !== false
5190
+ };
5191
+ }
5192
+ function tileflowStreetLightsDesignOf(module, context) {
5193
+ const config = isRecord5(module) ? module : {};
5194
+ const layers = context.data.schema.layers;
5195
+ const style = config.streetLights ?? {};
5196
+ if (config.enabled === false || style.enabled === false || !layers.streetFurniture)
5197
+ return void 0;
5198
+ const color3 = style.color === void 0 ? void 0 : toMapLibreStyleValue(style.color);
5199
+ const intensity = style.intensity === void 0 ? void 0 : toMapLibreStyleValue(style.intensity);
5200
+ const estimate = style.estimate;
5201
+ return {
5202
+ source: context.data.sourceId,
5203
+ layers: {
5204
+ streetFurniture: layers.streetFurniture,
5205
+ transportation: layers.road,
5206
+ ...layers.sidewalk ? { sidewalk: layers.sidewalk } : {}
5207
+ },
5208
+ estimate: estimate === "streets" || estimate === "none" || estimate === "lit" ? estimate : "lit",
5209
+ color: typeof color3 === "string" ? color3 : tileflowDefaultStreetLightsDesign.color,
5210
+ intensity: typeof intensity === "number" ? intensity : 1
5211
+ };
5212
+ }
5213
+ function isRecord5(value) {
5214
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5215
+ }
5216
+
5217
+ // src/cartography/contributions.ts
5218
+ var tileflowLayerSlots = [
5219
+ "background",
5220
+ "land",
5221
+ "terrain",
5222
+ "hydro",
5223
+ "building-areas",
5224
+ "transport-areas",
5225
+ "transport-tunnel-shadow",
5226
+ "transport-tunnel-casing",
5227
+ "transport-tunnel-fill",
5228
+ "transport-pedestrian-areas",
5229
+ "aeroways",
5230
+ "transport-surface-shadow",
5231
+ "transport-surface-casing",
5232
+ "transport-surface-fill",
5233
+ "transport-bridge-shadow",
5234
+ "transport-bridge-casing",
5235
+ "transport-bridge-fill",
5236
+ "transport-symbols",
5237
+ "boundaries",
5238
+ "buildings",
5239
+ "vegetation",
5240
+ "symbols"
5241
+ ];
5242
+ var tileflowLayerBands = [
5243
+ "land",
5244
+ "relief",
5245
+ "water",
5246
+ "roads",
5247
+ "boundaries",
5248
+ "buildings",
5249
+ "vegetation",
5250
+ "labels"
5251
+ ];
5252
+ var bandSlots = {
5253
+ land: ["background", "land"],
5254
+ relief: ["terrain", "terrain"],
5255
+ water: ["hydro", "hydro"],
5256
+ roads: ["transport-areas", "transport-symbols"],
5257
+ boundaries: ["boundaries", "boundaries"],
5258
+ buildings: ["buildings", "buildings"],
5259
+ vegetation: ["vegetation", "vegetation"],
5260
+ labels: ["symbols", "symbols"]
5261
+ };
5262
+ var tileflowBandPlacements = tileflowLayerBands.flatMap(
5263
+ (band) => [`below-${band}`, `above-${band}`]
5264
+ );
5265
+ function tileflowBandPlacementSlot(placement) {
5266
+ const [side, band] = splitBandPlacement(placement);
5267
+ return bandSlots[band][side === "below" ? 0 : 1];
5268
+ }
5269
+ function tileflowBandPlacementIndex(slotRanks, placement) {
5270
+ const [side, band] = splitBandPlacement(placement);
5271
+ const [first, last] = bandSlots[band];
5272
+ const threshold = side === "below" ? tileflowLayerSlots.indexOf(first) : tileflowLayerSlots.indexOf(last) + 1;
5273
+ const index = slotRanks.findIndex((rank) => rank !== void 0 && rank >= threshold);
5274
+ return index < 0 ? slotRanks.length : index;
5275
+ }
5276
+ function splitBandPlacement(placement) {
5277
+ const separator = placement.indexOf("-");
5278
+ return [
5279
+ placement.slice(0, separator),
5280
+ placement.slice(separator + 1)
5281
+ ];
5282
+ }
5283
+ var tileflowSemanticTargetPattern = /^[a-z][a-z0-9-]*(?:\.[A-Za-z0-9_-]+)*$/u;
5284
+ var tileflowRenderStackOperationNamePattern = /^[a-z][A-Za-z0-9_-]*$/u;
5285
+ var tileflowCompilerMetadataKeys = Object.freeze({
5286
+ owner: "tileflow:compiler-owner",
5287
+ slot: "tileflow:compiler-slot",
5288
+ target: "tileflow:compiler-target"
5289
+ });
5290
+
5291
+ // src/modules/addresses/index.ts
5292
+ function addresses(options = {}) {
5293
+ return { type: "addresses", ...cloneJson4(options) };
5294
+ }
5295
+ function cloneJson4(value) {
5296
+ return JSON.parse(JSON.stringify(value));
5297
+ }
5298
+
5299
+ // src/cartography/context.ts
5300
+ function typographyTextStyle(typography) {
5301
+ return {
5302
+ font: typography.font,
5303
+ ...typography.fallbacks ? { fallbacks: typography.fallbacks } : {},
5304
+ ...typography.letterSpacing === void 0 ? {} : { letterSpacing: typography.letterSpacing },
5305
+ ...typography.transform === void 0 ? {} : { transform: typography.transform }
5306
+ };
5307
+ }
5308
+
4849
5309
  // src/modules/addresses/compiler.ts
4850
5310
  init_values();
4851
5311
  function compileAddresses(request, context) {
@@ -7645,7 +8105,8 @@ function resolveColors(overrides = {}, defaults4 = defaultColors, moduleOverride
7645
8105
  resolved.landcover.grass,
7646
8106
  resolved.landcover.meadow
7647
8107
  ],
7648
- conifer: [resolved.landcover.protected, resolved.landcover.scrub, resolved.landcover.wood]
8108
+ conifer: [resolved.landcover.protected, resolved.landcover.scrub, resolved.landcover.wood],
8109
+ palm: [resolved.landcover.urbanPark, resolved.landcover.grass, resolved.landcover.meadow]
7649
8110
  }
7650
8111
  }
7651
8112
  };
@@ -7812,6 +8273,11 @@ function resolveThemeColors(theme) {
7812
8273
  optionalColor("vegetation.tree.conifer.a", colors.vegetation.tree.conifer[0]),
7813
8274
  optionalColor("vegetation.tree.conifer.b", colors.vegetation.tree.conifer[1]),
7814
8275
  optionalColor("vegetation.tree.conifer.c", colors.vegetation.tree.conifer[2])
8276
+ ],
8277
+ palm: [
8278
+ optionalColor("vegetation.tree.palm.a", colors.vegetation.tree.palm[0]),
8279
+ optionalColor("vegetation.tree.palm.b", colors.vegetation.tree.palm[1]),
8280
+ optionalColor("vegetation.tree.palm.c", colors.vegetation.tree.palm[2])
7815
8281
  ]
7816
8282
  }
7817
8283
  }
@@ -9161,77 +9627,6 @@ function cloneJson16(value) {
9161
9627
  return JSON.parse(JSON.stringify(value));
9162
9628
  }
9163
9629
 
9164
- // src/modules/vegetation/compiler.ts
9165
- init_values();
9166
- function compileVegetation(request, context) {
9167
- const minZoom = request?.flat?.minZoom ?? 16;
9168
- const mode = request?.mode ?? "3d";
9169
- const config = mergeTileflowDesign(
9170
- {
9171
- type: "vegetation",
9172
- enabled: true,
9173
- flat: {
9174
- color: context.colors.landcover.wood,
9175
- minZoom,
9176
- opacity: mode === "3d" ? 0.82 : 0.9,
9177
- pitchAlignment: "map",
9178
- pitchScale: "map",
9179
- radius: minZoom < 24 ? zoom.linear([
9180
- [minZoom, 2.2],
9181
- [minZoom < 20 ? 20 : 24, 8]
9182
- ]) : 2.2,
9183
- strokeColor: context.colors.park,
9184
- strokeOpacity: 0.55,
9185
- strokeWidth: 0.8
9186
- },
9187
- mode,
9188
- threeDimensional: {
9189
- barkColor: context.colors.vegetation.tree.bark,
9190
- broadleafColors: context.colors.vegetation.tree.broadleaf,
9191
- coniferColors: context.colors.vegetation.tree.conifer,
9192
- crownScale: 1,
9193
- heightScale: 1
9194
- }
9195
- },
9196
- request
9197
- );
9198
- if (config.enabled === false || !context.data.schema.layers.tree) return [];
9199
- const resolvedMode = config.mode ?? "3d";
9200
- const threeDimensional = config.threeDimensional;
9201
- return [
9202
- {
9203
- kind: "layer",
9204
- layer: applyCircleStyle(
9205
- {
9206
- id: "tileflow-vegetation-trees",
9207
- type: "circle",
9208
- source: context.data.sourceId,
9209
- "source-layer": context.data.schema.layers.tree,
9210
- metadata: {
9211
- "tileflow:vegetation-mode": resolvedMode,
9212
- ...resolvedMode === "3d" ? { "tileflow:vegetation-fallback": "flat-circle" } : {},
9213
- "tileflow:tree-bark-color": threeDimensional.barkColor,
9214
- "tileflow:tree-broadleaf-colors": threeDimensional.broadleafColors,
9215
- "tileflow:tree-conifer-colors": threeDimensional.coniferColors,
9216
- "tileflow:tree-crown-field": context.data.schema.fields.diameterCrown,
9217
- "tileflow:tree-crown-scale": threeDimensional.crownScale,
9218
- "tileflow:tree-genus-field": context.data.schema.fields.genus,
9219
- "tileflow:tree-height-field": context.data.schema.fields.height,
9220
- "tileflow:tree-height-scale": threeDimensional.heightScale,
9221
- "tileflow:tree-leaf-type-field": context.data.schema.fields.leafType,
9222
- "tileflow:tree-species-field": context.data.schema.fields.species
9223
- }
9224
- },
9225
- config.flat ?? {}
9226
- ),
9227
- localOrder: 1,
9228
- owner: "vegetation",
9229
- slot: "vegetation",
9230
- target: "vegetation.trees"
9231
- }
9232
- ];
9233
- }
9234
-
9235
9630
  // src/modules/water/index.ts
9236
9631
  function water(options = {}) {
9237
9632
  return { type: "water", ...cloneJson17(options) };
@@ -10933,21 +11328,21 @@ function bindSemanticValue(value, schema, sourceId) {
10933
11328
  }
10934
11329
  if (isDataSourceReference(value)) return sourceId;
10935
11330
  if (Array.isArray(value)) return value.map((item) => bindSemanticValue(item, schema, sourceId));
10936
- if (!isRecord5(value)) return value;
11331
+ if (!isRecord8(value)) return value;
10937
11332
  return Object.fromEntries(
10938
11333
  Object.entries(value).map(([key, item]) => [key, bindSemanticValue(item, schema, sourceId)])
10939
11334
  );
10940
11335
  }
10941
11336
  function isDataFieldReference2(value) {
10942
- return isRecord5(value) && value.kind === "tileflow-data-field" && typeof value.name === "string";
11337
+ return isRecord8(value) && value.kind === "tileflow-data-field" && typeof value.name === "string";
10943
11338
  }
10944
11339
  function isDataLayerReference(value) {
10945
- return isRecord5(value) && value.kind === "tileflow-data-layer" && typeof value.name === "string";
11340
+ return isRecord8(value) && value.kind === "tileflow-data-layer" && typeof value.name === "string";
10946
11341
  }
10947
11342
  function isDataSourceReference(value) {
10948
- return isRecord5(value) && value.kind === "tileflow-data-source" && value.name === "primary";
11343
+ return isRecord8(value) && value.kind === "tileflow-data-source" && value.name === "primary";
10949
11344
  }
10950
- function isRecord5(value) {
11345
+ function isRecord8(value) {
10951
11346
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
10952
11347
  }
10953
11348
  function compareCodeUnits5(left, right) {
@@ -11051,8 +11446,8 @@ function createTileflowLayerTemplateIR(layer, semanticKey) {
11051
11446
  renderer: type,
11052
11447
  ...filter === void 0 ? {} : { selector: cloneJson20(filter) },
11053
11448
  style: Object.freeze({
11054
- ...isRecord6(layout) ? { placement: Object.freeze(cloneJson20(layout)) } : {},
11055
- ...isRecord6(paint) ? { appearance: Object.freeze(cloneJson20(paint)) } : {}
11449
+ ...isRecord9(layout) ? { placement: Object.freeze(cloneJson20(layout)) } : {},
11450
+ ...isRecord9(paint) ? { appearance: Object.freeze(cloneJson20(paint)) } : {}
11056
11451
  })
11057
11452
  });
11058
11453
  }
@@ -11292,21 +11687,21 @@ function visitReferences(value, counts, visited = /* @__PURE__ */ new WeakSet())
11292
11687
  }
11293
11688
  }
11294
11689
  function isSemanticFieldReference(value) {
11295
- return isRecord6(value) && value.kind === "tileflow-data-field";
11690
+ return isRecord9(value) && value.kind === "tileflow-data-field";
11296
11691
  }
11297
11692
  function isSemanticLayerReference(value) {
11298
- return isRecord6(value) && value.kind === "tileflow-data-layer";
11693
+ return isRecord9(value) && value.kind === "tileflow-data-layer";
11299
11694
  }
11300
11695
  function isSemanticSourceReference(value) {
11301
- return isRecord6(value) && value.kind === "tileflow-data-source";
11696
+ return isRecord9(value) && value.kind === "tileflow-data-source";
11302
11697
  }
11303
11698
  function cloneJson20(value) {
11304
11699
  return JSON.parse(JSON.stringify(value));
11305
11700
  }
11306
11701
  function asRecord4(value) {
11307
- return isRecord6(value) ? value : {};
11702
+ return isRecord9(value) ? value : {};
11308
11703
  }
11309
- function isRecord6(value) {
11704
+ function isRecord9(value) {
11310
11705
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
11311
11706
  }
11312
11707
 
@@ -11370,7 +11765,7 @@ function validateTileflowRenderSelectorConstraints(input) {
11370
11765
  };
11371
11766
  while (pending.length > 0) {
11372
11767
  const current = pending.pop();
11373
- if (!isRecord7(current.selector) || typeof current.selector.kind !== "string") {
11768
+ if (!isRecord10(current.selector) || typeof current.selector.kind !== "string") {
11374
11769
  addIssue(
11375
11770
  "invalid-selector",
11376
11771
  "a render selector must be a semantic selector node",
@@ -11431,7 +11826,7 @@ function validateTileflowRenderSelectorConstraints(input) {
11431
11826
  }
11432
11827
  for (const [index, branch] of current.selector.branches.entries()) {
11433
11828
  const branchPath = [...branchesPath, index];
11434
- if (!isRecord7(branch)) {
11829
+ if (!isRecord10(branch)) {
11435
11830
  addIssue("invalid-selector", "match selector branch must be an object", branchPath);
11436
11831
  continue;
11437
11832
  }
@@ -11462,14 +11857,14 @@ function validateTileflowRenderSelectorConstraints(input) {
11462
11857
  for (let index = current.selector.stops.length - 1; index >= 0; index -= 1) {
11463
11858
  const stop = current.selector.stops[index];
11464
11859
  const stopPath = [...stopsPath, index];
11465
- if (!isRecord7(stop)) {
11860
+ if (!isRecord10(stop)) {
11466
11861
  addIssue("invalid-selector", "step selector stop must be an object", stopPath);
11467
11862
  continue;
11468
11863
  }
11469
11864
  pushChild(stop.selector, current.depth + 1, [...stopPath, "selector"]);
11470
11865
  }
11471
11866
  for (const [index, stop] of current.selector.stops.entries()) {
11472
- if (!isRecord7(stop)) continue;
11867
+ if (!isRecord10(stop)) continue;
11473
11868
  const zoom3 = stop.zoom;
11474
11869
  const zoomPath = [...stopsPath, index, "zoom"];
11475
11870
  if (typeof zoom3 !== "number" || !Number.isFinite(zoom3) || zoom3 < 0 || zoom3 > 24) {
@@ -11852,7 +12247,7 @@ function applyRendererStyle(layer, renderer, style) {
11852
12247
  };
11853
12248
  }
11854
12249
  function lowerSelector(selector, data) {
11855
- if (!isRecord7(selector) || typeof selector.kind !== "string") {
12250
+ if (!isRecord10(selector) || typeof selector.kind !== "string") {
11856
12251
  throw renderStackError(
11857
12252
  "invalid-selector",
11858
12253
  "A render selector must be a semantic selector node."
@@ -12055,7 +12450,7 @@ function requireRenderer(value) {
12055
12450
  function requirePlacement(value) {
12056
12451
  if (typeof value === "string") {
12057
12452
  if (tileflowBandPlacements.includes(value)) return;
12058
- } else if (isRecord7(value)) {
12453
+ } else if (isRecord10(value)) {
12059
12454
  const keys = Object.keys(value);
12060
12455
  if (keys.length === 1 && (keys[0] === "above" || keys[0] === "below")) {
12061
12456
  requirePortableTarget(value[keys[0]], "render placement target");
@@ -12082,7 +12477,7 @@ function validateRefinementShape(operation) {
12082
12477
  validateStyle(operation.renderer, operation.style);
12083
12478
  }
12084
12479
  function validateStyle(renderer, style) {
12085
- if (!isRecord7(style)) {
12480
+ if (!isRecord10(style)) {
12086
12481
  throw renderStackError(
12087
12482
  "invalid-style",
12088
12483
  `A ${renderer} render operation requires a style object.`
@@ -12184,7 +12579,7 @@ function normalizeRequirements(requirements) {
12184
12579
  return [...requirements];
12185
12580
  }
12186
12581
  function requireRenderStackEntries(stack) {
12187
- if (!isRecord7(stack)) {
12582
+ if (!isRecord10(stack)) {
12188
12583
  throw renderStackError("invalid-stack", "A render stack must be a named operation record.");
12189
12584
  }
12190
12585
  const entries = Object.entries(stack);
@@ -12331,9 +12726,9 @@ function cloneJson21(value) {
12331
12726
  return JSON.parse(JSON.stringify(value));
12332
12727
  }
12333
12728
  function asRecord5(value) {
12334
- return isRecord7(value) ? value : {};
12729
+ return isRecord10(value) ? value : {};
12335
12730
  }
12336
- function isRecord7(value) {
12731
+ function isRecord10(value) {
12337
12732
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
12338
12733
  }
12339
12734
 
@@ -13372,6 +13767,17 @@ var waterModuleSchema = z7.object({
13372
13767
  stream: lineStyleSchema.optional()
13373
13768
  }).strict().optional()
13374
13769
  }).strict();
13770
+ function boundedThemeNumberSchema(min, max) {
13771
+ return z7.union([
13772
+ z7.number().finite().min(min).max(max),
13773
+ themeNumberReferenceSchema,
13774
+ z7.object({
13775
+ kind: z7.literal("theme-fixed"),
13776
+ reason: z7.string().trim().min(1),
13777
+ value: z7.number().finite().min(min).max(max)
13778
+ }).strict()
13779
+ ]);
13780
+ }
13375
13781
  var buildingsModuleSchema = z7.object({
13376
13782
  type: z7.literal("buildings"),
13377
13783
  businessCorridor: areaStyleSchema.optional(),
@@ -13379,7 +13785,15 @@ var buildingsModuleSchema = z7.object({
13379
13785
  extrusion: extrusionStyleSchema.optional(),
13380
13786
  flat: areaStyleSchema.optional(),
13381
13787
  mode: z7.enum(["3d", "flat"]).optional(),
13382
- renderStack: renderStackSchema.optional()
13788
+ renderStack: renderStackSchema.optional(),
13789
+ threeDimensional: z7.object({
13790
+ edges: boundedThemeNumberSchema(0, 1).optional(),
13791
+ edgeColor: themeColorValueSchema.optional(),
13792
+ nightLight: boundedThemeNumberSchema(0, 2).optional(),
13793
+ occlusion: z7.boolean().optional(),
13794
+ shadows: z7.boolean().optional(),
13795
+ windows: z7.boolean().optional()
13796
+ }).strict().optional()
13383
13797
  }).strict();
13384
13798
  var addressesModuleSchema = z7.object({
13385
13799
  type: z7.literal("addresses"),
@@ -13492,6 +13906,12 @@ var roadsModuleSchema = z7.object({
13492
13906
  pattern: fillStyleSchema.optional(),
13493
13907
  surface: fillStyleSchema.optional()
13494
13908
  }).strict().optional(),
13909
+ streetLights: z7.object({
13910
+ color: themeColorValueSchema.optional(),
13911
+ enabled: z7.boolean().optional(),
13912
+ estimate: z7.enum(["lit", "streets", "none"]).optional(),
13913
+ intensity: boundedThemeNumberSchema(0, 2).optional()
13914
+ }).strict().optional(),
13495
13915
  structures: roadStructureMapSchema.optional(),
13496
13916
  weight: z7.enum(["thin", "regular", "bold"]).optional(),
13497
13917
  widthScale: z7.partialRecord(roadClassSchema, themeNumberValueSchema).optional()
@@ -13580,6 +14000,16 @@ var nauticalModuleSchema = z7.object({
13580
14000
  wreckAreas: areaStyleSchema.optional(),
13581
14001
  wrecks: markerSymbolStyleSchema.optional()
13582
14002
  }).strict();
14003
+ var treeFormMixSchema = z7.object({
14004
+ round: z7.number().finite().min(0).optional(),
14005
+ open: z7.number().finite().min(0).optional(),
14006
+ tall: z7.number().finite().min(0).optional(),
14007
+ conifer: z7.number().finite().min(0).optional(),
14008
+ columnar: z7.number().finite().min(0).optional(),
14009
+ palm: z7.number().finite().min(0).optional()
14010
+ }).strict().refine((mix2) => Object.values(mix2).some((weight) => (weight ?? 0) > 0), {
14011
+ message: "Expected at least one tree form with a weight above 0"
14012
+ });
13583
14013
  var modulesSchema = z7.object({
13584
14014
  addresses: addressesModuleSchema.optional(),
13585
14015
  aeroways: aerowaysModuleSchema.optional(),
@@ -13602,6 +14032,39 @@ var modulesSchema = z7.object({
13602
14032
  barkColor: themeColorValueSchema.optional(),
13603
14033
  broadleafColors: z7.array(themeColorValueSchema).min(1).max(8).optional(),
13604
14034
  coniferColors: z7.array(themeColorValueSchema).min(1).max(8).optional(),
14035
+ palmColors: z7.array(themeColorValueSchema).min(1).max(8).optional(),
14036
+ finish: z7.enum(["faceted", "smooth", "toon"]).optional(),
14037
+ edges: boundedThemeNumberSchema(0, 1).optional(),
14038
+ edgeColor: themeColorValueSchema.optional(),
14039
+ forms: z7.partialRecord(
14040
+ z7.enum(["round", "open", "tall", "conifer", "columnar", "palm"]),
14041
+ z7.object({
14042
+ crownScale: boundedThemeNumberSchema(0.1, 10).optional(),
14043
+ heightScale: boundedThemeNumberSchema(0.1, 10).optional(),
14044
+ models: z7.array(
14045
+ z7.string().max(64).regex(
14046
+ tileflowTreeModelIdPattern,
14047
+ "Expected a model id: its .glb file name in lowercase letters, digits and hyphens"
14048
+ )
14049
+ ).min(1).max(4).refine((ids) => new Set(ids).size === ids.length, {
14050
+ message: "Expected each model once"
14051
+ }).optional()
14052
+ }).strict()
14053
+ ).optional(),
14054
+ unknownForms: treeFormMixSchema.optional(),
14055
+ regions: z7.array(
14056
+ z7.object({
14057
+ bounds: z7.tuple([
14058
+ z7.number().finite().min(-180).max(180),
14059
+ z7.number().finite().min(-90).max(90),
14060
+ z7.number().finite().min(-180).max(180),
14061
+ z7.number().finite().min(-90).max(90)
14062
+ ]).refine(([, south, , north]) => south <= north, {
14063
+ message: "Expected bounds as [west, south, east, north] with south below north"
14064
+ }),
14065
+ unknownForms: treeFormMixSchema
14066
+ }).strict()
14067
+ ).max(16).optional(),
13605
14068
  crownScale: z7.union([
13606
14069
  z7.number().finite().positive().max(10),
13607
14070
  themeNumberReferenceSchema,
@@ -13934,6 +14397,7 @@ var iconDirectoriesSchema = z7.array(
13934
14397
  }
13935
14398
  });
13936
14399
  var fontDirectoriesSchema = z7.array(assetDirectorySchema).max(16);
14400
+ var modelDirectoriesSchema = z7.array(assetDirectorySchema).max(16);
13937
14401
  var fontStacksSchema = z7.array(
13938
14402
  z7.string().min(1).max(200).refine(
13939
14403
  (value) => value === value.trim() && value === value.normalize("NFC") && !/[\p{Cc}\\/]/u.test(value),
@@ -14155,6 +14619,7 @@ var resolvedTileflowMapSchema = z7.object({
14155
14619
  icons: iconDirectoriesSchema.optional(),
14156
14620
  id: tileflowMapIdSchema,
14157
14621
  marine: marineSchema.optional(),
14622
+ models: modelDirectoriesSchema.optional(),
14158
14623
  modules: modulesSchema.optional(),
14159
14624
  name: z7.string().trim().min(1),
14160
14625
  overlays: overlaysSchema.optional(),
@@ -15097,11 +15562,11 @@ function isTileflowPoiCategory(value) {
15097
15562
  return typeof value === "string" && tileflowPoiCategories.includes(value);
15098
15563
  }
15099
15564
  function semanticTarget(layer) {
15100
- if (!isRecord8(layer.metadata)) return void 0;
15565
+ if (!isRecord11(layer.metadata)) return void 0;
15101
15566
  const value = layer.metadata[tileflowCompilerMetadataKeys.target];
15102
15567
  return typeof value === "string" ? value : void 0;
15103
15568
  }
15104
- function isRecord8(value) {
15569
+ function isRecord11(value) {
15105
15570
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
15106
15571
  }
15107
15572
  function isSafeArtifactName(value) {
@@ -16145,8 +16610,19 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
16145
16610
  "domains",
16146
16611
  () => compileSemanticDomains(modules, context)
16147
16612
  );
16613
+ const vegetation3D = runCompilationPhase(
16614
+ "domains",
16615
+ () => withPreparedTreeModels(
16616
+ tileflowVegetationDescription(
16617
+ modules.vegetation,
16618
+ context
16619
+ ),
16620
+ config,
16621
+ options.preparedAssets
16622
+ )
16623
+ );
16148
16624
  const renderStackModules = Object.values(modules).filter(
16149
- (module) => isRecord9(module) && module.enabled !== false && "renderStack" in module && isRecord9(module.renderStack)
16625
+ (module) => isRecord12(module) && module.enabled !== false && "renderStack" in module && isRecord12(module.renderStack)
16150
16626
  );
16151
16627
  const renderOperations = runCompilationPhase(
16152
16628
  "render-stack",
@@ -16306,6 +16782,15 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
16306
16782
  () => createTileflowOverlayPlacementManifest(plannedLayers)
16307
16783
  );
16308
16784
  const layers = runCompilationPhase("finalization", () => finalizeTileflowLayers(plannedLayers));
16785
+ const buildings3D = tileflowBuildings3DDesignOf(
16786
+ modules.buildings,
16787
+ context,
16788
+ layers
16789
+ );
16790
+ const streetLights = tileflowStreetLightsDesignOf(
16791
+ modules.roads,
16792
+ context
16793
+ );
16309
16794
  runCompilationPhase(
16310
16795
  "finalization",
16311
16796
  () => assertTileflowInteractionManifestLayers(interactionManifest, layers)
@@ -16378,13 +16863,33 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
16378
16863
  "tileflow:colorScheme": resolvedTheme.colorScheme,
16379
16864
  [tileflowRouteMetadataKey]: tileflowRouteMetadata(resolveTileflowRouteInks(resolvedTheme)),
16380
16865
  ...resolvedTheme.sun ? { [tileflowSunMetadataKey]: tileflowSunMetadata(resolvedTheme.sun) } : {},
16866
+ ...buildings3D ? {
16867
+ [tileflowBuildingsMetadataKey]: tileflowBuildingsMetadata(
16868
+ bindSemanticReferences(buildings3D, semanticData)
16869
+ )
16870
+ } : {},
16871
+ ...streetLights ? {
16872
+ [tileflowStreetLightsMetadataKey]: tileflowStreetLightsMetadata(
16873
+ bindSemanticReferences(streetLights, semanticData)
16874
+ )
16875
+ } : {},
16876
+ ...vegetation3D ? {
16877
+ [tileflowVegetationMetadataKey]: tileflowVegetationMetadata({
16878
+ ...bindSemanticReferences(vegetation3D, semanticData),
16879
+ // The layers that draw the trees flat: the module's own and its render passes.
16880
+ flatLayers: layers.filter((layer) => {
16881
+ const metadata = layer.metadata;
16882
+ return metadata?.["tileflow:vegetation-mode"] !== void 0 || metadata?.["tileflow:domain"] === "vegetation";
16883
+ }).map((layer) => String(layer.id))
16884
+ })
16885
+ } : {},
16381
16886
  "tileflow:data": data.identity,
16382
16887
  "tileflow:sources": sourceIdentities,
16383
16888
  ...Object.keys(loweredOverlays.identities).length > 0 ? { "tileflow:overlays": loweredOverlays.identities } : {},
16384
16889
  "tileflow:sourceRequirements": sourceRequirements,
16385
16890
  ...interactionManifest ? { [tileflowInteractionManifestMetadataKey]: interactionManifest } : {},
16386
16891
  [tileflowOverlayPlacementManifestMetadataKey]: overlayPlacementManifest,
16387
- "tileflow:modules": Object.entries(modules).filter(([, module]) => !isRecord9(module) || module.enabled !== false).map(([name]) => name).sort(),
16892
+ "tileflow:modules": Object.entries(modules).filter(([, module]) => !isRecord12(module) || module.enabled !== false).map(([name]) => name).sort(),
16388
16893
  ...config.view ? { "tileflow:view": config.view } : {}
16389
16894
  }
16390
16895
  };
@@ -16436,7 +16941,7 @@ function finalizeTileflowLayers(input) {
16436
16941
  if (typeof minimum2 === "number" && typeof maximum2 === "number" && minimum2 > maximum2) {
16437
16942
  throw new Error(`Compiled Tileflow layer ${id2} requires minzoom <= maxzoom.`);
16438
16943
  }
16439
- const metadata = isRecord9(layer.metadata) ? { ...layer.metadata } : void 0;
16944
+ const metadata = isRecord12(layer.metadata) ? { ...layer.metadata } : void 0;
16440
16945
  if (metadata) {
16441
16946
  const owner = metadata[tileflowCompilerMetadataKeys.owner];
16442
16947
  delete metadata[tileflowCompilerProvenanceMetadataKey];
@@ -16470,13 +16975,32 @@ function assertMapLibreStyle(style, mapId) {
16470
16975
  }
16471
16976
  );
16472
16977
  }
16473
- function isRecord9(value) {
16978
+ function isRecord12(value) {
16474
16979
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
16475
16980
  }
16476
16981
  function resolveGlyphs(config) {
16477
16982
  if (config.fonts !== void 0) return void 0;
16478
16983
  return config.glyphs?.url;
16479
16984
  }
16985
+ function withPreparedTreeModels(vegetation2, config, prepared) {
16986
+ if (!vegetation2) return void 0;
16987
+ const referenced = [
16988
+ ...new Set(Object.values(vegetation2.forms).flatMap((form) => form?.models ?? []))
16989
+ ].sort();
16990
+ if (referenced.length === 0) return vegetation2;
16991
+ const available = prepared?.models ?? {};
16992
+ const missing = referenced.filter((id2) => !Object.hasOwn(available, id2));
16993
+ if (missing.length > 0) {
16994
+ 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.";
16995
+ throw new Error(
16996
+ `Tileflow map "${config.id}" draws missing 3D models: ${missing.join(", ")}. ${authoring}`
16997
+ );
16998
+ }
16999
+ return {
17000
+ ...vegetation2,
17001
+ models: Object.fromEntries(referenced.map((id2) => [id2, available[id2]]))
17002
+ };
17003
+ }
16480
17004
  function assertPreparedIconReferences(style, config, prepared) {
16481
17005
  const references = collectStyleImageReferences(style);
16482
17006
  if (references.size === 0) return;
@@ -16491,8 +17015,8 @@ function assertPreparedIconReferences(style, config, prepared) {
16491
17015
  function collectStyleImageReferences(style) {
16492
17016
  const result = /* @__PURE__ */ new Set();
16493
17017
  for (const layer of style.layers) {
16494
- const layout = isRecord9(layer.layout) ? layer.layout : {};
16495
- const paint = isRecord9(layer.paint) ? layer.paint : {};
17018
+ const layout = isRecord12(layer.layout) ? layer.layout : {};
17019
+ const paint = isRecord12(layer.paint) ? layer.paint : {};
16496
17020
  const layerId = typeof layer.id === "string" ? layer.id : "<unknown>";
16497
17021
  for (const [property, value] of [
16498
17022
  ["icon-image", layout["icon-image"]],
@@ -16581,7 +17105,7 @@ function assertGlyphFontStacks(style, config) {
16581
17105
  const declared = new Set(config.glyphs.fontStacks);
16582
17106
  const missing = /* @__PURE__ */ new Set();
16583
17107
  for (const layer of style.layers) {
16584
- const layout = isRecord9(layer.layout) ? layer.layout : void 0;
17108
+ const layout = isRecord12(layer.layout) ? layer.layout : void 0;
16585
17109
  const font = layout?.["text-font"];
16586
17110
  if (!Array.isArray(font) || !font.every((entry) => typeof entry === "string")) continue;
16587
17111
  const stack = font.join(",");
@@ -16597,7 +17121,7 @@ function assertGlyphFontStacks(style, config) {
16597
17121
  function assertTextAssets(style, config) {
16598
17122
  const textLayers = [];
16599
17123
  for (const layer of style.layers) {
16600
- const layout = isRecord9(layer.layout) ? layer.layout : void 0;
17124
+ const layout = isRecord12(layer.layout) ? layer.layout : void 0;
16601
17125
  if (layout?.["text-field"] === void 0) continue;
16602
17126
  textLayers.push(typeof layer.id === "string" ? layer.id : "<unknown>");
16603
17127
  const font = layout["text-font"];
@@ -16610,7 +17134,7 @@ function assertTextAssets(style, config) {
16610
17134
  if (textLayers.length === 0) return;
16611
17135
  if (config.fonts !== void 0) {
16612
17136
  const backed = style.layers.find((layer) => {
16613
- const font = isRecord9(layer.layout) ? layer.layout["text-font"] : void 0;
17137
+ const font = isRecord12(layer.layout) ? layer.layout["text-font"] : void 0;
16614
17138
  return Array.isArray(font) && font.length === 1 && typeof font[0] === "string" && parseTileflowLineBackgroundFontStack(font[0]) !== void 0;
16615
17139
  });
16616
17140
  if (backed) {
@@ -16636,7 +17160,7 @@ function runCompilationPhase(phase, operation) {
16636
17160
  }
16637
17161
  }
16638
17162
  function withCompilationPhase(error, phase) {
16639
- const record2 = isRecord9(error) ? error : {};
17163
+ const record2 = isRecord12(error) ? error : {};
16640
17164
  if (typeof record2.phase === "string") return error;
16641
17165
  if (error instanceof Error && Object.isExtensible(error)) {
16642
17166
  Object.defineProperty(error, "phase", { configurable: true, enumerable: true, value: phase });
@@ -16733,6 +17257,7 @@ var tileflowMapMergeStrategies = {
16733
17257
  icons: "icons",
16734
17258
  id: "identity",
16735
17259
  marine: "atomic",
17260
+ models: "atomic",
16736
17261
  modules: "modules",
16737
17262
  name: "identity",
16738
17263
  overlays: "keyed-resources",
@@ -17078,11 +17603,11 @@ function cloneDesign(value) {
17078
17603
  function describeMap(value) {
17079
17604
  return typeof value.id === "string" && value.id ? value.id : "<anonymous>";
17080
17605
  }
17081
- function isRecord10(value) {
17606
+ function isRecord13(value) {
17082
17607
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
17083
17608
  }
17084
17609
  function isPlainRecord4(value) {
17085
- if (!isRecord10(value)) return false;
17610
+ if (!isRecord13(value)) return false;
17086
17611
  const prototype = Object.getPrototypeOf(value);
17087
17612
  return prototype === Object.prototype || prototype === null;
17088
17613
  }
@@ -17840,7 +18365,7 @@ function semanticDesign(map) {
17840
18365
  }
17841
18366
  function compareValues(before, after, path, output) {
17842
18367
  if (Object.is(before, after)) return;
17843
- if (isRecord11(before) && isRecord11(after)) {
18368
+ if (isRecord14(before) && isRecord14(after)) {
17844
18369
  const keys = [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])].sort(
17845
18370
  compareCodeUnits9
17846
18371
  );
@@ -17882,7 +18407,7 @@ function toSemanticValue(value, path) {
17882
18407
  }
17883
18408
  throw new TypeError(`Tileflow semantic value at ${path || "/"} is not JSON-serializable.`);
17884
18409
  }
17885
- function isRecord11(value) {
18410
+ function isRecord14(value) {
17886
18411
  return value !== null && typeof value === "object" && !Array.isArray(value);
17887
18412
  }
17888
18413
  function isUnknownRecord(value) {
@@ -17895,7 +18420,7 @@ function deepEqual(left, right) {
17895
18420
  if (Array.isArray(left) || Array.isArray(right)) {
17896
18421
  return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => deepEqual(value, right[index]));
17897
18422
  }
17898
- if (!isRecord11(left) || !isRecord11(right)) return false;
18423
+ if (!isRecord14(left) || !isRecord14(right)) return false;
17899
18424
  const leftKeys = Object.keys(left).sort(compareCodeUnits9);
17900
18425
  const rightKeys = Object.keys(right).sort(compareCodeUnits9);
17901
18426
  return leftKeys.length === rightKeys.length && leftKeys.every((key, index) => key === rightKeys[index] && deepEqual(left[key], right[key]));
@@ -18696,7 +19221,7 @@ async function requestSessionGrant(input) {
18696
19221
  else throw error;
18697
19222
  }
18698
19223
  throwIfSessionGrantAborted(signal);
18699
- if (response.status === 409 && isRecord12(body) && body.code === "COMMERCIAL_SESSION_RESTART_REQUIRED" && body.ok === false && body.retryWithNewSession === true && body.sessionId === input.sessionId) {
19224
+ if (response.status === 409 && isRecord15(body) && body.code === "COMMERCIAL_SESSION_RESTART_REQUIRED" && body.ok === false && body.retryWithNewSession === true && body.sessionId === input.sessionId) {
18700
19225
  throw new TileflowSessionRestartError();
18701
19226
  }
18702
19227
  if (!response.ok) {
@@ -18769,7 +19294,7 @@ function sessionGrantAbortReason(signal) {
18769
19294
  return error;
18770
19295
  }
18771
19296
  function parseSessionGrantResponse(value, expectedSessionId) {
18772
- if (!isRecord12(value) || value.ok !== true || value.sessionId !== expectedSessionId) return null;
19297
+ if (!isRecord15(value) || value.ok !== true || value.sessionId !== expectedSessionId) return null;
18773
19298
  if (value.usageMode !== void 0 && value.usageMode !== "session") return null;
18774
19299
  if (value.billingEnabled !== void 0 && value.billingEnabled !== false) return null;
18775
19300
  if (value.disposition !== void 0 && value.disposition !== "ordinary" && value.disposition !== "unbilled_fail_open") {
@@ -18788,11 +19313,11 @@ function parseSessionGrantResponse(value, expectedSessionId) {
18788
19313
  }
18789
19314
  return value;
18790
19315
  }
18791
- function isRecord12(value) {
19316
+ function isRecord15(value) {
18792
19317
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
18793
19318
  }
18794
19319
  function isPlainRuntimeRecord(value) {
18795
- if (!isRecord12(value)) return false;
19320
+ if (!isRecord15(value)) return false;
18796
19321
  const prototype = Object.getPrototypeOf(value);
18797
19322
  return prototype === Object.prototype || prototype === null;
18798
19323
  }
@@ -19465,10 +19990,10 @@ var tileflowIconCompositionSchema = z13.object({
19465
19990
  })
19466
19991
  ).min(1).max(tileflowIconPackageLimits.maxIconCount)
19467
19992
  }).strict().superRefine((receipt, context) => {
19468
- const version = receipt.winners.some(
19993
+ const version2 = receipt.winners.some(
19469
19994
  (winner) => winner.appearance !== void 0 || winner.layout !== void 0
19470
19995
  ) ? 2 : 1;
19471
- if (receipt.compositionVersion !== version || receipt.format !== `tileflow-icon-composition-v${version}`)
19996
+ if (receipt.compositionVersion !== version2 || receipt.format !== `tileflow-icon-composition-v${version2}`)
19472
19997
  context.addIssue({
19473
19998
  code: "custom",
19474
19999
  path: ["compositionVersion"],
@@ -19574,6 +20099,164 @@ var tileflowIconSpriteIndexSchema = z14.record(tileflowIconIdSchema, tileflowIco
19574
20099
  (index) => Object.keys(index).length > 0 && Object.keys(index).length <= tileflowIconPackageLimits.maxIconCount,
19575
20100
  `A generated sprite index must contain 1 through ${tileflowIconPackageLimits.maxIconCount} icons`
19576
20101
  );
20102
+
20103
+ // src/trees-3d-mesh.ts
20104
+ var tileflowTreeParts = { bark: 0, leaves: 1, fronds: 2, stem: 3 };
20105
+ var golden = (1 + Math.sqrt(5)) / 2;
20106
+ var icosahedron = {
20107
+ corners: [
20108
+ [-1, golden, 0],
20109
+ [1, golden, 0],
20110
+ [-1, -golden, 0],
20111
+ [1, -golden, 0],
20112
+ [0, -1, golden],
20113
+ [0, 1, golden],
20114
+ [0, -1, -golden],
20115
+ [0, 1, -golden],
20116
+ [golden, 0, -1],
20117
+ [golden, 0, 1],
20118
+ [-golden, 0, -1],
20119
+ [-golden, 0, 1]
20120
+ ].map((corner) => normalize(corner)),
20121
+ faces: [
20122
+ [0, 11, 5],
20123
+ [0, 5, 1],
20124
+ [0, 1, 7],
20125
+ [0, 7, 10],
20126
+ [0, 10, 11],
20127
+ [1, 5, 9],
20128
+ [5, 11, 4],
20129
+ [11, 10, 2],
20130
+ [10, 7, 6],
20131
+ [7, 1, 8],
20132
+ [3, 9, 4],
20133
+ [3, 4, 2],
20134
+ [3, 2, 6],
20135
+ [3, 6, 8],
20136
+ [3, 8, 9],
20137
+ [4, 9, 5],
20138
+ [2, 4, 11],
20139
+ [6, 2, 10],
20140
+ [8, 6, 7],
20141
+ [9, 8, 1]
20142
+ ]
20143
+ };
20144
+ function normalize(v) {
20145
+ const size = Math.hypot(v[0], v[1], v[2]) || 1;
20146
+ return [v[0] / size, v[1] / size, v[2] / size];
20147
+ }
20148
+
20149
+ // src/trees-3d-model.ts
20150
+ var tileflowTreeModelLimits = Object.freeze({
20151
+ /** Triangles of a near model, the one drawn close to the camera. */
20152
+ nearTriangles: 1e3,
20153
+ /** Triangles of a far model, drawn beyond the nearest tiles. */
20154
+ farTriangles: 120,
20155
+ /** Vertices of one model. */
20156
+ vertices: 4096,
20157
+ /** Models one map carries. */
20158
+ models: 16,
20159
+ /** Models one form draws from. */
20160
+ perForm: 4
20161
+ });
20162
+ var magic = [84, 70, 84, 77];
20163
+ var version = 1;
20164
+ var headerBytes = 16;
20165
+ var vertexBytes = 12;
20166
+ var positionRange = 2;
20167
+ var partCount = Object.keys(tileflowTreeParts).length;
20168
+ function encodeTileflowTreeModel(mesh, level) {
20169
+ const count = mesh.positions.length / 3;
20170
+ const triangles = mesh.indices.length / 3;
20171
+ const limit = level === "near" ? tileflowTreeModelLimits.nearTriangles : tileflowTreeModelLimits.farTriangles;
20172
+ if (!Number.isInteger(count) || count < 3 || count > tileflowTreeModelLimits.vertices)
20173
+ throw new RangeError(`A tree model has 3 to ${tileflowTreeModelLimits.vertices} vertices.`);
20174
+ if (!Number.isInteger(triangles) || triangles < 1 || triangles > limit)
20175
+ throw new RangeError(`A ${level} tree model has 1 to ${limit} triangles.`);
20176
+ for (const list of [mesh.normals, mesh.positions])
20177
+ if (list.length !== count * 3)
20178
+ throw new RangeError("Every vertex needs a position and normal.");
20179
+ for (const list of [mesh.parts, mesh.shades, mesh.occlusion])
20180
+ if (list.length !== count) throw new RangeError("Every vertex needs a part, shade and light.");
20181
+ const bytes = new Uint8Array(headerBytes + count * vertexBytes + mesh.indices.length * 2);
20182
+ const view = new DataView(bytes.buffer);
20183
+ bytes.set(magic, 0);
20184
+ view.setUint16(4, version, true);
20185
+ view.setUint16(6, count, true);
20186
+ view.setUint32(8, mesh.indices.length, true);
20187
+ view.setUint16(12, Math.round(clamp(mesh.proportion, 0.1, 3) * 1e3), true);
20188
+ for (let index = 0; index < count; index += 1) {
20189
+ const at = headerBytes + index * vertexBytes;
20190
+ for (let axis = 0; axis < 3; axis += 1) {
20191
+ const value = clamp(mesh.positions[index * 3 + axis] / positionRange, -1, 1);
20192
+ view.setInt16(at + axis * 2, Math.round(value * 32767), true);
20193
+ view.setInt8(at + 6 + axis, Math.round(clamp(mesh.normals[index * 3 + axis], -1, 1) * 127));
20194
+ }
20195
+ const part = mesh.parts[index];
20196
+ if (!Number.isInteger(part) || part < 0 || part >= partCount)
20197
+ throw new RangeError(`Vertex ${index} has no tree part.`);
20198
+ bytes[at + 9] = part;
20199
+ bytes[at + 10] = Math.round((clamp(mesh.shades[index], -1, 1) + 1) / 2 * 255);
20200
+ bytes[at + 11] = Math.round(clamp(mesh.occlusion[index], 0, 1) * 255);
20201
+ }
20202
+ const indexAt = headerBytes + count * vertexBytes;
20203
+ mesh.indices.forEach((vertex, index) => {
20204
+ if (!Number.isInteger(vertex) || vertex < 0 || vertex >= count)
20205
+ throw new RangeError(`Index ${index} names no vertex.`);
20206
+ view.setUint16(indexAt + index * 2, vertex, true);
20207
+ });
20208
+ let text = "";
20209
+ for (let at = 0; at < bytes.length; at += 32768)
20210
+ text += String.fromCharCode(...bytes.subarray(at, at + 32768));
20211
+ return btoa(text);
20212
+ }
20213
+ function decodeTileflowTreeModel(text, level) {
20214
+ if (typeof text !== "string" || text.length > 2e5) return void 0;
20215
+ let bytes;
20216
+ try {
20217
+ const binary = atob(text);
20218
+ bytes = new Uint8Array(binary.length);
20219
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
20220
+ } catch {
20221
+ return void 0;
20222
+ }
20223
+ if (bytes.length < headerBytes || magic.some((byte, index) => bytes[index] !== byte))
20224
+ return void 0;
20225
+ const view = new DataView(bytes.buffer);
20226
+ const count = view.getUint16(6, true);
20227
+ const indexCount = view.getUint32(8, true);
20228
+ const proportion = view.getUint16(12, true) / 1e3;
20229
+ const limit = level === "near" ? tileflowTreeModelLimits.nearTriangles : tileflowTreeModelLimits.farTriangles;
20230
+ if (view.getUint16(4, true) !== version || count < 3 || count > tileflowTreeModelLimits.vertices || indexCount < 3 || indexCount % 3 !== 0 || indexCount / 3 > limit || !(proportion >= 0.1 && proportion <= 3) || bytes.length !== headerBytes + count * vertexBytes + indexCount * 2)
20231
+ return void 0;
20232
+ const positions = [];
20233
+ const normals = [];
20234
+ const parts = [];
20235
+ const shades = [];
20236
+ const occlusion = [];
20237
+ for (let index = 0; index < count; index += 1) {
20238
+ const at = headerBytes + index * vertexBytes;
20239
+ for (let axis = 0; axis < 3; axis += 1) {
20240
+ positions.push(view.getInt16(at + axis * 2, true) / 32767 * positionRange);
20241
+ normals.push(view.getInt8(at + 6 + axis) / 127);
20242
+ }
20243
+ if (bytes[at + 9] >= partCount) return void 0;
20244
+ parts.push(bytes[at + 9]);
20245
+ shades.push(bytes[at + 10] / 255 * 2 - 1);
20246
+ occlusion.push(bytes[at + 11] / 255);
20247
+ }
20248
+ const indices = [];
20249
+ const indexAt = headerBytes + count * vertexBytes;
20250
+ for (let index = 0; index < indexCount; index += 1) {
20251
+ const vertex = view.getUint16(indexAt + index * 2, true);
20252
+ if (vertex >= count) return void 0;
20253
+ indices.push(vertex);
20254
+ }
20255
+ return { proportion, positions, normals, parts, shades, occlusion, indices };
20256
+ }
20257
+ function clamp(value, low, high) {
20258
+ return Number.isFinite(value) ? Math.min(high, Math.max(low, value)) : 0;
20259
+ }
19577
20260
  export {
19578
20261
  TileflowIconSetError,
19579
20262
  TileflowMapResolutionError,
@@ -19602,6 +20285,7 @@ export {
19602
20285
  createTileflowLineBackgroundFontStack,
19603
20286
  createTileflowSessionController,
19604
20287
  createTileflowSessionId,
20288
+ decodeTileflowTreeModel,
19605
20289
  defaultTileflowManifestUrl,
19606
20290
  defaultTileflowRuntimeView,
19607
20291
  defineMap,
@@ -19610,6 +20294,7 @@ export {
19610
20294
  diffTileflowIconPackageManifests,
19611
20295
  diffTileflowMaps,
19612
20296
  disable,
20297
+ encodeTileflowTreeModel,
19613
20298
  expr,
19614
20299
  field,
19615
20300
  fixed,
@@ -19658,8 +20343,11 @@ export {
19658
20343
  parseTileflowMap,
19659
20344
  parseTileflowRuntimeManifest,
19660
20345
  poi,
20346
+ readTileflowBuildings3D,
19661
20347
  readTileflowIconLayout,
20348
+ readTileflowStreetLights,
19662
20349
  readTileflowSunPreset,
20350
+ readTileflowVegetation3D,
19663
20351
  refine,
19664
20352
  refineRenderTarget,
19665
20353
  remove,
@@ -19700,6 +20388,7 @@ export {
19700
20388
  tileflowBathymetryDemSourceId,
19701
20389
  tileflowBathymetrySourceId,
19702
20390
  tileflowBathymetryV1Schema,
20391
+ tileflowBuildingsMetadataKey,
19703
20392
  tileflowCaptureApplicationTargetSchema,
19704
20393
  tileflowCaptureBoundsCameraSchema,
19705
20394
  tileflowCaptureCameraSchema,
@@ -19788,6 +20477,7 @@ export {
19788
20477
  tileflowRuntimeManifestSchema,
19789
20478
  tileflowRuntimeManifestVersion,
19790
20479
  tileflowSemanticDiffSchemaVersion,
20480
+ tileflowStreetLightsMetadataKey,
19791
20481
  tileflowStyleFontFaceLimits,
19792
20482
  tileflowStyleFontFacesMetadataKey,
19793
20483
  tileflowStyleSunPreset,
@@ -19798,6 +20488,11 @@ export {
19798
20488
  tileflowThemeLimits,
19799
20489
  tileflowThemeNameSchema,
19800
20490
  tileflowThemeTokenCategories,
20491
+ tileflowTreeForms,
20492
+ tileflowTreeModelIdPattern,
20493
+ tileflowTreeModelLimits,
20494
+ tileflowTreeParts,
20495
+ tileflowVegetationMetadataKey,
19801
20496
  tileflowWorld,
19802
20497
  tileflowWorldGeneration,
19803
20498
  tileflowWorldReleaseIdMaximumLength,