【2025最新】ArcGIS for JS点聚合功能实现

ArcGIS 点聚合功能实现

一、教程概述

本教程基于 ArcGIS API for JavaScript 4.33 版本,详细讲解如何实现地图点聚合功能。通过随机生成 5000 个地理点数据,结合天地图底图,实现点数据的自动聚合展示、聚合标签、弹窗及图例等核心功能,适用于大数据量地理点的可视化场景。

文章目录

工具 /插件/系统 名 版本 说明
ArcGIS for JavaScript 4.33 适用4.28 - 4.33版本

效果图

效果图 1 效果图 2 效果图 3

二、实现的核心功能

  1. 点聚合功能:实现点数据的自动聚合与分散,依据比例尺动态调整(超过 50000 比例尺不聚合);配置聚合点的专属弹窗,展示统计信息;生成聚合标签,直观呈现聚合数量。
  2. 详情弹框:支持点击聚合点查看详情,实现聚合区域的清晰标识。
  3. 添加图例控件展示图层信息。
  4. 加载天地图作为底图 案例中已经将渲染天地图(点击直达)文章中的内容额外封装成为loadTiandituBasemap方法,文章内提供了文中所需的封装代码,有其他需要的可自行封装,这里不多赘述。

(二)基础页面结构

创建包含地图容器和信息面板的 HTML 结构,设置全屏样式确保地图占满可视区域:

复制代码
<div id="viewDiv"></div>

<div id="infoDiv" class="esri-widget">

    <div id="legendDiv"></div>

</div>

<style>

    html, body, #viewDiv {

        height: 100%;

        width: 100%;

        margin: 0;

        padding: 0;

    }

     #infoDiv {

        background: white;

        padding: 10px;

    }

</style>

三、核心功能分步实现

(一)模块导入与初始化

采用 ES 模块方式导入所需 ArcGIS 核心模块,确保按需加载关键组件:

复制代码
// 导入天地图加载工具

import { loadTiandituBasemap } from './js/tiandituLoader.js';

// 导入 ArcGIS 核心模块

const [Map, MapView, FeatureLayer, Legend, Expand, clusterLabelCreator, clusterPopupCreator, reactiveUtils] = await \$arcgis.import([

    "@arcgis/core/WebMap.js",

    "@arcgis/core/views/MapView.js",

    "@arcgis/core/layers/FeatureLayer.js",

    "@arcgis/core/widgets/Legend.js",

    "@arcgis/core/widgets/Expand.js",

    "@arcgis/core/smartMapping/labels/clusters.js",

    "@arcgis/core/smartMapping/popup/clusters.js",

    "@arcgis/core/core/reactiveUtils.js",

]);

// 加载天地图底图配置

const { tiandituBasemap } = await loadTiandituBasemap();

(二)生成随机点数据

创建 generateRandomPoints 函数,生成指定数量的地理点数据,包含随机经纬度、类别及数值属性:

复制代码
function generateRandomPoints(count) {

    const points = [];

    for (let i = 0; i < count; i++) {

        // 经度范围:0-119°E,纬度范围:30-60°N

        const lon = Math.random() * 119;

        const lat = Math.random() * 30 + 30;

        // 随机分配 0-2 三类数据

        const category = Math.floor(Math.random() * 3);

        points.push({

            geometry: { type: "point", longitude: lon, latitude: lat },

            attributes: {

                ObjectID: i,

                category: category,

                value: Math.floor(Math.random() * 100)

            }

        });

    }

    return points;

}

(三)创建要素图层

定义包含点数据的要素图层,配置字段结构、输出字段及基础弹窗模板:

复制代码
const layer = new FeatureLayer({

    source: generateRandomPoints(5000), // 加载5000个随机点

    title: "点聚合",

    fields: [

        { name: "ObjectID", alias: "ObjectID", type: "oid" },

        { name: "category", alias: "类别", type: "integer" },

        { name: "value", alias: "值", type: "integer" }

    ],

    outFields: ["*"], // 输出所有字段

    popupTemplate: { title: "{name}", content: [] }

});

(四)初始化地图与视图

创建地图实例并绑定天地图底图,配置地图视图的容器、中心点及初始缩放级别:

复制代码
// 初始化地图

const map = new Map({

    basemap: tiandituBasemap, // 绑定天地图底图

    layers: [layer] // 添加要素图层

});

// 初始化地图视图

