Skip To Content

Search for features

In this topic

The ArcGIS Runtime SDK provides three primary ways to search for features that meet a specified set of spatial and/or attribute criteria: Find, Query, and Identify.

Find allows you to search for a text value within a specified set of layers and fields. For example, you can find all features that contain the text Santa in either the COUNTY_NAME or CITY_NAME attribute for all the California layers in your map. Find can be configured to match the search text exactly or to match where the text is present as part of the attribute value.

Query, although limited to searching a single layer or table at a time, allows you to build more advanced attribute search expressions using multiple fields and value types. You can also use spatial criteria (either instead of or in addition to attribute criteria) to further refine your search. For example, you can find all parcels within the current map extent that are valued over $250,000 and are vacant.

Identify uses spatial criteria only (the most common workflow being a point clicked on the map) to return information about features in one or several layers. For example, to show attributes from several demographic layers for a location the user has clicked on the map, even if those layers aren't currently being displayed.

Note:

For Identify and Query, you can also include temporal criteria for the search.

Tasks overview

Searching is implemented using tasks. Specifically, the FindTask, QueryTask, and IdentifyTask classes allow you to tailor your searches for the type of questions you need to answer. To implement one of these tasks in your code, follow these general steps:

  1. Create a new Task object and point it to an appropriate online or local resource (for example, a FeatureService).
  2. Specify Task parameters, which are stored as properties of an appropriate parameters object. (for example, FindParameters for use with a FindTask). Task parameters specify the search criteria as well as the output spatial reference and fields.
  3. Execute the task, passing in the parameters object you've defined.
  4. Process the results that are returned (summarize values, display graphics, and so on.).

Tip:

You can give the user the ability to cancel long-running tasks by providing a CancellationToken when creating the task.

In addition to the search tasks described here, the ArcGIS Runtime SDK provides a variety of other Task classes. There are tasks for geocoding addresses, getting driving directions, printing, and more. Although individual tasks provide a range of functionality for use in your app, they all share the same essential programming pattern described above.

Find features

FindTask provides the ability to return a subset of features based exclusively on attribute criteria. The task will search for a single (text) value in a specified set of attribute fields within a specified set of layers and return all features that have a matching attribute value. FindTask can be configured to match the search value exactly, or to match attribute values that simply contain the search string as part of the attribute value.

For example, the following image shows the result of executing a FindTask to return state, city, or river features that contain the text Red in either the NAME or CITY_NAME attribute:

Results of executing the FindTask

The following code illustrates how to return all features in a set of specified service layers that contain a search term in one of the specified attributes:

// Create a new FindTask, pointing to the map service to search in the constructor
var url = 
  "http://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer";
var findTask = new FindTask(new Uri(url));

// Create a FindParameters object
var findParameters = new FindParameters();

// Set properties to define the search
//--the layer ids to search
findParameters.LayerIDs.Add(0); // Cities
findParameters.LayerIDs.Add(3); // Counties
findParameters.LayerIDs.Add(2); // States
//--the fields to search
findParameters.SearchFields.Add("name");
findParameters.SearchFields.Add("areaname");
findParameters.SearchFields.Add("state_name");
//--return feature geometry with the results
findParameters.ReturnGeometry = true;
//--the spatial reference for result geometry (match the map's)
findParameters.SpatialReference = MyMapView.SpatialReference;
//--the text to search for and how to evaluate matches
findParameters.SearchText = SearchForTextBox.Text;
findParameters.Contains = true; // false == match exactly

// Asynchronously execute the task; await the result
FindResult findResult = await findTask.ExecuteAsync(findParameters);

var foundCities = 0;
var foundCounties = 0;
var foundStates = 0;

// Loop thru results; count the matches found in each layer
foreach (FindItem findItem in findResult.Results)
{
    switch (findItem.LayerID)
    {
        case 0: // Cities
            foundCities++;
            break;
        case 3: // Counties
            foundCounties++;
            break;
        case 2: // States
            foundStates++;
            break;
    }
}

// Report the number of matches for each layer
var msg = string.Format("Found {0} cities, {1} counties, and {2} states containing '" + SearchForTextBox.Text + "' in a Name attribute", 
                         foundCities, foundCounties, foundStates);

var messageDlg = new MessageDialog(msg);
await messageDlg.ShowAsync();

