Online searching

In this section, we'll go over searching content from online layers.

FindTask

In Chapter 1, Introduction to ArcGIS Runtime, we were introduced to a tool that searched through several layers using a FindTask constructor. We didn't really discuss how it worked, but we made some changes to it and just enjoyed when it searched through several layers. Let's go over that code in more detail. Here's the code again:

var findTask = new FindTask(new System.Uri(this.USAUri));

var findParameters = new FindParameters();
findParameters.LayerIDs.Add(0); // Cities
findParameters.LayerIDs.Add(3); // Counties
findParameters.LayerIDs.Add(2); // States

findParameters.SearchFields.Add("name");
findParameters.SearchFields.Add("areaname");
findParameters.SearchFields.Add("state_name");

findParameters.ReturnGeometry = true;

SpatialReference sr = new SpatialReference(wkid);
findParameters.SpatialReference = sr;


findParameters.SearchText = this.SearchText;
findParameters.Contains = true;

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 '" + this.SearchText +
    "' in a Name attribute", foundCities, foundCounties,
    foundStates);

// Bind the results to a DataGrid control on the page
IReadOnlyList<FindItem> temp = findResult.Results;

ObservableCollection<FindItem> obsCollection = new ObservableCollection<FindItem>();
foreach (FindItem item in temp)
{
    obsCollection.Add(item);
}

this.GridDataResults = obsCollection;

// show message
Messenger.Default.Send<NotificationMessage>(new NotificationMessage(msg));

The first thing that happens with this code is the FindTask constructor is instantiated with an online service. After that, the most important part of this algorithm is FindParameters. The FindParameters class allows you to specify which layers to search though using LayerIDs. As we discussed earlier, a map service can have one or more layers in it. You specify those layers using their ID, which is always an integer. If you don't know the layers, inspect the service to determine the layers that are in the service and their unique IDs. After the layers are specified, the fields to search against are added to SearchFields. You can specify the field names as shown previously, or you can instantiate a generic List<string> instance as shown here:

System.Collections.Generic.List<string> searchFields = new 
    System.Collections.Generic.List<string>();
searchFields.Add("areaname");
findParameters.SearchFields = searchFields;

You can also specify the fields using a comma-separated list like so:

findParameters.SearchFields.AddRange(new string[] { "CITY_NAME", 
    "NAME", "SYSTEM", "STATE_ABBR", "STATE_NAME" });  

If you don't specify SearchFields, all fields will be searched, and that can reduce performance, especially in large layers. After the fields are set, you really need to set the coordinate system so that it matches the coordinate system of MapView. The next parameter is the text you want to search for. If you set Contains to true, an SQL-like expression is used that matches any part of the string literal the user specifies. If Contains is set to false, it will explicitly look for any record that exactly matches the text that the user enters.

Some other properties worth discussing are ReturnGeometry and LayerDefinitions. The ReturnGeometry property is a Boolean value that indicates whether to return the geometry or not. This option is helpful if you want to do something with the geometry of the results. The LayerDefinitions property is a get or set property that allows you to determine whether the layers had an attribute definition used against them when the layers were originally published. For example, if someone had all of the countries in the world, but only wanted to show European countries, they would have set an expression in ArcMap via a definition query (CONTINENT = 'Europe') that only shows those countries. When the layer is published, ArcGIS Server will honor that expression as it is stored with the layer. You can get or change the expression with LayerDefinitions, using an example such as this:

findParameters.LayerDefinitions = new LayerDefinition[]   
{  
    new LayerDefinition() { LayerID = 0, Definition = 
        "COUNTRY='Norway" },  
    new LayerDefinition() { LayerID = 2, Definition = 
        "COUNTRY='France" }   
};

Once the parameters are specified, they are passed into the FindTask constructor:

FindResult findResult = await findTask.ExecuteAsync(findParameters);

