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.
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 resulting features are displayed as graphics on the map. A pop-up is also used to display feature attributes.
Prerequisites
You need 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 ArcGIS services.
Add script references
This tutorial uses ArcGIS REST JS to query the feature layer.
-
In the
<head
element, add references to the ArcGIS REST JS library.> Use dark colors for code blocks <!-- Load MapLibre GL JS from CDN --> <script src=https://unpkg.com/maplibre-gl@5.9.0/dist/maplibre-gl.js></script> <link href=https://unpkg.com/maplibre-gl@5.9.0/dist/maplibre-gl.css rel="stylesheet" /> <!-- Load MapLibre ArcGIS from CDN --> <script src="https://unpkg.com/@esri/maplibre-arcgis@1.0.0/dist/umd/maplibre-arcgis.min.js"></script> <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 zoom: 12, // starting zoom center: [-118.80543, 34.03] // starting location [longitude, latitude] }); const basemapStyle = maplibreArcGIS.BasemapStyle.applyStyle(map, { style: 'arcgis/outdoor', token: accessToken }); 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 Acknowledgments section and copy its value.
- Paste the copied value to the
attribution
property.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.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 query
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 geometry
property. 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 that calls
arcgis
. Specify GeoJSON as the return type, requestingRest.query Features return
and specificGeometry out
. All of the features within the geometry will be returned with attribute information set by theFields out
property.Fields 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.
-
Add a
click
event handler to theparcels-fill
layer. Inside, create a popup that display the attributes of the clicked parcel. Set the position of the pop-up and add it to the map.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 attributes.What's next?
Learn how to use additional 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.

Get local data
Query regional facts and spending trends for a study area in the United States.

Add a feature layer as GeoJSON
Display and style GeoJSON features from a feature service.

Style a feature layer
Use data-driven styling to apply symbol colors and styles to feature layers.