animatedclusterlayer.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. /*
  2. Copyright (c) 2015 Jean-Marc VIGLINO,
  3. released under the CeCILL-B license (http://www.cecill.info/).
  4. github: https://github.com/Viglino/OL3-AnimatedCluster
  5. ol.layer.AnimatedCluster is a vector layer tha animate cluster
  6. olx.layer.AnimatedClusterOptions: extend olx.layer.Options
  7. { animationDuration {Number} animation duration in ms, default is 700ms
  8. animationMethod {function} easing method to use, default ol.easing.easeOut
  9. }
  10. */
  11. /**
  12. * @constructor AnimatedCluster
  13. * @extends {ol.layer.Vector}
  14. * @param {olx.layer.AnimatedClusterOptions=} options
  15. * @todo
  16. */
  17. ol.layer.AnimatedCluster = function(opt_options)
  18. { var options = opt_options || {};
  19. ol.layer.Vector.call (this, options);
  20. this.oldcluster = new ol.source.Vector();
  21. this.clusters = [];
  22. this.animation={start:false};
  23. this.set('animationDuration', typeof(options.animationDuration)=='number' ? options.animationDuration : 700);
  24. this.set('animationMethod', options.animationMethod || ol.easing.easeOut);
  25. // Save cluster before change
  26. this.getSource().on('change', this.saveCluster, this);
  27. // Animate the cluster
  28. this.on('precompose', this.animate, this);
  29. this.on('postcompose', this.postanimate, this);
  30. };
  31. ol.inherits (ol.layer.AnimatedCluster, ol.layer.Vector);
  32. /** @private save cluster features before change
  33. */
  34. ol.layer.AnimatedCluster.prototype.saveCluster = function()
  35. { this.oldcluster.clear();
  36. if (!this.get('animationDuration')) return;
  37. var features = this.getSource().getFeatures();
  38. if (features.length && features[0].get('features'))
  39. { this.oldcluster.addFeatures (this.clusters);
  40. this.clusters = features.slice(0);
  41. this.sourceChanged = true;
  42. }
  43. };
  44. /** @private Get the cluster that contains a feature
  45. */
  46. ol.layer.AnimatedCluster.prototype.getClusterForFeature = function(f, cluster)
  47. { for (var j=0, c; c=cluster[j]; j++)
  48. { var features = cluster[j].get('features');
  49. if (features && features.length)
  50. { for (var k=0, f2; f2=features[k]; k++)
  51. { if (f===f2)
  52. { return cluster[j];
  53. }
  54. }
  55. }
  56. }
  57. return false;
  58. };
  59. /** @private
  60. */
  61. ol.layer.AnimatedCluster.prototype.stopAnimation = function()
  62. { this.animation.start = false;
  63. this.animation.cA = [];
  64. this.animation.cB = [];
  65. };
  66. /** @private animate the cluster
  67. */
  68. ol.layer.AnimatedCluster.prototype.animate = function(e)
  69. { var duration = this.get('animationDuration');
  70. if (!duration) return;
  71. var resolution = e.frameState.viewState.resolution;
  72. var a = this.animation;
  73. var time = e.frameState.time;
  74. // Start a new animation, if change resolution and source has changed
  75. if (a.resolution != resolution && this.sourceChanged)
  76. { var extent = e.frameState.extent;
  77. if (a.resolution < resolution)
  78. { extent = ol.extent.buffer(extent, 100*resolution);
  79. a.cA = this.oldcluster.getFeaturesInExtent(extent);
  80. a.cB = this.getSource().getFeaturesInExtent(extent);
  81. a.revers = false;
  82. }
  83. else
  84. { extent = ol.extent.buffer(extent, 100*resolution);
  85. a.cA = this.getSource().getFeaturesInExtent(extent);
  86. a.cB = this.oldcluster.getFeaturesInExtent(extent);
  87. a.revers = true;
  88. }
  89. a.clusters = [];
  90. for (var i=0, c0; c0=a.cA[i]; i++)
  91. { var f = c0.get('features');
  92. if (f && f.length)
  93. { var c = this.getClusterForFeature (f[0], a.cB);
  94. if (c) a.clusters.push({ f:c0, pt:c.getGeometry().getCoordinates() });
  95. }
  96. }
  97. // Save state
  98. a.resolution = resolution;
  99. this.sourceChanged = false;
  100. // No cluster or too much to animate
  101. if (!a.clusters.length || a.clusters.length>1000)
  102. { this.stopAnimation();
  103. return;
  104. }
  105. // Start animation from now
  106. time = a.start = (new Date()).getTime();
  107. }
  108. // Run animation
  109. if (a.start)
  110. { var vectorContext = e.vectorContext;
  111. var d = (time - a.start) / duration;
  112. // Animation ends
  113. if (d > 1.0)
  114. { this.stopAnimation();
  115. d = 1;
  116. }
  117. d = this.get('animationMethod')(d);
  118. // Animate
  119. var style = this.getStyle();
  120. var stylefn = (typeof(style) == 'function') ? style : style.length ? function(){ return style; } : function(){ return [style]; } ;
  121. // Layer opacity
  122. e.context.save();
  123. e.context.globalAlpha = this.getOpacity();
  124. // Retina device
  125. var ratio = e.frameState.pixelRatio;
  126. for (var i=0, c; c=a.clusters[i]; i++)
  127. { var pt = c.f.getGeometry().getCoordinates();
  128. if (a.revers)
  129. { pt[0] = c.pt[0] + d * (pt[0]-c.pt[0]);
  130. pt[1] = c.pt[1] + d * (pt[1]-c.pt[1]);
  131. }
  132. else
  133. { pt[0] = pt[0] + d * (c.pt[0]-pt[0]);
  134. pt[1] = pt[1] + d * (c.pt[1]-pt[1]);
  135. }
  136. // Draw feature
  137. var st = stylefn(c.f, resolution);
  138. /* Preserve pixel ration on retina */
  139. var geo = new ol.geom.Point(pt);
  140. for (var k=0; s=st[k]; k++)
  141. { var sc;
  142. // OL < v4.3 : setImageStyle doesn't check retina
  143. var imgs = ol.Map.prototype.getFeaturesAtPixel ? false : s.getImage();
  144. if (imgs)
  145. { sc = imgs.getScale();
  146. imgs.setScale(sc*ratio);
  147. }
  148. // OL3 > v3.14
  149. if (vectorContext.setStyle)
  150. { vectorContext.setStyle(s);
  151. vectorContext.drawGeometry(geo);
  152. }
  153. // older version
  154. else
  155. { vectorContext.setImageStyle(imgs);
  156. vectorContext.setTextStyle(s.getText());
  157. vectorContext.drawPointGeometry(geo);
  158. }
  159. if (imgs) imgs.setScale(sc);
  160. }
  161. /*/
  162. var f = new ol.Feature(new ol.geom.Point(pt));
  163. for (var k=0; s=st[k]; k++)
  164. { var imgs = s.getImage();
  165. var sc = imgs.getScale();
  166. imgs.setScale(sc*ratio); // drawFeature don't check retina
  167. vectorContext.drawFeature(f, s);
  168. imgs.setScale(sc);
  169. }
  170. /**/
  171. }
  172. e.context.restore();
  173. // tell OL3 to continue postcompose animation
  174. e.frameState.animate = true;
  175. // Prevent layer drawing (clip with null rect)
  176. e.context.save();
  177. e.context.beginPath();
  178. e.context.rect(0,0,0,0);
  179. e.context.clip();
  180. this.clip_ = true;
  181. }
  182. return;
  183. };
  184. /** @private remove clipping after the layer is drawn
  185. */
  186. ol.layer.AnimatedCluster.prototype.postanimate = function(e)
  187. { if (this.clip_)
  188. { e.context.restore();
  189. this.clip_ = false;
  190. }
  191. };