The FindTask constructor will execute the task asynchronously, and then return a FindResult class. The FindResult class contains the results of FindTask and must be awaited. The FindResult class has two properties worth further discussion: ExceededTransferLimit and Results. The ExceededTransferLimit property is a Boolean value that simply lets you know whether the number of records returned exceeds the maximum number of records that ArcGIS Server has been set to return when querying a layer. The default value in ArcGIS Server is 1,000 records. Generally speaking, you should check this just to make sure that you haven't exceeded this value, not only for performance reasons, but also because if you exceed this value only 1,000 records will be returned. In essence, if your query returned 1,500 records, but ArcGIS Server is set to 1,000, your query will not have the other 500 records. As a result, it's important that you communicate with your ArcGIS Server administrator to let them know that this setting needs to be higher than the default value.

The Results property is the most important object you will be using after FindTask finishes, because it has what you're interested in: data. As shown in the preceding code, ExecuteAsync returns Results, which you can use to get the total number of records, iterate over to present to the user, summarize as shown in the code, and many other useful techniques. The Results property contains a collection of FindItem, which, as shown previously, you can iterate over using a foreach statement. Once the data is summarized, it is then translated into an ObservableCollection object so that the View can bind to the data.

Canceling a task

The ExecuteAsync method also has an overload that includes a System.Threading.CancellationToken parameter, which allows you to cancel a task using code such as this:

System.Threading.CancellationTokenSource canceller = new 
    System.Threading.CancellationTokenSource();
canceller.CancelAfter(10000); // 10 seconds
var token = canceller.Token;

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

This code will simply stop running if the FindTask property takes longer than 10 seconds, and then you could let your user know that the query took too long.

QueryTask

As noted earlier, QueryTask is more powerful than FindTask because it allows you to include spatial queries. But before we delve into spatial queries, let's go over a simple example to show how similar QueryTask is to FindTask. Before you set up a QueryTask property, you must first define a Query task. This is similar in concept to FindParameters, but with a lot more options.

A basic example of Query looks like the following code:

Esri.ArcGISRuntime.Tasks.Query.Query query = new 
    Esri.ArcGISRuntime.Tasks.Query.Query(sqlQuery);
query.Geometry = this.mapView.Extent;
query.OutSpatialReference = this.mapView.SpatialReference;
query.OutFields.Add("*");

The Query task has four overloads, which allow you to either pass an IEnumerable method of object IDs in a layer or table, a WHERE clause, TimeExtent, or a Geometry class with the spatial relationship. Let's first discuss the object IDs. An object ID is simply a unique ID for each feature in a FeatureLayer resource. Every record in a FeatureService or FeatureLayer resource will automatically have a unique object ID. A WHERE clause is the same as discussed earlier with a FindTask property. A TimeExtent instance is a time window, such as 5 minutes ago:

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);

The last option we will discuss here is the Geometry and spatial relationship overload. With this overload, you can pass in geometry and query the layer using a spatial relationship. For example, you can pass in a MapPoint class and use it to see if it intersects with a polygon:

QueryTask

In the preceding screenshot, the point intersects with the state of Texas, so in this case, the state would be returned in the query. The other spatial relationships available include Contains, Crosses, EnvelopeIntersects, IndexIntersects, Overlap, Touches, Within, and Relation.

Once you've set up the Query object, it can be passed into the QueryTask constructor, as shown here:

Query query = new Query("areaname = '" + this.SearchText + "'");
query.OutFields.Add("*");
System.Uri uri = new System.Uri(this.USAUri + "/0");
QueryTask queryTask = new QueryTask(uri);
QueryResult queryResult = await queryTask.ExecuteAsync(query);

Note that this code is using the same layer we used in earlier chapters. In this case, we're querying the cities layer (layer 0) using the areaname field. However, with QueryTask, you can build more complicated queries such as this:

query.Where = "(TYPE = 2 AND STATUS = 1) OR (SPEED <= 10)";

Once we execute the QueryTask constructor, it returns a QueryResult class. The most important thing to note about the difference between FindTask and QueryTask is that QueryTask works on a single layer, not multiple layers. Also, QueryResult has a property called FeatureSet, which contains a set of features. The Features class is a base class to graphics, but you can add these features to a GraphicsLayer class using code such as this:

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

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

