Select to view content in your preferred language

Alternative libraries to arcpy to make the "find conntected" function?

125
1
a week ago
TeresaBartolomei
Emerging Contributor

Hi, 

I use ArcGIS Desktop and I would like to build with python the function behind the standard GIS tool "find connected", which is able to select all the feature topologically connected to each other, starting from a given line feature.

I do not need the arcpy.TraceGeometricNetwork function because even though it really gives you all the feature connected to each other, in doing so, it creates other layers, different from the original and I am looking for a solution that selects features inside their original layers.

Does anyone know any other python library that could have this type of funtion related no networks?

Thanks in advance

1 Reply
HaydenWelch
MVP Regular Contributor

You can implement it yourself using Cursors:

from pathlib import Path
from arcpy import (
    Parameter,
    Polyline,
    Geometry,
)
from arcpy.da import (
    SearchCursor,
)
from arcpy._mp import (
    Layer
)

def find_connected(line: Polyline, feature_classes: list[str|Layer]) -> dict[str, list[int]]:
    """Find all features in each feature class that are connected to the input line
    
    Args:
        line (Polyline): The connecting line that will be used to filter the input features
        feature_classes (list[str|Layer]): A list of paths or Layers that will be searched for connected features
    
    Returns:
        ( dict[str, list[int]] ): A mapping of feature names to connected feature OIDs
    """
    connected = {}
    for fc in feature_classes:
        if isinstance(fc, Layer):
            name = fc.name
        else:
            name = Path(fc).name
            
        connected[name] = [row[0] for row in SearchCursor(fc, ['OID@'], spatial_filter=line, spatial_relationship='INTERSECTS')]
    return connected

 

Here's a more modular version if you don't need all the OIDs immediately (say you're just gonna loop over them later):

def find_connected(line: Polyline, feature_class: str|Layer) -> Generator[int, None, None]:
    """Find all features in each feature class that are connected to the input line
    
    Args:
        line (Polyline): The connecting line that will be used to filter the input features
        feature_class (str|Layer): The feature class to find connections in
    
    Yields:
        ( int ): Consume this generator 
    """
    yield from (
        row[0] 
        for row in SearchCursor(
            feature_class, 
            ['OID@'], 
            spatial_filter=line, 
            spatial_relationship='INTERSECTS'
            )
        )

def get_connected_for(line: Polyline, feature_classes: list[str|Layer], as_list: bool=True) -> dict[str, list[int]]:
    """Get a mapping of connected features for multiple feature classes
    
    Args:
        line (Polyline) : The connecting line
        feature_classes (list[str|Layer]) : The feature classes to find connections with
        as_list (bool): Flag for returning a sequence of generators or populated lists
    Returns:
        (dict[str, list[int]]) : A mapping of the input fcs to the OIDs of the connected features
    """
    return {
        fc if isinstance(fc, str) else fc.longName: list(find_connected(line, fc)) if as_list else find_connected(line, fc)
        for fc in feature_classes
    }

 

The first function makes a generator for the supplied connector and feature class, the second will map a sequence of feature classes to either a list of the connected OIDs or to a generator object that can be iterated over one at a time. If you have a ton of connections per line, the generator solution will be a lot more memory efficient, but if you know the upper bound for the number of connections is small (<10 or so) immediately converting that connection generator to a list will be more memory efficient

Here's a quick sample of the size (in bytes) of both calls with ~2-3 connections each:

>>> cxns = get_connected_for(line, ['fc1', 'fc2'], as_list=False)
>>> cxns
{'fc1': <generator object find_connected at 0x000002504CC24400>, 'fc2': <generator object find_connected at 0x000002507BBEBA60>}
>>> getsize(cxns)
1292

...

>>> cxns = get_connected_for(line, ['fc1', 'fc2'])
>>> cxns
{'fc1': [1, 2], 'fc2': [1, 2, 3]}
>>> getsize(cxns)
564

Note: The first example is larger because the generators are a fixed size. That means even if there are a million connections in there, it'll still only be 1292 bytes.