OpenLayers OpenLayers

Ticket #1401: WFSTile.patch

File WFSTile.patch, 31.3 kB (added by kleptog, 2 years ago)

Initial patch

  • tests/Layer/test_WFS.html

    old new  
    2828            g_MarkersDestroyed = true; 
    2929        } 
    3030 
     31        var tGridDestroy = OpenLayers.Layer.Grid.prototype.destroy; 
     32        OpenLayers.Layer.Grid.prototype.destroy = function() { 
     33            g_GridDestroyed = true; 
     34        } 
     35 
    3136        var layer = { 
    3237            'vectorMode': true, 
    33             'tile': { 
    34                 'destroy': function() { 
    35                     t.ok(true, "wfs layer's tile is destroyed"); 
    36                 } 
    37             }, 
    38             'ratio': {}, 
    39             'featureClass': {}, 
    40             'format': {}, 
    41             'formatObject': { 
    42                 'destroy': function() { 
    43                     t.ok(true, "wfs layer's format object is destroyed"); 
    44                 } 
    45             }, 
    46             'formatOptions': {}, 
    47             'encodeBBOX': {}, 
    48             'extractAttributes': {} 
    4938        }; 
    5039         
    5140        //this call should set off two tests (destroys for tile and format object) 
    5241        g_VectorDestroyed = null; 
    53         g_MarkersDestroyed = null;       
     42        g_MarkersDestroyed = null; 
     43        g_GridDestroyed = null; 
    5444        OpenLayers.Layer.WFS.prototype.destroy.apply(layer, []);         
    5545 
    5646        t.ok(g_VectorDestroyed && !g_MarkersDestroyed, "when vector mode is set to true, the default vector layer's destroy() method is called"); 
  • lib/OpenLayers/Tile/WFS.js

    old new  
    114114            this.request.transport.abort(); 
    115115            //this.request.destroy(); 
    116116        } 
    117         this.request = OpenLayers.loadURL(this.url, null, this, success); 
     117    var url = this.url; 
     118    if( url == null ) 
     119        url = this.layer.getURL(this.bounds); 
     120        this.request = OpenLayers.loadURL(url, null, this, success, failure); 
    118121    }, 
    119122     
    120123    /** 
     
    133136                doc = OpenLayers.parseXMLString(request.responseText); 
    134137            } 
    135138            if (this.layer.vectorMode) { 
    136                 this.layer.addFeatures(this.layer.formatObject.read(doc)); 
     139                this.features = this.layer.formatObject.read(doc); 
     140                this.layer.addFeatures(this.features); 
    137141            } else { 
    138142                var resultFeatures = OpenLayers.Ajax.getElementsByTagNameNS( 
    139143                    doc, "http://www.opengis.net/gml", "gml", "featureMember" 
     
    173177     *   the local array 
    174178     */ 
    175179    destroyAllFeatures: function() { 
    176         while(this.features.length > 0) { 
    177             var feature = this.features.shift(); 
    178             feature.destroy(); 
     180        if( this.vectorMode ) 
     181        { 
     182            this.layer.removeFeatures(this.features); 
     183        this.features = []; 
    179184        } 
     185        else 
     186        { 
     187            while(this.features.length > 0) { 
     188                var feature = this.features.shift(); 
     189                feature.destroy(); 
     190            } 
     191        } 
    180192    }, 
    181193 
    182194    CLASS_NAME: "OpenLayers.Tile.WFS" 
  • lib/OpenLayers/Layer/WFSTile.js

    old new  
     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/Layer/Vector.js 
     8 * @requires OpenLayers/Layer/Grid.js 
     9 */ 
     10 
     11/** 
     12 * Class: OpenLayers.Layer.WFSTile 
     13 *  
     14 * Inherits from: 
     15 *  - <OpenLayers.Layer.Vector> 
     16 *  - <OpenLayers.Layer.Grid> 
     17 */ 
     18OpenLayers.Layer.WFSTile = OpenLayers.Class( 
     19  OpenLayers.Layer.Vector, OpenLayers.Layer.Grid, { 
     20 
     21    /** 
     22     * APIProperty: isBaseLayer 
     23     * {Boolean} WFS layer is not a base layer by default.  
     24     */ 
     25    isBaseLayer: false, 
     26     
     27    /** 
     28     * APIProperty: tileSize 
     29     * {<OpenLayers.Size>} 
     30     */ 
     31    tileSize: new OpenLayers.Size(512,512), 
     32     
     33    /**   
     34     * Property: DEFAULT_PARAMS 
     35     * {Object} Hashtable of default key/value parameters 
     36     */ 
     37    DEFAULT_PARAMS: { service: "WFS", 
     38                      version: "1.0.0", 
     39                      request: "GetFeature" 
     40                    }, 
     41     
     42    /** 
     43      * APIProperty: format 
     44      * {<OpenLayers.Format>} The format you want the data to be parsed with. 
     45      * Must be passed in the constructor. Should be a class, not an instance. 
     46      */ 
     47    format: null, 
     48 
     49    /**  
     50     * Property: formatObject 
     51     * {<OpenLayers.Format>} Internally created/managed format object, used by 
     52     * the Tile to parse data. 
     53     */ 
     54    formatObject: null, 
     55 
     56    /** 
     57     * APIProperty: formatOptions 
     58     * {Object} Hash of options which should be passed to the format when it is 
     59     * created. Must be passed in the constructor. 
     60     */ 
     61    formatOptions: null,  
     62 
     63    /** 
     64     * APIProperty: encodeBBOX 
     65     * {Boolean} Should the BBOX commas be encoded? The WMS spec says 'no',  
     66     *     but some services want it that way. Default false. 
     67     */ 
     68    encodeBBOX: false, 
     69     
     70    /** 
     71     * APIProperty: extractAttributes  
     72     * {Boolean} Should the WFS layer parse attributes from the retrieved 
     73     *     GML? Defaults to false. If enabled, parsing is slower, but  
     74     *     attributes are available in the attributes property of  
     75     *     layer features. 
     76     */ 
     77    extractAttributes: false, 
     78 
     79    /* Needed to control how the Tiles pass up the features */ 
     80    vectorMode: true, 
     81    buffer: 0, 
     82 
     83    /** 
     84     * Constructor: OpenLayers.Layer.WFSTile 
     85     * 
     86     * Parameters: 
     87     * name - {String}  
     88     * url - {String}  
     89     * params - {Object}  
     90     * options - {Object} Hashtable of extra options to tag onto the layer 
     91     */ 
     92    initialize: function(name, url, params, options) { 
     93        if (options == undefined) { options = {}; }  
     94         
     95        // Turn off error reporting, browsers like Safari may work 
     96        // depending on the setup, and we don't want an unneccesary alert. 
     97        OpenLayers.Util.extend(options, {'reportError': false}); 
     98        var newArguments = []; 
     99        newArguments.push(name, options); 
     100        OpenLayers.Layer.Vector.prototype.initialize.apply(this, newArguments); 
     101        OpenLayers.Layer.Grid.prototype.initialize.apply(this, [name, url, params, options]); 
     102         
     103        if (this.params && this.params.typename && !this.options.typename) { 
     104            this.options.typename = this.params.typename; 
     105        } 
     106         
     107        if (!this.options.geometry_column) { 
     108            this.options.geometry_column = "the_geom"; 
     109        }     
     110         
     111        this.params = params; 
     112        OpenLayers.Util.applyDefaults( 
     113                       this.params,  
     114                       OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS) 
     115                       ); 
     116        this.url = url; 
     117    this.featureIndex = {}; 
     118    },     
     119     
     120 
     121    /** 
     122     * APIMethod: destroy 
     123     */ 
     124    destroy: function() { 
     125        OpenLayers.Layer.Grid.prototype.destroy.apply(this, arguments); 
     126        OpenLayers.Layer.Vector.prototype.destroy.apply(this, arguments); 
     127 
     128        this.ratio = null; 
     129        this.format = null; 
     130 
     131        if (this.formatObject && this.formatObject.destroy) { 
     132            this.formatObject.destroy(); 
     133        } 
     134        this.formatObject = null; 
     135         
     136        this.formatOptions = null; 
     137        this.encodeBBOX = null; 
     138        this.extractAttributes = null; 
     139    }, 
     140     
     141    /** 
     142     * Method: setMap 
     143     *  
     144     * Parameters: 
     145     * map - {<OpenLayers.Map>}  
     146     */ 
     147    setMap: function(map) { 
     148          OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments); 
     149          OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments); 
     150           
     151          var options = { 
     152            'extractAttributes': this.extractAttributes 
     153          }; 
     154           
     155          OpenLayers.Util.extend(options, this.formatOptions); 
     156          if (this.map && !this.projection.equals(this.map.getProjectionObject())) { 
     157              options.externalProjection = this.projection; 
     158              options.internalProjection = this.map.getProjectionObject(); 
     159          }     
     160           
     161          this.formatObject = this.format ? new this.format(options) : new OpenLayers.Format.GML(options); 
     162    }, 
     163     
     164    /**  
     165     * Method: moveTo 
     166     *  
     167     * Parameters: 
     168     * bounds - {<OpenLayers.Bounds>}  
     169     * zoomChanged - {Boolean}  
     170     * dragging - {Boolean}  
     171     */ 
     172    moveTo:function(bounds, zoomChanged, dragging) { 
     173        OpenLayers.Layer.Vector.prototype.moveTo.apply(this, arguments); 
     174        OpenLayers.Layer.Grid.prototype.moveTo.apply(this, arguments); 
     175    }, 
     176 
     177    /**  
     178     * Method: addTileMonitoringHooks 
     179     * This function takes a tile as input and adds the appropriate hooks to  
     180     *     the tile so that the layer can keep track of the loading tile 
     181     *     (making sure to check that the tile is always the layer's current 
     182     *     tile before taking any action). 
     183     *  
     184     * Parameters:  
     185     * tile - {<OpenLayers.Tile>} 
     186     */ 
     187    addTileMonitoringHooks: function(tile) { 
     188        tile.onLoadStart = function() { 
     189            //if this is the the layer's current tile, then trigger  
     190            // a 'loadstart' 
     191            if (this == this.layer.tile) { 
     192                this.layer.events.triggerEvent("loadstart"); 
     193            } 
     194        }; 
     195        tile.events.register("loadstart", tile, tile.onLoadStart); 
     196       
     197        tile.onLoadEnd = function() { 
     198            //if this is the the layer's current tile, then trigger  
     199            // a 'tileloaded' and 'loadend' 
     200            if (this == this.layer.tile) { 
     201                this.layer.events.triggerEvent("tileloaded"); 
     202                this.layer.events.triggerEvent("loadend"); 
     203            } 
     204        }; 
     205        tile.events.register("loadend", tile, tile.onLoadEnd); 
     206    }, 
     207 
     208    /** 
     209     * Method: addTile 
     210     * addTile creates a tile, initializes it, and adds it to the layer div. 
     211     * 
     212     * Parameters: 
     213     * bounds - {<OpenLayers.Bounds>} 
     214     * 
     215     * Returns: 
     216     * {<OpenLayers.Tile.Image>} The added OpenLayers.Tile.Image 
     217     */ 
     218    addTile:function(bounds,position) { 
     219        return new OpenLayers.Tile.WFS(this, position, bounds, 
     220                                         null, this.tileSize); 
     221    }, 
     222     
     223    /**  
     224     * Method: removeTileMonitoringHooks 
     225     * This function takes a tile as input and removes the tile hooks  
     226     *     that were added in addTileMonitoringHooks() 
     227     *  
     228     * Parameters:  
     229     * tile - {<OpenLayers.Tile>} 
     230     */ 
     231    removeTileMonitoringHooks: function(tile) { 
     232        tile.events.unregister("loadstart", tile, tile.onLoadStart); 
     233        tile.events.unregister("loadend", tile, tile.onLoadEnd); 
     234    }, 
     235 
     236    /** 
     237     * Method: onMapResize 
     238     * Call the onMapResize method of the appropriate parent class.  
     239     */ 
     240    onMapResize: function() { 
     241        OpenLayers.Layer.Vector.prototype.onMapResize.apply(this,  
     242                                                                arguments); 
     243    }, 
     244     
     245    /** 
     246     * APIMethod: mergeNewParams 
     247     * Modify parameters for the layer and redraw. 
     248     *  
     249     * Parameters: 
     250     * newParams - {Object} 
     251     */ 
     252    mergeNewParams:function(newParams) { 
     253        var upperParams = OpenLayers.Util.upperCaseObject(newParams); 
     254        var newArguments = [upperParams]; 
     255        OpenLayers.Layer.HTTPRequest.prototype.mergeNewParams.apply(this,  
     256                                                                 newArguments); 
     257    }, 
     258 
     259    /** 
     260     * APIMethod: clone 
     261     * 
     262     * Parameters: 
     263     * obj - {Object}  
     264     *  
     265     * Returns: 
     266     * {<OpenLayers.Layer.WFSTile>} An exact clone of this OpenLayers.Layer.WFS 
     267     */ 
     268    clone: function (obj) { 
     269         
     270        if (obj == null) { 
     271            obj = new OpenLayers.Layer.WFSTile(this.name, 
     272                                           this.url, 
     273                                           this.params, 
     274                                           this.options); 
     275        } 
     276 
     277        //get all additions from superclasses 
     278        obj = OpenLayers.Layer.Vector.prototype.clone.apply(this, [obj]); 
     279 
     280        // copy/set any non-init, non-simple values here 
     281 
     282        return obj; 
     283    }, 
     284 
     285    /**  
     286     * APIMethod: getFullRequestString 
     287     * combine the layer's url with its params and these newParams.  
     288     *    
     289     *    Add the SRS parameter from 'projection' -- this is probably 
     290     *     more eloquently done via a setProjection() method, but this  
     291     *     works for now and always. 
     292     * 
     293     * Parameters: 
     294     * newParams - {Object}  
     295     * altUrl - {String} Use this as the url instead of the layer's url 
     296     */ 
     297    getFullRequestString:function(newParams, altUrl) { 
     298        var projectionCode = this.formatOptions.externalProjection ? this.formatOptions.externalProjection.projCode : this.map.getProjection(); 
     299        this.params.SRS = (projectionCode == "none") ? null : projectionCode; 
     300 
     301        return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply( 
     302                                                    this, arguments); 
     303    }, 
     304 
     305    getURL: function(bounds) { 
     306        //formulate request url string 
     307        var url = this.getFullRequestString(); 
     308 
     309        if( this.formatOptions.externalProjection ) 
     310            bounds.transform( this.formatOptions.internalProjection, this.formatOptions.externalProjection ); 
     311 
     312        var params = {BBOX: this.encodeBBOX ? bounds.toBBOX() 
     313                                            : bounds.toArray()}; 
     314        url += "&" + OpenLayers.Util.getParameterString(params); 
     315 
     316    return url; 
     317    }, 
     318    
     319    /* We override the addFeatures here to index them by fid. If we find 
     320     * them we just increment the ref count and skip it. */ 
     321    addFeatures: function(features, options) { 
     322        if (!(features instanceof Array)) { 
     323            features = [features]; 
     324        } 
     325        var newfeatures = []; 
     326 
     327        for (var i = 0; i < features.length; i++) { 
     328            var feature = features[i]; 
     329             
     330            if( feature.fid == null ) 
     331                newfeatures.push(feature); 
     332            else if( this.featureIndex[feature.fid] != null ) 
     333                this.featureIndex[feature.fid][0]++; 
     334            else 
     335            { 
     336                this.featureIndex[feature.fid] = [1,feature]; 
     337                newfeatures.push(feature); 
     338            } 
     339        } 
     340        if( newfeatures.length ) 
     341            OpenLayers.Layer.Vector.prototype.addFeatures.apply(this, [newfeatures, options]); 
     342    }, 
     343 
     344    removeFeatures: function(features, options) { 
     345        if (!(features instanceof Array)) { 
     346            features = [features]; 
     347        } 
     348        var oldfeatures = []; 
     349 
     350        for (var i = 0; i < features.length; i++) { 
     351            var feature = features[i]; 
     352             
     353            if( feature.fid == null || this.featureIndex[feature.fid] == null ) 
     354                newfeatures.push(feature); 
     355            else if( this.featureIndex[feature.fid][0] > 1 ) 
     356                this.featureIndex[feature.fid][0]--; 
     357            else 
     358            { 
     359                oldfeatures.push(this.featureIndex[feature.fid][1]); 
     360                delete this.featureIndex[feature.fid]; 
     361            } 
     362        } 
     363        if( oldfeatures.length ) 
     364            OpenLayers.Layer.Vector.prototype.removeFeatures.apply(this, [oldfeatures, options]); 
     365    }, 
     366 
     367    /** 
     368     * APIMethod: commit 
     369     * Write out the data to a WFS server. 
     370     */ 
     371    commit: function() { 
     372        if (!this.writer) { 
     373            this.writer = new OpenLayers.Format.WFS({},this); 
     374        } 
     375 
     376        var data = this.writer.write(this.features); 
     377         
     378        var url = this.url; 
     379        if (OpenLayers.ProxyHost && 
     380            OpenLayers.String.startsWith(this.url, "http")) { 
     381            url = OpenLayers.ProxyHost + escape(this.url); 
     382        } 
     383 
     384        var success = OpenLayers.Function.bind(this.commitSuccess, this); 
     385 
     386        var failure = OpenLayers.Function.bind(this.commitFailure, this); 
     387         
     388        data = OpenLayers.Ajax.serializeXMLToString(data); 
     389         
     390        // from prototype.js 
     391        new OpenLayers.Ajax.Request(url,  
     392                         {   method: 'post',  
     393                             postBody: data, 
     394                             onComplete: success,  
     395                             onFailure: failure 
     396                          } 
     397                         ); 
     398    }, 
     399 
     400    /** 
     401     * Method: commitSuccess 
     402     * Called when the Ajax request returns a response 
     403     * 
     404     * Parameters: 
     405     * response - {XmlNode} from server 
     406     */ 
     407    commitSuccess: function(request) { 
     408        var response = request.responseText; 
     409        if (response.indexOf('SUCCESS') != -1) { 
     410            this.commitReport('WFS Transaction: SUCCESS', response); 
     411             
     412            for(var i = 0; i < this.features.length; i++) { 
     413                this.features[i].state = null; 
     414            }     
     415            // TBD redraw the layer or reset the state of features 
     416            // foreach features: set state to null 
     417        } else if (response.indexOf('FAILED') != -1 || 
     418            response.indexOf('Exception') != -1) { 
     419            this.commitReport('WFS Transaction: FAILED', response); 
     420        } 
     421    }, 
     422     
     423    /** 
     424     * Method: commitFailure 
     425     * Called when the Ajax request fails 
     426     * 
     427     * Parameters: 
     428     * response - {XmlNode} from server 
     429     */ 
     430    commitFailure: function(request) {}, 
     431     
     432    /** 
     433     * APIMethod: commitReport  
     434     * Called with a 'success' message if the commit succeeded, otherwise 
     435     *     a failure message, and the full request text as a second parameter. 
     436     *     Override this function to provide custom transaction reporting. 
     437     * 
     438     * string - {String} reporting string 
     439     * response - {String} full XML response 
     440     */ 
     441    commitReport: function(string, response) { 
     442        alert(string); 
     443    }, 
     444 
     445     
     446    /** 
     447     * APIMethod: refresh 
     448     * Refreshes all the features of the layer 
     449     */ 
     450    refresh: function() { 
     451        /* TODO: What shoud happen here? */ 
     452    }, 
     453 
     454    CLASS_NAME: "OpenLayers.Layer.WFSTile" 
     455}); 
  • lib/OpenLayers/Layer/WFS.js

    old new  
    66/** 
    77 * @requires OpenLayers/Layer/Vector.js 
    88 * @requires OpenLayers/Layer/Markers.js 
     9 * @requires OpenLayers/Layer/Grid.js 
    910 */ 
    1011 
    1112/** 
     
    1415 * Inherits from: 
    1516 *  - <OpenLayers.Layer.Vector> 
    1617 *  - <OpenLayers.Layer.Markers> 
     18 *  - <OpenLayers.Layer.Grid> 
    1719 */ 
    1820OpenLayers.Layer.WFS = OpenLayers.Class( 
    19   OpenLayers.Layer.Vector, OpenLayers.Layer.Markers,
     21  OpenLayers.Layer.Vector, OpenLayers.Layer.Markers, OpenLayers.Layer.Grid,
    2022 
    2123    /** 
    2224     * APIProperty: isBaseLayer 
     
    3133    tile: null,     
    3234     
    3335    /** 
     36     * Property: tileSize 
     37     * {<OpenLayers.Size>} 
     38     */ 
     39    tileSize: new OpenLayers.Size(512,512),     
     40     
     41    /** 
    3442     * APIProperty: ratio 
    3543     * {Float} the ratio of image/tile size to map size (this is the untiled 
    3644     *     buffer) 
    3745     */ 
    3846    ratio: 2, 
     47    buffer: 0, 
     48    singleTile: true, 
    3949 
    4050    /**   
    4151     * Property: DEFAULT_PARAMS 
     
    129139            OpenLayers.Layer.Markers.prototype.initialize.apply(this,  
    130140                                                                newArguments); 
    131141        } 
     142        OpenLayers.Layer.Grid.prototype.initialize.apply(this, [name, url, params, options]); 
    132143         
    133144        if (this.params && this.params.typename && !this.options.typename) { 
    134145            this.options.typename = this.params.typename; 
     
    144155                       OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS) 
    145156                       ); 
    146157        this.url = url; 
     158    this.featureIndex = {}; 
    147159    },     
    148160     
    149161 
     
    151163     * APIMethod: destroy 
    152164     */ 
    153165    destroy: function() { 
     166        OpenLayers.Layer.Grid.prototype.destroy.apply(this, arguments); 
    154167        if (this.vectorMode) { 
    155168            OpenLayers.Layer.Vector.prototype.destroy.apply(this, arguments); 
    156169        } else {     
    157170            OpenLayers.Layer.Markers.prototype.destroy.apply(this, arguments); 
    158171        }     
    159         if (this.tile) { 
    160             this.tile.destroy(); 
    161         } 
    162         this.tile = null; 
    163172 
    164173        this.ratio = null; 
    165174        this.featureClass = null; 
     
    174183        this.vectorMode = null; 
    175184        this.encodeBBOX = null; 
    176185        this.extractAttributes = null; 
     186        this.featureIndex = null; 
    177187    }, 
    178188     
    179189    /** 
     
    200210        } else {     
    201211            OpenLayers.Layer.Markers.prototype.setMap.apply(this, arguments); 
    202212        }     
     213        OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments); 
    203214    }, 
    204215     
    205216    /**  
     
    211222     * dragging - {Boolean}  
    212223     */ 
    213224    moveTo:function(bounds, zoomChanged, dragging) { 
     225        OpenLayers.Layer.Grid.prototype.moveTo.apply(this, arguments); 
    214226        if (this.vectorMode) { 
    215227            OpenLayers.Layer.Vector.prototype.moveTo.apply(this, arguments); 
    216228        } else { 
    217229            OpenLayers.Layer.Markers.prototype.moveTo.apply(this, arguments); 
    218230        }     
    219  
    220         // don't load wfs features while dragging, wait for drag end 
    221         if (dragging) { 
    222             // TBD try to hide the vector layer while dragging 
    223             // this.setVisibility(false); 
    224             // this will probably help for panning performances 
    225             return false; 
    226         } 
    227          
    228         if ( zoomChanged ) { 
    229             if (this.vectorMode) { 
    230                 this.renderer.clear(); 
    231             } 
    232         } 
    233          
    234     //DEPRECATED - REMOVE IN 3.0 
    235         // don't load data if current zoom level doesn't match 
    236         if (this.options.minZoomLevel) { 
    237              
    238             var err = "The minZoomLevel property is only intended for use " + 
    239                     "with the FixedZoomLevels-descendent layers. That this " + 
    240                     "wfs layer checks for minZoomLevel is a relic of the" + 
    241                     "past. We cannot, however, remove it without possibly " + 
    242                     "breaking OL based applications that may depend on it." + 
    243                     " Therefore we are deprecating it -- the minZoomLevel " + 
    244                     "check below will be removed at 3.0. Please instead " + 
    245                     "use min/max resolution setting as described here: " + 
    246                     "http://trac.openlayers.org/wiki/SettingZoomLevels"; 
    247             OpenLayers.Console.warn(err); 
    248              
    249             if (this.map.getZoom() < this.options.minZoomLevel) { 
    250                 return null; 
    251             } 
    252         } 
    253          
    254         if (bounds == null) { 
    255             bounds = this.map.getExtent(); 
    256         } 
    257  
    258         var firstRendering = (this.tile == null); 
    259  
    260         //does the new bounds to which we need to move fall outside of the  
    261         // current tile's bounds? 
    262         var outOfBounds = (!firstRendering && 
    263                            !this.tile.bounds.containsBounds(bounds)); 
    264  
    265         if (zoomChanged || firstRendering || (!dragging && outOfBounds)) { 
    266             //determine new tile bounds 
    267             var center = bounds.getCenterLonLat(); 
    268             var tileWidth = bounds.getWidth() * this.ratio; 
    269             var tileHeight = bounds.getHeight() * this.ratio; 
    270             var tileBounds =  
    271                 new OpenLayers.Bounds(center.lon - (tileWidth / 2), 
    272                                       center.lat - (tileHeight / 2), 
    273                                       center.lon + (tileWidth / 2), 
    274                                       center.lat + (tileHeight / 2)); 
    275  
    276             //determine new tile size 
    277             var tileSize = this.map.getSize(); 
    278             tileSize.w = tileSize.w * this.ratio; 
    279             tileSize.h = tileSize.h * this.ratio; 
    280  
    281             //determine new position (upper left corner of new bounds) 
    282             var ul = new OpenLayers.LonLat(tileBounds.left, tileBounds.top); 
    283             var pos = this.map.getLayerPxFromLonLat(ul); 
    284  
    285             //formulate request url string 
    286             var url = this.getFullRequestString(); 
    287          
    288             var params = {BBOX: this.encodeBBOX ? tileBounds.toBBOX()  
    289                                                 : tileBounds.toArray()}; 
    290             url += "&" + OpenLayers.Util.getParameterString(params); 
    291  
    292             if (!this.tile) { 
    293                 this.tile = new OpenLayers.Tile.WFS(this, pos, tileBounds,  
    294                                                      url, tileSize); 
    295                 this.addTileMonitoringHooks(this.tile); 
    296                 this.tile.draw(); 
    297             } else { 
    298                 if (this.vectorMode) { 
    299                     this.destroyFeatures(); 
    300                     this.renderer.clear(); 
    301                 } else { 
    302                     this.clearMarkers(); 
    303                 }     
    304                 this.removeTileMonitoringHooks(this.tile); 
    305                 this.tile.destroy(); 
    306                  
    307                 this.tile = null; 
    308                 this.tile = new OpenLayers.Tile.WFS(this, pos, tileBounds,  
    309                                                      url, tileSize); 
    310                 this.addTileMonitoringHooks(this.tile); 
    311                 this.tile.draw(); 
    312             }  
    313         } 
    314231    }, 
    315232 
    316     /**  
    317      * Method: addTileMonitoringHooks 
    318      * This function takes a tile as input and adds the appropriate hooks to  
    319      *     the tile so that the layer can keep track of the loading tile 
    320      *     (making sure to check that the tile is always the layer's current 
    321      *     tile before taking any action). 
    322      *  
    323      * Parameters:  
    324      * tile - {<OpenLayers.Tile>} 
    325      */ 
    326     addTileMonitoringHooks: function(tile) { 
    327         tile.onLoadStart = function() { 
    328             //if this is the the layer's current tile, then trigger  
    329             // a 'loadstart' 
    330             if (this == this.layer.tile) { 
    331                 this.layer.events.triggerEvent("loadstart"); 
    332             } 
    333         }; 
    334         tile.events.register("loadstart", tile, tile.onLoadStart); 
    335        
    336         tile.onLoadEnd = function() { 
    337             //if this is the the layer's current tile, then trigger  
    338             // a 'tileloaded' and 'loadend' 
    339             if (this == this.layer.tile) { 
    340                 this.layer.events.triggerEvent("tileloaded"); 
    341                 this.layer.events.triggerEvent("loadend"); 
    342             } 
    343         }; 
    344         tile.events.register("loadend", tile, tile.onLoadEnd); 
    345     }, 
    346      
    347     /**  
    348      * Method: removeTileMonitoringHooks 
    349      * This function takes a tile as input and removes the tile hooks  
    350      *     that were added in addTileMonitoringHooks() 
    351      *  
    352      * Parameters:  
    353      * tile - {<OpenLayers.Tile>} 
    354      */ 
    355     removeTileMonitoringHooks: function(tile) { 
    356         tile.events.un({ 
    357             "loadstart": tile.onLoadStart, 
    358             "loadend": tile.onLoadEnd, 
    359             scope: tile 
    360         }); 
    361     }, 
    362  
    363233    /** 
    364234     * Method: onMapResize 
    365235     * Call the onMapResize method of the appropriate parent class.  
    366236     */ 
    367237    onMapResize: function() { 
     238        OpenLayers.Layer.Grid.prototype.onMapResize.apply(this, arguments); 
    368239        if(this.vectorMode) { 
    369240            OpenLayers.Layer.Vector.prototype.onMapResize.apply(this,  
    370241                                                                arguments); 
     
    412283        } else { 
    413284            obj = OpenLayers.Layer.Markers.prototype.clone.apply(this, [obj]); 
    414285        }     
     286        obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]); 
    415287 
    416288        // copy/set any non-init, non-simple values here 
    417289 
    418290        return obj; 
    419291    }, 
    420292 
     293    /** 
     294     * Method: addTile 
     295     * addTile creates a tile, initializes it, and adds it to the layer div. 
     296     * 
     297     * Parameters: 
     298     * bounds - {<OpenLayers.Bounds>} 
     299     * 
     300     * Returns: 
     301     * {<OpenLayers.Tile.Image>} The added OpenLayers.Tile.Image 
     302     */ 
     303    addTile:function(bounds,position) { 
     304        return new OpenLayers.Tile.WFS(this, position, bounds, 
     305                                         null, this.tileSize); 
     306    }, 
     307     
    421308    /**  
    422309     * APIMethod: getFullRequestString 
    423310     * combine the layer's url with its params and these newParams.  
     
    431318     * altUrl - {String} Use this as the url instead of the layer's url 
    432319     */ 
    433320    getFullRequestString:function(newParams, altUrl) { 
    434         var projectionCode = this.map.getProjection(); 
     321        var projectionCode = (this.formatOptions && this.formatOptions.externalProjection) ? this.formatOptions.externalProjection.projCode : this.map.getProjection(); 
    435322        this.params.SRS = (projectionCode == "none") ? null : projectionCode; 
    436323 
    437324        return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply( 
    438325                                                    this, arguments); 
    439326    }, 
    440327    
     328    getURL: function(bounds) { 
     329        //formulate request url string 
     330        var url = this.getFullRequestString(); 
     331 
     332        if( this.formatOptions.externalProjection ) 
     333            bounds = bounds.clone().transform( this.formatOptions.internalProjection, this.formatOptions.externalProjection ); 
     334 
     335        var params = {BBOX: this.encodeBBOX ? bounds.toBBOX() 
     336                                            : bounds.toArray()}; 
     337        url += "&" + OpenLayers.Util.getParameterString(params); 
     338 
     339    return url; 
     340    }, 
     341    
     342    /* We override the addFeatures here to index them by fid. If we find 
     343     * them we just increment the ref count and skip it. */ 
     344    addFeatures: function(features, options) { 
     345        if (!(features instanceof Array)) { 
     346            features = [features]; 
     347        } 
     348        var newfeatures = []; 
     349 
     350        for (var i = 0; i < features.length; i++) { 
     351            var feature = features[i]; 
     352             
     353            if( feature.fid == null ) 
     354                newfeatures.push(feature); 
     355            else if( this.featureIndex[feature.fid] != null ) 
     356                this.featureIndex[feature.fid][0]++; 
     357            else 
     358            { 
     359                this.featureIndex[feature.fid] = [1,feature]; 
     360                newfeatures.push(feature); 
     361            } 
     362        } 
     363        if( newfeatures.length ) 
     364            OpenLayers.Layer.Vector.prototype.addFeatures.apply(this, [newfeatures, options]); 
     365    }, 
     366 
     367    removeFeatures: function(features, options) { 
     368        if (!(features instanceof Array)) { 
     369            features = [features]; 
     370        } 
     371        var oldfeatures = []; 
     372 
     373        for (var i = 0; i < features.length; i++) { 
     374            var feature = features[i]; 
     375             
     376            if( feature.fid == null || this.featureIndex[feature.fid] == null ) 
     377                newfeatures.push(feature); 
     378            else if( this.featureIndex[feature.fid][0] > 1 ) 
     379                this.featureIndex[feature.fid][0]--; 
     380            else 
     381            { 
     382                oldfeatures.push(this.featureIndex[feature.fid][1]); 
     383                delete this.featureIndex[feature.fid]; 
     384            } 
     385        } 
     386        if( oldfeatures.length ) 
     387            OpenLayers.Layer.Vector.prototype.removeFeatures.apply(this, [oldfeatures, options]); 
     388    }, 
     389 
    441390    /** 
    442391     * APIMethod: commit 
    443392     * Write out the data to a WFS server. 
     
    522471     * Refreshes all the features of the layer 
    523472     */ 
    524473    refresh: function() { 
    525         if (this.tile) { 
    526             if (this.vectorMode) { 
    527                 this.renderer.clear(); 
    528                 this.features.length = 0; 
    529             } else {    
    530                 this.clearMarkers(); 
    531                 this.markers.length = 0; 
    532             }     
    533             this.tile.draw(); 
    534         } 
     474        // TODO: what is this function for? 
    535475    }, 
    536476 
    537477    CLASS_NAME: "OpenLayers.Layer.WFS"