@chrtco/sdk 0.10.43 → 0.10.45
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 +12 -0
- package/dist/atlas/browser.cjs +135 -36
- package/dist/atlas/browser.cjs.map +1 -1
- package/dist/atlas/browser.d.cts +26 -0
- package/dist/atlas/browser.d.ts +26 -0
- package/dist/atlas/browser.js +135 -36
- package/dist/atlas/browser.js.map +1 -1
- package/dist/index.cjs +106 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -1
- package/dist/index.d.ts +27 -1
- package/dist/index.js +98 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/atlas/browser.d.cts
CHANGED
|
@@ -279,6 +279,32 @@ declare function reloadChrtRasterSymbolImages(map: maplibregl.Map, colorTable: C
|
|
|
279
279
|
* The rasterising itself (SVG → canvas → pixels) is main-thread work, so it
|
|
280
280
|
* runs in slices with a yield between them and reports progress; the chart
|
|
281
281
|
* keeps drawing while the icons come over.
|
|
282
|
+
*
|
|
283
|
+
* ===========================================================================
|
|
284
|
+
* ⚠ ONE SVG DOCUMENT PER SYMBOL STARVES THE MAP. A SWITCH DRAWS FROM SHEETS.
|
|
285
|
+
* ===========================================================================
|
|
286
|
+
*
|
|
287
|
+
* An `<img>` with an SVG data URL is a whole Blink document — its own page,
|
|
288
|
+
* parser, font set, idleness timers, memory registration and mojo pipes — and
|
|
289
|
+
* each one queues a dozen-odd small main-thread tasks, some long after
|
|
290
|
+
* `decode()` resolves. 569 of them queued 6,452 tasks at once (Chrome trace,
|
|
291
|
+
* palette harness, 2026-10-03), and MapLibre's worker messages waited behind
|
|
292
|
+
* them: the paint diff's tile reload, answered by the workers in ~30 ms, was
|
|
293
|
+
* picked up by the main thread 21 s and then 27 s later at 4x CPU throttle.
|
|
294
|
+
* Until it lands, data-driven paint — soundings, light circles — keeps its
|
|
295
|
+
* DAY colours. One decode in flight at a time did not help; fewer DOCUMENTS
|
|
296
|
+
* did. `rethemeSymbolImages` therefore draws up to `SHEET_MAX_SYMBOLS`
|
|
297
|
+
* symbols from ONE document — a column of nested `<svg>`s at whole-pixel
|
|
298
|
+
* rows — and rasterises each symbol by drawing that sheet shifted up by the
|
|
299
|
+
* symbol's row onto a canvas the symbol's size.
|
|
300
|
+
*
|
|
301
|
+
* ⚠ SHIFT THE SHEET; DO NOT CUT ROWS OUT OF ONE DRAW. Drawn once and cut,
|
|
302
|
+
* ~15% of symbols moved an anti-aliased edge pixel (up to 64/255): a symbol
|
|
303
|
+
* at row y is painted at y + s·p, which rounds differently in float from the
|
|
304
|
+
* s·p it gets alone. Shifted by −y, the canvas and sheet translations cancel
|
|
305
|
+
* in the matrix (−y + y = 0, exactly) before any point is mapped, and every
|
|
306
|
+
* symbol of every family and palette comes out byte-identical to its own
|
|
307
|
+
* document (`scripts/perf/palette-switch/verify-run.mjs`).
|
|
282
308
|
*/
|
|
283
309
|
|
|
284
310
|
interface SymbolImageProgress {
|
package/dist/atlas/browser.d.ts
CHANGED
|
@@ -279,6 +279,32 @@ declare function reloadChrtRasterSymbolImages(map: maplibregl.Map, colorTable: C
|
|
|
279
279
|
* The rasterising itself (SVG → canvas → pixels) is main-thread work, so it
|
|
280
280
|
* runs in slices with a yield between them and reports progress; the chart
|
|
281
281
|
* keeps drawing while the icons come over.
|
|
282
|
+
*
|
|
283
|
+
* ===========================================================================
|
|
284
|
+
* ⚠ ONE SVG DOCUMENT PER SYMBOL STARVES THE MAP. A SWITCH DRAWS FROM SHEETS.
|
|
285
|
+
* ===========================================================================
|
|
286
|
+
*
|
|
287
|
+
* An `<img>` with an SVG data URL is a whole Blink document — its own page,
|
|
288
|
+
* parser, font set, idleness timers, memory registration and mojo pipes — and
|
|
289
|
+
* each one queues a dozen-odd small main-thread tasks, some long after
|
|
290
|
+
* `decode()` resolves. 569 of them queued 6,452 tasks at once (Chrome trace,
|
|
291
|
+
* palette harness, 2026-10-03), and MapLibre's worker messages waited behind
|
|
292
|
+
* them: the paint diff's tile reload, answered by the workers in ~30 ms, was
|
|
293
|
+
* picked up by the main thread 21 s and then 27 s later at 4x CPU throttle.
|
|
294
|
+
* Until it lands, data-driven paint — soundings, light circles — keeps its
|
|
295
|
+
* DAY colours. One decode in flight at a time did not help; fewer DOCUMENTS
|
|
296
|
+
* did. `rethemeSymbolImages` therefore draws up to `SHEET_MAX_SYMBOLS`
|
|
297
|
+
* symbols from ONE document — a column of nested `<svg>`s at whole-pixel
|
|
298
|
+
* rows — and rasterises each symbol by drawing that sheet shifted up by the
|
|
299
|
+
* symbol's row onto a canvas the symbol's size.
|
|
300
|
+
*
|
|
301
|
+
* ⚠ SHIFT THE SHEET; DO NOT CUT ROWS OUT OF ONE DRAW. Drawn once and cut,
|
|
302
|
+
* ~15% of symbols moved an anti-aliased edge pixel (up to 64/255): a symbol
|
|
303
|
+
* at row y is painted at y + s·p, which rounds differently in float from the
|
|
304
|
+
* s·p it gets alone. Shifted by −y, the canvas and sheet translations cancel
|
|
305
|
+
* in the matrix (−y + y = 0, exactly) before any point is mapped, and every
|
|
306
|
+
* symbol of every family and palette comes out byte-identical to its own
|
|
307
|
+
* document (`scripts/perf/palette-switch/verify-run.mjs`).
|
|
282
308
|
*/
|
|
283
309
|
|
|
284
310
|
interface SymbolImageProgress {
|
package/dist/atlas/browser.js
CHANGED
|
@@ -1127,17 +1127,110 @@ async function decode(spec) {
|
|
|
1127
1127
|
}
|
|
1128
1128
|
}
|
|
1129
1129
|
var scratch = null;
|
|
1130
|
-
function
|
|
1130
|
+
function scratchContext() {
|
|
1131
1131
|
if (!scratch) {
|
|
1132
|
-
const
|
|
1133
|
-
scratch =
|
|
1132
|
+
const canvas = document.createElement("canvas");
|
|
1133
|
+
scratch = canvas.getContext("2d", { willReadFrequently: true });
|
|
1134
1134
|
if (!scratch) throw new Error("symbol-images: no 2d canvas context");
|
|
1135
1135
|
}
|
|
1136
|
-
|
|
1136
|
+
return scratch;
|
|
1137
|
+
}
|
|
1138
|
+
function rasterise(src, spec) {
|
|
1139
|
+
const ctx = scratchContext();
|
|
1140
|
+
const canvas = ctx.canvas;
|
|
1137
1141
|
canvas.width = spec.width;
|
|
1138
1142
|
canvas.height = spec.height;
|
|
1139
|
-
|
|
1140
|
-
return
|
|
1143
|
+
ctx.drawImage(src.img, 0, -src.y, src.width, src.height);
|
|
1144
|
+
return ctx.getImageData(0, 0, spec.width, spec.height);
|
|
1145
|
+
}
|
|
1146
|
+
var SHEET_MAX_SYMBOLS = 32;
|
|
1147
|
+
var SHEET_MAX_HEIGHT = 4096;
|
|
1148
|
+
var ROOT = /^\s*<svg\b([^>]*)>[\s\S]*<\/svg>\s*$/;
|
|
1149
|
+
var NOT_SHAREABLE = /<\?xml|<!DOCTYPE|<style\b|<script\b|<foreignObject\b|<svg\b[\s\S]*<svg\b/i;
|
|
1150
|
+
function rootSize(attrs) {
|
|
1151
|
+
const w = /\swidth="(\d+(?:\.\d+)?)"/.exec(attrs);
|
|
1152
|
+
const h = /\sheight="(\d+(?:\.\d+)?)"/.exec(attrs);
|
|
1153
|
+
return w && h ? { w: Number(w[1]), h: Number(h[1]) } : null;
|
|
1154
|
+
}
|
|
1155
|
+
function shareable(spec) {
|
|
1156
|
+
if (!(spec.width > 0 && spec.height > 0) || spec.height > SHEET_MAX_HEIGHT) return null;
|
|
1157
|
+
const root = ROOT.exec(spec.svg);
|
|
1158
|
+
const own = root ? rootSize(root[1]) : null;
|
|
1159
|
+
if (!own || NOT_SHAREABLE.test(spec.svg)) return null;
|
|
1160
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1161
|
+
for (const m of spec.svg.matchAll(/\sid="([^"]*)"/g)) {
|
|
1162
|
+
if (ids.has(m[1])) return null;
|
|
1163
|
+
ids.add(m[1]);
|
|
1164
|
+
}
|
|
1165
|
+
for (const m of spec.svg.matchAll(/url\(\s*['"]?#([^'")\s]*)|href="#([^"]*)"/g)) {
|
|
1166
|
+
if (!ids.has(m[1] ?? m[2])) return null;
|
|
1167
|
+
}
|
|
1168
|
+
return { spec, own, ids };
|
|
1169
|
+
}
|
|
1170
|
+
function sheetOf(run) {
|
|
1171
|
+
let y = 0;
|
|
1172
|
+
let width = 0;
|
|
1173
|
+
const parts = [];
|
|
1174
|
+
const slots = [];
|
|
1175
|
+
for (const { spec, own } of run) {
|
|
1176
|
+
parts.push(
|
|
1177
|
+
`<svg x="0" y="${y}" width="${spec.width}" height="${spec.height}" viewBox="0 0 ${own.w} ${own.h}" preserveAspectRatio="none">${spec.svg}</svg>`
|
|
1178
|
+
);
|
|
1179
|
+
slots.push({ spec, y });
|
|
1180
|
+
y += spec.height;
|
|
1181
|
+
width = Math.max(width, spec.width);
|
|
1182
|
+
}
|
|
1183
|
+
return {
|
|
1184
|
+
svg: `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${y}">${parts.join("")}</svg>`,
|
|
1185
|
+
width,
|
|
1186
|
+
height: y,
|
|
1187
|
+
slots
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
function planRasterUnits(specs) {
|
|
1191
|
+
const units = [];
|
|
1192
|
+
let run = [];
|
|
1193
|
+
let runIds = /* @__PURE__ */ new Set();
|
|
1194
|
+
let runHeight = 0;
|
|
1195
|
+
const flush = () => {
|
|
1196
|
+
if (run.length === 1) units.push({ one: run[0].spec });
|
|
1197
|
+
else if (run.length > 1) units.push({ sheet: sheetOf(run) });
|
|
1198
|
+
run = [];
|
|
1199
|
+
runIds = /* @__PURE__ */ new Set();
|
|
1200
|
+
runHeight = 0;
|
|
1201
|
+
};
|
|
1202
|
+
for (const spec of specs) {
|
|
1203
|
+
const s = shareable(spec);
|
|
1204
|
+
if (!s) {
|
|
1205
|
+
flush();
|
|
1206
|
+
units.push({ one: spec });
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
if (run.length >= SHEET_MAX_SYMBOLS || runHeight + spec.height > SHEET_MAX_HEIGHT || [...s.ids].some((id) => runIds.has(id))) {
|
|
1210
|
+
flush();
|
|
1211
|
+
}
|
|
1212
|
+
run.push(s);
|
|
1213
|
+
runHeight += spec.height;
|
|
1214
|
+
for (const id of s.ids) runIds.add(id);
|
|
1215
|
+
}
|
|
1216
|
+
flush();
|
|
1217
|
+
return units;
|
|
1218
|
+
}
|
|
1219
|
+
async function sourcesFor(unit, decoded) {
|
|
1220
|
+
const img = await decoded;
|
|
1221
|
+
if ("one" in unit) {
|
|
1222
|
+
return [img ? { img, y: 0, width: unit.one.width, height: unit.one.height } : null];
|
|
1223
|
+
}
|
|
1224
|
+
const { sheet } = unit;
|
|
1225
|
+
if (img) return sheet.slots.map(({ y }) => ({ img, y, width: sheet.width, height: sheet.height }));
|
|
1226
|
+
const singles = sheet.slots.map(({ spec }) => decode(spec));
|
|
1227
|
+
const out = [];
|
|
1228
|
+
for (let i = 0; i < singles.length; i++) {
|
|
1229
|
+
const one = await singles[i];
|
|
1230
|
+
const { spec } = sheet.slots[i];
|
|
1231
|
+
out.push(one ? { img: one, y: 0, width: spec.width, height: spec.height } : null);
|
|
1232
|
+
}
|
|
1233
|
+
return out;
|
|
1141
1234
|
}
|
|
1142
1235
|
function yieldToBrowser() {
|
|
1143
1236
|
const scheduler = globalThis.scheduler;
|
|
@@ -1172,45 +1265,51 @@ async function rethemeSymbolImages(map, specs, opts = {}) {
|
|
|
1172
1265
|
const total = specs.length;
|
|
1173
1266
|
const result = { updated: 0, added: 0, failed: 0, aborted: false };
|
|
1174
1267
|
let done = 0;
|
|
1175
|
-
const
|
|
1268
|
+
const units = planRasterUnits(specs);
|
|
1269
|
+
const decoded = units.map((unit) => decode("one" in unit ? unit.one : unit.sheet));
|
|
1176
1270
|
let sliceStart = performance.now();
|
|
1177
1271
|
let lastRepaint = sliceStart;
|
|
1178
|
-
for (let
|
|
1272
|
+
outer: for (let u = 0; u < units.length; u++) {
|
|
1273
|
+
const unit = units[u];
|
|
1274
|
+
const unitSpecs = "one" in unit ? [unit.one] : unit.sheet.slots.map((slot) => slot.spec);
|
|
1179
1275
|
if (opts.signal?.aborted) {
|
|
1180
1276
|
result.aborted = true;
|
|
1181
1277
|
break;
|
|
1182
1278
|
}
|
|
1183
|
-
const
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
const have = existingSize(map, spec.id);
|
|
1194
|
-
if (have && have.w === spec.width && have.h === spec.height) {
|
|
1195
|
-
map.updateImage(spec.id, pixels);
|
|
1196
|
-
result.updated++;
|
|
1279
|
+
const sources = await sourcesFor(unit, decoded[u]);
|
|
1280
|
+
for (let i = 0; i < unitSpecs.length; i++) {
|
|
1281
|
+
if (opts.signal?.aborted) {
|
|
1282
|
+
result.aborted = true;
|
|
1283
|
+
break outer;
|
|
1284
|
+
}
|
|
1285
|
+
const spec = unitSpecs[i];
|
|
1286
|
+
const src = sources[i];
|
|
1287
|
+
if (!src) {
|
|
1288
|
+
result.failed++;
|
|
1197
1289
|
} else {
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1290
|
+
const px = rasterise(src, spec);
|
|
1291
|
+
const have = existingSize(map, spec.id);
|
|
1292
|
+
if (have && have.w === spec.width && have.h === spec.height) {
|
|
1293
|
+
map.updateImage(spec.id, px);
|
|
1294
|
+
result.updated++;
|
|
1295
|
+
} else {
|
|
1296
|
+
if (map.hasImage(spec.id)) map.removeImage(spec.id);
|
|
1297
|
+
map.addImage(spec.id, px, { sdf: spec.sdf, pixelRatio: spec.pixelRatio });
|
|
1298
|
+
result.added++;
|
|
1299
|
+
}
|
|
1300
|
+
opts.onApplied?.(spec.id);
|
|
1201
1301
|
}
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1302
|
+
done++;
|
|
1303
|
+
const now = performance.now();
|
|
1304
|
+
if (now - sliceStart >= sliceMs) {
|
|
1305
|
+
if (now - lastRepaint >= repaintEveryMs) {
|
|
1306
|
+
map.triggerRepaint();
|
|
1307
|
+
opts.onProgress?.({ done, total });
|
|
1308
|
+
lastRepaint = now;
|
|
1309
|
+
}
|
|
1310
|
+
await yieldToBrowser();
|
|
1311
|
+
sliceStart = performance.now();
|
|
1211
1312
|
}
|
|
1212
|
-
await yieldToBrowser();
|
|
1213
|
-
sliceStart = performance.now();
|
|
1214
1313
|
}
|
|
1215
1314
|
}
|
|
1216
1315
|
map.triggerRepaint();
|