BERATools 0.2.0__py3-none-any.whl → 0.2.2__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.
- beratools/__init__.py +1 -7
- beratools/core/algo_centerline.py +491 -351
- beratools/core/algo_common.py +497 -0
- beratools/core/algo_cost.py +192 -0
- beratools/core/{dijkstra_algorithm.py → algo_dijkstra.py} +503 -460
- beratools/core/algo_footprint_rel.py +577 -0
- beratools/core/algo_line_grouping.py +944 -0
- beratools/core/algo_merge_lines.py +214 -0
- beratools/core/algo_split_with_lines.py +304 -0
- beratools/core/algo_tiler.py +428 -0
- beratools/core/algo_vertex_optimization.py +469 -0
- beratools/core/constants.py +52 -86
- beratools/core/logger.py +76 -85
- beratools/core/tool_base.py +196 -133
- beratools/gui/__init__.py +11 -15
- beratools/gui/{beratools.json → assets/beratools.json} +2185 -2300
- beratools/gui/batch_processing_dlg.py +513 -463
- beratools/gui/bt_data.py +481 -487
- beratools/gui/bt_gui_main.py +710 -691
- beratools/gui/main.py +26 -0
- beratools/gui/map_window.py +162 -146
- beratools/gui/tool_widgets.py +725 -493
- beratools/tools/Beratools_r_script.r +1120 -1120
- beratools/tools/Ht_metrics.py +116 -116
- beratools/tools/__init__.py +7 -7
- beratools/tools/batch_processing.py +136 -132
- beratools/tools/canopy_threshold_relative.py +672 -670
- beratools/tools/canopycostraster.py +222 -222
- beratools/tools/centerline.py +136 -176
- beratools/tools/common.py +857 -885
- beratools/tools/fl_regen_csf.py +428 -428
- beratools/tools/forest_line_attributes.py +408 -408
- beratools/tools/line_footprint_absolute.py +213 -363
- beratools/tools/line_footprint_fixed.py +436 -282
- beratools/tools/line_footprint_functions.py +733 -720
- beratools/tools/line_footprint_relative.py +73 -64
- beratools/tools/line_grouping.py +45 -0
- beratools/tools/ln_relative_metrics.py +615 -615
- beratools/tools/r_cal_lpi_elai.r +24 -24
- beratools/tools/r_generate_pd_focalraster.r +100 -100
- beratools/tools/r_interface.py +79 -79
- beratools/tools/r_point_density.r +8 -8
- beratools/tools/rpy_chm2trees.py +86 -86
- beratools/tools/rpy_dsm_chm_by.py +81 -81
- beratools/tools/rpy_dtm_by.py +63 -63
- beratools/tools/rpy_find_cellsize.py +43 -43
- beratools/tools/rpy_gnd_csf.py +74 -74
- beratools/tools/rpy_hummock_hollow.py +85 -85
- beratools/tools/rpy_hummock_hollow_raster.py +71 -71
- beratools/tools/rpy_las_info.py +51 -51
- beratools/tools/rpy_laz2las.py +40 -40
- beratools/tools/rpy_lpi_elai_lascat.py +466 -466
- beratools/tools/rpy_normalized_lidar_by.py +56 -56
- beratools/tools/rpy_percent_above_dbh.py +80 -80
- beratools/tools/rpy_points2trees.py +88 -88
- beratools/tools/rpy_vegcoverage.py +94 -94
- beratools/tools/tiler.py +48 -206
- beratools/tools/tool_template.py +69 -54
- beratools/tools/vertex_optimization.py +61 -620
- beratools/tools/zonal_threshold.py +144 -144
- beratools-0.2.2.dist-info/METADATA +108 -0
- beratools-0.2.2.dist-info/RECORD +74 -0
- {beratools-0.2.0.dist-info → beratools-0.2.2.dist-info}/WHEEL +1 -1
- {beratools-0.2.0.dist-info → beratools-0.2.2.dist-info}/licenses/LICENSE +22 -22
- beratools/gui/cli.py +0 -18
- beratools/gui/gui.json +0 -8
- beratools/gui_tk/ASCII Banners.txt +0 -248
- beratools/gui_tk/__init__.py +0 -20
- beratools/gui_tk/beratools_main.py +0 -515
- beratools/gui_tk/bt_widgets.py +0 -442
- beratools/gui_tk/cli.py +0 -18
- beratools/gui_tk/img/BERALogo.png +0 -0
- beratools/gui_tk/img/closed.gif +0 -0
- beratools/gui_tk/img/closed.png +0 -0
- beratools/gui_tk/img/open.gif +0 -0
- beratools/gui_tk/img/open.png +0 -0
- beratools/gui_tk/img/tool.gif +0 -0
- beratools/gui_tk/img/tool.png +0 -0
- beratools/gui_tk/main.py +0 -14
- beratools/gui_tk/map_window.py +0 -144
- beratools/gui_tk/runner.py +0 -1481
- beratools/gui_tk/tooltip.py +0 -55
- beratools/third_party/pyqtlet2/__init__.py +0 -9
- beratools/third_party/pyqtlet2/leaflet/__init__.py +0 -26
- beratools/third_party/pyqtlet2/leaflet/control/__init__.py +0 -6
- beratools/third_party/pyqtlet2/leaflet/control/control.py +0 -59
- beratools/third_party/pyqtlet2/leaflet/control/draw.py +0 -52
- beratools/third_party/pyqtlet2/leaflet/control/layers.py +0 -20
- beratools/third_party/pyqtlet2/leaflet/core/Parser.py +0 -24
- beratools/third_party/pyqtlet2/leaflet/core/__init__.py +0 -2
- beratools/third_party/pyqtlet2/leaflet/core/evented.py +0 -180
- beratools/third_party/pyqtlet2/leaflet/layer/__init__.py +0 -5
- beratools/third_party/pyqtlet2/leaflet/layer/featuregroup.py +0 -34
- beratools/third_party/pyqtlet2/leaflet/layer/icon/__init__.py +0 -1
- beratools/third_party/pyqtlet2/leaflet/layer/icon/icon.py +0 -30
- beratools/third_party/pyqtlet2/leaflet/layer/imageoverlay.py +0 -18
- beratools/third_party/pyqtlet2/leaflet/layer/layer.py +0 -105
- beratools/third_party/pyqtlet2/leaflet/layer/layergroup.py +0 -45
- beratools/third_party/pyqtlet2/leaflet/layer/marker/__init__.py +0 -1
- beratools/third_party/pyqtlet2/leaflet/layer/marker/marker.py +0 -91
- beratools/third_party/pyqtlet2/leaflet/layer/tile/__init__.py +0 -2
- beratools/third_party/pyqtlet2/leaflet/layer/tile/gridlayer.py +0 -4
- beratools/third_party/pyqtlet2/leaflet/layer/tile/tilelayer.py +0 -16
- beratools/third_party/pyqtlet2/leaflet/layer/vector/__init__.py +0 -5
- beratools/third_party/pyqtlet2/leaflet/layer/vector/circle.py +0 -15
- beratools/third_party/pyqtlet2/leaflet/layer/vector/circlemarker.py +0 -18
- beratools/third_party/pyqtlet2/leaflet/layer/vector/path.py +0 -5
- beratools/third_party/pyqtlet2/leaflet/layer/vector/polygon.py +0 -14
- beratools/third_party/pyqtlet2/leaflet/layer/vector/polyline.py +0 -18
- beratools/third_party/pyqtlet2/leaflet/layer/vector/rectangle.py +0 -14
- beratools/third_party/pyqtlet2/leaflet/map/__init__.py +0 -1
- beratools/third_party/pyqtlet2/leaflet/map/map.py +0 -220
- beratools/third_party/pyqtlet2/mapwidget.py +0 -45
- beratools/third_party/pyqtlet2/web/custom.js +0 -43
- beratools/third_party/pyqtlet2/web/map.html +0 -23
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/layers-2x.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/layers.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-icon-2x.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-icon.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-shadow.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.css +0 -656
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.js +0 -6
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.codeclimate.yml +0 -14
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.editorconfig +0 -4
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.gitattributes +0 -22
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.travis.yml +0 -43
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/LICENSE +0 -20
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/layers-2x.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/layers.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-icon-2x.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-icon.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-shadow.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet-2x.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet.svg +0 -156
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.css +0 -10
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.js +0 -10
- beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/LICENSE +0 -22
- beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/leaflet.rotatedMarker.js +0 -57
- beratools/tools/forest_line_ecosite.py +0 -216
- beratools/tools/lapis_all.py +0 -103
- beratools/tools/least_cost_path_from_chm.py +0 -152
- beratools-0.2.0.dist-info/METADATA +0 -63
- beratools-0.2.0.dist-info/RECORD +0 -142
- /beratools/gui/{img → assets}/BERALogo.png +0 -0
- /beratools/gui/{img → assets}/closed.gif +0 -0
- /beratools/gui/{img → assets}/closed.png +0 -0
- /beratools/{gui_tk → gui/assets}/gui.json +0 -0
- /beratools/gui/{img → assets}/open.gif +0 -0
- /beratools/gui/{img → assets}/open.png +0 -0
- /beratools/gui/{img → assets}/tool.gif +0 -0
- /beratools/gui/{img → assets}/tool.png +0 -0
- {beratools-0.2.0.dist-info → beratools-0.2.2.dist-info}/entry_points.txt +0 -0
|
@@ -1,363 +1,213 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
# TODO: deal with NODATA
|
|
215
|
-
clip_cost, out_meta = clip_raster(in_cost, feat, max_ln_width)
|
|
216
|
-
out_transform = out_meta['transform']
|
|
217
|
-
cell_size_x = out_transform[0]
|
|
218
|
-
cell_size_y = -out_transform[4]
|
|
219
|
-
|
|
220
|
-
if not HAS_COST_RASTER:
|
|
221
|
-
clip_cost, clip_canopy = cost_raster(clip_cost, out_meta)
|
|
222
|
-
else:
|
|
223
|
-
clip_canopy, out_meta = clip_raster(in_canopy, feat, max_ln_width)
|
|
224
|
-
|
|
225
|
-
# Work out the corridor from both end of the centerline
|
|
226
|
-
try:
|
|
227
|
-
if len(clip_canopy.shape) > 2:
|
|
228
|
-
clip_canopy = np.squeeze(clip_canopy, axis=0)
|
|
229
|
-
|
|
230
|
-
transformer = rasterio.transform.AffineTransformer(out_transform)
|
|
231
|
-
source = [transformer.rowcol(x1, y1)]
|
|
232
|
-
destination = [transformer.rowcol(x2, y2)]
|
|
233
|
-
|
|
234
|
-
corridor_thresh = corridor_raster(clip_cost, out_meta, source, destination,
|
|
235
|
-
(cell_size_x, cell_size_y), corridor_th_value)
|
|
236
|
-
|
|
237
|
-
def morph_raster(corridor_raster, canopy_raster, exp_shk_cell, cell_size_x):
|
|
238
|
-
# Process: Stamp CC and Max Line Width
|
|
239
|
-
temp1 = (corridor_thresh + clip_canopy)
|
|
240
|
-
raster_class = np.ma.where(temp1 == 0, 1, 0).data
|
|
241
|
-
|
|
242
|
-
if exp_shk_cell > 0 and cell_size_x < 1:
|
|
243
|
-
# Process: Expand
|
|
244
|
-
# FLM original Expand equivalent
|
|
245
|
-
cell_size = int(exp_shk_cell * 2 + 1)
|
|
246
|
-
expanded = ndimage.grey_dilation(raster_class, size=(cell_size, cell_size))
|
|
247
|
-
|
|
248
|
-
# Process: Shrink
|
|
249
|
-
# FLM original Shrink equivalent
|
|
250
|
-
file_shrink = ndimage.grey_erosion(expanded, size=(cell_size, cell_size))
|
|
251
|
-
|
|
252
|
-
else:
|
|
253
|
-
if BT_DEBUGGING:
|
|
254
|
-
print('No Expand And Shrink cell performed.')
|
|
255
|
-
file_shrink = raster_class
|
|
256
|
-
|
|
257
|
-
# Process: Boundary Clean
|
|
258
|
-
clean_raster = ndimage.gaussian_filter(file_shrink, sigma=0, mode='nearest')
|
|
259
|
-
|
|
260
|
-
return clean_raster
|
|
261
|
-
|
|
262
|
-
clean_raster = morph_raster(corridor_thresh, in_canopy, exp_shk_cell, cell_size_x)
|
|
263
|
-
|
|
264
|
-
# creat mask for non-polygon area
|
|
265
|
-
msk = np.where(clean_raster == 1, True, False)
|
|
266
|
-
|
|
267
|
-
# Process: ndarray to shapely Polygon
|
|
268
|
-
out_polygon = features.shapes(clean_raster, mask=msk, transform=out_transform)
|
|
269
|
-
|
|
270
|
-
# create a shapely multipolygon
|
|
271
|
-
multi_polygon = []
|
|
272
|
-
for shp, value in out_polygon:
|
|
273
|
-
multi_polygon.append(shapely.geometry.shape(shp))
|
|
274
|
-
poly = shapely.geometry.MultiPolygon(multi_polygon)
|
|
275
|
-
|
|
276
|
-
# create a pandas dataframe for the footprint
|
|
277
|
-
out_data = pd.DataFrame({'OLnFID': [OID], 'OLnSEG': [FID], 'geometry': poly})
|
|
278
|
-
out_gdata = gpd.GeoDataFrame(out_data, geometry='geometry', crs=shapefile_proj)
|
|
279
|
-
|
|
280
|
-
if not GROUPING_SEGMENT:
|
|
281
|
-
print(f"LP:PSLS: Processing line ID: {dict_segment['OLnSEG']}, done.", flush=True)
|
|
282
|
-
|
|
283
|
-
# find contiguous corridor polygon for centerline
|
|
284
|
-
corridor_poly_gpd = find_corridor_polygon(corridor_thresh, out_transform, line_gpd)
|
|
285
|
-
centerline, status = find_centerline(corridor_poly_gpd.geometry.iloc[0], feat)
|
|
286
|
-
|
|
287
|
-
return out_gdata, corridor_poly_gpd, centerline
|
|
288
|
-
|
|
289
|
-
except Exception as e:
|
|
290
|
-
print(f'Exception: {e}')
|
|
291
|
-
return None
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
def line_prepare(callback, line_seg, in_canopy, in_cost, corridor_th_field, corridor_th_value,
|
|
295
|
-
max_ln_width, exp_shk_cell, proc_seg, out_footprint, out_centerline, ori_total_feat):
|
|
296
|
-
# get the list of original columns names
|
|
297
|
-
field_list_col = field_name_list(line_seg)
|
|
298
|
-
keep_field_name = []
|
|
299
|
-
for col_name in line_seg.columns:
|
|
300
|
-
if col_name != 'geometry':
|
|
301
|
-
keep_field_name.append(col_name)
|
|
302
|
-
|
|
303
|
-
list_of_segment = []
|
|
304
|
-
|
|
305
|
-
i = 0
|
|
306
|
-
# process when shapefile is not an empty feature class
|
|
307
|
-
if len(line_seg) > 0:
|
|
308
|
-
for row in range(0, len(line_seg)):
|
|
309
|
-
# creates a geometry object
|
|
310
|
-
line_gpd = line_seg.loc[[row]]
|
|
311
|
-
feat = line_gpd.geometry.iloc[0]
|
|
312
|
-
if feat:
|
|
313
|
-
feature_attributes = {'seg_length': feat.length, 'geometry': feat,
|
|
314
|
-
'Proj_crs': line_seg.crs, 'line_gpd': line_gpd}
|
|
315
|
-
|
|
316
|
-
for col_name in keep_field_name:
|
|
317
|
-
feature_attributes[col_name] = line_seg.loc[row, col_name]
|
|
318
|
-
list_of_segment.append(feature_attributes)
|
|
319
|
-
i += 1
|
|
320
|
-
|
|
321
|
-
print(f"There are {ori_total_feat} lines to be processed.")
|
|
322
|
-
else:
|
|
323
|
-
print("Input line feature is corrupted, exit!")
|
|
324
|
-
exit()
|
|
325
|
-
|
|
326
|
-
# Add tools arguments into GeoDataFrame record
|
|
327
|
-
for record in list_of_segment:
|
|
328
|
-
record['in_canopy'] = in_canopy
|
|
329
|
-
record['in_cost'] = in_cost
|
|
330
|
-
record['corridor_th_field'] = corridor_th_field
|
|
331
|
-
record['corridor_th_value'] = record['CorridorTh']
|
|
332
|
-
record['max_ln_width'] = max_ln_width
|
|
333
|
-
record['exp_shk_cell'] = exp_shk_cell
|
|
334
|
-
record['proc_seg'] = proc_seg
|
|
335
|
-
record['out_footprint'] = out_footprint
|
|
336
|
-
record['out_centerline'] = out_centerline
|
|
337
|
-
record['org_col'] = field_list_col
|
|
338
|
-
|
|
339
|
-
# TODO: data type changed - return list of GeoDataFrame represents each line or segment
|
|
340
|
-
# returns list of list of line attributes, arguments and line gpd
|
|
341
|
-
if GROUPING_SEGMENT:
|
|
342
|
-
# group line segments by line id
|
|
343
|
-
def key_func(x):
|
|
344
|
-
return x['OLnFID']
|
|
345
|
-
|
|
346
|
-
lines = []
|
|
347
|
-
|
|
348
|
-
for key, group in itertools.groupby(list_of_segment, key_func):
|
|
349
|
-
lines.append(list(group))
|
|
350
|
-
|
|
351
|
-
return lines
|
|
352
|
-
else:
|
|
353
|
-
return list_of_segment
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if __name__ == '__main__':
|
|
357
|
-
start_time = time.time()
|
|
358
|
-
print('Footprint processing started')
|
|
359
|
-
print(f'Current time: {time.strftime("%b %Y %H:%M:%S", time.localtime())}')
|
|
360
|
-
|
|
361
|
-
in_args, in_verbose = check_arguments()
|
|
362
|
-
line_footprint(print, **in_args.input, processes=int(in_args.processes), verbose=in_verbose)
|
|
363
|
-
print(f'Current time: {time.strftime("%b %Y %H:%M:%S", time.localtime())}')
|
|
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 main interface for canopy footprint tool.
|
|
14
|
+
The tool is used to generate the footprint of a line based on absolute threshold.
|
|
15
|
+
"""
|
|
16
|
+
import time
|
|
17
|
+
|
|
18
|
+
import geopandas as gpd
|
|
19
|
+
import numpy as np
|
|
20
|
+
import pandas as pd
|
|
21
|
+
import rasterio
|
|
22
|
+
import shapely
|
|
23
|
+
|
|
24
|
+
import beratools.core.algo_centerline as algo_cl
|
|
25
|
+
import beratools.core.algo_common as algo_common
|
|
26
|
+
import beratools.core.algo_cost as algo_cost
|
|
27
|
+
import beratools.core.constants as bt_const
|
|
28
|
+
import beratools.core.tool_base as bt_base
|
|
29
|
+
import beratools.tools.common as bt_common
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FootprintAbsolute:
|
|
33
|
+
"""Class to compute the footprint of a line based on absolute threshold."""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
line_seg,
|
|
38
|
+
in_chm,
|
|
39
|
+
corridor_thresh,
|
|
40
|
+
max_ln_width,
|
|
41
|
+
exp_shk_cell,
|
|
42
|
+
):
|
|
43
|
+
self.line_seg = line_seg
|
|
44
|
+
self.in_chm = in_chm
|
|
45
|
+
self.corridor_thresh = corridor_thresh
|
|
46
|
+
self.max_ln_width = max_ln_width
|
|
47
|
+
self.exp_shk_cell = exp_shk_cell
|
|
48
|
+
|
|
49
|
+
self.footprint = None
|
|
50
|
+
self.corridor_poly_gpd = None
|
|
51
|
+
self.centerline = None
|
|
52
|
+
|
|
53
|
+
def compute(self):
|
|
54
|
+
"""Generate line footprint."""
|
|
55
|
+
in_chm = self.in_chm
|
|
56
|
+
corridor_thresh = self.corridor_thresh
|
|
57
|
+
line_gpd = self.line_seg
|
|
58
|
+
max_ln_width = self.max_ln_width
|
|
59
|
+
exp_shk_cell = self.exp_shk_cell
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
corridor_thresh = float(corridor_thresh)
|
|
63
|
+
if corridor_thresh < 0.0:
|
|
64
|
+
corridor_thresh = 3.0
|
|
65
|
+
except ValueError as e:
|
|
66
|
+
print(f"process_single_line_segment: {e}")
|
|
67
|
+
corridor_thresh = 3.0
|
|
68
|
+
|
|
69
|
+
segment_list = []
|
|
70
|
+
feat = self.line_seg.geometry[0]
|
|
71
|
+
for coord in feat.coords:
|
|
72
|
+
segment_list.append(coord)
|
|
73
|
+
|
|
74
|
+
# Find origin and destination coordinates
|
|
75
|
+
x1, y1 = segment_list[0][0], segment_list[0][1]
|
|
76
|
+
x2, y2 = segment_list[-1][0], segment_list[-1][1]
|
|
77
|
+
|
|
78
|
+
# Buffer around line and clip cost raster and canopy raster
|
|
79
|
+
# TODO: deal with NODATA
|
|
80
|
+
clip_cost, out_meta = bt_common.clip_raster(in_chm, feat, max_ln_width)
|
|
81
|
+
out_transform = out_meta["transform"]
|
|
82
|
+
cell_size_x = out_transform[0]
|
|
83
|
+
cell_size_y = -out_transform[4]
|
|
84
|
+
|
|
85
|
+
clip_cost, clip_canopy = algo_cost.cost_raster(clip_cost, out_meta)
|
|
86
|
+
|
|
87
|
+
# Work out the corridor from both end of the centerline
|
|
88
|
+
if len(clip_canopy.shape) > 2:
|
|
89
|
+
clip_canopy = np.squeeze(clip_canopy, axis=0)
|
|
90
|
+
|
|
91
|
+
transformer = rasterio.transform.AffineTransformer(out_transform)
|
|
92
|
+
source = [transformer.rowcol(x1, y1)]
|
|
93
|
+
destination = [transformer.rowcol(x2, y2)]
|
|
94
|
+
|
|
95
|
+
corridor_thresh = algo_common.corridor_raster(
|
|
96
|
+
clip_cost,
|
|
97
|
+
out_meta,
|
|
98
|
+
source,
|
|
99
|
+
destination,
|
|
100
|
+
(cell_size_x, cell_size_y),
|
|
101
|
+
corridor_thresh,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
clean_raster = algo_common.morph_raster(
|
|
105
|
+
corridor_thresh, clip_canopy, exp_shk_cell, cell_size_x
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# create mask for non-polygon area
|
|
109
|
+
msk = np.where(clean_raster == 1, True, False)
|
|
110
|
+
if clean_raster.dtype == np.int64:
|
|
111
|
+
clean_raster = clean_raster.astype(np.int32)
|
|
112
|
+
|
|
113
|
+
# Process: ndarray to shapely Polygon
|
|
114
|
+
out_polygon = rasterio.features.shapes(
|
|
115
|
+
clean_raster, mask=msk, transform=out_transform
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# create a shapely multipolygon
|
|
119
|
+
multi_polygon = []
|
|
120
|
+
for shp, value in out_polygon:
|
|
121
|
+
multi_polygon.append(shapely.geometry.shape(shp))
|
|
122
|
+
poly = shapely.geometry.MultiPolygon(multi_polygon)
|
|
123
|
+
|
|
124
|
+
# create a pandas dataframe for the footprint
|
|
125
|
+
footprint = gpd.GeoDataFrame(geometry=[poly], crs=self.line_seg.crs)
|
|
126
|
+
|
|
127
|
+
# find contiguous corridor polygon for centerline
|
|
128
|
+
corridor_poly_gpd = algo_cl.find_corridor_polygon(
|
|
129
|
+
corridor_thresh, out_transform, line_gpd
|
|
130
|
+
)
|
|
131
|
+
centerline, status = algo_cl.find_centerline(
|
|
132
|
+
corridor_poly_gpd.geometry.iloc[0], feat
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
self.footprint = footprint
|
|
136
|
+
self.corridor_poly_gpd = corridor_poly_gpd
|
|
137
|
+
self.centerline = centerline
|
|
138
|
+
|
|
139
|
+
def process_single_line(line_footprint):
|
|
140
|
+
line_footprint.compute()
|
|
141
|
+
return line_footprint
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def generate_line_class_list(
|
|
145
|
+
in_line,
|
|
146
|
+
in_chm,
|
|
147
|
+
corridor_thresh,
|
|
148
|
+
max_ln_width,
|
|
149
|
+
exp_shk_cell,
|
|
150
|
+
in_layer=None,
|
|
151
|
+
):
|
|
152
|
+
line_classes = []
|
|
153
|
+
line_list = algo_common.prepare_lines_gdf(in_line, in_layer, proc_segments=False)
|
|
154
|
+
|
|
155
|
+
for line in line_list:
|
|
156
|
+
line_classes.append(
|
|
157
|
+
FootprintAbsolute(line, in_chm, corridor_thresh, max_ln_width, exp_shk_cell)
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return line_classes
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def line_footprint_abs(
|
|
164
|
+
in_line,
|
|
165
|
+
in_chm,
|
|
166
|
+
corridor_thresh,
|
|
167
|
+
max_ln_width,
|
|
168
|
+
exp_shk_cell,
|
|
169
|
+
out_footprint,
|
|
170
|
+
processes,
|
|
171
|
+
verbose,
|
|
172
|
+
in_layer=None,
|
|
173
|
+
out_layer=None,
|
|
174
|
+
parallel_mode=bt_const.ParallelMode.MULTIPROCESSING
|
|
175
|
+
):
|
|
176
|
+
max_ln_width = float(max_ln_width)
|
|
177
|
+
exp_shk_cell = int(exp_shk_cell)
|
|
178
|
+
|
|
179
|
+
footprint_list = []
|
|
180
|
+
poly_list = []
|
|
181
|
+
|
|
182
|
+
line_class_list = generate_line_class_list(
|
|
183
|
+
in_line, in_chm, corridor_thresh, max_ln_width, exp_shk_cell, in_layer
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
feat_list = bt_base.execute_multiprocessing(
|
|
187
|
+
process_single_line,
|
|
188
|
+
line_class_list,
|
|
189
|
+
"Line footprint",
|
|
190
|
+
processes,
|
|
191
|
+
1,
|
|
192
|
+
verbose=verbose,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if feat_list:
|
|
196
|
+
for i in feat_list:
|
|
197
|
+
footprint_list.append(i.footprint)
|
|
198
|
+
poly_list.append(i.corridor_poly_gpd)
|
|
199
|
+
|
|
200
|
+
results = gpd.GeoDataFrame(pd.concat(footprint_list))
|
|
201
|
+
results = results.reset_index(drop=True)
|
|
202
|
+
results.to_file(out_footprint, layer=out_layer)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
if __name__ == "__main__":
|
|
206
|
+
start_time = time.time()
|
|
207
|
+
print("Footprint processing started")
|
|
208
|
+
|
|
209
|
+
in_args, in_verbose = bt_common.check_arguments()
|
|
210
|
+
line_footprint_abs(
|
|
211
|
+
**in_args.input, processes=int(in_args.processes), verbose=in_verbose
|
|
212
|
+
)
|
|
213
|
+
print('Elapsed time: {}'.format(time.time() - start_time))
|