From 5322f286788f87e6963b113ad2ed88e8ee1cbdef Mon Sep 17 00:00:00 2001 From: root Date: Wed, 25 Apr 2018 10:02:14 +0100 Subject: [PATCH] added fade and interpolation for wms layers, see demo at https://irishmarineinstitute.github.io/timeliER/#IMI_CONN_3D --- README.md | 8 +- src/leaflet.timedimension.js | 25 +++-- src/leaflet.timedimension.layer.wms.js | 134 +++++++++++++++++++------ src/leaflet.timedimension.player.js | 25 +++-- 4 files changed, 143 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 1de58fa..03bf5a1 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,10 @@ Option | Type | Default `getCapabilitiesLayerName` | `String` | `null` | Alternative layer name for the GetCapabilities request (useful if using a cache service like GeoWebCache) `setDefaultTime` | `Boolean` | `false` | If true, it will change the current time to the default time of the layer (according to getCapabilities) `wmsVersion` | `String` | `layer.options.version` or `"1.1.1"` | WMS version of the layer. Used to construct the getCapabilities request +`fadeFrames` | `Number` | `1` | Number of animation frames over which to fade in and fade out layers, using css opacity. +`fadeInFrames` | `Number` | `layer.options.fadeFrames` | Number of animation frames over which to fade in and layers, using css opacity. 10 might be a good option. +`fadeOutFrames` | `Number` | `layer.options.fadeFrames` | Number of animation frames over which to fade out layers, using css opacity. 8 might be a good option. +`interpolate` | `Boolean` | `false` | Whether gradually fade through layers, using css opacity, over the duration. If using this feature, probably also set fadeOutFrames. (The interpolate option takes precedence over fadeInFrames, if both were specified). ### L.TimeDimension.Layer.GeoJSON @@ -341,12 +345,12 @@ Update mode can be one of these values: `intersect`, `union`, `replace`, `extrem ## Talks - Leaflet.TimeDimension: ¡esto se anima!(esp) [10as Jornadas SIG libre. Girona 2016](http://www.sigte.udg.edu/jornadassiglibre2016/) | -[Video](https://vimeo.com/172724621) | +[Video](https://vimeo.com/172724621) | [Slides](http://apps.socib.es/Leaflet.TimeDimension/slides/slides-siglibre10.html) - Leaflet.TimeDimension webinar (eng) [Interoperability and Technology/Tech Dive Webinar Series](http://wiki.esipfed.org/index.php/Interoperability_and_Technology/Tech_Dive_Webinar_Series) from [ESIP](http://www.esipfed.org/) | -[Video](https://www.youtube.com/watch?v=US5FUUPqlww) | +[Video](https://www.youtube.com/watch?v=US5FUUPqlww) | [Slides](http://apps.socib.es/Leaflet.TimeDimension/slides/slides-esip.html) diff --git a/src/leaflet.timedimension.js b/src/leaflet.timedimension.js index f0e3b0e..35b902e 100644 --- a/src/leaflet.timedimension.js +++ b/src/leaflet.timedimension.js @@ -40,6 +40,10 @@ L.TimeDimension = (L.Layer || L.Class).extend({ return this._currentTimeIndex; }, + getInterpolator: function(){ + return this._interp; + }, + getCurrentTime: function () { var index = -1; if (this._loadingTimeIndex !== -1) { @@ -58,7 +62,8 @@ L.TimeDimension = (L.Layer || L.Class).extend({ return (this._loadingTimeIndex !== -1); }, - setCurrentTimeIndex: function (newIndex) { + setCurrentTimeIndex: function (newIndex,interp) { + this._interp = interp || {position: function(){return 1;}}; var upperLimit = this._upperLimit || this._availableTimes.length - 1; var lowerLimit = this._lowerLimit || 0; //clamp the value @@ -68,7 +73,7 @@ L.TimeDimension = (L.Layer || L.Class).extend({ } this._loadingTimeIndex = newIndex; var newTime = this._availableTimes[newIndex]; - console.log('INIT -- Current time: ' + new Date(newTime).toISOString()); + //console.log('INIT -- Current time: ' + new Date(newTime).toISOString()); if (this._checkSyncedLayersReady(this._availableTimes[this._loadingTimeIndex])) { this._newTimeIndexLoaded(); } else { @@ -78,7 +83,7 @@ L.TimeDimension = (L.Layer || L.Class).extend({ // add timeout of 3 seconds if layers doesn't response setTimeout((function (index) { if (index == this._loadingTimeIndex) { - console.log('Change time for timeout'); + //console.log('Change time for timeout'); this._newTimeIndexLoaded(); } }).bind(this, newIndex), this._loadingTimeout); @@ -91,25 +96,25 @@ L.TimeDimension = (L.Layer || L.Class).extend({ return; } var time = this._availableTimes[this._loadingTimeIndex]; - console.log('END -- Current time: ' + new Date(time).toISOString()); + //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 false; } } } return true; }, - + setCurrentTime: function (time) { var newIndex = this._seekNearestTimeIndex(time); this.setCurrentTimeIndex(newIndex); @@ -120,7 +125,7 @@ L.TimeDimension = (L.Layer || L.Class).extend({ return this._availableTimes[index]; }, - nextTime: function (numSteps, loop) { + nextTime: function (numSteps, loop, interp) { if (!numSteps) { numSteps = 1; } @@ -146,7 +151,7 @@ L.TimeDimension = (L.Layer || L.Class).extend({ newIndex = lowerLimit; } } - this.setCurrentTimeIndex(newIndex); + this.setCurrentTimeIndex(newIndex,interp); }, prepareNextTimes: function (numSteps, howmany, loop) { @@ -328,7 +333,7 @@ L.TimeDimension = (L.Layer || L.Class).extend({ availableTimes: this._availableTimes, currentTime: currentTime }); - console.log('available times changed'); + //console.log('available times changed'); }, getLowerLimit: function () { return this._availableTimes[this.getLowerLimitIndex()]; diff --git a/src/leaflet.timedimension.layer.wms.js b/src/leaflet.timedimension.layer.wms.js index de8f6d6..1ce1f06 100644 --- a/src/leaflet.timedimension.layer.wms.js +++ b/src/leaflet.timedimension.layer.wms.js @@ -16,6 +16,10 @@ L.TimeDimension.Layer.WMS = L.TimeDimension.Layer.extend({ this._updateTimeDimension = this.options.updateTimeDimension || false; this._setDefaultTime = this.options.setDefaultTime || false; this._updateTimeDimensionMode = this.options.updateTimeDimensionMode || 'intersect'; // 'union' or 'replace' + this._fadeFrames = parseFloat(this.options.fadeFrames) || 1; + this._fadeInFrames = this.options.fadeInFrames? parseFloat(this.options.fadeInFrames) || this._fadeFrames : this._fadeFrames; + this._fadeOutFrames = this.options.fadeInFrames? parseFloat(this.options.fadeInFrames) || this._fadeFrames : this._fadeFrames; + this._interpolate = this.options.interpolate || false; this._layers = {}; this._defaultTime = 0; this._availableTimes = []; @@ -89,7 +93,7 @@ L.TimeDimension.Layer.WMS = L.TimeDimension.Layer.extend({ if (!this._map.hasLayer(layer)) { this._map.addLayer(layer); } else { - this._showLayer(layer, time); + this._showLayer(layer, time, this._timeDimension.getInterpolator()); } }, @@ -160,19 +164,23 @@ L.TimeDimension.Layer.WMS = L.TimeDimension.Layer.extend({ } } }, - _showLayer: function(layer, time) { - if (this._currentLayer && this._currentLayer !== layer) { - this._currentLayer.hide(); + _showLayer: function(layer, time, interp) { + if(!(this._interpolate && interp)){ + interp = new LtdwGlide(this._fadeInFrames); + } + layer.show(interp).then(function(oldLayer, newLayer){ + if (oldLayer && oldLayer !== newLayer) { + oldLayer.hide(new LtdwGlide(this._fadeOutFrames)); } - layer.show(); - if (this._currentLayer && this._currentLayer === layer) { - return; - } - this._currentLayer = layer; - this._currentTime = time; - console.log('Show layer ' + layer.wmsParams.layers + ' with time: ' + new Date(time).toISOString()); - this._evictCachedTimes(this._timeCacheForward, this._timeCacheBackward); + }.bind(this,this._currentLayer, layer)); + + if (this._currentLayer && this._currentLayer === layer) { + return; + } + this._currentLayer = layer; + this._currentTime = time; + //console.log('Show layer ' + layer.wmsParams.layers + ' with time: ' + new Date(time).toISOString()); }, _getLayerForTime: function(time) { @@ -199,7 +207,7 @@ L.TimeDimension.Layer.WMS = L.TimeDimension.Layer.extend({ this._layers[time] = layer; } if (this._timeDimension && time == this._timeDimension.getCurrentTime() && !this._timeDimension.isLoading()) { - this._showLayer(layer, time); + this._showLayer(layer, time, this._timeDimension.getInterpolator()); } // console.log('Loaded layer ' + layer.wmsParams.layers + ' with time: ' + new Date(time).toISOString()); this.fire('timeload', { @@ -235,6 +243,7 @@ L.TimeDimension.Layer.WMS = L.TimeDimension.Layer.extend({ }, _removeLayers: function(times) { + return; for (var i = 0, l = times.length; i < l; i++) { if (this._map) this._map.removeLayer(this._layers[times[i]]); @@ -260,11 +269,13 @@ L.TimeDimension.Layer.WMS = L.TimeDimension.Layer.extend({ var oReq = new XMLHttpRequest(); oReq.addEventListener("load", (function(xhr) { var data = xhr.currentTarget.responseXML; - 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) { + if(data){ + 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'); @@ -402,6 +413,65 @@ if (!L.NonTiledLayer) { L.NonTiledLayer = (L.Layer || L.Class).extend({}); } +var LtdwGlide = function(n){ + this._position = 0.; + this._step = 1/n; +}; +LtdwGlide.prototype.position = function(){ + this._position += this._step; + return this._position > 1? 1: this._position; +}; +/* + * returns a promise to fade out an element using css opacity. + */ +function ltdlwFadeOut(el,interp){ + return new Promise(function(resolve,reject){ + if(!el){ + resolve(); + return; + } + interp = interp || new LtdwGlide(7); + var startOpacity = parseFloat(el.style.opacity); + (function fade() { + var opacity = startOpacity * (1-interp.position()); + if(opacity >= 0.1){ + el.style.opacity = opacity; + requestAnimationFrame(fade); + }else{ + el.style.display = 'none'; + el.style.opacity = startOpacity; + resolve(); + } + })(); + }); +} +/* + * returns a promise to fade in an element using css opacity. + */ +function ltdlwFadeIn(el, interp){ + return new Promise(function(resolve,reject){ + if(!el){ + resolve(); + return; + } + var targetOpacity = parseFloat(el.style.opacity) || 1; + interp = interp || new LtdwGlide(10); + el.style.opacity = 0; + el.style.display = "block"; + (function fade() { + var position = interp.position(); + if ( position < 1 ) { + el.style.opacity = position * targetOpacity; + requestAnimationFrame(fade); + }else{ + el.style.opacity = targetOpacity; + resolve(); + } + })(); + }); + +} + L.NonTiledLayer.include({ _visible: true, _loaded: false, @@ -429,14 +499,17 @@ L.NonTiledLayer.include({ return this._loaded; }, - hide: function() { - this._visible = false; - this._div.style.display = 'none'; + hide: function(interp) { + return ltdlwFadeOut(this._div,interp).then( + function(){ + this._visible = false; + }.bind(this) + ); }, - show: function() { + show: function(interp) { this._visible = true; - this._div.style.display = 'block'; + return ltdlwFadeIn(this._div,interp); }, getURL: function() { @@ -466,18 +539,17 @@ L.TileLayer.include({ return this._loaded; }, - hide: function() { - this._visible = false; - if (this._container) { - this._container.style.display = 'none'; - } + hide: function(interp) { + return ltdlwFadeOut(this._container,interp).then( + function(){ + this._visible = false; + }.bind(this) + ); }, - show: function() { + show: function(interp) { this._visible = true; - if (this._container) { - this._container.style.display = 'block'; - } + return ltdlwFadeIn(this._container,interp); }, getURL: function() { diff --git a/src/leaflet.timedimension.player.js b/src/leaflet.timedimension.player.js index 87fd168..6bd5632 100644 --- a/src/leaflet.timedimension.player.js +++ b/src/leaflet.timedimension.player.js @@ -1,7 +1,19 @@ /*jshint indent: 4, browser:true*/ /*global L*/ - +/* + * L.TimeDimension.Interpolator + */ +//'use strict'; +var LtdInterpolator = function(transitionTime,interp){ + this._start_time = new Date().getTime(); + this._transitionTime = transitionTime; + this._start_position = interp ? interp.position() : 0.; +}; +LtdInterpolator.prototype.position = function(){ + var position = (new Date().getTime() - this._start_time) / this._transitionTime; + return position > 1? 1.:position; +} /* * L.TimeDimension.Player */ @@ -23,7 +35,7 @@ L.TimeDimension.Player = (L.Layer || L.Class).extend({ 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)); @@ -82,14 +94,15 @@ L.TimeDimension.Player = (L.Layer || L.Class).extend({ } } this.pause(); - this._timeDimension.nextTime(this._steps, this._loop); + this._interp = new LtdInterpolator(this._transitionTime); + this._timeDimension.nextTime(this._steps, this._loop, this._interp); if (buffer > 0) { this._timeDimension.prepareNextTimes(this._steps, buffer, this._loop); } }, - + _getMaxIndex: function(){ - return Math.min(this._timeDimension.getAvailableTimes().length - 1, + return Math.min(this._timeDimension.getAvailableTimes().length - 1, this._timeDimension.getUpperLimitIndex() || Infinity); }, @@ -99,7 +112,7 @@ L.TimeDimension.Player = (L.Layer || L.Class).extend({ this._waitingForBuffer = false; if (this.options.startOver){ if (this._timeDimension.getCurrentTimeIndex() === this._getMaxIndex()){ - this._timeDimension.setCurrentTimeIndex(this._timeDimension.getLowerLimitIndex() || 0); + this._timeDimension.setCurrentTimeIndex(this._timeDimension.getLowerLimitIndex() || 0, this._interp); } } this.release();