????????在現代的地理信息系統(GIS)應用中,地圖功能是不可或缺的一部分。無論是展示商業網點、旅游景點還是公共服務設施,地圖都能以直觀的方式呈現數據。然而,當數據量較大時,地圖上可能會出現大量的標記點,這不僅會影響用戶體驗,還會導致性能問題。因此,我們需要一種方法來智能地聚合這些標記點,同時允許用戶在需要時查看詳細信息。
Leaflet 是一個輕量級的開源 JavaScript 地圖庫,它提供了豐富的 API 和插件支持,非常適合實現這種功能。結合 Leaflet.markercluster 插件,我們可以輕松實現標記點的聚合和動態顯示。
項目背景
在最近開發中,我遇到了一個有趣的需求:實現基于 Leaflet 的地圖功能,支持多類型點位標記和聚合。這個功能需要展示不同類型的數據點,并在地圖縮放時智能聚合,以避免地圖過于擁擠。經過一番努力,我成功實現了這一功能,并在此分享我的經驗和代碼實現。
實現思路
1. 數據結構設計
在實現功能之前,我們需要定義清晰的數據結構,以便更好地管理和展示不同類型的數據點。以下是點位數據的格式示例:
markerData: [{latitude: "23.131828",longitude: "113.326821",name: "麥當勞(正佳廣場五樓店)",address: "廣州市天河區天河南街道天河路228號正佳廣場五樓561鋪",type: "fast_food"},{latitude: "23.116523",longitude: "113.390699",name: "肯德基(車陂南店)",address: "廣州市天河區車陂路6號一層",type: "fast_food"}...
]
此外,我們還需要定義支持的標記點類型和對應的樣式配置:
markerType: ["fast_food","coffee","landmark","tourist","shopping","entertainment","transport"
],typeList: [{name: "全部",type: "all",color: "#000000",iconUrl: require("../../assets/img/all.png")},{name: "快餐店",type: "fast_food",color: "blue",iconUrl: require("../../assets/img/fast_food.png")},{name: "咖啡店",type: "coffee",color: "green",iconUrl: require("../../assets/img/coffee.png")},{name: "城市地標",type: "landmark",color: "orange",iconUrl: require("../../assets/img/landmark.png")},{name: "旅游景點",type: "tourist",color: "red",iconUrl: require("../../assets/img/tourist.png")},{name: "購物中心",type: "shopping",color: "purple",iconUrl: require("../../assets/img/shopping.png")},{name: "娛樂場所",type: "entertainment",color: "yellow",iconUrl: require("../../assets/img/entertainment.png")},{name: "交通設施",type: "transport",color: "teal",iconUrl: require("../../assets/img/transport.png")}
]
2. 地圖初始化
首先,我們需要初始化地圖。Leaflet 提供了簡單易用的 API 來創建地圖實例,并設置中心點、縮放級別等基本參數。在我們的項目中,我們使用了天地圖(Tianditu)作為地圖源,這需要通過 WMTS 服務加載矢量地圖和影像地圖圖層。
this.map = L.map("mapRef", {center: [23.111532, 113.324357], // 地圖中心zoom: 13, // 縮放比例zoomControl: false, // 是否顯示縮放按鈕doubleClickZoom: false, // 是否雙擊放大attributionControl: false, // 是否顯示右下角 Leaflet 標識minZoom: 3, // 最小縮放級別
});
3. 添加地圖圖層
我們使用了天地圖的矢量地圖和影像地圖圖層。需要注意的是,天地圖的訪問需要一個有效的 API 密鑰(tk
),并且圖層的 URL 需要按照 WMTS 服務的規范進行拼接。
const tiandiKey = "YOUR_TIANDITU_API_KEY"; // 替換為你的天地圖 API 密鑰
const mapUrl = `http://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tiandiKey}`;
const cvaLayer = L.tileLayer(`http://t0.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tiandiKey}`
);this.map.addLayer(L.tileLayer(mapUrl)); // 添加矢量地圖圖層
this.map.addLayer(cvaLayer); // 添加影像地圖圖層
注意:如果你在加載天地圖時遇到問題,請檢查 API 密鑰是否有效,以及 URL 拼接是否正確。如果問題仍然存在,可能是網絡問題或鏈接本身的問題,建議適當重試或聯系天地圖官方支持。
4. 創建聚合圖層
為了實現標記點的聚合,我們使用了 Leaflet.markercluster 插件。該插件可以將大量的標記點智能地聚合在一起,形成可點擊的群組。當用戶放大地圖時,這些群組會自動拆分成更小的子集或單個標記點。
我們為每種類型的標記點創建了一個獨立的聚合圖層,并通過 iconCreateFunction
動態設置聚合圖標的顏色和樣式。
this.markerType.forEach((type) => {const iconCreateFunction = (cluster) => {const dynamicClassName = `custom-marker cluster-icon-${type}`;return L.divIcon({html: `<div class="${dynamicClassName}"><span>${cluster.getChildCount()}</span></div>`,className: `custom-marker cluster-icon-${type}`,iconSize: L.point(40, 40),});};this.markerClusters[type] = L.markerClusterGroup({iconCreateFunction: iconCreateFunction,});
});
5. 添加標記點
標記點的數據通常以數組的形式存儲,每個數據項包含類型、名稱、地址和坐標等信息,可以點擊展示點位信息。我們根據數據項的類型,將其添加到對應的聚合圖層中。
this.markerData.forEach((item) => {const [longitude, latitude] = gcoord.transform([item.longitude, item.latitude],gcoord.GCJ02,gcoord.WGS84);const marker = L.marker([latitude, longitude], {icon: icons[item.type],});marker.bindPopup(`<b>${item.name}</b><br>${item.address}`);this.markerClusters[item.type].addLayer(marker);
});Object.values(this.markerClusters).forEach((cluster) => {this.map.addLayer(cluster);
});
6. 動態過濾標記點
為了提升用戶體驗,我們允許用戶通過點擊按鈕來篩選特定類型的標記點。當用戶點擊按鈕時,我們移除所有聚合圖層,并根據用戶的選擇重新添加對應的圖層。
filterType(item) {Object.values(this.markerClusters).forEach((cluster) => {this.map.removeLayer(cluster);});if (item.type === "all") {Object.values(this.markerClusters).forEach((cluster) => {this.map.addLayer(cluster);});} else {if (this.markerClusters[item.type]) {this.map.addLayer(this.markerClusters[item.type]);}}
}
性能優化
在處理大量標記點時,性能優化是至關重要的。Leaflet.markercluster 插件本身已經對性能進行了優化,但我們仍然可以通過以下方式進一步提升性能:
-
限制標記點數量:在地圖縮放級別較低時,可以隱藏一些不重要的標記點。
-
使用緩存:對于重復加載的數據,可以使用緩存機制減少重復請求。
-
異步加載數據:如果標記點數據量較大,可以采用分頁或懶加載的方式動態加載數據。
總結
通過 Leaflet 和 Leaflet.markercluster 插件,我們成功實現了一個支持多類型點位標記和聚合的地圖功能。這個功能不僅提升了用戶體驗,還為地圖應用提供了更強大的數據展示能力。在開發過程中,我們需要注意地圖圖層的加載、聚合圖標的動態設置以及性能優化等方面,以確保應用的穩定性和流暢性。