ImageryLayer.js 54 KB

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