扩展Leaflet: Layers

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);

扩展Leaflet: Layers_第1张图片
https://leafletjs.com/examples/extending/kittenlayer.html
在这里插入图片描述
通常情况下,getTileLayer()接入坐标点(x:13,y:6,z:4)用来生成一个瓦片获取地址。在Kitten例子里面,我们忽略了这些,用一个简单的随机数得到不同的小猫图片。

分离插件代码

在上个例子中,L.TileLayer.Kitten 在使用的时候同时进行定义,对于插件来说,最好将插件代码分离到它自己的文件中,在使用时候再进行引用。
对于KittenLayer,应该创建一个文件L.KittenLayer.js,如下
扩展Leaflet: Layers_第2张图片

L.GridLayer and DOM elements

另一个扩展方法是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() );

扩展Leaflet: Layers_第3张图片

如果元素必须做一些异步初始化工作,在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的。
    这可能有点超纲,看下面用于解释的地图:
    扩展Leaflet: Layers_第4张图片
    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方法

在一些应用不需要整个onAdd方法重构,而是父类方法可以重用,然后可以根据需要在初始化之前或之后添加一些方法。
提供一个例子,我们定义一个L.Polyline的子类,线条一直为红色:

L.Polyline.Red = L.Polyline.extend({
    onAdd: function(map) {
        this.options.color = 'red';
        L.Polyline.prototype.onAdd.call(this, map);
    }
});

你可能感兴趣的:(Leaflet,WebGIS)