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
@@ -10,9 +10,10 @@ Description:
10
10
  This script is part of the BERA Tools.
11
11
  Webpage: https://github.com/appliedgrg/beratools
12
12
 
13
- This file is intended to be hosting algorithms and utility functions/classes
13
+ This file is intended to be hosting algorithms and utility functions/classes
14
14
  for centerline tool.
15
15
  """
16
+
16
17
  import enum
17
18
  from itertools import compress
18
19
 
@@ -30,23 +31,40 @@ import beratools.core.algo_cost as algo_cost
30
31
  import beratools.core.algo_dijkstra as bt_dijkstra
31
32
  import beratools.core.constants as bt_const
32
33
  import beratools.core.tool_base as bt_base
33
- import beratools.tools.common as bt_common
34
+ import beratools.utility.spatial_common as sp_common
34
35
 
35
36
 
36
37
  class CenterlineParams(float, enum.Enum):
38
+ """
39
+ Parameters for centerline generation.
40
+
41
+ These parameters are used to control the behavior of centerline generation
42
+ and should be adjusted based on the specific requirements of the application.
43
+ """
44
+
37
45
  BUFFER_CLIP = 5.0
38
46
  SEGMENTIZE_LENGTH = 1.0
39
47
  SIMPLIFY_LENGTH = 0.5
40
48
  SMOOTH_SIGMA = 0.8
41
49
  CLEANUP_POLYGON_BY_AREA = 1.0
42
50
 
51
+
43
52
  @enum.unique
44
53
  class CenterlineStatus(enum.IntEnum):
54
+ """
55
+ Status of centerline generation.
56
+
57
+ This enum is used to indicate the status of centerline generation.
58
+ It can be used to track the success or failure of the centerline generation process.
59
+
60
+ """
61
+
45
62
  SUCCESS = 1
46
63
  FAILED = 2
47
64
  REGENERATE_SUCCESS = 3
48
65
  REGENERATE_FAILED = 4
49
66
 
67
+
50
68
  def centerline_is_valid(centerline, input_line):
51
69
  """
52
70
  Check if centerline is valid.
@@ -66,10 +84,8 @@ def centerline_is_valid(centerline, input_line):
66
84
  # centerline length less the half of least cost path
67
85
  if (
68
86
  centerline.length < input_line.length / 2
69
- or centerline.distance(sh_geom.Point(input_line.coords[0]))
70
- > bt_const.BT_EPSILON
71
- or centerline.distance(sh_geom.Point(input_line.coords[-1]))
72
- > bt_const.BT_EPSILON
87
+ or centerline.distance(sh_geom.Point(input_line.coords[0])) > bt_const.BT_EPSILON
88
+ or centerline.distance(sh_geom.Point(input_line.coords[-1])) > bt_const.BT_EPSILON
73
89
  ):
74
90
  return False
75
91
 
@@ -80,12 +96,12 @@ def snap_end_to_end(in_line, line_reference):
80
96
  if type(in_line) is sh_geom.MultiLineString:
81
97
  in_line = sh_ops.linemerge(in_line)
82
98
  if type(in_line) is sh_geom.MultiLineString:
83
- print(f'algo_centerline: MultiLineString found {in_line.centroid}, pass.')
99
+ print(f"algo_centerline: MultiLineString found {in_line.centroid}, pass.")
84
100
  return None
85
101
 
86
102
  pts = list(in_line.coords)
87
103
  if len(pts) < 2:
88
- print('snap_end_to_end: input line invalid.')
104
+ print("snap_end_to_end: input line invalid.")
89
105
  return in_line
90
106
 
91
107
  line_start = sh_geom.Point(pts[0])
