Nested cursors problem/question

877
10
Jump to solution
02-09-2014 02:49 AM
GrljAles
Occasional Contributor
Hi,

I'm trying to write a script, that will select certain polygons according to their position in relation to some other polygons. The idea is to use a cursor in one table to select polygons with aspect ratio equal or more than two. When such polygon is selected an identifier is written to memory and select layer by location tool triggers with this polygon as selection feature. After that another cursor attempts to find which features were selected by polygon above in the select layer by location tool's input feature layer.The purpose of this cursor is that adds a value of 1 and FID of the selecting polygon to the selected polygon's  attribure table.

The script works for 1 values but it only adds one FID (119).

Could some one take a look and tell me what is missing?

depressions = depressionsLocation + "depressions.shp" selectingBuffer = depressionsLocation + "dissolvedBuffers.shp" wasSelectedField = ('SELECTED', 'SELECTOR') isSelectorField = ('ASPECT_R', 'ID')  try:     arcpy.MakeFeatureLayer_management(depressions, "depressionsLayer")       arcpy.MakeFeatureLayer_management(selectingBuffer, "selectingBufferLayer")  except:     print "Could not create feature layers"        try:           with arcpy.da.UpdateCursor("selectingBufferLayer", isSelectorField) as cursor:         for row in cursor:             if (row[0] >= 2):                 oid = row[1]                 arcpy.SelectLayerByLocation_management("depressionsLayer", "INTERSECT", "selectingBufferLayer")                 with arcpy.da.UpdateCursor("depressionsLayer", wasSelectedField) as cursor:                     for row in cursor:                         row[0] = "True"                         row[0] = "1"                         row[1] = oid                         cursor.updateRow(row) finally:      arcpy.Delete_management("selectingBufferLayer")     arcpy.Delete_management("depressionsLayer")
Tags (2)
0 Kudos
1 Solution

Accepted Solutions
RichardFairhurst
MVP Honored Contributor
Ok, thanks for that, it helped a little. I also changed some other parts of code however the problem persists and FIDs are all 0 now.

Here is what I changed:
try:           with arcpy.da.UpdateCursor("selectingBufferLayer", isSelectorField) as cursor:         for row in cursor:             arcpy.SelectLayerByAttribute_management("selectingBufferLayer", "NEW_SELECTION", """ "ASPECT_R" >= 2 """)             oid = int(row[1])             print oid                              arcpy.SelectLayerByLocation_management("depressionsLayer", "INTERSECT", "selectingBufferLayer")             with arcpy.da.UpdateCursor("depressionsLayer", wasSelectedField) as selcursor:                 for selrow in selcursor:                     selrow[0] = "1"                     selrow[1] = oid                     print selrow[1]                     selcursor.updateRow(selrow)                 #del selcursor, selrow                              arcpy.DeleteRows_management("selectingBufferLayer")                   del cursor, row finally:      if arcpy.Exists("depressionsLayer"): arcpy.Delete_management("depressionsLayer")     if arcpy.Exists("selectingBufferLayer"): arcpy.Delete_management("selectingBufferLayer")


I don't think you can set things up this way.  You seem to want to have multiple features selected to begin with, but then only use one feature at a time on the Select By Location.  That is definitely not what your code is doing.  You need a separate layer from the one with the full selection set to operate on the buffers with the restriction of the single OID you want to process. 

Why do you want to delete the rows in the buffer data?  That makes no sense.  You just wrote attributes to them.  You are missing a step if you intend to delete the buffers you are writing attributes to.

My pseudo code would be:

Get input layer from current map with selection.

Create the inmemory layers like you originally did using the same source shapefile as the one in the map so you can process one feature at a time provided by the cursor without messing up the record selection the cursor is reading.  Or else create the selection the cursor is reading and operate on the cursor feature in two separate layers pointing to the one shapefile.

Process the cursor on the map input selection layer.

Do an if clause within the cursor loop to verify the selected feature the cursor is reading has ASPECT_R >= 2 and only do the following steps when it meets that criteria, otherwise skip that feature.

For each feature the cursor reads that qualifies do a Select by Attribute on the inmemory layer where ID = the OID of the row the cursor is operating on.

