/*jshint indent: 4, browser:true*/ /*global L*/ /* * L.TimeDimension: TimeDimension object manages the time component of a layer. * It can be shared among different layers and it can be added to a map, and become * the default timedimension component for any layer added to the map. */ L.TimeDimension = (L.Layer || L.Class).extend({ includes: L.Mixin.Events, initialize: function (options) { L.setOptions(this, options); // _availableTimes is an array with all the available times in ms. this._availableTimes = this._generateAvailableTimes(); this._currentTimeIndex = -1; this._loadingTimeIndex = -1; this._loadingTimeout = this.options.loadingTimeout || 3000; this._syncedLayers = []; if (this._availableTimes.length > 0) { this.setCurrentTime(this.options.currentTime || this._getDefaultCurrentTime()); } if (this.options.lowerLimitTime) { this.setLowerLimit(this.options.lowerLimitTime); } if (this.options.upperLimitTime) { this.setUpperLimit(this.options.upperLimitTime); } }, getAvailableTimes: function () { return this._availableTimes; }, getCurrentTimeIndex: function () { if (this._currentTimeIndex === -1) { return this._availableTimes.length - 1; } return this._currentTimeIndex; }, getCurrentTime: function () { var index = -1; if (this._loadingTimeIndex !== -1) { index = this._loadingTimeIndex; } else { index = this.getCurrentTimeIndex(); } if (index >= 0) { return this._availableTimes[index]; } else { return null; } }, isLoading: function () { return (this._loadingTimeIndex !== -1); }, setCurrentTimeIndex: function (newIndex) { var upperLimit = this._upperLimit || this._availableTimes.length - 1; var lowerLimit = this._lowerLimit || 0; //clamp the value newIndex = Math.min(Math.max(lowerLimit, newIndex), upperLimit); if (newIndex < 0) { return; } this._loadingTimeIndex = newIndex; var newTime = this._availableTimes[newIndex]; console.log('INIT -- Current time: ' + new Date(newTime).toISOString()); if (this._checkSyncedLayersReady(this._availableTimes[this._loadingTimeIndex])) { this._newTimeIndexLoaded(); } else { this.fire('timeloading', { time: newTime }); // add timeout of 3 seconds if layers doesn't response setTimeout((function (index) { if (index == this._loadingTimeIndex) { console.log('Change time for timeout'); this._newTimeIndexLoaded(); } }).bind(this, newIndex), this._loadingTimeout); } }, _newTimeIndexLoaded: function () { if (this._loadingTimeIndex === -1) { return; } var time = this._availableTimes[this._loadingTimeIndex]; console.log('END -- Current time: ' + new Date(time).toISOString()); this._currentTimeIndex = this._loadingTimeIndex; this.fire('timeload', { time: time }); this._loadingTimeIndex = -1; }, _checkSyncedLayersReady: function (time) { for (var i = 0, len = this._syncedLayers.length; i < len; i++) { if (this._syncedLayers[i].isReady) { if (!this._syncedLayers[i].isReady(time)) { return false; } } } return true; }, setCurrentTime: function (time) { var newIndex = this._seekNearestTimeIndex(time); this.setCurrentTimeIndex(newIndex); }, seekNearestTime: function (time) { var index = this._seekNearestTimeIndex(time); return this._availableTimes[index]; }, nextTime: function (numSteps, loop) { if (!numSteps) { numSteps = 1; } var newIndex = this._currentTimeIndex; var upperLimit = this._upperLimit || this._availableTimes.length - 1; var lowerLimit = this._lowerLimit || 0; if (this._loadingTimeIndex > -1) { newIndex = this._loadingTimeIndex; } newIndex = newIndex + numSteps; if (newIndex > upperLimit) { if (!!loop) { newIndex = lowerLimit; } else { newIndex = upperLimit; } } // loop backwards if (newIndex < lowerLimit) { if (!!loop) { newIndex = upperLimit; } else { newIndex = lowerLimit; } } this.setCurrentTimeIndex(newIndex); }, prepareNextTimes: function (numSteps, howmany, loop) { if (!numSteps) { numSteps = 1; } var newIndex = this._currentTimeIndex; var currentIndex = newIndex; if (this._loadingTimeIndex > -1) { newIndex = this._loadingTimeIndex; } // assure synced layers have a buffer/cache of at least howmany elements for (var i = 0, len = this._syncedLayers.length; i < len; i++) { if (this._syncedLayers[i].setMinimumForwardCache) { this._syncedLayers[i].setMinimumForwardCache(howmany); } } var count = howmany; var upperLimit = this._upperLimit || this._availableTimes.length - 1; var lowerLimit = this._lowerLimit || 0; while (count > 0) { newIndex = newIndex + numSteps; if (newIndex > upperLimit) { if (!!loop) { newIndex = lowerLimit; } else { break; } } if (newIndex < lowerLimit) { if (!!loop) { newIndex = upperLimit; } else { break; } } if (currentIndex === newIndex) { //we looped around the timeline //no need to load further, the next times are already loading break; } this.fire('timeloading', { time: this._availableTimes[newIndex] }); count--; } }, getNumberNextTimesReady: function (numSteps, howmany, loop) { if (!numSteps) { numSteps = 1; } var newIndex = this._currentTimeIndex; if (this._loadingTimeIndex > -1) { newIndex = this._loadingTimeIndex; } var count = howmany; var ready = 0; var upperLimit = this._upperLimit || this._availableTimes.length - 1; var lowerLimit = this._lowerLimit || 0; while (count > 0) { newIndex = newIndex + numSteps; if (newIndex > upperLimit) { if (!!loop) { newIndex = lowerLimit; } else { count = 0; ready = howmany; break; } } if (newIndex < lowerLimit) { if (!!loop) { newIndex = upperLimit; } else { count = 0; ready = howmany; break; } } var time = this._availableTimes[newIndex]; if (this._checkSyncedLayersReady(time)) { ready++; } count--; } return ready; }, previousTime: function (numSteps, loop) { this.nextTime(numSteps*(-1), loop); }, registerSyncedLayer: function (layer) { this._syncedLayers.push(layer); layer.on('timeload', this._onSyncedLayerLoaded, this); }, unregisterSyncedLayer: function (layer) { var index = this._syncedLayers.indexOf(layer); if (index != -1) { this._syncedLayers.splice(index, 1); } layer.off('timeload', this._onSyncedLayerLoaded, this); }, _onSyncedLayerLoaded: function (e) { if (e.time == this._availableTimes[this._loadingTimeIndex] && this._checkSyncedLayersReady(e.time)) { this._newTimeIndexLoaded(); } }, _generateAvailableTimes: function () { if (this.options.times) { return L.TimeDimension.Util.parseTimesExpression(this.options.times); } else if (this.options.timeInterval) { var tiArray = L.TimeDimension.Util.parseTimeInterval(this.options.timeInterval); var period = this.options.period || 'P1D'; var validTimeRange = this.options.validTimeRange || undefined; return L.TimeDimension.Util.explodeTimeRange(tiArray[0], tiArray[1], period, validTimeRange); } else { return []; } }, _getDefaultCurrentTime: function () { var index = this._seekNearestTimeIndex(new Date().getTime()); return this._availableTimes[index]; }, _seekNearestTimeIndex: function (time) { var newIndex = 0; var len = this._availableTimes.length; for (; newIndex < len; newIndex++) { if (time < this._availableTimes[newIndex]) { break; } } // We've found the first index greater than the time. Return the previous if (newIndex > 0) { newIndex--; } return newIndex; }, setAvailableTimes: function (times, mode) { var currentTime = this.getCurrentTime(), lowerLimitTime = this.getLowerLimit(), upperLimitTime = this.getUpperLimit(); if (mode == 'extremes') { var period = this.options.period || 'P1D'; this._availableTimes = L.TimeDimension.Util.explodeTimeRange(new Date(times[0]), new Date(times[times.length - 1]), period); } else { var parsedTimes = L.TimeDimension.Util.parseTimesExpression(times); if (this._availableTimes.length === 0) { this._availableTimes = parsedTimes; } else if (mode == 'intersect') { this._availableTimes = L.TimeDimension.Util.intersect_arrays(parsedTimes, this._availableTimes); } else if (mode == 'union') { this._availableTimes = L.TimeDimension.Util.union_arrays(parsedTimes, this._availableTimes); } else if (mode == 'replace') { this._availableTimes = parsedTimes; } else { throw 'Merge available times mode not implemented: ' + mode; } } if (lowerLimitTime) { this.setLowerLimit(lowerLimitTime); //restore lower limit } if (upperLimitTime) { this.setUpperLimit(upperLimitTime); //restore upper limit } this.setCurrentTime(currentTime); this.fire('availabletimeschanged', { availableTimes: this._availableTimes, currentTime: currentTime }); console.log('available times changed'); }, getLowerLimit: function () { return this._availableTimes[this.getLowerLimitIndex()]; }, getUpperLimit: function () { return this._availableTimes[this.getUpperLimitIndex()]; }, setLowerLimit: function (time) { var index = this._seekNearestTimeIndex(time); this.setLowerLimitIndex(index); }, setUpperLimit: function (time) { var index = this._seekNearestTimeIndex(time); this.setUpperLimitIndex(index); }, setLowerLimitIndex: function (index) { this._lowerLimit = Math.min(Math.max(index || 0, 0), this._upperLimit || this._availableTimes.length - 1); this.fire('limitschanged', { lowerLimit: this._lowerLimit, upperLimit: this._upperLimit }); }, setUpperLimitIndex: function (index) { this._upperLimit = Math.max(Math.min(index, this._availableTimes.length - 1), this._lowerLimit || 0); this.fire('limitschanged', { lowerLimit: this._lowerLimit, upperLimit: this._upperLimit }); }, getLowerLimitIndex: function () { return this._lowerLimit; }, getUpperLimitIndex: function () { return this._upperLimit; } }); L.Map.addInitHook(function () { if (this.options.timeDimension) { this.timeDimension = L.timeDimension(this.options.timeDimensionOptions || {}); } }); L.timeDimension = function (options) { return new L.TimeDimension(options); };