BERATools 0.2.0__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 (142) hide show
  1. beratools/__init__.py +9 -0
  2. beratools/core/__init__.py +0 -0
  3. beratools/core/algo_centerline.py +351 -0
  4. beratools/core/constants.py +86 -0
  5. beratools/core/dijkstra_algorithm.py +460 -0
  6. beratools/core/logger.py +85 -0
  7. beratools/core/tool_base.py +133 -0
  8. beratools/gui/__init__.py +15 -0
  9. beratools/gui/batch_processing_dlg.py +463 -0
  10. beratools/gui/beratools.json +2300 -0
  11. beratools/gui/bt_data.py +487 -0
  12. beratools/gui/bt_gui_main.py +691 -0
  13. beratools/gui/cli.py +18 -0
  14. beratools/gui/gui.json +8 -0
  15. beratools/gui/img/BERALogo.png +0 -0
  16. beratools/gui/img/closed.gif +0 -0
  17. beratools/gui/img/closed.png +0 -0
  18. beratools/gui/img/open.gif +0 -0
  19. beratools/gui/img/open.png +0 -0
  20. beratools/gui/img/tool.gif +0 -0
  21. beratools/gui/img/tool.png +0 -0
  22. beratools/gui/map_window.py +146 -0
  23. beratools/gui/tool_widgets.py +493 -0
  24. beratools/gui_tk/ASCII Banners.txt +248 -0
  25. beratools/gui_tk/__init__.py +20 -0
  26. beratools/gui_tk/beratools_main.py +515 -0
  27. beratools/gui_tk/bt_widgets.py +442 -0
  28. beratools/gui_tk/cli.py +18 -0
  29. beratools/gui_tk/gui.json +8 -0
  30. beratools/gui_tk/img/BERALogo.png +0 -0
  31. beratools/gui_tk/img/closed.gif +0 -0
  32. beratools/gui_tk/img/closed.png +0 -0
  33. beratools/gui_tk/img/open.gif +0 -0
  34. beratools/gui_tk/img/open.png +0 -0
  35. beratools/gui_tk/img/tool.gif +0 -0
  36. beratools/gui_tk/img/tool.png +0 -0
  37. beratools/gui_tk/main.py +14 -0
  38. beratools/gui_tk/map_window.py +144 -0
  39. beratools/gui_tk/runner.py +1481 -0
  40. beratools/gui_tk/tooltip.py +55 -0
  41. beratools/third_party/pyqtlet2/__init__.py +9 -0
  42. beratools/third_party/pyqtlet2/leaflet/__init__.py +26 -0
  43. beratools/third_party/pyqtlet2/leaflet/control/__init__.py +6 -0
  44. beratools/third_party/pyqtlet2/leaflet/control/control.py +59 -0
  45. beratools/third_party/pyqtlet2/leaflet/control/draw.py +52 -0
  46. beratools/third_party/pyqtlet2/leaflet/control/layers.py +20 -0
  47. beratools/third_party/pyqtlet2/leaflet/core/Parser.py +24 -0
  48. beratools/third_party/pyqtlet2/leaflet/core/__init__.py +2 -0
  49. beratools/third_party/pyqtlet2/leaflet/core/evented.py +180 -0
  50. beratools/third_party/pyqtlet2/leaflet/layer/__init__.py +5 -0
  51. beratools/third_party/pyqtlet2/leaflet/layer/featuregroup.py +34 -0
  52. beratools/third_party/pyqtlet2/leaflet/layer/icon/__init__.py +1 -0
  53. beratools/third_party/pyqtlet2/leaflet/layer/icon/icon.py +30 -0
  54. beratools/third_party/pyqtlet2/leaflet/layer/imageoverlay.py +18 -0
  55. beratools/third_party/pyqtlet2/leaflet/layer/layer.py +105 -0
  56. beratools/third_party/pyqtlet2/leaflet/layer/layergroup.py +45 -0
  57. beratools/third_party/pyqtlet2/leaflet/layer/marker/__init__.py +1 -0
  58. beratools/third_party/pyqtlet2/leaflet/layer/marker/marker.py +91 -0
  59. beratools/third_party/pyqtlet2/leaflet/layer/tile/__init__.py +2 -0
  60. beratools/third_party/pyqtlet2/leaflet/layer/tile/gridlayer.py +4 -0
  61. beratools/third_party/pyqtlet2/leaflet/layer/tile/tilelayer.py +16 -0
  62. beratools/third_party/pyqtlet2/leaflet/layer/vector/__init__.py +5 -0
  63. beratools/third_party/pyqtlet2/leaflet/layer/vector/circle.py +15 -0
  64. beratools/third_party/pyqtlet2/leaflet/layer/vector/circlemarker.py +18 -0
  65. beratools/third_party/pyqtlet2/leaflet/layer/vector/path.py +5 -0
  66. beratools/third_party/pyqtlet2/leaflet/layer/vector/polygon.py +14 -0
  67. beratools/third_party/pyqtlet2/leaflet/layer/vector/polyline.py +18 -0
  68. beratools/third_party/pyqtlet2/leaflet/layer/vector/rectangle.py +14 -0
  69. beratools/third_party/pyqtlet2/leaflet/map/__init__.py +1 -0
  70. beratools/third_party/pyqtlet2/leaflet/map/map.py +220 -0
  71. beratools/third_party/pyqtlet2/mapwidget.py +45 -0
  72. beratools/third_party/pyqtlet2/web/custom.js +43 -0
  73. beratools/third_party/pyqtlet2/web/map.html +23 -0
  74. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/layers-2x.png +0 -0
  75. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/layers.png +0 -0
  76. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-icon-2x.png +0 -0
  77. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-icon.png +0 -0
  78. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-shadow.png +0 -0
  79. beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.css +656 -0
  80. beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.js +6 -0
  81. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.codeclimate.yml +14 -0
  82. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.editorconfig +4 -0
  83. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.gitattributes +22 -0
  84. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.travis.yml +43 -0
  85. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/LICENSE +20 -0
  86. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/layers-2x.png +0 -0
  87. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/layers.png +0 -0
  88. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-icon-2x.png +0 -0
  89. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-icon.png +0 -0
  90. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-shadow.png +0 -0
  91. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet-2x.png +0 -0
  92. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet.png +0 -0
  93. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet.svg +156 -0
  94. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.css +10 -0
  95. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.js +10 -0
  96. beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/LICENSE +22 -0
  97. beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/leaflet.rotatedMarker.js +57 -0
  98. beratools/tools/Beratools_r_script.r +1120 -0
  99. beratools/tools/Ht_metrics.py +116 -0
  100. beratools/tools/__init__.py +7 -0
  101. beratools/tools/batch_processing.py +132 -0
  102. beratools/tools/canopy_threshold_relative.py +670 -0
  103. beratools/tools/canopycostraster.py +222 -0
  104. beratools/tools/centerline.py +176 -0
  105. beratools/tools/common.py +885 -0
  106. beratools/tools/fl_regen_csf.py +428 -0
  107. beratools/tools/forest_line_attributes.py +408 -0
  108. beratools/tools/forest_line_ecosite.py +216 -0
  109. beratools/tools/lapis_all.py +103 -0
  110. beratools/tools/least_cost_path_from_chm.py +152 -0
  111. beratools/tools/line_footprint_absolute.py +363 -0
  112. beratools/tools/line_footprint_fixed.py +282 -0
  113. beratools/tools/line_footprint_functions.py +720 -0
  114. beratools/tools/line_footprint_relative.py +64 -0
  115. beratools/tools/ln_relative_metrics.py +615 -0
  116. beratools/tools/r_cal_lpi_elai.r +25 -0
  117. beratools/tools/r_generate_pd_focalraster.r +101 -0
  118. beratools/tools/r_interface.py +80 -0
  119. beratools/tools/r_point_density.r +9 -0
  120. beratools/tools/rpy_chm2trees.py +86 -0
  121. beratools/tools/rpy_dsm_chm_by.py +81 -0
  122. beratools/tools/rpy_dtm_by.py +63 -0
  123. beratools/tools/rpy_find_cellsize.py +43 -0
  124. beratools/tools/rpy_gnd_csf.py +74 -0
  125. beratools/tools/rpy_hummock_hollow.py +85 -0
  126. beratools/tools/rpy_hummock_hollow_raster.py +71 -0
  127. beratools/tools/rpy_las_info.py +51 -0
  128. beratools/tools/rpy_laz2las.py +40 -0
  129. beratools/tools/rpy_lpi_elai_lascat.py +466 -0
  130. beratools/tools/rpy_normalized_lidar_by.py +56 -0
  131. beratools/tools/rpy_percent_above_dbh.py +80 -0
  132. beratools/tools/rpy_points2trees.py +88 -0
  133. beratools/tools/rpy_vegcoverage.py +94 -0
  134. beratools/tools/tiler.py +206 -0
  135. beratools/tools/tool_template.py +54 -0
  136. beratools/tools/vertex_optimization.py +620 -0
  137. beratools/tools/zonal_threshold.py +144 -0
  138. beratools-0.2.0.dist-info/METADATA +63 -0
  139. beratools-0.2.0.dist-info/RECORD +142 -0
  140. beratools-0.2.0.dist-info/WHEEL +4 -0
  141. beratools-0.2.0.dist-info/entry_points.txt +2 -0
  142. beratools-0.2.0.dist-info/licenses/LICENSE +22 -0
