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
The following are required for this tutorial:
- An ArcGIS account to access API keys. If you don't have an account, sign up for free.
- 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 Change 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 Change 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 project, set your API key.
An API Key enables access to services, web maps, and web scenes hosted in ArcGIS Online.
-
Go to your developer dashboard to get your API key. For these tutorials, use your default API key. It is scoped to include all of the services demonstrated in the tutorials.
-
In Android Studio: in the Android tool window, open app > java > com.example.app > MainActivity.
-
In the
set
method, find theA p i K e y F o r App() ArcGISRuntime
call and paste your API key inside the quotes, replacing YOUR_API_KEY.Environment.set A p i Key("YOUR_ API_ KEY") MainActivity.ktUse dark colors for code blocks override fun onDestroy() { mapView.dispose() super.onDestroy() } private fun setApiKeyForApp(){ // set your API key // Note: it is not best practice to store API keys in source code. The API key is referenced // here for the convenience of this tutorial. ArcGISRuntimeEnvironment.setApiKey("YOUR_API_KEY") }
-
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.Color
. 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.I d 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 O
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
o
property with an object expression that implements the interfacen Item Selected Listener Adapter
. ImplementView.On Item Selected Listener o
with an empty function body. Implementn Nothing() o
so it gets the current extent and the current selection in the spinner and then callsn Item 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: