本教學假設您已閱讀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
是定義在它被使用的相同位置。對於外掛程式,最好將外掛程式碼分割到自己的檔案中,並在使用時包含該檔案。
對於 KittenLayer,您應該建立一個像 L.KittenLayer.js
這樣的檔案,內容為
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>
的網格,但 <div>
、<canvas>
或 <picture>
(或任何東西)的網格也是可能的。createTile()
只需要返回一個給定圖磚坐標的 HTMLElement
實例。了解如何在 DOM 中操作元素在這裡很重要:Leaflet 期望 HTMLElement
的實例,因此使用像 jQuery 這樣的函式庫建立的元素會產生問題。
自訂 GridLayer
的一個範例是在 <div>
中顯示圖磚坐標。這對於偵錯 Leaflet 的內部運作以及了解圖磚坐標如何運作特別有用
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
,並在圖磚準備就緒時(例如,當圖片已完全載入時)或發生錯誤時呼叫它。在這裡,我們只會人工延遲圖磚
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
,外掛程式可以完全控制組成網格的 HTML 元素。一些外掛程式已經以這種方式使用 <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
容器具有「地圖窗格」,它們是<div>
。L.Layer
是地圖窗格內的 HTML 元素- 地圖會將所有
LatLng
轉換為地圖 CRS 中的坐標,並從中轉換為絕對「像素坐標」(CRS 的原點與像素坐標的原點相同) - 當
L.Map
準備就緒(具有中心LatLng
和縮放等級)時,左上角的絕對像素坐標會變成「像素原點」 - 每個
L.Layer
都會根據像素原點和圖層的LatLng
的絕對像素坐標,從其地圖窗格偏移 - 在
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.Marker
只會重新定位其圖示;L.GridLayer
會計算地圖的邊界(以絕對像素坐標表示),然後計算要請求的圖磚坐標列表;向量圖層(折線、多邊形、圓形標記等)會將每個 LatLng
轉換為像素,並使用 SVG 或 <canvas>
繪製幾何圖形。
onAdd
和 onRemove
在其核心中,所有 L.Layer
都是地圖窗格內的 HTML 元素,其位置和內容由圖層的程式碼定義。但是,在圖層實例化時無法建立 HTML 元素;相反地,這是當圖層新增至地圖時完成的 - 圖層直到那時才知道地圖(甚至不知道 document
)。
換句話說:地圖會呼叫圖層的 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) {
this._container.remove();
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);
}
});