@chrtco/sdk 0.10.45 → 0.10.47

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.cjs CHANGED
@@ -57,6 +57,7 @@ __export(index_exports, {
57
57
  ChrtError: () => ChrtError,
58
58
  DEFAULT_CLICK_TOLERANCE: () => DEFAULT_CLICK_TOLERANCE,
59
59
  DEFAULT_CUSTOM_STYLE_CONFIG: () => DEFAULT_CUSTOM_STYLE_CONFIG,
60
+ DEFAULT_DEPTH_LABEL_SIZE: () => DEFAULT_DEPTH_LABEL_SIZE,
60
61
  DEFAULT_INSPECT_CLASS_RANK: () => DEFAULT_INSPECT_CLASS_RANK,
61
62
  DEFAULT_LABEL_SETTINGS: () => DEFAULT_LABEL_SETTINGS,
62
63
  DEFAULT_NAV_AID_SETTINGS: () => DEFAULT_NAV_AID_SETTINGS,
@@ -68,7 +69,14 @@ __export(index_exports, {
68
69
  DEFAULT_WATER_BODY_SETTINGS: () => DEFAULT_WATER_BODY_SETTINGS,
69
70
  DEPARE_FILL_BRANCH_TOKENS: () => DEPARE_FILL_BRANCH_TOKENS,
70
71
  DEPARE_FILL_FALLBACK_TOKEN: () => DEPARE_FILL_FALLBACK_TOKEN,
72
+ DEPTH_LABEL_CLASSES: () => DEPTH_LABEL_CLASSES,
73
+ DEPTH_LABEL_SIZES: () => DEPTH_LABEL_SIZES,
71
74
  DRAPEABLE_LAYER_TYPES: () => DRAPEABLE_LAYER_TYPES,
75
+ EINK_COLOR_TABLES: () => EINK_COLOR_TABLES,
76
+ EINK_DANGER_PATTERN: () => EINK_DANGER_PATTERN,
77
+ EINK_DOTS_PATTERN: () => EINK_DOTS_PATTERN,
78
+ EINK_HATCH_PATTERN: () => EINK_HATCH_PATTERN,
79
+ EINK_MONO_GREYS: () => EINK_MONO_GREYS,
72
80
  INSPECT_CLASS_RANK: () => INSPECT_CLASS_RANK,
73
81
  INSPECT_RANK_DEVIATIONS: () => INSPECT_RANK_DEVIATIONS,
74
82
  InteractClient: () => InteractClient,
@@ -84,6 +92,7 @@ __export(index_exports, {
84
92
  LAND_ATTRIBUTION: () => LAND_ATTRIBUTION,
85
93
  LAND_BUILD_MAXZOOM: () => LAND_BUILD_MAXZOOM,
86
94
  LAND_REGION: () => LAND_REGION,
95
+ LARGE_SOUNDING_PADDING_PX: () => LARGE_SOUNDING_PADDING_PX,
87
96
  NAUTICAL_SYMBOLS: () => NAUTICAL_SYMBOLS,
88
97
  NAV_AID_LAYER_GROUPS: () => NAV_AID_LAYER_GROUPS,
89
98
  NAV_AID_QUERY_LAYERS: () => NAV_AID_QUERY_LAYERS,
@@ -120,6 +129,9 @@ __export(index_exports, {
120
129
  S52_DAY: () => S52_DAY,
121
130
  S52_DRAW_ORDER: () => S52_DRAW_ORDER,
122
131
  S52_DUSK: () => S52_DUSK,
132
+ S52_EINK_COLOR: () => S52_EINK_COLOR,
133
+ S52_EINK_MONO: () => S52_EINK_MONO,
134
+ S52_EINK_NIGHT: () => S52_EINK_NIGHT,
123
135
  S52_LOOKUP: () => S52_LOOKUP,
124
136
  S52_NIGHT: () => S52_NIGHT,
125
137
  S52_PATTERN_SPRITE_PREFIX: () => S52_PATTERN_SPRITE_PREFIX,
@@ -138,6 +150,8 @@ __export(index_exports, {
138
150
  applyChrtSymbolSet: () => applyChrtSymbolSet,
139
151
  applyColorTheme: () => applyColorTheme,
140
152
  applyCustomStyleSettings: () => applyCustomStyleSettings,
153
+ applyDepthLabelSize: () => applyDepthLabelSize,
154
+ applyEinkPortrayal: () => applyEinkPortrayal,
141
155
  applyRasterSymbolSet: () => applyRasterSymbolSet,
142
156
  applyS101SymbolSet: () => applyS101SymbolSet,
143
157
  atonIconExpression: () => atonIconExpression,
@@ -217,7 +231,10 @@ __export(index_exports, {
217
231
  iconExpressionForFamily: () => iconExpressionForFamily,
218
232
  isDepthAreaClassLayerId: () => isDepthAreaClassLayerId,
219
233
  isDrapeable: () => isDrapeable,
234
+ isEinkColorTable: () => isEinkColorTable,
235
+ isEinkGreyTable: () => isEinkGreyTable,
220
236
  isLabelLayerVisible: () => isLabelLayerVisible,
237
+ isMapLive: () => isMapLive,
221
238
  isNavAidLayer: () => isNavAidLayer,
222
239
  isNavAidLayerVisible: () => isNavAidLayerVisible,
223
240
  isSoundingLayerVisible: () => isSoundingLayerVisible,
@@ -235,6 +252,7 @@ __export(index_exports, {
235
252
  normalizeTheme: () => normalizeTheme,
236
253
  opaqueSplitBaseOf: () => opaqueSplitBaseOf,
237
254
  paginate: () => paginate,
255
+ parseDepthLabelSize: () => parseDepthLabelSize,
238
256
  parseListCodes: () => parseListCodes,
239
257
  parseSigseqTerms: () => parseSigseqTerms,
240
258
  parseSymbolFamily: () => parseSymbolFamily,
@@ -254,6 +272,7 @@ __export(index_exports, {
254
272
  resolveTargetClass: () => resolveTargetClass,
255
273
  rhythmProperties: () => rhythmProperties,
256
274
  s101ArtworkProvenance: () => s101ArtworkProvenance,
275
+ safetyContourValues: () => safetyContourValues,
257
276
  sanitizeCustomStyleConfig: () => sanitizeCustomStyleConfig,
258
277
  scaleLinearLight: () => scaleLinearLight,
259
278
  scaleSizeValue: () => scaleSizeValue,
@@ -2329,6 +2348,19 @@ function s57AbbrTable(acronym) {
2329
2348
  }
2330
2349
 
2331
2350
  // src/atlas/s52-colors.ts
2351
+ var EINK_COLOR_TABLES = { color: "EINK_COLOR", mono: "EINK_MONO", night: "EINK_NIGHT" };
2352
+ function isEinkColorTable(ct) {
2353
+ return ct === "EINK_COLOR" || ct === "EINK_MONO" || ct === "EINK_NIGHT";
2354
+ }
2355
+ function isEinkGreyTable(ct) {
2356
+ return ct === "EINK_MONO" || ct === "EINK_NIGHT";
2357
+ }
2358
+ var EINK_MONO_GREYS = {
2359
+ INK: "#000000",
2360
+ DARK: "#555555",
2361
+ MID: "#aaaaaa",
2362
+ PAPER: "#ffffff"
2363
+ };
2332
2364
  var S52_DAY = {
2333
2365
  // Depth
2334
2366
  NODTA: "#a4a4a4",
@@ -2609,11 +2641,235 @@ var S52_NIGHT = {
2609
2641
  RES02: "#383028",
2610
2642
  RES03: "#383028"
2611
2643
  };
2644
+ var S52_EINK_COLOR = {
2645
+ NODTA: "#b4b4b4",
2646
+ DEPIT: "#4f8f3a",
2647
+ DEPVS: "#74ade0",
2648
+ DEPMS: "#bad6ef",
2649
+ DEPMD: "#ffffff",
2650
+ DEPDW: "#ffffff",
2651
+ LANDA: "#f2e2b0",
2652
+ LANDF: "#c49a4a",
2653
+ CHBRN: "#8b5a2b",
2654
+ CHGRF: "#9a9a9a",
2655
+ CHGRD: "#444444",
2656
+ CHBLK: "#000000",
2657
+ CSTLN: "#000000",
2658
+ OUTLW: "#000000",
2659
+ BKAJ1: "#333333",
2660
+ TRFCD: "#c800c8",
2661
+ TRFCF: "#c800c8",
2662
+ RESBL: "#0050c8",
2663
+ APTS1: "#c800c8",
2664
+ DNGHL: "#e00000",
2665
+ ISHDG: "#e00000",
2666
+ LITRD: "#e00000",
2667
+ LITGN: "#00a000",
2668
+ LITYW: "#f0c000",
2669
+ LITWT: "#ffffff",
2670
+ LITBL: "#0050c8",
2671
+ LITOR: "#f07000",
2672
+ RADHI: "#00a000",
2673
+ RADLO: "#006000",
2674
+ PLRTE: "#e00000",
2675
+ ARPAT: "#008040",
2676
+ ISDNG: "#c800c8",
2677
+ CHRED: "#e00000",
2678
+ CHGRN: "#00a000",
2679
+ CHYLW: "#f0c000",
2680
+ CHMGD: "#c800c8",
2681
+ CHMGF: "#c800c8",
2682
+ CHWHT: "#ffffff",
2683
+ CHCOR: "#f07000",
2684
+ // Deeper-than-safety soundings: dark grey, the one secondary ink. Shoal
2685
+ // soundings are black — the louder numeral, as in every other table.
2686
+ SNDG1: "#555555",
2687
+ SNDG2: "#000000",
2688
+ DEPSC: "#000000",
2689
+ DEPCN: "#555555",
2690
+ UINFD: "#000000",
2691
+ UINFF: "#555555",
2692
+ UINFR: "#e00000",
2693
+ UINFG: "#00a000",
2694
+ UINFO: "#f07000",
2695
+ UINFB: "#0050c8",
2696
+ UINFM: "#c800c8",
2697
+ UIBCK: "#ffffff",
2698
+ UIBDR: "#555555",
2699
+ UIAFD: "#74ade0",
2700
+ UIAFF: "#c49a4a",
2701
+ PIPLN: "#008000",
2702
+ OUTLL: "#c49a4a",
2703
+ SCLBR: "#f07000",
2704
+ NINFO: "#f07000",
2705
+ ADINF: "#b08800",
2706
+ RESGR: "#555555",
2707
+ SHIPS: "#000000",
2708
+ PSTRK: "#000000",
2709
+ SYTRK: "#555555",
2710
+ APLRT: "#f07000",
2711
+ CURSR: "#f07000",
2712
+ BKAJ2: "#000000",
2713
+ RES01: "#808080",
2714
+ RES02: "#808080",
2715
+ RES03: "#808080"
2716
+ };
2717
+ var M = EINK_MONO_GREYS;
2718
+ var S52_EINK_MONO = {
2719
+ // No data is DARK, never the land tone: a no-data edge in the land tone
2720
+ // reads as a coastline (seen on the Boston render, 2026-10-05).
2721
+ NODTA: M.DARK,
2722
+ DEPIT: M.PAPER,
2723
+ DEPVS: M.PAPER,
2724
+ DEPMS: M.PAPER,
2725
+ DEPMD: M.PAPER,
2726
+ DEPDW: M.PAPER,
2727
+ LANDA: M.MID,
2728
+ LANDF: M.DARK,
2729
+ CHBRN: M.DARK,
2730
+ CHGRF: M.MID,
2731
+ CHGRD: M.DARK,
2732
+ CHBLK: M.INK,
2733
+ CSTLN: M.INK,
2734
+ OUTLW: M.INK,
2735
+ BKAJ1: M.DARK,
2736
+ TRFCD: M.INK,
2737
+ TRFCF: M.INK,
2738
+ RESBL: M.INK,
2739
+ APTS1: M.INK,
2740
+ DNGHL: M.INK,
2741
+ ISHDG: M.INK,
2742
+ LITRD: M.INK,
2743
+ LITGN: M.INK,
2744
+ LITYW: M.INK,
2745
+ LITWT: M.PAPER,
2746
+ LITBL: M.INK,
2747
+ LITOR: M.INK,
2748
+ RADHI: M.INK,
2749
+ RADLO: M.DARK,
2750
+ PLRTE: M.INK,
2751
+ ARPAT: M.INK,
2752
+ ISDNG: M.INK,
2753
+ CHRED: M.DARK,
2754
+ CHGRN: M.MID,
2755
+ CHYLW: M.MID,
2756
+ CHMGD: M.INK,
2757
+ CHMGF: M.INK,
2758
+ CHWHT: M.PAPER,
2759
+ CHCOR: M.MID,
2760
+ SNDG1: M.DARK,
2761
+ SNDG2: M.INK,
2762
+ DEPSC: M.INK,
2763
+ DEPCN: M.DARK,
2764
+ UINFD: M.INK,
2765
+ UINFF: M.DARK,
2766
+ UINFR: M.INK,
2767
+ UINFG: M.INK,
2768
+ UINFO: M.INK,
2769
+ UINFB: M.INK,
2770
+ UINFM: M.INK,
2771
+ UIBCK: M.PAPER,
2772
+ UIBDR: M.DARK,
2773
+ UIAFD: M.MID,
2774
+ UIAFF: M.MID,
2775
+ PIPLN: M.INK,
2776
+ OUTLL: M.DARK,
2777
+ SCLBR: M.INK,
2778
+ NINFO: M.INK,
2779
+ ADINF: M.DARK,
2780
+ RESGR: M.DARK,
2781
+ SHIPS: M.INK,
2782
+ PSTRK: M.INK,
2783
+ SYTRK: M.DARK,
2784
+ APLRT: M.INK,
2785
+ CURSR: M.INK,
2786
+ BKAJ2: M.INK,
2787
+ RES01: M.DARK,
2788
+ RES02: M.DARK,
2789
+ RES03: M.DARK
2790
+ };
2791
+ var N = { GROUND: M.INK, DIM: M.DARK, LINE: M.MID, BRIGHT: M.PAPER };
2792
+ var S52_EINK_NIGHT = {
2793
+ NODTA: N.GROUND,
2794
+ DEPIT: N.GROUND,
2795
+ DEPVS: N.GROUND,
2796
+ DEPMS: N.GROUND,
2797
+ DEPMD: N.GROUND,
2798
+ DEPDW: N.GROUND,
2799
+ LANDA: N.DIM,
2800
+ LANDF: N.LINE,
2801
+ CHBRN: N.LINE,
2802
+ CHGRF: N.DIM,
2803
+ CHGRD: N.LINE,
2804
+ CHBLK: N.LINE,
2805
+ CSTLN: N.LINE,
2806
+ OUTLW: N.LINE,
2807
+ BKAJ1: N.DIM,
2808
+ TRFCD: N.LINE,
2809
+ TRFCF: N.LINE,
2810
+ RESBL: N.LINE,
2811
+ APTS1: N.LINE,
2812
+ DNGHL: N.BRIGHT,
2813
+ ISHDG: N.BRIGHT,
2814
+ LITRD: N.BRIGHT,
2815
+ LITGN: N.BRIGHT,
2816
+ LITYW: N.BRIGHT,
2817
+ LITWT: N.BRIGHT,
2818
+ LITBL: N.BRIGHT,
2819
+ LITOR: N.BRIGHT,
2820
+ RADHI: N.LINE,
2821
+ RADLO: N.DIM,
2822
+ PLRTE: N.LINE,
2823
+ ARPAT: N.LINE,
2824
+ ISDNG: N.BRIGHT,
2825
+ // Mark bodies by tone, as in mono, so they stay apart from each other and
2826
+ // from the black water; shape still says which side.
2827
+ CHRED: N.DIM,
2828
+ CHGRN: N.LINE,
2829
+ CHYLW: N.LINE,
2830
+ CHMGD: N.LINE,
2831
+ CHMGF: N.LINE,
2832
+ CHWHT: N.BRIGHT,
2833
+ CHCOR: N.LINE,
2834
+ SNDG1: N.LINE,
2835
+ SNDG2: N.BRIGHT,
2836
+ DEPSC: N.BRIGHT,
2837
+ DEPCN: N.LINE,
2838
+ UINFD: N.LINE,
2839
+ UINFF: N.DIM,
2840
+ UINFR: N.BRIGHT,
2841
+ UINFG: N.LINE,
2842
+ UINFO: N.LINE,
2843
+ UINFB: N.LINE,
2844
+ UINFM: N.LINE,
2845
+ UIBCK: N.GROUND,
2846
+ UIBDR: N.DIM,
2847
+ UIAFD: N.DIM,
2848
+ UIAFF: N.DIM,
2849
+ PIPLN: N.LINE,
2850
+ OUTLL: N.DIM,
2851
+ SCLBR: N.LINE,
2852
+ NINFO: N.LINE,
2853
+ ADINF: N.DIM,
2854
+ RESGR: N.DIM,
2855
+ SHIPS: N.BRIGHT,
2856
+ PSTRK: N.LINE,
2857
+ SYTRK: N.DIM,
2858
+ APLRT: N.BRIGHT,
2859
+ CURSR: N.BRIGHT,
2860
+ BKAJ2: N.GROUND,
2861
+ RES01: N.DIM,
2862
+ RES02: N.DIM,
2863
+ RES03: N.DIM
2864
+ };
2612
2865
  var S52_COLORS = S52_DAY;
2613
2866
  var COLOR_TABLES = {
2614
2867
  DAY: S52_DAY,
2615
2868
  DUSK: S52_DUSK,
2616
- NIGHT: S52_NIGHT
2869
+ NIGHT: S52_NIGHT,
2870
+ EINK_COLOR: S52_EINK_COLOR,
2871
+ EINK_MONO: S52_EINK_MONO,
2872
+ EINK_NIGHT: S52_EINK_NIGHT
2617
2873
  };
2618
2874
  function getColorTable(name) {
2619
2875
  return COLOR_TABLES[name];
@@ -5260,6 +5516,254 @@ var InteractClient = class {
5260
5516
  }
5261
5517
  };
5262
5518
 
5519
+ // src/atlas/chrt-svgs-generated.ts
5520
+ var CHRT_GLYPH_SVGS = {
5521
+ "badge-radar": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><circle cx="16" cy="19" r="2.6" fill="#D200D2"/><path d="M9 19a7 7 0 0 1 14 0M5 19a11 11 0 0 1 22 0" stroke="#D200D2" stroke-width="2.4" fill="none" stroke-linecap="round" stroke-dasharray="4 3"/></svg>',
5522
+ "badge-sound": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><circle cx="7" cy="16" r="2.2" fill="#D200D2"/><path d="M12 10a9 9 0 0 1 0 12M16.5 6.5a15 15 0 0 1 0 19M21 3a21 21 0 0 1 0 26" stroke="#D200D2" stroke-width="2.4" fill="none" stroke-linecap="round"/></svg>',
5523
+ "beacon-cairn": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><circle cx="16" cy="11" r="4" fill="#fff"/><circle cx="11.5" cy="19" r="4.6" fill="#fff"/><circle cx="20.5" cy="19" r="4.6" fill="#fff"/><path d="M7 28h18" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
5524
+ "beacon-generic": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M16 25V12" stroke="#fff" stroke-width="2.6" stroke-linecap="round"/><path d="M16 4l6 8H10Z" fill="#fff"/><path d="M8 28h16M11 25h10" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
5525
+ "beacon-lattice": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M11 25 13 6h6l2 19M12 20h8M12.6 13h6.8M11 25l9-12M21 25l-9-12" stroke="#fff" stroke-width="1.8" stroke-linecap="round" fill="none"/><path d="M8 28h16" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
5526
+ "beacon-stake": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M16 25V6" stroke="#fff" stroke-width="2.8" stroke-linecap="round"/><path d="M9 28h14" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
5527
+ "beacon-tower": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M12 6h8l1.6 19H10.4Z" fill="#fff"/><path d="M10 6h12" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/><path d="M8 28h16" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
5528
+ "buoy-barrel": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><rect x="7" y="13" width="18" height="11" rx="5.5" fill="#fff"/><path d="M6 28h20" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
5529
+ "buoy-can": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M9 12h14v11a2 2 0 0 1-2 2H11a2 2 0 0 1-2-2Z" fill="#fff"/><path d="M6 28h20" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
5530
+ "buoy-conical": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M16 8 24.5 25H7.5Z" fill="#fff"/><path d="M6 28h20" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
5531
+ "buoy-pillar": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M13 6h6l1.4 19H11.6Z" fill="#fff"/><path d="M6 28h20" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
5532
+ "buoy-spar": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M14.5 5h3l1 20h-5Z" fill="#fff"/><path d="M6 28h20" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
5533
+ "buoy-spherical": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><circle cx="16" cy="17.5" r="8.5" fill="#fff"/><path d="M6 28h20" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
5534
+ "buoy-super": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M7 17h18l-2.5 8h-13Z" fill="#fff"/><path d="M16 17V8" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/><circle cx="16" cy="6.5" r="2" fill="#fff"/><path d="M5 28h22" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
5535
+ "cardinal-e": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M16 1.5 20 6h-8Z" fill="#101418"/><path d="M16 11 12 6.5h8Z" fill="#101418"/><path d="M13 12h6l1 13H12Z" fill="#FFC805"/><path d="M6 28h20" stroke="#101418" stroke-width="2.4" stroke-linecap="round"/></svg>',
5536
+ "cardinal-n": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M16 1.5 20 6h-8Z" fill="#101418"/><path d="M16 6.5 20 11h-8Z" fill="#101418"/><path d="M13 12h6l1 13H12Z" fill="#FFC805"/><path d="M6 28h20" stroke="#101418" stroke-width="2.4" stroke-linecap="round"/></svg>',
5537
+ "cardinal-s": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M16 11 12 6.5h8Z" fill="#101418"/><path d="M16 6 12 1.5h8Z" fill="#101418"/><path d="M13 12h6l1 13H12Z" fill="#FFC805"/><path d="M6 28h20" stroke="#101418" stroke-width="2.4" stroke-linecap="round"/></svg>',
5538
+ "cardinal-w": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M16 6 12 1.5h8Z" fill="#101418"/><path d="M16 6.5 20 11h-8Z" fill="#101418"/><path d="M13 12h6l1 13H12Z" fill="#FFC805"/><path d="M6 28h20" stroke="#101418" stroke-width="2.4" stroke-linecap="round"/></svg>',
5539
+ "daymark-square": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><rect x="6" y="6" width="20" height="20" rx="1.5" fill="#fff"/></svg>',
5540
+ "daymark-triangle": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M16 5 28 26H4Z" fill="#fff"/></svg>',
5541
+ "eink-danger": '<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8"><path d="M-2 2 L2 -2 M0 8 L8 0 M6 10 L10 6" stroke="#000000" stroke-width="1" fill="none"/><path d="M-2 6 L2 10 M0 0 L8 8 M6 -2 L10 2" stroke="#000000" stroke-width="1" fill="none"/></svg>',
5542
+ "eink-dots": '<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10"><circle cx="2.5" cy="2.5" r="0.9" fill="#000000"/><circle cx="7.5" cy="7.5" r="0.9" fill="#000000"/></svg>',
5543
+ "eink-hatch": '<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8"><path d="M-2 2 L2 -2 M0 8 L8 0 M6 10 L10 6" stroke="#000000" stroke-width="0.9" fill="none"/></svg>',
5544
+ "hatch-nocoverage": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M-3 9 L9 -3 M-3 21 L21 -3 M3 27 L27 3 M15 27 L27 15" stroke="#8A97A0" stroke-width="0.8" fill="none"/><path d="M-3 15 L15 33 M-3 3 L21 27 M3 -3 L27 21 M15 -3 L27 9" stroke="#8A97A0" stroke-width="0.8" fill="none"/></svg>',
5545
+ "hatch-uncharted": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M-3 9 L9 -3 M-3 21 L21 -3 M3 27 L27 3 M15 27 L27 15" stroke="#5B7383" stroke-width="0.65" fill="none"/></svg>',
5546
+ "isolated-danger": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><circle cx="16" cy="3.5" r="2.4" fill="#101418"/><circle cx="16" cy="9.5" r="2.4" fill="#101418"/><path d="M13 13h6l1 12H12Z" fill="#101418"/><rect x="12.4" y="17.5" width="7.2" height="3.4" fill="#E5484D"/><path d="M6 28h20" stroke="#101418" stroke-width="2.4" stroke-linecap="round"/></svg>',
5547
+ "light-flare": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><circle cx="11" cy="11" r="3.2" fill="#fff"/><path d="M13.4 13.4 24 19.5a2.6 2.6 0 0 1-4.5 4.5L13.4 13.4Z" fill="#fff"/></svg>',
5548
+ "lightship": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M6 20l3-5h14l3 5-3.5 4h-13Z" fill="#fff"/><path d="M16 15V8" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/><circle cx="16" cy="6" r="2.2" fill="#fff"/><path d="M5 28h22" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
5549
+ "safe-water": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><circle cx="16" cy="6" r="2.4" fill="#E5484D"/><circle cx="16" cy="17.5" r="8" fill="#F8FAFC"/><path d="M12.2 10.5a8 8 0 0 0 0 14M19.8 10.5a8 8 0 0 1 0 14" stroke="#E5484D" stroke-width="3" fill="none"/><path d="M6 28h20" stroke="#101418" stroke-width="2.4" stroke-linecap="round"/></svg>'
5550
+ };
5551
+
5552
+ // src/atlas/s52-pattern.ts
5553
+ var S52_PATTERN_SPRITE_PREFIX = "AP:";
5554
+ function patternSpriteId(patternName) {
5555
+ return `${S52_PATTERN_SPRITE_PREFIX}${patternName}`;
5556
+ }
5557
+
5558
+ // src/atlas/s52-conditional.ts
5559
+ var DEFAULT_SAFETY_CONTOUR_M = 10;
5560
+ var DEPARE_DEEP_CUT_M = 18.2;
5561
+ var DEPARE_SHALLOW_CUT_M = 2;
5562
+ function effectiveSafetyContour(safetyContourM) {
5563
+ if (!Number.isFinite(safetyContourM)) return DEFAULT_SAFETY_CONTOUR_M;
5564
+ return Math.min(Math.max(safetyContourM, DEPARE_SHALLOW_CUT_M), DEPARE_DEEP_CUT_M);
5565
+ }
5566
+ function depareColor(props, ct = "DAY", safetyContourM = DEFAULT_SAFETY_CONTOUR_M) {
5567
+ const drval1 = Number(props.DRVAL1 ?? props.drval1 ?? -1);
5568
+ const drval2 = Number(props.DRVAL2 ?? props.drval2 ?? drval1);
5569
+ const safety = effectiveSafetyContour(safetyContourM);
5570
+ if (drval1 < 0 && drval2 <= 0) return resolveColor("DEPIT", ct);
5571
+ if (drval1 < DEPARE_SHALLOW_CUT_M) return resolveColor("DEPVS", ct);
5572
+ if (drval1 < safety) return resolveColor("DEPMS", ct);
5573
+ if (drval1 < DEPARE_DEEP_CUT_M) return resolveColor("DEPMD", ct);
5574
+ return resolveColor("DEPDW", ct);
5575
+ }
5576
+ var DEPARE_FILL_BRANCH_TOKENS = ["DEPIT", "DEPVS", "DEPMS", "DEPMD"];
5577
+ var DEPARE_FILL_FALLBACK_TOKEN = "DEPDW";
5578
+ function depareFillExpression(ct = "DAY", safetyContourM = DEFAULT_SAFETY_CONTOUR_M) {
5579
+ const safety = effectiveSafetyContour(safetyContourM);
5580
+ const [it, vs, ms, md] = DEPARE_FILL_BRANCH_TOKENS;
5581
+ return [
5582
+ "case",
5583
+ [
5584
+ "all",
5585
+ ["<", ["coalesce", ["get", "DRVAL1"], ["get", "drval1"], -1], 0],
5586
+ ["<=", ["coalesce", ["get", "DRVAL2"], ["get", "drval2"], 0], 0]
5587
+ ],
5588
+ resolveColor(it, ct),
5589
+ ["<", ["coalesce", ["get", "DRVAL1"], ["get", "drval1"], -1], DEPARE_SHALLOW_CUT_M],
5590
+ resolveColor(vs, ct),
5591
+ ["<", ["coalesce", ["get", "DRVAL1"], ["get", "drval1"], -1], safety],
5592
+ resolveColor(ms, ct),
5593
+ ["<", ["coalesce", ["get", "DRVAL1"], ["get", "drval1"], -1], DEPARE_DEEP_CUT_M],
5594
+ resolveColor(md, ct),
5595
+ resolveColor(DEPARE_FILL_FALLBACK_TOKEN, ct)
5596
+ ];
5597
+ }
5598
+ function listAttrString(attr) {
5599
+ return ["to-string", ["coalesce", ["get", attr], ["get", attr.toLowerCase()], ""]];
5600
+ }
5601
+ function listAttrCase(attr, cases, fallback) {
5602
+ const attrStr = listAttrString(attr);
5603
+ const expr = ["case"];
5604
+ for (const [code, value] of cases) {
5605
+ expr.push([">=", ["index-of", `"${code}"`, attrStr], 0], value);
5606
+ }
5607
+ for (const [code, value] of cases) {
5608
+ expr.push(["==", attrStr, code], value);
5609
+ }
5610
+ for (const [code, value] of cases) {
5611
+ expr.push(["==", attrStr, `[${code}]`], value);
5612
+ }
5613
+ expr.push(fallback);
5614
+ return expr;
5615
+ }
5616
+ function listAttrHas(attr, codes) {
5617
+ const attrStr = listAttrString(attr);
5618
+ const clauses = [];
5619
+ for (const code of codes) {
5620
+ clauses.push([">=", ["index-of", `"${code}"`, attrStr], 0]);
5621
+ clauses.push(["==", attrStr, code]);
5622
+ clauses.push(["==", attrStr, `[${code}]`]);
5623
+ }
5624
+ return ["any", ...clauses];
5625
+ }
5626
+ function colourCodeCase(cases, fallback) {
5627
+ return listAttrCase("COLOUR", cases, fallback);
5628
+ }
5629
+ function lightColor(props, ct = "DAY") {
5630
+ const raw = props.COLOUR ?? props.colour;
5631
+ const code = Array.isArray(raw) ? Number(raw[0]) : typeof raw === "string" && raw.startsWith("[") ? Number(JSON.parse(raw)[0]) : Number(raw ?? 0);
5632
+ switch (code) {
5633
+ case 1:
5634
+ return resolveColor("LITWT", ct);
5635
+ case 3:
5636
+ return resolveColor("LITRD", ct);
5637
+ case 4:
5638
+ return resolveColor("LITGN", ct);
5639
+ case 5:
5640
+ return resolveColor("LITBL", ct);
5641
+ case 6:
5642
+ return resolveColor("LITYW", ct);
5643
+ case 11:
5644
+ return resolveColor("LITOR", ct);
5645
+ default:
5646
+ return resolveColor("LITYW", ct);
5647
+ }
5648
+ }
5649
+ function lightFillExpression(ct = "DAY") {
5650
+ return colourCodeCase(
5651
+ [
5652
+ ["3", resolveColor("LITRD", ct)],
5653
+ ["4", resolveColor("LITGN", ct)],
5654
+ ["6", resolveColor("LITYW", ct)],
5655
+ ["11", resolveColor("LITOR", ct)],
5656
+ ["5", resolveColor("LITBL", ct)],
5657
+ ["1", resolveColor("LITWT", ct)]
5658
+ ],
5659
+ resolveColor("LITYW", ct)
5660
+ );
5661
+ }
5662
+ function lateralColor(props, ct = "DAY") {
5663
+ const catlam = Number(props.CATLAM ?? props.catlam ?? 0);
5664
+ if (catlam === 1) return resolveColor("LITRD", ct);
5665
+ if (catlam === 2) return resolveColor("LITGN", ct);
5666
+ return resolveColor("LITYW", ct);
5667
+ }
5668
+ function lateralFillExpression(ct = "DAY") {
5669
+ return [
5670
+ "match",
5671
+ ["coalesce", ["get", "CATLAM"], ["get", "catlam"], 0],
5672
+ 1,
5673
+ resolveColor("LITRD", ct),
5674
+ 2,
5675
+ resolveColor("LITGN", ct),
5676
+ resolveColor("LITYW", ct)
5677
+ ];
5678
+ }
5679
+ function hazardColor(props, ct = "DAY") {
5680
+ const watlev = Number(props.WATLEV ?? props.watlev ?? 0);
5681
+ const valsou = props.VALSOU ?? props.valsou;
5682
+ if (watlev === 1 || watlev === 2) return resolveColor("DNGHL", ct);
5683
+ if (valsou != null && Number(valsou) < 2) return resolveColor("DNGHL", ct);
5684
+ return resolveColor("RESBL", ct);
5685
+ }
5686
+ function hazardFillExpression(ct = "DAY") {
5687
+ return [
5688
+ "case",
5689
+ [
5690
+ "any",
5691
+ ["==", ["coalesce", ["get", "WATLEV"], ["get", "watlev"], 0], 1],
5692
+ ["==", ["coalesce", ["get", "WATLEV"], ["get", "watlev"], 0], 2]
5693
+ ],
5694
+ resolveColor("DNGHL", ct),
5695
+ [
5696
+ "all",
5697
+ ["has", "VALSOU"],
5698
+ ["<", ["get", "VALSOU"], 2]
5699
+ ],
5700
+ resolveColor("DNGHL", ct),
5701
+ resolveColor("RESBL", ct)
5702
+ ];
5703
+ }
5704
+ function vegatnPatternExpression() {
5705
+ return [
5706
+ "match",
5707
+ ["coalesce", ["get", "CATVEG"], ["get", "catveg"], 0],
5708
+ 1,
5709
+ patternSpriteId("VEGATN03"),
5710
+ // grass
5711
+ 3,
5712
+ patternSpriteId("VEGATN03"),
5713
+ // bush
5714
+ 4,
5715
+ patternSpriteId("VEGATN04"),
5716
+ // deciduous wood
5717
+ 5,
5718
+ patternSpriteId("VEGATN04"),
5719
+ // coniferous wood
5720
+ 6,
5721
+ patternSpriteId("VEGATN04"),
5722
+ // wood (mixed)
5723
+ 7,
5724
+ patternSpriteId("MARSHES1"),
5725
+ // mangroves
5726
+ 10,
5727
+ patternSpriteId("VEGATN03"),
5728
+ // scrub
5729
+ 11,
5730
+ patternSpriteId("VEGATN04"),
5731
+ // tree
5732
+ 12,
5733
+ patternSpriteId("VEGATN04"),
5734
+ // evergreen
5735
+ 16,
5736
+ patternSpriteId("MARSHES1"),
5737
+ // swamp
5738
+ 17,
5739
+ patternSpriteId("MARSHES1"),
5740
+ // marsh
5741
+ 21,
5742
+ patternSpriteId("VEGATN04"),
5743
+ // savannah
5744
+ patternSpriteId("VEGATN03")
5745
+ // default: grass pattern
5746
+ ];
5747
+ }
5748
+ function daymarkColor(props, ct = "DAY") {
5749
+ const raw = props.COLOUR ?? props.colour;
5750
+ const code = Array.isArray(raw) ? Number(raw[0]) : typeof raw === "string" && raw.startsWith("[") ? Number(JSON.parse(raw)[0]) : Number(raw ?? 0);
5751
+ switch (code) {
5752
+ case 1:
5753
+ return resolveColor("LITWT", ct);
5754
+ case 3:
5755
+ return resolveColor("LITRD", ct);
5756
+ case 4:
5757
+ return resolveColor("LITGN", ct);
5758
+ case 6:
5759
+ return resolveColor("LITYW", ct);
5760
+ case 11:
5761
+ return resolveColor("LITOR", ct);
5762
+ default:
5763
+ return resolveColor("LITGN", ct);
5764
+ }
5765
+ }
5766
+
5263
5767
  // src/atlas/palette-light.ts
5264
5768
  var HEX6 = /^#?([0-9a-f]{6})$/i;
5265
5769
  function channels(hex) {
@@ -5298,8 +5802,971 @@ function capLuminance(hex, ceiling) {
5298
5802
  }
5299
5803
  var NIGHT_INK = S52_NIGHT.CHBLK;
5300
5804
  var NIGHT_MUTED_INK = scaleLinearLight(S52_NIGHT.CHBLK, 0.43);
5301
-
5302
- // src/atlas/s52-linestyles-generated.ts
5805
+ function einkMonoGrey(hex) {
5806
+ const l = relativeLuminance(hex);
5807
+ const g2 = EINK_MONO_GREYS;
5808
+ const steps = [g2.INK, g2.DARK, g2.MID, g2.PAPER].map((c) => [c, relativeLuminance(c)]);
5809
+ let best = steps[0];
5810
+ for (const step of steps) if (Math.abs(step[1] - l) < Math.abs(best[1] - l)) best = step;
5811
+ return best[0];
5812
+ }
5813
+ function einkNightGrey(hex) {
5814
+ const l = relativeLuminance(hex);
5815
+ const g2 = EINK_MONO_GREYS;
5816
+ const steps = [g2.DARK, g2.MID, g2.PAPER].map((c) => [c, relativeLuminance(c)]);
5817
+ let best = steps[0];
5818
+ for (const step of steps) if (Math.abs(step[1] - l) < Math.abs(best[1] - l)) best = step;
5819
+ return best[0];
5820
+ }
5821
+
5822
+ // src/atlas/chrt-style.ts
5823
+ var CHRT_ICON_PREFIX = "chrt:";
5824
+ var CHRT_SDF_GLYPHS = /* @__PURE__ */ new Set([
5825
+ "buoy-can",
5826
+ "buoy-conical",
5827
+ "buoy-spherical",
5828
+ "buoy-pillar",
5829
+ "buoy-spar",
5830
+ "buoy-barrel",
5831
+ "buoy-super",
5832
+ "beacon-generic",
5833
+ "beacon-tower",
5834
+ "beacon-lattice",
5835
+ "beacon-stake",
5836
+ "beacon-cairn",
5837
+ "light-flare",
5838
+ "lightship",
5839
+ "daymark-triangle",
5840
+ "daymark-square"
5841
+ ]);
5842
+ var CHRT_GLYPH_NAMES = new Set(Object.keys(CHRT_GLYPH_SVGS));
5843
+ var CHRT_ATON_COLORS = {
5844
+ "1": "#F8FAFC",
5845
+ // white
5846
+ "2": "#101418",
5847
+ // black
5848
+ "3": "#E5484D",
5849
+ // red
5850
+ "4": "#30A46C",
5851
+ // green
5852
+ "5": "#3E63DD",
5853
+ // blue
5854
+ "6": "#FFC805",
5855
+ // yellow
5856
+ "7": "#8B9298",
5857
+ // grey
5858
+ "8": "#A66B3A",
5859
+ // brown
5860
+ "9": "#FFB224",
5861
+ // amber
5862
+ "10": "#7E4CCB",
5863
+ // violet
5864
+ "11": "#F76B15",
5865
+ // orange
5866
+ "12": "#D671C1"
5867
+ // pink (magenta-ish)
5868
+ };
5869
+ var CHRT_UNKNOWN_TINT = "#8B9298";
5870
+ var HOUSE_BLACK = "#101418";
5871
+ function chrtHouseColor(dayHex, palette = "DAY") {
5872
+ if (palette === "DAY" || palette === "EINK_COLOR") return dayHex;
5873
+ if (palette === "EINK_MONO") return einkMonoGrey(dayHex);
5874
+ if (palette === "EINK_NIGHT") return dayHex.toLowerCase() === HOUSE_BLACK.toLowerCase() || dayHex.toLowerCase() === "#000000" ? "#aaaaaa" : einkNightGrey(dayHex);
5875
+ const isBlack = dayHex.toLowerCase() === HOUSE_BLACK.toLowerCase();
5876
+ if (palette === "DUSK") {
5877
+ return isBlack ? dayHex : capLuminance(dayHex, relativeLuminance(resolveColor("CHBLK", "DUSK")));
5878
+ }
5879
+ return isBlack ? NIGHT_INK : capLuminance(dayHex, relativeLuminance(NIGHT_MUTED_INK));
5880
+ }
5881
+ function themeChrtGlyph(svg3, palette = "DAY") {
5882
+ if (palette === "DAY" || palette === "EINK_COLOR") return svg3;
5883
+ return svg3.replace(
5884
+ /(fill|stroke)="(#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6})"/g,
5885
+ (_m, attr, hex) => {
5886
+ const six = hex.length === 4 ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex;
5887
+ return `${attr}="${chrtHouseColor(six, palette)}"`;
5888
+ }
5889
+ );
5890
+ }
5891
+ function chrtAtonTintExpression(palette = "DAY") {
5892
+ const c = (code) => chrtHouseColor(CHRT_ATON_COLORS[code], palette);
5893
+ return colourCodeCase(
5894
+ [
5895
+ ["3", c("3")],
5896
+ ["4", c("4")],
5897
+ ["6", c("6")],
5898
+ ["11", c("11")],
5899
+ ["9", c("9")],
5900
+ ["5", c("5")],
5901
+ ["1", c("1")],
5902
+ ["2", c("2")],
5903
+ ["8", c("8")],
5904
+ ["10", c("10")],
5905
+ ["12", c("12")],
5906
+ ["7", c("7")]
5907
+ ],
5908
+ chrtHouseColor(CHRT_UNKNOWN_TINT, palette)
5909
+ );
5910
+ }
5911
+ function chrtLightTintExpression(palette = "DAY") {
5912
+ const c = (code) => chrtHouseColor(CHRT_ATON_COLORS[code], palette);
5913
+ return colourCodeCase(
5914
+ [
5915
+ ["3", c("3")],
5916
+ ["4", c("4")],
5917
+ ["6", c("6")],
5918
+ ["11", c("11")],
5919
+ ["9", c("9")],
5920
+ ["5", c("5")],
5921
+ ["1", c("1")]
5922
+ ],
5923
+ c("1")
5924
+ );
5925
+ }
5926
+ function scalar(attr) {
5927
+ return ["coalesce", ["get", attr], ["get", attr.toLowerCase()], 0];
5928
+ }
5929
+ var g = (name) => `${CHRT_ICON_PREFIX}${name}`;
5930
+ function buoyBodyExpression() {
5931
+ return [
5932
+ "match",
5933
+ scalar("BOYSHP"),
5934
+ 1,
5935
+ g("buoy-conical"),
5936
+ 2,
5937
+ g("buoy-can"),
5938
+ 3,
5939
+ g("buoy-spherical"),
5940
+ 4,
5941
+ g("buoy-pillar"),
5942
+ 5,
5943
+ g("buoy-spar"),
5944
+ 6,
5945
+ g("buoy-barrel"),
5946
+ 7,
5947
+ g("buoy-super"),
5948
+ 8,
5949
+ g("buoy-super"),
5950
+ g("buoy-pillar")
5951
+ ];
5952
+ }
5953
+ function beaconBodyExpression() {
5954
+ return [
5955
+ "match",
5956
+ scalar("BCNSHP"),
5957
+ 1,
5958
+ g("beacon-stake"),
5959
+ 2,
5960
+ g("beacon-stake"),
5961
+ 3,
5962
+ g("beacon-tower"),
5963
+ 4,
5964
+ g("beacon-lattice"),
5965
+ 5,
5966
+ g("beacon-stake"),
5967
+ 6,
5968
+ g("beacon-cairn"),
5969
+ g("beacon-generic")
5970
+ ];
5971
+ }
5972
+ function cardinalExpression() {
5973
+ return [
5974
+ "match",
5975
+ scalar("CATCAM"),
5976
+ 1,
5977
+ g("cardinal-n"),
5978
+ 2,
5979
+ g("cardinal-e"),
5980
+ 3,
5981
+ g("cardinal-s"),
5982
+ 4,
5983
+ g("cardinal-w"),
5984
+ g("cardinal-n")
5985
+ ];
5986
+ }
5987
+ var CHRT_ICON_EXPRESSIONS = {
5988
+ BOYLAT: buoyBodyExpression,
5989
+ BOYSPP: buoyBodyExpression,
5990
+ BOYINB: buoyBodyExpression,
5991
+ BOYCAR: cardinalExpression,
5992
+ BOYISD: () => g("isolated-danger"),
5993
+ BOYSAW: () => g("safe-water"),
5994
+ BCNLAT: beaconBodyExpression,
5995
+ BCNSPP: beaconBodyExpression,
5996
+ BCNCAR: cardinalExpression,
5997
+ BCNISD: () => g("isolated-danger"),
5998
+ BCNSAW: () => g("safe-water"),
5999
+ LIGHTS: () => g("light-flare"),
6000
+ LITFLT: () => g("lightship"),
6001
+ LITVES: () => g("lightship"),
6002
+ FOGSIG: () => g("badge-sound"),
6003
+ RTPBCN: () => g("badge-radar"),
6004
+ RDOSTA: () => g("badge-radar"),
6005
+ DAYMAR: () => [
6006
+ "match",
6007
+ scalar("TOPSHP"),
6008
+ 1,
6009
+ g("daymark-triangle"),
6010
+ 2,
6011
+ g("daymark-triangle"),
6012
+ 5,
6013
+ g("daymark-triangle"),
6014
+ g("daymark-square")
6015
+ ]
6016
+ };
6017
+ var TINTED_CLASSES = /* @__PURE__ */ new Set([
6018
+ "BOYLAT",
6019
+ "BOYSPP",
6020
+ "BOYINB",
6021
+ "BCNLAT",
6022
+ "BCNSPP",
6023
+ "DAYMAR"
6024
+ ]);
6025
+ var CHRT_HALO = { color: HOUSE_BLACK, width: 1 };
6026
+ var CHRT_LINE_DASH = {
6027
+ CBLSUB: { color: "CHMGD", width: 0.8, dash: [6, 3] },
6028
+ PIPSOL: { color: "PIPLN", width: 0.8, dash: [6, 3] }
6029
+ };
6030
+ function applyChrtSymbolSet(style, palette = "DAY") {
6031
+ for (const layer of style.layers) {
6032
+ if (layer.type === "line") {
6033
+ const cls = layer.metadata?.["s52:objectClass"];
6034
+ const dash = cls ? CHRT_LINE_DASH[cls] : void 0;
6035
+ const paint2 = layer.paint ?? {};
6036
+ if (dash && paint2["line-pattern"] != null) {
6037
+ delete paint2["line-pattern"];
6038
+ paint2["line-color"] = resolveColor(dash.color, palette);
6039
+ paint2["line-width"] = dash.width;
6040
+ paint2["line-dasharray"] = dash.dash;
6041
+ layer.paint = paint2;
6042
+ }
6043
+ continue;
6044
+ }
6045
+ if (layer.type !== "symbol") continue;
6046
+ const objectClass2 = layer.metadata?.["s52:objectClass"];
6047
+ if (!objectClass2 || !CHRT_ICON_EXPRESSIONS[objectClass2]) continue;
6048
+ const layout = layer.layout;
6049
+ if (!layout || layout["icon-image"] == null) continue;
6050
+ layout["icon-image"] = CHRT_ICON_EXPRESSIONS[objectClass2]();
6051
+ const paint = layer.paint ?? {};
6052
+ const haloColor = palette === "EINK_MONO" ? chrtHouseColor(HOUSE_BLACK, palette) : palette === "EINK_NIGHT" ? "#000000" : CHRT_HALO.color;
6053
+ if (TINTED_CLASSES.has(objectClass2)) {
6054
+ paint["icon-color"] = chrtAtonTintExpression(palette);
6055
+ paint["icon-halo-color"] = haloColor;
6056
+ paint["icon-halo-width"] = CHRT_HALO.width;
6057
+ } else if (objectClass2 === "LIGHTS") {
6058
+ paint["icon-color"] = chrtLightTintExpression(palette);
6059
+ paint["icon-halo-color"] = haloColor;
6060
+ paint["icon-halo-width"] = CHRT_HALO.width;
6061
+ }
6062
+ layer.paint = paint;
6063
+ }
6064
+ const metadata = style.metadata;
6065
+ metadata["chrt:symbol-set"] = "chrt";
6066
+ return style;
6067
+ }
6068
+
6069
+ // src/atlas/base-layers.ts
6070
+ var NOCOVERAGE_PATTERN = "chrt:hatch-nocoverage";
6071
+ var UNCHARTED_PATTERN = "chrt:hatch-uncharted";
6072
+ function inlandWaterColor(palette) {
6073
+ return (BASE_THEMES[palette] ?? BASE_THEMES.DAY).inlandWater;
6074
+ }
6075
+ var BASE_THEMES = {
6076
+ DAY: {
6077
+ // ── `earth` is the jurisdiction tan DIMMED (~0.55), not a blue-grey ──
6078
+ // Outside the jurisdiction the map still owes the reader "what is land".
6079
+ // State 3 in the header is "the geography, under a crosshatch", and until
6080
+ // 2026-08-31 that geography was unreadable: earth #2A3439 against sea
6081
+ // #1B2328 measured 1.25:1, so Hispaniola showed charted boxes with tan
6082
+ // land inside and nothing recognisable around them (owner report: "no
6083
+ // land painting down near the DR at some zooms"). Land is warm and sea is
6084
+ // cool because that is what land and sea are; dimming the JURISDICTION
6085
+ // tan rather than inventing a colour says the true thing — this is the
6086
+ // same land, we simply do not carry charts for it. Held against BOTH
6087
+ // neighbours: legible over sea, and never mistakable for the full tan of
6088
+ // a country we do carry. DAY 3.59:1 vs sea, 3.17:1 vs tan.
6089
+ earth: "#807761",
6090
+ water: "#1B2328",
6091
+ river: "#24313A",
6092
+ boundary: "#4A575E",
6093
+ jurisdiction: "#D4C69D",
6094
+ // CHRTland's own tan — one land colour
6095
+ inlandWater: "#B9CFDD",
6096
+ // muted, unmistakably not a charted blue
6097
+ // The uncharted hatch's stripe, held to 2.3:1 against `inlandWater` in
6098
+ // every palette (`inland-hatch-contrast.test.ts`) — the hatch says "we have
6099
+ // not charted this water", and stripes the eye cannot find say nothing.
6100
+ // DAY 3.08:1, darker than its ground. Also the colour the artwork file
6101
+ // itself is drawn in.
6102
+ inlandHatch: "#5B7383",
6103
+ // State furniture. These three were referenced by the layers but missing
6104
+ // from this table (and its type — the long-standing TS2339 trio), so the
6105
+ // lines shipped on MapLibre's DEFAULT black. Now deliberate: a hairline
6106
+ // a shade deeper than the tan, quiet caps, tan halo.
6107
+ stateLine: "#B1A077",
6108
+ stateLabel: "#8E8468",
6109
+ stateHalo: "#D4C69D",
6110
+ // Rough-lake furniture (owner ask 2026-08-28: "the lakes need labels,
6111
+ // and shorelines drawn"): a shoreline a shade deeper than the rough
6112
+ // water, and a name in the water's own voice, haloed by the water.
6113
+ inlandShore: "#8FA9BA",
6114
+ inlandLabel: "#54707F",
6115
+ inlandHalo: "#B9CFDD"
6116
+ },
6117
+ DUSK: {
6118
+ // ── Re-derived 2026-09-01: the basemap sits UNDER the chart ──────────
6119
+ // These were the DAY tones scaled by a constant, never measured against
6120
+ // the DUSK chart — so `jurisdiction` came out at L=0.506 against a chart
6121
+ // whose own land (LANDA #8a7d5e) is L=0.209, i.e. the context layer was
6122
+ // 2.4x brighter than the data layer. Now capped at the chart's own land.
6123
+ // The rest of the block rides the same 0.666 channel factor so every
6124
+ // relationship the palette already chose is preserved, only moved down.
6125
+ // `water` is deliberately NOT scaled: it is the reference the land/sea
6126
+ // floor is measured against.
6127
+ earth: "#544E3E",
6128
+ water: "#161D21",
6129
+ river: "#141B20",
6130
+ boundary: "#293135",
6131
+ jurisdiction: "#867D64",
6132
+ inlandWater: "#62717A",
6133
+ // Owner's choice, 2026-10-04: dark stripes, 2.37:1. The DAY stripe here
6134
+ // was 1.02:1 — invisible, so an uncharted lake read as plain water.
6135
+ inlandHatch: "#2E383F",
6136
+ // Label stays DARK here — the halo is still mid-toned, so 3:1 is reachable
6137
+ // downward and a bright label would be a new light source at dusk.
6138
+ stateLine: "#B5A987",
6139
+ stateLabel: "#38342A",
6140
+ stateHalo: "#867D64",
6141
+ inlandShore: "#4D5B67",
6142
+ inlandLabel: "#212629",
6143
+ inlandHalo: "#62717A"
6144
+ },
6145
+ NIGHT: {
6146
+ // ── Re-derived 2026-09-01 — the owner-reported night defect ──────────
6147
+ // `jurisdiction` was L=0.240 against a chart whose own land (LANDA
6148
+ // #3a3020) is L=0.031: the world basemap was 7.7x BRIGHTER than the chart
6149
+ // it sits under, so at z<11 the uncharted interior read as a pale
6150
+ // continent-sized slab on a night chart. Capped at the chart's own land.
6151
+ //
6152
+ // ⚠ `earth` AND `jurisdiction` CONVERGE HERE, and that is forced, not
6153
+ // sloppy. Holding earth 2:1 above the ocean AND 2:1 below the jurisdiction
6154
+ // tan requires jurisdiction >= L 0.174 — 5.6x the chart's own land. At
6155
+ // night the range between the ocean (L 0.006) and the cap (L 0.031) is too
6156
+ // compressed to carry two land tones. COVERAGE IS STILL SAID, by the
6157
+ // `chrt:hatch-nocoverage` crosshatch, which is a stronger signal than tone
6158
+ // and is the one thing that cannot be lost to palette compression.
6159
+ //
6160
+ // Labels invert to light-on-dark: 3:1 downward is unreachable off a halo
6161
+ // this close to black, and CHRTland's own night town labels already sit at
6162
+ // L=0.177 for exactly this reason.
6163
+ earth: "#333026",
6164
+ water: "#10161A",
6165
+ river: "#090D0F",
6166
+ boundary: "#131719",
6167
+ jurisdiction: "#353128",
6168
+ inlandWater: "#232A30",
6169
+ // The DAY stripe, lighter than this ground: 2.92:1, and L 0.16, under the
6170
+ // NIGHT muted-ink ceiling (`night-luminance.test.ts`). Red night draws the
6171
+ // NIGHT chart, so this is its stripe too.
6172
+ inlandHatch: "#5B7383",
6173
+ stateLine: "#484236",
6174
+ stateLabel: "#837963",
6175
+ stateHalo: "#353128",
6176
+ inlandShore: "#1C2227",
6177
+ inlandLabel: "#617484",
6178
+ inlandHalo: "#232A30"
6179
+ },
6180
+ // ── E-ink: uncharted is DARK, never paper ─────────────────────────────
6181
+ // On an e-ink chart, charted safe water is paper. Water outside coverage
6182
+ // must never read as that, so it is the darkest fill on the page — the
6183
+ // e-ink twin of DAY's dark carto — with the no-coverage crosshatch over it
6184
+ // (invariant 5: the coverage portrayal survives). Land outside coverage
6185
+ // keeps the chart's own land tone.
6186
+ EINK_COLOR: {
6187
+ earth: "#d8ccb0",
6188
+ water: "#4f5a62",
6189
+ river: "#4f5a62",
6190
+ boundary: "#000000",
6191
+ jurisdiction: "#f2e2b0",
6192
+ inlandWater: "#c8d6e0",
6193
+ // The house stripe, 3.4:1 on this rough water — strong enough for the
6194
+ // 150 ppi colour layer.
6195
+ inlandHatch: "#5B7383",
6196
+ stateLine: "#555555",
6197
+ stateLabel: "#000000",
6198
+ stateHalo: "#ffffff",
6199
+ inlandShore: "#555555",
6200
+ inlandLabel: "#000000",
6201
+ inlandHalo: "#ffffff"
6202
+ },
6203
+ // Mono: the same rule in the four greys. Uncharted water DARK under a MID
6204
+ // crosshatch; land MID; rough inland water PAPER under the uncharted hatch.
6205
+ EINK_MONO: {
6206
+ earth: "#aaaaaa",
6207
+ water: "#555555",
6208
+ river: "#555555",
6209
+ boundary: "#000000",
6210
+ jurisdiction: "#aaaaaa",
6211
+ inlandWater: "#ffffff",
6212
+ inlandHatch: "#555555",
6213
+ stateLine: "#555555",
6214
+ stateLabel: "#000000",
6215
+ stateHalo: "#ffffff",
6216
+ inlandShore: "#555555",
6217
+ inlandLabel: "#000000",
6218
+ inlandHalo: "#ffffff"
6219
+ },
6220
+ // Night: the inverse. Water outside coverage is the black ground UNDER the
6221
+ // light crosshatch — charted safe water is plain black, so the hatch is what
6222
+ // tells them apart, here as everywhere. Land the DIM grey; labels the line
6223
+ // grey on black.
6224
+ EINK_NIGHT: {
6225
+ earth: "#555555",
6226
+ water: "#000000",
6227
+ river: "#000000",
6228
+ boundary: "#aaaaaa",
6229
+ jurisdiction: "#555555",
6230
+ inlandWater: "#000000",
6231
+ inlandHatch: "#aaaaaa",
6232
+ stateLine: "#aaaaaa",
6233
+ stateLabel: "#aaaaaa",
6234
+ stateHalo: "#000000",
6235
+ inlandShore: "#aaaaaa",
6236
+ inlandLabel: "#aaaaaa",
6237
+ inlandHalo: "#000000"
6238
+ }
6239
+ };
6240
+ function baseLayers(opts = {}) {
6241
+ const palette = opts.palette ?? "DAY";
6242
+ const T = BASE_THEMES[palette] ?? BASE_THEMES.DAY;
6243
+ const src = opts.sourceId ?? BASE_REGION;
6244
+ const L = (o) => o;
6245
+ return [
6246
+ // 1. Geography — the world as it is, in the quiet dark voice. The OCEAN
6247
+ // is a background, not a polygon: the archive ships land and lakes,
6248
+ // and "everything else is sea" is exactly what a background expresses
6249
+ // — geometry-free, gap-free at every antimeridian and pole. Without
6250
+ // it the sea outside coverage was bare S-52 NODTA, a light grey that
6251
+ // read as "charted ground" with a hatch on it.
6252
+ L({
6253
+ id: "chrtbase-ocean",
6254
+ type: "background",
6255
+ paint: { "background-color": T.water }
6256
+ }),
6257
+ L({
6258
+ id: "chrtbase-earth",
6259
+ type: "fill",
6260
+ source: src,
6261
+ "source-layer": "base_earth",
6262
+ paint: { "fill-color": T.earth }
6263
+ }),
6264
+ L({
6265
+ id: "chrtbase-water",
6266
+ type: "fill",
6267
+ source: src,
6268
+ "source-layer": "base_water",
6269
+ paint: { "fill-color": T.water }
6270
+ }),
6271
+ L({
6272
+ id: "chrtbase-river",
6273
+ type: "line",
6274
+ source: src,
6275
+ "source-layer": "base_river",
6276
+ minzoom: 9,
6277
+ paint: {
6278
+ "line-color": T.river,
6279
+ "line-width": ["interpolate", ["linear"], ["zoom"], 9, 0.6, 14, 1.6]
6280
+ }
6281
+ }),
6282
+ L({
6283
+ id: "chrtbase-boundary",
6284
+ type: "line",
6285
+ source: src,
6286
+ "source-layer": "base_boundary",
6287
+ paint: { "line-color": T.boundary, "line-width": 0.6, "line-opacity": 0.8 }
6288
+ }),
6289
+ // 2. THE DEFAULT: no coverage, everywhere, as a geometry-free background.
6290
+ // Constant opacity by design — see the header. What kills it is being
6291
+ // painted OVER, never fading.
6292
+ L({
6293
+ id: "chrtbase-nocoverage",
6294
+ type: "background",
6295
+ paint: { "background-pattern": NOCOVERAGE_PATTERN, "background-opacity": 0.55 }
6296
+ }),
6297
+ // 3. The jurisdiction: countries whose charts CHRT carries go tan — the
6298
+ // same tan as CHRTland, so "land in our world" is ONE colour. Opaque,
6299
+ // so it takes the crosshatch off its ground entirely.
6300
+ L({
6301
+ id: "chrtbase-jurisdiction",
6302
+ type: "fill",
6303
+ source: src,
6304
+ "source-layer": "jurisdiction",
6305
+ paint: { "fill-color": T.jurisdiction }
6306
+ }),
6307
+ // 4. States — internal boundaries and names, inside the jurisdiction
6308
+ // only (owner decision 2026-08-28). A hairline a shade deeper than the
6309
+ // tan, and quiet letter-spaced caps that fade as harbour detail takes
6310
+ // over. The one deliberate exception to no-names-on-the-basemap:
6311
+ // ADMIN furniture, nothing else — see the fence test.
6312
+ L({
6313
+ id: "chrtbase-state-boundary",
6314
+ type: "line",
6315
+ source: src,
6316
+ "source-layer": "state_boundary",
6317
+ minzoom: 4,
6318
+ paint: {
6319
+ "line-color": T.stateLine,
6320
+ "line-width": ["interpolate", ["linear"], ["zoom"], 4, 0.5, 10, 1],
6321
+ "line-dasharray": [3, 2],
6322
+ "line-opacity": 0.8
6323
+ }
6324
+ }),
6325
+ // 5. Country borders, drawn to be HOISTED above the whole chart by the
6326
+ // generator (owner, 2026-08-31: "where there is charted land, can we
6327
+ // add country borders on top of everything? I want it clear where
6328
+ // there is a boarder."). Sourced from NE boundary_lines_land — land
6329
+ // borders only, never coastline — so riding on top cannot stroke a
6330
+ // coast. Dashed so it reads as an administrative line, not chart
6331
+ // linework.
6332
+ L({
6333
+ id: "chrtbase-country-boundary",
6334
+ type: "line",
6335
+ source: src,
6336
+ "source-layer": "country_boundary",
6337
+ paint: {
6338
+ "line-color": T.boundary,
6339
+ "line-width": ["interpolate", ["linear"], ["zoom"], 3, 0.8, 8, 1.4, 12, 2.2],
6340
+ "line-dasharray": [4, 2],
6341
+ "line-opacity": 0.9
6342
+ }
6343
+ }),
6344
+ L({
6345
+ id: "chrtbase-state-label",
6346
+ type: "symbol",
6347
+ source: src,
6348
+ "source-layer": "state_label",
6349
+ minzoom: 4,
6350
+ maxzoom: 10,
6351
+ layout: {
6352
+ "text-field": ["upcase", ["get", "name"]],
6353
+ "text-font": ["Open Sans Regular"],
6354
+ "text-size": ["interpolate", ["linear"], ["zoom"], 4, 9, 8, 12],
6355
+ "text-letter-spacing": 0.25,
6356
+ "text-allow-overlap": false,
6357
+ "text-padding": 8
6358
+ },
6359
+ paint: {
6360
+ "text-color": T.stateLabel,
6361
+ "text-halo-color": T.stateHalo,
6362
+ "text-halo-width": 1.2,
6363
+ "text-opacity": ["interpolate", ["linear"], ["zoom"], 4, 0.7, 8, 0.85, 10, 0.4]
6364
+ }
6365
+ })
6366
+ ];
6367
+ }
6368
+ function baseInlandWaterLayers(opts = {}) {
6369
+ const palette = opts.palette ?? "DAY";
6370
+ const T = BASE_THEMES[palette] ?? BASE_THEMES.DAY;
6371
+ const src = opts.sourceId ?? BASE_REGION;
6372
+ const L = (o) => o;
6373
+ const NE_TIER = ["!", ["has", "kind"]];
6374
+ const PLANET_TIER = ["has", "kind"];
6375
+ const HANDOVER = 9;
6376
+ return [
6377
+ // ── The fill pair, SPLIT BY TIER at the handover ────────────────────────
6378
+ //
6379
+ // Both tiers share `inland_water` (tile-join concatenates the same layer
6380
+ // name), and an unsplit fill draws BOTH — which means the NE polygon is
6381
+ // painted over the planet one at every zoom. That is not a cosmetic
6382
+ // double-draw: NE 10m carries no interior rings, so its blob has no
6383
+ // ISLANDS in it, and painting it over the planet's holed polygons buries
6384
+ // every island in the lake under "uncharted water". Owner-reported at
6385
+ // Winnipesaukee z11.7 ("there is land (and islands) being covered by the
6386
+ // 'no chart here' boundary"), and measured: rendering the planet tier
6387
+ // alone brings back the whole archipelago — Rattlesnake, Bear, Long
6388
+ // Island, Diamond, the Barndoors — while the NE tier alone is a faceted
6389
+ // blob whose edges bear no relation to the shoreline drawn beneath it.
6390
+ //
6391
+ // So each tier owns its own zoom range, the same handover the shoreline
6392
+ // below uses. Known residue, measured over the whole archive: of the 141
6393
+ // NE lakes we still carry, 138 have planet water beneath them; the
6394
+ // exceptions are two unnamed western-Alaska tundra lakes and a 2e-5 deg²
6395
+ // Lake Michigan sliver, which lose their rough fill above z9 because the
6396
+ // planet has no `water` polygon there at all (checked against the raw
6397
+ // pre-clip NDJSON — not a clip artefact).
6398
+ L({
6399
+ id: "chrtbase-inland-water",
6400
+ type: "fill",
6401
+ source: src,
6402
+ "source-layer": "inland_water",
6403
+ filter: NE_TIER,
6404
+ maxzoom: HANDOVER,
6405
+ paint: { "fill-color": T.inlandWater }
6406
+ }),
6407
+ L({
6408
+ id: "chrtbase-inland-hatch",
6409
+ type: "fill",
6410
+ source: src,
6411
+ "source-layer": "inland_water",
6412
+ filter: NE_TIER,
6413
+ maxzoom: HANDOVER,
6414
+ paint: { "fill-pattern": UNCHARTED_PATTERN, "fill-opacity": 0.7 }
6415
+ }),
6416
+ L({
6417
+ id: "chrtbase-inland-water-detail",
6418
+ type: "fill",
6419
+ source: src,
6420
+ "source-layer": "inland_water",
6421
+ filter: PLANET_TIER,
6422
+ minzoom: HANDOVER,
6423
+ paint: { "fill-color": T.inlandWater }
6424
+ }),
6425
+ L({
6426
+ id: "chrtbase-inland-hatch-detail",
6427
+ type: "fill",
6428
+ source: src,
6429
+ "source-layer": "inland_water",
6430
+ filter: PLANET_TIER,
6431
+ minzoom: HANDOVER,
6432
+ paint: { "fill-pattern": UNCHARTED_PATTERN, "fill-opacity": 0.7 }
6433
+ }),
6434
+ // The rough shoreline (owner ask 2026-08-28). Drawn ABOVE the hatch so
6435
+ // the edge stays crisp through the texture, and BELOW the charted-water
6436
+ // repaint clones the generator splices in after it — so wherever the
6437
+ // chart repaints its own water, the rough NE10m line yields to the
6438
+ // chart's COALNE instead of double-striking it.
6439
+ //
6440
+ // NE-tier geometry ONLY (`kind` absent): the planet detail tier joins
6441
+ // the same source-layer from z9 as MANY polygons whose chunk-clip seams
6442
+ // rejoin invisibly as FILLS — but a line layer strokes every seam, and
6443
+ // Winnipesaukee rendered as a mesh of internal edges. Planet features
6444
+ // carry `kind` (the INLAND_KINDS filter's own field); NE's carry only
6445
+ // `name`, so the absence of `kind` IS the tier discriminator.
6446
+ L({
6447
+ id: "chrtbase-inland-shore",
6448
+ type: "line",
6449
+ source: src,
6450
+ "source-layer": "inland_water",
6451
+ filter: NE_TIER,
6452
+ // Hands the shoreline over to the planet tier at ITS floor. Both
6453
+ // tiers live in `inland_water` from z9 up (tile-join concatenates the
6454
+ // same layer name), and their FILLS merge invisibly — but two
6455
+ // outlines do not: NE 10m generalises Winnipesaukee's shape, so its
6456
+ // outline ran straight across the middle of the lake the planet had
6457
+ // drawn correctly around. The coarse line is right only while it is
6458
+ // the only shape we have.
6459
+ maxzoom: HANDOVER,
6460
+ paint: {
6461
+ "line-color": T.inlandShore,
6462
+ "line-width": ["interpolate", ["linear"], ["zoom"], 4, 0.5, 10, 1.1, 14, 1.6],
6463
+ "line-opacity": 0.9
6464
+ }
6465
+ }),
6466
+ // The PLANET tier's shoreline (owner ask 2026-08-31: "uncharted small
6467
+ // lakes render with no outline"). It reads its own `inland_shore` layer,
6468
+ // NOT `inland_water`, and that indirection is the whole fix: the mesh
6469
+ // that forced the `!has kind` fence above is a CLIP artefact — an MVT
6470
+ // feature arrives already cut to its own z10 tile, so stroking the
6471
+ // polygon strokes every tile boundary it crosses. The build now walks
6472
+ // those rings, drops the axis-aligned runs that sit on the read zoom's
6473
+ // tile grid, and emits what is left as lines (measured on one New
6474
+ // England chunk: 61 long axis-aligned mesh edges → 5, and the 5 survivors
6475
+ // are 0.02–0.5 tiles off the grid, i.e. real straight water edges).
6476
+ //
6477
+ // Thinner and quieter than the NE line: this is finer geometry describing
6478
+ // water nobody has surveyed, and approximate data should look
6479
+ // approximate.
6480
+ L({
6481
+ id: "chrtbase-inland-shore-detail",
6482
+ type: "line",
6483
+ source: src,
6484
+ "source-layer": "inland_shore",
6485
+ minzoom: HANDOVER,
6486
+ paint: {
6487
+ "line-color": T.inlandShore,
6488
+ "line-width": ["interpolate", ["linear"], ["zoom"], 9, 0.5, 12, 0.9, 15, 1.2],
6489
+ "line-opacity": 0.85
6490
+ }
6491
+ }),
6492
+ // The lake's name (owner ask 2026-08-28) — the second scoped exception
6493
+ // to no-names-on-the-basemap, after the states: WATER-BODY names only.
6494
+ // No maxzoom: an uncharted lake keeps its name at every zoom; over
6495
+ // charted lakes the chart's own SEAARE label takes over at detail zooms
6496
+ // and collision culls the echo.
6497
+ //
6498
+ // The archive's `inland_water_label` anchors (v3+). v3 shipped ONE
6499
+ // PointOnSurface anchor per lake, and a big lake's single anchor pans
6500
+ // off-viewport — Winnipesaukee at z11.7 read nameless with its name two
6501
+ // tiles west of the screen (owner-reported). Labelling the polygons
6502
+ // instead is NOT the fix: tippecanoe's coalescing drops the NE polygon
6503
+ // from some z11 tiles (measured: 11/619/747 carries only planet pieces),
6504
+ // so a polygon-sourced label vanishes tile by tile. v4 therefore bakes a
6505
+ // deterministic anchor GRID into big lakes — the paper-chart convention
6506
+ // of repeating a name along a large water body — and this one layer
6507
+ // renders however many anchors are in view; collision culls the rest.
6508
+ // Planet-tier water-body NAMES (owner ask 2026-08-31: NE 10m names only
6509
+ // 113 US lakes). Emitted BELOW the NE layer on purpose — MapLibre places
6510
+ // symbols topmost-layer-first, so the big NE lake wins collision against
6511
+ // the pond beside it, and both lose to the chart's own labels (every ENC
6512
+ // class is emitted after this pair).
6513
+ //
6514
+ // The size threshold is OSM's own `min_zoom`, carried through the build:
6515
+ // Lake Winnipesaukee reads 10, Squam Lake 12, so a pond simply is not in
6516
+ // the filter until the user is close enough for it to be the answer to
6517
+ // "what is that water". `["zoom"]` in a filter evaluates at integer zoom
6518
+ // levels and removes the feature from placement entirely — verified
6519
+ // against maplibre-gl-native rather than assumed, because a text-size-0
6520
+ // gate would still claim a collision slot.
6521
+ //
6522
+ // The tiers are separate LAYERS, not a filtered split of one — see the
6523
+ // builder for why: an archive that a not-yet-deployed style would
6524
+ // mis-render cannot be promoted first, and this one has to be.
6525
+ L({
6526
+ id: "chrtbase-inland-label-detail",
6527
+ type: "symbol",
6528
+ source: src,
6529
+ "source-layer": "inland_water_label_detail",
6530
+ minzoom: HANDOVER,
6531
+ filter: [">=", ["zoom"], ["get", "min_zoom"]],
6532
+ layout: {
6533
+ "text-field": ["get", "name"],
6534
+ "text-font": ["Open Sans Regular"],
6535
+ "text-size": ["interpolate", ["linear"], ["zoom"], 9, 9, 12, 10.5, 15, 11],
6536
+ "text-max-width": 8,
6537
+ "text-allow-overlap": false,
6538
+ "text-padding": 6,
6539
+ // Bigger water first within the tier — min_zoom IS the size rank.
6540
+ "symbol-sort-key": ["get", "min_zoom"]
6541
+ },
6542
+ paint: {
6543
+ "text-color": T.inlandLabel,
6544
+ "text-halo-color": T.inlandHalo,
6545
+ "text-halo-width": 1,
6546
+ "text-opacity": 0.9
6547
+ }
6548
+ }),
6549
+ L({
6550
+ id: "chrtbase-inland-label",
6551
+ type: "symbol",
6552
+ source: src,
6553
+ "source-layer": "inland_water_label",
6554
+ minzoom: 5,
6555
+ layout: {
6556
+ "text-field": ["get", "name"],
6557
+ "text-font": ["Open Sans Regular"],
6558
+ "text-size": ["interpolate", ["linear"], ["zoom"], 5, 9.5, 10, 12],
6559
+ "text-max-width": 7,
6560
+ "text-allow-overlap": false,
6561
+ // Zoom-scaled spacing for the anchor GRID: at z8.9 the 0.08-deg
6562
+ // anchors sit ~54 px apart and a flat padding let six copies of
6563
+ // "Lake Ontario" crowd one screen (owner-reported 2026-08-30).
6564
+ // Wide collision boxes at low zoom thin the grid to the repeat-
6565
+ // by-convention cadence; by z12 the anchors are ~850 px apart and
6566
+ // the padding relaxes to normal.
6567
+ "text-padding": ["interpolate", ["linear"], ["zoom"], 5, 80, 9, 64, 12, 16]
6568
+ },
6569
+ paint: {
6570
+ "text-color": T.inlandLabel,
6571
+ "text-halo-color": T.inlandHalo,
6572
+ "text-halo-width": 1.1
6573
+ }
6574
+ })
6575
+ ];
6576
+ }
6577
+
6578
+ // src/atlas/eink-portrayal.ts
6579
+ var EINK_DOTS_PATTERN = `${CHRT_ICON_PREFIX}eink-dots`;
6580
+ var EINK_HATCH_PATTERN = `${CHRT_ICON_PREFIX}eink-hatch`;
6581
+ var EINK_DANGER_PATTERN = `${CHRT_ICON_PREFIX}eink-danger`;
6582
+ var EINK_LINE_SCALE = 1.5;
6583
+ var EINK_LINE_MIN_PX = 1;
6584
+ var EINK_TEXT_SCALE = 1.15;
6585
+ var EINK_HALO_MIN_PX = 1.5;
6586
+ var EINK_DANGER_ICON_SCALE = 1.35;
6587
+ var EINK_WASH_CUTOFF = 0.3;
6588
+ var EINK_SAFETY_CONTOUR_WIDTH = [2, 3.5];
6589
+ var EINK_DANGER_CLASSES = /* @__PURE__ */ new Set(["OBSTRN", "WRECKS", "UWTROC"]);
6590
+ var METRIC_CONTOUR_LADDER_M = [0, 2, 5, 10, 15, 20, 30, 50, 100, 200, 500, 1e3];
6591
+ var IMPERIAL_CONTOUR_LADDER_M = [
6592
+ 1.8,
6593
+ 3.6,
6594
+ 5.4,
6595
+ 9.1,
6596
+ 18.2,
6597
+ 27.4,
6598
+ 36.5,
6599
+ 54.8,
6600
+ 91.4,
6601
+ 182.8,
6602
+ 365.7,
6603
+ 914.4
6604
+ ];
6605
+ function snapToLadder(safetyM, ladder) {
6606
+ for (const rung of ladder) if (rung >= safetyM - 1e-6) return rung;
6607
+ return null;
6608
+ }
6609
+ function safetyContourValues(safetyContourM) {
6610
+ const s = effectiveSafetyContour(safetyContourM);
6611
+ const out = [snapToLadder(s, METRIC_CONTOUR_LADDER_M), snapToLadder(s, IMPERIAL_CONTOUR_LADDER_M)];
6612
+ return [...new Set(out.filter((v) => v !== null))];
6613
+ }
6614
+ function isExpr(v) {
6615
+ return Array.isArray(v) && typeof v[0] === "string";
6616
+ }
6617
+ function scaleNumeric(v, k, min = 0) {
6618
+ if (typeof v === "number") return Math.max(v * k, v > 0 ? min : 0);
6619
+ if (!isExpr(v)) return v;
6620
+ const op = v[0];
6621
+ if (op === "interpolate" || op === "interpolate-hcl" || op === "interpolate-lab") {
6622
+ const out = v.slice(0, 3);
6623
+ for (let i = 3; i < v.length; i += 2) out.push(v[i], scaleNumeric(v[i + 1], k, min));
6624
+ return out;
6625
+ }
6626
+ if (op === "step") {
6627
+ const out = [op, v[1], scaleNumeric(v[2], k, min)];
6628
+ for (let i = 3; i < v.length; i += 2) out.push(v[i], scaleNumeric(v[i + 1], k, min));
6629
+ return out;
6630
+ }
6631
+ if (op === "literal") return v;
6632
+ return ["max", ["*", v, k], min];
6633
+ }
6634
+ function objectClass(layer) {
6635
+ const md = layer.metadata;
6636
+ const c = md?.["s52:objectClass"];
6637
+ return typeof c === "string" ? c : null;
6638
+ }
6639
+ function scaffoldId(prefix, baseId) {
6640
+ return `${prefix}${baseId.replace(/-band(\d)/g, "-b$1")}`;
6641
+ }
6642
+ var DRVAL1 = ["coalesce", ["get", "DRVAL1"], ["get", "drval1"], -1];
6643
+ var DRVAL2 = ["coalesce", ["get", "DRVAL2"], ["get", "drval2"], 0];
6644
+ var VALDCO = ["to-number", ["coalesce", ["get", "VALDCO"], ["get", "valdco"], -1], -1];
6645
+ function withFilter(base, extra) {
6646
+ return base === void 0 ? extra : ["all", base, extra];
6647
+ }
6648
+ function applyEinkPortrayal(style, palette, opts = {}) {
6649
+ if (palette !== "EINK_COLOR" && palette !== "EINK_MONO" && palette !== "EINK_NIGHT") return style;
6650
+ const mono = isEinkGreyTable(palette);
6651
+ const night = palette === "EINK_NIGHT";
6652
+ const safetyInk = resolveColor("DEPSC", palette);
6653
+ const groundInk = resolveColor("UIBCK", palette);
6654
+ const safety = effectiveSafetyContour(opts.safetyContour ?? DEFAULT_SAFETY_CONTOUR_M);
6655
+ const safetyValues = safetyContourValues(safety);
6656
+ const out = [];
6657
+ for (const original of style.layers) {
6658
+ const id = original.id;
6659
+ const type = original.type;
6660
+ if (type === "hillshade" || id.startsWith("chrtland-fade-")) continue;
6661
+ const layer = structuredClone(original);
6662
+ const paint = layer.paint ??= {};
6663
+ const layout = layer.layout;
6664
+ const cls = objectClass(layer);
6665
+ if (type === "line") {
6666
+ if (paint["line-width"] !== void 0) {
6667
+ paint["line-width"] = scaleNumeric(paint["line-width"], EINK_LINE_SCALE, EINK_LINE_MIN_PX);
6668
+ } else {
6669
+ paint["line-width"] = EINK_LINE_SCALE;
6670
+ }
6671
+ const o = paint["line-opacity"];
6672
+ if (typeof o === "number" && o > 0 && o < 1) paint["line-opacity"] = 1;
6673
+ if (paint["line-blur"] !== void 0) paint["line-blur"] = 0;
6674
+ }
6675
+ if (type === "fill") {
6676
+ const o = paint["fill-opacity"];
6677
+ if (typeof o === "number" && o > 0 && o < 1) {
6678
+ if (cls && EINK_DANGER_CLASSES.has(cls)) {
6679
+ paint["fill-pattern"] = EINK_DANGER_PATTERN;
6680
+ paint["fill-opacity"] = 1;
6681
+ } else {
6682
+ paint["fill-opacity"] = o < EINK_WASH_CUTOFF ? 0 : 1;
6683
+ }
6684
+ }
6685
+ if (paint["fill-antialias"] === void 0) paint["fill-antialias"] = true;
6686
+ }
6687
+ if (type === "background" && night && id === "chrt-background") {
6688
+ paint["background-pattern"] = NOCOVERAGE_PATTERN;
6689
+ }
6690
+ if (type === "background") {
6691
+ const o = paint["background-opacity"];
6692
+ if (typeof o === "number" && o > 0 && o < 1) paint["background-opacity"] = 1;
6693
+ }
6694
+ if (type === "circle") {
6695
+ const blur = paint["circle-blur"];
6696
+ if (typeof blur === "number" && blur >= 0.5) {
6697
+ paint["circle-opacity"] = 0;
6698
+ } else {
6699
+ if (blur !== void 0) paint["circle-blur"] = 0;
6700
+ const o = paint["circle-opacity"];
6701
+ if (typeof o === "number" && o > 0 && o < 1) paint["circle-opacity"] = 1;
6702
+ }
6703
+ }
6704
+ if (type === "symbol") {
6705
+ if (layout?.["text-field"] !== void 0) {
6706
+ if (layout["text-size"] !== void 0) {
6707
+ layout["text-size"] = scaleNumeric(layout["text-size"], EINK_TEXT_SCALE);
6708
+ }
6709
+ if (paint["text-opacity"] !== void 0) paint["text-opacity"] = 1;
6710
+ if (typeof paint["text-halo-color"] === "string") paint["text-halo-color"] = groundInk;
6711
+ const hw = paint["text-halo-width"];
6712
+ paint["text-halo-width"] = typeof hw === "number" ? Math.max(hw, EINK_HALO_MIN_PX) : hw ?? EINK_HALO_MIN_PX;
6713
+ if (paint["text-halo-blur"] !== void 0) paint["text-halo-blur"] = 0;
6714
+ }
6715
+ const io = paint["icon-opacity"];
6716
+ if (typeof io === "number" && io > 0 && io < 1) paint["icon-opacity"] = 1;
6717
+ if (cls && EINK_DANGER_CLASSES.has(cls) && layout?.["icon-image"] !== void 0) {
6718
+ layout["icon-size"] = scaleNumeric(layout["icon-size"] ?? 1, EINK_DANGER_ICON_SCALE);
6719
+ }
6720
+ }
6721
+ out.push(layer);
6722
+ if (type === "fill" && cls === "DEPARE" && mono) {
6723
+ const drying = ["all", ["<", DRVAL1, 0], ["<=", DRVAL2, 0]];
6724
+ out.push({
6725
+ ...structuredClone(original),
6726
+ id: scaffoldId("chrt-eink-depth-of-", id),
6727
+ filter: withFilter(original.filter, ["<", DRVAL1, safety]),
6728
+ paint: {
6729
+ "fill-pattern": ["case", drying, EINK_HATCH_PATTERN, EINK_DOTS_PATTERN]
6730
+ }
6731
+ });
6732
+ }
6733
+ if (type === "line" && cls === "DEPCNT") {
6734
+ const isSafety = ["any", ...safetyValues.map((v) => ["<", ["abs", ["-", VALDCO, v]], 0.05])];
6735
+ out.push({
6736
+ ...structuredClone(original),
6737
+ id: scaffoldId("chrt-eink-safety-of-", id),
6738
+ filter: withFilter(original.filter, isSafety),
6739
+ paint: {
6740
+ "line-color": safetyInk,
6741
+ "line-width": [
6742
+ "interpolate",
6743
+ ["linear"],
6744
+ ["zoom"],
6745
+ 8,
6746
+ EINK_SAFETY_CONTOUR_WIDTH[0],
6747
+ 14,
6748
+ EINK_SAFETY_CONTOUR_WIDTH[1]
6749
+ ]
6750
+ },
6751
+ layout: { "line-join": "round", "line-cap": "round" }
6752
+ });
6753
+ }
6754
+ }
6755
+ return {
6756
+ ...style,
6757
+ // No transitions: a property change is one repaint, never an animation.
6758
+ ...{ transition: { duration: 0, delay: 0 } },
6759
+ metadata: { ...style.metadata, "chrt:eink": night ? "night" : mono ? "mono" : "color" },
6760
+ layers: out
6761
+ };
6762
+ }
6763
+
6764
+ // src/atlas/map-live.ts
6765
+ function isMapLive(map) {
6766
+ return map._removed !== true;
6767
+ }
6768
+
6769
+ // src/atlas/s52-linestyles-generated.ts
5303
6770
  var S52_LINESTYLES = {
5304
6771
  ACHARE51: {
5305
6772
  name: "ACHARE51",
@@ -5925,252 +7392,37 @@ function houseWidth(meta) {
5925
7392
  }
5926
7393
  var S52_LINE_SPRITE_PREFIX = "LC:";
5927
7394
  function lineSpriteId(lineStyleName) {
5928
- return `${S52_LINE_SPRITE_PREFIX}${lineStyleName}`;
5929
- }
5930
- function lineStylePaint(name) {
5931
- const meta = S52_LINESTYLES[name];
5932
- if (!meta) {
5933
- throw new Error(
5934
- `Unknown S-52 linestyle "${name}". Known: ${Object.keys(S52_LINESTYLES).length} styles (regenerate with: bun run scripts/extract-opencpn-linestyles.ts)`
5935
- );
5936
- }
5937
- return {
5938
- "line-pattern": lineSpriteId(meta.name),
5939
- "line-width": houseWidth(meta)
5940
- };
5941
- }
5942
- function lineStylePaintCase(cases, fallback) {
5943
- const resolve = (name) => {
5944
- const meta = S52_LINESTYLES[name];
5945
- if (!meta) throw new Error(`Unknown S-52 linestyle "${name}"`);
5946
- return meta;
5947
- };
5948
- const fallbackMeta = resolve(fallback);
5949
- const pattern = ["case"];
5950
- const width = ["case"];
5951
- for (const [condition, name] of cases) {
5952
- const meta = resolve(name);
5953
- pattern.push(condition, lineSpriteId(meta.name));
5954
- width.push(condition, houseWidth(meta));
5955
- }
5956
- pattern.push(lineSpriteId(fallbackMeta.name));
5957
- width.push(houseWidth(fallbackMeta));
5958
- return { "line-pattern": pattern, "line-width": width };
5959
- }
5960
-
5961
- // src/atlas/s52-pattern.ts
5962
- var S52_PATTERN_SPRITE_PREFIX = "AP:";
5963
- function patternSpriteId(patternName) {
5964
- return `${S52_PATTERN_SPRITE_PREFIX}${patternName}`;
5965
- }
5966
-
5967
- // src/atlas/s52-conditional.ts
5968
- var DEFAULT_SAFETY_CONTOUR_M = 10;
5969
- var DEPARE_DEEP_CUT_M = 18.2;
5970
- var DEPARE_SHALLOW_CUT_M = 2;
5971
- function effectiveSafetyContour(safetyContourM) {
5972
- if (!Number.isFinite(safetyContourM)) return DEFAULT_SAFETY_CONTOUR_M;
5973
- return Math.min(Math.max(safetyContourM, DEPARE_SHALLOW_CUT_M), DEPARE_DEEP_CUT_M);
5974
- }
5975
- function depareColor(props, ct = "DAY", safetyContourM = DEFAULT_SAFETY_CONTOUR_M) {
5976
- const drval1 = Number(props.DRVAL1 ?? props.drval1 ?? -1);
5977
- const drval2 = Number(props.DRVAL2 ?? props.drval2 ?? drval1);
5978
- const safety = effectiveSafetyContour(safetyContourM);
5979
- if (drval1 < 0 && drval2 <= 0) return resolveColor("DEPIT", ct);
5980
- if (drval1 < DEPARE_SHALLOW_CUT_M) return resolveColor("DEPVS", ct);
5981
- if (drval1 < safety) return resolveColor("DEPMS", ct);
5982
- if (drval1 < DEPARE_DEEP_CUT_M) return resolveColor("DEPMD", ct);
5983
- return resolveColor("DEPDW", ct);
5984
- }
5985
- var DEPARE_FILL_BRANCH_TOKENS = ["DEPIT", "DEPVS", "DEPMS", "DEPMD"];
5986
- var DEPARE_FILL_FALLBACK_TOKEN = "DEPDW";
5987
- function depareFillExpression(ct = "DAY", safetyContourM = DEFAULT_SAFETY_CONTOUR_M) {
5988
- const safety = effectiveSafetyContour(safetyContourM);
5989
- const [it, vs, ms, md] = DEPARE_FILL_BRANCH_TOKENS;
5990
- return [
5991
- "case",
5992
- [
5993
- "all",
5994
- ["<", ["coalesce", ["get", "DRVAL1"], ["get", "drval1"], -1], 0],
5995
- ["<=", ["coalesce", ["get", "DRVAL2"], ["get", "drval2"], 0], 0]
5996
- ],
5997
- resolveColor(it, ct),
5998
- ["<", ["coalesce", ["get", "DRVAL1"], ["get", "drval1"], -1], DEPARE_SHALLOW_CUT_M],
5999
- resolveColor(vs, ct),
6000
- ["<", ["coalesce", ["get", "DRVAL1"], ["get", "drval1"], -1], safety],
6001
- resolveColor(ms, ct),
6002
- ["<", ["coalesce", ["get", "DRVAL1"], ["get", "drval1"], -1], DEPARE_DEEP_CUT_M],
6003
- resolveColor(md, ct),
6004
- resolveColor(DEPARE_FILL_FALLBACK_TOKEN, ct)
6005
- ];
6006
- }
6007
- function listAttrString(attr) {
6008
- return ["to-string", ["coalesce", ["get", attr], ["get", attr.toLowerCase()], ""]];
6009
- }
6010
- function listAttrCase(attr, cases, fallback) {
6011
- const attrStr = listAttrString(attr);
6012
- const expr = ["case"];
6013
- for (const [code, value] of cases) {
6014
- expr.push([">=", ["index-of", `"${code}"`, attrStr], 0], value);
6015
- }
6016
- for (const [code, value] of cases) {
6017
- expr.push(["==", attrStr, code], value);
6018
- }
6019
- for (const [code, value] of cases) {
6020
- expr.push(["==", attrStr, `[${code}]`], value);
6021
- }
6022
- expr.push(fallback);
6023
- return expr;
6024
- }
6025
- function listAttrHas(attr, codes) {
6026
- const attrStr = listAttrString(attr);
6027
- const clauses = [];
6028
- for (const code of codes) {
6029
- clauses.push([">=", ["index-of", `"${code}"`, attrStr], 0]);
6030
- clauses.push(["==", attrStr, code]);
6031
- clauses.push(["==", attrStr, `[${code}]`]);
6032
- }
6033
- return ["any", ...clauses];
6034
- }
6035
- function colourCodeCase(cases, fallback) {
6036
- return listAttrCase("COLOUR", cases, fallback);
6037
- }
6038
- function lightColor(props, ct = "DAY") {
6039
- const raw = props.COLOUR ?? props.colour;
6040
- const code = Array.isArray(raw) ? Number(raw[0]) : typeof raw === "string" && raw.startsWith("[") ? Number(JSON.parse(raw)[0]) : Number(raw ?? 0);
6041
- switch (code) {
6042
- case 1:
6043
- return resolveColor("LITWT", ct);
6044
- case 3:
6045
- return resolveColor("LITRD", ct);
6046
- case 4:
6047
- return resolveColor("LITGN", ct);
6048
- case 5:
6049
- return resolveColor("LITBL", ct);
6050
- case 6:
6051
- return resolveColor("LITYW", ct);
6052
- case 11:
6053
- return resolveColor("LITOR", ct);
6054
- default:
6055
- return resolveColor("LITYW", ct);
6056
- }
6057
- }
6058
- function lightFillExpression(ct = "DAY") {
6059
- return colourCodeCase(
6060
- [
6061
- ["3", resolveColor("LITRD", ct)],
6062
- ["4", resolveColor("LITGN", ct)],
6063
- ["6", resolveColor("LITYW", ct)],
6064
- ["11", resolveColor("LITOR", ct)],
6065
- ["5", resolveColor("LITBL", ct)],
6066
- ["1", resolveColor("LITWT", ct)]
6067
- ],
6068
- resolveColor("LITYW", ct)
6069
- );
6070
- }
6071
- function lateralColor(props, ct = "DAY") {
6072
- const catlam = Number(props.CATLAM ?? props.catlam ?? 0);
6073
- if (catlam === 1) return resolveColor("LITRD", ct);
6074
- if (catlam === 2) return resolveColor("LITGN", ct);
6075
- return resolveColor("LITYW", ct);
6076
- }
6077
- function lateralFillExpression(ct = "DAY") {
6078
- return [
6079
- "match",
6080
- ["coalesce", ["get", "CATLAM"], ["get", "catlam"], 0],
6081
- 1,
6082
- resolveColor("LITRD", ct),
6083
- 2,
6084
- resolveColor("LITGN", ct),
6085
- resolveColor("LITYW", ct)
6086
- ];
6087
- }
6088
- function hazardColor(props, ct = "DAY") {
6089
- const watlev = Number(props.WATLEV ?? props.watlev ?? 0);
6090
- const valsou = props.VALSOU ?? props.valsou;
6091
- if (watlev === 1 || watlev === 2) return resolveColor("DNGHL", ct);
6092
- if (valsou != null && Number(valsou) < 2) return resolveColor("DNGHL", ct);
6093
- return resolveColor("RESBL", ct);
6094
- }
6095
- function hazardFillExpression(ct = "DAY") {
6096
- return [
6097
- "case",
6098
- [
6099
- "any",
6100
- ["==", ["coalesce", ["get", "WATLEV"], ["get", "watlev"], 0], 1],
6101
- ["==", ["coalesce", ["get", "WATLEV"], ["get", "watlev"], 0], 2]
6102
- ],
6103
- resolveColor("DNGHL", ct),
6104
- [
6105
- "all",
6106
- ["has", "VALSOU"],
6107
- ["<", ["get", "VALSOU"], 2]
6108
- ],
6109
- resolveColor("DNGHL", ct),
6110
- resolveColor("RESBL", ct)
6111
- ];
6112
- }
6113
- function vegatnPatternExpression() {
6114
- return [
6115
- "match",
6116
- ["coalesce", ["get", "CATVEG"], ["get", "catveg"], 0],
6117
- 1,
6118
- patternSpriteId("VEGATN03"),
6119
- // grass
6120
- 3,
6121
- patternSpriteId("VEGATN03"),
6122
- // bush
6123
- 4,
6124
- patternSpriteId("VEGATN04"),
6125
- // deciduous wood
6126
- 5,
6127
- patternSpriteId("VEGATN04"),
6128
- // coniferous wood
6129
- 6,
6130
- patternSpriteId("VEGATN04"),
6131
- // wood (mixed)
6132
- 7,
6133
- patternSpriteId("MARSHES1"),
6134
- // mangroves
6135
- 10,
6136
- patternSpriteId("VEGATN03"),
6137
- // scrub
6138
- 11,
6139
- patternSpriteId("VEGATN04"),
6140
- // tree
6141
- 12,
6142
- patternSpriteId("VEGATN04"),
6143
- // evergreen
6144
- 16,
6145
- patternSpriteId("MARSHES1"),
6146
- // swamp
6147
- 17,
6148
- patternSpriteId("MARSHES1"),
6149
- // marsh
6150
- 21,
6151
- patternSpriteId("VEGATN04"),
6152
- // savannah
6153
- patternSpriteId("VEGATN03")
6154
- // default: grass pattern
6155
- ];
6156
- }
6157
- function daymarkColor(props, ct = "DAY") {
6158
- const raw = props.COLOUR ?? props.colour;
6159
- const code = Array.isArray(raw) ? Number(raw[0]) : typeof raw === "string" && raw.startsWith("[") ? Number(JSON.parse(raw)[0]) : Number(raw ?? 0);
6160
- switch (code) {
6161
- case 1:
6162
- return resolveColor("LITWT", ct);
6163
- case 3:
6164
- return resolveColor("LITRD", ct);
6165
- case 4:
6166
- return resolveColor("LITGN", ct);
6167
- case 6:
6168
- return resolveColor("LITYW", ct);
6169
- case 11:
6170
- return resolveColor("LITOR", ct);
6171
- default:
6172
- return resolveColor("LITGN", ct);
7395
+ return `${S52_LINE_SPRITE_PREFIX}${lineStyleName}`;
7396
+ }
7397
+ function lineStylePaint(name) {
7398
+ const meta = S52_LINESTYLES[name];
7399
+ if (!meta) {
7400
+ throw new Error(
7401
+ `Unknown S-52 linestyle "${name}". Known: ${Object.keys(S52_LINESTYLES).length} styles (regenerate with: bun run scripts/extract-opencpn-linestyles.ts)`
7402
+ );
7403
+ }
7404
+ return {
7405
+ "line-pattern": lineSpriteId(meta.name),
7406
+ "line-width": houseWidth(meta)
7407
+ };
7408
+ }
7409
+ function lineStylePaintCase(cases, fallback) {
7410
+ const resolve = (name) => {
7411
+ const meta = S52_LINESTYLES[name];
7412
+ if (!meta) throw new Error(`Unknown S-52 linestyle "${name}"`);
7413
+ return meta;
7414
+ };
7415
+ const fallbackMeta = resolve(fallback);
7416
+ const pattern = ["case"];
7417
+ const width = ["case"];
7418
+ for (const [condition, name] of cases) {
7419
+ const meta = resolve(name);
7420
+ pattern.push(condition, lineSpriteId(meta.name));
7421
+ width.push(condition, houseWidth(meta));
6173
7422
  }
7423
+ pattern.push(lineSpriteId(fallbackMeta.name));
7424
+ width.push(houseWidth(fallbackMeta));
7425
+ return { "line-pattern": pattern, "line-width": width };
6174
7426
  }
6175
7427
 
6176
7428
  // src/atlas/s52-lookup.ts
@@ -20063,9 +21315,9 @@ function compileAtonExpression(lookup, mapSymbol = (name) => name) {
20063
21315
  expr.push(fallback);
20064
21316
  return expr;
20065
21317
  }
20066
- function atonIconExpression(objectClass) {
20067
- const lookup = S52_ATON_LOOKUPS[objectClass];
20068
- if (!lookup) throw new Error(`s52-aton-expressions: no generated lookup for ${objectClass}`);
21318
+ function atonIconExpression(objectClass2) {
21319
+ const lookup = S52_ATON_LOOKUPS[objectClass2];
21320
+ if (!lookup) throw new Error(`s52-aton-expressions: no generated lookup for ${objectClass2}`);
20069
21321
  return compileAtonExpression(lookup);
20070
21322
  }
20071
21323
  function lightsIconExpression() {
@@ -23150,7 +24402,9 @@ var LABEL_TINT = {
23150
24402
  };
23151
24403
  function tintFor(tint, ct) {
23152
24404
  const day = LABEL_TINT[tint].day;
23153
- if (ct === "DAY") return day;
24405
+ if (ct === "DAY" || ct === "EINK_COLOR") return day;
24406
+ if (ct === "EINK_MONO") return einkMonoGrey(day);
24407
+ if (ct === "EINK_NIGHT") return einkNightGrey(day);
23154
24408
  return capLuminance(day, relativeLuminance(resolveColor("CHBLK", ct)));
23155
24409
  }
23156
24410
  function nightText(tint) {
@@ -23161,10 +24415,14 @@ function nightText(tint) {
23161
24415
  }
23162
24416
  function onHalo(tint, ct) {
23163
24417
  if (ct === "NIGHT") return { textColor: nightText(tint), textHaloColor: NIGHT_KNOCKOUT };
24418
+ if (ct === "EINK_NIGHT") return { textColor: EINK_MONO_GREYS.MID, textHaloColor: EINK_MONO_GREYS.INK };
24419
+ if (isEinkColorTable(ct)) return { textColor: EINK_MONO_GREYS.INK, textHaloColor: EINK_MONO_GREYS.PAPER };
23164
24420
  return { textColor: DARK_BG, textHaloColor: tintFor(tint, ct) };
23165
24421
  }
23166
24422
  function onKnockout(tint, ct) {
23167
24423
  if (ct === "NIGHT") return { textColor: nightText(tint), textHaloColor: NIGHT_KNOCKOUT };
24424
+ if (ct === "EINK_NIGHT") return { textColor: EINK_MONO_GREYS.MID, textHaloColor: EINK_MONO_GREYS.INK };
24425
+ if (isEinkColorTable(ct)) return { textColor: EINK_MONO_GREYS.INK, textHaloColor: EINK_MONO_GREYS.PAPER };
23168
24426
  return { textColor: tintFor(tint, ct), textHaloColor: DARK_BG };
23169
24427
  }
23170
24428
  var NAV_AID_LABEL_TEXT_SIZE = [
@@ -27350,409 +28608,145 @@ function applyColorTheme(svgString, colorTable = "DAY") {
27350
28608
  }
27351
28609
  );
27352
28610
  }
27353
- function getAvailablePointSymbols() {
27354
- return Object.keys(S52_POINT_SVGS);
27355
- }
27356
- function getAvailablePatterns() {
27357
- return Object.keys(S52_PATTERN_SVGS);
27358
- }
27359
- function getAvailableLineSymbols() {
27360
- return Object.keys(S52_LINE_SVGS);
27361
- }
27362
- function getS52SymbolStats() {
27363
- return {
27364
- pointCount: Object.keys(S52_POINT_SVGS).length,
27365
- patternCount: Object.keys(S52_PATTERN_SVGS).length,
27366
- lineCount: Object.keys(S52_LINE_SVGS).length,
27367
- totalCount: Object.keys(ALL_S52_SVGS).length
27368
- };
27369
- }
27370
-
27371
- // src/atlas/s101-style.ts
27372
- var S101_ICON_PREFIX = "s101:";
27373
- var S101_SYMBOL_NAMES = new Set(Object.keys(S101_POINT_SVGS));
27374
- var S101_NO_OFFICIAL_COUNTERPART = /* @__PURE__ */ new Set([
27375
- "DNGCIR01",
27376
- "RADRFL03",
27377
- "UWTROC01",
27378
- "LNDMRK18",
27379
- "LNDMRK19",
27380
- "LIGHTHOUSE01",
27381
- "SISTAT02",
27382
- "WATTUR01"
27383
- ]);
27384
- var S101_TABLES = {
27385
- DAY: S101_DAY,
27386
- DUSK: S101_DUSK,
27387
- NIGHT: S101_NIGHT
27388
- };
27389
- var warnedTokens = /* @__PURE__ */ new Set();
27390
- function reportUnresolvedToken(token, table) {
27391
- const key = `${table}:${token}`;
27392
- if (warnedTokens.has(key)) return;
27393
- warnedTokens.add(key);
27394
- console.warn(
27395
- `[chrt/s101] colour token "${token}" is not defined in the official S-101 ${table} profile. The symbol using it will draw with a substituted colour, which on a chart changes what the symbol MEANS. Re-check the vendored assets/s101/css/${table.toLowerCase()}SvgStyle.css against the pinned catalogue commit.`
27396
- );
27397
- }
27398
- function resolveS101Color(token, table = "DAY") {
27399
- const hex = S101_TABLES[table][token];
27400
- if (hex) return hex;
27401
- reportUnresolvedToken(token, table);
27402
- return "#000000";
27403
- }
27404
- function getThemedS101Svg(symbolName, table = "DAY") {
27405
- const raw = S101_POINT_SVGS[symbolName];
27406
- if (!raw) return null;
27407
- return raw.replace(/(fill|stroke)="([A-Z][A-Z0-9]{3,5})"/g, (match, attr, token) => {
27408
- if (token === "none") return match;
27409
- const hex = S101_TABLES[table][token];
27410
- if (hex) return `${attr}="${hex}"`;
27411
- reportUnresolvedToken(token, table);
27412
- return match;
27413
- });
27414
- }
27415
- function s101ArtworkProvenance(symbolName) {
27416
- if (!symbolName) return "chrt-drawn";
27417
- if (S101_SYMBOL_NAMES.has(symbolName)) return "official";
27418
- if (getS52SvgString(symbolName) !== null) return "s52-substitute";
27419
- return "chrt-drawn";
27420
- }
27421
- function rewriteIconToS101(node) {
27422
- if (typeof node === "string") {
27423
- return S101_SYMBOL_NAMES.has(node) ? `${S101_ICON_PREFIX}${node}` : node;
27424
- }
27425
- if (Array.isArray(node)) return node.map(rewriteIconToS101);
27426
- return node;
27427
- }
27428
- var S101_AREA_FILL_NAMES = /* @__PURE__ */ new Set();
27429
- var S101_LINE_STYLE_NAMES = /* @__PURE__ */ new Set();
27430
- function rewritePatternToS101(node, available) {
27431
- if (typeof node === "string") {
27432
- return available.has(node) ? `${S101_ICON_PREFIX}${node}` : node;
27433
- }
27434
- if (Array.isArray(node)) return node.map((n) => rewritePatternToS101(n, available));
27435
- return node;
27436
- }
27437
- function collectLiterals(node, out) {
27438
- if (typeof node === "string") {
27439
- out.add(node);
27440
- return;
27441
- }
27442
- if (Array.isArray(node)) for (const n of node) collectLiterals(n, out);
27443
- }
27444
- function applyS101SymbolSet(style) {
27445
- const seen = { icon: /* @__PURE__ */ new Set(), fill: /* @__PURE__ */ new Set(), line: /* @__PURE__ */ new Set() };
27446
- for (const layer of style.layers) {
27447
- const layout = layer.layout;
27448
- const paint = layer.paint;
27449
- if (layer.type === "symbol" && layout && layout["icon-image"] != null) {
27450
- layout["icon-image"] = rewriteIconToS101(layout["icon-image"]);
27451
- collectLiterals(layout["icon-image"], seen.icon);
27452
- }
27453
- for (const bag of [layout, paint]) {
27454
- if (!bag) continue;
27455
- if (bag["fill-pattern"] != null) {
27456
- bag["fill-pattern"] = rewritePatternToS101(bag["fill-pattern"], S101_AREA_FILL_NAMES);
27457
- collectLiterals(bag["fill-pattern"], seen.fill);
27458
- }
27459
- if (bag["line-pattern"] != null) {
27460
- bag["line-pattern"] = rewritePatternToS101(bag["line-pattern"], S101_LINE_STYLE_NAMES);
27461
- collectLiterals(bag["line-pattern"], seen.line);
27462
- }
27463
- }
27464
- }
27465
- const official = (names) => [...names].filter((n) => n.startsWith(S101_ICON_PREFIX)).length;
27466
- const iconSubs = [...seen.icon].filter((n) => S101_NO_OFFICIAL_COUNTERPART.has(n)).length;
27467
- const patternSubs = (names) => [...names].filter((n) => /^(AP|LC):/.test(n)).length;
27468
- const coverage = {
27469
- pointSymbols: { official: official(seen.icon), s52Substitute: iconSubs },
27470
- areaFills: { official: official(seen.fill), s52Substitute: patternSubs(seen.fill) },
27471
- lineStyles: { official: official(seen.line), s52Substitute: patternSubs(seen.line) }
27472
- };
27473
- const metadata = style.metadata;
27474
- metadata["chrt:symbol-set"] = "s101";
27475
- metadata["chrt:s101-source-commit"] = S101_SOURCE_COMMIT;
27476
- metadata["chrt:s101-coverage"] = {
27477
- ...coverage,
27478
- /** True only when NOTHING in this document is an S-52 substitute. A
27479
- * consumer that wants "S-101 or nothing" branches on this one boolean. */
27480
- complete: Object.values(coverage).every((c) => c.s52Substitute === 0)
27481
- };
27482
- return style;
27483
- }
27484
-
27485
- // src/atlas/chrt-svgs-generated.ts
27486
- var CHRT_GLYPH_SVGS = {
27487
- "badge-radar": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><circle cx="16" cy="19" r="2.6" fill="#D200D2"/><path d="M9 19a7 7 0 0 1 14 0M5 19a11 11 0 0 1 22 0" stroke="#D200D2" stroke-width="2.4" fill="none" stroke-linecap="round" stroke-dasharray="4 3"/></svg>',
27488
- "badge-sound": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><circle cx="7" cy="16" r="2.2" fill="#D200D2"/><path d="M12 10a9 9 0 0 1 0 12M16.5 6.5a15 15 0 0 1 0 19M21 3a21 21 0 0 1 0 26" stroke="#D200D2" stroke-width="2.4" fill="none" stroke-linecap="round"/></svg>',
27489
- "beacon-cairn": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><circle cx="16" cy="11" r="4" fill="#fff"/><circle cx="11.5" cy="19" r="4.6" fill="#fff"/><circle cx="20.5" cy="19" r="4.6" fill="#fff"/><path d="M7 28h18" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
27490
- "beacon-generic": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M16 25V12" stroke="#fff" stroke-width="2.6" stroke-linecap="round"/><path d="M16 4l6 8H10Z" fill="#fff"/><path d="M8 28h16M11 25h10" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
27491
- "beacon-lattice": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M11 25 13 6h6l2 19M12 20h8M12.6 13h6.8M11 25l9-12M21 25l-9-12" stroke="#fff" stroke-width="1.8" stroke-linecap="round" fill="none"/><path d="M8 28h16" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
27492
- "beacon-stake": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M16 25V6" stroke="#fff" stroke-width="2.8" stroke-linecap="round"/><path d="M9 28h14" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
27493
- "beacon-tower": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M12 6h8l1.6 19H10.4Z" fill="#fff"/><path d="M10 6h12" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/><path d="M8 28h16" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
27494
- "buoy-barrel": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><rect x="7" y="13" width="18" height="11" rx="5.5" fill="#fff"/><path d="M6 28h20" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
27495
- "buoy-can": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M9 12h14v11a2 2 0 0 1-2 2H11a2 2 0 0 1-2-2Z" fill="#fff"/><path d="M6 28h20" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
27496
- "buoy-conical": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M16 8 24.5 25H7.5Z" fill="#fff"/><path d="M6 28h20" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
27497
- "buoy-pillar": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M13 6h6l1.4 19H11.6Z" fill="#fff"/><path d="M6 28h20" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
27498
- "buoy-spar": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M14.5 5h3l1 20h-5Z" fill="#fff"/><path d="M6 28h20" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
27499
- "buoy-spherical": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><circle cx="16" cy="17.5" r="8.5" fill="#fff"/><path d="M6 28h20" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
27500
- "buoy-super": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M7 17h18l-2.5 8h-13Z" fill="#fff"/><path d="M16 17V8" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/><circle cx="16" cy="6.5" r="2" fill="#fff"/><path d="M5 28h22" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
27501
- "cardinal-e": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M16 1.5 20 6h-8Z" fill="#101418"/><path d="M16 11 12 6.5h8Z" fill="#101418"/><path d="M13 12h6l1 13H12Z" fill="#FFC805"/><path d="M6 28h20" stroke="#101418" stroke-width="2.4" stroke-linecap="round"/></svg>',
27502
- "cardinal-n": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M16 1.5 20 6h-8Z" fill="#101418"/><path d="M16 6.5 20 11h-8Z" fill="#101418"/><path d="M13 12h6l1 13H12Z" fill="#FFC805"/><path d="M6 28h20" stroke="#101418" stroke-width="2.4" stroke-linecap="round"/></svg>',
27503
- "cardinal-s": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M16 11 12 6.5h8Z" fill="#101418"/><path d="M16 6 12 1.5h8Z" fill="#101418"/><path d="M13 12h6l1 13H12Z" fill="#FFC805"/><path d="M6 28h20" stroke="#101418" stroke-width="2.4" stroke-linecap="round"/></svg>',
27504
- "cardinal-w": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M16 6 12 1.5h8Z" fill="#101418"/><path d="M16 6.5 20 11h-8Z" fill="#101418"/><path d="M13 12h6l1 13H12Z" fill="#FFC805"/><path d="M6 28h20" stroke="#101418" stroke-width="2.4" stroke-linecap="round"/></svg>',
27505
- "daymark-square": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><rect x="6" y="6" width="20" height="20" rx="1.5" fill="#fff"/></svg>',
27506
- "daymark-triangle": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M16 5 28 26H4Z" fill="#fff"/></svg>',
27507
- "hatch-nocoverage": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M-3 9 L9 -3 M-3 21 L21 -3 M3 27 L27 3 M15 27 L27 15" stroke="#8A97A0" stroke-width="0.8" fill="none"/><path d="M-3 15 L15 33 M-3 3 L21 27 M3 -3 L27 21 M15 -3 L27 9" stroke="#8A97A0" stroke-width="0.8" fill="none"/></svg>',
27508
- "hatch-uncharted": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M-3 9 L9 -3 M-3 21 L21 -3 M3 27 L27 3 M15 27 L27 15" stroke="#5B7383" stroke-width="0.65" fill="none"/></svg>',
27509
- "isolated-danger": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><circle cx="16" cy="3.5" r="2.4" fill="#101418"/><circle cx="16" cy="9.5" r="2.4" fill="#101418"/><path d="M13 13h6l1 12H12Z" fill="#101418"/><rect x="12.4" y="17.5" width="7.2" height="3.4" fill="#E5484D"/><path d="M6 28h20" stroke="#101418" stroke-width="2.4" stroke-linecap="round"/></svg>',
27510
- "light-flare": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><circle cx="11" cy="11" r="3.2" fill="#fff"/><path d="M13.4 13.4 24 19.5a2.6 2.6 0 0 1-4.5 4.5L13.4 13.4Z" fill="#fff"/></svg>',
27511
- "lightship": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M6 20l3-5h14l3 5-3.5 4h-13Z" fill="#fff"/><path d="M16 15V8" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/><circle cx="16" cy="6" r="2.2" fill="#fff"/><path d="M5 28h22" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
27512
- "safe-water": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><circle cx="16" cy="6" r="2.4" fill="#E5484D"/><circle cx="16" cy="17.5" r="8" fill="#F8FAFC"/><path d="M12.2 10.5a8 8 0 0 0 0 14M19.8 10.5a8 8 0 0 1 0 14" stroke="#E5484D" stroke-width="3" fill="none"/><path d="M6 28h20" stroke="#101418" stroke-width="2.4" stroke-linecap="round"/></svg>'
27513
- };
27514
-
27515
- // src/atlas/chrt-style.ts
27516
- var CHRT_ICON_PREFIX = "chrt:";
27517
- var CHRT_SDF_GLYPHS = /* @__PURE__ */ new Set([
27518
- "buoy-can",
27519
- "buoy-conical",
27520
- "buoy-spherical",
27521
- "buoy-pillar",
27522
- "buoy-spar",
27523
- "buoy-barrel",
27524
- "buoy-super",
27525
- "beacon-generic",
27526
- "beacon-tower",
27527
- "beacon-lattice",
27528
- "beacon-stake",
27529
- "beacon-cairn",
27530
- "light-flare",
27531
- "lightship",
27532
- "daymark-triangle",
27533
- "daymark-square"
27534
- ]);
27535
- var CHRT_GLYPH_NAMES = new Set(Object.keys(CHRT_GLYPH_SVGS));
27536
- var CHRT_ATON_COLORS = {
27537
- "1": "#F8FAFC",
27538
- // white
27539
- "2": "#101418",
27540
- // black
27541
- "3": "#E5484D",
27542
- // red
27543
- "4": "#30A46C",
27544
- // green
27545
- "5": "#3E63DD",
27546
- // blue
27547
- "6": "#FFC805",
27548
- // yellow
27549
- "7": "#8B9298",
27550
- // grey
27551
- "8": "#A66B3A",
27552
- // brown
27553
- "9": "#FFB224",
27554
- // amber
27555
- "10": "#7E4CCB",
27556
- // violet
27557
- "11": "#F76B15",
27558
- // orange
27559
- "12": "#D671C1"
27560
- // pink (magenta-ish)
27561
- };
27562
- var CHRT_UNKNOWN_TINT = "#8B9298";
27563
- var HOUSE_BLACK = "#101418";
27564
- function chrtHouseColor(dayHex, palette = "DAY") {
27565
- if (palette === "DAY") return dayHex;
27566
- const isBlack = dayHex.toLowerCase() === HOUSE_BLACK.toLowerCase();
27567
- if (palette === "DUSK") {
27568
- return isBlack ? dayHex : capLuminance(dayHex, relativeLuminance(resolveColor("CHBLK", "DUSK")));
27569
- }
27570
- return isBlack ? NIGHT_INK : capLuminance(dayHex, relativeLuminance(NIGHT_MUTED_INK));
27571
- }
27572
- function themeChrtGlyph(svg3, palette = "DAY") {
27573
- if (palette === "DAY") return svg3;
27574
- return svg3.replace(
27575
- /(fill|stroke)="(#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6})"/g,
27576
- (_m, attr, hex) => {
27577
- const six = hex.length === 4 ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex;
27578
- return `${attr}="${chrtHouseColor(six, palette)}"`;
27579
- }
27580
- );
27581
- }
27582
- function chrtAtonTintExpression(palette = "DAY") {
27583
- const c = (code) => chrtHouseColor(CHRT_ATON_COLORS[code], palette);
27584
- return colourCodeCase(
27585
- [
27586
- ["3", c("3")],
27587
- ["4", c("4")],
27588
- ["6", c("6")],
27589
- ["11", c("11")],
27590
- ["9", c("9")],
27591
- ["5", c("5")],
27592
- ["1", c("1")],
27593
- ["2", c("2")],
27594
- ["8", c("8")],
27595
- ["10", c("10")],
27596
- ["12", c("12")],
27597
- ["7", c("7")]
27598
- ],
27599
- chrtHouseColor(CHRT_UNKNOWN_TINT, palette)
27600
- );
28611
+ function getAvailablePointSymbols() {
28612
+ return Object.keys(S52_POINT_SVGS);
27601
28613
  }
27602
- function chrtLightTintExpression(palette = "DAY") {
27603
- const c = (code) => chrtHouseColor(CHRT_ATON_COLORS[code], palette);
27604
- return colourCodeCase(
27605
- [
27606
- ["3", c("3")],
27607
- ["4", c("4")],
27608
- ["6", c("6")],
27609
- ["11", c("11")],
27610
- ["9", c("9")],
27611
- ["5", c("5")],
27612
- ["1", c("1")]
27613
- ],
27614
- c("1")
28614
+ function getAvailablePatterns() {
28615
+ return Object.keys(S52_PATTERN_SVGS);
28616
+ }
28617
+ function getAvailableLineSymbols() {
28618
+ return Object.keys(S52_LINE_SVGS);
28619
+ }
28620
+ function getS52SymbolStats() {
28621
+ return {
28622
+ pointCount: Object.keys(S52_POINT_SVGS).length,
28623
+ patternCount: Object.keys(S52_PATTERN_SVGS).length,
28624
+ lineCount: Object.keys(S52_LINE_SVGS).length,
28625
+ totalCount: Object.keys(ALL_S52_SVGS).length
28626
+ };
28627
+ }
28628
+
28629
+ // src/atlas/s101-style.ts
28630
+ var S101_ICON_PREFIX = "s101:";
28631
+ var S101_SYMBOL_NAMES = new Set(Object.keys(S101_POINT_SVGS));
28632
+ var S101_NO_OFFICIAL_COUNTERPART = /* @__PURE__ */ new Set([
28633
+ "DNGCIR01",
28634
+ "RADRFL03",
28635
+ "UWTROC01",
28636
+ "LNDMRK18",
28637
+ "LNDMRK19",
28638
+ "LIGHTHOUSE01",
28639
+ "SISTAT02",
28640
+ "WATTUR01"
28641
+ ]);
28642
+ function s101EinkTable(einkS52, fallback) {
28643
+ const out = {};
28644
+ for (const [token, dayHex] of Object.entries(S101_DAY)) {
28645
+ out[token] = einkS52[token] ?? fallback(dayHex);
28646
+ }
28647
+ return out;
28648
+ }
28649
+ var S101_TABLES = {
28650
+ DAY: S101_DAY,
28651
+ DUSK: S101_DUSK,
28652
+ NIGHT: S101_NIGHT,
28653
+ EINK_COLOR: s101EinkTable(S52_EINK_COLOR, (hex) => hex),
28654
+ EINK_MONO: s101EinkTable(S52_EINK_MONO, einkMonoGrey),
28655
+ EINK_NIGHT: s101EinkTable(S52_EINK_NIGHT, einkNightGrey)
28656
+ };
28657
+ var warnedTokens = /* @__PURE__ */ new Set();
28658
+ function reportUnresolvedToken(token, table) {
28659
+ const key = `${table}:${token}`;
28660
+ if (warnedTokens.has(key)) return;
28661
+ warnedTokens.add(key);
28662
+ console.warn(
28663
+ `[chrt/s101] colour token "${token}" is not defined in the official S-101 ${table} profile. The symbol using it will draw with a substituted colour, which on a chart changes what the symbol MEANS. Re-check the vendored assets/s101/css/${table.toLowerCase()}SvgStyle.css against the pinned catalogue commit.`
27615
28664
  );
27616
28665
  }
27617
- function scalar(attr) {
27618
- return ["coalesce", ["get", attr], ["get", attr.toLowerCase()], 0];
28666
+ function resolveS101Color(token, table = "DAY") {
28667
+ const hex = S101_TABLES[table][token];
28668
+ if (hex) return hex;
28669
+ reportUnresolvedToken(token, table);
28670
+ return "#000000";
27619
28671
  }
27620
- var g = (name) => `${CHRT_ICON_PREFIX}${name}`;
27621
- function buoyBodyExpression() {
27622
- return [
27623
- "match",
27624
- scalar("BOYSHP"),
27625
- 1,
27626
- g("buoy-conical"),
27627
- 2,
27628
- g("buoy-can"),
27629
- 3,
27630
- g("buoy-spherical"),
27631
- 4,
27632
- g("buoy-pillar"),
27633
- 5,
27634
- g("buoy-spar"),
27635
- 6,
27636
- g("buoy-barrel"),
27637
- 7,
27638
- g("buoy-super"),
27639
- 8,
27640
- g("buoy-super"),
27641
- g("buoy-pillar")
27642
- ];
28672
+ function getThemedS101Svg(symbolName, table = "DAY") {
28673
+ const raw = S101_POINT_SVGS[symbolName];
28674
+ if (!raw) return null;
28675
+ return raw.replace(/(fill|stroke)="([A-Z][A-Z0-9]{3,5})"/g, (match, attr, token) => {
28676
+ if (token === "none") return match;
28677
+ const hex = S101_TABLES[table][token];
28678
+ if (hex) return `${attr}="${hex}"`;
28679
+ reportUnresolvedToken(token, table);
28680
+ return match;
28681
+ });
27643
28682
  }
27644
- function beaconBodyExpression() {
27645
- return [
27646
- "match",
27647
- scalar("BCNSHP"),
27648
- 1,
27649
- g("beacon-stake"),
27650
- 2,
27651
- g("beacon-stake"),
27652
- 3,
27653
- g("beacon-tower"),
27654
- 4,
27655
- g("beacon-lattice"),
27656
- 5,
27657
- g("beacon-stake"),
27658
- 6,
27659
- g("beacon-cairn"),
27660
- g("beacon-generic")
27661
- ];
28683
+ function s101ArtworkProvenance(symbolName) {
28684
+ if (!symbolName) return "chrt-drawn";
28685
+ if (S101_SYMBOL_NAMES.has(symbolName)) return "official";
28686
+ if (getS52SvgString(symbolName) !== null) return "s52-substitute";
28687
+ return "chrt-drawn";
27662
28688
  }
27663
- function cardinalExpression() {
27664
- return [
27665
- "match",
27666
- scalar("CATCAM"),
27667
- 1,
27668
- g("cardinal-n"),
27669
- 2,
27670
- g("cardinal-e"),
27671
- 3,
27672
- g("cardinal-s"),
27673
- 4,
27674
- g("cardinal-w"),
27675
- g("cardinal-n")
27676
- ];
28689
+ function rewriteIconToS101(node) {
28690
+ if (typeof node === "string") {
28691
+ return S101_SYMBOL_NAMES.has(node) ? `${S101_ICON_PREFIX}${node}` : node;
28692
+ }
28693
+ if (Array.isArray(node)) return node.map(rewriteIconToS101);
28694
+ return node;
27677
28695
  }
27678
- var CHRT_ICON_EXPRESSIONS = {
27679
- BOYLAT: buoyBodyExpression,
27680
- BOYSPP: buoyBodyExpression,
27681
- BOYINB: buoyBodyExpression,
27682
- BOYCAR: cardinalExpression,
27683
- BOYISD: () => g("isolated-danger"),
27684
- BOYSAW: () => g("safe-water"),
27685
- BCNLAT: beaconBodyExpression,
27686
- BCNSPP: beaconBodyExpression,
27687
- BCNCAR: cardinalExpression,
27688
- BCNISD: () => g("isolated-danger"),
27689
- BCNSAW: () => g("safe-water"),
27690
- LIGHTS: () => g("light-flare"),
27691
- LITFLT: () => g("lightship"),
27692
- LITVES: () => g("lightship"),
27693
- FOGSIG: () => g("badge-sound"),
27694
- RTPBCN: () => g("badge-radar"),
27695
- RDOSTA: () => g("badge-radar"),
27696
- DAYMAR: () => [
27697
- "match",
27698
- scalar("TOPSHP"),
27699
- 1,
27700
- g("daymark-triangle"),
27701
- 2,
27702
- g("daymark-triangle"),
27703
- 5,
27704
- g("daymark-triangle"),
27705
- g("daymark-square")
27706
- ]
27707
- };
27708
- var TINTED_CLASSES = /* @__PURE__ */ new Set([
27709
- "BOYLAT",
27710
- "BOYSPP",
27711
- "BOYINB",
27712
- "BCNLAT",
27713
- "BCNSPP",
27714
- "DAYMAR"
27715
- ]);
27716
- var CHRT_HALO = { color: HOUSE_BLACK, width: 1 };
27717
- var CHRT_LINE_DASH = {
27718
- CBLSUB: { color: "CHMGD", width: 0.8, dash: [6, 3] },
27719
- PIPSOL: { color: "PIPLN", width: 0.8, dash: [6, 3] }
27720
- };
27721
- function applyChrtSymbolSet(style, palette = "DAY") {
28696
+ var S101_AREA_FILL_NAMES = /* @__PURE__ */ new Set();
28697
+ var S101_LINE_STYLE_NAMES = /* @__PURE__ */ new Set();
28698
+ function rewritePatternToS101(node, available) {
28699
+ if (typeof node === "string") {
28700
+ return available.has(node) ? `${S101_ICON_PREFIX}${node}` : node;
28701
+ }
28702
+ if (Array.isArray(node)) return node.map((n) => rewritePatternToS101(n, available));
28703
+ return node;
28704
+ }
28705
+ function collectLiterals(node, out) {
28706
+ if (typeof node === "string") {
28707
+ out.add(node);
28708
+ return;
28709
+ }
28710
+ if (Array.isArray(node)) for (const n of node) collectLiterals(n, out);
28711
+ }
28712
+ function applyS101SymbolSet(style) {
28713
+ const seen = { icon: /* @__PURE__ */ new Set(), fill: /* @__PURE__ */ new Set(), line: /* @__PURE__ */ new Set() };
27722
28714
  for (const layer of style.layers) {
27723
- if (layer.type === "line") {
27724
- const cls = layer.metadata?.["s52:objectClass"];
27725
- const dash = cls ? CHRT_LINE_DASH[cls] : void 0;
27726
- const paint2 = layer.paint ?? {};
27727
- if (dash && paint2["line-pattern"] != null) {
27728
- delete paint2["line-pattern"];
27729
- paint2["line-color"] = resolveColor(dash.color, palette);
27730
- paint2["line-width"] = dash.width;
27731
- paint2["line-dasharray"] = dash.dash;
27732
- layer.paint = paint2;
27733
- }
27734
- continue;
27735
- }
27736
- if (layer.type !== "symbol") continue;
27737
- const objectClass = layer.metadata?.["s52:objectClass"];
27738
- if (!objectClass || !CHRT_ICON_EXPRESSIONS[objectClass]) continue;
27739
28715
  const layout = layer.layout;
27740
- if (!layout || layout["icon-image"] == null) continue;
27741
- layout["icon-image"] = CHRT_ICON_EXPRESSIONS[objectClass]();
27742
- const paint = layer.paint ?? {};
27743
- if (TINTED_CLASSES.has(objectClass)) {
27744
- paint["icon-color"] = chrtAtonTintExpression(palette);
27745
- paint["icon-halo-color"] = CHRT_HALO.color;
27746
- paint["icon-halo-width"] = CHRT_HALO.width;
27747
- } else if (objectClass === "LIGHTS") {
27748
- paint["icon-color"] = chrtLightTintExpression(palette);
27749
- paint["icon-halo-color"] = CHRT_HALO.color;
27750
- paint["icon-halo-width"] = CHRT_HALO.width;
28716
+ const paint = layer.paint;
28717
+ if (layer.type === "symbol" && layout && layout["icon-image"] != null) {
28718
+ layout["icon-image"] = rewriteIconToS101(layout["icon-image"]);
28719
+ collectLiterals(layout["icon-image"], seen.icon);
28720
+ }
28721
+ for (const bag of [layout, paint]) {
28722
+ if (!bag) continue;
28723
+ if (bag["fill-pattern"] != null) {
28724
+ bag["fill-pattern"] = rewritePatternToS101(bag["fill-pattern"], S101_AREA_FILL_NAMES);
28725
+ collectLiterals(bag["fill-pattern"], seen.fill);
28726
+ }
28727
+ if (bag["line-pattern"] != null) {
28728
+ bag["line-pattern"] = rewritePatternToS101(bag["line-pattern"], S101_LINE_STYLE_NAMES);
28729
+ collectLiterals(bag["line-pattern"], seen.line);
28730
+ }
27751
28731
  }
27752
- layer.paint = paint;
27753
28732
  }
28733
+ const official = (names) => [...names].filter((n) => n.startsWith(S101_ICON_PREFIX)).length;
28734
+ const iconSubs = [...seen.icon].filter((n) => S101_NO_OFFICIAL_COUNTERPART.has(n)).length;
28735
+ const patternSubs = (names) => [...names].filter((n) => /^(AP|LC):/.test(n)).length;
28736
+ const coverage = {
28737
+ pointSymbols: { official: official(seen.icon), s52Substitute: iconSubs },
28738
+ areaFills: { official: official(seen.fill), s52Substitute: patternSubs(seen.fill) },
28739
+ lineStyles: { official: official(seen.line), s52Substitute: patternSubs(seen.line) }
28740
+ };
27754
28741
  const metadata = style.metadata;
27755
- metadata["chrt:symbol-set"] = "chrt";
28742
+ metadata["chrt:symbol-set"] = "s101";
28743
+ metadata["chrt:s101-source-commit"] = S101_SOURCE_COMMIT;
28744
+ metadata["chrt:s101-coverage"] = {
28745
+ ...coverage,
28746
+ /** True only when NOTHING in this document is an S-52 substitute. A
28747
+ * consumer that wants "S-101 or nothing" branches on this one boolean. */
28748
+ complete: Object.values(coverage).every((c) => c.s52Substitute === 0)
28749
+ };
27756
28750
  return style;
27757
28751
  }
27758
28752
 
@@ -27909,9 +28903,9 @@ var RASTER_ATON_CLASSES = Object.keys(
27909
28903
  if (!s52) return true;
27910
28904
  return JSON.stringify({ r: paper.rows, d: paper.defaultSymbol }) !== JSON.stringify({ r: s52.rows, d: s52.defaultSymbol });
27911
28905
  });
27912
- function rasterAtonIconExpression(objectClass) {
27913
- const lookup = S52_ATON_LOOKUPS_PAPER[objectClass];
27914
- if (!lookup) throw new Error(`raster-style: no paper lookup for ${objectClass}`);
28906
+ function rasterAtonIconExpression(objectClass2) {
28907
+ const lookup = S52_ATON_LOOKUPS_PAPER[objectClass2];
28908
+ if (!lookup) throw new Error(`raster-style: no paper lookup for ${objectClass2}`);
27915
28909
  return compileAtonExpression(lookup, resolveRasterImage);
27916
28910
  }
27917
28911
  function getRasterSvg(name, table = "DAY") {
@@ -27925,9 +28919,9 @@ function applyRasterSymbolSet(style) {
27925
28919
  const layout = layer.layout;
27926
28920
  if (!layout || layout["icon-image"] == null) continue;
27927
28921
  const metadata2 = layer.metadata;
27928
- const objectClass = metadata2?.["s52:objectClass"];
27929
- if (typeof objectClass !== "string" || !classes.has(objectClass)) continue;
27930
- layout["icon-image"] = rasterAtonIconExpression(objectClass);
28922
+ const objectClass2 = metadata2?.["s52:objectClass"];
28923
+ if (typeof objectClass2 !== "string" || !classes.has(objectClass2)) continue;
28924
+ layout["icon-image"] = rasterAtonIconExpression(objectClass2);
27931
28925
  }
27932
28926
  const metadata = style.metadata;
27933
28927
  metadata["chrt:symbol-set"] = "raster";
@@ -27959,22 +28953,83 @@ function parseSymbolFamily(raw) {
27959
28953
  const lower = raw.toLowerCase();
27960
28954
  return SYMBOL_FAMILIES.find((f) => f === lower) ?? null;
27961
28955
  }
27962
- function iconExpressionForFamily(objectClass, family) {
27963
- const s52 = getIconExpression(objectClass);
28956
+ function iconExpressionForFamily(objectClass2, family) {
28957
+ const s52 = getIconExpression(objectClass2);
27964
28958
  switch (family) {
27965
28959
  case "s52":
27966
28960
  return s52;
27967
28961
  case "s101":
27968
28962
  return rewriteIconToS101(s52);
27969
28963
  case "chrt": {
27970
- const build = CHRT_ICON_EXPRESSIONS[objectClass];
28964
+ const build = CHRT_ICON_EXPRESSIONS[objectClass2];
27971
28965
  return build ? build() : s52;
27972
28966
  }
27973
28967
  case "raster":
27974
- return RASTER_ATON_CLASSES.includes(objectClass) ? rasterAtonIconExpression(objectClass) : s52;
28968
+ return RASTER_ATON_CLASSES.includes(objectClass2) ? rasterAtonIconExpression(objectClass2) : s52;
27975
28969
  }
27976
28970
  }
27977
28971
 
28972
+ // src/atlas/depth-label-size.ts
28973
+ var DEPTH_LABEL_SIZES = ["standard", "large"];
28974
+ var DEFAULT_DEPTH_LABEL_SIZE = "standard";
28975
+ function parseDepthLabelSize(raw) {
28976
+ if (!raw) return null;
28977
+ const lower = raw.toLowerCase();
28978
+ return DEPTH_LABEL_SIZES.find((s) => s === lower) ?? null;
28979
+ }
28980
+ var DEPTH_LABEL_CLASSES = /* @__PURE__ */ new Set([
28981
+ "SOUNDG",
28982
+ "DEPCNT",
28983
+ "UWTROC",
28984
+ "OBSTRN",
28985
+ "WRECKS"
28986
+ ]);
28987
+ var SEMIBOLD = ["Open Sans Semibold"];
28988
+ var HAZARD_DEPTH = {
28989
+ textSize: ["interpolate", ["linear"], ["zoom"], 12, 12, 16, 15],
28990
+ haloWidth: 1.4,
28991
+ font: SEMIBOLD
28992
+ };
28993
+ var LARGE = {
28994
+ // Standard: 7 / 8 / 10 / 12 at z8 / 11 / 14 / 16, halo 1.0, Semibold.
28995
+ SOUNDG: {
28996
+ textSize: ["interpolate", ["linear"], ["zoom"], 8, 10, 11, 11, 14, 14, 16, 17],
28997
+ haloWidth: 1.2,
28998
+ font: SEMIBOLD
28999
+ },
29000
+ // Standard: 8 / 10 at z10 / 14, halo 1.2, Regular.
29001
+ //
29002
+ // ⚠ ~1.2x, NOT 1.4x, AND SEMIBOLD CARRIES THE REST. A contour value rides
29003
+ // its line (`symbol-placement: line`) and needs a straight-enough run to fit;
29004
+ // it also shares rank 300 with the soundings, which place first. At 1.4x the
29005
+ // Boston harbour view kept 15 of Standard's 20 contour labels; at 1.2x, 18.
29006
+ DEPCNT: {
29007
+ textSize: ["interpolate", ["linear"], ["zoom"], 10, 10, 14, 12],
29008
+ haloWidth: 1.2,
29009
+ font: SEMIBOLD
29010
+ },
29011
+ UWTROC: HAZARD_DEPTH,
29012
+ OBSTRN: HAZARD_DEPTH,
29013
+ WRECKS: HAZARD_DEPTH
29014
+ };
29015
+ var LARGE_SOUNDING_PADDING_PX = 3.5;
29016
+ function applyDepthLabelSize(objectClass2, layer, size) {
29017
+ if (size !== "large") return layer;
29018
+ const large = LARGE[objectClass2];
29019
+ if (large === void 0) return layer;
29020
+ const layout = {
29021
+ ...layer.layout ?? {},
29022
+ "text-size": large.textSize,
29023
+ "text-font": [...large.font]
29024
+ };
29025
+ if (objectClass2 === "SOUNDG") layout["text-padding"] = LARGE_SOUNDING_PADDING_PX;
29026
+ return {
29027
+ ...layer,
29028
+ layout,
29029
+ paint: { ...layer.paint, "text-halo-width": large.haloWidth }
29030
+ };
29031
+ }
29032
+
27978
29033
  // src/atlas/base-bundle.ts
27979
29034
  var BASE_BUNDLE_ID = BASE_REGION;
27980
29035
  var BASE_BUNDLE_SCHEMA = "chrt.basemap-bundle/1";
@@ -28164,47 +29219,47 @@ function buildLightLabelExpression() {
28164
29219
  ];
28165
29220
  return ["concat", litchrExpr, siggrpExpr, colourExpr, sigperExpr, valnmrExpr];
28166
29221
  }
28167
- function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts = {}) {
28168
- if (!S52_LOOKUP[objectClass]) return [];
28169
- const entry = S52_LOOKUP[objectClass];
29222
+ function buildLayersForClass(objectClass2, ct, sourceId = DEFAULT_REGION, opts = {}) {
29223
+ if (!S52_LOOKUP[objectClass2]) return [];
29224
+ const entry = S52_LOOKUP[objectClass2];
28170
29225
  const layers = [];
28171
- const geomTypes = getGeomTypesForClass(objectClass, entry?.style.type);
29226
+ const geomTypes = getGeomTypesForClass(objectClass2, entry?.style.type);
28172
29227
  for (const geomType of geomTypes) {
28173
- const resolved = resolveStyle(objectClass, geomType, ct, { safetyContour: opts.safetyContour });
28174
- const layerId = `s52-${objectClass}-${resolved.type}`;
29228
+ const resolved = resolveStyle(objectClass2, geomType, ct, { safetyContour: opts.safetyContour });
29229
+ const layerId = `s52-${objectClass2}-${resolved.type}`;
28175
29230
  const layer = {
28176
29231
  id: layerId,
28177
29232
  type: resolved.type,
28178
29233
  source: sourceId,
28179
- "source-layer": objectClass,
29234
+ "source-layer": objectClass2,
28180
29235
  paint: resolved.paint,
28181
29236
  metadata: {
28182
- "s52:objectClass": objectClass,
29237
+ "s52:objectClass": objectClass2,
28183
29238
  "s52:displayCategory": entry?.displayCategory ?? "OTHER",
28184
29239
  "s52:category": entry?.category ?? "Meta"
28185
29240
  }
28186
29241
  };
28187
- if (resolved.type === "line" && LINE_FROM_POLYGON_LAYERS.has(objectClass)) {
29242
+ if (resolved.type === "line" && LINE_FROM_POLYGON_LAYERS.has(objectClass2)) {
28188
29243
  layer.filter = GEOM_FILTERS.fill;
28189
29244
  } else if (GEOM_FILTERS[resolved.type]) {
28190
29245
  layer.filter = GEOM_FILTERS[resolved.type];
28191
29246
  }
28192
- if (SCAMIN_DECLUTTER_CLASSES.has(objectClass)) {
29247
+ if (SCAMIN_DECLUTTER_CLASSES.has(objectClass2)) {
28193
29248
  layer.filter = layer.filter ? ["all", layer.filter, scaminZoomGateFilter()] : ["all", scaminZoomGateFilter()];
28194
29249
  }
28195
29250
  if (resolved.minzoom) {
28196
29251
  layer.minzoom = resolved.minzoom;
28197
29252
  }
28198
- if (resolved.type === "fill" && OPAQUE_FILLS.has(objectClass)) {
29253
+ if (resolved.type === "fill" && OPAQUE_FILLS.has(objectClass2)) {
28199
29254
  layer.paint["fill-opacity"] = 1;
28200
29255
  }
28201
- if (resolved.type === "line" && ZOOM_SCALED_LINES2[objectClass]) {
28202
- const [z1, w1, z2, w2] = ZOOM_SCALED_LINES2[objectClass];
29256
+ if (resolved.type === "line" && ZOOM_SCALED_LINES2[objectClass2]) {
29257
+ const [z1, w1, z2, w2] = ZOOM_SCALED_LINES2[objectClass2];
28203
29258
  layer.paint["line-width"] = ["interpolate", ["linear"], ["zoom"], z1, w1, z2, w2];
28204
29259
  }
28205
29260
  if (resolved.type === "symbol") {
28206
- const iconExpr = getIconExpression(objectClass);
28207
- const childOffset = CHILD_ICON_OFFSETS[objectClass];
29261
+ const iconExpr = getIconExpression(objectClass2);
29262
+ const childOffset = CHILD_ICON_OFFSETS[objectClass2];
28208
29263
  const isSafetyCritical = SAFETY_GROUPS.has(entry?.viewingGroup ?? 0);
28209
29264
  const safetyCriticalIconSize = [
28210
29265
  "interpolate",
@@ -28327,7 +29382,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28327
29382
  ]
28328
29383
  };
28329
29384
  }
28330
- if (objectClass === "LIGHTS") {
29385
+ if (objectClass2 === "LIGHTS") {
28331
29386
  layer.layout = {
28332
29387
  ...layer.layout,
28333
29388
  "icon-allow-overlap": true,
@@ -28369,14 +29424,14 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28369
29424
  } else if (resolved.layout) {
28370
29425
  layer.layout = resolved.layout;
28371
29426
  }
28372
- if (objectClass === "LNDMRK" && resolved.type === "symbol") {
29427
+ if (objectClass2 === "LNDMRK" && resolved.type === "symbol") {
28373
29428
  const baseFilterParts = layer.filter && layer.filter[0] === "all" ? layer.filter.slice(1) : layer.filter ? [layer.filter] : [];
28374
29429
  const isWind = listAttrHas("CATLMK", ["18", "19"]);
28375
29430
  const isLighthouse = listAttrHas("CATLMK", ["17"]);
28376
29431
  const scaminFloor = scaminToMinZoom(entry?.scamin);
28377
29432
  const windLayer = {
28378
29433
  ...layer,
28379
- id: `s52-${objectClass}-wind-symbol`,
29434
+ id: `s52-${objectClass2}-wind-symbol`,
28380
29435
  filter: ["all", ...baseFilterParts, isWind],
28381
29436
  // Explicit, not inherited: turbines share exact coordinates with
28382
29437
  // their aviation-light LIGHTS feature; without allow-overlap the
@@ -28389,7 +29444,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28389
29444
  delete windLayer.minzoom;
28390
29445
  const lighthouseLayer = {
28391
29446
  ...layer,
28392
- id: `s52-${objectClass}-lighthouse-symbol`,
29447
+ id: `s52-${objectClass2}-lighthouse-symbol`,
28393
29448
  filter: ["all", ...baseFilterParts, isLighthouse, ["!", isWind]],
28394
29449
  layout: {
28395
29450
  ...layer.layout ?? {},
@@ -28400,7 +29455,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28400
29455
  delete lighthouseLayer.minzoom;
28401
29456
  const otherLayer = {
28402
29457
  ...layer,
28403
- id: `s52-${objectClass}-other-symbol`,
29458
+ id: `s52-${objectClass2}-other-symbol`,
28404
29459
  filter: ["all", ...baseFilterParts, ["!", isWind], ["!", isLighthouse]]
28405
29460
  };
28406
29461
  if (scaminFloor !== void 0) {
@@ -28415,10 +29470,10 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28415
29470
  const valsouNum = ["to-number", ["coalesce", ["get", "VALSOU"], ["get", "valsou"], 999], 999];
28416
29471
  const watlevNum = ["to-number", ["coalesce", ["get", "WATLEV"], ["get", "watlev"], 0], 0];
28417
29472
  layers.push({
28418
- id: `s52-${objectClass}-danger-ring`,
29473
+ id: `s52-${objectClass2}-danger-ring`,
28419
29474
  type: "symbol",
28420
29475
  source: sourceId,
28421
- "source-layer": objectClass,
29476
+ "source-layer": objectClass2,
28422
29477
  filter: [
28423
29478
  "all",
28424
29479
  GEOM_FILTERS.symbol,
@@ -28451,48 +29506,48 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28451
29506
  },
28452
29507
  paint: {},
28453
29508
  minzoom: 13,
28454
- metadata: { "s52:objectClass": objectClass, "s52:role": "isolated-danger-ring" }
29509
+ metadata: { "s52:objectClass": objectClass2, "s52:role": "isolated-danger-ring" }
28455
29510
  });
28456
29511
  }
28457
- if (resolved.type === "fill" && FILL_OUTLINE_LAYERS.has(objectClass)) {
28458
- const outlineColor = objectClass === "DEPARE" ? resolveColor("CHGRD", ct) : resolveColor("CHGRF", ct);
29512
+ if (resolved.type === "fill" && FILL_OUTLINE_LAYERS.has(objectClass2)) {
29513
+ const outlineColor = objectClass2 === "DEPARE" ? resolveColor("CHGRD", ct) : resolveColor("CHGRF", ct);
28459
29514
  layers.push({
28460
- id: `s52-${objectClass}-outline`,
29515
+ id: `s52-${objectClass2}-outline`,
28461
29516
  type: "line",
28462
29517
  source: sourceId,
28463
- "source-layer": objectClass,
29518
+ "source-layer": objectClass2,
28464
29519
  filter: GEOM_FILTERS.fill,
28465
29520
  paint: {
28466
29521
  "line-color": outlineColor,
28467
29522
  "line-width": 0.3,
28468
29523
  "line-opacity": 0.4
28469
29524
  },
28470
- metadata: { "s52:objectClass": objectClass, "s52:role": "outline" }
29525
+ metadata: { "s52:objectClass": objectClass2, "s52:role": "outline" }
28471
29526
  });
28472
29527
  }
28473
- if (resolved.type === "fill" && REGULATED_AREA_BOUNDARY_LAYERS[objectClass]) {
28474
- const lineStyle = REGULATED_AREA_BOUNDARY_LINESTYLES[objectClass];
29528
+ if (resolved.type === "fill" && REGULATED_AREA_BOUNDARY_LAYERS[objectClass2]) {
29529
+ const lineStyle = REGULATED_AREA_BOUNDARY_LINESTYLES[objectClass2];
28475
29530
  const boundaryPaint = lineStyle ? lineStylePaint(lineStyle) : {
28476
- "line-color": resolveColor(REGULATED_AREA_BOUNDARY_LAYERS[objectClass], ct),
29531
+ "line-color": resolveColor(REGULATED_AREA_BOUNDARY_LAYERS[objectClass2], ct),
28477
29532
  "line-width": 1,
28478
29533
  "line-dasharray": [4, 2],
28479
29534
  "line-opacity": 0.85
28480
29535
  };
28481
29536
  layers.push({
28482
- id: `s52-${objectClass}-boundary`,
29537
+ id: `s52-${objectClass2}-boundary`,
28483
29538
  type: "line",
28484
29539
  source: sourceId,
28485
- "source-layer": objectClass,
29540
+ "source-layer": objectClass2,
28486
29541
  filter: GEOM_FILTERS.fill,
28487
29542
  paint: boundaryPaint,
28488
29543
  metadata: {
28489
- "s52:objectClass": objectClass,
29544
+ "s52:objectClass": objectClass2,
28490
29545
  "s52:role": "boundary",
28491
29546
  ...lineStyle ? { "s52:lineStyle": lineStyle } : {}
28492
29547
  }
28493
29548
  });
28494
29549
  }
28495
- if (objectClass === "ACHARE" && resolved.type === "fill") {
29550
+ if (objectClass2 === "ACHARE" && resolved.type === "fill") {
28496
29551
  layers.push({
28497
29552
  id: "s52-ACHARE-boundary",
28498
29553
  type: "line",
@@ -28508,7 +29563,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28508
29563
  metadata: { "s52:objectClass": "ACHARE", "s52:role": "boundary" }
28509
29564
  });
28510
29565
  }
28511
- if (objectClass === "DRGARE" && resolved.type === "fill") {
29566
+ if (objectClass2 === "DRGARE" && resolved.type === "fill") {
28512
29567
  layers.push({
28513
29568
  id: "s52-DRGARE-boundary",
28514
29569
  type: "line",
@@ -28524,7 +29579,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28524
29579
  metadata: { "s52:objectClass": "DRGARE", "s52:role": "boundary" }
28525
29580
  });
28526
29581
  }
28527
- if (objectClass === "ACHARE" && resolved.type === "fill") {
29582
+ if (objectClass2 === "ACHARE" && resolved.type === "fill") {
28528
29583
  layers.push({
28529
29584
  id: "s52-ACHARE-anchor-icon",
28530
29585
  type: "symbol",
@@ -28553,7 +29608,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28553
29608
  metadata: { "s52:objectClass": "ACHARE", "s52:role": "centroid-icon" }
28554
29609
  });
28555
29610
  }
28556
- if (objectClass === "LNDARE" && resolved.type === "fill") {
29611
+ if (objectClass2 === "LNDARE" && resolved.type === "fill") {
28557
29612
  layers.push({
28558
29613
  id: `s52-LNDARE-point`,
28559
29614
  type: "circle",
@@ -28569,7 +29624,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28569
29624
  metadata: { "s52:objectClass": "LNDARE", "s52:role": "point" }
28570
29625
  });
28571
29626
  }
28572
- if (objectClass === "LNDARE" && resolved.type === "fill") {
29627
+ if (objectClass2 === "LNDARE" && resolved.type === "fill") {
28573
29628
  layers.push({
28574
29629
  id: `s52-LNDARE-line`,
28575
29630
  type: "line",
@@ -28583,7 +29638,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28583
29638
  metadata: { "s52:objectClass": "LNDARE", "s52:role": "line" }
28584
29639
  });
28585
29640
  }
28586
- if (objectClass === "BUAARE" && resolved.type === "fill") {
29641
+ if (objectClass2 === "BUAARE" && resolved.type === "fill") {
28587
29642
  layers.push({
28588
29643
  id: `s52-BUAARE-point`,
28589
29644
  type: "circle",
@@ -28599,7 +29654,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28599
29654
  metadata: { "s52:objectClass": "BUAARE", "s52:role": "point" }
28600
29655
  });
28601
29656
  }
28602
- if (objectClass === "RIVERS" && resolved.type === "fill") {
29657
+ if (objectClass2 === "RIVERS" && resolved.type === "fill") {
28603
29658
  layers.push({
28604
29659
  id: `s52-RIVERS-line`,
28605
29660
  type: "line",
@@ -28613,7 +29668,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28613
29668
  metadata: { "s52:objectClass": "RIVERS", "s52:role": "line" }
28614
29669
  });
28615
29670
  }
28616
- if (objectClass === "BUISGL" && resolved.type === "circle") {
29671
+ if (objectClass2 === "BUISGL" && resolved.type === "circle") {
28617
29672
  layers.push({
28618
29673
  id: `s52-BUISGL-polygon`,
28619
29674
  type: "fill",
@@ -28639,7 +29694,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28639
29694
  metadata: { "s52:objectClass": "BUISGL", "s52:role": "polygon-outline" }
28640
29695
  });
28641
29696
  }
28642
- if (objectClass === "BRIDGE" && resolved.type === "line") {
29697
+ if (objectClass2 === "BRIDGE" && resolved.type === "line") {
28643
29698
  layers.push({
28644
29699
  id: `s52-BRIDGE-polygon`,
28645
29700
  type: "fill",
@@ -28665,7 +29720,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28665
29720
  metadata: { "s52:objectClass": "BRIDGE", "s52:role": "polygon-outline" }
28666
29721
  });
28667
29722
  }
28668
- if (objectClass === "PYLONS" && resolved.type === "fill") {
29723
+ if (objectClass2 === "PYLONS" && resolved.type === "fill") {
28669
29724
  layers.push({
28670
29725
  id: `s52-PYLONS-point`,
28671
29726
  type: "circle",
@@ -28681,7 +29736,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28681
29736
  metadata: { "s52:objectClass": "PYLONS", "s52:role": "point" }
28682
29737
  });
28683
29738
  }
28684
- if (objectClass === "WEDKLP" && resolved.type === "fill") {
29739
+ if (objectClass2 === "WEDKLP" && resolved.type === "fill") {
28685
29740
  layers.push({
28686
29741
  id: `s52-WEDKLP-point`,
28687
29742
  type: "circle",
@@ -28698,7 +29753,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28698
29753
  metadata: { "s52:objectClass": "WEDKLP", "s52:role": "point" }
28699
29754
  });
28700
29755
  }
28701
- if (objectClass === "SLOGRD" && resolved.type === "fill") {
29756
+ if (objectClass2 === "SLOGRD" && resolved.type === "fill") {
28702
29757
  layers.push({
28703
29758
  id: `s52-SLOGRD-point`,
28704
29759
  type: "circle",
@@ -28715,7 +29770,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28715
29770
  metadata: { "s52:objectClass": "SLOGRD", "s52:role": "point" }
28716
29771
  });
28717
29772
  }
28718
- if (objectClass === "RUNWAY" && resolved.type === "fill") {
29773
+ if (objectClass2 === "RUNWAY" && resolved.type === "fill") {
28719
29774
  layers.push({
28720
29775
  id: `s52-RUNWAY-line`,
28721
29776
  type: "line",
@@ -28729,7 +29784,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28729
29784
  metadata: { "s52:objectClass": "RUNWAY", "s52:role": "line" }
28730
29785
  });
28731
29786
  }
28732
- if (objectClass === "FORSTC" && resolved.type === "fill") {
29787
+ if (objectClass2 === "FORSTC" && resolved.type === "fill") {
28733
29788
  layers.push({
28734
29789
  id: `s52-FORSTC-line`,
28735
29790
  type: "line",
@@ -28743,7 +29798,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28743
29798
  metadata: { "s52:objectClass": "FORSTC", "s52:role": "line" }
28744
29799
  });
28745
29800
  }
28746
- if (objectClass === "FSHFAC" && resolved.type === "fill") {
29801
+ if (objectClass2 === "FSHFAC" && resolved.type === "fill") {
28747
29802
  layers.push({
28748
29803
  id: `s52-FSHFAC-point`,
28749
29804
  type: "circle",
@@ -28759,7 +29814,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28759
29814
  metadata: { "s52:objectClass": "FSHFAC", "s52:role": "point" }
28760
29815
  });
28761
29816
  }
28762
- if (objectClass === "OBSTRN" && resolved.type === "symbol") {
29817
+ if (objectClass2 === "OBSTRN" && resolved.type === "symbol") {
28763
29818
  layers.push({
28764
29819
  id: `s52-OBSTRN-polygon`,
28765
29820
  type: "fill",
@@ -28799,7 +29854,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28799
29854
  metadata: { "s52:objectClass": "OBSTRN", "s52:role": "line" }
28800
29855
  });
28801
29856
  }
28802
- if (objectClass === "SLCONS" && resolved.type === "line") {
29857
+ if (objectClass2 === "SLCONS" && resolved.type === "line") {
28803
29858
  layers.push({
28804
29859
  id: `s52-SLCONS-polygon`,
28805
29860
  type: "fill",
@@ -28839,7 +29894,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28839
29894
  metadata: { "s52:objectClass": "SLCONS", "s52:role": "point" }
28840
29895
  });
28841
29896
  }
28842
- if (objectClass === "PRDARE" && resolved.type === "line") {
29897
+ if (objectClass2 === "PRDARE" && resolved.type === "line") {
28843
29898
  layers.push({
28844
29899
  id: `s52-PRDARE-point`,
28845
29900
  type: "circle",
@@ -28856,7 +29911,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28856
29911
  metadata: { "s52:objectClass": "PRDARE", "s52:role": "point" }
28857
29912
  });
28858
29913
  }
28859
- if (objectClass === "CTNARE" && resolved.type === "line") {
29914
+ if (objectClass2 === "CTNARE" && resolved.type === "line") {
28860
29915
  layers.push({
28861
29916
  id: `s52-CTNARE-point`,
28862
29917
  type: "circle",
@@ -28872,7 +29927,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28872
29927
  metadata: { "s52:objectClass": "CTNARE", "s52:role": "point" }
28873
29928
  });
28874
29929
  }
28875
- if (objectClass === "PONTON" && resolved.type === "line") {
29930
+ if (objectClass2 === "PONTON" && resolved.type === "line") {
28876
29931
  layers.push({
28877
29932
  id: `s52-PONTON-polygon`,
28878
29933
  type: "fill",
@@ -28898,7 +29953,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28898
29953
  metadata: { "s52:objectClass": "PONTON", "s52:role": "polygon-outline" }
28899
29954
  });
28900
29955
  }
28901
- if (objectClass === "WRECKS" && resolved.type === "symbol") {
29956
+ if (objectClass2 === "WRECKS" && resolved.type === "symbol") {
28902
29957
  layers.push({
28903
29958
  id: `s52-WRECKS-polygon`,
28904
29959
  type: "fill",
@@ -28925,7 +29980,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28925
29980
  metadata: { "s52:objectClass": "WRECKS", "s52:role": "polygon-outline" }
28926
29981
  });
28927
29982
  }
28928
- if (objectClass === "WATTUR" && resolved.type === "symbol") {
29983
+ if (objectClass2 === "WATTUR" && resolved.type === "symbol") {
28929
29984
  layers.push({
28930
29985
  id: `s52-WATTUR-polygon`,
28931
29986
  type: "fill",
@@ -28939,7 +29994,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28939
29994
  metadata: { "s52:objectClass": "WATTUR", "s52:role": "polygon" }
28940
29995
  });
28941
29996
  }
28942
- if (objectClass === "MAGVAR" && resolved.type === "line") {
29997
+ if (objectClass2 === "MAGVAR" && resolved.type === "line") {
28943
29998
  layers.push({
28944
29999
  id: "s52-MAGVAR-point",
28945
30000
  type: "circle",
@@ -28956,7 +30011,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28956
30011
  metadata: { "s52:objectClass": "MAGVAR", "s52:role": "point" }
28957
30012
  });
28958
30013
  }
28959
- if (objectClass === "DMPGRD" && resolved.type === "fill") {
30014
+ if (objectClass2 === "DMPGRD" && resolved.type === "fill") {
28960
30015
  layers.push({
28961
30016
  id: `s52-DMPGRD-point`,
28962
30017
  type: "circle",
@@ -28973,7 +30028,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28973
30028
  metadata: { "s52:objectClass": "DMPGRD", "s52:role": "point" }
28974
30029
  });
28975
30030
  }
28976
- if (objectClass === "DAMCON" && resolved.type === "line") {
30031
+ if (objectClass2 === "DAMCON" && resolved.type === "line") {
28977
30032
  layers.push({
28978
30033
  id: `s52-DAMCON-polygon`,
28979
30034
  type: "fill",
@@ -28999,7 +30054,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
28999
30054
  metadata: { "s52:objectClass": "DAMCON", "s52:role": "outline" }
29000
30055
  });
29001
30056
  }
29002
- if (objectClass === "GATCON" && resolved.type === "line") {
30057
+ if (objectClass2 === "GATCON" && resolved.type === "line") {
29003
30058
  layers.push({
29004
30059
  id: `s52-GATCON-polygon`,
29005
30060
  type: "fill",
@@ -29025,7 +30080,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
29025
30080
  metadata: { "s52:objectClass": "GATCON", "s52:role": "outline" }
29026
30081
  });
29027
30082
  }
29028
- if (objectClass === "CANALS" && resolved.type === "line") {
30083
+ if (objectClass2 === "CANALS" && resolved.type === "line") {
29029
30084
  layers.push({
29030
30085
  id: `s52-CANALS-polygon`,
29031
30086
  type: "fill",
@@ -29039,7 +30094,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
29039
30094
  metadata: { "s52:objectClass": "CANALS", "s52:role": "polygon" }
29040
30095
  });
29041
30096
  }
29042
- if (objectClass === "PILBOP" && resolved.type === "symbol") {
30097
+ if (objectClass2 === "PILBOP" && resolved.type === "symbol") {
29043
30098
  layers.push({
29044
30099
  id: `s52-PILBOP-polygon`,
29045
30100
  type: "fill",
@@ -29066,7 +30121,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
29066
30121
  metadata: { "s52:objectClass": "PILBOP", "s52:role": "outline" }
29067
30122
  });
29068
30123
  }
29069
- if (objectClass === "HRBFAC" && resolved.type === "circle") {
30124
+ if (objectClass2 === "HRBFAC" && resolved.type === "circle") {
29070
30125
  layers.push({
29071
30126
  id: `s52-HRBFAC-polygon`,
29072
30127
  type: "fill",
@@ -29080,7 +30135,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
29080
30135
  metadata: { "s52:objectClass": "HRBFAC", "s52:role": "polygon" }
29081
30136
  });
29082
30137
  }
29083
- if (objectClass === "MARCUL" && resolved.type === "fill") {
30138
+ if (objectClass2 === "MARCUL" && resolved.type === "fill") {
29084
30139
  layers.push({
29085
30140
  id: `s52-MARCUL-point`,
29086
30141
  type: "circle",
@@ -29110,7 +30165,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
29110
30165
  metadata: { "s52:objectClass": "MARCUL", "s52:role": "line" }
29111
30166
  });
29112
30167
  }
29113
- if (objectClass === "RECTRC" && resolved.type === "line") {
30168
+ if (objectClass2 === "RECTRC" && resolved.type === "line") {
29114
30169
  layers.push({
29115
30170
  id: `s52-RECTRC-polygon`,
29116
30171
  type: "fill",
@@ -29124,7 +30179,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
29124
30179
  metadata: { "s52:objectClass": "RECTRC", "s52:role": "polygon" }
29125
30180
  });
29126
30181
  }
29127
- if (objectClass === "PONTON" && resolved.type === "fill") {
30182
+ if (objectClass2 === "PONTON" && resolved.type === "fill") {
29128
30183
  layers.push({
29129
30184
  id: `s52-PONTON-line`,
29130
30185
  type: "line",
@@ -29138,7 +30193,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
29138
30193
  metadata: { "s52:objectClass": "PONTON", "s52:role": "line" }
29139
30194
  });
29140
30195
  }
29141
- if (objectClass === "SILTNK" && resolved.type === "symbol") {
30196
+ if (objectClass2 === "SILTNK" && resolved.type === "symbol") {
29142
30197
  layers.push({
29143
30198
  id: `s52-SILTNK-symbol-polygon`,
29144
30199
  type: "symbol",
@@ -29156,7 +30211,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
29156
30211
  metadata: { "s52:objectClass": "SILTNK", "s52:role": "symbol-polygon" }
29157
30212
  });
29158
30213
  }
29159
- if (objectClass === "ACHBRT" && resolved.type === "symbol") {
30214
+ if (objectClass2 === "ACHBRT" && resolved.type === "symbol") {
29160
30215
  layers.push({
29161
30216
  id: `s52-ACHBRT-symbol-polygon`,
29162
30217
  type: "symbol",
@@ -29174,7 +30229,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
29174
30229
  metadata: { "s52:objectClass": "ACHBRT", "s52:role": "symbol-polygon" }
29175
30230
  });
29176
30231
  }
29177
- if (objectClass === "BERTHS" && resolved.type === "line") {
30232
+ if (objectClass2 === "BERTHS" && resolved.type === "line") {
29178
30233
  layers.push({
29179
30234
  id: `s52-BERTHS-circle`,
29180
30235
  type: "circle",
@@ -29190,888 +30245,451 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
29190
30245
  metadata: { "s52:objectClass": "BERTHS", "s52:role": "point" }
29191
30246
  });
29192
30247
  }
29193
- if (objectClass === "TUNNEL" && resolved.type === "line") {
30248
+ if (objectClass2 === "TUNNEL" && resolved.type === "line") {
29194
30249
  layers.push({
29195
30250
  // NOT `s52-TUNNEL-line` — that id is already taken by the main layer
29196
30251
  // (`s52-${objectClass}-${resolved.type}`), and duplicate layer ids
29197
30252
  // are a Mapbox style-spec violation (caught by style-integrity.test).
29198
30253
  id: `s52-TUNNEL-linestring`,
29199
30254
  type: "line",
29200
- source: sourceId,
29201
- "source-layer": "TUNNEL",
29202
- filter: GEOM_FILTERS.line,
29203
- paint: {
29204
- "line-color": resolveColor("CHGRD", ct),
29205
- "line-width": 1,
29206
- "line-dasharray": [2, 2]
29207
- },
29208
- metadata: { "s52:objectClass": "TUNNEL", "s52:role": "line" }
29209
- });
29210
- }
29211
- }
29212
- return layers;
29213
- }
29214
- function buildLabelLayer2(objectClass, ct, sourceId = DEFAULT_REGION, depthUnits = "meters", labelOpts = {}) {
29215
- if (!LABELED_LAYERS.has(objectClass)) return null;
29216
- const config = getLabelConfig(objectClass, {
29217
- colorTable: ct,
29218
- safetyContour: labelOpts.safetyContour,
29219
- twoShadeSoundings: labelOpts.twoShadeSoundings,
29220
- soundingFontWeight: labelOpts.soundingFontWeight
29221
- });
29222
- if (!config) return null;
29223
- let textField = config.textField;
29224
- if (objectClass === "LIGHTS") {
29225
- textField = buildLightLabelExpression();
29226
- }
29227
- if (typeof textField !== "string" && Array.isArray(textField)) {
29228
- const flat = JSON.stringify(textField);
29229
- if (flat.includes("_navAidLabel")) {
29230
- textField = buildNavAidLabelExpression();
29231
- }
29232
- }
29233
- if (depthUnits === "feet") {
29234
- if (objectClass === "SOUNDG") {
29235
- textField = [
29236
- "concat",
29237
- [
29238
- "round",
29239
- [
29240
- "*",
29241
- [
29242
- "to-number",
29243
- ["coalesce", ["get", "depth"], ["get", "DEPTH"], ["get", "VALSOU"]],
29244
- 0
29245
- ],
29246
- 3.28084
29247
- ]
29248
- ],
29249
- ""
29250
- ];
29251
- } else if (objectClass === "DEPCNT") {
29252
- textField = [
29253
- "concat",
29254
- ["round", ["*", ["to-number", ["get", "VALDCO"]], 3.28084]],
29255
- "ft"
29256
- ];
29257
- } else if (objectClass === "UWTROC" || objectClass === "OBSTRN" || objectClass === "WRECKS") {
29258
- const valsouFt = [
29259
- "to-number",
29260
- ["coalesce", ["get", "VALSOU"], ["get", "valsou"], 0],
29261
- 0
29262
- ];
29263
- textField = [
29264
- "case",
29265
- [">", valsouFt, 0],
29266
- ["concat", ["to-string", ["round", ["*", valsouFt, 3.28084]]], "ft"],
29267
- ""
29268
- ];
29269
- }
29270
- }
29271
- const labelPriority = getLabelPriority(objectClass);
29272
- const layer = {
29273
- id: `s52-${objectClass}-label`,
29274
- type: "symbol",
29275
- source: sourceId,
29276
- "source-layer": objectClass,
29277
- filter: GEOM_FILTERS.symbol,
29278
- layout: {
29279
- "text-field": textField,
29280
- "text-size": config.textSize,
29281
- "text-font": config.textFont ?? ["Open Sans Regular"],
29282
- "text-offset": config.textOffset,
29283
- "text-anchor": "top",
29284
- "text-allow-overlap": false,
29285
- "text-optional": true,
29286
- "text-padding": 4,
29287
- "symbol-sort-key": labelPriority * 1e3,
29288
- ...config.layout ?? {}
29289
- },
29290
- paint: {
29291
- "text-color": config.textColor,
29292
- "text-halo-color": config.textHaloColor,
29293
- "text-halo-width": config.textHaloWidth
29294
- },
29295
- metadata: {
29296
- "s52:objectClass": objectClass,
29297
- "chrt:labelPriority": labelPriority
29298
- }
29299
- };
29300
- if (config.minzoom) {
29301
- layer.minzoom = config.minzoom;
29302
- }
29303
- if (objectClass === "DEPCNT") {
29304
- layer.filter = GEOM_FILTERS.line;
29305
- }
29306
- if (objectClass === "CBLOHD" || objectClass === "PIPOHD") {
29307
- layer.filter = GEOM_FILTERS.line;
29308
- }
29309
- if (objectClass === "SBDARE") {
29310
- delete layer.filter;
29311
- }
29312
- if (objectClass === "MAGVAR") {
29313
- delete layer.filter;
29314
- }
29315
- if (getLabelBehavior(objectClass) === "orientation-cross-band") {
29316
- layer.filter = LABEL_ANCHOR_FILTER;
29317
- }
29318
- return layer;
29319
- }
29320
- function getGeomTypesForClass(objectClass, defaultType) {
29321
- if (SYMBOL_MAP[objectClass] && defaultType === "symbol") {
29322
- return ["symbol"];
29323
- }
29324
- if (objectClass === "SOUNDG") {
29325
- return ["circle"];
29326
- }
29327
- if (defaultType) {
29328
- return [defaultType];
29329
- }
29330
- return ["circle"];
29331
- }
29332
- function getOrderedClasses() {
29333
- const ordered = [...S52_DRAW_ORDER];
29334
- for (const key of Object.keys(S52_LOOKUP)) {
29335
- if (!ordered.includes(key)) {
29336
- const insertIdx = ordered.indexOf("SOUNDG");
29337
- if (insertIdx >= 0) {
29338
- ordered.splice(insertIdx, 0, key);
29339
- } else {
29340
- ordered.push(key);
29341
- }
29342
- }
29343
- }
29344
- return ordered;
29345
- }
29346
-
29347
- // src/atlas/terrain-order.ts
29348
- var DRAPEABLE_LAYER_TYPES = /* @__PURE__ */ new Set([
29349
- "background",
29350
- "fill",
29351
- "line",
29352
- "raster",
29353
- "hillshade",
29354
- "color-relief"
29355
- ]);
29356
- function isDrapeable(layer) {
29357
- return DRAPEABLE_LAYER_TYPES.has(layer.type);
29358
- }
29359
- function drapedFirst(layers) {
29360
- const draped = [];
29361
- const direct = [];
29362
- for (const l of layers) (isDrapeable(l) ? draped : direct).push(l);
29363
- return [...draped, ...direct];
29364
- }
29365
- function terrainStackCount(layers) {
29366
- let stacks = 0;
29367
- let prevDraped = false;
29368
- for (const l of layers) {
29369
- const d = isDrapeable(l);
29370
- if (d && !prevDraped) stacks++;
29371
- prevDraped = d;
29372
- }
29373
- return stacks;
29374
- }
29375
- function drapeHalves(layers) {
29376
- const draped = [];
29377
- const direct = [];
29378
- for (const l of layers) (isDrapeable(l) ? draped : direct).push(l);
29379
- return { draped, direct };
29380
- }
29381
-
29382
- // src/atlas/lamps.ts
29383
- var LAMP_GLOW_LAYER_ID = "s52-LIGHTS-lamp-glow";
29384
- var LAMP_CORE_LAYER_ID = "s52-LIGHTS-lamp-core";
29385
- var LAMP_LAYER_IDS = [LAMP_GLOW_LAYER_ID, LAMP_CORE_LAYER_ID];
29386
- var RANGE_NM = ["to-number", ["coalesce", ["get", "VALNMR"], ["get", "valnmr"]], 6];
29387
- var UNSECTORED_POINTS = [
29388
- "all",
29389
- ["any", ["==", ["geometry-type"], "Point"], ["==", ["geometry-type"], "MultiPoint"]],
29390
- ["!", ["has", "SECTR1"]],
29391
- // …and not lights with a charted rhythm either: those flash, and the
29392
- // flashing is computed by the app from `SIGSEQ` (light-sigseq.ts). What is
29393
- // left here is exactly the set that is steady from every bearing.
29394
- ["!", ["has", "SIGSEQ"]]
29395
- ];
29396
- function buildLampLayers(sourceId, ct) {
29397
- const colour = lightFillExpression(ct);
29398
- const common = {
29399
- type: "circle",
29400
- source: sourceId,
29401
- "source-layer": "LIGHTS",
29402
- filter: UNSECTORED_POINTS,
29403
- layout: { visibility: "none" }
29404
- };
29405
- const metadata = { "chrt:role": "lamp", "s52:objectClass": "LIGHTS" };
29406
- return [
29407
- {
29408
- ...common,
29409
- id: LAMP_GLOW_LAYER_ID,
29410
- paint: {
29411
- "circle-color": colour,
29412
- // 4 nm → 7 px, 12 nm → 12 px, 25 nm → 20 px; capped, because a
29413
- // primary seacoast light is not a searchlight.
29414
- "circle-radius": ["interpolate", ["linear"], RANGE_NM, 0, 5, 4, 7, 12, 12, 25, 20],
29415
- "circle-blur": 1,
29416
- "circle-opacity": 0.55,
29417
- // A lamp is a point of light: it does not grow as it comes near, and
29418
- // it does not lie down with the chart.
29419
- "circle-pitch-alignment": "viewport",
29420
- "circle-pitch-scale": "viewport"
29421
- },
29422
- metadata: { ...metadata, "chrt:lamp": "glow" }
29423
- },
29424
- {
29425
- ...common,
29426
- id: LAMP_CORE_LAYER_ID,
29427
- paint: {
29428
- "circle-color": colour,
29429
- "circle-radius": ["interpolate", ["linear"], RANGE_NM, 0, 1.5, 12, 2.5, 25, 3.5],
29430
- "circle-blur": 0.25,
29431
- "circle-opacity": 1,
29432
- "circle-pitch-alignment": "viewport",
29433
- "circle-pitch-scale": "viewport"
29434
- },
29435
- metadata: { ...metadata, "chrt:lamp": "core" }
30255
+ source: sourceId,
30256
+ "source-layer": "TUNNEL",
30257
+ filter: GEOM_FILTERS.line,
30258
+ paint: {
30259
+ "line-color": resolveColor("CHGRD", ct),
30260
+ "line-width": 1,
30261
+ "line-dasharray": [2, 2]
30262
+ },
30263
+ metadata: { "s52:objectClass": "TUNNEL", "s52:role": "line" }
30264
+ });
29436
30265
  }
29437
- ];
29438
- }
29439
-
29440
- // src/atlas/band-priority.ts
29441
- var BAND_PRIORITY_RANGES = {
29442
- "1": { minzoom: 0 },
29443
- "2": { minzoom: 5 },
29444
- "3": { minzoom: 7 },
29445
- "4": { minzoom: 9 },
29446
- "5": { minzoom: 11 },
29447
- "6": { minzoom: 11 }
29448
- };
29449
- var BAND_DATA_MINZOOM = {
29450
- "1": 0,
29451
- "2": 5,
29452
- "3": 7,
29453
- "4": 9,
29454
- "5": 11,
29455
- "6": 13
29456
- };
29457
- function dominatedVariantMaxzoom(D) {
29458
- return Math.max(BAND_PRIORITY_RANGES[D].minzoom, BAND_DATA_MINZOOM[D]);
29459
- }
29460
- var DOMINANCE_EXEMPT_FILL_CLASSES = /* @__PURE__ */ new Set([
29461
- "LNDARE",
29462
- "DEPARE",
29463
- "UNSARE",
29464
- "LAKARE",
29465
- "DRGARE",
29466
- "SEAARE"
29467
- ]);
29468
- function isExemptOpaqueFill(layer) {
29469
- if (layer.type !== "fill") return false;
29470
- for (const cls of DOMINANCE_EXEMPT_FILL_CLASSES) {
29471
- if (layer.id.startsWith(`s52-${cls}-`)) return true;
29472
- }
29473
- return false;
29474
- }
29475
- var GROUND_FILL_CLASSES = /* @__PURE__ */ new Set([
29476
- ...DOMINANCE_EXEMPT_FILL_CLASSES,
29477
- "RIVERS"
29478
- ]);
29479
- function isGroundFill(layer) {
29480
- if (layer.type !== "fill") return false;
29481
- for (const cls of GROUND_FILL_CLASSES) {
29482
- if (layer.id.startsWith(`s52-${cls}-`)) return true;
29483
- }
29484
- return false;
29485
- }
29486
- var FILL_FLOOR_FREE_BANDS = /* @__PURE__ */ new Set([
29487
- "1",
29488
- "2",
29489
- "3"
29490
- ]);
29491
- function bandVariantMinzoom(layer, B) {
29492
- const classMinzoom = layer.minzoom ?? 0;
29493
- if (isGroundFill(layer) && FILL_FLOOR_FREE_BANDS.has(B)) {
29494
- return 0;
29495
- }
29496
- if (isGroundFill(layer)) {
29497
- return BAND_PRIORITY_RANGES[B].minzoom;
29498
- }
29499
- return Math.max(classMinzoom, BAND_PRIORITY_RANGES[B].minzoom);
29500
- }
29501
- var GENERALISED_AREA_FILL_CLASSES = /* @__PURE__ */ new Set([
29502
- "BUAARE",
29503
- // 2026-08-28, New York Harbor: the "New York Marine Inspection Zone and
29504
- // Captain of the Port Zone" RESARE exists at bands 2/3 as a generalized
29505
- // shore-hugging polygon whose point-sampled dominance tag MISSED (the
29506
- // Providence-BUAARE failure mode), so its magenta dashed boundary drew
29507
- // OFFSET over the band-5 detail at z11.5 — read in the field as "a
29508
- // magenta shoreline bleeding down from a less coarse band". The same
29509
- // tile carried untagged band-2 ADMARE and CTNARE giants in the same
29510
- // state. All three are area classes whose coarse-band boundaries hug
29511
- // shorelines and whose fine-band copies are authoritative.
29512
- "RESARE",
29513
- "CTNARE",
29514
- "ADMARE"
29515
- ]);
29516
- var GENERALISED_AREA_MAX_BAND = 3;
29517
- function generalisedAreaCeiling(layer, B) {
29518
- if (layer.type !== "fill" && layer.type !== "line") return null;
29519
- if (parseInt(B, 10) > GENERALISED_AREA_MAX_BAND) return null;
29520
- for (const cls of GENERALISED_AREA_FILL_CLASSES) {
29521
- if (layer.id.startsWith(`s52-${cls}-`)) return BAND_PRIORITY_RANGES["5"].minzoom;
29522
- }
29523
- return null;
29524
- }
29525
- var BAND_DRAW_ORDER = ["1", "2", "3", "4", "5", "6"];
29526
- var CHART_BAND_CODES = {
29527
- Overview: ["1"],
29528
- General: ["2"],
29529
- Coastal: ["3"],
29530
- Approach: ["4"],
29531
- Harbor: ["5", "6"]
29532
- };
29533
- var CHART_CASCADE_CODES = {
29534
- Overview: ["1"],
29535
- General: ["1", "2"],
29536
- Coastal: ["1", "2", "3"],
29537
- Approach: ["1", "2", "3", "4"],
29538
- Harbor: ["1", "2", "3", "4", "5", "6"]
29539
- };
29540
- var CHRT_STYLE_MODES = [
29541
- "continuous",
29542
- ...Object.keys(CHART_BAND_CODES).map(
29543
- (band) => `chart:${band}`
29544
- )
29545
- ];
29546
- function normalizeStyleMode(raw) {
29547
- if (raw === null || raw === void 0 || raw === "") return "continuous";
29548
- const lower = raw.toLowerCase();
29549
- return CHRT_STYLE_MODES.find((mode) => mode.toLowerCase() === lower) ?? null;
29550
- }
29551
- function styleModeBand(mode) {
29552
- return mode === "continuous" ? null : mode.slice("chart:".length);
29553
- }
29554
- function bandFilterExpression(codes) {
29555
- return cellBandMatch(["slice", ["get", "_cell_id"], 2, 3], codes);
29556
- }
29557
- var BAND_CODE_ALIASES = {
29558
- "5": ["7"]
29559
- };
29560
- function cellBandCharacters(code) {
29561
- return [code, ...BAND_CODE_ALIASES[code] ?? []];
29562
- }
29563
- function cellBandMatch(cellBandExpr, codes) {
29564
- const list = Array.isArray(codes) ? codes : [codes];
29565
- const chars = [...new Set(list.flatMap((c) => cellBandCharacters(c)))];
29566
- if (chars.length === 1) {
29567
- return ["==", cellBandExpr, chars[0]];
29568
30266
  }
29569
- return ["in", cellBandExpr, ["literal", chars]];
30267
+ return layers;
29570
30268
  }
29571
- function combineWithBandFilter(baseFilter, bandFilter) {
29572
- if (!baseFilter) return bandFilter;
29573
- if (baseFilter[0] === "all") {
29574
- return ["all", ...baseFilter.slice(1), bandFilter];
30269
+ function buildLabelLayer2(objectClass2, ct, sourceId = DEFAULT_REGION, depthUnits = "meters", labelOpts = {}) {
30270
+ if (!LABELED_LAYERS.has(objectClass2)) return null;
30271
+ const config = getLabelConfig(objectClass2, {
30272
+ colorTable: ct,
30273
+ safetyContour: labelOpts.safetyContour,
30274
+ twoShadeSoundings: labelOpts.twoShadeSoundings,
30275
+ soundingFontWeight: labelOpts.soundingFontWeight
30276
+ });
30277
+ if (!config) return null;
30278
+ let textField = config.textField;
30279
+ if (objectClass2 === "LIGHTS") {
30280
+ textField = buildLightLabelExpression();
29575
30281
  }
29576
- return ["all", baseFilter, bandFilter];
29577
- }
29578
- var NOT_DOMINATED_FILTER = [
29579
- "all",
29580
- ["!", ["has", "_dominated_by_min"]],
29581
- ["!=", ["get", "_dominated"], true]
29582
- ];
29583
- function dominatedByMinFilter(D) {
29584
- const dInt = parseInt(D, 10);
29585
- if (D === "6") {
29586
- return [
29587
- "any",
29588
- ["==", ["get", "_dominated_by_min"], dInt],
29589
- ["==", ["get", "_dominated"], true]
29590
- ];
30282
+ if (typeof textField !== "string" && Array.isArray(textField)) {
30283
+ const flat = JSON.stringify(textField);
30284
+ if (flat.includes("_navAidLabel")) {
30285
+ textField = buildNavAidLabelExpression();
30286
+ }
29591
30287
  }
29592
- return ["==", ["get", "_dominated_by_min"], dInt];
29593
- }
29594
- function withinBboxFilter(bbox) {
29595
- const [w, s] = bbox.sw;
29596
- const [e, n] = bbox.ne;
29597
- return [
29598
- "within",
29599
- {
29600
- type: "Polygon",
29601
- coordinates: [
30288
+ if (depthUnits === "feet") {
30289
+ if (objectClass2 === "SOUNDG") {
30290
+ textField = [
30291
+ "concat",
29602
30292
  [
29603
- [w, s],
29604
- [e, s],
29605
- [e, n],
29606
- [w, n],
29607
- [w, s]
29608
- ]
29609
- ]
30293
+ "round",
30294
+ [
30295
+ "*",
30296
+ [
30297
+ "to-number",
30298
+ ["coalesce", ["get", "depth"], ["get", "DEPTH"], ["get", "VALSOU"]],
30299
+ 0
30300
+ ],
30301
+ 3.28084
30302
+ ]
30303
+ ],
30304
+ ""
30305
+ ];
30306
+ } else if (objectClass2 === "DEPCNT") {
30307
+ textField = [
30308
+ "concat",
30309
+ ["round", ["*", ["to-number", ["get", "VALDCO"]], 3.28084]],
30310
+ "ft"
30311
+ ];
30312
+ } else if (objectClass2 === "UWTROC" || objectClass2 === "OBSTRN" || objectClass2 === "WRECKS") {
30313
+ const valsouFt = [
30314
+ "to-number",
30315
+ ["coalesce", ["get", "VALSOU"], ["get", "valsou"], 0],
30316
+ 0
30317
+ ];
30318
+ textField = [
30319
+ "case",
30320
+ [">", valsouFt, 0],
30321
+ ["concat", ["to-string", ["round", ["*", valsouFt, 3.28084]]], "ft"],
30322
+ ""
30323
+ ];
30324
+ }
30325
+ }
30326
+ const labelPriority = getLabelPriority(objectClass2);
30327
+ const layer = {
30328
+ id: `s52-${objectClass2}-label`,
30329
+ type: "symbol",
30330
+ source: sourceId,
30331
+ "source-layer": objectClass2,
30332
+ filter: GEOM_FILTERS.symbol,
30333
+ layout: {
30334
+ "text-field": textField,
30335
+ "text-size": config.textSize,
30336
+ "text-font": config.textFont ?? ["Open Sans Regular"],
30337
+ "text-offset": config.textOffset,
30338
+ "text-anchor": "top",
30339
+ "text-allow-overlap": false,
30340
+ "text-optional": true,
30341
+ "text-padding": 4,
30342
+ "symbol-sort-key": labelPriority * 1e3,
30343
+ ...config.layout ?? {}
30344
+ },
30345
+ paint: {
30346
+ "text-color": config.textColor,
30347
+ "text-halo-color": config.textHaloColor,
30348
+ "text-halo-width": config.textHaloWidth
30349
+ },
30350
+ metadata: {
30351
+ "s52:objectClass": objectClass2,
30352
+ "chrt:labelPriority": labelPriority
29610
30353
  }
29611
- ];
29612
- }
29613
- function worldMinusBboxPolygon(bbox) {
29614
- const [w, s] = bbox.sw;
29615
- const [e, n] = bbox.ne;
29616
- return {
29617
- type: "Polygon",
29618
- coordinates: [
29619
- [
29620
- [-180, -85],
29621
- [180, -85],
29622
- [180, 85],
29623
- [-180, 85],
29624
- [-180, -85]
29625
- ],
29626
- // Inner ring (hole) — winding opposite to outer ring per GeoJSON
29627
- // spec. MapLibre is permissive but we follow strict winding.
29628
- [
29629
- [w, s],
29630
- [w, n],
29631
- [e, n],
29632
- [e, s],
29633
- [w, s]
29634
- ]
29635
- ]
29636
30354
  };
30355
+ if (config.minzoom) {
30356
+ layer.minzoom = config.minzoom;
30357
+ }
30358
+ if (objectClass2 === "DEPCNT") {
30359
+ layer.filter = GEOM_FILTERS.line;
30360
+ }
30361
+ if (objectClass2 === "CBLOHD" || objectClass2 === "PIPOHD") {
30362
+ layer.filter = GEOM_FILTERS.line;
30363
+ }
30364
+ if (objectClass2 === "SBDARE") {
30365
+ delete layer.filter;
30366
+ }
30367
+ if (objectClass2 === "MAGVAR") {
30368
+ delete layer.filter;
30369
+ }
30370
+ if (getLabelBehavior(objectClass2) === "orientation-cross-band") {
30371
+ layer.filter = LABEL_ANCHOR_FILTER;
30372
+ }
30373
+ return applyDepthLabelSize(objectClass2, layer, labelOpts.depthLabelSize);
29637
30374
  }
29638
-
29639
- // src/atlas/base-layers.ts
29640
- var NOCOVERAGE_PATTERN = "chrt:hatch-nocoverage";
29641
- var UNCHARTED_PATTERN = "chrt:hatch-uncharted";
29642
- function inlandWaterColor(palette) {
29643
- return (BASE_THEMES[palette] ?? BASE_THEMES.DAY).inlandWater;
29644
- }
29645
- var BASE_THEMES = {
29646
- DAY: {
29647
- // ── `earth` is the jurisdiction tan DIMMED (~0.55), not a blue-grey ──
29648
- // Outside the jurisdiction the map still owes the reader "what is land".
29649
- // State 3 in the header is "the geography, under a crosshatch", and until
29650
- // 2026-08-31 that geography was unreadable: earth #2A3439 against sea
29651
- // #1B2328 measured 1.25:1, so Hispaniola showed charted boxes with tan
29652
- // land inside and nothing recognisable around them (owner report: "no
29653
- // land painting down near the DR at some zooms"). Land is warm and sea is
29654
- // cool because that is what land and sea are; dimming the JURISDICTION
29655
- // tan rather than inventing a colour says the true thing — this is the
29656
- // same land, we simply do not carry charts for it. Held against BOTH
29657
- // neighbours: legible over sea, and never mistakable for the full tan of
29658
- // a country we do carry. DAY 3.59:1 vs sea, 3.17:1 vs tan.
29659
- earth: "#807761",
29660
- water: "#1B2328",
29661
- river: "#24313A",
29662
- boundary: "#4A575E",
29663
- jurisdiction: "#D4C69D",
29664
- // CHRTland's own tan — one land colour
29665
- inlandWater: "#B9CFDD",
29666
- // muted, unmistakably not a charted blue
29667
- // State furniture. These three were referenced by the layers but missing
29668
- // from this table (and its type — the long-standing TS2339 trio), so the
29669
- // lines shipped on MapLibre's DEFAULT black. Now deliberate: a hairline
29670
- // a shade deeper than the tan, quiet caps, tan halo.
29671
- stateLine: "#B1A077",
29672
- stateLabel: "#8E8468",
29673
- stateHalo: "#D4C69D",
29674
- // Rough-lake furniture (owner ask 2026-08-28: "the lakes need labels,
29675
- // and shorelines drawn"): a shoreline a shade deeper than the rough
29676
- // water, and a name in the water's own voice, haloed by the water.
29677
- inlandShore: "#8FA9BA",
29678
- inlandLabel: "#54707F",
29679
- inlandHalo: "#B9CFDD"
29680
- },
29681
- DUSK: {
29682
- // ── Re-derived 2026-09-01: the basemap sits UNDER the chart ──────────
29683
- // These were the DAY tones scaled by a constant, never measured against
29684
- // the DUSK chart — so `jurisdiction` came out at L=0.506 against a chart
29685
- // whose own land (LANDA #8a7d5e) is L=0.209, i.e. the context layer was
29686
- // 2.4x brighter than the data layer. Now capped at the chart's own land.
29687
- // The rest of the block rides the same 0.666 channel factor so every
29688
- // relationship the palette already chose is preserved, only moved down.
29689
- // `water` is deliberately NOT scaled: it is the reference the land/sea
29690
- // floor is measured against.
29691
- earth: "#544E3E",
29692
- water: "#161D21",
29693
- river: "#141B20",
29694
- boundary: "#293135",
29695
- jurisdiction: "#867D64",
29696
- inlandWater: "#62717A",
29697
- // Label stays DARK here — the halo is still mid-toned, so 3:1 is reachable
29698
- // downward and a bright label would be a new light source at dusk.
29699
- stateLine: "#B5A987",
29700
- stateLabel: "#38342A",
29701
- stateHalo: "#867D64",
29702
- inlandShore: "#4D5B67",
29703
- inlandLabel: "#212629",
29704
- inlandHalo: "#62717A"
29705
- },
29706
- NIGHT: {
29707
- // ── Re-derived 2026-09-01 — the owner-reported night defect ──────────
29708
- // `jurisdiction` was L=0.240 against a chart whose own land (LANDA
29709
- // #3a3020) is L=0.031: the world basemap was 7.7x BRIGHTER than the chart
29710
- // it sits under, so at z<11 the uncharted interior read as a pale
29711
- // continent-sized slab on a night chart. Capped at the chart's own land.
29712
- //
29713
- // ⚠ `earth` AND `jurisdiction` CONVERGE HERE, and that is forced, not
29714
- // sloppy. Holding earth 2:1 above the ocean AND 2:1 below the jurisdiction
29715
- // tan requires jurisdiction >= L 0.174 — 5.6x the chart's own land. At
29716
- // night the range between the ocean (L 0.006) and the cap (L 0.031) is too
29717
- // compressed to carry two land tones. COVERAGE IS STILL SAID, by the
29718
- // `chrt:hatch-nocoverage` crosshatch, which is a stronger signal than tone
29719
- // and is the one thing that cannot be lost to palette compression.
29720
- //
29721
- // Labels invert to light-on-dark: 3:1 downward is unreachable off a halo
29722
- // this close to black, and CHRTland's own night town labels already sit at
29723
- // L=0.177 for exactly this reason.
29724
- earth: "#333026",
29725
- water: "#10161A",
29726
- river: "#090D0F",
29727
- boundary: "#131719",
29728
- jurisdiction: "#353128",
29729
- inlandWater: "#232A30",
29730
- stateLine: "#484236",
29731
- stateLabel: "#837963",
29732
- stateHalo: "#353128",
29733
- inlandShore: "#1C2227",
29734
- inlandLabel: "#617484",
29735
- inlandHalo: "#232A30"
30375
+ function getGeomTypesForClass(objectClass2, defaultType) {
30376
+ if (SYMBOL_MAP[objectClass2] && defaultType === "symbol") {
30377
+ return ["symbol"];
29736
30378
  }
29737
- };
29738
- function baseLayers(opts = {}) {
29739
- const palette = opts.palette ?? "DAY";
29740
- const T = BASE_THEMES[palette] ?? BASE_THEMES.DAY;
29741
- const src = opts.sourceId ?? BASE_REGION;
29742
- const L = (o) => o;
29743
- return [
29744
- // 1. Geography — the world as it is, in the quiet dark voice. The OCEAN
29745
- // is a background, not a polygon: the archive ships land and lakes,
29746
- // and "everything else is sea" is exactly what a background expresses
29747
- // — geometry-free, gap-free at every antimeridian and pole. Without
29748
- // it the sea outside coverage was bare S-52 NODTA, a light grey that
29749
- // read as "charted ground" with a hatch on it.
29750
- L({
29751
- id: "chrtbase-ocean",
29752
- type: "background",
29753
- paint: { "background-color": T.water }
29754
- }),
29755
- L({
29756
- id: "chrtbase-earth",
29757
- type: "fill",
29758
- source: src,
29759
- "source-layer": "base_earth",
29760
- paint: { "fill-color": T.earth }
29761
- }),
29762
- L({
29763
- id: "chrtbase-water",
29764
- type: "fill",
29765
- source: src,
29766
- "source-layer": "base_water",
29767
- paint: { "fill-color": T.water }
29768
- }),
29769
- L({
29770
- id: "chrtbase-river",
29771
- type: "line",
29772
- source: src,
29773
- "source-layer": "base_river",
29774
- minzoom: 9,
29775
- paint: {
29776
- "line-color": T.river,
29777
- "line-width": ["interpolate", ["linear"], ["zoom"], 9, 0.6, 14, 1.6]
29778
- }
29779
- }),
29780
- L({
29781
- id: "chrtbase-boundary",
29782
- type: "line",
29783
- source: src,
29784
- "source-layer": "base_boundary",
29785
- paint: { "line-color": T.boundary, "line-width": 0.6, "line-opacity": 0.8 }
29786
- }),
29787
- // 2. THE DEFAULT: no coverage, everywhere, as a geometry-free background.
29788
- // Constant opacity by design — see the header. What kills it is being
29789
- // painted OVER, never fading.
29790
- L({
29791
- id: "chrtbase-nocoverage",
29792
- type: "background",
29793
- paint: { "background-pattern": NOCOVERAGE_PATTERN, "background-opacity": 0.55 }
29794
- }),
29795
- // 3. The jurisdiction: countries whose charts CHRT carries go tan — the
29796
- // same tan as CHRTland, so "land in our world" is ONE colour. Opaque,
29797
- // so it takes the crosshatch off its ground entirely.
29798
- L({
29799
- id: "chrtbase-jurisdiction",
29800
- type: "fill",
29801
- source: src,
29802
- "source-layer": "jurisdiction",
29803
- paint: { "fill-color": T.jurisdiction }
29804
- }),
29805
- // 4. States — internal boundaries and names, inside the jurisdiction
29806
- // only (owner decision 2026-08-28). A hairline a shade deeper than the
29807
- // tan, and quiet letter-spaced caps that fade as harbour detail takes
29808
- // over. The one deliberate exception to no-names-on-the-basemap:
29809
- // ADMIN furniture, nothing else — see the fence test.
29810
- L({
29811
- id: "chrtbase-state-boundary",
29812
- type: "line",
29813
- source: src,
29814
- "source-layer": "state_boundary",
29815
- minzoom: 4,
29816
- paint: {
29817
- "line-color": T.stateLine,
29818
- "line-width": ["interpolate", ["linear"], ["zoom"], 4, 0.5, 10, 1],
29819
- "line-dasharray": [3, 2],
29820
- "line-opacity": 0.8
29821
- }
29822
- }),
29823
- // 5. Country borders, drawn to be HOISTED above the whole chart by the
29824
- // generator (owner, 2026-08-31: "where there is charted land, can we
29825
- // add country borders on top of everything? I want it clear where
29826
- // there is a boarder."). Sourced from NE boundary_lines_land — land
29827
- // borders only, never coastline — so riding on top cannot stroke a
29828
- // coast. Dashed so it reads as an administrative line, not chart
29829
- // linework.
29830
- L({
29831
- id: "chrtbase-country-boundary",
29832
- type: "line",
29833
- source: src,
29834
- "source-layer": "country_boundary",
29835
- paint: {
29836
- "line-color": T.boundary,
29837
- "line-width": ["interpolate", ["linear"], ["zoom"], 3, 0.8, 8, 1.4, 12, 2.2],
29838
- "line-dasharray": [4, 2],
29839
- "line-opacity": 0.9
29840
- }
29841
- }),
29842
- L({
29843
- id: "chrtbase-state-label",
29844
- type: "symbol",
29845
- source: src,
29846
- "source-layer": "state_label",
29847
- minzoom: 4,
29848
- maxzoom: 10,
29849
- layout: {
29850
- "text-field": ["upcase", ["get", "name"]],
29851
- "text-font": ["Open Sans Regular"],
29852
- "text-size": ["interpolate", ["linear"], ["zoom"], 4, 9, 8, 12],
29853
- "text-letter-spacing": 0.25,
29854
- "text-allow-overlap": false,
29855
- "text-padding": 8
29856
- },
29857
- paint: {
29858
- "text-color": T.stateLabel,
29859
- "text-halo-color": T.stateHalo,
29860
- "text-halo-width": 1.2,
29861
- "text-opacity": ["interpolate", ["linear"], ["zoom"], 4, 0.7, 8, 0.85, 10, 0.4]
30379
+ if (objectClass2 === "SOUNDG") {
30380
+ return ["circle"];
30381
+ }
30382
+ if (defaultType) {
30383
+ return [defaultType];
30384
+ }
30385
+ return ["circle"];
30386
+ }
30387
+ function getOrderedClasses() {
30388
+ const ordered = [...S52_DRAW_ORDER];
30389
+ for (const key of Object.keys(S52_LOOKUP)) {
30390
+ if (!ordered.includes(key)) {
30391
+ const insertIdx = ordered.indexOf("SOUNDG");
30392
+ if (insertIdx >= 0) {
30393
+ ordered.splice(insertIdx, 0, key);
30394
+ } else {
30395
+ ordered.push(key);
29862
30396
  }
29863
- })
29864
- ];
30397
+ }
30398
+ }
30399
+ return ordered;
29865
30400
  }
29866
- function baseInlandWaterLayers(opts = {}) {
29867
- const palette = opts.palette ?? "DAY";
29868
- const T = BASE_THEMES[palette] ?? BASE_THEMES.DAY;
29869
- const src = opts.sourceId ?? BASE_REGION;
29870
- const L = (o) => o;
29871
- const NE_TIER = ["!", ["has", "kind"]];
29872
- const PLANET_TIER = ["has", "kind"];
29873
- const HANDOVER = 9;
30401
+
30402
+ // src/atlas/terrain-order.ts
30403
+ var DRAPEABLE_LAYER_TYPES = /* @__PURE__ */ new Set([
30404
+ "background",
30405
+ "fill",
30406
+ "line",
30407
+ "raster",
30408
+ "hillshade",
30409
+ "color-relief"
30410
+ ]);
30411
+ function isDrapeable(layer) {
30412
+ return DRAPEABLE_LAYER_TYPES.has(layer.type);
30413
+ }
30414
+ function drapedFirst(layers) {
30415
+ const draped = [];
30416
+ const direct = [];
30417
+ for (const l of layers) (isDrapeable(l) ? draped : direct).push(l);
30418
+ return [...draped, ...direct];
30419
+ }
30420
+ function terrainStackCount(layers) {
30421
+ let stacks = 0;
30422
+ let prevDraped = false;
30423
+ for (const l of layers) {
30424
+ const d = isDrapeable(l);
30425
+ if (d && !prevDraped) stacks++;
30426
+ prevDraped = d;
30427
+ }
30428
+ return stacks;
30429
+ }
30430
+ function drapeHalves(layers) {
30431
+ const draped = [];
30432
+ const direct = [];
30433
+ for (const l of layers) (isDrapeable(l) ? draped : direct).push(l);
30434
+ return { draped, direct };
30435
+ }
30436
+
30437
+ // src/atlas/lamps.ts
30438
+ var LAMP_GLOW_LAYER_ID = "s52-LIGHTS-lamp-glow";
30439
+ var LAMP_CORE_LAYER_ID = "s52-LIGHTS-lamp-core";
30440
+ var LAMP_LAYER_IDS = [LAMP_GLOW_LAYER_ID, LAMP_CORE_LAYER_ID];
30441
+ var RANGE_NM = ["to-number", ["coalesce", ["get", "VALNMR"], ["get", "valnmr"]], 6];
30442
+ var UNSECTORED_POINTS = [
30443
+ "all",
30444
+ ["any", ["==", ["geometry-type"], "Point"], ["==", ["geometry-type"], "MultiPoint"]],
30445
+ ["!", ["has", "SECTR1"]],
30446
+ // …and not lights with a charted rhythm either: those flash, and the
30447
+ // flashing is computed by the app from `SIGSEQ` (light-sigseq.ts). What is
30448
+ // left here is exactly the set that is steady from every bearing.
30449
+ ["!", ["has", "SIGSEQ"]]
30450
+ ];
30451
+ function buildLampLayers(sourceId, ct) {
30452
+ const colour = lightFillExpression(ct);
30453
+ const common = {
30454
+ type: "circle",
30455
+ source: sourceId,
30456
+ "source-layer": "LIGHTS",
30457
+ filter: UNSECTORED_POINTS,
30458
+ layout: { visibility: "none" }
30459
+ };
30460
+ const metadata = { "chrt:role": "lamp", "s52:objectClass": "LIGHTS" };
29874
30461
  return [
29875
- // ── The fill pair, SPLIT BY TIER at the handover ────────────────────────
29876
- //
29877
- // Both tiers share `inland_water` (tile-join concatenates the same layer
29878
- // name), and an unsplit fill draws BOTH — which means the NE polygon is
29879
- // painted over the planet one at every zoom. That is not a cosmetic
29880
- // double-draw: NE 10m carries no interior rings, so its blob has no
29881
- // ISLANDS in it, and painting it over the planet's holed polygons buries
29882
- // every island in the lake under "uncharted water". Owner-reported at
29883
- // Winnipesaukee z11.7 ("there is land (and islands) being covered by the
29884
- // 'no chart here' boundary"), and measured: rendering the planet tier
29885
- // alone brings back the whole archipelago — Rattlesnake, Bear, Long
29886
- // Island, Diamond, the Barndoors — while the NE tier alone is a faceted
29887
- // blob whose edges bear no relation to the shoreline drawn beneath it.
29888
- //
29889
- // So each tier owns its own zoom range, the same handover the shoreline
29890
- // below uses. Known residue, measured over the whole archive: of the 141
29891
- // NE lakes we still carry, 138 have planet water beneath them; the
29892
- // exceptions are two unnamed western-Alaska tundra lakes and a 2e-5 deg²
29893
- // Lake Michigan sliver, which lose their rough fill above z9 because the
29894
- // planet has no `water` polygon there at all (checked against the raw
29895
- // pre-clip NDJSON — not a clip artefact).
29896
- L({
29897
- id: "chrtbase-inland-water",
29898
- type: "fill",
29899
- source: src,
29900
- "source-layer": "inland_water",
29901
- filter: NE_TIER,
29902
- maxzoom: HANDOVER,
29903
- paint: { "fill-color": T.inlandWater }
29904
- }),
29905
- L({
29906
- id: "chrtbase-inland-hatch",
29907
- type: "fill",
29908
- source: src,
29909
- "source-layer": "inland_water",
29910
- filter: NE_TIER,
29911
- maxzoom: HANDOVER,
29912
- paint: { "fill-pattern": UNCHARTED_PATTERN, "fill-opacity": 0.7 }
29913
- }),
29914
- L({
29915
- id: "chrtbase-inland-water-detail",
29916
- type: "fill",
29917
- source: src,
29918
- "source-layer": "inland_water",
29919
- filter: PLANET_TIER,
29920
- minzoom: HANDOVER,
29921
- paint: { "fill-color": T.inlandWater }
29922
- }),
29923
- L({
29924
- id: "chrtbase-inland-hatch-detail",
29925
- type: "fill",
29926
- source: src,
29927
- "source-layer": "inland_water",
29928
- filter: PLANET_TIER,
29929
- minzoom: HANDOVER,
29930
- paint: { "fill-pattern": UNCHARTED_PATTERN, "fill-opacity": 0.7 }
29931
- }),
29932
- // The rough shoreline (owner ask 2026-08-28). Drawn ABOVE the hatch so
29933
- // the edge stays crisp through the texture, and BELOW the charted-water
29934
- // repaint clones the generator splices in after it — so wherever the
29935
- // chart repaints its own water, the rough NE10m line yields to the
29936
- // chart's COALNE instead of double-striking it.
29937
- //
29938
- // NE-tier geometry ONLY (`kind` absent): the planet detail tier joins
29939
- // the same source-layer from z9 as MANY polygons whose chunk-clip seams
29940
- // rejoin invisibly as FILLS — but a line layer strokes every seam, and
29941
- // Winnipesaukee rendered as a mesh of internal edges. Planet features
29942
- // carry `kind` (the INLAND_KINDS filter's own field); NE's carry only
29943
- // `name`, so the absence of `kind` IS the tier discriminator.
29944
- L({
29945
- id: "chrtbase-inland-shore",
29946
- type: "line",
29947
- source: src,
29948
- "source-layer": "inland_water",
29949
- filter: NE_TIER,
29950
- // Hands the shoreline over to the planet tier at ITS floor. Both
29951
- // tiers live in `inland_water` from z9 up (tile-join concatenates the
29952
- // same layer name), and their FILLS merge invisibly — but two
29953
- // outlines do not: NE 10m generalises Winnipesaukee's shape, so its
29954
- // outline ran straight across the middle of the lake the planet had
29955
- // drawn correctly around. The coarse line is right only while it is
29956
- // the only shape we have.
29957
- maxzoom: HANDOVER,
29958
- paint: {
29959
- "line-color": T.inlandShore,
29960
- "line-width": ["interpolate", ["linear"], ["zoom"], 4, 0.5, 10, 1.1, 14, 1.6],
29961
- "line-opacity": 0.9
29962
- }
29963
- }),
29964
- // The PLANET tier's shoreline (owner ask 2026-08-31: "uncharted small
29965
- // lakes render with no outline"). It reads its own `inland_shore` layer,
29966
- // NOT `inland_water`, and that indirection is the whole fix: the mesh
29967
- // that forced the `!has kind` fence above is a CLIP artefact — an MVT
29968
- // feature arrives already cut to its own z10 tile, so stroking the
29969
- // polygon strokes every tile boundary it crosses. The build now walks
29970
- // those rings, drops the axis-aligned runs that sit on the read zoom's
29971
- // tile grid, and emits what is left as lines (measured on one New
29972
- // England chunk: 61 long axis-aligned mesh edges → 5, and the 5 survivors
29973
- // are 0.02–0.5 tiles off the grid, i.e. real straight water edges).
29974
- //
29975
- // Thinner and quieter than the NE line: this is finer geometry describing
29976
- // water nobody has surveyed, and approximate data should look
29977
- // approximate.
29978
- L({
29979
- id: "chrtbase-inland-shore-detail",
29980
- type: "line",
29981
- source: src,
29982
- "source-layer": "inland_shore",
29983
- minzoom: HANDOVER,
29984
- paint: {
29985
- "line-color": T.inlandShore,
29986
- "line-width": ["interpolate", ["linear"], ["zoom"], 9, 0.5, 12, 0.9, 15, 1.2],
29987
- "line-opacity": 0.85
29988
- }
29989
- }),
29990
- // The lake's name (owner ask 2026-08-28) — the second scoped exception
29991
- // to no-names-on-the-basemap, after the states: WATER-BODY names only.
29992
- // No maxzoom: an uncharted lake keeps its name at every zoom; over
29993
- // charted lakes the chart's own SEAARE label takes over at detail zooms
29994
- // and collision culls the echo.
29995
- //
29996
- // The archive's `inland_water_label` anchors (v3+). v3 shipped ONE
29997
- // PointOnSurface anchor per lake, and a big lake's single anchor pans
29998
- // off-viewport — Winnipesaukee at z11.7 read nameless with its name two
29999
- // tiles west of the screen (owner-reported). Labelling the polygons
30000
- // instead is NOT the fix: tippecanoe's coalescing drops the NE polygon
30001
- // from some z11 tiles (measured: 11/619/747 carries only planet pieces),
30002
- // so a polygon-sourced label vanishes tile by tile. v4 therefore bakes a
30003
- // deterministic anchor GRID into big lakes — the paper-chart convention
30004
- // of repeating a name along a large water body — and this one layer
30005
- // renders however many anchors are in view; collision culls the rest.
30006
- // Planet-tier water-body NAMES (owner ask 2026-08-31: NE 10m names only
30007
- // 113 US lakes). Emitted BELOW the NE layer on purpose — MapLibre places
30008
- // symbols topmost-layer-first, so the big NE lake wins collision against
30009
- // the pond beside it, and both lose to the chart's own labels (every ENC
30010
- // class is emitted after this pair).
30011
- //
30012
- // The size threshold is OSM's own `min_zoom`, carried through the build:
30013
- // Lake Winnipesaukee reads 10, Squam Lake 12, so a pond simply is not in
30014
- // the filter until the user is close enough for it to be the answer to
30015
- // "what is that water". `["zoom"]` in a filter evaluates at integer zoom
30016
- // levels and removes the feature from placement entirely — verified
30017
- // against maplibre-gl-native rather than assumed, because a text-size-0
30018
- // gate would still claim a collision slot.
30019
- //
30020
- // The tiers are separate LAYERS, not a filtered split of one — see the
30021
- // builder for why: an archive that a not-yet-deployed style would
30022
- // mis-render cannot be promoted first, and this one has to be.
30023
- L({
30024
- id: "chrtbase-inland-label-detail",
30025
- type: "symbol",
30026
- source: src,
30027
- "source-layer": "inland_water_label_detail",
30028
- minzoom: HANDOVER,
30029
- filter: [">=", ["zoom"], ["get", "min_zoom"]],
30030
- layout: {
30031
- "text-field": ["get", "name"],
30032
- "text-font": ["Open Sans Regular"],
30033
- "text-size": ["interpolate", ["linear"], ["zoom"], 9, 9, 12, 10.5, 15, 11],
30034
- "text-max-width": 8,
30035
- "text-allow-overlap": false,
30036
- "text-padding": 6,
30037
- // Bigger water first within the tier — min_zoom IS the size rank.
30038
- "symbol-sort-key": ["get", "min_zoom"]
30039
- },
30462
+ {
30463
+ ...common,
30464
+ id: LAMP_GLOW_LAYER_ID,
30040
30465
  paint: {
30041
- "text-color": T.inlandLabel,
30042
- "text-halo-color": T.inlandHalo,
30043
- "text-halo-width": 1,
30044
- "text-opacity": 0.9
30045
- }
30046
- }),
30047
- L({
30048
- id: "chrtbase-inland-label",
30049
- type: "symbol",
30050
- source: src,
30051
- "source-layer": "inland_water_label",
30052
- minzoom: 5,
30053
- layout: {
30054
- "text-field": ["get", "name"],
30055
- "text-font": ["Open Sans Regular"],
30056
- "text-size": ["interpolate", ["linear"], ["zoom"], 5, 9.5, 10, 12],
30057
- "text-max-width": 7,
30058
- "text-allow-overlap": false,
30059
- // Zoom-scaled spacing for the anchor GRID: at z8.9 the 0.08-deg
30060
- // anchors sit ~54 px apart and a flat padding let six copies of
30061
- // "Lake Ontario" crowd one screen (owner-reported 2026-08-30).
30062
- // Wide collision boxes at low zoom thin the grid to the repeat-
30063
- // by-convention cadence; by z12 the anchors are ~850 px apart and
30064
- // the padding relaxes to normal.
30065
- "text-padding": ["interpolate", ["linear"], ["zoom"], 5, 80, 9, 64, 12, 16]
30466
+ "circle-color": colour,
30467
+ // 4 nm → 7 px, 12 nm → 12 px, 25 nm → 20 px; capped, because a
30468
+ // primary seacoast light is not a searchlight.
30469
+ "circle-radius": ["interpolate", ["linear"], RANGE_NM, 0, 5, 4, 7, 12, 12, 25, 20],
30470
+ "circle-blur": 1,
30471
+ "circle-opacity": 0.55,
30472
+ // A lamp is a point of light: it does not grow as it comes near, and
30473
+ // it does not lie down with the chart.
30474
+ "circle-pitch-alignment": "viewport",
30475
+ "circle-pitch-scale": "viewport"
30066
30476
  },
30477
+ metadata: { ...metadata, "chrt:lamp": "glow" }
30478
+ },
30479
+ {
30480
+ ...common,
30481
+ id: LAMP_CORE_LAYER_ID,
30067
30482
  paint: {
30068
- "text-color": T.inlandLabel,
30069
- "text-halo-color": T.inlandHalo,
30070
- "text-halo-width": 1.1
30071
- }
30072
- })
30483
+ "circle-color": colour,
30484
+ "circle-radius": ["interpolate", ["linear"], RANGE_NM, 0, 1.5, 12, 2.5, 25, 3.5],
30485
+ "circle-blur": 0.25,
30486
+ "circle-opacity": 1,
30487
+ "circle-pitch-alignment": "viewport",
30488
+ "circle-pitch-scale": "viewport"
30489
+ },
30490
+ metadata: { ...metadata, "chrt:lamp": "core" }
30491
+ }
30492
+ ];
30493
+ }
30494
+
30495
+ // src/atlas/band-priority.ts
30496
+ var BAND_PRIORITY_RANGES = {
30497
+ "1": { minzoom: 0 },
30498
+ "2": { minzoom: 5 },
30499
+ "3": { minzoom: 7 },
30500
+ "4": { minzoom: 9 },
30501
+ "5": { minzoom: 11 },
30502
+ "6": { minzoom: 11 }
30503
+ };
30504
+ var BAND_DATA_MINZOOM = {
30505
+ "1": 0,
30506
+ "2": 5,
30507
+ "3": 7,
30508
+ "4": 9,
30509
+ "5": 11,
30510
+ "6": 13
30511
+ };
30512
+ function dominatedVariantMaxzoom(D) {
30513
+ return Math.max(BAND_PRIORITY_RANGES[D].minzoom, BAND_DATA_MINZOOM[D]);
30514
+ }
30515
+ var DOMINANCE_EXEMPT_FILL_CLASSES = /* @__PURE__ */ new Set([
30516
+ "LNDARE",
30517
+ "DEPARE",
30518
+ "UNSARE",
30519
+ "LAKARE",
30520
+ "DRGARE",
30521
+ "SEAARE"
30522
+ ]);
30523
+ function isExemptOpaqueFill(layer) {
30524
+ if (layer.type !== "fill") return false;
30525
+ for (const cls of DOMINANCE_EXEMPT_FILL_CLASSES) {
30526
+ if (layer.id.startsWith(`s52-${cls}-`)) return true;
30527
+ }
30528
+ return false;
30529
+ }
30530
+ var GROUND_FILL_CLASSES = /* @__PURE__ */ new Set([
30531
+ ...DOMINANCE_EXEMPT_FILL_CLASSES,
30532
+ "RIVERS"
30533
+ ]);
30534
+ function isGroundFill(layer) {
30535
+ if (layer.type !== "fill") return false;
30536
+ for (const cls of GROUND_FILL_CLASSES) {
30537
+ if (layer.id.startsWith(`s52-${cls}-`)) return true;
30538
+ }
30539
+ return false;
30540
+ }
30541
+ var FILL_FLOOR_FREE_BANDS = /* @__PURE__ */ new Set([
30542
+ "1",
30543
+ "2",
30544
+ "3"
30545
+ ]);
30546
+ function bandVariantMinzoom(layer, B) {
30547
+ const classMinzoom = layer.minzoom ?? 0;
30548
+ if (isGroundFill(layer) && FILL_FLOOR_FREE_BANDS.has(B)) {
30549
+ return 0;
30550
+ }
30551
+ if (isGroundFill(layer)) {
30552
+ return BAND_PRIORITY_RANGES[B].minzoom;
30553
+ }
30554
+ return Math.max(classMinzoom, BAND_PRIORITY_RANGES[B].minzoom);
30555
+ }
30556
+ var GENERALISED_AREA_FILL_CLASSES = /* @__PURE__ */ new Set([
30557
+ "BUAARE",
30558
+ // 2026-08-28, New York Harbor: the "New York Marine Inspection Zone and
30559
+ // Captain of the Port Zone" RESARE exists at bands 2/3 as a generalized
30560
+ // shore-hugging polygon whose point-sampled dominance tag MISSED (the
30561
+ // Providence-BUAARE failure mode), so its magenta dashed boundary drew
30562
+ // OFFSET over the band-5 detail at z11.5 — read in the field as "a
30563
+ // magenta shoreline bleeding down from a less coarse band". The same
30564
+ // tile carried untagged band-2 ADMARE and CTNARE giants in the same
30565
+ // state. All three are area classes whose coarse-band boundaries hug
30566
+ // shorelines and whose fine-band copies are authoritative.
30567
+ "RESARE",
30568
+ "CTNARE",
30569
+ "ADMARE"
30570
+ ]);
30571
+ var GENERALISED_AREA_MAX_BAND = 3;
30572
+ function generalisedAreaCeiling(layer, B) {
30573
+ if (layer.type !== "fill" && layer.type !== "line") return null;
30574
+ if (parseInt(B, 10) > GENERALISED_AREA_MAX_BAND) return null;
30575
+ for (const cls of GENERALISED_AREA_FILL_CLASSES) {
30576
+ if (layer.id.startsWith(`s52-${cls}-`)) return BAND_PRIORITY_RANGES["5"].minzoom;
30577
+ }
30578
+ return null;
30579
+ }
30580
+ var BAND_DRAW_ORDER = ["1", "2", "3", "4", "5", "6"];
30581
+ var CHART_BAND_CODES = {
30582
+ Overview: ["1"],
30583
+ General: ["2"],
30584
+ Coastal: ["3"],
30585
+ Approach: ["4"],
30586
+ Harbor: ["5", "6"]
30587
+ };
30588
+ var CHART_CASCADE_CODES = {
30589
+ Overview: ["1"],
30590
+ General: ["1", "2"],
30591
+ Coastal: ["1", "2", "3"],
30592
+ Approach: ["1", "2", "3", "4"],
30593
+ Harbor: ["1", "2", "3", "4", "5", "6"]
30594
+ };
30595
+ var CHRT_STYLE_MODES = [
30596
+ "continuous",
30597
+ ...Object.keys(CHART_BAND_CODES).map(
30598
+ (band) => `chart:${band}`
30599
+ )
30600
+ ];
30601
+ function normalizeStyleMode(raw) {
30602
+ if (raw === null || raw === void 0 || raw === "") return "continuous";
30603
+ const lower = raw.toLowerCase();
30604
+ return CHRT_STYLE_MODES.find((mode) => mode.toLowerCase() === lower) ?? null;
30605
+ }
30606
+ function styleModeBand(mode) {
30607
+ return mode === "continuous" ? null : mode.slice("chart:".length);
30608
+ }
30609
+ function bandFilterExpression(codes) {
30610
+ return cellBandMatch(["slice", ["get", "_cell_id"], 2, 3], codes);
30611
+ }
30612
+ var BAND_CODE_ALIASES = {
30613
+ "5": ["7"]
30614
+ };
30615
+ function cellBandCharacters(code) {
30616
+ return [code, ...BAND_CODE_ALIASES[code] ?? []];
30617
+ }
30618
+ function cellBandMatch(cellBandExpr, codes) {
30619
+ const list = Array.isArray(codes) ? codes : [codes];
30620
+ const chars = [...new Set(list.flatMap((c) => cellBandCharacters(c)))];
30621
+ if (chars.length === 1) {
30622
+ return ["==", cellBandExpr, chars[0]];
30623
+ }
30624
+ return ["in", cellBandExpr, ["literal", chars]];
30625
+ }
30626
+ function combineWithBandFilter(baseFilter, bandFilter) {
30627
+ if (!baseFilter) return bandFilter;
30628
+ if (baseFilter[0] === "all") {
30629
+ return ["all", ...baseFilter.slice(1), bandFilter];
30630
+ }
30631
+ return ["all", baseFilter, bandFilter];
30632
+ }
30633
+ var NOT_DOMINATED_FILTER = [
30634
+ "all",
30635
+ ["!", ["has", "_dominated_by_min"]],
30636
+ ["!=", ["get", "_dominated"], true]
30637
+ ];
30638
+ function dominatedByMinFilter(D) {
30639
+ const dInt = parseInt(D, 10);
30640
+ if (D === "6") {
30641
+ return [
30642
+ "any",
30643
+ ["==", ["get", "_dominated_by_min"], dInt],
30644
+ ["==", ["get", "_dominated"], true]
30645
+ ];
30646
+ }
30647
+ return ["==", ["get", "_dominated_by_min"], dInt];
30648
+ }
30649
+ function withinBboxFilter(bbox) {
30650
+ const [w, s] = bbox.sw;
30651
+ const [e, n] = bbox.ne;
30652
+ return [
30653
+ "within",
30654
+ {
30655
+ type: "Polygon",
30656
+ coordinates: [
30657
+ [
30658
+ [w, s],
30659
+ [e, s],
30660
+ [e, n],
30661
+ [w, n],
30662
+ [w, s]
30663
+ ]
30664
+ ]
30665
+ }
30073
30666
  ];
30074
30667
  }
30668
+ function worldMinusBboxPolygon(bbox) {
30669
+ const [w, s] = bbox.sw;
30670
+ const [e, n] = bbox.ne;
30671
+ return {
30672
+ type: "Polygon",
30673
+ coordinates: [
30674
+ [
30675
+ [-180, -85],
30676
+ [180, -85],
30677
+ [180, 85],
30678
+ [-180, 85],
30679
+ [-180, -85]
30680
+ ],
30681
+ // Inner ring (hole) — winding opposite to outer ring per GeoJSON
30682
+ // spec. MapLibre is permissive but we follow strict winding.
30683
+ [
30684
+ [w, s],
30685
+ [w, n],
30686
+ [e, n],
30687
+ [e, s],
30688
+ [w, s]
30689
+ ]
30690
+ ]
30691
+ };
30692
+ }
30075
30693
 
30076
30694
  // src/atlas/tile-number.ts
30077
30695
  function tileNumber(property, fallback) {
@@ -30142,6 +30760,57 @@ var LAND_THEMES = {
30142
30760
  // skipper is navigating by. Left at the shipped value rather than
30143
30761
  // lowered, because lowering it is a separate decision from this one.
30144
30762
  exaggeration: 0.45
30763
+ },
30764
+ // ── E-ink: paper land, no relief ──────────────────────────────────────
30765
+ // Hillshade is a continuous grey ramp — exactly what an e-ink panel cannot
30766
+ // show (and a moving one costs a refresh), so both e-ink palettes switch it
30767
+ // off (`exaggeration: 0`). Land is the chart's own LANDA in that table, so
30768
+ // charted and CHRTland land are one tone; labels are black on paper.
30769
+ EINK_COLOR: {
30770
+ base: "#f2e2b0",
30771
+ cover: { wood: "#d6e0b0", wetland: "#d0dccc", urban: "#e0dacb", sand: "#f2e2b0" },
30772
+ contour: "#a08a5a",
30773
+ contourIndex: "#6b5a3e",
30774
+ road: "#d8c08a",
30775
+ roadMajor: "#c49a4a",
30776
+ label: "#000000",
30777
+ halo: "#ffffff",
30778
+ shadow: "#6b5a3e",
30779
+ highlight: "#ffffff",
30780
+ fade: "#ffffff",
30781
+ exaggeration: 0
30782
+ },
30783
+ // Mono: one land tone (MID). Land cover is not drawn as tone — four greys
30784
+ // cannot spare one for "wood" — so every cover class is the land tone.
30785
+ EINK_MONO: {
30786
+ base: "#aaaaaa",
30787
+ cover: { wood: "#aaaaaa", wetland: "#aaaaaa", urban: "#aaaaaa", sand: "#aaaaaa" },
30788
+ contour: "#555555",
30789
+ contourIndex: "#555555",
30790
+ road: "#ffffff",
30791
+ roadMajor: "#ffffff",
30792
+ label: "#000000",
30793
+ halo: "#ffffff",
30794
+ shadow: "#555555",
30795
+ highlight: "#ffffff",
30796
+ fade: "#ffffff",
30797
+ exaggeration: 0
30798
+ },
30799
+ // Night: land is the DIM grey (the chart's EINK_NIGHT LANDA), drawn on in
30800
+ // black so the land adds no lit lines of its own; labels the night line grey.
30801
+ EINK_NIGHT: {
30802
+ base: "#555555",
30803
+ cover: { wood: "#555555", wetland: "#555555", urban: "#555555", sand: "#555555" },
30804
+ contour: "#000000",
30805
+ contourIndex: "#000000",
30806
+ road: "#000000",
30807
+ roadMajor: "#000000",
30808
+ label: "#aaaaaa",
30809
+ halo: "#000000",
30810
+ shadow: "#000000",
30811
+ highlight: "#555555",
30812
+ fade: "#000000",
30813
+ exaggeration: 0
30145
30814
  }
30146
30815
  };
30147
30816
  var FITS = ["<=", tileNumber("min_z", 0), ["zoom"]];
@@ -30524,7 +31193,10 @@ var META_LAYERS = /* @__PURE__ */ new Set([
30524
31193
  var PALETTE_NAMES = {
30525
31194
  DAY: "DAY_BRIGHT",
30526
31195
  DUSK: "DUSK",
30527
- NIGHT: "NIGHT"
31196
+ NIGHT: "NIGHT",
31197
+ EINK_COLOR: "EINK_COLOR",
31198
+ EINK_MONO: "EINK_MONO",
31199
+ EINK_NIGHT: "EINK_NIGHT"
30528
31200
  };
30529
31201
  var SYMBOL_SET_LABELS = {
30530
31202
  s52: "S-52",
@@ -30636,7 +31308,7 @@ var ORIENTATION_OPACITY_SCALE = {
30636
31308
  };
30637
31309
  var ORIENTATION_OPACITY_MAX = 0.92;
30638
31310
  var ORIENTATION_BOOST_MINZOOM = 11;
30639
- function fanOrientationLabelByCellBand(label, bandFan, objectClass) {
31311
+ function fanOrientationLabelByCellBand(label, bandFan, objectClass2) {
30640
31312
  const cellBandExpr = [
30641
31313
  "slice",
30642
31314
  ["coalesce", ["get", "_cell_id"], "??9???"],
@@ -30644,7 +31316,7 @@ function fanOrientationLabelByCellBand(label, bandFan, objectClass) {
30644
31316
  3
30645
31317
  ];
30646
31318
  const variants = [];
30647
- const scale = objectClass ? ORIENTATION_OPACITY_SCALE[objectClass] ?? 1 : 1;
31319
+ const scale = objectClass2 ? ORIENTATION_OPACITY_SCALE[objectClass2] ?? 1 : 1;
30648
31320
  for (const code of BAND_DRAW_ORDER) {
30649
31321
  const range = BAND_PRIORITY_RANGES[code];
30650
31322
  const curve = CELL_BAND_OPACITY_CURVE[code];
@@ -30843,17 +31515,18 @@ function generateStyle(palette = "DAY", opts = {}) {
30843
31515
  const bandFan = opts.bandFan ?? "collapsed";
30844
31516
  const lockedBounds = opts.chartMode?.lockedBounds;
30845
31517
  const bundles = [];
30846
- for (const objectClass of orderedClasses) {
30847
- if (META_LAYERS.has(objectClass)) continue;
31518
+ for (const objectClass2 of orderedClasses) {
31519
+ if (META_LAYERS.has(objectClass2)) continue;
30848
31520
  bundles.push({
30849
- objectClass,
30850
- featureLayers: buildLayersForClass(objectClass, palette, sourceId, {
31521
+ objectClass: objectClass2,
31522
+ featureLayers: buildLayersForClass(objectClass2, palette, sourceId, {
30851
31523
  safetyContour: opts.safetyContour
30852
31524
  }),
30853
- label: buildLabelLayer2(objectClass, palette, sourceId, opts.depthUnits ?? "meters", {
31525
+ label: buildLabelLayer2(objectClass2, palette, sourceId, opts.depthUnits ?? "meters", {
30854
31526
  safetyContour: opts.safetyContour,
30855
31527
  twoShadeSoundings: opts.twoShadeSoundings,
30856
- soundingFontWeight: opts.soundingFontWeight
31528
+ soundingFontWeight: opts.soundingFontWeight,
31529
+ depthLabelSize: opts.depthLabelSize
30857
31530
  })
30858
31531
  });
30859
31532
  }
@@ -30994,17 +31667,17 @@ function generateStyle(palette = "DAY", opts = {}) {
30994
31667
  if (opts.labels === "placename") {
30995
31668
  labelLayers.push(...buildPlaceNameLayers(sourceId, palette));
30996
31669
  } else {
30997
- for (const { objectClass, label } of bundles) {
31670
+ for (const { objectClass: objectClass2, label } of bundles) {
30998
31671
  if (!label) continue;
30999
- if (getLabelBehavior(objectClass) === "orientation-cross-band") {
31000
- if (objectClass === "LNDARE" || objectClass === "SEAARE") {
31672
+ if (getLabelBehavior(objectClass2) === "orientation-cross-band") {
31673
+ if (objectClass2 === "LNDARE" || objectClass2 === "SEAARE") {
31001
31674
  const Z = ISLAND_LABEL_HOIST_MINZOOM;
31002
- const { hoist, watermark } = objectClass === "LNDARE" ? splitOrientationFan(
31003
- fanOrientationLabelByCellBand(label, bandFan, objectClass),
31675
+ const { hoist, watermark } = objectClass2 === "LNDARE" ? splitOrientationFan(
31676
+ fanOrientationLabelByCellBand(label, bandFan, objectClass2),
31004
31677
  [">=", ["zoom"], Z],
31005
31678
  ["<", ["zoom"], Z]
31006
31679
  ) : splitOrientationFan(
31007
- fanOrientationLabelByCellBand(label, bandFan, objectClass),
31680
+ fanOrientationLabelByCellBand(label, bandFan, objectClass2),
31008
31681
  ["<=", buildSeaAreaTierExpression(), MAJOR_WATER_TIER_MAX],
31009
31682
  [">", buildSeaAreaTierExpression(), MAJOR_WATER_TIER_MAX],
31010
31683
  { "text-padding": MAJOR_WATER_TEXT_PADDING }
@@ -31013,7 +31686,7 @@ function generateStyle(palette = "DAY", opts = {}) {
31013
31686
  labelLayers.push(...watermark);
31014
31687
  continue;
31015
31688
  }
31016
- labelLayers.push(...fanOrientationLabelByCellBand(label, bandFan, objectClass));
31689
+ labelLayers.push(...fanOrientationLabelByCellBand(label, bandFan, objectClass2));
31017
31690
  }
31018
31691
  }
31019
31692
  }
@@ -31277,10 +31950,8 @@ function generateStyle(palette = "DAY", opts = {}) {
31277
31950
  glyphs: `${glyphsBase}/{fontstack}/{range}.pbf`,
31278
31951
  layers: finalLayers
31279
31952
  };
31280
- if (symbolSet === "s101") return applyS101SymbolSet(style);
31281
- if (symbolSet === "chrt") return applyChrtSymbolSet(style, palette);
31282
- if (symbolSet === "raster") return applyRasterSymbolSet(style);
31283
- return style;
31953
+ const familyStyle = symbolSet === "s101" ? applyS101SymbolSet(style) : symbolSet === "chrt" ? applyChrtSymbolSet(style, palette) : symbolSet === "raster" ? applyRasterSymbolSet(style) : style;
31954
+ return applyEinkPortrayal(familyStyle, palette, { safetyContour: opts.safetyContour });
31284
31955
  }
31285
31956
  function generateStyleJSON(palette = "DAY", opts = {}) {
31286
31957
  return JSON.stringify(generateStyle(palette, opts), null, 2);
@@ -31424,7 +32095,7 @@ var DEFAULT_CUSTOM_STYLE_CONFIG = {
31424
32095
  water_body_settings: DEFAULT_WATER_BODY_SETTINGS,
31425
32096
  visible_layers: null
31426
32097
  };
31427
- var COLOR_TABLE_NAMES = /* @__PURE__ */ new Set(["DAY", "DUSK", "NIGHT"]);
32098
+ var COLOR_TABLE_NAMES = /* @__PURE__ */ new Set(["DAY", "DUSK", "NIGHT", "EINK_COLOR", "EINK_MONO", "EINK_NIGHT"]);
31428
32099
  var DISPLAY_MODES = /* @__PURE__ */ new Set(["BASE", "STANDARD", "ALL"]);
31429
32100
  var LAYER_NAME_RE = /^[A-Za-z0-9_$]{2,16}$/;
31430
32101
  var MAX_VISIBLE_LAYERS = 512;
@@ -31531,12 +32202,12 @@ function sanitizeCustomStyleConfig(raw) {
31531
32202
  }
31532
32203
  function normalizeTheme(raw) {
31533
32204
  if (!raw) return null;
31534
- const upper = raw.trim().toUpperCase();
32205
+ const upper = raw.trim().toUpperCase().replace(/-/g, "_");
31535
32206
  return COLOR_TABLE_NAMES.has(upper) ? upper : null;
31536
32207
  }
31537
32208
  function resolveCustomTheme(config, requested) {
31538
32209
  if (!requested) return { palette: config.default_color_table, themeFellBack: false };
31539
- if (config.enabled_color_tables.includes(requested)) {
32210
+ if (isEinkColorTable(requested) || config.enabled_color_tables.includes(requested)) {
31540
32211
  return { palette: requested, themeFellBack: false };
31541
32212
  }
31542
32213
  return { palette: config.default_color_table, themeFellBack: true };
@@ -32200,6 +32871,7 @@ var Chrt = class {
32200
32871
  ChrtError,
32201
32872
  DEFAULT_CLICK_TOLERANCE,
32202
32873
  DEFAULT_CUSTOM_STYLE_CONFIG,
32874
+ DEFAULT_DEPTH_LABEL_SIZE,
32203
32875
  DEFAULT_INSPECT_CLASS_RANK,
32204
32876
  DEFAULT_LABEL_SETTINGS,
32205
32877
  DEFAULT_NAV_AID_SETTINGS,
@@ -32211,7 +32883,14 @@ var Chrt = class {
32211
32883
  DEFAULT_WATER_BODY_SETTINGS,
32212
32884
  DEPARE_FILL_BRANCH_TOKENS,
32213
32885
  DEPARE_FILL_FALLBACK_TOKEN,
32886
+ DEPTH_LABEL_CLASSES,
32887
+ DEPTH_LABEL_SIZES,
32214
32888
  DRAPEABLE_LAYER_TYPES,
32889
+ EINK_COLOR_TABLES,
32890
+ EINK_DANGER_PATTERN,
32891
+ EINK_DOTS_PATTERN,
32892
+ EINK_HATCH_PATTERN,
32893
+ EINK_MONO_GREYS,
32215
32894
  INSPECT_CLASS_RANK,
32216
32895
  INSPECT_RANK_DEVIATIONS,
32217
32896
  InteractClient,
@@ -32227,6 +32906,7 @@ var Chrt = class {
32227
32906
  LAND_ATTRIBUTION,
32228
32907
  LAND_BUILD_MAXZOOM,
32229
32908
  LAND_REGION,
32909
+ LARGE_SOUNDING_PADDING_PX,
32230
32910
  NAUTICAL_SYMBOLS,
32231
32911
  NAV_AID_LAYER_GROUPS,
32232
32912
  NAV_AID_QUERY_LAYERS,
@@ -32263,6 +32943,9 @@ var Chrt = class {
32263
32943
  S52_DAY,
32264
32944
  S52_DRAW_ORDER,
32265
32945
  S52_DUSK,
32946
+ S52_EINK_COLOR,
32947
+ S52_EINK_MONO,
32948
+ S52_EINK_NIGHT,
32266
32949
  S52_LOOKUP,
32267
32950
  S52_NIGHT,
32268
32951
  S52_PATTERN_SPRITE_PREFIX,
@@ -32281,6 +32964,8 @@ var Chrt = class {
32281
32964
  applyChrtSymbolSet,
32282
32965
  applyColorTheme,
32283
32966
  applyCustomStyleSettings,
32967
+ applyDepthLabelSize,
32968
+ applyEinkPortrayal,
32284
32969
  applyRasterSymbolSet,
32285
32970
  applyS101SymbolSet,
32286
32971
  atonIconExpression,
@@ -32360,7 +33045,10 @@ var Chrt = class {
32360
33045
  iconExpressionForFamily,
32361
33046
  isDepthAreaClassLayerId,
32362
33047
  isDrapeable,
33048
+ isEinkColorTable,
33049
+ isEinkGreyTable,
32363
33050
  isLabelLayerVisible,
33051
+ isMapLive,
32364
33052
  isNavAidLayer,
32365
33053
  isNavAidLayerVisible,
32366
33054
  isSoundingLayerVisible,
@@ -32378,6 +33066,7 @@ var Chrt = class {
32378
33066
  normalizeTheme,
32379
33067
  opaqueSplitBaseOf,
32380
33068
  paginate,
33069
+ parseDepthLabelSize,
32381
33070
  parseListCodes,
32382
33071
  parseSigseqTerms,
32383
33072
  parseSymbolFamily,
@@ -32397,6 +33086,7 @@ var Chrt = class {
32397
33086
  resolveTargetClass,
32398
33087
  rhythmProperties,
32399
33088
  s101ArtworkProvenance,
33089
+ safetyContourValues,
32400
33090
  sanitizeCustomStyleConfig,
32401
33091
  scaleLinearLight,
32402
33092
  scaleSizeValue,