【2025最新】ArcGIS 点聚合功能实现全教程(进阶版)

ArcGIS 点聚合功能实现全教程(进阶版)

本教程聚焦点数据生成、多类别样式配置、聚合开关控制及类别筛选等核心功能的进阶实现逻辑,深入解析点聚合功能进阶用法。本文为《【2025最新】ArcGIS for JS点聚合功能实现》进阶版,关于地图初始化、模块导入、基础组件配置等入门内容不再赘述。

文章目录

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

效果图

效果1 效果2

实现功能

  1. 聚合开关切换:通过 "启用 / 禁用聚合" 按钮控制聚合状态,点击时切换图层featureReduction属性(聚合模式 / 非聚合模式),并同步更新按钮文本,实现交互化聚合控制。
  2. 类别条件筛选:通过下拉框提供 "全部 + 4 个类别" 的筛选选项,选择后触发图层筛选逻辑,仅显示符合category条件的点要素,同时关闭当前弹窗避免信息干扰,实现精准数据筛选。
  3. 聚合样式:不同类别的聚合簇采用对应的图标。

一、核心功能实现步骤

(一)点要素图层创建

  1. 定义数据结构:设置字段信息,包含唯一标识、类别与数值字段

    fields: [{

    复制代码
     name: "ObjectID",
    
     alias: "ObjectID",
    
     type: "oid"

    }, {

    复制代码
     name: "category",
    
     alias: "类别",
    
     type: "integer"

    }, {

    复制代码
     name: "value",
    
     alias: "值",
    
     type: "integer"

    }]

  2. 生成随机点数据:通过generateRandomPoints函数创建 5000 个随机点,经度范围 0-119,纬度范围 30-60,随机分配 4 个类别

    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() * 4);
                 points.push({
                     geometry: {
                         type: "point",
                         longitude: lon,
                         latitude: lat
                     },
                     attributes: {
                         ObjectID: i,
                         category: category,
                         value: Math.floor(Math.random() * 100)
                     }
                 });
             }
             return points;
         }
  3. 设置要素样式:采用唯一值渲染,为 4 个类别分别设置不同样式,包括简单圆形标记与图片标记

    renderer: {

    复制代码
     type: "unique-value",
    
     field: "category",
    
     uniqueValueInfos: [
    
         {
    
             value: 0, symbol: {
    
                 type: 'simple-marker',
    
                 color: [24, 174, 255, 0.5],
    
                 size: 16,
    
                 style: "circle",
    
                 outline: {
    
                     color: [24, 174, 255, 0.8],
    
                     width: 2
    
                 }
    
             },
    
             label: "类别1"
    
         },
    
         // 其他类别样式配置...
    
     ]

    }

  4. 配置弹窗模板:点击单点时显示类别与数值信息

    popupTemplate: {

    复制代码
     title: "{category}",
    
     content: "类别:{category},值:{value}",

    }

(二)点聚合功能配置

  1. 生成聚合配置:通过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(res => res.primaryScheme);
    
     return {
    
         type: "cluster",
    
         popupTemplate,
    
         labelingInfo,
    
         clusterMinSize,
    
         maxScale: 50000
    
     };

    }

  2. 实现聚合开关:通过toggleClustering函数切换聚合状态,点击按钮时修改featureReduction属性

    function toggleClustering() {

    复制代码
     let fr = layer.featureReduction;
    
     layer.featureReduction = fr && fr.type === "cluster" ? null : featureReduction;
    
     toggleButton.innerText = toggleButton.innerText === "启用聚合" ? "禁用聚合" : "启用聚合";

    }

