1
|
/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
|
2
|
* full list of contributors). Published under the 2-clause BSD license.
|
3
|
* See license.txt in the OpenLayers distribution or repository for the
|
4
|
* full text of the license. */
|
5
|
|
6
|
/**
|
7
|
* @requires OpenLayers/Strategy.js
|
8
|
* @requires OpenLayers/Filter/Spatial.js
|
9
|
*/
|
10
|
|
11
|
/**
|
12
|
* Class: OpenLayers.Strategy.BBOX
|
13
|
* A simple strategy that reads new features when the viewport invalidates
|
14
|
* some bounds.
|
15
|
*
|
16
|
* Inherits from:
|
17
|
* - <OpenLayers.Strategy>
|
18
|
*/
|
19
|
OpenLayers.Strategy.BBOX = OpenLayers.Class(OpenLayers.Strategy, {
|
20
|
|
21
|
/**
|
22
|
* Property: bounds
|
23
|
* {<OpenLayers.Bounds>} The current data bounds (in the same projection
|
24
|
* as the layer - not always the same projection as the map).
|
25
|
*/
|
26
|
bounds: null,
|
27
|
|
28
|
/**
|
29
|
* Property: resolution
|
30
|
* {Float} The current data resolution.
|
31
|
*/
|
32
|
resolution: null,
|
33
|
|
34
|
/**
|
35
|
* APIProperty: ratio
|
36
|
* {Float} The ratio of the data bounds to the viewport bounds (in each
|
37
|
* dimension). Default is 2.
|
38
|
*/
|
39
|
ratio: 2,
|
40
|
|
41
|
/**
|
42
|
* Property: resFactor
|
43
|
* {Float} Optional factor used to determine when previously requested
|
44
|
* features are invalid. If set, the resFactor will be compared to the
|
45
|
* resolution of the previous request to the current map resolution.
|
46
|
* If resFactor > (old / new) and 1/resFactor < (old / new). If you
|
47
|
* set a resFactor of 1, data will be requested every time the
|
48
|
* resolution changes. If you set a resFactor of 3, data will be
|
49
|
* requested if the old resolution is 3 times the new, or if the new is
|
50
|
* 3 times the old. If the old bounds do not contain the new bounds
|
51
|
* new data will always be requested (with or without considering
|
52
|
* resFactor).
|
53
|
*/
|
54
|
resFactor: null,
|
55
|
|
56
|
/**
|
57
|
* Property: response
|
58
|
* {<OpenLayers.Protocol.Response>} The protocol response object returned
|
59
|
* by the layer protocol.
|
60
|
*/
|
61
|
response: null,
|
62
|
|
63
|
/**
|
64
|
* Constructor: OpenLayers.Strategy.BBOX
|
65
|
* Create a new BBOX strategy.
|
66
|
*
|
67
|
* Parameters:
|
68
|
* options - {Object} Optional object whose properties will be set on the
|
69
|
* instance.
|
70
|
*/
|
71
|
|
72
|
/**
|
73
|
* Method: activate
|
74
|
* Set up strategy with regard to reading new batches of remote data.
|
75
|
*
|
76
|
* Returns:
|
77
|
* {Boolean} The strategy was successfully activated.
|
78
|
*/
|
79
|
activate: function() {
|
80
|
var activated = OpenLayers.Strategy.prototype.activate.call(this);
|
81
|
if(activated) {
|
82
|
this.layer.events.on({
|
83
|
"moveend": this.update,
|
84
|
"refresh": this.update,
|
85
|
"visibilitychanged": this.update,
|
86
|
scope: this
|
87
|
});
|
88
|
this.update();
|
89
|
}
|
90
|
return activated;
|
91
|
},
|
92
|
|
93
|
/**
|
94
|
* Method: deactivate
|
95
|
* Tear down strategy with regard to reading new batches of remote data.
|
96
|
*
|
97
|
* Returns:
|
98
|
* {Boolean} The strategy was successfully deactivated.
|
99
|
*/
|
100
|
deactivate: function() {
|
101
|
var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
|
102
|
if(deactivated) {
|
103
|
this.layer.events.un({
|
104
|
"moveend": this.update,
|
105
|
"refresh": this.update,
|
106
|
"visibilitychanged": this.update,
|
107
|
scope: this
|
108
|
});
|
109
|
}
|
110
|
return deactivated;
|
111
|
},
|
112
|
|
113
|
/**
|
114
|
* Method: update
|
115
|
* Callback function called on "moveend" or "refresh" layer events.
|
116
|
*
|
117
|
* Parameters:
|
118
|
* options - {Object} Optional object whose properties will determine
|
119
|
* the behaviour of this Strategy
|
120
|
*
|
121
|
* Valid options include:
|
122
|
* force - {Boolean} if true, new data must be unconditionally read.
|
123
|
* noAbort - {Boolean} if true, do not abort previous requests.
|
124
|
*/
|
125
|
update: function(options) {
|
126
|
var mapBounds = this.getMapBounds();
|
127
|
if (mapBounds !== null && ((options && options.force) ||
|
128
|
(this.layer.visibility && this.layer.calculateInRange() && this.invalidBounds(mapBounds)))) {
|
129
|
this.calculateBounds(mapBounds);
|
130
|
this.resolution = this.layer.map.getResolution();
|
131
|
this.triggerRead(options);
|
132
|
}
|
133
|
},
|
134
|
|
135
|
/**
|
136
|
* Method: getMapBounds
|
137
|
* Get the map bounds expressed in the same projection as this layer.
|
138
|
*
|
139
|
* Returns:
|
140
|
* {<OpenLayers.Bounds>} Map bounds in the projection of the layer.
|
141
|
*/
|
142
|
getMapBounds: function() {
|
143
|
if (this.layer.map === null) {
|
144
|
return null;
|
145
|
}
|
146
|
var bounds = this.layer.map.getExtent();
|
147
|
if(bounds && !this.layer.projection.equals(
|
148
|
this.layer.map.getProjectionObject())) {
|
149
|
bounds = bounds.clone().transform(
|
150
|
this.layer.map.getProjectionObject(), this.layer.projection
|
151
|
);
|
152
|
}
|
153
|
return bounds;
|
154
|
},
|
155
|
|
156
|
/**
|
157
|
* Method: invalidBounds
|
158
|
* Determine whether the previously requested set of features is invalid.
|
159
|
* This occurs when the new map bounds do not contain the previously
|
160
|
* requested bounds. In addition, if <resFactor> is set, it will be
|
161
|
* considered.
|
162
|
*
|
163
|
* Parameters:
|
164
|
* mapBounds - {<OpenLayers.Bounds>} the current map extent, will be
|
165
|
* retrieved from the map object if not provided
|
166
|
*
|
167
|
* Returns:
|
168
|
* {Boolean}
|
169
|
*/
|
170
|
invalidBounds: function(mapBounds) {
|
171
|
if(!mapBounds) {
|
172
|
mapBounds = this.getMapBounds();
|
173
|
}
|
174
|
var invalid = !this.bounds || !this.bounds.containsBounds(mapBounds);
|
175
|
if(!invalid && this.resFactor) {
|
176
|
var ratio = this.resolution / this.layer.map.getResolution();
|
177
|
invalid = (ratio >= this.resFactor || ratio <= (1 / this.resFactor));
|
178
|
}
|
179
|
return invalid;
|
180
|
},
|
181
|
|
182
|
/**
|
183
|
* Method: calculateBounds
|
184
|
*
|
185
|
* Parameters:
|
186
|
* mapBounds - {<OpenLayers.Bounds>} the current map extent, will be
|
187
|
* retrieved from the map object if not provided
|
188
|
*/
|
189
|
calculateBounds: function(mapBounds) {
|
190
|
if(!mapBounds) {
|
191
|
mapBounds = this.getMapBounds();
|
192
|
}
|
193
|
var center = mapBounds.getCenterLonLat();
|
194
|
var dataWidth = mapBounds.getWidth() * this.ratio;
|
195
|
var dataHeight = mapBounds.getHeight() * this.ratio;
|
196
|
this.bounds = new OpenLayers.Bounds(
|
197
|
center.lon - (dataWidth / 2),
|
198
|
center.lat - (dataHeight / 2),
|
199
|
center.lon + (dataWidth / 2),
|
200
|
center.lat + (dataHeight / 2)
|
201
|
);
|
202
|
},
|
203
|
|
204
|
/**
|
205
|
* Method: triggerRead
|
206
|
*
|
207
|
* Parameters:
|
208
|
* options - {Object} Additional options for the protocol's read method
|
209
|
* (optional)
|
210
|
*
|
211
|
* Returns:
|
212
|
* {<OpenLayers.Protocol.Response>} The protocol response object
|
213
|
* returned by the layer protocol.
|
214
|
*/
|
215
|
triggerRead: function(options) {
|
216
|
if (this.response && !(options && options.noAbort === true)) {
|
217
|
this.layer.protocol.abort(this.response);
|
218
|
this.layer.events.triggerEvent("loadend");
|
219
|
}
|
220
|
var evt = {filter: this.createFilter()};
|
221
|
this.layer.events.triggerEvent("loadstart", evt);
|
222
|
this.response = this.layer.protocol.read(
|
223
|
OpenLayers.Util.applyDefaults({
|
224
|
filter: evt.filter,
|
225
|
callback: this.merge,
|
226
|
scope: this
|
227
|
}, options));
|
228
|
},
|
229
|
|
230
|
/**
|
231
|
* Method: createFilter
|
232
|
* Creates a spatial BBOX filter. If the layer that this strategy belongs
|
233
|
* to has a filter property, this filter will be combined with the BBOX
|
234
|
* filter.
|
235
|
*
|
236
|
* Returns
|
237
|
* {<OpenLayers.Filter>} The filter object.
|
238
|
*/
|
239
|
createFilter: function() {
|
240
|
var filter = new OpenLayers.Filter.Spatial({
|
241
|
type: OpenLayers.Filter.Spatial.BBOX,
|
242
|
value: this.bounds,
|
243
|
projection: this.layer.projection
|
244
|
});
|
245
|
if (this.layer.filter) {
|
246
|
filter = new OpenLayers.Filter.Logical({
|
247
|
type: OpenLayers.Filter.Logical.AND,
|
248
|
filters: [this.layer.filter, filter]
|
249
|
});
|
250
|
}
|
251
|
return filter;
|
252
|
},
|
253
|
|
254
|
/**
|
255
|
* Method: merge
|
256
|
* Given a list of features, determine which ones to add to the layer.
|
257
|
* If the layer projection differs from the map projection, features
|
258
|
* will be transformed from the layer projection to the map projection.
|
259
|
*
|
260
|
* Parameters:
|
261
|
* resp - {<OpenLayers.Protocol.Response>} The response object passed
|
262
|
* by the protocol.
|
263
|
*/
|
264
|
merge: function(resp) {
|
265
|
this.layer.destroyFeatures();
|
266
|
if (resp.success()) {
|
267
|
var features = resp.features;
|
268
|
if(features && features.length > 0) {
|
269
|
var remote = this.layer.projection;
|
270
|
var local = this.layer.map.getProjectionObject();
|
271
|
if(!local.equals(remote)) {
|
272
|
var geom;
|
273
|
for(var i=0, len=features.length; i<len; ++i) {
|
274
|
geom = features[i].geometry;
|
275
|
if(geom) {
|
276
|
geom.transform(remote, local);
|
277
|
}
|
278
|
}
|
279
|
}
|
280
|
this.layer.addFeatures(features);
|
281
|
}
|
282
|
} else {
|
283
|
this.bounds = null;
|
284
|
}
|
285
|
this.response = null;
|
286
|
this.layer.events.triggerEvent("loadend", {response: resp});
|
287
|
},
|
288
|
|
289
|
CLASS_NAME: "OpenLayers.Strategy.BBOX"
|
290
|
});
|