Use that inmemory layer as the layer that selects buffer features using the Select By Location.

Now the selected buffers actually were selected by the one feature the cursor is operating on, so write the OID value to it.

Loop repeats for each qualifying feature until done.  Code ends and cleans up everything, so don't delete your inmemory layers until all loops are done.  Layers don't need to be destroyed and recreated to process multiple selection operations.

View solution in original post

0 Kudos
10 Replies
RichardFairhurst
MVP Honored Contributor
Hi,

I'm trying to write a script, that will select certain polygons according to their position in relation to some other polygons. The idea is to use a cursor in one table to select polygons with aspect ratio equal or more than two. When such polygon is selected an identifier is written to memory and select layer by location tool triggers with this polygon as selection feature. After that another cursor attempts to find which features were selected by polygon above in the select layer by location tool's input feature layer.The purpose of this cursor is that adds a value of 1 and FID of the selecting polygon to the selected polygon's  attribure table.

The script works for 1 values but it only adds one FID (119).

Could some one take a look and tell me what is missing?

depressions = depressionsLocation + "depressions.shp"
selectingBuffer = depressionsLocation + "dissolvedBuffers.shp"
wasSelectedField = ('SELECTED', 'SELECTOR')
isSelectorField = ('ASPECT_R', 'ID')

try:
    arcpy.MakeFeatureLayer_management(depressions, "depressionsLayer")
 
    arcpy.MakeFeatureLayer_management(selectingBuffer, "selectingBufferLayer")

except:
    print "Could not create feature layers"
    


try:      
    with arcpy.da.UpdateCursor("selectingBufferLayer", isSelectorField) as cursor:
        for row in cursor:
            if (row[0] >= 2):
                oid = row[1]
                arcpy.SelectLayerByLocation_management("depressionsLayer", "INTERSECT", "selectingBufferLayer")
                with arcpy.da.UpdateCursor("depressionsLayer", wasSelectedField) as cursor:
                    for row in cursor:
                        row[0] = "True"
                        row[0] = "1"
                        row[1] = oid
                        cursor.updateRow(row)
finally: 
    arcpy.Delete_management("selectingBufferLayer")
    arcpy.Delete_management("depressionsLayer")


You are destroying and overwriting your outer cursor with the inner loop rather than managing two cursors, because you use the same variable names within the loop that were used for the outer loop.  In the outer loop use cursor and row and for the inner loop use selCursor and selRow and most of your problems should disappear.  Here is the revised inner loop:

                with arcpy.da.UpdateCursor("depressionsLayer", wasSelectedField) as selCursor:
                    for selRow in selCursor:
                        selRow[0] = "True"
                        selRow[0] = "1"
                        selRow[1] = oid
                        selCursor.updateRow(selRow)
0 Kudos
RichardFairhurst
MVP Honored Contributor
Bump to make this the primary thread.
0 Kudos
GrljAles
Occasional Contributor
Ok, thanks for that, it helped a little. I also changed some other parts of code however the problem persists and FIDs are all 0 now.

Here is what I changed:
try:      
    with arcpy.da.UpdateCursor("selectingBufferLayer", isSelectorField) as cursor:
        for row in cursor:
            arcpy.SelectLayerByAttribute_management("selectingBufferLayer", "NEW_SELECTION", """ "ASPECT_R" >= 2 """)
            oid = int(row[1])
            print oid
                
            arcpy.SelectLayerByLocation_management("depressionsLayer", "INTERSECT", "selectingBufferLayer")
            with arcpy.da.UpdateCursor("depressionsLayer", wasSelectedField) as selcursor:
                for selrow in selcursor:
                    selrow[0] = "1"
                    selrow[1] = oid
                    print selrow[1]
                    selcursor.updateRow(selrow)
                #del selcursor, selrow
                
            arcpy.DeleteRows_management("selectingBufferLayer")
         
        del cursor, row
finally: 
    if arcpy.Exists("depressionsLayer"): arcpy.Delete_management("depressionsLayer")
    if arcpy.Exists("selectingBufferLayer"): arcpy.Delete_management("selectingBufferLayer")
