mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +00:00
refactor(street-level): drop unused tile and pose fields; keep the sequence error on the sequence
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
7411ebaf1c
commit
c78c872f92
10 files changed
+32
-60
No files matched your search
@@ -60,7 +60,6 @@ export function createStreetLevelLayer({
|
|||||||
photoViewer || createMapillaryViewer({ source, render: services.render }),
|
photoViewer || createMapillaryViewer({ source, render: services.render }),
|
||||||
});
|
});
|
||||||
parts.openImage = openImage;
|
parts.openImage = openImage;
|
||||||
parts.reportSequenceError = reportSequenceError;
|
|
||||||
parts.selection = createSelection(context);
|
parts.selection = createSelection(context);
|
||||||
|
|
||||||
let notifyQueued = false;
|
let notifyQueued = false;
|
||||||
@@ -81,24 +80,6 @@ export function createStreetLevelLayer({
|
|||||||
}
|
}
|
||||||
state.notify = notify;
|
state.notify = notify;
|
||||||
|
|
||||||
/** The error a sequence lookup last showed, until it withdraws it. */
|
|
||||||
let sequenceError = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show a sequence error, or withdraw it (null). A withdrawal clears only
|
|
||||||
* that error, never a newer one from the viewer.
|
|
||||||
*/
|
|
||||||
function reportSequenceError(message) {
|
|
||||||
if (message) {
|
|
||||||
state.street.error = sequenceError = message;
|
|
||||||
} else {
|
|
||||||
if (!sequenceError) return;
|
|
||||||
if (state.street.error === sequenceError) state.street.error = null;
|
|
||||||
sequenceError = null;
|
|
||||||
}
|
|
||||||
notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Coverage waits for the key status: no tile requests without a key. */
|
/** Coverage waits for the key status: no tile requests without a key. */
|
||||||
async function checkKey() {
|
async function checkKey() {
|
||||||
let configured;
|
let configured;
|
||||||
@@ -172,6 +153,7 @@ export function createStreetLevelLayer({
|
|||||||
selectedId: state.sequence.selectedId,
|
selectedId: state.sequence.selectedId,
|
||||||
images: state.sequence.images.length,
|
images: state.sequence.images.length,
|
||||||
loading: state.sequence.loading,
|
loading: state.sequence.loading,
|
||||||
|
error: state.sequence.error,
|
||||||
},
|
},
|
||||||
street,
|
street,
|
||||||
};
|
};
|
||||||
@@ -228,7 +210,6 @@ export function createStreetLevelLayer({
|
|||||||
state.listeners.clear();
|
state.listeners.clear();
|
||||||
state.viewer = null;
|
state.viewer = null;
|
||||||
state.initialized = false;
|
state.initialized = false;
|
||||||
state.destroyed = true;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getStats() {
|
getStats() {
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ test('switching Mapillary off clears coverage and closes the image it shows', as
|
|||||||
assert.equal(layer.getUIState().providerOn, false);
|
assert.equal(layer.getUIState().providerOn, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('a sequence error is withdrawn with the sequence, never the viewer’s own', async (t) => {
|
test('a sequence error goes with the sequence and leaves the viewer’s own', async (t) => {
|
||||||
const source = fakeMapillarySource({
|
const source = fakeMapillarySource({
|
||||||
getSequenceImages: async () => {
|
getSequenceImages: async () => {
|
||||||
throw new Error('Sequence images unavailable');
|
throw new Error('Sequence images unavailable');
|
||||||
@@ -213,16 +213,15 @@ test('a sequence error is withdrawn with the sequence, never the viewer’s own'
|
|||||||
});
|
});
|
||||||
const { layer } = await startLayer(t, { source });
|
const { layer } = await startLayer(t, { source });
|
||||||
await layer.selectSequence('seq-1');
|
await layer.selectSequence('seq-1');
|
||||||
assert.equal(layer.getUIState().street.error, 'Sequence images unavailable');
|
|
||||||
layer.clearSequence();
|
|
||||||
assert.equal(layer.getUIState().street.error, null, 'withdrawn');
|
|
||||||
|
|
||||||
// The viewer's own error is not the sequence's to clear.
|
|
||||||
await layer.selectSequence('seq-2');
|
|
||||||
layer.attachViewerHost(null);
|
layer.attachViewerHost(null);
|
||||||
await layer.openImage('img1');
|
await layer.openImage('img1');
|
||||||
|
let ui = layer.getUIState();
|
||||||
|
assert.equal(ui.sequence.error, 'Sequence images unavailable');
|
||||||
|
assert.match(ui.street.error, /Open the Street Level panel/);
|
||||||
layer.clearSequence();
|
layer.clearSequence();
|
||||||
assert.match(layer.getUIState().street.error, /Open the Street Level panel/);
|
ui = layer.getUIState();
|
||||||
|
assert.equal(ui.sequence.error, null, 'withdrawn with the sequence');
|
||||||
|
assert.match(ui.street.error, /Open the Street Level panel/, 'kept');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('closing the photo, switching the layer off or destroying it stops the framing flight', async (t) => {
|
test('closing the photo, switching the layer off or destroying it stops the framing flight', async (t) => {
|
||||||
|
|||||||
@@ -30,13 +30,8 @@ export function decodeCoverageTile(bytes, address) {
|
|||||||
if (!parts.length) continue;
|
if (!parts.length) continue;
|
||||||
result.sequences.push({
|
result.sequences.push({
|
||||||
id: String(props.id ?? feature.id ?? `${x}/${y}/${i}`),
|
id: String(props.id ?? feature.id ?? `${x}/${y}/${i}`),
|
||||||
imageId: props.image_id != null ? String(props.image_id) : null,
|
|
||||||
capturedAt: Number(props.captured_at) || 0,
|
capturedAt: Number(props.captured_at) || 0,
|
||||||
isPano: props.is_pano === true,
|
isPano: props.is_pano === true,
|
||||||
onFoot: props.foot === true,
|
|
||||||
quality: Number.isFinite(props.quality_score)
|
|
||||||
? props.quality_score
|
|
||||||
: null,
|
|
||||||
parts,
|
parts,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,9 +25,6 @@ function normalizeSequenceImage(record) {
|
|||||||
compassAngle: Number(record.compass_angle) || 0,
|
compassAngle: Number(record.compass_angle) || 0,
|
||||||
capturedAt: Number(record.captured_at) || 0,
|
capturedAt: Number(record.captured_at) || 0,
|
||||||
isPano: record.is_pano === true,
|
isPano: record.is_pano === true,
|
||||||
altitude: Number.isFinite(record.computed_altitude)
|
|
||||||
? record.computed_altitude
|
|
||||||
: null,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,11 +53,6 @@ export function createSequences({ state, source, parts }) {
|
|||||||
state.notify?.();
|
state.notify?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Show a sequence error, or withdraw it (null); see `reportSequenceError`. */
|
|
||||||
function reportError(message) {
|
|
||||||
parts.reportSequenceError?.(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Drawn cone billboards. */
|
/** Drawn cone billboards. */
|
||||||
let cones = [];
|
let cones = [];
|
||||||
|
|
||||||
@@ -151,7 +143,7 @@ export function createSequences({ state, source, parts }) {
|
|||||||
state.sequence.loading = false;
|
state.sequence.loading = false;
|
||||||
state.sequence.images = cached;
|
state.sequence.images = cached;
|
||||||
renderCones(cached);
|
renderCones(cached);
|
||||||
reportError(null);
|
state.sequence.error = null;
|
||||||
notify();
|
notify();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -177,13 +169,13 @@ export function createSequences({ state, source, parts }) {
|
|||||||
remember(sequenceId, images);
|
remember(sequenceId, images);
|
||||||
state.sequence.images = images;
|
state.sequence.images = images;
|
||||||
renderCones(images);
|
renderCones(images);
|
||||||
reportError(null);
|
state.sequence.error = null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!controller.signal.aborted) {
|
if (!controller.signal.aborted) {
|
||||||
// Nothing to show: drop the highlight, so a click asks again.
|
// Nothing to show: drop the highlight, so a click asks again.
|
||||||
parts.coverage.recolorSequence(sequenceId, false);
|
parts.coverage.recolorSequence(sequenceId, false);
|
||||||
state.sequence.selectedId = null;
|
state.sequence.selectedId = null;
|
||||||
reportError(error?.message || 'Sequence images unavailable');
|
state.sequence.error = error?.message || 'Sequence images unavailable';
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (state.sequence.abort === controller) {
|
if (state.sequence.abort === controller) {
|
||||||
@@ -203,7 +195,7 @@ export function createSequences({ state, source, parts }) {
|
|||||||
state.sequence.loading = false;
|
state.sequence.loading = false;
|
||||||
clearCones();
|
clearCones();
|
||||||
// An error about the sequence goes with it.
|
// An error about the sequence goes with it.
|
||||||
reportError(null);
|
state.sequence.error = null;
|
||||||
requestRender();
|
requestRender();
|
||||||
notify();
|
notify();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ function records(sequenceId, count = 2) {
|
|||||||
function setup() {
|
function setup() {
|
||||||
const source = deferredSource();
|
const source = deferredSource();
|
||||||
const colours = [];
|
const colours = [];
|
||||||
const errors = [];
|
|
||||||
const state = {
|
const state = {
|
||||||
services: {},
|
services: {},
|
||||||
viewer: {},
|
viewer: {},
|
||||||
@@ -70,6 +69,7 @@ function setup() {
|
|||||||
cache: new Map(),
|
cache: new Map(),
|
||||||
collection: cones(),
|
collection: cones(),
|
||||||
loading: false,
|
loading: false,
|
||||||
|
error: null,
|
||||||
abort: null,
|
abort: null,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -77,16 +77,15 @@ function setup() {
|
|||||||
coverage: {
|
coverage: {
|
||||||
recolorSequence: (id, selected) => colours.push([id, selected]),
|
recolorSequence: (id, selected) => colours.push([id, selected]),
|
||||||
},
|
},
|
||||||
reportSequenceError: (message) => errors.push(message),
|
|
||||||
};
|
};
|
||||||
const sequences = createSequences({ state, source, parts });
|
const sequences = createSequences({ state, source, parts });
|
||||||
const drawn = () =>
|
const drawn = () =>
|
||||||
state.sequence.collection.items.map((cone) => cone.id.split(':').at(-1));
|
state.sequence.collection.items.map((cone) => cone.id.split(':').at(-1));
|
||||||
return { state, source, sequences, colours, errors, drawn };
|
return { state, source, sequences, colours, drawn };
|
||||||
}
|
}
|
||||||
|
|
||||||
test('a failed sequence load leaves no stale cones and can be retried', async () => {
|
test('a failed sequence load leaves no stale cones and can be retried', async () => {
|
||||||
const { state, source, sequences, colours, errors, drawn } = setup();
|
const { state, source, sequences, colours, drawn } = setup();
|
||||||
sequences.select('A');
|
sequences.select('A');
|
||||||
source.last('A').resolve(records('A'));
|
source.last('A').resolve(records('A'));
|
||||||
await settle();
|
await settle();
|
||||||
@@ -100,7 +99,7 @@ test('a failed sequence load leaves no stale cones and can be retried', async ()
|
|||||||
assert.equal(state.sequence.loading, false);
|
assert.equal(state.sequence.loading, false);
|
||||||
assert.equal(state.sequence.selectedId, null, 'B is not left highlighted');
|
assert.equal(state.sequence.selectedId, null, 'B is not left highlighted');
|
||||||
assert.deepEqual(colours.at(-1), ['B', false]);
|
assert.deepEqual(colours.at(-1), ['B', false]);
|
||||||
assert.deepEqual(errors, [null, 'Sequence images unavailable']);
|
assert.equal(state.sequence.error, 'Sequence images unavailable');
|
||||||
|
|
||||||
// Clicking B again asks again.
|
// Clicking B again asks again.
|
||||||
sequences.select('B');
|
sequences.select('B');
|
||||||
@@ -109,7 +108,7 @@ test('a failed sequence load leaves no stale cones and can be retried', async ()
|
|||||||
await settle();
|
await settle();
|
||||||
assert.deepEqual(drawn(), ['B-0', 'B-1']);
|
assert.deepEqual(drawn(), ['B-0', 'B-1']);
|
||||||
assert.equal(state.sequence.selectedId, 'B');
|
assert.equal(state.sequence.selectedId, 'B');
|
||||||
assert.equal(errors.at(-1), null, 'the error is cleared once B loads');
|
assert.equal(state.sequence.error, null, 'cleared once B loads');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('a late answer for a superseded sequence neither draws nor is cached', async () => {
|
test('a late answer for a superseded sequence neither draws nor is cached', async () => {
|
||||||
@@ -151,7 +150,7 @@ test('a cached sequence is re-selected without a lookup', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('clearSelection cancels the lookup and uncolours the sequence', async () => {
|
test('clearSelection cancels the lookup and uncolours the sequence', async () => {
|
||||||
const { state, source, sequences, colours, errors, drawn } = setup();
|
const { state, source, sequences, colours, drawn } = setup();
|
||||||
sequences.select('A');
|
sequences.select('A');
|
||||||
sequences.clearSelection();
|
sequences.clearSelection();
|
||||||
assert.equal(source.last('A').signal.aborted, true);
|
assert.equal(source.last('A').signal.aborted, true);
|
||||||
@@ -161,7 +160,7 @@ test('clearSelection cancels the lookup and uncolours the sequence', async () =>
|
|||||||
]);
|
]);
|
||||||
assert.equal(state.sequence.selectedId, null);
|
assert.equal(state.sequence.selectedId, null);
|
||||||
assert.equal(state.sequence.loading, false);
|
assert.equal(state.sequence.loading, false);
|
||||||
assert.equal(errors.at(-1), null, 'an error about it goes too');
|
assert.equal(state.sequence.error, null, 'an error about it goes too');
|
||||||
source.last('A').resolve(records('A'));
|
source.last('A').resolve(records('A'));
|
||||||
await settle();
|
await settle();
|
||||||
assert.deepEqual(drawn(), [], 'the late answer draws nothing');
|
assert.deepEqual(drawn(), [], 'the late answer draws nothing');
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { mapillaryImageUrl } from './policy.js';
|
|||||||
* @property {string} imageId
|
* @property {string} imageId
|
||||||
* @property {{lon: number, lat: number}} position
|
* @property {{lon: number, lat: number}} position
|
||||||
* @property {number|null} bearing
|
* @property {number|null} bearing
|
||||||
* @property {number|null} tilt
|
|
||||||
* @property {number|null} altitude
|
* @property {number|null} altitude
|
||||||
* @property {boolean} isPano
|
* @property {boolean} isPano
|
||||||
* @property {number|null} capturedAt Epoch milliseconds.
|
* @property {number|null} capturedAt Epoch milliseconds.
|
||||||
@@ -94,7 +93,6 @@ export function createMapillaryViewer({ source, render } = {}) {
|
|||||||
...current,
|
...current,
|
||||||
position: { lon: lngLat.lng, lat: lngLat.lat },
|
position: { lon: lngLat.lng, lat: lngLat.lat },
|
||||||
bearing: Number.isFinite(pov?.bearing) ? pov.bearing : null,
|
bearing: Number.isFinite(pov?.bearing) ? pov.bearing : null,
|
||||||
tilt: Number.isFinite(pov?.tilt) ? pov.tilt : 0,
|
|
||||||
externalUrl: mapillaryImageUrl(current.imageId),
|
externalUrl: mapillaryImageUrl(current.imageId),
|
||||||
});
|
});
|
||||||
requestRender();
|
requestRender();
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export function freshStreet() {
|
|||||||
imageId: null,
|
imageId: null,
|
||||||
position: null,
|
position: null,
|
||||||
bearing: null,
|
bearing: null,
|
||||||
tilt: null,
|
|
||||||
altitude: null,
|
altitude: null,
|
||||||
isPano: false,
|
isPano: false,
|
||||||
capturedAt: null,
|
capturedAt: null,
|
||||||
@@ -55,7 +54,6 @@ export function createState({ services }) {
|
|||||||
viewer: null,
|
viewer: null,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
initialized: false,
|
initialized: false,
|
||||||
destroyed: false,
|
|
||||||
listeners: new Set(),
|
listeners: new Set(),
|
||||||
notify: null,
|
notify: null,
|
||||||
/** The share link's `mapillary` switch: off draws nothing. */
|
/** The share link's `mapillary` switch: off draws nothing. */
|
||||||
@@ -78,6 +76,8 @@ export function createState({ services }) {
|
|||||||
cache: new Map(),
|
cache: new Map(),
|
||||||
collection: null,
|
collection: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
/** Why the selected sequence's images could not load, until it goes. */
|
||||||
|
error: null,
|
||||||
abort: null,
|
abort: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ export function createViewerHost({ state, parts, adapter }) {
|
|||||||
imageId: pose.imageId,
|
imageId: pose.imageId,
|
||||||
position: pose.position ? { ...pose.position } : null,
|
position: pose.position ? { ...pose.position } : null,
|
||||||
bearing: Number.isFinite(pose.bearing) ? pose.bearing : null,
|
bearing: Number.isFinite(pose.bearing) ? pose.bearing : null,
|
||||||
tilt: Number.isFinite(pose.tilt) ? pose.tilt : 0,
|
|
||||||
altitude: Number.isFinite(pose.altitude) ? pose.altitude : null,
|
altitude: Number.isFinite(pose.altitude) ? pose.altitude : null,
|
||||||
isPano: pose.isPano === true,
|
isPano: pose.isPano === true,
|
||||||
capturedAt: pose.capturedAt ?? null,
|
capturedAt: pose.capturedAt ?? null,
|
||||||
|
|||||||
@@ -118,7 +118,10 @@ export function presentStreetLevelPanel(state, { now = Date.now() } = {}) {
|
|||||||
controlsDisabled: state.keyRequired === true,
|
controlsDisabled: state.keyRequired === true,
|
||||||
error: keyMissing
|
error: keyMissing
|
||||||
? keyHint()
|
? keyHint()
|
||||||
: state.street.error || state.coverage.error || null,
|
: state.street.error ||
|
||||||
|
state.sequence.error ||
|
||||||
|
state.coverage.error ||
|
||||||
|
null,
|
||||||
filter: { pano: filter.pano, sinceDays: Number(filter.sinceDays) || 0 },
|
filter: { pano: filter.pano, sinceDays: Number(filter.sinceDays) || 0 },
|
||||||
since: presentSince(filter.sinceDays, now),
|
since: presentSince(filter.sinceDays, now),
|
||||||
legend: state.providerOn !== false,
|
legend: state.providerOn !== false,
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ test('the header pill is the on/off switch and reads OFF, LOADING or ON', () =>
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('errors from the viewer or the coverage web surface in one alert', () => {
|
test('errors from the viewer, the sequence or the coverage surface in one alert', () => {
|
||||||
assert.equal(presentStreetLevelPanel(snapshot()).error, null);
|
assert.equal(presentStreetLevelPanel(snapshot()).error, null);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
presentStreetLevelPanel(
|
presentStreetLevelPanel(
|
||||||
@@ -132,6 +132,12 @@ test('errors from the viewer or the coverage web surface in one alert', () => {
|
|||||||
).error,
|
).error,
|
||||||
'Image could not be opened',
|
'Image could not be opened',
|
||||||
);
|
);
|
||||||
|
assert.equal(
|
||||||
|
presentStreetLevelPanel(
|
||||||
|
snapshot({ sequence: { error: 'Sequence images unavailable' } }),
|
||||||
|
).error,
|
||||||
|
'Sequence images unavailable',
|
||||||
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
presentStreetLevelPanel(snapshot({ coverage: { error: 'Tile HTTP 502' } }))
|
presentStreetLevelPanel(snapshot({ coverage: { error: 'Tile HTTP 502' } }))
|
||||||
.error,
|
.error,
|
||||||
|
|||||||
Reference in new issue
Block a user