@chrtco/sdk 0.10.19 → 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/index.cjs +348 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +348 -55
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -366,8 +366,8 @@ var RELIEF_REGION = "CHRT_RELIEF";
|
|
|
366
366
|
var BASE_REGION = "CHRT_BASE";
|
|
367
367
|
var BASE_MINZOOM = 0;
|
|
368
368
|
var BASE_MAXZOOM = 11;
|
|
369
|
-
var RELIEF_MINZOOM =
|
|
370
|
-
var RELIEF_MAXZOOM =
|
|
369
|
+
var RELIEF_MINZOOM = 9;
|
|
370
|
+
var RELIEF_MAXZOOM = 12;
|
|
371
371
|
var RELIEF_TILE_SIZE = 512;
|
|
372
372
|
var BUILD_PARAM = "v";
|
|
373
373
|
function tileTemplate(base, region, extension, buildId) {
|
|
@@ -22829,14 +22829,19 @@ var ORIENTATION_BAND_SORT_KEY = [
|
|
|
22829
22829
|
["slice", ["coalesce", ["get", "_cell_id"], "??9???"], 2, 3],
|
|
22830
22830
|
9
|
|
22831
22831
|
];
|
|
22832
|
-
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) {
|
|
22833
22838
|
return {
|
|
22834
22839
|
"text-anchor": "center",
|
|
22835
22840
|
"text-allow-overlap": false,
|
|
22836
22841
|
"text-ignore-placement": false,
|
|
22837
22842
|
"text-optional": true,
|
|
22838
22843
|
"text-padding": 15,
|
|
22839
|
-
"symbol-sort-key":
|
|
22844
|
+
"symbol-sort-key": orientationSortKey(tier),
|
|
22840
22845
|
...extras
|
|
22841
22846
|
};
|
|
22842
22847
|
}
|
|
@@ -22846,15 +22851,17 @@ function tieredSizeCurve(tierExpr, stops) {
|
|
|
22846
22851
|
if (i > 0) {
|
|
22847
22852
|
const [pz, pv] = stops[i - 1];
|
|
22848
22853
|
const [nz, nv] = stops[i];
|
|
22849
|
-
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)) {
|
|
22850
22855
|
const hz = Math.round((nz - 0.2) * 10) / 10;
|
|
22851
22856
|
if (hz > pz) {
|
|
22852
22857
|
const f = (hz - pz) / (nz - pz);
|
|
22853
22858
|
withHolds.push([
|
|
22854
22859
|
hz,
|
|
22855
|
-
pv.map(
|
|
22856
|
-
(v
|
|
22857
|
-
|
|
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
|
+
})
|
|
22858
22865
|
]);
|
|
22859
22866
|
}
|
|
22860
22867
|
}
|
|
@@ -22883,6 +22890,19 @@ var ORIENTATION_HALO_WIDTH = [
|
|
|
22883
22890
|
16,
|
|
22884
22891
|
0.7
|
|
22885
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
|
+
];
|
|
22886
22906
|
var LABEL_BEHAVIOR = {
|
|
22887
22907
|
// Orientation labels — render once across all bands.
|
|
22888
22908
|
SEAARE: "orientation-cross-band",
|
|
@@ -22956,6 +22976,9 @@ var LABEL_PRIORITY = {
|
|
|
22956
22976
|
};
|
|
22957
22977
|
var DEFAULT_LABEL_PRIORITY = 450;
|
|
22958
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;
|
|
22959
22982
|
function getLabelPriority(layerName) {
|
|
22960
22983
|
if (getLabelBehavior(layerName) === "orientation-cross-band") {
|
|
22961
22984
|
return ORIENTATION_LABEL_PRIORITY;
|
|
@@ -23128,21 +23151,25 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23128
23151
|
// LNDRGN labels until coastal zoom. The Carto basemap already labels
|
|
23129
23152
|
// major land regions at z5-z6. Columns: t1 major region, t2 local
|
|
23130
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.
|
|
23131
23157
|
textSize: tieredSizeCurve(tierExpr, [
|
|
23132
23158
|
[5, [0, 0]],
|
|
23133
23159
|
[7, [11, 0]],
|
|
23134
23160
|
[9, [12, 0]],
|
|
23135
|
-
[11, [12,
|
|
23136
|
-
[13, [11, 11]],
|
|
23137
|
-
[16, [10,
|
|
23161
|
+
[11, [12, 10]],
|
|
23162
|
+
[13, [11.5, 11.5]],
|
|
23163
|
+
[16, [10.5, 11.5]]
|
|
23138
23164
|
]),
|
|
23139
23165
|
textColor: DARK_BG,
|
|
23140
23166
|
textHaloColor: COLOR.LAND_REGION,
|
|
23141
|
-
|
|
23167
|
+
// Land halo, not the shared taper — see LAND_HALO_WIDTH.
|
|
23168
|
+
textHaloWidth: LAND_HALO_WIDTH,
|
|
23142
23169
|
textOffset: [0, 0],
|
|
23143
23170
|
textFont: ["Open Sans Regular"],
|
|
23144
23171
|
minzoom: 5,
|
|
23145
|
-
layout: orientationLabelLayout()
|
|
23172
|
+
layout: orientationLabelLayout({}, tierExpr)
|
|
23146
23173
|
};
|
|
23147
23174
|
}
|
|
23148
23175
|
case "LNDMRK":
|
|
@@ -23307,14 +23334,56 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23307
23334
|
// at approach scale (z9) when the user has zoomed in enough that
|
|
23308
23335
|
// individual banks are useful context. Columns: t1 ocean, t2 sea/gulf,
|
|
23309
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.
|
|
23310
23379
|
textSize: tieredSizeCurve(tierExpr, [
|
|
23311
23380
|
[3, [14, 8, 0, 0, 0]],
|
|
23312
23381
|
[5, [16, 13, 9, 0, 0]],
|
|
23313
23382
|
[7, [14, 14, 12, 0, 0]],
|
|
23314
|
-
[9, [
|
|
23315
|
-
[11, [
|
|
23316
|
-
[13, [
|
|
23317
|
-
[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]]
|
|
23318
23387
|
]),
|
|
23319
23388
|
textColor: DARK_BG,
|
|
23320
23389
|
textHaloColor: COLOR.WATER_DEEP,
|
|
@@ -23338,8 +23407,41 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23338
23407
|
// the tiles and replaces this fan outright — do not restore
|
|
23339
23408
|
// always-paint on a starvation report; flip `labels: "placename"`
|
|
23340
23409
|
// instead once the build carries the layer.
|
|
23341
|
-
|
|
23342
|
-
|
|
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)
|
|
23343
23445
|
};
|
|
23344
23446
|
}
|
|
23345
23447
|
case "LAKARE": {
|
|
@@ -23360,7 +23462,7 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23360
23462
|
textOffset: [0, 0],
|
|
23361
23463
|
textFont: ["Open Sans Italic"],
|
|
23362
23464
|
minzoom: 7,
|
|
23363
|
-
layout: orientationLabelLayout()
|
|
23465
|
+
layout: orientationLabelLayout({}, tierExpr)
|
|
23364
23466
|
};
|
|
23365
23467
|
}
|
|
23366
23468
|
case "RIVERS": {
|
|
@@ -23381,7 +23483,7 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23381
23483
|
textOffset: [0, 0],
|
|
23382
23484
|
textFont: ["Open Sans Italic"],
|
|
23383
23485
|
minzoom: 7,
|
|
23384
|
-
layout: orientationLabelLayout()
|
|
23486
|
+
layout: orientationLabelLayout({}, tierExpr)
|
|
23385
23487
|
};
|
|
23386
23488
|
}
|
|
23387
23489
|
case "CANALS":
|
|
@@ -23412,21 +23514,31 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23412
23514
|
return {
|
|
23413
23515
|
type: "symbol",
|
|
23414
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.
|
|
23415
23526
|
textSize: tieredSizeCurve(tierExpr, [
|
|
23416
23527
|
[5, [8, 0]],
|
|
23417
23528
|
[7, [10, 0]],
|
|
23418
23529
|
[9, [11, 0]],
|
|
23419
|
-
[11, [11,
|
|
23420
|
-
[13, [
|
|
23421
|
-
[16, [
|
|
23530
|
+
[11, [11.5, 10]],
|
|
23531
|
+
[13, [11.5, 11.5]],
|
|
23532
|
+
[16, [10.5, 11.5]]
|
|
23422
23533
|
]),
|
|
23423
23534
|
textColor: DARK_BG,
|
|
23424
23535
|
textHaloColor: COLOR.LAND_NATURAL,
|
|
23425
|
-
|
|
23536
|
+
// Land halo, not the shared taper — see LAND_HALO_WIDTH.
|
|
23537
|
+
textHaloWidth: LAND_HALO_WIDTH,
|
|
23426
23538
|
textOffset: [0, 0],
|
|
23427
23539
|
textFont: ["Open Sans Regular"],
|
|
23428
23540
|
minzoom: 5,
|
|
23429
|
-
layout: orientationLabelLayout()
|
|
23541
|
+
layout: orientationLabelLayout({}, tierExpr)
|
|
23430
23542
|
};
|
|
23431
23543
|
}
|
|
23432
23544
|
case "BUAARE": {
|
|
@@ -23451,7 +23563,7 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23451
23563
|
minzoom: 3,
|
|
23452
23564
|
layout: orientationLabelLayout({
|
|
23453
23565
|
"text-letter-spacing": 0.05
|
|
23454
|
-
})
|
|
23566
|
+
}, tierExpr)
|
|
23455
23567
|
};
|
|
23456
23568
|
}
|
|
23457
23569
|
case "BRIDGE":
|
|
@@ -23543,7 +23655,7 @@ function getLabelConfig(layerName, opts = {}) {
|
|
|
23543
23655
|
textOffset: [0, 0],
|
|
23544
23656
|
textFont: ["Open Sans Regular"],
|
|
23545
23657
|
minzoom: 7,
|
|
23546
|
-
layout: orientationLabelLayout()
|
|
23658
|
+
layout: orientationLabelLayout({}, tierExpr)
|
|
23547
23659
|
};
|
|
23548
23660
|
}
|
|
23549
23661
|
case "SBDARE":
|
|
@@ -28381,8 +28493,7 @@ function worldMinusBboxPolygon(bbox) {
|
|
|
28381
28493
|
|
|
28382
28494
|
// src/atlas/land-layers.ts
|
|
28383
28495
|
var LAND_SURFACE_MINZOOM = BAND_PRIORITY_RANGES["5"].minzoom;
|
|
28384
|
-
var
|
|
28385
|
-
var LAND_SURFACE_LAYERS = (id) => id === "chrtland-base" || id === "chrtland-cover" || id === "chrtland-contour" || id.startsWith("chrtland-fade-");
|
|
28496
|
+
var LAND_SURFACE_LAYERS = (id) => id === "chrtland-base" || id === "chrtland-cover" || id === "chrtland-contour" || id === "chrtland-hillshade" || id.startsWith("chrtland-fade-");
|
|
28386
28497
|
var FADE_PX = 80;
|
|
28387
28498
|
var FADE_BANDS = 16;
|
|
28388
28499
|
function fadeAlpha(r) {
|
|
@@ -28501,10 +28612,11 @@ function landLayers(opts = {}) {
|
|
|
28501
28612
|
}));
|
|
28502
28613
|
if (opts.relief) {
|
|
28503
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.
|
|
28504
28617
|
id: "chrtland-hillshade",
|
|
28505
28618
|
type: "hillshade",
|
|
28506
28619
|
source: demSrc,
|
|
28507
|
-
minzoom: RELIEF_MINZOOM2,
|
|
28508
28620
|
paint: {
|
|
28509
28621
|
"hillshade-exaggeration": T.exaggeration,
|
|
28510
28622
|
"hillshade-shadow-color": T.shadow,
|
|
@@ -28881,12 +28993,38 @@ function baseInlandWaterLayers(opts = {}) {
|
|
|
28881
28993
|
const T = BASE_THEMES[palette] ?? BASE_THEMES.DAY;
|
|
28882
28994
|
const src = opts.sourceId ?? BASE_REGION;
|
|
28883
28995
|
const L = (o) => o;
|
|
28996
|
+
const NE_TIER = ["!", ["has", "kind"]];
|
|
28997
|
+
const PLANET_TIER = ["has", "kind"];
|
|
28998
|
+
const HANDOVER = 9;
|
|
28884
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).
|
|
28885
29021
|
L({
|
|
28886
29022
|
id: "chrtbase-inland-water",
|
|
28887
29023
|
type: "fill",
|
|
28888
29024
|
source: src,
|
|
28889
29025
|
"source-layer": "inland_water",
|
|
29026
|
+
filter: NE_TIER,
|
|
29027
|
+
maxzoom: HANDOVER,
|
|
28890
29028
|
paint: { "fill-color": T.inlandWater }
|
|
28891
29029
|
}),
|
|
28892
29030
|
L({
|
|
@@ -28894,6 +29032,26 @@ function baseInlandWaterLayers(opts = {}) {
|
|
|
28894
29032
|
type: "fill",
|
|
28895
29033
|
source: src,
|
|
28896
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,
|
|
28897
29055
|
paint: { "fill-pattern": UNCHARTED_PATTERN, "fill-opacity": 0.7 }
|
|
28898
29056
|
}),
|
|
28899
29057
|
// The rough shoreline (owner ask 2026-08-28). Drawn ABOVE the hatch so
|
|
@@ -28913,13 +29071,47 @@ function baseInlandWaterLayers(opts = {}) {
|
|
|
28913
29071
|
type: "line",
|
|
28914
29072
|
source: src,
|
|
28915
29073
|
"source-layer": "inland_water",
|
|
28916
|
-
filter:
|
|
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,
|
|
28917
29083
|
paint: {
|
|
28918
29084
|
"line-color": T.inlandShore,
|
|
28919
29085
|
"line-width": ["interpolate", ["linear"], ["zoom"], 4, 0.5, 10, 1.1, 14, 1.6],
|
|
28920
29086
|
"line-opacity": 0.9
|
|
28921
29087
|
}
|
|
28922
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
|
+
}),
|
|
28923
29115
|
// The lake's name (owner ask 2026-08-28) — the second scoped exception
|
|
28924
29116
|
// to no-names-on-the-basemap, after the states: WATER-BODY names only.
|
|
28925
29117
|
// No maxzoom: an uncharted lake keeps its name at every zoom; over
|
|
@@ -28936,6 +29128,47 @@ function baseInlandWaterLayers(opts = {}) {
|
|
|
28936
29128
|
// deterministic anchor GRID into big lakes — the paper-chart convention
|
|
28937
29129
|
// of repeating a name along a large water body — and this one layer
|
|
28938
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
|
+
}),
|
|
28939
29172
|
L({
|
|
28940
29173
|
id: "chrtbase-inland-label",
|
|
28941
29174
|
type: "symbol",
|
|
@@ -29167,14 +29400,21 @@ function makeBandVariants(layer, B) {
|
|
|
29167
29400
|
return variants;
|
|
29168
29401
|
}
|
|
29169
29402
|
var CELL_BAND_OPACITY_CURVE = {
|
|
29170
|
-
"1": [[3, 0.5], [5, 0.
|
|
29171
|
-
"2": [[5, 0.
|
|
29172
|
-
"3": [[7, 0.55], [9, 0.
|
|
29173
|
-
"4": [[9, 0.
|
|
29174
|
-
"5": [[11, 0.
|
|
29175
|
-
"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]]
|
|
29409
|
+
};
|
|
29410
|
+
var ORIENTATION_OPACITY_SCALE = {
|
|
29411
|
+
LNDARE: 1.45,
|
|
29412
|
+
LNDRGN: 1.4,
|
|
29413
|
+
BUAARE: 1.25
|
|
29176
29414
|
};
|
|
29177
|
-
|
|
29415
|
+
var ORIENTATION_OPACITY_MAX = 0.92;
|
|
29416
|
+
var ORIENTATION_BOOST_MINZOOM = 11;
|
|
29417
|
+
function fanOrientationLabelByCellBand(label, bandFan, objectClass) {
|
|
29178
29418
|
const cellBandExpr = [
|
|
29179
29419
|
"slice",
|
|
29180
29420
|
["coalesce", ["get", "_cell_id"], "??9???"],
|
|
@@ -29182,11 +29422,15 @@ function fanOrientationLabelByCellBand(label, bandFan) {
|
|
|
29182
29422
|
3
|
|
29183
29423
|
];
|
|
29184
29424
|
const variants = [];
|
|
29425
|
+
const scale = objectClass ? ORIENTATION_OPACITY_SCALE[objectClass] ?? 1 : 1;
|
|
29185
29426
|
for (const code of BAND_DRAW_ORDER) {
|
|
29186
29427
|
const range = BAND_PRIORITY_RANGES[code];
|
|
29187
29428
|
const curve = CELL_BAND_OPACITY_CURVE[code];
|
|
29188
29429
|
const opacityExpr = ["interpolate", ["linear"], ["zoom"]];
|
|
29189
|
-
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
|
+
}
|
|
29190
29434
|
const cellFiltered = combineWithBandFilter(
|
|
29191
29435
|
label.filter,
|
|
29192
29436
|
["==", cellBandExpr, code]
|
|
@@ -29233,6 +29477,33 @@ function fanOrientationLabelByCellBand(label, bandFan) {
|
|
|
29233
29477
|
}
|
|
29234
29478
|
return variants;
|
|
29235
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
|
+
}
|
|
29236
29507
|
function byLabelPriorityBottomUp(bundles) {
|
|
29237
29508
|
return [...bundles].sort(
|
|
29238
29509
|
(a, b) => getLabelPriority(b.objectClass) - getLabelPriority(a.objectClass)
|
|
@@ -29332,6 +29603,7 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
29332
29603
|
layers.push(...baseInlandWaterLayers({ palette }));
|
|
29333
29604
|
}
|
|
29334
29605
|
const labelLayers = [];
|
|
29606
|
+
const majorWaterLayers = [];
|
|
29335
29607
|
const chartCascade = opts.chartMode ? CHART_CASCADE_CODES[opts.chartMode.band] : null;
|
|
29336
29608
|
const chartRequestedCodes = opts.chartMode ? CHART_BAND_CODES[opts.chartMode.band] : null;
|
|
29337
29609
|
const bandFan = opts.bandFan ?? "collapsed";
|
|
@@ -29448,19 +29720,18 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
29448
29720
|
}
|
|
29449
29721
|
}
|
|
29450
29722
|
{
|
|
29451
|
-
const TERMINAL_FILL_RE = /^s52-(LNDARE|DEPARE)-fill-band
|
|
29452
|
-
for (
|
|
29453
|
-
const
|
|
29454
|
-
|
|
29455
|
-
|
|
29456
|
-
|
|
29457
|
-
|
|
29458
|
-
|
|
29459
|
-
|
|
29460
|
-
|
|
29461
|
-
|
|
29462
|
-
|
|
29463
|
-
layers.splice(i + 1, 0, clone);
|
|
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;
|
|
29464
29735
|
}
|
|
29465
29736
|
}
|
|
29466
29737
|
if (opts.labels === "placename") {
|
|
@@ -29469,7 +29740,23 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
29469
29740
|
for (const { objectClass, label } of bundles) {
|
|
29470
29741
|
if (!label) continue;
|
|
29471
29742
|
if (getLabelBehavior(objectClass) === "orientation-cross-band") {
|
|
29472
|
-
|
|
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));
|
|
29473
29760
|
}
|
|
29474
29761
|
}
|
|
29475
29762
|
}
|
|
@@ -29478,7 +29765,12 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
29478
29765
|
(b) => b.label !== null && getLabelBehavior(b.objectClass) !== "orientation-cross-band"
|
|
29479
29766
|
)
|
|
29480
29767
|
);
|
|
29481
|
-
|
|
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
|
+
}
|
|
29482
29774
|
if (bandFan === "collapsed") {
|
|
29483
29775
|
labelLayers.push(makeCollapsedCrossBandLabel(label, isDominanceExempt(label)));
|
|
29484
29776
|
continue;
|
|
@@ -29487,6 +29779,7 @@ function generateStyle(palette = "DAY", opts = {}) {
|
|
|
29487
29779
|
labelLayers.push(...emitBand(label, code));
|
|
29488
29780
|
}
|
|
29489
29781
|
}
|
|
29782
|
+
if (!majorsEmitted) labelLayers.push(...majorWaterLayers);
|
|
29490
29783
|
layers.push(...labelLayers);
|
|
29491
29784
|
}
|
|
29492
29785
|
const symbolSet = opts.symbolSet ?? "s52";
|