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, )
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)
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})
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
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
# 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",