UrlTemplateImageryProvider.js 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959
  1. import Cartesian2 from '../Core/Cartesian2.js';
  2. import Cartesian3 from '../Core/Cartesian3.js';
  3. import Cartographic from '../Core/Cartographic.js';
  4. import combine from '../Core/combine.js';
  5. import Credit from '../Core/Credit.js';
  6. import defaultValue from '../Core/defaultValue.js';
  7. import defined from '../Core/defined.js';
  8. import defineProperties from '../Core/defineProperties.js';
  9. import DeveloperError from '../Core/DeveloperError.js';
  10. import Event from '../Core/Event.js';
  11. import GeographicProjection from '../Core/GeographicProjection.js';
  12. import isArray from '../Core/isArray.js';
  13. import CesiumMath from '../Core/Math.js';
  14. import Rectangle from '../Core/Rectangle.js';
  15. import Resource from '../Core/Resource.js';
  16. import WebMercatorTilingScheme from '../Core/WebMercatorTilingScheme.js';
  17. import when from '../ThirdParty/when.js';
  18. import ImageryProvider from './ImageryProvider.js';
  19. var templateRegex = /{[^}]+}/g;
  20. var tags = {
  21. 'x': xTag,
  22. 'y': yTag,
  23. 'z': zTag,
  24. 's': sTag,
  25. 'reverseX': reverseXTag,
  26. 'reverseY': reverseYTag,
  27. 'reverseZ': reverseZTag,
  28. 'westDegrees': westDegreesTag,
  29. 'southDegrees': southDegreesTag,
  30. 'eastDegrees': eastDegreesTag,
  31. 'northDegrees': northDegreesTag,
  32. 'westProjected': westProjectedTag,
  33. 'southProjected': southProjectedTag,
  34. 'eastProjected': eastProjectedTag,
  35. 'northProjected': northProjectedTag,
  36. 'width': widthTag,
  37. 'height': heightTag
  38. };
  39. var pickFeaturesTags = combine(tags, {
  40. 'i' : iTag,
  41. 'j' : jTag,
  42. 'reverseI' : reverseITag,
  43. 'reverseJ' : reverseJTag,
  44. 'longitudeDegrees' : longitudeDegreesTag,
  45. 'latitudeDegrees' : latitudeDegreesTag,
  46. 'longitudeProjected' : longitudeProjectedTag,
  47. 'latitudeProjected' : latitudeProjectedTag,
  48. 'format' : formatTag
  49. });
  50. /**
  51. * Provides imagery by requesting tiles using a specified URL template.
  52. *
  53. * @alias UrlTemplateImageryProvider
  54. * @constructor
  55. *
  56. * @param {Promise.<Object>|Object} [options] Object with the following properties:
  57. * @param {Resource|String} options.url The URL template to use to request tiles. It has the following keywords:
  58. * <ul>
  59. * <li><code>{z}</code>: The level of the tile in the tiling scheme. Level zero is the root of the quadtree pyramid.</li>
  60. * <li><code>{x}</code>: The tile X coordinate in the tiling scheme, where 0 is the Westernmost tile.</li>
  61. * <li><code>{y}</code>: The tile Y coordinate in the tiling scheme, where 0 is the Northernmost tile.</li>
  62. * <li><code>{s}</code>: One of the available subdomains, used to overcome browser limits on the number of simultaneous requests per host.</li>
  63. * <li><code>{reverseX}</code>: The tile X coordinate in the tiling scheme, where 0 is the Easternmost tile.</li>
  64. * <li><code>{reverseY}</code>: The tile Y coordinate in the tiling scheme, where 0 is the Southernmost tile.</li>
  65. * <li><code>{reverseZ}</code>: The level of the tile in the tiling scheme, where level zero is the maximum level of the quadtree pyramid. In order to use reverseZ, maximumLevel must be defined.</li>
  66. * <li><code>{westDegrees}</code>: The Western edge of the tile in geodetic degrees.</li>
  67. * <li><code>{southDegrees}</code>: The Southern edge of the tile in geodetic degrees.</li>
  68. * <li><code>{eastDegrees}</code>: The Eastern edge of the tile in geodetic degrees.</li>
  69. * <li><code>{northDegrees}</code>: The Northern edge of the tile in geodetic degrees.</li>
  70. * <li><code>{westProjected}</code>: The Western edge of the tile in projected coordinates of the tiling scheme.</li>
  71. * <li><code>{southProjected}</code>: The Southern edge of the tile in projected coordinates of the tiling scheme.</li>
  72. * <li><code>{eastProjected}</code>: The Eastern edge of the tile in projected coordinates of the tiling scheme.</li>
  73. * <li><code>{northProjected}</code>: The Northern edge of the tile in projected coordinates of the tiling scheme.</li>
  74. * <li><code>{width}</code>: The width of each tile in pixels.</li>
  75. * <li><code>{height}</code>: The height of each tile in pixels.</li>
  76. * </ul>
  77. * @param {Resource|String} [options.pickFeaturesUrl] The URL template to use to pick features. If this property is not specified,
  78. * {@link UrlTemplateImageryProvider#pickFeatures} will immediately returned undefined, indicating no
  79. * features picked. The URL template supports all of the keywords supported by the <code>url</code>
  80. * parameter, plus the following:
  81. * <ul>
  82. * <li><code>{i}</code>: The pixel column (horizontal coordinate) of the picked position, where the Westernmost pixel is 0.</li>
  83. * <li><code>{j}</code>: The pixel row (vertical coordinate) of the picked position, where the Northernmost pixel is 0.</li>
  84. * <li><code>{reverseI}</code>: The pixel column (horizontal coordinate) of the picked position, where the Easternmost pixel is 0.</li>
  85. * <li><code>{reverseJ}</code>: The pixel row (vertical coordinate) of the picked position, where the Southernmost pixel is 0.</li>
  86. * <li><code>{longitudeDegrees}</code>: The longitude of the picked position in degrees.</li>
  87. * <li><code>{latitudeDegrees}</code>: The latitude of the picked position in degrees.</li>
  88. * <li><code>{longitudeProjected}</code>: The longitude of the picked position in the projected coordinates of the tiling scheme.</li>
  89. * <li><code>{latitudeProjected}</code>: The latitude of the picked position in the projected coordinates of the tiling scheme.</li>
  90. * <li><code>{format}</code>: The format in which to get feature information, as specified in the {@link GetFeatureInfoFormat}.</li>
  91. * </ul>
  92. * @param {Object} [options.urlSchemeZeroPadding] Gets the URL scheme zero padding for each tile coordinate. The format is '000' where
  93. * each coordinate will be padded on the left with zeros to match the width of the passed string of zeros. e.g. Setting:
  94. * urlSchemeZeroPadding : { '{x}' : '0000'}
  95. * will cause an 'x' value of 12 to return the string '0012' for {x} in the generated URL.
  96. * It the passed object has the following keywords:
  97. * <ul>
  98. * <li> <code>{z}</code>: The zero padding for the level of the tile in the tiling scheme.</li>
  99. * <li> <code>{x}</code>: The zero padding for the tile X coordinate in the tiling scheme.</li>
  100. * <li> <code>{y}</code>: The zero padding for the the tile Y coordinate in the tiling scheme.</li>
  101. * <li> <code>{reverseX}</code>: The zero padding for the tile reverseX coordinate in the tiling scheme.</li>
  102. * <li> <code>{reverseY}</code>: The zero padding for the tile reverseY coordinate in the tiling scheme.</li>
  103. * <li> <code>{reverseZ}</code>: The zero padding for the reverseZ coordinate of the tile in the tiling scheme.</li>
  104. * </ul>
  105. * @param {String|String[]} [options.subdomains='abc'] The subdomains to use for the <code>{s}</code> placeholder in the URL template.
  106. * If this parameter is a single string, each character in the string is a subdomain. If it is
  107. * an array, each element in the array is a subdomain.
  108. * @param {Credit|String} [options.credit=''] A credit for the data source, which is displayed on the canvas.
  109. * @param {Number} [options.minimumLevel=0] The minimum level-of-detail supported by the imagery provider. Take care when specifying
  110. * this that the number of tiles at the minimum level is small, such as four or less. A larger number is likely
  111. * to result in rendering problems.
  112. * @param {Number} [options.maximumLevel] The maximum level-of-detail supported by the imagery provider, or undefined if there is no limit.
  113. * @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the image.
  114. * @param {TilingScheme} [options.tilingScheme=WebMercatorTilingScheme] The tiling scheme specifying how the ellipsoidal
  115. * surface is broken into tiles. If this parameter is not provided, a {@link WebMercatorTilingScheme}
  116. * is used.
  117. * @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If the tilingScheme is specified,
  118. * this parameter is ignored and the tiling scheme's ellipsoid is used instead. If neither
  119. * parameter is specified, the WGS84 ellipsoid is used.
  120. * @param {Number} [options.tileWidth=256] Pixel width of image tiles.
  121. * @param {Number} [options.tileHeight=256] Pixel height of image tiles.
  122. * @param {Boolean} [options.hasAlphaChannel=true] true if the images provided by this imagery provider
  123. * include an alpha channel; otherwise, false. If this property is false, an alpha channel, if
  124. * present, will be ignored. If this property is true, any images without an alpha channel will
  125. * be treated as if their alpha is 1.0 everywhere. When this property is false, memory usage
  126. * and texture upload time are potentially reduced.
  127. * @param {GetFeatureInfoFormat[]} [options.getFeatureInfoFormats] The formats in which to get feature information at a
  128. * specific location when {@link UrlTemplateImageryProvider#pickFeatures} is invoked. If this
  129. * parameter is not specified, feature picking is disabled.
  130. * @param {Boolean} [options.enablePickFeatures=true] If true, {@link UrlTemplateImageryProvider#pickFeatures} will
  131. * request the <code>options.pickFeaturesUrl</code> and attempt to interpret the features included in the response. If false,
  132. * {@link UrlTemplateImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable
  133. * features) without communicating with the server. Set this property to false if you know your data
  134. * source does not support picking features or if you don't want this provider's features to be pickable. Note
  135. * that this can be dynamically overridden by modifying the {@link UriTemplateImageryProvider#enablePickFeatures}
  136. * property.
  137. * @param {Object} [options.customTags] Allow to replace custom keywords in the URL template. The object must have strings as keys and functions as values.
  138. *
  139. *
  140. * @example
  141. * // Access Natural Earth II imagery, which uses a TMS tiling scheme and Geographic (EPSG:4326) project
  142. * var tms = new Cesium.UrlTemplateImageryProvider({
  143. * url : Cesium.buildModuleUrl('Assets/Textures/NaturalEarthII') + '/{z}/{x}/{reverseY}.jpg',
  144. * credit : '© Analytical Graphics, Inc.',
  145. * tilingScheme : new Cesium.GeographicTilingScheme(),
  146. * maximumLevel : 5
  147. * });
  148. * // Access the CartoDB Positron basemap, which uses an OpenStreetMap-like tiling scheme.
  149. * var positron = new Cesium.UrlTemplateImageryProvider({
  150. * url : 'http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png',
  151. * credit : 'Map tiles by CartoDB, under CC BY 3.0. Data by OpenStreetMap, under ODbL.'
  152. * });
  153. * // Access a Web Map Service (WMS) server.
  154. * var wms = new Cesium.UrlTemplateImageryProvider({
  155. * url : 'https://programs.communications.gov.au/geoserver/ows?tiled=true&' +
  156. * 'transparent=true&format=image%2Fpng&exceptions=application%2Fvnd.ogc.se_xml&' +
  157. * 'styles=&service=WMS&version=1.1.1&request=GetMap&' +
  158. * 'layers=public%3AMyBroadband_Availability&srs=EPSG%3A3857&' +
  159. * 'bbox={westProjected}%2C{southProjected}%2C{eastProjected}%2C{northProjected}&' +
  160. * 'width=256&height=256',
  161. * rectangle : Cesium.Rectangle.fromDegrees(96.799393, -43.598214999057824, 153.63925700000001, -9.2159219997013)
  162. * });
  163. * // Using custom tags in your template url.
  164. * var custom = new Cesium.UrlTemplateImageryProvider({
  165. * url : 'https://yoururl/{Time}/{z}/{y}/{x}.png',
  166. * customTags : {
  167. * Time: function(imageryProvider, x, y, level) {
  168. * return '20171231'
  169. * }
  170. * }
  171. * });
  172. *
  173. * @see ArcGisMapServerImageryProvider
  174. * @see BingMapsImageryProvider
  175. * @see GoogleEarthEnterpriseMapsProvider
  176. * @see OpenStreetMapImageryProvider
  177. * @see SingleTileImageryProvider
  178. * @see TileMapServiceImageryProvider
  179. * @see WebMapServiceImageryProvider
  180. * @see WebMapTileServiceImageryProvider
  181. */
  182. function UrlTemplateImageryProvider(options) {
  183. //>>includeStart('debug', pragmas.debug);
  184. if (!defined(options)) {
  185. throw new DeveloperError('options is required.');
  186. }
  187. if (!when.isPromise(options) && !defined(options.url)) {
  188. throw new DeveloperError('options is required.');
  189. }
  190. //>>includeEnd('debug');
  191. this._errorEvent = new Event();
  192. this._resource = undefined;
  193. this._urlSchemeZeroPadding = undefined;
  194. this._pickFeaturesResource = undefined;
  195. this._tileWidth = undefined;
  196. this._tileHeight = undefined;
  197. this._maximumLevel = undefined;
  198. this._minimumLevel = undefined;
  199. this._tilingScheme = undefined;
  200. this._rectangle = undefined;
  201. this._tileDiscardPolicy = undefined;
  202. this._credit = undefined;
  203. this._hasAlphaChannel = undefined;
  204. this._readyPromise = undefined;
  205. this._tags = undefined;
  206. this._pickFeaturesTags = undefined;
  207. /**
  208. * Gets or sets a value indicating whether feature picking is enabled. If true, {@link UrlTemplateImageryProvider#pickFeatures} will
  209. * request the <code>options.pickFeaturesUrl</code> and attempt to interpret the features included in the response. If false,
  210. * {@link UrlTemplateImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable
  211. * features) without communicating with the server. Set this property to false if you know your data
  212. * source does not support picking features or if you don't want this provider's features to be pickable.
  213. * @type {Boolean}
  214. * @default true
  215. */
  216. this.enablePickFeatures = true;
  217. this.reinitialize(options);
  218. }
  219. defineProperties(UrlTemplateImageryProvider.prototype, {
  220. /**
  221. * Gets the URL template to use to request tiles. It has the following keywords:
  222. * <ul>
  223. * <li> <code>{z}</code>: The level of the tile in the tiling scheme. Level zero is the root of the quadtree pyramid.</li>
  224. * <li> <code>{x}</code>: The tile X coordinate in the tiling scheme, where 0 is the Westernmost tile.</li>
  225. * <li> <code>{y}</code>: The tile Y coordinate in the tiling scheme, where 0 is the Northernmost tile.</li>
  226. * <li> <code>{s}</code>: One of the available subdomains, used to overcome browser limits on the number of simultaneous requests per host.</li>
  227. * <li> <code>{reverseX}</code>: The tile X coordinate in the tiling scheme, where 0 is the Easternmost tile.</li>
  228. * <li> <code>{reverseY}</code>: The tile Y coordinate in the tiling scheme, where 0 is the Southernmost tile.</li>
  229. * <li> <code>{reverseZ}</code>: The level of the tile in the tiling scheme, where level zero is the maximum level of the quadtree pyramid. In order to use reverseZ, maximumLevel must be defined.</li>
  230. * <li> <code>{westDegrees}</code>: The Western edge of the tile in geodetic degrees.</li>
  231. * <li> <code>{southDegrees}</code>: The Southern edge of the tile in geodetic degrees.</li>
  232. * <li> <code>{eastDegrees}</code>: The Eastern edge of the tile in geodetic degrees.</li>
  233. * <li> <code>{northDegrees}</code>: The Northern edge of the tile in geodetic degrees.</li>
  234. * <li> <code>{westProjected}</code>: The Western edge of the tile in projected coordinates of the tiling scheme.</li>
  235. * <li> <code>{southProjected}</code>: The Southern edge of the tile in projected coordinates of the tiling scheme.</li>
  236. * <li> <code>{eastProjected}</code>: The Eastern edge of the tile in projected coordinates of the tiling scheme.</li>
  237. * <li> <code>{northProjected}</code>: The Northern edge of the tile in projected coordinates of the tiling scheme.</li>
  238. * <li> <code>{width}</code>: The width of each tile in pixels.</li>
  239. * <li> <code>{height}</code>: The height of each tile in pixels.</li>
  240. * </ul>
  241. * @memberof UrlTemplateImageryProvider.prototype
  242. * @type {String}
  243. * @readonly
  244. */
  245. url : {
  246. get : function() {
  247. return this._resource.url;
  248. }
  249. },
  250. /**
  251. * Gets the URL scheme zero padding for each tile coordinate. The format is '000' where each coordinate will be padded on
  252. * the left with zeros to match the width of the passed string of zeros. e.g. Setting:
  253. * urlSchemeZeroPadding : { '{x}' : '0000'}
  254. * will cause an 'x' value of 12 to return the string '0012' for {x} in the generated URL.
  255. * It has the following keywords:
  256. * <ul>
  257. * <li> <code>{z}</code>: The zero padding for the level of the tile in the tiling scheme.</li>
  258. * <li> <code>{x}</code>: The zero padding for the tile X coordinate in the tiling scheme.</li>
  259. * <li> <code>{y}</code>: The zero padding for the the tile Y coordinate in the tiling scheme.</li>
  260. * <li> <code>{reverseX}</code>: The zero padding for the tile reverseX coordinate in the tiling scheme.</li>
  261. * <li> <code>{reverseY}</code>: The zero padding for the tile reverseY coordinate in the tiling scheme.</li>
  262. * <li> <code>{reverseZ}</code>: The zero padding for the reverseZ coordinate of the tile in the tiling scheme.</li>
  263. * </ul>
  264. * @memberof UrlTemplateImageryProvider.prototype
  265. * @type {Object}
  266. * @readonly
  267. */
  268. urlSchemeZeroPadding : {
  269. get : function() {
  270. return this._urlSchemeZeroPadding;
  271. }
  272. },
  273. /**
  274. * Gets the URL template to use to use to pick features. If this property is not specified,
  275. * {@link UrlTemplateImageryProvider#pickFeatures} will immediately returned undefined, indicating no
  276. * features picked. The URL template supports all of the keywords supported by the
  277. * {@link UrlTemplateImageryProvider#url} property, plus the following:
  278. * <ul>
  279. * <li><code>{i}</code>: The pixel column (horizontal coordinate) of the picked position, where the Westernmost pixel is 0.</li>
  280. * <li><code>{j}</code>: The pixel row (vertical coordinate) of the picked position, where the Northernmost pixel is 0.</li>
  281. * <li><code>{reverseI}</code>: The pixel column (horizontal coordinate) of the picked position, where the Easternmost pixel is 0.</li>
  282. * <li><code>{reverseJ}</code>: The pixel row (vertical coordinate) of the picked position, where the Southernmost pixel is 0.</li>
  283. * <li><code>{longitudeDegrees}</code>: The longitude of the picked position in degrees.</li>
  284. * <li><code>{latitudeDegrees}</code>: The latitude of the picked position in degrees.</li>
  285. * <li><code>{longitudeProjected}</code>: The longitude of the picked position in the projected coordinates of the tiling scheme.</li>
  286. * <li><code>{latitudeProjected}</code>: The latitude of the picked position in the projected coordinates of the tiling scheme.</li>
  287. * <li><code>{format}</code>: The format in which to get feature information, as specified in the {@link GetFeatureInfoFormat}.</li>
  288. * </ul>
  289. * @memberof UrlTemplateImageryProvider.prototype
  290. * @type {String}
  291. * @readonly
  292. */
  293. pickFeaturesUrl : {
  294. get : function() {
  295. return this._pickFeaturesResource.url;
  296. }
  297. },
  298. /**
  299. * Gets the proxy used by this provider.
  300. * @memberof UrlTemplateImageryProvider.prototype
  301. * @type {Proxy}
  302. * @readonly
  303. * @default undefined
  304. */
  305. proxy : {
  306. get : function() {
  307. return this._resource.proxy;
  308. }
  309. },
  310. /**
  311. * Gets the width of each tile, in pixels. This function should
  312. * not be called before {@link UrlTemplateImageryProvider#ready} returns true.
  313. * @memberof UrlTemplateImageryProvider.prototype
  314. * @type {Number}
  315. * @readonly
  316. * @default 256
  317. */
  318. tileWidth : {
  319. get : function() {
  320. //>>includeStart('debug', pragmas.debug);
  321. if (!this.ready) {
  322. throw new DeveloperError('tileWidth must not be called before the imagery provider is ready.');
  323. }
  324. //>>includeEnd('debug');
  325. return this._tileWidth;
  326. }
  327. },
  328. /**
  329. * Gets the height of each tile, in pixels. This function should
  330. * not be called before {@link UrlTemplateImageryProvider#ready} returns true.
  331. * @memberof UrlTemplateImageryProvider.prototype
  332. * @type {Number}
  333. * @readonly
  334. * @default 256
  335. */
  336. tileHeight: {
  337. get : function() {
  338. //>>includeStart('debug', pragmas.debug);
  339. if (!this.ready) {
  340. throw new DeveloperError('tileHeight must not be called before the imagery provider is ready.');
  341. }
  342. //>>includeEnd('debug');
  343. return this._tileHeight;
  344. }
  345. },
  346. /**
  347. * Gets the maximum level-of-detail that can be requested, or undefined if there is no limit.
  348. * This function should not be called before {@link UrlTemplateImageryProvider#ready} returns true.
  349. * @memberof UrlTemplateImageryProvider.prototype
  350. * @type {Number}
  351. * @readonly
  352. * @default undefined
  353. */
  354. maximumLevel : {
  355. get : function() {
  356. //>>includeStart('debug', pragmas.debug);
  357. if (!this.ready) {
  358. throw new DeveloperError('maximumLevel must not be called before the imagery provider is ready.');
  359. }
  360. //>>includeEnd('debug');
  361. return this._maximumLevel;
  362. }
  363. },
  364. /**
  365. * Gets the minimum level-of-detail that can be requested. This function should
  366. * not be called before {@link UrlTemplateImageryProvider#ready} returns true.
  367. * @memberof UrlTemplateImageryProvider.prototype
  368. * @type {Number}
  369. * @readonly
  370. * @default 0
  371. */
  372. minimumLevel : {
  373. get : function() {
  374. //>>includeStart('debug', pragmas.debug);
  375. if (!this.ready) {
  376. throw new DeveloperError('minimumLevel must not be called before the imagery provider is ready.');
  377. }
  378. //>>includeEnd('debug');
  379. return this._minimumLevel;
  380. }
  381. },
  382. /**
  383. * Gets the tiling scheme used by this provider. This function should
  384. * not be called before {@link UrlTemplateImageryProvider#ready} returns true.
  385. * @memberof UrlTemplateImageryProvider.prototype
  386. * @type {TilingScheme}
  387. * @readonly
  388. * @default new WebMercatorTilingScheme()
  389. */
  390. tilingScheme : {
  391. get : function() {
  392. //>>includeStart('debug', pragmas.debug);
  393. if (!this.ready) {
  394. throw new DeveloperError('tilingScheme must not be called before the imagery provider is ready.');
  395. }
  396. //>>includeEnd('debug');
  397. return this._tilingScheme;
  398. }
  399. },
  400. /**
  401. * Gets the rectangle, in radians, of the imagery provided by this instance. This function should
  402. * not be called before {@link UrlTemplateImageryProvider#ready} returns true.
  403. * @memberof UrlTemplateImageryProvider.prototype
  404. * @type {Rectangle}
  405. * @readonly
  406. * @default tilingScheme.rectangle
  407. */
  408. rectangle : {
  409. get : function() {
  410. //>>includeStart('debug', pragmas.debug);
  411. if (!this.ready) {
  412. throw new DeveloperError('rectangle must not be called before the imagery provider is ready.');
  413. }
  414. //>>includeEnd('debug');
  415. return this._rectangle;
  416. }
  417. },
  418. /**
  419. * Gets the tile discard policy. If not undefined, the discard policy is responsible
  420. * for filtering out "missing" tiles via its shouldDiscardImage function. If this function
  421. * returns undefined, no tiles are filtered. This function should
  422. * not be called before {@link UrlTemplateImageryProvider#ready} returns true.
  423. * @memberof UrlTemplateImageryProvider.prototype
  424. * @type {TileDiscardPolicy}
  425. * @readonly
  426. * @default undefined
  427. */
  428. tileDiscardPolicy : {
  429. get : function() {
  430. //>>includeStart('debug', pragmas.debug);
  431. if (!this.ready) {
  432. throw new DeveloperError('tileDiscardPolicy must not be called before the imagery provider is ready.');
  433. }
  434. //>>includeEnd('debug');
  435. return this._tileDiscardPolicy;
  436. }
  437. },
  438. /**
  439. * Gets an event that is raised when the imagery provider encounters an asynchronous error. By subscribing
  440. * to the event, you will be notified of the error and can potentially recover from it. Event listeners
  441. * are passed an instance of {@link TileProviderError}.
  442. * @memberof UrlTemplateImageryProvider.prototype
  443. * @type {Event}
  444. * @readonly
  445. */
  446. errorEvent : {
  447. get : function() {
  448. return this._errorEvent;
  449. }
  450. },
  451. /**
  452. * Gets a value indicating whether or not the provider is ready for use.
  453. * @memberof UrlTemplateImageryProvider.prototype
  454. * @type {Boolean}
  455. * @readonly
  456. */
  457. ready : {
  458. get : function() {
  459. return defined(this._resource);
  460. }
  461. },
  462. /**
  463. * Gets a promise that resolves to true when the provider is ready for use.
  464. * @memberof UrlTemplateImageryProvider.prototype
  465. * @type {Promise.<Boolean>}
  466. * @readonly
  467. */
  468. readyPromise : {
  469. get : function() {
  470. return this._readyPromise;
  471. }
  472. },
  473. /**
  474. * Gets the credit to display when this imagery provider is active. Typically this is used to credit
  475. * the source of the imagery. This function should not be called before {@link UrlTemplateImageryProvider#ready} returns true.
  476. * @memberof UrlTemplateImageryProvider.prototype
  477. * @type {Credit}
  478. * @readonly
  479. * @default undefined
  480. */
  481. credit : {
  482. get : function() {
  483. //>>includeStart('debug', pragmas.debug);
  484. if (!this.ready) {
  485. throw new DeveloperError('credit must not be called before the imagery provider is ready.');
  486. }
  487. //>>includeEnd('debug');
  488. return this._credit;
  489. }
  490. },
  491. /**
  492. * Gets a value indicating whether or not the images provided by this imagery provider
  493. * include an alpha channel. If this property is false, an alpha channel, if present, will
  494. * be ignored. If this property is true, any images without an alpha channel will be treated
  495. * as if their alpha is 1.0 everywhere. When this property is false, memory usage
  496. * and texture upload time are reduced. This function should
  497. * not be called before {@link ImageryProvider#ready} returns true.
  498. * @memberof UrlTemplateImageryProvider.prototype
  499. * @type {Boolean}
  500. * @readonly
  501. * @default true
  502. */
  503. hasAlphaChannel : {
  504. get : function() {
  505. //>>includeStart('debug', pragmas.debug);
  506. if (!this.ready) {
  507. throw new DeveloperError('hasAlphaChannel must not be called before the imagery provider is ready.');
  508. }
  509. //>>includeEnd('debug');
  510. return this._hasAlphaChannel;
  511. }
  512. }
  513. });
  514. /**
  515. * Reinitializes this instance. Reinitializing an instance already in use is supported, but it is not
  516. * recommended because existing tiles provided by the imagery provider will not be updated.
  517. *
  518. * @param {Promise.<Object>|Object} options Any of the options that may be passed to the {@link UrlTemplateImageryProvider} constructor.
  519. */
  520. UrlTemplateImageryProvider.prototype.reinitialize = function(options) {
  521. var that = this;
  522. that._readyPromise = when(options).then(function(properties) {
  523. //>>includeStart('debug', pragmas.debug);
  524. if (!defined(properties)) {
  525. throw new DeveloperError('options is required.');
  526. }
  527. if (!defined(properties.url)) {
  528. throw new DeveloperError('options.url is required.');
  529. }
  530. //>>includeEnd('debug');
  531. var customTags = properties.customTags;
  532. var allTags = combine(tags, customTags);
  533. var allPickFeaturesTags = combine(pickFeaturesTags, customTags);
  534. var resource = Resource.createIfNeeded(properties.url);
  535. var pickFeaturesResource = Resource.createIfNeeded(properties.pickFeaturesUrl);
  536. that.enablePickFeatures = defaultValue(properties.enablePickFeatures, that.enablePickFeatures);
  537. that._urlSchemeZeroPadding = defaultValue(properties.urlSchemeZeroPadding, that.urlSchemeZeroPadding);
  538. that._tileDiscardPolicy = properties.tileDiscardPolicy;
  539. that._getFeatureInfoFormats = properties.getFeatureInfoFormats;
  540. that._subdomains = properties.subdomains;
  541. if (isArray(that._subdomains)) {
  542. that._subdomains = that._subdomains.slice();
  543. } else if (defined(that._subdomains) && that._subdomains.length > 0) {
  544. that._subdomains = that._subdomains.split('');
  545. } else {
  546. that._subdomains = ['a', 'b', 'c'];
  547. }
  548. that._tileWidth = defaultValue(properties.tileWidth, 256);
  549. that._tileHeight = defaultValue(properties.tileHeight, 256);
  550. that._minimumLevel = defaultValue(properties.minimumLevel, 0);
  551. that._maximumLevel = properties.maximumLevel;
  552. that._tilingScheme = defaultValue(properties.tilingScheme, new WebMercatorTilingScheme({ ellipsoid : properties.ellipsoid }));
  553. that._rectangle = defaultValue(properties.rectangle, that._tilingScheme.rectangle);
  554. that._rectangle = Rectangle.intersection(that._rectangle, that._tilingScheme.rectangle);
  555. that._hasAlphaChannel = defaultValue(properties.hasAlphaChannel, true);
  556. var credit = properties.credit;
  557. if (typeof credit === 'string') {
  558. credit = new Credit(credit);
  559. }
  560. that._credit = credit;
  561. that._resource = resource;
  562. that._tags = allTags;
  563. that._pickFeaturesResource = pickFeaturesResource;
  564. that._pickFeaturesTags = allPickFeaturesTags;
  565. return true;
  566. });
  567. };
  568. /**
  569. * Gets the credits to be displayed when a given tile is displayed.
  570. *
  571. * @param {Number} x The tile X coordinate.
  572. * @param {Number} y The tile Y coordinate.
  573. * @param {Number} level The tile level;
  574. * @returns {Credit[]} The credits to be displayed when the tile is displayed.
  575. *
  576. * @exception {DeveloperError} <code>getTileCredits</code> must not be called before the imagery provider is ready.
  577. */
  578. UrlTemplateImageryProvider.prototype.getTileCredits = function(x, y, level) {
  579. //>>includeStart('debug', pragmas.debug);
  580. if (!this.ready) {
  581. throw new DeveloperError('getTileCredits must not be called before the imagery provider is ready.');
  582. }
  583. //>>includeEnd('debug');
  584. return undefined;
  585. };
  586. /**
  587. * Requests the image for a given tile. This function should
  588. * not be called before {@link UrlTemplateImageryProvider#ready} returns true.
  589. *
  590. * @param {Number} x The tile X coordinate.
  591. * @param {Number} y The tile Y coordinate.
  592. * @param {Number} level The tile level.
  593. * @param {Request} [request] The request object. Intended for internal use only.
  594. * @returns {Promise.<Image|Canvas>|undefined} A promise for the image that will resolve when the image is available, or
  595. * undefined if there are too many active requests to the server, and the request
  596. * should be retried later. The resolved image may be either an
  597. * Image or a Canvas DOM object.
  598. */
  599. UrlTemplateImageryProvider.prototype.requestImage = function(x, y, level, request) {
  600. //>>includeStart('debug', pragmas.debug);
  601. if (!this.ready) {
  602. throw new DeveloperError('requestImage must not be called before the imagery provider is ready.');
  603. }
  604. //>>includeEnd('debug');
  605. return ImageryProvider.loadImage(this, buildImageResource(this, x, y, level, request));
  606. };
  607. /**
  608. * Asynchronously determines what features, if any, are located at a given longitude and latitude within
  609. * a tile. This function should not be called before {@link ImageryProvider#ready} returns true.
  610. *
  611. * @param {Number} x The tile X coordinate.
  612. * @param {Number} y The tile Y coordinate.
  613. * @param {Number} level The tile level.
  614. * @param {Number} longitude The longitude at which to pick features.
  615. * @param {Number} latitude The latitude at which to pick features.
  616. * @return {Promise.<ImageryLayerFeatureInfo[]>|undefined} A promise for the picked features that will resolve when the asynchronous
  617. * picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo}
  618. * instances. The array may be empty if no features are found at the given location.
  619. * It may also be undefined if picking is not supported.
  620. */
  621. UrlTemplateImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) {
  622. //>>includeStart('debug', pragmas.debug);
  623. if (!this.ready) {
  624. throw new DeveloperError('pickFeatures must not be called before the imagery provider is ready.');
  625. }
  626. //>>includeEnd('debug');
  627. if (!this.enablePickFeatures || !defined(this._pickFeaturesResource) || this._getFeatureInfoFormats.length === 0) {
  628. return undefined;
  629. }
  630. var formatIndex = 0;
  631. var that = this;
  632. function handleResponse(format, data) {
  633. return format.callback(data);
  634. }
  635. function doRequest() {
  636. if (formatIndex >= that._getFeatureInfoFormats.length) {
  637. // No valid formats, so no features picked.
  638. return when([]);
  639. }
  640. var format = that._getFeatureInfoFormats[formatIndex];
  641. var resource = buildPickFeaturesResource(that, x, y, level, longitude, latitude, format.format);
  642. ++formatIndex;
  643. if (format.type === 'json') {
  644. return resource.fetchJson().then(format.callback).otherwise(doRequest);
  645. } else if (format.type === 'xml') {
  646. return resource.fetchXML().then(format.callback).otherwise(doRequest);
  647. } else if (format.type === 'text' || format.type === 'html') {
  648. return resource.fetchText().then(format.callback).otherwise(doRequest);
  649. }
  650. return resource.fetch({
  651. responseType: format.format
  652. }).then(handleResponse.bind(undefined, format)).otherwise(doRequest);
  653. }
  654. return doRequest();
  655. };
  656. var degreesScratchComputed = false;
  657. var degreesScratch = new Rectangle();
  658. var projectedScratchComputed = false;
  659. var projectedScratch = new Rectangle();
  660. function buildImageResource(imageryProvider, x, y, level, request) {
  661. degreesScratchComputed = false;
  662. projectedScratchComputed = false;
  663. var resource = imageryProvider._resource;
  664. var url = resource.getUrlComponent(true);
  665. var allTags = imageryProvider._tags;
  666. var templateValues = {};
  667. var match = url.match(templateRegex);
  668. if (defined(match)) {
  669. match.forEach(function(tag) {
  670. var key = tag.substring(1, tag.length - 1); //strip {}
  671. if (defined(allTags[key])) {
  672. templateValues[key] = allTags[key](imageryProvider, x, y, level);
  673. }
  674. });
  675. }
  676. return resource.getDerivedResource({
  677. request: request,
  678. templateValues: templateValues
  679. });
  680. }
  681. var ijScratchComputed = false;
  682. var ijScratch = new Cartesian2();
  683. var longitudeLatitudeProjectedScratchComputed = false;
  684. function buildPickFeaturesResource(imageryProvider, x, y, level, longitude, latitude, format) {
  685. degreesScratchComputed = false;
  686. projectedScratchComputed = false;
  687. ijScratchComputed = false;
  688. longitudeLatitudeProjectedScratchComputed = false;
  689. var resource = imageryProvider._pickFeaturesResource;
  690. var url = resource.getUrlComponent(true);
  691. var allTags = imageryProvider._pickFeaturesTags;
  692. var templateValues = {};
  693. var match = url.match(templateRegex);
  694. if (defined(match)) {
  695. match.forEach(function(tag) {
  696. var key = tag.substring(1, tag.length - 1); //strip {}
  697. if (defined(allTags[key])) {
  698. templateValues[key] = allTags[key](imageryProvider, x, y, level, longitude, latitude, format);
  699. }
  700. });
  701. }
  702. return resource.getDerivedResource({
  703. templateValues: templateValues
  704. });
  705. }
  706. function padWithZerosIfNecessary(imageryProvider, key, value) {
  707. if (imageryProvider &&
  708. imageryProvider.urlSchemeZeroPadding &&
  709. imageryProvider.urlSchemeZeroPadding.hasOwnProperty(key) )
  710. {
  711. var paddingTemplate = imageryProvider.urlSchemeZeroPadding[key];
  712. if (typeof paddingTemplate === 'string') {
  713. var paddingTemplateWidth = paddingTemplate.length;
  714. if (paddingTemplateWidth > 1) {
  715. value = (value.length >= paddingTemplateWidth) ? value : new Array(paddingTemplateWidth - value.toString().length + 1).join('0') + value;
  716. }
  717. }
  718. }
  719. return value;
  720. }
  721. function xTag(imageryProvider, x, y, level) {
  722. return padWithZerosIfNecessary(imageryProvider, '{x}', x);
  723. }
  724. function reverseXTag(imageryProvider, x, y, level) {
  725. var reverseX = imageryProvider.tilingScheme.getNumberOfXTilesAtLevel(level) - x - 1;
  726. return padWithZerosIfNecessary(imageryProvider, '{reverseX}', reverseX);
  727. }
  728. function yTag(imageryProvider, x, y, level) {
  729. return padWithZerosIfNecessary(imageryProvider, '{y}', y);
  730. }
  731. function reverseYTag(imageryProvider, x, y, level) {
  732. var reverseY = imageryProvider.tilingScheme.getNumberOfYTilesAtLevel(level) - y - 1;
  733. return padWithZerosIfNecessary(imageryProvider, '{reverseY}', reverseY);
  734. }
  735. function reverseZTag(imageryProvider, x, y, level) {
  736. var maximumLevel = imageryProvider.maximumLevel;
  737. var reverseZ = defined(maximumLevel) && level < maximumLevel ? maximumLevel - level - 1 : level;
  738. return padWithZerosIfNecessary(imageryProvider, '{reverseZ}', reverseZ);
  739. }
  740. function zTag(imageryProvider, x, y, level) {
  741. return padWithZerosIfNecessary(imageryProvider, '{z}', level);
  742. }
  743. function sTag(imageryProvider, x, y, level) {
  744. var index = (x + y + level) % imageryProvider._subdomains.length;
  745. return imageryProvider._subdomains[index];
  746. }
  747. function computeDegrees(imageryProvider, x, y, level) {
  748. if (degreesScratchComputed) {
  749. return;
  750. }
  751. imageryProvider.tilingScheme.tileXYToRectangle(x, y, level, degreesScratch);
  752. degreesScratch.west = CesiumMath.toDegrees(degreesScratch.west);
  753. degreesScratch.south = CesiumMath.toDegrees(degreesScratch.south);
  754. degreesScratch.east = CesiumMath.toDegrees(degreesScratch.east);
  755. degreesScratch.north = CesiumMath.toDegrees(degreesScratch.north);
  756. degreesScratchComputed = true;
  757. }
  758. function westDegreesTag(imageryProvider, x, y, level) {
  759. computeDegrees(imageryProvider, x, y, level);
  760. return degreesScratch.west;
  761. }
  762. function southDegreesTag(imageryProvider, x, y, level) {
  763. computeDegrees(imageryProvider, x, y, level);
  764. return degreesScratch.south;
  765. }
  766. function eastDegreesTag(imageryProvider, x, y, level) {
  767. computeDegrees(imageryProvider, x, y, level);
  768. return degreesScratch.east;
  769. }
  770. function northDegreesTag(imageryProvider, x, y, level) {
  771. computeDegrees(imageryProvider, x, y, level);
  772. return degreesScratch.north;
  773. }
  774. function computeProjected(imageryProvider, x, y, level) {
  775. if (projectedScratchComputed) {
  776. return;
  777. }
  778. imageryProvider.tilingScheme.tileXYToNativeRectangle(x, y, level, projectedScratch);
  779. projectedScratchComputed = true;
  780. }
  781. function westProjectedTag(imageryProvider, x, y, level) {
  782. computeProjected(imageryProvider, x, y, level);
  783. return projectedScratch.west;
  784. }
  785. function southProjectedTag(imageryProvider, x, y, level) {
  786. computeProjected(imageryProvider, x, y, level);
  787. return projectedScratch.south;
  788. }
  789. function eastProjectedTag(imageryProvider, x, y, level) {
  790. computeProjected(imageryProvider, x, y, level);
  791. return projectedScratch.east;
  792. }
  793. function northProjectedTag(imageryProvider, x, y, level) {
  794. computeProjected(imageryProvider, x, y, level);
  795. return projectedScratch.north;
  796. }
  797. function widthTag(imageryProvider, x, y, level) {
  798. return imageryProvider.tileWidth;
  799. }
  800. function heightTag(imageryProvider, x, y, level) {
  801. return imageryProvider.tileHeight;
  802. }
  803. function iTag(imageryProvider, x, y, level, longitude, latitude, format) {
  804. computeIJ(imageryProvider, x, y, level, longitude, latitude);
  805. return ijScratch.x;
  806. }
  807. function jTag(imageryProvider, x, y, level, longitude, latitude, format) {
  808. computeIJ(imageryProvider, x, y, level, longitude, latitude);
  809. return ijScratch.y;
  810. }
  811. function reverseITag(imageryProvider, x, y, level, longitude, latitude, format) {
  812. computeIJ(imageryProvider, x, y, level, longitude, latitude);
  813. return imageryProvider.tileWidth - ijScratch.x - 1;
  814. }
  815. function reverseJTag(imageryProvider, x, y, level, longitude, latitude, format) {
  816. computeIJ(imageryProvider, x, y, level, longitude, latitude);
  817. return imageryProvider.tileHeight - ijScratch.y - 1;
  818. }
  819. var rectangleScratch = new Rectangle();
  820. var longitudeLatitudeProjectedScratch = new Cartesian3();
  821. function computeIJ(imageryProvider, x, y, level, longitude, latitude, format) {
  822. if (ijScratchComputed) {
  823. return;
  824. }
  825. computeLongitudeLatitudeProjected(imageryProvider, x, y, level, longitude, latitude);
  826. var projected = longitudeLatitudeProjectedScratch;
  827. var rectangle = imageryProvider.tilingScheme.tileXYToNativeRectangle(x, y, level, rectangleScratch);
  828. ijScratch.x = (imageryProvider.tileWidth * (projected.x - rectangle.west) / rectangle.width) | 0;
  829. ijScratch.y = (imageryProvider.tileHeight * (rectangle.north - projected.y) / rectangle.height) | 0;
  830. ijScratchComputed = true;
  831. }
  832. function longitudeDegreesTag(imageryProvider, x, y, level, longitude, latitude, format) {
  833. return CesiumMath.toDegrees(longitude);
  834. }
  835. function latitudeDegreesTag(imageryProvider, x, y, level, longitude, latitude, format) {
  836. return CesiumMath.toDegrees(latitude);
  837. }
  838. function longitudeProjectedTag(imageryProvider, x, y, level, longitude, latitude, format) {
  839. computeLongitudeLatitudeProjected(imageryProvider, x, y, level, longitude, latitude);
  840. return longitudeLatitudeProjectedScratch.x;
  841. }
  842. function latitudeProjectedTag(imageryProvider, x, y, level, longitude, latitude, format) {
  843. computeLongitudeLatitudeProjected(imageryProvider, x, y, level, longitude, latitude);
  844. return longitudeLatitudeProjectedScratch.y;
  845. }
  846. var cartographicScratch = new Cartographic();
  847. function computeLongitudeLatitudeProjected(imageryProvider, x, y, level, longitude, latitude, format) {
  848. if (longitudeLatitudeProjectedScratchComputed) {
  849. return;
  850. }
  851. if (imageryProvider.tilingScheme.projection instanceof GeographicProjection) {
  852. longitudeLatitudeProjectedScratch.x = CesiumMath.toDegrees(longitude);
  853. longitudeLatitudeProjectedScratch.y = CesiumMath.toDegrees(latitude);
  854. } else {
  855. var cartographic = cartographicScratch;
  856. cartographic.longitude = longitude;
  857. cartographic.latitude = latitude;
  858. imageryProvider.tilingScheme.projection.project(cartographic, longitudeLatitudeProjectedScratch);
  859. }
  860. longitudeLatitudeProjectedScratchComputed = true;
  861. }
  862. function formatTag(imageryProvider, x, y, level, longitude, latitude, format) {
  863. return format;
  864. }
  865. export default UrlTemplateImageryProvider;