# [筆記]Web地圖開發工作坊
#### by 廖洧杰 / 六角學院校長
##### 2020/02/07
###### tags: `w3HexSchool`, `線上直播`
#### 共筆文件:[Leaflet + OpenStreetMap (OSM) 特訓班](https://quip.com/vdqYAiFHHkaV)
#### 直播錄影:[Leaflet + OpenStreetMap 地圖應用開發](https://www.youtube.com/watch?v=pUizu62dlnY)
---
## 前言
* 使用別人服務的 API,會有個各別的 KEY,依照其 KEY 收費
* Google Maps 要收費,很貴...
* [科技防疫|自製「超商口罩地圖」的工程師:地圖上線6小時,我收到60萬Google帳單](https://futurecity.cw.com.tw/article/1239?fbclid=IwAR37iKIX0O8pdEf-bf2_AnvOsbVhamgNMPVAtd4ipIQWo0zyloMulWaASjc)
* Google Maps, OSM(OpenStreetMap),... 都是地圖應用程式
* SPA => Js 底層應用要很熟:let, const, 立即函式, 閉包...
## Leaflet (Js 框架)
Leaflet 是一套開放原始碼的輕量級 JavaScript 網頁地圖函式庫。主要特色:簡單、方便、跨平台
* 載入圖資(地圖資料)
* 標示點(Maker)
* [官網](https://leafletjs.com/)
## OpenStreetMap (圖資--地圖資料)
OpenStreetMap (開放街圖,簡稱OSM) 採用類似Wiki的協作編輯以及開放的授權與格式,因而被稱之為「維基版的地圖」,也被視為Google最強大的競爭者。
##### 補充資料:[OpenStreetMap 台灣](https://osm.tw/)
### 地圖圖層概念
地圖由多個圖層組成,又每個圖層由多個圖磚(PNG 圖片)所組成
##### 補充資料:[圖層示意圖](https://www.androidpit.com/google-maps-gesture-controls)
## 實作
### 1. 載入地圖
先載入其 css 與 js
```javascript=
var map = L.map('map', {
center: [22.604799,120.2976256],
zoom: 3
});
// 設定地圖,把map定位在 #map,先定位在 center 座標,其縮放等級為 3
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
```
* L 表示 Leaflet的所有應用服務
* 使用 map 這個函式,傳入兩個參數:1. id為map的字串;2. 物件{地圖中心:[緯度, 經度], 縮放等級}
* tileLayer 內放入要用誰的圖資
### 2. 建立 UI Layer Marker
#### 建立圖標 (Marker)
```javascript=
L.marker([22.604799,120.2976256]).addTo(map)
.bindPopup('<h1>測試藥局</h1><p>成人口罩:50<br>兒童口罩:50</p>')
.openPopup();
// 我要加上一個 marker,並設定他的座標,同時將這個座標放入對應的地圖裡
```
* 把圖標(Marker)加入圖層中
* 彈跳方框(bindPopup)內可放入 HTML 標籤
* openPopup => 滑鼠滑過去就會顯示 Popup
#### 改變 Marker 顏色
https://github.com/pointhi/leaflet-color-markers
```javascript=
var greenIcon = new L.Icon({
iconUrl: 'https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
L.marker([51.5, -0.09], {icon: greenIcon}).addTo(map);
```
#### 兩個點以上 Marker
```javascript=
L.marker([22.604799,120.2976256], {icon: greenIcon}).addTo(map)
.bindPopup('<h1>測試藥局</h1><p>成人口罩:50<br>兒童口罩:50</p>')
L.marker([22.6066728,120.3015429]).addTo(map)
.bindPopup('<h1>IKEA</h1><p>成人口罩:50<br>兒童口罩:50</p>')
```
建立兩個 marker 物件
#### 利用for迴圈建立多個 Marker
```javascript=
var data = [
{'name':'軟體園區',lat:22.604799,lng:120.2976256},
{'name':'ikea',lat:22.6066728,lng:120.3015429}
]
for(var i =0;data.length>i;i++){
L.marker([data[i].lat,data[i].lng], {icon: greenIcon}).addTo(map)
.bindPopup('<h1>'+ data[i].name +'</h1>')
}
```
#### 效能處理
1. 載入額外的Js插件 [Leaflet.markercluster](https://github.com/Leaflet/Leaflet.markercluster) => 讓 Marker 群組化
```htmlmixed=
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.4.1/MarkerCluster.css"></link>
<link href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.4.1/MarkerCluster.Default.css"></link>
<div id="map"></div>
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.4.1/leaflet.markercluster.js"></script>
```
2. 新增一圖層,這圖層專門放 Marker 群組
```javascript=
var markers = new L.MarkerClusterGroup().addTo(map);;
```
3. 在該圖層上放入各個 Marker
```javascript=
for(let i =0;data.length>i;i++){
console.log(data[i].name)
markers.addLayer(L.marker([data[i].lat,data[i].lng], {icon: greenIcon}));
// add more markers here...
// L.marker().addTo(map)
// )
}
map.addLayer(markers);
```
### 3. 載入真實資料
```javascript=
var markers = new L.MarkerClusterGroup().addTo(map);;
// 開啟一個網路請求
var xhr = new XMLHttpRequest();
// 準備跟某伺服器要什麼資料
xhr.open("get","https://raw.githubusercontent.com/kiang/pharmacies/master/json/points.json");
// 執行要資料的動作
xhr.send();
// 當資料回傳時,下面語法就會自動觸發
xhr.onload = function(){
// 把字串String轉成物件陣列的Json格式,我要的是features內的陣列資料
var data = JSON.parse(xhr.responseText).features
// 依序把 marker 放入圖層內
for(let i =0;data.length>i;i++){ markers.addLayer(L.marker([data[i].geometry.coordinates[1],data[i].geometry.coordinates[0]], {icon: greenIcon}).bindPopup(data[i].properties.name));
}
map.addLayer(markers);
}
```
* ajax撈回來的資料都是字串String格式,所以一定要做 JSON.parse
### 4. 下if判斷式
下判斷,若無剩餘口罩,就顯示為紅色Marker,若還有則是綠色Marker
```javascript=
var map = L.map('map', {
center: [22.604799,120.2976256],
zoom: 16
});
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
var greenIcon = new L.Icon({
iconUrl: 'https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
var redIcon = new L.Icon({
iconUrl: 'https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
var markers = new L.MarkerClusterGroup().addTo(map);;
var xhr = new XMLHttpRequest();
xhr.open("get","https://raw.githubusercontent.com/kiang/pharmacies/master/json/points.json");
xhr.send();
xhr.onload = function(){
var data = JSON.parse(xhr.responseText).features
for(let i =0;data.length>i;i++){
var mask;
if(data[i].properties.mask_adult == 0){
mask = redIcon;
}else{
mask = greenIcon;
} markers.addLayer(L.marker([data[i].geometry.coordinates[1],data[i].geometry.coordinates[0]], {icon: mask}).bindPopup('<h1>'+data[i].properties.name+'</h1>'+'<p>成人口罩數量'+data[i].properties.mask_adult+'</p>'));
// add more markers here...
// L.marker().addTo(map)
// )
}
map.addLayer(markers);
}
```
### 5. Geolocation (地理位置定位)
HTML5 提供了 Geolocation API 讓使用者可以由 Web Apps 取得目前的位置。而基於隱私權的考量,這些 Web Apps 均必須取得使用者的許可之後,才能發佈位置資訊。
##### 參考資料:[MDN Web APIs-Geolocation](https://developer.mozilla.org/zh-TW/docs/Web/API/Geolocation/Using_geolocation)
##### 參考資料:[認識 HTML5 Geolocation API](https://sites.google.com/site/edreamer/html5-specialtraining/geolocation-google-maps-api)
##### 參考資料:[[30apis] Day 2 : Google Map Geolocation API](https://ithelp.ithome.com.tw/articles/10192680)
### 資料參考
* 示意介面參考(hackmd):https://g0v.hackmd.io/gGrOI4_aTsmpoMfLP1OU4A
* API 網址:https://raw.githubusercontent.com/kiang/pharmacies/master/json/points.json
### 額外補充
* [示範:使用 Vuejs 結合 Open Street Map 製作口罩地圖](https://www.youtube.com/watch?v=7CXnNMVMXeo)