const view = new MapView({

    container: "viewDiv", // 绑定地图容器

    map,

    center: [116.39, 39.9], // 中心点:北京坐标

    zoom: 4 // 初始缩放级别

});

(五)配置点聚合功能

通过 generateClusterConfig 函数配置聚合参数,包含弹窗模板、标签样式及聚合触发条件:

复制代码
async function generateClusterConfig(layer) {

    // 生成聚合弹窗模板

    const popupTemplate = await clusterPopupCreator

        .getTemplates({ layer })

        .then(res => res.primaryTemplate.value);

    

    // 生成聚合标签样式

    const { labelingInfo, clusterMinSize } = await clusterLabelCreator

        .getLabelSchemes({ layer, view })

        .then(schemes => schemes.primaryScheme);

    

    // 返回聚合配置

    return {

        type: "cluster",

        popupTemplate, // 聚合弹窗

        labelingInfo, // 聚合标签

        clusterMinSize, // 最小聚合尺寸

        maxScale: 50000 // 最大聚合比例尺(超过此值不聚合)

    };

}

// 图层加载完成后启用聚合

layer.when()

    .then(generateClusterConfig)

    .then(featureReduction => {

        layer.featureReduction = featureReduction;

    })

    .catch(error => console.error(error));

(六)添加控件与交互优化

  1. 聚合弹窗样式优化:修改聚合区域的边界样式,提升视觉效果

    reactiveUtils.whenOnce(() => view.popup.viewModel)

    复制代码
     .then(() => {
    
         view.popup.viewModel.selectedClusterBoundaryFeature.symbol = {
    
             type: "simple-fill",
    
             style: "solid",
    
             color: "rgba(50,50,50,0.15)",
    
             outline: { width: 0.5, color: "rgba(50,50,50,0.25)" }
    
         };
    
     });
  2. 添加图例控件:创建图例并通过折叠面板添加到地图右上角

    // 创建图例

    const legend = new Legend({ view, container: "legendDiv" });

    // 创建折叠面板

    const infoExpand = new Expand({

    复制代码
     view,
    
     content: document.getElementById("infoDiv"),
    
     expandIcon: "list-bullet",
    
     expanded: true // 默认展开

    });

    // 添加到地图UI

    view.ui.add(infoExpand, "top-right");

四、全部代码

index.html