0 Kudos
RichardFairhurst
MVP Honored Contributor
Ok, thanks for that, it helped a little. I also changed some other parts of code however the problem persists and FIDs are all 0 now.

Here is what I changed:
try:           with arcpy.da.UpdateCursor("selectingBufferLayer", isSelectorField) as cursor:         for row in cursor:             arcpy.SelectLayerByAttribute_management("selectingBufferLayer", "NEW_SELECTION", """ "ASPECT_R" >= 2 """)             oid = int(row[1])             print oid                              arcpy.SelectLayerByLocation_management("depressionsLayer", "INTERSECT", "selectingBufferLayer")             with arcpy.da.UpdateCursor("depressionsLayer", wasSelectedField) as selcursor:                 for selrow in selcursor:                     selrow[0] = "1"                     selrow[1] = oid                     print selrow[1]                     selcursor.updateRow(selrow)                 #del selcursor, selrow                              arcpy.DeleteRows_management("selectingBufferLayer")                   del cursor, row finally:      if arcpy.Exists("depressionsLayer"): arcpy.Delete_management("depressionsLayer")     if arcpy.Exists("selectingBufferLayer"): arcpy.Delete_management("selectingBufferLayer")


I don't think you can set things up this way.  You seem to want to have multiple features selected to begin with, but then only use one feature at a time on the Select By Location.  That is definitely not what your code is doing.  You need a separate layer from the one with the full selection set to operate on the buffers with the restriction of the single OID you want to process. 

Why do you want to delete the rows in the buffer data?  That makes no sense.  You just wrote attributes to them.  You are missing a step if you intend to delete the buffers you are writing attributes to.

My pseudo code would be:

Get input layer from current map with selection.

Create the inmemory layers like you originally did using the same source shapefile as the one in the map so you can process one feature at a time provided by the cursor without messing up the record selection the cursor is reading.  Or else create the selection the cursor is reading and operate on the cursor feature in two separate layers pointing to the one shapefile.

Process the cursor on the map input selection layer.

Do an if clause within the cursor loop to verify the selected feature the cursor is reading has ASPECT_R >= 2 and only do the following steps when it meets that criteria, otherwise skip that feature.

For each feature the cursor reads that qualifies do a Select by Attribute on the inmemory layer where ID = the OID of the row the cursor is operating on.

Use that inmemory layer as the layer that selects buffer features using the Select By Location.

Now the selected buffers actually were selected by the one feature the cursor is operating on, so write the OID value to it.

Loop repeats for each qualifying feature until done.  Code ends and cleans up everything, so don't delete your inmemory layers until all loops are done.  Layers don't need to be destroyed and recreated to process multiple selection operations.
0 Kudos
GrljAles
Occasional Contributor
Well I'm quite new to working with Python and arcpy. I have an idea of how this should work but lack knowledge to tell it to the computer. What you said in the first paragraph is true; I want to work only with elongated polygons and then select one at a time to perform select layer by location.

But let me explain a little more. The original input data for this script is a polygon feature class of karst depressions. Using minimum bounding geometry I obtained orientation and aspect ratio of this depressions. Then I converted polygons to points and created line features starting at polygon center, pointing in polygon's orientation and with length (polygon length / 2 + 100). At the tips of these lines I created buffers to act as a sort of sensors that will detect if there is another depredssion.

So both buffers and depressions have matching attributes (buffer has the same FID as the depression it was created from). That means I could (if I knew how) select the depression that intersects with buffer and the depression that the same buffer came from. However I changed my plan a bit yesterday; now I would like to append selected features to a new feature class instead of writing attributes. I believe in this case the inner loop should have insert cursor?

I think I made some progress in that direction. Below is the new code though I have not tried to implement the exporting part the selection should be working:

try:
    arcpy.MakeFeatureLayer_management(depressions, "depressionsLayer")
    arcpy.MakeFeatureLayer_management(selectingBuffer, "selectingBufferLayer")

except:
    print "Could not create feature layers"

