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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/build.js CHANGED
@@ -45,6 +45,25 @@ function compileTileflowSky(options) {
45
45
  };
46
46
  }
47
47
 
48
+ // src/buildings-3d-design.ts
49
+ var tileflowBuildingsMetadataKey = "tileflow:buildings";
50
+ var tileflowDefaultBuildings3DDesign = Object.freeze({
51
+ source: "tileflow",
52
+ layers: Object.freeze({ building: "building", places: "poi" }),
53
+ color: "#d8d0c0",
54
+ edges: 0,
55
+ lighting: "style",
56
+ colorVariation: 0,
57
+ verticalGradient: true,
58
+ nightLight: 0,
59
+ windows: false,
60
+ shadows: false,
61
+ occlusion: false
62
+ });
63
+ function tileflowBuildingsMetadata(design) {
64
+ return { version: 1, ...design };
65
+ }
66
+
48
67
  // src/data/world-generation.ts
49
68
  var tileflowWorldGeneration = "v1";
50
69
 
@@ -135,6 +154,7 @@ var canonicalFields = {
135
154
  markings: "markings",
136
155
  minHeight: "min_height",
137
156
  minZoom: "min_zoom",
157
+ mode: "mode",
138
158
  mtbScale: "mtb_scale",
139
159
  name: "name",
140
160
  nameEnglish: "name:en",
@@ -635,7 +655,16 @@ var tileflowBathymetrySourceId = "tileflow-bathymetry";
635
655
  var tileflowBathymetryDemSourceId = "tileflow-bathymetry-dem";
636
656
  var tileflowNauticalSourceId = "tileflow-nautical";
637
657
  var tileflowBathymetryV1Schema = Object.freeze({
638
- fields: Object.freeze({ minDepth: "min_depth", sortKey: "sort_key" }),
658
+ fields: Object.freeze({
659
+ class: "class",
660
+ depth: "depth",
661
+ id: "id",
662
+ maxZoom: "max_zoom",
663
+ minDepth: "min_depth",
664
+ minZoom: "min_zoom",
665
+ name: "name",
666
+ sortKey: "sort_key"
667
+ }),
639
668
  layer: "bathymetry",
640
669
  layers: Object.freeze({
641
670
  bands: "bathymetry",
@@ -700,6 +729,7 @@ var tileflowNauticalV1Schema = Object.freeze({
700
729
  product: "nautical-v1",
701
730
  schemaVersion: 1
702
731
  });
732
+ var tileflowBathymetryNameLayerLimits = Object.freeze({ maxZoomRanges: 128 });
703
733
  function resolveMarine(marine, apiBaseUrl = "https://api.tileflow.dev") {
704
734
  if (marine === void 0) return void 0;
705
735
  if (typeof marine === "string") {
@@ -739,6 +769,7 @@ function resolveBathymetry(input, apiBaseUrl) {
739
769
  ...advanced?.contours === void 0 ? {} : { contours: advanced.contours },
740
770
  display,
741
771
  ...advanced?.labels === void 0 ? {} : { labels: advanced.labels },
772
+ ...advanced?.vectorLayers === void 0 ? {} : { vectorLayers: cloneJson(advanced.vectorLayers) },
742
773
  ...reliefVisible ? { relief: resolveBathymetryRelief(reliefConfig ?? {}, apiBaseUrl) } : {},
743
774
  ...usesVector ? {
744
775
  vector: resolveMarineSource({
@@ -905,20 +936,20 @@ function createTileflowLineBackgroundFontStack(source, metrics = {}) {
905
936
  return stack;
906
937
  }
907
938
  function parseTileflowLineBackgroundFontStack(stack) {
908
- const match2 = STACK_PATTERN.exec(stack);
909
- if (!match2 || stack.length > MAX_STACK_LENGTH || !SOURCE_PATTERN.test(match2[1]))
939
+ const match3 = STACK_PATTERN.exec(stack);
940
+ if (!match3 || stack.length > MAX_STACK_LENGTH || !SOURCE_PATTERN.test(match3[1]))
910
941
  return void 0;
911
942
  const metrics = {
912
- letterSpacing: Number(match2[2]) / 1e3,
913
- lineHeight: Number(match2[3]) / 1e3,
914
- padding: Number(match2[4]) / 1e3
943
+ letterSpacing: Number(match3[2]) / 1e3,
944
+ lineHeight: Number(match3[3]) / 1e3,
945
+ padding: Number(match3[4]) / 1e3
915
946
  };
916
947
  try {
917
948
  const normalized = normalizeTileflowLineBackgroundMetrics(metrics);
918
- if (`lines-v1-t${thousandths(normalized.letterSpacing)}-l${thousandths(normalized.lineHeight)}-p${thousandths(normalized.padding)}` !== stack.slice(match2[1].length + 1)) {
949
+ if (`lines-v1-t${thousandths(normalized.letterSpacing)}-l${thousandths(normalized.lineHeight)}-p${thousandths(normalized.padding)}` !== stack.slice(match3[1].length + 1)) {
919
950
  return void 0;
920
951
  }
921
- return { metrics: normalized, source: match2[1] };
952
+ return { metrics: normalized, source: match3[1] };
922
953
  } catch {
923
954
  return void 0;
924
955
  }
@@ -1655,7 +1686,7 @@ function compileVegetation(request, context) {
1655
1686
  }
1656
1687
  function tileflowVegetationDescription(request, context) {
1657
1688
  const { schema } = context.data;
1658
- if (request?.enabled === false || (request?.mode ?? "3d") !== "3d" || !schema.layers.tree)
1689
+ if (request?.enabled === false || request?.mode === "flat" || !schema.layers.tree)
1659
1690
  return void 0;
1660
1691
  const tree = context.colors.vegetation.tree;
1661
1692
  const style = request?.threeDimensional ?? {};
@@ -1718,102 +1749,6 @@ function tileflowVegetationDescription(request, context) {
1718
1749
  };
1719
1750
  }
1720
1751
 
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
1752
  // src/cartography/contributions.ts
1818
1753
  var tileflowLayerSlots = [
1819
1754
  "background",
@@ -2372,7 +2307,21 @@ function buildingColorExpression(context) {
2372
2307
  colors.destination,
2373
2308
  ["==", ["coalesce", ["get", fields.buildingTone], ""], "commercial"],
2374
2309
  colors.commercial,
2375
- colors.generic
2310
+ // Without a tone, World's use class: housing, shops and offices, public buildings and works
2311
+ // each take their theme colour; a shed, a garage or an unknown use the generic one.
2312
+ [
2313
+ "match",
2314
+ ["coalesce", ["get", fields.class], ""],
2315
+ "residential",
2316
+ colors.residential,
2317
+ "commercial",
2318
+ colors.commercial,
2319
+ ["civic", "education", "entertainment", "medical", "military", "religious", "transportation"],
2320
+ colors.civic,
2321
+ ["agricultural", "industrial", "service"],
2322
+ colors.industrial,
2323
+ colors.generic
2324
+ ]
2376
2325
  ];
2377
2326
  }
2378
2327
  function buildingVisibilityFilter() {
@@ -2636,6 +2585,9 @@ function cloneJson9(value) {
2636
2585
  function labelField(language, context) {
2637
2586
  return expression(labelFieldExpression(language, context));
2638
2587
  }
2588
+ function labelNameFields(language, context) {
2589
+ return labelFieldExpression(language, context).slice(1).map((entry) => entry[1]);
2590
+ }
2639
2591
  function labelFieldExpression(language, context) {
2640
2592
  const fields = context.data.schema.fields;
2641
2593
  if (language === "local") {
@@ -3493,12 +3445,12 @@ function compileLand(request, context) {
3493
3445
  if (name === "recreation") continue;
3494
3446
  appendLanduse(name, classes);
3495
3447
  }
3496
- for (const [name, match2] of landcoverClasses) {
3448
+ for (const [name, match3] of landcoverClasses) {
3497
3449
  const style = config.landcover?.[name];
3498
3450
  if (!style) continue;
3499
3451
  const protectedSource = name === "protected";
3500
3452
  const sourceLayer = protectedSource ? schema.layers.park : schema.layers.landcover;
3501
- const filter = protectedSource && schema.semantics.parkLayer === "protected-only" ? void 0 : landcoverFilter(classField, subclassField, match2);
3453
+ const filter = protectedSource && schema.semantics.parkLayer === "protected-only" ? void 0 : landcoverFilter(classField, subclassField, match3);
3502
3454
  for (const area of createAreaLayers(
3503
3455
  {
3504
3456
  id: `tileflow-landcover-${name}`,
@@ -3554,11 +3506,11 @@ function compileLand(request, context) {
3554
3506
  function classFilter2(field2, classes) {
3555
3507
  return ["match", ["get", field2], classes, true, false];
3556
3508
  }
3557
- function landcoverFilter(classField, subclassField, match2) {
3558
- const filter = classFilter2(classField, match2.class);
3559
- if (match2.subclass) return ["all", filter, classFilter2(subclassField, match2.subclass)];
3560
- if (match2.excludeSubclass) {
3561
- return ["all", filter, ["!", classFilter2(subclassField, match2.excludeSubclass)]];
3509
+ function landcoverFilter(classField, subclassField, match3) {
3510
+ const filter = classFilter2(classField, match3.class);
3511
+ if (match3.subclass) return ["all", filter, classFilter2(subclassField, match3.subclass)];
3512
+ if (match3.excludeSubclass) {
3513
+ return ["all", filter, ["!", classFilter2(subclassField, match3.excludeSubclass)]];
3562
3514
  }
3563
3515
  return filter;
3564
3516
  }
@@ -4267,6 +4219,12 @@ var tileflowPoiImageRoles = Object.freeze({
4267
4219
  transport: { fallback: "major-transit", token: "poi.transport" },
4268
4220
  "visitor-amenity": { fallback: "services", token: "poi.visitor-amenity" }
4269
4221
  });
4222
+ var tileflowPoiIconOnlyTypes = Object.freeze([
4223
+ "parking",
4224
+ "parking_garage",
4225
+ "parking_lot",
4226
+ "public_restroom"
4227
+ ]);
4270
4228
  function poi(options = {}) {
4271
4229
  return { type: "poi", ...cloneJson13(options) };
4272
4230
  }
@@ -4280,7 +4238,10 @@ function resolvePoi(request) {
4280
4238
  minZoom: request?.minZoom ?? 0,
4281
4239
  placement: {
4282
4240
  coupleIconAndLabel: request?.placement?.coupleIconAndLabel ?? false,
4241
+ iconOnlyTypes: [...request?.placement?.iconOnlyTypes ?? tileflowPoiIconOnlyTypes],
4283
4242
  iconPadding: request?.placement?.iconPadding ?? 2,
4243
+ order: request?.placement?.order ?? "importance",
4244
+ ...request?.placement?.prominentRank === void 0 ? {} : { prominentRank: request.placement.prominentRank },
4284
4245
  textPadding: request?.placement?.textPadding ?? 2
4285
4246
  }
4286
4247
  };
@@ -4289,127 +4250,526 @@ function cloneJson13(value) {
4289
4250
  return JSON.parse(JSON.stringify(value));
4290
4251
  }
4291
4252
 
4253
+ // src/modules/poi/placement.ts
4254
+ var tileflowPoiFeatureVaryingProperties = Object.freeze({
4255
+ circle: Object.freeze({
4256
+ "circle-blur": 0,
4257
+ "circle-color": "#000000",
4258
+ "circle-opacity": 1,
4259
+ "circle-radius": 5,
4260
+ "circle-sort-key": void 0,
4261
+ "circle-stroke-color": "#000000",
4262
+ "circle-stroke-opacity": 1,
4263
+ "circle-stroke-width": 0
4264
+ }),
4265
+ symbol: Object.freeze({
4266
+ "icon-anchor": "center",
4267
+ "icon-color": "#000000",
4268
+ "icon-halo-blur": 0,
4269
+ "icon-halo-color": "rgba(0, 0, 0, 0)",
4270
+ "icon-halo-width": 0,
4271
+ // An empty image draws no icon, so a category without one stays text-only.
4272
+ "icon-image": "",
4273
+ "icon-offset": [0, 0],
4274
+ "icon-opacity": 1,
4275
+ "icon-padding": [2],
4276
+ "icon-rotate": 0,
4277
+ "icon-size": 1,
4278
+ "symbol-sort-key": void 0,
4279
+ "text-anchor": "center",
4280
+ "text-color": "#000000",
4281
+ "text-field": "",
4282
+ // MapLibre can vary `text-font` per feature, but Tileflow prepares glyphs for a static font
4283
+ // stack, so the font stays a layer-wide property.
4284
+ "text-halo-blur": 0,
4285
+ "text-halo-color": "rgba(0, 0, 0, 0)",
4286
+ "text-halo-width": 0,
4287
+ "text-justify": "center",
4288
+ "text-letter-spacing": 0,
4289
+ "text-max-width": 10,
4290
+ "text-offset": [0, 0],
4291
+ "text-opacity": 1,
4292
+ "text-radial-offset": 0,
4293
+ "text-rotate": 0,
4294
+ "text-size": 16,
4295
+ "text-transform": "none",
4296
+ "text-variable-anchor-offset": void 0
4297
+ })
4298
+ });
4299
+ var tileflowPoiLayerPropertyDefaults = Object.freeze({
4300
+ "circle-pitch-alignment": "viewport",
4301
+ "circle-pitch-scale": "map",
4302
+ "circle-translate": [0, 0],
4303
+ "circle-translate-anchor": "map",
4304
+ "icon-allow-overlap": false,
4305
+ "icon-ignore-placement": false,
4306
+ "icon-keep-upright": false,
4307
+ "icon-optional": false,
4308
+ "icon-pitch-alignment": "auto",
4309
+ "icon-rotation-alignment": "auto",
4310
+ "icon-text-fit": "none",
4311
+ "icon-text-fit-padding": [0, 0, 0, 0],
4312
+ "symbol-avoid-edges": false,
4313
+ "symbol-placement": "point",
4314
+ "symbol-spacing": 250,
4315
+ "symbol-z-order": "auto",
4316
+ "text-allow-overlap": false,
4317
+ "text-ignore-placement": false,
4318
+ "text-keep-upright": true,
4319
+ "text-line-height": 1.2,
4320
+ "text-max-angle": 45,
4321
+ "text-optional": false,
4322
+ "text-padding": 2,
4323
+ "text-pitch-alignment": "auto",
4324
+ "text-rotation-alignment": "auto",
4325
+ visibility: "visible"
4326
+ });
4327
+ var TileflowPoiPlacementError = class extends Error {
4328
+ code = "TILEFLOW_POI_PLACEMENT_CONFLICT";
4329
+ constructor(message) {
4330
+ super(message);
4331
+ this.name = "TileflowPoiPlacementError";
4332
+ }
4333
+ };
4334
+ function mergePoiCategoryLayers(entries, options) {
4335
+ const [first] = entries;
4336
+ if (!first) throw new TileflowPoiPlacementError("A POI placement layer needs a category.");
4337
+ const kind = first.layer.type === "circle" ? "circle" : "symbol";
4338
+ const varying = tileflowPoiFeatureVaryingProperties[kind];
4339
+ const categories = entries.map(({ category }) => category);
4340
+ for (const property of ["type", "source", "source-layer", "minzoom", "maxzoom"]) {
4341
+ requireShared(entries, property, (layer) => layer[property]);
4342
+ }
4343
+ const result = {
4344
+ ...first.layer,
4345
+ id: options.id,
4346
+ filter: options.filter
4347
+ };
4348
+ for (const part of ["layout", "paint"]) {
4349
+ const records = entries.map(({ layer }) => asRecord2(layer[part]));
4350
+ const properties2 = [...new Set(records.flatMap((record3) => Object.keys(record3)))].sort();
4351
+ const merged = {};
4352
+ for (const property of properties2) {
4353
+ const fallback = Object.hasOwn(varying, property) ? varying[property] : tileflowPoiLayerPropertyDefaults[property];
4354
+ const values = records.map(
4355
+ (record3) => Object.hasOwn(record3, property) ? record3[property] : fallback
4356
+ );
4357
+ if (values.every((value) => sameJson(value, values[0]))) {
4358
+ if (values[0] !== void 0) merged[property] = values[0];
4359
+ continue;
4360
+ }
4361
+ if (!Object.hasOwn(varying, property)) {
4362
+ throw conflict(entries, property, (layer) => asRecord2(layer[part])[property]);
4363
+ }
4364
+ if (values.some((value) => value === void 0)) {
4365
+ throw new TileflowPoiPlacementError(
4366
+ `POI categories ${categories.join(", ")} set ${property} for only some categories, and MapLibre has no default to fill in for the rest. Set it for every category.`
4367
+ );
4368
+ }
4369
+ merged[property] = matchByCategory(
4370
+ property,
4371
+ categories.map((category, index) => [category, values[index]]),
4372
+ ["get", options.categoryField]
4373
+ );
4374
+ }
4375
+ if (Object.keys(merged).length) result[part] = merged;
4376
+ else delete result[part];
4377
+ }
4378
+ return result;
4379
+ }
4380
+ function requireShared(entries, property, read) {
4381
+ const value = read(entries[0].layer);
4382
+ if (entries.every(({ layer }) => sameJson(read(layer), value))) return;
4383
+ throw conflict(entries, property, read);
4384
+ }
4385
+ function conflict(entries, property, read) {
4386
+ const described = entries.map(({ category, layer }) => `${category}: ${JSON.stringify(read(layer)) ?? "unset"}`).join("; ");
4387
+ return new TileflowPoiPlacementError(
4388
+ `POI categories need different ${property} values (${described}), but MapLibre keeps ${property} constant within one layer. Give them the same ${property}, or set placement.order to 'categories' to place each category in its own layer.`
4389
+ );
4390
+ }
4391
+ function matchByCategory(property, values, input) {
4392
+ const curves = values.map(([, value]) => zoomCurve(value));
4393
+ const present = curves.filter((curve) => curve !== void 0);
4394
+ if (present.length === 0) return match(input, values);
4395
+ const reference = present[0];
4396
+ for (const curve of present) {
4397
+ if (curve.kind !== reference.kind || curve.kind === "interpolate" && reference.kind === "interpolate" && (curve.operator !== reference.operator || !sameJson(curve.interpolation, reference.interpolation))) {
4398
+ throw new TileflowPoiPlacementError(
4399
+ `POI categories use different kinds of zoom curve for ${property}. Use one kind of curve (linear, the same exponential base, or step) for every category, or set placement.order to 'categories'.`
4400
+ );
4401
+ }
4402
+ }
4403
+ const stops = [...new Set(present.flatMap((curve) => curve.stops.map(([zoom3]) => zoom3)))].sort(
4404
+ (left, right) => left - right
4405
+ );
4406
+ const at = (zoom3) => match(
4407
+ input,
4408
+ values.map(([category, value], index) => [
4409
+ category,
4410
+ curves[index] ? evaluateCurve(property, curves[index], zoom3) : value
4411
+ ])
4412
+ );
4413
+ if (reference.kind === "step") {
4414
+ const base = match(
4415
+ input,
4416
+ values.map(([category, value], index) => {
4417
+ const curve = curves[index];
4418
+ return [category, curve?.kind === "step" ? curve.base : value];
4419
+ })
4420
+ );
4421
+ return ["step", ["zoom"], base, ...stops.flatMap((zoom3) => [zoom3, at(zoom3)])];
4422
+ }
4423
+ return [
4424
+ reference.operator,
4425
+ reference.interpolation,
4426
+ ["zoom"],
4427
+ ...stops.flatMap((zoom3) => [zoom3, at(zoom3)])
4428
+ ];
4429
+ }
4430
+ function match(input, values) {
4431
+ const groups = [];
4432
+ for (const [category, value] of values) {
4433
+ const group = groups.find((candidate) => sameJson(candidate.value, value));
4434
+ if (group) group.categories.push(category);
4435
+ else groups.push({ categories: [category], value });
4436
+ }
4437
+ if (groups.length === 1) return groups[0].value;
4438
+ const fallback = groups.reduce(
4439
+ (largest, group) => group.categories.length > largest.categories.length ? group : largest
4440
+ );
4441
+ return [
4442
+ "match",
4443
+ input,
4444
+ ...groups.filter((group) => group !== fallback).flatMap((group) => [
4445
+ group.categories.length === 1 ? group.categories[0] : group.categories,
4446
+ expressionValue(group.value)
4447
+ ]),
4448
+ expressionValue(fallback.value)
4449
+ ];
4450
+ }
4451
+ function zoomCurve(value) {
4452
+ if (!Array.isArray(value)) return void 0;
4453
+ const [operator] = value;
4454
+ if ((operator === "interpolate" || operator === "interpolate-hcl" || operator === "interpolate-lab") && isZoomInput3(value[2])) {
4455
+ return {
4456
+ kind: "interpolate",
4457
+ operator,
4458
+ interpolation: value[1],
4459
+ stops: pairs(value.slice(3))
4460
+ };
4461
+ }
4462
+ if (operator === "step" && isZoomInput3(value[1])) {
4463
+ return { kind: "step", base: value[2], stops: pairs(value.slice(3)) };
4464
+ }
4465
+ return void 0;
4466
+ }
4467
+ function evaluateCurve(property, curve, zoom3) {
4468
+ const { stops } = curve;
4469
+ if (curve.kind === "step") {
4470
+ let value2 = curve.base;
4471
+ for (const [stop, output] of stops) if (stop <= zoom3) value2 = output;
4472
+ return value2;
4473
+ }
4474
+ const exact = stops.find(([stop]) => stop === zoom3);
4475
+ if (exact) return exact[1];
4476
+ if (zoom3 < stops[0][0]) return stops[0][1];
4477
+ if (zoom3 > stops.at(-1)[0]) return stops.at(-1)[1];
4478
+ const upperIndex = stops.findIndex(([stop]) => stop > zoom3);
4479
+ const [lowerZoom, lower] = stops[upperIndex - 1];
4480
+ const [upperZoom, upper] = stops[upperIndex];
4481
+ const t = interpolationFactor(curve.interpolation, zoom3, lowerZoom, upperZoom);
4482
+ const value = interpolateOutput(lower, upper, t);
4483
+ if (value === void 0) {
4484
+ throw new TileflowPoiPlacementError(
4485
+ `POI categories interpolate ${property} between values Tileflow cannot evaluate at zoom ${zoom3}. Give every category the same zoom stops for ${property}, or set placement.order to 'categories'.`
4486
+ );
4487
+ }
4488
+ return value;
4489
+ }
4490
+ function interpolationFactor(interpolation, zoom3, lower, upper) {
4491
+ const progress = zoom3 - lower;
4492
+ const range2 = upper - lower;
4493
+ if (Array.isArray(interpolation) && interpolation[0] === "exponential") {
4494
+ const base = Number(interpolation[1]);
4495
+ if (base !== 1) return (base ** progress - 1) / (base ** range2 - 1);
4496
+ }
4497
+ return progress / range2;
4498
+ }
4499
+ function interpolateOutput(lower, upper, t) {
4500
+ if (typeof lower === "number" && typeof upper === "number") {
4501
+ return roundOutput(lower + (upper - lower) * t);
4502
+ }
4503
+ const lowerArray = numericArray(lower);
4504
+ const upperArray = numericArray(upper);
4505
+ if (lowerArray && upperArray && lowerArray.length === upperArray.length) {
4506
+ return [
4507
+ "literal",
4508
+ lowerArray.map((value, index) => roundOutput(value + (upperArray[index] - value) * t))
4509
+ ];
4510
+ }
4511
+ if (isNumericOutput(lower) && isNumericOutput(upper)) {
4512
+ return ["+", lower, ["*", roundOutput(t), ["-", upper, lower]]];
4513
+ }
4514
+ return void 0;
4515
+ }
4516
+ function isNumericOutput(value) {
4517
+ if (typeof value === "number") return true;
4518
+ if (!Array.isArray(value) || typeof value[0] !== "string") return false;
4519
+ return isMapLibreExpressionOperator(value[0]) && !["literal", "array"].includes(value[0]);
4520
+ }
4521
+ function numericArray(value) {
4522
+ const array = Array.isArray(value) && value[0] === "literal" && Array.isArray(value[1]) ? value[1] : value;
4523
+ return Array.isArray(array) && array.every((entry) => typeof entry === "number") ? array : void 0;
4524
+ }
4525
+ function roundOutput(value) {
4526
+ return Math.round(value * 1e6) / 1e6;
4527
+ }
4528
+ function expressionValue(value) {
4529
+ if (!Array.isArray(value)) return value;
4530
+ if (typeof value[0] === "string" && isMapLibreExpressionOperator(value[0])) return value;
4531
+ return ["literal", value];
4532
+ }
4533
+ function pairs(values) {
4534
+ const result = [];
4535
+ for (let index = 0; index + 1 < values.length; index += 2) {
4536
+ result.push([values[index], values[index + 1]]);
4537
+ }
4538
+ return result;
4539
+ }
4540
+ function isZoomInput3(value) {
4541
+ return Array.isArray(value) && value.length === 1 && value[0] === "zoom";
4542
+ }
4543
+ function sameJson(left, right) {
4544
+ return JSON.stringify(left) === JSON.stringify(right);
4545
+ }
4546
+ function asRecord2(value) {
4547
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
4548
+ }
4549
+
4292
4550
  // src/modules/poi/compiler.ts
4551
+ var representationOrder = {
4552
+ marker: 0,
4553
+ combined: 1,
4554
+ icon: 1,
4555
+ label: 2
4556
+ };
4293
4557
  function compilePoi(request, context, language = "auto") {
4294
4558
  if (request?.enabled === false) return [];
4295
4559
  const semantics = resolvePoi(request);
4296
- const result = [];
4560
+ const selection2 = createPoiSelection(semantics, context, language);
4561
+ const parts = semantics.categories.flatMap(
4562
+ (category) => compileCategory(category, request, semantics, context, language, selection2)
4563
+ );
4564
+ if (semantics.placement.order === "categories") {
4565
+ return parts.map((part) => {
4566
+ const rank = semantics.categories.length - 1 - semantics.categories.indexOf(part.category);
4567
+ const suffix = part.representation === "combined" ? "" : `.${part.representation}`;
4568
+ return poiContribution(
4569
+ `${part.category}${suffix}`,
4570
+ rank * 4 + representationOrder[part.representation],
4571
+ {
4572
+ ...part.layer,
4573
+ id: `tileflow-poi-${part.category}${suffix.replace(".", "-")}`,
4574
+ filter: selection2.filter([part.category], part.representation)
4575
+ }
4576
+ );
4577
+ });
4578
+ }
4579
+ const { prominentRank } = semantics.placement;
4580
+ const filterRank = numericPoiField(context.data.schema.fields.poiFilterRank, 6);
4581
+ return ["marker", "combined", "icon", "label"].flatMap((representation) => {
4582
+ const entries = parts.filter((part) => part.representation === representation);
4583
+ if (entries.length === 0) return [];
4584
+ const suffix = representation === "combined" ? "" : `.${representation}`;
4585
+ const filter = selection2.filter(
4586
+ entries.map(({ category }) => category),
4587
+ representation
4588
+ );
4589
+ const layer = mergePoiCategoryLayers(entries, {
4590
+ categoryField: context.data.schema.fields.poiCategory,
4591
+ filter,
4592
+ id: `tileflow-poi${suffix.replace(".", "-")}`
4593
+ });
4594
+ if (prominentRank === void 0 || representation === "marker") {
4595
+ return [poiContribution(suffix.slice(1), representationOrder[representation], layer)];
4596
+ }
4597
+ return [
4598
+ poiContribution(suffix.slice(1), representationOrder[representation], {
4599
+ ...layer,
4600
+ filter: [...filter, [">", filterRank, prominentRank]]
4601
+ }),
4602
+ poiContribution(`prominent${suffix}`, 400 + representationOrder[representation], {
4603
+ ...layer,
4604
+ id: `tileflow-poi-prominent${suffix.replace(".", "-")}`,
4605
+ filter: [...filter, ["<=", filterRank, prominentRank]]
4606
+ })
4607
+ ];
4608
+ });
4609
+ }
4610
+ function compileCategory(category, request, semantics, context, language, selection2) {
4297
4611
  const { sourceId: source, schema } = context.data;
4298
- for (const [index, category] of semantics.categories.entries()) {
4299
- const requestedCategoryStyle = request?.styles?.[category];
4300
- const categoryDensity = requestedCategoryStyle?.density ?? semantics.density;
4301
- const { density: _density, ...categoryStyle } = requestedCategoryStyle ?? {};
4302
- const categoryColor = semantics.color === "category" ? context.colors.poi[category] ?? context.colors.poi.label : context.colors.poi.label;
4303
- const defaults4 = {
4612
+ const coupled = semantics.placement.coupleIconAndLabel;
4613
+ const categoryColor = semantics.color === "category" ? context.colors.poi[category] ?? context.colors.poi.label : context.colors.poi.label;
4614
+ const defaults4 = {
4615
+ icon: {
4616
+ optional: !coupled,
4617
+ padding: semantics.placement.iconPadding,
4618
+ size: semantics.minZoom < 17 ? zoom.linear([
4619
+ [semantics.minZoom, 0.85],
4620
+ [17, 1]
4621
+ ]) : 1
4622
+ },
4623
+ text: poiTextStyle(context, {
4624
+ color: categoryColor,
4625
+ field: labelField(language, context),
4626
+ // A coupled POI is its pictogram and its name: it is placed only when both fit.
4627
+ optional: !coupled,
4628
+ padding: semantics.placement.textPadding,
4629
+ size: semantics.minZoom < 17 ? zoom.linear([
4630
+ [semantics.minZoom, 10],
4631
+ [17, 12]
4632
+ ]) : 12
4633
+ })
4634
+ };
4635
+ let style = mergeTileflowDesign(defaults4, request?.styles?.[category] ?? {});
4636
+ const markerStyle = style.marker ? resolveMarkerStyle(style) : void 0;
4637
+ const hidden = style.visible === false;
4638
+ const showText = !hidden && semantics.labels && style.text.visible !== false;
4639
+ const showIcon = !hidden && semantics.icons && style.icon.visible !== false;
4640
+ style = {
4641
+ ...withoutVisibility(style),
4642
+ icon: withoutVisibility(style.icon),
4643
+ text: withoutVisibility(style.text)
4644
+ };
4645
+ if (showIcon && style.icon.image === void 0) {
4646
+ style = {
4647
+ ...style,
4304
4648
  icon: {
4305
- optional: !semantics.placement.coupleIconAndLabel,
4306
- padding: semantics.placement.iconPadding,
4307
- size: semantics.minZoom < 17 ? zoom.linear([
4308
- [semantics.minZoom, 0.85],
4309
- [17, 1]
4310
- ]) : 1
4311
- },
4312
- text: poiTextStyle(context, {
4313
- color: categoryColor,
4314
- field: labelField(language, context),
4315
- padding: semantics.placement.textPadding,
4316
- size: semantics.minZoom < 17 ? zoom.linear([
4317
- [semantics.minZoom, 10],
4318
- [17, 12]
4319
- ]) : 12
4320
- })
4649
+ ...style.icon,
4650
+ image: resolvePoiImage(category, context, schema.fields.poiIcon)
4651
+ }
4321
4652
  };
4322
- let style = mergeTileflowDesign(defaults4, categoryStyle);
4323
- const markerStyle = style.marker ? resolveMarkerStyle(style) : void 0;
4324
- const showText = semantics.labels && style.text.visible !== false;
4325
- const showIcon = semantics.icons && style.icon.visible !== false;
4326
- if (showIcon && style.icon.image === void 0) {
4327
- style = {
4328
- ...style,
4329
- icon: {
4330
- ...style.icon,
4331
- image: resolvePoiImage(category, context, schema.fields.poiIcon)
4332
- }
4333
- };
4334
- }
4335
- style = withPoiTextClearance(style, showText && showIcon);
4336
- const symbolStyle2 = withoutMarker2(style);
4337
- if (markerStyle && markerStyle.visible !== false) {
4338
- const base = createPoiLayer({
4339
- category,
4340
- categoryField: schema.fields.poiCategory,
4341
- density: categoryDensity,
4342
- filterRankField: schema.fields.poiFilterRank,
4343
- id: `tileflow-poi-${category}-marker`,
4344
- minZoom: semantics.minZoom,
4345
- sizeRankField: schema.fields.poiSizeRank,
4346
- source,
4347
- sourceLayer: schema.layers.poi
4348
- });
4349
- const markerLayer = applyCircleStyle(
4653
+ }
4654
+ style = withPoiTextClearance(style, showText && showIcon);
4655
+ const symbolStyle2 = withoutMarker2(style);
4656
+ const base = () => ({
4657
+ id: `tileflow-poi-${category}`,
4658
+ type: "symbol",
4659
+ source,
4660
+ "source-layer": schema.layers.poi,
4661
+ minzoom: semantics.minZoom,
4662
+ layout: { "symbol-sort-key": poiSortKey(schema.fields.poiFilterRank, schema.fields.poiSizeRank) }
4663
+ });
4664
+ const parts = [];
4665
+ selection2.register(category, { icon: showIcon, text: showText });
4666
+ if (!hidden && markerStyle && markerStyle.visible !== false) {
4667
+ const markerBase = base();
4668
+ parts.push({
4669
+ category,
4670
+ representation: "marker",
4671
+ layer: applyCircleStyle(
4350
4672
  {
4351
- ...base,
4673
+ ...markerBase,
4352
4674
  type: "circle",
4353
4675
  layout: {
4354
4676
  "circle-sort-key": poiSortKey(schema.fields.poiFilterRank, schema.fields.poiSizeRank)
4355
4677
  }
4356
4678
  },
4357
4679
  markerStyle
4358
- );
4359
- result.push(poiContribution(`${category}.marker`, index * 4, markerLayer));
4360
- }
4361
- if (semantics.placement.coupleIconAndLabel && showText && showIcon) {
4362
- let layer = createPoiLayer({
4363
- category,
4364
- categoryField: schema.fields.poiCategory,
4365
- density: categoryDensity,
4366
- filterRankField: schema.fields.poiFilterRank,
4367
- id: `tileflow-poi-${category}`,
4368
- minZoom: semantics.minZoom,
4369
- sizeRankField: schema.fields.poiSizeRank,
4370
- source,
4371
- sourceLayer: schema.layers.poi
4372
- });
4373
- layer = applySymbolStyle(layer, symbolStyle2);
4374
- result.push(poiContribution(category, index * 4 + 1, layer));
4375
- continue;
4376
- }
4377
- if (showIcon) {
4378
- const layer = applySymbolStyle(
4379
- createPoiLayer({
4380
- category,
4381
- categoryField: schema.fields.poiCategory,
4382
- density: categoryDensity,
4383
- filterRankField: schema.fields.poiFilterRank,
4384
- id: `tileflow-poi-${category}-icon`,
4385
- minZoom: semantics.minZoom,
4386
- sizeRankField: schema.fields.poiSizeRank,
4387
- source,
4388
- sourceLayer: schema.layers.poi
4389
- }),
4390
- { ...symbolStyle2, text: void 0 }
4391
- );
4392
- result.push(poiContribution(`${category}.icon`, index * 4 + 1, layer));
4393
- }
4394
- if (showText) {
4395
- const layer = applySymbolStyle(
4396
- createPoiLayer({
4397
- category,
4398
- categoryField: schema.fields.poiCategory,
4399
- density: categoryDensity,
4400
- filterRankField: schema.fields.poiFilterRank,
4401
- id: `tileflow-poi-${category}-label`,
4402
- minZoom: semantics.minZoom,
4403
- sizeRankField: schema.fields.poiSizeRank,
4404
- source,
4405
- sourceLayer: schema.layers.poi
4406
- }),
4407
- { ...symbolStyle2, icon: void 0 }
4408
- );
4409
- result.push(poiContribution(`${category}.label`, index * 4 + 2, layer));
4410
- }
4680
+ )
4681
+ });
4411
4682
  }
4412
- return result;
4683
+ const combined = coupled && (semantics.placement.order === "importance" ? semantics.icons && semantics.labels && (showIcon || showText) : showIcon && showText);
4684
+ if (combined) {
4685
+ const combinedStyle = {
4686
+ ...symbolStyle2,
4687
+ icon: showIcon ? symbolStyle2.icon : { image: "" },
4688
+ text: showText ? { ...symbolStyle2.text, field: selection2.textField(symbolStyle2.text.field) } : { field: "" }
4689
+ };
4690
+ parts.push({
4691
+ category,
4692
+ representation: "combined",
4693
+ layer: applySymbolStyle(base(), combinedStyle)
4694
+ });
4695
+ return parts;
4696
+ }
4697
+ if (showIcon) {
4698
+ parts.push({
4699
+ category,
4700
+ representation: "icon",
4701
+ layer: applySymbolStyle(base(), { ...symbolStyle2, text: void 0 })
4702
+ });
4703
+ }
4704
+ if (showText) {
4705
+ parts.push({
4706
+ category,
4707
+ representation: "label",
4708
+ layer: applySymbolStyle(base(), { ...symbolStyle2, icon: void 0 })
4709
+ });
4710
+ }
4711
+ return parts;
4712
+ }
4713
+ function createPoiSelection(semantics, context, language) {
4714
+ const { fields } = context.data.schema;
4715
+ const shown = /* @__PURE__ */ new Map();
4716
+ const iconOnlyTypes = [...new Set(semantics.placement.iconOnlyTypes)];
4717
+ const iconOnly = () => iconOnlyTypes.length ? ["match", ["get", fields.poiType], iconOnlyTypes, true, false] : ["literal", false];
4718
+ const inCategories = (categories) => categories.length === 1 ? ["==", ["get", fields.poiCategory], categories[0]] : ["match", ["get", fields.poiCategory], [...categories], true, false];
4719
+ const named = () => [
4720
+ "any",
4721
+ ...labelNameFields(language, context).map((field2) => ["has", field2])
4722
+ ];
4723
+ return {
4724
+ register(category, parts) {
4725
+ shown.set(category, parts);
4726
+ },
4727
+ textField(field2) {
4728
+ if (!iconOnlyTypes.length || field2 === void 0) return field2;
4729
+ return expression([
4730
+ "match",
4731
+ ["get", fields.poiType],
4732
+ iconOnlyTypes,
4733
+ "",
4734
+ fieldValue(field2)
4735
+ ]);
4736
+ },
4737
+ filter(categories, representation) {
4738
+ const filterRank = numericPoiField(fields.poiFilterRank, 6);
4739
+ const sizeRank = numericPoiField(fields.poiSizeRank, 17);
4740
+ const result = [
4741
+ "all",
4742
+ inCategories(categories),
4743
+ ["has", fields.poiFilterRank],
4744
+ [">=", filterRank, 0],
4745
+ ["<=", filterRank, poiRankThreshold(semantics.density)],
4746
+ ["has", fields.poiSizeRank],
4747
+ [">=", sizeRank, 0],
4748
+ ["<=", sizeRank, 16]
4749
+ ];
4750
+ if (!semantics.icons || !semantics.labels) return result;
4751
+ const textless = categories.filter((category) => !shown.get(category)?.text);
4752
+ const iconless = categories.filter((category) => !shown.get(category)?.icon);
4753
+ if (representation === "label") {
4754
+ if (iconless.length === categories.length || !iconOnlyTypes.length) return result;
4755
+ const keep = ["!", iconOnly()];
4756
+ return [...result, iconless.length ? ["any", inCategories(iconless), keep] : keep];
4757
+ }
4758
+ if (textless.length === categories.length) return result;
4759
+ return [
4760
+ ...result,
4761
+ [
4762
+ "any",
4763
+ named(),
4764
+ ...iconOnlyTypes.length ? [iconOnly()] : [],
4765
+ ...textless.length ? [inCategories(textless)] : []
4766
+ ]
4767
+ ];
4768
+ }
4769
+ };
4770
+ }
4771
+ function fieldValue(field2) {
4772
+ return typeof field2 === "object" && field2 !== null && "kind" in field2 && field2.kind === "expression" ? field2.value : field2;
4413
4773
  }
4414
4774
  function resolvePoiImage(category, context, iconField) {
4415
4775
  const role = `poi.${category}`;
@@ -4432,28 +4792,6 @@ function resolveMarkerStyle(style) {
4432
4792
  style.marker
4433
4793
  );
4434
4794
  }
4435
- function createPoiLayer(options) {
4436
- const filterRank = numericPoiField(options.filterRankField, 6);
4437
- const sizeRank = numericPoiField(options.sizeRankField, 17);
4438
- return {
4439
- id: options.id,
4440
- type: "symbol",
4441
- source: options.source,
4442
- "source-layer": options.sourceLayer,
4443
- minzoom: options.minZoom,
4444
- filter: [
4445
- "all",
4446
- ["==", ["get", options.categoryField], options.category],
4447
- ["has", options.filterRankField],
4448
- [">=", filterRank, 0],
4449
- ["<=", filterRank, poiRankThreshold(options.density)],
4450
- ["has", options.sizeRankField],
4451
- [">=", sizeRank, 0],
4452
- ["<=", sizeRank, 16]
4453
- ],
4454
- layout: { "symbol-sort-key": poiSortKey(options.filterRankField, options.sizeRankField) }
4455
- };
4456
- }
4457
4795
  function poiRankThreshold(density) {
4458
4796
  return ["+", ["step", ["zoom"], 0, 16, 1, 17, 2], density];
4459
4797
  }
@@ -4470,7 +4808,7 @@ function poiContribution(target, localOrder, layer) {
4470
4808
  localOrder: 1e3 + localOrder,
4471
4809
  owner: "poi",
4472
4810
  slot: "symbols",
4473
- target: `poi.${target}`
4811
+ target: target ? `poi.${target}` : "poi"
4474
4812
  };
4475
4813
  }
4476
4814
  function poiTextStyle(context, overrides) {
@@ -4480,8 +4818,7 @@ function poiTextStyle(context, overrides) {
4480
4818
  color: context.colors.poi.label,
4481
4819
  ...typographyTextStyle(context.typography.poi),
4482
4820
  haloColor: context.colors.poi.halo,
4483
- haloWidth: 1,
4484
- optional: true
4821
+ haloWidth: 1
4485
4822
  },
4486
4823
  overrides
4487
4824
  );
@@ -4499,22 +4836,76 @@ function withPoiTextClearance(style, besideIcon) {
4499
4836
  }
4500
4837
  };
4501
4838
  }
4502
- function withoutMarker2(style) {
4503
- const result = { ...style };
4504
- delete result.marker;
4839
+ function withoutMarker2(style) {
4840
+ const result = { ...style };
4841
+ delete result.marker;
4842
+ return result;
4843
+ }
4844
+ function withoutVisibility(value) {
4845
+ const { visible: _visible, ...rest } = value;
4846
+ return rest;
4847
+ }
4848
+
4849
+ // src/portable-identity-rules.ts
4850
+ var tileflowPortableIdMaximumLength = 64;
4851
+ var tileflowPortableIdPattern = /^[a-z][a-z0-9-]{0,63}$/u;
4852
+ function isTileflowPortableId(value) {
4853
+ return typeof value === "string" && value.length <= tileflowPortableIdMaximumLength && tileflowPortableIdPattern.test(value) && !["constructor", "prototype"].includes(value) && !/^(?:AUX|CON|NUL|PRN|COM[1-9]|LPT[1-9])$/i.test(value);
4854
+ }
4855
+ function isTileflowThemeName(value) {
4856
+ return isTileflowPortableId(value) && value !== "system";
4857
+ }
4858
+
4859
+ // src/time-of-day.ts
4860
+ var tileflowTimesOfDay = ["day", "dawn", "dusk", "night"];
4861
+ function isTileflowTimeOfDay(value) {
4862
+ return typeof value === "string" && tileflowTimesOfDay.includes(value);
4863
+ }
4864
+ function validateTileflowTimeOfDayVariants(variants, themes) {
4865
+ const issues = [];
4866
+ const owners = /* @__PURE__ */ new Map();
4867
+ const families = Object.entries(variants);
4868
+ for (const [family, moments] of families) {
4869
+ if (!isTileflowThemeName(family) || !Object.hasOwn(themes, family)) {
4870
+ issues.push({ path: [family], message: "Time-of-day family must name an existing theme" });
4871
+ }
4872
+ if (!Object.values(moments).includes(family)) {
4873
+ issues.push({ path: [family], message: "A family must include its own theme in its presets" });
4874
+ }
4875
+ for (const [moment, name] of Object.entries(moments)) {
4876
+ if (name === void 0) continue;
4877
+ if (!isTileflowTimeOfDay(moment) || !isTileflowThemeName(name) || !Object.hasOwn(themes, name)) {
4878
+ issues.push({
4879
+ path: [family, moment],
4880
+ message: "Expected a supported moment and an existing theme"
4881
+ });
4882
+ }
4883
+ const owner = owners.get(name);
4884
+ if (owner !== void 0 && owner !== family) {
4885
+ issues.push({
4886
+ path: [family, moment],
4887
+ message: `Theme "${name}" already belongs to family "${owner}"`
4888
+ });
4889
+ }
4890
+ owners.set(name, family);
4891
+ }
4892
+ }
4893
+ return issues;
4894
+ }
4895
+ function resolveTileflowTimeOfDayTheme(variants, theme, timeOfDay) {
4896
+ if (timeOfDay === void 0 || variants === void 0) return theme;
4897
+ if (!isTileflowTimeOfDay(timeOfDay)) throw new TypeError("Expected day, dawn, dusk, or night");
4898
+ const family = Object.hasOwn(variants, theme) ? variants[theme] : Object.values(variants).find((moments) => Object.values(moments).includes(theme));
4899
+ if (!family) return theme;
4900
+ const result = family[timeOfDay];
4901
+ if (result === void 0) {
4902
+ throw new Error(
4903
+ `Theme "${theme}" does not offer timeOfDay="${timeOfDay}". Available: ${Object.keys(family).join(", ")}.`
4904
+ );
4905
+ }
4505
4906
  return result;
4506
4907
  }
4507
4908
 
4508
- // src/portable-identity-rules.ts
4509
- var tileflowPortableIdMaximumLength = 64;
4510
- var tileflowPortableIdPattern = /^[a-z][a-z0-9-]{0,63}$/u;
4511
- function isTileflowPortableId(value) {
4512
- return typeof value === "string" && value.length <= tileflowPortableIdMaximumLength && tileflowPortableIdPattern.test(value) && !["constructor", "prototype"].includes(value) && !/^(?:AUX|CON|NUL|PRN|COM[1-9]|LPT[1-9])$/i.test(value);
4513
- }
4514
- function isTileflowThemeName(value) {
4515
- return isTileflowPortableId(value) && value !== "system";
4516
- }
4517
-
4518
4909
  // src/sun.ts
4519
4910
  var tileflowSunMetadataKey = "tileflow:sun";
4520
4911
  var tileflowDefaultSunPreset = Object.freeze({
@@ -4737,7 +5128,7 @@ function resolveThemeValues(value, theme, path = "value") {
4737
5128
  if (errors.length > 0) throw new TileflowThemeAuditError(errors);
4738
5129
  return resolved;
4739
5130
  }
4740
- function resolveThemeSelection(map, requested) {
5131
+ function resolveThemeSelection(map, requested, timeOfDay) {
4741
5132
  const name = requested ?? map.defaultTheme;
4742
5133
  if (name === "system") {
4743
5134
  throw new Error(
@@ -4754,7 +5145,8 @@ function resolveThemeSelection(map, requested) {
4754
5145
  const available = Object.keys(map.themes).sort().join(", ") || "<none>";
4755
5146
  throw new Error(`Unknown Tileflow theme "${name}"; available themes: ${available}.`);
4756
5147
  }
4757
- return { name, theme };
5148
+ const resolvedName = resolveTileflowTimeOfDayTheme(map.timeOfDay, name, timeOfDay);
5149
+ return { name: resolvedName, theme: map.themes[resolvedName] };
4758
5150
  }
4759
5151
  function auditTileflowThemeValues(value, path = "value", context = inferAuditContext(path)) {
4760
5152
  const diagnostics = [];
@@ -4767,7 +5159,7 @@ function auditTileflowThemeValues(value, path = "value", context = inferAuditCon
4767
5159
  return diagnostics.sort(compareAuditDiagnostics);
4768
5160
  }
4769
5161
  function auditTileflowMapThemeValues(map) {
4770
- const modules = isRecord4(map.modules) ? map.modules : void 0;
5162
+ const modules = isRecord3(map.modules) ? map.modules : void 0;
4771
5163
  const moduleDiagnostics = modules ? Object.keys(modules).sort(compareCodeUnits2).flatMap(
4772
5164
  (owner) => auditTileflowThemeValues(modules[owner], `modules.${owner}`, {
4773
5165
  owner,
@@ -4870,7 +5262,7 @@ function isThemePrimitiveForCategory(value, category) {
4870
5262
  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;
4871
5263
  }
4872
5264
  function isThemeValueCandidate(value) {
4873
- return isRecord4(value) && (value.kind === "theme-token" || value.kind === "theme-fixed" || value.kind === "theme-color");
5265
+ return isRecord3(value) && (value.kind === "theme-token" || value.kind === "theme-fixed" || value.kind === "theme-color");
4874
5266
  }
4875
5267
  function visitAuditableValue(value, path, category, state, intentRequired = false) {
4876
5268
  if (isThemeStructuralPath(path)) category = void 0;
@@ -4886,7 +5278,7 @@ function visitAuditableValue(value, path, category, state, intentRequired = fals
4886
5278
  visitAuditableArray(value, path, category, state, false, intentRequired);
4887
5279
  return;
4888
5280
  }
4889
- if (!isRecord4(value)) return;
5281
+ if (!isRecord3(value)) return;
4890
5282
  if (value.kind === "expression" && Array.isArray(value.value)) {
4891
5283
  visitAuditableArray(value.value, `${path}.value`, category, state, true, intentRequired);
4892
5284
  return;
@@ -5201,7 +5593,7 @@ var ThemeValueResolver = class {
5201
5593
  if (Array.isArray(value)) {
5202
5594
  return value.map((entry, index) => this.resolveTree(entry, `${path}[${index}]`, category));
5203
5595
  }
5204
- if (!isRecord4(value)) return value;
5596
+ if (!isRecord3(value)) return value;
5205
5597
  if (value.kind === "expression" && Array.isArray(value.value)) {
5206
5598
  return {
5207
5599
  ...value,
@@ -5513,7 +5905,7 @@ function assertThemeLighting(value, path) {
5513
5905
  }
5514
5906
  }
5515
5907
  function assertThemeCategoryValue(value, expectedCategory, path) {
5516
- if (!isRecord4(value)) {
5908
+ if (!isRecord3(value)) {
5517
5909
  assertTokenPrimitive(expectedCategory, value, path);
5518
5910
  return;
5519
5911
  }
@@ -5865,11 +6257,11 @@ function cloneJson14(value) {
5865
6257
  if (serialized === void 0) throw new Error("Tileflow themes must be JSON-serializable.");
5866
6258
  return JSON.parse(serialized);
5867
6259
  }
5868
- function isRecord4(value) {
6260
+ function isRecord3(value) {
5869
6261
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
5870
6262
  }
5871
6263
  function isPlainRecord(value) {
5872
- if (!isRecord4(value)) return false;
6264
+ if (!isRecord3(value)) return false;
5873
6265
  const prototype = Object.getPrototypeOf(value);
5874
6266
  return prototype === Object.prototype || prototype === null;
5875
6267
  }
@@ -6823,9 +7215,10 @@ function compileRoundabouts(request, context, contributions) {
6823
7215
  contributions.push({
6824
7216
  kind: "layer",
6825
7217
  layer: applyCircleStyle({ ...base, id: "tileflow-road-circular-casing" }, config.casing),
6826
- localOrder: 10,
7218
+ localOrder: 900,
6827
7219
  owner: "roads",
6828
- slot: "transport-symbols",
7220
+ // Approach fills must cover the circular casing where the roads meet.
7221
+ slot: "transport-surface-casing",
6829
7222
  target: "roads.roundabouts.casing"
6830
7223
  });
6831
7224
  }
@@ -6833,9 +7226,9 @@ function compileRoundabouts(request, context, contributions) {
6833
7226
  contributions.push({
6834
7227
  kind: "layer",
6835
7228
  layer: applyCircleStyle({ ...base, id: "tileflow-road-circular-fill" }, config.fill),
6836
- localOrder: 20,
7229
+ localOrder: 900,
6837
7230
  owner: "roads",
6838
- slot: "transport-symbols",
7231
+ slot: "transport-surface-fill",
6839
7232
  target: "roads.roundabouts.fill"
6840
7233
  });
6841
7234
  }
@@ -6900,7 +7293,7 @@ var circularRoadBorderTotalWidth = 1;
6900
7293
  function circularRoadInnerRadius(fields, casing) {
6901
7294
  const values = circularRoadValues(fields);
6902
7295
  const stops = [];
6903
- for (let level = 15; level <= 22; level += 1) {
7296
+ for (let level = 15; level <= 24; level += 1) {
6904
7297
  const centerlineRadius = level === 15 ? values.radiusAtZoom15 : ["*", values.radiusAtZoom15, 2 ** (level - 15)];
6905
7298
  const physicalInnerRadius = [
6906
7299
  "*",
@@ -6917,13 +7310,13 @@ function circularRoadInnerRadius(fields, casing) {
6917
7310
  casing ? ["-", innerRadius, circularRoadBorderTotalWidth / 2] : innerRadius
6918
7311
  ]);
6919
7312
  }
6920
- return ["interpolate", ["linear"], ["zoom"], ...stops];
7313
+ return ["interpolate", ["exponential", 2], ["zoom"], ...stops];
6921
7314
  }
6922
7315
  function circularRoadStrokeWidth(fields, casing) {
6923
7316
  const values = circularRoadValues(fields);
6924
7317
  const baseWidth = circularRoadBaseWidth(fields.class);
6925
7318
  const stops = [];
6926
- for (let level = 15; level <= 22; level += 1) {
7319
+ for (let level = 15; level <= 24; level += 1) {
6927
7320
  const centerlineRadius = level === 15 ? values.radiusAtZoom15 : ["*", values.radiusAtZoom15, 2 ** (level - 15)];
6928
7321
  const physicalWidth = [
6929
7322
  "*",
@@ -6938,7 +7331,7 @@ function circularRoadStrokeWidth(fields, casing) {
6938
7331
  casing ? ["+", fallbackWidth, circularRoadBorderTotalWidth] : fallbackWidth
6939
7332
  ]);
6940
7333
  }
6941
- return ["interpolate", ["linear"], ["zoom"], ...stops];
7334
+ return ["interpolate", ["exponential", 2], ["zoom"], ...stops];
6942
7335
  }
6943
7336
  function circularRoadBaseWidth(classField) {
6944
7337
  return [
@@ -7102,6 +7495,10 @@ function applyRoadTreatments(base, treatments, structure, phase, fallbackColor)
7102
7495
  return value === void 0 ? [] : [{ condition, resolve: () => expressionOutput(value) }];
7103
7496
  });
7104
7497
  if (branches.length > 0) {
7498
+ if (property === "dash" && isSolidRoadDash(result[property] ?? treatmentPaintDefaults.dash) && branches.every(({ resolve }) => isSolidRoadDash(resolve()))) {
7499
+ delete result[property];
7500
+ continue;
7501
+ }
7105
7502
  result[property] = conditionalStyleValue(
7106
7503
  result[property],
7107
7504
  branches,
@@ -7118,9 +7515,21 @@ function applyRoadTreatments(base, treatments, structure, phase, fallbackColor)
7118
7515
  widthScales,
7119
7516
  treatmentPaintDefaults.width
7120
7517
  );
7518
+ if (result.gapWidth !== void 0 && result.gapWidth !== 0) {
7519
+ result.gapWidth = conditionalWidthScaleValue(
7520
+ result.gapWidth,
7521
+ widthScales,
7522
+ treatmentPaintDefaults.gapWidth
7523
+ );
7524
+ }
7121
7525
  }
7122
7526
  return result;
7123
7527
  }
7528
+ function isSolidRoadDash(value) {
7529
+ const output = expressionOutput(value);
7530
+ const dash = Array.isArray(output) && output[0] === "literal" ? output[1] : output;
7531
+ return Array.isArray(dash) && dash.length === 2 && dash[0] === 1 && dash[1] === 0;
7532
+ }
7124
7533
  function roadTreatmentPhaseConditions(treatments, structure, phase) {
7125
7534
  return treatments.flatMap(
7126
7535
  ({ condition, style }) => style[structure]?.[phase] === void 0 ? [] : [condition]
@@ -7205,10 +7614,10 @@ function isExpressionOutput(value) {
7205
7614
  }
7206
7615
  function rewriteZoomOutputs(value, rewrite) {
7207
7616
  if (!Array.isArray(value)) return rewrite(value);
7208
- if (value[0] === "interpolate" && isZoomInput3(value[2])) {
7617
+ if (value[0] === "interpolate" && isZoomInput4(value[2])) {
7209
7618
  return value.map((entry, index) => index >= 4 && index % 2 === 0 ? rewrite(entry) : entry);
7210
7619
  }
7211
- if (value[0] === "step" && isZoomInput3(value[1])) {
7620
+ if (value[0] === "step" && isZoomInput4(value[1])) {
7212
7621
  return value.map(
7213
7622
  (entry, index) => index === 2 || index >= 4 && index % 2 === 0 ? rewrite(entry) : entry
7214
7623
  );
@@ -7218,7 +7627,7 @@ function rewriteZoomOutputs(value, rewrite) {
7218
7627
  function expressionOutput(value) {
7219
7628
  return Array.isArray(value) && typeof value[0] !== "string" ? ["literal", value] : value;
7220
7629
  }
7221
- function isZoomInput3(value) {
7630
+ function isZoomInput4(value) {
7222
7631
  return Array.isArray(value) && value.length === 1 && value[0] === "zoom";
7223
7632
  }
7224
7633
  function restrictedAccessFilter(field2) {
@@ -7597,7 +8006,7 @@ var waterwayClasses = {
7597
8006
  river: ["river"],
7598
8007
  stream: ["stream"]
7599
8008
  };
7600
- function compileWater(request, context) {
8009
+ function compileWater(request, context, language = "auto") {
7601
8010
  const config = mergeTileflowDesign(
7602
8011
  {
7603
8012
  type: "water",
@@ -7749,8 +8158,11 @@ function compileWater(request, context) {
7749
8158
  appendBathymetryRelief(contributions, bathymetryProduct.relief, context);
7750
8159
  }
7751
8160
  const productContours = bathymetryProduct?.contours;
8161
+ const nativeContours = sidecarBathymetry && bathymetryProduct?.vectorLayers?.contours === true;
8162
+ const contourLayer = nativeContours ? tileflowBathymetryV1Schema.layers.contours : bathymetryLayer;
8163
+ const contourDepthField = nativeContours ? tileflowBathymetryV1Schema.fields.depth : bathymetryDepthField;
7752
8164
  const contourRequest = productContours === false ? void 0 : productContours === void 0 ? request?.bathymetryContours : mergeTileflowDesign(productContours, request?.bathymetryContours);
7753
- if (contourRequest !== void 0 && bathymetryLayer && bathymetryDepthField) {
8165
+ if (contourRequest !== void 0 && contourLayer && contourDepthField) {
7754
8166
  const bathymetryContours = mergeTileflowDesign(
7755
8167
  {
7756
8168
  color: context.colors.hydro.label,
@@ -7773,12 +8185,12 @@ function compileWater(request, context) {
7773
8185
  id: "tileflow-bathymetry-contours",
7774
8186
  type: "line",
7775
8187
  source: bathymetrySource,
7776
- "source-layer": bathymetryLayer,
8188
+ "source-layer": contourLayer,
7777
8189
  filter: [
7778
8190
  "all",
7779
- ["==", ["geometry-type"], "Polygon"],
7780
- ["has", bathymetryDepthField],
7781
- ["<", ["to-number", ["get", bathymetryDepthField], 0], 0]
8191
+ ["==", ["geometry-type"], nativeContours ? "LineString" : "Polygon"],
8192
+ ["has", contourDepthField],
8193
+ ["<", ["to-number", ["get", contourDepthField], 0], 0]
7782
8194
  ]
7783
8195
  },
7784
8196
  bathymetryContours
@@ -7792,19 +8204,19 @@ function compileWater(request, context) {
7792
8204
  }
7793
8205
  const productLabels = bathymetryProduct?.labels;
7794
8206
  const labelRequest = productLabels === false ? void 0 : productLabels === void 0 ? request?.bathymetryLabels : mergeTileflowDesign(productLabels, request?.bathymetryLabels);
7795
- if (labelRequest !== void 0 && bathymetryLayer && bathymetryDepthField) {
8207
+ if (labelRequest !== void 0 && contourLayer && contourDepthField) {
7796
8208
  const bathymetryLabels = mergeTileflowDesign(
7797
8209
  {
7798
8210
  maxZoom: 10,
7799
8211
  minZoom: 3,
7800
- placement: "point",
8212
+ placement: nativeContours ? "line" : "point",
7801
8213
  priority: 5,
7802
8214
  text: {
7803
8215
  allowOverlap: false,
7804
8216
  color: context.colors.hydro.label,
7805
8217
  field: expression([
7806
8218
  "to-string",
7807
- ["abs", ["to-number", ["get", bathymetryDepthField], 0]]
8219
+ ["abs", ["to-number", ["get", contourDepthField], 0]]
7808
8220
  ]),
7809
8221
  ...typographyTextStyle(context.typography.water),
7810
8222
  haloColor: context.colors.hydro.water,
@@ -7825,8 +8237,13 @@ function compileWater(request, context) {
7825
8237
  id: "tileflow-bathymetry-labels",
7826
8238
  type: "symbol",
7827
8239
  source: bathymetrySource,
7828
- "source-layer": bathymetryLayer,
7829
- filter: ["all", ["==", ["geometry-type"], "Polygon"], ["has", bathymetryDepthField]]
8240
+ "source-layer": contourLayer,
8241
+ filter: [
8242
+ "all",
8243
+ ["==", ["geometry-type"], nativeContours ? "LineString" : "Polygon"],
8244
+ ["has", contourDepthField],
8245
+ ...nativeContours ? [["<", ["to-number", ["get", contourDepthField], 0], 0]] : []
8246
+ ]
7830
8247
  },
7831
8248
  bathymetryLabels
7832
8249
  ),
@@ -7837,6 +8254,37 @@ function compileWater(request, context) {
7837
8254
  });
7838
8255
  }
7839
8256
  }
8257
+ if (sidecarBathymetry) {
8258
+ const { layers: marineLayers } = tileflowBathymetryV1Schema;
8259
+ const landforms2 = bathymetryProduct?.vectorLayers?.landforms;
8260
+ const waterNames = bathymetryProduct?.vectorLayers?.waterNames;
8261
+ if (landforms2) {
8262
+ appendBathymetryNames(contributions, {
8263
+ availability: landforms2,
8264
+ context,
8265
+ language,
8266
+ localOrder: -300,
8267
+ source: sidecarBathymetry.sourceId,
8268
+ sourceLayer: marineLayers.landforms,
8269
+ style: config.bathymetryLandforms,
8270
+ target: "water.bathymetryLandforms",
8271
+ id: "tileflow-bathymetry-landforms"
8272
+ });
8273
+ }
8274
+ if (waterNames) {
8275
+ appendBathymetryNames(contributions, {
8276
+ availability: waterNames,
8277
+ context,
8278
+ language,
8279
+ localOrder: -150,
8280
+ source: sidecarBathymetry.sourceId,
8281
+ sourceLayer: marineLayers.waterNames,
8282
+ style: config.bathymetryWaterNames,
8283
+ target: "water.bathymetryWaterNames",
8284
+ id: "tileflow-bathymetry-water-names"
8285
+ });
8286
+ }
8287
+ }
7840
8288
  let localOrder = 10;
7841
8289
  for (const [name, classes] of Object.entries(waterwayClasses)) {
7842
8290
  const style = config.waterways?.[name];
@@ -7900,6 +8348,99 @@ function compileWater(request, context) {
7900
8348
  }
7901
8349
  return contributions;
7902
8350
  }
8351
+ function appendBathymetryNames(contributions, options) {
8352
+ const { context, language } = options;
8353
+ const { fields } = tileflowBathymetryV1Schema;
8354
+ const localName = ["to-string", ["coalesce", ["get", fields.name], ""]];
8355
+ const name = language === "auto" || language === "local" ? localName : [
8356
+ "case",
8357
+ ["!=", ["to-string", ["coalesce", ["get", `name:${language}`], ""]], ""],
8358
+ ["to-string", ["get", `name:${language}`]],
8359
+ localName
8360
+ ];
8361
+ const style = mergeTileflowDesign(
8362
+ {
8363
+ minZoom: 0,
8364
+ placement: "point",
8365
+ priority: expression(["to-number", ["get", fields.sortKey], 0]),
8366
+ priorityOrder: "lower-first",
8367
+ text: {
8368
+ ...typographyTextStyle(context.typography.water),
8369
+ allowOverlap: false,
8370
+ ignorePlacement: false,
8371
+ color: context.colors.hydro.label,
8372
+ field: expression(name),
8373
+ haloColor: context.colors.hydro.water,
8374
+ haloWidth: 1,
8375
+ optional: false,
8376
+ padding: 12,
8377
+ size: 11
8378
+ }
8379
+ },
8380
+ options.style
8381
+ );
8382
+ if (style.visible === false || style.text?.visible === false) return;
8383
+ const layer = applySymbolStyle(
8384
+ {
8385
+ id: options.id,
8386
+ type: "symbol",
8387
+ source: options.source,
8388
+ "source-layer": options.sourceLayer,
8389
+ filter: [
8390
+ "all",
8391
+ ["match", ["geometry-type"], ["Point", "LineString", "Polygon"], true, false],
8392
+ ["!=", name, ""]
8393
+ ]
8394
+ },
8395
+ style
8396
+ );
8397
+ const sourceMinimum = ["to-number", ["coalesce", ["get", fields.minZoom], 0], 0];
8398
+ const sourceMaximum = ["to-number", ["coalesce", ["get", fields.maxZoom], 24], 24];
8399
+ const baseFilter = layer.filter;
8400
+ const append = (result, target, order = 0) => {
8401
+ contributions.push({
8402
+ kind: "layer",
8403
+ layer: result,
8404
+ localOrder: options.localOrder + order,
8405
+ owner: "water",
8406
+ slot: "symbols",
8407
+ target
8408
+ });
8409
+ };
8410
+ if (options.availability === true) {
8411
+ append(
8412
+ {
8413
+ ...layer,
8414
+ filter: [...baseFilter, [">=", ["zoom"], sourceMinimum], ["<", ["zoom"], sourceMaximum]]
8415
+ },
8416
+ options.target
8417
+ );
8418
+ return;
8419
+ }
8420
+ const ranges = [...options.availability.zoomRanges].sort(
8421
+ (left, right) => left.minZoom - right.minZoom || left.maxZoom - right.maxZoom
8422
+ );
8423
+ for (const [index, range2] of ranges.entries()) {
8424
+ const minzoom = Math.max(Number(layer.minzoom ?? 0), range2.minZoom);
8425
+ const maxzoom = Math.min(Number(layer.maxzoom ?? 24), range2.maxZoom);
8426
+ if (minzoom >= maxzoom) continue;
8427
+ append(
8428
+ {
8429
+ ...layer,
8430
+ id: `${options.id}-cohort-${index}`,
8431
+ minzoom,
8432
+ maxzoom,
8433
+ filter: [
8434
+ ...baseFilter,
8435
+ ["==", sourceMinimum, range2.minZoom],
8436
+ ["==", sourceMaximum, range2.maxZoom]
8437
+ ]
8438
+ },
8439
+ `${options.target}.cohort${index}`,
8440
+ index
8441
+ );
8442
+ }
8443
+ }
7903
8444
  function appendBathymetryRelief(contributions, relief, context) {
7904
8445
  const { sourceId, style } = relief;
7905
8446
  const opacity = resolvedNumber(style.opacity, 0.22, "bathymetry relief opacity");
@@ -8108,12 +8649,6 @@ var tileflowSemanticDomainRegistry = Object.freeze([
8108
8649
  dependencies: {},
8109
8650
  name: "land"
8110
8651
  }),
8111
- defineDomain({
8112
- compile: ({ context, module }) => compileWater(module, context),
8113
- defaultModule: water(),
8114
- dependencies: {},
8115
- name: "water"
8116
- }),
8117
8652
  defineDomain({
8118
8653
  compile: ({ context, module }) => compileNautical(module, context),
8119
8654
  defaultModule: nautical(),
@@ -8202,6 +8737,12 @@ var tileflowSemanticDomainRegistry = Object.freeze([
8202
8737
  provides: ["language"],
8203
8738
  resolveServices: ({ module }) => ({ language: resolveLabels(module).language })
8204
8739
  }),
8740
+ defineDomain({
8741
+ compile: ({ context, dependencies, module }) => compileWater(module, context, dependencies.service("language")),
8742
+ defaultModule: water(),
8743
+ dependencies: { services: ["language"] },
8744
+ name: "water"
8745
+ }),
8205
8746
  defineDomain({
8206
8747
  compile: ({ context, dependencies, module }) => compileLandforms(module, dependencies.service("language"), context),
8207
8748
  defaultModule: landforms(),
@@ -8320,7 +8861,7 @@ function createTileflowCompilerProvenance(owner, slot, target) {
8320
8861
  return [{ operations: [], owner, slot, target }];
8321
8862
  }
8322
8863
  function readTileflowCompilerProvenance(layer) {
8323
- const metadata = asRecord2(layer.metadata);
8864
+ const metadata = asRecord3(layer.metadata);
8324
8865
  const raw = metadata[tileflowCompilerProvenanceMetadataKey];
8325
8866
  if (Array.isArray(raw)) {
8326
8867
  const parsed = raw.flatMap((value) => {
@@ -8348,7 +8889,7 @@ function createTileflowStyleInspection(map, theme, layers) {
8348
8889
  };
8349
8890
  }
8350
8891
  function parseContribution(value) {
8351
- const input = asRecord2(value);
8892
+ const input = asRecord3(value);
8352
8893
  const owner = requireLayerDomain(input.owner);
8353
8894
  const slot = requireLayerSlot(input.slot);
8354
8895
  const target = input.target;
@@ -8362,7 +8903,7 @@ function parseContribution(value) {
8362
8903
  return { operations, owner, slot, target };
8363
8904
  }
8364
8905
  function parseRenderOperation(value) {
8365
- const input = asRecord2(value);
8906
+ const input = asRecord3(value);
8366
8907
  const owner = requireLayerDomain(input.owner);
8367
8908
  const target = input.target;
8368
8909
  if (input.kind !== "pass" && input.kind !== "refinement" || !owner || typeof target !== "string" || !tileflowSemanticTargetPattern.test(target)) {
@@ -8376,7 +8917,7 @@ function requireLayerDomain(value) {
8376
8917
  function requireLayerSlot(value) {
8377
8918
  return typeof value === "string" && tileflowLayerSlots.includes(value) ? value : void 0;
8378
8919
  }
8379
- function asRecord2(value) {
8920
+ function asRecord3(value) {
8380
8921
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
8381
8922
  }
8382
8923
 
@@ -8423,6 +8964,7 @@ var classicManifestSchemaOperations = {
8423
8964
  return z3.enum(values);
8424
8965
  },
8425
8966
  literal: (value) => z3.literal(value),
8967
+ union: (left, right) => z3.union([classic(left), classic(right)]),
8426
8968
  tuplePair(left, right) {
8427
8969
  return z3.tuple([classic(left), classic(right)]);
8428
8970
  },
@@ -8745,7 +9287,7 @@ function not(value) {
8745
9287
  function has(reference) {
8746
9288
  return make(["has", reference]);
8747
9289
  }
8748
- function match(input, branches, fallback) {
9290
+ function match2(input, branches, fallback) {
8749
9291
  validateBranchCount(branches.length, "expr.match");
8750
9292
  validateMatchLabels(branches);
8751
9293
  return make([
@@ -8843,7 +9385,7 @@ function limitError(message) {
8843
9385
  return new Error(site ? `${message} Called at ${site}.` : message);
8844
9386
  }
8845
9387
  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 }));
9388
+ const locations = (stack ?? "").split("\n").slice(1).map((line) => /\(?([^()\s]+?):(\d+):(\d+)\)?$/.exec(line.trim())).filter((match3) => match3 !== null).map(([, file, line, column]) => ({ file: file.replace(/^file:\/\//, ""), line, column }));
8847
9389
  const own = locations[0]?.file;
8848
9390
  const site = locations.find(({ file }) => file !== own && !file.startsWith("node:"));
8849
9391
  return site ? `${site.file}:${site.line}:${site.column}` : void 0;
@@ -9228,7 +9770,7 @@ var expr = Object.freeze({
9228
9770
  literal,
9229
9771
  lt: (left, right) => compare("<", left, right),
9230
9772
  lte: (left, right) => compare("<=", left, right),
9231
- match,
9773
+ match: match2,
9232
9774
  max: maximum,
9233
9775
  min: minimum,
9234
9776
  modulo,
@@ -9252,6 +9794,12 @@ var tileflowSemanticFieldNames = Object.freeze(
9252
9794
  var tileflowSemanticLayerNames = Object.freeze(
9253
9795
  Object.keys(completeSemanticDataContract.layers).sort(compareCodeUnits5)
9254
9796
  );
9797
+ var auxiliaryFieldBindings = Object.freeze({
9798
+ bathymetryContourDepth: tileflowBathymetryV1Schema.fields.depth
9799
+ });
9800
+ var tileflowSemanticExpressionFieldNames = Object.freeze(
9801
+ [...tileflowSemanticFieldNames, ...Object.keys(auxiliaryFieldBindings)].sort(compareCodeUnits5)
9802
+ );
9255
9803
  function field(name) {
9256
9804
  return Object.freeze({ kind: "tileflow-data-field", name });
9257
9805
  }
@@ -9283,6 +9831,9 @@ function bindSemanticReferences(value, data) {
9283
9831
  }
9284
9832
  function bindSemanticValue(value, schema, sourceId) {
9285
9833
  if (isDataFieldReference2(value)) {
9834
+ if (Object.hasOwn(auxiliaryFieldBindings, value.name)) {
9835
+ return auxiliaryFieldBindings[value.name];
9836
+ }
9286
9837
  const binding = schema.fields[value.name];
9287
9838
  if (!binding) throw new Error(`Tileflow data schema does not provide field ${value.name}.`);
9288
9839
  return binding;
@@ -9294,21 +9845,21 @@ function bindSemanticValue(value, schema, sourceId) {
9294
9845
  }
9295
9846
  if (isDataSourceReference(value)) return sourceId;
9296
9847
  if (Array.isArray(value)) return value.map((item) => bindSemanticValue(item, schema, sourceId));
9297
- if (!isRecord5(value)) return value;
9848
+ if (!isRecord4(value)) return value;
9298
9849
  return Object.fromEntries(
9299
9850
  Object.entries(value).map(([key, item]) => [key, bindSemanticValue(item, schema, sourceId)])
9300
9851
  );
9301
9852
  }
9302
9853
  function isDataFieldReference2(value) {
9303
- return isRecord5(value) && value.kind === "tileflow-data-field" && typeof value.name === "string";
9854
+ return isRecord4(value) && value.kind === "tileflow-data-field" && typeof value.name === "string";
9304
9855
  }
9305
9856
  function isDataLayerReference(value) {
9306
- return isRecord5(value) && value.kind === "tileflow-data-layer" && typeof value.name === "string";
9857
+ return isRecord4(value) && value.kind === "tileflow-data-layer" && typeof value.name === "string";
9307
9858
  }
9308
9859
  function isDataSourceReference(value) {
9309
- return isRecord5(value) && value.kind === "tileflow-data-source" && value.name === "primary";
9860
+ return isRecord4(value) && value.kind === "tileflow-data-source" && value.name === "primary";
9310
9861
  }
9311
- function isRecord5(value) {
9862
+ function isRecord4(value) {
9312
9863
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
9313
9864
  }
9314
9865
  function compareCodeUnits5(left, right) {
@@ -9412,8 +9963,8 @@ function createTileflowLayerTemplateIR(layer, semanticKey) {
9412
9963
  renderer: type,
9413
9964
  ...filter === void 0 ? {} : { selector: cloneJson20(filter) },
9414
9965
  style: Object.freeze({
9415
- ...isRecord6(layout) ? { placement: Object.freeze(cloneJson20(layout)) } : {},
9416
- ...isRecord6(paint) ? { appearance: Object.freeze(cloneJson20(paint)) } : {}
9966
+ ...isRecord5(layout) ? { placement: Object.freeze(cloneJson20(layout)) } : {},
9967
+ ...isRecord5(paint) ? { appearance: Object.freeze(cloneJson20(paint)) } : {}
9417
9968
  })
9418
9969
  });
9419
9970
  }
@@ -9487,72 +10038,77 @@ function emitLayer(family, resolvePhysicalId) {
9487
10038
  function physicalLayerIdForSemanticKey(semanticKey) {
9488
10039
  const exact = exactPhysicalLayerIds[semanticKey];
9489
10040
  if (exact) return exact;
9490
- let match2 = /^([A-Za-z0-9_-]+)\.render\.([A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)$/u.exec(
10041
+ let match3 = /^([A-Za-z0-9_-]+)\.render\.([A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)$/u.exec(
9491
10042
  semanticKey
9492
10043
  );
9493
- if (match2) return `tileflow-${match2[1]}-render-${match2[2].replaceAll(".", "-")}`;
9494
- match2 = /^roads\.classes\.([^.]+)\.(tunnel|surface|bridge)\.(shadow|casing|fill|hatch)$/u.exec(
10044
+ if (match3) return `tileflow-${match3[1]}-render-${match3[2].replaceAll(".", "-")}`;
10045
+ match3 = /^roads\.classes\.([^.]+)\.(tunnel|surface|bridge)\.(shadow|casing|fill|hatch)$/u.exec(
9495
10046
  semanticKey
9496
10047
  );
9497
- if (match2) return `tileflow-road-${match2[2]}-${match2[1]}-${match2[3]}`;
9498
- match2 = /^roads\.cohorts\.(tunnel|surface|bridge)\.([^.]+)\.(shadow|casing|fill)$/u.exec(
10048
+ if (match3) return `tileflow-road-${match3[2]}-${match3[1]}-${match3[3]}`;
10049
+ match3 = /^roads\.cohorts\.(tunnel|surface|bridge)\.([^.]+)\.(shadow|casing|fill)$/u.exec(
9499
10050
  semanticKey
9500
10051
  );
9501
- if (match2) return `tileflow-road-${match2[1]}-highzoom-${match2[2]}-${match2[3]}`;
9502
- match2 = /^roads\.cohorts\.(tunnel|surface|bridge)\.hatch$/u.exec(semanticKey);
9503
- if (match2) return `tileflow-road-${match2[1]}-hatch`;
9504
- match2 = /^roads\.areas\.(road|pedestrian|pier)\.(fill|outline)$/u.exec(semanticKey);
9505
- if (match2) {
9506
- const base = match2[1] === "road" ? "tileflow-road-area" : `tileflow-road-${match2[1]}-area`;
9507
- return match2[2] === "fill" ? base : `${base}-outline`;
9508
- }
9509
- match2 = /^land\.(landcover|landuse)\.([^.]+)\.(fill|outline)$/u.exec(semanticKey);
9510
- if (match2) {
9511
- const base = `tileflow-${match2[1]}-${match2[2]}`;
9512
- return match2[3] === "fill" ? base : `${base}-outline`;
9513
- }
9514
- match2 = /^land\.compatibility\.legacyPark\.(fill|outline)$/u.exec(semanticKey);
9515
- if (match2) {
9516
- return match2[1] === "fill" ? "tileflow-landcover-legacy-park" : "tileflow-landcover-legacy-park-outline";
9517
- }
9518
- match2 = /^land\.cohorts\.(landcover|landuse)(?:\.cohort(\d+))?$/u.exec(semanticKey);
9519
- if (match2) return `tileflow-${match2[1]}${match2[2] === void 0 ? "" : `-${match2[2]}`}`;
9520
- match2 = /^water\.(intermittent\.)?bodies\.(fill|outline)$/u.exec(semanticKey);
9521
- if (match2) {
9522
- const base = `tileflow-water${match2[1] ? "-intermittent" : ""}`;
9523
- return match2[2] === "fill" ? base : `${base}-outline`;
9524
- }
9525
- match2 = /^water\.(intermittent\.)?waterways\.([^.]+)$/u.exec(semanticKey);
9526
- if (match2) return `tileflow-waterway-${match2[2]}${match2[1] ? "-intermittent" : ""}`;
9527
- match2 = /^buildings\.(businessCorridor|flat)\.(fill|outline)$/u.exec(semanticKey);
9528
- if (match2) {
9529
- const base = match2[1] === "businessCorridor" ? "tileflow-business-corridor" : "tileflow-buildings-fill";
9530
- return match2[2] === "fill" ? base : `${base}-outline`;
9531
- }
9532
- match2 = /^aeroways\.area\.(fill|outline)$/u.exec(semanticKey);
9533
- if (match2) return match2[1] === "fill" ? "tileflow-aeroway-area" : "tileflow-aeroway-area-outline";
9534
- match2 = /^aeroways\.(taxiway|runway)\.(shadow|casing|fill)$/u.exec(semanticKey);
9535
- if (match2) return `tileflow-aeroway-${match2[1]}-${match2[2]}`;
9536
- match2 = /^transit\.([A-Za-z0-9_-]+)(?:\.(tunnel|surface|bridge))?$/u.exec(semanticKey);
9537
- if (match2) return `tileflow-transit-${match2[1]}${match2[2] ? `-${match2[2]}` : ""}`;
9538
- match2 = /^boundaries\.([A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)$/u.exec(semanticKey);
9539
- if (match2) return `tileflow-boundary-${match2[1].replaceAll(".", "-")}`;
9540
- match2 = /^labels\.places\.([^.]+)$/u.exec(semanticKey);
9541
- if (match2) return `tileflow-label-place-${match2[1]}`;
9542
- match2 = /^labels\.roads\.([^.]+)$/u.exec(semanticKey);
9543
- if (match2) return `tileflow-label-road-${match2[1]}`;
9544
- match2 = /^labels\.shields\.([^.]+)$/u.exec(semanticKey);
9545
- if (match2) return `tileflow-label-road-shield-${match2[1]}`;
9546
- match2 = /^labels\.water\.([^.]+)$/u.exec(semanticKey);
9547
- if (match2) return `tileflow-label-water-${match2[1]}`;
9548
- match2 = /^labels\.cohorts\.roads\.(major|local)$/u.exec(semanticKey);
9549
- if (match2) return `tileflow-label-road-${match2[1]}`;
9550
- match2 = /^labels\.cohorts\.roads\.cohort(\d+)$/u.exec(semanticKey);
9551
- if (match2) return `tileflow-label-road-cohort-${match2[1]}`;
9552
- match2 = /^landforms\.classes\.([^.]+)$/u.exec(semanticKey);
9553
- if (match2) return `tileflow-landform-${match2[1]}`;
9554
- match2 = /^poi\.([A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)$/u.exec(semanticKey);
9555
- if (match2) return `tileflow-poi-${match2[1].replaceAll(".", "-")}`;
10052
+ if (match3) return `tileflow-road-${match3[1]}-highzoom-${match3[2]}-${match3[3]}`;
10053
+ match3 = /^roads\.cohorts\.(tunnel|surface|bridge)\.hatch$/u.exec(semanticKey);
10054
+ if (match3) return `tileflow-road-${match3[1]}-hatch`;
10055
+ match3 = /^roads\.areas\.(road|pedestrian|pier)\.(fill|outline)$/u.exec(semanticKey);
10056
+ if (match3) {
10057
+ const base = match3[1] === "road" ? "tileflow-road-area" : `tileflow-road-${match3[1]}-area`;
10058
+ return match3[2] === "fill" ? base : `${base}-outline`;
10059
+ }
10060
+ match3 = /^land\.(landcover|landuse)\.([^.]+)\.(fill|outline)$/u.exec(semanticKey);
10061
+ if (match3) {
10062
+ const base = `tileflow-${match3[1]}-${match3[2]}`;
10063
+ return match3[3] === "fill" ? base : `${base}-outline`;
10064
+ }
10065
+ match3 = /^land\.compatibility\.legacyPark\.(fill|outline)$/u.exec(semanticKey);
10066
+ if (match3) {
10067
+ return match3[1] === "fill" ? "tileflow-landcover-legacy-park" : "tileflow-landcover-legacy-park-outline";
10068
+ }
10069
+ match3 = /^land\.cohorts\.(landcover|landuse)(?:\.cohort(\d+))?$/u.exec(semanticKey);
10070
+ if (match3) return `tileflow-${match3[1]}${match3[2] === void 0 ? "" : `-${match3[2]}`}`;
10071
+ match3 = /^water\.(intermittent\.)?bodies\.(fill|outline)$/u.exec(semanticKey);
10072
+ if (match3) {
10073
+ const base = `tileflow-water${match3[1] ? "-intermittent" : ""}`;
10074
+ return match3[2] === "fill" ? base : `${base}-outline`;
10075
+ }
10076
+ match3 = /^water\.(intermittent\.)?waterways\.([^.]+)$/u.exec(semanticKey);
10077
+ if (match3) return `tileflow-waterway-${match3[2]}${match3[1] ? "-intermittent" : ""}`;
10078
+ match3 = /^buildings\.(businessCorridor|flat)\.(fill|outline)$/u.exec(semanticKey);
10079
+ if (match3) {
10080
+ const base = match3[1] === "businessCorridor" ? "tileflow-business-corridor" : "tileflow-buildings-fill";
10081
+ return match3[2] === "fill" ? base : `${base}-outline`;
10082
+ }
10083
+ match3 = /^aeroways\.area\.(fill|outline)$/u.exec(semanticKey);
10084
+ if (match3) return match3[1] === "fill" ? "tileflow-aeroway-area" : "tileflow-aeroway-area-outline";
10085
+ match3 = /^aeroways\.(taxiway|runway)\.(shadow|casing|fill)$/u.exec(semanticKey);
10086
+ if (match3) return `tileflow-aeroway-${match3[1]}-${match3[2]}`;
10087
+ match3 = /^transit\.([A-Za-z0-9_-]+)(?:\.(tunnel|surface|bridge))?$/u.exec(semanticKey);
10088
+ if (match3) return `tileflow-transit-${match3[1]}${match3[2] ? `-${match3[2]}` : ""}`;
10089
+ match3 = /^boundaries\.([A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)$/u.exec(semanticKey);
10090
+ if (match3) return `tileflow-boundary-${match3[1].replaceAll(".", "-")}`;
10091
+ match3 = /^labels\.places\.([^.]+)$/u.exec(semanticKey);
10092
+ if (match3) return `tileflow-label-place-${match3[1]}`;
10093
+ match3 = /^labels\.roads\.([^.]+)$/u.exec(semanticKey);
10094
+ if (match3) return `tileflow-label-road-${match3[1]}`;
10095
+ match3 = /^labels\.shields\.([^.]+)$/u.exec(semanticKey);
10096
+ if (match3) return `tileflow-label-road-shield-${match3[1]}`;
10097
+ match3 = /^labels\.water\.([^.]+)$/u.exec(semanticKey);
10098
+ if (match3) return `tileflow-label-water-${match3[1]}`;
10099
+ match3 = /^labels\.cohorts\.roads\.(major|local)$/u.exec(semanticKey);
10100
+ if (match3) return `tileflow-label-road-${match3[1]}`;
10101
+ match3 = /^labels\.cohorts\.roads\.cohort(\d+)$/u.exec(semanticKey);
10102
+ if (match3) return `tileflow-label-road-cohort-${match3[1]}`;
10103
+ match3 = /^landforms\.classes\.([^.]+)$/u.exec(semanticKey);
10104
+ if (match3) return `tileflow-landform-${match3[1]}`;
10105
+ match3 = /^water\.(bathymetryLandforms|bathymetryWaterNames)\.cohort(\d+)$/u.exec(semanticKey);
10106
+ if (match3) {
10107
+ const base = match3[1] === "bathymetryLandforms" ? "landforms" : "water-names";
10108
+ return `tileflow-bathymetry-${base}-cohort-${match3[2]}`;
10109
+ }
10110
+ match3 = /^poi\.([A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)$/u.exec(semanticKey);
10111
+ if (match3) return `tileflow-poi-${match3[1].replaceAll(".", "-")}`;
9556
10112
  const nautical2 = nauticalPhysicalLayerId(semanticKey);
9557
10113
  if (nautical2) return nautical2;
9558
10114
  throw new TileflowDomainIRError(
@@ -9569,6 +10125,7 @@ var exactPhysicalLayerIds = Object.freeze({
9569
10125
  "labels.junctions": "tileflow-label-road-junction",
9570
10126
  "land.background": "tileflow-background",
9571
10127
  "land.globalLandcover": "tileflow-global-landcover",
10128
+ poi: "tileflow-poi",
9572
10129
  "landforms.classes": "tileflow-landforms",
9573
10130
  "roads.crossings": "tileflow-road-crossing",
9574
10131
  "roads.oneWayMarkers": "tileflow-road-oneway",
@@ -9585,6 +10142,8 @@ var exactPhysicalLayerIds = Object.freeze({
9585
10142
  "water.bathymetry": "tileflow-bathymetry",
9586
10143
  "water.bathymetryContours": "tileflow-bathymetry-contours",
9587
10144
  "water.bathymetryLabels": "tileflow-bathymetry-labels",
10145
+ "water.bathymetryLandforms": "tileflow-bathymetry-landforms",
10146
+ "water.bathymetryWaterNames": "tileflow-bathymetry-water-names",
9588
10147
  "water.bathymetryRelief.color": "tileflow-bathymetry-color-relief",
9589
10148
  "water.bathymetryRelief.hillshade": "tileflow-bathymetry-relief"
9590
10149
  });
@@ -9609,15 +10168,15 @@ var nauticalPhysicalBases = Object.freeze({
9609
10168
  function nauticalPhysicalLayerId(semanticKey) {
9610
10169
  const direct = nauticalPhysicalBases[semanticKey];
9611
10170
  if (direct) return direct;
9612
- const match2 = /^(nautical\.[A-Za-z0-9_.-]+)\.(fill|outline|marker|symbol)$/u.exec(semanticKey);
9613
- if (!match2) return void 0;
9614
- const base = nauticalPhysicalBases[match2[1]];
10171
+ const match3 = /^(nautical\.[A-Za-z0-9_.-]+)\.(fill|outline|marker|symbol)$/u.exec(semanticKey);
10172
+ if (!match3) return void 0;
10173
+ const base = nauticalPhysicalBases[match3[1]];
9615
10174
  if (!base) return void 0;
9616
- if (match2[2] === "fill" || match2[2] === "symbol") return base;
9617
- return `${base}-${match2[2]}`;
10175
+ if (match3[2] === "fill" || match3[2] === "symbol") return base;
10176
+ return `${base}-${match3[2]}`;
9618
10177
  }
9619
10178
  function publicMetadata(value) {
9620
- const metadata = { ...asRecord3(value) };
10179
+ const metadata = { ...asRecord4(value) };
9621
10180
  delete metadata[tileflowCompilerProvenanceMetadataKey];
9622
10181
  for (const key of Object.values(tileflowCompilerMetadataKeys)) delete metadata[key];
9623
10182
  return Object.keys(metadata).length > 0 ? cloneJson20(metadata) : void 0;
@@ -9653,21 +10212,21 @@ function visitReferences(value, counts, visited = /* @__PURE__ */ new WeakSet())
9653
10212
  }
9654
10213
  }
9655
10214
  function isSemanticFieldReference(value) {
9656
- return isRecord6(value) && value.kind === "tileflow-data-field";
10215
+ return isRecord5(value) && value.kind === "tileflow-data-field";
9657
10216
  }
9658
10217
  function isSemanticLayerReference(value) {
9659
- return isRecord6(value) && value.kind === "tileflow-data-layer";
10218
+ return isRecord5(value) && value.kind === "tileflow-data-layer";
9660
10219
  }
9661
10220
  function isSemanticSourceReference(value) {
9662
- return isRecord6(value) && value.kind === "tileflow-data-source";
10221
+ return isRecord5(value) && value.kind === "tileflow-data-source";
9663
10222
  }
9664
10223
  function cloneJson20(value) {
9665
10224
  return JSON.parse(JSON.stringify(value));
9666
10225
  }
9667
- function asRecord3(value) {
9668
- return isRecord6(value) ? value : {};
10226
+ function asRecord4(value) {
10227
+ return isRecord5(value) ? value : {};
9669
10228
  }
9670
- function isRecord6(value) {
10229
+ function isRecord5(value) {
9671
10230
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
9672
10231
  }
9673
10232
 
@@ -9719,7 +10278,7 @@ function validateTileflowRenderSelectorConstraints(input) {
9719
10278
  };
9720
10279
  while (pending.length > 0) {
9721
10280
  const current = pending.pop();
9722
- if (!isRecord7(current.selector) || typeof current.selector.kind !== "string") {
10281
+ if (!isRecord6(current.selector) || typeof current.selector.kind !== "string") {
9723
10282
  addIssue(
9724
10283
  "invalid-selector",
9725
10284
  "a render selector must be a semantic selector node",
@@ -9780,7 +10339,7 @@ function validateTileflowRenderSelectorConstraints(input) {
9780
10339
  }
9781
10340
  for (const [index, branch] of current.selector.branches.entries()) {
9782
10341
  const branchPath = [...branchesPath, index];
9783
- if (!isRecord7(branch)) {
10342
+ if (!isRecord6(branch)) {
9784
10343
  addIssue("invalid-selector", "match selector branch must be an object", branchPath);
9785
10344
  continue;
9786
10345
  }
@@ -9811,14 +10370,14 @@ function validateTileflowRenderSelectorConstraints(input) {
9811
10370
  for (let index = current.selector.stops.length - 1; index >= 0; index -= 1) {
9812
10371
  const stop = current.selector.stops[index];
9813
10372
  const stopPath = [...stopsPath, index];
9814
- if (!isRecord7(stop)) {
10373
+ if (!isRecord6(stop)) {
9815
10374
  addIssue("invalid-selector", "step selector stop must be an object", stopPath);
9816
10375
  continue;
9817
10376
  }
9818
10377
  pushChild(stop.selector, current.depth + 1, [...stopPath, "selector"]);
9819
10378
  }
9820
10379
  for (const [index, stop] of current.selector.stops.entries()) {
9821
- if (!isRecord7(stop)) continue;
10380
+ if (!isRecord6(stop)) continue;
9822
10381
  const zoom3 = stop.zoom;
9823
10382
  const zoomPath = [...stopsPath, index, "zoom"];
9824
10383
  if (typeof zoom3 !== "number" || !Number.isFinite(zoom3) || zoom3 < 0 || zoom3 > 24) {
@@ -10138,12 +10697,12 @@ function applyRendererStyle(layer, renderer, style) {
10138
10697
  if (!visibilityGroup) return result;
10139
10698
  return {
10140
10699
  ...result,
10141
- layout: { ...asRecord4(result.layout), visibility: "none" },
10142
- metadata: { ...asRecord4(result.metadata), "tileflow:3d-toggle": visibilityGroup }
10700
+ layout: { ...asRecord5(result.layout), visibility: "none" },
10701
+ metadata: { ...asRecord5(result.metadata), "tileflow:3d-toggle": visibilityGroup }
10143
10702
  };
10144
10703
  }
10145
10704
  function lowerSelector(selector, data) {
10146
- if (!isRecord7(selector) || typeof selector.kind !== "string") {
10705
+ if (!isRecord6(selector) || typeof selector.kind !== "string") {
10147
10706
  throw renderStackError(
10148
10707
  "invalid-selector",
10149
10708
  "A render selector must be a semantic selector node."
@@ -10346,7 +10905,7 @@ function requireRenderer(value) {
10346
10905
  function requirePlacement(value) {
10347
10906
  if (typeof value === "string") {
10348
10907
  if (tileflowBandPlacements.includes(value)) return;
10349
- } else if (isRecord7(value)) {
10908
+ } else if (isRecord6(value)) {
10350
10909
  const keys = Object.keys(value);
10351
10910
  if (keys.length === 1 && (keys[0] === "above" || keys[0] === "below")) {
10352
10911
  requirePortableTarget(value[keys[0]], "render placement target");
@@ -10373,7 +10932,7 @@ function validateRefinementShape(operation) {
10373
10932
  validateStyle(operation.renderer, operation.style);
10374
10933
  }
10375
10934
  function validateStyle(renderer, style) {
10376
- if (!isRecord7(style)) {
10935
+ if (!isRecord6(style)) {
10377
10936
  throw renderStackError(
10378
10937
  "invalid-style",
10379
10938
  `A ${renderer} render operation requires a style object.`
@@ -10475,7 +11034,7 @@ function normalizeRequirements(requirements) {
10475
11034
  return [...requirements];
10476
11035
  }
10477
11036
  function requireRenderStackEntries(stack) {
10478
- if (!isRecord7(stack)) {
11037
+ if (!isRecord6(stack)) {
10479
11038
  throw renderStackError("invalid-stack", "A render stack must be a named operation record.");
10480
11039
  }
10481
11040
  const entries = Object.entries(stack);
@@ -10621,10 +11180,10 @@ function compareCodeUnits6(left, right) {
10621
11180
  function cloneJson21(value) {
10622
11181
  return JSON.parse(JSON.stringify(value));
10623
11182
  }
10624
- function asRecord4(value) {
10625
- return isRecord7(value) ? value : {};
11183
+ function asRecord5(value) {
11184
+ return isRecord6(value) ? value : {};
10626
11185
  }
10627
- function isRecord7(value) {
11186
+ function isRecord6(value) {
10628
11187
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
10629
11188
  }
10630
11189
 
@@ -10819,6 +11378,19 @@ function isLoopbackHostname(hostname) {
10819
11378
  return normalized === "localhost" || normalized === "::1" || normalized === "0:0:0:0:0:0:0:1" || /^127(?:\.\d{1,3}){3}$/u.test(normalized);
10820
11379
  }
10821
11380
 
11381
+ // src/time-of-day-schema.ts
11382
+ function createTileflowTimeOfDaySchema(operations, themeName) {
11383
+ return operations.record(
11384
+ themeName,
11385
+ operations.object({
11386
+ day: operations.optional(themeName),
11387
+ dawn: operations.optional(themeName),
11388
+ dusk: operations.optional(themeName),
11389
+ night: operations.optional(themeName)
11390
+ })
11391
+ );
11392
+ }
11393
+
10822
11394
  // src/resolved-map-schema.ts
10823
11395
  var TileflowResolvedMapValidationError = class extends Error {
10824
11396
  code = "CONFIG_INVALID";
@@ -10966,7 +11538,7 @@ function validateExpressionFieldNames(value, context, path) {
10966
11538
  }
10967
11539
  if (!value || typeof value !== "object") return;
10968
11540
  const candidate = value;
10969
- if (candidate.kind === "tileflow-data-field" && (typeof candidate.name !== "string" || !Object.hasOwn(fieldBindingsSchema.shape, candidate.name))) {
11541
+ if (candidate.kind === "tileflow-data-field" && (typeof candidate.name !== "string" || !tileflowSemanticExpressionFieldNames.some((name) => name === candidate.name))) {
10970
11542
  context.addIssue({
10971
11543
  code: "custom",
10972
11544
  message: "Expected a registered semantic field name.",
@@ -11120,12 +11692,12 @@ var hexColorSchema = z5.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a
11120
11692
  message: "Expected a hex color like #F6F7F3 or #F6F7F3FF"
11121
11693
  });
11122
11694
  var rgbColorSchema = z5.string().refine((value) => {
11123
- const match2 = value.match(
11695
+ const match3 = value.match(
11124
11696
  /^rgba?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)(?:\s*,\s*(\d+(?:\.\d+)?))?\s*\)$/iu
11125
11697
  );
11126
- if (!match2) return false;
11127
- const channels = match2.slice(1, 4).map(Number);
11128
- const alpha2 = Number(match2[4] ?? 1);
11698
+ if (!match3) return false;
11699
+ const channels = match3.slice(1, 4).map(Number);
11700
+ const alpha2 = Number(match3[4] ?? 1);
11129
11701
  return channels.every((channel) => channel >= 0 && channel <= 255) && alpha2 >= 0 && alpha2 <= 1;
11130
11702
  }, "Expected a Tileflow hex, rgb(), or rgba() color");
11131
11703
  var terrainColorLiteralSchema = z5.union([hexColorSchema, rgbColorSchema]);
@@ -11402,9 +11974,7 @@ var symbolStyleSchema = z5.object({
11402
11974
  text: textStyleSchema.optional()
11403
11975
  }).strict();
11404
11976
  var markerSymbolStyleSchema = symbolStyleSchema.extend({ marker: circleStyleSchema.optional() }).strict();
11405
- var poiCategoryStyleSchema = markerSymbolStyleSchema.omit({ priority: true, priorityOrder: true }).extend({
11406
- density: z5.union([z5.literal(1), z5.literal(2), z5.literal(3), z5.literal(4), z5.literal(5)]).optional()
11407
- }).strict();
11977
+ var poiCategoryStyleSchema = markerSymbolStyleSchema.omit({ priority: true, priorityOrder: true }).strict();
11408
11978
  var renderScalarSchema = z5.union([z5.boolean(), z5.number().finite(), z5.string()]);
11409
11979
  var renderTargetSchema = z5.string().regex(tileflowSemanticTargetPattern, "Expected a semantic target");
11410
11980
  var renderStackOperationNameSchema = z5.string().regex(
@@ -11637,6 +12207,8 @@ var waterModuleSchema = z5.object({
11637
12207
  bathymetry: fillStyleSchema.optional(),
11638
12208
  bathymetryContours: lineStyleSchema.optional(),
11639
12209
  bathymetryLabels: symbolStyleSchema.optional(),
12210
+ bathymetryLandforms: symbolStyleSchema.optional(),
12211
+ bathymetryWaterNames: symbolStyleSchema.optional(),
11640
12212
  bodies: areaStyleSchema.optional(),
11641
12213
  enabled: z5.boolean().optional(),
11642
12214
  intermittent: z5.object({ bodies: areaStyleSchema.optional(), waterways: lineStyleSchema.optional() }).strict().optional(),
@@ -11668,12 +12240,43 @@ var buildingsModuleSchema = z5.object({
11668
12240
  mode: z5.enum(["3d", "flat"]).optional(),
11669
12241
  renderStack: renderStackSchema.optional(),
11670
12242
  threeDimensional: z5.object({
12243
+ lighting: z5.enum(["style", "sun"]).optional(),
12244
+ colorVariation: boundedThemeNumberSchema(0, 1).optional(),
11671
12245
  edges: boundedThemeNumberSchema(0, 1).optional(),
11672
12246
  edgeColor: themeColorValueSchema.optional(),
11673
12247
  nightLight: boundedThemeNumberSchema(0, 2).optional(),
11674
12248
  occlusion: z5.boolean().optional(),
11675
12249
  shadows: z5.boolean().optional(),
11676
- windows: z5.boolean().optional()
12250
+ windows: z5.boolean().optional(),
12251
+ models: z5.array(
12252
+ z5.object({
12253
+ model: z5.string().max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u),
12254
+ at: z5.tuple([z5.number().min(-180).max(180), z5.number().min(-85).max(85)]),
12255
+ heading: z5.number().finite().optional(),
12256
+ url: z5.url().max(2048).refine((value) => new URL(value).protocol === "https:", {
12257
+ message: "A model served elsewhere has an HTTPS address."
12258
+ }).optional()
12259
+ }).strict()
12260
+ ).max(256).optional(),
12261
+ modelColors: z5.object({
12262
+ surface: themeColorValueSchema.optional(),
12263
+ secondary: themeColorValueSchema.optional(),
12264
+ glass: themeColorValueSchema.optional(),
12265
+ accent: themeColorValueSchema.optional(),
12266
+ roof: themeColorValueSchema.optional()
12267
+ }).strict().optional(),
12268
+ landmarks: z5.union([
12269
+ z5.boolean(),
12270
+ z5.object({
12271
+ url: z5.url().max(2048).refine(
12272
+ (value) => {
12273
+ const url = new URL(value);
12274
+ return url.protocol === "https:" || url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
12275
+ },
12276
+ { message: "A landmarks tileset has an HTTPS address, or a local one." }
12277
+ )
12278
+ }).strict()
12279
+ ]).optional()
11677
12280
  }).strict().optional()
11678
12281
  }).strict();
11679
12282
  var addressesModuleSchema = z5.object({
@@ -11852,7 +12455,10 @@ var poiModuleSchema = z5.object({
11852
12455
  minZoom: zoomNumberSchema.optional(),
11853
12456
  placement: z5.object({
11854
12457
  coupleIconAndLabel: z5.boolean().optional(),
12458
+ iconOnlyTypes: z5.array(z5.string().regex(/^[a-z0-9]+(?:_[a-z0-9]+)*$/u, "Expected a World POI type")).max(64).optional(),
11855
12459
  iconPadding: z5.number().finite().min(0).optional(),
12460
+ order: z5.enum(["categories", "importance"]).optional(),
12461
+ prominentRank: z5.number().int().min(0).max(5).optional(),
11856
12462
  textPadding: z5.number().finite().min(0).optional()
11857
12463
  }).strict().optional(),
11858
12464
  renderStack: renderStackSchema.optional(),
@@ -11907,7 +12513,9 @@ var modulesSchema = z5.object({
11907
12513
  type: z5.literal("vegetation"),
11908
12514
  enabled: z5.boolean().optional(),
11909
12515
  flat: circleStyleSchema.optional(),
11910
- mode: z5.enum(["3d", "flat"]).optional(),
12516
+ mode: z5.enum(["3d", "flat", "auto"]).optional(),
12517
+ fallback: z5.enum(["flat", "none"]).optional(),
12518
+ visible: z5.boolean().optional(),
11911
12519
  renderStack: renderStackSchema.optional(),
11912
12520
  threeDimensional: z5.object({
11913
12521
  barkColor: themeColorValueSchema.optional(),
@@ -12044,6 +12652,7 @@ var fieldBindingsSchema = z5.object({
12044
12652
  markings: z5.string().trim().min(1),
12045
12653
  minHeight: z5.string().trim().min(1),
12046
12654
  minZoom: z5.string().trim().min(1),
12655
+ mode: z5.string().trim().min(1),
12047
12656
  mtbScale: z5.string().trim().min(1),
12048
12657
  name: z5.string().trim().min(1),
12049
12658
  nameEnglish: z5.string().trim().min(1),
@@ -12147,6 +12756,32 @@ var bathymetryReliefSchema = z5.object({
12147
12756
  });
12148
12757
  }
12149
12758
  });
12759
+ var bathymetryNameLayerSchema = z5.union([
12760
+ z5.boolean(),
12761
+ z5.object({
12762
+ zoomRanges: z5.array(z5.object({ minZoom: zoomNumberSchema, maxZoom: zoomNumberSchema }).strict()).min(1).max(tileflowBathymetryNameLayerLimits.maxZoomRanges).superRefine((ranges, context) => {
12763
+ const seen = /* @__PURE__ */ new Set();
12764
+ for (const [index, range2] of ranges.entries()) {
12765
+ if (range2.minZoom >= range2.maxZoom) {
12766
+ context.addIssue({
12767
+ code: "custom",
12768
+ message: "Expected minZoom < maxZoom",
12769
+ path: [index, "maxZoom"]
12770
+ });
12771
+ }
12772
+ const key = `${range2.minZoom}/${range2.maxZoom}`;
12773
+ if (seen.has(key)) {
12774
+ context.addIssue({
12775
+ code: "custom",
12776
+ message: "Expected unique nominal zoom ranges",
12777
+ path: [index]
12778
+ });
12779
+ }
12780
+ seen.add(key);
12781
+ }
12782
+ })
12783
+ }).strict()
12784
+ ]);
12150
12785
  var bathymetryConfigSchema = z5.object({
12151
12786
  ...marineSourceSchema.shape,
12152
12787
  bands: z5.union([z5.literal(false), fillStyleSchema]).optional(),
@@ -12154,7 +12789,12 @@ var bathymetryConfigSchema = z5.object({
12154
12789
  display: z5.enum(["bands", "relief", "hybrid"]).optional(),
12155
12790
  labels: z5.union([z5.literal(false), symbolStyleSchema]).optional(),
12156
12791
  relief: z5.union([z5.literal(false), bathymetryReliefSchema]).optional(),
12157
- type: z5.literal("bathymetry")
12792
+ type: z5.literal("bathymetry"),
12793
+ vectorLayers: z5.object({
12794
+ contours: z5.boolean().optional(),
12795
+ landforms: bathymetryNameLayerSchema.optional(),
12796
+ waterNames: bathymetryNameLayerSchema.optional()
12797
+ }).strict().optional()
12158
12798
  }).strict();
12159
12799
  var marineSchema = z5.union([
12160
12800
  z5.enum(["none", "bathymetry", "nautical", "chart"]),
@@ -12509,6 +13149,10 @@ var resolvedTileflowMapSchema = z5.object({
12509
13149
  systemThemes: systemThemesSchema.optional(),
12510
13150
  terrain: terrainSchema.optional(),
12511
13151
  themes: themesSchema,
13152
+ timeOfDay: createTileflowTimeOfDaySchema(
13153
+ classicManifestSchemaOperations,
13154
+ tileflowThemeNameSchema
13155
+ ).optional(),
12512
13156
  version: z5.number().int().positive(),
12513
13157
  view: viewSchema.optional()
12514
13158
  }).strict().superRefine((map, context) => {
@@ -12533,6 +13177,15 @@ var resolvedTileflowMapSchema = z5.object({
12533
13177
  path: ["defaultTheme"]
12534
13178
  });
12535
13179
  }
13180
+ if (map.timeOfDay) {
13181
+ for (const issue of validateTileflowTimeOfDayVariants(map.timeOfDay, map.themes)) {
13182
+ context.addIssue({
13183
+ code: "custom",
13184
+ message: issue.message,
13185
+ path: ["timeOfDay", ...issue.path]
13186
+ });
13187
+ }
13188
+ }
12536
13189
  const themeEntries = Object.entries(map.themes).sort(
12537
13190
  ([left], [right]) => left < right ? -1 : left > right ? 1 : 0
12538
13191
  );
@@ -12755,6 +13408,23 @@ function tileflowRouteMetadata(inks) {
12755
13408
  return Object.freeze({ version: 1, ...inks });
12756
13409
  }
12757
13410
 
13411
+ // src/street-lights.ts
13412
+ var tileflowStreetLightsMetadataKey = "tileflow:street-lights";
13413
+ var tileflowDefaultStreetLightsDesign = Object.freeze({
13414
+ source: "tileflow",
13415
+ layers: Object.freeze({
13416
+ streetFurniture: "street_furniture",
13417
+ transportation: "transportation",
13418
+ sidewalk: "sidewalk"
13419
+ }),
13420
+ estimate: "lit",
13421
+ color: "#ffcf8f",
13422
+ intensity: 1
13423
+ });
13424
+ function tileflowStreetLightsMetadata(design) {
13425
+ return { version: 1, ...design };
13426
+ }
13427
+
12758
13428
  // src/terrain/index.ts
12759
13429
  function resolveTerrain(terrain, apiBaseUrl) {
12760
13430
  if (!terrain || terrain === "none") return void 0;
@@ -12933,7 +13603,7 @@ function terrainContribution(target, localOrder, layer) {
12933
13603
  };
12934
13604
  }
12935
13605
  function applyLayerRange2(layer, range2) {
12936
- const layout = { ...asRecord5(layer.layout) };
13606
+ const layout = { ...asRecord6(layer.layout) };
12937
13607
  if (range2.visible !== void 0) layout.visibility = range2.visible ? "visible" : "none";
12938
13608
  return {
12939
13609
  ...layer,
@@ -12942,10 +13612,13 @@ function applyLayerRange2(layer, range2) {
12942
13612
  ...Object.keys(layout).length === 0 ? {} : { layout }
12943
13613
  };
12944
13614
  }
12945
- function asRecord5(value) {
13615
+ function asRecord6(value) {
12946
13616
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
12947
13617
  }
12948
13618
 
13619
+ // src/vegetation-display.ts
13620
+ var tileflowVegetationDisplayMetadataKey = "tileflow:vegetation-display";
13621
+
12949
13622
  // src/cartography/compilation-report.ts
12950
13623
  var tileflowCompilationReportSchemaVersion = 1;
12951
13624
  var tileflowCompilationPhases = Object.freeze([
@@ -13317,14 +13990,16 @@ function defaultPaint(image, key, names, appearances, ids) {
13317
13990
 
13318
13991
  // src/cartography/interaction-manifest.ts
13319
13992
  var tileflowInteractionManifestMetadataKey = "tileflow:interaction-manifest";
13320
- var tileflowInteractionManifestVersion = 2;
13993
+ var tileflowInteractionManifestVersion = 3;
13321
13994
  function createTileflowInteractionManifest(layers, fields) {
13322
13995
  const poiLayers = layers.flatMap((layer, priority) => {
13323
13996
  const id2 = typeof layer.id === "string" ? layer.id : void 0;
13324
13997
  const source = typeof layer.source === "string" ? layer.source : void 0;
13325
13998
  const sourceLayer = typeof layer["source-layer"] === "string" ? layer["source-layer"] : void 0;
13326
13999
  const target = semanticTarget(layer);
13327
- if (!id2 || !source || !sourceLayer || !target?.startsWith("poi.")) return [];
14000
+ if (!id2 || !source || !sourceLayer || target !== "poi" && !target?.startsWith("poi.")) {
14001
+ return [];
14002
+ }
13328
14003
  const parsed = parsePoiTarget(target);
13329
14004
  if (!parsed) return [];
13330
14005
  return [
@@ -13372,7 +14047,7 @@ function assertTileflowInteractionManifestLayers(manifest, layers) {
13372
14047
  );
13373
14048
  }
13374
14049
  for (const layer of poiLayers) {
13375
- if (!isTileflowPoiCategory(layer.category) || !isSafeArtifactName(layer.layerId) || !isSafeArtifactName(layer.source) || !isSafeArtifactName(layer.sourceLayer)) {
14050
+ if (!isSafeArtifactName(layer.layerId) || !isSafeArtifactName(layer.source) || !isSafeArtifactName(layer.sourceLayer)) {
13376
14051
  throw new Error("Tileflow interaction manifest contains an invalid physical POI lookup.");
13377
14052
  }
13378
14053
  const finalized = layersById.get(layer.layerId);
@@ -13395,25 +14070,26 @@ function assertTileflowInteractionManifestLayers(manifest, layers) {
13395
14070
  }
13396
14071
  function parsePoiTarget(target) {
13397
14072
  const segments = target.split(".");
13398
- if (segments[0] !== "poi" || segments.length < 2 || segments.length > 3) return void 0;
13399
- const category = segments[1];
13400
- if (!isTileflowPoiCategory(category)) return void 0;
13401
- const suffix = segments[2];
13402
- if (suffix === void 0) return { category, representation: "combined" };
13403
- if (suffix === "icon" || suffix === "label" || suffix === "marker") {
13404
- return { category, representation: suffix };
13405
- }
13406
- return void 0;
14073
+ if (segments[0] !== "poi" || segments.length > 3) return void 0;
14074
+ const [, first, second] = segments;
14075
+ if (first === void 0) return { representation: "combined" };
14076
+ if (segments.length === 2 && isRepresentationPart(first)) return { representation: first };
14077
+ if (first !== "prominent" && !isTileflowPoiCategory(first)) return void 0;
14078
+ if (second === void 0) return { representation: "combined" };
14079
+ return isRepresentationPart(second) ? { representation: second } : void 0;
14080
+ }
14081
+ function isRepresentationPart(value) {
14082
+ return value === "icon" || value === "label" || value === "marker";
13407
14083
  }
13408
14084
  function isTileflowPoiCategory(value) {
13409
14085
  return typeof value === "string" && tileflowPoiCategories.includes(value);
13410
14086
  }
13411
14087
  function semanticTarget(layer) {
13412
- if (!isRecord8(layer.metadata)) return void 0;
14088
+ if (!isRecord7(layer.metadata)) return void 0;
13413
14089
  const value = layer.metadata[tileflowCompilerMetadataKeys.target];
13414
14090
  return typeof value === "string" ? value : void 0;
13415
14091
  }
13416
- function isRecord8(value) {
14092
+ function isRecord7(value) {
13417
14093
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
13418
14094
  }
13419
14095
  function isSafeArtifactName(value) {
@@ -14137,7 +14813,7 @@ function conditionalExpression(entries, fallback) {
14137
14813
  ];
14138
14814
  }
14139
14815
  const classMatches = entries.map(({ condition }) => splitClassMatch(condition));
14140
- if (classMatches.every((match2) => match2 !== void 0) && allEqual(classMatches.map((match2) => match2?.field)) && allEqual(classMatches.map((match2) => match2?.remainder)) && classMatchLabelsAreDisjoint(classMatches.map((match2) => match2.labels))) {
14816
+ if (classMatches.every((match3) => match3 !== void 0) && allEqual(classMatches.map((match3) => match3?.field)) && allEqual(classMatches.map((match3) => match3?.remainder)) && classMatchLabelsAreDisjoint(classMatches.map((match3) => match3.labels))) {
14141
14817
  return [
14142
14818
  "match",
14143
14819
  ["get", classMatches[0].field],
@@ -14301,6 +14977,114 @@ function stableSerialize(value) {
14301
14977
  return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => `${JSON.stringify(key)}:${stableSerialize(entry)}`).join(",")}}`;
14302
14978
  }
14303
14979
 
14980
+ // src/cartography/scene-3d-metadata.ts
14981
+ var colourSources = [
14982
+ ["tileflow-buildings-render-extrusion", "fill-extrusion-color"],
14983
+ ["tileflow-buildings-3d", "fill-extrusion-color"],
14984
+ ["tileflow-buildings-fill", "fill-color"]
14985
+ ];
14986
+ function tileflowBuildings3DDesignOf(module, context, layers, buildingModels, tileflowLandmarks) {
14987
+ const config = isRecord8(module) ? module : {};
14988
+ if (config.enabled === false) return void 0;
14989
+ const style = config.threeDimensional ?? {};
14990
+ let color2;
14991
+ let verticalGradient = true;
14992
+ for (const [id2, property] of colourSources) {
14993
+ const value = layers.find((layer) => layer.id === id2)?.paint?.[property];
14994
+ if (typeof value === "string" || Array.isArray(value)) {
14995
+ color2 = value;
14996
+ verticalGradient = layers.find((layer) => layer.id === id2)?.paint?.["fill-extrusion-vertical-gradient"] !== false;
14997
+ break;
14998
+ }
14999
+ }
15000
+ const number = (value, fallback) => {
15001
+ const resolved = value === void 0 ? void 0 : toMapLibreStyleValue(value);
15002
+ return typeof resolved === "number" ? resolved : fallback;
15003
+ };
15004
+ const edgeColor = style.edgeColor === void 0 ? void 0 : toMapLibreStyleValue(style.edgeColor);
15005
+ return {
15006
+ source: context.data.sourceId,
15007
+ layers: {
15008
+ building: context.data.schema.layers.building,
15009
+ ...context.data.schema.layers.poi ? { places: context.data.schema.layers.poi } : {}
15010
+ },
15011
+ color: color2 ?? context.colors.buildings.generic,
15012
+ lighting: style.lighting === "sun" ? "sun" : "style",
15013
+ colorVariation: number(style.colorVariation, 0),
15014
+ verticalGradient,
15015
+ edges: number(style.edges, 0),
15016
+ ...typeof edgeColor === "string" ? { edgeColor } : {},
15017
+ nightLight: number(style.nightLight, 0),
15018
+ windows: style.windows === true,
15019
+ shadows: style.shadows === true,
15020
+ occlusion: style.occlusion === true,
15021
+ ...placedModels(style.models, buildingModels),
15022
+ ...modelColours(style.modelColors),
15023
+ ...landmarksOf(style.landmarks, tileflowLandmarks)
15024
+ };
15025
+ }
15026
+ function landmarksOf(value, tileflowLandmarks) {
15027
+ if (value === false) return {};
15028
+ if (isRecord8(value) && typeof value.url === "string") return { landmarks: { url: value.url } };
15029
+ return tileflowLandmarks ? { landmarks: { url: tileflowLandmarks } } : {};
15030
+ }
15031
+ function placedModels(value, served) {
15032
+ if (!Array.isArray(value) || value.length === 0) return {};
15033
+ const placements = value;
15034
+ const missing = [
15035
+ ...new Set(
15036
+ placements.filter((entry) => !entry.url).map((entry) => entry.model).filter((id2) => !served?.[id2])
15037
+ )
15038
+ ].sort();
15039
+ if (missing.length > 0)
15040
+ throw new Error(
15041
+ `The buildings module places missing 3D models: ${missing.join(", ")}. Add ${missing.map((id2) => `${id2}.glb`).join(
15042
+ ", "
15043
+ )} to a map.models directory, with materials named surface, secondary, glass, accent or roof, and run the Node build so the directories are prepared.`
15044
+ );
15045
+ return {
15046
+ models: placements.map((entry) => ({
15047
+ model: entry.model,
15048
+ url: entry.url ?? served[entry.model],
15049
+ at: [entry.at[0], entry.at[1]],
15050
+ heading: ((entry.heading ?? 0) % 360 + 360) % 360
15051
+ }))
15052
+ };
15053
+ }
15054
+ function modelColours(value) {
15055
+ if (!isRecord8(value)) return {};
15056
+ const colours = {};
15057
+ for (const [part, colour] of Object.entries(value)) {
15058
+ const resolved = colour === void 0 ? void 0 : toMapLibreStyleValue(colour);
15059
+ if (typeof resolved === "string") colours[part] = resolved;
15060
+ }
15061
+ return Object.keys(colours).length > 0 ? { modelColors: colours } : {};
15062
+ }
15063
+ function tileflowStreetLightsDesignOf(module, context) {
15064
+ const config = isRecord8(module) ? module : {};
15065
+ const layers = context.data.schema.layers;
15066
+ const style = config.streetLights ?? {};
15067
+ if (config.enabled === false || style.enabled === false || !layers.streetFurniture)
15068
+ return void 0;
15069
+ const color2 = style.color === void 0 ? void 0 : toMapLibreStyleValue(style.color);
15070
+ const intensity = style.intensity === void 0 ? void 0 : toMapLibreStyleValue(style.intensity);
15071
+ const estimate = style.estimate;
15072
+ return {
15073
+ source: context.data.sourceId,
15074
+ layers: {
15075
+ streetFurniture: layers.streetFurniture,
15076
+ transportation: layers.road,
15077
+ ...layers.sidewalk ? { sidewalk: layers.sidewalk } : {}
15078
+ },
15079
+ estimate: estimate === "streets" || estimate === "none" || estimate === "lit" ? estimate : "lit",
15080
+ color: typeof color2 === "string" ? color2 : tileflowDefaultStreetLightsDesign.color,
15081
+ intensity: typeof intensity === "number" ? intensity : 1
15082
+ };
15083
+ }
15084
+ function isRecord8(value) {
15085
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15086
+ }
15087
+
14304
15088
  // src/cartography/semantic-compiler.ts
14305
15089
  var tileflowSemanticCompilerIdentity = Object.freeze({
14306
15090
  name: "tileflow-semantic",
@@ -14331,7 +15115,10 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
14331
15115
  suggestion,
14332
15116
  ...target ? { target } : {}
14333
15117
  }));
14334
- const selected = runCompilationPhase("theme", () => resolveThemeSelection(input, options.theme));
15118
+ const selected = runCompilationPhase(
15119
+ "theme",
15120
+ () => resolveThemeSelection(input, options.theme, options.timeOfDay)
15121
+ );
14335
15122
  const resolvedTheme = runCompilationPhase("theme", () => resolveTileflowTheme(selected.theme));
14336
15123
  const { overlays, themes: _themes, ...themeableConfig } = input;
14337
15124
  const resolvedThemeConfig = runCompilationPhase(
@@ -14612,7 +15399,9 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
14612
15399
  const buildings3D = tileflowBuildings3DDesignOf(
14613
15400
  modules.buildings,
14614
15401
  context,
14615
- layers
15402
+ layers,
15403
+ options.preparedAssets?.buildingModels,
15404
+ data.kind === "tileflow-world" ? `${apiBaseUrl}/landmarks/tiles.json` : void 0
14616
15405
  );
14617
15406
  const streetLights = tileflowStreetLightsDesignOf(
14618
15407
  modules.roads,
@@ -14665,6 +15454,24 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
14665
15454
  layers
14666
15455
  })
14667
15456
  );
15457
+ const flatVegetationLayers = layers.filter((layer) => {
15458
+ const metadata = layer.metadata;
15459
+ return metadata?.["tileflow:vegetation-mode"] !== void 0 || metadata?.["tileflow:domain"] === "vegetation";
15460
+ });
15461
+ const vegetationDisplay = flatVegetationLayers.length || vegetation3D ? {
15462
+ version: 1,
15463
+ mode: modules.vegetation?.mode ?? "3d",
15464
+ fallback: modules.vegetation?.fallback ?? "flat",
15465
+ visible: modules.vegetation?.visible ?? true,
15466
+ flatLayers: flatVegetationLayers.map((layer) => ({
15467
+ id: String(layer.id),
15468
+ visible: layer.layout?.visibility !== "none"
15469
+ }))
15470
+ } : void 0;
15471
+ if (vegetationDisplay && (!vegetationDisplay.visible || vegetationDisplay.mode === "3d" && vegetationDisplay.fallback === "none")) {
15472
+ for (const layer of flatVegetationLayers)
15473
+ layer.layout = { ...layer.layout, visibility: "none" };
15474
+ }
14668
15475
  const atmosphere = resolveTileflowAtmosphere(config.atmosphere);
14669
15476
  const style = {
14670
15477
  ...atmosphere ? { sky: compileTileflowSky(atmosphere) } : {},
@@ -14704,12 +15511,10 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
14704
15511
  [tileflowVegetationMetadataKey]: tileflowVegetationMetadata({
14705
15512
  ...bindSemanticReferences(vegetation3D, semanticData),
14706
15513
  // 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))
15514
+ flatLayers: flatVegetationLayers.map((layer) => String(layer.id))
14711
15515
  })
14712
15516
  } : {},
15517
+ ...vegetationDisplay ? { [tileflowVegetationDisplayMetadataKey]: vegetationDisplay } : {},
14713
15518
  "tileflow:data": data.identity,
14714
15519
  "tileflow:sources": sourceIdentities,
14715
15520
  ...Object.keys(loweredOverlays.identities).length > 0 ? { "tileflow:overlays": loweredOverlays.identities } : {},
@@ -15090,12 +15895,24 @@ function createTileflowRuntimeManifestSchema(primitives, operations, identities
15090
15895
  })
15091
15896
  ),
15092
15897
  themes: operations.record(tileflowThemeNameSchema2, manifestThemeSchema),
15898
+ timeOfDay: operations.optional(
15899
+ createTileflowTimeOfDaySchema(operations, tileflowThemeNameSchema2)
15900
+ ),
15093
15901
  usageMode: operations.optional(operations.literal("session")),
15094
15902
  view: operations.optional(viewSchema3),
15095
15903
  worldGeneration: operations.optional(operations.literal("v1"))
15096
15904
  }),
15097
15905
  (entry, context) => {
15098
15906
  const names = Object.keys(entry.themes);
15907
+ if (entry.timeOfDay) {
15908
+ for (const issue of validateTileflowTimeOfDayVariants(entry.timeOfDay, entry.themes)) {
15909
+ context.addIssue({
15910
+ code: "custom",
15911
+ message: issue.message,
15912
+ path: ["timeOfDay", ...issue.path]
15913
+ });
15914
+ }
15915
+ }
15099
15916
  if (names.length === 0) {
15100
15917
  context.addIssue({
15101
15918
  code: "custom",
@@ -15149,10 +15966,23 @@ function createTileflowRuntimeManifestSchema(primitives, operations, identities
15149
15966
  operations.object({
15150
15967
  apiUrl: operations.optional(apiOriginSchema),
15151
15968
  maps: operations.record(tileflowPortableIdSchema2, manifestMapSchema),
15152
- version: operations.literal(tileflowRuntimeManifestVersion)
15969
+ version: operations.union(
15970
+ operations.literal(tileflowRuntimeManifestVersion),
15971
+ operations.literal(2)
15972
+ )
15153
15973
  }),
15154
15974
  (manifest, context) => {
15155
15975
  const mapCount = Object.keys(manifest.maps).length;
15976
+ if (manifest.version === 1) {
15977
+ for (const [name, map] of Object.entries(manifest.maps)) {
15978
+ if (map.timeOfDay !== void 0)
15979
+ context.addIssue({
15980
+ code: "custom",
15981
+ message: "timeOfDay requires manifest version 2",
15982
+ path: ["maps", name, "timeOfDay"]
15983
+ });
15984
+ }
15985
+ }
15156
15986
  if (mapCount === 0) {
15157
15987
  context.addIssue({ code: "custom", message: "Expected at least one map", path: ["maps"] });
15158
15988
  }
@@ -15558,6 +16388,7 @@ var tileflowMapMergeStrategies = {
15558
16388
  systemThemes: "atomic",
15559
16389
  terrain: "atomic",
15560
16390
  themes: "atomic",
16391
+ timeOfDay: "atomic",
15561
16392
  version: "identity",
15562
16393
  view: "deep"
15563
16394
  };
@@ -15572,7 +16403,10 @@ function resolveMap(map, options = {}) {
15572
16403
  for (let index = lineage.length - 1; index >= 0; index -= 1) {
15573
16404
  const current = lineage[index];
15574
16405
  const currentId = current.id;
15575
- if (current.themes !== void 0) delete design.systemThemes;
16406
+ if (current.themes !== void 0) {
16407
+ delete design.systemThemes;
16408
+ delete design.timeOfDay;
16409
+ }
15576
16410
  for (const key of Object.keys(current)) {
15577
16411
  const value = current[key];
15578
16412
  const strategy = tileflowMapMergeStrategies[key];
@@ -16350,6 +17184,7 @@ async function createTileflowMapBuildManifest(maps, options = {}) {
16350
17184
  semanticCompiler: { ...tileflowSemanticCompilerIdentity },
16351
17185
  sourceAssets,
16352
17186
  ...map.systemThemes ? { systemThemes: { ...map.systemThemes } } : {},
17187
+ ...map.timeOfDay && Object.keys(map.timeOfDay).length ? { timeOfDay: map.timeOfDay } : {},
16353
17188
  themes
16354
17189
  }
16355
17190
  ];
@@ -16513,8 +17348,8 @@ function normalizeSourceAssets(input) {
16513
17348
  }
16514
17349
  const models = [...input.models ?? []].map((model) => {
16515
17350
  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.`);
17351
+ if (model.level !== "near" && model.level !== "mid" && model.level !== "far")
17352
+ throw new Error(`Tileflow model ${model.id} is near, mid or far.`);
16518
17353
  return { id: model.id, level: model.level, sha256: model.sha256 };
16519
17354
  }).sort(
16520
17355
  (left, right) => compareCodeUnits8(`${left.id}\0${left.level}`, `${right.id}\0${right.level}`)
@@ -25296,7 +26131,7 @@ function createNativeUrlPolicy(parse) {
25296
26131
  const resourcePath = resolved.href.slice(resolved.origin.length);
25297
26132
  const suffix = resourcePath.replace(
25298
26133
  new RegExp(`${prefix}(\\d+)__`, "gu"),
25299
- (match2, index) => placeholders[Number(index)] ?? match2
26134
+ (match3, index) => placeholders[Number(index)] ?? match3
25300
26135
  );
25301
26136
  const result = resolved.origin + suffix;
25302
26137
  assertUrlText(result, "resourceUrl");
@@ -25314,8 +26149,8 @@ function createNativeUrlPolicy(parse) {
25314
26149
  if (value === void 0) return void 0;
25315
26150
  try {
25316
26151
  assertUrlText(value, "developmentOrigin");
25317
- const match2 = /^(http:\/\/[^/?#]+)\/?$/iu.exec(value);
25318
- if (!match2 || /[{}@*]/u.test(value)) throw new Error();
26152
+ const match3 = /^(http:\/\/[^/?#]+)\/?$/iu.exec(value);
26153
+ if (!match3 || /[{}@*]/u.test(value)) throw new Error();
25319
26154
  const url = parseUrl(value, "developmentOrigin");
25320
26155
  if (url.protocol !== "http:" || url.pathname !== "/" || url.search || url.hash || url.hostname.includes("*")) {
25321
26156
  throw new Error();
@@ -25726,11 +26561,11 @@ function parserIssuePointer(key, style) {
25726
26561
  let path = "";
25727
26562
  while (rest) {
25728
26563
  if (Array.isArray(value)) {
25729
- const match2 = /^\[(\d+)\]/u.exec(rest);
25730
- if (!match2 || Number(match2[1]) >= value.length) return path;
25731
- path = nativePointer(path, match2[1]);
25732
- value = value[Number(match2[1])];
25733
- rest = rest.slice(match2[0].length).replace(/^\./u, "");
26564
+ const match3 = /^\[(\d+)\]/u.exec(rest);
26565
+ if (!match3 || Number(match3[1]) >= value.length) return path;
26566
+ path = nativePointer(path, match3[1]);
26567
+ value = value[Number(match3[1])];
26568
+ rest = rest.slice(match3[0].length).replace(/^\./u, "");
25734
26569
  } else if (record2(value)) {
25735
26570
  const matches = Object.keys(value).filter(
25736
26571
  (name2) => rest === name2 || rest.startsWith(`${name2}.`) || rest.startsWith(`${name2}[`)
@@ -25823,7 +26658,11 @@ var mapBuildSchema = z15.object({
25823
26658
  semanticCompiler: z15.object({ name: z15.literal("tileflow-semantic"), version: z15.literal(1) }).strict(),
25824
26659
  sourceAssets: sourceAssetsSchema,
25825
26660
  systemThemes: z15.object({ dark: tileflowThemeNameSchema, light: tileflowThemeNameSchema }).strict().optional(),
25826
- themes: z15.record(tileflowThemeNameSchema, themeBuildSchema)
26661
+ themes: z15.record(tileflowThemeNameSchema, themeBuildSchema),
26662
+ timeOfDay: createTileflowTimeOfDaySchema(
26663
+ classicManifestSchemaOperations,
26664
+ tileflowThemeNameSchema
26665
+ ).optional()
25827
26666
  }).strict();
25828
26667
  var buildManifestSchema = z15.object({
25829
26668
  schemaVersion: z15.literal(1),
@@ -25885,6 +26724,8 @@ var tileflowRendererDeploymentArtifactSchema = z15.object({
25885
26724
  const themes = Object.keys(map.themes).sort();
25886
26725
  if (!themes.length || themes.length > tileflowRendererDeploymentLimits.maximumThemes || !equal(themes, Object.keys(family.styles).sort()) || !themes.includes(map.defaultTheme) || map.lineage[0]?.id !== artifact.mapId || map.lineage[0]?.mapVersion !== map.mapVersion || Boolean(map.sourceAssets.iconComposition) !== (map.mapRevisionSchemaVersion === 2))
25887
26726
  fail();
26727
+ if (map.timeOfDay && validateTileflowTimeOfDayVariants(map.timeOfDay, map.themes).length)
26728
+ fail();
25888
26729
  for (const scheme of ["light", "dark"]) {
25889
26730
  if (map.systemThemes && map.themes[map.systemThemes[scheme]]?.colorScheme !== scheme)
25890
26731
  fail();
@@ -26000,7 +26841,7 @@ var artifactName = z15.string().min(1).max(256).refine(
26000
26841
  })
26001
26842
  );
26002
26843
  var semanticSchema = z15.object({
26003
- version: z15.literal(2),
26844
+ version: z15.literal(3),
26004
26845
  domains: z15.object({
26005
26846
  poi: z15.object({
26006
26847
  deduplication: z15.object({
@@ -26032,7 +26873,6 @@ var semanticSchema = z15.object({
26032
26873
  layers: z15.array(
26033
26874
  z15.object({
26034
26875
  anchor: z15.literal("pointer-coordinate"),
26035
- category: z15.enum(tileflowPoiCategories),
26036
26876
  layerId: artifactName,
26037
26877
  priority: z15.number().int().min(0).max(4095),
26038
26878
  representation: z15.enum(["combined", "icon", "label", "marker"]),
@@ -26204,6 +27044,7 @@ function createManifest(catalog, options = {}) {
26204
27044
  defaultTheme: resolved.defaultTheme,
26205
27045
  ...resolved.systemThemes ? { systemThemes: resolved.systemThemes } : {},
26206
27046
  themes,
27047
+ ...options.manifestVersion !== 1 && resolved.timeOfDay && Object.keys(resolved.timeOfDay).length ? { timeOfDay: resolved.timeOfDay } : {},
26207
27048
  ...resolved.view ? { view: resolved.view } : {}
26208
27049
  }
26209
27050
  ];
@@ -26211,7 +27052,7 @@ function createManifest(catalog, options = {}) {
26211
27052
  );
26212
27053
  const manifest = {
26213
27054
  maps,
26214
- version: tileflowRuntimeManifestVersion
27055
+ version: options.manifestVersion ?? (Object.values(maps).some((map) => map.timeOfDay) ? 2 : tileflowRuntimeManifestVersion)
26215
27056
  };
26216
27057
  return parseTileflowRuntimeManifest(manifest);
26217
27058
  }