聚合

地震已聚合到边长为 30 英里的六边形中。

什么是聚合?

聚合允许您将具有许多要素的大型数据集汇总(或聚合)到具有较少要素的图层。这通常通过汇总多边形内的点来完成,其中每个多边形可显示多边形中包含的点数。

虽然本主题中的示例侧重于点到面的聚合,但所描述的每个原则也适用于点到折线和面到面的聚合。

为什么聚合有用?

您可能会问:当点聚类可动态进行聚合时,为什么我应将点聚合到多边形?

在两种情况下,将点聚合到面图层比点聚类更有利:

  1. 点数据集太大,无法在客户端进行聚类。某些点数据集太大,无法合理地加载到浏览器并以良好的性能进行可视化。将点聚合到面图层允许您以高性能的方式表示数据。
  2. 可以在不规则多边形边界内汇总数据。您可能需要将点数据汇总到有意义的预定义多边形边界内,例如县、国会区、学区或警察局。聚类总是在屏幕空间中处理,而不考虑地缘政治边界。在某些情况下,决策者需要通过预定义的不规则多边形进行汇总。

聚合的工作原理

点到面聚合是数据预处理步骤,通常在 web 地图中可视化数据之前完成。有多种工具可用于执行此操作,但以下示例是使用 GeoScene Online 地图查看器中的聚合点分析工具准备的。

要在 GeoScene Online 中聚合点,您必须选择要聚合的点图层,以及用于计算汇总统计数据的面图层。这将创建一个新要素服务。默认情况下,与每个多边形相交的点数将包含在输出表中。您可以选择性地选择点图层中的字段,以使用各种统计数据进行汇总,例如数字字段的平均值或字符串字段的主要值。您还可以按字符串字段的值对数字统计信息进行分组。

示例

六边形

以下示例演示了如何可视化聚合到六边形的地震点。该图层是使用 GeoScene Online 地图视图中的聚合点分析工具创建的。输出图层包含与每个图格相交的地震总数。

该图层是使用颜色视觉变量通过连续色带进行样式化的。通常,如果没有某种形式的数据标准化,您不应使用颜色来可视化多边形中的总计数。但是,在此示例中是允许的,因为每个六边形的面积相等,从而可有效地按面积对数据进行归一化。

地震已聚合到边长为 30 英里的六边形中。弹出窗口使用 Arcade 显示有关每个六边形表示的地震的摘要信息。

此地图是在 GeoScene Online 地图查看器中配置的。除了聚合点工具生成的六边形图层之外,它还包括可见性由比例进行控制的地震点图层。这允许我们使用 Arcade 在弹出窗口中汇总地震统计数据。

例如,要显示最大地震的大小,您可以编写以下表达式。

  
1
2
var earthquakes = Intersects(FeatureSetByName($map, "earthquake points"), $feature);
Text( Max(earthquakes, "mag"), "#.#" );

然后在弹出内容中引用表达式。这可以在代码中完成,也可以直接在 GeoScene Online 中完成。

在六边形图层上设置 maxScale 并在点图层上设置等效的 minScale,允许您在用户缩放到更大比例时创建平滑过渡。当用户缩放超过一定比例时,可以安全地加载点图层,而无需担心性能问题。根据六边形的分辨率,以大比例查看图格在视觉上没有意义。

