使用高德地图的海量点绘制和区域绘制,区域绘制会因为海量点导致卡顿,有什么好的优化方法?

使用高德地图的海量点绘制和区域绘制,区域绘制会因为海量点导致卡顿,有什么好的优化方法?

分批次绘制海量点,依旧会导致绘制区域的时候卡断

// 第六步,初始化地图
const BATCH_SIZE = 10; // 每批处理的标注数量
const BATCH_DELAY = 100; // 每批之间的延迟时间(毫秒)
function initMap() {
    mountKey()
    AMapLoader.load(loadOptions as any).then((AMap: any) => {
        mapInstance = new AMap.Map('map-container', assignedConfig);
        mapInstance.on('complete', () => {
            getAdministrativeRegionInfo(AMap)
            // 第七步, 添加矩形绘制工具
            geometryConstructers.value.GeometryUtil = AMap.GeometryUtil;
            const _MouseTool = new AMap.MouseTool(mapInstance);
            geometryConstructers.value.MouseTool = _MouseTool;
            // 第八步,监听摄像机列表变化,添加海量点标注
            watch(() => tenantDeviceList.value, async (newVal, oldVal) => {
                console.log('newVal :>> ', newVal);
                if (newVal.length === 0) return
                // 如果已存在图层,先移除
                if (markersLayer) {
                    mapInstance.remove(markersLayer);
                }
                // 1、创建 AMap.LabelsLayer 图层
                markersLayer = new AMap.LabelsLayer({
                    zooms: defaultSetting.zooms,
                    zIndex: 9999,
                    collision: true
                });

                //2、图层添加至地图实例
                // mapInstance && mapInstance.add && mapInstance.add(markersLayer);
                // 3、分批处理标注
                const processMarkersBatch = async (startIndex: number) => {
                    const endIndex = Math.min(startIndex + BATCH_SIZE, newVal.length);
                    const batchMarkers = [] as any;

                    for (let i = startIndex; i < endIndex; i++) {
                        const obj = newVal[i];
                        let icon;

                        // 检查相同位置的设备
                        const sameLocationDevices = newVal.filter((item: any) =>
                            item.hyssDeviceEntity.latitude === obj.hyssDeviceEntity.latitude &&
                            item.hyssDeviceEntity.longitude === obj.hyssDeviceEntity.longitude
                        );

                        if (sameLocationDevices.length > 1) {
                            icon = collect_icon;
                        } else {
                            icon = obj.hyssDeviceEntity && obj.hyssDeviceEntity.deviceState === 1 ? online_icon : offline_icon;
                        }

                        let iconLable = obj?.hyssDeviceEntity?.deviceAddress || obj?.hyssDeviceEntity?.deviceName || '';

                        const markerObj = new AMap.LabelMarker({
                            extData: obj,
                            icon,
                            text: {
                                content: iconLable,
                                zooms: [12, 26],
                                style: {
                                    padding: [1, 1, 1, 1],
                                    fillColor: '#fff',
                                    fontSize: 14,
                                    strokeColor: '#000'
                                }
                            },
                            position: [obj?.hyssDeviceEntity?.longitude, obj?.hyssDeviceEntity?.latitude],
                            offset: new AMap.Pixel(0, 0),
                            anchor: 'bottom-center',
                            fitZoom: 11,
                            scaleFactor: 0.5,
                            maxScale: 2,
                            minScale: 0.5
                        });

                        batchMarkers.push(markerObj);
                    }

                    // 添加这一批标注
                    markersLayer && markersLayer.add && markersLayer.add(batchMarkers);
                    mapInstance && mapInstance.add && mapInstance.add(markersLayer);

                    // 如果还有剩余标注,继续处理下一批
                    if (endIndex < newVal.length) {
                        await new Promise(resolve => setTimeout(resolve, BATCH_DELAY));
                        await processMarkersBatch(endIndex);
                    }
                };
                // 开始处理第一批
                await processMarkersBatch(0);
            })
        })
    })
}
阅读 438
3 个回答

通过用户对地图的zoom去实现对多个相邻海量点 进行合并。 比如某个半径内有几十个小点,那就只渲染一个大的点。

加分片还有用 Web Worker 用 Web Worker 处,用缩放

import { ref, watch } from 'vue';
import AMapLoader from '@amap/amap-jsapi-loader';
import { defaultSetting, loadOptions, assignedConfig } from './config';

let mapInstance: any = null;
let markersLayer: any = null;
let worker: Worker | null = null;

const CONFIG = {
    BATCH_SIZE: 1000, // 每批处理的标注数量
    BATCH_DELAY: 50, // 每批之间的延迟时间(毫秒)
    MIN_ZOOM_LEVEL: 12, // 最小显示标签的缩放级别
    CLUSTER_RADIUS: 80, // 聚合半径
};

const createWorker = () => {
    const workerCode = `
        self.onmessage = function(e) {
            const points = e.data;
           
            const clusters = clusterPoints(points);
            self.postMessage(clusters);
        };

        function clusterPoints(points) {
           
            const grid = {};
            const gridSize = 0.01; // 约1km的网格大小

            points.forEach(point => {
                const key = Math.floor(point.longitude/gridSize) + '_' + 
                          Math.floor(point.latitude/gridSize);
                if (!grid[key]) {
                    grid[key] = [];
                }
                grid[key].push(point);
            });

            return Object.values(grid);
        }
    `;

    const blob = new Blob([workerCode], { type: 'application/javascript' });
    return new Worker(URL.createObjectURL(blob));
};