复制代码
<!doctype html>
<html lang="zh-CN">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />

    <title>ArcGIS 点聚合基础示例</title>

    <link rel="stylesheet" href="https://js.arcgis.com/4.33/esri/themes/light/main.css" />
    <script src="https://js.arcgis.com/4.33/"></script>

    <style>
        html,
        body,
        #viewDiv {
            height: 100%;
            width: 100%;
            margin: 0;
            padding: 0;
        }

        #infoDiv {
            background: white;
            padding: 10px;
        }
    </style>

    <script type="module">
        import { loadTiandituBasemap } from './js/tiandituLoader.js';
        const [
            Map,
            MapView,
            FeatureLayer,
            Legend,
            Expand,
            clusterLabelCreator,
            clusterPopupCreator,
            reactiveUtils,
        ] = await $arcgis.import([
            "@arcgis/core/WebMap.js",
            "@arcgis/core/views/MapView.js",
            "@arcgis/core/layers/FeatureLayer.js",
            "@arcgis/core/widgets/Legend.js",
            "@arcgis/core/widgets/Expand.js",
            "@arcgis/core/smartMapping/labels/clusters.js",
            "@arcgis/core/smartMapping/popup/clusters.js",
            "@arcgis/core/core/reactiveUtils.js",
        ]);
        const { tileInfo, config, getUrlTemplate, tiandituBasemap, Basemap, WebTileLayer } = await loadTiandituBasemap();
        const layer = new FeatureLayer({
            source: generateRandomPoints(5000),
            title: "点聚合",
            fields: [{
                name: "ObjectID",
                alias: "ObjectID",
                type: "oid"
            }, {
                name: "category",
                alias: "类别",
                type: "integer"
            }, {
                name: "value",
                alias: "值",
                type: "integer"
            }],
            outFields: ["*"],
            popupTemplate: {
                title: "{category}",
                content: "类别:{category},值:{value}",
            },
        });

        const map = new Map({
            basemap: tiandituBasemap,
            layers: [layer],
        });

        const view = new MapView({
            container: "viewDiv",
            map,
            center: [116.39, 39.9],
            zoom: 4,
        });

        reactiveUtils
            .whenOnce(() => view.popup.viewModel)
            .then(() => {
                // Override the default symbol representing the cluster extent
                view.popup.viewModel.selectedClusterBoundaryFeature.symbol = {
                    type: "simple-fill",
                    style: "solid",
                    color: "rgba(50,50,50,0.15)",
                    outline: {
                        width: 0.5,
                        color: "rgba(50,50,50,0.25)",
                    },
                };
            });

        const legend = new Legend({
            view,
            container: "legendDiv",
        });

        const infoDiv = document.getElementById("infoDiv");
        view.ui.add(
            new Expand({
                view,
                content: infoDiv,
                expandIcon: "list-bullet",
                expanded: true,
            }),
            "top-right",
        );


        layer
            .when()
            .then(generateClusterConfig)
            .then((featureReduction) => {
                layer.featureReduction = featureReduction;
            })
            .catch((error) => {
                console.error(error);
            });


        async function generateClusterConfig(layer) {

            // generates default popupTemplate
            const popupTemplate = await clusterPopupCreator
                .getTemplates({ layer })
                .then((popupTemplateResponse) => popupTemplateResponse.primaryTemplate.value);


            // generates default labelingInfo
            const { labelingInfo, clusterMinSize } = await clusterLabelCreator
                .getLabelSchemes({ layer, view })
                .then((labelSchemes) => labelSchemes.primaryScheme);


            return {
                type: "cluster",
                popupTemplate,
                labelingInfo,
                clusterMinSize,
                maxScale: 50000,
            };
        }
        function generateRandomPoints(count) {
            const points = [];
            for (let i = 0; i < count; i++) {
                const lon = (Math.random() * 119) ; // 经度范围
                const lat = (Math.random() * 30) + 30;  // 纬度范围

                // 随机分配类别
                const category = Math.floor(Math.random() * 3);
                points.push({
                    geometry: {
                        type: "point",
                        longitude: lon,
                        latitude: lat
                    },
                    attributes: {
                        ObjectID: i,
                        category: category,
                        value: Math.floor(Math.random() * 100)
                    }
                });
            }
            return points;
        }
    </script>
</head>

<body> 
    <div id="viewDiv"></div>
    <div id="infoDiv" class="esri-widget">
        <div id="legendDiv"></div>
    </div>
</body>

</html>

tiandituLoader.js

复制代码
/**
 * 天地图加载公共模块
 * 功能:封装天地图底图加载逻辑,返回配置好的Basemap实例
 * 依赖:ArcGIS API 4.x
 */
