diff --git a/Apps/CesiumViewer/CesiumViewer.js b/Apps/CesiumViewer/CesiumViewer.js index e3577b817566..37cdec736bfd 100644 --- a/Apps/CesiumViewer/CesiumViewer.js +++ b/Apps/CesiumViewer/CesiumViewer.js @@ -10,7 +10,7 @@ define([ 'Cesium/DataSources/CzmlDataSource', 'Cesium/DataSources/GeoJsonDataSource', 'Cesium/DataSources/KmlDataSource', - 'Cesium/Scene/TileMapServiceImageryProvider', + 'Cesium/Scene/createTileMapServiceImageryProvider', 'Cesium/Widgets/Viewer/Viewer', 'Cesium/Widgets/Viewer/viewerCesiumInspectorMixin', 'Cesium/Widgets/Viewer/viewerDragDropMixin', @@ -26,7 +26,7 @@ define([ CzmlDataSource, GeoJsonDataSource, KmlDataSource, - TileMapServiceImageryProvider, + createTileMapServiceImageryProvider, Viewer, viewerCesiumInspectorMixin, viewerDragDropMixin) { @@ -47,7 +47,7 @@ define([ var imageryProvider; if (endUserOptions.tmsImageryUrl) { - imageryProvider = new TileMapServiceImageryProvider({ + imageryProvider = createTileMapServiceImageryProvider({ url : endUserOptions.tmsImageryUrl }); } diff --git a/Apps/Sandcastle/gallery/Imagery Layers Manipulation.html b/Apps/Sandcastle/gallery/Imagery Layers Manipulation.html index 94960cdbb793..b7b9798daabb 100644 --- a/Apps/Sandcastle/gallery/Imagery Layers Manipulation.html +++ b/Apps/Sandcastle/gallery/Imagery Layers Manipulation.html @@ -139,7 +139,7 @@ })); addBaseLayerOption( 'Natural Earth II (local)', - new Cesium.TileMapServiceImageryProvider({ + Cesium.createTileMapServiceImageryProvider({ url : require.toUrl('Assets/Textures/NaturalEarthII') })); addBaseLayerOption( @@ -181,7 +181,7 @@ })); addAdditionalLayerOption( 'TileMapService Image', - new Cesium.TileMapServiceImageryProvider({ + Cesium.createTileMapServiceImageryProvider({ url : '../images/cesium_maptiler/Cesium_Logo_Color' }), 0.2); diff --git a/Apps/Sandcastle/gallery/Imagery Layers.html b/Apps/Sandcastle/gallery/Imagery Layers.html index 79554ad4e04b..757804bad070 100644 --- a/Apps/Sandcastle/gallery/Imagery Layers.html +++ b/Apps/Sandcastle/gallery/Imagery Layers.html @@ -35,7 +35,7 @@ }); var layers = viewer.imageryLayers; -var blackMarble = layers.addImageryProvider(new Cesium.TileMapServiceImageryProvider({ +var blackMarble = layers.addImageryProvider(Cesium.createTileMapServiceImageryProvider({ url : '//cesiumjs.org/blackmarble', maximumLevel : 8, credit : 'Black Marble imagery courtesy NASA Earth Observatory' diff --git a/CHANGES.md b/CHANGES.md index cc9ac419b3fe..ac13a2c85238 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,11 +14,15 @@ Change Log * Removed `HeightmapTessellator` from the public API. It is now private and subject to change without notice. * Removed `TerrainMesh` from the public API. It is now private and subject to change without notice. * Removed `jsonp`. Use `loadJsonp` instead. +* Deprecated + * Deprecated `TileMapServiceImageryProvider`. It will be removed in 1.18. Use `createTileMapServiceImageryProvider` instead. +* Refactored `UrlTemplateImageryProvider.reinitialize` to accept a promise to `options`. * Reduced the amount of both GPU and CPU memory used by terrain. The CPU memory was reduced by up to 40%. * `CorridorGeometry` and `PolylineVolumeGeometry` render short segments [#3293](https://github.com/AnalyticalGraphicsInc/cesium/issues/3293) * `Rectangle.fromCartographicArray` finds the smallest rectangle regardess of whether or not it crosses the international date line. [#3227](https://github.com/AnalyticalGraphicsInc/cesium/issues/3227) * Bug fix for `CorridorGeometry` with nearly colinear points [#3320](https://github.com/AnalyticalGraphicsInc/cesium/issues/3320) + ### 1.16 - 2015-12-01 * Deprecated diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index e32f4346caaf..7928404910a0 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -79,7 +79,7 @@ define([ * * // Terrain geometry near the surface of the globe is difficult to view when using NaturalEarthII imagery, * // unless the TerrainProvider provides additional lighting information to shade the terrain (as shown above). - * var imageryProvider = new Cesium.TileMapServiceImageryProvider({ + * var imageryProvider = Cesium.createTileMapServiceImageryProvider({ * url : 'http://localhost:8080/Source/Assets/Textures/NaturalEarthII', * fileExtension : 'jpg' * }); diff --git a/Source/Scene/ArcGisMapServerImageryProvider.js b/Source/Scene/ArcGisMapServerImageryProvider.js index 55455e2fa5f1..20471850cf08 100644 --- a/Source/Scene/ArcGisMapServerImageryProvider.js +++ b/Source/Scene/ArcGisMapServerImageryProvider.js @@ -97,7 +97,7 @@ define([ * @see GoogleEarthImageryProvider * @see createOpenStreetMapImageryProvider * @see SingleTileImageryProvider - * @see TileMapServiceImageryProvider + * @see createTileMapServiceImageryProvider * @see WebMapServiceImageryProvider * @see WebMapTileServiceImageryProvider * @see UrlTemplateImageryProvider diff --git a/Source/Scene/BingMapsImageryProvider.js b/Source/Scene/BingMapsImageryProvider.js index 1bf8337b393d..bfa3e571742c 100644 --- a/Source/Scene/BingMapsImageryProvider.js +++ b/Source/Scene/BingMapsImageryProvider.js @@ -79,7 +79,7 @@ define([ * @see GoogleEarthImageryProvider * @see createOpenStreetMapImageryProvider * @see SingleTileImageryProvider - * @see TileMapServiceImageryProvider + * @see createTileMapServiceImageryProvider * @see WebMapServiceImageryProvider * @see WebMapTileServiceImageryProvider * @see UrlTemplateImageryProvider diff --git a/Source/Scene/GoogleEarthImageryProvider.js b/Source/Scene/GoogleEarthImageryProvider.js index 201b9a723802..049cfd8fe528 100644 --- a/Source/Scene/GoogleEarthImageryProvider.js +++ b/Source/Scene/GoogleEarthImageryProvider.js @@ -84,7 +84,7 @@ define([ * @see BingMapsImageryProvider * @see createOpenStreetMapImageryProvider * @see SingleTileImageryProvider - * @see TileMapServiceImageryProvider + * @see createTileMapServiceImageryProvider * @see WebMapServiceImageryProvider * @see WebMapTileServiceImageryProvider * @see UrlTemplateImageryProvider @@ -591,4 +591,3 @@ define([ return GoogleEarthImageryProvider; }); - diff --git a/Source/Scene/ImageryProvider.js b/Source/Scene/ImageryProvider.js index 74994e657e4a..abbd46cf974d 100644 --- a/Source/Scene/ImageryProvider.js +++ b/Source/Scene/ImageryProvider.js @@ -233,7 +233,8 @@ define([ * 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. + * and texture upload time are reduced. This function should + * not be called before {@link ImageryProvider#ready} returns true. * @memberof ImageryProvider.prototype * @type {Boolean} * @readonly diff --git a/Source/Scene/OpenStreetMapImageryProvider.js b/Source/Scene/OpenStreetMapImageryProvider.js index 0bc20da0367b..7f4d17740385 100644 --- a/Source/Scene/OpenStreetMapImageryProvider.js +++ b/Source/Scene/OpenStreetMapImageryProvider.js @@ -51,7 +51,7 @@ define([ * @see BingMapsImageryProvider * @see GoogleEarthImageryProvider * @see SingleTileImageryProvider - * @see TileMapServiceImageryProvider + * @see createTileMapServiceImageryProvider * @see WebMapServiceImageryProvider * @see WebMapTileServiceImageryProvider * @see UrlTemplateImageryProvider @@ -64,7 +64,7 @@ define([ * var osm = new Cesium.OpenStreetMapImageryProvider({ * url : '//a.tile.openstreetmap.org/' * }); - * + * * @deprecated */ function OpenStreetMapImageryProvider(options) { diff --git a/Source/Scene/SingleTileImageryProvider.js b/Source/Scene/SingleTileImageryProvider.js index 601377cba6e2..cf115512c209 100644 --- a/Source/Scene/SingleTileImageryProvider.js +++ b/Source/Scene/SingleTileImageryProvider.js @@ -45,7 +45,7 @@ define([ * @see BingMapsImageryProvider * @see GoogleEarthImageryProvider * @see createOpenStreetMapImageryProvider - * @see TileMapServiceImageryProvider + * @see createTileMapServiceImageryProvider * @see WebMapServiceImageryProvider * @see WebMapTileServiceImageryProvider * @see UrlTemplateImageryProvider diff --git a/Source/Scene/TileMapServiceImageryProvider.js b/Source/Scene/TileMapServiceImageryProvider.js index 6b3c405fc7c9..eec7d7ddffb1 100644 --- a/Source/Scene/TileMapServiceImageryProvider.js +++ b/Source/Scene/TileMapServiceImageryProvider.js @@ -6,6 +6,7 @@ define([ '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', + '../Core/deprecationWarning', '../Core/DeveloperError', '../Core/Event', '../Core/GeographicTilingScheme', @@ -24,6 +25,7 @@ define([ defaultValue, defined, defineProperties, + deprecationWarning, DeveloperError, Event, GeographicTilingScheme, @@ -34,10 +36,13 @@ define([ TileProviderError, WebMercatorTilingScheme, when, - ImageryProvider) { + ImageryProvider + ) { "use strict"; /** + * @deprecated + * * Provides tiled imagery as generated by {@link http://www.maptiler.org/'>MapTiler / >includeStart('debug', pragmas.debug); diff --git a/Source/Scene/UrlTemplateImageryProvider.js b/Source/Scene/UrlTemplateImageryProvider.js index d0a5251daad6..33298251a667 100644 --- a/Source/Scene/UrlTemplateImageryProvider.js +++ b/Source/Scene/UrlTemplateImageryProvider.js @@ -54,7 +54,7 @@ define([ * @constructor * * @param {Object} [options] Object with the following properties: - * @param {String} [options.url] The URL template to use to request tiles. It has the following keywords: + * @param {String} options.url The URL template to use to request tiles. It has the following keywords: * * @param {String} [options.pickFeaturesUrl] The URL template to use to pick features. If this property is not specified, - * {@see UrlTemplateImageryProvider#pickFeatures} will immediately returned undefined, indicating no + * {@link UrlTemplateImageryProvider#pickFeatures} will immediately returned undefined, indicating no * features picked. The URL template supports all of the keywords supported by the url * parameter, plus the following: * * @param {String|String[]} [options.subdomains='abc'] The subdomains to use for the {s} placeholder in the URL template. * If this parameter is a single string, each character in the string is a subdomain. If it is @@ -113,15 +113,14 @@ define([ * be treated as if their alpha is 1.0 everywhere. When this property is false, memory usage * and texture upload time are potentially reduced. * @param {GetFeatureInfoFormat[]} [options.getFeatureInfoFormats] The formats in which to get feature information at a - * specific location when {@see UrlTemplateImageryProvider#pickFeatures} is invoked. If this + * specific location when {@link UrlTemplateImageryProvider#pickFeatures} is invoked. If this * parameter is not specified, feature picking is disabled. - * * @see ArcGisMapServerImageryProvider * @see BingMapsImageryProvider * @see GoogleEarthImageryProvider * @see createOpenStreetMapImageryProvider * @see SingleTileImageryProvider - * @see TileMapServiceImageryProvider + * @see createTileMapServiceImageryProvider * @see WebMapServiceImageryProvider * @see WebMapTileServiceImageryProvider * @@ -151,47 +150,71 @@ define([ */ function UrlTemplateImageryProvider(options) { //>>includeStart('debug', pragmas.debug); - if (!defined(options) || !defined(options.url)) { - throw new DeveloperError('options.url is required.'); + if (!defined(options)) { + throw new DeveloperError('options is required.'); + } + if (!when.isPromise(options) && !defined(options.url)) { + throw new DeveloperError('options is required.'); } //>>includeEnd('debug'); - this._url = options.url; - this._pickFeaturesUrl = options.pickFeaturesUrl; - this._proxy = options.proxy; - this._tileDiscardPolicy = options.tileDiscardPolicy; - this._getFeatureInfoFormats = options.getFeatureInfoFormats; - this._errorEvent = new Event(); - this._subdomains = options.subdomains; - if (Array.isArray(this._subdomains)) { - this._subdomains = this._subdomains.slice(); - } else if (defined(this._subdomains) && this._subdomains.length > 0) { - this._subdomains = this._subdomains.split(''); - } else { - this._subdomains = ['a', 'b', 'c']; - } + this._url = undefined; + this._pickFeaturesUrl = undefined; + this._proxy = undefined; + this._tileWidth = undefined; + this._tileHeight = undefined; + this._maximumLevel = undefined; + this._minimumLevel = undefined; + this._tilingScheme = undefined; + this._rectangle = undefined; + this._tileDiscardPolicy = undefined; + this._credit = undefined; + this._hasAlphaChannel = undefined; - this._tileWidth = defaultValue(options.tileWidth, 256); - this._tileHeight = defaultValue(options.tileHeight, 256); - this._minimumLevel = defaultValue(options.minimumLevel, 0); - this._maximumLevel = options.maximumLevel; - this._tilingScheme = defaultValue(options.tilingScheme, new WebMercatorTilingScheme({ ellipsoid : options.ellipsoid })); - this._rectangle = defaultValue(options.rectangle, this._tilingScheme.rectangle); - this._rectangle = Rectangle.intersection(this._rectangle, this._tilingScheme.rectangle); - this._hasAlphaChannel = defaultValue(options.hasAlphaChannel, true); - - var credit = options.credit; - if (typeof credit === 'string') { - credit = new Credit(credit); - } - this._credit = credit; + var that = this; + this._readyPromise = when(options).then(function(properties) { + //>>includeStart('debug', pragmas.debug); + if (!defined(properties.url)) { + throw new DeveloperError('options.url is required.'); + } + //>>includeEnd('debug'); + + that._url = properties.url; + that._pickFeaturesUrl = properties.pickFeaturesUrl; + that._proxy = properties.proxy; + that._tileDiscardPolicy = properties.tileDiscardPolicy; + that._getFeatureInfoFormats = properties.getFeatureInfoFormats; + + that._subdomains = properties.subdomains; + if (Array.isArray(that._subdomains)) { + that._subdomains = that._subdomains.slice(); + } else if (defined(that._subdomains) && that._subdomains.length > 0) { + that._subdomains = that._subdomains.split(''); + } else { + that._subdomains = ['a', 'b', 'c']; + } - this._urlParts = urlTemplateToParts(this._url, tags); - this._pickFeaturesUrlParts = urlTemplateToParts(this._pickFeaturesUrl, pickFeaturesTags); + that._tileWidth = defaultValue(properties.tileWidth, 256); + that._tileHeight = defaultValue(properties.tileHeight, 256); + that._minimumLevel = defaultValue(properties.minimumLevel, 0); + that._maximumLevel = properties.maximumLevel; + that._tilingScheme = defaultValue(properties.tilingScheme, new WebMercatorTilingScheme({ ellipsoid : properties.ellipsoid })); + that._rectangle = defaultValue(properties.rectangle, that._tilingScheme.rectangle); + that._rectangle = Rectangle.intersection(that._rectangle, that._tilingScheme.rectangle); + that._hasAlphaChannel = defaultValue(properties.hasAlphaChannel, true); + + var credit = properties.credit; + if (typeof credit === 'string') { + credit = new Credit(credit); + } + that._credit = credit; - this._readyPromise = when.resolve(true); + that._urlParts = urlTemplateToParts(that._url, tags); + that._pickFeaturesUrlParts = urlTemplateToParts(that._pickFeaturesUrl, pickFeaturesTags); + return true; + }); } defineProperties(UrlTemplateImageryProvider.prototype, { @@ -274,6 +297,11 @@ define([ */ tileWidth : { get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this.ready) { + throw new DeveloperError('tileWidth must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); return this._tileWidth; } }, @@ -288,6 +316,11 @@ define([ */ tileHeight: { get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this.ready) { + throw new DeveloperError('tileHeight must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); return this._tileHeight; } }, @@ -302,6 +335,11 @@ define([ */ maximumLevel : { get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this.ready) { + throw new DeveloperError('maximumLevel must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); return this._maximumLevel; } }, @@ -316,6 +354,11 @@ define([ */ minimumLevel : { get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this.ready) { + throw new DeveloperError('minimumLevel must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); return this._minimumLevel; } }, @@ -330,6 +373,11 @@ define([ */ tilingScheme : { get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this.ready) { + throw new DeveloperError('tilingScheme must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); return this._tilingScheme; } }, @@ -344,6 +392,11 @@ define([ */ rectangle : { get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this.ready) { + throw new DeveloperError('rectangle must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); return this._rectangle; } }, @@ -360,6 +413,11 @@ define([ */ tileDiscardPolicy : { get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this.ready) { + throw new DeveloperError('tileDiscardPolicy must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); return this._tileDiscardPolicy; } }, @@ -386,7 +444,7 @@ define([ */ ready : { get : function() { - return true; + return defined(this._urlParts); } }, @@ -412,6 +470,11 @@ define([ */ credit : { get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this.ready) { + throw new DeveloperError('credit must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); return this._credit; } }, @@ -421,7 +484,8 @@ define([ * 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. + * and texture upload time are reduced. This function should + * not be called before {@link ImageryProvider#ready} returns true. * @memberof UrlTemplateImageryProvider.prototype * @type {Boolean} * @readonly @@ -429,6 +493,11 @@ define([ */ hasAlphaChannel : { get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this.ready) { + throw new DeveloperError('hasAlphaChannel must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); return this._hasAlphaChannel; } } @@ -445,6 +514,11 @@ define([ * @exception {DeveloperError} getTileCredits must not be called before the imagery provider is ready. */ UrlTemplateImageryProvider.prototype.getTileCredits = function(x, y, level) { + //>>includeStart('debug', pragmas.debug); + if (!this.ready) { + throw new DeveloperError('getTileCredits must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); return undefined; }; @@ -461,6 +535,11 @@ define([ * Image or a Canvas DOM object. */ UrlTemplateImageryProvider.prototype.requestImage = function(x, y, level) { + //>>includeStart('debug', pragmas.debug); + if (!this.ready) { + throw new DeveloperError('requestImage must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); var url = buildImageUrl(this, x, y, level); return ImageryProvider.loadImage(this, url); }; @@ -480,6 +559,12 @@ define([ * It may also be undefined if picking is not supported. */ UrlTemplateImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) { + //>>includeStart('debug', pragmas.debug); + if (!this.ready) { + throw new DeveloperError('pickFeatures must not be called before the imagery provider is ready.'); + } + //>>includeEnd('debug'); + if (!defined(this._pickFeaturesUrl) || this._getFeatureInfoFormats.length === 0) { return undefined; } diff --git a/Source/Scene/WebMapServiceImageryProvider.js b/Source/Scene/WebMapServiceImageryProvider.js index fd70162937b8..f9e9362651ad 100644 --- a/Source/Scene/WebMapServiceImageryProvider.js +++ b/Source/Scene/WebMapServiceImageryProvider.js @@ -73,7 +73,7 @@ define([ * @see GoogleEarthImageryProvider * @see createOpenStreetMapImageryProvider * @see SingleTileImageryProvider - * @see TileMapServiceImageryProvider + * @see createTileMapServiceImageryProvider * @see WebMapTileServiceImageryProvider * @see UrlTemplateImageryProvider * diff --git a/Source/Scene/WebMapTileServiceImageryProvider.js b/Source/Scene/WebMapTileServiceImageryProvider.js index 2e48c58494d6..b629cd016e3d 100644 --- a/Source/Scene/WebMapTileServiceImageryProvider.js +++ b/Source/Scene/WebMapTileServiceImageryProvider.js @@ -65,7 +65,7 @@ define([ * @see GoogleEarthImageryProvider * @see createOpenStreetMapImageryProvider * @see SingleTileImageryProvider - * @see TileMapServiceImageryProvider + * @see createTileMapServiceImageryProvider * @see WebMapServiceImageryProvider * @see UrlTemplateImageryProvider * diff --git a/Source/Scene/createOpenStreetMapImageryProvider.js b/Source/Scene/createOpenStreetMapImageryProvider.js index cb555e10a55b..5db9837e06fe 100644 --- a/Source/Scene/createOpenStreetMapImageryProvider.js +++ b/Source/Scene/createOpenStreetMapImageryProvider.js @@ -43,7 +43,7 @@ define([ * @see BingMapsImageryProvider * @see GoogleEarthImageryProvider * @see SingleTileImageryProvider - * @see TileMapServiceImageryProvider + * @see createTileMapServiceImageryProvider * @see WebMapServiceImageryProvider * @see WebMapTileServiceImageryProvider * @see UrlTemplateImageryProvider diff --git a/Source/Scene/createTileMapServiceImageryProvider.js b/Source/Scene/createTileMapServiceImageryProvider.js new file mode 100644 index 000000000000..5a2c6edfaee2 --- /dev/null +++ b/Source/Scene/createTileMapServiceImageryProvider.js @@ -0,0 +1,288 @@ +/*global define*/ +define([ + '../Core/appendForwardSlash', + '../Core/Cartesian2', + '../Core/Cartographic', + '../Core/Credit', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/Event', + '../Core/GeographicTilingScheme', + '../Core/joinUrls', + '../Core/loadXML', + '../Core/Rectangle', + '../Core/RuntimeError', + '../Core/TileProviderError', + '../Core/WebMercatorTilingScheme', + '../ThirdParty/when', + './UrlTemplateImageryProvider' + ], function( + appendForwardSlash, + Cartesian2, + Cartographic, + Credit, + defaultValue, + defined, + defineProperties, + DeveloperError, + Event, + GeographicTilingScheme, + joinUrls, + loadXML, + Rectangle, + RuntimeError, + TileProviderError, + WebMercatorTilingScheme, + when, + UrlTemplateImageryProvider) { + "use strict"; + + /** + * Creates a {@link UrlTemplateImageryProvider} instance that provides tiled imagery as generated by + * {@link http://www.maptiler.org/'>MapTiler / >includeStart('debug', pragmas.debug); + if (!defined(options.url)) { + throw new DeveloperError('options.url is required.'); + } + //>>includeEnd('debug'); + + var url = appendForwardSlash(options.url); + + var deferred = when.defer(); + var imageryProvider = new UrlTemplateImageryProvider(deferred.promise); + + var metadataError; + + function metadataSuccess(xml) { + var tileFormatRegex = /tileformat/i; + var tileSetRegex = /tileset/i; + var tileSetsRegex = /tilesets/i; + var bboxRegex = /boundingbox/i; + var srsRegex = /srs/i; + var format, bbox, tilesets, srs; + var tilesetsList = []; //list of TileSets + + // Allowing options properties (already copied to that) to override XML values + + // Iterate XML Document nodes for properties + var nodeList = xml.childNodes[0].childNodes; + for (var i = 0; i < nodeList.length; i++){ + if (tileFormatRegex.test(nodeList.item(i).nodeName)) { + format = nodeList.item(i); + } else if (tileSetsRegex.test(nodeList.item(i).nodeName)) { + tilesets = nodeList.item(i); // Node list of TileSets + var tileSetNodes = nodeList.item(i).childNodes; + // Iterate the nodes to find all TileSets + for(var j = 0; j < tileSetNodes.length; j++) { + if (tileSetRegex.test(tileSetNodes.item(j).nodeName)) { + // Add them to tilesets list + tilesetsList.push(tileSetNodes.item(j)); + } + } + } else if (bboxRegex.test(nodeList.item(i).nodeName)) { + bbox = nodeList.item(i); + } else if (srsRegex.test(nodeList.item(i).nodeName)) { + srs = nodeList.item(i).textContent; + } + } + + var fileExtension = defaultValue(options.fileExtension, format.getAttribute('extension')); + var tileWidth = defaultValue(options.tileWidth, parseInt(format.getAttribute('width'), 10)); + var tileHeight = defaultValue(options.tileHeight, parseInt(format.getAttribute('height'), 10)); + var minimumLevel = defaultValue(options.minimumLevel, parseInt(tilesetsList[0].getAttribute('order'), 10)); + var maximumLevel = defaultValue(options.maximumLevel, parseInt(tilesetsList[tilesetsList.length - 1].getAttribute('order'), 10)); + + // Determine based on the profile attribute if this tileset was generated by gdal2tiles.py ('mercator' or 'geodetic' profile, in which + // case X is latitude and Y is longitude) or by a tool compliant with the TMS standard ('global-mercator' or 'global-geodetic' profile, + // in which case X is longitude and Y is latitude). + var tilingSchemeName = tilesets.getAttribute('profile'); + + var flipXY = false; + if (tilingSchemeName === 'geodetic' || tilingSchemeName === 'mercator') { + flipXY = true; + } + + var tilingScheme = options.tilingScheme; + + if (!defined(tilingScheme)) { + if (tilingSchemeName === 'geodetic' || tilingSchemeName === 'global-geodetic') { + tilingScheme = new GeographicTilingScheme({ ellipsoid : options.ellipsoid }); + } else if (tilingSchemeName === 'mercator' || tilingSchemeName === 'global-mercator') { + tilingScheme = new WebMercatorTilingScheme({ ellipsoid : options.ellipsoid }); + } else { + var message = joinUrls(url, 'tilemapresource.xml') + 'specifies an unsupported profile attribute, ' + tilingSchemeName + '.'; + metadataError = TileProviderError.handleError(metadataError, imageryProvider, imageryProvider.errorEvent, message, undefined, undefined, undefined, requestMetadata); + if(!metadataError.retry) { + deferred.reject(new RuntimeError(message)); + } + return; + } + } + + // rectangle handling + var rectangle = Rectangle.clone(options.rectangle); + + if (!defined(rectangle)) { + var swXY; + var neXY; + var sw; + var ne; + + if (flipXY) { + swXY = new Cartesian2(parseFloat(bbox.getAttribute('miny')), parseFloat(bbox.getAttribute('minx'))); + neXY = new Cartesian2(parseFloat(bbox.getAttribute('maxy')), parseFloat(bbox.getAttribute('maxx'))); + + // In old tilers with X/Y flipped, coordinate are always geodetic degrees. + sw = Cartographic.fromDegrees(swXY.x, swXY.y); + ne = Cartographic.fromDegrees(neXY.x, neXY.y); + } else { + swXY = new Cartesian2(parseFloat(bbox.getAttribute('minx')), parseFloat(bbox.getAttribute('miny'))); + neXY = new Cartesian2(parseFloat(bbox.getAttribute('maxx')), parseFloat(bbox.getAttribute('maxy'))); + + if (tilingScheme instanceof GeographicTilingScheme) { + sw = Cartographic.fromDegrees(swXY.x, swXY.y); + ne = Cartographic.fromDegrees(neXY.x, neXY.y); + } else { + var projection = tilingScheme.projection; + sw = projection.unproject(swXY); + ne = projection.unproject(neXY); + } + } + + rectangle = new Rectangle(sw.longitude, sw.latitude, ne.longitude, ne.latitude); + } + + // The rectangle must not be outside the bounds allowed by the tiling scheme. + if (rectangle.west < tilingScheme.rectangle.west) { + rectangle.west = tilingScheme.rectangle.west; + } + if (rectangle.east > tilingScheme.rectangle.east) { + rectangle.east = tilingScheme.rectangle.east; + } + if (rectangle.south < tilingScheme.rectangle.south) { + rectangle.south = tilingScheme.rectangle.south; + } + if (rectangle.north > tilingScheme.rectangle.north) { + rectangle.north = tilingScheme.rectangle.north; + } + + // Check the number of tiles at the minimum level. If it's more than four, + // try requesting the lower levels anyway, because starting at the higher minimum + // level will cause too many tiles to be downloaded and rendered. + var swTile = tilingScheme.positionToTileXY(Rectangle.southwest(rectangle), minimumLevel); + var neTile = tilingScheme.positionToTileXY(Rectangle.northeast(rectangle), minimumLevel); + var tileCount = (Math.abs(neTile.x - swTile.x) + 1) * (Math.abs(neTile.y - swTile.y) + 1); + if (tileCount > 4) { + minimumLevel = 0; + } + + var templateUrl = url + '{z}/{x}/{reverseY}.' + fileExtension; + + deferred.resolve({ + url : templateUrl, + tilingScheme : tilingScheme, + rectangle : rectangle, + tileWidth : tileWidth, + tileHeight : tileHeight, + minimumLevel : minimumLevel, + maximumLevel : maximumLevel, + proxy : options.proxy, + tileDiscardPolicy : options.tileDiscardPolicy, + credit: options.credit + }); + } + + function metadataFailure(error) { + // Can't load XML, still allow options and defaults + var fileExtension = defaultValue(options.fileExtension, 'png'); + var tileWidth = defaultValue(options.tileWidth, 256); + var tileHeight = defaultValue(options.tileHeight, 256); + var minimumLevel = defaultValue(options.minimumLevel, 0); + var maximumLevel = options.maximumLevel; + var tilingScheme = defined(options.tilingScheme) ? options.tilingScheme : new WebMercatorTilingScheme({ ellipsoid : options.ellipsoid }); + var rectangle = defaultValue(options.rectangle, tilingScheme.rectangle); + + var templateUrl = url + '{z}/{x}/{reverseY}.' + fileExtension; + + deferred.resolve({ + url : templateUrl, + tilingScheme : tilingScheme, + rectangle : rectangle, + tileWidth : tileWidth, + tileHeight : tileHeight, + minimumLevel : minimumLevel, + maximumLevel : maximumLevel, + proxy : options.proxy, + tileDiscardPolicy : options.tileDiscardPolicy, + credit: options.credit + }); + } + + function requestMetadata() { + var resourceUrl = url + 'tilemapresource.xml'; + var proxy = options.proxy; + if (defined(proxy)) { + resourceUrl = proxy.getURL(resourceUrl); + } + // Try to load remaining parameters from XML + when(loadXML(resourceUrl), metadataSuccess, metadataFailure); + } + + requestMetadata(); + return imageryProvider; + } + + return createTileMapServiceImageryProvider; +}); diff --git a/Source/Widgets/BaseLayerPicker/BaseLayerPicker.js b/Source/Widgets/BaseLayerPicker/BaseLayerPicker.js index c4675d990b07..041451151efe 100644 --- a/Source/Widgets/BaseLayerPicker/BaseLayerPicker.js +++ b/Source/Widgets/BaseLayerPicker/BaseLayerPicker.js @@ -75,7 +75,7 @@ define([ * tooltip : 'The lights of cities and villages trace the outlines of civilization \ * in this global view of the Earth at night as seen by NASA/NOAA\'s Suomi NPP satellite.', * creationFunction : function() { - * return new Cesium.TileMapServiceImageryProvider({ + * return Cesium.createTileMapServiceImageryProvider({ * url : '//cesiumjs.org/blackmarble', * maximumLevel : 8, * credit : 'Black Marble imagery courtesy NASA Earth Observatory' @@ -88,7 +88,7 @@ define([ * iconUrl : Cesium.buildModuleUrl('Widgets/Images/ImageryProviders/naturalEarthII.png'), * tooltip : 'Natural Earth II, darkened for contrast.\nhttp://www.naturalearthdata.com/', * creationFunction : function() { - * return new Cesium.TileMapServiceImageryProvider({ + * return Cesium.createTileMapServiceImageryProvider({ * url : Cesium.buildModuleUrl('Assets/Textures/NaturalEarthII') * }); * } diff --git a/Source/Widgets/BaseLayerPicker/createDefaultImageryProviderViewModels.js b/Source/Widgets/BaseLayerPicker/createDefaultImageryProviderViewModels.js index c5340fd341b3..92f6f33ac597 100644 --- a/Source/Widgets/BaseLayerPicker/createDefaultImageryProviderViewModels.js +++ b/Source/Widgets/BaseLayerPicker/createDefaultImageryProviderViewModels.js @@ -6,7 +6,7 @@ define([ '../../Scene/BingMapsStyle', '../../Scene/MapboxImageryProvider', '../../Scene/createOpenStreetMapImageryProvider', - '../../Scene/TileMapServiceImageryProvider', + '../../Scene/createTileMapServiceImageryProvider', '../BaseLayerPicker/ProviderViewModel' ], function( buildModuleUrl, @@ -15,7 +15,7 @@ define([ BingMapsStyle, MapboxImageryProvider, createOpenStreetMapImageryProvider, - TileMapServiceImageryProvider, + createTileMapServiceImageryProvider, ProviderViewModel) { "use strict"; @@ -198,7 +198,7 @@ map of the world.\nhttp://www.openstreetmap.org', tooltip : 'The lights of cities and villages trace the outlines of civilization in this global view of the \ Earth at night as seen by NASA/NOAA\'s Suomi NPP satellite.', creationFunction : function() { - return new TileMapServiceImageryProvider({ + return createTileMapServiceImageryProvider({ url : '//cesiumjs.org/blackmarble', maximumLevel : 8, credit : 'Black Marble imagery courtesy NASA Earth Observatory' @@ -211,7 +211,7 @@ Earth at night as seen by NASA/NOAA\'s Suomi NPP satellite.', iconUrl : buildModuleUrl('Widgets/Images/ImageryProviders/naturalEarthII.png'), tooltip : 'Natural Earth II, darkened for contrast.\nhttp://www.naturalearthdata.com/', creationFunction : function() { - return new TileMapServiceImageryProvider({ + return createTileMapServiceImageryProvider({ url : buildModuleUrl('Assets/Textures/NaturalEarthII') }); } @@ -221,4 +221,4 @@ Earth at night as seen by NASA/NOAA\'s Suomi NPP satellite.', } return createDefaultImageryProviderViewModels; -}); \ No newline at end of file +}); diff --git a/Specs/Scene/ImageryLayerSpec.js b/Specs/Scene/ImageryLayerSpec.js index 41b4a5dd38ef..e5668584a23c 100644 --- a/Specs/Scene/ImageryLayerSpec.js +++ b/Specs/Scene/ImageryLayerSpec.js @@ -17,7 +17,7 @@ defineSuite([ 'Scene/NeverTileDiscardPolicy', 'Scene/QuadtreeTile', 'Scene/SingleTileImageryProvider', - 'Scene/TileMapServiceImageryProvider', + 'Scene/createTileMapServiceImageryProvider', 'Scene/WebMapServiceImageryProvider', 'Specs/createContext', 'Specs/createFrameState', @@ -40,7 +40,7 @@ defineSuite([ NeverTileDiscardPolicy, QuadtreeTile, SingleTileImageryProvider, - TileMapServiceImageryProvider, + createTileMapServiceImageryProvider, WebMapServiceImageryProvider, createContext, createFrameState, @@ -239,7 +239,7 @@ defineSuite([ describe('createTileImagerySkeletons', function() { it('handles a base layer that does not cover the entire globe', function() { - var provider = new TileMapServiceImageryProvider({ + var provider = createTileMapServiceImageryProvider({ url : 'Data/TMS/SmallArea' }); @@ -288,7 +288,7 @@ defineSuite([ url : 'Data/Images/Blue.png' }); - var provider = new TileMapServiceImageryProvider({ + var provider = createTileMapServiceImageryProvider({ url : 'Data/TMS/SmallArea' }); @@ -334,7 +334,7 @@ defineSuite([ url : 'Data/Images/Green4x4.png' }); - var provider = new TileMapServiceImageryProvider({ + var provider = createTileMapServiceImageryProvider({ url : 'Data/TMS/SmallArea' }); diff --git a/Specs/Scene/UrlTemplateImageryProviderSpec.js b/Specs/Scene/UrlTemplateImageryProviderSpec.js index 03056bcb88a8..c7d32253855f 100644 --- a/Specs/Scene/UrlTemplateImageryProviderSpec.js +++ b/Specs/Scene/UrlTemplateImageryProviderSpec.js @@ -547,4 +547,76 @@ defineSuite([ expect(provider.pickFeatures(0, 0, 0, 0.0, 0.0)).toBeUndefined(); }); }); + + it('throws if tileWidth called before provider is ready', function() { + var provider = new UrlTemplateImageryProvider(when.defer()); + + expect(function() { + return provider.tileWidth(); + }).toThrowDeveloperError(); + }); + + it('throws if tileHeight called before provider is ready', function() { + var provider = new UrlTemplateImageryProvider(when.defer()); + + expect(function() { + return provider.tileHeight(); + }).toThrowDeveloperError(); + }); + + it('throws if maximumLevel called before provider is ready', function() { + var provider = new UrlTemplateImageryProvider(when.defer()); + + expect(function() { + return provider.maximumLevel(); + }).toThrowDeveloperError(); + }); + + it('throws if minimumLevel called before provider is ready', function() { + var provider = new UrlTemplateImageryProvider(when.defer()); + + expect(function() { + return provider.minimumLevel(); + }).toThrowDeveloperError(); + }); + + it('throws if tilingScheme called before provider is ready', function() { + var provider = new UrlTemplateImageryProvider(when.defer()); + + expect(function() { + return provider.tilingScheme(); + }).toThrowDeveloperError(); + }); + + it('throws if rectangle called before provider is ready', function() { + var provider = new UrlTemplateImageryProvider(when.defer()); + + expect(function() { + return provider.rectangle(); + }).toThrowDeveloperError(); + }); + + it('throws if tileDiscardPolicy called before provider is ready', function() { + var provider = new UrlTemplateImageryProvider(when.defer()); + + expect(function() { + return provider.tileDiscardPolicy(); + }).toThrowDeveloperError(); + }); + + it('throws if credit called before provider is ready', function() { + var provider = new UrlTemplateImageryProvider(when.defer()); + + expect(function() { + return provider.credit(); + }).toThrowDeveloperError(); + }); + + it('throws if hasAlphaChannel called before provider is ready', function() { + var provider = new UrlTemplateImageryProvider(when.defer()); + + expect(function() { + return provider.hasAlphaChannel(); + }).toThrowDeveloperError(); + }); }); diff --git a/Specs/Scene/createTileMapServiceImageryProviderSpec.js b/Specs/Scene/createTileMapServiceImageryProviderSpec.js new file mode 100644 index 000000000000..b9d2f42a015e --- /dev/null +++ b/Specs/Scene/createTileMapServiceImageryProviderSpec.js @@ -0,0 +1,709 @@ +/*global defineSuite*/ +defineSuite([ + 'Scene/createTileMapServiceImageryProvider', + 'Core/Cartesian2', + 'Core/Cartographic', + 'Core/DefaultProxy', + 'Core/defined', + 'Core/GeographicProjection', + 'Core/GeographicTilingScheme', + 'Core/loadImage', + 'Core/loadWithXhr', + 'Core/Math', + 'Core/Rectangle', + 'Core/WebMercatorProjection', + 'Core/WebMercatorTilingScheme', + 'Scene/Imagery', + 'Scene/ImageryLayer', + 'Scene/UrlTemplateImageryProvider', + 'Scene/ImageryState', + 'Specs/pollToPromise', + 'ThirdParty/when' + ], function( + createTileMapServiceImageryProvider, + Cartesian2, + Cartographic, + DefaultProxy, + defined, + GeographicProjection, + GeographicTilingScheme, + loadImage, + loadWithXhr, + CesiumMath, + Rectangle, + WebMercatorProjection, + WebMercatorTilingScheme, + Imagery, + ImageryLayer, + UrlTemplateImageryProvider, + ImageryState, + pollToPromise, + when) { + "use strict"; + + afterEach(function() { + loadImage.createImage = loadImage.defaultCreateImage; + loadWithXhr.load = loadWithXhr.defaultLoad; + }); + + it('return a UrlTemplateImageryProvider', function() { + var provider = createTileMapServiceImageryProvider({ + url: 'made/up/tms/server/' + }); + expect(provider).toBeInstanceOf(UrlTemplateImageryProvider); + }); + + it('resolves readyPromise', function() { + var provider = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server/' + }); + + return provider.readyPromise.then(function(result) { + expect(result).toBe(true); + expect(provider.ready).toBe(true); + }); + }); + + it('rejects readyPromise on error', function() { + loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + // We can't resolve the promise immediately, because then the error would be raised + // before we could subscribe to it. This a problem particular to tests. + setTimeout(function() { + var parser = new DOMParser(); + var xmlString = + '' + + ' ' + + ' <Abstract/>' + + ' <SRS>EPSG:4326</SRS>' + + ' <BoundingBox minx="-10.0" miny="-123.0" maxx="11.0" maxy="-110.0"/>' + + ' <Origin x="-90.0" y="-180.0"/>' + + ' <TileFormat width="256" height="256" mime-type="image/png" extension="png"/>' + + ' <TileSets profile="foobar">' + + ' <TileSet href="2" units-per-pixel="39135.75848201024200" order="2"/>' + + ' <TileSet href="3" units-per-pixel="19567.87924100512100" order="3"/>' + + ' </TileSets>' + + '</TileMap>'; + var xml = parser.parseFromString(xmlString, "text/xml"); + deferred.resolve(xml); + }, 1); + }; + + var provider = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server' + }); + + return provider.readyPromise.then(function() { + fail('should not resolve'); + }).otherwise(function (e) { + expect(provider.ready).toBe(false); + expect(e.message).toContain('unsupported profile'); + }); + }); + + it('requires the url to be specified', function() { + function createWithoutUrl() { + return createTileMapServiceImageryProvider({}); + } + expect(createWithoutUrl).toThrowDeveloperError(); + }); + + it('returns valid value for hasAlphaChannel', function() { + var provider = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server/' + }); + + 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 = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server/' + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + spyOn(loadImage, 'createImage').and.callFake(function(url, crossOrigin, deferred) { + expect(url).not.toContain('//'); + + // Just return any old image. + loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred); + }); + + return provider.requestImage(0, 0, 0).then(function(image) { + expect(loadImage.createImage).toHaveBeenCalled(); + expect(image).toBeInstanceOf(Image); + }); + }); + }); + + it('supports no slash at the endof the URL', function() { + var provider = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server' + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + spyOn(loadImage, 'createImage').and.callFake(function(url, crossOrigin, deferred) { + expect(url).toContain('made/up/tms/server/'); + + // Just return any old image. + loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred); + }); + + return provider.requestImage(0, 0, 0).then(function(image) { + expect(loadImage.createImage).toHaveBeenCalled(); + expect(image).toBeInstanceOf(Image); + }); + }); + }); + + it('supports a query string at the end of the URL', function() { + var provider = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server/?a=some&b=query' + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + spyOn(loadImage, 'createImage').and.callFake(function(url, crossOrigin, deferred) { + expect(url).not.toContain('//'); + + // Just return any old image. + loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred); + }); + + return provider.requestImage(0, 0, 0).then(function(image) { + expect(loadImage.createImage).toHaveBeenCalled(); + expect(image).toBeInstanceOf(Image); + }); + }); + }); + + it('requestImage returns a promise for an image and loads it for cross-origin use', function() { + var provider = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server/' + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + expect(provider.url).toEqual('made/up/tms/server/{z}/{x}/{reverseY}.png'); + 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(loadImage, 'createImage').and.callFake(function(url, crossOrigin, deferred) { + // Just return any old image. + loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred); + }); + + return provider.requestImage(0, 0, 0).then(function(image) { + expect(loadImage.createImage).toHaveBeenCalled(); + expect(image).toBeInstanceOf(Image); + }); + }); + }); + + it('when no credit is supplied, the provider has no logo', function() { + var provider = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server' + }); + return pollToPromise(function() { + return provider.ready; + }).then(function() { + expect(provider.credit).toBeUndefined(); + }); + }); + + it('turns the supplied credit into a logo', function() { + var providerWithCredit = createTileMapServiceImageryProvider({ + url : 'made/up/gms/server', + credit : 'Thanks to our awesome made up source of this imagery!' + }); + return pollToPromise(function() { + return providerWithCredit.ready; + }).then(function() { + expect(providerWithCredit.credit).toBeDefined(); + }); + }); + + it('routes resource request through a proxy if one is specified', function() { + /*jshint unused: false*/ + var proxy = new DefaultProxy('/proxy/'); + var requestMetadata = when.defer(); + spyOn(loadWithXhr, 'load').and.callFake(function(url, responseType, method, data, headers, deferred, overrideMimeType) { + requestMetadata.resolve(url); + deferred.reject(); //since the TMS server doesn't exist (and doesn't need too) we can just reject here. + }); + + var provider = createTileMapServiceImageryProvider({ + url : 'server.invalid', + proxy : proxy + }); + + return requestMetadata.promise.then(function(url) { + expect(url.indexOf(proxy.getURL('server.invalid'))).toEqual(0); + }); + }); + + it('routes tile requests through a proxy if one is specified', function() { + var proxy = new DefaultProxy('/proxy/'); + var provider = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server', + proxy : proxy + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + expect(provider.proxy).toEqual(proxy); + + spyOn(loadImage, 'createImage').and.callFake(function(url, crossOrigin, deferred) { + expect(url.indexOf(proxy.getURL('made/up/tms/server'))).toEqual(0); + + // Just return any old image. + loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred); + }); + + return provider.requestImage(0, 0, 0).then(function(image) { + expect(loadImage.createImage).toHaveBeenCalled(); + expect(image).toBeInstanceOf(Image); + }); + }); + }); + + 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 = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server', + 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.west).toEqualEpsilon(rectangle.west, CesiumMath.EPSILON14); + expect(provider.rectangle.east).toEqualEpsilon(rectangle.east, CesiumMath.EPSILON14); + expect(provider.rectangle.north).toEqualEpsilon(rectangle.north, CesiumMath.EPSILON14); + expect(provider.rectangle.south).toEqualEpsilon(rectangle.south, CesiumMath.EPSILON14); + expect(provider.tileDiscardPolicy).toBeUndefined(); + + spyOn(loadImage, 'createImage').and.callFake(function(url, crossOrigin, deferred) { + expect(url).toContain('/0/0/0'); + + // Just return any old image. + loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred); + }); + + return provider.requestImage(0, 0, 0).then(function(image) { + expect(loadImage.createImage).toHaveBeenCalled(); + expect(image).toBeInstanceOf(Image); + }); + }); + }); + + it('uses maximumLevel passed to constructor', function() { + var provider = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server', + maximumLevel : 5 + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + expect(provider.maximumLevel).toEqual(5); + }); + }); + + it('raises error event when image cannot be loaded', function() { + var provider = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server' + }); + + 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; + } + }); + + loadImage.createImage = function(url, crossOrigin, deferred) { + if (tries === 2) { + // Succeed after 2 tries + loadImage.defaultCreateImage('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); + + return pollToPromise(function() { + return imagery.state === ImageryState.RECEIVED; + }).then(function() { + expect(imagery.image).toBeInstanceOf(Image); + expect(tries).toEqual(2); + imagery.releaseReference(); + }); + }); + }); + + it('keeps the rectangle within the bounds allowed by the tiling scheme no matter what the tilemapresource.xml says.', function() { + loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + var parser = new DOMParser(); + var xmlString = + "<TileMap version='1.0.0' tilemapservice='http://tms.osgeo.org/1.0.0'>" + + " <Title>dnb_land_ocean_ice.2012.54000x27000_geo.tif" + + " " + + " EPSG:900913" + + " " + + " " + + " " + + " " + + " " + + " " + + ""; + var xml = parser.parseFromString(xmlString, "text/xml"); + deferred.resolve(xml); + }; + + var provider = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server' + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + expect(provider.rectangle.west).toEqualEpsilon(CesiumMath.toRadians(-180.0), CesiumMath.EPSILON14); + expect(provider.rectangle.west).toBeGreaterThanOrEqualTo(provider.tilingScheme.rectangle.west); + expect(provider.rectangle.east).toEqualEpsilon(CesiumMath.toRadians(180.0), CesiumMath.EPSILON14); + expect(provider.rectangle.east).toBeLessThanOrEqualTo(provider.tilingScheme.rectangle.east); + expect(provider.rectangle.south).toEqualEpsilon(-WebMercatorProjection.MaximumLatitude, CesiumMath.EPSILON14); + expect(provider.rectangle.south).toBeGreaterThanOrEqualTo(provider.tilingScheme.rectangle.south); + expect(provider.rectangle.north).toEqualEpsilon(WebMercatorProjection.MaximumLatitude, CesiumMath.EPSILON14); + expect(provider.rectangle.north).toBeLessThanOrEqualTo(provider.tilingScheme.rectangle.north); + }); + }); + + it('uses a minimum level if the tilemapresource.xml specifies one and it is reasonable', function() { + loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + var parser = new DOMParser(); + var xmlString = + "" + + " dnb_land_ocean_ice.2012.54000x27000_geo.tif" + + " " + + " EPSG:900913" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""; + var xml = parser.parseFromString(xmlString, "text/xml"); + deferred.resolve(xml); + }; + + var provider = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server' + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + expect(provider.maximumLevel).toBe(8); + expect(provider.minimumLevel).toBe(7); + }); + }); + + it('ignores the minimum level in the tilemapresource.xml if it is unreasonable', function() { + loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + var parser = new DOMParser(); + var xmlString = + "" + + " dnb_land_ocean_ice.2012.54000x27000_geo.tif" + + " " + + " EPSG:900913" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""; + var xml = parser.parseFromString(xmlString, "text/xml"); + deferred.resolve(xml); + }; + + var provider = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server' + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + expect(provider.maximumLevel).toBe(8); + expect(provider.minimumLevel).toBe(0); + }); + }); + + it('handles XML with casing differences', function() { + loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + var parser = new DOMParser(); + var xmlString = + "" + + " dnb_land_ocean_ice.2012.54000x27000_geo.tif" + + " " + + " EPSG:900913" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + ""; + var xml = parser.parseFromString(xmlString, "text/xml"); + deferred.resolve(xml); + }; + + var provider = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server' + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + expect(provider.maximumLevel).toBe(8); + expect(provider.minimumLevel).toBe(7); + }); + }); + + it('supports the global-mercator profile with a non-flipped, mercator bounding box', function() { + loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + var parser = new DOMParser(); + var xmlString = + '' + + ' ' + + ' <Abstract/>' + + ' <SRS>EPSG:900913</SRS>' + + ' <BoundingBox minx="-11877789.66764229300000" miny="1707163.75952051670000" maxx="-4696205.45407573510000" maxy="7952627.07365330120000"/>' + + ' <Origin x="-20037508.34278924400000" y="-20037508.34278924400000"/>' + + ' <TileFormat width="256" height="256" mime-type="image/png" extension="png"/>' + + ' <TileSets profile="global-mercator">' + + ' <TileSet href="2" units-per-pixel="39135.75848201024200" order="2"/>' + + ' <TileSet href="3" units-per-pixel="19567.87924100512100" order="3"/>' + + ' </TileSets>' + + '</TileMap>'; + var xml = parser.parseFromString(xmlString, "text/xml"); + deferred.resolve(xml); + }; + + var provider = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server' + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + expect(provider.tilingScheme).toBeInstanceOf(WebMercatorTilingScheme); + expect(provider.tilingScheme.projection).toBeInstanceOf(WebMercatorProjection); + + var projection = provider.tilingScheme.projection; + var expectedSW = projection.unproject(new Cartesian2(-11877789.66764229300000, 1707163.75952051670000)); + var expectedNE = projection.unproject(new Cartesian2(-4696205.45407573510000, 7952627.07365330120000)); + + expect(provider.rectangle.west).toEqual(expectedSW.longitude); + expect(provider.rectangle.south).toEqual(expectedSW.latitude); + expect(provider.rectangle.east).toBeCloseTo(expectedNE.longitude, CesiumMath.EPSILON14); + expect(provider.rectangle.north).toEqual(expectedNE.latitude); + }); + }); + + it('supports the global-geodetic profile with a non-flipped, geographic bounding box', function() { + loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + var parser = new DOMParser(); + var xmlString = + '<TileMap version="1.0.0" tilemapservice="http://tms.osgeo.org/1.0.0">' + + ' <Title/>' + + ' <Abstract/>' + + ' <SRS>EPSG:4326</SRS>' + + ' <BoundingBox minx="-123.0" miny="-10.0" maxx="-110.0" maxy="11.0"/>' + + ' <Origin x="-180.0" y="-90.0"/>' + + ' <TileFormat width="256" height="256" mime-type="image/png" extension="png"/>' + + ' <TileSets profile="global-geodetic">' + + ' <TileSet href="2" units-per-pixel="39135.75848201024200" order="2"/>' + + ' <TileSet href="3" units-per-pixel="19567.87924100512100" order="3"/>' + + ' </TileSets>' + + '</TileMap>'; + var xml = parser.parseFromString(xmlString, "text/xml"); + deferred.resolve(xml); + }; + + var provider = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server' + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + expect(provider.tilingScheme).toBeInstanceOf(GeographicTilingScheme); + expect(provider.tilingScheme.projection).toBeInstanceOf(GeographicProjection); + + var expectedSW = Cartographic.fromDegrees(-123.0, -10.0); + var expectedNE = Cartographic.fromDegrees(-110.0, 11.0); + + expect(provider.rectangle.west).toBeCloseTo(expectedSW.longitude, CesiumMath.EPSILON14); + expect(provider.rectangle.south).toEqual(expectedSW.latitude); + expect(provider.rectangle.east).toBeCloseTo(expectedNE.longitude, CesiumMath.EPSILON14); + expect(provider.rectangle.north).toEqual(expectedNE.latitude); + }); + }); + + it('supports the old mercator profile with a flipped, geographic bounding box', function() { + loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + var parser = new DOMParser(); + var xmlString = + '<TileMap version="1.0.0" tilemapservice="http://tms.osgeo.org/1.0.0">' + + ' <Title/>' + + ' <Abstract/>' + + ' <SRS>EPSG:900913</SRS>' + + ' <BoundingBox minx="-10.0" miny="-123.0" maxx="11.0" maxy="-110.0"/>' + + ' <Origin x="-90.0" y="-180.0"/>' + + ' <TileFormat width="256" height="256" mime-type="image/png" extension="png"/>' + + ' <TileSets profile="mercator">' + + ' <TileSet href="2" units-per-pixel="39135.75848201024200" order="2"/>' + + ' <TileSet href="3" units-per-pixel="19567.87924100512100" order="3"/>' + + ' </TileSets>' + + '</TileMap>'; + var xml = parser.parseFromString(xmlString, "text/xml"); + deferred.resolve(xml); + }; + + var provider = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server' + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + expect(provider.tilingScheme).toBeInstanceOf(WebMercatorTilingScheme); + expect(provider.tilingScheme.projection).toBeInstanceOf(WebMercatorProjection); + + var expectedSW = Cartographic.fromDegrees(-123.0, -10.0); + var expectedNE = Cartographic.fromDegrees(-110.0, 11.0); + + expect(provider.rectangle.west).toBeCloseTo(expectedSW.longitude, CesiumMath.EPSILON14); + expect(provider.rectangle.south).toEqual(expectedSW.latitude); + expect(provider.rectangle.east).toBeCloseTo(expectedNE.longitude, CesiumMath.EPSILON14); + expect(provider.rectangle.north).toEqual(expectedNE.latitude); + }); + }); + + it('supports the old geodetic profile with a flipped, geographic bounding box', function() { + loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + var parser = new DOMParser(); + var xmlString = + '<TileMap version="1.0.0" tilemapservice="http://tms.osgeo.org/1.0.0">' + + ' <Title/>' + + ' <Abstract/>' + + ' <SRS>EPSG:4326</SRS>' + + ' <BoundingBox minx="-10.0" miny="-123.0" maxx="11.0" maxy="-110.0"/>' + + ' <Origin x="-90.0" y="-180.0"/>' + + ' <TileFormat width="256" height="256" mime-type="image/png" extension="png"/>' + + ' <TileSets profile="geodetic">' + + ' <TileSet href="2" units-per-pixel="39135.75848201024200" order="2"/>' + + ' <TileSet href="3" units-per-pixel="19567.87924100512100" order="3"/>' + + ' </TileSets>' + + '</TileMap>'; + var xml = parser.parseFromString(xmlString, "text/xml"); + deferred.resolve(xml); + }; + + var provider = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server' + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + expect(provider.tilingScheme).toBeInstanceOf(GeographicTilingScheme); + expect(provider.tilingScheme.projection).toBeInstanceOf(GeographicProjection); + + var expectedSW = Cartographic.fromDegrees(-123.0, -10.0); + var expectedNE = Cartographic.fromDegrees(-110.0, 11.0); + + expect(provider.rectangle.west).toBeCloseTo(expectedSW.longitude, CesiumMath.EPSILON14); + expect(provider.rectangle.south).toEqual(expectedSW.latitude); + expect(provider.rectangle.east).toBeCloseTo(expectedNE.longitude, CesiumMath.EPSILON14); + expect(provider.rectangle.north).toEqual(expectedNE.latitude); + }); + }); + + it('raises an error if tilemapresource.xml specifies an unsupported profile', function() { + loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + // We can't resolve the promise immediately, because then the error would be raised + // before we could subscribe to it. This a problem particular to tests. + setTimeout(function() { + var parser = new DOMParser(); + var xmlString = + '<TileMap version="1.0.0" tilemapservice="http://tms.osgeo.org/1.0.0">' + + ' <Title/>' + + ' <Abstract/>' + + ' <SRS>EPSG:4326</SRS>' + + ' <BoundingBox minx="-10.0" miny="-123.0" maxx="11.0" maxy="-110.0"/>' + + ' <Origin x="-90.0" y="-180.0"/>' + + ' <TileFormat width="256" height="256" mime-type="image/png" extension="png"/>' + + ' <TileSets profile="foobar">' + + ' <TileSet href="2" units-per-pixel="39135.75848201024200" order="2"/>' + + ' <TileSet href="3" units-per-pixel="19567.87924100512100" order="3"/>' + + ' </TileSets>' + + '</TileMap>'; + var xml = parser.parseFromString(xmlString, "text/xml"); + deferred.resolve(xml); + }, 1); + }; + + var provider = createTileMapServiceImageryProvider({ + url : 'made/up/tms/server' + }); + + var errorRaised = false; + provider.errorEvent.addEventListener(function(e) { + expect(e.message).toContain('unsupported profile'); + errorRaised = true; + }); + + return pollToPromise(function() { + return errorRaised; + }).then(function() { + expect(errorRaised).toBe(true); + }); + }); +});