refactor(street-level): drop hover, legend and byte-budget cache; flatten the provider directory

- No pointer cursor over coverage lines (no other layer has one); a click
  test replaces the hover tests.
- No legend: one source, one colour.
- The on-globe credit lives in index.js; credits.js is gone.
- Billboard glyphs are SVG data URLs with the same geometry, so tests need
  no canvas stub.
- The tile cache keeps the 120 most recently used tiles, same 24 h TTL,
  instead of a byte budget; listTileLayers was test-only.
- providers/mapillary/ is flattened into the layer and its policy merged
  into one policy.js; the source's method list is a literal in the catalog.
- One fakeCesiumViewer for the layer tests.
- The fixture gate keeps the steps the unit tests do not cover: coverage and
  credit, a rejected key, line -> cone -> photo, close, a keyless page.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
German KhokhlovandClaude Fable 5.1 committed 2026-10-08 19:17:45 +04:00
1 parent 34d43c7e3f
commit e214129e45
41 files changed
+335 -909

No files matched your search

+13 -29
View File
@@ -18,9 +18,8 @@ const FETCH_TIMEOUT_MS = 60_000;
export const TILE_UPSTREAM_CONCURRENCY = 6;
/** Tiles change only when new imagery is processed. */
export const TILE_TTL_MS = 24 * 60 * 60 * 1000;
export const TILE_MEMORY_BUDGET_BYTES = 96 * 1024 * 1024;
/** Per-entry cost on top of its bytes, so empty tiles still count. */
const ENTRY_OVERHEAD_BYTES = 1024;
/** Trimmed tiles are ~80 KB: a dozen 9-tile views, well under 100 MB. */
export const TILE_MEMORY_MAX_TILES = 120;
/** The client token lives in the browser by design; the server adds it to tile URLs too. */
export function mapillaryToken() {
@@ -139,39 +138,30 @@ function unlessAborted(promise, signal) {
});
}
/** @type {Map<string, {bytes: Buffer, at: number}>} insertion-ordered LRU */
/** @type {Map<string, {bytes: Buffer, at: number}>} least recently used first */
const memory = new Map();
let memoryBytes = 0;
/**
* Upstream fetches shared per tile, with the requests still waiting on each.
* @type {Map<string, {promise: Promise<Buffer>, controller: AbortController, waiters: number}>}
*/
const inFlight = new Map();
const cost = (bytes) => bytes.length + ENTRY_OVERHEAD_BYTES;
function forget(key) {
const hit = memory.get(key);
if (!hit) return null;
memory.delete(key);
memoryBytes -= cost(hit.bytes);
return hit;
}
function remember(key, bytes, at = Date.now()) {
// One huge (untrimmed) tile must not flush everything else.
if (cost(bytes) > TILE_MEMORY_BUDGET_BYTES / 2) return;
forget(key);
memory.delete(key);
memory.set(key, { bytes, at });
memoryBytes += cost(bytes);
for (const [oldest] of memory) {
if (memoryBytes <= TILE_MEMORY_BUDGET_BYTES) break;
forget(oldest);
for (const oldest of memory.keys()) {
if (memory.size <= TILE_MEMORY_MAX_TILES) break;
memory.delete(oldest);
}
}
function recall(key) {
const hit = forget(key);
if (!hit || Date.now() - hit.at > TILE_TTL_MS) return null;
const hit = memory.get(key);
if (!hit) return null;
if (Date.now() - hit.at > TILE_TTL_MS) {
memory.delete(key);
return null;
}
remember(key, hit.bytes, hit.at); // most recently used again
return hit.bytes;
}
@@ -225,11 +215,5 @@ export async function fetchTile(address, { signal } = {}) {
/** Test seam: forget every cached and in-flight tile. */
export function _resetTileCacheForTest() {
memory.clear();
memoryBytes = 0;
inFlight.clear();
}
/** Test seam: how many tiles memory holds and the bytes charged for them. */
export function _tileMemoryForTest() {
return { entries: memory.size, bytes: memoryBytes };
}
-13
View File
@@ -41,16 +41,3 @@ export function stripTileLayers(bytes, dropNames) {
}, null);
return dropped ? Buffer.from(writer.finish()) : bytes;
}
/** Names of the layers inside a vector tile, in order. */
export function listTileLayers(bytes) {
const names = [];
if (!bytes?.length) return names;
new PbfReader(bytes).readFields((tag, _result, pbf) => {
if (tag !== TILE_LAYER_FIELD) return;
const end = pbf.readVarint() + pbf.pos;
names.push(layerName(bytes.subarray(pbf.pos, end)));
pbf.pos = end;
}, null);
return names;
}