Tutorial: Query features (spatial)

Learn how to execute a spatial query to access polygon features from a feature service.

Make a spatial query using API key authentication

A feature layer can contain a large number of features stored in ArcGIS. To access a subset of these features, you can execute an SQL or spatial query, either together or individually. The results can contain the attributes, geometry, or both for each record. SQL and spatial queries are useful when a feature layer is very large and you want to access only a subset of its data.

In this tutorial, you sketch a feature on the map and use ArcGIS REST JS to perform a spatial query against the LA County Parcels hosted feature layer. The layer contains ±2.4 million features. The spatial query returns all of the parcels that intersect the sketched feature.

Prerequisites

Get the starter app

Select a type of authentication below and follow the steps to create a new application.

You can choose one of the following to create a new CodePen:

  • Option 1: Complete the Display a scene tutorial; or,
  • Option 2: Start from the Display a scene tutorial .

Set up authentication

Create developer credentials in your portal for the type of authentication you selected.

Create a new API key credential with the correct privileges to access the resources used in this tutorial.

  1. Go to the Create an API key tutorial and create an API key with the following privilege(s):
    • Privileges
      • Location services > Basemaps
  2. Copy the API key access token to your clipboard when prompted.

Set developer credentials

Use the API key or OAuth developer credentials created in the previous step in your application.

  1. Add <script> elements in the HTML <body> and create an accessToken variable to store your access token. Set YOUR_ACCESS_TOKEN with the access token you previously copied from your API key credentials.

    Expand
    Use dark colors for code blocks
    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
      <script>
    
        /* Use for API key authentication */
        const accessToken = "YOUR_ACCESS_TOKEN";
    
      </script>
    
    Expand
  2. Set the defaultAccessToken included with Cesium to authenticate requests to the ArcGIS services used in this tutorial.

    Expand
    Use dark colors for code blocks
    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
      <script>
    
        /* Use for API key authentication */
        const accessToken = "YOUR_ACCESS_TOKEN";
    
        Cesium.ArcGisMapService.defaultAccessToken = accessToken;
    
      </script>
    
    Expand

Get a Cesium ion access token

All Cesium applications must use an access token provided through Cesium ion. This token allows you to access assets such as Cesium World Terrain in your application.

  1. Go to your Cesium ion dashboard to generate an access token. Copy the key to your clipboard.

  2. Create a cesiumAccessToken variable and replace YOUR_CESIUM_ACCESS_TOKEN with the access token you copied from the Cesium ion dashboard.

    Use dark colors for code blocks
    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
      <script>
    
        /* Use for API key authentication */
        const accessToken = "YOUR_ACCESS_TOKEN";
    
        // or
    
        /* Use for user authentication */
        // const session = await arcgisRest.ArcGISIdentityManager.beginOAuth2({
        //   clientId: "YOUR_CLIENT_ID", // Your client ID from OAuth credentials
        //   redirectUri: "YOUR_REDIRECT_URL", // The redirect URL registered in your OAuth credentials
        //   portal: "YOUR_PORTAL_URL" // Your portal URL
        // })
    
        // const accessToken = session.token;
    
        Cesium.ArcGisMapService.defaultAccessToken = accessToken;
    
        const cesiumAccessToken = "YOUR_CESIUM_ACCESS_TOKEN";
    
      </script>
    
  3. Configure Cesium.Ion.defaultAccessToken with the Cesium access token to validate the application.

    Use dark colors for code blocks
    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
      <script>
    
        /* Use for API key authentication */
        const accessToken = "YOUR_ACCESS_TOKEN";
    
        // or
    
        /* Use for user authentication */
        // const session = await arcgisRest.ArcGISIdentityManager.beginOAuth2({
        //   clientId: "YOUR_CLIENT_ID", // Your client ID from OAuth credentials
        //   redirectUri: "YOUR_REDIRECT_URL", // The redirect URL registered in your OAuth credentials
        //   portal: "YOUR_PORTAL_URL" // Your portal URL
        // })
    
        // const accessToken = session.token;
    
        Cesium.ArcGisMapService.defaultAccessToken = accessToken;
    
        const cesiumAccessToken = "YOUR_CESIUM_ACCESS_TOKEN";
    
        Cesium.Ion.defaultAccessToken = cesiumAccessToken;
    
      </script>
    

Code

Use dark colors for code blocksCopy
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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
<!DOCTYPE html>
<html lang="en">


