地图点聚合(谷歌)

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

高德地图

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

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方法。该方法较为庞大复杂,性能不高,我也没找到其他好办法,如果有更好的方案,可以探讨下。

相关推荐
skiyee8 小时前
被多个 UView 系 UI 库 “抄” 的 @uni-ku/root 究竟怎么实现?
前端
鱼毓屿御8 小时前
从「只会聊」到「边想边做」的跃迁
前端·学习·react.js
sugar__salt8 小时前
Vue3 表单双向绑定与响应式核心 API 技术详解
前端·javascript·vue.js
郝亚军9 小时前
使用Vue 3和Nginx打包和部署Vue.js项目的一般步骤
前端·vue.js·nginx
SmartBoyW9 小时前
CSS弹性布局(Flexbox)学习笔记:从零开始搞懂弹性布局
前端·javascript
用户69371750013849 小时前
从代码生产者到 AI 协作者:软件工程师的角色重构
android·前端·后端
嘟嘟07179 小时前
搞懂 useState 从原生 DOM 到 React 惰性初始化的完整链路
前端·react.js·编程语言
牧艺9 小时前
cos-design RippleWater & SmokeFog:水面涟漪与烟雾雾气怎么做
前端·canvas·视觉设计
用户938515635079 小时前
写了这么久 useState,你真的知道它在干什么吗?
前端·javascript
慢功夫9 小时前
💡第七篇:VSCode语言服务中,代码跳转和诊断是怎么做的?
前端·visual studio code