Leaflet源码解析系列(七):Layer 对象解读

739 阅读4分钟

Layer是 leaflet api 使用中接触最多的对象,各种数据都是通过 layer 来展示的,比如最基础的瓦片底图、点、线、面矢量图层等。

Map一样,Layer 也是扩展自Evented类,自带了 onoff 事件函数。Layer是所有图层(除了icon之外)的基类,我们也可以基于它和它的子类进一步扩展。

图层之间的继承关系:

下面是 Layer 的继承扩展关系图。

Layer.js 源码

下面是 Layer 代码的解析,基类比较简单,不涉及复杂的数学和地图知识。

import {Evented} from '../core/Events';
import {Map} from '../map/Map';
import * as Util from '../core/Util';

/*
 * @class Layer
 * @inherits Evented
 * @aka L.Layer
 * @aka ILayer
 *
* 从 L.Evented 扩展而来,是所有图层的基类
 *
 * @event add: Event
 * layer 添加到地图的时候触发
 *
 * @event remove: Event
 * layer 从地图上移除的时候触发
 */


export const Layer = Evented.extend({

	// 扩展自 Layer 的图层将继承以下 option:
	options: {
		// @option pane: String = 'overlayPane'
		// 默认所有的图层会被添加到 overlaypane 对应的 dom 中,覆盖这个属性会导致图层被添加到新的 pane dom 容器中
		pane: 'overlayPane',

		// @option attribution: String = null
		//  attribution control 中展示的内容,通常是描述图层的数据内容、数据提供者或者版权信息.
		attribution: null,

		bubblingMouseEvents: true
	},

	/* 将 layer 添加到地图 */
	addTo(map) {
		map.addLayer(this);
		return this;
	},

	/** 从地图中移除这个图层 */
	remove() {
		return this.removeFrom(this._map || this._mapToAdd);
	},

	/** 从地图或者图层组中移除图层 */
	removeFrom(obj) {
		if (obj) {
			obj.removeLayer(this);
		}
		return this;
	},

	/** 返回指定名称的 pane dom 元素,没有传 name 就返回当前图层的 pane */
	getPane(name) {
		return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
	},

	/** 将当前图层添加到 map._targets,在 map 响应事件的时候查找事件来源 */
	addInteractiveTarget(targetEl) {
		this._map._targets[Util.stamp(targetEl)] = this;
		return this;
	},

	/** 将当前图层从 map._targets 移除,该图层的事件不会响应到 map 上 */
	removeInteractiveTarget(targetEl) {
		delete this._map._targets[Util.stamp(targetEl)];
		return this;
	},

	// @method getAttribution: String
	//  attribution control 里调用, returns the [attribution option](#gridlayer-attribution).
	getAttribution() {
		return this.options.attribution;
	},

	_layerAdd(e) {
		const map = e.target;

		// check in case layer gets added and then removed before the map is ready
		if (!map.hasLayer(this)) { return; }

		this._map = map;
		this._zoomAnimated = map._zoomAnimated;

		if (this.getEvents) {
			const events = this.getEvents();
			map.on(events, this);
			this.once('remove', function () {
				map.off(events, this);
			}, this);
		}

		this.onAdd(map);

		// 触发用户注册的事件
		this.fire('add');
		map.fire('layeradd', {layer: this});
	}
});

/* @section
 * @uninheritable
 *
 * 所有扩展子类实现以下方法
 *
 * @method onAdd(map: Map): this
 * Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer).
 * 创建图层对应的 dom 元素,并添加到 map pane,并监听相关的地图事件,在 map.addLayer 的时候调用
 *
 * @method onRemove(map: Map): this
 * Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer).
 * 移除图层对应的 dom 元素,并添加到 map pane,并移除监听的相关的地图事件,在 map.removeLayer 的时候调用
 *
 * @method getEvents(): Object
 * This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer.
 * 可选的方法,返回一个类似 { viewreset: this._reset } 格式的对象,addEventListener 方法会随着图层的添加和删除自动从 map 中注册和取消。
 *
 * @method getAttribution(): String
 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
 * 可选的方法,不论图层是否可见,这个方法都返回 Attribution 字符串
 *
 * @method beforeAdd(map: Map): this
 * Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only.
 * 可选的方法,调用 map.addLayer(layer) 时候执行,在图层被添加到地图之前、在事件被初始化之前执行,不用等到地图处于可用状态。仅用于早期初始化。
 */


