/* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the Clear BSD license. * See http://svn.openlayers.org/trunk/openlayers/license.txt for the * full text of the license. */ /** * @requires OpenLayers/Tile/WFS.js * @requires OpenLayers/Layer/Vector.js * @requires OpenLayers/Layer/Markers.js * @requires OpenLayers/Console.js */ /** * Class: OpenLayers.Layer.WFS * *Deprecated*. To be removed in 3.0. Instead use OpenLayers.Layer.Vector * with a Protocol.WFS and one or more Strategies. * * Inherits from: * - * - */ OpenLayers.Layer.WFS = OpenLayers.Class( OpenLayers.Layer.Vector, OpenLayers.Layer.Markers, { /** * APIProperty: isBaseLayer * {Boolean} WFS layer is not a base layer by default. */ isBaseLayer: false, /** * Property: tile * {} */ tile: null, /** * APIProperty: ratio * {Float} The ratio property determines the size of the serverside query * relative to the map viewport size. By default, we load an area twice * as big as the map, to allow for panning without immediately reload. * Setting this to 1 will cause the area of the WFS request to match * the map area exactly. It is recommended to set this to some number * at least slightly larger than 1, otherwise accidental clicks can * cause a data reload, by moving the map only 1 pixel. */ ratio: 2, /** * Property: DEFAULT_PARAMS * {Object} Hashtable of default key/value parameters */ DEFAULT_PARAMS: { service: "WFS", version: "1.0.0", request: "GetFeature" }, /** * APIProperty: featureClass * {} If featureClass is defined, an old-style markers * based WFS layer is created instead of a new-style vector layer. If * sent, this should be a subclass of OpenLayers.Feature */ featureClass: null, /** * APIProperty: format * {} The format you want the data to be parsed with. * Must be passed in the constructor. Should be a class, not an instance. * This option can only be used if no featureClass is passed / vectorMode * is false: if a featureClass is passed, then this parameter is ignored. */ format: null, /** * Property: formatObject * {} Internally created/managed format object, used by * the Tile to parse data. */ formatObject: null, /** * APIProperty: formatOptions * {Object} Hash of options which should be passed to the format when it is * created. Must be passed in the constructor. */ formatOptions: null, /** * Property: vectorMode * {Boolean} Should be calculated automatically. Determines whether the * layer is in vector mode or marker mode. */ vectorMode: true, /** * APIProperty: encodeBBOX * {Boolean} Should the BBOX commas be encoded? The WMS spec says 'no', * but some services want it that way. Default false. */ encodeBBOX: false, /** * APIProperty: extractAttributes * {Boolean} Should the WFS layer parse attributes from the retrieved * GML? Defaults to false. If enabled, parsing is slower, but * attributes are available in the attributes property of * layer features. */ extractAttributes: false, /** * Constructor: OpenLayers.Layer.WFS * * Parameters: * name - {String} * url - {String} * params - {Object} * options - {Object} Hashtable of extra options to tag onto the layer */ initialize: function(name, url, params, options) { if (options == undefined) { options = {}; } if (options.featureClass || !OpenLayers.Layer.Vector || !OpenLayers.Feature.Vector) { this.vectorMode = false; } // Uppercase params params = OpenLayers.Util.upperCaseObject(params); // Turn off error reporting, browsers like Safari may work // depending on the setup, and we don't want an unneccesary alert. OpenLayers.Util.extend(options, {'reportError': false}); var newArguments = []; newArguments.push(name, options); OpenLayers.Layer.Vector.prototype.initialize.apply(this, newArguments); if (!this.renderer || !this.vectorMode) { this.vectorMode = false; if (!options.featureClass) { options.featureClass = OpenLayers.Feature.WFS; } OpenLayers.Layer.Markers.prototype.initialize.apply(this, newArguments); } if (this.params && this.params.typename && !this.options.typename) { this.options.typename = this.params.typename; } if (!this.options.geometry_column) { this.options.geometry_column = "the_geom"; } this.params = OpenLayers.Util.applyDefaults( params, OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS) ); this.url = url; }, /** * APIMethod: destroy */ destroy: function() { if (this.vectorMode) { OpenLayers.Layer.Vector.prototype.destroy.apply(this, arguments); } else { OpenLayers.Layer.Markers.prototype.destroy.apply(this, arguments); } if (this.tile) { this.tile.destroy(); } this.tile = null; this.ratio = null; this.featureClass = null; this.format = null; if (this.formatObject && this.formatObject.destroy) { this.formatObject.destroy(); } this.formatObject = null; this.formatOptions = null; this.vectorMode = null; this.encodeBBOX = null; this.extractAttributes = null; }, /** * Method: setMap * * Parameters: * map - {} */ setMap: function(map) { if (this.vectorMode) { OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments); var options = { 'extractAttributes': this.extractAttributes }; OpenLayers.Util.extend(options, this.formatOptions); if (this.map && !this.projection.equals(this.map.getProjectionObject())) { options.externalProjection = this.projection; options.internalProjection = this.map.getProjectionObject(); } this.formatObject = this.format ? new this.format(options) : new OpenLayers.Format.GML(options); } else { OpenLayers.Layer.Markers.prototype.setMap.apply(this, arguments); } }, /** * Method: moveTo * * Parameters: * bounds - {} * zoomChanged - {Boolean} * dragging - {Boolean} */ moveTo:function(bounds, zoomChanged, dragging) { if (this.vectorMode) { OpenLayers.Layer.Vector.prototype.moveTo.apply(this, arguments); } else { OpenLayers.Layer.Markers.prototype.moveTo.apply(this, arguments); } // don't load wfs features while dragging, wait for drag end if (dragging) { // TBD try to hide the vector layer while dragging // this.setVisibility(false); // this will probably help for panning performances return false; } if ( zoomChanged ) { if (this.vectorMode) { this.renderer.clear(); } } //DEPRECATED - REMOVE IN 3.0 // don't load data if current zoom level doesn't match if (this.options.minZoomLevel) { OpenLayers.Console.warn(OpenLayers.i18n('minZoomLevelError')); if (this.map.getZoom() < this.options.minZoomLevel) { return null; } } if (bounds == null) { bounds = this.map.getExtent(); } var firstRendering = (this.tile == null); //does the new bounds to which we need to move fall outside of the // current tile's bounds? var outOfBounds = (!firstRendering && !this.tile.bounds.containsBounds(bounds)); if (zoomChanged || firstRendering || (!dragging && outOfBounds)) { //determine new tile bounds var center = bounds.getCenterLonLat(); var tileWidth = bounds.getWidth() * this.ratio; var tileHeight = bounds.getHeight() * this.ratio; var tileBounds = new OpenLayers.Bounds(center.lon - (tileWidth / 2), center.lat - (tileHeight / 2), center.lon + (tileWidth / 2), center.lat + (tileHeight / 2)); //determine new tile size var tileSize = this.map.getSize(); tileSize.w = tileSize.w * this.ratio; tileSize.h = tileSize.h * this.ratio; //determine new position (upper left corner of new bounds) var ul = new OpenLayers.LonLat(tileBounds.left, tileBounds.top); var pos = this.map.getLayerPxFromLonLat(ul); //formulate request url string var url = this.getFullRequestString(); var params = null; // Cant combine "filter" and "BBOX". This is a cheap hack to help // people out who can't migrate to the WFS protocol immediately. var filter = this.params.filter || this.params.FILTER; if (filter) { params = {FILTER: filter}; } else { params = {BBOX: this.encodeBBOX ? tileBounds.toBBOX() : tileBounds.toArray()}; } if (this.map && !this.projection.equals(this.map.getProjectionObject())) { var projectedBounds = tileBounds.clone(); projectedBounds.transform(this.map.getProjectionObject(), this.projection); if (!filter){ params.BBOX = this.encodeBBOX ? projectedBounds.toBBOX() : projectedBounds.toArray(); } } url += "&" + OpenLayers.Util.getParameterString(params); if (!this.tile) { this.tile = new OpenLayers.Tile.WFS(this, pos, tileBounds, url, tileSize); this.addTileMonitoringHooks(this.tile); this.tile.draw(); } else { if (this.vectorMode) { this.destroyFeatures(); this.renderer.clear(); } else { this.clearMarkers(); } this.removeTileMonitoringHooks(this.tile); this.tile.destroy(); this.tile = null; this.tile = new OpenLayers.Tile.WFS(this, pos, tileBounds, url, tileSize); this.addTileMonitoringHooks(this.tile); this.tile.draw(); } } }, /** * Method: addTileMonitoringHooks * This function takes a tile as input and adds the appropriate hooks to * the tile so that the layer can keep track of the loading tile * (making sure to check that the tile is always the layer's current * tile before taking any action). * * Parameters: * tile - {} */ addTileMonitoringHooks: function(tile) { tile.onLoadStart = function() { //if this is the the layer's current tile, then trigger // a 'loadstart' if (this == this.layer.tile) { this.layer.events.triggerEvent("loadstart"); } }; tile.events.register("loadstart", tile, tile.onLoadStart); tile.onLoadEnd = function() { //if this is the the layer's current tile, then trigger // a 'tileloaded' and 'loadend' if (this == this.layer.tile) { this.layer.events.triggerEvent("tileloaded"); this.layer.events.triggerEvent("loadend"); } }; tile.events.register("loadend", tile, tile.onLoadEnd); tile.events.register("unload", tile, tile.onLoadEnd); }, /** * Method: removeTileMonitoringHooks * This function takes a tile as input and removes the tile hooks * that were added in addTileMonitoringHooks() * * Parameters: * tile - {} */ removeTileMonitoringHooks: function(tile) { tile.unload(); tile.events.un({ "loadstart": tile.onLoadStart, "loadend": tile.onLoadEnd, "unload": tile.onLoadEnd, scope: tile }); }, /** * Method: onMapResize * Call the onMapResize method of the appropriate parent class. */ onMapResize: function() { if(this.vectorMode) { OpenLayers.Layer.Vector.prototype.onMapResize.apply(this, arguments); } else { OpenLayers.Layer.Markers.prototype.onMapResize.apply(this, arguments); } }, /** * Method: display * Call the display method of the appropriate parent class. */ display: function() { if(this.vectorMode) { OpenLayers.Layer.Vector.prototype.display.apply(this, arguments); } else { OpenLayers.Layer.Markers.prototype.display.apply(this, arguments); } }, /** * APIMethod: mergeNewParams * Modify parameters for the layer and redraw. * * Parameters: * newParams - {Object} */ mergeNewParams:function(newParams) { var upperParams = OpenLayers.Util.upperCaseObject(newParams); var newArguments = [upperParams]; return OpenLayers.Layer.HTTPRequest.prototype.mergeNewParams.apply(this, newArguments); }, /** * APIMethod: clone * * Parameters: * obj - {Object} * * Returns: * {} An exact clone of this OpenLayers.Layer.WFS */ clone: function (obj) { if (obj == null) { obj = new OpenLayers.Layer.WFS(this.name, this.url, this.params, this.getOptions()); } //get all additions from superclasses if (this.vectorMode) { obj = OpenLayers.Layer.Vector.prototype.clone.apply(this, [obj]); } else { obj = OpenLayers.Layer.Markers.prototype.clone.apply(this, [obj]); } // copy/set any non-init, non-simple values here return obj; }, /** * APIMethod: getFullRequestString * combine the layer's url with its params and these newParams. * * Add the SRS parameter from 'projection' -- this is probably * more eloquently done via a setProjection() method, but this * works for now and always. * * Parameters: * newParams - {Object} * altUrl - {String} Use this as the url instead of the layer's url */ getFullRequestString:function(newParams, altUrl) { var projectionCode = this.projection.getCode() || this.map.getProjection(); this.params.SRS = (projectionCode == "none") ? null : projectionCode; return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply( this, arguments); }, /** * APIMethod: commit * Write out the data to a WFS server. */ commit: function() { if (!this.writer) { var options = {}; if (this.map && !this.projection.equals(this.map.getProjectionObject())) { options.externalProjection = this.projection; options.internalProjection = this.map.getProjectionObject(); } this.writer = new OpenLayers.Format.WFS(options,this); } var data = this.writer.write(this.features); OpenLayers.Request.POST({ url: this.url, data: data, success: this.commitSuccess, failure: this.commitFailure, scope: this }); }, /** * Method: commitSuccess * Called when the Ajax request returns a response * * Parameters: * response - {XmlNode} from server */ commitSuccess: function(request) { var response = request.responseText; if (response.indexOf('SUCCESS') != -1) { this.commitReport(OpenLayers.i18n("commitSuccess", {'response':response})); for(var i = 0; i < this.features.length; i++) { this.features[i].state = null; } // TBD redraw the layer or reset the state of features // foreach features: set state to null } else if (response.indexOf('FAILED') != -1 || response.indexOf('Exception') != -1) { this.commitReport(OpenLayers.i18n("commitFailed", {'response':response})); } }, /** * Method: commitFailure * Called when the Ajax request fails * * Parameters: * response - {XmlNode} from server */ commitFailure: function(request) {}, /** * APIMethod: commitReport * Called with a 'success' message if the commit succeeded, otherwise * a failure message, and the full request text as a second parameter. * Override this function to provide custom transaction reporting. * * string - {String} reporting string * response - {String} full XML response */ commitReport: function(string, response) { OpenLayers.Console.userError(string); }, /** * APIMethod: refresh * Refreshes all the features of the layer */ refresh: function() { if (this.tile) { if (this.vectorMode) { this.renderer.clear(); this.features.length = 0; } else { this.clearMarkers(); this.markers.length = 0; } this.tile.draw(); } }, /** * APIMethod: getDataExtent * Calculates the max extent which includes all of the layer data. * * Returns: * {} */ getDataExtent: function () { var extent; //get all additions from superclasses if (this.vectorMode) { extent = OpenLayers.Layer.Vector.prototype.getDataExtent.apply(this); } else { extent = OpenLayers.Layer.Markers.prototype.getDataExtent.apply(this); } return extent; }, /** * APIMethod: setOpacity * Call the setOpacity method of the appropriate parent class to set the * opacity. * * Parameter: * opacity - {Float} */ setOpacity: function (opacity) { if (this.vectorMode) { OpenLayers.Layer.Vector.prototype.setOpacity.apply(this, [opacity]); } else { OpenLayers.Layer.Markers.prototype.setOpacity.apply(this, [opacity]); } }, CLASS_NAME: "OpenLayers.Layer.WFS" });