ArcGIS Runtime SDK for Android

Identify features

Maps and scenes often combine multiple sources of information such as feature layers, image layers, and graphics. Labels and legends don't always provide enough information to work out what the map or scene is displaying. Use the identify methods to quickly answer the question 'what is this item here?', allowing your users to easily explore and learn about the map or scene content by tapping or clicking on them. Information returned can be shown in pop-ups or other UI components in your app.

You can identify the visible items at a specific point on screen:

  • Within all the layers in the map or scene, or only within a specific layer
  • Within all the graphics overlays in the view or only within a specific graphics overlay
  • Returning only the topmost item or all the items at that location
  • Returning the feature, graphic, or other item at that location, or by returning pop-ups for pop-up-enabled layers

Identify methods are asynchronous, so that the UI thread of your application is not blocked waiting for results. This is especially important when working with data which resides on a server, as results do not return immediately. The results of an identify take into account:

  • Symbology—tapping on a large marker symbol used to draw a point, or a wide line symbol used to draw a polyline or outline of a polygon will include those items in the results; it's not just the geometry that is used.
  • Visibility—the visibility of a graphics overlay or layer, and of an individual feature or graphic is checked, and results will only include the visible items. The opacity of a layer or graphic is ignored.
  • Visible extent—only items within the currently visible map or scene extent are included in identify results.

Identify is supported on feature layers, map image layers, Web Map Service (WMS) layers, graphics overlays, scene layers, and also on tiled layers that are based on map services that support identify.

Note:

If time-based filtering is being used (a time extent has been set on the displaying map or scene view), identify will only return features that are within the time extent set on the geo view.

The sections below show you how to identify different items in the map or scene in different ways, but these approaches can be combined to provide general identify functionality if required.

Identify features in a feature layer

