@chrtco/sdk 0.10.39 → 0.10.40
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 +134 -0
- package/dist/index.cjs +987 -654
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +529 -17
- package/dist/index.d.ts +529 -17
- package/dist/index.js +956 -654
- package/dist/index.js.map +1 -1
- package/package.json +3 -20
package/dist/index.js
CHANGED
|
@@ -367,9 +367,13 @@ var RELIEF_REGION = "CHRT_RELIEF";
|
|
|
367
367
|
var BASE_REGION = "CHRT_BASE";
|
|
368
368
|
var BASE_MINZOOM = 0;
|
|
369
369
|
var BASE_MAXZOOM = 11;
|
|
370
|
+
var BASE_ATTRIBUTION = "\xA9 CHRT \xB7 Made with Natural Earth \xB7 \xA9 OpenStreetMap contributors";
|
|
371
|
+
var LAND_ATTRIBUTION = "\xA9 OpenStreetMap contributors";
|
|
372
|
+
var RELIEF_ATTRIBUTION = "Elevation: USGS 3D Elevation Program";
|
|
370
373
|
var RELIEF_MINZOOM = 9;
|
|
371
374
|
var RELIEF_MAXZOOM = 13;
|
|
372
375
|
var RELIEF_TILE_SIZE = 512;
|
|
376
|
+
var RELIEF_ENCODING = "terrarium";
|
|
373
377
|
var BUILD_PARAM = "v";
|
|
374
378
|
function tileTemplate(base, region, extension, buildId) {
|
|
375
379
|
const url = `${base}/v1/tiles/${region}/{z}/{x}/{y}.${extension}`;
|
|
@@ -413,14 +417,15 @@ function buildSources(opts = {}) {
|
|
|
413
417
|
type: "vector",
|
|
414
418
|
tiles: [tileTemplate(base, BASE_REGION, "mvt", buildFor(BASE_REGION))],
|
|
415
419
|
minzoom: BASE_MINZOOM,
|
|
416
|
-
maxzoom: BASE_MAXZOOM
|
|
420
|
+
maxzoom: BASE_MAXZOOM,
|
|
421
|
+
attribution: BASE_ATTRIBUTION
|
|
417
422
|
};
|
|
418
423
|
}
|
|
419
424
|
if (opts.relief) {
|
|
420
425
|
sources[RELIEF_REGION] = {
|
|
421
426
|
type: "raster-dem",
|
|
422
427
|
tiles: [tileTemplate(base, RELIEF_REGION, "png", buildFor(RELIEF_REGION))],
|
|
423
|
-
encoding:
|
|
428
|
+
encoding: RELIEF_ENCODING,
|
|
424
429
|
tileSize: RELIEF_TILE_SIZE,
|
|
425
430
|
minzoom: RELIEF_MINZOOM,
|
|
426
431
|
maxzoom: RELIEF_MAXZOOM
|
|
@@ -446,6 +451,7 @@ var TilesEndpoint = class {
|
|
|
446
451
|
if (opts.mode) params.set("mode", opts.mode);
|
|
447
452
|
if (opts.land) params.set("land", "1");
|
|
448
453
|
if (opts.relief) params.set("relief", "1");
|
|
454
|
+
if (opts.units === "imperial") params.set("units", "imperial");
|
|
449
455
|
const query2 = params.size > 0 ? `?${params.toString()}` : "";
|
|
450
456
|
return `${this.tileBaseUrl}/v1/styles/${this.region}/style.json${query2}`;
|
|
451
457
|
}
|
|
@@ -571,6 +577,9 @@ var WeatherEndpoint = class {
|
|
|
571
577
|
*
|
|
572
578
|
* `unavailable` names every field the station did not report, so absence is
|
|
573
579
|
* stated rather than inferred from a missing key.
|
|
580
|
+
*
|
|
581
|
+
* Pass `hours` for the station's recent readings as `series` — NDBC's own
|
|
582
|
+
* readings at NDBC's own instants, never interpolated.
|
|
574
583
|
*/
|
|
575
584
|
async conditions(options) {
|
|
576
585
|
return this.http.request(
|
|
@@ -579,7 +588,8 @@ var WeatherEndpoint = class {
|
|
|
579
588
|
lat: options.lat,
|
|
580
589
|
lon: options.lon,
|
|
581
590
|
units: options.units,
|
|
582
|
-
station: options.station
|
|
591
|
+
station: options.station,
|
|
592
|
+
hours: options.hours
|
|
583
593
|
},
|
|
584
594
|
options.signal
|
|
585
595
|
);
|
|
@@ -1043,10 +1053,10 @@ async function readManifest(manifestPath, fs) {
|
|
|
1043
1053
|
if (!manifestPath) return emptyManifest();
|
|
1044
1054
|
if (!await fs.exists(manifestPath)) return emptyManifest();
|
|
1045
1055
|
const raw = await fs.readFile(manifestPath);
|
|
1046
|
-
const
|
|
1056
|
+
const text2 = new TextDecoder().decode(raw);
|
|
1047
1057
|
let parsed;
|
|
1048
1058
|
try {
|
|
1049
|
-
parsed = JSON.parse(
|
|
1059
|
+
parsed = JSON.parse(text2);
|
|
1050
1060
|
} catch {
|
|
1051
1061
|
throw new OfflineError(
|
|
1052
1062
|
"unexpected_response",
|
|
@@ -1070,8 +1080,8 @@ async function readManifest(manifestPath, fs) {
|
|
|
1070
1080
|
};
|
|
1071
1081
|
}
|
|
1072
1082
|
async function writeManifest(manifestPath, fs, manifest) {
|
|
1073
|
-
const
|
|
1074
|
-
const bytes = new TextEncoder().encode(
|
|
1083
|
+
const text2 = JSON.stringify(manifest, null, 2);
|
|
1084
|
+
const bytes = new TextEncoder().encode(text2);
|
|
1075
1085
|
await fs.writeFile(manifestPath, bytes);
|
|
1076
1086
|
}
|
|
1077
1087
|
function usedBytes(manifest, excludeRegion) {
|
|
@@ -2036,110 +2046,438 @@ function s57AbbrTable(acronym) {
|
|
|
2036
2046
|
return cached;
|
|
2037
2047
|
}
|
|
2038
2048
|
|
|
2039
|
-
// src/
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2049
|
+
// src/atlas/s52-colors.ts
|
|
2050
|
+
var S52_DAY = {
|
|
2051
|
+
// Depth
|
|
2052
|
+
NODTA: "#a4a4a4",
|
|
2053
|
+
DEPIT: "#009678",
|
|
2054
|
+
DEPVS: "#7fbfdf",
|
|
2055
|
+
DEPMS: "#a7d4e8",
|
|
2056
|
+
DEPMD: "#cae7f3",
|
|
2057
|
+
DEPDW: "#ecf7fc",
|
|
2058
|
+
// Land / topography
|
|
2059
|
+
LANDA: "#e6d1a1",
|
|
2060
|
+
LANDF: "#c8b078",
|
|
2061
|
+
CHBRN: "#ab7948",
|
|
2062
|
+
CHGRF: "#878787",
|
|
2063
|
+
CHGRD: "#555555",
|
|
2064
|
+
// Chart symbology
|
|
2065
|
+
CHBLK: "#000000",
|
|
2066
|
+
CSTLN: "#000000",
|
|
2067
|
+
OUTLW: "#000000",
|
|
2068
|
+
BKAJ1: "#4d4d4d",
|
|
2069
|
+
// Navigation / traffic
|
|
2070
|
+
TRFCD: "#d200d2",
|
|
2071
|
+
TRFCF: "#e680e6",
|
|
2072
|
+
RESBL: "#0064b4",
|
|
2073
|
+
APTS1: "#d200d2",
|
|
2074
|
+
// Danger / hazard
|
|
2075
|
+
DNGHL: "#ff0000",
|
|
2076
|
+
ISHDG: "#cc0000",
|
|
2077
|
+
// Lights
|
|
2078
|
+
LITRD: "#ee0000",
|
|
2079
|
+
LITGN: "#00c800",
|
|
2080
|
+
LITYW: "#fddc00",
|
|
2081
|
+
LITWT: "#ffffff",
|
|
2082
|
+
LITBL: "#0064ff",
|
|
2083
|
+
LITOR: "#ff8c00",
|
|
2084
|
+
// Buoys / marks
|
|
2085
|
+
RADHI: "#68e456",
|
|
2086
|
+
RADLO: "#3f8a34",
|
|
2087
|
+
PLRTE: "#dc4025",
|
|
2088
|
+
ARPAT: "#3fa56f",
|
|
2089
|
+
ISDNG: "#c545c3",
|
|
2090
|
+
// Chart colours
|
|
2091
|
+
CHRED: "#f15469",
|
|
2092
|
+
CHGRN: "#68e456",
|
|
2093
|
+
CHYLW: "#f4da48",
|
|
2094
|
+
CHMGD: "#c545c3",
|
|
2095
|
+
CHMGF: "#d3a6e9",
|
|
2096
|
+
CHWHT: "#d4eaee",
|
|
2097
|
+
CHCOR: "#eb7d36",
|
|
2098
|
+
// Soundings
|
|
2099
|
+
SNDG1: "#7d898c",
|
|
2100
|
+
SNDG2: "#070707",
|
|
2101
|
+
DEPSC: "#525a5c",
|
|
2102
|
+
DEPCN: "#555555",
|
|
2103
|
+
// Utility / infrastructure
|
|
2104
|
+
UINFD: "#070707",
|
|
2105
|
+
UINFF: "#7d898c",
|
|
2106
|
+
UINFR: "#f15469",
|
|
2107
|
+
UINFG: "#68e456",
|
|
2108
|
+
UINFO: "#eb7d36",
|
|
2109
|
+
UINFB: "#3a78f0",
|
|
2110
|
+
UINFM: "#c545c3",
|
|
2111
|
+
UIBCK: "#d4eaee",
|
|
2112
|
+
UIBDR: "#7d898c",
|
|
2113
|
+
UIAFD: "#73b6ef",
|
|
2114
|
+
UIAFF: "#c9b97a",
|
|
2115
|
+
PIPLN: "#55aa00",
|
|
2116
|
+
// Outlines
|
|
2117
|
+
OUTLL: "#c9b97a",
|
|
2118
|
+
// Miscellaneous
|
|
2119
|
+
SCLBR: "#eb7d36",
|
|
2120
|
+
NINFO: "#eb7d36",
|
|
2121
|
+
ADINF: "#b29f34",
|
|
2122
|
+
RESGR: "#7d898c",
|
|
2123
|
+
SHIPS: "#070707",
|
|
2124
|
+
PSTRK: "#070707",
|
|
2125
|
+
SYTRK: "#7d898c",
|
|
2126
|
+
APLRT: "#eb7d36",
|
|
2127
|
+
CURSR: "#eb7d36",
|
|
2128
|
+
BKAJ2: "#232728",
|
|
2129
|
+
RES01: "#a3b4b7",
|
|
2130
|
+
RES02: "#a3b4b7",
|
|
2131
|
+
RES03: "#a3b4b7"
|
|
2064
2132
|
};
|
|
2065
|
-
var
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2133
|
+
var S52_DUSK = {
|
|
2134
|
+
NODTA: "#6b6b6b",
|
|
2135
|
+
DEPIT: "#006650",
|
|
2136
|
+
DEPVS: "#487085",
|
|
2137
|
+
DEPMS: "#608a9d",
|
|
2138
|
+
DEPMD: "#7ba6b8",
|
|
2139
|
+
DEPDW: "#95bfd0",
|
|
2140
|
+
LANDA: "#8a7d5e",
|
|
2141
|
+
LANDF: "#7a6a48",
|
|
2142
|
+
CHBRN: "#6e4e30",
|
|
2143
|
+
CHGRF: "#585858",
|
|
2144
|
+
CHGRD: "#3a3a3a",
|
|
2145
|
+
CHBLK: "#b0b0b0",
|
|
2146
|
+
CSTLN: "#b0b0b0",
|
|
2147
|
+
OUTLW: "#b0b0b0",
|
|
2148
|
+
BKAJ1: "#707070",
|
|
2149
|
+
TRFCD: "#a000a0",
|
|
2150
|
+
TRFCF: "#b060b0",
|
|
2151
|
+
RESBL: "#004a88",
|
|
2152
|
+
APTS1: "#a000a0",
|
|
2153
|
+
DNGHL: "#cc0000",
|
|
2154
|
+
ISHDG: "#990000",
|
|
2155
|
+
LITRD: "#bb0000",
|
|
2156
|
+
LITGN: "#009900",
|
|
2157
|
+
LITYW: "#c8b000",
|
|
2158
|
+
LITWT: "#d0d0d0",
|
|
2159
|
+
LITBL: "#004abb",
|
|
2160
|
+
LITOR: "#cc7000",
|
|
2161
|
+
RADHI: "#009940",
|
|
2162
|
+
RADLO: "#2d6330",
|
|
2163
|
+
PLRTE: "#a03020",
|
|
2164
|
+
ARPAT: "#306040",
|
|
2165
|
+
ISDNG: "#903090",
|
|
2166
|
+
CHRED: "#b05060",
|
|
2167
|
+
CHGRN: "#509040",
|
|
2168
|
+
CHYLW: "#b0a030",
|
|
2169
|
+
CHMGD: "#903090",
|
|
2170
|
+
CHMGF: "#905090",
|
|
2171
|
+
CHWHT: "#90a0a0",
|
|
2172
|
+
CHCOR: "#a06030",
|
|
2173
|
+
SNDG1: "#606060",
|
|
2174
|
+
SNDG2: "#b0b0b0",
|
|
2175
|
+
DEPSC: "#606060",
|
|
2176
|
+
DEPCN: "#2E2E2E",
|
|
2177
|
+
UINFD: "#b0b0b0",
|
|
2178
|
+
UINFF: "#606060",
|
|
2179
|
+
UINFR: "#b05060",
|
|
2180
|
+
UINFG: "#509040",
|
|
2181
|
+
UINFO: "#a06030",
|
|
2182
|
+
UINFB: "#304090",
|
|
2183
|
+
UINFM: "#903090",
|
|
2184
|
+
UIBCK: "#1a1a1a",
|
|
2185
|
+
UIBDR: "#606060",
|
|
2186
|
+
UIAFD: "#405060",
|
|
2187
|
+
UIAFF: "#605840",
|
|
2188
|
+
PIPLN: "#3d7a00",
|
|
2189
|
+
OUTLL: "#605840",
|
|
2190
|
+
SCLBR: "#a06030",
|
|
2191
|
+
NINFO: "#a06030",
|
|
2192
|
+
ADINF: "#807020",
|
|
2193
|
+
RESGR: "#606060",
|
|
2194
|
+
SHIPS: "#b0b0b0",
|
|
2195
|
+
PSTRK: "#b0b0b0",
|
|
2196
|
+
SYTRK: "#606060",
|
|
2197
|
+
APLRT: "#a06030",
|
|
2198
|
+
CURSR: "#cc0000",
|
|
2199
|
+
BKAJ2: "#252525",
|
|
2200
|
+
RES01: "#606060",
|
|
2201
|
+
RES02: "#606060",
|
|
2202
|
+
RES03: "#606060"
|
|
2111
2203
|
};
|
|
2112
|
-
var
|
|
2113
|
-
"
|
|
2114
|
-
"
|
|
2115
|
-
"
|
|
2116
|
-
"
|
|
2117
|
-
"
|
|
2118
|
-
"
|
|
2119
|
-
"
|
|
2120
|
-
"
|
|
2121
|
-
"
|
|
2122
|
-
"
|
|
2123
|
-
"
|
|
2124
|
-
"
|
|
2125
|
-
"
|
|
2126
|
-
"
|
|
2127
|
-
"
|
|
2128
|
-
"
|
|
2129
|
-
"
|
|
2130
|
-
"
|
|
2131
|
-
"
|
|
2132
|
-
"
|
|
2133
|
-
"
|
|
2134
|
-
"
|
|
2135
|
-
"
|
|
2136
|
-
"
|
|
2137
|
-
"
|
|
2138
|
-
"
|
|
2139
|
-
"
|
|
2140
|
-
"
|
|
2141
|
-
"
|
|
2142
|
-
"
|
|
2204
|
+
var S52_NIGHT = {
|
|
2205
|
+
NODTA: "#3a3a32",
|
|
2206
|
+
DEPIT: "#003828",
|
|
2207
|
+
DEPVS: "#356577",
|
|
2208
|
+
DEPMS: "#265066",
|
|
2209
|
+
DEPMD: "#102e44",
|
|
2210
|
+
DEPDW: "#051a28",
|
|
2211
|
+
LANDA: "#3a3020",
|
|
2212
|
+
LANDF: "#302818",
|
|
2213
|
+
CHBRN: "#382410",
|
|
2214
|
+
CHGRF: "#2a2a22",
|
|
2215
|
+
CHGRD: "#1e1e18",
|
|
2216
|
+
CHBLK: "#c8aa78",
|
|
2217
|
+
CSTLN: "#c8aa78",
|
|
2218
|
+
OUTLW: "#c8aa78",
|
|
2219
|
+
BKAJ1: "#504830",
|
|
2220
|
+
TRFCD: "#6e006e",
|
|
2221
|
+
TRFCF: "#803880",
|
|
2222
|
+
RESBL: "#002a50",
|
|
2223
|
+
APTS1: "#6e006e",
|
|
2224
|
+
DNGHL: "#a00000",
|
|
2225
|
+
ISHDG: "#700000",
|
|
2226
|
+
LITRD: "#880000",
|
|
2227
|
+
LITGN: "#006600",
|
|
2228
|
+
LITYW: "#887800",
|
|
2229
|
+
LITWT: "#a09878",
|
|
2230
|
+
LITBL: "#003088",
|
|
2231
|
+
LITOR: "#884800",
|
|
2232
|
+
RADHI: "#005528",
|
|
2233
|
+
RADLO: "#1a3a20",
|
|
2234
|
+
PLRTE: "#6e2018",
|
|
2235
|
+
ARPAT: "#1a3028",
|
|
2236
|
+
ISDNG: "#481848",
|
|
2237
|
+
CHRED: "#682030",
|
|
2238
|
+
CHGRN: "#285020",
|
|
2239
|
+
CHYLW: "#685828",
|
|
2240
|
+
CHMGD: "#481848",
|
|
2241
|
+
CHMGF: "#482848",
|
|
2242
|
+
CHWHT: "#484840",
|
|
2243
|
+
CHCOR: "#683818",
|
|
2244
|
+
SNDG1: "#383028",
|
|
2245
|
+
SNDG2: "#c8aa78",
|
|
2246
|
+
DEPSC: "#383028",
|
|
2247
|
+
// ⚠ DELIBERATE DEVIATION from the published S-52 NIGHT value (#1f2223),
|
|
2248
|
+
// recorded here because `docs/validation/s52-spec-gap-matrix.json` tracks
|
|
2249
|
+
// this token as `value-differs` and the CHRTstyles programme is bringing
|
|
2250
|
+
// colours TO spec — this one moves further away, on purpose.
|
|
2251
|
+
//
|
|
2252
|
+
// Spec NIGHT DEPCN is near-black, which measures ~1.1:1 against this
|
|
2253
|
+
// palette's own deep water (#051a28). S-52's NIGHT table assumes a
|
|
2254
|
+
// dark-adapted eye in a darkened wheelhouse; on a phone or a laptop at
|
|
2255
|
+
// anchor it is not dim, it is absent — reported as "can't see bottom
|
|
2256
|
+
// contours in night mode only". A contour nobody can see fails the
|
|
2257
|
+
// standard's intent while matching its number.
|
|
2258
|
+
//
|
|
2259
|
+
// The palette already makes this trade elsewhere and in the same
|
|
2260
|
+
// direction: NIGHT's CHBLK/CSTLN/OUTLW are #c8aa78, light warm linework on
|
|
2261
|
+
// dark water. This value is that family, dimmed to stay subordinate to the
|
|
2262
|
+
// coastline. Revisit with the CHRTstyles programme, which owns the
|
|
2263
|
+
// spec-vs-legibility call across all 63 tokens.
|
|
2264
|
+
DEPCN: "#A68E84",
|
|
2265
|
+
UINFD: "#c8aa78",
|
|
2266
|
+
UINFF: "#383028",
|
|
2267
|
+
UINFR: "#682030",
|
|
2268
|
+
UINFG: "#285020",
|
|
2269
|
+
UINFO: "#683818",
|
|
2270
|
+
UINFB: "#182848",
|
|
2271
|
+
UINFM: "#481848",
|
|
2272
|
+
UIBCK: "#0a0a08",
|
|
2273
|
+
UIBDR: "#383028",
|
|
2274
|
+
UIAFD: "#182838",
|
|
2275
|
+
UIAFF: "#302818",
|
|
2276
|
+
PIPLN: "#284800",
|
|
2277
|
+
OUTLL: "#302818",
|
|
2278
|
+
SCLBR: "#683818",
|
|
2279
|
+
NINFO: "#683818",
|
|
2280
|
+
ADINF: "#483818",
|
|
2281
|
+
RESGR: "#383028",
|
|
2282
|
+
SHIPS: "#c8aa78",
|
|
2283
|
+
PSTRK: "#c8aa78",
|
|
2284
|
+
SYTRK: "#484030",
|
|
2285
|
+
APLRT: "#683818",
|
|
2286
|
+
CURSR: "#a00000",
|
|
2287
|
+
BKAJ2: "#0a0a08",
|
|
2288
|
+
RES01: "#383028",
|
|
2289
|
+
RES02: "#383028",
|
|
2290
|
+
RES03: "#383028"
|
|
2291
|
+
};
|
|
2292
|
+
var S52_COLORS = S52_DAY;
|
|
2293
|
+
var COLOR_TABLES = {
|
|
2294
|
+
DAY: S52_DAY,
|
|
2295
|
+
DUSK: S52_DUSK,
|
|
2296
|
+
NIGHT: S52_NIGHT
|
|
2297
|
+
};
|
|
2298
|
+
function getColorTable(name) {
|
|
2299
|
+
return COLOR_TABLES[name];
|
|
2300
|
+
}
|
|
2301
|
+
function resolveColor(token, table = "DAY") {
|
|
2302
|
+
const t = COLOR_TABLES[table];
|
|
2303
|
+
return t[token] ?? (table === "NIGHT" ? "#c8aa78" : "#000000");
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
// src/interact/light-sigseq.ts
|
|
2307
|
+
var SIGSEQ_MAX_TERMS = 24;
|
|
2308
|
+
var SIGPER_TOLERANCE_SECONDS = 0.06;
|
|
2309
|
+
var LAMP_TICK_MS = 100;
|
|
2310
|
+
var TERM = /^\d+(?:\.\d+)?$/;
|
|
2311
|
+
function text(v) {
|
|
2312
|
+
if (v === null || v === void 0) return null;
|
|
2313
|
+
const s = String(v).trim();
|
|
2314
|
+
return s === "" ? null : s;
|
|
2315
|
+
}
|
|
2316
|
+
function parseSigseqTerms(sigseq, sigper) {
|
|
2317
|
+
const raw = text(sigseq);
|
|
2318
|
+
if (raw === null) return { refusal: "absent" };
|
|
2319
|
+
const terms = raw.split("+");
|
|
2320
|
+
if (terms.length === 0 || terms.length > SIGSEQ_MAX_TERMS) return { refusal: "malformed" };
|
|
2321
|
+
const phases = [];
|
|
2322
|
+
for (const term of terms) {
|
|
2323
|
+
const t = term.trim();
|
|
2324
|
+
if (t === "") return { refusal: "malformed" };
|
|
2325
|
+
const isEclipse = t.startsWith("(") && t.endsWith(")");
|
|
2326
|
+
const body = (isEclipse ? t.slice(1, -1) : t).trim();
|
|
2327
|
+
if (!TERM.test(body)) return { refusal: "malformed" };
|
|
2328
|
+
const seconds = Number(body);
|
|
2329
|
+
if (!(seconds > 0) || !Number.isFinite(seconds)) return { refusal: "nonPositiveDuration" };
|
|
2330
|
+
phases.push({ seconds, isOn: !isEclipse });
|
|
2331
|
+
}
|
|
2332
|
+
const sum = phases.reduce((t, p) => t + p.seconds, 0);
|
|
2333
|
+
if (!(sum > 0)) return { refusal: "nonPositiveDuration" };
|
|
2334
|
+
if (sigper !== null && sigper > 0) {
|
|
2335
|
+
if (Math.abs(sum - sigper) > SIGPER_TOLERANCE_SECONDS) return { refusal: "sigperMismatch" };
|
|
2336
|
+
return { phases, periodSeconds: sigper, checkedAgainstSigper: true };
|
|
2337
|
+
}
|
|
2338
|
+
return { phases, periodSeconds: sum, checkedAgainstSigper: false };
|
|
2339
|
+
}
|
|
2340
|
+
function rhythmProperties(parsed, onColoursHex) {
|
|
2341
|
+
if (parsed === null || parsed.phases.length === 0) return { rp: 0, rn: 0 };
|
|
2342
|
+
const out = { rp: parsed.periodSeconds, rn: parsed.phases.length };
|
|
2343
|
+
let end = 0;
|
|
2344
|
+
let onIndex = 0;
|
|
2345
|
+
parsed.phases.forEach((p, i) => {
|
|
2346
|
+
end += p.seconds;
|
|
2347
|
+
const k = i + 1;
|
|
2348
|
+
out[`r${k}`] = Math.round(end * 1e3) / 1e3;
|
|
2349
|
+
out[`o${k}`] = p.isOn ? 1 : 0;
|
|
2350
|
+
if (p.isOn) {
|
|
2351
|
+
out[`c${k}`] = onColoursHex[onIndex % Math.max(1, onColoursHex.length)] ?? "#ffffff";
|
|
2352
|
+
onIndex += 1;
|
|
2353
|
+
}
|
|
2354
|
+
});
|
|
2355
|
+
return out;
|
|
2356
|
+
}
|
|
2357
|
+
function phaseExpr(nowSeconds2) {
|
|
2358
|
+
return ["%", nowSeconds2, ["max", ["get", "rp"], 1e-3]];
|
|
2359
|
+
}
|
|
2360
|
+
function lampRhythmOpacity(nowSeconds2, base) {
|
|
2361
|
+
const cases = ["case", ["<=", ["get", "rn"], 0], base];
|
|
2362
|
+
for (let k = 1; k <= SIGSEQ_MAX_TERMS; k++) {
|
|
2363
|
+
cases.push(["<", ["var", "ph"], ["coalesce", ["get", `r${k}`], 1e9]], ["*", base, ["coalesce", ["get", `o${k}`], 1]]);
|
|
2364
|
+
}
|
|
2365
|
+
cases.push(0);
|
|
2366
|
+
return ["let", "ph", phaseExpr(nowSeconds2), cases];
|
|
2367
|
+
}
|
|
2368
|
+
function lampRhythmColour(nowSeconds2, fallback) {
|
|
2369
|
+
const cases = ["case", ["<=", ["get", "rn"], 0], fallback];
|
|
2370
|
+
for (let k = 1; k <= SIGSEQ_MAX_TERMS; k++) {
|
|
2371
|
+
cases.push(["<", ["var", "ph"], ["coalesce", ["get", `r${k}`], 1e9]], ["coalesce", ["get", `c${k}`], fallback]);
|
|
2372
|
+
}
|
|
2373
|
+
cases.push(fallback);
|
|
2374
|
+
return ["let", "ph", phaseExpr(nowSeconds2), cases];
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
// src/interact/decode.ts
|
|
2378
|
+
function nameAbbrTable(acronym) {
|
|
2379
|
+
const names = s57NameTable(acronym);
|
|
2380
|
+
const abbrs = s57AbbrTable(acronym);
|
|
2381
|
+
const out = {};
|
|
2382
|
+
for (const [code, name] of Object.entries(names)) {
|
|
2383
|
+
const abbr = abbrs[Number(code)];
|
|
2384
|
+
if (abbr !== void 0) out[Number(code)] = { name, abbr };
|
|
2385
|
+
}
|
|
2386
|
+
return out;
|
|
2387
|
+
}
|
|
2388
|
+
var COLOUR_HEX = {
|
|
2389
|
+
1: "#ffffff",
|
|
2390
|
+
2: "#333333",
|
|
2391
|
+
3: "#ef4444",
|
|
2392
|
+
4: "#22c55e",
|
|
2393
|
+
5: "#3b82f6",
|
|
2394
|
+
6: "#eab308",
|
|
2395
|
+
7: "#9ca3af",
|
|
2396
|
+
8: "#92400e",
|
|
2397
|
+
9: "#f59e0b",
|
|
2398
|
+
10: "#8b5cf6",
|
|
2399
|
+
11: "#f97316",
|
|
2400
|
+
12: "#d946ef",
|
|
2401
|
+
13: "#f472b6"
|
|
2402
|
+
};
|
|
2403
|
+
var LITCHR_TABLE = nameAbbrTable("LITCHR");
|
|
2404
|
+
var COLOUR_TABLE = Object.fromEntries(
|
|
2405
|
+
Object.values(COLOUR_CODES).filter((c) => c.abbr !== void 0 && COLOUR_HEX[c.code] !== void 0).map((c) => [c.code, { name: c.name, abbr: c.abbr, hex: COLOUR_HEX[c.code] }])
|
|
2406
|
+
);
|
|
2407
|
+
var BOYSHP_TABLE = s57NameTable("BOYSHP");
|
|
2408
|
+
var CATLAM_TABLE = s57NameTable("CATLAM");
|
|
2409
|
+
var CATCAM_TABLE = s57NameTable("CATCAM");
|
|
2410
|
+
var CATLMK_TABLE = s57NameTable("CATLMK");
|
|
2411
|
+
var CATFOG_TABLE = s57NameTable("CATFOG");
|
|
2412
|
+
var CATFOG_ABBR_TABLE = s57AbbrTable("CATFOG");
|
|
2413
|
+
var CATFOG_NAUTOPHONE = Object.values(CATFOG_CODES).find(
|
|
2414
|
+
(c) => c.name.toLowerCase() === "nautophone"
|
|
2415
|
+
)?.code;
|
|
2416
|
+
var CATLIT_TABLE = s57NameTable("CATLIT");
|
|
2417
|
+
var TOPSHP_TABLE = s57NameTable("TOPSHP");
|
|
2418
|
+
var STATUS_TABLE = s57NameTable("STATUS");
|
|
2419
|
+
var MARSYS_TABLE = s57NameTable("MARSYS");
|
|
2420
|
+
var LAYER_TYPE_MAP = {
|
|
2421
|
+
BOYLAT: "Lateral Buoy",
|
|
2422
|
+
BOYCAR: "Cardinal Buoy",
|
|
2423
|
+
BOYSPP: "Special Purpose Buoy",
|
|
2424
|
+
BOYISD: "Isolated Danger Buoy",
|
|
2425
|
+
BOYSAW: "Safe Water Buoy",
|
|
2426
|
+
BCNLAT: "Lateral Beacon",
|
|
2427
|
+
BCNCAR: "Cardinal Beacon",
|
|
2428
|
+
BCNSPP: "Special Purpose Beacon",
|
|
2429
|
+
BCNISD: "Isolated Danger Beacon",
|
|
2430
|
+
BCNSAW: "Safe Water Beacon",
|
|
2431
|
+
LIGHTS: "Light",
|
|
2432
|
+
DAYMAR: "Daymark",
|
|
2433
|
+
FOGSIG: "Fog Signal",
|
|
2434
|
+
RTPBCN: "Radar Transponder Beacon",
|
|
2435
|
+
RDOSTA: "Radio/Radar Station",
|
|
2436
|
+
TOPMAR: "Topmark",
|
|
2437
|
+
MORFAC: "Mooring Facility",
|
|
2438
|
+
LNDMRK: "Landmark",
|
|
2439
|
+
SISTAT: "Signal Station",
|
|
2440
|
+
PILBOP: "Pilot Boarding Place",
|
|
2441
|
+
HRBFAC: "Harbour Facility",
|
|
2442
|
+
SMCFAC: "Small Craft Facility",
|
|
2443
|
+
CRANES: "Crane",
|
|
2444
|
+
OFSPLF: "Offshore Platform",
|
|
2445
|
+
ACHBRT: "Anchor Berth",
|
|
2446
|
+
DISMAR: "Distance Mark",
|
|
2447
|
+
LITVES: "Light Vessel",
|
|
2448
|
+
LITFLT: "Light Float"
|
|
2449
|
+
};
|
|
2450
|
+
var NAV_AID_QUERY_LAYERS = [
|
|
2451
|
+
"enc-BOYLAT",
|
|
2452
|
+
"enc-BOYCAR",
|
|
2453
|
+
"enc-BOYSPP",
|
|
2454
|
+
"enc-BOYISD",
|
|
2455
|
+
"enc-BOYSAW",
|
|
2456
|
+
"enc-BCNLAT",
|
|
2457
|
+
"enc-BCNCAR",
|
|
2458
|
+
"enc-BCNSPP",
|
|
2459
|
+
"enc-BCNISD",
|
|
2460
|
+
"enc-BCNSAW",
|
|
2461
|
+
"enc-LIGHTS",
|
|
2462
|
+
"enc-LIGHTS-point",
|
|
2463
|
+
"enc-DAYMAR",
|
|
2464
|
+
"enc-FOGSIG",
|
|
2465
|
+
"enc-RTPBCN",
|
|
2466
|
+
"enc-RDOSTA",
|
|
2467
|
+
"enc-TOPMAR",
|
|
2468
|
+
"enc-MORFAC",
|
|
2469
|
+
"enc-MORFAC-point",
|
|
2470
|
+
"enc-LNDMRK",
|
|
2471
|
+
"enc-LNDMRK-point",
|
|
2472
|
+
"enc-SISTAT",
|
|
2473
|
+
"enc-SISTAT-point",
|
|
2474
|
+
"enc-PILBOP",
|
|
2475
|
+
"enc-PILBOP-point",
|
|
2476
|
+
"enc-HRBFAC",
|
|
2477
|
+
"enc-SMCFAC",
|
|
2478
|
+
"enc-CRANES",
|
|
2479
|
+
"enc-CRANES-point",
|
|
2480
|
+
"enc-OFSPLF",
|
|
2143
2481
|
"enc-OFSPLF-point",
|
|
2144
2482
|
"enc-ACHBRT",
|
|
2145
2483
|
"enc-DISMAR",
|
|
@@ -2942,14 +3280,8 @@ function decodeNavAidProps(layerName, props, coords) {
|
|
|
2942
3280
|
const valnmr = props.VALNMR != null ? Number(props.VALNMR) : null;
|
|
2943
3281
|
const nominalRange = valnmr != null && !isNaN(valnmr) && valnmr > 0 ? valnmr : null;
|
|
2944
3282
|
if (isSectorLight) {
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
sectorEnd: sectr2,
|
|
2948
|
-
colour: colourEntry?.name ?? "White",
|
|
2949
|
-
colourHex: colourEntry?.hex ?? "#ffffff",
|
|
2950
|
-
notation,
|
|
2951
|
-
nominalRange
|
|
2952
|
-
});
|
|
3283
|
+
const sec = sectorFromProps(props, sectr1, sectr2, "DAY", false);
|
|
3284
|
+
if (sec) sectors.push({ ...sec, notation });
|
|
2953
3285
|
}
|
|
2954
3286
|
return {
|
|
2955
3287
|
name,
|
|
@@ -2967,6 +3299,7 @@ function decodeNavAidProps(layerName, props, coords) {
|
|
|
2967
3299
|
lightCharacterFull: litchrEntry?.name ?? null,
|
|
2968
3300
|
period: sigper,
|
|
2969
3301
|
group,
|
|
3302
|
+
sigseq: props.SIGSEQ != null && String(props.SIGSEQ).trim() !== "" ? String(props.SIGSEQ).trim() : null,
|
|
2970
3303
|
fogSignal: fogSignal2,
|
|
2971
3304
|
fogSignalAbbr,
|
|
2972
3305
|
radarReflector,
|
|
@@ -2997,6 +3330,18 @@ function buildNotation(litchrAbbr, colourAbbrs, sigper, group) {
|
|
|
2997
3330
|
function buildFlashPattern(decoded) {
|
|
2998
3331
|
const litchr = decoded.lightCharacter;
|
|
2999
3332
|
if (!litchr) return null;
|
|
3333
|
+
const charted = parseSigseqTerms(decoded.sigseq, decoded.period);
|
|
3334
|
+
if (!("refusal" in charted)) {
|
|
3335
|
+
const colours2 = decoded.colourHexList.length > 0 ? decoded.colourHexList : [decoded.lightColourHex];
|
|
3336
|
+
let onIndex = 0;
|
|
3337
|
+
const chartedPhases = charted.phases.map((p) => {
|
|
3338
|
+
if (!p.isOn) return { on: false, duration: p.seconds * 1e3 };
|
|
3339
|
+
const color = colours2[onIndex % colours2.length];
|
|
3340
|
+
onIndex += 1;
|
|
3341
|
+
return { on: true, duration: p.seconds * 1e3, color };
|
|
3342
|
+
});
|
|
3343
|
+
return { phases: chartedPhases, totalCycle: charted.periodSeconds * 1e3, notation: decoded.notation, timing: "charted" };
|
|
3344
|
+
}
|
|
3000
3345
|
const period = decoded.period && decoded.period > 0 ? decoded.period * 1e3 : 6e3;
|
|
3001
3346
|
const groups = decoded.group ?? [1];
|
|
3002
3347
|
const totalFlashes = groups.reduce((a, b) => a + b, 0);
|
|
@@ -3165,7 +3510,7 @@ function buildFlashPattern(decoded) {
|
|
|
3165
3510
|
}
|
|
3166
3511
|
}
|
|
3167
3512
|
const totalCycle = phases.reduce((sum, p) => sum + p.duration, 0);
|
|
3168
|
-
return { phases, totalCycle, notation };
|
|
3513
|
+
return { timing: "invented", phases, totalCycle, notation };
|
|
3169
3514
|
}
|
|
3170
3515
|
function uncoveredSectorArcs(sectors) {
|
|
3171
3516
|
const norm = (d) => (d % 360 + 360) % 360;
|
|
@@ -3204,7 +3549,70 @@ function uncoveredSectorArcs(sectors) {
|
|
|
3204
3549
|
}
|
|
3205
3550
|
return gaps.filter(([a, b]) => Math.abs(b - a) >= 0.05 || b < a).map(([a, b]) => ({ start: norm(a), end: norm(b) }));
|
|
3206
3551
|
}
|
|
3207
|
-
function
|
|
3552
|
+
function parseCodes(val) {
|
|
3553
|
+
if (val == null) return [];
|
|
3554
|
+
const str = String(val).trim();
|
|
3555
|
+
let parts;
|
|
3556
|
+
if (str.startsWith("[")) {
|
|
3557
|
+
try {
|
|
3558
|
+
const arr = JSON.parse(str);
|
|
3559
|
+
parts = Array.isArray(arr) ? arr.map(String) : [];
|
|
3560
|
+
} catch {
|
|
3561
|
+
parts = str.split(/[,;]/);
|
|
3562
|
+
}
|
|
3563
|
+
} else {
|
|
3564
|
+
parts = str.split(/[,;]/);
|
|
3565
|
+
}
|
|
3566
|
+
return parts.map((p) => Number(p.trim())).filter((n) => !isNaN(n));
|
|
3567
|
+
}
|
|
3568
|
+
var OBSCURED_LITVIS = /* @__PURE__ */ new Set([3, 7, 8]);
|
|
3569
|
+
function sectorArcColourToken(colourCodes, obscured) {
|
|
3570
|
+
if (obscured) return "CHBRN";
|
|
3571
|
+
const has = (c) => colourCodes.includes(c);
|
|
3572
|
+
if (colourCodes.length === 1) {
|
|
3573
|
+
if (has(3)) return "LITRD";
|
|
3574
|
+
if (has(4)) return "LITGN";
|
|
3575
|
+
if (has(1) || has(6) || has(11)) return "LITYW";
|
|
3576
|
+
return "CHMGD";
|
|
3577
|
+
}
|
|
3578
|
+
if (colourCodes.length === 2) {
|
|
3579
|
+
if (has(1) && has(3)) return "LITRD";
|
|
3580
|
+
if (has(1) && has(4)) return "LITGN";
|
|
3581
|
+
return "CHMGD";
|
|
3582
|
+
}
|
|
3583
|
+
return "CHMGD";
|
|
3584
|
+
}
|
|
3585
|
+
function sectorFromProps(props, s1, s2, ct, allRound) {
|
|
3586
|
+
const colourCodes = parseCodes(props.COLOUR);
|
|
3587
|
+
const entries = colourCodes.map((c) => COLOUR_TABLE[c]).filter((e) => e != null);
|
|
3588
|
+
if (allRound && entries.length === 0) return null;
|
|
3589
|
+
const obscured = parseCodes(props.LITVIS).some((v) => OBSCURED_LITVIS.has(v));
|
|
3590
|
+
const litchrCode = parseCode(props.LITCHR);
|
|
3591
|
+
const le = litchrCode ? LITCHR_TABLE[litchrCode] : null;
|
|
3592
|
+
const sigper = props.SIGPER != null ? Number(props.SIGPER) : null;
|
|
3593
|
+
const group = parseGroup(props.SIGGRP);
|
|
3594
|
+
const base = buildNotation(le?.abbr ?? null, entries.length > 0 ? [entries.map((e) => e.abbr).join("")] : [], sigper, group);
|
|
3595
|
+
const notation = obscured && base ? `${base} (obsc)` : base;
|
|
3596
|
+
const valnmr = props.VALNMR != null ? Number(props.VALNMR) : null;
|
|
3597
|
+
const nominalRange = valnmr != null && !isNaN(valnmr) && valnmr > 0 ? valnmr : null;
|
|
3598
|
+
const arcColourToken = sectorArcColourToken(colourCodes, obscured);
|
|
3599
|
+
return {
|
|
3600
|
+
sectorStart: s1,
|
|
3601
|
+
sectorEnd: s2,
|
|
3602
|
+
colour: entries[0]?.name ?? "White",
|
|
3603
|
+
colourHex: entries[0]?.hex ?? "#ffffff",
|
|
3604
|
+
notation,
|
|
3605
|
+
nominalRange,
|
|
3606
|
+
colourCodes,
|
|
3607
|
+
arcColourToken,
|
|
3608
|
+
arcColourHex: resolveColor(arcColourToken, ct),
|
|
3609
|
+
obscured,
|
|
3610
|
+
sigseq: props.SIGSEQ != null && String(props.SIGSEQ).trim() !== "" ? String(props.SIGSEQ).trim() : null,
|
|
3611
|
+
sigper: sigper != null && !isNaN(sigper) && sigper > 0 ? sigper : null,
|
|
3612
|
+
...allRound ? { allRound: true } : {}
|
|
3613
|
+
};
|
|
3614
|
+
}
|
|
3615
|
+
function buildSectorsFromFeatures(features, ct = "DAY") {
|
|
3208
3616
|
const sectors = [];
|
|
3209
3617
|
const allRound = [];
|
|
3210
3618
|
for (const f of features) {
|
|
@@ -3215,47 +3623,15 @@ function buildSectorsFromFeatures(features) {
|
|
|
3215
3623
|
if (props.SECTR1 == null && props.SECTR2 == null && parseCode(props.COLOUR) != null) allRound.push(props);
|
|
3216
3624
|
continue;
|
|
3217
3625
|
}
|
|
3218
|
-
const
|
|
3219
|
-
|
|
3220
|
-
const litchrCode = parseCode(props.LITCHR);
|
|
3221
|
-
const le = litchrCode ? LITCHR_TABLE[litchrCode] : null;
|
|
3222
|
-
const sigper = props.SIGPER != null ? Number(props.SIGPER) : null;
|
|
3223
|
-
const group = parseGroup(props.SIGGRP);
|
|
3224
|
-
const notation = buildNotation(le?.abbr ?? null, ce ? [ce.abbr] : [], sigper, group);
|
|
3225
|
-
const valnmr = props.VALNMR != null ? Number(props.VALNMR) : null;
|
|
3226
|
-
const nominalRange = valnmr != null && !isNaN(valnmr) && valnmr > 0 ? valnmr : null;
|
|
3227
|
-
sectors.push({
|
|
3228
|
-
sectorStart: s1,
|
|
3229
|
-
sectorEnd: s2,
|
|
3230
|
-
colour: ce?.name ?? "White",
|
|
3231
|
-
colourHex: ce?.hex ?? "#ffffff",
|
|
3232
|
-
notation,
|
|
3233
|
-
nominalRange
|
|
3234
|
-
});
|
|
3626
|
+
const sec = sectorFromProps(props, s1, s2, ct, false);
|
|
3627
|
+
if (sec) sectors.push(sec);
|
|
3235
3628
|
}
|
|
3236
3629
|
if (sectors.length > 0 && allRound.length > 0) {
|
|
3237
3630
|
const gaps = uncoveredSectorArcs(sectors);
|
|
3238
3631
|
for (const props of allRound) {
|
|
3239
|
-
const colourCode = parseCode(props.COLOUR);
|
|
3240
|
-
const ce = colourCode ? COLOUR_TABLE[colourCode] : null;
|
|
3241
|
-
if (!ce) continue;
|
|
3242
|
-
const litchrCode = parseCode(props.LITCHR);
|
|
3243
|
-
const le = litchrCode ? LITCHR_TABLE[litchrCode] : null;
|
|
3244
|
-
const sigper = props.SIGPER != null ? Number(props.SIGPER) : null;
|
|
3245
|
-
const group = parseGroup(props.SIGGRP);
|
|
3246
|
-
const notation = buildNotation(le?.abbr ?? null, [ce.abbr], sigper, group);
|
|
3247
|
-
const valnmr = props.VALNMR != null ? Number(props.VALNMR) : null;
|
|
3248
|
-
const nominalRange = valnmr != null && !isNaN(valnmr) && valnmr > 0 ? valnmr : null;
|
|
3249
3632
|
for (const gap of gaps) {
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
sectorEnd: gap.end,
|
|
3253
|
-
colour: ce.name,
|
|
3254
|
-
colourHex: ce.hex,
|
|
3255
|
-
notation,
|
|
3256
|
-
nominalRange,
|
|
3257
|
-
allRound: true
|
|
3258
|
-
});
|
|
3633
|
+
const sec = sectorFromProps(props, gap.start, gap.end, ct, true);
|
|
3634
|
+
if (sec) sectors.push(sec);
|
|
3259
3635
|
}
|
|
3260
3636
|
}
|
|
3261
3637
|
}
|
|
@@ -3305,12 +3681,16 @@ function buildSectorArcGeoJSON(center, startBearing, endBearing, radiusNm = 0.5,
|
|
|
3305
3681
|
}
|
|
3306
3682
|
function buildSectorArcsFC(center, sectors, radiusNm = 0.4, innerRadiusFrac = 0.92) {
|
|
3307
3683
|
const innerNm = innerRadiusFrac > 0 ? radiusNm * innerRadiusFrac : 0;
|
|
3308
|
-
const
|
|
3684
|
+
const drawn = sectors.filter((sec) => !sec.allRound);
|
|
3685
|
+
const features = drawn.map((sec, i) => {
|
|
3309
3686
|
const arc = buildSectorArcGeoJSON(center, sec.sectorStart, sec.sectorEnd, radiusNm, innerNm);
|
|
3310
3687
|
arc.properties = {
|
|
3311
3688
|
sectorIndex: i,
|
|
3312
3689
|
colour: sec.colour,
|
|
3313
3690
|
colourHex: sec.colourHex,
|
|
3691
|
+
arcColourHex: sec.arcColourHex,
|
|
3692
|
+
arcColourToken: sec.arcColourToken,
|
|
3693
|
+
obscured: sec.obscured,
|
|
3314
3694
|
notation: sec.notation,
|
|
3315
3695
|
sectorStart: sec.sectorStart,
|
|
3316
3696
|
sectorEnd: sec.sectorEnd,
|
|
@@ -3323,7 +3703,7 @@ function buildSectorArcsFC(center, sectors, radiusNm = 0.4, innerRadiusFrac = 0.
|
|
|
3323
3703
|
const seenBearings = /* @__PURE__ */ new Set();
|
|
3324
3704
|
const TICK_OUTER_OVERHANG = 1.05;
|
|
3325
3705
|
const TICK_INNER_OVERHANG = 0.93;
|
|
3326
|
-
for (const sec of
|
|
3706
|
+
for (const sec of drawn) {
|
|
3327
3707
|
for (const bearing of [sec.sectorStart, sec.sectorEnd]) {
|
|
3328
3708
|
const rad = sectorLimitToArcBearing(bearing) * Math.PI / 180;
|
|
3329
3709
|
const radiusDeg = radiusNm / 60;
|
|
@@ -4193,10 +4573,10 @@ function ensureStyles(doc) {
|
|
|
4193
4573
|
doc.head.appendChild(style);
|
|
4194
4574
|
styleInjected = true;
|
|
4195
4575
|
}
|
|
4196
|
-
function el(doc, tag, className,
|
|
4576
|
+
function el(doc, tag, className, text2) {
|
|
4197
4577
|
const node = doc.createElement(tag);
|
|
4198
4578
|
node.className = className;
|
|
4199
|
-
if (
|
|
4579
|
+
if (text2 != null) node.textContent = text2;
|
|
4200
4580
|
return node;
|
|
4201
4581
|
}
|
|
4202
4582
|
function animateDot(dot, pattern) {
|
|
@@ -4356,466 +4736,209 @@ ${notice.dataCurrency}`;
|
|
|
4356
4736
|
if (position === "static" || position === "") {
|
|
4357
4737
|
container.style.position = "relative";
|
|
4358
4738
|
}
|
|
4359
|
-
container.appendChild(root);
|
|
4360
|
-
this.root = root;
|
|
4361
|
-
this.lngLat = lngLat;
|
|
4362
|
-
this.place();
|
|
4363
|
-
this.map.on("move", this.reposition);
|
|
4364
|
-
}
|
|
4365
|
-
place() {
|
|
4366
|
-
if (!this.root || !this.lngLat) return;
|
|
4367
|
-
const { x, y } = this.map.project(this.lngLat);
|
|
4368
|
-
this.root.style.left = `${x}px`;
|
|
4369
|
-
this.root.style.top = `${y}px`;
|
|
4370
|
-
}
|
|
4371
|
-
close() {
|
|
4372
|
-
if (this.stopAnimation) {
|
|
4373
|
-
this.stopAnimation();
|
|
4374
|
-
this.stopAnimation = null;
|
|
4375
|
-
}
|
|
4376
|
-
if (this.root) {
|
|
4377
|
-
this.map.off("move", this.reposition);
|
|
4378
|
-
this.root.remove();
|
|
4379
|
-
this.root = null;
|
|
4380
|
-
}
|
|
4381
|
-
this.lngLat = null;
|
|
4382
|
-
}
|
|
4383
|
-
};
|
|
4384
|
-
|
|
4385
|
-
// src/interact/index.ts
|
|
4386
|
-
var InteractClient = class {
|
|
4387
|
-
constructor(http) {
|
|
4388
|
-
this.http = http;
|
|
4389
|
-
this.entitlementClient = new EntitlementClient(http);
|
|
4390
|
-
}
|
|
4391
|
-
entitlementClient;
|
|
4392
|
-
/**
|
|
4393
|
-
* Check the CHRTinteract entitlement for this API key (server-side, plan
|
|
4394
|
-
* derived, cached per client). Throws `InteractError` with code
|
|
4395
|
-
* `interact_not_enabled` for Free-plan teams.
|
|
4396
|
-
*/
|
|
4397
|
-
async entitlement() {
|
|
4398
|
-
return this.entitlementClient.check();
|
|
4399
|
-
}
|
|
4400
|
-
/**
|
|
4401
|
-
* Decode a chart feature (from `queryRenderedFeatures`, the features API,
|
|
4402
|
-
* or plain `{ layer, properties }`) into a typed {@link InspectResult}.
|
|
4403
|
-
*
|
|
4404
|
-
* The first call performs the server-side entitlement check. With
|
|
4405
|
-
* `{ enrich: true }` the result is upgraded with full S-57 properties and
|
|
4406
|
-
* source-cell provenance (edition / update / issue date) via the features
|
|
4407
|
-
* API — metered against the key's `featureCalls` quota.
|
|
4408
|
-
*
|
|
4409
|
-
* @example
|
|
4410
|
-
* const result = await chrt.interact.inspect(feature);
|
|
4411
|
-
* if (result.kind === "navaid") console.log(result.characteristic); // "Fl R 4s"
|
|
4412
|
-
*/
|
|
4413
|
-
async inspect(feature, opts) {
|
|
4414
|
-
await this.entitlementClient.check();
|
|
4415
|
-
const result = buildInspectResult(feature, opts);
|
|
4416
|
-
if (opts?.enrich) {
|
|
4417
|
-
return enrichResult(this.http, result, opts.signal);
|
|
4418
|
-
}
|
|
4419
|
-
return result;
|
|
4420
|
-
}
|
|
4421
|
-
/**
|
|
4422
|
-
* Collect EVERY inspectable feature under a point on a CHRT map —
|
|
4423
|
-
* deduplicated across tile boundaries by S-57 identity (`LNAM`) and
|
|
4424
|
-
* ordered by class priority (point targets > lines > areas, safety
|
|
4425
|
-
* classes first within each band). Area features never preempt the buoy
|
|
4426
|
-
* on top of them but always stay selectable: a click on open water
|
|
4427
|
-
* resolves to its DEPARE.
|
|
4428
|
-
*
|
|
4429
|
-
* `point` is a screen pixel point (`e.point` from a map click) or
|
|
4430
|
-
* `[lng, lat]`. Point/line targets are picked up within
|
|
4431
|
-
* `opts.tolerance` px (default 8); polygon candidates require the exact
|
|
4432
|
-
* point to fall inside them.
|
|
4433
|
-
*
|
|
4434
|
-
* The first call performs the server-side entitlement check.
|
|
4435
|
-
*
|
|
4436
|
-
* @example
|
|
4437
|
-
* const { candidates, select } = await chrt.interact.inspectAll(map, e.point);
|
|
4438
|
-
* const result = await select(candidates[0]); // full decode (+ merge)
|
|
4439
|
-
*/
|
|
4440
|
-
async inspectAll(map, point, opts = {}) {
|
|
4441
|
-
if (!map || typeof map.queryRenderedFeatures !== "function") {
|
|
4442
|
-
throw new InteractError(
|
|
4443
|
-
"unexpected_response",
|
|
4444
|
-
"inspectAll(): expected a MapLibre / Mapbox GL map instance"
|
|
4445
|
-
);
|
|
4446
|
-
}
|
|
4447
|
-
await this.entitlementClient.check();
|
|
4448
|
-
const screen = Array.isArray(point) ? map.project([point[0], point[1]]) : point;
|
|
4449
|
-
const lngLat = Array.isArray(point) ? { lng: point[0], lat: point[1] } : map.unproject?.([point.x, point.y]) ?? null;
|
|
4450
|
-
const at = { point: screen, lngLat };
|
|
4451
|
-
const candidates = collectClickCandidates(map, screen, opts);
|
|
4452
|
-
const http = this.http;
|
|
4453
|
-
return {
|
|
4454
|
-
candidates,
|
|
4455
|
-
select: async (candidate, selectOpts) => {
|
|
4456
|
-
const result = decodeCandidate(map, candidate, at, opts);
|
|
4457
|
-
if (selectOpts?.enrich) {
|
|
4458
|
-
return enrichResult(http, result, selectOpts.signal);
|
|
4459
|
-
}
|
|
4460
|
-
return result;
|
|
4461
|
-
}
|
|
4462
|
-
};
|
|
4463
|
-
}
|
|
4464
|
-
/**
|
|
4465
|
-
* Attach click-to-inspect to a MapLibre / Mapbox GL map rendering CHRT
|
|
4466
|
-
* tiles. Resolves once the server-side entitlement check passes; rejects
|
|
4467
|
-
* with `InteractError` (`interact_not_enabled` on Free plans) otherwise —
|
|
4468
|
-
* no listeners are wired on denial.
|
|
4469
|
-
*
|
|
4470
|
-
* Headless-first: pass `onInspect` to receive every decoded
|
|
4471
|
-
* {@link InspectResult} and render your own UI. Without callbacks, the
|
|
4472
|
-
* built-in minimal popup is shown (opt back in explicitly with
|
|
4473
|
-
* `popup: true` to have both).
|
|
4474
|
-
*
|
|
4475
|
-
* A click that hits SEVERAL features (deduplicated by S-57 identity)
|
|
4476
|
-
* disambiguates instead of silently taking the topmost:
|
|
4477
|
-
* - `onCandidates` provided → it receives the priority-ordered list;
|
|
4478
|
-
* resolve a choice with `ctx.select(candidate)`.
|
|
4479
|
-
* - built-in popup → shows a candidate list; picking a row opens the
|
|
4480
|
-
* result card.
|
|
4481
|
-
* - `onInspect` only → the top candidate is inspected immediately, and
|
|
4482
|
-
* clicking the SAME spot again cycles to the next candidate — so an
|
|
4483
|
-
* area feature under a buoy is reachable with a second click.
|
|
4484
|
-
*
|
|
4485
|
-
* @example
|
|
4486
|
-
* const handle = await chrt.interact.attach(map, {
|
|
4487
|
-
* onInspect: (result) => console.log(result.title, result.characteristic),
|
|
4488
|
-
* });
|
|
4489
|
-
* // later: handle.detach();
|
|
4490
|
-
*/
|
|
4491
|
-
async attach(map, opts = {}) {
|
|
4492
|
-
if (!map || typeof map.queryRenderedFeatures !== "function" || typeof map.on !== "function") {
|
|
4493
|
-
throw new InteractError(
|
|
4494
|
-
"unexpected_response",
|
|
4495
|
-
"attach(): expected a MapLibre / Mapbox GL map instance"
|
|
4496
|
-
);
|
|
4497
|
-
}
|
|
4498
|
-
await this.entitlementClient.check();
|
|
4499
|
-
const showPopup = opts.popup ?? (!opts.onInspect && !opts.onCandidates);
|
|
4500
|
-
const popup = showPopup ? new InteractPopup(map) : null;
|
|
4501
|
-
const http = this.http;
|
|
4502
|
-
let cycle = null;
|
|
4503
|
-
const samePoint = (a, b) => {
|
|
4504
|
-
const pad = opts.tolerance ?? DEFAULT_CLICK_TOLERANCE;
|
|
4505
|
-
return Math.abs(a.x - b.x) <= pad && Math.abs(a.y - b.y) <= pad;
|
|
4506
|
-
};
|
|
4507
|
-
const select = (candidate, e) => {
|
|
4508
|
-
const result = decodeCandidate(map, candidate, e, opts);
|
|
4509
|
-
const finish = (r) => {
|
|
4510
|
-
popup?.show(r, r.coordinates ?? [e.lngLat.lng, e.lngLat.lat]);
|
|
4511
|
-
opts.onInspect?.(r, { lngLat: e.lngLat, point: e.point, feature: candidate.feature });
|
|
4512
|
-
return r;
|
|
4513
|
-
};
|
|
4514
|
-
if (opts.enrich) {
|
|
4515
|
-
return enrichResult(http, result).then(finish).catch(() => finish(result));
|
|
4516
|
-
}
|
|
4517
|
-
return Promise.resolve(finish(result));
|
|
4518
|
-
};
|
|
4519
|
-
const clickHandler = (e) => {
|
|
4520
|
-
const candidates = collectClickCandidates(map, e.point, opts);
|
|
4521
|
-
if (candidates.length === 0) {
|
|
4522
|
-
cycle = null;
|
|
4523
|
-
popup?.close();
|
|
4524
|
-
opts.onMiss?.({ lngLat: e.lngLat });
|
|
4525
|
-
return;
|
|
4526
|
-
}
|
|
4527
|
-
if (candidates.length === 1) {
|
|
4528
|
-
cycle = null;
|
|
4529
|
-
void select(candidates[0], e);
|
|
4530
|
-
return;
|
|
4531
|
-
}
|
|
4532
|
-
if (opts.onCandidates || popup) {
|
|
4533
|
-
cycle = null;
|
|
4534
|
-
if (popup) {
|
|
4535
|
-
popup.showList(candidates, [e.lngLat.lng, e.lngLat.lat], (c) => void select(c, e));
|
|
4536
|
-
}
|
|
4537
|
-
opts.onCandidates?.(candidates, {
|
|
4538
|
-
lngLat: e.lngLat,
|
|
4539
|
-
point: e.point,
|
|
4540
|
-
select: (c) => select(c, e)
|
|
4541
|
-
});
|
|
4542
|
-
return;
|
|
4543
|
-
}
|
|
4544
|
-
const key = candidates.map((c) => c.identity ?? c.layerId).join("/");
|
|
4545
|
-
if (cycle && cycle.key === key && samePoint(cycle.point, e.point)) {
|
|
4546
|
-
cycle.index = (cycle.index + 1) % candidates.length;
|
|
4547
|
-
cycle.point = e.point;
|
|
4548
|
-
} else {
|
|
4549
|
-
cycle = { key, point: e.point, index: 0 };
|
|
4550
|
-
}
|
|
4551
|
-
void select(candidates[cycle.index], e);
|
|
4552
|
-
};
|
|
4553
|
-
map.on("click", clickHandler);
|
|
4554
|
-
return {
|
|
4555
|
-
detach: () => {
|
|
4556
|
-
map.off("click", clickHandler);
|
|
4557
|
-
popup?.close();
|
|
4558
|
-
}
|
|
4559
|
-
};
|
|
4560
|
-
}
|
|
4561
|
-
};
|
|
4562
|
-
|
|
4563
|
-
// src/atlas/s52-colors.ts
|
|
4564
|
-
var S52_DAY = {
|
|
4565
|
-
// Depth
|
|
4566
|
-
NODTA: "#a4a4a4",
|
|
4567
|
-
DEPIT: "#009678",
|
|
4568
|
-
DEPVS: "#7fbfdf",
|
|
4569
|
-
DEPMS: "#a7d4e8",
|
|
4570
|
-
DEPMD: "#cae7f3",
|
|
4571
|
-
DEPDW: "#ecf7fc",
|
|
4572
|
-
// Land / topography
|
|
4573
|
-
LANDA: "#e6d1a1",
|
|
4574
|
-
LANDF: "#c8b078",
|
|
4575
|
-
CHBRN: "#ab7948",
|
|
4576
|
-
CHGRF: "#878787",
|
|
4577
|
-
CHGRD: "#555555",
|
|
4578
|
-
// Chart symbology
|
|
4579
|
-
CHBLK: "#000000",
|
|
4580
|
-
CSTLN: "#000000",
|
|
4581
|
-
OUTLW: "#000000",
|
|
4582
|
-
BKAJ1: "#4d4d4d",
|
|
4583
|
-
// Navigation / traffic
|
|
4584
|
-
TRFCD: "#d200d2",
|
|
4585
|
-
TRFCF: "#e680e6",
|
|
4586
|
-
RESBL: "#0064b4",
|
|
4587
|
-
APTS1: "#d200d2",
|
|
4588
|
-
// Danger / hazard
|
|
4589
|
-
DNGHL: "#ff0000",
|
|
4590
|
-
ISHDG: "#cc0000",
|
|
4591
|
-
// Lights
|
|
4592
|
-
LITRD: "#ee0000",
|
|
4593
|
-
LITGN: "#00c800",
|
|
4594
|
-
LITYW: "#fddc00",
|
|
4595
|
-
LITWT: "#ffffff",
|
|
4596
|
-
LITBL: "#0064ff",
|
|
4597
|
-
LITOR: "#ff8c00",
|
|
4598
|
-
// Buoys / marks
|
|
4599
|
-
RADHI: "#68e456",
|
|
4600
|
-
RADLO: "#3f8a34",
|
|
4601
|
-
PLRTE: "#dc4025",
|
|
4602
|
-
ARPAT: "#3fa56f",
|
|
4603
|
-
ISDNG: "#c545c3",
|
|
4604
|
-
// Chart colours
|
|
4605
|
-
CHRED: "#f15469",
|
|
4606
|
-
CHGRN: "#68e456",
|
|
4607
|
-
CHYLW: "#f4da48",
|
|
4608
|
-
CHMGD: "#c545c3",
|
|
4609
|
-
CHMGF: "#d3a6e9",
|
|
4610
|
-
CHWHT: "#d4eaee",
|
|
4611
|
-
CHCOR: "#eb7d36",
|
|
4612
|
-
// Soundings
|
|
4613
|
-
SNDG1: "#7d898c",
|
|
4614
|
-
SNDG2: "#070707",
|
|
4615
|
-
DEPSC: "#525a5c",
|
|
4616
|
-
DEPCN: "#555555",
|
|
4617
|
-
// Utility / infrastructure
|
|
4618
|
-
UINFD: "#070707",
|
|
4619
|
-
UINFF: "#7d898c",
|
|
4620
|
-
UINFR: "#f15469",
|
|
4621
|
-
UINFG: "#68e456",
|
|
4622
|
-
UINFO: "#eb7d36",
|
|
4623
|
-
UINFB: "#3a78f0",
|
|
4624
|
-
UINFM: "#c545c3",
|
|
4625
|
-
UIBCK: "#d4eaee",
|
|
4626
|
-
UIBDR: "#7d898c",
|
|
4627
|
-
UIAFD: "#73b6ef",
|
|
4628
|
-
UIAFF: "#c9b97a",
|
|
4629
|
-
PIPLN: "#55aa00",
|
|
4630
|
-
// Outlines
|
|
4631
|
-
OUTLL: "#c9b97a",
|
|
4632
|
-
// Miscellaneous
|
|
4633
|
-
SCLBR: "#eb7d36",
|
|
4634
|
-
NINFO: "#eb7d36",
|
|
4635
|
-
ADINF: "#b29f34",
|
|
4636
|
-
RESGR: "#7d898c",
|
|
4637
|
-
SHIPS: "#070707",
|
|
4638
|
-
PSTRK: "#070707",
|
|
4639
|
-
SYTRK: "#7d898c",
|
|
4640
|
-
APLRT: "#eb7d36",
|
|
4641
|
-
CURSR: "#eb7d36",
|
|
4642
|
-
BKAJ2: "#232728",
|
|
4643
|
-
RES01: "#a3b4b7",
|
|
4644
|
-
RES02: "#a3b4b7",
|
|
4645
|
-
RES03: "#a3b4b7"
|
|
4646
|
-
};
|
|
4647
|
-
var S52_DUSK = {
|
|
4648
|
-
NODTA: "#6b6b6b",
|
|
4649
|
-
DEPIT: "#006650",
|
|
4650
|
-
DEPVS: "#487085",
|
|
4651
|
-
DEPMS: "#608a9d",
|
|
4652
|
-
DEPMD: "#7ba6b8",
|
|
4653
|
-
DEPDW: "#95bfd0",
|
|
4654
|
-
LANDA: "#8a7d5e",
|
|
4655
|
-
LANDF: "#7a6a48",
|
|
4656
|
-
CHBRN: "#6e4e30",
|
|
4657
|
-
CHGRF: "#585858",
|
|
4658
|
-
CHGRD: "#3a3a3a",
|
|
4659
|
-
CHBLK: "#b0b0b0",
|
|
4660
|
-
CSTLN: "#b0b0b0",
|
|
4661
|
-
OUTLW: "#b0b0b0",
|
|
4662
|
-
BKAJ1: "#707070",
|
|
4663
|
-
TRFCD: "#a000a0",
|
|
4664
|
-
TRFCF: "#b060b0",
|
|
4665
|
-
RESBL: "#004a88",
|
|
4666
|
-
APTS1: "#a000a0",
|
|
4667
|
-
DNGHL: "#cc0000",
|
|
4668
|
-
ISHDG: "#990000",
|
|
4669
|
-
LITRD: "#bb0000",
|
|
4670
|
-
LITGN: "#009900",
|
|
4671
|
-
LITYW: "#c8b000",
|
|
4672
|
-
LITWT: "#d0d0d0",
|
|
4673
|
-
LITBL: "#004abb",
|
|
4674
|
-
LITOR: "#cc7000",
|
|
4675
|
-
RADHI: "#009940",
|
|
4676
|
-
RADLO: "#2d6330",
|
|
4677
|
-
PLRTE: "#a03020",
|
|
4678
|
-
ARPAT: "#306040",
|
|
4679
|
-
ISDNG: "#903090",
|
|
4680
|
-
CHRED: "#b05060",
|
|
4681
|
-
CHGRN: "#509040",
|
|
4682
|
-
CHYLW: "#b0a030",
|
|
4683
|
-
CHMGD: "#903090",
|
|
4684
|
-
CHMGF: "#905090",
|
|
4685
|
-
CHWHT: "#90a0a0",
|
|
4686
|
-
CHCOR: "#a06030",
|
|
4687
|
-
SNDG1: "#606060",
|
|
4688
|
-
SNDG2: "#b0b0b0",
|
|
4689
|
-
DEPSC: "#606060",
|
|
4690
|
-
DEPCN: "#2E2E2E",
|
|
4691
|
-
UINFD: "#b0b0b0",
|
|
4692
|
-
UINFF: "#606060",
|
|
4693
|
-
UINFR: "#b05060",
|
|
4694
|
-
UINFG: "#509040",
|
|
4695
|
-
UINFO: "#a06030",
|
|
4696
|
-
UINFB: "#304090",
|
|
4697
|
-
UINFM: "#903090",
|
|
4698
|
-
UIBCK: "#1a1a1a",
|
|
4699
|
-
UIBDR: "#606060",
|
|
4700
|
-
UIAFD: "#405060",
|
|
4701
|
-
UIAFF: "#605840",
|
|
4702
|
-
PIPLN: "#3d7a00",
|
|
4703
|
-
OUTLL: "#605840",
|
|
4704
|
-
SCLBR: "#a06030",
|
|
4705
|
-
NINFO: "#a06030",
|
|
4706
|
-
ADINF: "#807020",
|
|
4707
|
-
RESGR: "#606060",
|
|
4708
|
-
SHIPS: "#b0b0b0",
|
|
4709
|
-
PSTRK: "#b0b0b0",
|
|
4710
|
-
SYTRK: "#606060",
|
|
4711
|
-
APLRT: "#a06030",
|
|
4712
|
-
CURSR: "#cc0000",
|
|
4713
|
-
BKAJ2: "#252525",
|
|
4714
|
-
RES01: "#606060",
|
|
4715
|
-
RES02: "#606060",
|
|
4716
|
-
RES03: "#606060"
|
|
4717
|
-
};
|
|
4718
|
-
var S52_NIGHT = {
|
|
4719
|
-
NODTA: "#3a3a32",
|
|
4720
|
-
DEPIT: "#003828",
|
|
4721
|
-
DEPVS: "#356577",
|
|
4722
|
-
DEPMS: "#265066",
|
|
4723
|
-
DEPMD: "#102e44",
|
|
4724
|
-
DEPDW: "#051a28",
|
|
4725
|
-
LANDA: "#3a3020",
|
|
4726
|
-
LANDF: "#302818",
|
|
4727
|
-
CHBRN: "#382410",
|
|
4728
|
-
CHGRF: "#2a2a22",
|
|
4729
|
-
CHGRD: "#1e1e18",
|
|
4730
|
-
CHBLK: "#c8aa78",
|
|
4731
|
-
CSTLN: "#c8aa78",
|
|
4732
|
-
OUTLW: "#c8aa78",
|
|
4733
|
-
BKAJ1: "#504830",
|
|
4734
|
-
TRFCD: "#6e006e",
|
|
4735
|
-
TRFCF: "#803880",
|
|
4736
|
-
RESBL: "#002a50",
|
|
4737
|
-
APTS1: "#6e006e",
|
|
4738
|
-
DNGHL: "#a00000",
|
|
4739
|
-
ISHDG: "#700000",
|
|
4740
|
-
LITRD: "#880000",
|
|
4741
|
-
LITGN: "#006600",
|
|
4742
|
-
LITYW: "#887800",
|
|
4743
|
-
LITWT: "#a09878",
|
|
4744
|
-
LITBL: "#003088",
|
|
4745
|
-
LITOR: "#884800",
|
|
4746
|
-
RADHI: "#005528",
|
|
4747
|
-
RADLO: "#1a3a20",
|
|
4748
|
-
PLRTE: "#6e2018",
|
|
4749
|
-
ARPAT: "#1a3028",
|
|
4750
|
-
ISDNG: "#481848",
|
|
4751
|
-
CHRED: "#682030",
|
|
4752
|
-
CHGRN: "#285020",
|
|
4753
|
-
CHYLW: "#685828",
|
|
4754
|
-
CHMGD: "#481848",
|
|
4755
|
-
CHMGF: "#482848",
|
|
4756
|
-
CHWHT: "#484840",
|
|
4757
|
-
CHCOR: "#683818",
|
|
4758
|
-
SNDG1: "#383028",
|
|
4759
|
-
SNDG2: "#c8aa78",
|
|
4760
|
-
DEPSC: "#383028",
|
|
4761
|
-
// ⚠ DELIBERATE DEVIATION from the published S-52 NIGHT value (#1f2223),
|
|
4762
|
-
// recorded here because `docs/validation/s52-spec-gap-matrix.json` tracks
|
|
4763
|
-
// this token as `value-differs` and the CHRTstyles programme is bringing
|
|
4764
|
-
// colours TO spec — this one moves further away, on purpose.
|
|
4765
|
-
//
|
|
4766
|
-
// Spec NIGHT DEPCN is near-black, which measures ~1.1:1 against this
|
|
4767
|
-
// palette's own deep water (#051a28). S-52's NIGHT table assumes a
|
|
4768
|
-
// dark-adapted eye in a darkened wheelhouse; on a phone or a laptop at
|
|
4769
|
-
// anchor it is not dim, it is absent — reported as "can't see bottom
|
|
4770
|
-
// contours in night mode only". A contour nobody can see fails the
|
|
4771
|
-
// standard's intent while matching its number.
|
|
4772
|
-
//
|
|
4773
|
-
// The palette already makes this trade elsewhere and in the same
|
|
4774
|
-
// direction: NIGHT's CHBLK/CSTLN/OUTLW are #c8aa78, light warm linework on
|
|
4775
|
-
// dark water. This value is that family, dimmed to stay subordinate to the
|
|
4776
|
-
// coastline. Revisit with the CHRTstyles programme, which owns the
|
|
4777
|
-
// spec-vs-legibility call across all 63 tokens.
|
|
4778
|
-
DEPCN: "#A68E84",
|
|
4779
|
-
UINFD: "#c8aa78",
|
|
4780
|
-
UINFF: "#383028",
|
|
4781
|
-
UINFR: "#682030",
|
|
4782
|
-
UINFG: "#285020",
|
|
4783
|
-
UINFO: "#683818",
|
|
4784
|
-
UINFB: "#182848",
|
|
4785
|
-
UINFM: "#481848",
|
|
4786
|
-
UIBCK: "#0a0a08",
|
|
4787
|
-
UIBDR: "#383028",
|
|
4788
|
-
UIAFD: "#182838",
|
|
4789
|
-
UIAFF: "#302818",
|
|
4790
|
-
PIPLN: "#284800",
|
|
4791
|
-
OUTLL: "#302818",
|
|
4792
|
-
SCLBR: "#683818",
|
|
4793
|
-
NINFO: "#683818",
|
|
4794
|
-
ADINF: "#483818",
|
|
4795
|
-
RESGR: "#383028",
|
|
4796
|
-
SHIPS: "#c8aa78",
|
|
4797
|
-
PSTRK: "#c8aa78",
|
|
4798
|
-
SYTRK: "#484030",
|
|
4799
|
-
APLRT: "#683818",
|
|
4800
|
-
CURSR: "#a00000",
|
|
4801
|
-
BKAJ2: "#0a0a08",
|
|
4802
|
-
RES01: "#383028",
|
|
4803
|
-
RES02: "#383028",
|
|
4804
|
-
RES03: "#383028"
|
|
4739
|
+
container.appendChild(root);
|
|
4740
|
+
this.root = root;
|
|
4741
|
+
this.lngLat = lngLat;
|
|
4742
|
+
this.place();
|
|
4743
|
+
this.map.on("move", this.reposition);
|
|
4744
|
+
}
|
|
4745
|
+
place() {
|
|
4746
|
+
if (!this.root || !this.lngLat) return;
|
|
4747
|
+
const { x, y } = this.map.project(this.lngLat);
|
|
4748
|
+
this.root.style.left = `${x}px`;
|
|
4749
|
+
this.root.style.top = `${y}px`;
|
|
4750
|
+
}
|
|
4751
|
+
close() {
|
|
4752
|
+
if (this.stopAnimation) {
|
|
4753
|
+
this.stopAnimation();
|
|
4754
|
+
this.stopAnimation = null;
|
|
4755
|
+
}
|
|
4756
|
+
if (this.root) {
|
|
4757
|
+
this.map.off("move", this.reposition);
|
|
4758
|
+
this.root.remove();
|
|
4759
|
+
this.root = null;
|
|
4760
|
+
}
|
|
4761
|
+
this.lngLat = null;
|
|
4762
|
+
}
|
|
4805
4763
|
};
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4764
|
+
|
|
4765
|
+
// src/interact/index.ts
|
|
4766
|
+
var InteractClient = class {
|
|
4767
|
+
constructor(http) {
|
|
4768
|
+
this.http = http;
|
|
4769
|
+
this.entitlementClient = new EntitlementClient(http);
|
|
4770
|
+
}
|
|
4771
|
+
entitlementClient;
|
|
4772
|
+
/**
|
|
4773
|
+
* Check the CHRTinteract entitlement for this API key (server-side, plan
|
|
4774
|
+
* derived, cached per client). Throws `InteractError` with code
|
|
4775
|
+
* `interact_not_enabled` for Free-plan teams.
|
|
4776
|
+
*/
|
|
4777
|
+
async entitlement() {
|
|
4778
|
+
return this.entitlementClient.check();
|
|
4779
|
+
}
|
|
4780
|
+
/**
|
|
4781
|
+
* Decode a chart feature (from `queryRenderedFeatures`, the features API,
|
|
4782
|
+
* or plain `{ layer, properties }`) into a typed {@link InspectResult}.
|
|
4783
|
+
*
|
|
4784
|
+
* The first call performs the server-side entitlement check. With
|
|
4785
|
+
* `{ enrich: true }` the result is upgraded with full S-57 properties and
|
|
4786
|
+
* source-cell provenance (edition / update / issue date) via the features
|
|
4787
|
+
* API — metered against the key's `featureCalls` quota.
|
|
4788
|
+
*
|
|
4789
|
+
* @example
|
|
4790
|
+
* const result = await chrt.interact.inspect(feature);
|
|
4791
|
+
* if (result.kind === "navaid") console.log(result.characteristic); // "Fl R 4s"
|
|
4792
|
+
*/
|
|
4793
|
+
async inspect(feature, opts) {
|
|
4794
|
+
await this.entitlementClient.check();
|
|
4795
|
+
const result = buildInspectResult(feature, opts);
|
|
4796
|
+
if (opts?.enrich) {
|
|
4797
|
+
return enrichResult(this.http, result, opts.signal);
|
|
4798
|
+
}
|
|
4799
|
+
return result;
|
|
4800
|
+
}
|
|
4801
|
+
/**
|
|
4802
|
+
* Collect EVERY inspectable feature under a point on a CHRT map —
|
|
4803
|
+
* deduplicated across tile boundaries by S-57 identity (`LNAM`) and
|
|
4804
|
+
* ordered by class priority (point targets > lines > areas, safety
|
|
4805
|
+
* classes first within each band). Area features never preempt the buoy
|
|
4806
|
+
* on top of them but always stay selectable: a click on open water
|
|
4807
|
+
* resolves to its DEPARE.
|
|
4808
|
+
*
|
|
4809
|
+
* `point` is a screen pixel point (`e.point` from a map click) or
|
|
4810
|
+
* `[lng, lat]`. Point/line targets are picked up within
|
|
4811
|
+
* `opts.tolerance` px (default 8); polygon candidates require the exact
|
|
4812
|
+
* point to fall inside them.
|
|
4813
|
+
*
|
|
4814
|
+
* The first call performs the server-side entitlement check.
|
|
4815
|
+
*
|
|
4816
|
+
* @example
|
|
4817
|
+
* const { candidates, select } = await chrt.interact.inspectAll(map, e.point);
|
|
4818
|
+
* const result = await select(candidates[0]); // full decode (+ merge)
|
|
4819
|
+
*/
|
|
4820
|
+
async inspectAll(map, point, opts = {}) {
|
|
4821
|
+
if (!map || typeof map.queryRenderedFeatures !== "function") {
|
|
4822
|
+
throw new InteractError(
|
|
4823
|
+
"unexpected_response",
|
|
4824
|
+
"inspectAll(): expected a MapLibre / Mapbox GL map instance"
|
|
4825
|
+
);
|
|
4826
|
+
}
|
|
4827
|
+
await this.entitlementClient.check();
|
|
4828
|
+
const screen = Array.isArray(point) ? map.project([point[0], point[1]]) : point;
|
|
4829
|
+
const lngLat = Array.isArray(point) ? { lng: point[0], lat: point[1] } : map.unproject?.([point.x, point.y]) ?? null;
|
|
4830
|
+
const at = { point: screen, lngLat };
|
|
4831
|
+
const candidates = collectClickCandidates(map, screen, opts);
|
|
4832
|
+
const http = this.http;
|
|
4833
|
+
return {
|
|
4834
|
+
candidates,
|
|
4835
|
+
select: async (candidate, selectOpts) => {
|
|
4836
|
+
const result = decodeCandidate(map, candidate, at, opts);
|
|
4837
|
+
if (selectOpts?.enrich) {
|
|
4838
|
+
return enrichResult(http, result, selectOpts.signal);
|
|
4839
|
+
}
|
|
4840
|
+
return result;
|
|
4841
|
+
}
|
|
4842
|
+
};
|
|
4843
|
+
}
|
|
4844
|
+
/**
|
|
4845
|
+
* Attach click-to-inspect to a MapLibre / Mapbox GL map rendering CHRT
|
|
4846
|
+
* tiles. Resolves once the server-side entitlement check passes; rejects
|
|
4847
|
+
* with `InteractError` (`interact_not_enabled` on Free plans) otherwise —
|
|
4848
|
+
* no listeners are wired on denial.
|
|
4849
|
+
*
|
|
4850
|
+
* Headless-first: pass `onInspect` to receive every decoded
|
|
4851
|
+
* {@link InspectResult} and render your own UI. Without callbacks, the
|
|
4852
|
+
* built-in minimal popup is shown (opt back in explicitly with
|
|
4853
|
+
* `popup: true` to have both).
|
|
4854
|
+
*
|
|
4855
|
+
* A click that hits SEVERAL features (deduplicated by S-57 identity)
|
|
4856
|
+
* disambiguates instead of silently taking the topmost:
|
|
4857
|
+
* - `onCandidates` provided → it receives the priority-ordered list;
|
|
4858
|
+
* resolve a choice with `ctx.select(candidate)`.
|
|
4859
|
+
* - built-in popup → shows a candidate list; picking a row opens the
|
|
4860
|
+
* result card.
|
|
4861
|
+
* - `onInspect` only → the top candidate is inspected immediately, and
|
|
4862
|
+
* clicking the SAME spot again cycles to the next candidate — so an
|
|
4863
|
+
* area feature under a buoy is reachable with a second click.
|
|
4864
|
+
*
|
|
4865
|
+
* @example
|
|
4866
|
+
* const handle = await chrt.interact.attach(map, {
|
|
4867
|
+
* onInspect: (result) => console.log(result.title, result.characteristic),
|
|
4868
|
+
* });
|
|
4869
|
+
* // later: handle.detach();
|
|
4870
|
+
*/
|
|
4871
|
+
async attach(map, opts = {}) {
|
|
4872
|
+
if (!map || typeof map.queryRenderedFeatures !== "function" || typeof map.on !== "function") {
|
|
4873
|
+
throw new InteractError(
|
|
4874
|
+
"unexpected_response",
|
|
4875
|
+
"attach(): expected a MapLibre / Mapbox GL map instance"
|
|
4876
|
+
);
|
|
4877
|
+
}
|
|
4878
|
+
await this.entitlementClient.check();
|
|
4879
|
+
const showPopup = opts.popup ?? (!opts.onInspect && !opts.onCandidates);
|
|
4880
|
+
const popup = showPopup ? new InteractPopup(map) : null;
|
|
4881
|
+
const http = this.http;
|
|
4882
|
+
let cycle = null;
|
|
4883
|
+
const samePoint = (a, b) => {
|
|
4884
|
+
const pad = opts.tolerance ?? DEFAULT_CLICK_TOLERANCE;
|
|
4885
|
+
return Math.abs(a.x - b.x) <= pad && Math.abs(a.y - b.y) <= pad;
|
|
4886
|
+
};
|
|
4887
|
+
const select = (candidate, e) => {
|
|
4888
|
+
const result = decodeCandidate(map, candidate, e, opts);
|
|
4889
|
+
const finish = (r) => {
|
|
4890
|
+
popup?.show(r, r.coordinates ?? [e.lngLat.lng, e.lngLat.lat]);
|
|
4891
|
+
opts.onInspect?.(r, { lngLat: e.lngLat, point: e.point, feature: candidate.feature });
|
|
4892
|
+
return r;
|
|
4893
|
+
};
|
|
4894
|
+
if (opts.enrich) {
|
|
4895
|
+
return enrichResult(http, result).then(finish).catch(() => finish(result));
|
|
4896
|
+
}
|
|
4897
|
+
return Promise.resolve(finish(result));
|
|
4898
|
+
};
|
|
4899
|
+
const clickHandler = (e) => {
|
|
4900
|
+
const candidates = collectClickCandidates(map, e.point, opts);
|
|
4901
|
+
if (candidates.length === 0) {
|
|
4902
|
+
cycle = null;
|
|
4903
|
+
popup?.close();
|
|
4904
|
+
opts.onMiss?.({ lngLat: e.lngLat });
|
|
4905
|
+
return;
|
|
4906
|
+
}
|
|
4907
|
+
if (candidates.length === 1) {
|
|
4908
|
+
cycle = null;
|
|
4909
|
+
void select(candidates[0], e);
|
|
4910
|
+
return;
|
|
4911
|
+
}
|
|
4912
|
+
if (opts.onCandidates || popup) {
|
|
4913
|
+
cycle = null;
|
|
4914
|
+
if (popup) {
|
|
4915
|
+
popup.showList(candidates, [e.lngLat.lng, e.lngLat.lat], (c) => void select(c, e));
|
|
4916
|
+
}
|
|
4917
|
+
opts.onCandidates?.(candidates, {
|
|
4918
|
+
lngLat: e.lngLat,
|
|
4919
|
+
point: e.point,
|
|
4920
|
+
select: (c) => select(c, e)
|
|
4921
|
+
});
|
|
4922
|
+
return;
|
|
4923
|
+
}
|
|
4924
|
+
const key = candidates.map((c) => c.identity ?? c.layerId).join("/");
|
|
4925
|
+
if (cycle && cycle.key === key && samePoint(cycle.point, e.point)) {
|
|
4926
|
+
cycle.index = (cycle.index + 1) % candidates.length;
|
|
4927
|
+
cycle.point = e.point;
|
|
4928
|
+
} else {
|
|
4929
|
+
cycle = { key, point: e.point, index: 0 };
|
|
4930
|
+
}
|
|
4931
|
+
void select(candidates[cycle.index], e);
|
|
4932
|
+
};
|
|
4933
|
+
map.on("click", clickHandler);
|
|
4934
|
+
return {
|
|
4935
|
+
detach: () => {
|
|
4936
|
+
map.off("click", clickHandler);
|
|
4937
|
+
popup?.close();
|
|
4938
|
+
}
|
|
4939
|
+
};
|
|
4940
|
+
}
|
|
4811
4941
|
};
|
|
4812
|
-
function getColorTable(name) {
|
|
4813
|
-
return COLOR_TABLES[name];
|
|
4814
|
-
}
|
|
4815
|
-
function resolveColor(token, table = "DAY") {
|
|
4816
|
-
const t = COLOR_TABLES[table];
|
|
4817
|
-
return t[token] ?? (table === "NIGHT" ? "#c8aa78" : "#000000");
|
|
4818
|
-
}
|
|
4819
4942
|
|
|
4820
4943
|
// src/atlas/s52-linestyles-generated.ts
|
|
4821
4944
|
var S52_LINESTYLES = {
|
|
@@ -27466,6 +27589,35 @@ function iconExpressionForFamily(objectClass, family) {
|
|
|
27466
27589
|
}
|
|
27467
27590
|
}
|
|
27468
27591
|
|
|
27592
|
+
// src/atlas/base-bundle.ts
|
|
27593
|
+
var BASE_BUNDLE_ID = BASE_REGION;
|
|
27594
|
+
var BASE_BUNDLE_SCHEMA = "chrt.basemap-bundle/1";
|
|
27595
|
+
var BASE_BUNDLE_MAXZOOM = 6;
|
|
27596
|
+
var BASE_BUNDLE_SOURCE_LAYERS = [
|
|
27597
|
+
"base_earth",
|
|
27598
|
+
"base_water",
|
|
27599
|
+
"base_boundary",
|
|
27600
|
+
"country_boundary"
|
|
27601
|
+
];
|
|
27602
|
+
var BASE_BUNDLE_ATTRIBUTION = "\xA9 CHRT \xB7 Made with Natural Earth";
|
|
27603
|
+
var BASE_CLIENT_RASTER_BEFORE_ID = "chrtbase-nocoverage";
|
|
27604
|
+
function applyBaseBundle(style, opts) {
|
|
27605
|
+
const keep = new Set(BASE_BUNDLE_SOURCE_LAYERS);
|
|
27606
|
+
style.layers = style.layers.filter((raw) => {
|
|
27607
|
+
const layer = raw;
|
|
27608
|
+
if (layer.source !== BASE_REGION) return true;
|
|
27609
|
+
return typeof layer["source-layer"] === "string" && keep.has(layer["source-layer"]);
|
|
27610
|
+
});
|
|
27611
|
+
style.sources[BASE_REGION] = {
|
|
27612
|
+
type: "vector",
|
|
27613
|
+
tiles: [opts.tiles],
|
|
27614
|
+
minzoom: BASE_MINZOOM,
|
|
27615
|
+
maxzoom: BASE_BUNDLE_MAXZOOM,
|
|
27616
|
+
attribution: BASE_BUNDLE_ATTRIBUTION
|
|
27617
|
+
};
|
|
27618
|
+
return style;
|
|
27619
|
+
}
|
|
27620
|
+
|
|
27469
27621
|
// src/atlas/layer-builder.ts
|
|
27470
27622
|
var SCAMIN_DECLUTTER_CLASSES = /* @__PURE__ */ new Set([
|
|
27471
27623
|
"OFSPLF",
|
|
@@ -28806,6 +28958,99 @@ function getOrderedClasses() {
|
|
|
28806
28958
|
return ordered;
|
|
28807
28959
|
}
|
|
28808
28960
|
|
|
28961
|
+
// src/atlas/terrain-order.ts
|
|
28962
|
+
var DRAPEABLE_LAYER_TYPES = /* @__PURE__ */ new Set([
|
|
28963
|
+
"background",
|
|
28964
|
+
"fill",
|
|
28965
|
+
"line",
|
|
28966
|
+
"raster",
|
|
28967
|
+
"hillshade",
|
|
28968
|
+
"color-relief"
|
|
28969
|
+
]);
|
|
28970
|
+
function isDrapeable(layer) {
|
|
28971
|
+
return DRAPEABLE_LAYER_TYPES.has(layer.type);
|
|
28972
|
+
}
|
|
28973
|
+
function drapedFirst(layers) {
|
|
28974
|
+
const draped = [];
|
|
28975
|
+
const direct = [];
|
|
28976
|
+
for (const l of layers) (isDrapeable(l) ? draped : direct).push(l);
|
|
28977
|
+
return [...draped, ...direct];
|
|
28978
|
+
}
|
|
28979
|
+
function terrainStackCount(layers) {
|
|
28980
|
+
let stacks = 0;
|
|
28981
|
+
let prevDraped = false;
|
|
28982
|
+
for (const l of layers) {
|
|
28983
|
+
const d = isDrapeable(l);
|
|
28984
|
+
if (d && !prevDraped) stacks++;
|
|
28985
|
+
prevDraped = d;
|
|
28986
|
+
}
|
|
28987
|
+
return stacks;
|
|
28988
|
+
}
|
|
28989
|
+
function drapeHalves(layers) {
|
|
28990
|
+
const draped = [];
|
|
28991
|
+
const direct = [];
|
|
28992
|
+
for (const l of layers) (isDrapeable(l) ? draped : direct).push(l);
|
|
28993
|
+
return { draped, direct };
|
|
28994
|
+
}
|
|
28995
|
+
|
|
28996
|
+
// src/atlas/lamps.ts
|
|
28997
|
+
var LAMP_GLOW_LAYER_ID = "s52-LIGHTS-lamp-glow";
|
|
28998
|
+
var LAMP_CORE_LAYER_ID = "s52-LIGHTS-lamp-core";
|
|
28999
|
+
var LAMP_LAYER_IDS = [LAMP_GLOW_LAYER_ID, LAMP_CORE_LAYER_ID];
|
|
29000
|
+
var RANGE_NM = ["to-number", ["coalesce", ["get", "VALNMR"], ["get", "valnmr"]], 6];
|
|
29001
|
+
var UNSECTORED_POINTS = [
|
|
29002
|
+
"all",
|
|
29003
|
+
["any", ["==", ["geometry-type"], "Point"], ["==", ["geometry-type"], "MultiPoint"]],
|
|
29004
|
+
["!", ["has", "SECTR1"]],
|
|
29005
|
+
// …and not lights with a charted rhythm either: those flash, and the
|
|
29006
|
+
// flashing is computed by the app from `SIGSEQ` (light-sigseq.ts). What is
|
|
29007
|
+
// left here is exactly the set that is steady from every bearing.
|
|
29008
|
+
["!", ["has", "SIGSEQ"]]
|
|
29009
|
+
];
|
|
29010
|
+
function buildLampLayers(sourceId, ct) {
|
|
29011
|
+
const colour = lightFillExpression(ct);
|
|
29012
|
+
const common = {
|
|
29013
|
+
type: "circle",
|
|
29014
|
+
source: sourceId,
|
|
29015
|
+
"source-layer": "LIGHTS",
|
|
29016
|
+
filter: UNSECTORED_POINTS,
|
|
29017
|
+
layout: { visibility: "none" }
|
|
29018
|
+
};
|
|
29019
|
+
const metadata = { "chrt:role": "lamp", "s52:objectClass": "LIGHTS" };
|
|
29020
|
+
return [
|
|
29021
|
+
{
|
|
29022
|
+
...common,
|
|
29023
|
+
id: LAMP_GLOW_LAYER_ID,
|
|
29024
|
+
paint: {
|
|
29025
|
+
"circle-color": colour,
|
|
29026
|
+
// 4 nm → 7 px, 12 nm → 12 px, 25 nm → 20 px; capped, because a
|
|
29027
|
+
// primary seacoast light is not a searchlight.
|
|
29028
|
+
"circle-radius": ["interpolate", ["linear"], RANGE_NM, 0, 5, 4, 7, 12, 12, 25, 20],
|
|
29029
|
+
"circle-blur": 1,
|
|
29030
|
+
"circle-opacity": 0.55,
|
|
29031
|
+
// A lamp is a point of light: it does not grow as it comes near, and
|
|
29032
|
+
// it does not lie down with the chart.
|
|
29033
|
+
"circle-pitch-alignment": "viewport",
|
|
29034
|
+
"circle-pitch-scale": "viewport"
|
|
29035
|
+
},
|
|
29036
|
+
metadata: { ...metadata, "chrt:lamp": "glow" }
|
|
29037
|
+
},
|
|
29038
|
+
{
|
|
29039
|
+
...common,
|
|
29040
|
+
id: LAMP_CORE_LAYER_ID,
|
|
29041
|
+
paint: {
|
|
29042
|
+
"circle-color": colour,
|
|
29043
|
+
"circle-radius": ["interpolate", ["linear"], RANGE_NM, 0, 1.5, 12, 2.5, 25, 3.5],
|
|
29044
|
+
"circle-blur": 0.25,
|
|
29045
|
+
"circle-opacity": 1,
|
|
29046
|
+
"circle-pitch-alignment": "viewport",
|
|
29047
|
+
"circle-pitch-scale": "viewport"
|
|
29048
|
+
},
|
|
29049
|
+
metadata: { ...metadata, "chrt:lamp": "core" }
|
|
29050
|
+
}
|
|
29051
|
+
];
|
|
29052
|
+
}
|
|
29053
|
+
|
|
28809
29054
|
// src/atlas/band-priority.ts
|
|
28810
29055
|
var BAND_PRIORITY_RANGES = {
|
|
28811
29056
|
"1": { minzoom: 0 },
|
|
@@ -28921,11 +29166,21 @@ function styleModeBand(mode) {
|
|
|
28921
29166
|
return mode === "continuous" ? null : mode.slice("chart:".length);
|
|
28922
29167
|
}
|
|
28923
29168
|
function bandFilterExpression(codes) {
|
|
29169
|
+
return cellBandMatch(["slice", ["get", "_cell_id"], 2, 3], codes);
|
|
29170
|
+
}
|
|
29171
|
+
var BAND_CODE_ALIASES = {
|
|
29172
|
+
"5": ["7"]
|
|
29173
|
+
};
|
|
29174
|
+
function cellBandCharacters(code) {
|
|
29175
|
+
return [code, ...BAND_CODE_ALIASES[code] ?? []];
|
|
29176
|
+
}
|
|
29177
|
+
function cellBandMatch(cellBandExpr, codes) {
|
|
28924
29178
|
const list = Array.isArray(codes) ? codes : [codes];
|
|
28925
|
-
|
|
28926
|
-
|
|
29179
|
+
const chars = [...new Set(list.flatMap((c) => cellBandCharacters(c)))];
|
|
29180
|
+
if (chars.length === 1) {
|
|
29181
|
+
return ["==", cellBandExpr, chars[0]];
|
|
28927
29182
|
}
|
|
28928
|
-
return ["in",
|
|
29183
|
+
return ["in", cellBandExpr, ["literal", chars]];
|
|
28929
29184
|
}
|
|
28930
29185
|
function combineWithBandFilter(baseFilter, bandFilter) {
|
|
28931
29186
|
if (!baseFilter) return bandFilter;
|
|
@@ -30009,7 +30264,8 @@ function fanOrientationLabelByCellBand(label, bandFan, objectClass) {
|
|
|
30009
30264
|
}
|
|
30010
30265
|
const cellFiltered = combineWithBandFilter(
|
|
30011
30266
|
label.filter,
|
|
30012
|
-
|
|
30267
|
+
// Aliases included (USACE `7` rides with band 5 — #269).
|
|
30268
|
+
cellBandMatch(cellBandExpr, code)
|
|
30013
30269
|
);
|
|
30014
30270
|
const minzoom = Math.max(
|
|
30015
30271
|
label.minzoom ?? range.minzoom,
|
|
@@ -30214,6 +30470,7 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
30214
30470
|
(b) => !TOP_STACK_LIGHT_CLASSES.has(b.objectClass) && !BOTTOM_STACK_WATER_BASE_CLASSES.has(b.objectClass)
|
|
30215
30471
|
);
|
|
30216
30472
|
const topLightBundles = bundles.filter((b) => TOP_STACK_LIGHT_CLASSES.has(b.objectClass));
|
|
30473
|
+
const lampLayers = buildLampLayers(sourceId, palette);
|
|
30217
30474
|
const waterBaseBundles = bundles.filter(
|
|
30218
30475
|
(b) => BOTTOM_STACK_WATER_BASE_CLASSES.has(b.objectClass)
|
|
30219
30476
|
);
|
|
@@ -30247,6 +30504,7 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
30247
30504
|
emitChartFeatureVariants(waterBaseBundles);
|
|
30248
30505
|
emitChartFeatureVariants(regularBundles);
|
|
30249
30506
|
emitChartFeatureVariants(topLightBundles);
|
|
30507
|
+
layers.push(...lampLayers);
|
|
30250
30508
|
const chartLabelBundles = byLabelPriorityBottomUp(
|
|
30251
30509
|
bundles.filter(
|
|
30252
30510
|
(b) => b.label !== null && (opts.labels !== "placename" || getLabelBehavior(b.objectClass) !== "orientation-cross-band")
|
|
@@ -30324,6 +30582,7 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
30324
30582
|
}
|
|
30325
30583
|
}
|
|
30326
30584
|
}
|
|
30585
|
+
layers.push(...lampLayers);
|
|
30327
30586
|
{
|
|
30328
30587
|
const TERMINAL_FILL_RE = new RegExp(
|
|
30329
30588
|
`^s52-(?:${[...DOMINANCE_EXEMPT_FILL_CLASSES].join("|")})-fill-band[456]$`
|
|
@@ -30412,13 +30671,18 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
30412
30671
|
const glyphsBase = `${opts.tileBaseUrl ?? "https://tiles.chrt.co"}/v1/glyphs`;
|
|
30413
30672
|
const isLndareFill = (l) => /^s52-LNDARE-fill-band\d+$/.test(l.id);
|
|
30414
30673
|
const isWaterBaseFill = (l) => /^s52-(DEPARE|DRGARE|UNSARE)-fill-band\d+$/.test(l.id);
|
|
30674
|
+
const isLakareFill = (l) => /^s52-LAKARE-fill-band\d+$/.test(l.id);
|
|
30415
30675
|
const bandOf = (l) => {
|
|
30416
30676
|
const m = /-band(\d+)$/.exec(l.id);
|
|
30417
30677
|
return m ? Number(m[1]) : 0;
|
|
30418
30678
|
};
|
|
30419
30679
|
const emitted = opts.land ? (() => {
|
|
30420
|
-
const relocated = layers.filter(
|
|
30421
|
-
|
|
30680
|
+
const relocated = layers.filter(
|
|
30681
|
+
(l) => isLndareFill(l) || isWaterBaseFill(l) || isLakareFill(l)
|
|
30682
|
+
);
|
|
30683
|
+
const others = layers.filter(
|
|
30684
|
+
(l) => !isLndareFill(l) && !isWaterBaseFill(l) && !isLakareFill(l)
|
|
30685
|
+
);
|
|
30422
30686
|
const isSeaareFill = (l) => /^s52-SEAARE-fill-band\d+$/.test(l.id);
|
|
30423
30687
|
const seaare = others.filter(isSeaareFill);
|
|
30424
30688
|
const rest = others.filter((l) => !isSeaareFill(l));
|
|
@@ -30442,40 +30706,43 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
30442
30706
|
for (const b of bands) {
|
|
30443
30707
|
ordered.push(...relocated.filter((l) => bandOf(l) === b && isWaterBaseFill(l)));
|
|
30444
30708
|
ordered.push(...relocated.filter((l) => bandOf(l) === b && isLndareFill(l)).map(asBaseTan));
|
|
30709
|
+
ordered.push(...relocated.filter((l) => bandOf(l) === b && isLakareFill(l)));
|
|
30445
30710
|
}
|
|
30446
30711
|
const findRest = (id) => restAbove.findIndex((l) => l.id === id);
|
|
30447
|
-
const
|
|
30448
|
-
const pairAt = shoreAt !== -1 ? shoreAt : findRest("chrtbase-inland-hatch");
|
|
30712
|
+
const pairAt = findRest("chrtbase-inland-water");
|
|
30449
30713
|
let restFinal = restAbove;
|
|
30450
30714
|
if (pairAt !== -1) {
|
|
30451
30715
|
const repaint = [];
|
|
30452
|
-
|
|
30453
|
-
|
|
30454
|
-
|
|
30455
|
-
|
|
30456
|
-
|
|
30716
|
+
const pushCascadeClones = (l, b) => {
|
|
30717
|
+
const src = l;
|
|
30718
|
+
const meta = { ...src.metadata ?? {}, "chrt:role": "charted-water-repaint" };
|
|
30719
|
+
const notDom = {
|
|
30720
|
+
...l,
|
|
30721
|
+
id: `${src.id}-overpair`,
|
|
30722
|
+
filter: ["all", src.filter, NOT_DOMINATED_FILTER],
|
|
30723
|
+
metadata: meta
|
|
30724
|
+
};
|
|
30725
|
+
delete notDom.maxzoom;
|
|
30726
|
+
delete notDom.minzoom;
|
|
30727
|
+
repaint.push(notDom);
|
|
30728
|
+
for (const D of BAND_DRAW_ORDER) {
|
|
30729
|
+
if (parseInt(D, 10) <= b) continue;
|
|
30730
|
+
const dMaxzoom = dominatedVariantMaxzoom(D);
|
|
30731
|
+
if (dMaxzoom <= 0) continue;
|
|
30732
|
+
const domVariant = {
|
|
30457
30733
|
...l,
|
|
30458
|
-
id: `${src.id}-overpair`,
|
|
30459
|
-
filter: ["all", src.filter,
|
|
30460
|
-
metadata: meta
|
|
30734
|
+
id: `${src.id}-overpair-domBy${D}`,
|
|
30735
|
+
filter: ["all", src.filter, dominatedByMinFilter(D)],
|
|
30736
|
+
metadata: meta,
|
|
30737
|
+
maxzoom: dMaxzoom
|
|
30461
30738
|
};
|
|
30462
|
-
delete
|
|
30463
|
-
|
|
30464
|
-
|
|
30465
|
-
|
|
30466
|
-
|
|
30467
|
-
|
|
30468
|
-
|
|
30469
|
-
const domVariant = {
|
|
30470
|
-
...l,
|
|
30471
|
-
id: `${src.id}-overpair-domBy${D}`,
|
|
30472
|
-
filter: ["all", src.filter, dominatedByMinFilter(D)],
|
|
30473
|
-
metadata: meta,
|
|
30474
|
-
maxzoom: dMaxzoom
|
|
30475
|
-
};
|
|
30476
|
-
delete domVariant.minzoom;
|
|
30477
|
-
repaint.push(domVariant);
|
|
30478
|
-
}
|
|
30739
|
+
delete domVariant.minzoom;
|
|
30740
|
+
repaint.push(domVariant);
|
|
30741
|
+
}
|
|
30742
|
+
};
|
|
30743
|
+
for (const b of bands) {
|
|
30744
|
+
for (const l of relocated.filter((x) => bandOf(x) === b && isWaterBaseFill(x))) {
|
|
30745
|
+
pushCascadeClones(l, b);
|
|
30479
30746
|
}
|
|
30480
30747
|
if (parseInt(b, 10) >= FINE_LAND_REPAINT_MIN_BAND) {
|
|
30481
30748
|
for (const l of relocated.filter(
|
|
@@ -30491,6 +30758,9 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
30491
30758
|
repaint.push(landClone);
|
|
30492
30759
|
}
|
|
30493
30760
|
}
|
|
30761
|
+
for (const l of relocated.filter((x) => bandOf(x) === b && isLakareFill(x))) {
|
|
30762
|
+
pushCascadeClones(l, b);
|
|
30763
|
+
}
|
|
30494
30764
|
}
|
|
30495
30765
|
const isCoalneLine = (l) => /^s52-COALNE-line-band\d(-domBy\d)?$/.test(l.id);
|
|
30496
30766
|
for (const l of layers.filter(isCoalneLine)) {
|
|
@@ -30514,9 +30784,9 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
30514
30784
|
repaint.push(terminal);
|
|
30515
30785
|
}
|
|
30516
30786
|
restFinal = [
|
|
30517
|
-
...restAbove.slice(0, pairAt
|
|
30787
|
+
...restAbove.slice(0, pairAt),
|
|
30518
30788
|
...repaint,
|
|
30519
|
-
...restAbove.slice(pairAt
|
|
30789
|
+
...restAbove.slice(pairAt)
|
|
30520
30790
|
];
|
|
30521
30791
|
}
|
|
30522
30792
|
{
|
|
@@ -30565,6 +30835,7 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
30565
30835
|
emitted.push(layer);
|
|
30566
30836
|
}
|
|
30567
30837
|
}
|
|
30838
|
+
const finalLayers = drapedFirst(emitted);
|
|
30568
30839
|
const style = {
|
|
30569
30840
|
version: 8,
|
|
30570
30841
|
name: `CHRT ${SYMBOL_SET_LABELS[symbolSet]} ${PALETTE_NAMES[palette]}${opts.chartMode ? ` \u2014 chart:${opts.chartMode.band}` : ""}`,
|
|
@@ -30573,7 +30844,7 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
30573
30844
|
"chrt:symbol-set": symbolSet,
|
|
30574
30845
|
"chrt:color-table": palette,
|
|
30575
30846
|
"chrt:generated-at": (/* @__PURE__ */ new Date()).toISOString(),
|
|
30576
|
-
"chrt:layer-count":
|
|
30847
|
+
"chrt:layer-count": finalLayers.length,
|
|
30577
30848
|
"chrt:land": Boolean(opts.land),
|
|
30578
30849
|
"chrt:relief": Boolean(opts.relief),
|
|
30579
30850
|
"chrt:base": Boolean(opts.base),
|
|
@@ -30613,7 +30884,7 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
30613
30884
|
// upstream glyph byte-identical and composite only those six in from
|
|
30614
30885
|
// Noto Sans; see scripts/generate-glyphs.mjs.
|
|
30615
30886
|
glyphs: `${glyphsBase}/{fontstack}/{range}.pbf`,
|
|
30616
|
-
layers:
|
|
30887
|
+
layers: finalLayers
|
|
30617
30888
|
};
|
|
30618
30889
|
if (symbolSet === "s101") return applyS101SymbolSet(style);
|
|
30619
30890
|
if (symbolSet === "chrt") return applyChrtSymbolSet(style, palette);
|
|
@@ -31441,8 +31712,16 @@ export {
|
|
|
31441
31712
|
ALL_WATER_BODY_LAYERS,
|
|
31442
31713
|
AREA_QUERY_LAYERS,
|
|
31443
31714
|
ATON_EXPRESSION_CLASSES,
|
|
31715
|
+
BAND_CODE_ALIASES,
|
|
31444
31716
|
BAND_DRAW_ORDER,
|
|
31445
31717
|
BAND_PRIORITY_RANGES,
|
|
31718
|
+
BASE_ATTRIBUTION,
|
|
31719
|
+
BASE_BUNDLE_ATTRIBUTION,
|
|
31720
|
+
BASE_BUNDLE_ID,
|
|
31721
|
+
BASE_BUNDLE_MAXZOOM,
|
|
31722
|
+
BASE_BUNDLE_SCHEMA,
|
|
31723
|
+
BASE_BUNDLE_SOURCE_LAYERS,
|
|
31724
|
+
BASE_CLIENT_RASTER_BEFORE_ID,
|
|
31446
31725
|
BASE_MAXZOOM,
|
|
31447
31726
|
BASE_MINZOOM,
|
|
31448
31727
|
BASE_REGION,
|
|
@@ -31475,6 +31754,7 @@ export {
|
|
|
31475
31754
|
DEFAULT_TILE_BASE_URL,
|
|
31476
31755
|
DEFAULT_TILE_REGION,
|
|
31477
31756
|
DEFAULT_WATER_BODY_SETTINGS,
|
|
31757
|
+
DRAPEABLE_LAYER_TYPES,
|
|
31478
31758
|
INSPECT_CLASS_RANK,
|
|
31479
31759
|
INSPECT_RANK_DEVIATIONS,
|
|
31480
31760
|
InteractClient,
|
|
@@ -31483,6 +31763,11 @@ export {
|
|
|
31483
31763
|
LABEL_BEHAVIOR,
|
|
31484
31764
|
LABEL_LAYER_GROUPS,
|
|
31485
31765
|
LABEL_PRIORITY,
|
|
31766
|
+
LAMP_CORE_LAYER_ID,
|
|
31767
|
+
LAMP_GLOW_LAYER_ID,
|
|
31768
|
+
LAMP_LAYER_IDS,
|
|
31769
|
+
LAMP_TICK_MS,
|
|
31770
|
+
LAND_ATTRIBUTION,
|
|
31486
31771
|
LAND_BUILD_MAXZOOM,
|
|
31487
31772
|
LAND_REGION,
|
|
31488
31773
|
NAUTICAL_SYMBOLS,
|
|
@@ -31498,7 +31783,10 @@ export {
|
|
|
31498
31783
|
RASTER_ATON_CLASSES,
|
|
31499
31784
|
RASTER_GLYPH_NAMES,
|
|
31500
31785
|
RASTER_ICON_PREFIX,
|
|
31786
|
+
RELIEF_ATTRIBUTION,
|
|
31787
|
+
RELIEF_ENCODING,
|
|
31501
31788
|
RELIEF_REGION,
|
|
31789
|
+
RELIEF_TILE_SIZE,
|
|
31502
31790
|
S101_AREA_FILL_NAMES,
|
|
31503
31791
|
S101_CHART_SECTIONS,
|
|
31504
31792
|
S101_FEATURES,
|
|
@@ -31518,6 +31806,8 @@ export {
|
|
|
31518
31806
|
S52_LOOKUP,
|
|
31519
31807
|
S52_NIGHT,
|
|
31520
31808
|
S52_PATTERN_SPRITE_PREFIX,
|
|
31809
|
+
SIGPER_TOLERANCE_SECONDS,
|
|
31810
|
+
SIGSEQ_MAX_TERMS,
|
|
31521
31811
|
SOUNDING_LAYERS,
|
|
31522
31812
|
SYMBOL_FAMILIES,
|
|
31523
31813
|
SYMBOL_FAMILY_META,
|
|
@@ -31527,6 +31817,7 @@ export {
|
|
|
31527
31817
|
TileTokenError,
|
|
31528
31818
|
WATER_BODY_LAYER_GROUPS,
|
|
31529
31819
|
WeatherEndpoint,
|
|
31820
|
+
applyBaseBundle,
|
|
31530
31821
|
applyChrtSymbolSet,
|
|
31531
31822
|
applyColorTheme,
|
|
31532
31823
|
applyCustomStyleSettings,
|
|
@@ -31539,6 +31830,7 @@ export {
|
|
|
31539
31830
|
buildFlashPattern,
|
|
31540
31831
|
buildLabelLayer,
|
|
31541
31832
|
buildLakeTierExpression,
|
|
31833
|
+
buildLampLayers,
|
|
31542
31834
|
buildLandAreaTierExpression,
|
|
31543
31835
|
buildLandRegionTierExpression,
|
|
31544
31836
|
buildLayersForClass,
|
|
@@ -31551,6 +31843,8 @@ export {
|
|
|
31551
31843
|
buildSectorsFromFeatures,
|
|
31552
31844
|
buildSources,
|
|
31553
31845
|
candidateRank,
|
|
31846
|
+
cellBandCharacters,
|
|
31847
|
+
cellBandMatch,
|
|
31554
31848
|
chrtAtonTintExpression,
|
|
31555
31849
|
chrtLightTintExpression,
|
|
31556
31850
|
collectCandidates,
|
|
@@ -31565,6 +31859,8 @@ export {
|
|
|
31565
31859
|
depareColor,
|
|
31566
31860
|
depareFillExpression,
|
|
31567
31861
|
deriveMarinerName,
|
|
31862
|
+
drapeHalves,
|
|
31863
|
+
drapedFirst,
|
|
31568
31864
|
extractNavAidDesignation,
|
|
31569
31865
|
featureIdentity,
|
|
31570
31866
|
generateCustomStyle,
|
|
@@ -31597,6 +31893,7 @@ export {
|
|
|
31597
31893
|
hazardColor,
|
|
31598
31894
|
hazardFillExpression,
|
|
31599
31895
|
iconExpressionForFamily,
|
|
31896
|
+
isDrapeable,
|
|
31600
31897
|
isLabelLayerVisible,
|
|
31601
31898
|
isNavAidLayer,
|
|
31602
31899
|
isNavAidLayerVisible,
|
|
@@ -31604,6 +31901,8 @@ export {
|
|
|
31604
31901
|
isVisibleInMode,
|
|
31605
31902
|
isWaterBodyLayerVisible,
|
|
31606
31903
|
labelSortKey,
|
|
31904
|
+
lampRhythmColour,
|
|
31905
|
+
lampRhythmOpacity,
|
|
31607
31906
|
lateralColor,
|
|
31608
31907
|
lateralFillExpression,
|
|
31609
31908
|
lightColor,
|
|
@@ -31613,6 +31912,7 @@ export {
|
|
|
31613
31912
|
normalizeTheme,
|
|
31614
31913
|
paginate,
|
|
31615
31914
|
parseListCodes,
|
|
31915
|
+
parseSigseqTerms,
|
|
31616
31916
|
parseSymbolFamily,
|
|
31617
31917
|
parseWebhook,
|
|
31618
31918
|
patternSpriteId,
|
|
@@ -31627,12 +31927,14 @@ export {
|
|
|
31627
31927
|
resolveS101Color,
|
|
31628
31928
|
resolveStyle,
|
|
31629
31929
|
resolveTargetClass,
|
|
31930
|
+
rhythmProperties,
|
|
31630
31931
|
s101ArtworkProvenance,
|
|
31631
31932
|
sanitizeCustomStyleConfig,
|
|
31632
31933
|
scaleSizeValue,
|
|
31633
31934
|
scaminToMinZoom,
|
|
31634
31935
|
sectorLimitToArcBearing,
|
|
31635
31936
|
styleModeBand,
|
|
31937
|
+
terrainStackCount,
|
|
31636
31938
|
tileTemplate,
|
|
31637
31939
|
uncoveredSectorArcs,
|
|
31638
31940
|
withinBboxFilter,
|