OSM + Leaflet 學習筆記 3：定位、全螢幕、小地圖、列印、客製選單
Tags: osm, tutorial, leaflet, javascript
Published: 2022-08-20 14:34:47

## 本篇要解決的問題

之前寫過二篇 OSM + Leaflet.js 的筆記文，當時總覺得跟 Google Maps 比起來有點陽春。前陣子有一次搭客運到宜蘭，瞄到客運員工開了一個網頁在看各車次到了哪裡，使用的就是 OpenStreetMap（OSM），當時看到就覺得，原來 OSM 還可以這樣子用呀（筆記）~

前幾天回頭看官方文件，發現文件裡有一頁是「[Plugins](https://leafletjs.com/plugins.html)」，裡面洋洋灑灑地列出了一堆套件，讓使用地圖的人可以直接套用，減少開發時間。

本篇就是先看了幾個套件，選出幾個覺得未來可能會到的，並實作出一頁 Demo 來，把這些套件都套進地圖裡。

選出的套件有：

- 定位
- 全螢幕
- 右下方同步小地圖
- 列印
- 含 collapse 功能的客製選單

還有幾個套件也覺得好用，但五個套件在一篇文章裡有點多了，就看以後有沒有機會再寫第四篇學習筆記文。

OSM + Leaflet.js 的基本使用，像是繪製地圖、放 markder 等，請看前二篇筆記文，這邊不會有詳細的解釋。

- OSM + Leaflet 學習筆記 1：[建地圖、marker、事件、換圖層](https://www.letswrite.tw/leaflet-osm-basic/)
- OSM + Leaflet 學習筆記 2：[移動中心點、抓目前地點](https://www.letswrite.tw/leaflet-osm-locate/)

---

## SHP 轉 GeoJSON，座標轉 WGS84

這次先從政府資料開放平臺裡，選出「[國家公園、國家森林遊樂區及國家風景區範圍內之觀光景點(本島)](https://data.gov.tw/dataset/73234)」當地圖上的點。

結果，抓出來的資料格式是 SHP，需要轉成 GeoJSON 後才能使用。

然後就踩坑了。

主要的坑就是，當用了 [mapshaper](https://mapshaper.org/) 轉成 GeoJSON 後，因為政府提供的資料裡，座標是真的座標，但我們用的 OSM 是使用麥卡托投影法，座標必須轉成 WGS84 的格式，放上地圖時才會正確。

之前有寫過一篇「[D3.js、Vue 畫一個台灣地圖](https://www.letswrite.tw/d3-vue-taiwan-map/)」，裡面是靠 D3.js 原有的 function 去處理，而這邊是用 Leaflet.js，需要另尋出路。

Leaflet.js 文件上有看到一個「[coordsToLatLng](https://leafletjs.com/reference.html#geojson-coordstolatlng)」的 function，但不論試了各種寫法都沒有轉成功。

後來再 Google 查了一下，發現地圖投影法真的是另一個小宇宙，有夠複雜，投影法的介紹可以看這篇「[地理小課堂：那些被錯付的地圖投影法](https://www.mindiworldnews.com/20220617-2/)」，這邊就用最簡單的方式來做轉換。

這個簡單的方式就是：**在 mapshaper 轉成 GeoJSON 時，輸出前先將座標轉為 WGS84**。

這個方式主要來自於這篇：[How to make your coordinates WGS84 with mapshaper.org](https://help.flourish.studio/article/67-how-to-make-your-coordinates-wgs84-with-mapshaper-org)

1 將要轉出的檔案拉進 mapshaper 裡：

![將要轉出的檔案拉進 mapshaper](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/igsocgz81eovw17ax8y8.png)

2 點擊右上角的 Console，輸入 `-proj wgs84` 後按下 enter：

![console 裡輸入指令](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vt52ladgdrlvqcmvwox3.png)

3 點右上角的 Export，格式選 GeoJSON：

![Export 格式選 GeoJSON](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f3r17am0lfc8yismwfls.png)

以上，這樣匯出的 JSON 檔，裡面的座標就會是 WGS84 格式，可以正確放上地圖。

---

## 定位：Leaflet.Locate

官方文件：[https://github.com/domoritz/leaflet-locatecontrol](https://github.com/domoritz/leaflet-locatecontrol)

官方示例：[https://domoritz.github.io/leaflet-locatecontrol/demo/](https://domoritz.github.io/leaflet-locatecontrol/demo/)

這個套件蠻好用的，除了有定位光點的樣式，還給了目前朝哪的方向。

不過遇到一個很奇怪的狀況，JS 檔如果是引用 CDN 的，就會一直抓不準定位，誤差距離會到二公里以上，只有直接從 GitHub 上把 JS 下載下來使用，定位才正常。

實測了好幾次都是這樣，所以大家如果要用，建議直接從 GitHub 裡抓 JS。

CSS 則可以引用 CDN，CSS 裡引用圖檔的部份就不用再另外下載。

Leaflet.Locate 使用方式很簡單，當地圖渲染完成後，JS 如下：

```javascript
L.control.locate({
  position: 'topleft',
  locateOptions: {
    enableHighAccuracy: true
  },
  strings: {
    title: '定位我的位置',
    metersUnit: '公尺',
    feetUnit: '英尺',
    popup: '距離誤差：{distance}{unit}以內'
  },
  clickBehavior: {
    inView: 'stop',
    outOfView: 'setView',
    inViewNotFollowing: 'inView'
  }
}).addTo(map);
```

`position`：定位按鈕要放地圖的哪個位置。

`enableHighAccuracy`：要不要啟用精準定位。

`strings` ：顯示文字的部份。這邊是 August 自行翻譯的，其中 `popup` 內容跟原本的不太一樣，但覺得說明誤差距離會比較好懂意思。

`clickBehavior` ：點擊執行按鈕時，不同狀況要給的不同動作。

這套使用起來很簡單，文件中也列出了一堆參數，想看還有什麼可以使用的參數請自行上官方文件查看。

---

## 全螢幕：Leaflet.Control.FullScreen

官方文件：[https://github.com/brunob/leaflet.fullscreen](https://github.com/brunob/leaflet.fullscreen)

官方示例：[https://brunob.github.io/leaflet.fullscreen/](https://brunob.github.io/leaflet.fullscreen/)

一般嵌入地圖在網頁上，很少會是全螢幕的嵌入，因為當地圖是全螢幕時，滑鼠或手勢的操作就會全被地圖的操作給吃掉，頁面的其它部份就不容易被看到。

不過如果是只嵌在網頁上的一小塊，桌機時還好，畢竟螢幕夠大的話還是可以看見地圖內容。

但手機就不方便了，像是本篇的 Demo 加了一堆按鈕上去，地圖本身的內容就會被擋住一大部份。

這時有個全螢幕按鈕讓地圖是全螢幕呈現，就很方便。

使用方式很簡單，當地圖渲染完成後，JS 如下：

```javascript
L.control.fullscreen({
  position: 'topleft',
  title: '進入全螢幕',
  titleCancel: '離開全螢幕',
  content: '<img class="p-1" src="dist/size-fullscreen.svg">',
  forceSeparateButton: true,
  forcePseudoFullscreen: true,
  fullscreenElement: false
}).addTo(map);
```

`position` ：全螢幕按鈕要放地圖的哪個位置。

`content` ：全螢幕按鈕裡的 HTML，這邊是放一張 svg 的圖檔。

`forceSeparateButton` ：全螢幕按鈕要不要跟縮放按鈕分開來。

`forcePseudoFullscreen` ：全螢幕要玩真的還是玩假的 XD~

`true` 代表要執行的是假的全螢幕、`false` 代表是真的全螢幕。

**假的** 就是讓地圖的寬高塞滿頁面而已，瀏覽器上的那些網址列或書籤列都還看得到。

**真的** 就是像 Youtube 上我們點全螢幕一樣，會看到一個滿滿的地圖大平台。

`fullscreenElement` ：看了文件看不懂意思，只知道當設為 `true` 時，就必須要在點擊按鈕後再執行某個 callback，不然全螢幕功能會失效。

套件裡還給了二個事件：`enterFullscreen`（進入全螢幕）、`exitFullscreen`（離開全螢幕）。

本篇的 Demo 有使用到這二個事件，拿來做全螢幕按鈕上的圖片切換：

```javascript
const fullscreenBtn =
	document.querySelector('.leaflet-control-zoom-fullscreen');
map.on('enterFullscreen', () => {
  fullscreenBtn.innerHTML =
    '<img src="dist/fullscreen-exit.svg">';
});
map.on('exitFullscreen', () => {
  fullscreenBtn.innerHTML =
    '<img src="dist/size-fullscreen.svg">';
});
```

---

## 小地圖：Leaflet.MiniMap

官方文件：[https://github.com/Norkart/Leaflet-MiniMap](https://github.com/Norkart/Leaflet-MiniMap)

這個套件就是在地圖的右下角再放一個小地圖，可以同步大地圖顯示目前我們選擇的地方。

![小地圖功能示例](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hlgsasgeqtppuzhv4k46.png)

它的作法其實就是嵌入第二個地圖，因此我們在寫渲染地圖的部份，可以把相同的參數設成變數。

```javascript
// 相同的參數存成變數
const center = {地圖中心點};
const zoom = {縮放值};
const map = L.map('map').setView(center, zoom);
const osmUri = {Tiles 的檔按路徑};
const attribution = {版權宣告內容};

// 主要地圖
L.tileLayer(osmUri, {
  attribution: attribution
}).addTo(map);

// 小地圖
const miniOSM = new L.TileLayer(osmUri, {
  minZoom: 0, maxZoom: 13,
  attribution: attribution
});
```

---

## 列印：leaflet-easyPrint

官方文件：[https://github.com/rowanwins/leaflet-easyPrint](https://github.com/rowanwins/leaflet-easyPrint)

官方示例：[https://rowanwins.github.io/leaflet-easyPrint/](https://rowanwins.github.io/leaflet-easyPrint/)

這個……其實也不確定功能的實用性，但 Google Maps 有分享地圖的功能，而 OSM 目前還沒看到分享功能之類的套件，就先拿列印功能來充數，至少可以讓其它人知道我們看到的地圖長什麼樣子。

```javascript
L.easyPrint({
  title: '列印地圖',
  position: 'topleft',
  sizeModes: ['Current', 'A4Portrait', 'A4Landscape']
}).addTo(map);
```

`sizeModes` 是要列印成哪些格式，預設有三種，也可以另外自己去設定寬高。

完整的參數就請自行看文件囉。

---

## 客製選單：sidebar-v2

官方文件：[https://github.com/turbo87/sidebar-v2/](https://github.com/turbo87/sidebar-v2/)

官方示例：[https://turbo87.github.io/sidebar-v2/examples/index.html](https://turbo87.github.io/sidebar-v2/examples/index.html)

這套用起來稍微麻煩的地方，就是必須要調整 HTML，它的作法就是另外寫好一個 collapse 的 HTML，然後塞進地圖裡。

如果想要自己寫一個不一樣的選單，是可以自己花時間寫，用套件單純就是為了節省開發時間。

程式碼的部份，可以看官方提供的：

[https://github.com/Turbo87/sidebar-v2/blob/master/examples/index.html](https://github.com/Turbo87/sidebar-v2/blob/master/examples/index.html)

本篇 Demo 因為有寫了讓地圖維持在 16:9、4:3 的比例，所以 HTML、CSS 都有再做修改，跟官方提供的程式碼有不同，各位如果要使用，建議是看原本的程式碼，複製貼上後再針對各自的頁面去修改樣式。

除了 sidebar-v2，也有蠻多客製按鈕的套件很不錯，以下列出，可自行看示例選擇要不要使用：

- [Leaflet.TileLegend](https://github.com/yohanboniface/Leaflet.TileLegend)
- [Leaflet.Control.Custom](https://github.com/yigityuce/Leaflet.Control.Custom)
- [Leaflet.Legend](https://github.com/ptma/Leaflet.Legend)
- [Leaflet.SidePanel](https://github.com/maxwell-ilai/Leaflet.SidePanel)

---

## 本篇 Demo 及原始碼

最後附上本篇的 Demo 網址，及原始碼的 Github 網址。

Demo：[https://letswritetw.github.io/letswrite-leaflet-plugins/](https://letswritetw.github.io/letswrite-leaflet-plugins/)

GitHub：[https://github.com/letswritetw/letswrite-leaflet-plugins](https://github.com/letswritetw/letswrite-leaflet-plugins)

---

## OSM + Leaflet 學習筆記系列

[OSM + Leaflet 學習筆記 1：建地圖、marker、事件、換圖層](https://www.letswrite.tw/leaflet-osm-basic/)

[OSM + Leaflet 學習筆記 2：移動中心點、抓目前地點](https://www.letswrite.tw/leaflet-osm-locate/)

[OSM + Leaflet 學習筆記 3：定位、全螢幕、小地圖、列印、客製選單](https://www.letswrite.tw/leaflet-plugins/)