More trip planner testing with colors
[busui.git] / labs / openlayers / lib / OpenLayers / Strategy / BBOX.js
blob:a/labs/openlayers/lib/OpenLayers/Strategy/BBOX.js -> blob:b/labs/openlayers/lib/OpenLayers/Strategy/BBOX.js
--- a/labs/openlayers/lib/OpenLayers/Strategy/BBOX.js
+++ b/labs/openlayers/lib/OpenLayers/Strategy/BBOX.js
@@ -1,1 +1,279 @@
-
+/* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for 
+ * full list of contributors). Published under the Clear BSD license.  
+ * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ * @requires OpenLayers/Filter/Spatial.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.BBOX
+ * A simple strategy that reads new features when the viewport invalidates
+ *     some bounds.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Strategy>
+ */
+OpenLayers.Strategy.BBOX = OpenLayers.Class(OpenLayers.Strategy, {
+    
+    /**
+     * Property: bounds
+     * {<OpenLayers.Bounds>} The current data bounds (in the same projection
+     *     as the layer - not always the same projection as the map).
+     */
+    bounds: null,
+    
+    /** 
+     * Property: resolution 
+     * {Float} The current data resolution. 
+     */ 
+    resolution: null, 
+           
+    /**
+     * APIProperty: ratio
+     * {Float} The ratio of the data bounds to the viewport bounds (in each
+     *     dimension).  Default is 2.
+     */
+    ratio: 2,
+
+    /** 
+     * Property: resFactor 
+     * {Float} Optional factor used to determine when previously requested 
+     *     features are invalid.  If set, the resFactor will be compared to the
+     *     resolution of the previous request to the current map resolution.
+     *     If resFactor > (old / new) and 1/resFactor < (old / new).  If you
+     *     set a resFactor of 1, data will be requested every time the
+     *     resolution changes.  If you set a resFactor of 3, data will be
+     *     requested if the old resolution is 3 times the new, or if the new is
+     *     3 times the old.  If the old bounds do not contain the new bounds
+     *     new data will always be requested (with or without considering
+     *     resFactor). 
+     */ 
+    resFactor: null, 
+    
+    /**
+     * Property: response
+     * {<OpenLayers.Protocol.Response>} The protocol response object returned
+     *      by the layer protocol.
+     */
+    response: null,
+
+    /**
+     * Constructor: OpenLayers.Strategy.BBOX
+     * Create a new BBOX strategy.
+     *
+     * Parameters:
+     * options - {Object} Optional object whose properties will be set on the
+     *     instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Strategy.prototype.initialize.apply(this, [options]);
+    },
+    
+    /**
+     * Method: activate
+     * Set up strategy with regard to reading new batches of remote data.
+     * 
+     * Returns:
+     * {Boolean} The strategy was successfully activated.
+     */
+    activate: function() {
+        var activated = OpenLayers.Strategy.prototype.activate.call(this);
+        if(activated) {
+            this.layer.events.on({
+                "moveend": this.update,
+                scope: this
+            });
+            this.layer.events.on({
+                "refresh": this.update,
+                scope: this
+            });
+        }
+        return activated;
+    },
+    
+    /**
+     * Method: deactivate
+     * Tear down strategy with regard to reading new batches of remote data.
+     * 
+     * Returns:
+     * {Boolean} The strategy was successfully deactivated.
+     */
+    deactivate: function() {
+        var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
+        if(deactivated) {
+            this.layer.events.un({
+                "moveend": this.update,
+                scope: this
+            });
+            this.layer.events.un({
+                "refresh": this.update,
+                scope: this
+            });
+        }
+        return deactivated;
+    },
+
+    /**
+     * Method: update
+     * Callback function called on "moveend" or "refresh" layer events.
+     *
+     * Parameters:
+     * options - {Object} An object with a property named "force", this
+     *      property references a boolean value indicating if new data
+     *      must be incondtionally read.
+     */
+    update: function(options) {
+        var mapBounds = this.getMapBounds();
+        if ((options && options.force) || this.invalidBounds(mapBounds)) {
+            this.calculateBounds(mapBounds);
+            this.resolution = this.layer.map.getResolution(); 
+            this.triggerRead();
+        }
+    },
+    
+    /**
+     * Method: getMapBounds
+     * Get the map bounds expressed in the same projection as this layer.
+     *
+     * Returns:
+     * {<OpenLayers.Bounds>} Map bounds in the projection of the layer.
+     */
+    getMapBounds: function() {
+        var bounds = this.layer.map.getExtent();
+        if(!this.layer.projection.equals(this.layer.map.getProjectionObject())) {
+            bounds = bounds.clone().transform(
+                this.layer.map.getProjectionObject(), this.layer.projection
+            );
+        }
+        return bounds;
+    },
+
+    /**
+     * Method: invalidBounds
+     * Determine whether the previously requested set of features is invalid. 
+     *     This occurs when the new map bounds do not contain the previously 
+     *     requested bounds.  In addition, if <resFactor> is set, it will be 
+     *     considered.
+     *
+     * Parameters:
+     * mapBounds - {<OpenLayers.Bounds>} the current map extent, will be
+     *      retrieved from the map object if not provided
+     *
+     * Returns:
+     * {Boolean} 
+     */
+    invalidBounds: function(mapBounds) {
+        if(!mapBounds) {
+            mapBounds = this.getMapBounds();
+        }
+        var invalid = !this.bounds || !this.bounds.containsBounds(mapBounds);
+        if(!invalid && this.resFactor) {
+            var ratio = this.resolution / this.layer.map.getResolution();
+            invalid = (ratio >= this.resFactor || ratio <= (1 / this.resFactor));
+        }
+        return invalid;
+    },
+ 
+    /**
+     * Method: calculateBounds
+     *
+     * Parameters:
+     * mapBounds - {<OpenLayers.Bounds>} the current map extent, will be
+     *      retrieved from the map object if not provided
+     */
+    calculateBounds: function(mapBounds) {
+        if(!mapBounds) {
+            mapBounds = this.getMapBounds();
+        }
+        var center = mapBounds.getCenterLonLat();
+        var dataWidth = mapBounds.getWidth() * this.ratio;
+        var dataHeight = mapBounds.getHeight() * this.ratio;
+        this.bounds = new OpenLayers.Bounds(
+            center.lon - (dataWidth / 2),
+            center.lat - (dataHeight / 2),
+            center.lon + (dataWidth / 2),
+            center.lat + (dataHeight / 2)
+        );
+    },
+    
+    /**
+     * Method: triggerRead
+     *
+     * Returns:
+     * {<OpenLayers.Protocol.Response>} The protocol response object
+     *      returned by the layer protocol.
+     */
+    triggerRead: function() {
+        if (this.response) {
+            this.layer.protocol.abort(this.response);
+            this.layer.events.triggerEvent("loadend");
+        }
+        this.layer.events.triggerEvent("loadstart");
+        this.response = this.layer.protocol.read({
+            filter: this.createFilter(),
+            callback: this.merge,
+            scope: this
+        });
+    },
+ 
+    /**
+     * Method: createFilter
+     * Creates a spatial BBOX filter. If the layer that this strategy belongs
+     * to has a filter property, this filter will be combined with the BBOX 
+     * filter.
+     * 
+     * Returns
+     * {<OpenLayers.Filter>} The filter object.
+     */
+    createFilter: function() {
+        var filter = new OpenLayers.Filter.Spatial({
+            type: OpenLayers.Filter.Spatial.BBOX,
+            value: this.bounds,
+            projection: this.layer.projection
+        });
+        if (this.layer.filter) {
+            filter = new OpenLayers.Filter.Logical({
+                type: OpenLayers.Filter.Logical.AND,
+                filters: [this.layer.filter, filter]
+            });
+        }
+        return filter;
+    },
+   
+    /**
+     * Method: merge
+     * Given a list of features, determine which ones to add to the layer.
+     *     If the layer projection differs from the map projection, features
+     *     will be transformed from the layer projection to the map projection.
+     *
+     * Parameters:
+     * resp - {<OpenLayers.Protocol.Response>} The response object passed
+     *      by the protocol.
+     */
+    merge: function(resp) {
+        this.layer.destroyFeatures();
+        var features = resp.features;
+        if(features && features.length > 0) {
+            var remote = this.layer.projection;
+            var local = this.layer.map.getProjectionObject();
+            if(!local.equals(remote)) {
+                var geom;
+                for(var i=0, len=features.length; i<len; ++i) {
+                    geom = features[i].geometry;
+                    if(geom) {
+                        geom.transform(remote, local);
+                    }
+                }
+            }
+            this.layer.addFeatures(features);
+        }
+        this.response = null;
+        this.layer.events.triggerEvent("loadend");
+    },
+   
+    CLASS_NAME: "OpenLayers.Strategy.BBOX" 
+});
+