The steps below show you how to identify the features within a specific feature layer in the map or scene. Later in this topic, these steps are adapted to identify only the topmost feature, identify against multiple layers, different types of layers, and to identify graphics.

  1. Listen to a tap or click event on the map or scene view, and get the point representing the center of the tap or click.

    Create a class that inherits from DefaultMapViewOnTouchListener and provide a default constructor to set the layer to be identified. Override onSingleTapConfirmed and in it, create an android.graphics.Point from the x,y coordinates of the MotionEvent parameter. Set an instance of this class onto the MapView or SceneView.

    private class IdentifyFeatureLayerTouchListener extends DefaultMapViewOnTouchListener {
    
      private FeatureLayer layer = null; // reference to the layer to identify features in
    
      // provide a default constructor
      public IdentifyFeatureLayerTouchListener(Context context, MapView mapView, FeatureLayer layerToIdentify) {
        super(context, mapView);
        layer = layerToIdentify;
      }
    
      // override the onSingleTapConfirmed gesture to handle a single tap on the MapView
      @Override
      public boolean onSingleTapConfirmed(MotionEvent e) {
        // get the screen point where user tapped
        android.graphics.Point screenPoint = new android.graphics.Point((int) e.getX(), (int) e.getY());
        // ...
    
        return true;
      }
    }
    mapView.setOnTouchListener(new IdentifyFeatureLayerTouchListener(this, mapView, featureLayerToIdentify));

  2. Call the required identify method, passing in the screen point from the previous step.
    1. Choose either to identify against a specific layer, or to identify against all layers.
    2. Specify a tolerance for the search radius of the identify operation. A tolerance of 0 identifies only items at the single pixel at the screen point. However, typically this level of precision is hard to achieve, so you can supply a tolerance around the screen point. A suitable tolerance for a tap operation on a touch screen should be equivalent to the size of the tip of a finger, and a smaller tolerance should be considered for mouse pointers. The units of the tolerance are in Device-independent pixels (DPs) - .
    3. Specify whether to include pop-up information on the geo-elements returned. (If no pop-ups are defined for a layer or graphics overlay, pop-ups will not be included in the results regardless of this parameter value).
    4. Optionally, specify the maximum number of results per-layer to return. This may be especially useful if identifying on service layers, as you can limit the amount of information returned to the client (the maximum results will also be limited by the server).

    The identifyLayersAsync method returns a ListenableFuture—you will use this to return the identify results. Add a done listener to this future, and in the done listener Runnable, call get—this will return the actual identify results as an IdentifyLayerResult.

    // call identifyLayerAsync, specifying the layer to identify, the screen location, tolerance, types to return, and
    // maximum results
    final ListenableFuture<IdentifyLayerResult> identifyFuture =
        mMapView.identifyLayerAsync(layer, screenPoint, 20, false, 25);
    
    // add a listener to the future
    identifyFuture.addDoneListener(new Runnable() {
    
      @Override
      public void run() {
        try {
          // get the identify results from the future - returns when the operation is complete
          IdentifyLayerResult identifyLayerResult = identifyFuture.get();
          // ...
    
        } catch (InterruptedException | ExecutionException ex) {
          // must deal with checked exceptions thrown from the async identify operation
          dealWithException(ex);
        }
      }
    });

  3. Results consist of layer content information about the layer the results are from, and a list of GeoElement. Each GeoElement represents a feature identified at the given screen point. Iterate the results, access the geometry and attributes of the identified features, and use them as required. In the example below, each identified feature is selected.

    To select the identified features, cast the LayerContent from IdentifyLayerResult.getLayerContent() to FeatureLayer. Iterate each GeoElement from IdentifyLayerResult.getIdentifiedElements() and cast to feature. Call FeatureLayer.selectFeature(), passing in each identified feature. You could additionally add code to clear any existing selection before each identify operation.

    // add a listener to the future
    identifyFuture.addDoneListener(new Runnable() {
    
      @Override
      public void run() {
        try {
    
          // get the identify results from the future - returns when the operation is complete
          IdentifyLayerResult identifyLayerResult = identifyFuture.get();
    
          // a reference to the feature layer can be used, for example, to select identified features
          FeatureLayer featureLayer = null;
          if (identifyLayerResult.getLayerContent() instanceof FeatureLayer) {
            featureLayer = (FeatureLayer) identifyLayerResult.getLayerContent();
          }
    
          // iterate each identified geoelement from the specified layer and cast to Feature
          for (GeoElement identifiedElement : identifyLayerResult.getElements()) {
            if (identifiedElement instanceof Feature) {
              // access attributes or geometry of the feature, or select it as shown below
              Feature identifiedFeature = (Feature) identifiedElement;
              if (featureLayer != null) {
                featureLayer.selectFeature(identifiedFeature);
              }
            }
          }
    
        } catch (InterruptedException | ExecutionException ex) {
          dealWithException(ex); // deal with exceptions thrown from the async identify operation
        }
      }
    });

    Tip:

    Features are loadable, but when using identify methods, they are always returned already loaded.

Identify features in all feature layers

When you do not know which specific layer to identify on, you can identify items in any layer in the map or scene. Change the first workflow above so that you do not specify the layer to identify. This time, the results are returned as a list of IdentifyLayerResult instead of a single result.

// call identifyLayersAsync, passing in the screen point, tolerance, return types, and maximum results, but no layer
final ListenableFuture<List<IdentifyLayerResult>> identifyFuture = mMapView.identifyLayersAsync(
    screenPoint, 20, false, 25);

// add a listener to the future
identifyFuture.addDoneListener(new Runnable() {
  @Override
  public void run() {
    try {
      // get the identify results from the future - returns when the operation is complete
      List<IdentifyLayerResult> identifyLayersResults = identifyFuture.get();

      // iterate all the layers in the identify result
      for (IdentifyLayerResult identifyLayerResult : identifyLayersResults) {

        // iterate each result in each identified layer, and check for Feature results
        for (GeoElement identifiedElement : identifyLayerResult.getElements()) {
          if (identifiedElement instanceof Feature) {
            Feature identifiedFeature = (Feature) identifiedElement;

            // Use feature as required, for example access attributes or geometry, select, build a table, etc...
            processIdentifyFeatureResult(identifiedFeature, identifyLayerResult.getLayerContent());
          }
        }
      }
    } catch (InterruptedException | ExecutionException ex) {
      dealWithException(ex); // must deal with exceptions thrown from the async identify operation
    }
  }
});