class DeviceMarkerManager {
    private markers: Map<string, any> = new Map();
    private visibleMarkers: Set<string> = new Set();
    private worker: Worker | null = null;

    constructor(private map: any, private layer: any) {
        this.worker = createWorker();
        this.setupWorker();
        this.setupMapEvents();
    }

    private setupWorker() {
        if (!this.worker) return;

        this.worker.onmessage = (e) => {
            const clusters = e.data;
            this.updateMarkers(clusters);
        };
    }

    private setupMapEvents() {
        this.map.on('zoomend', () => this.updateVisibleMarkers());
        this.map.on('moveend', () => this.updateVisibleMarkers());
    }

    private updateVisibleMarkers() {
        const bounds = this.map.getBounds();
        const zoom = this.map.getZoom();

        this.visibleMarkers.clear();
        this.markers.forEach((marker, id) => {
            const pos = marker.getPosition();
            if (bounds.contains(pos) && zoom >= CONFIG.MIN_ZOOM_LEVEL) {
                this.visibleMarkers.add(id);
            }
        });

        this.updateMarkerVisibility();
    }

    private updateMarkerVisibility() {
        this.markers.forEach((marker, id) => {
            if (this.visibleMarkers.has(id)) {
                marker.show();
            } else {
                marker.hide();
            }
        });
    }

    async addDevices(devices: any[]) {
        // 清除现有标注
        this.clear();

        // 分批处理设备
        for (let i = 0; i < devices.length; i += CONFIG.BATCH_SIZE) {
            const batch = devices.slice(i, i + CONFIG.BATCH_SIZE);
            await this.processBatch(batch);
            await new Promise(resolve => setTimeout(resolve, CONFIG.BATCH_DELAY));
        }

        // 更新可见标注
        this.updateVisibleMarkers();
    }

    private async processBatch(devices: any[]) {
        const batchMarkers = devices.map(device => this.createMarker(device));
        this.layer.add(batchMarkers);
        
        batchMarkers.forEach(marker => {
            this.markers.set(marker.getExtData().id, marker);
        });
    }

    private createMarker(device: any) {
        const { hyssDeviceEntity } = device;
        const position = [hyssDeviceEntity.longitude, hyssDeviceEntity.latitude];
        
        return new AMap.LabelMarker({
            position,
            extData: device,
            icon: {
                type: 'image',
                image: hyssDeviceEntity.deviceState === 1 ? 'online_icon.png' : 'offline_icon.png',
                size: [20, 20],
                anchor: 'bottom-center'
            },
            text: {
                content: hyssDeviceEntity.deviceAddress || hyssDeviceEntity.deviceName || '',
                direction: 'top',
                offset: [0, 5],
                style: {
                    fontSize: 12,
                    fillColor: '#333',
                    strokeColor: '#fff',
                    strokeWidth: 2,
                }
            }
        });
    }

    clear() {
        this.layer.clear();
        this.markers.clear();
        this.visibleMarkers.clear();
    }

    destroy() {
        this.clear();
        this.worker?.terminate();
        this.worker = null;
    }
}

function initMap() {
    const geometryConstructers = ref<any>({});
    let markerManager: DeviceMarkerManager | null = null;

    const mountKey = () => {
        //mountKey 逻辑
    };

    const getAdministrativeRegionInfo = (AMap: any) => {
        // 行政区域信息获取逻辑
    };

    mountKey();

    return AMapLoader.load(loadOptions).then((AMap: any) => {
        mapInstance = new AMap.Map('map-container', assignedConfig);
        
        mapInstance.on('complete', () => {
            getAdministrativeRegionInfo(AMap);
            
            // 初始化几何工具
            geometryConstructers.value.GeometryUtil = AMap.GeometryUtil;
            geometryConstructers.value.MouseTool = new AMap.MouseTool(mapInstance);

            // 创建标注图层
            markersLayer = new AMap.LabelsLayer({
                zooms: defaultSetting.zooms,
                zIndex: 9999,
                collision: true
            });

            mapInstance.add(markersLayer);
            markerManager = new DeviceMarkerManager(mapInstance, markersLayer);

            // 监听设备列表变化
            watch(() => tenantDeviceList.value, async (newVal) => {
                if (!newVal?.length) return;
                await markerManager?.addDevices(newVal);
            });
        });
    });
}

// 清理函数
function cleanup() {
    if (worker) {
        worker.terminate();
        worker = null;
    }```

    if (markersLayer) {
        mapInstance?.remove(markersLayer);
        markersLayer =
 null;
    }
    if (mapInstance) {
        mapInstance.destroy();
        mapInstance = null;
    }
}

export {
    initMap,
    cleanup
};

用的时候:

initMap();

onUnmounted(() => {
    cleanup();
});

我试了官方Demo中5w多个点一次全加载没有卡顿, 做法就是把两个Demo合一下去测,其他都不改;不清楚你具体的业务数据量级有多大;
我觉得有几点思路可以参考:
1、Map实例化后,页面里相关的Layer和Tool就可以实例化,业务数据请求到后直接添加到对应的Layer中,数据变更也是清除Layer中的数据重新添加,尽量少去频繁的删除和添加图层
2、数据量如果没有5w,可以先用常规的测试,毕竟你已经用了海量点的图层;
3、和Map相关的实例不要做双向绑定

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