Learn how to execute a SQL query to return features from a feature layer based on spatial and attribute criteria.
A feature layer can contain a large number of features stored in ArcGIS. You can query a layer to access a subset of its features using any combination of spatial and attribute criteria. You can control whether or not each feature's geometry is returned, as well as which attributes are included in the results. Queries allow you to return a well-defined subset of your hosted data for analysis or display in your ArcGIS Runtime app.
In this tutorial, you'll write code to perform SQL queries that return a subset of features in the LA County Parcel feature layer (containing over 2.4 million features). Features that meet the query criteria are selected in the map.
Prerequisites
Before starting this tutorial:
-
You need an ArcGIS Location Platform or ArcGIS Online account.
-
Confirm that your system meets the system requirements.
-
An IDE for Android development in Kotlin.
Steps
Open an Android Studio project
-
To start this tutorial, complete the Display a map tutorial. Or download and unzip the Display a map solution in a new folder.
-
Modify the old project for use in this new tutorial. Expand More info for instructions.
-
On your file system, delete the .idea folder, if present, at the top level of your project.
-
In the Android tool window, open app > res > values > strings.xml.
In the
<string name="app
element, change the text content to Query a feature layer (SQL)._name" > strings.xmlUse dark colors for code blocks 1 2 4 5Change line <resources> <string name="app_name">Query a feature layer (SQL)</string> </resources>
-
In the Android tool window, open Gradle Scripts > settings.gradle.
Change the value of
root
to "Query a feature layer (SQL)".Project.name settings.gradleUse dark colors for code blocks 23 24Change line rootProject.name = "Query a feature layer (SQL)" include ':app'
-
Click File > Sync Project with Gradle files. Android Studio will recognize your changes and create a new .idea folder.
-
-
If you downloaded the solution, get an access token and set the API key.
-
Go to the Create an API key tutorial to obtain a new API key access token using your ArcGIS Location Platform or ArcGIS Online account. Ensure that the following privilege is enabled: Location services > Basemaps > Basemap styles service. Copy the access token as it will be used in the next step.
-
In Android Studio: in the Android tool window, open app > java > com.example.app > MainActivity.
-
In the
set
method, find theApi Key For App() ArcGIS
call and paste your access token inside the double quotes, replacing YOUR_ACCESS_TOKEN.Runtime Environment.set Api Key(" YOUR _ACCESS _TOKE N") MainActivity.ktUse dark colors for code blocks private fun setApiKeyForApp(){ ArcGISRuntimeEnvironment.setApiKey("YOUR_ACCESS_TOKEN") }
-
Add import statements
Replace app-specific imports statements with the imports needed for this tutorial.
package com.example.app
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.graphics.Color
import android.util.Log
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Spinner
import android.widget.Toast
import com.esri.arcgisruntime.ArcGISRuntimeEnvironment
import com.esri.arcgisruntime.concurrent.ListenableFuture
import com.esri.arcgisruntime.data.FeatureQueryResult
import com.esri.arcgisruntime.data.QueryParameters
import com.esri.arcgisruntime.data.ServiceFeatureTable
import com.esri.arcgisruntime.geometry.Envelope
import com.esri.arcgisruntime.layers.FeatureLayer
import com.esri.arcgisruntime.mapping.ArcGISMap
import com.esri.arcgisruntime.mapping.BasemapStyle
import com.esri.arcgisruntime.mapping.Viewpoint
import com.esri.arcgisruntime.mapping.view.MapView
import com.example.app.databinding.ActivityMainBinding
Among the imports you are pasting into your app is one from the Android API: import android.graphics.
. You will need this class when setting colors for the symbols.
Steps
Add UI for selecting a predefined query expression
To make performing a query on a feature layer more flexible, add a Spinner
control that presents a list of predefined attribute queries for the dataset from the LA County Parcels feature layer.
-
In Android Studio, in the Android tool window, open app > res > values > strings.xml.
-
Add XML that specifies text to use as a label for the spinner. Then provide the strings that the spinner will present to the user. These are the SQL expressions for querying the feature layer.
string.xmlUse dark colors for code blocks <string name="spinner_label_text">Where Expression:</string> <string-array name="parcel_categories"> <item>Select an expression</item> <item>UseType = \'Government\'</item> <item>UseType = \'Residential\'</item> <item>UseType = \'Irrigated Farm\'</item> <item>TaxRateArea = 10853</item> <item>TaxRateArea = 10860</item> <item>Roll_LandValue > 1000000</item> <item>Roll_LandValue < 1000000</item> </string-array>
-
Open app > res > layout > activity_main.xml
-
Add a new
Constraint
inside the existingLayout Constraint
. In the new constraint layout, define aLayout Text
for the spinner label, and then define theView Spinner
.
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/spinnerLabel"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:text="@string/spinner_label_text"
android:textAppearance="@style/TextAppearance.AppCompat.Widget.ActionBar.Menu"
app:layout_constraintBaseline_toBaselineOf="@id/spinner"
app:layout_constraintEnd_toStartOf="@id/spinner"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Spinner
android:id="@+id/spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="12dp"
app:layout_constraintStart_toEndOf="@id/spinnerLabel"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Add code to execute the selected query for the current map extent
In this step, create a new function that queries a
FeatureLayer
(identified using its ID) using both attribute and spatial criteria. After clearing any currently selected features, a new query will be executed to find features in the map's current extent that meet the selected attribute expression. The features in the
FeatureQueryResult
will be selected in the parcels layer.
The function takes three arguments: the ID for the layer to query (string), a SQL expression that defines attribute criteria (string), and the area of the
MapView
currently being viewed (
Envelope
).
-
Create a
query
function takes three parameters. Access the map's operational layers, and useFeature Layer() layer
to retrieve the feature layer you wish to query. Then get the layer's feature table.Id MainActivity.ktUse dark colors for code blocks // Query the feature layer, providing a Where expression and the current extent // to the featureLayer, zoom to it, and select it. private fun queryFeatureLayer(layerId: String, whereExpression: String, queryExtent: Envelope) { // Get the layer based on its Id. lateinit var featureLayerToQuery: FeatureLayer mapView.map.operationalLayers.iterator().forEach { layer -> if (layer.id == layerId) { featureLayerToQuery = layer as FeatureLayer } } // get the feature table from the feature layer val featureTableToQuery = featureLayerToQuery.featureTable }
-
Clear any previous selections from the feature layer. Then create query parameters, using the
where
andExpression query
parameters passed into the function.Extent MainActivity.ktUse dark colors for code blocks // clear any previous selections featureLayerToQuery.clearSelection() // create a query for the state that was entered val query = QueryParameters().apply { // make search case insensitive whereClause = whereExpression isReturnGeometry = true geometry = queryExtent }
-
Call
query
, passing in the query parameters. The call returns a listenable future that will contain the feature query result. Add a done listener, which will execute when the query is complete.Features Async() MainActivity.ktUse dark colors for code blocks // call select feature val future: ListenableFuture<FeatureQueryResult> = featureTableToQuery.queryFeaturesAsync(query) // add done listener to fire when the selection returns future.addDoneListener { try { // check if there are some results val resultIterator = future.get().iterator() if (resultIterator.hasNext()) { resultIterator.forEach { feature -> // select the feature featureLayerToQuery.selectFeature(feature) } } else { "No parcels found in the current extent, using Where expression: $whereExpression".also { Toast.makeText(this, it, Toast.LENGTH_LONG).show() Log.d(TAG, it) } } } catch (e: Exception) { "Feature search failed for: $whereExpression. Error: ${e.message}".also { Toast.makeText(this, it, Toast.LENGTH_LONG).show() Log.e(TAG, it) } } }
Create the Parcels feature layer and add a selection listener to the spinner
Create the LA County Parcels
FeatureLayer
from the feature service. Providing a Layer.id allows for referencing the Parcels layer from the layer collection when needed.
Next, create the spinner that was defined in activity
, and add an On
to the spinner.
Then add the parcels feature layer to map's collection of data layers (
GeoModel.getOperationalLayers()
) and finally, define the selection color via the map view's
SelectionProperties
to distinguish selected features in the map.
-
Create a new function named
create
and pass in the map. Create a service feature table from a feature service URL. Then add anFeature Layer() id
property for use when querying the layer.MainActivity.ktUse dark colors for code blocks // Create the parcels feature layer. When it is loaded, add a listener on the spinner. // Add the layer to the map, and set the map view's selection properties to yellow. private fun createFeatureLayer(map: ArcGISMap) { val serviceFeatureTable = ServiceFeatureTable("https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/LA_County_Parcels/FeatureServer/0") val parcelsFeatureLayer = FeatureLayer(serviceFeatureTable) // give the layer an ID so we can easily find it later, then add it to the map parcelsFeatureLayer.id = "Parcels" }
-
Load the feature layer and add a done loading listener. In the listener, declare local variable
spinner
and bind it to the spinner you defined in activity_main.xml. Then create the spinner usingArray
.Adapter.create From Resource() MainActivity.ktUse dark colors for code blocks parcelsFeatureLayer.loadAsync() parcelsFeatureLayer.addDoneLoadingListener { val spinner = activityMainBinding.spinner // set up the spinner with the parcel_categories string-array in strings.xml ArrayAdapter.createFromResource( this, R.array.parcel_categories, android.R.layout.simple_spinner_item ).also { adapter -> adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) spinner.adapter = adapter } }
-
Inside the layer's done loading listener, initialize the spinner's
on
property with an object expression that implements the interfaceItem Selected Listener Adapter
. ImplementView. On Item Selected Listener on
with an empty function body. ImplementNothing() on
so it gets the current extent and the current selection in the spinner and then callsItem Selected() query
with layer Id "Parcels", the spinner choice, and the extent.Feature Layer() MainActivity.ktUse dark colors for code blocks parcelsFeatureLayer.loadAsync() parcelsFeatureLayer.addDoneLoadingListener { val spinner = activityMainBinding.spinner // set up the spinner with the parcel_categories string-array in strings.xml ArrayAdapter.createFromResource( this, R.array.parcel_categories, android.R.layout.simple_spinner_item ).also { adapter -> adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) spinner.adapter = adapter } spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onNothingSelected(parent: AdapterView<*>?) { } override fun onItemSelected( parent: AdapterView<*>, view: View?, position: Int, id: Long ) { if (position == 0) { parcelsFeatureLayer.clearSelection() return } val currentSpinnerChoice: String = parent.getItemAtPosition(position) as String val currentExtent: Envelope = mapView.getCurrentViewpoint(Viewpoint.Type.BOUNDING_GEOMETRY).targetGeometry as Envelope queryFeatureLayer("Parcels", currentSpinnerChoice, currentExtent) } } }
-
Add the feature layer to the map's operational layers.
MainActivity.ktUse dark colors for code blocks map.operationalLayers.add(parcelsFeatureLayer)
-
Set the color that will highlight those features returned by the query. In this app, we set
selection
to yellow.Properties MainActivity.ktUse dark colors for code blocks // get selection properties (bound to the MapView) mapView.selectionProperties.color = Color.YELLOW
Call createFeatureLayer() and add a companion object
-
In the
setup
function, callMap() create
.Feature Layer() MainActivity.ktUse dark colors for code blocks // Set up your map here. You will call this method from onCreate(). private fun setupMap() { // create a map with a topographic basemap and set the map on the mapview val map = ArcGISMap(BasemapStyle.ARCGIS_TOPOGRAPHIC) mapView.map = map // set the viewpoint, Viewpoint(latitude, longitude, scale) mapView.setViewpoint(Viewpoint(34.0270, -118.8050, 72000.0)) createFeatureLayer(map) }
-
At the bottom of the
Main
class, add a companion object that has aActivity TAG
property. This is used by the variousToast
messages in this app.MainActivity.ktUse dark colors for code blocks companion object { private val TAG: String = MainActivity::class.java.simpleName }
-
Click Run > Run > app to run the app.
The app loads with the map centered on the Santa Monica Mountains in California with the parcels feature layer displayed. Choose an attribute expression, and parcels in the current extent that meet the selected criteria will display in the specified selection color.
What's next?
Learn how to use additional API features, ArcGIS location services, and ArcGIS tools in these tutorials: