Nesting a While Loop Within a For Loop using arcpy.da.UpdateCursor()

1495
4
Jump to solution
11-29-2022 04:41 PM
daveostrander
Occasional Contributor II

Hi Community,

I am attempting to use the arcpy.Geometry() objects .union() method to update records in a feature class that were appended but now need to be merged with an existing feature where appropriate (governed by a SQL expression). I am using the arcpy.da.UpdateCursor() to perform the updates. I am attempting to do something like the following, but I am getting an "AttributeError: 'Array' object has no attribute 'union'" error.

 

arcpy.MakeFeatureLayer_management(zoning, 'zoning_lyr')

arcpy.management.SelectLayerByAttribute('zoning_lyr', "NEW_SELECTION", "Zoning = 'A'")

selectCount = int(arcpy.GetCount_management('zoning_lyr')[0])


if selectCount >= 2:

    zoningGeom = arcpy.CopyFeatures_management('zoning_lyr',arcpy.Geometry())


    edit.startEditing(False, False)

    edit.startOperation()

    N = 1
    with arcpy.da.UpdateCursor(zoning, "Shape@", "Zoning = 'A'") as cursor:
        for row in cursor:
            if N == selectCount:break
            while N < selectCount:
               row = row[0].union(zoningGeom[N])   # Fails here
               cursor.updateRow([row])
               N += 1

    del row, cursor


    edit.stopOperation()

    edit.stopEditing(True)


    arcpy.Delete_management('zoning_lyr')

 

The code below works without error, except it doesn't run through the whole list of zoningGeom features before moving to the next zoning record in the for loop. Ideally I would have it run through the records in the zoningGeom geometry object, unioning each one to the zoning layer before running out of features to union and exiting both loops.

 

N = 1
with arcpy.da.UpdateCursor(zoning, "Shape@", "Zoning = 'A'") as cursor:
    for row in cursor:
        if N == selectCount:break
        row = row[0].union(zoningGeom[N])
        cursor.updateRow([row])
        N += 1

 

I think I am just missing something fundamental here. Hopefully, someone can swoop in and point me in the right direction or, better yet, offer a solution.

 

Thank you in advance,

 

 

0 Kudos
1 Solution

Accepted Solutions
JohannesLindner
MVP Frequent Contributor

Your first script fails because you forgot the index in the failing line. So you're trying to use the non-existing union method of an array (row) instead of the geometry (row[0]).

But the script would fail there anyway, because arcpy.management.CopyFeatures() does not what you think it does. You seem to want to extract the geometries, but this tool copies a feature class from one location to another.

 

You got the gist of what you need to do:

  1. Extract the geometries
  2. Iterate over the rows with an UpdateCursor
    1. union the current geometry with all extracted geometries
    2. write it into the feature class
    3. optionally: you probably should delete every row after the first one, so you don't get overlapping features.

 

zones = "TestPolygons"  # layer name or fc path
zone_name_field = "TextField"  # name of the field that determines the "sameness"
only_keep_first = True  # only keep the first row of each zone, delete the following rows
zone_names = ["A"]  # Names you want to union
#  You can also do it automatically for every zone name:
#zone_names = {r[0] for r in arcpy.da.SearchCursor(zones, [zone_name_field])}


for zone_name in zone_names:
    sql = f"{zone_name_field} = '{zone_name}'"
    # extract the geometries into a list
    zone_geometries = [r[0] for r in arcpy.da.SearchCursor(zones, ["SHAPE@"], sql)]
    first_row = True
    # iterate over the fc
    with arcpy.da.UpdateCursor(zones, ["SHAPE@"], sql) as cursor:
        for r in cursor:
            if first_row:
                # union the current geometry with every extracted geometry
                geo = r[0]
                for other_geo in zone_geometries:
                    geo = geo.union(other_geo)
                cursor.updateRow([geo])
                # delete the following rows if only_keep_first == True
                first_row = not only_keep_first
            else:
                cursor.deleteRow()

 

Before:

JohannesLindner_0-1669788424910.png

 

With only_keep_first == True:

JohannesLindner_1-1669788802251.png

 

With only_keep_first == False:

JohannesLindner_2-1669789685199.png

 


Have a great day!
Johannes

View solution in original post

4 Replies
DanPatterson
MVP Esteemed Contributor

edit and format your code would be helpful

Code formatting ... the Community Version - Esri Community


... sort of retired...
0 Kudos
daveostrander
Occasional Contributor II

It doesn’t appear to be possible on mobile. I will update/correct  tomorrow. Thank you for the tip.

0 Kudos
JohannesLindner
MVP Frequent Contributor

Your first script fails because you forgot the index in the failing line. So you're trying to use the non-existing union method of an array (row) instead of the geometry (row[0]).

But the script would fail there anyway, because arcpy.management.CopyFeatures() does not what you think it does. You seem to want to extract the geometries, but this tool copies a feature class from one location to another.

 

You got the gist of what you need to do:

  1. Extract the geometries
  2. Iterate over the rows with an UpdateCursor
    1. union the current geometry with all extracted geometries
    2. write it into the feature class
    3. optionally: you probably should delete every row after the first one, so you don't get overlapping features.

 

zones = "TestPolygons"  # layer name or fc path
zone_name_field = "TextField"  # name of the field that determines the "sameness"
only_keep_first = True  # only keep the first row of each zone, delete the following rows
zone_names = ["A"]  # Names you want to union
#  You can also do it automatically for every zone name:
#zone_names = {r[0] for r in arcpy.da.SearchCursor(zones, [zone_name_field])}


for zone_name in zone_names:
    sql = f"{zone_name_field} = '{zone_name}'"
    # extract the geometries into a list
    zone_geometries = [r[0] for r in arcpy.da.SearchCursor(zones, ["SHAPE@"], sql)]
    first_row = True
    # iterate over the fc
    with arcpy.da.UpdateCursor(zones, ["SHAPE@"], sql) as cursor:
        for r in cursor:
            if first_row:
                # union the current geometry with every extracted geometry
                geo = r[0]
                for other_geo in zone_geometries:
                    geo = geo.union(other_geo)
                cursor.updateRow([geo])
                # delete the following rows if only_keep_first == True
                first_row = not only_keep_first
            else:
                cursor.deleteRow()

 

Before:

JohannesLindner_0-1669788424910.png

 

With only_keep_first == True:

JohannesLindner_1-1669788802251.png

 

With only_keep_first == False:

JohannesLindner_2-1669789685199.png

 


Have a great day!
Johannes
daveostrander
Occasional Contributor II

Not only the solution I was after but served on a silver platter!!!

Thank you @JohannesLindner 

0 Kudos