Adding a BBOX strategy. This triggers requests for data within a bounding box. When the map bounds invalidate previous data bounds, another request is issued. Reads are triggered with a spatial filter, and protocols that understand how to deal with filters may serialize the filter to request a subset of the data collection. The HTTP protocol understands one filter - the bbox filter. So the BBOX strategy can be used in conjunction with the HTTP protocol to request data via http in a bounding box. Thanks for the collaboration and tests Eric. r=elemoine,crschmidt (#1731)
git-svn-id: http://svn.openlayers.org/trunk/openlayers@8000 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
This commit is contained in:
@@ -0,0 +1,64 @@
|
|||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<title>OpenLayers BBOX Strategy Example</title>
|
||||||
|
<link rel="stylesheet" href="../theme/default/style.css" type="text/css" />
|
||||||
|
<link rel="stylesheet" href="style.css" type="text/css" />
|
||||||
|
<script src="../lib/OpenLayers.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var map, photos;
|
||||||
|
OpenLayers.ProxyHost = (window.location.host == "localhost") ?
|
||||||
|
"/cgi-bin/proxy.cgi?url=" : "proxy.cgi?url=";
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
map = new OpenLayers.Map('map', {
|
||||||
|
restrictedExtent: new OpenLayers.Bounds(-180, -90, 180, 90)
|
||||||
|
});
|
||||||
|
var base = new OpenLayers.Layer.WMS("OpenLayers WMS",
|
||||||
|
["http://t3.labs.metacarta.com/wms-c/Basic.py",
|
||||||
|
"http://t2.labs.metacarta.com/wms-c/Basic.py",
|
||||||
|
"http://t1.labs.metacarta.com/wms-c/Basic.py"],
|
||||||
|
{layers: 'satellite'}
|
||||||
|
);
|
||||||
|
|
||||||
|
var style = new OpenLayers.Style({
|
||||||
|
externalGraphic: "${img_url}",
|
||||||
|
pointRadius: 30
|
||||||
|
});
|
||||||
|
|
||||||
|
photos = new OpenLayers.Layer.Vector("Photos", {
|
||||||
|
strategies: [new OpenLayers.Strategy.BBOX()],
|
||||||
|
protocol: new OpenLayers.Protocol.HTTP({
|
||||||
|
url: "http://labs.metacarta.com/flickrbrowse/flickr.py/flickr",
|
||||||
|
params: {
|
||||||
|
format: "WFS",
|
||||||
|
sort: "interestingness-desc",
|
||||||
|
service: "WFS",
|
||||||
|
request: "GetFeatures",
|
||||||
|
srs: "EPSG:4326",
|
||||||
|
maxfeatures: 10
|
||||||
|
},
|
||||||
|
format: new OpenLayers.Format.GML()
|
||||||
|
}),
|
||||||
|
styleMap: new OpenLayers.StyleMap(style)
|
||||||
|
});
|
||||||
|
|
||||||
|
map.addLayers([base, photos]);
|
||||||
|
map.setCenter(new OpenLayers.LonLat(-116.45, 35.42), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body onload="init()">
|
||||||
|
<h1 id="title">BBOX Strategy Example</h1>
|
||||||
|
<p id="shortdesc">
|
||||||
|
Uses a BBOX strategy to request features within a bounding box.
|
||||||
|
</p>
|
||||||
|
<div id="map" class="smallmap"></div>
|
||||||
|
<div id="docs">
|
||||||
|
<p>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.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -185,6 +185,7 @@
|
|||||||
"OpenLayers/Layer/Vector.js",
|
"OpenLayers/Layer/Vector.js",
|
||||||
"OpenLayers/Strategy.js",
|
"OpenLayers/Strategy.js",
|
||||||
"OpenLayers/Strategy/Fixed.js",
|
"OpenLayers/Strategy/Fixed.js",
|
||||||
|
"OpenLayers/Strategy/BBOX.js",
|
||||||
"OpenLayers/Protocol.js",
|
"OpenLayers/Protocol.js",
|
||||||
"OpenLayers/Protocol/HTTP.js",
|
"OpenLayers/Protocol/HTTP.js",
|
||||||
"OpenLayers/Layer/PointTrack.js",
|
"OpenLayers/Layer/PointTrack.js",
|
||||||
|
|||||||
@@ -112,6 +112,15 @@ OpenLayers.Protocol.HTTP = OpenLayers.Class(OpenLayers.Protocol, {
|
|||||||
* options - {Object} Optional object for configuring the request.
|
* options - {Object} Optional object for configuring the request.
|
||||||
* This object is modified and should not be reused.
|
* 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 - {<OpenLayers.Filter.BBOX>} 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:
|
* Returns:
|
||||||
* {<OpenLayers.Protocol.Response>} A response object, whose "priv" property
|
* {<OpenLayers.Protocol.Response>} A response object, whose "priv" property
|
||||||
* references the HTTP request, this object is also passed to the
|
* 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) {
|
read: function(options) {
|
||||||
options = OpenLayers.Util.applyDefaults(options, this.options);
|
options = OpenLayers.Util.applyDefaults(options, this.options);
|
||||||
var resp = new OpenLayers.Protocol.Response({requestType: "read"});
|
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({
|
resp.priv = OpenLayers.Request.GET({
|
||||||
url: options.url,
|
url: options.url,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
*/
|
||||||
|
OpenLayers.Strategy.BBOX = OpenLayers.Class(OpenLayers.Strategy, {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property: bounds
|
||||||
|
* {<OpenLayers.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
|
||||||
|
* {<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.layer.map.getExtent();
|
||||||
|
if ((options && options.force) || this.invalidBounds(mapBounds)) {
|
||||||
|
this.calculateBounds(mapBounds);
|
||||||
|
this.triggerRead();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method: invalidBounds
|
||||||
|
*
|
||||||
|
* 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.layer.map.getExtent();
|
||||||
|
}
|
||||||
|
return !this.bounds || !this.bounds.containsBounds(mapBounds);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.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:
|
||||||
|
* {<OpenLayers.Protocol.Response>} 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
|
||||||
|
* {<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.
|
||||||
|
*
|
||||||
|
* 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) {
|
||||||
|
this.layer.addFeatures(features);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
CLASS_NAME: "OpenLayers.Strategy.BBOX"
|
||||||
|
});
|
||||||
@@ -104,6 +104,31 @@
|
|||||||
var resp = protocol.read(readOptions);
|
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) {
|
function test_parseFeatures(t) {
|
||||||
t.plan(5);
|
t.plan(5);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="../../lib/OpenLayers.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
function test_initialize(t) {
|
||||||
|
t.plan(1);
|
||||||
|
|
||||||
|
var ratio = 4;
|
||||||
|
|
||||||
|
var s = new OpenLayers.Strategy.BBOX({ratio: ratio});
|
||||||
|
t.eq(s.ratio, ratio, "ctor sets ratio");
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_activate(t) {
|
||||||
|
t.plan(5);
|
||||||
|
|
||||||
|
var l = new OpenLayers.Layer.Vector();
|
||||||
|
var s = new OpenLayers.Strategy.BBOX();
|
||||||
|
s.setLayer(l);
|
||||||
|
|
||||||
|
t.eq(s.active, false, "not active after construction");
|
||||||
|
|
||||||
|
var activated = s.activate();
|
||||||
|
t.eq(activated, true, "activate returns true");
|
||||||
|
t.eq(s.active, true, "activated after activate");
|
||||||
|
t.ok(l.events.listeners["moveend"][0].obj == s &&
|
||||||
|
l.events.listeners["moveend"][0].func == s.update,
|
||||||
|
"activates registers moveend listener");
|
||||||
|
t.ok(l.events.listeners["refresh"][0].obj == s &&
|
||||||
|
l.events.listeners["refresh"][0].func == s.update,
|
||||||
|
"activates registers refresh listener");
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_update(t) {
|
||||||
|
t.plan(6);
|
||||||
|
|
||||||
|
var s = new OpenLayers.Strategy.BBOX();
|
||||||
|
|
||||||
|
var invalidBoundsReturnValue;
|
||||||
|
var bounds = new OpenLayers.Bounds(-100, -40, 100, 40);
|
||||||
|
|
||||||
|
s.invalidBounds = function(b) {
|
||||||
|
t.ok(b == bounds,
|
||||||
|
"update calls invalidBounds with correct arg");
|
||||||
|
return invalidBoundsReturnValue;
|
||||||
|
};
|
||||||
|
s.calculateBounds = function(b) {
|
||||||
|
t.ok(b == bounds,
|
||||||
|
"update calls calculateBounds with correct arg");
|
||||||
|
};
|
||||||
|
s.triggerRead = function() {
|
||||||
|
t.ok(true,
|
||||||
|
"update calls triggerRead");
|
||||||
|
};
|
||||||
|
|
||||||
|
s.setLayer({
|
||||||
|
map: {
|
||||||
|
getExtent: function() {
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2 tests
|
||||||
|
invalidBoundsReturnValue = true;
|
||||||
|
s.update({force: true});
|
||||||
|
|
||||||
|
// 3 tests
|
||||||
|
invalidBoundsReturnValue = true;
|
||||||
|
s.update();
|
||||||
|
|
||||||
|
// 1 tests
|
||||||
|
invalidBoundsReturnValue = false;
|
||||||
|
s.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_triggerRead(t) {
|
||||||
|
t.plan(7);
|
||||||
|
|
||||||
|
var s = new OpenLayers.Strategy.BBOX();
|
||||||
|
|
||||||
|
var filter = {"fake": "filter"};
|
||||||
|
|
||||||
|
s.createFilter = function() {
|
||||||
|
return filter;
|
||||||
|
};
|
||||||
|
|
||||||
|
s.setLayer({
|
||||||
|
protocol: {
|
||||||
|
read: function(options) {
|
||||||
|
t.ok(options.filter == filter,
|
||||||
|
"protocol read called with correct filter");
|
||||||
|
t.ok(options.callback == s.merge,
|
||||||
|
"protocol read called with correct callback");
|
||||||
|
t.ok(options.scope == s,
|
||||||
|
"protocol read called with correct scope");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3 tests
|
||||||
|
s.triggerRead();
|
||||||
|
|
||||||
|
// 4 tests
|
||||||
|
s.response = {
|
||||||
|
priv: {
|
||||||
|
abort: function() {
|
||||||
|
t.ok(true,
|
||||||
|
"triggerRead aborts previous read request");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
s.triggerRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_createFilter(t) {
|
||||||
|
t.plan(3);
|
||||||
|
|
||||||
|
var s = new OpenLayers.Strategy.BBOX();
|
||||||
|
|
||||||
|
var f;
|
||||||
|
|
||||||
|
// 2 test
|
||||||
|
s.setLayer({});
|
||||||
|
f = s.createFilter();
|
||||||
|
t.ok(f.CLASS_NAME.search(/^OpenLayers.Filter.Spatial/) != -1,
|
||||||
|
"createFilter returns a spatial filter object");
|
||||||
|
t.eq(f.type, OpenLayers.Filter.Spatial.BBOX,
|
||||||
|
"createFilter returns a BBOX-typed filter");
|
||||||
|
|
||||||
|
// 1 test
|
||||||
|
s.setLayer({filter: {fake: "filter"}});
|
||||||
|
f = s.createFilter();
|
||||||
|
t.ok(f.CLASS_NAME.search(/^OpenLayers.Filter.Logical/) != -1,
|
||||||
|
"createFilter returns a logical filter object");
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_merge(t) {
|
||||||
|
t.plan(2);
|
||||||
|
|
||||||
|
var s = new OpenLayers.Strategy.BBOX();
|
||||||
|
|
||||||
|
var features = ["fake", "feature", "array"];
|
||||||
|
|
||||||
|
s.setLayer({
|
||||||
|
destroyFeatures: function() {
|
||||||
|
t.ok(true,
|
||||||
|
"merge calls destroyFeatures");
|
||||||
|
},
|
||||||
|
addFeatures: function(f) {
|
||||||
|
t.ok(f == features,
|
||||||
|
"merge calls addFeatures with the correct features");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2 tests
|
||||||
|
s.merge({features: features});
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map" style="width: 400px; height: 200px" />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -125,6 +125,7 @@
|
|||||||
<li>Rule.html</li>
|
<li>Rule.html</li>
|
||||||
<li>Strategy.html</li>
|
<li>Strategy.html</li>
|
||||||
<li>Strategy/Fixed.html</li>
|
<li>Strategy/Fixed.html</li>
|
||||||
|
<li>Strategy/BBOX.html</li>
|
||||||
<li>Style.html</li>
|
<li>Style.html</li>
|
||||||
<li>StyleMap.html</li>
|
<li>StyleMap.html</li>
|
||||||
<li>Tile.html</li>
|
<li>Tile.html</li>
|
||||||
|
|||||||
Reference in New Issue
Block a user