// Display result graphics
graphicsLayer.GraphicsSource = resultFeatures;

You also have the option of whether or not to return the geometry by setting ReturnGeometry to true or false for Query. With Query, you can also set the GeometryPrecision property, which indicates the precision of the geometry. Also, you can set the OrderByFields property, which works just like ORDER BY in an SQL database. Another useful property is OutStatistics, which returns statistics about the field values. With OutStatistics you can compute the average, count, max, min, standard deviation, sum, and variance. You can use OutStatistics on a layer using an example such as this:

SpatialReference sr = new SpatialReference(wkid);

Query queryCityStats = new Query("pop2000 > 1000");
queryCityStats.OutSpatialReference = sr;
queryCityStats.OutFields.Add("*");
queryCityStats.OutStatistics = new List<OutStatistic> { 
    new OutStatistic(){
        OnStatisticField = "pop2000",
        OutStatisticFieldName = "citysum",
        StatisticType = StatisticType.Sum
    },
    new OutStatistic(){
        OnStatisticField = "pop2000",
        OutStatisticFieldName = "cityavg",
        StatisticType = StatisticType.Average
    }
};
QueryTask queryTaskCityStats = new QueryTask(new System.Uri(this.USAUri + "/0"));
QueryResult queryResultCityStatus = await queryTaskCityStats.ExecuteAsync(queryCityStats);

IReadOnlyList<Feature> featuresCityStats = queryResultCityStatus.FeatureSet.Features;
Feature feature = featuresCityStats[0];
double sum = (double)feature.Attributes["citysum"];
double avg = (double)feature.Attributes["cityavg"];

In this example code, an OutStatistics property is created using a generic List instance of the OutStatistic type on the field called pop2000. The average and sum of the population of all cities over 1,000 are calculated. The OutStatisticFieldName instance is set to citysum and cityavg. Using the results of the QueryTask constructor, the attribute values are cast to a couple of variables of the type double.

A quick example using QueryTask

Let's create an app as we did earlier, where the user was able to search for a city, state, or county using a string literal. However, instead of walking you through all of the steps, you can create a project from scratch or just check out the provided sample project called Chapter7. In those early chapters, we used a FindTask constructor, so let's refactor and use a QueryTask constructor instead. You won't need a DataGrid control. Also, instead of reminding you of everything you need to do to set up this project, we will skip that step and let you build the project based on what you've learned in earlier chapters:

  1. Create a UI that allows the user to enter in a name and a button that allows them to search. Use the following code:
    public async void Search(int wkid)
    {
    
        // Create the symbol
        SimpleMarkerSymbol markerSymbol = new SimpleMarkerSymbol();
        markerSymbol.Size = 25;
        markerSymbol.Color = Colors.Red;
        markerSymbol.Style = SimpleMarkerStyle.Diamond;
    
        SimpleFillSymbol sfsState = new SimpleFillSymbol()
        {
            Color = Colors.Red,
            Style = SimpleFillStyle.Solid
        };
        SimpleFillSymbol sfsCounty = new SimpleFillSymbol()
        {
            Color = Colors.Red,
            Style = SimpleFillStyle.Solid
        };
    
        SpatialReference sr = new SpatialReference(wkid);
    
        Query queryCity = new Query("areaname = '" + this.SearchText + "'");
        queryCity.OutSpatialReference = sr;
        queryCity.OutFields.Add("*");
        QueryTask queryTaskCity = 
            new QueryTask(new System.Uri(this.USAUri + "/0"));
        QueryResult queryResultCity = await queryTaskCity.ExecuteAsync(queryCity);
    
        Query queryStates = 
            new Query("state_name = '" + this.SearchText + "'");
        queryStates.OutSpatialReference = sr;
        queryStates.OutFields.Add("*");
        QueryTask queryTaskStates = 
            new QueryTask(new System.Uri(this.USAUri + "/2"));
        QueryResult queryResultStates = await queryTaskStates.ExecuteAsync(queryStates);
    
        Query queryCounties = new Query("name = '" + this.SearchText + "'");
        queryCounties.OutSpatialReference = sr;
        queryCounties.OutFields.Add("*");
        QueryTask queryTaskCounties = 
            new QueryTask(new System.Uri(this.USAUri + "/3"));
        QueryResult queryResultCounties= await queryTaskCounties.ExecuteAsync(queryCounties);
    
        // Get the list of features (graphics) from the result
        IReadOnlyList<Feature> featuresCity = 
            queryResultCity.FeatureSet.Features;
        foreach (Feature featureCity in featuresCity)
        {
            Graphic graphicCity = (Graphic)featureCity;
            graphicCity.Symbol = markerSymbol;
            this.graphicsLayerCity.Graphics.Add(graphicCity);
        }
    
        // Get the list of features (graphics) from the result
        IReadOnlyList<Feature> featuresStates = 
            queryResultStates.FeatureSet.Features;
        foreach (Feature featureState in featuresStates)
        {
            Graphic graphicState = (Graphic)featureState;
            graphicState.Symbol = sfsState;
            this.graphicsLayerState.Graphics.Add(graphicState);
        }
        // Get the list of features (graphics) from the result
        IReadOnlyList<Feature> featuresCounties =  
            queryResultCounties.FeatureSet.Features;
        foreach (Feature featureCounty in featuresCounties)
        {
            Graphic graphicCounty = (Graphic)featureCounty;
            graphicCounty.Symbol = sfsCounty;
            this.graphicsLayerCounty.Graphics.Add(graphicCounty);
        }
    } 
  2. Run your app, and then zoom in around the eastern U.S.

