ImageryLayer.js 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962
  1. /*global define*/
  2. define([
  3. '../Core/BoundingRectangle',
  4. '../Core/Cartesian2',
  5. '../Core/Cartesian4',
  6. '../Core/Color',
  7. '../Core/ComponentDatatype',
  8. '../Core/defaultValue',
  9. '../Core/defined',
  10. '../Core/defineProperties',
  11. '../Core/destroyObject',
  12. '../Core/FeatureDetection',
  13. '../Core/GeographicTilingScheme',
  14. '../Core/IndexDatatype',
  15. '../Core/Math',
  16. '../Core/PixelFormat',
  17. '../Core/PrimitiveType',
  18. '../Core/Rectangle',
  19. '../Core/TerrainProvider',
  20. '../Core/TileProviderError',
  21. '../Renderer/BufferUsage',
  22. '../Renderer/ClearCommand',
  23. '../Renderer/DrawCommand',
  24. '../Renderer/MipmapHint',
  25. '../Renderer/ShaderSource',
  26. '../Renderer/TextureMagnificationFilter',
  27. '../Renderer/TextureMinificationFilter',
  28. '../Renderer/TextureWrap',
  29. '../Shaders/ReprojectWebMercatorFS',
  30. '../Shaders/ReprojectWebMercatorVS',
  31. '../ThirdParty/when',
  32. './Imagery',
  33. './ImageryState',
  34. './TileImagery'
  35. ], function(
  36. BoundingRectangle,
  37. Cartesian2,
  38. Cartesian4,
  39. Color,
  40. ComponentDatatype,
  41. defaultValue,
  42. defined,
  43. defineProperties,
  44. destroyObject,
  45. FeatureDetection,
  46. GeographicTilingScheme,
  47. IndexDatatype,
  48. CesiumMath,
  49. PixelFormat,
  50. PrimitiveType,
  51. Rectangle,
  52. TerrainProvider,
  53. TileProviderError,
  54. BufferUsage,
  55. ClearCommand,
  56. DrawCommand,
  57. MipmapHint,
  58. ShaderSource,
  59. TextureMagnificationFilter,
  60. TextureMinificationFilter,
  61. TextureWrap,
  62. ReprojectWebMercatorFS,
  63. ReprojectWebMercatorVS,
  64. when,
  65. Imagery,
  66. ImageryState,
  67. TileImagery) {
  68. "use strict";
  69. /**
  70. * An imagery layer that displays tiled image data from a single imagery provider
  71. * on a {@link Globe}.
  72. *
  73. * @alias ImageryLayer
  74. * @constructor
  75. *
  76. * @param {ImageryProvider} imageryProvider The imagery provider to use.
  77. * @param {Object} [options] Object with the following properties:
  78. * @param {Rectangle} [options.rectangle=imageryProvider.rectangle] The rectangle of the layer. This rectangle
  79. * can limit the visible portion of the imagery provider.
  80. * @param {Number|Function} [options.alpha=1.0] The alpha blending value of this layer, from 0.0 to 1.0.
  81. * This can either be a simple number or a function with the signature
  82. * <code>function(frameState, layer, x, y, level)</code>. The function is passed the
  83. * current frame state, this layer, and the x, y, and level coordinates of the
  84. * imagery tile for which the alpha is required, and it is expected to return
  85. * the alpha value to use for the tile.
  86. * @param {Number|Function} [options.brightness=1.0] The brightness of this layer. 1.0 uses the unmodified imagery
  87. * color. Less than 1.0 makes the imagery darker while greater than 1.0 makes it brighter.
  88. * This can either be a simple number or a function with the signature
  89. * <code>function(frameState, layer, x, y, level)</code>. The function is passed the
  90. * current frame state, this layer, and the x, y, and level coordinates of the
  91. * imagery tile for which the brightness is required, and it is expected to return
  92. * the brightness value to use for the tile. The function is executed for every
  93. * frame and for every tile, so it must be fast.
  94. * @param {Number|Function} [options.contrast=1.0] The contrast of this layer. 1.0 uses the unmodified imagery color.
  95. * Less than 1.0 reduces the contrast while greater than 1.0 increases it.
  96. * This can either be a simple number or a function with the signature
  97. * <code>function(frameState, layer, x, y, level)</code>. The function is passed the
  98. * current frame state, this layer, and the x, y, and level coordinates of the
  99. * imagery tile for which the contrast is required, and it is expected to return
  100. * the contrast value to use for the tile. The function is executed for every
  101. * frame and for every tile, so it must be fast.
  102. * @param {Number|Function} [options.hue=0.0] The hue of this layer. 0.0 uses the unmodified imagery color.
  103. * This can either be a simple number or a function with the signature
  104. * <code>function(frameState, layer, x, y, level)</code>. The function is passed the
  105. * current frame state, this layer, and the x, y, and level coordinates
  106. * of the imagery tile for which the hue is required, and it is expected to return
  107. * the contrast value to use for the tile. The function is executed for every
  108. * frame and for every tile, so it must be fast.
  109. * @param {Number|Function} [options.saturation=1.0] The saturation of this layer. 1.0 uses the unmodified imagery color.
  110. * Less than 1.0 reduces the saturation while greater than 1.0 increases it.
  111. * This can either be a simple number or a function with the signature
  112. * <code>function(frameState, layer, x, y, level)</code>. The function is passed the
  113. * current frame state, this layer, and the x, y, and level coordinates
  114. * of the imagery tile for which the saturation is required, and it is expected to return
  115. * the contrast value to use for the tile. The function is executed for every
  116. * frame and for every tile, so it must be fast.
  117. * @param {Number|Function} [options.gamma=1.0] The gamma correction to apply to this layer. 1.0 uses the unmodified imagery color.
  118. * This can either be a simple number or a function with the signature
  119. * <code>function(frameState, layer, x, y, level)</code>. The function is passed the
  120. * current frame state, this layer, and the x, y, and level coordinates of the
  121. * imagery tile for which the gamma is required, and it is expected to return
  122. * the gamma value to use for the tile. The function is executed for every
  123. * frame and for every tile, so it must be fast.
  124. * @param {Boolean} [options.show=true] True if the layer is shown; otherwise, false.
  125. * @param {Number} [options.maximumAnisotropy=maximum supported] The maximum anisotropy level to use
  126. * for texture filtering. If this parameter is not specified, the maximum anisotropy supported
  127. * by the WebGL stack will be used. Larger values make the imagery look better in horizon
  128. * views.
  129. * @param {Number} [options.minimumTerrainLevel] The minimum terrain level-of-detail at which to show this imagery layer,
  130. * or undefined to show it at all levels. Level zero is the least-detailed level.
  131. * @param {Number} [options.maximumTerrainLevel] The maximum terrain level-of-detail at which to show this imagery layer,
  132. * or undefined to show it at all levels. Level zero is the least-detailed level.
  133. */
  134. var ImageryLayer = function ImageryLayer(imageryProvider, options) {
  135. this._imageryProvider = imageryProvider;
  136. options = defaultValue(options, {});
  137. /**
  138. * The alpha blending value of this layer, with 0.0 representing fully transparent and
  139. * 1.0 representing fully opaque.
  140. *
  141. * @type {Number}
  142. * @default 1.0
  143. */
  144. this.alpha = defaultValue(options.alpha, defaultValue(imageryProvider.defaultAlpha, 1.0));
  145. /**
  146. * The brightness of this layer. 1.0 uses the unmodified imagery color. Less than 1.0
  147. * makes the imagery darker while greater than 1.0 makes it brighter.
  148. *
  149. * @type {Number}
  150. * @default {@link ImageryLayer.DEFAULT_BRIGHTNESS}
  151. */
  152. this.brightness = defaultValue(options.brightness, defaultValue(imageryProvider.defaultBrightness, ImageryLayer.DEFAULT_BRIGHTNESS));
  153. /**
  154. * The contrast of this layer. 1.0 uses the unmodified imagery color. Less than 1.0 reduces
  155. * the contrast while greater than 1.0 increases it.
  156. *
  157. * @type {Number}
  158. * @default {@link ImageryLayer.DEFAULT_CONTRAST}
  159. */
  160. this.contrast = defaultValue(options.contrast, defaultValue(imageryProvider.defaultContrast, ImageryLayer.DEFAULT_CONTRAST));
  161. /**
  162. * The hue of this layer in radians. 0.0 uses the unmodified imagery color.
  163. *
  164. * @type {Number}
  165. * @default {@link ImageryLayer.DEFAULT_HUE}
  166. */
  167. this.hue = defaultValue(options.hue, defaultValue(imageryProvider.defaultHue, ImageryLayer.DEFAULT_HUE));
  168. /**
  169. * The saturation of this layer. 1.0 uses the unmodified imagery color. Less than 1.0 reduces the
  170. * saturation while greater than 1.0 increases it.
  171. *
  172. * @type {Number}
  173. * @default {@link ImageryLayer.DEFAULT_SATURATION}
  174. */
  175. this.saturation = defaultValue(options.saturation, defaultValue(imageryProvider.defaultSaturation, ImageryLayer.DEFAULT_SATURATION));
  176. /**
  177. * The gamma correction to apply to this layer. 1.0 uses the unmodified imagery color.
  178. *
  179. * @type {Number}
  180. * @default {@link ImageryLayer.DEFAULT_GAMMA}
  181. */
  182. this.gamma = defaultValue(options.gamma, defaultValue(imageryProvider.defaultGamma, ImageryLayer.DEFAULT_GAMMA));
  183. /**
  184. * Determines if this layer is shown.
  185. *
  186. * @type {Boolean}
  187. * @default true
  188. */
  189. this.show = defaultValue(options.show, true);
  190. this._minimumTerrainLevel = options.minimumTerrainLevel;
  191. this._maximumTerrainLevel = options.maximumTerrainLevel;
  192. this._rectangle = defaultValue(options.rectangle, Rectangle.MAX_VALUE);
  193. this._maximumAnisotropy = options.maximumAnisotropy;
  194. this._imageryCache = {};
  195. this._skeletonPlaceholder = new TileImagery(Imagery.createPlaceholder(this));
  196. // The value of the show property on the last update.
  197. this._show = true;
  198. // The index of this layer in the ImageryLayerCollection.
  199. this._layerIndex = -1;
  200. // true if this is the base (lowest shown) layer.
  201. this._isBaseLayer = false;
  202. this._requestImageError = undefined;
  203. };
  204. defineProperties(ImageryLayer.prototype, {
  205. /**
  206. * Gets the imagery provider for this layer.
  207. * @memberof ImageryLayer.prototype
  208. * @type {ImageryProvider}
  209. * @readonly
  210. */
  211. imageryProvider : {
  212. get: function() {
  213. return this._imageryProvider;
  214. }
  215. },
  216. /**
  217. * Gets the rectangle of this layer. If this rectangle is smaller than the rectangle of the
  218. * {@link ImageryProvider}, only a portion of the imagery provider is shown.
  219. * @memberof ImageryLayer.prototype
  220. * @type {Rectangle}
  221. * @readonly
  222. */
  223. rectangle: {
  224. get: function() {
  225. return this._rectangle;
  226. }
  227. }
  228. });
  229. /**
  230. * This value is used as the default brightness for the imagery layer if one is not provided during construction
  231. * or by the imagery provider. This value does not modify the brightness of the imagery.
  232. * @type {Number}
  233. * @default 1.0
  234. */
  235. ImageryLayer.DEFAULT_BRIGHTNESS = 1.0;
  236. /**
  237. * This value is used as the default contrast for the imagery layer if one is not provided during construction
  238. * or by the imagery provider. This value does not modify the contrast of the imagery.
  239. * @type {Number}
  240. * @default 1.0
  241. */
  242. ImageryLayer.DEFAULT_CONTRAST = 1.0;
  243. /**
  244. * This value is used as the default hue for the imagery layer if one is not provided during construction
  245. * or by the imagery provider. This value does not modify the hue of the imagery.
  246. * @type {Number}
  247. * @default 0.0
  248. */
  249. ImageryLayer.DEFAULT_HUE = 0.0;
  250. /**
  251. * This value is used as the default saturation for the imagery layer if one is not provided during construction
  252. * or by the imagery provider. This value does not modify the saturation of the imagery.
  253. * @type {Number}
  254. * @default 1.0
  255. */
  256. ImageryLayer.DEFAULT_SATURATION = 1.0;
  257. /**
  258. * This value is used as the default gamma for the imagery layer if one is not provided during construction
  259. * or by the imagery provider. This value does not modify the gamma of the imagery.
  260. * @type {Number}
  261. * @default 1.0
  262. */
  263. ImageryLayer.DEFAULT_GAMMA = 1.0;
  264. /**
  265. * Gets a value indicating whether this layer is the base layer in the
  266. * {@link ImageryLayerCollection}. The base layer is the one that underlies all
  267. * others. It is special in that it is treated as if it has global rectangle, even if
  268. * it actually does not, by stretching the texels at the edges over the entire
  269. * globe.
  270. *
  271. * @returns {Boolean} true if this is the base layer; otherwise, false.
  272. */
  273. ImageryLayer.prototype.isBaseLayer = function() {
  274. return this._isBaseLayer;
  275. };
  276. /**
  277. * Returns true if this object was destroyed; otherwise, false.
  278. * <br /><br />
  279. * If this object was destroyed, it should not be used; calling any function other than
  280. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  281. *
  282. * @returns {Boolean} True if this object was destroyed; otherwise, false.
  283. *
  284. * @see ImageryLayer#destroy
  285. */
  286. ImageryLayer.prototype.isDestroyed = function() {
  287. return false;
  288. };
  289. /**
  290. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  291. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  292. * <br /><br />
  293. * Once an object is destroyed, it should not be used; calling any function other than
  294. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  295. * assign the return value (<code>undefined</code>) to the object as done in the example.
  296. *
  297. * @returns {undefined}
  298. *
  299. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  300. *
  301. * @see ImageryLayer#isDestroyed
  302. *
  303. * @example
  304. * imageryLayer = imageryLayer && imageryLayer.destroy();
  305. */
  306. ImageryLayer.prototype.destroy = function() {
  307. return destroyObject(this);
  308. };
  309. var imageryBoundsScratch = new Rectangle();
  310. var tileImageryBoundsScratch = new Rectangle();
  311. var clippedRectangleScratch = new Rectangle();
  312. /**
  313. * Create skeletons for the imagery tiles that partially or completely overlap a given terrain
  314. * tile.
  315. *
  316. * @private
  317. *
  318. * @param {Tile} tile The terrain tile.
  319. * @param {TerrainProvider} terrainProvider The terrain provider associated with the terrain tile.
  320. * @param {Number} insertionPoint The position to insert new skeletons before in the tile's imagery lsit.
  321. * @returns {Boolean} true if this layer overlaps any portion of the terrain tile; otherwise, false.
  322. */
  323. ImageryLayer.prototype._createTileImagerySkeletons = function(tile, terrainProvider, insertionPoint) {
  324. var surfaceTile = tile.data;
  325. if (defined(this._minimumTerrainLevel) && tile.level < this._minimumTerrainLevel) {
  326. return false;
  327. }
  328. if (defined(this._maximumTerrainLevel) && tile.level > this._maximumTerrainLevel) {
  329. return false;
  330. }
  331. var imageryProvider = this._imageryProvider;
  332. if (!defined(insertionPoint)) {
  333. insertionPoint = surfaceTile.imagery.length;
  334. }
  335. if (!imageryProvider.ready) {
  336. // The imagery provider is not ready, so we can't create skeletons, yet.
  337. // Instead, add a placeholder so that we'll know to create
  338. // the skeletons once the provider is ready.
  339. this._skeletonPlaceholder.loadingImagery.addReference();
  340. surfaceTile.imagery.splice(insertionPoint, 0, this._skeletonPlaceholder);
  341. return true;
  342. }
  343. // Compute the rectangle of the imagery from this imageryProvider that overlaps
  344. // the geometry tile. The ImageryProvider and ImageryLayer both have the
  345. // opportunity to constrain the rectangle. The imagery TilingScheme's rectangle
  346. // always fully contains the ImageryProvider's rectangle.
  347. var imageryBounds = Rectangle.intersection(imageryProvider.rectangle, this._rectangle, imageryBoundsScratch);
  348. var rectangle = Rectangle.intersection(tile.rectangle, imageryBounds, tileImageryBoundsScratch);
  349. if (!defined(rectangle)) {
  350. // There is no overlap between this terrain tile and this imagery
  351. // provider. Unless this is the base layer, no skeletons need to be created.
  352. // We stretch texels at the edge of the base layer over the entire globe.
  353. if (!this.isBaseLayer()) {
  354. return false;
  355. }
  356. var baseImageryRectangle = imageryBounds;
  357. var baseTerrainRectangle = tile.rectangle;
  358. rectangle = tileImageryBoundsScratch;
  359. if (baseTerrainRectangle.south >= baseImageryRectangle.north) {
  360. rectangle.north = rectangle.south = baseImageryRectangle.north;
  361. } else if (baseTerrainRectangle.north <= baseImageryRectangle.south) {
  362. rectangle.north = rectangle.south = baseImageryRectangle.south;
  363. }
  364. if (baseTerrainRectangle.west >= baseImageryRectangle.east) {
  365. rectangle.west = rectangle.east = baseImageryRectangle.east;
  366. } else if (baseTerrainRectangle.east <= baseImageryRectangle.west) {
  367. rectangle.west = rectangle.east = baseImageryRectangle.west;
  368. }
  369. }
  370. var latitudeClosestToEquator = 0.0;
  371. if (rectangle.south > 0.0) {
  372. latitudeClosestToEquator = rectangle.south;
  373. } else if (rectangle.north < 0.0) {
  374. latitudeClosestToEquator = rectangle.north;
  375. }
  376. // Compute the required level in the imagery tiling scheme.
  377. // The errorRatio should really be imagerySSE / terrainSSE rather than this hard-coded value.
  378. // But first we need configurable imagery SSE and we need the rendering to be able to handle more
  379. // images attached to a terrain tile than there are available texture units. So that's for the future.
  380. var errorRatio = 1.0;
  381. var targetGeometricError = errorRatio * terrainProvider.getLevelMaximumGeometricError(tile.level);
  382. var imageryLevel = getLevelWithMaximumTexelSpacing(this, targetGeometricError, latitudeClosestToEquator);
  383. imageryLevel = Math.max(0, imageryLevel);
  384. var maximumLevel = imageryProvider.maximumLevel;
  385. if (imageryLevel > maximumLevel) {
  386. imageryLevel = maximumLevel;
  387. }
  388. if (defined(imageryProvider.minimumLevel)) {
  389. var minimumLevel = imageryProvider.minimumLevel;
  390. if (imageryLevel < minimumLevel) {
  391. imageryLevel = minimumLevel;
  392. }
  393. }
  394. var imageryTilingScheme = imageryProvider.tilingScheme;
  395. var northwestTileCoordinates = imageryTilingScheme.positionToTileXY(Rectangle.northwest(rectangle), imageryLevel);
  396. var southeastTileCoordinates = imageryTilingScheme.positionToTileXY(Rectangle.southeast(rectangle), imageryLevel);
  397. // If the southeast corner of the rectangle lies very close to the north or west side
  398. // of the southeast tile, we don't actually need the southernmost or easternmost
  399. // tiles.
  400. // Similarly, if the northwest corner of the rectangle lies very close to the south or east side
  401. // of the northwest tile, we don't actually need the northernmost or westernmost tiles.
  402. // We define "very close" as being within 1/512 of the width of the tile.
  403. var veryCloseX = tile.rectangle.height / 512.0;
  404. var veryCloseY = tile.rectangle.width / 512.0;
  405. var northwestTileRectangle = imageryTilingScheme.tileXYToRectangle(northwestTileCoordinates.x, northwestTileCoordinates.y, imageryLevel);
  406. if (Math.abs(northwestTileRectangle.south - tile.rectangle.north) < veryCloseY && northwestTileCoordinates.y < southeastTileCoordinates.y) {
  407. ++northwestTileCoordinates.y;
  408. }
  409. if (Math.abs(northwestTileRectangle.east - tile.rectangle.west) < veryCloseX && northwestTileCoordinates.x < southeastTileCoordinates.x) {
  410. ++northwestTileCoordinates.x;
  411. }
  412. var southeastTileRectangle = imageryTilingScheme.tileXYToRectangle(southeastTileCoordinates.x, southeastTileCoordinates.y, imageryLevel);
  413. if (Math.abs(southeastTileRectangle.north - tile.rectangle.south) < veryCloseY && southeastTileCoordinates.y > northwestTileCoordinates.y) {
  414. --southeastTileCoordinates.y;
  415. }
  416. if (Math.abs(southeastTileRectangle.west - tile.rectangle.east) < veryCloseX && southeastTileCoordinates.x > northwestTileCoordinates.x) {
  417. --southeastTileCoordinates.x;
  418. }
  419. // Create TileImagery instances for each imagery tile overlapping this terrain tile.
  420. // We need to do all texture coordinate computations in the imagery tile's tiling scheme.
  421. var terrainRectangle = tile.rectangle;
  422. var imageryRectangle = imageryTilingScheme.tileXYToRectangle(northwestTileCoordinates.x, northwestTileCoordinates.y, imageryLevel);
  423. var clippedImageryRectangle = Rectangle.intersection(imageryRectangle, imageryBounds, clippedRectangleScratch);
  424. var minU;
  425. var maxU = 0.0;
  426. var minV = 1.0;
  427. var maxV;
  428. // If this is the northern-most or western-most tile in the imagery tiling scheme,
  429. // it may not start at the northern or western edge of the terrain tile.
  430. // Calculate where it does start.
  431. if (!this.isBaseLayer() && Math.abs(clippedImageryRectangle.west - tile.rectangle.west) >= veryCloseX) {
  432. maxU = Math.min(1.0, (clippedImageryRectangle.west - terrainRectangle.west) / terrainRectangle.width);
  433. }
  434. if (!this.isBaseLayer() && Math.abs(clippedImageryRectangle.north - tile.rectangle.north) >= veryCloseY) {
  435. minV = Math.max(0.0, (clippedImageryRectangle.north - terrainRectangle.south) / terrainRectangle.height);
  436. }
  437. var initialMinV = minV;
  438. for ( var i = northwestTileCoordinates.x; i <= southeastTileCoordinates.x; i++) {
  439. minU = maxU;
  440. imageryRectangle = imageryTilingScheme.tileXYToRectangle(i, northwestTileCoordinates.y, imageryLevel);
  441. clippedImageryRectangle = Rectangle.intersection(imageryRectangle, imageryBounds, clippedRectangleScratch);
  442. maxU = Math.min(1.0, (clippedImageryRectangle.east - terrainRectangle.west) / terrainRectangle.width);
  443. // If this is the eastern-most imagery tile mapped to this terrain tile,
  444. // and there are more imagery tiles to the east of this one, the maxU
  445. // should be 1.0 to make sure rounding errors don't make the last
  446. // image fall shy of the edge of the terrain tile.
  447. if (i === southeastTileCoordinates.x && (this.isBaseLayer() || Math.abs(clippedImageryRectangle.east - tile.rectangle.east) < veryCloseX)) {
  448. maxU = 1.0;
  449. }
  450. minV = initialMinV;
  451. for ( var j = northwestTileCoordinates.y; j <= southeastTileCoordinates.y; j++) {
  452. maxV = minV;
  453. imageryRectangle = imageryTilingScheme.tileXYToRectangle(i, j, imageryLevel);
  454. clippedImageryRectangle = Rectangle.intersection(imageryRectangle, imageryBounds, clippedRectangleScratch);
  455. minV = Math.max(0.0, (clippedImageryRectangle.south - terrainRectangle.south) / terrainRectangle.height);
  456. // If this is the southern-most imagery tile mapped to this terrain tile,
  457. // and there are more imagery tiles to the south of this one, the minV
  458. // should be 0.0 to make sure rounding errors don't make the last
  459. // image fall shy of the edge of the terrain tile.
  460. if (j === southeastTileCoordinates.y && (this.isBaseLayer() || Math.abs(clippedImageryRectangle.south - tile.rectangle.south) < veryCloseY)) {
  461. minV = 0.0;
  462. }
  463. var texCoordsRectangle = new Cartesian4(minU, minV, maxU, maxV);
  464. var imagery = this.getImageryFromCache(i, j, imageryLevel, imageryRectangle);
  465. surfaceTile.imagery.splice(insertionPoint, 0, new TileImagery(imagery, texCoordsRectangle));
  466. ++insertionPoint;
  467. }
  468. }
  469. return true;
  470. };
  471. /**
  472. * Calculate the translation and scale for a particular {@link TileImagery} attached to a
  473. * particular terrain tile.
  474. *
  475. * @private
  476. *
  477. * @param {Tile} tile The terrain tile.
  478. * @param {TileImagery} tileImagery The imagery tile mapping.
  479. * @returns {Cartesian4} The translation and scale where X and Y are the translation and Z and W
  480. * are the scale.
  481. */
  482. ImageryLayer.prototype._calculateTextureTranslationAndScale = function(tile, tileImagery) {
  483. var imageryRectangle = tileImagery.readyImagery.rectangle;
  484. var terrainRectangle = tile.rectangle;
  485. var terrainWidth = terrainRectangle.width;
  486. var terrainHeight = terrainRectangle.height;
  487. var scaleX = terrainWidth / imageryRectangle.width;
  488. var scaleY = terrainHeight / imageryRectangle.height;
  489. return new Cartesian4(
  490. scaleX * (terrainRectangle.west - imageryRectangle.west) / terrainWidth,
  491. scaleY * (terrainRectangle.south - imageryRectangle.south) / terrainHeight,
  492. scaleX,
  493. scaleY);
  494. };
  495. /**
  496. * Request a particular piece of imagery from the imagery provider. This method handles raising an
  497. * error event if the request fails, and retrying the request if necessary.
  498. *
  499. * @private
  500. *
  501. * @param {Imagery} imagery The imagery to request.
  502. */
  503. ImageryLayer.prototype._requestImagery = function(imagery) {
  504. var imageryProvider = this._imageryProvider;
  505. var that = this;
  506. function success(image) {
  507. if (!defined(image)) {
  508. return failure();
  509. }
  510. imagery.image = image;
  511. imagery.state = ImageryState.RECEIVED;
  512. TileProviderError.handleSuccess(that._requestImageError);
  513. }
  514. function failure(e) {
  515. // Initially assume failure. handleError may retry, in which case the state will
  516. // change to TRANSITIONING.
  517. imagery.state = ImageryState.FAILED;
  518. var message = 'Failed to obtain image tile X: ' + imagery.x + ' Y: ' + imagery.y + ' Level: ' + imagery.level + '.';
  519. that._requestImageError = TileProviderError.handleError(
  520. that._requestImageError,
  521. imageryProvider,
  522. imageryProvider.errorEvent,
  523. message,
  524. imagery.x, imagery.y, imagery.level,
  525. doRequest);
  526. }
  527. function doRequest() {
  528. imagery.state = ImageryState.TRANSITIONING;
  529. var imagePromise = imageryProvider.requestImage(imagery.x, imagery.y, imagery.level);
  530. if (!defined(imagePromise)) {
  531. // Too many parallel requests, so postpone loading tile.
  532. imagery.state = ImageryState.UNLOADED;
  533. return;
  534. }
  535. if (defined(imageryProvider.getTileCredits)) {
  536. imagery.credits = imageryProvider.getTileCredits(imagery.x, imagery.y, imagery.level);
  537. }
  538. when(imagePromise, success, failure);
  539. }
  540. doRequest();
  541. };
  542. /**
  543. * Create a WebGL texture for a given {@link Imagery} instance.
  544. *
  545. * @private
  546. *
  547. * @param {Context} context The rendered context to use to create textures.
  548. * @param {Imagery} imagery The imagery for which to create a texture.
  549. */
  550. ImageryLayer.prototype._createTexture = function(context, imagery) {
  551. var imageryProvider = this._imageryProvider;
  552. // If this imagery provider has a discard policy, use it to check if this
  553. // image should be discarded.
  554. if (defined(imageryProvider.tileDiscardPolicy)) {
  555. var discardPolicy = imageryProvider.tileDiscardPolicy;
  556. if (defined(discardPolicy)) {
  557. // If the discard policy is not ready yet, transition back to the
  558. // RECEIVED state and we'll try again next time.
  559. if (!discardPolicy.isReady()) {
  560. imagery.state = ImageryState.RECEIVED;
  561. return;
  562. }
  563. // Mark discarded imagery tiles invalid. Parent imagery will be used instead.
  564. if (discardPolicy.shouldDiscardImage(imagery.image)) {
  565. imagery.state = ImageryState.INVALID;
  566. return;
  567. }
  568. }
  569. }
  570. // Imagery does not need to be discarded, so upload it to WebGL.
  571. var texture = context.createTexture2D({
  572. source : imagery.image,
  573. pixelFormat : imageryProvider.hasAlphaChannel ? PixelFormat.RGBA : PixelFormat.RGB
  574. });
  575. imagery.texture = texture;
  576. imagery.image = undefined;
  577. imagery.state = ImageryState.TEXTURE_LOADED;
  578. };
  579. /**
  580. * Reproject a texture to a {@link GeographicProjection}, if necessary, and generate
  581. * mipmaps for the geographic texture.
  582. *
  583. * @private
  584. *
  585. * @param {Context} context The rendered context to use.
  586. * @param {Imagery} imagery The imagery instance to reproject.
  587. */
  588. ImageryLayer.prototype._reprojectTexture = function(context, imagery) {
  589. var texture = imagery.texture;
  590. var rectangle = imagery.rectangle;
  591. // Reproject this texture if it is not already in a geographic projection and
  592. // the pixels are more than 1e-5 radians apart. The pixel spacing cutoff
  593. // avoids precision problems in the reprojection transformation while making
  594. // no noticeable difference in the georeferencing of the image.
  595. if (!(this._imageryProvider.tilingScheme instanceof GeographicTilingScheme) &&
  596. rectangle.width / texture.width > 1e-5) {
  597. var reprojectedTexture = reprojectToGeographic(this, context, texture, imagery.rectangle);
  598. texture.destroy();
  599. imagery.texture = texture = reprojectedTexture;
  600. }
  601. // Use mipmaps if this texture has power-of-two dimensions.
  602. if (CesiumMath.isPowerOfTwo(texture.width) && CesiumMath.isPowerOfTwo(texture.height)) {
  603. var mipmapSampler = context.cache.imageryLayer_mipmapSampler;
  604. if (!defined(mipmapSampler)) {
  605. var maximumSupportedAnisotropy = context.maximumTextureFilterAnisotropy;
  606. mipmapSampler = context.cache.imageryLayer_mipmapSampler = context.createSampler({
  607. wrapS : TextureWrap.CLAMP_TO_EDGE,
  608. wrapT : TextureWrap.CLAMP_TO_EDGE,
  609. minificationFilter : TextureMinificationFilter.LINEAR_MIPMAP_LINEAR,
  610. magnificationFilter : TextureMagnificationFilter.LINEAR,
  611. maximumAnisotropy : Math.min(maximumSupportedAnisotropy, defaultValue(this._maximumAnisotropy, maximumSupportedAnisotropy))
  612. });
  613. }
  614. texture.generateMipmap(MipmapHint.NICEST);
  615. texture.sampler = mipmapSampler;
  616. } else {
  617. var nonMipmapSampler = context.cache.imageryLayer_nonMipmapSampler;
  618. if (!defined(nonMipmapSampler)) {
  619. nonMipmapSampler = context.cache.imageryLayer_nonMipmapSampler = context.createSampler({
  620. wrapS : TextureWrap.CLAMP_TO_EDGE,
  621. wrapT : TextureWrap.CLAMP_TO_EDGE,
  622. minificationFilter : TextureMinificationFilter.LINEAR,
  623. magnificationFilter : TextureMagnificationFilter.LINEAR
  624. });
  625. }
  626. texture.sampler = nonMipmapSampler;
  627. }
  628. imagery.state = ImageryState.READY;
  629. };
  630. ImageryLayer.prototype.getImageryFromCache = function(x, y, level, imageryRectangle) {
  631. var cacheKey = getImageryCacheKey(x, y, level);
  632. var imagery = this._imageryCache[cacheKey];
  633. if (!defined(imagery)) {
  634. imagery = new Imagery(this, x, y, level, imageryRectangle);
  635. this._imageryCache[cacheKey] = imagery;
  636. }
  637. imagery.addReference();
  638. return imagery;
  639. };
  640. ImageryLayer.prototype.removeImageryFromCache = function(imagery) {
  641. var cacheKey = getImageryCacheKey(imagery.x, imagery.y, imagery.level);
  642. delete this._imageryCache[cacheKey];
  643. };
  644. function getImageryCacheKey(x, y, level) {
  645. return JSON.stringify([x, y, level]);
  646. }
  647. var uniformMap = {
  648. u_textureDimensions : function() {
  649. return this.textureDimensions;
  650. },
  651. u_texture : function() {
  652. return this.texture;
  653. },
  654. textureDimensions : new Cartesian2(),
  655. texture : undefined
  656. };
  657. var float32ArrayScratch = FeatureDetection.supportsTypedArrays() ? new Float32Array(2 * 64) : undefined;
  658. function reprojectToGeographic(imageryLayer, context, texture, rectangle) {
  659. // This function has gone through a number of iterations, because GPUs are awesome.
  660. //
  661. // Originally, we had a very simple vertex shader and computed the Web Mercator texture coordinates
  662. // per-fragment in the fragment shader. That worked well, except on mobile devices, because
  663. // fragment shaders have limited precision on many mobile devices. The result was smearing artifacts
  664. // at medium zoom levels because different geographic texture coordinates would be reprojected to Web
  665. // Mercator as the same value.
  666. //
  667. // Our solution was to reproject to Web Mercator in the vertex shader instead of the fragment shader.
  668. // This required far more vertex data. With fragment shader reprojection, we only needed a single quad.
  669. // But to achieve the same precision with vertex shader reprojection, we needed a vertex for each
  670. // output pixel. So we used a grid of 256x256 vertices, because most of our imagery
  671. // tiles are 256x256. Fortunately the grid could be created and uploaded to the GPU just once and
  672. // re-used for all reprojections, so the performance was virtually unchanged from our original fragment
  673. // shader approach. See https://github.com/AnalyticalGraphicsInc/cesium/pull/714.
  674. //
  675. // Over a year later, we noticed (https://github.com/AnalyticalGraphicsInc/cesium/issues/2110)
  676. // that our reprojection code was creating a rare but severe artifact on some GPUs (Intel HD 4600
  677. // for one). The problem was that the GLSL sin function on these GPUs had a discontinuity at fine scales in
  678. // a few places.
  679. //
  680. // We solved this by implementing a more reliable sin function based on the CORDIC algorithm
  681. // (https://github.com/AnalyticalGraphicsInc/cesium/pull/2111). Even though this was a fair
  682. // amount of code to be executing per vertex, the performance seemed to be pretty good on most GPUs.
  683. // Unfortunately, on some GPUs, the performance was absolutely terrible
  684. // (https://github.com/AnalyticalGraphicsInc/cesium/issues/2258).
  685. //
  686. // So that brings us to our current solution, the one you see here. Effectively, we compute the Web
  687. // Mercator texture coordinates on the CPU and store the T coordinate with each vertex (the S coordinate
  688. // is the same in Geographic and Web Mercator). To make this faster, we reduced our reprojection mesh
  689. // to be only 2 vertices wide and 64 vertices high. We should have reduced the width to 2 sooner,
  690. // because the extra vertices weren't buying us anything. The height of 64 means we are technically
  691. // doing a slightly less accurate reprojection than we were before, but we can't see the difference
  692. // so it's worth the 4x speedup.
  693. var reproject = context.cache.imageryLayer_reproject;
  694. if (!defined(reproject)) {
  695. reproject = context.cache.imageryLayer_reproject = {
  696. framebuffer : undefined,
  697. vertexArray : undefined,
  698. shaderProgram : undefined,
  699. renderState : undefined,
  700. sampler : undefined,
  701. destroy : function() {
  702. if (defined(this.framebuffer)) {
  703. this.framebuffer.destroy();
  704. }
  705. if (defined(this.vertexArray)) {
  706. this.vertexArray.destroy();
  707. }
  708. if (defined(this.shaderProgram)) {
  709. this.shaderProgram.destroy();
  710. }
  711. }
  712. };
  713. var positions = new Float32Array(2 * 64 * 2);
  714. var index = 0;
  715. for (var j = 0; j < 64; ++j) {
  716. var y = j / 63.0;
  717. positions[index++] = 0.0;
  718. positions[index++] = y;
  719. positions[index++] = 1.0;
  720. positions[index++] = y;
  721. }
  722. var reprojectAttributeIndices = {
  723. position : 0,
  724. webMercatorT : 1
  725. };
  726. var indices = TerrainProvider.getRegularGridIndices(2, 64);
  727. var indexBuffer = context.createIndexBuffer(indices, BufferUsage.STATIC_DRAW, IndexDatatype.UNSIGNED_SHORT);
  728. reproject.vertexArray = context.createVertexArray([
  729. {
  730. index : reprojectAttributeIndices.position,
  731. vertexBuffer : context.createVertexBuffer(positions, BufferUsage.STATIC_DRAW),
  732. componentsPerAttribute : 2
  733. },
  734. {
  735. index : reprojectAttributeIndices.webMercatorT,
  736. vertexBuffer : context.createVertexBuffer(64 * 2 * 4, BufferUsage.STREAM_DRAW),
  737. componentsPerAttribute : 1
  738. }
  739. ], indexBuffer);
  740. var vs = new ShaderSource({
  741. sources : [ReprojectWebMercatorVS]
  742. });
  743. reproject.shaderProgram = context.createShaderProgram(vs, ReprojectWebMercatorFS, reprojectAttributeIndices);
  744. reproject.sampler = context.createSampler({
  745. wrapS : TextureWrap.CLAMP_TO_EDGE,
  746. wrapT : TextureWrap.CLAMP_TO_EDGE,
  747. minificationFilter : TextureMinificationFilter.LINEAR,
  748. magnificationFilter : TextureMagnificationFilter.LINEAR
  749. });
  750. }
  751. texture.sampler = reproject.sampler;
  752. var width = texture.width;
  753. var height = texture.height;
  754. uniformMap.textureDimensions.x = width;
  755. uniformMap.textureDimensions.y = height;
  756. uniformMap.texture = texture;
  757. var sinLatitude = Math.sin(rectangle.south);
  758. var southMercatorY = 0.5 * Math.log((1 + sinLatitude) / (1 - sinLatitude));
  759. sinLatitude = Math.sin(rectangle.north);
  760. var northMercatorY = 0.5 * Math.log((1 + sinLatitude) / (1 - sinLatitude));
  761. var oneOverMercatorHeight = 1.0 / (northMercatorY - southMercatorY);
  762. var outputTexture = context.createTexture2D({
  763. width : width,
  764. height : height,
  765. pixelFormat : texture.pixelFormat,
  766. pixelDatatype : texture.pixelDatatype,
  767. preMultiplyAlpha : texture.preMultiplyAlpha
  768. });
  769. // Allocate memory for the mipmaps. Failure to do this before rendering
  770. // to the texture via the FBO, and calling generateMipmap later,
  771. // will result in the texture appearing blank. I can't pretend to
  772. // understand exactly why this is.
  773. outputTexture.generateMipmap(MipmapHint.NICEST);
  774. if (defined(reproject.framebuffer)) {
  775. reproject.framebuffer.destroy();
  776. }
  777. reproject.framebuffer = context.createFramebuffer({
  778. colorTextures : [outputTexture]
  779. });
  780. reproject.framebuffer.destroyAttachments = false;
  781. var south = rectangle.south;
  782. var north = rectangle.north;
  783. var webMercatorT = float32ArrayScratch;
  784. var outputIndex = 0;
  785. for (var webMercatorTIndex = 0; webMercatorTIndex < 64; ++webMercatorTIndex) {
  786. var fraction = webMercatorTIndex / 63.0;
  787. var latitude = CesiumMath.lerp(south, north, fraction);
  788. sinLatitude = Math.sin(latitude);
  789. var mercatorY = 0.5 * Math.log((1.0 + sinLatitude) / (1.0 - sinLatitude));
  790. var mercatorFraction = (mercatorY - southMercatorY) * oneOverMercatorHeight;
  791. webMercatorT[outputIndex++] = mercatorFraction;
  792. webMercatorT[outputIndex++] = mercatorFraction;
  793. }
  794. reproject.vertexArray.getAttribute(1).vertexBuffer.copyFromArrayView(webMercatorT);
  795. var command = new ClearCommand({
  796. color : Color.BLACK,
  797. framebuffer : reproject.framebuffer
  798. });
  799. command.execute(context);
  800. if ((!defined(reproject.renderState)) ||
  801. (reproject.renderState.viewport.width !== width) ||
  802. (reproject.renderState.viewport.height !== height)) {
  803. reproject.renderState = context.createRenderState({
  804. viewport : new BoundingRectangle(0, 0, width, height)
  805. });
  806. }
  807. var drawCommand = new DrawCommand({
  808. framebuffer : reproject.framebuffer,
  809. shaderProgram : reproject.shaderProgram,
  810. renderState : reproject.renderState,
  811. primitiveType : PrimitiveType.TRIANGLES,
  812. vertexArray : reproject.vertexArray,
  813. uniformMap : uniformMap
  814. });
  815. drawCommand.execute(context);
  816. return outputTexture;
  817. }
  818. /**
  819. * Gets the level with the specified world coordinate spacing between texels, or less.
  820. *
  821. * @param {Number} texelSpacing The texel spacing for which to find a corresponding level.
  822. * @param {Number} latitudeClosestToEquator The latitude closest to the equator that we're concerned with.
  823. * @returns {Number} The level with the specified texel spacing or less.
  824. */
  825. function getLevelWithMaximumTexelSpacing(layer, texelSpacing, latitudeClosestToEquator) {
  826. // PERFORMANCE_IDEA: factor out the stuff that doesn't change.
  827. var imageryProvider = layer._imageryProvider;
  828. var tilingScheme = imageryProvider.tilingScheme;
  829. var ellipsoid = tilingScheme.ellipsoid;
  830. var latitudeFactor = !(layer._imageryProvider.tilingScheme instanceof GeographicTilingScheme) ? Math.cos(latitudeClosestToEquator) : 1.0;
  831. var tilingSchemeRectangle = tilingScheme.rectangle;
  832. var levelZeroMaximumTexelSpacing = ellipsoid.maximumRadius * tilingSchemeRectangle.width * latitudeFactor / (imageryProvider.tileWidth * tilingScheme.getNumberOfXTilesAtLevel(0));
  833. var twoToTheLevelPower = levelZeroMaximumTexelSpacing / texelSpacing;
  834. var level = Math.log(twoToTheLevelPower) / Math.log(2);
  835. var rounded = Math.round(level);
  836. return rounded | 0;
  837. }
  838. return ImageryLayer;
  839. });