@@ -123,17 +139,15 @@ def find_centerline(poly, input_line):
123
139
  """
124
140
  default_return = input_line, CenterlineStatus.FAILED
125
141
  if not poly:
126
- print('find_centerline: No polygon found')
142
+ print("find_centerline: No polygon found")
127
143
  return default_return
128
144
 
129
- poly = shapely.segmentize(
130
- poly, max_segment_length=CenterlineParams.SEGMENTIZE_LENGTH
131
- )
145
+ poly = shapely.segmentize(poly, max_segment_length=CenterlineParams.SEGMENTIZE_LENGTH)
132
146
 
133
147
  # buffer to reduce MultiPolygons
134
- poly = poly.buffer(bt_const.SMALL_BUFFER)
148
+ poly = poly.buffer(bt_const.SMALL_BUFFER)
135
149
  if type(poly) is sh_geom.MultiPolygon:
136
- print('sh_geom.MultiPolygon encountered, skip.')
150
+ print("sh_geom.MultiPolygon encountered, skip.")
137
151
  return default_return
138
152
 
139
153
  exterior_pts = list(poly.exterior.coords)
@@ -146,16 +160,8 @@ def find_centerline(poly, input_line):
146
160
  line_coords = list(input_line.coords)
147
161
 
148
162
  # TODO add more code to filter Voronoi vertices
149
- src_geom = (
150
- sh_geom.Point(line_coords[0])
151
- .buffer(CenterlineParams.BUFFER_CLIP * 3)
152
- .intersection(poly)
153
- )
154
- dst_geom = (
155
- sh_geom.Point(line_coords[-1])
156
- .buffer(CenterlineParams.BUFFER_CLIP * 3)
157
- .intersection(poly)
158
- )
163
+ src_geom = sh_geom.Point(line_coords[0]).buffer(CenterlineParams.BUFFER_CLIP * 3).intersection(poly)
164
+ dst_geom = sh_geom.Point(line_coords[-1]).buffer(CenterlineParams.BUFFER_CLIP * 3).intersection(poly)
159
165
  src_geom = None
160
166
  dst_geom = None
161
167
 
@@ -171,7 +177,7 @@ def find_centerline(poly, input_line):
171
177
  dst_geom=dst_geom,
172
178
  )
173
179
  except Exception as e:
174
- print(f'find_centerline: {e}')
180
+ print(f"find_centerline: {e}")
175
181
  return default_return
176
182
 
177
183
  if not centerline:
@@ -189,14 +195,10 @@ def find_centerline(poly, input_line):
189
195
  cl_coords = list(centerline.coords)
190
196
 
191
197
  # trim centerline at two ends
192
- head_buffer = sh_geom.Point(cl_coords[0]).buffer(
193
- CenterlineParams.BUFFER_CLIP
194
- )
198
+ head_buffer = sh_geom.Point(cl_coords[0]).buffer(CenterlineParams.BUFFER_CLIP)
195
199
  centerline = centerline.difference(head_buffer)
196
200
 
197
- end_buffer = sh_geom.Point(cl_coords[-1]).buffer(
198
- CenterlineParams.BUFFER_CLIP
199
- )
201
+ end_buffer = sh_geom.Point(cl_coords[-1]).buffer(CenterlineParams.BUFFER_CLIP)
200
202
  centerline = centerline.difference(end_buffer)
201
203
 
202
204
  # No centerline detected, use input line instead.
@@ -207,18 +209,18 @@ def find_centerline(poly, input_line):
207
209
  if centerline.is_empty:
208
210
  return default_return
209
211
  except Exception as e:
210
- print(f'find_centerline: {e}')
212
+ print(f"find_centerline: {e}")
211
213
 
212
214
  centerline = snap_end_to_end(centerline, input_line)
213
215
 
214
216
  # Check centerline. If valid, regenerate by splitting polygon into two halves.
215
217
  if not centerline_is_valid(centerline, input_line):
216
218
  try:
217
- print('Regenerating line ...')
219
+ print("Regenerating line ...")
218
220
  centerline = regenerate_centerline(poly, input_line)
219
221
  return centerline, CenterlineStatus.REGENERATE_SUCCESS
220
222
  except Exception as e:
221
- print(f'find_centerline: {e}')
223
+ print(f"find_centerline: {e}")
222
224
  return input_line, CenterlineStatus.REGENERATE_FAILED
223
225
 
224
226
  return centerline, CenterlineStatus.SUCCESS
@@ -231,9 +233,7 @@ def find_corridor_polygon(corridor_thresh, in_transform, line_gpd):
231
233
  corridor_thresh_cl = corridor_thresh_cl.astype(np.int32)
232
234
 
233
235
  corridor_mask = np.where(1 == corridor_thresh_cl, True, False)
234
- poly_generator = rasterio.features.shapes(
235
- corridor_thresh_cl, mask=corridor_mask, transform=in_transform
236
- )
236
+ poly_generator = rasterio.features.shapes(corridor_thresh_cl, mask=corridor_mask, transform=in_transform)
237
237
  corridor_polygon = []
238
238
 
239
239
  try:
@@ -244,7 +244,7 @@ def find_corridor_polygon(corridor_thresh, in_transform, line_gpd):
244
244
  print(f"find_corridor_polygon: {e}")
245
245
 
246
246
  if corridor_polygon:
247
- corridor_polygon = (sh_ops.unary_union(corridor_polygon))
247
+ corridor_polygon = sh_ops.unary_union(corridor_polygon)
248
248
  if type(corridor_polygon) is sh_geom.MultiPolygon:
249
249
  poly_list = shapely.get_parts(corridor_polygon)
250
250
  merge_poly = poly_list[0]
@@ -283,7 +283,7 @@ def process_single_centerline(row_and_path):
283
283
 
284
284
  poly = row.geometry.iloc[0]
285
285
  centerline, status = find_centerline(poly, lc_path)
286
- row['centerline'] = centerline
286
+ row["centerline"] = centerline
287
287
 
288
288
  return row
289
289
 
@@ -295,14 +295,14 @@ def find_centerlines(poly_gpd, line_seg, processes):
295
295
  try:
296
296
  for i in poly_gpd.index:
297
297
  row = poly_gpd.loc[[i]]
298
- if 'OLnSEG' in line_seg.columns:
299
- line_id, Seg_id = row['OLnFID'].iloc[0], row['OLnSEG'].iloc[0]
300
- lc_path = line_seg.loc[
301
- (line_seg.OLnFID == line_id) & (line_seg.OLnSEG == Seg_id)
302
- ]["geometry"].iloc[0]
298
+ if "OLnSEG" in line_seg.columns:
299
+ line_id, Seg_id = row["OLnFID"].iloc[0], row["OLnSEG"].iloc[0]
300
+ lc_path = line_seg.loc[(line_seg.OLnFID == line_id) & (line_seg.OLnSEG == Seg_id)][
301
+ "geometry"
302
+ ].iloc[0]
303
303
  else:
304
- line_id = row['OLnFID'].iloc[0]
305
- lc_path = line_seg.loc[(line_seg.OLnFID == line_id)]['geometry'].iloc[0]
304
+ line_id = row["OLnFID"].iloc[0]
305
+ lc_path = line_seg.loc[(line_seg.OLnFID == line_id)]["geometry"].iloc[0]
306
306
 
307
307
  rows_and_paths.append((row, lc_path))
308
308
  except Exception as e:
@@ -326,12 +326,8 @@ def regenerate_centerline(poly, input_line):
326
326
  sh_geom.MultiLineString
327
327
 
328
328
  """
