Leaflet源码解析–TileLayer
目录
TileLayer类继承关系
var TileLayer = GridLayer.extend({
var GridLayer = Layer.extend({
TileLayer DOM树
介绍一下Leaflet中pane的DOM树结构,一个map-pane>tile-pane>layer,如果tileLayer没有设定zIndex,MT_GOOGLE,DEBUG_GRID,WIND_CONT这三层按照加载顺序显示。
#map > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-tile-pane > div.leaflet-layer.MT_GOOGLE
当进行海图缩放操作时,将出现几层瓦片的tile-container,tile-container中用来容纳瓦片。当缩放动作结束,之前一层瓦片的container将会消失,两层瓦片的zIndex由各自的比例尺决定。
Leaflet中HTML元素使用
L.Layer
// @option pane: String = 'overlayPane'
// By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default.
//默认情况下,该层将被添加到地图的[overlay pane](#map-overlaypane)。覆盖此选项将会导致图层被默认的放到另一层
pane: 'overlayPane',
// @method getPane(name? : String): HTMLElement
// Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
// 返回一个Html的元素 ,如果提供name返回已经命名的pane,不提供name参量,返回自己的pane。
getPane: function (name) {
return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
},
Layer提供getPane方法返回的是HTML 元素,本身可以通过DOM HTML方法进行操作。也可以通过Leaflet中提供的L.DomUtil中的方法进行操作。官方例子的使用方法如下:
this.getPane().appendChild(this._container);
L.DomUtil.setPosition(this._container,point);
TileLayer 源码解析
提供一段L.GridLayer的使用方法,L.GridLayer中做三件事:
- _initContainer():初始化元素(其中使用Pane加载)
- _resetView():在不同比例尺下为_level数组赋值
- _update():将获得的所有tile一次性加载到_level.el上
_level中拥有当前zoom下element与原点值。
el:div.leaflet-tile-container.leaflet-zoom-animated
origin:Point {x: 2477, y: 1208}
zoom:4
L.GridLayer
onAdd: function () {
this._initContainer();
//一层layer中拥有多层tile-container,这里level的意思的不同比例尺的level,也有不同zIndex的level意思
this._levels = {};
this._tiles = {};
this._resetView();
this._update();
},
_initContainer: function () {
if (this._container) { return; }
//创建layer层
this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
//没设置的话 zIndex=1
this._updateZIndex();
if (this.options.opacity < 1) {
this._updateOpacity();
}
//就是HTML元素的操作,都简单不赘述
this.getPane().appendChild(this._container);
},
_resetView: function (e) {
var animating = e && (e.pinch || e.flyTo);
this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
},
//在缩放时触发
_setView: function (center, zoom, noPrune, noUpdate) {
var tileZoom = this._clampZoom(Math.round(zoom));
if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
(this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
tileZoom = undefined;
}
var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
if (!noUpdate || tileZoomChanged) {
this._tileZoom = tileZoom;
if (this._abortLoading) {
this._abortLoading();
}
//更新level 更新网格
this._updateLevels();
this._resetGrid();
//比例尺发生变化
if (tileZoom !== undefined) {
//center是this._map.getCenter() LatLon类型
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;
}
//设置level中tile的贴片位置
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();
//此处level.el已经为tile的img,transfrom在map初始化时已经定义完成,tile的贴片位置在setTransform得出
if (any3d) {
setTransform(level.el, translate, scale);
} else {
setPosition(level.el, translate);
}
},
// 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 = this._clampZoom(map.getZoom());
if (center === undefined) { center = map.getCenter(); }
if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
//使用center与zoom计算出像素坐标边界,用来计算那些tile需要进行更换
var pixelBounds = this._getTiledPixelBounds(center),
tileRange = this._pxBoundsToTileRange(pixelBounds),
tileCenter = tileRange.getCenter(),
queue = [],
margin = this.options.keepBuffer,
noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
tileRange.getTopRight().add([margin, -margin]));
// Sanity check: panic if the tile range contains Infinity somewhere.
//完整性检查:如果平铺的范围在某个地方包含无穷大,就慌了。就是个检查
if (!(isFinite(tileRange.min.x) &&
isFinite(tileRange.min.y) &&
isFinite(tileRange.max.x) &&
isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
//下面的代码逻辑复杂
//大概意思就是将获得的Tiles放到fragment里面,批量加载到_level里面的leaflet-tile-container,瓦片就加载上了
for (var key in this._tiles) {
var c = this._tiles[key].coords;
if (c.z !== this._tileZoom || !noPruneRange.contains(new 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 Point(i, j);
coords.z = this._tileZoom;
if (!this._isValidTile(coords)) { continue; }
if (!this._tiles[this._tileCoordsToKey(coords)]) {
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);
}
},
_updateLevels: function () {
var zoom = this._tileZoom,
maxZoom = this.options.maxZoom;
if (zoom === undefined) { return undefined; }
//多层level更新zIndex 用于显示层级,最终只保留最新一层,其他level删除
for (var z in this._levels) {
if (this._levels[z].el.children.length || z === zoom) {
this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
this._onUpdateLevel(z);
} else {
remove(this._levels[z].el);
this._removeTilesAtZoom(z);
this._onRemoveLevel(z);
delete this._levels[z];
}
}
//level新建
var level = this._levels[zoom],
map = this._map;
if (!level) {
level = this._levels[zoom] = {};
level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
level.el.style.zIndex = maxZoom;
level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
level.zoom = zoom;
this._setZoomTransform(level, map.getCenter(), map.getZoom());
// force the browser to consider the newly added element for transition
falseFn(level.el.offsetWidth);
this._onCreateLevel(level);
}
this._level = level;
return level;
},
在瓦片加载之前可以更改_container的位置用来原点坐标对应使用,当然在计算瓦片号时也需要计算像素边界,用来显示正确的瓦片。
var crs = map.options.crs;
if (crs.code == "EPSG:3395") {
var offsetY = (256 * Math.pow(2, zoom - 1)) * (0.773378 - 1);
L.DomUtil.setPosition(this._container, L.point(0, offsetY));
}
var pixelBounds = this._getTiledPixelBounds(center);
//中心有偏移
if (crs.code == "EPSG:3396") {
pixelBounds.max.y -= offsetY;
pixelBounds.min.y -= offsetY;
}
提供原点偏移之后,贴图位置可以重合。
转载自:https://blog.csdn.net/weixin_39279307/article/details/86506557