@chrtco/sdk 0.8.1 → 0.9.0
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 +21 -0
- package/dist/index.cjs +3231 -189
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +931 -112
- package/dist/index.d.ts +931 -112
- package/dist/index.js +3189 -189
- package/dist/index.js.map +1 -1
- package/package.json +3 -20
package/dist/index.cjs
CHANGED
|
@@ -20,8 +20,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
ALL_NAV_AID_LAYERS: () => ALL_NAV_AID_LAYERS,
|
|
24
|
+
ALL_WATER_BODY_LAYERS: () => ALL_WATER_BODY_LAYERS,
|
|
25
|
+
AREA_QUERY_LAYERS: () => AREA_QUERY_LAYERS,
|
|
23
26
|
BAND_DRAW_ORDER: () => BAND_DRAW_ORDER,
|
|
24
27
|
BAND_PRIORITY_RANGES: () => BAND_PRIORITY_RANGES,
|
|
28
|
+
BRIDGE_QUERY_LAYERS: () => BRIDGE_QUERY_LAYERS,
|
|
25
29
|
BUILD_MAXZOOM: () => BUILD_MAXZOOM,
|
|
26
30
|
BUILD_MINZOOM: () => BUILD_MINZOOM,
|
|
27
31
|
CHART_BAND_CODES: () => CHART_BAND_CODES,
|
|
@@ -29,12 +33,25 @@ __export(index_exports, {
|
|
|
29
33
|
CHART_SECTIONS: () => CHART_SECTIONS,
|
|
30
34
|
Chrt: () => Chrt,
|
|
31
35
|
ChrtError: () => ChrtError,
|
|
36
|
+
DEFAULT_CUSTOM_STYLE_CONFIG: () => DEFAULT_CUSTOM_STYLE_CONFIG,
|
|
37
|
+
DEFAULT_LABEL_SETTINGS: () => DEFAULT_LABEL_SETTINGS,
|
|
38
|
+
DEFAULT_NAV_AID_SETTINGS: () => DEFAULT_NAV_AID_SETTINGS,
|
|
32
39
|
DEFAULT_REGION: () => DEFAULT_REGION,
|
|
40
|
+
DEFAULT_SOUNDING_SETTINGS: () => DEFAULT_SOUNDING_SETTINGS,
|
|
33
41
|
DEFAULT_TILE_BASE_URL: () => DEFAULT_TILE_BASE_URL,
|
|
34
42
|
DEFAULT_TILE_REGION: () => DEFAULT_TILE_REGION,
|
|
43
|
+
DEFAULT_WATER_BODY_SETTINGS: () => DEFAULT_WATER_BODY_SETTINGS,
|
|
44
|
+
InteractClient: () => InteractClient,
|
|
45
|
+
InteractError: () => InteractError,
|
|
35
46
|
LABELED_LAYERS: () => LABELED_LAYERS,
|
|
36
47
|
LABEL_BEHAVIOR: () => LABEL_BEHAVIOR,
|
|
48
|
+
LABEL_LAYER_GROUPS: () => LABEL_LAYER_GROUPS,
|
|
49
|
+
LABEL_PRIORITY: () => LABEL_PRIORITY,
|
|
37
50
|
NAUTICAL_SYMBOLS: () => NAUTICAL_SYMBOLS,
|
|
51
|
+
NAV_AID_LAYER_GROUPS: () => NAV_AID_LAYER_GROUPS,
|
|
52
|
+
NAV_AID_QUERY_LAYERS: () => NAV_AID_QUERY_LAYERS,
|
|
53
|
+
NON_SOLAS_DISCLAIMER: () => NON_SOLAS_DISCLAIMER,
|
|
54
|
+
NON_SOLAS_DISCLAIMER_SHORT: () => NON_SOLAS_DISCLAIMER_SHORT,
|
|
38
55
|
OfflineError: () => OfflineError,
|
|
39
56
|
OfflineStyleError: () => OfflineStyleError,
|
|
40
57
|
S101_CHART_SECTIONS: () => S101_CHART_SECTIONS,
|
|
@@ -47,21 +64,34 @@ __export(index_exports, {
|
|
|
47
64
|
S52_DUSK: () => S52_DUSK,
|
|
48
65
|
S52_LOOKUP: () => S52_LOOKUP,
|
|
49
66
|
S52_NIGHT: () => S52_NIGHT,
|
|
67
|
+
SOUNDING_LAYERS: () => SOUNDING_LAYERS,
|
|
50
68
|
SYMBOL_GROUPS: () => SYMBOL_GROUPS,
|
|
51
69
|
SYMBOL_MAP: () => SYMBOL_MAP,
|
|
52
70
|
TileAuth: () => TileAuth,
|
|
53
71
|
TileTokenError: () => TileTokenError,
|
|
72
|
+
WATER_BODY_LAYER_GROUPS: () => WATER_BODY_LAYER_GROUPS,
|
|
54
73
|
applyColorTheme: () => applyColorTheme,
|
|
74
|
+
applyCustomStyleSettings: () => applyCustomStyleSettings,
|
|
55
75
|
bandFilterExpression: () => bandFilterExpression,
|
|
76
|
+
buildFlashPattern: () => buildFlashPattern,
|
|
56
77
|
buildLabelLayer: () => buildLabelLayer,
|
|
57
78
|
buildLayersForClass: () => buildLayersForClass,
|
|
58
79
|
buildOfflineStyle: () => buildOfflineStyle,
|
|
80
|
+
buildSectorArcGeoJSON: () => buildSectorArcGeoJSON,
|
|
81
|
+
buildSectorArcsFC: () => buildSectorArcsFC,
|
|
82
|
+
buildSectorsFromFeatures: () => buildSectorsFromFeatures,
|
|
59
83
|
buildSources: () => buildSources,
|
|
60
84
|
combineWithBandFilter: () => combineWithBandFilter,
|
|
61
85
|
daymarkColor: () => daymarkColor,
|
|
62
86
|
decimateFeatures: () => decimateFeatures,
|
|
87
|
+
decodeAreaFeatureProps: () => decodeAreaFeatureProps,
|
|
88
|
+
decodeBridgeProps: () => decodeBridgeProps,
|
|
89
|
+
decodeNavAidProps: () => decodeNavAidProps,
|
|
63
90
|
depareColor: () => depareColor,
|
|
64
91
|
depareFillExpression: () => depareFillExpression,
|
|
92
|
+
deriveMarinerName: () => deriveMarinerName,
|
|
93
|
+
extractNavAidDesignation: () => extractNavAidDesignation,
|
|
94
|
+
generateCustomStyle: () => generateCustomStyle,
|
|
65
95
|
generateSpriteManifest: () => generateSpriteManifest,
|
|
66
96
|
generateSpriteSheet: () => generateSpriteSheet,
|
|
67
97
|
generateStyle: () => generateStyle,
|
|
@@ -76,6 +106,7 @@ __export(index_exports, {
|
|
|
76
106
|
getIconExpression: () => getIconExpression,
|
|
77
107
|
getLabelBehavior: () => getLabelBehavior,
|
|
78
108
|
getLabelConfig: () => getLabelConfig,
|
|
109
|
+
getLabelPriority: () => getLabelPriority,
|
|
79
110
|
getLookupEntry: () => getLookupEntry,
|
|
80
111
|
getMaxCellsForZoom: () => getMaxCellsForZoom,
|
|
81
112
|
getOrderedClasses: () => getOrderedClasses,
|
|
@@ -84,22 +115,33 @@ __export(index_exports, {
|
|
|
84
115
|
getStyleStats: () => getStyleStats,
|
|
85
116
|
getSymbolSvgString: () => getSymbolSvgString,
|
|
86
117
|
getThemedS52Svg: () => getThemedS52Svg,
|
|
118
|
+
getTopmarkMeaning: () => getTopmarkMeaning,
|
|
87
119
|
hazardColor: () => hazardColor,
|
|
88
120
|
hazardFillExpression: () => hazardFillExpression,
|
|
121
|
+
isLabelLayerVisible: () => isLabelLayerVisible,
|
|
89
122
|
isNavAidLayer: () => isNavAidLayer,
|
|
123
|
+
isNavAidLayerVisible: () => isNavAidLayerVisible,
|
|
124
|
+
isSoundingLayerVisible: () => isSoundingLayerVisible,
|
|
90
125
|
isVisibleInMode: () => isVisibleInMode,
|
|
126
|
+
isWaterBodyLayerVisible: () => isWaterBodyLayerVisible,
|
|
127
|
+
labelSortKey: () => labelSortKey,
|
|
91
128
|
lateralColor: () => lateralColor,
|
|
92
129
|
lateralFillExpression: () => lateralFillExpression,
|
|
93
130
|
lightColor: () => lightColor,
|
|
94
131
|
lightFillExpression: () => lightFillExpression,
|
|
132
|
+
normalizeTheme: () => normalizeTheme,
|
|
95
133
|
paginate: () => paginate,
|
|
134
|
+
parseListCodes: () => parseListCodes,
|
|
96
135
|
parseWebhook: () => parseWebhook,
|
|
97
136
|
preprocessLights: () => preprocessLights,
|
|
98
137
|
preprocessNavAids: () => preprocessNavAids,
|
|
99
138
|
preprocessSoundings: () => preprocessSoundings,
|
|
100
139
|
preprocessWaterBodies: () => preprocessWaterBodies,
|
|
101
140
|
resolveColor: () => resolveColor,
|
|
141
|
+
resolveCustomTheme: () => resolveCustomTheme,
|
|
102
142
|
resolveStyle: () => resolveStyle,
|
|
143
|
+
sanitizeCustomStyleConfig: () => sanitizeCustomStyleConfig,
|
|
144
|
+
scaleSizeValue: () => scaleSizeValue,
|
|
103
145
|
scaminToMinZoom: () => scaminToMinZoom,
|
|
104
146
|
withinBboxFilter: () => withinBboxFilter,
|
|
105
147
|
worldMinusBboxPolygon: () => worldMinusBboxPolygon
|
|
@@ -243,6 +285,7 @@ var TileTokenError = class extends Error {
|
|
|
243
285
|
}
|
|
244
286
|
};
|
|
245
287
|
var TILE_URL_PATH = /\/v1\/tiles\/[A-Za-z0-9_]+(?:\/[a-z][a-z0-9-]*)?\/\d+\/\d+\/\d+\.mvt(?:\?.*)?$/i;
|
|
288
|
+
var STYLE_URL_PATH = /\/v1\/styles\/[A-Za-z0-9_]+\/style\.json(?:\?.*)?$/i;
|
|
246
289
|
var REFRESH_MARGIN_SECONDS = 30;
|
|
247
290
|
var STALE_GRACE_SECONDS = 1;
|
|
248
291
|
var MINT_RETRY_DELAYS_MS = [500, 1e3, 2e3];
|
|
@@ -386,17 +429,22 @@ var TileAuth = class {
|
|
|
386
429
|
}
|
|
387
430
|
/**
|
|
388
431
|
* Produce a MapLibre / Mapbox GL `transformRequest`. Attaches
|
|
389
|
-
* `Authorization: Bearer <jwt>` only to CHRT tile URLs
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
432
|
+
* `Authorization: Bearer <jwt>` only to CHRT tile URLs and the CHRT
|
|
433
|
+
* served-style URL (`/v1/styles/{region}/style.json`); sprite, glyph, and
|
|
434
|
+
* third-party URLs pass through untouched (never leak the token to
|
|
435
|
+
* third-party CDNs). Kicks off a background mint immediately so the cache
|
|
436
|
+
* warms within one round-trip; the first tile or two may go
|
|
437
|
+
* unauthenticated and be retried by the renderer. When pointing a map's
|
|
438
|
+
* `style` at the served style URL, obtain it via `chrt.styleUrl()` — it
|
|
439
|
+
* awaits the mint, so the style request (which MapLibre does NOT retry)
|
|
440
|
+
* is guaranteed to carry auth.
|
|
394
441
|
*/
|
|
395
442
|
transformRequest(region) {
|
|
396
443
|
const r = region ?? this.defaultRegion;
|
|
397
444
|
void this.getToken(r).catch(() => void 0);
|
|
398
445
|
return (url) => {
|
|
399
|
-
if (!url.startsWith(this.tileBaseUrl)
|
|
446
|
+
if (!url.startsWith(this.tileBaseUrl)) return void 0;
|
|
447
|
+
if (!TILE_URL_PATH.test(url) && !STYLE_URL_PATH.test(url)) return void 0;
|
|
400
448
|
const token = this.peek(r);
|
|
401
449
|
if (!token) return { url };
|
|
402
450
|
return { url, headers: { Authorization: `Bearer ${token}` } };
|
|
@@ -434,23 +482,67 @@ var TileAuth = class {
|
|
|
434
482
|
|
|
435
483
|
// src/endpoints/tiles.ts
|
|
436
484
|
var TilesEndpoint = class {
|
|
437
|
-
constructor(http, opts) {
|
|
485
|
+
constructor(http, tileAuth, opts) {
|
|
438
486
|
this.http = http;
|
|
487
|
+
this.tileAuth = tileAuth;
|
|
439
488
|
this.region = opts?.region ?? DEFAULT_TILE_REGION;
|
|
440
489
|
this.tileBaseUrl = (opts?.tileBaseUrl ?? DEFAULT_TILE_BASE_URL).replace(/\/+$/, "");
|
|
441
490
|
}
|
|
442
491
|
region;
|
|
443
492
|
tileBaseUrl;
|
|
493
|
+
styleEndpoint(theme) {
|
|
494
|
+
const query2 = theme ? `?theme=${theme}` : "";
|
|
495
|
+
return `${this.tileBaseUrl}/v1/styles/${this.region}/style.json${query2}`;
|
|
496
|
+
}
|
|
444
497
|
/**
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
*
|
|
498
|
+
* URL of the served S-52 style for the configured region — the CHRTstyle
|
|
499
|
+
* Style API (`GET /v1/styles/{region}/style.json`). The served document
|
|
500
|
+
* honors the API key's bound custom style (a Dev+ feature configured at
|
|
501
|
+
* platform → Map Styles); Free keys and unbound keys get the default
|
|
502
|
+
* themes, identical to the locally generated `chrt.style()`.
|
|
503
|
+
*
|
|
504
|
+
* The endpoint is JWT-gated, so pair the URL with
|
|
505
|
+
* `chrt.transformRequest()`, which attaches the Bearer token to both the
|
|
506
|
+
* style request and its tile requests. This method is async because it
|
|
507
|
+
* pre-mints the tile token, guaranteeing the auth cache is warm before
|
|
508
|
+
* MapLibre fetches the URL.
|
|
509
|
+
*
|
|
510
|
+
* @example
|
|
511
|
+
* const map = new maplibregl.Map({
|
|
512
|
+
* container: 'map',
|
|
513
|
+
* style: await chrt.styleUrl({ theme: 'dusk' }),
|
|
514
|
+
* transformRequest: chrt.transformRequest(),
|
|
515
|
+
* });
|
|
449
516
|
*/
|
|
450
|
-
styleUrl() {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
517
|
+
async styleUrl(opts = {}) {
|
|
518
|
+
await this.tileAuth.getToken(this.region);
|
|
519
|
+
return this.styleEndpoint(opts.theme);
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Fetch the served S-52 style as a MapLibre style object (see
|
|
523
|
+
* {@link styleUrl} for what "served" means). Prefer this over `styleUrl()`
|
|
524
|
+
* when you want to inspect or mutate the document, or when you can't use
|
|
525
|
+
* `transformRequest` for the style request itself.
|
|
526
|
+
*
|
|
527
|
+
* Falls back nowhere: a network or auth failure throws (`ChrtError` /
|
|
528
|
+
* `TileTokenError`). For an offline-capable local fallback, use
|
|
529
|
+
* `chrt.style()` — the client-side generator.
|
|
530
|
+
*/
|
|
531
|
+
async fetchStyle(opts = {}) {
|
|
532
|
+
const token = await this.tileAuth.getToken(this.region);
|
|
533
|
+
const res = await fetch(this.styleEndpoint(opts.theme), {
|
|
534
|
+
headers: { authorization: `Bearer ${token.token}` },
|
|
535
|
+
signal: opts.signal
|
|
536
|
+
});
|
|
537
|
+
if (!res.ok) {
|
|
538
|
+
let body;
|
|
539
|
+
try {
|
|
540
|
+
body = await res.json();
|
|
541
|
+
} catch {
|
|
542
|
+
}
|
|
543
|
+
throw new ChrtError(res.status, body);
|
|
544
|
+
}
|
|
545
|
+
return await res.json();
|
|
454
546
|
}
|
|
455
547
|
/**
|
|
456
548
|
* Vector tile URL template for the configured region, served by the
|
|
@@ -1254,51 +1346,2409 @@ var OfflineEndpoint = class {
|
|
|
1254
1346
|
{ resumable: true, bytes_written: bytesDownloaded }
|
|
1255
1347
|
);
|
|
1256
1348
|
}
|
|
1257
|
-
emit(true);
|
|
1258
|
-
const computedSha = await fs.sha256(partialPath);
|
|
1259
|
-
if (computedSha.toLowerCase() !== bundle.sha256) {
|
|
1260
|
-
await fs.unlink(partialPath);
|
|
1261
|
-
throw new OfflineError(
|
|
1262
|
-
"download_corrupt",
|
|
1263
|
-
`SHA-256 mismatch for ${region}: expected ${bundle.sha256}, got ${computedSha}`,
|
|
1264
|
-
void 0,
|
|
1265
|
-
{ expected: bundle.sha256, actual: computedSha }
|
|
1349
|
+
emit(true);
|
|
1350
|
+
const computedSha = await fs.sha256(partialPath);
|
|
1351
|
+
if (computedSha.toLowerCase() !== bundle.sha256) {
|
|
1352
|
+
await fs.unlink(partialPath);
|
|
1353
|
+
throw new OfflineError(
|
|
1354
|
+
"download_corrupt",
|
|
1355
|
+
`SHA-256 mismatch for ${region}: expected ${bundle.sha256}, got ${computedSha}`,
|
|
1356
|
+
void 0,
|
|
1357
|
+
{ expected: bundle.sha256, actual: computedSha }
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1360
|
+
await fs.rename(partialPath, path);
|
|
1361
|
+
await this.persistManifestEntry(region, bundle, path, fs);
|
|
1362
|
+
return {
|
|
1363
|
+
path,
|
|
1364
|
+
bytes: bundle.bytes,
|
|
1365
|
+
sha256: bundle.sha256,
|
|
1366
|
+
build_id: bundle.build_id,
|
|
1367
|
+
build_tag: bundle.build_tag,
|
|
1368
|
+
resumed
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
async persistManifestEntry(region, bundle, path, fs) {
|
|
1372
|
+
if (!this.manifestPath) return;
|
|
1373
|
+
const manifest = await readManifest(this.manifestPath, fs);
|
|
1374
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
1375
|
+
manifest.regions[region] = {
|
|
1376
|
+
path,
|
|
1377
|
+
bytes: bundle.bytes,
|
|
1378
|
+
sha256: bundle.sha256,
|
|
1379
|
+
build_id: bundle.build_id,
|
|
1380
|
+
build_tag: bundle.build_tag,
|
|
1381
|
+
downloaded_at: nowSec,
|
|
1382
|
+
last_accessed_at: nowSec
|
|
1383
|
+
};
|
|
1384
|
+
await writeManifest(this.manifestPath, fs, manifest);
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
function dirname(p) {
|
|
1388
|
+
const i = p.lastIndexOf("/");
|
|
1389
|
+
const j = p.lastIndexOf("\\");
|
|
1390
|
+
const k = Math.max(i, j);
|
|
1391
|
+
if (k <= 0) return ".";
|
|
1392
|
+
return p.slice(0, k);
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
// src/interact/decode.ts
|
|
1396
|
+
var LITCHR_TABLE = {
|
|
1397
|
+
1: { name: "Fixed", abbr: "F" },
|
|
1398
|
+
2: { name: "Flashing", abbr: "Fl" },
|
|
1399
|
+
3: { name: "Long Flashing", abbr: "LFl" },
|
|
1400
|
+
4: { name: "Occulting", abbr: "Oc" },
|
|
1401
|
+
5: { name: "Isophase", abbr: "Iso" },
|
|
1402
|
+
6: { name: "Short Flashing", abbr: "S.Fl" },
|
|
1403
|
+
7: { name: "Short Long Flashing", abbr: "S.LFl" },
|
|
1404
|
+
8: { name: "Alternating Occulting", abbr: "Al.Oc" },
|
|
1405
|
+
9: { name: "Alternating Long Flashing", abbr: "Al.LFl" },
|
|
1406
|
+
10: { name: "Alternating Flashing", abbr: "Al.Fl" },
|
|
1407
|
+
11: { name: "Alternating Group", abbr: "Al.Gp" },
|
|
1408
|
+
12: { name: "Morse", abbr: "Mo" },
|
|
1409
|
+
13: { name: "Quick", abbr: "Q" },
|
|
1410
|
+
14: { name: "Very Quick", abbr: "VQ" },
|
|
1411
|
+
15: { name: "Ultra Quick", abbr: "UQ" },
|
|
1412
|
+
16: { name: "Alternating", abbr: "Al" },
|
|
1413
|
+
17: { name: "Alternating Fixed/Flash", abbr: "Al.F.Fl" },
|
|
1414
|
+
25: { name: "Quick + Long Flash", abbr: "Q+LFl" },
|
|
1415
|
+
26: { name: "Very Quick + Long Flash", abbr: "VQ+LFl" },
|
|
1416
|
+
27: { name: "Ultra Quick + Long Flash", abbr: "UQ+LFl" },
|
|
1417
|
+
28: { name: "Alternating", abbr: "Al" },
|
|
1418
|
+
29: { name: "Fixed + Long Flash", abbr: "F.LFl" }
|
|
1419
|
+
};
|
|
1420
|
+
var COLOUR_TABLE = {
|
|
1421
|
+
1: { name: "White", abbr: "W", hex: "#ffffff" },
|
|
1422
|
+
2: { name: "Black", abbr: "B", hex: "#333333" },
|
|
1423
|
+
3: { name: "Red", abbr: "R", hex: "#ef4444" },
|
|
1424
|
+
4: { name: "Green", abbr: "G", hex: "#22c55e" },
|
|
1425
|
+
5: { name: "Blue", abbr: "Bu", hex: "#3b82f6" },
|
|
1426
|
+
6: { name: "Yellow", abbr: "Y", hex: "#eab308" },
|
|
1427
|
+
7: { name: "Grey", abbr: "Gr", hex: "#9ca3af" },
|
|
1428
|
+
8: { name: "Brown", abbr: "Bn", hex: "#92400e" },
|
|
1429
|
+
9: { name: "Amber", abbr: "Am", hex: "#f59e0b" },
|
|
1430
|
+
10: { name: "Violet", abbr: "Vi", hex: "#8b5cf6" },
|
|
1431
|
+
11: { name: "Orange", abbr: "Or", hex: "#f97316" },
|
|
1432
|
+
12: { name: "Magenta", abbr: "Mg", hex: "#d946ef" },
|
|
1433
|
+
13: { name: "Pink", abbr: "Pk", hex: "#f472b6" }
|
|
1434
|
+
};
|
|
1435
|
+
var BOYSHP_TABLE = {
|
|
1436
|
+
1: "Conical (nun)",
|
|
1437
|
+
2: "Can (cylindrical)",
|
|
1438
|
+
3: "Spherical",
|
|
1439
|
+
4: "Pillar",
|
|
1440
|
+
5: "Spar",
|
|
1441
|
+
6: "Barrel",
|
|
1442
|
+
7: "Super buoy",
|
|
1443
|
+
8: "Ice buoy"
|
|
1444
|
+
};
|
|
1445
|
+
var CATLAM_TABLE = {
|
|
1446
|
+
1: "Port",
|
|
1447
|
+
2: "Starboard",
|
|
1448
|
+
3: "Preferred channel to starboard",
|
|
1449
|
+
4: "Preferred channel to port"
|
|
1450
|
+
};
|
|
1451
|
+
var CATCAM_TABLE = {
|
|
1452
|
+
1: "North cardinal",
|
|
1453
|
+
2: "East cardinal",
|
|
1454
|
+
3: "South cardinal",
|
|
1455
|
+
4: "West cardinal"
|
|
1456
|
+
};
|
|
1457
|
+
var CATLMK_TABLE = {
|
|
1458
|
+
1: "Cairn",
|
|
1459
|
+
2: "Cemetery",
|
|
1460
|
+
3: "Chimney",
|
|
1461
|
+
4: "Dish aerial",
|
|
1462
|
+
5: "Flagstaff",
|
|
1463
|
+
6: "Flare stack",
|
|
1464
|
+
7: "Mast",
|
|
1465
|
+
8: "Windsock",
|
|
1466
|
+
9: "Monument",
|
|
1467
|
+
10: "Column / pillar",
|
|
1468
|
+
11: "Memorial plaque",
|
|
1469
|
+
12: "Obelisk",
|
|
1470
|
+
13: "Statue",
|
|
1471
|
+
14: "Cross",
|
|
1472
|
+
15: "Dome",
|
|
1473
|
+
16: "Radar scanner",
|
|
1474
|
+
17: "Tower",
|
|
1475
|
+
18: "Windmill",
|
|
1476
|
+
19: "Wind turbine",
|
|
1477
|
+
20: "Spire"
|
|
1478
|
+
};
|
|
1479
|
+
var CATFOG_TABLE = {
|
|
1480
|
+
1: "Explosive",
|
|
1481
|
+
2: "Diaphone",
|
|
1482
|
+
3: "Siren",
|
|
1483
|
+
4: "Nautophone",
|
|
1484
|
+
5: "Bell",
|
|
1485
|
+
6: "Whistle",
|
|
1486
|
+
7: "Reed",
|
|
1487
|
+
8: "Gong",
|
|
1488
|
+
9: "Horn"
|
|
1489
|
+
};
|
|
1490
|
+
var CATFOG_ABBR_TABLE = {
|
|
1491
|
+
1: "EXPLOS",
|
|
1492
|
+
2: "DIA",
|
|
1493
|
+
3: "SIREN",
|
|
1494
|
+
4: "NAUTO",
|
|
1495
|
+
5: "BELL",
|
|
1496
|
+
6: "WHIS",
|
|
1497
|
+
7: "REED",
|
|
1498
|
+
8: "GONG",
|
|
1499
|
+
9: "HORN"
|
|
1500
|
+
};
|
|
1501
|
+
var TOPSHP_TABLE = {
|
|
1502
|
+
1: "Cone, point up",
|
|
1503
|
+
2: "Cone, point down",
|
|
1504
|
+
3: "Sphere",
|
|
1505
|
+
4: "2 spheres",
|
|
1506
|
+
5: "Cylinder",
|
|
1507
|
+
6: "Board",
|
|
1508
|
+
7: "X-shape",
|
|
1509
|
+
8: "Upright cross",
|
|
1510
|
+
9: "Cube, point up",
|
|
1511
|
+
10: "2 cones, point to point",
|
|
1512
|
+
11: "2 cones, base to base",
|
|
1513
|
+
12: "Rhombus",
|
|
1514
|
+
13: "2 cones, up",
|
|
1515
|
+
14: "2 cones, down",
|
|
1516
|
+
15: "Besom, point up",
|
|
1517
|
+
16: "Besom, point down",
|
|
1518
|
+
17: "Flag",
|
|
1519
|
+
18: "Sphere over rhombus",
|
|
1520
|
+
19: "Square",
|
|
1521
|
+
33: "T-shape"
|
|
1522
|
+
};
|
|
1523
|
+
var STATUS_TABLE = {
|
|
1524
|
+
1: "Permanent",
|
|
1525
|
+
2: "Occasional",
|
|
1526
|
+
3: "Recommended",
|
|
1527
|
+
4: "Not in use",
|
|
1528
|
+
5: "Periodic/intermittent",
|
|
1529
|
+
6: "Reserved",
|
|
1530
|
+
7: "Temporary",
|
|
1531
|
+
8: "Private",
|
|
1532
|
+
9: "Mandatory",
|
|
1533
|
+
11: "Extinguished",
|
|
1534
|
+
12: "Illuminated",
|
|
1535
|
+
13: "Historic",
|
|
1536
|
+
14: "Public",
|
|
1537
|
+
15: "Synchronized",
|
|
1538
|
+
16: "Watched",
|
|
1539
|
+
17: "Un-watched",
|
|
1540
|
+
18: "Existence doubtful"
|
|
1541
|
+
};
|
|
1542
|
+
var MARSYS_TABLE = {
|
|
1543
|
+
1: "IALA-A",
|
|
1544
|
+
2: "IALA-B"
|
|
1545
|
+
};
|
|
1546
|
+
var LAYER_TYPE_MAP = {
|
|
1547
|
+
BOYLAT: "Lateral Buoy",
|
|
1548
|
+
BOYCAR: "Cardinal Buoy",
|
|
1549
|
+
BOYSPP: "Special Purpose Buoy",
|
|
1550
|
+
BOYISD: "Isolated Danger Buoy",
|
|
1551
|
+
BOYSAW: "Safe Water Buoy",
|
|
1552
|
+
BCNLAT: "Lateral Beacon",
|
|
1553
|
+
BCNCAR: "Cardinal Beacon",
|
|
1554
|
+
BCNSPP: "Special Purpose Beacon",
|
|
1555
|
+
BCNISD: "Isolated Danger Beacon",
|
|
1556
|
+
BCNSAW: "Safe Water Beacon",
|
|
1557
|
+
LIGHTS: "Light",
|
|
1558
|
+
DAYMAR: "Daymark",
|
|
1559
|
+
FOGSIG: "Fog Signal",
|
|
1560
|
+
RTPBCN: "Radar Transponder Beacon",
|
|
1561
|
+
RDOSTA: "Radio/Radar Station",
|
|
1562
|
+
TOPMAR: "Topmark",
|
|
1563
|
+
MORFAC: "Mooring Facility",
|
|
1564
|
+
LNDMRK: "Landmark",
|
|
1565
|
+
SISTAT: "Signal Station",
|
|
1566
|
+
PILBOP: "Pilot Boarding Place",
|
|
1567
|
+
HRBFAC: "Harbour Facility",
|
|
1568
|
+
SMCFAC: "Small Craft Facility",
|
|
1569
|
+
CRANES: "Crane",
|
|
1570
|
+
OFSPLF: "Offshore Platform",
|
|
1571
|
+
ACHBRT: "Anchor Berth",
|
|
1572
|
+
DISMAR: "Distance Mark",
|
|
1573
|
+
LITVES: "Light Vessel",
|
|
1574
|
+
LITFLT: "Light Float"
|
|
1575
|
+
};
|
|
1576
|
+
var NAV_AID_QUERY_LAYERS = [
|
|
1577
|
+
"enc-BOYLAT",
|
|
1578
|
+
"enc-BOYCAR",
|
|
1579
|
+
"enc-BOYSPP",
|
|
1580
|
+
"enc-BOYISD",
|
|
1581
|
+
"enc-BOYSAW",
|
|
1582
|
+
"enc-BCNLAT",
|
|
1583
|
+
"enc-BCNCAR",
|
|
1584
|
+
"enc-BCNSPP",
|
|
1585
|
+
"enc-BCNISD",
|
|
1586
|
+
"enc-BCNSAW",
|
|
1587
|
+
"enc-LIGHTS",
|
|
1588
|
+
"enc-LIGHTS-point",
|
|
1589
|
+
"enc-DAYMAR",
|
|
1590
|
+
"enc-FOGSIG",
|
|
1591
|
+
"enc-RTPBCN",
|
|
1592
|
+
"enc-RDOSTA",
|
|
1593
|
+
"enc-TOPMAR",
|
|
1594
|
+
"enc-MORFAC",
|
|
1595
|
+
"enc-MORFAC-point",
|
|
1596
|
+
"enc-LNDMRK",
|
|
1597
|
+
"enc-LNDMRK-point",
|
|
1598
|
+
"enc-SISTAT",
|
|
1599
|
+
"enc-SISTAT-point",
|
|
1600
|
+
"enc-PILBOP",
|
|
1601
|
+
"enc-PILBOP-point",
|
|
1602
|
+
"enc-HRBFAC",
|
|
1603
|
+
"enc-SMCFAC",
|
|
1604
|
+
"enc-CRANES",
|
|
1605
|
+
"enc-CRANES-point",
|
|
1606
|
+
"enc-OFSPLF",
|
|
1607
|
+
"enc-OFSPLF-point",
|
|
1608
|
+
"enc-ACHBRT",
|
|
1609
|
+
"enc-DISMAR",
|
|
1610
|
+
"enc-LITVES",
|
|
1611
|
+
"enc-LITFLT"
|
|
1612
|
+
];
|
|
1613
|
+
var AREA_QUERY_LAYERS = [
|
|
1614
|
+
"enc-DEPARE",
|
|
1615
|
+
"enc-DEPARE-polygon",
|
|
1616
|
+
"enc-OBSTRN",
|
|
1617
|
+
"enc-OBSTRN-polygon",
|
|
1618
|
+
"enc-OBSTRN-point",
|
|
1619
|
+
"enc-WRECKS",
|
|
1620
|
+
"enc-WRECKS-polygon",
|
|
1621
|
+
"enc-WRECKS-point",
|
|
1622
|
+
"enc-RESARE",
|
|
1623
|
+
"enc-RESARE-polygon",
|
|
1624
|
+
"enc-ACHARE",
|
|
1625
|
+
"enc-ACHARE-polygon",
|
|
1626
|
+
"enc-CTNARE",
|
|
1627
|
+
"enc-CTNARE-polygon",
|
|
1628
|
+
"enc-DRGARE",
|
|
1629
|
+
"enc-DRGARE-polygon",
|
|
1630
|
+
"enc-FAIRWY",
|
|
1631
|
+
"enc-FAIRWY-polygon",
|
|
1632
|
+
"enc-DMPGRD",
|
|
1633
|
+
"enc-DMPGRD-polygon",
|
|
1634
|
+
"enc-MIPARE",
|
|
1635
|
+
"enc-MIPARE-polygon",
|
|
1636
|
+
"enc-PRCARE",
|
|
1637
|
+
"enc-PRCARE-polygon",
|
|
1638
|
+
"enc-TSSLPT",
|
|
1639
|
+
"enc-TSSLPT-polygon",
|
|
1640
|
+
"enc-TSELNE",
|
|
1641
|
+
"enc-TSELNE-line",
|
|
1642
|
+
"enc-ISTZNE",
|
|
1643
|
+
"enc-ISTZNE-polygon",
|
|
1644
|
+
"enc-OSPARE",
|
|
1645
|
+
"enc-OSPARE-polygon",
|
|
1646
|
+
"enc-MARCUL",
|
|
1647
|
+
"enc-MARCUL-polygon",
|
|
1648
|
+
"enc-CBLARE",
|
|
1649
|
+
"enc-CBLARE-polygon",
|
|
1650
|
+
"enc-PIPARE",
|
|
1651
|
+
"enc-PIPARE-polygon",
|
|
1652
|
+
// Tier 3: infrastructure lines (BRIDGE handled by BRIDGE_QUERY_LAYERS)
|
|
1653
|
+
"enc-CBLSUB",
|
|
1654
|
+
"enc-CBLSUB-line",
|
|
1655
|
+
"enc-CBLOHD",
|
|
1656
|
+
"enc-CBLOHD-line",
|
|
1657
|
+
"enc-PIPSOL",
|
|
1658
|
+
"enc-PIPSOL-line",
|
|
1659
|
+
"enc-PIPOHD",
|
|
1660
|
+
"enc-PIPOHD-line",
|
|
1661
|
+
"enc-BERTHS",
|
|
1662
|
+
"enc-BERTHS-line",
|
|
1663
|
+
"enc-DAMCON",
|
|
1664
|
+
"enc-DAMCON-line",
|
|
1665
|
+
"enc-FERYRT",
|
|
1666
|
+
"enc-FERYRT-line",
|
|
1667
|
+
"enc-RECTRC",
|
|
1668
|
+
"enc-RECTRC-line",
|
|
1669
|
+
// Tier 4: land/context features
|
|
1670
|
+
"enc-BUISGL",
|
|
1671
|
+
"enc-BUISGL-polygon",
|
|
1672
|
+
"enc-BUISGL-point",
|
|
1673
|
+
"enc-SILTNK",
|
|
1674
|
+
"enc-SILTNK-polygon",
|
|
1675
|
+
"enc-SILTNK-point",
|
|
1676
|
+
"enc-HULKES",
|
|
1677
|
+
"enc-HULKES-polygon",
|
|
1678
|
+
"enc-HULKES-point",
|
|
1679
|
+
"enc-DRYDOC",
|
|
1680
|
+
"enc-DRYDOC-polygon",
|
|
1681
|
+
"enc-FLODOC",
|
|
1682
|
+
"enc-FLODOC-polygon",
|
|
1683
|
+
"enc-PONTON",
|
|
1684
|
+
"enc-PONTON-polygon",
|
|
1685
|
+
"enc-GATCON",
|
|
1686
|
+
"enc-GATCON-polygon",
|
|
1687
|
+
"enc-GATCON-point",
|
|
1688
|
+
"enc-LOGPON",
|
|
1689
|
+
"enc-LOGPON-polygon",
|
|
1690
|
+
"enc-AIRARE",
|
|
1691
|
+
"enc-AIRARE-polygon",
|
|
1692
|
+
"enc-SLCONS",
|
|
1693
|
+
"enc-SLCONS-polygon",
|
|
1694
|
+
"enc-SLCONS-line",
|
|
1695
|
+
"enc-SLCONS-point",
|
|
1696
|
+
// Tier 5: hydrographic context
|
|
1697
|
+
"enc-COALNE",
|
|
1698
|
+
"enc-COALNE-line",
|
|
1699
|
+
"enc-DEPCNT",
|
|
1700
|
+
"enc-DEPCNT-line",
|
|
1701
|
+
"enc-RIVERS",
|
|
1702
|
+
"enc-RIVERS-polygon",
|
|
1703
|
+
"enc-RIVERS-line",
|
|
1704
|
+
"enc-CANALS",
|
|
1705
|
+
"enc-CANALS-polygon",
|
|
1706
|
+
"enc-CANALS-line",
|
|
1707
|
+
"enc-LAKARE",
|
|
1708
|
+
"enc-LAKARE-polygon",
|
|
1709
|
+
"enc-RAPIDS",
|
|
1710
|
+
"enc-RAPIDS-polygon",
|
|
1711
|
+
"enc-RAPIDS-point",
|
|
1712
|
+
"enc-WATFAL",
|
|
1713
|
+
"enc-WATFAL-polygon",
|
|
1714
|
+
"enc-WATFAL-point",
|
|
1715
|
+
"enc-LNDARE",
|
|
1716
|
+
"enc-LNDARE-polygon"
|
|
1717
|
+
];
|
|
1718
|
+
var BRIDGE_QUERY_LAYERS = [
|
|
1719
|
+
"enc-BRIDGE-midpoint",
|
|
1720
|
+
"enc-BRIDGE",
|
|
1721
|
+
"enc-BRIDGE-line",
|
|
1722
|
+
"s52-BRIDGE-polygon",
|
|
1723
|
+
"s52-BRIDGE-polygon-outline"
|
|
1724
|
+
];
|
|
1725
|
+
var CATBRG_LABELS = {
|
|
1726
|
+
1: "Fixed bridge",
|
|
1727
|
+
2: "Opening bridge",
|
|
1728
|
+
3: "Swing bridge",
|
|
1729
|
+
4: "Lifting bridge",
|
|
1730
|
+
5: "Bascule bridge",
|
|
1731
|
+
6: "Pontoon bridge",
|
|
1732
|
+
7: "Drawbridge",
|
|
1733
|
+
8: "Transporter bridge",
|
|
1734
|
+
9: "Footbridge",
|
|
1735
|
+
10: "Viaduct",
|
|
1736
|
+
11: "Aqueduct",
|
|
1737
|
+
12: "Suspension bridge"
|
|
1738
|
+
};
|
|
1739
|
+
var CONDTN_TABLE = {
|
|
1740
|
+
1: "Under construction",
|
|
1741
|
+
2: "Ruined",
|
|
1742
|
+
3: "Under reclamation",
|
|
1743
|
+
5: "Planned"
|
|
1744
|
+
};
|
|
1745
|
+
function parseFirstNum(val) {
|
|
1746
|
+
if (val == null) return null;
|
|
1747
|
+
const s = String(val);
|
|
1748
|
+
if (s.startsWith("[")) {
|
|
1749
|
+
try {
|
|
1750
|
+
const arr = JSON.parse(s);
|
|
1751
|
+
if (Array.isArray(arr)) {
|
|
1752
|
+
const n2 = Number(arr[0]);
|
|
1753
|
+
return Number.isFinite(n2) ? n2 : null;
|
|
1754
|
+
}
|
|
1755
|
+
} catch {
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
const n = Number(s.split(",")[0]);
|
|
1759
|
+
return Number.isFinite(n) ? n : null;
|
|
1760
|
+
}
|
|
1761
|
+
function asNumber(val) {
|
|
1762
|
+
if (val == null || val === "") return null;
|
|
1763
|
+
const n = Number(val);
|
|
1764
|
+
return Number.isFinite(n) ? n : null;
|
|
1765
|
+
}
|
|
1766
|
+
function decodeBridgeProps(props) {
|
|
1767
|
+
const catCode = parseFirstNum(props.CATBRG);
|
|
1768
|
+
const statusCode = parseFirstNum(props.STATUS);
|
|
1769
|
+
const condCode = parseFirstNum(props.CONDTN);
|
|
1770
|
+
return {
|
|
1771
|
+
name: props.OBJNAM ? String(props.OBJNAM) : null,
|
|
1772
|
+
categoryCode: catCode,
|
|
1773
|
+
categoryLabel: catCode ? CATBRG_LABELS[catCode] ?? null : null,
|
|
1774
|
+
verticalClearance: asNumber(props.VERCLR),
|
|
1775
|
+
verticalClearanceClosed: asNumber(props.VERCCL),
|
|
1776
|
+
verticalClearanceOpen: asNumber(props.VERCOP),
|
|
1777
|
+
horizontalClearance: asNumber(props.HORCLR),
|
|
1778
|
+
status: statusCode ? STATUS_TABLE[statusCode] ?? null : null,
|
|
1779
|
+
condition: condCode ? CONDTN_TABLE[condCode] ?? null : null,
|
|
1780
|
+
information: props.INFORM ? String(props.INFORM) : null,
|
|
1781
|
+
nationalName: props.NOBJNM ? String(props.NOBJNM) : null,
|
|
1782
|
+
sourceIndication: props.SORIND ? String(props.SORIND) : null,
|
|
1783
|
+
sourceDate: props.SORDAT ? String(props.SORDAT) : null
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
var CATOBS_TABLE = {
|
|
1787
|
+
1: "Snag / stump",
|
|
1788
|
+
2: "Wellhead",
|
|
1789
|
+
3: "Diffuser",
|
|
1790
|
+
4: "Crib",
|
|
1791
|
+
5: "Fish haven",
|
|
1792
|
+
6: "Foul area",
|
|
1793
|
+
7: "Foul ground",
|
|
1794
|
+
8: "Ice boom",
|
|
1795
|
+
9: "Ground tackle",
|
|
1796
|
+
10: "Boom",
|
|
1797
|
+
11: "Platform"
|
|
1798
|
+
};
|
|
1799
|
+
var CATWRK_TABLE = {
|
|
1800
|
+
1: "Non-dangerous",
|
|
1801
|
+
2: "Dangerous",
|
|
1802
|
+
3: "Distributed remains",
|
|
1803
|
+
4: "Mast showing",
|
|
1804
|
+
5: "Hull showing"
|
|
1805
|
+
};
|
|
1806
|
+
var WATLEV_TABLE = {
|
|
1807
|
+
1: "Partly submerged at high water",
|
|
1808
|
+
2: "Always dry",
|
|
1809
|
+
3: "Always submerged",
|
|
1810
|
+
4: "Covers and uncovers",
|
|
1811
|
+
5: "Awash",
|
|
1812
|
+
6: "Subject to inundation / flooding",
|
|
1813
|
+
7: "Floating"
|
|
1814
|
+
};
|
|
1815
|
+
var CATREA_TABLE = {
|
|
1816
|
+
1: "Offshore safety zone",
|
|
1817
|
+
2: "Anchoring prohibition",
|
|
1818
|
+
3: "Fishing prohibition",
|
|
1819
|
+
4: "Nature reserve",
|
|
1820
|
+
5: "Bird sanctuary",
|
|
1821
|
+
6: "Game preserve",
|
|
1822
|
+
7: "Seal sanctuary",
|
|
1823
|
+
8: "Degaussing range",
|
|
1824
|
+
9: "Military area",
|
|
1825
|
+
10: "Historic wreck",
|
|
1826
|
+
12: "Navigational aid safety zone",
|
|
1827
|
+
14: "No wake zone",
|
|
1828
|
+
18: "Swimming area",
|
|
1829
|
+
19: "Waiting area",
|
|
1830
|
+
20: "Research area",
|
|
1831
|
+
21: "Dredging area",
|
|
1832
|
+
22: "Fish sanctuary",
|
|
1833
|
+
23: "Ecological reserve",
|
|
1834
|
+
24: "No discharge zone",
|
|
1835
|
+
25: "No anchoring area",
|
|
1836
|
+
26: "No fishing area",
|
|
1837
|
+
27: "Speed restriction zone",
|
|
1838
|
+
28: "To be avoided area"
|
|
1839
|
+
};
|
|
1840
|
+
var RESTRN_TABLE = {
|
|
1841
|
+
1: "Anchoring prohibited",
|
|
1842
|
+
2: "Anchoring restricted",
|
|
1843
|
+
3: "Fishing prohibited",
|
|
1844
|
+
4: "Fishing restricted",
|
|
1845
|
+
5: "Trawling prohibited",
|
|
1846
|
+
6: "Trawling restricted",
|
|
1847
|
+
7: "Entry prohibited",
|
|
1848
|
+
8: "Entry restricted",
|
|
1849
|
+
9: "Dredging prohibited",
|
|
1850
|
+
10: "Dredging restricted",
|
|
1851
|
+
11: "Diving prohibited",
|
|
1852
|
+
12: "Diving restricted",
|
|
1853
|
+
13: "No wake",
|
|
1854
|
+
14: "To be avoided",
|
|
1855
|
+
15: "Construction prohibited",
|
|
1856
|
+
16: "Discharging prohibited",
|
|
1857
|
+
17: "Discharging restricted",
|
|
1858
|
+
18: "Industrial prohibited",
|
|
1859
|
+
19: "Metallic hull vessels prohibited",
|
|
1860
|
+
20: "Navigating prohibited",
|
|
1861
|
+
21: "Speed restricted",
|
|
1862
|
+
24: "Stopping prohibited",
|
|
1863
|
+
25: "Landing prohibited"
|
|
1864
|
+
};
|
|
1865
|
+
var CATHAF_TABLE = {
|
|
1866
|
+
1: "RoRo terminal",
|
|
1867
|
+
3: "Ferry terminal",
|
|
1868
|
+
4: "Fishing harbour",
|
|
1869
|
+
5: "Yacht harbour / marina",
|
|
1870
|
+
6: "Naval base",
|
|
1871
|
+
7: "Tanker terminal",
|
|
1872
|
+
8: "Passenger terminal",
|
|
1873
|
+
9: "Shipyard",
|
|
1874
|
+
10: "Container terminal",
|
|
1875
|
+
11: "Bulk terminal",
|
|
1876
|
+
12: "Synchrolift",
|
|
1877
|
+
13: "Straddle carrier",
|
|
1878
|
+
14: "Port terminal"
|
|
1879
|
+
};
|
|
1880
|
+
var CATSCF_TABLE = {
|
|
1881
|
+
1: "Visitors' berth",
|
|
1882
|
+
2: "Nautical club",
|
|
1883
|
+
3: "Boat hoist",
|
|
1884
|
+
4: "Sailmaker",
|
|
1885
|
+
5: "Boatyard",
|
|
1886
|
+
6: "Public inn",
|
|
1887
|
+
7: "Restaurant",
|
|
1888
|
+
8: "Chandler",
|
|
1889
|
+
9: "Provisions",
|
|
1890
|
+
10: "Doctor",
|
|
1891
|
+
11: "Pharmacy",
|
|
1892
|
+
12: "Water tap",
|
|
1893
|
+
13: "Fuel station",
|
|
1894
|
+
14: "Electricity",
|
|
1895
|
+
15: "Bottle gas",
|
|
1896
|
+
16: "Showers",
|
|
1897
|
+
17: "Launderette",
|
|
1898
|
+
18: "Public toilets",
|
|
1899
|
+
19: "Post box"
|
|
1900
|
+
};
|
|
1901
|
+
var CATCRN_TABLE = {
|
|
1902
|
+
2: "Container crane / gantry",
|
|
1903
|
+
3: "Sheerlegs",
|
|
1904
|
+
4: "Travelling crane"
|
|
1905
|
+
};
|
|
1906
|
+
var CATACH_TABLE = {
|
|
1907
|
+
1: "Unrestricted anchorage",
|
|
1908
|
+
2: "Deep water anchorage",
|
|
1909
|
+
3: "Tanker anchorage",
|
|
1910
|
+
4: "Explosives anchorage",
|
|
1911
|
+
5: "Quarantine anchorage",
|
|
1912
|
+
6: "Sea-plane anchorage",
|
|
1913
|
+
7: "Small craft anchorage",
|
|
1914
|
+
8: "Small craft mooring area",
|
|
1915
|
+
9: "Anchorage for 24 hours or less"
|
|
1916
|
+
};
|
|
1917
|
+
var CATDPG_TABLE = {
|
|
1918
|
+
1: "General dumping ground",
|
|
1919
|
+
2: "Chemical waste",
|
|
1920
|
+
3: "Nuclear waste",
|
|
1921
|
+
4: "Explosives",
|
|
1922
|
+
5: "Spoil ground",
|
|
1923
|
+
6: "Vessel dumping ground"
|
|
1924
|
+
};
|
|
1925
|
+
var CATMFA_TABLE = {
|
|
1926
|
+
1: "Crustaceans",
|
|
1927
|
+
2: "Oysters / mussels",
|
|
1928
|
+
3: "Fish",
|
|
1929
|
+
4: "Seaweed",
|
|
1930
|
+
5: "Pearl culture"
|
|
1931
|
+
};
|
|
1932
|
+
var CATCBL_TABLE = {
|
|
1933
|
+
1: "Submarine power cable",
|
|
1934
|
+
2: "Submarine transmission cable",
|
|
1935
|
+
3: "Submarine telegraph cable",
|
|
1936
|
+
4: "Submarine telephone cable",
|
|
1937
|
+
5: "Mooring cable",
|
|
1938
|
+
6: "Ferry cable"
|
|
1939
|
+
};
|
|
1940
|
+
var CATPIP_TABLE = {
|
|
1941
|
+
1: "Pipeline",
|
|
1942
|
+
2: "Outfall pipe",
|
|
1943
|
+
3: "Intake pipe",
|
|
1944
|
+
4: "Sewer",
|
|
1945
|
+
5: "Bubbler system",
|
|
1946
|
+
6: "Supply pipe"
|
|
1947
|
+
};
|
|
1948
|
+
var TRAFIC_TABLE = {
|
|
1949
|
+
1: "Inbound",
|
|
1950
|
+
2: "Outbound",
|
|
1951
|
+
3: "One-way",
|
|
1952
|
+
4: "Two-way"
|
|
1953
|
+
};
|
|
1954
|
+
var QUASOU_TABLE = {
|
|
1955
|
+
1: "Depth known",
|
|
1956
|
+
2: "Depth unknown",
|
|
1957
|
+
3: "Doubtful sounding",
|
|
1958
|
+
4: "Unreliable sounding",
|
|
1959
|
+
6: "Least depth known",
|
|
1960
|
+
7: "Least depth unknown",
|
|
1961
|
+
9: "Not regularly maintained",
|
|
1962
|
+
10: "Maintained depth",
|
|
1963
|
+
11: "Not surveyed"
|
|
1964
|
+
};
|
|
1965
|
+
function decodeAreaFeatureProps(layerName, props) {
|
|
1966
|
+
const rawLayer = layerName.replace(/^enc-/, "").replace(/-(point|line|polygon|midpoint)$/, "");
|
|
1967
|
+
const parseCodeLocal = (val) => {
|
|
1968
|
+
if (val == null) return null;
|
|
1969
|
+
const s = String(val);
|
|
1970
|
+
try {
|
|
1971
|
+
const arr = JSON.parse(s);
|
|
1972
|
+
if (Array.isArray(arr)) return Number(arr[0]) || null;
|
|
1973
|
+
} catch {
|
|
1974
|
+
}
|
|
1975
|
+
return Number(s.split(",")[0]) || null;
|
|
1976
|
+
};
|
|
1977
|
+
const entries = [];
|
|
1978
|
+
let type = rawLayer;
|
|
1979
|
+
let title = props.OBJNAM ? String(props.OBJNAM) : "";
|
|
1980
|
+
if (rawLayer === "DEPARE") {
|
|
1981
|
+
type = "Depth Area";
|
|
1982
|
+
const drval1 = props.DRVAL1 != null ? Number(props.DRVAL1) : null;
|
|
1983
|
+
const drval2 = props.DRVAL2 != null ? Number(props.DRVAL2) : null;
|
|
1984
|
+
if (drval1 != null && drval2 != null) {
|
|
1985
|
+
title = title || `${drval1}m \u2013 ${drval2}m`;
|
|
1986
|
+
entries.push({ label: "Depth Range", value: `${drval1}m \u2013 ${drval2}m` });
|
|
1987
|
+
} else if (drval1 != null) {
|
|
1988
|
+
entries.push({ label: "Min Depth", value: `${drval1}m` });
|
|
1989
|
+
}
|
|
1990
|
+
const qc = parseCodeLocal(props.QUASOU);
|
|
1991
|
+
if (qc && QUASOU_TABLE[qc]) entries.push({ label: "Quality", value: QUASOU_TABLE[qc] });
|
|
1992
|
+
} else if (rawLayer === "OBSTRN") {
|
|
1993
|
+
type = "Obstruction";
|
|
1994
|
+
const cat = parseCodeLocal(props.CATOBS);
|
|
1995
|
+
if (cat && CATOBS_TABLE[cat]) {
|
|
1996
|
+
entries.push({ label: "Category", value: CATOBS_TABLE[cat] });
|
|
1997
|
+
if (!title) title = CATOBS_TABLE[cat];
|
|
1998
|
+
}
|
|
1999
|
+
if (props.VALSOU != null) entries.push({ label: "Depth", value: `${props.VALSOU}m` });
|
|
2000
|
+
const wl = parseCodeLocal(props.WATLEV);
|
|
2001
|
+
if (wl && WATLEV_TABLE[wl]) entries.push({ label: "Water Level", value: WATLEV_TABLE[wl] });
|
|
2002
|
+
const qc = parseCodeLocal(props.QUASOU);
|
|
2003
|
+
if (qc && QUASOU_TABLE[qc]) entries.push({ label: "Sounding Quality", value: QUASOU_TABLE[qc] });
|
|
2004
|
+
} else if (rawLayer === "WRECKS") {
|
|
2005
|
+
type = "Wreck";
|
|
2006
|
+
const cat = parseCodeLocal(props.CATWRK);
|
|
2007
|
+
if (cat && CATWRK_TABLE[cat]) {
|
|
2008
|
+
entries.push({ label: "Category", value: CATWRK_TABLE[cat] });
|
|
2009
|
+
if (!title) title = CATWRK_TABLE[cat];
|
|
2010
|
+
}
|
|
2011
|
+
if (props.VALSOU != null) entries.push({ label: "Depth", value: `${props.VALSOU}m` });
|
|
2012
|
+
const wl = parseCodeLocal(props.WATLEV);
|
|
2013
|
+
if (wl && WATLEV_TABLE[wl]) entries.push({ label: "Water Level", value: WATLEV_TABLE[wl] });
|
|
2014
|
+
const qc = parseCodeLocal(props.QUASOU);
|
|
2015
|
+
if (qc && QUASOU_TABLE[qc]) entries.push({ label: "Sounding Quality", value: QUASOU_TABLE[qc] });
|
|
2016
|
+
} else if (rawLayer === "RESARE") {
|
|
2017
|
+
type = "Restricted Area";
|
|
2018
|
+
const cat = parseCodeLocal(props.CATREA);
|
|
2019
|
+
if (cat && CATREA_TABLE[cat]) {
|
|
2020
|
+
entries.push({ label: "Category", value: CATREA_TABLE[cat] });
|
|
2021
|
+
if (!title) title = CATREA_TABLE[cat];
|
|
2022
|
+
}
|
|
2023
|
+
if (props.RESTRN) {
|
|
2024
|
+
const codes = parseListCodes(props.RESTRN).filter((n) => RESTRN_TABLE[n]);
|
|
2025
|
+
if (codes.length > 0) {
|
|
2026
|
+
entries.push({ label: "Restrictions", value: codes.map((c) => RESTRN_TABLE[c]).join("; ") });
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
} else if (rawLayer === "ACHARE") {
|
|
2030
|
+
type = "Anchorage Area";
|
|
2031
|
+
const cat = parseCodeLocal(props.CATACH);
|
|
2032
|
+
if (cat && CATACH_TABLE[cat]) {
|
|
2033
|
+
entries.push({ label: "Category", value: CATACH_TABLE[cat] });
|
|
2034
|
+
if (!title) title = CATACH_TABLE[cat];
|
|
2035
|
+
}
|
|
2036
|
+
if (props.RESTRN) {
|
|
2037
|
+
const codes = parseListCodes(props.RESTRN).filter((n) => RESTRN_TABLE[n]);
|
|
2038
|
+
if (codes.length > 0) entries.push({ label: "Restrictions", value: codes.map((c) => RESTRN_TABLE[c]).join("; ") });
|
|
2039
|
+
}
|
|
2040
|
+
const st2 = parseCodeLocal(props.STATUS);
|
|
2041
|
+
if (st2 && STATUS_TABLE[st2]) entries.push({ label: "Status", value: STATUS_TABLE[st2] });
|
|
2042
|
+
} else if (rawLayer === "CTNARE") {
|
|
2043
|
+
type = "Caution Area";
|
|
2044
|
+
} else if (rawLayer === "DRGARE") {
|
|
2045
|
+
type = "Dredged Area";
|
|
2046
|
+
const drval1 = props.DRVAL1 != null ? Number(props.DRVAL1) : null;
|
|
2047
|
+
const drval2 = props.DRVAL2 != null ? Number(props.DRVAL2) : null;
|
|
2048
|
+
if (drval1 != null && drval2 != null) {
|
|
2049
|
+
entries.push({ label: "Maintained Depth", value: `${drval1}m \u2013 ${drval2}m` });
|
|
2050
|
+
} else if (drval1 != null) {
|
|
2051
|
+
entries.push({ label: "Maintained Depth", value: `${drval1}m` });
|
|
2052
|
+
}
|
|
2053
|
+
const qc = parseCodeLocal(props.QUASOU);
|
|
2054
|
+
if (qc && QUASOU_TABLE[qc]) entries.push({ label: "Quality", value: QUASOU_TABLE[qc] });
|
|
2055
|
+
} else if (rawLayer === "FAIRWY") {
|
|
2056
|
+
type = "Fairway";
|
|
2057
|
+
const trafic = parseCodeLocal(props.TRAFIC);
|
|
2058
|
+
if (trafic && TRAFIC_TABLE[trafic]) entries.push({ label: "Traffic", value: TRAFIC_TABLE[trafic] });
|
|
2059
|
+
if (props.ORIENT != null) entries.push({ label: "Orientation", value: `${props.ORIENT}\xB0` });
|
|
2060
|
+
} else if (rawLayer === "DMPGRD") {
|
|
2061
|
+
type = "Dumping Ground";
|
|
2062
|
+
const cat = parseCodeLocal(props.CATDPG);
|
|
2063
|
+
if (cat && CATDPG_TABLE[cat]) {
|
|
2064
|
+
entries.push({ label: "Category", value: CATDPG_TABLE[cat] });
|
|
2065
|
+
if (!title) title = CATDPG_TABLE[cat];
|
|
2066
|
+
}
|
|
2067
|
+
} else if (rawLayer === "MIPARE") {
|
|
2068
|
+
type = "Military Practice Area";
|
|
2069
|
+
if (props.RESTRN) {
|
|
2070
|
+
const codes = parseListCodes(props.RESTRN).filter((n) => RESTRN_TABLE[n]);
|
|
2071
|
+
if (codes.length > 0) entries.push({ label: "Restrictions", value: codes.map((c) => RESTRN_TABLE[c]).join("; ") });
|
|
2072
|
+
}
|
|
2073
|
+
} else if (rawLayer === "PRCARE") {
|
|
2074
|
+
type = "Precautionary Area";
|
|
2075
|
+
const trafic = parseCodeLocal(props.TRAFIC);
|
|
2076
|
+
if (trafic && TRAFIC_TABLE[trafic]) entries.push({ label: "Traffic", value: TRAFIC_TABLE[trafic] });
|
|
2077
|
+
if (props.ORIENT != null) entries.push({ label: "Orientation", value: `${props.ORIENT}\xB0` });
|
|
2078
|
+
} else if (rawLayer === "TSSLPT") {
|
|
2079
|
+
type = "TSS Lane";
|
|
2080
|
+
const trafic = parseCodeLocal(props.TRAFIC);
|
|
2081
|
+
if (trafic && TRAFIC_TABLE[trafic]) entries.push({ label: "Traffic", value: TRAFIC_TABLE[trafic] });
|
|
2082
|
+
if (props.ORIENT != null) entries.push({ label: "Orientation", value: `${props.ORIENT}\xB0` });
|
|
2083
|
+
} else if (rawLayer === "TSELNE") {
|
|
2084
|
+
type = "TSS Boundary";
|
|
2085
|
+
} else if (rawLayer === "ISTZNE") {
|
|
2086
|
+
type = "Inshore Traffic Zone";
|
|
2087
|
+
} else if (rawLayer === "OSPARE") {
|
|
2088
|
+
type = "Offshore Production Area";
|
|
2089
|
+
if (props.RESTRN) {
|
|
2090
|
+
const codes = parseListCodes(props.RESTRN).filter((n) => RESTRN_TABLE[n]);
|
|
2091
|
+
if (codes.length > 0) entries.push({ label: "Restrictions", value: codes.map((c) => RESTRN_TABLE[c]).join("; ") });
|
|
2092
|
+
}
|
|
2093
|
+
} else if (rawLayer === "MARCUL") {
|
|
2094
|
+
type = "Marine Farm / Culture";
|
|
2095
|
+
const cat = parseCodeLocal(props.CATMFA);
|
|
2096
|
+
if (cat && CATMFA_TABLE[cat]) {
|
|
2097
|
+
entries.push({ label: "Category", value: CATMFA_TABLE[cat] });
|
|
2098
|
+
if (!title) title = CATMFA_TABLE[cat];
|
|
2099
|
+
}
|
|
2100
|
+
if (props.RESTRN) {
|
|
2101
|
+
const codes = parseListCodes(props.RESTRN).filter((n) => RESTRN_TABLE[n]);
|
|
2102
|
+
if (codes.length > 0) entries.push({ label: "Restrictions", value: codes.map((c) => RESTRN_TABLE[c]).join("; ") });
|
|
2103
|
+
}
|
|
2104
|
+
} else if (rawLayer === "CBLARE") {
|
|
2105
|
+
type = "Cable Area";
|
|
2106
|
+
const cat = parseCodeLocal(props.CATCBL);
|
|
2107
|
+
if (cat && CATCBL_TABLE[cat]) {
|
|
2108
|
+
entries.push({ label: "Category", value: CATCBL_TABLE[cat] });
|
|
2109
|
+
if (!title) title = `${CATCBL_TABLE[cat]} area`;
|
|
2110
|
+
}
|
|
2111
|
+
const st2 = parseCodeLocal(props.STATUS);
|
|
2112
|
+
if (st2 && STATUS_TABLE[st2]) entries.push({ label: "Status", value: STATUS_TABLE[st2] });
|
|
2113
|
+
if (props.RESTRN) {
|
|
2114
|
+
const codes = parseListCodes(props.RESTRN).filter((n) => RESTRN_TABLE[n]);
|
|
2115
|
+
if (codes.length > 0) entries.push({ label: "Restrictions", value: codes.map((c) => RESTRN_TABLE[c]).join("; ") });
|
|
2116
|
+
}
|
|
2117
|
+
} else if (rawLayer === "PIPARE") {
|
|
2118
|
+
type = "Pipeline Area";
|
|
2119
|
+
const cat = parseCodeLocal(props.CATPIP);
|
|
2120
|
+
if (cat && CATPIP_TABLE[cat]) {
|
|
2121
|
+
entries.push({ label: "Category", value: CATPIP_TABLE[cat] });
|
|
2122
|
+
if (!title) title = `${CATPIP_TABLE[cat]} area`;
|
|
2123
|
+
}
|
|
2124
|
+
const st2 = parseCodeLocal(props.STATUS);
|
|
2125
|
+
if (st2 && STATUS_TABLE[st2]) entries.push({ label: "Status", value: STATUS_TABLE[st2] });
|
|
2126
|
+
if (props.RESTRN) {
|
|
2127
|
+
const codes = parseListCodes(props.RESTRN).filter((n) => RESTRN_TABLE[n]);
|
|
2128
|
+
if (codes.length > 0) entries.push({ label: "Restrictions", value: codes.map((c) => RESTRN_TABLE[c]).join("; ") });
|
|
2129
|
+
}
|
|
2130
|
+
} else if (rawLayer === "BRIDGE") {
|
|
2131
|
+
const CATBRG_SHORT = {
|
|
2132
|
+
1: "Fixed",
|
|
2133
|
+
2: "Opening",
|
|
2134
|
+
3: "Swing",
|
|
2135
|
+
4: "Lifting",
|
|
2136
|
+
5: "Bascule",
|
|
2137
|
+
6: "Pontoon",
|
|
2138
|
+
7: "Draw",
|
|
2139
|
+
8: "Transporter",
|
|
2140
|
+
9: "Footbridge",
|
|
2141
|
+
10: "Viaduct",
|
|
2142
|
+
11: "Aqueduct",
|
|
2143
|
+
12: "Suspension"
|
|
2144
|
+
};
|
|
2145
|
+
const NATCON_TABLE = {
|
|
2146
|
+
1: "Masonry",
|
|
2147
|
+
2: "Concrete",
|
|
2148
|
+
3: "Loose boulders",
|
|
2149
|
+
4: "Hard-surfaced",
|
|
2150
|
+
5: "Unsurfaced",
|
|
2151
|
+
6: "Wooden",
|
|
2152
|
+
7: "Metal",
|
|
2153
|
+
8: "GRP"
|
|
2154
|
+
};
|
|
2155
|
+
const CONDTN_SHORT = {
|
|
2156
|
+
1: "Under construction",
|
|
2157
|
+
2: "Ruined",
|
|
2158
|
+
3: "Under reclamation",
|
|
2159
|
+
5: "Planned"
|
|
2160
|
+
};
|
|
2161
|
+
const catCode = parseCodeLocal(props.CATBRG);
|
|
2162
|
+
const catName = catCode ? CATBRG_SHORT[catCode] : null;
|
|
2163
|
+
type = catName ? `${catName} Bridge` : "Bridge";
|
|
2164
|
+
if (catName) entries.push({ label: "Category", value: catName });
|
|
2165
|
+
if (props.VERCLR != null) entries.push({ label: "Vertical Clearance", value: `${props.VERCLR} m` });
|
|
2166
|
+
if (props.VERCCL != null) entries.push({ label: "Clearance (closed)", value: `${props.VERCCL} m` });
|
|
2167
|
+
if (props.VERCOP != null) entries.push({ label: "Clearance (open)", value: `${props.VERCOP} m` });
|
|
2168
|
+
if (props.HORCLR != null) entries.push({ label: "Horizontal Clearance", value: `${props.HORCLR} m` });
|
|
2169
|
+
const natCode = parseCodeLocal(props.NATCON);
|
|
2170
|
+
if (natCode && NATCON_TABLE[natCode]) entries.push({ label: "Construction", value: NATCON_TABLE[natCode] });
|
|
2171
|
+
const condCode = parseCodeLocal(props.CONDTN);
|
|
2172
|
+
if (condCode && CONDTN_SHORT[condCode]) entries.push({ label: "Condition", value: CONDTN_SHORT[condCode] });
|
|
2173
|
+
const st2 = parseCodeLocal(props.STATUS);
|
|
2174
|
+
if (st2 && STATUS_TABLE[st2]) entries.push({ label: "Status", value: STATUS_TABLE[st2] });
|
|
2175
|
+
if (props.NOBJNM) entries.push({ label: "National Name", value: String(props.NOBJNM) });
|
|
2176
|
+
} else if (rawLayer === "CBLSUB" || rawLayer === "CBLOHD") {
|
|
2177
|
+
const CATCBL_LINE = {
|
|
2178
|
+
1: "Power line",
|
|
2179
|
+
2: "Transmission line",
|
|
2180
|
+
3: "Telephone",
|
|
2181
|
+
4: "Telegraph",
|
|
2182
|
+
5: "Mooring cable/chain",
|
|
2183
|
+
6: "Ferry cable"
|
|
2184
|
+
};
|
|
2185
|
+
type = rawLayer === "CBLSUB" ? "Submarine Cable" : "Overhead Cable";
|
|
2186
|
+
const cat = parseCodeLocal(props.CATCBL);
|
|
2187
|
+
if (cat && CATCBL_LINE[cat]) {
|
|
2188
|
+
entries.push({ label: "Category", value: CATCBL_LINE[cat] });
|
|
2189
|
+
if (!title) title = `${CATCBL_LINE[cat]} (${rawLayer === "CBLSUB" ? "submarine" : "overhead"})`;
|
|
2190
|
+
}
|
|
2191
|
+
if (props.VERCLR != null) entries.push({ label: "Vertical Clearance", value: `${props.VERCLR}m` });
|
|
2192
|
+
if (props.VERCSA != null) entries.push({ label: "Clearance (safe)", value: `${props.VERCSA}m` });
|
|
2193
|
+
if (props.RESTRN) {
|
|
2194
|
+
const codes = parseListCodes(props.RESTRN).filter((n) => RESTRN_TABLE[n]);
|
|
2195
|
+
if (codes.length > 0) entries.push({ label: "Restrictions", value: codes.map((c) => RESTRN_TABLE[c]).join("; ") });
|
|
2196
|
+
}
|
|
2197
|
+
} else if (rawLayer === "PIPSOL" || rawLayer === "PIPOHD") {
|
|
2198
|
+
const CATPIP_LINE = {
|
|
2199
|
+
2: "Outfall pipe",
|
|
2200
|
+
3: "Intake pipe",
|
|
2201
|
+
4: "Sewer",
|
|
2202
|
+
5: "Bubbler system",
|
|
2203
|
+
6: "Supply pipe"
|
|
2204
|
+
};
|
|
2205
|
+
type = rawLayer === "PIPSOL" ? "Submarine Pipeline" : "Overhead Pipeline";
|
|
2206
|
+
const cat = parseCodeLocal(props.CATPIP);
|
|
2207
|
+
if (cat && CATPIP_LINE[cat]) {
|
|
2208
|
+
entries.push({ label: "Category", value: CATPIP_LINE[cat] });
|
|
2209
|
+
if (!title) title = `${CATPIP_LINE[cat]} (${rawLayer === "PIPSOL" ? "submarine" : "overhead"})`;
|
|
2210
|
+
}
|
|
2211
|
+
if (props.VERCLR != null) entries.push({ label: "Vertical Clearance", value: `${props.VERCLR}m` });
|
|
2212
|
+
if (props.RESTRN) {
|
|
2213
|
+
const codes = parseListCodes(props.RESTRN).filter((n) => RESTRN_TABLE[n]);
|
|
2214
|
+
if (codes.length > 0) entries.push({ label: "Restrictions", value: codes.map((c) => RESTRN_TABLE[c]).join("; ") });
|
|
2215
|
+
}
|
|
2216
|
+
} else if (rawLayer === "BERTHS") {
|
|
2217
|
+
type = "Berth";
|
|
2218
|
+
} else if (rawLayer === "DAMCON") {
|
|
2219
|
+
type = "Dam";
|
|
2220
|
+
if (props.VERCLR != null) entries.push({ label: "Vertical Clearance", value: `${props.VERCLR}m` });
|
|
2221
|
+
} else if (rawLayer === "FERYRT") {
|
|
2222
|
+
type = "Ferry Route";
|
|
2223
|
+
} else if (rawLayer === "RECTRC") {
|
|
2224
|
+
type = "Recommended Track";
|
|
2225
|
+
const trafic = parseCodeLocal(props.TRAFIC);
|
|
2226
|
+
if (trafic && TRAFIC_TABLE[trafic]) entries.push({ label: "Traffic", value: TRAFIC_TABLE[trafic] });
|
|
2227
|
+
if (props.ORIENT != null) entries.push({ label: "Orientation", value: `${props.ORIENT}\xB0` });
|
|
2228
|
+
} else if (rawLayer === "BUISGL") {
|
|
2229
|
+
type = "Building";
|
|
2230
|
+
const cat = parseCodeLocal(props.CATBUA);
|
|
2231
|
+
if (cat) {
|
|
2232
|
+
const CATBUA = {
|
|
2233
|
+
1: "Urban area",
|
|
2234
|
+
5: "Settlement",
|
|
2235
|
+
6: "Town",
|
|
2236
|
+
7: "Village",
|
|
2237
|
+
8: "City"
|
|
2238
|
+
};
|
|
2239
|
+
if (CATBUA[cat]) {
|
|
2240
|
+
entries.push({ label: "Category", value: CATBUA[cat] });
|
|
2241
|
+
if (!title) title = CATBUA[cat];
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
const func = parseCodeLocal(props.FUNCTN);
|
|
2245
|
+
if (func) {
|
|
2246
|
+
const FUNCTN = {
|
|
2247
|
+
2: "Harbour master's office",
|
|
2248
|
+
3: "Customs office",
|
|
2249
|
+
4: "Health office",
|
|
2250
|
+
5: "Hospital",
|
|
2251
|
+
6: "Post office",
|
|
2252
|
+
7: "Hotel",
|
|
2253
|
+
8: "Railway station",
|
|
2254
|
+
9: "Police station",
|
|
2255
|
+
10: "Water-police station",
|
|
2256
|
+
11: "Pilot office",
|
|
2257
|
+
13: "Bank",
|
|
2258
|
+
14: "Headquarters",
|
|
2259
|
+
15: "Transit shed/warehouse",
|
|
2260
|
+
18: "Church",
|
|
2261
|
+
19: "Chapel",
|
|
2262
|
+
20: "Temple",
|
|
2263
|
+
21: "Synagogue",
|
|
2264
|
+
22: "Mosque",
|
|
2265
|
+
24: "Lookout",
|
|
2266
|
+
25: "Communication",
|
|
2267
|
+
28: "Power station",
|
|
2268
|
+
33: "Factory",
|
|
2269
|
+
35: "Refinery",
|
|
2270
|
+
36: "Religious order",
|
|
2271
|
+
37: "School",
|
|
2272
|
+
38: "University"
|
|
2273
|
+
};
|
|
2274
|
+
if (FUNCTN[func]) entries.push({ label: "Function", value: FUNCTN[func] });
|
|
2275
|
+
}
|
|
2276
|
+
if (props.VERLEN != null) entries.push({ label: "Height", value: `${props.VERLEN}m` });
|
|
2277
|
+
} else if (rawLayer === "SILTNK") {
|
|
2278
|
+
type = "Silo / Tank";
|
|
2279
|
+
const cat = parseCodeLocal(props.CATSIL);
|
|
2280
|
+
if (cat) {
|
|
2281
|
+
const CATSIL = { 1: "Silo", 2: "Tank", 3: "Grain elevator", 4: "Water tower" };
|
|
2282
|
+
if (CATSIL[cat]) {
|
|
2283
|
+
type = CATSIL[cat];
|
|
2284
|
+
if (!title) title = CATSIL[cat];
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
if (parseCodeLocal(props.CONVIS) === 1) entries.push({ label: "Visibility", value: "Conspicuous from seaward" });
|
|
2288
|
+
if (props.VERLEN != null) entries.push({ label: "Height", value: `${props.VERLEN}m` });
|
|
2289
|
+
} else if (rawLayer === "HULKES") {
|
|
2290
|
+
type = "Hulk";
|
|
2291
|
+
if (props.CATHLK != null) {
|
|
2292
|
+
const cat = parseCodeLocal(props.CATHLK);
|
|
2293
|
+
const CATHLK = { 1: "Floating restaurant", 2: "Historic ship", 3: "Museum", 4: "Accommodation", 5: "Floating breakwater" };
|
|
2294
|
+
if (cat && CATHLK[cat]) {
|
|
2295
|
+
entries.push({ label: "Use", value: CATHLK[cat] });
|
|
2296
|
+
if (!title) title = CATHLK[cat];
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
} else if (rawLayer === "DRYDOC") {
|
|
2300
|
+
type = "Dry Dock";
|
|
2301
|
+
} else if (rawLayer === "FLODOC") {
|
|
2302
|
+
type = "Floating Dock";
|
|
2303
|
+
if (props.VERLEN != null) entries.push({ label: "Length", value: `${props.VERLEN}m` });
|
|
2304
|
+
if (props.HORLEN != null) entries.push({ label: "Horiz. Length", value: `${props.HORLEN}m` });
|
|
2305
|
+
} else if (rawLayer === "PONTON") {
|
|
2306
|
+
type = "Pontoon";
|
|
2307
|
+
} else if (rawLayer === "PYLONS") {
|
|
2308
|
+
type = "Pylon / Bridge Support";
|
|
2309
|
+
const cat = parseCodeLocal(props.CATPYL);
|
|
2310
|
+
if (cat) {
|
|
2311
|
+
const CATPYL = {
|
|
2312
|
+
1: "Power transmission pylon",
|
|
2313
|
+
2: "Telecom pylon",
|
|
2314
|
+
3: "Aerial cableway pylon",
|
|
2315
|
+
4: "Bridge pylon",
|
|
2316
|
+
5: "Bridge pier",
|
|
2317
|
+
6: "Chimney",
|
|
2318
|
+
7: "Flare stack"
|
|
2319
|
+
};
|
|
2320
|
+
if (CATPYL[cat]) {
|
|
2321
|
+
entries.push({ label: "Type", value: CATPYL[cat] });
|
|
2322
|
+
if (!title) title = CATPYL[cat];
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
if (props.VERLEN != null) entries.push({ label: "Height", value: `${props.VERLEN}m` });
|
|
2326
|
+
} else if (rawLayer === "GATCON") {
|
|
2327
|
+
type = "Gate";
|
|
2328
|
+
const cat = parseCodeLocal(props.CATGAT);
|
|
2329
|
+
if (cat) {
|
|
2330
|
+
const CATGAT = { 1: "General gate", 2: "Flood barrage gate", 3: "Caisson", 4: "Lock gate", 5: "Dyke gate" };
|
|
2331
|
+
if (CATGAT[cat]) {
|
|
2332
|
+
entries.push({ label: "Type", value: CATGAT[cat] });
|
|
2333
|
+
if (!title) title = CATGAT[cat];
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
} else if (rawLayer === "LOGPON") {
|
|
2337
|
+
type = "Log Pond";
|
|
2338
|
+
} else if (rawLayer === "AIRARE") {
|
|
2339
|
+
type = "Airport / Airfield";
|
|
2340
|
+
const cat = parseCodeLocal(props.CATAIR);
|
|
2341
|
+
if (cat) {
|
|
2342
|
+
const CATAIR = { 1: "Airport", 2: "Heliport", 3: "Helipad", 4: "Emergency helipad" };
|
|
2343
|
+
if (CATAIR[cat]) {
|
|
2344
|
+
entries.push({ label: "Type", value: CATAIR[cat] });
|
|
2345
|
+
if (!title) title = CATAIR[cat];
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
} else if (rawLayer === "SLCONS") {
|
|
2349
|
+
type = "Shoreline Construction";
|
|
2350
|
+
const cat = parseCodeLocal(props.CATSLC);
|
|
2351
|
+
if (cat) {
|
|
2352
|
+
const CATSLC = {
|
|
2353
|
+
1: "Breakwater",
|
|
2354
|
+
2: "Groyne",
|
|
2355
|
+
3: "Mole",
|
|
2356
|
+
4: "Pier",
|
|
2357
|
+
5: "Promenade pier",
|
|
2358
|
+
6: "Wharf",
|
|
2359
|
+
7: "Training wall",
|
|
2360
|
+
8: "Rip rap",
|
|
2361
|
+
9: "Revetment",
|
|
2362
|
+
10: "Sea wall",
|
|
2363
|
+
11: "Landing stairs",
|
|
2364
|
+
12: "Landing place",
|
|
2365
|
+
13: "Fender",
|
|
2366
|
+
14: "Lock",
|
|
2367
|
+
15: "Flood gate",
|
|
2368
|
+
16: "Slipway",
|
|
2369
|
+
17: "Log ramp"
|
|
2370
|
+
};
|
|
2371
|
+
if (CATSLC[cat]) {
|
|
2372
|
+
entries.push({ label: "Type", value: CATSLC[cat] });
|
|
2373
|
+
if (!title) title = CATSLC[cat];
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
const wl = parseCodeLocal(props.WATLEV);
|
|
2377
|
+
if (wl && WATLEV_TABLE[wl]) entries.push({ label: "Water Level", value: WATLEV_TABLE[wl] });
|
|
2378
|
+
} else if (rawLayer === "COALNE") {
|
|
2379
|
+
type = "Coastline";
|
|
2380
|
+
const cat = parseCodeLocal(props.CATCOA);
|
|
2381
|
+
if (cat) {
|
|
2382
|
+
const CATCOA = {
|
|
2383
|
+
1: "Steep coast",
|
|
2384
|
+
2: "Flat coast",
|
|
2385
|
+
3: "Sandy shore",
|
|
2386
|
+
4: "Stony shore",
|
|
2387
|
+
5: "Shingly shore",
|
|
2388
|
+
6: "Glacier",
|
|
2389
|
+
7: "Mangrove",
|
|
2390
|
+
8: "Marshy shore",
|
|
2391
|
+
9: "Coral reef",
|
|
2392
|
+
10: "Ice coast",
|
|
2393
|
+
11: "Rocky shore"
|
|
2394
|
+
};
|
|
2395
|
+
if (CATCOA[cat]) {
|
|
2396
|
+
entries.push({ label: "Type", value: CATCOA[cat] });
|
|
2397
|
+
if (!title) title = CATCOA[cat];
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
} else if (rawLayer === "DEPCNT") {
|
|
2401
|
+
type = "Depth Contour";
|
|
2402
|
+
if (props.VALDCO != null) {
|
|
2403
|
+
title = title || `${props.VALDCO}m contour`;
|
|
2404
|
+
entries.push({ label: "Depth", value: `${props.VALDCO}m` });
|
|
2405
|
+
}
|
|
2406
|
+
} else if (rawLayer === "RIVERS") {
|
|
2407
|
+
type = "River";
|
|
2408
|
+
} else if (rawLayer === "CANALS") {
|
|
2409
|
+
type = "Canal";
|
|
2410
|
+
if (props.HORWID != null) entries.push({ label: "Width", value: `${props.HORWID}m` });
|
|
2411
|
+
} else if (rawLayer === "LAKARE") {
|
|
2412
|
+
type = "Lake";
|
|
2413
|
+
} else if (rawLayer === "RAPIDS") {
|
|
2414
|
+
type = "Rapids";
|
|
2415
|
+
} else if (rawLayer === "WATFAL") {
|
|
2416
|
+
type = "Waterfall";
|
|
2417
|
+
if (props.HEIGHT != null) entries.push({ label: "Height", value: `${props.HEIGHT}m` });
|
|
2418
|
+
} else if (rawLayer === "LNDARE") {
|
|
2419
|
+
type = "Land Area";
|
|
2420
|
+
}
|
|
2421
|
+
if (props.INFORM) entries.push({ label: "Information", value: String(props.INFORM) });
|
|
2422
|
+
if (props.TXTDSC) entries.push({ label: "Text Description", value: String(props.TXTDSC) });
|
|
2423
|
+
const st = parseCodeLocal(props.STATUS);
|
|
2424
|
+
if (st && STATUS_TABLE[st]) entries.push({ label: "Status", value: STATUS_TABLE[st] });
|
|
2425
|
+
return { type, title: title || type, properties: entries };
|
|
2426
|
+
}
|
|
2427
|
+
function parseCode(val) {
|
|
2428
|
+
if (val == null) return null;
|
|
2429
|
+
let s = String(val).trim();
|
|
2430
|
+
if (s.startsWith("[")) {
|
|
2431
|
+
try {
|
|
2432
|
+
const arr = JSON.parse(s);
|
|
2433
|
+
if (Array.isArray(arr) && arr.length > 0) s = String(arr[0]);
|
|
2434
|
+
else return null;
|
|
2435
|
+
} catch {
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
s = s.split(/[,;]/)[0].trim();
|
|
2439
|
+
const n = Number(s);
|
|
2440
|
+
return isNaN(n) ? null : n;
|
|
2441
|
+
}
|
|
2442
|
+
function parseListCodes(val) {
|
|
2443
|
+
if (val == null) return [];
|
|
2444
|
+
if (Array.isArray(val)) {
|
|
2445
|
+
return val.map(Number).filter((n) => !isNaN(n) && n > 0);
|
|
2446
|
+
}
|
|
2447
|
+
const s = String(val).trim();
|
|
2448
|
+
if (s.startsWith("[")) {
|
|
2449
|
+
try {
|
|
2450
|
+
const arr = JSON.parse(s);
|
|
2451
|
+
if (Array.isArray(arr)) return arr.map(Number).filter((n) => !isNaN(n) && n > 0);
|
|
2452
|
+
} catch {
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
return s.split(/[,;]/).map((v) => Number(v.trim())).filter((n) => !isNaN(n) && n > 0);
|
|
2456
|
+
}
|
|
2457
|
+
function parseGroup(val) {
|
|
2458
|
+
if (val == null) return null;
|
|
2459
|
+
const s = String(val).trim();
|
|
2460
|
+
const parenMatch = s.match(/^\(?([\d+]+)\)?$/);
|
|
2461
|
+
if (parenMatch) {
|
|
2462
|
+
const parts = parenMatch[1].split("+").map(Number);
|
|
2463
|
+
if (parts.every((n2) => !isNaN(n2) && n2 > 0)) return parts;
|
|
2464
|
+
}
|
|
2465
|
+
const n = Number(s);
|
|
2466
|
+
return isNaN(n) || n <= 0 ? null : [n];
|
|
2467
|
+
}
|
|
2468
|
+
var CHILD_LAYERS = /* @__PURE__ */ new Set(["LIGHTS", "FOGSIG", "RTPBCN", "DAYMAR", "TOPMAR"]);
|
|
2469
|
+
var PARENT_LAYERS = [
|
|
2470
|
+
"enc-BOYLAT",
|
|
2471
|
+
"enc-BOYCAR",
|
|
2472
|
+
"enc-BOYSPP",
|
|
2473
|
+
"enc-BOYISD",
|
|
2474
|
+
"enc-BOYSAW",
|
|
2475
|
+
"enc-BCNLAT",
|
|
2476
|
+
"enc-BCNCAR",
|
|
2477
|
+
"enc-BCNSPP",
|
|
2478
|
+
"enc-BCNISD",
|
|
2479
|
+
"enc-BCNSAW"
|
|
2480
|
+
];
|
|
2481
|
+
function deriveMarinerName(objnam, colourCode) {
|
|
2482
|
+
if (!objnam) return "";
|
|
2483
|
+
const m = objnam.match(/(\d+[A-Z]?)\s*$/);
|
|
2484
|
+
if (!m) return objnam;
|
|
2485
|
+
const num = m[1];
|
|
2486
|
+
const colourAbbr = colourCode ? COLOUR_TABLE[colourCode]?.abbr : null;
|
|
2487
|
+
if (colourAbbr && colourAbbr !== "W") {
|
|
2488
|
+
return `${colourAbbr} "${num}"`;
|
|
2489
|
+
}
|
|
2490
|
+
return `"${num}"`;
|
|
2491
|
+
}
|
|
2492
|
+
function getTopmarkMeaning(topshpCode, rawLayer) {
|
|
2493
|
+
if (topshpCode == null) return null;
|
|
2494
|
+
switch (topshpCode) {
|
|
2495
|
+
case 10:
|
|
2496
|
+
return "West cardinal \u2014 pass to the west";
|
|
2497
|
+
// 2 cones, point to point
|
|
2498
|
+
case 11:
|
|
2499
|
+
return "East cardinal \u2014 pass to the east";
|
|
2500
|
+
// 2 cones, base to base
|
|
2501
|
+
case 13:
|
|
2502
|
+
return "North cardinal \u2014 pass to the north";
|
|
2503
|
+
// 2 cones, points up
|
|
2504
|
+
case 14:
|
|
2505
|
+
return "South cardinal \u2014 pass to the south";
|
|
2506
|
+
}
|
|
2507
|
+
const parent = rawLayer.replace(/^(BOY|BCN)/, "");
|
|
2508
|
+
if (parent === "LAT") {
|
|
2509
|
+
if (topshpCode === 1) return "Starboard-hand mark";
|
|
2510
|
+
if (topshpCode === 5) return "Port-hand mark";
|
|
2511
|
+
}
|
|
2512
|
+
if (parent === "SAW" && topshpCode === 3) return "Safe water \u2014 navigable water all around";
|
|
2513
|
+
if (parent === "ISD" && topshpCode === 4) return "Isolated danger \u2014 danger of limited extent";
|
|
2514
|
+
if (parent === "SPP" && topshpCode === 7) return "Special-purpose mark";
|
|
2515
|
+
return null;
|
|
2516
|
+
}
|
|
2517
|
+
function decodeNavAidProps(layerName, props, coords) {
|
|
2518
|
+
const rawLayer = layerName.replace(/^enc-/, "").replace(/-(point|line|polygon|midpoint)$/, "");
|
|
2519
|
+
const fullName = props.OBJNAM || props.NOBJNM || "";
|
|
2520
|
+
const colourCode = parseCode(props.COLOUR);
|
|
2521
|
+
const marinerName = deriveMarinerName(fullName, colourCode);
|
|
2522
|
+
const name = marinerName || fullName;
|
|
2523
|
+
const type = LAYER_TYPE_MAP[rawLayer] ?? rawLayer;
|
|
2524
|
+
const lightColourSource = props.LIGHT_COLOUR ?? props.COLOUR;
|
|
2525
|
+
const lightColourCodes = parseListCodes(lightColourSource);
|
|
2526
|
+
const colourEntry = colourCode ? COLOUR_TABLE[colourCode] : null;
|
|
2527
|
+
const lightColourHexList = lightColourCodes.map((c) => COLOUR_TABLE[c]?.hex ?? "#ffffff");
|
|
2528
|
+
const boyshpCode = parseCode(props.BOYSHP);
|
|
2529
|
+
const shape = boyshpCode ? BOYSHP_TABLE[boyshpCode] ?? null : null;
|
|
2530
|
+
const topshpCode = parseCode(props.TOPSHP);
|
|
2531
|
+
const topmark = topshpCode ? TOPSHP_TABLE[topshpCode] ?? null : null;
|
|
2532
|
+
const topmarkMeaning = getTopmarkMeaning(topshpCode, rawLayer);
|
|
2533
|
+
const litchrCode = parseCode(props.LITCHR);
|
|
2534
|
+
const litchrEntry = litchrCode ? LITCHR_TABLE[litchrCode] : null;
|
|
2535
|
+
const sigper = props.SIGPER != null ? Number(props.SIGPER) : null;
|
|
2536
|
+
const group = parseGroup(props.SIGGRP);
|
|
2537
|
+
const nameForFog = String(props.OBJNAM ?? "").toLowerCase();
|
|
2538
|
+
let catfogCode = null;
|
|
2539
|
+
if (/\bwhistle\b/.test(nameForFog)) catfogCode = 6;
|
|
2540
|
+
else if (/\bgong\b/.test(nameForFog)) catfogCode = 8;
|
|
2541
|
+
else if (/\bbell\b/.test(nameForFog)) catfogCode = 5;
|
|
2542
|
+
else if (/\bhorn\b/.test(nameForFog)) catfogCode = 9;
|
|
2543
|
+
else if (/\bsiren\b/.test(nameForFog)) catfogCode = 3;
|
|
2544
|
+
else if (/diaphone/.test(nameForFog)) catfogCode = 2;
|
|
2545
|
+
else if (/nautophone|nauto/.test(nameForFog)) catfogCode = 4;
|
|
2546
|
+
if (!catfogCode) catfogCode = parseCode(props.CATFOG);
|
|
2547
|
+
const fogSignal2 = catfogCode ? CATFOG_TABLE[catfogCode] ?? null : null;
|
|
2548
|
+
const fogSignalAbbr = catfogCode ? CATFOG_ABBR_TABLE[catfogCode] ?? null : null;
|
|
2549
|
+
const radarReflector = props.RETRFL != null && String(props.RETRFL) !== "0" && String(props.RETRFL) !== "";
|
|
2550
|
+
const valsou = props.VALSOU != null ? Number(props.VALSOU) : null;
|
|
2551
|
+
const depth = valsou != null && !isNaN(valsou) ? valsou : null;
|
|
2552
|
+
const statusCode = parseCode(props.STATUS);
|
|
2553
|
+
const status = statusCode ? STATUS_TABLE[statusCode] ?? null : null;
|
|
2554
|
+
const marsysCode = parseCode(props.MARSYS);
|
|
2555
|
+
const markingSystem = marsysCode ? MARSYS_TABLE[marsysCode] ?? null : null;
|
|
2556
|
+
const catlamCode = parseCode(props.CATLAM);
|
|
2557
|
+
const catcamCode = parseCode(props.CATCAM);
|
|
2558
|
+
const cathafCode = parseCode(props.CATHAF);
|
|
2559
|
+
const catscfCode = parseCode(props.CATSCF);
|
|
2560
|
+
const catcrnCode = parseCode(props.CATCRN);
|
|
2561
|
+
const catlmkNames = parseListCodes(props.CATLMK).map((c) => CATLMK_TABLE[c]).filter((n) => Boolean(n));
|
|
2562
|
+
const category = catlamCode ? CATLAM_TABLE[catlamCode] ?? null : catcamCode ? CATCAM_TABLE[catcamCode] ?? null : cathafCode ? CATHAF_TABLE[cathafCode] ?? null : catscfCode ? CATSCF_TABLE[catscfCode] ?? null : catcrnCode ? CATCRN_TABLE[catcrnCode] ?? null : catlmkNames.length > 0 ? catlmkNames.join(" / ") : null;
|
|
2563
|
+
const information = props.INFORM || null;
|
|
2564
|
+
const lightColourAbbrs = lightColourCodes.map((c) => COLOUR_TABLE[c]?.abbr).filter(Boolean);
|
|
2565
|
+
const notation = buildNotation(litchrEntry?.abbr ?? null, lightColourAbbrs, sigper, group);
|
|
2566
|
+
const sectr1 = props.SECTR1 != null ? Number(props.SECTR1) : null;
|
|
2567
|
+
const sectr2 = props.SECTR2 != null ? Number(props.SECTR2) : null;
|
|
2568
|
+
const orient = props.ORIENT != null ? Number(props.ORIENT) : null;
|
|
2569
|
+
const catlit = parseCode(props.CATLIT);
|
|
2570
|
+
const isDirectional = catlit === 1;
|
|
2571
|
+
const sectors = [];
|
|
2572
|
+
const isSectorLight = sectr1 != null && sectr2 != null && !isNaN(sectr1) && !isNaN(sectr2);
|
|
2573
|
+
const valnmr = props.VALNMR != null ? Number(props.VALNMR) : null;
|
|
2574
|
+
const nominalRange = valnmr != null && !isNaN(valnmr) && valnmr > 0 ? valnmr : null;
|
|
2575
|
+
if (isSectorLight) {
|
|
2576
|
+
sectors.push({
|
|
2577
|
+
sectorStart: sectr1,
|
|
2578
|
+
sectorEnd: sectr2,
|
|
2579
|
+
colour: colourEntry?.name ?? "White",
|
|
2580
|
+
colourHex: colourEntry?.hex ?? "#ffffff",
|
|
2581
|
+
notation,
|
|
2582
|
+
nominalRange
|
|
2583
|
+
});
|
|
2584
|
+
}
|
|
2585
|
+
return {
|
|
2586
|
+
name,
|
|
2587
|
+
fullName: fullName && fullName !== name ? fullName : null,
|
|
2588
|
+
type,
|
|
2589
|
+
layerName: rawLayer,
|
|
2590
|
+
colour: colourEntry?.name ?? null,
|
|
2591
|
+
colourHex: colourEntry?.hex ?? "#ffffff",
|
|
2592
|
+
lightColourHex: lightColourHexList[0] ?? colourEntry?.hex ?? "#ffffff",
|
|
2593
|
+
colourHexList: lightColourHexList.length > 0 ? lightColourHexList : [colourEntry?.hex ?? "#ffffff"],
|
|
2594
|
+
shape,
|
|
2595
|
+
topmark,
|
|
2596
|
+
topmarkMeaning,
|
|
2597
|
+
lightCharacter: litchrEntry?.abbr ?? null,
|
|
2598
|
+
lightCharacterFull: litchrEntry?.name ?? null,
|
|
2599
|
+
period: sigper,
|
|
2600
|
+
group,
|
|
2601
|
+
fogSignal: fogSignal2,
|
|
2602
|
+
fogSignalAbbr,
|
|
2603
|
+
radarReflector,
|
|
2604
|
+
depth,
|
|
2605
|
+
status,
|
|
2606
|
+
markingSystem,
|
|
2607
|
+
category,
|
|
2608
|
+
information,
|
|
2609
|
+
notation,
|
|
2610
|
+
coordinates: coords ?? null,
|
|
2611
|
+
sectors,
|
|
2612
|
+
orientation: orient != null && !isNaN(orient) ? orient : null,
|
|
2613
|
+
isDirectional,
|
|
2614
|
+
isSectorLight
|
|
2615
|
+
};
|
|
2616
|
+
}
|
|
2617
|
+
function buildNotation(litchrAbbr, colourAbbrs, sigper, group) {
|
|
2618
|
+
if (!litchrAbbr) return "";
|
|
2619
|
+
const parts = [];
|
|
2620
|
+
let charPart = litchrAbbr;
|
|
2621
|
+
const totalFlashes = group ? group.reduce((a, b) => a + b, 0) : 0;
|
|
2622
|
+
if (totalFlashes > 1) charPart += `(${group.join("+")})`;
|
|
2623
|
+
parts.push(charPart);
|
|
2624
|
+
if (colourAbbrs.length > 0) parts.push(colourAbbrs.join("."));
|
|
2625
|
+
if (sigper && sigper > 0) parts.push(`${sigper}s`);
|
|
2626
|
+
return parts.join(" ");
|
|
2627
|
+
}
|
|
2628
|
+
function buildFlashPattern(decoded) {
|
|
2629
|
+
const litchr = decoded.lightCharacter;
|
|
2630
|
+
if (!litchr) return null;
|
|
2631
|
+
const period = decoded.period && decoded.period > 0 ? decoded.period * 1e3 : 6e3;
|
|
2632
|
+
const groups = decoded.group ?? [1];
|
|
2633
|
+
const totalFlashes = groups.reduce((a, b) => a + b, 0);
|
|
2634
|
+
const hex = decoded.lightColourHex;
|
|
2635
|
+
const colours = decoded.colourHexList;
|
|
2636
|
+
const notation = decoded.notation;
|
|
2637
|
+
const phases = [];
|
|
2638
|
+
switch (litchr) {
|
|
2639
|
+
case "F": {
|
|
2640
|
+
phases.push({ on: true, duration: period, color: hex });
|
|
2641
|
+
break;
|
|
2642
|
+
}
|
|
2643
|
+
case "Fl":
|
|
2644
|
+
case "S.Fl": {
|
|
2645
|
+
const flashDur = 400;
|
|
2646
|
+
const interFlashGap = 300;
|
|
2647
|
+
const interGroupGap = 800;
|
|
2648
|
+
const activeDur = groups.reduce((sum, g) => sum + g * flashDur + (g - 1) * interFlashGap, 0) + (groups.length - 1) * interGroupGap;
|
|
2649
|
+
const eclipseDur = Math.max(period - activeDur, 500);
|
|
2650
|
+
for (let gi = 0; gi < groups.length; gi++) {
|
|
2651
|
+
const g = groups[gi];
|
|
2652
|
+
for (let i = 0; i < g; i++) {
|
|
2653
|
+
phases.push({ on: true, duration: flashDur, color: hex });
|
|
2654
|
+
if (i < g - 1) phases.push({ on: false, duration: interFlashGap });
|
|
2655
|
+
}
|
|
2656
|
+
if (gi < groups.length - 1) phases.push({ on: false, duration: interGroupGap });
|
|
2657
|
+
}
|
|
2658
|
+
phases.push({ on: false, duration: eclipseDur });
|
|
2659
|
+
break;
|
|
2660
|
+
}
|
|
2661
|
+
case "LFl":
|
|
2662
|
+
case "S.LFl": {
|
|
2663
|
+
const onDur = 2e3;
|
|
2664
|
+
phases.push({ on: true, duration: onDur, color: hex });
|
|
2665
|
+
phases.push({ on: false, duration: Math.max(period - onDur, 1e3) });
|
|
2666
|
+
break;
|
|
2667
|
+
}
|
|
2668
|
+
case "Oc": {
|
|
2669
|
+
const offDur = 400;
|
|
2670
|
+
const interGroupOn = 300;
|
|
2671
|
+
const interGroupGap = 800;
|
|
2672
|
+
const totalOcclusions = totalFlashes;
|
|
2673
|
+
const activeDur = totalOcclusions * offDur + (totalOcclusions - 1) * interGroupOn + (groups.length > 1 ? (groups.length - 1) * (interGroupGap - interGroupOn) : 0);
|
|
2674
|
+
const onDur = Math.max(period - activeDur, 500);
|
|
2675
|
+
phases.push({ on: true, duration: onDur, color: hex });
|
|
2676
|
+
for (let gi = 0; gi < groups.length; gi++) {
|
|
2677
|
+
const g = groups[gi];
|
|
2678
|
+
for (let i = 0; i < g; i++) {
|
|
2679
|
+
phases.push({ on: false, duration: offDur });
|
|
2680
|
+
if (i < g - 1) phases.push({ on: true, duration: interGroupOn, color: hex });
|
|
2681
|
+
}
|
|
2682
|
+
if (gi < groups.length - 1) phases.push({ on: true, duration: interGroupGap, color: hex });
|
|
2683
|
+
}
|
|
2684
|
+
break;
|
|
2685
|
+
}
|
|
2686
|
+
case "Iso": {
|
|
2687
|
+
const half = period / 2;
|
|
2688
|
+
phases.push({ on: true, duration: half, color: hex });
|
|
2689
|
+
phases.push({ on: false, duration: half });
|
|
2690
|
+
break;
|
|
2691
|
+
}
|
|
2692
|
+
case "Q": {
|
|
2693
|
+
const cycleDur = 1e3;
|
|
2694
|
+
const count = Math.max(totalFlashes, 1);
|
|
2695
|
+
if (count === 1) {
|
|
2696
|
+
phases.push({ on: true, duration: cycleDur / 2, color: hex });
|
|
2697
|
+
phases.push({ on: false, duration: cycleDur / 2 });
|
|
2698
|
+
} else {
|
|
2699
|
+
const flashDur = 250;
|
|
2700
|
+
const gap = 250;
|
|
2701
|
+
for (let i = 0; i < count; i++) {
|
|
2702
|
+
phases.push({ on: true, duration: flashDur, color: hex });
|
|
2703
|
+
if (i < count - 1) phases.push({ on: false, duration: gap });
|
|
2704
|
+
}
|
|
2705
|
+
phases.push({ on: false, duration: Math.max(period - count * (flashDur + gap), 500) });
|
|
2706
|
+
}
|
|
2707
|
+
break;
|
|
2708
|
+
}
|
|
2709
|
+
case "VQ": {
|
|
2710
|
+
const flashDur = 150;
|
|
2711
|
+
const gap = 150;
|
|
2712
|
+
const count = Math.max(totalFlashes, 1);
|
|
2713
|
+
if (count === 1) {
|
|
2714
|
+
phases.push({ on: true, duration: flashDur, color: hex });
|
|
2715
|
+
phases.push({ on: false, duration: gap });
|
|
2716
|
+
} else {
|
|
2717
|
+
for (let i = 0; i < count; i++) {
|
|
2718
|
+
phases.push({ on: true, duration: flashDur, color: hex });
|
|
2719
|
+
if (i < count - 1) phases.push({ on: false, duration: gap });
|
|
2720
|
+
}
|
|
2721
|
+
phases.push({ on: false, duration: Math.max(period - count * (flashDur + gap), 500) });
|
|
2722
|
+
}
|
|
2723
|
+
break;
|
|
2724
|
+
}
|
|
2725
|
+
case "UQ": {
|
|
2726
|
+
const flashDur = 80;
|
|
2727
|
+
const gap = 80;
|
|
2728
|
+
phases.push({ on: true, duration: flashDur, color: hex });
|
|
2729
|
+
phases.push({ on: false, duration: gap });
|
|
2730
|
+
break;
|
|
2731
|
+
}
|
|
2732
|
+
case "Mo": {
|
|
2733
|
+
const dot = 300;
|
|
2734
|
+
const dash = 900;
|
|
2735
|
+
const gap = 300;
|
|
2736
|
+
phases.push({ on: true, duration: dot, color: hex });
|
|
2737
|
+
phases.push({ on: false, duration: gap });
|
|
2738
|
+
phases.push({ on: true, duration: dash, color: hex });
|
|
2739
|
+
phases.push({ on: false, duration: gap });
|
|
2740
|
+
phases.push({ on: true, duration: dot, color: hex });
|
|
2741
|
+
phases.push({ on: false, duration: Math.max(period - 3 * gap - dot * 2 - dash, 1e3) });
|
|
2742
|
+
break;
|
|
2743
|
+
}
|
|
2744
|
+
case "Al":
|
|
2745
|
+
case "Al.Gp":
|
|
2746
|
+
case "Al.Fl":
|
|
2747
|
+
case "Al.LFl":
|
|
2748
|
+
case "Al.Oc":
|
|
2749
|
+
case "Al.F.Fl": {
|
|
2750
|
+
const isFlashing = litchr.includes("Fl") || litchr === "Al.Gp";
|
|
2751
|
+
const isOcculting = litchr.includes("Oc");
|
|
2752
|
+
const isFixed = litchr === "Al" || litchr === "Al.F.Fl";
|
|
2753
|
+
if (isFlashing && totalFlashes > 1) {
|
|
2754
|
+
const flashDur = 400;
|
|
2755
|
+
const interFlashGap = 300;
|
|
2756
|
+
const interGroupGap = 800;
|
|
2757
|
+
const activeDur = groups.reduce((sum, g) => sum + g * flashDur + (g - 1) * interFlashGap, 0) + (groups.length - 1) * interGroupGap;
|
|
2758
|
+
const eclipseDur = Math.max(period - activeDur, 500);
|
|
2759
|
+
for (let gi = 0; gi < groups.length; gi++) {
|
|
2760
|
+
const g = groups[gi];
|
|
2761
|
+
const groupColour = colours[gi % colours.length];
|
|
2762
|
+
for (let i = 0; i < g; i++) {
|
|
2763
|
+
phases.push({ on: true, duration: flashDur, color: groupColour });
|
|
2764
|
+
if (i < g - 1) phases.push({ on: false, duration: interFlashGap });
|
|
2765
|
+
}
|
|
2766
|
+
if (gi < groups.length - 1) phases.push({ on: false, duration: interGroupGap });
|
|
2767
|
+
}
|
|
2768
|
+
phases.push({ on: false, duration: eclipseDur });
|
|
2769
|
+
} else if (isOcculting) {
|
|
2770
|
+
const numColours = Math.max(colours.length, 2);
|
|
2771
|
+
const offDur = 400;
|
|
2772
|
+
const onDur = (period - numColours * offDur) / numColours;
|
|
2773
|
+
for (let i = 0; i < numColours; i++) {
|
|
2774
|
+
phases.push({ on: true, duration: Math.max(onDur, 300), color: colours[i % colours.length] });
|
|
2775
|
+
phases.push({ on: false, duration: offDur });
|
|
2776
|
+
}
|
|
2777
|
+
} else if (isFixed) {
|
|
2778
|
+
const numColours = Math.max(colours.length, 2);
|
|
2779
|
+
const phaseDur = period / numColours;
|
|
2780
|
+
for (let i = 0; i < numColours; i++) {
|
|
2781
|
+
phases.push({ on: true, duration: phaseDur, color: colours[i % colours.length] });
|
|
2782
|
+
}
|
|
2783
|
+
} else {
|
|
2784
|
+
const numColours = Math.max(colours.length, 2);
|
|
2785
|
+
const phaseDur = period / numColours;
|
|
2786
|
+
for (let i = 0; i < numColours; i++) {
|
|
2787
|
+
phases.push({ on: true, duration: phaseDur, color: colours[i % colours.length] });
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
break;
|
|
2791
|
+
}
|
|
2792
|
+
default: {
|
|
2793
|
+
phases.push({ on: true, duration: 500, color: hex });
|
|
2794
|
+
phases.push({ on: false, duration: Math.max(period - 500, 500) });
|
|
2795
|
+
break;
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
const totalCycle = phases.reduce((sum, p) => sum + p.duration, 0);
|
|
2799
|
+
return { phases, totalCycle, notation };
|
|
2800
|
+
}
|
|
2801
|
+
function buildSectorsFromFeatures(features) {
|
|
2802
|
+
const sectors = [];
|
|
2803
|
+
for (const f of features) {
|
|
2804
|
+
const props = f.properties ?? {};
|
|
2805
|
+
const s1 = props.SECTR1 != null ? Number(props.SECTR1) : null;
|
|
2806
|
+
const s2 = props.SECTR2 != null ? Number(props.SECTR2) : null;
|
|
2807
|
+
if (s1 == null || s2 == null || isNaN(s1) || isNaN(s2)) continue;
|
|
2808
|
+
const colourCode = parseCode(props.COLOUR);
|
|
2809
|
+
const ce = colourCode ? COLOUR_TABLE[colourCode] : null;
|
|
2810
|
+
const litchrCode = parseCode(props.LITCHR);
|
|
2811
|
+
const le = litchrCode ? LITCHR_TABLE[litchrCode] : null;
|
|
2812
|
+
const sigper = props.SIGPER != null ? Number(props.SIGPER) : null;
|
|
2813
|
+
const group = parseGroup(props.SIGGRP);
|
|
2814
|
+
const notation = buildNotation(le?.abbr ?? null, ce ? [ce.abbr] : [], sigper, group);
|
|
2815
|
+
const valnmr = props.VALNMR != null ? Number(props.VALNMR) : null;
|
|
2816
|
+
const nominalRange = valnmr != null && !isNaN(valnmr) && valnmr > 0 ? valnmr : null;
|
|
2817
|
+
sectors.push({
|
|
2818
|
+
sectorStart: s1,
|
|
2819
|
+
sectorEnd: s2,
|
|
2820
|
+
colour: ce?.name ?? "White",
|
|
2821
|
+
colourHex: ce?.hex ?? "#ffffff",
|
|
2822
|
+
notation,
|
|
2823
|
+
nominalRange
|
|
2824
|
+
});
|
|
2825
|
+
}
|
|
2826
|
+
return sectors;
|
|
2827
|
+
}
|
|
2828
|
+
function buildSectorArcGeoJSON(center, startBearing, endBearing, radiusNm = 0.5, innerRadiusNm = 0, steps = 64) {
|
|
2829
|
+
const radiusDeg = radiusNm / 60;
|
|
2830
|
+
const innerDeg = innerRadiusNm > 0 ? innerRadiusNm / 60 : 0;
|
|
2831
|
+
const [lng, lat] = center;
|
|
2832
|
+
const cosLat = Math.cos(lat * Math.PI / 180);
|
|
2833
|
+
const s = (startBearing % 360 + 360) % 360;
|
|
2834
|
+
const e = (endBearing % 360 + 360) % 360;
|
|
2835
|
+
let sweep = e - s;
|
|
2836
|
+
if (sweep <= 0) sweep += 360;
|
|
2837
|
+
const project = (bearingDeg, rDeg) => {
|
|
2838
|
+
const rad = bearingDeg * Math.PI / 180;
|
|
2839
|
+
return [
|
|
2840
|
+
lng + rDeg * Math.sin(rad) / cosLat,
|
|
2841
|
+
lat + rDeg * Math.cos(rad)
|
|
2842
|
+
];
|
|
2843
|
+
};
|
|
2844
|
+
const outerCoords = [];
|
|
2845
|
+
for (let i = 0; i <= steps; i++) {
|
|
2846
|
+
outerCoords.push(project(s + sweep * i / steps, radiusDeg));
|
|
2847
|
+
}
|
|
2848
|
+
let ring;
|
|
2849
|
+
if (innerDeg > 0) {
|
|
2850
|
+
const innerCoords = [];
|
|
2851
|
+
for (let i = steps; i >= 0; i--) {
|
|
2852
|
+
innerCoords.push(project(s + sweep * i / steps, innerDeg));
|
|
2853
|
+
}
|
|
2854
|
+
ring = [...outerCoords, ...innerCoords, outerCoords[0]];
|
|
2855
|
+
} else {
|
|
2856
|
+
ring = [[lng, lat], ...outerCoords, [lng, lat]];
|
|
2857
|
+
}
|
|
2858
|
+
return {
|
|
2859
|
+
type: "Feature",
|
|
2860
|
+
properties: {},
|
|
2861
|
+
geometry: {
|
|
2862
|
+
type: "Polygon",
|
|
2863
|
+
coordinates: [ring]
|
|
2864
|
+
}
|
|
2865
|
+
};
|
|
2866
|
+
}
|
|
2867
|
+
function buildSectorArcsFC(center, sectors, radiusNm = 0.4, innerRadiusFrac = 0.92) {
|
|
2868
|
+
const innerNm = innerRadiusFrac > 0 ? radiusNm * innerRadiusFrac : 0;
|
|
2869
|
+
const features = sectors.map((sec, i) => {
|
|
2870
|
+
const arc = buildSectorArcGeoJSON(center, sec.sectorStart, sec.sectorEnd, radiusNm, innerNm);
|
|
2871
|
+
arc.properties = {
|
|
2872
|
+
sectorIndex: i,
|
|
2873
|
+
colour: sec.colour,
|
|
2874
|
+
colourHex: sec.colourHex,
|
|
2875
|
+
notation: sec.notation,
|
|
2876
|
+
sectorStart: sec.sectorStart,
|
|
2877
|
+
sectorEnd: sec.sectorEnd,
|
|
2878
|
+
nominalRange: sec.nominalRange ?? null
|
|
2879
|
+
};
|
|
2880
|
+
return arc;
|
|
2881
|
+
});
|
|
2882
|
+
const lineFeatures = [];
|
|
2883
|
+
const labelFeatures = [];
|
|
2884
|
+
const seenBearings = /* @__PURE__ */ new Set();
|
|
2885
|
+
const TICK_OUTER_OVERHANG = 1.05;
|
|
2886
|
+
const TICK_INNER_OVERHANG = 0.93;
|
|
2887
|
+
for (const sec of sectors) {
|
|
2888
|
+
for (const bearing of [sec.sectorStart, sec.sectorEnd]) {
|
|
2889
|
+
const rad = bearing * Math.PI / 180;
|
|
2890
|
+
const radiusDeg = radiusNm / 60;
|
|
2891
|
+
const innerRadiusDeg = innerNm > 0 ? innerNm / 60 : 0;
|
|
2892
|
+
const cosLat = Math.cos(center[1] * Math.PI / 180);
|
|
2893
|
+
const sinB = Math.sin(rad);
|
|
2894
|
+
const cosB = Math.cos(rad);
|
|
2895
|
+
const tickOuterDeg = radiusDeg * TICK_OUTER_OVERHANG;
|
|
2896
|
+
const outerPt = [
|
|
2897
|
+
center[0] + tickOuterDeg * sinB / cosLat,
|
|
2898
|
+
center[1] + tickOuterDeg * cosB
|
|
2899
|
+
];
|
|
2900
|
+
const tickInnerDeg = innerRadiusDeg > 0 ? Math.min(innerRadiusDeg * TICK_INNER_OVERHANG, innerRadiusDeg) : 0;
|
|
2901
|
+
const innerPt = tickInnerDeg > 0 ? [center[0] + tickInnerDeg * sinB / cosLat, center[1] + tickInnerDeg * cosB] : center;
|
|
2902
|
+
lineFeatures.push({
|
|
2903
|
+
type: "Feature",
|
|
2904
|
+
properties: { type: "sector-boundary" },
|
|
2905
|
+
geometry: {
|
|
2906
|
+
type: "LineString",
|
|
2907
|
+
coordinates: [innerPt, outerPt]
|
|
2908
|
+
}
|
|
2909
|
+
});
|
|
2910
|
+
const roundedBearing = Math.round(bearing * 10) / 10;
|
|
2911
|
+
if (!seenBearings.has(roundedBearing)) {
|
|
2912
|
+
seenBearings.add(roundedBearing);
|
|
2913
|
+
const labelRadius = radiusDeg * 1.12;
|
|
2914
|
+
const ldlat = labelRadius * Math.cos(rad);
|
|
2915
|
+
const ldlng = labelRadius * Math.sin(rad) / cosLat;
|
|
2916
|
+
labelFeatures.push({
|
|
2917
|
+
type: "Feature",
|
|
2918
|
+
properties: {
|
|
2919
|
+
type: "bearing-label",
|
|
2920
|
+
bearing: `${roundedBearing}\xB0`,
|
|
2921
|
+
bearingDeg: roundedBearing
|
|
2922
|
+
},
|
|
2923
|
+
geometry: {
|
|
2924
|
+
type: "Point",
|
|
2925
|
+
coordinates: [center[0] + ldlng, center[1] + ldlat]
|
|
2926
|
+
}
|
|
2927
|
+
});
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
const rangeRingFeatures = [];
|
|
2932
|
+
const seenRanges = /* @__PURE__ */ new Set();
|
|
2933
|
+
for (const sec of sectors) {
|
|
2934
|
+
if (sec.nominalRange != null && sec.nominalRange > 0 && !seenRanges.has(sec.nominalRange)) {
|
|
2935
|
+
seenRanges.add(sec.nominalRange);
|
|
2936
|
+
const rangeDeg = sec.nominalRange / 60;
|
|
2937
|
+
const cosLat = Math.cos(center[1] * Math.PI / 180);
|
|
2938
|
+
const ringCoords = [];
|
|
2939
|
+
const ringSteps = 72;
|
|
2940
|
+
for (let i = 0; i <= ringSteps; i++) {
|
|
2941
|
+
const angle = 360 * i / ringSteps;
|
|
2942
|
+
const rad = angle * Math.PI / 180;
|
|
2943
|
+
const dlat = rangeDeg * Math.cos(rad);
|
|
2944
|
+
const dlng = rangeDeg * Math.sin(rad) / cosLat;
|
|
2945
|
+
ringCoords.push([center[0] + dlng, center[1] + dlat]);
|
|
2946
|
+
}
|
|
2947
|
+
rangeRingFeatures.push({
|
|
2948
|
+
type: "Feature",
|
|
2949
|
+
properties: { type: "range-ring", nominalRange: sec.nominalRange },
|
|
2950
|
+
geometry: { type: "LineString", coordinates: ringCoords }
|
|
2951
|
+
});
|
|
2952
|
+
rangeRingFeatures.push({
|
|
2953
|
+
type: "Feature",
|
|
2954
|
+
properties: { type: "range-label", rangeLabel: `${sec.nominalRange} NM` },
|
|
2955
|
+
geometry: { type: "Point", coordinates: [center[0], center[1] + rangeDeg] }
|
|
2956
|
+
});
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
return {
|
|
2960
|
+
type: "FeatureCollection",
|
|
2961
|
+
features: [...features, ...lineFeatures, ...labelFeatures, ...rangeRingFeatures]
|
|
2962
|
+
};
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2965
|
+
// src/interact/notices.ts
|
|
2966
|
+
var NON_SOLAS_DISCLAIMER = "\u26A0\uFE0F CHRT data is derived from official electronic navigational charts but is NOT FOR PRIMARY NAVIGATION and is NOT SOLAS-compliant. Always use official, type-approved ECDIS or paper charts for navigation safety. CHRT provides situational awareness data only.";
|
|
2967
|
+
var NON_SOLAS_DISCLAIMER_SHORT = "Not for primary navigation";
|
|
2968
|
+
function baseNotice() {
|
|
2969
|
+
return {
|
|
2970
|
+
disclaimer: NON_SOLAS_DISCLAIMER,
|
|
2971
|
+
attribution: "Derived from official Electronic Navigational Charts issued by national hydrographic offices (e.g. NOAA).",
|
|
2972
|
+
dataCurrency: "Tile attributes carry no per-feature edition/update metadata; call inspect({ enrich: true }) to resolve the source cell's edition, update_number and issue_date."
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
function enrichedNotice(cell) {
|
|
2976
|
+
const title = cell.title ? ` \u2014 ${cell.title}` : "";
|
|
2977
|
+
return {
|
|
2978
|
+
disclaimer: NON_SOLAS_DISCLAIMER,
|
|
2979
|
+
attribution: `Official ENC data \xA9 ${cell.chart_source} (cell ${cell.cell_id}${title}).`,
|
|
2980
|
+
dataCurrency: `Edition ${cell.edition ?? "unknown"}, update ${cell.update_number ?? "unknown"}, issued ${cell.issue_date ?? "unknown"}.`
|
|
2981
|
+
};
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
// src/interact/errors.ts
|
|
2985
|
+
var InteractError = class extends Error {
|
|
2986
|
+
code;
|
|
2987
|
+
status;
|
|
2988
|
+
/** The team's current plan id, when the server included it (denials). */
|
|
2989
|
+
plan;
|
|
2990
|
+
constructor(code, message, opts) {
|
|
2991
|
+
super(message);
|
|
2992
|
+
this.name = "InteractError";
|
|
2993
|
+
this.code = code;
|
|
2994
|
+
this.status = opts?.status;
|
|
2995
|
+
this.plan = opts?.plan;
|
|
2996
|
+
}
|
|
2997
|
+
};
|
|
2998
|
+
|
|
2999
|
+
// src/interact/inspect.ts
|
|
3000
|
+
var NAV_AID_RAW_LAYERS = new Set(
|
|
3001
|
+
NAV_AID_QUERY_LAYERS.map((id) => stripLayerId(id))
|
|
3002
|
+
);
|
|
3003
|
+
var BRIDGE_RAW_LAYERS = new Set(BRIDGE_QUERY_LAYERS.map((id) => stripLayerId(id)));
|
|
3004
|
+
function stripLayerId(layerId) {
|
|
3005
|
+
return layerId.replace(/^(enc|s52)-/, "").replace(/-(point|line|polygon|midpoint|outline)$/, "").replace(/-(point|line|polygon|midpoint)$/, "");
|
|
3006
|
+
}
|
|
3007
|
+
function resolveLayerId(feature, opts) {
|
|
3008
|
+
if (opts?.layer) {
|
|
3009
|
+
return opts.layer.includes("-") || opts.layer.startsWith("enc") ? opts.layer : `enc-${opts.layer}`;
|
|
3010
|
+
}
|
|
3011
|
+
const layer = feature.layer;
|
|
3012
|
+
if (typeof layer === "string" && layer) {
|
|
3013
|
+
return layer.includes("-") ? layer : `enc-${layer}`;
|
|
3014
|
+
}
|
|
3015
|
+
if (layer && typeof layer === "object" && typeof layer.id === "string" && layer.id) {
|
|
3016
|
+
return layer.id;
|
|
3017
|
+
}
|
|
3018
|
+
if (feature.sourceLayer) return `enc-${feature.sourceLayer}`;
|
|
3019
|
+
throw new InteractError(
|
|
3020
|
+
"unknown_layer",
|
|
3021
|
+
"inspect(): cannot determine the feature's chart layer \u2014 pass { layer } or a feature from queryRenderedFeatures()"
|
|
3022
|
+
);
|
|
3023
|
+
}
|
|
3024
|
+
function resolveCoordinates(feature, opts) {
|
|
3025
|
+
if (opts?.coordinates) return opts.coordinates;
|
|
3026
|
+
const geom = feature.geometry;
|
|
3027
|
+
if (geom && geom.type === "Point" && Array.isArray(geom.coordinates)) {
|
|
3028
|
+
const [lng, lat] = geom.coordinates;
|
|
3029
|
+
if (typeof lng === "number" && typeof lat === "number") return [lng, lat];
|
|
3030
|
+
}
|
|
3031
|
+
return null;
|
|
3032
|
+
}
|
|
3033
|
+
function navAidPropertyRows(decoded) {
|
|
3034
|
+
const rows = [];
|
|
3035
|
+
const push = (label, value) => {
|
|
3036
|
+
if (value == null || value === "") return;
|
|
3037
|
+
rows.push({ label, value: String(value) });
|
|
3038
|
+
};
|
|
3039
|
+
push("Colour", decoded.colour);
|
|
3040
|
+
push("Shape", decoded.shape);
|
|
3041
|
+
push("Topmark", decoded.topmark);
|
|
3042
|
+
push("Topmark meaning", decoded.topmarkMeaning);
|
|
3043
|
+
push("Light", decoded.lightCharacterFull);
|
|
3044
|
+
push("Characteristic", decoded.notation || null);
|
|
3045
|
+
if (decoded.orientation != null) push("Orientation", `${decoded.orientation}\xB0`);
|
|
3046
|
+
push("Fog Signal", decoded.fogSignal);
|
|
3047
|
+
if (decoded.radarReflector) push("Radar", "Reflector");
|
|
3048
|
+
if (decoded.depth != null) push("Depth", `${decoded.depth}m`);
|
|
3049
|
+
push("Status", decoded.status);
|
|
3050
|
+
push("System", decoded.markingSystem);
|
|
3051
|
+
push("Category", decoded.category);
|
|
3052
|
+
return rows;
|
|
3053
|
+
}
|
|
3054
|
+
function bridgePropertyRows(decoded) {
|
|
3055
|
+
const rows = [];
|
|
3056
|
+
const push = (label, value) => {
|
|
3057
|
+
if (value == null || value === "") return;
|
|
3058
|
+
rows.push({ label, value });
|
|
3059
|
+
};
|
|
3060
|
+
push("Category", decoded.categoryLabel);
|
|
3061
|
+
if (decoded.verticalClearanceClosed != null && decoded.verticalClearanceOpen != null) {
|
|
3062
|
+
push(
|
|
3063
|
+
"Vertical clearance",
|
|
3064
|
+
`Closed: ${decoded.verticalClearanceClosed}m / Open: ${decoded.verticalClearanceOpen}m`
|
|
3065
|
+
);
|
|
3066
|
+
} else if (decoded.verticalClearance != null) {
|
|
3067
|
+
push("Vertical clearance", `${decoded.verticalClearance}m`);
|
|
3068
|
+
} else if (decoded.verticalClearanceClosed != null) {
|
|
3069
|
+
push("Vertical clearance", `${decoded.verticalClearanceClosed}m (closed)`);
|
|
3070
|
+
} else if (decoded.verticalClearanceOpen != null) {
|
|
3071
|
+
push("Vertical clearance", `${decoded.verticalClearanceOpen}m (open)`);
|
|
3072
|
+
}
|
|
3073
|
+
if (decoded.horizontalClearance != null) {
|
|
3074
|
+
push("Horizontal clearance", `${decoded.horizontalClearance}m`);
|
|
3075
|
+
}
|
|
3076
|
+
push("Status", decoded.status);
|
|
3077
|
+
push("Condition", decoded.condition);
|
|
3078
|
+
push("National name", decoded.nationalName);
|
|
3079
|
+
push("Source", decoded.sourceIndication);
|
|
3080
|
+
push("Source date", decoded.sourceDate);
|
|
3081
|
+
return rows;
|
|
3082
|
+
}
|
|
3083
|
+
function buildInspectResult(feature, opts) {
|
|
3084
|
+
const layerId = resolveLayerId(feature, opts);
|
|
3085
|
+
const rawLayer = stripLayerId(layerId);
|
|
3086
|
+
const props = { ...feature.properties ?? {} };
|
|
3087
|
+
const coords = resolveCoordinates(feature, opts);
|
|
3088
|
+
const notice = baseNotice();
|
|
3089
|
+
if (BRIDGE_RAW_LAYERS.has(rawLayer)) {
|
|
3090
|
+
const bridge = decodeBridgeProps(props);
|
|
3091
|
+
const result2 = {
|
|
3092
|
+
kind: "bridge",
|
|
3093
|
+
layer: "BRIDGE",
|
|
3094
|
+
layerId,
|
|
3095
|
+
objectClass: bridge.categoryLabel ?? "Bridge",
|
|
3096
|
+
title: bridge.name || bridge.categoryLabel || "Bridge",
|
|
3097
|
+
coordinates: coords,
|
|
3098
|
+
properties: bridgePropertyRows(bridge),
|
|
3099
|
+
raw: props,
|
|
3100
|
+
notice,
|
|
3101
|
+
enrichment: null,
|
|
3102
|
+
bridge
|
|
3103
|
+
};
|
|
3104
|
+
return result2;
|
|
3105
|
+
}
|
|
3106
|
+
if (NAV_AID_RAW_LAYERS.has(rawLayer)) {
|
|
3107
|
+
const navAid = decodeNavAidProps(layerId, props, coords ?? void 0);
|
|
3108
|
+
const flashPattern = buildFlashPattern(navAid);
|
|
3109
|
+
const result2 = {
|
|
3110
|
+
kind: "navaid",
|
|
3111
|
+
layer: navAid.layerName,
|
|
3112
|
+
layerId,
|
|
3113
|
+
objectClass: navAid.type,
|
|
3114
|
+
title: navAid.fullName || navAid.name || navAid.type,
|
|
3115
|
+
coordinates: navAid.coordinates,
|
|
3116
|
+
properties: navAidPropertyRows(navAid),
|
|
3117
|
+
raw: props,
|
|
3118
|
+
notice,
|
|
3119
|
+
enrichment: null,
|
|
3120
|
+
navAid,
|
|
3121
|
+
characteristic: navAid.notation || null,
|
|
3122
|
+
flashPattern
|
|
3123
|
+
};
|
|
3124
|
+
return result2;
|
|
3125
|
+
}
|
|
3126
|
+
const area = decodeAreaFeatureProps(layerId, props);
|
|
3127
|
+
const result = {
|
|
3128
|
+
kind: "area",
|
|
3129
|
+
layer: rawLayer,
|
|
3130
|
+
layerId,
|
|
3131
|
+
objectClass: area.type,
|
|
3132
|
+
title: area.title,
|
|
3133
|
+
coordinates: coords,
|
|
3134
|
+
properties: area.properties,
|
|
3135
|
+
raw: props,
|
|
3136
|
+
notice,
|
|
3137
|
+
enrichment: null,
|
|
3138
|
+
area
|
|
3139
|
+
};
|
|
3140
|
+
return result;
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
// src/interact/attach.ts
|
|
3144
|
+
function bboxAround(point, pad) {
|
|
3145
|
+
return [
|
|
3146
|
+
[point.x - pad, point.y - pad],
|
|
3147
|
+
[point.x + pad, point.y + pad]
|
|
3148
|
+
];
|
|
3149
|
+
}
|
|
3150
|
+
function existingLayers(map, ids) {
|
|
3151
|
+
return ids.filter((id) => {
|
|
3152
|
+
try {
|
|
3153
|
+
return Boolean(map.getLayer(id));
|
|
3154
|
+
} catch {
|
|
3155
|
+
return false;
|
|
3156
|
+
}
|
|
3157
|
+
});
|
|
3158
|
+
}
|
|
3159
|
+
function query(map, geometry, layers) {
|
|
3160
|
+
if (layers.length === 0) return [];
|
|
3161
|
+
try {
|
|
3162
|
+
return map.queryRenderedFeatures(geometry, { layers }) ?? [];
|
|
3163
|
+
} catch {
|
|
3164
|
+
return [];
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
function featureLayerId(f) {
|
|
3168
|
+
const layer = f.layer;
|
|
3169
|
+
if (layer && typeof layer === "object" && typeof layer.id === "string") return layer.id;
|
|
3170
|
+
if (typeof layer === "string") return layer;
|
|
3171
|
+
return f.sourceLayer ? `enc-${f.sourceLayer}` : "";
|
|
3172
|
+
}
|
|
3173
|
+
function decodeClickedNavAid(map, feature, e, navAidLayers) {
|
|
3174
|
+
const layerId = featureLayerId(feature);
|
|
3175
|
+
const props = { ...feature.properties ?? {} };
|
|
3176
|
+
const geom = feature.geometry;
|
|
3177
|
+
const coords = geom?.type === "Point" && Array.isArray(geom.coordinates) ? [geom.coordinates[0], geom.coordinates[1]] : [e.lngLat.lng, e.lngLat.lat];
|
|
3178
|
+
const rawLayer = stripLayerId(layerId);
|
|
3179
|
+
if (!props.OBJNAM && CHILD_LAYERS.has(rawLayer)) {
|
|
3180
|
+
const parentLayers = existingLayers(map, PARENT_LAYERS);
|
|
3181
|
+
const parents = query(map, bboxAround(e.point, 8), parentLayers);
|
|
3182
|
+
for (const p of parents) {
|
|
3183
|
+
const pName = p.properties?.OBJNAM;
|
|
3184
|
+
if (pName) {
|
|
3185
|
+
props.OBJNAM = pName;
|
|
3186
|
+
if (!props.COLOUR && p.properties?.COLOUR) {
|
|
3187
|
+
props.COLOUR = p.properties.COLOUR;
|
|
3188
|
+
}
|
|
3189
|
+
break;
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
if (!props.CATFOG) {
|
|
3194
|
+
const fogFeats = query(map, bboxAround(e.point, 10), existingLayers(map, ["enc-FOGSIG"]));
|
|
3195
|
+
for (const f of fogFeats) {
|
|
3196
|
+
if (f.properties?.CATFOG) {
|
|
3197
|
+
props.CATFOG = f.properties.CATFOG;
|
|
3198
|
+
break;
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
if (!props.RETRFL) {
|
|
3203
|
+
const radarFeats = query(map, bboxAround(e.point, 10), existingLayers(map, ["enc-RTPBCN"]));
|
|
3204
|
+
if (radarFeats.length > 0) props.RETRFL = "1";
|
|
3205
|
+
}
|
|
3206
|
+
if (!props.TOPSHP) {
|
|
3207
|
+
const topmarFeats = query(map, bboxAround(e.point, 10), existingLayers(map, ["enc-TOPMAR"]));
|
|
3208
|
+
for (const f of topmarFeats) {
|
|
3209
|
+
if (f.properties?.TOPSHP) {
|
|
3210
|
+
props.TOPSHP = f.properties.TOPSHP;
|
|
3211
|
+
break;
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
const lightLayerIds = existingLayers(
|
|
3216
|
+
map,
|
|
3217
|
+
navAidLayers.filter((id) => id === "enc-LIGHTS" || id === "enc-LIGHTS-point")
|
|
3218
|
+
);
|
|
3219
|
+
let coLocatedLightFeats = null;
|
|
3220
|
+
const isLightsLayer = layerId.includes("LIGHTS");
|
|
3221
|
+
if (!props.LITCHR) {
|
|
3222
|
+
const lightFeats = query(map, bboxAround(e.point, 10), lightLayerIds);
|
|
3223
|
+
if (lightFeats.length > 0) {
|
|
3224
|
+
coLocatedLightFeats = lightFeats;
|
|
3225
|
+
let primaryLight = null;
|
|
3226
|
+
for (const lf of lightFeats) {
|
|
3227
|
+
if (lf.properties?.LITCHR) {
|
|
3228
|
+
if (!lf.properties?.SECTR1 && !lf.properties?.SECTR2) {
|
|
3229
|
+
primaryLight = lf;
|
|
3230
|
+
break;
|
|
3231
|
+
}
|
|
3232
|
+
if (!primaryLight) primaryLight = lf;
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
if (primaryLight?.properties) {
|
|
3236
|
+
props.LITCHR = primaryLight.properties.LITCHR;
|
|
3237
|
+
if (!props.SIGPER && primaryLight.properties.SIGPER) props.SIGPER = primaryLight.properties.SIGPER;
|
|
3238
|
+
if (!props.SIGGRP && primaryLight.properties.SIGGRP) props.SIGGRP = primaryLight.properties.SIGGRP;
|
|
3239
|
+
if (primaryLight.properties.COLOUR) props.LIGHT_COLOUR = primaryLight.properties.COLOUR;
|
|
3240
|
+
if (primaryLight.properties.CATLIT) props.CATLIT = primaryLight.properties.CATLIT;
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
const result = buildInspectResult(
|
|
3245
|
+
{ ...feature, properties: props },
|
|
3246
|
+
{ layer: layerId, coordinates: coords }
|
|
3247
|
+
);
|
|
3248
|
+
if (result.kind === "navaid") {
|
|
3249
|
+
let sectorSources = null;
|
|
3250
|
+
if (isLightsLayer) {
|
|
3251
|
+
const nearby = query(map, bboxAround(e.point, 12), lightLayerIds);
|
|
3252
|
+
if (nearby.length > 1) sectorSources = nearby;
|
|
3253
|
+
} else if (coLocatedLightFeats && coLocatedLightFeats.length > 0) {
|
|
3254
|
+
sectorSources = coLocatedLightFeats;
|
|
3255
|
+
}
|
|
3256
|
+
if (sectorSources) {
|
|
3257
|
+
const sectors = buildSectorsFromFeatures(
|
|
3258
|
+
sectorSources.map((f) => ({ properties: f.properties ?? {} }))
|
|
3259
|
+
);
|
|
3260
|
+
if (sectors.length > 0) {
|
|
3261
|
+
result.navAid.sectors = sectors;
|
|
3262
|
+
result.navAid.isSectorLight = true;
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
return result;
|
|
3267
|
+
}
|
|
3268
|
+
function decodeClick(map, e, opts) {
|
|
3269
|
+
const navAidLayers = opts.navAidLayers ?? NAV_AID_QUERY_LAYERS;
|
|
3270
|
+
const bridgeLayers = opts.bridgeLayers ?? BRIDGE_QUERY_LAYERS;
|
|
3271
|
+
const areaLayers = opts.areaLayers ?? AREA_QUERY_LAYERS;
|
|
3272
|
+
const navFeatures = query(map, e.point, existingLayers(map, navAidLayers));
|
|
3273
|
+
if (navFeatures.length > 0) {
|
|
3274
|
+
const feature = navFeatures[0];
|
|
3275
|
+
return { result: decodeClickedNavAid(map, feature, e, navAidLayers), feature };
|
|
3276
|
+
}
|
|
3277
|
+
const bridgeFeatures = query(map, e.point, existingLayers(map, bridgeLayers));
|
|
3278
|
+
if (bridgeFeatures.length > 0) {
|
|
3279
|
+
const feature = bridgeFeatures[0];
|
|
3280
|
+
const result = buildInspectResult(feature, {
|
|
3281
|
+
layer: featureLayerId(feature) || "enc-BRIDGE",
|
|
3282
|
+
coordinates: [e.lngLat.lng, e.lngLat.lat]
|
|
3283
|
+
});
|
|
3284
|
+
return { result, feature };
|
|
3285
|
+
}
|
|
3286
|
+
const areaFeatures = query(map, e.point, existingLayers(map, areaLayers));
|
|
3287
|
+
if (areaFeatures.length > 0) {
|
|
3288
|
+
const feature = areaFeatures[0];
|
|
3289
|
+
const result = buildInspectResult(feature, {
|
|
3290
|
+
layer: featureLayerId(feature),
|
|
3291
|
+
coordinates: [e.lngLat.lng, e.lngLat.lat]
|
|
3292
|
+
});
|
|
3293
|
+
return { result, feature };
|
|
3294
|
+
}
|
|
3295
|
+
return null;
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3298
|
+
// src/interact/entitlement.ts
|
|
3299
|
+
function mapDenialCode(raw, status) {
|
|
3300
|
+
if (raw === "interact_not_enabled") return "interact_not_enabled";
|
|
3301
|
+
if (status === 401) return "invalid_api_key";
|
|
3302
|
+
if (status === 403) return "missing_scope";
|
|
3303
|
+
return "unexpected_response";
|
|
3304
|
+
}
|
|
3305
|
+
var EntitlementClient = class {
|
|
3306
|
+
constructor(http) {
|
|
3307
|
+
this.http = http;
|
|
3308
|
+
}
|
|
3309
|
+
cached = null;
|
|
3310
|
+
pending = null;
|
|
3311
|
+
/**
|
|
3312
|
+
* Check (and cache) the interact entitlement for this client's API key.
|
|
3313
|
+
* Concurrent callers share one in-flight request; a denial is NOT cached so
|
|
3314
|
+
* a plan upgrade takes effect on the next call.
|
|
3315
|
+
*/
|
|
3316
|
+
async check() {
|
|
3317
|
+
if (this.cached) return this.cached;
|
|
3318
|
+
if (this.pending) return this.pending;
|
|
3319
|
+
this.pending = (async () => {
|
|
3320
|
+
try {
|
|
3321
|
+
const result = await this.checkOnce();
|
|
3322
|
+
this.cached = result;
|
|
3323
|
+
return result;
|
|
3324
|
+
} finally {
|
|
3325
|
+
this.pending = null;
|
|
3326
|
+
}
|
|
3327
|
+
})();
|
|
3328
|
+
return this.pending;
|
|
3329
|
+
}
|
|
3330
|
+
async checkOnce() {
|
|
3331
|
+
let res;
|
|
3332
|
+
try {
|
|
3333
|
+
res = await this.http.postJson("/interact/entitlement", {});
|
|
3334
|
+
} catch (err) {
|
|
3335
|
+
throw new InteractError(
|
|
3336
|
+
"network_error",
|
|
3337
|
+
err instanceof Error ? err.message : "entitlement request failed"
|
|
3338
|
+
);
|
|
3339
|
+
}
|
|
3340
|
+
if (!res.ok) {
|
|
3341
|
+
const code = mapDenialCode(res.body?.error, res.status);
|
|
3342
|
+
const message = typeof res.body?.message === "string" ? res.body.message : code === "interact_not_enabled" ? "CHRTinteract requires the Dev plan or above. Upgrade at https://chrt.co/pricing." : `entitlement check failed (HTTP ${res.status})`;
|
|
3343
|
+
const plan = typeof res.body?.plan === "string" ? res.body.plan : void 0;
|
|
3344
|
+
throw new InteractError(code, message, { status: res.status, plan });
|
|
3345
|
+
}
|
|
3346
|
+
if (res.body?.enabled !== true) {
|
|
3347
|
+
throw new InteractError(
|
|
3348
|
+
"unexpected_response",
|
|
3349
|
+
"entitlement endpoint returned a malformed body",
|
|
3350
|
+
{ status: res.status }
|
|
3351
|
+
);
|
|
3352
|
+
}
|
|
3353
|
+
return {
|
|
3354
|
+
enabled: true,
|
|
3355
|
+
...typeof res.body.plan === "string" ? { plan: res.body.plan } : {}
|
|
3356
|
+
};
|
|
3357
|
+
}
|
|
3358
|
+
/** Drop the cached grant (e.g. after an API-key swap in tests). */
|
|
3359
|
+
reset() {
|
|
3360
|
+
this.cached = null;
|
|
3361
|
+
this.pending = null;
|
|
3362
|
+
}
|
|
3363
|
+
};
|
|
3364
|
+
|
|
3365
|
+
// src/interact/enrich.ts
|
|
3366
|
+
var ENRICH_BBOX_PAD_DEG = 1e-3;
|
|
3367
|
+
var ENRICH_LIMIT = 5;
|
|
3368
|
+
function featurePoint(f) {
|
|
3369
|
+
if (f.geometry?.type === "Point" && Array.isArray(f.geometry.coordinates)) {
|
|
3370
|
+
const [lng, lat] = f.geometry.coordinates;
|
|
3371
|
+
if (typeof lng === "number" && typeof lat === "number") return [lng, lat];
|
|
3372
|
+
}
|
|
3373
|
+
return null;
|
|
3374
|
+
}
|
|
3375
|
+
function distSq(a, b) {
|
|
3376
|
+
return (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2;
|
|
3377
|
+
}
|
|
3378
|
+
function matchEnrichedFeature(candidates, raw, coords) {
|
|
3379
|
+
if (candidates.length === 0) return null;
|
|
3380
|
+
const objnam = typeof raw.OBJNAM === "string" && raw.OBJNAM ? raw.OBJNAM : null;
|
|
3381
|
+
if (objnam) {
|
|
3382
|
+
const byName = candidates.find(
|
|
3383
|
+
(f) => f.properties?.OBJNAM === objnam
|
|
3384
|
+
);
|
|
3385
|
+
if (byName) return byName;
|
|
3386
|
+
}
|
|
3387
|
+
if (coords) {
|
|
3388
|
+
let best = null;
|
|
3389
|
+
let bestD = Infinity;
|
|
3390
|
+
for (const f of candidates) {
|
|
3391
|
+
const pt = featurePoint(f);
|
|
3392
|
+
if (!pt) continue;
|
|
3393
|
+
const d = distSq(pt, coords);
|
|
3394
|
+
if (d < bestD) {
|
|
3395
|
+
bestD = d;
|
|
3396
|
+
best = f;
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
if (best) return best;
|
|
3400
|
+
}
|
|
3401
|
+
return candidates[0];
|
|
3402
|
+
}
|
|
3403
|
+
async function enrichResult(http, result, signal) {
|
|
3404
|
+
const coords = result.coordinates;
|
|
3405
|
+
if (!coords) {
|
|
3406
|
+
throw new InteractError(
|
|
3407
|
+
"enrich_failed",
|
|
3408
|
+
"enrich: the feature has no coordinates to look up \u2014 pass { coordinates } to inspect()"
|
|
3409
|
+
);
|
|
3410
|
+
}
|
|
3411
|
+
const [lng, lat] = coords;
|
|
3412
|
+
const bbox = [
|
|
3413
|
+
lng - ENRICH_BBOX_PAD_DEG,
|
|
3414
|
+
lat - ENRICH_BBOX_PAD_DEG,
|
|
3415
|
+
lng + ENRICH_BBOX_PAD_DEG,
|
|
3416
|
+
lat + ENRICH_BBOX_PAD_DEG
|
|
3417
|
+
].join(",");
|
|
3418
|
+
let fc;
|
|
3419
|
+
try {
|
|
3420
|
+
fc = await http.request(
|
|
3421
|
+
"/features",
|
|
3422
|
+
{ bbox, layer: result.layer, limit: ENRICH_LIMIT },
|
|
3423
|
+
signal
|
|
3424
|
+
);
|
|
3425
|
+
} catch (err) {
|
|
3426
|
+
if (err instanceof ChrtError) {
|
|
3427
|
+
throw new InteractError("enrich_failed", `features lookup failed: ${err.message}`, {
|
|
3428
|
+
status: err.status
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
3431
|
+
throw new InteractError(
|
|
3432
|
+
"network_error",
|
|
3433
|
+
err instanceof Error ? err.message : "features lookup failed"
|
|
3434
|
+
);
|
|
3435
|
+
}
|
|
3436
|
+
const matched = matchEnrichedFeature(fc.features ?? [], result.raw, coords);
|
|
3437
|
+
const enrichment = {
|
|
3438
|
+
matched: matched != null,
|
|
3439
|
+
properties: matched ? matched.properties : null,
|
|
3440
|
+
cellId: null,
|
|
3441
|
+
cell: null
|
|
3442
|
+
};
|
|
3443
|
+
if (matched) {
|
|
3444
|
+
const cellId = matched.properties?.cell_id;
|
|
3445
|
+
if (typeof cellId === "string" && cellId) {
|
|
3446
|
+
enrichment.cellId = cellId;
|
|
3447
|
+
try {
|
|
3448
|
+
const cell = await http.request(
|
|
3449
|
+
`/cells/${encodeURIComponent(cellId)}`,
|
|
3450
|
+
void 0,
|
|
3451
|
+
signal
|
|
3452
|
+
);
|
|
3453
|
+
enrichment.cell = cell;
|
|
3454
|
+
result.notice = enrichedNotice(cell);
|
|
3455
|
+
} catch {
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
result.enrichment = enrichment;
|
|
3460
|
+
return result;
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
// src/interact/popup.ts
|
|
3464
|
+
var POPUP_CLASS = "chrt-interact-popup";
|
|
3465
|
+
var POPUP_CSS = `
|
|
3466
|
+
.${POPUP_CLASS} {
|
|
3467
|
+
position: absolute;
|
|
3468
|
+
z-index: 40;
|
|
3469
|
+
min-width: 220px;
|
|
3470
|
+
max-width: 320px;
|
|
3471
|
+
transform: translate(-50%, calc(-100% - 12px));
|
|
3472
|
+
background: var(--chrt-interact-bg, #0b1524);
|
|
3473
|
+
color: var(--chrt-interact-fg, #e2e8f0);
|
|
3474
|
+
border: 1px solid var(--chrt-interact-border, rgba(148, 163, 184, 0.25));
|
|
3475
|
+
border-radius: 8px;
|
|
3476
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
|
|
3477
|
+
font: 12px/1.45 var(--chrt-interact-font, ui-sans-serif, system-ui, sans-serif);
|
|
3478
|
+
pointer-events: auto;
|
|
3479
|
+
}
|
|
3480
|
+
.${POPUP_CLASS} * { box-sizing: border-box; }
|
|
3481
|
+
.${POPUP_CLASS}__inner { padding: 10px 12px 8px; }
|
|
3482
|
+
.${POPUP_CLASS}__close {
|
|
3483
|
+
position: absolute; top: 4px; right: 6px;
|
|
3484
|
+
border: 0; background: none; color: inherit; opacity: 0.55;
|
|
3485
|
+
font-size: 14px; line-height: 1; cursor: pointer; padding: 4px;
|
|
3486
|
+
}
|
|
3487
|
+
.${POPUP_CLASS}__close:hover { opacity: 1; }
|
|
3488
|
+
.${POPUP_CLASS}__title { font-weight: 600; font-size: 13px; padding-right: 16px; }
|
|
3489
|
+
.${POPUP_CLASS}__subtitle { color: var(--chrt-interact-muted, #94a3b8); margin-top: 1px; }
|
|
3490
|
+
.${POPUP_CLASS}__badge {
|
|
3491
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
3492
|
+
margin-top: 6px; padding: 2px 8px;
|
|
3493
|
+
border: 1px solid var(--chrt-interact-border, rgba(148, 163, 184, 0.25));
|
|
3494
|
+
border-radius: 4px;
|
|
3495
|
+
font-family: var(--chrt-interact-mono, ui-monospace, monospace);
|
|
3496
|
+
font-weight: 600; font-size: 11px;
|
|
3497
|
+
}
|
|
3498
|
+
.${POPUP_CLASS}__dot {
|
|
3499
|
+
width: 10px; height: 10px; border-radius: 50%;
|
|
3500
|
+
background: #555; opacity: 0.15;
|
|
3501
|
+
}
|
|
3502
|
+
.${POPUP_CLASS}__rows { margin-top: 8px; border-top: 1px solid var(--chrt-interact-border, rgba(148,163,184,0.18)); padding-top: 6px; }
|
|
3503
|
+
.${POPUP_CLASS}__row { display: flex; justify-content: space-between; gap: 12px; padding: 1px 0; }
|
|
3504
|
+
.${POPUP_CLASS}__label { color: var(--chrt-interact-muted, #94a3b8); flex-shrink: 0; }
|
|
3505
|
+
.${POPUP_CLASS}__value { text-align: right; font-weight: 500; }
|
|
3506
|
+
.${POPUP_CLASS}__info { margin-top: 4px; font-style: italic; color: var(--chrt-interact-muted, #94a3b8); font-size: 11px; }
|
|
3507
|
+
.${POPUP_CLASS}__footer {
|
|
3508
|
+
margin-top: 8px; padding: 6px 12px 8px;
|
|
3509
|
+
border-top: 1px solid var(--chrt-interact-border, rgba(148,163,184,0.18));
|
|
3510
|
+
color: var(--chrt-interact-muted, #94a3b8);
|
|
3511
|
+
font-size: 10px;
|
|
3512
|
+
}
|
|
3513
|
+
.${POPUP_CLASS}__footer strong { color: var(--chrt-interact-warn, #fbbf24); font-weight: 600; }
|
|
3514
|
+
`;
|
|
3515
|
+
var styleInjected = false;
|
|
3516
|
+
function ensureStyles(doc) {
|
|
3517
|
+
if (styleInjected && doc.getElementById(`${POPUP_CLASS}-style`)) return;
|
|
3518
|
+
const style = doc.createElement("style");
|
|
3519
|
+
style.id = `${POPUP_CLASS}-style`;
|
|
3520
|
+
style.textContent = POPUP_CSS;
|
|
3521
|
+
doc.head.appendChild(style);
|
|
3522
|
+
styleInjected = true;
|
|
3523
|
+
}
|
|
3524
|
+
function el(doc, tag, className, text) {
|
|
3525
|
+
const node = doc.createElement(tag);
|
|
3526
|
+
node.className = className;
|
|
3527
|
+
if (text != null) node.textContent = text;
|
|
3528
|
+
return node;
|
|
3529
|
+
}
|
|
3530
|
+
function animateDot(dot, pattern) {
|
|
3531
|
+
if (pattern.totalCycle <= 0 || typeof requestAnimationFrame === "undefined") {
|
|
3532
|
+
return () => void 0;
|
|
3533
|
+
}
|
|
3534
|
+
let raf = 0;
|
|
3535
|
+
const start = performance.now();
|
|
3536
|
+
const tick = (now2) => {
|
|
3537
|
+
const elapsed = (now2 - start) % pattern.totalCycle;
|
|
3538
|
+
let acc = 0;
|
|
3539
|
+
let phase = pattern.phases[0];
|
|
3540
|
+
for (const p of pattern.phases) {
|
|
3541
|
+
acc += p.duration;
|
|
3542
|
+
if (elapsed < acc) {
|
|
3543
|
+
phase = p;
|
|
3544
|
+
break;
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
if (phase.on) {
|
|
3548
|
+
const color = phase.color || "#ffffff";
|
|
3549
|
+
dot.style.opacity = "1";
|
|
3550
|
+
dot.style.backgroundColor = color;
|
|
3551
|
+
dot.style.boxShadow = `0 0 8px 2px ${color}88`;
|
|
3552
|
+
} else {
|
|
3553
|
+
dot.style.opacity = "0.15";
|
|
3554
|
+
dot.style.backgroundColor = "#555";
|
|
3555
|
+
dot.style.boxShadow = "none";
|
|
3556
|
+
}
|
|
3557
|
+
raf = requestAnimationFrame(tick);
|
|
3558
|
+
};
|
|
3559
|
+
raf = requestAnimationFrame(tick);
|
|
3560
|
+
return () => cancelAnimationFrame(raf);
|
|
3561
|
+
}
|
|
3562
|
+
var InteractPopup = class {
|
|
3563
|
+
constructor(map) {
|
|
3564
|
+
this.map = map;
|
|
3565
|
+
}
|
|
3566
|
+
root = null;
|
|
3567
|
+
stopAnimation = null;
|
|
3568
|
+
lngLat = null;
|
|
3569
|
+
reposition = () => this.place();
|
|
3570
|
+
show(result, lngLat) {
|
|
3571
|
+
const container = this.map.getContainer();
|
|
3572
|
+
const doc = container.ownerDocument;
|
|
3573
|
+
ensureStyles(doc);
|
|
3574
|
+
this.close();
|
|
3575
|
+
const root = el(doc, "div", POPUP_CLASS);
|
|
3576
|
+
const inner = el(doc, "div", `${POPUP_CLASS}__inner`);
|
|
3577
|
+
const close = el(doc, "button", `${POPUP_CLASS}__close`, "\u2715");
|
|
3578
|
+
close.setAttribute("aria-label", "Close");
|
|
3579
|
+
close.addEventListener("click", () => this.close());
|
|
3580
|
+
root.appendChild(close);
|
|
3581
|
+
inner.appendChild(el(doc, "div", `${POPUP_CLASS}__title`, result.title));
|
|
3582
|
+
const subtitleParts = [result.objectClass];
|
|
3583
|
+
if (result.kind === "navaid") {
|
|
3584
|
+
if (result.navAid.shape) subtitleParts.push(result.navAid.shape);
|
|
3585
|
+
if (result.navAid.isSectorLight) subtitleParts.push("Sector");
|
|
3586
|
+
if (result.navAid.isDirectional) subtitleParts.push("Directional");
|
|
3587
|
+
}
|
|
3588
|
+
inner.appendChild(
|
|
3589
|
+
el(doc, "div", `${POPUP_CLASS}__subtitle`, subtitleParts.filter(Boolean).join(" \xB7 "))
|
|
3590
|
+
);
|
|
3591
|
+
if (result.kind === "navaid" && result.characteristic) {
|
|
3592
|
+
const badge = el(doc, "div", `${POPUP_CLASS}__badge`);
|
|
3593
|
+
const dot = el(doc, "span", `${POPUP_CLASS}__dot`);
|
|
3594
|
+
badge.appendChild(dot);
|
|
3595
|
+
badge.appendChild(doc.createTextNode(result.characteristic));
|
|
3596
|
+
if (result.navAid.lightColourHex && result.navAid.lightColourHex !== "#ffffff") {
|
|
3597
|
+
badge.style.color = result.navAid.lightColourHex;
|
|
3598
|
+
}
|
|
3599
|
+
inner.appendChild(badge);
|
|
3600
|
+
if (result.flashPattern) {
|
|
3601
|
+
this.stopAnimation = animateDot(dot, result.flashPattern);
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
if (result.properties.length > 0) {
|
|
3605
|
+
const rows = el(doc, "div", `${POPUP_CLASS}__rows`);
|
|
3606
|
+
for (const p of result.properties) {
|
|
3607
|
+
const row = el(doc, "div", `${POPUP_CLASS}__row`);
|
|
3608
|
+
row.appendChild(el(doc, "span", `${POPUP_CLASS}__label`, p.label));
|
|
3609
|
+
row.appendChild(el(doc, "span", `${POPUP_CLASS}__value`, p.value));
|
|
3610
|
+
rows.appendChild(row);
|
|
3611
|
+
}
|
|
3612
|
+
inner.appendChild(rows);
|
|
3613
|
+
}
|
|
3614
|
+
if (result.kind === "navaid" && result.navAid.information) {
|
|
3615
|
+
inner.appendChild(el(doc, "div", `${POPUP_CLASS}__info`, result.navAid.information));
|
|
3616
|
+
}
|
|
3617
|
+
root.appendChild(inner);
|
|
3618
|
+
const footer = el(doc, "div", `${POPUP_CLASS}__footer`);
|
|
3619
|
+
footer.title = `${NON_SOLAS_DISCLAIMER}
|
|
3620
|
+
|
|
3621
|
+
${result.notice.attribution}
|
|
3622
|
+
${result.notice.dataCurrency}`;
|
|
3623
|
+
const warn = el(doc, "strong", "", `\u26A0 ${NON_SOLAS_DISCLAIMER_SHORT}`);
|
|
3624
|
+
footer.appendChild(warn);
|
|
3625
|
+
footer.appendChild(doc.createElement("br"));
|
|
3626
|
+
footer.appendChild(doc.createTextNode(result.notice.attribution));
|
|
3627
|
+
const currency = result.notice.dataCurrency;
|
|
3628
|
+
if (currency.startsWith("Edition")) {
|
|
3629
|
+
footer.appendChild(doc.createElement("br"));
|
|
3630
|
+
footer.appendChild(doc.createTextNode(currency));
|
|
3631
|
+
}
|
|
3632
|
+
root.appendChild(footer);
|
|
3633
|
+
const position = typeof getComputedStyle !== "undefined" ? getComputedStyle(container).position : container.style.position;
|
|
3634
|
+
if (position === "static" || position === "") {
|
|
3635
|
+
container.style.position = "relative";
|
|
3636
|
+
}
|
|
3637
|
+
container.appendChild(root);
|
|
3638
|
+
this.root = root;
|
|
3639
|
+
this.lngLat = lngLat;
|
|
3640
|
+
this.place();
|
|
3641
|
+
this.map.on("move", this.reposition);
|
|
3642
|
+
}
|
|
3643
|
+
place() {
|
|
3644
|
+
if (!this.root || !this.lngLat) return;
|
|
3645
|
+
const { x, y } = this.map.project(this.lngLat);
|
|
3646
|
+
this.root.style.left = `${x}px`;
|
|
3647
|
+
this.root.style.top = `${y}px`;
|
|
3648
|
+
}
|
|
3649
|
+
close() {
|
|
3650
|
+
if (this.stopAnimation) {
|
|
3651
|
+
this.stopAnimation();
|
|
3652
|
+
this.stopAnimation = null;
|
|
3653
|
+
}
|
|
3654
|
+
if (this.root) {
|
|
3655
|
+
this.map.off("move", this.reposition);
|
|
3656
|
+
this.root.remove();
|
|
3657
|
+
this.root = null;
|
|
3658
|
+
}
|
|
3659
|
+
this.lngLat = null;
|
|
3660
|
+
}
|
|
3661
|
+
};
|
|
3662
|
+
|
|
3663
|
+
// src/interact/index.ts
|
|
3664
|
+
var InteractClient = class {
|
|
3665
|
+
constructor(http) {
|
|
3666
|
+
this.http = http;
|
|
3667
|
+
this.entitlementClient = new EntitlementClient(http);
|
|
3668
|
+
}
|
|
3669
|
+
entitlementClient;
|
|
3670
|
+
/**
|
|
3671
|
+
* Check the CHRTinteract entitlement for this API key (server-side, plan
|
|
3672
|
+
* derived, cached per client). Throws `InteractError` with code
|
|
3673
|
+
* `interact_not_enabled` for Free-plan teams.
|
|
3674
|
+
*/
|
|
3675
|
+
async entitlement() {
|
|
3676
|
+
return this.entitlementClient.check();
|
|
3677
|
+
}
|
|
3678
|
+
/**
|
|
3679
|
+
* Decode a chart feature (from `queryRenderedFeatures`, the features API,
|
|
3680
|
+
* or plain `{ layer, properties }`) into a typed {@link InspectResult}.
|
|
3681
|
+
*
|
|
3682
|
+
* The first call performs the server-side entitlement check. With
|
|
3683
|
+
* `{ enrich: true }` the result is upgraded with full S-57 properties and
|
|
3684
|
+
* source-cell provenance (edition / update / issue date) via the features
|
|
3685
|
+
* API — metered against the key's `featureCalls` quota.
|
|
3686
|
+
*
|
|
3687
|
+
* @example
|
|
3688
|
+
* const result = await chrt.interact.inspect(feature);
|
|
3689
|
+
* if (result.kind === "navaid") console.log(result.characteristic); // "Fl R 4s"
|
|
3690
|
+
*/
|
|
3691
|
+
async inspect(feature, opts) {
|
|
3692
|
+
await this.entitlementClient.check();
|
|
3693
|
+
const result = buildInspectResult(feature, opts);
|
|
3694
|
+
if (opts?.enrich) {
|
|
3695
|
+
return enrichResult(this.http, result, opts.signal);
|
|
3696
|
+
}
|
|
3697
|
+
return result;
|
|
3698
|
+
}
|
|
3699
|
+
/**
|
|
3700
|
+
* Attach click-to-inspect to a MapLibre / Mapbox GL map rendering CHRT
|
|
3701
|
+
* tiles. Resolves once the server-side entitlement check passes; rejects
|
|
3702
|
+
* with `InteractError` (`interact_not_enabled` on Free plans) otherwise —
|
|
3703
|
+
* no listeners are wired on denial.
|
|
3704
|
+
*
|
|
3705
|
+
* Headless-first: pass `onInspect` to receive every decoded
|
|
3706
|
+
* {@link InspectResult} and render your own UI. Without `onInspect`, the
|
|
3707
|
+
* built-in minimal popup is shown (opt back in explicitly with
|
|
3708
|
+
* `popup: true` to have both).
|
|
3709
|
+
*
|
|
3710
|
+
* @example
|
|
3711
|
+
* const handle = await chrt.interact.attach(map, {
|
|
3712
|
+
* onInspect: (result) => console.log(result.title, result.characteristic),
|
|
3713
|
+
* });
|
|
3714
|
+
* // later: handle.detach();
|
|
3715
|
+
*/
|
|
3716
|
+
async attach(map, opts = {}) {
|
|
3717
|
+
if (!map || typeof map.queryRenderedFeatures !== "function" || typeof map.on !== "function") {
|
|
3718
|
+
throw new InteractError(
|
|
3719
|
+
"unexpected_response",
|
|
3720
|
+
"attach(): expected a MapLibre / Mapbox GL map instance"
|
|
1266
3721
|
);
|
|
1267
3722
|
}
|
|
1268
|
-
await
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
3723
|
+
await this.entitlementClient.check();
|
|
3724
|
+
const showPopup = opts.popup ?? !opts.onInspect;
|
|
3725
|
+
const popup = showPopup ? new InteractPopup(map) : null;
|
|
3726
|
+
const clickHandler = (e) => {
|
|
3727
|
+
const hit = decodeClick(map, e, opts);
|
|
3728
|
+
if (!hit) {
|
|
3729
|
+
popup?.close();
|
|
3730
|
+
opts.onMiss?.({ lngLat: e.lngLat });
|
|
3731
|
+
return;
|
|
3732
|
+
}
|
|
3733
|
+
const finish = (result) => {
|
|
3734
|
+
popup?.show(result, result.coordinates ?? [e.lngLat.lng, e.lngLat.lat]);
|
|
3735
|
+
opts.onInspect?.(result, { lngLat: e.lngLat, point: e.point, feature: hit.feature });
|
|
3736
|
+
};
|
|
3737
|
+
if (opts.enrich) {
|
|
3738
|
+
enrichResult(this.http, hit.result).then(finish).catch(() => finish(hit.result));
|
|
3739
|
+
} else {
|
|
3740
|
+
finish(hit.result);
|
|
3741
|
+
}
|
|
1277
3742
|
};
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
path,
|
|
1285
|
-
bytes: bundle.bytes,
|
|
1286
|
-
sha256: bundle.sha256,
|
|
1287
|
-
build_id: bundle.build_id,
|
|
1288
|
-
build_tag: bundle.build_tag,
|
|
1289
|
-
downloaded_at: nowSec,
|
|
1290
|
-
last_accessed_at: nowSec
|
|
3743
|
+
map.on("click", clickHandler);
|
|
3744
|
+
return {
|
|
3745
|
+
detach: () => {
|
|
3746
|
+
map.off("click", clickHandler);
|
|
3747
|
+
popup?.close();
|
|
3748
|
+
}
|
|
1291
3749
|
};
|
|
1292
|
-
await writeManifest(this.manifestPath, fs, manifest);
|
|
1293
3750
|
}
|
|
1294
3751
|
};
|
|
1295
|
-
function dirname(p) {
|
|
1296
|
-
const i = p.lastIndexOf("/");
|
|
1297
|
-
const j = p.lastIndexOf("\\");
|
|
1298
|
-
const k = Math.max(i, j);
|
|
1299
|
-
if (k <= 0) return ".";
|
|
1300
|
-
return p.slice(0, k);
|
|
1301
|
-
}
|
|
1302
3752
|
|
|
1303
3753
|
// src/atlas/s52-colors.ts
|
|
1304
3754
|
var S52_DAY = {
|
|
@@ -7022,6 +9472,118 @@ var LABEL_ANCHOR_FILTER = [
|
|
|
7022
9472
|
["get", "_label_anchor"],
|
|
7023
9473
|
false
|
|
7024
9474
|
];
|
|
9475
|
+
var LABEL_PRIORITY = {
|
|
9476
|
+
LIGHTS: 100,
|
|
9477
|
+
UWTROC: 100,
|
|
9478
|
+
OBSTRN: 100,
|
|
9479
|
+
WRECKS: 100,
|
|
9480
|
+
CBLOHD: 100,
|
|
9481
|
+
PIPOHD: 100,
|
|
9482
|
+
BOYLAT: 200,
|
|
9483
|
+
BOYCAR: 200,
|
|
9484
|
+
BOYSPP: 200,
|
|
9485
|
+
BOYISD: 200,
|
|
9486
|
+
BOYSAW: 200,
|
|
9487
|
+
BCNLAT: 200,
|
|
9488
|
+
BCNCAR: 200,
|
|
9489
|
+
BCNSPP: 200,
|
|
9490
|
+
FOGSIG: 250,
|
|
9491
|
+
SOUNDG: 300,
|
|
9492
|
+
DEPCNT: 300,
|
|
9493
|
+
BRIDGE: 400,
|
|
9494
|
+
LNDMRK: 400,
|
|
9495
|
+
PILPNT: 400,
|
|
9496
|
+
MORFAC: 400,
|
|
9497
|
+
SLCONS: 400,
|
|
9498
|
+
BUISGL: 400,
|
|
9499
|
+
SBDARE: 500,
|
|
9500
|
+
MAGVAR: 500,
|
|
9501
|
+
WEDKLP: 500
|
|
9502
|
+
};
|
|
9503
|
+
var DEFAULT_LABEL_PRIORITY = 450;
|
|
9504
|
+
var ORIENTATION_LABEL_PRIORITY = 900;
|
|
9505
|
+
function getLabelPriority(layerName) {
|
|
9506
|
+
if (getLabelBehavior(layerName) === "orientation-cross-band") {
|
|
9507
|
+
return ORIENTATION_LABEL_PRIORITY;
|
|
9508
|
+
}
|
|
9509
|
+
return LABEL_PRIORITY[layerName] ?? DEFAULT_LABEL_PRIORITY;
|
|
9510
|
+
}
|
|
9511
|
+
function labelSortKey(rank, within) {
|
|
9512
|
+
if (within === void 0) return rank * 1e3;
|
|
9513
|
+
return [
|
|
9514
|
+
"+",
|
|
9515
|
+
rank * 1e3,
|
|
9516
|
+
["min", 999, ["max", 0, within]]
|
|
9517
|
+
];
|
|
9518
|
+
}
|
|
9519
|
+
var NAV_DESIGNATION_STOPWORDS = [
|
|
9520
|
+
"Buoy",
|
|
9521
|
+
"Can",
|
|
9522
|
+
"Nun",
|
|
9523
|
+
"Bell",
|
|
9524
|
+
"Gong",
|
|
9525
|
+
"Mark"
|
|
9526
|
+
];
|
|
9527
|
+
function extractNavAidDesignation(objnam) {
|
|
9528
|
+
const trimmed = objnam.trim();
|
|
9529
|
+
if (/^[A-Za-z0-9]{1,4}$/.test(trimmed)) {
|
|
9530
|
+
return NAV_DESIGNATION_STOPWORDS.some((w) => w === trimmed) ? null : trimmed;
|
|
9531
|
+
}
|
|
9532
|
+
const m = trimmed.match(/ ([A-Za-z0-9]{1,4})$/);
|
|
9533
|
+
if (!m) return null;
|
|
9534
|
+
const token = m[1];
|
|
9535
|
+
if (NAV_DESIGNATION_STOPWORDS.some((w) => w === token)) return null;
|
|
9536
|
+
return token;
|
|
9537
|
+
}
|
|
9538
|
+
function navAidDesignationTextExpression(objnam, colourPrefix) {
|
|
9539
|
+
const len = ["length", objnam];
|
|
9540
|
+
const tokenBranches = [];
|
|
9541
|
+
for (let k = 1; k <= 4; k++) {
|
|
9542
|
+
tokenBranches.push(
|
|
9543
|
+
[
|
|
9544
|
+
"all",
|
|
9545
|
+
[">", len, k],
|
|
9546
|
+
["==", ["slice", objnam, ["-", len, k + 1], ["-", len, k]], " "]
|
|
9547
|
+
],
|
|
9548
|
+
["slice", objnam, ["-", len, k]]
|
|
9549
|
+
);
|
|
9550
|
+
}
|
|
9551
|
+
return [
|
|
9552
|
+
"let",
|
|
9553
|
+
"desig",
|
|
9554
|
+
["case", ...tokenBranches, ""],
|
|
9555
|
+
[
|
|
9556
|
+
"case",
|
|
9557
|
+
[
|
|
9558
|
+
"any",
|
|
9559
|
+
["==", ["var", "desig"], ""],
|
|
9560
|
+
["match", ["var", "desig"], [...NAV_DESIGNATION_STOPWORDS], true, false]
|
|
9561
|
+
],
|
|
9562
|
+
["concat", colourPrefix, objnam],
|
|
9563
|
+
["concat", colourPrefix, '"', ["var", "desig"], '"']
|
|
9564
|
+
]
|
|
9565
|
+
];
|
|
9566
|
+
}
|
|
9567
|
+
function navAidLabelLayout(sortKey) {
|
|
9568
|
+
return {
|
|
9569
|
+
"text-allow-overlap": false,
|
|
9570
|
+
"text-ignore-placement": false,
|
|
9571
|
+
"text-optional": true,
|
|
9572
|
+
"text-padding": 4,
|
|
9573
|
+
"symbol-sort-key": sortKey
|
|
9574
|
+
};
|
|
9575
|
+
}
|
|
9576
|
+
var NAV_AID_SORT_KEY = labelSortKey(200, [
|
|
9577
|
+
"case",
|
|
9578
|
+
["has", "VALNMR"],
|
|
9579
|
+
["-", 300, ["min", 299, ["to-number", ["get", "VALNMR"], 0]]],
|
|
9580
|
+
["+", 400, ["min", 500, ["length", ["coalesce", ["get", "OBJNAM"], ""]]]]
|
|
9581
|
+
]);
|
|
9582
|
+
var LIGHT_SORT_KEY = labelSortKey(100, [
|
|
9583
|
+
"-",
|
|
9584
|
+
300,
|
|
9585
|
+
["min", 299, ["to-number", ["coalesce", ["get", "VALNMR"], 0], 0]]
|
|
9586
|
+
]);
|
|
7025
9587
|
function getLabelConfig(layerName, opts = {}) {
|
|
7026
9588
|
const ct = opts.colorTable ?? "DAY";
|
|
7027
9589
|
switch (layerName) {
|
|
@@ -7089,14 +9651,15 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
7089
9651
|
// sizes — established cartographic typography practice.
|
|
7090
9652
|
"text-letter-spacing": 0.02,
|
|
7091
9653
|
// Shallowest-wins: Mapbox GL renders lower sort keys first and
|
|
7092
|
-
// gives them collision priority.
|
|
7093
|
-
// the shallowest sounding in a cluster is always the
|
|
7094
|
-
// even when labels are hidden due to overlap.
|
|
7095
|
-
|
|
9654
|
+
// gives them collision priority. Depth as the within-class key
|
|
9655
|
+
// ensures the shallowest sounding in a cluster is always the
|
|
9656
|
+
// one shown, even when labels are hidden due to overlap. Folded
|
|
9657
|
+
// into the rank-300 band of the LABEL_PRIORITY ladder.
|
|
9658
|
+
"symbol-sort-key": labelSortKey(300, [
|
|
7096
9659
|
"to-number",
|
|
7097
9660
|
["coalesce", ["get", "depth"], ["get", "DEPTH"], ["get", "VALSOU"]],
|
|
7098
9661
|
999
|
|
7099
|
-
]
|
|
9662
|
+
])
|
|
7100
9663
|
}
|
|
7101
9664
|
};
|
|
7102
9665
|
}
|
|
@@ -7136,8 +9699,14 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
7136
9699
|
textColor: DARK_BG,
|
|
7137
9700
|
textHaloColor: "#d4c9b0",
|
|
7138
9701
|
textHaloWidth: 1.5,
|
|
7139
|
-
textOffset: [0,
|
|
7140
|
-
minzoom: 9
|
|
9702
|
+
textOffset: [0, 0],
|
|
9703
|
+
minzoom: 9,
|
|
9704
|
+
layout: {
|
|
9705
|
+
"text-variable-anchor": ["top", "bottom", "right", "left"],
|
|
9706
|
+
"text-radial-offset": 1.2,
|
|
9707
|
+
"text-justify": "auto",
|
|
9708
|
+
"symbol-sort-key": labelSortKey(400, ["length", ["coalesce", ["get", "OBJNAM"], ""]])
|
|
9709
|
+
}
|
|
7141
9710
|
};
|
|
7142
9711
|
case "BOYLAT":
|
|
7143
9712
|
return {
|
|
@@ -7149,7 +9718,8 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
7149
9718
|
textHaloColor: navHalo("WHITE", ct),
|
|
7150
9719
|
textHaloWidth: 1.5,
|
|
7151
9720
|
textOffset: [0, 1.5],
|
|
7152
|
-
minzoom: 10
|
|
9721
|
+
minzoom: 10,
|
|
9722
|
+
layout: navAidLabelLayout(NAV_AID_SORT_KEY)
|
|
7153
9723
|
};
|
|
7154
9724
|
case "BCNLAT":
|
|
7155
9725
|
return {
|
|
@@ -7161,7 +9731,8 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
7161
9731
|
textHaloColor: navHalo("WHITE", ct),
|
|
7162
9732
|
textHaloWidth: 1.5,
|
|
7163
9733
|
textOffset: [0, 1.5],
|
|
7164
|
-
minzoom: 10
|
|
9734
|
+
minzoom: 10,
|
|
9735
|
+
layout: navAidLabelLayout(NAV_AID_SORT_KEY)
|
|
7165
9736
|
};
|
|
7166
9737
|
case "BOYCAR":
|
|
7167
9738
|
case "BCNCAR":
|
|
@@ -7174,7 +9745,8 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
7174
9745
|
textHaloColor: navHalo("CREAM", ct),
|
|
7175
9746
|
textHaloWidth: 1.5,
|
|
7176
9747
|
textOffset: [0, 1.5],
|
|
7177
|
-
minzoom: 10
|
|
9748
|
+
minzoom: 10,
|
|
9749
|
+
layout: navAidLabelLayout(NAV_AID_SORT_KEY)
|
|
7178
9750
|
};
|
|
7179
9751
|
case "BOYSPP":
|
|
7180
9752
|
case "BCNSPP":
|
|
@@ -7187,7 +9759,8 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
7187
9759
|
textHaloColor: navHalo("YELLOW", ct),
|
|
7188
9760
|
textHaloWidth: 1.5,
|
|
7189
9761
|
textOffset: [0, 1.5],
|
|
7190
|
-
minzoom: 10
|
|
9762
|
+
minzoom: 10,
|
|
9763
|
+
layout: navAidLabelLayout(NAV_AID_SORT_KEY)
|
|
7191
9764
|
};
|
|
7192
9765
|
case "BOYISD":
|
|
7193
9766
|
case "BOYSAW":
|
|
@@ -7200,7 +9773,8 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
7200
9773
|
textHaloColor: navHalo("WHITE", ct),
|
|
7201
9774
|
textHaloWidth: 1.5,
|
|
7202
9775
|
textOffset: [0, 1.5],
|
|
7203
|
-
minzoom: 10
|
|
9776
|
+
minzoom: 10,
|
|
9777
|
+
layout: navAidLabelLayout(NAV_AID_SORT_KEY)
|
|
7204
9778
|
};
|
|
7205
9779
|
case "LIGHTS":
|
|
7206
9780
|
return {
|
|
@@ -7214,7 +9788,12 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
7214
9788
|
textOffset: [2, 0],
|
|
7215
9789
|
minzoom: 9,
|
|
7216
9790
|
layout: {
|
|
7217
|
-
"text-anchor": "left"
|
|
9791
|
+
"text-anchor": "left",
|
|
9792
|
+
"text-allow-overlap": false,
|
|
9793
|
+
"text-ignore-placement": false,
|
|
9794
|
+
"text-optional": true,
|
|
9795
|
+
"text-padding": 2,
|
|
9796
|
+
"symbol-sort-key": LIGHT_SORT_KEY
|
|
7218
9797
|
}
|
|
7219
9798
|
};
|
|
7220
9799
|
case "FOGSIG": {
|
|
@@ -7252,7 +9831,8 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
7252
9831
|
layout: {
|
|
7253
9832
|
"text-anchor": "left",
|
|
7254
9833
|
"text-allow-overlap": false,
|
|
7255
|
-
"text-optional": true
|
|
9834
|
+
"text-optional": true,
|
|
9835
|
+
"symbol-sort-key": labelSortKey(250)
|
|
7256
9836
|
}
|
|
7257
9837
|
};
|
|
7258
9838
|
}
|
|
@@ -7425,11 +10005,15 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
7425
10005
|
textColor: "#bfa88a",
|
|
7426
10006
|
textHaloColor: DARK_BG,
|
|
7427
10007
|
textHaloWidth: 1.5,
|
|
7428
|
-
textOffset: [0,
|
|
10008
|
+
textOffset: [0, 0],
|
|
7429
10009
|
minzoom: 12,
|
|
7430
10010
|
layout: {
|
|
10011
|
+
"text-variable-anchor": ["top", "bottom", "right", "left"],
|
|
10012
|
+
"text-radial-offset": 1,
|
|
10013
|
+
"text-justify": "auto",
|
|
7431
10014
|
"text-allow-overlap": false,
|
|
7432
|
-
"text-optional": true
|
|
10015
|
+
"text-optional": true,
|
|
10016
|
+
"symbol-sort-key": labelSortKey(400, ["length", ["coalesce", ["get", "OBJNAM"], ""]])
|
|
7433
10017
|
}
|
|
7434
10018
|
};
|
|
7435
10019
|
case "PILPNT":
|
|
@@ -7653,17 +10237,26 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
7653
10237
|
textColor: resolveColor("CHBLK", ct2),
|
|
7654
10238
|
textHaloColor: resolveColor("CHWHT", ct2),
|
|
7655
10239
|
textHaloWidth: 1.2,
|
|
7656
|
-
|
|
7657
|
-
// of the asterisk / cross. Anchor top-left so the offset grows
|
|
7658
|
-
// away from the symbol as text-size grows.
|
|
7659
|
-
textOffset: [0.8, -0.4],
|
|
10240
|
+
textOffset: [0, 0],
|
|
7660
10241
|
textFont: ["Open Sans Regular"],
|
|
7661
10242
|
minzoom: 12,
|
|
10243
|
+
// Variable anchors: the depth callout tries each corner of the
|
|
10244
|
+
// hazard symbol and takes the first free spot, so in a rocky
|
|
10245
|
+
// field the callouts fan out around their symbols instead of
|
|
10246
|
+
// overprinting or vanishing. Safe here (unlike nav-aids) because
|
|
10247
|
+
// UWTROC/OBSTRN/WRECKS are dominance-PARTICIPATING classes —
|
|
10248
|
+
// exactly one band's copy renders at any zoom, so anchor-shifting
|
|
10249
|
+
// can't split a cross-band duplicate pair into two visible labels.
|
|
10250
|
+
// Shallowest (most dangerous) hazard wins within-class contests;
|
|
10251
|
+
// VALSOU-less hazards (awash / unsounded) rank first of all.
|
|
7662
10252
|
layout: {
|
|
7663
|
-
"text-anchor": "top-left",
|
|
10253
|
+
"text-variable-anchor": ["top-left", "bottom-left", "top-right", "bottom-right"],
|
|
10254
|
+
"text-radial-offset": 0.9,
|
|
10255
|
+
"text-justify": "auto",
|
|
7664
10256
|
"text-allow-overlap": false,
|
|
7665
10257
|
"text-optional": true,
|
|
7666
|
-
"text-padding": 2
|
|
10258
|
+
"text-padding": 2,
|
|
10259
|
+
"symbol-sort-key": labelSortKey(100, ["*", 10, valsou])
|
|
7667
10260
|
}
|
|
7668
10261
|
};
|
|
7669
10262
|
}
|
|
@@ -7702,7 +10295,13 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
7702
10295
|
"text-rotation-alignment": "map",
|
|
7703
10296
|
"text-allow-overlap": false,
|
|
7704
10297
|
"text-optional": true,
|
|
7705
|
-
"text-letter-spacing": 0.02
|
|
10298
|
+
"text-letter-spacing": 0.02,
|
|
10299
|
+
// Lower clearance = more dangerous = places first.
|
|
10300
|
+
"symbol-sort-key": labelSortKey(100, [
|
|
10301
|
+
"*",
|
|
10302
|
+
5,
|
|
10303
|
+
["to-number", ["coalesce", ["get", "VERCLR"], 199], 199]
|
|
10304
|
+
])
|
|
7706
10305
|
}
|
|
7707
10306
|
};
|
|
7708
10307
|
}
|
|
@@ -7731,7 +10330,10 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
7731
10330
|
"symbol-spacing": 250,
|
|
7732
10331
|
"text-rotation-alignment": "map",
|
|
7733
10332
|
"text-allow-overlap": false,
|
|
7734
|
-
"text-optional": true
|
|
10333
|
+
"text-optional": true,
|
|
10334
|
+
// Shallower contours win within-class contests, mirroring the
|
|
10335
|
+
// SOUNDG shallowest-wins rule.
|
|
10336
|
+
"symbol-sort-key": labelSortKey(300, ["to-number", ["get", "VALDCO"], 999])
|
|
7735
10337
|
}
|
|
7736
10338
|
};
|
|
7737
10339
|
default:
|
|
@@ -7904,9 +10506,8 @@ function preprocessNavAids(fc) {
|
|
|
7904
10506
|
const props = f.properties ?? {};
|
|
7905
10507
|
const objnam = props.OBJNAM;
|
|
7906
10508
|
if (!objnam) return f;
|
|
7907
|
-
const
|
|
7908
|
-
if (!
|
|
7909
|
-
const designation = match[1];
|
|
10509
|
+
const designation = extractNavAidDesignation(objnam);
|
|
10510
|
+
if (!designation) return f;
|
|
7910
10511
|
const colourCode = firstListCode(props.COLOUR);
|
|
7911
10512
|
const prefix = NAV_COLOUR_PREFIX[colourCode];
|
|
7912
10513
|
const label = prefix ? `${prefix} "${designation}"` : `"${designation}"`;
|
|
@@ -9660,7 +12261,7 @@ function getS52SymbolStats() {
|
|
|
9660
12261
|
}
|
|
9661
12262
|
|
|
9662
12263
|
// src/atlas/tile-zoom.ts
|
|
9663
|
-
var BUILD_MINZOOM =
|
|
12264
|
+
var BUILD_MINZOOM = 0;
|
|
9664
12265
|
var BUILD_MAXZOOM = 14;
|
|
9665
12266
|
|
|
9666
12267
|
// src/atlas/source-registry.ts
|
|
@@ -9760,33 +12361,6 @@ var CHILD_ICON_OFFSETS = {
|
|
|
9760
12361
|
LIGHTS: [14, 14]
|
|
9761
12362
|
// lower-right, off the buoy hull so the flare is visible
|
|
9762
12363
|
};
|
|
9763
|
-
var SAFETY_CRITICAL_LABELS = /* @__PURE__ */ new Set([
|
|
9764
|
-
"LIGHTS",
|
|
9765
|
-
"SOUNDG",
|
|
9766
|
-
"BOYLAT",
|
|
9767
|
-
"BCNLAT",
|
|
9768
|
-
"BOYCAR",
|
|
9769
|
-
"BCNCAR",
|
|
9770
|
-
"BOYSPP",
|
|
9771
|
-
"BCNSPP",
|
|
9772
|
-
"BOYISD",
|
|
9773
|
-
"BOYSAW",
|
|
9774
|
-
"FOGSIG",
|
|
9775
|
-
"RTPBCN"
|
|
9776
|
-
]);
|
|
9777
|
-
var HIGH_DENSITY_NAV_AIDS = /* @__PURE__ */ new Set([
|
|
9778
|
-
"BOYLAT",
|
|
9779
|
-
"BOYCAR",
|
|
9780
|
-
"BOYSPP",
|
|
9781
|
-
"BOYISD",
|
|
9782
|
-
"BOYSAW",
|
|
9783
|
-
"BOYINB",
|
|
9784
|
-
"BCNLAT",
|
|
9785
|
-
"BCNCAR",
|
|
9786
|
-
"BCNSPP",
|
|
9787
|
-
"BCNISD",
|
|
9788
|
-
"BCNSAW"
|
|
9789
|
-
]);
|
|
9790
12364
|
function buildNavAidLabelExpression() {
|
|
9791
12365
|
const colourPrefix = colourCodeCase(
|
|
9792
12366
|
[
|
|
@@ -9801,7 +12375,12 @@ function buildNavAidLabelExpression() {
|
|
|
9801
12375
|
["has", "_navAidLabel"],
|
|
9802
12376
|
["get", "_navAidLabel"],
|
|
9803
12377
|
["has", "OBJNAM"],
|
|
9804
|
-
|
|
12378
|
+
// Chart-convention abbreviation: `R "4"` instead of the full NOAA
|
|
12379
|
+
// name ("R Boston North Channel Lighted Buoy 4") — the tile pipeline
|
|
12380
|
+
// doesn't precompute `_navAidLabel`, so before this expression every
|
|
12381
|
+
// production buoy label carried its full 30-40 glyph OBJNAM, the
|
|
12382
|
+
// biggest single driver of Harbor/Approach label overprint (E3).
|
|
12383
|
+
navAidDesignationTextExpression(["get", "OBJNAM"], colourPrefix),
|
|
9805
12384
|
""
|
|
9806
12385
|
];
|
|
9807
12386
|
}
|
|
@@ -9992,6 +12571,11 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION) {
|
|
|
9992
12571
|
layer.paint["icon-color"] = lightFillExpression(ct);
|
|
9993
12572
|
layer.paint["icon-halo-color"] = resolveColor("CHBLK", ct);
|
|
9994
12573
|
layer.paint["icon-halo-width"] = 1;
|
|
12574
|
+
layer.layout = {
|
|
12575
|
+
...layer.layout,
|
|
12576
|
+
"icon-allow-overlap": true,
|
|
12577
|
+
"icon-ignore-placement": true
|
|
12578
|
+
};
|
|
9995
12579
|
}
|
|
9996
12580
|
} else if (resolved.layout) {
|
|
9997
12581
|
layer.layout = resolved.layout;
|
|
@@ -10862,7 +13446,7 @@ function buildLabelLayer2(objectClass, ct, sourceId = DEFAULT_REGION, depthUnits
|
|
|
10862
13446
|
];
|
|
10863
13447
|
}
|
|
10864
13448
|
}
|
|
10865
|
-
const
|
|
13449
|
+
const labelPriority = getLabelPriority(objectClass);
|
|
10866
13450
|
const layer = {
|
|
10867
13451
|
id: `s52-${objectClass}-label`,
|
|
10868
13452
|
type: "symbol",
|
|
@@ -10875,15 +13459,20 @@ function buildLabelLayer2(objectClass, ct, sourceId = DEFAULT_REGION, depthUnits
|
|
|
10875
13459
|
"text-font": config.textFont ?? ["Open Sans Regular"],
|
|
10876
13460
|
"text-offset": config.textOffset,
|
|
10877
13461
|
"text-anchor": "top",
|
|
10878
|
-
"text-allow-overlap":
|
|
10879
|
-
"text-optional":
|
|
10880
|
-
"text-padding":
|
|
13462
|
+
"text-allow-overlap": false,
|
|
13463
|
+
"text-optional": true,
|
|
13464
|
+
"text-padding": 4,
|
|
13465
|
+
"symbol-sort-key": labelPriority * 1e3,
|
|
10881
13466
|
...config.layout ?? {}
|
|
10882
13467
|
},
|
|
10883
13468
|
paint: {
|
|
10884
13469
|
"text-color": config.textColor,
|
|
10885
13470
|
"text-halo-color": config.textHaloColor,
|
|
10886
13471
|
"text-halo-width": config.textHaloWidth
|
|
13472
|
+
},
|
|
13473
|
+
metadata: {
|
|
13474
|
+
"s52:objectClass": objectClass,
|
|
13475
|
+
"chrt:labelPriority": labelPriority
|
|
10887
13476
|
}
|
|
10888
13477
|
};
|
|
10889
13478
|
if (config.minzoom) {
|
|
@@ -10935,13 +13524,24 @@ function getOrderedClasses() {
|
|
|
10935
13524
|
|
|
10936
13525
|
// src/atlas/band-priority.ts
|
|
10937
13526
|
var BAND_PRIORITY_RANGES = {
|
|
10938
|
-
"1": { minzoom:
|
|
13527
|
+
"1": { minzoom: 0 },
|
|
10939
13528
|
"2": { minzoom: 5 },
|
|
10940
13529
|
"3": { minzoom: 7 },
|
|
10941
13530
|
"4": { minzoom: 9 },
|
|
10942
13531
|
"5": { minzoom: 11 },
|
|
10943
13532
|
"6": { minzoom: 11 }
|
|
10944
13533
|
};
|
|
13534
|
+
var BAND_DATA_MINZOOM = {
|
|
13535
|
+
"1": 0,
|
|
13536
|
+
"2": 5,
|
|
13537
|
+
"3": 7,
|
|
13538
|
+
"4": 9,
|
|
13539
|
+
"5": 11,
|
|
13540
|
+
"6": 13
|
|
13541
|
+
};
|
|
13542
|
+
function dominatedVariantMaxzoom(D) {
|
|
13543
|
+
return Math.max(BAND_PRIORITY_RANGES[D].minzoom, BAND_DATA_MINZOOM[D]);
|
|
13544
|
+
}
|
|
10945
13545
|
var BAND_DRAW_ORDER = ["1", "2", "3", "4", "5", "6"];
|
|
10946
13546
|
var CHART_BAND_CODES = {
|
|
10947
13547
|
Overview: ["1"],
|
|
@@ -11102,12 +13702,26 @@ var DOMINANCE_EXEMPT_CLASSES = /* @__PURE__ */ new Set([
|
|
|
11102
13702
|
"RTPBCN",
|
|
11103
13703
|
"RDOSTA"
|
|
11104
13704
|
]);
|
|
13705
|
+
var DOMINANCE_EXEMPT_FILL_CLASSES = /* @__PURE__ */ new Set([
|
|
13706
|
+
"LNDARE",
|
|
13707
|
+
"DEPARE",
|
|
13708
|
+
"UNSARE",
|
|
13709
|
+
"LAKARE",
|
|
13710
|
+
"DRGARE",
|
|
13711
|
+
"SEAARE"
|
|
13712
|
+
]);
|
|
11105
13713
|
function isDominanceExempt(layer) {
|
|
11106
13714
|
for (const cls of DOMINANCE_EXEMPT_CLASSES) {
|
|
11107
13715
|
if (layer.id.startsWith(`s52-${cls}-`)) return true;
|
|
11108
13716
|
}
|
|
13717
|
+
if (layer.type === "fill") {
|
|
13718
|
+
for (const cls of DOMINANCE_EXEMPT_FILL_CLASSES) {
|
|
13719
|
+
if (layer.id.startsWith(`s52-${cls}-`)) return true;
|
|
13720
|
+
}
|
|
13721
|
+
}
|
|
11109
13722
|
return false;
|
|
11110
13723
|
}
|
|
13724
|
+
var TOP_STACK_LIGHT_CLASSES = /* @__PURE__ */ new Set(["LIGHTS"]);
|
|
11111
13725
|
function makeBandVariants(layer, B) {
|
|
11112
13726
|
const range = BAND_PRIORITY_RANGES[B];
|
|
11113
13727
|
const classMinzoom = layer.minzoom ?? 0;
|
|
@@ -11138,8 +13752,8 @@ function makeBandVariants(layer, B) {
|
|
|
11138
13752
|
for (const D of BAND_DRAW_ORDER) {
|
|
11139
13753
|
const dInt = parseInt(D, 10);
|
|
11140
13754
|
if (dInt <= bInt) continue;
|
|
11141
|
-
const
|
|
11142
|
-
if (
|
|
13755
|
+
const dMaxzoom = dominatedVariantMaxzoom(D);
|
|
13756
|
+
if (dMaxzoom <= minzoom) continue;
|
|
11143
13757
|
const domVariant = {
|
|
11144
13758
|
...layer,
|
|
11145
13759
|
id: `${layer.id}-band${B}-domBy${D}`,
|
|
@@ -11148,7 +13762,7 @@ function makeBandVariants(layer, B) {
|
|
|
11148
13762
|
["all", bandFilterExpression(B), dominatedByMinFilter(D)]
|
|
11149
13763
|
),
|
|
11150
13764
|
minzoom,
|
|
11151
|
-
maxzoom:
|
|
13765
|
+
maxzoom: dMaxzoom
|
|
11152
13766
|
};
|
|
11153
13767
|
variants.push(domVariant);
|
|
11154
13768
|
}
|
|
@@ -11195,46 +13809,26 @@ function fanOrientationLabelByCellBand(label) {
|
|
|
11195
13809
|
paint
|
|
11196
13810
|
});
|
|
11197
13811
|
for (const D of BAND_DRAW_ORDER) {
|
|
11198
|
-
const
|
|
11199
|
-
if (
|
|
13812
|
+
const dMaxzoom = dominatedVariantMaxzoom(D);
|
|
13813
|
+
if (dMaxzoom <= minzoom) continue;
|
|
11200
13814
|
variants.push({
|
|
11201
13815
|
...label,
|
|
11202
13816
|
id: `${label.id}-cell${code}-domBy${D}`,
|
|
11203
13817
|
filter: combineWithBandFilter(cellFiltered, dominatedByMinFilter(D)),
|
|
11204
13818
|
minzoom,
|
|
11205
|
-
|
|
13819
|
+
// Data-aware ceiling: band 6 has no tile data until z13 despite
|
|
13820
|
+
// being style-eligible at z11 (see dominatedVariantMaxzoom).
|
|
13821
|
+
maxzoom: dMaxzoom,
|
|
11206
13822
|
paint
|
|
11207
13823
|
});
|
|
11208
13824
|
}
|
|
11209
13825
|
}
|
|
11210
13826
|
return variants;
|
|
11211
13827
|
}
|
|
11212
|
-
function
|
|
11213
|
-
|
|
11214
|
-
|
|
11215
|
-
|
|
11216
|
-
return {
|
|
11217
|
-
...variant,
|
|
11218
|
-
layout: {
|
|
11219
|
-
...oldLayout,
|
|
11220
|
-
"text-allow-overlap": false,
|
|
11221
|
-
"text-optional": true,
|
|
11222
|
-
// Wider padding so even allowed labels space visually instead of
|
|
11223
|
-
// touching. 6 is enough to read individual buoy designations clearly
|
|
11224
|
-
// at z12-14 over Boston Harbor.
|
|
11225
|
-
"text-padding": 6,
|
|
11226
|
-
// Deterministic collision arbitration: prefer features with
|
|
11227
|
-
// longer-range nav-aid significance (VALNMR — visible nautical
|
|
11228
|
-
// miles), then shorter OBJNAM (tighter labels win), then FIDN as
|
|
11229
|
-
// tiebreaker. Lower sort-key wins in MapLibre.
|
|
11230
|
-
"symbol-sort-key": [
|
|
11231
|
-
"case",
|
|
11232
|
-
["has", "VALNMR"],
|
|
11233
|
-
["-", 0, ["coalesce", ["to-number", ["get", "VALNMR"]], 0]],
|
|
11234
|
-
["length", ["coalesce", ["get", "OBJNAM"], ""]]
|
|
11235
|
-
]
|
|
11236
|
-
}
|
|
11237
|
-
};
|
|
13828
|
+
function byLabelPriorityBottomUp(bundles) {
|
|
13829
|
+
return [...bundles].sort(
|
|
13830
|
+
(a, b) => getLabelPriority(b.objectClass) - getLabelPriority(a.objectClass)
|
|
13831
|
+
);
|
|
11238
13832
|
}
|
|
11239
13833
|
function chartModeFilterForBand(layer, B, higherCascadeBands, lockedBounds, isRequestedBand) {
|
|
11240
13834
|
let filter = combineWithBandFilter(layer.filter, bandFilterExpression(B));
|
|
@@ -11270,37 +13864,47 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
11270
13864
|
})
|
|
11271
13865
|
});
|
|
11272
13866
|
}
|
|
13867
|
+
const regularBundles = bundles.filter((b) => !TOP_STACK_LIGHT_CLASSES.has(b.objectClass));
|
|
13868
|
+
const topLightBundles = bundles.filter((b) => TOP_STACK_LIGHT_CLASSES.has(b.objectClass));
|
|
11273
13869
|
if (chartCascade && chartRequestedCodes) {
|
|
11274
13870
|
const requestedSet = new Set(chartRequestedCodes);
|
|
11275
|
-
|
|
11276
|
-
|
|
11277
|
-
|
|
11278
|
-
|
|
11279
|
-
|
|
11280
|
-
for (const
|
|
11281
|
-
const
|
|
11282
|
-
|
|
11283
|
-
|
|
11284
|
-
|
|
11285
|
-
|
|
11286
|
-
|
|
11287
|
-
|
|
11288
|
-
|
|
11289
|
-
|
|
11290
|
-
|
|
11291
|
-
|
|
11292
|
-
|
|
11293
|
-
|
|
11294
|
-
|
|
13871
|
+
const emitChartFeatureVariants = (bundleSet) => {
|
|
13872
|
+
for (let i = 0; i < chartCascade.length; i++) {
|
|
13873
|
+
const B = chartCascade[i];
|
|
13874
|
+
const higher = chartCascade.slice(i + 1);
|
|
13875
|
+
const isRequested = requestedSet.has(B);
|
|
13876
|
+
for (const { featureLayers } of bundleSet) {
|
|
13877
|
+
for (const base of featureLayers) {
|
|
13878
|
+
const filter = chartModeFilterForBand(
|
|
13879
|
+
base,
|
|
13880
|
+
B,
|
|
13881
|
+
higher,
|
|
13882
|
+
lockedBounds,
|
|
13883
|
+
isRequested
|
|
13884
|
+
);
|
|
13885
|
+
const variant = {
|
|
13886
|
+
...base,
|
|
13887
|
+
id: `${base.id}-band${B}`,
|
|
13888
|
+
filter
|
|
13889
|
+
};
|
|
13890
|
+
delete variant.maxzoom;
|
|
13891
|
+
layers.push(variant);
|
|
13892
|
+
}
|
|
11295
13893
|
}
|
|
11296
13894
|
}
|
|
11297
|
-
}
|
|
11298
|
-
|
|
11299
|
-
|
|
11300
|
-
|
|
11301
|
-
|
|
11302
|
-
|
|
11303
|
-
|
|
13895
|
+
};
|
|
13896
|
+
emitChartFeatureVariants(regularBundles);
|
|
13897
|
+
emitChartFeatureVariants(topLightBundles);
|
|
13898
|
+
const chartLabelBundles = byLabelPriorityBottomUp(
|
|
13899
|
+
bundles.filter(
|
|
13900
|
+
(b) => b.label !== null
|
|
13901
|
+
)
|
|
13902
|
+
);
|
|
13903
|
+
for (const { label } of chartLabelBundles) {
|
|
13904
|
+
for (let i = 0; i < chartCascade.length; i++) {
|
|
13905
|
+
const B = chartCascade[i];
|
|
13906
|
+
const higher = chartCascade.slice(i + 1);
|
|
13907
|
+
const isRequested = requestedSet.has(B);
|
|
11304
13908
|
const filter = chartModeFilterForBand(
|
|
11305
13909
|
label,
|
|
11306
13910
|
B,
|
|
@@ -11320,7 +13924,14 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
11320
13924
|
layers.push(...labelLayers);
|
|
11321
13925
|
} else {
|
|
11322
13926
|
for (const code of BAND_DRAW_ORDER) {
|
|
11323
|
-
for (const { featureLayers } of
|
|
13927
|
+
for (const { featureLayers } of regularBundles) {
|
|
13928
|
+
for (const base of featureLayers) {
|
|
13929
|
+
layers.push(...makeBandVariants(base, code));
|
|
13930
|
+
}
|
|
13931
|
+
}
|
|
13932
|
+
}
|
|
13933
|
+
for (const code of BAND_DRAW_ORDER) {
|
|
13934
|
+
for (const { featureLayers } of topLightBundles) {
|
|
11324
13935
|
for (const base of featureLayers) {
|
|
11325
13936
|
layers.push(...makeBandVariants(base, code));
|
|
11326
13937
|
}
|
|
@@ -11332,13 +13943,14 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
11332
13943
|
labelLayers.push(...fanOrientationLabelByCellBand(label));
|
|
11333
13944
|
}
|
|
11334
13945
|
}
|
|
11335
|
-
|
|
11336
|
-
|
|
11337
|
-
|
|
11338
|
-
|
|
11339
|
-
|
|
11340
|
-
|
|
11341
|
-
|
|
13946
|
+
const featureLabelBundles = byLabelPriorityBottomUp(
|
|
13947
|
+
bundles.filter(
|
|
13948
|
+
(b) => b.label !== null && getLabelBehavior(b.objectClass) !== "orientation-cross-band"
|
|
13949
|
+
)
|
|
13950
|
+
);
|
|
13951
|
+
for (const { label } of featureLabelBundles) {
|
|
13952
|
+
for (const code of BAND_DRAW_ORDER) {
|
|
13953
|
+
labelLayers.push(...makeBandVariants(label, code));
|
|
11342
13954
|
}
|
|
11343
13955
|
}
|
|
11344
13956
|
layers.push(...labelLayers);
|
|
@@ -11404,6 +14016,363 @@ function getStyleStats(style) {
|
|
|
11404
14016
|
};
|
|
11405
14017
|
}
|
|
11406
14018
|
|
|
14019
|
+
// src/atlas/custom-style.ts
|
|
14020
|
+
var DEFAULT_NAV_AID_SETTINGS = {
|
|
14021
|
+
buoysVisible: true,
|
|
14022
|
+
beaconsVisible: true,
|
|
14023
|
+
lightsVisible: true,
|
|
14024
|
+
scaminOverride: 0,
|
|
14025
|
+
iconScale: 0.8,
|
|
14026
|
+
iconSizeLocked: false,
|
|
14027
|
+
allowOverlap: false,
|
|
14028
|
+
showSectorArcs: true,
|
|
14029
|
+
fogSignalsVisible: true,
|
|
14030
|
+
radarVisible: true
|
|
14031
|
+
};
|
|
14032
|
+
var DEFAULT_LABEL_SETTINGS = {
|
|
14033
|
+
navAidLabelsVisible: true,
|
|
14034
|
+
landLabelsVisible: true,
|
|
14035
|
+
structureLabelsVisible: true,
|
|
14036
|
+
depthContoursVisible: true,
|
|
14037
|
+
navAidFullNames: false,
|
|
14038
|
+
minzoomOverride: null,
|
|
14039
|
+
textScale: 1,
|
|
14040
|
+
allowOverlap: false
|
|
14041
|
+
};
|
|
14042
|
+
var DEFAULT_SOUNDING_SETTINGS = {
|
|
14043
|
+
visible: true,
|
|
14044
|
+
minzoomOverride: null,
|
|
14045
|
+
textScale: 1,
|
|
14046
|
+
allowOverlap: false,
|
|
14047
|
+
showUnits: false,
|
|
14048
|
+
safetyContour: 10,
|
|
14049
|
+
fontWeight: "semibold",
|
|
14050
|
+
twoShade: true
|
|
14051
|
+
};
|
|
14052
|
+
var DEFAULT_WATER_BODY_SETTINGS = {
|
|
14053
|
+
seaAreasVisible: true,
|
|
14054
|
+
lakesVisible: true,
|
|
14055
|
+
riversVisible: true,
|
|
14056
|
+
canalsVisible: true,
|
|
14057
|
+
minzoomOverride: null,
|
|
14058
|
+
textScale: 1,
|
|
14059
|
+
allowOverlap: false,
|
|
14060
|
+
uppercaseSeaAreas: true
|
|
14061
|
+
};
|
|
14062
|
+
var NAV_AID_LAYER_GROUPS = {
|
|
14063
|
+
buoys: ["BOYLAT", "BOYCAR", "BOYSPP", "BOYISD", "BOYSAW"],
|
|
14064
|
+
beacons: ["BCNLAT", "BCNCAR", "BCNSPP"],
|
|
14065
|
+
lights: ["LIGHTS"],
|
|
14066
|
+
fogSignals: ["FOGSIG"],
|
|
14067
|
+
radar: ["RTPBCN", "RDOSTA"]
|
|
14068
|
+
};
|
|
14069
|
+
var ALL_NAV_AID_LAYERS = new Set(
|
|
14070
|
+
Object.values(NAV_AID_LAYER_GROUPS).flat()
|
|
14071
|
+
);
|
|
14072
|
+
var LABEL_LAYER_GROUPS = {
|
|
14073
|
+
navAids: ["BOYLAT", "BCNLAT", "BOYCAR", "BOYSPP", "BOYISD", "BOYSAW", "BCNCAR", "BCNSPP", "LIGHTS", "FOGSIG", "RTPBCN", "RDOSTA"],
|
|
14074
|
+
land: ["LNDRGN", "LNDMRK", "LNDARE"],
|
|
14075
|
+
structures: ["BRIDGE", "PILPNT", "MORFAC", "SLCONS", "BUISGL", "AIRARE"],
|
|
14076
|
+
depthContours: ["DEPCNT"]
|
|
14077
|
+
};
|
|
14078
|
+
var SOUNDING_LAYERS = ["SOUNDG"];
|
|
14079
|
+
var WATER_BODY_LAYER_GROUPS = {
|
|
14080
|
+
seaAreas: ["SEAARE"],
|
|
14081
|
+
lakes: ["LAKARE"],
|
|
14082
|
+
rivers: ["RIVERS"],
|
|
14083
|
+
canals: ["CANALS"]
|
|
14084
|
+
};
|
|
14085
|
+
var ALL_WATER_BODY_LAYERS = new Set(
|
|
14086
|
+
Object.values(WATER_BODY_LAYER_GROUPS).flat()
|
|
14087
|
+
);
|
|
14088
|
+
function isNavAidLayerVisible(layerName, settings) {
|
|
14089
|
+
if (NAV_AID_LAYER_GROUPS.buoys.includes(layerName)) return settings.buoysVisible;
|
|
14090
|
+
if (NAV_AID_LAYER_GROUPS.beacons.includes(layerName)) return settings.beaconsVisible;
|
|
14091
|
+
if (NAV_AID_LAYER_GROUPS.lights.includes(layerName)) return settings.lightsVisible;
|
|
14092
|
+
if (NAV_AID_LAYER_GROUPS.fogSignals.includes(layerName)) return settings.fogSignalsVisible;
|
|
14093
|
+
if (NAV_AID_LAYER_GROUPS.radar.includes(layerName)) return settings.radarVisible;
|
|
14094
|
+
return true;
|
|
14095
|
+
}
|
|
14096
|
+
function isLabelLayerVisible(layerName, settings) {
|
|
14097
|
+
if (LABEL_LAYER_GROUPS.navAids.includes(layerName)) return settings.navAidLabelsVisible;
|
|
14098
|
+
if (LABEL_LAYER_GROUPS.land.includes(layerName)) return settings.landLabelsVisible;
|
|
14099
|
+
if (LABEL_LAYER_GROUPS.structures.includes(layerName)) return settings.structureLabelsVisible;
|
|
14100
|
+
if (LABEL_LAYER_GROUPS.depthContours.includes(layerName)) return settings.depthContoursVisible;
|
|
14101
|
+
return true;
|
|
14102
|
+
}
|
|
14103
|
+
function isSoundingLayerVisible(settings) {
|
|
14104
|
+
return settings.visible;
|
|
14105
|
+
}
|
|
14106
|
+
function isWaterBodyLayerVisible(layerName, settings) {
|
|
14107
|
+
if (WATER_BODY_LAYER_GROUPS.seaAreas.includes(layerName)) return settings.seaAreasVisible;
|
|
14108
|
+
if (WATER_BODY_LAYER_GROUPS.lakes.includes(layerName)) return settings.lakesVisible;
|
|
14109
|
+
if (WATER_BODY_LAYER_GROUPS.rivers.includes(layerName)) return settings.riversVisible;
|
|
14110
|
+
if (WATER_BODY_LAYER_GROUPS.canals.includes(layerName)) return settings.canalsVisible;
|
|
14111
|
+
return true;
|
|
14112
|
+
}
|
|
14113
|
+
var DEFAULT_CUSTOM_STYLE_CONFIG = {
|
|
14114
|
+
enabled_color_tables: ["DAY", "DUSK", "NIGHT"],
|
|
14115
|
+
default_color_table: "DAY",
|
|
14116
|
+
display_mode: "STANDARD",
|
|
14117
|
+
nav_aid_settings: DEFAULT_NAV_AID_SETTINGS,
|
|
14118
|
+
label_settings: DEFAULT_LABEL_SETTINGS,
|
|
14119
|
+
sounding_settings: DEFAULT_SOUNDING_SETTINGS,
|
|
14120
|
+
water_body_settings: DEFAULT_WATER_BODY_SETTINGS,
|
|
14121
|
+
visible_layers: null
|
|
14122
|
+
};
|
|
14123
|
+
var COLOR_TABLE_NAMES = /* @__PURE__ */ new Set(["DAY", "DUSK", "NIGHT"]);
|
|
14124
|
+
var DISPLAY_MODES = /* @__PURE__ */ new Set(["BASE", "STANDARD", "ALL"]);
|
|
14125
|
+
var LAYER_NAME_RE = /^[A-Za-z0-9_$]{2,16}$/;
|
|
14126
|
+
var MAX_VISIBLE_LAYERS = 512;
|
|
14127
|
+
function clampNumber(raw, min, max, fallback) {
|
|
14128
|
+
const n = typeof raw === "number" ? raw : NaN;
|
|
14129
|
+
if (!Number.isFinite(n)) return fallback;
|
|
14130
|
+
return Math.min(max, Math.max(min, n));
|
|
14131
|
+
}
|
|
14132
|
+
function intOrNull(raw, min, max, fallback) {
|
|
14133
|
+
if (raw === null) return null;
|
|
14134
|
+
const n = typeof raw === "number" ? raw : NaN;
|
|
14135
|
+
if (!Number.isFinite(n)) return fallback;
|
|
14136
|
+
return Math.round(Math.min(max, Math.max(min, n)));
|
|
14137
|
+
}
|
|
14138
|
+
function bool(raw, fallback) {
|
|
14139
|
+
return typeof raw === "boolean" ? raw : fallback;
|
|
14140
|
+
}
|
|
14141
|
+
function asRecord(raw) {
|
|
14142
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
14143
|
+
}
|
|
14144
|
+
function sanitizeNavAid(raw) {
|
|
14145
|
+
const r = asRecord(raw);
|
|
14146
|
+
const d = DEFAULT_NAV_AID_SETTINGS;
|
|
14147
|
+
return {
|
|
14148
|
+
buoysVisible: bool(r.buoysVisible, d.buoysVisible),
|
|
14149
|
+
beaconsVisible: bool(r.beaconsVisible, d.beaconsVisible),
|
|
14150
|
+
lightsVisible: bool(r.lightsVisible, d.lightsVisible),
|
|
14151
|
+
scaminOverride: intOrNull(r.scaminOverride, 0, 14, d.scaminOverride),
|
|
14152
|
+
iconScale: clampNumber(r.iconScale, 0.2, 2, d.iconScale),
|
|
14153
|
+
iconSizeLocked: bool(r.iconSizeLocked, d.iconSizeLocked),
|
|
14154
|
+
allowOverlap: bool(r.allowOverlap, d.allowOverlap),
|
|
14155
|
+
showSectorArcs: bool(r.showSectorArcs, d.showSectorArcs),
|
|
14156
|
+
fogSignalsVisible: bool(r.fogSignalsVisible, d.fogSignalsVisible),
|
|
14157
|
+
radarVisible: bool(r.radarVisible, d.radarVisible)
|
|
14158
|
+
};
|
|
14159
|
+
}
|
|
14160
|
+
function sanitizeLabels(raw) {
|
|
14161
|
+
const r = asRecord(raw);
|
|
14162
|
+
const d = DEFAULT_LABEL_SETTINGS;
|
|
14163
|
+
return {
|
|
14164
|
+
navAidLabelsVisible: bool(r.navAidLabelsVisible, d.navAidLabelsVisible),
|
|
14165
|
+
landLabelsVisible: bool(r.landLabelsVisible, d.landLabelsVisible),
|
|
14166
|
+
structureLabelsVisible: bool(r.structureLabelsVisible, d.structureLabelsVisible),
|
|
14167
|
+
depthContoursVisible: bool(r.depthContoursVisible, d.depthContoursVisible),
|
|
14168
|
+
navAidFullNames: bool(r.navAidFullNames, d.navAidFullNames),
|
|
14169
|
+
minzoomOverride: intOrNull(r.minzoomOverride, 0, 14, d.minzoomOverride),
|
|
14170
|
+
textScale: clampNumber(r.textScale, 0.5, 2, d.textScale),
|
|
14171
|
+
allowOverlap: bool(r.allowOverlap, d.allowOverlap)
|
|
14172
|
+
};
|
|
14173
|
+
}
|
|
14174
|
+
function sanitizeSoundings(raw) {
|
|
14175
|
+
const r = asRecord(raw);
|
|
14176
|
+
const d = DEFAULT_SOUNDING_SETTINGS;
|
|
14177
|
+
return {
|
|
14178
|
+
visible: bool(r.visible, d.visible),
|
|
14179
|
+
minzoomOverride: intOrNull(r.minzoomOverride, 0, 14, d.minzoomOverride),
|
|
14180
|
+
textScale: clampNumber(r.textScale, 0.5, 2, d.textScale),
|
|
14181
|
+
allowOverlap: bool(r.allowOverlap, d.allowOverlap),
|
|
14182
|
+
showUnits: bool(r.showUnits, d.showUnits),
|
|
14183
|
+
safetyContour: clampNumber(r.safetyContour, 0, 100, d.safetyContour),
|
|
14184
|
+
fontWeight: r.fontWeight === "regular" || r.fontWeight === "semibold" ? r.fontWeight : d.fontWeight,
|
|
14185
|
+
twoShade: bool(r.twoShade, d.twoShade)
|
|
14186
|
+
};
|
|
14187
|
+
}
|
|
14188
|
+
function sanitizeWaterBody(raw) {
|
|
14189
|
+
const r = asRecord(raw);
|
|
14190
|
+
const d = DEFAULT_WATER_BODY_SETTINGS;
|
|
14191
|
+
return {
|
|
14192
|
+
seaAreasVisible: bool(r.seaAreasVisible, d.seaAreasVisible),
|
|
14193
|
+
lakesVisible: bool(r.lakesVisible, d.lakesVisible),
|
|
14194
|
+
riversVisible: bool(r.riversVisible, d.riversVisible),
|
|
14195
|
+
canalsVisible: bool(r.canalsVisible, d.canalsVisible),
|
|
14196
|
+
minzoomOverride: intOrNull(r.minzoomOverride, 0, 14, d.minzoomOverride),
|
|
14197
|
+
textScale: clampNumber(r.textScale, 0.5, 2, d.textScale),
|
|
14198
|
+
allowOverlap: bool(r.allowOverlap, d.allowOverlap),
|
|
14199
|
+
uppercaseSeaAreas: bool(r.uppercaseSeaAreas, d.uppercaseSeaAreas)
|
|
14200
|
+
};
|
|
14201
|
+
}
|
|
14202
|
+
function sanitizeCustomStyleConfig(raw) {
|
|
14203
|
+
const r = asRecord(raw);
|
|
14204
|
+
const enabledRaw = Array.isArray(r.enabled_color_tables) ? r.enabled_color_tables : [];
|
|
14205
|
+
const enabled = [...new Set(
|
|
14206
|
+
enabledRaw.filter((c) => typeof c === "string" && COLOR_TABLE_NAMES.has(c))
|
|
14207
|
+
)];
|
|
14208
|
+
const enabledFinal = enabled.length > 0 ? enabled : ["DAY", "DUSK", "NIGHT"];
|
|
14209
|
+
const defaultRaw = typeof r.default_color_table === "string" ? r.default_color_table : "";
|
|
14210
|
+
const defaultFinal = enabledFinal.includes(defaultRaw) ? defaultRaw : enabledFinal[0];
|
|
14211
|
+
const modeRaw = typeof r.display_mode === "string" ? r.display_mode : "";
|
|
14212
|
+
const mode = DISPLAY_MODES.has(modeRaw) ? modeRaw : "STANDARD";
|
|
14213
|
+
let visibleLayers = null;
|
|
14214
|
+
if (Array.isArray(r.visible_layers)) {
|
|
14215
|
+
visibleLayers = r.visible_layers.filter((l) => typeof l === "string" && LAYER_NAME_RE.test(l)).slice(0, MAX_VISIBLE_LAYERS).map((l) => l.toUpperCase());
|
|
14216
|
+
}
|
|
14217
|
+
return {
|
|
14218
|
+
enabled_color_tables: enabledFinal,
|
|
14219
|
+
default_color_table: defaultFinal,
|
|
14220
|
+
display_mode: mode,
|
|
14221
|
+
nav_aid_settings: sanitizeNavAid(r.nav_aid_settings),
|
|
14222
|
+
label_settings: sanitizeLabels(r.label_settings),
|
|
14223
|
+
sounding_settings: sanitizeSoundings(r.sounding_settings),
|
|
14224
|
+
water_body_settings: sanitizeWaterBody(r.water_body_settings),
|
|
14225
|
+
visible_layers: visibleLayers
|
|
14226
|
+
};
|
|
14227
|
+
}
|
|
14228
|
+
function normalizeTheme(raw) {
|
|
14229
|
+
if (!raw) return null;
|
|
14230
|
+
const upper = raw.trim().toUpperCase();
|
|
14231
|
+
return COLOR_TABLE_NAMES.has(upper) ? upper : null;
|
|
14232
|
+
}
|
|
14233
|
+
function resolveCustomTheme(config, requested) {
|
|
14234
|
+
if (!requested) return { palette: config.default_color_table, themeFellBack: false };
|
|
14235
|
+
if (config.enabled_color_tables.includes(requested)) {
|
|
14236
|
+
return { palette: requested, themeFellBack: false };
|
|
14237
|
+
}
|
|
14238
|
+
return { palette: config.default_color_table, themeFellBack: true };
|
|
14239
|
+
}
|
|
14240
|
+
function layerObjectClass(layer) {
|
|
14241
|
+
const meta = layer.metadata;
|
|
14242
|
+
const fromMeta = meta?.["s52:objectClass"];
|
|
14243
|
+
if (typeof fromMeta === "string" && fromMeta.length > 0) return fromMeta;
|
|
14244
|
+
const m = /^s52-([A-Za-z0-9_$]+)-/.exec(layer.id);
|
|
14245
|
+
return m ? m[1] : null;
|
|
14246
|
+
}
|
|
14247
|
+
function isLabelLayer(layer) {
|
|
14248
|
+
if (layer.type !== "symbol") return false;
|
|
14249
|
+
const layout = layer.layout;
|
|
14250
|
+
return layout?.["text-field"] !== void 0;
|
|
14251
|
+
}
|
|
14252
|
+
function isIconLayer(layer) {
|
|
14253
|
+
if (layer.type !== "symbol") return false;
|
|
14254
|
+
const layout = layer.layout;
|
|
14255
|
+
return layout?.["icon-image"] !== void 0 && layout?.["text-field"] === void 0;
|
|
14256
|
+
}
|
|
14257
|
+
function bandFloor(layerId) {
|
|
14258
|
+
const m = /-band(\d)/.exec(layerId);
|
|
14259
|
+
if (!m) return 0;
|
|
14260
|
+
const range = BAND_PRIORITY_RANGES[m[1]];
|
|
14261
|
+
return range ? range.minzoom : 0;
|
|
14262
|
+
}
|
|
14263
|
+
function scaleSizeValue(value, factor) {
|
|
14264
|
+
if (typeof value === "number") {
|
|
14265
|
+
return Math.round(value * factor * 100) / 100;
|
|
14266
|
+
}
|
|
14267
|
+
if (!Array.isArray(value) || value.length === 0) return value;
|
|
14268
|
+
const op = value[0];
|
|
14269
|
+
if (op === "interpolate") {
|
|
14270
|
+
const out = [...value];
|
|
14271
|
+
for (let i = 4; i < out.length; i += 2) out[i] = scaleSizeValue(out[i], factor);
|
|
14272
|
+
return out;
|
|
14273
|
+
}
|
|
14274
|
+
if (op === "step") {
|
|
14275
|
+
const out = [...value];
|
|
14276
|
+
for (let i = 2; i < out.length; i += 2) out[i] = scaleSizeValue(out[i], factor);
|
|
14277
|
+
return out;
|
|
14278
|
+
}
|
|
14279
|
+
if (op === "case") {
|
|
14280
|
+
const out = [...value];
|
|
14281
|
+
for (let i = 2; i < out.length - 1; i += 2) out[i] = scaleSizeValue(out[i], factor);
|
|
14282
|
+
out[out.length - 1] = scaleSizeValue(out[out.length - 1], factor);
|
|
14283
|
+
return out;
|
|
14284
|
+
}
|
|
14285
|
+
if (op === "match") {
|
|
14286
|
+
const out = [...value];
|
|
14287
|
+
for (let i = 3; i < out.length - 1; i += 2) out[i] = scaleSizeValue(out[i], factor);
|
|
14288
|
+
out[out.length - 1] = scaleSizeValue(out[out.length - 1], factor);
|
|
14289
|
+
return out;
|
|
14290
|
+
}
|
|
14291
|
+
return value;
|
|
14292
|
+
}
|
|
14293
|
+
function setLayout(layer, key, val) {
|
|
14294
|
+
const layout = { ...layer.layout ?? {} };
|
|
14295
|
+
layout[key] = val;
|
|
14296
|
+
layer.layout = layout;
|
|
14297
|
+
}
|
|
14298
|
+
function hideLayer(layer) {
|
|
14299
|
+
setLayout(layer, "visibility", "none");
|
|
14300
|
+
}
|
|
14301
|
+
function applyCustomStyleSettings(style, config) {
|
|
14302
|
+
const nav = config.nav_aid_settings;
|
|
14303
|
+
const labels = config.label_settings;
|
|
14304
|
+
const soundings = config.sounding_settings;
|
|
14305
|
+
const water = config.water_body_settings;
|
|
14306
|
+
const visibleSet = config.visible_layers ? new Set(config.visible_layers) : null;
|
|
14307
|
+
for (const layer of style.layers) {
|
|
14308
|
+
const cls = layerObjectClass(layer);
|
|
14309
|
+
if (!cls) continue;
|
|
14310
|
+
const label = isLabelLayer(layer);
|
|
14311
|
+
if (visibleSet && !visibleSet.has(cls)) {
|
|
14312
|
+
hideLayer(layer);
|
|
14313
|
+
continue;
|
|
14314
|
+
}
|
|
14315
|
+
if (label) {
|
|
14316
|
+
const hidden = SOUNDING_LAYERS.includes(cls) && !isSoundingLayerVisible(soundings) || ALL_WATER_BODY_LAYERS.has(cls) && !isWaterBodyLayerVisible(cls, water) || !SOUNDING_LAYERS.includes(cls) && !ALL_WATER_BODY_LAYERS.has(cls) && !isLabelLayerVisible(cls, labels);
|
|
14317
|
+
if (hidden) {
|
|
14318
|
+
hideLayer(layer);
|
|
14319
|
+
continue;
|
|
14320
|
+
}
|
|
14321
|
+
} else {
|
|
14322
|
+
const hidden = ALL_NAV_AID_LAYERS.has(cls) && !isNavAidLayerVisible(cls, nav) || ALL_WATER_BODY_LAYERS.has(cls) && !isWaterBodyLayerVisible(cls, water) || SOUNDING_LAYERS.includes(cls) && !isSoundingLayerVisible(soundings);
|
|
14323
|
+
if (hidden) {
|
|
14324
|
+
hideLayer(layer);
|
|
14325
|
+
continue;
|
|
14326
|
+
}
|
|
14327
|
+
}
|
|
14328
|
+
if (label) {
|
|
14329
|
+
const bundle = SOUNDING_LAYERS.includes(cls) ? { textScale: soundings.textScale, minzoom: soundings.minzoomOverride, allowOverlap: soundings.allowOverlap } : ALL_WATER_BODY_LAYERS.has(cls) ? { textScale: water.textScale, minzoom: water.minzoomOverride, allowOverlap: water.allowOverlap } : { textScale: labels.textScale, minzoom: labels.minzoomOverride, allowOverlap: labels.allowOverlap };
|
|
14330
|
+
if (bundle.textScale !== 1) {
|
|
14331
|
+
const layout = layer.layout;
|
|
14332
|
+
if (layout?.["text-size"] !== void 0) {
|
|
14333
|
+
setLayout(layer, "text-size", scaleSizeValue(layout["text-size"], bundle.textScale));
|
|
14334
|
+
}
|
|
14335
|
+
}
|
|
14336
|
+
if (bundle.allowOverlap === true) {
|
|
14337
|
+
setLayout(layer, "text-allow-overlap", true);
|
|
14338
|
+
setLayout(layer, "text-optional", false);
|
|
14339
|
+
}
|
|
14340
|
+
if (bundle.minzoom !== null) {
|
|
14341
|
+
layer.minzoom = Math.max(bundle.minzoom, bandFloor(layer.id));
|
|
14342
|
+
}
|
|
14343
|
+
if (cls === "SEAARE") {
|
|
14344
|
+
setLayout(layer, "text-transform", water.uppercaseSeaAreas ? "uppercase" : "none");
|
|
14345
|
+
}
|
|
14346
|
+
}
|
|
14347
|
+
if (!label && ALL_NAV_AID_LAYERS.has(cls) && isIconLayer(layer)) {
|
|
14348
|
+
if (nav.iconScale !== 1) {
|
|
14349
|
+
const layout = layer.layout;
|
|
14350
|
+
const current = layout?.["icon-size"];
|
|
14351
|
+
setLayout(layer, "icon-size", current === void 0 ? Math.round(nav.iconScale * 100) / 100 : scaleSizeValue(current, nav.iconScale));
|
|
14352
|
+
}
|
|
14353
|
+
if (nav.allowOverlap === true) {
|
|
14354
|
+
setLayout(layer, "icon-allow-overlap", true);
|
|
14355
|
+
}
|
|
14356
|
+
if (nav.scaminOverride !== null) {
|
|
14357
|
+
layer.minzoom = Math.max(nav.scaminOverride, bandFloor(layer.id));
|
|
14358
|
+
}
|
|
14359
|
+
}
|
|
14360
|
+
}
|
|
14361
|
+
return style;
|
|
14362
|
+
}
|
|
14363
|
+
function generateCustomStyle(config, opts = {}) {
|
|
14364
|
+
const { theme, ...generatorOpts } = opts;
|
|
14365
|
+
const { palette, themeFellBack } = resolveCustomTheme(config, theme ?? null);
|
|
14366
|
+
const style = generateStyle(palette, {
|
|
14367
|
+
...generatorOpts,
|
|
14368
|
+
safetyContour: config.sounding_settings.safetyContour,
|
|
14369
|
+
twoShadeSoundings: config.sounding_settings.twoShade,
|
|
14370
|
+
soundingFontWeight: config.sounding_settings.fontWeight
|
|
14371
|
+
});
|
|
14372
|
+
applyCustomStyleSettings(style, config);
|
|
14373
|
+
return { style, palette, themeFellBack };
|
|
14374
|
+
}
|
|
14375
|
+
|
|
11407
14376
|
// src/pagination.ts
|
|
11408
14377
|
async function* paginate(fetchPage, getItems, getTotalCount, options) {
|
|
11409
14378
|
const pageSize = options?.pageSize ?? 100;
|
|
@@ -11459,6 +14428,12 @@ var Chrt = class {
|
|
|
11459
14428
|
* requires `manifestPath`.
|
|
11460
14429
|
*/
|
|
11461
14430
|
offline;
|
|
14431
|
+
/**
|
|
14432
|
+
* CHRTinteract — click-to-inspect: `attach(map)` for wired popups/events,
|
|
14433
|
+
* `inspect(feature)` for headless decode. Included with the Dev plan and
|
|
14434
|
+
* above; enforced server-side at attach/inspect time.
|
|
14435
|
+
*/
|
|
14436
|
+
interact;
|
|
11462
14437
|
featuresEp;
|
|
11463
14438
|
queryEp;
|
|
11464
14439
|
http;
|
|
@@ -11471,13 +14446,14 @@ var Chrt = class {
|
|
|
11471
14446
|
this.tileBaseUrl = config.tileBaseUrl ?? DEFAULT_TILE_BASE_URL;
|
|
11472
14447
|
this.http = new HttpClient(config.apiKey, config.baseUrl, config.retries);
|
|
11473
14448
|
const tileOpts = { region: this.region, tileBaseUrl: this.tileBaseUrl };
|
|
11474
|
-
this.
|
|
14449
|
+
this.tileAuth = new TileAuth(this.http, tileOpts);
|
|
14450
|
+
this.tiles = new TilesEndpoint(this.http, this.tileAuth, tileOpts);
|
|
11475
14451
|
this.cells = new CellsEndpoint(this.http);
|
|
11476
14452
|
this.coverage = new CoverageEndpoint(this.http);
|
|
11477
14453
|
this.featuresEp = new FeaturesEndpoint(this.http);
|
|
11478
14454
|
this.queryEp = new QueryEndpoint(this.http);
|
|
11479
14455
|
this.offline = new OfflineEndpoint(this.http, config.deviceId, config.manifestPath);
|
|
11480
|
-
this.
|
|
14456
|
+
this.interact = new InteractClient(this.http);
|
|
11481
14457
|
}
|
|
11482
14458
|
/* ── Convenience shortcuts ───────────────────────── */
|
|
11483
14459
|
/** Query GeoJSON features by bounding box */
|
|
@@ -11571,6 +14547,9 @@ var Chrt = class {
|
|
|
11571
14547
|
* });
|
|
11572
14548
|
*
|
|
11573
14549
|
* @remarks S-101 portrayal is on the roadmap; today `style()` emits S-52.
|
|
14550
|
+
* Generated locally, so it never applies a key-bound CHRTstyle custom
|
|
14551
|
+
* style — use {@link styleUrl} / {@link fetchStyle} (the served Style API)
|
|
14552
|
+
* for that.
|
|
11574
14553
|
*/
|
|
11575
14554
|
style(opts = {}) {
|
|
11576
14555
|
const { palette = "DAY", ...styleOpts } = opts;
|
|
@@ -11581,12 +14560,33 @@ var Chrt = class {
|
|
|
11581
14560
|
});
|
|
11582
14561
|
}
|
|
11583
14562
|
/**
|
|
11584
|
-
*
|
|
11585
|
-
*
|
|
11586
|
-
*
|
|
14563
|
+
* URL of the served S-52 style — the CHRTstyle Style API at
|
|
14564
|
+
* `tiles.chrt.co/v1/styles/{region}/style.json`. Unlike {@link style}
|
|
14565
|
+
* (generated locally), the served document honors the API key's bound
|
|
14566
|
+
* CHRTstyle custom style: palettes, layer visibility, label/icon scale,
|
|
14567
|
+
* and the rest of the platform's Map Styles settings, applied server-side
|
|
14568
|
+
* and tier-gated (custom styles are Dev+; Free keys get the default
|
|
14569
|
+
* themes). Async because it pre-mints the tile token, so the style
|
|
14570
|
+
* request is guaranteed to carry auth via `transformRequest()`.
|
|
14571
|
+
*
|
|
14572
|
+
* @example
|
|
14573
|
+
* const map = new maplibregl.Map({
|
|
14574
|
+
* container: 'map',
|
|
14575
|
+
* style: await chrt.styleUrl({ theme: 'day' }),
|
|
14576
|
+
* transformRequest: chrt.transformRequest(),
|
|
14577
|
+
* });
|
|
14578
|
+
*/
|
|
14579
|
+
async styleUrl(opts) {
|
|
14580
|
+
return this.tiles.styleUrl(opts);
|
|
14581
|
+
}
|
|
14582
|
+
/**
|
|
14583
|
+
* Fetch the served S-52 style (see {@link styleUrl}) as a MapLibre style
|
|
14584
|
+
* object — inspect or mutate it before handing it to the map.
|
|
14585
|
+
* `chrt.style()` remains the local generator that needs no network
|
|
14586
|
+
* round-trip (and therefore applies no custom style).
|
|
11587
14587
|
*/
|
|
11588
|
-
|
|
11589
|
-
return this.tiles.
|
|
14588
|
+
async fetchStyle(opts) {
|
|
14589
|
+
return this.tiles.fetchStyle(opts);
|
|
11590
14590
|
}
|
|
11591
14591
|
/**
|
|
11592
14592
|
* Returns a `transformRequest` for Mapbox GL JS / MapLibre GL JS that mints a
|
|
@@ -11615,8 +14615,12 @@ var Chrt = class {
|
|
|
11615
14615
|
};
|
|
11616
14616
|
// Annotate the CommonJS export names for ESM import in node:
|
|
11617
14617
|
0 && (module.exports = {
|
|
14618
|
+
ALL_NAV_AID_LAYERS,
|
|
14619
|
+
ALL_WATER_BODY_LAYERS,
|
|
14620
|
+
AREA_QUERY_LAYERS,
|
|
11618
14621
|
BAND_DRAW_ORDER,
|
|
11619
14622
|
BAND_PRIORITY_RANGES,
|
|
14623
|
+
BRIDGE_QUERY_LAYERS,
|
|
11620
14624
|
BUILD_MAXZOOM,
|
|
11621
14625
|
BUILD_MINZOOM,
|
|
11622
14626
|
CHART_BAND_CODES,
|
|
@@ -11624,12 +14628,25 @@ var Chrt = class {
|
|
|
11624
14628
|
CHART_SECTIONS,
|
|
11625
14629
|
Chrt,
|
|
11626
14630
|
ChrtError,
|
|
14631
|
+
DEFAULT_CUSTOM_STYLE_CONFIG,
|
|
14632
|
+
DEFAULT_LABEL_SETTINGS,
|
|
14633
|
+
DEFAULT_NAV_AID_SETTINGS,
|
|
11627
14634
|
DEFAULT_REGION,
|
|
14635
|
+
DEFAULT_SOUNDING_SETTINGS,
|
|
11628
14636
|
DEFAULT_TILE_BASE_URL,
|
|
11629
14637
|
DEFAULT_TILE_REGION,
|
|
14638
|
+
DEFAULT_WATER_BODY_SETTINGS,
|
|
14639
|
+
InteractClient,
|
|
14640
|
+
InteractError,
|
|
11630
14641
|
LABELED_LAYERS,
|
|
11631
14642
|
LABEL_BEHAVIOR,
|
|
14643
|
+
LABEL_LAYER_GROUPS,
|
|
14644
|
+
LABEL_PRIORITY,
|
|
11632
14645
|
NAUTICAL_SYMBOLS,
|
|
14646
|
+
NAV_AID_LAYER_GROUPS,
|
|
14647
|
+
NAV_AID_QUERY_LAYERS,
|
|
14648
|
+
NON_SOLAS_DISCLAIMER,
|
|
14649
|
+
NON_SOLAS_DISCLAIMER_SHORT,
|
|
11633
14650
|
OfflineError,
|
|
11634
14651
|
OfflineStyleError,
|
|
11635
14652
|
S101_CHART_SECTIONS,
|
|
@@ -11642,21 +14659,34 @@ var Chrt = class {
|
|
|
11642
14659
|
S52_DUSK,
|
|
11643
14660
|
S52_LOOKUP,
|
|
11644
14661
|
S52_NIGHT,
|
|
14662
|
+
SOUNDING_LAYERS,
|
|
11645
14663
|
SYMBOL_GROUPS,
|
|
11646
14664
|
SYMBOL_MAP,
|
|
11647
14665
|
TileAuth,
|
|
11648
14666
|
TileTokenError,
|
|
14667
|
+
WATER_BODY_LAYER_GROUPS,
|
|
11649
14668
|
applyColorTheme,
|
|
14669
|
+
applyCustomStyleSettings,
|
|
11650
14670
|
bandFilterExpression,
|
|
14671
|
+
buildFlashPattern,
|
|
11651
14672
|
buildLabelLayer,
|
|
11652
14673
|
buildLayersForClass,
|
|
11653
14674
|
buildOfflineStyle,
|
|
14675
|
+
buildSectorArcGeoJSON,
|
|
14676
|
+
buildSectorArcsFC,
|
|
14677
|
+
buildSectorsFromFeatures,
|
|
11654
14678
|
buildSources,
|
|
11655
14679
|
combineWithBandFilter,
|
|
11656
14680
|
daymarkColor,
|
|
11657
14681
|
decimateFeatures,
|
|
14682
|
+
decodeAreaFeatureProps,
|
|
14683
|
+
decodeBridgeProps,
|
|
14684
|
+
decodeNavAidProps,
|
|
11658
14685
|
depareColor,
|
|
11659
14686
|
depareFillExpression,
|
|
14687
|
+
deriveMarinerName,
|
|
14688
|
+
extractNavAidDesignation,
|
|
14689
|
+
generateCustomStyle,
|
|
11660
14690
|
generateSpriteManifest,
|
|
11661
14691
|
generateSpriteSheet,
|
|
11662
14692
|
generateStyle,
|
|
@@ -11671,6 +14701,7 @@ var Chrt = class {
|
|
|
11671
14701
|
getIconExpression,
|
|
11672
14702
|
getLabelBehavior,
|
|
11673
14703
|
getLabelConfig,
|
|
14704
|
+
getLabelPriority,
|
|
11674
14705
|
getLookupEntry,
|
|
11675
14706
|
getMaxCellsForZoom,
|
|
11676
14707
|
getOrderedClasses,
|
|
@@ -11679,22 +14710,33 @@ var Chrt = class {
|
|
|
11679
14710
|
getStyleStats,
|
|
11680
14711
|
getSymbolSvgString,
|
|
11681
14712
|
getThemedS52Svg,
|
|
14713
|
+
getTopmarkMeaning,
|
|
11682
14714
|
hazardColor,
|
|
11683
14715
|
hazardFillExpression,
|
|
14716
|
+
isLabelLayerVisible,
|
|
11684
14717
|
isNavAidLayer,
|
|
14718
|
+
isNavAidLayerVisible,
|
|
14719
|
+
isSoundingLayerVisible,
|
|
11685
14720
|
isVisibleInMode,
|
|
14721
|
+
isWaterBodyLayerVisible,
|
|
14722
|
+
labelSortKey,
|
|
11686
14723
|
lateralColor,
|
|
11687
14724
|
lateralFillExpression,
|
|
11688
14725
|
lightColor,
|
|
11689
14726
|
lightFillExpression,
|
|
14727
|
+
normalizeTheme,
|
|
11690
14728
|
paginate,
|
|
14729
|
+
parseListCodes,
|
|
11691
14730
|
parseWebhook,
|
|
11692
14731
|
preprocessLights,
|
|
11693
14732
|
preprocessNavAids,
|
|
11694
14733
|
preprocessSoundings,
|
|
11695
14734
|
preprocessWaterBodies,
|
|
11696
14735
|
resolveColor,
|
|
14736
|
+
resolveCustomTheme,
|
|
11697
14737
|
resolveStyle,
|
|
14738
|
+
sanitizeCustomStyleConfig,
|
|
14739
|
+
scaleSizeValue,
|
|
11698
14740
|
scaminToMinZoom,
|
|
11699
14741
|
withinBboxFilter,
|
|
11700
14742
|
worldMinusBboxPolygon
|