329
- line_1 = sh_ops.substring(
330
- input_line, start_dist=0.0, end_dist=input_line.length / 2
331
- )
332
- line_2 = sh_ops.substring(
333
- input_line, start_dist=input_line.length / 2, end_dist=input_line.length
334
- )
329
+ line_1 = sh_ops.substring(input_line, start_dist=0.0, end_dist=input_line.length / 2)
330
+ line_2 = sh_ops.substring(input_line, start_dist=input_line.length / 2, end_dist=input_line.length)
335
331
 
336
332
  pts = shapely.force_2d(
337
333
  [
@@ -358,9 +354,7 @@ def regenerate_centerline(poly, input_line):
358
354
 
359
355
  poly = sh_geom.Polygon(poly_geoms[0])
360
356
 
361
- poly_exterior = sh_geom.Polygon(
362
- poly.buffer(bt_const.SMALL_BUFFER).exterior
363
- )
357
+ poly_exterior = sh_geom.Polygon(poly.buffer(bt_const.SMALL_BUFFER).exterior)
364
358
  poly_split = sh_ops.split(poly_exterior, perp)
365
359
 
366
360
  if len(poly_split.geoms) < 2:
@@ -400,6 +394,7 @@ def regenerate_centerline(poly, input_line):
400
394
  print("Centerline is regenerated.")
401
395
  return sh_ops.linemerge(sh_geom.MultiLineString([center_line_1, center_line_2]))
402
396
 
397
+
403
398
  class SeedLine:
404
399
  """Class to store seed line and least cost path."""
405
400
 
@@ -416,24 +411,19 @@ class SeedLine:
416
411
  line_radius = self.line_radius
417
412
  in_raster = self.raster
418
413
  seed_line = line # LineString
419
- default_return = (seed_line, seed_line, None)
420
414
 
421
- ras_clip, out_meta = bt_common.clip_raster(in_raster, seed_line, line_radius)
415
+ ras_clip, out_meta = sp_common.clip_raster(in_raster, seed_line, line_radius)
422
416
  cost_clip, _ = algo_cost.cost_raster(ras_clip, out_meta)
423
417
 
424
418
  lc_path = line
425
419
  try:
426
420
  if bt_const.CenterlineFlags.USE_SKIMAGE_GRAPH:
427
- lc_path = bt_dijkstra.find_least_cost_path_skimage(
428
- cost_clip, out_meta, seed_line
429
- )
421
+ lc_path = bt_dijkstra.find_least_cost_path_skimage(cost_clip, out_meta, seed_line)
430
422
  else:
431
- lc_path = bt_dijkstra.find_least_cost_path(
432
- cost_clip, out_meta, seed_line
433
- )
423
+ lc_path = bt_dijkstra.find_least_cost_path(cost_clip, out_meta, seed_line)
434
424
  except Exception as e:
435
425
  print(e)
436
- return default_return
426
+ return
437
427
 
438
428
  if lc_path:
439
429
  lc_path_coords = lc_path.coords
@@ -445,14 +435,12 @@ class SeedLine:
445
435
  # search for centerline
446
436
  if len(lc_path_coords) < 2:
447
437
  print("No least cost path detected, use input line.")
448
- self.line["status"] = CenterlineStatus.FAILED.value
449
- return default_return
438
+ self.line["cl_status"] = CenterlineStatus.FAILED.value
439
+ return
450
440
 
451
441
  # get corridor raster
452
442
  lc_path = sh_geom.LineString(lc_path_coords)
453
- ras_clip, out_meta = bt_common.clip_raster(
454
- in_raster, lc_path, line_radius * 0.9
455
- )
443
+ ras_clip, out_meta = sp_common.clip_raster(in_raster, lc_path, line_radius * 0.9)
456
444
  cost_clip, _ = algo_cost.cost_raster(ras_clip, out_meta)
457
445
 
458
446
  out_transform = out_meta["transform"]
@@ -474,13 +462,9 @@ class SeedLine:
474
462
 
475
463
  # find contiguous corridor polygon and extract centerline
476
464
  df = gpd.GeoDataFrame(geometry=[seed_line], crs=out_meta["crs"])
477
- corridor_poly_gpd = find_corridor_polygon(
478
- corridor_thresh_cl, out_transform, df
479
- )
480
- center_line, status = find_centerline(
481
- corridor_poly_gpd.geometry.iloc[0], lc_path
482
- )
483
- self.line["status"] = status.value
465
+ corridor_poly_gpd = find_corridor_polygon(corridor_thresh_cl, out_transform, df)
466
+ center_line, status = find_centerline(corridor_poly_gpd.geometry.iloc[0], lc_path)
467
+ self.line["cl_status"] = status.value
484
468
 
485
469
  self.lc_path = self.line.copy()
486
470
  self.lc_path.geometry = [lc_path]
@@ -488,4 +472,4 @@ class SeedLine:
488
472
  self.centerline = self.line.copy()
489
473
  self.centerline.geometry = [center_line]
490
474
 
491
- self.corridor_poly_gpd = corridor_poly_gpd
475
+ self.corridor_poly_gpd = corridor_poly_gpd
@@ -13,6 +13,7 @@ Description:
13
13
  The purpose of this script is to provide common algorithms
14
14
  and utility functions/classes.
15
15
  """
16
+
16
17
  import math
17
18
  import tempfile
18
19
  from pathlib import Path
@@ -32,8 +33,10 @@ from scipy import ndimage
32
33
  import beratools.core.algo_cost as algo_cost
33
34
  import beratools.core.constants as bt_const
34
35
 
36
+ gpd.options.io_engine = "pyogrio"
35
37
  DISTANCE_THRESHOLD = 2 # 1 meter for intersection neighborhood
36
38
 
39
+
37
40
  def process_single_item(cls_obj):
38
41
  """
39
42
  Process a class object for universal multiprocessing.
@@ -45,8 +48,16 @@ def process_single_item(cls_obj):
45
48
  cls_obj: Class object after processing
46
49
 
47
50
  """
48
- cls_obj.compute()
49
- return cls_obj
51
+ try:
52
+ cls_obj.compute()
53
+ return cls_obj
54
+ except Exception as e:
55
+ import traceback
56
+
57
+ print(f"❌ Exception during compute() for object: {e}")
58
+ traceback.print_exc()
59
+ return None
60
+
50
61
 
51
62
  def read_geospatial_file(file_path, layer=None):
52
63
  """
@@ -64,12 +75,11 @@ def read_geospatial_file(file_path, layer=None):
64
75
 
65
76
  """
66
77
  try:
67
- if layer is None:
68
- # Read the file without specifying a layer
69
- gdf = gpd.read_file(file_path)
70
- else:
71
- # Read the file with the specified layer
72
- gdf = gpd.read_file(file_path, layer=layer)
78
+ kwargs = {}
79
+ if layer is not None:
80
+ kwargs['layer'] = layer
81
+
82
+ gdf = gpd.read_file(file_path, **kwargs)
73
83
 
74
84
  # Clean the geometries in the GeoDataFrame
75
85
  gdf = clean_geometries(gdf)
@@ -80,12 +90,14 @@ def read_geospatial_file(file_path, layer=None):
80
90
  print(f"Error reading file {file_path}: {e}")
81
91
  return None
82
92
 
93
+
83
94
  def has_multilinestring(gdf):
84
95
  """Check if any geometry is a MultiLineString."""
85
96
  # Filter out None values (invalid geometries) from the GeoDataFrame
86
97
  valid_geometries = gdf.geometry
87
98
  return any(isinstance(geom, sh_geom.MultiLineString) for geom in valid_geometries)
88
99
 
100
+
89
101
  def clean_geometries(gdf):
90
102
  """
91
103
  Remove rows with invalid, None, or empty geometries from the GeoDataFrame.
@@ -101,26 +113,23 @@ def clean_geometries(gdf):
101
113
  # Remove rows where the geometry is invalid, None, or empty
102
114
  gdf = gdf[gdf.geometry.is_valid] # Only keep valid geometries
103
115
  gdf = gdf[~gdf.geometry.isna()] # Remove rows with None geometries
104
- gdf = gdf[
105
- gdf.geometry.apply(lambda geom: not geom.is_empty)
106
- ] # Remove empty geometries
116
+ gdf = gdf[gdf.geometry.apply(lambda geom: not geom.is_empty)] # Remove empty geometries
107
117
  return gdf
108
118
 
119
+
109
120
  def clean_line_geometries(line_gdf):
110
121
  """Clean line geometries in the GeoDataFrame."""
111
122
  if line_gdf is None:
112
123
  return line_gdf
113
-
124
+
114
125
  if line_gdf.empty:
115
126
  return line_gdf
116
-
117
- line_gdf = line_gdf[
118
- ~line_gdf.geometry.isna()
119
- & ~line_gdf.geometry.is_empty
120
- ]
127
+
128
+ line_gdf = line_gdf[~line_gdf.geometry.isna() & ~line_gdf.geometry.is_empty]
121
129
  line_gdf = line_gdf[line_gdf.geometry.length > bt_const.SMALL_BUFFER]
122
130
  return line_gdf
123
131
 
132
+
124
133
  def prepare_lines_gdf(file_path, layer=None, proc_segments=True):
125
134
  """
126
135
  Split lines at vertices or return original rows.
@@ -149,22 +158,14 @@ def prepare_lines_gdf(file_path, layer=None, proc_segments=True):
149
158
  segment = sh_geom.LineString([coords[i], coords[i + 1]])
150
159
 
151
160
  # Copy over all non-geometry columns (excluding 'geometry')
152
- attributes = {
153
- col: getattr(row, col) for col in gdf.columns if col != "geometry"
154
- }
155
- single_row_gdf = gpd.GeoDataFrame(
156
- [attributes], geometry=[segment], crs=gdf.crs
157
- )
161
+ attributes = {col: getattr(row, col) for col in gdf.columns if col != "geometry"}
162
+ single_row_gdf = gpd.GeoDataFrame([attributes], geometry=[segment], crs=gdf.crs)
158
163
  split_gdf_list.append(single_row_gdf)
159
164
 
160
165
  else:
161
166
  # If not proc_segment, add the original row as a single-row GeoDataFrame
162
- attributes = {
163
- col: getattr(row, col) for col in gdf.columns if col != "geometry"
164
- }
165
- single_row_gdf = gpd.GeoDataFrame(
166
- [attributes], geometry=[line], crs=gdf.crs
167
- )
167
+ attributes = {col: getattr(row, col) for col in gdf.columns if col != "geometry"}
168
+ single_row_gdf = gpd.GeoDataFrame([attributes], geometry=[line], crs=gdf.crs)
168
169
  split_gdf_list.append(single_row_gdf)
169
170
 
170
171
  return split_gdf_list
@@ -180,15 +181,11 @@ def morph_raster(corridor_thresh, canopy_raster, exp_shk_cell, cell_size_x):
180
181
  # Process: Expand
181
182
  # FLM original Expand equivalent
182
183
  cell_size = int(exp_shk_cell * 2 + 1)
183
- expanded = ndimage.grey_dilation(
184
- raster_class, size=(cell_size, cell_size)
185
- )
184
+ expanded = ndimage.grey_dilation(raster_class, size=(cell_size, cell_size))
186
185
 
187
186
  # Process: Shrink
188
187
  # FLM original Shrink equivalent
189
- file_shrink = ndimage.grey_erosion(
190
- expanded, size=(cell_size, cell_size)
191
- )
188
+ file_shrink = ndimage.grey_erosion(expanded, size=(cell_size, cell_size))
192
189
 
193
190
  else:
194
191
  if bt_const.BT_DEBUGGING:
@@ -250,6 +247,7 @@ def intersection_of_lines(line_1, line_2):
250
247
 
251
248
  return inter
252
249
 
250
+
253
251
  def get_angle(line, vertex_index):
254
252
  """
255
253
  Calculate the angle of the first or last segment.
@@ -287,15 +285,14 @@ def get_angle(line, vertex_index):
287
285
 
288
286
  return angle
289
287
 
288
+
290
289
  def points_are_close(pt1, pt2):
291
- if (
292
- abs(pt1.x - pt2.x) < DISTANCE_THRESHOLD
293
- and abs(pt1.y - pt2.y) < DISTANCE_THRESHOLD
294
- ):
290
+ if abs(pt1.x - pt2.x) < DISTANCE_THRESHOLD and abs(pt1.y - pt2.y) < DISTANCE_THRESHOLD:
295
291
  return True
296
292
  else:
297
293
  return False
298
294
 
295
+
299
296
  def generate_raster_footprint(in_raster, latlon=True):
300
297
  inter_img = "image_overview.tif"
301
298
 
@@ -331,6 +328,7 @@ def generate_raster_footprint(in_raster, latlon=True):
331
328
 
332
329
  return geom
333
330
 
331
+
334
332
  def save_raster_to_file(in_raster_mem, in_meta, out_raster_file):
335
333
  """
336
334
  Save raster matrix in memory to file.
@@ -344,6 +342,7 @@ def save_raster_to_file(in_raster_mem, in_meta, out_raster_file):
344
342
  with rasterio.open(out_raster_file, "w", **in_meta) as dest:
345
343
  dest.write(in_raster_mem, indexes=1)
346
344
 
345
+
347
346
  def generate_perpendicular_line_precise(points, offset=20):
348
347
  """
349
348
  Generate a perpendicular line to the input line at the given point.
@@ -359,7 +358,7 @@ def generate_perpendicular_line_precise(points, offset=20):
359
358
  # Compute the angle of the line
360
359
  if len(points) not in [2, 3]:
361
360
  return None
362
-
361
+
363
362
  center = points[1]
364
363
  perp_line = None
365
364
 
@@ -379,9 +378,7 @@ def generate_perpendicular_line_precise(points, offset=20):
379
378
  start = [center.x + offset / 2.0, center.y]
380
379
  end = [center.x - offset / 2.0, center.y]
381
380
  line = sh_geom.LineString([start, end])
382
- perp_line = sh_aff.rotate(
383
- line, angle + math.pi / 2.0, origin=center, use_radians=True
384
- )
381
+ perp_line = sh_aff.rotate(line, angle + math.pi / 2.0, origin=center, use_radians=True)
385
382
  elif len(points) == 3:
386
383
  head = points[0]
387
384
  tail = points[2]
@@ -397,15 +394,9 @@ def generate_perpendicular_line_precise(points, offset=20):
397
394
  head_new = shapely.force_3d(head_new)
398
395
  try:
399
396
  perp_seg_1 = sh_geom.LineString([center, head_new])
400
- perp_seg_1 = sh_aff.rotate(
401
- perp_seg_1, angle_diff, origin=center, use_radians=True
402
- )
403
- perp_seg_2 = sh_aff.rotate(
404
- perp_seg_1, math.pi, origin=center, use_radians=True
405
- )
406
- perp_line = sh_geom.LineString(
407
- [list(perp_seg_1.coords)[1], list(perp_seg_2.coords)[1]]
408
- )
397
+ perp_seg_1 = sh_aff.rotate(perp_seg_1, angle_diff, origin=center, use_radians=True)
398
+ perp_seg_2 = sh_aff.rotate(perp_seg_1, math.pi, origin=center, use_radians=True)
399
+ perp_line = sh_geom.LineString([list(perp_seg_1.coords)[1], list(perp_seg_2.coords)[1]])
409
400
  except Exception as e:
410
401
  print(e)
411
402
 
@@ -426,9 +417,8 @@ def _line_angle(point_1, point_2):
426
417
  angle = math.atan2(delta_y, delta_x)
427
418
  return angle
428
419
 
429
- def corridor_raster(
430
- raster_clip, out_meta, source, destination, cell_size, corridor_threshold
431
- ):
420
+
421
+ def corridor_raster(raster_clip, out_meta, source, destination, cell_size, corridor_threshold):
432
422
  """
433
423
  Calculate corridor raster.
434
424
 
@@ -442,7 +432,7 @@ def corridor_raster(
442
432
 
443
433
  Returns:
444
434
  corridor raster
445
-
435
+
446
436
  """
447
437
  try:
448
438
  # change all nan to BT_NODATA_COST for workaround
@@ -481,6 +471,7 @@ def corridor_raster(
481
471
 
482
472
  return corridor_thresh_cl
483
473
 
474
+
484
475
  def remove_holes(geom):
485
476
  if geom.geom_type == "Polygon":
486
477
  if geom.interiors:
@@ -494,4 +485,4 @@ def remove_holes(geom):
494
485
  else:
495
486
  new_polygons.append(polygon)
496
487
  return sh_geom.MultiPolygon(new_polygons)
497
- return geom # Return other geometry types as is
488
+ return geom # Return other geometry types as is