Query task

The Query task allows you to retrieve features from a single layer or table in an ArcGIS Server map service or a feature service. The task also allows you to query metadata of an ArcGIS Server image service that is based on a mosaic dataset.

A layer in a map or feature service can be of two types—a vector layer, also sometimes known as a feature layer or a raster layer. A vector layer stores geographic data in the form of shapes or vectors. A raster layer stores data in the form of imagery. The Query task works with vector layers, not raster layers. In some cases, a vector layer may not store any geographic data, it may only contain attribute data. In such cases, the layer can be considered a simple table. The Query task also works with tables. When a vector layer is queried, the results are features that contain geometry information. When a table is queried, the results are features without any geometry information.

ArcGIS Server map, feature, and image services are accessible on the web as SOAP and REST web services. These services provide operations that the Query task relies on. For example, a layer in a map service provides Query and Query Related Records operations. While publishing a service, the administrator may decide to disable some operations. Verify that the REST resource you intend to use supports the necessary operations

You can use the ArcGIS Server Services Directory to view details about the service you want to use and to determine the supported query operations.

Query operations
In fact, if you are familiar with the ArcGIS Server REST API, you can invoke query operations directly from a browser using the services directory.

Creating a Query task

To instantiate a Query task, provide a URL to the REST resource that supports query operations. If the service is secured, provide the credentials that can be used to access the service.

ServiceREST resource supporting Query operationsURL example

Map service

Vector layer or table


Feature service

Vector layer or table


Image service

Image service


The following code snippet shows how to create a Query task for the States layer in the ESRI_Census_USA map service on ArcGIS Online.

NSURL* url = [NSURL URLWithString: @"http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Demographics/ESRI_Census_USA/MapServer/5"];
AGSQueryTask* queryTask = [[AGSQueryTask alloc] initWithURL: url];

When you create the task, you need to ensure its memory does not get deallocated before it has a chance to execute. To do this, you need to retain the task if you're using manual retain-release, or create a strong reference to it if you're using ARC. See Apple's Memory Management Programming Guide for more information on how to manage object memory.

Querying features

The Query task allows you to perform attribute, spatial, and temporal queries. These queries are explained in the sections below. You can combine these queries into a single query to narrow the search results.

When performing queries, the task allows you to retrieve the following:

  • Only a count of how many features satisfy the query using executeFeatureCountWithQuery:
  • Only the IDs of features that satisfy the query using executeForIdsWithQuery:
  • Actual features that satisfy the query using executeWithQuery:

When you retrieve the actual features, remember that the number of returned features may be restricted by the limit set on the service. For example, if 2000 features match your query, but the service is configured to return only a maximum of 1000 features, you only get the first 1000 features. Take this into account when constructing your queries and talk to your server administrator to configure a reasonable limit.

If you don't intend to display all the features together, consider fetching only the IDs of the features at first. Unlike returning actual features, there is no limit on the number of IDs returned to you. You can then implement pagination by subsequently querying for features using a batch of IDs. There are trade-offs between fetching all the features at once versus fetching them in batches. The former requires less network requests but could take longer to complete and require more application memory. The latter might require more network requests but could show the first set of results quickly using less application memory. Decide which approach is more suitable for your application.

When retrieving actual features, you have a few options on how you want the results to return. You can specify an output spatial reference if you want the features to be projected into a different coordinate system. This is useful, for instance, if the service's spatial reference is different from the spatial reference of the map on which you want to display the features. You can also choose which feature attributes should be included in the results. This is useful if you want to hide some attributes from the user that are only meant for example, internal use. You can also reduce the payload of the response by opting to generalize the returned features' geometries or by skipping the geometry altogether.

Attribute queries

You can construct and execute queries that are based on attribute relationships. Such queries match features that have a certain set of attribute values. For example, a query that matches features in a Countries layer whose names begin with the letter Z, or have a population greater than 100 million. These queries are similar to the ones you might perform on a relational database using a WHERE clause. You can combine many attribute criteria into a single WHERE clause to narrow your search. Constructing a WHERE clause requires you to know what attributes are available on the features. You can find this information in the services directory listed under the Fields section of the REST resource you are querying.

