import Check from '../Core/Check.js';
import defaultValue from '../Core/defaultValue.js';
import defined from '../Core/defined.js';
import defineProperties from '../Core/defineProperties.js';
import DeveloperError from '../Core/DeveloperError.js';
import JulianDate from '../Core/JulianDate.js';
import Request from '../Core/Request.js';
import RequestType from '../Core/RequestType.js';
/**
* Provides functionality for ImageryProviders that have time dynamic imagery
*
* @alias TimeDynamicImagery
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Clock} options.clock A Clock instance that is used when determining the value for the time dimension. Required when options.times
is specified.
* @param {TimeIntervalCollection} options.times TimeIntervalCollection with its data
property being an object containing time dynamic dimension and their values.
* @param {Function} options.requestImageFunction A function that will request imagery tiles.
* @param {Function} options.reloadFunction A function that will be called when all imagery tiles need to be reloaded.
*/
function TimeDynamicImagery(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object('options.clock', options.clock);
Check.typeOf.object('options.times', options.times);
Check.typeOf.func('options.requestImageFunction', options.requestImageFunction);
Check.typeOf.func('options.reloadFunction', options.reloadFunction);
//>>includeEnd('debug');
this._tileCache = {};
this._tilesRequestedForInterval = [];
var clock = this._clock = options.clock;
this._times = options.times;
this._requestImageFunction = options.requestImageFunction;
this._reloadFunction = options.reloadFunction;
this._currentIntervalIndex = -1;
clock.onTick.addEventListener(this._clockOnTick, this);
this._clockOnTick(clock);
}
defineProperties(TimeDynamicImagery.prototype, {
/**
* Gets or sets a clock that is used to get keep the time used for time dynamic parameters.
* @memberof TimeDynamicImagery.prototype
* @type {Clock}
*/
clock : {
get : function() {
return this._clock;
},
set : function(value) {
//>>includeStart('debug', pragmas.debug);
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
//>>includeEnd('debug');
if (this._clock !== value) {
this._clock = value;
this._clockOnTick(value);
this._reloadFunction();
}
}
},
/**
* Gets or sets a time interval collection.
* @memberof TimeDynamicImagery.prototype
* @type {TimeIntervalCollection}
*/
times : {
get : function() {
return this._times;
},
set : function(value) {
//>>includeStart('debug', pragmas.debug);
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
//>>includeEnd('debug');
if (this._times !== value) {
this._times = value;
this._clockOnTick(this._clock);
this._reloadFunction();
}
}
},
/**
* Gets the current interval.
* @memberof TimeDynamicImagery.prototype
* @type {TimeInterval}
*/
currentInterval : {
get : function() {
return this._times.get(this._currentIntervalIndex);
}
}
});
/**
* Gets the tile from the cache if its available.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @param {Request} [request] The request object. Intended for internal use only.
*
* @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or
* undefined if the tile is not in the cache.
*/
TimeDynamicImagery.prototype.getFromCache = function(x, y, level, request) {
var key = getKey(x, y, level);
var result;
var cache = this._tileCache[this._currentIntervalIndex];
if (defined(cache) && defined(cache[key])) {
var item = cache[key];
result = item.promise
.otherwise(function(e) {
// Set the correct state in case it was cancelled
request.state = item.request.state;
throw e;
});
delete cache[key];
}
return result;
};
/**
* Checks if the next interval is approaching and will start preload the tile if necessary. Otherwise it will
* just add the tile to a list to preload when we approach the next interval.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @param {Request} [request] The request object. Intended for internal use only.
*/
TimeDynamicImagery.prototype.checkApproachingInterval = function(x, y, level, request) {
var key = getKey(x, y, level);
var tilesRequestedForInterval = this._tilesRequestedForInterval;
// If we are approaching an interval, preload this tile in the next interval
var approachingInterval = getApproachingInterval(this);
var tile = {
key : key,
// Determines priority based on camera distance to the tile.
// Since the imagery regardless of time will be attached to the same tile we can just steal it.
priorityFunction : request.priorityFunction
};
if (!defined(approachingInterval) || !addToCache(this, tile, approachingInterval)) {
// Add to recent request list if we aren't approaching and interval or the request was throttled
tilesRequestedForInterval.push(tile);
}
// Don't let the tile list get out of hand
if (tilesRequestedForInterval.length >= 512) {
tilesRequestedForInterval.splice(0, 256);
}
};
TimeDynamicImagery.prototype._clockOnTick = function(clock) {
var time = clock.currentTime;
var times = this._times;
var index = times.indexOf(time);
var currentIntervalIndex = this._currentIntervalIndex;
if (index !== currentIntervalIndex) {
// Cancel all outstanding requests and clear out caches not from current time interval
var currentCache = this._tileCache[currentIntervalIndex];
for (var t in currentCache) {
if (currentCache.hasOwnProperty(t)) {
currentCache[t].request.cancel();
}
}
delete this._tileCache[currentIntervalIndex];
this._tilesRequestedForInterval = [];
this._currentIntervalIndex = index;
this._reloadFunction();
return;
}
var approachingInterval = getApproachingInterval(this);
if (defined(approachingInterval)) {
// Start loading recent tiles from end of this._tilesRequestedForInterval
// We keep preloading until we hit a throttling limit.
var tilesRequested = this._tilesRequestedForInterval;
var success = true;
while (success) {
if (tilesRequested.length === 0) {
break;
}
var tile = tilesRequested.pop();
success = addToCache(this, tile, approachingInterval);
if (!success) {
tilesRequested.push(tile);
}
}
}
};
function getKey(x, y, level) {
return x + '-' + y + '-' + level;
}
function getKeyElements(key) {
var s = key.split('-');
if (s.length !== 3) {
return undefined;
}
return {
x : Number(s[0]),
y : Number(s[1]),
level : Number(s[2])
};
}
function getApproachingInterval(that) {
var times = that._times;
if (!defined(times)) {
return undefined;
}
var clock = that._clock;
var time = clock.currentTime;
var isAnimating = clock.canAnimate && clock.shouldAnimate;
var multiplier = clock.multiplier;
if (!isAnimating && multiplier !== 0) {
return undefined;
}
var seconds;
var index = times.indexOf(time);
if (index < 0) {
return undefined;
}
var interval = times.get(index);
if (multiplier > 0) { // animating forward
seconds = JulianDate.secondsDifference(interval.stop, time);
++index;
} else { //backwards
seconds = JulianDate.secondsDifference(interval.start, time); // Will be negative
--index;
}
seconds /= multiplier; // Will always be positive
// Less than 5 wall time seconds
return (index >= 0 && seconds <= 5.0) ? times.get(index) : undefined;
}
function addToCache(that, tile, interval) {
var index = that._times.indexOf(interval.start);
var tileCache = that._tileCache;
var intervalTileCache = tileCache[index];
if (!defined(intervalTileCache)) {
intervalTileCache = tileCache[index] = {};
}
var key = tile.key;
if (defined(intervalTileCache[key])) {
return true; // Already in the cache
}
var keyElements = getKeyElements(key);
var request = new Request({
throttle : true,
throttleByServer : true,
type : RequestType.IMAGERY,
priorityFunction : tile.priorityFunction
});
var promise = that._requestImageFunction(keyElements.x, keyElements.y, keyElements.level, request, interval);
if (!defined(promise)) {
return false;
}
intervalTileCache[key] = {
promise : promise,
request : request
};
return true;
}
export default TimeDynamicImagery;