diff --git a/examples/strategy-bbox.html b/examples/strategy-bbox.html new file mode 100644 index 0000000000..173d33f1f7 --- /dev/null +++ b/examples/strategy-bbox.html @@ -0,0 +1,64 @@ + + + OpenLayers BBOX Strategy Example + + + + + + +

BBOX Strategy Example

+

+ Uses a BBOX strategy to request features within a bounding box. +

+
+
+

The BBOX strategy requests data within a bounding box. When the + previously requested data bounds are invalidated (by browsing to + some area not covered by those bounds), another request for data + is issued.

+
+ + diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js index a1c04e0526..cae000e7b7 100644 --- a/lib/OpenLayers.js +++ b/lib/OpenLayers.js @@ -185,6 +185,7 @@ "OpenLayers/Layer/Vector.js", "OpenLayers/Strategy.js", "OpenLayers/Strategy/Fixed.js", + "OpenLayers/Strategy/BBOX.js", "OpenLayers/Protocol.js", "OpenLayers/Protocol/HTTP.js", "OpenLayers/Layer/PointTrack.js", diff --git a/lib/OpenLayers/Protocol/HTTP.js b/lib/OpenLayers/Protocol/HTTP.js index 866c8e318e..76c5fbc83d 100644 --- a/lib/OpenLayers/Protocol/HTTP.js +++ b/lib/OpenLayers/Protocol/HTTP.js @@ -112,6 +112,15 @@ OpenLayers.Protocol.HTTP = OpenLayers.Class(OpenLayers.Protocol, { * options - {Object} Optional object for configuring the request. * This object is modified and should not be reused. * + * Valid options: + * url - {String} Url for the request. + * params - {Object} Parameters to get serialized as a query string. + * headers - {Object} Headers to be set on the request. + * filter - {} If a bbox filter is sent, it will be + * serialized according to the OpenSearch Geo extension + * (bbox=minx,miny,maxx,maxy). Note that a BBOX filter as the child + * of a logical filter will not be serialized. + * * Returns: * {} A response object, whose "priv" property * references the HTTP request, this object is also passed to the @@ -121,6 +130,14 @@ OpenLayers.Protocol.HTTP = OpenLayers.Class(OpenLayers.Protocol, { read: function(options) { options = OpenLayers.Util.applyDefaults(options, this.options); var resp = new OpenLayers.Protocol.Response({requestType: "read"}); + + if(options.filter && options.filter instanceof OpenLayers.Filter.Spatial) { + if(options.filter.type == OpenLayers.Filter.Spatial.BBOX) { + options.params = OpenLayers.Util.extend(options.params, { + bbox: options.filter.value.toArray() + }); + } + } resp.priv = OpenLayers.Request.GET({ url: options.url, diff --git a/lib/OpenLayers/Strategy/BBOX.js b/lib/OpenLayers/Strategy/BBOX.js new file mode 100644 index 0000000000..f37fd0154d --- /dev/null +++ b/lib/OpenLayers/Strategy/BBOX.js @@ -0,0 +1,210 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., 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.BBOX = OpenLayers.Class(OpenLayers.Strategy, { + + /** + * Property: bounds + * {} The current data bounds. + */ + bounds: null, + + /** + * Property: ratio + * {Float} The ratio of the data bounds to the viewport bounds (in each + * dimension). + */ + ratio: 2, + + /** + * Property: 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.layer.map.getExtent(); + if ((options && options.force) || this.invalidBounds(mapBounds)) { + this.calculateBounds(mapBounds); + this.triggerRead(); + } + }, + + /** + * Method: invalidBounds + * + * Parameters: + * mapBounds - {} the current map extent, will be + * retrieved from the map object if not provided + * + * Returns: + * {Boolean} + */ + invalidBounds: function(mapBounds) { + if(!mapBounds) { + mapBounds = this.layer.map.getExtent(); + } + return !this.bounds || !this.bounds.containsBounds(mapBounds); + }, + + /** + * Method: calculateBounds + * + * Parameters: + * mapBounds - {} the current map extent, will be + * retrieved from the map object if not provided + */ + calculateBounds: function(mapBounds) { + if(!mapBounds) { + mapBounds = this.layer.map.getExtent(); + } + 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: + * {} The protocol response object + * returned by the layer protocol. + */ + triggerRead: function() { + var filter = this.createFilter(); + if (this.response && this.response.priv && + typeof this.response.priv.abort == "function") { + this.response.priv.abort(); + } + this.response = this.layer.protocol.read({ + filter: filter, + callback: this.merge, + scope: this + }); + }, + + /** + * Method: createFilter + * + * Returns + * {} 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. + * + * Parameters: + * resp - {} The response object passed + * by the protocol. + */ + merge: function(resp) { + this.layer.destroyFeatures(); + var features = resp.features; + if(features && features.length > 0) { + this.layer.addFeatures(features); + } + }, + + CLASS_NAME: "OpenLayers.Strategy.BBOX" +}); diff --git a/tests/Protocol/HTTP.html b/tests/Protocol/HTTP.html index 56a27b4476..72685969a8 100644 --- a/tests/Protocol/HTTP.html +++ b/tests/Protocol/HTTP.html @@ -104,6 +104,31 @@ var resp = protocol.read(readOptions); } + function test_read_bbox(t) { + t.plan(1); + var protocol = new OpenLayers.Protocol.HTTP(); + + // fake XHR request object + var request = {'status': 200}; + + var _get = OpenLayers.Request.GET; + + var bounds = new OpenLayers.Bounds(1, 2, 3, 4); + var filter = new OpenLayers.Filter.Spatial({ + type: OpenLayers.Filter.Spatial.BBOX, + value: bounds, + projection: "foo" + }); + + OpenLayers.Request.GET = function(options) { + t.eq(options.params['bbox'].toString(), bounds.toArray().toString(), + 'GET called with bbox filter in params'); + return request; + }; + + var resp = protocol.read({filter: filter}); + } + function test_parseFeatures(t) { t.plan(5); diff --git a/tests/Strategy/BBOX.html b/tests/Strategy/BBOX.html new file mode 100644 index 0000000000..bb019de260 --- /dev/null +++ b/tests/Strategy/BBOX.html @@ -0,0 +1,166 @@ + + + + + + +
+ + diff --git a/tests/list-tests.html b/tests/list-tests.html index 68cb559996..b762e8ddf0 100644 --- a/tests/list-tests.html +++ b/tests/list-tests.html @@ -125,6 +125,7 @@
  • Rule.html
  • Strategy.html
  • Strategy/Fixed.html
  • +
  • Strategy/BBOX.html
  • Style.html
  • StyleMap.html
  • Tile.html