diff --git a/examples/mobile-base.js b/examples/mobile-base.js index 5440f932e2..6d8c35b1d8 100644 --- a/examples/mobile-base.js +++ b/examples/mobile-base.js @@ -42,6 +42,7 @@ var init = function (onSelectFeatureFunction) { theme: null, projection: sm, numZoomLevels: 18, + tileManager: new OpenLayers.TileManager(), controls: [ new OpenLayers.Control.Attribution(), new OpenLayers.Control.TouchNavigation({ diff --git a/examples/mobile-wmts-vienna.js b/examples/mobile-wmts-vienna.js index 05a97e3a66..ab593e04ce 100644 --- a/examples/mobile-wmts-vienna.js +++ b/examples/mobile-wmts-vienna.js @@ -99,6 +99,7 @@ var map; maxExtent: [-20037508.34, -20037508.34, 20037508.34, 20037508.34], maxResolution: 156543.0339, numZoomLevels: 20, + tileManager: new OpenLayers.TileManager(), controls: [ new OpenLayers.Control.Navigation({ mouseWheelOptions: { diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js index d57fb362b0..aeb7e90a75 100644 --- a/lib/OpenLayers.js +++ b/lib/OpenLayers.js @@ -400,6 +400,7 @@ "OpenLayers/Lang.js", "OpenLayers/Lang/en.js", "OpenLayers/Spherical.js", + "OpenLayers/TileManager.js", "OpenLayers/WPSClient.js", "OpenLayers/WPSProcess.js" ]; // etc. diff --git a/lib/OpenLayers/Layer/Bing.js b/lib/OpenLayers/Layer/Bing.js index 638891dadb..06711b6a99 100644 --- a/lib/OpenLayers/Layer/Bing.js +++ b/lib/OpenLayers/Layer/Bing.js @@ -197,20 +197,12 @@ OpenLayers.Layer.Bing = OpenLayers.Class(OpenLayers.Layer.XYZ, { res.zoomMax + 1 - res.zoomMin, this.numZoomLevels ) }, true); + if (!this.isBaseLayer) { + this.redraw(); + } this.updateAttribution(); }, - - /** - * Method: drawTileFromQueue - * Draws the first tile from the tileQueue, and unqueues that tile - */ - drawTileFromQueue: function() { - // don't start working on the queue before we have a url from initLayer - if (this.url) { - OpenLayers.Layer.XYZ.prototype.drawTileFromQueue.apply(this, arguments); - } - }, - + /** * Method: getURL * @@ -218,6 +210,9 @@ OpenLayers.Layer.Bing = OpenLayers.Class(OpenLayers.Layer.XYZ, { * bounds - {} */ getURL: function(bounds) { + if (!this.url) { + return; + } var xyz = this.getXYZ(bounds), x = xyz.x, y = xyz.y, z = xyz.z; var quadDigits = []; for (var i = z; i > 0; --i) { diff --git a/lib/OpenLayers/Layer/Grid.js b/lib/OpenLayers/Layer/Grid.js index 9356a7c222..edc9bc3ba5 100644 --- a/lib/OpenLayers/Layer/Grid.js +++ b/lib/OpenLayers/Layer/Grid.js @@ -113,14 +113,6 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { */ numLoadingTiles: 0, - /** - * APIProperty: tileLoadingDelay - * {Integer} Number of milliseconds before we shift and load - * tiles when panning. Ignored if is - * true. Default is 85. - */ - tileLoadingDelay: 85, - /** * Property: serverResolutions * {Array(Number}} This property is documented in subclasses as @@ -128,32 +120,6 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { */ serverResolutions: null, - /** - * Property: moveTimerId - * {Number} The id of the timer. - */ - moveTimerId: null, - - /** - * Property: deferMoveGriddedTiles - * {Function} A function that defers execution of by - * . If is true, this - * is null and unused. - */ - deferMoveGriddedTiles: null, - - /** - * Property: tileQueueId - * {Number} The id of the animation. - */ - tileQueueId: null, - - /** - * Property: tileQueue - * {Array()} Tiles queued for drawing. - */ - tileQueue: null, - /** * Property: loading * {Boolean} Indicates if tiles are being loaded. @@ -240,7 +206,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { * should not be zero. */ className: null, - + /** * Register a listener for a particular event with the following syntax: * (code) @@ -255,6 +221,9 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { * element - {DOMElement} A reference to layer.events.element. * * Supported event types: + * addtile - Triggered when a tile is added to this layer. Listeners receive + * an object as first argument, which has a tile property that + * references the tile that has been added. * tileloadstart - Triggered when a tile starts loading. Listeners receive * an object as first argument, which has a tile property that * references the tile that starts loading. @@ -269,6 +238,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { * still hidden) if a tile failed to load. Listeners receive an object * as first argument, which has a tile property that references the * tile that could not be loaded. + * retile - Triggered when the layer recreates its tile grid. */ /** @@ -308,18 +278,10 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this, arguments); this.grid = []; - this.tileQueue = []; this._removeBackBuffer = OpenLayers.Function.bind(this.removeBackBuffer, this); this.initProperties(); - if (!OpenLayers.Animation.isNative) { - this.deferMoveGriddedTiles = OpenLayers.Function.bind(function() { - this.moveGriddedTiles(true); - this.moveTimerId = null; - }, this); - } - this.rowSign = this.tileOriginCorner.substr(0, 1) === "t" ? 1 : -1; }, @@ -358,15 +320,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { * map - {} The map. */ removeMap: function(map) { - if (this.moveTimerId !== null) { - window.clearTimeout(this.moveTimerId); - this.moveTimerId = null; - } - this.clearTileQueue(); - if(this.backBufferTimerId !== null) { - window.clearTimeout(this.backBufferTimerId); - this.backBufferTimerId = null; - } + this.removeBackBuffer(); }, /** @@ -388,7 +342,6 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { * destroy() on each of them to kill circular references */ clearGrid:function() { - this.clearTileQueue(); if (this.grid) { for(var iRow=0, len=this.grid.length; iRow} */ initSingleTile: function(bounds) { - this.clearTileQueue(); + this.events.triggerEvent("retile"); //determine new tile bounds var center = bounds.getCenterLonLat(); @@ -1014,7 +920,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { * bounds - {} */ initGriddedTiles:function(bounds) { - this.clearTileQueue(); + this.events.triggerEvent("retile"); // work out mininum number of rows and columns; this is the number of // tiles required to cover the viewport plus at least one for panning @@ -1093,8 +999,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { //shave off exceess rows and colums this.removeExcessTiles(rowidx, colidx); - var resolution = this.getServerResolution(), - immediately = resolution === this.gridResolution; + var resolution = this.getServerResolution(); // store the resolution of the grid this.gridResolution = resolution; @@ -1103,7 +1008,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { return a.distance - b.distance; }); for (var i=0, ii=tileData.length; i} If configured at construction time, the map + * will use the TileManager to queue image requests and to cache tile image + * elements. + */ + tileManager: null, /** * APIProperty: fallThrough @@ -579,6 +587,10 @@ OpenLayers.Map = OpenLayers.Class({ this, this.viewPortDiv, null, this.fallThrough, {includeXY: true} ); + + if (this.tileManager) { + this.tileManager.addMap(this); + } // the layerContainerDiv is the one that holds all the layers id = this.id + "_OpenLayers_Container"; @@ -778,6 +790,11 @@ OpenLayers.Map = OpenLayers.Class({ this.viewPortDiv.parentNode.removeChild(this.viewPortDiv); } this.viewPortDiv = null; + + if (this.tileManager) { + this.tileManager.removeMap(this); + this.tileManager = null; + } if(this.eventListeners) { this.events.un(this.eventListeners); diff --git a/lib/OpenLayers/Tile.js b/lib/OpenLayers/Tile.js index 50a3ae709b..f36204892e 100644 --- a/lib/OpenLayers/Tile.js +++ b/lib/OpenLayers/Tile.js @@ -193,22 +193,23 @@ OpenLayers.Tile = OpenLayers.Class({ * is to call and return the result from . * * Parameters: - * immediately - {Boolean} When e.g. drawing was aborted by returning false - * from a *beforedraw* listener, the queue manager needs to pass true, - * so the tile will not be cleared and immediately be drawn. Otherwise, - * the tile will be cleared and a *beforedraw* event will be fired. + * force - {Boolean} If true, the tile will not be cleared and no beforedraw + * event will be fired. This is used for drawing tiles asynchronously + * after drawing has been cancelled by returning false from a beforedraw + * listener. * * Returns: - * {Boolean} Whether or not the tile should actually be drawn. + * {Boolean} Whether or not the tile should actually be drawn. Returns null + * if a beforedraw listener returned false. */ - draw: function(immediately) { - if (!immediately) { + draw: function(force) { + if (!force) { //clear tile's contents and mark as not drawn this.clear(); } var draw = this.shouldDraw(); - if (draw && !immediately) { - draw = this.events.triggerEvent("beforedraw") !== false; + if (draw && !force && this.events.triggerEvent("beforedraw") === false) { + draw = null; } return draw; }, diff --git a/lib/OpenLayers/Tile/Image.js b/lib/OpenLayers/Tile/Image.js index 76107d09e8..0d2ecba964 100644 --- a/lib/OpenLayers/Tile/Image.js +++ b/lib/OpenLayers/Tile/Image.js @@ -20,6 +20,23 @@ */ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { + /** + * APIProperty: events + * {} An events object that handles all + * events on the tile. + * + * Register a listener for a particular event with the following syntax: + * (code) + * tile.events.register(type, obj, listener); + * (end) + * + * Supported event types (in addition to the events): + * beforeload - Triggered before an image is prepared for loading, when the + * url for the image is known already. Listeners may call on + * the tile instance. If they do so, that image will be used and no new + * one will be created. + */ + /** * APIProperty: url * {String} The URL of the image being requested. No default. Filled in by @@ -146,11 +163,12 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { * Check that a tile should be drawn, and draw it. * * Returns: - * {Boolean} Was a tile drawn? + * {Boolean} Was a tile drawn? Or null if a beforedraw listener returned + * false. */ draw: function() { - var drawn = OpenLayers.Tile.prototype.draw.apply(this, arguments); - if (drawn) { + var shouldDraw = OpenLayers.Tile.prototype.draw.apply(this, arguments); + if (shouldDraw) { // The layer's reproject option is deprecated. if (this.layer != this.layer.map.baseLayer && this.layer.reproject) { // getBoundsFromBaseLayer is defined in deprecated.js. @@ -158,17 +176,17 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { } if (this.isLoading) { //if we're already loading, send 'reload' instead of 'loadstart'. - this._loadEvent = "reload"; + this._loadEvent = "reload"; } else { this.isLoading = true; this._loadEvent = "loadstart"; } - this.positionTile(); this.renderTile(); - } else { + this.positionTile(); + } else if (shouldDraw === false) { this.unload(); } - return drawn; + return shouldDraw; }, /** @@ -177,7 +195,6 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { * position it correctly, and set its url. */ renderTile: function() { - this.layer.div.appendChild(this.getTile()); if (this.layer.async) { // Asynchronous image requests call the asynchronous getURL method // on the layer to fetch an image that covers 'this.bounds'. @@ -278,18 +295,34 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { return this.imgDiv; }, + + /** + * APIMethod: setImage + * Sets the image element for this tile. This method should only be called + * from beforeload listeners. + * + * Parameters + * img - {HTMLImageElement} The image to use for this tile. + */ + setImage: function(img) { + this.imgDiv = img; + }, /** * Method: initImage * Creates the content for the frame on the tile. */ initImage: function() { + this.events.triggerEvent('beforeload'); + this.layer.div.appendChild(this.getTile()); this.events.triggerEvent(this._loadEvent); var img = this.getImage(); if (this.url && img.getAttribute("src") == this.url) { - this.onImageLoad(); + this._loadTimeout = window.setTimeout( + OpenLayers.Function.bind(this.onImageLoad, this), 0 + ); } else { - OpenLayers.Event.stopObservingElement(img); + this.stopLoading(); if (this.crossOriginKeyword) { img.removeAttribute("crossorigin"); } @@ -328,7 +361,7 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { } else { // Remove reference to the image, and leave it to the browser's // caching and garbage collection. - OpenLayers.Event.stopObservingElement(this.imgDiv); + this.stopLoading(); this.imgDiv = null; if (img.parentNode) { img.parentNode.removeChild(img); @@ -378,7 +411,7 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { */ onImageLoad: function() { var img = this.imgDiv; - OpenLayers.Event.stopObservingElement(img); + this.stopLoading(); img.style.visibility = 'inherit'; img.style.opacity = this.layer.opacity; this.isLoading = false; @@ -409,6 +442,16 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { } } }, + + /** + * Method: stopLoading + * Stops a loading sequence so won't be executed. + */ + stopLoading: function() { + OpenLayers.Event.stopObservingElement(this.imgDiv); + window.clearTimeout(this._loadTimeout); + delete this._loadTimeout; + }, /** * APIMethod: getCanvasContext diff --git a/lib/OpenLayers/TileManager.js b/lib/OpenLayers/TileManager.js new file mode 100644 index 0000000000..3805c2a883 --- /dev/null +++ b/lib/OpenLayers/TileManager.js @@ -0,0 +1,404 @@ +/* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Layer/Grid.js + * @requires OpenLayers/Util.js + * @requires OpenLayers/BaseTypes.js + * @requires OpenLayers/BaseTypes/Element.js + */ + +/** + * Class: OpenLayers.TileManager + * Provides queueing of image requests and caching of image elements. + * + * Queueing avoids unnecessary image requests while changing zoom levels + * quickly, and helps improve dragging performance on mobile devices that show + * a lag in dragging when loading of new images starts. and + * are the configuration options to control this behavior. + * + * Caching avoids setting the src on image elements for images that have already + * been used. Several maps can share a TileManager instance, in which case each + * map gets its own tile queue, but all maps share the same tile cache. + */ +OpenLayers.TileManager = OpenLayers.Class({ + + /** + * APIProperty: cacheSize + * {Number} Number of image elements to keep referenced in this instance's + * cache for fast reuse. Default is 256. + */ + cacheSize: 256, + + /** + * APIProperty: moveDelay + * {Number} Delay in milliseconds after a map's move event before loading + * tiles. Default is 100. + */ + moveDelay: 100, + + /** + * APIProperty: zoomDelay + * {Number} Delay in milliseconds after a map's zoomend event before loading + * tiles. Default is 200. + */ + zoomDelay: 200, + + /** + * Property: maps + * {Array()} The maps to manage tiles on. + */ + maps: null, + + /** + * Property: tileQueueId + * {Object} The ids of the loop, keyed by map id. + */ + tileQueueId: null, + + /** + * Property: tileQueue + * {Object(Array())} Tiles queued for drawing, keyed by + * map id. + */ + tileQueue: null, + + /** + * Property: tileCache + * {Object} Cached image elements, keyed by URL. + */ + tileCache: null, + + /** + * Property: tileCacheIndex + * {Array(String)} URLs of cached tiles. First entry is the least recently + * used. + */ + tileCacheIndex: null, + + /** + * Constructor: OpenLayers.TileManager + * Constructor for a new instance. + * + * Parameters: + * options - {Object} Configuration for this instance. + */ + initialize: function(options) { + OpenLayers.Util.extend(this, options); + this.maps = []; + this.tileQueueId = {}; + this.tileQueue = {}; + this.tileCache = {}; + this.tileCacheIndex = []; + }, + + /** + * Method: addMap + * Binds this instance to a map + * + * Parameters: + * map - {} + */ + addMap: function(map) { + if (this._destroyed) { + return; + } + this.maps.push(map); + this.tileQueue[map.id] = []; + for (var i=0, ii=map.layers.length; i} + */ + removeMap: function(map) { + if (this._destroyed) { + return; + } + window.clearTimeout(this.tileQueueId[map.id]); + if (map.layers) { + for (var i=0, ii=map.layers.length; i=0; --i) { + for (j=layer.grid[i].length-1; j>=0; --j) { + tile = layer.grid[i][j]; + this.addTile({tile: tile}); + if (tile.url) { + this.manageTileCache({object: tile}); + } + } + } + } + }, + + /** + * Method: removeLayer + * Handles the map's preremovelayer event + * + * Parameters: + * evt - {Object} The listener argument + */ + removeLayer: function(evt) { + var layer = evt.layer; + if (layer instanceof OpenLayers.Layer.Grid) { + this.clearTileQueue({object: layer}); + if (layer.events) { + layer.events.un({ + addtile: this.addTile, + retile: this.clearTileQueue, + scope: this + }); + } + if (layer.grid) { + var i, j, tile; + for (i=layer.grid.length-1; i>=0; --i) { + for (j=layer.grid[i].length-1; j>=0; --j) { + tile = layer.grid[i][j]; + this.unloadTile({object: tile}); + if (tile.url) { + this.manageTileCache({object: tile}); + } + } + } + } + } + }, + + /** + * Method: updateTimeout + * Applies the or to the loop. + * + * Parameters: + * map - {} The map to update the timeout for + * delay - {Number} The delay to apply + */ + updateTimeout: function(map, delay) { + window.clearTimeout(this.tileQueueId[map.id]); + if (this.tileQueue[map.id].length) { + this.tileQueueId[map.id] = window.setTimeout( + OpenLayers.Function.bind(function() { + this.drawTilesFromQueue(map); + }, this), delay + ); + } + }, + + /** + * Method: addTile + * Listener for the layer's addtile event + * + * Parameters: + * evt - {Object} The listener argument + */ + addTile: function(evt) { + evt.tile.events.on({ + beforedraw: this.queueTileDraw, + beforeload: this.manageTileCache, + loadend: this.addToCache, + unload: this.unloadTile, + scope: this + }); + }, + + /** + * Method: unloadTile + * Listener for the tile's unload event + * + * Parameters: + * evt - {Object} The listener argument + */ + unloadTile: function(evt) { + var tile = evt.object; + tile.events.un({ + beforedraw: this.queueTileDraw, + beforeload: this.manageTileCache, + loadend: this.addToCache, + unload: this.unloadTile, + scope: this + }); + OpenLayers.Util.removeItem(this.tileQueue[tile.layer.map.id], tile); + }, + + /** + * Method: queueTileDraw + * Adds a tile to the queue that will draw it. + * + * Parameters: + * evt - {Object} Listener argument of the tile's beforedraw event + */ + queueTileDraw: function(evt) { + var tile = evt.object; + var queued = false; + var layer = tile.layer; + // queue only if image with same url not cached already + if (layer.url && (layer.async || + !this.tileCache[layer.getURL(tile.bounds)])) { + // add to queue only if not in queue already + var tileQueue = this.tileQueue[layer.map.id]; + if (!~OpenLayers.Util.indexOf(tileQueue, tile)) { + tileQueue.push(tile); + } + queued = true; + } + return !queued; + }, + + /** + * Method: drawTilesFromQueue + * Draws tiles from the tileQueue, and unqueues the tiles + */ + drawTilesFromQueue: function(map) { + var tileQueue = this.tileQueue[map.id]; + while (tileQueue.length) { + tileQueue.shift().draw(true); + } + }, + + /** + * Method: manageTileCache + * Adds, updates, removes and fetches cache entries. + * + * Parameters: + * evt - {Object} Listener argument of the tile's beforeload event + */ + manageTileCache: function(evt) { + var tile = evt.object; + var img = this.tileCache[tile.url]; + // only use image from cache if it is not on a layer already + if (img && (!img.parentNode || + OpenLayers.Element.hasClass(img.parentNode, 'olBackBuffer'))) { + if (tile.layer.backBuffer) { + img.style.opacity = 0; + img.style.visibility = 'hidden'; + } + tile.setImage(img); + // LRU - move tile to the end of the array to mark it as the most + // recently used + OpenLayers.Util.removeItem(this.tileCacheIndex, tile.url); + this.tileCacheIndex.push(tile.url); + } + }, + + /** + * Method: addToCache + * + * Parameters: + * evt - {Object} Listener argument for the tile's loadend event + */ + addToCache: function(evt) { + var tile = evt.object; + if (!this.tileCache[tile.url]) { + if (!OpenLayers.Element.hasClass(tile.imgDiv, 'olImageLoadError')) { + if (this.tileCacheIndex.length >= this.cacheSize) { + delete this.tileCache[this.tileCacheIndex[0]]; + this.tileCacheIndex.shift(); + } + this.tileCache[tile.url] = tile.imgDiv; + this.tileCacheIndex.push(tile.url); + } + } + }, + + /** + * Method: clearTileQueue + * Clears the tile queue from tiles of a specific layer + * + * Parameters: + * evt - {Object} Listener argument of the layer's retile event + */ + clearTileQueue: function(evt) { + var layer = evt.object; + var tileQueue = this.tileQueue[layer.map.id]; + for (var i=tileQueue.length-1; i>=0; --i) { + if (tileQueue[i].layer === layer) { + tileQueue.splice(i, 1); + } + } + }, + + /** + * Method: destroy + */ + destroy: function() { + for (var i=this.maps.length-1; i>=0; --i) { + this.removeMap(this.maps[i]); + } + this.maps = null; + this.tileQueue = null; + this.tileQueueId = null; + this.tileCache = null; + this.tileCacheIndex = null; + this._destroyed = true; + } + +}); \ No newline at end of file diff --git a/notes/2.13.md b/notes/2.13.md index 9de5f7626e..18fc3d833a 100644 --- a/notes/2.13.md +++ b/notes/2.13.md @@ -37,6 +37,20 @@ Corresponding issues/pull requests: # Behavior Changes from Past Releases +## Layer.Grid: Tile queue and tileLoadingDelay changes + +With the introduction of OpenLayers.TileManager, tile queueing has become optional. The default behavior is back to how it was in OpenLayers 2.11. To use a tile queue in 2.13, the map needs to be configured with a tileManager, e.g.: + + var map = new OpenLayers.Map('map', { + tileManager: new OpenLayers.TileManager() + }); + +The tile queue also works differently than before: it no longer loads one tile at a time. Instead, it waits after a zoom or pan, and loads all tiles after a delay. This has the same effect as previously (less burden on the server), but makes use of the browser's request management. The delay can be configured separately for zooming and moving the map, using the `zoomDelay` (default: 200 ms) and `moveDelay` (default: 100 ms) config options of the TileManager. + +The `moveDelay` is the replacement for the `tileLoadingDelay` layer config option, which has been removed. There is no magic any more to only use the delay when requestAnimationFrame is not natively available. + +In general, when targeting mobile devices or when using slow servers or connections for tiled layers, it is recommended to configure the map with a TileManager. + ## window.$ is no longer an alias for OpenLayers.Util.getElement We do no longer create a global variable '$' when such a symbol isn't already diff --git a/tests/Layer/ArcGIS93Rest.html b/tests/Layer/ArcGIS93Rest.html index 6c00732e4e..568dff0326 100644 --- a/tests/Layer/ArcGIS93Rest.html +++ b/tests/Layer/ArcGIS93Rest.html @@ -4,9 +4,6 @@ + + + + + + \ No newline at end of file diff --git a/tests/deprecated/Layer/MapServer/Untiled.html b/tests/deprecated/Layer/MapServer/Untiled.html index c3f9c44fe4..1b1dc94f36 100644 --- a/tests/deprecated/Layer/MapServer/Untiled.html +++ b/tests/deprecated/Layer/MapServer/Untiled.html @@ -5,9 +5,6 @@