--- title: Leaflet Note description: tags: map lang: zh-tw robots: noindex, nofollow --- {%hackmd BkVfcTxlQ %} # Leaflet # setting - map tolerance ![](https://hackmd.io/_uploads/rJrcE1eyp.png) ## 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/)套件,然後踩了一堆坑。 ### 灰色底圖 ![](https://i.imgur.com/6R7FG9Y.png) 這個狀態視窗改變大小之後,原本未顯示在可見視窗內的底圖會變成灰色,搜尋網路半天、參考了幾篇問答: - [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),除了圖層可用滑鼠平移滑動外,其他子層元件可能會超出視窗範圍,例如圖中紅色區域的地圖要素不在黑框代表的視窗內: ![](https://i.imgur.com/1ri9ai7.png) 如果要讓紅色區域的地圖要素浮動在藍色區域的響應式地圖上方,應該要把地圖要素抽離 `<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/> ### 空白標籤 ![](https://i.imgur.com/pTTgRzt.png) 如圖,專案已經用了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)