11import json
2+
23import geopandas
34import shapely
4-
55from webcolors import name_to_hex
66
7-
87TOLERANCE = 0.0001
98
109
1110def get_properties (feature ):
1211 properties = json .loads (
1312 feature .drop (['geometry' , 'index' ], errors = 'ignore' ).fillna ('' ).to_json ()
1413 )
15- properties .update (dict (
16- colors = ',' .join ([
17- name_to_hex ((properties .get ('color' ,) or 'black' ).replace (' ' , '' )),
18- '#ffffff'
19- ])
20- ))
14+ properties .update (
15+ dict (
16+ colors = ',' .join (
17+ [
18+ name_to_hex (
19+ (
20+ properties .get (
21+ 'color' ,
22+ )
23+ or 'black'
24+ ).replace (' ' , '' )
25+ ),
26+ '#ffffff' ,
27+ ]
28+ )
29+ )
30+ )
2131 return properties
2232
2333
@@ -43,7 +53,7 @@ def merge_properties(p1, p2):
4353def cut_crossed_lines (gdf ):
4454 # cut lines at any cross points
4555 separated_features = []
46- for feat_index , feature in gdf .iterrows ():
56+ for _ , feature in gdf .iterrows ():
4757 properties = json .loads (
4858 feature .drop (['geometry' , 'index' ], errors = 'ignore' ).fillna ('' ).to_json ()
4959 )
@@ -52,7 +62,7 @@ def cut_crossed_lines(gdf):
5262 separated = []
5363 if len (crosses ) > 0 :
5464 split_points = []
55- for c_id , cross in crosses .iterrows ():
65+ for _ , cross in crosses .iterrows ():
5666 p = cross .geometry .intersection (curr_geom )
5767 if p .geom_type == 'MultiPoint' :
5868 split_points .append (p .geoms [0 ])
@@ -61,14 +71,16 @@ def cut_crossed_lines(gdf):
6171 separated = shapely .ops .split (curr_geom , shapely .MultiPoint (split_points )).geoms
6272 else :
6373 separated = [feature .geometry ]
64- separated_features .extend ([
65- dict (
66- type = 'Feature' ,
67- geometry = json .loads (shapely .to_geojson (s )),
68- properties = properties
69- )
70- for s in separated
71- ])
74+ separated_features .extend (
75+ [
76+ dict (
77+ type = 'Feature' ,
78+ geometry = json .loads (shapely .to_geojson (s )),
79+ properties = properties ,
80+ )
81+ for s in separated
82+ ]
83+ )
7284
7385 gdf = geopandas .GeoDataFrame .from_features (separated_features )
7486 return gdf
@@ -87,50 +99,55 @@ def merge_lines(gdf):
8799 not_visited = gdf [~ gdf .index .isin (visited )]
88100 touching = not_visited [not_visited .geometry .touches (curr_geom )]
89101 snapped = touching .snap (curr_geom , TOLERANCE * 2 )
90- merge = shapely .line_merge (shapely .union_all ([
91- * snapped .geometry , curr_geom
92- ]))
102+ merge = shapely .line_merge (shapely .union_all ([* snapped .geometry , curr_geom ]))
93103 curr_segment = None
94104 if merge .geom_type == 'MultiLineString' :
95105 for segment in merge .geoms :
96106 if segment .contains (curr_geom ) and not any (
97- s .touches (segment )
98- for s in merge .geoms
99- if s != segment
107+ s .touches (segment ) for s in merge .geoms if s != segment
100108 ):
101109 curr_segment = segment
102110 elif merge .geom_type == 'LineString' :
103111 curr_segment = merge
104112
105113 if curr_segment is None :
106114 # no valid merge segment, include feature as-is
107- merged_features .append (dict (
108- type = 'Feature' ,
109- geometry = json .loads (shapely .to_geojson (curr_geom )),
110- properties = properties
111- ))
115+ merged_features .append (
116+ dict (
117+ type = 'Feature' ,
118+ geometry = json .loads (shapely .to_geojson (curr_geom )),
119+ properties = properties ,
120+ )
121+ )
112122 else :
113123 # valid merge segment, mark constituent features as visited
114124 visited_indexes = list (snapped [snapped .geometry .within (curr_segment )].index )
115125 visited += visited_indexes
116126 visited_features = gdf .loc [visited_indexes ]
117- for v_index , v_feature in visited_features .iterrows ():
118- properties = merge_properties (properties , json .loads (
119- v_feature .drop (['geometry' , 'index' ], errors = 'ignore' ).fillna ('' ).to_json ()
120- ))
121- merged_features .append (dict (
122- type = 'Feature' ,
123- geometry = json .loads (shapely .to_geojson (curr_segment )),
124- properties = properties
125- ))
127+ for _ , v_feature in visited_features .iterrows ():
128+ properties = merge_properties (
129+ properties ,
130+ json .loads (
131+ v_feature .drop (['geometry' , 'index' ], errors = 'ignore' )
132+ .fillna ('' )
133+ .to_json ()
134+ ),
135+ )
136+ merged_features .append (
137+ dict (
138+ type = 'Feature' ,
139+ geometry = json .loads (shapely .to_geojson (curr_segment )),
140+ properties = properties ,
141+ )
142+ )
126143 gdf = geopandas .GeoDataFrame .from_features (merged_features )
127144 return cut_crossed_lines (gdf )
128145
129146
130147def find_nodes (gdf ):
131148 nodes = []
132149
133- for feat_index , feature in gdf .iterrows ():
150+ for _ , feature in gdf .iterrows ():
134151 properties = get_properties (feature )
135152 curr_geom = feature .geometry
136153 points = [shapely .Point (* p ) for p in curr_geom .coords ]
@@ -144,22 +161,23 @@ def find_nodes(gdf):
144161 # omit duplicates within tolerance radius
145162 not existing_node_locations .dwithin (endpoint , TOLERANCE ).any ()
146163 ):
147- nodes .append (dict (
148- location = endpoint ,
149- metadata = properties
150- ))
164+ nodes .append (dict (location = endpoint , metadata = properties ))
151165 return nodes
152166
153167
154168def find_edges (gdf , nodes ):
155169 edges = []
156170 existing_node_locations = geopandas .GeoSeries ([n ['location' ] for n in nodes ])
157- for feat_index , feature in gdf .iterrows ():
171+ for _ , feature in gdf .iterrows ():
158172 properties = get_properties (feature )
159173 curr_geom = feature .geometry
160174 points = [shapely .Point (* p ) for p in curr_geom .coords ]
161- nearby_nodes = existing_node_locations [existing_node_locations .dwithin (curr_geom , TOLERANCE )]
162- snapped = shapely .snap (shapely .MultiPoint (list (nearby_nodes .geometry )), curr_geom , TOLERANCE )
175+ nearby_nodes = existing_node_locations [
176+ existing_node_locations .dwithin (curr_geom , TOLERANCE )
177+ ]
178+ snapped = shapely .snap (
179+ shapely .MultiPoint (list (nearby_nodes .geometry )), curr_geom , TOLERANCE
180+ )
163181 separated = shapely .ops .split (shapely .LineString (points ), snapped ).geoms
164182 existing_edge_geometries = geopandas .GeoSeries ([e ['line_geometry' ] for e in edges ])
165183 for segment in separated :
@@ -170,16 +188,20 @@ def find_edges(gdf, nodes):
170188 from_points = nearby_nodes [nearby_nodes .dwithin (endpoints [0 ], TOLERANCE )]
171189 to_points = nearby_nodes [nearby_nodes .dwithin (endpoints [1 ], TOLERANCE )]
172190 if (
173- len (from_points ) > 0 and
174- len (to_points ) > 0 and
175- not shapely .snap (existing_edge_geometries , segment , TOLERANCE ).covers (segment ).any ()
191+ len (from_points ) > 0
192+ and len (to_points ) > 0
193+ and not shapely .snap (existing_edge_geometries , segment , TOLERANCE )
194+ .covers (segment )
195+ .any ()
176196 ):
177- edges .append (dict (
178- line_geometry = segment ,
179- from_point = from_points .iloc [0 ],
180- to_point = to_points .iloc [0 ],
181- metadata = properties ,
182- ))
197+ edges .append (
198+ dict (
199+ line_geometry = segment ,
200+ from_point = from_points .iloc [0 ],
201+ to_point = to_points .iloc [0 ],
202+ metadata = properties ,
203+ )
204+ )
183205 return edges
184206
185207
0 commit comments