BERATools 0.2.3__py3-none-any.whl → 0.2.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. beratools/__init__.py +8 -3
  2. beratools/core/{algo_footprint_rel.py → algo_canopy_footprint_exp.py} +176 -139
  3. beratools/core/algo_centerline.py +61 -77
  4. beratools/core/algo_common.py +48 -57
  5. beratools/core/algo_cost.py +18 -25
  6. beratools/core/algo_dijkstra.py +37 -45
  7. beratools/core/algo_line_grouping.py +100 -100
  8. beratools/core/algo_merge_lines.py +40 -8
  9. beratools/core/algo_split_with_lines.py +289 -304
  10. beratools/core/algo_vertex_optimization.py +25 -46
  11. beratools/core/canopy_threshold_relative.py +755 -0
  12. beratools/core/constants.py +8 -9
  13. beratools/{tools → core}/line_footprint_functions.py +411 -258
  14. beratools/core/logger.py +18 -2
  15. beratools/core/tool_base.py +17 -75
  16. beratools/gui/assets/BERALogo.ico +0 -0
  17. beratools/gui/assets/BERA_Splash.gif +0 -0
  18. beratools/gui/assets/BERA_WizardImage.png +0 -0
  19. beratools/gui/assets/beratools.json +475 -2171
  20. beratools/gui/bt_data.py +585 -234
  21. beratools/gui/bt_gui_main.py +129 -91
  22. beratools/gui/main.py +4 -7
  23. beratools/gui/tool_widgets.py +530 -354
  24. beratools/tools/__init__.py +0 -7
  25. beratools/tools/{line_footprint_absolute.py → canopy_footprint_absolute.py} +81 -56
  26. beratools/tools/canopy_footprint_exp.py +113 -0
  27. beratools/tools/centerline.py +30 -37
  28. beratools/tools/check_seed_line.py +127 -0
  29. beratools/tools/common.py +65 -586
  30. beratools/tools/{line_footprint_fixed.py → ground_footprint.py} +140 -117
  31. beratools/tools/line_footprint_relative.py +64 -35
  32. beratools/tools/tool_template.py +48 -40
  33. beratools/tools/vertex_optimization.py +20 -34
  34. beratools/utility/env_checks.py +53 -0
  35. beratools/utility/spatial_common.py +210 -0
  36. beratools/utility/tool_args.py +138 -0
  37. beratools-0.2.5.dist-info/METADATA +134 -0
  38. beratools-0.2.5.dist-info/RECORD +50 -0
  39. {beratools-0.2.3.dist-info → beratools-0.2.5.dist-info}/WHEEL +1 -1
  40. beratools-0.2.5.dist-info/entry_points.txt +3 -0
  41. beratools-0.2.5.dist-info/licenses/LICENSE +674 -0
  42. beratools/core/algo_tiler.py +0 -428
  43. beratools/gui/__init__.py +0 -11
  44. beratools/gui/batch_processing_dlg.py +0 -513
  45. beratools/gui/map_window.py +0 -162
  46. beratools/tools/Beratools_r_script.r +0 -1120
  47. beratools/tools/Ht_metrics.py +0 -116
  48. beratools/tools/batch_processing.py +0 -136
  49. beratools/tools/canopy_threshold_relative.py +0 -672
  50. beratools/tools/canopycostraster.py +0 -222
  51. beratools/tools/fl_regen_csf.py +0 -428
  52. beratools/tools/forest_line_attributes.py +0 -408
  53. beratools/tools/line_grouping.py +0 -45
  54. beratools/tools/ln_relative_metrics.py +0 -615
  55. beratools/tools/r_cal_lpi_elai.r +0 -25
  56. beratools/tools/r_generate_pd_focalraster.r +0 -101
  57. beratools/tools/r_interface.py +0 -80
  58. beratools/tools/r_point_density.r +0 -9
  59. beratools/tools/rpy_chm2trees.py +0 -86
  60. beratools/tools/rpy_dsm_chm_by.py +0 -81
  61. beratools/tools/rpy_dtm_by.py +0 -63
  62. beratools/tools/rpy_find_cellsize.py +0 -43
  63. beratools/tools/rpy_gnd_csf.py +0 -74
  64. beratools/tools/rpy_hummock_hollow.py +0 -85
  65. beratools/tools/rpy_hummock_hollow_raster.py +0 -71
  66. beratools/tools/rpy_las_info.py +0 -51
  67. beratools/tools/rpy_laz2las.py +0 -40
  68. beratools/tools/rpy_lpi_elai_lascat.py +0 -466
  69. beratools/tools/rpy_normalized_lidar_by.py +0 -56
  70. beratools/tools/rpy_percent_above_dbh.py +0 -80
  71. beratools/tools/rpy_points2trees.py +0 -88
  72. beratools/tools/rpy_vegcoverage.py +0 -94
  73. beratools/tools/tiler.py +0 -48
  74. beratools/tools/zonal_threshold.py +0 -144
  75. beratools-0.2.3.dist-info/METADATA +0 -108
  76. beratools-0.2.3.dist-info/RECORD +0 -74
  77. beratools-0.2.3.dist-info/entry_points.txt +0 -2
  78. beratools-0.2.3.dist-info/licenses/LICENSE +0 -22
