@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/build.js CHANGED
@@ -4,6 +4,9 @@ import { validateStyleMin } from "@maplibre/maplibre-gl-style-spec";
4
4
  // src/atmosphere.ts
5
5
  var tileflowAtmosphereMetadataKey = "tileflow:atmosphere";
6
6
 
7
+ // src/sky.ts
8
+ var tileflowSkyMetadataKey = "tileflow:sky";
9
+
7
10
  // src/atmosphere-compile.ts
8
11
  import { Color } from "@maplibre/maplibre-gl-style-spec";
9
12
  var defaults = {
@@ -17,14 +20,7 @@ var defaults = {
17
20
  function resolveTileflowAtmosphere(value) {
18
21
  if (!value) return void 0;
19
22
  const options = { ...defaults, ...value === true ? {} : value };
20
- const normalize = (input) => {
21
- const color2 = typeof input === "string" ? Color.parse(input) : void 0;
22
- if (!color2 || color2.a !== 1)
23
- throw new Error("Atmosphere colors must resolve to opaque colors.");
24
- return `#${[color2.r, color2.g, color2.b].map(
25
- (channel) => Math.round(channel * 255).toString(16).padStart(2, "0")
26
- ).join("")}`;
27
- };
23
+ const normalize = (input) => opaqueHex(input, "Atmosphere colors");
28
24
  return {
29
25
  ...options,
30
26
  skyColor: normalize(options.skyColor),
@@ -33,6 +29,13 @@ function resolveTileflowAtmosphere(value) {
33
29
  spaceColor: normalize(options.spaceColor)
34
30
  };
35
31
  }
32
+ function opaqueHex(input, subject) {
33
+ const color2 = typeof input === "string" ? Color.parse(input) : void 0;
34
+ if (!color2 || color2.a !== 1) throw new Error(`${subject} must resolve to opaque colors.`);
35
+ return `#${[color2.r, color2.g, color2.b].map(
36
+ (channel) => Math.round(channel * 255).toString(16).padStart(2, "0")
37
+ ).join("")}`;
38
+ }
36
39
  function compileTileflowSky(options) {
37
40
  return {
38
41
  "sky-color": options.skyColor,
@@ -44,6 +47,41 @@ function compileTileflowSky(options) {
44
47
  "atmosphere-blend": ["interpolate", ["linear"], ["zoom"], 0, 0.55, 3, 0.45, 5, 0.2, 7, 0]
45
48
  };
46
49
  }
50
+ var skyDefaults = {
51
+ color: "#5c9ce6",
52
+ horizonColor: "#dcebf7",
53
+ horizonBlend: 0.6,
54
+ glowColor: "#ffc68a",
55
+ starIntensity: 0.6
56
+ };
57
+ function resolveTileflowSky(value) {
58
+ if (!value) return void 0;
59
+ const options = { ...skyDefaults, ...value === true ? {} : value };
60
+ const unit = (input, name) => {
61
+ if (typeof input !== "number" || !Number.isFinite(input) || input < 0 || input > 1)
62
+ throw new Error(`Sky ${name} must resolve to a number from 0 to 1.`);
63
+ return input;
64
+ };
65
+ return {
66
+ color: opaqueHex(options.color, "Sky colors"),
67
+ horizonColor: opaqueHex(options.horizonColor, "Sky colors"),
68
+ horizonBlend: unit(options.horizonBlend, "horizonBlend"),
69
+ glowColor: opaqueHex(options.glowColor, "Sky colors"),
70
+ starIntensity: unit(options.starIntensity, "starIntensity")
71
+ };
72
+ }
73
+ function compileTileflowStreetSky(options) {
74
+ return {
75
+ "sky-color": options.color,
76
+ "horizon-color": options.horizonColor,
77
+ // MapLibre fogs only 3D terrain; the haze matches the horizon so terrain meets the sky.
78
+ "fog-color": options.horizonColor,
79
+ "sky-horizon-blend": options.horizonBlend,
80
+ "horizon-fog-blend": 0.6,
81
+ "fog-ground-blend": 0.75,
82
+ "atmosphere-blend": ["interpolate", ["linear"], ["zoom"], 0, 0.55, 3, 0.45, 5, 0.2, 7, 0]
83
+ };
84
+ }
47
85
 
48
86
  // src/models-3d-model.ts
49
87
  var tileflowModelParts = Object.freeze({
@@ -3429,12 +3467,46 @@ var tileflowNauticalImageRoles = Object.freeze({
3429
3467
  port: "nautical.buoy.port",
3430
3468
  starboard: "nautical.buoy.starboard"
3431
3469
  }),
3432
- harbours: Object.freeze({ default: "nautical.harbour" }),
3470
+ distanceMarks: Object.freeze({ default: "nautical.distance-mark" }),
3471
+ /**
3472
+ * A facility that lists several categories shows the first of `tileflowNauticalFacilityIcons`
3473
+ * it lists; facilities without a variant token fall back to `nautical.facility`.
3474
+ */
3475
+ facilities: Object.freeze({
3476
+ "boat-hoist": "nautical.facility.boat-hoist",
3477
+ boatyard: "nautical.facility.boatyard",
3478
+ default: "nautical.facility",
3479
+ "fuel-station": "nautical.facility.fuel-station",
3480
+ "pump-out": "nautical.facility.pump-out",
3481
+ slipway: "nautical.facility.slipway",
3482
+ "visitor-berth": "nautical.facility.visitor-berth",
3483
+ "water-tap": "nautical.facility.water-tap"
3484
+ }),
3485
+ /** Harbours whose `subclass` lists a marina use `nautical.marina` when the theme declares it. */
3486
+ harbours: Object.freeze({ default: "nautical.harbour", marina: "nautical.marina" }),
3433
3487
  lighthouses: Object.freeze({ default: "nautical.lighthouse" }),
3434
3488
  lights: Object.freeze({ default: "nautical.light" }),
3489
+ /** Notice boards by function; a board without a variant token uses `nautical.notice`. */
3490
+ notices: Object.freeze({
3491
+ default: "nautical.notice",
3492
+ information: "nautical.notice.information",
3493
+ prohibition: "nautical.notice.prohibition",
3494
+ recommendation: "nautical.notice.recommendation",
3495
+ regulation: "nautical.notice.regulation",
3496
+ restriction: "nautical.notice.restriction"
3497
+ }),
3435
3498
  ports: Object.freeze({ default: "nautical.port" }),
3436
3499
  wrecks: Object.freeze({ default: "nautical.wreck" })
3437
3500
  });
3501
+ var tileflowNauticalFacilityIcons = Object.freeze([
3502
+ "fuel-station",
3503
+ "slipway",
3504
+ "boat-hoist",
3505
+ "pump-out",
3506
+ "water-tap",
3507
+ "visitor-berth",
3508
+ "boatyard"
3509
+ ]);
3438
3510
  var tileflowNauticalNotice = Object.freeze({
3439
3511
  id: "nautical-not-for-navigation",
3440
3512
  text: "Not for navigation"
@@ -3493,10 +3565,15 @@ var tileflowNauticalV1Schema = Object.freeze({
3493
3565
  country: "country",
3494
3566
  coverage: "coverage",
3495
3567
  depth: "depth",
3568
+ distance: "distance",
3569
+ distanceUnit: "distance_unit",
3496
3570
  edition: "edition",
3497
3571
  families: "families",
3498
3572
  group: "group",
3499
3573
  heightM: "height_m",
3574
+ horizontalClearance: "horizontal_clearance",
3575
+ impact: "impact",
3576
+ information: "information",
3500
3577
  label: "label",
3501
3578
  licence: "licence",
3502
3579
  lit: "lit",
@@ -3514,6 +3591,7 @@ var tileflowNauticalV1Schema = Object.freeze({
3514
3591
  scale: "scale",
3515
3592
  sectorEnd: "sector_end",
3516
3593
  sectorStart: "sector_start",
3594
+ sequence: "sequence",
3517
3595
  shape: "shape",
3518
3596
  size: "size",
3519
3597
  subclass: "subclass",
@@ -3521,6 +3599,10 @@ var tileflowNauticalV1Schema = Object.freeze({
3521
3599
  topmark: "topmark",
3522
3600
  unlocode: "unlocode",
3523
3601
  update: "update",
3602
+ verticalClearance: "vertical_clearance",
3603
+ verticalClearanceClosed: "vertical_clearance_closed",
3604
+ verticalClearanceOpen: "vertical_clearance_open",
3605
+ verticalClearanceSafe: "vertical_clearance_safe",
3524
3606
  waterLevel: "water_level"
3525
3607
  }),
3526
3608
  geometryTypes: Object.freeze({
@@ -3544,6 +3626,16 @@ var tileflowNauticalV1Schema = Object.freeze({
3544
3626
  wrecks: "wreck"
3545
3627
  }),
3546
3628
  product: "nautical-v1",
3629
+ /**
3630
+ * Known `provider` values and the `class` of their `coverage` cells. The source's TileJSON owns
3631
+ * each provider's credit; the module never adds attribution.
3632
+ */
3633
+ providers: Object.freeze({
3634
+ "nga-list-of-lights": "list",
3635
+ "noaa-office-of-coast-survey": "chart",
3636
+ openstreetmap: "community",
3637
+ "usace-ienc": "chart"
3638
+ }),
3547
3639
  schemaVersion: 1
3548
3640
  });
3549
3641
  var tileflowBathymetryNameLayerLimits = Object.freeze({ maxZoomRanges: 128 });
@@ -3628,8 +3720,11 @@ function resolveBathymetryRelief(style, apiBaseUrl) {
3628
3720
  style: cloneJson12(style)
3629
3721
  };
3630
3722
  }
3723
+ var tileflowNauticalPromoteId = Object.freeze({
3724
+ [tileflowNauticalV1Schema.layers.lights]: tileflowNauticalV1Schema.fields.provenance
3725
+ });
3631
3726
  function resolveNauticalSource(config, apiBaseUrl) {
3632
- return resolveMarineSource({
3727
+ const resolved = resolveMarineSource({
3633
3728
  config,
3634
3729
  defaultPath: "/tiles/nautical/tiles.json",
3635
3730
  defaultSourceId: tileflowNauticalSourceId,
@@ -3637,6 +3732,7 @@ function resolveNauticalSource(config, apiBaseUrl) {
3637
3732
  product: tileflowNauticalV1Schema.product,
3638
3733
  apiBaseUrl
3639
3734
  });
3735
+ return { ...resolved, source: { ...resolved.source, promoteId: { ...tileflowNauticalPromoteId } } };
3640
3736
  }
3641
3737
  function resolveMarineSource(options) {
3642
3738
  const sourceId = validateSourceId(options.config.sourceId ?? options.defaultSourceId);
@@ -3698,6 +3794,42 @@ function cloneJson12(value) {
3698
3794
  return JSON.parse(JSON.stringify(value));
3699
3795
  }
3700
3796
 
3797
+ // src/nautical-lights.ts
3798
+ var tileflowNauticalLightsMetadataKey = "tileflow:nautical-lights";
3799
+ var tileflowNauticalLightStates = Object.freeze({
3800
+ /** Index of the colour shown, into the light's comma-separated `color` list. */
3801
+ colour: "tileflowLightColour",
3802
+ /** `1` while the light shines, `0` while it is eclipsed, in between for a fixed part. */
3803
+ intensity: "tileflowLightIntensity"
3804
+ });
3805
+ var tileflowNauticalLightEclipseOpacity = 0.15;
3806
+ var tileflowNauticalLightsLimits = Object.freeze({ maxLayers: 16 });
3807
+ function declareTileflowNauticalLights(layers2, sourceId, sourceLayer) {
3808
+ if (!sourceId) return void 0;
3809
+ const ids = layers2.filter(
3810
+ (layer) => layer.source === sourceId && layer["source-layer"] === sourceLayer && readsLightState(layer.paint)
3811
+ ).map(({ id: id2 }) => id2).filter((id2) => typeof id2 === "string").slice(0, tileflowNauticalLightsLimits.maxLayers);
3812
+ if (ids.length === 0) return void 0;
3813
+ return { layers: ids, source: sourceId, sourceLayer, version: 1 };
3814
+ }
3815
+ function tileflowNauticalLightStateRead(state, steadyValue) {
3816
+ return ["number", ["feature-state", tileflowNauticalLightStates[state]], steadyValue];
3817
+ }
3818
+ var stateKeys = new Set(Object.values(tileflowNauticalLightStates));
3819
+ function readsLightState(value) {
3820
+ if (!Array.isArray(value)) {
3821
+ return isRecord4(value) ? Object.values(value).some(readsLightState) : false;
3822
+ }
3823
+ if (value[0] === "literal") return false;
3824
+ if (value[0] === "feature-state" && typeof value[1] === "string" && stateKeys.has(value[1])) {
3825
+ return true;
3826
+ }
3827
+ return value.some(readsLightState);
3828
+ }
3829
+ function isRecord4(value) {
3830
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
3831
+ }
3832
+
3701
3833
  // src/modules/nautical/compiler.ts
3702
3834
  var { fields, layers } = tileflowNauticalV1Schema;
3703
3835
  var markColours = Object.freeze({
@@ -3740,6 +3872,24 @@ var operationalAreaClasses = [
3740
3872
  var routeClasses = ["route", "traffic-separation"];
3741
3873
  var cableClasses = ["cable", "cable-area", "pipeline", "pipeline-area"];
3742
3874
  var portSizes = ["large", "medium", "small", "very-small"];
3875
+ var ownAreaClasses = ["berth", "bridge", "facility", "harbour"];
3876
+ var ownAidClasses = ["distance-mark", "notice"];
3877
+ var marinaSubclasses = ["marina", "marina-no-facilities"];
3878
+ var noticeFunctionColours = Object.freeze({
3879
+ information: markColours.blue,
3880
+ prohibition: markColours.red,
3881
+ recommendation: markColours.green,
3882
+ regulation: markColours.red,
3883
+ restriction: markColours.red
3884
+ });
3885
+ var distanceUnits = Object.freeze({
3886
+ hectometres: "hm",
3887
+ kilometres: "km",
3888
+ metres: "m",
3889
+ "nautical-miles": "NM",
3890
+ "statute-miles": "mi"
3891
+ });
3892
+ var maxAlternatingColours = 3;
3743
3893
  function compileNautical(request, context) {
3744
3894
  const nauticalSource = context.marine?.nautical;
3745
3895
  if (!nauticalSource || request?.enabled === false) return [];
@@ -3770,10 +3920,30 @@ function compileNautical(request, context) {
3770
3920
  const belowLabel = { anchor: "top", offset: [0, 0.75], optional: true };
3771
3921
  const markColour = expression(colourMatch(firstColour(fields.color), markColours, ink));
3772
3922
  const markStroke = expression(colourMatch(secondColour(fields.color), markColours, ink));
3923
+ const animateLights = request?.animateLights !== false;
3773
3924
  const flareColour = expression(
3774
- colourMatch(firstColour(fields.color), lightColours, lightColours.white)
3925
+ colourMatch(
3926
+ animateLights ? nthColour(fields.color, tileflowNauticalLightStateRead("colour", 0)) : firstColour(fields.color),
3927
+ lightColours,
3928
+ lightColours.white
3929
+ )
3775
3930
  );
3776
3931
  const iconPaint = { color: markColour, haloColor: ink, haloWidth: 1 };
3932
+ const noticeColours = [
3933
+ "match",
3934
+ ["get", fields.subclass],
3935
+ ...Object.entries(noticeFunctionColours).flat(),
3936
+ ink
3937
+ ];
3938
+ const noticeColour = expression(noticeColours);
3939
+ const distanceOpacity = (installed, notInstalled) => expression([
3940
+ "match",
3941
+ ["get", fields.category],
3942
+ "not-installed",
3943
+ notInstalled,
3944
+ installed
3945
+ ]);
3946
+ const isGuestBerth = listIncludes(fields.subclass, "guest");
3777
3947
  const defaults4 = {
3778
3948
  type: "nautical",
3779
3949
  enabled: true,
@@ -3864,6 +4034,60 @@ function compileNautical(request, context) {
3864
4034
  [14, 1.3]
3865
4035
  ])
3866
4036
  },
4037
+ harbourAreas: {
4038
+ fill: { color: ink, minZoom: 10, opacity: 0.05 },
4039
+ outline: {
4040
+ color: ink,
4041
+ minZoom: 10,
4042
+ opacity: zoom.linear([
4043
+ [10, 0.35],
4044
+ [14, 0.6]
4045
+ ]),
4046
+ width: zoom.linear([
4047
+ [10, 0.5],
4048
+ [16, 1]
4049
+ ])
4050
+ }
4051
+ },
4052
+ berths: {
4053
+ fill: { color: ink, minZoom: 13, opacity: 0.06 },
4054
+ outline: {
4055
+ color: ink,
4056
+ minZoom: 13,
4057
+ opacity: expression(["case", isGuestBerth, 0.95, 0.5]),
4058
+ width: expression([
4059
+ "interpolate",
4060
+ ["linear"],
4061
+ ["zoom"],
4062
+ 13,
4063
+ ["case", isGuestBerth, 1.2, 0.6],
4064
+ 16,
4065
+ ["case", isGuestBerth, 2.4, 1.2]
4066
+ ])
4067
+ }
4068
+ },
4069
+ bridges: {
4070
+ fill: { color: colors.land, minZoom: 12, opacity: 0.9 },
4071
+ outline: {
4072
+ color: ink,
4073
+ minZoom: 12,
4074
+ opacity: 0.85,
4075
+ width: zoom.linear([
4076
+ [12, 0.8],
4077
+ [16, 1.8]
4078
+ ])
4079
+ }
4080
+ },
4081
+ overheadCables: {
4082
+ color: accent,
4083
+ dash: [6, 2, 1, 2],
4084
+ minZoom: 10,
4085
+ opacity: 0.75,
4086
+ width: zoom.linear([
4087
+ [10, 0.7],
4088
+ [16, 1.3]
4089
+ ])
4090
+ },
3867
4091
  hazardAreas: {
3868
4092
  fill: { color: accent, minZoom: 10, opacity: 0.08 },
3869
4093
  outline: { color: accent, dash: [1, 1], minZoom: 10, opacity: 0.7, width: 0.8 }
@@ -3973,6 +4197,7 @@ function compileNautical(request, context) {
3973
4197
  harbours: {
3974
4198
  minZoom: 11,
3975
4199
  placement: "point",
4200
+ ...rankPriority,
3976
4201
  ...images.harbours ? {
3977
4202
  icon: {
3978
4203
  color: ink,
@@ -4023,6 +4248,147 @@ function compileNautical(request, context) {
4023
4248
  },
4024
4249
  text: { ...markText, ...sideLabel, field: nameFrom(15), padding: 3, size: 9 }
4025
4250
  },
4251
+ notices: {
4252
+ minZoom: 12,
4253
+ placement: "point",
4254
+ ...rankPriority,
4255
+ ...images.notices ? {
4256
+ icon: {
4257
+ color: noticeColour,
4258
+ haloColor: halo,
4259
+ haloWidth: 1,
4260
+ image: images.notices,
4261
+ padding: 1,
4262
+ size: zoom.linear([
4263
+ [12, 0.8],
4264
+ [16, 1.2]
4265
+ ])
4266
+ }
4267
+ } : {
4268
+ marker: {
4269
+ color: expression([
4270
+ "match",
4271
+ ["get", fields.subclass],
4272
+ ["prohibition", "recommendation", "information"],
4273
+ noticeColours,
4274
+ halo
4275
+ ]),
4276
+ radius: zoom.linear([
4277
+ [12, 1.8],
4278
+ [16, 3.2]
4279
+ ]),
4280
+ strokeColor: expression([
4281
+ "match",
4282
+ ["get", fields.subclass],
4283
+ ["prohibition", "recommendation", "information"],
4284
+ halo,
4285
+ noticeColours
4286
+ ]),
4287
+ strokeWidth: 1
4288
+ }
4289
+ },
4290
+ text: {
4291
+ ...markText,
4292
+ ...sideLabel,
4293
+ field: expression([
4294
+ "step",
4295
+ ["zoom"],
4296
+ "",
4297
+ 14,
4298
+ ["to-string", ["coalesce", ["get", fields.information], ""]]
4299
+ ]),
4300
+ padding: 2,
4301
+ size: 9
4302
+ }
4303
+ },
4304
+ distanceMarks: {
4305
+ minZoom: 11,
4306
+ placement: "point",
4307
+ ...rankPriority,
4308
+ ...images.distanceMarks ? {
4309
+ icon: {
4310
+ color: ink,
4311
+ haloColor: halo,
4312
+ haloWidth: 1,
4313
+ image: images.distanceMarks,
4314
+ opacity: distanceOpacity(1, 0.6),
4315
+ size: zoom.linear([
4316
+ [11, 0.7],
4317
+ [16, 1]
4318
+ ])
4319
+ }
4320
+ } : {
4321
+ marker: {
4322
+ color: ink,
4323
+ opacity: distanceOpacity(1, 0.6),
4324
+ radius: zoom.linear([
4325
+ [11, 1.2],
4326
+ [16, 2.2]
4327
+ ]),
4328
+ strokeColor: halo,
4329
+ strokeWidth: 0.8
4330
+ }
4331
+ },
4332
+ text: {
4333
+ ...markText,
4334
+ ...sideLabel,
4335
+ field: expression([
4336
+ "step",
4337
+ ["zoom"],
4338
+ "",
4339
+ 13,
4340
+ [
4341
+ "case",
4342
+ ["has", fields.distance],
4343
+ [
4344
+ "concat",
4345
+ ["to-string", ["get", fields.distance]],
4346
+ [
4347
+ "match",
4348
+ ["get", fields.distanceUnit],
4349
+ ...Object.entries(distanceUnits).flatMap(([unit, label]) => [unit, ` ${label}`]),
4350
+ ""
4351
+ ]
4352
+ ],
4353
+ ""
4354
+ ]
4355
+ ]),
4356
+ opacity: distanceOpacity(1, 0.75),
4357
+ padding: 2,
4358
+ size: zoom.linear([
4359
+ [13, 8.5],
4360
+ [16, 10]
4361
+ ])
4362
+ }
4363
+ },
4364
+ facilities: {
4365
+ minZoom: 13,
4366
+ placement: "point",
4367
+ ...images.facilities ? {
4368
+ icon: {
4369
+ color: ink,
4370
+ haloColor: halo,
4371
+ haloWidth: 1,
4372
+ image: images.facilities,
4373
+ padding: 1,
4374
+ size: zoom.linear([
4375
+ [13, 0.8],
4376
+ [16, 1.15]
4377
+ ])
4378
+ }
4379
+ } : {
4380
+ marker: {
4381
+ color: halo,
4382
+ radius: zoom.linear([
4383
+ [13, 1.8],
4384
+ [16, 3]
4385
+ ]),
4386
+ strokeColor: ink,
4387
+ strokeWidth: 1
4388
+ }
4389
+ },
4390
+ text: { ...markText, ...sideLabel, field: nameFrom(15), padding: 3, size: 9 }
4391
+ },
4026
4392
  beacons: {
4027
4393
  minZoom: 11,
4028
4394
  placement: "point",
@@ -4205,6 +4571,32 @@ function compileNautical(request, context) {
4205
4571
  }
4206
4572
  },
4207
4573
  labels: {
4574
+ bridges: {
4575
+ minZoom: 13,
4576
+ placement: "point",
4577
+ text: {
4578
+ ...markText,
4579
+ field: expression(bridgeClearance()),
4580
+ padding: 2,
4581
+ size: zoom.linear([
4582
+ [13, 8.5],
4583
+ [16, 10]
4584
+ ])
4585
+ }
4586
+ },
4587
+ overheadCables: {
4588
+ minZoom: 12,
4589
+ placement: "point",
4590
+ spacing: 360,
4591
+ text: {
4592
+ ...markText,
4593
+ color: accent,
4594
+ field: expression(cableClearance()),
4595
+ maxAngle: 30,
4596
+ padding: 2,
4597
+ size: 8.5
4598
+ }
4599
+ },
4208
4600
  coverage: {
4209
4601
  maxZoom: 9,
4210
4602
  minZoom: 4,
@@ -4254,6 +4646,7 @@ function compileNautical(request, context) {
4254
4646
  const source = nauticalSource.sourceId;
4255
4647
  const contributions = [];
4256
4648
  const add2 = (options) => appendNauticalLayer(contributions, { ...options, source });
4649
+ const ofClass = (value) => ["==", ["get", fields.class], value];
4257
4650
  add2({
4258
4651
  // Only coverage that speaks for every family; lists of lights alone would double the tint.
4259
4652
  filter: ["all", polygons, ["in", layers.aids, ["coalesce", ["get", fields.families], ""]]],
@@ -4264,13 +4657,30 @@ function compileNautical(request, context) {
4264
4657
  target: "nautical.coverage"
4265
4658
  });
4266
4659
  add2({
4267
- filter: polygons,
4660
+ filter: ["all", polygons, ["match", ["get", fields.class], [...ownAreaClasses], false, true]],
4268
4661
  id: "tileflow-nautical-navigation-areas",
4269
4662
  localOrder: 390,
4270
4663
  sourceLayer: layers.navigationAreas,
4271
4664
  style: config.navigationAreas,
4272
4665
  target: "nautical.navigationAreas"
4273
4666
  });
4667
+ add2({
4668
+ filter: ["all", polygons, ofClass("harbour")],
4669
+ id: "tileflow-nautical-harbour-areas",
4670
+ localOrder: 392,
4671
+ sourceLayer: layers.navigationAreas,
4672
+ style: config.harbourAreas,
4673
+ target: "nautical.harbourAreas"
4674
+ });
4675
+ add2({
4676
+ filter: ["all", polygons, ofClass("berth")],
4677
+ id: "tileflow-nautical-berths",
4678
+ localOrder: 394,
4679
+ outlineFilter: ["all", linesOrPolygons, ofClass("berth")],
4680
+ sourceLayer: layers.navigationAreas,
4681
+ style: config.berths,
4682
+ target: "nautical.berths"
4683
+ });
4274
4684
  add2({
4275
4685
  filter: polygons,
4276
4686
  id: "tileflow-nautical-reefs",
@@ -4297,11 +4707,20 @@ function compileNautical(request, context) {
4297
4707
  style: config.routes,
4298
4708
  target: "nautical.routes"
4299
4709
  });
4710
+ add2({
4711
+ filter: linesOf(["overhead-cable"]),
4712
+ id: "tileflow-nautical-overhead-cables",
4713
+ line: true,
4714
+ localOrder: 408,
4715
+ sourceLayer: layers.navigationAreas,
4716
+ style: config.overheadCables,
4717
+ target: "nautical.overheadCables"
4718
+ });
4300
4719
  add2({
4301
4720
  filter: polygons,
4302
4721
  id: "tileflow-nautical-hazard-areas",
4303
4722
  localOrder: 410,
4304
- outlineFilter: ["match", ["geometry-type"], ["LineString", "Polygon"], true, false],
4723
+ outlineFilter: linesOrPolygons,
4305
4724
  sourceLayer: layers.hazards,
4306
4725
  style: config.hazardAreas,
4307
4726
  target: "nautical.hazardAreas"
@@ -4314,15 +4733,42 @@ function compileNautical(request, context) {
4314
4733
  style: config.wreckAreas,
4315
4734
  target: "nautical.wreckAreas"
4316
4735
  });
4317
- for (const [key, id2, sourceLayer, localOrder] of [
4318
- ["coverage", "tileflow-nautical-coverage-labels", layers.coverage, 560],
4319
- ["navigationAreas", "tileflow-nautical-navigation-area-labels", layers.navigationAreas, 562],
4320
- ["reefs", "tileflow-nautical-reef-labels", layers.reefs, 564],
4321
- ["hazards", "tileflow-nautical-hazard-area-labels", layers.hazards, 566],
4322
- ["wrecks", "tileflow-nautical-wreck-area-labels", layers.wrecks, 568]
4736
+ add2({
4737
+ // Bridges draw over the water and the areas below them.
4738
+ filter: ["all", polygons, ofClass("bridge")],
4739
+ id: "tileflow-nautical-bridges",
4740
+ localOrder: 424,
4741
+ outlineFilter: ["all", linesOrPolygons, ofClass("bridge")],
4742
+ sourceLayer: layers.navigationAreas,
4743
+ style: config.bridges,
4744
+ target: "nautical.bridges"
4745
+ });
4746
+ for (const [key, id2, sourceLayer, localOrder, filter] of [
4747
+ [
4748
+ "coverage",
4749
+ "tileflow-nautical-coverage-labels",
4750
+ layers.coverage,
4751
+ 560,
4752
+ ["has", fields.provider]
4753
+ ],
4754
+ [
4755
+ "navigationAreas",
4756
+ "tileflow-nautical-navigation-area-labels",
4757
+ layers.navigationAreas,
4758
+ 562,
4759
+ // Harbours, facilities and bridges carry their own symbols and labels.
4760
+ [
4761
+ "all",
4762
+ ["has", fields.name],
4763
+ ["match", ["get", fields.class], ["bridge", "facility", "harbour"], false, true]
4764
+ ]
4765
+ ],
4766
+ ["reefs", "tileflow-nautical-reef-labels", layers.reefs, 564, ["has", fields.name]],
4767
+ ["hazards", "tileflow-nautical-hazard-area-labels", layers.hazards, 566, ["has", fields.name]],
4768
+ ["wrecks", "tileflow-nautical-wreck-area-labels", layers.wrecks, 568, ["has", fields.name]]
4323
4769
  ]) {
4324
4770
  add2({
4325
- filter: ["all", polygons, ["has", key === "coverage" ? fields.provider : fields.name]],
4771
+ filter: ["all", polygons, filter],
4326
4772
  id: id2,
4327
4773
  localOrder,
4328
4774
  sourceLayer,
@@ -4330,6 +4776,37 @@ function compileNautical(request, context) {
4330
4776
  target: `nautical.labels.${key}`
4331
4777
  });
4332
4778
  }
4779
+ add2({
4780
+ filter: [
4781
+ "all",
4782
+ ofClass("bridge"),
4783
+ [
4784
+ "any",
4785
+ ["has", fields.verticalClearance],
4786
+ ["has", fields.verticalClearanceClosed],
4787
+ ["has", fields.verticalClearanceOpen]
4788
+ ]
4789
+ ],
4790
+ id: "tileflow-nautical-bridge-labels",
4791
+ lines: { localOrder: 557, placement: "line-center" },
4792
+ localOrder: 556,
4793
+ sourceLayer: layers.navigationAreas,
4794
+ style: config.labels?.bridges,
4795
+ target: "nautical.labels.bridges"
4796
+ });
4797
+ add2({
4798
+ filter: [
4799
+ "all",
4800
+ ofClass("overhead-cable"),
4801
+ ["any", ["has", fields.verticalClearanceSafe], ["has", fields.verticalClearance]]
4802
+ ],
4803
+ id: "tileflow-nautical-overhead-cable-labels",
4804
+ lines: { localOrder: 559, placement: "line" },
4805
+ localOrder: 558,
4806
+ sourceLayer: layers.navigationAreas,
4807
+ style: config.labels?.overheadCables,
4808
+ target: "nautical.labels.overheadCables"
4809
+ });
4333
4810
  add2({
4334
4811
  filter: ["all", points, ["has", fields.depth]],
4335
4812
  id: "tileflow-nautical-soundings",
@@ -4348,6 +4825,16 @@ function compileNautical(request, context) {
4348
4825
  symbolFilter: ["==", ["get", fields.class], "rock"],
4349
4826
  target: "nautical.hazards"
4350
4827
  });
4828
+ add2({
4829
+ filter: ofClass("facility"),
4830
+ id: "tileflow-nautical-facilities",
4831
+ lines: { localOrder: 580, placement: "line-center" },
4832
+ localOrder: 576,
4833
+ markerFilter: points,
4834
+ sourceLayer: layers.navigationAreas,
4835
+ style: config.facilities,
4836
+ target: "nautical.facilities"
4837
+ });
4351
4838
  add2({
4352
4839
  filter: points,
4353
4840
  id: "tileflow-nautical-wrecks",
@@ -4357,13 +4844,23 @@ function compileNautical(request, context) {
4357
4844
  target: "nautical.wrecks"
4358
4845
  });
4359
4846
  add2({
4360
- filter: ["all", points, ["==", ["get", fields.class], "harbour"]],
4847
+ // Harbour polygons, such as marinas, carry their symbol and name at their centre.
4848
+ filter: ["all", pointsOrPolygons, ofClass("harbour")],
4361
4849
  id: "tileflow-nautical-harbours",
4362
4850
  localOrder: 582,
4851
+ markerFilter: points,
4363
4852
  sourceLayer: layers.navigationAreas,
4364
4853
  style: config.harbours,
4365
4854
  target: "nautical.harbours"
4366
4855
  });
4856
+ add2({
4857
+ filter: ["all", points, ofClass("distance-mark")],
4858
+ id: "tileflow-nautical-distance-marks",
4859
+ localOrder: 584,
4860
+ sourceLayer: layers.aids,
4861
+ style: config.distanceMarks,
4862
+ target: "nautical.distanceMarks"
4863
+ });
4367
4864
  add2({
4368
4865
  filter: [
4369
4866
  "all",
@@ -4371,7 +4868,7 @@ function compileNautical(request, context) {
4371
4868
  [
4372
4869
  "match",
4373
4870
  ["get", fields.class],
4374
- [...floatingAidClasses, ...fixedAidClasses, "lighthouse"],
4871
+ [...floatingAidClasses, ...fixedAidClasses, ...ownAidClasses, "lighthouse"],
4375
4872
  false,
4376
4873
  true
4377
4874
  ]
@@ -4382,6 +4879,14 @@ function compileNautical(request, context) {
4382
4879
  style: config.aids,
4383
4880
  target: "nautical.aids"
4384
4881
  });
4882
+ add2({
4883
+ filter: ["all", points, ofClass("notice")],
4884
+ id: "tileflow-nautical-notices",
4885
+ localOrder: 588,
4886
+ sourceLayer: layers.aids,
4887
+ style: config.notices,
4888
+ target: "nautical.notices"
4889
+ });
4385
4890
  add2({
4386
4891
  filter: ["all", points, ["match", ["get", fields.class], [...fixedAidClasses], true, false]],
4387
4892
  id: "tileflow-nautical-beacons",
@@ -4392,6 +4897,7 @@ function compileNautical(request, context) {
4392
4897
  });
4393
4898
  add2({
4394
4899
  filter: points,
4900
+ flash: animateLights,
4395
4901
  id: "tileflow-nautical-lights",
4396
4902
  localOrder: 594,
4397
4903
  sourceLayer: layers.lights,
@@ -4407,7 +4913,7 @@ function compileNautical(request, context) {
4407
4913
  target: "nautical.buoys"
4408
4914
  });
4409
4915
  add2({
4410
- filter: ["all", points, ["==", ["get", fields.class], "lighthouse"]],
4916
+ filter: ["all", points, ofClass("lighthouse")],
4411
4917
  id: "tileflow-nautical-lighthouses",
4412
4918
  localOrder: 602,
4413
4919
  sourceLayer: layers.aids,
@@ -4415,7 +4921,7 @@ function compileNautical(request, context) {
4415
4921
  target: "nautical.lighthouses"
4416
4922
  });
4417
4923
  add2({
4418
- filter: ["all", points, ["==", ["get", fields.class], "port"]],
4924
+ filter: ["all", points, ofClass("port")],
4419
4925
  id: "tileflow-nautical-ports",
4420
4926
  localOrder: 606,
4421
4927
  sourceLayer: layers.navigationAreas,
@@ -4426,12 +4932,14 @@ function compileNautical(request, context) {
4426
4932
  }
4427
4933
  var points = ["==", ["geometry-type"], "Point"];
4428
4934
  var polygons = ["==", ["geometry-type"], "Polygon"];
4935
+ var lines = ["==", ["geometry-type"], "LineString"];
4936
+ var pointsOrPolygons = ["match", ["geometry-type"], ["Point", "Polygon"], true, false];
4937
+ var linesOrPolygons = ["match", ["geometry-type"], ["LineString", "Polygon"], true, false];
4429
4938
  function linesOf(classes) {
4430
- return [
4431
- "all",
4432
- ["==", ["geometry-type"], "LineString"],
4433
- ["match", ["get", fields.class], [...classes], true, false]
4434
- ];
4939
+ return ["all", lines, ["match", ["get", fields.class], [...classes], true, false]];
4940
+ }
4941
+ function listIncludes(field2, item) {
4942
+ return ["in", `,${item},`, ["concat", ",", ["to-string", ["coalesce", ["get", field2], ""]], ","]];
4435
4943
  }
4436
4944
  var fromFeatureMinZoom = [">=", ["zoom"], ["to-number", ["get", fields.minZoom], 0]];
4437
4945
  function appendNauticalLayer(contributions, options) {
@@ -4488,24 +4996,25 @@ function appendNauticalLayer(contributions, options) {
4488
4996
  if (point.visible === false) return;
4489
4997
  const isLabel = options.target.startsWith("nautical.labels.");
4490
4998
  if (!isLabel && point.marker && point.marker.visible !== false) {
4999
+ const marker = applyCircleStyle(
5000
+ base(
5001
+ `${options.id}-marker`,
5002
+ "circle",
5003
+ options.markerFilter ? ["all", options.filter, options.markerFilter] : options.filter
5004
+ ),
5005
+ mergeTileflowDesign(
5006
+ {
5007
+ ...point.maxZoom === void 0 ? {} : { maxZoom: point.maxZoom },
5008
+ ...point.minZoom === void 0 ? {} : { minZoom: point.minZoom }
5009
+ },
5010
+ point.marker
5011
+ )
5012
+ );
4491
5013
  contributions.push(
4492
5014
  nauticalContribution(
4493
5015
  `${options.target}.marker`,
4494
5016
  options.localOrder,
4495
- applyCircleStyle(
4496
- base(
4497
- `${options.id}-marker`,
4498
- "circle",
4499
- options.markerFilter ? ["all", options.filter, options.markerFilter] : options.filter
4500
- ),
4501
- mergeTileflowDesign(
4502
- {
4503
- ...point.maxZoom === void 0 ? {} : { maxZoom: point.maxZoom },
4504
- ...point.minZoom === void 0 ? {} : { minZoom: point.minZoom }
4505
- },
4506
- point.marker
4507
- )
4508
- ),
5017
+ options.flash ? flashing(marker, ["circle-opacity", "circle-stroke-opacity"]) : marker,
4509
5018
  "symbols"
4510
5019
  )
4511
5020
  );
@@ -4513,21 +5022,79 @@ function appendNauticalLayer(contributions, options) {
4513
5022
  const hasText = Boolean(point.text && point.text.visible !== false);
4514
5023
  const hasIcon = Boolean(point.icon && point.icon.visible !== false);
4515
5024
  if (!hasText && !hasIcon) return;
5025
+ const symbolFilter = [
5026
+ options.filter,
5027
+ ...options.symbolFilter ? [options.symbolFilter] : [],
5028
+ ...options.lines ? [["!=", ["geometry-type"], "LineString"]] : []
5029
+ ];
5030
+ const symbol = applySymbolStyle(
5031
+ base(
5032
+ options.id,
5033
+ "symbol",
5034
+ symbolFilter.length === 1 ? symbolFilter[0] : ["all", ...symbolFilter]
5035
+ ),
5036
+ withoutMarker(point)
5037
+ );
4516
5038
  contributions.push(
4517
5039
  nauticalContribution(
4518
5040
  isLabel ? options.target : `${options.target}.symbol`,
4519
5041
  options.localOrder + (isLabel ? 0 : 1),
4520
- applySymbolStyle(
4521
- base(
4522
- options.id,
4523
- "symbol",
4524
- options.symbolFilter ? ["all", options.filter, options.symbolFilter] : options.filter
4525
- ),
4526
- withoutMarker(point)
4527
- ),
5042
+ options.flash && hasIcon ? flashing(symbol, ["icon-opacity"]) : symbol,
4528
5043
  "symbols"
4529
5044
  )
4530
5045
  );
5046
+ if (options.lines) {
5047
+ contributions.push(
5048
+ nauticalContribution(
5049
+ `${options.target}.lines`,
5050
+ options.lines.localOrder,
5051
+ applySymbolStyle(
5052
+ base(`${options.id}-lines`, "symbol", [
5053
+ "all",
5054
+ options.filter,
5055
+ ...options.symbolFilter ? [options.symbolFilter] : [],
5056
+ lines
5057
+ ]),
5058
+ { ...withoutMarker(point), placement: options.lines.placement }
5059
+ ),
5060
+ "symbols"
5061
+ )
5062
+ );
5063
+ }
5064
+ }
5065
+ function flashing(layer, properties2) {
5066
+ const paint = { ...layer.paint ?? {} };
5067
+ const factor = [
5068
+ "interpolate",
5069
+ ["linear"],
5070
+ tileflowNauticalLightStateRead("intensity", 1),
5071
+ 0,
5072
+ tileflowNauticalLightEclipseOpacity,
5073
+ 1,
5074
+ 1
5075
+ ];
5076
+ for (const property of properties2) {
5077
+ const value = paint[property];
5078
+ if (property !== "circle-opacity" && property !== "icon-opacity" && value === void 0) {
5079
+ continue;
5080
+ }
5081
+ paint[property] = scaleOutputs(value ?? 1, factor);
5082
+ }
5083
+ return { ...layer, paint };
5084
+ }
5085
+ function scaleOutputs(value, factor) {
5086
+ if (Array.isArray(value) && isZoomCurve(value)) {
5087
+ const firstOutput = value[0] === "step" ? 2 : 4;
5088
+ return value.map(
5089
+ (entry, index) => index >= firstOutput && (index - firstOutput) % 2 === 0 ? ["*", entry, factor] : entry
5090
+ );
5091
+ }
5092
+ return ["*", value, factor];
5093
+ }
5094
+ function isZoomCurve(value) {
5095
+ const isZoom3 = (input) => Array.isArray(input) && input[0] === "zoom";
5096
+ if (value[0] === "step") return isZoom3(value[1]);
5097
+ return (value[0] === "interpolate" || value[0] === "interpolate-hcl" || value[0] === "interpolate-lab") && isZoom3(value[2]);
4531
5098
  }
4532
5099
  function withoutMarker(style) {
4533
5100
  const result = { ...style };
@@ -4564,8 +5131,96 @@ function themedImages(context) {
4564
5131
  ]
4565
5132
  ]);
4566
5133
  }
5134
+ const notice = images.notices;
5135
+ if (typeof notice === "string") {
5136
+ const variants = ["information", "prohibition", "recommendation", "regulation", "restriction"].flatMap((role) => [role, context.images[roles.notices[role]] ?? notice]);
5137
+ images.notices = expression(["match", ["get", fields.subclass], ...variants, notice]);
5138
+ }
5139
+ const facility = images.facilities;
5140
+ if (typeof facility === "string") {
5141
+ const cases = tileflowNauticalFacilityIcons.flatMap((category) => {
5142
+ const image = context.images[roles.facilities[category]];
5143
+ if (!image || image === facility) return [];
5144
+ const categories = category === "visitor-berth" ? ["visitor-berth", "visitors-mooring"] : [category];
5145
+ return [["any", ...categories.map((name) => listIncludes(fields.subclass, name))], image];
5146
+ });
5147
+ if (cases.length > 0) images.facilities = expression(["case", ...cases, facility]);
5148
+ }
5149
+ const harbour = images.harbours;
5150
+ const marina = context.images[roles.harbours.marina];
5151
+ if (typeof harbour === "string" && marina && marina !== harbour) {
5152
+ images.harbours = expression([
5153
+ "case",
5154
+ ["any", ...marinaSubclasses.map((name) => listIncludes(fields.subclass, name))],
5155
+ marina,
5156
+ harbour
5157
+ ]);
5158
+ }
4567
5159
  return images;
4568
5160
  }
5161
+ function bridgeClearance() {
5162
+ const metres = (field2) => ["concat", ["to-string", ["get", field2]], " m"];
5163
+ const closed = fields.verticalClearanceClosed;
5164
+ const open = fields.verticalClearanceOpen;
5165
+ return [
5166
+ "case",
5167
+ ["any", ["has", closed], ["has", open]],
5168
+ [
5169
+ "concat",
5170
+ ["case", ["has", closed], ["concat", "closed ", metres(closed)], ""],
5171
+ ["case", ["all", ["has", closed], ["has", open]], " \xB7 ", ""],
5172
+ ["case", ["has", open], ["concat", "open ", metres(open)], ""]
5173
+ ],
5174
+ ["has", fields.verticalClearance],
5175
+ ["concat", "clearance ", metres(fields.verticalClearance)],
5176
+ ""
5177
+ ];
5178
+ }
5179
+ function cableClearance() {
5180
+ return [
5181
+ "case",
5182
+ ["has", fields.verticalClearanceSafe],
5183
+ ["concat", "safe clearance ", ["to-string", ["get", fields.verticalClearanceSafe]], " m"],
5184
+ ["has", fields.verticalClearance],
5185
+ ["concat", "clearance ", ["to-string", ["get", fields.verticalClearance]], " m"],
5186
+ ""
5187
+ ];
5188
+ }
5189
+ function nthColour(field2, index) {
5190
+ const list = ["var", "nauticalColours"];
5191
+ const cases = [];
5192
+ for (let position = 1; position < maxAlternatingColours; position += 1) {
5193
+ cases.push(["==", ["var", "nauticalColourIndex"], position], nthName(list, position));
5194
+ }
5195
+ return [
5196
+ "let",
5197
+ "nauticalColours",
5198
+ ["to-string", ["coalesce", ["get", field2], ""]],
5199
+ "nauticalColourIndex",
5200
+ index,
5201
+ ["case", ...cases, leadingName(list)]
5202
+ ];
5203
+ }
5204
+ function nthName(list, position) {
5205
+ if (position === 0) return leadingName(list);
5206
+ const rest = `nauticalRest${position}`;
5207
+ return [
5208
+ "let",
5209
+ rest,
5210
+ [
5211
+ "let",
5212
+ "nauticalComma",
5213
+ ["index-of", ",", list],
5214
+ [
5215
+ "case",
5216
+ ["<", ["var", "nauticalComma"], 0],
5217
+ "",
5218
+ ["slice", list, ["+", ["var", "nauticalComma"], 1]]
5219
+ ]
5220
+ ],
5221
+ nthName(["var", rest], position - 1)
5222
+ ];
5223
+ }
4569
5224
  function firstColour(field2) {
4570
5225
  return [
4571
5226
  "let",
@@ -5431,6 +6086,7 @@ var visualNumberProperties = /* @__PURE__ */ new Set([
5431
6086
  "colorMix",
5432
6087
  "metallic",
5433
6088
  "roughness",
6089
+ "horizonBlend",
5434
6090
  "hue",
5435
6091
  "saturation",
5436
6092
  "lightness",
@@ -5458,6 +6114,7 @@ var visualNumberProperties = /* @__PURE__ */ new Set([
5458
6114
  "roundLimit",
5459
6115
  "size",
5460
6116
  "spacing",
6117
+ "starIntensity",
5461
6118
  "strokeOpacity",
5462
6119
  "strokeWidth",
5463
6120
  "textFitPadding",
@@ -5651,7 +6308,7 @@ function auditTileflowThemeValues(value, path = "value", context = inferAuditCon
5651
6308
  return diagnostics.sort(compareAuditDiagnostics);
5652
6309
  }
5653
6310
  function auditTileflowMapThemeValues(map) {
5654
- const modules = isRecord4(map.modules) ? map.modules : void 0;
6311
+ const modules = isRecord5(map.modules) ? map.modules : void 0;
5655
6312
  const moduleDiagnostics = modules ? Object.keys(modules).sort(compareCodeUnits2).flatMap(
5656
6313
  (owner) => auditTileflowThemeValues(modules[owner], `modules.${owner}`, {
5657
6314
  owner,
@@ -5754,7 +6411,7 @@ function isThemePrimitiveForCategory(value, category) {
5754
6411
  return category === "number" ? typeof value === "number" && Number.isFinite(value) || Array.isArray(value) && value.every((entry) => typeof entry === "number" && Number.isFinite(entry)) : typeof value === "string" && value.length > 0;
5755
6412
  }
5756
6413
  function isThemeValueCandidate(value) {
5757
- return isRecord4(value) && (value.kind === "theme-token" || value.kind === "theme-fixed" || value.kind === "theme-color");
6414
+ return isRecord5(value) && (value.kind === "theme-token" || value.kind === "theme-fixed" || value.kind === "theme-color");
5758
6415
  }
5759
6416
  function visitAuditableValue(value, path, category, state, intentRequired = false) {
5760
6417
  if (isThemeStructuralPath(path)) category = void 0;
@@ -5770,7 +6427,7 @@ function visitAuditableValue(value, path, category, state, intentRequired = fals
5770
6427
  visitAuditableArray(value, path, category, state, false, intentRequired);
5771
6428
  return;
5772
6429
  }
5773
- if (!isRecord4(value)) return;
6430
+ if (!isRecord5(value)) return;
5774
6431
  if (value.kind === "expression" && Array.isArray(value.value)) {
5775
6432
  visitAuditableArray(value.value, `${path}.value`, category, state, true, intentRequired);
5776
6433
  return;
@@ -6089,7 +6746,7 @@ var ThemeValueResolver = class {
6089
6746
  if (Array.isArray(value)) {
6090
6747
  return value.map((entry, index) => this.resolveTree(entry, `${path}[${index}]`, category));
6091
6748
  }
6092
- if (!isRecord4(value)) return value;
6749
+ if (!isRecord5(value)) return value;
6093
6750
  if (value.kind === "expression" && Array.isArray(value.value)) {
6094
6751
  return {
6095
6752
  ...value,
@@ -6403,7 +7060,7 @@ function assertThemeLighting(value, path, allowReset = false) {
6403
7060
  }
6404
7061
  }
6405
7062
  function assertThemeCategoryValue(value, expectedCategory, path) {
6406
- if (!isRecord4(value)) {
7063
+ if (!isRecord5(value)) {
6407
7064
  assertTokenPrimitive(expectedCategory, value, path);
6408
7065
  return;
6409
7066
  }
@@ -6755,11 +7412,11 @@ function cloneJson14(value) {
6755
7412
  if (serialized === void 0) throw new Error("Tileflow themes must be JSON-serializable.");
6756
7413
  return JSON.parse(serialized);
6757
7414
  }
6758
- function isRecord4(value) {
7415
+ function isRecord5(value) {
6759
7416
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
6760
7417
  }
6761
7418
  function isPlainRecord2(value) {
6762
- if (!isRecord4(value)) return false;
7419
+ if (!isRecord5(value)) return false;
6763
7420
  const prototype = Object.getPrototypeOf(value);
6764
7421
  return prototype === Object.prototype || prototype === null;
6765
7422
  }
@@ -7493,7 +8150,7 @@ function phaseVisibility(surfaces, phase, fields2) {
7493
8150
  function zoomLevels(values) {
7494
8151
  const levels = new Set(Array.from({ length: 10 }, (_, index) => index + 15));
7495
8152
  for (const value of values) {
7496
- if (!isZoomCurve(value)) continue;
8153
+ if (!isZoomCurve2(value)) continue;
7497
8154
  for (let index = 3; index < value.length - 1; index += 2) {
7498
8155
  const level = value[index];
7499
8156
  if (level < 15 || level > 24) continue;
@@ -7551,7 +8208,7 @@ function interpolationFactor2(interpolation, distance, span) {
7551
8208
  const logarithm = Math.log(interpolation[1]);
7552
8209
  return Math.expm1(logarithm * distance) / Math.expm1(logarithm * span);
7553
8210
  }
7554
- function isZoomCurve(value) {
8211
+ function isZoomCurve2(value) {
7555
8212
  return Array.isArray(value) && (value[0] === "interpolate" && isZoom(value[2]) || value[0] === "step" && isZoom(value[1]));
7556
8213
  }
7557
8214
  function isZoom(value) {
@@ -7560,7 +8217,7 @@ function isZoom(value) {
7560
8217
  function atZoom(value, level) {
7561
8218
  if (!Array.isArray(value) || value[0] === "literal") return value;
7562
8219
  if (isZoom(value)) return level;
7563
- if (!isZoomCurve(value)) return value.map((entry) => atZoom(entry, level));
8220
+ if (!isZoomCurve2(value)) return value.map((entry) => atZoom(entry, level));
7564
8221
  const stops = [];
7565
8222
  for (let index = 3; index < value.length - 1; index += 2) {
7566
8223
  stops.push({ level: value[index], value: value[index + 1] });
@@ -11067,21 +11724,21 @@ function bindSemanticValue(value, schema, sourceId) {
11067
11724
  }
11068
11725
  if (isDataSourceReference(value)) return sourceId;
11069
11726
  if (Array.isArray(value)) return value.map((item) => bindSemanticValue(item, schema, sourceId));
11070
- if (!isRecord5(value)) return value;
11727
+ if (!isRecord6(value)) return value;
11071
11728
  return Object.fromEntries(
11072
11729
  Object.entries(value).map(([key, item]) => [key, bindSemanticValue(item, schema, sourceId)])
11073
11730
  );
11074
11731
  }
11075
11732
  function isDataFieldReference2(value) {
11076
- return isRecord5(value) && value.kind === "tileflow-data-field" && typeof value.name === "string";
11733
+ return isRecord6(value) && value.kind === "tileflow-data-field" && typeof value.name === "string";
11077
11734
  }
11078
11735
  function isDataLayerReference(value) {
11079
- return isRecord5(value) && value.kind === "tileflow-data-layer" && typeof value.name === "string";
11736
+ return isRecord6(value) && value.kind === "tileflow-data-layer" && typeof value.name === "string";
11080
11737
  }
11081
11738
  function isDataSourceReference(value) {
11082
- return isRecord5(value) && value.kind === "tileflow-data-source" && value.name === "primary";
11739
+ return isRecord6(value) && value.kind === "tileflow-data-source" && value.name === "primary";
11083
11740
  }
11084
- function isRecord5(value) {
11741
+ function isRecord6(value) {
11085
11742
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
11086
11743
  }
11087
11744
  function compareCodeUnits5(left, right) {
@@ -11185,8 +11842,8 @@ function createTileflowLayerTemplateIR(layer, semanticKey) {
11185
11842
  renderer: type,
11186
11843
  ...filter === void 0 ? {} : { selector: cloneJson20(filter) },
11187
11844
  style: Object.freeze({
11188
- ...isRecord6(layout) ? { placement: Object.freeze(cloneJson20(layout)) } : {},
11189
- ...isRecord6(paint) ? { appearance: Object.freeze(cloneJson20(paint)) } : {}
11845
+ ...isRecord7(layout) ? { placement: Object.freeze(cloneJson20(layout)) } : {},
11846
+ ...isRecord7(paint) ? { appearance: Object.freeze(cloneJson20(paint)) } : {}
11190
11847
  })
11191
11848
  });
11192
11849
  }
@@ -11378,20 +12035,29 @@ var exactPhysicalLayerIds = Object.freeze({
11378
12035
  var nauticalPhysicalBases = Object.freeze({
11379
12036
  "nautical.aids": "tileflow-nautical-aids",
11380
12037
  "nautical.beacons": "tileflow-nautical-beacons",
12038
+ "nautical.berths": "tileflow-nautical-berths",
12039
+ "nautical.bridges": "tileflow-nautical-bridges",
11381
12040
  "nautical.buoys": "tileflow-nautical-buoys",
11382
12041
  "nautical.cables": "tileflow-nautical-cables",
11383
12042
  "nautical.coverage": "tileflow-nautical-coverage",
12043
+ "nautical.distanceMarks": "tileflow-nautical-distance-marks",
12044
+ "nautical.facilities": "tileflow-nautical-facilities",
12045
+ "nautical.harbourAreas": "tileflow-nautical-harbour-areas",
11384
12046
  "nautical.harbours": "tileflow-nautical-harbours",
11385
12047
  "nautical.hazardAreas": "tileflow-nautical-hazard-areas",
11386
12048
  "nautical.hazards": "tileflow-nautical-hazards",
12049
+ "nautical.labels.bridges": "tileflow-nautical-bridge-labels",
11387
12050
  "nautical.labels.coverage": "tileflow-nautical-coverage-labels",
11388
12051
  "nautical.labels.hazards": "tileflow-nautical-hazard-area-labels",
11389
12052
  "nautical.labels.navigationAreas": "tileflow-nautical-navigation-area-labels",
12053
+ "nautical.labels.overheadCables": "tileflow-nautical-overhead-cable-labels",
11390
12054
  "nautical.labels.reefs": "tileflow-nautical-reef-labels",
11391
12055
  "nautical.labels.wrecks": "tileflow-nautical-wreck-area-labels",
11392
12056
  "nautical.lighthouses": "tileflow-nautical-lighthouses",
11393
12057
  "nautical.lights": "tileflow-nautical-lights",
11394
12058
  "nautical.navigationAreas": "tileflow-nautical-navigation-areas",
12059
+ "nautical.notices": "tileflow-nautical-notices",
12060
+ "nautical.overheadCables": "tileflow-nautical-overhead-cables",
11395
12061
  "nautical.ports": "tileflow-nautical-ports",
11396
12062
  "nautical.reefs": "tileflow-nautical-reefs",
11397
12063
  "nautical.routes": "tileflow-nautical-routes",
@@ -11402,7 +12068,9 @@ var nauticalPhysicalBases = Object.freeze({
11402
12068
  function nauticalPhysicalLayerId(semanticKey) {
11403
12069
  const direct = nauticalPhysicalBases[semanticKey];
11404
12070
  if (direct) return direct;
11405
- const match3 = /^(nautical\.[A-Za-z0-9_.-]+)\.(fill|outline|marker|symbol)$/u.exec(semanticKey);
12071
+ const match3 = /^(nautical\.[A-Za-z0-9_.-]+)\.(fill|outline|marker|symbol|lines)$/u.exec(
12072
+ semanticKey
12073
+ );
11406
12074
  if (!match3) return void 0;
11407
12075
  const base = nauticalPhysicalBases[match3[1]];
11408
12076
  if (!base) return void 0;
@@ -11446,21 +12114,21 @@ function visitReferences(value, counts, visited = /* @__PURE__ */ new WeakSet())
11446
12114
  }
11447
12115
  }
11448
12116
  function isSemanticFieldReference(value) {
11449
- return isRecord6(value) && value.kind === "tileflow-data-field";
12117
+ return isRecord7(value) && value.kind === "tileflow-data-field";
11450
12118
  }
11451
12119
  function isSemanticLayerReference(value) {
11452
- return isRecord6(value) && value.kind === "tileflow-data-layer";
12120
+ return isRecord7(value) && value.kind === "tileflow-data-layer";
11453
12121
  }
11454
12122
  function isSemanticSourceReference(value) {
11455
- return isRecord6(value) && value.kind === "tileflow-data-source";
12123
+ return isRecord7(value) && value.kind === "tileflow-data-source";
11456
12124
  }
11457
12125
  function cloneJson20(value) {
11458
12126
  return JSON.parse(JSON.stringify(value));
11459
12127
  }
11460
12128
  function asRecord4(value) {
11461
- return isRecord6(value) ? value : {};
12129
+ return isRecord7(value) ? value : {};
11462
12130
  }
11463
- function isRecord6(value) {
12131
+ function isRecord7(value) {
11464
12132
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
11465
12133
  }
11466
12134
 
@@ -11512,7 +12180,7 @@ function validateTileflowRenderSelectorConstraints(input) {
11512
12180
  };
11513
12181
  while (pending.length > 0) {
11514
12182
  const current = pending.pop();
11515
- if (!isRecord7(current.selector) || typeof current.selector.kind !== "string") {
12183
+ if (!isRecord8(current.selector) || typeof current.selector.kind !== "string") {
11516
12184
  addIssue(
11517
12185
  "invalid-selector",
11518
12186
  "a render selector must be a semantic selector node",
@@ -11573,7 +12241,7 @@ function validateTileflowRenderSelectorConstraints(input) {
11573
12241
  }
11574
12242
  for (const [index, branch] of current.selector.branches.entries()) {
11575
12243
  const branchPath = [...branchesPath, index];
11576
- if (!isRecord7(branch)) {
12244
+ if (!isRecord8(branch)) {
11577
12245
  addIssue("invalid-selector", "match selector branch must be an object", branchPath);
11578
12246
  continue;
11579
12247
  }
@@ -11604,14 +12272,14 @@ function validateTileflowRenderSelectorConstraints(input) {
11604
12272
  for (let index = current.selector.stops.length - 1; index >= 0; index -= 1) {
11605
12273
  const stop = current.selector.stops[index];
11606
12274
  const stopPath = [...stopsPath, index];
11607
- if (!isRecord7(stop)) {
12275
+ if (!isRecord8(stop)) {
11608
12276
  addIssue("invalid-selector", "step selector stop must be an object", stopPath);
11609
12277
  continue;
11610
12278
  }
11611
12279
  pushChild(stop.selector, current.depth + 1, [...stopPath, "selector"]);
11612
12280
  }
11613
12281
  for (const [index, stop] of current.selector.stops.entries()) {
11614
- if (!isRecord7(stop)) continue;
12282
+ if (!isRecord8(stop)) continue;
11615
12283
  const zoom3 = stop.zoom;
11616
12284
  const zoomPath = [...stopsPath, index, "zoom"];
11617
12285
  if (typeof zoom3 !== "number" || !Number.isFinite(zoom3) || zoom3 < 0 || zoom3 > 24) {
@@ -11938,7 +12606,7 @@ function applyRendererStyle(layer, renderer, style) {
11938
12606
  };
11939
12607
  }
11940
12608
  function lowerSelector(selector, data) {
11941
- if (!isRecord7(selector) || typeof selector.kind !== "string") {
12609
+ if (!isRecord8(selector) || typeof selector.kind !== "string") {
11942
12610
  throw renderStackError(
11943
12611
  "invalid-selector",
11944
12612
  "A render selector must be a semantic selector node."
@@ -12141,7 +12809,7 @@ function requireRenderer(value) {
12141
12809
  function requirePlacement(value) {
12142
12810
  if (typeof value === "string") {
12143
12811
  if (tileflowBandPlacements.includes(value)) return;
12144
- } else if (isRecord7(value)) {
12812
+ } else if (isRecord8(value)) {
12145
12813
  const keys = Object.keys(value);
12146
12814
  if (keys.length === 1 && (keys[0] === "above" || keys[0] === "below")) {
12147
12815
  requirePortableTarget(value[keys[0]], "render placement target");
@@ -12168,7 +12836,7 @@ function validateRefinementShape(operation) {
12168
12836
  validateStyle(operation.renderer, operation.style);
12169
12837
  }
12170
12838
  function validateStyle(renderer, style) {
12171
- if (!isRecord7(style)) {
12839
+ if (!isRecord8(style)) {
12172
12840
  throw renderStackError(
12173
12841
  "invalid-style",
12174
12842
  `A ${renderer} render operation requires a style object.`
@@ -12270,7 +12938,7 @@ function normalizeRequirements(requirements) {
12270
12938
  return [...requirements];
12271
12939
  }
12272
12940
  function requireRenderStackEntries(stack) {
12273
- if (!isRecord7(stack)) {
12941
+ if (!isRecord8(stack)) {
12274
12942
  throw renderStackError("invalid-stack", "A render stack must be a named operation record.");
12275
12943
  }
12276
12944
  const entries = Object.entries(stack);
@@ -12420,9 +13088,9 @@ function cloneJson21(value) {
12420
13088
  return JSON.parse(JSON.stringify(value));
12421
13089
  }
12422
13090
  function asRecord5(value) {
12423
- return isRecord7(value) ? value : {};
13091
+ return isRecord8(value) ? value : {};
12424
13092
  }
12425
- function isRecord7(value) {
13093
+ function isRecord8(value) {
12426
13094
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
12427
13095
  }
12428
13096
 
@@ -12630,6 +13298,9 @@ function createTileflowTimeOfDaySchema(operations, themeName) {
12630
13298
  );
12631
13299
  }
12632
13300
 
13301
+ // src/camera-limits.ts
13302
+ var tileflowMaxPitch = 85;
13303
+
12633
13304
  // src/resolved-map-schema.ts
12634
13305
  var TileflowResolvedMapValidationError = class extends Error {
12635
13306
  code = "CONFIG_INVALID";
@@ -13788,24 +14459,34 @@ var poiModuleSchema = z5.object({
13788
14459
  var nauticalModuleSchema = z5.object({
13789
14460
  type: z5.literal("nautical"),
13790
14461
  aids: markerSymbolStyleSchema.optional(),
14462
+ animateLights: z5.boolean().optional(),
13791
14463
  beacons: markerSymbolStyleSchema.optional(),
14464
+ berths: areaStyleSchema.optional(),
14465
+ bridges: areaStyleSchema.optional(),
13792
14466
  buoys: markerSymbolStyleSchema.optional(),
13793
14467
  cables: lineStyleSchema.optional(),
13794
14468
  coverage: areaStyleSchema.optional(),
14469
+ distanceMarks: markerSymbolStyleSchema.optional(),
13795
14470
  enabled: z5.boolean().optional(),
14471
+ facilities: markerSymbolStyleSchema.optional(),
14472
+ harbourAreas: areaStyleSchema.optional(),
13796
14473
  harbours: markerSymbolStyleSchema.optional(),
13797
14474
  hazardAreas: areaStyleSchema.optional(),
13798
14475
  hazards: markerSymbolStyleSchema.optional(),
13799
14476
  labels: z5.object({
14477
+ bridges: symbolStyleSchema.optional(),
13800
14478
  coverage: symbolStyleSchema.optional(),
13801
14479
  hazards: symbolStyleSchema.optional(),
13802
14480
  navigationAreas: symbolStyleSchema.optional(),
14481
+ overheadCables: symbolStyleSchema.optional(),
13803
14482
  reefs: symbolStyleSchema.optional(),
13804
14483
  wrecks: symbolStyleSchema.optional()
13805
14484
  }).strict().optional(),
13806
14485
  lighthouses: markerSymbolStyleSchema.optional(),
13807
14486
  lights: markerSymbolStyleSchema.optional(),
13808
14487
  navigationAreas: areaStyleSchema.optional(),
14488
+ notices: markerSymbolStyleSchema.optional(),
14489
+ overheadCables: lineStyleSchema.optional(),
13809
14490
  ports: markerSymbolStyleSchema.optional(),
13810
14491
  reefs: areaStyleSchema.optional(),
13811
14492
  renderStack: renderStackSchema.optional(),
@@ -14338,7 +15019,7 @@ var terrainContoursSchema = z5.object({
14338
15019
  demUrl: z5.string().min(1).max(2048).refine(
14339
15020
  isSafeTileflowDemUrlTemplate,
14340
15021
  "Expected a safe HTTP(S) DEM URL template containing {z}, {x}, and {y}"
14341
- ),
15022
+ ).optional(),
14342
15023
  index: terrainContourLineSchema.optional(),
14343
15024
  labels: terrainContourLabelSchema.optional(),
14344
15025
  maxZoom: z5.number().int().min(0).max(24).optional(),
@@ -14462,10 +15143,25 @@ var atmosphereSchema = z5.union([
14462
15143
  starParallax: z5.number().finite().min(0).max(1).optional()
14463
15144
  }).strict()
14464
15145
  ]);
15146
+ var skyNumberSchema = z5.union([
15147
+ z5.number().finite().min(0).max(1),
15148
+ fixedNumberSchema.extend({ value: z5.number().finite().min(0).max(1) }),
15149
+ themeNumberReferenceSchema
15150
+ ]);
15151
+ var skySchema = z5.union([
15152
+ z5.boolean(),
15153
+ z5.object({
15154
+ color: atmosphereColorSchema.optional(),
15155
+ horizonColor: atmosphereColorSchema.optional(),
15156
+ horizonBlend: skyNumberSchema.optional(),
15157
+ glowColor: atmosphereColorSchema.optional(),
15158
+ starIntensity: skyNumberSchema.optional()
15159
+ }).strict()
15160
+ ]);
14465
15161
  var viewSchema = z5.object({
14466
15162
  bearing: z5.number().finite().min(-180).max(180).optional(),
14467
15163
  center: z5.tuple([z5.number().finite().min(-180).max(180), z5.number().finite().min(-90).max(90)]).optional(),
14468
- pitch: z5.number().finite().min(0).max(85).optional(),
15164
+ pitch: z5.number().finite().min(0).max(tileflowMaxPitch).optional(),
14469
15165
  zoom: zoomNumberSchema.optional()
14470
15166
  }).strict();
14471
15167
  var themesSchema = z5.record(tileflowThemeNameSchema, tileflowThemeSchema).superRefine((themes, context) => {
@@ -14508,6 +15204,7 @@ var resolvedTileflowMapSchema = z5.object({
14508
15204
  name: z5.string().trim().min(1),
14509
15205
  overlays: overlaysSchema.optional(),
14510
15206
  projection: z5.enum(["globe", "mercator"]).optional(),
15207
+ sky: skySchema.optional(),
14511
15208
  sources: hostedSourcesSchema.optional(),
14512
15209
  systemThemes: systemThemesSchema.optional(),
14513
15210
  terrain: terrainSchema.optional(),
@@ -14795,7 +15492,7 @@ function resolveTerrain(terrain, apiBaseUrl) {
14795
15492
  const mode = terrainConfig.mode ?? "hillshade";
14796
15493
  const encoding = terrainConfig.encoding ?? "terrarium";
14797
15494
  const raster = mode === "none" ? void 0 : resolveRaster(terrainConfig, apiBaseUrl, encoding);
14798
- const contours = terrainConfig.contours ? resolveContours(terrainConfig.contours, encoding, terrainConfig.attribution) : void 0;
15495
+ const contours = terrainConfig.contours ? resolveContours(terrainConfig.contours, encoding, terrainConfig.attribution, apiBaseUrl) : void 0;
14799
15496
  if (!raster && !contours) return void 0;
14800
15497
  return {
14801
15498
  ...contours ? { contours } : {},
@@ -14927,7 +15624,7 @@ function resolveRaster(terrain, apiBaseUrl, encoding) {
14927
15624
  }
14928
15625
  };
14929
15626
  }
14930
- function resolveContours(contours, encoding, attribution) {
15627
+ function resolveContours(contours, encoding, attribution, apiBaseUrl) {
14931
15628
  const thresholdZooms = Object.keys(contours.thresholds).map(Number);
14932
15629
  const minimumZoom2 = contours.minZoom ?? Math.min(...thresholdZooms);
14933
15630
  const maximumZoom2 = contours.maxZoom ?? Math.max(...thresholdZooms);
@@ -14935,7 +15632,7 @@ function resolveContours(contours, encoding, attribution) {
14935
15632
  const tiles = [
14936
15633
  createTileflowContourProtocolUrl({
14937
15634
  demMaxzoom: contours.demMaxZoom,
14938
- demUrl: contours.demUrl,
15635
+ demUrl: contours.demUrl ?? `${apiBaseUrl}/tiles/terrain/{z}/{x}/{y}.webp`,
14939
15636
  encoding,
14940
15637
  maxzoom: maximumZoom2,
14941
15638
  multiplier: contours.multiplier ?? 1,
@@ -15536,11 +16233,11 @@ function isTileflowPoiCategory(value) {
15536
16233
  return typeof value === "string" && tileflowPoiCategories.includes(value);
15537
16234
  }
15538
16235
  function semanticTarget(layer) {
15539
- if (!isRecord8(layer.metadata)) return void 0;
16236
+ if (!isRecord9(layer.metadata)) return void 0;
15540
16237
  const value = layer.metadata[tileflowCompilerMetadataKeys.target];
15541
16238
  return typeof value === "string" ? value : void 0;
15542
16239
  }
15543
- function isRecord8(value) {
16240
+ function isRecord9(value) {
15544
16241
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
15545
16242
  }
15546
16243
  function isSafeArtifactName(value) {
@@ -16438,7 +17135,7 @@ var colourSources = [
16438
17135
  ["tileflow-buildings-fill", "fill-color"]
16439
17136
  ];
16440
17137
  function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, tileflowLandmarks) {
16441
- const config = isRecord9(module) ? module : {};
17138
+ const config = isRecord10(module) ? module : {};
16442
17139
  if (config.enabled === false) return void 0;
16443
17140
  const style = config.threeDimensional ?? {};
16444
17141
  const defaults4 = tileflowDefaultBuildings3DDesign;
@@ -16471,7 +17168,7 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
16471
17168
  edgeRadius: number(style.edgeRadius, defaults4.edgeRadius),
16472
17169
  ...typeof edgeColor === "string" ? { edgeColor } : {},
16473
17170
  nightLight: number(style.nightLight, 0),
16474
- ...isRecord9(style.floodLight) ? {
17171
+ ...isRecord10(style.floodLight) ? {
16475
17172
  floodLight: readBuildingFloodLight({
16476
17173
  ...style.floodLight,
16477
17174
  intensity: number(style.floodLight.intensity, 0),
@@ -16479,10 +17176,10 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
16479
17176
  color: style.floodLight.color === void 0 ? void 0 : toMapLibreStyleValue(style.floodLight.color)
16480
17177
  })
16481
17178
  } : {},
16482
- ...isRecord9(style.modelLighting) ? {
17179
+ ...isRecord10(style.modelLighting) ? {
16483
17180
  modelLighting: readModelRoleLighting(
16484
17181
  Object.fromEntries(
16485
- Object.entries(style.modelLighting).filter(([, entry]) => isRecord9(entry)).map(([role, entry]) => {
17182
+ Object.entries(style.modelLighting).filter(([, entry]) => isRecord10(entry)).map(([role, entry]) => {
16486
17183
  const own = entry;
16487
17184
  return [
16488
17185
  role,
@@ -16493,7 +17190,7 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
16493
17190
  ["emissiveVariation", "colorMix", "metallic", "roughness"].filter((key) => own[key] !== void 0).map((key) => [key, number(own[key], 0)])
16494
17191
  ),
16495
17192
  ...Array.isArray(own.emissiveHeight) ? { emissiveHeight: own.emissiveHeight.map((v) => number(v, 0)) } : {},
16496
- ...isRecord9(own.colorVariation) ? {
17193
+ ...isRecord10(own.colorVariation) ? {
16497
17194
  colorVariation: {
16498
17195
  ...own.colorVariation,
16499
17196
  ...Object.fromEntries(
@@ -16526,7 +17223,7 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
16526
17223
  }
16527
17224
  function landmarksOf(value, tileflowLandmarks) {
16528
17225
  if (value === false) return {};
16529
- if (isRecord9(value) && typeof value.url === "string") return { landmarks: { url: value.url } };
17226
+ if (isRecord10(value) && typeof value.url === "string") return { landmarks: { url: value.url } };
16530
17227
  return tileflowLandmarks ? { landmarks: { url: tileflowLandmarks } } : {};
16531
17228
  }
16532
17229
  function placedModels(value, served) {
@@ -16553,7 +17250,7 @@ function placedModels(value, served) {
16553
17250
  };
16554
17251
  }
16555
17252
  function modelColours(value) {
16556
- if (!isRecord9(value)) return {};
17253
+ if (!isRecord10(value)) return {};
16557
17254
  const colours = {};
16558
17255
  for (const [part, colour] of Object.entries(value)) {
16559
17256
  const resolved = colour === void 0 ? void 0 : toMapLibreStyleValue(colour);
@@ -16562,7 +17259,7 @@ function modelColours(value) {
16562
17259
  return Object.keys(colours).length > 0 ? { modelColors: colours } : {};
16563
17260
  }
16564
17261
  function tileflowStreetLightsDesignOf(module, context) {
16565
- const config = isRecord9(module) ? module : {};
17262
+ const config = isRecord10(module) ? module : {};
16566
17263
  const layers2 = context.data.schema.layers;
16567
17264
  const style = config.streetLights ?? {};
16568
17265
  if (config.enabled === false || style.enabled !== true || !layers2.streetFurniture)
@@ -16582,7 +17279,7 @@ function tileflowStreetLightsDesignOf(module, context) {
16582
17279
  intensity: typeof intensity === "number" ? intensity : 1
16583
17280
  };
16584
17281
  }
16585
- function isRecord9(value) {
17282
+ function isRecord10(value) {
16586
17283
  return typeof value === "object" && value !== null && !Array.isArray(value);
16587
17284
  }
16588
17285
 
@@ -16754,7 +17451,7 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
16754
17451
  )
16755
17452
  );
16756
17453
  const renderStackModules = Object.values(modules).filter(
16757
- (module) => isRecord10(module) && module.enabled !== false && "renderStack" in module && isRecord10(module.renderStack)
17454
+ (module) => isRecord11(module) && module.enabled !== false && "renderStack" in module && isRecord11(module.renderStack)
16758
17455
  );
16759
17456
  const renderOperations = runCompilationPhase(
16760
17457
  "render-stack",
@@ -17000,9 +17697,15 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
17000
17697
  layer.layout = { ...layer.layout, visibility: "none" };
17001
17698
  }
17002
17699
  const atmosphere = resolveTileflowAtmosphere(config.atmosphere);
17700
+ const sky = resolveTileflowSky(config.sky);
17003
17701
  const notices = compiledNotices(layers2, marine);
17702
+ const nauticalLights = declareTileflowNauticalLights(
17703
+ layers2,
17704
+ marine?.nautical?.sourceId,
17705
+ tileflowNauticalV1Schema.layers.lights
17706
+ );
17004
17707
  const style = {
17005
- ...atmosphere ? { sky: compileTileflowSky(atmosphere) } : {},
17708
+ ...sky ? { sky: compileTileflowStreetSky(sky) } : atmosphere ? { sky: compileTileflowSky(atmosphere) } : {},
17006
17709
  version: 8,
17007
17710
  name: config.name ?? "Streets",
17008
17711
  ...glyphs ? { glyphs } : {},
@@ -17021,6 +17724,7 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
17021
17724
  ...mapMetadata.lineage && mapMetadata.lineage.length > 1 ? { "tileflow:extends": mapMetadata.lineage.slice(1) } : {}
17022
17725
  } : {},
17023
17726
  ...atmosphere ? { [tileflowAtmosphereMetadataKey]: { version: 1, ...atmosphere } } : {},
17727
+ ...sky ? { [tileflowSkyMetadataKey]: skyMetadata(input, selected.name, sky) } : {},
17024
17728
  "tileflow:theme": selected.name,
17025
17729
  "tileflow:colorScheme": resolvedTheme.colorScheme,
17026
17730
  [tileflowRouteMetadataKey]: tileflowRouteMetadata(resolveTileflowRouteInks(resolvedTheme)),
@@ -17049,9 +17753,10 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
17049
17753
  "tileflow:sourceRequirements": sourceRequirements,
17050
17754
  ...interactionManifest ? { [tileflowInteractionManifestMetadataKey]: interactionManifest } : {},
17051
17755
  [tileflowOverlayPlacementManifestMetadataKey]: overlayPlacementManifest,
17052
- "tileflow:modules": Object.entries(modules).filter(([, module]) => !isRecord10(module) || module.enabled !== false).map(([name]) => name).sort(),
17756
+ "tileflow:modules": Object.entries(modules).filter(([, module]) => !isRecord11(module) || module.enabled !== false).map(([name]) => name).sort(),
17053
17757
  ...config.view ? { "tileflow:view": config.view } : {},
17054
- ...notices.length > 0 ? { [tileflowNoticesMetadataKey]: notices } : {}
17758
+ ...notices.length > 0 ? { [tileflowNoticesMetadataKey]: notices } : {},
17759
+ ...nauticalLights ? { [tileflowNauticalLightsMetadataKey]: nauticalLights } : {}
17055
17760
  }
17056
17761
  };
17057
17762
  const report = {
@@ -17111,7 +17816,7 @@ function finalizeTileflowLayers(input) {
17111
17816
  if (typeof minimum2 === "number" && typeof maximum2 === "number" && minimum2 > maximum2) {
17112
17817
  throw new Error(`Compiled Tileflow layer ${id2} requires minzoom <= maxzoom.`);
17113
17818
  }
17114
- const metadata = isRecord10(layer.metadata) ? { ...layer.metadata } : void 0;
17819
+ const metadata = isRecord11(layer.metadata) ? { ...layer.metadata } : void 0;
17115
17820
  if (metadata) {
17116
17821
  const owner = metadata[tileflowCompilerMetadataKeys.owner];
17117
17822
  delete metadata[tileflowCompilerProvenanceMetadataKey];
@@ -17145,7 +17850,7 @@ function assertMapLibreStyle(style, mapId) {
17145
17850
  }
17146
17851
  );
17147
17852
  }
17148
- function isRecord10(value) {
17853
+ function isRecord11(value) {
17149
17854
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
17150
17855
  }
17151
17856
  function compiledNotices(layers2, marine) {
@@ -17190,8 +17895,8 @@ function assertPreparedIconReferences(style, config, prepared) {
17190
17895
  function collectStyleImageReferences(style) {
17191
17896
  const result = /* @__PURE__ */ new Set();
17192
17897
  for (const layer of style.layers) {
17193
- const layout = isRecord10(layer.layout) ? layer.layout : {};
17194
- const paint = isRecord10(layer.paint) ? layer.paint : {};
17898
+ const layout = isRecord11(layer.layout) ? layer.layout : {};
17899
+ const paint = isRecord11(layer.paint) ? layer.paint : {};
17195
17900
  const layerId = typeof layer.id === "string" ? layer.id : "<unknown>";
17196
17901
  for (const [property, value] of [
17197
17902
  ["icon-image", layout["icon-image"]],
@@ -17280,7 +17985,7 @@ function assertGlyphFontStacks(style, config) {
17280
17985
  const declared = new Set(config.glyphs.fontStacks);
17281
17986
  const missing = /* @__PURE__ */ new Set();
17282
17987
  for (const layer of style.layers) {
17283
- const layout = isRecord10(layer.layout) ? layer.layout : void 0;
17988
+ const layout = isRecord11(layer.layout) ? layer.layout : void 0;
17284
17989
  const font = layout?.["text-font"];
17285
17990
  if (!Array.isArray(font) || !font.every((entry) => typeof entry === "string")) continue;
17286
17991
  const stack = font.join(",");
@@ -17296,7 +18001,7 @@ function assertGlyphFontStacks(style, config) {
17296
18001
  function assertTextAssets(style, config) {
17297
18002
  const textLayers = [];
17298
18003
  for (const layer of style.layers) {
17299
- const layout = isRecord10(layer.layout) ? layer.layout : void 0;
18004
+ const layout = isRecord11(layer.layout) ? layer.layout : void 0;
17300
18005
  if (layout?.["text-field"] === void 0) continue;
17301
18006
  textLayers.push(typeof layer.id === "string" ? layer.id : "<unknown>");
17302
18007
  const font = layout["text-font"];
@@ -17309,7 +18014,7 @@ function assertTextAssets(style, config) {
17309
18014
  if (textLayers.length === 0) return;
17310
18015
  if (config.fonts !== void 0) {
17311
18016
  const backed = style.layers.find((layer) => {
17312
- const font = isRecord10(layer.layout) ? layer.layout["text-font"] : void 0;
18017
+ const font = isRecord11(layer.layout) ? layer.layout["text-font"] : void 0;
17313
18018
  return Array.isArray(font) && font.length === 1 && typeof font[0] === "string" && parseTileflowLineBackgroundFontStack(font[0]) !== void 0;
17314
18019
  });
17315
18020
  if (backed) {
@@ -17335,7 +18040,7 @@ function runCompilationPhase(phase, operation) {
17335
18040
  }
17336
18041
  }
17337
18042
  function withCompilationPhase(error, phase) {
17338
- const record4 = isRecord10(error) ? error : {};
18043
+ const record4 = isRecord11(error) ? error : {};
17339
18044
  if (typeof record4.phase === "string") return error;
17340
18045
  if (error instanceof Error && Object.isExtensible(error)) {
17341
18046
  Object.defineProperty(error, "phase", { configurable: true, enumerable: true, value: phase });
@@ -17366,6 +18071,35 @@ function compareCodeUnits7(left, right) {
17366
18071
  function semanticReportKey(...parts) {
17367
18072
  return parts.join("\0");
17368
18073
  }
18074
+ function skyMetadata(input, themeName, sky) {
18075
+ const family = Object.values(input.timeOfDay ?? {}).find(
18076
+ (moments) => Object.values(moments).includes(themeName)
18077
+ );
18078
+ const colours = (name) => {
18079
+ const theme = name === void 0 ? void 0 : input.themes[name];
18080
+ if (!theme) return void 0;
18081
+ const resolved = resolveTileflowSky(
18082
+ resolveThemeValues({ sky: input.sky }, theme, `map.${input.id}.themes.${name}`).sky
18083
+ );
18084
+ return resolved ? {
18085
+ color: resolved.color,
18086
+ horizonColor: resolved.horizonColor,
18087
+ horizonBlend: resolved.horizonBlend,
18088
+ glowColor: resolved.glowColor
18089
+ } : void 0;
18090
+ };
18091
+ const day = colours(family?.day);
18092
+ const night = colours(family?.night);
18093
+ const dawn = colours(family?.dawn);
18094
+ const dusk = colours(family?.dusk);
18095
+ const timesOfDay = day && night && (dawn ?? dusk) ? { day, night, dawn: dawn ?? dusk, dusk: dusk ?? dawn } : void 0;
18096
+ return {
18097
+ version: 1,
18098
+ starIntensity: sky.starIntensity,
18099
+ glowColor: sky.glowColor,
18100
+ ...timesOfDay ? { timesOfDay } : {}
18101
+ };
18102
+ }
17369
18103
 
17370
18104
  // src/manifest-types.ts
17371
18105
  var tileflowRuntimeManifestVersion = 1;
@@ -17378,7 +18112,7 @@ function createTileflowRuntimeViewSchema(operations) {
17378
18112
  center: operations.optional(
17379
18113
  operations.tuplePair(operations.number(-180, 180), operations.number(-90, 90))
17380
18114
  ),
17381
- pitch: operations.optional(operations.number(0, 85)),
18115
+ pitch: operations.optional(operations.number(0, tileflowMaxPitch)),
17382
18116
  zoom: operations.optional(operations.number(0, 24))
17383
18117
  });
17384
18118
  }
@@ -17652,7 +18386,7 @@ var tileflowCaptureSceneLimits = Object.freeze({
17652
18386
  boundsPadding: { maximum: 1024, minimum: 0 },
17653
18387
  captureIdLength: tileflowPortableIdMaximumLength,
17654
18388
  maximumPhysicalPixels: 16777216,
17655
- pitch: { maximum: 85, minimum: 0 },
18389
+ pitch: { maximum: tileflowMaxPitch, minimum: 0 },
17656
18390
  selectorLength: 256,
17657
18391
  viewport: { maximum: 4096, minimum: 64 },
17658
18392
  zoom: { maximum: 24, minimum: 0 }
@@ -17905,6 +18639,7 @@ var tileflowMapMergeStrategies = {
17905
18639
  projection: "atomic",
17906
18640
  scenes: "leaf",
17907
18641
  shots: "leaf",
18642
+ sky: "atomic",
17908
18643
  sources: "keyed-resources",
17909
18644
  systemThemes: "atomic",
17910
18645
  terrain: "atomic",
@@ -18251,11 +18986,11 @@ function cloneDesign(value) {
18251
18986
  function describeMap(value) {
18252
18987
  return typeof value.id === "string" && value.id ? value.id : "<anonymous>";
18253
18988
  }
18254
- function isRecord11(value) {
18989
+ function isRecord12(value) {
18255
18990
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
18256
18991
  }
18257
18992
  function isPlainRecord4(value) {
18258
- if (!isRecord11(value)) return false;
18993
+ if (!isRecord12(value)) return false;
18259
18994
  const prototype = Object.getPrototypeOf(value);
18260
18995
  return prototype === Object.prototype || prototype === null;
18261
18996
  }
@@ -19089,6 +19824,8 @@ var tileflowNativeStyleTransformationSchema = strictNativeObject({
19089
19824
  inputLayers: layerCountSchema,
19090
19825
  outputLayers: layerCountSchema,
19091
19826
  projection: z13.enum(["none", "mercator-to-implicit", "globe-to-mercator"]),
19827
+ /** Present when preparation removed a browser sky, which Native does not draw. */
19828
+ sky: z13.literal("removed").optional(),
19092
19829
  layers: z13.array(layerTransformationSchema).max(tileflowNativeProfileLimits.maximumLayers),
19093
19830
  symbolOverlap: z13.array(symbolOverlapTransformationSchema).max(tileflowNativeProfileLimits.maximumLayers)
19094
19831
  }).superRefine((style, context) => {
@@ -28221,7 +28958,7 @@ var viewSchema2 = z15.object({
28221
28958
  center: z15.tuple([z15.number().min(-180).max(180), z15.number().min(-90).max(90)]).optional(),
28222
28959
  zoom: z15.number().min(0).max(24).optional(),
28223
28960
  bearing: z15.number().finite().optional(),
28224
- pitch: z15.number().min(0).max(85).optional()
28961
+ pitch: z15.number().min(0).max(tileflowMaxPitch).optional()
28225
28962
  }).strict();
28226
28963
  var tileflowRendererDeploymentArtifactSchema = z15.object({
28227
28964
  kind: z15.literal("tileflow-map-deployment"),