Fields information in service directory

The following code snippet queries for states with a population greater than 1 million in the year 2000. The query also requests the returned features only contain the POP2000 and STATE_NAME fields.

AGSQuery* query = [AGSQuery query];
query.where = @"POP2000 > 1000000";
query.outFields = [NSArray arrayWithObjects: @"STATE_NAME", @"POP2000", nil];

[queryTask executeWithQuery:query] ;

Spatial queries

You can construct and execute queries based on spatial relationships. These queries match features that participate in a certain geometric relationship with a given feature. Using spatial queries you can find, for example, all cities within a state or all roads that run through a burn area. The Query task provides a set of commonly used spatial operators such as Intersects, Within, Touches, and so on. You can also construct custom spatial operators using the shape comparison language.

The following example shows how to query features that intersect a given envelope.

AGSEnvelope* env = ...;

AGSQuery* query = [AGSQuery query];
query.geometry = env;
query.spatialRelationship =  AGSSpatialRelationshipIntersects;

[queryTask executeWithQuery:query] ;

Temporal queries

If the service you are querying is time aware, you can execute queries to find features based on temporal relationships. Temporal relationships are specified using a time extent. For example, you may want to find all the earthquakes that occurred between December 3, 2001 and March 17, 2002. The following example shows how to execute queries based on time periods:

//create a time period
NSDate* start = ... ; //Dec, 3 2001
NSDate* end = ...; //Mar 17, 2002
AGSTimeExtent* time_period = [[AGSTimeExtent alloc] initWithStart:start end:end];

AGSQuery* query = [AGSQuery query];
query.timeExtent = time_period;
[queryTask executeWithQuery:query] ;

If you want to query data starting from a particular time up to the end, specify the starting time and leave the ending time as nil. The following extent represents a time period from March 17, 2002 and onwards:

NSDate* mar17 = ...;
AGSTimeExtent* mar17_onwards = [[AGSTimeExtent alloc] initWithStart:mar17 end:nil];
Conversely, if you want to query data starting from the beginning up to a particular time, specify the ending time and leave the starting time as nil. The following time extent represents a time period up to Dec 3, 2001:
NSDate* dec3 = ...;
AGSTimeExtent* till_dec3 = [[AGSTimeExtent alloc] initWithStart:nil end:dec3];
Sometimes, you may want to find features at a specific time instant and not during a time period. For example, the location of hurricane Katrina on August 25, 2005. The following code snippet shows how to execute queries based on time instants:
//create a time instant
NSDate* on = ...; //Aug 25, 2005
AGSTimeExtent* time_instant = [[AGSTimeExtent alloc] initWithStart:on end:on];

AGSQuery* query = [AGSQuery query];
query.timeExtent = time_instant;
[queryTask executeWithQuery:query] ;

The starting and ending times on the map extent are inclusive.

Grouping and ordering results

Services hosted on ArcGIS Server 10.1 or above allow you group and order results.

You typically group results when you want a statistic to be calculated for each group. For example, suppose you have a database of all cities in the world. You could group them by country and compute the average population for cities in each group (country).

//define the groups
NSMutableArray* groups =[[[NSMutableArray alloc] init] autorelease];
[groups addObject:@"COUNTRY"]; //assuming each feature has a field 'COUNTRY' 
query.groupByFieldsForStatistics = groups;

//define the statistic 
AGSOutStatistic* avg = [[[AGSOutStatistic alloc] init] autorelease];
avg.onStatisticField = @"POP1990"; //assuming each feature has a field 'POP1990' containing the population value
avg.statisticType = AGSQueryStatisticsTypeAvg; //type of statistic to be computed
avg.outStatisticFieldName = @"AvgPopulation"; //output field that should contain the result
query.outStatistics = [[[NSMutableArray alloc] initWithObjects: avg, nil] autorelease];

You could also compute another statistic for each group (the number of cities in each country).

