@tileflow/core 0.1.0-alpha.38 → 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.
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) {
@@ -881,6 +890,7 @@ var init_visual_semantics = __esm({
881
890
  "colorMix",
882
891
  "metallic",
883
892
  "roughness",
893
+ "horizonBlend",
884
894
  "hue",
885
895
  "saturation",
886
896
  "lightness",
@@ -908,6 +918,7 @@ var init_visual_semantics = __esm({
908
918
  "roundLimit",
909
919
  "size",
910
920
  "spacing",
921
+ "starIntensity",
911
922
  "strokeOpacity",
912
923
  "strokeWidth",
913
924
  "textFitPadding",
@@ -1078,7 +1089,7 @@ function auditTileflowThemeValues(value, path = "value", context = inferAuditCon
1078
1089
  return diagnostics.sort(compareAuditDiagnostics);
1079
1090
  }
1080
1091
  function auditTileflowMapThemeValues(map) {
1081
- const modules = isRecord5(map.modules) ? map.modules : void 0;
1092
+ const modules = isRecord6(map.modules) ? map.modules : void 0;
1082
1093
  const moduleDiagnostics = modules ? Object.keys(modules).sort(compareCodeUnits2).flatMap(
1083
1094
  (owner) => auditTileflowThemeValues(modules[owner], `modules.${owner}`, {
1084
1095
  owner,
@@ -1185,7 +1196,7 @@ function isThemePrimitiveForCategory(value, category) {
1185
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;
1186
1197
  }
1187
1198
  function isThemeValueCandidate(value) {
1188
- 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");
1189
1200
  }
1190
1201
  function visitAuditableValue(value, path, category, state, intentRequired = false) {
1191
1202
  if (isThemeStructuralPath(path)) category = void 0;
@@ -1201,7 +1212,7 @@ function visitAuditableValue(value, path, category, state, intentRequired = fals
1201
1212
  visitAuditableArray(value, path, category, state, false, intentRequired);
1202
1213
  return;
1203
1214
  }
1204
- if (!isRecord5(value)) return;
1215
+ if (!isRecord6(value)) return;
1205
1216
  if (value.kind === "expression" && Array.isArray(value.value)) {
1206
1217
  visitAuditableArray(value.value, `${path}.value`, category, state, true, intentRequired);
1207
1218
  return;
@@ -1590,7 +1601,7 @@ function assertThemeLighting(value, path, allowReset = false) {
1590
1601
  }
1591
1602
  }
1592
1603
  function assertThemeCategoryValue(value, expectedCategory, path) {
1593
- if (!isRecord5(value)) {
1604
+ if (!isRecord6(value)) {
1594
1605
  assertTokenPrimitive(expectedCategory, value, path);
1595
1606
  return;
1596
1607
  }
@@ -1955,11 +1966,11 @@ function cloneJson14(value) {
1955
1966
  if (serialized === void 0) throw new Error("Tileflow themes must be JSON-serializable.");
1956
1967
  return JSON.parse(serialized);
1957
1968
  }
1958
- function isRecord5(value) {
1969
+ function isRecord6(value) {
1959
1970
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
1960
1971
  }
1961
1972
  function isPlainRecord2(value) {
1962
- if (!isRecord5(value)) return false;
1973
+ if (!isRecord6(value)) return false;
1963
1974
  const prototype = Object.getPrototypeOf(value);
1964
1975
  return prototype === Object.prototype || prototype === null;
1965
1976
  }
@@ -2074,7 +2085,7 @@ var init_model = __esm({
2074
2085
  if (Array.isArray(value)) {
2075
2086
  return value.map((entry, index) => this.resolveTree(entry, `${path}[${index}]`, category));
2076
2087
  }
2077
- if (!isRecord5(value)) return value;
2088
+ if (!isRecord6(value)) return value;
2078
2089
  if (value.kind === "expression" && Array.isArray(value.value)) {
2079
2090
  return {
2080
2091
  ...value,
@@ -2333,13 +2344,14 @@ function createTileflowRuntimeViewSchema(operations2) {
2333
2344
  center: operations2.optional(
2334
2345
  operations2.tuplePair(operations2.number(-180, 180), operations2.number(-90, 90))
2335
2346
  ),
2336
- pitch: operations2.optional(operations2.number(0, 85)),
2347
+ pitch: operations2.optional(operations2.number(0, tileflowMaxPitch)),
2337
2348
  zoom: operations2.optional(operations2.number(0, 24))
2338
2349
  });
2339
2350
  }
2340
2351
  var init_runtime_view_schema = __esm({
2341
2352
  "src/runtime-view-schema.ts"() {
2342
2353
  "use strict";
2354
+ init_camera_limits();
2343
2355
  }
2344
2356
  });
2345
2357
 
@@ -2909,8 +2921,68 @@ var init_meshopt_decoder = __esm({
2909
2921
  }
2910
2922
  });
2911
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
+
2912
2983
  // src/capture-scene.ts
2913
2984
  init_portable_identity();
2985
+ init_camera_limits();
2914
2986
  import { z as z3 } from "zod";
2915
2987
  var tileflowCaptureSceneSchemaVersion = 1;
2916
2988
  var tileflowCaptureSceneLimits = Object.freeze({
@@ -2918,7 +2990,7 @@ var tileflowCaptureSceneLimits = Object.freeze({
2918
2990
  boundsPadding: { maximum: 1024, minimum: 0 },
2919
2991
  captureIdLength: tileflowPortableIdMaximumLength,
2920
2992
  maximumPhysicalPixels: 16777216,
2921
- pitch: { maximum: 85, minimum: 0 },
2993
+ pitch: { maximum: tileflowMaxPitch, minimum: 0 },
2922
2994
  selectorLength: 256,
2923
2995
  viewport: { maximum: 4096, minimum: 64 },
2924
2996
  zoom: { maximum: 24, minimum: 0 }
@@ -3402,14 +3474,7 @@ var defaults = {
3402
3474
  function resolveTileflowAtmosphere(value) {
3403
3475
  if (!value) return void 0;
3404
3476
  const options = { ...defaults, ...value === true ? {} : value };
3405
- const normalize2 = (input) => {
3406
- const color3 = typeof input === "string" ? Color.parse(input) : void 0;
3407
- if (!color3 || color3.a !== 1)
3408
- throw new Error("Atmosphere colors must resolve to opaque colors.");
3409
- return `#${[color3.r, color3.g, color3.b].map(
3410
- (channel) => Math.round(channel * 255).toString(16).padStart(2, "0")
3411
- ).join("")}`;
3412
- };
3477
+ const normalize2 = (input) => opaqueHex(input, "Atmosphere colors");
3413
3478
  return {
3414
3479
  ...options,
3415
3480
  skyColor: normalize2(options.skyColor),
@@ -3418,6 +3483,13 @@ function resolveTileflowAtmosphere(value) {
3418
3483
  spaceColor: normalize2(options.spaceColor)
3419
3484
  };
3420
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
+ }
3421
3493
  function compileTileflowSky(options) {
3422
3494
  return {
3423
3495
  "sky-color": options.skyColor,
@@ -3429,6 +3501,41 @@ function compileTileflowSky(options) {
3429
3501
  "atmosphere-blend": ["interpolate", ["linear"], ["zoom"], 0, 0.55, 3, 0.45, 5, 0.2, 7, 0]
3430
3502
  };
3431
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
+ }
3432
3539
 
3433
3540
  // src/models-3d-model.ts
3434
3541
  var tileflowModelParts = Object.freeze({
@@ -7313,12 +7420,46 @@ var tileflowNauticalImageRoles = Object.freeze({
7313
7420
  port: "nautical.buoy.port",
7314
7421
  starboard: "nautical.buoy.starboard"
7315
7422
  }),
7316
- 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" }),
7317
7440
  lighthouses: Object.freeze({ default: "nautical.lighthouse" }),
7318
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
+ }),
7319
7451
  ports: Object.freeze({ default: "nautical.port" }),
7320
7452
  wrecks: Object.freeze({ default: "nautical.wreck" })
7321
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
+ ]);
7322
7463
  var tileflowNauticalNotice = Object.freeze({
7323
7464
  id: "nautical-not-for-navigation",
7324
7465
  text: "Not for navigation"
@@ -7380,10 +7521,15 @@ var tileflowNauticalV1Schema = Object.freeze({
7380
7521
  country: "country",
7381
7522
  coverage: "coverage",
7382
7523
  depth: "depth",
7524
+ distance: "distance",
7525
+ distanceUnit: "distance_unit",
7383
7526
  edition: "edition",
7384
7527
  families: "families",
7385
7528
  group: "group",
7386
7529
  heightM: "height_m",
7530
+ horizontalClearance: "horizontal_clearance",
7531
+ impact: "impact",
7532
+ information: "information",
7387
7533
  label: "label",
7388
7534
  licence: "licence",
7389
7535
  lit: "lit",
@@ -7401,6 +7547,7 @@ var tileflowNauticalV1Schema = Object.freeze({
7401
7547
  scale: "scale",
7402
7548
  sectorEnd: "sector_end",
7403
7549
  sectorStart: "sector_start",
7550
+ sequence: "sequence",
7404
7551
  shape: "shape",
7405
7552
  size: "size",
7406
7553
  subclass: "subclass",
@@ -7408,6 +7555,10 @@ var tileflowNauticalV1Schema = Object.freeze({
7408
7555
  topmark: "topmark",
7409
7556
  unlocode: "unlocode",
7410
7557
  update: "update",
7558
+ verticalClearance: "vertical_clearance",
7559
+ verticalClearanceClosed: "vertical_clearance_closed",
7560
+ verticalClearanceOpen: "vertical_clearance_open",
7561
+ verticalClearanceSafe: "vertical_clearance_safe",
7411
7562
  waterLevel: "water_level"
7412
7563
  }),
7413
7564
  geometryTypes: Object.freeze({
@@ -7431,6 +7582,16 @@ var tileflowNauticalV1Schema = Object.freeze({
7431
7582
  wrecks: "wreck"
7432
7583
  }),
7433
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
+ }),
7434
7595
  schemaVersion: 1
7435
7596
  });
7436
7597
  var tileflowBathymetryNameLayerLimits = Object.freeze({ maxZoomRanges: 128 });
@@ -7518,8 +7679,11 @@ function resolveBathymetryRelief(style, apiBaseUrl) {
7518
7679
  style: cloneJson12(style)
7519
7680
  };
7520
7681
  }
7682
+ var tileflowNauticalPromoteId = Object.freeze({
7683
+ [tileflowNauticalV1Schema.layers.lights]: tileflowNauticalV1Schema.fields.provenance
7684
+ });
7521
7685
  function resolveNauticalSource(config, apiBaseUrl) {
7522
- return resolveMarineSource({
7686
+ const resolved = resolveMarineSource({
7523
7687
  config,
7524
7688
  defaultPath: "/tiles/nautical/tiles.json",
7525
7689
  defaultSourceId: tileflowNauticalSourceId,
@@ -7527,6 +7691,7 @@ function resolveNauticalSource(config, apiBaseUrl) {
7527
7691
  product: tileflowNauticalV1Schema.product,
7528
7692
  apiBaseUrl
7529
7693
  });
7694
+ return { ...resolved, source: { ...resolved.source, promoteId: { ...tileflowNauticalPromoteId } } };
7530
7695
  }
7531
7696
  function resolveMarineSource(options) {
7532
7697
  const sourceId = validateSourceId(options.config.sourceId ?? options.defaultSourceId);
@@ -7588,6 +7753,85 @@ function cloneJson12(value) {
7588
7753
  return JSON.parse(JSON.stringify(value));
7589
7754
  }
7590
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
+
7591
7835
  // src/modules/nautical/compiler.ts
7592
7836
  var { fields, layers } = tileflowNauticalV1Schema;
7593
7837
  var markColours = Object.freeze({
@@ -7630,6 +7874,24 @@ var operationalAreaClasses = [
7630
7874
  var routeClasses = ["route", "traffic-separation"];
7631
7875
  var cableClasses = ["cable", "cable-area", "pipeline", "pipeline-area"];
7632
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;
7633
7895
  function compileNautical(request, context) {
7634
7896
  const nauticalSource = context.marine?.nautical;
7635
7897
  if (!nauticalSource || request?.enabled === false) return [];
@@ -7660,10 +7922,30 @@ function compileNautical(request, context) {
7660
7922
  const belowLabel = { anchor: "top", offset: [0, 0.75], optional: true };
7661
7923
  const markColour = expression(colourMatch(firstColour(fields.color), markColours, ink));
7662
7924
  const markStroke = expression(colourMatch(secondColour(fields.color), markColours, ink));
7925
+ const animateLights = request?.animateLights !== false;
7663
7926
  const flareColour = expression(
7664
- 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
+ )
7665
7932
  );
7666
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");
7667
7949
  const defaults4 = {
7668
7950
  type: "nautical",
7669
7951
  enabled: true,
@@ -7754,6 +8036,60 @@ function compileNautical(request, context) {
7754
8036
  [14, 1.3]
7755
8037
  ])
7756
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
+ },
7757
8093
  hazardAreas: {
7758
8094
  fill: { color: accent, minZoom: 10, opacity: 0.08 },
7759
8095
  outline: { color: accent, dash: [1, 1], minZoom: 10, opacity: 0.7, width: 0.8 }
@@ -7863,6 +8199,7 @@ function compileNautical(request, context) {
7863
8199
  harbours: {
7864
8200
  minZoom: 11,
7865
8201
  placement: "point",
8202
+ ...rankPriority,
7866
8203
  ...images.harbours ? {
7867
8204
  icon: {
7868
8205
  color: ink,
@@ -7893,18 +8230,159 @@ function compileNautical(request, context) {
7893
8230
  ...rankPriority,
7894
8231
  ...images.aids ? {
7895
8232
  icon: {
7896
- ...iconPaint,
7897
- image: images.aids,
8233
+ ...iconPaint,
8234
+ image: images.aids,
8235
+ size: zoom.linear([
8236
+ [11, 0.8],
8237
+ [16, 1.2]
8238
+ ])
8239
+ }
8240
+ } : {
8241
+ marker: {
8242
+ color: halo,
8243
+ radius: zoom.linear([
8244
+ [11, 1.8],
8245
+ [16, 3]
8246
+ ]),
8247
+ strokeColor: ink,
8248
+ strokeWidth: 1
8249
+ }
8250
+ },
8251
+ text: { ...markText, ...sideLabel, field: nameFrom(15), padding: 3, size: 9 }
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,
7898
8376
  size: zoom.linear([
7899
- [11, 0.8],
7900
- [16, 1.2]
8377
+ [13, 0.8],
8378
+ [16, 1.15]
7901
8379
  ])
7902
8380
  }
7903
8381
  } : {
7904
8382
  marker: {
7905
8383
  color: halo,
7906
8384
  radius: zoom.linear([
7907
- [11, 1.8],
8385
+ [13, 1.8],
7908
8386
  [16, 3]
7909
8387
  ]),
7910
8388
  strokeColor: ink,
@@ -8095,6 +8573,32 @@ function compileNautical(request, context) {
8095
8573
  }
8096
8574
  },
8097
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
+ },
8098
8602
  coverage: {
8099
8603
  maxZoom: 9,
8100
8604
  minZoom: 4,
@@ -8144,6 +8648,7 @@ function compileNautical(request, context) {
8144
8648
  const source = nauticalSource.sourceId;
8145
8649
  const contributions = [];
8146
8650
  const add2 = (options) => appendNauticalLayer(contributions, { ...options, source });
8651
+ const ofClass = (value) => ["==", ["get", fields.class], value];
8147
8652
  add2({
8148
8653
  // Only coverage that speaks for every family; lists of lights alone would double the tint.
8149
8654
  filter: ["all", polygons, ["in", layers.aids, ["coalesce", ["get", fields.families], ""]]],
@@ -8154,13 +8659,30 @@ function compileNautical(request, context) {
8154
8659
  target: "nautical.coverage"
8155
8660
  });
8156
8661
  add2({
8157
- filter: polygons,
8662
+ filter: ["all", polygons, ["match", ["get", fields.class], [...ownAreaClasses], false, true]],
8158
8663
  id: "tileflow-nautical-navigation-areas",
8159
8664
  localOrder: 390,
8160
8665
  sourceLayer: layers.navigationAreas,
8161
8666
  style: config.navigationAreas,
8162
8667
  target: "nautical.navigationAreas"
8163
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
+ });
8164
8686
  add2({
8165
8687
  filter: polygons,
8166
8688
  id: "tileflow-nautical-reefs",
@@ -8187,11 +8709,20 @@ function compileNautical(request, context) {
8187
8709
  style: config.routes,
8188
8710
  target: "nautical.routes"
8189
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
+ });
8190
8721
  add2({
8191
8722
  filter: polygons,
8192
8723
  id: "tileflow-nautical-hazard-areas",
8193
8724
  localOrder: 410,
8194
- outlineFilter: ["match", ["geometry-type"], ["LineString", "Polygon"], true, false],
8725
+ outlineFilter: linesOrPolygons,
8195
8726
  sourceLayer: layers.hazards,
8196
8727
  style: config.hazardAreas,
8197
8728
  target: "nautical.hazardAreas"
@@ -8204,15 +8735,42 @@ function compileNautical(request, context) {
8204
8735
  style: config.wreckAreas,
8205
8736
  target: "nautical.wreckAreas"
8206
8737
  });
8207
- for (const [key, id2, sourceLayer, localOrder] of [
8208
- ["coverage", "tileflow-nautical-coverage-labels", layers.coverage, 560],
8209
- ["navigationAreas", "tileflow-nautical-navigation-area-labels", layers.navigationAreas, 562],
8210
- ["reefs", "tileflow-nautical-reef-labels", layers.reefs, 564],
8211
- ["hazards", "tileflow-nautical-hazard-area-labels", layers.hazards, 566],
8212
- ["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]]
8213
8771
  ]) {
8214
8772
  add2({
8215
- filter: ["all", polygons, ["has", key === "coverage" ? fields.provider : fields.name]],
8773
+ filter: ["all", polygons, filter],
8216
8774
  id: id2,
8217
8775
  localOrder,
8218
8776
  sourceLayer,
@@ -8220,6 +8778,37 @@ function compileNautical(request, context) {
8220
8778
  target: `nautical.labels.${key}`
8221
8779
  });
8222
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
+ });
8223
8812
  add2({
8224
8813
  filter: ["all", points, ["has", fields.depth]],
8225
8814
  id: "tileflow-nautical-soundings",
@@ -8238,6 +8827,16 @@ function compileNautical(request, context) {
8238
8827
  symbolFilter: ["==", ["get", fields.class], "rock"],
8239
8828
  target: "nautical.hazards"
8240
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
+ });
8241
8840
  add2({
8242
8841
  filter: points,
8243
8842
  id: "tileflow-nautical-wrecks",
@@ -8247,13 +8846,23 @@ function compileNautical(request, context) {
8247
8846
  target: "nautical.wrecks"
8248
8847
  });
8249
8848
  add2({
8250
- 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")],
8251
8851
  id: "tileflow-nautical-harbours",
8252
8852
  localOrder: 582,
8853
+ markerFilter: points,
8253
8854
  sourceLayer: layers.navigationAreas,
8254
8855
  style: config.harbours,
8255
8856
  target: "nautical.harbours"
8256
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
+ });
8257
8866
  add2({
8258
8867
  filter: [
8259
8868
  "all",
@@ -8261,7 +8870,7 @@ function compileNautical(request, context) {
8261
8870
  [
8262
8871
  "match",
8263
8872
  ["get", fields.class],
8264
- [...floatingAidClasses, ...fixedAidClasses, "lighthouse"],
8873
+ [...floatingAidClasses, ...fixedAidClasses, ...ownAidClasses, "lighthouse"],
8265
8874
  false,
8266
8875
  true
8267
8876
  ]
@@ -8272,6 +8881,14 @@ function compileNautical(request, context) {
8272
8881
  style: config.aids,
8273
8882
  target: "nautical.aids"
8274
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
+ });
8275
8892
  add2({
8276
8893
  filter: ["all", points, ["match", ["get", fields.class], [...fixedAidClasses], true, false]],
8277
8894
  id: "tileflow-nautical-beacons",
@@ -8282,6 +8899,7 @@ function compileNautical(request, context) {
8282
8899
  });
8283
8900
  add2({
8284
8901
  filter: points,
8902
+ flash: animateLights,
8285
8903
  id: "tileflow-nautical-lights",
8286
8904
  localOrder: 594,
8287
8905
  sourceLayer: layers.lights,
@@ -8297,7 +8915,7 @@ function compileNautical(request, context) {
8297
8915
  target: "nautical.buoys"
8298
8916
  });
8299
8917
  add2({
8300
- filter: ["all", points, ["==", ["get", fields.class], "lighthouse"]],
8918
+ filter: ["all", points, ofClass("lighthouse")],
8301
8919
  id: "tileflow-nautical-lighthouses",
8302
8920
  localOrder: 602,
8303
8921
  sourceLayer: layers.aids,
@@ -8305,7 +8923,7 @@ function compileNautical(request, context) {
8305
8923
  target: "nautical.lighthouses"
8306
8924
  });
8307
8925
  add2({
8308
- filter: ["all", points, ["==", ["get", fields.class], "port"]],
8926
+ filter: ["all", points, ofClass("port")],
8309
8927
  id: "tileflow-nautical-ports",
8310
8928
  localOrder: 606,
8311
8929
  sourceLayer: layers.navigationAreas,
@@ -8316,12 +8934,14 @@ function compileNautical(request, context) {
8316
8934
  }
8317
8935
  var points = ["==", ["geometry-type"], "Point"];
8318
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];
8319
8940
  function linesOf(classes) {
8320
- return [
8321
- "all",
8322
- ["==", ["geometry-type"], "LineString"],
8323
- ["match", ["get", fields.class], [...classes], true, false]
8324
- ];
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], ""]], ","]];
8325
8945
  }
8326
8946
  var fromFeatureMinZoom = [">=", ["zoom"], ["to-number", ["get", fields.minZoom], 0]];
8327
8947
  function appendNauticalLayer(contributions, options) {
@@ -8378,24 +8998,25 @@ function appendNauticalLayer(contributions, options) {
8378
8998
  if (point.visible === false) return;
8379
8999
  const isLabel = options.target.startsWith("nautical.labels.");
8380
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
+ );
8381
9015
  contributions.push(
8382
9016
  nauticalContribution(
8383
9017
  `${options.target}.marker`,
8384
9018
  options.localOrder,
8385
- applyCircleStyle(
8386
- base(
8387
- `${options.id}-marker`,
8388
- "circle",
8389
- options.markerFilter ? ["all", options.filter, options.markerFilter] : options.filter
8390
- ),
8391
- mergeTileflowDesign(
8392
- {
8393
- ...point.maxZoom === void 0 ? {} : { maxZoom: point.maxZoom },
8394
- ...point.minZoom === void 0 ? {} : { minZoom: point.minZoom }
8395
- },
8396
- point.marker
8397
- )
8398
- ),
9019
+ options.flash ? flashing(marker, ["circle-opacity", "circle-stroke-opacity"]) : marker,
8399
9020
  "symbols"
8400
9021
  )
8401
9022
  );
@@ -8403,21 +9024,79 @@ function appendNauticalLayer(contributions, options) {
8403
9024
  const hasText = Boolean(point.text && point.text.visible !== false);
8404
9025
  const hasIcon = Boolean(point.icon && point.icon.visible !== false);
8405
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
+ );
8406
9040
  contributions.push(
8407
9041
  nauticalContribution(
8408
9042
  isLabel ? options.target : `${options.target}.symbol`,
8409
9043
  options.localOrder + (isLabel ? 0 : 1),
8410
- applySymbolStyle(
8411
- base(
8412
- options.id,
8413
- "symbol",
8414
- options.symbolFilter ? ["all", options.filter, options.symbolFilter] : options.filter
8415
- ),
8416
- withoutMarker(point)
8417
- ),
9044
+ options.flash && hasIcon ? flashing(symbol, ["icon-opacity"]) : symbol,
8418
9045
  "symbols"
8419
9046
  )
8420
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]);
8421
9100
  }
8422
9101
  function withoutMarker(style) {
8423
9102
  const result = { ...style };
@@ -8454,8 +9133,96 @@ function themedImages(context) {
8454
9133
  ]
8455
9134
  ]);
8456
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
+ }
8457
9161
  return images;
8458
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
+ }
8459
9226
  function firstColour(field2) {
8460
9227
  return [
8461
9228
  "let",
@@ -9928,7 +10695,7 @@ function phaseVisibility(surfaces, phase, fields2) {
9928
10695
  function zoomLevels(values) {
9929
10696
  const levels = new Set(Array.from({ length: 10 }, (_, index) => index + 15));
9930
10697
  for (const value of values) {
9931
- if (!isZoomCurve(value)) continue;
10698
+ if (!isZoomCurve2(value)) continue;
9932
10699
  for (let index = 3; index < value.length - 1; index += 2) {
9933
10700
  const level = value[index];
9934
10701
  if (level < 15 || level > 24) continue;
@@ -9986,7 +10753,7 @@ function interpolationFactor2(interpolation, distance, span) {
9986
10753
  const logarithm = Math.log(interpolation[1]);
9987
10754
  return Math.expm1(logarithm * distance) / Math.expm1(logarithm * span);
9988
10755
  }
9989
- function isZoomCurve(value) {
10756
+ function isZoomCurve2(value) {
9990
10757
  return Array.isArray(value) && (value[0] === "interpolate" && isZoom(value[2]) || value[0] === "step" && isZoom(value[1]));
9991
10758
  }
9992
10759
  function isZoom(value) {
@@ -9995,7 +10762,7 @@ function isZoom(value) {
9995
10762
  function atZoom(value, level) {
9996
10763
  if (!Array.isArray(value) || value[0] === "literal") return value;
9997
10764
  if (isZoom(value)) return level;
9998
- if (!isZoomCurve(value)) return value.map((entry) => atZoom(entry, level));
10765
+ if (!isZoomCurve2(value)) return value.map((entry) => atZoom(entry, level));
9999
10766
  const stops = [];
10000
10767
  for (let index = 3; index < value.length - 1; index += 2) {
10001
10768
  stops.push({ level: value[index], value: value[index + 1] });
@@ -10021,7 +10788,7 @@ function foldConstants(value) {
10021
10788
  const [operator, ...operands] = folded;
10022
10789
  if (!operands.length || !operands.every((operand) => typeof operand === "number")) return folded;
10023
10790
  const numbers = operands;
10024
- const result = operator === "*" ? numbers.reduce((product, number) => product * number, 1) : operator === "+" ? numbers.reduce((sum, number) => sum + number, 0) : operator === "-" ? numbers.length === 1 ? -numbers[0] : numbers[0] - numbers[1] : operator === "/" && numbers.length === 2 ? numbers[0] / numbers[1] : operator === "max" ? Math.max(...numbers) : operator === "min" ? Math.min(...numbers) : void 0;
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;
10025
10792
  return result !== void 0 && Number.isFinite(result) ? result : folded;
10026
10793
  }
10027
10794
  function interpolated(lower, upper, at) {
@@ -11321,13 +12088,13 @@ function tileflowVegetationMetadata(vegetation2) {
11321
12088
  return { version: 1, ...vegetation2 };
11322
12089
  }
11323
12090
  function readTileflowVegetation3D(metadata) {
11324
- if (!isRecord6(metadata)) return void 0;
12091
+ if (!isRecord7(metadata)) return void 0;
11325
12092
  const value = metadata[tileflowVegetationMetadataKey];
11326
- if (!isRecord6(value) || value.version !== 1) return void 0;
12093
+ if (!isRecord7(value) || value.version !== 1) return void 0;
11327
12094
  const { source, layers: layers2, fields: fields2, colors, heightScale, crownScale, unknownForms, regions: regions2 } = value;
11328
- if (typeof source !== "string" || !isRecord6(layers2) || typeof layers2.tree !== "string")
12095
+ if (typeof source !== "string" || !isRecord7(layers2) || typeof layers2.tree !== "string")
11329
12096
  return void 0;
11330
- 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;
11331
12098
  const palette = (list) => Array.isArray(list) && list.length > 0 && list.every((entry) => typeof entry === "string") ? list : void 0;
11332
12099
  const broadleaf = palette(colors.broadleaf);
11333
12100
  const conifer = palette(colors.conifer);
@@ -11337,18 +12104,18 @@ function readTileflowVegetation3D(metadata) {
11337
12104
  const scale = (number) => typeof number === "number" && number > 0 && number <= 10 ? number : 1;
11338
12105
  const mix2 = tileflowTreeFormMixProblem(unknownForms) === void 0 ? unknownForms : void 0;
11339
12106
  const models = Object.fromEntries(
11340
- Object.entries(isRecord6(value.models) ? value.models : {}).filter(
11341
- ([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")
11342
12109
  ).slice(0, 16).map(([id2, model]) => {
11343
12110
  const { near, far } = model;
11344
12111
  return [id2, far === void 0 ? { near } : { near, far }];
11345
12112
  })
11346
12113
  );
11347
- const ownScales = isRecord6(value.forms) ? value.forms : {};
12114
+ const ownScales = isRecord7(value.forms) ? value.forms : {};
11348
12115
  const forms = Object.fromEntries(
11349
12116
  tileflowTreeForms.flatMap((form) => {
11350
12117
  const own = ownScales[form];
11351
- if (!isRecord6(own)) return [];
12118
+ if (!isRecord7(own)) return [];
11352
12119
  const drawn = Array.isArray(own.models) ? [...new Set(own.models)].filter((id2) => typeof id2 === "string" && id2 in models).slice(0, 4) : [];
11353
12120
  return [
11354
12121
  [
@@ -11362,10 +12129,10 @@ function readTileflowVegetation3D(metadata) {
11362
12129
  ];
11363
12130
  })
11364
12131
  );
11365
- const finish = tileflowTreeFinishes.find((name) => name === value.finish) ?? "faceted";
12132
+ const finish2 = tileflowTreeFinishes.find((name) => name === value.finish) ?? "faceted";
11366
12133
  const edges = typeof value.edges === "number" && value.edges >= 0 && value.edges <= 1 ? value.edges : 0;
11367
12134
  const zones = Array.isArray(regions2) ? regions2.filter(
11368
- (region) => isRecord6(region) && tileflowTreeRegionProblem(region) === void 0
12135
+ (region) => isRecord7(region) && tileflowTreeRegionProblem(region) === void 0
11369
12136
  ) : [];
11370
12137
  const named = Object.fromEntries(
11371
12138
  Object.entries(tileflowWorldTreeFields).map(([key, fallback]) => [
@@ -11386,7 +12153,7 @@ function readTileflowVegetation3D(metadata) {
11386
12153
  crownScale: scale(crownScale),
11387
12154
  forms,
11388
12155
  models,
11389
- finish,
12156
+ finish: finish2,
11390
12157
  edges,
11391
12158
  ...typeof value.edgeColor === "string" ? { edgeColor: value.edgeColor } : {},
11392
12159
  unknownForms: mix2 ?? tileflowDefaultUnknownTreeForms,
@@ -11395,7 +12162,7 @@ function readTileflowVegetation3D(metadata) {
11395
12162
  };
11396
12163
  }
11397
12164
  function tileflowTreeRegionProblem(region) {
11398
- 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";
11399
12166
  const bounds = region.bounds;
11400
12167
  if (!Array.isArray(bounds) || bounds.length !== 4 || !bounds.every((value) => typeof value === "number" && Number.isFinite(value)))
11401
12168
  return "bounds are [west, south, east, north] in degrees";
@@ -11406,7 +12173,7 @@ function tileflowTreeRegionProblem(region) {
11406
12173
  const mix2 = tileflowTreeFormMixProblem(region.unknownForms);
11407
12174
  return mix2 ? `unknownForms: ${mix2}` : void 0;
11408
12175
  }
11409
- function isRecord6(value) {
12176
+ function isRecord7(value) {
11410
12177
  return typeof value === "object" && value !== null && !Array.isArray(value);
11411
12178
  }
11412
12179
 
@@ -13105,9 +13872,9 @@ var tileflowNoticeLimits = Object.freeze({
13105
13872
  maxSources: 16,
13106
13873
  maxTextLength: 120
13107
13874
  });
13108
- 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;
13109
13876
  function readTileflowNotices(metadata) {
13110
- if (!isRecord7(metadata)) return [];
13877
+ if (!isRecord8(metadata)) return [];
13111
13878
  const value = metadata[tileflowNoticesMetadataKey];
13112
13879
  if (!Array.isArray(value)) return [];
13113
13880
  const notices = [];
@@ -13121,18 +13888,18 @@ function readTileflowNotices(metadata) {
13121
13888
  return Object.freeze(notices);
13122
13889
  }
13123
13890
  function readNotice(value) {
13124
- if (!isRecord7(value)) return void 0;
13891
+ if (!isRecord8(value)) return void 0;
13125
13892
  const { id: id2, sources, text } = value;
13126
- if (typeof id2 !== "string" || !identifierPattern.test(id2)) return void 0;
13893
+ if (typeof id2 !== "string" || !identifierPattern2.test(id2)) return void 0;
13127
13894
  if (typeof text !== "string" || text.trim() !== text || !text || text.length > tileflowNoticeLimits.maxTextLength || /\p{Cc}/u.test(text)) {
13128
13895
  return void 0;
13129
13896
  }
13130
- 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))) {
13131
13898
  return void 0;
13132
13899
  }
13133
13900
  return Object.freeze({ id: id2, sources: Object.freeze([...sources]), text });
13134
13901
  }
13135
- function isRecord7(value) {
13902
+ function isRecord8(value) {
13136
13903
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
13137
13904
  }
13138
13905
 
@@ -13809,21 +14576,21 @@ function bindSemanticValue(value, schema, sourceId) {
13809
14576
  }
13810
14577
  if (isDataSourceReference(value)) return sourceId;
13811
14578
  if (Array.isArray(value)) return value.map((item) => bindSemanticValue(item, schema, sourceId));
13812
- if (!isRecord8(value)) return value;
14579
+ if (!isRecord9(value)) return value;
13813
14580
  return Object.fromEntries(
13814
14581
  Object.entries(value).map(([key, item]) => [key, bindSemanticValue(item, schema, sourceId)])
13815
14582
  );
13816
14583
  }
13817
14584
  function isDataFieldReference2(value) {
13818
- 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";
13819
14586
  }
13820
14587
  function isDataLayerReference(value) {
13821
- 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";
13822
14589
  }
13823
14590
  function isDataSourceReference(value) {
13824
- return isRecord8(value) && value.kind === "tileflow-data-source" && value.name === "primary";
14591
+ return isRecord9(value) && value.kind === "tileflow-data-source" && value.name === "primary";
13825
14592
  }
13826
- function isRecord8(value) {
14593
+ function isRecord9(value) {
13827
14594
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
13828
14595
  }
13829
14596
  function compareCodeUnits5(left, right) {
@@ -13927,8 +14694,8 @@ function createTileflowLayerTemplateIR(layer, semanticKey) {
13927
14694
  renderer: type,
13928
14695
  ...filter === void 0 ? {} : { selector: cloneJson20(filter) },
13929
14696
  style: Object.freeze({
13930
- ...isRecord9(layout) ? { placement: Object.freeze(cloneJson20(layout)) } : {},
13931
- ...isRecord9(paint) ? { appearance: Object.freeze(cloneJson20(paint)) } : {}
14697
+ ...isRecord10(layout) ? { placement: Object.freeze(cloneJson20(layout)) } : {},
14698
+ ...isRecord10(paint) ? { appearance: Object.freeze(cloneJson20(paint)) } : {}
13932
14699
  })
13933
14700
  });
13934
14701
  }
@@ -14120,20 +14887,29 @@ var exactPhysicalLayerIds = Object.freeze({
14120
14887
  var nauticalPhysicalBases = Object.freeze({
14121
14888
  "nautical.aids": "tileflow-nautical-aids",
14122
14889
  "nautical.beacons": "tileflow-nautical-beacons",
14890
+ "nautical.berths": "tileflow-nautical-berths",
14891
+ "nautical.bridges": "tileflow-nautical-bridges",
14123
14892
  "nautical.buoys": "tileflow-nautical-buoys",
14124
14893
  "nautical.cables": "tileflow-nautical-cables",
14125
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",
14126
14898
  "nautical.harbours": "tileflow-nautical-harbours",
14127
14899
  "nautical.hazardAreas": "tileflow-nautical-hazard-areas",
14128
14900
  "nautical.hazards": "tileflow-nautical-hazards",
14901
+ "nautical.labels.bridges": "tileflow-nautical-bridge-labels",
14129
14902
  "nautical.labels.coverage": "tileflow-nautical-coverage-labels",
14130
14903
  "nautical.labels.hazards": "tileflow-nautical-hazard-area-labels",
14131
14904
  "nautical.labels.navigationAreas": "tileflow-nautical-navigation-area-labels",
14905
+ "nautical.labels.overheadCables": "tileflow-nautical-overhead-cable-labels",
14132
14906
  "nautical.labels.reefs": "tileflow-nautical-reef-labels",
14133
14907
  "nautical.labels.wrecks": "tileflow-nautical-wreck-area-labels",
14134
14908
  "nautical.lighthouses": "tileflow-nautical-lighthouses",
14135
14909
  "nautical.lights": "tileflow-nautical-lights",
14136
14910
  "nautical.navigationAreas": "tileflow-nautical-navigation-areas",
14911
+ "nautical.notices": "tileflow-nautical-notices",
14912
+ "nautical.overheadCables": "tileflow-nautical-overhead-cables",
14137
14913
  "nautical.ports": "tileflow-nautical-ports",
14138
14914
  "nautical.reefs": "tileflow-nautical-reefs",
14139
14915
  "nautical.routes": "tileflow-nautical-routes",
@@ -14144,7 +14920,9 @@ var nauticalPhysicalBases = Object.freeze({
14144
14920
  function nauticalPhysicalLayerId(semanticKey) {
14145
14921
  const direct = nauticalPhysicalBases[semanticKey];
14146
14922
  if (direct) return direct;
14147
- 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
+ );
14148
14926
  if (!match3) return void 0;
14149
14927
  const base = nauticalPhysicalBases[match3[1]];
14150
14928
  if (!base) return void 0;
@@ -14188,21 +14966,21 @@ function visitReferences(value, counts, visited = /* @__PURE__ */ new WeakSet())
14188
14966
  }
14189
14967
  }
14190
14968
  function isSemanticFieldReference(value) {
14191
- return isRecord9(value) && value.kind === "tileflow-data-field";
14969
+ return isRecord10(value) && value.kind === "tileflow-data-field";
14192
14970
  }
14193
14971
  function isSemanticLayerReference(value) {
14194
- return isRecord9(value) && value.kind === "tileflow-data-layer";
14972
+ return isRecord10(value) && value.kind === "tileflow-data-layer";
14195
14973
  }
14196
14974
  function isSemanticSourceReference(value) {
14197
- return isRecord9(value) && value.kind === "tileflow-data-source";
14975
+ return isRecord10(value) && value.kind === "tileflow-data-source";
14198
14976
  }
14199
14977
  function cloneJson20(value) {
14200
14978
  return JSON.parse(JSON.stringify(value));
14201
14979
  }
14202
14980
  function asRecord5(value) {
14203
- return isRecord9(value) ? value : {};
14981
+ return isRecord10(value) ? value : {};
14204
14982
  }
14205
- function isRecord9(value) {
14983
+ function isRecord10(value) {
14206
14984
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
14207
14985
  }
14208
14986
 
@@ -14266,7 +15044,7 @@ function validateTileflowRenderSelectorConstraints(input) {
14266
15044
  };
14267
15045
  while (pending.length > 0) {
14268
15046
  const current = pending.pop();
14269
- if (!isRecord10(current.selector) || typeof current.selector.kind !== "string") {
15047
+ if (!isRecord11(current.selector) || typeof current.selector.kind !== "string") {
14270
15048
  addIssue(
14271
15049
  "invalid-selector",
14272
15050
  "a render selector must be a semantic selector node",
@@ -14327,7 +15105,7 @@ function validateTileflowRenderSelectorConstraints(input) {
14327
15105
  }
14328
15106
  for (const [index, branch] of current.selector.branches.entries()) {
14329
15107
  const branchPath = [...branchesPath, index];
14330
- if (!isRecord10(branch)) {
15108
+ if (!isRecord11(branch)) {
14331
15109
  addIssue("invalid-selector", "match selector branch must be an object", branchPath);
14332
15110
  continue;
14333
15111
  }
@@ -14358,14 +15136,14 @@ function validateTileflowRenderSelectorConstraints(input) {
14358
15136
  for (let index = current.selector.stops.length - 1; index >= 0; index -= 1) {
14359
15137
  const stop = current.selector.stops[index];
14360
15138
  const stopPath = [...stopsPath, index];
14361
- if (!isRecord10(stop)) {
15139
+ if (!isRecord11(stop)) {
14362
15140
  addIssue("invalid-selector", "step selector stop must be an object", stopPath);
14363
15141
  continue;
14364
15142
  }
14365
15143
  pushChild(stop.selector, current.depth + 1, [...stopPath, "selector"]);
14366
15144
  }
14367
15145
  for (const [index, stop] of current.selector.stops.entries()) {
14368
- if (!isRecord10(stop)) continue;
15146
+ if (!isRecord11(stop)) continue;
14369
15147
  const zoom3 = stop.zoom;
14370
15148
  const zoomPath = [...stopsPath, index, "zoom"];
14371
15149
  if (typeof zoom3 !== "number" || !Number.isFinite(zoom3) || zoom3 < 0 || zoom3 > 24) {
@@ -14750,7 +15528,7 @@ function applyRendererStyle(layer, renderer, style) {
14750
15528
  };
14751
15529
  }
14752
15530
  function lowerSelector(selector, data) {
14753
- if (!isRecord10(selector) || typeof selector.kind !== "string") {
15531
+ if (!isRecord11(selector) || typeof selector.kind !== "string") {
14754
15532
  throw renderStackError(
14755
15533
  "invalid-selector",
14756
15534
  "A render selector must be a semantic selector node."
@@ -14953,7 +15731,7 @@ function requireRenderer(value) {
14953
15731
  function requirePlacement(value) {
14954
15732
  if (typeof value === "string") {
14955
15733
  if (tileflowBandPlacements.includes(value)) return;
14956
- } else if (isRecord10(value)) {
15734
+ } else if (isRecord11(value)) {
14957
15735
  const keys = Object.keys(value);
14958
15736
  if (keys.length === 1 && (keys[0] === "above" || keys[0] === "below")) {
14959
15737
  requirePortableTarget(value[keys[0]], "render placement target");
@@ -14980,7 +15758,7 @@ function validateRefinementShape(operation) {
14980
15758
  validateStyle(operation.renderer, operation.style);
14981
15759
  }
14982
15760
  function validateStyle(renderer, style) {
14983
- if (!isRecord10(style)) {
15761
+ if (!isRecord11(style)) {
14984
15762
  throw renderStackError(
14985
15763
  "invalid-style",
14986
15764
  `A ${renderer} render operation requires a style object.`
@@ -15082,7 +15860,7 @@ function normalizeRequirements(requirements) {
15082
15860
  return [...requirements];
15083
15861
  }
15084
15862
  function requireRenderStackEntries(stack) {
15085
- if (!isRecord10(stack)) {
15863
+ if (!isRecord11(stack)) {
15086
15864
  throw renderStackError("invalid-stack", "A render stack must be a named operation record.");
15087
15865
  }
15088
15866
  const entries = Object.entries(stack);
@@ -15232,9 +16010,9 @@ function cloneJson21(value) {
15232
16010
  return JSON.parse(JSON.stringify(value));
15233
16011
  }
15234
16012
  function asRecord6(value) {
15235
- return isRecord10(value) ? value : {};
16013
+ return isRecord11(value) ? value : {};
15236
16014
  }
15237
- function isRecord10(value) {
16015
+ function isRecord11(value) {
15238
16016
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
15239
16017
  }
15240
16018
 
@@ -15450,6 +16228,7 @@ function isLoopbackHostname(hostname) {
15450
16228
  // src/resolved-map-schema.ts
15451
16229
  init_time_of_day();
15452
16230
  init_time_of_day_schema();
16231
+ init_camera_limits();
15453
16232
  var TileflowResolvedMapValidationError = class extends Error {
15454
16233
  code = "CONFIG_INVALID";
15455
16234
  messages;
@@ -16607,24 +17386,34 @@ var poiModuleSchema = z7.object({
16607
17386
  var nauticalModuleSchema = z7.object({
16608
17387
  type: z7.literal("nautical"),
16609
17388
  aids: markerSymbolStyleSchema.optional(),
17389
+ animateLights: z7.boolean().optional(),
16610
17390
  beacons: markerSymbolStyleSchema.optional(),
17391
+ berths: areaStyleSchema.optional(),
17392
+ bridges: areaStyleSchema.optional(),
16611
17393
  buoys: markerSymbolStyleSchema.optional(),
16612
17394
  cables: lineStyleSchema.optional(),
16613
17395
  coverage: areaStyleSchema.optional(),
17396
+ distanceMarks: markerSymbolStyleSchema.optional(),
16614
17397
  enabled: z7.boolean().optional(),
17398
+ facilities: markerSymbolStyleSchema.optional(),
17399
+ harbourAreas: areaStyleSchema.optional(),
16615
17400
  harbours: markerSymbolStyleSchema.optional(),
16616
17401
  hazardAreas: areaStyleSchema.optional(),
16617
17402
  hazards: markerSymbolStyleSchema.optional(),
16618
17403
  labels: z7.object({
17404
+ bridges: symbolStyleSchema.optional(),
16619
17405
  coverage: symbolStyleSchema.optional(),
16620
17406
  hazards: symbolStyleSchema.optional(),
16621
17407
  navigationAreas: symbolStyleSchema.optional(),
17408
+ overheadCables: symbolStyleSchema.optional(),
16622
17409
  reefs: symbolStyleSchema.optional(),
16623
17410
  wrecks: symbolStyleSchema.optional()
16624
17411
  }).strict().optional(),
16625
17412
  lighthouses: markerSymbolStyleSchema.optional(),
16626
17413
  lights: markerSymbolStyleSchema.optional(),
16627
17414
  navigationAreas: areaStyleSchema.optional(),
17415
+ notices: markerSymbolStyleSchema.optional(),
17416
+ overheadCables: lineStyleSchema.optional(),
16628
17417
  ports: markerSymbolStyleSchema.optional(),
16629
17418
  reefs: areaStyleSchema.optional(),
16630
17419
  renderStack: renderStackSchema.optional(),
@@ -17157,7 +17946,7 @@ var terrainContoursSchema = z7.object({
17157
17946
  demUrl: z7.string().min(1).max(2048).refine(
17158
17947
  isSafeTileflowDemUrlTemplate,
17159
17948
  "Expected a safe HTTP(S) DEM URL template containing {z}, {x}, and {y}"
17160
- ),
17949
+ ).optional(),
17161
17950
  index: terrainContourLineSchema.optional(),
17162
17951
  labels: terrainContourLabelSchema.optional(),
17163
17952
  maxZoom: z7.number().int().min(0).max(24).optional(),
@@ -17281,10 +18070,25 @@ var atmosphereSchema = z7.union([
17281
18070
  starParallax: z7.number().finite().min(0).max(1).optional()
17282
18071
  }).strict()
17283
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
+ ]);
17284
18088
  var viewSchema = z7.object({
17285
18089
  bearing: z7.number().finite().min(-180).max(180).optional(),
17286
18090
  center: z7.tuple([z7.number().finite().min(-180).max(180), z7.number().finite().min(-90).max(90)]).optional(),
17287
- pitch: z7.number().finite().min(0).max(85).optional(),
18091
+ pitch: z7.number().finite().min(0).max(tileflowMaxPitch).optional(),
17288
18092
  zoom: zoomNumberSchema.optional()
17289
18093
  }).strict();
17290
18094
  var themesSchema = z7.record(tileflowThemeNameSchema, tileflowThemeSchema).superRefine((themes, context) => {
@@ -17327,6 +18131,7 @@ var resolvedTileflowMapSchema = z7.object({
17327
18131
  name: z7.string().trim().min(1),
17328
18132
  overlays: overlaysSchema.optional(),
17329
18133
  projection: z7.enum(["globe", "mercator"]).optional(),
18134
+ sky: skySchema.optional(),
17330
18135
  sources: hostedSourcesSchema.optional(),
17331
18136
  systemThemes: systemThemesSchema.optional(),
17332
18137
  terrain: terrainSchema.optional(),
@@ -17640,7 +18445,7 @@ function resolveTerrain(terrain, apiBaseUrl) {
17640
18445
  const mode = terrainConfig.mode ?? "hillshade";
17641
18446
  const encoding = terrainConfig.encoding ?? "terrarium";
17642
18447
  const raster = mode === "none" ? void 0 : resolveRaster(terrainConfig, apiBaseUrl, encoding);
17643
- 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;
17644
18449
  if (!raster && !contours) return void 0;
17645
18450
  return {
17646
18451
  ...contours ? { contours } : {},
@@ -17772,7 +18577,7 @@ function resolveRaster(terrain, apiBaseUrl, encoding) {
17772
18577
  }
17773
18578
  };
17774
18579
  }
17775
- function resolveContours(contours, encoding, attribution) {
18580
+ function resolveContours(contours, encoding, attribution, apiBaseUrl) {
17776
18581
  const thresholdZooms = Object.keys(contours.thresholds).map(Number);
17777
18582
  const minimumZoom2 = contours.minZoom ?? Math.min(...thresholdZooms);
17778
18583
  const maximumZoom2 = contours.maxZoom ?? Math.max(...thresholdZooms);
@@ -17780,7 +18585,7 @@ function resolveContours(contours, encoding, attribution) {
17780
18585
  const tiles = [
17781
18586
  createTileflowContourProtocolUrl({
17782
18587
  demMaxzoom: contours.demMaxZoom,
17783
- demUrl: contours.demUrl,
18588
+ demUrl: contours.demUrl ?? `${apiBaseUrl}/tiles/terrain/{z}/{x}/{y}.webp`,
17784
18589
  encoding,
17785
18590
  maxzoom: maximumZoom2,
17786
18591
  multiplier: contours.multiplier ?? 1,
@@ -18438,11 +19243,11 @@ function isTileflowPoiCategory(value) {
18438
19243
  return typeof value === "string" && tileflowPoiCategories.includes(value);
18439
19244
  }
18440
19245
  function semanticTarget(layer) {
18441
- if (!isRecord11(layer.metadata)) return void 0;
19246
+ if (!isRecord12(layer.metadata)) return void 0;
18442
19247
  const value = layer.metadata[tileflowCompilerMetadataKeys.target];
18443
19248
  return typeof value === "string" ? value : void 0;
18444
19249
  }
18445
- function isRecord11(value) {
19250
+ function isRecord12(value) {
18446
19251
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
18447
19252
  }
18448
19253
  function isSafeArtifactName(value) {
@@ -18886,8 +19691,8 @@ function consolidateFillFamily(layers2, group, mergedId) {
18886
19691
  }
18887
19692
  const replacements = /* @__PURE__ */ new Map();
18888
19693
  let suffix = 0;
18889
- for (const grouped of groups.values()) {
18890
- for (const group2 of contiguousRuns(grouped)) {
19694
+ for (const grouped2 of groups.values()) {
19695
+ for (const group2 of contiguousRuns(grouped2)) {
18891
19696
  if (group2.length < 2) continue;
18892
19697
  const first = group2[0].layer;
18893
19698
  if (!group2.every(({ layer }) => sameTopLevelProperties(layer, first))) continue;
@@ -19342,7 +20147,7 @@ var colourSources = [
19342
20147
  ["tileflow-buildings-fill", "fill-color"]
19343
20148
  ];
19344
20149
  function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, tileflowLandmarks) {
19345
- const config = isRecord12(module) ? module : {};
20150
+ const config = isRecord13(module) ? module : {};
19346
20151
  if (config.enabled === false) return void 0;
19347
20152
  const style = config.threeDimensional ?? {};
19348
20153
  const defaults4 = tileflowDefaultBuildings3DDesign;
@@ -19375,7 +20180,7 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
19375
20180
  edgeRadius: number(style.edgeRadius, defaults4.edgeRadius),
19376
20181
  ...typeof edgeColor === "string" ? { edgeColor } : {},
19377
20182
  nightLight: number(style.nightLight, 0),
19378
- ...isRecord12(style.floodLight) ? {
20183
+ ...isRecord13(style.floodLight) ? {
19379
20184
  floodLight: readBuildingFloodLight({
19380
20185
  ...style.floodLight,
19381
20186
  intensity: number(style.floodLight.intensity, 0),
@@ -19383,10 +20188,10 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
19383
20188
  color: style.floodLight.color === void 0 ? void 0 : toMapLibreStyleValue(style.floodLight.color)
19384
20189
  })
19385
20190
  } : {},
19386
- ...isRecord12(style.modelLighting) ? {
20191
+ ...isRecord13(style.modelLighting) ? {
19387
20192
  modelLighting: readModelRoleLighting(
19388
20193
  Object.fromEntries(
19389
- Object.entries(style.modelLighting).filter(([, entry]) => isRecord12(entry)).map(([role, entry]) => {
20194
+ Object.entries(style.modelLighting).filter(([, entry]) => isRecord13(entry)).map(([role, entry]) => {
19390
20195
  const own = entry;
19391
20196
  return [
19392
20197
  role,
@@ -19397,7 +20202,7 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
19397
20202
  ["emissiveVariation", "colorMix", "metallic", "roughness"].filter((key) => own[key] !== void 0).map((key) => [key, number(own[key], 0)])
19398
20203
  ),
19399
20204
  ...Array.isArray(own.emissiveHeight) ? { emissiveHeight: own.emissiveHeight.map((v) => number(v, 0)) } : {},
19400
- ...isRecord12(own.colorVariation) ? {
20205
+ ...isRecord13(own.colorVariation) ? {
19401
20206
  colorVariation: {
19402
20207
  ...own.colorVariation,
19403
20208
  ...Object.fromEntries(
@@ -19430,7 +20235,7 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
19430
20235
  }
19431
20236
  function landmarksOf2(value, tileflowLandmarks) {
19432
20237
  if (value === false) return {};
19433
- 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 } };
19434
20239
  return tileflowLandmarks ? { landmarks: { url: tileflowLandmarks } } : {};
19435
20240
  }
19436
20241
  function placedModels(value, served) {
@@ -19457,7 +20262,7 @@ function placedModels(value, served) {
19457
20262
  };
19458
20263
  }
19459
20264
  function modelColours(value) {
19460
- if (!isRecord12(value)) return {};
20265
+ if (!isRecord13(value)) return {};
19461
20266
  const colours = {};
19462
20267
  for (const [part, colour] of Object.entries(value)) {
19463
20268
  const resolved = colour === void 0 ? void 0 : toMapLibreStyleValue(colour);
@@ -19466,7 +20271,7 @@ function modelColours(value) {
19466
20271
  return Object.keys(colours).length > 0 ? { modelColors: colours } : {};
19467
20272
  }
19468
20273
  function tileflowStreetLightsDesignOf(module, context) {
19469
- const config = isRecord12(module) ? module : {};
20274
+ const config = isRecord13(module) ? module : {};
19470
20275
  const layers2 = context.data.schema.layers;
19471
20276
  const style = config.streetLights ?? {};
19472
20277
  if (config.enabled === false || style.enabled !== true || !layers2.streetFurniture)
@@ -19486,7 +20291,7 @@ function tileflowStreetLightsDesignOf(module, context) {
19486
20291
  intensity: typeof intensity === "number" ? intensity : 1
19487
20292
  };
19488
20293
  }
19489
- function isRecord12(value) {
20294
+ function isRecord13(value) {
19490
20295
  return typeof value === "object" && value !== null && !Array.isArray(value);
19491
20296
  }
19492
20297
 
@@ -19677,7 +20482,7 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
19677
20482
  )
19678
20483
  );
19679
20484
  const renderStackModules = Object.values(modules).filter(
19680
- (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)
19681
20486
  );
19682
20487
  const renderOperations = runCompilationPhase(
19683
20488
  "render-stack",
@@ -19923,9 +20728,15 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
19923
20728
  layer.layout = { ...layer.layout, visibility: "none" };
19924
20729
  }
19925
20730
  const atmosphere = resolveTileflowAtmosphere(config.atmosphere);
20731
+ const sky = resolveTileflowSky(config.sky);
19926
20732
  const notices = compiledNotices(layers2, marine);
20733
+ const nauticalLights = declareTileflowNauticalLights(
20734
+ layers2,
20735
+ marine?.nautical?.sourceId,
20736
+ tileflowNauticalV1Schema.layers.lights
20737
+ );
19927
20738
  const style = {
19928
- ...atmosphere ? { sky: compileTileflowSky(atmosphere) } : {},
20739
+ ...sky ? { sky: compileTileflowStreetSky(sky) } : atmosphere ? { sky: compileTileflowSky(atmosphere) } : {},
19929
20740
  version: 8,
19930
20741
  name: config.name ?? "Streets",
19931
20742
  ...glyphs ? { glyphs } : {},
@@ -19944,6 +20755,7 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
19944
20755
  ...mapMetadata.lineage && mapMetadata.lineage.length > 1 ? { "tileflow:extends": mapMetadata.lineage.slice(1) } : {}
19945
20756
  } : {},
19946
20757
  ...atmosphere ? { [tileflowAtmosphereMetadataKey]: { version: 1, ...atmosphere } } : {},
20758
+ ...sky ? { [tileflowSkyMetadataKey]: skyMetadata(input, selected.name, sky) } : {},
19947
20759
  "tileflow:theme": selected.name,
19948
20760
  "tileflow:colorScheme": resolvedTheme.colorScheme,
19949
20761
  [tileflowRouteMetadataKey]: tileflowRouteMetadata(resolveTileflowRouteInks(resolvedTheme)),
@@ -19972,9 +20784,10 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
19972
20784
  "tileflow:sourceRequirements": sourceRequirements,
19973
20785
  ...interactionManifest ? { [tileflowInteractionManifestMetadataKey]: interactionManifest } : {},
19974
20786
  [tileflowOverlayPlacementManifestMetadataKey]: overlayPlacementManifest,
19975
- "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(),
19976
20788
  ...config.view ? { "tileflow:view": config.view } : {},
19977
- ...notices.length > 0 ? { [tileflowNoticesMetadataKey]: notices } : {}
20789
+ ...notices.length > 0 ? { [tileflowNoticesMetadataKey]: notices } : {},
20790
+ ...nauticalLights ? { [tileflowNauticalLightsMetadataKey]: nauticalLights } : {}
19978
20791
  }
19979
20792
  };
19980
20793
  const report = {
@@ -20034,7 +20847,7 @@ function finalizeTileflowLayers(input) {
20034
20847
  if (typeof minimum2 === "number" && typeof maximum2 === "number" && minimum2 > maximum2) {
20035
20848
  throw new Error(`Compiled Tileflow layer ${id2} requires minzoom <= maxzoom.`);
20036
20849
  }
20037
- const metadata = isRecord13(layer.metadata) ? { ...layer.metadata } : void 0;
20850
+ const metadata = isRecord14(layer.metadata) ? { ...layer.metadata } : void 0;
20038
20851
  if (metadata) {
20039
20852
  const owner = metadata[tileflowCompilerMetadataKeys.owner];
20040
20853
  delete metadata[tileflowCompilerProvenanceMetadataKey];
@@ -20068,7 +20881,7 @@ function assertMapLibreStyle(style, mapId) {
20068
20881
  }
20069
20882
  );
20070
20883
  }
20071
- function isRecord13(value) {
20884
+ function isRecord14(value) {
20072
20885
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
20073
20886
  }
20074
20887
  function compiledNotices(layers2, marine) {
@@ -20113,8 +20926,8 @@ function assertPreparedIconReferences(style, config, prepared) {
20113
20926
  function collectStyleImageReferences(style) {
20114
20927
  const result = /* @__PURE__ */ new Set();
20115
20928
  for (const layer of style.layers) {
20116
- const layout = isRecord13(layer.layout) ? layer.layout : {};
20117
- const paint = isRecord13(layer.paint) ? layer.paint : {};
20929
+ const layout = isRecord14(layer.layout) ? layer.layout : {};
20930
+ const paint = isRecord14(layer.paint) ? layer.paint : {};
20118
20931
  const layerId = typeof layer.id === "string" ? layer.id : "<unknown>";
20119
20932
  for (const [property, value] of [
20120
20933
  ["icon-image", layout["icon-image"]],
@@ -20203,7 +21016,7 @@ function assertGlyphFontStacks(style, config) {
20203
21016
  const declared = new Set(config.glyphs.fontStacks);
20204
21017
  const missing = /* @__PURE__ */ new Set();
20205
21018
  for (const layer of style.layers) {
20206
- const layout = isRecord13(layer.layout) ? layer.layout : void 0;
21019
+ const layout = isRecord14(layer.layout) ? layer.layout : void 0;
20207
21020
  const font = layout?.["text-font"];
20208
21021
  if (!Array.isArray(font) || !font.every((entry) => typeof entry === "string")) continue;
20209
21022
  const stack = font.join(",");
@@ -20219,7 +21032,7 @@ function assertGlyphFontStacks(style, config) {
20219
21032
  function assertTextAssets(style, config) {
20220
21033
  const textLayers = [];
20221
21034
  for (const layer of style.layers) {
20222
- const layout = isRecord13(layer.layout) ? layer.layout : void 0;
21035
+ const layout = isRecord14(layer.layout) ? layer.layout : void 0;
20223
21036
  if (layout?.["text-field"] === void 0) continue;
20224
21037
  textLayers.push(typeof layer.id === "string" ? layer.id : "<unknown>");
20225
21038
  const font = layout["text-font"];
@@ -20232,7 +21045,7 @@ function assertTextAssets(style, config) {
20232
21045
  if (textLayers.length === 0) return;
20233
21046
  if (config.fonts !== void 0) {
20234
21047
  const backed = style.layers.find((layer) => {
20235
- const font = isRecord13(layer.layout) ? layer.layout["text-font"] : void 0;
21048
+ const font = isRecord14(layer.layout) ? layer.layout["text-font"] : void 0;
20236
21049
  return Array.isArray(font) && font.length === 1 && typeof font[0] === "string" && parseTileflowLineBackgroundFontStack(font[0]) !== void 0;
20237
21050
  });
20238
21051
  if (backed) {
@@ -20258,7 +21071,7 @@ function runCompilationPhase(phase, operation) {
20258
21071
  }
20259
21072
  }
20260
21073
  function withCompilationPhase(error, phase) {
20261
- const record3 = isRecord13(error) ? error : {};
21074
+ const record3 = isRecord14(error) ? error : {};
20262
21075
  if (typeof record3.phase === "string") return error;
20263
21076
  if (error instanceof Error && Object.isExtensible(error)) {
20264
21077
  Object.defineProperty(error, "phase", { configurable: true, enumerable: true, value: phase });
@@ -20289,6 +21102,35 @@ function compareCodeUnits7(left, right) {
20289
21102
  function semanticReportKey(...parts) {
20290
21103
  return parts.join("\0");
20291
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
+ }
20292
21134
 
20293
21135
  // src/maps/define.ts
20294
21136
  function defineMap(map) {
@@ -20331,6 +21173,7 @@ var tileflowMapMergeStrategies = {
20331
21173
  projection: "atomic",
20332
21174
  scenes: "leaf",
20333
21175
  shots: "leaf",
21176
+ sky: "atomic",
20334
21177
  sources: "keyed-resources",
20335
21178
  systemThemes: "atomic",
20336
21179
  terrain: "atomic",
@@ -20677,11 +21520,11 @@ function cloneDesign(value) {
20677
21520
  function describeMap(value) {
20678
21521
  return typeof value.id === "string" && value.id ? value.id : "<anonymous>";
20679
21522
  }
20680
- function isRecord14(value) {
21523
+ function isRecord15(value) {
20681
21524
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
20682
21525
  }
20683
21526
  function isPlainRecord4(value) {
20684
- if (!isRecord14(value)) return false;
21527
+ if (!isRecord15(value)) return false;
20685
21528
  const prototype = Object.getPrototypeOf(value);
20686
21529
  return prototype === Object.prototype || prototype === null;
20687
21530
  }
@@ -21451,7 +22294,7 @@ function semanticDesign(map) {
21451
22294
  }
21452
22295
  function compareValues(before, after, path, output) {
21453
22296
  if (Object.is(before, after)) return;
21454
- if (isRecord15(before) && isRecord15(after)) {
22297
+ if (isRecord16(before) && isRecord16(after)) {
21455
22298
  const keys = [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])].sort(
21456
22299
  compareCodeUnits9
21457
22300
  );
@@ -21493,7 +22336,7 @@ function toSemanticValue(value, path) {
21493
22336
  }
21494
22337
  throw new TypeError(`Tileflow semantic value at ${path || "/"} is not JSON-serializable.`);
21495
22338
  }
21496
- function isRecord15(value) {
22339
+ function isRecord16(value) {
21497
22340
  return value !== null && typeof value === "object" && !Array.isArray(value);
21498
22341
  }
21499
22342
  function isUnknownRecord(value) {
@@ -21506,7 +22349,7 @@ function deepEqual(left, right) {
21506
22349
  if (Array.isArray(left) || Array.isArray(right)) {
21507
22350
  return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => deepEqual(value, right[index]));
21508
22351
  }
21509
- if (!isRecord15(left) || !isRecord15(right)) return false;
22352
+ if (!isRecord16(left) || !isRecord16(right)) return false;
21510
22353
  const leftKeys = Object.keys(left).sort(compareCodeUnits9);
21511
22354
  const rightKeys = Object.keys(right).sort(compareCodeUnits9);
21512
22355
  return leftKeys.length === rightKeys.length && leftKeys.every((key, index) => key === rightKeys[index] && deepEqual(left[key], right[key]));
@@ -21813,6 +22656,394 @@ function isClassTier(value) {
21813
22656
  return tileflowBoundaryClassTiers.includes(value);
21814
22657
  }
21815
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
+
21816
23047
  // src/icon-package.ts
21817
23048
  import { z as z10 } from "zod";
21818
23049
  var tileflowIconPackageFileNames = [
@@ -22518,7 +23749,7 @@ async function requestSessionGrant(input) {
22518
23749
  else throw error;
22519
23750
  }
22520
23751
  throwIfSessionGrantAborted(signal);
22521
- 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) {
22522
23753
  throw new TileflowSessionRestartError();
22523
23754
  }
22524
23755
  if (!response.ok) {
@@ -22591,7 +23822,7 @@ function sessionGrantAbortReason(signal) {
22591
23822
  return error;
22592
23823
  }
22593
23824
  function parseSessionGrantResponse(value, expectedSessionId) {
22594
- if (!isRecord16(value) || value.ok !== true || value.sessionId !== expectedSessionId) return null;
23825
+ if (!isRecord17(value) || value.ok !== true || value.sessionId !== expectedSessionId) return null;
22595
23826
  if (value.usageMode !== void 0 && value.usageMode !== "session") return null;
22596
23827
  if (value.billingEnabled !== void 0 && value.billingEnabled !== false) return null;
22597
23828
  if (value.disposition !== void 0 && value.disposition !== "ordinary" && value.disposition !== "unbilled_fail_open") {
@@ -22610,11 +23841,11 @@ function parseSessionGrantResponse(value, expectedSessionId) {
22610
23841
  }
22611
23842
  return value;
22612
23843
  }
22613
- function isRecord16(value) {
23844
+ function isRecord17(value) {
22614
23845
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
22615
23846
  }
22616
23847
  function isPlainRuntimeRecord(value) {
22617
- if (!isRecord16(value)) return false;
23848
+ if (!isRecord17(value)) return false;
22618
23849
  const prototype = Object.getPrototypeOf(value);
22619
23850
  return prototype === Object.prototype || prototype === null;
22620
23851
  }
@@ -24079,7 +25310,7 @@ function readTileflowModelFileSummary(buffer) {
24079
25310
  height: round2(height, 100),
24080
25311
  footprint: {
24081
25312
  rings: footprint.length,
24082
- points: footprint.reduce((sum, ring) => sum + ring.length / 2, 0),
25313
+ points: footprint.reduce((sum2, ring) => sum2 + ring.length / 2, 0),
24083
25314
  area: round2(Math.abs(area), 10)
24084
25315
  }
24085
25316
  };
@@ -24275,7 +25506,7 @@ function checkModel(model) {
24275
25506
  throw new RangeError("A footprint ring has at least three points.");
24276
25507
  if (footprint.some((ring) => ring.some((value) => !Number.isFinite(value))))
24277
25508
  throw new RangeError("A footprint point has no place.");
24278
- 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)
24279
25510
  throw new RangeError(
24280
25511
  `A footprint has at most ${tileflowModelLimits.footprintPoints} points in all.`
24281
25512
  );
@@ -24541,7 +25772,7 @@ function validFootprint(footprint) {
24541
25772
  return points2 <= tileflowModelLimits.footprintPoints;
24542
25773
  }
24543
25774
  function readTileflowLandmarkRegion(json) {
24544
- if (!isRecord17(json)) return void 0;
25775
+ if (!isRecord18(json)) return void 0;
24545
25776
  const named = json.format === "tileflow-landmark-region-v2";
24546
25777
  if (!named && json.format !== "tileflow-landmark-region-v1") return void 0;
24547
25778
  const region = json.region;
@@ -24572,7 +25803,7 @@ function readLandmarks(listed, tx, ty) {
24572
25803
  if (!Array.isArray(listed) || listed.length === 0) return void 0;
24573
25804
  const out = [];
24574
25805
  for (const landmark of listed) {
24575
- if (!isRecord17(landmark)) return void 0;
25806
+ if (!isRecord18(landmark)) return void 0;
24576
25807
  const { id: id2, at, heading, footprint } = landmark;
24577
25808
  if (typeof id2 !== "string" || !landmarkId.test(id2)) return void 0;
24578
25809
  if (!Array.isArray(at) || at.length !== 2 || !at.every((value) => typeof value === "number"))
@@ -24596,10 +25827,10 @@ function readLandmarks(listed, tx, ty) {
24596
25827
  return out;
24597
25828
  }
24598
25829
  function readTileflowLandmarksTileJson(json, url) {
24599
- if (!isRecord17(json) || json.format !== "tileflow-landmarks-v2") return void 0;
25830
+ if (!isRecord18(json) || json.format !== "tileflow-landmarks-v2") return void 0;
24600
25831
  const tile = Array.isArray(json.tiles) ? json.tiles[0] : void 0;
24601
25832
  const regions2 = json.regions;
24602
- 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))
24603
25834
  return void 0;
24604
25835
  const available = /* @__PURE__ */ new Set();
24605
25836
  for (const region of regions2.available) {
@@ -24617,7 +25848,7 @@ function readTileflowLandmarksTileJson(json, url) {
24617
25848
  regions: available
24618
25849
  };
24619
25850
  }
24620
- function isRecord17(value) {
25851
+ function isRecord18(value) {
24621
25852
  return typeof value === "object" && value !== null && !Array.isArray(value);
24622
25853
  }
24623
25854
 
@@ -24656,6 +25887,7 @@ export {
24656
25887
  createTileflowLineBackgroundFontStack,
24657
25888
  createTileflowSessionController,
24658
25889
  createTileflowSessionId,
25890
+ declareTileflowNauticalLights,
24659
25891
  decodeTileflowModels,
24660
25892
  decodeTileflowTreeModel,
24661
25893
  defaultTileflowManifestUrl,
@@ -24728,7 +25960,9 @@ export {
24728
25960
  readTileflowLandmarksTileJson,
24729
25961
  readTileflowModelFileIds,
24730
25962
  readTileflowModelFileSummary,
25963
+ readTileflowNauticalLights,
24731
25964
  readTileflowNotices,
25965
+ readTileflowSky,
24732
25966
  readTileflowStreetLights,
24733
25967
  readTileflowSunPreset,
24734
25968
  readTileflowTeamModelName,
@@ -24771,6 +26005,7 @@ export {
24771
26005
  sha256Hex,
24772
26006
  shouldLoadTileflowManifest,
24773
26007
  startTileflowSession,
26008
+ steadyTileflowNauticalLights,
24774
26009
  tileflowAuthoringManifest,
24775
26010
  tileflowAuthoringManifestSchemaVersion,
24776
26011
  tileflowBandPlacements,
@@ -24849,6 +26084,10 @@ export {
24849
26084
  tileflowLandmarkTileOf,
24850
26085
  tileflowLandmarkTileZoom,
24851
26086
  tileflowLayerBands,
26087
+ tileflowLightRhythm,
26088
+ tileflowLightRhythmConventions,
26089
+ tileflowLightRhythmLimits,
26090
+ tileflowLightStateAt,
24852
26091
  tileflowLineBackgroundDefaults,
24853
26092
  tileflowLineBackgroundHaloRatio,
24854
26093
  tileflowLineBackgroundLimits,
@@ -24856,6 +26095,7 @@ export {
24856
26095
  tileflowLocalDirectoryMessage,
24857
26096
  tileflowMapDefaultMaxDepth,
24858
26097
  tileflowMapIdSchema,
26098
+ tileflowMaxPitch,
24859
26099
  tileflowModelContentType,
24860
26100
  tileflowModelDefaultLightingRoles,
24861
26101
  tileflowModelFileName,
@@ -24866,8 +26106,14 @@ export {
24866
26106
  tileflowModelSurfaceLimits,
24867
26107
  tileflowModelTextureChannels,
24868
26108
  tileflowModelVertexBytes,
26109
+ tileflowNauticalFacilityIcons,
24869
26110
  tileflowNauticalImageRoles,
26111
+ tileflowNauticalLightEclipseOpacity,
26112
+ tileflowNauticalLightStates,
26113
+ tileflowNauticalLightsLimits,
26114
+ tileflowNauticalLightsMetadataKey,
24870
26115
  tileflowNauticalNotice,
26116
+ tileflowNauticalPromoteId,
24871
26117
  tileflowNauticalSourceId,
24872
26118
  tileflowNauticalV1Schema,
24873
26119
  tileflowNoticeLimits,
@@ -24895,6 +26141,8 @@ export {
24895
26141
  tileflowRuntimeManifestSchema,
24896
26142
  tileflowRuntimeManifestVersion,
24897
26143
  tileflowSemanticDiffSchemaVersion,
26144
+ tileflowSkyAtSun,
26145
+ tileflowSkyMetadataKey,
24898
26146
  tileflowStreetLightsMetadataKey,
24899
26147
  tileflowStyleFontFaceLimits,
24900
26148
  tileflowStyleFontFacesMetadataKey,