Layers that do not support identify or do not have any results based on the inputs are not included in the returned list.

Identify topmost item only

To identify only the topmost item at the screen point (or the topmost item per layer, if identifying against all layers) change the first workflow above by removing the maximum number of results parameter. GeoElements are still returned as a list, but the list will have only zero or one items.

The code below selects only the topmost feature identified at the screen point, for all feature layers in the map.

// call identifyLayersAsync, passing in the screen point, tolerance, and return types, but no maximum results value
final ListenableFuture<List<IdentifyLayerResult>> identifyFuture = mMapView.identifyLayersAsync(screenPoint, 20,
    false);

// add a listener to the future
identifyFuture.addDoneListener(new Runnable() {
  @Override
  public void run() {
    try {
      // get the identify results from the future - returns when the operation is complete
      List<IdentifyLayerResult> identifyLayersResults = identifyFuture.get();

      // iterate all the layers in the identify result
      for (IdentifyLayerResult identifyLayerResult : identifyLayersResults) {

        // each identified layer should find only one or zero results, when identifying topmost GeoElement only
        if (identifyLayerResult.getElements().size() > 0) {
          GeoElement topmostElement = identifyLayerResult.getElements().get(0);
          if (topmostElement instanceof Feature) {
            Feature identifiedFeature = (Feature)topmostElement;

            // Use feature as required, for example access attributes or geometry, select, build a table, etc...
            processIdentifyFeatureResult(identifiedFeature, identifyLayerResult.getLayerContent());
          }
        }
      }
    } catch (InterruptedException | ExecutionException ex) {
      dealWithException(ex); // must deal with exceptions thrown from the async identify operation
    }
  }
});

Identify on map image layers

Identifying against map image layers and tiled map layers follows the same workflow as shown for feature layers. The difference when identifying against map image layers are:

  • Results are returned as Features; unlike other features however, they will not have a reference to a FeatureTable.
  • Map image layers may have one or more sublayers—identify results from map image layers reflect this structure, and return results for each sublayer separately. (Note that if you have specified a maximum number of results to return, this value applies per sublayer.)

The code below shows how identify results are returned as Features for map image layers, which may have sublayer results that are also Features. A recursive function can be used to iterate through all the layer and sublayer results.

Demonstrates how to get all Features that will be identified at the screenPoint given for all layers attached to MapView or SceneView.

final ListenableFuture<List<IdentifyLayerResult>> identifyFuture = mMapView.identifyLayersAsync(screenPoint, 20,
    false, 10);
identifyFuture.addDoneListener(new Runnable() {
  @Override
  public void run() {
    try {
      // get the identify results from the future - returns when the operation is complete
      List<IdentifyLayerResult> identifyLayersResults = identifyFuture.get();

      // iterate all the layers in the identify result
      iterateIdentifyImageLayerResults(identifyLayersResults);

    } catch (InterruptedException | ExecutionException ex) {
      dealWithException(ex); // must deal with exceptions thrown from the async identify operation
    }
  }
});

Demonstrates how to cycles through all identified layers and get Features that were identified.

public void iterateIdentifyImageLayerResults(List<IdentifyLayerResult> identifyLayerResults) {
  if ((identifyLayerResults == null) || (identifyLayerResults.size() < 1)) return;

  // iterate all the layers in the identify result
  for (IdentifyLayerResult identifyLayerResult : identifyLayerResults) {

    // for each result, get the GeoElements
    for (GeoElement identifiedElement : identifyLayerResult.getElements()) {

      if (identifiedElement instanceof Feature) {
        // Map image layer identify results are returned as Features with a null FeatureTable; they cannot be
        // selected, but you can get the attributes and geometry of them...
        processIdentifyImageLayerResult(((Feature)identifiedElement), identifyLayerResult.getLayerContent());
      }
    }

    // for each result, get the sublayer results by recursion
    iterateIdentifyImageLayerResults(identifyLayerResult.getSublayerResults());
  }
}

Return pop-ups as results

Some types of layer support pop-ups. You can use an identify method to return pop-ups for map content at a screen point.