@@ -1,304 +1,289 @@
1
- """Split lines at intersections using a class-based approach."""
2
-
3
- from itertools import combinations
4
-
5
- import geopandas as gpd
6
- from shapely import STRtree, snap
7
- from shapely.geometry import LineString, MultiPoint, Point
8
-
9
- import beratools.core.algo_common as algo_common
10
-
11
- EPSILON = 1e-5
12
- INTER_STATUS_COL = 'INTER_STATUS'
13
-
14
- def min_distance_in_multipoint(multipoint):
15
- points = list(multipoint.geoms)
16
- min_dist = float("inf")
17
- for p1, p2 in combinations(points, 2):
18
- dist = p1.distance(p2)
19
- if dist < min_dist:
20
- min_dist = dist
21
- return min_dist
22
-
23
- class LineSplitter:
24
- """Split lines at intersections."""
25
-
26
- def __init__(self, line_gdf):
27
- """
28
- Initialize the LineSplitter with the input GeoPackage and layer name.
29
-
30
- Args:
31
- input_gpkg (str): Path to the input GeoPackage file.
32
- layer_name (str): Name of the layer to read from the GeoPackage.
33
-
34
- """
35
- # Explode if needed for multi-part geometries
36
- self.line_gdf = line_gdf.explode()
37
- self.line_gdf[INTER_STATUS_COL] = 1 # record line intersection status
38
- self.inter_status = {}
39
- self.sindex = self.line_gdf.sindex # Spatial index for faster operations
40
-
41
- self.intersection_gdf = []
42
- self.split_lines_gdf = None
43
-
44
- def cut_line_by_points(self, line, points):
45
- """
46
- Cuts a LineString into segments based on the given points.
47
-
48
- Args:
49
- line: A shapely LineString to be cut.
50
- points: A list of Point objects where the LineString needs to be cut.
51
-
52
- Return:
53
- A list of LineString segments after the cuts.
54
-
55
- """
56
- # Create a spatial index for the coordinates of the LineString
57
- line_coords = [Point(x, y) for x, y in line.coords]
58
- sindex = STRtree(line_coords)
59
-
60
- # Sort points based on their projected position along the line
61
- sorted_points = sorted(points, key=lambda p: line.project(p))
62
- segments = []
63
-
64
- # Process each point, inserting it into the correct location
65
- start_idx = 0
66
- start_pt = None
67
- end_pt = None
68
-
69
- for point in sorted_points:
70
- # Find the closest segment on the line using the spatial index
71
- nearest_pt_idx = sindex.nearest(point)
72
- end_idx = nearest_pt_idx
73
- end_pt = point
74
-
75
- dist1 = line.project(point)
76
- dist2 = line.project(line_coords[nearest_pt_idx])
77
-
78
- if dist1 > dist2:
79
- end_idx = nearest_pt_idx + 1
80
-
81
- # Create a new segment
82
- new_coords = line_coords[start_idx:end_idx]
83
- if start_pt: # Append start point
84
- new_coords = [start_pt] + new_coords
85
-
86
- if end_pt: # Append end point
87
- new_coords = new_coords + [end_pt]
88
-
89
- nearest_segment = LineString(new_coords)
90
- start_idx = end_idx
91
- start_pt = end_pt
92
-
93
- segments.append(nearest_segment)
94
-
95
- # Add remaining part of the line after the last point
96
- if start_idx < len(line_coords):
97
- # If last point is not close to end point of line
98
- if start_pt.distance(line_coords[-1]) > EPSILON:
99
- remaining_part = LineString([start_pt] + line_coords[end_idx:])
100
- segments.append(remaining_part)
101
-
102
- return segments
103
-
104
- def find_intersections(self):
105
- """
106
- Find intersections between lines in the GeoDataFrame.
107
-
108
- Return:
109
- List of Point geometries where the lines intersect.
110
-
111
- """
112
- visited_pairs = set()
113
- intersection_points = []
114
-
115
- # Iterate through each line geometry to find intersections
116
- for idx, line1 in enumerate(self.line_gdf.geometry):
117
- # Use spatial index to find candidates for intersection
118
- indices = list(self.sindex.intersection(line1.bounds))
119
- indices.remove(idx) # Remove the current index from the list
120
-
121
- for match_idx in indices:
122
- line2 = self.line_gdf.iloc[match_idx].geometry
123
-
124
- # Create an index pair where the smaller index comes first
125
- pair = tuple(sorted([idx, match_idx]))
126
-
127
- # Skip if this pair has already been visited
128
- if pair in visited_pairs:
129
- continue
130
-
131
- # Mark the pair as visited
132
- visited_pairs.add(pair)
133
-
134
- # Only check lines that are different and intersect
135
- line1 = snap(line1, line2, tolerance=EPSILON)
136
- if line1.intersects(line2):
137
- # Find intersection points (can be multiple)
138
- intersections = line1.intersection(line2)
139
-
140
- if intersections.is_empty:
141
- continue
142
-
143
- # Intersection can be Point, MultiPoint, LineString or GeometryCollection
144
- if isinstance(intersections, Point):
145
- intersection_points.append(intersections)
146
- else:
147
- # record for further inspection
148
- # GeometryCollection, MultiLineString
149
- if isinstance(intersections, MultiPoint):
150
- intersection_points.extend(intersections.geoms)
151
- elif isinstance(intersections, LineString):
152
- intersection_points.append(
153
- intersections.interpolate(0.5, normalized=True)
154
- )
155
-
156
- # if minimum distance between points is greater than threshold
157
- # mark line as valid
158
- if isinstance(intersections, MultiPoint):
159
- if (
160
- min_distance_in_multipoint(intersections)
161
- > algo_common.DISTANCE_THRESHOLD
162
- ):
163
- continue
164
- # if intersection is a line, mark line as valid
165
- if isinstance(intersections, LineString):
166
- continue
167
-
168
- for item in pair:
169
- self.inter_status[item] = 0
170
-
171
- self.intersection_gdf = gpd.GeoDataFrame(
172
- geometry=intersection_points, crs=self.line_gdf.crs
173
- )
174
-
175
- def split_lines_at_intersections(self):
176
- """
177
- Split lines at the given intersection points.
178
-
179
- Args:
180
- intersection_points: List of Point geometries where the lines should be split.
181
-
182
- Returns:
183
- A GeoDataFrame with the split lines.
184
-
185
- """
186
- # Create a spatial index for faster point-line intersection checks
187
- sindex = self.intersection_gdf.sindex
188
-
189
- # List to hold the new split line segments
190
- new_rows = []
191
-
192
- # Iterate through each intersection point to split lines at that point
193
- for row in self.line_gdf.itertuples():
194
- if not isinstance(row.geometry, LineString):
195
- continue
196
-
197
- # Use spatial index to find possible line candidates for intersection
198
- possible_matches = sindex.query(row.geometry.buffer(EPSILON))
199
- end_pts = MultiPoint([row.geometry.coords[0], row.geometry.coords[-1]])
200
-
201
- pt_list = []
202
- new_segments = [row.geometry]
203
-
204
- for idx in possible_matches:
205
- point = self.intersection_gdf.loc[idx].geometry
206
- # Check if the point is on the line
207
- if row.geometry.distance(point) < EPSILON:
208
- if end_pts.distance(point) < EPSILON:
209
- continue
210
- else:
211
- pt_list.append(point)
212
-
213
- if len(pt_list) > 0:
214
- # Split the line at the intersection
215
- new_segments = self.cut_line_by_points(row.geometry, pt_list)
216
-
217
- # If the line was split into multiple segments, create new rows
218
- for segment in new_segments:
219
- new_row = row._asdict() # Convert the original row into a dictionary
220
- new_row['geometry'] = segment # Update the geometry with the split one
221
- new_rows.append(new_row)
222
-
223
- self.split_lines_gdf = gpd.GeoDataFrame(
224
- new_rows, columns=self.line_gdf.columns, crs=self.line_gdf.crs
225
- )
226
-
227
- self.split_lines_gdf = algo_common.clean_line_geometries(self.split_lines_gdf)
228
-
229
- # Debugging: print how many segments were created
230
- print(f"Total new line segments created: {len(new_rows)}")
231
-
232
- def save_to_geopackage(
233
- self,
234
- input_gpkg,
235
- line_layer="split_lines",
236
- intersection_layer=None,
237
- invalid_layer=None,
238
- ):
239
- """
240
- Save the split lines and intersection points to the GeoPackage.
241
-
242
- Args:
243
- line_layer: split lines layer name in the GeoPackage.
244
- intersection_layer: layer name for intersection points in the GeoPackage.
245
-
246
- """
247
- # Save intersection points and split lines to the GeoPackage
248
- if self.split_lines_gdf is not None and intersection_layer:
249
- if len(self.intersection_gdf) > 0:
250
- self.intersection_gdf.to_file(
251
- input_gpkg, layer=intersection_layer, driver="GPKG"
252
- )
253
-
254
- if self.split_lines_gdf is not None and line_layer:
255
- if len(self.split_lines_gdf) > 0:
256
- self.split_lines_gdf['length'] = self.split_lines_gdf.geometry.length
257
- self.split_lines_gdf.to_file(
258
- input_gpkg, layer=line_layer, driver="GPKG"
259
- )
260
-
261
- # save invalid splits
262
- invalid_splits = self.line_gdf.loc[self.line_gdf[INTER_STATUS_COL] == 0]
263
- if not invalid_splits.empty and invalid_layer:
264
- if len(invalid_splits) > 0:
265
- invalid_splits.to_file(
266
- input_gpkg, layer=invalid_layer, driver="GPKG"
267
- )
268
-
269
- def process(self, intersection_gdf=None):
270
- """
271
- Find intersection points, split lines at intersections.
272
-
273
- Args:
274
- intersection_gdf: external GeoDataFrame with intersection points.
275
-
276
- """
277
- if intersection_gdf is not None:
278
- self.intersection_gdf = intersection_gdf
279
- else:
280
- self.find_intersections()
281
-
282
- if self.inter_status:
283
- for idx in self.inter_status.keys():
284
- self.line_gdf.loc[idx, INTER_STATUS_COL] = self.inter_status[idx]
285
-
286
- if not self.intersection_gdf.empty:
287
- # Split the lines at intersection points
288
- self.split_lines_at_intersections()
289
- else:
290
- print("No intersection points found, no lines to split.")
291
-
292
- def split_with_lines(input_gpkg, layer_name):
293
- splitter = LineSplitter(input_gpkg, layer_name)
294
- splitter.process()
295
- splitter.save_to_geopackage()
296
-
297
- if __name__ == "__main__":
298
- input_gpkg = r"I:\Temp\footprint_final.gpkg"
299
- layer_name = "merged_lines_original"
300
-
301
- gdf = gpd.read_file(input_gpkg, layer=layer_name)
302
- splitter = LineSplitter(gdf)
303
- splitter.process()
304
- splitter.save_to_geopackage(input_gpkg)
1
+ """Split lines at intersections using a class-based approach."""
2
+
3
+ from itertools import combinations
4
+
5
+ import geopandas as gpd
6
+ from shapely import STRtree, snap
7
+ from shapely.geometry import LineString, MultiPoint, Point
8
+
9
+ import beratools.core.algo_common as algo_common
10
+
11
+ EPSILON = 1e-5
12
+ INTER_STATUS_COL = "INTER_STATUS"
13
+
14
+
15
+ def min_distance_in_multipoint(multipoint):
16
+ points = list(multipoint.geoms)
17
+ min_dist = float("inf")
18
+ for p1, p2 in combinations(points, 2):
19
+ dist = p1.distance(p2)
20
+ if dist < min_dist:
21
+ min_dist = dist
22
+ return min_dist
23
+
24
+
25
+ class LineSplitter:
26
+ """Split lines at intersections."""
27
+
28
+ def __init__(self, line_gdf):
29
+ """
30
+ Initialize the LineSplitter with the input GeoPackage and layer name.
31
+
32
+ Args:
33
+ input_gpkg (str): Path to the input GeoPackage file.
34
+ layer_name (str): Name of the layer to read from the GeoPackage.
35
+
36
+ """
37
+ # Explode if needed for multi-part geometries
38
+ self.line_gdf = line_gdf.explode()
39
+ self.line_gdf[INTER_STATUS_COL] = 1 # record line intersection status
40
+ self.inter_status = {}
41
+ self.sindex = self.line_gdf.sindex # Spatial index for faster operations
42
+
43
+ self.intersection_gdf = []
44
+ self.split_lines_gdf = None
45
+
46
+ def cut_line_by_points(self, line, points):
47
+ """
48
+ Cuts a LineString into segments based on the given points.
49
+
50
+ Args:
51
+ line: A shapely LineString to be cut.
52
+ points: A list of Point objects where the LineString needs to be cut.
53
+
54
+ Return:
55
+ A list of LineString segments after the cuts.
56
+
57
+ """
58
+ # Create a spatial index for the coordinates of the LineString
59
+ line_coords = [Point(x, y) for x, y in line.coords]
60
+ sindex = STRtree(line_coords)
61
+
62
+ # Sort points based on their projected position along the line
63
+ sorted_points = sorted(points, key=lambda p: line.project(p))
64
+ segments = []
65
+
66
+ # Process each point, inserting it into the correct location
67
+ start_idx = 0
68
+ start_pt = None
69
+ end_pt = None
70
+
71
+ for point in sorted_points:
72
+ # Find the closest segment on the line using the spatial index
73
+ nearest_pt_idx = sindex.nearest(point)
74
+ end_idx = nearest_pt_idx
75
+ end_pt = point
76
+
77
+ dist1 = line.project(point)
78
+ dist2 = line.project(line_coords[nearest_pt_idx])
79
+
80
+ if dist1 > dist2:
81
+ end_idx = nearest_pt_idx + 1
82
+
83
+ # Create a new segment
84
+ new_coords = line_coords[start_idx:end_idx]
85
+ if start_pt: # Append start point
86
+ new_coords = [start_pt] + new_coords
87
+
88
+ if end_pt: # Append end point
89
+ new_coords = new_coords + [end_pt]
90
+
91
+ nearest_segment = LineString(new_coords)
92
+ start_idx = end_idx
93
+ start_pt = end_pt
94
+
95
+ segments.append(nearest_segment)
96
+
97
+ # Add remaining part of the line after the last point
98
+ if start_idx < len(line_coords):
99
+ # If last point is not close to end point of line
100
+ if start_pt.distance(line_coords[-1]) > EPSILON:
101
+ remaining_part = LineString([start_pt] + line_coords[end_idx:])
102
+ segments.append(remaining_part)
103
+
104
+ return segments
105
+
106
+ def find_intersections(self):
107
+ """
108
+ Find intersections between lines in the GeoDataFrame.
109
+
110
+ Return:
111
+ List of Point geometries where the lines intersect.
112
+
113
+ """
114
+ visited_pairs = set()
115
+ intersection_points = []
116
+
117
+ # Iterate through each line geometry to find intersections
118
+ for idx, line1 in enumerate(self.line_gdf.geometry):
119
+ # Use spatial index to find candidates for intersection
120
+ indices = list(self.sindex.intersection(line1.bounds))
121
+ indices.remove(idx) # Remove the current index from the list
122
+
123
+ for match_idx in indices:
124
+ line2 = self.line_gdf.iloc[match_idx].geometry
125
+
126
+ # Create an index pair where the smaller index comes first
127
+ pair = tuple(sorted([idx, match_idx]))
128
+
129
+ # Skip if this pair has already been visited
130
+ if pair in visited_pairs:
131
+ continue
132
+
133
+ # Mark the pair as visited
134
+ visited_pairs.add(pair)
135
+
136
+ # Only check lines that are different and intersect
137
+ line1 = snap(line1, line2, tolerance=EPSILON)
138
+ if line1.intersects(line2):
139
+ # Find intersection points (can be multiple)
140
+ intersections = line1.intersection(line2)
141
+
142
+ if intersections.is_empty:
143
+ continue
144
+
145
+ # Intersection can be Point, MultiPoint, LineString
146
+ # or GeometryCollection
147
+ if isinstance(intersections, Point):
148
+ intersection_points.append(intersections)
149
+ else:
150
+ # record for further inspection
151
+ # GeometryCollection, MultiLineString
152
+ if isinstance(intersections, MultiPoint):
153
+ intersection_points.extend(intersections.geoms)
154
+ elif isinstance(intersections, LineString):
155
+ intersection_points.append(intersections.interpolate(0.5, normalized=True))
156
+
157
+ # if minimum distance between points is greater than threshold
158
+ # mark line as valid
159
+ if isinstance(intersections, MultiPoint):
160
+ if min_distance_in_multipoint(intersections) > algo_common.DISTANCE_THRESHOLD:
161
+ continue
162
+ # if intersection is a line, mark line as valid
163
+ if isinstance(intersections, LineString):
164
+ continue
165
+
166
+ for item in pair:
167
+ self.inter_status[item] = 0
168
+
169
+ self.intersection_gdf = gpd.GeoDataFrame(geometry=intersection_points, crs=self.line_gdf.crs)
170
+
171
+ def split_lines_at_intersections(self):
172
+ """
173
+ Split lines at the given intersection points.
174
+
175
+ Args:
176
+ intersection_points: List of Point geometries where the lines should be split.
177
+
178
+ Returns:
179
+ A GeoDataFrame with the split lines.
180
+
181
+ """
182
+ # Create a spatial index for faster point-line intersection checks
183
+ sindex = self.intersection_gdf.sindex
184
+
185
+ # List to hold the new split line segments
186
+ new_rows = []
187
+
188
+ # Iterate through each intersection point to split lines at that point
189
+ for row in self.line_gdf.itertuples():
190
+ if not isinstance(row.geometry, LineString):
191
+ continue
192
+
193
+ # Use spatial index to find possible line candidates for intersection
194
+ possible_matches = sindex.query(row.geometry.buffer(EPSILON))
195
+ end_pts = MultiPoint([row.geometry.coords[0], row.geometry.coords[-1]])
196
+
197
+ pt_list = []
198
+ new_segments = [row.geometry]
199
+
200
+ for idx in possible_matches:
201
+ point = self.intersection_gdf.loc[idx].geometry
202
+ # Check if the point is on the line
203
+ if row.geometry.distance(point) < EPSILON:
204
+ if end_pts.distance(point) < EPSILON:
205
+ continue
206
+ else:
207
+ pt_list.append(point)
208
+
209
+ if len(pt_list) > 0:
210
+ # Split the line at the intersection
211
+ new_segments = self.cut_line_by_points(row.geometry, pt_list)
212
+
213
+ # If the line was split into multiple segments, create new rows
214
+ for segment in new_segments:
215
+ new_row = row._asdict() # Convert the original row into a dictionary
216
+ new_row["geometry"] = segment # Update the geometry with the split one
217
+ new_rows.append(new_row)
218
+
219
+ self.split_lines_gdf = gpd.GeoDataFrame(
220
+ new_rows, columns=self.line_gdf.columns, crs=self.line_gdf.crs
221
+ )
222
+
223
+ self.split_lines_gdf = algo_common.clean_line_geometries(self.split_lines_gdf)
224
+
225
+ # Debugging: print how many segments were created
226
+ print(f"Total new line segments created: {len(new_rows)}")
227
+
228
+ def save_to_geopackage(
229
+ self,
230
+ input_gpkg,
231
+ line_layer="split_lines",
232
+ intersection_layer=None,
233
+ invalid_layer=None,
234
+ ):
235
+ """
236
+ Save the split lines and intersection points to the GeoPackage.
237
+
238
+ Args:
239
+ line_layer: split lines layer name in the GeoPackage.
240
+ intersection_layer: layer name for intersection points in the GeoPackage.
241
+
242
+ """
243
+ # Save intersection points and split lines to the GeoPackage
244
+ if self.split_lines_gdf is not None and intersection_layer:
245
+ if len(self.intersection_gdf) > 0:
246
+ self.intersection_gdf.to_file(input_gpkg, layer=intersection_layer, driver="GPKG")
247
+
248
+ if self.split_lines_gdf is not None and line_layer:
249
+ if len(self.split_lines_gdf) > 0:
250
+ self.split_lines_gdf["length"] = self.split_lines_gdf.geometry.length
251
+ self.split_lines_gdf.to_file(input_gpkg, layer=line_layer, driver="GPKG")
252
+
253
+ # save invalid splits
254
+ invalid_splits = self.line_gdf.loc[self.line_gdf[INTER_STATUS_COL] == 0]
255
+ if not invalid_splits.empty and invalid_layer:
256
+ if len(invalid_splits) > 0:
257
+ invalid_splits.to_file(input_gpkg, layer=invalid_layer, driver="GPKG")
258
+
259
+ def process(self, intersection_gdf=None):
260
+ """
261
+ Find intersection points, split lines at intersections.
262
+
263
+ Args:
264
+ intersection_gdf: external GeoDataFrame with intersection points.
265
+
266
+ """
267
+ if intersection_gdf is not None:
268
+ self.intersection_gdf = intersection_gdf
269
+ else:
270
+ self.find_intersections()
271
+
272
+ if self.inter_status:
273
+ for idx in self.inter_status.keys():
274
+ self.line_gdf.loc[idx, INTER_STATUS_COL] = self.inter_status[idx]
275
+
276
+ if not self.intersection_gdf.empty:
277
+ # Split the lines at intersection points
278
+ self.split_lines_at_intersections()
279
+ else:
280
+ print("No intersection points found, no lines to split.")
281
+
282
+ if __name__ == "__main__":
283
+ input_gpkg = r"I:\Temp\footprint_final.gpkg"
284
+ layer_name = "merged_lines_original"
285
+
286
+ gdf = gpd.read_file(input_gpkg, layer=layer_name)
287
+ splitter = LineSplitter(gdf)
288
+ splitter.process()
289
+ splitter.save_to_geopackage(input_gpkg)