Example #1
0
 def assign_branches_nodes(
     self,
     branches: Optional[gpd.GeoDataFrame] = None,
     nodes: Optional[gpd.GeoDataFrame] = None,
 ):
     """
     Determine and assign branches and nodes as attributes.
     """
     if branches is None or nodes is None:
         branches, nodes = branches_and_nodes(
             self.trace_gdf,
             self.area_gdf,
             self.snap_threshold,
             already_clipped=self.truncate_traces,
             # unary_size_threshold=self.unary_size_threshold,
         )
     else:
         branches = branches.copy()
         nodes = nodes.copy()
     # if self.trace_gdf.crs is not None:
     #     branches.set_crs(self.trace_gdf.crs, inplace=True)
     #     nodes.set_crs(self.trace_gdf.crs, inplace=True)
     self.branch_gdf = branches
     self.node_gdf = nodes
     self.topology_determined = True
     self.branch_data = LineData(
         _line_gdf=self.branch_gdf,
         azimuth_set_ranges=self.azimuth_set_ranges,
         azimuth_set_names=self.azimuth_set_names,
         length_set_ranges=self.branch_length_set_ranges,
         length_set_names=self.branch_length_set_names,
         area_boundary_intersects=self.
         branch_intersects_target_area_boundary,
     )
Example #2
0
def test_branches_and_nodes_troubling():
    """
    Test branches and nodes with known troubling data.
    """
    traces = Helpers.troubling_traces
    areas = Helpers.sample_areas
    snap_threshold = 0.001
    branches, nodes = branches_and_nodes.branches_and_nodes(
        traces, areas, snap_threshold, allowed_loops=10, already_clipped=False)
    assert isinstance(branches, gpd.GeoDataFrame)
    assert isinstance(nodes, gpd.GeoDataFrame)
Example #3
0
def test_branches_and_nodes_regression(traces, areas, snap_threshold,
                                       allowed_loops, already_clipped,
                                       data_regression):
    """
    Test branches and nodes with regression.
    """
    branches, nodes = branches_and_nodes.branches_and_nodes(
        traces, areas, snap_threshold, allowed_loops, already_clipped)

    branches_value_counts = branches[
        general.CONNECTION_COLUMN].value_counts().to_dict()
    nodes_value_counts = nodes[general.CLASS_COLUMN].value_counts().to_dict()

    data_regression.check({**branches_value_counts, **nodes_value_counts})
Example #4
0
def test_with_known_mls_error():
    """
    Test branches_and_nodes with known mls error.
    """
    linestrings = samples.mls_from_these_linestrings_list
    target_area = [box(*MultiLineString(linestrings).bounds)]
    branches, nodes = branches_and_nodes.branches_and_nodes(
        gpd.GeoSeries(linestrings), gpd.GeoSeries(target_area),
        Helpers.snap_threshold)
    for branch in branches.geometry:
        assert EE_branch not in str(branches[CONNECTION_COLUMN])
        assert isinstance(branch, LineString)
        assert branch.is_simple
        assert not branch.is_empty
    for node in nodes.geometry:
        assert isinstance(node, Point)
        assert not node.is_empty