The code below shows how you can use IdentifyLayerResult.getPopups() to return Popups as identify results, instead of returning GeoElements.

// call identifyLayersAsync, passing in the screen location, tolerance, and specifying that pop-ups should be
// returned as well as features; not passing in maximum results parameter means only topmost item is identified
// note that you can choose to return popups only, instead of both geoelements and popups if available
final ListenableFuture<List<IdentifyLayerResult>> identifyFuture = mapView.identifyLayersAsync(screenPoint, 10, false);

// add a listener to the future
identifyFuture.addDoneListener(new Runnable() {

  @Override
  public void run() {
    try {

      // get the identify results from the future - returns when the operation is complete
      List<IdentifyLayerResult> identifyLayersResults = identifyFuture.get();

      // Here we just show the pop-up for the first (top) layer; if more than one layer may have pop-ups, or more
      // than one element is identified in a single layer, add controls to the pop-up content to iterate through
      // multiple pop-ups.
      if (identifyLayersResults.size() >= 1) {
        IdentifyLayerResult identifyLayerResult = identifyLayersResults.get(0);

        // Only identifying the topmost item in this example, so only expecting one pop-up
        if (identifyLayerResult.getPopups().size() >= 1) {
          Popup identifiedPopup = identifyLayerResult.getPopups().get(0);
          showPopup(identifiedPopup, screenPoint);
        }
      }

    } catch (InterruptedException | ExecutionException ex) {
      dealWithException(ex); // deal with exceptions thrown from the async identify operation
    }
  }
});

Identify features in a WMS layer

WMS layers differ from other layers as they do not support returning individual attributes or geometry for a feature. WMS services perform identification on the server and return HTML documents describing identified features. You can access the returned HTML document string through the "HTML" entry in the feature's attributes. This HTML string is suitable for display in a web view.

It is impossible to get the geometry for an identified (or any other) WMS feature. An identified WMS feature's geometry will always be null. Consequently, WMS layers do not support feature selection/highlight.

//specifying the layer to identify, where to identify, tolerance around point, to return pop-ups only, and
// maximum results
final ListenableFuture<IdentifyLayerResult> identifyFuture =
  mapView.identifyLayerAsync(wmsLayer, screenPoint, 20, false, 25);


// add a listener to the future
identifyFuture.addDoneListener(() -> {
  try {
    // get the identify results from the future - returns when the operation is complete
    IdentifyLayerResult identifyLayerResult = identifyFuture.get();


    // go through the results
    for (GeoElement element : identifyLayerResult.getElements()) {
      // is it a WMS feature?
      if (element instanceof WmsFeature) {
        WmsFeature wmsFeature = (WmsFeature) element;


        // get the HTML text which can be displayed in a UI component
        String htmlContent = (String) wmsFeature.getAttributes().get("HTML");
      }
    }
  } catch (InterruptedException | ExecutionException ex) {
    // ... must deal with checked exceptions thrown from the async identify operation
  }
});

Identify graphics

Graphics are identified using methods different than those used for layers. You can choose to return graphics in a specific graphic overlay or for all graphics overlays; you can also limit results to only the topmost graphic in each graphics overlay.

The code below shows how you can use MapView.identifyGraphicsOverlayAsync() or SceneView.identifyGraphicsOverlayAsync()to return Graphics as identify results, and select them.

// identify graphics on the graphics overlay
final ListenableFuture<IdentifyGraphicsOverlayResult> identifyFuture = mMapView.identifyGraphicsOverlayAsync(overlay,
    screenPoint, 10, false, 10);

identifyFuture.addDoneListener(new Runnable() {
  @Override
  public void run() {
    try {
      // get the list of graphics returned by identify
      List<Graphic> identifiedGraphics = identifyFuture.get().getGraphics();

      // iterate the graphics
      for (Graphic graphic : identifiedGraphics) {
        // Use identified graphics as required, for example access attributes or geometry, select, build a table, etc...
        graphic.setSelected(true);
      }
    } catch (InterruptedException | ExecutionException ex) {
      dealWithException(ex); // must deal with checked exceptions
    }

  }
});