mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-11 22:02:21 +00:00
Street Level: a posted view without a photo point grounds its look-at too
show_in_gods_eye_view measured a view's altitude from the terrain before taking the point the camera looks at; a view posted straight into the embedded app used the ellipsoid altitude, so a camera over high ground opened photos kilometres ahead. groundedLookAt moves to the view module, and the embedded app uses it with the app's terrain heights for a posted street_level that names no point.
This commit is contained in:
1 parent
a8970c1f18
commit
f612fc2180
5 files changed
+107
-26
No files matched your search
@@ -19,6 +19,7 @@ import {
|
||||
annotationsFromParams,
|
||||
createView,
|
||||
followFromParams,
|
||||
groundedLookAt,
|
||||
streetLevelFromParams,
|
||||
} from '../view/index.js';
|
||||
|
||||
@@ -329,6 +330,7 @@ export function installViews({
|
||||
dataManager,
|
||||
run,
|
||||
signal,
|
||||
terrain = null,
|
||||
location = globalThis.location,
|
||||
windowRef = globalThis.window,
|
||||
}) {
|
||||
@@ -477,8 +479,27 @@ export function installViews({
|
||||
// A panel's host can hand back old results on a remount: only one issued
|
||||
// just now may open a billed photo. A framing page's view is its own.
|
||||
const autoOpen = !isEmbeddedInline() || event.data.fresh === true;
|
||||
// Photos asked for without a point open where the camera looks, measured
|
||||
// from the ground under it, as show_in_gods_eye_view places them.
|
||||
const asked = event.data.view?.street_level;
|
||||
const unplaced =
|
||||
asked &&
|
||||
typeof asked === 'object' &&
|
||||
asked.lat == null &&
|
||||
asked.lon == null;
|
||||
queue = queue.then(async () => {
|
||||
if (signal?.aborted) return;
|
||||
if (unplaced && view.street_level) {
|
||||
const point = await groundedLookAt(view.camera, terrain, signal).catch(
|
||||
() => null,
|
||||
);
|
||||
if (signal?.aborted) return;
|
||||
if (point)
|
||||
view = createView({
|
||||
...event.data.view,
|
||||
street_level: { ...asked, ...point },
|
||||
});
|
||||
}
|
||||
const steps = await applyView(view, {
|
||||
viewer,
|
||||
dataManager,
|
||||
|
||||
@@ -104,6 +104,65 @@ test('a followed entity is retried until its layer has it', async () => {
|
||||
assert.deepEqual(steps.at(-1), { step: 'follow', ok: true });
|
||||
});
|
||||
|
||||
test('a posted view without a photo point opens its photos where the camera looks over the ground', async () => {
|
||||
const parent = { postMessage() {} };
|
||||
const windowRef = new EventTarget();
|
||||
windowRef.parent = parent;
|
||||
windowRef.document = { body: { classList: new Set() } };
|
||||
windowRef.document.body.classList.add = Set.prototype.add;
|
||||
const shell = { initialRestorePromise: Promise.resolve(), setCleanView() {} };
|
||||
const { calls, run } = recorder();
|
||||
const asked = [];
|
||||
// Denver: the ground is 1,590 m above the ellipsoid under a camera at 1,909 m.
|
||||
const terrain = {
|
||||
getHeights: async (points) => {
|
||||
asked.push(points);
|
||||
return points.map(() => ({ ellipsoid: 1590 }));
|
||||
},
|
||||
};
|
||||
const remove = installViews({
|
||||
shell,
|
||||
viewer: fakeViewer(),
|
||||
dataManager: fakeLayers(new Set()),
|
||||
run,
|
||||
terrain,
|
||||
location: { search: '?embed=1', hash: '' },
|
||||
windowRef,
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
const event = new Event('message');
|
||||
Object.assign(event, {
|
||||
source: parent,
|
||||
origin: 'https://host.example',
|
||||
data: {
|
||||
type: 'gev:view',
|
||||
id: 1,
|
||||
view: {
|
||||
camera: {
|
||||
lat: 39.74,
|
||||
lon: -104.99,
|
||||
altitude_m: 1909,
|
||||
pitch_deg: -30,
|
||||
heading_deg: 0,
|
||||
},
|
||||
street_level: { provider: 'mapillary' },
|
||||
},
|
||||
},
|
||||
});
|
||||
windowRef.dispatchEvent(event);
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
remove();
|
||||
const [, args] = calls.find(([name]) => name === 'show_street_level');
|
||||
// 319 m above the ground at -30° looks ~550 m ahead, not the ~3.3 km the
|
||||
// ellipsoid altitude would give.
|
||||
assert.deepEqual(asked, [[{ lat: 39.74, lon: -104.99 }]]);
|
||||
assert.ok(
|
||||
args.latitude > 39.744 && args.latitude < 39.746,
|
||||
String(args.latitude),
|
||||
);
|
||||
assert.equal(args.longitude, -104.99);
|
||||
});
|
||||
|
||||
test('an embedded page takes views only from its parent and answers it', async () => {
|
||||
const posted = [];
|
||||
const targets = [];
|
||||
|
||||
@@ -249,6 +249,7 @@ export function createApplicationTools({
|
||||
run: (name, args, options = {}) =>
|
||||
voiceCommands.runner(name, args, { ...options, signal }),
|
||||
signal,
|
||||
terrain: operations.requests.terrain,
|
||||
}),
|
||||
);
|
||||
// An application's own tool takes the pointer as Draw does, and its own
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import {
|
||||
VIEW_PROPERTIES,
|
||||
createView,
|
||||
lookAtPoint,
|
||||
groundedLookAt,
|
||||
viewUrl,
|
||||
} from '../../view/index.js';
|
||||
import { defineTool, ToolError } from '../catalog.js';
|
||||
@@ -41,30 +41,6 @@ async function followedAircraftPosition(follow, tools) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Where a tilted camera looks, measured from the ground under it: a view's
|
||||
* altitude is above the ellipsoid, and a city a mile up would otherwise put
|
||||
* the point kilometres too far ahead. Null when the terrain cannot say.
|
||||
*/
|
||||
async function groundedLookAt(camera, services, signal) {
|
||||
if (typeof services?.terrain?.getHeights !== 'function') return null;
|
||||
if (!Number.isFinite(camera.altitude_m) || !(camera.pitch_deg > -89))
|
||||
return null;
|
||||
let heights;
|
||||
try {
|
||||
heights = await services.terrain.getHeights(
|
||||
[{ lat: camera.lat, lon: camera.lon }],
|
||||
{ signal },
|
||||
);
|
||||
} catch (error) {
|
||||
if (signal?.aborted) throw error;
|
||||
return null;
|
||||
}
|
||||
const ground = Number(heights?.[0]?.ellipsoid);
|
||||
if (!Number.isFinite(ground)) return null;
|
||||
return lookAtPoint({ ...camera, altitude_m: camera.altitude_m - ground });
|
||||
}
|
||||
|
||||
/**
|
||||
* The view described by tool arguments. An area frames the camera straight
|
||||
* down over it; camera fields given alongside it override the framing. A
|
||||
@@ -131,7 +107,7 @@ export async function resolveViewArguments(args, { services, signal, tools }) {
|
||||
streetLevel.lat === undefined &&
|
||||
streetLevel.lon === undefined
|
||||
) {
|
||||
const point = await groundedLookAt(camera, services, signal);
|
||||
const point = await groundedLookAt(camera, services?.terrain, signal);
|
||||
if (point) streetLevel = { ...streetLevel, ...point };
|
||||
}
|
||||
let view;
|
||||
|
||||
@@ -123,6 +123,30 @@ export function lookAtPoint(camera) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Where a tilted camera looks, measured from the ground under it: a view's
|
||||
* altitude is above the ellipsoid, and a city a mile up would otherwise put
|
||||
* the point kilometres too far ahead. `terrain` is a heights service
|
||||
* (`getHeights`); null when it is missing or cannot say.
|
||||
*/
|
||||
export async function groundedLookAt(camera, terrain, signal) {
|
||||
if (typeof terrain?.getHeights !== 'function') return null;
|
||||
if (!Number.isFinite(camera?.altitude_m) || !(camera.pitch_deg > -89))
|
||||
return null;
|
||||
let heights;
|
||||
try {
|
||||
heights = await terrain.getHeights([{ lat: camera.lat, lon: camera.lon }], {
|
||||
signal,
|
||||
});
|
||||
} catch (error) {
|
||||
if (signal?.aborted) throw error;
|
||||
return null;
|
||||
}
|
||||
const ground = Number(heights?.[0]?.ellipsoid);
|
||||
if (!Number.isFinite(ground)) return null;
|
||||
return lookAtPoint({ ...camera, altitude_m: camera.altitude_m - ground });
|
||||
}
|
||||
|
||||
/** Annotation kinds the app draws, as in its annotate_map action. */
|
||||
export const VIEW_ANNOTATION_TYPES = Object.freeze([
|
||||
'pin',
|
||||
|
||||
Reference in new issue
Block a user