1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2 * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3 * full text of the license. */
7 * @requires OpenLayers/Tile/WFS.js
8 * @requires OpenLayers/Layer/Vector.js
9 * @requires OpenLayers/Layer/Markers.js
10 * @requires OpenLayers/Console.js
14 * Class: OpenLayers.Layer.WFS
17 * - <OpenLayers.Layer.Vector>
18 * - <OpenLayers.Layer.Markers>
20 OpenLayers.Layer.WFS = OpenLayers.Class(
21 OpenLayers.Layer.Vector, OpenLayers.Layer.Markers, {
24 * APIProperty: isBaseLayer
25 * {Boolean} WFS layer is not a base layer by default.
31 * {<OpenLayers.Tile.WFS>}
37 * {Float} the ratio of image/tile size to map size (this is the untiled
43 * Property: DEFAULT_PARAMS
44 * {Object} Hashtable of default key/value parameters
46 DEFAULT_PARAMS: { service: "WFS",
52 * APIProperty: featureClass
53 * {<OpenLayers.Feature>} If featureClass is defined, an old-style markers
54 * based WFS layer is created instead of a new-style vector layer. If
55 * sent, this should be a subclass of OpenLayers.Feature
61 * {<OpenLayers.Format>} The format you want the data to be parsed with.
62 * Must be passed in the constructor. Should be a class, not an instance.
63 * This option can only be used if no featureClass is passed / vectorMode
64 * is false: if a featureClass is passed, then this parameter is ignored.
69 * Property: formatObject
70 * {<OpenLayers.Format>} Internally created/managed format object, used by
71 * the Tile to parse data.
76 * APIProperty: formatOptions
77 * {Object} Hash of options which should be passed to the format when it is
78 * created. Must be passed in the constructor.
83 * Property: vectorMode
84 * {Boolean} Should be calculated automatically. Determines whether the
85 * layer is in vector mode or marker mode.
90 * APIProperty: encodeBBOX
91 * {Boolean} Should the BBOX commas be encoded? The WMS spec says 'no',
92 * but some services want it that way. Default false.
97 * APIProperty: extractAttributes
98 * {Boolean} Should the WFS layer parse attributes from the retrieved
99 * GML? Defaults to false. If enabled, parsing is slower, but
100 * attributes are available in the attributes property of
103 extractAttributes: false,
106 * Constructor: OpenLayers.Layer.WFS
112 * options - {Object} Hashtable of extra options to tag onto the layer
114 initialize: function(name, url, params, options) {
115 if (options == undefined) { options = {}; }
117 if (options.featureClass ||
118 !OpenLayers.Layer.Vector ||
119 !OpenLayers.Feature.Vector) {
120 this.vectorMode = false;
123 // Turn off error reporting, browsers like Safari may work
124 // depending on the setup, and we don't want an unneccesary alert.
125 OpenLayers.Util.extend(options, {'reportError': false});
126 var newArguments = [];
127 newArguments.push(name, options);
128 OpenLayers.Layer.Vector.prototype.initialize.apply(this, newArguments);
129 if (!this.renderer || !this.vectorMode) {
130 this.vectorMode = false;
131 if (!options.featureClass) {
132 options.featureClass = OpenLayers.Feature.WFS;
134 OpenLayers.Layer.Markers.prototype.initialize.apply(this,
138 if (this.params && this.params.typename && !this.options.typename) {
139 this.options.typename = this.params.typename;
142 if (!this.options.geometry_column) {
143 this.options.geometry_column = "the_geom";
146 this.params = OpenLayers.Util.applyDefaults(
148 OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS)
157 destroy: function() {
158 if (this.vectorMode) {
159 OpenLayers.Layer.Vector.prototype.destroy.apply(this, arguments);
161 OpenLayers.Layer.Markers.prototype.destroy.apply(this, arguments);
169 this.featureClass = null;
172 if (this.formatObject && this.formatObject.destroy) {
173 this.formatObject.destroy();
175 this.formatObject = null;
177 this.formatOptions = null;
178 this.vectorMode = null;
179 this.encodeBBOX = null;
180 this.extractAttributes = null;
187 * map - {<OpenLayers.Map>}
189 setMap: function(map) {
190 if (this.vectorMode) {
191 OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments);
194 'extractAttributes': this.extractAttributes
197 OpenLayers.Util.extend(options, this.formatOptions);
198 if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
199 options.externalProjection = this.projection;
200 options.internalProjection = this.map.getProjectionObject();
203 this.formatObject = this.format ? new this.format(options) : new OpenLayers.Format.GML(options);
205 OpenLayers.Layer.Markers.prototype.setMap.apply(this, arguments);
213 * bounds - {<OpenLayers.Bounds>}
214 * zoomChanged - {Boolean}
215 * dragging - {Boolean}
217 moveTo:function(bounds, zoomChanged, dragging) {
218 if (this.vectorMode) {
219 OpenLayers.Layer.Vector.prototype.moveTo.apply(this, arguments);
221 OpenLayers.Layer.Markers.prototype.moveTo.apply(this, arguments);
224 // don't load wfs features while dragging, wait for drag end
226 // TBD try to hide the vector layer while dragging
227 // this.setVisibility(false);
228 // this will probably help for panning performances
233 if (this.vectorMode) {
234 this.renderer.clear();
238 //DEPRECATED - REMOVE IN 3.0
239 // don't load data if current zoom level doesn't match
240 if (this.options.minZoomLevel) {
241 OpenLayers.Console.warn(OpenLayers.i18n('minZoomLevelError'));
243 if (this.map.getZoom() < this.options.minZoomLevel) {
248 if (bounds == null) {
249 bounds = this.map.getExtent();
252 var firstRendering = (this.tile == null);
254 //does the new bounds to which we need to move fall outside of the
255 // current tile's bounds?
256 var outOfBounds = (!firstRendering &&
257 !this.tile.bounds.containsBounds(bounds));
259 if (zoomChanged || firstRendering || (!dragging && outOfBounds)) {
260 //determine new tile bounds
261 var center = bounds.getCenterLonLat();
262 var tileWidth = bounds.getWidth() * this.ratio;
263 var tileHeight = bounds.getHeight() * this.ratio;
265 new OpenLayers.Bounds(center.lon - (tileWidth / 2),
266 center.lat - (tileHeight / 2),
267 center.lon + (tileWidth / 2),
268 center.lat + (tileHeight / 2));
270 //determine new tile size
271 var tileSize = this.map.getSize();
272 tileSize.w = tileSize.w * this.ratio;
273 tileSize.h = tileSize.h * this.ratio;
275 //determine new position (upper left corner of new bounds)
276 var ul = new OpenLayers.LonLat(tileBounds.left, tileBounds.top);
277 var pos = this.map.getLayerPxFromLonLat(ul);
279 //formulate request url string
280 var url = this.getFullRequestString();
284 // Cant combine "filter" and "BBOX". This is a cheap hack to help
285 // people out who can't migrate to the WFS protocol immediately.
286 var filter = this.params.filter || this.params.FILTER;
288 params = {FILTER: filter};
291 params = {BBOX: this.encodeBBOX ? tileBounds.toBBOX()
292 : tileBounds.toArray()};
295 if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
296 var projectedBounds = tileBounds.clone();
297 projectedBounds.transform(this.map.getProjectionObject(),
300 params.BBOX = this.encodeBBOX ? projectedBounds.toBBOX()
301 : projectedBounds.toArray();
305 url += "&" + OpenLayers.Util.getParameterString(params);
308 this.tile = new OpenLayers.Tile.WFS(this, pos, tileBounds,
310 this.addTileMonitoringHooks(this.tile);
313 if (this.vectorMode) {
314 this.destroyFeatures();
315 this.renderer.clear();
319 this.removeTileMonitoringHooks(this.tile);
323 this.tile = new OpenLayers.Tile.WFS(this, pos, tileBounds,
325 this.addTileMonitoringHooks(this.tile);
332 * Method: addTileMonitoringHooks
333 * This function takes a tile as input and adds the appropriate hooks to
334 * the tile so that the layer can keep track of the loading tile
335 * (making sure to check that the tile is always the layer's current
336 * tile before taking any action).
339 * tile - {<OpenLayers.Tile>}
341 addTileMonitoringHooks: function(tile) {
342 tile.onLoadStart = function() {
343 //if this is the the layer's current tile, then trigger
345 if (this == this.layer.tile) {
346 this.layer.events.triggerEvent("loadstart");
349 tile.events.register("loadstart", tile, tile.onLoadStart);
351 tile.onLoadEnd = function() {
352 //if this is the the layer's current tile, then trigger
353 // a 'tileloaded' and 'loadend'
354 if (this == this.layer.tile) {
355 this.layer.events.triggerEvent("tileloaded");
356 this.layer.events.triggerEvent("loadend");
359 tile.events.register("loadend", tile, tile.onLoadEnd);
360 tile.events.register("unload", tile, tile.onLoadEnd);
364 * Method: removeTileMonitoringHooks
365 * This function takes a tile as input and removes the tile hooks
366 * that were added in addTileMonitoringHooks()
369 * tile - {<OpenLayers.Tile>}
371 removeTileMonitoringHooks: function(tile) {
374 "loadstart": tile.onLoadStart,
375 "loadend": tile.onLoadEnd,
376 "unload": tile.onLoadEnd,
382 * Method: onMapResize
383 * Call the onMapResize method of the appropriate parent class.
385 onMapResize: function() {
386 if(this.vectorMode) {
387 OpenLayers.Layer.Vector.prototype.onMapResize.apply(this,
390 OpenLayers.Layer.Markers.prototype.onMapResize.apply(this,
397 * Call the display method of the appropriate parent class.
399 display: function() {
400 if(this.vectorMode) {
401 OpenLayers.Layer.Vector.prototype.display.apply(this,
404 OpenLayers.Layer.Markers.prototype.display.apply(this,
410 * APIMethod: mergeNewParams
411 * Modify parameters for the layer and redraw.
414 * newParams - {Object}
416 mergeNewParams:function(newParams) {
417 var upperParams = OpenLayers.Util.upperCaseObject(newParams);
418 var newArguments = [upperParams];
419 return OpenLayers.Layer.HTTPRequest.prototype.mergeNewParams.apply(this,
430 * {<OpenLayers.Layer.WFS>} An exact clone of this OpenLayers.Layer.WFS
432 clone: function (obj) {
435 obj = new OpenLayers.Layer.WFS(this.name,
441 //get all additions from superclasses
442 if (this.vectorMode) {
443 obj = OpenLayers.Layer.Vector.prototype.clone.apply(this, [obj]);
445 obj = OpenLayers.Layer.Markers.prototype.clone.apply(this, [obj]);
448 // copy/set any non-init, non-simple values here
454 * APIMethod: getFullRequestString
455 * combine the layer's url with its params and these newParams.
457 * Add the SRS parameter from 'projection' -- this is probably
458 * more eloquently done via a setProjection() method, but this
459 * works for now and always.
462 * newParams - {Object}
463 * altUrl - {String} Use this as the url instead of the layer's url
465 getFullRequestString:function(newParams, altUrl) {
466 var projectionCode = this.projection.getCode() || this.map.getProjection();
467 this.params.SRS = (projectionCode == "none") ? null : projectionCode;
469 return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(
475 * Write out the data to a WFS server.
480 if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
481 options.externalProjection = this.projection;
482 options.internalProjection = this.map.getProjectionObject();
485 this.writer = new OpenLayers.Format.WFS(options,this);
488 var data = this.writer.write(this.features);
490 OpenLayers.Request.POST({
493 success: this.commitSuccess,
494 failure: this.commitFailure,
500 * Method: commitSuccess
501 * Called when the Ajax request returns a response
504 * response - {XmlNode} from server
506 commitSuccess: function(request) {
507 var response = request.responseText;
508 if (response.indexOf('SUCCESS') != -1) {
509 this.commitReport(OpenLayers.i18n("commitSuccess", {'response':response}));
511 for(var i = 0; i < this.features.length; i++) {
512 this.features[i].state = null;
514 // TBD redraw the layer or reset the state of features
515 // foreach features: set state to null
516 } else if (response.indexOf('FAILED') != -1 ||
517 response.indexOf('Exception') != -1) {
518 this.commitReport(OpenLayers.i18n("commitFailed", {'response':response}));
523 * Method: commitFailure
524 * Called when the Ajax request fails
527 * response - {XmlNode} from server
529 commitFailure: function(request) {},
532 * APIMethod: commitReport
533 * Called with a 'success' message if the commit succeeded, otherwise
534 * a failure message, and the full request text as a second parameter.
535 * Override this function to provide custom transaction reporting.
537 * string - {String} reporting string
538 * response - {String} full XML response
540 commitReport: function(string, response) {
541 OpenLayers.Console.userError(string);
547 * Refreshes all the features of the layer
549 refresh: function() {
551 if (this.vectorMode) {
552 this.renderer.clear();
553 this.features.length = 0;
556 this.markers.length = 0;
563 * APIMethod: getDataExtent
564 * Calculates the max extent which includes all of the layer data.
567 * {<OpenLayers.Bounds>}
569 getDataExtent: function () {
571 //get all additions from superclasses
572 if (this.vectorMode) {
573 extent = OpenLayers.Layer.Vector.prototype.getDataExtent.apply(this);
575 extent = OpenLayers.Layer.Markers.prototype.getDataExtent.apply(this);
582 * APIMethod: setOpacity
583 * Call the setOpacity method of the appropriate parent class to set the
589 setOpacity: function (opacity) {
590 if (this.vectorMode) {
591 OpenLayers.Layer.Vector.prototype.setOpacity.apply(this, [opacity]);
593 OpenLayers.Layer.Markers.prototype.setOpacity.apply(this, [opacity]);
597 CLASS_NAME: "OpenLayers.Layer.WFS"