OSM + Leaflet 學習筆記 1：建地圖、marker、事件、換圖層
Tags: openstreetmap, leaftjs, map, tutorial
Published: 2022-07-26 02:54:06

## 本篇要解決的問題

之前有寫過一系列的 [Google Maps API](https://letswrite.tw/category/google/maps/) 學習筆記。Google Maps 好用，但過了一定的額度會收費，當時就有人留言問說免費地圖的事，有留上了心。

原本是直接找 OpenStreetMap 的 API，後來看到文件上回來的都是用 XML，實在是懶得處理轉 JSON 這段。之後幸運地找到一個可以接 OpenStreetMap 的 Leaflet，一個用 JavaScript 接 API 的開源碼，就翻了一下文件，整理成這篇。

本篇會示範的功能如下：

1. 使用者請求地點，建立地圖
2. 放置 + 客製 marker
3. Popup
4. Tooltip
5. Map click event
6. 外部 zoom in、zoom out
7. 改變地圖樣式

本篇主要參考文件：[Leaflet Quick Start Guide](https://leafletjs.com/examples/quick-start/)

後續各段會用到的參考文件，將寫在各段之中。

本篇最後會完成的 Demo：

<https://letswritetw.github.io/letswrite-leaflet-osm-basic/>

---

## 請求地點，建立地圖

跟 Google Maps API 一樣，Leaflet 在建立地圖時需要一個中心點。

跟使用者請求所在位置座的座標，需要使用者同意，因此也要先預設一個當使用者拒絕時，或是所擁有的裝置不支援提供所在座標時的地點。

本篇預設地點設為台北市立動物園~ 想去看水豚君啊~~~

關於找地點的座標，August 用的是比較簡單的方法，如果有人有更好的方法歡迎留言提供。

首先進到 [Google Maps](https://www.google.com.tw/maps/@23.546162,120.6402133,8z?hl=zh-TW) 的網站，搜尋想要知道座標的地點，我們這邊搜尋「動物園」後，選到動物園，接著按下資料卡片的「分享」：

![點擊分享](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ld10gz674y3wwn0ausz.png)

接著點擊「複製連結」：

![點擊「複製連結」](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a2t7frhj9jm8a4ehw9bq.png)

然後把複製的網址貼到瀏覽器上打開，短網址會回復成原網址，就能從網址中看見座標，像這樣：

![從網址中看見座標](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m1tvtxcdga4af921v8y6.png)

這是 August 知道不用接任何 API 就能找到一個地點座標的方式。

### 跟使用者要權限

當我們跟使用者要權限時，使用者會看到這樣的請求：

![跟使用者要位置資訊](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x144l6g0lil37k11zo0z.png)

當使用者按下了「允許」，我們就可以得到使用者的座標，程式碼如下：

{% embed https://gist.github.com/letswritetw/9e7c95bd0da71c4cdd7dff00f7c5744e %}

### 建立地圖

不管有沒有取到使用者的所在位置，都一樣可以建立 OpenStreepMap 的地圖。

首先先引用 CSS、JS：

```html
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.8.0/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.8.0/dist/leaflet.js"></script>
```

接著在我們要放置地圖的地方，放上一個空 div 並帶上我們指定的 id：

```html
<div id="map"></div>
```

下一步，給我們的 div 一個高度：

```css
#map {
  height: 180px;
}
```

高度的部份本篇的 Demo 是用 `50vh`，實際應用時依各頁面的狀況再作修改。

最後就是用 Leaflet 來建立地圖：

{% embed https://gist.github.com/letswritetw/e0a976d6e28bba31a68292bfe54eb04b %}

Options 的部份可自行參考 [文件](https://leafletjs.com/reference-1.7.1.html#map-option)，這邊僅列出 August 覺得會用到的。

`L.tileLayer` 這個是指地圖要讀取哪一個樣式，文件中的預設樣式就是這個：

```
https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png
```

少了 `tileLayer` 地圖會是一片灰。

開啟頁面後，地圖就出來了：

![建立地圖完成](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tdooli0hymhh9acvod0k.png)

---

## 放置 Marker

放置 marker 的方式就是新增一個出來後，add 到 map 裡，程式碼如下：

{% embed https://gist.github.com/letswritetw/1061ed78517b09a6acae4e4153d54c90 %}

`center` 就是要放置的座標。

預設的 Maker 樣式如下：

![預設的 Maker 樣式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/flokut6to4e74ndp8zij.png)

### 客製 Maker 圖示

Marker 可以用客製的 icon，如下：

{% embed https://gist.github.com/letswritetw/1cfe23f63e1e248bd4888b8aed3da6be %}

我們這邊多放置幾個來看：

![客製 Marker](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/902xrcd1j5zbgtkg9p2i.png)

一次放置多個可以用迴圈，Demo 的原始檔會附上。

### Marker 加上 Popup

Marker 能加上 Popup 跟 Tooltip。

Popup 可以預設打開。

{% embed https://gist.github.com/letswritetw/cdd34af8e4f02dc963638cc5b52ae063 %}

`openPopup()` 就是打開 Popup。

Popup 長這個樣子：

![Popup 樣式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aze3b4smt1yrvtdldkyj.png)

### Marker 加上 Tooltip

Tooltip 就是我們常見的 Tooltip（嗯？有寫跟沒寫一樣）。

一樣可以預設要不要打開，也可以設定顯示的位置：

{% embed https://gist.github.com/letswritetw/9af14f0ca033ccec87fcd61f35f278fb %}

Tooltip 長這樣：

![Tooltip 樣式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ir9yapdhetpx72rs5yp7.png)

---

## Map 點擊事件

Leaflet 可以監聽 Map 的點擊。

在官方的範例就有示範點擊地圖後，加上一個 Popup 寫著此處座標，這邊 August 改寫一下：

{% embed https://gist.github.com/letswritetw/6fc4294370f2fdf16e81686fbd3655f6 %}

隨便點擊地可上任一地方，就會出現寫著座標的 Popup：

![點擊後顯示座標](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/02drne24liw0vte3tydb.png)

---

## zoom in, zoom out

除了地圖上預設的功能鍵，也可以用地圖外的按鈕，用 function 執行 zoom in、zoom out。

```javascript
map.zoomIn(1);
map.zoomOut(1);
```

數字是一次要 zoom 多少，實際測了一下，zoom 的值是 0 – 18。

0 是世界地圖，18 就是到街景了。

---

## 改變地圖樣式 Layer

參考：[How to change base layer using JS and leaflet layers control](https://stackoverflow.com/questions/33759578/how-to-change-base-layer-using-js-and-leaflet-layers-control)

除了預設的地圖樣式，還有蠻多種樣式可以使用的，在 [Leaflet Provider Demo](https://leaflet-extras.github.io/leaflet-providers/preview/) 上可以看到多種，大部份也都有附上 Layer 的網址。

不過，有些不確定是否有收費，因為看到需要 token。

改變地圖樣式的按鈕，可以放在地圖之中，也可以在地圖之外放按鈕控制。

### 在地圖之內

在地圖之內，總覺得樣式有點陽春 XD~

但勝在方便。

{% embed https://gist.github.com/letswritetw/2e8a27ee3a4c16d8fc576ef4d518a3d4 %}

把控制選項放在地圖上，長這樣：

![地圖上有 Layer 的控制選項](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ga3qogl5ie5hx2x400m2.png)

Hover 時會秀出選單：

![秀出 Layer 選單](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fd4aph4q1apg786jx2mh.png)

實際使用，覺得切換的時間有點久，大家看 Demo 時要等一下。

最喜歡的是深色的一款 [Stadia.AlidadeSmoothDark](https://leaflet-extras.github.io/leaflet-providers/preview/#filter=Stadia.AlidadeSmoothDark)，不過這款免費使用下有限制，可以看 [官網](https://stadiamaps.com/) 說明。

### 在地圖之外

改變 Layer 主要是這行：

```javascript
L.tileLayer('layer 的 url').addTo(map);
```

因此要用地圖外面的按鈕來切換地圖的樣式，只需要寫 click 時執行上面那行就行。

---

## 本篇 Demo 及原始碼

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

Demo：<https://letswritetw.github.io/letswrite-leaflet-osm-basic/>

Github：<https://github.com/letswritetw/letswrite-leaflet-osm-basic>