BBOX.js 9.4 KB

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