本教程假设您已阅读过 Leaflet 类继承理论。 在 Leaflet 中,“层”是指地图移动时移动的任何东西。在了解如何从头开始创建它们之前,解释如何进行简单的扩展会更容易。
“扩展方法”
一些 Leaflet 类具有所谓的“扩展方法”:为子类编写代码的入口点。 其中之一是 L.TileLayer.getTileUrl()。每当新图块需要知道要加载哪个图像时,L.TileLayer 都会在内部调用此方法。通过创建 L.TileLayer 的子类并重写其 getTileUrl() 函数,我们可以创建自定义行为。 让我们用一个自定义的 L.TileLayer 来说明,它将显示来自 PlaceKitten 的随机小猫图像:
L.TileLayer.Kitten = L.TileLayer.extend({
getTileUrl: function(coords) {
var i = Math.ceil( Math.random() * 4 );
return "https://placekitten.com/256/256?image=" + i;
},
getAttribution: function() {
return "<a href='https://placekitten.com/attribution.html'>PlaceKitten</a>"
}
});
L.tileLayer.kitten = function() {
return new L.TileLayer.Kitten();
}
L.tileLayer.kitten().addTo(map);
通常,getTileUrl() 接收瓦片坐标(如 coords.x、coords.y 和 coords.z)并从中生成瓦片 URL。在我们的示例中,我们忽略了这些,只是使用一个随机数每次都得到不同的小猫。 拆分插件代码 在前面的示例中,L.TileLayer.Kitten 的定义与使用它的位置相同。对于插件,最好将插件代码拆分为自己的文件,并在使用时包含该文件。
L.TileLayer.Kitten = L.TileLayer.extend({
getTileUrl: function(coords) {
var i = Math.ceil( Math.random() * 4 );
return "https://placekitten.com/256/256?image=" + i;
},
getAttribution: function() {
return "<a href='https://placekitten.com/attribution.html'>PlaceKitten</a>"
}
});
然后,在显示地图时包含该文件:
<html>
…
<script src='leaflet.js'>
<script src='L.KittenLayer.js'>
<script>
var map = L.map('map-div-id');
L.tileLayer.kitten().addTo(map);
</script>
…
L.GridLayer 和 DOM 元素 另一个扩展方法是 L.GridLayer.createTile()。 L.TileLayer 假设存在图像网格(作为 <img> 元素),L.GridLayer 不假设 – 它允许创建任何类型的 HTML 元素的网格。 L.GridLayer 允许创建 <img>s 的网格,但 <div>s、<canvas>es 或 <picture>s(或任何东西)的网格是可能的。 createTile() 只需要返回给定 tile 坐标的 HTMLElement 实例。了解如何在 DOM 中操作元素在这里很重要:Leaflet 需要 HTMLElement 实例,因此使用 jQuery 等库创建的元素会出现问题。 自定义 GridLayer 的一个示例是在 <div> 中显示图块坐标。这在调试 Leaflet 的内部以及了解 tile 坐标的工作原理时特别有用:
L.GridLayer.DebugCoords = L.GridLayer.extend({
createTile: function (coords) {
var tile = document.createElement('div');
tile.innerHTML = [coords.x, coords.y, coords.z].join(', ');
tile.style.outline = '1px solid red';
return tile;
}
});
L.gridLayer.debugCoords = function(opts) {
return new L.GridLayer.DebugCoords(opts);
};
map.addLayer( L.gridLayer.debugCoords() );
如果元素必须进行一些异步初始化,则使用第二个函数参数 done 并在 tile 准备好(例如,当图像已完全加载时)或出现错误时调用它。在这里,我们将人为地延迟瓷砖:
使用这些自定义 GridLayers,插件可以完全控制构成网格的 HTML 元素。一些插件已经以这种方式使用 <canvas>es 进行高级渲染。 一个非常基本的 <canvas> GridLayer 看起来像:
L.GridLayer.CanvasCircles = L.GridLayer.extend({
createTile: function (coords) {
var tile = document.createElement('canvas');
var tileSize = this.getTileSize();
tile.setAttribute('width', tileSize.x);
tile.setAttribute('height', tileSize.y);
var ctx = tile.getContext('2d');
// Draw whatever is needed in the canvas context
// For example, circles which get bigger as we zoom in
ctx.beginPath();
ctx.arc(tileSize.x/2, tileSize.x/2, 4 + coords.z*4, 0, 2*Math.PI, false);
ctx.fill();
return tile;
}
});
像素原点 创建自定义 L.Layers 是可能的,但需要更深入地了解 Leaflet 如何定位 HTML 元素。精简版是: L.Map 容器具有“地图窗格”,即 <div>。 L.Layers 是地图窗格中的 HTML 元素 地图将所有 LatLng 转换为地图 CRS 中的坐标,然后再转换为绝对“像素坐标”(CRS 的原点与像素坐标的原点相同) 当 L.Map 准备好(具有中心 LatLng 和缩放级别)时,左上角的绝对像素坐标成为“像素原点” 每个 L.Layer 根据像素原点和图层 LatLngs 的绝对像素坐标从其地图窗格偏移 在 L.Map 上的每个 zoomend 或 viewreset 事件之后,像素原点都会重置,并且每个 L.Layer 都必须重新计算其位置(如果需要) 平移地图时不会重置像素原点;相反,整个窗格被重新定位。 这可能有点压倒性,因此请考虑以下解释性地图:
CRS 原点(绿色)保持在相同的 LatLng 中。像素原点(红色)始终从左上角开始。平移地图时像素原点会四处移动(地图窗格相对于地图的容器重新定位),并且在缩放时保持在屏幕中的相同位置(地图窗格不会重新定位,但图层可能会重新绘制自身)。缩放时更新像素原点的绝对像素坐标,但平移时不更新。请注意,每次放大地图时,绝对像素坐标(到绿色括号的距离)如何翻倍。 为了定位任何东西(例如,蓝色 L.Marker),它的 LatLng 被转换为地图 L.CRS 内的绝对像素坐标。然后从其绝对像素坐标中减去像素原点的绝对像素坐标,得到相对于像素原点的偏移量(浅蓝色)。由于像素原点是所有地图窗格的左上角,因此可以将此偏移量应用于标记图标的 HTML 元素。标记的 iconAnchor(深蓝色线)是通过负 CSS 边距实现的。 L.Map.project() 和 L.Map.unproject() 方法使用这些绝对像素坐标进行操作。同样,L.Map.latLngToLayerPoint() 和 L.Map.layerPointToLatLng() 使用相对于像素原点的偏移量。 不同的层以不同的方式应用这些计算。 L.Markers 只是重新定位他们的图标; L.GridLayers 计算地图的边界(以绝对像素坐标),然后计算要请求的瓦片坐标列表;矢量图层(折线、多边形、圆形标记等)将每个 LatLng 转换为像素,并使用 SVG 或 <canvas> 绘制几何图形。 onAdd 和 onRemove 从本质上讲,所有 L.Layers 都是地图窗格中的 HTML 元素,它们的位置和内容由图层代码定义。但是,实例化层时无法创建 HTML 元素;相反,这是在图层添加到地图时完成的 – 直到那时图层才知道地图(甚至不知道文档)。 换句话说:地图调用图层的 onAdd() 方法,然后图层创建其 HTML 元素(通常称为“容器”元素)并将它们添加到地图窗格中。相反,当图层从地图中移除时,会调用其 onRemove() 方法。图层添加到地图时必须更新其内容,并在地图视图更新时重新定位它们。图层骨架如下所示:
L.CustomLayer = L.Layer.extend({
onAdd: function(map) {
var pane = map.getPane(this.options.pane);
this._container = L.DomUtil.create(…);
pane.appendChild(this._container);
// Calculate initial position of container with `L.Map.latLngToLayerPoint()`, `getPixelOrigin()` and/or `getPixelBounds()`
L.DomUtil.setPosition(this._container, point);
// Add and position children elements if needed
map.on('zoomend viewreset', this._update, this);
},
onRemove: function(map) {
L.DomUtil.remove(this._container);
map.off('zoomend viewreset', this._update, this);
},
_update: function() {
// Recalculate position of container
L.DomUtil.setPosition(this._container, point);
// Add/remove/reposition children elements if needed
}
});
如何准确定位图层的 HTML 元素取决于图层的具体情况,但本介绍应该有助于您阅读 Leaflet 的图层代码,并创建新图层。 使用父级的 onAdd 一些用例不需要重新创建整个 onAdd 代码,而是可以重用父级的代码,然后可以在初始化之前或之后添加一些细节(根据需要)。 举个例子,我们可以有一个 L.Polyline 的子类,它总是红色的(忽略选项),比如:
L.Polyline.Red = L.Polyline.extend({
onAdd: function(map) {
this.options.color = 'red';
L.Polyline.prototype.onAdd.call(this, map);
}
});