// Bind the results to a DataGrid control on the page
MyDataGrid.ItemsSource = findResult.Results;

Executing a FindTask returns a collection of result objects, each one indicating which layer, field, and attribute value was matched by executing the task. Perhaps most importantly, it gives you access to a graphic for the match, which includes all attributes and (if geometry was requested in the input parameters) the shape and location of the feature that was found.

Geometry for the find results can be returned in a specified spatial reference by specifying the output spatial reference in the parameters used as input for the task. If a spatial reference is not specified, results will be returned in the spatial reference of the map service being searched. Most often, you will need the result features in the same spatial reference as your app's map.

Tip:

If you will not need to use the geometry of your results (for example, for displaying the located features), you should indicate that geometry should not be returned by the task (in the task parameters). Excluding the geometry portion of your results can greatly improve performance, especially if several features (or complex geometries) are potentially being returned.

Query features

A QueryTask provides the ability to return a subset of features from a single layer based on any combination of attribute, spatial, and temporal criteria. Rather than searching for a single text value as FindTask does, a QueryTask can take a compound expression that uses several attribute fields, data types, and operators (for example, POP > 1000000 AND NAME LIKE 'San%'). Unlike FindTask, QueryTask does not let you specify a set of layers to search in a single operation but gives you much more control over the criteria used to perform a search.

Note:
A query does not require that all types of criteria be defined (attribute, spatial, and temporal). Leaving spatial and temporal filters undefined, for example, means that the search will not be restricted using those criteria (that is, the entire spatial and temporal extent will be evaluated).

The following code example queries the Police layer (index = 1) in the RedlandsEmergencyVehicles service using attribute, spatial, and temporal criteria. A TimeExtent is passed into the constructor for the QueryTask that identifies a period from five minutes ago to the present time. The attribute criteria is applied with an expression to find all unmarked vehicles (TYPE = 2) that are currently available (STATUS = 1) or any vehicle that is traveling at or below 10 miles per hour (SPEED <= 10). The map's extent is used as the spatial criteria to ensure that only features in the current extent are considered in the query.

Code to query a feature service layer and display the results as graphics in the map.

// Create a new QueryTask, indicate the map service to query in the constructor
var url = 
     "http://sampleserver5.arcgisonline.com/arcgis/rest/services/RedlandsEmergencyVehicles/FeatureServer/1";
var queryTask = new QueryTask(new Uri(url));

// Create a new Query object to define parameters; pass in a time extent
var timeWindow = new Esri.ArcGISRuntime.Data.TimeExtent
        (DateTime.Now.Subtract(new TimeSpan(0, 5, 0, 0)), DateTime.Now); // 5 minutes ago to present
var queryParams = new Esri.ArcGISRuntime.Tasks.Query.Query(timeWindow);

// Set properties of the Query
//--attribute criteria (where expression)
queryParams.Where = "(TYPE = 2 AND STATUS = 1) OR (SPEED <= 10)";
//--spatial criterion; default relationship is "intersects"
queryParams.Geometry = MyMapView.Extent;
//--return feature geometry with the results
queryParams.ReturnGeometry = true;
//--output spatial reference
queryParams.OutSpatialReference = MyMapView.SpatialReference;

// Execute the task and await the result
QueryResult queryResult = await queryTask.ExecuteAsync(queryParams);

// Get the list of features (graphics) from the result
var resultFeatures = queryResult.FeatureSet.Features;

// Get the graphics layer in the map
var graphicsLayer = MyMap.Layers["Graphics"] as GraphicsLayer;

// Display result graphics
graphicsLayer.GraphicsSource = resultFeatures;

Query result displayed as point graphics

Query criteria can be specified using any combination of attribute, spatial, and temporal criteria. Attribute criteria is defined using a SQL expression in the form "attribute operator value" (for example, "POPULATION < 2000000"). Several conditions can also be connected using AND or OR to produce compound attribute expressions: "(POPULATION < 2000000 AND (REGION = 'Pacific' OR REGION = 'Midwest'))".

