Leaflet

一個開源的 JavaScript 函式庫
適用於行動裝置友善的互動式地圖

← 教學

非地球範疇

有時,地圖並不代表地球表面的事物,因此不具備地理緯度和地理經度的概念。大多數情況下,這指的是大型掃描影像,例如遊戲地圖。

在本教學中,我們選擇了來自《星際控制II》的星圖,這款遊戲現在以開源專案 The Ur-Quan Masters 的形式提供。這些地圖是使用一個工具來讀取遊戲的開源資料檔製作的(網頁似乎已關閉,請參閱封存的版本),看起來像這樣


正如在角落所見,遊戲具有內建的方形坐標系統。這將使我們能夠建立坐標系統。


CRS.Simple

CRS 代表坐標參考系統,這是地理學家用來解釋坐標向量中坐標含義的術語。例如,如果使用地球上的緯度-經度,[15, 60] 代表印度洋中的一個點,或者在我們的星圖中代表克魯格-Z 星系。

一個 Leaflet 地圖只有一個 CRS (而且僅能有一個 CRS),可以在建立地圖時更改。對於我們的遊戲地圖,我們將使用 CRS.Simple,它代表一個方形網格

var map = L.map('map', {
	crs: L.CRS.Simple
});

然後,我們可以簡單地添加一個帶有星圖影像及其近似邊界的 L.ImageOverlay

var bounds = [[0,0], [1000,1000]];
var image = L.imageOverlay('uqm_map_full.png', bounds).addTo(map);

並顯示整個地圖

map.fitBounds(bounds);
請參閱此獨立範例。

此範例並未完全奏效,因為在執行 fitBounds() 後,我們無法看到整個地圖。

CRS.Simple 地圖中常見的陷阱

在預設的 Leaflet CRS CRS.Earth 中,360 度經度被對應到 256 個水平像素(在縮放層級 0 時),而大約 170 度緯度被對應到 256 個垂直像素(在縮放層級 0 時)。

CRS.Simple 中,一個水平地圖單位對應到一個水平像素,垂直方向亦然。這表示整個地圖大約為 1000x1000 像素大小,無法容納在我們的 HTML 容器中。幸運的是,我們可以將 minZoom 設定為小於零的值

var map = L.map('map', {
	crs: L.CRS.Simple,
	minZoom: -5
});

像素 vs. 地圖單位

使用 CRS.Simple 時,一個常見的錯誤是假設地圖單位等於影像像素。在本例中,地圖覆蓋 1000x1000 個單位,但影像為 2315x2315 像素大小。不同的情況會要求一個像素 = 一個地圖單位,或 64 個像素 = 一個地圖單位,或任何其他情況。請以網格中的地圖單位思考,然後據此添加您的圖層(L.ImageOverlayL.Marker 等)。

實際上,我們使用的影像覆蓋超過 1000 個地圖單位 - 有相當大的邊距。測量 0 到 1000 坐標之間有多少像素,並推斷,我們可以獲得此影像的正確坐標邊界

var bounds = [[-26.5,-25], [1021.5,1023]];
var image = L.imageOverlay('uqm_map_full.png', bounds).addTo(map);

在我們繼續進行的同時,讓我們添加一些標記

var sol = L.latLng([ 145, 175.2 ]);
L.marker(sol).addTo(map);
map.setView( [70, 120], 1);
請參閱此獨立範例。

這不是您要找的 LatLng

您會注意到 Sol 的坐標是 [145,175] 而不是 [175,145],地圖中心也會發生同樣的情況。CRS.Simple 中的坐標採用 [y, x] 的形式,而不是 [x, y],就像 Leaflet 使用 [lat, lng] 而不是 [lng, lat] 的方式一樣。

(在技術上,Leaflet 偏好使用[northing, easting] 而不是 [easting, northing] - 坐標對中的第一個坐標指向「北方」,第二個指向「東方」)

關於 [lng, lat][lat, lng][y, x][x, y] 的爭論並非新鮮事,而且沒有明確的共識。缺乏共識是為什麼 Leaflet 有一個名為 L.LatLng 的類別,而不是更易混淆的 L.Coordinate 的原因。

如果使用名為 L.LatLng 的東西來處理 [y, x] 坐標對您來說沒有多大意義,您可以輕鬆地為它們建立包裝函式

var yx = L.latLng;

var xy = function(x, y) {
	if (Array.isArray(x)) {    // When doing xy([x, y]);
		return yx(x[1], x[0]);
	}
	return yx(y, x);  // When doing xy(x, y);
};

現在,我們可以添加一些星星,甚至使用 [x, y] 坐標添加一條導航線

var sol      = xy(175.2, 145.0);
var mizar    = xy( 41.6, 130.1);
var kruegerZ = xy( 13.4,  56.5);
var deneb    = xy(218.7,   8.3);

L.marker(     sol).addTo(map).bindPopup(      'Sol');
L.marker(   mizar).addTo(map).bindPopup(    'Mizar');
L.marker(kruegerZ).addTo(map).bindPopup('Krueger-Z');
L.marker(   deneb).addTo(map).bindPopup(    'Deneb');

var travel = L.polyline([sol, deneb]).addTo(map);

地圖看起來幾乎相同,但程式碼的可讀性更高

請參閱此獨立範例。