<head>
    <meta charset="utf-8">
    <title>CesiumJS: Query a feature layer (spatial)</title>
    <!-- Include the CesiumJS JavaScript and CSS files -->
    <script src="https://cesium.com/downloads/cesiumjs/releases/1.121/Build/Cesium/Cesium.js"></script>
    <link href="https://cesium.com/downloads/cesiumjs/releases/1.121/Build/Cesium/Widgets/widgets.css" rel="stylesheet">

    <script src="https://unpkg.com/@esri/arcgis-rest-request@4/dist/bundled/request.umd.js"></script>
    <script
        src="https://unpkg.com/@esri/arcgis-rest-feature-service@4/dist/bundled/feature-service.umd.js"></script>

    <script src="https://unpkg.com/@terraformer/arcgis@2.0.7/dist/t-arcgis.umd.js"></script>
    <style>
        html,
        body,
        #cesiumContainer {
            width: 100%;
            height: 100%;
            padding: 0px;
            margin: 0px;
        }

        #mode-select {
            position: absolute;
            top: 8px;
            left: 8px;
            padding: 4px 8px;
            font-size: 16px;
            border-radius: 0;
            /* Causes more uniform appearance across browsers. */
        }
    </style>

</head>

<body>
    <div id="cesiumContainer"></div>

    <select id="mode-select">
        <option value="">Choose a type of feature to draw...</option>
        <option value="Point">Draw point</option>
        <option value="Polyline">Draw line</option>
        <option value="Polygon" selected>Draw polygon</option>
    </select>

    <script>

        /* Use for API key authentication */
        const accessToken = "YOUR_ACCESS_TOKEN";

        // or

        /* Use for user authentication */
        // const session = await arcgisRest.ArcGISIdentityManager.beginOAuth2({
        //   clientId: "YOUR_CLIENT_ID", // Your client ID from OAuth credentials
        //   redirectUri: "YOUR_REDIRECT_URL", // The redirect URL registered in your OAuth credentials
        //   portal: "https://www.arcgis.com/sharing/rest" // Your portal URL
        // })

        // const accessToken = session.token;

        Cesium.ArcGisMapService.defaultAccessToken = accessToken;

        const authentication = arcgisRest.ApiKeyManager.fromKey(accessToken);

        const cesiumAccessToken = "YOUR_CESIUM_ACCESS_TOKEN";
        Cesium.Ion.defaultAccessToken = cesiumAccessToken;

        const arcGisImagery = Cesium.ArcGisMapServerImageryProvider.fromBasemapType(Cesium.ArcGisBaseMapType.SATELLITE, {
            enablePickFeatures: false
        });

        const viewer = new Cesium.Viewer("cesiumContainer", {

            baseLayer: Cesium.ImageryLayer.fromProviderAsync(arcGisImagery),

            timeline: false,
            animation: false,
            geocoder: false

        });

        viewer.camera.setView({
            destination: Cesium.Cartesian3.fromDegrees(-118.80624, 34.008, 3000),
            orientation: {
                heading: Cesium.Math.toRadians(0.0),
                pitch: Cesium.Math.toRadians(-70.0),
            }
        });

        // Add Esri attribution
        // Learn more in https://esriurl.com/attribution
        const poweredByEsri = new Cesium.Credit("Powered by <a href='https://www.esri.com/en-us/home' target='_blank'>Esri</a>", true)
        viewer.creditDisplay.addStaticCredit(poweredByEsri);

        const layerName = "LA_County_Parcels";
        const layerURL = "https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/" + layerName + "/FeatureServer/0";

        // Attribution text retrieved from https://arcgis.com/home/item.html?id=a6fdf2ee0e454393a53ba32b9838b303
        viewer.creditDisplay.addStaticCredit(new Cesium.Credit("County of Los Angeles Office of the Assessor", false));

        // Draw features
        //
        if (!viewer.scene.pickPositionSupported) {
            window.alert("This browser does not support pickPosition.");
        }

        viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(
            Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK
        );
        function createPoint(worldPosition) {
            const point = viewer.entities.add({
                position: worldPosition,
                point: {
                    color: Cesium.Color.WHITE,
                    pixelSize: 5,
                    heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
                },
            });
            return point;
        }
        let drawingMode;
        function drawShape(positionData) {
            let shape;
            if (drawingMode === "Polyline") {
                shape = viewer.entities.add({
                    polyline: {
                        positions: positionData,
                        clampToGround: true,
                        width: 3,
                    },
                });
            }
            else if (drawingMode === "Polygon") {
                shape = viewer.entities.add({
                    polygon: {
                        hierarchy: positionData,
                        outline: true,
                        outlineWidth: 10,
                        material: new Cesium.ColorMaterialProperty(
                            Cesium.Color.WHITE.withAlpha(0.4)
                        ),
                    },
                });
            }
            return shape;
        }
        let activeShapePoints = [];
        let activeShape;
        let floatingPoint;
        let newShape = true;

        const handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
        handler.setInputAction(function (event) {
            if (!drawingMode) return;
            if (newShape) {
                viewer.entities.removeAll();
                newShape = false;
            }

            const earthPosition = viewer.camera.pickEllipsoid(event.position);
            // `earthPosition` will be undefined if our mouse is not over the globe.
            if (Cesium.defined(earthPosition)) {

                if (drawingMode === "Point") {
                    activeShapePoints.push(earthPosition);
                    createPoint(earthPosition);
                    performQuery()
                }
                else {
                    if (activeShapePoints.length === 0) {
                        floatingPoint = createPoint(earthPosition);
                        activeShapePoints.push(earthPosition);
                        const dynamicPositions = new Cesium.CallbackProperty(function () {
                            if (drawingMode === "Polygon") {
                                return new Cesium.PolygonHierarchy(activeShapePoints);
                            }
                            return activeShapePoints;
                        }, false);
                        activeShape = drawShape(dynamicPositions);
                    }
                    activeShapePoints.push(earthPosition);
                    createPoint(earthPosition);
                }
            }
        }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

        handler.setInputAction(function (event) {
            if (Cesium.defined(floatingPoint)) {
                const newPosition = viewer.camera.pickEllipsoid(event.endPosition);
                if (Cesium.defined(newPosition)) {
                    floatingPoint.position.setValue(newPosition);
                    activeShapePoints.pop();
                    activeShapePoints.push(newPosition);
                }
            }
        }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
        //
        // Process features
        //

        function getArcGISFeature(points) {
            //convert each point to cartesian latlng
            const degrees = []
            for (point of points) {
                const cartographic = Cesium.Cartographic.fromCartesian(point);
                degrees.push([Cesium.Math.toDegrees(cartographic.longitude), Cesium.Math.toDegrees(cartographic.latitude)]);
            }
            let coords;
            let type = drawingMode;
            if (drawingMode === "Point") {
                coords = degrees[0];
            }
            else if (drawingMode === "Polyline") {
                type = "LineString";
                coords = degrees;
            }
            else {
                coords = [degrees];
            }

            const result = Terraformer.geojsonToArcGIS({
                "coordinates": coords,
                "type": type
            })

            return result;

        }

        function executeQuery(geometry) {

            arcgisRest.queryFeatures({
                url: layerURL,
                authentication,
                f: "geojson",

                geometry: geometry,
                geometryType: "esriGeometry" + drawingMode,
                inSR: 4326,
                spatialRel: "esriSpatialRelIntersects",
                returnGeometry: true

            })

                .then((response) => {

                    Cesium.GeoJsonDataSource.load(response).then((data) => {
                        viewer.dataSources.add(data);
                    })

                })
        }

        function performQuery() {
            viewer.dataSources.removeAll();

            const geometry = getArcGISFeature(activeShapePoints);
            executeQuery(geometry);

            drawShape(activeShapePoints);
            terminateShape()
        }

        // Redraw the shape so it's not dynamic and remove the dynamic shape.
        function terminateShape() {

            viewer.entities.remove(floatingPoint);
            viewer.entities.remove(activeShape);
            floatingPoint = undefined;
            activeShape = undefined;
            activeShapePoints = [];
            newShape = true;
        }

        // Event listeners
        handler.setInputAction(function (event) {
            if (drawingMode === "Point" || !drawingMode) return;
            activeShapePoints.pop();
            performQuery();
        }, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);

        const select = document.getElementById("mode-select");
        select.addEventListener("change", () => {
            terminateShape();
            viewer.entities.removeAll();
            drawingMode = select.value;
        })


        const startCoords = [[[-118.80364, 34.02090], [-118.79641, 34.01991], [-118.79818, 34.01585], [-118.80347, 34.01620]]];
        drawingMode = 'Polygon';

        // Draw initial shape
        const startPoints = []
        for (coord of startCoords[0]) {
            startPoints.push(Cesium.Cartesian3.fromDegrees(coord[0], coord[1]))
        }
        drawShape(startPoints)

        // Query initial shape
        const startShape = Terraformer.geojsonToArcGIS({
            "coordinates": startCoords,
            "type": drawingMode
        })
        executeQuery(startShape);
        terminateShape();


    </script>

</body>

</html>

What's next?

Learn how to use additional ArcGIS location services in these tutorials:

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