@@ -0,0 +1,282 @@
1
+ import time
2
+ from shapely.geometry import Polygon, MultiPolygon, LineString, MultiLineString
3
+ from beratools.tools.common import *
4
+
5
+
6
+ def prepare_line_args(shp_line, shp_poly, n_samples, offset):
7
+ """
8
+ Parameters
9
+ ----------
10
+ shp_line
11
+ shp_poly
12
+ n_samples
13
+ offset
14
+
15
+ Returns
16
+ -------
17
+ line_args : list
18
+ row :
19
+ inter_poly :
20
+ n_samples :
21
+ offset :
22
+ i : line ID
23
+
24
+ """
25
+ line_gdf = gpd.read_file(shp_line)
26
+ poly_gdf = gpd.read_file(shp_poly)
27
+ spatial_index = poly_gdf.sindex
28
+ line_args = []
29
+
30
+ i = 0
31
+ for i, row in line_gdf.iterrows():
32
+ line = row.geometry
33
+
34
+ # Skip rows where geometry is None
35
+ if line is None:
36
+ print(row)
37
+ continue
38
+
39
+ inter_poly = poly_gdf.iloc[spatial_index.query(line)]
40
+ line_args.append([line_gdf.iloc[[i]], inter_poly, n_samples, offset, i])
41
+
42
+ return line_args
43
+
44
+
45
+ # Calculating Line Widths
46
+ def generate_sample_points(line, n_samples=10):
47
+ """
48
+ Generate evenly spaced points along a line.
49
+
50
+ Parameters
51
+ ----------
52
+ line : shapely LineString
53
+ The line along which to generate points.
54
+ n_samples : int, optional
55
+ The number of points to generate (default is 10).
56
+
57
+ Returns
58
+ -------
59
+ list
60
+ List of shapely Point objects.
61
+ """
62
+ # return [line.interpolate(i / n_samples, normalized=True) for i in range(n_samples)]
63
+ return [Point(item) for item in list(line.coords)]
64
+
65
+
66
+ def generate_perpendicular_line(point, line, offset=FP_PERP_LINE_OFFSET):
67
+ """
68
+ Generate a perpendicular line to the input line at the given point.
69
+
70
+ Parameters
71
+ ----------
72
+ point : shapely.geometry.Point
73
+ The point on the line where the perpendicular should be generated.
74
+ line : shapely.geometry.LineString
75
+ The line to which the perpendicular line will be generated.
76
+ offset : float, optional
77
+ The length of the perpendicular line.
78
+
79
+ Returns
80
+ -------
81
+ shapely.geometry.LineString
82
+ The generated perpendicular line.
83
+ """
84
+ # Compute the angle of the line
85
+ p1, p2 = line.coords[0], line.coords[-1] # Modify this line
86
+ angle = np.arctan2(p2[1] - p1[1], p2[0] - p1[0])
87
+
88
+ # Compute the angle of the perpendicular line
89
+ angle_perp = angle + np.pi / 2.0 # Perpendicular angle
90
+
91
+ # Generate the perpendicular line
92
+ perp_line = LineString([(point.x - offset * np.cos(angle_perp), point.y - offset * np.sin(angle_perp)),
93
+ (point.x + offset * np.cos(angle_perp), point.y + offset * np.sin(angle_perp))])
94
+
95
+ return perp_line
96
+
97
+
98
+ def process_single_line(line_arg):
99
+ row = line_arg[0]
100
+ inter_poly = line_arg[1]
101
+ n_samples = line_arg[2]
102
+ offset = line_arg[3]
103
+ line_id = line_arg[4]
104
+
105
+ widths, line, perp_lines = calculate_average_width(row.iloc[0].geometry, inter_poly, offset, n_samples)
106
+
107
+ # Calculate the 75th percentile width
108
+ # filter zeros in width array
109
+ arr_filter = [False if math.isclose(i, 0.0) else True for i in widths]
110
+ widths = widths[arr_filter]
111
+
112
+ q3_width = FP_FIXED_WIDTH_DEFAULT
113
+ q4_width = FP_FIXED_WIDTH_DEFAULT
114
+ try:
115
+ q3_width = np.percentile(widths, 40)
116
+ q4_width = np.percentile(widths, 90)
117
+ except Exception as e:
118
+ print(e)
119
+
120
+ # Store the 75th percentile width as a new attribute
121
+ row['avg_width'] = q3_width
122
+ row['max_width'] = q4_width
123
+ hist, bins = np.histogram(widths)
124
+ bins = pd.Series(bins).rolling(2).mean()[1:].to_numpy() # mid-points of bins
125
+ row['width_hist'] = str(hist)
126
+ row['width_bins'] = str(bins)
127
+
128
+ row['geometry'] = line
129
+ try:
130
+ row['perp_lines'] = perp_lines
131
+ except Exception as e:
132
+ print(e)
133
+
134
+ print('line processed: {}'.format(line_id))
135
+
136
+ return row
137
+
138
+
139
+ def generate_fixed_width_footprint(line_gdf, shp_footprint, max_width=False):
140
+ """
141
+ Creates a buffer around each line in the GeoDataFrame using its 'max_width' attribute and
142
+ saves the resulting polygons in a new shapefile.
143
+
144
+ Parameters:
145
+ - line_gdf: A GeoDataFrame containing LineString geometries with 'max_width' attribute.
146
+ - output_file_path: The path where the output shapefile will be stored.
147
+ """
148
+ # Create a new GeoDataFrame with the buffer polygons
149
+ buffer_gdf = line_gdf.copy(deep=True)
150
+
151
+ mean_avg_width = line_gdf['avg_width'].mean()
152
+ mean_max_width = line_gdf['max_width'].mean()
153
+
154
+ line_gdf['avg_width'].fillna(mean_avg_width, inplace=True)
155
+ line_gdf['max_width'].fillna(mean_max_width, inplace=True)
156
+
157
+ line_gdf['avg_width'].replace(0.0, mean_avg_width, inplace=True)
158
+ line_gdf['max_width'].replace(0.0, mean_max_width, inplace=True)
159
+
160
+ if not max_width:
161
+ print('Using quantile 75% width')
162
+ buffer_gdf['geometry'] = line_gdf.apply(
163
+ lambda row: row.geometry.buffer(row.avg_width / 2) if row.geometry is not None else None, axis=1)
164
+ else:
165
+ print('Using quantile 90% + 20% width')
166
+ buffer_gdf['geometry'] = line_gdf.apply(
167
+ lambda row: row.geometry.buffer(row.max_width * 1.2 / 2) if row.geometry is not None else None, axis=1)
168
+
169
+ return buffer_gdf
170
+
171
+
172
+ def smooth_linestring(line, tolerance=1.0):
173
+ """
174
+ Smooths a LineString geometry using the Ramer-Douglas-Peucker algorithm.
175
+
176
+ Parameters:
177
+ - line: The LineString geometry to smooth.
178
+ - tolerance: The maximum distance from a point to a line for the point to be considered part of the line.
179
+
180
+ Returns:
181
+ The smoothed LineString geometry.
182
+ """
183
+ # simplified_line = line.simplify(tolerance)
184
+ simplified_line = line
185
+ return simplified_line
186
+
187
+
188
+ def calculate_average_width(line, polygon, offset, n_samples):
189
+ """
190
+ Calculates the average width of a polygon perpendicular to the given line.
191
+ """
192
+ # Smooth the line
193
+ line = smooth_linestring(line, tolerance=1.0)
194
+
195
+ valid_widths = 0
196
+ sample_points = generate_sample_points(line, n_samples=n_samples)
197
+ sample_points_pairs = list(zip(sample_points[:-2], sample_points[1:-1], sample_points[2:]))
198
+ widths = np.zeros(len(sample_points_pairs))
199
+ perp_lines = []
200
+
201
+ # remove polygon holes
202
+ poly_list = []
203
+ for geom in polygon.geometry:
204
+ if type(geom) is MultiPolygon:
205
+ for item in geom.geoms:
206
+ poly_list.append(Polygon(list(item.exterior.coords)))
207
+ else:
208
+ poly_list.append(Polygon(list(geom.exterior.coords)))
209
+
210
+ polygon_no_holes = gpd.GeoDataFrame(geometry=poly_list, crs=polygon.crs)
211
+
212
+ for i, points in enumerate(sample_points_pairs):
213
+ perp_line = generate_perpendicular_line_precise(points, offset=offset)
214
+
215
+ polygon_intersect = polygon_no_holes.iloc[polygon_no_holes.sindex.query(perp_line)]
216
+ intersections = polygon_intersect.intersection(perp_line)
217
+
218
+ line_list = []
219
+ try:
220
+ for inter in intersections:
221
+ if not inter.is_empty:
222
+ if type(inter) is MultiLineString:
223
+ line_list += list(inter.geoms)
224
+ else:
225
+ line_list.append(inter)
226
+
227
+ perp_lines += line_list
228
+ except Exception as e:
229
+ print(e)
230
+
231
+ try:
232
+ for item in line_list:
233
+ widths[i] = max(widths[i], item.length)
234
+ valid_widths += 1
235
+ except Exception as e:
236
+ print(e)
237
+
238
+ return widths, line, MultiLineString(perp_lines)
239
+
240
+
241
+ def line_footprint_fixed(callback, in_line, in_footprint, n_samples, offset, max_width,
242
+ out_footprint, processes, verbose):
243
+ n_samples = int(n_samples)
244
+ offset = float(offset)
245
+ line_args = prepare_line_args(in_line, in_footprint, n_samples, offset)
246
+
247
+ out_lines = execute_multiprocessing(process_single_line, line_args, 'Fixed footprint',
248
+ processes, 1, verbose=verbose)
249
+ line_attr = pd.concat(out_lines)
250
+
251
+ # create fixed width footprint
252
+ buffer_gdf = generate_fixed_width_footprint(line_attr, in_footprint, max_width=max_width)
253
+
254
+ # Save the lines with attributes and polygons to a new shapefile
255
+ perp_lines_gdf = buffer_gdf.copy(deep=True)
256
+ buffer_gdf = buffer_gdf.drop(columns=['perp_lines'])
257
+ buffer_gdf.crs = perp_lines_gdf.crs
258
+ buffer_gdf.to_file(out_footprint)
259
+
260
+ # perpendicular lines
261
+ perp_lines_gdf = perp_lines_gdf.set_geometry('perp_lines')
262
+ perp_lines_gdf = perp_lines_gdf.drop(columns=['geometry'])
263
+ perp_lines_gdf.crs = buffer_gdf.crs
264
+ perp_lines_path = Path(out_footprint).with_stem(Path(out_footprint).stem + '_perp_lines')
265
+ perp_lines_gdf.to_file(perp_lines_path)
266
+
267
+ geojson_path = Path(out_footprint).with_suffix('.geojson')
268
+ buffer_gpd_4326 = buffer_gdf.to_crs('EPSG:4326')
269
+ buffer_gpd_4326.to_file(geojson_path.as_posix(), driver='GeoJSON')
270
+
271
+ gdf_simplified_path = Path(in_line).with_stem(Path(in_line).stem + "_simplified")
272
+ line_attr = line_attr.drop(columns='perp_lines')
273
+ line_attr.to_file(gdf_simplified_path)
274
+
275
+ callback('Fixed width footprint tool finished.')
276
+
277
+
278
+ if __name__ == '__main__':
279
+ in_args, in_verbose = check_arguments()
280
+ start_time = time.time()
281
+ line_footprint_fixed(print, **in_args.input, processes=int(in_args.processes), verbose=in_verbose)
282
+ print('Elapsed time: {}'.format(time.time() - start_time))