@tileflow/core 0.1.0-alpha.37 → 0.1.0-alpha.39

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.
Files changed (37) hide show
  1. package/README.md +10 -10
  2. package/dist/3d-BkaJjwfk.d.ts +1083 -0
  3. package/dist/3d.d.ts +1 -0
  4. package/dist/3d.js +12331 -0
  5. package/dist/browser.d.ts +788 -1409
  6. package/dist/browser.js +11457 -9407
  7. package/dist/build.d.ts +4 -3
  8. package/dist/build.js +1135 -203
  9. package/dist/capture.js +4 -1
  10. package/dist/config-reference.json +927 -548
  11. package/dist/{icon-composition-C5X3Q9Ya.d.ts → icon-composition-Ivgoxz8d.d.ts} +172 -5
  12. package/dist/index.d.ts +174 -5
  13. package/dist/index.js +1797 -330
  14. package/dist/{manifest-types-jrBdBrES.d.ts → manifest-types-DRy2980g.d.ts} +5 -2
  15. package/dist/manifest.d.ts +2 -2
  16. package/dist/manifest.js +4 -1
  17. package/dist/native-profile.d.ts +4 -0
  18. package/dist/native-profile.js +2 -0
  19. package/dist/native.js +11 -1
  20. package/dist/runtime.d.ts +1 -1
  21. package/dist/runtime.js +11 -1
  22. package/dist/worker/camera-tile-worker.global.js +20 -0
  23. package/docs/application-tile-reading.md +5 -2
  24. package/docs/authoring-model.md +8 -0
  25. package/docs/buildings-3d.md +80 -12
  26. package/docs/compiled-style-performance.md +11 -2
  27. package/docs/data-is-separate-from-design.md +6 -3
  28. package/docs/globe-atmosphere.md +3 -0
  29. package/docs/native-artifact-profile.md +2 -2
  30. package/docs/nautical-marks.md +147 -39
  31. package/docs/public-api-and-browser-subpath.md +7 -2
  32. package/docs/street-lights.md +5 -1
  33. package/docs/street-sky.md +153 -0
  34. package/docs/sun-and-light.md +4 -2
  35. package/docs/trees-3d.md +11 -6
  36. package/package.json +6 -1
  37. package/dist/worker/buildings-3d-worker.global.js +0 -1
package/dist/build.js CHANGED
@@ -4,6 +4,9 @@ import { validateStyleMin } from "@maplibre/maplibre-gl-style-spec";
4
4
  // src/atmosphere.ts
5
5
  var tileflowAtmosphereMetadataKey = "tileflow:atmosphere";
6
6
 
7
+ // src/sky.ts
8
+ var tileflowSkyMetadataKey = "tileflow:sky";
9
+
7
10
  // src/atmosphere-compile.ts
8
11
  import { Color } from "@maplibre/maplibre-gl-style-spec";
