284 lines
9.0 KiB
JavaScript
284 lines
9.0 KiB
JavaScript
/*
|
|
Copyright (c) 2015 Jean-Marc VIGLINO,
|
|
released under the CeCILL-B license (http://www.cecill.info/).
|
|
|
|
ol_layer_AnimatedCluster is a vector layer that animate cluster
|
|
*/
|
|
import ol_layer_Vector from 'ol/layer/Vector.js'
|
|
import ol_source_Vector from 'ol/source/Vector.js'
|
|
import ol_Feature from 'ol/Feature.js'
|
|
import {easeOut as ol_easing_easeOut} from 'ol/easing.js'
|
|
import {buffer as ol_extent_buffer} from 'ol/extent.js'
|
|
import ol_geom_Point from 'ol/geom/Point.js'
|
|
import ol_render_getVectorContext from '../util/getVectorContext.js';
|
|
import ol_ext_getVectorContextStyle from '../util/getVectorContextStyle.js'
|
|
|
|
/**
|
|
* A vector layer for animated cluster
|
|
* @constructor
|
|
* @extends {ol.layer.Vector}
|
|
* @param {olx.layer.AnimatedClusterOptions=} options extend olx.layer.Options
|
|
* @param {Number} options.animationDuration animation duration in ms, default is 700ms
|
|
* @param {ol.easingFunction} animationMethod easing method to use, default ol.easing.easeOut
|
|
*/
|
|
var ol_layer_AnimatedCluster = class ollayerAnimatedCluster extends ol_layer_Vector {
|
|
constructor(opt_options) {
|
|
var options = opt_options || {}
|
|
|
|
super(options)
|
|
|
|
this.oldcluster = new ol_source_Vector()
|
|
this.clusters = []
|
|
this.animation = { start: false }
|
|
this.set('animationDuration', typeof (options.animationDuration) == 'number' ? options.animationDuration : 700)
|
|
this.set('animationMethod', options.animationMethod || ol_easing_easeOut)
|
|
|
|
// Animate the cluster
|
|
this.on(['precompose', 'prerender'], this.animate.bind(this))
|
|
this.on(['postcompose', 'postrender'], this.postanimate.bind(this))
|
|
}
|
|
/** Set the cluster source
|
|
* @param {ol_source_Vector} source
|
|
*/
|
|
setSource(source) {
|
|
if (!this._saveClusterFn) this._saveClusterFn = this.saveCluster.bind(this)
|
|
// Save cluster before change
|
|
if (this.getSource()) this.getSource().un('change', this._saveClusterFn)
|
|
ol_layer_Vector.prototype.setSource.call(this, source)
|
|
if (this.getSource()) this.getSource().on('change', this._saveClusterFn)
|
|
}
|
|
/** save cluster features before change
|
|
* @private
|
|
*/
|
|
saveCluster() {
|
|
if (this.oldcluster) {
|
|
this.oldcluster.clear()
|
|
if (!this.get('animationDuration'))
|
|
return
|
|
var features = this.getSource().getFeatures()
|
|
if (features.length && features[0].get('features')) {
|
|
this.oldcluster.addFeatures(this.clusters)
|
|
this.clusters = features.slice(0)
|
|
this.sourceChanged = true
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Get the cluster that contains a feature
|
|
* @private
|
|
*/
|
|
getClusterForFeature(f, cluster) {
|
|
for (var j = 0, c; c = cluster[j]; j++) {
|
|
var features = c.get('features')
|
|
if (features && features.length) {
|
|
for (var k = 0, f2; f2 = features[k]; k++) {
|
|
if (f === f2) {
|
|
return c
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
/**
|
|
* Stop animation
|
|
* @private
|
|
*/
|
|
stopAnimation() {
|
|
this.animation.start = false
|
|
this.animation.cA = []
|
|
this.animation.cB = []
|
|
}
|
|
/**
|
|
* animate the cluster
|
|
* @private
|
|
*/
|
|
animate(e) {
|
|
var duration = this.get('animationDuration')
|
|
if (!duration)
|
|
return
|
|
var resolution = e.frameState.viewState.resolution
|
|
// var ratio = e.frameState.pixelRatio;
|
|
var i, c0, a = this.animation
|
|
var time = e.frameState.time
|
|
|
|
// Start a new animation, if change resolution and source has changed
|
|
if (a.resolution != resolution && this.sourceChanged) {
|
|
var extent = e.frameState.extent
|
|
if (a.resolution < resolution) {
|
|
extent = ol_extent_buffer(extent, 100 * resolution)
|
|
a.cA = this.oldcluster.getFeaturesInExtent(extent)
|
|
a.cB = this.getSource().getFeaturesInExtent(extent)
|
|
a.revers = false
|
|
} else {
|
|
extent = ol_extent_buffer(extent, 100 * resolution)
|
|
a.cA = this.getSource().getFeaturesInExtent(extent)
|
|
a.cB = this.oldcluster.getFeaturesInExtent(extent)
|
|
a.revers = true
|
|
}
|
|
a.clusters = []
|
|
for (i = 0, c0; c0 = a.cA[i]; i++) {
|
|
var f = c0.get('features')
|
|
if (f && f.length) {
|
|
var c = this.getClusterForFeature(f[0], a.cB)
|
|
if (c)
|
|
a.clusters.push({ f: c0, pt: c.getGeometry().getCoordinates() })
|
|
}
|
|
}
|
|
// Save state
|
|
a.resolution = resolution
|
|
this.sourceChanged = false
|
|
|
|
// No cluster or too much to animate
|
|
if (!a.clusters.length || a.clusters.length > 1000) {
|
|
this.stopAnimation()
|
|
return
|
|
}
|
|
// Start animation from now
|
|
time = a.start = (new Date()).getTime()
|
|
}
|
|
|
|
// Run animation
|
|
if (a.start) {
|
|
var vectorContext = e.vectorContext || ol_render_getVectorContext(e)
|
|
var d = (time - a.start) / duration
|
|
// Animation ends
|
|
if (d > 1.0) {
|
|
this.stopAnimation()
|
|
d = 1
|
|
}
|
|
d = this.get('animationMethod')(d)
|
|
// Animate
|
|
var style = this.getStyle()
|
|
var stylefn = (typeof (style) == 'function') ? style : style.length ? function () { return style } : function () { return [style] }
|
|
// Layer opacity
|
|
e.context.save()
|
|
e.context.globalAlpha = this.getOpacity()
|
|
for (i = 0, c; c = a.clusters[i]; i++) {
|
|
var pt = c.f.getGeometry().getCoordinates()
|
|
var dx = pt[0] - c.pt[0]
|
|
var dy = pt[1] - c.pt[1]
|
|
if (a.revers) {
|
|
pt[0] = c.pt[0] + d * dx
|
|
pt[1] = c.pt[1] + d * dy
|
|
} else {
|
|
pt[0] = pt[0] - d * dx
|
|
pt[1] = pt[1] - d * dy
|
|
}
|
|
// Draw feature
|
|
var st = stylefn(c.f, resolution, true)
|
|
if (!Array.isArray(st)) {
|
|
st = [st]
|
|
}
|
|
// If one feature: draw the feature
|
|
if (c.f.get("features").length === 1 && !dx && !dy) {
|
|
f = c.f.get("features")[0]
|
|
} else {
|
|
// else draw a point
|
|
var geo = new ol_geom_Point(pt)
|
|
f = new ol_Feature(geo)
|
|
}
|
|
for (var k = 0, s; s = st[k]; k++) {
|
|
// Multi-line text
|
|
if (s.getText() && /\n/.test(s.getText().getText())) {
|
|
var offsetX = s.getText().getOffsetX()
|
|
var offsetY = s.getText().getOffsetY()
|
|
var rot = s.getText().getRotation() || 0
|
|
var fontSize = Number((s.getText().getFont() || '10px').match(/\d+/)) * 1.2
|
|
var str = s.getText().getText().split('\n')
|
|
var dl, nb = str.length - 1
|
|
var s2 = s.clone()
|
|
// Draw each lines
|
|
str.forEach(function (t, i) {
|
|
if (i == 1) {
|
|
// Allready drawn
|
|
s2.setImage()
|
|
s2.setFill()
|
|
s2.setStroke()
|
|
}
|
|
switch (s.getText().getTextBaseline()) {
|
|
case 'alphabetic':
|
|
case 'ideographic':
|
|
case 'bottom': {
|
|
dl = nb
|
|
break
|
|
}
|
|
case 'hanging':
|
|
case 'top': {
|
|
dl = 0
|
|
break
|
|
}
|
|
default: {
|
|
dl = nb / 2
|
|
break
|
|
}
|
|
}
|
|
s2.getText().setOffsetX(offsetX - Math.sin(rot) * fontSize * (i - dl))
|
|
s2.getText().setOffsetY(offsetY + Math.cos(rot) * fontSize * (i - dl))
|
|
s2.getText().setText(t)
|
|
vectorContext.drawFeature(f, ol_ext_getVectorContextStyle(e, s2))
|
|
})
|
|
} else {
|
|
vectorContext.drawFeature(f, ol_ext_getVectorContextStyle(e, s))
|
|
}
|
|
/* OLD VERSION OL < 4.3
|
|
// Retina device
|
|
var ratio = e.frameState.pixelRatio;
|
|
|
|
var sc;
|
|
// OL < v4.3 : setImageStyle doesn't check retina
|
|
var imgs = ol_Map.prototype.getFeaturesAtPixel ? false : s.getImage();
|
|
if (imgs)
|
|
{ sc = imgs.getScale();
|
|
imgs.setScale(sc*ratio);
|
|
}
|
|
// OL3 > v3.14
|
|
if (vectorContext.setStyle)
|
|
{ // If one feature: draw the feature
|
|
if (c.f.get("features").length===1 && !dx && !dy) {
|
|
vectorContext.drawFeature(c.f.get("features")[0], s);
|
|
}
|
|
// else draw a point
|
|
else {
|
|
vectorContext.setStyle(s);
|
|
vectorContext.drawGeometry(geo);
|
|
}
|
|
}
|
|
// older version
|
|
else
|
|
{ vectorContext.setImageStyle(imgs);
|
|
vectorContext.setTextStyle(s.getText());
|
|
vectorContext.drawPointGeometry(geo);
|
|
}
|
|
if (imgs) imgs.setScale(sc);
|
|
*/
|
|
}
|
|
}
|
|
e.context.restore()
|
|
// tell ol to continue postcompose animation
|
|
e.frameState.animate = true
|
|
|
|
// Prevent layer drawing (clip with null rect)
|
|
e.context.save()
|
|
e.context.beginPath()
|
|
e.context.rect(0, 0, 0, 0)
|
|
e.context.clip()
|
|
this.clip_ = true
|
|
}
|
|
|
|
return
|
|
}
|
|
/**
|
|
* remove clipping after the layer is drawn
|
|
* @private
|
|
*/
|
|
postanimate(e) {
|
|
if (this.clip_) {
|
|
e.context.restore()
|
|
this.clip_ = false
|
|
}
|
|
}
|
|
}
|
|
|
|
export default ol_layer_AnimatedCluster
|