@chrtco/sdk 0.10.38 → 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 +995 -651
- 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 +964 -651
- 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) {
|
|
@@ -4362,460 +4742,203 @@ ${notice.dataCurrency}`;
|
|
|
4362
4742
|
this.place();
|
|
4363
4743
|
this.map.on("move", this.reposition);
|
|
4364
4744
|
}
|
|
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"
|
|
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 },
|
|
@@ -28841,6 +29086,17 @@ function isExemptOpaqueFill(layer) {
|
|
|
28841
29086
|
}
|
|
28842
29087
|
return false;
|
|
28843
29088
|
}
|
|
29089
|
+
var GROUND_FILL_CLASSES = /* @__PURE__ */ new Set([
|
|
29090
|
+
...DOMINANCE_EXEMPT_FILL_CLASSES,
|
|
29091
|
+
"RIVERS"
|
|
29092
|
+
]);
|
|
29093
|
+
function isGroundFill(layer) {
|
|
29094
|
+
if (layer.type !== "fill") return false;
|
|
29095
|
+
for (const cls of GROUND_FILL_CLASSES) {
|
|
29096
|
+
if (layer.id.startsWith(`s52-${cls}-`)) return true;
|
|
29097
|
+
}
|
|
29098
|
+
return false;
|
|
29099
|
+
}
|
|
28844
29100
|
var FILL_FLOOR_FREE_BANDS = /* @__PURE__ */ new Set([
|
|
28845
29101
|
"1",
|
|
28846
29102
|
"2",
|
|
@@ -28848,10 +29104,10 @@ var FILL_FLOOR_FREE_BANDS = /* @__PURE__ */ new Set([
|
|
|
28848
29104
|
]);
|
|
28849
29105
|
function bandVariantMinzoom(layer, B) {
|
|
28850
29106
|
const classMinzoom = layer.minzoom ?? 0;
|
|
28851
|
-
if (
|
|
28852
|
-
return
|
|
29107
|
+
if (isGroundFill(layer) && FILL_FLOOR_FREE_BANDS.has(B)) {
|
|
29108
|
+
return 0;
|
|
28853
29109
|
}
|
|
28854
|
-
if (
|
|
29110
|
+
if (isGroundFill(layer)) {
|
|
28855
29111
|
return BAND_PRIORITY_RANGES[B].minzoom;
|
|
28856
29112
|
}
|
|
28857
29113
|
return Math.max(classMinzoom, BAND_PRIORITY_RANGES[B].minzoom);
|
|
@@ -28910,11 +29166,21 @@ function styleModeBand(mode) {
|
|
|
28910
29166
|
return mode === "continuous" ? null : mode.slice("chart:".length);
|
|
28911
29167
|
}
|
|
28912
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) {
|
|
28913
29178
|
const list = Array.isArray(codes) ? codes : [codes];
|
|
28914
|
-
|
|
28915
|
-
|
|
29179
|
+
const chars = [...new Set(list.flatMap((c) => cellBandCharacters(c)))];
|
|
29180
|
+
if (chars.length === 1) {
|
|
29181
|
+
return ["==", cellBandExpr, chars[0]];
|
|
28916
29182
|
}
|
|
28917
|
-
return ["in",
|
|
29183
|
+
return ["in", cellBandExpr, ["literal", chars]];
|
|
28918
29184
|
}
|
|
28919
29185
|
function combineWithBandFilter(baseFilter, bandFilter) {
|
|
28920
29186
|
if (!baseFilter) return bandFilter;
|
|
@@ -29998,7 +30264,8 @@ function fanOrientationLabelByCellBand(label, bandFan, objectClass) {
|
|
|
29998
30264
|
}
|
|
29999
30265
|
const cellFiltered = combineWithBandFilter(
|
|
30000
30266
|
label.filter,
|
|
30001
|
-
|
|
30267
|
+
// Aliases included (USACE `7` rides with band 5 — #269).
|
|
30268
|
+
cellBandMatch(cellBandExpr, code)
|
|
30002
30269
|
);
|
|
30003
30270
|
const minzoom = Math.max(
|
|
30004
30271
|
label.minzoom ?? range.minzoom,
|
|
@@ -30203,6 +30470,7 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
30203
30470
|
(b) => !TOP_STACK_LIGHT_CLASSES.has(b.objectClass) && !BOTTOM_STACK_WATER_BASE_CLASSES.has(b.objectClass)
|
|
30204
30471
|
);
|
|
30205
30472
|
const topLightBundles = bundles.filter((b) => TOP_STACK_LIGHT_CLASSES.has(b.objectClass));
|
|
30473
|
+
const lampLayers = buildLampLayers(sourceId, palette);
|
|
30206
30474
|
const waterBaseBundles = bundles.filter(
|
|
30207
30475
|
(b) => BOTTOM_STACK_WATER_BASE_CLASSES.has(b.objectClass)
|
|
30208
30476
|
);
|
|
@@ -30236,6 +30504,7 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
30236
30504
|
emitChartFeatureVariants(waterBaseBundles);
|
|
30237
30505
|
emitChartFeatureVariants(regularBundles);
|
|
30238
30506
|
emitChartFeatureVariants(topLightBundles);
|
|
30507
|
+
layers.push(...lampLayers);
|
|
30239
30508
|
const chartLabelBundles = byLabelPriorityBottomUp(
|
|
30240
30509
|
bundles.filter(
|
|
30241
30510
|
(b) => b.label !== null && (opts.labels !== "placename" || getLabelBehavior(b.objectClass) !== "orientation-cross-band")
|
|
@@ -30313,6 +30582,7 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
30313
30582
|
}
|
|
30314
30583
|
}
|
|
30315
30584
|
}
|
|
30585
|
+
layers.push(...lampLayers);
|
|
30316
30586
|
{
|
|
30317
30587
|
const TERMINAL_FILL_RE = new RegExp(
|
|
30318
30588
|
`^s52-(?:${[...DOMINANCE_EXEMPT_FILL_CLASSES].join("|")})-fill-band[456]$`
|
|
@@ -30401,13 +30671,18 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
30401
30671
|
const glyphsBase = `${opts.tileBaseUrl ?? "https://tiles.chrt.co"}/v1/glyphs`;
|
|
30402
30672
|
const isLndareFill = (l) => /^s52-LNDARE-fill-band\d+$/.test(l.id);
|
|
30403
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);
|
|
30404
30675
|
const bandOf = (l) => {
|
|
30405
30676
|
const m = /-band(\d+)$/.exec(l.id);
|
|
30406
30677
|
return m ? Number(m[1]) : 0;
|
|
30407
30678
|
};
|
|
30408
30679
|
const emitted = opts.land ? (() => {
|
|
30409
|
-
const relocated = layers.filter(
|
|
30410
|
-
|
|
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
|
+
);
|
|
30411
30686
|
const isSeaareFill = (l) => /^s52-SEAARE-fill-band\d+$/.test(l.id);
|
|
30412
30687
|
const seaare = others.filter(isSeaareFill);
|
|
30413
30688
|
const rest = others.filter((l) => !isSeaareFill(l));
|
|
@@ -30431,40 +30706,43 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
30431
30706
|
for (const b of bands) {
|
|
30432
30707
|
ordered.push(...relocated.filter((l) => bandOf(l) === b && isWaterBaseFill(l)));
|
|
30433
30708
|
ordered.push(...relocated.filter((l) => bandOf(l) === b && isLndareFill(l)).map(asBaseTan));
|
|
30709
|
+
ordered.push(...relocated.filter((l) => bandOf(l) === b && isLakareFill(l)));
|
|
30434
30710
|
}
|
|
30435
30711
|
const findRest = (id) => restAbove.findIndex((l) => l.id === id);
|
|
30436
|
-
const
|
|
30437
|
-
const pairAt = shoreAt !== -1 ? shoreAt : findRest("chrtbase-inland-hatch");
|
|
30712
|
+
const pairAt = findRest("chrtbase-inland-water");
|
|
30438
30713
|
let restFinal = restAbove;
|
|
30439
30714
|
if (pairAt !== -1) {
|
|
30440
30715
|
const repaint = [];
|
|
30441
|
-
|
|
30442
|
-
|
|
30443
|
-
|
|
30444
|
-
|
|
30445
|
-
|
|
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 = {
|
|
30446
30733
|
...l,
|
|
30447
|
-
id: `${src.id}-overpair`,
|
|
30448
|
-
filter: ["all", src.filter,
|
|
30449
|
-
metadata: meta
|
|
30734
|
+
id: `${src.id}-overpair-domBy${D}`,
|
|
30735
|
+
filter: ["all", src.filter, dominatedByMinFilter(D)],
|
|
30736
|
+
metadata: meta,
|
|
30737
|
+
maxzoom: dMaxzoom
|
|
30450
30738
|
};
|
|
30451
|
-
delete
|
|
30452
|
-
|
|
30453
|
-
|
|
30454
|
-
|
|
30455
|
-
|
|
30456
|
-
|
|
30457
|
-
|
|
30458
|
-
const domVariant = {
|
|
30459
|
-
...l,
|
|
30460
|
-
id: `${src.id}-overpair-domBy${D}`,
|
|
30461
|
-
filter: ["all", src.filter, dominatedByMinFilter(D)],
|
|
30462
|
-
metadata: meta,
|
|
30463
|
-
maxzoom: dMaxzoom
|
|
30464
|
-
};
|
|
30465
|
-
delete domVariant.minzoom;
|
|
30466
|
-
repaint.push(domVariant);
|
|
30467
|
-
}
|
|
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);
|
|
30468
30746
|
}
|
|
30469
30747
|
if (parseInt(b, 10) >= FINE_LAND_REPAINT_MIN_BAND) {
|
|
30470
30748
|
for (const l of relocated.filter(
|
|
@@ -30480,6 +30758,9 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
30480
30758
|
repaint.push(landClone);
|
|
30481
30759
|
}
|
|
30482
30760
|
}
|
|
30761
|
+
for (const l of relocated.filter((x) => bandOf(x) === b && isLakareFill(x))) {
|
|
30762
|
+
pushCascadeClones(l, b);
|
|
30763
|
+
}
|
|
30483
30764
|
}
|
|
30484
30765
|
const isCoalneLine = (l) => /^s52-COALNE-line-band\d(-domBy\d)?$/.test(l.id);
|
|
30485
30766
|
for (const l of layers.filter(isCoalneLine)) {
|
|
@@ -30503,9 +30784,9 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
30503
30784
|
repaint.push(terminal);
|
|
30504
30785
|
}
|
|
30505
30786
|
restFinal = [
|
|
30506
|
-
...restAbove.slice(0, pairAt
|
|
30787
|
+
...restAbove.slice(0, pairAt),
|
|
30507
30788
|
...repaint,
|
|
30508
|
-
...restAbove.slice(pairAt
|
|
30789
|
+
...restAbove.slice(pairAt)
|
|
30509
30790
|
];
|
|
30510
30791
|
}
|
|
30511
30792
|
{
|
|
@@ -30554,6 +30835,7 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
30554
30835
|
emitted.push(layer);
|
|
30555
30836
|
}
|
|
30556
30837
|
}
|
|
30838
|
+
const finalLayers = drapedFirst(emitted);
|
|
30557
30839
|
const style = {
|
|
30558
30840
|
version: 8,
|
|
30559
30841
|
name: `CHRT ${SYMBOL_SET_LABELS[symbolSet]} ${PALETTE_NAMES[palette]}${opts.chartMode ? ` \u2014 chart:${opts.chartMode.band}` : ""}`,
|
|
@@ -30562,7 +30844,7 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
30562
30844
|
"chrt:symbol-set": symbolSet,
|
|
30563
30845
|
"chrt:color-table": palette,
|
|
30564
30846
|
"chrt:generated-at": (/* @__PURE__ */ new Date()).toISOString(),
|
|
30565
|
-
"chrt:layer-count":
|
|
30847
|
+
"chrt:layer-count": finalLayers.length,
|
|
30566
30848
|
"chrt:land": Boolean(opts.land),
|
|
30567
30849
|
"chrt:relief": Boolean(opts.relief),
|
|
30568
30850
|
"chrt:base": Boolean(opts.base),
|
|
@@ -30602,7 +30884,7 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
30602
30884
|
// upstream glyph byte-identical and composite only those six in from
|
|
30603
30885
|
// Noto Sans; see scripts/generate-glyphs.mjs.
|
|
30604
30886
|
glyphs: `${glyphsBase}/{fontstack}/{range}.pbf`,
|
|
30605
|
-
layers:
|
|
30887
|
+
layers: finalLayers
|
|
30606
30888
|
};
|
|
30607
30889
|
if (symbolSet === "s101") return applyS101SymbolSet(style);
|
|
30608
30890
|
if (symbolSet === "chrt") return applyChrtSymbolSet(style, palette);
|
|
@@ -31430,8 +31712,16 @@ export {
|
|
|
31430
31712
|
ALL_WATER_BODY_LAYERS,
|
|
31431
31713
|
AREA_QUERY_LAYERS,
|
|
31432
31714
|
ATON_EXPRESSION_CLASSES,
|
|
31715
|
+
BAND_CODE_ALIASES,
|
|
31433
31716
|
BAND_DRAW_ORDER,
|
|
31434
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,
|
|
31435
31725
|
BASE_MAXZOOM,
|
|
31436
31726
|
BASE_MINZOOM,
|
|
31437
31727
|
BASE_REGION,
|
|
@@ -31464,6 +31754,7 @@ export {
|
|
|
31464
31754
|
DEFAULT_TILE_BASE_URL,
|
|
31465
31755
|
DEFAULT_TILE_REGION,
|
|
31466
31756
|
DEFAULT_WATER_BODY_SETTINGS,
|
|
31757
|
+
DRAPEABLE_LAYER_TYPES,
|
|
31467
31758
|
INSPECT_CLASS_RANK,
|
|
31468
31759
|
INSPECT_RANK_DEVIATIONS,
|
|
31469
31760
|
InteractClient,
|
|
@@ -31472,6 +31763,11 @@ export {
|
|
|
31472
31763
|
LABEL_BEHAVIOR,
|
|
31473
31764
|
LABEL_LAYER_GROUPS,
|
|
31474
31765
|
LABEL_PRIORITY,
|
|
31766
|
+
LAMP_CORE_LAYER_ID,
|
|
31767
|
+
LAMP_GLOW_LAYER_ID,
|
|
31768
|
+
LAMP_LAYER_IDS,
|
|
31769
|
+
LAMP_TICK_MS,
|
|
31770
|
+
LAND_ATTRIBUTION,
|
|
31475
31771
|
LAND_BUILD_MAXZOOM,
|
|
31476
31772
|
LAND_REGION,
|
|
31477
31773
|
NAUTICAL_SYMBOLS,
|
|
@@ -31487,7 +31783,10 @@ export {
|
|
|
31487
31783
|
RASTER_ATON_CLASSES,
|
|
31488
31784
|
RASTER_GLYPH_NAMES,
|
|
31489
31785
|
RASTER_ICON_PREFIX,
|
|
31786
|
+
RELIEF_ATTRIBUTION,
|
|
31787
|
+
RELIEF_ENCODING,
|
|
31490
31788
|
RELIEF_REGION,
|
|
31789
|
+
RELIEF_TILE_SIZE,
|
|
31491
31790
|
S101_AREA_FILL_NAMES,
|
|
31492
31791
|
S101_CHART_SECTIONS,
|
|
31493
31792
|
S101_FEATURES,
|
|
@@ -31507,6 +31806,8 @@ export {
|
|
|
31507
31806
|
S52_LOOKUP,
|
|
31508
31807
|
S52_NIGHT,
|
|
31509
31808
|
S52_PATTERN_SPRITE_PREFIX,
|
|
31809
|
+
SIGPER_TOLERANCE_SECONDS,
|
|
31810
|
+
SIGSEQ_MAX_TERMS,
|
|
31510
31811
|
SOUNDING_LAYERS,
|
|
31511
31812
|
SYMBOL_FAMILIES,
|
|
31512
31813
|
SYMBOL_FAMILY_META,
|
|
@@ -31516,6 +31817,7 @@ export {
|
|
|
31516
31817
|
TileTokenError,
|
|
31517
31818
|
WATER_BODY_LAYER_GROUPS,
|
|
31518
31819
|
WeatherEndpoint,
|
|
31820
|
+
applyBaseBundle,
|
|
31519
31821
|
applyChrtSymbolSet,
|
|
31520
31822
|
applyColorTheme,
|
|
31521
31823
|
applyCustomStyleSettings,
|
|
@@ -31528,6 +31830,7 @@ export {
|
|
|
31528
31830
|
buildFlashPattern,
|
|
31529
31831
|
buildLabelLayer,
|
|
31530
31832
|
buildLakeTierExpression,
|
|
31833
|
+
buildLampLayers,
|
|
31531
31834
|
buildLandAreaTierExpression,
|
|
31532
31835
|
buildLandRegionTierExpression,
|
|
31533
31836
|
buildLayersForClass,
|
|
@@ -31540,6 +31843,8 @@ export {
|
|
|
31540
31843
|
buildSectorsFromFeatures,
|
|
31541
31844
|
buildSources,
|
|
31542
31845
|
candidateRank,
|
|
31846
|
+
cellBandCharacters,
|
|
31847
|
+
cellBandMatch,
|
|
31543
31848
|
chrtAtonTintExpression,
|
|
31544
31849
|
chrtLightTintExpression,
|
|
31545
31850
|
collectCandidates,
|
|
@@ -31554,6 +31859,8 @@ export {
|
|
|
31554
31859
|
depareColor,
|
|
31555
31860
|
depareFillExpression,
|
|
31556
31861
|
deriveMarinerName,
|
|
31862
|
+
drapeHalves,
|
|
31863
|
+
drapedFirst,
|
|
31557
31864
|
extractNavAidDesignation,
|
|
31558
31865
|
featureIdentity,
|
|
31559
31866
|
generateCustomStyle,
|
|
@@ -31586,6 +31893,7 @@ export {
|
|
|
31586
31893
|
hazardColor,
|
|
31587
31894
|
hazardFillExpression,
|
|
31588
31895
|
iconExpressionForFamily,
|
|
31896
|
+
isDrapeable,
|
|
31589
31897
|
isLabelLayerVisible,
|
|
31590
31898
|
isNavAidLayer,
|
|
31591
31899
|
isNavAidLayerVisible,
|
|
@@ -31593,6 +31901,8 @@ export {
|
|
|
31593
31901
|
isVisibleInMode,
|
|
31594
31902
|
isWaterBodyLayerVisible,
|
|
31595
31903
|
labelSortKey,
|
|
31904
|
+
lampRhythmColour,
|
|
31905
|
+
lampRhythmOpacity,
|
|
31596
31906
|
lateralColor,
|
|
31597
31907
|
lateralFillExpression,
|
|
31598
31908
|
lightColor,
|
|
@@ -31602,6 +31912,7 @@ export {
|
|
|
31602
31912
|
normalizeTheme,
|
|
31603
31913
|
paginate,
|
|
31604
31914
|
parseListCodes,
|
|
31915
|
+
parseSigseqTerms,
|
|
31605
31916
|
parseSymbolFamily,
|
|
31606
31917
|
parseWebhook,
|
|
31607
31918
|
patternSpriteId,
|
|
@@ -31616,12 +31927,14 @@ export {
|
|
|
31616
31927
|
resolveS101Color,
|
|
31617
31928
|
resolveStyle,
|
|
31618
31929
|
resolveTargetClass,
|
|
31930
|
+
rhythmProperties,
|
|
31619
31931
|
s101ArtworkProvenance,
|
|
31620
31932
|
sanitizeCustomStyleConfig,
|
|
31621
31933
|
scaleSizeValue,
|
|
31622
31934
|
scaminToMinZoom,
|
|
31623
31935
|
sectorLimitToArcBearing,
|
|
31624
31936
|
styleModeBand,
|
|
31937
|
+
terrainStackCount,
|
|
31625
31938
|
tileTemplate,
|
|
31626
31939
|
uncoveredSectorArcs,
|
|
31627
31940
|
withinBboxFilter,
|