try:      
    with arcpy.da.UpdateCursor("selectingBufferLayer", isSelectorField) as cursor:
        for row in cursor:
            fid = int(row[0])
            oid = int(row[2])
            if (row[1] >= 2):
                arcpy.SelectLayerByAttribute_management("selectingBufferLayer", "NEW_SELECTION", '"ID" = {}'.format(oid))
                    
                arcpy.SelectLayerByLocation_management("depressionsLayer", "INTERSECT", "selectingBufferLayer")
                with arcpy.da.UpdateCursor("depressionsLayer", wasSelectedField) as selcursor:
                    for selrow in selcursor:
                        arcpy.SelectLayerByAttribute_management("depressionsLayer", "ADD_TO_SELECTION", '"ID" = {}'.format(oid))
                        selrow[0] = "1"
                        selcursor.updateRow(selrow)
                        selrow[1] = oid
                        print "This is selrow[1] - " + str(selrow[1]) + " and " + str(oid) + " selected it." 
                        selcursor.updateRow(selrow)
                        break
                    del selcursor, selrow
            else:
                cursor.next()
        del cursor, row
finally: 
    if arcpy.Exists("depressionsLayer"): arcpy.Delete_management("depressionsLayer")
    if arcpy.Exists("selectingBufferLayer"): arcpy.Delete_management("selectingBufferLayer")



I really appreciate your help rfairhur24!!!
0 Kudos
RichardFairhurst
MVP Honored Contributor
Your last response tells me that you should give the Spatial Join tool a try first before doing any more programming.  If it gets you 80% or 90% of the way there you may not need most of this code.  I would do the One To Many relationship first.  You can select out the self intersecting features that way and just include the touched outer depressions.  This option keeps the FIDs of the input and output.

If you use the One To One option calculate the value you want grouped into a new long field if it is the OID values, since ObjectID is dropped in this mode.  Then use the merge properties to make that field a Join field and change the output type to string of length 254 and put in a delimiter of "; " or something you like.  Then that will create a list of OID values.  You could strip out the OID for self intersects if you like with a calculation so it only includes the OID values of the surrounding buffers.  Only the calculation might need python (although I personally would use VB Script).  Anyway seee how far the Spatial Jopin tool gets you toward your goal.  That will give us both a better frame of reference for what you want as opposed to what that tool does.
0 Kudos
GrljAles
Occasional Contributor
Hmmmm, actually I was alredy thinking about this, before I decided to try with cursors but somehow I could not make such logical conclusion... Will definitley try this.

However in the meanwhile I came up with something. It works quite fine except for a few polygons. I do not know why but while checking the result I found some polygons that should have been selected (exported) but were not.

I tested it also on second area and it returned strange error message:

Traceback (most recent call last):
  File "LINK TO THE SCRIPT", line 235, in <module>
    cursor.next()
StopIteration

Any idea what it means?

Here is the last code:

spatialReference = arcpy.Describe(depressionsLocation + depressionsName).spatialReference

arcpy.CreateFeatureclass_management(depressionsLocation, unroofedCaves, "POLYGON", depressionsLocation + depressionsName, "SAME_AS_TEMPLATE", "DISABLED", spatialReference)


depressions = depressionsLocation + "depressions.shp"
selectingBuffer = depressionsLocation + "dissolvedBuffers.shp"
wasSelectedField = ('ID')
isSelectorField = ('ASPECT_R', 'ID')


try:
    arcpy.MakeFeatureLayer_management(depressions, "depressionsLayer")
    arcpy.MakeFeatureLayer_management(selectingBuffer, "selectingBufferLayer")

except:
    print "Could not create feature layers"

try:      
    with arcpy.da.SearchCursor("selectingBufferLayer", isSelectorField) as cursor:
        for row in cursor:
            oid = int(row[1])
            if (row[0] >= 2):
                arcpy.SelectLayerByAttribute_management("selectingBufferLayer", "NEW_SELECTION", '"ID" = {}'.format(oid))    
                arcpy.SelectLayerByLocation_management("depressionsLayer", "INTERSECT", "selectingBufferLayer")
                with arcpy.da.SearchCursor("depressionsLayer", wasSelectedField) as selcursor:
                    for selrow in selcursor:
                        arcpy.SelectLayerByAttribute_management("depressionsLayer", "ADD_TO_SELECTION", '"ID" = {}'.format(oid))
                        arcpy.Append_management("depressionsLayer", depressionsLocation + unroofedCaves, "NO_TEST", "", "")
                        print "Exporting "
                        break
                    #del selcursor, selrow
            else:
                cursor.next()
        del cursor, row