(三)辅助功能实现

  1. 图例组件:创建图例并关联地图视图,展示各类别样式说明

    const legend = new Legend({

    复制代码
     view,
    
     container: "legendDiv",

    });

  2. 类别筛选:监听下拉框变化,通过layerView.filter设置筛选条件,筛选特定类别点数据

    filterSelect.addEventListener("change", (event) => {

    复制代码
     const newValue = event.target.value;
    
     const whereClause = newValue ? \`category = ${newValue}\` : null;
    
     layerView.filter = { where: whereClause };
    
     view.closePopup();

    });

  3. 聚合弹窗样式优化:修改聚合区域边界样式,增强视觉辨识度

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

    复制代码
     .then(() => {
    
         view.popup.viewModel.selectedClusterBoundaryFeature.symbol = {
    
             type: "simple-fill",
    
             style: "solid",
    
             color: "rgba(50,50,50,0.8)",
    
             outline: { width: 0.5, color: "rgba(50,50,50,0.8)" }
    
         };
    
     });

二、其他

  1. 符号类型匹配:确保点要素使用simple-markerpicture-marker类型,避免因符号类型错误导致渲染失效

  2. 类别值匹配:筛选下拉框的value属性需与category字段值一致(0-3),原示例中值为 1-4 需修正为 0-4

  3. 按钮初始文本:聚合按钮初始文本应设为 "启用聚合",与切换逻辑匹配

  4. 要素加载时机:所有依赖图层的操作需在layer.when()回调中执行,确保图层加载完成

三、全部代码

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}",
            },
            renderer: {
                type: "unique-value",
                field: "category",
                uniqueValueInfos: [
                    {
                        value: 0, symbol: {
                            type: 'simple-marker', // 点要素专用符号类型(核心修复点)
                            color: [24, 174, 255, 0.5], // 半透明蓝色(与原需求一致)
                            size: 16, // 点的大小(像素,建议12-20,适配视觉)
                            style: "circle", // 点的形状(circle/方形/square/三角形/triangle等)
                            outline: { // 点的描边(增强辨识度)
                                color: [24, 174, 255, 0.8],
                                width: 2
                            }
                        },
                        label: "类别1"
                    },
                    {
                        value: 1, symbol: {
                            type: "picture-marker",
                            url: "https://picsum.photos/20/20", // 20x20的图片
                            width: "20px",
                            height: "20px",
                            yoffset: "10px" // 图标偏移,使底部对准点位置
                        }, label: "类别2"
                    },
                    {
                        value: 2, symbol: {
                            type: 'simple-marker', // 点要素专用符号类型(核心修复点)
                            color: [24, 14, 255, 0.5], // 半透明蓝色(与原需求一致)
                            size: 16, // 点的大小(像素,建议12-20,适配视觉)
                            style: "circle", // 点的形状(circle/方形/square/三角形/triangle等)
                            outline: { // 点的描边(增强辨识度)
                                color: [24, 14, 255, 0.8],
                                width: 2
                            }
                        }, label: "类别3"
                    },
                    {
                        value: 3, symbol: {
                            type: 'simple-marker', // 点要素专用符号类型(核心修复点)
                            color: [24, 144, 55, 0.5], // 半透明蓝色(与原需求一致)
                            size: 16, // 点的大小(像素,建议12-20,适配视觉)
                            style: "circle", // 点的形状(circle/方形/square/三角形/triangle等)
                            outline: { // 点的描边(增强辨识度)
                                color: [24, 144, 55, 0.5],
                                width: 2
                            }
                        }, label: "类别4"
                    }
                ]
            }

        });

        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.8)",
                    outline: {
                        width: 0.5,
                        color: "rgba(50,50,50,0.8)",
                    },
                };
            });

        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;

                // 核心添加,切换点聚合模式,
                const toggleButton = document.getElementById("toggle-cluster");
                toggleButton.addEventListener("click", toggleClustering);

                // To turn off clustering on a layer, set the
                // featureReduction property to null
                function toggleClustering() {
                    let fr = layer.featureReduction;
                    layer.featureReduction = fr && fr.type === "cluster" ? null : featureReduction;

                    toggleButton.innerText =
                        toggleButton.innerText === "启用聚合"
                            ? "禁用聚合"
                            : "启用聚合";
                }

                view.whenLayerView(layer).then((layerView) => {
                    const filterSelect = document.getElementById("filter");
                    filterSelect.addEventListener("change", (event) => {
                        const newValue = event.target.value;

                        const whereClause = newValue ? `category = ${newValue}` : null;
                        console.log(whereClause)
                        layerView.filter = {
                            where: whereClause,
                        };
                        view.closePopup();
                    });
                });
            })
            .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() * 4);
                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">
        类别筛选(category):
        <select id="filter" class="esri-select">
            <option value="">全部</option>
            <option value="1">类别1</option>
            <option value="2">类别2</option>
            <option value="3">类别3</option>
            <option value="4">类别4</option>
        </select>
        <div style="padding-top: 10px">
            <button id="toggle-cluster" class="esri-button">点聚合</button>
        </div>
        <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 小时前
【2025最新】ArcGIS for JS点聚合功能实现
开发语言·javascript·arcgis
你是一个铁憨憨8 天前
ArcGIS定向影像(1)——非传统影像轻量级解决方案
arcgis·gis·影像·定向影像
QQ3596773458 天前
ArcGIS Pro实现基于 Excel 表格批量创建标准地理数据库(GDB)——高效数据库建库解决方案
数据库·arcgis·excel
阿智@1110 天前
推荐使用 pnpm 而不是 npm
前端·arcgis·npm
GIS思维10 天前
ArcGIS(Pro)在线地图服务被禁?提示感叹号?应急方案来了——重新正常显示
arcgis·arcgispro
bug总结10 天前
多楼层室内定位可视化 Demo(A*路径避障)
arcgis
草木红11 天前
express 框架基础和 EJS 模板
arcgis·node.js·express
wanzhong233312 天前
ArcGIS学习-17 实战-密度分析
学习·arcgis
树谷-胡老师13 天前
公元前3400年至公元2024年全球国家地理边界演变数据集
数据库·arcgis·信息可视化