Imagine for a moment that you have multiple layers in your map or scene, and you want to click on a particular location and find everything at that location. See the following image:
If you click on these layers, the IdentifyTask
object will return every feature in every layer that is located where you clicked. The IdentifyTask
class uses the concept of a hit test. A hit test simply means that if the point where the user clicks intersects with any feature in any layer, those features are returned to IdentifyTask
. It's possible to perform an Identify
operation on both online and offline data. Let's look at an online example first, and then explore this further with the same kind of operation but with offline data.
All examples in this section are in the code provided with this book, which is called Chapter7a
.
Let's add some code, and then discuss how IdentifyTask
works using an online service:
MapViewBehavior.cs
file and add the following using
statement: using Esri.ArcGISRuntime.Tasks.Query;
. Then, add a new event to the OnAttached
method, as shown here:AssociatedObject.MouseUp += AssociatedObject_MouseUp;
async void AssociatedObject_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e) { MapView mapView = sender as MapView; var screenPoint = e.GetPosition(mapView); // Convert the screen point to a point in map coordinates var mapPoint = mapView.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 //--current map extent (Envelope) var extent = mapView.Extent; //--tolerance, in pixels, for finding features var tolerance = 7; //--current height, in pixels, of the map control var height = (int)mapView.ActualHeight; //--current width, in pixels, of the map control var width = (int)mapView.ActualWidth; // Create a new IdentifyParameter; pass the variables above to // the constructor var identifyParams = new IdentifyParameters(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 = mapView.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; // do something } }
Note that this example first takes where the user clicked and converts it to a screen point. Next, that screen location is converted to a MapPoint
class. Then, an online service URI is created and passed into the IdentifyTask
constructor. Next, the extent, tolerance (in pixels), extent height, and width are created, and then passed into the IdentifyParameters
argument, where several parameters are set up. From there, IdentifyTask
is executed and IdentifyResult
returned so that something can be done with it.
The IdentifyParameters
argument has several options, so let's discuss them in more detail. Other than the MapPoint
class, you must specify an extent so that it limits the search area to perform the Identify
operation. Next is the tolerance. This is the distance in screen pixels from the specified geometry within which to execute the identify operation. The tolerance is very important because it is dependent on the size of the symbol being used for the features. If the symbol is really small, a larger tolerance needs to be used. If the symbol is very large, a smaller tolerance can be used. Lastly, the screen width and height are also specified.
The LayerOption
instance allows you to specify that only the Top
, Visible
, or All
layers will be hit tested for the Identify
operation. The All
option should be used carefully with map services that have many layers, because it can reduce performance. Also, with the Visible
option, only layers that have their visibility set to true
will be used in the Identify
operation. Lastly, if layers are grouped, you must specify the All
option; otherwise, nothing will be returned in the layers in GroupLayer
.
The LayerIDs
property allows you to specify specific layers to perform the Identify
operation on. Think of this property as an AND
operation. If layer IDs 0
and 1
are specified and are visible, they will be identified.
Let's add some code, and then discuss how an identify operation works with offline data:
MapViewBehavior.cs
file and add the following using
statements for Esri.ArcGISRuntime.Tasks.Query
and Esri.ArcGISRuntime.Data
. Then, add a new event in the OnAttached
method, as shown here:AssociatedObject.MouseUp += AssociatedObject_MouseUp;
MapView mapView = sender as MapView; var screenPoint = e.GetPosition(mapView); // Convert the screen point to a point in map coordinates var mapPoint = mapView.ScreenToLocation(screenPoint); // get the FeatureLayer FeatureLayer featureLayer = mapView.Map.Layers[1] as FeatureLayer; // Get the FeatureTable from the FeatureLayer. Esri.ArcGISRuntime.Data.FeatureTable featureTable = featureLayer.FeatureTable; // Translate the MapPoint into Microsoft Point object. System.Windows.Point windowsPoint = mapView.LocationToScreen(mapPoint); // get the Row IDs of the features that are hit long[] featureLayerRowIDs = await featureLayer.HitTestAsync(mapView, windowsPoint); if (featureLayerRowIDs.Length == 1) { // Cause the features in the FeatureLayer to highlight (cyan) in // the Map. featureLayer.SelectFeatures(featureLayerRowIDs); // Perform a Query on the FeatureLayer.FeatureTable using the // ObjectID of the feature tapped/clicked on in the map. // Query the layer using the Row IDs IEnumerable<GeodatabaseFeature> geoDatabaseFeature = (IEnumerable<GeodatabaseFeature>)await featureTable.QueryAsync(featureLayerRowIDs); foreach (Esri.ArcGISRuntime.Data.GeodatabaseFeature oneGeoDatabaseFeature in geoDatabaseFeature) { // Get the desired Field attribute values from the // GeodatabaseFeature. System.Collections.Generic.IDictionary<string, object> attributes = oneGeoDatabaseFeature.Attributes; object postID = attributes["POST_ID"]; // Construct a StringBuilder to hold the text from the Field // attributes. System.Text.StringBuilder stringBuilder = new System.Text.StringBuilder(); stringBuilder.AppendLine("POST ID: " + postID.ToString()); // Send to messenger Messenger.Default.Send<NotificationMessage>(new NotificationMessage(stringBuilder.ToString())); } }
This code segment essentially produces the same result as the IdentifyTask
constructors we used in the previous section, but with one big difference: we didn't use IdentifyTask
. The reason for this is that IdentifyTask
requires a URI to an online service, but we're using a local ArcGIS Runtime geodatabase. In fact, in the code we're just getting the second layer in the layers that are in the map. Once we get the FeatureTable
class of FeatureLayer
, we perform a hit test using HitTestAsync
on MapView
using the Windows point, which is in pixels. This returns a set of row IDs. In this case, we only wanted to perform the hit test on one layer, but we could have easily performed the hit test on whatever layers we wanted (Top
, Visible
, or All
).
Once we have the row IDs, we put them into a selected state. If you zoom in, you'll note that the color of the parking meter has changed to cyan
. This means it's selected. This isn't necessary, but it lets the user know what they're identifying. If they missed the right feature, they will immediately know it. Once the feature is selected, we perform a QueryAsync
variation using the row IDs. We then iterate over the returned GeodatabaseFeature
class and create a dictionary of attributes so that we can retrieve the attribute fields and values. Most importantly, we then sent the attributes in StringBuilder
to the MVVM Light messenger we set up in Chapter 2, The MVVM Pattern. As a result of using a custom behavior and the messenger, we've satisfied the notion of SoC.
Some other things to keep in mind include how you want to handle overlays and the Identify
operations, as shown in this chapter. For example, if you want to perform an Identify
operation and overlay, you'll need to figure out which mouse events to use in Windows. Do you use right-click, left-click, or mouse up? This can get even more complicated in Windows Store because you typically will want to handle MapViewTapped
. How will you handle an Identify
operation and overlay? You could give the user a dialog that provides an option, or you could pick and choose which layers to use for the overlay and which to use for the Identify
operation. Also, when designing an Identify
tool, you need to think about which fields to show. If your app has a pre-set list of layers with known fields, this would simply be a matter of hardcoding them in, but that's not the most elegant solution. With Runtime, you can read the layer fields and write your code in such a manner to generate the result dynamically. No matter what, careful consideration should be given to how you implement these interactive tools.