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

You may also like...

退出移动版