From 2b33321690e0dba8eff41662a3b8cd0f1c2f31fe Mon Sep 17 00:00:00 2001 From: Aristeidis Bampakos Date: Mon, 1 Apr 2019 18:54:06 +0200 Subject: [PATCH 1/5] Create new imagery provider for MapBox styles --- Source/Scene/MapboxStyleProvider.js | 368 ++++++++++++++++++ .../createDefaultImageryProviderViewModels.js | 16 +- 2 files changed, 376 insertions(+), 8 deletions(-) create mode 100644 Source/Scene/MapboxStyleProvider.js diff --git a/Source/Scene/MapboxStyleProvider.js b/Source/Scene/MapboxStyleProvider.js new file mode 100644 index 000000000000..ad5fc7126cc3 --- /dev/null +++ b/Source/Scene/MapboxStyleProvider.js @@ -0,0 +1,368 @@ +define([ + '../Core/Credit', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/MapboxApi', + '../Core/Resource', + './UrlTemplateImageryProvider' +], function( + Credit, + defaultValue, + defined, + defineProperties, + DeveloperError, + MapboxApi, + Resource, + UrlTemplateImageryProvider) { +'use strict'; + +var trailingSlashRegex = /\/$/; +var defaultCredit = new Credit('© Mapbox © OpenStreetMap Improve this map'); + +/** + * Provides tiled imagery hosted by Mapbox. + * + * @alias MapboxStyleProvider + * @constructor + * + * @param {Object} [options] Object with the following properties: + * @param {String} [options.url='https://api.mapbox.com/styles/v1/'] The Mapbox server url. + * @param {String} [options.username='mapbox'] The username of the account to which the map belongs. + * @param {String} options.styleId The Mapbox Style ID. + * @param {String} [options.accessToken] The public access token for the imagery. + * @param {Number} [options.tilesize=512] The size of the image tile. + * @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used. + * @param {Number} [options.minimumLevel=0] The minimum level-of-detail supported by the imagery provider. Take care when specifying + * this that the number of tiles at the minimum level is small, such as four or less. A larger number is likely + * to result in rendering problems. + * @param {Number} [options.maximumLevel] The maximum level-of-detail supported by the imagery provider, or undefined if there is no limit. + * @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the image. + * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas. + * + * + * @example + * // Mapbox tile provider + * var mapbox = new Cesium.MapboxStyleProvider({ + * styleId: 'streets-v11', + * accessToken: 'thisIsMyAccessToken' + * }); + * + * @see {@link https://docs.mapbox.com/api/maps/#styles} + * @see {@link https://docs.mapbox.com/api/#access-tokens-and-token-scopes} + */ +function MapboxStyleProvider(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var styleId = options.styleId; + //>>includeStart('debug', pragmas.debug); + if (!defined(styleId)) { + throw new DeveloperError('options.styleId is required.'); + } + //>>includeEnd('debug'); + + var url = options.url; + if (!defined(url)) { + url = 'https://api.mapbox.com/styles/v1/'; + } + this._url = url; + + var resource = Resource.createIfNeeded(url); + + var accessToken = MapboxApi.getAccessToken(options.accessToken); + this._styleId = styleId; + this._accessToken = accessToken; + + this._accessTokenErrorCredit = Credit.clone(MapboxApi.getErrorCredit(options.accessToken)); + + var tilesize = defaultValue(options.tilesize, 512); + this._tilesize = tilesize; + + var username = defaultValue(options.username, 'mapbox'); + this._username = username; + + var templateUrl = resource.getUrlComponent(); + if (!trailingSlashRegex.test(templateUrl)) { + templateUrl += '/'; + } + templateUrl += this._username + '/' + styleId + '/tiles/' + this._tilesize + '/{z}/{x}/{y}'; + resource.url = templateUrl; + + resource.setQueryParameters({ + access_token: accessToken + }); + + var credit; + if (defined(options.credit)) { + credit = options.credit; + if (typeof credit === 'string') { + credit = new Credit(credit); + } + } else { + credit = defaultCredit; + } + + this._resource = resource; + this._imageryProvider = new UrlTemplateImageryProvider({ + url: resource, + credit: credit, + ellipsoid: options.ellipsoid, + minimumLevel: options.minimumLevel, + maximumLevel: options.maximumLevel, + rectangle: options.rectangle + }); +} + +defineProperties(MapboxStyleProvider.prototype, { + /** + * Gets the URL of the Mapbox server. + * @memberof MapboxStyleProvider.prototype + * @type {String} + * @readonly + */ + url : { + get : function() { + return this._url; + } + }, + + /** + * Gets a value indicating whether or not the provider is ready for use. + * @memberof MapboxStyleProvider.prototype + * @type {Boolean} + * @readonly + */ + ready : { + get : function() { + return this._imageryProvider.ready; + } + }, + + /** + * Gets a promise that resolves to true when the provider is ready for use. + * @memberof MapboxStyleProvider.prototype + * @type {Promise.} + * @readonly + */ + readyPromise : { + get : function() { + return this._imageryProvider.readyPromise; + } + }, + + /** + * Gets the rectangle, in radians, of the imagery provided by the instance. This function should + * not be called before {@link MapboxStyleProvider#ready} returns true. + * @memberof MapboxStyleProvider.prototype + * @type {Rectangle} + * @readonly + */ + rectangle: { + get : function() { + return this._imageryProvider.rectangle; + } + }, + + /** + * Gets the width of each tile, in pixels. This function should + * not be called before {@link MapboxStyleProvider#ready} returns true. + * @memberof MapboxStyleProvider.prototype + * @type {Number} + * @readonly + */ + tileWidth : { + get : function() { + return this._imageryProvider.tileWidth; + } + }, + + /** + * Gets the height of each tile, in pixels. This function should + * not be called before {@link MapboxStyleProvider#ready} returns true. + * @memberof MapboxStyleProvider.prototype + * @type {Number} + * @readonly + */ + tileHeight : { + get : function() { + return this._imageryProvider.tileHeight; + } + }, + + /** + * Gets the maximum level-of-detail that can be requested. This function should + * not be called before {@link MapboxStyleProvider#ready} returns true. + * @memberof MapboxStyleProvider.prototype + * @type {Number} + * @readonly + */ + maximumLevel : { + get : function() { + return this._imageryProvider.maximumLevel; + } + }, + + /** + * Gets the minimum level-of-detail that can be requested. This function should + * not be called before {@link MapboxStyleProvider#ready} returns true. Generally, + * a minimum level should only be used when the rectangle of the imagery is small + * enough that the number of tiles at the minimum level is small. An imagery + * provider with more than a few tiles at the minimum level will lead to + * rendering problems. + * @memberof MapboxStyleProvider.prototype + * @type {Number} + * @readonly + */ + minimumLevel : { + get : function() { + return this._imageryProvider.minimumLevel; + } + }, + + /** + * Gets the tiling scheme used by the provider. This function should + * not be called before {@link MapboxStyleProvider#ready} returns true. + * @memberof MapboxStyleProvider.prototype + * @type {TilingScheme} + * @readonly + */ + tilingScheme : { + get : function() { + return this._imageryProvider.tilingScheme; + } + }, + + /** + * Gets the tile discard policy. If not undefined, the discard policy is responsible + * for filtering out "missing" tiles via its shouldDiscardImage function. If this function + * returns undefined, no tiles are filtered. This function should + * not be called before {@link MapboxStyleProvider#ready} returns true. + * @memberof MapboxStyleProvider.prototype + * @type {TileDiscardPolicy} + * @readonly + */ + tileDiscardPolicy : { + get : function() { + return this._imageryProvider.tileDiscardPolicy; + } + }, + + /** + * Gets an event that is raised when the imagery provider encounters an asynchronous error.. By subscribing + * to the event, you will be notified of the error and can potentially recover from it. Event listeners + * are passed an instance of {@link TileProviderError}. + * @memberof MapboxStyleProvider.prototype + * @type {Event} + * @readonly + */ + errorEvent : { + get : function() { + return this._imageryProvider.errorEvent; + } + }, + + /** + * Gets the credit to display when this imagery provider is active. Typically this is used to credit + * the source of the imagery. This function should + * not be called before {@link MapboxStyleProvider#ready} returns true. + * @memberof MapboxStyleProvider.prototype + * @type {Credit} + * @readonly + */ + credit : { + get : function() { + return this._imageryProvider.credit; + } + }, + + /** + * Gets the proxy used by this provider. + * @memberof MapboxStyleProvider.prototype + * @type {Proxy} + * @readonly + */ + proxy : { + get : function() { + return this._imageryProvider.proxy; + } + }, + + /** + * Gets a value indicating whether or not the images provided by this imagery provider + * include an alpha channel. If this property is false, an alpha channel, if present, will + * be ignored. If this property is true, any images without an alpha channel will be treated + * as if their alpha is 1.0 everywhere. When this property is false, memory usage + * and texture upload time are reduced. + * @memberof MapboxStyleProvider.prototype + * @type {Boolean} + * @readonly + */ + hasAlphaChannel : { + get : function() { + return this._imageryProvider.hasAlphaChannel; + } + } +}); + +/** + * Gets the credits to be displayed when a given tile is displayed. + * + * @param {Number} x The tile X coordinate. + * @param {Number} y The tile Y coordinate. + * @param {Number} level The tile level; + * @returns {Credit[]} The credits to be displayed when the tile is displayed. + * + * @exception {DeveloperError} getTileCredits must not be called before the imagery provider is ready. + */ +MapboxStyleProvider.prototype.getTileCredits = function(x, y, level) { + if (defined(this._accessTokenErrorCredit)) { + return [this._accessTokenErrorCredit]; + } +}; + +/** + * Requests the image for a given tile. This function should + * not be called before {@link MapboxStyleProvider#ready} returns true. + * + * @param {Number} x The tile X coordinate. + * @param {Number} y The tile Y coordinate. + * @param {Number} level The tile level. + * @param {Request} [request] The request object. Intended for internal use only. + * @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or + * undefined if there are too many active requests to the server, and the request + * should be retried later. The resolved image may be either an + * Image or a Canvas DOM object. + * + * @exception {DeveloperError} requestImage must not be called before the imagery provider is ready. + */ +MapboxStyleProvider.prototype.requestImage = function(x, y, level, request) { + return this._imageryProvider.requestImage(x, y, level, request); +}; + +/** + * Asynchronously determines what features, if any, are located at a given longitude and latitude within + * a tile. This function should not be called before {@link MapboxStyleProvider#ready} returns true. + * This function is optional, so it may not exist on all ImageryProviders. + * + * + * @param {Number} x The tile X coordinate. + * @param {Number} y The tile Y coordinate. + * @param {Number} level The tile level. + * @param {Number} longitude The longitude at which to pick features. + * @param {Number} latitude The latitude at which to pick features. + * @return {Promise.|undefined} A promise for the picked features that will resolve when the asynchronous + * picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo} + * instances. The array may be empty if no features are found at the given location. + * It may also be undefined if picking is not supported. + * + * @exception {DeveloperError} pickFeatures must not be called before the imagery provider is ready. + */ +MapboxStyleProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) { + return this._imageryProvider.pickFeatures(x, y, level, longitude, latitude); +}; + +// Exposed for tests +MapboxStyleProvider._defaultCredit = defaultCredit; + +return MapboxStyleProvider; +}); diff --git a/Source/Widgets/BaseLayerPicker/createDefaultImageryProviderViewModels.js b/Source/Widgets/BaseLayerPicker/createDefaultImageryProviderViewModels.js index ff82277b7ce7..6c9ec0ef826b 100644 --- a/Source/Widgets/BaseLayerPicker/createDefaultImageryProviderViewModels.js +++ b/Source/Widgets/BaseLayerPicker/createDefaultImageryProviderViewModels.js @@ -6,7 +6,7 @@ define([ '../../Scene/createTileMapServiceImageryProvider', '../../Scene/createWorldImagery', '../../Scene/IonImageryProvider', - '../../Scene/MapboxImageryProvider', + '../../Scene/MapboxStyleProvider', '../BaseLayerPicker/ProviderViewModel' ], function( buildModuleUrl, @@ -16,7 +16,7 @@ define([ createTileMapServiceImageryProvider, createWorldImagery, IonImageryProvider, - MapboxImageryProvider, + MapboxStyleProvider, ProviderViewModel) { 'use strict'; @@ -67,8 +67,8 @@ define([ iconUrl: buildModuleUrl('Widgets/Images/ImageryProviders/mapboxSatellite.png'), category : 'Other', creationFunction: function() { - return new MapboxImageryProvider({ - mapId: 'mapbox.satellite' + return new MapboxStyleProvider({ + styleId: 'satellite-v9' }); } })); @@ -79,8 +79,8 @@ define([ iconUrl: buildModuleUrl('Widgets/Images/ImageryProviders/mapboxTerrain.png'), category : 'Other', creationFunction: function() { - return new MapboxImageryProvider({ - mapId: 'mapbox.streets' + return new MapboxStyleProvider({ + styleId: 'satellite-streets-v11' }); } })); @@ -91,8 +91,8 @@ define([ iconUrl: buildModuleUrl('Widgets/Images/ImageryProviders/mapboxStreets.png'), category : 'Other', creationFunction: function() { - return new MapboxImageryProvider({ - mapId: 'mapbox.streets-basic' + return new MapboxStyleProvider({ + styleId: 'streets-v11' }); } })); From 37678289379475c8221bd75c23099fcd9127ddbb Mon Sep 17 00:00:00 2001 From: Aristeidis Bampakos Date: Thu, 4 Apr 2019 17:40:22 +0300 Subject: [PATCH 2/5] Add scaleFactor to provider options --- Source/Scene/MapboxStyleProvider.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Source/Scene/MapboxStyleProvider.js b/Source/Scene/MapboxStyleProvider.js index ad5fc7126cc3..f0a1981ff2b8 100644 --- a/Source/Scene/MapboxStyleProvider.js +++ b/Source/Scene/MapboxStyleProvider.js @@ -29,10 +29,11 @@ var defaultCredit = new Credit('© Date: Thu, 4 Apr 2019 19:02:27 +0300 Subject: [PATCH 3/5] Add unit test --- Specs/Scene/MapboxStyleProviderSpec.js | 358 +++++++++++++++++++++++++ 1 file changed, 358 insertions(+) create mode 100644 Specs/Scene/MapboxStyleProviderSpec.js diff --git a/Specs/Scene/MapboxStyleProviderSpec.js b/Specs/Scene/MapboxStyleProviderSpec.js new file mode 100644 index 000000000000..abac21d598bd --- /dev/null +++ b/Specs/Scene/MapboxStyleProviderSpec.js @@ -0,0 +1,358 @@ +defineSuite([ + 'Scene/MapboxStyleProvider', + 'Core/Math', + 'Core/Rectangle', + 'Core/RequestScheduler', + 'Core/Resource', + 'Core/WebMercatorTilingScheme', + 'Scene/Imagery', + 'Scene/ImageryLayer', + 'Scene/ImageryProvider', + 'Scene/ImageryState', + 'Specs/pollToPromise' + ], function( + MapboxStyleProvider, + CesiumMath, + Rectangle, + RequestScheduler, + Resource, + WebMercatorTilingScheme, + Imagery, + ImageryLayer, + ImageryProvider, + ImageryState, + pollToPromise) { + 'use strict'; + + beforeEach(function() { + RequestScheduler.clearForSpecs(); + }); + + afterEach(function() { + Resource._Implementations.createImage = Resource._DefaultImplementations.createImage; + }); + + it('conforms to ImageryProvider interface', function() { + expect(MapboxStyleProvider).toConformToInterface(ImageryProvider); + }); + + it('requires the styleId to be specified', function() { + expect(function() { + return new MapboxStyleProvider({}); + }).toThrowDeveloperError('styleId is required'); + }); + + it('resolves readyPromise', function() { + var provider = new MapboxStyleProvider({ + url : 'made/up/mapbox/server/', + styleId: 'test-id' + }); + + return provider.readyPromise.then(function (result) { + expect(result).toBe(true); + expect(provider.ready).toBe(true); + }); + }); + + it('resolves readyPromise with Resource', function() { + var resource = new Resource({ + url : 'made/up/mapbox/server/' + }); + + var provider = new MapboxStyleProvider({ + url : resource, + styleId: 'test-id' + }); + + return provider.readyPromise.then(function (result) { + expect(result).toBe(true); + expect(provider.ready).toBe(true); + }); + }); + + it('returns valid value for hasAlphaChannel', function() { + var provider = new MapboxStyleProvider({ + url : 'made/up/mapbox/server/', + styleId: 'test-id' + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + expect(typeof provider.hasAlphaChannel).toBe('boolean'); + }); + }); + + it('supports a slash at the end of the URL', function() { + var provider = new MapboxStyleProvider({ + url : 'made/up/mapbox/server/', + styleId: 'test-id' + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + spyOn(Resource._Implementations, 'createImage').and.callFake(function(url, crossOrigin, deferred) { + expect(url).not.toContain('//'); + + // Just return any old image. + Resource._DefaultImplementations.createImage('Data/Images/Red16x16.png', crossOrigin, deferred); + }); + + return provider.requestImage(0, 0, 0).then(function(image) { + expect(Resource._Implementations.createImage).toHaveBeenCalled(); + expect(image).toBeImageOrImageBitmap(); + }); + }); + }); + + it('supports no slash at the endof the URL', function() { + var provider = new MapboxStyleProvider({ + url : 'made/up/mapbox/server', + styleId: 'test-id' + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + spyOn(Resource._Implementations, 'createImage').and.callFake(function(url, crossOrigin, deferred) { + expect(url).toContain('made/up/mapbox/server/'); + + // Just return any old image. + Resource._DefaultImplementations.createImage('Data/Images/Red16x16.png', crossOrigin, deferred); + }); + + return provider.requestImage(0, 0, 0).then(function(image) { + expect(Resource._Implementations.createImage).toHaveBeenCalled(); + expect(image).toBeImageOrImageBitmap(); + }); + }); + }); + + it('requestImage returns a promise for an image and loads it for cross-origin use', function() { + var provider = new MapboxStyleProvider({ + url : 'made/up/mapbox/server/', + styleId: 'test-id' + }); + + expect(provider.url).toEqual('made/up/mapbox/server/'); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + expect(provider.tileWidth).toEqual(256); + expect(provider.tileHeight).toEqual(256); + expect(provider.maximumLevel).toBeUndefined(); + expect(provider.tilingScheme).toBeInstanceOf(WebMercatorTilingScheme); + expect(provider.rectangle).toEqual(new WebMercatorTilingScheme().rectangle); + + spyOn(Resource._Implementations, 'createImage').and.callFake(function(url, crossOrigin, deferred) { + // Just return any old image. + Resource._DefaultImplementations.createImage('Data/Images/Red16x16.png', crossOrigin, deferred); + }); + + return provider.requestImage(0, 0, 0).then(function(image) { + expect(Resource._Implementations.createImage).toHaveBeenCalled(); + expect(image).toBeImageOrImageBitmap(); + }); + }); + }); + + it('rectangle passed to constructor does not affect tile numbering', function() { + var rectangle = new Rectangle(0.1, 0.2, 0.3, 0.4); + var provider = new MapboxStyleProvider({ + url : 'made/up/mapbox/server/', + styleId: 'test-id', + rectangle : rectangle + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + expect(provider.tileWidth).toEqual(256); + expect(provider.tileHeight).toEqual(256); + expect(provider.maximumLevel).toBeUndefined(); + expect(provider.tilingScheme).toBeInstanceOf(WebMercatorTilingScheme); + expect(provider.rectangle).toEqualEpsilon(rectangle, CesiumMath.EPSILON14); + expect(provider.tileDiscardPolicy).toBeUndefined(); + + spyOn(Resource._Implementations, 'createImage').and.callFake(function(url, crossOrigin, deferred) { + expect(url).toContain('/0/0/0'); + + // Just return any old image. + Resource._DefaultImplementations.createImage('Data/Images/Red16x16.png', crossOrigin, deferred); + }); + + return provider.requestImage(0, 0, 0).then(function(image) { + expect(Resource._Implementations.createImage).toHaveBeenCalled(); + expect(image).toBeImageOrImageBitmap(); + }); + }); + }); + + it('uses maximumLevel passed to constructor', function() { + var provider = new MapboxStyleProvider({ + url : 'made/up/mapbox/server/', + styleId: 'test-id', + maximumLevel : 5 + }); + expect(provider.maximumLevel).toEqual(5); + }); + + it('uses minimumLevel passed to constructor', function() { + var provider = new MapboxStyleProvider({ + url : 'made/up/mapbox/server/', + styleId: 'test-id', + minimumLevel : 1 + }); + expect(provider.minimumLevel).toEqual(1); + }); + + it('when no credit is supplied, the provider adds a default credit', function() { + var provider = new MapboxStyleProvider({ + url : 'made/up/mapbox/server/', + styleId: 'test-id' + }); + expect(provider.credit).toBe(MapboxStyleProvider._defaultCredit); + }); + + it('turns the supplied credit into a logo', function() { + var creditText = 'Thanks to our awesome made up source of this imagery!'; + var providerWithCredit = new MapboxStyleProvider({ + url : 'made/up/mapbox/server/', + styleId: 'test-id', + credit: creditText + }); + expect(providerWithCredit.credit.html).toEqual(creditText); + }); + + it('raises error event when image cannot be loaded', function() { + var provider = new MapboxStyleProvider({ + url : 'made/up/mapbox/server/', + styleId: 'test-id' + }); + + var layer = new ImageryLayer(provider); + + var tries = 0; + provider.errorEvent.addEventListener(function(error) { + expect(error.timesRetried).toEqual(tries); + ++tries; + if (tries < 3) { + error.retry = true; + } + setTimeout(function() { + RequestScheduler.update(); + }, 1); + }); + + Resource._Implementations.createImage = function(url, crossOrigin, deferred) { + if (tries === 2) { + // Succeed after 2 tries + Resource._DefaultImplementations.createImage('Data/Images/Red16x16.png', crossOrigin, deferred); + } else { + // fail + setTimeout(function() { + deferred.reject(); + }, 1); + } + }; + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + var imagery = new Imagery(layer, 0, 0, 0); + imagery.addReference(); + layer._requestImagery(imagery); + RequestScheduler.update(); + + return pollToPromise(function() { + return imagery.state === ImageryState.RECEIVED; + }).then(function() { + expect(imagery.image).toBeImageOrImageBitmap(); + expect(tries).toEqual(2); + imagery.releaseReference(); + }); + }); + }); + + it('contains specified url', function() { + var provider = new MapboxStyleProvider({ + url: 'http://fake.map.com', + styleId: 'test-id' + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + spyOn(Resource._Implementations, 'createImage').and.callFake(function(url, crossOrigin, deferred) { + expect(url).toContain('http://fake.map.com'); + + // Just return any old image. + Resource._DefaultImplementations.createImage('Data/Images/Red16x16.png', crossOrigin, deferred); + }); + + return provider.requestImage(0, 0, 0); + }); + }); + + it('contains specified username', function() { + var provider = new MapboxStyleProvider({ + styleId: 'test-id', + username: 'fakeUsername' + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + spyOn(Resource._Implementations, 'createImage').and.callFake(function(url, crossOrigin, deferred) { + expect(url).toContain('https://api.mapbox.com/styles/v1/fakeUsername'); + + // Just return any old image. + Resource._DefaultImplementations.createImage('Data/Images/Red16x16.png', crossOrigin, deferred); + }); + + return provider.requestImage(0, 0, 0); + }); + }); + + it('contains specified tilesize', function() { + var provider = new MapboxStyleProvider({ + styleId: 'test-id', + tilesize: 256 + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + spyOn(Resource._Implementations, 'createImage').and.callFake(function(url, crossOrigin, deferred) { + expect(url).toContain('https://api.mapbox.com/styles/v1/mapbox/test-id/tiles/256'); + + // Just return any old image. + Resource._DefaultImplementations.createImage('Data/Images/Red16x16.png', crossOrigin, deferred); + }); + + return provider.requestImage(0, 0, 0); + }); + }); + + it('enables @2x scale factor', function() { + var provider = new MapboxStyleProvider({ + styleId: 'test-id', + scaleFactor: true + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + spyOn(Resource._Implementations, 'createImage').and.callFake(function(url, crossOrigin, deferred) { + expect(url).toContain('https://api.mapbox.com/styles/v1/mapbox/test-id/tiles/512/0/0/0@2x'); + + // Just return any old image. + Resource._DefaultImplementations.createImage('Data/Images/Red16x16.png', crossOrigin, deferred); + }); + + return provider.requestImage(0, 0, 0); + }); + }); +}); From c9ce7cd684b74a1a861ad4a2cff6553a13c69e94 Mon Sep 17 00:00:00 2001 From: Aristeidis Bampakos Date: Wed, 22 May 2019 21:30:50 +0300 Subject: [PATCH 4/5] MapboxStyleImageryProvider - Rename from MapboxStyleProvider - Modify CHANGES.md - Add reference to ImageryProvider class --- CHANGES.md | 1 + Source/Scene/ImageryProvider.js | 1 + ...vider.js => MapboxStyleImageryProvider.js} | 66 +++++++++---------- .../createDefaultImageryProviderViewModels.js | 10 +-- ...c.js => MapboxStyleImageryProviderSpec.js} | 42 ++++++------ 5 files changed, 61 insertions(+), 59 deletions(-) rename Source/Scene/{MapboxStyleProvider.js => MapboxStyleImageryProvider.js} (84%) rename Specs/Scene/{MapboxStyleProviderSpec.js => MapboxStyleImageryProviderSpec.js} (90%) diff --git a/CHANGES.md b/CHANGES.md index a3ef7df37f78..a783ab36a063 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ Change Log * Added `checkerboard` material to CZML. [#7845](https://github.com/AnalyticalGraphicsInc/cesium/pull/7845) * `BingMapsImageryProvider` now uses `DiscardEmptyTileImagePolicy` by default to detect missing tiles as zero-length responses instead of inspecting pixel values. [#7810](https://github.com/AnalyticalGraphicsInc/cesium/pull/7810) * Reduce the number of Bing transactions and ion Bing sessions used when destroying and recreating the same imagery layer to 1. [#7848](https://github.com/AnalyticalGraphicsInc/cesium/pull/7848) +* Added support for new Mapbox Style API. [#7698](https://github.com/AnalyticalGraphicsInc/cesium/pull/7698) ##### Fixes :wrench: * Fixed an edge case where Cesium would provide ion access token credentials to non-ion servers if the actual asset entrypoint was being hosted by ion. [#7839](https://github.com/AnalyticalGraphicsInc/cesium/pull/7839) diff --git a/Source/Scene/ImageryProvider.js b/Source/Scene/ImageryProvider.js index addce4ab2f09..1a372ddc6a06 100644 --- a/Source/Scene/ImageryProvider.js +++ b/Source/Scene/ImageryProvider.js @@ -31,6 +31,7 @@ define([ * @see GoogleEarthEnterpriseMapsProvider * @see GridImageryProvider * @see MapboxImageryProvider + * @see MapboxStyleImageryProvider * @see SingleTileImageryProvider * @see TileCoordinatesImageryProvider * @see UrlTemplateImageryProvider diff --git a/Source/Scene/MapboxStyleProvider.js b/Source/Scene/MapboxStyleImageryProvider.js similarity index 84% rename from Source/Scene/MapboxStyleProvider.js rename to Source/Scene/MapboxStyleImageryProvider.js index f0a1981ff2b8..b196fffbe66c 100644 --- a/Source/Scene/MapboxStyleProvider.js +++ b/Source/Scene/MapboxStyleImageryProvider.js @@ -24,7 +24,7 @@ var defaultCredit = new Credit('© getTileCredits must not be called before the imagery provider is ready. */ -MapboxStyleProvider.prototype.getTileCredits = function(x, y, level) { +MapboxStyleImageryProvider.prototype.getTileCredits = function(x, y, level) { if (defined(this._accessTokenErrorCredit)) { return [this._accessTokenErrorCredit]; } @@ -325,7 +325,7 @@ MapboxStyleProvider.prototype.getTileCredits = function(x, y, level) { /** * Requests the image for a given tile. This function should - * not be called before {@link MapboxStyleProvider#ready} returns true. + * not be called before {@link MapboxStyleImageryProvider#ready} returns true. * * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. @@ -338,13 +338,13 @@ MapboxStyleProvider.prototype.getTileCredits = function(x, y, level) { * * @exception {DeveloperError} requestImage must not be called before the imagery provider is ready. */ -MapboxStyleProvider.prototype.requestImage = function(x, y, level, request) { +MapboxStyleImageryProvider.prototype.requestImage = function(x, y, level, request) { return this._imageryProvider.requestImage(x, y, level, request); }; /** * Asynchronously determines what features, if any, are located at a given longitude and latitude within - * a tile. This function should not be called before {@link MapboxStyleProvider#ready} returns true. + * a tile. This function should not be called before {@link MapboxStyleImageryProvider#ready} returns true. * This function is optional, so it may not exist on all ImageryProviders. * * @@ -360,12 +360,12 @@ MapboxStyleProvider.prototype.requestImage = function(x, y, level, request) { * * @exception {DeveloperError} pickFeatures must not be called before the imagery provider is ready. */ -MapboxStyleProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) { +MapboxStyleImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) { return this._imageryProvider.pickFeatures(x, y, level, longitude, latitude); }; // Exposed for tests -MapboxStyleProvider._defaultCredit = defaultCredit; +MapboxStyleImageryProvider._defaultCredit = defaultCredit; -return MapboxStyleProvider; +return MapboxStyleImageryProvider; }); diff --git a/Source/Widgets/BaseLayerPicker/createDefaultImageryProviderViewModels.js b/Source/Widgets/BaseLayerPicker/createDefaultImageryProviderViewModels.js index 6c9ec0ef826b..25992e7f4260 100644 --- a/Source/Widgets/BaseLayerPicker/createDefaultImageryProviderViewModels.js +++ b/Source/Widgets/BaseLayerPicker/createDefaultImageryProviderViewModels.js @@ -6,7 +6,7 @@ define([ '../../Scene/createTileMapServiceImageryProvider', '../../Scene/createWorldImagery', '../../Scene/IonImageryProvider', - '../../Scene/MapboxStyleProvider', + '../../Scene/MapboxStyleImageryProvider', '../BaseLayerPicker/ProviderViewModel' ], function( buildModuleUrl, @@ -16,7 +16,7 @@ define([ createTileMapServiceImageryProvider, createWorldImagery, IonImageryProvider, - MapboxStyleProvider, + MapboxStyleImageryProvider, ProviderViewModel) { 'use strict'; @@ -67,7 +67,7 @@ define([ iconUrl: buildModuleUrl('Widgets/Images/ImageryProviders/mapboxSatellite.png'), category : 'Other', creationFunction: function() { - return new MapboxStyleProvider({ + return new MapboxStyleImageryProvider({ styleId: 'satellite-v9' }); } @@ -79,7 +79,7 @@ define([ iconUrl: buildModuleUrl('Widgets/Images/ImageryProviders/mapboxTerrain.png'), category : 'Other', creationFunction: function() { - return new MapboxStyleProvider({ + return new MapboxStyleImageryProvider({ styleId: 'satellite-streets-v11' }); } @@ -91,7 +91,7 @@ define([ iconUrl: buildModuleUrl('Widgets/Images/ImageryProviders/mapboxStreets.png'), category : 'Other', creationFunction: function() { - return new MapboxStyleProvider({ + return new MapboxStyleImageryProvider({ styleId: 'streets-v11' }); } diff --git a/Specs/Scene/MapboxStyleProviderSpec.js b/Specs/Scene/MapboxStyleImageryProviderSpec.js similarity index 90% rename from Specs/Scene/MapboxStyleProviderSpec.js rename to Specs/Scene/MapboxStyleImageryProviderSpec.js index abac21d598bd..347c25a804a4 100644 --- a/Specs/Scene/MapboxStyleProviderSpec.js +++ b/Specs/Scene/MapboxStyleImageryProviderSpec.js @@ -1,5 +1,5 @@ defineSuite([ - 'Scene/MapboxStyleProvider', + 'Scene/MapboxStyleImageryProvider', 'Core/Math', 'Core/Rectangle', 'Core/RequestScheduler', @@ -11,7 +11,7 @@ defineSuite([ 'Scene/ImageryState', 'Specs/pollToPromise' ], function( - MapboxStyleProvider, + MapboxStyleImageryProvider, CesiumMath, Rectangle, RequestScheduler, @@ -33,17 +33,17 @@ defineSuite([ }); it('conforms to ImageryProvider interface', function() { - expect(MapboxStyleProvider).toConformToInterface(ImageryProvider); + expect(MapboxStyleImageryProvider).toConformToInterface(ImageryProvider); }); it('requires the styleId to be specified', function() { expect(function() { - return new MapboxStyleProvider({}); + return new MapboxStyleImageryProvider({}); }).toThrowDeveloperError('styleId is required'); }); it('resolves readyPromise', function() { - var provider = new MapboxStyleProvider({ + var provider = new MapboxStyleImageryProvider({ url : 'made/up/mapbox/server/', styleId: 'test-id' }); @@ -59,7 +59,7 @@ defineSuite([ url : 'made/up/mapbox/server/' }); - var provider = new MapboxStyleProvider({ + var provider = new MapboxStyleImageryProvider({ url : resource, styleId: 'test-id' }); @@ -71,7 +71,7 @@ defineSuite([ }); it('returns valid value for hasAlphaChannel', function() { - var provider = new MapboxStyleProvider({ + var provider = new MapboxStyleImageryProvider({ url : 'made/up/mapbox/server/', styleId: 'test-id' }); @@ -84,7 +84,7 @@ defineSuite([ }); it('supports a slash at the end of the URL', function() { - var provider = new MapboxStyleProvider({ + var provider = new MapboxStyleImageryProvider({ url : 'made/up/mapbox/server/', styleId: 'test-id' }); @@ -107,7 +107,7 @@ defineSuite([ }); it('supports no slash at the endof the URL', function() { - var provider = new MapboxStyleProvider({ + var provider = new MapboxStyleImageryProvider({ url : 'made/up/mapbox/server', styleId: 'test-id' }); @@ -130,7 +130,7 @@ defineSuite([ }); it('requestImage returns a promise for an image and loads it for cross-origin use', function() { - var provider = new MapboxStyleProvider({ + var provider = new MapboxStyleImageryProvider({ url : 'made/up/mapbox/server/', styleId: 'test-id' }); @@ -160,7 +160,7 @@ defineSuite([ it('rectangle passed to constructor does not affect tile numbering', function() { var rectangle = new Rectangle(0.1, 0.2, 0.3, 0.4); - var provider = new MapboxStyleProvider({ + var provider = new MapboxStyleImageryProvider({ url : 'made/up/mapbox/server/', styleId: 'test-id', rectangle : rectangle @@ -191,7 +191,7 @@ defineSuite([ }); it('uses maximumLevel passed to constructor', function() { - var provider = new MapboxStyleProvider({ + var provider = new MapboxStyleImageryProvider({ url : 'made/up/mapbox/server/', styleId: 'test-id', maximumLevel : 5 @@ -200,7 +200,7 @@ defineSuite([ }); it('uses minimumLevel passed to constructor', function() { - var provider = new MapboxStyleProvider({ + var provider = new MapboxStyleImageryProvider({ url : 'made/up/mapbox/server/', styleId: 'test-id', minimumLevel : 1 @@ -209,16 +209,16 @@ defineSuite([ }); it('when no credit is supplied, the provider adds a default credit', function() { - var provider = new MapboxStyleProvider({ + var provider = new MapboxStyleImageryProvider({ url : 'made/up/mapbox/server/', styleId: 'test-id' }); - expect(provider.credit).toBe(MapboxStyleProvider._defaultCredit); + expect(provider.credit).toBe(MapboxStyleImageryProvider._defaultCredit); }); it('turns the supplied credit into a logo', function() { var creditText = 'Thanks to our awesome made up source of this imagery!'; - var providerWithCredit = new MapboxStyleProvider({ + var providerWithCredit = new MapboxStyleImageryProvider({ url : 'made/up/mapbox/server/', styleId: 'test-id', credit: creditText @@ -227,7 +227,7 @@ defineSuite([ }); it('raises error event when image cannot be loaded', function() { - var provider = new MapboxStyleProvider({ + var provider = new MapboxStyleImageryProvider({ url : 'made/up/mapbox/server/', styleId: 'test-id' }); @@ -277,7 +277,7 @@ defineSuite([ }); it('contains specified url', function() { - var provider = new MapboxStyleProvider({ + var provider = new MapboxStyleImageryProvider({ url: 'http://fake.map.com', styleId: 'test-id' }); @@ -297,7 +297,7 @@ defineSuite([ }); it('contains specified username', function() { - var provider = new MapboxStyleProvider({ + var provider = new MapboxStyleImageryProvider({ styleId: 'test-id', username: 'fakeUsername' }); @@ -317,7 +317,7 @@ defineSuite([ }); it('contains specified tilesize', function() { - var provider = new MapboxStyleProvider({ + var provider = new MapboxStyleImageryProvider({ styleId: 'test-id', tilesize: 256 }); @@ -337,7 +337,7 @@ defineSuite([ }); it('enables @2x scale factor', function() { - var provider = new MapboxStyleProvider({ + var provider = new MapboxStyleImageryProvider({ styleId: 'test-id', scaleFactor: true }); From beb1490e35ef3112b3f67026ea986a8a26902917 Mon Sep 17 00:00:00 2001 From: Aristeidis Bampakos Date: Wed, 29 May 2019 22:00:55 +0300 Subject: [PATCH 5/5] Update url parameter type --- Source/Scene/MapboxStyleImageryProvider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Scene/MapboxStyleImageryProvider.js b/Source/Scene/MapboxStyleImageryProvider.js index b196fffbe66c..753098bbf3c1 100644 --- a/Source/Scene/MapboxStyleImageryProvider.js +++ b/Source/Scene/MapboxStyleImageryProvider.js @@ -28,7 +28,7 @@ var defaultCredit = new Credit('©