/* Copyright (c) 2016 Jean-Marc VIGLINO, released under the CeCILL license (http://www.cecill.info/). */ import ol from 'ol' import ol_Object from 'ol/object' import ol_easing from 'ol/easing' import ol_Map from 'ol/map' import ol_layer_Vector from 'ol/layer/vector' import ol_extent from 'ol/extent' import ol_Observable from 'ol/observable' /** Feature animation base class * Use the {@link _ol_Map_#animateFeature} or {@link _ol_layer_Vector_#animateFeature} to animate a feature * on postcompose in a map or a layer * @constructor * @fires animationstart|animationend * @param {ol_featureAnimationOptions} options * @param {Number} options.duration duration of the animation in ms, default 1000 * @param {bool} options.revers revers the animation direction * @param {Number} options.repeat number of time to repeat the animation, default 0 * @param {oo.style.Style} options.hiddenStyle a style to display the feature when playing the animation * to be used to make the feature selectable when playing animation * (@see {@link ../examples/map.featureanimation.select.html}), default the feature * will be hidden when playing (and niot selectable) * @param {ol_easing_Function} options.fade an easing function used to fade in the feature, default none * @param {ol_easing_Function} options.easing an easing function for the animation, default ol_easing.linear */ var ol_featureAnimation = function(options) { options = options || {}; this.duration_ = typeof (options.duration)=='number' ? (options.duration>=0 ? options.duration : 0) : 1000; this.fade_ = typeof(options.fade) == 'function' ? options.fade : null; this.repeat_ = Number(options.repeat); var easing = typeof(options.easing) =='function' ? options.easing : ol_easing.linear; if (options.revers) this.easing_ = function(t) { return (1 - easing(t)); }; else this.easing_ = easing; this.hiddenStyle = options.hiddenStyle; ol_Object.call(this); }; ol.inherits(ol_featureAnimation, ol_Object); /** Draw a geometry * @param {olx.animateFeatureEvent} e * @param {ol.geom} geom geometry for shadow * @param {ol.geom} shadow geometry for shadow (ie. style with zIndex = -1) * @private */ ol_featureAnimation.prototype.drawGeom_ = function (e, geom, shadow) { if (this.fade_) { e.context.globalAlpha = this.fade_(1-e.elapsed); } var style = e.style; for (var i=0; i} fanim the animation to play * @return {olx.animationControler} an object to control animation with start, stop and isPlaying function */ ol_Map.prototype.animateFeature = /** Animate feature on a vector layer * @fires animationstart, animationend * @param {ol.Feature} feature Feature to animate * @param {ol_featureAnimation|Array} fanim the animation to play * @return {olx.animationControler} an object to control animation with start, stop and isPlaying function */ ol_layer_Vector.prototype.animateFeature = function(feature, fanim) { var self = this; var listenerKey; // Save style var style = feature.getStyle(); var flashStyle = style || (this.getStyleFunction ? this.getStyleFunction()(feature) : null); if (!flashStyle) flashStyle=[]; if (!(flashStyle instanceof Array)) flashStyle = [flashStyle]; // Hide feature while animating feature.setStyle(fanim.hiddenStyle || []); // Structure pass for animating var event = { // Frame context vectorContext: null, frameState: null, start: 0, time: 0, elapsed: 0, extent: false, // Feature information feature: feature, geom: feature.getGeometry(), typeGeom: feature.getGeometry().getType(), bbox: feature.getGeometry().getExtent(), coord: ol_extent.getCenter(feature.getGeometry().getExtent()), style: flashStyle }; if (!(fanim instanceof Array)) fanim = [fanim]; // Remove null animations for (var i=fanim.length-1; i>=0; i--) { if (fanim[i].duration_===0) fanim.splice(i,1); } var nb=0, step = 0; function animate(e) { event.vectorContext = e.vectorContext; event.frameState = e.frameState; if (!event.extent) { event.extent = e.frameState.extent; event.start = e.frameState.time; event.context = e.context; } event.time = e.frameState.time - event.start; event.elapsed = event.time / fanim[step].duration_; if (event.elapsed > 1) event.elapsed = 1; // Stop animation? if (!fanim[step].animate(event)) { nb++; // Repeat animation if (nb < fanim[step].repeat_) { event.extent = false; } // newt step else if (step < fanim.length-1) { fanim[step].dispatchEvent({ type:'animationend', feature: feature }); step++; nb=0; event.extent = false; } // the end else { stop(); } } // tell OL3 to continue postcompose animation e.frameState.animate = true; } // Stop animation function stop(options) { ol_Observable.unByKey(listenerKey); listenerKey = null; feature.setStyle(style); // Send event var event = { type:'animationend', feature: feature }; if (options) { for (var i in options) if (options.hasOwnProperty(i)) { event[i] = options[i]; } } fanim[step].dispatchEvent(event); self.dispatchEvent(event); } // Launch animation function start(options) { if (fanim.length && !listenerKey) { listenerKey = self.on('postcompose', animate, self); // map or layer? if (self.renderSync) self.renderSync(); else self.changed(); // Send event var event = { type:'animationstart', feature: feature }; if (options) { for (var i in options) if (options.hasOwnProperty(i)) { event[i] = options[i]; } } fanim[step].dispatchEvent(event); self.dispatchEvent(event); } } start(); // Return animation controler return { start: start, stop: stop, isPlaying: function() { return (!!listenerKey); } }; }; export default ol_featureAnimation