707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819 | def map_river_banks(fname, dirname, start_x, start_y, end_x, end_y, file_type,
ch_belt_smooth_factor=1e9, remove_smaller_components=True, delete_pixels_polys=False,
ch_belt_half_width=2000, water_index_type='mndwi', mndwi_threshold=0.01, small_hole_threshold=64, plot_D_primal=False,
min_g_primal_length=100000, solidity_filter=True, radius=50, min_main_path_length=2000,
flip_outlier_edges=False, check_edges=False, filter_contours=False,
min_tributary_length=100):
"""
Map channel centerlines and banks from a georeferenced image.
This function has been refactored into multiple phases for better maintainability.
"""
# Phase 1: Initial setup and skeletonization
result = _initial_skeletonization_and_graph_setup(
fname, dirname, start_x, start_y, end_x, end_y, file_type,
water_index_type, mndwi_threshold, delete_pixels_polys, small_hole_threshold,
remove_smaller_components, solidity_filter
)
if result[0] is None:
return None, None, None, None, None, None, None, None, None, None, None
graph, start_ind, end_ind, mndwi, dataset, left_utm_x, upper_utm_y, right_utm_x, lower_utm_y, delta_x, delta_y = result
if dataset is None:
print('dataset is not defined!')
# Phase 2: Find main path with fallback logic
graph, path, start_ind, end_ind = _find_main_path_with_fallbacks(
graph, start_x, start_y, end_x, end_y,
start_ind, end_ind, mndwi, left_utm_x, upper_utm_y, delta_x, delta_y
)
if path is None:
return None, None, None, None, None, None, None, None, None, None, None
# Phase 3: Process graph and extend edges
G = _process_graph_and_extend_edges(graph, path, start_ind, end_ind, radius,
left_utm_x=left_utm_x, upper_utm_y=upper_utm_y,
delta_x=delta_x, delta_y=delta_y,
min_tributary_length=min_tributary_length)
# Phase 4: Extract and smooth main path
path_result = _extract_and_smooth_main_path(G, start_ind, end_ind, min_main_path_length)
if path_result[0] is None:
return None, None, None, None, None, None, None, None, None, None, None
xcoords, ycoords, xcoords_sm, ycoords_sm, main_path = path_result
# Phase 5: Polygonize centerline network
gdf = _polygonize_and_process_centerlines(G, main_path)
# Phase 6: Create channel belt and boundaries
xs, ys, poly1, poly2, xcoords1, xcoords2, ycoords1, ycoords2 = _create_channel_belt_and_boundaries(
xcoords, ycoords, xcoords_sm, ycoords_sm, ch_belt_smooth_factor, ch_belt_half_width, gdf
)
# Phase 7: Create UTM geodataframe
gdf2, poly1_utm, poly2_utm = _create_UTM_geodataframe(gdf, poly1, poly2, left_utm_x, upper_utm_y, delta_x, delta_y, dataset, mndwi)
# Phase 8: Create primal graph
G_primal, primal_start_ind, primal_end_ind = _create_primal_graph(gdf2, xcoords1, ycoords1, xcoords2, ycoords2, left_utm_x,
upper_utm_y, delta_x, delta_y, min_g_primal_length)
if G_primal is None:
return None, None, None, None, None, None, None, None, None, None, None
# Phase 9: Create rook graph
G_rook = _create_rook_graph(gdf2, poly1_utm, poly2_utm, mndwi, dataset, ch_belt_half_width, delta_x, filter_contours=filter_contours)
if G_rook is None:
return None, None, None, None, None, None, None, None, None, None, None
smooth_banklines(G_rook, dataset, mndwi, save_smooth_lines=True)
# Phase 10: Set half channel widths
print('Phase 10: Set half channel widths')
set_half_channel_widths(G_primal, G_rook, dataset, mndwi)
# Phase 11: Create directed graph
print('Phase 11: Create directed graph')
xs, ys = convert_to_utm(np.array(xs), np.array(ys), left_utm_x, upper_utm_y, delta_x, delta_y)
# sometimes xs and ys need to be flipped (I have no idea why):
start_point_dist = np.sqrt((xs[0]-start_x)**2 + (ys[0]-start_y)**2)
end_point_dist = np.sqrt((xs[0]-end_x)**2 + (ys[0]-end_y)**2)
if end_point_dist < start_point_dist:
xs = xs[::-1]; ys = ys[::-1]
D_primal, source_nodes, sink_nodes = create_directed_multigraph(G_primal, G_rook, xs, ys, primal_start_ind, primal_end_ind,
flip_outlier_edges=flip_outlier_edges, check_edges=check_edges)
# Phase 12: Get bank coordinates for main channel
print('Phase 12: Get bank coordinates for main channel')
start_node, inds = find_start_node(D_primal)
if start_node is not None:
edge_path = traverse_multigraph(D_primal, start_node)
D_primal.graph['main_path'] = edge_path # store main path as a graph attribute
x, y, x_utm1, y_utm1, x_utm2, y_utm2 = get_bank_coords_for_main_channel(D_primal, mndwi, edge_path, dataset)
D_primal.graph['main_channel_cl_coords'] = np.vstack((x, y)).T
D_primal.graph['main_channel_bank1_coords'] = np.vstack((x_utm1, y_utm1)).T
D_primal.graph['main_channel_bank2_coords'] = np.vstack((x_utm2, y_utm2)).T
# Carry tributary confluence information from skeleton graph to D_primal
D_primal.graph['tributary_confluences'] = G.graph.get('tributary_confluences', [])
# Set graph names
D_primal.name = fname
G_rook.name = fname
G_primal.name = fname
if plot_D_primal:
fig, ax = plot_im_and_lines(mndwi, left_utm_x, right_utm_x, lower_utm_y, upper_utm_y,
G_rook, G_primal, D_primal, smoothing=False, start_x=start_x, start_y=start_y, end_x=end_x,
end_y=end_y, plot_lines=False)
plot_graph_w_colors(D_primal, ax)
return D_primal, G_rook, G_primal, mndwi, dataset, left_utm_x, right_utm_x, lower_utm_y, upper_utm_y, xs, ys
|