Skip To Content

Search for features

In this topic

The query task allows you to retrieve features from a single layer or table in an ArcGIS Server map service or 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 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 and may only contain attribute data. In such cases, the layer can be considered a table.

ArcGIS Server map, feature, and image services are accessible on the web as SOAP and REST web services. These services provide operations 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 find out details about the service you want to use. You can determine what query operations are supported.

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.

Create a query task

To instantiate a query task, provide a URL to REST resources 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: @"";
AGSQueryTask* queryTask = [[AGSQueryTask alloc] initWithURL: url];

When you create the task, ensure you take ownership of it. Otherwise, it might get deallocated before it has a chance to execute. If you instantiate the task using alloc as shown above, you automatically have ownership. However, if you instantiate the task using a convenience constructor, for instance queryTaskWithURL:, take ownership by assigning it to a property that retains it.

When you finish using the task, relinquish ownership so that its memory can be reclaimed. Do this by setting the corresponding property to nil.


Refer to Apple's Advanced Memory Management Programming Guide for more information on how to manage object memory.

Query 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 down the search results.

When performing queries, the task allows you to retrieve:

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

When you retrieve the actual features, remember that the number of features returned 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 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 on displaying 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 returned IDs. 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 may require more network requests but could show the first set of results faster 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 project 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 may 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 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.whereClause = @"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. However, 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 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 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 to a particular time, specify the ending time and leave the starting time as nil. The following time extent represents a time period 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 start and end times on the map extent are both inclusive.

Group and order results

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

You 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 which 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 don't 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;

Query 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/table in the service may be related to features from another layer/table. For instance, a pressure valve can be related to the pipeline on which it is installed and maybe some recent inspection records. The relationships that a layer/table participates in are listed under the Relationshipssection on the services directory page for the layer/table. The following example shows the relationships that a hypothetical pressure value 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 project into a different coordinate system. You can choose which feature attributes to include in the results. You can also choose to generalize the returned features' geometries or skip the geometry altogether.

Get results and handle errors

The query task informs its delegate when operations complete successfully or when errors occur. 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 : NSViewController <AGSQueryTaskDelegate> {  

An instance of your class must also be set as the task's delegate. This allows the task to invoke methods on your class in response to operations 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 thequeryTask: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 occurs. The error is passed into the method as an NSError object.

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