@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.js
CHANGED
|
@@ -134,7 +134,7 @@ var TileTokenError = class extends Error {
|
|
|
134
134
|
this.status = status;
|
|
135
135
|
}
|
|
136
136
|
};
|
|
137
|
-
var TILE_URL_PATH = /\/v1\/tiles\/[A-Za-z0-9_]+(?:\/[a-z][a-z0-9-]*)?\/\d+\/\d+\/\d+\.mvt(?:\?.*)?$/i;
|
|
137
|
+
var TILE_URL_PATH = /\/v1\/tiles\/[A-Za-z0-9_]+(?:\/[a-z][a-z0-9-]*)?\/\d+\/\d+\/\d+\.(?:mvt|png)(?:\?.*)?$/i;
|
|
138
138
|
var STYLE_URL_PATH = /\/v1\/styles\/[A-Za-z0-9_]+\/style\.json(?:\?.*)?$/i;
|
|
139
139
|
var REFRESH_MARGIN_SECONDS = 30;
|
|
140
140
|
var STALE_GRACE_SECONDS = 1;
|
|
@@ -145,6 +145,15 @@ function nowSeconds() {
|
|
|
145
145
|
function sleep2(ms) {
|
|
146
146
|
return new Promise((r) => setTimeout(r, ms));
|
|
147
147
|
}
|
|
148
|
+
function readBuildTags(raw, region, buildTag) {
|
|
149
|
+
const out = { [region]: buildTag };
|
|
150
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
151
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
152
|
+
if (typeof value === "string" && value) out[key] = value;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
148
157
|
function mapErrorCode(raw, status) {
|
|
149
158
|
const known = [
|
|
150
159
|
"invalid_api_key",
|
|
@@ -202,6 +211,7 @@ var TileAuth = class {
|
|
|
202
211
|
expiresAt: b.expires_at,
|
|
203
212
|
kid: b.kid,
|
|
204
213
|
buildTag: b.build_tag,
|
|
214
|
+
buildTags: readBuildTags(b.build_tags, b.region, b.build_tag),
|
|
205
215
|
region: b.region
|
|
206
216
|
};
|
|
207
217
|
}
|
|
@@ -277,6 +287,22 @@ var TileAuth = class {
|
|
|
277
287
|
if (entry.response.expiresAt - nowSeconds() <= STALE_GRACE_SECONDS) return null;
|
|
278
288
|
return entry.response.token;
|
|
279
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Synchronous peek at the cached mint's per-region build ids — `{}` when the
|
|
292
|
+
* cache is cold.
|
|
293
|
+
*
|
|
294
|
+
* `chrt.style()` is synchronous and cannot mint, so this is how a style
|
|
295
|
+
* generated after the cache is warm picks up the build discriminator without
|
|
296
|
+
* the caller threading it by hand. A cold cache yields `{}`, which produces
|
|
297
|
+
* the undiscriminated URL the SDK emitted before this existed — never an
|
|
298
|
+
* error, and never a wrong build.
|
|
299
|
+
*/
|
|
300
|
+
peekBuildTags(region) {
|
|
301
|
+
const entry = this.cache.get(region ?? this.defaultRegion);
|
|
302
|
+
if (!entry) return {};
|
|
303
|
+
if (entry.response.expiresAt - nowSeconds() <= STALE_GRACE_SECONDS) return {};
|
|
304
|
+
return entry.response.buildTags;
|
|
305
|
+
}
|
|
280
306
|
/**
|
|
281
307
|
* Produce a MapLibre / Mapbox GL `transformRequest`. Attaches
|
|
282
308
|
* `Authorization: Bearer <jwt>` only to CHRT tile URLs and the CHRT
|
|
@@ -330,6 +356,78 @@ var TileAuth = class {
|
|
|
330
356
|
}
|
|
331
357
|
};
|
|
332
358
|
|
|
359
|
+
// src/atlas/tile-zoom.ts
|
|
360
|
+
var BUILD_MINZOOM = 0;
|
|
361
|
+
var BUILD_MAXZOOM = 14;
|
|
362
|
+
|
|
363
|
+
// src/atlas/source-registry.ts
|
|
364
|
+
var LAND_REGION = "CHRT_LAND";
|
|
365
|
+
var RELIEF_REGION = "CHRT_RELIEF";
|
|
366
|
+
var BASE_REGION = "CHRT_BASE";
|
|
367
|
+
var BASE_MINZOOM = 0;
|
|
368
|
+
var BASE_MAXZOOM = 11;
|
|
369
|
+
var RELIEF_MINZOOM = 9;
|
|
370
|
+
var RELIEF_MAXZOOM = 12;
|
|
371
|
+
var RELIEF_TILE_SIZE = 512;
|
|
372
|
+
var BUILD_PARAM = "v";
|
|
373
|
+
function tileTemplate(base, region, extension, buildId) {
|
|
374
|
+
const url = `${base}/v1/tiles/${region}/{z}/{x}/{y}.${extension}`;
|
|
375
|
+
const id = buildId?.trim();
|
|
376
|
+
if (!id) return url;
|
|
377
|
+
return `${url}?${BUILD_PARAM}=${encodeURIComponent(id)}`;
|
|
378
|
+
}
|
|
379
|
+
var DEFAULT_TILE_BASE_URL2 = "https://tiles.chrt.co";
|
|
380
|
+
var DEFAULT_REGION = "NOAA_ENC";
|
|
381
|
+
function buildSources(opts = {}) {
|
|
382
|
+
const mode = opts.mode ?? "pmtiles";
|
|
383
|
+
const region = opts.region ?? DEFAULT_REGION;
|
|
384
|
+
if (mode === "mapbox") {
|
|
385
|
+
return {
|
|
386
|
+
[region]: {
|
|
387
|
+
type: "vector",
|
|
388
|
+
url: `mapbox://chrt.${region.toLowerCase()}`
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
const base = (opts.tileBaseUrl ?? DEFAULT_TILE_BASE_URL2).replace(/\/+$/, "");
|
|
393
|
+
const buildFor = (r) => opts.buildTags?.[r] ?? (r === region ? opts.buildTag : void 0);
|
|
394
|
+
const sources = {
|
|
395
|
+
[region]: {
|
|
396
|
+
type: "vector",
|
|
397
|
+
tiles: [tileTemplate(base, region, "mvt", buildFor(region))],
|
|
398
|
+
minzoom: BUILD_MINZOOM,
|
|
399
|
+
maxzoom: BUILD_MAXZOOM
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
if (opts.land) {
|
|
403
|
+
sources[LAND_REGION] = {
|
|
404
|
+
type: "vector",
|
|
405
|
+
tiles: [tileTemplate(base, LAND_REGION, "mvt", buildFor(LAND_REGION))],
|
|
406
|
+
minzoom: BUILD_MINZOOM,
|
|
407
|
+
maxzoom: BUILD_MAXZOOM
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
if (opts.base) {
|
|
411
|
+
sources[BASE_REGION] = {
|
|
412
|
+
type: "vector",
|
|
413
|
+
tiles: [`${base}/v1/tiles/${BASE_REGION}/{z}/{x}/{y}.mvt`],
|
|
414
|
+
minzoom: BASE_MINZOOM,
|
|
415
|
+
maxzoom: BASE_MAXZOOM
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
if (opts.relief) {
|
|
419
|
+
sources[RELIEF_REGION] = {
|
|
420
|
+
type: "raster-dem",
|
|
421
|
+
tiles: [tileTemplate(base, RELIEF_REGION, "png", buildFor(RELIEF_REGION))],
|
|
422
|
+
encoding: "terrarium",
|
|
423
|
+
tileSize: RELIEF_TILE_SIZE,
|
|
424
|
+
minzoom: RELIEF_MINZOOM,
|
|
425
|
+
maxzoom: RELIEF_MAXZOOM
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
return sources;
|
|
429
|
+
}
|
|
430
|
+
|
|
333
431
|
// src/endpoints/tiles.ts
|
|
334
432
|
var TilesEndpoint = class {
|
|
335
433
|
constructor(http, tileAuth, opts) {
|
|
@@ -401,13 +499,20 @@ var TilesEndpoint = class {
|
|
|
401
499
|
return await res.json();
|
|
402
500
|
}
|
|
403
501
|
/**
|
|
404
|
-
* Vector tile URL
|
|
405
|
-
*
|
|
502
|
+
* Vector tile URL for the configured region, served by the tile-worker at
|
|
503
|
+
* `tiles.chrt.co`. Tiles are JWT-gated — pair this with
|
|
406
504
|
* `chrt.transformRequest()`, which attaches the `Authorization: Bearer`
|
|
407
505
|
* header. Prefer `chrt.style()`, which wires the source for you.
|
|
506
|
+
*
|
|
507
|
+
* Carries the build discriminator (`?v=`) once the token cache is warm, so
|
|
508
|
+
* two buildTags never share one cache entry — see `BUILD_PARAM`. A cold cache
|
|
509
|
+
* yields the bare URL, which is equally valid: the server ignores the
|
|
510
|
+
* parameter, and warming is a `chrt.warmTiles()` (or one
|
|
511
|
+
* `chrt.transformRequest()` round-trip) away.
|
|
408
512
|
*/
|
|
409
513
|
tileUrl(z, x, y) {
|
|
410
|
-
|
|
514
|
+
const build = this.tileAuth.peekBuildTags(this.region)[this.region];
|
|
515
|
+
return tileTemplate(this.tileBaseUrl, this.region, "mvt", build).replace("{z}", String(z)).replace("{x}", String(x)).replace("{y}", String(y));
|
|
411
516
|
}
|
|
412
517
|
/**
|
|
413
518
|
* @deprecated Tilesets were consolidated to one PMTiles per region (2026-04);
|
|
@@ -633,6 +738,27 @@ var SearchEndpoint = class {
|
|
|
633
738
|
}
|
|
634
739
|
};
|
|
635
740
|
|
|
741
|
+
// src/endpoints/poi.ts
|
|
742
|
+
var PoiEndpoint = class {
|
|
743
|
+
constructor(http) {
|
|
744
|
+
this.http = http;
|
|
745
|
+
}
|
|
746
|
+
async list(options) {
|
|
747
|
+
return this.http.request(
|
|
748
|
+
"/poi",
|
|
749
|
+
{
|
|
750
|
+
bbox: options.bbox ? options.bbox.join(",") : void 0,
|
|
751
|
+
near: options.near ? `${options.near[0]},${options.near[1]}` : void 0,
|
|
752
|
+
radius_m: options.radiusM,
|
|
753
|
+
category: options.category,
|
|
754
|
+
limit: options.limit,
|
|
755
|
+
offset: options.offset
|
|
756
|
+
},
|
|
757
|
+
options.signal
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
|
|
636
762
|
// src/endpoints/query.ts
|
|
637
763
|
var METRES_PER_NAUTICAL_MILE2 = 1852;
|
|
638
764
|
var QueryEndpoint = class {
|
|
@@ -6153,6 +6279,38 @@ var S52_LOOKUP = {
|
|
|
6153
6279
|
// polygons that overlap the security-zone CTNARE polygons; with both
|
|
6154
6280
|
// as flood fills, the harbor inner waters disappeared under stacked
|
|
6155
6281
|
// magenta. Same boundary-only treatment as CTNARE.
|
|
6282
|
+
// ── DASHED, 2026-08-24. A solid line here read as a second coastline.
|
|
6283
|
+
//
|
|
6284
|
+
// User report: "narragansett bay has 2 offset coastlines... this is so
|
|
6285
|
+
// sloppy. we can't sell this right now." Measured by ablating classes
|
|
6286
|
+
// out of the shipping style and diffing offscreen renders of that exact
|
|
6287
|
+
// frame (z10.54, -71.1745/41.6031): dropping every magenta-painting
|
|
6288
|
+
// class changed 38,580 px and the doubled shoreline vanished; RESARE
|
|
6289
|
+
// ALONE was 37,613 of them — 97.5%. CBLARE, CTNARE, ACHARE and ADMARE
|
|
6290
|
+
// each changed zero.
|
|
6291
|
+
//
|
|
6292
|
+
// Restricted areas in Narragansett Bay follow the shore, so their
|
|
6293
|
+
// boundary runs parallel to the coastline a short distance off it. Drawn
|
|
6294
|
+
// SOLID, in a chart where the coastline is also a solid line, that is a
|
|
6295
|
+
// second coastline to any reader — the geometry is right and the
|
|
6296
|
+
// portrayal lies about what it is.
|
|
6297
|
+
//
|
|
6298
|
+
// This is not a SCAMIN problem, and the first fix attempted was wrong:
|
|
6299
|
+
// the tile pipeline already honours SCAMIN (the z10 tile carries 9
|
|
6300
|
+
// RESARE features, all SCAMIN 1:119,999-1:999,999 — NOAA's own
|
|
6301
|
+
// instruction to draw them at overview scale — while the z11 tile
|
|
6302
|
+
// carries 35). Gating harder would have hidden features the HO asks for.
|
|
6303
|
+
//
|
|
6304
|
+
// Dashed restores the convention this class was already believed to
|
|
6305
|
+
// follow: MIPARE [4,3], TESARE [6,4] and PRDARE [4,2] are all dashed
|
|
6306
|
+
// magenta boundaries, and PRDARE's own comment describes itself as
|
|
6307
|
+
// "matching RESARE/CTNARE/MIPARE/TESARE convention". RESARE was the lone
|
|
6308
|
+
// solid outlier. It also moves toward the S-52 line style for this
|
|
6309
|
+
// class, RESARE51 ("boundary of a restricted area"), a broken magenta
|
|
6310
|
+
// line with inward T-marks — which cannot be used directly yet: the
|
|
6311
|
+
// served sprite (s52-day, 101 entries) carries no RESARE51 pattern, and
|
|
6312
|
+
// a `line-pattern` naming a missing sprite renders NOTHING, which would
|
|
6313
|
+
// turn a portrayal complaint into invisible restricted areas.
|
|
6156
6314
|
objectClass: "RESARE",
|
|
6157
6315
|
name: "Restricted Area",
|
|
6158
6316
|
category: "Regulated Areas",
|
|
@@ -6163,7 +6321,8 @@ var S52_LOOKUP = {
|
|
|
6163
6321
|
paint: {
|
|
6164
6322
|
"line-color": "TRFCD",
|
|
6165
6323
|
"line-width": 1.2,
|
|
6166
|
-
"line-opacity": 0.7
|
|
6324
|
+
"line-opacity": 0.7,
|
|
6325
|
+
"line-dasharray": [4, 3]
|
|
6167
6326
|
}
|
|
6168
6327
|
},
|
|
6169
6328
|
viewingGroup: 26040,
|
|
@@ -6247,7 +6406,13 @@ var S52_LOOKUP = {
|
|
|
6247
6406
|
category: "Regulated Areas",
|
|
6248
6407
|
displayCategory: "OTHER",
|
|
6249
6408
|
int1Ref: "N23",
|
|
6250
|
-
|
|
6409
|
+
// 0.15 painted Hanapepe Bay's dumping ground as a solid land-green wash
|
|
6410
|
+
// over the white deep-water ground — owner-read as "land painting up to
|
|
6411
|
+
// the coastline" (2026-08-31). INT1 N23/N24 portrays a dumping ground
|
|
6412
|
+
// as a dashed limit with a label, not an area tint; 0.04 matches the
|
|
6413
|
+
// TSSLPT/TSSRON faintness so the area reads as WATER with a regulated
|
|
6414
|
+
// overlay, at every tier that shows it.
|
|
6415
|
+
style: { type: "fill", paint: { "fill-color": "LITYW", "fill-opacity": 0.04 } },
|
|
6251
6416
|
viewingGroup: 26230,
|
|
6252
6417
|
scamin: 22e3
|
|
6253
6418
|
},
|
|
@@ -7297,12 +7462,19 @@ var S52_LOOKUP = {
|
|
|
7297
7462
|
// Inherits RESARE (S-57 112) — boundary-only, no flood fill. The stacked
|
|
7298
7463
|
// magenta wash that motivated RESARE's treatment applies here too: an
|
|
7299
7464
|
// inland reach can carry overlapping regulated areas just as a harbour can.
|
|
7465
|
+
// ── DASHED, matching RESARE (2026-08-25). The inland twin was missed
|
|
7466
|
+
// when RESARE was dashed the day before, so USACE inland waters kept
|
|
7467
|
+
// drawing restricted areas as a SOLID magenta line running alongside
|
|
7468
|
+
// the bank — the same "second coastline" a user reported on the coastal
|
|
7469
|
+
// chart, still shipping on the inland one. Found by auditing every
|
|
7470
|
+
// lowercase variant against its NOAA parent: of 14, this was the only
|
|
7471
|
+
// pair that diverged.
|
|
7300
7472
|
objectClass: "resare",
|
|
7301
7473
|
name: "Restricted Area (Inland)",
|
|
7302
7474
|
category: "Regulated Areas",
|
|
7303
7475
|
displayCategory: "STANDARD",
|
|
7304
7476
|
int1Ref: "N2",
|
|
7305
|
-
style: { type: "line", paint: { "line-color": "TRFCD", "line-width": 1.2, "line-opacity": 0.7 } },
|
|
7477
|
+
style: { type: "line", paint: { "line-color": "TRFCD", "line-width": 1.2, "line-opacity": 0.7, "line-dasharray": [4, 3] } },
|
|
7306
7478
|
viewingGroup: 26010,
|
|
7307
7479
|
scamin: 9e4
|
|
7308
7480
|
},
|
|
@@ -22657,14 +22829,19 @@ var ORIENTATION_BAND_SORT_KEY = [
|
|
|
22657
22829
|
["slice", ["coalesce", ["get", "_cell_id"], "??9???"], 2, 3],
|
|
22658
22830
|
9
|
|
22659
22831
|
];
|
|
22660
|
-
function
|
|
22832
|
+
function orientationSortKey(tier) {
|
|
22833
|
+
const band = ["*", 100, ORIENTATION_BAND_SORT_KEY];
|
|
22834
|
+
if (!tier) return band;
|
|
22835
|
+
return ["+", band, tier];
|
|
22836
|
+
}
|
|
22837
|
+
function orientationLabelLayout(extras = {}, tier) {
|
|
22661
22838
|
return {
|
|
22662
22839
|
"text-anchor": "center",
|
|
22663
22840
|
"text-allow-overlap": false,
|
|
22664
22841
|
"text-ignore-placement": false,
|
|
22665
22842
|
"text-optional": true,
|
|
22666
22843
|
"text-padding": 15,
|
|
22667
|
-
"symbol-sort-key":
|
|
22844
|
+
"symbol-sort-key": orientationSortKey(tier),
|
|
22668
22845
|
...extras
|
|
22669
22846
|
};
|
|
22670
22847
|
}
|
|
@@ -22674,15 +22851,17 @@ function tieredSizeCurve(tierExpr, stops) {
|
|
|
22674
22851
|
if (i > 0) {
|
|
22675
22852
|
const [pz, pv] = stops[i - 1];
|
|
22676
22853
|
const [nz, nv] = stops[i];
|
|
22677
|
-
if (pv.some((v, t) => v === 0 && nv[t] > 0)) {
|
|
22854
|
+
if (pv.some((v, t) => v === 0 && nv[t] > 0 || v > 0 && nv[t] === 0)) {
|
|
22678
22855
|
const hz = Math.round((nz - 0.2) * 10) / 10;
|
|
22679
22856
|
if (hz > pz) {
|
|
22680
22857
|
const f = (hz - pz) / (nz - pz);
|
|
22681
22858
|
withHolds.push([
|
|
22682
22859
|
hz,
|
|
22683
|
-
pv.map(
|
|
22684
|
-
(v
|
|
22685
|
-
|
|
22860
|
+
pv.map((v, t) => {
|
|
22861
|
+
if (v === 0 && nv[t] > 0) return 0;
|
|
22862
|
+
if (v > 0 && nv[t] === 0) return v;
|
|
22863
|
+
return Math.round((v + (nv[t] - v) * f) * 10) / 10;
|
|
22864
|
+
})
|
|
22686
22865
|
]);
|
|
22687
22866
|
}
|
|
22688
22867
|
}
|
|
@@ -22711,6 +22890,19 @@ var ORIENTATION_HALO_WIDTH = [
|
|
|
22711
22890
|
16,
|
|
22712
22891
|
0.7
|
|
22713
22892
|
];
|
|
22893
|
+
var LAND_HALO_WIDTH = [
|
|
22894
|
+
"interpolate",
|
|
22895
|
+
["linear"],
|
|
22896
|
+
["zoom"],
|
|
22897
|
+
8,
|
|
22898
|
+
1.6,
|
|
22899
|
+
11,
|
|
22900
|
+
1.7,
|
|
22901
|
+
14,
|
|
22902
|
+
1.8,
|
|
22903
|
+
16,
|
|
22904
|
+
1.8
|
|
22905
|
+
];
|
|
22714
22906
|
var LABEL_BEHAVIOR = {
|
|
22715
22907
|
// Orientation labels — render once across all bands.
|
|
22716
22908
|
SEAARE: "orientation-cross-band",
|
|
@@ -22784,6 +22976,9 @@ var LABEL_PRIORITY = {
|
|
|
22784
22976
|
};
|
|
22785
22977
|
var DEFAULT_LABEL_PRIORITY = 450;
|
|
22786
22978
|
var ORIENTATION_LABEL_PRIORITY = 900;
|
|
22979
|
+
var MAJOR_WATER_TIER_MAX = 3;
|
|
22980
|
+
var MAJOR_WATER_LABEL_PRIORITY = 280;
|
|
22981
|
+
var ISLAND_LABEL_HOIST_MINZOOM = 12;
|
|
22787
22982
|
function getLabelPriority(layerName) {
|
|
22788
22983
|
if (getLabelBehavior(layerName) === "orientation-cross-band") {
|
|
22789
22984
|
return ORIENTATION_LABEL_PRIORITY;
|
|
@@ -22956,21 +23151,25 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
22956
23151
|
// LNDRGN labels until coastal zoom. The Carto basemap already labels
|
|
22957
23152
|
// major land regions at z5-z6. Columns: t1 major region, t2 local
|
|
22958
23153
|
// point (also the default).
|
|
23154
|
+
// Tier 2 (local point: "Bird Island", "Cottage Park") gets the same
|
|
23155
|
+
// lift as LNDARE's small islands — it is the other half of "show me
|
|
23156
|
+
// what this land is". Tier 1 is unchanged.
|
|
22959
23157
|
textSize: tieredSizeCurve(tierExpr, [
|
|
22960
23158
|
[5, [0, 0]],
|
|
22961
23159
|
[7, [11, 0]],
|
|
22962
23160
|
[9, [12, 0]],
|
|
22963
|
-
[11, [12,
|
|
22964
|
-
[13, [11, 11]],
|
|
22965
|
-
[16, [10,
|
|
23161
|
+
[11, [12, 10]],
|
|
23162
|
+
[13, [11.5, 11.5]],
|
|
23163
|
+
[16, [10.5, 11.5]]
|
|
22966
23164
|
]),
|
|
22967
23165
|
textColor: DARK_BG,
|
|
22968
23166
|
textHaloColor: COLOR.LAND_REGION,
|
|
22969
|
-
|
|
23167
|
+
// Land halo, not the shared taper — see LAND_HALO_WIDTH.
|
|
23168
|
+
textHaloWidth: LAND_HALO_WIDTH,
|
|
22970
23169
|
textOffset: [0, 0],
|
|
22971
23170
|
textFont: ["Open Sans Regular"],
|
|
22972
23171
|
minzoom: 5,
|
|
22973
|
-
layout: orientationLabelLayout()
|
|
23172
|
+
layout: orientationLabelLayout({}, tierExpr)
|
|
22974
23173
|
};
|
|
22975
23174
|
}
|
|
22976
23175
|
case "LNDMRK":
|
|
@@ -23135,14 +23334,56 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23135
23334
|
// at approach scale (z9) when the user has zoomed in enough that
|
|
23136
23335
|
// individual banks are useful context. Columns: t1 ocean, t2 sea/gulf,
|
|
23137
23336
|
// t3 bay/sound, t4 anchorage, default.
|
|
23337
|
+
// ── The zoom ladder (owner, 2026-08-31) ────────────────────────────
|
|
23338
|
+
//
|
|
23339
|
+
// "When zoomed in I want water area labels (Nubble Channel etc.); as
|
|
23340
|
+
// I zoom out, the major stuff."
|
|
23341
|
+
//
|
|
23342
|
+
// The old curve gave EVERY tier a size at z9-z11, and the field that
|
|
23343
|
+
// produced is the defect. Rendered in isolation at Boston z10.4
|
|
23344
|
+
// (prod 48404b1d): ten copies of "North Atlantic Ocean" scattered
|
|
23345
|
+
// across the harbour, a dozen ledges and shoals — Finns, Boston,
|
|
23346
|
+
// Thieves, Ultonia, Harding, Tinkers, Dread, Newcomb, Three and
|
|
23347
|
+
// One-Half Fathom — and, competing with all of them for the same
|
|
23348
|
+
// pixels, the bays the user actually needs: Broad Sound, Dorchester,
|
|
23349
|
+
// Quincy, Hull, Hingham, President Roads. Nothing was missing. The
|
|
23350
|
+
// majors simply had no better claim on the space than a ledge did.
|
|
23351
|
+
//
|
|
23352
|
+
// So the ladder now steps tiers OUT as well as in:
|
|
23353
|
+
//
|
|
23354
|
+
// t1 ocean overview only — gone by z11. "North Atlantic
|
|
23355
|
+
// Ocean" inside Boston Harbor is noise, not context.
|
|
23356
|
+
// t2 sea/gulf through approach — gone by z13.
|
|
23357
|
+
// t3 bay/sound the spine: visible z5 through z16, peaking where
|
|
23358
|
+
// a bay IS the answer to "where am I".
|
|
23359
|
+
// t4 anchorage harbour detail, z13+ (unchanged).
|
|
23360
|
+
// t5 default channels, coves, ledges. Floor UNCHANGED at z9 —
|
|
23361
|
+
// and deliberately so. "Nubble Channel" is already
|
|
23362
|
+
// a z11 feature without touching this curve: NOAA
|
|
23363
|
+
// encodes it only in band-5 cells (US5BOSCD ×2,
|
|
23364
|
+
// US5BOSCE), band-5 features first exist in tiles at
|
|
23365
|
+
// z11, and the cell-band fan floors their label
|
|
23366
|
+
// variant at z11 too. The owner's "channels/coves at
|
|
23367
|
+
// z11+" is therefore satisfied by the DATA, and
|
|
23368
|
+
// moving this floor would only have cost the
|
|
23369
|
+
// offshore relief its approach-zoom entry (Georges
|
|
23370
|
+
// Bank, Hudson Canyon — the population this tier was
|
|
23371
|
+
// built for, pinned by seaare-tier.test.ts).
|
|
23372
|
+
//
|
|
23373
|
+
// What crowds the bays at z10 is fixed by RANK, not by floors:
|
|
23374
|
+
// `orientationSortKey` now places tier 3 ahead of tier 5 inside a
|
|
23375
|
+
// band, and the padding ladder below thins the field so that
|
|
23376
|
+
// ordering decides the survivors.
|
|
23377
|
+
//
|
|
23378
|
+
// Columns: t1 ocean, t2 sea/gulf, t3 bay/sound, t4 anchorage, default.
|
|
23138
23379
|
textSize: tieredSizeCurve(tierExpr, [
|
|
23139
23380
|
[3, [14, 8, 0, 0, 0]],
|
|
23140
23381
|
[5, [16, 13, 9, 0, 0]],
|
|
23141
23382
|
[7, [14, 14, 12, 0, 0]],
|
|
23142
|
-
[9, [
|
|
23143
|
-
[11, [
|
|
23144
|
-
[13, [
|
|
23145
|
-
[16, [
|
|
23383
|
+
[9, [11, 13, 14, 0, 9]],
|
|
23384
|
+
[11, [0, 11, 14, 0, 11]],
|
|
23385
|
+
[13, [0, 0, 13, 10, 11]],
|
|
23386
|
+
[16, [0, 0, 12, 11, 10]]
|
|
23146
23387
|
]),
|
|
23147
23388
|
textColor: DARK_BG,
|
|
23148
23389
|
textHaloColor: COLOR.WATER_DEEP,
|
|
@@ -23166,8 +23407,41 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23166
23407
|
// the tiles and replaces this fan outright — do not restore
|
|
23167
23408
|
// always-paint on a starvation report; flip `labels: "placename"`
|
|
23168
23409
|
// instead once the build carries the layer.
|
|
23169
|
-
|
|
23170
|
-
|
|
23410
|
+
//
|
|
23411
|
+
// ── The padding is the zoom LADDER (owner, 2026-08-31) ───────────
|
|
23412
|
+
//
|
|
23413
|
+
// "When zoomed in I want water area labels (Nubble Channel etc.);
|
|
23414
|
+
// as I zoom out, the major stuff."
|
|
23415
|
+
//
|
|
23416
|
+
// A flat 2 px moat gives every name in view the same claim on the
|
|
23417
|
+
// space, so at z10 all 34 band-4 names in one Boston tile compete
|
|
23418
|
+
// as peers and the result reads as clutter rather than as
|
|
23419
|
+
// orientation. Widening the moat as the user zooms OUT thins that
|
|
23420
|
+
// field by collision alone: with a 20 px moat only a few names
|
|
23421
|
+
// survive per screen, and `orientationSortKey` decides which —
|
|
23422
|
+
// lowest band first, then lowest class tier, i.e. the bays and
|
|
23423
|
+
// sounds. Narrowing it back to 2 px by z13 restores exactly
|
|
23424
|
+
// today's harbour behaviour, where the channels and coves should
|
|
23425
|
+
// all be readable.
|
|
23426
|
+
//
|
|
23427
|
+
// Same mechanism (and the same lesson) as the lake-label grid:
|
|
23428
|
+
// a flat padding let six copies of "Lake Ontario" crowd z8.9.
|
|
23429
|
+
// Nothing is filtered and no name is invented — the ladder is
|
|
23430
|
+
// entirely a collision-space decision.
|
|
23431
|
+
"text-padding": [
|
|
23432
|
+
"interpolate",
|
|
23433
|
+
["linear"],
|
|
23434
|
+
["zoom"],
|
|
23435
|
+
5,
|
|
23436
|
+
20,
|
|
23437
|
+
9,
|
|
23438
|
+
14,
|
|
23439
|
+
11,
|
|
23440
|
+
8,
|
|
23441
|
+
13,
|
|
23442
|
+
2
|
|
23443
|
+
]
|
|
23444
|
+
}, tierExpr)
|
|
23171
23445
|
};
|
|
23172
23446
|
}
|
|
23173
23447
|
case "LAKARE": {
|
|
@@ -23188,7 +23462,7 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23188
23462
|
textOffset: [0, 0],
|
|
23189
23463
|
textFont: ["Open Sans Italic"],
|
|
23190
23464
|
minzoom: 7,
|
|
23191
|
-
layout: orientationLabelLayout()
|
|
23465
|
+
layout: orientationLabelLayout({}, tierExpr)
|
|
23192
23466
|
};
|
|
23193
23467
|
}
|
|
23194
23468
|
case "RIVERS": {
|
|
@@ -23209,7 +23483,7 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23209
23483
|
textOffset: [0, 0],
|
|
23210
23484
|
textFont: ["Open Sans Italic"],
|
|
23211
23485
|
minzoom: 7,
|
|
23212
|
-
layout: orientationLabelLayout()
|
|
23486
|
+
layout: orientationLabelLayout({}, tierExpr)
|
|
23213
23487
|
};
|
|
23214
23488
|
}
|
|
23215
23489
|
case "CANALS":
|
|
@@ -23240,21 +23514,31 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23240
23514
|
return {
|
|
23241
23515
|
type: "symbol",
|
|
23242
23516
|
textField: ["get", "OBJNAM"],
|
|
23517
|
+
// Tier 2 is the SMALL island — the one the owner cannot identify.
|
|
23518
|
+
// Its old curve peaked at 10 px and fell to 10 px, i.e. it never got
|
|
23519
|
+
// its own scale: a name that only exists because you zoomed in was
|
|
23520
|
+
// rendered at the size used for ambient context. It now grows through
|
|
23521
|
+
// harbour zoom (10 → 11.5) while tier 1, the major island already
|
|
23522
|
+
// legible from coastal zoom, is left almost where it was. Small
|
|
23523
|
+
// absolute numbers on purpose: the ask was "clear", not "large" —
|
|
23524
|
+
// the legibility comes mostly from LAND_HALO_WIDTH and the opacity
|
|
23525
|
+
// scale, both of which cost no extra footprint.
|
|
23243
23526
|
textSize: tieredSizeCurve(tierExpr, [
|
|
23244
23527
|
[5, [8, 0]],
|
|
23245
23528
|
[7, [10, 0]],
|
|
23246
23529
|
[9, [11, 0]],
|
|
23247
|
-
[11, [11,
|
|
23248
|
-
[13, [
|
|
23249
|
-
[16, [
|
|
23530
|
+
[11, [11.5, 10]],
|
|
23531
|
+
[13, [11.5, 11.5]],
|
|
23532
|
+
[16, [10.5, 11.5]]
|
|
23250
23533
|
]),
|
|
23251
23534
|
textColor: DARK_BG,
|
|
23252
23535
|
textHaloColor: COLOR.LAND_NATURAL,
|
|
23253
|
-
|
|
23536
|
+
// Land halo, not the shared taper — see LAND_HALO_WIDTH.
|
|
23537
|
+
textHaloWidth: LAND_HALO_WIDTH,
|
|
23254
23538
|
textOffset: [0, 0],
|
|
23255
23539
|
textFont: ["Open Sans Regular"],
|
|
23256
23540
|
minzoom: 5,
|
|
23257
|
-
layout: orientationLabelLayout()
|
|
23541
|
+
layout: orientationLabelLayout({}, tierExpr)
|
|
23258
23542
|
};
|
|
23259
23543
|
}
|
|
23260
23544
|
case "BUAARE": {
|
|
@@ -23279,7 +23563,7 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23279
23563
|
minzoom: 3,
|
|
23280
23564
|
layout: orientationLabelLayout({
|
|
23281
23565
|
"text-letter-spacing": 0.05
|
|
23282
|
-
})
|
|
23566
|
+
}, tierExpr)
|
|
23283
23567
|
};
|
|
23284
23568
|
}
|
|
23285
23569
|
case "BRIDGE":
|
|
@@ -23371,7 +23655,7 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23371
23655
|
textOffset: [0, 0],
|
|
23372
23656
|
textFont: ["Open Sans Regular"],
|
|
23373
23657
|
minzoom: 7,
|
|
23374
|
-
layout: orientationLabelLayout()
|
|
23658
|
+
layout: orientationLabelLayout({}, tierExpr)
|
|
23375
23659
|
};
|
|
23376
23660
|
}
|
|
23377
23661
|
case "SBDARE":
|
|
@@ -25755,7 +26039,7 @@ var S52_POINT_SVGS = {
|
|
|
25755
26039
|
"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',
|
|
25756
26040
|
"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',
|
|
25757
26041
|
"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',
|
|
25758
|
-
"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 -
|
|
26042
|
+
"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',
|
|
25759
26043
|
"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',
|
|
25760
26044
|
"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',
|
|
25761
26045
|
"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',
|
|
@@ -26270,6 +26554,8 @@ var CHRT_GLYPH_SVGS = {
|
|
|
26270
26554
|
"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>',
|
|
26271
26555
|
"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>',
|
|
26272
26556
|
"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>',
|
|
26557
|
+
"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>',
|
|
26558
|
+
"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>',
|
|
26273
26559
|
"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>',
|
|
26274
26560
|
"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>',
|
|
26275
26561
|
"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>',
|
|
@@ -26718,59 +27004,6 @@ function iconExpressionForFamily(objectClass, family) {
|
|
|
26718
27004
|
}
|
|
26719
27005
|
}
|
|
26720
27006
|
|
|
26721
|
-
// src/atlas/tile-zoom.ts
|
|
26722
|
-
var BUILD_MINZOOM = 0;
|
|
26723
|
-
var BUILD_MAXZOOM = 14;
|
|
26724
|
-
|
|
26725
|
-
// src/atlas/source-registry.ts
|
|
26726
|
-
var LAND_REGION = "CHRT_LAND";
|
|
26727
|
-
var RELIEF_REGION = "CHRT_RELIEF";
|
|
26728
|
-
var RELIEF_MINZOOM = 8;
|
|
26729
|
-
var RELIEF_MAXZOOM = 13;
|
|
26730
|
-
var RELIEF_TILE_SIZE = 512;
|
|
26731
|
-
var DEFAULT_TILE_BASE_URL2 = "https://tiles.chrt.co";
|
|
26732
|
-
var DEFAULT_REGION = "NOAA_ENC";
|
|
26733
|
-
function buildSources(opts = {}) {
|
|
26734
|
-
const mode = opts.mode ?? "pmtiles";
|
|
26735
|
-
const region = opts.region ?? DEFAULT_REGION;
|
|
26736
|
-
if (mode === "mapbox") {
|
|
26737
|
-
return {
|
|
26738
|
-
[region]: {
|
|
26739
|
-
type: "vector",
|
|
26740
|
-
url: `mapbox://chrt.${region.toLowerCase()}`
|
|
26741
|
-
}
|
|
26742
|
-
};
|
|
26743
|
-
}
|
|
26744
|
-
const base = (opts.tileBaseUrl ?? DEFAULT_TILE_BASE_URL2).replace(/\/+$/, "");
|
|
26745
|
-
const sources = {
|
|
26746
|
-
[region]: {
|
|
26747
|
-
type: "vector",
|
|
26748
|
-
tiles: [`${base}/v1/tiles/${region}/{z}/{x}/{y}.mvt`],
|
|
26749
|
-
minzoom: BUILD_MINZOOM,
|
|
26750
|
-
maxzoom: BUILD_MAXZOOM
|
|
26751
|
-
}
|
|
26752
|
-
};
|
|
26753
|
-
if (opts.land) {
|
|
26754
|
-
sources[LAND_REGION] = {
|
|
26755
|
-
type: "vector",
|
|
26756
|
-
tiles: [`${base}/v1/tiles/${LAND_REGION}/{z}/{x}/{y}.mvt`],
|
|
26757
|
-
minzoom: BUILD_MINZOOM,
|
|
26758
|
-
maxzoom: BUILD_MAXZOOM
|
|
26759
|
-
};
|
|
26760
|
-
}
|
|
26761
|
-
if (opts.relief) {
|
|
26762
|
-
sources[RELIEF_REGION] = {
|
|
26763
|
-
type: "raster-dem",
|
|
26764
|
-
tiles: [`${base}/v1/tiles/${RELIEF_REGION}/{z}/{x}/{y}.png`],
|
|
26765
|
-
encoding: "terrarium",
|
|
26766
|
-
tileSize: RELIEF_TILE_SIZE,
|
|
26767
|
-
minzoom: RELIEF_MINZOOM,
|
|
26768
|
-
maxzoom: RELIEF_MAXZOOM
|
|
26769
|
-
};
|
|
26770
|
-
}
|
|
26771
|
-
return sources;
|
|
26772
|
-
}
|
|
26773
|
-
|
|
26774
27007
|
// src/atlas/layer-builder.ts
|
|
26775
27008
|
var SCAMIN_DECLUTTER_CLASSES = /* @__PURE__ */ new Set([
|
|
26776
27009
|
"OFSPLF",
|
|
@@ -27098,7 +27331,27 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
|
|
|
27098
27331
|
layer.layout = {
|
|
27099
27332
|
...layer.layout,
|
|
27100
27333
|
"icon-allow-overlap": true,
|
|
27101
|
-
"icon-ignore-placement": true
|
|
27334
|
+
"icon-ignore-placement": true,
|
|
27335
|
+
// Flare-specific size (owner ask 2026-08-31: "make the flares a
|
|
27336
|
+
// little smaller — they should scale down a lot as you zoom out").
|
|
27337
|
+
// The safety-critical curve serves buoy/beacon HULLS, whose set
|
|
27338
|
+
// this overrides for LIGHTS only: flares are annotations on a
|
|
27339
|
+
// point, not shapes a mariner ranges against, so they start small
|
|
27340
|
+
// at planning scales and only approach full size at berthing
|
|
27341
|
+
// zooms. Applied after the safety-critical spread so it wins.
|
|
27342
|
+
"icon-size": [
|
|
27343
|
+
"interpolate",
|
|
27344
|
+
["linear"],
|
|
27345
|
+
["zoom"],
|
|
27346
|
+
6,
|
|
27347
|
+
0.3,
|
|
27348
|
+
12,
|
|
27349
|
+
0.42,
|
|
27350
|
+
14,
|
|
27351
|
+
0.6,
|
|
27352
|
+
18,
|
|
27353
|
+
1
|
|
27354
|
+
]
|
|
27102
27355
|
};
|
|
27103
27356
|
const valnmrNum = ["to-number", ["coalesce", ["get", "VALNMR"], ["get", "valnmr"], 0], 0];
|
|
27104
27357
|
const isMajor = [">=", valnmrNum, 10];
|
|
@@ -28063,85 +28316,265 @@ function getOrderedClasses() {
|
|
|
28063
28316
|
return ordered;
|
|
28064
28317
|
}
|
|
28065
28318
|
|
|
28066
|
-
// src/atlas/
|
|
28067
|
-
var
|
|
28068
|
-
|
|
28069
|
-
|
|
28070
|
-
|
|
28071
|
-
}
|
|
28072
|
-
|
|
28073
|
-
|
|
28074
|
-
base: "#e8d9b0",
|
|
28075
|
-
cover: { wood: "#cfd8b4", wetland: "#c9d3c4", urban: "#dedad2", sand: "#efe4c2" },
|
|
28076
|
-
contour: "#a89878",
|
|
28077
|
-
contourIndex: "#8a7a5c",
|
|
28078
|
-
road: "#d9c9a4",
|
|
28079
|
-
roadMajor: "#d2bd8e",
|
|
28080
|
-
label: "#5b4f3a",
|
|
28081
|
-
halo: "#f2ead6",
|
|
28082
|
-
shadow: "#6b5a3e",
|
|
28083
|
-
highlight: "#fffaf0",
|
|
28084
|
-
fade: "#e8f2f8",
|
|
28085
|
-
// Raised from 0.45 on 2026-08-23. ⚠ Above the ~0.5 that
|
|
28086
|
-
// hillshade-style.mjs warns "starts competing with depth areas" — a
|
|
28087
|
-
// deliberate, sighted choice made against rendered evidence, not a
|
|
28088
|
-
// default. If soundings or DEPARE ever read as fighting the terrain,
|
|
28089
|
-
// this number is the first thing to walk back.
|
|
28090
|
-
exaggeration: 0.8
|
|
28091
|
-
},
|
|
28092
|
-
DUSK: {
|
|
28093
|
-
base: "#4a4433",
|
|
28094
|
-
cover: { wood: "#414a38", wetland: "#3f4740", urban: "#4e4a42", sand: "#544c39" },
|
|
28095
|
-
contour: "#6b6250",
|
|
28096
|
-
contourIndex: "#837961",
|
|
28097
|
-
road: "#565040",
|
|
28098
|
-
roadMajor: "#63594440",
|
|
28099
|
-
label: "#c3b898",
|
|
28100
|
-
halo: "#2b2820",
|
|
28101
|
-
shadow: "#2e2a20",
|
|
28102
|
-
highlight: "#8a8068",
|
|
28103
|
-
fade: "#16202a",
|
|
28104
|
-
// Unchanged. DUSK was not the palette the 0.8 decision was made against.
|
|
28105
|
-
exaggeration: 0.45
|
|
28106
|
-
},
|
|
28107
|
-
NIGHT: {
|
|
28108
|
-
base: "#2a2620",
|
|
28109
|
-
cover: { wood: "#242a20", wetland: "#232823", urban: "#2c2925", sand: "#302b22" },
|
|
28110
|
-
contour: "#413b30",
|
|
28111
|
-
contourIndex: "#524a3b",
|
|
28112
|
-
road: "#332f27",
|
|
28113
|
-
roadMajor: "#3d382d",
|
|
28114
|
-
label: "#7d745e",
|
|
28115
|
-
halo: "#15130f",
|
|
28116
|
-
shadow: "#1a1712",
|
|
28117
|
-
highlight: "#4a4436",
|
|
28118
|
-
fade: "#07090b",
|
|
28119
|
-
// ⚠ DELIBERATELY NOT RAISED. See the type's note: a strong hillshade
|
|
28120
|
-
// under red night vision is a grey haze over exactly the contrast a
|
|
28121
|
-
// skipper is navigating by. Left at the shipped value rather than
|
|
28122
|
-
// lowered, because lowering it is a separate decision from this one.
|
|
28123
|
-
exaggeration: 0.45
|
|
28124
|
-
}
|
|
28319
|
+
// src/atlas/band-priority.ts
|
|
28320
|
+
var BAND_PRIORITY_RANGES = {
|
|
28321
|
+
"1": { minzoom: 0 },
|
|
28322
|
+
"2": { minzoom: 5 },
|
|
28323
|
+
"3": { minzoom: 7 },
|
|
28324
|
+
"4": { minzoom: 9 },
|
|
28325
|
+
"5": { minzoom: 11 },
|
|
28326
|
+
"6": { minzoom: 11 }
|
|
28125
28327
|
};
|
|
28126
|
-
var
|
|
28127
|
-
|
|
28128
|
-
|
|
28129
|
-
"
|
|
28130
|
-
|
|
28131
|
-
"
|
|
28132
|
-
|
|
28133
|
-
|
|
28134
|
-
|
|
28135
|
-
|
|
28136
|
-
|
|
28137
|
-
|
|
28138
|
-
|
|
28139
|
-
|
|
28140
|
-
|
|
28141
|
-
|
|
28142
|
-
|
|
28143
|
-
|
|
28144
|
-
|
|
28328
|
+
var BAND_DATA_MINZOOM = {
|
|
28329
|
+
"1": 0,
|
|
28330
|
+
"2": 5,
|
|
28331
|
+
"3": 7,
|
|
28332
|
+
"4": 9,
|
|
28333
|
+
"5": 11,
|
|
28334
|
+
"6": 13
|
|
28335
|
+
};
|
|
28336
|
+
function dominatedVariantMaxzoom(D) {
|
|
28337
|
+
return Math.max(BAND_PRIORITY_RANGES[D].minzoom, BAND_DATA_MINZOOM[D]);
|
|
28338
|
+
}
|
|
28339
|
+
var DOMINANCE_EXEMPT_FILL_CLASSES = /* @__PURE__ */ new Set([
|
|
28340
|
+
"LNDARE",
|
|
28341
|
+
"DEPARE",
|
|
28342
|
+
"UNSARE",
|
|
28343
|
+
"LAKARE",
|
|
28344
|
+
"DRGARE",
|
|
28345
|
+
"SEAARE"
|
|
28346
|
+
]);
|
|
28347
|
+
function isExemptOpaqueFill(layer) {
|
|
28348
|
+
if (layer.type !== "fill") return false;
|
|
28349
|
+
for (const cls of DOMINANCE_EXEMPT_FILL_CLASSES) {
|
|
28350
|
+
if (layer.id.startsWith(`s52-${cls}-`)) return true;
|
|
28351
|
+
}
|
|
28352
|
+
return false;
|
|
28353
|
+
}
|
|
28354
|
+
var FILL_FLOOR_FREE_BANDS = /* @__PURE__ */ new Set([
|
|
28355
|
+
"1",
|
|
28356
|
+
"2",
|
|
28357
|
+
"3"
|
|
28358
|
+
]);
|
|
28359
|
+
function bandVariantMinzoom(layer, B) {
|
|
28360
|
+
const classMinzoom = layer.minzoom ?? 0;
|
|
28361
|
+
if (isExemptOpaqueFill(layer) && FILL_FLOOR_FREE_BANDS.has(B)) {
|
|
28362
|
+
return classMinzoom;
|
|
28363
|
+
}
|
|
28364
|
+
return Math.max(classMinzoom, BAND_PRIORITY_RANGES[B].minzoom);
|
|
28365
|
+
}
|
|
28366
|
+
var GENERALISED_AREA_FILL_CLASSES = /* @__PURE__ */ new Set([
|
|
28367
|
+
"BUAARE",
|
|
28368
|
+
// 2026-08-28, New York Harbor: the "New York Marine Inspection Zone and
|
|
28369
|
+
// Captain of the Port Zone" RESARE exists at bands 2/3 as a generalized
|
|
28370
|
+
// shore-hugging polygon whose point-sampled dominance tag MISSED (the
|
|
28371
|
+
// Providence-BUAARE failure mode), so its magenta dashed boundary drew
|
|
28372
|
+
// OFFSET over the band-5 detail at z11.5 — read in the field as "a
|
|
28373
|
+
// magenta shoreline bleeding down from a less coarse band". The same
|
|
28374
|
+
// tile carried untagged band-2 ADMARE and CTNARE giants in the same
|
|
28375
|
+
// state. All three are area classes whose coarse-band boundaries hug
|
|
28376
|
+
// shorelines and whose fine-band copies are authoritative.
|
|
28377
|
+
"RESARE",
|
|
28378
|
+
"CTNARE",
|
|
28379
|
+
"ADMARE"
|
|
28380
|
+
]);
|
|
28381
|
+
var GENERALISED_AREA_MAX_BAND = 3;
|
|
28382
|
+
function generalisedAreaCeiling(layer, B) {
|
|
28383
|
+
if (layer.type !== "fill" && layer.type !== "line") return null;
|
|
28384
|
+
if (parseInt(B, 10) > GENERALISED_AREA_MAX_BAND) return null;
|
|
28385
|
+
for (const cls of GENERALISED_AREA_FILL_CLASSES) {
|
|
28386
|
+
if (layer.id.startsWith(`s52-${cls}-`)) return BAND_PRIORITY_RANGES["5"].minzoom;
|
|
28387
|
+
}
|
|
28388
|
+
return null;
|
|
28389
|
+
}
|
|
28390
|
+
var BAND_DRAW_ORDER = ["1", "2", "3", "4", "5", "6"];
|
|
28391
|
+
var CHART_BAND_CODES = {
|
|
28392
|
+
Overview: ["1"],
|
|
28393
|
+
General: ["2"],
|
|
28394
|
+
Coastal: ["3"],
|
|
28395
|
+
Approach: ["4"],
|
|
28396
|
+
Harbor: ["5", "6"]
|
|
28397
|
+
};
|
|
28398
|
+
var CHART_CASCADE_CODES = {
|
|
28399
|
+
Overview: ["1"],
|
|
28400
|
+
General: ["1", "2"],
|
|
28401
|
+
Coastal: ["1", "2", "3"],
|
|
28402
|
+
Approach: ["1", "2", "3", "4"],
|
|
28403
|
+
Harbor: ["1", "2", "3", "4", "5", "6"]
|
|
28404
|
+
};
|
|
28405
|
+
var CHRT_STYLE_MODES = [
|
|
28406
|
+
"continuous",
|
|
28407
|
+
...Object.keys(CHART_BAND_CODES).map(
|
|
28408
|
+
(band) => `chart:${band}`
|
|
28409
|
+
)
|
|
28410
|
+
];
|
|
28411
|
+
function normalizeStyleMode(raw) {
|
|
28412
|
+
if (raw === null || raw === void 0 || raw === "") return "continuous";
|
|
28413
|
+
const lower = raw.toLowerCase();
|
|
28414
|
+
return CHRT_STYLE_MODES.find((mode) => mode.toLowerCase() === lower) ?? null;
|
|
28415
|
+
}
|
|
28416
|
+
function styleModeBand(mode) {
|
|
28417
|
+
return mode === "continuous" ? null : mode.slice("chart:".length);
|
|
28418
|
+
}
|
|
28419
|
+
function bandFilterExpression(codes) {
|
|
28420
|
+
const list = Array.isArray(codes) ? codes : [codes];
|
|
28421
|
+
if (list.length === 1) {
|
|
28422
|
+
return ["==", ["slice", ["get", "_cell_id"], 2, 3], list[0]];
|
|
28423
|
+
}
|
|
28424
|
+
return ["in", ["slice", ["get", "_cell_id"], 2, 3], ["literal", [...list]]];
|
|
28425
|
+
}
|
|
28426
|
+
function combineWithBandFilter(baseFilter, bandFilter) {
|
|
28427
|
+
if (!baseFilter) return bandFilter;
|
|
28428
|
+
if (baseFilter[0] === "all") {
|
|
28429
|
+
return ["all", ...baseFilter.slice(1), bandFilter];
|
|
28430
|
+
}
|
|
28431
|
+
return ["all", baseFilter, bandFilter];
|
|
28432
|
+
}
|
|
28433
|
+
var NOT_DOMINATED_FILTER = [
|
|
28434
|
+
"all",
|
|
28435
|
+
["!", ["has", "_dominated_by_min"]],
|
|
28436
|
+
["!=", ["get", "_dominated"], true]
|
|
28437
|
+
];
|
|
28438
|
+
function dominatedByMinFilter(D) {
|
|
28439
|
+
const dInt = parseInt(D, 10);
|
|
28440
|
+
if (D === "6") {
|
|
28441
|
+
return [
|
|
28442
|
+
"any",
|
|
28443
|
+
["==", ["get", "_dominated_by_min"], dInt],
|
|
28444
|
+
["==", ["get", "_dominated"], true]
|
|
28445
|
+
];
|
|
28446
|
+
}
|
|
28447
|
+
return ["==", ["get", "_dominated_by_min"], dInt];
|
|
28448
|
+
}
|
|
28449
|
+
function withinBboxFilter(bbox) {
|
|
28450
|
+
const [w, s] = bbox.sw;
|
|
28451
|
+
const [e, n] = bbox.ne;
|
|
28452
|
+
return [
|
|
28453
|
+
"within",
|
|
28454
|
+
{
|
|
28455
|
+
type: "Polygon",
|
|
28456
|
+
coordinates: [
|
|
28457
|
+
[
|
|
28458
|
+
[w, s],
|
|
28459
|
+
[e, s],
|
|
28460
|
+
[e, n],
|
|
28461
|
+
[w, n],
|
|
28462
|
+
[w, s]
|
|
28463
|
+
]
|
|
28464
|
+
]
|
|
28465
|
+
}
|
|
28466
|
+
];
|
|
28467
|
+
}
|
|
28468
|
+
function worldMinusBboxPolygon(bbox) {
|
|
28469
|
+
const [w, s] = bbox.sw;
|
|
28470
|
+
const [e, n] = bbox.ne;
|
|
28471
|
+
return {
|
|
28472
|
+
type: "Polygon",
|
|
28473
|
+
coordinates: [
|
|
28474
|
+
[
|
|
28475
|
+
[-180, -85],
|
|
28476
|
+
[180, -85],
|
|
28477
|
+
[180, 85],
|
|
28478
|
+
[-180, 85],
|
|
28479
|
+
[-180, -85]
|
|
28480
|
+
],
|
|
28481
|
+
// Inner ring (hole) — winding opposite to outer ring per GeoJSON
|
|
28482
|
+
// spec. MapLibre is permissive but we follow strict winding.
|
|
28483
|
+
[
|
|
28484
|
+
[w, s],
|
|
28485
|
+
[w, n],
|
|
28486
|
+
[e, n],
|
|
28487
|
+
[e, s],
|
|
28488
|
+
[w, s]
|
|
28489
|
+
]
|
|
28490
|
+
]
|
|
28491
|
+
};
|
|
28492
|
+
}
|
|
28493
|
+
|
|
28494
|
+
// src/atlas/land-layers.ts
|
|
28495
|
+
var LAND_SURFACE_MINZOOM = BAND_PRIORITY_RANGES["5"].minzoom;
|
|
28496
|
+
var LAND_SURFACE_LAYERS = (id) => id === "chrtland-base" || id === "chrtland-cover" || id === "chrtland-contour" || id === "chrtland-hillshade" || id.startsWith("chrtland-fade-");
|
|
28497
|
+
var FADE_PX = 80;
|
|
28498
|
+
var FADE_BANDS = 16;
|
|
28499
|
+
function fadeAlpha(r) {
|
|
28500
|
+
return Math.max(0, Math.min(1, (FADE_PX / 2 - Math.abs(r)) / FADE_PX));
|
|
28501
|
+
}
|
|
28502
|
+
function landBaseColor(palette) {
|
|
28503
|
+
return (LAND_THEMES[palette] ?? LAND_THEMES.DAY).base;
|
|
28504
|
+
}
|
|
28505
|
+
var LAND_THEMES = {
|
|
28506
|
+
DAY: {
|
|
28507
|
+
base: "#e8d9b0",
|
|
28508
|
+
cover: { wood: "#cfd8b4", wetland: "#c9d3c4", urban: "#dedad2", sand: "#efe4c2" },
|
|
28509
|
+
contour: "#a89878",
|
|
28510
|
+
contourIndex: "#8a7a5c",
|
|
28511
|
+
road: "#d9c9a4",
|
|
28512
|
+
roadMajor: "#d2bd8e",
|
|
28513
|
+
label: "#5b4f3a",
|
|
28514
|
+
halo: "#f2ead6",
|
|
28515
|
+
shadow: "#6b5a3e",
|
|
28516
|
+
highlight: "#fffaf0",
|
|
28517
|
+
fade: "#e8f2f8",
|
|
28518
|
+
// Raised from 0.45 on 2026-08-23. ⚠ Above the ~0.5 that
|
|
28519
|
+
// hillshade-style.mjs warns "starts competing with depth areas" — a
|
|
28520
|
+
// deliberate, sighted choice made against rendered evidence, not a
|
|
28521
|
+
// default. If soundings or DEPARE ever read as fighting the terrain,
|
|
28522
|
+
// this number is the first thing to walk back.
|
|
28523
|
+
exaggeration: 0.8
|
|
28524
|
+
},
|
|
28525
|
+
DUSK: {
|
|
28526
|
+
base: "#4a4433",
|
|
28527
|
+
cover: { wood: "#414a38", wetland: "#3f4740", urban: "#4e4a42", sand: "#544c39" },
|
|
28528
|
+
contour: "#6b6250",
|
|
28529
|
+
contourIndex: "#837961",
|
|
28530
|
+
road: "#565040",
|
|
28531
|
+
roadMajor: "#63594440",
|
|
28532
|
+
label: "#c3b898",
|
|
28533
|
+
halo: "#2b2820",
|
|
28534
|
+
shadow: "#2e2a20",
|
|
28535
|
+
highlight: "#8a8068",
|
|
28536
|
+
fade: "#16202a",
|
|
28537
|
+
// Unchanged. DUSK was not the palette the 0.8 decision was made against.
|
|
28538
|
+
exaggeration: 0.45
|
|
28539
|
+
},
|
|
28540
|
+
NIGHT: {
|
|
28541
|
+
base: "#2a2620",
|
|
28542
|
+
cover: { wood: "#242a20", wetland: "#232823", urban: "#2c2925", sand: "#302b22" },
|
|
28543
|
+
contour: "#413b30",
|
|
28544
|
+
contourIndex: "#524a3b",
|
|
28545
|
+
road: "#332f27",
|
|
28546
|
+
roadMajor: "#3d382d",
|
|
28547
|
+
label: "#7d745e",
|
|
28548
|
+
halo: "#15130f",
|
|
28549
|
+
shadow: "#1a1712",
|
|
28550
|
+
highlight: "#4a4436",
|
|
28551
|
+
fade: "#07090b",
|
|
28552
|
+
// ⚠ DELIBERATELY NOT RAISED. See the type's note: a strong hillshade
|
|
28553
|
+
// under red night vision is a grey haze over exactly the contrast a
|
|
28554
|
+
// skipper is navigating by. Left at the shipped value rather than
|
|
28555
|
+
// lowered, because lowering it is a separate decision from this one.
|
|
28556
|
+
exaggeration: 0.45
|
|
28557
|
+
}
|
|
28558
|
+
};
|
|
28559
|
+
var FITS = ["<=", ["coalesce", ["get", "min_z"], 0], ["zoom"]];
|
|
28560
|
+
var ANCHOR = ["coalesce", ["get", "anchor"], "top"];
|
|
28561
|
+
var anchorOffset = (em) => [
|
|
28562
|
+
"match",
|
|
28563
|
+
["get", "anchor"],
|
|
28564
|
+
"left",
|
|
28565
|
+
["literal", [0.6, 0]],
|
|
28566
|
+
"right",
|
|
28567
|
+
["literal", [-0.6, 0]],
|
|
28568
|
+
"bottom",
|
|
28569
|
+
["literal", [0, -em]],
|
|
28570
|
+
["literal", [0, em]]
|
|
28571
|
+
];
|
|
28572
|
+
var FONT = ["Open Sans Regular"];
|
|
28573
|
+
var FONT_ITALIC = ["Open Sans Italic"];
|
|
28574
|
+
function landLayers(opts = {}) {
|
|
28575
|
+
const palette = opts.palette ?? "DAY";
|
|
28576
|
+
const T = LAND_THEMES[palette] ?? LAND_THEMES.DAY;
|
|
28577
|
+
const src = opts.sourceId ?? LAND_REGION;
|
|
28145
28578
|
const demSrc = opts.reliefSourceId ?? RELIEF_REGION;
|
|
28146
28579
|
const L = (o) => o;
|
|
28147
28580
|
const layers = [
|
|
@@ -28179,6 +28612,8 @@ function landLayers(opts = {}) {
|
|
|
28179
28612
|
}));
|
|
28180
28613
|
if (opts.relief) {
|
|
28181
28614
|
layers.push(L({
|
|
28615
|
+
// No minzoom here: LAND_SURFACE_LAYERS floors it with the rest of the
|
|
28616
|
+
// surface, so the two can never drift apart again.
|
|
28182
28617
|
id: "chrtland-hillshade",
|
|
28183
28618
|
type: "hillshade",
|
|
28184
28619
|
source: demSrc,
|
|
@@ -28234,12 +28669,12 @@ function landLayers(opts = {}) {
|
|
|
28234
28669
|
}
|
|
28235
28670
|
}));
|
|
28236
28671
|
layers.push(L({
|
|
28237
|
-
id: "chrtland-
|
|
28672
|
+
id: "chrtland-city-major",
|
|
28238
28673
|
type: "symbol",
|
|
28239
28674
|
source: src,
|
|
28240
28675
|
"source-layer": "land_town",
|
|
28241
|
-
minzoom:
|
|
28242
|
-
filter:
|
|
28676
|
+
minzoom: 6.5,
|
|
28677
|
+
filter: [">=", ["coalesce", ["get", "prank"], 0], 10],
|
|
28243
28678
|
layout: {
|
|
28244
28679
|
"text-field": [
|
|
28245
28680
|
"case",
|
|
@@ -28248,211 +28683,526 @@ function landLayers(opts = {}) {
|
|
|
28248
28683
|
["get", "name"]
|
|
28249
28684
|
],
|
|
28250
28685
|
"text-font": FONT,
|
|
28251
|
-
"text-size": [
|
|
28252
|
-
|
|
28686
|
+
"text-size": [
|
|
28687
|
+
"interpolate",
|
|
28688
|
+
["linear"],
|
|
28689
|
+
["zoom"],
|
|
28690
|
+
6.5,
|
|
28691
|
+
11.5,
|
|
28692
|
+
9,
|
|
28693
|
+
["+", 10, ["*", 0.4, ["coalesce", ["get", "prank"], 10]]],
|
|
28694
|
+
13,
|
|
28695
|
+
["+", 13, ["*", 0.5, ["coalesce", ["get", "prank"], 10]]]
|
|
28696
|
+
],
|
|
28697
|
+
"text-letter-spacing": ["case", ["==", ["get", "upcase"], true], 0.14, 0.02],
|
|
28253
28698
|
"text-offset": anchorOffset(0.75),
|
|
28254
28699
|
"text-anchor": ANCHOR,
|
|
28255
|
-
"text-padding":
|
|
28700
|
+
"text-padding": 10,
|
|
28256
28701
|
"text-allow-overlap": false,
|
|
28257
|
-
"symbol-sort-key":
|
|
28702
|
+
"symbol-sort-key": ["*", -1, ["coalesce", ["get", "pop"], 0]]
|
|
28258
28703
|
},
|
|
28259
|
-
paint: { "text-color": T.label, "text-halo-color": T.halo, "text-halo-width": 1.
|
|
28704
|
+
paint: { "text-color": T.label, "text-halo-color": T.halo, "text-halo-width": 1.6 }
|
|
28260
28705
|
}));
|
|
28261
28706
|
layers.push(L({
|
|
28262
|
-
id: "chrtland-
|
|
28707
|
+
id: "chrtland-town-port",
|
|
28263
28708
|
type: "symbol",
|
|
28264
28709
|
source: src,
|
|
28265
|
-
"source-layer": "
|
|
28266
|
-
minzoom:
|
|
28267
|
-
filter: [
|
|
28710
|
+
"source-layer": "land_town",
|
|
28711
|
+
minzoom: 8.5,
|
|
28712
|
+
filter: [
|
|
28713
|
+
"all",
|
|
28714
|
+
["==", ["get", "enc_dup"], true],
|
|
28715
|
+
["<", ["coalesce", ["get", "prank"], 0], 10]
|
|
28716
|
+
],
|
|
28268
28717
|
layout: {
|
|
28269
|
-
"text-field": ["
|
|
28718
|
+
"text-field": ["get", "name"],
|
|
28270
28719
|
"text-font": FONT,
|
|
28271
|
-
"text-size": ["interpolate", ["linear"], ["zoom"],
|
|
28272
|
-
"text-offset": anchorOffset(0.
|
|
28720
|
+
"text-size": ["interpolate", ["linear"], ["zoom"], 8.5, 11, 12, 13.5],
|
|
28721
|
+
"text-offset": anchorOffset(0.75),
|
|
28273
28722
|
"text-anchor": ANCHOR,
|
|
28274
|
-
"text-padding":
|
|
28723
|
+
"text-padding": 8,
|
|
28275
28724
|
"text-allow-overlap": false,
|
|
28276
|
-
|
|
28725
|
+
// Within the tier, the bigger place wins the space: without this the
|
|
28726
|
+
// tie broke on data order and "West Gloucester" beat Gloucester.
|
|
28727
|
+
"symbol-sort-key": ["*", -1, ["coalesce", ["get", "pop"], 0]]
|
|
28277
28728
|
},
|
|
28278
|
-
paint: { "text-color": T.label, "text-halo-color": T.halo, "text-halo-width": 1.
|
|
28729
|
+
paint: { "text-color": T.label, "text-halo-color": T.halo, "text-halo-width": 1.4 }
|
|
28279
28730
|
}));
|
|
28280
28731
|
layers.push(L({
|
|
28281
|
-
id: "chrtland-
|
|
28732
|
+
id: "chrtland-town",
|
|
28282
28733
|
type: "symbol",
|
|
28283
28734
|
source: src,
|
|
28284
|
-
"source-layer": "
|
|
28285
|
-
minzoom:
|
|
28286
|
-
filter:
|
|
28735
|
+
"source-layer": "land_town",
|
|
28736
|
+
minzoom: 9,
|
|
28737
|
+
filter: [
|
|
28738
|
+
"all",
|
|
28739
|
+
FITS,
|
|
28740
|
+
["<", ["coalesce", ["get", "prank"], 0], 10],
|
|
28741
|
+
["!=", ["get", "enc_dup"], true]
|
|
28742
|
+
],
|
|
28287
28743
|
layout: {
|
|
28288
|
-
"text-field": [
|
|
28289
|
-
|
|
28290
|
-
|
|
28291
|
-
|
|
28744
|
+
"text-field": [
|
|
28745
|
+
"case",
|
|
28746
|
+
["==", ["get", "upcase"], true],
|
|
28747
|
+
["upcase", ["get", "name"]],
|
|
28748
|
+
["get", "name"]
|
|
28749
|
+
],
|
|
28750
|
+
"text-font": FONT,
|
|
28751
|
+
"text-size": ["interpolate", ["linear"], ["zoom"], 9, 10, 14, 13.5],
|
|
28752
|
+
"text-letter-spacing": ["case", ["==", ["get", "upcase"], true], 0.14, 0],
|
|
28753
|
+
"text-offset": anchorOffset(0.75),
|
|
28292
28754
|
"text-anchor": ANCHOR,
|
|
28293
|
-
"text-padding":
|
|
28755
|
+
"text-padding": 6,
|
|
28294
28756
|
"text-allow-overlap": false,
|
|
28295
|
-
"symbol-sort-key":
|
|
28757
|
+
"symbol-sort-key": 2
|
|
28296
28758
|
},
|
|
28297
28759
|
paint: { "text-color": T.label, "text-halo-color": T.halo, "text-halo-width": 1.2 }
|
|
28298
28760
|
}));
|
|
28299
|
-
|
|
28300
|
-
|
|
28301
|
-
|
|
28302
|
-
|
|
28303
|
-
|
|
28304
|
-
|
|
28305
|
-
|
|
28306
|
-
|
|
28307
|
-
|
|
28308
|
-
|
|
28309
|
-
|
|
28310
|
-
|
|
28311
|
-
|
|
28312
|
-
|
|
28313
|
-
|
|
28314
|
-
|
|
28315
|
-
|
|
28316
|
-
|
|
28317
|
-
|
|
28318
|
-
|
|
28319
|
-
|
|
28320
|
-
|
|
28321
|
-
|
|
28322
|
-
|
|
28323
|
-
|
|
28324
|
-
|
|
28325
|
-
|
|
28326
|
-
|
|
28327
|
-
|
|
28328
|
-
|
|
28329
|
-
|
|
28330
|
-
|
|
28331
|
-
|
|
28332
|
-
|
|
28333
|
-
|
|
28334
|
-
|
|
28335
|
-
|
|
28336
|
-
}
|
|
28337
|
-
|
|
28338
|
-
|
|
28339
|
-
|
|
28340
|
-
|
|
28341
|
-
]);
|
|
28342
|
-
function bandVariantMinzoom(layer, B) {
|
|
28343
|
-
const classMinzoom = layer.minzoom ?? 0;
|
|
28344
|
-
if (isExemptOpaqueFill(layer) && FILL_FLOOR_FREE_BANDS.has(B)) {
|
|
28345
|
-
return classMinzoom;
|
|
28346
|
-
}
|
|
28347
|
-
return Math.max(classMinzoom, BAND_PRIORITY_RANGES[B].minzoom);
|
|
28348
|
-
}
|
|
28349
|
-
var BAND_DRAW_ORDER = ["1", "2", "3", "4", "5", "6"];
|
|
28350
|
-
var CHART_BAND_CODES = {
|
|
28351
|
-
Overview: ["1"],
|
|
28352
|
-
General: ["2"],
|
|
28353
|
-
Coastal: ["3"],
|
|
28354
|
-
Approach: ["4"],
|
|
28355
|
-
Harbor: ["5", "6"]
|
|
28356
|
-
};
|
|
28357
|
-
var CHART_CASCADE_CODES = {
|
|
28358
|
-
Overview: ["1"],
|
|
28359
|
-
General: ["1", "2"],
|
|
28360
|
-
Coastal: ["1", "2", "3"],
|
|
28361
|
-
Approach: ["1", "2", "3", "4"],
|
|
28362
|
-
Harbor: ["1", "2", "3", "4", "5", "6"]
|
|
28363
|
-
};
|
|
28364
|
-
var CHRT_STYLE_MODES = [
|
|
28365
|
-
"continuous",
|
|
28366
|
-
...Object.keys(CHART_BAND_CODES).map(
|
|
28367
|
-
(band) => `chart:${band}`
|
|
28368
|
-
)
|
|
28369
|
-
];
|
|
28370
|
-
function normalizeStyleMode(raw) {
|
|
28371
|
-
if (raw === null || raw === void 0 || raw === "") return "continuous";
|
|
28372
|
-
const lower = raw.toLowerCase();
|
|
28373
|
-
return CHRT_STYLE_MODES.find((mode) => mode.toLowerCase() === lower) ?? null;
|
|
28374
|
-
}
|
|
28375
|
-
function styleModeBand(mode) {
|
|
28376
|
-
return mode === "continuous" ? null : mode.slice("chart:".length);
|
|
28377
|
-
}
|
|
28378
|
-
function bandFilterExpression(codes) {
|
|
28379
|
-
const list = Array.isArray(codes) ? codes : [codes];
|
|
28380
|
-
if (list.length === 1) {
|
|
28381
|
-
return ["==", ["slice", ["get", "_cell_id"], 2, 3], list[0]];
|
|
28382
|
-
}
|
|
28383
|
-
return ["in", ["slice", ["get", "_cell_id"], 2, 3], ["literal", [...list]]];
|
|
28384
|
-
}
|
|
28385
|
-
function combineWithBandFilter(baseFilter, bandFilter) {
|
|
28386
|
-
if (!baseFilter) return bandFilter;
|
|
28387
|
-
if (baseFilter[0] === "all") {
|
|
28388
|
-
return ["all", ...baseFilter.slice(1), bandFilter];
|
|
28761
|
+
layers.push(L({
|
|
28762
|
+
id: "chrtland-summit",
|
|
28763
|
+
type: "symbol",
|
|
28764
|
+
source: src,
|
|
28765
|
+
"source-layer": "land_summit",
|
|
28766
|
+
minzoom: 11.5,
|
|
28767
|
+
filter: ["all", FITS, [">=", ["get", "peak_m"], 40]],
|
|
28768
|
+
layout: {
|
|
28769
|
+
"text-field": ["concat", ["get", "name"], "\n", ["to-string", ["get", "peak_m"]], " m"],
|
|
28770
|
+
"text-font": FONT,
|
|
28771
|
+
"text-size": ["interpolate", ["linear"], ["zoom"], 11.5, 8.5, 14, 11],
|
|
28772
|
+
"text-offset": anchorOffset(0.8),
|
|
28773
|
+
"text-anchor": ANCHOR,
|
|
28774
|
+
"text-padding": 5,
|
|
28775
|
+
"text-allow-overlap": false,
|
|
28776
|
+
"symbol-sort-key": 3
|
|
28777
|
+
},
|
|
28778
|
+
paint: { "text-color": T.label, "text-halo-color": T.halo, "text-halo-width": 1.2 }
|
|
28779
|
+
}));
|
|
28780
|
+
layers.push(L({
|
|
28781
|
+
id: "chrtland-poi",
|
|
28782
|
+
type: "symbol",
|
|
28783
|
+
source: src,
|
|
28784
|
+
"source-layer": "land_poi",
|
|
28785
|
+
minzoom: 13,
|
|
28786
|
+
filter: FITS,
|
|
28787
|
+
layout: {
|
|
28788
|
+
"text-field": ["get", "name"],
|
|
28789
|
+
"text-font": FONT_ITALIC,
|
|
28790
|
+
"text-size": ["interpolate", ["linear"], ["zoom"], 13, 8, 14, 9.5],
|
|
28791
|
+
"text-offset": anchorOffset(0.7),
|
|
28792
|
+
"text-anchor": ANCHOR,
|
|
28793
|
+
"text-padding": 5,
|
|
28794
|
+
"text-allow-overlap": false,
|
|
28795
|
+
"symbol-sort-key": 5
|
|
28796
|
+
},
|
|
28797
|
+
paint: { "text-color": T.label, "text-halo-color": T.halo, "text-halo-width": 1.2 }
|
|
28798
|
+
}));
|
|
28799
|
+
for (const layer of layers) {
|
|
28800
|
+
const l = layer;
|
|
28801
|
+
if (!LAND_SURFACE_LAYERS(l.id)) continue;
|
|
28802
|
+
l.minzoom = Math.max(l.minzoom ?? 0, LAND_SURFACE_MINZOOM);
|
|
28389
28803
|
}
|
|
28390
|
-
return
|
|
28804
|
+
return layers;
|
|
28391
28805
|
}
|
|
28392
|
-
|
|
28393
|
-
|
|
28394
|
-
|
|
28395
|
-
|
|
28396
|
-
|
|
28397
|
-
|
|
28398
|
-
|
|
28399
|
-
|
|
28400
|
-
|
|
28401
|
-
|
|
28402
|
-
|
|
28403
|
-
|
|
28404
|
-
|
|
28806
|
+
|
|
28807
|
+
// src/atlas/base-layers.ts
|
|
28808
|
+
var NOCOVERAGE_PATTERN = "chrt:hatch-nocoverage";
|
|
28809
|
+
var UNCHARTED_PATTERN = "chrt:hatch-uncharted";
|
|
28810
|
+
var BASE_THEMES = {
|
|
28811
|
+
DAY: {
|
|
28812
|
+
earth: "#2A3439",
|
|
28813
|
+
water: "#1B2328",
|
|
28814
|
+
river: "#24313A",
|
|
28815
|
+
boundary: "#4A575E",
|
|
28816
|
+
jurisdiction: "#E8D9B0",
|
|
28817
|
+
// CHRTland's own tan — one land colour
|
|
28818
|
+
inlandWater: "#B9CFDD",
|
|
28819
|
+
// muted, unmistakably not a charted blue
|
|
28820
|
+
// State furniture. These three were referenced by the layers but missing
|
|
28821
|
+
// from this table (and its type — the long-standing TS2339 trio), so the
|
|
28822
|
+
// lines shipped on MapLibre's DEFAULT black. Now deliberate: a hairline
|
|
28823
|
+
// a shade deeper than the tan, quiet caps, tan halo.
|
|
28824
|
+
stateLine: "#C4B389",
|
|
28825
|
+
stateLabel: "#8E8468",
|
|
28826
|
+
stateHalo: "#E8D9B0",
|
|
28827
|
+
// Rough-lake furniture (owner ask 2026-08-28: "the lakes need labels,
|
|
28828
|
+
// and shorelines drawn"): a shoreline a shade deeper than the rough
|
|
28829
|
+
// water, and a name in the water's own voice, haloed by the water.
|
|
28830
|
+
inlandShore: "#8FA9BA",
|
|
28831
|
+
inlandLabel: "#54707F",
|
|
28832
|
+
inlandHalo: "#B9CFDD"
|
|
28833
|
+
},
|
|
28834
|
+
DUSK: {
|
|
28835
|
+
earth: "#232B30",
|
|
28836
|
+
water: "#161D21",
|
|
28837
|
+
river: "#1E2930",
|
|
28838
|
+
boundary: "#3E4A50",
|
|
28839
|
+
jurisdiction: "#C9BC96",
|
|
28840
|
+
inlandWater: "#93A9B7",
|
|
28841
|
+
stateLine: "#A6976F",
|
|
28842
|
+
stateLabel: "#776E55",
|
|
28843
|
+
stateHalo: "#C9BC96",
|
|
28844
|
+
inlandShore: "#74899A",
|
|
28845
|
+
inlandLabel: "#41586A",
|
|
28846
|
+
inlandHalo: "#93A9B7"
|
|
28847
|
+
},
|
|
28848
|
+
NIGHT: {
|
|
28849
|
+
earth: "#1A2125",
|
|
28850
|
+
water: "#10161A",
|
|
28851
|
+
river: "#182228",
|
|
28852
|
+
boundary: "#333E44",
|
|
28853
|
+
jurisdiction: "#8F866C",
|
|
28854
|
+
inlandWater: "#5F7280",
|
|
28855
|
+
stateLine: "#6B6350",
|
|
28856
|
+
stateLabel: "#565040",
|
|
28857
|
+
stateHalo: "#8F866C",
|
|
28858
|
+
inlandShore: "#4A5C68",
|
|
28859
|
+
inlandLabel: "#8CA2B0",
|
|
28860
|
+
inlandHalo: "#5F7280"
|
|
28405
28861
|
}
|
|
28406
|
-
|
|
28407
|
-
}
|
|
28408
|
-
|
|
28409
|
-
const [
|
|
28410
|
-
const
|
|
28862
|
+
};
|
|
28863
|
+
function baseLayers(opts = {}) {
|
|
28864
|
+
const palette = opts.palette ?? "DAY";
|
|
28865
|
+
const T = BASE_THEMES[palette] ?? BASE_THEMES.DAY;
|
|
28866
|
+
const src = opts.sourceId ?? BASE_REGION;
|
|
28867
|
+
const L = (o) => o;
|
|
28411
28868
|
return [
|
|
28412
|
-
|
|
28413
|
-
|
|
28414
|
-
|
|
28415
|
-
|
|
28416
|
-
|
|
28417
|
-
|
|
28418
|
-
|
|
28419
|
-
|
|
28420
|
-
|
|
28421
|
-
|
|
28422
|
-
|
|
28423
|
-
|
|
28424
|
-
|
|
28869
|
+
// 1. Geography — the world as it is, in the quiet dark voice. The OCEAN
|
|
28870
|
+
// is a background, not a polygon: the archive ships land and lakes,
|
|
28871
|
+
// and "everything else is sea" is exactly what a background expresses
|
|
28872
|
+
// — geometry-free, gap-free at every antimeridian and pole. Without
|
|
28873
|
+
// it the sea outside coverage was bare S-52 NODTA, a light grey that
|
|
28874
|
+
// read as "charted ground" with a hatch on it.
|
|
28875
|
+
L({
|
|
28876
|
+
id: "chrtbase-ocean",
|
|
28877
|
+
type: "background",
|
|
28878
|
+
paint: { "background-color": T.water }
|
|
28879
|
+
}),
|
|
28880
|
+
L({
|
|
28881
|
+
id: "chrtbase-earth",
|
|
28882
|
+
type: "fill",
|
|
28883
|
+
source: src,
|
|
28884
|
+
"source-layer": "base_earth",
|
|
28885
|
+
paint: { "fill-color": T.earth }
|
|
28886
|
+
}),
|
|
28887
|
+
L({
|
|
28888
|
+
id: "chrtbase-water",
|
|
28889
|
+
type: "fill",
|
|
28890
|
+
source: src,
|
|
28891
|
+
"source-layer": "base_water",
|
|
28892
|
+
paint: { "fill-color": T.water }
|
|
28893
|
+
}),
|
|
28894
|
+
L({
|
|
28895
|
+
id: "chrtbase-river",
|
|
28896
|
+
type: "line",
|
|
28897
|
+
source: src,
|
|
28898
|
+
"source-layer": "base_river",
|
|
28899
|
+
minzoom: 9,
|
|
28900
|
+
paint: {
|
|
28901
|
+
"line-color": T.river,
|
|
28902
|
+
"line-width": ["interpolate", ["linear"], ["zoom"], 9, 0.6, 14, 1.6]
|
|
28903
|
+
}
|
|
28904
|
+
}),
|
|
28905
|
+
L({
|
|
28906
|
+
id: "chrtbase-boundary",
|
|
28907
|
+
type: "line",
|
|
28908
|
+
source: src,
|
|
28909
|
+
"source-layer": "base_boundary",
|
|
28910
|
+
paint: { "line-color": T.boundary, "line-width": 0.6, "line-opacity": 0.8 }
|
|
28911
|
+
}),
|
|
28912
|
+
// 2. THE DEFAULT: no coverage, everywhere, as a geometry-free background.
|
|
28913
|
+
// Constant opacity by design — see the header. What kills it is being
|
|
28914
|
+
// painted OVER, never fading.
|
|
28915
|
+
L({
|
|
28916
|
+
id: "chrtbase-nocoverage",
|
|
28917
|
+
type: "background",
|
|
28918
|
+
paint: { "background-pattern": NOCOVERAGE_PATTERN, "background-opacity": 0.55 }
|
|
28919
|
+
}),
|
|
28920
|
+
// 3. The jurisdiction: countries whose charts CHRT carries go tan — the
|
|
28921
|
+
// same tan as CHRTland, so "land in our world" is ONE colour. Opaque,
|
|
28922
|
+
// so it takes the crosshatch off its ground entirely.
|
|
28923
|
+
L({
|
|
28924
|
+
id: "chrtbase-jurisdiction",
|
|
28925
|
+
type: "fill",
|
|
28926
|
+
source: src,
|
|
28927
|
+
"source-layer": "jurisdiction",
|
|
28928
|
+
paint: { "fill-color": T.jurisdiction }
|
|
28929
|
+
}),
|
|
28930
|
+
// 4. States — internal boundaries and names, inside the jurisdiction
|
|
28931
|
+
// only (owner decision 2026-08-28). A hairline a shade deeper than the
|
|
28932
|
+
// tan, and quiet letter-spaced caps that fade as harbour detail takes
|
|
28933
|
+
// over. The one deliberate exception to no-names-on-the-basemap:
|
|
28934
|
+
// ADMIN furniture, nothing else — see the fence test.
|
|
28935
|
+
L({
|
|
28936
|
+
id: "chrtbase-state-boundary",
|
|
28937
|
+
type: "line",
|
|
28938
|
+
source: src,
|
|
28939
|
+
"source-layer": "state_boundary",
|
|
28940
|
+
minzoom: 4,
|
|
28941
|
+
paint: {
|
|
28942
|
+
"line-color": T.stateLine,
|
|
28943
|
+
"line-width": ["interpolate", ["linear"], ["zoom"], 4, 0.5, 10, 1],
|
|
28944
|
+
"line-dasharray": [3, 2],
|
|
28945
|
+
"line-opacity": 0.8
|
|
28946
|
+
}
|
|
28947
|
+
}),
|
|
28948
|
+
// 5. Country borders, drawn to be HOISTED above the whole chart by the
|
|
28949
|
+
// generator (owner, 2026-08-31: "where there is charted land, can we
|
|
28950
|
+
// add country borders on top of everything? I want it clear where
|
|
28951
|
+
// there is a boarder."). Sourced from NE boundary_lines_land — land
|
|
28952
|
+
// borders only, never coastline — so riding on top cannot stroke a
|
|
28953
|
+
// coast. Dashed so it reads as an administrative line, not chart
|
|
28954
|
+
// linework.
|
|
28955
|
+
L({
|
|
28956
|
+
id: "chrtbase-country-boundary",
|
|
28957
|
+
type: "line",
|
|
28958
|
+
source: src,
|
|
28959
|
+
"source-layer": "country_boundary",
|
|
28960
|
+
paint: {
|
|
28961
|
+
"line-color": T.boundary,
|
|
28962
|
+
"line-width": ["interpolate", ["linear"], ["zoom"], 3, 0.8, 8, 1.4, 12, 2.2],
|
|
28963
|
+
"line-dasharray": [4, 2],
|
|
28964
|
+
"line-opacity": 0.9
|
|
28965
|
+
}
|
|
28966
|
+
}),
|
|
28967
|
+
L({
|
|
28968
|
+
id: "chrtbase-state-label",
|
|
28969
|
+
type: "symbol",
|
|
28970
|
+
source: src,
|
|
28971
|
+
"source-layer": "state_label",
|
|
28972
|
+
minzoom: 4,
|
|
28973
|
+
maxzoom: 10,
|
|
28974
|
+
layout: {
|
|
28975
|
+
"text-field": ["upcase", ["get", "name"]],
|
|
28976
|
+
"text-font": ["Open Sans Regular"],
|
|
28977
|
+
"text-size": ["interpolate", ["linear"], ["zoom"], 4, 9, 8, 12],
|
|
28978
|
+
"text-letter-spacing": 0.25,
|
|
28979
|
+
"text-allow-overlap": false,
|
|
28980
|
+
"text-padding": 8
|
|
28981
|
+
},
|
|
28982
|
+
paint: {
|
|
28983
|
+
"text-color": T.stateLabel,
|
|
28984
|
+
"text-halo-color": T.stateHalo,
|
|
28985
|
+
"text-halo-width": 1.2,
|
|
28986
|
+
"text-opacity": ["interpolate", ["linear"], ["zoom"], 4, 0.7, 8, 0.85, 10, 0.4]
|
|
28987
|
+
}
|
|
28988
|
+
})
|
|
28425
28989
|
];
|
|
28426
28990
|
}
|
|
28427
|
-
function
|
|
28428
|
-
const
|
|
28429
|
-
const [
|
|
28430
|
-
|
|
28431
|
-
|
|
28432
|
-
|
|
28433
|
-
|
|
28434
|
-
|
|
28435
|
-
|
|
28436
|
-
|
|
28437
|
-
|
|
28438
|
-
|
|
28439
|
-
|
|
28440
|
-
|
|
28441
|
-
|
|
28442
|
-
|
|
28443
|
-
|
|
28444
|
-
|
|
28445
|
-
|
|
28446
|
-
|
|
28447
|
-
|
|
28448
|
-
|
|
28449
|
-
|
|
28450
|
-
|
|
28991
|
+
function baseInlandWaterLayers(opts = {}) {
|
|
28992
|
+
const palette = opts.palette ?? "DAY";
|
|
28993
|
+
const T = BASE_THEMES[palette] ?? BASE_THEMES.DAY;
|
|
28994
|
+
const src = opts.sourceId ?? BASE_REGION;
|
|
28995
|
+
const L = (o) => o;
|
|
28996
|
+
const NE_TIER = ["!", ["has", "kind"]];
|
|
28997
|
+
const PLANET_TIER = ["has", "kind"];
|
|
28998
|
+
const HANDOVER = 9;
|
|
28999
|
+
return [
|
|
29000
|
+
// ── The fill pair, SPLIT BY TIER at the handover ────────────────────────
|
|
29001
|
+
//
|
|
29002
|
+
// Both tiers share `inland_water` (tile-join concatenates the same layer
|
|
29003
|
+
// name), and an unsplit fill draws BOTH — which means the NE polygon is
|
|
29004
|
+
// painted over the planet one at every zoom. That is not a cosmetic
|
|
29005
|
+
// double-draw: NE 10m carries no interior rings, so its blob has no
|
|
29006
|
+
// ISLANDS in it, and painting it over the planet's holed polygons buries
|
|
29007
|
+
// every island in the lake under "uncharted water". Owner-reported at
|
|
29008
|
+
// Winnipesaukee z11.7 ("there is land (and islands) being covered by the
|
|
29009
|
+
// 'no chart here' boundary"), and measured: rendering the planet tier
|
|
29010
|
+
// alone brings back the whole archipelago — Rattlesnake, Bear, Long
|
|
29011
|
+
// Island, Diamond, the Barndoors — while the NE tier alone is a faceted
|
|
29012
|
+
// blob whose edges bear no relation to the shoreline drawn beneath it.
|
|
29013
|
+
//
|
|
29014
|
+
// So each tier owns its own zoom range, the same handover the shoreline
|
|
29015
|
+
// below uses. Known residue, measured over the whole archive: of the 141
|
|
29016
|
+
// NE lakes we still carry, 138 have planet water beneath them; the
|
|
29017
|
+
// exceptions are two unnamed western-Alaska tundra lakes and a 2e-5 deg²
|
|
29018
|
+
// Lake Michigan sliver, which lose their rough fill above z9 because the
|
|
29019
|
+
// planet has no `water` polygon there at all (checked against the raw
|
|
29020
|
+
// pre-clip NDJSON — not a clip artefact).
|
|
29021
|
+
L({
|
|
29022
|
+
id: "chrtbase-inland-water",
|
|
29023
|
+
type: "fill",
|
|
29024
|
+
source: src,
|
|
29025
|
+
"source-layer": "inland_water",
|
|
29026
|
+
filter: NE_TIER,
|
|
29027
|
+
maxzoom: HANDOVER,
|
|
29028
|
+
paint: { "fill-color": T.inlandWater }
|
|
29029
|
+
}),
|
|
29030
|
+
L({
|
|
29031
|
+
id: "chrtbase-inland-hatch",
|
|
29032
|
+
type: "fill",
|
|
29033
|
+
source: src,
|
|
29034
|
+
"source-layer": "inland_water",
|
|
29035
|
+
filter: NE_TIER,
|
|
29036
|
+
maxzoom: HANDOVER,
|
|
29037
|
+
paint: { "fill-pattern": UNCHARTED_PATTERN, "fill-opacity": 0.7 }
|
|
29038
|
+
}),
|
|
29039
|
+
L({
|
|
29040
|
+
id: "chrtbase-inland-water-detail",
|
|
29041
|
+
type: "fill",
|
|
29042
|
+
source: src,
|
|
29043
|
+
"source-layer": "inland_water",
|
|
29044
|
+
filter: PLANET_TIER,
|
|
29045
|
+
minzoom: HANDOVER,
|
|
29046
|
+
paint: { "fill-color": T.inlandWater }
|
|
29047
|
+
}),
|
|
29048
|
+
L({
|
|
29049
|
+
id: "chrtbase-inland-hatch-detail",
|
|
29050
|
+
type: "fill",
|
|
29051
|
+
source: src,
|
|
29052
|
+
"source-layer": "inland_water",
|
|
29053
|
+
filter: PLANET_TIER,
|
|
29054
|
+
minzoom: HANDOVER,
|
|
29055
|
+
paint: { "fill-pattern": UNCHARTED_PATTERN, "fill-opacity": 0.7 }
|
|
29056
|
+
}),
|
|
29057
|
+
// The rough shoreline (owner ask 2026-08-28). Drawn ABOVE the hatch so
|
|
29058
|
+
// the edge stays crisp through the texture, and BELOW the charted-water
|
|
29059
|
+
// repaint clones the generator splices in after it — so wherever the
|
|
29060
|
+
// chart repaints its own water, the rough NE10m line yields to the
|
|
29061
|
+
// chart's COALNE instead of double-striking it.
|
|
29062
|
+
//
|
|
29063
|
+
// NE-tier geometry ONLY (`kind` absent): the planet detail tier joins
|
|
29064
|
+
// the same source-layer from z9 as MANY polygons whose chunk-clip seams
|
|
29065
|
+
// rejoin invisibly as FILLS — but a line layer strokes every seam, and
|
|
29066
|
+
// Winnipesaukee rendered as a mesh of internal edges. Planet features
|
|
29067
|
+
// carry `kind` (the INLAND_KINDS filter's own field); NE's carry only
|
|
29068
|
+
// `name`, so the absence of `kind` IS the tier discriminator.
|
|
29069
|
+
L({
|
|
29070
|
+
id: "chrtbase-inland-shore",
|
|
29071
|
+
type: "line",
|
|
29072
|
+
source: src,
|
|
29073
|
+
"source-layer": "inland_water",
|
|
29074
|
+
filter: NE_TIER,
|
|
29075
|
+
// Hands the shoreline over to the planet tier at ITS floor. Both
|
|
29076
|
+
// tiers live in `inland_water` from z9 up (tile-join concatenates the
|
|
29077
|
+
// same layer name), and their FILLS merge invisibly — but two
|
|
29078
|
+
// outlines do not: NE 10m generalises Winnipesaukee's shape, so its
|
|
29079
|
+
// outline ran straight across the middle of the lake the planet had
|
|
29080
|
+
// drawn correctly around. The coarse line is right only while it is
|
|
29081
|
+
// the only shape we have.
|
|
29082
|
+
maxzoom: HANDOVER,
|
|
29083
|
+
paint: {
|
|
29084
|
+
"line-color": T.inlandShore,
|
|
29085
|
+
"line-width": ["interpolate", ["linear"], ["zoom"], 4, 0.5, 10, 1.1, 14, 1.6],
|
|
29086
|
+
"line-opacity": 0.9
|
|
29087
|
+
}
|
|
29088
|
+
}),
|
|
29089
|
+
// The PLANET tier's shoreline (owner ask 2026-08-31: "uncharted small
|
|
29090
|
+
// lakes render with no outline"). It reads its own `inland_shore` layer,
|
|
29091
|
+
// NOT `inland_water`, and that indirection is the whole fix: the mesh
|
|
29092
|
+
// that forced the `!has kind` fence above is a CLIP artefact — an MVT
|
|
29093
|
+
// feature arrives already cut to its own z10 tile, so stroking the
|
|
29094
|
+
// polygon strokes every tile boundary it crosses. The build now walks
|
|
29095
|
+
// those rings, drops the axis-aligned runs that sit on the read zoom's
|
|
29096
|
+
// tile grid, and emits what is left as lines (measured on one New
|
|
29097
|
+
// England chunk: 61 long axis-aligned mesh edges → 5, and the 5 survivors
|
|
29098
|
+
// are 0.02–0.5 tiles off the grid, i.e. real straight water edges).
|
|
29099
|
+
//
|
|
29100
|
+
// Thinner and quieter than the NE line: this is finer geometry describing
|
|
29101
|
+
// water nobody has surveyed, and approximate data should look
|
|
29102
|
+
// approximate.
|
|
29103
|
+
L({
|
|
29104
|
+
id: "chrtbase-inland-shore-detail",
|
|
29105
|
+
type: "line",
|
|
29106
|
+
source: src,
|
|
29107
|
+
"source-layer": "inland_shore",
|
|
29108
|
+
minzoom: HANDOVER,
|
|
29109
|
+
paint: {
|
|
29110
|
+
"line-color": T.inlandShore,
|
|
29111
|
+
"line-width": ["interpolate", ["linear"], ["zoom"], 9, 0.5, 12, 0.9, 15, 1.2],
|
|
29112
|
+
"line-opacity": 0.85
|
|
29113
|
+
}
|
|
29114
|
+
}),
|
|
29115
|
+
// The lake's name (owner ask 2026-08-28) — the second scoped exception
|
|
29116
|
+
// to no-names-on-the-basemap, after the states: WATER-BODY names only.
|
|
29117
|
+
// No maxzoom: an uncharted lake keeps its name at every zoom; over
|
|
29118
|
+
// charted lakes the chart's own SEAARE label takes over at detail zooms
|
|
29119
|
+
// and collision culls the echo.
|
|
29120
|
+
//
|
|
29121
|
+
// The archive's `inland_water_label` anchors (v3+). v3 shipped ONE
|
|
29122
|
+
// PointOnSurface anchor per lake, and a big lake's single anchor pans
|
|
29123
|
+
// off-viewport — Winnipesaukee at z11.7 read nameless with its name two
|
|
29124
|
+
// tiles west of the screen (owner-reported). Labelling the polygons
|
|
29125
|
+
// instead is NOT the fix: tippecanoe's coalescing drops the NE polygon
|
|
29126
|
+
// from some z11 tiles (measured: 11/619/747 carries only planet pieces),
|
|
29127
|
+
// so a polygon-sourced label vanishes tile by tile. v4 therefore bakes a
|
|
29128
|
+
// deterministic anchor GRID into big lakes — the paper-chart convention
|
|
29129
|
+
// of repeating a name along a large water body — and this one layer
|
|
29130
|
+
// renders however many anchors are in view; collision culls the rest.
|
|
29131
|
+
// Planet-tier water-body NAMES (owner ask 2026-08-31: NE 10m names only
|
|
29132
|
+
// 113 US lakes). Emitted BELOW the NE layer on purpose — MapLibre places
|
|
29133
|
+
// symbols topmost-layer-first, so the big NE lake wins collision against
|
|
29134
|
+
// the pond beside it, and both lose to the chart's own labels (every ENC
|
|
29135
|
+
// class is emitted after this pair).
|
|
29136
|
+
//
|
|
29137
|
+
// The size threshold is OSM's own `min_zoom`, carried through the build:
|
|
29138
|
+
// Lake Winnipesaukee reads 10, Squam Lake 12, so a pond simply is not in
|
|
29139
|
+
// the filter until the user is close enough for it to be the answer to
|
|
29140
|
+
// "what is that water". `["zoom"]` in a filter evaluates at integer zoom
|
|
29141
|
+
// levels and removes the feature from placement entirely — verified
|
|
29142
|
+
// against maplibre-gl-native rather than assumed, because a text-size-0
|
|
29143
|
+
// gate would still claim a collision slot.
|
|
29144
|
+
//
|
|
29145
|
+
// The tiers are separate LAYERS, not a filtered split of one — see the
|
|
29146
|
+
// builder for why: an archive that a not-yet-deployed style would
|
|
29147
|
+
// mis-render cannot be promoted first, and this one has to be.
|
|
29148
|
+
L({
|
|
29149
|
+
id: "chrtbase-inland-label-detail",
|
|
29150
|
+
type: "symbol",
|
|
29151
|
+
source: src,
|
|
29152
|
+
"source-layer": "inland_water_label_detail",
|
|
29153
|
+
minzoom: HANDOVER,
|
|
29154
|
+
filter: [">=", ["zoom"], ["get", "min_zoom"]],
|
|
29155
|
+
layout: {
|
|
29156
|
+
"text-field": ["get", "name"],
|
|
29157
|
+
"text-font": ["Open Sans Regular"],
|
|
29158
|
+
"text-size": ["interpolate", ["linear"], ["zoom"], 9, 9, 12, 10.5, 15, 11],
|
|
29159
|
+
"text-max-width": 8,
|
|
29160
|
+
"text-allow-overlap": false,
|
|
29161
|
+
"text-padding": 6,
|
|
29162
|
+
// Bigger water first within the tier — min_zoom IS the size rank.
|
|
29163
|
+
"symbol-sort-key": ["get", "min_zoom"]
|
|
29164
|
+
},
|
|
29165
|
+
paint: {
|
|
29166
|
+
"text-color": T.inlandLabel,
|
|
29167
|
+
"text-halo-color": T.inlandHalo,
|
|
29168
|
+
"text-halo-width": 1,
|
|
29169
|
+
"text-opacity": 0.9
|
|
29170
|
+
}
|
|
29171
|
+
}),
|
|
29172
|
+
L({
|
|
29173
|
+
id: "chrtbase-inland-label",
|
|
29174
|
+
type: "symbol",
|
|
29175
|
+
source: src,
|
|
29176
|
+
"source-layer": "inland_water_label",
|
|
29177
|
+
minzoom: 5,
|
|
29178
|
+
layout: {
|
|
29179
|
+
"text-field": ["get", "name"],
|
|
29180
|
+
"text-font": ["Open Sans Regular"],
|
|
29181
|
+
"text-size": ["interpolate", ["linear"], ["zoom"], 5, 9.5, 10, 12],
|
|
29182
|
+
"text-max-width": 7,
|
|
29183
|
+
"text-allow-overlap": false,
|
|
29184
|
+
// Zoom-scaled spacing for the anchor GRID: at z8.9 the 0.08-deg
|
|
29185
|
+
// anchors sit ~54 px apart and a flat padding let six copies of
|
|
29186
|
+
// "Lake Ontario" crowd one screen (owner-reported 2026-08-30).
|
|
29187
|
+
// Wide collision boxes at low zoom thin the grid to the repeat-
|
|
29188
|
+
// by-convention cadence; by z12 the anchors are ~850 px apart and
|
|
29189
|
+
// the padding relaxes to normal.
|
|
29190
|
+
"text-padding": ["interpolate", ["linear"], ["zoom"], 5, 80, 9, 64, 12, 16]
|
|
29191
|
+
},
|
|
29192
|
+
paint: {
|
|
29193
|
+
"text-color": T.inlandLabel,
|
|
29194
|
+
"text-halo-color": T.inlandHalo,
|
|
29195
|
+
"text-halo-width": 1.1
|
|
29196
|
+
}
|
|
29197
|
+
})
|
|
29198
|
+
];
|
|
28451
29199
|
}
|
|
28452
29200
|
|
|
28453
29201
|
// src/atlas/band-collapse.ts
|
|
28454
|
-
function collapsedDominanceGate(B, minzoom) {
|
|
28455
|
-
const clauses = [
|
|
29202
|
+
function collapsedDominanceGate(B, minzoom, notDominatedCeiling = null) {
|
|
29203
|
+
const clauses = [
|
|
29204
|
+
notDominatedCeiling !== null && notDominatedCeiling > minzoom ? ["all", NOT_DOMINATED_FILTER, ["<", ["zoom"], notDominatedCeiling]] : NOT_DOMINATED_FILTER
|
|
29205
|
+
];
|
|
28456
29206
|
const bInt = parseInt(B, 10);
|
|
28457
29207
|
for (const D of BAND_DRAW_ORDER) {
|
|
28458
29208
|
const dInt = parseInt(D, 10);
|
|
@@ -28481,7 +29231,7 @@ function makeCollapsedBandVariant(layer, B, isExempt) {
|
|
|
28481
29231
|
filter: combineWithBandFilter(layer.filter, [
|
|
28482
29232
|
"all",
|
|
28483
29233
|
bandFilterExpression(B),
|
|
28484
|
-
collapsedDominanceGate(B, minzoom)
|
|
29234
|
+
collapsedDominanceGate(B, minzoom, generalisedAreaCeiling(layer, B))
|
|
28485
29235
|
]),
|
|
28486
29236
|
minzoom
|
|
28487
29237
|
};
|
|
@@ -28616,6 +29366,7 @@ function makeBandVariants(layer, B) {
|
|
|
28616
29366
|
delete exempt.maxzoom;
|
|
28617
29367
|
return [exempt];
|
|
28618
29368
|
}
|
|
29369
|
+
const ceiling = generalisedAreaCeiling(layer, B);
|
|
28619
29370
|
const notDom = {
|
|
28620
29371
|
...layer,
|
|
28621
29372
|
id: `${layer.id}-band${B}`,
|
|
@@ -28626,6 +29377,7 @@ function makeBandVariants(layer, B) {
|
|
|
28626
29377
|
minzoom
|
|
28627
29378
|
};
|
|
28628
29379
|
delete notDom.maxzoom;
|
|
29380
|
+
if (ceiling !== null && ceiling > minzoom) notDom.maxzoom = ceiling;
|
|
28629
29381
|
variants.push(notDom);
|
|
28630
29382
|
const bInt = parseInt(B, 10);
|
|
28631
29383
|
for (const D of BAND_DRAW_ORDER) {
|
|
@@ -28648,14 +29400,21 @@ function makeBandVariants(layer, B) {
|
|
|
28648
29400
|
return variants;
|
|
28649
29401
|
}
|
|
28650
29402
|
var CELL_BAND_OPACITY_CURVE = {
|
|
28651
|
-
"1": [[3, 0.5], [5, 0.
|
|
28652
|
-
"2": [[5, 0.
|
|
28653
|
-
"3": [[7, 0.55], [9, 0.
|
|
28654
|
-
"4": [[9, 0.
|
|
28655
|
-
"5": [[11, 0.
|
|
28656
|
-
"6": [[13, 0.75], [16, 0.
|
|
29403
|
+
"1": [[3, 0.5], [5, 0.42], [16, 0.34]],
|
|
29404
|
+
"2": [[5, 0.52], [7, 0.44], [16, 0.34]],
|
|
29405
|
+
"3": [[7, 0.55], [9, 0.46], [16, 0.36]],
|
|
29406
|
+
"4": [[9, 0.58], [11, 0.5], [16, 0.42]],
|
|
29407
|
+
"5": [[11, 0.62], [13, 0.52], [18, 0.44]],
|
|
29408
|
+
"6": [[13, 0.75], [16, 0.6], [22, 0.5]]
|
|
28657
29409
|
};
|
|
28658
|
-
|
|
29410
|
+
var ORIENTATION_OPACITY_SCALE = {
|
|
29411
|
+
LNDARE: 1.45,
|
|
29412
|
+
LNDRGN: 1.4,
|
|
29413
|
+
BUAARE: 1.25
|
|
29414
|
+
};
|
|
29415
|
+
var ORIENTATION_OPACITY_MAX = 0.92;
|
|
29416
|
+
var ORIENTATION_BOOST_MINZOOM = 11;
|
|
29417
|
+
function fanOrientationLabelByCellBand(label, bandFan, objectClass) {
|
|
28659
29418
|
const cellBandExpr = [
|
|
28660
29419
|
"slice",
|
|
28661
29420
|
["coalesce", ["get", "_cell_id"], "??9???"],
|
|
@@ -28663,11 +29422,15 @@ function fanOrientationLabelByCellBand(label, bandFan) {
|
|
|
28663
29422
|
3
|
|
28664
29423
|
];
|
|
28665
29424
|
const variants = [];
|
|
29425
|
+
const scale = objectClass ? ORIENTATION_OPACITY_SCALE[objectClass] ?? 1 : 1;
|
|
28666
29426
|
for (const code of BAND_DRAW_ORDER) {
|
|
28667
29427
|
const range = BAND_PRIORITY_RANGES[code];
|
|
28668
29428
|
const curve = CELL_BAND_OPACITY_CURVE[code];
|
|
28669
29429
|
const opacityExpr = ["interpolate", ["linear"], ["zoom"]];
|
|
28670
|
-
for (const [z, o] of curve)
|
|
29430
|
+
for (const [z, o] of curve) {
|
|
29431
|
+
const s = z >= ORIENTATION_BOOST_MINZOOM ? scale : 1;
|
|
29432
|
+
opacityExpr.push(z, Math.round(Math.min(o * s, ORIENTATION_OPACITY_MAX) * 100) / 100);
|
|
29433
|
+
}
|
|
28671
29434
|
const cellFiltered = combineWithBandFilter(
|
|
28672
29435
|
label.filter,
|
|
28673
29436
|
["==", cellBandExpr, code]
|
|
@@ -28714,6 +29477,33 @@ function fanOrientationLabelByCellBand(label, bandFan) {
|
|
|
28714
29477
|
}
|
|
28715
29478
|
return variants;
|
|
28716
29479
|
}
|
|
29480
|
+
var MAJOR_WATER_TEXT_PADDING = [
|
|
29481
|
+
"interpolate",
|
|
29482
|
+
["linear"],
|
|
29483
|
+
["zoom"],
|
|
29484
|
+
5,
|
|
29485
|
+
24,
|
|
29486
|
+
9,
|
|
29487
|
+
20,
|
|
29488
|
+
13,
|
|
29489
|
+
16,
|
|
29490
|
+
16,
|
|
29491
|
+
14
|
|
29492
|
+
];
|
|
29493
|
+
function splitOrientationFan(fan, hoistedFilter, watermarkFilter, hoistedLayout) {
|
|
29494
|
+
return {
|
|
29495
|
+
hoist: fan.map((l) => ({
|
|
29496
|
+
...l,
|
|
29497
|
+
id: `${l.id}-major`,
|
|
29498
|
+
filter: combineWithBandFilter(l.filter, hoistedFilter),
|
|
29499
|
+
...hoistedLayout ? { layout: { ...l.layout ?? {}, ...hoistedLayout } } : {}
|
|
29500
|
+
})),
|
|
29501
|
+
watermark: fan.map((l) => ({
|
|
29502
|
+
...l,
|
|
29503
|
+
filter: combineWithBandFilter(l.filter, watermarkFilter)
|
|
29504
|
+
}))
|
|
29505
|
+
};
|
|
29506
|
+
}
|
|
28717
29507
|
function byLabelPriorityBottomUp(bundles) {
|
|
28718
29508
|
return [...bundles].sort(
|
|
28719
29509
|
(a, b) => getLabelPriority(b.objectClass) - getLabelPriority(a.objectClass)
|
|
@@ -28775,13 +29565,45 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
28775
29565
|
paint: { "background-color": resolveColor("NODTA", palette) }
|
|
28776
29566
|
};
|
|
28777
29567
|
const layers = [backgroundLayer];
|
|
29568
|
+
if (opts.base) {
|
|
29569
|
+
const base = baseLayers({ palette });
|
|
29570
|
+
const coverageGround = {
|
|
29571
|
+
id: "s52-coverage-ground",
|
|
29572
|
+
type: "fill",
|
|
29573
|
+
source: sourceId,
|
|
29574
|
+
"source-layer": "M_COVR",
|
|
29575
|
+
filter: [
|
|
29576
|
+
"all",
|
|
29577
|
+
["any", ["==", ["geometry-type"], "Polygon"], ["==", ["geometry-type"], "MultiPolygon"]],
|
|
29578
|
+
["==", ["to-number", ["coalesce", ["get", "CATCOV"], 0]], 1]
|
|
29579
|
+
],
|
|
29580
|
+
paint: { "fill-color": resolveColor("DEPDW", palette) },
|
|
29581
|
+
metadata: { "s52:objectClass": "M_COVR", "s52:role": "coverage-ground" }
|
|
29582
|
+
};
|
|
29583
|
+
const jurisdictionAt = base.findIndex(
|
|
29584
|
+
(l) => l.id === "chrtbase-jurisdiction"
|
|
29585
|
+
);
|
|
29586
|
+
if (jurisdictionAt === -1) {
|
|
29587
|
+
layers.push(...base, coverageGround);
|
|
29588
|
+
} else {
|
|
29589
|
+
layers.push(
|
|
29590
|
+
...base.slice(0, jurisdictionAt),
|
|
29591
|
+
coverageGround,
|
|
29592
|
+
...base.slice(jurisdictionAt)
|
|
29593
|
+
);
|
|
29594
|
+
}
|
|
29595
|
+
}
|
|
28778
29596
|
if (opts.land) {
|
|
28779
29597
|
layers.push(...landLayers({
|
|
28780
29598
|
palette,
|
|
28781
29599
|
...opts.relief ? { relief: true } : {}
|
|
28782
29600
|
}));
|
|
28783
29601
|
}
|
|
29602
|
+
if (opts.base) {
|
|
29603
|
+
layers.push(...baseInlandWaterLayers({ palette }));
|
|
29604
|
+
}
|
|
28784
29605
|
const labelLayers = [];
|
|
29606
|
+
const majorWaterLayers = [];
|
|
28785
29607
|
const chartCascade = opts.chartMode ? CHART_CASCADE_CODES[opts.chartMode.band] : null;
|
|
28786
29608
|
const chartRequestedCodes = opts.chartMode ? CHART_BAND_CODES[opts.chartMode.band] : null;
|
|
28787
29609
|
const bandFan = opts.bandFan ?? "collapsed";
|
|
@@ -28897,13 +29719,44 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
28897
29719
|
}
|
|
28898
29720
|
}
|
|
28899
29721
|
}
|
|
29722
|
+
{
|
|
29723
|
+
const TERMINAL_FILL_RE = /^s52-(?:LNDARE|DEPARE)-fill-band[456]$/;
|
|
29724
|
+
for (const l of layers) {
|
|
29725
|
+
const lay = l;
|
|
29726
|
+
if (!TERMINAL_FILL_RE.test(lay.id)) continue;
|
|
29727
|
+
const floor = lay.minzoom ?? 0;
|
|
29728
|
+
if (floor <= 0) continue;
|
|
29729
|
+
lay.filter = [
|
|
29730
|
+
"all",
|
|
29731
|
+
lay.filter,
|
|
29732
|
+
["any", [">=", ["zoom"], floor], ["==", ["get", "_terminal_fill"], true]]
|
|
29733
|
+
];
|
|
29734
|
+
delete lay.minzoom;
|
|
29735
|
+
}
|
|
29736
|
+
}
|
|
28900
29737
|
if (opts.labels === "placename") {
|
|
28901
29738
|
labelLayers.push(...buildPlaceNameLayers(sourceId));
|
|
28902
29739
|
} else {
|
|
28903
29740
|
for (const { objectClass, label } of bundles) {
|
|
28904
29741
|
if (!label) continue;
|
|
28905
29742
|
if (getLabelBehavior(objectClass) === "orientation-cross-band") {
|
|
28906
|
-
|
|
29743
|
+
if (objectClass === "LNDARE" || objectClass === "SEAARE") {
|
|
29744
|
+
const Z = ISLAND_LABEL_HOIST_MINZOOM;
|
|
29745
|
+
const { hoist, watermark } = objectClass === "LNDARE" ? splitOrientationFan(
|
|
29746
|
+
fanOrientationLabelByCellBand(label, bandFan, objectClass),
|
|
29747
|
+
[">=", ["zoom"], Z],
|
|
29748
|
+
["<", ["zoom"], Z]
|
|
29749
|
+
) : splitOrientationFan(
|
|
29750
|
+
fanOrientationLabelByCellBand(label, bandFan, objectClass),
|
|
29751
|
+
["<=", buildSeaAreaTierExpression(), MAJOR_WATER_TIER_MAX],
|
|
29752
|
+
[">", buildSeaAreaTierExpression(), MAJOR_WATER_TIER_MAX],
|
|
29753
|
+
{ "text-padding": MAJOR_WATER_TEXT_PADDING }
|
|
29754
|
+
);
|
|
29755
|
+
majorWaterLayers.push(...hoist);
|
|
29756
|
+
labelLayers.push(...watermark);
|
|
29757
|
+
continue;
|
|
29758
|
+
}
|
|
29759
|
+
labelLayers.push(...fanOrientationLabelByCellBand(label, bandFan, objectClass));
|
|
28907
29760
|
}
|
|
28908
29761
|
}
|
|
28909
29762
|
}
|
|
@@ -28912,7 +29765,12 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
28912
29765
|
(b) => b.label !== null && getLabelBehavior(b.objectClass) !== "orientation-cross-band"
|
|
28913
29766
|
)
|
|
28914
29767
|
);
|
|
28915
|
-
|
|
29768
|
+
let majorsEmitted = majorWaterLayers.length === 0;
|
|
29769
|
+
for (const { label, objectClass: cls } of featureLabelBundles) {
|
|
29770
|
+
if (!majorsEmitted && getLabelPriority(cls) < MAJOR_WATER_LABEL_PRIORITY) {
|
|
29771
|
+
labelLayers.push(...majorWaterLayers);
|
|
29772
|
+
majorsEmitted = true;
|
|
29773
|
+
}
|
|
28916
29774
|
if (bandFan === "collapsed") {
|
|
28917
29775
|
labelLayers.push(makeCollapsedCrossBandLabel(label, isDominanceExempt(label)));
|
|
28918
29776
|
continue;
|
|
@@ -28921,18 +29779,121 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
28921
29779
|
labelLayers.push(...emitBand(label, code));
|
|
28922
29780
|
}
|
|
28923
29781
|
}
|
|
29782
|
+
if (!majorsEmitted) labelLayers.push(...majorWaterLayers);
|
|
28924
29783
|
layers.push(...labelLayers);
|
|
28925
29784
|
}
|
|
28926
29785
|
const symbolSet = opts.symbolSet ?? "s52";
|
|
28927
29786
|
const spriteBase = `${opts.tileBaseUrl ?? "https://tiles.chrt.co"}/v1/sprites`;
|
|
28928
29787
|
const isLndareFill = (l) => /^s52-LNDARE-fill-band\d+$/.test(l.id);
|
|
28929
|
-
const
|
|
29788
|
+
const isWaterBaseFill = (l) => /^s52-(DEPARE|DRGARE|UNSARE)-fill-band\d+$/.test(l.id);
|
|
29789
|
+
const bandOf = (l) => {
|
|
29790
|
+
const m = /-band(\d+)$/.exec(l.id);
|
|
29791
|
+
return m ? Number(m[1]) : 0;
|
|
29792
|
+
};
|
|
28930
29793
|
const emitted = opts.land ? (() => {
|
|
28931
|
-
const
|
|
28932
|
-
const
|
|
28933
|
-
const
|
|
28934
|
-
|
|
29794
|
+
const relocated = layers.filter((l) => isLndareFill(l) || isWaterBaseFill(l));
|
|
29795
|
+
const others = layers.filter((l) => !isLndareFill(l) && !isWaterBaseFill(l));
|
|
29796
|
+
const isSeaareFill = (l) => /^s52-SEAARE-fill-band\d+$/.test(l.id);
|
|
29797
|
+
const seaare = others.filter(isSeaareFill);
|
|
29798
|
+
const rest = others.filter((l) => !isSeaareFill(l));
|
|
29799
|
+
const isBaseLayer = (l) => {
|
|
29800
|
+
const id = l.id;
|
|
29801
|
+
if (id.startsWith("chrtbase-inland-")) return false;
|
|
29802
|
+
return id.startsWith("chrtbase-") || id === "s52-coverage-ground";
|
|
29803
|
+
};
|
|
29804
|
+
const baseBlock = rest.filter(isBaseLayer);
|
|
29805
|
+
const restAbove = rest.filter((l) => !isBaseLayer(l));
|
|
29806
|
+
const bands = [...new Set(relocated.map(bandOf))].sort((a, b) => a - b);
|
|
29807
|
+
const oneTan = landBaseColor(palette);
|
|
29808
|
+
const asBaseTan = (l) => ({
|
|
29809
|
+
...l,
|
|
29810
|
+
paint: {
|
|
29811
|
+
...l.paint ?? {},
|
|
29812
|
+
"fill-color": oneTan
|
|
29813
|
+
}
|
|
29814
|
+
});
|
|
29815
|
+
const ordered = [];
|
|
29816
|
+
for (const b of bands) {
|
|
29817
|
+
ordered.push(...relocated.filter((l) => bandOf(l) === b && isWaterBaseFill(l)));
|
|
29818
|
+
ordered.push(...relocated.filter((l) => bandOf(l) === b && isLndareFill(l)).map(asBaseTan));
|
|
29819
|
+
}
|
|
29820
|
+
const findRest = (id) => restAbove.findIndex((l) => l.id === id);
|
|
29821
|
+
const shoreAt = findRest("chrtbase-inland-shore");
|
|
29822
|
+
const pairAt = shoreAt !== -1 ? shoreAt : findRest("chrtbase-inland-hatch");
|
|
29823
|
+
let restFinal = restAbove;
|
|
29824
|
+
if (pairAt !== -1) {
|
|
29825
|
+
const repaint = [];
|
|
29826
|
+
for (const b of bands) {
|
|
29827
|
+
for (const l of relocated.filter((x) => bandOf(x) === b && isWaterBaseFill(x))) {
|
|
29828
|
+
const src = l;
|
|
29829
|
+
const meta = { ...src.metadata ?? {}, "chrt:role": "charted-water-repaint" };
|
|
29830
|
+
const notDom = {
|
|
29831
|
+
...l,
|
|
29832
|
+
id: `${src.id}-overpair`,
|
|
29833
|
+
filter: ["all", src.filter, NOT_DOMINATED_FILTER],
|
|
29834
|
+
metadata: meta
|
|
29835
|
+
};
|
|
29836
|
+
delete notDom.maxzoom;
|
|
29837
|
+
delete notDom.minzoom;
|
|
29838
|
+
repaint.push(notDom);
|
|
29839
|
+
for (const D of BAND_DRAW_ORDER) {
|
|
29840
|
+
if (parseInt(D, 10) <= b) continue;
|
|
29841
|
+
const dMaxzoom = dominatedVariantMaxzoom(D);
|
|
29842
|
+
if (dMaxzoom <= 0) continue;
|
|
29843
|
+
const domVariant = {
|
|
29844
|
+
...l,
|
|
29845
|
+
id: `${src.id}-overpair-domBy${D}`,
|
|
29846
|
+
filter: ["all", src.filter, dominatedByMinFilter(D)],
|
|
29847
|
+
metadata: meta,
|
|
29848
|
+
maxzoom: dMaxzoom
|
|
29849
|
+
};
|
|
29850
|
+
delete domVariant.minzoom;
|
|
29851
|
+
repaint.push(domVariant);
|
|
29852
|
+
}
|
|
29853
|
+
}
|
|
29854
|
+
}
|
|
29855
|
+
const isCoalneLine = (l) => /^s52-COALNE-line-band\d(-domBy\d)?$/.test(l.id);
|
|
29856
|
+
for (const l of layers.filter(isCoalneLine)) {
|
|
29857
|
+
const src = l;
|
|
29858
|
+
const meta = { ...src.metadata ?? {}, "chrt:role": "charted-water-repaint" };
|
|
29859
|
+
const banded = {
|
|
29860
|
+
...l,
|
|
29861
|
+
id: `${src.id}-overpair`,
|
|
29862
|
+
metadata: meta,
|
|
29863
|
+
minzoom: 9
|
|
29864
|
+
};
|
|
29865
|
+
repaint.push(banded);
|
|
29866
|
+
const terminal = {
|
|
29867
|
+
...l,
|
|
29868
|
+
id: `${src.id}-overpair-terminal`,
|
|
29869
|
+
filter: ["all", src.filter, ["==", ["get", "_terminal_shore"], true]],
|
|
29870
|
+
metadata: meta
|
|
29871
|
+
};
|
|
29872
|
+
delete terminal.minzoom;
|
|
29873
|
+
terminal.maxzoom = 9;
|
|
29874
|
+
repaint.push(terminal);
|
|
29875
|
+
}
|
|
29876
|
+
restFinal = [
|
|
29877
|
+
...restAbove.slice(0, pairAt + 1),
|
|
29878
|
+
...repaint,
|
|
29879
|
+
...restAbove.slice(pairAt + 1)
|
|
29880
|
+
];
|
|
29881
|
+
}
|
|
29882
|
+
return [restFinal[0], ...baseBlock, ...seaare, ...ordered, ...restFinal.slice(1)];
|
|
28935
29883
|
})() : layers;
|
|
29884
|
+
for (const id of [
|
|
29885
|
+
"chrtbase-country-boundary",
|
|
29886
|
+
"chrtland-town-port",
|
|
29887
|
+
"chrtland-city-major"
|
|
29888
|
+
]) {
|
|
29889
|
+
const at = emitted.findIndex(
|
|
29890
|
+
(l) => l.id === id
|
|
29891
|
+
);
|
|
29892
|
+
if (at !== -1) {
|
|
29893
|
+
const [layer] = emitted.splice(at, 1);
|
|
29894
|
+
emitted.push(layer);
|
|
29895
|
+
}
|
|
29896
|
+
}
|
|
28936
29897
|
const style = {
|
|
28937
29898
|
version: 8,
|
|
28938
29899
|
name: `CHRT ${SYMBOL_SET_LABELS[symbolSet]} ${PALETTE_NAMES[palette]}${opts.chartMode ? ` \u2014 chart:${opts.chartMode.band}` : ""}`,
|
|
@@ -28944,6 +29905,7 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
28944
29905
|
"chrt:layer-count": emitted.length,
|
|
28945
29906
|
"chrt:land": Boolean(opts.land),
|
|
28946
29907
|
"chrt:relief": Boolean(opts.relief),
|
|
29908
|
+
"chrt:base": Boolean(opts.base),
|
|
28947
29909
|
"chrt:render-mode": opts.chartMode ? `chart:${opts.chartMode.band}` : "continuous",
|
|
28948
29910
|
"chrt:description": "IHO S-52 portrayal rules for Electronic Navigational Charts (ENCs). Generated from the CHRT atlas engine."
|
|
28949
29911
|
},
|
|
@@ -29437,6 +30399,7 @@ var Chrt = class {
|
|
|
29437
30399
|
interact;
|
|
29438
30400
|
featuresEp;
|
|
29439
30401
|
searchEp;
|
|
30402
|
+
poiEp;
|
|
29440
30403
|
queryEp;
|
|
29441
30404
|
http;
|
|
29442
30405
|
tileAuth;
|
|
@@ -29458,6 +30421,7 @@ var Chrt = class {
|
|
|
29458
30421
|
this.weather = new WeatherEndpoint(this.http);
|
|
29459
30422
|
this.featuresEp = new FeaturesEndpoint(this.http);
|
|
29460
30423
|
this.searchEp = new SearchEndpoint(this.http);
|
|
30424
|
+
this.poiEp = new PoiEndpoint(this.http);
|
|
29461
30425
|
this.queryEp = new QueryEndpoint(this.http);
|
|
29462
30426
|
this.offline = new OfflineEndpoint(this.http, config.deviceId, config.manifestPath);
|
|
29463
30427
|
this.interact = new InteractClient(this.http);
|
|
@@ -29493,6 +30457,26 @@ var Chrt = class {
|
|
|
29493
30457
|
async search(options) {
|
|
29494
30458
|
return this.searchEp.find(options);
|
|
29495
30459
|
}
|
|
30460
|
+
/**
|
|
30461
|
+
* CHRTpoi — charted marine facilities in a viewport or radius.
|
|
30462
|
+
*
|
|
30463
|
+
* v1 serves marinas, derived from the ENC's own HRBFAC facility class —
|
|
30464
|
+
* the chart's data, with the source cell's currency inline per POI.
|
|
30465
|
+
*
|
|
30466
|
+
* ```ts
|
|
30467
|
+
* const { pois, coverage } = await chrt.pois({ bbox: [-70.7, 41.4, -70.5, 41.6] });
|
|
30468
|
+
* ```
|
|
30469
|
+
*
|
|
30470
|
+
* **Read `coverage.state` before interpreting an empty list.** `covered` +
|
|
30471
|
+
* empty means no marinas are charted there — a real answer. `none` means
|
|
30472
|
+
* CHRT cannot answer for that area at all, and an empty list means nothing.
|
|
30473
|
+
* The two must never be shown the same way, and `coverage.message` is
|
|
30474
|
+
* written to be relayed unedited. Metered on its own allowance
|
|
30475
|
+
* (`poi_called`), separate from features and search; not gated by tier.
|
|
30476
|
+
*/
|
|
30477
|
+
async pois(options) {
|
|
30478
|
+
return this.poiEp.list(options);
|
|
30479
|
+
}
|
|
29496
30480
|
/** Spatial proximity query around a point */
|
|
29497
30481
|
async query(options) {
|
|
29498
30482
|
return this.queryEp.nearby(options);
|
|
@@ -29663,6 +30647,18 @@ var Chrt = class {
|
|
|
29663
30647
|
return generateStyle(palette, {
|
|
29664
30648
|
region: this.region,
|
|
29665
30649
|
tileBaseUrl: this.tileBaseUrl,
|
|
30650
|
+
// Pin each source's tile URL to the build it serves, from whatever the
|
|
30651
|
+
// token cache already holds. Without a discriminator one URL is shared
|
|
30652
|
+
// by every build, so after a cutover a map keeps painting already-fetched
|
|
30653
|
+
// tiles from the OLD build beside newly-fetched ones from the new.
|
|
30654
|
+
//
|
|
30655
|
+
// BEFORE the spread, so an explicit `buildTags` wins — and only ever a
|
|
30656
|
+
// warm-cache read: this method is synchronous and must not mint. A cold
|
|
30657
|
+
// cache yields `{}`, i.e. exactly the URLs this SDK emitted before the
|
|
30658
|
+
// discriminator existed. Warm it with `chrt.transformRequest()` (which
|
|
30659
|
+
// kicks off a mint) or `await chrt.styleUrl()`; `await chrt.warmTiles()`
|
|
30660
|
+
// is the explicit form, and it returns the map.
|
|
30661
|
+
buildTags: this.tileAuth.peekBuildTags(this.region),
|
|
29666
30662
|
...styleOpts,
|
|
29667
30663
|
// The vessel's safety depth IS the style's safety contour — same number, same
|
|
29668
30664
|
// meaning. Threading it here is what stops the chart shading at one depth while
|
|
@@ -29736,9 +30732,21 @@ var Chrt = class {
|
|
|
29736
30732
|
* Pre-mint the tile token for a region. Optional — `transformRequest()` warms
|
|
29737
30733
|
* the cache automatically — but useful if you want to surface a mint error
|
|
29738
30734
|
* (e.g. `region_not_licensed`, `tile_quota_exceeded`) before the map loads.
|
|
30735
|
+
*
|
|
30736
|
+
* Returns what it minted, so a caller building its own sources can take the
|
|
30737
|
+
* `buildTags` map straight from here:
|
|
30738
|
+
*
|
|
30739
|
+
* ```ts
|
|
30740
|
+
* const { buildTags } = await chrt.warmTiles();
|
|
30741
|
+
* map.addSource("chrt", buildSources({ region: "NOAA_ENC", buildTags })["NOAA_ENC"]);
|
|
30742
|
+
* ```
|
|
30743
|
+
*
|
|
30744
|
+
* That pins each tile URL to the build it serves, so a build cutover gets its
|
|
30745
|
+
* own client cache namespace instead of leaving old and new tiles mixed on
|
|
30746
|
+
* screen. `chrt.style()` reads the same cache and needs no argument.
|
|
29739
30747
|
*/
|
|
29740
30748
|
async warmTiles(region) {
|
|
29741
|
-
|
|
30749
|
+
return this.tileAuth.getToken(region);
|
|
29742
30750
|
}
|
|
29743
30751
|
};
|
|
29744
30752
|
export {
|
|
@@ -29748,9 +30756,13 @@ export {
|
|
|
29748
30756
|
ATON_EXPRESSION_CLASSES,
|
|
29749
30757
|
BAND_DRAW_ORDER,
|
|
29750
30758
|
BAND_PRIORITY_RANGES,
|
|
30759
|
+
BASE_MAXZOOM,
|
|
30760
|
+
BASE_MINZOOM,
|
|
30761
|
+
BASE_REGION,
|
|
29751
30762
|
BRIDGE_QUERY_LAYERS,
|
|
29752
30763
|
BUILD_MAXZOOM,
|
|
29753
30764
|
BUILD_MINZOOM,
|
|
30765
|
+
BUILD_PARAM,
|
|
29754
30766
|
CHART_BAND_CODES,
|
|
29755
30767
|
CHART_CASCADE_CODES,
|
|
29756
30768
|
CHART_SECTIONS,
|
|
@@ -29932,6 +30944,7 @@ export {
|
|
|
29932
30944
|
scaleSizeValue,
|
|
29933
30945
|
scaminToMinZoom,
|
|
29934
30946
|
styleModeBand,
|
|
30947
|
+
tileTemplate,
|
|
29935
30948
|
withinBboxFilter,
|
|
29936
30949
|
worldMinusBboxPolygon
|
|
29937
30950
|
};
|