ImageryLayer.js 57 KB

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