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