地图点聚合(谷歌)

分享一下地图上的标记点按省市区聚合的实现方法

高德地图

直接按官方示例做就好了,非常方便

lbs.amap.com/api/javascr...

谷歌地图

谷歌地图官方示例只有按距离聚合,没有按索引聚合的例子,需要自己实现

这里要用到MarkerManager,源文件不大,可以直接copy到项目里

github.com/googlemaps/...

准备数据
yaml 复制代码
this.clusterFields = ['province', 'city', 'district']
this.points = [
    { 
        serial: 1,
        label: xxx,
        address: xxx,
        position: { longitude: xxx, latitude: xxx },
        province: xx省,
        city: xx市,
        district: xx区
    },
    ...
]
初始化地图
php 复制代码
this.map = new google.maps.Map(
    document.getElementById("map") as HTMLElement,
    {
      zoom: 3,
      center: { lat: -28.024, lng: 140.887 },
      mapId: 'DEMO_MAP_ID',
    }
);
初始化MarkerManager
kotlin 复制代码
this.mgr = new MarkerManager(this.map, {})
google.maps.event.addListener(this.mgr, 'loaded', () => {
    // 需要在loaded之后才能用
    this.generateMarkers()
})
聚合规则
ini 复制代码
const generateMarkers = () => {
    // 重新渲染点时清空一下
    this.mgr.clearMarkers()
    
    // 按省市区分类好标记点
    const levelObj = {}
    const allMarkers = this.points.map((p) => {
        for (const field of this.clusterFields) {
            if (!levelObj[field]) {
                levelObj[field] = {}
            }
            // 每个省市区只取第一个数据作为中心点
            if (p[field]) {
                if (!levelObj[field][p[field]]) {
                    levelObj[field][p[field]] = { data: p, count: 1 }
                } else {
                    levelObj[field][p[field]].count++
                }
            }
        }
        
        // 不聚合时显示的标记点
        const marker = new google.maps.Marker({
            position: { lng: p.position.longitude, lat: p.position.latitude },
            optimized: true,
            label: p.label,
            zIndex: p.serial
        })
        // 点击标记显示卡片信息
        marker.addListener('click', () => {
            if (!this.infoWindow || !this.infoWindow.open) {
                const position = marker.getPosition()
                // infoWindow要依存有map的marker才能显示。当前marker的map会随着缩放变为null,所以copy一个不可见的marker
                let copyMarker = new google.maps.Marker({
                    map: this.map,
                    position: position,
                    optimized: true
                    visible: false
                })
                this.infoWindow = new google.maps.InfoWindow({
                    content: p.address,
                    disableAutoPan: true
                })
                this.infoWindow.addListener('close', () => {
                    // infoWindow关闭时移除copyMarker
                    copyMarker.setMap(null)
                    copyMarker = null
                })
                this.infoWindow.open({ anchor: copyMarker, shouldFocus: false })
            }
        })
        return marker
    })
    
    // 聚合时显示的标记
    this.clusterFields.forEach((field, index) => {
        // 转化为省市区数组
        levelObj[field] = Object.values(levelObj[field]).map((obj) => {
            const p = obj.data
            const count = obj.count
            let size = 70 + index * 10
            const label = p[field]
            const div = document.createElement('div')
            div.className = 'map-cluster'
            div.style.backgroundColor = 'rgba(255, 57, 57, 0.8)'
            div.style.backgroundSize = 'cover'
            div.style.width = size + 'px'
            div.style.height = size + 'px'
            div.style.borderRadius = size + 'px'
            div.innerHTML = `<span class="showName" title="${label}">${label}</span><span class="showCount">${count}</span>`
            div.style.color = '#ffffff'
            div.style.textAlign = 'center'
            div.onclick = () => {
                let curZoom = this.map.getZoom()
                if (curZoom < 20) {
                    curZoom += 1
                }
                this.map.setZoom(curZoom)
                this.map.setCenter({ lng: p.position.longitude, lat: p.position.latitude })
            }
            const marker = new google.maps.Marker({
                position: { lng: p.position.longitude, lat: p.position.latitude },
                optimized: true,
                visible: false // 不显示marker
            })
            // 谷歌的marker无法显示两行文字,这里使用InfoWindow挂在marker上,并隐藏marker
            const infowindow = new google.maps.InfoWindow({
                content: div,
                disableAutoPan: true,
                pixelOffset: new google.maps.Size(0, size / 2 + 6)
            })
            infowindow.open({ anchor: marker, shouldFocus: false })
        })
    })
    
    // 序号级别显示全部标记
    levelObj['serial'] = allMarkers
    
    // 平均分配层级
    const totalLevels = this.clusterFields.length
    const zoomStart = 11
    const zoomEnd = 1
    const zoomStep = (zoomStart - zoomEnd) / totalLevels
    
    // 第一步:先计算所有层级的 maxZoom
    const maxZooms = []
    for (let i = 0; i < totalLevels; i++) {
        const maxZoom = Math.floor(zoomStart - i * zoomStep)
        maxZooms.push(maxZoom)
    }
    
    // 第二步:根据 maxZoom 推导 minZoom
    for (let i = 0; i < totalLevels; i++) {
        let minZoom
        if (i < totalLevels - 1) {
            minZoom = maxZooms[i + 1] + 1
        } else {
            minZoom = 1 // 最底层的 minZoom 设为 1
        }
        this.mgr.addMarkers(levelObj[this.clusterFields[i]], Math.max(minZoom, 1), maxZooms[i])
    }
    this.mgr.addMarkers(levelObj['serial'], 12, 20)
    
    // 将MarkerManager添加到地图上
    this.mgr.refresh()
}

效果:

至此,主要代码就结束了。

如果数据变更了,需要重新调用generateMarkers方法。该方法较为庞大复杂,性能不高,我也没找到其他好办法,如果有更好的方案,可以探讨下。

相关推荐
猩猩程序员33 分钟前
Go 1.24 全面拥抱 Swiss Table:让内置 map 提速 60% 的秘密
前端
1024小神35 分钟前
vue3 + vite项目,如果在build的时候对代码加密混淆
前端·javascript
轻语呢喃1 小时前
useRef :掌握 DOM 访问与持久化状态的利器
前端·javascript·react.js
wwy_frontend1 小时前
useState 的 9个常见坑与最佳实践
前端·react.js
w_y_fan1 小时前
flutter_riverpod: ^2.6.1 应用笔记 (一)
前端·flutter
Jerry1 小时前
Compose 界面工具包
前端
Focusbe1 小时前
从0到1开发一个AI助手
前端·人工智能·面试
egghead263161 小时前
React组件通信
前端·react.js
RIKA1 小时前
【前端工具】使用 Node.js 脚本实现项目打包后自动压缩
前端
橙某人1 小时前
🖼️照片展示新境界!等高不等宽自适应布局完整教程⚡⚡⚡
前端·javascript·css