mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
feat(street-level): KartaView as a third, keyless imagery provider
Coverage lines from KartaView's z13-14 JSON tiles (OSM ways with photos, chained end to start), the photo nearest a clicked line, cones along its sequence, and a viewer for flat photos and 360° panoramas (a small WebGL equirectangular renderer, no new dependency) with ‹ › along the sequence. Mapillary's camera-driven line coverage and sequence cones move to the provider-neutral core (lineCoverage.js, sequenceCones.js) with the tile fetch, zoom ladder, colours and pick prefixes injected; Mapillary keeps thin wrappers and its tests pass unchanged. KartaView quirks handled: api.kartaview.org (the documented api.openstreetcam.org serves empty tiles), photo URLs that name storage1 for photos stored elsewhere, and a photo API that answers 429 to more than about three requests at once (two in flight, retried with backoff). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
9beadbda63
commit
cb01014e36
33 files changed
+4046
-1196
No files matched your search
@@ -8,7 +8,12 @@ import { createSelection } from './selection.js';
|
||||
* `frame()` runs them; timers and Date.now() are mocked so a burst of moves
|
||||
* has a known timeline.
|
||||
*/
|
||||
function harness({ selected = false } = {}) {
|
||||
function harness({
|
||||
selected = false,
|
||||
router = null,
|
||||
providers = new Map(),
|
||||
services = {},
|
||||
} = {}) {
|
||||
const saved = {
|
||||
document: globalThis.document,
|
||||
requestAnimationFrame: globalThis.requestAnimationFrame,
|
||||
@@ -60,12 +65,16 @@ function harness({ selected = false } = {}) {
|
||||
viewer,
|
||||
enabled: true,
|
||||
clickHandler: null,
|
||||
services: { picking: null, input: null },
|
||||
providers,
|
||||
services: { picking: null, input: null, ...services },
|
||||
};
|
||||
const selection = createSelection({
|
||||
state,
|
||||
parts: {
|
||||
router: { ownsPick: (id) => id === 'mly:seq:1', resolve: () => null },
|
||||
router: router || {
|
||||
ownsPick: (id) => id === 'mly:seq:1',
|
||||
resolve: () => null,
|
||||
},
|
||||
hasSelectedSequence: () => sequences.selected,
|
||||
openAtGround: (position, picked) =>
|
||||
groundClicks.push({ position, picked }),
|
||||
@@ -282,3 +291,43 @@ test('a click on nothing of ours goes to the ground handler with the pick, which
|
||||
h.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('a click on a provider line hands it the ground under the click, measured only if asked', () => {
|
||||
const handled = [];
|
||||
const instance = {
|
||||
handlePick: (id, click) => handled.push({ id, click }),
|
||||
};
|
||||
const groundPicks = [];
|
||||
const h = harness({
|
||||
router: {
|
||||
ownsPick: () => true,
|
||||
resolve: (id) => ({ providerId: 'kartaview', instance, id }),
|
||||
},
|
||||
providers: new Map([['kartaview', { on: true }]]),
|
||||
services: {
|
||||
scenePick: {
|
||||
pickGroundPosition: (viewer, position) => {
|
||||
groundPicks.push(position);
|
||||
return Cesium.Cartesian3.fromDegrees(-83.0458, 42.3314, 190);
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
try {
|
||||
h.scene.under = 'kv:line:14/1/2/3';
|
||||
h.click(40);
|
||||
assert.equal(handled.length, 1);
|
||||
assert.equal(handled[0].id, 'kv:line:14/1/2/3');
|
||||
assert.equal(
|
||||
groundPicks.length,
|
||||
0,
|
||||
'no depth read until the provider asks',
|
||||
);
|
||||
const point = handled[0].click.groundPoint();
|
||||
assert.ok(Math.abs(point.lon + 83.0458) < 1e-9);
|
||||
assert.ok(Math.abs(point.lat - 42.3314) < 1e-9);
|
||||
assert.deepEqual(groundPicks, [{ x: 40, y: 10 }]);
|
||||
} finally {
|
||||
h.restore();
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user