mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
Merge branch 'main' into i18n-spanish
This commit is contained in:
4 files changed
+204
-1
No files matched your search
@@ -1,5 +1,9 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
- Bing Aerial and Bing Labels request HTTPS tiles on HTTP localhost through
|
||||||
|
their ion-backed providers, retaining ion and per-tile attribution without
|
||||||
|
widening the Content Security Policy.
|
||||||
|
|
||||||
- Add New York City traffic cameras from the NYC DOT Traffic Management Center
|
- Add New York City traffic cameras from the NYC DOT Traffic Management Center
|
||||||
as an opt-in CCTV pack. NYC DOT requires a data-sharing agreement for the
|
as an opt-in CCTV pack. NYC DOT requires a data-sharing agreement for the
|
||||||
camera feed, so the pack is off unless `CCTV_NYC_ENABLED=1` is set by an
|
camera feed, so the pack is off unless `CCTV_NYC_ENABLED=1` is set by an
|
||||||
|
|||||||
@@ -5222,6 +5222,12 @@ photoreal globe is hidden.
|
|||||||
|
|
||||||
Map credentials are passed to each source constructor rather than changing SDK-wide Google or ion defaults.
|
Map credentials are passed to each source constructor rather than changing SDK-wide Google or ion defaults.
|
||||||
|
|
||||||
|
Bing Aerial and Bing Labels resolve their ion imagery endpoint with the supplied
|
||||||
|
token, then construct the Bing provider with `tileProtocol: 'https'`. Both styles
|
||||||
|
request HTTPS tiles even on HTTP localhost, with the existing Content Security
|
||||||
|
Policy unchanged. Ion attribution and Bing's per-tile credits are preserved;
|
||||||
|
other ion imagery assets continue through the SDK's standard ion factory.
|
||||||
|
|
||||||
The controller accepts other source registries without adding provider branches.
|
The controller accepts other source registries without adding provider branches.
|
||||||
Each instance caches source construction, ignores superseded scene changes and
|
Each instance caches source construction, ignores superseded scene changes and
|
||||||
releases imagery layers/listeners on replacement. Destroy invalidates pending
|
releases imagery layers/listeners on replacement. Destroy invalidates pending
|
||||||
|
|||||||
@@ -25,5 +25,42 @@ export function createEsriImagery() {
|
|||||||
export function createIonImagery(style, accessToken) {
|
export function createIonImagery(style, accessToken) {
|
||||||
accessToken = String(accessToken || '').trim();
|
accessToken = String(accessToken || '').trim();
|
||||||
if (!accessToken) throw new Error('Ion imagery requires an explicit token');
|
if (!accessToken) throw new Error('Ion imagery requires an explicit token');
|
||||||
|
if (
|
||||||
|
style === Cesium.IonWorldImageryStyle.AERIAL ||
|
||||||
|
style === Cesium.IonWorldImageryStyle.AERIAL_WITH_LABELS
|
||||||
|
) {
|
||||||
|
return createBingIonImagery(style, accessToken);
|
||||||
|
}
|
||||||
return Cesium.IonImageryProvider.fromAssetId(style, { accessToken });
|
return Cesium.IonImageryProvider.fromAssetId(style, { accessToken });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createBingIonImagery(style, accessToken) {
|
||||||
|
const endpoint = await new Cesium.Resource({
|
||||||
|
url: `https://api.cesium.com/v1/assets/${style}/endpoint`,
|
||||||
|
queryParameters: { access_token: accessToken },
|
||||||
|
}).fetchJson();
|
||||||
|
if (
|
||||||
|
endpoint?.type !== 'IMAGERY' ||
|
||||||
|
endpoint.externalType !== 'BING' ||
|
||||||
|
typeof endpoint.options?.url !== 'string'
|
||||||
|
) {
|
||||||
|
throw new Error('Ion world imagery did not return a Bing imagery endpoint');
|
||||||
|
}
|
||||||
|
// Bing otherwise follows the page's HTTP scheme on localhost. Resolve its
|
||||||
|
// ion-issued credentials through public APIs so tiles use HTTPS without
|
||||||
|
// weakening CSP, changing SDK defaults or touching provider internals.
|
||||||
|
const { url, ...options } = endpoint.options;
|
||||||
|
const provider = await Cesium.BingMapsImageryProvider.fromUrl(url, {
|
||||||
|
...options,
|
||||||
|
tileProtocol: 'https',
|
||||||
|
});
|
||||||
|
const ionCredits = (endpoint.attributions ?? []).map(
|
||||||
|
({ html, collapsible }) => new Cesium.Credit(html, collapsible === false),
|
||||||
|
);
|
||||||
|
const getTileCredits = provider.getTileCredits.bind(provider);
|
||||||
|
provider.getTileCredits = (x, y, level) => [
|
||||||
|
...ionCredits,
|
||||||
|
...(getTileCredits(x, y, level) ?? []),
|
||||||
|
];
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
@@ -89,7 +89,7 @@ test('imagery and terrain pass their own ion token without relying on SDK defaul
|
|||||||
calls.push({ kind: 'terrain', resource, options });
|
calls.push({ kind: 'terrain', resource, options });
|
||||||
return { id: 'terrain' };
|
return { id: 'terrain' };
|
||||||
};
|
};
|
||||||
await createIonImagery(Cesium.IonWorldImageryStyle.AERIAL, 'imagery-token');
|
await createIonImagery(12345, 'imagery-token');
|
||||||
const result = await createWorldTerrain('terrain-token');
|
const result = await createWorldTerrain('terrain-token');
|
||||||
assert.equal(calls[0].options.accessToken, 'imagery-token');
|
assert.equal(calls[0].options.accessToken, 'imagery-token');
|
||||||
assert.equal(calls[1].options.accessToken, 'terrain-token');
|
assert.equal(calls[1].options.accessToken, 'terrain-token');
|
||||||
@@ -104,6 +104,162 @@ test('imagery and terrain pass their own ion token without relying on SDK defaul
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('both Bing ion stacks use HTTPS and retain ion and per-tile attribution', async (t) => {
|
||||||
|
const defaultToken = Cesium.Ion.defaultAccessToken;
|
||||||
|
const metadataCalls = [];
|
||||||
|
const bingCalls = [];
|
||||||
|
const perTileCalls = [];
|
||||||
|
const bingCredit = new Cesium.Credit('Bing tile credit', true);
|
||||||
|
const endpoint = {
|
||||||
|
type: 'IMAGERY',
|
||||||
|
externalType: 'BING',
|
||||||
|
options: Object.freeze({
|
||||||
|
url: 'https://dev.virtualearth.net',
|
||||||
|
key: 'ion-issued-bing-fixture',
|
||||||
|
mapStyle: 'Aerial',
|
||||||
|
tileProtocol: 'http',
|
||||||
|
}),
|
||||||
|
attributions: [
|
||||||
|
{ html: 'Ion required credit', collapsible: false },
|
||||||
|
{ html: 'Ion optional credit', collapsible: true },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
t.mock.method(Cesium.Resource.prototype, 'fetchJson', async function () {
|
||||||
|
metadataCalls.push({
|
||||||
|
url: this.url,
|
||||||
|
token: this.queryParameters.access_token,
|
||||||
|
});
|
||||||
|
const mapStyle = this.url.includes(
|
||||||
|
`/assets/${Cesium.IonWorldImageryStyle.AERIAL_WITH_LABELS}/`,
|
||||||
|
)
|
||||||
|
? 'AerialWithLabels'
|
||||||
|
: 'Aerial';
|
||||||
|
return {
|
||||||
|
...endpoint,
|
||||||
|
options: Object.freeze({ ...endpoint.options, mapStyle }),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
t.mock.method(Cesium.IonImageryProvider, 'fromAssetId', () =>
|
||||||
|
assert.fail('Bing must not inherit the local HTTP page protocol'),
|
||||||
|
);
|
||||||
|
t.mock.method(
|
||||||
|
Cesium.BingMapsImageryProvider,
|
||||||
|
'fromUrl',
|
||||||
|
async (url, options) => {
|
||||||
|
bingCalls.push({ url, options });
|
||||||
|
const provider = {
|
||||||
|
errorEvent: new Cesium.Event(),
|
||||||
|
getTileCredits(x, y, level) {
|
||||||
|
assert.equal(this, provider);
|
||||||
|
perTileCalls.push([x, y, level]);
|
||||||
|
return level === 0 ? undefined : [bingCredit];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return provider;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
for (const style of [
|
||||||
|
Cesium.IonWorldImageryStyle.AERIAL,
|
||||||
|
Cesium.IonWorldImageryStyle.AERIAL_WITH_LABELS,
|
||||||
|
]) {
|
||||||
|
const provider = await createIonImagery(style, ' own-ion-token ');
|
||||||
|
const credits = provider.getTileCredits(3, 4, 5);
|
||||||
|
assert.deepEqual(
|
||||||
|
credits.map(({ html, showOnScreen }) => ({ html, showOnScreen })),
|
||||||
|
[
|
||||||
|
{ html: 'Ion required credit', showOnScreen: true },
|
||||||
|
{ html: 'Ion optional credit', showOnScreen: false },
|
||||||
|
{ html: 'Bing tile credit', showOnScreen: true },
|
||||||
|
],
|
||||||
|
);
|
||||||
|
assert.equal(credits[2], bingCredit);
|
||||||
|
assert.equal(provider.getTileCredits(0, 0, 0).length, 2);
|
||||||
|
assert.ok(provider.errorEvent instanceof Cesium.Event);
|
||||||
|
assert.equal(metadataCalls.at(-1).token, 'own-ion-token');
|
||||||
|
const requested = new URL(metadataCalls.at(-1).url);
|
||||||
|
assert.equal(requested.origin, 'https://api.cesium.com');
|
||||||
|
assert.equal(requested.pathname, `/v1/assets/${style}/endpoint`);
|
||||||
|
}
|
||||||
|
assert.deepEqual(perTileCalls, [
|
||||||
|
[3, 4, 5],
|
||||||
|
[0, 0, 0],
|
||||||
|
[3, 4, 5],
|
||||||
|
[0, 0, 0],
|
||||||
|
]);
|
||||||
|
assert.equal(bingCalls.length, 2);
|
||||||
|
for (const [index, { url, options }] of bingCalls.entries()) {
|
||||||
|
assert.equal(url, 'https://dev.virtualearth.net');
|
||||||
|
assert.deepEqual(options, {
|
||||||
|
key: 'ion-issued-bing-fixture',
|
||||||
|
mapStyle: index === 0 ? 'Aerial' : 'AerialWithLabels',
|
||||||
|
tileProtocol: 'https',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
assert.equal(endpoint.options.tileProtocol, 'http');
|
||||||
|
assert.equal(Cesium.Ion.defaultAccessToken, defaultToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Bing ion metadata and construction failures propagate without another source', async (t) => {
|
||||||
|
const style = Cesium.IonWorldImageryStyle.AERIAL;
|
||||||
|
const metadataError = new Error('ion metadata fixture failure');
|
||||||
|
const providerError = new Error('Bing construction fixture failure');
|
||||||
|
let metadata = () => {
|
||||||
|
throw metadataError;
|
||||||
|
};
|
||||||
|
let bingCalls = 0;
|
||||||
|
t.mock.method(Cesium.Resource.prototype, 'fetchJson', async () => metadata());
|
||||||
|
t.mock.method(Cesium.BingMapsImageryProvider, 'fromUrl', async () => {
|
||||||
|
bingCalls += 1;
|
||||||
|
throw providerError;
|
||||||
|
});
|
||||||
|
t.mock.method(Cesium.IonImageryProvider, 'fromAssetId', () =>
|
||||||
|
assert.fail('Bing failures must not silently use an alternate provider'),
|
||||||
|
);
|
||||||
|
await assert.rejects(createIonImagery(style, 'test-token'), metadataError);
|
||||||
|
for (const endpoint of [
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
type: 'TERRAIN',
|
||||||
|
externalType: 'BING',
|
||||||
|
options: { url: 'https://example.test' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'IMAGERY',
|
||||||
|
externalType: 'OTHER',
|
||||||
|
options: { url: 'https://example.test' },
|
||||||
|
},
|
||||||
|
{ type: 'IMAGERY', externalType: 'BING', options: {} },
|
||||||
|
]) {
|
||||||
|
metadata = () => endpoint;
|
||||||
|
await assert.rejects(
|
||||||
|
createIonImagery(style, 'test-token'),
|
||||||
|
/Bing imagery endpoint/,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert.equal(bingCalls, 0);
|
||||||
|
metadata = () => ({
|
||||||
|
type: 'IMAGERY',
|
||||||
|
externalType: 'BING',
|
||||||
|
options: { url: 'https://dev.virtualearth.net', key: 'bing-fixture' },
|
||||||
|
});
|
||||||
|
await assert.rejects(createIonImagery(style, 'test-token'), providerError);
|
||||||
|
assert.equal(bingCalls, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('other ion imagery assets keep the standard SDK factory', async (t) => {
|
||||||
|
const expected = {};
|
||||||
|
const calls = [];
|
||||||
|
t.mock.method(Cesium.Resource.prototype, 'fetchJson', () =>
|
||||||
|
assert.fail('generic assets must use the SDK ion factory'),
|
||||||
|
);
|
||||||
|
t.mock.method(Cesium.IonImageryProvider, 'fromAssetId', async (...args) => {
|
||||||
|
calls.push(args);
|
||||||
|
return expected;
|
||||||
|
});
|
||||||
|
assert.equal(await createIonImagery(12345, ' custom-token '), expected);
|
||||||
|
assert.deepEqual(calls, [[12345, { accessToken: 'custom-token' }]]);
|
||||||
|
});
|
||||||
|
|
||||||
test('cancellation after ion metadata prevents terrain construction', async () => {
|
test('cancellation after ion metadata prevents terrain construction', async () => {
|
||||||
const originalResource = Cesium.IonResource.fromAssetId;
|
const originalResource = Cesium.IonResource.fromAssetId;
|
||||||
const originalTerrain = Cesium.CesiumTerrainProvider.fromUrl;
|
const originalTerrain = Cesium.CesiumTerrainProvider.fromUrl;
|
||||||
|
|||||||
Reference in new issue
Block a user