Spatial criteria for a query requires two pieces of information: a Geometry object whose relationship to features in the query layer will be evaluated, and the type of relationship to evaluate. The available spatial relationships are as follows:

  • Intersects—Part of an input feature is contained in the query geometry
  • Contains—Part or all of an input feature is contained within the query geometry
  • Crosses—An input feature crosses the query geometry
  • EnvelopeIntersects—The envelope of an input feature intersects the envelope of the query geometry
  • Overlaps—An input feature overlaps the query geometry
  • Touches—An input feature touches the border of the query geometry
  • Within—An input feature is completely enclosed by the query geometry

Temporal criteria are defined using a TimeExtent, which can define a single point in time or a time range.

Tip:

To return all features with a query, you can use an expression that is always true, such as "1=1", and leave the spatial and temporal criteria undefined.

Similar to the FindTask, a QueryTask is generally used to return what is essentially a collection of result objects. The individual results (graphics) can be accessed from the result in order to display them in the map or to work with their attribute values. Depending on the needs for your app, you can simply return a list of ObjectIDs (rather than the actual features) that meet the query criteria. Similarly, you can return just the count of resulting features. Returning the count or a list of IDs will usually perform much better than returning a set of features.

You can also use a QueryTask to find features in a related dataset for a particular layer. You can pass in the same types of input for the task parameters when searching related records (such as query criteria, output spatial reference, and whether to include geometry), but you also need to indicate the relationship ID for the related dataset. This ID can be found by consulting the metadata for the layer in the service (REST page).

Geometry for the query results can be returned in a specified spatial reference by specifying the output spatial reference in the parameters used as input for the task. If a spatial reference is not specified, results will be returned in the spatial reference of the map service being searched. Most often, you will need the result features in the same spatial reference as your app's map.

Tip:

If you will not need to use the geometry of your results (for example, for displaying the located features), you should indicate that geometry should not be returned by the task (in the task parameters). Excluding the geometry portion of your results can greatly improve performance, especially if several features (or complex geometries) are potentially being returned.

Query offline content

The ArcGIS Runtime SDK allows you to work with data that is stored locally, such as features stored in a local geodatabase. Local data sources, such as a geodatabase feature table, cannot be queried using the QueryTask as previously described (which requires a URI for an online data source). Instead, you can query the underlying table directly using attribute and/or spatial criteria to return a collection of result features. The process is similar, however, in that you need to provide a variety of parameters (including query criteria) and as a result will get a collection of features that meet your criteria.

The local data you want to query may or may not be displayed in the map. If the dataset is the source for one of the map's layers, you can access the local feature table through a property on the layer. Otherwise, you will need to open the local geodatabase table before executing the query.

The following code example illustrates how to query a layer that is based on a geodatabase feature table (stored locally on the client). The query finds features in the current map extent that also have an EventType attribute value of 1.

// Get a layer from the map that is referencing a local data source
var layer = MyMap.Layers["OfflinePoints"] as FeatureLayer;
if (layer != null)
{
    // Get the geodatabase feature table that the layer is displaying
    var table = layer.FeatureTable as Esri.ArcGISRuntime.Data.GeodatabaseFeatureTable;

    // Create a new SpatialQueryFilter object to define the query criteria
    var query = new Esri.ArcGISRuntime.Data.SpatialQueryFilter();

    // Use the current map extent as the query geometry (project it to match the layer's dataset)
    var searchExt = GeometryEngine.Project(MyMapView.Extent, table.Extent.SpatialReference);
    query.Geometry = searchExt;
    query.SpatialRelationship = Esri.ArcGISRuntime.Geometry.SpatialRelationship.Intersects;

    // Set the where clause to find features that also have an EventType value of 1
    query.WhereClause = "EventType=1";

    if (table.SupportsQuery)
    {
        // Query the table and await the result
        var result = await table.QueryAsync(query); 

        // Get a graphics layer in the map to display results
        var graphicsLayer = MyMap.Layers["ResultGraphics"] as GraphicsLayer;

        // Clear any existing graphics in the result layer
        graphicsLayer.Graphics.Clear();

        // Loop through query results
        foreach (Esri.ArcGISRuntime.Data.Feature f in result)
        {
            // Use the geometry and attributes of each result to create a graphic
            var g = new Graphic(f.Geometry, f.Attributes);

            // Add each graphic to the graphics layer
            graphicsLayer.Graphics.Add(g);
        }
    }
}

