OpenLayers OpenLayers

Ticket #1606: strategy.patch

File strategy.patch, 62.4 kB (added by tschaub, 1 year 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); 
     224    }, 
     225    linear: function(x) { 
     226        return x; 
     227    }, 
     228    easeIn: Animator.makeEaseIn(1.5), 
     229    easeOut: Animator.makeEaseOut(1.5), 
     230    strongEaseIn: Animator.makeEaseIn(2.5), 
     231    strongEaseOut: Animator.makeEaseOut(2.5), 
     232    elastic: Animator.makeElastic(1), 
     233    veryElastic: Animator.makeElastic(3), 
     234    bouncy: Animator.makeBounce(1), 
     235    veryBouncy: Animator.makeBounce(3) 
     236} 
     237 
     238// animates a pixel-based style property between two integer values 
     239function NumericalStyleSubject(els, property, from, to, units) { 
     240    this.els = Animator.makeArray(els); 
     241    if (property == 'opacity' && window.ActiveXObject) { 
     242        this.property = 'filter'; 
     243    } else { 
     244        this.property = Animator.camelize(property); 
     245    } 
     246    this.from = parseFloat(from); 
     247    this.to = parseFloat(to); 
     248    this.units = units != null ? units : 'px'; 
     249} 
     250NumericalStyleSubject.prototype = { 
     251    setState: function(state) { 
     252        var style = this.getStyle(state); 
     253        var visibility = (this.property == 'opacity' && state == 0) ? 'hidden' : ''; 
     254        var j=0; 
     255        for (var i=0; i<this.els.length; i++) { 
     256            try { 
     257                this.els[i].style[this.property] = style; 
     258            } catch (e) { 
     259                // ignore fontWeight - intermediate numerical values cause exeptions in firefox 
     260                if (this.property != 'fontWeight') throw e; 
     261            } 
     262            if (j++ > 20) return; 
     263        } 
     264    }, 
     265    getStyle: function(state) { 
     266        state = this.from + ((this.to - this.from) * state); 
     267        if (this.property == 'filter') return "alpha(opacity=" + Math.round(state*100) + ")"; 
     268        if (this.property == 'opacity') return state; 
     269        return Math.round(state) + this.units; 
     270    }, 
     271    inspect: function() { 
     272        return "\t" + this.property + "(" + this.from + this.units + " to " + this.to + this.units + ")\n"; 
     273    } 
     274} 
     275 
     276// animates a colour based style property between two hex values 
     277function ColorStyleSubject(els, property, from, to) { 
     278    this.els = Animator.makeArray(els); 
     279    this.property = Animator.camelize(property); 
     280    this.to = this.expandColor(to); 
     281    this.from = this.expandColor(from); 
     282    this.origFrom = from; 
     283    this.origTo = to; 
     284} 
     285 
     286ColorStyleSubject.prototype = { 
     287    // parse "#FFFF00" to [256, 256, 0] 
     288    expandColor: function(color) { 
     289        var hexColor, red, green, blue; 
     290        hexColor = ColorStyleSubject.parseColor(color); 
     291        if (hexColor) { 
     292            red = parseInt(hexColor.slice(1, 3), 16); 
     293            green = parseInt(hexColor.slice(3, 5), 16); 
     294            blue = parseInt(hexColor.slice(5, 7), 16); 
     295            return [red,green,blue] 
     296        } 
     297        if (window.DEBUG) { 
     298            alert("Invalid colour: '" + color + "'"); 
     299        } 
     300    }, 
     301    getValueForState: function(color, state) { 
     302        return Math.round(this.from[color] + ((this.to[color] - this.from[color]) * state)); 
     303    }, 
     304    setState: function(state) { 
     305        var color = '#' 
     306                + ColorStyleSubject.toColorPart(this.getValueForState(0, state)) 
     307                + ColorStyleSubject.toColorPart(this.getValueForState(1, state)) 
     308                + ColorStyleSubject.toColorPart(this.getValueForState(2, state)); 
     309        for (var i=0; i<this.els.length; i++) { 
     310            this.els[i].style[this.property] = color; 
     311        } 
     312    }, 
     313    inspect: function() { 
     314        return "\t" + this.property + "(" + this.origFrom + " to " + this.origTo + ")\n"; 
     315    } 
     316} 
     317 
     318// return a properly formatted 6-digit hex colour spec, or false 
     319ColorStyleSubject.parseColor = function(string) { 
     320    var color = '#', match; 
     321    if(match = ColorStyleSubject.parseColor.rgbRe.exec(string)) { 
     322        var part; 
     323        for (var i=1; i<=3; i++) { 
     324            part = Math.max(0, Math.min(255, parseInt(match[i]))); 
     325            color += ColorStyleSubject.toColorPart(part); 
     326        } 
     327        return color; 
     328    } 
     329    if (match = ColorStyleSubject.parseColor.hexRe.exec(string)) { 
     330        if(match[1].length == 3) { 
     331            for (var i=0; i<3; i++) { 
     332                color += match[1].charAt(i) + match[1].charAt(i); 
     333            } 
     334            return color; 
     335        } 
     336        return '#' + match[1]; 
     337    } 
     338    return false; 
     339} 
     340// convert a number to a 2 digit hex string 
     341ColorStyleSubject.toColorPart = function(number) { 
     342    if (number > 255) number = 255; 
     343    var digits = number.toString(16); 
     344    if (number < 16) return '0' + digits; 
     345    return digits; 
     346} 
     347ColorStyleSubject.parseColor.rgbRe = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i; 
     348ColorStyleSubject.parseColor.hexRe = /^\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/; 
     349 
     350// Animates discrete styles, i.e. ones that do not scale but have discrete values 
     351// that can't be interpolated 
     352function DiscreteStyleSubject(els, property, from, to, threshold) { 
     353    this.els = Animator.makeArray(els); 
     354    this.property = Animator.camelize(property); 
     355    this.from = from; 
     356    this.to = to; 
     357    this.threshold = threshold || 0.5; 
     358} 
     359 
     360DiscreteStyleSubject.prototype = { 
     361    setState: function(state) { 
     362        var j=0; 
     363        for (var i=0; i<this.els.length; i++) { 
     364            this.els[i].style[this.property] = state <= this.threshold ? this.from : this.to;  
     365        } 
     366    }, 
     367    inspect: function() { 
     368        return "\t" + this.property + "(" + this.from + " to " + this.to + " @ " + this.threshold + ")\n"; 
     369    } 
     370} 
     371 
     372// animates between two styles defined using CSS. 
     373// if style1 and style2 are present, animate between them, if only style1 
     374// is present, animate between the element's current style and style1 
     375function CSSStyleSubject(els, style1, style2) { 
     376    els = Animator.makeArray(els); 
     377    this.subjects = []; 
     378    if (els.length == 0) return; 
     379    var prop, toStyle, fromStyle; 
     380    if (style2) { 
     381        fromStyle = this.parseStyle(style1, els[0]); 
     382        toStyle = this.parseStyle(style2, els[0]); 
     383    } else { 
     384        toStyle = this.parseStyle(style1, els[0]); 
     385        fromStyle = {}; 
     386        for (prop in toStyle) { 
     387            fromStyle[prop] = CSSStyleSubject.getStyle(els[0], prop); 
     388        } 
     389    } 
     390    // remove unchanging properties 
     391    var prop; 
     392    for (prop in fromStyle) { 
     393        if (fromStyle[prop] == toStyle[prop]) { 
     394            delete fromStyle[prop]; 
     395            delete toStyle[prop]; 
     396        } 
     397    } 
     398    // discover the type (numerical or colour) of each style 
     399    var prop, units, match, type, from, to; 
     400    for (prop in fromStyle) { 
     401        var fromProp = String(fromStyle[prop]); 
     402        var toProp = String(toStyle[prop]); 
     403        if (toStyle[prop] == null) { 
     404            if (window.DEBUG) alert("No to style provided for '" + prop + '"'); 
     405            continue; 
     406        } 
     407         
     408        if (from = ColorStyleSubject.parseColor(fromProp)) { 
     409            to = ColorStyleSubject.parseColor(toProp); 
     410            type = ColorStyleSubject; 
     411        } else if (fromProp.match(CSSStyleSubject.numericalRe) 
     412                && toProp.match(CSSStyleSubject.numericalRe)) { 
     413            from = parseFloat(fromProp); 
     414            to = parseFloat(toProp); 
     415            type = NumericalStyleSubject; 
     416            match = CSSStyleSubject.numericalRe.exec(fromProp); 
     417            var reResult = CSSStyleSubject.numericalRe.exec(toProp); 
     418            if (match[1] != null) { 
     419                units = match[1]; 
     420            } else if (reResult[1] != null) { 
     421                units = reResult[1]; 
     422            } else { 
     423                units = reResult; 
     424            } 
     425        } else if (fromProp.match(CSSStyleSubject.discreteRe) 
     426                && toProp.match(CSSStyleSubject.discreteRe)) { 
     427            from = fromProp; 
     428            to = toProp; 
     429            type = DiscreteStyleSubject; 
     430            units = 0;   // hack - how to get an animator option down to here 
     431        } else { 
     432            if (window.DEBUG) { 
     433                alert("Unrecognised format for value of " 
     434                    + prop + ": '" + fromStyle[prop] + "'"); 
     435            } 
     436            continue; 
     437        } 
     438        this.subjects[this.subjects.length] = new type(els, prop, from, to, units); 
     439    } 
     440} 
     441 
     442CSSStyleSubject.prototype = { 
     443    // parses "width: 400px; color: #FFBB2E" to {width: "400px", color: "#FFBB2E"} 
     444    parseStyle: function(style, el) { 
     445        var rtn = {}; 
     446        // if style is a rule set 
     447        if (style.indexOf(":") != -1) { 
     448            var styles = style.split(";"); 
     449            for (var i=0; i<styles.length; i++) { 
     450                var parts = CSSStyleSubject.ruleRe.exec(styles[i]); 
     451                if (parts) { 
     452                    rtn[parts[1]] = parts[2]; 
     453                } 
     454            } 
     455        } 
     456        // else assume style is a class name 
     457        else { 
     458            var prop, value, oldClass; 
     459            oldClass = el.className; 
     460            el.className = style; 
     461            for (var i=0; i<CSSStyleSubject.cssProperties.length; i++) { 
     462                prop = CSSStyleSubject.cssProperties[i]; 
     463                value = CSSStyleSubject.getStyle(el, prop); 
     464                if (value != null) { 
     465                    rtn[prop] = value; 
     466                } 
     467            } 
     468            el.className = oldClass; 
     469        } 
     470        return rtn; 
     471         
     472    }, 
     473    setState: function(state) { 
     474        for (var i=0; i<this.subjects.length; i++) { 
     475            this.subjects[i].setState(state); 
     476        } 
     477    }, 
     478    inspect: function() { 
     479        var str = ""; 
     480        for (var i=0; i<this.subjects.length; i++) { 
     481            str += this.subjects[i].inspect(); 
     482        } 
     483        return str; 
     484    } 
     485} 
     486// get the current value of a css property,  
     487CSSStyleSubject.getStyle = function(el, property){ 
     488    var style; 
     489    if(document.defaultView && document.defaultView.getComputedStyle){ 
     490        style = document.defaultView.getComputedStyle(el, "").getPropertyValue(property); 
     491        if (style) { 
     492            return style; 
     493        } 
     494    } 
     495    property = Animator.camelize(property); 
     496    if(el.currentStyle){ 
     497        style = el.currentStyle[property]; 
     498    } 
     499    return style || el.style[property] 
     500} 
     501 
     502 
     503CSSStyleSubject.ruleRe = /^\s*([a-zA-Z\-]+)\s*:\s*(\S(.+\S)?)\s*$/; 
     504CSSStyleSubject.numericalRe = /^-?\d+(?:\.\d+)?(%|[a-zA-Z]{2})?$/; 
     505CSSStyleSubject.discreteRe = /^\w+$/; 
     506 
     507// required because the style object of elements isn't enumerable in Safari 
     508/* 
     509CSSStyleSubject.cssProperties = ['background-color','border','border-color','border-spacing', 
     510'border-style','border-top','border-right','border-bottom','border-left','border-top-color', 
     511'border-right-color','border-bottom-color','border-left-color','border-top-width','border-right-width', 
     512'border-bottom-width','border-left-width','border-width','bottom','color','font-size','font-size-adjust', 
     513'font-stretch','font-style','height','left','letter-spacing','line-height','margin','margin-top', 
     514'margin-right','margin-bottom','margin-left','marker-offset','max-height','max-width','min-height', 
     515'min-width','orphans','outline','outline-color','outline-style','outline-width','overflow','padding', 
     516'padding-top','padding-right','padding-bottom','padding-left','quotes','right','size','text-indent', 
     517'top','width','word-spacing','z-index','opacity','outline-offset'];*/ 
     518 
     519 
     520CSSStyleSubject.cssProperties = ['azimuth','background','background-attachment','background-color','background-image','background-position','background-repeat','border-collapse','border-color','border-spacing','border-style','border-top','border-top-color','border-right-color','border-bottom-color','border-left-color','border-top-style','border-right-style','border-bottom-style','border-left-style','border-top-width','border-right-width','border-bottom-width','border-left-width','border-width','bottom','clear','clip','color','content','cursor','direction','display','elevation','empty-cells','css-float','font','font-family','font-size','font-size-adjust','font-stretch','font-style','font-variant','font-weight','height','left','letter-spacing','line-height','list-style','list-style-image','list-style-position','list-style-type','margin','margin-top','margin-right','margin-bottom','margin-left','max-height','max-width','min-height','min-width','orphans','outline','outline-color','outline-style','outline-width','overflow','padding','padding-top','padding-right','padding-bottom','padding-left','pause','position','right','size','table-layout','text-align','text-decoration','text-indent','text-shadow','text-transform','top','vertical-align','visibility','white-space','width','word-spacing','z-index','opacity','outline-offset','overflow-x','overflow-y']; 
     521 
     522 
     523// chains several Animator objects together 
     524function AnimatorChain(animators, options) { 
     525    this.animators = animators; 
     526    this.setOptions(options); 
     527    for (var i=0; i<this.animators.length; i++) { 
     528        this.listenTo(this.animators[i]); 
     529    } 
     530    this.forwards = false; 
     531    this.current = 0; 
     532} 
     533 
     534AnimatorChain.prototype = { 
     535    // apply defaults 
     536    setOptions: function(options) { 
     537        this.options = Animator.applyDefaults({ 
     538            // by default, each call to AnimatorChain.play() calls jumpTo(0) of each animator 
     539            // before playing, which can cause flickering if you have multiple animators all 
     540            // targeting the same element. Set this to false to avoid this. 
     541            resetOnPlay: true 
     542        }, options); 
     543    }, 
     544    // play each animator in turn 
     545    play: function() { 
     546        this.forwards = true; 
     547        this.current = -1; 
     548        if (this.options.resetOnPlay) { 
     549            for (var i=0; i<this.animators.length; i++) { 
     550                this.animators[i].jumpTo(0); 
     551            } 
     552        } 
     553        this.advance(); 
     554    }, 
     555    // play all animators backwards 
     556    reverse: function() { 
     557        this.forwards = false; 
     558        this.current = this.animators.length; 
     559        if (this.options.resetOnPlay) { 
     560            for (var i=0; i<this.animators.length; i++) { 
     561                this.animators[i].jumpTo(1); 
     562            } 
     563        } 
     564        this.advance(); 
     565    }, 
     566    // if we have just play()'d, then call reverse(), and vice versa 
     567    toggle: function() { 
     568        if (this.forwards) { 
     569            this.seekTo(0); 
     570        } else { 
     571            this.seekTo(1); 
     572        } 
     573    }, 
     574    // internal: install an event listener on an animator's onComplete option 
     575    // to trigger the next animator 
     576    listenTo: function(animator) { 
     577        var oldOnComplete = animator.options.onComplete; 
     578        var _this = this; 
     579        animator.options.onComplete = function() { 
     580            if (oldOnComplete) oldOnComplete.call(animator); 
     581            _this.advance(); 
     582        } 
     583    }, 
     584    // play the next animator 
     585    advance: function() { 
     586        if (this.forwards) { 
     587            if (this.animators[this.current + 1] == null) return; 
     588            this.current++; 
     589            this.animators[this.current].play(); 
     590        } else { 
     591            if (this.animators[this.current - 1] == null) return; 
     592            this.current--; 
     593            this.animators[this.current].reverse(); 
     594        } 
     595    }, 
     596    // this function is provided for drop-in compatibility with Animator objects, 
     597    // but only accepts 0 and 1 as target values 
     598    seekTo: function(target) { 
     599        if (target <= 0) { 
     600            this.forwards = false; 
     601            this.animators[this.current].seekTo(0); 
     602        } else { 
     603            this.forwards = true; 
     604            this.animators[this.current].seekTo(1); 
     605        } 
     606    } 
     607} 
     608 
     609// an Accordion is a class that creates and controls a number of Animators. An array of elements is passed in, 
     610// and for each element an Animator and a activator button is created. When an Animator's activator button is 
     611// clicked, the Animator and all before it seek to 0, and all Animators after it seek to 1. This can be used to 
     612// create the classic Accordion effect, hence the name. 
     613// see setOptions for arguments 
     614function Accordion(options) { 
     615    this.setOptions(options); 
     616    var selected = this.options.initialSection, current; 
     617    if (this.options.rememberance) { 
     618        current = document.location.hash.substring(1); 
     619    } 
     620    this.rememberanceTexts = []; 
     621    this.ans = []; 
     622    var _this = this; 
     623    for (var i=0; i<this.options.sections.length; i++) { 
     624        var el = this.options.sections[i]; 
     625        var an = new Animator(this.options.animatorOptions); 
     626        var from = this.options.from + (this.options.shift * i); 
     627        var to = this.options.to + (this.options.shift * i); 
     628        an.addSubject(new NumericalStyleSubject(el, this.options.property, from, to, this.options.units)); 
     629        an.jumpTo(0); 
     630        var activator = this.options.getActivator(el); 
     631        activator.index = i; 
     632        activator.onclick = function(){_this.show(this.index)}; 
     633        this.ans[this.ans.length] = an; 
     634        this.rememberanceTexts[i] = activator.innerHTML.replace(/\s/g, ""); 
     635        if (this.rememberanceTexts[i] === current) { 
     636            selected = i; 
     637        } 
     638    } 
     639    this.show(selected); 
     640} 
     641 
     642Accordion.prototype = { 
     643    // apply defaults 
     644    setOptions: function(options) { 
     645        this.options = Object.extend({ 
     646            // REQUIRED: an array of elements to use as the accordion sections 
     647            sections: null, 
     648            // a function that locates an activator button element given a section element. 
     649            // by default it takes a button id from the section's "activator" attibute 
     650            getActivator: function(el) {return document.getElementById(el.getAttribute("activator"))}, 
     651            // shifts each animator's range, for example with options {from:0,to:100,shift:20} 
     652            // the animators' ranges will be 0-100, 20-120, 40-140 etc. 
     653            shift: 0, 
     654            // the first page to show 
     655            initialSection: 0, 
     656            // if set to true, document.location.hash will be used to preserve the open section across page reloads  
     657            rememberance: true, 
     658            // constructor arguments to the Animator objects 
     659            animatorOptions: {} 
     660        }, options || {}); 
     661    }, 
     662    show: function(section) { 
     663        for (var i=0; i<this.ans.length; i++) { 
     664            this.ans[i].seekTo(i > section ? 1 : 0); 
     665        } 
     666        if (this.options.rememberance) { 
     667            document.location.hash = this.rememberanceTexts[section]; 
     668        } 
     669    } 
     670} 
  • examples/strategy-cluster.html

    old new  
     1<html xmlns="http://www.w3.org/1999/xhtml"> 
     2    <head> 
     3        <title>OpenLayers Cluster Strategy Example</title> 
     4        <link rel="stylesheet" href="../theme/default/style.css" type="text/css" /> 
     5        <link rel="stylesheet" href="style.css" type="text/css" /> 
     6        <style type="text/css"> 
     7            #photos { 
     8                height: 100px; 
     9                width: 512px; 
     10                position: relative; 
     11                white-space: nowrap; 
     12            } 
     13            .shift { 
     14                height: 25px; 
     15                line-height: 25px; 
     16                background-color: #fefefe; 
     17                text-align: center; 
     18                position: absolute; 
     19                bottom: 10px; 
     20                font-size: 8px; 
     21                font-weight: bold; 
     22                color: #696969; 
     23                width: 25px; 
     24            } 
     25            #scroll-start { 
     26                left: 0px; 
     27            } 
     28            #scroll-end { 
     29                right: 0px; 
     30            } 
     31            #scroll { 
     32                left: 30px; 
     33                width: 452px; 
     34                height: 100px; 
     35                overflow: hidden; 
     36                position: absolute; 
     37                bottom: 0px; 
     38            } 
     39            #photos ul { 
     40                position: absolute; 
     41                bottom: 0px; 
     42                padding: 0; 
     43                margin: 0; 
     44            } 
     45            #photos ul.start { 
     46                left: 0px; 
     47            } 
     48            #photos ul.end { 
     49                right: 80px; 
     50            } 
     51            #photos ul li { 
     52                padding 10px; 
     53                margin: 0; 
     54                list-style: none; 
     55                display: inline; 
     56            } 
     57            img.thumb { 
     58                height: 30px; 
     59            } 
     60            img.big { 
     61                height: 90px; 
     62            } 
     63        </style> 
     64        <script src="../lib/OpenLayers.js"></script> 
     65        <script src="Jugl.js"></script> 
     66        <script src="animator.js"></script> 
     67        <script type="text/javascript"> 
     68            var map, template; 
     69            var Jugl = window["http://jugl.tschaub.net/trunk/lib/Jugl.js"]; 
     70            OpenLayers.ProxyHost = (window.location.host == "localhost") ? 
     71                "/cgi-bin/proxy.cgi?url=" : "proxy.cgi?url="; 
     72 
     73            function init() { 
     74                map = new OpenLayers.Map('map', { 
     75                    restrictedExtent: new OpenLayers.Bounds(-180, -90, 180, 90) 
     76                }); 
     77                var base = new OpenLayers.Layer.WMS("OpenLayers WMS",  
     78                    ["http://t3.labs.metacarta.com/wms-c/Basic.py", 
     79                     "http://t2.labs.metacarta.com/wms-c/Basic.py", 
     80                     "http://t1.labs.metacarta.com/wms-c/Basic.py"],  
     81                    {layers: 'satellite'} 
     82                ); 
     83 
     84                var style = new OpenLayers.Style({ 
     85                    pointRadius: "${radius}", 
     86                    fillColor: "#ffcc66", 
     87                    fillOpacity: 0.8, 
     88                    strokeColor: "#cc6633", 
     89                    strokeWidth: 2, 
     90                    strokeOpacity: 0.8 
     91                }, { 
     92                    context: { 
     93                        radius: function(feature) { 
     94                            return Math.min(feature.attributes.count, 7) + 3; 
     95                        } 
     96                    } 
     97                }); 
     98 
     99                var photos = new OpenLayers.Layer.Vector("Photos", { 
     100                    strategies: [ 
     101                        new OpenLayers.Strategy.Fixed(), 
     102                        new OpenLayers.Strategy.Cluster() 
     103                    ], 
     104                    protocol: new OpenLayers.Protocol.HTTP({ 
     105                        url: "http://labs.metacarta.com/flickrbrowse/flickr.py/flickr", 
     106                        params: { 
     107                            format: "WFS", 
     108                            sort: "interestingness-desc", 
     109                            service: "WFS", 
     110                            request: "GetFeatures", 
     111                            srs: "EPSG:4326", 
     112                            maxfeatures: 150, 
     113                            bbox: [-180, -90, 180, 90] 
     114                        }, 
     115                        format: new OpenLayers.Format.GML() 
     116                    }), 
     117                    styleMap: new OpenLayers.StyleMap({ 
     118                        "default": style, 
     119                        "select": { 
     120                            fillColor: "#8aeeef", 
     121                            strokeColor: "#32a8a9" 
     122                        } 
     123                    }) 
     124                }); 
     125                 
     126                var select = new OpenLayers.Control.SelectFeature( 
     127                    photos, {hover: true} 
     128                ); 
     129                map.addControl(select); 
     130                select.activate(); 
     131                photos.events.on({"featureselected": display}); 
     132 
     133                map.addLayers([base, photos]); 
     134                map.setCenter(new OpenLayers.LonLat(0, 0), 1); 
     135                 
     136                // template setup 
     137                template = new Jugl.Template("template"); 
     138 
     139            } 
     140             
     141            function display(event) { 
     142                // clear previous photo list and create new one 
     143                $("photos").innerHTML = ""; 
     144                var node = template.process({ 
     145                    context: {features: event.feature.cluster}, 
     146                    clone: true, 
     147                    parent: $("photos") 
     148                }); 
     149                // set up forward/rewind 
     150                var forward = Animator.apply($("list"), ["start", "end"], {duration: 1500}); 
     151                $("scroll-end").onmouseover = function() {forward.seekTo(1)}; 
     152                $("scroll-end").onmouseout = function() {forward.seekTo(forward.state)}; 
     153                $("scroll-start").onmouseover = function() {forward.seekTo(0)}; 
     154                $("scroll-start").onmouseout = function() {forward.seekTo(forward.state)}; 
     155                // set up photo zoom 
     156                for(var i=0; i<event.feature.cluster.length; ++i) { 
     157                    listen($("link-" + i), Animator.apply($("photo-" + i), ["thumb", "big"])); 
     158                } 
     159            } 
     160             
     161            function listen(el, anim) { 
     162                el.onmouseover = function() {anim.seekTo(1)}; 
     163                el.onmouseout = function() {anim.seekTo(0)}; 
     164            } 
     165             
     166        </script> 
     167    </head> 
     168    <body onload="init()"> 
     169        <h1 id="title">Cluster Strategy Example</h1> 
     170        <p id="shortdesc"> 
     171            Uses a cluster strategy to render points representing clusters of features. 
     172        </p> 
     173        <div id="map" class="smallmap"></div> 
     174        <div id="docs"> 
     175            <p>The Cluster strategy lets you display points representing clusters 
     176            of features within some pixel distance.</p> 
     177        </div> 
     178        <div id="photos"></div> 
     179        <p>Hover over a cluster on the map to see the photos it includes.</p> 
     180        <div style="display: none;"> 
     181            <div id="template"> 
     182                <div class="shift" id="scroll-start">&lt;&lt;</div> 
     183                <div id="scroll"> 
     184                    <ul id="list" class="start"> 
     185                        <li jugl:repeat="feature features"> 
     186                            <a jugl:attributes="href feature.attributes.img_url; 
     187                                                id 'link-' + repeat.feature.index" 
     188                               target="_blank"> 
     189                                <img jugl:attributes="src feature.attributes.img_url; 
     190                                                      title feature.attributes.title; 
     191                                                      id 'photo-' + repeat.feature.index" 
     192                                     class="thumb" /> 
     193                            </a> 
     194                        </li> 
     195                    </ul> 
     196                </div> 
     197                <div class="shift" id="scroll-end">&gt;&gt;</div> 
     198            </div> 
     199        </div> 
     200    </body> 
     201</html> 
  • examples/strategy-paging.html

    old new  
     1<html xmlns="http://www.w3.org/1999/xhtml"> 
     2    <head> 
     3        <title>OpenLayers Paging Strategy Example</title> 
     4        <link rel="stylesheet" href="../theme/default/style.css" type="text/css" /> 
     5        <link rel="stylesheet" href="style.css" type="text/css" /> 
     6        <script src="../lib/OpenLayers.js"></script> 
     7        <script type="text/javascript"> 
     8            var map, photos, paging; 
     9            OpenLayers.ProxyHost = (window.location.host == "localhost") ? 
     10                "/cgi-bin/proxy.cgi?url=" : "proxy.cgi?url="; 
     11         
     12            function init() { 
     13                map = new OpenLayers.Map('map', { 
     14                    restrictedExtent: new OpenLayers.Bounds(-180, -90, 180, 90) 
     15                }); 
     16                var base = new OpenLayers.Layer.WMS("OpenLayers WMS",  
     17                    ["http://t3.labs.metacarta.com/wms-c/Basic.py", 
     18                     "http://t2.labs.metacarta.com/wms-c/Basic.py", 
     19                     "http://t1.labs.metacarta.com/wms-c/Basic.py"],  
     20                    {layers: 'satellite'} 
     21                ); 
     22 
     23                var style = new OpenLayers.Style({ 
     24                    externalGraphic: "${img_url}", 
     25                    pointRadius: 30 
     26                }); 
     27 
     28                paging = new OpenLayers.Strategy.Paging(); 
     29 
     30                photos = new OpenLayers.Layer.Vector("Photos", { 
     31                    strategies: [new OpenLayers.Strategy.Fixed(), paging], 
     32                    protocol: new OpenLayers.Protocol.HTTP({ 
     33                        url: "http://labs.metacarta.com/flickrbrowse/flickr.py/flickr", 
     34                        params: { 
     35                            format: "WFS", 
     36                            sort: "interestingness-desc", 
     37                            service: "WFS", 
     38                            request: "GetFeatures", 
     39                            srs: "EPSG:4326", 
     40                            maxfeatures: 100, 
     41                            bbox: [-180, -90, 180, 90] 
     42                        }, 
     43                        format: new OpenLayers.Format.GML() 
     44                    }), 
     45                    styleMap: new OpenLayers.StyleMap(style) 
     46                }); 
     47 
     48                map.addLayers([base, photos]); 
     49                photos.events.on({"featuresadded": updateButtons}); 
     50                map.setCenter(new OpenLayers.LonLat(0, 0), 1); 
     51            } 
     52             
     53            function updateButtons() { 
     54                document.getElementById("prev").disabled = (paging.pageNum() < 1); 
     55                document.getElementById("next").disabled = (paging.pageNum() >= paging.pageCount() - 1); 
     56                document.getElementById("num").innerHTML = paging.pageNum() + 1; 
     57                document.getElementById("count").innerHTML = paging.pageCount(); 
     58            } 
     59        </script> 
     60    </head> 
     61    <body onload="init()"> 
     62        <h1 id="title">Paging Strategy Example</h1> 
     63        <p id="shortdesc"> 
     64            Uses a paging strategy to cache large batches of features and render a page at a time. 
     65        </p> 
     66        <div id="map" class="smallmap"></div> 
     67        Displaying page <span id="num">0</span> of <span id="count">...</span> 
     68        <button id="prev" disabled="disabled" onclick="paging.pagePrevious();">previous</button> 
     69        <button id="next" disabled="disabled" onclick="paging.pageNext();">next</button> 
     70        <br /><br /> 
     71        <div id="docs"> 
     72            <p>The Paging strategy lets you apply client side paging for protocols 
     73            that do not support paging on the server.  In this case, the protocol requests a 
     74            batch of 100 features, the strategy caches those and supplies a single 
     75            page at a time to the layer.</p> 
     76        </div> 
     77    </body> 
     78</html>