ImageryLayer.js 55 KB

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