Using the Search Cursor Spatial Filter Parameter with ArcPy

ArcGIS Pro 3.2 added three new parameters to the Search Cursor (and Update Cursor) with ArcPy. These parameters are named spatial_filter, spatial_relationship, and search_order. Here, we will discuss the first two. The search_order parameter can only be used with an enterprise geodatabase. Check out the documentation here.

As of ArcGIS Pro 3.2, the latest syntax for the SearchCursor is as follows…

arcpy.da.SearchCursor (
    in_table, 
    field_names, 
    {where_clause}, 
    {spatial_reference}, 
    {explode_to_points}, 
    {sql_clause}, 
    {datum_transformation}, 
    {spatial_filter}, 
    {spatial_relationship}, 
    {search_order}
)

The da stands for the Data Access module. You can find out more about the Data Access module here.

If you are interested in mastering ArcPy Cursors (Search, Insert, Update), then check out this course!

What is a SearchCursor?

A SearchCursor in ArcPy is an iterable Python object that enables the retrieval of data from a table or feature class in ArcGIS Pro. The SearchCursor object allows sequential access, as rows, to the data returned. The SearchCursor facilitates a way to programmatically interact with tabular GIS datasets and the extraction of attribute and geometry information for analysis and processing.

The spatial_filter Parameter

A geometry object used to spatially filter features.

Esri

A GEOMETRY OBJECT! This is very important. Generally when we think of a spatial filter or relationships we tend to think features and feature classes as a whole. Just look at the Select by Location tool in ArcGIS Pro. The spatial_filter parameter takes one Geometry object and that’s it! Stating that it takes a Geometry object is a bit misleading. There is an ArcPy Geometry object, which the documentation would lead you to believe must be used, but you can also use the other ArcPy (geometry) objects; Multipoint, PointGeometry, Polygon, and Polyline. All except Point.

When the spatial_filter parameter is used, the spatial_realtionship parameter is also be used. By default, it will be set to INTERSECTS.

The spatial_relationship Parameter

This parameter represents the spatial relationship between the geometries from the in_table parameter supplied for the SearchCursor and the Geometry object from the spatial_filter parameter. The spatial relationship options are limited and are pretty much your basic relationship types. As mentioned, the default is INTERSECTS.

INTERSECTS – rows from the in_table are returned if their geometry intersects the Geometry object.
ENVELOPE_INTERSECTS – rows from the in_table are returned if their geometry intersects the bounding-box of the Geometry object.
INDEX_INTERSECTS – a faster version than ENVERLOPE_INTERSECTS, uses the in_table underlying spatial grid index.
TOUCHES – rows from the in_table are returned if the Geometry object touches the row’s geometry.
CROSSES – rows from the in_table are returned if the Geometry object crosses the row’s geometry.
WITHIN – rows from the in_table are returned if the the Geometry object is within the row’s geometry.
CONTAINS – rows from the in_table are returned when the Geometry object contains the row’s geometry.

The spatial_filter in Action!

You can download the data used below here. It is a subset of OSM downloaded from GeoFabrik. We have four layers;
1. Celbridge Points of Interest represented by Point Geometry
2. Celbridge Roads as Polyline Geometry
3. Celbridge Buildings as Polygon Geometry
4, Celbridge Landuse, also as Polygon Geometry but Multipart Geometry!

From the Anaysis tab > Python dropdown > select Python Window.

The Python Window will open in ArcGIS Pro.

Let’s use the SearchCursor to return all rows from the Celbridge Buildings layer that intersects the ‘retail’ from Celbridge Landuse.

We first get the Geometry object for the retail polygon. We will create a variable called retail_geom and use Python list comprehension with the SearchCursor. Our in_table parameter for our SearchCursor is the ‘Celbridge Landuse’ layer. We are only interested in the geometry of the retail polygon so we use the SHAPE@ token as the only entry to our field_names parameter. We use a where_clause to limit the rows in the SearchCursor to only return where the fclass attribute value is ‘retail’. row[0] represents accessing the first attribute returned for each row, there is only one attribute and that is the geometry from the SHAPE@ token. List comprehension always returns a list so we use the [0] index at the end of our Python statement to assign the geometry object to our retail_geom variable.

retail_geom = [row[0] for row in arcpy.da.SearchCursor('Celbridge Landuse', "SHAPE@", "fclass = 'retail'")][0]

Print the retail_geom varibale to screen and we see that we have a Polygon object.

We can now use or Polygon object as the Geometry object for a spatial_filter in another SearchCursor. We are interested in each building polygon from the Celbridge Buildings layer that intersects our Geometry object.

Create an empty list with a variable name oid_list.

oid_list = []

This time around we will use the Python with statement and a for loop to iterate through the SearchCursor object. Our in_table parameter for the SearchCursor is our ‘Celbridge Buildings’ layer. We are interested in two fields, the OBJECTID and the name attribute, so we supply these as a list using the OID@ token and the name field. We set our spatial_filter parameter to our Polygon object as the retail_geom variable. We do not need to set the spatial_relationship parameter as we know it defaults to INTERSECTS. For each row in the SearchCursor we will print row[0], which is the OID, and row[1] which is the name attribute. We will also append each OID into our oid_list.

with arcpy.da.SearchCursor('Celbridge Buildings', ["OID@", "name"], spatial_filter=retail_geom) as cursor:
    for row in cursor:
        print(row[0], row[1])
        oid_list.append(row[0])

The printed output.

All these buildings intersect with our Polygon object that represents the ‘retail’ landuse areas. Let’s use our oid_list to select these in the map.

aprx = arcpy.mp.ArcGISProject("CURRENT")
m = aprx.listMaps("Cursors_Map")[0] ## change to the name of your map
lyr = m.listLayers("Celbridge Buildings")[0]
lyr.setSelectionSet(oid_list)

We can see all the buildings highlighted where the building intersects the ‘retail’ landuse.

Conclusion

The spatial_filter parameter is a nice addition to the SearchCursor with ArcPy but it has it’s limitations. As we have seen, the spatial_filter parameter must be a Geometry type object. This means that you can only use the geometry from one record/feature, unless you do some prior processing to create a multipart geometry (you can achieve this with ArcPy). The SearchCursor will only return rows based on the spatial_relationship parameter and as such does not enable negation. You cannot return rows that do not intersect the geometry object for example. You could have course do some post-processing use ArcPy to achieve this. Because of these limitations, I feel there is more control and options using the SelectLayerByAttribute and SelectLayerByLocation tools in tandem with the SearchCursor to achieve more than what the spatial_filter and spatial_relationship parameters currently offer.

Mastering ArcGIS Pro ArcPy Search, Insert, & Update Cursors

If you are interested in mastering ArcPy Cursors (Search, Insert, Update), then check out this course!

Leave a Comment

Your email address will not be published. Required fields are marked *