Before attempting a query, you can check a geodatabase feature table to see if it supports queries. If a query on a local data table is successful, it will return a collection of results that can be used to access feature information such as attributes and geometry in order to display your results. In addition to querying a local table with attribute and/or spatial criteria, you can also pass in a list of object IDs to return the corresponding features.

The following code example illustrates how to open a local geodatabase, access a particular feature table, and execute an attribute query to get some results:

// Specify a path to a local geodatabase (downloaded from a feature service, e.g.)
var path = System.IO.Path.Combine(@"C:\Temp\Cache", "WildfiresLocal");

// Open the geodatabase
var gdb = await Esri.ArcGISRuntime.Data.Geodatabase.OpenAsync(path);

// Loop through all tables
foreach (var t in gdb.FeatureTables.ToList())
{
    // Find a table by name
    if (t.Name.ToLower().Contains("points") )
    {
        // Define an attribute query
        var filter = new Esri.ArcGISRuntime.Data.QueryFilter();
        filter.WhereClause = "EventType=1";

        // Execute the query and await results
        var r = await t.QueryAsync(filter);

        // Process results here ...

    }
}

Identify features

An identify operation is a user-driven operation typically involving a single click on the map and the retrieval of information about features hit by that clicked point from any layer in the map. In the ArcGIS Runtime SDK for .NET, you can use the IdentifyTask to search the layers in both local and online data for features that intersect an input geometry. Once the matching features are returned, you can display their geometries and attributes in your app.

Define parameters to control how you want the identify to operate. The following are some basic guidelines:

  • Information about the map is required for an IdentifyTask to correctly evaluate the relationship between features in the service and the input geometry. These include the map dimensions (width and height, in pixels), the current map extent, and the screen resolution (dots per inch—the default value is 96).
  • Identify operates by intersecting a geometry with the features in one or more layers. Provide the intersection geometry using a Point, Polyline, Polygon, Envelope, or Multipoint.
    Tip:

    To control the spatial relationship evaluated in the identify (a relationship other than intersection), use a QueryTask instead.

  • Specify a tolerance to control the distance (in pixels) from your geometry within which the intersection will be performed.
  • Geometry for the IdentifyTask results can be returned in a specified spatial reference by setting the output spatial reference. This value also indicates which spatial reference is being used for the input geometry and input map extent values. If it is not specified, the spatial reference of the map service being searched will be assumed. In general, you should set this property using the spatial reference of your map.
  • An Identify operation has the potential to retrieve a lot of information, especially if there are several layers involved. To improve performance and to make your Identify operation more efficient, you can specify the layers to include in your search. You can specify the layers, by ID, that take part in the Identify task. Alternatively, you can identify only the visible layers, only the top most (visible) layer, or all layers (regardless of visibility).

The results of the Identify task are returned as a collection of result objects. Each item in the collection represents the identify result for a layer included in the operation. You can process the contents of the layer results to get to the individual features (geometry and attributes) returned by the operation.

The following code example uses an IdentifyTask in response to a click on the map to display information about a feature clicked by the user:

// When the mouse button is released on the Map, use Identify to get info
private async void MyMapView_MouseUp(object sender, MouseButtonEventArgs e)
{
    var screenPoint = e.GetPosition(MyMapView);  

    // Convert the screen point to a point in map coordinates
    var mapPoint = MyMapView.ScreenToLocation(screenPoint); 

    // Create a new IdentifyTask pointing to the map service to identify (USA)
    var uri = new Uri("http://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer");
    var identifyTask = new IdentifyTask(uri);

    // Create variables to store identify parameter information   
    var extent = MyMapView.Extent;            //--current map extent (Envelope)   
    var tolerance = 7;                        //--tolerance, in pixels, for finding features   
    var height = (int)MyMapView.ActualHeight; //--current height, in pixels, of the map control   
    var width = (int)MyMapView.ActualWidth;   //--current width, in pixels, of the map control

    // Create a new IdentifyParameter; pass the variables above to the constructor
    var identifyParams = new IdentifyParameter(mapPoint, extent, tolerance, height, width);

    // Identify only the top most visible layer in the service
    identifyParams.LayerOption = LayerOption.Top;

    // Set the spatial reference to match with the map's
    identifyParams.SpatialReference = MyMapView.SpatialReference;

    // Execute the task and await the result
    IdentifyResult idResult = await identifyTask.ExecuteAsync(identifyParams);

    // See if a result was returned 
    if (idResult != null && idResult.Results.Count > 0)
    {
        // Get the feature for the first result
        var topLayerFeature = idResult.Results[0].Feature as Graphic;

        Symbol symbol = null;
        Color color = Colors.Red;

        // Apply a symbol (check geometry type)
        if (topLayerFeature.Geometry.GeometryType == GeometryType.Point) {
            symbol = new SimpleMarkerSymbol { Style = SimpleMarkerStyle.Circle, Color = color, Size = 10 };
        }
        else if (topLayerFeature.Geometry.GeometryType == GeometryType.Polygon)
        {
            symbol = new SimpleFillSymbol { Color = color};
        }
        else if (topLayerFeature.Geometry.GeometryType == GeometryType.Polyline)
        {
            symbol = new SimpleLineSymbol { Color = color, Width = 3 };
        }


        topLayerFeature.Symbol = symbol;

        // Display the feature as a graphic on the map
        var resultLayer = MyMap.Layers["IdResultGraphics"] as GraphicsLayer;

        resultLayer.Graphics.Clear();
        resultLayer.Graphics.Add(topLayerFeature);

        // Display the attributes in a data grid on the page
        this.ResultsDataGrid.ItemsSource = topLayerFeature.Attributes;
    }
}