finally: 
    if arcpy.Exists("depressionsLayer"): arcpy.Delete_management("depressionsLayer")
    if arcpy.Exists("selectingBufferLayer"): arcpy.Delete_management("selectingBufferLayer")
0 Kudos
RichardFairhurst
MVP Honored Contributor
There is no cursor.next() command and it is not necessary.  Just eliminate the else clause, since it is not needed and let the cursor progress to the next record without doing anything.  If it does not qualify for the if condition the record does nothing by default.  So I commented out the offending code you don't need.

I think the skipped over polygons occur because you are doing the search for ID on the same layer that your cursor is processing.  Make a second search layer and use the cursor on one and the Select By Attribute on the other.

Hmmmm, actually I was alredy thinking about this, before I decided to try with cursors but somehow I could not make such logical conclusion... Will definitley try this.

However in the meanwhile I came up with something. It works quite fine except for a few polygons. I do not know why but while checking the result I found some polygons that should have been selected (exported) but were not.

I tested it also on second area and it returned strange error message:

Traceback (most recent call last):
  File "LINK TO THE SCRIPT", line 235, in <module>
    cursor.next()
StopIteration

Any idea what it means?

Here is the last code:

spatialReference = arcpy.Describe(depressionsLocation + depressionsName).spatialReference

arcpy.CreateFeatureclass_management(depressionsLocation, unroofedCaves, "POLYGON", depressionsLocation + depressionsName, "SAME_AS_TEMPLATE", "DISABLED", spatialReference)


depressions = depressionsLocation + "depressions.shp"
selectingBuffer = depressionsLocation + "dissolvedBuffers.shp"
wasSelectedField = ('ID')
isSelectorField = ('ASPECT_R', 'ID')


try:
    arcpy.MakeFeatureLayer_management(depressions, "depressionsLayer")
    arcpy.MakeFeatureLayer_management(selectingBuffer, "selectingBufferLayer")
    arcpy.MakeFeatureLayer_management(selectingBuffer, "selectingBufferLayer2")
except:
    print "Could not create feature layers"

try:      
    with arcpy.da.SearchCursor("selectingBufferLayer", isSelectorField) as cursor:
        for row in cursor:
            oid = int(row[1])
            if (row[0] >= 2):
                arcpy.SelectLayerByAttribute_management("selectingBufferLayer2", "NEW_SELECTION", '"ID" = {}'.format(oid))    
                arcpy.SelectLayerByLocation_management("depressionsLayer", "INTERSECT", "selectingBufferLayer2")
                with arcpy.da.SearchCursor("depressionsLayer", wasSelectedField) as selcursor:
                    for selrow in selcursor:
                        arcpy.SelectLayerByAttribute_management("depressionsLayer", "ADD_TO_SELECTION", '"ID" = {}'.format(oid))
                        arcpy.Append_management("depressionsLayer", depressionsLocation + unroofedCaves, "NO_TEST", "", "")
                        print "Exporting "
                        break
                    #del selcursor, selrow
#            else:
#                cursor.next()
        del cursor, row
finally: 
    if arcpy.Exists("depressionsLayer"): arcpy.Delete_management("depressionsLayer")
    if arcpy.Exists("selectingBufferLayer"): arcpy.Delete_management("selectingBufferLayer")
    if arcpy.Exists("selectingBufferLayer2"): arcpy.Delete_management("selectingBufferLayer2")
0 Kudos
GrljAles
Occasional Contributor
Hey, sorry for my absence this last days.

The script is finnaly working as it should! Tried also the other method, it worked but since I spent so much time on script I will stick with it.

rfairhur24, thank you for your time, patience and help. I aprecciate it very much and stay tuned for more as I am sure that some more help will be needed in future :).

Aleš out.
0 Kudos