BERATools 0.2.0__py3-none-any.whl → 0.2.1__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 (153) hide show
  1. beratools/__init__.py +1 -7
  2. beratools/core/algo_centerline.py +491 -351
  3. beratools/core/algo_common.py +497 -0
  4. beratools/core/algo_cost.py +192 -0
  5. beratools/core/{dijkstra_algorithm.py → algo_dijkstra.py} +503 -460
  6. beratools/core/algo_footprint_rel.py +577 -0
  7. beratools/core/algo_line_grouping.py +944 -0
  8. beratools/core/algo_merge_lines.py +214 -0
  9. beratools/core/algo_split_with_lines.py +304 -0
  10. beratools/core/algo_tiler.py +428 -0
  11. beratools/core/algo_vertex_optimization.py +469 -0
  12. beratools/core/constants.py +52 -86
  13. beratools/core/logger.py +76 -85
  14. beratools/core/tool_base.py +196 -133
  15. beratools/gui/__init__.py +11 -15
  16. beratools/gui/{beratools.json → assets/beratools.json} +2185 -2300
  17. beratools/gui/batch_processing_dlg.py +513 -463
  18. beratools/gui/bt_data.py +481 -487
  19. beratools/gui/bt_gui_main.py +710 -691
  20. beratools/gui/main.py +26 -0
  21. beratools/gui/map_window.py +162 -146
  22. beratools/gui/tool_widgets.py +725 -493
  23. beratools/tools/Beratools_r_script.r +1120 -1120
  24. beratools/tools/Ht_metrics.py +116 -116
  25. beratools/tools/__init__.py +7 -7
  26. beratools/tools/batch_processing.py +136 -132
  27. beratools/tools/canopy_threshold_relative.py +672 -670
  28. beratools/tools/canopycostraster.py +222 -222
  29. beratools/tools/centerline.py +136 -176
  30. beratools/tools/common.py +857 -885
  31. beratools/tools/fl_regen_csf.py +428 -428
  32. beratools/tools/forest_line_attributes.py +408 -408
  33. beratools/tools/line_footprint_absolute.py +213 -363
  34. beratools/tools/line_footprint_fixed.py +436 -282
  35. beratools/tools/line_footprint_functions.py +733 -720
  36. beratools/tools/line_footprint_relative.py +73 -64
  37. beratools/tools/line_grouping.py +45 -0
  38. beratools/tools/ln_relative_metrics.py +615 -615
  39. beratools/tools/r_cal_lpi_elai.r +24 -24
  40. beratools/tools/r_generate_pd_focalraster.r +100 -100
  41. beratools/tools/r_interface.py +79 -79
  42. beratools/tools/r_point_density.r +8 -8
  43. beratools/tools/rpy_chm2trees.py +86 -86
  44. beratools/tools/rpy_dsm_chm_by.py +81 -81
  45. beratools/tools/rpy_dtm_by.py +63 -63
  46. beratools/tools/rpy_find_cellsize.py +43 -43
  47. beratools/tools/rpy_gnd_csf.py +74 -74
  48. beratools/tools/rpy_hummock_hollow.py +85 -85
  49. beratools/tools/rpy_hummock_hollow_raster.py +71 -71
  50. beratools/tools/rpy_las_info.py +51 -51
  51. beratools/tools/rpy_laz2las.py +40 -40
  52. beratools/tools/rpy_lpi_elai_lascat.py +466 -466
  53. beratools/tools/rpy_normalized_lidar_by.py +56 -56
  54. beratools/tools/rpy_percent_above_dbh.py +80 -80
  55. beratools/tools/rpy_points2trees.py +88 -88
  56. beratools/tools/rpy_vegcoverage.py +94 -94
  57. beratools/tools/tiler.py +48 -206
  58. beratools/tools/tool_template.py +69 -54
  59. beratools/tools/vertex_optimization.py +61 -620
  60. beratools/tools/zonal_threshold.py +144 -144
  61. beratools-0.2.1.dist-info/METADATA +109 -0
  62. beratools-0.2.1.dist-info/RECORD +74 -0
  63. {beratools-0.2.0.dist-info → beratools-0.2.1.dist-info}/WHEEL +1 -1
  64. {beratools-0.2.0.dist-info → beratools-0.2.1.dist-info}/licenses/LICENSE +22 -22
  65. beratools/gui/cli.py +0 -18
  66. beratools/gui/gui.json +0 -8
  67. beratools/gui_tk/ASCII Banners.txt +0 -248
  68. beratools/gui_tk/__init__.py +0 -20
  69. beratools/gui_tk/beratools_main.py +0 -515
  70. beratools/gui_tk/bt_widgets.py +0 -442
  71. beratools/gui_tk/cli.py +0 -18
  72. beratools/gui_tk/img/BERALogo.png +0 -0
  73. beratools/gui_tk/img/closed.gif +0 -0
  74. beratools/gui_tk/img/closed.png +0 -0
  75. beratools/gui_tk/img/open.gif +0 -0
  76. beratools/gui_tk/img/open.png +0 -0
  77. beratools/gui_tk/img/tool.gif +0 -0
  78. beratools/gui_tk/img/tool.png +0 -0
  79. beratools/gui_tk/main.py +0 -14
  80. beratools/gui_tk/map_window.py +0 -144
  81. beratools/gui_tk/runner.py +0 -1481
  82. beratools/gui_tk/tooltip.py +0 -55
  83. beratools/third_party/pyqtlet2/__init__.py +0 -9
  84. beratools/third_party/pyqtlet2/leaflet/__init__.py +0 -26
  85. beratools/third_party/pyqtlet2/leaflet/control/__init__.py +0 -6
  86. beratools/third_party/pyqtlet2/leaflet/control/control.py +0 -59
  87. beratools/third_party/pyqtlet2/leaflet/control/draw.py +0 -52
  88. beratools/third_party/pyqtlet2/leaflet/control/layers.py +0 -20
  89. beratools/third_party/pyqtlet2/leaflet/core/Parser.py +0 -24
  90. beratools/third_party/pyqtlet2/leaflet/core/__init__.py +0 -2
  91. beratools/third_party/pyqtlet2/leaflet/core/evented.py +0 -180
  92. beratools/third_party/pyqtlet2/leaflet/layer/__init__.py +0 -5
  93. beratools/third_party/pyqtlet2/leaflet/layer/featuregroup.py +0 -34
  94. beratools/third_party/pyqtlet2/leaflet/layer/icon/__init__.py +0 -1
  95. beratools/third_party/pyqtlet2/leaflet/layer/icon/icon.py +0 -30
  96. beratools/third_party/pyqtlet2/leaflet/layer/imageoverlay.py +0 -18
  97. beratools/third_party/pyqtlet2/leaflet/layer/layer.py +0 -105
  98. beratools/third_party/pyqtlet2/leaflet/layer/layergroup.py +0 -45
  99. beratools/third_party/pyqtlet2/leaflet/layer/marker/__init__.py +0 -1
  100. beratools/third_party/pyqtlet2/leaflet/layer/marker/marker.py +0 -91
  101. beratools/third_party/pyqtlet2/leaflet/layer/tile/__init__.py +0 -2
  102. beratools/third_party/pyqtlet2/leaflet/layer/tile/gridlayer.py +0 -4
  103. beratools/third_party/pyqtlet2/leaflet/layer/tile/tilelayer.py +0 -16
  104. beratools/third_party/pyqtlet2/leaflet/layer/vector/__init__.py +0 -5
  105. beratools/third_party/pyqtlet2/leaflet/layer/vector/circle.py +0 -15
  106. beratools/third_party/pyqtlet2/leaflet/layer/vector/circlemarker.py +0 -18
  107. beratools/third_party/pyqtlet2/leaflet/layer/vector/path.py +0 -5
  108. beratools/third_party/pyqtlet2/leaflet/layer/vector/polygon.py +0 -14
  109. beratools/third_party/pyqtlet2/leaflet/layer/vector/polyline.py +0 -18
  110. beratools/third_party/pyqtlet2/leaflet/layer/vector/rectangle.py +0 -14
  111. beratools/third_party/pyqtlet2/leaflet/map/__init__.py +0 -1
  112. beratools/third_party/pyqtlet2/leaflet/map/map.py +0 -220
  113. beratools/third_party/pyqtlet2/mapwidget.py +0 -45
  114. beratools/third_party/pyqtlet2/web/custom.js +0 -43
  115. beratools/third_party/pyqtlet2/web/map.html +0 -23
  116. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/layers-2x.png +0 -0
  117. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/layers.png +0 -0
  118. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-icon-2x.png +0 -0
  119. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-icon.png +0 -0
  120. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-shadow.png +0 -0
  121. beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.css +0 -656
  122. beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.js +0 -6
  123. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.codeclimate.yml +0 -14
  124. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.editorconfig +0 -4
  125. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.gitattributes +0 -22
  126. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.travis.yml +0 -43
  127. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/LICENSE +0 -20
  128. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/layers-2x.png +0 -0
  129. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/layers.png +0 -0
  130. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-icon-2x.png +0 -0
  131. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-icon.png +0 -0
  132. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-shadow.png +0 -0
  133. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet-2x.png +0 -0
  134. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet.png +0 -0
  135. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet.svg +0 -156
  136. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.css +0 -10
  137. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.js +0 -10
  138. beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/LICENSE +0 -22
  139. beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/leaflet.rotatedMarker.js +0 -57
  140. beratools/tools/forest_line_ecosite.py +0 -216
  141. beratools/tools/lapis_all.py +0 -103
  142. beratools/tools/least_cost_path_from_chm.py +0 -152
  143. beratools-0.2.0.dist-info/METADATA +0 -63
  144. beratools-0.2.0.dist-info/RECORD +0 -142
  145. /beratools/gui/{img → assets}/BERALogo.png +0 -0
  146. /beratools/gui/{img → assets}/closed.gif +0 -0
  147. /beratools/gui/{img → assets}/closed.png +0 -0
  148. /beratools/{gui_tk → gui/assets}/gui.json +0 -0
  149. /beratools/gui/{img → assets}/open.gif +0 -0
  150. /beratools/gui/{img → assets}/open.png +0 -0
  151. /beratools/gui/{img → assets}/tool.gif +0 -0
  152. /beratools/gui/{img → assets}/tool.png +0 -0
  153. {beratools-0.2.0.dist-info → beratools-0.2.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,469 @@
1
+ """
2
+ Copyright (C) 2025 Applied Geospatial Research Group.
3
+
4
+ This script is licensed under the GNU General Public License v3.0.
5
+ See <https://gnu.org/licenses/gpl-3.0> for full license details.
6
+
7
+ Author: Richard Zeng
8
+
9
+ Description:
10
+ This script is part of the BERA Tools.
11
+ Webpage: https://github.com/appliedgrg/beratools
12
+
13
+ The purpose of this script is to move line vertices to the right
14
+ seismic line courses for improved alignment and analysis in
15
+ geospatial data processing.
16
+ """
17
+
18
+ from pathlib import Path
19
+
20
+ import geopandas as gpd
21
+ import numpy as np
22
+ import pandas as pd
23
+ import shapely.geometry as sh_geom
24
+ from shapely import STRtree
25
+
26
+ import beratools.core.algo_common as algo_common
27
+ import beratools.core.algo_cost as algo_cost
28
+ import beratools.core.constants as bt_const
29
+ import beratools.core.tool_base as bt_base
30
+ import beratools.tools.common as bt_common
31
+ from beratools.core import algo_dijkstra
32
+
33
+
34
+ def update_line_end_pt(line, index, new_vertex):
35
+ if not line:
36
+ return None
37
+
38
+ if index >= len(line.coords) or index < -1:
39
+ return line
40
+
41
+ coords = list(line.coords)
42
+ if len(coords[index]) == 2:
43
+ coords[index] = (new_vertex.x, new_vertex.y)
44
+ elif len(coords[index]) == 3:
45
+ coords[index] = (new_vertex.x, new_vertex.y, 0.0)
46
+
47
+ return sh_geom.LineString(coords)
48
+
49
+
50
+ class _SingleLine:
51
+ """Single line object with anchor point."""
52
+
53
+ def __init__(self, line_gdf, line_no, end_no, search_distance):
54
+ self.line_gdf = line_gdf
55
+ self.line = self.line_gdf.geometry[0]
56
+ self.line_no = line_no
57
+ self.end_no = end_no
58
+ self.search_distance = search_distance
59
+ self.anchor = None
60
+
61
+ self.add_anchors_to_line()
62
+
63
+ def is_valid(self):
64
+ return self.line.is_valid
65
+
66
+ def line_coord_list(self):
67
+ return algo_common.line_coord_list(self.line)
68
+
69
+ def get_end_vertex(self):
70
+ return self.line_coord_list()[self.end_no]
71
+
72
+ def touches_point(self, vertex):
73
+ return algo_common.points_are_close(vertex, self.get_end_vertex())
74
+
75
+ def get_angle(self):
76
+ return algo_common.get_angle(self.line, self.end_no)
77
+
78
+ def add_anchors_to_line(self):
79
+ """
80
+ Append new vertex to vertex group, by calculating distance to existing vertices.
81
+
82
+ An anchor point will be added together with line
83
+ """
84
+ # Calculate anchor point for each vertex
85
+ point = self.get_end_vertex()
86
+ line_string = self.line
87
+ index = self.end_no
88
+ pts = algo_common.line_coord_list(line_string)
89
+
90
+ pt_1 = None
91
+ pt_2 = None
92
+ if index == 0:
93
+ pt_1 = point
94
+ pt_2 = pts[1]
95
+ elif index == -1:
96
+ pt_1 = point
97
+ pt_2 = pts[-2]
98
+
99
+ # Calculate anchor point
100
+ dist_pt = 0.0
101
+ if pt_1 and pt_2:
102
+ dist_pt = pt_1.distance(pt_2)
103
+
104
+ # TODO: check why two points are the same
105
+ if np.isclose(dist_pt, 0.0):
106
+ print("Points are close, return")
107
+ return None
108
+
109
+ X = pt_1.x + (pt_2.x - pt_1.x) * self.search_distance / dist_pt
110
+ Y = pt_1.y + (pt_2.y - pt_1.y) * self.search_distance / dist_pt
111
+ self.anchor = sh_geom.Point(X, Y) # add anchor point
112
+
113
+
114
+ class _Vertex:
115
+ """Vertex object with multiple lines."""
116
+
117
+ def __init__(self, line_obj):
118
+ self.vertex = line_obj.get_end_vertex()
119
+ self.search_distance = line_obj.search_distance
120
+
121
+ self.cost_footprint = None
122
+ self.vertex_opt = None # optimized vertex
123
+ self.centerlines = None
124
+ self.anchors = None
125
+ self.in_raster = None
126
+ self.line_radius = None
127
+ self.lines = [] # SingleLine objects
128
+
129
+ self.add_line(line_obj)
130
+
131
+ def add_line(self, line_obj):
132
+ self.lines.append(line_obj)
133
+
134
+ def generate_anchor_pairs(self):
135
+ """
136
+ Extend line following outward direction to length of search_distance.
137
+
138
+ Use the end point as anchor point.
139
+
140
+ vertex: input intersection with all related lines
141
+ return:
142
+ one or two pairs of anchors according to numbers of lines
143
+ intersected.
144
+ two pairs anchors return when 3 or 4 lines intersected
145
+ one pair anchors return when 1 or 2 lines intersected.
146
+ """
147
+ lines = self.get_lines()
148
+ vertex = self.get_vertex()
149
+ slopes = []
150
+ for line in self.lines:
151
+ slopes.append(line.get_angle())
152
+
153
+ index = 0 # the index of line which paired with first line.
154
+ pt_start_1 = None
155
+ pt_end_1 = None
156
+ pt_start_2 = None
157
+ pt_end_2 = None
158
+
159
+ if len(slopes) == 4:
160
+ # get sort order of angles
161
+ index = np.argsort(slopes)
162
+
163
+ # first anchor pair (first and third in the sorted array)
164
+ pt_start_1 = self.lines[index[0]].anchor
165
+ pt_end_1 = self.lines[index[2]].anchor
166
+
167
+ pt_start_2 = self.lines[index[1]].anchor
168
+ pt_end_2 = self.lines[index[3]].anchor
169
+ elif len(slopes) == 3:
170
+ # find the largest difference between angles
171
+ angle_diff = [
172
+ abs(slopes[0] - slopes[1]),
173
+ abs(slopes[0] - slopes[2]),
174
+ abs(slopes[1] - slopes[2]),
175
+ ]
176
+ angle_diff_norm = [2 * np.pi - i if i > np.pi else i for i in angle_diff]
177
+ index = np.argmax(angle_diff_norm)
178
+ pairs = [(0, 1), (0, 2), (1, 2)]
179
+ pair = pairs[index]
180
+
181
+ # first anchor pair
182
+ pt_start_1 = self.lines[pair[0]].anchor
183
+ pt_end_1 = self.lines[pair[1]].anchor
184
+
185
+ # the rest one index
186
+ remain = list({0, 1, 2} - set(pair))[0] # the remaining index
187
+
188
+ try:
189
+ pt_start_2 = lines[remain][2]
190
+ # symmetry point of pt_start_2 regarding vertex["point"]
191
+ X = vertex.x - (pt_start_2.x - vertex.x)
192
+ Y = vertex.y - (pt_start_2.y - vertex.y)
193
+ pt_end_2 = sh_geom.Point(X, Y)
194
+ except Exception as e:
195
+ print(e)
196
+
197
+ # this scenario only use two anchors
198
+ # and find the closest point on least cost path
199
+ elif len(slopes) == 2:
200
+ pt_start_1 = self.lines[0].anchor
201
+ pt_end_1 = self.lines[1].anchor
202
+ elif len(slopes) == 1:
203
+ pt_start_1 = self.lines[0].anchor
204
+ # symmetry point of pt_start_1 regarding vertex["point"]
205
+ X = vertex.x - (pt_start_1.x - vertex.x)
206
+ Y = vertex.y - (pt_start_1.y - vertex.y)
207
+ pt_end_1 = sh_geom.Point(X, Y)
208
+
209
+ if not pt_start_1 or not pt_end_1:
210
+ print("Anchors not found")
211
+
212
+ # if points are outside of cost footprint, set to None
213
+ points = [pt_start_1, pt_end_1, pt_start_2, pt_end_2]
214
+ for index, pt in enumerate(points):
215
+ if pt:
216
+ if not self.cost_footprint.contains(sh_geom.Point(pt)):
217
+ points[index] = None
218
+
219
+ if len(slopes) == 4 or len(slopes) == 3:
220
+ if None in points:
221
+ return None
222
+ else:
223
+ return points
224
+ elif len(slopes) == 2 or len(slopes) == 1:
225
+ if None in (pt_start_1, pt_end_1):
226
+ return None
227
+ else:
228
+ return pt_start_1, pt_end_1
229
+
230
+ def compute(self):
231
+ try:
232
+ self.anchors = self.generate_anchor_pairs()
233
+ except Exception as e:
234
+ print(e)
235
+
236
+ if not self.anchors:
237
+ if bt_const.BT_DEBUGGING:
238
+ print("No anchors retrieved")
239
+ return None
240
+
241
+ centerline_1 = None
242
+ centerline_2 = None
243
+ intersection = None
244
+
245
+ if bt_const.CenterlineFlags.USE_SKIMAGE_GRAPH:
246
+ find_lc_path = algo_dijkstra.find_least_cost_path_skimage
247
+ else:
248
+ find_lc_path = algo_dijkstra.find_least_cost_path
249
+
250
+ try:
251
+ if len(self.anchors) == 4:
252
+ seed_line = sh_geom.LineString(self.anchors[0:2])
253
+
254
+ raster_clip, out_meta = bt_common.clip_raster(
255
+ self.in_raster, seed_line, self.line_radius
256
+ )
257
+ raster_clip, _ = algo_cost.cost_raster(raster_clip, out_meta)
258
+ centerline_1 = find_lc_path(raster_clip, out_meta, seed_line)
259
+ seed_line = sh_geom.LineString(self.anchors[2:4])
260
+
261
+ raster_clip, out_meta = bt_common.clip_raster(
262
+ self.in_raster, seed_line, self.line_radius
263
+ )
264
+ raster_clip, _ = algo_cost.cost_raster(raster_clip, out_meta)
265
+ centerline_2 = find_lc_path(raster_clip, out_meta, seed_line)
266
+
267
+ if centerline_1 and centerline_2:
268
+ intersection = algo_common.intersection_of_lines(
269
+ centerline_1, centerline_2
270
+ )
271
+ elif len(self.anchors) == 2:
272
+ seed_line = sh_geom.LineString(self.anchors)
273
+
274
+ raster_clip, out_meta = bt_common.clip_raster(
275
+ self.in_raster, seed_line, self.line_radius
276
+ )
277
+ raster_clip, _ = algo_cost.cost_raster(raster_clip, out_meta)
278
+ centerline_1 = find_lc_path(raster_clip, out_meta, seed_line)
279
+
280
+ if centerline_1:
281
+ intersection = algo_common.closest_point_to_line(
282
+ self.get_vertex(), centerline_1
283
+ )
284
+ except Exception as e:
285
+ print(e)
286
+
287
+ # Update vertices according to intersection, new center lines are returned
288
+ if type(intersection) is sh_geom.MultiPoint:
289
+ intersection = intersection.centroid
290
+
291
+ self.centerlines = [centerline_1, centerline_2]
292
+ self.vertex_opt = intersection
293
+
294
+ def get_lines(self):
295
+ lines = [item.line for item in self.lines]
296
+ return lines
297
+
298
+ def get_vertex(self):
299
+ return self.vertex
300
+
301
+
302
+ class VertexGrouping:
303
+ """A class used to group vertices and perform vertex optimization."""
304
+
305
+ def __init__(
306
+ self,
307
+ in_line,
308
+ in_raster,
309
+ search_distance,
310
+ line_radius,
311
+ out_line,
312
+ processes,
313
+ verbose,
314
+ in_layer=None,
315
+ out_layer=None,
316
+ ):
317
+ self.in_line = in_line
318
+ self.in_raster = in_raster
319
+ self.line_radius = float(line_radius)
320
+ self.search_distance = float(search_distance)
321
+ self.out_line = out_line
322
+ self.processes = processes
323
+ self.verbose = verbose
324
+ self.parallel_mode = bt_const.PARALLEL_MODE
325
+ self.in_layer = in_layer
326
+ self.out_layer = out_layer
327
+
328
+ self.crs = None
329
+ self.vertex_grp = []
330
+ self.sindex = None
331
+
332
+ self.line_list = []
333
+ self.line_visited = None
334
+
335
+ # calculate cost raster footprint
336
+ self.cost_footprint = algo_common.generate_raster_footprint(
337
+ self.in_raster, latlon=False
338
+ )
339
+
340
+ def set_parallel_mode(self, parallel_mode):
341
+ self.parallel_mode = parallel_mode
342
+
343
+ def create_vertex_group(self, line_obj):
344
+ """
345
+ Create a new vertex group.
346
+
347
+ Args:
348
+ line_obj : _SingleLine
349
+
350
+ """
351
+ # all end points not added will stay with this vertex
352
+ vertex = line_obj.get_end_vertex()
353
+ vertex_obj = _Vertex(line_obj)
354
+ search = self.sindex.query(vertex.buffer(bt_const.SMALL_BUFFER))
355
+
356
+ # add more vertices to the new group
357
+ for i in search:
358
+ line = self.line_list[i]
359
+ if i == line_obj.line_no:
360
+ continue
361
+
362
+ if not self.line_visited[i][0]:
363
+ new_line = _SingleLine(line, i, 0, self.search_distance)
364
+ if new_line.touches_point(vertex):
365
+ vertex_obj.add_line(new_line)
366
+ self.line_visited[i][0] = True
367
+
368
+ if not self.line_visited[i][-1]:
369
+ new_line = _SingleLine(line, i, -1, self.search_distance)
370
+ if new_line.touches_point(vertex):
371
+ vertex_obj.add_line(new_line)
372
+ self.line_visited[i][-1] = True
373
+
374
+ vertex_obj.in_raster = self.in_raster
375
+
376
+ vertex_obj.line_radius = self.line_radius
377
+ vertex_obj.cost_footprint = self.cost_footprint
378
+ self.vertex_grp.append(vertex_obj)
379
+
380
+ def create_all_vertex_groups(self):
381
+ self.line_list = algo_common.prepare_lines_gdf(
382
+ self.in_line, layer=self.in_layer, proc_segments=True
383
+ )
384
+ self.sindex = STRtree([item.geometry[0] for item in self.line_list])
385
+ self.line_visited = [{0: False, -1: False} for _ in range(len(self.line_list))]
386
+
387
+ i = 0
388
+ for line_no in range(len(self.line_list)):
389
+ if not self.line_visited[line_no][0]:
390
+ line = _SingleLine(
391
+ self.line_list[line_no], line_no, 0, self.search_distance
392
+ )
393
+
394
+ if not line.is_valid:
395
+ print(f"Line {line['line_no']} is invalid")
396
+ continue
397
+
398
+ self.create_vertex_group(line)
399
+ self.line_visited[line_no][0] = True
400
+ i += 1
401
+
402
+ if not self.line_visited[line_no][-1]:
403
+ line = _SingleLine(
404
+ self.line_list[line_no], line_no, -1, self.search_distance
405
+ )
406
+
407
+ if not line.is_valid:
408
+ print(f"Line {line['line_no']} is invalid")
409
+ continue
410
+
411
+ self.create_vertex_group(line)
412
+ self.line_visited[line_no][-1] = True
413
+ i += 1
414
+
415
+ def update_all_lines(self):
416
+ for vertex_obj in self.vertex_grp:
417
+ for line in vertex_obj.lines:
418
+ if not vertex_obj.vertex_opt:
419
+ continue
420
+
421
+ old_line = self.line_list[line.line_no].geometry[0]
422
+ self.line_list[line.line_no].geometry = [
423
+ update_line_end_pt(old_line, line.end_no, vertex_obj.vertex_opt)
424
+ ]
425
+
426
+ def save_all_layers(self, line_file):
427
+ line_file = Path(line_file)
428
+ lines = pd.concat(self.line_list)
429
+ lines.to_file(line_file, layer=self.out_layer)
430
+
431
+ aux_file = line_file
432
+ if line_file.suffix == ".shp":
433
+ file_stem = line_file.stem
434
+ aux_file = line_file.with_stem(file_stem + "_aux").with_suffix(".gpkg")
435
+
436
+ lc_paths = []
437
+ anchors = []
438
+ vertices = []
439
+ for item in self.vertex_grp:
440
+ if item.centerlines:
441
+ lc_paths.extend(item.centerlines)
442
+ if item.anchors:
443
+ anchors.extend(item.anchors)
444
+ if item.vertex_opt:
445
+ vertices.append(item.vertex_opt)
446
+
447
+ lc_paths = [item for item in lc_paths if item is not None]
448
+ anchors = [item for item in anchors if item is not None]
449
+ vertices = [item for item in vertices if item is not None]
450
+
451
+ lc_paths = gpd.GeoDataFrame(geometry=lc_paths, crs=lines.crs)
452
+ anchors = gpd.GeoDataFrame(geometry=anchors, crs=lines.crs)
453
+ vertices = gpd.GeoDataFrame(geometry=vertices, crs=lines.crs)
454
+
455
+ lc_paths.to_file(aux_file, layer="lc_paths")
456
+ anchors.to_file(aux_file, layer="anchors")
457
+ vertices.to_file(aux_file, layer="vertices")
458
+
459
+ def compute(self):
460
+ vertex_grp = bt_base.execute_multiprocessing(
461
+ algo_common.process_single_item,
462
+ self.vertex_grp,
463
+ "Vertex Optimization",
464
+ self.processes,
465
+ 1,
466
+ verbose=self.verbose,
467
+ )
468
+
469
+ self.vertex_grp = vertex_grp
@@ -1,86 +1,52 @@
1
- import numpy as np
2
- from enum import Flag, Enum, IntEnum, unique
3
-
4
-
5
- NADDatum = ['NAD83 Canadian Spatial Reference System', 'North American Datum 1983']
6
-
7
- BT_DEBUGGING = False
8
- BT_SHOW_ADVANCED_OPTIONS = False
9
- HAS_COST_RASTER = False
10
-
11
- BT_UID = 'BT_UID'
12
-
13
- BT_EPSILON = np.finfo(float).eps
14
- BT_NODATA_COST = np.inf
15
- BT_NODATA = -9999
16
- BT_MAXIMUM_CPU_CORES = 60 # multiprocessing has limit of 64, consider pathos
17
- BT_BUFFER_RATIO = 0.0 # overlapping ratio of raster when clipping lines
18
- BT_LABEL_MIN_WIDTH = 130
19
-
20
- GROUPING_SEGMENT = True
21
- LP_SEGMENT_LENGTH = 500
22
-
23
- FP_CORRIDOR_THRESHOLD = 2.5
24
- FP_SEGMENTIZE_LENGTH = 2.0
25
- FP_FIXED_WIDTH_DEFAULT = 5.0
26
- FP_PERP_LINE_OFFSET = 30.0
27
-
28
- # centerline
29
- CL_USE_SKIMAGE_GRAPH = False
30
- CL_DELETE_HOLES = True
31
- CL_SIMPLIFY_POLYGON = True
32
-
33
- CL_BUFFER_CLIP = 10.0
34
- CL_BUFFER_CENTROID = 3.0
35
- CL_SNAP_TOLERANCE = 15.0
36
- CL_SEGMENTIZE_LENGTH = 1.0
37
- CL_SIMPLIFY_LENGTH = 0.5
38
- CL_SMOOTH_SIGMA = 0.8
39
- CL_CLEANUP_POLYGON_BY_AREA = 1.0
40
- CL_POLYGON_BUFFER = 1e-6
41
-
42
-
43
- class FootprintParams(float, Enum):
44
- FP_CORRIDOR_THRESHOLD = 2.5
45
- FP_SEGMENTIZE_LENGTH = 2.0
46
- FP_FIXED_WIDTH_DEFAULT = 5.0
47
- FP_PERP_LINE_OFFSET = 30.0
48
-
49
-
50
- class CenterlineParams(float, Enum):
51
- BUFFER_CLIP = 5.0
52
- BUFFER_CENTROID = 3.0
53
- SNAP_TOLERANCE = 15.0
54
- SEGMENTIZE_LENGTH = 1.0
55
- SIMPLIFY_LENGTH = 0.5
56
- SMOOTH_SIGMA = 0.8
57
- CLEANUP_POLYGON_BY_AREA = 1.0
58
- POLYGON_BUFFER = 1e-6
59
-
60
-
61
- class CenterlineFlags(Flag):
62
- USE_SKIMAGE_GRAPH = False
63
- DELETE_HOLES = True
64
- SIMPLIFY_POLYGON = True
65
-
66
-
67
- @unique
68
- class CenterlineStatus(IntEnum):
69
- SUCCESS = 1
70
- FAILED = 2
71
- REGENERATE_SUCCESS = 3
72
- REGENERATE_FAILED = 4
73
-
74
-
75
- @unique
76
- class ParallelMode(IntEnum):
77
- SEQUENTIAL = 1
78
- MULTIPROCESSING = 2
79
- CONCURRENT = 3
80
- DASK = 4
81
- # RAY = 5
82
-
83
-
84
- PARALLEL_MODE = ParallelMode.MULTIPROCESSING
85
-
86
-
1
+ """
2
+ Copyright (C) 2025 Applied Geospatial Research Group.
3
+
4
+ This script is licensed under the GNU General Public License v3.0.
5
+ See <https://gnu.org/licenses/gpl-3.0> for full license details.
6
+
7
+ Author: Richard Zeng
8
+
9
+ Description:
10
+ This script is part of the BERA Tools.
11
+ Webpage: https://github.com/appliedgrg/beratools
12
+
13
+ The purpose of this script is to provide common constants.
14
+ """
15
+ import enum
16
+
17
+ import numpy as np
18
+
19
+ NADDatum = ['NAD83 Canadian Spatial Reference System', 'North American Datum 1983']
20
+
21
+ ASSETS_PATH = "assets"
22
+ BT_DEBUGGING = False
23
+ BT_UID = 'BT_UID'
24
+ BT_GROUP = "group"
25
+
26
+ BT_EPSILON = np.finfo(float).eps
27
+ BT_NODATA_COST = np.inf
28
+ BT_NODATA = -9999
29
+
30
+ LP_SEGMENT_LENGTH = 500
31
+ FP_CORRIDOR_THRESHOLD = 2.5
32
+ SMALL_BUFFER = 1e-3
33
+
34
+ class CenterlineFlags(enum.Flag):
35
+ """Flags for the centerline algorithm."""
36
+
37
+ USE_SKIMAGE_GRAPH = False
38
+ DELETE_HOLES = True
39
+ SIMPLIFY_POLYGON = True
40
+
41
+ @enum.unique
42
+ class ParallelMode(enum.IntEnum):
43
+ """Defines the parallel mode for the algorithms."""
44
+
45
+ SEQUENTIAL = 1
46
+ MULTIPROCESSING = 2
47
+ CONCURRENT = 3
48
+ DASK = 4
49
+ SLURM = 5
50
+ # RAY = 6
51
+
52
+ PARALLEL_MODE = ParallelMode.MULTIPROCESSING