UNPKG

leaflet-timedimension

Version:

Add time dimension capabilities on a Leaflet map

1,500 lines (1,343 loc) 76.6 kB
/* * Leaflet TimeDimension v1.1.1 - 2019-11-05 * * Copyright 2019 Biel Frontera (ICTS SOCIB) * datacenter@socib.es * http://www.socib.es/ * * Licensed under the MIT license. * * Demos: * http://apps.socib.es/Leaflet.TimeDimension/ * * Source: * git://github.com/socib/Leaflet.TimeDimension.git * */ (function (factory, window) { if (typeof define === 'function' && define.amd) { // define an AMD module that relies on leaflet define(['leaflet', 'iso8601-js-period'], factory); } else if (typeof exports === 'object') { // define a Common JS module that relies on leaflet module.exports = factory(require('leaflet'), require('iso8601-js-period')); } else if (typeof window !== 'undefined' && window.L && typeof L !== 'undefined') { // get the iso8601 from the expected to be global nezasa scope var iso8601 = nezasa.iso8601; // attach your plugin to the global L variable window.L.TimeDimension = factory(L, iso8601); } }(function (L, iso8601) { // make sure iso8601 module js period module is available under the nezasa scope if (typeof nezasa === 'undefined') { var nezasa = { iso8601: iso8601 }; } // TimeDimension plugin implementation /*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.Evented || 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]; 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) { this._newTimeIndexLoaded(); } }).bind(this, newIndex), this._loadingTimeout); } }, _newTimeIndexLoaded: function () { if (this._loadingTimeIndex === -1) { return; } var time = this._availableTimes[this._loadingTimeIndex]; 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 }); }, 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); }; /* * L.TimeDimension.Util */ L.TimeDimension.Util = { getTimeDuration: function(ISODuration) { if (typeof nezasa === 'undefined') { throw "iso8601-js-period library is required for Leatlet.TimeDimension: https://github.com/nezasa/iso8601-js-period"; } return nezasa.iso8601.Period.parse(ISODuration, true); }, addTimeDuration: function(date, duration, utc) { if (typeof utc === 'undefined') { utc = true; } if (typeof duration == 'string' || duration instanceof String) { duration = this.getTimeDuration(duration); } var l = duration.length; var get = utc ? "getUTC" : "get"; var set = utc ? "setUTC" : "set"; if (l > 0 && duration[0] != 0) { date[set + "FullYear"](date[get + "FullYear"]() + duration[0]); } if (l > 1 && duration[1] != 0) { date[set + "Month"](date[get + "Month"]() + duration[1]); } if (l > 2 && duration[2] != 0) { // weeks date[set + "Date"](date[get + "Date"]() + (duration[2] * 7)); } if (l > 3 && duration[3] != 0) { date[set + "Date"](date[get + "Date"]() + duration[3]); } if (l > 4 && duration[4] != 0) { date[set + "Hours"](date[get + "Hours"]() + duration[4]); } if (l > 5 && duration[5] != 0) { date[set + "Minutes"](date[get + "Minutes"]() + duration[5]); } if (l > 6 && duration[6] != 0) { date[set + "Seconds"](date[get + "Seconds"]() + duration[6]); } }, subtractTimeDuration: function(date, duration, utc) { if (typeof duration == 'string' || duration instanceof String) { duration = this.getTimeDuration(duration); } var subDuration = []; for (var i = 0, l = duration.length; i < l; i++) { subDuration.push(-duration[i]); } this.addTimeDuration(date, subDuration, utc); }, parseAndExplodeTimeRange: function(timeRange, overwritePeriod) { var tr = timeRange.split('/'); var startTime = new Date(Date.parse(tr[0])); var endTime = new Date(Date.parse(tr[1])); var period = (tr.length > 2 && tr[2].length) ? tr[2] : "P1D"; if (overwritePeriod !== undefined && overwritePeriod !== null){ period = overwritePeriod; } return this.explodeTimeRange(startTime, endTime, period); }, explodeTimeRange: function(startTime, endTime, ISODuration, validTimeRange) { var duration = this.getTimeDuration(ISODuration); var result = []; var currentTime = new Date(startTime.getTime()); var minHour = null, minMinutes = null, maxHour = null, maxMinutes = null; if (validTimeRange !== undefined) { var validTimeRangeArray = validTimeRange.split('/'); minHour = validTimeRangeArray[0].split(':')[0]; minMinutes = validTimeRangeArray[0].split(':')[1]; maxHour = validTimeRangeArray[1].split(':')[0]; maxMinutes = validTimeRangeArray[1].split(':')[1]; } while (currentTime < endTime) { if (validTimeRange === undefined || (currentTime.getUTCHours() >= minHour && currentTime.getUTCHours() <= maxHour) ) { if ((currentTime.getUTCHours() != minHour || currentTime.getUTCMinutes() >= minMinutes) && (currentTime.getUTCHours() != maxHour || currentTime.getUTCMinutes() <= maxMinutes)) { result.push(currentTime.getTime()); } } this.addTimeDuration(currentTime, duration); } if (currentTime >= endTime){ result.push(endTime.getTime()); } return result; }, parseTimeInterval: function(timeInterval) { var parts = timeInterval.split("/"); if (parts.length != 2) { throw "Incorrect ISO9601 TimeInterval: " + timeInterval; } var startTime = Date.parse(parts[0]); var endTime = null; var duration = null; if (isNaN(startTime)) { // -> format duration/endTime duration = this.getTimeDuration(parts[0]); endTime = Date.parse(parts[1]); startTime = new Date(endTime); this.subtractTimeDuration(startTime, duration, true); endTime = new Date(endTime); } else { endTime = Date.parse(parts[1]); if (isNaN(endTime)) { // -> format startTime/duration duration = this.getTimeDuration(parts[1]); endTime = new Date(startTime); this.addTimeDuration(endTime, duration, true); } else { // -> format startTime/endTime endTime = new Date(endTime); } startTime = new Date(startTime); } return [startTime, endTime]; }, parseTimesExpression: function(times, overwritePeriod) { var result = []; if (!times) { return result; } if (typeof times == 'string' || times instanceof String) { var timeRanges = times.split(","); var timeRange; var timeValue; for (var i=0, l=timeRanges.length; i<l; i++){ timeRange = timeRanges[i]; if (timeRange.split("/").length == 3) { result = result.concat(this.parseAndExplodeTimeRange(timeRange, overwritePeriod)); } else { timeValue = Date.parse(timeRange); if (!isNaN(timeValue)) { result.push(timeValue); } } } } else { result = times; } return result.sort(function(a, b) { return a - b; }); }, intersect_arrays: function(arrayA, arrayB) { var a = arrayA.slice(0); var b = arrayB.slice(0); var result = []; while (a.length > 0 && b.length > 0) { if (a[0] < b[0]) { a.shift(); } else if (a[0] > b[0]) { b.shift(); } else /* they're equal */ { result.push(a.shift()); b.shift(); } } return result; }, union_arrays: function(arrayA, arrayB) { var a = arrayA.slice(0); var b = arrayB.slice(0); var result = []; while (a.length > 0 && b.length > 0) { if (a[0] < b[0]) { result.push(a.shift()); } else if (a[0] > b[0]) { result.push(b.shift()); } else /* they're equal */ { result.push(a.shift()); b.shift(); } } if (a.length > 0) { result = result.concat(a); } else if (b.length > 0) { result = result.concat(b); } return result; }, sort_and_deduplicate: function(arr) { arr = arr.slice(0).sort(); var result = []; var last = null; for (var i = 0, l = arr.length; i < l; i++) { if (arr[i] !== last){ result.push(arr[i]); last = arr[i]; } } return result; } }; /* * L.TimeDimension.Layer: an abstract Layer that can be managed/synchronized with a TimeDimension. * The constructor recieves a layer (of any kind) and options. * Any children class should implement `_onNewTimeLoading`, `isReady` and `_update` functions * to react to time changes. */ L.TimeDimension.Layer = (L.Layer || L.Class).extend({ includes: (L.Evented || L.Mixin.Events), options: { opacity: 1, zIndex: 1 }, initialize: function(layer, options) { L.setOptions(this, options || {}); this._map = null; this._baseLayer = layer; this._currentLayer = null; this._timeDimension = this.options.timeDimension || null; }, addTo: function(map) { map.addLayer(this); return this; }, onAdd: function(map) { this._map = map; if (!this._timeDimension && map.timeDimension) { this._timeDimension = map.timeDimension; } this._timeDimension.on("timeloading", this._onNewTimeLoading, this); this._timeDimension.on("timeload", this._update, this); this._timeDimension.registerSyncedLayer(this); this._update(); }, onRemove: function(map) { this._timeDimension.unregisterSyncedLayer(this); this._timeDimension.off("timeloading", this._onNewTimeLoading, this); this._timeDimension.off("timeload", this._update, this); this.eachLayer(map.removeLayer, map); this._map = null; }, eachLayer: function(method, context) { method.call(context, this._baseLayer); return this; }, setZIndex: function(zIndex) { this.options.zIndex = zIndex; if (this._baseLayer.setZIndex) { this._baseLayer.setZIndex(zIndex); } if (this._currentLayer && this._currentLayer.setZIndex) { this._currentLayer.setZIndex(zIndex); } return this; }, setOpacity: function(opacity) { this.options.opacity = opacity; if (this._baseLayer.setOpacity) { this._baseLayer.setOpacity(opacity); } if (this._currentLayer && this._currentLayer.setOpacity) { this._currentLayer.setOpacity(opacity); } return this; }, bringToBack: function() { if (!this._currentLayer) { return; } this._currentLayer.bringToBack(); return this; }, bringToFront: function() { if (!this._currentLayer) { return; } this._currentLayer.bringToFront(); return this; }, _onNewTimeLoading: function(ev) { // to be implemented for each type of layer this.fire('timeload', { time: ev.time }); return; }, isReady: function(time) { // to be implemented for each type of layer return true; }, _update: function() { // to be implemented for each type of layer return true; }, getBaseLayer: function() { return this._baseLayer; }, getBounds: function() { var bounds = new L.LatLngBounds(); if (this._currentLayer) { bounds.extend(this._currentLayer.getBounds ? this._currentLayer.getBounds() : this._currentLayer.getLatLng()); } return bounds; } }); L.timeDimension.layer = function(layer, options) { return new L.TimeDimension.Layer(layer, options); }; /* * L.TimeDimension.Layer.WMS: wms Layer associated to a TimeDimension */ L.TimeDimension.Layer.WMS = L.TimeDimension.Layer.extend({ initialize: function(layer, options) { L.TimeDimension.Layer.prototype.initialize.call(this, layer, options); this._timeCacheBackward = this.options.cacheBackward || this.options.cache || 0; this._timeCacheForward = this.options.cacheForward || this.options.cache || 0; this._wmsVersion = this.options.wmsVersion || this.options.version || layer.options.version || "1.1.1"; this._getCapabilitiesParams = this.options.getCapabilitiesParams || {}; this._getCapabilitiesAlternateUrl = this.options.getCapabilitiesUrl || null; this._getCapabilitiesAlternateLayerName = this.options.getCapabilitiesLayerName || null; this._proxy = this.options.proxy || null; this._updateTimeDimension = this.options.updateTimeDimension || false; this._setDefaultTime = this.options.setDefaultTime || false; this._updateTimeDimensionMode = this.options.updateTimeDimensionMode || 'intersect'; // 'union' or 'replace' this._period = this.options.period || null; this._layers = {}; this._defaultTime = 0; this._availableTimes = []; this._capabilitiesRequested = false; if (this._updateTimeDimension || this.options.requestTimeFromCapabilities) { this._requestTimeDimensionFromCapabilities(); } this._baseLayer.on('load', (function() { this._baseLayer.setLoaded(true); this.fire('timeload', { time: this._defaultTime }); }).bind(this)); }, getEvents: function() { var clearCache = L.bind(this._unvalidateCache, this); return { moveend: clearCache, zoomend: clearCache } }, eachLayer: function(method, context) { for (var prop in this._layers) { if (this._layers.hasOwnProperty(prop)) { method.call(context, this._layers[prop]); } } return L.TimeDimension.Layer.prototype.eachLayer.call(this, method, context); }, _onNewTimeLoading: function(ev) { // var layer = this._getLayerForTime(ev.time); if (!this._map.hasLayer(layer)) { this._map.addLayer(layer); // } }, isReady: function(time) { var layer = this._getLayerForTime(time); if (this.options.bounds && this._map) if (!this._map.getBounds().contains(this.options.bounds)) return true; return layer.isLoaded(); }, onAdd: function(map) { L.TimeDimension.Layer.prototype.onAdd.call(this, map); if (this._availableTimes.length == 0) { this._requestTimeDimensionFromCapabilities(); } else { this._updateTimeDimensionAvailableTimes(); } }, _update: function() { if (!this._map) return; var time = this._timeDimension.getCurrentTime(); // It will get the layer for this time (create or get) // Then, the layer will be loaded if necessary, adding it to the map (and show it after loading). // If it already on the map (but probably hidden), it will be shown var layer = this._getLayerForTime(time); if (this._currentLayer == null) { this._currentLayer = layer; } if (!this._map.hasLayer(layer)) { this._map.addLayer(layer); } else { this._showLayer(layer, time); } }, setOpacity: function(opacity) { L.TimeDimension.Layer.prototype.setOpacity.apply(this, arguments); // apply to all preloaded caches for (var prop in this._layers) { if (this._layers.hasOwnProperty(prop) && this._layers[prop].setOpacity) { this._layers[prop].setOpacity(opacity); } } }, setZIndex: function(zIndex){ L.TimeDimension.Layer.prototype.setZIndex.apply(this, arguments); // apply to all preloaded caches for (var prop in this._layers) { if (this._layers.hasOwnProperty(prop) && this._layers[prop].setZIndex) { this._layers[prop].setZIndex(zIndex); } } }, setParams: function(params, noRedraw) { L.extend(this._baseLayer.options, params); if (this._baseLayer.setParams) { this._baseLayer.setParams(params, noRedraw); } for (var prop in this._layers) { if (this._layers.hasOwnProperty(prop) && this._layers[prop].setParams) { this._layers[prop].setLoaded(false); // mark it as unloaded this._layers[prop].setParams(params, noRedraw); } } return this; }, _unvalidateCache: function() { var time = this._timeDimension.getCurrentTime(); for (var prop in this._layers) { if (time != prop && this._layers.hasOwnProperty(prop)) { this._layers[prop].setLoaded(false); // mark it as unloaded this._layers[prop].redraw(); } } }, _evictCachedTimes: function(keepforward, keepbackward) { // Cache management var times = this._getLoadedTimes(); var strTime = String(this._currentTime); var index = times.indexOf(strTime); var remove = []; // remove times before current time if (keepbackward > -1) { var objectsToRemove = index - keepbackward; if (objectsToRemove > 0) { remove = times.splice(0, objectsToRemove); this._removeLayers(remove); } } if (keepforward > -1) { index = times.indexOf(strTime); var objectsToRemove = times.length - index - keepforward - 1; if (objectsToRemove > 0) { remove = times.splice(index + keepforward + 1, objectsToRemove); this._removeLayers(remove); } } }, _showLayer: function(layer, time) { if (this._currentLayer && this._currentLayer !== layer) { this._currentLayer.hide(); } layer.show(); if (this._currentLayer && this._currentLayer === layer) { return; } this._currentLayer = layer; this._currentTime = time; this._evictCachedTimes(this._timeCacheForward, this._timeCacheBackward); }, _getLayerForTime: function(time) { if (time == 0 || time == this._defaultTime || time == null) { return this._baseLayer; } if (this._layers.hasOwnProperty(time)) { return this._layers[time]; } var nearestTime = this._getNearestTime(time); if (this._layers.hasOwnProperty(nearestTime)) { return this._layers[nearestTime]; } var newLayer = this._createLayerForTime(nearestTime); this._layers[time] = newLayer; newLayer.on('load', (function(layer, time) { layer.setLoaded(true); // this time entry should exists inside _layers // but it might be deleted by cache management if (!this._layers[time]) { this._layers[time] = layer; } if (this._timeDimension && time == this._timeDimension.getCurrentTime() && !this._timeDimension.isLoading()) { this._showLayer(layer, time); } // this.fire('timeload', { time: time }); }).bind(this, newLayer, time)); // Hack to hide the layer when added to the map. // It will be shown when timeload event is fired from the map (after all layers are loaded) newLayer.onAdd = (function(map) { Object.getPrototypeOf(this).onAdd.call(this, map); this.hide(); }).bind(newLayer); return newLayer; }, _createLayerForTime:function(time){ var wmsParams = this._baseLayer.options; wmsParams.time = new Date(time).toISOString(); return new this._baseLayer.constructor(this._baseLayer.getURL(), wmsParams); }, _getLoadedTimes: function() { var result = []; for (var prop in this._layers) { if (this._layers.hasOwnProperty(prop)) { result.push(prop); } } return result.sort(function(a, b) { return a - b; }); }, _removeLayers: function(times) { for (var i = 0, l = times.length; i < l; i++) { if (this._map) this._map.removeLayer(this._layers[times[i]]); delete this._layers[times[i]]; } }, setMinimumForwardCache: function(value) { if (value > this._timeCacheForward) { this._timeCacheForward = value; } }, _requestTimeDimensionFromCapabilities: function() { if (this._capabilitiesRequested) { return; } this._capabilitiesRequested = true; var url = this._getCapabilitiesUrl(); if (this._proxy) { url = this._proxy + '?url=' + encodeURIComponent(url); } var oReq = new XMLHttpRequest(); oReq.addEventListener("load", (function(xhr) { var data = xhr.currentTarget.responseXML; if (data !== null){ this._defaultTime = Date.parse(this._getDefaultTimeFromCapabilities(data)); this._setDefaultTime = this._setDefaultTime || (this._timeDimension && this._timeDimension.getAvailableTimes().length == 0); this.setAvailableTimes(this._parseTimeDimensionFromCapabilities(data)); if (this._setDefaultTime && this._timeDimension) { this._timeDimension.setCurrentTime(this._defaultTime); } } }).bind(this)); oReq.overrideMimeType('application/xml'); oReq.open("GET", url); oReq.send(); }, _getCapabilitiesUrl: function() { var url = this._baseLayer.getURL(); if (this._getCapabilitiesAlternateUrl) url = this._getCapabilitiesAlternateUrl; var params = L.extend({}, this._getCapabilitiesParams, { 'request': 'GetCapabilities', 'service': 'WMS', 'version': this._wmsVersion }); url = url + L.Util.getParamString(params, url, params.uppercase); return url; }, _parseTimeDimensionFromCapabilities: function(xml) { var layers = xml.querySelectorAll('Layer[queryable="1"]'); var layerName = this._baseLayer.wmsParams.layers; var layer = null; var times = null; layers.forEach(function(current) { if (current.querySelector("Name").innerHTML === layerName) { layer = current; } }) if (layer) { times = this._getTimesFromLayerCapabilities(layer); if (!times) { times = this._getTimesFromLayerCapabilities(layer.parentNode); } } return times; }, _getTimesFromLayerCapabilities: function(layer) { var times = null; var nodes = layer.children; for (var i=0, l=nodes.length; i<l; i++){ if (nodes[i].nodeName !== 'Extent' && nodes[i].nodeName !== 'Dimension') continue; if (nodes[i].getAttribute('name') !== 'time') continue; if (!nodes[i].textContent.length) continue; times = nodes[i].textContent.trim(); break; } return times; }, _getDefaultTimeFromCapabilities: function(xml) { var layers = xml.querySelectorAll('Layer[queryable="1"]'); var layerName = this._baseLayer.wmsParams.layers; var layer = null; layers.forEach(function(current) { if (current.querySelector("Name").innerHTML === layerName) { layer = current; } }) var defaultTime = 0; if (layer) { defaultTime = this._getDefaultTimeFromLayerCapabilities(layer); if (defaultTime == 0) { defaultTime = this._getDefaultTimeFromLayerCapabilities(layer.parentNode); } } return defaultTime; }, _getDefaultTimeFromLayerCapabilities: function(layer) { var defaultTime = 0; var nodes = layer.children; for (var i=0, l=nodes.length; i<l; i++) { if (nodes[i].nodeName !== 'Extent' && nodes[i].nodeName !== 'Dimension') continue; if (nodes[i].getAttribute('name') !== 'time') continue; if (!nodes[i].attributes.default) continue; if (!nodes[i].attributes.default.textContent.length) continue; defaultTime = nodes[i].attributes.default.textContent.trim(); break; } return defaultTime; }, setAvailableTimes: function(times) { this._availableTimes = L.TimeDimension.Util.parseTimesExpression(times, this._period); this._updateTimeDimensionAvailableTimes(); }, _updateTimeDimensionAvailableTimes: function() { if ((this._timeDimension && this._updateTimeDimension) || (this._timeDimension && this._timeDimension.getAvailableTimes().length == 0)) { this._timeDimension.setAvailableTimes(this._availableTimes, this._updateTimeDimensionMode); if (this._setDefaultTime && this._defaultTime > 0) { this._timeDimension.setCurrentTime(this._defaultTime); } } }, _getNearestTime: function(time) { if (this._layers.hasOwnProperty(time)) { return time; } if (this._availableTimes.length == 0) { return time; } var index = 0; var len = this._availableTimes.length; for (; index < len; index++) { if (time < this._availableTimes[index]) { break; } } // We've found the first index greater than the time. Get the previous if (index > 0) { index--; } if (time != this._availableTimes[index]) { } return this._availableTimes[index]; }, }); if (!L.NonTiledLayer) { L.NonTiledLayer = (L.Layer || L.Class).extend({}); } L.NonTiledLayer.include({ _visible: true, _loaded: false, _originalUpdate: L.NonTiledLayer.prototype._update, _originalOnRemove: L.NonTiledLayer.prototype.onRemove, _update: function() { if (!this._visible && this._loaded) { return; } this._originalUpdate(); }, onRemove: function(map) { this._loaded = false; this._originalOnRemove(map); }, setLoaded: function(loaded) { this._loaded = loaded; }, isLoaded: function() { return this._loaded; }, hide: function() { this._visible = false; this._div.style.display = 'none'; }, show: function() { this._visible = true; this._div.style.display = 'block'; }, getURL: function() { return this._wmsUrl; } }); L.TileLayer.include({ _visible: true, _loaded: false, _originalUpdate: L.TileLayer.prototype._update, _update: function() { if (!this._visible && this._loaded) { return; } this._originalUpdate(); }, setLoaded: function(loaded) { this._loaded = loaded; }, isLoaded: function() { return this._loaded; }, hide: function() { this._visible = false; if (this._container) { this._container.style.display = 'none'; } }, show: function() { this._visible = true; if (this._container) { this._container.style.display = 'block'; } }, getURL: function() { return this._url; } }); L.timeDimension.layer.wms = function(layer, options) { return new L.TimeDimension.Layer.WMS(layer, options); }; /* * L.TimeDimension.Layer.GeoJson: */ L.TimeDimension.Layer.GeoJson = L.TimeDimension.Layer.extend({ initialize: function(layer, options) { L.TimeDimension.Layer.prototype.initialize.call(this, layer, options); this._updateTimeDimension = this.options.updateTimeDimension || false; this._updateTimeDimensionMode = this.options.updateTimeDimensionMode || 'extremes'; // 'union', 'replace' or extremes this._duration = this.options.duration || null; this._addlastPoint = this.options.addlastPoint || false; this._waitForReady = this.options.waitForReady || false; this._defaultTime = 0; this._availableTimes = []; this._loaded = false; if (this._baseLayer.getLayers().length == 0) { if (this._waitForReady){ this._baseLayer.on("ready", this._onReadyBaseLayer, this); }else{ this._loaded = true; } } else { this._loaded = true; this._setAvailableTimes(); } // reload available times if data is added to the base layer this._baseLayer.on('layeradd', (function () { if (this._loaded) { this._setAvailableTimes(); } }).bind(this)); }, onAdd: function(map) { L.TimeDimension.Layer.prototype.onAdd.call(this, map); if (this._loaded) { this._setAvailableTimes(); } }, eachLayer: function(method, context) { if (this._currentLayer) { method.call(context, this._currentLayer); } return L.TimeDimension.Layer.prototype.eachLayer.call(this, method, context); }, isReady: function(time) { return this._loaded; }, _update: function() { if (!this._map) return; if (!this._loaded) { return; } var time = this._timeDimension.getCurrentTime(); var maxTime = this._timeDimension.getCurrentTime(), minTime = 0; if (this._duration) { var date = new Date(maxTime); L.TimeDimension.Util.subtractTimeDuration(date, this._duration, true); minTime = date.getTime(); } // new coordinates: var layer = L.geoJson(null, this._baseLayer.options); var layers = this._baseLayer.getLayers(); for (var i = 0, l = layers.length; i < l; i++) { var feature = this._getFeatureBetweenDates(layers[i].feature, minTime, maxTime); if (feature) { layer.addData(feature); if (this._addlastPoint && feature.geometry.type == "LineString") { if (feature.geometry.coordinates.length > 0) { var properties = feature.properties; properties.last = true; layer.addData({ type: 'Feature', properties: properties, geometry: { type: 'Point', coordinates: feature.geometry.coordinates[feature.geometry.coordinates.length - 1] } }); } } } } if (this._currentLayer) { this._map.removeLayer(this._currentLayer); } if (layer.getLayers().length) { layer.addTo(this._map); this._currentLayer = layer; } }, _setAvailableTimes: function() { var times = []; var layers = this._baseLayer.getLayers(); for (var i = 0, l = layers.length; i < l; i++) { if (layers[i].feature) { var featureTimes = this._getFeatureTimes(layers[i].feature); for (var j = 0, m = featureTimes.length; j < m; j++) { times.push(featureTimes[j]); } } } this._availableTimes = L.TimeDimension.Util.sort_and_deduplicate(times); if (this._timeDimension && (this._updateTimeDimension || this._timeDimension.getAvailableTimes().length == 0)) { this._timeDimension.setAvailableTimes(this._availableTimes, this._updateTimeDimensionMode); } }, _getFeatureTimes: function(feature) { if (!feature.featureTimes) { if (!feature.properties) { feature.featureTimes = []; } else if (feature.properties.hasOwnProperty('coordTimes')) { feature.featureTimes = feature.properties.coordTimes; } else if (feature.properties.hasOwnProperty('times')) { feature.featureTimes = feature.properties.times; } else if (feature.properties.hasOwnProperty('linestringTimestamps')) { feature.featureTimes = feature.properties.linestringTimestamps; } else if (feature.properties.hasOwnProperty('time')) { feature.featureTimes = [feature.properties.time]; } else { feature.featureTimes = []; } // String dates to ms for (var i = 0, l = feature.featureTimes.length; i < l; i++) { var time = feature.featureTimes[i]; if (typeof time == 'string' || time instanceof String) { time = Date.parse(time.trim()); feature.featureTimes[i] = time; } } } return feature.featureTimes; }, _getFeatureBetweenDates: function(feature, minTime, maxTime) { var featureTimes = this._getFeatureTimes(feature); if (featureTimes.length == 0) { return feature; } var index_min = null, index_max = null, l = featureTimes.length; if (featureTimes[0] > maxTime || featureTimes[l - 1] < minTime) { return null; } if (featureTimes[l - 1] > minTime) { for (var i = 0; i < l; i++) { if (index_min === null && featureTimes[i] > minTime) { // set index_min the first time that current time is greater the minTime index_min = i; } if (featureTimes[i] > maxTime) { index_max = i; break; } } } if (index_min === null) { index_min = 0; } if (index_max === null) { index_max = l; } var new_coordinates = []; if (feature.geometry.coordinates[0].length) { new_coordinates = feature.geometry.coordinates.slice(index_min, index_max); } else { new_coordinates = feature.geometry.coordinates; } return { type: 'Feature', properties: feature.properties, geometry: { type: feature.geometry.type, coordinates: new_coordinates } }; }, _onReadyBaseLayer: function() { this._loaded = true; this._setAvailableTimes(); this._update(); }, }); L.timeDimension.layer.geoJson = function(layer, options) { return new L.TimeDimension.Layer.GeoJson(layer, options); }; /*jshint indent: 4, browser:true*/ /*global L*/ /* * L.TimeDimension.Player */ //'use strict'; L.TimeDimension.Player = (L.Layer || L.Class).extend({ includes: (L.Evented || L.Mixin.Events), initialize: function(options, timeDimension) { L.setOptions(this, options); this._timeDimension = timeDimension; this._paused = false; this._buffer = this.options.buffer || 5; this._minBufferReady = this.options.minBufferReady || 1; this._waitingForBuffer = false; this._loop = this.options.loop || false; this._steps = 1; this._timeDimension.on('timeload', (function(data) { this.release(); // free clock this._waitingForBuffer = false; // reset buffer }).bind(this)); this.setTransitionTime(this.options.transitionTime || 1000); this._timeDimension.on('limitschanged availabletimeschanged timeload', (function(data) { this._timeDimension.prepareNextTimes(this._steps, this._minBufferReady, this._loop); }).bind(this)); }, _tick: function() { var maxIndex = this._getMaxIndex(); var maxForward = (this._timeDimension.getCurrentTimeIndex() >= maxIndex) && (this._steps > 0); var maxBackward = (this._timeDimension.getCurrentTimeIndex() == 0) && (this._steps < 0); if (maxForward || maxBackward) { // we reached the last step if (!this._loop) { this.pause(); this.stop(); this.fire('animationfinished'); return; } } if (this._paused) { r