mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-08 12:23:59 +00:00
Add data credits through the renderer's credits port
Data attribution no longer builds Cesium credits itself: each credit is added through the renderer's credits port, in the attribution list as before. The port's credit carries one link, so a credit whose markup holds several links becomes one credit per link, shown together in order, its wording kept. dataCredits.js leaves the Cesium allow-list.
This commit is contained in:
1 parent
8f612ba858
commit
9049357916
3 files changed
+167
-25
No files matched your search
+65
-24
@@ -1,4 +1,4 @@
|
||||
import * as Cesium from 'cesium';
|
||||
import { creditFromMarkup } from '../render/creditMarkup.js';
|
||||
import { findCredits } from '../render/rendererOf.js';
|
||||
|
||||
/**
|
||||
@@ -617,43 +617,84 @@ export const BHOTE_KOSHI_LOCATOR_CREDIT = {
|
||||
'(CC BY-NC 4.0)',
|
||||
};
|
||||
|
||||
/** A link, its whole element. */
|
||||
const LINK = /<a\b(?:[^>"']|"[^"]*"|'[^']*')*>[\s\S]*?<\/a\s*>/gi;
|
||||
/**
|
||||
* The separators text between two links can break at; it breaks at its
|
||||
* last. A semicolon ending a character reference is not one. A full stop
|
||||
* after an abbreviation reads as one, so keep "Dr." and "e.g." out of the
|
||||
* text between links.
|
||||
*/
|
||||
const SEPARATOR = /\s*(?:[,·—–]|(?<!&#?\w+);)\s*|\.\s+|\s+(?=\()/g;
|
||||
|
||||
/**
|
||||
* A credit's markup as the renderer's credits, one link each: the
|
||||
* contract's credit carries one link, so markup with several becomes one
|
||||
* credit per link, shown together in order. The text between two links
|
||||
* breaks at its last separator (a comma, semicolon, dot or dash, a full
|
||||
* stop, or before an opening bracket); closing brackets right after a link
|
||||
* stay with it, and a break's comma, semicolon, dot or dash is dropped.
|
||||
* @param {string} html
|
||||
* @returns {import('../render/contract/credits').Credit[]}
|
||||
*/
|
||||
export function creditsFromMarkup(html) {
|
||||
const markup = String(html);
|
||||
const pieces = [''];
|
||||
let at = 0;
|
||||
for (const link of markup.matchAll(LINK)) {
|
||||
let gap = markup.slice(at, link.index);
|
||||
if (at > 0) {
|
||||
const close = /^[)\]]*/.exec(gap)[0];
|
||||
gap = gap.slice(close.length);
|
||||
const cut = [...gap.matchAll(SEPARATOR)].at(-1);
|
||||
const before = cut ? gap.slice(0, cut.index) : '';
|
||||
const stop = cut && cut[0].trimStart().startsWith('.') ? '.' : '';
|
||||
pieces[pieces.length - 1] += close + before + stop;
|
||||
pieces.push(cut ? gap.slice(cut.index + cut[0].length) : gap);
|
||||
} else pieces[0] += gap;
|
||||
pieces[pieces.length - 1] += link[0];
|
||||
at = link.index + link[0].length;
|
||||
}
|
||||
pieces[pieces.length - 1] += markup.slice(at);
|
||||
return pieces
|
||||
.map((piece) => ({ ...creditFromMarkup(piece), inline: false }))
|
||||
.filter((credit) => credit.text);
|
||||
}
|
||||
|
||||
/** Add a data credit to the attribution list through the renderer. */
|
||||
function addDataCredit(viewer, credit) {
|
||||
const credits = findCredits(viewer);
|
||||
if (!credits) return false;
|
||||
for (const one of creditsFromMarkup(credit.html)) credits.addStatic(one);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @type {Set<string>} Keys of dynamic credits already registered this session. */
|
||||
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
|
||||
* Idempotent per `credit.key`; lands in the same "Data attribution" list
|
||||
* as the static credits, through the renderer's credits port.
|
||||
* @param {object} viewer — the scene object the renderer is bound to
|
||||
* @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;
|
||||
}
|
||||
if (!credit?.key || !credit?.html) return false;
|
||||
if (_dynamicCreditKeys.has(credit.key)) return true;
|
||||
creditDisplay.addStaticCredit(new Cesium.Credit(credit.html, false));
|
||||
if (!viewer || !credit?.key || !credit?.html) return false;
|
||||
if (_dynamicCreditKeys.has(credit.key)) return Boolean(findCredits(viewer));
|
||||
if (!addDataCredit(viewer, credit)) return false;
|
||||
_dynamicCreditKeys.add(credit.key);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Register every per-layer data credit through the renderer's credits
|
||||
* port. Safe to call once at init. Credits are static and always present
|
||||
* in the "Data attribution" list, not on the map's credit line.
|
||||
* @param {object} viewer — the scene object the renderer is bound to
|
||||
*/
|
||||
export function registerDataCredits(viewer, credits = DATA_CREDITS) {
|
||||
const creditDisplay = viewer?.creditDisplay;
|
||||
if (!creditDisplay || typeof creditDisplay.addStaticCredit !== 'function') {
|
||||
return;
|
||||
}
|
||||
for (const { html } of credits) {
|
||||
// showOnScreen=false → lives in the expandable "Data attribution" popover,
|
||||
// not the on-globe credit line.
|
||||
creditDisplay.addStaticCredit(new Cesium.Credit(html, false));
|
||||
}
|
||||
if (!viewer) return;
|
||||
for (const credit of credits) addDataCredit(viewer, credit);
|
||||
}
|
||||
@@ -142,3 +142,105 @@ test('every Street Level imagery source is in the attribution lightbox', () => {
|
||||
`${key} has no credit`,
|
||||
);
|
||||
});
|
||||
|
||||
test('a credit with several links becomes one credit per link, in order', async () => {
|
||||
const {
|
||||
creditsFromMarkup,
|
||||
OSM_CREDIT,
|
||||
OPENMAPTILES_CREDIT,
|
||||
WOF_CREDIT,
|
||||
BHOTE_KOSHI_CREDIT,
|
||||
} = await import('./dataCredits.js');
|
||||
const html = (key) => DATA_CREDITS.find((entry) => entry.key === key).html;
|
||||
const read = (markup) =>
|
||||
creditsFromMarkup(markup).map(({ text, href }) => [text, href]);
|
||||
assert.deepEqual(read(OSM_CREDIT.html), [
|
||||
[
|
||||
'Map and place data © OpenStreetMap contributors',
|
||||
'https://www.openstreetmap.org/copyright',
|
||||
],
|
||||
['(ODbL)', 'https://opendatacommons.org/licenses/odbl/1-0/'],
|
||||
]);
|
||||
assert.deepEqual(read(OPENMAPTILES_CREDIT.html), [
|
||||
['Vector tiles: OpenFreeMap', 'https://openfreemap.org'],
|
||||
['© OpenMapTiles', 'https://openmaptiles.org'],
|
||||
]);
|
||||
assert.deepEqual(read(html('nasa-gibs'))[1], [
|
||||
'HLS product page',
|
||||
'https://lpdaac.usgs.gov/products/hlss30v002/',
|
||||
]);
|
||||
assert.deepEqual(read(html('wfigs')), [
|
||||
[
|
||||
'Wildfire perimeters: National Interagency Fire Center (WFIGS)',
|
||||
'https://data-nifc.opendata.arcgis.com/',
|
||||
],
|
||||
['Incident information: InciWeb', 'https://inciweb.wildfire.gov/'],
|
||||
]);
|
||||
assert.deepEqual(read(html('eccc-aqhi')), [
|
||||
[
|
||||
'Air quality (AQHI): Environment and Climate Change Canada — MSC GeoMet (eccc-msc.github.io/open-data)',
|
||||
'https://eccc-msc.github.io/open-data/',
|
||||
],
|
||||
[
|
||||
'ECCC Data Servers End-use Licence',
|
||||
'https://eccc-msc.github.io/open-data/licence/readme_en/',
|
||||
],
|
||||
]);
|
||||
assert.deepEqual(
|
||||
read(BHOTE_KOSHI_CREDIT.html)[1][0].slice(0, 15),
|
||||
'and GeoPera (CC',
|
||||
);
|
||||
assert.deepEqual(
|
||||
read(WOF_CREDIT.html).map(([text]) => text),
|
||||
[
|
||||
"Neighborhood boundaries: Who's On First and contributors, simplified",
|
||||
'(sources and licences)',
|
||||
],
|
||||
);
|
||||
const link = (text) => `<a href="https://example.org">${text}</a>`;
|
||||
assert.deepEqual(
|
||||
read(`${link('A')} by Hoffleit & Warren ${link('B')}`).map(
|
||||
([text]) => text,
|
||||
),
|
||||
['A', 'by Hoffleit & Warren B'],
|
||||
'a character reference is not a separator',
|
||||
);
|
||||
assert.deepEqual(read(html('usgs')), [
|
||||
['Earthquakes: Data courtesy of the U.S. Geological Survey', undefined],
|
||||
]);
|
||||
for (const entry of DATA_CREDITS) {
|
||||
const credits = creditsFromMarkup(entry.html);
|
||||
const links = entry.html.match(/<a\b/g)?.length ?? 0;
|
||||
assert.equal(credits.length, Math.max(links, 1), entry.key);
|
||||
assert.ok(
|
||||
credits.every((credit) => credit.inline === false),
|
||||
`${entry.key} belongs in the attribution list`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('data credits go through the renderer to the attribution list', async () => {
|
||||
const { registerDataCredits, registerDynamicCredit, TOMTOM_CREDIT } =
|
||||
await import('./dataCredits.js');
|
||||
const shown = [];
|
||||
const viewer = bindFakeCesiumCredits({
|
||||
creditDisplay: {
|
||||
addStaticCredit: (c) => shown.push(c),
|
||||
removeStaticCredit() {},
|
||||
},
|
||||
});
|
||||
const credits = DATA_CREDITS.filter((entry) =>
|
||||
['openstreetmap', 'usgs'].includes(entry.key),
|
||||
);
|
||||
registerDataCredits(viewer, credits);
|
||||
assert.equal(shown.length, 3);
|
||||
assert.ok(shown.every((credit) => !credit.showOnScreen));
|
||||
const odbl = shown.find((credit) => credit.html.includes('opendatacommons'));
|
||||
assert.match(odbl.html, /href="https:\/\/opendatacommons.org/);
|
||||
assert.match(odbl.html, />\(ODbL\)</);
|
||||
assert.equal(registerDynamicCredit(viewer, TOMTOM_CREDIT), true);
|
||||
assert.equal(registerDynamicCredit(viewer, TOMTOM_CREDIT), true);
|
||||
assert.equal(shown.length, 4, 'a dynamic credit is added once');
|
||||
assert.match(shown[3].html, /Traffic flow data © TomTom/);
|
||||
assert.equal(registerDynamicCredit({}, { key: 'none', html: 'x' }), false);
|
||||
});
|
||||
@@ -24,7 +24,6 @@
|
||||
"src/data/cctvGizmo.js",
|
||||
"src/data/cctvViewshed.js",
|
||||
"src/data/contactTrailRenderer.js",
|
||||
"src/data/dataCredits.js",
|
||||
"src/data/detection.js",
|
||||
"src/data/focusDeemphasis.js",
|
||||
"src/data/geoFileImport.js",
|
||||
|
||||
Reference in new issue
Block a user