Example #5
0
def populate_sample_cell(
    sample_cell: Polygon,
    sample_cell_area: float,
    traces_sindex: PyGEOSSTRTreeIndex,
    traces: gpd.GeoDataFrame,
    nodes: gpd.GeoDataFrame,
    snap_threshold: float,
    resolve_branches_and_nodes: bool,
) -> Dict[str, float]:
    """
    Take a single grid polygon and populate it with parameters.

    Mauldon determination requires that E-nodes are defined for
    every single sample circle. If correct Mauldon values are
    wanted `resolve_branches_and_nodes` must be passed as True.
    This will result in much longer analysis time.

    """
    _centroid = sample_cell.centroid
    if not isinstance(_centroid, Point):
        raise TypeError("Expected Point centroid.")
    centroid = _centroid
    sample_circle = safe_buffer(centroid, np.sqrt(sample_cell_area) * 1.5)
    sample_circle_area = sample_circle.area
    assert sample_circle_area > 0

    # Choose geometries that are either within the sample_circle or
    # intersect it
    # Use spatial indexing to filter to only spatially relevant traces,
    # traces and nodes
    trace_candidates_idx = spatial_index_intersection(
        traces_sindex, geom_bounds(sample_circle))
    trace_candidates = traces.iloc[trace_candidates_idx]

    assert isinstance(trace_candidates, gpd.GeoDataFrame)

    if len(trace_candidates) == 0:
        return determine_topology_parameters(
            trace_length_array=np.array([]),
            node_counts=determine_node_type_counts(np.array([]),
                                                   branches_defined=True),
            area=sample_circle_area,
        )
    if resolve_branches_and_nodes:
        # Solve branches and nodes for each cell if wanted
        # Only way to make sure Mauldon parameters are correct
        _, nodes = branches_and_nodes(
            traces=trace_candidates,
            areas=gpd.GeoSeries([sample_circle], crs=traces.crs),
            snap_threshold=snap_threshold,
        )
    # node_candidates_idx = list(nodes_sindex.intersection(sample_circle.bounds))
    node_candidates_idx = spatial_index_intersection(
        spatial_index=pygeos_spatial_index(nodes),
        coordinates=geom_bounds(sample_circle),
    )

    node_candidates = nodes.iloc[node_candidates_idx]

    # Crop traces to sample circle
    # First check if any geometries intersect
    # If not: sample_features is an empty GeoDataFrame
    if any(
            trace_candidate.intersects(sample_circle)
            for trace_candidate in trace_candidates.geometry.values):
        sample_traces = crop_to_target_areas(
            traces=trace_candidates,
            areas=gpd.GeoSeries([sample_circle]),
            is_filtered=True,
            keep_column_data=False,
        )
    else:
        sample_traces = traces.iloc[0:0]
    if any(node.intersects(sample_circle) for node in nodes.geometry.values):
        # if any(nodes.intersects(sample_circle)):
        # TODO: Is node clipping stable?
        sample_nodes = gpd.clip(node_candidates, sample_circle)
        assert sample_nodes is not None
        assert all(
            isinstance(val, Point) for val in sample_nodes.geometry.values)
    else:
        sample_nodes = nodes.iloc[0:0]

    assert isinstance(sample_nodes, gpd.GeoDataFrame)
    assert isinstance(sample_traces, gpd.GeoDataFrame)

    sample_node_type_values = sample_nodes[CLASS_COLUMN].values
    assert isinstance(sample_node_type_values, np.ndarray)

    node_counts = determine_node_type_counts(sample_node_type_values,
                                             branches_defined=True)

    topology_parameters = determine_topology_parameters(
        trace_length_array=sample_traces.geometry.length.values,
        node_counts=node_counts,
        area=sample_circle_area,
        correct_mauldon=resolve_branches_and_nodes,
    )
    return topology_parameters
Example #6
0
# Plot the traces and target area with their branches and nodes
# -------------------------------------------------------------
#
# After we've manually created some traces and delineated their target area
# with the ``area`` ``Polygon`` we can determine branches and nodes
# of the traces network.
#
# You may notice that the branches and nodes are cropped to the original
# target area. Branches and nodes will never be determined outside the target
# area.

# %%
# Determine branches and nodes
# ----------------------------

branches, nodes = branches_and_nodes(traces, area, snap_threshold=0.001)

# %%
# Plot the data
# -------------

# Initialize matplotlib figure and two axes
# One axis is for traces and other for determined branches and nodes
fig, axes = plt.subplots(1, 2)

# Plot traces
traces.plot(ax=axes[0], color="blue", label="Traces")

# Plot the area boundary, not the full Polygon
area.boundary.plot(ax=axes[0],
                   color="black",