/* @namespace Map
 * 向 map 中添加了两个事件
 * @event layeradd: LayerEvent
 * Fired when a new layer is added to the map.
 *
 * @event layerremove: LayerEvent
 * Fired when some layer is removed from the map
 */
Map.include({
	// 扩展的 map 中的方法
	/** 添加图层 */
	addLayer(layer) {
		if (!layer._layerAdd) {
			throw new Error('The provided object is not a Layer.');
		}

		const id = Util.stamp(layer);
		if (this._layers[id]) { return this; }
		this._layers[id] = layer;

		layer._mapToAdd = this;

		if (layer.beforeAdd) {
			layer.beforeAdd(this);
		}

		this.whenReady(layer._layerAdd, layer);

		return this;
	},

	/** 移除图层 */
	removeLayer(layer) {
		const id = Util.stamp(layer);

		if (!this._layers[id]) { return this; }

		if (this._loaded) {
			layer.onRemove(this);
		}

		delete this._layers[id];

		if (this._loaded) {
			this.fire('layerremove', {layer});
			layer.fire('remove');
		}

		layer._map = layer._mapToAdd = null;

		return this;
	},

	/** 检查 map 中是否存在该图层 */
	hasLayer(layer) {
		// 通过判断唯一 id 是否存在
		return Util.stamp(layer) in this._layers;
	},

	/* @method eachLayer(fn: Function, context?: Object): this
	 * Iterates over the layers of the map, optionally specifying context of the iterator function.
	 * 遍历所有图层,可以指定上下文
	 */
	eachLayer(method, context) {
		for (const i in this._layers) {
			method.call(context, this._layers[i]);
		}
		return this;
	},

	_addLayers(layers) {
		// 判断传入的参数是不是个数组
		layers = layers ? (Array.isArray(layers) ? layers : [layers]) : [];
		// 循环加入
		for (let i = 0, len = layers.length; i < len; i++) {
			this.addLayer(layers[i]);
		}
	},

	/** 添加图层的时候更新地图的 minZoom maxZoom */
	_addZoomLimit(layer) {
		// minZoom 和 minZoom 都没有设置,则从图层里更新
		if (!isNaN(layer.options.minZoom) || !isNaN(layer.options.minZoom)) {
			this._zoomBoundLayers[Util.stamp(layer)] = layer;
			this._updateZoomLevels();
		}
	},

	/** 移除图层的时候更新地图的 minZoom maxZoom */
	_removeZoomLimit(layer) {
		const id = Util.stamp(layer);
		// 这个图层参与了 map 的 zoom 计算,删除这个图层后重新计算map的最小和最大 Zoom
		if (this._zoomBoundLayers[id]) {
			delete this._zoomBoundLayers[id];
			this._updateZoomLevels();
		}
	},

	/** 从所有图层中,取最小 minZoom 和最大 maxZoom,如果 zoom 不在新的 zoomSpan 内,更新 map的 zoom 缩放层级 */
	_updateZoomLevels() {
		let minZoom = Infinity,
		    maxZoom = -Infinity;
		const oldZoomSpan = this._getZoomSpan();

		// 遍历所有图层,取图层里的最大、最小 zoom
		for (const i in this._zoomBoundLayers) {
			const options = this._zoomBoundLayers[i].options;

			minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
			maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
		}

		// 图层里也没有 maxZoom,那么还是赋值为 undefined
		this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
		// 图层里也没有 minZoom,那么还是赋值为 undefined
		this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;

		// @section Map state change events
		// @event zoomlevelschange: Event
		// 如果添加了一个图层导致map的 zoomSpan 变了,触发这个事件
		if (oldZoomSpan !== this._getZoomSpan()) {
			this.fire('zoomlevelschange');
		}

		// 如果当前的地图 zoom 超出了最新的 maxZoom,就设置为 maxZoom
		if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
			this.setZoom(this._layersMaxZoom);
		}
		// 如果当前的地图 zoom 超出了最新的 minZoom,就设置为 minZoom
		if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
			this.setZoom(this._layersMinZoom);
		}
	}
});