TimeDynamicImagery.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. import Check from '../Core/Check.js';
  2. import defaultValue from '../Core/defaultValue.js';
  3. import defined from '../Core/defined.js';
  4. import defineProperties from '../Core/defineProperties.js';
  5. import DeveloperError from '../Core/DeveloperError.js';
  6. import JulianDate from '../Core/JulianDate.js';
  7. import Request from '../Core/Request.js';
  8. import RequestType from '../Core/RequestType.js';
  9. /**
  10. * Provides functionality for ImageryProviders that have time dynamic imagery
  11. *
  12. * @alias TimeDynamicImagery
  13. * @constructor
  14. *
  15. * @param {Object} options Object with the following properties:
  16. * @param {Clock} options.clock A Clock instance that is used when determining the value for the time dimension. Required when <code>options.times</code> is specified.
  17. * @param {TimeIntervalCollection} options.times TimeIntervalCollection with its <code>data</code> property being an object containing time dynamic dimension and their values.
  18. * @param {Function} options.requestImageFunction A function that will request imagery tiles.
  19. * @param {Function} options.reloadFunction A function that will be called when all imagery tiles need to be reloaded.
  20. */
  21. function TimeDynamicImagery(options) {
  22. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  23. //>>includeStart('debug', pragmas.debug);
  24. Check.typeOf.object('options.clock', options.clock);
  25. Check.typeOf.object('options.times', options.times);
  26. Check.typeOf.func('options.requestImageFunction', options.requestImageFunction);
  27. Check.typeOf.func('options.reloadFunction', options.reloadFunction);
  28. //>>includeEnd('debug');
  29. this._tileCache = {};
  30. this._tilesRequestedForInterval = [];
  31. var clock = this._clock = options.clock;
  32. this._times = options.times;
  33. this._requestImageFunction = options.requestImageFunction;
  34. this._reloadFunction = options.reloadFunction;
  35. this._currentIntervalIndex = -1;
  36. clock.onTick.addEventListener(this._clockOnTick, this);
  37. this._clockOnTick(clock);
  38. }
  39. defineProperties(TimeDynamicImagery.prototype, {
  40. /**
  41. * Gets or sets a clock that is used to get keep the time used for time dynamic parameters.
  42. * @memberof TimeDynamicImagery.prototype
  43. * @type {Clock}
  44. */
  45. clock : {
  46. get : function() {
  47. return this._clock;
  48. },
  49. set : function(value) {
  50. //>>includeStart('debug', pragmas.debug);
  51. if (!defined(value)) {
  52. throw new DeveloperError('value is required.');
  53. }
  54. //>>includeEnd('debug');
  55. if (this._clock !== value) {
  56. this._clock = value;
  57. this._clockOnTick(value);
  58. this._reloadFunction();
  59. }
  60. }
  61. },
  62. /**
  63. * Gets or sets a time interval collection.
  64. * @memberof TimeDynamicImagery.prototype
  65. * @type {TimeIntervalCollection}
  66. */
  67. times : {
  68. get : function() {
  69. return this._times;
  70. },
  71. set : function(value) {
  72. //>>includeStart('debug', pragmas.debug);
  73. if (!defined(value)) {
  74. throw new DeveloperError('value is required.');
  75. }
  76. //>>includeEnd('debug');
  77. if (this._times !== value) {
  78. this._times = value;
  79. this._clockOnTick(this._clock);
  80. this._reloadFunction();
  81. }
  82. }
  83. },
  84. /**
  85. * Gets the current interval.
  86. * @memberof TimeDynamicImagery.prototype
  87. * @type {TimeInterval}
  88. */
  89. currentInterval : {
  90. get : function() {
  91. return this._times.get(this._currentIntervalIndex);
  92. }
  93. }
  94. });
  95. /**
  96. * Gets the tile from the cache if its available.
  97. *
  98. * @param {Number} x The tile X coordinate.
  99. * @param {Number} y The tile Y coordinate.
  100. * @param {Number} level The tile level.
  101. * @param {Request} [request] The request object. Intended for internal use only.
  102. *
  103. * @returns {Promise.<Image>|undefined} A promise for the image that will resolve when the image is available, or
  104. * undefined if the tile is not in the cache.
  105. */
  106. TimeDynamicImagery.prototype.getFromCache = function(x, y, level, request) {
  107. var key = getKey(x, y, level);
  108. var result;
  109. var cache = this._tileCache[this._currentIntervalIndex];
  110. if (defined(cache) && defined(cache[key])) {
  111. var item = cache[key];
  112. result = item.promise
  113. .otherwise(function(e) {
  114. // Set the correct state in case it was cancelled
  115. request.state = item.request.state;
  116. throw e;
  117. });
  118. delete cache[key];
  119. }
  120. return result;
  121. };
  122. /**
  123. * Checks if the next interval is approaching and will start preload the tile if necessary. Otherwise it will
  124. * just add the tile to a list to preload when we approach the next interval.
  125. *
  126. * @param {Number} x The tile X coordinate.
  127. * @param {Number} y The tile Y coordinate.
  128. * @param {Number} level The tile level.
  129. * @param {Request} [request] The request object. Intended for internal use only.
  130. */
  131. TimeDynamicImagery.prototype.checkApproachingInterval = function(x, y, level, request) {
  132. var key = getKey(x, y, level);
  133. var tilesRequestedForInterval = this._tilesRequestedForInterval;
  134. // If we are approaching an interval, preload this tile in the next interval
  135. var approachingInterval = getApproachingInterval(this);
  136. var tile = {
  137. key : key,
  138. // Determines priority based on camera distance to the tile.
  139. // Since the imagery regardless of time will be attached to the same tile we can just steal it.
  140. priorityFunction : request.priorityFunction
  141. };
  142. if (!defined(approachingInterval) || !addToCache(this, tile, approachingInterval)) {
  143. // Add to recent request list if we aren't approaching and interval or the request was throttled
  144. tilesRequestedForInterval.push(tile);
  145. }
  146. // Don't let the tile list get out of hand
  147. if (tilesRequestedForInterval.length >= 512) {
  148. tilesRequestedForInterval.splice(0, 256);
  149. }
  150. };
  151. TimeDynamicImagery.prototype._clockOnTick = function(clock) {
  152. var time = clock.currentTime;
  153. var times = this._times;
  154. var index = times.indexOf(time);
  155. var currentIntervalIndex = this._currentIntervalIndex;
  156. if (index !== currentIntervalIndex) {
  157. // Cancel all outstanding requests and clear out caches not from current time interval
  158. var currentCache = this._tileCache[currentIntervalIndex];
  159. for (var t in currentCache) {
  160. if (currentCache.hasOwnProperty(t)) {
  161. currentCache[t].request.cancel();
  162. }
  163. }
  164. delete this._tileCache[currentIntervalIndex];
  165. this._tilesRequestedForInterval = [];
  166. this._currentIntervalIndex = index;
  167. this._reloadFunction();
  168. return;
  169. }
  170. var approachingInterval = getApproachingInterval(this);
  171. if (defined(approachingInterval)) {
  172. // Start loading recent tiles from end of this._tilesRequestedForInterval
  173. // We keep preloading until we hit a throttling limit.
  174. var tilesRequested = this._tilesRequestedForInterval;
  175. var success = true;
  176. while (success) {
  177. if (tilesRequested.length === 0) {
  178. break;
  179. }
  180. var tile = tilesRequested.pop();
  181. success = addToCache(this, tile, approachingInterval);
  182. if (!success) {
  183. tilesRequested.push(tile);
  184. }
  185. }
  186. }
  187. };
  188. function getKey(x, y, level) {
  189. return x + '-' + y + '-' + level;
  190. }
  191. function getKeyElements(key) {
  192. var s = key.split('-');
  193. if (s.length !== 3) {
  194. return undefined;
  195. }
  196. return {
  197. x : Number(s[0]),
  198. y : Number(s[1]),
  199. level : Number(s[2])
  200. };
  201. }
  202. function getApproachingInterval(that) {
  203. var times = that._times;
  204. if (!defined(times)) {
  205. return undefined;
  206. }
  207. var clock = that._clock;
  208. var time = clock.currentTime;
  209. var isAnimating = clock.canAnimate && clock.shouldAnimate;
  210. var multiplier = clock.multiplier;
  211. if (!isAnimating && multiplier !== 0) {
  212. return undefined;
  213. }
  214. var seconds;
  215. var index = times.indexOf(time);
  216. if (index < 0) {
  217. return undefined;
  218. }
  219. var interval = times.get(index);
  220. if (multiplier > 0) { // animating forward
  221. seconds = JulianDate.secondsDifference(interval.stop, time);
  222. ++index;
  223. } else { //backwards
  224. seconds = JulianDate.secondsDifference(interval.start, time); // Will be negative
  225. --index;
  226. }
  227. seconds /= multiplier; // Will always be positive
  228. // Less than 5 wall time seconds
  229. return (index >= 0 && seconds <= 5.0) ? times.get(index) : undefined;
  230. }
  231. function addToCache(that, tile, interval) {
  232. var index = that._times.indexOf(interval.start);
  233. var tileCache = that._tileCache;
  234. var intervalTileCache = tileCache[index];
  235. if (!defined(intervalTileCache)) {
  236. intervalTileCache = tileCache[index] = {};
  237. }
  238. var key = tile.key;
  239. if (defined(intervalTileCache[key])) {
  240. return true; // Already in the cache
  241. }
  242. var keyElements = getKeyElements(key);
  243. var request = new Request({
  244. throttle : true,
  245. throttleByServer : true,
  246. type : RequestType.IMAGERY,
  247. priorityFunction : tile.priorityFunction
  248. });
  249. var promise = that._requestImageFunction(keyElements.x, keyElements.y, keyElements.level, request, interval);
  250. if (!defined(promise)) {
  251. return false;
  252. }
  253. intervalTileCache[key] = {
  254. promise : promise,
  255. request : request
  256. };
  257. return true;
  258. }
  259. export default TimeDynamicImagery;