AGSOutStatistic* count = [[[AGSOutStatistic alloc] init] autorelease];
count.onStatisticField = @"OBJECTID"; //this field really doesn't matter, because you dont need to look at any particular field to keep a count 
count.statisticType = AGSQueryStatisticsTypeCount;
count.outStatisticFieldName = @"NumberOfCities"; 

query.outStatistics = [[[NSMutableArray alloc] initWithObjects: avg, count, nil] autorelease];

And finally, you can also order the results alphabetically by country or numerically by average population.

NSMutableArray* order = [[[NSMutableArray alloc] init] autorelease];
    [order addObject:@"COUNTRY ASC"]; //The format of the string is : <FIELD_NAME> <ASC or DESC>
				//or     [order addObject:@"AvgPopulation DESC"];
    query.orderByFields = order;

Querying related features

Relationships capture how two or more geographic features may be related. For ArcGIS Server map or feature services, relationships are defined on a layer or table in the service. Features belonging to one layer or table in the service may be related to features from another layer or table. For instance, a pressure valve could be related to the pipeline on which it is installed and maybe some recent inspection records. The relationships that a layer or table participates in are listed under the Relationshipssection on the Services Directory page for the layer or table. The following example shows the relationships that a hypothetical Pressure Valve layer participates in.

Relationship information in services directory
Each relationship is identified by a unique ID. In the example above, the Installed on relationship has an ID of 0.

The Query task allows you to retrieve related features using executeWithRelationshipQuery:. Specify the ID of the feature or features whose related features you want to fetch and the ID of the relationship that links the features. Using the example shown above, the following code snippet queries for the pipeline on which the pressure valve is installed.

//ID of the valve for which you want to find the related pipeline
NSNumber* valveID = ...; 

AGSRelationshipQuery* relQuery = [AGSRelationshipQuery relationshipQuery];
relQuery.objectIds = [NSArray arrayWithObject: valveID];
relQuery.relationshipId = 0 ; //ID of 'Installed on' relationship

[queryTask executeWithRelationshipQuery: relQuery];

Just as with querying actual features, you have a few options on how you want the related features to return. You can specify an output spatial reference if you want the features to be projected into a different coordinate system. You can choose which feature attributes should be included in the results. You can also choose to generalize the returned features' geometries or skip the geometry.

Getting results and handling errors

The Query task informs its delegate when operations complete successfully or when errors are encountered. To get results from the task and to properly handle errors, set one of your classes as the task's delegate. Do this by making your class (typically the view controller that uses the task) adopt the AGSQueryTaskDelegate protocol.

@interface MyViewController : UIViewController <AGSQueryTaskDelegate> {  

Set an instance of your class as the task's delegate. This allows the task to invoke methods on your class in response to operations that it performs.

queryTask.delegate = self;

Your class should implement one or more methods defined in the protocol that pertains to the performed query. There are a pair of methods for every type of query, one for success and the other for failure. For instance, the delegate should implement the queryTask:operation:didExecuteWithRelatedFeatures: method to be informed when the query for related features completes successfully. Results are passed to the delegate method as a dictionary of key-value pairs.

- (void) queryTask:(AGSQueryTask*)queryTask operation:(NSOperation*)op didExecuteWithRelatedFeatures:(NSDictionary*) relatedFeatures {
  //The valve for which you are finding related features
  NSNumber* valveID = ...; 

  AGSFeatureSet* results = [relatedFeatures objectForKey: valveID];

  for (int i=0; i< [results.features count]; i++) {
    AGSGraphic *graphic = [results.features objectAtIndex:i];
    NSLog(@"graphic: %@",graphic);

Similarly, the delegate should implement the queryTask:operation:didFailRelationshipQueryWithError: method to be informed when an error is encountered. The error is passed into the method as an NSError object.

- (void) queryTask:(AGSQueryTask*)queryTask operation:(NSOperation*)	op didFailRelationshipQueryWithError: (NSError*)	error {
  NSLog(@"Error: %@",error); 

If your delegate methods are not being invoked when the task finishes execution, check to make sure that the task is not getting deallocated prematurely. You need to retain the task if you're using manual retain-release, or create a strong reference to it if you're using ARC.

See Also