mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +00:00
refactor(credits): use the globe engine
Map credits (maps/credits.js) are shown through a credits capability (engine.credits.addStatic) instead of the scene's credit display; the shared map scene takes the engine's credits, so the Cesium engine's maps and its credits part share one owner. Data credits (registerDataCredits, registerDynamicCredit, show/hideOsmCredit) take the globe engine and keep accepting a viewer from layers that still hold one; the scene registers the data credits on its engine. The Cesium credits part reads Viewer.creditDisplay, else the scene frame state display it aliases.
This commit is contained in:
1 parent
be56144d75
commit
83c13f330d
11 files changed
+169
-59
No files matched your search
@@ -1607,6 +1607,7 @@
|
||||
"./maps/defaults"
|
||||
],
|
||||
"modules": [
|
||||
"src/globe/cesium/credits.js",
|
||||
"src/maps/availability.js",
|
||||
"src/maps/catalog.js",
|
||||
"src/maps/controller.js",
|
||||
@@ -3086,6 +3087,7 @@
|
||||
"application-operations": {
|
||||
"exports": ["./application/operations"],
|
||||
"modules": [
|
||||
"src/globe/cesium/credits.js",
|
||||
"src/services/groundSnap.js",
|
||||
"src/globe/contract.js",
|
||||
"src/annotations/annotationResolver.js",
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ export async function createApplicationScene({
|
||||
const tilesetRef = { current: null };
|
||||
const engine = createSceneEngine(viewer, { tilesetRef, defer });
|
||||
defer(installTrackpadPinchZoom(viewer));
|
||||
registerDataCredits(viewer, credits);
|
||||
registerDataCredits(engine, credits);
|
||||
configureCreditKeyboardAccess(document);
|
||||
loaderStatus.textContent =
|
||||
googleApiKey || googleTokens || cesiumToken
|
||||
|
||||
+60
-41
@@ -1,4 +1,5 @@
|
||||
import * as Cesium from 'cesium';
|
||||
import { isGlobeEngine } from '../globe/contract.js';
|
||||
import { createCesiumCredits } from '../globe/cesium/credits.js';
|
||||
|
||||
/**
|
||||
* Per-layer data attribution registered into Cesium's credit display.
|
||||
@@ -35,8 +36,32 @@ export const OPENMAPTILES_CREDIT = {
|
||||
};
|
||||
|
||||
const osmDisplays = new WeakMap();
|
||||
const viewerCredits = new WeakMap();
|
||||
|
||||
function updateOsmDisplay(viewer, state) {
|
||||
/**
|
||||
* Credits of a globe engine, or of a Cesium viewer's credit display for
|
||||
* callers that still hold a viewer (layers until phase 1c); null without one.
|
||||
* @returns {{ addStatic: Function, request: () => void } | null}
|
||||
*/
|
||||
function creditsOf(target) {
|
||||
if (isGlobeEngine(target))
|
||||
return {
|
||||
addStatic: (html, options) => target.credits.addStatic(html, options),
|
||||
request: () => target.render.request('credits'),
|
||||
};
|
||||
if (typeof target?.creditDisplay?.addStaticCredit !== 'function') return null;
|
||||
let credits = viewerCredits.get(target);
|
||||
if (!credits) {
|
||||
credits = {
|
||||
addStatic: createCesiumCredits(target).credits.addStatic,
|
||||
request: () => target.scene?.requestRender?.(),
|
||||
};
|
||||
viewerCredits.set(target, credits);
|
||||
}
|
||||
return credits;
|
||||
}
|
||||
|
||||
function updateOsmDisplay(credits, state) {
|
||||
const osm = state.owners.size > 0;
|
||||
let tiles = false;
|
||||
for (const usesTiles of state.owners.values()) tiles ||= usesTiles;
|
||||
@@ -44,50 +69,48 @@ function updateOsmDisplay(viewer, state) {
|
||||
['osm', osm],
|
||||
['tiles', tiles],
|
||||
]) {
|
||||
if (state[key].visible === visible) continue;
|
||||
state[key].visible = visible;
|
||||
viewer.creditDisplay[visible ? 'addStaticCredit' : 'removeStaticCredit'](
|
||||
state[key].credit,
|
||||
);
|
||||
viewer.scene?.requestRender?.();
|
||||
const entry = state[key];
|
||||
if ((entry.remove !== null) === visible) continue;
|
||||
if (visible)
|
||||
entry.remove = credits.addStatic(entry.html, { onScreen: true });
|
||||
else {
|
||||
entry.remove();
|
||||
entry.remove = null;
|
||||
}
|
||||
credits.request();
|
||||
}
|
||||
}
|
||||
|
||||
/** Keep one short inline credit while an OSM display owner has visible data. */
|
||||
export function showOsmCredit(viewer, owner, { openMapTiles = false } = {}) {
|
||||
if (!viewer?.creditDisplay?.addStaticCredit || !owner) return false;
|
||||
let state = osmDisplays.get(viewer);
|
||||
export function showOsmCredit(target, owner, { openMapTiles = false } = {}) {
|
||||
const credits = creditsOf(target);
|
||||
if (!credits || !owner) return false;
|
||||
let state = osmDisplays.get(target);
|
||||
if (!state) {
|
||||
state = {
|
||||
owners: new Map(),
|
||||
osm: {
|
||||
visible: false,
|
||||
credit: new Cesium.Credit(
|
||||
'<a href="https://www.openstreetmap.org/copyright" target="_blank" rel="noopener">© OpenStreetMap</a>',
|
||||
true,
|
||||
),
|
||||
remove: null,
|
||||
html: '<a href="https://www.openstreetmap.org/copyright" target="_blank" rel="noopener">© OpenStreetMap</a>',
|
||||
},
|
||||
tiles: {
|
||||
visible: false,
|
||||
credit: new Cesium.Credit(
|
||||
'<a href="https://openmaptiles.org" target="_blank" rel="noopener">© OpenMapTiles</a>',
|
||||
true,
|
||||
),
|
||||
remove: null,
|
||||
html: '<a href="https://openmaptiles.org" target="_blank" rel="noopener">© OpenMapTiles</a>',
|
||||
},
|
||||
};
|
||||
osmDisplays.set(viewer, state);
|
||||
osmDisplays.set(target, state);
|
||||
}
|
||||
if (state.owners.get(owner) === openMapTiles) return false;
|
||||
state.owners.set(owner, openMapTiles);
|
||||
updateOsmDisplay(viewer, state);
|
||||
updateOsmDisplay(credits, state);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Release only this owner's credit; other displayed OSM sources retain theirs. */
|
||||
export function hideOsmCredit(viewer, owner) {
|
||||
const state = osmDisplays.get(viewer);
|
||||
export function hideOsmCredit(target, owner) {
|
||||
const state = osmDisplays.get(target);
|
||||
if (!state?.owners.delete(owner)) return false;
|
||||
updateOsmDisplay(viewer, state);
|
||||
updateOsmDisplay(creditsOf(target), state);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -440,18 +463,16 @@ const _dynamicCreditKeys = new Set();
|
||||
* Register a conditional credit at the moment its data source activates.
|
||||
* Idempotent per `credit.key`; lands in the same "Data attribution" popover
|
||||
* as the static credits (showOnScreen=false).
|
||||
* @param {Cesium.Viewer} viewer — the initialized Cesium viewer
|
||||
* @param {object} target — the globe engine, or the initialized Cesium viewer
|
||||
* @param {{ key: string, html: string }} credit — e.g. `TOMTOM_CREDIT`
|
||||
* @returns {boolean} True when the credit is (now) registered.
|
||||
*/
|
||||
export function registerDynamicCredit(viewer, credit) {
|
||||
const creditDisplay = viewer?.creditDisplay;
|
||||
if (!creditDisplay || typeof creditDisplay.addStaticCredit !== 'function') {
|
||||
return false;
|
||||
}
|
||||
export function registerDynamicCredit(target, credit) {
|
||||
const credits = creditsOf(target);
|
||||
if (!credits) return false;
|
||||
if (!credit?.key || !credit?.html) return false;
|
||||
if (_dynamicCreditKeys.has(credit.key)) return true;
|
||||
creditDisplay.addStaticCredit(new Cesium.Credit(credit.html, false));
|
||||
credits.addStatic(credit.html, { onScreen: false });
|
||||
_dynamicCreditKeys.add(credit.key);
|
||||
return true;
|
||||
}
|
||||
@@ -460,16 +481,14 @@ export function registerDynamicCredit(viewer, credit) {
|
||||
* Register every per-layer data credit into the viewer's credit display.
|
||||
* Idempotent: safe to call once at init. Credits are static and always
|
||||
* present in the "Data attribution" popover.
|
||||
* @param {Cesium.Viewer} viewer — the initialized Cesium viewer
|
||||
* @param {object} target — the globe engine, or the initialized Cesium viewer
|
||||
*/
|
||||
export function registerDataCredits(viewer, credits = DATA_CREDITS) {
|
||||
const creditDisplay = viewer?.creditDisplay;
|
||||
if (!creditDisplay || typeof creditDisplay.addStaticCredit !== 'function') {
|
||||
return;
|
||||
}
|
||||
export function registerDataCredits(target, credits = DATA_CREDITS) {
|
||||
const display = creditsOf(target);
|
||||
if (!display) return;
|
||||
for (const { html } of credits) {
|
||||
// showOnScreen=false → lives in the expandable "Data attribution" popover,
|
||||
// onScreen false → lives in the expandable "Data attribution" popover,
|
||||
// not the on-globe credit line.
|
||||
creditDisplay.addStaticCredit(new Cesium.Credit(html, false));
|
||||
display.addStatic(html, { onScreen: false });
|
||||
}
|
||||
}
|
||||
@@ -120,3 +120,42 @@ test('tile attribution follows source changes and viewers have independent owner
|
||||
assert.equal(a.credits.size, 0);
|
||||
assert.equal(b.credits.size, 1);
|
||||
});
|
||||
|
||||
test('data credits go through the globe engine credits', async () => {
|
||||
const {
|
||||
registerDataCredits,
|
||||
registerDynamicCredit,
|
||||
showOsmCredit,
|
||||
hideOsmCredit,
|
||||
} = await import('./dataCredits.js');
|
||||
const { createFakeEngine } = await import('../globe/testing/fakeEngine.js');
|
||||
const engine = createFakeEngine();
|
||||
registerDataCredits(engine, [{ html: 'A' }, { html: 'B' }]);
|
||||
assert.deepEqual(engine.fake.credits, [
|
||||
{ html: 'A', onScreen: false },
|
||||
{ html: 'B', onScreen: false },
|
||||
]);
|
||||
assert.equal(
|
||||
registerDynamicCredit(engine, { key: 'engine-dyn', html: 'D' }),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
registerDynamicCredit(engine, { key: 'engine-dyn', html: 'D' }),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
engine.fake.credits.filter(({ html }) => html === 'D').length,
|
||||
1,
|
||||
);
|
||||
engine.fake.resetCalls();
|
||||
assert.equal(showOsmCredit(engine, 'alpr', { openMapTiles: true }), true);
|
||||
const inline = engine.fake.credits.filter(({ onScreen }) => onScreen);
|
||||
assert.equal(inline.length, 2);
|
||||
assert.match(inline[0].html, /© OpenStreetMap/);
|
||||
assert.ok(engine.fake.callsOf('render.request').length > 0);
|
||||
assert.equal(hideOsmCredit(engine, 'alpr'), true);
|
||||
assert.equal(
|
||||
engine.fake.credits.filter(({ onScreen }) => onScreen).length,
|
||||
0,
|
||||
);
|
||||
});
|
||||
@@ -34,6 +34,7 @@ export function createCesiumEngine(viewer, options = {}) {
|
||||
// Viewer.canvas is scene.canvas; the engine reads the public alias.
|
||||
const canvas = viewer.canvas ?? viewer.scene.canvas;
|
||||
const render = createCesiumRender(viewer, options);
|
||||
const credits = createCesiumCredits(viewer);
|
||||
const parts = {
|
||||
camera: createCesiumCamera(viewer),
|
||||
picker: createCesiumPicker(viewer),
|
||||
@@ -42,9 +43,10 @@ export function createCesiumEngine(viewer, options = {}) {
|
||||
render,
|
||||
surface: createCesiumSurface(viewer),
|
||||
input: createCesiumInput(viewer, options),
|
||||
credits: createCesiumCredits(viewer),
|
||||
credits,
|
||||
maps: createCesiumMaps(viewer, {
|
||||
...options,
|
||||
credits: credits.credits,
|
||||
requestRender: (reason) => render.render.request(reason),
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -8,7 +8,9 @@ import * as Cesium from 'cesium';
|
||||
*/
|
||||
export function createCesiumCredits(viewer) {
|
||||
const added = new Set();
|
||||
const display = () => viewer.creditDisplay;
|
||||
// Viewer.creditDisplay is the scene frame state's display.
|
||||
const display = () =>
|
||||
viewer.creditDisplay ?? viewer.scene?.frameState?.creditDisplay;
|
||||
const remove = (credit) => {
|
||||
if (!added.delete(credit)) return;
|
||||
display()?.removeStaticCredit(credit);
|
||||
|
||||
@@ -31,3 +31,18 @@ test('destroy removes the credits the engine added', () => {
|
||||
assert.equal(viewer.staticCredits.length, 0);
|
||||
assert.equal(viewer.callsOf('creditDisplay.removeStaticCredit').length, 2);
|
||||
});
|
||||
|
||||
test('the display is Viewer.creditDisplay, else the scene frame state one it aliases', () => {
|
||||
const added = [];
|
||||
const display = {
|
||||
addStaticCredit: (credit) => added.push(credit),
|
||||
removeStaticCredit: (credit) => added.splice(added.indexOf(credit), 1),
|
||||
};
|
||||
const { credits } = createCesiumCredits({
|
||||
scene: { frameState: { creditDisplay: display } },
|
||||
});
|
||||
const remove = credits.addStatic('frame state');
|
||||
assert.equal(added[0].html, 'frame state');
|
||||
remove();
|
||||
assert.deepEqual(added, []);
|
||||
});
|
||||
@@ -63,6 +63,7 @@ const dispose = (value) => {
|
||||
* tilesetRef?: { current: object | null } | (() => object | null),
|
||||
* requestRender?: (reason: string) => void,
|
||||
* createImageryLayer?: (provider: object) => object,
|
||||
* credits?: object,
|
||||
* loadPhotorealTileset?: (credentials: object) => Promise<{ tileset: object | null, errors: Error[] }>,
|
||||
* }} [options] `tilesetRef` names the startup Google tileset the app added.
|
||||
* @returns {{ maps: object, destroy: () => void }}
|
||||
@@ -73,12 +74,16 @@ export function createCesiumMaps(
|
||||
tilesetRef = null,
|
||||
requestRender = () => viewer.scene.requestRender?.(),
|
||||
createImageryLayer = (provider) => new Cesium.ImageryLayer(provider),
|
||||
credits,
|
||||
loadPhotorealTileset = (credentials) =>
|
||||
loadPhotorealisticTileset(Cesium, credentials),
|
||||
} = {},
|
||||
) {
|
||||
const abort = new AbortController();
|
||||
const mapScene = createMapScene(viewer, { createImageryLayer });
|
||||
const mapScene = createMapScene(viewer, {
|
||||
createImageryLayer,
|
||||
...(credits && { credits }),
|
||||
});
|
||||
const providers = new Map();
|
||||
let ownedTileset = null;
|
||||
let shownTileset = null;
|
||||
|
||||
+11
-11
@@ -1,21 +1,21 @@
|
||||
import * as Cesium from 'cesium';
|
||||
|
||||
/** Static credits follow the source actually on screen, including fallback. */
|
||||
export function createMapCredits(viewer) {
|
||||
let active = null;
|
||||
/**
|
||||
* Static credits follow the source actually on screen, including fallback.
|
||||
* @param {{ addStatic: (html: string, options?: { onScreen?: boolean }) => () => void }} credits
|
||||
* A globe engine's credits capability.
|
||||
*/
|
||||
export function createMapCredits(credits) {
|
||||
let remove = null;
|
||||
let markup = null;
|
||||
return {
|
||||
show(html) {
|
||||
if (html === markup) return;
|
||||
const display = viewer?.scene?.frameState?.creditDisplay;
|
||||
if (!display) return;
|
||||
try {
|
||||
if (active) display.removeStaticCredit(active);
|
||||
active = html ? new Cesium.Credit(html, true) : null;
|
||||
remove?.();
|
||||
remove = null;
|
||||
if (html) remove = credits.addStatic(html, { onScreen: true });
|
||||
markup = html;
|
||||
if (active) display.addStaticCredit(active);
|
||||
} catch {
|
||||
/* Switching remains usable on older credit displays. */
|
||||
/* Switching remains usable without a credit display. */
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
|
||||
+11
-3
@@ -1,4 +1,5 @@
|
||||
import * as Cesium from 'cesium';
|
||||
import { createCesiumCredits } from '../globe/cesium/credits.js';
|
||||
import { createMapCredits } from './credits.js';
|
||||
|
||||
/**
|
||||
@@ -7,13 +8,20 @@ import { createMapCredits } from './credits.js';
|
||||
* Shared by MapSourceController and the Cesium globe engine's `maps`, so
|
||||
* both apply sources the same way. Creating it touches nothing.
|
||||
* @param {Cesium.Viewer} viewer
|
||||
* @param {{ createImageryLayer?: (provider: object) => object }} [options]
|
||||
* @param {{
|
||||
* createImageryLayer?: (provider: object) => object,
|
||||
* credits?: import('../globe/contract.js').GlobeEngine['credits'],
|
||||
* }} [options] `credits`: the globe engine's credits (default: the viewer's
|
||||
* credit display through the Cesium engine's credits).
|
||||
*/
|
||||
export function createMapScene(
|
||||
viewer,
|
||||
{ createImageryLayer = (provider) => new Cesium.ImageryLayer(provider) } = {},
|
||||
{
|
||||
createImageryLayer = (provider) => new Cesium.ImageryLayer(provider),
|
||||
credits: engineCredits = createCesiumCredits(viewer).credits,
|
||||
} = {},
|
||||
) {
|
||||
const credits = createMapCredits(viewer);
|
||||
const credits = createMapCredits(engineCredits);
|
||||
let layer = null;
|
||||
let provider = null;
|
||||
const scene = {
|
||||
|
||||
@@ -89,3 +89,21 @@ test('removeBaseImagery, tilesets, globe and credits', () => {
|
||||
assert.deepEqual(layers, []);
|
||||
assert.deepEqual(credits, []);
|
||||
});
|
||||
|
||||
test('the map scene shows its credit through a given credits capability', async () => {
|
||||
const { createFakeEngine } = await import('../globe/testing/fakeEngine.js');
|
||||
const engine = createFakeEngine();
|
||||
const { viewer, credits } = fixture();
|
||||
const scene = createMapScene(viewer, {
|
||||
createImageryLayer: (provider) => ({ provider }),
|
||||
credits: engine.credits,
|
||||
});
|
||||
scene.showCredit('Esri');
|
||||
scene.showCredit('Esri');
|
||||
assert.deepEqual(engine.fake.credits, [{ html: 'Esri', onScreen: true }]);
|
||||
scene.showCredit('OSM');
|
||||
assert.deepEqual(engine.fake.credits, [{ html: 'OSM', onScreen: true }]);
|
||||
scene.destroy();
|
||||
assert.deepEqual(engine.fake.credits, []);
|
||||
assert.deepEqual(credits, [], 'nothing on the viewer display');
|
||||
});
|
||||
Reference in new issue
Block a user