Is there a way to use a spatial selection to populate fields in another layer via python?

905
3
10-16-2019 09:57 AM
RPGIS
by
Occasional Contributor III

Hi,

I have a script where I spatially select another layer, and the selected records in that layer are then used to populate the fields in the layer that was used for the spatial selection. So specifically I am trying to transfer attributes, but I did some research and the only concern with scripting the Transfer Attributes is the additional field that may get added. I believe I could possibly delete the field to fix that issue but I am unfortunately working in an sde database and I don't want to risk messing with the schemas. If anyone has another possible solution for this I would be very open to it.

with arcpy.da.UpdateCursor(fc, ["OID@", "LANDDISTRICT", "LANDLOT"], """LANDDISTRICT IS NULL OR LANDLOT IS NULL""") as selected_fc:
            for row in selected_fc:
                
                #Create temp fc copy
                arcpy.MakeFeatureLayer_management(fc, r"in_memory\fcLayer")

                #Select layer by attribute
                arcpy.SelectLayerByAttribute_management (r"in_memory\fcLayer", "NEW_SELECTION", "OBJECTID = {}".format(row[0]))

                #Select layer by location
                arcpy.SelectLayerByLocation_management(r"in_memory\DLL_layer", "INTERSECT", r"in_memory\fcLayer", '', "NEW_SELECTION")
                

                #If selected district and landlot in row are null
                if row[1] is None and row[2] is None:
                    print '{} Land District is {} and Land Lot is {} '.format(row[0], row[1], row[2])

                    def DL():

                        for DL in arcpy.da.SearchCursor(r"in_memory\DLL_layer", ["LAND_DIST"]):                 
                            print DL
                        return DL[0]

                    def LL():

                        for LL in arcpy.da.SearchCursor(r"in_memory\DLL_layer", ["LAND_LOT"]):
                            print LL
                        return LL[0]
                    
                    row[1] = DL()
                    row[2] = LL()
                    
                    selected_fc.updateRow(row)
                    print '{} Land District is {} and Land Lot is {} '.format(row[0], row[1], row[2])

I have tried several attempts with this and I either run into the same error or an error referring to something else.

Traceback (most recent call last):
  File "U:\Models_Tools\Scripts related to Landlot and District\Populate Landlot and District Test 2.py", line 65, in <module>
    row[1] = DL()
  File "U:\Models_Tools\Scripts related to Landlot and District\Populate Landlot and District Test 2.py", line 57, in DL
    return DL[0]
UnboundLocalError: local variable 'DL' referenced before assignment
0 Kudos
3 Replies
Arne_Gelfert
Occasional Contributor III

Stumbled over this while trying to refresh my grasp of arcpy cursors. Not entirely sure what you're doing here. But the first thing I notice is that your Update and Search Cursors reference different fields; "LANDLOT" vs "LAND_LOT", and "LANDDISTRICT" vs "LAND_DISTRICT". So DL never gets assigned anything. So when you call DL[0], your error screams: 

local variable 'DL' referenced before assignment

There is a lot of info on this kind of error out there, e.g. Understanding UnboundLocalError in Python - Eli Bendersky's website.

MicahBabinski
Occasional Contributor III

Hi Robert,

Is this the same process you were working on here?:

Trouble with selecting features by location using arcpy?

If so, I think you should take a look at Spatial Join. This is really the best/only way to transfer attributes the way you are describing via arcpy. Transfer Attributes is only for transferring attributes between linear features.

Anyways, I believe the workflow I recommended in your previous post is still your best bet. In code it would look something like this:

# set up environment to overwrite output and output to in memory
arcpy.env.overwriteOutput = True
arcpy.env.workspace = "in_memory"

# create feature layer from your DLL feature class
arcpy.MakeFeatureLayer_management(DLL_featureclass, "DLL_Layer")

# loop through your point layers, updating LANDDISTRICT first, then LANDLOT
for fc in fcs_to_update:

    # make feature layer only containing the features with null values
    arcpy.MakeFeatureLayer_management(fc, "temp_layer", "LANDDISTRICT IS NULL")

    # spatial join to output a feature class in memory with the attributes of your polygon dataset
    arcpy.SpatialJoin_analysis("temp_layer", "DLL_Layer", "tempSpatialJoinOutput",
                               "JOIN_ONE_TO_ONE", "KEEP_COMMON", "#", "INTERSECT")

    # join the output back to your temporary layer based on TARGET_FID
    arcpy.AddJoin_management("temp_layer", arcpy.Describe("temp_layer").OIDFieldName,
                             "tempSpatialJoinOutput", "TARGET_FID")

    # get field name to update, since these field names will be qualified now that it is joined
    LANDDISTRICT_field_name = "{}.LANDDISTRICT".format(fc.split("\\")[-1])

    # update the values
    arcpy.CalculateField_management("temp_layer", LANDDISTRICT_field_name,
                                    "!tempSpatialJoinOutput.LANDDISTRICT!", "PYTHON_9.3")

    # same thing, but for LANDLOT field
    arcpy.MakeFeatureLayer_management(fc, "temp_layer", "LANDLOT IS NULL")

    # spatial join to output a feature class in memory with the attributes of your polygon dataset
    arcpy.SpatialJoin_analysis("temp_layer", "DLL_Layer", "tempSpatialJoinOutput",
                               "JOIN_ONE_TO_ONE", "KEEP_COMMON", "#", "INTERSECT")

    # join the output back to your temporary layer based on TARGET_FID
    arcpy.AddJoin_management("temp_layer", arcpy.Describe("temp_layer").OIDFieldName,
                             "tempSpatialJoinOutput", "TARGET_FID")

    # get field name to update, since these field names will be qualified now that it is joined
    LANDLOT_field_name = "{}.LANDLOT".format(fc.split("\\")[-1])

    # update the values
    arcpy.CalculateField_management("temp_layer", LANDDISTRICT_field_name,
                                    "!tempSpatialJoinOutput.LANDLOT!", "PYTHON_9.3")

    # remove the join
    arcpy.RemoveJoin_management("temp_layer")

# cleanup
arcpy.Delete_management("in_memory")
arcpy.Delete_management("temp_layer")

I believe this would be much faster and simpler than any sort of feature-by-feature nested cursor approach.

Micah

0 Kudos
RPGIS
by
Occasional Contributor III

Thanks Micah,

I thought about doing a spatial join but my only concern is that I have a

line feature and, though I haven't fully tested it out, I got a different

result than what I was looking for. Some of the line features cross

multiple polygons and I'm trying to determine how to best go about this.

Some are entirely inside a polygon or cross either two or more polygons. So

I tried to convert the feature to point so I could determine which part of

the polygon either contained the largest portion of the line or at least

it's determine where the middle of the line fell. I looked at other ways

and I think I found a couple but I will give your example a try. I didn't

think about the spatial join after converting to point so that might

definitely be a better solution than what I thought to try or attempt.

0 Kudos