Leaflet

setting

  • map tolerance

marker

async pointToLayer?

  • Leaflet: async alternative to pointToLayer?
  • 下面這樣也可以
    ​​​​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
    ​​​​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

加上popup, marker位置偏移

有buffer的點位

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
    • 可用上方解答的code或在map加上 zoomAnimation: false
    • (有些情況下zoomAnimation: false 沒用)
    ​​​​/*
    ​​​​* 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));
    ​​​​};
    
    





KML




React-Leaflet

使用套件:

這次使用別人寫好的React Leaflet套件,而不是用原生的Leaflet

其實也是可用原生的leaflet去寫,寫法可以參考這篇 - Using Leaflet.js in a React Project: Build a Mapping Application,但忘記在哪一篇問答看到建議使用React版套件,所以還是用了寫好的React Leaflet套件,然後踩了一堆坑。

灰色底圖


這個狀態視窗改變大小之後,原本未顯示在可見視窗內的底圖會變成灰色,搜尋網路半天、參考了幾篇問答:

統整以上問答內的資訊,大概得出幾個解法:

  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套件的正規寫法如下:
    ​​​​const map = useMap()
    
    ​​​​useEffect(() => {
    ​​​​    setTimeout(() => { 
    ​​​​        map.invalidateSize(); 
    ​​​​    }, 250); 
    ​​​​}, [map])
    

前兩個解法其實React Leaflet文件上也有寫,有些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>
​    );

​}

置中子層的地圖元素的目的是為了避免設置地圖中心會有位移的狀況發生

不過寫上 style={{ height: '100vh', width: '100vw' }} 的缺點是,<MapContainer> 元件內的子層元件(child components),除了圖層可用滑鼠平移滑動外,其他子層元件可能會超出視窗範圍,例如圖中紅色區域的地圖要素不在黑框代表的視窗內:

如果要讓紅色區域的地圖要素浮動在藍色區域的響應式地圖上方,應該要把地圖要素抽離 <MapContainer> 以外,但如果地圖要素會用到拿來存取地圖的 const map = useMap hook,譬如 map.zoomIn()map.setView() 等,這個 useMap hook只限定在 <MapContainer> 以內使用,所以必須用其他方式來使用leaflet存取地圖的方法。


存取地圖方法

前一個問題是要怎麼在 <MapContainer> 組件以外的地方存取像是 zoomIn()setView() 這些地圖的方法,參考以下兩篇的作法:

因為還要配合專案全域(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

使用時跟一般的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>
}



空白標籤


如圖,專案已經用了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值確實也解決出現空白標籤的問題:


GeoJSON的data未更新




References