1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2 * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3 * full text of the license. */
6 * @requires OpenLayers/Strategy.js
10 * Class: OpenLayers.Strategy.Cluster
11 * Strategy for vector feature clustering.
14 * - <OpenLayers.Strategy>
16 OpenLayers.Strategy.Cluster = OpenLayers.Class(OpenLayers.Strategy, {
19 * APIProperty: distance
20 * {Integer} Pixel distance between features that should be considered a
21 * single cluster. Default is 20 pixels.
26 * APIProperty: threshold
27 * {Integer} Optional threshold below which original features will be
28 * added to the layer instead of clusters. For example, a threshold
29 * of 3 would mean that any time there are 2 or fewer features in
30 * a cluster, those features will be added directly to the layer instead
31 * of a cluster representing those features. Default is null (which is
32 * equivalent to 1 - meaning that clusters may contain just one feature).
38 * {Array(<OpenLayers.Feature.Vector>)} Cached features.
44 * {Array(<OpenLayers.Feature.Vector>)} Calculated clusters.
49 * Property: clustering
50 * {Boolean} The strategy is currently clustering features.
55 * Property: resolution
56 * {Float} The resolution (map units per pixel) of the current cluster set.
61 * Constructor: OpenLayers.Strategy.Cluster
62 * Create a new clustering strategy.
65 * options - {Object} Optional object whose properties will be set on the
68 initialize: function(options) {
69 OpenLayers.Strategy.prototype.initialize.apply(this, [options]);
74 * Activate the strategy. Register any listeners, do appropriate setup.
77 * {Boolean} The strategy was successfully activated.
79 activate: function() {
80 var activated = OpenLayers.Strategy.prototype.activate.call(this);
82 this.layer.events.on({
83 "beforefeaturesadded": this.cacheFeatures,
84 "moveend": this.cluster,
92 * APIMethod: deactivate
93 * Deactivate the strategy. Unregister any listeners, do appropriate
97 * {Boolean} The strategy was successfully deactivated.
99 deactivate: function() {
100 var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
103 this.layer.events.un({
104 "beforefeaturesadded": this.cacheFeatures,
105 "moveend": this.cluster,
113 * Method: cacheFeatures
114 * Cache features before they are added to the layer.
117 * event - {Object} The event that this was listening for. This will come
118 * with a batch of features to be clustered.
121 * {Boolean} False to stop layer from being added to the layer.
123 cacheFeatures: function(event) {
124 var propagate = true;
125 if(!this.clustering) {
127 this.features = event.features;
136 * Clear out the cached features.
138 clearCache: function() {
139 this.features = null;
144 * Cluster features based on some threshold distance.
147 * event - {Object} The event received when cluster is called as a
148 * result of a moveend event.
150 cluster: function(event) {
151 if((!event || event.zoomChanged) && this.features) {
152 var resolution = this.layer.map.getResolution();
153 if(resolution != this.resolution || !this.clustersExist()) {
154 this.resolution = resolution;
156 var feature, clustered, cluster;
157 for(var i=0; i<this.features.length; ++i) {
158 feature = this.features[i];
159 if(feature.geometry) {
161 for(var j=0; j<clusters.length; ++j) {
162 cluster = clusters[j];
163 if(this.shouldCluster(cluster, feature)) {
164 this.addToCluster(cluster, feature);
170 clusters.push(this.createCluster(this.features[i]));
174 this.layer.destroyFeatures();
175 if(clusters.length > 0) {
176 if(this.threshold > 1) {
177 var clone = clusters.slice();
180 for(var i=0, len=clone.length; i<len; ++i) {
181 candidate = clone[i];
182 if(candidate.attributes.count < this.threshold) {
183 Array.prototype.push.apply(clusters, candidate.cluster);
185 clusters.push(candidate);
189 this.clustering = true;
190 // A legitimate feature addition could occur during this
191 // addFeatures call. For clustering to behave well, features
192 // should be removed from a layer before requesting a new batch.
193 this.layer.addFeatures(clusters);
194 this.clustering = false;
196 this.clusters = clusters;
202 * Method: clustersExist
203 * Determine whether calculated clusters are already on the layer.
206 * {Boolean} The calculated clusters are already on the layer.
208 clustersExist: function() {
210 if(this.clusters && this.clusters.length > 0 &&
211 this.clusters.length == this.layer.features.length) {
213 for(var i=0; i<this.clusters.length; ++i) {
214 if(this.clusters[i] != this.layer.features[i]) {
224 * Method: shouldCluster
225 * Determine whether to include a feature in a given cluster.
228 * cluster - {<OpenLayers.Feature.Vector>} A cluster.
229 * feature - {<OpenLayers.Feature.Vector>} A feature.
232 * {Boolean} The feature should be included in the cluster.
234 shouldCluster: function(cluster, feature) {
235 var cc = cluster.geometry.getBounds().getCenterLonLat();
236 var fc = feature.geometry.getBounds().getCenterLonLat();
239 Math.pow((cc.lon - fc.lon), 2) + Math.pow((cc.lat - fc.lat), 2)
242 return (distance <= this.distance);
246 * Method: addToCluster
247 * Add a feature to a cluster.
250 * cluster - {<OpenLayers.Feature.Vector>} A cluster.
251 * feature - {<OpenLayers.Feature.Vector>} A feature.
253 addToCluster: function(cluster, feature) {
254 cluster.cluster.push(feature);
255 cluster.attributes.count += 1;
259 * Method: createCluster
260 * Given a feature, create a cluster.
263 * feature - {<OpenLayers.Feature.Vector>}
266 * {<OpenLayers.Feature.Vector>} A cluster.
268 createCluster: function(feature) {
269 var center = feature.geometry.getBounds().getCenterLonLat();
270 var cluster = new OpenLayers.Feature.Vector(
271 new OpenLayers.Geometry.Point(center.lon, center.lat),
274 cluster.cluster = [feature];
278 CLASS_NAME: "OpenLayers.Strategy.Cluster"