@chrtco/sdk 0.10.18 → 0.10.20
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/dist/atlas/browser.cjs +29 -1
- package/dist/atlas/browser.cjs.map +1 -1
- package/dist/atlas/browser.d.cts +2 -2
- package/dist/atlas/browser.d.ts +2 -2
- package/dist/atlas/browser.js +28 -1
- package/dist/atlas/browser.js.map +1 -1
- package/dist/index.cjs +1377 -359
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +283 -6
- package/dist/index.d.ts +283 -6
- package/dist/index.js +1372 -359
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -26,9 +26,13 @@ __export(index_exports, {
|
|
|
26
26
|
ATON_EXPRESSION_CLASSES: () => ATON_EXPRESSION_CLASSES,
|
|
27
27
|
BAND_DRAW_ORDER: () => BAND_DRAW_ORDER,
|
|
28
28
|
BAND_PRIORITY_RANGES: () => BAND_PRIORITY_RANGES,
|
|
29
|
+
BASE_MAXZOOM: () => BASE_MAXZOOM,
|
|
30
|
+
BASE_MINZOOM: () => BASE_MINZOOM,
|
|
31
|
+
BASE_REGION: () => BASE_REGION,
|
|
29
32
|
BRIDGE_QUERY_LAYERS: () => BRIDGE_QUERY_LAYERS,
|
|
30
33
|
BUILD_MAXZOOM: () => BUILD_MAXZOOM,
|
|
31
34
|
BUILD_MINZOOM: () => BUILD_MINZOOM,
|
|
35
|
+
BUILD_PARAM: () => BUILD_PARAM,
|
|
32
36
|
CHART_BAND_CODES: () => CHART_BAND_CODES,
|
|
33
37
|
CHART_CASCADE_CODES: () => CHART_CASCADE_CODES,
|
|
34
38
|
CHART_SECTIONS: () => CHART_SECTIONS,
|
|
@@ -210,6 +214,7 @@ __export(index_exports, {
|
|
|
210
214
|
scaleSizeValue: () => scaleSizeValue,
|
|
211
215
|
scaminToMinZoom: () => scaminToMinZoom,
|
|
212
216
|
styleModeBand: () => styleModeBand,
|
|
217
|
+
tileTemplate: () => tileTemplate,
|
|
213
218
|
withinBboxFilter: () => withinBboxFilter,
|
|
214
219
|
worldMinusBboxPolygon: () => worldMinusBboxPolygon
|
|
215
220
|
});
|
|
@@ -351,7 +356,7 @@ var TileTokenError = class extends Error {
|
|
|
351
356
|
this.status = status;
|
|
352
357
|
}
|
|
353
358
|
};
|
|
354
|
-
var TILE_URL_PATH = /\/v1\/tiles\/[A-Za-z0-9_]+(?:\/[a-z][a-z0-9-]*)?\/\d+\/\d+\/\d+\.mvt(?:\?.*)?$/i;
|
|
359
|
+
var TILE_URL_PATH = /\/v1\/tiles\/[A-Za-z0-9_]+(?:\/[a-z][a-z0-9-]*)?\/\d+\/\d+\/\d+\.(?:mvt|png)(?:\?.*)?$/i;
|
|
355
360
|
var STYLE_URL_PATH = /\/v1\/styles\/[A-Za-z0-9_]+\/style\.json(?:\?.*)?$/i;
|
|
356
361
|
var REFRESH_MARGIN_SECONDS = 30;
|
|
357
362
|
var STALE_GRACE_SECONDS = 1;
|
|
@@ -362,6 +367,15 @@ function nowSeconds() {
|
|
|
362
367
|
function sleep2(ms) {
|
|
363
368
|
return new Promise((r) => setTimeout(r, ms));
|
|
364
369
|
}
|
|
370
|
+
function readBuildTags(raw, region, buildTag) {
|
|
371
|
+
const out = { [region]: buildTag };
|
|
372
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
373
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
374
|
+
if (typeof value === "string" && value) out[key] = value;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return out;
|
|
378
|
+
}
|
|
365
379
|
function mapErrorCode(raw, status) {
|
|
366
380
|
const known = [
|
|
367
381
|
"invalid_api_key",
|
|
@@ -419,6 +433,7 @@ var TileAuth = class {
|
|
|
419
433
|
expiresAt: b.expires_at,
|
|
420
434
|
kid: b.kid,
|
|
421
435
|
buildTag: b.build_tag,
|
|
436
|
+
buildTags: readBuildTags(b.build_tags, b.region, b.build_tag),
|
|
422
437
|
region: b.region
|
|
423
438
|
};
|
|
424
439
|
}
|
|
@@ -494,6 +509,22 @@ var TileAuth = class {
|
|
|
494
509
|
if (entry.response.expiresAt - nowSeconds() <= STALE_GRACE_SECONDS) return null;
|
|
495
510
|
return entry.response.token;
|
|
496
511
|
}
|
|
512
|
+
/**
|
|
513
|
+
* Synchronous peek at the cached mint's per-region build ids — `{}` when the
|
|
514
|
+
* cache is cold.
|
|
515
|
+
*
|
|
516
|
+
* `chrt.style()` is synchronous and cannot mint, so this is how a style
|
|
517
|
+
* generated after the cache is warm picks up the build discriminator without
|
|
518
|
+
* the caller threading it by hand. A cold cache yields `{}`, which produces
|
|
519
|
+
* the undiscriminated URL the SDK emitted before this existed — never an
|
|
520
|
+
* error, and never a wrong build.
|
|
521
|
+
*/
|
|
522
|
+
peekBuildTags(region) {
|
|
523
|
+
const entry = this.cache.get(region ?? this.defaultRegion);
|
|
524
|
+
if (!entry) return {};
|
|
525
|
+
if (entry.response.expiresAt - nowSeconds() <= STALE_GRACE_SECONDS) return {};
|
|
526
|
+
return entry.response.buildTags;
|
|
527
|
+
}
|
|
497
528
|
/**
|
|
498
529
|
* Produce a MapLibre / Mapbox GL `transformRequest`. Attaches
|
|
499
530
|
* `Authorization: Bearer <jwt>` only to CHRT tile URLs and the CHRT
|
|
@@ -547,6 +578,78 @@ var TileAuth = class {
|
|
|
547
578
|
}
|
|
548
579
|
};
|
|
549
580
|
|
|
581
|
+
// src/atlas/tile-zoom.ts
|
|
582
|
+
var BUILD_MINZOOM = 0;
|
|
583
|
+
var BUILD_MAXZOOM = 14;
|
|
584
|
+
|
|
585
|
+
// src/atlas/source-registry.ts
|
|
586
|
+
var LAND_REGION = "CHRT_LAND";
|
|
587
|
+
var RELIEF_REGION = "CHRT_RELIEF";
|
|
588
|
+
var BASE_REGION = "CHRT_BASE";
|
|
589
|
+
var BASE_MINZOOM = 0;
|
|
590
|
+
var BASE_MAXZOOM = 11;
|
|
591
|
+
var RELIEF_MINZOOM = 9;
|
|
592
|
+
var RELIEF_MAXZOOM = 12;
|
|
593
|
+
var RELIEF_TILE_SIZE = 512;
|
|
594
|
+
var BUILD_PARAM = "v";
|
|
595
|
+
function tileTemplate(base, region, extension, buildId) {
|
|
596
|
+
const url = `${base}/v1/tiles/${region}/{z}/{x}/{y}.${extension}`;
|
|
597
|
+
const id = buildId?.trim();
|
|
598
|
+
if (!id) return url;
|
|
599
|
+
return `${url}?${BUILD_PARAM}=${encodeURIComponent(id)}`;
|
|
600
|
+
}
|
|
601
|
+
var DEFAULT_TILE_BASE_URL2 = "https://tiles.chrt.co";
|
|
602
|
+
var DEFAULT_REGION = "NOAA_ENC";
|
|
603
|
+
function buildSources(opts = {}) {
|
|
604
|
+
const mode = opts.mode ?? "pmtiles";
|
|
605
|
+
const region = opts.region ?? DEFAULT_REGION;
|
|
606
|
+
if (mode === "mapbox") {
|
|
607
|
+
return {
|
|
608
|
+
[region]: {
|
|
609
|
+
type: "vector",
|
|
610
|
+
url: `mapbox://chrt.${region.toLowerCase()}`
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
const base = (opts.tileBaseUrl ?? DEFAULT_TILE_BASE_URL2).replace(/\/+$/, "");
|
|
615
|
+
const buildFor = (r) => opts.buildTags?.[r] ?? (r === region ? opts.buildTag : void 0);
|
|
616
|
+
const sources = {
|
|
617
|
+
[region]: {
|
|
618
|
+
type: "vector",
|
|
619
|
+
tiles: [tileTemplate(base, region, "mvt", buildFor(region))],
|
|
620
|
+
minzoom: BUILD_MINZOOM,
|
|
621
|
+
maxzoom: BUILD_MAXZOOM
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
if (opts.land) {
|
|
625
|
+
sources[LAND_REGION] = {
|
|
626
|
+
type: "vector",
|
|
627
|
+
tiles: [tileTemplate(base, LAND_REGION, "mvt", buildFor(LAND_REGION))],
|
|
628
|
+
minzoom: BUILD_MINZOOM,
|
|
629
|
+
maxzoom: BUILD_MAXZOOM
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
if (opts.base) {
|
|
633
|
+
sources[BASE_REGION] = {
|
|
634
|
+
type: "vector",
|
|
635
|
+
tiles: [`${base}/v1/tiles/${BASE_REGION}/{z}/{x}/{y}.mvt`],
|
|
636
|
+
minzoom: BASE_MINZOOM,
|
|
637
|
+
maxzoom: BASE_MAXZOOM
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
if (opts.relief) {
|
|
641
|
+
sources[RELIEF_REGION] = {
|
|
642
|
+
type: "raster-dem",
|
|
643
|
+
tiles: [tileTemplate(base, RELIEF_REGION, "png", buildFor(RELIEF_REGION))],
|
|
644
|
+
encoding: "terrarium",
|
|
645
|
+
tileSize: RELIEF_TILE_SIZE,
|
|
646
|
+
minzoom: RELIEF_MINZOOM,
|
|
647
|
+
maxzoom: RELIEF_MAXZOOM
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
return sources;
|
|
651
|
+
}
|
|
652
|
+
|
|
550
653
|
// src/endpoints/tiles.ts
|
|
551
654
|
var TilesEndpoint = class {
|
|
552
655
|
constructor(http, tileAuth, opts) {
|
|
@@ -618,13 +721,20 @@ var TilesEndpoint = class {
|
|
|
618
721
|
return await res.json();
|
|
619
722
|
}
|
|
620
723
|
/**
|
|
621
|
-
* Vector tile URL
|
|
622
|
-
*
|
|
724
|
+
* Vector tile URL for the configured region, served by the tile-worker at
|
|
725
|
+
* `tiles.chrt.co`. Tiles are JWT-gated — pair this with
|
|
623
726
|
* `chrt.transformRequest()`, which attaches the `Authorization: Bearer`
|
|
624
727
|
* header. Prefer `chrt.style()`, which wires the source for you.
|
|
728
|
+
*
|
|
729
|
+
* Carries the build discriminator (`?v=`) once the token cache is warm, so
|
|
730
|
+
* two buildTags never share one cache entry — see `BUILD_PARAM`. A cold cache
|
|
731
|
+
* yields the bare URL, which is equally valid: the server ignores the
|
|
732
|
+
* parameter, and warming is a `chrt.warmTiles()` (or one
|
|
733
|
+
* `chrt.transformRequest()` round-trip) away.
|
|
625
734
|
*/
|
|
626
735
|
tileUrl(z, x, y) {
|
|
627
|
-
|
|
736
|
+
const build = this.tileAuth.peekBuildTags(this.region)[this.region];
|
|
737
|
+
return tileTemplate(this.tileBaseUrl, this.region, "mvt", build).replace("{z}", String(z)).replace("{x}", String(x)).replace("{y}", String(y));
|
|
628
738
|
}
|
|
629
739
|
/**
|
|
630
740
|
* @deprecated Tilesets were consolidated to one PMTiles per region (2026-04);
|
|
@@ -850,6 +960,27 @@ var SearchEndpoint = class {
|
|
|
850
960
|
}
|
|
851
961
|
};
|
|
852
962
|
|
|
963
|
+
// src/endpoints/poi.ts
|
|
964
|
+
var PoiEndpoint = class {
|
|
965
|
+
constructor(http) {
|
|
966
|
+
this.http = http;
|
|
967
|
+
}
|
|
968
|
+
async list(options) {
|
|
969
|
+
return this.http.request(
|
|
970
|
+
"/poi",
|
|
971
|
+
{
|
|
972
|
+
bbox: options.bbox ? options.bbox.join(",") : void 0,
|
|
973
|
+
near: options.near ? `${options.near[0]},${options.near[1]}` : void 0,
|
|
974
|
+
radius_m: options.radiusM,
|
|
975
|
+
category: options.category,
|
|
976
|
+
limit: options.limit,
|
|
977
|
+
offset: options.offset
|
|
978
|
+
},
|
|
979
|
+
options.signal
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
};
|
|
983
|
+
|
|
853
984
|
// src/endpoints/query.ts
|
|
854
985
|
var METRES_PER_NAUTICAL_MILE2 = 1852;
|
|
855
986
|
var QueryEndpoint = class {
|
|
@@ -6370,6 +6501,38 @@ var S52_LOOKUP = {
|
|
|
6370
6501
|
// polygons that overlap the security-zone CTNARE polygons; with both
|
|
6371
6502
|
// as flood fills, the harbor inner waters disappeared under stacked
|
|
6372
6503
|
// magenta. Same boundary-only treatment as CTNARE.
|
|
6504
|
+
// ── DASHED, 2026-08-24. A solid line here read as a second coastline.
|
|
6505
|
+
//
|
|
6506
|
+
// User report: "narragansett bay has 2 offset coastlines... this is so
|
|
6507
|
+
// sloppy. we can't sell this right now." Measured by ablating classes
|
|
6508
|
+
// out of the shipping style and diffing offscreen renders of that exact
|
|
6509
|
+
// frame (z10.54, -71.1745/41.6031): dropping every magenta-painting
|
|
6510
|
+
// class changed 38,580 px and the doubled shoreline vanished; RESARE
|
|
6511
|
+
// ALONE was 37,613 of them — 97.5%. CBLARE, CTNARE, ACHARE and ADMARE
|
|
6512
|
+
// each changed zero.
|
|
6513
|
+
//
|
|
6514
|
+
// Restricted areas in Narragansett Bay follow the shore, so their
|
|
6515
|
+
// boundary runs parallel to the coastline a short distance off it. Drawn
|
|
6516
|
+
// SOLID, in a chart where the coastline is also a solid line, that is a
|
|
6517
|
+
// second coastline to any reader — the geometry is right and the
|
|
6518
|
+
// portrayal lies about what it is.
|
|
6519
|
+
//
|
|
6520
|
+
// This is not a SCAMIN problem, and the first fix attempted was wrong:
|
|
6521
|
+
// the tile pipeline already honours SCAMIN (the z10 tile carries 9
|
|
6522
|
+
// RESARE features, all SCAMIN 1:119,999-1:999,999 — NOAA's own
|
|
6523
|
+
// instruction to draw them at overview scale — while the z11 tile
|
|
6524
|
+
// carries 35). Gating harder would have hidden features the HO asks for.
|
|
6525
|
+
//
|
|
6526
|
+
// Dashed restores the convention this class was already believed to
|
|
6527
|
+
// follow: MIPARE [4,3], TESARE [6,4] and PRDARE [4,2] are all dashed
|
|
6528
|
+
// magenta boundaries, and PRDARE's own comment describes itself as
|
|
6529
|
+
// "matching RESARE/CTNARE/MIPARE/TESARE convention". RESARE was the lone
|
|
6530
|
+
// solid outlier. It also moves toward the S-52 line style for this
|
|
6531
|
+
// class, RESARE51 ("boundary of a restricted area"), a broken magenta
|
|
6532
|
+
// line with inward T-marks — which cannot be used directly yet: the
|
|
6533
|
+
// served sprite (s52-day, 101 entries) carries no RESARE51 pattern, and
|
|
6534
|
+
// a `line-pattern` naming a missing sprite renders NOTHING, which would
|
|
6535
|
+
// turn a portrayal complaint into invisible restricted areas.
|
|
6373
6536
|
objectClass: "RESARE",
|
|
6374
6537
|
name: "Restricted Area",
|
|
6375
6538
|
category: "Regulated Areas",
|
|
@@ -6380,7 +6543,8 @@ var S52_LOOKUP = {
|
|
|
6380
6543
|
paint: {
|
|
6381
6544
|
"line-color": "TRFCD",
|
|
6382
6545
|
"line-width": 1.2,
|
|
6383
|
-
"line-opacity": 0.7
|
|
6546
|
+
"line-opacity": 0.7,
|
|
6547
|
+
"line-dasharray": [4, 3]
|
|
6384
6548
|
}
|
|
6385
6549
|
},
|
|
6386
6550
|
viewingGroup: 26040,
|
|
@@ -6464,7 +6628,13 @@ var S52_LOOKUP = {
|
|
|
6464
6628
|
category: "Regulated Areas",
|
|
6465
6629
|
displayCategory: "OTHER",
|
|
6466
6630
|
int1Ref: "N23",
|
|
6467
|
-
|
|
6631
|
+
// 0.15 painted Hanapepe Bay's dumping ground as a solid land-green wash
|
|
6632
|
+
// over the white deep-water ground — owner-read as "land painting up to
|
|
6633
|
+
// the coastline" (2026-08-31). INT1 N23/N24 portrays a dumping ground
|
|
6634
|
+
// as a dashed limit with a label, not an area tint; 0.04 matches the
|
|
6635
|
+
// TSSLPT/TSSRON faintness so the area reads as WATER with a regulated
|
|
6636
|
+
// overlay, at every tier that shows it.
|
|
6637
|
+
style: { type: "fill", paint: { "fill-color": "LITYW", "fill-opacity": 0.04 } },
|
|
6468
6638
|
viewingGroup: 26230,
|
|
6469
6639
|
scamin: 22e3
|
|
6470
6640
|
},
|
|
@@ -7514,12 +7684,19 @@ var S52_LOOKUP = {
|
|
|
7514
7684
|
// Inherits RESARE (S-57 112) — boundary-only, no flood fill. The stacked
|
|
7515
7685
|
// magenta wash that motivated RESARE's treatment applies here too: an
|
|
7516
7686
|
// inland reach can carry overlapping regulated areas just as a harbour can.
|
|
7687
|
+
// ── DASHED, matching RESARE (2026-08-25). The inland twin was missed
|
|
7688
|
+
// when RESARE was dashed the day before, so USACE inland waters kept
|
|
7689
|
+
// drawing restricted areas as a SOLID magenta line running alongside
|
|
7690
|
+
// the bank — the same "second coastline" a user reported on the coastal
|
|
7691
|
+
// chart, still shipping on the inland one. Found by auditing every
|
|
7692
|
+
// lowercase variant against its NOAA parent: of 14, this was the only
|
|
7693
|
+
// pair that diverged.
|
|
7517
7694
|
objectClass: "resare",
|
|
7518
7695
|
name: "Restricted Area (Inland)",
|
|
7519
7696
|
category: "Regulated Areas",
|
|
7520
7697
|
displayCategory: "STANDARD",
|
|
7521
7698
|
int1Ref: "N2",
|
|
7522
|
-
style: { type: "line", paint: { "line-color": "TRFCD", "line-width": 1.2, "line-opacity": 0.7 } },
|
|
7699
|
+
style: { type: "line", paint: { "line-color": "TRFCD", "line-width": 1.2, "line-opacity": 0.7, "line-dasharray": [4, 3] } },
|
|
7523
7700
|
viewingGroup: 26010,
|
|
7524
7701
|
scamin: 9e4
|
|
7525
7702
|
},
|
|
@@ -22874,14 +23051,19 @@ var ORIENTATION_BAND_SORT_KEY = [
|
|
|
22874
23051
|
["slice", ["coalesce", ["get", "_cell_id"], "??9???"], 2, 3],
|
|
22875
23052
|
9
|
|
22876
23053
|
];
|
|
22877
|
-
function
|
|
23054
|
+
function orientationSortKey(tier) {
|
|
23055
|
+
const band = ["*", 100, ORIENTATION_BAND_SORT_KEY];
|
|
23056
|
+
if (!tier) return band;
|
|
23057
|
+
return ["+", band, tier];
|
|
23058
|
+
}
|
|
23059
|
+
function orientationLabelLayout(extras = {}, tier) {
|
|
22878
23060
|
return {
|
|
22879
23061
|
"text-anchor": "center",
|
|
22880
23062
|
"text-allow-overlap": false,
|
|
22881
23063
|
"text-ignore-placement": false,
|
|
22882
23064
|
"text-optional": true,
|
|
22883
23065
|
"text-padding": 15,
|
|
22884
|
-
"symbol-sort-key":
|
|
23066
|
+
"symbol-sort-key": orientationSortKey(tier),
|
|
22885
23067
|
...extras
|
|
22886
23068
|
};
|
|
22887
23069
|
}
|
|
@@ -22891,15 +23073,17 @@ function tieredSizeCurve(tierExpr, stops) {
|
|
|
22891
23073
|
if (i > 0) {
|
|
22892
23074
|
const [pz, pv] = stops[i - 1];
|
|
22893
23075
|
const [nz, nv] = stops[i];
|
|
22894
|
-
if (pv.some((v, t) => v === 0 && nv[t] > 0)) {
|
|
23076
|
+
if (pv.some((v, t) => v === 0 && nv[t] > 0 || v > 0 && nv[t] === 0)) {
|
|
22895
23077
|
const hz = Math.round((nz - 0.2) * 10) / 10;
|
|
22896
23078
|
if (hz > pz) {
|
|
22897
23079
|
const f = (hz - pz) / (nz - pz);
|
|
22898
23080
|
withHolds.push([
|
|
22899
23081
|
hz,
|
|
22900
|
-
pv.map(
|
|
22901
|
-
(v
|
|
22902
|
-
|
|
23082
|
+
pv.map((v, t) => {
|
|
23083
|
+
if (v === 0 && nv[t] > 0) return 0;
|
|
23084
|
+
if (v > 0 && nv[t] === 0) return v;
|
|
23085
|
+
return Math.round((v + (nv[t] - v) * f) * 10) / 10;
|
|
23086
|
+
})
|
|
22903
23087
|
]);
|
|
22904
23088
|
}
|
|
22905
23089
|
}
|
|
@@ -22928,6 +23112,19 @@ var ORIENTATION_HALO_WIDTH = [
|
|
|
22928
23112
|
16,
|
|
22929
23113
|
0.7
|
|
22930
23114
|
];
|
|
23115
|
+
var LAND_HALO_WIDTH = [
|
|
23116
|
+
"interpolate",
|
|
23117
|
+
["linear"],
|
|
23118
|
+
["zoom"],
|
|
23119
|
+
8,
|
|
23120
|
+
1.6,
|
|
23121
|
+
11,
|
|
23122
|
+
1.7,
|
|
23123
|
+
14,
|
|
23124
|
+
1.8,
|
|
23125
|
+
16,
|
|
23126
|
+
1.8
|
|
23127
|
+
];
|
|
22931
23128
|
var LABEL_BEHAVIOR = {
|
|
22932
23129
|
// Orientation labels — render once across all bands.
|
|
22933
23130
|
SEAARE: "orientation-cross-band",
|
|
@@ -23001,6 +23198,9 @@ var LABEL_PRIORITY = {
|
|
|
23001
23198
|
};
|
|
23002
23199
|
var DEFAULT_LABEL_PRIORITY = 450;
|
|
23003
23200
|
var ORIENTATION_LABEL_PRIORITY = 900;
|
|
23201
|
+
var MAJOR_WATER_TIER_MAX = 3;
|
|
23202
|
+
var MAJOR_WATER_LABEL_PRIORITY = 280;
|
|
23203
|
+
var ISLAND_LABEL_HOIST_MINZOOM = 12;
|
|
23004
23204
|
function getLabelPriority(layerName) {
|
|
23005
23205
|
if (getLabelBehavior(layerName) === "orientation-cross-band") {
|
|
23006
23206
|
return ORIENTATION_LABEL_PRIORITY;
|
|
@@ -23173,21 +23373,25 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23173
23373
|
// LNDRGN labels until coastal zoom. The Carto basemap already labels
|
|
23174
23374
|
// major land regions at z5-z6. Columns: t1 major region, t2 local
|
|
23175
23375
|
// point (also the default).
|
|
23376
|
+
// Tier 2 (local point: "Bird Island", "Cottage Park") gets the same
|
|
23377
|
+
// lift as LNDARE's small islands — it is the other half of "show me
|
|
23378
|
+
// what this land is". Tier 1 is unchanged.
|
|
23176
23379
|
textSize: tieredSizeCurve(tierExpr, [
|
|
23177
23380
|
[5, [0, 0]],
|
|
23178
23381
|
[7, [11, 0]],
|
|
23179
23382
|
[9, [12, 0]],
|
|
23180
|
-
[11, [12,
|
|
23181
|
-
[13, [11, 11]],
|
|
23182
|
-
[16, [10,
|
|
23383
|
+
[11, [12, 10]],
|
|
23384
|
+
[13, [11.5, 11.5]],
|
|
23385
|
+
[16, [10.5, 11.5]]
|
|
23183
23386
|
]),
|
|
23184
23387
|
textColor: DARK_BG,
|
|
23185
23388
|
textHaloColor: COLOR.LAND_REGION,
|
|
23186
|
-
|
|
23389
|
+
// Land halo, not the shared taper — see LAND_HALO_WIDTH.
|
|
23390
|
+
textHaloWidth: LAND_HALO_WIDTH,
|
|
23187
23391
|
textOffset: [0, 0],
|
|
23188
23392
|
textFont: ["Open Sans Regular"],
|
|
23189
23393
|
minzoom: 5,
|
|
23190
|
-
layout: orientationLabelLayout()
|
|
23394
|
+
layout: orientationLabelLayout({}, tierExpr)
|
|
23191
23395
|
};
|
|
23192
23396
|
}
|
|
23193
23397
|
case "LNDMRK":
|
|
@@ -23352,14 +23556,56 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23352
23556
|
// at approach scale (z9) when the user has zoomed in enough that
|
|
23353
23557
|
// individual banks are useful context. Columns: t1 ocean, t2 sea/gulf,
|
|
23354
23558
|
// t3 bay/sound, t4 anchorage, default.
|
|
23559
|
+
// ── The zoom ladder (owner, 2026-08-31) ────────────────────────────
|
|
23560
|
+
//
|
|
23561
|
+
// "When zoomed in I want water area labels (Nubble Channel etc.); as
|
|
23562
|
+
// I zoom out, the major stuff."
|
|
23563
|
+
//
|
|
23564
|
+
// The old curve gave EVERY tier a size at z9-z11, and the field that
|
|
23565
|
+
// produced is the defect. Rendered in isolation at Boston z10.4
|
|
23566
|
+
// (prod 48404b1d): ten copies of "North Atlantic Ocean" scattered
|
|
23567
|
+
// across the harbour, a dozen ledges and shoals — Finns, Boston,
|
|
23568
|
+
// Thieves, Ultonia, Harding, Tinkers, Dread, Newcomb, Three and
|
|
23569
|
+
// One-Half Fathom — and, competing with all of them for the same
|
|
23570
|
+
// pixels, the bays the user actually needs: Broad Sound, Dorchester,
|
|
23571
|
+
// Quincy, Hull, Hingham, President Roads. Nothing was missing. The
|
|
23572
|
+
// majors simply had no better claim on the space than a ledge did.
|
|
23573
|
+
//
|
|
23574
|
+
// So the ladder now steps tiers OUT as well as in:
|
|
23575
|
+
//
|
|
23576
|
+
// t1 ocean overview only — gone by z11. "North Atlantic
|
|
23577
|
+
// Ocean" inside Boston Harbor is noise, not context.
|
|
23578
|
+
// t2 sea/gulf through approach — gone by z13.
|
|
23579
|
+
// t3 bay/sound the spine: visible z5 through z16, peaking where
|
|
23580
|
+
// a bay IS the answer to "where am I".
|
|
23581
|
+
// t4 anchorage harbour detail, z13+ (unchanged).
|
|
23582
|
+
// t5 default channels, coves, ledges. Floor UNCHANGED at z9 —
|
|
23583
|
+
// and deliberately so. "Nubble Channel" is already
|
|
23584
|
+
// a z11 feature without touching this curve: NOAA
|
|
23585
|
+
// encodes it only in band-5 cells (US5BOSCD ×2,
|
|
23586
|
+
// US5BOSCE), band-5 features first exist in tiles at
|
|
23587
|
+
// z11, and the cell-band fan floors their label
|
|
23588
|
+
// variant at z11 too. The owner's "channels/coves at
|
|
23589
|
+
// z11+" is therefore satisfied by the DATA, and
|
|
23590
|
+
// moving this floor would only have cost the
|
|
23591
|
+
// offshore relief its approach-zoom entry (Georges
|
|
23592
|
+
// Bank, Hudson Canyon — the population this tier was
|
|
23593
|
+
// built for, pinned by seaare-tier.test.ts).
|
|
23594
|
+
//
|
|
23595
|
+
// What crowds the bays at z10 is fixed by RANK, not by floors:
|
|
23596
|
+
// `orientationSortKey` now places tier 3 ahead of tier 5 inside a
|
|
23597
|
+
// band, and the padding ladder below thins the field so that
|
|
23598
|
+
// ordering decides the survivors.
|
|
23599
|
+
//
|
|
23600
|
+
// Columns: t1 ocean, t2 sea/gulf, t3 bay/sound, t4 anchorage, default.
|
|
23355
23601
|
textSize: tieredSizeCurve(tierExpr, [
|
|
23356
23602
|
[3, [14, 8, 0, 0, 0]],
|
|
23357
23603
|
[5, [16, 13, 9, 0, 0]],
|
|
23358
23604
|
[7, [14, 14, 12, 0, 0]],
|
|
23359
|
-
[9, [
|
|
23360
|
-
[11, [
|
|
23361
|
-
[13, [
|
|
23362
|
-
[16, [
|
|
23605
|
+
[9, [11, 13, 14, 0, 9]],
|
|
23606
|
+
[11, [0, 11, 14, 0, 11]],
|
|
23607
|
+
[13, [0, 0, 13, 10, 11]],
|
|
23608
|
+
[16, [0, 0, 12, 11, 10]]
|
|
23363
23609
|
]),
|
|
23364
23610
|
textColor: DARK_BG,
|
|
23365
23611
|
textHaloColor: COLOR.WATER_DEEP,
|
|
@@ -23383,8 +23629,41 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23383
23629
|
// the tiles and replaces this fan outright — do not restore
|
|
23384
23630
|
// always-paint on a starvation report; flip `labels: "placename"`
|
|
23385
23631
|
// instead once the build carries the layer.
|
|
23386
|
-
|
|
23387
|
-
|
|
23632
|
+
//
|
|
23633
|
+
// ── The padding is the zoom LADDER (owner, 2026-08-31) ───────────
|
|
23634
|
+
//
|
|
23635
|
+
// "When zoomed in I want water area labels (Nubble Channel etc.);
|
|
23636
|
+
// as I zoom out, the major stuff."
|
|
23637
|
+
//
|
|
23638
|
+
// A flat 2 px moat gives every name in view the same claim on the
|
|
23639
|
+
// space, so at z10 all 34 band-4 names in one Boston tile compete
|
|
23640
|
+
// as peers and the result reads as clutter rather than as
|
|
23641
|
+
// orientation. Widening the moat as the user zooms OUT thins that
|
|
23642
|
+
// field by collision alone: with a 20 px moat only a few names
|
|
23643
|
+
// survive per screen, and `orientationSortKey` decides which —
|
|
23644
|
+
// lowest band first, then lowest class tier, i.e. the bays and
|
|
23645
|
+
// sounds. Narrowing it back to 2 px by z13 restores exactly
|
|
23646
|
+
// today's harbour behaviour, where the channels and coves should
|
|
23647
|
+
// all be readable.
|
|
23648
|
+
//
|
|
23649
|
+
// Same mechanism (and the same lesson) as the lake-label grid:
|
|
23650
|
+
// a flat padding let six copies of "Lake Ontario" crowd z8.9.
|
|
23651
|
+
// Nothing is filtered and no name is invented — the ladder is
|
|
23652
|
+
// entirely a collision-space decision.
|
|
23653
|
+
"text-padding": [
|
|
23654
|
+
"interpolate",
|
|
23655
|
+
["linear"],
|
|
23656
|
+
["zoom"],
|
|
23657
|
+
5,
|
|
23658
|
+
20,
|
|
23659
|
+
9,
|
|
23660
|
+
14,
|
|
23661
|
+
11,
|
|
23662
|
+
8,
|
|
23663
|
+
13,
|
|
23664
|
+
2
|
|
23665
|
+
]
|
|
23666
|
+
}, tierExpr)
|
|
23388
23667
|
};
|
|
23389
23668
|
}
|
|
23390
23669
|
case "LAKARE": {
|
|
@@ -23405,7 +23684,7 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23405
23684
|
textOffset: [0, 0],
|
|
23406
23685
|
textFont: ["Open Sans Italic"],
|
|
23407
23686
|
minzoom: 7,
|
|
23408
|
-
layout: orientationLabelLayout()
|
|
23687
|
+
layout: orientationLabelLayout({}, tierExpr)
|
|
23409
23688
|
};
|
|
23410
23689
|
}
|
|
23411
23690
|
case "RIVERS": {
|
|
@@ -23426,7 +23705,7 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23426
23705
|
textOffset: [0, 0],
|
|
23427
23706
|
textFont: ["Open Sans Italic"],
|
|
23428
23707
|
minzoom: 7,
|
|
23429
|
-
layout: orientationLabelLayout()
|
|
23708
|
+
layout: orientationLabelLayout({}, tierExpr)
|
|
23430
23709
|
};
|
|
23431
23710
|
}
|
|
23432
23711
|
case "CANALS":
|
|
@@ -23457,21 +23736,31 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23457
23736
|
return {
|
|
23458
23737
|
type: "symbol",
|
|
23459
23738
|
textField: ["get", "OBJNAM"],
|
|
23739
|
+
// Tier 2 is the SMALL island — the one the owner cannot identify.
|
|
23740
|
+
// Its old curve peaked at 10 px and fell to 10 px, i.e. it never got
|
|
23741
|
+
// its own scale: a name that only exists because you zoomed in was
|
|
23742
|
+
// rendered at the size used for ambient context. It now grows through
|
|
23743
|
+
// harbour zoom (10 → 11.5) while tier 1, the major island already
|
|
23744
|
+
// legible from coastal zoom, is left almost where it was. Small
|
|
23745
|
+
// absolute numbers on purpose: the ask was "clear", not "large" —
|
|
23746
|
+
// the legibility comes mostly from LAND_HALO_WIDTH and the opacity
|
|
23747
|
+
// scale, both of which cost no extra footprint.
|
|
23460
23748
|
textSize: tieredSizeCurve(tierExpr, [
|
|
23461
23749
|
[5, [8, 0]],
|
|
23462
23750
|
[7, [10, 0]],
|
|
23463
23751
|
[9, [11, 0]],
|
|
23464
|
-
[11, [11,
|
|
23465
|
-
[13, [
|
|
23466
|
-
[16, [
|
|
23752
|
+
[11, [11.5, 10]],
|
|
23753
|
+
[13, [11.5, 11.5]],
|
|
23754
|
+
[16, [10.5, 11.5]]
|
|
23467
23755
|
]),
|
|
23468
23756
|
textColor: DARK_BG,
|
|
23469
23757
|
textHaloColor: COLOR.LAND_NATURAL,
|
|
23470
|
-
|
|
23758
|
+
// Land halo, not the shared taper — see LAND_HALO_WIDTH.
|
|
23759
|
+
textHaloWidth: LAND_HALO_WIDTH,
|
|
23471
23760
|
textOffset: [0, 0],
|
|
23472
23761
|
textFont: ["Open Sans Regular"],
|
|
23473
23762
|
minzoom: 5,
|
|
23474
|
-
layout: orientationLabelLayout()
|
|
23763
|
+
layout: orientationLabelLayout({}, tierExpr)
|
|
23475
23764
|
};
|
|
23476
23765
|
}
|
|
23477
23766
|
case "BUAARE": {
|
|
@@ -23496,7 +23785,7 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23496
23785
|
minzoom: 3,
|
|
23497
23786
|
layout: orientationLabelLayout({
|
|
23498
23787
|
"text-letter-spacing": 0.05
|
|
23499
|
-
})
|
|
23788
|
+
}, tierExpr)
|
|
23500
23789
|
};
|
|
23501
23790
|
}
|
|
23502
23791
|
case "BRIDGE":
|
|
@@ -23588,7 +23877,7 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23588
23877
|
textOffset: [0, 0],
|
|
23589
23878
|
textFont: ["Open Sans Regular"],
|
|
23590
23879
|
minzoom: 7,
|
|
23591
|
-
layout: orientationLabelLayout()
|
|
23880
|
+
layout: orientationLabelLayout({}, tierExpr)
|
|
23592
23881
|
};
|
|
23593
23882
|
}
|
|
23594
23883
|
case "SBDARE":
|
|
@@ -25972,7 +26261,7 @@ var S52_POINT_SVGS = {
|
|
|
25972
26261
|
"LIGHTS14": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">\n <g data-chrt-scale="0.8438" transform="translate(16 16) scale(0.8438) translate(-16 -16)">\n <path d="M 16.3 28.0 L 11.8 7.1 L 11.8 6.3 L 11.8 4.9 L 12.4 3.5 L 13.2 3.0 L 14.3 2.1 L 16.0 1.9 L 17.4 2.1 L 18.5 2.4 L 19.6 3.2 L 20.2 4.6 L 20.4 5.7 L 20.4 6.9 L 16.3 28.0 Z" fill="CHMGD" />\n <path d="M 16.3 28.0 L 11.8 7.1 L 11.8 6.3 L 11.8 4.9 L 12.4 3.5 L 13.2 3.0 L 14.3 2.1 L 16.0 1.9 L 17.4 2.1 L 18.5 2.4 L 19.6 3.2 L 20.2 4.6 L 20.4 5.7 L 20.4 6.9 L 16.3 28.0" fill="none" stroke="CHMGD" stroke-width="1.185" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 21.3 5.2 L 21.0 6.6 L 16.3 31.0" fill="none" stroke="CHBLK" stroke-width="1.185" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 16.3 28.0 L 11.5 6.6 L 11.8 5.2 L 12.1 4.1 L 12.9 3.2 L 14.1 2.1 L 15.4 1.9 L 16.3 1.9 L 17.4 2.1 L 18.8 2.7 L 19.6 3.5 L 20.2 4.6 L 20.4 5.7 L 20.2 7.7 L 16.3 28.0" fill="none" stroke="CHMGD" stroke-width="1.185" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 16.3 31.0 L 10.7 6.0 L 11.5 3.5 L 13.8 1.6 L 16.3 1.0 L 18.5 1.9 L 19.9 2.7 L 21.0 4.9" fill="none" stroke="CHBLK" stroke-width="1.185" stroke-linecap="butt" stroke-linejoin="round" />\n </g>\n</svg>\n',
|
|
25973
26262
|
"LIGHTS81": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">\n <g data-chrt-scale="0.3896" transform="translate(16 16) scale(0.3896) translate(-16 -16)">\n <path d="M 1.0 25.9 L 6.9 5.9 L 12.9 26.0 L 19.0 5.9 L 25.0 26.1 L 31.0 5.9" fill="none" stroke="CHMGD" stroke-width="2.567" stroke-linecap="butt" stroke-linejoin="round" />\n </g>\n</svg>\n',
|
|
25974
26263
|
"LIGHTS82": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">\n <g data-chrt-scale="0.6083" transform="translate(16 16) scale(0.6083) translate(-16 -15.98)">\n <circle cx="6.96" cy="17.80" r="5.96" fill="none" stroke="CHMGD" stroke-width="3.288" />\n <path d="M 14.9 23.1 L 25.9 23.5" fill="none" stroke="CHMGD" stroke-width="3.288" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 13.3 12.1 L 28.0 8.2" fill="none" stroke="CHMGD" stroke-width="3.288" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 15.8 17.5 L 31.0 16.0" fill="none" stroke="CHMGD" stroke-width="3.288" stroke-linecap="butt" stroke-linejoin="round" />\n </g>\n</svg>\n',
|
|
25975
|
-
"LITDEF11": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">\n <g data-chrt-scale="0.7292" transform="translate(16 16) scale(0.7292) translate(-16 -
|
|
26264
|
+
"LITDEF11": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">\n <g data-chrt-scale="0.7292" transform="translate(16 16) scale(0.7292) translate(-16.1 -14.95)">\n <g transform="translate(10 10) rotate(135) scale(0.9) translate(-16.3 -31)">\n <path d="M 16.3 31.0 L 10.9 6.9 L 10.9 5.8 L 11.2 4.2 L 12.0 2.8 L 12.8 2.1 L 14.1 1.0 L 16.0 1.0 L 17.6 1.0 L 18.7 1.3 L 20.3 2.3 L 20.8 3.9 L 21.1 5.3 L 21.1 6.6 L 16.3 31.0 Z" fill="CHMGD" />\n <path d="M 16.3 31.0 L 10.9 6.9 L 10.9 5.8 L 11.2 4.2 L 12.0 2.8 L 12.8 2.1 L 14.1 1.0 L 16.0 1.0 L 17.6 1.0 L 18.7 1.3 L 20.3 2.3 L 20.8 3.9 L 21.1 5.3 L 21.1 6.6 L 16.3 31.0" fill="none" stroke="CHMGD" stroke-width="1.371" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 16.3 31.0 L 10.9 6.4 L 10.9 4.7 L 11.4 3.1 L 12.3 2.3 L 13.6 1.3 L 15.2 1.0 L 16.3 1.0 L 17.6 1.0 L 18.9 1.5 L 20.0 2.6 L 20.8 4.2 L 21.1 5.3 L 20.8 7.7 L 16.3 31.0" fill="none" stroke="CHMGD" stroke-width="1.371" stroke-linecap="butt" stroke-linejoin="round" />\n </g>\n </g>\n</svg>\n',
|
|
25976
26265
|
"LITFLT01": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">\n <g data-chrt-scale="0.9375" transform="translate(16 16) scale(0.9375) translate(-16 -15.985)">\n <circle cx="16.00" cy="18.97" r="2.40" fill="none" stroke="CHBLK" stroke-width="2.133" />\n <path d="M 1.0 19.0 L 12.7 19.0" fill="none" stroke="CHBLK" stroke-width="2.133" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 6.0 19.0 L 2.7 12.3" fill="none" stroke="CHBLK" stroke-width="2.133" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 26.0 19.0 L 29.3 12.3" fill="none" stroke="CHBLK" stroke-width="2.133" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 11.0 14.0 L 12.7 10.6 L 19.3 10.6 L 21.0 14.0" fill="none" stroke="CHBLK" stroke-width="2.133" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 2.7 12.3 L 9.3 14.0 L 22.7 14.0 L 29.3 12.3" fill="none" stroke="CHBLK" stroke-width="2.133" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 19.3 19.0 L 31.0 19.0" fill="none" stroke="CHBLK" stroke-width="2.133" stroke-linecap="butt" stroke-linejoin="round" />\n </g>\n</svg>\n',
|
|
25977
26266
|
"LITFLT02": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">\n <g data-chrt-scale="0.6385" transform="translate(16 16) scale(0.6385) translate(-16 -16)">\n <path d="M 1.0 13.0 L 5.6 21.5 L 26.9 21.5 L 31.0 12.9 Z" fill="CHBLK" />\n <path d="M 1.0 13.0 L 5.6 21.5 L 26.9 21.5 L 31.0 12.9" fill="none" stroke="CHBLK" stroke-width="1.566" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 1.0 12.9 L 5.6 21.5 L 26.9 21.5 L 30.9 12.9 L 1.0 12.9 Z" fill="CHBLK" />\n <path d="M 1.0 12.9 L 5.6 21.5 L 26.9 21.5 L 30.9 12.9 L 1.0 12.9" fill="none" stroke="CHBLK" stroke-width="1.566" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 10.9 12.9 L 10.9 10.5 L 21.2 10.5 L 21.2 12.9" fill="none" stroke="CHBLK" stroke-width="1.566" stroke-linecap="butt" stroke-linejoin="round" />\n </g>\n</svg>\n',
|
|
25978
26267
|
"LITVES01": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">\n <g data-chrt-scale="0.9375" transform="translate(16 16) scale(0.9375) translate(-16 -15.98)">\n <circle cx="16.00" cy="24.83" r="2.33" fill="none" stroke="CHBLK" stroke-width="2.133" />\n <path d="M 1.0 24.8 L 12.7 24.8" fill="none" stroke="CHBLK" stroke-width="2.133" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 6.0 24.8 L 2.7 16.5" fill="none" stroke="CHBLK" stroke-width="2.133" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 26.0 24.8 L 29.3 16.5" fill="none" stroke="CHBLK" stroke-width="2.133" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 16.0 19.0 L 16.0 9.8" fill="none" stroke="CHBLK" stroke-width="2.133" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 9.3 19.0 L 9.3 13.0" fill="none" stroke="CHBLK" stroke-width="2.133" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 22.7 19.0 L 22.7 13.2" fill="none" stroke="CHBLK" stroke-width="2.133" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 16.0 9.8 L 16.0 4.8" fill="none" stroke="CHBLK" stroke-width="2.133" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 12.7 6.5 L 19.3 9.8" fill="none" stroke="CHBLK" stroke-width="2.133" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 12.7 9.8 L 19.3 6.5" fill="none" stroke="CHBLK" stroke-width="2.133" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 4.7 21.5 L 10.2 21.5" fill="none" stroke="CHBLK" stroke-width="2.133" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 21.8 21.5 L 27.3 21.5" fill="none" stroke="CHBLK" stroke-width="2.133" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 2.7 16.5 L 9.2 19.0 L 16.0 19.0 L 22.7 19.0 L 29.3 16.5" fill="none" stroke="CHBLK" stroke-width="2.133" stroke-linecap="butt" stroke-linejoin="round" />\n <path d="M 19.3 24.8 L 31.0 24.8" fill="none" stroke="CHBLK" stroke-width="2.133" stroke-linecap="butt" stroke-linejoin="round" />\n </g>\n</svg>\n',
|
|
@@ -26487,6 +26776,8 @@ var CHRT_GLYPH_SVGS = {
|
|
|
26487
26776
|
"cardinal-w": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M16 6 12 1.5h8Z" fill="#101418"/><path d="M16 6.5 20 11h-8Z" fill="#101418"/><path d="M13 12h6l1 13H12Z" fill="#FFC805"/><path d="M6 28h20" stroke="#101418" stroke-width="2.4" stroke-linecap="round"/></svg>',
|
|
26488
26777
|
"daymark-square": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><rect x="6" y="6" width="20" height="20" rx="1.5" fill="#fff"/></svg>',
|
|
26489
26778
|
"daymark-triangle": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M16 5 28 26H4Z" fill="#fff"/></svg>',
|
|
26779
|
+
"hatch-nocoverage": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M-3 9 L9 -3 M-3 21 L21 -3 M3 27 L27 3 M15 27 L27 15" stroke="#8A97A0" stroke-width="0.8" fill="none"/><path d="M-3 15 L15 33 M-3 3 L21 27 M3 -3 L27 21 M15 -3 L27 9" stroke="#8A97A0" stroke-width="0.8" fill="none"/></svg>',
|
|
26780
|
+
"hatch-uncharted": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M-3 9 L9 -3 M-3 21 L21 -3 M3 27 L27 3 M15 27 L27 15" stroke="#5B7383" stroke-width="0.65" fill="none"/></svg>',
|
|
26490
26781
|
"isolated-danger": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><circle cx="16" cy="3.5" r="2.4" fill="#101418"/><circle cx="16" cy="9.5" r="2.4" fill="#101418"/><path d="M13 13h6l1 12H12Z" fill="#101418"/><rect x="12.4" y="17.5" width="7.2" height="3.4" fill="#E5484D"/><path d="M6 28h20" stroke="#101418" stroke-width="2.4" stroke-linecap="round"/></svg>',
|
|
26491
26782
|
"light-flare": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><circle cx="11" cy="11" r="3.2" fill="#fff"/><path d="M13.4 13.4 24 19.5a2.6 2.6 0 0 1-4.5 4.5L13.4 13.4Z" fill="#fff"/></svg>',
|
|
26492
26783
|
"lightship": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M6 20l3-5h14l3 5-3.5 4h-13Z" fill="#fff"/><path d="M16 15V8" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/><circle cx="16" cy="6" r="2.2" fill="#fff"/><path d="M5 28h22" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/></svg>',
|
|
@@ -26935,59 +27226,6 @@ function iconExpressionForFamily(objectClass, family) {
|
|
|
26935
27226
|
}
|
|
26936
27227
|
}
|
|
26937
27228
|
|
|
26938
|
-
// src/atlas/tile-zoom.ts
|
|
26939
|
-
var BUILD_MINZOOM = 0;
|
|
26940
|
-
var BUILD_MAXZOOM = 14;
|
|
26941
|
-
|
|
26942
|
-
// src/atlas/source-registry.ts
|
|
26943
|
-
var LAND_REGION = "CHRT_LAND";
|
|
26944
|
-
var RELIEF_REGION = "CHRT_RELIEF";
|
|
26945
|
-
var RELIEF_MINZOOM = 8;
|
|
26946
|
-
var RELIEF_MAXZOOM = 13;
|
|
26947
|
-
var RELIEF_TILE_SIZE = 512;
|
|
26948
|
-
var DEFAULT_TILE_BASE_URL2 = "https://tiles.chrt.co";
|
|
26949
|
-
var DEFAULT_REGION = "NOAA_ENC";
|
|
26950
|
-
function buildSources(opts = {}) {
|
|
26951
|
-
const mode = opts.mode ?? "pmtiles";
|
|
26952
|
-
const region = opts.region ?? DEFAULT_REGION;
|
|
26953
|
-
if (mode === "mapbox") {
|
|
26954
|
-
return {
|
|
26955
|
-
[region]: {
|
|
26956
|
-
type: "vector",
|
|
26957
|
-
url: `mapbox://chrt.${region.toLowerCase()}`
|
|
26958
|
-
}
|
|
26959
|
-
};
|
|
26960
|
-
}
|
|
26961
|
-
const base = (opts.tileBaseUrl ?? DEFAULT_TILE_BASE_URL2).replace(/\/+$/, "");
|
|
26962
|
-
const sources = {
|
|
26963
|
-
[region]: {
|
|
26964
|
-
type: "vector",
|
|
26965
|
-
tiles: [`${base}/v1/tiles/${region}/{z}/{x}/{y}.mvt`],
|
|
26966
|
-
minzoom: BUILD_MINZOOM,
|
|
26967
|
-
maxzoom: BUILD_MAXZOOM
|
|
26968
|
-
}
|
|
26969
|
-
};
|
|
26970
|
-
if (opts.land) {
|
|
26971
|
-
sources[LAND_REGION] = {
|
|
26972
|
-
type: "vector",
|
|
26973
|
-
tiles: [`${base}/v1/tiles/${LAND_REGION}/{z}/{x}/{y}.mvt`],
|
|
26974
|
-
minzoom: BUILD_MINZOOM,
|
|
26975
|
-
maxzoom: BUILD_MAXZOOM
|
|
26976
|
-
};
|
|
26977
|
-
}
|
|
26978
|
-
if (opts.relief) {
|
|
26979
|
-
sources[RELIEF_REGION] = {
|
|
26980
|
-
type: "raster-dem",
|
|
26981
|
-
tiles: [`${base}/v1/tiles/${RELIEF_REGION}/{z}/{x}/{y}.png`],
|
|
26982
|
-
encoding: "terrarium",
|
|
26983
|
-
tileSize: RELIEF_TILE_SIZE,
|
|
26984
|
-
minzoom: RELIEF_MINZOOM,
|
|
26985
|
-
maxzoom: RELIEF_MAXZOOM
|
|
26986
|
-
};
|
|
26987
|
-
}
|
|
26988
|
-
return sources;
|
|
26989
|
-
}
|
|
26990
|
-
|
|
26991
27229
|
// src/atlas/layer-builder.ts
|
|
26992
27230
|
var SCAMIN_DECLUTTER_CLASSES = /* @__PURE__ */ new Set([
|
|
26993
27231
|
"OFSPLF",
|
|
@@ -27315,7 +27553,27 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
|
|
|
27315
27553
|
layer.layout = {
|
|
27316
27554
|
...layer.layout,
|
|
27317
27555
|
"icon-allow-overlap": true,
|
|
27318
|
-
"icon-ignore-placement": true
|
|
27556
|
+
"icon-ignore-placement": true,
|
|
27557
|
+
// Flare-specific size (owner ask 2026-08-31: "make the flares a
|
|
27558
|
+
// little smaller — they should scale down a lot as you zoom out").
|
|
27559
|
+
// The safety-critical curve serves buoy/beacon HULLS, whose set
|
|
27560
|
+
// this overrides for LIGHTS only: flares are annotations on a
|
|
27561
|
+
// point, not shapes a mariner ranges against, so they start small
|
|
27562
|
+
// at planning scales and only approach full size at berthing
|
|
27563
|
+
// zooms. Applied after the safety-critical spread so it wins.
|
|
27564
|
+
"icon-size": [
|
|
27565
|
+
"interpolate",
|
|
27566
|
+
["linear"],
|
|
27567
|
+
["zoom"],
|
|
27568
|
+
6,
|
|
27569
|
+
0.3,
|
|
27570
|
+
12,
|
|
27571
|
+
0.42,
|
|
27572
|
+
14,
|
|
27573
|
+
0.6,
|
|
27574
|
+
18,
|
|
27575
|
+
1
|
|
27576
|
+
]
|
|
27319
27577
|
};
|
|
27320
27578
|
const valnmrNum = ["to-number", ["coalesce", ["get", "VALNMR"], ["get", "valnmr"], 0], 0];
|
|
27321
27579
|
const isMajor = [">=", valnmrNum, 10];
|
|
@@ -28280,85 +28538,265 @@ function getOrderedClasses() {
|
|
|
28280
28538
|
return ordered;
|
|
28281
28539
|
}
|
|
28282
28540
|
|
|
28283
|
-
// src/atlas/
|
|
28284
|
-
var
|
|
28285
|
-
|
|
28286
|
-
|
|
28287
|
-
|
|
28288
|
-
}
|
|
28289
|
-
|
|
28290
|
-
|
|
28291
|
-
base: "#e8d9b0",
|
|
28292
|
-
cover: { wood: "#cfd8b4", wetland: "#c9d3c4", urban: "#dedad2", sand: "#efe4c2" },
|
|
28293
|
-
contour: "#a89878",
|
|
28294
|
-
contourIndex: "#8a7a5c",
|
|
28295
|
-
road: "#d9c9a4",
|
|
28296
|
-
roadMajor: "#d2bd8e",
|
|
28297
|
-
label: "#5b4f3a",
|
|
28298
|
-
halo: "#f2ead6",
|
|
28299
|
-
shadow: "#6b5a3e",
|
|
28300
|
-
highlight: "#fffaf0",
|
|
28301
|
-
fade: "#e8f2f8",
|
|
28302
|
-
// Raised from 0.45 on 2026-08-23. ⚠ Above the ~0.5 that
|
|
28303
|
-
// hillshade-style.mjs warns "starts competing with depth areas" — a
|
|
28304
|
-
// deliberate, sighted choice made against rendered evidence, not a
|
|
28305
|
-
// default. If soundings or DEPARE ever read as fighting the terrain,
|
|
28306
|
-
// this number is the first thing to walk back.
|
|
28307
|
-
exaggeration: 0.8
|
|
28308
|
-
},
|
|
28309
|
-
DUSK: {
|
|
28310
|
-
base: "#4a4433",
|
|
28311
|
-
cover: { wood: "#414a38", wetland: "#3f4740", urban: "#4e4a42", sand: "#544c39" },
|
|
28312
|
-
contour: "#6b6250",
|
|
28313
|
-
contourIndex: "#837961",
|
|
28314
|
-
road: "#565040",
|
|
28315
|
-
roadMajor: "#63594440",
|
|
28316
|
-
label: "#c3b898",
|
|
28317
|
-
halo: "#2b2820",
|
|
28318
|
-
shadow: "#2e2a20",
|
|
28319
|
-
highlight: "#8a8068",
|
|
28320
|
-
fade: "#16202a",
|
|
28321
|
-
// Unchanged. DUSK was not the palette the 0.8 decision was made against.
|
|
28322
|
-
exaggeration: 0.45
|
|
28323
|
-
},
|
|
28324
|
-
NIGHT: {
|
|
28325
|
-
base: "#2a2620",
|
|
28326
|
-
cover: { wood: "#242a20", wetland: "#232823", urban: "#2c2925", sand: "#302b22" },
|
|
28327
|
-
contour: "#413b30",
|
|
28328
|
-
contourIndex: "#524a3b",
|
|
28329
|
-
road: "#332f27",
|
|
28330
|
-
roadMajor: "#3d382d",
|
|
28331
|
-
label: "#7d745e",
|
|
28332
|
-
halo: "#15130f",
|
|
28333
|
-
shadow: "#1a1712",
|
|
28334
|
-
highlight: "#4a4436",
|
|
28335
|
-
fade: "#07090b",
|
|
28336
|
-
// ⚠ DELIBERATELY NOT RAISED. See the type's note: a strong hillshade
|
|
28337
|
-
// under red night vision is a grey haze over exactly the contrast a
|
|
28338
|
-
// skipper is navigating by. Left at the shipped value rather than
|
|
28339
|
-
// lowered, because lowering it is a separate decision from this one.
|
|
28340
|
-
exaggeration: 0.45
|
|
28341
|
-
}
|
|
28541
|
+
// src/atlas/band-priority.ts
|
|
28542
|
+
var BAND_PRIORITY_RANGES = {
|
|
28543
|
+
"1": { minzoom: 0 },
|
|
28544
|
+
"2": { minzoom: 5 },
|
|
28545
|
+
"3": { minzoom: 7 },
|
|
28546
|
+
"4": { minzoom: 9 },
|
|
28547
|
+
"5": { minzoom: 11 },
|
|
28548
|
+
"6": { minzoom: 11 }
|
|
28342
28549
|
};
|
|
28343
|
-
var
|
|
28344
|
-
|
|
28345
|
-
|
|
28346
|
-
"
|
|
28347
|
-
|
|
28348
|
-
"
|
|
28349
|
-
|
|
28350
|
-
|
|
28351
|
-
|
|
28352
|
-
|
|
28353
|
-
|
|
28354
|
-
|
|
28355
|
-
|
|
28356
|
-
|
|
28357
|
-
|
|
28358
|
-
|
|
28359
|
-
|
|
28360
|
-
|
|
28361
|
-
|
|
28550
|
+
var BAND_DATA_MINZOOM = {
|
|
28551
|
+
"1": 0,
|
|
28552
|
+
"2": 5,
|
|
28553
|
+
"3": 7,
|
|
28554
|
+
"4": 9,
|
|
28555
|
+
"5": 11,
|
|
28556
|
+
"6": 13
|
|
28557
|
+
};
|
|
28558
|
+
function dominatedVariantMaxzoom(D) {
|
|
28559
|
+
return Math.max(BAND_PRIORITY_RANGES[D].minzoom, BAND_DATA_MINZOOM[D]);
|
|
28560
|
+
}
|
|
28561
|
+
var DOMINANCE_EXEMPT_FILL_CLASSES = /* @__PURE__ */ new Set([
|
|
28562
|
+
"LNDARE",
|
|
28563
|
+
"DEPARE",
|
|
28564
|
+
"UNSARE",
|
|
28565
|
+
"LAKARE",
|
|
28566
|
+
"DRGARE",
|
|
28567
|
+
"SEAARE"
|
|
28568
|
+
]);
|
|
28569
|
+
function isExemptOpaqueFill(layer) {
|
|
28570
|
+
if (layer.type !== "fill") return false;
|
|
28571
|
+
for (const cls of DOMINANCE_EXEMPT_FILL_CLASSES) {
|
|
28572
|
+
if (layer.id.startsWith(`s52-${cls}-`)) return true;
|
|
28573
|
+
}
|
|
28574
|
+
return false;
|
|
28575
|
+
}
|
|
28576
|
+
var FILL_FLOOR_FREE_BANDS = /* @__PURE__ */ new Set([
|
|
28577
|
+
"1",
|
|
28578
|
+
"2",
|
|
28579
|
+
"3"
|
|
28580
|
+
]);
|
|
28581
|
+
function bandVariantMinzoom(layer, B) {
|
|
28582
|
+
const classMinzoom = layer.minzoom ?? 0;
|
|
28583
|
+
if (isExemptOpaqueFill(layer) && FILL_FLOOR_FREE_BANDS.has(B)) {
|
|
28584
|
+
return classMinzoom;
|
|
28585
|
+
}
|
|
28586
|
+
return Math.max(classMinzoom, BAND_PRIORITY_RANGES[B].minzoom);
|
|
28587
|
+
}
|
|
28588
|
+
var GENERALISED_AREA_FILL_CLASSES = /* @__PURE__ */ new Set([
|
|
28589
|
+
"BUAARE",
|
|
28590
|
+
// 2026-08-28, New York Harbor: the "New York Marine Inspection Zone and
|
|
28591
|
+
// Captain of the Port Zone" RESARE exists at bands 2/3 as a generalized
|
|
28592
|
+
// shore-hugging polygon whose point-sampled dominance tag MISSED (the
|
|
28593
|
+
// Providence-BUAARE failure mode), so its magenta dashed boundary drew
|
|
28594
|
+
// OFFSET over the band-5 detail at z11.5 — read in the field as "a
|
|
28595
|
+
// magenta shoreline bleeding down from a less coarse band". The same
|
|
28596
|
+
// tile carried untagged band-2 ADMARE and CTNARE giants in the same
|
|
28597
|
+
// state. All three are area classes whose coarse-band boundaries hug
|
|
28598
|
+
// shorelines and whose fine-band copies are authoritative.
|
|
28599
|
+
"RESARE",
|
|
28600
|
+
"CTNARE",
|
|
28601
|
+
"ADMARE"
|
|
28602
|
+
]);
|
|
28603
|
+
var GENERALISED_AREA_MAX_BAND = 3;
|
|
28604
|
+
function generalisedAreaCeiling(layer, B) {
|
|
28605
|
+
if (layer.type !== "fill" && layer.type !== "line") return null;
|
|
28606
|
+
if (parseInt(B, 10) > GENERALISED_AREA_MAX_BAND) return null;
|
|
28607
|
+
for (const cls of GENERALISED_AREA_FILL_CLASSES) {
|
|
28608
|
+
if (layer.id.startsWith(`s52-${cls}-`)) return BAND_PRIORITY_RANGES["5"].minzoom;
|
|
28609
|
+
}
|
|
28610
|
+
return null;
|
|
28611
|
+
}
|
|
28612
|
+
var BAND_DRAW_ORDER = ["1", "2", "3", "4", "5", "6"];
|
|
28613
|
+
var CHART_BAND_CODES = {
|
|
28614
|
+
Overview: ["1"],
|
|
28615
|
+
General: ["2"],
|
|
28616
|
+
Coastal: ["3"],
|
|
28617
|
+
Approach: ["4"],
|
|
28618
|
+
Harbor: ["5", "6"]
|
|
28619
|
+
};
|
|
28620
|
+
var CHART_CASCADE_CODES = {
|
|
28621
|
+
Overview: ["1"],
|
|
28622
|
+
General: ["1", "2"],
|
|
28623
|
+
Coastal: ["1", "2", "3"],
|
|
28624
|
+
Approach: ["1", "2", "3", "4"],
|
|
28625
|
+
Harbor: ["1", "2", "3", "4", "5", "6"]
|
|
28626
|
+
};
|
|
28627
|
+
var CHRT_STYLE_MODES = [
|
|
28628
|
+
"continuous",
|
|
28629
|
+
...Object.keys(CHART_BAND_CODES).map(
|
|
28630
|
+
(band) => `chart:${band}`
|
|
28631
|
+
)
|
|
28632
|
+
];
|
|
28633
|
+
function normalizeStyleMode(raw) {
|
|
28634
|
+
if (raw === null || raw === void 0 || raw === "") return "continuous";
|
|
28635
|
+
const lower = raw.toLowerCase();
|
|
28636
|
+
return CHRT_STYLE_MODES.find((mode) => mode.toLowerCase() === lower) ?? null;
|
|
28637
|
+
}
|
|
28638
|
+
function styleModeBand(mode) {
|
|
28639
|
+
return mode === "continuous" ? null : mode.slice("chart:".length);
|
|
28640
|
+
}
|
|
28641
|
+
function bandFilterExpression(codes) {
|
|
28642
|
+
const list = Array.isArray(codes) ? codes : [codes];
|
|
28643
|
+
if (list.length === 1) {
|
|
28644
|
+
return ["==", ["slice", ["get", "_cell_id"], 2, 3], list[0]];
|
|
28645
|
+
}
|
|
28646
|
+
return ["in", ["slice", ["get", "_cell_id"], 2, 3], ["literal", [...list]]];
|
|
28647
|
+
}
|
|
28648
|
+
function combineWithBandFilter(baseFilter, bandFilter) {
|
|
28649
|
+
if (!baseFilter) return bandFilter;
|
|
28650
|
+
if (baseFilter[0] === "all") {
|
|
28651
|
+
return ["all", ...baseFilter.slice(1), bandFilter];
|
|
28652
|
+
}
|
|
28653
|
+
return ["all", baseFilter, bandFilter];
|
|
28654
|
+
}
|
|
28655
|
+
var NOT_DOMINATED_FILTER = [
|
|
28656
|
+
"all",
|
|
28657
|
+
["!", ["has", "_dominated_by_min"]],
|
|
28658
|
+
["!=", ["get", "_dominated"], true]
|
|
28659
|
+
];
|
|
28660
|
+
function dominatedByMinFilter(D) {
|
|
28661
|
+
const dInt = parseInt(D, 10);
|
|
28662
|
+
if (D === "6") {
|
|
28663
|
+
return [
|
|
28664
|
+
"any",
|
|
28665
|
+
["==", ["get", "_dominated_by_min"], dInt],
|
|
28666
|
+
["==", ["get", "_dominated"], true]
|
|
28667
|
+
];
|
|
28668
|
+
}
|
|
28669
|
+
return ["==", ["get", "_dominated_by_min"], dInt];
|
|
28670
|
+
}
|
|
28671
|
+
function withinBboxFilter(bbox) {
|
|
28672
|
+
const [w, s] = bbox.sw;
|
|
28673
|
+
const [e, n] = bbox.ne;
|
|
28674
|
+
return [
|
|
28675
|
+
"within",
|
|
28676
|
+
{
|
|
28677
|
+
type: "Polygon",
|
|
28678
|
+
coordinates: [
|
|
28679
|
+
[
|
|
28680
|
+
[w, s],
|
|
28681
|
+
[e, s],
|
|
28682
|
+
[e, n],
|
|
28683
|
+
[w, n],
|
|
28684
|
+
[w, s]
|
|
28685
|
+
]
|
|
28686
|
+
]
|
|
28687
|
+
}
|
|
28688
|
+
];
|
|
28689
|
+
}
|
|
28690
|
+
function worldMinusBboxPolygon(bbox) {
|
|
28691
|
+
const [w, s] = bbox.sw;
|
|
28692
|
+
const [e, n] = bbox.ne;
|
|
28693
|
+
return {
|
|
28694
|
+
type: "Polygon",
|
|
28695
|
+
coordinates: [
|
|
28696
|
+
[
|
|
28697
|
+
[-180, -85],
|
|
28698
|
+
[180, -85],
|
|
28699
|
+
[180, 85],
|
|
28700
|
+
[-180, 85],
|
|
28701
|
+
[-180, -85]
|
|
28702
|
+
],
|
|
28703
|
+
// Inner ring (hole) — winding opposite to outer ring per GeoJSON
|
|
28704
|
+
// spec. MapLibre is permissive but we follow strict winding.
|
|
28705
|
+
[
|
|
28706
|
+
[w, s],
|
|
28707
|
+
[w, n],
|
|
28708
|
+
[e, n],
|
|
28709
|
+
[e, s],
|
|
28710
|
+
[w, s]
|
|
28711
|
+
]
|
|
28712
|
+
]
|
|
28713
|
+
};
|
|
28714
|
+
}
|
|
28715
|
+
|
|
28716
|
+
// src/atlas/land-layers.ts
|
|
28717
|
+
var LAND_SURFACE_MINZOOM = BAND_PRIORITY_RANGES["5"].minzoom;
|
|
28718
|
+
var LAND_SURFACE_LAYERS = (id) => id === "chrtland-base" || id === "chrtland-cover" || id === "chrtland-contour" || id === "chrtland-hillshade" || id.startsWith("chrtland-fade-");
|
|
28719
|
+
var FADE_PX = 80;
|
|
28720
|
+
var FADE_BANDS = 16;
|
|
28721
|
+
function fadeAlpha(r) {
|
|
28722
|
+
return Math.max(0, Math.min(1, (FADE_PX / 2 - Math.abs(r)) / FADE_PX));
|
|
28723
|
+
}
|
|
28724
|
+
function landBaseColor(palette) {
|
|
28725
|
+
return (LAND_THEMES[palette] ?? LAND_THEMES.DAY).base;
|
|
28726
|
+
}
|
|
28727
|
+
var LAND_THEMES = {
|
|
28728
|
+
DAY: {
|
|
28729
|
+
base: "#e8d9b0",
|
|
28730
|
+
cover: { wood: "#cfd8b4", wetland: "#c9d3c4", urban: "#dedad2", sand: "#efe4c2" },
|
|
28731
|
+
contour: "#a89878",
|
|
28732
|
+
contourIndex: "#8a7a5c",
|
|
28733
|
+
road: "#d9c9a4",
|
|
28734
|
+
roadMajor: "#d2bd8e",
|
|
28735
|
+
label: "#5b4f3a",
|
|
28736
|
+
halo: "#f2ead6",
|
|
28737
|
+
shadow: "#6b5a3e",
|
|
28738
|
+
highlight: "#fffaf0",
|
|
28739
|
+
fade: "#e8f2f8",
|
|
28740
|
+
// Raised from 0.45 on 2026-08-23. ⚠ Above the ~0.5 that
|
|
28741
|
+
// hillshade-style.mjs warns "starts competing with depth areas" — a
|
|
28742
|
+
// deliberate, sighted choice made against rendered evidence, not a
|
|
28743
|
+
// default. If soundings or DEPARE ever read as fighting the terrain,
|
|
28744
|
+
// this number is the first thing to walk back.
|
|
28745
|
+
exaggeration: 0.8
|
|
28746
|
+
},
|
|
28747
|
+
DUSK: {
|
|
28748
|
+
base: "#4a4433",
|
|
28749
|
+
cover: { wood: "#414a38", wetland: "#3f4740", urban: "#4e4a42", sand: "#544c39" },
|
|
28750
|
+
contour: "#6b6250",
|
|
28751
|
+
contourIndex: "#837961",
|
|
28752
|
+
road: "#565040",
|
|
28753
|
+
roadMajor: "#63594440",
|
|
28754
|
+
label: "#c3b898",
|
|
28755
|
+
halo: "#2b2820",
|
|
28756
|
+
shadow: "#2e2a20",
|
|
28757
|
+
highlight: "#8a8068",
|
|
28758
|
+
fade: "#16202a",
|
|
28759
|
+
// Unchanged. DUSK was not the palette the 0.8 decision was made against.
|
|
28760
|
+
exaggeration: 0.45
|
|
28761
|
+
},
|
|
28762
|
+
NIGHT: {
|
|
28763
|
+
base: "#2a2620",
|
|
28764
|
+
cover: { wood: "#242a20", wetland: "#232823", urban: "#2c2925", sand: "#302b22" },
|
|
28765
|
+
contour: "#413b30",
|
|
28766
|
+
contourIndex: "#524a3b",
|
|
28767
|
+
road: "#332f27",
|
|
28768
|
+
roadMajor: "#3d382d",
|
|
28769
|
+
label: "#7d745e",
|
|
28770
|
+
halo: "#15130f",
|
|
28771
|
+
shadow: "#1a1712",
|
|
28772
|
+
highlight: "#4a4436",
|
|
28773
|
+
fade: "#07090b",
|
|
28774
|
+
// ⚠ DELIBERATELY NOT RAISED. See the type's note: a strong hillshade
|
|
28775
|
+
// under red night vision is a grey haze over exactly the contrast a
|
|
28776
|
+
// skipper is navigating by. Left at the shipped value rather than
|
|
28777
|
+
// lowered, because lowering it is a separate decision from this one.
|
|
28778
|
+
exaggeration: 0.45
|
|
28779
|
+
}
|
|
28780
|
+
};
|
|
28781
|
+
var FITS = ["<=", ["coalesce", ["get", "min_z"], 0], ["zoom"]];
|
|
28782
|
+
var ANCHOR = ["coalesce", ["get", "anchor"], "top"];
|
|
28783
|
+
var anchorOffset = (em) => [
|
|
28784
|
+
"match",
|
|
28785
|
+
["get", "anchor"],
|
|
28786
|
+
"left",
|
|
28787
|
+
["literal", [0.6, 0]],
|
|
28788
|
+
"right",
|
|
28789
|
+
["literal", [-0.6, 0]],
|
|
28790
|
+
"bottom",
|
|
28791
|
+
["literal", [0, -em]],
|
|
28792
|
+
["literal", [0, em]]
|
|
28793
|
+
];
|
|
28794
|
+
var FONT = ["Open Sans Regular"];
|
|
28795
|
+
var FONT_ITALIC = ["Open Sans Italic"];
|
|
28796
|
+
function landLayers(opts = {}) {
|
|
28797
|
+
const palette = opts.palette ?? "DAY";
|
|
28798
|
+
const T = LAND_THEMES[palette] ?? LAND_THEMES.DAY;
|
|
28799
|
+
const src = opts.sourceId ?? LAND_REGION;
|
|
28362
28800
|
const demSrc = opts.reliefSourceId ?? RELIEF_REGION;
|
|
28363
28801
|
const L = (o) => o;
|
|
28364
28802
|
const layers = [
|
|
@@ -28396,6 +28834,8 @@ function landLayers(opts = {}) {
|
|
|
28396
28834
|
}));
|
|
28397
28835
|
if (opts.relief) {
|
|
28398
28836
|
layers.push(L({
|
|
28837
|
+
// No minzoom here: LAND_SURFACE_LAYERS floors it with the rest of the
|
|
28838
|
+
// surface, so the two can never drift apart again.
|
|
28399
28839
|
id: "chrtland-hillshade",
|
|
28400
28840
|
type: "hillshade",
|
|
28401
28841
|
source: demSrc,
|
|
@@ -28451,12 +28891,12 @@ function landLayers(opts = {}) {
|
|
|
28451
28891
|
}
|
|
28452
28892
|
}));
|
|
28453
28893
|
layers.push(L({
|
|
28454
|
-
id: "chrtland-
|
|
28894
|
+
id: "chrtland-city-major",
|
|
28455
28895
|
type: "symbol",
|
|
28456
28896
|
source: src,
|
|
28457
28897
|
"source-layer": "land_town",
|
|
28458
|
-
minzoom:
|
|
28459
|
-
filter:
|
|
28898
|
+
minzoom: 6.5,
|
|
28899
|
+
filter: [">=", ["coalesce", ["get", "prank"], 0], 10],
|
|
28460
28900
|
layout: {
|
|
28461
28901
|
"text-field": [
|
|
28462
28902
|
"case",
|
|
@@ -28465,211 +28905,526 @@ function landLayers(opts = {}) {
|
|
|
28465
28905
|
["get", "name"]
|
|
28466
28906
|
],
|
|
28467
28907
|
"text-font": FONT,
|
|
28468
|
-
"text-size": [
|
|
28469
|
-
|
|
28908
|
+
"text-size": [
|
|
28909
|
+
"interpolate",
|
|
28910
|
+
["linear"],
|
|
28911
|
+
["zoom"],
|
|
28912
|
+
6.5,
|
|
28913
|
+
11.5,
|
|
28914
|
+
9,
|
|
28915
|
+
["+", 10, ["*", 0.4, ["coalesce", ["get", "prank"], 10]]],
|
|
28916
|
+
13,
|
|
28917
|
+
["+", 13, ["*", 0.5, ["coalesce", ["get", "prank"], 10]]]
|
|
28918
|
+
],
|
|
28919
|
+
"text-letter-spacing": ["case", ["==", ["get", "upcase"], true], 0.14, 0.02],
|
|
28470
28920
|
"text-offset": anchorOffset(0.75),
|
|
28471
28921
|
"text-anchor": ANCHOR,
|
|
28472
|
-
"text-padding":
|
|
28922
|
+
"text-padding": 10,
|
|
28473
28923
|
"text-allow-overlap": false,
|
|
28474
|
-
"symbol-sort-key":
|
|
28924
|
+
"symbol-sort-key": ["*", -1, ["coalesce", ["get", "pop"], 0]]
|
|
28475
28925
|
},
|
|
28476
|
-
paint: { "text-color": T.label, "text-halo-color": T.halo, "text-halo-width": 1.
|
|
28926
|
+
paint: { "text-color": T.label, "text-halo-color": T.halo, "text-halo-width": 1.6 }
|
|
28477
28927
|
}));
|
|
28478
28928
|
layers.push(L({
|
|
28479
|
-
id: "chrtland-
|
|
28929
|
+
id: "chrtland-town-port",
|
|
28480
28930
|
type: "symbol",
|
|
28481
28931
|
source: src,
|
|
28482
|
-
"source-layer": "
|
|
28483
|
-
minzoom:
|
|
28484
|
-
filter: [
|
|
28932
|
+
"source-layer": "land_town",
|
|
28933
|
+
minzoom: 8.5,
|
|
28934
|
+
filter: [
|
|
28935
|
+
"all",
|
|
28936
|
+
["==", ["get", "enc_dup"], true],
|
|
28937
|
+
["<", ["coalesce", ["get", "prank"], 0], 10]
|
|
28938
|
+
],
|
|
28485
28939
|
layout: {
|
|
28486
|
-
"text-field": ["
|
|
28940
|
+
"text-field": ["get", "name"],
|
|
28487
28941
|
"text-font": FONT,
|
|
28488
|
-
"text-size": ["interpolate", ["linear"], ["zoom"],
|
|
28489
|
-
"text-offset": anchorOffset(0.
|
|
28942
|
+
"text-size": ["interpolate", ["linear"], ["zoom"], 8.5, 11, 12, 13.5],
|
|
28943
|
+
"text-offset": anchorOffset(0.75),
|
|
28490
28944
|
"text-anchor": ANCHOR,
|
|
28491
|
-
"text-padding":
|
|
28945
|
+
"text-padding": 8,
|
|
28492
28946
|
"text-allow-overlap": false,
|
|
28493
|
-
|
|
28947
|
+
// Within the tier, the bigger place wins the space: without this the
|
|
28948
|
+
// tie broke on data order and "West Gloucester" beat Gloucester.
|
|
28949
|
+
"symbol-sort-key": ["*", -1, ["coalesce", ["get", "pop"], 0]]
|
|
28494
28950
|
},
|
|
28495
|
-
paint: { "text-color": T.label, "text-halo-color": T.halo, "text-halo-width": 1.
|
|
28951
|
+
paint: { "text-color": T.label, "text-halo-color": T.halo, "text-halo-width": 1.4 }
|
|
28496
28952
|
}));
|
|
28497
28953
|
layers.push(L({
|
|
28498
|
-
id: "chrtland-
|
|
28954
|
+
id: "chrtland-town",
|
|
28499
28955
|
type: "symbol",
|
|
28500
28956
|
source: src,
|
|
28501
|
-
"source-layer": "
|
|
28502
|
-
minzoom:
|
|
28503
|
-
filter:
|
|
28957
|
+
"source-layer": "land_town",
|
|
28958
|
+
minzoom: 9,
|
|
28959
|
+
filter: [
|
|
28960
|
+
"all",
|
|
28961
|
+
FITS,
|
|
28962
|
+
["<", ["coalesce", ["get", "prank"], 0], 10],
|
|
28963
|
+
["!=", ["get", "enc_dup"], true]
|
|
28964
|
+
],
|
|
28504
28965
|
layout: {
|
|
28505
|
-
"text-field": [
|
|
28506
|
-
|
|
28507
|
-
|
|
28508
|
-
|
|
28966
|
+
"text-field": [
|
|
28967
|
+
"case",
|
|
28968
|
+
["==", ["get", "upcase"], true],
|
|
28969
|
+
["upcase", ["get", "name"]],
|
|
28970
|
+
["get", "name"]
|
|
28971
|
+
],
|
|
28972
|
+
"text-font": FONT,
|
|
28973
|
+
"text-size": ["interpolate", ["linear"], ["zoom"], 9, 10, 14, 13.5],
|
|
28974
|
+
"text-letter-spacing": ["case", ["==", ["get", "upcase"], true], 0.14, 0],
|
|
28975
|
+
"text-offset": anchorOffset(0.75),
|
|
28509
28976
|
"text-anchor": ANCHOR,
|
|
28510
|
-
"text-padding":
|
|
28977
|
+
"text-padding": 6,
|
|
28511
28978
|
"text-allow-overlap": false,
|
|
28512
|
-
"symbol-sort-key":
|
|
28979
|
+
"symbol-sort-key": 2
|
|
28513
28980
|
},
|
|
28514
28981
|
paint: { "text-color": T.label, "text-halo-color": T.halo, "text-halo-width": 1.2 }
|
|
28515
28982
|
}));
|
|
28516
|
-
|
|
28517
|
-
|
|
28518
|
-
|
|
28519
|
-
|
|
28520
|
-
|
|
28521
|
-
|
|
28522
|
-
|
|
28523
|
-
|
|
28524
|
-
|
|
28525
|
-
|
|
28526
|
-
|
|
28527
|
-
|
|
28528
|
-
|
|
28529
|
-
|
|
28530
|
-
|
|
28531
|
-
|
|
28532
|
-
|
|
28533
|
-
|
|
28534
|
-
|
|
28535
|
-
|
|
28536
|
-
|
|
28537
|
-
|
|
28538
|
-
|
|
28539
|
-
|
|
28540
|
-
|
|
28541
|
-
|
|
28542
|
-
|
|
28543
|
-
|
|
28544
|
-
|
|
28545
|
-
|
|
28546
|
-
|
|
28547
|
-
|
|
28548
|
-
|
|
28549
|
-
|
|
28550
|
-
|
|
28551
|
-
|
|
28552
|
-
|
|
28553
|
-
}
|
|
28554
|
-
|
|
28555
|
-
|
|
28556
|
-
|
|
28557
|
-
|
|
28558
|
-
]);
|
|
28559
|
-
function bandVariantMinzoom(layer, B) {
|
|
28560
|
-
const classMinzoom = layer.minzoom ?? 0;
|
|
28561
|
-
if (isExemptOpaqueFill(layer) && FILL_FLOOR_FREE_BANDS.has(B)) {
|
|
28562
|
-
return classMinzoom;
|
|
28563
|
-
}
|
|
28564
|
-
return Math.max(classMinzoom, BAND_PRIORITY_RANGES[B].minzoom);
|
|
28565
|
-
}
|
|
28566
|
-
var BAND_DRAW_ORDER = ["1", "2", "3", "4", "5", "6"];
|
|
28567
|
-
var CHART_BAND_CODES = {
|
|
28568
|
-
Overview: ["1"],
|
|
28569
|
-
General: ["2"],
|
|
28570
|
-
Coastal: ["3"],
|
|
28571
|
-
Approach: ["4"],
|
|
28572
|
-
Harbor: ["5", "6"]
|
|
28573
|
-
};
|
|
28574
|
-
var CHART_CASCADE_CODES = {
|
|
28575
|
-
Overview: ["1"],
|
|
28576
|
-
General: ["1", "2"],
|
|
28577
|
-
Coastal: ["1", "2", "3"],
|
|
28578
|
-
Approach: ["1", "2", "3", "4"],
|
|
28579
|
-
Harbor: ["1", "2", "3", "4", "5", "6"]
|
|
28580
|
-
};
|
|
28581
|
-
var CHRT_STYLE_MODES = [
|
|
28582
|
-
"continuous",
|
|
28583
|
-
...Object.keys(CHART_BAND_CODES).map(
|
|
28584
|
-
(band) => `chart:${band}`
|
|
28585
|
-
)
|
|
28586
|
-
];
|
|
28587
|
-
function normalizeStyleMode(raw) {
|
|
28588
|
-
if (raw === null || raw === void 0 || raw === "") return "continuous";
|
|
28589
|
-
const lower = raw.toLowerCase();
|
|
28590
|
-
return CHRT_STYLE_MODES.find((mode) => mode.toLowerCase() === lower) ?? null;
|
|
28591
|
-
}
|
|
28592
|
-
function styleModeBand(mode) {
|
|
28593
|
-
return mode === "continuous" ? null : mode.slice("chart:".length);
|
|
28594
|
-
}
|
|
28595
|
-
function bandFilterExpression(codes) {
|
|
28596
|
-
const list = Array.isArray(codes) ? codes : [codes];
|
|
28597
|
-
if (list.length === 1) {
|
|
28598
|
-
return ["==", ["slice", ["get", "_cell_id"], 2, 3], list[0]];
|
|
28599
|
-
}
|
|
28600
|
-
return ["in", ["slice", ["get", "_cell_id"], 2, 3], ["literal", [...list]]];
|
|
28601
|
-
}
|
|
28602
|
-
function combineWithBandFilter(baseFilter, bandFilter) {
|
|
28603
|
-
if (!baseFilter) return bandFilter;
|
|
28604
|
-
if (baseFilter[0] === "all") {
|
|
28605
|
-
return ["all", ...baseFilter.slice(1), bandFilter];
|
|
28983
|
+
layers.push(L({
|
|
28984
|
+
id: "chrtland-summit",
|
|
28985
|
+
type: "symbol",
|
|
28986
|
+
source: src,
|
|
28987
|
+
"source-layer": "land_summit",
|
|
28988
|
+
minzoom: 11.5,
|
|
28989
|
+
filter: ["all", FITS, [">=", ["get", "peak_m"], 40]],
|
|
28990
|
+
layout: {
|
|
28991
|
+
"text-field": ["concat", ["get", "name"], "\n", ["to-string", ["get", "peak_m"]], " m"],
|
|
28992
|
+
"text-font": FONT,
|
|
28993
|
+
"text-size": ["interpolate", ["linear"], ["zoom"], 11.5, 8.5, 14, 11],
|
|
28994
|
+
"text-offset": anchorOffset(0.8),
|
|
28995
|
+
"text-anchor": ANCHOR,
|
|
28996
|
+
"text-padding": 5,
|
|
28997
|
+
"text-allow-overlap": false,
|
|
28998
|
+
"symbol-sort-key": 3
|
|
28999
|
+
},
|
|
29000
|
+
paint: { "text-color": T.label, "text-halo-color": T.halo, "text-halo-width": 1.2 }
|
|
29001
|
+
}));
|
|
29002
|
+
layers.push(L({
|
|
29003
|
+
id: "chrtland-poi",
|
|
29004
|
+
type: "symbol",
|
|
29005
|
+
source: src,
|
|
29006
|
+
"source-layer": "land_poi",
|
|
29007
|
+
minzoom: 13,
|
|
29008
|
+
filter: FITS,
|
|
29009
|
+
layout: {
|
|
29010
|
+
"text-field": ["get", "name"],
|
|
29011
|
+
"text-font": FONT_ITALIC,
|
|
29012
|
+
"text-size": ["interpolate", ["linear"], ["zoom"], 13, 8, 14, 9.5],
|
|
29013
|
+
"text-offset": anchorOffset(0.7),
|
|
29014
|
+
"text-anchor": ANCHOR,
|
|
29015
|
+
"text-padding": 5,
|
|
29016
|
+
"text-allow-overlap": false,
|
|
29017
|
+
"symbol-sort-key": 5
|
|
29018
|
+
},
|
|
29019
|
+
paint: { "text-color": T.label, "text-halo-color": T.halo, "text-halo-width": 1.2 }
|
|
29020
|
+
}));
|
|
29021
|
+
for (const layer of layers) {
|
|
29022
|
+
const l = layer;
|
|
29023
|
+
if (!LAND_SURFACE_LAYERS(l.id)) continue;
|
|
29024
|
+
l.minzoom = Math.max(l.minzoom ?? 0, LAND_SURFACE_MINZOOM);
|
|
28606
29025
|
}
|
|
28607
|
-
return
|
|
29026
|
+
return layers;
|
|
28608
29027
|
}
|
|
28609
|
-
|
|
28610
|
-
|
|
28611
|
-
|
|
28612
|
-
|
|
28613
|
-
|
|
28614
|
-
|
|
28615
|
-
|
|
28616
|
-
|
|
28617
|
-
|
|
28618
|
-
|
|
28619
|
-
|
|
28620
|
-
|
|
28621
|
-
|
|
29028
|
+
|
|
29029
|
+
// src/atlas/base-layers.ts
|
|
29030
|
+
var NOCOVERAGE_PATTERN = "chrt:hatch-nocoverage";
|
|
29031
|
+
var UNCHARTED_PATTERN = "chrt:hatch-uncharted";
|
|
29032
|
+
var BASE_THEMES = {
|
|
29033
|
+
DAY: {
|
|
29034
|
+
earth: "#2A3439",
|
|
29035
|
+
water: "#1B2328",
|
|
29036
|
+
river: "#24313A",
|
|
29037
|
+
boundary: "#4A575E",
|
|
29038
|
+
jurisdiction: "#E8D9B0",
|
|
29039
|
+
// CHRTland's own tan — one land colour
|
|
29040
|
+
inlandWater: "#B9CFDD",
|
|
29041
|
+
// muted, unmistakably not a charted blue
|
|
29042
|
+
// State furniture. These three were referenced by the layers but missing
|
|
29043
|
+
// from this table (and its type — the long-standing TS2339 trio), so the
|
|
29044
|
+
// lines shipped on MapLibre's DEFAULT black. Now deliberate: a hairline
|
|
29045
|
+
// a shade deeper than the tan, quiet caps, tan halo.
|
|
29046
|
+
stateLine: "#C4B389",
|
|
29047
|
+
stateLabel: "#8E8468",
|
|
29048
|
+
stateHalo: "#E8D9B0",
|
|
29049
|
+
// Rough-lake furniture (owner ask 2026-08-28: "the lakes need labels,
|
|
29050
|
+
// and shorelines drawn"): a shoreline a shade deeper than the rough
|
|
29051
|
+
// water, and a name in the water's own voice, haloed by the water.
|
|
29052
|
+
inlandShore: "#8FA9BA",
|
|
29053
|
+
inlandLabel: "#54707F",
|
|
29054
|
+
inlandHalo: "#B9CFDD"
|
|
29055
|
+
},
|
|
29056
|
+
DUSK: {
|
|
29057
|
+
earth: "#232B30",
|
|
29058
|
+
water: "#161D21",
|
|
29059
|
+
river: "#1E2930",
|
|
29060
|
+
boundary: "#3E4A50",
|
|
29061
|
+
jurisdiction: "#C9BC96",
|
|
29062
|
+
inlandWater: "#93A9B7",
|
|
29063
|
+
stateLine: "#A6976F",
|
|
29064
|
+
stateLabel: "#776E55",
|
|
29065
|
+
stateHalo: "#C9BC96",
|
|
29066
|
+
inlandShore: "#74899A",
|
|
29067
|
+
inlandLabel: "#41586A",
|
|
29068
|
+
inlandHalo: "#93A9B7"
|
|
29069
|
+
},
|
|
29070
|
+
NIGHT: {
|
|
29071
|
+
earth: "#1A2125",
|
|
29072
|
+
water: "#10161A",
|
|
29073
|
+
river: "#182228",
|
|
29074
|
+
boundary: "#333E44",
|
|
29075
|
+
jurisdiction: "#8F866C",
|
|
29076
|
+
inlandWater: "#5F7280",
|
|
29077
|
+
stateLine: "#6B6350",
|
|
29078
|
+
stateLabel: "#565040",
|
|
29079
|
+
stateHalo: "#8F866C",
|
|
29080
|
+
inlandShore: "#4A5C68",
|
|
29081
|
+
inlandLabel: "#8CA2B0",
|
|
29082
|
+
inlandHalo: "#5F7280"
|
|
28622
29083
|
}
|
|
28623
|
-
|
|
28624
|
-
}
|
|
28625
|
-
|
|
28626
|
-
const [
|
|
28627
|
-
const
|
|
29084
|
+
};
|
|
29085
|
+
function baseLayers(opts = {}) {
|
|
29086
|
+
const palette = opts.palette ?? "DAY";
|
|
29087
|
+
const T = BASE_THEMES[palette] ?? BASE_THEMES.DAY;
|
|
29088
|
+
const src = opts.sourceId ?? BASE_REGION;
|
|
29089
|
+
const L = (o) => o;
|
|
28628
29090
|
return [
|
|
28629
|
-
|
|
28630
|
-
|
|
28631
|
-
|
|
28632
|
-
|
|
28633
|
-
|
|
28634
|
-
|
|
28635
|
-
|
|
28636
|
-
|
|
28637
|
-
|
|
28638
|
-
|
|
28639
|
-
|
|
28640
|
-
|
|
28641
|
-
|
|
29091
|
+
// 1. Geography — the world as it is, in the quiet dark voice. The OCEAN
|
|
29092
|
+
// is a background, not a polygon: the archive ships land and lakes,
|
|
29093
|
+
// and "everything else is sea" is exactly what a background expresses
|
|
29094
|
+
// — geometry-free, gap-free at every antimeridian and pole. Without
|
|
29095
|
+
// it the sea outside coverage was bare S-52 NODTA, a light grey that
|
|
29096
|
+
// read as "charted ground" with a hatch on it.
|
|
29097
|
+
L({
|
|
29098
|
+
id: "chrtbase-ocean",
|
|
29099
|
+
type: "background",
|
|
29100
|
+
paint: { "background-color": T.water }
|
|
29101
|
+
}),
|
|
29102
|
+
L({
|
|
29103
|
+
id: "chrtbase-earth",
|
|
29104
|
+
type: "fill",
|
|
29105
|
+
source: src,
|
|
29106
|
+
"source-layer": "base_earth",
|
|
29107
|
+
paint: { "fill-color": T.earth }
|
|
29108
|
+
}),
|
|
29109
|
+
L({
|
|
29110
|
+
id: "chrtbase-water",
|
|
29111
|
+
type: "fill",
|
|
29112
|
+
source: src,
|
|
29113
|
+
"source-layer": "base_water",
|
|
29114
|
+
paint: { "fill-color": T.water }
|
|
29115
|
+
}),
|
|
29116
|
+
L({
|
|
29117
|
+
id: "chrtbase-river",
|
|
29118
|
+
type: "line",
|
|
29119
|
+
source: src,
|
|
29120
|
+
"source-layer": "base_river",
|
|
29121
|
+
minzoom: 9,
|
|
29122
|
+
paint: {
|
|
29123
|
+
"line-color": T.river,
|
|
29124
|
+
"line-width": ["interpolate", ["linear"], ["zoom"], 9, 0.6, 14, 1.6]
|
|
29125
|
+
}
|
|
29126
|
+
}),
|
|
29127
|
+
L({
|
|
29128
|
+
id: "chrtbase-boundary",
|
|
29129
|
+
type: "line",
|
|
29130
|
+
source: src,
|
|
29131
|
+
"source-layer": "base_boundary",
|
|
29132
|
+
paint: { "line-color": T.boundary, "line-width": 0.6, "line-opacity": 0.8 }
|
|
29133
|
+
}),
|
|
29134
|
+
// 2. THE DEFAULT: no coverage, everywhere, as a geometry-free background.
|
|
29135
|
+
// Constant opacity by design — see the header. What kills it is being
|
|
29136
|
+
// painted OVER, never fading.
|
|
29137
|
+
L({
|
|
29138
|
+
id: "chrtbase-nocoverage",
|
|
29139
|
+
type: "background",
|
|
29140
|
+
paint: { "background-pattern": NOCOVERAGE_PATTERN, "background-opacity": 0.55 }
|
|
29141
|
+
}),
|
|
29142
|
+
// 3. The jurisdiction: countries whose charts CHRT carries go tan — the
|
|
29143
|
+
// same tan as CHRTland, so "land in our world" is ONE colour. Opaque,
|
|
29144
|
+
// so it takes the crosshatch off its ground entirely.
|
|
29145
|
+
L({
|
|
29146
|
+
id: "chrtbase-jurisdiction",
|
|
29147
|
+
type: "fill",
|
|
29148
|
+
source: src,
|
|
29149
|
+
"source-layer": "jurisdiction",
|
|
29150
|
+
paint: { "fill-color": T.jurisdiction }
|
|
29151
|
+
}),
|
|
29152
|
+
// 4. States — internal boundaries and names, inside the jurisdiction
|
|
29153
|
+
// only (owner decision 2026-08-28). A hairline a shade deeper than the
|
|
29154
|
+
// tan, and quiet letter-spaced caps that fade as harbour detail takes
|
|
29155
|
+
// over. The one deliberate exception to no-names-on-the-basemap:
|
|
29156
|
+
// ADMIN furniture, nothing else — see the fence test.
|
|
29157
|
+
L({
|
|
29158
|
+
id: "chrtbase-state-boundary",
|
|
29159
|
+
type: "line",
|
|
29160
|
+
source: src,
|
|
29161
|
+
"source-layer": "state_boundary",
|
|
29162
|
+
minzoom: 4,
|
|
29163
|
+
paint: {
|
|
29164
|
+
"line-color": T.stateLine,
|
|
29165
|
+
"line-width": ["interpolate", ["linear"], ["zoom"], 4, 0.5, 10, 1],
|
|
29166
|
+
"line-dasharray": [3, 2],
|
|
29167
|
+
"line-opacity": 0.8
|
|
29168
|
+
}
|
|
29169
|
+
}),
|
|
29170
|
+
// 5. Country borders, drawn to be HOISTED above the whole chart by the
|
|
29171
|
+
// generator (owner, 2026-08-31: "where there is charted land, can we
|
|
29172
|
+
// add country borders on top of everything? I want it clear where
|
|
29173
|
+
// there is a boarder."). Sourced from NE boundary_lines_land — land
|
|
29174
|
+
// borders only, never coastline — so riding on top cannot stroke a
|
|
29175
|
+
// coast. Dashed so it reads as an administrative line, not chart
|
|
29176
|
+
// linework.
|
|
29177
|
+
L({
|
|
29178
|
+
id: "chrtbase-country-boundary",
|
|
29179
|
+
type: "line",
|
|
29180
|
+
source: src,
|
|
29181
|
+
"source-layer": "country_boundary",
|
|
29182
|
+
paint: {
|
|
29183
|
+
"line-color": T.boundary,
|
|
29184
|
+
"line-width": ["interpolate", ["linear"], ["zoom"], 3, 0.8, 8, 1.4, 12, 2.2],
|
|
29185
|
+
"line-dasharray": [4, 2],
|
|
29186
|
+
"line-opacity": 0.9
|
|
29187
|
+
}
|
|
29188
|
+
}),
|
|
29189
|
+
L({
|
|
29190
|
+
id: "chrtbase-state-label",
|
|
29191
|
+
type: "symbol",
|
|
29192
|
+
source: src,
|
|
29193
|
+
"source-layer": "state_label",
|
|
29194
|
+
minzoom: 4,
|
|
29195
|
+
maxzoom: 10,
|
|
29196
|
+
layout: {
|
|
29197
|
+
"text-field": ["upcase", ["get", "name"]],
|
|
29198
|
+
"text-font": ["Open Sans Regular"],
|
|
29199
|
+
"text-size": ["interpolate", ["linear"], ["zoom"], 4, 9, 8, 12],
|
|
29200
|
+
"text-letter-spacing": 0.25,
|
|
29201
|
+
"text-allow-overlap": false,
|
|
29202
|
+
"text-padding": 8
|
|
29203
|
+
},
|
|
29204
|
+
paint: {
|
|
29205
|
+
"text-color": T.stateLabel,
|
|
29206
|
+
"text-halo-color": T.stateHalo,
|
|
29207
|
+
"text-halo-width": 1.2,
|
|
29208
|
+
"text-opacity": ["interpolate", ["linear"], ["zoom"], 4, 0.7, 8, 0.85, 10, 0.4]
|
|
29209
|
+
}
|
|
29210
|
+
})
|
|
28642
29211
|
];
|
|
28643
29212
|
}
|
|
28644
|
-
function
|
|
28645
|
-
const
|
|
28646
|
-
const [
|
|
28647
|
-
|
|
28648
|
-
|
|
28649
|
-
|
|
28650
|
-
|
|
28651
|
-
|
|
28652
|
-
|
|
28653
|
-
|
|
28654
|
-
|
|
28655
|
-
|
|
28656
|
-
|
|
28657
|
-
|
|
28658
|
-
|
|
28659
|
-
|
|
28660
|
-
|
|
28661
|
-
|
|
28662
|
-
|
|
28663
|
-
|
|
28664
|
-
|
|
28665
|
-
|
|
28666
|
-
|
|
28667
|
-
|
|
29213
|
+
function baseInlandWaterLayers(opts = {}) {
|
|
29214
|
+
const palette = opts.palette ?? "DAY";
|
|
29215
|
+
const T = BASE_THEMES[palette] ?? BASE_THEMES.DAY;
|
|
29216
|
+
const src = opts.sourceId ?? BASE_REGION;
|
|
29217
|
+
const L = (o) => o;
|
|
29218
|
+
const NE_TIER = ["!", ["has", "kind"]];
|
|
29219
|
+
const PLANET_TIER = ["has", "kind"];
|
|
29220
|
+
const HANDOVER = 9;
|
|
29221
|
+
return [
|
|
29222
|
+
// ── The fill pair, SPLIT BY TIER at the handover ────────────────────────
|
|
29223
|
+
//
|
|
29224
|
+
// Both tiers share `inland_water` (tile-join concatenates the same layer
|
|
29225
|
+
// name), and an unsplit fill draws BOTH — which means the NE polygon is
|
|
29226
|
+
// painted over the planet one at every zoom. That is not a cosmetic
|
|
29227
|
+
// double-draw: NE 10m carries no interior rings, so its blob has no
|
|
29228
|
+
// ISLANDS in it, and painting it over the planet's holed polygons buries
|
|
29229
|
+
// every island in the lake under "uncharted water". Owner-reported at
|
|
29230
|
+
// Winnipesaukee z11.7 ("there is land (and islands) being covered by the
|
|
29231
|
+
// 'no chart here' boundary"), and measured: rendering the planet tier
|
|
29232
|
+
// alone brings back the whole archipelago — Rattlesnake, Bear, Long
|
|
29233
|
+
// Island, Diamond, the Barndoors — while the NE tier alone is a faceted
|
|
29234
|
+
// blob whose edges bear no relation to the shoreline drawn beneath it.
|
|
29235
|
+
//
|
|
29236
|
+
// So each tier owns its own zoom range, the same handover the shoreline
|
|
29237
|
+
// below uses. Known residue, measured over the whole archive: of the 141
|
|
29238
|
+
// NE lakes we still carry, 138 have planet water beneath them; the
|
|
29239
|
+
// exceptions are two unnamed western-Alaska tundra lakes and a 2e-5 deg²
|
|
29240
|
+
// Lake Michigan sliver, which lose their rough fill above z9 because the
|
|
29241
|
+
// planet has no `water` polygon there at all (checked against the raw
|
|
29242
|
+
// pre-clip NDJSON — not a clip artefact).
|
|
29243
|
+
L({
|
|
29244
|
+
id: "chrtbase-inland-water",
|
|
29245
|
+
type: "fill",
|
|
29246
|
+
source: src,
|
|
29247
|
+
"source-layer": "inland_water",
|
|
29248
|
+
filter: NE_TIER,
|
|
29249
|
+
maxzoom: HANDOVER,
|
|
29250
|
+
paint: { "fill-color": T.inlandWater }
|
|
29251
|
+
}),
|
|
29252
|
+
L({
|
|
29253
|
+
id: "chrtbase-inland-hatch",
|
|
29254
|
+
type: "fill",
|
|
29255
|
+
source: src,
|
|
29256
|
+
"source-layer": "inland_water",
|
|
29257
|
+
filter: NE_TIER,
|
|
29258
|
+
maxzoom: HANDOVER,
|
|
29259
|
+
paint: { "fill-pattern": UNCHARTED_PATTERN, "fill-opacity": 0.7 }
|
|
29260
|
+
}),
|
|
29261
|
+
L({
|
|
29262
|
+
id: "chrtbase-inland-water-detail",
|
|
29263
|
+
type: "fill",
|
|
29264
|
+
source: src,
|
|
29265
|
+
"source-layer": "inland_water",
|
|
29266
|
+
filter: PLANET_TIER,
|
|
29267
|
+
minzoom: HANDOVER,
|
|
29268
|
+
paint: { "fill-color": T.inlandWater }
|
|
29269
|
+
}),
|
|
29270
|
+
L({
|
|
29271
|
+
id: "chrtbase-inland-hatch-detail",
|
|
29272
|
+
type: "fill",
|
|
29273
|
+
source: src,
|
|
29274
|
+
"source-layer": "inland_water",
|
|
29275
|
+
filter: PLANET_TIER,
|
|
29276
|
+
minzoom: HANDOVER,
|
|
29277
|
+
paint: { "fill-pattern": UNCHARTED_PATTERN, "fill-opacity": 0.7 }
|
|
29278
|
+
}),
|
|
29279
|
+
// The rough shoreline (owner ask 2026-08-28). Drawn ABOVE the hatch so
|
|
29280
|
+
// the edge stays crisp through the texture, and BELOW the charted-water
|
|
29281
|
+
// repaint clones the generator splices in after it — so wherever the
|
|
29282
|
+
// chart repaints its own water, the rough NE10m line yields to the
|
|
29283
|
+
// chart's COALNE instead of double-striking it.
|
|
29284
|
+
//
|
|
29285
|
+
// NE-tier geometry ONLY (`kind` absent): the planet detail tier joins
|
|
29286
|
+
// the same source-layer from z9 as MANY polygons whose chunk-clip seams
|
|
29287
|
+
// rejoin invisibly as FILLS — but a line layer strokes every seam, and
|
|
29288
|
+
// Winnipesaukee rendered as a mesh of internal edges. Planet features
|
|
29289
|
+
// carry `kind` (the INLAND_KINDS filter's own field); NE's carry only
|
|
29290
|
+
// `name`, so the absence of `kind` IS the tier discriminator.
|
|
29291
|
+
L({
|
|
29292
|
+
id: "chrtbase-inland-shore",
|
|
29293
|
+
type: "line",
|
|
29294
|
+
source: src,
|
|
29295
|
+
"source-layer": "inland_water",
|
|
29296
|
+
filter: NE_TIER,
|
|
29297
|
+
// Hands the shoreline over to the planet tier at ITS floor. Both
|
|
29298
|
+
// tiers live in `inland_water` from z9 up (tile-join concatenates the
|
|
29299
|
+
// same layer name), and their FILLS merge invisibly — but two
|
|
29300
|
+
// outlines do not: NE 10m generalises Winnipesaukee's shape, so its
|
|
29301
|
+
// outline ran straight across the middle of the lake the planet had
|
|
29302
|
+
// drawn correctly around. The coarse line is right only while it is
|
|
29303
|
+
// the only shape we have.
|
|
29304
|
+
maxzoom: HANDOVER,
|
|
29305
|
+
paint: {
|
|
29306
|
+
"line-color": T.inlandShore,
|
|
29307
|
+
"line-width": ["interpolate", ["linear"], ["zoom"], 4, 0.5, 10, 1.1, 14, 1.6],
|
|
29308
|
+
"line-opacity": 0.9
|
|
29309
|
+
}
|
|
29310
|
+
}),
|
|
29311
|
+
// The PLANET tier's shoreline (owner ask 2026-08-31: "uncharted small
|
|
29312
|
+
// lakes render with no outline"). It reads its own `inland_shore` layer,
|
|
29313
|
+
// NOT `inland_water`, and that indirection is the whole fix: the mesh
|
|
29314
|
+
// that forced the `!has kind` fence above is a CLIP artefact — an MVT
|
|
29315
|
+
// feature arrives already cut to its own z10 tile, so stroking the
|
|
29316
|
+
// polygon strokes every tile boundary it crosses. The build now walks
|
|
29317
|
+
// those rings, drops the axis-aligned runs that sit on the read zoom's
|
|
29318
|
+
// tile grid, and emits what is left as lines (measured on one New
|
|
29319
|
+
// England chunk: 61 long axis-aligned mesh edges → 5, and the 5 survivors
|
|
29320
|
+
// are 0.02–0.5 tiles off the grid, i.e. real straight water edges).
|
|
29321
|
+
//
|
|
29322
|
+
// Thinner and quieter than the NE line: this is finer geometry describing
|
|
29323
|
+
// water nobody has surveyed, and approximate data should look
|
|
29324
|
+
// approximate.
|
|
29325
|
+
L({
|
|
29326
|
+
id: "chrtbase-inland-shore-detail",
|
|
29327
|
+
type: "line",
|
|
29328
|
+
source: src,
|
|
29329
|
+
"source-layer": "inland_shore",
|
|
29330
|
+
minzoom: HANDOVER,
|
|
29331
|
+
paint: {
|
|
29332
|
+
"line-color": T.inlandShore,
|
|
29333
|
+
"line-width": ["interpolate", ["linear"], ["zoom"], 9, 0.5, 12, 0.9, 15, 1.2],
|
|
29334
|
+
"line-opacity": 0.85
|
|
29335
|
+
}
|
|
29336
|
+
}),
|
|
29337
|
+
// The lake's name (owner ask 2026-08-28) — the second scoped exception
|
|
29338
|
+
// to no-names-on-the-basemap, after the states: WATER-BODY names only.
|
|
29339
|
+
// No maxzoom: an uncharted lake keeps its name at every zoom; over
|
|
29340
|
+
// charted lakes the chart's own SEAARE label takes over at detail zooms
|
|
29341
|
+
// and collision culls the echo.
|
|
29342
|
+
//
|
|
29343
|
+
// The archive's `inland_water_label` anchors (v3+). v3 shipped ONE
|
|
29344
|
+
// PointOnSurface anchor per lake, and a big lake's single anchor pans
|
|
29345
|
+
// off-viewport — Winnipesaukee at z11.7 read nameless with its name two
|
|
29346
|
+
// tiles west of the screen (owner-reported). Labelling the polygons
|
|
29347
|
+
// instead is NOT the fix: tippecanoe's coalescing drops the NE polygon
|
|
29348
|
+
// from some z11 tiles (measured: 11/619/747 carries only planet pieces),
|
|
29349
|
+
// so a polygon-sourced label vanishes tile by tile. v4 therefore bakes a
|
|
29350
|
+
// deterministic anchor GRID into big lakes — the paper-chart convention
|
|
29351
|
+
// of repeating a name along a large water body — and this one layer
|
|
29352
|
+
// renders however many anchors are in view; collision culls the rest.
|
|
29353
|
+
// Planet-tier water-body NAMES (owner ask 2026-08-31: NE 10m names only
|
|
29354
|
+
// 113 US lakes). Emitted BELOW the NE layer on purpose — MapLibre places
|
|
29355
|
+
// symbols topmost-layer-first, so the big NE lake wins collision against
|
|
29356
|
+
// the pond beside it, and both lose to the chart's own labels (every ENC
|
|
29357
|
+
// class is emitted after this pair).
|
|
29358
|
+
//
|
|
29359
|
+
// The size threshold is OSM's own `min_zoom`, carried through the build:
|
|
29360
|
+
// Lake Winnipesaukee reads 10, Squam Lake 12, so a pond simply is not in
|
|
29361
|
+
// the filter until the user is close enough for it to be the answer to
|
|
29362
|
+
// "what is that water". `["zoom"]` in a filter evaluates at integer zoom
|
|
29363
|
+
// levels and removes the feature from placement entirely — verified
|
|
29364
|
+
// against maplibre-gl-native rather than assumed, because a text-size-0
|
|
29365
|
+
// gate would still claim a collision slot.
|
|
29366
|
+
//
|
|
29367
|
+
// The tiers are separate LAYERS, not a filtered split of one — see the
|
|
29368
|
+
// builder for why: an archive that a not-yet-deployed style would
|
|
29369
|
+
// mis-render cannot be promoted first, and this one has to be.
|
|
29370
|
+
L({
|
|
29371
|
+
id: "chrtbase-inland-label-detail",
|
|
29372
|
+
type: "symbol",
|
|
29373
|
+
source: src,
|
|
29374
|
+
"source-layer": "inland_water_label_detail",
|
|
29375
|
+
minzoom: HANDOVER,
|
|
29376
|
+
filter: [">=", ["zoom"], ["get", "min_zoom"]],
|
|
29377
|
+
layout: {
|
|
29378
|
+
"text-field": ["get", "name"],
|
|
29379
|
+
"text-font": ["Open Sans Regular"],
|
|
29380
|
+
"text-size": ["interpolate", ["linear"], ["zoom"], 9, 9, 12, 10.5, 15, 11],
|
|
29381
|
+
"text-max-width": 8,
|
|
29382
|
+
"text-allow-overlap": false,
|
|
29383
|
+
"text-padding": 6,
|
|
29384
|
+
// Bigger water first within the tier — min_zoom IS the size rank.
|
|
29385
|
+
"symbol-sort-key": ["get", "min_zoom"]
|
|
29386
|
+
},
|
|
29387
|
+
paint: {
|
|
29388
|
+
"text-color": T.inlandLabel,
|
|
29389
|
+
"text-halo-color": T.inlandHalo,
|
|
29390
|
+
"text-halo-width": 1,
|
|
29391
|
+
"text-opacity": 0.9
|
|
29392
|
+
}
|
|
29393
|
+
}),
|
|
29394
|
+
L({
|
|
29395
|
+
id: "chrtbase-inland-label",
|
|
29396
|
+
type: "symbol",
|
|
29397
|
+
source: src,
|
|
29398
|
+
"source-layer": "inland_water_label",
|
|
29399
|
+
minzoom: 5,
|
|
29400
|
+
layout: {
|
|
29401
|
+
"text-field": ["get", "name"],
|
|
29402
|
+
"text-font": ["Open Sans Regular"],
|
|
29403
|
+
"text-size": ["interpolate", ["linear"], ["zoom"], 5, 9.5, 10, 12],
|
|
29404
|
+
"text-max-width": 7,
|
|
29405
|
+
"text-allow-overlap": false,
|
|
29406
|
+
// Zoom-scaled spacing for the anchor GRID: at z8.9 the 0.08-deg
|
|
29407
|
+
// anchors sit ~54 px apart and a flat padding let six copies of
|
|
29408
|
+
// "Lake Ontario" crowd one screen (owner-reported 2026-08-30).
|
|
29409
|
+
// Wide collision boxes at low zoom thin the grid to the repeat-
|
|
29410
|
+
// by-convention cadence; by z12 the anchors are ~850 px apart and
|
|
29411
|
+
// the padding relaxes to normal.
|
|
29412
|
+
"text-padding": ["interpolate", ["linear"], ["zoom"], 5, 80, 9, 64, 12, 16]
|
|
29413
|
+
},
|
|
29414
|
+
paint: {
|
|
29415
|
+
"text-color": T.inlandLabel,
|
|
29416
|
+
"text-halo-color": T.inlandHalo,
|
|
29417
|
+
"text-halo-width": 1.1
|
|
29418
|
+
}
|
|
29419
|
+
})
|
|
29420
|
+
];
|
|
28668
29421
|
}
|
|
28669
29422
|
|
|
28670
29423
|
// src/atlas/band-collapse.ts
|
|
28671
|
-
function collapsedDominanceGate(B, minzoom) {
|
|
28672
|
-
const clauses = [
|
|
29424
|
+
function collapsedDominanceGate(B, minzoom, notDominatedCeiling = null) {
|
|
29425
|
+
const clauses = [
|
|
29426
|
+
notDominatedCeiling !== null && notDominatedCeiling > minzoom ? ["all", NOT_DOMINATED_FILTER, ["<", ["zoom"], notDominatedCeiling]] : NOT_DOMINATED_FILTER
|
|
29427
|
+
];
|
|
28673
29428
|
const bInt = parseInt(B, 10);
|
|
28674
29429
|
for (const D of BAND_DRAW_ORDER) {
|
|
28675
29430
|
const dInt = parseInt(D, 10);
|
|
@@ -28698,7 +29453,7 @@ function makeCollapsedBandVariant(layer, B, isExempt) {
|
|
|
28698
29453
|
filter: combineWithBandFilter(layer.filter, [
|
|
28699
29454
|
"all",
|
|
28700
29455
|
bandFilterExpression(B),
|
|
28701
|
-
collapsedDominanceGate(B, minzoom)
|
|
29456
|
+
collapsedDominanceGate(B, minzoom, generalisedAreaCeiling(layer, B))
|
|
28702
29457
|
]),
|
|
28703
29458
|
minzoom
|
|
28704
29459
|
};
|
|
@@ -28833,6 +29588,7 @@ function makeBandVariants(layer, B) {
|
|
|
28833
29588
|
delete exempt.maxzoom;
|
|
28834
29589
|
return [exempt];
|
|
28835
29590
|
}
|
|
29591
|
+
const ceiling = generalisedAreaCeiling(layer, B);
|
|
28836
29592
|
const notDom = {
|
|
28837
29593
|
...layer,
|
|
28838
29594
|
id: `${layer.id}-band${B}`,
|
|
@@ -28843,6 +29599,7 @@ function makeBandVariants(layer, B) {
|
|
|
28843
29599
|
minzoom
|
|
28844
29600
|
};
|
|
28845
29601
|
delete notDom.maxzoom;
|
|
29602
|
+
if (ceiling !== null && ceiling > minzoom) notDom.maxzoom = ceiling;
|
|
28846
29603
|
variants.push(notDom);
|
|
28847
29604
|
const bInt = parseInt(B, 10);
|
|
28848
29605
|
for (const D of BAND_DRAW_ORDER) {
|
|
@@ -28865,14 +29622,21 @@ function makeBandVariants(layer, B) {
|
|
|
28865
29622
|
return variants;
|
|
28866
29623
|
}
|
|
28867
29624
|
var CELL_BAND_OPACITY_CURVE = {
|
|
28868
|
-
"1": [[3, 0.5], [5, 0.
|
|
28869
|
-
"2": [[5, 0.
|
|
28870
|
-
"3": [[7, 0.55], [9, 0.
|
|
28871
|
-
"4": [[9, 0.
|
|
28872
|
-
"5": [[11, 0.
|
|
28873
|
-
"6": [[13, 0.75], [16, 0.
|
|
29625
|
+
"1": [[3, 0.5], [5, 0.42], [16, 0.34]],
|
|
29626
|
+
"2": [[5, 0.52], [7, 0.44], [16, 0.34]],
|
|
29627
|
+
"3": [[7, 0.55], [9, 0.46], [16, 0.36]],
|
|
29628
|
+
"4": [[9, 0.58], [11, 0.5], [16, 0.42]],
|
|
29629
|
+
"5": [[11, 0.62], [13, 0.52], [18, 0.44]],
|
|
29630
|
+
"6": [[13, 0.75], [16, 0.6], [22, 0.5]]
|
|
28874
29631
|
};
|
|
28875
|
-
|
|
29632
|
+
var ORIENTATION_OPACITY_SCALE = {
|
|
29633
|
+
LNDARE: 1.45,
|
|
29634
|
+
LNDRGN: 1.4,
|
|
29635
|
+
BUAARE: 1.25
|
|
29636
|
+
};
|
|
29637
|
+
var ORIENTATION_OPACITY_MAX = 0.92;
|
|
29638
|
+
var ORIENTATION_BOOST_MINZOOM = 11;
|
|
29639
|
+
function fanOrientationLabelByCellBand(label, bandFan, objectClass) {
|
|
28876
29640
|
const cellBandExpr = [
|
|
28877
29641
|
"slice",
|
|
28878
29642
|
["coalesce", ["get", "_cell_id"], "??9???"],
|
|
@@ -28880,11 +29644,15 @@ function fanOrientationLabelByCellBand(label, bandFan) {
|
|
|
28880
29644
|
3
|
|
28881
29645
|
];
|
|
28882
29646
|
const variants = [];
|
|
29647
|
+
const scale = objectClass ? ORIENTATION_OPACITY_SCALE[objectClass] ?? 1 : 1;
|
|
28883
29648
|
for (const code of BAND_DRAW_ORDER) {
|
|
28884
29649
|
const range = BAND_PRIORITY_RANGES[code];
|
|
28885
29650
|
const curve = CELL_BAND_OPACITY_CURVE[code];
|
|
28886
29651
|
const opacityExpr = ["interpolate", ["linear"], ["zoom"]];
|
|
28887
|
-
for (const [z, o] of curve)
|
|
29652
|
+
for (const [z, o] of curve) {
|
|
29653
|
+
const s = z >= ORIENTATION_BOOST_MINZOOM ? scale : 1;
|
|
29654
|
+
opacityExpr.push(z, Math.round(Math.min(o * s, ORIENTATION_OPACITY_MAX) * 100) / 100);
|
|
29655
|
+
}
|
|
28888
29656
|
const cellFiltered = combineWithBandFilter(
|
|
28889
29657
|
label.filter,
|
|
28890
29658
|
["==", cellBandExpr, code]
|
|
@@ -28931,6 +29699,33 @@ function fanOrientationLabelByCellBand(label, bandFan) {
|
|
|
28931
29699
|
}
|
|
28932
29700
|
return variants;
|
|
28933
29701
|
}
|
|
29702
|
+
var MAJOR_WATER_TEXT_PADDING = [
|
|
29703
|
+
"interpolate",
|
|
29704
|
+
["linear"],
|
|
29705
|
+
["zoom"],
|
|
29706
|
+
5,
|
|
29707
|
+
24,
|
|
29708
|
+
9,
|
|
29709
|
+
20,
|
|
29710
|
+
13,
|
|
29711
|
+
16,
|
|
29712
|
+
16,
|
|
29713
|
+
14
|
|
29714
|
+
];
|
|
29715
|
+
function splitOrientationFan(fan, hoistedFilter, watermarkFilter, hoistedLayout) {
|
|
29716
|
+
return {
|
|
29717
|
+
hoist: fan.map((l) => ({
|
|
29718
|
+
...l,
|
|
29719
|
+
id: `${l.id}-major`,
|
|
29720
|
+
filter: combineWithBandFilter(l.filter, hoistedFilter),
|
|
29721
|
+
...hoistedLayout ? { layout: { ...l.layout ?? {}, ...hoistedLayout } } : {}
|
|
29722
|
+
})),
|
|
29723
|
+
watermark: fan.map((l) => ({
|
|
29724
|
+
...l,
|
|
29725
|
+
filter: combineWithBandFilter(l.filter, watermarkFilter)
|
|
29726
|
+
}))
|
|
29727
|
+
};
|
|
29728
|
+
}
|
|
28934
29729
|
function byLabelPriorityBottomUp(bundles) {
|
|
28935
29730
|
return [...bundles].sort(
|
|
28936
29731
|
(a, b) => getLabelPriority(b.objectClass) - getLabelPriority(a.objectClass)
|
|
@@ -28992,13 +29787,45 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
28992
29787
|
paint: { "background-color": resolveColor("NODTA", palette) }
|
|
28993
29788
|
};
|
|
28994
29789
|
const layers = [backgroundLayer];
|
|
29790
|
+
if (opts.base) {
|
|
29791
|
+
const base = baseLayers({ palette });
|
|
29792
|
+
const coverageGround = {
|
|
29793
|
+
id: "s52-coverage-ground",
|
|
29794
|
+
type: "fill",
|
|
29795
|
+
source: sourceId,
|
|
29796
|
+
"source-layer": "M_COVR",
|
|
29797
|
+
filter: [
|
|
29798
|
+
"all",
|
|
29799
|
+
["any", ["==", ["geometry-type"], "Polygon"], ["==", ["geometry-type"], "MultiPolygon"]],
|
|
29800
|
+
["==", ["to-number", ["coalesce", ["get", "CATCOV"], 0]], 1]
|
|
29801
|
+
],
|
|
29802
|
+
paint: { "fill-color": resolveColor("DEPDW", palette) },
|
|
29803
|
+
metadata: { "s52:objectClass": "M_COVR", "s52:role": "coverage-ground" }
|
|
29804
|
+
};
|
|
29805
|
+
const jurisdictionAt = base.findIndex(
|
|
29806
|
+
(l) => l.id === "chrtbase-jurisdiction"
|
|
29807
|
+
);
|
|
29808
|
+
if (jurisdictionAt === -1) {
|
|
29809
|
+
layers.push(...base, coverageGround);
|
|
29810
|
+
} else {
|
|
29811
|
+
layers.push(
|
|
29812
|
+
...base.slice(0, jurisdictionAt),
|
|
29813
|
+
coverageGround,
|
|
29814
|
+
...base.slice(jurisdictionAt)
|
|
29815
|
+
);
|
|
29816
|
+
}
|
|
29817
|
+
}
|
|
28995
29818
|
if (opts.land) {
|
|
28996
29819
|
layers.push(...landLayers({
|
|
28997
29820
|
palette,
|
|
28998
29821
|
...opts.relief ? { relief: true } : {}
|
|
28999
29822
|
}));
|
|
29000
29823
|
}
|
|
29824
|
+
if (opts.base) {
|
|
29825
|
+
layers.push(...baseInlandWaterLayers({ palette }));
|
|
29826
|
+
}
|
|
29001
29827
|
const labelLayers = [];
|
|
29828
|
+
const majorWaterLayers = [];
|
|
29002
29829
|
const chartCascade = opts.chartMode ? CHART_CASCADE_CODES[opts.chartMode.band] : null;
|
|
29003
29830
|
const chartRequestedCodes = opts.chartMode ? CHART_BAND_CODES[opts.chartMode.band] : null;
|
|
29004
29831
|
const bandFan = opts.bandFan ?? "collapsed";
|
|
@@ -29114,13 +29941,44 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
29114
29941
|
}
|
|
29115
29942
|
}
|
|
29116
29943
|
}
|
|
29944
|
+
{
|
|
29945
|
+
const TERMINAL_FILL_RE = /^s52-(?:LNDARE|DEPARE)-fill-band[456]$/;
|
|
29946
|
+
for (const l of layers) {
|
|
29947
|
+
const lay = l;
|
|
29948
|
+
if (!TERMINAL_FILL_RE.test(lay.id)) continue;
|
|
29949
|
+
const floor = lay.minzoom ?? 0;
|
|
29950
|
+
if (floor <= 0) continue;
|
|
29951
|
+
lay.filter = [
|
|
29952
|
+
"all",
|
|
29953
|
+
lay.filter,
|
|
29954
|
+
["any", [">=", ["zoom"], floor], ["==", ["get", "_terminal_fill"], true]]
|
|
29955
|
+
];
|
|
29956
|
+
delete lay.minzoom;
|
|
29957
|
+
}
|
|
29958
|
+
}
|
|
29117
29959
|
if (opts.labels === "placename") {
|
|
29118
29960
|
labelLayers.push(...buildPlaceNameLayers(sourceId));
|
|
29119
29961
|
} else {
|
|
29120
29962
|
for (const { objectClass, label } of bundles) {
|
|
29121
29963
|
if (!label) continue;
|
|
29122
29964
|
if (getLabelBehavior(objectClass) === "orientation-cross-band") {
|
|
29123
|
-
|
|
29965
|
+
if (objectClass === "LNDARE" || objectClass === "SEAARE") {
|
|
29966
|
+
const Z = ISLAND_LABEL_HOIST_MINZOOM;
|
|
29967
|
+
const { hoist, watermark } = objectClass === "LNDARE" ? splitOrientationFan(
|
|
29968
|
+
fanOrientationLabelByCellBand(label, bandFan, objectClass),
|
|
29969
|
+
[">=", ["zoom"], Z],
|
|
29970
|
+
["<", ["zoom"], Z]
|
|
29971
|
+
) : splitOrientationFan(
|
|
29972
|
+
fanOrientationLabelByCellBand(label, bandFan, objectClass),
|
|
29973
|
+
["<=", buildSeaAreaTierExpression(), MAJOR_WATER_TIER_MAX],
|
|
29974
|
+
[">", buildSeaAreaTierExpression(), MAJOR_WATER_TIER_MAX],
|
|
29975
|
+
{ "text-padding": MAJOR_WATER_TEXT_PADDING }
|
|
29976
|
+
);
|
|
29977
|
+
majorWaterLayers.push(...hoist);
|
|
29978
|
+
labelLayers.push(...watermark);
|
|
29979
|
+
continue;
|
|
29980
|
+
}
|
|
29981
|
+
labelLayers.push(...fanOrientationLabelByCellBand(label, bandFan, objectClass));
|
|
29124
29982
|
}
|
|
29125
29983
|
}
|
|
29126
29984
|
}
|
|
@@ -29129,7 +29987,12 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
29129
29987
|
(b) => b.label !== null && getLabelBehavior(b.objectClass) !== "orientation-cross-band"
|
|
29130
29988
|
)
|
|
29131
29989
|
);
|
|
29132
|
-
|
|
29990
|
+
let majorsEmitted = majorWaterLayers.length === 0;
|
|
29991
|
+
for (const { label, objectClass: cls } of featureLabelBundles) {
|
|
29992
|
+
if (!majorsEmitted && getLabelPriority(cls) < MAJOR_WATER_LABEL_PRIORITY) {
|
|
29993
|
+
labelLayers.push(...majorWaterLayers);
|
|
29994
|
+
majorsEmitted = true;
|
|
29995
|
+
}
|
|
29133
29996
|
if (bandFan === "collapsed") {
|
|
29134
29997
|
labelLayers.push(makeCollapsedCrossBandLabel(label, isDominanceExempt(label)));
|
|
29135
29998
|
continue;
|
|
@@ -29138,18 +30001,121 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
29138
30001
|
labelLayers.push(...emitBand(label, code));
|
|
29139
30002
|
}
|
|
29140
30003
|
}
|
|
30004
|
+
if (!majorsEmitted) labelLayers.push(...majorWaterLayers);
|
|
29141
30005
|
layers.push(...labelLayers);
|
|
29142
30006
|
}
|
|
29143
30007
|
const symbolSet = opts.symbolSet ?? "s52";
|
|
29144
30008
|
const spriteBase = `${opts.tileBaseUrl ?? "https://tiles.chrt.co"}/v1/sprites`;
|
|
29145
30009
|
const isLndareFill = (l) => /^s52-LNDARE-fill-band\d+$/.test(l.id);
|
|
29146
|
-
const
|
|
30010
|
+
const isWaterBaseFill = (l) => /^s52-(DEPARE|DRGARE|UNSARE)-fill-band\d+$/.test(l.id);
|
|
30011
|
+
const bandOf = (l) => {
|
|
30012
|
+
const m = /-band(\d+)$/.exec(l.id);
|
|
30013
|
+
return m ? Number(m[1]) : 0;
|
|
30014
|
+
};
|
|
29147
30015
|
const emitted = opts.land ? (() => {
|
|
29148
|
-
const
|
|
29149
|
-
const
|
|
29150
|
-
const
|
|
29151
|
-
|
|
30016
|
+
const relocated = layers.filter((l) => isLndareFill(l) || isWaterBaseFill(l));
|
|
30017
|
+
const others = layers.filter((l) => !isLndareFill(l) && !isWaterBaseFill(l));
|
|
30018
|
+
const isSeaareFill = (l) => /^s52-SEAARE-fill-band\d+$/.test(l.id);
|
|
30019
|
+
const seaare = others.filter(isSeaareFill);
|
|
30020
|
+
const rest = others.filter((l) => !isSeaareFill(l));
|
|
30021
|
+
const isBaseLayer = (l) => {
|
|
30022
|
+
const id = l.id;
|
|
30023
|
+
if (id.startsWith("chrtbase-inland-")) return false;
|
|
30024
|
+
return id.startsWith("chrtbase-") || id === "s52-coverage-ground";
|
|
30025
|
+
};
|
|
30026
|
+
const baseBlock = rest.filter(isBaseLayer);
|
|
30027
|
+
const restAbove = rest.filter((l) => !isBaseLayer(l));
|
|
30028
|
+
const bands = [...new Set(relocated.map(bandOf))].sort((a, b) => a - b);
|
|
30029
|
+
const oneTan = landBaseColor(palette);
|
|
30030
|
+
const asBaseTan = (l) => ({
|
|
30031
|
+
...l,
|
|
30032
|
+
paint: {
|
|
30033
|
+
...l.paint ?? {},
|
|
30034
|
+
"fill-color": oneTan
|
|
30035
|
+
}
|
|
30036
|
+
});
|
|
30037
|
+
const ordered = [];
|
|
30038
|
+
for (const b of bands) {
|
|
30039
|
+
ordered.push(...relocated.filter((l) => bandOf(l) === b && isWaterBaseFill(l)));
|
|
30040
|
+
ordered.push(...relocated.filter((l) => bandOf(l) === b && isLndareFill(l)).map(asBaseTan));
|
|
30041
|
+
}
|
|
30042
|
+
const findRest = (id) => restAbove.findIndex((l) => l.id === id);
|
|
30043
|
+
const shoreAt = findRest("chrtbase-inland-shore");
|
|
30044
|
+
const pairAt = shoreAt !== -1 ? shoreAt : findRest("chrtbase-inland-hatch");
|
|
30045
|
+
let restFinal = restAbove;
|
|
30046
|
+
if (pairAt !== -1) {
|
|
30047
|
+
const repaint = [];
|
|
30048
|
+
for (const b of bands) {
|
|
30049
|
+
for (const l of relocated.filter((x) => bandOf(x) === b && isWaterBaseFill(x))) {
|
|
30050
|
+
const src = l;
|
|
30051
|
+
const meta = { ...src.metadata ?? {}, "chrt:role": "charted-water-repaint" };
|
|
30052
|
+
const notDom = {
|
|
30053
|
+
...l,
|
|
30054
|
+
id: `${src.id}-overpair`,
|
|
30055
|
+
filter: ["all", src.filter, NOT_DOMINATED_FILTER],
|
|
30056
|
+
metadata: meta
|
|
30057
|
+
};
|
|
30058
|
+
delete notDom.maxzoom;
|
|
30059
|
+
delete notDom.minzoom;
|
|
30060
|
+
repaint.push(notDom);
|
|
30061
|
+
for (const D of BAND_DRAW_ORDER) {
|
|
30062
|
+
if (parseInt(D, 10) <= b) continue;
|
|
30063
|
+
const dMaxzoom = dominatedVariantMaxzoom(D);
|
|
30064
|
+
if (dMaxzoom <= 0) continue;
|
|
30065
|
+
const domVariant = {
|
|
30066
|
+
...l,
|
|
30067
|
+
id: `${src.id}-overpair-domBy${D}`,
|
|
30068
|
+
filter: ["all", src.filter, dominatedByMinFilter(D)],
|
|
30069
|
+
metadata: meta,
|
|
30070
|
+
maxzoom: dMaxzoom
|
|
30071
|
+
};
|
|
30072
|
+
delete domVariant.minzoom;
|
|
30073
|
+
repaint.push(domVariant);
|
|
30074
|
+
}
|
|
30075
|
+
}
|
|
30076
|
+
}
|
|
30077
|
+
const isCoalneLine = (l) => /^s52-COALNE-line-band\d(-domBy\d)?$/.test(l.id);
|
|
30078
|
+
for (const l of layers.filter(isCoalneLine)) {
|
|
30079
|
+
const src = l;
|
|
30080
|
+
const meta = { ...src.metadata ?? {}, "chrt:role": "charted-water-repaint" };
|
|
30081
|
+
const banded = {
|
|
30082
|
+
...l,
|
|
30083
|
+
id: `${src.id}-overpair`,
|
|
30084
|
+
metadata: meta,
|
|
30085
|
+
minzoom: 9
|
|
30086
|
+
};
|
|
30087
|
+
repaint.push(banded);
|
|
30088
|
+
const terminal = {
|
|
30089
|
+
...l,
|
|
30090
|
+
id: `${src.id}-overpair-terminal`,
|
|
30091
|
+
filter: ["all", src.filter, ["==", ["get", "_terminal_shore"], true]],
|
|
30092
|
+
metadata: meta
|
|
30093
|
+
};
|
|
30094
|
+
delete terminal.minzoom;
|
|
30095
|
+
terminal.maxzoom = 9;
|
|
30096
|
+
repaint.push(terminal);
|
|
30097
|
+
}
|
|
30098
|
+
restFinal = [
|
|
30099
|
+
...restAbove.slice(0, pairAt + 1),
|
|
30100
|
+
...repaint,
|
|
30101
|
+
...restAbove.slice(pairAt + 1)
|
|
30102
|
+
];
|
|
30103
|
+
}
|
|
30104
|
+
return [restFinal[0], ...baseBlock, ...seaare, ...ordered, ...restFinal.slice(1)];
|
|
29152
30105
|
})() : layers;
|
|
30106
|
+
for (const id of [
|
|
30107
|
+
"chrtbase-country-boundary",
|
|
30108
|
+
"chrtland-town-port",
|
|
30109
|
+
"chrtland-city-major"
|
|
30110
|
+
]) {
|
|
30111
|
+
const at = emitted.findIndex(
|
|
30112
|
+
(l) => l.id === id
|
|
30113
|
+
);
|
|
30114
|
+
if (at !== -1) {
|
|
30115
|
+
const [layer] = emitted.splice(at, 1);
|
|
30116
|
+
emitted.push(layer);
|
|
30117
|
+
}
|
|
30118
|
+
}
|
|
29153
30119
|
const style = {
|
|
29154
30120
|
version: 8,
|
|
29155
30121
|
name: `CHRT ${SYMBOL_SET_LABELS[symbolSet]} ${PALETTE_NAMES[palette]}${opts.chartMode ? ` \u2014 chart:${opts.chartMode.band}` : ""}`,
|
|
@@ -29161,6 +30127,7 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
29161
30127
|
"chrt:layer-count": emitted.length,
|
|
29162
30128
|
"chrt:land": Boolean(opts.land),
|
|
29163
30129
|
"chrt:relief": Boolean(opts.relief),
|
|
30130
|
+
"chrt:base": Boolean(opts.base),
|
|
29164
30131
|
"chrt:render-mode": opts.chartMode ? `chart:${opts.chartMode.band}` : "continuous",
|
|
29165
30132
|
"chrt:description": "IHO S-52 portrayal rules for Electronic Navigational Charts (ENCs). Generated from the CHRT atlas engine."
|
|
29166
30133
|
},
|
|
@@ -29654,6 +30621,7 @@ var Chrt = class {
|
|
|
29654
30621
|
interact;
|
|
29655
30622
|
featuresEp;
|
|
29656
30623
|
searchEp;
|
|
30624
|
+
poiEp;
|
|
29657
30625
|
queryEp;
|
|
29658
30626
|
http;
|
|
29659
30627
|
tileAuth;
|
|
@@ -29675,6 +30643,7 @@ var Chrt = class {
|
|
|
29675
30643
|
this.weather = new WeatherEndpoint(this.http);
|
|
29676
30644
|
this.featuresEp = new FeaturesEndpoint(this.http);
|
|
29677
30645
|
this.searchEp = new SearchEndpoint(this.http);
|
|
30646
|
+
this.poiEp = new PoiEndpoint(this.http);
|
|
29678
30647
|
this.queryEp = new QueryEndpoint(this.http);
|
|
29679
30648
|
this.offline = new OfflineEndpoint(this.http, config.deviceId, config.manifestPath);
|
|
29680
30649
|
this.interact = new InteractClient(this.http);
|
|
@@ -29710,6 +30679,26 @@ var Chrt = class {
|
|
|
29710
30679
|
async search(options) {
|
|
29711
30680
|
return this.searchEp.find(options);
|
|
29712
30681
|
}
|
|
30682
|
+
/**
|
|
30683
|
+
* CHRTpoi — charted marine facilities in a viewport or radius.
|
|
30684
|
+
*
|
|
30685
|
+
* v1 serves marinas, derived from the ENC's own HRBFAC facility class —
|
|
30686
|
+
* the chart's data, with the source cell's currency inline per POI.
|
|
30687
|
+
*
|
|
30688
|
+
* ```ts
|
|
30689
|
+
* const { pois, coverage } = await chrt.pois({ bbox: [-70.7, 41.4, -70.5, 41.6] });
|
|
30690
|
+
* ```
|
|
30691
|
+
*
|
|
30692
|
+
* **Read `coverage.state` before interpreting an empty list.** `covered` +
|
|
30693
|
+
* empty means no marinas are charted there — a real answer. `none` means
|
|
30694
|
+
* CHRT cannot answer for that area at all, and an empty list means nothing.
|
|
30695
|
+
* The two must never be shown the same way, and `coverage.message` is
|
|
30696
|
+
* written to be relayed unedited. Metered on its own allowance
|
|
30697
|
+
* (`poi_called`), separate from features and search; not gated by tier.
|
|
30698
|
+
*/
|
|
30699
|
+
async pois(options) {
|
|
30700
|
+
return this.poiEp.list(options);
|
|
30701
|
+
}
|
|
29713
30702
|
/** Spatial proximity query around a point */
|
|
29714
30703
|
async query(options) {
|
|
29715
30704
|
return this.queryEp.nearby(options);
|
|
@@ -29880,6 +30869,18 @@ var Chrt = class {
|
|
|
29880
30869
|
return generateStyle(palette, {
|
|
29881
30870
|
region: this.region,
|
|
29882
30871
|
tileBaseUrl: this.tileBaseUrl,
|
|
30872
|
+
// Pin each source's tile URL to the build it serves, from whatever the
|
|
30873
|
+
// token cache already holds. Without a discriminator one URL is shared
|
|
30874
|
+
// by every build, so after a cutover a map keeps painting already-fetched
|
|
30875
|
+
// tiles from the OLD build beside newly-fetched ones from the new.
|
|
30876
|
+
//
|
|
30877
|
+
// BEFORE the spread, so an explicit `buildTags` wins — and only ever a
|
|
30878
|
+
// warm-cache read: this method is synchronous and must not mint. A cold
|
|
30879
|
+
// cache yields `{}`, i.e. exactly the URLs this SDK emitted before the
|
|
30880
|
+
// discriminator existed. Warm it with `chrt.transformRequest()` (which
|
|
30881
|
+
// kicks off a mint) or `await chrt.styleUrl()`; `await chrt.warmTiles()`
|
|
30882
|
+
// is the explicit form, and it returns the map.
|
|
30883
|
+
buildTags: this.tileAuth.peekBuildTags(this.region),
|
|
29883
30884
|
...styleOpts,
|
|
29884
30885
|
// The vessel's safety depth IS the style's safety contour — same number, same
|
|
29885
30886
|
// meaning. Threading it here is what stops the chart shading at one depth while
|
|
@@ -29953,9 +30954,21 @@ var Chrt = class {
|
|
|
29953
30954
|
* Pre-mint the tile token for a region. Optional — `transformRequest()` warms
|
|
29954
30955
|
* the cache automatically — but useful if you want to surface a mint error
|
|
29955
30956
|
* (e.g. `region_not_licensed`, `tile_quota_exceeded`) before the map loads.
|
|
30957
|
+
*
|
|
30958
|
+
* Returns what it minted, so a caller building its own sources can take the
|
|
30959
|
+
* `buildTags` map straight from here:
|
|
30960
|
+
*
|
|
30961
|
+
* ```ts
|
|
30962
|
+
* const { buildTags } = await chrt.warmTiles();
|
|
30963
|
+
* map.addSource("chrt", buildSources({ region: "NOAA_ENC", buildTags })["NOAA_ENC"]);
|
|
30964
|
+
* ```
|
|
30965
|
+
*
|
|
30966
|
+
* That pins each tile URL to the build it serves, so a build cutover gets its
|
|
30967
|
+
* own client cache namespace instead of leaving old and new tiles mixed on
|
|
30968
|
+
* screen. `chrt.style()` reads the same cache and needs no argument.
|
|
29956
30969
|
*/
|
|
29957
30970
|
async warmTiles(region) {
|
|
29958
|
-
|
|
30971
|
+
return this.tileAuth.getToken(region);
|
|
29959
30972
|
}
|
|
29960
30973
|
};
|
|
29961
30974
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -29966,9 +30979,13 @@ var Chrt = class {
|
|
|
29966
30979
|
ATON_EXPRESSION_CLASSES,
|
|
29967
30980
|
BAND_DRAW_ORDER,
|
|
29968
30981
|
BAND_PRIORITY_RANGES,
|
|
30982
|
+
BASE_MAXZOOM,
|
|
30983
|
+
BASE_MINZOOM,
|
|
30984
|
+
BASE_REGION,
|
|
29969
30985
|
BRIDGE_QUERY_LAYERS,
|
|
29970
30986
|
BUILD_MAXZOOM,
|
|
29971
30987
|
BUILD_MINZOOM,
|
|
30988
|
+
BUILD_PARAM,
|
|
29972
30989
|
CHART_BAND_CODES,
|
|
29973
30990
|
CHART_CASCADE_CODES,
|
|
29974
30991
|
CHART_SECTIONS,
|
|
@@ -30150,6 +31167,7 @@ var Chrt = class {
|
|
|
30150
31167
|
scaleSizeValue,
|
|
30151
31168
|
scaminToMinZoom,
|
|
30152
31169
|
styleModeBand,
|
|
31170
|
+
tileTemplate,
|
|
30153
31171
|
withinBboxFilter,
|
|
30154
31172
|
worldMinusBboxPolygon
|
|
30155
31173
|
});
|