export async function loadTiandituBasemap() {
    try {
        // 1. 按需导入ArcGIS核心模块
        const [
            WebTileLayer,
            Basemap,
            TileInfo
        ] = await $arcgis.import([
            "@arcgis/core/layers/WebTileLayer",
            "@arcgis/core/Basemap",
            "@arcgis/core/layers/support/TileInfo",
        ]);

        // 2. 配置参数(可根据需求调整)
        const config = {
            tk:你的密匙, // 天地图密钥
            spatialReference: { wkid: 4326 },       // 目标坐标系(WGS84)
            subDomains: ["0", "1", "2", "3", "4", "5", "6", "7"], // 多子域名
            tileMatrixSet: "c",                     // 天地图瓦片矩阵集
            layerType: {
                vec: "vec", // 矢量底图
                cva: "cva"  // 矢量注记
            }
        };

        // 3. 定义瓦片信息(匹配WGS84坐标系的瓦片规则)
        const tileInfo = new TileInfo({
            dpi: 90.71428571427429,
            rows: 256,
            cols: 256,
            compressionQuality: 0,
            origin: { x: -180, y: 90 },
            spatialReference: config.spatialReference,
            lods: [
                { level: 2, levelValue: 2, resolution: 0.3515625, scale: 147748796.52937502 },
                { level: 3, levelValue: 3, resolution: 0.17578125, scale: 73874398.264687508 },
                { level: 4, levelValue: 4, resolution: 0.087890625, scale: 36937199.132343754 },
                { level: 5, levelValue: 5, resolution: 0.0439453125, scale: 18468599.566171877 },
                { level: 6, levelValue: 6, resolution: 0.02197265625, scale: 9234299.7830859385 },
                { level: 7, levelValue: 7, resolution: 0.010986328125, scale: 4617149.8915429693 },
                { level: 8, levelValue: 8, resolution: 0.0054931640625, scale: 2308574.9457714846 },
                { level: 9, levelValue: 9, resolution: 0.00274658203125, scale: 1154287.4728857423 },
                { level: 10, levelValue: 10, resolution: 0.001373291015625, scale: 577143.73644287116 },
                { level: 11, levelValue: 11, resolution: 0.0006866455078125, scale: 288571.86822143558 },
                { level: 12, levelValue: 12, resolution: 0.00034332275390625, scale: 144285.93411071779 },
                { level: 13, levelValue: 13, resolution: 0.000171661376953125, scale: 72142.967055358895 },
                { level: 14, levelValue: 14, resolution: 8.58306884765625e-005, scale: 36071.483527679447 },
                { level: 15, levelValue: 15, resolution: 4.291534423828125e-005, scale: 18035.741763839724 },
                { level: 16, levelValue: 16, resolution: 2.1457672119140625e-005, scale: 9017.8708819198619 },
                { level: 17, levelValue: 17, resolution: 1.0728836059570313e-005, scale: 4508.9354409599309 },
                { level: 18, levelValue: 18, resolution: 5.3644180297851563e-006, scale: 2254.4677204799655 },
                { level: 19, levelValue: 19, resolution: 2.68220901489257815e-006, scale: 1127.23386023998275 },
                { level: 20, levelValue: 20, resolution: 1.341104507446289075e-006, scale: 563.616930119991375 }
            ]
        });

        // 4. 构建天地图URL模板(支持多子域名)
        const getUrlTemplate = (layer) => {
            return `http://t0.tianditu.gov.cn/${layer}_${config.tileMatrixSet}/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=${layer}&STYLE=default&TILEMATRIXSET=${config.tileMatrixSet}&TILEMATRIX={level}&TILEROW={row}&TILECOL={col}&FORMAT=tiles&tk=${config.tk}`;
        };

        // 5. 创建矢量底图图层
        const vecLayer = new WebTileLayer({
            urlTemplate: getUrlTemplate(config.layerType.vec),
            subDomains: config.subDomains,
            copyright: "天地图 © 国家地理信息公共服务平台",
            spatialReference: config.spatialReference,
            tileInfo: tileInfo
        });

        // 6. 创建矢量注记图层
        const cvaLayer = new WebTileLayer({
            urlTemplate: getUrlTemplate(config.layerType.cva),
            subDomains: config.subDomains,
            copyright: "天地图 © 国家地理信息公共服务平台",
            spatialReference: config.spatialReference,
            tileInfo: tileInfo
        });

        // 7. 创建自定义底图并返回
        const tiandituBasemap = new Basemap({
            baseLayers: [vecLayer],
            referenceLayers: [cvaLayer],
            title: "天地图矢量图(WGS84)",
            id: "tianditu-vector-wgs84"
        });

        return {
            tileInfo,
            config,
            getUrlTemplate,
            tiandituBasemap,
            WebTileLayer,
            Basemap
        };

    } catch (error) {
        console.error("天地图加载失败:", error);
        throw new Error("天地图公共模块加载异常,请检查依赖和配置");
    }
}
相关推荐
破浪前行·吴3 小时前
【学习】响应系统
前端·javascript·学习
叫我詹躲躲4 小时前
Web Animation性能优化:从EffectTiming到动画合成
前端·javascript
HMBBLOVEPDX4 小时前
Qt(常用的对话框)
开发语言·qt·常用对话框
YAY_tyy4 小时前
【Cesium 开发实战教程】第六篇:三维模型高级交互:点击查询、材质修改与动画控制
前端·javascript·3d·教程·cesium
正义的大古4 小时前
OpenLayers地图交互 -- 章节十:拖拽平移交互详解
前端·javascript·vue.js·openlayers
_extraordinary_4 小时前
Java HTTP协议(一)--- HTTP,报文格式,请求和响应
java·开发语言·http
小墨宝4 小时前
umijs 4.0学习 - umijs 的项目搭建+自动化eslint保存+项目结构
开发语言·前端·javascript
!win !1 天前
不定高元素动画实现方案(下)
前端·javascript·css
Mintopia3 天前
低代码平台如何集成 AIGC 技术?核心技术衔接点解析
前端·javascript·aigc