Learn how to query a feature layer with SQL.
A feature layer can contain a large number of features stored in ArcGIS. To access a subset of the features, you can execute a SQL or spatial query, or both at the same time. You can also return the attributes, geometry, or both attributes and geometry for each record. SQL and spatial queries are useful when a feature layer is very large and you just want to access a subset of the data.
In this tutorial, you perform server-side SQL queries to return a subset of the features from the LA County Parcel hosted feature layer. The feature layer contains over 2.4 million features. The resulting features are displayed as graphics on the map. A pop-up is also used to display feature attributes.
Prerequisites
An ArcGIS Location Platform or ArcGIS Online account.
Steps
Get the starter app
Select a type of authentication below and follow the steps to create a new application.
Set up authentication
Create developer credentials in your portal for the type of authentication you selected.
Set developer credentials
Use the API key or OAuth developer credentials so your application can access location services.
Add API references
This tutorial uses ArcGIS REST JS to query the feature layer.
- In the
<head
element, add references to the ArcGIS REST JS library.>
<script src=https://unpkg.com/maplibre-gl@4/dist/maplibre-gl.js></script>
<link href=https://unpkg.com/maplibre-gl@4/dist/maplibre-gl.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>
<style>
html,
body,
#map {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
font-family: Arial, Helvetica, sans-serif;
font-size: 14px;
color: #323232;
}
</style>
</head>
Create a SQL selector
Hosted feature layers support a standard SQL query where clause. Use an HTML <select
element to provide a list of SQL queries for the LA County Parcel feature layer.
To add the <select
HTML element as a Map control, you create an object that implements the MapLibre GL JS IControl interface.
Creating an IControl allows MapLibre GL JS to position it, which prevents your control overlapping other MapLibre GL JS controls.
-
Create a
Query
class with anControl on
function. Inside, create aAdd <div
element with a> <select
inside, with options for each of the SQL> where
clauses. Return this element.Use dark colors for code blocks const map = new maplibregl.Map({ container: "map", // the id of the div element style: `https://basemapstyles-api.arcgis.com/arcgis/rest/services/styles/v2/styles/${basemapEnum}?token=${accessToken}`, zoom: 12, // starting zoom center: [-118.80543, 34.03] // starting location [longitude, latitude] }); class QueryControl { onAdd(map) { const template = document.createElement("template"); template.innerHTML = `<div class="maplibregl-ctrl maplibregl-ctrl-group" style="margin:20px;"> <select style="font-size:16px; padding:4px 8px;"> <option value="">Choose a WHERE clause...</option> <option>UseType = 'Residential'</option> <option>UseType = 'Government'</option> <option>UseType = 'Irrigated Farm'</option> <option>TaxRateArea = 10853</option> <option>TaxRateArea = 10860</option> <option>TaxRateArea = 08637</option> <option>Roll_LandValue > 1000000</option> <option>Roll_LandValue < 1000000</option> </select> </div>`; return template.content; } }
-
Create a
Query
and add it to the Map withControl map.add
.Control Use dark colors for code blocks return template.content; } } const queryControl = new QueryControl(); map.addControl(queryControl);
Add parcel layer
Parcels returned by the query are simple GeoJSON polygons. You can display them with a GeoJSON source and a fill layer.
-
Create an
add
function. Inside, add a GeoJSON source and a fill layer. Add the layer before the first symbol layer.Parcel Layer By inserting the polygon layer beneath the icon and text layers, it keeps the labels readable. In MapLibre GL JS, there is no distinction between "basemap" layers and "overlay layers", so you can insert the layer anywhere.
To determine the first symbol layer, you can use
map.get
to get the style, then iterate to find the first layer whoseStyle type
issymbol
.See the MapLibre GL JS documentation for details.
Use dark colors for code blocks const queryControl = new QueryControl(); map.addControl(queryControl); function addParcelLayer() { map.addSource("parcels", { type: "geojson", data: { type: "FeatureCollection", features: [] }, }); const firstSymbolLayer = map.getStyle().layers.find((l) => l.type === "symbol"); map.addLayer( { id: "parcels-fill", source: "parcels", type: "fill", paint: { "fill-color": "hsla(200, 80%,50%, 0.5)", "fill-outline-color": "hsl(360, 100%, 100%)" } }, firstSymbolLayer.id ); // insert new layer before this one map.addLayer({ id: "parcels-line", source: "parcels", type: "line", paint: { "line-width": 0.5, "line-color": "hsl(360, 100%,100%)" } }); // set outline style for parcels }
-
Add the data attribution for the feature layer source.
- Go to the LA County Parcels item.
- Scroll down to the Credits (Attribution) section and copy its value.
- Create an
attribution
property and paste the attribution value from the item.Use dark colors for code blocks const queryControl = new QueryControl(); map.addControl(queryControl); function addParcelLayer() { map.addSource("parcels", { type: "geojson", data: { type: "FeatureCollection", features: [] }, // Attribution text retrieved from https://arcgis-devlabs.maps.arcgis.com/home/item.html?id=a6fdf2ee0e454393a53ba32b9838b303 attribution: "LA County Parcels" }); const firstSymbolLayer = map.getStyle().layers.find((l) => l.type === "symbol"); map.addLayer( { id: "parcels-fill", source: "parcels", type: "fill", paint: { "fill-color": "hsla(200, 80%,50%, 0.5)", "fill-outline-color": "hsl(360, 100%, 100%)" } }, firstSymbolLayer.id ); // insert new layer before this one map.addLayer({ id: "parcels-line", source: "parcels", type: "line", paint: { "line-width": 0.5, "line-color": "hsl(360, 100%,100%)" } }); // set outline style for parcels }
Add load handler
To add layers to the map, you need to use the load
event to ensure the map is fully loaded.
-
Add an event handler for the
load
event. Inside, calladd
.Parcel Layer Use dark colors for code blocks const firstSymbolLayer = map.getStyle().layers.find((l) => l.type === "symbol"); map.addLayer( { id: "parcels-fill", source: "parcels", type: "fill", paint: { "fill-color": "hsla(200, 80%,50%, 0.5)", "fill-outline-color": "hsl(360, 100%, 100%)" } }, firstSymbolLayer.id ); // insert new layer before this one map.addLayer({ id: "parcels-line", source: "parcels", type: "line", paint: { "line-width": 0.5, "line-color": "hsl(360, 100%,100%)" } }); // set outline style for parcels } map.on("load", () => { addParcelLayer(); });
Execute the query
Use the ArcGIS REST JS queryFeatures method to find features in the LA County Parcels feature layer that match the selected where
clause.
To limit the query results to the visible extent of the Map, you can set the queryFeatures geometry
property. To do so, use map.get
to get the extent of the Map viewport, and then convert it to the required esri
format of [minx, miny, maxx, maxy]
. This confines the SQL query to the geographic bounds (extent) of the map.
When the matching parcels are returned, call set
on the parcels source to display them.
-
Create a function called
execute
withQuery where
andClause geometry
parameters. Inside, create a newarcgis
to access the feature service. CallRest. Api Key Manager arcgis
. Pass theRest.query Features geometry
andgeometry
. Specify GeoJSON as the return type, requestingType return
and specificGeometry out
. All of the features within the geometry will be returned with attribute information set by theFields out
property.Fields There are many other spatial relationships that you can specify with
spatial
. For example, you can useRel esri
to only return parcels completely within the viewport. See the ArcGIS services reference for details.Spatial Rel Contains Use dark colors for code blocks function executeQuery(whereClause, geometry) { arcgisRest .queryFeatures({ url: "https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/LA_County_Parcels/FeatureServer/0", geometry: geometry, geometryType: "esriGeometryEnvelope", inSR: 4326, // EPSG:4326 uses latitudes and longitudes spatialRel: "esriSpatialRelIntersects", where: whereClause, f: "geojson", returnGeometry: true, outFields: ["APN", "UseType", "TaxRateCity", " Roll_LandValue"] }) }
-
Add a response handler. Inside, set the returned parcels as the data for the parcels source.
Use dark colors for code blocks arcgisRest .queryFeatures({ url: "https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/LA_County_Parcels/FeatureServer/0", geometry: geometry, geometryType: "esriGeometryEnvelope", inSR: 4326, // EPSG:4326 uses latitudes and longitudes spatialRel: "esriSpatialRelIntersects", where: whereClause, f: "geojson", returnGeometry: true, outFields: ["APN", "UseType", "TaxRateCity", " Roll_LandValue"] }) .then((response) => { map.getSource("parcels").setData(response); });
-
Update your
Query
to add aControl change
event handler to theselect
element. Inside, calculate the viewport extent and callexecute
with the extent andQuery where
clause.Use dark colors for code blocks class QueryControl { onAdd(map) { const template = document.createElement("template"); template.innerHTML = `<div class="maplibregl-ctrl maplibregl-ctrl-group" style="margin:20px;"> <select style="font-size:16px; padding:4px 8px;"> <option value="">Choose a WHERE clause...</option> <option>UseType = 'Residential'</option> <option>UseType = 'Government'</option> <option>UseType = 'Irrigated Farm'</option> <option>TaxRateArea = 10853</option> <option>TaxRateArea = 10860</option> <option>TaxRateArea = 08637</option> <option>Roll_LandValue > 1000000</option> <option>Roll_LandValue < 1000000</option> </select> </div>`; const select = template.content.querySelector("select"); select.addEventListener("change", () => { // Do nothing for the "Choose a WHERE clause..." option if (select.value !== "") { // Get bounds in [minx, miny, maxx, maxy] format const bounds = map.getBounds().toArray().flat(); executeQuery(select.value, bounds); } }); return template.content; } }
-
At the top right, click Run. When you select a where clause using the toolbox, a query will run against the feature layer and display all land parcels within the current viewport matching the where clause.
Add a pop-up
You can add a Popup
to view attributes of a parcel when the user clicks on it.
See the Display a pop-up tutorial for more details.
-
Add a
click
event handler to theparcels-fill
layer. Inside, construct the pop-up content from the attributes of the clicked parcel. Create a newPopup
, then usepopup.set
to set the content. Set the position of the pop-up and add it to the map.HTML Use dark colors for code blocks .then((response) => { map.getSource("parcels").setData(response); }); } map.on("click", "parcels-fill", (e) => { const parcel = e.features[0].properties; const landValue = parcel.Roll_LandValue != "null" ? `$${parcel.Roll_LandValue.toLocaleString()}` : `N/A`; const message = `<b>Parcel ${parcel.APN}</b></br>` + `Type: ${parcel.UseType} <br>` + `Land value: ${landValue} <br>` + `Tax Rate City: ${parcel.TaxRateCity}`; new maplibregl.Popup().setHTML(message).setLngLat(e.lngLat).addTo(map); });
Run the app
Run the app.
When the map displays, you should be able to use the query selector to display parcels. Click on a parcel to show a pop-up with the feature's attributes.
What's next?
Learn how to use additional ArcGIS location services in these tutorials:
Query a feature layer (spatial)
Execute a spatial query to access polygon features from a feature service.
Get global data
Query demographic information for locations around the world with the GeoEnrichment service.
Get local data
Query regional facts, spending trends, and psychographics with the GeoEnrichment service.
Add a feature layer
Add features from feature layers to a map.
Style a feature layer
Use data-driven styling to apply symbol colors and styles to feature layers.