@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/index.js CHANGED
@@ -13,6 +13,114 @@ var __export = (target, all2) => {
13
13
  __defProp(target, name, { get: all2[name], enumerable: true });
14
14
  };
15
15
 
16
+ // src/theme-blend.ts
17
+ function mixTileflowColours(from, to, t) {
18
+ const a = oklab(from);
19
+ const b = oklab(to);
20
+ if (!a || !b) return void 0;
21
+ return colourFromOklab(a.map((value, channel) => value + (b[channel] - value) * t));
22
+ }
23
+ function oklab(colour) {
24
+ const cached = oklabCache.get(colour);
25
+ if (cached !== void 0) return cached;
26
+ const rgba = parseColour(colour);
27
+ let result = null;
28
+ if (rgba) {
29
+ const [r, g, b] = rgba.slice(0, 3).map((channel) => {
30
+ const value = channel / 255;
31
+ return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
32
+ });
33
+ const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
34
+ const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
35
+ const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
36
+ result = [
37
+ 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
38
+ 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
39
+ 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
40
+ rgba[3]
41
+ ];
42
+ }
43
+ if (oklabCache.size > 4096) oklabCache.clear();
44
+ oklabCache.set(colour, result);
45
+ return result;
46
+ }
47
+ function colourFromOklab([lightness, a, b, alpha2]) {
48
+ const l = (lightness + 0.3963377774 * a + 0.2158037573 * b) ** 3;
49
+ const m = (lightness - 0.1055613458 * a - 0.0638541728 * b) ** 3;
50
+ const s = (lightness - 0.0894841775 * a - 1.291485548 * b) ** 3;
51
+ const [red, green, blue] = [
52
+ 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
53
+ -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
54
+ -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
55
+ ].map((value) => {
56
+ const clamped = Math.min(1, Math.max(0, value));
57
+ const encoded = clamped <= 31308e-7 ? clamped * 12.92 : 1.055 * clamped ** (1 / 2.4) - 0.055;
58
+ return Math.round(encoded * 255);
59
+ });
60
+ return `rgba(${red}, ${green}, ${blue}, ${Number(alpha2.toFixed(3))})`;
61
+ }
62
+ function tileflowColourChannels(colour) {
63
+ const rgba = parseColour(colour);
64
+ if (!rgba) return null;
65
+ return [
66
+ Math.min(1, Math.max(0, rgba[0] / 255)),
67
+ Math.min(1, Math.max(0, rgba[1] / 255)),
68
+ Math.min(1, Math.max(0, rgba[2] / 255)),
69
+ Math.min(1, Math.max(0, rgba[3]))
70
+ ];
71
+ }
72
+ function parseColour(colour) {
73
+ const text = colour.trim().toLowerCase();
74
+ const hex = /^#([\da-f]{3,8})$/u.exec(text)?.[1];
75
+ if (hex && [3, 4, 6, 8].includes(hex.length)) {
76
+ const full = hex.length <= 4 ? [...hex].map((digit) => digit + digit).join("") : hex;
77
+ const channels = full.match(/../gu).map((pair) => Number.parseInt(pair, 16));
78
+ return [channels[0], channels[1], channels[2], channels.length > 3 ? channels[3] / 255 : 1];
79
+ }
80
+ const functional = /^(rgba?|hsla?)\(([^)]+)\)$/u.exec(text);
81
+ if (!functional) return null;
82
+ const parts = functional[2].split(/[\s,/]+/u).filter(Boolean);
83
+ if (parts.length < 3 || parts.length > 4) return null;
84
+ const alpha2 = parts[3] === void 0 ? 1 : parseUnit(parts[3], 1);
85
+ if (functional[1].startsWith("rgb")) {
86
+ const [r, g, b] = parts.slice(0, 3).map((part) => parseUnit(part, 255));
87
+ if ([r, g, b, alpha2].some((value) => value === null)) return null;
88
+ return [r, g, b, alpha2];
89
+ }
90
+ const hue = Number.parseFloat(parts[0]);
91
+ const saturation = parseUnit(parts[1], 1);
92
+ const light = parseUnit(parts[2], 1);
93
+ if (!Number.isFinite(hue) || saturation === null || light === null || alpha2 === null) return null;
94
+ const chroma = (1 - Math.abs(2 * light - 1)) * saturation;
95
+ const sector = (hue % 360 + 360) % 360 / 60;
96
+ const x = chroma * (1 - Math.abs(sector % 2 - 1));
97
+ const [r1, g1, b1] = sector < 1 ? [chroma, x, 0] : sector < 2 ? [x, chroma, 0] : sector < 3 ? [0, chroma, x] : sector < 4 ? [0, x, chroma] : sector < 5 ? [x, 0, chroma] : [chroma, 0, x];
98
+ const offset = light - chroma / 2;
99
+ return [(r1 + offset) * 255, (g1 + offset) * 255, (b1 + offset) * 255, alpha2];
100
+ }
101
+ function parseUnit(part, scale) {
102
+ const percentage = part.endsWith("%");
103
+ const value = Number.parseFloat(part);
104
+ if (!Number.isFinite(value)) return null;
105
+ return percentage ? value / 100 * scale : value;
106
+ }
107
+ var oklabCache;
108
+ var init_theme_blend = __esm({
109
+ "src/theme-blend.ts"() {
110
+ "use strict";
111
+ oklabCache = /* @__PURE__ */ new Map();
112
+ }
113
+ });
114
+
115
+ // src/camera-limits.ts
116
+ var tileflowMaxPitch;
117
+ var init_camera_limits = __esm({
118
+ "src/camera-limits.ts"() {
119
+ "use strict";
120
+ tileflowMaxPitch = 85;
121
+ }
122
+ });
123
+
16
124
  // src/native-zod-object.ts
17
125
  import { z } from "zod";
18
126
  function strictNativeObject(shape) {
@@ -483,105 +591,6 @@ var init_operations = __esm({
483
591
  }
484
592
  });
485
593
 
486
- // src/theme-blend.ts
487
- function mixTileflowColours(from, to, t) {
488
- const a = oklab(from);
489
- const b = oklab(to);
490
- if (!a || !b) return void 0;
491
- return colourFromOklab(a.map((value, channel) => value + (b[channel] - value) * t));
492
- }
493
- function oklab(colour) {
494
- const cached = oklabCache.get(colour);
495
- if (cached !== void 0) return cached;
496
- const rgba = parseColour(colour);
497
- let result = null;
498
- if (rgba) {
499
- const [r, g, b] = rgba.slice(0, 3).map((channel) => {
500
- const value = channel / 255;
501
- return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
502
- });
503
- const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
504
- const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
505
- const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
506
- result = [
507
- 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
508
- 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
509
- 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
510
- rgba[3]
511
- ];
512
- }
513
- if (oklabCache.size > 4096) oklabCache.clear();
514
- oklabCache.set(colour, result);
515
- return result;
516
- }
517
- function colourFromOklab([lightness, a, b, alpha2]) {
518
- const l = (lightness + 0.3963377774 * a + 0.2158037573 * b) ** 3;
519
- const m = (lightness - 0.1055613458 * a - 0.0638541728 * b) ** 3;
520
- const s = (lightness - 0.0894841775 * a - 1.291485548 * b) ** 3;
521
- const [red, green, blue] = [
522
- 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
523
- -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
524
- -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
525
- ].map((value) => {
526
- const clamped = Math.min(1, Math.max(0, value));
527
- const encoded = clamped <= 31308e-7 ? clamped * 12.92 : 1.055 * clamped ** (1 / 2.4) - 0.055;
528
- return Math.round(encoded * 255);
529
- });
530
- return `rgba(${red}, ${green}, ${blue}, ${Number(alpha2.toFixed(3))})`;
531
- }
532
- function tileflowColourChannels(colour) {
533
- const rgba = parseColour(colour);
534
- if (!rgba) return null;
535
- return [
536
- Math.min(1, Math.max(0, rgba[0] / 255)),
537
- Math.min(1, Math.max(0, rgba[1] / 255)),
538
- Math.min(1, Math.max(0, rgba[2] / 255)),
539
- Math.min(1, Math.max(0, rgba[3]))
540
- ];
541
- }
542
- function parseColour(colour) {
543
- const text = colour.trim().toLowerCase();
544
- const hex = /^#([\da-f]{3,8})$/u.exec(text)?.[1];
545
- if (hex && [3, 4, 6, 8].includes(hex.length)) {
546
- const full = hex.length <= 4 ? [...hex].map((digit) => digit + digit).join("") : hex;
547
- const channels = full.match(/../gu).map((pair) => Number.parseInt(pair, 16));
548
- return [channels[0], channels[1], channels[2], channels.length > 3 ? channels[3] / 255 : 1];
549
- }
550
- const functional = /^(rgba?|hsla?)\(([^)]+)\)$/u.exec(text);
551
- if (!functional) return null;
552
- const parts = functional[2].split(/[\s,/]+/u).filter(Boolean);
553
- if (parts.length < 3 || parts.length > 4) return null;
554
- const alpha2 = parts[3] === void 0 ? 1 : parseUnit(parts[3], 1);
555
- if (functional[1].startsWith("rgb")) {
556
- const [r, g, b] = parts.slice(0, 3).map((part) => parseUnit(part, 255));
557
- if ([r, g, b, alpha2].some((value) => value === null)) return null;
558
- return [r, g, b, alpha2];
559
- }
560
- const hue = Number.parseFloat(parts[0]);
561
- const saturation = parseUnit(parts[1], 1);
562
- const light = parseUnit(parts[2], 1);
563
- if (!Number.isFinite(hue) || saturation === null || light === null || alpha2 === null) return null;
564
- const chroma = (1 - Math.abs(2 * light - 1)) * saturation;
565
- const sector = (hue % 360 + 360) % 360 / 60;
566
- const x = chroma * (1 - Math.abs(sector % 2 - 1));
567
- const [r1, g1, b1] = sector < 1 ? [chroma, x, 0] : sector < 2 ? [x, chroma, 0] : sector < 3 ? [0, chroma, x] : sector < 4 ? [0, x, chroma] : sector < 5 ? [x, 0, chroma] : [chroma, 0, x];
568
- const offset = light - chroma / 2;
569
- return [(r1 + offset) * 255, (g1 + offset) * 255, (b1 + offset) * 255, alpha2];
570
- }
571
- function parseUnit(part, scale) {
572
- const percentage = part.endsWith("%");
573
- const value = Number.parseFloat(part);
574
- if (!Number.isFinite(value)) return null;
575
- return percentage ? value / 100 * scale : value;
576
- }
577
- var oklabCache;
578
- var init_theme_blend = __esm({
579
- "src/theme-blend.ts"() {
580
- "use strict";
581
- oklabCache = /* @__PURE__ */ new Map();
582
- }
583
- });
584
-
585
594
  // src/sun.ts
