1+ import argparse
12import shapely
23import geopandas as gpd
34from geovoronoi import voronoi_regions_from_coords
5+ import networkx as nx
6+ import momepy
7+ import numpy as np
48
59BUFFER_DISTANCE = 0.0001
610SUBDIVISION_AMOUNT = 200
11+ '''
12+ parser = argparse.ArgumentParser(description='Find the centerline between two lines.')
13+ parser.add_argument("input_wall_1", help="the first valley wall shapefile")
14+ parser.add_argument("input_wall_2", help="the second valley wall shapefile")
15+ parser.add_argument("output", help="the filepath for the output shapefile")
16+
17+
18+ args = parser.parse_args()
19+ input_1 = gpd.read_file(args.input_wall_1)
20+ input_2 = gpd.read_file(args.input_wall_2)
21+ output = args.output
22+ '''
723
824# Import line shapefiles
9- input_1 = gpd .read_file ("C:/Users/pmitc/Documents/QGIS/Zumbro/Zumbro_SamplePoints/TestGround/Testwall_1.shp" )
10- input_2 = gpd .read_file ("C:/Users/pmitc/Documents/QGIS/Zumbro/Zumbro_SamplePoints/TestGround/Testwall_2.shp" )
25+
26+ input_1 = gpd .read_file ("C:\LSDTopoTools\Github\Valley-Centerline\WW\Input\WW_ValleyWall_N_10mpoints.shp" )
27+ input_2 = gpd .read_file ("C:\LSDTopoTools\Github\Valley-Centerline\WW\Input\WW_ValleyWall_S_10mpoints.shp" )
1128
1229wall_1 = shapely .geometry .shape (input_1 ['geometry' ][0 ])
1330wall_2 = shapely .geometry .shape (input_2 ['geometry' ][0 ])
@@ -20,7 +37,7 @@ def subdivide_wall(wall, subdivision_distance):
2037 dist += subdivision_distance
2138 return subdivided_coords
2239
23- extra_coords = subdivide_wall (wall_1 , SUBDIVISION_AMOUNT ) + subdivide_wall (wall_2 , SUBDIVISION_AMOUNT )
40+ # extra_coords = subdivide_wall(wall_1, SUBDIVISION_AMOUNT) + subdivide_wall(wall_2, SUBDIVISION_AMOUNT)
2441
2542# Convert valley walls into a collection of points for voronoi algorithm
2643
@@ -36,8 +53,11 @@ def subdivide_wall(wall, subdivision_distance):
3653 start_boundary = shapely .geometry .LineString ([wall_1 .coords [0 ], wall_2 .coords [- 1 ]])
3754 end_boundary = shapely .geometry .LineString ([wall_1 .coords [- 1 ], wall_2 .coords [0 ]])
3855
39- coords = coords + extra_coords
40- points = shapely .geometry .MultiPoint (coords )
56+ start_pole = start_boundary .interpolate (0.5 , normalized = True )
57+ end_pole = end_boundary .interpolate (0.5 , normalized = True )
58+
59+ #coords = coords + extra_coords
60+ points = [shapely .Point (i [0 ], i [1 ]) for i in coords ]
4161
4262# Generate polygon and buffer from the input walls
4363buffer = valleypoly .buffer (BUFFER_DISTANCE )
@@ -46,101 +66,101 @@ def subdivide_wall(wall, subdivision_distance):
4666geometry = [valleypoly ]
4767df = {'features' : features , 'geometry' : geometry }
4868gdf = gpd .GeoDataFrame (df )
49- gdf .to_file ('C:/Users/pmitc/Documents/QGIS/Zumbro/Zumbro_SamplePoints/TestGround/ValleyPoly.shp' )
69+ gdf = gdf .set_crs ("EPSG:32615" )
70+ gdf .to_file ('C:\LSDTopoTools\Github\Valley-Centerline\WW\Input\ValleyPoly.shp' )
5071
5172features = [0 ]
5273geometry = [buffer ]
5374df = {'features' : features , 'geometry' : geometry }
5475gdf = gpd .GeoDataFrame (df )
55- gdf .to_file ('C:/Users/pmitc/Documents/QGIS/Zumbro/Zumbro_SamplePoints/TestGround/ValleyBuffer.shp' )
56-
76+ gdf = gdf .set_crs ("EPSG:32615" )
77+ gdf .to_file ('C:\LSDTopoTools\Github\Valley-Centerline\WW\Input\ValleyBuffer.shp' )
78+ # print(coords)
5779# Create Voronoi Polygons
5880region_polys , region_pts = voronoi_regions_from_coords (points , buffer )
59-
81+ # print(type(region_polys))
82+ problem_polys = []
83+ for key , value in region_polys .items ():
84+ if value .geom_type == 'MultiPolygon' :
85+ problem_polys .append (key )
6086#Export the voronoi polygons
87+ for key in problem_polys :
88+ region_polys .pop (key )
6189
6290features = [i for i in range (len (region_polys ))]
6391geometry = [geom for geom in region_polys .values ()]
6492df = {'features' : features , 'geometry' : geometry }
6593gdf = gpd .GeoDataFrame (df )
66- gdf .to_file ('C:/Users/pmitc/Documents/QGIS/Zumbro/Zumbro_SamplePoints/TestGround/Voronoi_Demo.shp' )
94+ gdf = gdf .set_crs ("EPSG:32615" )
95+
96+ gdf .to_file ('C:\LSDTopoTools\Github\Valley-Centerline\WW\Input\Voronoi.shp' )
97+
6798
6899print ("Voronoi Polygons created." )
69- voronoi_edges = []
70100
101+
102+ ########################################
103+ # Find potential start/end edges #
104+ ########################################
105+ edges = np .array ([])
71106for poly in region_polys .values ():
72- if poly .geom_type == 'MultiPolygon' :
73- # If the buffer distance is too small (<0.0006 in this case), some of the voronoi outputs are MultiPolygons.
74- # I'm just discarding them here, which seems to work alright, but I'm wondering if there are cases when this would give a bad result.
75- pass
76- else :
77- for i in range (len (poly .exterior .coords ) - 1 ):
78- edge = shapely .geometry .LineString ((poly .exterior .coords [i ], poly .exterior .coords [i + 1 ]))
79- repeat = False
80-
81- # Edges will have duplicates if they're the border between polygons. If this is the case, we don't want to add the duplicates.
82- for test in voronoi_edges :
83- if edge .coords [:] == test .coords [:] or edge .coords [:] == test .coords [::- 1 ]:
84- # In all the cases so far, it seems that the coordinates have been flipped, so I'm not sure if this will ever be the case.
85- repeat = True
86- # Get rid of edges that are duplicates or outside of the valley
87- if edge .within (valleypoly ) and not repeat :
88- voronoi_edges .append (edge )
89- # Get the first and last segments, which should intersect the lines connecting the two valley walls
90- if not (edge .intersects (wall_1 ) or edge .intersects (wall_2 )):
91- if edge .intersects (start_boundary ):
92- start_edge = edge
93- print ('Found start edge!' )
94- if edge .intersects (end_boundary ):
95- end_edge = edge
96- print ('Found end edge!' )
97-
98- # Export voronoi edges (for testing purposes)
99- features = [i for i in range (len (voronoi_edges ))]
100- geometry = [geom for geom in voronoi_edges ]
107+ for i in range (len (poly .exterior .coords ) - 1 ):
108+ edge = shapely .geometry .LineString ((poly .exterior .coords [i ], poly .exterior .coords [i + 1 ]))
109+ if not (edge .intersects (wall_1 ) or edge .intersects (wall_2 )):
110+ edges = np .append (edges , edge )
111+
112+ features = [i for i in range (len (edges ))]
113+ geometry = edges
101114df = {'features' : features , 'geometry' : geometry }
102115gdf = gpd .GeoDataFrame (df )
103- gdf .to_file ('C:/Users/pmitc/Documents/QGIS/Zumbro/Zumbro_SamplePoints/TestGround/Voronoi_Edges_Demo.shp' )
104-
105- tried_edges = []
106-
107- # Recursive solution for getting only the centerline from beginning to end
108- def get_centerline_path (edge , from_edge ):
109- viable_edges = []
110- global centerlineedges
111- global tried_edges
112- # Add self to the list of tried edges
113- tried_edges .append (edge )
114- if edge .touches (end_edge ):
115- # If we've found the end, add ourselves to the list and let the edge that found us know
116- centerlineedges .append (edge )
117- centerlineedges .append (end_edge )
118- print ("We've reached the end!" )
119- return True
120- # See which neighbors haven't been checked yet
121- for other_edge in voronoi_edges :
122- if edge .touches (other_edge ) and other_edge not in tried_edges :
123- # Don't add the other edge if it's one connected to the parent edge
124- if from_edge is None or not (from_edge .touches (other_edge )):
125- viable_edges .append (other_edge )
126- for other_edge in viable_edges :
127- if get_centerline_path (other_edge , edge ):
128- # If one of our viable edges is on the path toward the end, add ourselves to the list and let the edge that found us know
129- centerlineedges .append (edge )
130- return True
131- return False
132-
133- centerlineedges = []
134- get_centerline_path (start_edge , None )
135-
136- # Export centerline
137- centerline = shapely .geometry .MultiLineString (centerlineedges )
138- centerline_merged = shapely .ops .linemerge (centerline )
139- print (centerline )
140- features = [0 ]
141- geometry = [centerline_merged ]
116+ edges_gdf = gdf .set_crs ("EPSG:32615" )
117+ edges_gdf .to_file ('C:\LSDTopoTools\Github\Valley-Centerline\WW\Input\edges.shp' )
118+ print ("Exported edges" )
119+
120+ features = [0 , 1 ]
121+ geometry = [start_pole , end_pole ]
142122df = {'features' : features , 'geometry' : geometry }
143123gdf = gpd .GeoDataFrame (df )
144- gdf = gdf .set_crs ("EPSG:32615" )
145- gdf .to_file ('C:/Users/pmitc/Documents/QGIS/Zumbro/Zumbro_SamplePoints/TestGround/ Centerline.shp' )
124+ poles_gdf = gdf .set_crs ("EPSG:32615" )
125+ poles_gdf .to_file ('C:\LSDTopoTools\Github\Valley- Centerline\WW\Input\poles .shp' )
146126print ("Exported" )
127+
128+
129+
130+ ########################################
131+ # Find nearest edges to poles #
132+ ########################################
133+ nearest_edges = gpd .sjoin_nearest (poles_gdf , edges_gdf ).merge (edges_gdf , left_on = "index_right" , right_index = True )
134+ cols = list (nearest_edges )
135+ print (cols )
136+ print ("Found nearest edges!" )
137+ features = [i for i in range (len (nearest_edges ['geometry_y' ]))]
138+ geometry = nearest_edges ['geometry_y' ]
139+ df = {'features' : features , 'geometry' : geometry }
140+ nearest_edges = gpd .GeoDataFrame (df )
141+ nearest_edges .to_file ('C:\LSDTopoTools\Github\Valley-Centerline\WW\Input\start_end.shp' )
142+
143+ ########################################
144+ # Find shortest path from start to end #
145+ ########################################
146+
147+ graph = momepy .gdf_to_nx (edges_gdf )
148+
149+ start = nearest_edges ['geometry' ][0 ].coords [0 ]
150+ end = nearest_edges ['geometry' ][1 ].coords [0 ]
151+ path = nx .shortest_path (graph , source = start , target = end )
152+ print ('Path found' )
153+ print (path [0 :5 ])
154+ path = shapely .LineString (path )
155+
156+ ########################################
157+ # Export centerline #
158+ ########################################
159+
160+ features = [0 ]
161+ geometry = path
162+ df = {'features' : features , 'geometry' : geometry }
163+ centerline_gdf = gpd .GeoDataFrame (df )
164+ centerline_gdf = centerline_gdf .set_crs ("EPSG:32615" )
165+ centerline_gdf .to_file ('C:\LSDTopoTools\Github\Valley-Centerline\WW\Input\centerline.shp' )
166+ print ("Exported" )
0 commit comments