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 at all, 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 which 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 upon. 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. You should 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 wish to use. You can easily find out what query operations are supported.
Creating a Query task
To instantiate a Query task, you need to provide a URL to REST resource that supports query operations. If the service is secured, you will also need to provide the credentials that can be used to access the service.
Service | REST Resource supporting Query operations | URL example |
---|---|---|
Map Service | A vector layer or table | http://<server>/<instance>/services/<service>/MapServer/<layer_or_table_ID> |
Feature Service | A vector layer or table | http://<server>/<instance>/services/<service>/FeatureServer/<layer_or_table_ID> |
Image Service | The service itself | http://<server>/<instance>/services/<service>/ImageServer |
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 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: , you need to take ownership by either sending it a retain message, or by assigning it to a property that will retain it.
When you are finished using the task, you should relinquish ownership so that its memory can be reclaimed. You do this either by sending the task a release message or by setting the corresponding property to nil.
Please refer to 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 even 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, keep in mind 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 will only get the first 1000 features . You should 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, you should 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 very quickly using less application memory. You need to 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 be returned. 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 which are only meant for, say, 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 present on the features. You can find this information in the Services Directory listed under the Fields section of REST resource you are querying.
The following code snippet queries for States which had a population greater than 1 million in the year 2000. The query also requests that 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 that are 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, etc. But 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 = AGSQuerySpatialRelationshipIntersects;
[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] ;
f you want to query data starting from a particular time up till the end, you can just specify the starting time and leave the ending time as nil. The following extent represent a time period from March 17, 2002 and onwards -
NSDate* mar17 = ...;
AGSTimeExtent* mar17_onwards = [[AGSTimeExtent alloc] initWithStart:mar17 end:nil];
NSDate* dec3 = ...;
AGSTimeExtent* till_dec3 = [[AGSTimeExtent alloc] initWithStart:nil end:dec3];
//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 both inclusive
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 a table in the service. Thus, features belonging to one layer/table in the service may be related to features from another layer/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/table participates in are listed under the Relationshipssection of Services Directory page for the layer/table. The following example shows the relationships that a hypothetical Pressure Valve layer participates in.
The Query task allows you to retrieve related features using executeWithRelationshipQuery:. You need to specify the ID of the feature(s) 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 on.
//ID of the valve for which we 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 feature, you have a few options on how you want the related features to be returned. 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 altogether.
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 any errors, you must set one of your classes as the task's delegate. You do this by making your class (typically the view controller which uses the task) adopt the AGSQueryTaskDelegate protocol.
@interface MyViewController : UIViewController <AGSQueryTaskDelegate> {
...
}
An instance of your class must also be set as the task's delegate. This will allow the task to invoke methods on your class in response to operations that it performs.
queryTask.delegate = self;
Your class should implement one ore more methods defined in the protocol which pertain to the query being performed. 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. Rresults 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 we 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);
}