GeoScene JS API
46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 47 48 49 49 49 49 49 49 49 49 49 49 49
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
    <title>Hexbin aggregation</title>

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

    <link rel="stylesheet" href="https://js.geoscene.cn/4.23/geoscene/themes/light/main.css" />
    <script src="https://js.geoscene.cn/4.23/"></script>

    <script>
      require([
        "geoscene/views/MapView",
        "geoscene/WebMap",
        "geoscene/widgets/Expand",
        "geoscene/widgets/Legend"
      ], (
      ) => {
        const view = new MapView({
          map: new WebMap({
            portalItem: {
              // autocasts as new PortalItem()
              id: "4ac45bfebc8647b39efad59cdf0be15a"
          container: "viewDiv"
        view.ui.add(new Expand({
          content: new Legend({ view })
        }), "top-right");
        const earthquakeLayer = view.map.layers.find(layer => layer.title === "earthquake points");
        earthquakeLayer.minScale = 577790;
        earthquakeLayer.maxScale = 0;
        earthquakeLayer.visible = true;
    </script>
  </head>

  <body>
    <div id="viewDiv"></div>
  </body>
</html>

不规则多边形

此示例演示了如何汇总相关不规则多边形内的点。在此应用程序中,表示命案地点的点图层被聚合到表示芝加哥警察局的多边形图层中。在比较警察辖区的统计数据时,按辖区汇总数据是有意义的。

由于多边形形状和大小的差异,我们应使用分级符号(即具有大小视觉变量的图标)来可视化总数。使用颜色会导致过分强调大面积区域,该区域的总面积可能比人口更稠密的小区域要小。

美国芝加哥警察局报告的凶杀案总数(2008-2017 年)。原始凶杀点图层聚合为代表警察分局的多边形图层。

在点到多边形聚合分析期间,命案计数按案件的状态(即未结案、因逮捕而关闭或因未逮捕而关闭)进行分组。这些统计信息保存在输出表中,以便在弹出窗口中轻松引用它们。

aggregation-popup

由于汇总统计信息作为属性包含在表中,因此您还可以根据任何分组统计信息进行渲染。例如,您可以显示未解决的凶杀案的总数,而不是显示所有凶杀案的总数。

美国芝加哥警察局报告的未解决凶杀案(2008-2017 年)。此应用程序使用与上一示例相同的图层,但现在根据数据的子集(未解决案件与总案件)渲染数据。

众数渲染

使用 GeoScene Online 中的聚合点工具,您还可以为最常见的字符串值创建一个字段。这允许我们创建一个众数渲染图,其中大小仍然表示总计数,但颜色表示特定类别的最常见值。以下示例使用颜色来表示每个区域中受害者的主要种族或民族。

美国芝加哥警察分局报告的凶杀案总数(2008-2017 年)。每个图标的颜色表示每个分局中受害者的主要种族或民族。

此样式使用 UniqueValueRenderer 和大小视觉变量进行配置。

浏览要素

默认情况下,聚类弹出窗口具有“浏览要素”操作,允许您选择聚类中的各个要素以查看其位置和属性信息。您可以在表示聚合点数据的多边形图层中实现相同的行为。

在以下示例中,单击任何一个六边形以探索属于该图格的各个要素。

地震已聚集到边长为 30 英里的六边形中。单击任何弹出窗口中的“浏览要素”操作以查看与特定地震事件相关的位置和信息。

要实现此浏览体验,您必须执行以下操作:

为聚合图层的弹出模板创建“浏览要素”操作。

GeoScene JS API
60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 61 62 63 64 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
    <title>Hexbin aggregation</title>

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

    <link rel="stylesheet" href="https://js.geoscene.cn/4.23/geoscene/themes/light/main.css" />
    <script src="https://js.geoscene.cn/4.23/"></script>

    <script>
      require([
        "geoscene/views/MapView",
        "geoscene/WebMap",
        "geoscene/widgets/Expand",
        "geoscene/widgets/Legend",
        "geoscene/core/watchUtils"
      ], (
      ) => {
        (async ()=>{
          view = new MapView({
            map: new WebMap({
              portalItem: {
                // autocasts as new PortalItem()
                id: "4ac45bfebc8647b39efad59cdf0be15a"
            constraints: {
              snapToZoom: false,
              minScale: 30000000
            container: "viewDiv"
          await view.when();
          const hexbinLayer = view.map.layers.find(layer => layer.title === "Hexbins");
          const earthquakeLayer = view.map.layers.find(layer => layer.title === "earthquake points");
          earthquakeLayer.minScale = 577790;
          earthquakeLayer.maxScale = 0;
          earthquakeLayer.visible = true;
          let selectedFeatureHandle = null;
          let selectedFeature = null;
          let selectedAggregateFeature = null;
          hexbinLayer.popupTemplate.actions = [{
            title: "Browse features",
            id: "browse",
            className: "esri-icon-table"
          }];
          view.ui.add(new Expand({
            content: new Legend({ view })
          }), "top-right");
          view.popup.on("trigger-action", (event) => {
            const id = event.action.id;
            if(id === "browse"){
          // Clear view graphics and popup when cluster is no longer selected
          watchUtils.whenFalse(view.popup, "visible", clear);
          view.watch("scale", (scale) => {
            if(scale < earthquakeLayer.minScale){
          async function browseFeatures(aggregateGraphic){
            const layer = view.map.layers.find(layer => layer.title === "earthquake points");
            const query = layer.createQuery();
            query.returnGeometry = true;
            const { features } = await layer.queryFeatures(query);
            selectedFeatureHandle = view.popup.watch("selectedFeature", async (feature) => {
              if(selectedAggregateFeature.getObjectId() === feature.getObjectId()){
                return;
                type: "simple-marker",
                color: "rgb(50,50,50)",
                size: 8,
                style: "x",
                outline: {
                  color: "rgb(50,50,50)",
                  width: 4
              if(selectedFeature && view.graphics.includes(selectedFeature)){
                selectedFeature = null;
              if(selectedAggregateFeature.getObjectId() !== feature.getObjectId()){
          function clear(){
            view.popup.features = view.popup.features.filter( feature => feature == selectedAggregateFeature );
            if(selectedFeatureHandle){
              selectedFeatureHandle = null;
              selectedFeature = null;
    </script>
  </head>

  <body>
    <div id="viewDiv"></div>
  </body>
</html>

当用户激活浏览时,执行一个函数来查询与所选要素相交的点图层中的要素。然后将获取的要素添加到视图的弹出窗口中。

GeoScene JS API
95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 95 96 97 98 99 100 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
    <title>Hexbin aggregation</title>

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

    <link rel="stylesheet" href="https://js.geoscene.cn/4.23/geoscene/themes/light/main.css" />
    <script src="https://js.geoscene.cn/4.23/"></script>

    <script>
      require([
        "geoscene/views/MapView",
        "geoscene/WebMap",
        "geoscene/widgets/Expand",
        "geoscene/widgets/Legend",
        "geoscene/core/watchUtils"
      ], (
      ) => {
        (async ()=>{
          view = new MapView({
            map: new WebMap({
              portalItem: {
                // autocasts as new PortalItem()
                id: "4ac45bfebc8647b39efad59cdf0be15a"
            constraints: {
              snapToZoom: false,
              minScale: 30000000
            container: "viewDiv"
          await view.when();
          const hexbinLayer = view.map.layers.find(layer => layer.title === "Hexbins");
          const earthquakeLayer = view.map.layers.find(layer => layer.title === "earthquake points");
          earthquakeLayer.minScale = 577790;
          earthquakeLayer.maxScale = 0;
          earthquakeLayer.visible = true;
          let selectedFeatureHandle = null;
          let selectedFeature = null;
          let selectedAggregateFeature = null;
            title: "Browse features",
            id: "browse",
            className: "esri-icon-table"
          view.ui.add(new Expand({
            content: new Legend({ view })
          }), "top-right");
          view.popup.on("trigger-action", (event) => {
            const id = event.action.id;
            if(id === "browse"){
          // Clear view graphics and popup when cluster is no longer selected
          watchUtils.whenFalse(view.popup, "visible", clear);
          view.watch("scale", (scale) => {
            if(scale < earthquakeLayer.minScale){
          async function browseFeatures(aggregateGraphic){
            const layer = view.map.layers.find(layer => layer.title === "earthquake points");
            const query = layer.createQuery();
            query.returnGeometry = true;
            query.geometry = aggregateGraphic.geometry;
            const { features } = await layer.queryFeatures(query);
            view.popup.features = [aggregateGraphic].concat(features);
            selectedFeatureHandle = view.popup.watch("selectedFeature", async (feature) => {
              if(selectedAggregateFeature.getObjectId() === feature.getObjectId()){
                return;
                type: "simple-marker",
                color: "rgb(50,50,50)",
                size: 8,
                style: "x",
                outline: {
                  color: "rgb(50,50,50)",
                  width: 4
              if(selectedFeature && view.graphics.includes(selectedFeature)){
                selectedFeature = null;
              if(selectedAggregateFeature.getObjectId() !== feature.getObjectId()){
          function clear(){
            view.popup.features = view.popup.features.filter( feature => feature == selectedAggregateFeature );
            if(selectedFeatureHandle){
              selectedFeatureHandle = null;
              selectedFeature = null;
    </script>
  </head>

  <body>
    <div id="viewDiv"></div>
  </body>
</html>

选择新要素后,将其添加到视图中并从视图中清除之前的任何图形。

GeoScene JS API
104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 127 127 127 127 127 127 127 127 127 127 127 127 127 127 127 127 127 127 127 127 127 127 127
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
    <title>Hexbin aggregation</title>

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

    <link rel="stylesheet" href="https://js.geoscene.cn/4.23/geoscene/themes/light/main.css" />
    <script src="https://js.geoscene.cn/4.23/"></script>

    <script>
      require([
        "geoscene/views/MapView",
        "geoscene/WebMap",
        "geoscene/widgets/Expand",
        "geoscene/widgets/Legend",
        "geoscene/core/watchUtils"
      ], (
      ) => {
        (async ()=>{
          view = new MapView({
            map: new WebMap({
              portalItem: {
                // autocasts as new PortalItem()
                id: "4ac45bfebc8647b39efad59cdf0be15a"
            constraints: {
              snapToZoom: false,
              minScale: 30000000
            container: "viewDiv"
          await view.when();
          const hexbinLayer = view.map.layers.find(layer => layer.title === "Hexbins");
          const earthquakeLayer = view.map.layers.find(layer => layer.title === "earthquake points");
          earthquakeLayer.minScale = 577790;
          earthquakeLayer.maxScale = 0;
          earthquakeLayer.visible = true;
          let selectedFeatureHandle = null;
          let selectedFeature = null;
          let selectedAggregateFeature = null;
            title: "Browse features",
            id: "browse",
            className: "esri-icon-table"
          view.ui.add(new Expand({
            content: new Legend({ view })
          }), "top-right");
          view.popup.on("trigger-action", (event) => {
            const id = event.action.id;
            if(id === "browse"){
          // Clear view graphics and popup when cluster is no longer selected
          watchUtils.whenFalse(view.popup, "visible", clear);
          view.watch("scale", (scale) => {
            if(scale < earthquakeLayer.minScale){
          async function browseFeatures(aggregateGraphic){
            const layer = view.map.layers.find(layer => layer.title === "earthquake points");
            const query = layer.createQuery();
            query.returnGeometry = true;
            const { features } = await layer.queryFeatures(query);
            selectedFeatureHandle = view.popup.watch("selectedFeature", async (feature) => {
              if(selectedAggregateFeature.getObjectId() === feature.getObjectId()){
                return;
              }
              feature.symbol = {
                type: "simple-marker",
                color: "rgb(50,50,50)",
                size: 8,
                style: "x",
                outline: {
                  color: "rgb(50,50,50)",
                  width: 4
                }
              }
              if(selectedFeature && view.graphics.includes(selectedFeature)){
                view.graphics.remove(selectedFeature);
                selectedFeature = null;
              }
              if(selectedAggregateFeature.getObjectId() !== feature.getObjectId()){
                view.graphics.add(feature);
                selectedFeature = feature;
              }
            });
          function clear(){
            view.popup.features = view.popup.features.filter( feature => feature == selectedAggregateFeature );
            if(selectedFeatureHandle){
              selectedFeatureHandle = null;
              selectedFeature = null;
    </script>
  </head>

  <body>
    <div id="viewDiv"></div>
  </body>
</html>

同样的技术也可应用于不规则多边形的聚合图层。通过单击弹出窗口中的操作并选择其中一个凶杀地点来浏览辖区内的要素。

命案聚合到警察局。单击任何弹出窗口中的“浏览要素”操作以查看与特定命案事件相关的位置和信息。

API 支持

下表描述了非常适合每种可视化技术的几何和视图类型。

全部支持部分支持不支持
  • 1. 不支持通过选择减少要素
  • 2. 仅支持通过选择减少要素
  • 3. 仅支持比例驱动过滤

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.