4

There is a lot of nested loops and wondering of ways I could speed up this code? Any ideas?

Note: This requires ArcMap 10.2

def cut_geometry(to_cut, cutter):
    """
    Cut a feature by a line, splitting it into two geometries.
    :param to_cut: The feature to cut.
    :param cutter: The polyline to cut the feature by.
    :return: The feature with the split geometry added to it.
    """
    arcpy.AddField_management(to_cut, "SOURCE_OID", "LONG")
    geometries = None
    with arcpy.da.SearchCursor(cutter, "SHAPE@") as lines:
        for line in lines:
            with arcpy.da.UpdateCursor(to_cut, ["SHAPE@", "OID@"]) as polygons:
                for polygon in polygons:
                    if line[0].disjoint(polygon[0]) == False:
                        geometries = polygon[0].cut(line[0])
                        polygons.deleteRow()
                with arcpy.da.InsertCursor(to_cut, ["SHAPE@", "SOURCE_OID"]) as insert_cursor:
                    for geometry in geometries:
                        if geometry.area > 0:
                            insert_cursor.insertRow([geometry, polygon[1]])

UPDATE as per @MichaelMiles-Stimson

I was able to move out the insert_cursor, by adding in code for an edit session. However can't figure out how to pull out the UpdateCursor without losing the functionality, since it's deleting and inserting rows back into the original feature it needs to get a new UpdateCursor each time.

def cut_geometry(to_cut, cutter):
    """
    Cut a feature by a line, splitting it into its separate geometries.
    :param to_cut: The feature to cut.
    :param cutter: The polylines to cut the feature by.
    :return: The feature with the split geometry added to it.
    """
    arcpy.AddField_management(to_cut, "SOURCE_OID", "LONG")
    geometries = None
    polygon = None

    edit = arcpy.da.Editor(os.path.dirname(to_cut))
    edit.startEditing(False, False)

    insert_cursor = arcpy.da.InsertCursor(to_cut, ["SHAPE@", "SOURCE_OID"])

    with arcpy.da.SearchCursor(cutter, "SHAPE@") as lines:
        for line in lines:
            with arcpy.da.UpdateCursor(to_cut, ["SHAPE@", "OID@"]) as polygons:
                for polygon in polygons:
                    if line[0].disjoint(polygon[0]) == False:
                        geometries = polygon[0].cut(line[0])
                        polygons.deleteRow()
                for geometry in geometries:
                    if geometry.area > 0:
                        insert_cursor.insertRow([geometry, polygon[1]])

    edit.stopEditing(True)
  • What data type are the to_cut and cutter layers (shp, FileGDB feature class...etc)? Script may perform faster on a FileGDB feature class compared to Shapefiles. – artwork21 Dec 2 '14 at 18:51
  • 1
    I would think that creating and dismissing your insert and update cursor every time would be a bit of an overhead. Consider moving it to just before/after the search cursor, which you can change to update cursor - you can iterate through an update cursor like a search cursor (but not an insert cursor). This alleviates the overhead of creating two cursors for each row and then removing them immediately. – Michael Stimson Dec 3 '14 at 6:27
  • @MichaelMiles-Stimson Good point thanks, I'll give it a shot. – Tristan Forward Dec 3 '14 at 16:10
  • @MichaelMiles-Stimson See Edit above – Tristan Forward Dec 3 '14 at 17:22
  • You're right. You cannot move the update cursor outside the loop or it wont find the new features. That's about as good as you can get it without using intersect or .net. As my answer was incorrect I have removed it. – Michael Stimson Dec 4 '14 at 22:39
2

[UPDATED TO HANDLE INTERSECTING LINES]

Can also be download as a toolbox here: https://github.com/tforward/CutPolygonByLines

Split Polygon on Lines

import os

def cut_geometry(to_cut, cutter):
    """
    Cut a feature by a line, splitting it into its separate geometries.
    :param to_cut: The feature to cut.
    :param cutter: The polylines to cut the feature by.
    :return: The feature with the split geometry added to it.
    """
    arcpy.AddField_management(to_cut, "SOURCE_OID", "LONG")
    geometries = []
    polygon = None

    edit = arcpy.da.Editor(os.path.dirname(to_cut))
    edit.startEditing(False, False)

    insert_cursor = arcpy.da.InsertCursor(to_cut, ["SHAPE@", "SOURCE_OID"])

    with arcpy.da.SearchCursor(cutter, "SHAPE@") as lines:
        for line in lines:
            with arcpy.da.UpdateCursor(to_cut, ["SHAPE@", "OID@", "SOURCE_OID"]) as polygons:
                for polygon in polygons:
                    if line[0].disjoint(polygon[0]) == False:
                        if polygon[2] == None:
                            id = polygon[1]
                        # Remove previous geom if additional cuts are needed for intersecting lines
                        if len(geometries) > 1:
                            del geometries[0] 
                        geometries.append([polygon[0].cut(line[0]), id])
                        polygons.deleteRow()
                for geometryList in geometries:
                    for geometry in geometryList[0]:
                        if geometry.area > 0:
                            insert_cursor.insertRow([geometry, geometryList[1]])

    edit.stopEditing(True)


cut_geometry(r"PATH_TO_POLY", r"PATH_TO_LINES")
1

One way to speed up your code is to use a dictionary. Iterating the dictionary is faster than the cursor even when you include loading the data into the dictionary from the search cursor. I did a test run on a single polygon to be cut by a single line and the difference in time was 0.74 seconds for cursor vs 0.39 seconds for the dictionary.

def cut_geometry(to_cut, cutter):
    """
    Cut a feature by a line, splitting it into two geometries.
    :param to_cut: The feature to cut.
    :param cutter: The polyline to cut the feature by.
    :return: The feature with the split geometry added to it.
    """
    print str(datetime.datetime.now())
    arcpy.AddField_management(to_cut, "SOURCE_OID", "LONG")
    geometries = None

    dictCutter = {}
    with arcpy.da.SearchCursor(cutter, ["SHAPE@", "OID@"]) as lines:
        for line in lines:
            dictCutter[line[1]] = line[0]

    for k, v in dictCutter.items():
        with arcpy.da.UpdateCursor(to_cut, ["SHAPE@", "OID@"]) as polygons:
            for polygon in polygons:
                if v.disjoint(polygon[0]) == False:
                    geometries = polygon[0].cut(v)
                    polygons.deleteRow()
            with arcpy.da.InsertCursor(to_cut, ["SHAPE@", "SOURCE_OID"]) as insert_cursor:
                for geometry in geometries:
                    if geometry.area > 0:
                        insert_cursor.insertRow([geometry, polygon[1]])
    print str(datetime.datetime.now())
  • Interesting method. I'm not convinced however. Can you test it using more lines. Like 6. I did so and got these results Dictionary [4 secs],[5 secs],[3 secs] Cursor [3 secs], [4 secs], [3 secs] – Tristan Forward Dec 5 '14 at 18:26
  • I tried with 6 lines and the improvement with a dictionary is neglible. I looked back at some old code where I used the dictionary in place of a cursor and I see that it was done to simulate a lookup table to avoid using a double cursor or a join, which were quite slower. Your situation is different and doesn't seem to benefit from using a dictionary. Also, I tested with very simple lines with few vertices so that may have an effect as well. – Oli Dec 6 '14 at 6:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.