From c50aaabd6d6d0c300dba108a92d719fc11d1733a Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Wed, 25 Jul 2012 17:10:11 +0300 Subject: [PATCH] TileLayer refactoring, add loading event, update changelog, closes #177 --- CHANGELOG.md | 4 +- src/layer/tile/TileLayer.js | 112 +++++++++++++++++++++++------------- 2 files changed, 74 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index caa9bd11..7052463a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ Icon API was improved to be more flexible, but one of the changes is backwards-i #### Other breaking API changes * Improved `TileLayer` constructor to interpolate URL template values from options (removed third `urlParams` argument). + * Changed `TileLayer` `scheme: 'tms'` option to `tms: true`. * Replaced ugly control position constants (e.g. `L.Control.Position.TOP_LEFT`) with light strings (`'topleft'`, `'bottomright'`, etc.) * Removed `Map` `locateAndSetView` method (use `locate` with `setView: true` option) * Changed popup `minWidth` and `maxWidth` options to be applied to content element, not the whole popup. @@ -73,6 +74,7 @@ Icon API was improved to be more flexible, but one of the changes is backwards-i * Added `TileLayer` `redraw` method for re-requesting tiles (by [@greeninfo](https://github.com/greeninfo)). [#719](https://github.com/CloudMade/Leaflet/issues/719) * Added `TileLayer` `setUrl` method for dynamically changing the tile URL template. * Added `bringToFront` and `bringToBack` methods to `TileLayer` and vector layers. [#185](https://github.com/CloudMade/Leaflet/issues/185) [#505](https://github.com/CloudMade/Leaflet/issues/505) + * Added `TileLayer` `loading` event that fires when its tiles start to load (thanks to [@lapinos03](https://github.com/lapinos03)). [#177](https://github.com/CloudMade/Leaflet/issues/177) * Added `TileLayer.WMS` `setParams` method for setting WMS parameters at runtime (by [@greeninfo](https://github.com/greeninfo)). [#719](https://github.com/CloudMade/Leaflet/issues/719) * Added `TileLayer.WMS` subdomain support (`{s}` in the url) (by [@greeninfo](https://github.com/greeninfo)). [#735](https://github.com/CloudMade/Leaflet/issues/735) * Added `originalEvent` property to `MouseEvent` (by [@k4](https://github.com/k4)). [#521](https://github.com/CloudMade/Leaflet/pull/521) @@ -118,7 +120,7 @@ Icon API was improved to be more flexible, but one of the changes is backwards-i * Fixed a bug where `TileLayer` `setOpacity` wouldn't work when setting it back to 1. * Fixed a bug where vector layer `setStyle({stroke: false})` wouldn't remove stroke and the same for fill. [#441](https://github.com/CloudMade/Leaflet/issues/441) * Fixed a bug where `Marker` `bindPopup` method wouldn't take `offset` option into account. - * Fixed a bug where `TileLayer` `load` event wasn't fired if some tile didn't load (by [@cfis](https://github.com/cfis)) [#682](https://github.com/CloudMade/Leaflet/pull/682) + * Fixed a bug where `TileLayer` `load` event wasn't fired if some tile didn't load (by [@lapinos03](https://github.com/lapinos03) and [@cfis](https://github.com/cfis)) [#682](https://github.com/CloudMade/Leaflet/pull/682) * Fixed error when removing `GeoJSON` layer. [#685](https://github.com/CloudMade/Leaflet/issues/685) * Fixed error when calling `GeoJSON` `clearLayer` (by [@runderwood](https://github.com/runderwood)). [#617](https://github.com/CloudMade/Leaflet/pull/617) * Fixed a bug where polygons/polylines sometimes throwed an error when making them editable manually (by [@cfis](https://github.com/cfis)). [#669](https://github.com/CloudMade/Leaflet/pull/669) diff --git a/src/layer/tile/TileLayer.js b/src/layer/tile/TileLayer.js index 0f00e310..fbb0c68e 100644 --- a/src/layer/tile/TileLayer.js +++ b/src/layer/tile/TileLayer.js @@ -13,7 +13,7 @@ L.TileLayer = L.Class.extend({ errorTileUrl: '', attribution: '', opacity: 1, - scheme: 'xyz', + tms: false, continuousWorld: false, noWrap: false, zoomOffset: 0, @@ -192,6 +192,7 @@ L.TileLayer = L.Class.extend({ } this._tiles = {}; + this._tilesToLoad = 0; if (this.options.reuseTiles) { this._unusedTiles = []; @@ -234,16 +235,21 @@ L.TileLayer = L.Class.extend({ var queue = [], center = bounds.getCenter(); - var j, i; + var j, i, point; + for (j = bounds.min.y; j <= bounds.max.y; j++) { for (i = bounds.min.x; i <= bounds.max.x; i++) { - if (!((i + ':' + j) in this._tiles)) { - queue.push(new L.Point(i, j)); + point = new L.Point(i, j); + + if (this._tileShouldBeLoaded(point)) { + queue.push(point); } } } - if (queue.length === 0) { return; } + var tilesToLoad = queue.length; + + if (tilesToLoad === 0) { return; } // load tiles in order of their distance to center queue.sort(function (a, b) { @@ -252,16 +258,37 @@ L.TileLayer = L.Class.extend({ var fragment = document.createDocumentFragment(); - this._tilesToLoad = queue.length; + // if its the first batch of tiles to load + if (!this._tilesToLoad) { + this.fire('loading'); + } - var k, len; - for (k = 0, len = this._tilesToLoad; k < len; k++) { - this._addTile(queue[k], fragment); + this._tilesToLoad += tilesToLoad; + + for (i = 0; i < tilesToLoad; i++) { + this._addTile(queue[i], fragment); } this._container.appendChild(fragment); }, + _tileShouldBeLoaded: function (tilePoint) { + if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) { + return false; // already loaded + } + + if (!this.options.continuousWorld) { + var limit = this._getWrapTileNum(); + + if (this.options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit) || + tilePoint.y < 0 || tilePoint.y >= limit) { + return false; // exceeds world bounds + } + } + + return true; + }, + _removeOtherTiles: function (bounds) { var kArr, x, y, key; @@ -299,25 +326,7 @@ L.TileLayer = L.Class.extend({ }, _addTile: function (tilePoint, container) { - var tilePos = this._getTilePos(tilePoint), - zoom = this._map.getZoom(), - key = tilePoint.x + ':' + tilePoint.y, - limit = Math.pow(2, this._getOffsetZoom(zoom)); - - // wrap tile coordinates - if (!this.options.continuousWorld) { - if (!this.options.noWrap) { - tilePoint.x = ((tilePoint.x % limit) + limit) % limit; - } else if (tilePoint.x < 0 || tilePoint.x >= limit) { - this._tilesToLoad--; - return; - } - - if (tilePoint.y < 0 || tilePoint.y >= limit) { - this._tilesToLoad--; - return; - } - } + var tilePos = this._getTilePos(tilePoint); // get unused tile - or create a new tile var tile = this._getTile(); @@ -327,22 +336,24 @@ L.TileLayer = L.Class.extend({ // (other browsers don't currently care) - see debug/hacks/jitter.html for an example L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome); - this._tiles[key] = tile; + this._tiles[tilePoint.x + ':' + tilePoint.y] = tile; - if (this.options.scheme === 'tms') { - tilePoint.y = limit - tilePoint.y - 1; - } - - this._loadTile(tile, tilePoint, zoom); + this._loadTile(tile, tilePoint); if (tile.parentNode !== this._container) { container.appendChild(tile); } }, - _getOffsetZoom: function (zoom) { - var options = this.options; - zoom = options.zoomReverse ? options.maxZoom - zoom : zoom; + _getZoomForUrl: function () { + + var options = this.options, + zoom = this._map.getZoom(); + + if (options.zoomReverse) { + zoom = options.maxZoom - zoom; + } + return zoom + options.zoomOffset; }, @@ -355,15 +366,34 @@ L.TileLayer = L.Class.extend({ // image-specific code (override to implement e.g. Canvas or SVG tile layer) - getTileUrl: function (tilePoint, zoom) { + getTileUrl: function (tilePoint) { + this._adjustTilePoint(tilePoint); + return L.Util.template(this._url, L.Util.extend({ s: this._getSubdomain(tilePoint), - z: this._getOffsetZoom(zoom), + z: this._getZoomForUrl(), x: tilePoint.x, y: tilePoint.y }, this.options)); }, + _getWrapTileNum: function () { + // TODO refactor, limit is not valid for non-standard projections + return Math.pow(2, this._getZoomForUrl()); + }, + + _adjustTilePoint: function (tilePoint) { + // wrap tile coordinates + if (!this.options.continuousWorld && !this.options.noWrap) { + var limit = this._getWrapTileNum(); + tilePoint.x = ((tilePoint.x % limit) + limit) % limit; + } + + if (this.options.tms) { + tilePoint.y = limit - tilePoint.y - 1; + } + }, + _getSubdomain: function (tilePoint) { var index = (tilePoint.x + tilePoint.y) % this.options.subdomains.length; return this.options.subdomains[index]; @@ -397,12 +427,12 @@ L.TileLayer = L.Class.extend({ return tile; }, - _loadTile: function (tile, tilePoint, zoom) { + _loadTile: function (tile, tilePoint) { tile._layer = this; tile.onload = this._tileOnLoad; tile.onerror = this._tileOnError; - tile.src = this.getTileUrl(tilePoint, zoom); + tile.src = this.getTileUrl(tilePoint); }, _tileLoaded: function () {