341 lines
11 KiB
JavaScript
341 lines
11 KiB
JavaScript
/*
|
|
Copyright (c) 2016 Jean-Marc VIGLINO,
|
|
released under the CeCILL license (http://www.cecill.info/).
|
|
*/
|
|
|
|
import ol_Object from 'ol/Object.js'
|
|
import {linear as ol_easing_linear} from 'ol/easing.js'
|
|
import ol_Map from 'ol/Map.js'
|
|
import {getCenter as ol_extent_getCenter} from 'ol/extent.js'
|
|
import {unByKey as ol_Observable_unByKey} from 'ol/Observable.js'
|
|
import ol_layer_Base from 'ol/layer/Base.js'
|
|
import ol_style_Style from 'ol/style/Style.js'
|
|
import ol_style_Circle from 'ol/style/Circle.js'
|
|
import ol_style_Stroke from 'ol/style/Stroke.js'
|
|
import ol_layer_Vector from 'ol/layer/Vector.js'
|
|
import ol_source_Vector from 'ol/source/Vector.js'
|
|
|
|
import ol_render_getVectorContext from '../util/getVectorContext.js';
|
|
import ol_ext_getVectorContextStyle from '../util/getVectorContextStyle.js'
|
|
|
|
/** 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
|
|
* @fires animating
|
|
* @fires animationrepeat
|
|
* @fires animationend
|
|
* @fires drawing
|
|
* @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 {ol.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 not 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 = class olfeatureAnimation extends ol_Object {
|
|
constructor(options) {
|
|
options = options || {}
|
|
super();
|
|
|
|
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
|
|
}
|
|
/** 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
|
|
*/
|
|
drawGeom_(e, geom, shadow) {
|
|
// Drawing event
|
|
var drawingEvt = {
|
|
type: 'drawing',
|
|
time: e.time,
|
|
feature: e.feature,
|
|
start: e.start,
|
|
stop: e.stop,
|
|
rotation: e.rotation,
|
|
style: e.style,
|
|
extra: e.extra
|
|
}
|
|
this.dispatchEvent(drawingEvt)
|
|
var style = (drawingEvt.style instanceof Array) ? drawingEvt.style : [drawingEvt.style];
|
|
// Draw
|
|
if (this.fade_) {
|
|
e.context.globalAlpha = this.fade_(1 - e.elapsed)
|
|
}
|
|
for (var i = 0; i < style.length; i++) {
|
|
// Prevent crach if the style is not ready (image not loaded)
|
|
try {
|
|
var vectorContext = e.vectorContext || ol_render_getVectorContext(e)
|
|
var s = ol_ext_getVectorContextStyle(e, style[i])
|
|
vectorContext.setStyle(s)
|
|
if (s.getZIndex() < 0) {
|
|
vectorContext.drawGeometry(shadow || geom)
|
|
} else {
|
|
vectorContext.drawGeometry(geom)
|
|
}
|
|
} catch (error) { /* ok */ }
|
|
}
|
|
}
|
|
/** Function to perform manipulations onpostcompose.
|
|
* This function is called with an ol_featureAnimationEvent argument.
|
|
* The function will be overridden by the child implementation.
|
|
* Return true to keep this function for the next frame, false to remove it.
|
|
* @param {ol_featureAnimationEvent} e
|
|
* @return {bool} true to continue animation.
|
|
* @api
|
|
*/
|
|
animate( /* e */) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
/** Hidden style: a transparent style
|
|
*/
|
|
ol_featureAnimation.hiddenStyle = new ol_style_Style({
|
|
image: new ol_style_Circle({}),
|
|
stroke: new ol_style_Stroke({
|
|
color: 'transparent'
|
|
})
|
|
});
|
|
|
|
/** An animation controler object an object to control animation with start, stop and isPlaying function.
|
|
* To be used with {@link olx.Map#animateFeature} or {@link ol.layer.Vector#animateFeature}
|
|
* @typedef {Object} animationControler
|
|
* @property {function} start - start animation.
|
|
* @property {function} stop - stop animation option arguments can be passed in animationend event.
|
|
* @property {function} isPlaying - return true if animation is playing.
|
|
*/
|
|
|
|
/** Animate feature on a map
|
|
* @function
|
|
* @param {ol.Feature} feature Feature to animate
|
|
* @param {ol_featureAnimation|Array<ol_featureAnimation>} fanim the animation to play
|
|
* @return {animationControler} an object to control animation with start, stop and isPlaying function
|
|
*/
|
|
ol_Map.prototype.animateFeature = function(feature, fanim) {
|
|
// Get or create an animation layer associated with the map
|
|
var layer = this._featureAnimationLayer;
|
|
if (!layer) {
|
|
layer = this._featureAnimationLayer = new ol_layer_Vector({ source: new ol_source_Vector() });
|
|
layer.setMap(this);
|
|
}
|
|
// Animate feature on this layer
|
|
layer.getSource().addFeature(feature);
|
|
var listener = fanim.on('animationend', function(e) {
|
|
if (e.feature===feature) {
|
|
// Remove feature on end
|
|
layer.getSource().removeFeature(feature);
|
|
ol_Observable_unByKey(listener);
|
|
}
|
|
});
|
|
return layer.animateFeature(feature, fanim);
|
|
};
|
|
|
|
/** Animate feature on a vector layer
|
|
* @fires animationstart, animationend
|
|
* @param {ol.Feature} feature Feature to animate
|
|
* @param {ol_featureAnimation|Array<ol_featureAnimation>} fanim the animation to play
|
|
* @param {boolean} useFilter use the filters of the layer
|
|
* @return {animationControler} an object to control animation with start, stop and isPlaying function
|
|
*/
|
|
ol_layer_Base.prototype.animateFeature = function(feature, fanim, useFilter) {
|
|
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];
|
|
|
|
// 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;
|
|
// Filter availiable on the layer
|
|
var filters = (useFilter && this.getFilters) ? this.getFilters() : [];
|
|
|
|
function animate(e) {
|
|
event.type = e.type;
|
|
try {
|
|
event.vectorContext = e.vectorContext || ol_render_getVectorContext(e);
|
|
} catch(e) { /* nothing todo */ }
|
|
event.frameState = e.frameState;
|
|
event.inversePixelTransform = e.inversePixelTransform;
|
|
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;
|
|
|
|
// Filter
|
|
e.context.save();
|
|
filters.forEach(function(f) {
|
|
if (f.get('active')) f.precompose(e);
|
|
});
|
|
if (this.getOpacity) {
|
|
e.context.globalAlpha = this.getOpacity();
|
|
}
|
|
|
|
// Before anim
|
|
/*
|
|
var beforeEvent = {
|
|
type: 'beforeanim',
|
|
step: step,
|
|
start: event.start,
|
|
time: event.time,
|
|
elapsed: event.elapsed,
|
|
rotation: event.rotation||0,
|
|
geom: event.geom,
|
|
coordinate: event.coord,
|
|
feature: feature,
|
|
extra: event.extra || {},
|
|
style: flashStyle
|
|
};
|
|
fanim[step].dispatchEvent(beforeEvent);
|
|
self.dispatchEvent(beforeEvent);
|
|
*/
|
|
|
|
// Stop animation?
|
|
if (!fanim[step].animate(event)) {
|
|
nb++;
|
|
// Repeat animation
|
|
if (nb < fanim[step].repeat_) {
|
|
event.extent = false;
|
|
fanim[step].dispatchEvent({ type:'animationrepeat', feature: feature });
|
|
} else if (step < fanim.length-1) {
|
|
// newt step
|
|
fanim[step].dispatchEvent({ type:'animationend', feature: feature });
|
|
step++;
|
|
nb=0;
|
|
event.extent = false;
|
|
} else {
|
|
// the end
|
|
stop();
|
|
}
|
|
} else {
|
|
var animEvent = {
|
|
type: 'animating',
|
|
step: step,
|
|
start: event.start,
|
|
time: event.time,
|
|
elapsed: event.elapsed,
|
|
rotation: event.rotation||0,
|
|
geom: event.geom,
|
|
coordinate: event.coord,
|
|
feature: feature,
|
|
extra: event.extra || {},
|
|
style: flashStyle
|
|
};
|
|
fanim[step].dispatchEvent(animEvent);
|
|
self.dispatchEvent(animEvent);
|
|
}
|
|
|
|
filters.forEach(function(f) {
|
|
if (f.get('active')) f.postcompose(e);
|
|
});
|
|
e.context.restore();
|
|
|
|
// tell OL3 to continue postcompose animation
|
|
e.frameState.animate = true;
|
|
}
|
|
|
|
// Stop animation
|
|
function stop(options) {
|
|
ol_Observable_unByKey(listenerKey);
|
|
listenerKey = null;
|
|
feature.setStyle(style);
|
|
event.stop = (new Date).getTime();
|
|
// Send event
|
|
var eventEnd = { type:'animationend', feature: feature };
|
|
if (options) {
|
|
for (var i in options) if (options.hasOwnProperty(i)) {
|
|
eventEnd[i] = options[i];
|
|
}
|
|
}
|
|
fanim[step].dispatchEvent(eventEnd);
|
|
self.dispatchEvent(eventEnd);
|
|
}
|
|
|
|
// Launch animation
|
|
function start(options) {
|
|
if (fanim.length && !listenerKey) {
|
|
// Restart at stop time
|
|
if (event.stop) {
|
|
event.start = (new Date).getTime() - event.stop + event.start;
|
|
event.stop = 0;
|
|
}
|
|
// Compose
|
|
listenerKey = self.on(['postcompose','postrender'], animate.bind(self));
|
|
// map or layer?
|
|
if (self.renderSync) {
|
|
try { self.renderSync(); } catch(e) { /* ok */ }
|
|
} else {
|
|
self.changed();
|
|
}
|
|
// Hide feature while animating
|
|
feature.setStyle(fanim[step].hiddenStyle || ol_featureAnimation.hiddenStyle);
|
|
// Send event
|
|
var eventStart = { type:'animationstart', feature: feature };
|
|
if (options) {
|
|
for (var i in options) if (options.hasOwnProperty(i)) {
|
|
eventStart[i] = options[i];
|
|
}
|
|
}
|
|
fanim[step].dispatchEvent(eventStart);
|
|
self.dispatchEvent(eventStart);
|
|
}
|
|
}
|
|
start();
|
|
|
|
// Return animation controler
|
|
return {
|
|
start: start,
|
|
stop: stop,
|
|
isPlaying: function() { return (!!listenerKey); }
|
|
};
|
|
};
|
|
|
|
export default ol_featureAnimation
|