Thinning

Roads and highways in Singapore. Zoom in and out to view how features are thinned based on highway classification. Only larger, more traveled highways display at small scales. This reduces the initial download size and declutters the visualization.

What is thinning?

Thinning is a method of decluttering the view by removing features that overlap one another. This is helpful when many features overlap and you want to display uncluttered data, but don't necessarily need to communicate its density.

How thinning works

There are a couple of approaches to thinning:

  1. Feature reduction by selection only applies to point layers in 3D scenes. Feature reduction selection is configured on the featureReduction property of point layers. When set, overlapping features are randomly removed from the view and displayed at scales and camera angles where they no longer overlap nearby features.
  2. Scale-driven filter is the process of controlling which features are downloaded to the client based on the view scale. This is useful for reducing the download size of large layers and improving the visualization, particularly for mobile devices.

Examples

Feature reduction by selection (3D)

The following example demonstrates how to declutter the view by randomly removing overlapping features. This is controlled using the FeatureReductionSelection option on point layers in 3D scenes. Simply set the featureReduction type on the layer to selection and features will be thinned automatically.

Points of interest in Lyon, France. Thinning helps declutter the view to make it easier for users to navigate a scene. Uncheck the box in the top right corner to compare the cluttered view with the decluttered view. Zoom in and out to see how feature reduction selection affects the visualization.
ArcGIS JS API
Use dark colors for code blocks
174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 174 175 176 177 177 177 177 177 177 177 177 177 177 177 177 177 177 177 177 177 177 177 177 177 177 177 177 177 177 177 177 177 177
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
<html>
  <head>
    <meta charset="utf-8" />
    <title>
      Point styles for cities
    </title>
    <link rel="stylesheet" href="https://js.arcgis.com/4.22/esri/themes/light/main.css" />
    <script src="https://js.arcgis.com/4.22/"></script>

    <style>
      html,
      body,
      #viewDiv {
        width: 100%;
        height: 100%;
        margin: 0;
        padding: 0;
      #cityStyle {
        background-color: white;
        text-align: center;
        padding: 10px;
        font-size: 0.9em;
      #cityStyle label {
        padding-right: 10px;
        cursor: pointer;
    </style>

    <script>
      require([
        "esri/WebScene",
        "esri/layers/FeatureLayer",
        "esri/views/SceneView",
        "esri/widgets/Legend"
      ], (WebScene, FeatureLayer, SceneView, Legend) => {
        // Load the webscene with buildings
        const webscene = new WebScene({
          portalItem: {
            // autocasts as new PortalItem()
            id: "711ddecedece4fd88b728bfe4322c22b"
        const view = new SceneView({
          container: "viewDiv",
          map: webscene,
          environment: {
            lighting: {
              directShadowsEnabled: true,
              ambientOcclusionEnabled: true
        // verticalOffset shifts the symbol vertically
        const verticalOffset = {
          screenLength: 40,
          maxWorldLength: 200,
          minWorldLength: 35
        // Function that automatically creates the symbol for the points of interest
        function getUniqueValueSymbol(name, color) {
          return {
            type: "point-3d",
            symbolLayers: [
                type: "icon",
                resource: {
                  href: name
                size: 20,
                outline: {
                  color: "white",
                  size: 2
            verticalOffset: verticalOffset,
            callout: {
              type: "line",
              color: "white",
              size: 2,
              border: {
                color: color
        const pointsRenderer = {
          type: "unique-value",
          field: "Type",
          uniqueValueInfos: [
              value: "Museum",
              symbol: getUniqueValueSymbol(
                "https://developers.arcgis.com/javascript/latest/sample-code/visualization-point-styles/live/Museum.png",
                "#D13470"
              value: "Restaurant",
              symbol: getUniqueValueSymbol(
                "https://developers.arcgis.com/javascript/latest/sample-code/visualization-point-styles/live/Restaurant.png",
                "#F97C5A"
              value: "Church",
              symbol: getUniqueValueSymbol(
                "https://developers.arcgis.com/javascript/latest/sample-code/visualization-point-styles/live/Church.png",
                "#884614"
              value: "Hotel",
              symbol: getUniqueValueSymbol(
                "https://developers.arcgis.com/javascript/latest/sample-code/visualization-point-styles/live/Hotel.png",
                "#56B2D6"
              value: "Park",
              symbol: getUniqueValueSymbol(
                "https://developers.arcgis.com/javascript/latest/sample-code/visualization-point-styles/live/Park.png",
                "#40C2B4"
        const pointsLayer = new FeatureLayer({
          url: "http://services.arcgis.com/V6ZHFr6zdgNZuVG0/arcgis/rest/services/LyonPointsOfInterest/FeatureServer",
          title: "Touristic attractions",
          elevationInfo: {
            mode: "relative-to-scene"
          renderer: pointsRenderer,
          outFields: ["*"],
          screenSizePerspectiveEnabled: true,
          labelingInfo: [
              labelExpressionInfo: {
                value: "{Name}"
              symbol: {
                type: "label-3d",
                symbolLayers: [
                    type: "text",
                    material: {
                      color: "white"
                    halo: {
                      size: 1,
                      color: [50, 50, 50]
                    size: 10
        pointsLayer.featureReduction = {
          type: "selection"
        };
        // add functionality on the controls for selection, perspective, callout lines and relative-to-scene elevation mode
        document
          .getElementById("cityStyle")
          .addEventListener("change", (event) => {
            if (event.target.id === "declutter") {
              const type = {
                type: "selection"
              pointsLayer.featureReduction = event.target.checked ? type : null;
        view.ui.add(document.getElementById("cityStyle"), "top-right");
    </script>
  </head>

  <body>
    <div id="viewDiv" class="esri-widget"></div>
    <div id="cityStyle">
      <input type="checkbox" id="declutter" name="mode" checked />
      <label for="declutter">Declutter view</label>
    </div>
  </body>
</html>

Scale-driven filter

This example demonstrates how to control which features are downloaded to the client based on the view scale. This is useful for reducing the download size of large layers, particularly for mobile devices.

Roads and highways in Singapore. Zoom in and out to view how features are thinned based on highway classification. Only larger, more traveled highways display at small scales. This reduces the initial download size and declutters the visualization.

For example, the OpenStreetMap Asia highways layer contains more than 46 million high resolution line features with more than 20 attributes. Since this layer requires several gigabytes of memory, it isn't feasible to load the entire dataset to a browser.

Thinning this data can significantly improve the initial download size, and the visualization.

_ThinningNo thinning
Initial download size141kB (27.8kB compressed)12.2MB (2.1MB compressed)
Preview thinned not thinned

First, you should set scale visibility constraints to restrict layer visibility at scales best suited given the density of the data. This is controlled by the minScale and maxScale properties of the layer.

ArcGIS JS API
Use dark colors for code blocks
37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 38 39 40 41 42 43 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44
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
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="initial-scale=1,maximum-scale=1,user-scalable=no"
    />
    <title>
      Highways in Singapore
    </title>
    <style>
      html,
      body,
      #viewDiv {
        padding: 0;
        margin: 0;
        height: 100%;
        width: 100%;
        background-color: white;
    </style>

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

    <script>
      require([
        "esri/views/MapView",
        "esri/WebMap",
        "esri/layers/FeatureLayer"
      ], (
      ) => {
        const highwaysLayer = new FeatureLayer({
          portalItem: {
            id: "af989f50f24040e4890dce13ee1b1561"
          },
          minScale: 1000000,
          maxScale: 0
        });
        const view = new MapView({
          container: "viewDiv",
          map: new WebMap({
            basemap: "gray-vector",
            layers: [ highwaysLayer ]
          constraints: {
            snapToZoom: false
          viewpoint: {
            rotation: 0,
            scale: 175000,
            targetGeometry: {
              type: "point",
              spatialReference: {
                latestWkid: 3857,
                wkid: 102100
              x: 11557036.825180829,
              y: 147507.71438237422
        const levels = [{
          minScale: Infinity,
          maxScale: 150000,
          definitionExpression: "highway IN ('motorway')"
          minScale: 150000,
          maxScale: 60000,
          definitionExpression: "highway IN ('motorway', 'trunk', 'trunk_link')"
          minScale: 60000,
          maxScale: 45000,
          definitionExpression: "highway IN ('motorway', 'motorway_link', 'trunk', 'trunk_link', 'primary', 'primary_link')"
          minScale: 45000,
          maxScale: 30000,
          definitionExpression: "highway IN ('motorway', 'motorway_link', 'trunk', 'trunk_link', 'primary', 'primary_link')"
          minScale: 30000,
          maxScale: 10000,
          definitionExpression: "highway IN ('motorway', 'motorway_link', 'trunk', 'trunk_link', 'primary', 'primary_link', 'secondary', 'secondary_link')"
        // Update the definition expression based on view scale
        view.watch("scale", function(scale){
          let definitionExpression = null;
          const level = levels.find(
            level =>
          if(level){
          if(definitionExpression !== highwaysLayer.definitionExpression){
    </script>
  </head>

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

While this will prevent users from loading unreasonable amounts of data to the browser, it still may not be aggressive enough, especially for mobile devices. You can further reduce download size by filtering features dynamically by updating the layer's definitionExpression as the view's scale changes.

This snippet shows how you can define definition expressions (i.e. SQL where clauses) to apply to the layer when the user zooms to certain scales. Notice how this allows you to establish a priority for display. For example, only the largest motorways are displayed at the smallest scale. As the user progressively zooms in, more highways load in the view based on their size classification. This ensures only the minimum data required by users is downloaded to the view, effectively thinning it.

ArcGIS JS API
Use dark colors for code blocks
71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 71 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 108 108 108 108 108 108 108 108 108 108
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
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="initial-scale=1,maximum-scale=1,user-scalable=no"
    />
    <title>
      Highways in Singapore
    </title>
    <style>
      html,
      body,
      #viewDiv {
        padding: 0;
        margin: 0;
        height: 100%;
        width: 100%;
        background-color: white;
    </style>

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

    <script>
      require([
        "esri/views/MapView",
        "esri/WebMap",
        "esri/layers/FeatureLayer"
      ], (
      ) => {
        const highwaysLayer = new FeatureLayer({
          portalItem: {
            id: "af989f50f24040e4890dce13ee1b1561"
          minScale: 1000000,
          maxScale: 0
        const view = new MapView({
          container: "viewDiv",
          map: new WebMap({
            basemap: "gray-vector",
            layers: [ highwaysLayer ]
          constraints: {
            snapToZoom: false
          viewpoint: {
            rotation: 0,
            scale: 175000,
            targetGeometry: {
              type: "point",
              spatialReference: {
                latestWkid: 3857,
                wkid: 102100
              x: 11557036.825180829,
              y: 147507.71438237422
        const levels = [{
          minScale: Infinity,
          maxScale: 150000,
          definitionExpression: "highway IN ('motorway')"
        }, {
          minScale: 150000,
          maxScale: 60000,
          definitionExpression: "highway IN ('motorway', 'trunk', 'trunk_link')"
        }, {
          minScale: 60000,
          maxScale: 45000,
          definitionExpression: "highway IN ('motorway', 'motorway_link', 'trunk', 'trunk_link', 'primary', 'primary_link')"
        }, {
          minScale: 45000,
          maxScale: 30000,
          definitionExpression: "highway IN ('motorway', 'motorway_link', 'trunk', 'trunk_link', 'primary', 'primary_link')"
        }, {
          minScale: 30000,
          maxScale: 10000,
          definitionExpression: "highway IN ('motorway', 'motorway_link', 'trunk', 'trunk_link', 'primary', 'primary_link', 'secondary', 'secondary_link')"
        }];

        // Update the definition expression based on view scale
        view.watch("scale", function(scale){
          let definitionExpression = null;
          const level = levels.find(
            level =>
            scale < level.minScale && scale >= level.maxScale
          );

          if(level){
            definitionExpression = level.definitionExpression;
          }
          if(definitionExpression !== highwaysLayer.definitionExpression){
            highwaysLayer.definitionExpression = definitionExpression;
          }
        });
    </script>
  </head>

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

API support

The following table describes the geometry and view types that are suited well for each visualization technique.

2D3DPointsLinesPolygonsMeshClient-sideServer-side
Clustering
Heatmap
Opacity
Bloom
Aggregation
Thinning11123
Visible scale range
Full supportPartial supportNo support
  • 1. Feature reduction selection not supported
  • 2. Only by feature reduction selection
  • 3. Only by scale-driven filter

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