@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/CHANGELOG.md +35 -0
- package/README.md +29 -6
- package/dist/atlas/browser.cjs +478 -15
- package/dist/atlas/browser.cjs.map +1 -1
- package/dist/atlas/browser.d.cts +8 -3
- package/dist/atlas/browser.d.ts +8 -3
- package/dist/atlas/browser.js +478 -15
- package/dist/atlas/browser.js.map +1 -1
- package/dist/index.cjs +2293 -1603
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1272 -1045
- package/dist/index.d.ts +1272 -1045
- package/dist/index.js +2274 -1603
- package/dist/index.js.map +1 -1
- package/dist/symbol-families-De7sN-p5.d.cts +93 -0
- package/dist/symbol-families-De7sN-p5.d.ts +93 -0
- package/package.json +1 -1
- package/dist/symbol-families-D2Kx20HJ.d.cts +0 -47
- package/dist/symbol-families-D2Kx20HJ.d.ts +0 -47
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
|
-
|
|
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(
|
|
20067
|
-
const lookup = S52_ATON_LOOKUPS[
|
|
20068
|
-
if (!lookup) throw new Error(`s52-aton-expressions: no generated lookup for ${
|
|
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
|
|
27603
|
-
|
|
27604
|
-
|
|
27605
|
-
|
|
27606
|
-
|
|
27607
|
-
|
|
27608
|
-
|
|
27609
|
-
|
|
27610
|
-
|
|
27611
|
-
|
|
27612
|
-
|
|
27613
|
-
|
|
27614
|
-
|
|
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
|
|
27618
|
-
|
|
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
|
-
|
|
27621
|
-
|
|
27622
|
-
return
|
|
27623
|
-
|
|
27624
|
-
|
|
27625
|
-
|
|
27626
|
-
|
|
27627
|
-
|
|
27628
|
-
|
|
27629
|
-
|
|
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
|
|
27645
|
-
return
|
|
27646
|
-
|
|
27647
|
-
|
|
27648
|
-
|
|
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
|
|
27664
|
-
|
|
27665
|
-
|
|
27666
|
-
|
|
27667
|
-
|
|
27668
|
-
|
|
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
|
|
27679
|
-
|
|
27680
|
-
|
|
27681
|
-
|
|
27682
|
-
|
|
27683
|
-
|
|
27684
|
-
|
|
27685
|
-
|
|
27686
|
-
|
|
27687
|
-
|
|
27688
|
-
|
|
27689
|
-
|
|
27690
|
-
|
|
27691
|
-
|
|
27692
|
-
|
|
27693
|
-
|
|
27694
|
-
|
|
27695
|
-
|
|
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
|
-
|
|
27741
|
-
layout["icon-image"]
|
|
27742
|
-
|
|
27743
|
-
|
|
27744
|
-
|
|
27745
|
-
|
|
27746
|
-
|
|
27747
|
-
|
|
27748
|
-
|
|
27749
|
-
|
|
27750
|
-
|
|
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"] = "
|
|
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(
|
|
27913
|
-
const lookup = S52_ATON_LOOKUPS_PAPER[
|
|
27914
|
-
if (!lookup) throw new Error(`raster-style: no paper lookup for ${
|
|
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
|
|
27929
|
-
if (typeof
|
|
27930
|
-
layout["icon-image"] = rasterAtonIconExpression(
|
|
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(
|
|
27963
|
-
const s52 = getIconExpression(
|
|
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[
|
|
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(
|
|
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(
|
|
28168
|
-
if (!S52_LOOKUP[
|
|
28169
|
-
const entry = S52_LOOKUP[
|
|
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(
|
|
29226
|
+
const geomTypes = getGeomTypesForClass(objectClass2, entry?.style.type);
|
|
28172
29227
|
for (const geomType of geomTypes) {
|
|
28173
|
-
const resolved = resolveStyle(
|
|
28174
|
-
const layerId = `s52-${
|
|
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":
|
|
29234
|
+
"source-layer": objectClass2,
|
|
28180
29235
|
paint: resolved.paint,
|
|
28181
29236
|
metadata: {
|
|
28182
|
-
"s52: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(
|
|
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(
|
|
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(
|
|
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[
|
|
28202
|
-
const [z1, w1, z2, w2] = ZOOM_SCALED_LINES2[
|
|
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(
|
|
28207
|
-
const childOffset = CHILD_ICON_OFFSETS[
|
|
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 (
|
|
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 (
|
|
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-${
|
|
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-${
|
|
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-${
|
|
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-${
|
|
29473
|
+
id: `s52-${objectClass2}-danger-ring`,
|
|
28419
29474
|
type: "symbol",
|
|
28420
29475
|
source: sourceId,
|
|
28421
|
-
"source-layer":
|
|
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":
|
|
29509
|
+
metadata: { "s52:objectClass": objectClass2, "s52:role": "isolated-danger-ring" }
|
|
28455
29510
|
});
|
|
28456
29511
|
}
|
|
28457
|
-
if (resolved.type === "fill" && FILL_OUTLINE_LAYERS.has(
|
|
28458
|
-
const outlineColor =
|
|
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-${
|
|
29515
|
+
id: `s52-${objectClass2}-outline`,
|
|
28461
29516
|
type: "line",
|
|
28462
29517
|
source: sourceId,
|
|
28463
|
-
"source-layer":
|
|
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":
|
|
29525
|
+
metadata: { "s52:objectClass": objectClass2, "s52:role": "outline" }
|
|
28471
29526
|
});
|
|
28472
29527
|
}
|
|
28473
|
-
if (resolved.type === "fill" && REGULATED_AREA_BOUNDARY_LAYERS[
|
|
28474
|
-
const lineStyle = REGULATED_AREA_BOUNDARY_LINESTYLES[
|
|
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[
|
|
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-${
|
|
29537
|
+
id: `s52-${objectClass2}-boundary`,
|
|
28483
29538
|
type: "line",
|
|
28484
29539
|
source: sourceId,
|
|
28485
|
-
"source-layer":
|
|
29540
|
+
"source-layer": objectClass2,
|
|
28486
29541
|
filter: GEOM_FILTERS.fill,
|
|
28487
29542
|
paint: boundaryPaint,
|
|
28488
29543
|
metadata: {
|
|
28489
|
-
"s52:objectClass":
|
|
29544
|
+
"s52:objectClass": objectClass2,
|
|
28490
29545
|
"s52:role": "boundary",
|
|
28491
29546
|
...lineStyle ? { "s52:lineStyle": lineStyle } : {}
|
|
28492
29547
|
}
|
|
28493
29548
|
});
|
|
28494
29549
|
}
|
|
28495
|
-
if (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
|
30267
|
+
return layers;
|
|
29570
30268
|
}
|
|
29571
|
-
function
|
|
29572
|
-
if (!
|
|
29573
|
-
|
|
29574
|
-
|
|
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
|
-
|
|
29577
|
-
|
|
29578
|
-
|
|
29579
|
-
|
|
29580
|
-
|
|
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
|
-
|
|
29593
|
-
|
|
29594
|
-
|
|
29595
|
-
|
|
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
|
-
|
|
29604
|
-
[
|
|
29605
|
-
|
|
29606
|
-
|
|
29607
|
-
|
|
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
|
-
|
|
29640
|
-
|
|
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
|
-
|
|
29739
|
-
|
|
29740
|
-
|
|
29741
|
-
|
|
29742
|
-
|
|
29743
|
-
return [
|
|
29744
|
-
|
|
29745
|
-
|
|
29746
|
-
|
|
29747
|
-
|
|
29748
|
-
|
|
29749
|
-
|
|
29750
|
-
|
|
29751
|
-
|
|
29752
|
-
|
|
29753
|
-
|
|
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
|
-
|
|
29867
|
-
|
|
29868
|
-
|
|
29869
|
-
|
|
29870
|
-
|
|
29871
|
-
|
|
29872
|
-
|
|
29873
|
-
|
|
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
|
-
|
|
29876
|
-
|
|
29877
|
-
|
|
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
|
-
"
|
|
30042
|
-
|
|
30043
|
-
|
|
30044
|
-
"
|
|
30045
|
-
|
|
30046
|
-
|
|
30047
|
-
|
|
30048
|
-
|
|
30049
|
-
|
|
30050
|
-
|
|
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
|
-
"
|
|
30069
|
-
"
|
|
30070
|
-
"
|
|
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,
|
|
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 =
|
|
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
|
|
30847
|
-
if (META_LAYERS.has(
|
|
31518
|
+
for (const objectClass2 of orderedClasses) {
|
|
31519
|
+
if (META_LAYERS.has(objectClass2)) continue;
|
|
30848
31520
|
bundles.push({
|
|
30849
|
-
objectClass,
|
|
30850
|
-
featureLayers: buildLayersForClass(
|
|
31521
|
+
objectClass: objectClass2,
|
|
31522
|
+
featureLayers: buildLayersForClass(objectClass2, palette, sourceId, {
|
|
30851
31523
|
safetyContour: opts.safetyContour
|
|
30852
31524
|
}),
|
|
30853
|
-
label: buildLabelLayer2(
|
|
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(
|
|
31000
|
-
if (
|
|
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 } =
|
|
31003
|
-
fanOrientationLabelByCellBand(label, bandFan,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
31281
|
-
|
|
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,
|