mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
refactor(street-level): one provider, no orbit overview, no prewarm, simpler tile choice
The layer now owns its Mapillary parts directly: the provider registry,
pick router, multi-provider UI summary, params codec helper and provider
context are gone. createStreetLevelLayer({ source }) builds coverage,
sequences, the nearest-image lookup and the MapillaryJS viewer itself; a
single providerOn switch keeps the share link's `mapillary` option (off
draws nothing), and getParams/setParams keep { mapillary, pano, sinceDays }.
No z0-5 overview points: above 60 km nothing is drawn and the panel says to
zoom in; the proxy now serves only coverage z11-14. Tiles come from the
visible ground within range of the camera, ranked from the ground under it
(no line-of-sight ranking or near-range padding). MapillaryJS loads on the
first photo; viewer prewarm is removed. Key status, a rejected key and a
rate limit share one `blocked` state on coverage.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
0f41fb04cd
commit
37a96072fa
51 files changed
+1240
-2659
No files matched your search
@@ -33,27 +33,16 @@ const { createMapillaryViewer } =
|
||||
/**
|
||||
* A viewer host over `adapter`. `marker` records where the position marker
|
||||
* was put (null once cleared), `framing` counts framing calls and
|
||||
* `selected` every sequence the provider was asked to select.
|
||||
* `selected` every sequence the layer was asked to select.
|
||||
*/
|
||||
function harness(adapter) {
|
||||
const state = {
|
||||
enabled: true,
|
||||
notify() {},
|
||||
providers: new Map(),
|
||||
street: { host: {}, renderMode: 'letterbox' },
|
||||
sequence: { selectedId: null },
|
||||
};
|
||||
const def = { id: 'mapillary', name: 'Mapillary', label: 'MAPILLARY' };
|
||||
const selected = [];
|
||||
let selectedId = null;
|
||||
const instance = {
|
||||
viewer: adapter,
|
||||
selectSequence(id) {
|
||||
selected.push(id);
|
||||
selectedId = id;
|
||||
},
|
||||
sequenceStats: () => ({ selectedId }),
|
||||
};
|
||||
state.providers.set('mapillary', { def, instance });
|
||||
const framing = { begun: 0, started: 0, cancelled: 0 };
|
||||
const marker = [];
|
||||
const parts = {
|
||||
@@ -67,17 +56,23 @@ function harness(adapter) {
|
||||
frame: (ticket) => ticket && framing.started++,
|
||||
cancel: () => framing.cancelled++,
|
||||
},
|
||||
sequences: {
|
||||
select(id) {
|
||||
selected.push(id);
|
||||
state.sequence.selectedId = id;
|
||||
},
|
||||
},
|
||||
};
|
||||
return {
|
||||
state,
|
||||
framing,
|
||||
marker,
|
||||
selected,
|
||||
/** Esc on the map: the provider drops its selected sequence. */
|
||||
/** Esc on the map: the selected sequence is dropped. */
|
||||
clearSequence() {
|
||||
selectedId = null;
|
||||
state.sequence.selectedId = null;
|
||||
},
|
||||
host: createViewerHost({ state, parts }),
|
||||
host: createViewerHost({ state, parts, adapter }),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -102,7 +97,6 @@ function fakeAdapter({ failMounts = 0, gate = null, openGate = null } = {}) {
|
||||
/** A pose event from the library, as MapillaryJS fires them. */
|
||||
emitPose(id, overrides = {}) {
|
||||
emit?.({
|
||||
providerId: 'mapillary',
|
||||
imageId: id,
|
||||
sequenceId: `seq-${id}`,
|
||||
position: { lon: 1, lat: 2 },
|
||||
@@ -139,10 +133,10 @@ function fakeAdapter({ failMounts = 0, gate = null, openGate = null } = {}) {
|
||||
test('a failed mount is retried on the next open instead of being cached', async () => {
|
||||
const adapter = fakeAdapter({ failMounts: 1 });
|
||||
const { state, host } = harness(adapter);
|
||||
await host.open('mapillary', 'a');
|
||||
await host.open('a');
|
||||
assert.equal(state.street.error, 'library failed to load');
|
||||
assert.equal(adapter.calls.listeners, 0, 'the pose listener was released');
|
||||
await host.open('mapillary', 'b');
|
||||
await host.open('b');
|
||||
assert.equal(state.street.error, null);
|
||||
assert.equal(adapter.calls.mount, 2);
|
||||
assert.deepEqual(adapter.calls.open, ['b']);
|
||||
@@ -153,7 +147,7 @@ test('a failed mount is retried on the next open instead of being cached', async
|
||||
test('concurrent opens share one mount and one pose listener', async () => {
|
||||
const adapter = fakeAdapter();
|
||||
const { host } = harness(adapter);
|
||||
await Promise.all([host.open('mapillary', 'a'), host.open('mapillary', 'b')]);
|
||||
await Promise.all([host.open('a'), host.open('b')]);
|
||||
assert.equal(adapter.calls.mount, 1);
|
||||
assert.equal(adapter.calls.listeners, 1);
|
||||
});
|
||||
@@ -161,62 +155,22 @@ test('concurrent opens share one mount and one pose listener', async () => {
|
||||
test('unmounting while a mount is in flight does not leave it active', async () => {
|
||||
const adapter = fakeAdapter();
|
||||
const { state, host } = harness(adapter);
|
||||
const opening = host.open('mapillary', 'a');
|
||||
const opening = host.open('a');
|
||||
host.unmount();
|
||||
await opening;
|
||||
assert.equal(adapter.calls.listeners, 0);
|
||||
assert.equal(adapter.calls.unmount, 1);
|
||||
assert.equal(adapter.calls.unmount, 2, 'layer off, then the late mount');
|
||||
assert.equal(state.street.open, false);
|
||||
// Not left active: the next open mounts again.
|
||||
await host.open('mapillary', 'b');
|
||||
await host.open('b');
|
||||
assert.equal(adapter.calls.mount, 2);
|
||||
assert.equal(state.street.imageId, 'b');
|
||||
});
|
||||
|
||||
test('a prewarmed viewer is released when the layer goes off, or at once if it went off mid-load', async () => {
|
||||
const adapter = fakeAdapter();
|
||||
let release = null;
|
||||
adapter.prewarm = () =>
|
||||
new Promise((resolve) => {
|
||||
release = resolve;
|
||||
});
|
||||
const { state, host } = harness(adapter);
|
||||
const entry = state.providers.get('mapillary');
|
||||
entry.on = true;
|
||||
|
||||
// Warmed, then the layer is switched off: unmount releases the warm viewer.
|
||||
const warming = host.prewarm([entry]);
|
||||
release();
|
||||
await warming;
|
||||
assert.equal(adapter.calls.unmount, 0);
|
||||
host.unmount();
|
||||
assert.equal(adapter.calls.unmount, 1, 'the WebGL viewer is destroyed');
|
||||
|
||||
// Switched off while the library was still loading: released on arrival.
|
||||
const late = host.prewarm([entry]);
|
||||
state.enabled = false;
|
||||
release();
|
||||
await late;
|
||||
assert.equal(adapter.calls.unmount, 2);
|
||||
});
|
||||
|
||||
test('switching one provider off releases only its viewer', async () => {
|
||||
const adapter = fakeAdapter();
|
||||
adapter.prewarm = async () => {};
|
||||
const { state, host } = harness(adapter);
|
||||
const entry = state.providers.get('mapillary');
|
||||
entry.on = true;
|
||||
await host.prewarm([entry]);
|
||||
host.unmount('panoramax');
|
||||
assert.equal(adapter.calls.unmount, 0, 'another provider leaves it alone');
|
||||
host.unmount('mapillary');
|
||||
assert.equal(adapter.calls.unmount, 1);
|
||||
});
|
||||
|
||||
test('a pose that arrives after the photo was closed does not bring it back', async () => {
|
||||
const adapter = fakeAdapter();
|
||||
const { state, host } = harness(adapter);
|
||||
await host.open('mapillary', 'a');
|
||||
await host.open('a');
|
||||
host.close();
|
||||
const before = { ...state.street };
|
||||
// The library's `image` event for a photo that was still loading.
|
||||
@@ -239,16 +193,16 @@ test('switching the layer off and on during a cold first open keeps the shared v
|
||||
const gate = deferred();
|
||||
const adapter = fakeAdapter({ gate });
|
||||
const { state, host } = harness(adapter);
|
||||
const first = host.open('mapillary', 'a'); // the library is still loading
|
||||
const first = host.open('a'); // the library is still loading
|
||||
host.unmount(); // layer off …
|
||||
const second = host.open('mapillary', 'b'); // … on again, and a new click
|
||||
const second = host.open('b'); // … on again, and a new click
|
||||
gate.resolve();
|
||||
await Promise.all([first, second]);
|
||||
assert.equal(adapter.calls.unmount, 0, 'the outdated mount left it alone');
|
||||
assert.equal(adapter.calls.unmount, 1, 'only the layer going off');
|
||||
assert.equal(adapter.calls.listeners, 1);
|
||||
assert.equal(state.street.error, null);
|
||||
assert.equal(state.street.imageId, 'b');
|
||||
await host.open('mapillary', 'c');
|
||||
await host.open('c');
|
||||
assert.equal(state.street.error, null, 'later opens are not stuck');
|
||||
assert.equal(state.street.imageId, 'c');
|
||||
});
|
||||
@@ -258,7 +212,7 @@ const settle = () => new Promise((resolve) => setTimeout(resolve, 0));
|
||||
test('every pose the viewer reports moves the marker; only the open frames the camera', async () => {
|
||||
const adapter = fakeAdapter();
|
||||
const { host, marker, framing } = harness(adapter);
|
||||
await host.open('mapillary', 'a');
|
||||
await host.open('a');
|
||||
assert.deepEqual(marker, [{ position: { lon: 1, lat: 2 }, bearing: 90 }]);
|
||||
// The user walks on: the marker goes along, the globe camera stays.
|
||||
adapter.emitPose('b', { position: { lon: 3, lat: 4 }, bearing: 180 });
|
||||
@@ -276,7 +230,7 @@ test('a pose on another sequence selects it, but not while the image still loads
|
||||
const openGate = deferred();
|
||||
const adapter = fakeAdapter({ openGate });
|
||||
const { state, host, selected, clearSequence } = harness(adapter);
|
||||
const opening = host.open('mapillary', 'a');
|
||||
const opening = host.open('a');
|
||||
await settle();
|
||||
assert.equal(state.street.loading, true, 'the image is downloading');
|
||||
assert.equal(state.street.sequenceId, 'seq-a', 'its pose is in');
|
||||
@@ -299,7 +253,7 @@ test('a pose on another sequence selects it, but not while the image still loads
|
||||
test('closing the photo clears the marker', async () => {
|
||||
const adapter = fakeAdapter();
|
||||
const { host, marker } = harness(adapter);
|
||||
await host.open('mapillary', 'a');
|
||||
await host.open('a');
|
||||
assert.notEqual(marker.at(-1), null);
|
||||
host.close();
|
||||
assert.equal(marker.at(-1), null);
|
||||
@@ -309,7 +263,7 @@ test('an open still mounting when the photo is closed does nothing when the moun
|
||||
const gate = deferred();
|
||||
const adapter = fakeAdapter({ gate });
|
||||
const { state, host, framing, selected } = harness(adapter);
|
||||
const opening = host.open('mapillary', 'a'); // MapillaryJS still loading
|
||||
const opening = host.open('a'); // MapillaryJS still loading
|
||||
await settle();
|
||||
host.close();
|
||||
gate.resolve();
|
||||
@@ -322,7 +276,7 @@ test('an open still mounting when the photo is closed does nothing when the moun
|
||||
[false, false, null],
|
||||
);
|
||||
// The mounted viewer serves the next open.
|
||||
assert.equal(await host.open('mapillary', 'b'), true);
|
||||
assert.equal(await host.open('b'), true);
|
||||
assert.equal(state.street.imageId, 'b');
|
||||
});
|
||||
|
||||
@@ -330,7 +284,7 @@ test('an image still opening when the photo is closed is neither framed nor sele
|
||||
const openGate = deferred();
|
||||
const adapter = fakeAdapter({ openGate });
|
||||
const { state, host, framing, selected } = harness(adapter);
|
||||
const opening = host.open('mapillary', 'a');
|
||||
const opening = host.open('a');
|
||||
await settle();
|
||||
assert.deepEqual(adapter.calls.open, ['a'], 'the image is downloading');
|
||||
host.close();
|
||||
@@ -408,16 +362,16 @@ test('the Mapillary viewer survives the layer going off and on during its first
|
||||
const { gate, viewers } = fakeLibrary();
|
||||
const adapter = createMapillaryViewer({ source: { token: 't' } });
|
||||
const { state, host } = harness(adapter);
|
||||
const first = host.open('mapillary', 'img1');
|
||||
const first = host.open('img1');
|
||||
await Promise.resolve();
|
||||
host.unmount();
|
||||
const second = host.open('mapillary', 'img2');
|
||||
const second = host.open('img2');
|
||||
await Promise.resolve();
|
||||
gate.resolve();
|
||||
await Promise.all([first, second]);
|
||||
assert.equal(state.street.error, null);
|
||||
assert.equal(state.street.imageId, 'img2');
|
||||
await host.open('mapillary', 'img3');
|
||||
await host.open('img3');
|
||||
assert.equal(state.street.error, null, 'later opens are not stuck');
|
||||
assert.equal(state.street.imageId, 'img3');
|
||||
assert.equal(viewers.live, 1);
|
||||
@@ -439,20 +393,6 @@ test('a Mapillary viewer unmounted mid-download is never built; the next mount b
|
||||
assert.equal(viewers.live, 0, 'nothing left holding a WebGL context');
|
||||
});
|
||||
|
||||
test('a prewarm that gave up when the layer went off and on mid-download is followed by one that builds', async () => {
|
||||
const { gate, viewers } = fakeLibrary();
|
||||
const adapter = createMapillaryViewer({ source: { token: 't' } });
|
||||
const element = {};
|
||||
const first = adapter.prewarm(element);
|
||||
adapter.unmount(); // layer off while MapillaryJS downloads
|
||||
const second = adapter.prewarm(element); // layer on again
|
||||
gate.resolve();
|
||||
await Promise.all([first, second]);
|
||||
assert.equal(viewers.created, 1, 'the second prewarm built the viewer');
|
||||
assert.equal(viewers.live, 1);
|
||||
adapter.unmount();
|
||||
});
|
||||
|
||||
test('the Mapillary viewer leaves resizing to the panel, so a hidden one never asks for z=NaN', async () => {
|
||||
const { gate, viewers } = fakeLibrary();
|
||||
gate.resolve();
|
||||
@@ -467,7 +407,7 @@ test('closing the photo stops sequence playback in the hidden viewer', async ()
|
||||
gate.resolve();
|
||||
const adapter = createMapillaryViewer({ source: { token: 't' } });
|
||||
const { host } = harness(adapter);
|
||||
await host.open('mapillary', 'img1');
|
||||
await host.open('img1');
|
||||
const [viewer] = viewers.instances;
|
||||
assert.equal(viewer.playback.stops, 0);
|
||||
host.close();
|
||||
@@ -478,13 +418,11 @@ test('closing the photo stops sequence playback in the hidden viewer', async ()
|
||||
test('closing the photo or switching the layer off stops its framing flight', async () => {
|
||||
const adapter = fakeAdapter();
|
||||
const { framing, host } = harness(adapter);
|
||||
await host.open('mapillary', 'a');
|
||||
await host.open('a');
|
||||
assert.equal(framing.started, 1, 'the open framed the photo');
|
||||
host.close();
|
||||
assert.equal(framing.cancelled, 1, 'closed: the globe stops flying to it');
|
||||
await host.open('mapillary', 'b');
|
||||
host.unmount('panoramax');
|
||||
assert.equal(framing.cancelled, 1, 'another provider leaves it alone');
|
||||
await host.open('b');
|
||||
host.unmount();
|
||||
assert.equal(framing.cancelled, 2, 'layer off: likewise');
|
||||
});
|
||||
@@ -557,7 +495,7 @@ test('a photo asks for the camera when it starts opening and frames with that ti
|
||||
const openGate = deferred();
|
||||
const adapter = fakeAdapter({ openGate });
|
||||
const { host, framing } = harness(adapter);
|
||||
const opening = host.open('mapillary', 'a');
|
||||
const opening = host.open('a');
|
||||
assert.equal(framing.begun, 1, 'claimed before the download');
|
||||
await settle();
|
||||
assert.equal(framing.started, 0);
|
||||
|
||||
Reference in new issue
Block a user