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
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.