mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
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:
1 parent
34d43c7e3f
commit
e214129e45
41 files changed
+335
-909
No files matched your search
@@ -5,19 +5,15 @@ import {
|
||||
mapillaryProxy,
|
||||
TILE_ROUTE_MAX_PER_MIN,
|
||||
} from 'gods-eye-view/server/providers/mapillary';
|
||||
import {
|
||||
listTileLayers,
|
||||
stripTileLayers,
|
||||
} from '../../server/providers/mapillary/trim.js';
|
||||
import { stripTileLayers } from '../../server/providers/mapillary/trim.js';
|
||||
import {
|
||||
TILE_MAX_BYTES,
|
||||
TILE_MEMORY_BUDGET_BYTES,
|
||||
TILE_MEMORY_MAX_TILES,
|
||||
TILE_UPSTREAM_CONCURRENCY,
|
||||
TILE_TTL_MS,
|
||||
fetchTile,
|
||||
parseTilePath,
|
||||
_resetTileCacheForTest,
|
||||
_tileMemoryForTest,
|
||||
} from '../../server/providers/mapillary/tiles.js';
|
||||
|
||||
/** Build a minimal MVT: layers with a name, a version and one opaque feature. */
|
||||
@@ -545,26 +541,18 @@ test('a tile held in memory past its TTL is fetched again', async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test('the memory cache evicts the least recently used tile past its budget', async () => {
|
||||
const MB = 1024 * 1024;
|
||||
// Three 30 MB tiles fit in the budget, a fourth does not.
|
||||
const big = tile([{ name: 'sequence', payload: Buffer.alloc(30 * MB) }]);
|
||||
assert.ok(3 * big.length < TILE_MEMORY_BUDGET_BYTES);
|
||||
assert.ok(4 * big.length > TILE_MEMORY_BUDGET_BYTES);
|
||||
test('the memory cache evicts the least recently used tile past its size', async () => {
|
||||
await withUpstream(
|
||||
() => ok(big),
|
||||
async ({ calls, call }) => {
|
||||
() => ok(),
|
||||
async ({ calls }) => {
|
||||
const get = async (x) =>
|
||||
(await call('/api/mapillary/tiles', `/coverage/14/${x}/20`)).headers[
|
||||
'x-gev-cache'
|
||||
];
|
||||
for (const x of [20, 21, 22]) assert.equal(await get(x), 'upstream');
|
||||
assert.equal(await get(20), 'memory', 'A is used again');
|
||||
assert.equal(await get(23), 'upstream');
|
||||
assert.equal(_tileMemoryForTest().entries, 3);
|
||||
for (const x of [20, 22, 23]) assert.equal(await get(x), 'memory');
|
||||
assert.equal(await get(21), 'upstream', 'B was evicted');
|
||||
assert.equal(calls.length, 5);
|
||||
(await fetchTile(parseTilePath(`/coverage/14/${x}/20`))).source;
|
||||
for (let x = 0; x < TILE_MEMORY_MAX_TILES; x++) await get(x);
|
||||
assert.equal(await get(0), 'memory', 'the first is used again');
|
||||
await get(TILE_MEMORY_MAX_TILES);
|
||||
assert.equal(await get(0), 'memory', 'so it stays');
|
||||
assert.equal(await get(1), 'upstream', 'the least recently used went');
|
||||
assert.equal(calls.length, TILE_MEMORY_MAX_TILES + 2);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -578,7 +566,6 @@ test('dropping a layer keeps the others byte-for-byte', () => {
|
||||
{ name: 'overview' },
|
||||
]);
|
||||
const trimmed = stripTileLayers(bytes, ['image']);
|
||||
assert.deepEqual(listTileLayers(trimmed), ['sequence', 'overview']);
|
||||
assert.deepEqual(trimmed, tile([{ name: 'sequence' }, { name: 'overview' }]));
|
||||
});
|
||||
|
||||
|
||||
@@ -1,89 +1,14 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { fixtureTile } from '../../scripts/qa-street-level.mjs';
|
||||
import {
|
||||
answerMapillaryRequest,
|
||||
describeCall,
|
||||
PHOTO_LINE,
|
||||
PHOTO_SEQUENCE_ID,
|
||||
photoImages,
|
||||
THUMB_HOST,
|
||||
} from '../../scripts/fixtures/street-level/mapillaryGraph.mjs';
|
||||
import { redact } from '../../scripts/qa-browserEvidence.mjs';
|
||||
|
||||
test('fixture tiles give the hermetic gate something real to filter', async () => {
|
||||
const { decodeCoverageTile } =
|
||||
await import('../layers/streetLevel/providers/mapillary/decode.js');
|
||||
const now = Date.UTC(2026, 9, 1);
|
||||
const street = decodeCoverageTile(fixtureTile(14, 2662, 6286, now), {
|
||||
x: 2662,
|
||||
y: 6286,
|
||||
z: 14,
|
||||
});
|
||||
const grid = street.sequences.filter((s) => s.id !== PHOTO_SEQUENCE_ID);
|
||||
assert.equal(grid.length, 8);
|
||||
const pano = grid.filter((s) => s.isPano).length;
|
||||
assert.ok(pano > 0 && pano < 8, '360° and flat both present');
|
||||
const year = 365 * 86_400_000;
|
||||
const old = grid.filter((s) => now - s.capturedAt > year).length;
|
||||
assert.ok(old > 0 && old < 8, 'recent and older both present');
|
||||
assert.equal(fixtureTile(3, 1, 3, now).length, 0, 'nothing from orbit');
|
||||
assert.equal(fixtureTile(8, 1, 1, now).length, 0, 'nor in between');
|
||||
});
|
||||
|
||||
/* ── Hermetic photo flow: the photo line and the Graph fixtures ────────── */
|
||||
|
||||
test('the photo sequence runs through the parked view, clipped to each tile it crosses', async () => {
|
||||
const { decodeCoverageTile } =
|
||||
await import('../layers/streetLevel/providers/mapillary/decode.js');
|
||||
const { lonToTileX, latToTileY } =
|
||||
await import('../layers/streetLevel/tileMath.js');
|
||||
const x = lonToTileX(PHOTO_LINE.lon, 14);
|
||||
const rows = new Set(
|
||||
[PHOTO_LINE.south, PHOTO_LINE.north].map((lat) => latToTileY(lat, 14)),
|
||||
);
|
||||
assert.ok(rows.size > 1, 'the line crosses a tile edge');
|
||||
let south = Infinity;
|
||||
let north = -Infinity;
|
||||
for (const y of rows) {
|
||||
const line = decodeCoverageTile(fixtureTile(14, x, y), {
|
||||
x,
|
||||
y,
|
||||
z: 14,
|
||||
}).sequences.find((s) => s.id === PHOTO_SEQUENCE_ID);
|
||||
assert.ok(line, `tile 14/${x}/${y} carries the photo line`);
|
||||
for (const [lon, lat] of line.parts[0]) {
|
||||
assert.ok(Math.abs(lon - PHOTO_LINE.lon) < 1e-4);
|
||||
south = Math.min(south, lat);
|
||||
north = Math.max(north, lat);
|
||||
}
|
||||
}
|
||||
assert.ok(Math.abs(south - PHOTO_LINE.south) < 1e-4);
|
||||
assert.ok(Math.abs(north - PHOTO_LINE.north) < 1e-4);
|
||||
// Far away there is no photo line, only the grid.
|
||||
const elsewhere = decodeCoverageTile(fixtureTile(14, 100, 100), {
|
||||
x: 100,
|
||||
y: 100,
|
||||
z: 14,
|
||||
});
|
||||
assert.equal(
|
||||
elsewhere.sequences.some((s) => s.id === PHOTO_SEQUENCE_ID),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test('the fixture photos: one sequence, 360° and flat, spaced past the cone thinning', () => {
|
||||
const images = photoImages(Date.UTC(2026, 9, 1));
|
||||
assert.ok(images.length >= 3);
|
||||
assert.ok(images.some((image) => image.isPano));
|
||||
assert.ok(images.some((image) => !image.isPano));
|
||||
for (let i = 1; i < images.length; i++) {
|
||||
const gap = (images[i].lat - images[i - 1].lat) * 110_540;
|
||||
assert.ok(gap > 3, `gap ${gap}`);
|
||||
}
|
||||
assert.equal(new Set(images.map((image) => image.id)).size, images.length);
|
||||
});
|
||||
|
||||
const graph = (path, params) =>
|
||||
`https://graph.mapillary.com/${path}?${new URLSearchParams({ access_token: 'MLY|0|qa-fixture', ...params })}`;
|
||||
const answer = (url, method = 'GET') => {
|
||||
@@ -94,22 +19,6 @@ const answer = (url, method = 'GET') => {
|
||||
};
|
||||
};
|
||||
|
||||
test('the Graph fixtures answer the provider’s sequence lookups', () => {
|
||||
const images = photoImages();
|
||||
const sequence = answer(
|
||||
graph('images', {
|
||||
sequence_ids: PHOTO_SEQUENCE_ID,
|
||||
fields: 'id,geometry,compass_angle,captured_at,is_pano',
|
||||
limit: '2000',
|
||||
}),
|
||||
);
|
||||
assert.equal(sequence.json.data.length, images.length);
|
||||
assert.deepEqual(
|
||||
answer(graph('images', { sequence_ids: 'other', fields: 'id' })).json.data,
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
test('the Graph fixtures answer every call MapillaryJS makes to open a photo', () => {
|
||||
const [first, second] = photoImages();
|
||||
const spatial = answer(
|
||||
|
||||
Reference in new issue
Block a user