]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Layer/WFS.js
fixes notices
[syp.git] / openlayers / lib / OpenLayers / Layer / WFS.js
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. */
4
5
6 /**
7  * @requires OpenLayers/Tile/WFS.js
8  * @requires OpenLayers/Layer/Vector.js
9  * @requires OpenLayers/Layer/Markers.js
10  * @requires OpenLayers/Console.js
11  */
12
13 /**
14  * Class: OpenLayers.Layer.WFS
15  * 
16  * Inherits from:
17  *  - <OpenLayers.Layer.Vector>
18  *  - <OpenLayers.Layer.Markers>
19  */
20 OpenLayers.Layer.WFS = OpenLayers.Class(
21   OpenLayers.Layer.Vector, OpenLayers.Layer.Markers, {
22
23     /**
24      * APIProperty: isBaseLayer
25      * {Boolean} WFS layer is not a base layer by default. 
26      */
27     isBaseLayer: false,
28     
29     /**
30      * Property: tile
31      * {<OpenLayers.Tile.WFS>}
32      */
33     tile: null,    
34     
35     /**
36      * APIProperty: ratio
37      * {Float} the ratio of image/tile size to map size (this is the untiled
38      *     buffer)
39      */
40     ratio: 2,
41
42     /**  
43      * Property: DEFAULT_PARAMS
44      * {Object} Hashtable of default key/value parameters
45      */
46     DEFAULT_PARAMS: { service: "WFS",
47                       version: "1.0.0",
48                       request: "GetFeature"
49                     },
50     
51     /** 
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
56      */
57     featureClass: null,
58     
59     /**
60       * APIProperty: format
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.
65       */
66     format: null,
67
68     /** 
69      * Property: formatObject
70      * {<OpenLayers.Format>} Internally created/managed format object, used by
71      * the Tile to parse data.
72      */
73     formatObject: null,
74
75     /**
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.
79      */
80     formatOptions: null, 
81
82     /**
83      * Property: vectorMode
84      * {Boolean} Should be calculated automatically. Determines whether the
85      *     layer is in vector mode or marker mode.
86      */
87     vectorMode: true, 
88     
89     /**
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.
93      */
94     encodeBBOX: false,
95     
96     /**
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 
101      *     layer features.
102      */
103     extractAttributes: false,
104
105     /**
106      * Constructor: OpenLayers.Layer.WFS
107      *
108      * Parameters:
109      * name - {String} 
110      * url - {String} 
111      * params - {Object} 
112      * options - {Object} Hashtable of extra options to tag onto the layer
113      */
114     initialize: function(name, url, params, options) {
115         if (options == undefined) { options = {}; } 
116         
117         if (options.featureClass || 
118             !OpenLayers.Layer.Vector || 
119             !OpenLayers.Feature.Vector) {
120             this.vectorMode = false;
121         }    
122         
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;
133             }   
134             OpenLayers.Layer.Markers.prototype.initialize.apply(this, 
135                                                                 newArguments);
136         }
137         
138         if (this.params && this.params.typename && !this.options.typename) {
139             this.options.typename = this.params.typename;
140         }
141         
142         if (!this.options.geometry_column) {
143             this.options.geometry_column = "the_geom";
144         }    
145         
146         this.params = OpenLayers.Util.applyDefaults(
147             params, 
148             OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS)
149         );
150         this.url = url;
151     },    
152     
153
154     /**
155      * APIMethod: destroy
156      */
157     destroy: function() {
158         if (this.vectorMode) {
159             OpenLayers.Layer.Vector.prototype.destroy.apply(this, arguments);
160         } else {    
161             OpenLayers.Layer.Markers.prototype.destroy.apply(this, arguments);
162         }    
163         if (this.tile) {
164             this.tile.destroy();
165         }
166         this.tile = null;
167
168         this.ratio = null;
169         this.featureClass = null;
170         this.format = null;
171
172         if (this.formatObject && this.formatObject.destroy) {
173             this.formatObject.destroy();
174         }
175         this.formatObject = null;
176         
177         this.formatOptions = null;
178         this.vectorMode = null;
179         this.encodeBBOX = null;
180         this.extractAttributes = null;
181     },
182     
183     /**
184      * Method: setMap
185      * 
186      * Parameters:
187      * map - {<OpenLayers.Map>} 
188      */
189     setMap: function(map) {
190         if (this.vectorMode) {
191             OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments);
192             
193             var options = {
194               'extractAttributes': this.extractAttributes
195             };
196             
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();
201             }    
202             
203             this.formatObject = this.format ? new this.format(options) : new OpenLayers.Format.GML(options);
204         } else {    
205             OpenLayers.Layer.Markers.prototype.setMap.apply(this, arguments);
206         }    
207     },
208     
209     /** 
210      * Method: moveTo
211      * 
212      * Parameters:
213      * bounds - {<OpenLayers.Bounds>} 
214      * zoomChanged - {Boolean} 
215      * dragging - {Boolean} 
216      */
217     moveTo:function(bounds, zoomChanged, dragging) {
218         if (this.vectorMode) {
219             OpenLayers.Layer.Vector.prototype.moveTo.apply(this, arguments);
220         } else {
221             OpenLayers.Layer.Markers.prototype.moveTo.apply(this, arguments);
222         }    
223
224         // don't load wfs features while dragging, wait for drag end
225         if (dragging) {
226             // TBD try to hide the vector layer while dragging
227             // this.setVisibility(false);
228             // this will probably help for panning performances
229             return false;
230         }
231         
232         if ( zoomChanged ) {
233             if (this.vectorMode) {
234                 this.renderer.clear();
235             }
236         }
237         
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'));
242             
243             if (this.map.getZoom() < this.options.minZoomLevel) {
244                 return null;
245             }
246         }
247         
248         if (bounds == null) {
249             bounds = this.map.getExtent();
250         }
251
252         var firstRendering = (this.tile == null);
253
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));
258
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;
264             var tileBounds = 
265                 new OpenLayers.Bounds(center.lon - (tileWidth / 2),
266                                       center.lat - (tileHeight / 2),
267                                       center.lon + (tileWidth / 2),
268                                       center.lat + (tileHeight / 2));
269
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;
274
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);
278
279             //formulate request url string
280             var url = this.getFullRequestString();
281         
282             var params = null;
283
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;
287             if (filter) {
288                 params = {FILTER: filter};
289             }
290             else {
291                 params = {BBOX: this.encodeBBOX ? tileBounds.toBBOX() 
292                                                     : tileBounds.toArray()};
293             }
294             
295             if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
296                 var projectedBounds = tileBounds.clone();
297                 projectedBounds.transform(this.map.getProjectionObject(), 
298                                           this.projection);
299                 if (!filter){
300                     params.BBOX = this.encodeBBOX ? projectedBounds.toBBOX() 
301                                                 : projectedBounds.toArray();
302                 }
303             }                                  
304
305             url += "&" + OpenLayers.Util.getParameterString(params);
306
307             if (!this.tile) {
308                 this.tile = new OpenLayers.Tile.WFS(this, pos, tileBounds, 
309                                                      url, tileSize);
310                 this.addTileMonitoringHooks(this.tile);
311                 this.tile.draw();
312             } else {
313                 if (this.vectorMode) {
314                     this.destroyFeatures();
315                     this.renderer.clear();
316                 } else {
317                     this.clearMarkers();
318                 }    
319                 this.removeTileMonitoringHooks(this.tile);
320                 this.tile.destroy();
321                 
322                 this.tile = null;
323                 this.tile = new OpenLayers.Tile.WFS(this, pos, tileBounds, 
324                                                      url, tileSize);
325                 this.addTileMonitoringHooks(this.tile);
326                 this.tile.draw();
327             } 
328         }
329     },
330
331     /** 
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).
337      * 
338      * Parameters: 
339      * tile - {<OpenLayers.Tile>}
340      */
341     addTileMonitoringHooks: function(tile) {
342         tile.onLoadStart = function() {
343             //if this is the the layer's current tile, then trigger 
344             // a 'loadstart'
345             if (this == this.layer.tile) {
346                 this.layer.events.triggerEvent("loadstart");
347             }
348         };
349         tile.events.register("loadstart", tile, tile.onLoadStart);
350       
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");
357             }
358         };
359         tile.events.register("loadend", tile, tile.onLoadEnd);
360         tile.events.register("unload", tile, tile.onLoadEnd);
361     },
362     
363     /** 
364      * Method: removeTileMonitoringHooks
365      * This function takes a tile as input and removes the tile hooks 
366      *     that were added in addTileMonitoringHooks()
367      * 
368      * Parameters: 
369      * tile - {<OpenLayers.Tile>}
370      */
371     removeTileMonitoringHooks: function(tile) {
372         tile.unload();
373         tile.events.un({
374             "loadstart": tile.onLoadStart,
375             "loadend": tile.onLoadEnd,
376             "unload": tile.onLoadEnd,
377             scope: tile
378         });
379     },
380
381     /**
382      * Method: onMapResize
383      * Call the onMapResize method of the appropriate parent class. 
384      */
385     onMapResize: function() {
386         if(this.vectorMode) {
387             OpenLayers.Layer.Vector.prototype.onMapResize.apply(this, 
388                                                                 arguments);
389         } else {
390             OpenLayers.Layer.Markers.prototype.onMapResize.apply(this, 
391                                                                  arguments);
392         }
393     },
394     
395     /**
396      * Method: display
397      * Call the display method of the appropriate parent class. 
398      */
399     display: function() {
400         if(this.vectorMode) {
401             OpenLayers.Layer.Vector.prototype.display.apply(this, 
402                                                                 arguments);
403         } else {
404             OpenLayers.Layer.Markers.prototype.display.apply(this, 
405                                                                  arguments);
406         }
407     },
408     
409     /**
410      * APIMethod: mergeNewParams
411      * Modify parameters for the layer and redraw.
412      * 
413      * Parameters:
414      * newParams - {Object}
415      */
416     mergeNewParams:function(newParams) {
417         var upperParams = OpenLayers.Util.upperCaseObject(newParams);
418         var newArguments = [upperParams];
419         return OpenLayers.Layer.HTTPRequest.prototype.mergeNewParams.apply(this, 
420                                                                  newArguments);
421     },
422
423     /**
424      * APIMethod: clone
425      *
426      * Parameters:
427      * obj - {Object} 
428      * 
429      * Returns:
430      * {<OpenLayers.Layer.WFS>} An exact clone of this OpenLayers.Layer.WFS
431      */
432     clone: function (obj) {
433         
434         if (obj == null) {
435             obj = new OpenLayers.Layer.WFS(this.name,
436                                            this.url,
437                                            this.params,
438                                            this.options);
439         }
440
441         //get all additions from superclasses
442         if (this.vectorMode) {
443             obj = OpenLayers.Layer.Vector.prototype.clone.apply(this, [obj]);
444         } else {
445             obj = OpenLayers.Layer.Markers.prototype.clone.apply(this, [obj]);
446         }    
447
448         // copy/set any non-init, non-simple values here
449
450         return obj;
451     },
452
453     /** 
454      * APIMethod: getFullRequestString
455      * combine the layer's url with its params and these newParams. 
456      *   
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.
460      *
461      * Parameters:
462      * newParams - {Object} 
463      * altUrl - {String} Use this as the url instead of the layer's url
464      */
465     getFullRequestString:function(newParams, altUrl) {
466         var projectionCode = this.projection.getCode() || this.map.getProjection();
467         this.params.SRS = (projectionCode == "none") ? null : projectionCode;
468
469         return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(
470                                                     this, arguments);
471     },
472    
473     /**
474      * APIMethod: commit
475      * Write out the data to a WFS server.
476      */
477     commit: function() {
478         if (!this.writer) {
479             var options = {};
480             if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
481                 options.externalProjection = this.projection;
482                 options.internalProjection = this.map.getProjectionObject();
483             }    
484             
485             this.writer = new OpenLayers.Format.WFS(options,this);
486         }
487
488         var data = this.writer.write(this.features);
489
490         OpenLayers.Request.POST({
491             url: this.url,
492             data: data,
493             success: this.commitSuccess,
494             failure: this.commitFailure,
495             scope: this
496         });
497     },
498
499     /**
500      * Method: commitSuccess
501      * Called when the Ajax request returns a response
502      *
503      * Parameters:
504      * response - {XmlNode} from server
505      */
506     commitSuccess: function(request) {
507         var response = request.responseText;
508         if (response.indexOf('SUCCESS') != -1) {
509             this.commitReport(OpenLayers.i18n("commitSuccess", {'response':response}));
510             
511             for(var i = 0; i < this.features.length; i++) {
512                 this.features[i].state = null;
513             }    
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}));
519         }
520     },
521     
522     /**
523      * Method: commitFailure
524      * Called when the Ajax request fails
525      *
526      * Parameters:
527      * response - {XmlNode} from server
528      */
529     commitFailure: function(request) {},
530     
531     /**
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.
536      *
537      * string - {String} reporting string
538      * response - {String} full XML response
539      */
540     commitReport: function(string, response) {
541         OpenLayers.Console.userError(string);
542     },
543
544     
545     /**
546      * APIMethod: refresh
547      * Refreshes all the features of the layer
548      */
549     refresh: function() {
550         if (this.tile) {
551             if (this.vectorMode) {
552                 this.renderer.clear();
553                 this.features.length = 0;
554             } else {   
555                 this.clearMarkers();
556                 this.markers.length = 0;
557             }    
558             this.tile.draw();
559         }
560     },
561     
562     /** 
563      * APIMethod: getDataExtent
564      * Calculates the max extent which includes all of the layer data.
565      * 
566      * Returns:
567      * {<OpenLayers.Bounds>}
568      */
569     getDataExtent: function () {
570         var extent; 
571         //get all additions from superclasses
572         if (this.vectorMode) {
573             extent = OpenLayers.Layer.Vector.prototype.getDataExtent.apply(this);
574         } else {
575             extent = OpenLayers.Layer.Markers.prototype.getDataExtent.apply(this);
576         }    
577
578         return extent;
579     },
580     
581     /** 
582      * APIMethod: setOpacity 
583      * Call the setOpacity method of the appropriate parent class to set the
584      *     opacity.  
585      * 
586      * Parameter: 
587      * opacity - {Float} 
588      */
589     setOpacity: function (opacity) {
590         if (this.vectorMode) {
591             OpenLayers.Layer.Vector.prototype.setOpacity.apply(this, [opacity]);
592         } else {
593             OpenLayers.Layer.Markers.prototype.setOpacity.apply(this, [opacity]);
594         }    
595     },
596
597     CLASS_NAME: "OpenLayers.Layer.WFS"
598 });