---
title: Leaflet Note
description:
tags: map
lang: zh-tw
robots: noindex, nofollow
---
{%hackmd BkVfcTxlQ %}
# Leaflet
# setting
- map tolerance

## marker
### async pointToLayer?
- [Leaflet: async alternative to pointToLayer?](https://stackoverflow.com/a/62314275/18073178)
- 下面這樣也可以
```
pointToLayer(){
const layerGroup = L.layerGroup();
(async ()=>{
...
})();
return layerGroup;
}
```
### zoom in and show all tooltip
- [Open the popups of all markers that are visible on the map with a zoom > 10](https://stackoverflow.com/questions/55745384/open-the-popups-of-all-markers-that-are-visible-on-the-map-with-a-zoom-10)
```javascript
const marker = L.marker(latlng, {
icon: L.icon({
iconUrl: iconUrl,
iconAnchor: [16, 32],
iconSize: [32, 32],
popupAnchor: [0, -16],
}),
})
.bindPopup(popupContent)
.bindTooltip(`${feature.properties.location_name}`)
.addTo(layerGroup);
map.on("zoomend", function () {
if (map.getZoom() >= 16) {
marker.openTooltip(latlng);
} else {
marker.closeTooltip();
}
});
```
### marker位置偏移
沒有設定anchor
- [LeafletJS markers move on zoom](https://stackoverflow.com/questions/17875438/leafletjs-markers-move-on-zoom)
### 加上popup, marker位置偏移
- 改成加在「layer group」,不要加在layer
- 還要確定為何加在marker就會偏移?
- [Bug while removing a marker with opened popup](https://github.com/Leaflet/Leaflet/issues/2762)
### 有buffer的點位
- 參考這兩篇:
- [Creating buffer around point in LeafletJS?](https://gis.stackexchange.com/a/79566)
- [Custom marker and properties from geoJSON in Leaflet](https://gis.stackexchange.com/a/227146)
<br/><br/>
## Popup & Tooltip
### zoom animation error (leaflet bugs in Vue3)
- error msg: `Cannot read property '_latLngToNewLayerPoint' of null`
- [Leaflet error when zoom after close popup in lightning component](https://salesforce.stackexchange.com/questions/180977/leaflet-error-when-zoom-after-close-popup-in-lightning-component/378048#378048)
- 可用上方解答的code或在map加上 `zoomAnimation: false`
- (`有些情況下zoomAnimation: false` 沒用)
```js
/*
* The error occurrs in Vue3 because of proxy variable
*/
// Fix zoom animation error when popup is opened and cannot be fixed using "zoomAnimation: false"
// *Code refer to: https://salesforce.stackexchange.com/a/378048
L.Popup.prototype._animateZoom = function (e) {
if (!this._map) {
return;
}
var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
anchor = this._getAnchor();
L.DomUtil.setPosition(this._container, pos.add(anchor));
};
// Fix error when markers bind tooltip
// *The error only occurrs when toggle flood sensor's modal (set another map) and cannot be fixed using "zoomAnimation: false"
L.Tooltip.prototype._animateZoom = function (e) {
if (!this._map) {
return;
}
var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
anchor = this._getAnchor();
L.DomUtil.setPosition(this._container, pos.add(anchor));
};
// *The error occurrs because set another map
L.Tooltip.prototype._updatePosition = function () {
if (!this._map) {
return;
}
var pos = this._map.latLngToLayerPoint(this._latlng),
anchor = this._getAnchor();
L.DomUtil.setPosition(this._container, pos.add(anchor));
};
```
<br/>
<br/><br/><br/>
-----
# KML
- [What is the best way to load KML layers on Leaflet?](https://stackoverflow.com/a/57350416/18073178)
- [leaflet-omnivore @NPM](https://www.npmjs.com/package/@mapbox/leaflet-omnivore)
- [leaflet-omnivore @GitHub](https://github.com/mapbox/leaflet-omnivore)
- [Leaflet-omnivore load multiple KMLs & center](https://gis.stackexchange.com/questions/267951/leaflet-omnivore-load-multiple-kmls-center)
- [Leaflet KML layer plugin @GitHub](https://github.com/windycom/leaflet-kml)
- [Display .kml file in Leaflet](https://gis.stackexchange.com/questions/331956/display-kml-file-in-leaflet)
- [[6-2] KML & GeoJSON - 以Leaflet KML layer plugin實現](https://ithelp.ithome.com.tw/articles/10248058?sc=hot)
- [leaflet使用L.KML.js插件上传本地kml文件到leaflet中](https://blog.csdn.net/q124467623/article/details/122107766)
<br/><br/>
-----
# React-Leaflet
使用套件:
- [react-leaflet](https://react-leaflet.js.org/)
- [react-leaflet-cluster](https://github.com/akursat/react-leaflet-cluster)
這次使用別人寫好的[React Leaflet](https://react-leaflet.js.org/)套件,而不是用[原生的Leaflet](https://leafletjs.com/)。
其實也是可用原生的leaflet去寫,寫法可以參考這篇 - [Using Leaflet.js in a React Project: Build a Mapping Application](https://javascript.plainenglish.io/using-leaflet-js-in-a-react-project-build-a-mapping-application-f97aeef25c33),但忘記在哪一篇問答看到建議使用React版套件,所以還是用了寫好的[React Leaflet](https://react-leaflet.js.org/)套件,然後踩了一堆坑。
### 灰色底圖

這個狀態視窗改變大小之後,原本未顯示在可見視窗內的底圖會變成灰色,搜尋網路半天、參考了幾篇問答:
- [react-leaflet map not correctly displayed](https://stackoverflow.com/questions/40365440/react-leaflet-map-not-correctly-displayed)
- [Map is not visible at initialization using react-leaflet](https://stackoverflow.com/questions/59856361/map-is-not-visible-at-initialization-using-react-leaflet)
- [Leaflet Map not showing in bootstrap div](https://stackoverflow.com/questions/31030949/leaflet-map-not-showing-in-bootstrap-div?rq=1)
- [React Leaflet - TileLayer does not render when it is first loaded](https://stackoverflow.com/questions/65286018/react-leaflet-tilelayer-does-not-render-when-it-is-first-loaded)
- [Next.js React Leaflet Map not Showing Properly](https://stackoverflow.com/questions/68267395/next-js-react-leaflet-map-not-showing-properly)
- [Map size not invalidated on height / width change](https://github.com/PaulLeCam/react-leaflet/issues/340)
統整以上問答內的資訊,大概得出幾個解法:
1. `import "leaflet/dist/leaflet.css";`
2. 給予width、height
這個解法要分別在`.leaflet-containter` 和 `<MapContainer></MapContainer>` 寫上寬高:
```
// .css
.leaflet-container {
width: 100vw;
height: 100vh;
}
```
```
// app.jsx
import {MapContanter} from 'react-leaflet';
export default function App(){
return (
<MapContainer
// style這行
style={{ height: '100vh', width: '100vw' }}
>
...
</MapContainer>
);
}
```
3. `map.invalidateSize()`
若是使用[React Leaflet](https://react-leaflet.js.org/)套件的正規寫法如下:
```
const map = useMap()
useEffect(() => {
setTimeout(() => {
map.invalidateSize();
}, 250);
}, [map])
```
前兩個解法其實[React Leaflet文件上也有寫](https://react-leaflet.js.org/docs/start-setup/),有些stackoverflow解答會說只要給高度 `100%` 或 `100vh`,但因為專案在電腦版和手機版都要做響應式設計,所以最後是用 `style={{ height: '100vh', width: '100vw' }}` 解決,而且最好要包一個親層元件(parent component),讓地圖只在親層元件範圍內有響應式的設計,不會佔用到親層元件以外的版面。
此外,以地圖寬度的 `100vw` 為例,若地圖外圍包了一個親層元件,事實上子層(child element)的地圖並不會置中,**若想要置中寬度為100vw的子層元素,要在子層元素的CSS再加上:**
```
position: relative;
left: 50%;
transform: translateX(-50%);
```
也就是說地圖的CSS還得這樣修改:
```
// app.jsx
import {MapContanter} from 'react-leaflet';
export default function App(){
return (
<MapContainer
// style這行
style={{
height: '100vh',
width: '100wh',
position: 'relative',
left: '50%',
transform: 'translateX(-50%)'
}}
>
...
</MapContainer>
);
}
```
置中子層的地圖元素的目的是為了**避免設置地圖中心會有位移的狀況發生**
<br/>
不過寫上 `style={{ height: '100vh', width: '100vw' }}` 的缺點是,`<MapContainer>` 元件內的子層元件(child components),除了圖層可用滑鼠平移滑動外,其他子層元件可能會超出視窗範圍,例如圖中紅色區域的地圖要素不在黑框代表的視窗內:

如果要讓紅色區域的地圖要素浮動在藍色區域的響應式地圖上方,應該要把地圖要素抽離 `<MapContainer>` 以外,但如果地圖要素會用到拿來存取地圖的 `const map = useMap` hook,譬如 `map.zoomIn()`、`map.setView()` 等,這個 `useMap` hook只限定在 `<MapContainer>` 以內使用,所以必須用其他方式來使用leaflet存取地圖的方法。
<br/><br/>
### 存取地圖方法
前一個問題是要怎麼在 `<MapContainer>` 組件以外的地方存取像是 `zoomIn()`、`setView()` 這些地圖的方法,參考以下兩篇的作法:
- [How to call useMap() outside of the file where <MapContainer> is NOT called?](https://stackoverflow.com/questions/71104753/how-to-call-usemap-outside-of-the-file-where-mapcontainer-is-not-called)
- [React + leaflet + routing machine : Problem accessing map outside MapContainer](https://stackoverflow.com/questions/71516019/react-leaflet-routing-machine-problem-accessing-map-outside-mapcontainer)
因為還要配合專案全域(global)使用這些地圖方法的需求,最後我是在context裡寫了一個 `const mapRef = useRef(null)`,然後:
```
// app.jsx
import {useContext} from 'react';
import {MapContext} from 'store';
import {MapContanter} from 'react-leaflet';
export default function App(){
const mapContext = useContext(MapContext);
return (
<MapContainer
ref={mapContext.mapRef}
style={{ height: '100vh', width: '100wh' }}
>
...
</MapContainer>
);
}
```
React全域狀態context的寫法可參考React新文件 ─ [Scaling Up with Reducer and Context](https://react.dev/learn/scaling-up-with-reducer-and-context)。
<br/>
使用時跟一般的ref一樣:
```
import {useContext} from 'react';
import {MapContext} from 'store';
import * as L from 'leaflet';
export default function ZoomInButton(){
const mapContext = useContext(MapContext);
return <button
onClick={
() => (mapContext.mapRef as unknown as React.MutableRefObject<L.Map>).current.zoomIn()
}
>放大地圖</button>
}
```
<br/><br/>
### 空白標籤

如圖,專案已經用了boolean型別的state來控制 `<Tooltip>` 的顯示與否,例如:
```
import {useState} from 'react';
export default function Map(){
const [isShow, setIsShow] = useState(false);
return(
<LayerGaroup>
{markers.map(marker=>
<Marker>
{isShow && <Tooltip>{marker.title}</Tooltip>}
</Marker>
)}
</LayerGaroup>
)
}
```
但是在state等於 `false` 時地圖上的 `<Tooltip>` 卻沒有完全消失。一開始有想到可能是沒有給key的問題,但發現React會render兩次,所以會出現key重複出現的錯誤。後來看了以下類似案例是用uuid解決,給定唯一的key值確實也解決出現空白標籤的問題:
- [Empty ghost Popup when dynamically rendering popup in react-leaflet](https://stackoverflow.com/questions/65151457/empty-ghost-popup-when-dynamically-rendering-popup-in-react-leaflet)
- [Empty ghost popup left over when dynamically rendering marker popup #832](https://github.com/PaulLeCam/react-leaflet/issues/832)
<br/>
## GeoJSON的data未更新
- [GeoJSON data not rendered after change](https://github.com/PaulLeCam/react-leaflet/issues/332)
<br/><br/>
---
## References
- [Using Leaflet.js in a React Project: Build a Mapping Application](https://javascript.plainenglish.io/using-leaflet-js-in-a-react-project-build-a-mapping-application-f97aeef25c33)
- [Tutorial: React ⚛ + Leaflet 🗺](https://dev.to/maj07/tutorial-react-leaflet-d65)
- [how to set child Div with 100vw of device width when parent div having width :80vw](https://stackoverflow.com/questions/45371811/how-to-set-child-div-with-100vw-of-device-width-when-parent-div-having-width-80)