OpenLayers OpenLayers

Ticket #1606: strategy.patch

File strategy.patch, 62.4 kB (added by tschaub, 4 months ago)

adds paging and cluster strategy

  • tests/Strategy/Cluster.html

    old new  
     1<html> 
     2<head> 
     3  <script src="../../lib/OpenLayers.js"></script> 
     4  <script type="text/javascript"> 
     5 
     6    function test_activate(t) { 
     7        t.plan(2); 
     8 
     9        var strategy = new OpenLayers.Strategy.Cluster(); 
     10        t.eq(strategy.active, false, "not active after construction"); 
     11 
     12        var layer = new OpenLayers.Layer.Vector("Vector Layer", { 
     13            strategies: [strategy] 
     14        }); 
     15        var map = new OpenLayers.Map('map'); 
     16        map.addLayer(layer); 
     17         
     18        t.eq(strategy.active, true, "active after adding to map"); 
     19    } 
     20 
     21    function test_clusters(t) { 
     22        t.plan(10); 
     23 
     24        function featuresEq(got, exp) { 
     25            var eq = false; 
     26            if(got instanceof Array && exp instanceof Array) { 
     27                if(got.length === exp.length) { 
     28                    for(var i=0; i<got.length; ++i) { 
     29                        if(got[i] !== exp[i]) { 
     30                            console.log(got[i], exp[i]); 
     31                            break; 
     32                        } 
     33                    } 
     34                    eq = (i == got.length); 
     35                } 
     36            } 
     37            return eq; 
     38        } 
     39 
     40        var strategy = new OpenLayers.Strategy.Cluster(); 
     41        var layer = new OpenLayers.Layer.Vector("Vector Layer", { 
     42            strategies: [strategy], 
     43            isBaseLayer: true 
     44        }); 
     45        var map = new OpenLayers.Map('map', { 
     46            resolutions: [4, 2, 1], 
     47            maxExtent: new OpenLayers.Bounds(-40, -40, 40, 40) 
     48        }); 
     49        map.addLayer(layer); 
     50         
     51        // create features in a line, 1 unit apart 
     52        var features = new Array(80); 
     53        for(var i=0; i<80; ++i) { 
     54            features[i] = new OpenLayers.Feature.Vector( 
     55                new OpenLayers.Geometry.Point(-40 + i, 0) 
     56            ); 
     57        } 
     58         
     59        map.setCenter(new OpenLayers.LonLat(0, 0), 0); 
     60        layer.addFeatures(features); 
     61         
     62        // resolution 4 
     63        // threshold: 4 * 20 = 80 units 
     64        // one cluster 
     65        t.eq(layer.features.length, 1, "[4] layer has one cluster"); 
     66        t.ok(featuresEq(layer.features[0].cluster, features), "[4] cluster includes all features"); 
     67         
     68        // resolution 2 
     69        // threshold: 2 * 20 = 40 units 
     70        // two clusters (41 and 39) - first cluster includes all features within 40 units of the first (0-40 or 41 features) 
     71        map.zoomIn(); 
     72        t.eq(layer.features.length, 2, "[2] layer has two clusters"); 
     73        t.ok(featuresEq(layer.features[0].cluster, features.slice(0, 41)), "[2] first cluster includes first 41 features"); 
     74        t.ok(featuresEq(layer.features[1].cluster, features.slice(41, 80)), "[2] second cluster includes last 39 features"); 
     75         
     76        // resolution 1 
     77        // threshold: 1 * 20 = 20 units 
     78        // four clusters (21, 21, 21, and 17) 
     79        map.zoomIn(); 
     80        t.eq(layer.features.length, 4, "[1] layer has four clusters"); 
     81        t.ok(featuresEq(layer.features[0].cluster, features.slice(0, 21)), "[1] first cluster includes first 21 features"); 
     82        t.ok(featuresEq(layer.features[1].cluster, features.slice(21, 42)), "[2] second cluster includes second 21 features"); 
     83        t.ok(featuresEq(layer.features[2].cluster, features.slice(42, 63)), "[2] third cluster includes third 21 features"); 
     84        t.ok(featuresEq(layer.features[3].cluster, features.slice(63, 80)), "[2] fourth cluster includes last 17 features"); 
     85    } 
     86 
     87    function test_deactivate(t) { 
     88        t.plan(2); 
     89 
     90        var strategy = new OpenLayers.Strategy.Cluster(); 
     91        var layer = new OpenLayers.Layer.Vector("Vector Layer", { 
     92            strategies: [strategy] 
     93        }); 
     94        var map = new OpenLayers.Map('map'); 
     95        map.addLayer(layer); 
     96         
     97        t.eq(strategy.active, true, "active after adding to map"); 
     98         
     99        map.removeLayer(layer); 
     100        t.eq(strategy.active, false, "not active after removing from map"); 
     101    } 
     102 
     103  </script> 
     104</head> 
     105<body> 
     106    <div id="map" style="width: 400px; height: 200px" /> 
     107</body> 
     108</html> 
  • tests/Strategy/Paging.html

    old new  
     1<html> 
     2<head> 
     3  <script src="../../lib/OpenLayers.js"></script> 
     4  <script type="text/javascript"> 
     5 
     6    function test_activate(t) { 
     7        t.plan(2); 
     8 
     9        var strategy = new OpenLayers.Strategy.Paging(); 
     10        t.eq(strategy.active, false, "not active after construction"); 
     11 
     12        var layer = new OpenLayers.Layer.Vector("Vector Layer", { 
     13            strategies: [strategy] 
     14        }); 
     15        var map = new OpenLayers.Map('map'); 
     16        map.addLayer(layer); 
     17         
     18        t.eq(strategy.active, true, "active after adding to map"); 
     19    } 
     20     
     21    function test_paging(t) { 
     22        t.plan(18); 
     23 
     24        var strategy = new OpenLayers.Strategy.Paging(); 
     25        var layer = new OpenLayers.Layer.Vector("Vector Layer", { 
     26            strategies: [strategy], 
     27            drawFeature: function() {} 
     28        }); 
     29        var map = new OpenLayers.Map('map'); 
     30        map.addLayer(layer); 
     31         
     32        var features = new Array(25); 
     33        for(var i=0; i<features.length; ++i) { 
     34            features[i] = {destroy: function() {}}; 
     35        } 
     36         
     37        function featuresEq(got, exp) { 
     38            var eq = false; 
     39            if(got instanceof Array && exp instanceof Array) { 
     40                if(got.length === exp.length) { 
     41                    for(var i=0; i<got.length; ++i) { 
     42                        if(got[i] !== exp[i]) { 
     43                            console.log(got[i], exp[i]); 
     44                            break; 
     45                        } 
     46                    } 
     47                    eq = (i == got.length); 
     48                } 
     49            } 
     50            return eq; 
     51        } 
     52         
     53        var len = strategy.pageLength(); 
     54        t.eq(len, 10, "page length defaults to 10"); 
     55         
     56        // add 25 features to the layer 
     57        layer.addFeatures(features); 
     58        t.eq(strategy.features.length, features.length, "strategy caches all features"); 
     59        t.eq(layer.features.length, len, "layer gets one page of features"); 
     60        t.ok(featuresEq(layer.features, features.slice(0, len)), "layer gets first page initially"); 
     61        t.eq(strategy.pageNum(), 0, "strategy reports 0 based page number"); 
     62        t.eq(strategy.pageCount(), Math.ceil(features.length / len), "strategy reports correct number of pages"); 
     63         
     64        // load next page of features 
     65        var changed = strategy.pageNext(); 
     66        t.eq(changed, true, "(1) strategy reports change"); 
     67        t.eq(strategy.pageNum(), 1, "second page"); 
     68        t.ok(featuresEq(layer.features, features.slice(len, 2*len)), "layer has second page of features"); 
     69         
     70        // load next page of features (half page) 
     71        changed = strategy.pageNext(); 
     72        t.eq(changed, true, "(2) strategy reports change"); 
     73        t.eq(strategy.pageNum(), 2, "third page"); 
     74         
     75        // try to change forward again 
     76        changed = strategy.pageNext(); 
     77        t.eq(changed, false, "strategy reports no change"); 
     78        t.eq(layer.features.length, features.length % len, "layer has partial page"); 
     79        t.ok(featuresEq(layer.features, features.slice(2*len, 3*len)), "layer has third page of features"); 
     80        t.eq(strategy.pageNum(), 2, "still on third page"); 
     81         
     82        // change back a page 
     83        changed = strategy.pagePrevious(); 
     84        t.eq(changed, true, "(3) strategy reports change"); 
     85        t.eq(strategy.pageNum(), 1, "back on second page"); 
     86        t.ok(featuresEq(layer.features, features.slice(len, 2*len)), "layer has second page of features again"); 
     87         
     88        layer.destroy(); 
     89         
     90    } 
     91 
     92    function test_deactivate(t) { 
     93        t.plan(2); 
     94 
     95        var strategy = new OpenLayers.Strategy.Paging(); 
     96        var layer = new OpenLayers.Layer.Vector("Vector Layer", { 
     97            strategies: [strategy] 
     98        }); 
     99        var map = new OpenLayers.Map('map'); 
     100        map.addLayer(layer); 
     101         
     102        t.eq(strategy.active, true, "active after adding to map"); 
     103         
     104        map.removeLayer(layer); 
     105        t.eq(strategy.active, false, "not active after removing from map"); 
     106    } 
     107 
     108  </script> 
     109</head> 
     110<body> 
     111    <div id="map" style="width: 400px; height: 200px" /> 
     112</body> 
     113</html> 
  • tests/list-tests.html

    old new  
    124124    <li>Request/XMLHttpRequest.html</li> 
    125125    <li>Rule.html</li> 
    126126    <li>Strategy.html</li> 
     127    <li>Strategy/Cluster.html</li> 
    127128    <li>Strategy/Fixed.html</li> 
     129    <li>Strategy/Paging.html</li> 
    128130    <li>Strategy/BBOX.html</li> 
    129131    <li>Style.html</li> 
    130132    <li>StyleMap.html</li> 
  • lib/OpenLayers/Strategy/Paging.js

    old new  
     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.Paging 
     11 * Strategy for vector feature paging 
     12 * 
     13 * Inherits from: 
     14 *  - <OpenLayers.Strategy> 
     15 */ 
     16OpenLayers.Strategy.Paging = OpenLayers.Class(OpenLayers.Strategy, { 
     17     
     18    /** 
     19     * Property: layer 
     20     * {<OpenLayers.Layer.Vector>} 
     21     */ 
     22    layer: null, 
     23     
     24    /** 
     25     * Property: features 
     26     * {Array(<OpenLayers.Feature.Vector>)} Cached features. 
     27     */ 
     28    features: null, 
     29     
     30    /** 
     31     * Property: length 
     32     * {Integer} Number of features per page.  Default is 10. 
     33     */ 
     34    length: 10, 
     35     
     36    /** 
     37     * Property: num 
     38     * {Integer} The currently displayed page number. 
     39     */ 
     40    num: null, 
     41     
     42    /** 
     43     * Property: paging 
     44     * {Boolean} The strategy is currently changing pages. 
     45     */ 
     46    paging: false, 
     47 
     48    /** 
     49     * Constructor: OpenLayers.Strategy.Paging 
     50     * Create a new paging strategy. 
     51     * 
     52     * Parameters: 
     53     * options - {Object} Optional object whose properties will be set on the 
     54     *     instance. 
     55     */ 
     56    initialize: function(options) { 
     57        OpenLayers.Strategy.prototype.initialize.apply(this, [options]); 
     58    }, 
     59     
     60    /** 
     61     * Method: activate 
     62     * Activate the strategy.  Register any listeners, do appropriate setup. 
     63     *  
     64     * Returns: 
     65     * {Boolean} The strategy was successfully activated. 
     66     */ 
     67    activate: function() { 
     68        var activated = OpenLayers.Strategy.prototype.activate.call(this); 
     69        if(activated) { 
     70            this.layer.events.on({ 
     71                "beforefeaturesadded": this.cacheFeatures, 
     72                scope: this 
     73            }); 
     74        } 
     75        return activated; 
     76    }, 
     77     
     78    /** 
     79     * Method: deactivate 
     80     * Deactivate the strategy.  Unregister any listeners, do appropriate 
     81     *     tear-down. 
     82     *  
     83     * Returns: 
     84     * {Boolean} The strategy was successfully deactivated. 
     85     */ 
     86    deactivate: function() { 
     87        var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this); 
     88        if(deactivated) { 
     89            this.clearCache(); 
     90            this.layer.events.un({ 
     91                "beforefeaturesadded": this.cacheFeatures, 
     92                scope: this 
     93            }); 
     94        } 
     95        return deactivated; 
     96    }, 
     97     
     98    /** 
     99     * Method: cacheFeatures 
     100     * Cache features before they are added to the layer. 
     101     */ 
     102    cacheFeatures: function(event) { 
     103        if(!this.paging) { 
     104            this.clearCache(); 
     105            this.features = event.features; 
     106            this.pageNext(event); 
     107        } 
     108    }, 
     109     
     110    /** 
     111     * Method: clearCache 
     112     * Clear out the cached features.  This destroys features, assuming 
     113     *     nothing else has a reference. 
     114     */ 
     115    clearCache: function() { 
     116        if(this.features) { 
     117            for(var i=0; i<this.features.length; ++i) { 
     118                this.features[i].destroy(); 
     119            } 
     120        } 
     121        this.features = null; 
     122        this.num = null; 
     123    }, 
     124     
     125    /** 
     126     * Method: pageCount 
     127     * Get the total count of pages given the current cache of features. 
     128     * 
     129     * Returns: 
     130     * {Integer} The page count. 
     131     */ 
     132    pageCount: function() { 
     133        var numFeatures = this.features ? this.features.length : 0; 
     134        return Math.ceil(numFeatures / this.length); 
     135    }, 
     136 
     137    /** 
     138     * Method: pageNum 
     139     * Get the zero based page number. 
     140     * 
     141     * Returns: 
     142     * {Integer} The current page number being displayed. 
     143     */ 
     144    pageNum: function() { 
     145        return this.num; 
     146    }, 
     147 
     148    /** 
     149     * Method: pageLength 
     150     * Gets or sets page length. 
     151     * 
     152     * Parameters: 
     153     * newLength: {Integer} Optional length to be set. 
     154     * 
     155     * Returns: 
     156     * {Integer} The length of a page (number of features per page). 
     157     */ 
     158    pageLength: function(newLength) { 
     159        if(newLength && newLength > 0) { 
     160            this.length = newLength; 
     161        } 
     162        return this.length; 
     163    }, 
     164 
     165    /** 
     166     * Method: pageNext 
     167     * Display the next page of features. 
     168     * 
     169     * Returns: 
     170     * {Boolean} A new page was displayed. 
     171     */ 
     172    pageNext: function(event) { 
     173        var changed = false; 
     174        if(this.features) { 
     175            if(this.num === null) { 
     176                this.num = -1; 
     177            } 
     178            var start = (this.num + 1) * this.length; 
     179            changed = this.page(start, event); 
     180        } 
     181        return changed; 
     182    }, 
     183 
     184    /** 
     185     * Method: pagePrevious 
     186     * Display the previous page of features. 
     187     * 
     188     * Returns: 
     189     * {Boolean} A new page was displayed. 
     190     */ 
     191    pagePrevious: function() { 
     192        var changed = false; 
     193        if(this.features) { 
     194            if(this.num === null) { 
     195                this.num = this.pageCount(); 
     196            } 
     197            var start = (this.num - 1) * this.length; 
     198            changed = this.page(start); 
     199        } 
     200        return changed; 
     201    }, 
     202     
     203    /** 
     204     * Method: page 
     205     * Display the page starting at the given index from the cache. 
     206     * 
     207     * Returns: 
     208     * {Boolean} A new page was displayed. 
     209     */ 
     210    page: function(start, event) { 
     211        var changed = false; 
     212        if(this.features) { 
     213            if(start >= 0 && start < this.features.length) { 
     214                var num = Math.floor(start / this.length); 
     215                if(num != this.num) { 
     216                    this.paging = true; 
     217                    var features = this.features.slice(start, start + this.length); 
     218                    this.layer.removeFeatures(this.layer.features); 
     219                    this.num = num; 
     220                    // modify the event if any 
     221                    if(event && event.features) { 
     222                        // this.was called by an event listener 
     223                        event.features = features; 
     224                    } else { 
     225                        // this was called directly on the strategy 
     226                        this.layer.addFeatures(features); 
     227                    } 
     228                    this.paging = false; 
     229                    changed = true; 
     230                } 
     231            } 
     232        } 
     233        return changed; 
     234    }, 
     235     
     236    CLASS_NAME: "OpenLayers.Strategy.Paging"  
     237}); 
  • lib/OpenLayers/Strategy/Cluster.js

    old new  
     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 */ 
     16OpenLayers.Strategy.Cluster = OpenLayers.Class(OpenLayers.Strategy, { 
     17     
     18    /** 
     19     * Property: layer 
     20     * {<OpenLayers.Layer.Vector>} 
     21     */ 
     22    layer: null, 
     23     
     24    /** 
     25     * Property: distance 
     26     * {Integer} Pixel distance between features that should be considered a 
     27     *     single cluster.  Default is 20 pixels. 
     28     */ 
     29    distance: 20, 
     30     
     31    /** 
     32     * Property: features 
     33     * {Array(<OpenLayers.Feature.Vector>)} Cached features. 
     34     */ 
     35    features: null, 
     36     
     37    /** 
     38     * Property: clusters 
     39     * {Array(<OpenLayers.Feature.Vector>)} Calculated clusters. 
     40     */ 
     41    clusters: null, 
     42     
     43    /** 
     44     * Property: clustering 
     45     * {Boolean} The strategy is currently clustering features. 
     46     */ 
     47    clustering: false, 
     48     
     49    /** 
     50     * Property: resolution 
     51     * {Float} The resolution (map units per pixel) of the current cluster set. 
     52     */ 
     53    resolution: null, 
     54 
     55    /** 
     56     * Constructor: OpenLayers.Strategy.Cluster 
     57     * Create a new clustering strategy. 
     58     * 
     59     * Parameters: 
     60     * options - {Object} Optional object whose properties will be set on the 
     61     *     instance. 
     62     */ 
     63    initialize: function(options) { 
     64        OpenLayers.Strategy.prototype.initialize.apply(this, [options]); 
     65    }, 
     66     
     67    /** 
     68     * Method: activate 
     69     * Activate the strategy.  Register any listeners, do appropriate setup. 
     70     *  
     71     * Returns: 
     72     * {Boolean} The strategy was successfully activated. 
     73     */ 
     74    activate: function() { 
     75        var activated = OpenLayers.Strategy.prototype.activate.call(this); 
     76        if(activated) { 
     77            this.layer.events.on({ 
     78                "beforefeaturesadded": this.cacheFeatures, 
     79                scope: this 
     80            }); 
     81            this.layer.map.events.on({"zoomend": this.cluster, scope: this}); 
     82        } 
     83        return activated; 
     84    }, 
     85     
     86    /** 
     87     * Method: deactivate 
     88     * Deactivate the strategy.  Unregister any listeners, do appropriate 
     89     *     tear-down. 
     90     *  
     91     * Returns: 
     92     * {Boolean} The strategy was successfully deactivated. 
     93     */ 
     94    deactivate: function() { 
     95        var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this); 
     96        if(deactivated) { 
     97            this.clearCache(); 
     98            this.layer.events.un({ 
     99                "beforefeaturesadded": this.cacheFeatures, 
     100                scope: this 
     101            }); 
     102            this.layer.map.events.un({"zoomend": this.cluster, scope: this}); 
     103        } 
     104        return deactivated; 
     105    }, 
     106     
     107    /** 
     108     * Method: cacheFeatures 
     109     * Cache features before they are added to the layer. 
     110     * 
     111     * Returns: 
     112     * {Boolean} False to stop layer from being added to the layer. 
     113     */ 
     114    cacheFeatures: function(event) { 
     115        var propagate = true; 
     116        if(!this.clustering) { 
     117            this.clearCache(); 
     118            this.features = event.features; 
     119            this.cluster(); 
     120            propagate = false; 
     121        } 
     122        return propagate; 
     123    }, 
     124     
     125    /** 
     126     * Method: clearCache 
     127     * Clear out the cached features.  This destroys features, assuming 
     128     *     nothing else has a reference. 
     129     */ 
     130    clearCache: function() { 
     131        if(this.features) { 
     132            for(var i=0; i<this.features.length; ++i) { 
     133                this.features[i].destroy(); 
     134            } 
     135        } 
     136        this.features = null; 
     137    }, 
     138     
     139    /** 
     140     * Method: cluster 
     141     * Cluster features based on some threshold distance. 
     142     */ 
     143    cluster: function() { 
     144        if(this.features) { 
     145            var resolution = this.layer.getResolution(); 
     146            if(resolution != this.resolution || !this.clustersExist()) { 
     147                this.resolution = resolution; 
     148                var clusters = []; 
     149                var feature, clustered, cluster; 
     150                for(var i=0; i<this.features.length; ++i) { 
     151                    feature = this.features[i]; 
     152                    clustered = false; 
     153                    for(var j=0; j<clusters.length; ++j) { 
     154                        cluster = clusters[j]; 
     155                        if(this.shouldCluster(cluster, feature)) { 
     156                            this.addToCluster(cluster, feature); 
     157                            clustered = true; 
     158                            break; 
     159                        } 
     160                    } 
     161                    if(!clustered) { 
     162                        clusters.push(this.createCluster(this.features[i])); 
     163                    } 
     164                } 
     165                this.layer.destroyFeatures(); 
     166                if(clusters.length > 0) { 
     167                    this.clustering = true; 
     168                    // A legitimate feature addition could occur during this 
     169                    // addFeatures call.  For clustering to behave well, features 
     170                    // should be removed from a layer before requesting a new batch. 
     171                    this.layer.addFeatures(clusters); 
     172                    this.clustering = false; 
     173                } 
     174                this.clusters = clusters; 
     175            } 
     176        } 
     177    }, 
     178     
     179    /** 
     180     * Method: clustersExist 
     181     * Determine whether calculated clusters are already on the layer. 
     182     * 
     183     * Returns: 
     184     * {Boolean} The calculated clusters are already on the layer. 
     185     */ 
     186    clustersExist: function() { 
     187        var exist = false; 
     188        if(this.clusters && this.clusters.length > 0 && 
     189           this.clusters.length == this.layer.features.length) { 
     190            exist = true; 
     191            for(var i=0; i<this.clusters.length; ++i) { 
     192                if(this.clusters[i] != this.layer.features[i]) { 
     193                    exist = false; 
     194                    break; 
     195                } 
     196            } 
     197        } 
     198        return exist; 
     199    }, 
     200     
     201    /** 
     202     * Method: shouldCluster 
     203     * Determine whether to include a feature in a given cluster. 
     204     * 
     205     * Parameters: 
     206     * cluster - {<OpenLayers.Feature.Vector>} A cluster. 
     207     * feature - {<OpenLayers.Feature.Vector>} A feature. 
     208     * 
     209     * Returns: 
     210     * {Boolean} The feature should be included in the cluster. 
     211     */ 
     212    shouldCluster: function(cluster, feature) { 
     213        var cc = cluster.geometry.getBounds().getCenterLonLat(); 
     214        var fc = feature.geometry.getBounds().getCenterLonLat(); 
     215        var distance = ( 
     216            Math.sqrt( 
     217                Math.pow((cc.lon - fc.lon), 2) + Math.pow((cc.lat - fc.lat), 2) 
     218            ) / this.resolution 
     219        ); 
     220        return (distance <= this.distance); 
     221    }, 
     222     
     223    /** 
     224     * Method: addToCluster 
     225     * Add a feature to a cluster. 
     226     * 
     227     * Parameters: 
     228     * cluster - {<OpenLayers.Feature.Vector>} A cluster. 
     229     * feature - {<OpenLayers.Feature.Vector>} A feature. 
     230     */ 
     231    addToCluster: function(cluster, feature) { 
     232        cluster.cluster.push(feature); 
     233        cluster.attributes.count += 1; 
     234    }, 
     235     
     236    /** 
     237     * Method: createCluster 
     238     * Given a feature, create a cluster. 
     239     * 
     240     * Parameters: 
     241     * feature - {<OpenLayers.Feature.Vector>} 
     242     * 
     243     * Returns: 
     244     * {<OpenLayers.Feature.Vector>} A cluster. 
     245     */ 
     246    createCluster: function(feature) { 
     247        var center = feature.geometry.getBounds().getCenterLonLat(); 
     248        var cluster = new OpenLayers.Feature.Vector( 
     249            new OpenLayers.Geometry.Point(center.lon, center.lat), 
     250            {count: 1} 
     251        ); 
     252        cluster.cluster = [feature]; 
     253        return cluster; 
     254    }, 
     255 
     256    CLASS_NAME: "OpenLayers.Strategy.Cluster"  
     257}); 
  • lib/OpenLayers/Layer/Vector.js

    old new  
    3838     * Supported map event types (in addition to those from <OpenLayers.Layer>): 
    3939     *  - *beforefeatureadded* Triggered before a feature is added.  Listeners 
    4040     *      will receive an object with a *feature* property referencing the 
    41      *      feature to be added. 
     41     *      feature to be added.  To stop the feature from being added, a 
     42     *      listener should return false. 
     43     *  - *beforefeaturesadded* Triggered before an array of features is added. 
     44     *      Listeners will receive an object with a *features* property 
     45     *      referencing the feature to be added. To stop the features from 
     46     *      being added, a listener should return false. 
    4247     *  - *featureadded* Triggered after a feature is added.  The event 
    4348     *      object passed to listeners will have a *feature* property with a 
    4449     *      reference to the added feature. 
     
    7277     *  - *refresh* Triggered when something wants a strategy to ask the protocol 
    7378     *      for a new set of features. 
    7479     */ 
    75     EVENT_TYPES: ["beforefeatureadded", "featureadded", "featuresadded", 
     80    EVENT_TYPES: ["beforefeatureadded", "beforefeaturesadded", 
     81                  "featureadded", "featuresadded", 
    7682                  "beforefeatureremoved", "featureremoved", "featuresremoved", 
    7783                  "beforefeatureselected", "featureselected", "featureunselected",  
    7884                  "beforefeaturemodified", "featuremodified", "afterfeaturemodified", 
     
    443449        } 
    444450         
    445451        var notify = !options || !options.silent; 
     452        if(notify) { 
     453            var event = {features: features}; 
     454            var ret = this.events.triggerEvent("beforefeaturesadded", event); 
     455            if(ret === false) { 
     456                return; 
     457            } 
     458            features = event.features; 
     459        } 
     460         
    446461 
    447462        for (var i=0, len=features.length; i<len; i++) { 
    448463            if (i != (features.length - 1)) { 
     
    469484            } 
    470485 
    471486            if (notify) { 
    472                 this.events.triggerEvent("beforefeatureadded", { 
    473                     feature: feature 
    474                 }); 
     487                if(this.events.triggerEvent("beforefeatureadded", 
     488                                            {feature: feature}) === false) { 
     489                    continue; 
     490                }; 
    475491                this.preFeatureInsert(feature); 
    476492            } 
    477493 
  • lib/OpenLayers.js

    old new  
    185185            "OpenLayers/Layer/Vector.js", 
    186186            "OpenLayers/Strategy.js", 
    187187            "OpenLayers/Strategy/Fixed.js", 
     188            "OpenLayers/Strategy/Cluster.js", 
     189            "OpenLayers/Strategy/Paging.js", 
    188190            "OpenLayers/Strategy/BBOX.js", 
    189191            "OpenLayers/Protocol.js", 
    190192            "OpenLayers/Protocol/HTTP.js", 
  • examples/animator.js

    old new  
     1/*   
     2    Animator.js 1.1.9 
     3     
     4    This library is released under the BSD license: 
     5 
     6    Copyright (c) 2006, Bernard Sumption. All rights reserved. 
     7     
     8    Redistribution and use in source and binary forms, with or without 
     9    modification, are permitted provided that the following conditions are met: 
     10     
     11    Redistributions of source code must retain the above copyright notice, this 
     12    list of conditions and the following disclaimer. Redistributions in binary 
     13    form must reproduce the above copyright notice, this list of conditions and 
     14    the following disclaimer in the documentation and/or other materials 
     15    provided with the distribution. Neither the name BernieCode nor 
     16    the names of its contributors may be used to endorse or promote products 
     17    derived from this software without specific prior written permission.  
     18     
     19    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
     20    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
     21    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
     22    ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR 
     23    ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
     24    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
     25    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
     26    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
     27    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 
     28    OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
     29    DAMAGE. 
     30 
     31*/ 
     32 
     33 
     34// Applies a sequence of numbers between 0 and 1 to a number of subjects 
     35// construct - see setOptions for parameters 
     36function Animator(options) { 
     37    this.setOptions(options); 
     38    var _this = this; 
     39    this.timerDelegate = function(){_this.onTimerEvent()}; 
     40    this.subjects = []; 
     41    this.target = 0; 
     42    this.state = 0; 
     43    this.lastTime = null; 
     44}; 
     45Animator.prototype = { 
     46    // apply defaults 
     47    setOptions: function(options) { 
     48        this.options = Animator.applyDefaults({ 
     49            interval: 20,  // time between animation frames 
     50            duration: 400, // length of animation 
     51            onComplete: function(){}, 
     52            onStep: function(){}, 
     53            transition: Animator.tx.easeInOut 
     54        }, options); 
     55    }, 
     56    // animate from the current state to provided value 
     57    seekTo: function(to) { 
     58        this.seekFromTo(this.state, to); 
     59    }, 
     60    // animate from the current state to provided value 
     61    seekFromTo: function(from, to) { 
     62        this.target = Math.max(0, Math.min(1, to)); 
     63        this.state = Math.max(0, Math.min(1, from)); 
     64        this.lastTime = new Date().getTime(); 
     65        if (!this.intervalId) { 
     66            this.intervalId = window.setInterval(this.timerDelegate, this.options.interval); 
     67        } 
     68    }, 
     69    // animate from the current state to provided value 
     70    jumpTo: function(to) { 
     71        this.target = this.state = Math.max(0, Math.min(1, to)); 
     72        this.propagate(); 
     73    }, 
     74    // seek to the opposite of the current target 
     75    toggle: function() { 
     76        this.seekTo(1 - this.target); 
     77    }, 
     78    // add a function or an object with a method setState(state) that will be called with a number 
     79    // between 0 and 1 on each frame of the animation 
     80    addSubject: function(subject) { 
     81        this.subjects[this.subjects.length] = subject; 
     82        return this; 
     83    }, 
     84    // remove all subjects 
     85    clearSubjects: function() { 
     86        this.subjects = []; 
     87    }, 
     88    // forward the current state to the animation subjects 
     89    propagate: function() { 
     90        var value = this.options.transition(this.state); 
     91        for (var i=0; i<this.subjects.length; i++) { 
     92            if (this.subjects[i].setState) { 
     93                this.subjects[i].setState(value); 
     94            } else { 
     95                this.subjects[i](value); 
     96            } 
     97        } 
     98    }, 
     99    // called once per frame to update the current state 
     100    onTimerEvent: function() { 
     101        var now = new Date().getTime(); 
     102        var timePassed = now - this.lastTime; 
     103        this.lastTime = now; 
     104        var movement = (timePassed / this.options.duration) * (this.state < this.target ? 1 : -1); 
     105        if (Math.abs(movement) >= Math.abs(this.state - this.target)) { 
     106            this.state = this.target; 
     107        } else { 
     108            this.state += movement; 
     109        } 
     110         
     111        try { 
     112            this.propagate(); 
     113        } finally { 
     114            this.options.onStep.call(this); 
     115            if (this.target == this.state) { 
     116                window.clearInterval(this.intervalId); 
     117                this.intervalId = null; 
     118                this.options.onComplete.call(this); 
     119            } 
     120        } 
     121    }, 
     122    // shortcuts 
     123    play: function() {this.seekFromTo(0, 1)}, 
     124    reverse: function() {this.seekFromTo(1, 0)}, 
     125    // return a string describing this Animator, for debugging 
     126    inspect: function() { 
     127        var str = "#<Animator:\n"; 
     128        for (var i=0; i<this.subjects.length; i++) { 
     129            str += this.subjects[i].inspect(); 
     130        } 
     131        str += ">"; 
     132        return str; 
     133    } 
     134} 
     135// merge the properties of two objects 
     136Animator.applyDefaults = function(defaults, prefs) { 
     137    prefs = prefs || {}; 
     138    var prop, result = {}; 
     139    for (prop in defaults) result[prop] = prefs[prop] !== undefined ? prefs[prop] : defaults[prop]; 
     140    return result; 
     141} 
     142// make an array from any object 
     143Animator.makeArray = function(o) { 
     144    if (o == null) return []; 
     145    if (!o.length) return [o]; 
     146    var result = []; 
     147    for (var i=0; i<o.length; i++) result[i] = o[i]; 
     148    return result; 
     149} 
     150// convert a dash-delimited-property to a camelCaseProperty (c/o Prototype, thanks Sam!) 
     151Animator.camelize = function(string) { 
     152    var oStringList = string.split('-'); 
     153    if (oStringList.length == 1) return oStringList[0]; 
     154     
     155    var camelizedString = string.indexOf('-') == 0 
     156        ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) 
     157        : oStringList[0]; 
     158     
     159    for (var i = 1, len = oStringList.length; i < len; i++) { 
     160        var s = oStringList[i]; 
     161        camelizedString += s.charAt(0).toUpperCase() + s.substring(1); 
     162    } 
     163    return camelizedString; 
     164} 
     165// syntactic sugar for creating CSSStyleSubjects 
     166Animator.apply = function(el, style, options) { 
     167    if (style instanceof Array) { 
     168        return new Animator(options).addSubject(new CSSStyleSubject(el, style[0], style[1])); 
     169    } 
     170    return new Animator(options).addSubject(new CSSStyleSubject(el, style)); 
     171} 
     172// make a transition function that gradually accelerates. pass a=1 for smooth 
     173// gravitational acceleration, higher values for an exaggerated effect 
     174Animator.makeEaseIn = function(a) { 
     175    return function(state) { 
     176        return Math.pow(state, a*2);  
     177    } 
     178} 
     179// as makeEaseIn but for deceleration 
     180Animator.makeEaseOut = function(a) { 
     181    return function(state) { 
     182        return 1 - Math.pow(1 - state, a*2);  
     183    } 
     184} 
     185// make a transition function that, like an object with momentum being attracted to a point, 
     186// goes past the target then returns 
     187Animator.makeElastic = function(bounces) { 
     188    return function(state) { 
     189        state = Animator.tx.easeInOut(state); 
     190        return ((1-Math.cos(state * Math.PI * bounces)) * (1 - state)) + state;  
     191    } 
     192} 
     193// make an Attack Decay Sustain Release envelope that starts and finishes on the same level 
     194//  
     195Animator.makeADSR = function(attackEnd, decayEnd, sustainEnd, sustainLevel) { 
     196    if (sustainLevel == null) sustainLevel = 0.5; 
     197    return function(state) { 
     198        if (state < attackEnd) { 
     199            return state / attackEnd; 
     200        } 
     201        if (state < decayEnd) { 
     202            return 1 - ((state - attackEnd) / (decayEnd - attackEnd) * (1 - sustainLevel)); 
     203        } 
     204        if (state < sustainEnd) { 
     205            return sustainLevel; 
     206        } 
     207        return sustainLevel * (1 - ((state - sustainEnd) / (1 - sustainEnd))); 
     208    } 
     209} 
     210// make a transition function that, like a ball falling to floor, reaches the target and/ 
     211// bounces back again 
     212Animator.makeBounce = function(bounces) { 
     213    var fn = Animator.makeElastic(bounces); 
     214    return function(state) { 
     215        state = fn(state);  
     216        return state <= 1 ? state : 2-state; 
     217    } 
     218} 
     219  
     220// pre-made transition functions to use with the 'transition' option 
     221Animator.tx = { 
     222    easeInOut: function(pos){ 
     223        return ((-Math.cos(pos*Math.PI)/2) + 0.5);<