- var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
-
- if (!noUpdate || tileZoomChanged) {
-
- this._tileZoom = tileZoom;
-
- if (this._abortLoading) {
- this._abortLoading();
- }
-
- this._updateLevels();
- this._resetGrid();
-
- if (tileZoom !== undefined) {
- this._update(center);
- }
-
- if (!noPrune) {
- this._pruneTiles();
- }
-
- // Flag to prevent _updateOpacity from pruning tiles during
- // a zoom anim or a pinch gesture
- this._noPrune = !!noPrune;
- }
-
- this._setZoomTransforms(center, zoom);
- },
-
- _setZoomTransforms: function (center, zoom) {
- for (var i in this._levels) {
- this._setZoomTransform(this._levels[i], center, zoom);
- }
- },
-
- _setZoomTransform: function (level, center, zoom) {
- var scale = this._map.getZoomScale(zoom, level.zoom),
- translate = level.origin.multiplyBy(scale)
- .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
-
- if (L.Browser.any3d) {
- L.DomUtil.setTransform(level.el, translate, scale);
- } else {
- L.DomUtil.setPosition(level.el, translate);
- }
- },
-
- _resetGrid: function () {
- var map = this._map,
- crs = map.options.crs,
- tileSize = this._tileSize = this.getTileSize(),
- tileZoom = this._tileZoom;
-
- var bounds = this._map.getPixelWorldBounds(this._tileZoom);
- if (bounds) {
- this._globalTileRange = this._pxBoundsToTileRange(bounds);
- }
-
- this._wrapX = crs.wrapLng && !this.options.noWrap && [
- Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
- Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
- ];
- this._wrapY = crs.wrapLat && !this.options.noWrap && [
- Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
- Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
- ];
- },
-
- _onMoveEnd: function () {
- if (!this._map || this._map._animatingZoom) { return; }
-
- this._update();
- },
-
- _getTiledPixelBounds: function (center) {
- var map = this._map,
- mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
- scale = map.getZoomScale(mapZoom, this._tileZoom),
- pixelCenter = map.project(center, this._tileZoom).floor(),
- halfSize = map.getSize().divideBy(scale * 2);
-
- return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
- },
-
- // Private method to load tiles in the grid's active zoom level according to map bounds
- _update: function (center) {
- var map = this._map;
- if (!map) { return; }
- var zoom = map.getZoom();
-
- if (center === undefined) { center = map.getCenter(); }
- if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
-
- var pixelBounds = this._getTiledPixelBounds(center),
- tileRange = this._pxBoundsToTileRange(pixelBounds),
- tileCenter = tileRange.getCenter(),
- queue = [],
- margin = this.options.keepBuffer,
- noPruneRange = new L.Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
- tileRange.getTopRight().add([margin, -margin]));
-
- for (var key in this._tiles) {
- var c = this._tiles[key].coords;
- if (c.z !== this._tileZoom || !noPruneRange.contains(L.point(c.x, c.y))) {
- this._tiles[key].current = false;
- }
- }
-
- // _update just loads more tiles. If the tile zoom level differs too much
- // from the map's, let _setView reset levels and prune old tiles.
- if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
-
- // create a queue of coordinates to load tiles from
- for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
- for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
- var coords = new L.Point(i, j);
- coords.z = this._tileZoom;
-
- if (!this._isValidTile(coords)) { continue; }
-
- var tile = this._tiles[this._tileCoordsToKey(coords)];
- if (tile) {
- tile.current = true;
- } else {
- queue.push(coords);
- }
- }
- }
-
- // sort tile queue to load tiles in order of their distance to center
- queue.sort(function (a, b) {
- return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
- });
-
- if (queue.length !== 0) {
- // if it's the first batch of tiles to load
- if (!this._loading) {
- this._loading = true;
- // @event loading: Event
- // Fired when the grid layer starts loading tiles.
- this.fire('loading');
- }
-
- // create DOM fragment to append tiles in one batch
- var fragment = document.createDocumentFragment();
-
- for (i = 0; i < queue.length; i++) {
- this._addTile(queue[i], fragment);
- }
-
- this._level.el.appendChild(fragment);
- }
- },
-
- _isValidTile: function (coords) {
- var crs = this._map.options.crs;
-
- if (!crs.infinite) {
- // don't load tile if it's out of bounds and not wrapped
- var bounds = this._globalTileRange;
- if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
- (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
- }
-
- if (!this.options.bounds) { return true; }
-
- // don't load tile if it doesn't intersect the bounds in options
- var tileBounds = this._tileCoordsToBounds(coords);
- return L.latLngBounds(this.options.bounds).overlaps(tileBounds);
- },
-
- _keyToBounds: function (key) {
- return this._tileCoordsToBounds(this._keyToTileCoords(key));
- },
-
- // converts tile coordinates to its geographical bounds
- _tileCoordsToBounds: function (coords) {
-
- var map = this._map,
- tileSize = this.getTileSize(),
-
- nwPoint = coords.scaleBy(tileSize),
- sePoint = nwPoint.add(tileSize),
-
- nw = map.unproject(nwPoint, coords.z),
- se = map.unproject(sePoint, coords.z);
-
- if (!this.options.noWrap) {
- nw = map.wrapLatLng(nw);
- se = map.wrapLatLng(se);
- }
-
- return new L.LatLngBounds(nw, se);
- },
-
- // converts tile coordinates to key for the tile cache
- _tileCoordsToKey: function (coords) {
- return coords.x + ':' + coords.y + ':' + coords.z;
- },
-
- // converts tile cache key to coordinates
- _keyToTileCoords: function (key) {
- var k = key.split(':'),
- coords = new L.Point(+k[0], +k[1]);
- coords.z = +k[2];
- return coords;
- },
-
- _removeTile: function (key) {
- var tile = this._tiles[key];
- if (!tile) { return; }
-
- L.DomUtil.remove(tile.el);
-
- delete this._tiles[key];
-
- // @event tileunload: TileEvent
- // Fired when a tile is removed (e.g. when a tile goes off the screen).
- this.fire('tileunload', {
- tile: tile.el,
- coords: this._keyToTileCoords(key)
- });
- },
-
- _initTile: function (tile) {
- L.DomUtil.addClass(tile, 'leaflet-tile');
-
- var tileSize = this.getTileSize();
- tile.style.width = tileSize.x + 'px';
- tile.style.height = tileSize.y + 'px';
-
- tile.onselectstart = L.Util.falseFn;
- tile.onmousemove = L.Util.falseFn;
-
- // update opacity on tiles in IE7-8 because of filter inheritance problems
- if (L.Browser.ielt9 && this.options.opacity < 1) {
- L.DomUtil.setOpacity(tile, this.options.opacity);
- }
-
- // without this hack, tiles disappear after zoom on Chrome for Android
- // https://github.com/Leaflet/Leaflet/issues/2078
- if (L.Browser.android && !L.Browser.android23) {
- tile.style.WebkitBackfaceVisibility = 'hidden';
- }
- },
-
- _addTile: function (coords, container) {
- var tilePos = this._getTilePos(coords),
- key = this._tileCoordsToKey(coords);
-
- var tile = this.createTile(this._wrapCoords(coords), L.bind(this._tileReady, this, coords));
-
- this._initTile(tile);
-
- // if createTile is defined with a second argument ("done" callback),
- // we know that tile is async and will be ready later; otherwise
- if (this.createTile.length < 2) {
- // mark tile as ready, but delay one frame for opacity animation to happen
- L.Util.requestAnimFrame(L.bind(this._tileReady, this, coords, null, tile));
- }
-
- L.DomUtil.setPosition(tile, tilePos);
-
- // save tile in cache
- this._tiles[key] = {
- el: tile,
- coords: coords,
- current: true
- };
-
- container.appendChild(tile);
- // @event tileloadstart: TileEvent
- // Fired when a tile is requested and starts loading.
- this.fire('tileloadstart', {
- tile: tile,
- coords: coords
- });
- },
-
- _tileReady: function (coords, err, tile) {
- if (!this._map) { return; }
-
- if (err) {
- // @event tileerror: TileErrorEvent
- // Fired when there is an error loading a tile.
- this.fire('tileerror', {
- error: err,
- tile: tile,
- coords: coords
- });
- }
-
- var key = this._tileCoordsToKey(coords);
-
- tile = this._tiles[key];
- if (!tile) { return; }
-
- tile.loaded = +new Date();
- if (this._map._fadeAnimated) {
- L.DomUtil.setOpacity(tile.el, 0);
- L.Util.cancelAnimFrame(this._fadeFrame);
- this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
- } else {
- tile.active = true;
- this._pruneTiles();
- }
-
- if (!err) {
- L.DomUtil.addClass(tile.el, 'leaflet-tile-loaded');
-
- // @event tileload: TileEvent
- // Fired when a tile loads.
- this.fire('tileload', {
- tile: tile.el,
- coords: coords
- });
- }
-
- if (this._noTilesToLoad()) {
- this._loading = false;
- // @event load: Event
- // Fired when the grid layer loaded all visible tiles.
- this.fire('load');
-
- if (L.Browser.ielt9 || !this._map._fadeAnimated) {
- L.Util.requestAnimFrame(this._pruneTiles, this);
- } else {
- // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
- // to trigger a pruning.
- setTimeout(L.bind(this._pruneTiles, this), 250);
- }
- }
- },
-
- _getTilePos: function (coords) {
- return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
- },
-
- _wrapCoords: function (coords) {
- var newCoords = new L.Point(
- this._wrapX ? L.Util.wrapNum(coords.x, this._wrapX) : coords.x,
- this._wrapY ? L.Util.wrapNum(coords.y, this._wrapY) : coords.y);
- newCoords.z = coords.z;
- return newCoords;
- },
-
- _pxBoundsToTileRange: function (bounds) {
- var tileSize = this.getTileSize();
- return new L.Bounds(
- bounds.min.unscaleBy(tileSize).floor(),
- bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
- },
-
- _noTilesToLoad: function () {
- for (var key in this._tiles) {
- if (!this._tiles[key].loaded) { return false; }
- }
- return true;
- }
-});
-
-// @factory L.gridLayer(options?: GridLayer options)
-// Creates a new instance of GridLayer with the supplied options.
-L.gridLayer = function (options) {
- return new L.GridLayer(options);
-};
-
-
-
-/*
- * @class TileLayer
- * @inherits GridLayer
- * @aka L.TileLayer
- * Used to load and display tile layers on the map. Extends `GridLayer`.
- *
- * @example
- *
- * ```js
- * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
- * ```
- *
- * @section URL template
- * @example
- *
- * A string of the following form:
- *
- * ```
- * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
- * ```
- *
- * `{s}` means one of the available subdomains (used sequentially to help with browser parallel requests per domain limitation; subdomain values are specified in options; `a`, `b` or `c` by default, can be omitted), `{z}` — zoom level, `{x}` and `{y}` — tile coordinates. `{r}` can be used to add @2x to the URL to load retina tiles.
- *
- * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
- *
- * ```
- * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
- * ```
- */
-
-
-L.TileLayer = L.GridLayer.extend({
-
- // @section
- // @aka TileLayer options
- options: {
- // @option minZoom: Number = 0
- // Minimum zoom number.
- minZoom: 0,
-
- // @option maxZoom: Number = 18
- // Maximum zoom number.
- maxZoom: 18,
-
- // @option maxNativeZoom: Number = null
- // Maximum zoom number the tile source has available. If it is specified,
- // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
- // from `maxNativeZoom` level and auto-scaled.
- maxNativeZoom: null,
-
- // @option subdomains: String|String[] = 'abc'
- // Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of strings.
- subdomains: 'abc',
-
- // @option errorTileUrl: String = ''
- // URL to the tile image to show in place of the tile that failed to load.
- errorTileUrl: '',
-
- // @option zoomOffset: Number = 0
- // The zoom number used in tile URLs will be offset with this value.
- zoomOffset: 0,
-
- // @option tms: Boolean = false
- // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
- tms: false,
-
- // @option zoomReverse: Boolean = false
- // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
- zoomReverse: false,
-
- // @option detectRetina: Boolean = false
- // If `true` and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution.
- detectRetina: false,
-
- // @option crossOrigin: Boolean = false
- // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
- crossOrigin: false
- },
-
- initialize: function (url, options) {
-
- this._url = url;
-
- options = L.setOptions(this, options);
-
- // detecting retina displays, adjusting tileSize and zoom levels
- if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
-
- options.tileSize = Math.floor(options.tileSize / 2);
-
- if (!options.zoomReverse) {
- options.zoomOffset++;
- options.maxZoom--;
- } else {
- options.zoomOffset--;
- options.minZoom++;
- }
-
- options.minZoom = Math.max(0, options.minZoom);
- }
-
- if (typeof options.subdomains === 'string') {
- options.subdomains = options.subdomains.split('');
- }
-
- // for https://github.com/Leaflet/Leaflet/issues/137
- if (!L.Browser.android) {
- this.on('tileunload', this._onTileRemove);
- }
- },
-
- // @method setUrl(url: String, noRedraw?: Boolean): this
- // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
- setUrl: function (url, noRedraw) {
- this._url = url;
-
- if (!noRedraw) {
- this.redraw();
- }
- return this;
- },
-
- // @method createTile(coords: Object, done?: Function): HTMLElement
- // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
- // to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done`
- // callback is called when the tile has been loaded.
- createTile: function (coords, done) {
- var tile = document.createElement('img');
-
- L.DomEvent.on(tile, 'load', L.bind(this._tileOnLoad, this, done, tile));
- L.DomEvent.on(tile, 'error', L.bind(this._tileOnError, this, done, tile));
-
- if (this.options.crossOrigin) {
- tile.crossOrigin = '';
- }
-
- /*
- Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
- http://www.w3.org/TR/WCAG20-TECHS/H67
- */
- tile.alt = '';
-
- tile.src = this.getTileUrl(coords);
-
- return tile;
- },
-
- // @section Extension methods
- // @uninheritable
- // Layers extending `TileLayer` might reimplement the following method.
- // @method getTileUrl(coords: Object): String
- // Called only internally, returns the URL for a tile given its coordinates.
- // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
- getTileUrl: function (coords) {
- var data = {
- r: L.Browser.retina ? '@2x' : '',
- s: this._getSubdomain(coords),
- x: coords.x,
- y: coords.y,
- z: this._getZoomForUrl()
- };
- if (this._map && !this._map.options.crs.infinite) {
- var invertedY = this._globalTileRange.max.y - coords.y;
- if (this.options.tms) {
- data['y'] = invertedY;
- }
- data['-y'] = invertedY;
- }
-
- return L.Util.template(this._url, L.extend(data, this.options));
- },
-
- _tileOnLoad: function (done, tile) {
- // For https://github.com/Leaflet/Leaflet/issues/3332
- if (L.Browser.ielt9) {
- setTimeout(L.bind(done, this, null, tile), 0);
- } else {
- done(null, tile);
- }
- },
-
- _tileOnError: function (done, tile, e) {
- var errorUrl = this.options.errorTileUrl;
- if (errorUrl) {
- tile.src = errorUrl;
- }
- done(e, tile);
- },
-
- getTileSize: function () {
- var map = this._map,
- tileSize = L.GridLayer.prototype.getTileSize.call(this),
- zoom = this._tileZoom + this.options.zoomOffset,
- zoomN = this.options.maxNativeZoom;
-
- // increase tile size when overscaling
- return zoomN !== null && zoom > zoomN ?
- tileSize.divideBy(map.getZoomScale(zoomN, zoom)).round() :
- tileSize;
- },
-
- _onTileRemove: function (e) {
- e.tile.onload = null;
- },
-
- _getZoomForUrl: function () {
-
- var options = this.options,
- zoom = this._tileZoom;
-
- if (options.zoomReverse) {
- zoom = options.maxZoom - zoom;
- }
-
- zoom += options.zoomOffset;
-
- return options.maxNativeZoom !== null ? Math.min(zoom, options.maxNativeZoom) : zoom;
- },
-
- _getSubdomain: function (tilePoint) {
- var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
- return this.options.subdomains[index];
- },
-
- // stops loading all tiles in the background layer
- _abortLoading: function () {
- var i, tile;
- for (i in this._tiles) {
- if (this._tiles[i].coords.z !== this._tileZoom) {
- tile = this._tiles[i].el;
-
- tile.onload = L.Util.falseFn;
- tile.onerror = L.Util.falseFn;
-
- if (!tile.complete) {
- tile.src = L.Util.emptyImageUrl;
- L.DomUtil.remove(tile);
- }
- }
- }
- }
-});
-
-
-// @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
-// Instantiates a tile layer object given a `URL template` and optionally an options object.
-
-L.tileLayer = function (url, options) {
- return new L.TileLayer(url, options);
-};