mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
feat(street-level): Google Street View as the second imagery provider
Google Street View registers beside Mapillary as provider `google`. It runs on the Maps JavaScript API with the browser GOOGLE_MAPS_API_KEY, loaded on first use (mapsLoader.js), and uses Google's own StreetViewPanorama as its viewer adapter, emitting provider-neutral poses (address and month in the caption, a link to Google Maps, steps along Google's arrows, FOLLOW on Google 3D). Its chip starts off because every panorama is billed to the key; the share option is `g`. Google offers no coverage layer for other maps, so the provider draws none: with it on, a click on bare ground at street zoom opens its nearest Google-collected outdoor panorama (groundClick.js). A key Google refuses reads KEY REJECTED and fails a pending open at once; a lookup Google never answers fails after 10 s. Core: providers may declare `groundClick` and `defaultOn`; poses may carry `capturedAtPrecision` and `title`; the coverage line keeps every provider's hint; opening one provider's image releases another's prewarmed viewer; FIT/FILL hides for viewers without render modes. CSP script-src admits https://maps.googleapis.com. The browser gate gains two Street View steps (the keyed one skips as 'no Google key' in CI). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
95fa816232
commit
22b0009138
42 files changed
+1879
-66
No files matched your search
@@ -0,0 +1,95 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createMapsLoader } from './mapsLoader.js';
|
||||
|
||||
/** A document whose appended scripts the test loads or fails. */
|
||||
function fakePage() {
|
||||
const scripts = [];
|
||||
const document = {
|
||||
createElement: () => {
|
||||
const script = {
|
||||
remove() {
|
||||
scripts.splice(scripts.indexOf(script), 1);
|
||||
},
|
||||
};
|
||||
return script;
|
||||
},
|
||||
head: { append: (script) => scripts.push(script) },
|
||||
};
|
||||
const global = {};
|
||||
const library = { name: 'streetView' };
|
||||
/** Google's script arrived: it defines google.maps and calls back. */
|
||||
const loadScript = () => {
|
||||
global.google = {
|
||||
maps: { importLibrary: async (name) => ({ ...library, name }) },
|
||||
};
|
||||
const callback = new URL(scripts.at(-1).src).searchParams.get('callback');
|
||||
global[callback]();
|
||||
};
|
||||
return { document, global, scripts, loadScript };
|
||||
}
|
||||
|
||||
const settle = () => new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
test('the API loads once, with the key, asynchronously, and hands out libraries', async () => {
|
||||
const page = fakePage();
|
||||
const loader = createMapsLoader({ getApiKey: () => 'k-1', ...page });
|
||||
const first = loader.importLibrary('streetView');
|
||||
const second = loader.importLibrary('streetView');
|
||||
await settle();
|
||||
assert.equal(page.scripts.length, 1, 'one script for concurrent imports');
|
||||
const url = new URL(page.scripts[0].src);
|
||||
assert.equal(
|
||||
url.origin + url.pathname,
|
||||
'https://maps.googleapis.com/maps/api/js',
|
||||
);
|
||||
assert.equal(url.searchParams.get('key'), 'k-1');
|
||||
assert.equal(url.searchParams.get('loading'), 'async');
|
||||
assert.equal(page.scripts[0].async, true);
|
||||
page.loadScript();
|
||||
assert.equal((await first).name, 'streetView');
|
||||
assert.equal((await second).name, 'streetView');
|
||||
const callback = url.searchParams.get('callback');
|
||||
assert.equal(page.global[callback], undefined, 'the callback is cleaned up');
|
||||
});
|
||||
|
||||
test('without a key nothing loads', async () => {
|
||||
const page = fakePage();
|
||||
const loader = createMapsLoader({ getApiKey: () => null, ...page });
|
||||
await assert.rejects(loader.importLibrary('streetView'), /not set/);
|
||||
assert.equal(page.scripts.length, 0);
|
||||
});
|
||||
|
||||
test('a script that fails to load can be retried', async () => {
|
||||
const page = fakePage();
|
||||
const loader = createMapsLoader({ getApiKey: () => 'k', ...page });
|
||||
const failed = loader.importLibrary('streetView');
|
||||
await settle();
|
||||
page.scripts[0].onerror();
|
||||
await assert.rejects(failed, /could not load/);
|
||||
assert.equal(page.scripts.length, 0, 'the failed script is removed');
|
||||
const retry = loader.importLibrary('streetView');
|
||||
await settle();
|
||||
assert.equal(page.scripts.length, 1);
|
||||
page.loadScript();
|
||||
assert.equal((await retry).name, 'streetView');
|
||||
});
|
||||
|
||||
test("Google's refusal of the key reaches every listener and the page's own handler", async () => {
|
||||
const page = fakePage();
|
||||
let before = 0;
|
||||
page.global.gm_authFailure = () => before++;
|
||||
const loader = createMapsLoader({ getApiKey: () => 'k', ...page });
|
||||
const heard = [];
|
||||
const stop = loader.onAuthFailure(() => heard.push('a'));
|
||||
loader.importLibrary('streetView');
|
||||
await settle();
|
||||
assert.equal(loader.authFailed(), false);
|
||||
page.global.gm_authFailure();
|
||||
assert.equal(loader.authFailed(), true);
|
||||
assert.deepEqual(heard, ['a']);
|
||||
assert.equal(before, 1, 'a handler installed earlier still runs');
|
||||
stop();
|
||||
page.global.gm_authFailure();
|
||||
assert.deepEqual(heard, ['a']);
|
||||
});
|
||||
Reference in new issue
Block a user