0

I have managed to write the following code where I am looping through features of "Source Layer", getting its primary and foreign key. Then selecting that feature using primary key from "Source Layer" and selecting the feature using foreign key in "Target Layer".

Now I want to calculate the distance between these two geometries regardless of their type and print.

import arcpy
Source_Layer = "Data1\\Asset_2"
Target_Layer = "FL1\\Road_FL"
with arcpy.da.SearchCursor(SL,['Asset_ID','FL_ID']) as cur:
   for rows in cur:       
    AssetID = rows[0]
    FLID = rows[1]        
    where_clause = "Asset_ID = {}".format(rows[0])
    where_clause2 = "ID = '" +  str(FLID)+ "'"
    Source_Feature = arcpy.management.SelectLayerByAttribute(SL,'New_Selection', where_clause)
    Target_Feature = arcpy.management.SelectLayerByAttribute(TL,'New_Selection', where_clause2)
    # Distance = (????????????) # calculate distance here
    print (AssetID, FLID, Distance)
4
  • Nesting reselection inside a cursor using that layer is not a great plan
    – Vince
    Commented Mar 2, 2022 at 3:17
  • I know this process is will be Slow and resource hungry but I don't know any other way. Open to suggestions.
    – Abdul Rauf
    Commented Mar 2, 2022 at 3:25
  • Also Target Layer has one to many relation with Source Layer. Since I need the distance for each feature hence above code.
    – Abdul Rauf
    Commented Mar 2, 2022 at 3:27
  • 1
    It's not the slow that matters so much as the segmentation violations that crash the Python binary. Do not do this. The Near command does it so much better. If you have to use Python, just load all the objects in memory and work it as a pair of nested loops.
    – Vince
    Commented Mar 2, 2022 at 4:20

3 Answers 3

1

You can use the geometry access token to get the geometry of both features and use distanceTo method to get their didtance

import arcpy
SL = "Data1\\Asset_2"
TL = "FL1\\Road_FL"
with arcpy.da.SearchCursor(SL,['Asset_ID','FL_ID', 'SHAPE@']) as scur:
   for rows in scur: 
    geom1 = row[2]      
    AssetID = rows[0]
    FLID = rows[1]        
    where_clause = "ID = '" +  str(FLID)+ "'"
    with arcpy.da.SearchCursor(TL,['ID', 'SHAPE@'], where_clause) as tcur:
        feat2 = tcur.next()
        geom2 = feat2[1]
        Distance = geom1.distanceTo(geom2) # calculate distance here
        print (AssetID, FLID, Distance)

3
  • 1
    Nested cursors are bad juju. Read the smaller table once, then iterate the objects inside a single cursor against the larger table.
    – Vince
    Commented Mar 2, 2022 at 4:23
  • I'm just asking. Can you share why nested cursors are bad in this case? I thought since they're of different data they shouldn't interfere with each other. @Vince
    – Ahmad Raji
    Commented Mar 2, 2022 at 4:38
  • Reading takes work. Never do more work than you have to. That's especially true with repeating the same work
    – Vince
    Commented Mar 2, 2022 at 4:52
0

Modified Ahmad's code as below and it works like a charm.

import arcpy
SL = "Data1\\Asset_2"
TL = "FL1\\Road_FL"
with arcpy.da.SearchCursor(SL,['Asset_ID','FL_ID', 'SHAPE@']) as scur:
    for rows in scur: 
    geom1 = rows[2]      
    AssetID = rows[0]
    FLID = rows[1]        
    where_clause = "ID = '" +  str(FLID)+ "'"
    with arcpy.da.SearchCursor(TL,['ID', 'SHAPE@'], where_clause) as tcur:
        for rowt in tcur:
        geom2 = rowt[1]
        Distance = geom1.distanceTo(geom2) # calculate distance here
        print (AssetID, FLID, Distance)

Next will work on Vince suggestion, instead of going 1to1 will go for 1toMany nested cursor to reduce the number of selections.

1
  • 2
    Your indent scheme here is a syntax error. You should always use four spaces, both after with and after for. Technically, you can use any scheme you want, but PEP8 says you should only want four spaces.
    – Vince
    Commented Mar 2, 2022 at 12:05
0

As other users have mentioned, it's generally considered bad form to have nested cursors, and definitely a bad idea to have selections inside an active cursor (same reason you don't modify a list while you're iterating over it). To avoid that you can loop once to load the data into a convenient data structure, then read from that during your second loop. Added benefits to doing it this way are improved readability and easier debugging.

A convenient structure when reading your source layer might be a dictionary like:

{
    TargetID: [(SourceID, SourceGeometry), (SourceID, SourceGeometry), ...]
}

We could construct it using defaultdict(lambda: []) so that each value is automatically a list:

import arcpy
from collections import defaultdict

source_Layer = "Data1\\Asset_2"
target_Layer = "FL1\\Road_FL"

source_dict = defaultdict(lambda: [])
with arcpy.da.SearchCursor(source_layer, ['Asset_ID', 'FL_ID', 'SHAPE@']) as cursor:
    for row in cursor:
        source_id = row[0]
        target_id = row[1]
        source_geom = row[2]
        source_dict[target_id].append((source_id, source_geom))

Now that we have all the data in source_dict, we can iterate over the target layer and print the results.

with arcpy.da.SearchCursor(target_layer, ['ID', 'SHAPE@']) as cursor:
    for row in cursor:
        target_id = row[0]
        target_geom = row[1]
        
        for source_id, source_geom in source_dict[target_geom]:
            dist = target_geom.distanceTo(source_geom)
            print(source_id, target_id, dist)
        

P.S. I assumed based on one of your comments that you have a one (target) to many (source) relationship. But if it's the other way around you just need to change up how you read/write to source_dict

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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