--- a/labs/openlayers/lib/OpenLayers/Strategy/Cluster.js +++ b/labs/openlayers/lib/OpenLayers/Strategy/Cluster.js @@ -1,1 +1,281 @@ - +/* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the Clear BSD license. + * See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Strategy.js + */ + +/** + * Class: OpenLayers.Strategy.Cluster + * Strategy for vector feature clustering. + * + * Inherits from: + * - + */ +OpenLayers.Strategy.Cluster = OpenLayers.Class(OpenLayers.Strategy, { + + /** + * APIProperty: distance + * {Integer} Pixel distance between features that should be considered a + * single cluster. Default is 20 pixels. + */ + distance: 20, + + /** + * APIProperty: threshold + * {Integer} Optional threshold below which original features will be + * added to the layer instead of clusters. For example, a threshold + * of 3 would mean that any time there are 2 or fewer features in + * a cluster, those features will be added directly to the layer instead + * of a cluster representing those features. Default is null (which is + * equivalent to 1 - meaning that clusters may contain just one feature). + */ + threshold: null, + + /** + * Property: features + * {Array()} Cached features. + */ + features: null, + + /** + * Property: clusters + * {Array()} Calculated clusters. + */ + clusters: null, + + /** + * Property: clustering + * {Boolean} The strategy is currently clustering features. + */ + clustering: false, + + /** + * Property: resolution + * {Float} The resolution (map units per pixel) of the current cluster set. + */ + resolution: null, + + /** + * Constructor: OpenLayers.Strategy.Cluster + * Create a new clustering strategy. + * + * Parameters: + * options - {Object} Optional object whose properties will be set on the + * instance. + */ + initialize: function(options) { + OpenLayers.Strategy.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: activate + * Activate the strategy. Register any listeners, do appropriate setup. + * + * Returns: + * {Boolean} The strategy was successfully activated. + */ + activate: function() { + var activated = OpenLayers.Strategy.prototype.activate.call(this); + if(activated) { + this.layer.events.on({ + "beforefeaturesadded": this.cacheFeatures, + "moveend": this.cluster, + scope: this + }); + } + return activated; + }, + + /** + * APIMethod: deactivate + * Deactivate the strategy. Unregister any listeners, do appropriate + * tear-down. + * + * Returns: + * {Boolean} The strategy was successfully deactivated. + */ + deactivate: function() { + var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this); + if(deactivated) { + this.clearCache(); + this.layer.events.un({ + "beforefeaturesadded": this.cacheFeatures, + "moveend": this.cluster, + scope: this + }); + } + return deactivated; + }, + + /** + * Method: cacheFeatures + * Cache features before they are added to the layer. + * + * Parameters: + * event - {Object} The event that this was listening for. This will come + * with a batch of features to be clustered. + * + * Returns: + * {Boolean} False to stop features from being added to the layer. + */ + cacheFeatures: function(event) { + var propagate = true; + if(!this.clustering) { + this.clearCache(); + this.features = event.features; + this.cluster(); + propagate = false; + } + return propagate; + }, + + /** + * Method: clearCache + * Clear out the cached features. + */ + clearCache: function() { + this.features = null; + }, + + /** + * Method: cluster + * Cluster features based on some threshold distance. + * + * Parameters: + * event - {Object} The event received when cluster is called as a + * result of a moveend event. + */ + cluster: function(event) { + if((!event || event.zoomChanged) && this.features) { + var resolution = this.layer.map.getResolution(); + if(resolution != this.resolution || !this.clustersExist()) { + this.resolution = resolution; + var clusters = []; + var feature, clustered, cluster; + for(var i=0; i=0; --j) { + cluster = clusters[j]; + if(this.shouldCluster(cluster, feature)) { + this.addToCluster(cluster, feature); + clustered = true; + break; + } + } + if(!clustered) { + clusters.push(this.createCluster(this.features[i])); + } + } + } + this.layer.removeAllFeatures(); + if(clusters.length > 0) { + if(this.threshold > 1) { + var clone = clusters.slice(); + clusters = []; + var candidate; + for(var i=0, len=clone.length; i 0 && + this.clusters.length == this.layer.features.length) { + exist = true; + for(var i=0; i} A cluster. + * feature - {} A feature. + * + * Returns: + * {Boolean} The feature should be included in the cluster. + */ + shouldCluster: function(cluster, feature) { + var cc = cluster.geometry.getBounds().getCenterLonLat(); + var fc = feature.geometry.getBounds().getCenterLonLat(); + var distance = ( + Math.sqrt( + Math.pow((cc.lon - fc.lon), 2) + Math.pow((cc.lat - fc.lat), 2) + ) / this.resolution + ); + return (distance <= this.distance); + }, + + /** + * Method: addToCluster + * Add a feature to a cluster. + * + * Parameters: + * cluster - {} A cluster. + * feature - {} A feature. + */ + addToCluster: function(cluster, feature) { + cluster.cluster.push(feature); + cluster.attributes.count += 1; + }, + + /** + * Method: createCluster + * Given a feature, create a cluster. + * + * Parameters: + * feature - {} + * + * Returns: + * {} A cluster. + */ + createCluster: function(feature) { + var center = feature.geometry.getBounds().getCenterLonLat(); + var cluster = new OpenLayers.Feature.Vector( + new OpenLayers.Geometry.Point(center.lon, center.lat), + {count: 1} + ); + cluster.cluster = [feature]; + return cluster; + }, + + CLASS_NAME: "OpenLayers.Strategy.Cluster" +}); +