Skip to content

Commit 132faa3

Browse files
committed
Merge pull request #7808. Merge branch 'on-demand-bing-maps'
2 parents 3a4d4e8 + 255081b commit 132faa3

9 files changed

+233
-18
lines changed

CHANGES.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
Change Log
22
==========
3+
### 1.58 - 2019-06-01
4+
5+
##### Additions :tada:
6+
* Added support for new `BingMapsStyle` values `ROAD_ON_DEMAND` and `AERIAL_WITH_LABELS_ON_DEMAND`. The older versions of these, `ROAD` and `AERIAL_WITH_LABELS`, have been deprecated by Bing. [#7808] (https://github.com/AnalyticalGraphicsInc/cesium/pull/7808)
37

48
### 1.57 - 2019-05-01
59

CONTRIBUTORS.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,13 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu
5050
* [Omar Shehata](https://github.com/OmarShehata)
5151
* [Matt Petry](https://github.com/MattPetry)
5252
* [Michael Squires](https://github.com/mksquires)
53-
* [NICTA](http://www.nicta.com.au/)
53+
* [NICTA/CSIRO's Data61](https://www.data61.csiro.au/)
5454
* [Kevin Ring](https://github.com/kring)
5555
* [Keith Grochow](https://github.com/kgrochow)
5656
* [Chloe Chen](https://github.com/chloeleichen)
5757
* [Arthur Street](https://github.com/RacingTadpole)
5858
* [Alex Gilleran](https://github.com/AlexGilleran)
59+
* [Emma Krantz](https://github.com/KeyboardSounds)
5960
* [EU Edge](http://euedge.com/)
6061
* [Ákos Maróy](https://github.com/akosmaroy)
6162
* [Raytheon Intelligence and Information Systems](http://www.raytheon.com/)

Source/Core/Resource.js

+4
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,10 @@ define([
944944
window.URL.revokeObjectURL(generatedBlobResource.url);
945945
}
946946

947+
// If the blob load succeeded but the image decode failed, provide access to the blob on the error object because it may provide useful insight.
948+
// In particular, BingMapsImageryProvider uses this to detect the zero-length "image" that some map styles return when a real tile is not available.
949+
error.blob = generatedBlob;
950+
947951
return when.reject(error);
948952
});
949953
};

Source/Scene/BingMapsImageryProvider.js

+41-15
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ define([
1818
'../ThirdParty/when',
1919
'./BingMapsStyle',
2020
'./DiscardMissingTileImagePolicy',
21+
'./DiscardEmptyTileImagePolicy',
2122
'./ImageryProvider'
2223
], function(
2324
BingMapsApi,
@@ -39,6 +40,7 @@ define([
3940
when,
4041
BingMapsStyle,
4142
DiscardMissingTileImagePolicy,
43+
DiscardEmptyTilePolicy,
4244
ImageryProvider) {
4345
'use strict';
4446

@@ -61,15 +63,16 @@ define([
6163
* for information on the supported cultures.
6264
* @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used.
6365
* @param {TileDiscardPolicy} [options.tileDiscardPolicy] The policy that determines if a tile
64-
* is invalid and should be discarded. If this value is not specified, a default
65-
* {@link DiscardMissingTileImagePolicy} is used which requests
66-
* tile 0,0 at the maximum tile level and checks pixels (0,0), (120,140), (130,160),
67-
* (200,50), and (200,200). If all of these pixels are transparent, the discard check is
68-
* disabled and no tiles are discarded. If any of them have a non-transparent color, any
69-
* tile that has the same values in these pixel locations is discarded. The end result of
70-
* these defaults should be correct tile discarding for a standard Bing Maps server. To ensure
71-
* that no tiles are discarded, construct and pass a {@link NeverTileDiscardPolicy} for this
72-
* parameter.
66+
* is invalid and should be discarded. The default value will depend on the map style. If
67+
* `BingMapsStyle.AERIAL_WITH_LABELS_ON_DEMAND` or `BingMapsStyle.ROADS_ON_DEMAND` is used, then a
68+
* {@link DiscardEmptyTileImagePolicy} will be used to handle the Bing Maps API sending no content instead of
69+
* a missing tile image, a behaviour specific to that imagery set. In all other cases, a default
70+
* {@link DiscardMissingTileImagePolicy} is used which requests tile 0,0 at the maximum tile level and checks
71+
* pixels (0,0), (120,140), (130,160), (200,50), and (200,200). If all of these pixels are transparent, the
72+
* discard check is disabled and no tiles are discarded. If any of them have a non-transparent color, any
73+
* tile that has the same values in these pixel locations is discarded. The end result of these defaults
74+
* should be correct tile discarding for a standard Bing Maps server. To ensure that no tiles are discarded,
75+
* construct and pass a {@link NeverTileDiscardPolicy} for this parameter.
7376
*
7477
* @see ArcGisMapServerImageryProvider
7578
* @see GoogleEarthEnterpriseMapsProvider
@@ -171,11 +174,18 @@ define([
171174

172175
// Install the default tile discard policy if none has been supplied.
173176
if (!defined(that._tileDiscardPolicy)) {
174-
that._tileDiscardPolicy = new DiscardMissingTileImagePolicy({
175-
missingImageUrl : buildImageResource(that, 0, 0, that._maximumLevel).url,
176-
pixelsToCheck : [new Cartesian2(0, 0), new Cartesian2(120, 140), new Cartesian2(130, 160), new Cartesian2(200, 50), new Cartesian2(200, 200)],
177-
disableCheckIfAllPixelsAreTransparent : true
178-
});
177+
// Our default depends on which map style we're using.
178+
if (that._mapStyle === BingMapsStyle.AERIAL_WITH_LABELS_ON_DEMAND
179+
|| that._mapStyle === BingMapsStyle.ROAD_ON_DEMAND) {
180+
// this map style uses a different API, which returns a tile with no data instead of a placeholder image
181+
that._tileDiscardPolicy = new DiscardEmptyTilePolicy();
182+
} else {
183+
that._tileDiscardPolicy = new DiscardMissingTileImagePolicy({
184+
missingImageUrl : buildImageResource(that, 0, 0, that._maximumLevel).url,
185+
pixelsToCheck : [new Cartesian2(0, 0), new Cartesian2(120, 140), new Cartesian2(130, 160), new Cartesian2(200, 50), new Cartesian2(200, 200)],
186+
disableCheckIfAllPixelsAreTransparent : true
187+
});
188+
}
179189
}
180190

181191
var attributionList = that._attributionList = resource.imageryProviders;
@@ -533,7 +543,23 @@ define([
533543
}
534544
//>>includeEnd('debug');
535545

536-
return ImageryProvider.loadImage(this, buildImageResource(this, x, y, level, request));
546+
var promise = ImageryProvider.loadImage(this, buildImageResource(this, x, y, level, request));
547+
548+
if (defined(promise)) {
549+
return promise.otherwise(function(error) {
550+
551+
// One possible cause of an error here is that the image we tried to load was empty. This isn't actually
552+
// a problem. In some imagery sets (eg. `BingMapsStyle.AERIAL_WITH_LABELS_ON_DEMAND`), an empty image is
553+
// returned rather than a blank "This Image is Missing" placeholder image. In this case, we supress the
554+
// error.
555+
if (defined(error.blob) && error.blob.size === 0) {
556+
return DiscardEmptyTilePolicy.EMPTY_IMAGE;
557+
}
558+
return when.reject(error);
559+
});
560+
}
561+
562+
return undefined;
537563
};
538564

539565
/**

Source/Scene/BingMapsStyle.js

+20
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,37 @@ define([
2525
*
2626
* @type {String}
2727
* @constant
28+
* @deprecated See https://github.com/AnalyticalGraphicsInc/cesium/issues/7128.
29+
* Use `BingMapsStyle.AERIAL_WITH_LABELS_ON_DEMAND` instead
2830
*/
2931
AERIAL_WITH_LABELS : 'AerialWithLabels',
3032

33+
/**
34+
* Aerial imagery with a road overlay.
35+
*
36+
* @type {String}
37+
* @constant
38+
*/
39+
AERIAL_WITH_LABELS_ON_DEMAND : 'AerialWithLabelsOnDemand',
40+
3141
/**
3242
* Roads without additional imagery.
3343
*
3444
* @type {String}
3545
* @constant
46+
* @deprecated See https://github.com/AnalyticalGraphicsInc/cesium/issues/7128.
47+
* Use `BingMapsStyle.ROAD_ON_DEMAND` instead
3648
*/
3749
ROAD : 'Road',
3850

51+
/**
52+
* Roads without additional imagery.
53+
*
54+
* @type {String}
55+
* @constant
56+
*/
57+
ROAD_ON_DEMAND : 'RoadOnDemand',
58+
3959
/**
4060
* A dark version of the road maps.
4161
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
define([
2+
'../Core/defined',
3+
'../Core/defaultValue'
4+
], function(
5+
defined,
6+
defaultValue) {
7+
'use strict';
8+
9+
/**
10+
* A policy for discarding tile images that contain no data (and so aren't actually images).
11+
*
12+
* @alias DiscardEmptyTileImagePolicy
13+
* @constructor
14+
*
15+
* @see DiscardMissingTileImagePolicy
16+
*/
17+
function DiscardEmptyTileImagePolicy(options) {
18+
}
19+
20+
/**
21+
* Determines if the discard policy is ready to process images.
22+
* @returns {Boolean} True if the discard policy is ready to process images; otherwise, false.
23+
*/
24+
DiscardEmptyTileImagePolicy.prototype.isReady = function() {
25+
return true;
26+
};
27+
28+
/**
29+
* Given a tile image, decide whether to discard that image.
30+
*
31+
* @param {Image} image An image to test.
32+
* @returns {Boolean} True if the image should be discarded; otherwise, false.
33+
*/
34+
DiscardEmptyTileImagePolicy.prototype.shouldDiscardImage = function(image) {
35+
return DiscardEmptyTileImagePolicy.EMPTY_IMAGE === image;
36+
};
37+
38+
/**
39+
* Default value for representing an empty image.
40+
*/
41+
DiscardEmptyTileImagePolicy.EMPTY_IMAGE = {};
42+
43+
return DiscardEmptyTileImagePolicy;
44+
});

Specs/Core/ResourceSpec.js

+21
Original file line numberDiff line numberDiff line change
@@ -1310,6 +1310,27 @@ defineSuite([
13101310
expect(error).toBeInstanceOf(RequestErrorEvent);
13111311
});
13121312
});
1313+
1314+
it('rejects the promise with extra error information when image errors and options.preferBlob is true', function() {
1315+
if (!supportsImageBitmapOptions) {
1316+
return;
1317+
}
1318+
1319+
// Force the fetching of a bad blob that is not an image to trigger the error
1320+
spyOn(Resource.prototype, 'fetch').and.returnValue(when.resolve(new Blob([new Uint8Array([])], { type: 'text/plain' })));
1321+
1322+
return Resource.fetchImage({
1323+
url: 'http://example.invalid/testuri.png',
1324+
preferImageBitmap: true,
1325+
preferBlob: true
1326+
})
1327+
.then(function() {
1328+
fail('expected promise to reject');
1329+
})
1330+
.otherwise(function(error) {
1331+
expect(error.blob).toBeInstanceOf(Blob);
1332+
});
1333+
});
13131334
});
13141335

13151336
describe('fetchImage without ImageBitmap', function() {

Specs/Scene/BingMapsImageryProviderSpec.js

+43-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ defineSuite([
1414
'Scene/ImageryProvider',
1515
'Scene/ImageryState',
1616
'Specs/pollToPromise',
17-
'ThirdParty/Uri'
17+
'ThirdParty/Uri',
18+
'ThirdParty/when',
19+
'Scene/DiscardEmptyTileImagePolicy'
1820
], function(
1921
BingMapsImageryProvider,
2022
appendForwardSlash,
@@ -31,7 +33,9 @@ defineSuite([
3133
ImageryProvider,
3234
ImageryState,
3335
pollToPromise,
34-
Uri) {
36+
Uri,
37+
when,
38+
DiscardEmptyTileImagePolicy) {
3539
'use strict';
3640

3741
var supportsImageBitmapOptions;
@@ -498,4 +502,41 @@ defineSuite([
498502
});
499503
});
500504
});
505+
506+
it('correctly handles empty tiles', function() {
507+
var url = 'http://foo.bar.invalid';
508+
var mapStyle = BingMapsStyle.ROAD_ON_DEMAND;
509+
510+
installFakeMetadataRequest(url, mapStyle);
511+
512+
var provider = new BingMapsImageryProvider({
513+
url : url,
514+
mapStyle : mapStyle
515+
});
516+
517+
var layer = new ImageryLayer(provider);
518+
519+
// Fake ImageryProvider.loadImage's expected output in the case of an empty tile
520+
var e = new Error();
521+
e.blob = {size: 0};
522+
var errorPromise = when.reject(e);
523+
524+
spyOn(ImageryProvider, 'loadImage').and.returnValue(errorPromise);
525+
526+
return pollToPromise(function() {
527+
return provider.ready;
528+
}).then(function() {
529+
var imagery = new Imagery(layer, 0, 0, 0);
530+
imagery.addReference();
531+
layer._requestImagery(imagery);
532+
RequestScheduler.update();
533+
534+
return pollToPromise(function() {
535+
return imagery.state === ImageryState.RECEIVED;
536+
}).then(function() {
537+
expect(imagery.image).toBe(DiscardEmptyTileImagePolicy.EMPTY_IMAGE);
538+
imagery.releaseReference();
539+
});
540+
});
541+
});
501542
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
defineSuite([
2+
'Scene/DiscardEmptyTileImagePolicy',
3+
'Core/Resource',
4+
'Specs/pollToPromise',
5+
'ThirdParty/when'
6+
], function(
7+
DiscardEmptyTileImagePolicy,
8+
Resource,
9+
pollToPromise,
10+
when) {
11+
'use strict';
12+
13+
afterEach(function() {
14+
Resource._Implementations.createImage = Resource._DefaultImplementations.createImage;
15+
Resource._Implementations.loadWithXhr = Resource._DefaultImplementations.loadWithXhr;
16+
});
17+
18+
describe('shouldDiscardImage', function() {
19+
it('does not discard a non-empty image', function() {
20+
var promises = [];
21+
promises.push(Resource.fetchImage('Data/Images/Green4x4.png'));
22+
23+
var policy = new DiscardEmptyTileImagePolicy();
24+
25+
promises.push(pollToPromise(function() {
26+
return policy.isReady();
27+
}));
28+
29+
return when.all(promises, function(results) {
30+
var greenImage = results[0];
31+
32+
expect(policy.shouldDiscardImage(greenImage)).toEqual(false);
33+
});
34+
});
35+
36+
it('discards an empty image', function() {
37+
var promises = [];
38+
promises.push(when.resolve(DiscardEmptyTileImagePolicy.EMPTY_IMAGE));
39+
40+
var policy = new DiscardEmptyTileImagePolicy();
41+
42+
promises.push(pollToPromise(function() {
43+
return policy.isReady();
44+
}));
45+
46+
return when.all(promises, function(results) {
47+
var emptyImage = results[0];
48+
49+
expect(policy.shouldDiscardImage(emptyImage)).toEqual(true);
50+
});
51+
});
52+
53+
});
54+
});

0 commit comments

Comments
 (0)