https://leafletjs.com/examples/extending/extending-2-layers.html
leaflet一些类中拥有“可扩展方法”:为编写子类提供入口方法。
// image-specific code (override to implement e.g. Canvas or SVG tile layer)
getTileUrl: function (tilePoint) {
return L.Util.template(this._url, L.extend({
s: this._getSubdomain(tilePoint),
z: tilePoint.z,
x: tilePoint.x,
y: tilePoint.y
}, this.options));
},
L.TileLayer.getTileUrl
当新的瓦片载入时,此方法在L.TileLayer内部调用。通过做一个L.TileLayer子类,重写他的getTileUrl()方法,在其中可以自定义功能。
提供一个PlaceKitten(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 "PlaceKitten"
}
});
L.tileLayer.kitten = function() {
return new L.TileLayer.Kitten();
}
L.tileLayer.kitten().addTo(self);
https://leafletjs.com/examples/extending/kittenlayer.html
通常情况下,getTileLayer()接入坐标点(x:13,y:6,z:4)用来生成一个瓦片获取地址。在Kitten例子里面,我们忽略了这些,用一个简单的随机数得到不同的小猫图片。
在上个例子中,L.TileLayer.Kitten 在使用的时候同时进行定义,对于插件来说,最好将插件代码分离到它自己的文件中,在使用时候再进行引用。
对于KittenLayer,应该创建一个文件L.KittenLayer.js,如下
另一个扩展方法是L.GridLayer.createTile()。当L.TileLayer作为一个图片的栅格容器。需要使用类似于HTML Elements去创建
栅格。
L.GridLayer允许创建img栅格。div、canvas、picture也是可行的。createTile()返回值必须是一个给与坐标的HTML Element,知道如何在DOM中如何操作Element在这里是很重要的。Leaflet需要HTML Element实例,那么由jQuery创建Element将无法使用。
在div中使用一个自定义的GridLayer显示坐标的例子,在我们为leaflet内部做debug时很有用处。也可以理解坐标是如何生效的。
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() );
如果元素必须做一些异步初始化工作,在tile加载结束后(完全加载成功或者加载失败),需要使用函数中第二个参数进行回调,在这里我们必须人为的延迟tile加载。
createTile: function (coords, done) {
var tile = document.createElement('div');
tile.innerHTML = [coords.x, coords.y, coords.z].join(', ');
tile.style.outline = '1px solid red';
setTimeout(function () {
done(null, tile); // Syntax is 'done(error, tile)'
}, 500 + Math.random() * 1500);
return tile;
}
使用这些自定义GridLayer,插件必须可以对组成Grid的HTML Element进行完全控制。少量的插件已经在使用canvas来做一些各为先进的功能。
一个非常基础的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.Layer是可行的,但是需要对Leaflet中HTML元素坐标有更深层的理解。版本中没有介绍的有:
L.Map容器中包含“map panes”,他们是div
L.Layer是在“map panes”中的HTML元素
Map将所有的经纬度通过CRS转换成坐标,之后将坐标转换为绝对的像素坐标(在CRS中定义的原点坐标同样作为像素坐标的原点)。
当L.Map已经使用(拥有中心经纬度、指定比例尺),左上角的坐标原点成为像素原点。
根据像素原点以及layer中经纬度元素的绝对像素坐标,产生每一层L.Layer对于它的map pane的坐标偏移量。
当Map变换比例尺、重载的时候像素原点将被重置。每一层的Layer需要重新计算它的位置(如果需要的话)。
地图在平移拖拽时,像素原点不会重置,但整个pane是重新position的。
这可能有点超纲,看下面用于解释的地图:
https://leafletjs.com/examples/extending/pixelorigin.html
绿色的CRS原点一直在同一经纬度上。
红色的像素原点一直停留在开始的左上角位置。
当地图平移时(map pane相对于map容器位置变化),像素原点也跟随移动。
当进行缩放(map pane位置没有发生变化,但是layer可能重绘),像素原点位置不发生变化。当缩放是,相对于像素原点的绝对像素坐标将发生变化,但是当平移时,此值不会发生变化。(注意每次放大地图时,绝对像素坐标(到绿色括号的距离)是如何加倍的)。
对于位置实例(例如Marker),它的经纬度需要通过Map提供L.CRS转换成绝对像素坐标。然后从绝对像素坐标中减去像素原点的绝对像素坐标,得到相对于像素原点的偏移量(浅蓝色)。由于像素原点是所有地图窗格的左上角,因此该偏移量可以应用于标记图标的HTML元素。Marker的iconAnchor(深蓝色线)是通过负CSS边距实现的。 L.Map的project()与unproject()方法使用这些绝地像素坐标。同样的,L.Map的latLngToLayerPoint()与layerPointToLatLng()使用相对于原点的偏移量。
latLngToLayerPoint: function (latlng) {
var projectedPoint = this.project(toLatLng(latlng))._round();
return projectedPoint._subtract(this.getPixelOrigin());
},
不同的Layer以不同的方式应用这些计算。,L.Marker只是重新定义他们的图标,L.GridLayer计算地图边界(绝对像素坐标)然后计算平铺坐标列表;VectorLayer(折线、多边形、圆形标记等)将每个LatLng转换为像素,并使用SVG或canvas进行绘制。
在他们的核心处,所有的L.Layer都是map pane内的HTML元素,它们的位置与内容由layer的代码定义。当一个layer被实例化之后,无论怎样HTML元素不能再被创建。相反的,当layer加载到map之后,元素才被完成。layer是不知道map的,直到layer被map加载。
换句话说,map调用Layer的onAdd()方法,layer才创建它的HTML元素(通常称为container元素)并把元素加到map pane上。相对的,当layer从map上移除,它的onRemove()方法将被调用。在加载到map时,layer必须更新它的内容,当map更新视图时,它的元素需要重新定位。一个layer的框架:
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
}
});
在一些应用不需要整个onAdd方法重构,而是父类方法可以重用,然后可以根据需要在初始化之前或之后添加一些方法。
提供一个例子,我们定义一个L.Polyline的子类,线条一直为红色:
L.Polyline.Red = L.Polyline.extend({
onAdd: function(map) {
this.options.color = 'red';
L.Polyline.prototype.onAdd.call(this, map);
}
});