In this topic
- Approaches to executing spatial queries on features and feature classes
- Prerequisites for code examples in this article
- Examples of spatial queries
- Find features within a polygon
- Query for features with relation to multiple geometries
- Buffering and querying
- Defining a spatial relationship with a Shape Comparison Language string
- Evaluating a specific spatial relationship using IRelationalOperator
- Using spatial caching to optimize spatial queries
- Spatial relationships and feature class tolerance
- Summary
There are two different APIs available to a developer who needs to query the spatial attributes of a feature class and the spatial relationships between features in a feature class.
- Spatial Filters: The ISpatialFilter interface can be used to return all features in a feature class that satisfy a specified spatial relationship with an inbound search geometry. A common way to perform a spatial query using a spatial filter is to create the spatial filter and use it as a parameter for IFeatureClass.search, IFeatureClass.select or similar methods on feature layers, selection sets, etc. These methods return a feature cursor or selection set with all the features that satisfy the specified relationship.
- Relational Operators: Relational operators compare two geometries and return a boolean value indicating whether or not the desired relationship exists. Some relationships require that the input geometries be of the same dimension while others have more flexible dimensional constraints. Most of the predefined relational operators are mutually exclusive Clementini operators. See the "Shape Comparison Language" topic in the General reference section of the SDK for more information about these. The IRelationalOperator interface is available in the Geometry library and has many methods for evaluating whether two input geometries satisfy a spatial relationship with one another.
Both types of spatial queries offer advantages in different applications. In general, relational operators are ideal for discrete geometry-on-geometry comparisons where the features being compared are known up front. On the other hand, spatial filters are good when working within the larger scope of a feature class and there is little to no information about the input features prior to executing the spatial query.
The following considerations apply to all code examples this article:
- In many of the example, geometries are retrieved based on hard-coded Object IDs. In a real-world application or tool, this would rarely be the case. Possible ways to find the Object ID of a specific feature include building UI that allows for manual entry by the user (or simply reading console input or a file in the case of non-UI applications), getting the selected feature(s) from the application’s map (if one exists), using a query filter to find the feature(s) that match certain criteria, or building a custom selection tool.
- The Geometry library contains high-level geometries and low-level geometries . High-level geometries include points, polylines, polygons, multipoints, envelopes and geometry bags, while low-level geometries include geometries that are components of high-level geometries, such as paths, rings, arcs and curves. The distinction is important in the context of this article because the ISpatialFilter interface requires a high-level geometry as a query geometry, and only high-level geometries implement the IRelationalOperator interface.
This section provides several illustrated examples of spatial queries along with code samples, using the ISpatialFilter and IRelationalOperator interfaces. The examples shown involve the following scenarios:
- Find features within a polygon : Display the names of the major highways that pass through a political region.
- Query for features with relation to multiple geometries : Given the Object IDs of four features from a feature class of blocks, find the parcels that are located within those blocks.
- Buffering and querying : Find cities with populations greater than 500,000 people within 500 kilometers of the city of Osaka, Japan, by applying a buffer to the city and identifying the cities that fall within the buffer.
- Defining a spatial relationship with a Shape Comparison Language string : Using a feature class with temporal data, where features with identical geometries exist, select all of the features that match a specific geometry.
- Evaluating a specific spatial relationship using IRelationalOperator : Evaluate whether a highway planned for construction will cross over marshland using the IRelationalOperator interface and two input geometries.
This example shows how to execute a spatial query that will find polyline features that intersect a polygon – specifically, major highways that pass through Iowa.
The illustration below shows several major highways running through Iowa:
The first step is to retrieve the geometry for the state of Iowa, assuming that the Object ID of the feature is known. Once the feature has been found, its geometry can be used as the query geometryin a spatial filter. Since the query should locate all highways that pass through it, and not necessarily those contained entirely within, the spatial filter’s relationship should be set to “intersects”.
The following code shows how to use the state’s geometry as a query geometry, construct a spatial filter using it, execute the query with the spatial filter, then iterate through the results, displaying the name of each highway:
// Get the feature and its geometry given an Object ID.
IFeature stateFeature = stateFeatureClass.getFeature(14);
IGeometry queryGeometry = stateFeature.getShape();
/* Create the spatial filter. "highwayFeatureClass" is the feature class containing
* the highway data. Set the SubFields property to "FULL_NAME", as only that field is
* going to be displayed.
*/
ISpatialFilter spatialFilter = new SpatialFilter();
spatialFilter.setGeometryByRef(queryGeometry);
spatialFilter.setGeometryField(highwayFeatureClass.getShapeFieldName());
spatialFilter.setSpatialRel(esriSpatialRelEnum.esriSpatialRelIntersects);
spatialFilter.setSubFields("FULL_NAME");
// Find the position of the "FULL_NAME" field in the Highway feature class.
int nameFieldPosition = highwayFeatureClass.findField("FULL_NAME");
// Execute the query and iterate through the cursor's results.
IFeatureCursor highwayCursor = highwayFeatureClass.search(spatialFilter, false);
IFeature highwayFeature = highwayCursor.nextFeature();
while (highwayFeature != null){
String name = (highwayFeature.getValue(nameFieldPosition)).toString();
System.out.println("Highway found: " + name);
highwayFeature = highwayCursor.nextFeature();
}
// The COM object cursor is no longer needed, so dispose of it.
Cleaner.release(highwayCursor);
This example shows how to use a geometry bag as a
query geometry. A geometry bag is a single
high-level geometry that stores a collection of
geometries. The use of a query bag as the query
geometry in a single query is an alternative to
performing multiple queries for each geometry. This
example shows how to find parcel features within a
known set of block features. The block features are
not adjacent, but their Object IDs are known.
The image below shows the blocks and the parcels
for our search area – the spatial query should
be limited to finding the parcels within the
blue highlighted blocks.
The first step in the process is to
create a new geometry bag and fill it
with the geometries of the three block
features. For this example, it’s assumed
the Object IDs of the blocks are known.
See the following code example:
// Create a new geometry bag and give it the same spatial reference as the
// blocks feature class.
IGeometryBag geometryBag = new GeometryBag();
IGeometryCollection geometryCollection = (IGeometryCollection)geometryBag;
IGeoDataset geoDataset = new IGeoDatasetProxy(blocksFeatureClass);
ISpatialReference spatialReference = geoDataset.getSpatialReference();
geometryBag.setSpatialReferenceByRef(spatialReference);
// Get a feature cursor for the three blocks and put their geometries into the geometry bag.
// Note that a non-recycling cursor is used, as the features'geometries are being stored
// for later use.
int[] blockObjectIDs = {
11043, 11049, 11057
};
IFeatureCursor blocksCursor = blocksFeatureClass.getFeatures(blockObjectIDs, false);
IFeature blockFeature = null;
Object missingType = null;
while ((blockFeature = blocksCursor.nextFeature()) != null){
geometryCollection.addGeometry(blockFeature.getShape(), missingType, missingType)
;
}
// The COM object cursor is no longer needed, so we need to dispose it.
Cleaner.release(blocksCursor);
When using a geometry bag as the query geometry, a
spatial index should be created on the geometry bag
to allow rapid access to the contained geometries
during the spatial query:
/**Cast the geometry bag to the ISpatialIndex interface
* and call the Invalidate method
* to generate a new spatial index.
*/
ISpatialIndex spatialIndex = (ISpatialIndex)geometryBag;
spatialIndex.setAllowIndexing(true);
spatialIndex.invalidate();
Now the geometry bag can be used as the query
geometry in a new spatial filter. The code below
shows how to use the spatial filter with a spatial
relationship of “contains” (for parcels completely
contained by the blocks) to find the number of
parcels inside of the three blocks:
/*Create the spatial filter. Note that the SubFields
* property specifies that only the Shape field is
* retrieved, since the features' attributes aren't being
* inspected.
*/
ISpatialFilter spatialFilter = new SpatialFilter();
spatialFilter.setGeometryByRef(geometryBag);
spatialFilter.setGeometryField(parcelsFeatureClass.getShapeFieldName());
spatialFilter.setSpatialRel(esriSpatialRelEnum.esriSpatialRelContains);
spatialFilter.setSubFields("Shape");
// Use IFeatureClass.FeatureCount to get a parcel count.
int parcelCount = parcelsFeatureClass.featureCount(spatialFilter);
System.out.println("Parcels in the three blocks: " + parcelCount);
Finding features within a certain distance of other features is a common task, and can be accomplished using a buffer. A buffer is a polygon that encloses a point, line or polygon at a specified distance. After buffering a feature, the buffer can then be used as the query geometry of a spatial filter to find all of the features within the specified distance of the feature. This example shows how to find the cities that have populations of 500,000 people or more within 500 kilometers of Osaka, Japan.
The image below shows the city data, along with the 500-kilometer buffer to be searched within (of course, the example assumes no such buffer already exists):
The first step is retrieving Osaka’s geometry (for this example, it’s assumed the feature’s Object ID is known) and applying a buffer to it. The buffer method takes an inbound argument in the same units as the spatial reference of the feature class being buffered. For the sake of simplicity it is assumed that the cities feature class is using a metric spatial reference and the units are meters.
// Find the feature for Osaka and get its geometry.
IFeature osakaFeature = citiesFeatureClass.getFeature(2263);
IGeometry osakaGeometry = osakaFeature.getShape();
// Use the ITopologicalOperator interface to create a buffer.
ITopologicalOperator topoOperator = (ITopologicalOperator)osakaGeometry;
IGeometry buffer = topoOperator.buffer(500000);
A spatial filter can now be created, using the
buffer as the query geometry, with a “Contains”
spatial relationship. A where clause can also be
applied, to remove smaller cities from the search –
the cities dataset contains a “POP_RANK” integer
field, with a lower number indicating higher
population. Cities with a population rank of 3 or
less have at least 500,000 people. Iterating through
the cursor’s results will return the names of the
cities within the buffer.
// Create the spatial filter.
ISpatialFilter spatialFilter = new SpatialFilter();
spatialFilter.setGeometryByRef(buffer);
spatialFilter.setGeometryField(citiesFeatureClass.getShapeFieldName());
spatialFilter.setSpatialRel(esriSpatialRelEnum.esriSpatialRelContains);
spatialFilter.setSubFields("CITY_NAME, POP_CLASS");
spatialFilter.setWhereClause("POP_RANK < 4");
// Find the position of the CITY_NAME and POP_CLASS fields in the feature class.
int cityNamePosition = citiesFeatureClass.findField("CITY_NAME");
int popClassPosition = citiesFeatureClass.findField("POP_CLASS");
// Execute the query.
IFeatureCursor featureCursor = citiesFeatureClass.search(spatialFilter, true);
IFeature feature = null;
while ((feature = featureCursor.nextFeature()) != null){
String cityName = (feature.getValue(cityNamePosition)).toString();
String popClass = (feature.getValue(popClassPosition)).toString();
System.out.println("City Name " + cityName + "Population: " + popClass);
}
In most cases, values from the esriSpatialRelEnum enumeration – such as esriSpatialRelTouches and esriSpatialRelWithin – can be used to define the appropriate spatial relationship for a query. If a query requires a spatial relationship that isn’t defined by the enumeration, a special value from the enumeration (esriSpatialRelRelation) and a Shape Comparison Language string can be used to define any spatial relationship. A filter’s shape comparison string can be set with the ISpatialFilter.setSpatialRelDescription() method. Each character in the string represents a relationship between the query geometry and the geometry being tested, and can have a value of ‘T’ (true), ‘F’ (false), or ‘*’ (not tested). The following table lists the relationships represented by each character:
|
Requested Geometry
|
|||
Interior
|
Boundary
|
Exterior
|
||
Query Geometry
|
Interior
|
1
|
2
|
3
|
Boundary
|
4
|
5
|
6
|
|
Exterior
|
9
|
8
|
7
|
Some examples of SpatialRelDescription strings include the following strings (more can be found in the javadoc for ISpatialFilter.SpatialRelDescription). The accompanying illustrations show the query geometry of each relationship with dashed-blue borders and the polygons that satisfy the relationship highlighted in red. Note that how spatial relationships are evaluated can vary depending on the geometry types of both the query geometry and the feature class being queried.
Illustration
|
String
|
Description
|
|
T********
|
The interiors of the geometries must intersect.
|
|
T***T****
|
The boundaries of the geometries must intersect and their interiors must intersect.
|
|
F***T****
|
The boundaries of the geometries must intersect and their interiors must not intersect.
|
|
FF*FF****
|
The geometries must be completely disjoint.
|
One scenario where building a string like those listed above can be useful is when a spatial query is needed to find identical geometries. The string used depends on the type of geometry being compared. When trying to find points with identical geometries, T******** can be used, because if two points share the same interior, they must be coincident. With polylines and polygons, TFFFTFFF* should be used to make sure that the interiors and boundaries of both geometries only intersect the interiors and boundaries of the other, and that neither intersect the other’s exterior. Note that the Exterior-Exterior relationship is never tested, as the exteriors of two geometries will always intersect.
This example will show how to find identical polygons in a temporal dataset containing several features for each US state, where the features of each have identical geometries but differ in date and population attributes. Although this example is contrived (the state name attribute could be used to achieve the same result), the process would be the same for datasets with coincident geometries and no common attributes to search by.
The following code example shows how to create a selection set containing features with the same geometry as a known feature (given its Object ID):
// Get the feature with the known Object ID (California).
IFeature caFeature = statesFeatureClass.getFeature(4);
IGeometry caGeometry = caFeature.getShape();
// Create a spatial filter with a SCL spatial relationship string.
ISpatialFilter spatialFilter = new SpatialFilter();
spatialFilter.setGeometryByRef(caGeometry);
spatialFilter.setGeometryField(statesFeatureClass.getShapeFieldName());
spatialFilter.setSpatialRel(esriSpatialRelEnum.esriSpatialRelRelation);
spatialFilter.setSpatialRelDescription("TFFFTFFF*");
spatialFilter.setSubFields("Start_Date, Population");
// Find the positions of the Start_Date and Population fields in the class.
int startDatePosition = statesFeatureClass.findField("Start_Date");
int populationPosition = statesFeatureClass.findField("Population");
// Execute the query.
ISelectionSet selectionSet = statesFeatureClass.select(spatialFilter,
esriSelectionType.esriSelectionTypeSnapshot,
esriSelectionOption.esriSelectionOptionNormal, null);
The
IRelationalOperator
interface can be used to verify the existence of
specific spatial relationships between two
high-level geometries. The interface is used by
casting a geometry to the interface, and providing
one of the methods with a comparison geometry. The
interface’s methods all have boolean return types
that indicate whether or not the specific
relationship actually exists.
The following scenario shows how to determine
whether a planned highway will come into contact
with a freshwater marsh, given a feature class
of roads being considered for construction and a
feature class of vegetation communities.
From the image above, it is easy to tell
that the highway (the red line)
intersects the marsh (the selected
polygon), but assuming the process
requires automation, the code example
below shows how to determine whether the
two intersect, assuming the ObjectIDs of
both features are known:
// Get the marsh and highway features from their respective classes.
IFeature marshFeature = vegFeatureClass.getFeature(518);
IFeature highwayFeature = roadsFeatureClass.getFeature(39);
// Get the geometries of the two features.
IGeometry marshGeometry = marshFeature.getShape();
IGeometry highwayGeometry = highwayFeature.getShape();
// Cast the highway's geometry to IRelationalOperator and determine whether or
// not it crosses the marsh's geometry.
IRelationalOperator relationalOperator = (IRelationalOperator)highwayGeometry;
boolean crosses = relationalOperator.crosses(marshGeometry);
System.out.println("Highway crosses marsh: " + crosses);
The IRelationalOperator3D interface provides similar functionality for evaluating whether Z-aware geometries are coincident, but with a single method - Disjoint3D.
Client-side caching of feature values within a
certain extent is known as
spatial caching
. Spatial caching can significantly improve
performance when multiple spatial queries are
performed in a common extent, as it reduces the
number of DBMS round trips required. An example of
where the spatial cache functionality is used in
ArcGIS Desktop is ArcMap’s Map Cache.
The
ISpatialCacheManager
interface (along with
ISpatialCacheManager2
and
ISpatialCacheManager3
) provides methods and properties to fill the
spatial cache given an extent, check the extent
and status of the cache, and to empty it when no
longer needed. The image below illustrates an
example of a situation where spatial caching
should be used; four spatial queries are
executed within a common extent (indicated with
the red-dashed line):
To use a spatial cache with these
queries, the required steps should be
taken:
- Open all the feature classes required by the queries. When the spatial cache is filled, only the features from open feature classes will be included.
- Fill the cache for the extent.
- Execute the queries.
- Empty the cache.
The following code example
shows how to do this:
// Open the feature classes used by the queries.
IFeatureClass blocksFeatureClass = featureWorkspace.openFeatureClass("Blocks");
IFeatureClass parcelsFeatureClass = featureWorkspace.openFeatureClass("Parcels");
// Fill the spatial cache.
ISpatialCacheManager spatialCacheManager = (ISpatialCacheManager)featureWorkspace;
// Check if the cache has been filled.
if (!spatialCacheManager.isCacheIsFull()){
// If not full, fill the cache.
spatialCacheManager.fillCache(cacheExtent);
}
// Execute spatial queries.
// Empty the cache.
spatialCacheManager.emptyCache();
It is important to consider the XY tolerance of a
feature class when evaluating the spatial
relationships. Different XY tolerances values can
produce different results for relational and
topological operations. For example, two geometries
might be classified as disjoint (features physically
not touching) with a small XY tolerance, but a
larger XY tolerance value might cause them to be
classified as intersecting. This is because the XY
tolerance is taken into consideration when
evaluating spatial relationships between objects in
the Geodatabase.
The choice to use ISpatialFilter or
IRelationalOperator for spatial queries depends on
what is known prior to executing the query, as well
as what type of results are desired. If the goal of
the query is to find features that satisfy a spatial
relationship with a single geometry (or a
collection, if a geometry bag is used), a spatial
filter should be used. When trying to verify that a
spatial relationship exists (or doesn’t exist)
between two specific geometries, a relational
operator would be a better choice.
Spatial caching and tolerance should be
considered when executing spatial queries. If
multiple spatial queries are going to be
executed within a common extent, the use of
spatial caching can significantly increase
performance by reducing round trips to the data
source. XY Tolerance of a feature class can have
an effect on the results of a spatial query, and
should be kept in mind when executing spatial
queries, particularly with feature classes that
have unusually large XY tolerances.
Development licensing | Deployment licensing |
---|---|
ArcView | ArcView |
ArcEditor | ArcEditor |
ArcInfo | ArcInfo |
Engine Developer Kit | Engine Runtime |