Select to view content in your preferred language

Calling arcpy spatial join on a layer with a selection resets the layer selection

1115
8
11-06-2023 02:34 PM
Flann
by
Occasional Contributor

Hi, I am the process of converting a geoprocessing script from ArcMap & Python 2 to Pro & Python 3 and I've run into an annoying issue with the spatial join tool.  My script takes a feature layer as input and then acts on the selection in that layer.  This worked fine in ArcMap but in Pro arcpy's spatial join is clearing the layer selection after it runs.  I am using ArcGIS Pro 3.1.  Here is an example script that demonstrates the problem:

 

 

 

import arcpy


def get_selection_count(in_layer):
    """return the number of selected features in the layer"""
    selection = in_layer.getSelectionSet()
    if selection:
        return len(selection)
    return 0


# envs
arcpy.env.overwriteOutput = True
arcpy.env.workspace = arcpy.env.scratchGDB

# create test feature class
fc = arcpy.management.CreateFeatureclass(arcpy.env.scratchGDB, "test_point_fc", "POINT")[0]
points = [arcpy.Point(100, 100), arcpy.Point(200, 200)]
with arcpy.da.InsertCursor(fc, ["SHAPE@"]) as crsr:
    for point in points:
        crsr.insertRow((point,))

# make a layer
layer = arcpy.management.MakeFeatureLayer(fc, "test_layer")[0]

# make a selection
layer = arcpy.management.SelectLayerByAttribute(layer, "NEW_SELECTION", "1=1")[0]


print(f"There are {get_selection_count(layer)} features selected.")

arcpy.SpatialJoin_analysis(layer, layer, "out_sj")

print(f"There are {get_selection_count(layer)} features selected.")

 

Edit:

The output would be

There are 2 features selected.
There are 0 features selected.

 


Is this a bug?  Am I missing something?  Thanks.

0 Kudos
8 Replies
DanPatterson
MVP Esteemed Contributor

What were the print results?

Does the other join work?

Add Spatial Join (Data Management)—ArcGIS Pro | Documentation

albeit, it is permanent


... sort of retired...
0 Kudos
Flann
by
Occasional Contributor

I've edited the post to include the output.  First, all 2 features are selected, then 0.  I haven't tried the other tool yet.  I would like to understand if this one is malfunctioning or not.  Thanks.

0 Kudos
DanPatterson
MVP Esteemed Contributor

Select Layer By Attribute (Data Management)—ArcGIS Pro | Documentation

from the python section, there is no assignment of the select layer by attributes to another variable name (line 27), perhaps that is causing the issues downstream


... sort of retired...
0 Kudos
DuncanHornby
MVP Notable Contributor

I can't replicate your problem, how are you running this script?

0 Kudos
Flann
by
Occasional Contributor

I'm running it as a standalone script, executed in VS Code through a cloned conda env from ArcGIS Pro 3.1.3, so Python 3.9.16 and arcpy version 3.1.

0 Kudos
DanPatterson
MVP Esteemed Contributor

did you address the layer assignment thing on line 32, I am not sure that the script knows what layer is what


... sort of retired...
0 Kudos
Flann
by
Occasional Contributor

If I don't rebind layer to the result from SelectLayerByAttribute, then the call to getSelectionSet() will return no selection.  To switch gears a bit, let me tell you a bit more about my actual project.

In my real python toolbox script, I take a feature layer as input and rely on the layer selection to process only certain features.  I will try to provide the important pieces of code so you can see how it works and where it fails.

Starting out, I use a function to define the parameters:

def make_param(disp_name, p_name, data_type, default_val=None, p_type="Required", p_dir="Input"):
    param = arcpy.Parameter(
        displayName=disp_name,
        name=p_name,
        datatype=data_type,
        parameterType=p_type,
        direction=p_dir,
    )
    param.value = default_val
    return param


Then, in the toolbox init, the layer parameter is defined like so:

self.parameters = [
            make_param("AOI Polygon Layer", "in_lyr", "GPFeatureLayer")
]

 

In the tool's execute method, the argument is assigned to a local variable like so:

in_lyr = parameters[0].value

 
Shortly thereafter, I check for a selection and raise an exception if there is none:

# check for selection on AOI layer
        selection = in_lyr.getSelectionSet()
        if not selection:
            arcpy.AddError("You must select AOI features to evaluate!")
            raise NoSelectionError("There was no selection in the AOI layer")

 

Then, I open a search cursor on the layer and create a dictionary that will be used to store the results from other tools:

with arcpy.da.SearchCursor(in_lyr, ["AOI_ID"]) as cursor:
            result = {row[0]: {"AOI_ID": row[0]} for row in cursor}

 

At this point, I run spatial join because later steps depend on its output.  You will notice at the end that I've now included a line to restore the selection back to its original state after this tool is run.

        # spatial join to check for overlaps in the AOI polygons if zonal stats to be used
        # creating the spatial joined feature class now, otherwise it might be done repeatedly
        if option_sl_score or option_gi_scores or option_canopy_cover:
            arcpy.AddMessage("Checking for overlap in input AOIs ...")
            arcpy.analysis.SpatialJoin(
                in_lyr,
                in_lyr,
                "out_spatial_join",
                "JOIN_ONE_TO_MANY",
                match_option="WITHIN_A_DISTANCE",
                search_radius=-0.1,
            )
            # spatial join clears layer selection, so we restore it
            in_lyr.setSelectionSet(list(selection))

 

If line 14 above is omitted, the selection in the layer will be cleared and the code breaks downstream because I've only started gathering results for a selection of features.  For example:

            arcpy.TabulateIntersection_analysis(
                in_zone_features=in_lyr,
                zone_fields="AOI_ID",
                in_class_features=wmo_bdry,
                out_table="wmo_tab",
                class_fields="NAME_TXT",
            )

            wmo_flds = ["AOI_ID", "NAME_TXT", "PERCENTAGE"]
            wmo_d = {"3": "MCWD", "6": "MWMO", "7": "BCWMC", "8": "SCWMC"}

            # store results in result dict

            with arcpy.da.SearchCursor("wmo_tab", wmo_flds) as cursor:
                for row in cursor:
                    aoi_id, wmo_id, perc = row

                    wmo = wmo_d.get(wmo_id, "OTHER")
                    pct_fld = f"PCT_{wmo}"

                    result[aoi_id][pct_fld] = round(perc, 2)

 

If the selection is not restored, tabulate intersection will operate on all features and the tool will fail with a KeyError exception because the aoi_id does not exist in the result dictionary.  Of course, this problem can be resolved with some tweaks to the script, but my reason for posting is to either figure out if I am using the layer object from the parameter inappropriately or to determine that this is indeed a bug in arcpy.

0 Kudos
DanPatterson
MVP Esteemed Contributor

setSelectionSet

Table—ArcGIS Pro | Documentation

This method provides an easy way to manage a table's selection. To clear the selection, use the NEW selection method with an empty list or don't set any parameters. Python lists are used for the oidList, but sets get returned from the getSelectionSet method on the Table object.

So you can confirm "selection" is > 0 (?) and you seem to have set it to a list appropriately (as per the help)


... sort of retired...
0 Kudos