TileMapServiceImageryProvider.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. /*global define*/
  2. define([
  3. '../Core/appendForwardSlash',
  4. '../Core/Cartesian2',
  5. '../Core/Cartographic',
  6. '../Core/Credit',
  7. '../Core/defaultValue',
  8. '../Core/defined',
  9. '../Core/defineProperties',
  10. '../Core/DeveloperError',
  11. '../Core/Event',
  12. '../Core/GeographicTilingScheme',
  13. '../Core/loadXML',
  14. '../Core/Rectangle',
  15. '../Core/TileProviderError',
  16. '../Core/WebMercatorTilingScheme',
  17. '../ThirdParty/when',
  18. './ImageryProvider'
  19. ], function(
  20. appendForwardSlash,
  21. Cartesian2,
  22. Cartographic,
  23. Credit,
  24. defaultValue,
  25. defined,
  26. defineProperties,
  27. DeveloperError,
  28. Event,
  29. GeographicTilingScheme,
  30. loadXML,
  31. Rectangle,
  32. TileProviderError,
  33. WebMercatorTilingScheme,
  34. when,
  35. ImageryProvider) {
  36. "use strict";
  37. /**
  38. * Provides tiled imagery as generated by {@link http://www.maptiler.org/'>MapTiler</a> / <a href='http://www.klokan.cz/projects/gdal2tiles/|GDDAL2Tiles} etc.
  39. *
  40. * @alias TileMapServiceImageryProvider
  41. * @constructor
  42. *
  43. * @param {Object} [options] Object with the following properties:
  44. * @param {String} [options.url='.'] Path to image tiles on server.
  45. * @param {String} [options.fileExtension='png'] The file extension for images on the server.
  46. * @param {Object} [options.proxy] A proxy to use for requests. This object is expected to have a getURL function which returns the proxied URL.
  47. * @param {Credit|String} [options.credit=''] A credit for the data source, which is displayed on the canvas.
  48. * @param {Number} [options.minimumLevel=0] The minimum level-of-detail supported by the imagery provider. Take care when specifying
  49. * this that the number of tiles at the minimum level is small, such as four or less. A larger number is likely
  50. * to result in rendering problems.
  51. * @param {Number} [options.maximumLevel=18] The maximum level-of-detail supported by the imagery provider.
  52. * @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the image.
  53. * @param {TilingScheme} [options.tilingScheme] The tiling scheme specifying how the ellipsoidal
  54. * surface is broken into tiles. If this parameter is not provided, a {@link WebMercatorTilingScheme}
  55. * is used.
  56. * @param {Number} [options.tileWidth=256] Pixel width of image tiles.
  57. * @param {Number} [options.tileHeight=256] Pixel height of image tiles.
  58. *
  59. * @see ArcGisMapServerImageryProvider
  60. * @see BingMapsImageryProvider
  61. * @see GoogleEarthImageryProvider
  62. * @see OpenStreetMapImageryProvider
  63. * @see WebMapTileServiceImageryProvider
  64. * @see SingleTileImageryProvider
  65. * @see WebMapServiceImageryProvider
  66. *
  67. * @see {@link http://www.maptiler.org/|MapTiler}
  68. * @see {@link http://www.klokan.cz/projects/gdal2tiles/|GDDAL2Tiles}
  69. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  70. *
  71. * @example
  72. * // TileMapService tile provider
  73. * var tms = new Cesium.TileMapServiceImageryProvider({
  74. * url : '../images/cesium_maptiler/Cesium_Logo_Color',
  75. * fileExtension: 'png',
  76. * maximumLevel: 4,
  77. * rectangle: new Cesium.Rectangle(
  78. * Cesium.Math.toRadians(-120.0),
  79. * Cesium.Math.toRadians(20.0),
  80. * Cesium.Math.toRadians(-60.0),
  81. * Cesium.Math.toRadians(40.0))
  82. * });
  83. */
  84. var TileMapServiceImageryProvider = function TileMapServiceImageryProvider(options) {
  85. options = defaultValue(options, {});
  86. //>>includeStart('debug', pragmas.debug);
  87. if (!defined(options.url)) {
  88. throw new DeveloperError('options.url is required.');
  89. }
  90. //>>includeEnd('debug');
  91. var url = appendForwardSlash(options.url);
  92. this._url = url;
  93. this._ready = false;
  94. this._proxy = options.proxy;
  95. this._tileDiscardPolicy = options.tileDiscardPolicy;
  96. this._errorEvent = new Event();
  97. this._fileExtension = options.fileExtension;
  98. this._tileWidth = options.tileWidth;
  99. this._tileHeight = options.tileHeight;
  100. this._minimumLevel = options.minimumLevel;
  101. this._maximumLevel = options.maximumLevel;
  102. this._rectangle = Rectangle.clone(options.rectangle);
  103. this._tilingScheme = options.tilingScheme;
  104. var credit = options.credit;
  105. if (typeof credit === 'string') {
  106. credit = new Credit(credit);
  107. }
  108. this._credit = credit;
  109. var that = this;
  110. var metadataError;
  111. function metadataSuccess(xml) {
  112. var tileFormatRegex = /tileformat/i;
  113. var tileSetRegex = /tileset/i;
  114. var tileSetsRegex = /tilesets/i;
  115. var bboxRegex = /boundingbox/i;
  116. var srsRegex = /srs/i;
  117. var format, bbox, tilesets, srs;
  118. var tilesetsList = []; //list of TileSets
  119. // Allowing options properties (already copied to that) to override XML values
  120. // Iterate XML Document nodes for properties
  121. var nodeList = xml.childNodes[0].childNodes;
  122. for (var i = 0; i < nodeList.length; i++){
  123. if (tileFormatRegex.test(nodeList.item(i).nodeName)) {
  124. format = nodeList.item(i);
  125. } else if (tileSetsRegex.test(nodeList.item(i).nodeName)) {
  126. tilesets = nodeList.item(i); // Node list of TileSets
  127. var tileSetNodes = nodeList.item(i).childNodes;
  128. // Iterate the nodes to find all TileSets
  129. for(var j = 0; j < tileSetNodes.length; j++) {
  130. if (tileSetRegex.test(tileSetNodes.item(j).nodeName)) {
  131. // Add them to tilesets list
  132. tilesetsList.push(tileSetNodes.item(j));
  133. }
  134. }
  135. } else if (bboxRegex.test(nodeList.item(i).nodeName)) {
  136. bbox = nodeList.item(i);
  137. } else if (srsRegex.test(nodeList.item(i).nodeName)) {
  138. srs = nodeList.item(i).textContent;
  139. }
  140. }
  141. that._fileExtension = defaultValue(that._fileExtension, format.getAttribute('extension'));
  142. that._tileWidth = defaultValue(that._tileWidth, parseInt(format.getAttribute('width'), 10));
  143. that._tileHeight = defaultValue(that._tileHeight, parseInt(format.getAttribute('height'), 10));
  144. that._minimumLevel = defaultValue(that._minimumLevel, parseInt(tilesetsList[0].getAttribute('order'), 10));
  145. that._maximumLevel = defaultValue(that._maximumLevel, parseInt(tilesetsList[tilesetsList.length - 1].getAttribute('order'), 10));
  146. // Determine based on the profile attribute if this tileset was generated by gdal2tiles.py ('mercator' or 'geodetic' profile, in which
  147. // case X is latitude and Y is longitude) or by a tool compliant with the TMS standard ('global-mercator' or 'global-geodetic' profile,
  148. // in which case X is longitude and Y is latitude).
  149. var tilingSchemeName = tilesets.getAttribute('profile');
  150. var flipXY = false;
  151. if (tilingSchemeName === 'geodetic' || tilingSchemeName === 'mercator') {
  152. flipXY = true;
  153. }
  154. if (!defined(that._tilingScheme)) {
  155. if (tilingSchemeName === 'geodetic' || tilingSchemeName === 'global-geodetic') {
  156. that._tilingScheme = new GeographicTilingScheme();
  157. } else if (tilingSchemeName === 'mercator' || tilingSchemeName === 'global-mercator') {
  158. that._tilingScheme = new WebMercatorTilingScheme();
  159. } else {
  160. var message = url + 'tilemapresource.xml specifies an unsupported profile attribute, ' + tilingSchemeName + '.';
  161. metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
  162. return;
  163. }
  164. }
  165. var tilingScheme = that._tilingScheme;
  166. // rectangle handling
  167. if (!defined(that._rectangle)) {
  168. var swXY;
  169. var neXY;
  170. var sw;
  171. var ne;
  172. if (flipXY) {
  173. swXY = new Cartesian2(parseFloat(bbox.getAttribute('miny')), parseFloat(bbox.getAttribute('minx')));
  174. neXY = new Cartesian2(parseFloat(bbox.getAttribute('maxy')), parseFloat(bbox.getAttribute('maxx')));
  175. // In old tilers with X/Y flipped, coordinate are always geodetic degrees.
  176. sw = Cartographic.fromDegrees(swXY.x, swXY.y);
  177. ne = Cartographic.fromDegrees(neXY.x, neXY.y);
  178. } else {
  179. swXY = new Cartesian2(parseFloat(bbox.getAttribute('minx')), parseFloat(bbox.getAttribute('miny')));
  180. neXY = new Cartesian2(parseFloat(bbox.getAttribute('maxx')), parseFloat(bbox.getAttribute('maxy')));
  181. if (that._tilingScheme instanceof GeographicTilingScheme) {
  182. sw = Cartographic.fromDegrees(swXY.x, swXY.y);
  183. ne = Cartographic.fromDegrees(neXY.x, neXY.y);
  184. } else {
  185. var projection = that._tilingScheme.projection;
  186. sw = projection.unproject(swXY);
  187. ne = projection.unproject(neXY);
  188. }
  189. }
  190. that._rectangle = new Rectangle(sw.longitude, sw.latitude, ne.longitude, ne.latitude);
  191. }
  192. // The rectangle must not be outside the bounds allowed by the tiling scheme.
  193. if (that._rectangle.west < tilingScheme.rectangle.west) {
  194. that._rectangle.west = tilingScheme.rectangle.west;
  195. }
  196. if (that._rectangle.east > tilingScheme.rectangle.east) {
  197. that._rectangle.east = tilingScheme.rectangle.east;
  198. }
  199. if (that._rectangle.south < tilingScheme.rectangle.south) {
  200. that._rectangle.south = tilingScheme.rectangle.south;
  201. }
  202. if (that._rectangle.north > tilingScheme.rectangle.north) {
  203. that._rectangle.north = tilingScheme.rectangle.north;
  204. }
  205. // Check the number of tiles at the minimum level. If it's more than four,
  206. // try requesting the lower levels anyway, because starting at the higher minimum
  207. // level will cause too many tiles to be downloaded and rendered.
  208. var swTile = tilingScheme.positionToTileXY(Rectangle.southwest(that._rectangle), that._minimumLevel);
  209. var neTile = tilingScheme.positionToTileXY(Rectangle.northeast(that._rectangle), that._minimumLevel);
  210. var tileCount = (Math.abs(neTile.x - swTile.x) + 1) * (Math.abs(neTile.y - swTile.y) + 1);
  211. if (tileCount > 4) {
  212. that._minimumLevel = 0;
  213. }
  214. that._tilingScheme = tilingScheme;
  215. that._ready = true;
  216. }
  217. function metadataFailure(error) {
  218. // Can't load XML, still allow options and defaults
  219. that._fileExtension = defaultValue(options.fileExtension, 'png');
  220. that._tileWidth = defaultValue(options.tileWidth, 256);
  221. that._tileHeight = defaultValue(options.tileHeight, 256);
  222. that._minimumLevel = defaultValue(options.minimumLevel, 0);
  223. that._maximumLevel = defaultValue(options.maximumLevel, 18);
  224. that._tilingScheme = defined(options.tilingScheme) ? options.tilingScheme : new WebMercatorTilingScheme();
  225. that._rectangle = defaultValue(options.rectangle, that._tilingScheme.rectangle);
  226. that._ready = true;
  227. }
  228. function requestMetadata() {
  229. var resourceUrl = url + 'tilemapresource.xml';
  230. var proxy = that._proxy;
  231. if (defined(proxy)) {
  232. resourceUrl = proxy.getURL(resourceUrl);
  233. }
  234. // Try to load remaining parameters from XML
  235. when(loadXML(resourceUrl), metadataSuccess, metadataFailure);
  236. }
  237. requestMetadata();
  238. };
  239. function buildImageUrl(imageryProvider, x, y, level) {
  240. var yTiles = imageryProvider._tilingScheme.getNumberOfYTilesAtLevel(level);
  241. var url = imageryProvider._url + level + '/' + x + '/' + (yTiles - y - 1) + '.' + imageryProvider._fileExtension;
  242. var proxy = imageryProvider._proxy;
  243. if (defined(proxy)) {
  244. url = proxy.getURL(url);
  245. }
  246. return url;
  247. }
  248. defineProperties(TileMapServiceImageryProvider.prototype, {
  249. /**
  250. * Gets the URL of the service hosting the imagery.
  251. * @memberof TileMapServiceImageryProvider.prototype
  252. * @type {String}
  253. * @readonly
  254. */
  255. url : {
  256. get : function() {
  257. return this._url;
  258. }
  259. },
  260. /**
  261. * Gets the proxy used by this provider.
  262. * @memberof TileMapServiceImageryProvider.prototype
  263. * @type {Proxy}
  264. * @readonly
  265. */
  266. proxy : {
  267. get : function() {
  268. return this._proxy;
  269. }
  270. },
  271. /**
  272. * Gets the width of each tile, in pixels. This function should
  273. * not be called before {@link TileMapServiceImageryProvider#ready} returns true.
  274. * @memberof TileMapServiceImageryProvider.prototype
  275. * @type {Number}
  276. * @readonly
  277. */
  278. tileWidth : {
  279. get : function() {
  280. //>>includeStart('debug', pragmas.debug);
  281. if (!this._ready) {
  282. throw new DeveloperError('tileWidth must not be called before the imagery provider is ready.');
  283. }
  284. //>>includeEnd('debug');
  285. return this._tileWidth;
  286. }
  287. },
  288. /**
  289. * Gets the height of each tile, in pixels. This function should
  290. * not be called before {@link TileMapServiceImageryProvider#ready} returns true.
  291. * @memberof TileMapServiceImageryProvider.prototype
  292. * @type {Number}
  293. * @readonly
  294. */
  295. tileHeight: {
  296. get : function() {
  297. //>>includeStart('debug', pragmas.debug);
  298. if (!this._ready) {
  299. throw new DeveloperError('tileHeight must not be called before the imagery provider is ready.');
  300. }
  301. //>>includeEnd('debug');
  302. return this._tileHeight;
  303. }
  304. },
  305. /**
  306. * Gets the maximum level-of-detail that can be requested. This function should
  307. * not be called before {@link TileMapServiceImageryProvider#ready} returns true.
  308. * @memberof TileMapServiceImageryProvider.prototype
  309. * @type {Number}
  310. * @readonly
  311. */
  312. maximumLevel : {
  313. get : function() {
  314. //>>includeStart('debug', pragmas.debug);
  315. if (!this._ready) {
  316. throw new DeveloperError('maximumLevel must not be called before the imagery provider is ready.');
  317. }
  318. //>>includeEnd('debug');
  319. return this._maximumLevel;
  320. }
  321. },
  322. /**
  323. * Gets the minimum level-of-detail that can be requested. This function should
  324. * not be called before {@link TileMapServiceImageryProvider#ready} returns true.
  325. * @memberof TileMapServiceImageryProvider.prototype
  326. * @type {Number}
  327. * @readonly
  328. */
  329. minimumLevel : {
  330. get : function() {
  331. //>>includeStart('debug', pragmas.debug);
  332. if (!this._ready) {
  333. throw new DeveloperError('minimumLevel must not be called before the imagery provider is ready.');
  334. }
  335. //>>includeEnd('debug');
  336. return this._minimumLevel;
  337. }
  338. },
  339. /**
  340. * Gets the tiling scheme used by this provider. This function should
  341. * not be called before {@link TileMapServiceImageryProvider#ready} returns true.
  342. * @memberof TileMapServiceImageryProvider.prototype
  343. * @type {TilingScheme}
  344. * @readonly
  345. */
  346. tilingScheme : {
  347. get : function() {
  348. //>>includeStart('debug', pragmas.debug);
  349. if (!this._ready) {
  350. throw new DeveloperError('tilingScheme must not be called before the imagery provider is ready.');
  351. }
  352. //>>includeEnd('debug');
  353. return this._tilingScheme;
  354. }
  355. },
  356. /**
  357. * Gets the rectangle, in radians, of the imagery provided by this instance. This function should
  358. * not be called before {@link TileMapServiceImageryProvider#ready} returns true.
  359. * @memberof TileMapServiceImageryProvider.prototype
  360. * @type {Rectangle}
  361. * @readonly
  362. */
  363. rectangle : {
  364. get : function() {
  365. //>>includeStart('debug', pragmas.debug);
  366. if (!this._ready) {
  367. throw new DeveloperError('rectangle must not be called before the imagery provider is ready.');
  368. }
  369. //>>includeEnd('debug');
  370. return this._rectangle;
  371. }
  372. },
  373. /**
  374. * Gets the tile discard policy. If not undefined, the discard policy is responsible
  375. * for filtering out "missing" tiles via its shouldDiscardImage function. If this function
  376. * returns undefined, no tiles are filtered. This function should
  377. * not be called before {@link TileMapServiceImageryProvider#ready} returns true.
  378. * @memberof TileMapServiceImageryProvider.prototype
  379. * @type {TileDiscardPolicy}
  380. * @readonly
  381. */
  382. tileDiscardPolicy : {
  383. get : function() {
  384. //>>includeStart('debug', pragmas.debug);
  385. if (!this._ready) {
  386. throw new DeveloperError('tileDiscardPolicy must not be called before the imagery provider is ready.');
  387. }
  388. //>>includeEnd('debug');
  389. return this._tileDiscardPolicy;
  390. }
  391. },
  392. /**
  393. * Gets an event that is raised when the imagery provider encounters an asynchronous error. By subscribing
  394. * to the event, you will be notified of the error and can potentially recover from it. Event listeners
  395. * are passed an instance of {@link TileProviderError}.
  396. * @memberof TileMapServiceImageryProvider.prototype
  397. * @type {Event}
  398. * @readonly
  399. */
  400. errorEvent : {
  401. get : function() {
  402. return this._errorEvent;
  403. }
  404. },
  405. /**
  406. * Gets a value indicating whether or not the provider is ready for use.
  407. * @memberof TileMapServiceImageryProvider.prototype
  408. * @type {Boolean}
  409. * @readonly
  410. */
  411. ready : {
  412. get : function() {
  413. return this._ready;
  414. }
  415. },
  416. /**
  417. * Gets the credit to display when this imagery provider is active. Typically this is used to credit
  418. * the source of the imagery. This function should not be called before {@link TileMapServiceImageryProvider#ready} returns true.
  419. * @memberof TileMapServiceImageryProvider.prototype
  420. * @type {Credit}
  421. * @readonly
  422. */
  423. credit : {
  424. get : function() {
  425. return this._credit;
  426. }
  427. },
  428. /**
  429. * Gets a value indicating whether or not the images provided by this imagery provider
  430. * include an alpha channel. If this property is false, an alpha channel, if present, will
  431. * be ignored. If this property is true, any images without an alpha channel will be treated
  432. * as if their alpha is 1.0 everywhere. When this property is false, memory usage
  433. * and texture upload time are reduced.
  434. * @memberof TileMapServiceImageryProvider.prototype
  435. * @type {Boolean}
  436. * @readonly
  437. */
  438. hasAlphaChannel : {
  439. get : function() {
  440. return true;
  441. }
  442. }
  443. });
  444. /**
  445. * Gets the credits to be displayed when a given tile is displayed.
  446. *
  447. * @param {Number} x The tile X coordinate.
  448. * @param {Number} y The tile Y coordinate.
  449. * @param {Number} level The tile level;
  450. * @returns {Credit[]} The credits to be displayed when the tile is displayed.
  451. *
  452. * @exception {DeveloperError} <code>getTileCredits</code> must not be called before the imagery provider is ready.
  453. */
  454. TileMapServiceImageryProvider.prototype.getTileCredits = function(x, y, level) {
  455. return undefined;
  456. };
  457. /**
  458. * Requests the image for a given tile. This function should
  459. * not be called before {@link TileMapServiceImageryProvider#ready} returns true.
  460. *
  461. * @param {Number} x The tile X coordinate.
  462. * @param {Number} y The tile Y coordinate.
  463. * @param {Number} level The tile level.
  464. * @returns {Promise} A promise for the image that will resolve when the image is available, or
  465. * undefined if there are too many active requests to the server, and the request
  466. * should be retried later. The resolved image may be either an
  467. * Image or a Canvas DOM object.
  468. */
  469. TileMapServiceImageryProvider.prototype.requestImage = function(x, y, level) {
  470. //>>includeStart('debug', pragmas.debug);
  471. if (!this._ready) {
  472. throw new DeveloperError('requestImage must not be called before the imagery provider is ready.');
  473. }
  474. //>>includeEnd('debug');
  475. var url = buildImageUrl(this, x, y, level);
  476. return ImageryProvider.loadImage(this, url);
  477. };
  478. /**
  479. * Picking features is not currently supported by this imagery provider, so this function simply returns
  480. * undefined.
  481. *
  482. * @param {Number} x The tile X coordinate.
  483. * @param {Number} y The tile Y coordinate.
  484. * @param {Number} level The tile level.
  485. * @param {Number} longitude The longitude at which to pick features.
  486. * @param {Number} latitude The latitude at which to pick features.
  487. * @return {Promise} A promise for the picked features that will resolve when the asynchronous
  488. * picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo}
  489. * instances. The array may be empty if no features are found at the given location.
  490. * It may also be undefined if picking is not supported.
  491. */
  492. TileMapServiceImageryProvider.prototype.pickFeatures = function() {
  493. return undefined;
  494. };
  495. return TileMapServiceImageryProvider;
  496. });