fix(street-level): IC8 review of the Street View provider

Google's Maps Platform terms forbid Street View imagery beside non-Google
maps, so the provider declares requiresMapStack: 'photoreal': off Google
3D its chip is dashed with the reason, lookups and opens skip it, and
leaving Google 3D closes its panorama.

- Ground clicks: only a click on the map itself (scenePick.isSurfacePick:
  nothing picked or Google 3D tile content) at street zoom opens Street
  View, and the camera stays put; higher up the hint says to zoom in.
  scenePick.pickGroundPosition replaces the copy in Directions.
- Viewer: poses go out at once and again when the date and address
  arrive, so an open never hangs on that lookup; one lookup per panorama,
  seeded by the nearest lookup; a close settles a pending open; failed
  lookups are asked again; poses follow position changes, not pano_changed.
- Loader: the gm_authFailure handler is installed once, so a retried load
  reports a refusal once.
- Viewer host: a prewarm that lands after another provider took the host
  is released; FIT/FILL availability is set on every open.
- Provider: a refusal while switched off reports no error over another
  photo; SINCE compares against the end of Google's month; messages name
  the referrer and network causes; FLAT says Street View is 360° only.
- The layer row names the key of the providers that are on; the MCP panel
  CSP admits Street View's tiles, control images and photos.
- Tests for each, against fakes that now follow Google's event order; the
  browser gate restores its state, waits instead of sleeping, aims its
  panel clicks and corner drags, and asserts the camera stays put.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Opus 5.5 committed 2026-10-08 02:59:51 +01:00
1 parent 22b0009138
commit 7bfcf177ac
35 files changed
+1108 -388

No files matched your search

+93 -50
View File
@@ -1,84 +1,127 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import * as Cesium from 'cesium';
import { createGroundClick } from './groundClick.js';
import { isPickedWorldPosition } from '../../data/scenePick.js';
import { createGroundClick, groundClickHeightOk } from './groundClick.js';
import * as scenePick from '../../data/scenePick.js';
const GROUND = Cesium.Cartesian3.fromDegrees(-121.4944, 38.5816, 10);
const TILESET = { isCesium3DTileset: true };
/** A layer state with providers `{id, on, groundClick, configured}` and a camera `height` m up. */
/**
* A layer state with providers `{id, on, groundClick, configured, available}`;
* `picked` is where the depth buffer lands, `ellipsoid` the fallback hit.
*/
function harness({
providers = [
{ id: 'mapillary', groundClick: false },
{ id: 'google', groundClick: true },
],
height = 400,
ready = true,
enabled = true,
picked = GROUND,
ellipsoid = undefined,
} = {}) {
const opened = [];
const state = {
enabled,
services: { scenePick: { isPickedWorldPosition } },
groundClickReady: ready,
services: { scenePick },
providers: new Map(
providers.map(({ id, on = true, groundClick, configured = true }) => [
id,
{
on,
def: { id, groundClick },
status: { configured },
},
]),
providers.map(
({
id,
on = true,
groundClick,
configured = true,
available = true,
}) => [
id,
{ on, def: { id, groundClick }, status: { configured }, available },
],
),
),
viewer: {
scene: {
pickPositionSupported: true,
pickPosition: () => picked,
globe: { getHeight: () => 10 },
},
camera: {
positionCartographic: Cesium.Cartographic.fromDegrees(
-121.4944,
38.5816,
10 + height,
),
pickEllipsoid: () => undefined,
},
scene: { pickPositionSupported: true, pickPosition: () => picked },
camera: { pickEllipsoid: () => ellipsoid },
},
};
const openAtGround = createGroundClick({
state,
parts: {},
openNearest: (point, options) => {
opened.push({ point, options });
return Promise.resolve(true);
},
isAvailable: (entry) => entry.available,
});
return { state, opened, openAtGround };
return { opened, openAtGround };
}
test('a ground click at street zoom opens the nearest image of the click-to-open providers there', () => {
const { opened, openAtGround } = harness();
openAtGround({ x: 10, y: 20 });
assert.equal(opened.length, 1);
const [{ point, options }] = opened;
assert.ok(Math.abs(point.lon - -121.4944) < 1e-9);
assert.ok(Math.abs(point.lat - 38.5816) < 1e-9);
assert.deepEqual(options, { providerIds: ['google'] });
});
const near = (point) =>
Math.abs(point.lon - -121.4944) < 1e-9 &&
Math.abs(point.lat - 38.5816) < 1e-9;
test('nothing opens from high up, over the sky, with the provider off or unconfigured, or the layer off', () => {
for (const options of [
{ height: 5000 },
{ picked: null },
{ picked: new Cesium.Cartesian3(500, 0, 0) },
{ providers: [{ id: 'google', groundClick: true, on: false }] },
{ providers: [{ id: 'google', groundClick: true, configured: false }] },
{ providers: [{ id: 'mapillary', groundClick: false }] },
{ enabled: false },
]) {
const { opened, openAtGround } = harness(options);
assert.equal(openAtGround({ x: 1, y: 1 }), false, JSON.stringify(options));
assert.equal(opened.length, 0);
test('a click on the map opens the click-to-open providers there, without moving the camera', () => {
for (const picked of [undefined, { primitive: TILESET }, { content: {} }]) {
const { opened, openAtGround } = harness();
openAtGround({ x: 10, y: 20 }, picked);
assert.equal(opened.length, 1, JSON.stringify(picked));
assert.ok(near(opened[0].point));
assert.deepEqual(opened[0].options, {
providerIds: ['google'],
frame: false,
});
}
});
test('where the depth buffer names no place, the ellipsoid answers', () => {
const { opened, openAtGround } = harness({
picked: new Cesium.Cartesian3(500, 0, 0),
ellipsoid: GROUND,
});
openAtGround({ x: 1, y: 1 });
assert.ok(near(opened[0].point));
});
test('nothing opens over another layer’s object, the sky, from high up, off its map stack, or with the provider off', () => {
const cases = [
{ picked: { id: { id: 'flight:1' } }, label: 'an entity' },
{ picked: { id: 'cctv:7', primitive: TILESET }, label: 'an id on a tile' },
{ options: { picked: null, ellipsoid: undefined }, label: 'the sky' },
{ options: { ready: false }, label: 'high up' },
{
options: {
providers: [{ id: 'google', groundClick: true, available: false }],
},
label: 'not Google 3D',
},
{
options: { providers: [{ id: 'google', groundClick: true, on: false }] },
label: 'off',
},
{
options: {
providers: [{ id: 'google', groundClick: true, configured: false }],
},
label: 'no key',
},
{ options: { enabled: false }, label: 'layer off' },
];
for (const { picked, options = {}, label } of cases) {
const { opened, openAtGround } = harness(options);
assert.equal(openAtGround({ x: 1, y: 1 }, picked), false, label);
assert.equal(opened.length, 0, label);
}
});
test('street zoom is measured above the ground under the camera', () => {
const viewer = (height) => ({
camera: {
positionCartographic: Cesium.Cartographic.fromDegrees(0, 0, height),
},
scene: { globe: { getHeight: () => 1500 } },
});
// 1,500 m up a plateau, 2,000 m above it: still a street.
assert.equal(groundClickHeightOk(viewer(3500)), true);
assert.equal(groundClickHeightOk(viewer(5000)), false);
assert.equal(groundClickHeightOk(null), false);
});