]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Strategy/Cluster.js
fixes notices
[syp.git] / openlayers / lib / OpenLayers / Strategy / Cluster.js
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. */
4
5 /**
6  * @requires OpenLayers/Strategy.js
7  */
8
9 /**
10  * Class: OpenLayers.Strategy.Cluster
11  * Strategy for vector feature clustering.
12  *
13  * Inherits from:
14  *  - <OpenLayers.Strategy>
15  */
16 OpenLayers.Strategy.Cluster = OpenLayers.Class(OpenLayers.Strategy, {
17     
18     /**
19      * APIProperty: distance
20      * {Integer} Pixel distance between features that should be considered a
21      *     single cluster.  Default is 20 pixels.
22      */
23     distance: 20,
24     
25     /**
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).
33      */
34     threshold: null,
35     
36     /**
37      * Property: features
38      * {Array(<OpenLayers.Feature.Vector>)} Cached features.
39      */
40     features: null,
41     
42     /**
43      * Property: clusters
44      * {Array(<OpenLayers.Feature.Vector>)} Calculated clusters.
45      */
46     clusters: null,
47     
48     /**
49      * Property: clustering
50      * {Boolean} The strategy is currently clustering features.
51      */
52     clustering: false,
53     
54     /**
55      * Property: resolution
56      * {Float} The resolution (map units per pixel) of the current cluster set.
57      */
58     resolution: null,
59
60     /**
61      * Constructor: OpenLayers.Strategy.Cluster
62      * Create a new clustering strategy.
63      *
64      * Parameters:
65      * options - {Object} Optional object whose properties will be set on the
66      *     instance.
67      */
68     initialize: function(options) {
69         OpenLayers.Strategy.prototype.initialize.apply(this, [options]);
70     },
71     
72     /**
73      * APIMethod: activate
74      * Activate the strategy.  Register any listeners, do appropriate setup.
75      * 
76      * Returns:
77      * {Boolean} The strategy was successfully activated.
78      */
79     activate: function() {
80         var activated = OpenLayers.Strategy.prototype.activate.call(this);
81         if(activated) {
82             this.layer.events.on({
83                 "beforefeaturesadded": this.cacheFeatures,
84                 "moveend": this.cluster,
85                 scope: this
86             });
87         }
88         return activated;
89     },
90     
91     /**
92      * APIMethod: deactivate
93      * Deactivate the strategy.  Unregister any listeners, do appropriate
94      *     tear-down.
95      * 
96      * Returns:
97      * {Boolean} The strategy was successfully deactivated.
98      */
99     deactivate: function() {
100         var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
101         if(deactivated) {
102             this.clearCache();
103             this.layer.events.un({
104                 "beforefeaturesadded": this.cacheFeatures,
105                 "moveend": this.cluster,
106                 scope: this
107             });
108         }
109         return deactivated;
110     },
111     
112     /**
113      * Method: cacheFeatures
114      * Cache features before they are added to the layer.
115      *
116      * Parameters:
117      * event - {Object} The event that this was listening for.  This will come
118      *     with a batch of features to be clustered.
119      *     
120      * Returns:
121      * {Boolean} False to stop layer from being added to the layer.
122      */
123     cacheFeatures: function(event) {
124         var propagate = true;
125         if(!this.clustering) {
126             this.clearCache();
127             this.features = event.features;
128             this.cluster();
129             propagate = false;
130         }
131         return propagate;
132     },
133     
134     /**
135      * Method: clearCache
136      * Clear out the cached features.
137      */
138     clearCache: function() {
139         this.features = null;
140     },
141     
142     /**
143      * Method: cluster
144      * Cluster features based on some threshold distance.
145      *
146      * Parameters:
147      * event - {Object} The event received when cluster is called as a
148      *     result of a moveend event.
149      */
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;
155                 var clusters = [];
156                 var feature, clustered, cluster;
157                 for(var i=0; i<this.features.length; ++i) {
158                     feature = this.features[i];
159                     if(feature.geometry) {
160                         clustered = false;
161                         for(var j=0; j<clusters.length; ++j) {
162                             cluster = clusters[j];
163                             if(this.shouldCluster(cluster, feature)) {
164                                 this.addToCluster(cluster, feature);
165                                 clustered = true;
166                                 break;
167                             }
168                         }
169                         if(!clustered) {
170                             clusters.push(this.createCluster(this.features[i]));
171                         }
172                     }
173                 }
174                 this.layer.destroyFeatures();
175                 if(clusters.length > 0) {
176                     if(this.threshold > 1) {
177                         var clone = clusters.slice();
178                         clusters = [];
179                         var candidate;
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);
184                             } else {
185                                 clusters.push(candidate);
186                             }
187                         }
188                     }
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;
195                 }
196                 this.clusters = clusters;
197             }
198         }
199     },
200     
201     /**
202      * Method: clustersExist
203      * Determine whether calculated clusters are already on the layer.
204      *
205      * Returns:
206      * {Boolean} The calculated clusters are already on the layer.
207      */
208     clustersExist: function() {
209         var exist = false;
210         if(this.clusters && this.clusters.length > 0 &&
211            this.clusters.length == this.layer.features.length) {
212             exist = true;
213             for(var i=0; i<this.clusters.length; ++i) {
214                 if(this.clusters[i] != this.layer.features[i]) {
215                     exist = false;
216                     break;
217                 }
218             }
219         }
220         return exist;
221     },
222     
223     /**
224      * Method: shouldCluster
225      * Determine whether to include a feature in a given cluster.
226      *
227      * Parameters:
228      * cluster - {<OpenLayers.Feature.Vector>} A cluster.
229      * feature - {<OpenLayers.Feature.Vector>} A feature.
230      *
231      * Returns:
232      * {Boolean} The feature should be included in the cluster.
233      */
234     shouldCluster: function(cluster, feature) {
235         var cc = cluster.geometry.getBounds().getCenterLonLat();
236         var fc = feature.geometry.getBounds().getCenterLonLat();
237         var distance = (
238             Math.sqrt(
239                 Math.pow((cc.lon - fc.lon), 2) + Math.pow((cc.lat - fc.lat), 2)
240             ) / this.resolution
241         );
242         return (distance <= this.distance);
243     },
244     
245     /**
246      * Method: addToCluster
247      * Add a feature to a cluster.
248      *
249      * Parameters:
250      * cluster - {<OpenLayers.Feature.Vector>} A cluster.
251      * feature - {<OpenLayers.Feature.Vector>} A feature.
252      */
253     addToCluster: function(cluster, feature) {
254         cluster.cluster.push(feature);
255         cluster.attributes.count += 1;
256     },
257     
258     /**
259      * Method: createCluster
260      * Given a feature, create a cluster.
261      *
262      * Parameters:
263      * feature - {<OpenLayers.Feature.Vector>}
264      *
265      * Returns:
266      * {<OpenLayers.Feature.Vector>} A cluster.
267      */
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),
272             {count: 1}
273         );
274         cluster.cluster = [feature];
275         return cluster;
276     },
277
278     CLASS_NAME: "OpenLayers.Strategy.Cluster" 
279 });