586
595
  function normalizeTileflowAzimuth(azimuth) {
587
596
  const turned = azimuth % 360;
@@ -705,9 +714,9 @@ function tileflowSunMetadata(preset) {
705
714
  return Object.freeze({ version: 1, ...preset });
706
715
  }
707
716
  function readTileflowSunPreset(metadata) {
708
- if (!isRecord4(metadata)) return void 0;
717
+ if (!isRecord5(metadata)) return void 0;
709
718
  const value = metadata[tileflowSunMetadataKey];
710
- if (!isRecord4(value) || value.version !== 1 || !isRecord4(value.ambient)) return void 0;
719
+ if (!isRecord5(value) || value.version !== 1 || !isRecord5(value.ambient)) return void 0;
711
720
  const { azimuth, altitude, color: color3, intensity, shadowIntensity } = value;
712
721
  const ambient = value.ambient;
713
722
  if (!isFiniteNumber(azimuth) || !isFiniteNumber(altitude) || !isUnit(intensity) || !isUnit(shadowIntensity) || !isUnit(ambient.intensity) || !isColour(color3) || !isColour(ambient.color))
@@ -724,7 +733,7 @@ function readTileflowSunPreset(metadata) {
724
733
  function tileflowStyleSunPreset(style) {
725
734
  const published = readTileflowSunPreset(style?.metadata);
726
735
  if (published) return published;
727
- const light = isRecord4(style?.light) ? style.light : void 0;
736
+ const light = isRecord5(style?.light) ? style.light : void 0;
728
737
  const position = light?.position;
729
738
  if (!position) return tileflowDefaultSunPreset;
730
739
  return Object.freeze({
@@ -762,7 +771,7 @@ function smoothstep2(from, to, value) {
762
771
  function clamp01(value) {
763
772
  return Math.min(1, Math.max(0, Number.isFinite(value) ? value : 0));
764
773
  }
765
- function isRecord4(value) {
774
+ function isRecord5(value) {
766
775
  return typeof value === "object" && value !== null && !Array.isArray(value);
767
776
  }
768
777
  function isFiniteNumber(value) {
@@ -850,8 +859,11 @@ var init_time_of_day = __esm({
850
859
  });
851
860
 
852
861
  // src/themes/visual-semantics.ts
862
+ function isTileflowVisualColorCollection(property) {
863
+ return property === "colors" || property.endsWith("Colors") || property.endsWith("-colors");
864
+ }
853
865
  function classifyTileflowVisualProperty(property, _value) {
854
- if (property === "color" || property === "colors" || property.endsWith("Color") || property.endsWith("Colors") || property.endsWith("-color") || property.endsWith("-colors")) {
866
+ if (property === "color" || property.endsWith("Color") || property.endsWith("-color") || isTileflowVisualColorCollection(property)) {
855
867
  return "color";
856
868
  }
857
869
  if (property === "font" || property === "fallbacks" || property.endsWith("-font")) return "font";
@@ -878,6 +890,7 @@ var init_visual_semantics = __esm({
878
890
  "colorMix",
879
891
  "metallic",
880
892
  "roughness",
893
+ "horizonBlend",
881
894
  "hue",
882
895
  "saturation",
883
896
  "lightness",
@@ -905,6 +918,7 @@ var init_visual_semantics = __esm({
905
918
  "roundLimit",
906
919
  "size",
907
920
  "spacing",
921
+ "starIntensity",
908
922
  "strokeOpacity",
909
923
  "strokeWidth",
910
924
  "textFitPadding",
@@ -1075,7 +1089,7 @@ function auditTileflowThemeValues(value, path = "value", context = inferAuditCon
1075
1089
  return diagnostics.sort(compareAuditDiagnostics);
1076
1090
  }
1077
1091
  function auditTileflowMapThemeValues(map) {
1078
- const modules = isRecord5(map.modules) ? map.modules : void 0;
1092
+ const modules = isRecord6(map.modules) ? map.modules : void 0;
1079
1093
  const moduleDiagnostics = modules ? Object.keys(modules).sort(compareCodeUnits2).flatMap(
1080
1094
  (owner) => auditTileflowThemeValues(modules[owner], `modules.${owner}`, {
1081
1095
  owner,
@@ -1182,7 +1196,7 @@ function isThemePrimitiveForCategory(value, category) {
1182
1196
  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;
1183
1197
  }
1184
1198
  function isThemeValueCandidate(value) {
1185
- return isRecord5(value) && (value.kind === "theme-token" || value.kind === "theme-fixed" || value.kind === "theme-color");
1199
+ return isRecord6(value) && (value.kind === "theme-token" || value.kind === "theme-fixed" || value.kind === "theme-color");
1186
1200
  }
1187
1201
  function visitAuditableValue(value, path, category, state, intentRequired = false) {
1188
1202
  if (isThemeStructuralPath(path)) category = void 0;
@@ -1198,7 +1212,7 @@ function visitAuditableValue(value, path, category, state, intentRequired = fals
1198
1212
  visitAuditableArray(value, path, category, state, false, intentRequired);
1199
1213
  return;
1200
1214
  }
1201
- if (!isRecord5(value)) return;
1215
+ if (!isRecord6(value)) return;
1202
1216
  if (value.kind === "expression" && Array.isArray(value.value)) {
1203
1217
  visitAuditableArray(value.value, `${path}.value`, category, state, true, intentRequired);
1204
1218
  return;
@@ -1218,15 +1232,19 @@ function visitAuditableValue(value, path, category, state, intentRequired = fals
1218
1232
  }
1219
1233
  return;
1220
1234
  }
1235
+ const collectionCategory = isTileflowVisualColorCollection(lastPathKey(path)) ? category : void 0;
1221
1236
  for (const key of Object.keys(value).sort(compareCodeUnits2)) {
1222
1237
  visitAuditableValue(
1223
1238
  value[key],
1224
1239
  `${path}.${key}`,
1225
- classifyTileflowVisualProperty(key, value[key]),
1240
+ classifyTileflowVisualProperty(key, value[key]) ?? collectionCategory,
1226
1241
  state
1227
1242
  );
1228
1243
  }
1229
1244
  }
1245
+ function lastPathKey(path) {
1246
+ return path.endsWith("]") ? "" : path.slice(path.lastIndexOf(".") + 1);
1247
+ }
1230
1248
  function visitAuditableArray(value, path, category, state, forceExpression = false, intentRequired = false) {
1231
1249
  const operator = typeof value[0] === "string" ? value[0] : void 0;
1232
1250
  const isExpression = forceExpression || isMapLibreExpressionOperator(operator);
@@ -1583,7 +1601,7 @@ function assertThemeLighting(value, path, allowReset = false) {
1583
1601
  }
1584
1602
  }
1585
1603
  function assertThemeCategoryValue(value, expectedCategory, path) {
1586
- if (!isRecord5(value)) {
1604
+ if (!isRecord6(value)) {
1587
1605
  assertTokenPrimitive(expectedCategory, value, path);
1588
1606
  return;
1589
1607
  }
@@ -1948,11 +1966,11 @@ function cloneJson14(value) {
1948
1966
  if (serialized === void 0) throw new Error("Tileflow themes must be JSON-serializable.");
1949
1967
  return JSON.parse(serialized);
1950
1968
  }
1951
- function isRecord5(value) {
1969
+ function isRecord6(value) {
1952
1970
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
1953
1971
  }
1954
1972
  function isPlainRecord2(value) {
1955
- if (!isRecord5(value)) return false;
1973
+ if (!isRecord6(value)) return false;
1956
1974
  const prototype = Object.getPrototypeOf(value);
1957
1975
  return prototype === Object.prototype || prototype === null;
1958
1976
  }
@@ -2067,7 +2085,7 @@ var init_model = __esm({
2067
2085
  if (Array.isArray(value)) {
2068
2086
  return value.map((entry, index) => this.resolveTree(entry, `${path}[${index}]`, category));
2069
2087
  }
2070
- if (!isRecord5(value)) return value;
2088
+ if (!isRecord6(value)) return value;
2071
2089
  if (value.kind === "expression" && Array.isArray(value.value)) {
2072
2090
  return {
2073
2091
  ...value,
@@ -2326,13 +2344,14 @@ function createTileflowRuntimeViewSchema(operations2) {
2326
2344
  center: operations2.optional(
2327
2345
  operations2.tuplePair(operations2.number(-180, 180), operations2.number(-90, 90))
2328
2346
  ),
2329
- pitch: operations2.optional(operations2.number(0, 85)),
2347
+ pitch: operations2.optional(operations2.number(0, tileflowMaxPitch)),
2330
2348
  zoom: operations2.optional(operations2.number(0, 24))
2331
2349
  });
2332
2350
  }
2333
2351
  var init_runtime_view_schema = __esm({
2334
2352
  "src/runtime-view-schema.ts"() {
2335
2353
  "use strict";
2354
+ init_camera_limits();
2336
2355
  }
2337
2356
  });
2338
2357
 
@@ -2902,8 +2921,68 @@ var init_meshopt_decoder = __esm({
2902
2921
  }
2903
2922
  });
2904
2923
 
2924
+ // src/sky.ts
2925
+ init_theme_blend();
2926
+ var tileflowSkyMetadataKey = "tileflow:sky";
2927
+ var hexColour = /^#[0-9a-f]{6}$/iu;
2928
+ var unit = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
2929
+ function readColours(value) {
2930
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
2931
+ const record3 = value;
2932
+ if (Object.keys(record3).length !== 4) return void 0;
2933
+ const { color: color3, horizonColor, horizonBlend, glowColor } = record3;
2934
+ if (typeof color3 !== "string" || !hexColour.test(color3) || typeof horizonColor !== "string" || !hexColour.test(horizonColor) || typeof glowColor !== "string" || !hexColour.test(glowColor) || !unit(horizonBlend))
2935
+ return void 0;
2936
+ return { color: color3, horizonColor, horizonBlend, glowColor };
2937
+ }
2938
+ function readTileflowSky(value) {
2939
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
2940
+ const record3 = value;
2941
+ const keys = Object.keys(record3);
2942
+ if (record3.version !== 1 || keys.length !== (record3.timesOfDay === void 0 ? 3 : 4))
2943
+ return void 0;
2944
+ const { starIntensity, glowColor, timesOfDay } = record3;
2945
+ if (!unit(starIntensity) || typeof glowColor !== "string" || !hexColour.test(glowColor))
2946
+ return void 0;
2947
+ if (timesOfDay === void 0) return { version: 1, starIntensity, glowColor };
2948
+ if (!timesOfDay || typeof timesOfDay !== "object" || Array.isArray(timesOfDay)) return void 0;
2949
+ const times = timesOfDay;
2950
+ if (Object.keys(times).length !== 4) return void 0;
2951
+ const day = readColours(times.day);
2952
+ const dawn = readColours(times.dawn);
2953
+ const dusk = readColours(times.dusk);
2954
+ const night = readColours(times.night);
2955
+ if (!day || !dawn || !dusk || !night) return void 0;
2956
+ return { version: 1, starIntensity, glowColor, timesOfDay: { day, dawn, dusk, night } };
2957
+ }
2958
+ var smooth = (from, to, value) => {
2959
+ const t = Math.min(1, Math.max(0, (value - from) / (to - from)));
2960
+ return t * t * (3 - 2 * t);
2961
+ };
2962
+ function mixColours(from, to, t) {
2963
+ if (t <= 0) return from;
2964
+ if (t >= 1) return to;
2965
+ return {
2966
+ color: mixTileflowColours(from.color, to.color, t) ?? to.color,
2967
+ horizonColor: mixTileflowColours(from.horizonColor, to.horizonColor, t) ?? to.horizonColor,
2968
+ horizonBlend: from.horizonBlend + (to.horizonBlend - from.horizonBlend) * t,
2969
+ glowColor: mixTileflowColours(from.glowColor, to.glowColor, t) ?? to.glowColor
2970
+ };
2971
+ }
2972
+ function tileflowSkyAtSun(times, sun) {
2973
+ const azimuth = (sun.azimuth % 360 + 360) % 360;
2974
+ const twilight = azimuth < 180 ? times.dawn : times.dusk;
2975
+ if (sun.altitude >= 2) return mixColours(twilight, times.day, smooth(2, 14, sun.altitude));
2976
+ if (sun.altitude >= -2) return twilight;
2977
+ return mixColours(times.night, twilight, smooth(-10, -2, sun.altitude));
2978
+ }
2979
+
2980
+ // src/index.ts
2981
+ init_camera_limits();
2982
+
2905
2983
  // src/capture-scene.ts
2906
2984
  init_portable_identity();
2985
+ init_camera_limits();
2907
2986
  import { z as z3 } from "zod";
2908
2987
  var tileflowCaptureSceneSchemaVersion = 1;
2909
2988
  var tileflowCaptureSceneLimits = Object.freeze({
@@ -2911,7 +2990,7 @@ var tileflowCaptureSceneLimits = Object.freeze({
2911
2990
  boundsPadding: { maximum: 1024, minimum: 0 },
2912
2991
  captureIdLength: tileflowPortableIdMaximumLength,
2913
2992
  maximumPhysicalPixels: 16777216,
2914
- pitch: { maximum: 85, minimum: 0 },
2993
+ pitch: { maximum: tileflowMaxPitch, minimum: 0 },
2915
2994
  selectorLength: 256,
2916
2995
  viewport: { maximum: 4096, minimum: 64 },
2917
2996
  zoom: { maximum: 24, minimum: 0 }
@@ -3395,14 +3474,7 @@ var defaults = {
3395
3474
  function resolveTileflowAtmosphere(value) {
3396
3475
  if (!value) return void 0;
3397
3476
  const options = { ...defaults, ...value === true ? {} : value };
3398
- const normalize2 = (input) => {
3399
- const color3 = typeof input === "string" ? Color.parse(input) : void 0;
3400
- if (!color3 || color3.a !== 1)
3401
- throw new Error("Atmosphere colors must resolve to opaque colors.");
3402
- return `#${[color3.r, color3.g, color3.b].map(
3403
- (channel) => Math.round(channel * 255).toString(16).padStart(2, "0")
3404
- ).join("")}`;
3405
- };
3477
+ const normalize2 = (input) => opaqueHex(input, "Atmosphere colors");
3406
3478
  return {
3407
3479
  ...options,
3408
3480
  skyColor: normalize2(options.skyColor),
@@ -3411,6 +3483,13 @@ function resolveTileflowAtmosphere(value) {
3411
3483
  spaceColor: normalize2(options.spaceColor)
3412
3484
  };
3413
3485
  }
3486
+ function opaqueHex(input, subject) {
3487
+ const color3 = typeof input === "string" ? Color.parse(input) : void 0;
3488
+ if (!color3 || color3.a !== 1) throw new Error(`${subject} must resolve to opaque colors.`);
3489
+ return `#${[color3.r, color3.g, color3.b].map(
3490
+ (channel) => Math.round(channel * 255).toString(16).padStart(2, "0")
3491
+ ).join("")}`;
3492
+ }
3414
3493
  function compileTileflowSky(options) {
3415
3494
  return {
3416
3495
  "sky-color": options.skyColor,
@@ -3422,6 +3501,41 @@ function compileTileflowSky(options) {
3422
3501
  "atmosphere-blend": ["interpolate", ["linear"], ["zoom"], 0, 0.55, 3, 0.45, 5, 0.2, 7, 0]
3423
3502
  };
3424
3503
  }
3504
+ var skyDefaults = {
3505
+ color: "#5c9ce6",
3506
+ horizonColor: "#dcebf7",
3507
+ horizonBlend: 0.6,
3508
+ glowColor: "#ffc68a",
3509
+ starIntensity: 0.6
3510
+ };
3511
+ function resolveTileflowSky(value) {
3512
+ if (!value) return void 0;
3513
+ const options = { ...skyDefaults, ...value === true ? {} : value };
3514
+ const unit2 = (input, name) => {
3515
+ if (typeof input !== "number" || !Number.isFinite(input) || input < 0 || input > 1)
3516
+ throw new Error(`Sky ${name} must resolve to a number from 0 to 1.`);
3517
+ return input;
3518
+ };
3519
+ return {
3520
+ color: opaqueHex(options.color, "Sky colors"),
3521
+ horizonColor: opaqueHex(options.horizonColor, "Sky colors"),
3522
+ horizonBlend: unit2(options.horizonBlend, "horizonBlend"),
3523
+ glowColor: opaqueHex(options.glowColor, "Sky colors"),
3524
+ starIntensity: unit2(options.starIntensity, "starIntensity")
3525
+ };
3526
+ }
3527
+ function compileTileflowStreetSky(options) {
3528
+ return {
3529
+ "sky-color": options.color,
3530
+ "horizon-color": options.horizonColor,
3531
+ // MapLibre fogs only 3D terrain; the haze matches the horizon so terrain meets the sky.
3532
+ "fog-color": options.horizonColor,
3533
+ "sky-horizon-blend": options.horizonBlend,
3534
+ "horizon-fog-blend": 0.6,
3535
+ "fog-ground-blend": 0.75,
3536
+ "atmosphere-blend": ["interpolate", ["linear"], ["zoom"], 0, 0.55, 3, 0.45, 5, 0.2, 7, 0]
3537
+ };
3538
+ }
3425
3539
 
3426
3540
  // src/models-3d-model.ts
3427
3541
  var tileflowModelParts = Object.freeze({
@@ -3655,6 +3769,7 @@ function readTileflowBuildings3D(metadata) {
3655
3769
  ...floodLight ? { floodLight } : {},
3656
3770
  ...modelLighting ? { modelLighting } : {},
3657
3771
  windows: value.windows === true,
3772
+ ...readTileflowBuildingsEntrance(value.entrance),
3658
3773
  shadows: typeof value.shadows === "boolean" ? value.shadows : defaults4.shadows,
3659
3774
  occlusion: typeof value.occlusion === "boolean" ? value.occlusion : defaults4.occlusion,
3660
3775
  modelOcclusion: within(value.modelOcclusion, 1, defaults4.modelOcclusion),
@@ -3663,6 +3778,13 @@ function readTileflowBuildings3D(metadata) {
3663
3778
  ...landmarksOf(value.landmarks)
3664
3779
  };
3665
3780
  }
3781
+ function readTileflowBuildingsEntrance(value) {
3782
+ if (!isRecord(value)) return {};
3783
+ 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;
3784
+ const buildings2 = band(value.buildings);
3785
+ const models = band(value.models);
3786
+ return buildings2 || models ? { entrance: { ...buildings2 ? { buildings: buildings2 } : {}, ...models ? { models } : {} } } : {};
3787
+ }
3666
3788
  var modelId = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
3667
3789
  var tileflowBuildingModelPlacementLimit = 256;
3668
3790
  var teamModelPrefix = "tileflow://team-models/";
@@ -3872,6 +3994,7 @@ function tileflowWorldV1Schema(options = {}) {
3872
3994
  sidewalk: "sidewalk",
3873
3995
  streetFurniture: "street_furniture",
3874
3996
  trafficControl: "traffic_control",
3997
+ transportationOutline: "transportation_outline",
3875
3998
  ...options.layers
3876
3999
  },
3877
4000
  semantics: { ...options.semantics, parkLayer: "protected-only" }
@@ -3891,6 +4014,11 @@ function validateTileflowWorldV1Tilejson(tilejson, schema = tileflowWorldV1Schem
3891
4014
  const roadShield = requireTilejsonLayer(layers2, schema.layers.roadShield, issues);
3892
4015
  const canopy = optionalTilejsonLayer(layers2, schema.layers.canopy, issues);
3893
4016
  const trafficControl = optionalTilejsonLayer(layers2, schema.layers.trafficControl, issues);
4017
+ const transportationOutline = requireTilejsonLayer(
4018
+ layers2,
4019
+ schema.layers.transportationOutline,
4020
+ issues
4021
+ );
3894
4022
  if (bathymetry2) {
3895
4023
  if (bathymetry2.minzoom !== 0 || bathymetry2.maxzoom !== 9) {
3896
4024
  issues.push(`Tileflow World V1 ${schema.layers.bathymetry} must declare z0-z9.`);
@@ -4081,6 +4209,27 @@ function validateTileflowWorldV1Tilejson(tilejson, schema = tileflowWorldV1Schem
4081
4209
  requireTilejsonField(canopy, schema.layers.canopy, field2, ["Number"], issues);
4082
4210
  }
4083
4211
  }
4212
+ if (transportationOutline) {
4213
+ if (transportationOutline.minzoom !== 13 || transportationOutline.maxzoom !== 15) {
4214
+ issues.push(
4215
+ `Tileflow World V1 ${schema.layers.transportationOutline} must declare native z13-z15.`
4216
+ );
4217
+ }
4218
+ requireTilejsonField(
4219
+ transportationOutline,
4220
+ schema.layers.transportationOutline,
4221
+ schema.fields.class,
4222
+ ["String"],
4223
+ issues
4224
+ );
4225
+ requireTilejsonField(
4226
+ transportationOutline,
4227
+ schema.layers.transportationOutline,
4228
+ schema.fields.subclass,
4229
+ ["String"],
4230
+ issues
4231
+ );
4232
+ }
4084
4233
  return issues;
4085
4234
  }
4086
4235
  function requireTilejsonLayer(layers2, id2, issues) {
@@ -5045,7 +5194,7 @@ function applyLineStyle(layer, style) {
5045
5194
  const layout = { ...asRecord2(layer.layout) };
5046
5195
  setPaint(paint, "line-blur", style.blur);
5047
5196
  setPaint(paint, "line-color", style.color);
5048
- setPaint(paint, "line-dasharray", style.dash);
5197
+ if (!isSolidDash(style.dash)) setPaint(paint, "line-dasharray", style.dash);
5049
5198
  setPaint(paint, "line-gap-width", style.gapWidth);
5050
5199
  setPaint(paint, "line-offset", style.offset);
5051
5200
  setPaint(paint, "line-opacity", style.opacity);
@@ -5289,6 +5438,12 @@ function withoutGovernedAllowOverlap(layout) {
5289
5438
  }
5290
5439
  return result;
5291
5440
  }
5441
+ function isSolidDash(value) {
5442
+ if (value === void 0) return false;
5443
+ const dash = toMapLibreStyleValue(value);
5444
+ const array = Array.isArray(dash) && dash[0] === "literal" ? dash[1] : dash;
5445
+ return Array.isArray(array) && array.length === 2 && array[0] === 1 && array[1] === 0;
5446
+ }
5292
5447
  function setPaint(paint, property, value) {
5293
5448
  if (value !== void 0) paint[property] = toMapLibreStyleValue(value);
5294
5449
  }
@@ -5816,6 +5971,21 @@ function compileBuildings(request, context) {
5816
5971
  }
5817
5972
  return contributions;
5818
5973
  }
5974
+ var buildingTargetsByMode = {
5975
+ flat: ["buildings.flat.fill", "buildings.flat.outline"],
5976
+ "3d": ["buildings.extrusion"]
5977
+ };
5978
+ function buildingTargetsOfOtherModes(request) {
5979
+ const activeMode = request?.mode ?? "flat";
5980
+ const targets = /* @__PURE__ */ new Map();
5981
+ for (const [mode, modeTargets] of Object.entries(buildingTargetsByMode)) {
5982
+ if (mode === activeMode) continue;
5983
+ for (const target of modeTargets) {
5984
+ targets.set(target, { activeMode, mode, owner: "buildings" });
5985
+ }
5986
+ }
5987
+ return targets;
5988
+ }
5819
5989
  function buildingColorExpression(context) {
5820
5990
  const fields2 = context.data.schema.fields;
5821
5991
  const colors = context.colors.buildings;
@@ -5933,29 +6103,23 @@ function isTileflowPathRoadClass(roadClass) {
5933
6103
  return tileflowPathRoadClasses.includes(roadClass);
5934
6104
  }
5935
6105
  function tileflowRoadClassFilter(fields2, roadClass) {
6106
+ const classes = tileflowRoadClassValues(roadClass);
5936
6107
  if (isTileflowPathRoadClass(roadClass)) {
5937
6108
  return [
5938
6109
  "all",
5939
- ["match", ["get", fields2.class], ["path", ...constructionClasses[roadClass]], true, false],
6110
+ ["match", ["get", fields2.class], classes, true, false],
5940
6111
  ["match", ["get", fields2.subclass], pathSubclasses[roadClass], true, false]
5941
6112
  ];
5942
6113
  }
5943
- const classes = roadClass === "minor" ? [
5944
- "minor",
5945
- "residential",
5946
- "unclassified",
5947
- ...compatibleRoadClasses[roadClass] ?? [],
5948
- ...constructionClasses[roadClass]
5949
- ] : roadClass === "service" ? [
5950
- "service",
5951
- ...compatibleRoadClasses[roadClass] ?? [],
5952
- ...constructionClasses[roadClass]
5953
- ] : [
5954
- roadClass,
6114
+ return ["match", ["get", fields2.class], classes, true, false];
6115
+ }
6116
+ function tileflowRoadClassValues(roadClass) {
6117
+ if (isTileflowPathRoadClass(roadClass)) return ["path", ...constructionClasses[roadClass]];
6118
+ return [
6119
+ ...roadClass === "minor" ? ["minor", "residential", "unclassified"] : [roadClass],
5955
6120
  ...compatibleRoadClasses[roadClass] ?? [],
5956
6121
  ...constructionClasses[roadClass]
5957
6122
  ];
5958
- return ["match", ["get", fields2.class], classes, true, false];
5959
6123
  }
5960
6124
 
5961
6125
  // src/modules/roads/index.ts
@@ -7256,12 +7420,46 @@ var tileflowNauticalImageRoles = Object.freeze({
7256
7420
  port: "nautical.buoy.port",
7257
7421
  starboard: "nautical.buoy.starboard"
7258
7422
  }),
7259
- harbours: Object.freeze({ default: "nautical.harbour" }),
7423
+ distanceMarks: Object.freeze({ default: "nautical.distance-mark" }),
7424
+ /**
7425
+ * A facility that lists several categories shows the first of `tileflowNauticalFacilityIcons`
7426
+ * it lists; facilities without a variant token fall back to `nautical.facility`.
7427
+ */
7428
+ facilities: Object.freeze({
7429
+ "boat-hoist": "nautical.facility.boat-hoist",
7430
+ boatyard: "nautical.facility.boatyard",
7431
+ default: "nautical.facility",
7432
+ "fuel-station": "nautical.facility.fuel-station",
7433
+ "pump-out": "nautical.facility.pump-out",
7434
+ slipway: "nautical.facility.slipway",
7435
+ "visitor-berth": "nautical.facility.visitor-berth",
7436
+ "water-tap": "nautical.facility.water-tap"
7437
+ }),
7438
+ /** Harbours whose `subclass` lists a marina use `nautical.marina` when the theme declares it. */
7439
+ harbours: Object.freeze({ default: "nautical.harbour", marina: "nautical.marina" }),
7260
7440
  lighthouses: Object.freeze({ default: "nautical.lighthouse" }),
7261
7441
  lights: Object.freeze({ default: "nautical.light" }),
7442
+ /** Notice boards by function; a board without a variant token uses `nautical.notice`. */
7443
+ notices: Object.freeze({
7444
+ default: "nautical.notice",
7445
+ information: "nautical.notice.information",
7446
+ prohibition: "nautical.notice.prohibition",
7447
+ recommendation: "nautical.notice.recommendation",
7448
+ regulation: "nautical.notice.regulation",
7449
+ restriction: "nautical.notice.restriction"
7450
+ }),
7262
7451
  ports: Object.freeze({ default: "nautical.port" }),
7263
7452
  wrecks: Object.freeze({ default: "nautical.wreck" })
7264
7453
  });
7454
+ var tileflowNauticalFacilityIcons = Object.freeze([
7455
+ "fuel-station",
7456
+ "slipway",
7457
+ "boat-hoist",
7458
+ "pump-out",
7459
+ "water-tap",
7460
+ "visitor-berth",
7461
+ "boatyard"
7462
+ ]);
7265
7463
  var tileflowNauticalNotice = Object.freeze({
7266
7464
  id: "nautical-not-for-navigation",
7267
7465
  text: "Not for navigation"
@@ -7323,10 +7521,15 @@ var tileflowNauticalV1Schema = Object.freeze({
7323
7521
  country: "country",
7324
7522
  coverage: "coverage",
7325
7523
  depth: "depth",
7524
+ distance: "distance",
7525
+ distanceUnit: "distance_unit",
7326
7526
  edition: "edition",
7327
7527
  families: "families",
7328
7528
  group: "group",
7329
7529
  heightM: "height_m",
7530
+ horizontalClearance: "horizontal_clearance",
7531
+ impact: "impact",
7532
+ information: "information",
7330
7533
  label: "label",
7331
7534
  licence: "licence",
7332
7535
  lit: "lit",
@@ -7344,6 +7547,7 @@ var tileflowNauticalV1Schema = Object.freeze({
7344
7547
  scale: "scale",
7345
7548
  sectorEnd: "sector_end",
7346
7549
  sectorStart: "sector_start",
7550
+ sequence: "sequence",
7347
7551
  shape: "shape",
7348
7552
  size: "size",
7349
7553
  subclass: "subclass",
@@ -7351,6 +7555,10 @@ var tileflowNauticalV1Schema = Object.freeze({
7351
7555
  topmark: "topmark",
7352
7556
  unlocode: "unlocode",
7353
7557
  update: "update",
7558
+ verticalClearance: "vertical_clearance",
7559
+ verticalClearanceClosed: "vertical_clearance_closed",
7560
+ verticalClearanceOpen: "vertical_clearance_open",
7561
+ verticalClearanceSafe: "vertical_clearance_safe",
7354
7562
  waterLevel: "water_level"
7355
7563
  }),
7356
7564
  geometryTypes: Object.freeze({
@@ -7374,6 +7582,16 @@ var tileflowNauticalV1Schema = Object.freeze({
7374
7582
  wrecks: "wreck"
7375
7583
  }),
7376
7584
  product: "nautical-v1",
7585
+ /**
7586
+ * Known `provider` values and the `class` of their `coverage` cells. The source's TileJSON owns
7587
+ * each provider's credit; the module never adds attribution.
7588
+ */
7589
+ providers: Object.freeze({
7590
+ "nga-list-of-lights": "list",
7591
+ "noaa-office-of-coast-survey": "chart",
7592
+ openstreetmap: "community",
7593
+ "usace-ienc": "chart"
7594
+ }),
7377
7595
  schemaVersion: 1
7378
7596
  });
7379
7597
  var tileflowBathymetryNameLayerLimits = Object.freeze({ maxZoomRanges: 128 });
@@ -7461,8 +7679,11 @@ function resolveBathymetryRelief(style, apiBaseUrl) {
7461
7679
  style: cloneJson12(style)
7462
7680
  };
7463
7681
  }
7682
+ var tileflowNauticalPromoteId = Object.freeze({
7683
+ [tileflowNauticalV1Schema.layers.lights]: tileflowNauticalV1Schema.fields.provenance
7684
+ });
7464
7685
  function resolveNauticalSource(config, apiBaseUrl) {
7465
- return resolveMarineSource({
7686
+ const resolved = resolveMarineSource({
7466
7687
  config,
7467
7688
  defaultPath: "/tiles/nautical/tiles.json",
7468
7689
  defaultSourceId: tileflowNauticalSourceId,
@@ -7470,6 +7691,7 @@ function resolveNauticalSource(config, apiBaseUrl) {
7470
7691
  product: tileflowNauticalV1Schema.product,
7471
7692
  apiBaseUrl
7472
7693
  });
7694
+ return { ...resolved, source: { ...resolved.source, promoteId: { ...tileflowNauticalPromoteId } } };
7473
7695
  }
7474
7696
  function resolveMarineSource(options) {
7475
7697
  const sourceId = validateSourceId(options.config.sourceId ?? options.defaultSourceId);
@@ -7531,6 +7753,85 @@ function cloneJson12(value) {
7531
7753
  return JSON.parse(JSON.stringify(value));
7532
7754
  }
7533
7755
 
7756
+ // src/nautical-lights.ts
7757
+ var tileflowNauticalLightsMetadataKey = "tileflow:nautical-lights";
7758
+ var tileflowNauticalLightStates = Object.freeze({
7759
+ /** Index of the colour shown, into the light's comma-separated `color` list. */
7760
+ colour: "tileflowLightColour",
7761
+ /** `1` while the light shines, `0` while it is eclipsed, in between for a fixed part. */
7762
+ intensity: "tileflowLightIntensity"
7763
+ });
7764
+ var tileflowNauticalLightEclipseOpacity = 0.15;
7765
+ var tileflowNauticalLightsLimits = Object.freeze({ maxLayers: 16 });
7766
+ var identifierPattern = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/u;
7767
+ var layerIdPattern = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/u;
7768
+ function readTileflowNauticalLights(metadata) {
7769
+ if (!isRecord4(metadata)) return void 0;
7770
+ const value = metadata[tileflowNauticalLightsMetadataKey];
7771
+ if (!isRecord4(value) || value.version !== 1) return void 0;
7772
+ const { layers: layers2, source, sourceLayer } = value;
7773
+ if (typeof source !== "string" || !identifierPattern.test(source)) return void 0;
7774
+ if (typeof sourceLayer !== "string" || !identifierPattern.test(sourceLayer)) return void 0;
7775
+ if (!Array.isArray(layers2) || layers2.length === 0 || layers2.length > tileflowNauticalLightsLimits.maxLayers || !layers2.every((layer) => typeof layer === "string" && layerIdPattern.test(layer)) || new Set(layers2).size !== layers2.length) {
7776
+ return void 0;
7777
+ }
7778
+ return Object.freeze({
7779
+ layers: Object.freeze([...layers2]),
7780
+ source,
7781
+ sourceLayer,
7782
+ version: 1
7783
+ });
7784
+ }
7785
+ function declareTileflowNauticalLights(layers2, sourceId, sourceLayer) {
7786
+ if (!sourceId) return void 0;
7787
+ const ids = layers2.filter(
7788
+ (layer) => layer.source === sourceId && layer["source-layer"] === sourceLayer && readsLightState(layer.paint)
7789
+ ).map(({ id: id2 }) => id2).filter((id2) => typeof id2 === "string").slice(0, tileflowNauticalLightsLimits.maxLayers);
7790
+ if (ids.length === 0) return void 0;
7791
+ return { layers: ids, source: sourceId, sourceLayer, version: 1 };
7792
+ }
7793
+ function steadyTileflowNauticalLights(style) {
7794
+ const declaration = isRecord4(style.metadata) ? readTileflowNauticalLights(style.metadata) : void 0;
7795
+ const hasKey = isRecord4(style.metadata) && tileflowNauticalLightsMetadataKey in style.metadata;
7796
+ if (!declaration && !hasKey) return style;
7797
+ const metadata = { ...style.metadata };
7798
+ delete metadata[tileflowNauticalLightsMetadataKey];
7799
+ const layers2 = Array.isArray(style.layers) ? style.layers.map(
7800
+ (layer) => isRecord4(layer) && isRecord4(layer.paint) && readsLightState(layer.paint) ? { ...layer, paint: steady(layer.paint) } : layer
7801
+ ) : style.layers;
7802
+ return { ...style, layers: layers2, metadata };
7803
+ }
7804
+ function tileflowNauticalLightStateRead(state, steadyValue) {
7805
+ return ["number", ["feature-state", tileflowNauticalLightStates[state]], steadyValue];
7806
+ }
7807
+ var stateKeys = new Set(Object.values(tileflowNauticalLightStates));
7808
+ function readsLightState(value) {
7809
+ if (!Array.isArray(value)) {
7810
+ return isRecord4(value) ? Object.values(value).some(readsLightState) : false;
7811
+ }
7812
+ if (value[0] === "literal") return false;
7813
+ if (value[0] === "feature-state" && typeof value[1] === "string" && stateKeys.has(value[1])) {
7814
+ return true;
7815
+ }
7816
+ return value.some(readsLightState);
7817
+ }
7818
+ function steady(value) {
7819
+ if (Array.isArray(value)) {
7820
+ if (value[0] === "literal") return value;
7821
+ if (value[0] === "number" && value.length === 3 && Array.isArray(value[1]) && value[1][0] === "feature-state" && typeof value[1][1] === "string" && stateKeys.has(value[1][1])) {
7822
+ return value[2];
7823
+ }
7824
+ return value.map(steady);
7825
+ }
7826
+ if (isRecord4(value)) {
7827
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, steady(entry)]));
7828
+ }
7829
+ return value;
7830
+ }
7831
+ function isRecord4(value) {
7832
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
7833
+ }
7834
+
7534
7835
  // src/modules/nautical/compiler.ts
7535
7836
  var { fields, layers } = tileflowNauticalV1Schema;
7536
7837
  var markColours = Object.freeze({
@@ -7573,6 +7874,24 @@ var operationalAreaClasses = [
7573
7874
  var routeClasses = ["route", "traffic-separation"];
7574
7875
  var cableClasses = ["cable", "cable-area", "pipeline", "pipeline-area"];
7575
7876
  var portSizes = ["large", "medium", "small", "very-small"];
7877
+ var ownAreaClasses = ["berth", "bridge", "facility", "harbour"];
7878
+ var ownAidClasses = ["distance-mark", "notice"];
7879
+ var marinaSubclasses = ["marina", "marina-no-facilities"];
7880
+ var noticeFunctionColours = Object.freeze({
7881
+ information: markColours.blue,
7882
+ prohibition: markColours.red,
7883
+ recommendation: markColours.green,
7884
+ regulation: markColours.red,
7885
+ restriction: markColours.red
7886
+ });
7887
+ var distanceUnits = Object.freeze({
7888
+ hectometres: "hm",
7889
+ kilometres: "km",
7890
+ metres: "m",
7891
+ "nautical-miles": "NM",
7892
+ "statute-miles": "mi"
7893
+ });
7894
+ var maxAlternatingColours = 3;
7576
7895
  function compileNautical(request, context) {
7577
7896
  const nauticalSource = context.marine?.nautical;
7578
7897
  if (!nauticalSource || request?.enabled === false) return [];
@@ -7603,10 +7922,30 @@ function compileNautical(request, context) {
7603
7922
  const belowLabel = { anchor: "top", offset: [0, 0.75], optional: true };
7604
7923
  const markColour = expression(colourMatch(firstColour(fields.color), markColours, ink));
7605
7924
  const markStroke = expression(colourMatch(secondColour(fields.color), markColours, ink));
7925
+ const animateLights = request?.animateLights !== false;
7606
7926
  const flareColour = expression(
7607
- colourMatch(firstColour(fields.color), lightColours, lightColours.white)
7927
+ colourMatch(
7928
+ animateLights ? nthColour(fields.color, tileflowNauticalLightStateRead("colour", 0)) : firstColour(fields.color),
7929
+ lightColours,
7930
+ lightColours.white
7931
+ )
7608
7932
  );
7609
7933
  const iconPaint = { color: markColour, haloColor: ink, haloWidth: 1 };
7934
+ const noticeColours = [
7935
+ "match",
7936
+ ["get", fields.subclass],
7937
+ ...Object.entries(noticeFunctionColours).flat(),
7938
+ ink
7939
+ ];
7940
+ const noticeColour = expression(noticeColours);
7941
+ const distanceOpacity = (installed, notInstalled) => expression([
7942
+ "match",
7943
+ ["get", fields.category],
7944
+ "not-installed",
7945
+ notInstalled,
7946
+ installed
7947
+ ]);
7948
+ const isGuestBerth = listIncludes(fields.subclass, "guest");
7610
7949
  const defaults4 = {
7611
7950
  type: "nautical",
7612
7951
  enabled: true,
@@ -7697,6 +8036,60 @@ function compileNautical(request, context) {
7697
8036
  [14, 1.3]
7698
8037
  ])
7699
8038
  },
8039
+ harbourAreas: {
8040
+ fill: { color: ink, minZoom: 10, opacity: 0.05 },
8041
+ outline: {
8042
+ color: ink,
8043
+ minZoom: 10,
8044
+ opacity: zoom.linear([
8045
+ [10, 0.35],
8046
+ [14, 0.6]
8047
+ ]),
8048
+ width: zoom.linear([
8049
+ [10, 0.5],
8050
+ [16, 1]
8051
+ ])
8052
+ }
8053
+ },
8054
+ berths: {
8055
+ fill: { color: ink, minZoom: 13, opacity: 0.06 },
8056
+ outline: {
8057
+ color: ink,
8058
+ minZoom: 13,
8059
+ opacity: expression(["case", isGuestBerth, 0.95, 0.5]),
8060
+ width: expression([
8061
+ "interpolate",
8062
+ ["linear"],
8063
+ ["zoom"],
8064
+ 13,
8065
+ ["case", isGuestBerth, 1.2, 0.6],
8066
+ 16,
8067
+ ["case", isGuestBerth, 2.4, 1.2]
8068
+ ])
8069
+ }
8070
+ },
8071
+ bridges: {
8072
+ fill: { color: colors.land, minZoom: 12, opacity: 0.9 },
8073
+ outline: {
8074
+ color: ink,
8075
+ minZoom: 12,
8076
+ opacity: 0.85,
8077
+ width: zoom.linear([
8078
+ [12, 0.8],
8079
+ [16, 1.8]
8080
+ ])
8081
+ }
8082
+ },
8083
+ overheadCables: {
8084
+ color: accent,
8085
+ dash: [6, 2, 1, 2],
8086
+ minZoom: 10,
8087
+ opacity: 0.75,
8088
+ width: zoom.linear([
8089
+ [10, 0.7],
8090
+ [16, 1.3]
8091
+ ])
8092
+ },
7700
8093
  hazardAreas: {
7701
8094
  fill: { color: accent, minZoom: 10, opacity: 0.08 },
7702
8095
  outline: { color: accent, dash: [1, 1], minZoom: 10, opacity: 0.7, width: 0.8 }
@@ -7806,6 +8199,7 @@ function compileNautical(request, context) {
7806
8199
  harbours: {
7807
8200
  minZoom: 11,
7808
8201
  placement: "point",
8202
+ ...rankPriority,
7809
8203
  ...images.harbours ? {
7810
8204
  icon: {
7811
8205
  color: ink,
@@ -7856,6 +8250,147 @@ function compileNautical(request, context) {
7856
8250
  },
7857
8251
  text: { ...markText, ...sideLabel, field: nameFrom(15), padding: 3, size: 9 }
7858
8252
  },
8253
+ notices: {
8254
+ minZoom: 12,
8255
+ placement: "point",
8256
+ ...rankPriority,
8257
+ ...images.notices ? {
8258
+ icon: {
8259
+ color: noticeColour,
8260
+ haloColor: halo,
8261
+ haloWidth: 1,
8262
+ image: images.notices,
8263
+ padding: 1,
8264
+ size: zoom.linear([
8265
+ [12, 0.8],
8266
+ [16, 1.2]
8267
+ ])
8268
+ }
8269
+ } : {
8270
+ marker: {
8271
+ color: expression([
8272
+ "match",
8273
+ ["get", fields.subclass],
8274
+ ["prohibition", "recommendation", "information"],
8275
+ noticeColours,
8276
+ halo
8277
+ ]),
8278
+ radius: zoom.linear([
8279
+ [12, 1.8],
8280
+ [16, 3.2]
8281
+ ]),
8282
+ strokeColor: expression([
8283
+ "match",
8284
+ ["get", fields.subclass],
8285
+ ["prohibition", "recommendation", "information"],
8286
+ halo,
8287
+ noticeColours
8288
+ ]),
8289
+ strokeWidth: 1
8290
+ }
8291
+ },
8292
+ text: {
8293
+ ...markText,
8294
+ ...sideLabel,
8295
+ field: expression([
8296
+ "step",
8297
+ ["zoom"],
8298
+ "",
8299
+ 14,
8300
+ ["to-string", ["coalesce", ["get", fields.information], ""]]
8301
+ ]),
8302
+ padding: 2,
8303
+ size: 9
8304
+ }
8305
+ },
8306
+ distanceMarks: {
8307
+ minZoom: 11,
8308
+ placement: "point",
8309
+ ...rankPriority,
8310
+ ...images.distanceMarks ? {
8311
+ icon: {
8312
+ color: ink,
8313
+ haloColor: halo,
8314
+ haloWidth: 1,
8315
+ image: images.distanceMarks,
8316
+ opacity: distanceOpacity(1, 0.6),
8317
+ size: zoom.linear([
8318
+ [11, 0.7],
8319
+ [16, 1]
8320
+ ])
8321
+ }
8322
+ } : {
8323
+ marker: {
8324
+ color: ink,
8325
+ opacity: distanceOpacity(1, 0.6),
8326
+ radius: zoom.linear([
8327
+ [11, 1.2],
8328
+ [16, 2.2]
8329
+ ]),
8330
+ strokeColor: halo,
8331
+ strokeWidth: 0.8
8332
+ }
8333
+ },
8334
+ text: {
8335
+ ...markText,
8336
+ ...sideLabel,
8337
+ field: expression([
8338
+ "step",
8339
+ ["zoom"],
8340
+ "",
8341
+ 13,
8342
+ [
8343
+ "case",
8344
+ ["has", fields.distance],
8345
+ [
8346
+ "concat",
8347
+ ["to-string", ["get", fields.distance]],
8348
+ [
8349
+ "match",
8350
+ ["get", fields.distanceUnit],
8351
+ ...Object.entries(distanceUnits).flatMap(([unit2, label]) => [unit2, ` ${label}`]),
8352
+ ""
8353
+ ]
8354
+ ],
8355
+ ""
8356
+ ]
8357
+ ]),
8358
+ opacity: distanceOpacity(1, 0.75),
8359
+ padding: 2,
8360
+ size: zoom.linear([
8361
+ [13, 8.5],
8362
+ [16, 10]
8363
+ ])
8364
+ }
8365
+ },
8366
+ facilities: {
8367
+ minZoom: 13,
8368
+ placement: "point",
8369
+ ...images.facilities ? {
8370
+ icon: {
8371
+ color: ink,
8372
+ haloColor: halo,
8373
+ haloWidth: 1,
8374
+ image: images.facilities,
8375
+ padding: 1,
8376
+ size: zoom.linear([
8377
+ [13, 0.8],
8378
+ [16, 1.15]
8379
+ ])
8380
+ }
8381
+ } : {
8382
+ marker: {
8383
+ color: halo,
8384
+ radius: zoom.linear([
8385
+ [13, 1.8],
8386
+ [16, 3]
8387
+ ]),
8388
+ strokeColor: ink,
8389
+ strokeWidth: 1
8390
+ }
8391
+ },
8392
+ text: { ...markText, ...sideLabel, field: nameFrom(15), padding: 3, size: 9 }
8393
+ },
7859
8394
  beacons: {
7860
8395
  minZoom: 11,
7861
8396
  placement: "point",
@@ -8038,6 +8573,32 @@ function compileNautical(request, context) {
8038
8573
  }
8039
8574
  },
8040
8575
  labels: {
8576
+ bridges: {
8577
+ minZoom: 13,
8578
+ placement: "point",
8579
+ text: {
8580
+ ...markText,
8581
+ field: expression(bridgeClearance()),
8582
+ padding: 2,
8583
+ size: zoom.linear([
8584
+ [13, 8.5],
8585
+ [16, 10]
8586
+ ])
8587
+ }
8588
+ },
8589
+ overheadCables: {
8590
+ minZoom: 12,
8591
+ placement: "point",
8592
+ spacing: 360,
8593
+ text: {
8594
+ ...markText,
8595
+ color: accent,
8596
+ field: expression(cableClearance()),
8597
+ maxAngle: 30,
8598
+ padding: 2,
8599
+ size: 8.5
8600
+ }
8601
+ },
8041
8602
  coverage: {
8042
8603
  maxZoom: 9,
8043
8604
  minZoom: 4,
@@ -8087,6 +8648,7 @@ function compileNautical(request, context) {
8087
8648
  const source = nauticalSource.sourceId;
8088
8649
  const contributions = [];
8089
8650
  const add2 = (options) => appendNauticalLayer(contributions, { ...options, source });
8651
+ const ofClass = (value) => ["==", ["get", fields.class], value];
8090
8652
  add2({
8091
8653
  // Only coverage that speaks for every family; lists of lights alone would double the tint.
8092
8654
  filter: ["all", polygons, ["in", layers.aids, ["coalesce", ["get", fields.families], ""]]],
@@ -8097,13 +8659,30 @@ function compileNautical(request, context) {
8097
8659
  target: "nautical.coverage"
8098
8660
  });
8099
8661
  add2({
8100
- filter: polygons,
8662
+ filter: ["all", polygons, ["match", ["get", fields.class], [...ownAreaClasses], false, true]],
8101
8663
  id: "tileflow-nautical-navigation-areas",
8102
8664
  localOrder: 390,
8103
8665
  sourceLayer: layers.navigationAreas,
8104
8666
  style: config.navigationAreas,
8105
8667
  target: "nautical.navigationAreas"
8106
8668
  });
8669
+ add2({
8670
+ filter: ["all", polygons, ofClass("harbour")],
8671
+ id: "tileflow-nautical-harbour-areas",
8672
+ localOrder: 392,
8673
+ sourceLayer: layers.navigationAreas,
8674
+ style: config.harbourAreas,
8675
+ target: "nautical.harbourAreas"
8676
+ });
8677
+ add2({
8678
+ filter: ["all", polygons, ofClass("berth")],
8679
+ id: "tileflow-nautical-berths",
8680
+ localOrder: 394,
8681
+ outlineFilter: ["all", linesOrPolygons, ofClass("berth")],
8682
+ sourceLayer: layers.navigationAreas,
8683
+ style: config.berths,
8684
+ target: "nautical.berths"
8685
+ });
8107
8686
  add2({
8108
8687
  filter: polygons,
8109
8688
  id: "tileflow-nautical-reefs",
@@ -8130,11 +8709,20 @@ function compileNautical(request, context) {
8130
8709
  style: config.routes,
8131
8710
  target: "nautical.routes"
8132
8711
  });
8712
+ add2({
8713
+ filter: linesOf(["overhead-cable"]),
8714
+ id: "tileflow-nautical-overhead-cables",
8715
+ line: true,
8716
+ localOrder: 408,
8717
+ sourceLayer: layers.navigationAreas,
8718
+ style: config.overheadCables,
8719
+ target: "nautical.overheadCables"
8720
+ });
8133
8721
  add2({
8134
8722
  filter: polygons,
8135
8723
  id: "tileflow-nautical-hazard-areas",
8136
8724
  localOrder: 410,
8137
- outlineFilter: ["match", ["geometry-type"], ["LineString", "Polygon"], true, false],
8725
+ outlineFilter: linesOrPolygons,
8138
8726
  sourceLayer: layers.hazards,
8139
8727
  style: config.hazardAreas,
8140
8728
  target: "nautical.hazardAreas"
@@ -8147,15 +8735,42 @@ function compileNautical(request, context) {
8147
8735
  style: config.wreckAreas,
8148
8736
  target: "nautical.wreckAreas"
8149
8737
  });
8150
- for (const [key, id2, sourceLayer, localOrder] of [
8151
- ["coverage", "tileflow-nautical-coverage-labels", layers.coverage, 560],
8152
- ["navigationAreas", "tileflow-nautical-navigation-area-labels", layers.navigationAreas, 562],
8153
- ["reefs", "tileflow-nautical-reef-labels", layers.reefs, 564],
8154
- ["hazards", "tileflow-nautical-hazard-area-labels", layers.hazards, 566],
8155
- ["wrecks", "tileflow-nautical-wreck-area-labels", layers.wrecks, 568]
8738
+ add2({
8739
+ // Bridges draw over the water and the areas below them.
8740
+ filter: ["all", polygons, ofClass("bridge")],
8741
+ id: "tileflow-nautical-bridges",
8742
+ localOrder: 424,
8743
+ outlineFilter: ["all", linesOrPolygons, ofClass("bridge")],
8744
+ sourceLayer: layers.navigationAreas,
8745
+ style: config.bridges,
8746
+ target: "nautical.bridges"
8747
+ });
8748
+ for (const [key, id2, sourceLayer, localOrder, filter] of [
8749
+ [
8750
+ "coverage",
8751
+ "tileflow-nautical-coverage-labels",
8752
+ layers.coverage,
8753
+ 560,
8754
+ ["has", fields.provider]
8755
+ ],
8756
+ [
8757
+ "navigationAreas",
8758
+ "tileflow-nautical-navigation-area-labels",
8759
+ layers.navigationAreas,
8760
+ 562,
8761
+ // Harbours, facilities and bridges carry their own symbols and labels.
8762
+ [
8763
+ "all",
8764
+ ["has", fields.name],
8765
+ ["match", ["get", fields.class], ["bridge", "facility", "harbour"], false, true]
8766
+ ]
8767
+ ],
8768
+ ["reefs", "tileflow-nautical-reef-labels", layers.reefs, 564, ["has", fields.name]],
8769
+ ["hazards", "tileflow-nautical-hazard-area-labels", layers.hazards, 566, ["has", fields.name]],
8770
+ ["wrecks", "tileflow-nautical-wreck-area-labels", layers.wrecks, 568, ["has", fields.name]]
8156
8771
  ]) {
8157
8772
  add2({
8158
- filter: ["all", polygons, ["has", key === "coverage" ? fields.provider : fields.name]],
8773
+ filter: ["all", polygons, filter],
8159
8774
  id: id2,
8160
8775
  localOrder,
8161
8776
  sourceLayer,
@@ -8163,6 +8778,37 @@ function compileNautical(request, context) {
8163
8778
  target: `nautical.labels.${key}`
8164
8779
  });
8165
8780
  }
8781
+ add2({
8782
+ filter: [
8783
+ "all",
8784
+ ofClass("bridge"),
8785
+ [
8786
+ "any",
8787
+ ["has", fields.verticalClearance],
8788
+ ["has", fields.verticalClearanceClosed],
8789
+ ["has", fields.verticalClearanceOpen]
8790
+ ]
8791
+ ],
8792
+ id: "tileflow-nautical-bridge-labels",
8793
+ lines: { localOrder: 557, placement: "line-center" },
8794
+ localOrder: 556,
8795
+ sourceLayer: layers.navigationAreas,
8796
+ style: config.labels?.bridges,
8797
+ target: "nautical.labels.bridges"
8798
+ });
8799
+ add2({
8800
+ filter: [
8801
+ "all",
8802
+ ofClass("overhead-cable"),
8803
+ ["any", ["has", fields.verticalClearanceSafe], ["has", fields.verticalClearance]]
8804
+ ],
8805
+ id: "tileflow-nautical-overhead-cable-labels",
8806
+ lines: { localOrder: 559, placement: "line" },
8807
+ localOrder: 558,
8808
+ sourceLayer: layers.navigationAreas,
8809
+ style: config.labels?.overheadCables,
8810
+ target: "nautical.labels.overheadCables"
8811
+ });
8166
8812
  add2({
8167
8813
  filter: ["all", points, ["has", fields.depth]],
8168
8814
  id: "tileflow-nautical-soundings",
@@ -8181,6 +8827,16 @@ function compileNautical(request, context) {
8181
8827
  symbolFilter: ["==", ["get", fields.class], "rock"],
8182
8828
  target: "nautical.hazards"
8183
8829
  });
8830
+ add2({
8831
+ filter: ofClass("facility"),
8832
+ id: "tileflow-nautical-facilities",
8833
+ lines: { localOrder: 580, placement: "line-center" },
8834
+ localOrder: 576,
8835
+ markerFilter: points,
8836
+ sourceLayer: layers.navigationAreas,
8837
+ style: config.facilities,
8838
+ target: "nautical.facilities"
8839
+ });
8184
8840
  add2({
8185
8841
  filter: points,
8186
8842
  id: "tileflow-nautical-wrecks",
@@ -8190,13 +8846,23 @@ function compileNautical(request, context) {
8190
8846
  target: "nautical.wrecks"
8191
8847
  });
8192
8848
  add2({
8193
- filter: ["all", points, ["==", ["get", fields.class], "harbour"]],
8849
+ // Harbour polygons, such as marinas, carry their symbol and name at their centre.
8850
+ filter: ["all", pointsOrPolygons, ofClass("harbour")],
8194
8851
  id: "tileflow-nautical-harbours",
8195
8852
  localOrder: 582,
8853
+ markerFilter: points,
8196
8854
  sourceLayer: layers.navigationAreas,
8197
8855
  style: config.harbours,
8198
8856
  target: "nautical.harbours"
8199
8857
  });
8858
+ add2({
8859
+ filter: ["all", points, ofClass("distance-mark")],
8860
+ id: "tileflow-nautical-distance-marks",
8861
+ localOrder: 584,
8862
+ sourceLayer: layers.aids,
8863
+ style: config.distanceMarks,
8864
+ target: "nautical.distanceMarks"
8865
+ });
8200
8866
  add2({
8201
8867
  filter: [
8202
8868
  "all",
@@ -8204,7 +8870,7 @@ function compileNautical(request, context) {
8204
8870
  [
8205
8871
  "match",
8206
8872
  ["get", fields.class],
8207
- [...floatingAidClasses, ...fixedAidClasses, "lighthouse"],
8873
+ [...floatingAidClasses, ...fixedAidClasses, ...ownAidClasses, "lighthouse"],
8208
8874
  false,
8209
8875
  true
8210
8876
  ]
@@ -8215,6 +8881,14 @@ function compileNautical(request, context) {
8215
8881
  style: config.aids,
8216
8882
  target: "nautical.aids"
8217
8883
  });
8884
+ add2({
8885
+ filter: ["all", points, ofClass("notice")],
8886
+ id: "tileflow-nautical-notices",
8887
+ localOrder: 588,
8888
+ sourceLayer: layers.aids,
8889
+ style: config.notices,
8890
+ target: "nautical.notices"
8891
+ });
8218
8892
  add2({
8219
8893
  filter: ["all", points, ["match", ["get", fields.class], [...fixedAidClasses], true, false]],
8220
8894
  id: "tileflow-nautical-beacons",
@@ -8225,6 +8899,7 @@ function compileNautical(request, context) {
8225
8899
  });
8226
8900
  add2({
8227
8901
  filter: points,
8902
+ flash: animateLights,
8228
8903
  id: "tileflow-nautical-lights",
8229
8904
  localOrder: 594,
8230
8905
  sourceLayer: layers.lights,
@@ -8240,7 +8915,7 @@ function compileNautical(request, context) {
8240
8915
  target: "nautical.buoys"
8241
8916
  });
8242
8917
  add2({
8243
- filter: ["all", points, ["==", ["get", fields.class], "lighthouse"]],
8918
+ filter: ["all", points, ofClass("lighthouse")],
8244
8919
  id: "tileflow-nautical-lighthouses",
8245
8920
  localOrder: 602,
8246
8921
  sourceLayer: layers.aids,
@@ -8248,7 +8923,7 @@ function compileNautical(request, context) {
8248
8923
  target: "nautical.lighthouses"
8249
8924
  });
8250
8925
  add2({
8251
- filter: ["all", points, ["==", ["get", fields.class], "port"]],
8926
+ filter: ["all", points, ofClass("port")],
8252
8927
  id: "tileflow-nautical-ports",
8253
8928
  localOrder: 606,
8254
8929
  sourceLayer: layers.navigationAreas,
@@ -8259,12 +8934,14 @@ function compileNautical(request, context) {
8259
8934
  }
8260
8935
  var points = ["==", ["geometry-type"], "Point"];
8261
8936
  var polygons = ["==", ["geometry-type"], "Polygon"];
8937
+ var lines = ["==", ["geometry-type"], "LineString"];
8938
+ var pointsOrPolygons = ["match", ["geometry-type"], ["Point", "Polygon"], true, false];
8939
+ var linesOrPolygons = ["match", ["geometry-type"], ["LineString", "Polygon"], true, false];
8262
8940
  function linesOf(classes) {
8263
- return [
8264
- "all",
8265
- ["==", ["geometry-type"], "LineString"],
8266
- ["match", ["get", fields.class], [...classes], true, false]
8267
- ];
8941
+ return ["all", lines, ["match", ["get", fields.class], [...classes], true, false]];
8942
+ }
8943
+ function listIncludes(field2, item) {
8944
+ return ["in", `,${item},`, ["concat", ",", ["to-string", ["coalesce", ["get", field2], ""]], ","]];
8268
8945
  }
8269
8946
  var fromFeatureMinZoom = [">=", ["zoom"], ["to-number", ["get", fields.minZoom], 0]];
8270
8947
  function appendNauticalLayer(contributions, options) {
@@ -8321,24 +8998,25 @@ function appendNauticalLayer(contributions, options) {
8321
8998
  if (point.visible === false) return;
8322
8999
  const isLabel = options.target.startsWith("nautical.labels.");
8323
9000
  if (!isLabel && point.marker && point.marker.visible !== false) {
9001
+ const marker = applyCircleStyle(
9002
+ base(
9003
+ `${options.id}-marker`,
9004
+ "circle",
9005
+ options.markerFilter ? ["all", options.filter, options.markerFilter] : options.filter
9006
+ ),
9007
+ mergeTileflowDesign(
9008
+ {
9009
+ ...point.maxZoom === void 0 ? {} : { maxZoom: point.maxZoom },
9010
+ ...point.minZoom === void 0 ? {} : { minZoom: point.minZoom }
9011
+ },
9012
+ point.marker
9013
+ )
9014
+ );
8324
9015
  contributions.push(
8325
9016
  nauticalContribution(
8326
9017
  `${options.target}.marker`,
8327
9018
  options.localOrder,
8328
- applyCircleStyle(
8329
- base(
8330
- `${options.id}-marker`,
8331
- "circle",
8332
- options.markerFilter ? ["all", options.filter, options.markerFilter] : options.filter
8333
- ),
8334
- mergeTileflowDesign(
8335
- {
8336
- ...point.maxZoom === void 0 ? {} : { maxZoom: point.maxZoom },
8337
- ...point.minZoom === void 0 ? {} : { minZoom: point.minZoom }
8338
- },
8339
- point.marker
8340
- )
8341
- ),
9019
+ options.flash ? flashing(marker, ["circle-opacity", "circle-stroke-opacity"]) : marker,
8342
9020
  "symbols"
8343
9021
  )
8344
9022
  );
@@ -8346,21 +9024,79 @@ function appendNauticalLayer(contributions, options) {
8346
9024
  const hasText = Boolean(point.text && point.text.visible !== false);
8347
9025
  const hasIcon = Boolean(point.icon && point.icon.visible !== false);
8348
9026
  if (!hasText && !hasIcon) return;
9027
+ const symbolFilter = [
9028
+ options.filter,
9029
+ ...options.symbolFilter ? [options.symbolFilter] : [],
9030
+ ...options.lines ? [["!=", ["geometry-type"], "LineString"]] : []
9031
+ ];
9032
+ const symbol = applySymbolStyle(
9033
+ base(
9034
+ options.id,
9035
+ "symbol",
9036
+ symbolFilter.length === 1 ? symbolFilter[0] : ["all", ...symbolFilter]
9037
+ ),
9038
+ withoutMarker(point)
9039
+ );
8349
9040
  contributions.push(
8350
9041
  nauticalContribution(
8351
9042
  isLabel ? options.target : `${options.target}.symbol`,
8352
9043
  options.localOrder + (isLabel ? 0 : 1),
8353
- applySymbolStyle(
8354
- base(
8355
- options.id,
8356
- "symbol",
8357
- options.symbolFilter ? ["all", options.filter, options.symbolFilter] : options.filter
8358
- ),
8359
- withoutMarker(point)
8360
- ),
9044
+ options.flash && hasIcon ? flashing(symbol, ["icon-opacity"]) : symbol,
8361
9045
  "symbols"
8362
9046
  )
8363
9047
  );
9048
+ if (options.lines) {
9049
+ contributions.push(
9050
+ nauticalContribution(
9051
+ `${options.target}.lines`,
9052
+ options.lines.localOrder,
9053
+ applySymbolStyle(
9054
+ base(`${options.id}-lines`, "symbol", [
9055
+ "all",
9056
+ options.filter,
9057
+ ...options.symbolFilter ? [options.symbolFilter] : [],
9058
+ lines
9059
+ ]),
9060
+ { ...withoutMarker(point), placement: options.lines.placement }
9061
+ ),
9062
+ "symbols"
9063
+ )
9064
+ );
9065
+ }
9066
+ }
9067
+ function flashing(layer, properties) {
9068
+ const paint = { ...layer.paint ?? {} };
9069
+ const factor = [
9070
+ "interpolate",
9071
+ ["linear"],
9072
+ tileflowNauticalLightStateRead("intensity", 1),
9073
+ 0,
9074
+ tileflowNauticalLightEclipseOpacity,
9075
+ 1,
9076
+ 1
9077
+ ];
9078
+ for (const property of properties) {
9079
+ const value = paint[property];
9080
+ if (property !== "circle-opacity" && property !== "icon-opacity" && value === void 0) {
9081
+ continue;
9082
+ }
9083
+ paint[property] = scaleOutputs(value ?? 1, factor);
9084
+ }
9085
+ return { ...layer, paint };
9086
+ }
9087
+ function scaleOutputs(value, factor) {
9088
+ if (Array.isArray(value) && isZoomCurve(value)) {
9089
+ const firstOutput = value[0] === "step" ? 2 : 4;
9090
+ return value.map(
9091
+ (entry, index) => index >= firstOutput && (index - firstOutput) % 2 === 0 ? ["*", entry, factor] : entry
9092
+ );
9093
+ }
9094
+ return ["*", value, factor];
9095
+ }
9096
+ function isZoomCurve(value) {
9097
+ const isZoom3 = (input) => Array.isArray(input) && input[0] === "zoom";
9098
+ if (value[0] === "step") return isZoom3(value[1]);
9099
+ return (value[0] === "interpolate" || value[0] === "interpolate-hcl" || value[0] === "interpolate-lab") && isZoom3(value[2]);
8364
9100
  }
8365
9101
  function withoutMarker(style) {
8366
9102
  const result = { ...style };
@@ -8397,8 +9133,96 @@ function themedImages(context) {
8397
9133
  ]
8398
9134
  ]);
8399
9135
  }
9136
+ const notice = images.notices;
9137
+ if (typeof notice === "string") {
9138
+ const variants = ["information", "prohibition", "recommendation", "regulation", "restriction"].flatMap((role) => [role, context.images[roles.notices[role]] ?? notice]);
9139
+ images.notices = expression(["match", ["get", fields.subclass], ...variants, notice]);
9140
+ }
9141
+ const facility = images.facilities;
9142
+ if (typeof facility === "string") {
9143
+ const cases = tileflowNauticalFacilityIcons.flatMap((category) => {
9144
+ const image = context.images[roles.facilities[category]];
9145
+ if (!image || image === facility) return [];
9146
+ const categories = category === "visitor-berth" ? ["visitor-berth", "visitors-mooring"] : [category];
9147
+ return [["any", ...categories.map((name) => listIncludes(fields.subclass, name))], image];
9148
+ });
9149
+ if (cases.length > 0) images.facilities = expression(["case", ...cases, facility]);
9150
+ }
9151
+ const harbour = images.harbours;
9152
+ const marina = context.images[roles.harbours.marina];
9153
+ if (typeof harbour === "string" && marina && marina !== harbour) {
9154
+ images.harbours = expression([
9155
+ "case",
9156
+ ["any", ...marinaSubclasses.map((name) => listIncludes(fields.subclass, name))],
9157
+ marina,
9158
+ harbour
9159
+ ]);
9160
+ }
8400
9161
  return images;
8401
9162
  }
9163
+ function bridgeClearance() {
9164
+ const metres = (field2) => ["concat", ["to-string", ["get", field2]], " m"];
9165
+ const closed = fields.verticalClearanceClosed;
9166
+ const open = fields.verticalClearanceOpen;
9167
+ return [
9168
+ "case",
9169
+ ["any", ["has", closed], ["has", open]],
9170
+ [
9171
+ "concat",
9172
+ ["case", ["has", closed], ["concat", "closed ", metres(closed)], ""],
9173
+ ["case", ["all", ["has", closed], ["has", open]], " \xB7 ", ""],
9174
+ ["case", ["has", open], ["concat", "open ", metres(open)], ""]
9175
+ ],
9176
+ ["has", fields.verticalClearance],
9177
+ ["concat", "clearance ", metres(fields.verticalClearance)],
9178
+ ""
9179
+ ];
9180
+ }
9181
+ function cableClearance() {
9182
+ return [
9183
+ "case",
9184
+ ["has", fields.verticalClearanceSafe],
9185
+ ["concat", "safe clearance ", ["to-string", ["get", fields.verticalClearanceSafe]], " m"],
9186
+ ["has", fields.verticalClearance],
9187
+ ["concat", "clearance ", ["to-string", ["get", fields.verticalClearance]], " m"],
9188
+ ""
9189
+ ];
9190
+ }
9191
+ function nthColour(field2, index) {
9192
+ const list = ["var", "nauticalColours"];
9193
+ const cases = [];
9194
+ for (let position = 1; position < maxAlternatingColours; position += 1) {
9195
+ cases.push(["==", ["var", "nauticalColourIndex"], position], nthName(list, position));
9196
+ }
9197
+ return [
9198
+ "let",
9199
+ "nauticalColours",
9200
+ ["to-string", ["coalesce", ["get", field2], ""]],
9201
+ "nauticalColourIndex",
9202
+ index,
9203
+ ["case", ...cases, leadingName(list)]
9204
+ ];
9205
+ }
9206
+ function nthName(list, position) {
9207
+ if (position === 0) return leadingName(list);
9208
+ const rest = `nauticalRest${position}`;
9209
+ return [
9210
+ "let",
9211
+ rest,
9212
+ [
9213
+ "let",
9214
+ "nauticalComma",
9215
+ ["index-of", ",", list],
9216
+ [
9217
+ "case",
9218
+ ["<", ["var", "nauticalComma"], 0],
9219
+ "",
9220
+ ["slice", list, ["+", ["var", "nauticalComma"], 1]]
9221
+ ]
9222
+ ],
9223
+ nthName(["var", rest], position - 1)
9224
+ ];
9225
+ }
8402
9226
  function firstColour(field2) {
8403
9227
  return [
8404
9228
  "let",
@@ -9689,35 +10513,37 @@ function compileRoundabouts(request, surfaces, context, contributions) {
9689
10513
  }),
9690
10514
  0
9691
10515
  ) : atZoom(borderWidth, level);
9692
- const geometry = (casing, width) => expression([
9693
- "interpolate",
9694
- ["exponential", 2],
9695
- ["zoom"],
9696
- ...geometryLevels.flatMap((level) => {
9697
- const centerline = ["*", radiusAt15, 2 ** (level - 15)];
9698
- const fallback = fallbackWidth === void 0 ? select(
9699
- widths.map(({ condition, value }) => ({ condition, value: atZoom(value, level) })),
9700
- 0
9701
- ) : atZoom(fallbackWidth, level);
9702
- const inner = [
9703
- "case",
9704
- physical,
9705
- ["*", centerline, ["/", innerMeters, radiusMeters]],
9706
- ["-", centerline, ["/", fallback, 2]]
9707
- ];
9708
- const fillWidth = [
9709
- "case",
9710
- physical,
9711
- ["*", centerline, ["/", ["-", outerMeters, innerMeters], radiusMeters]],
9712
- fallback
9713
- ];
9714
- const border = casing ? borderAt(level) : 0;
9715
- return [
9716
- level,
9717
- width ? ["max", 0, ["+", fillWidth, border]] : ["max", 0, ["-", inner, ["/", border, 2]]]
9718
- ];
9719
- })
9720
- ]);
10516
+ const geometry = (casing, width) => expression(
10517
+ bindRepeated([
10518
+ "interpolate",
10519
+ ["exponential", 2],
10520
+ ["zoom"],
10521
+ ...geometryLevels.flatMap((level) => {
10522
+ const centerline = ["*", radiusAt15, 2 ** (level - 15)];
10523
+ const fallback = fallbackWidth === void 0 ? select(
10524
+ widths.map(({ condition, value }) => ({ condition, value: atZoom(value, level) })),
10525
+ 0
10526
+ ) : atZoom(fallbackWidth, level);
10527
+ const inner = [
10528
+ "case",
10529
+ physical,
10530
+ ["*", centerline, ["/", innerMeters, radiusMeters]],
10531
+ ["-", centerline, ["/", fallback, 2]]
10532
+ ];
10533
+ const fillWidth = [
10534
+ "case",
10535
+ physical,
10536
+ ["*", centerline, ["/", ["-", outerMeters, innerMeters], radiusMeters]],
10537
+ fallback
10538
+ ];
10539
+ const border = casing ? borderAt(level) : 0;
10540
+ return [
10541
+ level,
10542
+ width ? ["max", 0, ["+", fillWidth, border]] : ["max", 0, ["-", inner, ["/", border, 2]]]
10543
+ ];
10544
+ })
10545
+ ])
10546
+ );
9721
10547
  const phaseStyle = (phase) => ({
9722
10548
  color: "rgba(0, 0, 0, 0)",
9723
10549
  minZoom: 15,
@@ -9774,6 +10600,63 @@ function compileRoundabouts(request, surfaces, context, contributions) {
9774
10600
  });
9775
10601
  }
9776
10602
  }
10603
+ var bindable = /* @__PURE__ */ new Set([
10604
+ "case",
10605
+ "match",
10606
+ "all",
10607
+ "any",
10608
+ "!",
10609
+ "==",
10610
+ "!=",
10611
+ "<",
10612
+ "<=",
10613
+ ">",
10614
+ ">=",
10615
+ "has",
10616
+ "get",
10617
+ "to-number",
10618
+ "coalesce",
10619
+ "+",
10620
+ "-",
10621
+ "*",
10622
+ "/",
10623
+ "max",
10624
+ "min"
10625
+ ]);
10626
+ function bindRepeated(curve) {
10627
+ let tree = curve;
10628
+ for (let index = 0; index < 2; index += 1) {
10629
+ const json = /* @__PURE__ */ new Map();
10630
+ const found = /* @__PURE__ */ new Map();
10631
+ const visit = (value) => {
10632
+ if (!Array.isArray(value) || value[0] === "literal")
10633
+ return { text: JSON.stringify(value), free: true };
10634
+ const parts = value.map(visit);
10635
+ const text = `[${parts.map((part) => part.text).join(",")}]`;
10636
+ json.set(value, text);
10637
+ const free = value[0] !== "zoom" && value[0] !== "var" && parts.every((part) => part.free);
10638
+ if (free && typeof value[0] === "string" && bindable.has(value[0]))
10639
+ found.set(text, (found.get(text) ?? 0) + 1);
10640
+ return { text, free };
10641
+ };
10642
+ visit(tree);
10643
+ let best;
10644
+ let saving = 256;
10645
+ for (const [text, count] of found) {
10646
+ const saved = (count - 1) * text.length;
10647
+ if (count > 1 && saved > saving) [best, saving] = [text, saved];
10648
+ }
10649
+ if (best === void 0) break;
10650
+ const name = `tileflow_${index}`;
10651
+ const replace = (value) => {
10652
+ if (!Array.isArray(value) || value[0] === "literal") return value;
10653
+ if (json.get(value) === best) return ["var", name];
10654
+ return value.map(replace);
10655
+ };
10656
+ tree = ["let", name, JSON.parse(best), replace(tree)];
10657
+ }
10658
+ return tree;
10659
+ }
9777
10660
  function select(values, fallback) {
9778
10661
  if (!values.length) return fallback;
9779
10662
  const serialized = JSON.stringify(values[0].value);
@@ -9812,7 +10695,7 @@ function phaseVisibility(surfaces, phase, fields2) {
9812
10695
  function zoomLevels(values) {
9813
10696
  const levels = new Set(Array.from({ length: 10 }, (_, index) => index + 15));
9814
10697
  for (const value of values) {
9815
- if (!isZoomCurve(value)) continue;
10698
+ if (!isZoomCurve2(value)) continue;
9816
10699
  for (let index = 3; index < value.length - 1; index += 2) {
9817
10700
  const level = value[index];
9818
10701
  if (level < 15 || level > 24) continue;
@@ -9870,7 +10753,7 @@ function interpolationFactor2(interpolation, distance, span) {
9870
10753
  const logarithm = Math.log(interpolation[1]);
9871
10754
  return Math.expm1(logarithm * distance) / Math.expm1(logarithm * span);
9872
10755
  }
9873
- function isZoomCurve(value) {
10756
+ function isZoomCurve2(value) {
9874
10757
  return Array.isArray(value) && (value[0] === "interpolate" && isZoom(value[2]) || value[0] === "step" && isZoom(value[1]));
9875
10758
  }
9876
10759
  function isZoom(value) {
@@ -9879,19 +10762,55 @@ function isZoom(value) {
9879
10762
  function atZoom(value, level) {
9880
10763
  if (!Array.isArray(value) || value[0] === "literal") return value;
9881
10764
  if (isZoom(value)) return level;
9882
- if (!isZoomCurve(value)) return value.map((entry) => atZoom(entry, level));
10765
+ if (!isZoomCurve2(value)) return value.map((entry) => atZoom(entry, level));
9883
10766
  const stops = [];
9884
10767
  for (let index = 3; index < value.length - 1; index += 2) {
9885
10768
  stops.push({ level: value[index], value: value[index + 1] });
9886
10769
  }
9887
10770
  const exact = stops.find((stop) => stop.level === level);
9888
- if (exact) return exact.value;
10771
+ if (exact) return foldConstants(exact.value);
9889
10772
  const lower = [...stops].reverse().find((stop) => stop.level < level);
9890
10773
  const upper = stops.find((stop) => stop.level > level);
9891
- if (value[0] === "step") return lower?.value ?? value[2];
9892
- if (!lower) return upper?.value ?? 0;
9893
- if (!upper) return lower.value;
9894
- return ["interpolate", value[1], level, lower.level, lower.value, upper.level, upper.value];
10774
+ if (value[0] === "step") return foldConstants(lower?.value ?? value[2]);
10775
+ if (!lower) return foldConstants(upper?.value ?? 0);
10776
+ if (!upper) return foldConstants(lower.value);
10777
+ const at = interpolationFactor2(
10778
+ value[1],
10779
+ level - lower.level,
10780
+ upper.level - lower.level
10781
+ );
10782
+ const folded = Number.isFinite(at) ? interpolated(foldConstants(lower.value), foldConstants(upper.value), at) : void 0;
10783
+ return folded ?? ["interpolate", value[1], level, lower.level, lower.value, upper.level, upper.value];
10784
+ }
10785
+ function foldConstants(value) {
10786
+ if (!Array.isArray(value) || value[0] === "literal") return value;
10787
+ const folded = value.map(foldConstants);
10788
+ const [operator, ...operands] = folded;
10789
+ if (!operands.length || !operands.every((operand) => typeof operand === "number")) return folded;
10790
+ const numbers = operands;
10791
+ const result = operator === "*" ? numbers.reduce((product, number) => product * number, 1) : operator === "+" ? numbers.reduce((sum2, number) => sum2 + 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;
10792
+ return result !== void 0 && Number.isFinite(result) ? result : folded;
10793
+ }
10794
+ function interpolated(lower, upper, at) {
10795
+ if (typeof lower === "number" && typeof upper === "number") return lower + (upper - lower) * at;
10796
+ if (JSON.stringify(lower) === JSON.stringify(upper)) return lower;
10797
+ if (!Array.isArray(lower) || !Array.isArray(upper) || lower.length !== upper.length || lower[0] !== "case" && lower[0] !== "match" || lower[0] !== upper[0])
10798
+ return void 0;
10799
+ const first = lower[0] === "case" ? 1 : 2;
10800
+ const result = lower.slice(0, first);
10801
+ if (JSON.stringify(result) !== JSON.stringify(upper.slice(0, first))) return void 0;
10802
+ for (let index = first; index < lower.length; index += 1) {
10803
+ const output = (index - first) % 2 === 1 || index === lower.length - 1;
10804
+ if (!output) {
10805
+ if (JSON.stringify(lower[index]) !== JSON.stringify(upper[index])) return void 0;
10806
+ result.push(lower[index]);
10807
+ continue;
10808
+ }
10809
+ const value = interpolated(lower[index], upper[index], at);
10810
+ if (value === void 0) return void 0;
10811
+ result.push(value);
10812
+ }
10813
+ return result;
9895
10814
  }
9896
10815
 
9897
10816
  // src/modules/roads/compiler.ts
@@ -9962,6 +10881,10 @@ function compileRoads(request, context) {
9962
10881
  request?.classes?.[roadClass]
9963
10882
  );
9964
10883
  const treatments = resolveRoadTreatments(request, schema.fields, roadClass);
10884
+ const drawn = {
10885
+ field: schema.fields.class,
10886
+ classes: new Set(tileflowRoadClassValues(roadClass))
10887
+ };
9965
10888
  const roundaboutSurface = { roadClass };
9966
10889
  roundaboutSurfaces.push(roundaboutSurface);
9967
10890
  for (const structure of ["tunnel", "surface", "bridge"]) {
@@ -9984,7 +10907,8 @@ function compileRoads(request, context) {
9984
10907
  treatments,
9985
10908
  structure,
9986
10909
  phase,
9987
- phaseDefaults.color
10910
+ phaseDefaults.color,
10911
+ drawn
9988
10912
  );
9989
10913
  const contribution = {
9990
10914
  family: roadLinePhysicalFamily(roadClass, structure, phase),
@@ -10047,7 +10971,8 @@ function compileRoads(request, context) {
10047
10971
  treatments,
10048
10972
  structure,
10049
10973
  "fill",
10050
- roadColor(context, roadClass)
10974
+ roadColor(context, roadClass),
10975
+ drawn
10051
10976
  );
10052
10977
  const size = hatch.size === void 0 ? scaleStyleValue(treatedFill.width ?? 1, 0.68) : toMapLibreStyleValue(hatch.size);
10053
10978
  const minZoom = hatch.minZoom ?? treatedFill.minZoom;
@@ -10168,31 +11093,24 @@ function compileRoads(request, context) {
10168
11093
  const areaTargets = [
10169
11094
  {
10170
11095
  id: "tileflow-road-area",
10171
- filter: ["==", ["geometry-type"], "Polygon"],
11096
+ kind: void 0,
10172
11097
  name: "road",
10173
11098
  style: areaStyles.road
10174
11099
  },
10175
11100
  {
10176
11101
  id: "tileflow-road-pedestrian-area",
10177
- filter: [
10178
- "all",
10179
- ["==", ["geometry-type"], "Polygon"],
10180
- tileflowRoadClassFilter(schema.fields, "pedestrian")
10181
- ],
11102
+ kind: tileflowRoadClassFilter(schema.fields, "pedestrian"),
10182
11103
  name: "pedestrian",
10183
11104
  style: areaStyles.pedestrian
10184
11105
  },
10185
11106
  {
10186
11107
  id: "tileflow-road-pier-area",
10187
- filter: [
10188
- "all",
10189
- ["==", ["geometry-type"], "Polygon"],
10190
- ["==", ["get", schema.fields.class], "pier"]
10191
- ],
11108
+ kind: ["==", ["get", schema.fields.class], "pier"],
10192
11109
  name: "pier",
10193
11110
  style: areaStyles.pier
10194
11111
  }
10195
11112
  ];
11113
+ const polygons2 = ["==", ["geometry-type"], "Polygon"];
10196
11114
  let areaOrder = 900;
10197
11115
  for (const target of areaTargets) {
10198
11116
  if (!target.style) continue;
@@ -10202,13 +11120,19 @@ function compileRoads(request, context) {
10202
11120
  type: "fill",
10203
11121
  source,
10204
11122
  "source-layer": schema.layers.road,
10205
- filter: target.filter
11123
+ filter: target.kind ? ["all", polygons2, target.kind] : polygons2
10206
11124
  },
10207
11125
  target.style
10208
11126
  )) {
11127
+ const { filter: _polygonFilter, ...unfiltered } = area.layer;
11128
+ const outline = area.phase === "outline" && schema.layers.transportationOutline ? {
11129
+ ...unfiltered,
11130
+ "source-layer": schema.layers.transportationOutline,
11131
+ ...target.kind ? { filter: target.kind } : {}
11132
+ } : area.layer;
10209
11133
  contributions.push({
10210
11134
  kind: "layer",
10211
- layer: area.layer,
11135
+ layer: outline,
10212
11136
  localOrder: areaOrder++,
10213
11137
  owner: "roads",
10214
11138
  slot: "transport-areas",
@@ -10485,9 +11409,10 @@ function resolveRoadTreatments(request, fields2, roadClass) {
10485
11409
  add2(request?.modifiers?.unpaved, ["==", ["get", fields2.surface], "unpaved"]);
10486
11410
  add2(request?.modifiers?.indoor, flagFilter(fields2.indoor));
10487
11411
  add2(request?.modifiers?.official, flagFilter(fields2.official));
10488
- for (const [scale, style] of Object.entries(request?.mountainBike ?? {})) {
10489
- add2(style, ["==", ["to-string", ["get", fields2.mtbScale]], scale]);
10490
- }
11412
+ if (isTileflowPathRoadClass(roadClass) || roadClass === "track")
11413
+ for (const [scale, style] of Object.entries(request?.mountainBike ?? {})) {
11414
+ add2(style, ["==", ["to-string", ["get", fields2.mtbScale]], scale]);
11415
+ }
10491
11416
  if (roadClass === "service") {
10492
11417
  for (const [serviceType, value] of Object.entries(serviceTypeValues)) {
10493
11418
  add2(request?.serviceTypes?.[serviceType], [
@@ -10499,7 +11424,7 @@ function resolveRoadTreatments(request, fields2, roadClass) {
10499
11424
  }
10500
11425
  return entries;
10501
11426
  }
10502
- function applyRoadTreatments(base, treatments, structure, phase, fallbackColor) {
11427
+ function applyRoadTreatments(base, treatments, structure, phase, fallbackColor, drawn) {
10503
11428
  const result = { ...base };
10504
11429
  const paintProperties = [
10505
11430
  "blur",
@@ -10516,12 +11441,12 @@ function applyRoadTreatments(base, treatments, structure, phase, fallbackColor)
10516
11441
  return value === void 0 ? [] : [
10517
11442
  {
10518
11443
  condition,
10519
- resolve: () => expressionOutput(toMapLibreStyleValue(value))
11444
+ resolve: () => expressionOutput(withinClasses(toMapLibreStyleValue(value), drawn))
10520
11445
  }
10521
11446
  ];
10522
11447
  });
10523
11448
  if (branches.length > 0) {
10524
- if (property === "dash" && isSolidRoadDash(result[property] ?? treatmentPaintDefaults.dash) && branches.every(({ resolve }) => isSolidRoadDash(resolve()))) {
11449
+ if (property === "dash" && isSolidRoadDash(withinClasses(result[property] ?? treatmentPaintDefaults.dash, drawn)) && branches.every(({ resolve }) => isSolidRoadDash(resolve()))) {
10525
11450
  delete result[property];
10526
11451
  continue;
10527
11452
  }
@@ -10551,6 +11476,49 @@ function applyRoadTreatments(base, treatments, structure, phase, fallbackColor)
10551
11476
  }
10552
11477
  return result;
10553
11478
  }
11479
+ function withinClasses(value, drawn) {
11480
+ const decided = (condition) => {
11481
+ if (!Array.isArray(condition)) return typeof condition === "boolean" ? condition : void 0;
11482
+ const [operator, ...operands] = condition;
11483
+ 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");
11484
+ const among = (labels2) => {
11485
+ const hits = [...drawn.classes].filter((value2) => labels2.includes(value2)).length;
11486
+ return hits === 0 ? false : hits === drawn.classes.size ? true : void 0;
11487
+ };
11488
+ if ((operator === "==" || operator === "!=") && operands.length === 2) {
11489
+ const label = isClass(operands[0]) ? operands[1] : isClass(operands[1]) ? operands[0] : null;
11490
+ if (typeof label !== "string") return void 0;
11491
+ const equal = among([label]);
11492
+ return equal === void 0 ? void 0 : operator === "==" ? equal : !equal;
11493
+ }
11494
+ if (operator === "match" && operands.length === 4 && isClass(operands[0]) && typeof operands[2] === "boolean" && typeof operands[3] === "boolean") {
11495
+ const labels2 = Array.isArray(operands[1]) ? operands[1] : [operands[1]];
11496
+ const matched = among(labels2);
11497
+ return matched === void 0 ? void 0 : matched ? operands[2] : operands[3];
11498
+ }
11499
+ if (operator === "!" && operands.length === 1) {
11500
+ const inner = decided(operands[0]);
11501
+ return inner === void 0 ? void 0 : !inner;
11502
+ }
11503
+ if (operator === "all" || operator === "any") {
11504
+ const results = operands.map(decided);
11505
+ if (results.includes(operator === "all" ? false : true)) return operator !== "all";
11506
+ return results.every((result) => result !== void 0) ? operator === "all" : void 0;
11507
+ }
11508
+ return void 0;
11509
+ };
11510
+ if (!Array.isArray(value) || value[0] !== "case") return value;
11511
+ const branches = [];
11512
+ for (let index = 1; index < value.length - 1; index += 2) {
11513
+ const holds = decided(value[index]);
11514
+ if (holds === false) continue;
11515
+ const output = withinClasses(value[index + 1], drawn);
11516
+ if (holds === true) return branches.length ? ["case", ...branches, output] : output;
11517
+ branches.push(value[index], output);
11518
+ }
11519
+ const fallback = withinClasses(value.at(-1), drawn);
11520
+ return branches.length ? ["case", ...branches, fallback] : fallback;
11521
+ }
10554
11522
  function isSolidRoadDash(value) {
10555
11523
  const output = expressionOutput(value);
10556
11524
  const dash = Array.isArray(output) && output[0] === "literal" ? output[1] : output;
@@ -11120,13 +12088,13 @@ function tileflowVegetationMetadata(vegetation2) {
11120
12088
  return { version: 1, ...vegetation2 };
11121
12089
  }
11122
12090
  function readTileflowVegetation3D(metadata) {
11123
- if (!isRecord6(metadata)) return void 0;
12091
+ if (!isRecord7(metadata)) return void 0;
11124
12092
  const value = metadata[tileflowVegetationMetadataKey];
11125
- if (!isRecord6(value) || value.version !== 1) return void 0;
12093
+ if (!isRecord7(value) || value.version !== 1) return void 0;
11126
12094
  const { source, layers: layers2, fields: fields2, colors, heightScale, crownScale, unknownForms, regions: regions2 } = value;
11127
- if (typeof source !== "string" || !isRecord6(layers2) || typeof layers2.tree !== "string")
12095
+ if (typeof source !== "string" || !isRecord7(layers2) || typeof layers2.tree !== "string")
11128
12096
  return void 0;
11129
- if (!isRecord6(fields2) || !isRecord6(colors) || typeof colors.bark !== "string") return void 0;
12097
+ if (!isRecord7(fields2) || !isRecord7(colors) || typeof colors.bark !== "string") return void 0;
11130
12098
  const palette = (list) => Array.isArray(list) && list.length > 0 && list.every((entry) => typeof entry === "string") ? list : void 0;
11131
12099
  const broadleaf = palette(colors.broadleaf);
11132
12100
  const conifer = palette(colors.conifer);
@@ -11136,18 +12104,18 @@ function readTileflowVegetation3D(metadata) {
11136
12104
  const scale = (number) => typeof number === "number" && number > 0 && number <= 10 ? number : 1;
11137
12105
  const mix2 = tileflowTreeFormMixProblem(unknownForms) === void 0 ? unknownForms : void 0;
11138
12106
  const models = Object.fromEntries(
11139
- Object.entries(isRecord6(value.models) ? value.models : {}).filter(
11140
- ([id2, model]) => tileflowTreeModelIdPattern.test(id2) && isRecord6(model) && typeof model.near === "string" && (model.far === void 0 || typeof model.far === "string")
12107
+ Object.entries(isRecord7(value.models) ? value.models : {}).filter(
12108
+ ([id2, model]) => tileflowTreeModelIdPattern.test(id2) && isRecord7(model) && typeof model.near === "string" && (model.far === void 0 || typeof model.far === "string")
11141
12109
  ).slice(0, 16).map(([id2, model]) => {
11142
12110
  const { near, far } = model;
11143
12111
  return [id2, far === void 0 ? { near } : { near, far }];
11144
12112
  })
11145
12113
  );
11146
- const ownScales = isRecord6(value.forms) ? value.forms : {};
12114
+ const ownScales = isRecord7(value.forms) ? value.forms : {};
11147
12115
  const forms = Object.fromEntries(
11148
12116
  tileflowTreeForms.flatMap((form) => {
11149
12117
  const own = ownScales[form];
11150
- if (!isRecord6(own)) return [];
12118
+ if (!isRecord7(own)) return [];
11151
12119
  const drawn = Array.isArray(own.models) ? [...new Set(own.models)].filter((id2) => typeof id2 === "string" && id2 in models).slice(0, 4) : [];
11152
12120
  return [
11153
12121
  [
@@ -11161,10 +12129,10 @@ function readTileflowVegetation3D(metadata) {
11161
12129
  ];
11162
12130
  })
11163
12131
  );
11164
- const finish = tileflowTreeFinishes.find((name) => name === value.finish) ?? "faceted";
12132
+ const finish2 = tileflowTreeFinishes.find((name) => name === value.finish) ?? "faceted";
11165
12133
  const edges = typeof value.edges === "number" && value.edges >= 0 && value.edges <= 1 ? value.edges : 0;
11166
12134
  const zones = Array.isArray(regions2) ? regions2.filter(
11167
- (region) => isRecord6(region) && tileflowTreeRegionProblem(region) === void 0
12135
+ (region) => isRecord7(region) && tileflowTreeRegionProblem(region) === void 0
11168
12136
  ) : [];
11169
12137
  const named = Object.fromEntries(
11170
12138
  Object.entries(tileflowWorldTreeFields).map(([key, fallback]) => [
@@ -11185,7 +12153,7 @@ function readTileflowVegetation3D(metadata) {
11185
12153
  crownScale: scale(crownScale),
11186
12154
  forms,
11187
12155
  models,
11188
- finish,
12156
+ finish: finish2,
11189
12157
  edges,
11190
12158
  ...typeof value.edgeColor === "string" ? { edgeColor: value.edgeColor } : {},
11191
12159
  unknownForms: mix2 ?? tileflowDefaultUnknownTreeForms,
@@ -11194,7 +12162,7 @@ function readTileflowVegetation3D(metadata) {
11194
12162
  };
11195
12163
  }
11196
12164
  function tileflowTreeRegionProblem(region) {
11197
- if (!isRecord6(region)) return "a region is an object with bounds and unknownForms";
12165
+ if (!isRecord7(region)) return "a region is an object with bounds and unknownForms";
11198
12166
  const bounds = region.bounds;
11199
12167
  if (!Array.isArray(bounds) || bounds.length !== 4 || !bounds.every((value) => typeof value === "number" && Number.isFinite(value)))
11200
12168
  return "bounds are [west, south, east, north] in degrees";
@@ -11205,7 +12173,7 @@ function tileflowTreeRegionProblem(region) {
11205
12173
  const mix2 = tileflowTreeFormMixProblem(region.unknownForms);
11206
12174
  return mix2 ? `unknownForms: ${mix2}` : void 0;
11207
12175
  }
11208
- function isRecord6(value) {
12176
+ function isRecord7(value) {
11209
12177
  return typeof value === "object" && value !== null && !Array.isArray(value);
11210
12178
  }
11211
12179
 
@@ -12904,9 +13872,9 @@ var tileflowNoticeLimits = Object.freeze({
12904
13872
  maxSources: 16,
12905
13873
  maxTextLength: 120
12906
13874
  });
12907
- var identifierPattern = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/u;
13875
+ var identifierPattern2 = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/u;
12908
13876
  function readTileflowNotices(metadata) {
12909
- if (!isRecord7(metadata)) return [];
13877
+ if (!isRecord8(metadata)) return [];
12910
13878
  const value = metadata[tileflowNoticesMetadataKey];
12911
13879
  if (!Array.isArray(value)) return [];
12912
13880
  const notices = [];
@@ -12920,18 +13888,18 @@ function readTileflowNotices(metadata) {
12920
13888
  return Object.freeze(notices);
12921
13889
  }
12922
13890
  function readNotice(value) {
12923
- if (!isRecord7(value)) return void 0;
13891
+ if (!isRecord8(value)) return void 0;
12924
13892
  const { id: id2, sources, text } = value;
12925
- if (typeof id2 !== "string" || !identifierPattern.test(id2)) return void 0;
13893
+ if (typeof id2 !== "string" || !identifierPattern2.test(id2)) return void 0;
12926
13894
  if (typeof text !== "string" || text.trim() !== text || !text || text.length > tileflowNoticeLimits.maxTextLength || /\p{Cc}/u.test(text)) {
12927
13895
  return void 0;
12928
13896
  }
12929
- if (!Array.isArray(sources) || sources.length > tileflowNoticeLimits.maxSources || !sources.every((source) => typeof source === "string" && identifierPattern.test(source))) {
13897
+ if (!Array.isArray(sources) || sources.length > tileflowNoticeLimits.maxSources || !sources.every((source) => typeof source === "string" && identifierPattern2.test(source))) {
12930
13898
  return void 0;
12931
13899
  }
12932
13900
  return Object.freeze({ id: id2, sources: Object.freeze([...sources]), text });
12933
13901
  }
12934
- function isRecord7(value) {
13902
+ function isRecord8(value) {
12935
13903
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
12936
13904
  }
12937
13905
 
@@ -13608,21 +14576,21 @@ function bindSemanticValue(value, schema, sourceId) {
13608
14576
  }
13609
14577
  if (isDataSourceReference(value)) return sourceId;
13610
14578
  if (Array.isArray(value)) return value.map((item) => bindSemanticValue(item, schema, sourceId));
13611
- if (!isRecord8(value)) return value;
14579
+ if (!isRecord9(value)) return value;
13612
14580
  return Object.fromEntries(
13613
14581
  Object.entries(value).map(([key, item]) => [key, bindSemanticValue(item, schema, sourceId)])
13614
14582
  );
13615
14583
  }
13616
14584
  function isDataFieldReference2(value) {
13617
- return isRecord8(value) && value.kind === "tileflow-data-field" && typeof value.name === "string";
14585
+ return isRecord9(value) && value.kind === "tileflow-data-field" && typeof value.name === "string";
13618
14586
  }
13619
14587
  function isDataLayerReference(value) {
13620
- return isRecord8(value) && value.kind === "tileflow-data-layer" && typeof value.name === "string";
14588
+ return isRecord9(value) && value.kind === "tileflow-data-layer" && typeof value.name === "string";
13621
14589
  }
13622
14590
  function isDataSourceReference(value) {
13623
- return isRecord8(value) && value.kind === "tileflow-data-source" && value.name === "primary";
14591
+ return isRecord9(value) && value.kind === "tileflow-data-source" && value.name === "primary";
13624
14592
  }
13625
- function isRecord8(value) {
14593
+ function isRecord9(value) {
13626
14594
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
13627
14595
  }
13628
14596
  function compareCodeUnits5(left, right) {
@@ -13726,8 +14694,8 @@ function createTileflowLayerTemplateIR(layer, semanticKey) {
13726
14694
  renderer: type,
13727
14695
  ...filter === void 0 ? {} : { selector: cloneJson20(filter) },
13728
14696
  style: Object.freeze({
13729
- ...isRecord9(layout) ? { placement: Object.freeze(cloneJson20(layout)) } : {},
13730
- ...isRecord9(paint) ? { appearance: Object.freeze(cloneJson20(paint)) } : {}
14697
+ ...isRecord10(layout) ? { placement: Object.freeze(cloneJson20(layout)) } : {},
14698
+ ...isRecord10(paint) ? { appearance: Object.freeze(cloneJson20(paint)) } : {}
13731
14699
  })
13732
14700
  });
13733
14701
  }
@@ -13919,20 +14887,29 @@ var exactPhysicalLayerIds = Object.freeze({
13919
14887
  var nauticalPhysicalBases = Object.freeze({
13920
14888
  "nautical.aids": "tileflow-nautical-aids",
13921
14889
  "nautical.beacons": "tileflow-nautical-beacons",
14890
+ "nautical.berths": "tileflow-nautical-berths",
14891
+ "nautical.bridges": "tileflow-nautical-bridges",
13922
14892
  "nautical.buoys": "tileflow-nautical-buoys",
13923
14893
  "nautical.cables": "tileflow-nautical-cables",
13924
14894
  "nautical.coverage": "tileflow-nautical-coverage",
14895
+ "nautical.distanceMarks": "tileflow-nautical-distance-marks",
14896
+ "nautical.facilities": "tileflow-nautical-facilities",
14897
+ "nautical.harbourAreas": "tileflow-nautical-harbour-areas",
13925
14898
  "nautical.harbours": "tileflow-nautical-harbours",
13926
14899
  "nautical.hazardAreas": "tileflow-nautical-hazard-areas",
13927
14900
  "nautical.hazards": "tileflow-nautical-hazards",
14901
+ "nautical.labels.bridges": "tileflow-nautical-bridge-labels",
13928
14902
  "nautical.labels.coverage": "tileflow-nautical-coverage-labels",
13929
14903
  "nautical.labels.hazards": "tileflow-nautical-hazard-area-labels",
13930
14904
  "nautical.labels.navigationAreas": "tileflow-nautical-navigation-area-labels",
14905
+ "nautical.labels.overheadCables": "tileflow-nautical-overhead-cable-labels",
13931
14906
  "nautical.labels.reefs": "tileflow-nautical-reef-labels",
13932
14907
  "nautical.labels.wrecks": "tileflow-nautical-wreck-area-labels",
13933
14908
  "nautical.lighthouses": "tileflow-nautical-lighthouses",
13934
14909
  "nautical.lights": "tileflow-nautical-lights",
13935
14910
  "nautical.navigationAreas": "tileflow-nautical-navigation-areas",
14911
+ "nautical.notices": "tileflow-nautical-notices",
14912
+ "nautical.overheadCables": "tileflow-nautical-overhead-cables",
13936
14913
  "nautical.ports": "tileflow-nautical-ports",
13937
14914
  "nautical.reefs": "tileflow-nautical-reefs",
13938
14915
  "nautical.routes": "tileflow-nautical-routes",
@@ -13943,7 +14920,9 @@ var nauticalPhysicalBases = Object.freeze({
13943
14920
  function nauticalPhysicalLayerId(semanticKey) {
13944
14921
  const direct = nauticalPhysicalBases[semanticKey];
13945
14922
  if (direct) return direct;
13946
- const match3 = /^(nautical\.[A-Za-z0-9_.-]+)\.(fill|outline|marker|symbol)$/u.exec(semanticKey);
14923
+ const match3 = /^(nautical\.[A-Za-z0-9_.-]+)\.(fill|outline|marker|symbol|lines)$/u.exec(
14924
+ semanticKey
14925
+ );
13947
14926
  if (!match3) return void 0;
13948
14927
  const base = nauticalPhysicalBases[match3[1]];
13949
14928
  if (!base) return void 0;
@@ -13987,21 +14966,21 @@ function visitReferences(value, counts, visited = /* @__PURE__ */ new WeakSet())
13987
14966
  }
13988
14967
  }
13989
14968
  function isSemanticFieldReference(value) {
13990
- return isRecord9(value) && value.kind === "tileflow-data-field";
14969
+ return isRecord10(value) && value.kind === "tileflow-data-field";
13991
14970
  }
13992
14971
  function isSemanticLayerReference(value) {
13993
- return isRecord9(value) && value.kind === "tileflow-data-layer";
14972
+ return isRecord10(value) && value.kind === "tileflow-data-layer";
13994
14973
  }
13995
14974
  function isSemanticSourceReference(value) {
13996
- return isRecord9(value) && value.kind === "tileflow-data-source";
14975
+ return isRecord10(value) && value.kind === "tileflow-data-source";
13997
14976
  }
13998
14977
  function cloneJson20(value) {
13999
14978
  return JSON.parse(JSON.stringify(value));
14000
14979
  }
14001
14980
  function asRecord5(value) {
14002
- return isRecord9(value) ? value : {};
14981
+ return isRecord10(value) ? value : {};
14003
14982
  }
14004
- function isRecord9(value) {
14983
+ function isRecord10(value) {
14005
14984
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
14006
14985
  }
14007
14986
 
@@ -14065,7 +15044,7 @@ function validateTileflowRenderSelectorConstraints(input) {
14065
15044
  };
14066
15045
  while (pending.length > 0) {
14067
15046
  const current = pending.pop();
14068
- if (!isRecord10(current.selector) || typeof current.selector.kind !== "string") {
15047
+ if (!isRecord11(current.selector) || typeof current.selector.kind !== "string") {
14069
15048
  addIssue(
14070
15049
  "invalid-selector",
14071
15050
  "a render selector must be a semantic selector node",
@@ -14126,7 +15105,7 @@ function validateTileflowRenderSelectorConstraints(input) {
14126
15105
  }
14127
15106
  for (const [index, branch] of current.selector.branches.entries()) {
14128
15107
  const branchPath = [...branchesPath, index];
14129
- if (!isRecord10(branch)) {
15108
+ if (!isRecord11(branch)) {
14130
15109
  addIssue("invalid-selector", "match selector branch must be an object", branchPath);
14131
15110
  continue;
14132
15111
  }
@@ -14157,14 +15136,14 @@ function validateTileflowRenderSelectorConstraints(input) {
14157
15136
  for (let index = current.selector.stops.length - 1; index >= 0; index -= 1) {
14158
15137
  const stop = current.selector.stops[index];
14159
15138
  const stopPath = [...stopsPath, index];
14160
- if (!isRecord10(stop)) {
15139
+ if (!isRecord11(stop)) {
14161
15140
  addIssue("invalid-selector", "step selector stop must be an object", stopPath);
14162
15141
  continue;
14163
15142
  }
14164
15143
  pushChild(stop.selector, current.depth + 1, [...stopPath, "selector"]);
14165
15144
  }
14166
15145
  for (const [index, stop] of current.selector.stops.entries()) {
14167
- if (!isRecord10(stop)) continue;
15146
+ if (!isRecord11(stop)) continue;
14168
15147
  const zoom3 = stop.zoom;
14169
15148
  const zoomPath = [...stopsPath, index, "zoom"];
14170
15149
  if (typeof zoom3 !== "number" || !Number.isFinite(zoom3) || zoom3 < 0 || zoom3 > 24) {
@@ -14286,7 +15265,7 @@ function compileRenderSelector(selector, data) {
14286
15265
  assertRenderSelectorConstraints(selector);
14287
15266
  return lowerSelector(selector, data);
14288
15267
  }
14289
- function applyCompiledRenderStacks(input, operations2) {
15268
+ function applyCompiledRenderStacks(input, operations2, targetsOfOtherModes = /* @__PURE__ */ new Map()) {
14290
15269
  let layers2 = input.map(cloneJson21);
14291
15270
  const passes = operations2.filter(
14292
15271
  (operation) => operation.kind === "layer"
@@ -14317,9 +15296,10 @@ function applyCompiledRenderStacks(input, operations2) {
14317
15296
  for (const refinement of activeRefinements) {
14318
15297
  const index = findTargetIndex(layers2, refinement.target);
14319
15298
  if (index < 0 && !passTargets.has(refinement.target)) {
15299
+ const other = targetsOfOtherModes.get(refinement.target);
14320
15300
  throw renderStackError(
14321
15301
  "unknown-refinement-target",
14322
- `Render refinement targets unknown semantic contribution: ${refinement.target}.`
15302
+ other === void 0 ? `Render refinement targets unknown semantic contribution: ${refinement.target}.` : `Render refinement targets ${refinement.target}, ${describeOtherMode(other)}`
14323
15303
  );
14324
15304
  }
14325
15305
  if (refinement.placement !== void 0 && placementAnchor(refinement.placement) === refinement.target) {
@@ -14341,9 +15321,10 @@ function applyCompiledRenderStacks(input, operations2) {
14341
15321
  for (const operation of pending) {
14342
15322
  const anchor = placementAnchor(operation.placement);
14343
15323
  if (anchor !== void 0 && !knownTargets.has(anchor)) {
15324
+ const other = targetsOfOtherModes.get(anchor);
14344
15325
  throw renderStackError(
14345
15326
  "unknown-or-circular-anchor",
14346
- `${operation.target} is placed beside unknown semantic target ${anchor}.`
15327
+ other === void 0 ? `${operation.target} is placed beside unknown semantic target ${anchor}.` : `${operation.target} is placed beside ${anchor}, ${describeOtherMode(other)}`
14347
15328
  );
14348
15329
  }
14349
15330
  }
@@ -14547,7 +15528,7 @@ function applyRendererStyle(layer, renderer, style) {
14547
15528
  };
14548
15529
  }
14549
15530
  function lowerSelector(selector, data) {
14550
- if (!isRecord10(selector) || typeof selector.kind !== "string") {
15531
+ if (!isRecord11(selector) || typeof selector.kind !== "string") {
14551
15532
  throw renderStackError(
14552
15533
  "invalid-selector",
14553
15534
  "A render selector must be a semantic selector node."
@@ -14750,7 +15731,7 @@ function requireRenderer(value) {
14750
15731
  function requirePlacement(value) {
14751
15732
  if (typeof value === "string") {
14752
15733
  if (tileflowBandPlacements.includes(value)) return;
14753
- } else if (isRecord10(value)) {
15734
+ } else if (isRecord11(value)) {
14754
15735
  const keys = Object.keys(value);
14755
15736
  if (keys.length === 1 && (keys[0] === "above" || keys[0] === "below")) {
14756
15737
  requirePortableTarget(value[keys[0]], "render placement target");
@@ -14777,7 +15758,7 @@ function validateRefinementShape(operation) {
14777
15758
  validateStyle(operation.renderer, operation.style);
14778
15759
  }
14779
15760
  function validateStyle(renderer, style) {
14780
- if (!isRecord10(style)) {
15761
+ if (!isRecord11(style)) {
14781
15762
  throw renderStackError(
14782
15763
  "invalid-style",
14783
15764
  `A ${renderer} render operation requires a style object.`
@@ -14879,7 +15860,7 @@ function normalizeRequirements(requirements) {
14879
15860
  return [...requirements];
14880
15861
  }
14881
15862
  function requireRenderStackEntries(stack) {
14882
- if (!isRecord10(stack)) {
15863
+ if (!isRecord11(stack)) {
14883
15864
  throw renderStackError("invalid-stack", "A render stack must be a named operation record.");
14884
15865
  }
14885
15866
  const entries = Object.entries(stack);
@@ -15016,6 +15997,9 @@ function assertOnlyKeys(input, allowed, description) {
15016
15997
  );
15017
15998
  }
15018
15999
  }
16000
+ function describeOtherMode({ activeMode, mode, owner }) {
16001
+ 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.`;
16002
+ }
15019
16003
  function renderStackError(code, message) {
15020
16004
  return Object.assign(new Error(message), { code, name: "TileflowRenderStackError" });
15021
16005
  }
@@ -15026,9 +16010,9 @@ function cloneJson21(value) {
15026
16010
  return JSON.parse(JSON.stringify(value));
15027
16011
  }
15028
16012
  function asRecord6(value) {
15029
- return isRecord10(value) ? value : {};
16013
+ return isRecord11(value) ? value : {};
15030
16014
  }
15031
- function isRecord10(value) {
16015
+ function isRecord11(value) {
15032
16016
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
15033
16017
  }
15034
16018
 
@@ -15244,6 +16228,7 @@ function isLoopbackHostname(hostname) {
15244
16228
  // src/resolved-map-schema.ts
15245
16229
  init_time_of_day();
15246
16230
  init_time_of_day_schema();
16231
+ init_camera_limits();
15247
16232
  var TileflowResolvedMapValidationError = class extends Error {
15248
16233
  code = "CONFIG_INVALID";
15249
16234
  messages;
@@ -16106,6 +17091,7 @@ function boundedThemeNumberSchema(min, max) {
16106
17091
  }).strict()
16107
17092
  ]);
16108
17093
  }
17094
+ var entranceBandSchema = z7.tuple([z7.number().finite().min(0).max(24), z7.number().finite().min(0).max(24)]).refine(([from, to]) => from < to, "A band starts at a lower zoom than it ends.");
16109
17095
  var modelRoleLightingSchema = z7.object({
16110
17096
  emissiveStrength: boundedThemeNumberSchema(0, 5).optional(),
16111
17097
  emissiveVariation: boundedThemeNumberSchema(0, 1).optional(),
@@ -16161,6 +17147,7 @@ var buildingsModuleSchema = z7.object({
16161
17147
  lamp: modelRoleLightingSchema.optional(),
16162
17148
  logo: modelRoleLightingSchema.optional()
16163
17149
  }).strict().optional(),
17150
+ entrance: z7.object({ buildings: entranceBandSchema.optional(), models: entranceBandSchema.optional() }).strict().optional(),
16164
17151
  occlusion: z7.boolean().optional(),
16165
17152
  modelOcclusion: boundedThemeNumberSchema(0, 1).optional(),
16166
17153
  shadows: z7.boolean().optional(),
@@ -16399,24 +17386,34 @@ var poiModuleSchema = z7.object({
16399
17386
  var nauticalModuleSchema = z7.object({
16400
17387
  type: z7.literal("nautical"),
16401
17388
  aids: markerSymbolStyleSchema.optional(),
17389
+ animateLights: z7.boolean().optional(),
16402
17390
  beacons: markerSymbolStyleSchema.optional(),
17391
+ berths: areaStyleSchema.optional(),
17392
+ bridges: areaStyleSchema.optional(),
16403
17393
  buoys: markerSymbolStyleSchema.optional(),
16404
17394
  cables: lineStyleSchema.optional(),
16405
17395
  coverage: areaStyleSchema.optional(),
17396
+ distanceMarks: markerSymbolStyleSchema.optional(),
16406
17397
  enabled: z7.boolean().optional(),
17398
+ facilities: markerSymbolStyleSchema.optional(),
17399
+ harbourAreas: areaStyleSchema.optional(),
16407
17400
  harbours: markerSymbolStyleSchema.optional(),
16408
17401
  hazardAreas: areaStyleSchema.optional(),
16409
17402
  hazards: markerSymbolStyleSchema.optional(),
16410
17403
  labels: z7.object({
17404
+ bridges: symbolStyleSchema.optional(),
16411
17405
  coverage: symbolStyleSchema.optional(),
16412
17406
  hazards: symbolStyleSchema.optional(),
16413
17407
  navigationAreas: symbolStyleSchema.optional(),
17408
+ overheadCables: symbolStyleSchema.optional(),
16414
17409
  reefs: symbolStyleSchema.optional(),
16415
17410
  wrecks: symbolStyleSchema.optional()
16416
17411
  }).strict().optional(),
16417
17412
  lighthouses: markerSymbolStyleSchema.optional(),
16418
17413
  lights: markerSymbolStyleSchema.optional(),
16419
17414
  navigationAreas: areaStyleSchema.optional(),
17415
+ notices: markerSymbolStyleSchema.optional(),
17416
+ overheadCables: lineStyleSchema.optional(),
16420
17417
  ports: markerSymbolStyleSchema.optional(),
16421
17418
  reefs: areaStyleSchema.optional(),
16422
17419
  renderStack: renderStackSchema.optional(),
@@ -16540,6 +17537,7 @@ var layerBindingsSchema = z7.object({
16540
17537
  streetFurniture: z7.string().trim().min(1).optional(),
16541
17538
  trafficControl: z7.string().trim().min(1).optional(),
16542
17539
  tree: z7.string().trim().min(1).optional(),
17540
+ transportationOutline: z7.string().trim().min(1).optional(),
16543
17541
  water: z7.string().trim().min(1),
16544
17542
  waterName: z7.string().trim().min(1),
16545
17543
  waterway: z7.string().trim().min(1)
@@ -16948,7 +17946,7 @@ var terrainContoursSchema = z7.object({
16948
17946
  demUrl: z7.string().min(1).max(2048).refine(
16949
17947
  isSafeTileflowDemUrlTemplate,
16950
17948
  "Expected a safe HTTP(S) DEM URL template containing {z}, {x}, and {y}"
16951
- ),
17949
+ ).optional(),
16952
17950
  index: terrainContourLineSchema.optional(),
16953
17951
  labels: terrainContourLabelSchema.optional(),
16954
17952
  maxZoom: z7.number().int().min(0).max(24).optional(),
@@ -17072,10 +18070,25 @@ var atmosphereSchema = z7.union([
17072
18070
  starParallax: z7.number().finite().min(0).max(1).optional()
17073
18071
  }).strict()
17074
18072
  ]);
18073
+ var skyNumberSchema = z7.union([
18074
+ z7.number().finite().min(0).max(1),
18075
+ fixedNumberSchema.extend({ value: z7.number().finite().min(0).max(1) }),
18076
+ themeNumberReferenceSchema
18077
+ ]);
18078
+ var skySchema = z7.union([
18079
+ z7.boolean(),
18080
+ z7.object({
18081
+ color: atmosphereColorSchema.optional(),
18082
+ horizonColor: atmosphereColorSchema.optional(),
18083
+ horizonBlend: skyNumberSchema.optional(),
18084
+ glowColor: atmosphereColorSchema.optional(),
18085
+ starIntensity: skyNumberSchema.optional()
18086
+ }).strict()
18087
+ ]);
17075
18088
  var viewSchema = z7.object({
17076
18089
  bearing: z7.number().finite().min(-180).max(180).optional(),
17077
18090
  center: z7.tuple([z7.number().finite().min(-180).max(180), z7.number().finite().min(-90).max(90)]).optional(),
17078
- pitch: z7.number().finite().min(0).max(85).optional(),
18091
+ pitch: z7.number().finite().min(0).max(tileflowMaxPitch).optional(),
17079
18092
  zoom: zoomNumberSchema.optional()
17080
18093
  }).strict();
17081
18094
  var themesSchema = z7.record(tileflowThemeNameSchema, tileflowThemeSchema).superRefine((themes, context) => {
@@ -17118,6 +18131,7 @@ var resolvedTileflowMapSchema = z7.object({
17118
18131
  name: z7.string().trim().min(1),
17119
18132
  overlays: overlaysSchema.optional(),
17120
18133
  projection: z7.enum(["globe", "mercator"]).optional(),
18134
+ sky: skySchema.optional(),
17121
18135
  sources: hostedSourcesSchema.optional(),
17122
18136
  systemThemes: systemThemesSchema.optional(),
17123
18137
  terrain: terrainSchema.optional(),
@@ -17431,7 +18445,7 @@ function resolveTerrain(terrain, apiBaseUrl) {
17431
18445
  const mode = terrainConfig.mode ?? "hillshade";
17432
18446
  const encoding = terrainConfig.encoding ?? "terrarium";
17433
18447
  const raster = mode === "none" ? void 0 : resolveRaster(terrainConfig, apiBaseUrl, encoding);
17434
- const contours = terrainConfig.contours ? resolveContours(terrainConfig.contours, encoding, terrainConfig.attribution) : void 0;
18448
+ const contours = terrainConfig.contours ? resolveContours(terrainConfig.contours, encoding, terrainConfig.attribution, apiBaseUrl) : void 0;
17435
18449
  if (!raster && !contours) return void 0;
17436
18450
  return {
17437
18451
  ...contours ? { contours } : {},
@@ -17563,7 +18577,7 @@ function resolveRaster(terrain, apiBaseUrl, encoding) {
17563
18577
  }
17564
18578
  };
17565
18579
  }
17566
- function resolveContours(contours, encoding, attribution) {
18580
+ function resolveContours(contours, encoding, attribution, apiBaseUrl) {
17567
18581
  const thresholdZooms = Object.keys(contours.thresholds).map(Number);
17568
18582
  const minimumZoom2 = contours.minZoom ?? Math.min(...thresholdZooms);
17569
18583
  const maximumZoom2 = contours.maxZoom ?? Math.max(...thresholdZooms);
@@ -17571,7 +18585,7 @@ function resolveContours(contours, encoding, attribution) {
17571
18585
  const tiles = [
17572
18586
  createTileflowContourProtocolUrl({
17573
18587
  demMaxzoom: contours.demMaxZoom,
17574
- demUrl: contours.demUrl,
18588
+ demUrl: contours.demUrl ?? `${apiBaseUrl}/tiles/terrain/{z}/{x}/{y}.webp`,
17575
18589
  encoding,
17576
18590
  maxzoom: maximumZoom2,
17577
18591
  multiplier: contours.multiplier ?? 1,
@@ -18229,11 +19243,11 @@ function isTileflowPoiCategory(value) {
18229
19243
  return typeof value === "string" && tileflowPoiCategories.includes(value);
18230
19244
  }
18231
19245
  function semanticTarget(layer) {
18232
- if (!isRecord11(layer.metadata)) return void 0;
19246
+ if (!isRecord12(layer.metadata)) return void 0;
18233
19247
  const value = layer.metadata[tileflowCompilerMetadataKeys.target];
18234
19248
  return typeof value === "string" ? value : void 0;
18235
19249
  }
18236
- function isRecord11(value) {
19250
+ function isRecord12(value) {
18237
19251
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
18238
19252
  }
18239
19253
  function isSafeArtifactName(value) {
@@ -18415,6 +19429,8 @@ function mergeRoadLineGroup(key, layers2) {
18415
19429
  const paintKeys = new Set(layers2.flatMap((layer) => Object.keys(layer.style.appearance ?? {})));
18416
19430
  for (const key2 of paintKeys) {
18417
19431
  const values = layers2.map((layer) => layer.style.appearance?.[key2]);
19432
+ if (key2 === "line-dasharray" && values.every((value) => isSolidDash(value ?? linePaintDefaults[key2])))
19433
+ continue;
18418
19434
  const combined = combineConditionalValues(
18419
19435
  values.map((value, index) => ({
18420
19436
  condition: conditions[index],
@@ -18675,8 +19691,8 @@ function consolidateFillFamily(layers2, group, mergedId) {
18675
19691
  }
18676
19692
  const replacements = /* @__PURE__ */ new Map();
18677
19693
  let suffix = 0;
18678
- for (const grouped of groups.values()) {
18679
- for (const group2 of contiguousRuns(grouped)) {
19694
+ for (const grouped2 of groups.values()) {
19695
+ for (const group2 of contiguousRuns(grouped2)) {
18680
19696
  if (group2.length < 2) continue;
18681
19697
  const first = group2[0].layer;
18682
19698
  if (!group2.every(({ layer }) => sameTopLevelProperties(layer, first))) continue;
@@ -19131,7 +20147,7 @@ var colourSources = [
19131
20147
  ["tileflow-buildings-fill", "fill-color"]
19132
20148
  ];
19133
20149
  function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, tileflowLandmarks) {
19134
- const config = isRecord12(module) ? module : {};
20150
+ const config = isRecord13(module) ? module : {};
19135
20151
  if (config.enabled === false) return void 0;
19136
20152
  const style = config.threeDimensional ?? {};
19137
20153
  const defaults4 = tileflowDefaultBuildings3DDesign;
@@ -19164,7 +20180,7 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
19164
20180
  edgeRadius: number(style.edgeRadius, defaults4.edgeRadius),
19165
20181
  ...typeof edgeColor === "string" ? { edgeColor } : {},
19166
20182
  nightLight: number(style.nightLight, 0),
19167
- ...isRecord12(style.floodLight) ? {
20183
+ ...isRecord13(style.floodLight) ? {
19168
20184
  floodLight: readBuildingFloodLight({
19169
20185
  ...style.floodLight,
19170
20186
  intensity: number(style.floodLight.intensity, 0),
@@ -19172,10 +20188,10 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
19172
20188
  color: style.floodLight.color === void 0 ? void 0 : toMapLibreStyleValue(style.floodLight.color)
19173
20189
  })
19174
20190
  } : {},
19175
- ...isRecord12(style.modelLighting) ? {
20191
+ ...isRecord13(style.modelLighting) ? {
19176
20192
  modelLighting: readModelRoleLighting(
19177
20193
  Object.fromEntries(
19178
- Object.entries(style.modelLighting).filter(([, entry]) => isRecord12(entry)).map(([role, entry]) => {
20194
+ Object.entries(style.modelLighting).filter(([, entry]) => isRecord13(entry)).map(([role, entry]) => {
19179
20195
  const own = entry;
19180
20196
  return [
19181
20197
  role,
@@ -19186,7 +20202,7 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
19186
20202
  ["emissiveVariation", "colorMix", "metallic", "roughness"].filter((key) => own[key] !== void 0).map((key) => [key, number(own[key], 0)])
19187
20203
  ),
19188
20204
  ...Array.isArray(own.emissiveHeight) ? { emissiveHeight: own.emissiveHeight.map((v) => number(v, 0)) } : {},
19189
- ...isRecord12(own.colorVariation) ? {
20205
+ ...isRecord13(own.colorVariation) ? {
19190
20206
  colorVariation: {
19191
20207
  ...own.colorVariation,
19192
20208
  ...Object.fromEntries(
@@ -19208,6 +20224,7 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
19208
20224
  )
19209
20225
  } : {},
19210
20226
  windows: style.windows === true,
20227
+ ...readTileflowBuildingsEntrance(style.entrance),
19211
20228
  shadows: typeof style.shadows === "boolean" ? style.shadows : defaults4.shadows,
19212
20229
  occlusion: typeof style.occlusion === "boolean" ? style.occlusion : defaults4.occlusion,
19213
20230
  modelOcclusion: number(style.modelOcclusion, defaults4.modelOcclusion),
@@ -19218,7 +20235,7 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
19218
20235
  }
19219
20236
  function landmarksOf2(value, tileflowLandmarks) {
19220
20237
  if (value === false) return {};
19221
- if (isRecord12(value) && typeof value.url === "string") return { landmarks: { url: value.url } };
20238
+ if (isRecord13(value) && typeof value.url === "string") return { landmarks: { url: value.url } };
19222
20239
  return tileflowLandmarks ? { landmarks: { url: tileflowLandmarks } } : {};
19223
20240
  }
19224
20241
  function placedModels(value, served) {
@@ -19245,7 +20262,7 @@ function placedModels(value, served) {
19245
20262
  };
19246
20263
  }
19247
20264
  function modelColours(value) {
19248
- if (!isRecord12(value)) return {};
20265
+ if (!isRecord13(value)) return {};
19249
20266
  const colours = {};
19250
20267
  for (const [part, colour] of Object.entries(value)) {
19251
20268
  const resolved = colour === void 0 ? void 0 : toMapLibreStyleValue(colour);
@@ -19254,7 +20271,7 @@ function modelColours(value) {
19254
20271
  return Object.keys(colours).length > 0 ? { modelColors: colours } : {};
19255
20272
  }
19256
20273
  function tileflowStreetLightsDesignOf(module, context) {
19257
- const config = isRecord12(module) ? module : {};
20274
+ const config = isRecord13(module) ? module : {};
19258
20275
  const layers2 = context.data.schema.layers;
19259
20276
  const style = config.streetLights ?? {};
19260
20277
  if (config.enabled === false || style.enabled !== true || !layers2.streetFurniture)
@@ -19274,7 +20291,7 @@ function tileflowStreetLightsDesignOf(module, context) {
19274
20291
  intensity: typeof intensity === "number" ? intensity : 1
19275
20292
  };
19276
20293
  }
19277
- function isRecord12(value) {
20294
+ function isRecord13(value) {
19278
20295
  return typeof value === "object" && value !== null && !Array.isArray(value);
19279
20296
  }
19280
20297
 
@@ -19465,7 +20482,7 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
19465
20482
  )
19466
20483
  );
19467
20484
  const renderStackModules = Object.values(modules).filter(
19468
- (module) => isRecord13(module) && module.enabled !== false && "renderStack" in module && isRecord13(module.renderStack)
20485
+ (module) => isRecord14(module) && module.enabled !== false && "renderStack" in module && isRecord14(module.renderStack)
19469
20486
  );
19470
20487
  const renderOperations = runCompilationPhase(
19471
20488
  "render-stack",
@@ -19491,7 +20508,11 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
19491
20508
  const requestedRenderOperations = domainIR.renderOperations;
19492
20509
  const stackedFamilies = runCompilationPhase(
19493
20510
  "render-stack",
19494
- () => applyCompiledRenderStacks(assembledFamilies, requestedRenderOperations)
20511
+ () => applyCompiledRenderStacks(
20512
+ assembledFamilies,
20513
+ requestedRenderOperations,
20514
+ buildingTargetsOfOtherModes(modules.buildings)
20515
+ )
19495
20516
  );
19496
20517
  const plannedFamilies = runCompilationPhase(
19497
20518
  "physical-planner",
@@ -19707,9 +20728,15 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
19707
20728
  layer.layout = { ...layer.layout, visibility: "none" };
19708
20729
  }
19709
20730
  const atmosphere = resolveTileflowAtmosphere(config.atmosphere);
20731
+ const sky = resolveTileflowSky(config.sky);
19710
20732
  const notices = compiledNotices(layers2, marine);
20733
+ const nauticalLights = declareTileflowNauticalLights(
20734
+ layers2,
20735
+ marine?.nautical?.sourceId,
20736
+ tileflowNauticalV1Schema.layers.lights
20737
+ );
19711
20738
  const style = {
19712
- ...atmosphere ? { sky: compileTileflowSky(atmosphere) } : {},
20739
+ ...sky ? { sky: compileTileflowStreetSky(sky) } : atmosphere ? { sky: compileTileflowSky(atmosphere) } : {},
19713
20740
  version: 8,
19714
20741
  name: config.name ?? "Streets",
19715
20742
  ...glyphs ? { glyphs } : {},
@@ -19728,6 +20755,7 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
19728
20755
  ...mapMetadata.lineage && mapMetadata.lineage.length > 1 ? { "tileflow:extends": mapMetadata.lineage.slice(1) } : {}
19729
20756
  } : {},
19730
20757
  ...atmosphere ? { [tileflowAtmosphereMetadataKey]: { version: 1, ...atmosphere } } : {},
20758
+ ...sky ? { [tileflowSkyMetadataKey]: skyMetadata(input, selected.name, sky) } : {},
19731
20759
  "tileflow:theme": selected.name,
19732
20760
  "tileflow:colorScheme": resolvedTheme.colorScheme,
19733
20761
  [tileflowRouteMetadataKey]: tileflowRouteMetadata(resolveTileflowRouteInks(resolvedTheme)),
@@ -19756,9 +20784,10 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
19756
20784
  "tileflow:sourceRequirements": sourceRequirements,
19757
20785
  ...interactionManifest ? { [tileflowInteractionManifestMetadataKey]: interactionManifest } : {},
19758
20786
  [tileflowOverlayPlacementManifestMetadataKey]: overlayPlacementManifest,
19759
- "tileflow:modules": Object.entries(modules).filter(([, module]) => !isRecord13(module) || module.enabled !== false).map(([name]) => name).sort(),
20787
+ "tileflow:modules": Object.entries(modules).filter(([, module]) => !isRecord14(module) || module.enabled !== false).map(([name]) => name).sort(),
19760
20788
  ...config.view ? { "tileflow:view": config.view } : {},
19761
- ...notices.length > 0 ? { [tileflowNoticesMetadataKey]: notices } : {}
20789
+ ...notices.length > 0 ? { [tileflowNoticesMetadataKey]: notices } : {},
20790
+ ...nauticalLights ? { [tileflowNauticalLightsMetadataKey]: nauticalLights } : {}
19762
20791
  }
19763
20792
  };
19764
20793
  const report = {
@@ -19818,7 +20847,7 @@ function finalizeTileflowLayers(input) {
19818
20847
  if (typeof minimum2 === "number" && typeof maximum2 === "number" && minimum2 > maximum2) {
19819
20848
  throw new Error(`Compiled Tileflow layer ${id2} requires minzoom <= maxzoom.`);
19820
20849
  }
19821
- const metadata = isRecord13(layer.metadata) ? { ...layer.metadata } : void 0;
20850
+ const metadata = isRecord14(layer.metadata) ? { ...layer.metadata } : void 0;
19822
20851
  if (metadata) {
19823
20852
  const owner = metadata[tileflowCompilerMetadataKeys.owner];
19824
20853
  delete metadata[tileflowCompilerProvenanceMetadataKey];
@@ -19852,7 +20881,7 @@ function assertMapLibreStyle(style, mapId) {
19852
20881
  }
19853
20882
  );
19854
20883
  }
19855
- function isRecord13(value) {
20884
+ function isRecord14(value) {
19856
20885
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
19857
20886
  }
19858
20887
  function compiledNotices(layers2, marine) {
@@ -19897,8 +20926,8 @@ function assertPreparedIconReferences(style, config, prepared) {
19897
20926
  function collectStyleImageReferences(style) {
19898
20927
  const result = /* @__PURE__ */ new Set();
19899
20928
  for (const layer of style.layers) {
19900
- const layout = isRecord13(layer.layout) ? layer.layout : {};
19901
- const paint = isRecord13(layer.paint) ? layer.paint : {};
20929
+ const layout = isRecord14(layer.layout) ? layer.layout : {};
20930
+ const paint = isRecord14(layer.paint) ? layer.paint : {};
19902
20931
  const layerId = typeof layer.id === "string" ? layer.id : "<unknown>";
19903
20932
  for (const [property, value] of [
19904
20933
  ["icon-image", layout["icon-image"]],
@@ -19987,7 +21016,7 @@ function assertGlyphFontStacks(style, config) {
19987
21016
  const declared = new Set(config.glyphs.fontStacks);
19988
21017
  const missing = /* @__PURE__ */ new Set();
19989
21018
  for (const layer of style.layers) {
19990
- const layout = isRecord13(layer.layout) ? layer.layout : void 0;
21019
+ const layout = isRecord14(layer.layout) ? layer.layout : void 0;
19991
21020
  const font = layout?.["text-font"];
19992
21021
  if (!Array.isArray(font) || !font.every((entry) => typeof entry === "string")) continue;
19993
21022
  const stack = font.join(",");
@@ -20003,7 +21032,7 @@ function assertGlyphFontStacks(style, config) {
20003
21032
  function assertTextAssets(style, config) {
20004
21033
  const textLayers = [];
20005
21034
  for (const layer of style.layers) {
20006
- const layout = isRecord13(layer.layout) ? layer.layout : void 0;
21035
+ const layout = isRecord14(layer.layout) ? layer.layout : void 0;
20007
21036
  if (layout?.["text-field"] === void 0) continue;
20008
21037
  textLayers.push(typeof layer.id === "string" ? layer.id : "<unknown>");
20009
21038
  const font = layout["text-font"];
@@ -20016,7 +21045,7 @@ function assertTextAssets(style, config) {
20016
21045
  if (textLayers.length === 0) return;
20017
21046
  if (config.fonts !== void 0) {
20018
21047
  const backed = style.layers.find((layer) => {
20019
- const font = isRecord13(layer.layout) ? layer.layout["text-font"] : void 0;
21048
+ const font = isRecord14(layer.layout) ? layer.layout["text-font"] : void 0;
20020
21049
  return Array.isArray(font) && font.length === 1 && typeof font[0] === "string" && parseTileflowLineBackgroundFontStack(font[0]) !== void 0;
20021
21050
  });
20022
21051
  if (backed) {
@@ -20042,7 +21071,7 @@ function runCompilationPhase(phase, operation) {
20042
21071
  }
20043
21072
  }
20044
21073
  function withCompilationPhase(error, phase) {
20045
- const record3 = isRecord13(error) ? error : {};
21074
+ const record3 = isRecord14(error) ? error : {};
20046
21075
  if (typeof record3.phase === "string") return error;
20047
21076
  if (error instanceof Error && Object.isExtensible(error)) {
20048
21077
  Object.defineProperty(error, "phase", { configurable: true, enumerable: true, value: phase });
@@ -20073,6 +21102,35 @@ function compareCodeUnits7(left, right) {
20073
21102
  function semanticReportKey(...parts) {
20074
21103
  return parts.join("\0");
20075
21104
  }
21105
+ function skyMetadata(input, themeName, sky) {
21106
+ const family = Object.values(input.timeOfDay ?? {}).find(
21107
+ (moments) => Object.values(moments).includes(themeName)
21108
+ );
21109
+ const colours = (name) => {
21110
+ const theme = name === void 0 ? void 0 : input.themes[name];
21111
+ if (!theme) return void 0;
21112
+ const resolved = resolveTileflowSky(
21113
+ resolveThemeValues({ sky: input.sky }, theme, `map.${input.id}.themes.${name}`).sky
21114
+ );
21115
+ return resolved ? {
21116
+ color: resolved.color,
21117
+ horizonColor: resolved.horizonColor,
21118
+ horizonBlend: resolved.horizonBlend,
21119
+ glowColor: resolved.glowColor
21120
+ } : void 0;
21121
+ };
21122
+ const day = colours(family?.day);
21123
+ const night = colours(family?.night);
21124
+ const dawn = colours(family?.dawn);
21125
+ const dusk = colours(family?.dusk);
21126
+ const timesOfDay = day && night && (dawn ?? dusk) ? { day, night, dawn: dawn ?? dusk, dusk: dusk ?? dawn } : void 0;
21127
+ return {
21128
+ version: 1,
21129
+ starIntensity: sky.starIntensity,
21130
+ glowColor: sky.glowColor,
21131
+ ...timesOfDay ? { timesOfDay } : {}
21132
+ };
21133
+ }
20076
21134
 
20077
21135
  // src/maps/define.ts
20078
21136
  function defineMap(map) {
@@ -20115,6 +21173,7 @@ var tileflowMapMergeStrategies = {
20115
21173
  projection: "atomic",
20116
21174
  scenes: "leaf",
20117
21175
  shots: "leaf",
21176
+ sky: "atomic",
20118
21177
  sources: "keyed-resources",
20119
21178
  systemThemes: "atomic",
20120
21179
  terrain: "atomic",
@@ -20296,6 +21355,9 @@ function applyModuleOperation(resolved, moduleName, operation, mapId, path) {
20296
21355
  assertNoReset(patch, mapId, `${path}/patches/${index}`, true);
20297
21356
  next = applyModulePatch(next, patch, mapId, `${path}/patches/${index}`);
20298
21357
  }
21358
+ if (isPlainRecord4(next.renderStack) && Object.keys(next.renderStack).length === 0) {
21359
+ delete next.renderStack;
21360
+ }
20299
21361
  assertModuleOwner(next, moduleName, mapId, path);
20300
21362
  resolved[moduleName] = next;
20301
21363
  return;
@@ -20458,11 +21520,11 @@ function cloneDesign(value) {
20458
21520
  function describeMap(value) {
20459
21521
  return typeof value.id === "string" && value.id ? value.id : "<anonymous>";
20460
21522
  }
20461
- function isRecord14(value) {
21523
+ function isRecord15(value) {
20462
21524
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
20463
21525
  }
20464
21526
  function isPlainRecord4(value) {
20465
- if (!isRecord14(value)) return false;
21527
+ if (!isRecord15(value)) return false;
20466
21528
  const prototype = Object.getPrototypeOf(value);
20467
21529
  return prototype === Object.prototype || prototype === null;
20468
21530
  }
@@ -21232,7 +22294,7 @@ function semanticDesign(map) {
21232
22294
  }
21233
22295
  function compareValues(before, after, path, output) {
21234
22296
  if (Object.is(before, after)) return;
21235
- if (isRecord15(before) && isRecord15(after)) {
22297
+ if (isRecord16(before) && isRecord16(after)) {
21236
22298
  const keys = [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])].sort(
21237
22299
  compareCodeUnits9
21238
22300
  );
@@ -21274,7 +22336,7 @@ function toSemanticValue(value, path) {
21274
22336
  }
21275
22337
  throw new TypeError(`Tileflow semantic value at ${path || "/"} is not JSON-serializable.`);
21276
22338
  }
21277
- function isRecord15(value) {
22339
+ function isRecord16(value) {
21278
22340
  return value !== null && typeof value === "object" && !Array.isArray(value);
21279
22341
  }
21280
22342
  function isUnknownRecord(value) {
@@ -21287,7 +22349,7 @@ function deepEqual(left, right) {
21287
22349
  if (Array.isArray(left) || Array.isArray(right)) {
21288
22350
  return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => deepEqual(value, right[index]));
21289
22351
  }
21290
- if (!isRecord15(left) || !isRecord15(right)) return false;
22352
+ if (!isRecord16(left) || !isRecord16(right)) return false;
21291
22353
  const leftKeys = Object.keys(left).sort(compareCodeUnits9);
21292
22354
  const rightKeys = Object.keys(right).sort(compareCodeUnits9);
21293
22355
  return leftKeys.length === rightKeys.length && leftKeys.every((key, index) => key === rightKeys[index] && deepEqual(left[key], right[key]));
@@ -21594,6 +22656,394 @@ function isClassTier(value) {
21594
22656
  return tileflowBoundaryClassTiers.includes(value);
21595
22657
  }
21596
22658
 
22659
+ // src/nautical-light-rhythm.ts
22660
+ var tileflowLightRhythmConventions = Object.freeze({
22661
+ cardinalPeriods: Object.freeze({
22662
+ Q: Object.freeze({ 3: 10, 6: 15, 9: 15 }),
22663
+ "Q+LFl": 15,
22664
+ VQ: Object.freeze({ 3: 5, 6: 10, 9: 10 }),
22665
+ "VQ+LFl": 10
22666
+ }),
22667
+ fixedIntensity: 0.5,
22668
+ flash: 1,
22669
+ flashShare: 0.3,
22670
+ interruptedShare: 0.6,
22671
+ longFlash: 2,
22672
+ morseUnit: 0.5,
22673
+ occultingShare: 0.25,
22674
+ quickCycle: Object.freeze({ Q: 1, UQ: 0.25, VQ: 0.5 }),
22675
+ quickDuty: 0.3
22676
+ });
22677
+ var tileflowLightRhythmLimits = Object.freeze({
22678
+ maxPeriod: 600,
22679
+ maxPhases: 256,
22680
+ maxSequenceLength: 512,
22681
+ minPhase: 0.05
22682
+ });
22683
+ var conventions = tileflowLightRhythmConventions;
22684
+ var limits = tileflowLightRhythmLimits;
22685
+ var lit = 1;
22686
+ var eclipsed = 0;
22687
+ var morse = Object.freeze({
22688
+ A: ".-",
22689
+ B: "-...",
22690
+ C: "-.-.",
22691
+ D: "-..",
22692
+ E: ".",
22693
+ F: "..-.",
22694
+ G: "--.",
22695
+ H: "....",
22696
+ I: "..",
22697
+ J: ".---",
22698
+ K: "-.-",
22699
+ L: ".-..",
22700
+ M: "--",
22701
+ N: "-.",
22702
+ O: "---",
22703
+ P: ".--.",
22704
+ Q: "--.-",
22705
+ R: ".-.",
22706
+ S: "...",
22707
+ T: "-",
22708
+ U: "..-",
22709
+ V: "...-",
22710
+ W: ".--",
22711
+ X: "-..-",
22712
+ Y: "-.--",
22713
+ Z: "--..",
22714
+ 0: "-----",
22715
+ 1: ".----",
22716
+ 2: "..---",
22717
+ 3: "...--",
22718
+ 4: "....-",
22719
+ 5: ".....",
22720
+ 6: "-....",
22721
+ 7: "--...",
22722
+ 8: "---..",
22723
+ 9: "----."
22724
+ });
22725
+ function tileflowLightRhythm(light) {
22726
+ const character = typeof light.character === "string" ? light.character.trim() : "";
22727
+ const alternating = /^Al(?:\.|$)/u.test(character);
22728
+ const base = alternating ? character.slice(3) : character;
22729
+ const fixedPart = base === "FFl" || base === "F+LFl";
22730
+ const period = positiveNumber(light.period_s);
22731
+ const colours = colourCount(light.color);
22732
+ let phases = parseSequence(light.sequence, fixedPart);
22733
+ if (!phases) {
22734
+ phases = alternating && (base === "" || base === "F") ? alternatingFixed(colours, period) : derivePhases(base, groupText(light.group), period);
22735
+ }
22736
+ if (!phases) return void 0;
22737
+ if (alternating && colours > 1) phases = alternateColours(phases, colours);
22738
+ return finish(phases);
22739
+ }
22740
+ function tileflowLightStateAt(rhythm, seconds) {
22741
+ const periodMs = Math.round(rhythm.period * 1e3);
22742
+ const timeMs = Number.isFinite(seconds) ? Math.floor(seconds * 1e3) : 0;
22743
+ const offset = (timeMs % periodMs + periodMs) % periodMs;
22744
+ let end = 0;
22745
+ for (const phase of rhythm.phases) {
22746
+ end += Math.round(phase.duration * 1e3);
22747
+ if (offset < end) {
22748
+ return { colour: phase.colour, intensity: phase.intensity, remaining: (end - offset) / 1e3 };
22749
+ }
22750
+ }
22751
+ const first = rhythm.phases[0];
22752
+ return { colour: first.colour, intensity: first.intensity, remaining: first.duration };
22753
+ }
22754
+ function parseSequence(value, fixedPart) {
22755
+ if (typeof value !== "string") return void 0;
22756
+ const text = value.trim();
22757
+ if (!text || text.length > limits.maxSequenceLength) return void 0;
22758
+ const phases = [];
22759
+ for (const token2 of text.split("+")) {
22760
+ const match3 = /^\s*(\()?\s*(\d+(?:\.\d+)?|\.\d+)\s*(\))?\s*$/u.exec(token2);
22761
+ if (!match3 || Boolean(match3[1]) !== Boolean(match3[3])) return void 0;
22762
+ const duration = Number(match3[2]);
22763
+ if (!(duration > 0)) return void 0;
22764
+ const intensity = match3[1] ? fixedPart ? conventions.fixedIntensity : eclipsed : lit;
22765
+ phases.push({ colour: 0, duration, intensity });
22766
+ }
22767
+ if (!phases.some(({ intensity }) => intensity === lit)) return void 0;
22768
+ if (!phases.some(({ intensity }) => intensity !== lit)) return void 0;
22769
+ return phases;
22770
+ }
22771
+ function derivePhases(character, group, period) {
22772
+ switch (character) {
22773
+ case "Fl":
22774
+ return grouped(
22775
+ groupCounts(group),
22776
+ period,
22777
+ flashOf(groupCounts(group), period),
22778
+ lit,
22779
+ eclipsed
22780
+ );
22781
+ case "LFl":
22782
+ return grouped(
22783
+ groupCounts(group),
22784
+ period,
22785
+ longFlashOf(groupCounts(group), period),
22786
+ lit,
22787
+ eclipsed
22788
+ );
22789
+ case "FFl":
22790
+ return grouped(
22791
+ groupCounts(group),
22792
+ period,
22793
+ flashOf(groupCounts(group), period),
22794
+ lit,
22795
+ conventions.fixedIntensity
22796
+ );
22797
+ case "F+LFl":
22798
+ return grouped(
22799
+ groupCounts(group),
22800
+ period,
22801
+ longFlashOf(groupCounts(group), period),
22802
+ lit,
22803
+ conventions.fixedIntensity
22804
+ );
22805
+ case "Oc":
22806
+ case "Oc+Fl": {
22807
+ const counts = groupCounts(group);
22808
+ if (!counts || !period) return void 0;
22809
+ const eclipse = conventions.occultingShare * period / sum(counts);
22810
+ return grouped(counts, period, eclipse, eclipsed, lit);
22811
+ }
22812
+ case "Iso":
22813
+ return period ? [
22814
+ { colour: 0, duration: period / 2, intensity: lit },
22815
+ { colour: 0, duration: period / 2, intensity: eclipsed }
22816
+ ] : void 0;
22817
+ case "Q":
22818
+ case "VQ":
22819
+ case "UQ":
22820
+ return quick(character, group, period);
22821
+ case "IQ":
22822
+ case "IVQ":
22823
+ case "IUQ":
22824
+ return interruptedQuick(character.slice(1), period);
22825
+ case "Mo":
22826
+ return morseCode(group, period);
22827
+ case "Q+LFl":
22828
+ case "VQ+LFl":
22829
+ case "UQ+LFl":
22830
+ case "Fl+LFl":
22831
+ return withLongFlash(character, group, period);
22832
+ default:
22833
+ return void 0;
22834
+ }
22835
+ }
22836
+ function grouped(counts, period, pulse, pulseIntensity, gapIntensity) {
22837
+ if (!counts || !period || !pulse || !(pulse >= limits.minPhase)) return void 0;
22838
+ const pulses = sum(counts);
22839
+ const groups = counts.length;
22840
+ const free = period - pulses * pulse;
22841
+ const units = pulses - groups + 2 * (groups - 1) + 3;
22842
+ const gap = Math.min(2 * pulse, free / units);
22843
+ if (!(gap >= limits.minPhase)) return void 0;
22844
+ const phases = [];
22845
+ counts.forEach((count, index) => {
22846
+ for (let pulseIndex = 0; pulseIndex < count; pulseIndex += 1) {
22847
+ phases.push({ colour: 0, duration: pulse, intensity: pulseIntensity });
22848
+ if (pulseIndex < count - 1) {
22849
+ phases.push({ colour: 0, duration: gap, intensity: gapIntensity });
22850
+ }
22851
+ }
22852
+ if (index < groups - 1) phases.push({ colour: 0, duration: 2 * gap, intensity: gapIntensity });
22853
+ });
22854
+ const used = phases.reduce((total, phase) => total + phase.duration, 0);
22855
+ phases.push({ colour: 0, duration: period - used, intensity: gapIntensity });
22856
+ return phases;
22857
+ }
22858
+ function quick(character, group, period) {
22859
+ const cycle = conventions.quickCycle[character];
22860
+ const flash = cycle * conventions.quickDuty;
22861
+ if (group === "") {
22862
+ return [
22863
+ { colour: 0, duration: flash, intensity: lit },
22864
+ { colour: 0, duration: cycle - flash, intensity: eclipsed }
22865
+ ];
22866
+ }
22867
+ const counts = groupCounts(group);
22868
+ if (!counts) return void 0;
22869
+ const count = sum(counts);
22870
+ const cardinal = conventions.cardinalPeriods[character]?.[count];
22871
+ const cyclePeriod = period ?? cardinal;
22872
+ if (!cyclePeriod && count === 1) return quick(character, "", void 0);
22873
+ if (!cyclePeriod || cyclePeriod < (count + 1) * cycle) return void 0;
22874
+ return [
22875
+ ...quickFlashes(count, cycle, flash),
22876
+ { colour: 0, duration: cyclePeriod - count * cycle, intensity: eclipsed }
22877
+ ];
22878
+ }
22879
+ function quickFlashes(count, cycle, flash) {
22880
+ const phases = [];
22881
+ for (let index = 0; index < count; index += 1) {
22882
+ phases.push({ colour: 0, duration: flash, intensity: lit });
22883
+ phases.push({ colour: 0, duration: cycle - flash, intensity: eclipsed });
22884
+ }
22885
+ return phases;
22886
+ }
22887
+ function interruptedQuick(character, period) {
22888
+ const cycle = conventions.quickCycle[character];
22889
+ if (!period) return void 0;
22890
+ const count = Math.max(2, Math.floor(conventions.interruptedShare * period / cycle));
22891
+ if (period < (count + 1) * cycle) return void 0;
22892
+ return [
22893
+ ...quickFlashes(count, cycle, cycle * conventions.quickDuty),
22894
+ { colour: 0, duration: period - count * cycle, intensity: eclipsed }
22895
+ ];
22896
+ }
22897
+ function withLongFlash(character, group, period) {
22898
+ const first = character.slice(0, character.indexOf("+"));
22899
+ const counts = groupCounts(group);
22900
+ if (group !== "" && !counts) return void 0;
22901
+ const count = counts ? sum(counts) : first === "Fl" ? 1 : 6;
22902
+ const cyclePeriod = period ?? conventions.cardinalPeriods[character] ?? void 0;
22903
+ if (typeof cyclePeriod !== "number") return void 0;
22904
+ let phases;
22905
+ if (first === "Fl") {
22906
+ const flash = Math.min(conventions.flash, conventions.flashShare * cyclePeriod / (count + 2));
22907
+ phases = [];
22908
+ for (let index = 0; index < count; index += 1) {
22909
+ phases.push({ colour: 0, duration: flash, intensity: lit });
22910
+ phases.push({ colour: 0, duration: 2 * flash, intensity: eclipsed });
22911
+ }
22912
+ } else {
22913
+ const cycle = conventions.quickCycle[first];
22914
+ phases = quickFlashes(count, cycle, cycle * conventions.quickDuty);
22915
+ }
22916
+ phases.push({ colour: 0, duration: conventions.longFlash, intensity: lit });
22917
+ const used = phases.reduce((total, phase) => total + phase.duration, 0);
22918
+ const rest = cyclePeriod - used;
22919
+ if (!(rest >= conventions.longFlash / 2)) return void 0;
22920
+ phases.push({ colour: 0, duration: rest, intensity: eclipsed });
22921
+ return phases;
22922
+ }
22923
+ function morseCode(group, period) {
22924
+ const letters = [...group.toUpperCase().replaceAll(/\s+/gu, "")];
22925
+ if (letters.length === 0 || letters.length > 4) return void 0;
22926
+ const codes = letters.map((letter) => morse[letter]);
22927
+ if (codes.some((code) => code === void 0)) return void 0;
22928
+ const signalUnits = [];
22929
+ codes.forEach((code, letterIndex) => {
22930
+ [...code].forEach((signal, signalIndex) => {
22931
+ if (signalIndex > 0) signalUnits.push({ units: 1, intensity: eclipsed });
22932
+ signalUnits.push({ units: signal === "." ? 1 : 3, intensity: lit });
22933
+ });
22934
+ if (letterIndex < codes.length - 1) signalUnits.push({ units: 3, intensity: eclipsed });
22935
+ });
22936
+ const units = signalUnits.reduce((total, signal) => total + signal.units, 0);
22937
+ const unit2 = period ? Math.min(conventions.morseUnit, period / (units + 3)) : conventions.morseUnit;
22938
+ const cyclePeriod = period ?? (units + 7) * unit2;
22939
+ if (!(unit2 >= limits.minPhase)) return void 0;
22940
+ return [
22941
+ ...signalUnits.map(({ units: count, intensity }) => ({
22942
+ colour: 0,
22943
+ duration: count * unit2,
22944
+ intensity
22945
+ })),
22946
+ { colour: 0, duration: cyclePeriod - units * unit2, intensity: eclipsed }
22947
+ ];
22948
+ }
22949
+ function alternatingFixed(colours, period) {
22950
+ if (colours < 2 || !period) return void 0;
22951
+ return Array.from({ length: colours }, (_, colour) => ({
22952
+ colour,
22953
+ duration: period / colours,
22954
+ intensity: lit
22955
+ }));
22956
+ }
22957
+ function alternateColours(phases, colours) {
22958
+ const litPhases = phases.filter(({ intensity }) => intensity > eclipsed).length;
22959
+ if (litPhases === 0) return [...phases];
22960
+ const repeats = colours / gcd(litPhases, colours);
22961
+ const result = [];
22962
+ let next = 0;
22963
+ for (let repeat = 0; repeat < repeats; repeat += 1) {
22964
+ for (const phase of phases) {
22965
+ if (phase.intensity > eclipsed) {
22966
+ result.push({ ...phase, colour: next % colours });
22967
+ next += 1;
22968
+ } else {
22969
+ result.push({ ...phase });
22970
+ }
22971
+ }
22972
+ }
22973
+ return result;
22974
+ }
22975
+ function finish(input) {
22976
+ const phases = [];
22977
+ for (const phase of input) {
22978
+ const duration = Math.round(phase.duration * 1e3) / 1e3;
22979
+ if (!Number.isFinite(duration) || duration < limits.minPhase) return void 0;
22980
+ const previous = phases.at(-1);
22981
+ if (previous && previous.intensity === phase.intensity && previous.colour === phase.colour) {
22982
+ previous.duration = Math.round((previous.duration + duration) * 1e3) / 1e3;
22983
+ } else {
22984
+ phases.push({ colour: phase.colour, duration, intensity: phase.intensity });
22985
+ }
22986
+ }
22987
+ if (phases.length > 1) {
22988
+ const first = phases[0];
22989
+ const last = phases.at(-1);
22990
+ if (first.intensity === last.intensity && first.colour === last.colour) {
22991
+ first.duration = Math.round((first.duration + last.duration) * 1e3) / 1e3;
22992
+ phases.pop();
22993
+ }
22994
+ }
22995
+ if (phases.length < 2 || phases.length > limits.maxPhases) return void 0;
22996
+ const periodMs = phases.reduce((total, phase) => total + Math.round(phase.duration * 1e3), 0);
22997
+ if (periodMs > limits.maxPeriod * 1e3) return void 0;
22998
+ const frozen = Object.freeze(phases.map((phase) => Object.freeze({ ...phase })));
22999
+ return Object.freeze({
23000
+ key: frozen.map(
23001
+ ({ colour, duration, intensity }) => `${Math.round(duration * 1e3)}:${intensity}:${colour}`
23002
+ ).join(","),
23003
+ period: periodMs / 1e3,
23004
+ phases: frozen
23005
+ });
23006
+ }
23007
+ function flashOf(counts, period) {
23008
+ if (!counts || !period) return void 0;
23009
+ return Math.min(conventions.flash, conventions.flashShare * period / sum(counts));
23010
+ }
23011
+ function longFlashOf(counts, period) {
23012
+ if (!counts || !period) return void 0;
23013
+ const pulses = sum(counts);
23014
+ return period >= 2 * pulses * conventions.longFlash ? conventions.longFlash : conventions.flashShare * period / pulses;
23015
+ }
23016
+ function groupCounts(group) {
23017
+ if (group === "") return [1];
23018
+ const parts = group.split("+");
23019
+ const counts = [];
23020
+ for (const part of parts) {
23021
+ if (!/^\s*\d{1,2}\s*$/u.test(part)) return void 0;
23022
+ const count = Number(part);
23023
+ if (count < 1) return void 0;
23024
+ counts.push(count);
23025
+ }
23026
+ return sum(counts) <= 32 ? counts : void 0;
23027
+ }
23028
+ function groupText(value) {
23029
+ if (typeof value === "number" && Number.isInteger(value)) return String(value);
23030
+ return typeof value === "string" ? value.trim() : "";
23031
+ }
23032
+ function colourCount(value) {
23033
+ if (typeof value !== "string") return 0;
23034
+ return value.split(",").filter((name) => name.trim() !== "").length;
23035
+ }
23036
+ function positiveNumber(value) {
23037
+ const number = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
23038
+ return typeof number === "number" && Number.isFinite(number) && number > 0 ? number : void 0;
23039
+ }
23040
+ function sum(values) {
23041
+ return values.reduce((total, value) => total + value, 0);
23042
+ }
23043
+ function gcd(a, b) {
23044
+ return b === 0 ? a : gcd(b, a % b);
23045
+ }
23046
+
21597
23047
  // src/icon-package.ts
21598
23048
  import { z as z10 } from "zod";
21599
23049
  var tileflowIconPackageFileNames = [
@@ -22299,7 +23749,7 @@ async function requestSessionGrant(input) {
22299
23749
  else throw error;
22300
23750
  }
22301
23751
  throwIfSessionGrantAborted(signal);
22302
- if (response.status === 409 && isRecord16(body) && body.code === "COMMERCIAL_SESSION_RESTART_REQUIRED" && body.ok === false && body.retryWithNewSession === true && body.sessionId === input.sessionId) {
23752
+ if (response.status === 409 && isRecord17(body) && body.code === "COMMERCIAL_SESSION_RESTART_REQUIRED" && body.ok === false && body.retryWithNewSession === true && body.sessionId === input.sessionId) {
22303
23753
  throw new TileflowSessionRestartError();
22304
23754
  }
22305
23755
  if (!response.ok) {
@@ -22372,7 +23822,7 @@ function sessionGrantAbortReason(signal) {
22372
23822
  return error;
22373
23823
  }
22374
23824
  function parseSessionGrantResponse(value, expectedSessionId) {
22375
- if (!isRecord16(value) || value.ok !== true || value.sessionId !== expectedSessionId) return null;
23825
+ if (!isRecord17(value) || value.ok !== true || value.sessionId !== expectedSessionId) return null;
22376
23826
  if (value.usageMode !== void 0 && value.usageMode !== "session") return null;
22377
23827
  if (value.billingEnabled !== void 0 && value.billingEnabled !== false) return null;
22378
23828
  if (value.disposition !== void 0 && value.disposition !== "ordinary" && value.disposition !== "unbilled_fail_open") {
@@ -22391,11 +23841,11 @@ function parseSessionGrantResponse(value, expectedSessionId) {
22391
23841
  }
22392
23842
  return value;
22393
23843
  }
22394
- function isRecord16(value) {
23844
+ function isRecord17(value) {
22395
23845
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
22396
23846
  }
22397
23847
  function isPlainRuntimeRecord(value) {
22398
- if (!isRecord16(value)) return false;
23848
+ if (!isRecord17(value)) return false;
22399
23849
  const prototype = Object.getPrototypeOf(value);
22400
23850
  return prototype === Object.prototype || prototype === null;
22401
23851
  }
@@ -23860,7 +25310,7 @@ function readTileflowModelFileSummary(buffer) {
23860
25310
  height: round2(height, 100),
23861
25311
  footprint: {
23862
25312
  rings: footprint.length,
23863
- points: footprint.reduce((sum, ring) => sum + ring.length / 2, 0),
25313
+ points: footprint.reduce((sum2, ring) => sum2 + ring.length / 2, 0),
23864
25314
  area: round2(Math.abs(area), 10)
23865
25315
  }
23866
25316
  };
@@ -24056,7 +25506,7 @@ function checkModel(model) {
24056
25506
  throw new RangeError("A footprint ring has at least three points.");
24057
25507
  if (footprint.some((ring) => ring.some((value) => !Number.isFinite(value))))
24058
25508
  throw new RangeError("A footprint point has no place.");
24059
- if (footprint.reduce((sum, ring) => sum + ring.length / 2, 0) > tileflowModelLimits.footprintPoints)
25509
+ if (footprint.reduce((sum2, ring) => sum2 + ring.length / 2, 0) > tileflowModelLimits.footprintPoints)
24060
25510
  throw new RangeError(
24061
25511
  `A footprint has at most ${tileflowModelLimits.footprintPoints} points in all.`
24062
25512
  );
@@ -24322,7 +25772,7 @@ function validFootprint(footprint) {
24322
25772
  return points2 <= tileflowModelLimits.footprintPoints;
24323
25773
  }
24324
25774
  function readTileflowLandmarkRegion(json) {
24325
- if (!isRecord17(json)) return void 0;
25775
+ if (!isRecord18(json)) return void 0;
24326
25776
  const named = json.format === "tileflow-landmark-region-v2";
24327
25777
  if (!named && json.format !== "tileflow-landmark-region-v1") return void 0;
24328
25778
  const region = json.region;
@@ -24353,7 +25803,7 @@ function readLandmarks(listed, tx, ty) {
24353
25803
  if (!Array.isArray(listed) || listed.length === 0) return void 0;
24354
25804
  const out = [];
24355
25805
  for (const landmark of listed) {
24356
- if (!isRecord17(landmark)) return void 0;
25806
+ if (!isRecord18(landmark)) return void 0;
24357
25807
  const { id: id2, at, heading, footprint } = landmark;
24358
25808
  if (typeof id2 !== "string" || !landmarkId.test(id2)) return void 0;
24359
25809
  if (!Array.isArray(at) || at.length !== 2 || !at.every((value) => typeof value === "number"))
@@ -24377,10 +25827,10 @@ function readLandmarks(listed, tx, ty) {
24377
25827
  return out;
24378
25828
  }
24379
25829
  function readTileflowLandmarksTileJson(json, url) {
24380
- if (!isRecord17(json) || json.format !== "tileflow-landmarks-v2") return void 0;
25830
+ if (!isRecord18(json) || json.format !== "tileflow-landmarks-v2") return void 0;
24381
25831
  const tile = Array.isArray(json.tiles) ? json.tiles[0] : void 0;
24382
25832
  const regions2 = json.regions;
24383
- if (typeof tile !== "string" || !tile.includes("{sha256}") || json.minzoom !== tileflowLandmarkTileZoom || json.maxzoom !== tileflowLandmarkTileZoom || !isRecord17(regions2) || regions2.zoom !== tileflowLandmarkRegionZoom || typeof regions2.url !== "string" || !regions2.url.includes("{x}") || !regions2.url.includes("{y}") || !Array.isArray(regions2.available))
25833
+ if (typeof tile !== "string" || !tile.includes("{sha256}") || json.minzoom !== tileflowLandmarkTileZoom || json.maxzoom !== tileflowLandmarkTileZoom || !isRecord18(regions2) || regions2.zoom !== tileflowLandmarkRegionZoom || typeof regions2.url !== "string" || !regions2.url.includes("{x}") || !regions2.url.includes("{y}") || !Array.isArray(regions2.available))
24384
25834
  return void 0;
24385
25835
  const available = /* @__PURE__ */ new Set();
24386
25836
  for (const region of regions2.available) {
@@ -24398,7 +25848,7 @@ function readTileflowLandmarksTileJson(json, url) {
24398
25848
  regions: available
24399
25849
  };
24400
25850
  }
24401
- function isRecord17(value) {
25851
+ function isRecord18(value) {
24402
25852
  return typeof value === "object" && value !== null && !Array.isArray(value);
24403
25853
  }
24404
25854
 
@@ -24437,6 +25887,7 @@ export {
24437
25887
  createTileflowLineBackgroundFontStack,
24438
25888
  createTileflowSessionController,
24439
25889
  createTileflowSessionId,
25890
+ declareTileflowNauticalLights,
24440
25891
  decodeTileflowModels,
24441
25892
  decodeTileflowTreeModel,
24442
25893
  defaultTileflowManifestUrl,
@@ -24509,7 +25960,9 @@ export {
24509
25960
  readTileflowLandmarksTileJson,
24510
25961
  readTileflowModelFileIds,
24511
25962
  readTileflowModelFileSummary,
25963
+ readTileflowNauticalLights,
24512
25964
  readTileflowNotices,
25965
+ readTileflowSky,
24513
25966
  readTileflowStreetLights,
24514
25967
  readTileflowSunPreset,
24515
25968
  readTileflowTeamModelName,
@@ -24552,6 +26005,7 @@ export {
24552
26005
  sha256Hex,
24553
26006
  shouldLoadTileflowManifest,
24554
26007
  startTileflowSession,
26008
+ steadyTileflowNauticalLights,
24555
26009
  tileflowAuthoringManifest,
24556
26010
  tileflowAuthoringManifestSchemaVersion,
24557
26011
  tileflowBandPlacements,
@@ -24630,6 +26084,10 @@ export {
24630
26084
  tileflowLandmarkTileOf,
24631
26085
  tileflowLandmarkTileZoom,
24632
26086
  tileflowLayerBands,
26087
+ tileflowLightRhythm,
26088
+ tileflowLightRhythmConventions,
26089
+ tileflowLightRhythmLimits,
26090
+ tileflowLightStateAt,
24633
26091
  tileflowLineBackgroundDefaults,
24634
26092
  tileflowLineBackgroundHaloRatio,
24635
26093
  tileflowLineBackgroundLimits,
@@ -24637,6 +26095,7 @@ export {
24637
26095
  tileflowLocalDirectoryMessage,
24638
26096
  tileflowMapDefaultMaxDepth,
24639
26097
  tileflowMapIdSchema,
26098
+ tileflowMaxPitch,
24640
26099
  tileflowModelContentType,
24641
26100
  tileflowModelDefaultLightingRoles,
24642
26101
  tileflowModelFileName,
@@ -24647,8 +26106,14 @@ export {
24647
26106
  tileflowModelSurfaceLimits,
24648
26107
  tileflowModelTextureChannels,
24649
26108
  tileflowModelVertexBytes,
26109
+ tileflowNauticalFacilityIcons,
24650
26110
  tileflowNauticalImageRoles,
26111
+ tileflowNauticalLightEclipseOpacity,
26112
+ tileflowNauticalLightStates,
26113
+ tileflowNauticalLightsLimits,
26114
+ tileflowNauticalLightsMetadataKey,
24651
26115
  tileflowNauticalNotice,
26116
+ tileflowNauticalPromoteId,
24652
26117
  tileflowNauticalSourceId,
24653
26118
  tileflowNauticalV1Schema,
24654
26119
  tileflowNoticeLimits,
@@ -24676,6 +26141,8 @@ export {
24676
26141
  tileflowRuntimeManifestSchema,
24677
26142
  tileflowRuntimeManifestVersion,
24678
26143
  tileflowSemanticDiffSchemaVersion,
26144
+ tileflowSkyAtSun,
26145
+ tileflowSkyMetadataKey,
24679
26146
  tileflowStreetLightsMetadataKey,
24680
26147
  tileflowStyleFontFaceLimits,
24681
26148
  tileflowStyleFontFacesMetadataKey,