Skip to content

Commit 885c3ec

Browse files
Merge pull request #13001 from CesiumGS/azure-2d-tiles
Add Azure 2D imagery provider
2 parents 673bd6a + 08bf897 commit 885c3ec

File tree

15 files changed

+146
-60
lines changed

15 files changed

+146
-60
lines changed

Apps/Sandcastle/gallery/development/Azure 2D Tiles.html renamed to Apps/Sandcastle/gallery/Azure 2D Tiles.html

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,7 @@
2424
window.startup = async function (Cesium) {
2525
"use strict";
2626
//Sandcastle_Begin
27-
Cesium.Ion.defaultServer = "https://api.ion-staging.cesium.com";
28-
Cesium.Ion.defaultAccessToken = "";
29-
30-
const assetId = 1683;
27+
const assetId = 3891169;
3128

3229
const azure = Cesium.ImageryLayer.fromProviderAsync(
3330
Cesium.IonImageryProvider.fromAssetId(assetId),

Apps/Sandcastle/gallery/Imagery Assets available from ion.html

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,9 @@
4545
{ label: "Google Maps 2D Roadmap", assetId: 3830184 },
4646
{ label: "Google Maps 2D Satellite", assetId: 3830182 },
4747
{ label: "Google Maps 2D Satellite with Labels", assetId: 3830183 },
48-
{ label: "Bing Maps Aerial", assetId: 2 },
49-
{ label: "Bing Maps Aerial with Labels", assetId: 3 },
50-
{ label: "Bing Maps Road", assetId: 4 },
51-
{ label: "Bing Maps Labels Only", assetId: 2411391 },
48+
{ label: "Azure Maps Aerial", assetId: 3891168 },
49+
{ label: "Azure Maps Roads", assetId: 3891169 },
50+
{ label: "Azure Maps Labels Only", assetId: 3891170 },
5251
{ label: "Sentinel-2", assetId: 3954 },
5352
];
5453

Apps/Sandcastle/gallery/Imagery Layers.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@
3030
"use strict";
3131
//Sandcastle_Begin
3232
const viewer = new Cesium.Viewer("cesiumContainer", {
33-
baseLayer: Cesium.ImageryLayer.fromWorldImagery({
34-
style: Cesium.IonWorldImageryStyle.AERIAL_WITH_LABELS,
35-
}),
33+
baseLayer: Cesium.ImageryLayer.fromProviderAsync(
34+
Cesium.IonImageryProvider.fromAssetId(3830183),
35+
),
3636
baseLayerPicker: false,
3737
});
3838
const layers = viewer.scene.imageryLayers;

packages/engine/Source/Scene/Azure2DImageryProvider.js

Lines changed: 96 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Check from "../Core/Check.js";
2+
import Frozen from "../Core/Frozen.js";
23
import Credit from "../Core/Credit.js";
34
import defined from "../Core/defined.js";
45
import Resource from "../Core/Resource.js";
@@ -29,7 +30,6 @@ const trailingSlashRegex = /\/$/;
2930
*
3031
* @alias Azure2DImageryProvider
3132
* @constructor
32-
* @private
3333
* @param {Azure2DImageryProvider.ConstructorOptions} options Object describing initialization options
3434
*
3535
* @example
@@ -41,16 +41,18 @@ const trailingSlashRegex = /\/$/;
4141
*/
4242
function Azure2DImageryProvider(options) {
4343
options = options ?? {};
44-
const maximumLevel = options.maximumLevel ?? 22;
45-
const minimumLevel = options.minimumLevel ?? 0;
4644
const tilesetId = options.tilesetId ?? "microsoft.imagery";
45+
this._maximumLevel = options.maximumLevel ?? 22;
46+
this._minimumLevel = options.minimumLevel ?? 0;
4747

48-
const subscriptionKey =
48+
this._subscriptionKey =
4949
options.subscriptionKey ?? options["subscription-key"];
5050
//>>includeStart('debug', pragmas.debug);
51-
Check.defined("options.subscriptionKey", subscriptionKey);
51+
Check.defined("options.subscriptionKey", this._subscriptionKey);
5252
//>>includeEnd('debug');
5353

54+
this._tilesetId = options.tilesetId;
55+
5456
const resource =
5557
options.url instanceof IonResource
5658
? options.url
@@ -60,19 +62,23 @@ function Azure2DImageryProvider(options) {
6062
if (!trailingSlashRegex.test(templateUrl)) {
6163
templateUrl += "/";
6264
}
63-
templateUrl += `map/tile`;
6465

65-
resource.url = templateUrl;
66+
const tilesUrl = `${templateUrl}map/tile`;
67+
this._viewportUrl = `${templateUrl}map/attribution`;
68+
69+
resource.url = tilesUrl;
6670

6771
resource.setQueryParameters({
6872
"api-version": "2024-04-01",
6973
tilesetId: tilesetId,
74+
"subscription-key": this._subscriptionKey,
7075
zoom: `{z}`,
7176
x: `{x}`,
7277
y: `{y}`,
73-
"subscription-key": subscriptionKey,
7478
});
7579

80+
this._resource = resource;
81+
7682
let credit;
7783
if (defined(options.credit)) {
7884
credit = options.credit;
@@ -83,8 +89,8 @@ function Azure2DImageryProvider(options) {
8389

8490
const provider = new UrlTemplateImageryProvider({
8591
...options,
86-
maximumLevel,
87-
minimumLevel,
92+
maximumLevel: this._maximumLevel,
93+
minimumLevel: this._minimumLevel,
8894
url: resource,
8995
credit: credit,
9096
});
@@ -93,6 +99,7 @@ function Azure2DImageryProvider(options) {
9399

94100
// This will be defined for ion resources
95101
this._tileCredits = resource.credits;
102+
this._attributionsByLevel = undefined;
96103
}
97104

98105
Object.defineProperties(Azure2DImageryProvider.prototype, {
@@ -263,7 +270,18 @@ Object.defineProperties(Azure2DImageryProvider.prototype, {
263270
* @returns {Credit[]|undefined} The credits to be displayed when the tile is displayed.
264271
*/
265272
Azure2DImageryProvider.prototype.getTileCredits = function (x, y, level) {
266-
return this._imageryProvider.getTileCredits(x, y, level);
273+
const hasAttributions = defined(this._attributionsByLevel);
274+
275+
if (!hasAttributions || !defined(this._tileCredits)) {
276+
return undefined;
277+
}
278+
279+
const innerCredits = this._attributionsByLevel.get(level);
280+
if (!defined(this._tileCredits)) {
281+
return innerCredits;
282+
}
283+
284+
return this._tileCredits.concat(innerCredits);
267285
};
268286

269287
/**
@@ -282,7 +300,21 @@ Azure2DImageryProvider.prototype.requestImage = function (
282300
level,
283301
request,
284302
) {
285-
return this._imageryProvider.requestImage(x, y, level, request);
303+
const promise = this._imageryProvider.requestImage(x, y, level, request);
304+
305+
// If the requestImage call returns undefined, it couldn't be scheduled this frame. Make sure to return undefined so this can be handled upstream.
306+
if (!defined(promise)) {
307+
return undefined;
308+
}
309+
310+
// Asynchronously request and populate _attributionsByLevel if it hasn't been already. We do this here so that the promise can be properly awaited.
311+
if (!defined(this._attributionsByLevel)) {
312+
return Promise.all([promise, this.getViewportCredits()]).then(
313+
(results) => results[0],
314+
);
315+
}
316+
317+
return promise;
286318
};
287319

288320
/**
@@ -306,5 +338,57 @@ Azure2DImageryProvider.prototype.pickFeatures = function (
306338
return undefined;
307339
};
308340

341+
/**
342+
* Get attribution for imagery from Azure Maps to display in the credits
343+
* @private
344+
* @return {Promise<Map<Credit[]>>} The list of attribution sources to display in the credits.
345+
*/
346+
Azure2DImageryProvider.prototype.getViewportCredits = async function () {
347+
const maximumLevel = this._maximumLevel;
348+
349+
const promises = [];
350+
for (let level = 0; level < maximumLevel + 1; level++) {
351+
promises.push(
352+
fetchViewportAttribution(
353+
this._resource,
354+
this._viewportUrl,
355+
this._subscriptionKey,
356+
this._tilesetId,
357+
level,
358+
),
359+
);
360+
}
361+
const results = await Promise.all(promises);
362+
363+
const attributionsByLevel = new Map();
364+
for (let level = 0; level < maximumLevel + 1; level++) {
365+
const credits = [];
366+
const attributions = results[level].join(",");
367+
if (attributions) {
368+
const levelCredits = new Credit(attributions);
369+
credits.push(levelCredits);
370+
}
371+
attributionsByLevel.set(level, credits);
372+
}
373+
374+
this._attributionsByLevel = attributionsByLevel;
375+
376+
return attributionsByLevel;
377+
};
378+
379+
async function fetchViewportAttribution(resource, url, key, tilesetId, level) {
380+
const viewportResource = resource.getDerivedResource({
381+
url,
382+
queryParameters: {
383+
zoom: level,
384+
bounds: "-180,-90,180,90",
385+
},
386+
data: JSON.stringify(Frozen.EMPTY_OBJECT),
387+
});
388+
389+
const viewportJson = await viewportResource.fetchJson();
390+
return viewportJson.copyrights;
391+
}
392+
309393
// Exposed for tests
310394
export default Azure2DImageryProvider;

packages/engine/Source/Scene/Google2DImageryProvider.js

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ function Google2DImageryProvider(options) {
101101
key: encodeURIComponent(options.key),
102102
});
103103

104+
this._resource = resource.clone();
105+
104106
let credit;
105107
if (defined(options.credit)) {
106108
credit = options.credit;
@@ -312,7 +314,7 @@ Object.defineProperties(Google2DImageryProvider.prototype, {
312314
* });
313315
* @example
314316
* // Google 2D roadmap overlay with custom styles
315-
* const googleTileProvider = Cesium.Google2DImageryProvider.fromIonAssetId({
317+
* const googleTilesProvider = Cesium.Google2DImageryProvider.fromIonAssetId({
316318
* assetId: 3830184,
317319
* overlayLayerType: "layerRoadmap",
318320
* styles: [
@@ -403,7 +405,7 @@ Google2DImageryProvider.fromIonAssetId = async function (options) {
403405
* // Google 2D roadmap overlay with custom styles
404406
* Cesium.GoogleMaps.defaultApiKey = "your-api-key";
405407
*
406-
* const googleTileProvider = Cesium.Google2DImageryProvider.fromUrl({
408+
* const googleTilesProvider = Cesium.Google2DImageryProvider.fromUrl({
407409
* overlayLayerType: "layerRoadmap",
408410
* styles: [
409411
* {
@@ -492,7 +494,7 @@ Google2DImageryProvider.prototype.requestImage = function (
492494
}
493495

494496
// Asynchronously request and populate _attributionsByLevel if it hasn't been already. We do this here so that the promise can be properly awaited.
495-
if (promise && !defined(this._attributionsByLevel)) {
497+
if (!defined(this._attributionsByLevel)) {
496498
return Promise.all([promise, this.getViewportCredits()]).then(
497499
(results) => results[0],
498500
);
@@ -533,12 +535,7 @@ Google2DImageryProvider.prototype.getViewportCredits = async function () {
533535
const promises = [];
534536
for (let level = 0; level < maximumLevel + 1; level++) {
535537
promises.push(
536-
fetchViewportAttribution(
537-
this._viewportUrl,
538-
this._key,
539-
this._session,
540-
level,
541-
),
538+
fetchViewportAttribution(this._resource, this._viewportUrl, level),
542539
);
543540
}
544541
const results = await Promise.all(promises);
@@ -559,12 +556,10 @@ Google2DImageryProvider.prototype.getViewportCredits = async function () {
559556
return attributionsByLevel;
560557
};
561558

562-
async function fetchViewportAttribution(url, key, session, level) {
563-
const viewport = await Resource.fetch({
564-
url: url,
559+
async function fetchViewportAttribution(resource, url, level) {
560+
const viewportResource = resource.getDerivedResource({
561+
url,
565562
queryParameters: {
566-
key,
567-
session,
568563
zoom: level,
569564
north: 90,
570565
south: -90,
@@ -573,7 +568,7 @@ async function fetchViewportAttribution(url, key, session, level) {
573568
},
574569
data: JSON.stringify(Frozen.EMPTY_OBJECT),
575570
});
576-
const viewportJson = JSON.parse(viewport);
571+
const viewportJson = await viewportResource.fetchJson();
577572
return viewportJson.copyright;
578573
}
579574

packages/engine/Specs/Scene/Azure2DImageryProviderSpec.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ describe("Scene/Azure2DImageryProvider", function () {
4040
tilesetId: "a-tileset-id",
4141
});
4242

43+
provider._attributionsByLevel = {};
44+
4345
expect(provider.url).toEqual(
44-
"https://atlas.microsoft.com/map/tile?api-version=2024-04-01&tilesetId=a-tileset-id&zoom={z}&x={x}&y={y}&subscription-key=test-subscriptionKey",
46+
"https://atlas.microsoft.com/map/tile?api-version=2024-04-01&tilesetId=a-tileset-id&subscription-key=test-subscriptionKey&zoom={z}&x={x}&y={y}",
4547
);
4648
expect(provider.tileWidth).toEqual(256);
4749
expect(provider.tileHeight).toEqual(256);
@@ -74,6 +76,8 @@ describe("Scene/Azure2DImageryProvider", function () {
7476
rectangle: rectangle,
7577
});
7678

79+
provider._attributionsByLevel = {};
80+
7781
expect(provider.tileWidth).toEqual(256);
7882
expect(provider.tileHeight).toEqual(256);
7983
expect(provider.maximumLevel).toBe(22);
@@ -133,6 +137,7 @@ describe("Scene/Azure2DImageryProvider", function () {
133137
subscriptionKey: "test-subscriptionKey",
134138
tilesetId: "a-tileset-id",
135139
});
140+
provider._attributionsByLevel = {};
136141

137142
const layer = new ImageryLayer(provider);
138143

packages/sandcastle/gallery/azure-2d-tiles/main.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import * as Cesium from "cesium";
22

3-
Cesium.Ion.defaultServer = "https://api.ion-staging.cesium.com";
4-
Cesium.Ion.defaultAccessToken = "";
5-
6-
const assetId = 1683;
3+
const assetId = 3891169;
74

85
const azure = Cesium.ImageryLayer.fromProviderAsync(
96
Cesium.IonImageryProvider.fromAssetId(assetId),
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
legacyId: Azure 2D Tiles.html
22
title: Azure 2D Tiles
33
description: Global imagery data from Azure Maps.
4-
development: true
54
labels:
65
- Imagery
7-
- Development
86
thumbnail: thumbnail.jpg

packages/sandcastle/gallery/imagery-assets-available-from-ion/main.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@ const dropdownOptions = [
2222
{ label: "Google Maps 2D Roadmap", assetId: 3830184 },
2323
{ label: "Google Maps 2D Satellite", assetId: 3830182 },
2424
{ label: "Google Maps 2D Satellite with Labels", assetId: 3830183 },
25-
{ label: "Bing Maps Aerial", assetId: 2 },
26-
{ label: "Bing Maps Aerial with Labels", assetId: 3 },
27-
{ label: "Bing Maps Road", assetId: 4 },
28-
{ label: "Bing Maps Labels Only", assetId: 2411391 },
25+
{ label: "Azure Maps Aerial", assetId: 3891168 },
26+
{ label: "Azure Maps Roads", assetId: 3891169 },
27+
{ label: "Azure Maps Labels Only", assetId: 3891170 },
2928
{ label: "Sentinel-2", assetId: 3954 },
3029
];
3130

0 commit comments

Comments
 (0)