Handle exceptions when executing a task

If an error is encountered while executing a task, System.Threading.Tasks.Task.Exception will be set. You can trap for such exceptions by wrapping the await call in a try catch block such as the one shown in the following code example. If the exception is raised from the underlying REST call, you can expect an Esri.ArcGISRuntime.Http.ArcGISWebException to be thrown.

QueryResult queryResult = null;
Task<QueryResult> task = queryTask.ExecuteAsync(queryParams);
try
{
    queryResult = await task;
}
catch (Esri.ArcGISRuntime.Http.ArcGISWebException exp)
{
    // "Invalid URL", "Invalid parameter", e.g.
    // ... handle exception here ...
}
catch (Exception exp)
{
    // ... handle exception here ...
}

if (queryResult != null)
{
    // Process results ...
}

Allow the user to cancel a task

A task can be cancelled during the course of its asynchronous execution by using a System.Threading.CancellationToken, which can optionally be passed into most ExecuteAsync methods for a task. A task can be cancelled manually (for example, if the user clicks a Cancel button) or by specifying a maximum time interval that the task is allowed to run. The following code example incorporates both techniques by setting a time limit (10 seconds) after which the task will be cancelled and also calls the Cancel method if the user clicks the Cancel button.

private System.Threading.CancellationTokenSource canceller;
private async void DoQuery()
{
    var url = 
       "http://sampleserver5.arcgisonline.com/arcgis/rest/services/RedlandsEmergencyVehicles/FeatureServer/1";
    var queryTask = new QueryTask(new Uri(url));

    // Create a new Query object to define parameters (get all features)
    var queryParams = new Esri.ArcGISRuntime.Tasks.Query.Query("1=1");

    // Set up the cancellation token
    this.canceller = new System.Threading.CancellationTokenSource();
    this.canceller.CancelAfter(10000); // 10 seconds
    var token = this.canceller.Token;

    // Execute the task with the cancellation token; await the result
    QueryResult queryResult = null;
    Task<QueryResult> task = queryTask.ExecuteAsync(queryParams, token);
    try
    {
        queryResult = await task;
    }
    catch (System.Threading.Tasks.TaskCanceledException exp)
    {
        // ... handle cancel here ...
    }

    if (queryResult == null) { return; }

    // Get the list of features (graphics) from the result
    var resultFeatures = queryResult.FeatureSet.Features;

    // Get the graphics layer in the map; set the source with the result features
    var graphicsLayer = MyMap.Layers["Graphics"] as GraphicsLayer;
    graphicsLayer.GraphicsSource = resultFeatures;
}

private void CancelButton_Click(object sender, RoutedEventArgs e)
{
    // If the Cancel button is clicked, cancel the task using the CancellationTokenSource object
    this.canceller.Cancel();
}

Cancelling the task will cause a System.Threading.Tasks.TaskCanceledException to be thrown. When allowing the user to cancel a task, you'll need to anticipate and handle these exceptions gracefully in your code.

Related topics