概述
TileLayer
是 Layer
的子类,继承自GridLayer
基类,用于加载和显示瓦片地图。它提供了加载和显示瓦片地图的功能,支持自定义瓦片的 URL 格式和参数。
源码分析
源码实现
TileLayer
的源码实现如下:
export var TileLayer = GridLayer.extend({
options: {
minZoom: 0, // 最小缩放级别
maxZoom: 18, // 最大缩放级别
subdomains: "abc", // 子域名,用于分散请求,绕过浏览器的并发限制
errorTileUrl: "", // 错误瓦片的URL,加载失败时显示的备用图片URL
zoomOffset: 0, // 缩放偏移量,调整请求中的z值
tms: false,// 是否使用TMS格式,y轴反转
zoomReverse: false, // 是否反转缩放级别
detectRetina: false, // 是否检测视网膜显示,加载高分辨率瓦片
crossOrigin: false, // 控制跨域请求
referrerPolicy: false, // 防盗链策略
},
initialize: function (url, options) {
this._url = url;
options = Util.setOptions(this, options);
if (options.detectRetina && Browser.retina && options.maxZoom > 0) {
options.tileSize = Math.floor(options.tileSize / 2);
if (!options.zoomReverse) {
options.zoomOffset++;
options.maxZoom = Math.max(options.minZoom, options.maxZoom - 1);
} else {
options.zoomOffset--;
options.minZoom = Math.min(options.maxZoom, options.minZoom + 1);
}
options.minZoom = Math.max(0, options.minZoom);
} else if (!options.zoomReverse) {
options.maxZoom = Math.max(options.minZoom, options.maxZoom);
} else {
options.minZoom = Math.min(options.maxZoom, options.minZoom);
}
if (typeof options.subdomains === "string") {
options.subdomains = options.subdomains.split("");
}
this.on("tileunload", this._onTileRemove);
},
setUrl: function (url, noRedraw) {
if (this._url === url && noRedraw === undefined) {
noRedraw = true;
}
this._url = url;
if (!noRedraw) {
this.redraw();
}
return this;
},
createTile: function (coords, done) {
var tile = document.createElement("img");
DomEvent.on(tile, "load", Util.bind(this._tileOnLoad, this, done, tile));
DomEvent.on(tile, "error", Util.bind(this._tileOnError, this, done, tile));
if (this.options.crossOrigin || this.options.crossOrigin === "") {
tile.crossOrigin =
this.options.crossOrigin === true ? "" : this.options.crossOrigin;
}
if (typeof this.options.referrerPolicy === "string") {
tile.referrerPolicy = this.options.referrerPolicy;
}
tile.alt = "";
tile.src = this.getTileUrl(coords);
return tile;
},
getTileUrl: function (coords) {
var data = {
r: 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 Util.template(this._url, Util.extend(data, this.options));
},
_tileOnLoad: function (done, tile) {
if (Browser.ielt9) {
setTimeout(Util.bind(done, this, null, tile), 0);
} else {
done(null, tile);
}
},
_tileOnError: function (done, tile, e) {
var errorUrl = this.options.errorTileUrl;
if (errorUrl && tile.getAttribute("src") !== errorUrl) {
tile.src = errorUrl;
}
done(e, tile);
},
_onTileRemove: function (e) {
e.tile.onload = null;
},
_getZoomForUrl: function () {
var zoom = this._tileZoom,
maxZoom = this.options.maxZoom,
zoomReverse = this.options.zoomReverse,
zoomOffset = this.options.zoomOffset;
if (zoomReverse) {
zoom = maxZoom - zoom;
}
return zoom + zoomOffset;
},
_getSubdomain: function (tilePoint) {
var index =
Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
return this.options.subdomains[index];
},
_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 = Util.falseFn;
tile.onerror = Util.falseFn;
if (!tile.complete) {
tile.src = Util.emptyImageUrl;
var coords = this._tiles[i].coords;
DomUtil.remove(tile);
delete this._tiles[i];
this.fire("tileabort", { tile, coords });
}
}
}
},
_removeTile: function (key) {
var tile = this._tiles[key];
tile.el.setAttribute("src", Util.emptyImageUrl);
return GridLayer.prototype._removeTile.call(this, key);
},
_tileReady: function (coords, err, tile) {
if (
!this._map ||
(tile && tile.getAttribute("src") === Util.emptyImageUrl)
) {
return;
}
return GridLayer.prototype._tileReady.call(this, coords, err, tile);
},
});
export function tileLayer(url, options) {
return new TileLayer(url, options);
}
源码解析
initialize
方法
initialize
方法用于初始化TileLayer
对象,设置默认选项和事件监听器,保存URL末班
this._url
:存储瓦片图层的URL。options
:用户提供的选项,用于覆盖默认选项。options.detectRetina
:如果启用了视网膜检测,将瓦片大小减半,并调整缩放级别和偏移量。options.subdomains
:如果是字符串,将其拆分为子域名数组。this.on("tileunload", this._onTileRemove)
:监听tileunload
事件,当瓦片被卸载时调
方法详解
setUrl(url,noRedraw)
:
- 更新URL模版,若URL未变化且
noRedraw
为true
,则不重绘图层。 - 调用
redraw()
方法强制刷新,重新绘制图层。
createTile(coords,done)
:
- 创建一个新的HTML图片元素作为瓦片。
- 设置图片元素的
load
和error
事件监听器,当图片加载完成或加载失败时调用done
回调函数。 - 设置图片元素的
crossOrigin
属性,控制跨域请求。 - 设置图片元素的
referrerPolicy
属性,控制防盗链策略。 - 设置图片元素的
alt
属性为空字符串。 - 设置图片元素的
src
属性为getTileUrl(coords)
方法返回的URL。 - 返回创建的图片元素。
getTileUrl(coords)
:
- 计算并返回URL模版,将URL中的占位符替换为实际的值。
- 占位符包括:
{r}
:如果启用了视网膜检测,替换为@2x
,否则为空字符串。{s}
:根据坐标计算子域名。{x}
:替换为X坐标。{y}
:替换为Y坐标。{z}
:替换为缩放级别。{-y}
:如果启用了TMS格式,替换为反转的Y坐标,否则为空字符串。
- 返回替换后的URL。
_tileOnLoad(done,tile)
:
- 当图片加载完成时调用
done
回调函数,IE9以下延迟触发。
_tileOnError(done,tile,e)
:
- 当图片加载失败时调用
done
回调函数,并设置错误信息。 - 如果启用了错误瓦片的URL,将图片元素的
src
属性设置为错误瓦片的URL,再次尝试加载。 - 返回错误信息。
_getZoomForUrl()
:
- 根据
zoomReverse
和zoomOffset
选项计算缩放级别。 - 如果启用了
zoomReverse
,将缩放级别反转。 - 返回计算后的缩放级别。
_getSubdomain(tilePoint)
: - 根据X和Y坐标计算子域名。
- 使用
subdomains
选项中的子域名数组,根据X和Y坐标的绝对值取模计算索引。 - 返回计算得到的子域名。
瓦片管理
_abortLoading()
:中止非当前缩放级别的瓦片加载
- 重置
onload
/onerror
事件监听器, - 将非当前缩放级别的瓦片的
src
属性设置为空字符串,并从_tiles
对象中移除。
_removeTile(key)
:
- 从
_tiles
对象中移除指定键的瓦片。 - 将瓦片的
src
属性设置为空字符串,取消网络请求。 - 调用父类的
_removeTile
方法,移除瓦片元素。
_tileReady(coords,err,tile)
:
- 当瓦片准备就绪时调用。
- 如果URL为空字符串或
_map
不存在,返回。 - 调用父类的
_tileReady
方法,处理瓦片就绪事件。
关键逻辑
- Retina
适配:通过detectRetina
自动调整瓦片尺寸和缩放,加载高分辨率图片。
- 子域名轮询:分散请求,避免浏览器并发限制。
- TMS坐标反转:通过
tms: true
适配不同的瓦片服务标准。 - 跨域处理:通过
crossOrigin
属性解决CORS
问题,访问像素数据。 - 错误恢复:加载失败时显示
errorTileUrl
,提升用户体验
总结
TileLayer
是Leaflet
中用于加载和显示瓦片地图的核心组件,通过继承GridLayer
基类,实现了瓦片的加载和管理,支持自定义URL格式和参数,提供了丰富的功能和选项,适用于各种瓦片地图服务。