UNPKG

leaflet

Version:

JavaScript library for mobile-friendly interactive maps

251 lines (202 loc) 7.57 kB
/* * @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); };