You will see something like the following screenshot if you enter Lancaster as the search string:

A quick example using QueryTask

Note that any city, state, or county with the name Lancaster was found and shown on the map. There were no states with the name Lancaster in them, but there were several cities (green triangles) and counties (filled in red) with that name.

The first thing you'll note is that three separate QueryTask objects were created. Then, we retrieved the features, converted them to graphics, and then added them to the GraphicsLayer class. You now know how to create the GraphicsLayer class.

The QueryTask options

The QueryTask option is a very powerful search object because it simply provides a lot of options. The ExecuteAsync method has several variations (ExecuteCountAync, ExecuteObjectIDsQueryAsync, and ExecuteRelationshipQueryAsync), which mainly allow you to search for the number of features in a layer, search and return just object IDs, and search for data in a related table. For best performance, search using QueryTask with ExecuteObjectIDsQueryAsync. This method will only return Object IDs instead of the attributes and geometry, so it is inherently faster. With these Object IDs, you can perform additional operations, such as placing the features into a selected state.

It's also possible to search for tables or layers that are related to the layer you're searching on, using ExecuteRelationshipQueryAsync. This method expects a RelationshipParameter class, which is another class for defining the relationships. Take a look at map service by navigating to http://sampleserver3.arcgisonline.com/ArcGIS/rest/services/Petroleum/KSPetro/MapServer. Click on the Wells layer. Near the bottom of the page, you'll see a section called Relationships. This shows that this layer has a relationship to a table called Tops and another layer called Fields. To set up the relationship so that you can return records, you would specify the RelationshipParameter class similar to this:

RelationshipParameter relationshipParameters = new 
    RelationshipParameter(){
    ObjectIds = (int[])ObjIds,
    DefinitionExpression =  exp,
    OutFields = new string[] { "OBJECTID, KID, FORMATION" },
    RelationshipId = 1,
    OutSpatialReference = MyMap.SpatialReference
};

In this example, the Object IDs can come from a query or by clicking on a single feature using an Identify operation (discussed later in this chapter); then you must specify the DefinitionExpression property (the WHERE clause), the output fields, relationship ID, and spatial reference. The relationship ID is in parentheses, as shown here:

The QueryTask options

In this example, the relationship ID is 3. The RelationshipParameter class returns a RelationshipResult object, which you can use to iterate over.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.128.226.255