@chrtco/sdk 0.10.45 → 0.10.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/atlas/browser.cjs +139 -10
- package/dist/atlas/browser.cjs.map +1 -1
- package/dist/atlas/browser.d.cts +7 -2
- package/dist/atlas/browser.d.ts +7 -2
- package/dist/atlas/browser.js +139 -10
- package/dist/atlas/browser.js.map +1 -1
- package/dist/index.cjs +96 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +155 -1
- package/dist/index.d.ts +155 -1
- package/dist/index.js +89 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,11 @@ All notable changes to `@chrtco/sdk` will be documented in this file.
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
7
|
### Added
|
|
8
|
+
- **`isMapLive(map)`** — `false` once MapLibre's `map.remove()` has run (it
|
|
9
|
+
reads the `_removed` flag `remove()` sets last). After `remove()`, `hasImage`,
|
|
10
|
+
`addImage`, `getSource` and their siblings throw, so anything that awaits and
|
|
11
|
+
then writes to a map it was handed should ask this first and stop quietly on
|
|
12
|
+
`false`. Takes any object, so structural map stand-ins can be asked too.
|
|
8
13
|
- `MarineZoneRef.bbox` on `weather.marineForecast()` — `[west, south, east, north]` of the
|
|
9
14
|
forecast's zone, from the same build as the `NWS_MARINE_ZONES` tile source. A camera hint: fit
|
|
10
15
|
the map to it; draw the zone from the tiles, never the box. `west > east` crosses 180°.
|
|
@@ -14,6 +19,14 @@ All notable changes to `@chrtco/sdk` will be documented in this file.
|
|
|
14
19
|
source themselves, such as the tile-worker's TileJSON.
|
|
15
20
|
|
|
16
21
|
### Fixed
|
|
22
|
+
- **The image loaders threw on a map removed while they waited.**
|
|
23
|
+
`loadS52SymbolImagesCached`, `loadSymbolImageSpecs`, `rethemeSymbolImages`,
|
|
24
|
+
`loadSymbolImages` and `loadThemedBridgeImages` await a decode or an
|
|
25
|
+
IndexedDB read and then write to the map. If the map had been removed
|
|
26
|
+
meanwhile, the write threw "Cannot read properties of undefined (reading
|
|
27
|
+
'getImage')", usually from a promise nobody was awaiting. They now check
|
|
28
|
+
`isMapLive` after each wait and leave a removed map alone; a re-theme cut
|
|
29
|
+
short that way returns `aborted: true`.
|
|
17
30
|
- **A palette switch held the map's tile answers behind its own SVG decodes.**
|
|
18
31
|
`rethemeSymbolImages` decoded every symbol as its own SVG `<img>`, and each
|
|
19
32
|
is a whole browser document whose many small main-thread tasks queued ahead
|
package/dist/atlas/browser.cjs
CHANGED
|
@@ -1161,6 +1161,11 @@ function applyStrokeBoost(svgString, multiplier) {
|
|
|
1161
1161
|
}
|
|
1162
1162
|
var S52_STROKE_BOOST = 1;
|
|
1163
1163
|
|
|
1164
|
+
// src/atlas/map-live.ts
|
|
1165
|
+
function isMapLive(map) {
|
|
1166
|
+
return map._removed !== true;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1164
1169
|
// src/atlas/symbol-images-browser.ts
|
|
1165
1170
|
function dataUrl(svg2) {
|
|
1166
1171
|
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg2)}`;
|
|
@@ -1301,9 +1306,9 @@ function existingSize(map, id) {
|
|
|
1301
1306
|
async function loadSymbolImageSpecs(map, specs) {
|
|
1302
1307
|
await Promise.all(
|
|
1303
1308
|
specs.map(async (spec) => {
|
|
1304
|
-
if (map.hasImage(spec.id)) return;
|
|
1309
|
+
if (!isMapLive(map) || map.hasImage(spec.id)) return;
|
|
1305
1310
|
const img = await decode(spec);
|
|
1306
|
-
if (!img || map.hasImage(spec.id)) return;
|
|
1311
|
+
if (!img || !isMapLive(map) || map.hasImage(spec.id)) return;
|
|
1307
1312
|
map.addImage(spec.id, img, { sdf: spec.sdf, pixelRatio: spec.pixelRatio });
|
|
1308
1313
|
})
|
|
1309
1314
|
);
|
|
@@ -1327,7 +1332,7 @@ async function rethemeSymbolImages(map, specs, opts = {}) {
|
|
|
1327
1332
|
}
|
|
1328
1333
|
const sources = await sourcesFor(unit, decoded[u]);
|
|
1329
1334
|
for (let i = 0; i < unitSpecs.length; i++) {
|
|
1330
|
-
if (opts.signal?.aborted) {
|
|
1335
|
+
if (opts.signal?.aborted || !isMapLive(map)) {
|
|
1331
1336
|
result.aborted = true;
|
|
1332
1337
|
break outer;
|
|
1333
1338
|
}
|
|
@@ -1361,6 +1366,10 @@ async function rethemeSymbolImages(map, specs, opts = {}) {
|
|
|
1361
1366
|
}
|
|
1362
1367
|
}
|
|
1363
1368
|
}
|
|
1369
|
+
if (!isMapLive(map)) {
|
|
1370
|
+
result.aborted = true;
|
|
1371
|
+
return result;
|
|
1372
|
+
}
|
|
1364
1373
|
map.triggerRepaint();
|
|
1365
1374
|
opts.onProgress?.({ done, total });
|
|
1366
1375
|
return result;
|
|
@@ -1537,7 +1546,7 @@ async function imageToBitmap(img, size) {
|
|
|
1537
1546
|
}
|
|
1538
1547
|
}
|
|
1539
1548
|
function addImageToMap(map, name, image) {
|
|
1540
|
-
if (map.hasImage(name)) return;
|
|
1549
|
+
if (!isMapLive(map) || map.hasImage(name)) return;
|
|
1541
1550
|
try {
|
|
1542
1551
|
map.addImage(name, image, {
|
|
1543
1552
|
sdf: SDF_SYMBOLS2.has(name),
|
|
@@ -1564,6 +1573,7 @@ async function loadS52SymbolImagesCached(map, colorTable = "DAY", opts = {}) {
|
|
|
1564
1573
|
cached = /* @__PURE__ */ new Map();
|
|
1565
1574
|
}
|
|
1566
1575
|
}
|
|
1576
|
+
if (!isMapLive(map)) return;
|
|
1567
1577
|
for (const [name, bitmap] of cached) {
|
|
1568
1578
|
addImageToMap(map, name, bitmap);
|
|
1569
1579
|
}
|
|
@@ -1577,7 +1587,7 @@ async function loadS52SymbolImagesCached(map, colorTable = "DAY", opts = {}) {
|
|
|
1577
1587
|
const size = rasterSize2(name, themed);
|
|
1578
1588
|
const img = await decodeSvgToImage(themed, size);
|
|
1579
1589
|
if (!img) return;
|
|
1580
|
-
addImageToMap(map, name, img);
|
|
1590
|
+
if (isMapLive(map)) addImageToMap(map, name, img);
|
|
1581
1591
|
try {
|
|
1582
1592
|
const bitmap = await imageToBitmap(img, size);
|
|
1583
1593
|
if (!bitmap) return;
|
|
@@ -15592,7 +15602,7 @@ async function loadSymbolImages(map) {
|
|
|
15592
15602
|
const svgStr = sym.getSvg();
|
|
15593
15603
|
const img = new Image(SIZE2, SIZE2);
|
|
15594
15604
|
img.onload = () => {
|
|
15595
|
-
if (!map.hasImage(sym.id)) {
|
|
15605
|
+
if (isMapLive(map) && !map.hasImage(sym.id)) {
|
|
15596
15606
|
map.addImage(sym.id, img, { sdf: false });
|
|
15597
15607
|
}
|
|
15598
15608
|
resolve();
|
|
@@ -15622,7 +15632,7 @@ async function loadThemedBridgeImages(map, colorTable) {
|
|
|
15622
15632
|
const svgStr = sym.getThemedSvg(chblk, trfcd);
|
|
15623
15633
|
const img = new Image(SIZE2, SIZE2);
|
|
15624
15634
|
img.onload = () => {
|
|
15625
|
-
if (!map.hasImage(sym.id)) {
|
|
15635
|
+
if (isMapLive(map) && !map.hasImage(sym.id)) {
|
|
15626
15636
|
map.addImage(sym.id, img, { sdf: false });
|
|
15627
15637
|
}
|
|
15628
15638
|
resolve();
|
|
@@ -15740,6 +15750,126 @@ function themeChrtGlyph(svg2, palette = "DAY") {
|
|
|
15740
15750
|
);
|
|
15741
15751
|
}
|
|
15742
15752
|
|
|
15753
|
+
// src/atlas/base-layers.ts
|
|
15754
|
+
var UNCHARTED_PATTERN = "chrt:hatch-uncharted";
|
|
15755
|
+
function inlandHatchColor(palette) {
|
|
15756
|
+
return (BASE_THEMES[palette] ?? BASE_THEMES.DAY).inlandHatch;
|
|
15757
|
+
}
|
|
15758
|
+
function basePatternSvg(name, palette) {
|
|
15759
|
+
const raw = CHRT_GLYPH_SVGS[name];
|
|
15760
|
+
if (!raw) return null;
|
|
15761
|
+
if (`${CHRT_ICON_PREFIX}${name}` === UNCHARTED_PATTERN) {
|
|
15762
|
+
return raw.replace(/stroke="#[0-9a-fA-F]{3,6}"/g, `stroke="${inlandHatchColor(palette)}"`);
|
|
15763
|
+
}
|
|
15764
|
+
return themeChrtGlyph(raw, palette);
|
|
15765
|
+
}
|
|
15766
|
+
var BASE_THEMES = {
|
|
15767
|
+
DAY: {
|
|
15768
|
+
// ── `earth` is the jurisdiction tan DIMMED (~0.55), not a blue-grey ──
|
|
15769
|
+
// Outside the jurisdiction the map still owes the reader "what is land".
|
|
15770
|
+
// State 3 in the header is "the geography, under a crosshatch", and until
|
|
15771
|
+
// 2026-08-31 that geography was unreadable: earth #2A3439 against sea
|
|
15772
|
+
// #1B2328 measured 1.25:1, so Hispaniola showed charted boxes with tan
|
|
15773
|
+
// land inside and nothing recognisable around them (owner report: "no
|
|
15774
|
+
// land painting down near the DR at some zooms"). Land is warm and sea is
|
|
15775
|
+
// cool because that is what land and sea are; dimming the JURISDICTION
|
|
15776
|
+
// tan rather than inventing a colour says the true thing — this is the
|
|
15777
|
+
// same land, we simply do not carry charts for it. Held against BOTH
|
|
15778
|
+
// neighbours: legible over sea, and never mistakable for the full tan of
|
|
15779
|
+
// a country we do carry. DAY 3.59:1 vs sea, 3.17:1 vs tan.
|
|
15780
|
+
earth: "#807761",
|
|
15781
|
+
water: "#1B2328",
|
|
15782
|
+
river: "#24313A",
|
|
15783
|
+
boundary: "#4A575E",
|
|
15784
|
+
jurisdiction: "#D4C69D",
|
|
15785
|
+
// CHRTland's own tan — one land colour
|
|
15786
|
+
inlandWater: "#B9CFDD",
|
|
15787
|
+
// muted, unmistakably not a charted blue
|
|
15788
|
+
// The uncharted hatch's stripe, held to 2.3:1 against `inlandWater` in
|
|
15789
|
+
// every palette (`inland-hatch-contrast.test.ts`) — the hatch says "we have
|
|
15790
|
+
// not charted this water", and stripes the eye cannot find say nothing.
|
|
15791
|
+
// DAY 3.08:1, darker than its ground. Also the colour the artwork file
|
|
15792
|
+
// itself is drawn in.
|
|
15793
|
+
inlandHatch: "#5B7383",
|
|
15794
|
+
// State furniture. These three were referenced by the layers but missing
|
|
15795
|
+
// from this table (and its type — the long-standing TS2339 trio), so the
|
|
15796
|
+
// lines shipped on MapLibre's DEFAULT black. Now deliberate: a hairline
|
|
15797
|
+
// a shade deeper than the tan, quiet caps, tan halo.
|
|
15798
|
+
stateLine: "#B1A077",
|
|
15799
|
+
stateLabel: "#8E8468",
|
|
15800
|
+
stateHalo: "#D4C69D",
|
|
15801
|
+
// Rough-lake furniture (owner ask 2026-08-28: "the lakes need labels,
|
|
15802
|
+
// and shorelines drawn"): a shoreline a shade deeper than the rough
|
|
15803
|
+
// water, and a name in the water's own voice, haloed by the water.
|
|
15804
|
+
inlandShore: "#8FA9BA",
|
|
15805
|
+
inlandLabel: "#54707F",
|
|
15806
|
+
inlandHalo: "#B9CFDD"
|
|
15807
|
+
},
|
|
15808
|
+
DUSK: {
|
|
15809
|
+
// ── Re-derived 2026-09-01: the basemap sits UNDER the chart ──────────
|
|
15810
|
+
// These were the DAY tones scaled by a constant, never measured against
|
|
15811
|
+
// the DUSK chart — so `jurisdiction` came out at L=0.506 against a chart
|
|
15812
|
+
// whose own land (LANDA #8a7d5e) is L=0.209, i.e. the context layer was
|
|
15813
|
+
// 2.4x brighter than the data layer. Now capped at the chart's own land.
|
|
15814
|
+
// The rest of the block rides the same 0.666 channel factor so every
|
|
15815
|
+
// relationship the palette already chose is preserved, only moved down.
|
|
15816
|
+
// `water` is deliberately NOT scaled: it is the reference the land/sea
|
|
15817
|
+
// floor is measured against.
|
|
15818
|
+
earth: "#544E3E",
|
|
15819
|
+
water: "#161D21",
|
|
15820
|
+
river: "#141B20",
|
|
15821
|
+
boundary: "#293135",
|
|
15822
|
+
jurisdiction: "#867D64",
|
|
15823
|
+
inlandWater: "#62717A",
|
|
15824
|
+
// Owner's choice, 2026-10-04: dark stripes, 2.37:1. The DAY stripe here
|
|
15825
|
+
// was 1.02:1 — invisible, so an uncharted lake read as plain water.
|
|
15826
|
+
inlandHatch: "#2E383F",
|
|
15827
|
+
// Label stays DARK here — the halo is still mid-toned, so 3:1 is reachable
|
|
15828
|
+
// downward and a bright label would be a new light source at dusk.
|
|
15829
|
+
stateLine: "#B5A987",
|
|
15830
|
+
stateLabel: "#38342A",
|
|
15831
|
+
stateHalo: "#867D64",
|
|
15832
|
+
inlandShore: "#4D5B67",
|
|
15833
|
+
inlandLabel: "#212629",
|
|
15834
|
+
inlandHalo: "#62717A"
|
|
15835
|
+
},
|
|
15836
|
+
NIGHT: {
|
|
15837
|
+
// ── Re-derived 2026-09-01 — the owner-reported night defect ──────────
|
|
15838
|
+
// `jurisdiction` was L=0.240 against a chart whose own land (LANDA
|
|
15839
|
+
// #3a3020) is L=0.031: the world basemap was 7.7x BRIGHTER than the chart
|
|
15840
|
+
// it sits under, so at z<11 the uncharted interior read as a pale
|
|
15841
|
+
// continent-sized slab on a night chart. Capped at the chart's own land.
|
|
15842
|
+
//
|
|
15843
|
+
// ⚠ `earth` AND `jurisdiction` CONVERGE HERE, and that is forced, not
|
|
15844
|
+
// sloppy. Holding earth 2:1 above the ocean AND 2:1 below the jurisdiction
|
|
15845
|
+
// tan requires jurisdiction >= L 0.174 — 5.6x the chart's own land. At
|
|
15846
|
+
// night the range between the ocean (L 0.006) and the cap (L 0.031) is too
|
|
15847
|
+
// compressed to carry two land tones. COVERAGE IS STILL SAID, by the
|
|
15848
|
+
// `chrt:hatch-nocoverage` crosshatch, which is a stronger signal than tone
|
|
15849
|
+
// and is the one thing that cannot be lost to palette compression.
|
|
15850
|
+
//
|
|
15851
|
+
// Labels invert to light-on-dark: 3:1 downward is unreachable off a halo
|
|
15852
|
+
// this close to black, and CHRTland's own night town labels already sit at
|
|
15853
|
+
// L=0.177 for exactly this reason.
|
|
15854
|
+
earth: "#333026",
|
|
15855
|
+
water: "#10161A",
|
|
15856
|
+
river: "#090D0F",
|
|
15857
|
+
boundary: "#131719",
|
|
15858
|
+
jurisdiction: "#353128",
|
|
15859
|
+
inlandWater: "#232A30",
|
|
15860
|
+
// The DAY stripe, lighter than this ground: 2.92:1, and L 0.16, under the
|
|
15861
|
+
// NIGHT muted-ink ceiling (`night-luminance.test.ts`). Red night draws the
|
|
15862
|
+
// NIGHT chart, so this is its stripe too.
|
|
15863
|
+
inlandHatch: "#5B7383",
|
|
15864
|
+
stateLine: "#484236",
|
|
15865
|
+
stateLabel: "#837963",
|
|
15866
|
+
stateHalo: "#353128",
|
|
15867
|
+
inlandShore: "#1C2227",
|
|
15868
|
+
inlandLabel: "#617484",
|
|
15869
|
+
inlandHalo: "#232A30"
|
|
15870
|
+
}
|
|
15871
|
+
};
|
|
15872
|
+
|
|
15743
15873
|
// src/atlas/chrtraster-svgs-generated.ts
|
|
15744
15874
|
var CHRTRASTER_SVGS = {
|
|
15745
15875
|
"BCNGEN05": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M 11.6 26.8 L 11.6 1.0 L 20.0 1.0 L 20.0 27.2 L 19.4 25.8 L 19.3 24.7 L 18.9 24.3 L 17.6 23.7 L 16.5 23.4 L 16.2 23.2 L 15.1 23.4 L 14.5 23.4 L 13.8 23.7 L 13.3 24.5 L 12.7 25.3 L 12.0 25.8 L 11.6 26.6 Z" fill="CHWHT" /><path d="M 11.6 26.8 L 11.6 1.0 L 20.0 1.0 L 20.0 27.2 L 19.4 25.8 L 19.3 24.7 L 18.9 24.3 L 17.6 23.7 L 16.5 23.4 L 16.2 23.2 L 15.1 23.4 L 14.5 23.4 L 13.8 23.7 L 13.3 24.5 L 12.7 25.3 L 12.0 25.8 L 11.6 26.6 Z" fill="none" stroke="CHBLK" stroke-width="1.2" stroke-linejoin="round" /><circle cx="16.00" cy="27.21" r="3.79" fill="none" stroke="CHBLK" stroke-width="1.2" /><path d="M 7.3 27.2 L 12.2 27.2" fill="none" stroke="CHBLK" stroke-width="1.2" /><path d="M 19.8 27.2 L 24.7 27.2" fill="none" stroke="CHBLK" stroke-width="1.2" /></svg>',
|
|
@@ -16339,10 +16469,9 @@ function declared(id, svg2) {
|
|
|
16339
16469
|
}
|
|
16340
16470
|
function basePatternImageSpecs(palette) {
|
|
16341
16471
|
return BASE_PATTERN_GLYPHS.flatMap((name) => {
|
|
16342
|
-
const
|
|
16343
|
-
if (!
|
|
16472
|
+
const svg2 = basePatternSvg(name, palette);
|
|
16473
|
+
if (!svg2) return [];
|
|
16344
16474
|
const id = `${CHRT_ICON_PREFIX}${name}`;
|
|
16345
|
-
const svg2 = themeChrtGlyph(raw, palette);
|
|
16346
16475
|
return [{ id, svg: svg2, ...declared(id, svg2), pixelRatio: 1, sdf: false }];
|
|
16347
16476
|
});
|
|
16348
16477
|
}
|