9
12
  var defaults = {
@@ -17,14 +20,7 @@ var defaults = {
17
20
  function resolveTileflowAtmosphere(value) {
18
21
  if (!value) return void 0;
19
22
  const options = { ...defaults, ...value === true ? {} : value };
20
- const normalize = (input) => {
21
- const color2 = typeof input === "string" ? Color.parse(input) : void 0;
22
- if (!color2 || color2.a !== 1)
23
- throw new Error("Atmosphere colors must resolve to opaque colors.");
24
- return `#${[color2.r, color2.g, color2.b].map(
25
- (channel) => Math.round(channel * 255).toString(16).padStart(2, "0")
26
- ).join("")}`;
27
- };
23
+ const normalize = (input) => opaqueHex(input, "Atmosphere colors");
28
24
  return {
29
25
  ...options,
30
26
  skyColor: normalize(options.skyColor),
@@ -33,6 +29,13 @@ function resolveTileflowAtmosphere(value) {
33
29
  spaceColor: normalize(options.spaceColor)
34
30
  };
35
31
  }
32
+ function opaqueHex(input, subject) {
33
+ const color2 = typeof input === "string" ? Color.parse(input) : void 0;
34
+ if (!color2 || color2.a !== 1) throw new Error(`${subject} must resolve to opaque colors.`);
35
+ return `#${[color2.r, color2.g, color2.b].map(
36
+ (channel) => Math.round(channel * 255).toString(16).padStart(2, "0")
37
+ ).join("")}`;
38
+ }
36
39
  function compileTileflowSky(options) {
37
40
  return {
38
41
  "sky-color": options.skyColor,
@@ -44,6 +47,41 @@ function compileTileflowSky(options) {
44
47
  "atmosphere-blend": ["interpolate", ["linear"], ["zoom"], 0, 0.55, 3, 0.45, 5, 0.2, 7, 0]
45
48
  };
46
49
  }
50
+ var skyDefaults = {
51
+ color: "#5c9ce6",
52
+ horizonColor: "#dcebf7",
53
+ horizonBlend: 0.6,
54
+ glowColor: "#ffc68a",
55
+ starIntensity: 0.6
56
+ };
57
+ function resolveTileflowSky(value) {
58
+ if (!value) return void 0;
59
+ const options = { ...skyDefaults, ...value === true ? {} : value };
60
+ const unit = (input, name) => {
61
+ if (typeof input !== "number" || !Number.isFinite(input) || input < 0 || input > 1)
62
+ throw new Error(`Sky ${name} must resolve to a number from 0 to 1.`);
63
+ return input;
64
+ };
65
+ return {
66
+ color: opaqueHex(options.color, "Sky colors"),
67
+ horizonColor: opaqueHex(options.horizonColor, "Sky colors"),
68
+ horizonBlend: unit(options.horizonBlend, "horizonBlend"),
69
+ glowColor: opaqueHex(options.glowColor, "Sky colors"),
70
+ starIntensity: unit(options.starIntensity, "starIntensity")
71
+ };
72
+ }
73
+ function compileTileflowStreetSky(options) {
74
+ return {
75
+ "sky-color": options.color,
76
+ "horizon-color": options.horizonColor,
77
+ // MapLibre fogs only 3D terrain; the haze matches the horizon so terrain meets the sky.
78
+ "fog-color": options.horizonColor,
79
+ "sky-horizon-blend": options.horizonBlend,
80
+ "horizon-fog-blend": 0.6,
81
+ "fog-ground-blend": 0.75,
82
+ "atmosphere-blend": ["interpolate", ["linear"], ["zoom"], 0, 0.55, 3, 0.45, 5, 0.2, 7, 0]
83
+ };
84
+ }
47
85
 
48
86
  // src/models-3d-model.ts
49
87
  var tileflowModelParts = Object.freeze({
@@ -171,6 +209,16 @@ var tileflowDefaultBuildings3DDesign = Object.freeze({
171
209
  function tileflowBuildingsMetadata(design) {
172
210
  return { version: 1, ...design };
173
211
  }
212
+ function readTileflowBuildingsEntrance(value) {
213
+ if (!isRecord(value)) return {};
214
+ const band = (entry) => Array.isArray(entry) && entry.length === 2 && entry.every((zoom3) => typeof zoom3 === "number" && zoom3 >= 0 && zoom3 <= 24) && entry[0] < entry[1] ? [entry[0], entry[1]] : void 0;
215
+ const buildings2 = band(value.buildings);
216
+ const models = band(value.models);
217
+ return buildings2 || models ? { entrance: { ...buildings2 ? { buildings: buildings2 } : {}, ...models ? { models } : {} } } : {};
218
+ }
219
+ function isRecord(value) {
220
+ return typeof value === "object" && value !== null && !Array.isArray(value);
221
+ }
174
222
 
175
223
  // src/data/world-generation.ts
176
224
  var tileflowWorldGeneration = "v1";
@@ -335,6 +383,7 @@ function tileflowWorldV1Schema(options = {}) {
335
383
  sidewalk: "sidewalk",
336
384
  streetFurniture: "street_furniture",
337
385
  trafficControl: "traffic_control",
386
+ transportationOutline: "transportation_outline",
338
387
  ...options.layers
339
388
  },
340
389
  semantics: { ...options.semantics, parkLayer: "protected-only" }
@@ -1109,26 +1158,26 @@ function toMapLibreStyleValue(value) {
1109
1158
  return cloneJson2(value);
1110
1159
  }
1111
1160
  function isTileflowExpression(value) {
1112
- return isRecord(value) && value.kind === "expression" && Array.isArray(value.value);
1161
+ return isRecord2(value) && value.kind === "expression" && Array.isArray(value.value);
1113
1162
  }
1114
1163
  function isTileflowZoomValue(value) {
1115
- return isRecord(value) && value.kind === "zoom" && Array.isArray(value.stops);
1164
+ return isRecord2(value) && value.kind === "zoom" && Array.isArray(value.stops);
1116
1165
  }
1117
1166
  function isTileflowThemeTokenReference(value) {
1118
- return isRecord(value) && hasExactOwnKeys(value, ["category", "kind", "token"]) && value.kind === "theme-token" && typeof value.token === "string" && /^[A-Za-z][A-Za-z0-9_-]*(?:\.[A-Za-z][A-Za-z0-9_-]*)*$/.test(value.token) && tileflowThemeTokenCategories.includes(value.category);
1167
+ return isRecord2(value) && hasExactOwnKeys(value, ["category", "kind", "token"]) && value.kind === "theme-token" && typeof value.token === "string" && /^[A-Za-z][A-Za-z0-9_-]*(?:\.[A-Za-z][A-Za-z0-9_-]*)*$/.test(value.token) && tileflowThemeTokenCategories.includes(value.category);
1119
1168
  }
1120
1169
  function isTileflowFixedValue(value) {
1121
- return isRecord(value) && hasExactOwnKeys(value, ["kind", "reason", "value"]) && value.kind === "theme-fixed" && typeof value.reason === "string" && Boolean(value.reason.trim()) && Object.hasOwn(value, "value");
1170
+ return isRecord2(value) && hasExactOwnKeys(value, ["kind", "reason", "value"]) && value.kind === "theme-fixed" && typeof value.reason === "string" && Boolean(value.reason.trim()) && Object.hasOwn(value, "value");
1122
1171
  }
1123
1172
  function isTileflowThemeColorOperation(value) {
1124
- if (!isRecord(value) || value.kind !== "theme-color") return false;
1173
+ if (!isRecord2(value) || value.kind !== "theme-color") return false;
1125
1174
  if (value.operation === "alpha") {
1126
1175
  return hasExactOwnKeys(value, ["color", "kind", "opacity", "operation"]) && Object.hasOwn(value, "color") && Object.hasOwn(value, "opacity");
1127
1176
  }
1128
1177
  return value.operation === "mix" && hasExactOwnKeys(value, ["amount", "from", "kind", "operation", "space", "to"]) && value.space === "oklch" && Object.hasOwn(value, "amount") && Object.hasOwn(value, "from") && Object.hasOwn(value, "to");
1129
1178
  }
1130
1179
  function isTileflowThemeValueNode(value) {
1131
- return isRecord(value) && (value.kind === "theme-token" || value.kind === "theme-fixed" || value.kind === "theme-color");
1180
+ return isRecord2(value) && (value.kind === "theme-token" || value.kind === "theme-fixed" || value.kind === "theme-color");
1132
1181
  }
1133
1182
  function createZoomValue(interpolation, stops, base) {
1134
1183
  if (stops.length === 0) {
@@ -1168,7 +1217,7 @@ function cloneJson2(value) {
1168
1217
  }
1169
1218
  return JSON.parse(serialized);
1170
1219
  }
1171
- function isRecord(value) {
1220
+ function isRecord2(value) {
1172
1221
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
1173
1222
  }
1174
1223
  function hasExactOwnKeys(value, expectedKeys) {
@@ -1201,7 +1250,7 @@ function applyLineStyle(layer, style) {
1201
1250
  const layout = { ...asRecord(layer.layout) };
1202
1251
  setPaint(paint, "line-blur", style.blur);
1203
1252
  setPaint(paint, "line-color", style.color);
1204
- setPaint(paint, "line-dasharray", style.dash);
1253
+ if (!isSolidDash(style.dash)) setPaint(paint, "line-dasharray", style.dash);
1205
1254
  setPaint(paint, "line-gap-width", style.gapWidth);
1206
1255
  setPaint(paint, "line-offset", style.offset);
1207
1256
  setPaint(paint, "line-opacity", style.opacity);
@@ -1445,6 +1494,12 @@ function withoutGovernedAllowOverlap(layout) {
1445
1494
  }
1446
1495
  return result;
1447
1496
  }
1497
+ function isSolidDash(value) {
1498
+ if (value === void 0) return false;
1499
+ const dash = toMapLibreStyleValue(value);
1500
+ const array = Array.isArray(dash) && dash[0] === "literal" ? dash[1] : dash;
1501
+ return Array.isArray(array) && array.length === 2 && array[0] === 1 && array[1] === 0;
1502
+ }
1448
1503
  function setPaint(paint, property, value) {
1449
1504
  if (value !== void 0) paint[property] = toMapLibreStyleValue(value);
1450
1505
  }
@@ -1489,10 +1544,10 @@ function mergeTileflowDesign(base, ...overlays) {
1489
1544
  }
1490
1545
  function mergeValue(base, overlay) {
1491
1546
  if (overlay === void 0) return cloneJson3(base);
1492
- if (Array.isArray(overlay) || isTileflowExpression(overlay) || isTileflowThemeValueNode(overlay) || isTileflowZoomValue(overlay) || !isRecord2(overlay)) {
1547
+ if (Array.isArray(overlay) || isTileflowExpression(overlay) || isTileflowThemeValueNode(overlay) || isTileflowZoomValue(overlay) || !isRecord3(overlay)) {
1493
1548
  return cloneJson3(overlay);
1494
1549
  }
1495
- const baseRecord = isRecord2(base) ? base : {};
1550
+ const baseRecord = isRecord3(base) ? base : {};
1496
1551
  const result = {};
1497
1552
  for (const key of /* @__PURE__ */ new Set([...Object.keys(baseRecord), ...Object.keys(overlay)])) {
1498
1553
  const overlayValue = overlay[key];
@@ -1508,7 +1563,7 @@ function cloneJson3(value) {
1508
1563
  if (value === void 0) return value;
1509
1564
  return JSON.parse(JSON.stringify(value));
1510
1565
  }
1511
- function isRecord2(value) {
1566
+ function isRecord3(value) {
1512
1567
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
1513
1568
  }
1514
1569
 
@@ -1967,6 +2022,21 @@ function compileBuildings(request, context) {
1967
2022
  }
1968
2023
  return contributions;
1969
2024
  }
2025
+ var buildingTargetsByMode = {
2026
+ flat: ["buildings.flat.fill", "buildings.flat.outline"],
2027
+ "3d": ["buildings.extrusion"]
2028
+ };
2029
+ function buildingTargetsOfOtherModes(request) {
2030
+ const activeMode = request?.mode ?? "flat";
2031
+ const targets = /* @__PURE__ */ new Map();
2032
+ for (const [mode, modeTargets] of Object.entries(buildingTargetsByMode)) {
2033
+ if (mode === activeMode) continue;
2034
+ for (const target of modeTargets) {
2035
+ targets.set(target, { activeMode, mode, owner: "buildings" });
2036
+ }
2037
+ }
2038
+ return targets;
2039
+ }
1970
2040
  function buildingColorExpression(context) {
1971
2041
  const fields2 = context.data.schema.fields;
1972
2042
  const colors = context.colors.buildings;
@@ -2084,29 +2154,23 @@ function isTileflowPathRoadClass(roadClass) {
2084
2154
  return tileflowPathRoadClasses.includes(roadClass);
2085
2155
  }
2086
2156
  function tileflowRoadClassFilter(fields2, roadClass) {
2157
+ const classes = tileflowRoadClassValues(roadClass);
2087
2158
  if (isTileflowPathRoadClass(roadClass)) {
2088
2159
  return [
2089
2160
  "all",
2090
- ["match", ["get", fields2.class], ["path", ...constructionClasses[roadClass]], true, false],
2161
+ ["match", ["get", fields2.class], classes, true, false],
2091
2162
  ["match", ["get", fields2.subclass], pathSubclasses[roadClass], true, false]
2092
2163
  ];
2093
2164
  }
2094
- const classes = roadClass === "minor" ? [
2095
- "minor",
2096
- "residential",
2097
- "unclassified",
2098
- ...compatibleRoadClasses[roadClass] ?? [],
2099
- ...constructionClasses[roadClass]
2100
- ] : roadClass === "service" ? [
2101
- "service",
2102
- ...compatibleRoadClasses[roadClass] ?? [],
2103
- ...constructionClasses[roadClass]
2104
- ] : [
2105
- roadClass,
2165
+ return ["match", ["get", fields2.class], classes, true, false];
2166
+ }
2167
+ function tileflowRoadClassValues(roadClass) {
2168
+ if (isTileflowPathRoadClass(roadClass)) return ["path", ...constructionClasses[roadClass]];
2169
+ return [
2170
+ ...roadClass === "minor" ? ["minor", "residential", "unclassified"] : [roadClass],
2106
2171
  ...compatibleRoadClasses[roadClass] ?? [],
2107
2172
  ...constructionClasses[roadClass]
2108
2173
  ];
2109
- return ["match", ["get", fields2.class], classes, true, false];
2110
2174
  }
2111
2175
 
2112
2176
  // src/modules/roads/index.ts
@@ -3403,12 +3467,46 @@ var tileflowNauticalImageRoles = Object.freeze({
3403
3467
  port: "nautical.buoy.port",
3404
3468
  starboard: "nautical.buoy.starboard"
3405
3469
  }),
3406
- harbours: Object.freeze({ default: "nautical.harbour" }),
3470
+ distanceMarks: Object.freeze({ default: "nautical.distance-mark" }),
3471
+ /**
3472
+ * A facility that lists several categories shows the first of `tileflowNauticalFacilityIcons`
3473
+ * it lists; facilities without a variant token fall back to `nautical.facility`.
3474
+ */
3475
+ facilities: Object.freeze({
3476
+ "boat-hoist": "nautical.facility.boat-hoist",
3477
+ boatyard: "nautical.facility.boatyard",
3478
+ default: "nautical.facility",
3479
+ "fuel-station": "nautical.facility.fuel-station",
3480
+ "pump-out": "nautical.facility.pump-out",
3481
+ slipway: "nautical.facility.slipway",
3482
+ "visitor-berth": "nautical.facility.visitor-berth",
3483
+ "water-tap": "nautical.facility.water-tap"
3484
+ }),
3485
+ /** Harbours whose `subclass` lists a marina use `nautical.marina` when the theme declares it. */
3486
+ harbours: Object.freeze({ default: "nautical.harbour", marina: "nautical.marina" }),
3407
3487
  lighthouses: Object.freeze({ default: "nautical.lighthouse" }),
3408
3488
  lights: Object.freeze({ default: "nautical.light" }),
3489
+ /** Notice boards by function; a board without a variant token uses `nautical.notice`. */
3490
+ notices: Object.freeze({
3491
+ default: "nautical.notice",
3492
+ information: "nautical.notice.information",
3493
+ prohibition: "nautical.notice.prohibition",
3494
+ recommendation: "nautical.notice.recommendation",
3495
+ regulation: "nautical.notice.regulation",
3496
+ restriction: "nautical.notice.restriction"
3497
+ }),
3409
3498
  ports: Object.freeze({ default: "nautical.port" }),
3410
3499
  wrecks: Object.freeze({ default: "nautical.wreck" })
3411
3500
  });
3501
+ var tileflowNauticalFacilityIcons = Object.freeze([
3502
+ "fuel-station",
3503
+ "slipway",
3504
+ "boat-hoist",
3505
+ "pump-out",
3506
+ "water-tap",
3507
+ "visitor-berth",
3508
+ "boatyard"
3509
+ ]);
3412
3510
  var tileflowNauticalNotice = Object.freeze({
3413
3511
  id: "nautical-not-for-navigation",
3414
3512
  text: "Not for navigation"
@@ -3467,10 +3565,15 @@ var tileflowNauticalV1Schema = Object.freeze({
3467
3565
  country: "country",
3468
3566
  coverage: "coverage",
3469
3567
  depth: "depth",
3568
+ distance: "distance",
3569
+ distanceUnit: "distance_unit",
3470
3570
  edition: "edition",
3471
3571
  families: "families",
3472
3572
  group: "group",
3473
3573
  heightM: "height_m",
3574
+ horizontalClearance: "horizontal_clearance",
3575
+ impact: "impact",
3576
+ information: "information",
3474
3577
  label: "label",
3475
3578
  licence: "licence",
3476
3579
  lit: "lit",
@@ -3488,6 +3591,7 @@ var tileflowNauticalV1Schema = Object.freeze({
3488
3591
  scale: "scale",
3489
3592
  sectorEnd: "sector_end",
3490
3593
  sectorStart: "sector_start",
3594
+ sequence: "sequence",
3491
3595
  shape: "shape",
3492
3596
  size: "size",
3493
3597
  subclass: "subclass",
@@ -3495,6 +3599,10 @@ var tileflowNauticalV1Schema = Object.freeze({
3495
3599
  topmark: "topmark",
3496
3600
  unlocode: "unlocode",
3497
3601
  update: "update",
3602
+ verticalClearance: "vertical_clearance",
3603
+ verticalClearanceClosed: "vertical_clearance_closed",
3604
+ verticalClearanceOpen: "vertical_clearance_open",
3605
+ verticalClearanceSafe: "vertical_clearance_safe",
3498
3606
  waterLevel: "water_level"
3499
3607
  }),
3500
3608
  geometryTypes: Object.freeze({
@@ -3518,6 +3626,16 @@ var tileflowNauticalV1Schema = Object.freeze({
3518
3626
  wrecks: "wreck"
3519
3627
  }),
3520
3628
  product: "nautical-v1",
3629
+ /**
3630
+ * Known `provider` values and the `class` of their `coverage` cells. The source's TileJSON owns
3631
+ * each provider's credit; the module never adds attribution.
3632
+ */
3633
+ providers: Object.freeze({
3634
+ "nga-list-of-lights": "list",
3635
+ "noaa-office-of-coast-survey": "chart",
3636
+ openstreetmap: "community",
3637
+ "usace-ienc": "chart"
3638
+ }),
3521
3639
  schemaVersion: 1
3522
3640
  });
3523
3641
  var tileflowBathymetryNameLayerLimits = Object.freeze({ maxZoomRanges: 128 });
@@ -3602,8 +3720,11 @@ function resolveBathymetryRelief(style, apiBaseUrl) {
3602
3720
  style: cloneJson12(style)
3603
3721
  };
3604
3722
  }
3723
+ var tileflowNauticalPromoteId = Object.freeze({
3724
+ [tileflowNauticalV1Schema.layers.lights]: tileflowNauticalV1Schema.fields.provenance
3725
+ });
3605
3726
  function resolveNauticalSource(config, apiBaseUrl) {
3606
- return resolveMarineSource({
3727
+ const resolved = resolveMarineSource({
3607
3728
  config,
3608
3729
  defaultPath: "/tiles/nautical/tiles.json",
3609
3730
  defaultSourceId: tileflowNauticalSourceId,
@@ -3611,6 +3732,7 @@ function resolveNauticalSource(config, apiBaseUrl) {
3611
3732
  product: tileflowNauticalV1Schema.product,
3612
3733
  apiBaseUrl
3613
3734
  });
3735
+ return { ...resolved, source: { ...resolved.source, promoteId: { ...tileflowNauticalPromoteId } } };
3614
3736
  }
3615
3737
  function resolveMarineSource(options) {
3616
3738
  const sourceId = validateSourceId(options.config.sourceId ?? options.defaultSourceId);
@@ -3672,6 +3794,42 @@ function cloneJson12(value) {
3672
3794
  return JSON.parse(JSON.stringify(value));
3673
3795
  }
3674
3796
 
3797
+ // src/nautical-lights.ts
3798
+ var tileflowNauticalLightsMetadataKey = "tileflow:nautical-lights";
3799
+ var tileflowNauticalLightStates = Object.freeze({
3800
+ /** Index of the colour shown, into the light's comma-separated `color` list. */
3801
+ colour: "tileflowLightColour",
3802
+ /** `1` while the light shines, `0` while it is eclipsed, in between for a fixed part. */
3803
+ intensity: "tileflowLightIntensity"
3804
+ });
3805
+ var tileflowNauticalLightEclipseOpacity = 0.15;
3806
+ var tileflowNauticalLightsLimits = Object.freeze({ maxLayers: 16 });
3807
+ function declareTileflowNauticalLights(layers2, sourceId, sourceLayer) {
3808
+ if (!sourceId) return void 0;
3809
+ const ids = layers2.filter(
3810
+ (layer) => layer.source === sourceId && layer["source-layer"] === sourceLayer && readsLightState(layer.paint)
3811
+ ).map(({ id: id2 }) => id2).filter((id2) => typeof id2 === "string").slice(0, tileflowNauticalLightsLimits.maxLayers);
3812
+ if (ids.length === 0) return void 0;
3813
+ return { layers: ids, source: sourceId, sourceLayer, version: 1 };
3814
+ }
3815
+ function tileflowNauticalLightStateRead(state, steadyValue) {
3816
+ return ["number", ["feature-state", tileflowNauticalLightStates[state]], steadyValue];
3817
+ }
3818
+ var stateKeys = new Set(Object.values(tileflowNauticalLightStates));
3819
+ function readsLightState(value) {
3820
+ if (!Array.isArray(value)) {
3821
+ return isRecord4(value) ? Object.values(value).some(readsLightState) : false;
3822
+ }
3823
+ if (value[0] === "literal") return false;
3824
+ if (value[0] === "feature-state" && typeof value[1] === "string" && stateKeys.has(value[1])) {
3825
+ return true;
3826
+ }
3827
+ return value.some(readsLightState);
3828
+ }
3829
+ function isRecord4(value) {
3830
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
3831
+ }
3832
+
3675
3833
  // src/modules/nautical/compiler.ts
3676
3834
  var { fields, layers } = tileflowNauticalV1Schema;
3677
3835
  var markColours = Object.freeze({
@@ -3714,6 +3872,24 @@ var operationalAreaClasses = [
3714
3872
  var routeClasses = ["route", "traffic-separation"];
3715
3873
  var cableClasses = ["cable", "cable-area", "pipeline", "pipeline-area"];
3716
3874
  var portSizes = ["large", "medium", "small", "very-small"];
3875
+ var ownAreaClasses = ["berth", "bridge", "facility", "harbour"];
3876
+ var ownAidClasses = ["distance-mark", "notice"];
3877
+ var marinaSubclasses = ["marina", "marina-no-facilities"];
3878
+ var noticeFunctionColours = Object.freeze({
3879
+ information: markColours.blue,
3880
+ prohibition: markColours.red,
3881
+ recommendation: markColours.green,
3882
+ regulation: markColours.red,
3883
+ restriction: markColours.red
3884
+ });
3885
+ var distanceUnits = Object.freeze({
3886
+ hectometres: "hm",
3887
+ kilometres: "km",
3888
+ metres: "m",
3889
+ "nautical-miles": "NM",
3890
+ "statute-miles": "mi"
3891
+ });
3892
+ var maxAlternatingColours = 3;
3717
3893
  function compileNautical(request, context) {
3718
3894
  const nauticalSource = context.marine?.nautical;
3719
3895
  if (!nauticalSource || request?.enabled === false) return [];
@@ -3744,10 +3920,30 @@ function compileNautical(request, context) {
3744
3920
  const belowLabel = { anchor: "top", offset: [0, 0.75], optional: true };
3745
3921
  const markColour = expression(colourMatch(firstColour(fields.color), markColours, ink));
3746
3922
  const markStroke = expression(colourMatch(secondColour(fields.color), markColours, ink));
3923
+ const animateLights = request?.animateLights !== false;
3747
3924
  const flareColour = expression(
3748
- colourMatch(firstColour(fields.color), lightColours, lightColours.white)
3925
+ colourMatch(
3926
+ animateLights ? nthColour(fields.color, tileflowNauticalLightStateRead("colour", 0)) : firstColour(fields.color),
3927
+ lightColours,
3928
+ lightColours.white
3929
+ )
3749
3930
  );
3750
3931
  const iconPaint = { color: markColour, haloColor: ink, haloWidth: 1 };
3932
+ const noticeColours = [
3933
+ "match",
3934
+ ["get", fields.subclass],
3935
+ ...Object.entries(noticeFunctionColours).flat(),
3936
+ ink
3937
+ ];
3938
+ const noticeColour = expression(noticeColours);
3939
+ const distanceOpacity = (installed, notInstalled) => expression([
3940
+ "match",
3941
+ ["get", fields.category],
3942
+ "not-installed",
3943
+ notInstalled,
3944
+ installed
3945
+ ]);
3946
+ const isGuestBerth = listIncludes(fields.subclass, "guest");
3751
3947
  const defaults4 = {
3752
3948
  type: "nautical",
3753
3949
  enabled: true,
@@ -3838,6 +4034,60 @@ function compileNautical(request, context) {
3838
4034
  [14, 1.3]
3839
4035
  ])
3840
4036
  },
4037
+ harbourAreas: {
4038
+ fill: { color: ink, minZoom: 10, opacity: 0.05 },
4039
+ outline: {
4040
+ color: ink,
4041
+ minZoom: 10,
4042
+ opacity: zoom.linear([
4043
+ [10, 0.35],
4044
+ [14, 0.6]
4045
+ ]),
4046
+ width: zoom.linear([
4047
+ [10, 0.5],
4048
+ [16, 1]
4049
+ ])
4050
+ }
4051
+ },
4052
+ berths: {
4053
+ fill: { color: ink, minZoom: 13, opacity: 0.06 },
4054
+ outline: {
4055
+ color: ink,
4056
+ minZoom: 13,
4057
+ opacity: expression(["case", isGuestBerth, 0.95, 0.5]),
4058
+ width: expression([
4059
+ "interpolate",
4060
+ ["linear"],
4061
+ ["zoom"],
4062
+ 13,
4063
+ ["case", isGuestBerth, 1.2, 0.6],
4064
+ 16,
4065
+ ["case", isGuestBerth, 2.4, 1.2]
4066
+ ])
4067
+ }
4068
+ },
4069
+ bridges: {
4070
+ fill: { color: colors.land, minZoom: 12, opacity: 0.9 },
4071
+ outline: {
4072
+ color: ink,
4073
+ minZoom: 12,
4074
+ opacity: 0.85,
4075
+ width: zoom.linear([
4076
+ [12, 0.8],
4077
+ [16, 1.8]
4078
+ ])
4079
+ }
4080
+ },
4081
+ overheadCables: {
4082
+ color: accent,
4083
+ dash: [6, 2, 1, 2],
4084
+ minZoom: 10,
4085
+ opacity: 0.75,
4086
+ width: zoom.linear([
4087
+ [10, 0.7],
4088
+ [16, 1.3]
4089
+ ])
4090
+ },
3841
4091
  hazardAreas: {
3842
4092
  fill: { color: accent, minZoom: 10, opacity: 0.08 },
3843
4093
  outline: { color: accent, dash: [1, 1], minZoom: 10, opacity: 0.7, width: 0.8 }
@@ -3947,6 +4197,7 @@ function compileNautical(request, context) {
3947
4197
  harbours: {
3948
4198
  minZoom: 11,
3949
4199
  placement: "point",
4200
+ ...rankPriority,
3950
4201
  ...images.harbours ? {
3951
4202
  icon: {
3952
4203
  color: ink,
@@ -3997,6 +4248,147 @@ function compileNautical(request, context) {
3997
4248
  },
3998
4249
  text: { ...markText, ...sideLabel, field: nameFrom(15), padding: 3, size: 9 }
3999
4250
  },
4251
+ notices: {
4252
+ minZoom: 12,
4253
+ placement: "point",
4254
+ ...rankPriority,
4255
+ ...images.notices ? {
4256
+ icon: {
4257
+ color: noticeColour,
4258
+ haloColor: halo,
4259
+ haloWidth: 1,
4260
+ image: images.notices,
4261
+ padding: 1,
4262
+ size: zoom.linear([
4263
+ [12, 0.8],
4264
+ [16, 1.2]
4265
+ ])
4266
+ }
4267
+ } : {
4268
+ marker: {
4269
+ color: expression([
4270
+ "match",
4271
+ ["get", fields.subclass],
4272
+ ["prohibition", "recommendation", "information"],
4273
+ noticeColours,
4274
+ halo
4275
+ ]),
4276
+ radius: zoom.linear([
4277
+ [12, 1.8],
4278
+ [16, 3.2]
4279
+ ]),
4280
+ strokeColor: expression([
4281
+ "match",
4282
+ ["get", fields.subclass],
4283
+ ["prohibition", "recommendation", "information"],
4284
+ halo,
4285
+ noticeColours
4286
+ ]),
4287
+ strokeWidth: 1
4288
+ }
4289
+ },
4290
+ text: {
4291
+ ...markText,
4292
+ ...sideLabel,
4293
+ field: expression([
4294
+ "step",
4295
+ ["zoom"],
4296
+ "",
4297
+ 14,
4298
+ ["to-string", ["coalesce", ["get", fields.information], ""]]
4299
+ ]),
4300
+ padding: 2,
4301
+ size: 9
4302
+ }
4303
+ },
4304
+ distanceMarks: {
4305
+ minZoom: 11,
4306
+ placement: "point",
4307
+ ...rankPriority,
4308
+ ...images.distanceMarks ? {
4309
+ icon: {
4310
+ color: ink,
4311
+ haloColor: halo,
4312
+ haloWidth: 1,
4313
+ image: images.distanceMarks,
4314
+ opacity: distanceOpacity(1, 0.6),
4315
+ size: zoom.linear([
4316
+ [11, 0.7],
4317
+ [16, 1]
4318
+ ])
4319
+ }
4320
+ } : {
4321
+ marker: {
4322
+ color: ink,
4323
+ opacity: distanceOpacity(1, 0.6),
4324
+ radius: zoom.linear([
4325
+ [11, 1.2],
4326
+ [16, 2.2]
4327
+ ]),
4328
+ strokeColor: halo,
4329
+ strokeWidth: 0.8
4330
+ }
4331
+ },
4332
+ text: {
4333
+ ...markText,
4334
+ ...sideLabel,
4335
+ field: expression([
4336
+ "step",
4337
+ ["zoom"],
4338
+ "",
4339
+ 13,
4340
+ [
4341
+ "case",
4342
+ ["has", fields.distance],
4343
+ [
4344
+ "concat",
4345
+ ["to-string", ["get", fields.distance]],
4346
+ [
4347
+ "match",
4348
+ ["get", fields.distanceUnit],
4349
+ ...Object.entries(distanceUnits).flatMap(([unit, label]) => [unit, ` ${label}`]),
4350
+ ""
4351
+ ]
4352
+ ],
4353
+ ""
4354
+ ]
4355
+ ]),
4356
+ opacity: distanceOpacity(1, 0.75),
4357
+ padding: 2,
4358
+ size: zoom.linear([
4359
+ [13, 8.5],
4360
+ [16, 10]
4361
+ ])
4362
+ }
4363
+ },
4364
+ facilities: {
4365
+ minZoom: 13,
4366
+ placement: "point",
4367
+ ...images.facilities ? {
4368
+ icon: {
4369
+ color: ink,
4370
+ haloColor: halo,
4371
+ haloWidth: 1,
4372
+ image: images.facilities,
4373
+ padding: 1,
4374
+ size: zoom.linear([
4375
+ [13, 0.8],
4376
+ [16, 1.15]
4377
+ ])
4378
+ }
4379
+ } : {
4380
+ marker: {
4381
+ color: halo,
4382
+ radius: zoom.linear([
4383
+ [13, 1.8],
4384
+ [16, 3]
4385
+ ]),
4386
+ strokeColor: ink,
4387
+ strokeWidth: 1
4388
+ }
4389
+ },
4390
+ text: { ...markText, ...sideLabel, field: nameFrom(15), padding: 3, size: 9 }
4391
+ },
4000
4392
  beacons: {
4001
4393
  minZoom: 11,
4002
4394
  placement: "point",
@@ -4179,6 +4571,32 @@ function compileNautical(request, context) {
4179
4571
  }
4180
4572
  },
4181
4573
  labels: {
4574
+ bridges: {
4575
+ minZoom: 13,
4576
+ placement: "point",
4577
+ text: {
4578
+ ...markText,
4579
+ field: expression(bridgeClearance()),
4580
+ padding: 2,
4581
+ size: zoom.linear([
4582
+ [13, 8.5],
4583
+ [16, 10]
4584
+ ])
4585
+ }
4586
+ },
4587
+ overheadCables: {
4588
+ minZoom: 12,
4589
+ placement: "point",
4590
+ spacing: 360,
4591
+ text: {
4592
+ ...markText,
4593
+ color: accent,
4594
+ field: expression(cableClearance()),
4595
+ maxAngle: 30,
4596
+ padding: 2,
4597
+ size: 8.5
4598
+ }
4599
+ },
4182
4600
  coverage: {
4183
4601
  maxZoom: 9,
4184
4602
  minZoom: 4,
@@ -4228,6 +4646,7 @@ function compileNautical(request, context) {
4228
4646
  const source = nauticalSource.sourceId;
4229
4647
  const contributions = [];
4230
4648
  const add2 = (options) => appendNauticalLayer(contributions, { ...options, source });
4649
+ const ofClass = (value) => ["==", ["get", fields.class], value];
4231
4650
  add2({
4232
4651
  // Only coverage that speaks for every family; lists of lights alone would double the tint.
4233
4652
  filter: ["all", polygons, ["in", layers.aids, ["coalesce", ["get", fields.families], ""]]],
@@ -4238,13 +4657,30 @@ function compileNautical(request, context) {
4238
4657
  target: "nautical.coverage"
4239
4658
  });
4240
4659
  add2({
4241
- filter: polygons,
4660
+ filter: ["all", polygons, ["match", ["get", fields.class], [...ownAreaClasses], false, true]],
4242
4661
  id: "tileflow-nautical-navigation-areas",
4243
4662
  localOrder: 390,
4244
4663
  sourceLayer: layers.navigationAreas,
4245
4664
  style: config.navigationAreas,
4246
4665
  target: "nautical.navigationAreas"
4247
4666
  });
4667
+ add2({
4668
+ filter: ["all", polygons, ofClass("harbour")],
4669
+ id: "tileflow-nautical-harbour-areas",
4670
+ localOrder: 392,
4671
+ sourceLayer: layers.navigationAreas,
4672
+ style: config.harbourAreas,
4673
+ target: "nautical.harbourAreas"
4674
+ });
4675
+ add2({
4676
+ filter: ["all", polygons, ofClass("berth")],
4677
+ id: "tileflow-nautical-berths",
4678
+ localOrder: 394,
4679
+ outlineFilter: ["all", linesOrPolygons, ofClass("berth")],
4680
+ sourceLayer: layers.navigationAreas,
4681
+ style: config.berths,
4682
+ target: "nautical.berths"
4683
+ });
4248
4684
  add2({
4249
4685
  filter: polygons,
4250
4686
  id: "tileflow-nautical-reefs",
@@ -4271,11 +4707,20 @@ function compileNautical(request, context) {
4271
4707
  style: config.routes,
4272
4708
  target: "nautical.routes"
4273
4709
  });
4710
+ add2({
4711
+ filter: linesOf(["overhead-cable"]),
4712
+ id: "tileflow-nautical-overhead-cables",
4713
+ line: true,
4714
+ localOrder: 408,
4715
+ sourceLayer: layers.navigationAreas,
4716
+ style: config.overheadCables,
4717
+ target: "nautical.overheadCables"
4718
+ });
4274
4719
  add2({
4275
4720
  filter: polygons,
4276
4721
  id: "tileflow-nautical-hazard-areas",
4277
4722
  localOrder: 410,
4278
- outlineFilter: ["match", ["geometry-type"], ["LineString", "Polygon"], true, false],
4723
+ outlineFilter: linesOrPolygons,
4279
4724
  sourceLayer: layers.hazards,
4280
4725
  style: config.hazardAreas,
4281
4726
  target: "nautical.hazardAreas"
@@ -4288,15 +4733,42 @@ function compileNautical(request, context) {
4288
4733
  style: config.wreckAreas,
4289
4734
  target: "nautical.wreckAreas"
4290
4735
  });
4291
- for (const [key, id2, sourceLayer, localOrder] of [
4292
- ["coverage", "tileflow-nautical-coverage-labels", layers.coverage, 560],
4293
- ["navigationAreas", "tileflow-nautical-navigation-area-labels", layers.navigationAreas, 562],
4294
- ["reefs", "tileflow-nautical-reef-labels", layers.reefs, 564],
4295
- ["hazards", "tileflow-nautical-hazard-area-labels", layers.hazards, 566],
4296
- ["wrecks", "tileflow-nautical-wreck-area-labels", layers.wrecks, 568]
4736
+ add2({
4737
+ // Bridges draw over the water and the areas below them.
4738
+ filter: ["all", polygons, ofClass("bridge")],
4739
+ id: "tileflow-nautical-bridges",
4740
+ localOrder: 424,
4741
+ outlineFilter: ["all", linesOrPolygons, ofClass("bridge")],
4742
+ sourceLayer: layers.navigationAreas,
4743
+ style: config.bridges,
4744
+ target: "nautical.bridges"
4745
+ });
4746
+ for (const [key, id2, sourceLayer, localOrder, filter] of [
4747
+ [
4748
+ "coverage",
4749
+ "tileflow-nautical-coverage-labels",
4750
+ layers.coverage,
4751
+ 560,
4752
+ ["has", fields.provider]
4753
+ ],
4754
+ [
4755
+ "navigationAreas",
4756
+ "tileflow-nautical-navigation-area-labels",
4757
+ layers.navigationAreas,
4758
+ 562,
4759
+ // Harbours, facilities and bridges carry their own symbols and labels.
4760
+ [
4761
+ "all",
4762
+ ["has", fields.name],
4763
+ ["match", ["get", fields.class], ["bridge", "facility", "harbour"], false, true]
4764
+ ]
4765
+ ],
4766
+ ["reefs", "tileflow-nautical-reef-labels", layers.reefs, 564, ["has", fields.name]],
4767
+ ["hazards", "tileflow-nautical-hazard-area-labels", layers.hazards, 566, ["has", fields.name]],
4768
+ ["wrecks", "tileflow-nautical-wreck-area-labels", layers.wrecks, 568, ["has", fields.name]]
4297
4769
  ]) {
4298
4770
  add2({
4299
- filter: ["all", polygons, ["has", key === "coverage" ? fields.provider : fields.name]],
4771
+ filter: ["all", polygons, filter],
4300
4772
  id: id2,
4301
4773
  localOrder,
4302
4774
  sourceLayer,
@@ -4304,6 +4776,37 @@ function compileNautical(request, context) {
4304
4776
  target: `nautical.labels.${key}`
4305
4777
  });
4306
4778
  }
4779
+ add2({
4780
+ filter: [
4781
+ "all",
4782
+ ofClass("bridge"),
4783
+ [
4784
+ "any",
4785
+ ["has", fields.verticalClearance],
4786
+ ["has", fields.verticalClearanceClosed],
4787
+ ["has", fields.verticalClearanceOpen]
4788
+ ]
4789
+ ],
4790
+ id: "tileflow-nautical-bridge-labels",
4791
+ lines: { localOrder: 557, placement: "line-center" },
4792
+ localOrder: 556,
4793
+ sourceLayer: layers.navigationAreas,
4794
+ style: config.labels?.bridges,
4795
+ target: "nautical.labels.bridges"
4796
+ });
4797
+ add2({
4798
+ filter: [
4799
+ "all",
4800
+ ofClass("overhead-cable"),
4801
+ ["any", ["has", fields.verticalClearanceSafe], ["has", fields.verticalClearance]]
4802
+ ],
4803
+ id: "tileflow-nautical-overhead-cable-labels",
4804
+ lines: { localOrder: 559, placement: "line" },
4805
+ localOrder: 558,
4806
+ sourceLayer: layers.navigationAreas,
4807
+ style: config.labels?.overheadCables,
4808
+ target: "nautical.labels.overheadCables"
4809
+ });
4307
4810
  add2({
4308
4811
  filter: ["all", points, ["has", fields.depth]],
4309
4812
  id: "tileflow-nautical-soundings",
@@ -4322,6 +4825,16 @@ function compileNautical(request, context) {
4322
4825
  symbolFilter: ["==", ["get", fields.class], "rock"],
4323
4826
  target: "nautical.hazards"
4324
4827
  });
4828
+ add2({
4829
+ filter: ofClass("facility"),
4830
+ id: "tileflow-nautical-facilities",
4831
+ lines: { localOrder: 580, placement: "line-center" },
4832
+ localOrder: 576,
4833
+ markerFilter: points,
4834
+ sourceLayer: layers.navigationAreas,
4835
+ style: config.facilities,
4836
+ target: "nautical.facilities"
4837
+ });
4325
4838
  add2({
4326
4839
  filter: points,
4327
4840
  id: "tileflow-nautical-wrecks",
@@ -4331,13 +4844,23 @@ function compileNautical(request, context) {
4331
4844
  target: "nautical.wrecks"
4332
4845
  });
4333
4846
  add2({
4334
- filter: ["all", points, ["==", ["get", fields.class], "harbour"]],
4847
+ // Harbour polygons, such as marinas, carry their symbol and name at their centre.
4848
+ filter: ["all", pointsOrPolygons, ofClass("harbour")],
4335
4849
  id: "tileflow-nautical-harbours",
4336
4850
  localOrder: 582,
4851
+ markerFilter: points,
4337
4852
  sourceLayer: layers.navigationAreas,
4338
4853
  style: config.harbours,
4339
4854
  target: "nautical.harbours"
4340
4855
  });
4856
+ add2({
4857
+ filter: ["all", points, ofClass("distance-mark")],
4858
+ id: "tileflow-nautical-distance-marks",
4859
+ localOrder: 584,
4860
+ sourceLayer: layers.aids,
4861
+ style: config.distanceMarks,
4862
+ target: "nautical.distanceMarks"
4863
+ });
4341
4864
  add2({
4342
4865
  filter: [
4343
4866
  "all",
@@ -4345,7 +4868,7 @@ function compileNautical(request, context) {
4345
4868
  [
4346
4869
  "match",
4347
4870
  ["get", fields.class],
4348
- [...floatingAidClasses, ...fixedAidClasses, "lighthouse"],
4871
+ [...floatingAidClasses, ...fixedAidClasses, ...ownAidClasses, "lighthouse"],
4349
4872
  false,
4350
4873
  true
4351
4874
  ]
@@ -4356,6 +4879,14 @@ function compileNautical(request, context) {
4356
4879
  style: config.aids,
4357
4880
  target: "nautical.aids"
4358
4881
  });
4882
+ add2({
4883
+ filter: ["all", points, ofClass("notice")],
4884
+ id: "tileflow-nautical-notices",
4885
+ localOrder: 588,
4886
+ sourceLayer: layers.aids,
4887
+ style: config.notices,
4888
+ target: "nautical.notices"
4889
+ });
4359
4890
  add2({
4360
4891
  filter: ["all", points, ["match", ["get", fields.class], [...fixedAidClasses], true, false]],
4361
4892
  id: "tileflow-nautical-beacons",
@@ -4366,6 +4897,7 @@ function compileNautical(request, context) {
4366
4897
  });
4367
4898
  add2({
4368
4899
  filter: points,
4900
+ flash: animateLights,
4369
4901
  id: "tileflow-nautical-lights",
4370
4902
  localOrder: 594,
4371
4903
  sourceLayer: layers.lights,
@@ -4381,7 +4913,7 @@ function compileNautical(request, context) {
4381
4913
  target: "nautical.buoys"
4382
4914
  });
4383
4915
  add2({
4384
- filter: ["all", points, ["==", ["get", fields.class], "lighthouse"]],
4916
+ filter: ["all", points, ofClass("lighthouse")],
4385
4917
  id: "tileflow-nautical-lighthouses",
4386
4918
  localOrder: 602,
4387
4919
  sourceLayer: layers.aids,
@@ -4389,7 +4921,7 @@ function compileNautical(request, context) {
4389
4921
  target: "nautical.lighthouses"
4390
4922
  });
4391
4923
  add2({
4392
- filter: ["all", points, ["==", ["get", fields.class], "port"]],
4924
+ filter: ["all", points, ofClass("port")],
4393
4925
  id: "tileflow-nautical-ports",
4394
4926
  localOrder: 606,
4395
4927
  sourceLayer: layers.navigationAreas,
@@ -4400,12 +4932,14 @@ function compileNautical(request, context) {
4400
4932
  }
4401
4933
  var points = ["==", ["geometry-type"], "Point"];
4402
4934
  var polygons = ["==", ["geometry-type"], "Polygon"];
4935
+ var lines = ["==", ["geometry-type"], "LineString"];
4936
+ var pointsOrPolygons = ["match", ["geometry-type"], ["Point", "Polygon"], true, false];
4937
+ var linesOrPolygons = ["match", ["geometry-type"], ["LineString", "Polygon"], true, false];
4403
4938
  function linesOf(classes) {
4404
- return [
4405
- "all",
4406
- ["==", ["geometry-type"], "LineString"],
4407
- ["match", ["get", fields.class], [...classes], true, false]
4408
- ];
4939
+ return ["all", lines, ["match", ["get", fields.class], [...classes], true, false]];
4940
+ }
4941
+ function listIncludes(field2, item) {
4942
+ return ["in", `,${item},`, ["concat", ",", ["to-string", ["coalesce", ["get", field2], ""]], ","]];
4409
4943
  }
4410
4944
  var fromFeatureMinZoom = [">=", ["zoom"], ["to-number", ["get", fields.minZoom], 0]];
4411
4945
  function appendNauticalLayer(contributions, options) {
@@ -4462,24 +4996,25 @@ function appendNauticalLayer(contributions, options) {
4462
4996
  if (point.visible === false) return;
4463
4997
  const isLabel = options.target.startsWith("nautical.labels.");
4464
4998
  if (!isLabel && point.marker && point.marker.visible !== false) {
4999
+ const marker = applyCircleStyle(
5000
+ base(
5001
+ `${options.id}-marker`,
5002
+ "circle",
5003
+ options.markerFilter ? ["all", options.filter, options.markerFilter] : options.filter
5004
+ ),
5005
+ mergeTileflowDesign(
5006
+ {
5007
+ ...point.maxZoom === void 0 ? {} : { maxZoom: point.maxZoom },
5008
+ ...point.minZoom === void 0 ? {} : { minZoom: point.minZoom }
5009
+ },
5010
+ point.marker
5011
+ )
5012
+ );
4465
5013
  contributions.push(
4466
5014
  nauticalContribution(
4467
5015
  `${options.target}.marker`,
4468
5016
  options.localOrder,
4469
- applyCircleStyle(
4470
- base(
4471
- `${options.id}-marker`,
4472
- "circle",
4473
- options.markerFilter ? ["all", options.filter, options.markerFilter] : options.filter
4474
- ),
4475
- mergeTileflowDesign(
4476
- {
4477
- ...point.maxZoom === void 0 ? {} : { maxZoom: point.maxZoom },
4478
- ...point.minZoom === void 0 ? {} : { minZoom: point.minZoom }
4479
- },
4480
- point.marker
4481
- )
4482
- ),
5017
+ options.flash ? flashing(marker, ["circle-opacity", "circle-stroke-opacity"]) : marker,
4483
5018
  "symbols"
4484
5019
  )
4485
5020
  );
@@ -4487,21 +5022,79 @@ function appendNauticalLayer(contributions, options) {
4487
5022
  const hasText = Boolean(point.text && point.text.visible !== false);
4488
5023
  const hasIcon = Boolean(point.icon && point.icon.visible !== false);
4489
5024
  if (!hasText && !hasIcon) return;
5025
+ const symbolFilter = [
5026
+ options.filter,
5027
+ ...options.symbolFilter ? [options.symbolFilter] : [],
5028
+ ...options.lines ? [["!=", ["geometry-type"], "LineString"]] : []
5029
+ ];
5030
+ const symbol = applySymbolStyle(
5031
+ base(
5032
+ options.id,
5033
+ "symbol",
5034
+ symbolFilter.length === 1 ? symbolFilter[0] : ["all", ...symbolFilter]
5035
+ ),
5036
+ withoutMarker(point)
5037
+ );
4490
5038
  contributions.push(
4491
5039
  nauticalContribution(
4492
5040
  isLabel ? options.target : `${options.target}.symbol`,
4493
5041
  options.localOrder + (isLabel ? 0 : 1),
4494
- applySymbolStyle(
4495
- base(
4496
- options.id,
4497
- "symbol",
4498
- options.symbolFilter ? ["all", options.filter, options.symbolFilter] : options.filter
4499
- ),
4500
- withoutMarker(point)
4501
- ),
5042
+ options.flash && hasIcon ? flashing(symbol, ["icon-opacity"]) : symbol,
4502
5043
  "symbols"
4503
5044
  )
4504
5045
  );
5046
+ if (options.lines) {
5047
+ contributions.push(
5048
+ nauticalContribution(
5049
+ `${options.target}.lines`,
5050
+ options.lines.localOrder,
5051
+ applySymbolStyle(
5052
+ base(`${options.id}-lines`, "symbol", [
5053
+ "all",
5054
+ options.filter,
5055
+ ...options.symbolFilter ? [options.symbolFilter] : [],
5056
+ lines
5057
+ ]),
5058
+ { ...withoutMarker(point), placement: options.lines.placement }
5059
+ ),
5060
+ "symbols"
5061
+ )
5062
+ );
5063
+ }
5064
+ }
5065
+ function flashing(layer, properties2) {
5066
+ const paint = { ...layer.paint ?? {} };
5067
+ const factor = [
5068
+ "interpolate",
5069
+ ["linear"],
5070
+ tileflowNauticalLightStateRead("intensity", 1),
5071
+ 0,
5072
+ tileflowNauticalLightEclipseOpacity,
5073
+ 1,
5074
+ 1
5075
+ ];
5076
+ for (const property of properties2) {
5077
+ const value = paint[property];
5078
+ if (property !== "circle-opacity" && property !== "icon-opacity" && value === void 0) {
5079
+ continue;
5080
+ }
5081
+ paint[property] = scaleOutputs(value ?? 1, factor);
5082
+ }
5083
+ return { ...layer, paint };
5084
+ }
5085
+ function scaleOutputs(value, factor) {
5086
+ if (Array.isArray(value) && isZoomCurve(value)) {
5087
+ const firstOutput = value[0] === "step" ? 2 : 4;
5088
+ return value.map(
5089
+ (entry, index) => index >= firstOutput && (index - firstOutput) % 2 === 0 ? ["*", entry, factor] : entry
5090
+ );
5091
+ }
5092
+ return ["*", value, factor];
5093
+ }
5094
+ function isZoomCurve(value) {
5095
+ const isZoom3 = (input) => Array.isArray(input) && input[0] === "zoom";
5096
+ if (value[0] === "step") return isZoom3(value[1]);
5097
+ return (value[0] === "interpolate" || value[0] === "interpolate-hcl" || value[0] === "interpolate-lab") && isZoom3(value[2]);
4505
5098
  }
4506
5099
  function withoutMarker(style) {
4507
5100
  const result = { ...style };
@@ -4538,8 +5131,96 @@ function themedImages(context) {
4538
5131
  ]
4539
5132
  ]);
4540
5133
  }
5134
+ const notice = images.notices;
5135
+ if (typeof notice === "string") {
5136
+ const variants = ["information", "prohibition", "recommendation", "regulation", "restriction"].flatMap((role) => [role, context.images[roles.notices[role]] ?? notice]);
5137
+ images.notices = expression(["match", ["get", fields.subclass], ...variants, notice]);
5138
+ }
5139
+ const facility = images.facilities;
5140
+ if (typeof facility === "string") {
5141
+ const cases = tileflowNauticalFacilityIcons.flatMap((category) => {
5142
+ const image = context.images[roles.facilities[category]];
5143
+ if (!image || image === facility) return [];
5144
+ const categories = category === "visitor-berth" ? ["visitor-berth", "visitors-mooring"] : [category];
5145
+ return [["any", ...categories.map((name) => listIncludes(fields.subclass, name))], image];
5146
+ });
5147
+ if (cases.length > 0) images.facilities = expression(["case", ...cases, facility]);
5148
+ }
5149
+ const harbour = images.harbours;
5150
+ const marina = context.images[roles.harbours.marina];
5151
+ if (typeof harbour === "string" && marina && marina !== harbour) {
5152
+ images.harbours = expression([
5153
+ "case",
5154
+ ["any", ...marinaSubclasses.map((name) => listIncludes(fields.subclass, name))],
5155
+ marina,
5156
+ harbour
5157
+ ]);
5158
+ }
4541
5159
  return images;
4542
5160
  }
5161
+ function bridgeClearance() {
5162
+ const metres = (field2) => ["concat", ["to-string", ["get", field2]], " m"];
5163
+ const closed = fields.verticalClearanceClosed;
5164
+ const open = fields.verticalClearanceOpen;
5165
+ return [
5166
+ "case",
5167
+ ["any", ["has", closed], ["has", open]],
5168
+ [
5169
+ "concat",
5170
+ ["case", ["has", closed], ["concat", "closed ", metres(closed)], ""],
5171
+ ["case", ["all", ["has", closed], ["has", open]], " \xB7 ", ""],
5172
+ ["case", ["has", open], ["concat", "open ", metres(open)], ""]
5173
+ ],
5174
+ ["has", fields.verticalClearance],
5175
+ ["concat", "clearance ", metres(fields.verticalClearance)],
5176
+ ""
5177
+ ];
5178
+ }
5179
+ function cableClearance() {
5180
+ return [
5181
+ "case",
5182
+ ["has", fields.verticalClearanceSafe],
5183
+ ["concat", "safe clearance ", ["to-string", ["get", fields.verticalClearanceSafe]], " m"],
5184
+ ["has", fields.verticalClearance],
5185
+ ["concat", "clearance ", ["to-string", ["get", fields.verticalClearance]], " m"],
5186
+ ""
5187
+ ];
5188
+ }
5189
+ function nthColour(field2, index) {
5190
+ const list = ["var", "nauticalColours"];
5191
+ const cases = [];
5192
+ for (let position = 1; position < maxAlternatingColours; position += 1) {
5193
+ cases.push(["==", ["var", "nauticalColourIndex"], position], nthName(list, position));
5194
+ }
5195
+ return [
5196
+ "let",
5197
+ "nauticalColours",
5198
+ ["to-string", ["coalesce", ["get", field2], ""]],
5199
+ "nauticalColourIndex",
5200
+ index,
5201
+ ["case", ...cases, leadingName(list)]
5202
+ ];
5203
+ }
5204
+ function nthName(list, position) {
5205
+ if (position === 0) return leadingName(list);
5206
+ const rest = `nauticalRest${position}`;
5207
+ return [
5208
+ "let",
5209
+ rest,
5210
+ [
5211
+ "let",
5212
+ "nauticalComma",
5213
+ ["index-of", ",", list],
5214
+ [
5215
+ "case",
5216
+ ["<", ["var", "nauticalComma"], 0],
5217
+ "",
5218
+ ["slice", list, ["+", ["var", "nauticalComma"], 1]]
5219
+ ]
5220
+ ],
5221
+ nthName(["var", rest], position - 1)
5222
+ ];
5223
+ }
4543
5224
  function firstColour(field2) {
4544
5225
  return [
4545
5226
  "let",
@@ -5405,6 +6086,7 @@ var visualNumberProperties = /* @__PURE__ */ new Set([
5405
6086
  "colorMix",
5406
6087
  "metallic",
5407
6088
  "roughness",
6089
+ "horizonBlend",
5408
6090
  "hue",
5409
6091
  "saturation",
5410
6092
  "lightness",
@@ -5432,6 +6114,7 @@ var visualNumberProperties = /* @__PURE__ */ new Set([
5432
6114
  "roundLimit",
5433
6115
  "size",
5434
6116
  "spacing",
6117
+ "starIntensity",
5435
6118
  "strokeOpacity",
5436
6119
  "strokeWidth",
5437
6120
  "textFitPadding",
@@ -5517,8 +6200,11 @@ var visualNumberProperties = /* @__PURE__ */ new Set([
5517
6200
  "text-translate",
5518
6201
  "symbol-spacing"
5519
6202
  ]);
6203
+ function isTileflowVisualColorCollection(property) {
6204
+ return property === "colors" || property.endsWith("Colors") || property.endsWith("-colors");
6205
+ }
5520
6206
  function classifyTileflowVisualProperty(property, _value) {
5521
- if (property === "color" || property === "colors" || property.endsWith("Color") || property.endsWith("Colors") || property.endsWith("-color") || property.endsWith("-colors")) {
6207
+ if (property === "color" || property.endsWith("Color") || property.endsWith("-color") || isTileflowVisualColorCollection(property)) {
5522
6208
  return "color";
5523
6209
  }
5524
6210
  if (property === "font" || property === "fallbacks" || property.endsWith("-font")) return "font";
@@ -5622,7 +6308,7 @@ function auditTileflowThemeValues(value, path = "value", context = inferAuditCon
5622
6308
  return diagnostics.sort(compareAuditDiagnostics);
5623
6309
  }
5624
6310
  function auditTileflowMapThemeValues(map) {
5625
- const modules = isRecord3(map.modules) ? map.modules : void 0;
6311
+ const modules = isRecord5(map.modules) ? map.modules : void 0;
5626
6312
  const moduleDiagnostics = modules ? Object.keys(modules).sort(compareCodeUnits2).flatMap(
5627
6313
  (owner) => auditTileflowThemeValues(modules[owner], `modules.${owner}`, {
5628
6314
  owner,
@@ -5725,7 +6411,7 @@ function isThemePrimitiveForCategory(value, category) {
5725
6411
  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;
5726
6412
  }
5727
6413
  function isThemeValueCandidate(value) {
5728
- return isRecord3(value) && (value.kind === "theme-token" || value.kind === "theme-fixed" || value.kind === "theme-color");
6414
+ return isRecord5(value) && (value.kind === "theme-token" || value.kind === "theme-fixed" || value.kind === "theme-color");
5729
6415
  }
5730
6416
  function visitAuditableValue(value, path, category, state, intentRequired = false) {
5731
6417
  if (isThemeStructuralPath(path)) category = void 0;
@@ -5741,7 +6427,7 @@ function visitAuditableValue(value, path, category, state, intentRequired = fals
5741
6427
  visitAuditableArray(value, path, category, state, false, intentRequired);
5742
6428
  return;
5743
6429
  }
5744
- if (!isRecord3(value)) return;
6430
+ if (!isRecord5(value)) return;
5745
6431
  if (value.kind === "expression" && Array.isArray(value.value)) {
5746
6432
  visitAuditableArray(value.value, `${path}.value`, category, state, true, intentRequired);
5747
6433
  return;
@@ -5761,15 +6447,19 @@ function visitAuditableValue(value, path, category, state, intentRequired = fals
5761
6447
  }
5762
6448
  return;
5763
6449
  }
6450
+ const collectionCategory = isTileflowVisualColorCollection(lastPathKey(path)) ? category : void 0;
5764
6451
  for (const key of Object.keys(value).sort(compareCodeUnits2)) {
5765
6452
  visitAuditableValue(
5766
6453
  value[key],
5767
6454
  `${path}.${key}`,
5768
- classifyTileflowVisualProperty(key, value[key]),
6455
+ classifyTileflowVisualProperty(key, value[key]) ?? collectionCategory,
5769
6456
  state
5770
6457
  );
5771
6458
  }
5772
6459
  }
6460
+ function lastPathKey(path) {
6461
+ return path.endsWith("]") ? "" : path.slice(path.lastIndexOf(".") + 1);
6462
+ }
5773
6463
  function visitAuditableArray(value, path, category, state, forceExpression = false, intentRequired = false) {
5774
6464
  const operator = typeof value[0] === "string" ? value[0] : void 0;
5775
6465
  const isExpression2 = forceExpression || isMapLibreExpressionOperator(operator);
@@ -6056,7 +6746,7 @@ var ThemeValueResolver = class {
6056
6746
  if (Array.isArray(value)) {
6057
6747
  return value.map((entry, index) => this.resolveTree(entry, `${path}[${index}]`, category));
6058
6748
  }
6059
- if (!isRecord3(value)) return value;
6749
+ if (!isRecord5(value)) return value;
6060
6750
  if (value.kind === "expression" && Array.isArray(value.value)) {
6061
6751
  return {
6062
6752
  ...value,
@@ -6370,7 +7060,7 @@ function assertThemeLighting(value, path, allowReset = false) {
6370
7060
  }
6371
7061
  }
6372
7062
  function assertThemeCategoryValue(value, expectedCategory, path) {
6373
- if (!isRecord3(value)) {
7063
+ if (!isRecord5(value)) {
6374
7064
  assertTokenPrimitive(expectedCategory, value, path);
6375
7065
  return;
6376
7066
  }
@@ -6722,11 +7412,11 @@ function cloneJson14(value) {
6722
7412
  if (serialized === void 0) throw new Error("Tileflow themes must be JSON-serializable.");
6723
7413
  return JSON.parse(serialized);
6724
7414
  }
6725
- function isRecord3(value) {
7415
+ function isRecord5(value) {
6726
7416
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
6727
7417
  }
6728
7418
  function isPlainRecord2(value) {
6729
- if (!isRecord3(value)) return false;
7419
+ if (!isRecord5(value)) return false;
6730
7420
  const prototype = Object.getPrototypeOf(value);
6731
7421
  return prototype === Object.prototype || prototype === null;
6732
7422
  }
@@ -7278,35 +7968,37 @@ function compileRoundabouts(request, surfaces, context, contributions) {
7278
7968
  }),
7279
7969
  0
7280
7970
  ) : atZoom(borderWidth, level);
7281
- const geometry = (casing, width) => expression([
7282
- "interpolate",
7283
- ["exponential", 2],
7284
- ["zoom"],
7285
- ...geometryLevels.flatMap((level) => {
7286
- const centerline = ["*", radiusAt15, 2 ** (level - 15)];
7287
- const fallback = fallbackWidth === void 0 ? select(
7288
- widths.map(({ condition, value }) => ({ condition, value: atZoom(value, level) })),
7289
- 0
7290
- ) : atZoom(fallbackWidth, level);
7291
- const inner = [
7292
- "case",
7293
- physical,
7294
- ["*", centerline, ["/", innerMeters, radiusMeters]],
7295
- ["-", centerline, ["/", fallback, 2]]
7296
- ];
7297
- const fillWidth = [
7298
- "case",
7299
- physical,
7300
- ["*", centerline, ["/", ["-", outerMeters, innerMeters], radiusMeters]],
7301
- fallback
7302
- ];
7303
- const border = casing ? borderAt(level) : 0;
7304
- return [
7305
- level,
7306
- width ? ["max", 0, ["+", fillWidth, border]] : ["max", 0, ["-", inner, ["/", border, 2]]]
7307
- ];
7308
- })
7309
- ]);
7971
+ const geometry = (casing, width) => expression(
7972
+ bindRepeated([
7973
+ "interpolate",
7974
+ ["exponential", 2],
7975
+ ["zoom"],
7976
+ ...geometryLevels.flatMap((level) => {
7977
+ const centerline = ["*", radiusAt15, 2 ** (level - 15)];
7978
+ const fallback = fallbackWidth === void 0 ? select(
7979
+ widths.map(({ condition, value }) => ({ condition, value: atZoom(value, level) })),
7980
+ 0
7981
+ ) : atZoom(fallbackWidth, level);
7982
+ const inner = [
7983
+ "case",
7984
+ physical,
7985
+ ["*", centerline, ["/", innerMeters, radiusMeters]],
7986
+ ["-", centerline, ["/", fallback, 2]]
7987
+ ];
7988
+ const fillWidth = [
7989
+ "case",
7990
+ physical,
7991
+ ["*", centerline, ["/", ["-", outerMeters, innerMeters], radiusMeters]],
7992
+ fallback
7993
+ ];
7994
+ const border = casing ? borderAt(level) : 0;
7995
+ return [
7996
+ level,
7997
+ width ? ["max", 0, ["+", fillWidth, border]] : ["max", 0, ["-", inner, ["/", border, 2]]]
7998
+ ];
7999
+ })
8000
+ ])
8001
+ );
7310
8002
  const phaseStyle = (phase) => ({
7311
8003
  color: "rgba(0, 0, 0, 0)",
7312
8004
  minZoom: 15,
@@ -7363,6 +8055,63 @@ function compileRoundabouts(request, surfaces, context, contributions) {
7363
8055
  });
7364
8056
  }
7365
8057
  }
8058
+ var bindable = /* @__PURE__ */ new Set([
8059
+ "case",
8060
+ "match",
8061
+ "all",
8062
+ "any",
8063
+ "!",
8064
+ "==",
8065
+ "!=",
8066
+ "<",
8067
+ "<=",
8068
+ ">",
8069
+ ">=",
8070
+ "has",
8071
+ "get",
8072
+ "to-number",
8073
+ "coalesce",
8074
+ "+",
8075
+ "-",
8076
+ "*",
8077
+ "/",
8078
+ "max",
8079
+ "min"
8080
+ ]);
8081
+ function bindRepeated(curve) {
8082
+ let tree = curve;
8083
+ for (let index = 0; index < 2; index += 1) {
8084
+ const json = /* @__PURE__ */ new Map();
8085
+ const found = /* @__PURE__ */ new Map();
8086
+ const visit = (value) => {
8087
+ if (!Array.isArray(value) || value[0] === "literal")
8088
+ return { text: JSON.stringify(value), free: true };
8089
+ const parts = value.map(visit);
8090
+ const text = `[${parts.map((part) => part.text).join(",")}]`;
8091
+ json.set(value, text);
8092
+ const free = value[0] !== "zoom" && value[0] !== "var" && parts.every((part) => part.free);
8093
+ if (free && typeof value[0] === "string" && bindable.has(value[0]))
8094
+ found.set(text, (found.get(text) ?? 0) + 1);
8095
+ return { text, free };
8096
+ };
8097
+ visit(tree);
8098
+ let best;
8099
+ let saving = 256;
8100
+ for (const [text, count] of found) {
8101
+ const saved = (count - 1) * text.length;
8102
+ if (count > 1 && saved > saving) [best, saving] = [text, saved];
8103
+ }
8104
+ if (best === void 0) break;
8105
+ const name = `tileflow_${index}`;
8106
+ const replace = (value) => {
8107
+ if (!Array.isArray(value) || value[0] === "literal") return value;
8108
+ if (json.get(value) === best) return ["var", name];
8109
+ return value.map(replace);
8110
+ };
8111
+ tree = ["let", name, JSON.parse(best), replace(tree)];
8112
+ }
8113
+ return tree;
8114
+ }
7366
8115
  function select(values, fallback) {
7367
8116
  if (!values.length) return fallback;
7368
8117
  const serialized = JSON.stringify(values[0].value);
@@ -7401,7 +8150,7 @@ function phaseVisibility(surfaces, phase, fields2) {
7401
8150
  function zoomLevels(values) {
7402
8151
  const levels = new Set(Array.from({ length: 10 }, (_, index) => index + 15));
7403
8152
  for (const value of values) {
7404
- if (!isZoomCurve(value)) continue;
8153
+ if (!isZoomCurve2(value)) continue;
7405
8154
  for (let index = 3; index < value.length - 1; index += 2) {
7406
8155
  const level = value[index];
7407
8156
  if (level < 15 || level > 24) continue;
@@ -7459,7 +8208,7 @@ function interpolationFactor2(interpolation, distance, span) {
7459
8208
  const logarithm = Math.log(interpolation[1]);
7460
8209
  return Math.expm1(logarithm * distance) / Math.expm1(logarithm * span);
7461
8210
  }
7462
- function isZoomCurve(value) {
8211
+ function isZoomCurve2(value) {
7463
8212
  return Array.isArray(value) && (value[0] === "interpolate" && isZoom(value[2]) || value[0] === "step" && isZoom(value[1]));
7464
8213
  }
7465
8214
  function isZoom(value) {
@@ -7468,19 +8217,55 @@ function isZoom(value) {
7468
8217
  function atZoom(value, level) {
7469
8218
  if (!Array.isArray(value) || value[0] === "literal") return value;
7470
8219
  if (isZoom(value)) return level;
7471
- if (!isZoomCurve(value)) return value.map((entry) => atZoom(entry, level));
8220
+ if (!isZoomCurve2(value)) return value.map((entry) => atZoom(entry, level));
7472
8221
  const stops = [];
7473
8222
  for (let index = 3; index < value.length - 1; index += 2) {
7474
8223
  stops.push({ level: value[index], value: value[index + 1] });
7475
8224
  }
7476
8225
  const exact = stops.find((stop) => stop.level === level);
7477
- if (exact) return exact.value;
8226
+ if (exact) return foldConstants(exact.value);
7478
8227
  const lower = [...stops].reverse().find((stop) => stop.level < level);
7479
8228
  const upper = stops.find((stop) => stop.level > level);
7480
- if (value[0] === "step") return lower?.value ?? value[2];
7481
- if (!lower) return upper?.value ?? 0;
7482
- if (!upper) return lower.value;
7483
- return ["interpolate", value[1], level, lower.level, lower.value, upper.level, upper.value];
8229
+ if (value[0] === "step") return foldConstants(lower?.value ?? value[2]);
8230
+ if (!lower) return foldConstants(upper?.value ?? 0);
8231
+ if (!upper) return foldConstants(lower.value);
8232
+ const at = interpolationFactor2(
8233
+ value[1],
8234
+ level - lower.level,
8235
+ upper.level - lower.level
8236
+ );
8237
+ const folded = Number.isFinite(at) ? interpolated(foldConstants(lower.value), foldConstants(upper.value), at) : void 0;
8238
+ return folded ?? ["interpolate", value[1], level, lower.level, lower.value, upper.level, upper.value];
8239
+ }
8240
+ function foldConstants(value) {
8241
+ if (!Array.isArray(value) || value[0] === "literal") return value;
8242
+ const folded = value.map(foldConstants);
8243
+ const [operator, ...operands] = folded;
8244
+ if (!operands.length || !operands.every((operand) => typeof operand === "number")) return folded;
8245
+ const numbers = operands;
8246
+ const result = operator === "*" ? numbers.reduce((product, number) => product * number, 1) : operator === "+" ? numbers.reduce((sum, number) => sum + number, 0) : operator === "-" ? numbers.length === 1 ? -numbers[0] : numbers[0] - numbers[1] : operator === "/" && numbers.length === 2 ? numbers[0] / numbers[1] : operator === "max" ? Math.max(...numbers) : operator === "min" ? Math.min(...numbers) : void 0;
8247
+ return result !== void 0 && Number.isFinite(result) ? result : folded;
8248
+ }
8249
+ function interpolated(lower, upper, at) {
8250
+ if (typeof lower === "number" && typeof upper === "number") return lower + (upper - lower) * at;
8251
+ if (JSON.stringify(lower) === JSON.stringify(upper)) return lower;
8252
+ if (!Array.isArray(lower) || !Array.isArray(upper) || lower.length !== upper.length || lower[0] !== "case" && lower[0] !== "match" || lower[0] !== upper[0])
8253
+ return void 0;
8254
+ const first = lower[0] === "case" ? 1 : 2;
8255
+ const result = lower.slice(0, first);
8256
+ if (JSON.stringify(result) !== JSON.stringify(upper.slice(0, first))) return void 0;
8257
+ for (let index = first; index < lower.length; index += 1) {
8258
+ const output = (index - first) % 2 === 1 || index === lower.length - 1;
8259
+ if (!output) {
8260
+ if (JSON.stringify(lower[index]) !== JSON.stringify(upper[index])) return void 0;
8261
+ result.push(lower[index]);
8262
+ continue;
8263
+ }
8264
+ const value = interpolated(lower[index], upper[index], at);
8265
+ if (value === void 0) return void 0;
8266
+ result.push(value);
8267
+ }
8268
+ return result;
7484
8269
  }
7485
8270
 
7486
8271
  // src/modules/roads/compiler.ts
@@ -7551,6 +8336,10 @@ function compileRoads(request, context) {
7551
8336
  request?.classes?.[roadClass]
7552
8337
  );
7553
8338
  const treatments = resolveRoadTreatments(request, schema.fields, roadClass);
8339
+ const drawn = {
8340
+ field: schema.fields.class,
8341
+ classes: new Set(tileflowRoadClassValues(roadClass))
8342
+ };
7554
8343
  const roundaboutSurface = { roadClass };
7555
8344
  roundaboutSurfaces.push(roundaboutSurface);
7556
8345
  for (const structure of ["tunnel", "surface", "bridge"]) {
@@ -7573,7 +8362,8 @@ function compileRoads(request, context) {
7573
8362
  treatments,
7574
8363
  structure,
7575
8364
  phase,
7576
- phaseDefaults.color
8365
+ phaseDefaults.color,
8366
+ drawn
7577
8367
  );
7578
8368
  const contribution = {
7579
8369
  family: roadLinePhysicalFamily(roadClass, structure, phase),
@@ -7636,7 +8426,8 @@ function compileRoads(request, context) {
7636
8426
  treatments,
7637
8427
  structure,
7638
8428
  "fill",
7639
- roadColor(context, roadClass)
8429
+ roadColor(context, roadClass),
8430
+ drawn
7640
8431
  );
7641
8432
  const size = hatch.size === void 0 ? scaleStyleValue(treatedFill.width ?? 1, 0.68) : toMapLibreStyleValue(hatch.size);
7642
8433
  const minZoom = hatch.minZoom ?? treatedFill.minZoom;
@@ -7757,31 +8548,24 @@ function compileRoads(request, context) {
7757
8548
  const areaTargets = [
7758
8549
  {
7759
8550
  id: "tileflow-road-area",
7760
- filter: ["==", ["geometry-type"], "Polygon"],
8551
+ kind: void 0,
7761
8552
  name: "road",
7762
8553
  style: areaStyles.road
7763
8554
  },
7764
8555
  {
7765
8556
  id: "tileflow-road-pedestrian-area",
7766
- filter: [
7767
- "all",
7768
- ["==", ["geometry-type"], "Polygon"],
7769
- tileflowRoadClassFilter(schema.fields, "pedestrian")
7770
- ],
8557
+ kind: tileflowRoadClassFilter(schema.fields, "pedestrian"),
7771
8558
  name: "pedestrian",
7772
8559
  style: areaStyles.pedestrian
7773
8560
  },
7774
8561
  {
7775
8562
  id: "tileflow-road-pier-area",
7776
- filter: [
7777
- "all",
7778
- ["==", ["geometry-type"], "Polygon"],
7779
- ["==", ["get", schema.fields.class], "pier"]
7780
- ],
8563
+ kind: ["==", ["get", schema.fields.class], "pier"],
7781
8564
  name: "pier",
7782
8565
  style: areaStyles.pier
7783
8566
  }
7784
8567
  ];
8568
+ const polygons2 = ["==", ["geometry-type"], "Polygon"];
7785
8569
  let areaOrder = 900;
7786
8570
  for (const target of areaTargets) {
7787
8571
  if (!target.style) continue;
@@ -7791,13 +8575,19 @@ function compileRoads(request, context) {
7791
8575
  type: "fill",
7792
8576
  source,
7793
8577
  "source-layer": schema.layers.road,
7794
- filter: target.filter
8578
+ filter: target.kind ? ["all", polygons2, target.kind] : polygons2
7795
8579
  },
7796
8580
  target.style
7797
8581
  )) {
8582
+ const { filter: _polygonFilter, ...unfiltered } = area.layer;
8583
+ const outline = area.phase === "outline" && schema.layers.transportationOutline ? {
8584
+ ...unfiltered,
8585
+ "source-layer": schema.layers.transportationOutline,
8586
+ ...target.kind ? { filter: target.kind } : {}
8587
+ } : area.layer;
7798
8588
  contributions.push({
7799
8589
  kind: "layer",
7800
- layer: area.layer,
8590
+ layer: outline,
7801
8591
  localOrder: areaOrder++,
7802
8592
  owner: "roads",
7803
8593
  slot: "transport-areas",
@@ -8074,9 +8864,10 @@ function resolveRoadTreatments(request, fields2, roadClass) {
8074
8864
  add2(request?.modifiers?.unpaved, ["==", ["get", fields2.surface], "unpaved"]);
8075
8865
  add2(request?.modifiers?.indoor, flagFilter(fields2.indoor));
8076
8866
  add2(request?.modifiers?.official, flagFilter(fields2.official));
8077
- for (const [scale, style] of Object.entries(request?.mountainBike ?? {})) {
8078
- add2(style, ["==", ["to-string", ["get", fields2.mtbScale]], scale]);
8079
- }
8867
+ if (isTileflowPathRoadClass(roadClass) || roadClass === "track")
8868
+ for (const [scale, style] of Object.entries(request?.mountainBike ?? {})) {
8869
+ add2(style, ["==", ["to-string", ["get", fields2.mtbScale]], scale]);
8870
+ }
8080
8871
  if (roadClass === "service") {
8081
8872
  for (const [serviceType, value] of Object.entries(serviceTypeValues)) {
8082
8873
  add2(request?.serviceTypes?.[serviceType], [
@@ -8088,7 +8879,7 @@ function resolveRoadTreatments(request, fields2, roadClass) {
8088
8879
  }
8089
8880
  return entries;
8090
8881
  }
8091
- function applyRoadTreatments(base, treatments, structure, phase, fallbackColor) {
8882
+ function applyRoadTreatments(base, treatments, structure, phase, fallbackColor, drawn) {
8092
8883
  const result = { ...base };
8093
8884
  const paintProperties = [
8094
8885
  "blur",
@@ -8105,12 +8896,12 @@ function applyRoadTreatments(base, treatments, structure, phase, fallbackColor)
8105
8896
  return value === void 0 ? [] : [
8106
8897
  {
8107
8898
  condition,
8108
- resolve: () => expressionOutput(toMapLibreStyleValue(value))
8899
+ resolve: () => expressionOutput(withinClasses(toMapLibreStyleValue(value), drawn))
8109
8900
  }
8110
8901
  ];
8111
8902
  });
8112
8903
  if (branches.length > 0) {
8113
- if (property === "dash" && isSolidRoadDash(result[property] ?? treatmentPaintDefaults.dash) && branches.every(({ resolve }) => isSolidRoadDash(resolve()))) {
8904
+ if (property === "dash" && isSolidRoadDash(withinClasses(result[property] ?? treatmentPaintDefaults.dash, drawn)) && branches.every(({ resolve }) => isSolidRoadDash(resolve()))) {
8114
8905
  delete result[property];
8115
8906
  continue;
8116
8907
  }
@@ -8140,6 +8931,49 @@ function applyRoadTreatments(base, treatments, structure, phase, fallbackColor)
8140
8931
  }
8141
8932
  return result;
8142
8933
  }
8934
+ function withinClasses(value, drawn) {
8935
+ const decided = (condition) => {
8936
+ if (!Array.isArray(condition)) return typeof condition === "boolean" ? condition : void 0;
8937
+ const [operator, ...operands] = condition;
8938
+ const isClass = (operand) => Array.isArray(operand) && operand.length === 2 && operand[0] === "get" && (operand[1] === drawn.field || typeof operand[1] === "object" && operand[1] !== null && operand[1].kind === "tileflow-data-field" && operand[1].name === "class");
8939
+ const among = (labels2) => {
8940
+ const hits = [...drawn.classes].filter((value2) => labels2.includes(value2)).length;
8941
+ return hits === 0 ? false : hits === drawn.classes.size ? true : void 0;
8942
+ };
8943
+ if ((operator === "==" || operator === "!=") && operands.length === 2) {
8944
+ const label = isClass(operands[0]) ? operands[1] : isClass(operands[1]) ? operands[0] : null;
8945
+ if (typeof label !== "string") return void 0;
8946
+ const equal2 = among([label]);
8947
+ return equal2 === void 0 ? void 0 : operator === "==" ? equal2 : !equal2;
8948
+ }
8949
+ if (operator === "match" && operands.length === 4 && isClass(operands[0]) && typeof operands[2] === "boolean" && typeof operands[3] === "boolean") {
8950
+ const labels2 = Array.isArray(operands[1]) ? operands[1] : [operands[1]];
8951
+ const matched = among(labels2);
8952
+ return matched === void 0 ? void 0 : matched ? operands[2] : operands[3];
8953
+ }
8954
+ if (operator === "!" && operands.length === 1) {
8955
+ const inner = decided(operands[0]);
8956
+ return inner === void 0 ? void 0 : !inner;
8957
+ }
8958
+ if (operator === "all" || operator === "any") {
8959
+ const results = operands.map(decided);
8960
+ if (results.includes(operator === "all" ? false : true)) return operator !== "all";
8961
+ return results.every((result) => result !== void 0) ? operator === "all" : void 0;
8962
+ }
8963
+ return void 0;
8964
+ };
8965
+ if (!Array.isArray(value) || value[0] !== "case") return value;
8966
+ const branches = [];
8967
+ for (let index = 1; index < value.length - 1; index += 2) {
8968
+ const holds = decided(value[index]);
8969
+ if (holds === false) continue;
8970
+ const output = withinClasses(value[index + 1], drawn);
8971
+ if (holds === true) return branches.length ? ["case", ...branches, output] : output;
8972
+ branches.push(value[index], output);
8973
+ }
8974
+ const fallback = withinClasses(value.at(-1), drawn);
8975
+ return branches.length ? ["case", ...branches, fallback] : fallback;
8976
+ }
8143
8977
  function isSolidRoadDash(value) {
8144
8978
  const output = expressionOutput(value);
8145
8979
  const dash = Array.isArray(output) && output[0] === "literal" ? output[1] : output;
@@ -10890,21 +11724,21 @@ function bindSemanticValue(value, schema, sourceId) {
10890
11724
  }
10891
11725
  if (isDataSourceReference(value)) return sourceId;
10892
11726
  if (Array.isArray(value)) return value.map((item) => bindSemanticValue(item, schema, sourceId));
10893
- if (!isRecord4(value)) return value;
11727
+ if (!isRecord6(value)) return value;
10894
11728
  return Object.fromEntries(
10895
11729
  Object.entries(value).map(([key, item]) => [key, bindSemanticValue(item, schema, sourceId)])
10896
11730
  );
10897
11731
  }
10898
11732
  function isDataFieldReference2(value) {
10899
- return isRecord4(value) && value.kind === "tileflow-data-field" && typeof value.name === "string";
11733
+ return isRecord6(value) && value.kind === "tileflow-data-field" && typeof value.name === "string";
10900
11734
  }
10901
11735
  function isDataLayerReference(value) {
10902
- return isRecord4(value) && value.kind === "tileflow-data-layer" && typeof value.name === "string";
11736
+ return isRecord6(value) && value.kind === "tileflow-data-layer" && typeof value.name === "string";
10903
11737
  }
10904
11738
  function isDataSourceReference(value) {
10905
- return isRecord4(value) && value.kind === "tileflow-data-source" && value.name === "primary";
11739
+ return isRecord6(value) && value.kind === "tileflow-data-source" && value.name === "primary";
10906
11740
  }
10907
- function isRecord4(value) {
11741
+ function isRecord6(value) {
10908
11742
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
10909
11743
  }
10910
11744
  function compareCodeUnits5(left, right) {
@@ -11008,8 +11842,8 @@ function createTileflowLayerTemplateIR(layer, semanticKey) {
11008
11842
  renderer: type,
11009
11843
  ...filter === void 0 ? {} : { selector: cloneJson20(filter) },
11010
11844
  style: Object.freeze({
11011
- ...isRecord5(layout) ? { placement: Object.freeze(cloneJson20(layout)) } : {},
11012
- ...isRecord5(paint) ? { appearance: Object.freeze(cloneJson20(paint)) } : {}
11845
+ ...isRecord7(layout) ? { placement: Object.freeze(cloneJson20(layout)) } : {},
11846
+ ...isRecord7(paint) ? { appearance: Object.freeze(cloneJson20(paint)) } : {}
11013
11847
  })
11014
11848
  });
11015
11849
  }
@@ -11201,20 +12035,29 @@ var exactPhysicalLayerIds = Object.freeze({
11201
12035
  var nauticalPhysicalBases = Object.freeze({
11202
12036
  "nautical.aids": "tileflow-nautical-aids",
11203
12037
  "nautical.beacons": "tileflow-nautical-beacons",
12038
+ "nautical.berths": "tileflow-nautical-berths",
12039
+ "nautical.bridges": "tileflow-nautical-bridges",
11204
12040
  "nautical.buoys": "tileflow-nautical-buoys",
11205
12041
  "nautical.cables": "tileflow-nautical-cables",
11206
12042
  "nautical.coverage": "tileflow-nautical-coverage",
12043
+ "nautical.distanceMarks": "tileflow-nautical-distance-marks",
12044
+ "nautical.facilities": "tileflow-nautical-facilities",
12045
+ "nautical.harbourAreas": "tileflow-nautical-harbour-areas",
11207
12046
  "nautical.harbours": "tileflow-nautical-harbours",
11208
12047
  "nautical.hazardAreas": "tileflow-nautical-hazard-areas",
11209
12048
  "nautical.hazards": "tileflow-nautical-hazards",
12049
+ "nautical.labels.bridges": "tileflow-nautical-bridge-labels",
11210
12050
  "nautical.labels.coverage": "tileflow-nautical-coverage-labels",
11211
12051
  "nautical.labels.hazards": "tileflow-nautical-hazard-area-labels",
11212
12052
  "nautical.labels.navigationAreas": "tileflow-nautical-navigation-area-labels",
12053
+ "nautical.labels.overheadCables": "tileflow-nautical-overhead-cable-labels",
11213
12054
  "nautical.labels.reefs": "tileflow-nautical-reef-labels",
11214
12055
  "nautical.labels.wrecks": "tileflow-nautical-wreck-area-labels",
11215
12056
  "nautical.lighthouses": "tileflow-nautical-lighthouses",
11216
12057
  "nautical.lights": "tileflow-nautical-lights",
11217
12058
  "nautical.navigationAreas": "tileflow-nautical-navigation-areas",
12059
+ "nautical.notices": "tileflow-nautical-notices",
12060
+ "nautical.overheadCables": "tileflow-nautical-overhead-cables",
11218
12061
  "nautical.ports": "tileflow-nautical-ports",
11219
12062
  "nautical.reefs": "tileflow-nautical-reefs",
11220
12063
  "nautical.routes": "tileflow-nautical-routes",
@@ -11225,7 +12068,9 @@ var nauticalPhysicalBases = Object.freeze({
11225
12068
  function nauticalPhysicalLayerId(semanticKey) {
11226
12069
  const direct = nauticalPhysicalBases[semanticKey];
11227
12070
  if (direct) return direct;
11228
- const match3 = /^(nautical\.[A-Za-z0-9_.-]+)\.(fill|outline|marker|symbol)$/u.exec(semanticKey);
12071
+ const match3 = /^(nautical\.[A-Za-z0-9_.-]+)\.(fill|outline|marker|symbol|lines)$/u.exec(
12072
+ semanticKey
12073
+ );
11229
12074
  if (!match3) return void 0;
11230
12075
  const base = nauticalPhysicalBases[match3[1]];
11231
12076
  if (!base) return void 0;
@@ -11269,21 +12114,21 @@ function visitReferences(value, counts, visited = /* @__PURE__ */ new WeakSet())
11269
12114
  }
11270
12115
  }
11271
12116
  function isSemanticFieldReference(value) {
11272
- return isRecord5(value) && value.kind === "tileflow-data-field";
12117
+ return isRecord7(value) && value.kind === "tileflow-data-field";
11273
12118
  }
11274
12119
  function isSemanticLayerReference(value) {
11275
- return isRecord5(value) && value.kind === "tileflow-data-layer";
12120
+ return isRecord7(value) && value.kind === "tileflow-data-layer";
11276
12121
  }
11277
12122
  function isSemanticSourceReference(value) {
11278
- return isRecord5(value) && value.kind === "tileflow-data-source";
12123
+ return isRecord7(value) && value.kind === "tileflow-data-source";
11279
12124
  }
11280
12125
  function cloneJson20(value) {
11281
12126
  return JSON.parse(JSON.stringify(value));
11282
12127
  }
11283
12128
  function asRecord4(value) {
11284
- return isRecord5(value) ? value : {};
12129
+ return isRecord7(value) ? value : {};
11285
12130
  }
11286
- function isRecord5(value) {
12131
+ function isRecord7(value) {
11287
12132
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
11288
12133
  }
11289
12134
 
@@ -11335,7 +12180,7 @@ function validateTileflowRenderSelectorConstraints(input) {
11335
12180
  };
11336
12181
  while (pending.length > 0) {
11337
12182
  const current = pending.pop();
11338
- if (!isRecord6(current.selector) || typeof current.selector.kind !== "string") {
12183
+ if (!isRecord8(current.selector) || typeof current.selector.kind !== "string") {
11339
12184
  addIssue(
11340
12185
  "invalid-selector",
11341
12186
  "a render selector must be a semantic selector node",
@@ -11396,7 +12241,7 @@ function validateTileflowRenderSelectorConstraints(input) {
11396
12241
  }
11397
12242
  for (const [index, branch] of current.selector.branches.entries()) {
11398
12243
  const branchPath = [...branchesPath, index];
11399
- if (!isRecord6(branch)) {
12244
+ if (!isRecord8(branch)) {
11400
12245
  addIssue("invalid-selector", "match selector branch must be an object", branchPath);
11401
12246
  continue;
11402
12247
  }
@@ -11427,14 +12272,14 @@ function validateTileflowRenderSelectorConstraints(input) {
11427
12272
  for (let index = current.selector.stops.length - 1; index >= 0; index -= 1) {
11428
12273
  const stop = current.selector.stops[index];
11429
12274
  const stopPath = [...stopsPath, index];
11430
- if (!isRecord6(stop)) {
12275
+ if (!isRecord8(stop)) {
11431
12276
  addIssue("invalid-selector", "step selector stop must be an object", stopPath);
11432
12277
  continue;
11433
12278
  }
11434
12279
  pushChild(stop.selector, current.depth + 1, [...stopPath, "selector"]);
11435
12280
  }
11436
12281
  for (const [index, stop] of current.selector.stops.entries()) {
11437
- if (!isRecord6(stop)) continue;
12282
+ if (!isRecord8(stop)) continue;
11438
12283
  const zoom3 = stop.zoom;
11439
12284
  const zoomPath = [...stopsPath, index, "zoom"];
11440
12285
  if (typeof zoom3 !== "number" || !Number.isFinite(zoom3) || zoom3 < 0 || zoom3 > 24) {
@@ -11498,7 +12343,7 @@ function compileRenderSelector(selector, data) {
11498
12343
  assertRenderSelectorConstraints(selector);
11499
12344
  return lowerSelector(selector, data);
11500
12345
  }
11501
- function applyCompiledRenderStacks(input, operations) {
12346
+ function applyCompiledRenderStacks(input, operations, targetsOfOtherModes = /* @__PURE__ */ new Map()) {
11502
12347
  let layers2 = input.map(cloneJson21);
11503
12348
  const passes = operations.filter(
11504
12349
  (operation) => operation.kind === "layer"
@@ -11529,9 +12374,10 @@ function applyCompiledRenderStacks(input, operations) {
11529
12374
  for (const refinement of activeRefinements) {
11530
12375
  const index = findTargetIndex(layers2, refinement.target);
11531
12376
  if (index < 0 && !passTargets.has(refinement.target)) {
12377
+ const other = targetsOfOtherModes.get(refinement.target);
11532
12378
  throw renderStackError(
11533
12379
  "unknown-refinement-target",
11534
- `Render refinement targets unknown semantic contribution: ${refinement.target}.`
12380
+ other === void 0 ? `Render refinement targets unknown semantic contribution: ${refinement.target}.` : `Render refinement targets ${refinement.target}, ${describeOtherMode(other)}`
11535
12381
  );
11536
12382
  }
11537
12383
  if (refinement.placement !== void 0 && placementAnchor(refinement.placement) === refinement.target) {
@@ -11553,9 +12399,10 @@ function applyCompiledRenderStacks(input, operations) {
11553
12399
  for (const operation of pending) {
11554
12400
  const anchor = placementAnchor(operation.placement);
11555
12401
  if (anchor !== void 0 && !knownTargets.has(anchor)) {
12402
+ const other = targetsOfOtherModes.get(anchor);
11556
12403
  throw renderStackError(
11557
12404
  "unknown-or-circular-anchor",
11558
- `${operation.target} is placed beside unknown semantic target ${anchor}.`
12405
+ other === void 0 ? `${operation.target} is placed beside unknown semantic target ${anchor}.` : `${operation.target} is placed beside ${anchor}, ${describeOtherMode(other)}`
11559
12406
  );
11560
12407
  }
11561
12408
  }
@@ -11759,7 +12606,7 @@ function applyRendererStyle(layer, renderer, style) {
11759
12606
  };
11760
12607
  }
11761
12608
  function lowerSelector(selector, data) {
11762
- if (!isRecord6(selector) || typeof selector.kind !== "string") {
12609
+ if (!isRecord8(selector) || typeof selector.kind !== "string") {
11763
12610
  throw renderStackError(
11764
12611
  "invalid-selector",
11765
12612
  "A render selector must be a semantic selector node."
@@ -11962,7 +12809,7 @@ function requireRenderer(value) {
11962
12809
  function requirePlacement(value) {
11963
12810
  if (typeof value === "string") {
11964
12811
  if (tileflowBandPlacements.includes(value)) return;
11965
- } else if (isRecord6(value)) {
12812
+ } else if (isRecord8(value)) {
11966
12813
  const keys = Object.keys(value);
11967
12814
  if (keys.length === 1 && (keys[0] === "above" || keys[0] === "below")) {
11968
12815
  requirePortableTarget(value[keys[0]], "render placement target");
@@ -11989,7 +12836,7 @@ function validateRefinementShape(operation) {
11989
12836
  validateStyle(operation.renderer, operation.style);
11990
12837
  }
11991
12838
  function validateStyle(renderer, style) {
11992
- if (!isRecord6(style)) {
12839
+ if (!isRecord8(style)) {
11993
12840
  throw renderStackError(
11994
12841
  "invalid-style",
11995
12842
  `A ${renderer} render operation requires a style object.`
@@ -12091,7 +12938,7 @@ function normalizeRequirements(requirements) {
12091
12938
  return [...requirements];
12092
12939
  }
12093
12940
  function requireRenderStackEntries(stack) {
12094
- if (!isRecord6(stack)) {
12941
+ if (!isRecord8(stack)) {
12095
12942
  throw renderStackError("invalid-stack", "A render stack must be a named operation record.");
12096
12943
  }
12097
12944
  const entries = Object.entries(stack);
@@ -12228,6 +13075,9 @@ function assertOnlyKeys(input, allowed, description) {
12228
13075
  );
12229
13076
  }
12230
13077
  }
13078
+ function describeOtherMode({ activeMode, mode, owner }) {
13079
+ return `which ${owner} draws only in mode '${mode}'; this map draws ${owner} in mode '${activeMode}'. Place the operation beside a target drawn in mode '${activeMode}' or at a band edge, or remove it; refine({mode: '${activeMode}', renderStack: reset()}) removes an inherited ${owner} render stack.`;
13080
+ }
12231
13081
  function renderStackError(code, message) {
12232
13082
  return Object.assign(new Error(message), { code, name: "TileflowRenderStackError" });
12233
13083
  }
@@ -12238,9 +13088,9 @@ function cloneJson21(value) {
12238
13088
  return JSON.parse(JSON.stringify(value));
12239
13089
  }
12240
13090
  function asRecord5(value) {
12241
- return isRecord6(value) ? value : {};
13091
+ return isRecord8(value) ? value : {};
12242
13092
  }
12243
- function isRecord6(value) {
13093
+ function isRecord8(value) {
12244
13094
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
12245
13095
  }
12246
13096
 
@@ -12448,6 +13298,9 @@ function createTileflowTimeOfDaySchema(operations, themeName) {
12448
13298
  );
12449
13299
  }
12450
13300
 
13301
+ // src/camera-limits.ts
13302
+ var tileflowMaxPitch = 85;
13303
+
12451
13304
  // src/resolved-map-schema.ts
12452
13305
  var TileflowResolvedMapValidationError = class extends Error {
12453
13306
  code = "CONFIG_INVALID";
@@ -13311,6 +14164,7 @@ function boundedThemeNumberSchema(min, max) {
13311
14164
  }).strict()
13312
14165
  ]);
13313
14166
  }
14167
+ var entranceBandSchema = z5.tuple([z5.number().finite().min(0).max(24), z5.number().finite().min(0).max(24)]).refine(([from, to]) => from < to, "A band starts at a lower zoom than it ends.");
13314
14168
  var modelRoleLightingSchema = z5.object({
13315
14169
  emissiveStrength: boundedThemeNumberSchema(0, 5).optional(),
13316
14170
  emissiveVariation: boundedThemeNumberSchema(0, 1).optional(),
@@ -13366,6 +14220,7 @@ var buildingsModuleSchema = z5.object({
13366
14220
  lamp: modelRoleLightingSchema.optional(),
13367
14221
  logo: modelRoleLightingSchema.optional()
13368
14222
  }).strict().optional(),
14223
+ entrance: z5.object({ buildings: entranceBandSchema.optional(), models: entranceBandSchema.optional() }).strict().optional(),
13369
14224
  occlusion: z5.boolean().optional(),
13370
14225
  modelOcclusion: boundedThemeNumberSchema(0, 1).optional(),
13371
14226
  shadows: z5.boolean().optional(),
@@ -13604,24 +14459,34 @@ var poiModuleSchema = z5.object({
13604
14459
  var nauticalModuleSchema = z5.object({
13605
14460
  type: z5.literal("nautical"),
13606
14461
  aids: markerSymbolStyleSchema.optional(),
14462
+ animateLights: z5.boolean().optional(),
13607
14463
  beacons: markerSymbolStyleSchema.optional(),
14464
+ berths: areaStyleSchema.optional(),
14465
+ bridges: areaStyleSchema.optional(),
13608
14466
  buoys: markerSymbolStyleSchema.optional(),
13609
14467
  cables: lineStyleSchema.optional(),
13610
14468
  coverage: areaStyleSchema.optional(),
14469
+ distanceMarks: markerSymbolStyleSchema.optional(),
13611
14470
  enabled: z5.boolean().optional(),
14471
+ facilities: markerSymbolStyleSchema.optional(),
14472
+ harbourAreas: areaStyleSchema.optional(),
13612
14473
  harbours: markerSymbolStyleSchema.optional(),
13613
14474
  hazardAreas: areaStyleSchema.optional(),
13614
14475
  hazards: markerSymbolStyleSchema.optional(),
13615
14476
  labels: z5.object({
14477
+ bridges: symbolStyleSchema.optional(),
13616
14478
  coverage: symbolStyleSchema.optional(),
13617
14479
  hazards: symbolStyleSchema.optional(),
13618
14480
  navigationAreas: symbolStyleSchema.optional(),
14481
+ overheadCables: symbolStyleSchema.optional(),
13619
14482
  reefs: symbolStyleSchema.optional(),
13620
14483
  wrecks: symbolStyleSchema.optional()
13621
14484
  }).strict().optional(),
13622
14485
  lighthouses: markerSymbolStyleSchema.optional(),
13623
14486
  lights: markerSymbolStyleSchema.optional(),
13624
14487
  navigationAreas: areaStyleSchema.optional(),
14488
+ notices: markerSymbolStyleSchema.optional(),
14489
+ overheadCables: lineStyleSchema.optional(),
13625
14490
  ports: markerSymbolStyleSchema.optional(),
13626
14491
  reefs: areaStyleSchema.optional(),
13627
14492
  renderStack: renderStackSchema.optional(),
@@ -13745,6 +14610,7 @@ var layerBindingsSchema = z5.object({
13745
14610
  streetFurniture: z5.string().trim().min(1).optional(),
13746
14611
  trafficControl: z5.string().trim().min(1).optional(),
13747
14612
  tree: z5.string().trim().min(1).optional(),
14613
+ transportationOutline: z5.string().trim().min(1).optional(),
13748
14614
  water: z5.string().trim().min(1),
13749
14615
  waterName: z5.string().trim().min(1),
13750
14616
  waterway: z5.string().trim().min(1)
@@ -14153,7 +15019,7 @@ var terrainContoursSchema = z5.object({
14153
15019
  demUrl: z5.string().min(1).max(2048).refine(
14154
15020
  isSafeTileflowDemUrlTemplate,
14155
15021
  "Expected a safe HTTP(S) DEM URL template containing {z}, {x}, and {y}"
14156
- ),
15022
+ ).optional(),
14157
15023
  index: terrainContourLineSchema.optional(),
14158
15024
  labels: terrainContourLabelSchema.optional(),
14159
15025
  maxZoom: z5.number().int().min(0).max(24).optional(),
@@ -14277,10 +15143,25 @@ var atmosphereSchema = z5.union([
14277
15143
  starParallax: z5.number().finite().min(0).max(1).optional()
14278
15144
  }).strict()
14279
15145
  ]);
15146
+ var skyNumberSchema = z5.union([
15147
+ z5.number().finite().min(0).max(1),
15148
+ fixedNumberSchema.extend({ value: z5.number().finite().min(0).max(1) }),
15149
+ themeNumberReferenceSchema
15150
+ ]);
15151
+ var skySchema = z5.union([
15152
+ z5.boolean(),
15153
+ z5.object({
15154
+ color: atmosphereColorSchema.optional(),
15155
+ horizonColor: atmosphereColorSchema.optional(),
15156
+ horizonBlend: skyNumberSchema.optional(),
15157
+ glowColor: atmosphereColorSchema.optional(),
15158
+ starIntensity: skyNumberSchema.optional()
15159
+ }).strict()
15160
+ ]);
14280
15161
  var viewSchema = z5.object({
14281
15162
  bearing: z5.number().finite().min(-180).max(180).optional(),
14282
15163
  center: z5.tuple([z5.number().finite().min(-180).max(180), z5.number().finite().min(-90).max(90)]).optional(),
14283
- pitch: z5.number().finite().min(0).max(85).optional(),
15164
+ pitch: z5.number().finite().min(0).max(tileflowMaxPitch).optional(),
14284
15165
  zoom: zoomNumberSchema.optional()
14285
15166
  }).strict();
14286
15167
  var themesSchema = z5.record(tileflowThemeNameSchema, tileflowThemeSchema).superRefine((themes, context) => {
@@ -14323,6 +15204,7 @@ var resolvedTileflowMapSchema = z5.object({
14323
15204
  name: z5.string().trim().min(1),
14324
15205
  overlays: overlaysSchema.optional(),
14325
15206
  projection: z5.enum(["globe", "mercator"]).optional(),
15207
+ sky: skySchema.optional(),
14326
15208
  sources: hostedSourcesSchema.optional(),
14327
15209
  systemThemes: systemThemesSchema.optional(),
14328
15210
  terrain: terrainSchema.optional(),
@@ -14610,7 +15492,7 @@ function resolveTerrain(terrain, apiBaseUrl) {
14610
15492
  const mode = terrainConfig.mode ?? "hillshade";
14611
15493
  const encoding = terrainConfig.encoding ?? "terrarium";
14612
15494
  const raster = mode === "none" ? void 0 : resolveRaster(terrainConfig, apiBaseUrl, encoding);
14613
- const contours = terrainConfig.contours ? resolveContours(terrainConfig.contours, encoding, terrainConfig.attribution) : void 0;
15495
+ const contours = terrainConfig.contours ? resolveContours(terrainConfig.contours, encoding, terrainConfig.attribution, apiBaseUrl) : void 0;
14614
15496
  if (!raster && !contours) return void 0;
14615
15497
  return {
14616
15498
  ...contours ? { contours } : {},
@@ -14742,7 +15624,7 @@ function resolveRaster(terrain, apiBaseUrl, encoding) {
14742
15624
  }
14743
15625
  };
14744
15626
  }
14745
- function resolveContours(contours, encoding, attribution) {
15627
+ function resolveContours(contours, encoding, attribution, apiBaseUrl) {
14746
15628
  const thresholdZooms = Object.keys(contours.thresholds).map(Number);
14747
15629
  const minimumZoom2 = contours.minZoom ?? Math.min(...thresholdZooms);
14748
15630
  const maximumZoom2 = contours.maxZoom ?? Math.max(...thresholdZooms);
@@ -14750,7 +15632,7 @@ function resolveContours(contours, encoding, attribution) {
14750
15632
  const tiles = [
14751
15633
  createTileflowContourProtocolUrl({
14752
15634
  demMaxzoom: contours.demMaxZoom,
14753
- demUrl: contours.demUrl,
15635
+ demUrl: contours.demUrl ?? `${apiBaseUrl}/tiles/terrain/{z}/{x}/{y}.webp`,
14754
15636
  encoding,
14755
15637
  maxzoom: maximumZoom2,
14756
15638
  multiplier: contours.multiplier ?? 1,
@@ -15351,11 +16233,11 @@ function isTileflowPoiCategory(value) {
15351
16233
  return typeof value === "string" && tileflowPoiCategories.includes(value);
15352
16234
  }
15353
16235
  function semanticTarget(layer) {
15354
- if (!isRecord7(layer.metadata)) return void 0;
16236
+ if (!isRecord9(layer.metadata)) return void 0;
15355
16237
  const value = layer.metadata[tileflowCompilerMetadataKeys.target];
15356
16238
  return typeof value === "string" ? value : void 0;
15357
16239
  }
15358
- function isRecord7(value) {
16240
+ function isRecord9(value) {
15359
16241
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
15360
16242
  }
15361
16243
  function isSafeArtifactName(value) {
@@ -15536,6 +16418,8 @@ function mergeRoadLineGroup(key, layers2) {
15536
16418
  const paintKeys = new Set(layers2.flatMap((layer) => Object.keys(layer.style.appearance ?? {})));
15537
16419
  for (const key2 of paintKeys) {
15538
16420
  const values = layers2.map((layer) => layer.style.appearance?.[key2]);
16421
+ if (key2 === "line-dasharray" && values.every((value) => isSolidDash(value ?? linePaintDefaults[key2])))
16422
+ continue;
15539
16423
  const combined = combineConditionalValues(
15540
16424
  values.map((value, index) => ({
15541
16425
  condition: conditions[index],
@@ -16251,7 +17135,7 @@ var colourSources = [
16251
17135
  ["tileflow-buildings-fill", "fill-color"]
16252
17136
  ];
16253
17137
  function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, tileflowLandmarks) {
16254
- const config = isRecord8(module) ? module : {};
17138
+ const config = isRecord10(module) ? module : {};
16255
17139
  if (config.enabled === false) return void 0;
16256
17140
  const style = config.threeDimensional ?? {};
16257
17141
  const defaults4 = tileflowDefaultBuildings3DDesign;
@@ -16284,7 +17168,7 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
16284
17168
  edgeRadius: number(style.edgeRadius, defaults4.edgeRadius),
16285
17169
  ...typeof edgeColor === "string" ? { edgeColor } : {},
16286
17170
  nightLight: number(style.nightLight, 0),
16287
- ...isRecord8(style.floodLight) ? {
17171
+ ...isRecord10(style.floodLight) ? {
16288
17172
  floodLight: readBuildingFloodLight({
16289
17173
  ...style.floodLight,
16290
17174
  intensity: number(style.floodLight.intensity, 0),
@@ -16292,10 +17176,10 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
16292
17176
  color: style.floodLight.color === void 0 ? void 0 : toMapLibreStyleValue(style.floodLight.color)
16293
17177
  })
16294
17178
  } : {},
16295
- ...isRecord8(style.modelLighting) ? {
17179
+ ...isRecord10(style.modelLighting) ? {
16296
17180
  modelLighting: readModelRoleLighting(
16297
17181
  Object.fromEntries(
16298
- Object.entries(style.modelLighting).filter(([, entry]) => isRecord8(entry)).map(([role, entry]) => {
17182
+ Object.entries(style.modelLighting).filter(([, entry]) => isRecord10(entry)).map(([role, entry]) => {
16299
17183
  const own = entry;
16300
17184
  return [
16301
17185
  role,
@@ -16306,7 +17190,7 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
16306
17190
  ["emissiveVariation", "colorMix", "metallic", "roughness"].filter((key) => own[key] !== void 0).map((key) => [key, number(own[key], 0)])
16307
17191
  ),
16308
17192
  ...Array.isArray(own.emissiveHeight) ? { emissiveHeight: own.emissiveHeight.map((v) => number(v, 0)) } : {},
16309
- ...isRecord8(own.colorVariation) ? {
17193
+ ...isRecord10(own.colorVariation) ? {
16310
17194
  colorVariation: {
16311
17195
  ...own.colorVariation,
16312
17196
  ...Object.fromEntries(
@@ -16328,6 +17212,7 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
16328
17212
  )
16329
17213
  } : {},
16330
17214
  windows: style.windows === true,
17215
+ ...readTileflowBuildingsEntrance(style.entrance),
16331
17216
  shadows: typeof style.shadows === "boolean" ? style.shadows : defaults4.shadows,
16332
17217
  occlusion: typeof style.occlusion === "boolean" ? style.occlusion : defaults4.occlusion,
16333
17218
  modelOcclusion: number(style.modelOcclusion, defaults4.modelOcclusion),
@@ -16338,7 +17223,7 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
16338
17223
  }
16339
17224
  function landmarksOf(value, tileflowLandmarks) {
16340
17225
  if (value === false) return {};
16341
- if (isRecord8(value) && typeof value.url === "string") return { landmarks: { url: value.url } };
17226
+ if (isRecord10(value) && typeof value.url === "string") return { landmarks: { url: value.url } };
16342
17227
  return tileflowLandmarks ? { landmarks: { url: tileflowLandmarks } } : {};
16343
17228
  }
16344
17229
  function placedModels(value, served) {
@@ -16365,7 +17250,7 @@ function placedModels(value, served) {
16365
17250
  };
16366
17251
  }
16367
17252
  function modelColours(value) {
16368
- if (!isRecord8(value)) return {};
17253
+ if (!isRecord10(value)) return {};
16369
17254
  const colours = {};
16370
17255
  for (const [part, colour] of Object.entries(value)) {
16371
17256
  const resolved = colour === void 0 ? void 0 : toMapLibreStyleValue(colour);
@@ -16374,7 +17259,7 @@ function modelColours(value) {
16374
17259
  return Object.keys(colours).length > 0 ? { modelColors: colours } : {};
16375
17260
  }
16376
17261
  function tileflowStreetLightsDesignOf(module, context) {
16377
- const config = isRecord8(module) ? module : {};
17262
+ const config = isRecord10(module) ? module : {};
16378
17263
  const layers2 = context.data.schema.layers;
16379
17264
  const style = config.streetLights ?? {};
16380
17265
  if (config.enabled === false || style.enabled !== true || !layers2.streetFurniture)
@@ -16394,7 +17279,7 @@ function tileflowStreetLightsDesignOf(module, context) {
16394
17279
  intensity: typeof intensity === "number" ? intensity : 1
16395
17280
  };
16396
17281
  }
16397
- function isRecord8(value) {
17282
+ function isRecord10(value) {
16398
17283
  return typeof value === "object" && value !== null && !Array.isArray(value);
16399
17284
  }
16400
17285
 
@@ -16566,7 +17451,7 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
16566
17451
  )
16567
17452
  );
16568
17453
  const renderStackModules = Object.values(modules).filter(
16569
- (module) => isRecord9(module) && module.enabled !== false && "renderStack" in module && isRecord9(module.renderStack)
17454
+ (module) => isRecord11(module) && module.enabled !== false && "renderStack" in module && isRecord11(module.renderStack)
16570
17455
  );
16571
17456
  const renderOperations = runCompilationPhase(
16572
17457
  "render-stack",
@@ -16592,7 +17477,11 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
16592
17477
  const requestedRenderOperations = domainIR.renderOperations;
16593
17478
  const stackedFamilies = runCompilationPhase(
16594
17479
  "render-stack",
16595
- () => applyCompiledRenderStacks(assembledFamilies, requestedRenderOperations)
17480
+ () => applyCompiledRenderStacks(
17481
+ assembledFamilies,
17482
+ requestedRenderOperations,
17483
+ buildingTargetsOfOtherModes(modules.buildings)
17484
+ )
16596
17485
  );
16597
17486
  const plannedFamilies = runCompilationPhase(
16598
17487
  "physical-planner",
@@ -16808,9 +17697,15 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
16808
17697
  layer.layout = { ...layer.layout, visibility: "none" };
16809
17698
  }
16810
17699
  const atmosphere = resolveTileflowAtmosphere(config.atmosphere);
17700
+ const sky = resolveTileflowSky(config.sky);
16811
17701
  const notices = compiledNotices(layers2, marine);
17702
+ const nauticalLights = declareTileflowNauticalLights(
17703
+ layers2,
17704
+ marine?.nautical?.sourceId,
17705
+ tileflowNauticalV1Schema.layers.lights
17706
+ );
16812
17707
  const style = {
16813
- ...atmosphere ? { sky: compileTileflowSky(atmosphere) } : {},
17708
+ ...sky ? { sky: compileTileflowStreetSky(sky) } : atmosphere ? { sky: compileTileflowSky(atmosphere) } : {},
16814
17709
  version: 8,
16815
17710
  name: config.name ?? "Streets",
16816
17711
  ...glyphs ? { glyphs } : {},
@@ -16829,6 +17724,7 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
16829
17724
  ...mapMetadata.lineage && mapMetadata.lineage.length > 1 ? { "tileflow:extends": mapMetadata.lineage.slice(1) } : {}
16830
17725
  } : {},
16831
17726
  ...atmosphere ? { [tileflowAtmosphereMetadataKey]: { version: 1, ...atmosphere } } : {},
17727
+ ...sky ? { [tileflowSkyMetadataKey]: skyMetadata(input, selected.name, sky) } : {},
16832
17728
  "tileflow:theme": selected.name,
16833
17729
  "tileflow:colorScheme": resolvedTheme.colorScheme,
16834
17730
  [tileflowRouteMetadataKey]: tileflowRouteMetadata(resolveTileflowRouteInks(resolvedTheme)),
@@ -16857,9 +17753,10 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
16857
17753
  "tileflow:sourceRequirements": sourceRequirements,
16858
17754
  ...interactionManifest ? { [tileflowInteractionManifestMetadataKey]: interactionManifest } : {},
16859
17755
  [tileflowOverlayPlacementManifestMetadataKey]: overlayPlacementManifest,
16860
- "tileflow:modules": Object.entries(modules).filter(([, module]) => !isRecord9(module) || module.enabled !== false).map(([name]) => name).sort(),
17756
+ "tileflow:modules": Object.entries(modules).filter(([, module]) => !isRecord11(module) || module.enabled !== false).map(([name]) => name).sort(),
16861
17757
  ...config.view ? { "tileflow:view": config.view } : {},
16862
- ...notices.length > 0 ? { [tileflowNoticesMetadataKey]: notices } : {}
17758
+ ...notices.length > 0 ? { [tileflowNoticesMetadataKey]: notices } : {},
17759
+ ...nauticalLights ? { [tileflowNauticalLightsMetadataKey]: nauticalLights } : {}
16863
17760
  }
16864
17761
  };
16865
17762
  const report = {
@@ -16919,7 +17816,7 @@ function finalizeTileflowLayers(input) {
16919
17816
  if (typeof minimum2 === "number" && typeof maximum2 === "number" && minimum2 > maximum2) {
16920
17817
  throw new Error(`Compiled Tileflow layer ${id2} requires minzoom <= maxzoom.`);
16921
17818
  }
16922
- const metadata = isRecord9(layer.metadata) ? { ...layer.metadata } : void 0;
17819
+ const metadata = isRecord11(layer.metadata) ? { ...layer.metadata } : void 0;
16923
17820
  if (metadata) {
16924
17821
  const owner = metadata[tileflowCompilerMetadataKeys.owner];
16925
17822
  delete metadata[tileflowCompilerProvenanceMetadataKey];
@@ -16953,7 +17850,7 @@ function assertMapLibreStyle(style, mapId) {
16953
17850
  }
16954
17851
  );
16955
17852
  }
16956
- function isRecord9(value) {
17853
+ function isRecord11(value) {
16957
17854
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
16958
17855
  }
16959
17856
  function compiledNotices(layers2, marine) {
@@ -16998,8 +17895,8 @@ function assertPreparedIconReferences(style, config, prepared) {
16998
17895
  function collectStyleImageReferences(style) {
16999
17896
  const result = /* @__PURE__ */ new Set();
17000
17897
  for (const layer of style.layers) {
17001
- const layout = isRecord9(layer.layout) ? layer.layout : {};
17002
- const paint = isRecord9(layer.paint) ? layer.paint : {};
17898
+ const layout = isRecord11(layer.layout) ? layer.layout : {};
17899
+ const paint = isRecord11(layer.paint) ? layer.paint : {};
17003
17900
  const layerId = typeof layer.id === "string" ? layer.id : "<unknown>";
17004
17901
  for (const [property, value] of [
17005
17902
  ["icon-image", layout["icon-image"]],
@@ -17088,7 +17985,7 @@ function assertGlyphFontStacks(style, config) {
17088
17985
  const declared = new Set(config.glyphs.fontStacks);
17089
17986
  const missing = /* @__PURE__ */ new Set();
17090
17987
  for (const layer of style.layers) {
17091
- const layout = isRecord9(layer.layout) ? layer.layout : void 0;
17988
+ const layout = isRecord11(layer.layout) ? layer.layout : void 0;
17092
17989
  const font = layout?.["text-font"];
17093
17990
  if (!Array.isArray(font) || !font.every((entry) => typeof entry === "string")) continue;
17094
17991
  const stack = font.join(",");
@@ -17104,7 +18001,7 @@ function assertGlyphFontStacks(style, config) {
17104
18001
  function assertTextAssets(style, config) {
17105
18002
  const textLayers = [];
17106
18003
  for (const layer of style.layers) {
17107
- const layout = isRecord9(layer.layout) ? layer.layout : void 0;
18004
+ const layout = isRecord11(layer.layout) ? layer.layout : void 0;
17108
18005
  if (layout?.["text-field"] === void 0) continue;
17109
18006
  textLayers.push(typeof layer.id === "string" ? layer.id : "<unknown>");
17110
18007
  const font = layout["text-font"];
@@ -17117,7 +18014,7 @@ function assertTextAssets(style, config) {
17117
18014
  if (textLayers.length === 0) return;
17118
18015
  if (config.fonts !== void 0) {
17119
18016
  const backed = style.layers.find((layer) => {
17120
- const font = isRecord9(layer.layout) ? layer.layout["text-font"] : void 0;
18017
+ const font = isRecord11(layer.layout) ? layer.layout["text-font"] : void 0;
17121
18018
  return Array.isArray(font) && font.length === 1 && typeof font[0] === "string" && parseTileflowLineBackgroundFontStack(font[0]) !== void 0;
17122
18019
  });
17123
18020
  if (backed) {
@@ -17143,7 +18040,7 @@ function runCompilationPhase(phase, operation) {
17143
18040
  }
17144
18041
  }
17145
18042
  function withCompilationPhase(error, phase) {
17146
- const record4 = isRecord9(error) ? error : {};
18043
+ const record4 = isRecord11(error) ? error : {};
17147
18044
  if (typeof record4.phase === "string") return error;
17148
18045
  if (error instanceof Error && Object.isExtensible(error)) {
17149
18046
  Object.defineProperty(error, "phase", { configurable: true, enumerable: true, value: phase });
@@ -17174,6 +18071,35 @@ function compareCodeUnits7(left, right) {
17174
18071
  function semanticReportKey(...parts) {
17175
18072
  return parts.join("\0");
17176
18073
  }
18074
+ function skyMetadata(input, themeName, sky) {
18075
+ const family = Object.values(input.timeOfDay ?? {}).find(
18076
+ (moments) => Object.values(moments).includes(themeName)
18077
+ );
18078
+ const colours = (name) => {
18079
+ const theme = name === void 0 ? void 0 : input.themes[name];
18080
+ if (!theme) return void 0;
18081
+ const resolved = resolveTileflowSky(
18082
+ resolveThemeValues({ sky: input.sky }, theme, `map.${input.id}.themes.${name}`).sky
18083
+ );
18084
+ return resolved ? {
18085
+ color: resolved.color,
18086
+ horizonColor: resolved.horizonColor,
18087
+ horizonBlend: resolved.horizonBlend,
18088
+ glowColor: resolved.glowColor
18089
+ } : void 0;
18090
+ };
18091
+ const day = colours(family?.day);
18092
+ const night = colours(family?.night);
18093
+ const dawn = colours(family?.dawn);
18094
+ const dusk = colours(family?.dusk);
18095
+ const timesOfDay = day && night && (dawn ?? dusk) ? { day, night, dawn: dawn ?? dusk, dusk: dusk ?? dawn } : void 0;
18096
+ return {
18097
+ version: 1,
18098
+ starIntensity: sky.starIntensity,
18099
+ glowColor: sky.glowColor,
18100
+ ...timesOfDay ? { timesOfDay } : {}
18101
+ };
18102
+ }
17177
18103
 
17178
18104
  // src/manifest-types.ts
17179
18105
  var tileflowRuntimeManifestVersion = 1;
@@ -17186,7 +18112,7 @@ function createTileflowRuntimeViewSchema(operations) {
17186
18112
  center: operations.optional(
17187
18113
  operations.tuplePair(operations.number(-180, 180), operations.number(-90, 90))
17188
18114
  ),
17189
- pitch: operations.optional(operations.number(0, 85)),
18115
+ pitch: operations.optional(operations.number(0, tileflowMaxPitch)),
17190
18116
  zoom: operations.optional(operations.number(0, 24))
17191
18117
  });
17192
18118
  }
@@ -17460,7 +18386,7 @@ var tileflowCaptureSceneLimits = Object.freeze({
17460
18386
  boundsPadding: { maximum: 1024, minimum: 0 },
17461
18387
  captureIdLength: tileflowPortableIdMaximumLength,
17462
18388
  maximumPhysicalPixels: 16777216,
17463
- pitch: { maximum: 85, minimum: 0 },
18389
+ pitch: { maximum: tileflowMaxPitch, minimum: 0 },
17464
18390
  selectorLength: 256,
17465
18391
  viewport: { maximum: 4096, minimum: 64 },
17466
18392
  zoom: { maximum: 24, minimum: 0 }
@@ -17713,6 +18639,7 @@ var tileflowMapMergeStrategies = {
17713
18639
  projection: "atomic",
17714
18640
  scenes: "leaf",
17715
18641
  shots: "leaf",
18642
+ sky: "atomic",
17716
18643
  sources: "keyed-resources",
17717
18644
  systemThemes: "atomic",
17718
18645
  terrain: "atomic",
@@ -17894,6 +18821,9 @@ function applyModuleOperation(resolved, moduleName, operation, mapId, path) {
17894
18821
  assertNoReset(patch, mapId, `${path}/patches/${index}`, true);
17895
18822
  next = applyModulePatch(next, patch, mapId, `${path}/patches/${index}`);
17896
18823
  }
18824
+ if (isPlainRecord4(next.renderStack) && Object.keys(next.renderStack).length === 0) {
18825
+ delete next.renderStack;
18826
+ }
17897
18827
  assertModuleOwner(next, moduleName, mapId, path);
17898
18828
  resolved[moduleName] = next;
17899
18829
  return;
@@ -18056,11 +18986,11 @@ function cloneDesign(value) {
18056
18986
  function describeMap(value) {
18057
18987
  return typeof value.id === "string" && value.id ? value.id : "<anonymous>";
18058
18988
  }
18059
- function isRecord10(value) {
18989
+ function isRecord12(value) {
18060
18990
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
18061
18991
  }
18062
18992
  function isPlainRecord4(value) {
18063
- if (!isRecord10(value)) return false;
18993
+ if (!isRecord12(value)) return false;
18064
18994
  const prototype = Object.getPrototypeOf(value);
18065
18995
  return prototype === Object.prototype || prototype === null;
18066
18996
  }
@@ -18894,6 +19824,8 @@ var tileflowNativeStyleTransformationSchema = strictNativeObject({
18894
19824
  inputLayers: layerCountSchema,
18895
19825
  outputLayers: layerCountSchema,
18896
19826
  projection: z13.enum(["none", "mercator-to-implicit", "globe-to-mercator"]),
19827
+ /** Present when preparation removed a browser sky, which Native does not draw. */
19828
+ sky: z13.literal("removed").optional(),
18897
19829
  layers: z13.array(layerTransformationSchema).max(tileflowNativeProfileLimits.maximumLayers),
18898
19830
  symbolOverlap: z13.array(symbolOverlapTransformationSchema).max(tileflowNativeProfileLimits.maximumLayers)
18899
19831
  }).superRefine((style, context) => {
@@ -28026,7 +28958,7 @@ var viewSchema2 = z15.object({
28026
28958
  center: z15.tuple([z15.number().min(-180).max(180), z15.number().min(-90).max(90)]).optional(),
28027
28959
  zoom: z15.number().min(0).max(24).optional(),
28028
28960
  bearing: z15.number().finite().optional(),
28029
- pitch: z15.number().min(0).max(85).optional()
28961
+ pitch: z15.number().min(0).max(tileflowMaxPitch).optional()
28030
28962
  }).strict();
28031
28963
  var tileflowRendererDeploymentArtifactSchema = z15.object({
28032
28964
  kind: z15.literal("tileflow-map-deployment"),