去年有參與開發一個 leaflet 圖台,那時匆匆忙忙再加上沒經驗,很多地方都沒有搞懂,程式能動就怎麼幹。今年圖台要新增兩個圖層,剛好我可以再回頭來研究一下 leaflet 的 L.geoJSON()。
甚麼是geoJSON
維基百科的解釋如下:
geoJSON是一種基於JSON的地理空間數據交換格式,它定義了幾種類型的JSON物件以及它們組合在一起的方法,以表示有關地理要素、屬性和它們的空間範圍的數據。
geoJSON 定義的地理數據類型(type)有以下幾種:
- 基本幾何圖形
- 點(Point)
- 線(LineString)
- 面(Polygon)
- 複合幾何圖形
- 多點(MultiPoint)
- 多線(MultiLineString)
- 多面(MultiPolygon)
geoJSON 的資料結構十分簡單,它是一個物件,有type 跟 coordinates 這2個屬性,type定義了點、線、面的資料類型,’coordinates’ 則是一個座標陣列,要注意的是經度在前,緯度在後:
點(Point):
{
"type": "Point",
"coordinates": [30, 10]
}
線(LineString)
{
"type": "LineString",
"coordinates": [
[30, 10], [10, 30], [40, 40]
]
}
多邊形(Polygon)
{
"type": "Polygon",
"coordinates": [
[[30, 10], [40, 40], [20, 40], [10, 20], [30, 10]]
]
}
挖洞的多邊形
因為第二個陣列裡的座標位置在第一個陣列的座標裡面,所以畫出來面被挖了一個洞。
{
"type": "Polygon",
"coordinates": [
[[35, 10], [45, 45], [15, 40], [10, 20], [35, 10]],
[[20, 30], [35, 35], [30, 20], [20, 30]]
]
}
以上是geoJSON的簡單說明,實際的結構可以更複雜。下面將針對如何在 leaflet 中使用 L.geoJSON 來將資料展現在地圖上做說明。
L.geoJSON 如何使用
在leaflet中可以使用它提供的 L.geoJSON 方法將點、線、面畫到地圖上。
L.geoJSON ()的第一個參數是 GeoJSON 物件,定義了空間資訊;第二個參數也是一個物件,可以放入它提供的 style、pointToLayer、onEachFeature及filter選項,這部分稍後說明。
const highBuilding = L.geoJSON(GeoJSON空間資料, {
style: 線段及面的樣式
}).addTo(map);
如何劃一條線
首先簡單定義一個簡單的線段 geoJSON 物件,type 是 LineString,coordinates為一個陣列,裡面有三個經緯度座標的陣列。
const lineJSON = {
type: "LineString",
coordinates: [
[121.56, 25.033],
[121.58, 25.015],
[121.61, 25.043]
]
};然後定義線段的顏色
const lineStyle = {
color: "#ff0000",
// weight 控制線的粗細
weight: 8,
// opacity 控制透明度
opacity: 0.65
};最後使用L.geoJSON方法把線段展在地圖上,第一個參數是 上面定義的lineJSON物件,第二個參數物件裡面放了 linesStyle 物件,定義了線段的顏色。
const highBuilding = L.geoJSON(line, {
style: lineStyle
}).addTo(map);
如果你想要畫二條以上的線,可以改變一下geoJSON 物件的資料,改成陣列包住線段物件。
const lineJSON = [{
"type": "LineString",
"coordinates": [[121.54, 25.033], [121.56, 25.033], [121.57, 25.033]]
}, {
"type": "LineString",
"coordinates": [[121.54, 25.030], [121.56, 25.030], [121.57, 25.030]]
}];
如何畫一個面
依樣定義一個面的 geoJSON 物件,type 是 Polygon,coordinates 是一個三個階層的陣列,第二層的陣列中放入至少3個的經緯度座標陣列。
const polygonJSON = {
type: "Polygon",
coordinates: [
[
[121.56, 25.033],
[121.58, 25.015],
[121.61, 25.043],
[121.56, 25.043]
],
[
[121.52, 25.033],
[121.48, 25.015],
[121.48, 25.043],
[121.52, 25.043]
]
]
};
定義面的樣式
const polygonStyle = {
color: "#ff0000",
// weight 控制線的粗細
weight: 8,
// opacity 控制透明度
opacity: 0.65,
// 如果沒有fillColor選項,則由 color 控制填色
fillColor: "#B0DE5C",
fillOpacity: 0.8
};
最後使用L.geoJSON方法把面展在地圖上,第一個參數是 上面定義的 polygonJSON 物件,第二個參數物件裡面放了 polygonStyle 物件,定義了面的顏色。
const polygons = L.geoJSON(polygonJSON, {
style: polygonStyle
}).addTo(map);
如何畫一個點
最後來談談如何展一個點,一樣先定義 geoJSON 資料,
const pointJSON = {
"type": "Point",
"coordinates": [121.5647238,25.0336659]
}
如果要自定義樣式,要使用 pointToLayer 方法,在這裡我們先不使用 pointToLayer 方法,而是使用預設的 marker。
直接使用L.geoJSON方法把面展在地圖上,第一個參數是 上面定義的 polygonJSON 物件,沒有第二個參數物件。
const highBuilding = L.geoJSON(pointJSON).addTo(map);
如果要自定義點的樣式就要在L.geoJSON的第二個參數物件使用它所提供的 pointToLayer 方法,回傳 L.circleMarker 或 L.marker。
const highBuilding = L.geoJSON(taipei101, {
pointToLayer(feature, latlng) {
return L.circleMarker(latlng, {
// 控制點的大小
radius: 8,
// ORANGE
fillColor: "#ff7800",
// 線框的顏色
color: "#000",
// 線框的粗細
weight: 1,
opacity: 1,
fillOpacity: 0.8
});
}
}).addTo(map);
更複雜多樣的geoJSON
geoJSON 也可以是以下的結構,外層的 type 設成 Feature ,在 geometry 放入 原本的 type (這裡的type類型為 Point、LineString及Polygon) 及 coordinates 屬性,還有一個 properties 屬性可以放入 style 及 要放在 popup 的內容。
const campus = {
"type": "Feature",
"properties": {
"popupContent": "This is the Auraria West Campus",
"style": {
weight: 2,
color: "#999",
opacity: 1,
// GREEN
fillColor: "#FF0000",
fillOpacity: 0.8
}
},
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[121.519708, 25.035685],
[121.523249, 25.035685],
[121.523249, 25.033586],
[121.519708, 25.033586],
],
]
]
}
};
也可以將外層的 type 設為 FeatureCollection,在內層的 features 陣列,放入有 geometry 、properites 及將內層type 設為 feature
const bicycleRental = {
"type": "FeatureCollection",
// "features" 為陣列,裡面放的物件們為點、線、面等屬性
"features": [
{
"geometry": {
"type": "Point",
"coordinates": [
121.5647238,25.0336659
]
},
"type": "Feature",
"properties": {
"popupContent": "台北i101"
},
"id": 51
},
]
};
當 type 設為 FeatureCollection 的時候,可以同時包含各種點、線及面的空間資訊。L.geoJSON 會自動判斷是點還是面。
const bicycleRental = {
"type": "FeatureCollection",
// "features" 為陣列
"features": [
{
"geometry": {
"type": "Point",
"coordinates": [
121.5647238,25.0336659
]
},
"type": "Feature",
"properties": {
"popupContent": "台北i101"
},
"id": 51
},
{
"geometry": {
"type": "Point",
"coordinates": [
121.5848238,25.0337659
]
},
"type": "Feature",
"properties": {
"popupContent": "This is a B-Cycle Station. Come pick up a bike and pay by the hour. What a deal!"
},
"id": 52
},
{
"geometry": {
"type": "Polygon",
"coordinates": [[
[121.55, 25.04],
[121.6, 25.05],
[121.6, 25.055],
[121.55, 25.055],
]]
},
"type": "Feature",
"properties": {
"popupContent": "This is a B-Cycle Station. Come pick up a bike and pay by the hour. What a deal!"
},
"id": 54
},
]
};
範例
L.geoJSON 所提供的四種選項方法
搭配這4種選項方法,geoJSON的資料屬性除了type、coordinates之外,也可以加入其他屬性使用。
style
用來定義線跟面的樣式,作為 L.geoJSON() 第二個參數的物件屬性。
以上面的 Polygon 為例:
const polygonStyle = {
color: "#ff0000",
// weight 控制線的粗細
weight: 8,
// opacity 控制透明度
opacity: 0.65,
// 如果沒有fillColor選項,則由 color 控制填色
fillColor: "#B0DE5C",
fillOpacity: 0.8
};
const polygons = L.geoJSON(polygonJSON, {
style: polygonStyle
}).addTo(map);
也可以將 style 當成方法來使用,以下為官網範例:
var states = [{
"type": "Feature",
"properties": {"party": "Republican"},
"geometry": {
"type": "Polygon",
"coordinates": [[
[-104.05, 48.99],
[-97.22, 48.98],
[-96.58, 45.94],
[-104.03, 45.94],
[-104.05, 48.99]
]]
}
}, {
"type": "Feature",
"properties": {"party": "Democrat"},
"geometry": {
"type": "Polygon",
"coordinates": [[
[-109.05, 41.00],
[-102.06, 40.99],
[-102.03, 36.99],
[-109.04, 36.99],
[-109.05, 41.00]
]]
}
}];
L.geoJSON(states, {
style: function(feature) {
switch (feature.properties.party) {
case 'Republican': return {color: "#ff0000"};
case 'Democrat': return {color: "#0000ff"};
}
}
}).addTo(map);
onEachFeature
onEachFeature是一個函數,在每一個空間資訊要被加入 geoJSON 圖層的時候會被調用。常見於要將 properties 中所帶的資訊呈現在 popup 的時候(官網範例):
function onEachFeature(feature, layer) {
// does this feature have a property named popupContent?
if (feature.properties && feature.properties.popupContent) {
layer.bindPopup(feature.properties.popupContent);
}
}
var geojsonFeature = {
"type": "Feature",
"properties": {
"name": "Coors Field",
"amenity": "Baseball Stadium",
"popupContent": "This is where the Rockies play!"
},
"geometry": {
"type": "Point",
"coordinates": [-104.99404, 39.75621]
}
};
L.geoJSON(geojsonFeature, {
onEachFeature: onEachFeature
}).addTo(map);
pointToLayer
如果要在將 geoJSON 中的點資訊畫在地圖上,而不使用預設的 marker ,必須使用 pointToLayer 這個方法(官網範例)。
var geojsonMarkerOptions = {
radius: 8,
fillColor: "#ff7800",
color: "#000",
weight: 1,
opacity: 1,
fillOpacity: 0.8
};
L.geoJSON(someGeojsonFeature, {
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, geojsonMarkerOptions);
}
}).addTo(map);
filter
可以用來過濾 geoJSON 的屬性,常常用來控制圖徵地可見性。(官網範例)
Busch Field 因為 show_on_map 屬性為 false,所以不會顯示在地圖上。
var someFeatures = [{
"type": "Feature",
"properties": {
"name": "Coors Field",
"show_on_map": true
},
"geometry": {
"type": "Point",
"coordinates": [-104.99404, 39.75621]
}
}, {
"type": "Feature",
"properties": {
"name": "Busch Field",
"show_on_map": false
},
"geometry": {
"type": "Point",
"coordinates": [-104.98404, 39.74621]
}
}];
L.geoJSON(someFeatures, {
filter: function(feature, layer) {
return feature.properties.show_on_map;
}
}).addTo(map);
參考資料:
