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.
- 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.1.dist-info/METADATA +109 -0
- beratools-0.2.1.dist-info/RECORD +74 -0
- {beratools-0.2.0.dist-info → beratools-0.2.1.dist-info}/WHEEL +1 -1
- {beratools-0.2.0.dist-info → beratools-0.2.1.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.1.dist-info}/entry_points.txt +0 -0
|
@@ -1,720 +1,733 @@
|
|
|
1
|
-
# ---------------------------------------------------------------------------
|
|
2
|
-
# Copyright (C) 2021 Applied Geospatial Research Group
|
|
3
|
-
#
|
|
4
|
-
# This program is free software: you can redistribute it and/or modify
|
|
5
|
-
# it under the terms of the GNU General Public License as published by
|
|
6
|
-
# the Free Software Foundation, version 3.
|
|
7
|
-
#
|
|
8
|
-
# This program is distributed in the hope that it will be useful,
|
|
9
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
-
# GNU General Public License for more details.
|
|
12
|
-
#
|
|
13
|
-
# You should have received a copy of the GNU General Public License
|
|
14
|
-
# along with this program. If not, see <https://gnu.org/licenses/gpl-3.0>.
|
|
15
|
-
#
|
|
16
|
-
# ---------------------------------------------------------------------------
|
|
17
|
-
#
|
|
18
|
-
# Refactor to use for produce dynamic footprint from dynamic canopy and cost raster with open source libraries
|
|
19
|
-
# Prerequisite: Line feature class must have the attribute Fields:"CorridorTh" "DynCanTh" "OLnFID"
|
|
20
|
-
# line_footprint_function.py
|
|
21
|
-
# Maverick Fong
|
|
22
|
-
# Date: 2023-Dec
|
|
23
|
-
# This script is part of the BERA toolset
|
|
24
|
-
# Webpage: https://github.com/
|
|
25
|
-
#
|
|
26
|
-
# Purpose: Creates dynamic footprint polygons for each input line based on a least
|
|
27
|
-
# cost corridor method and individual line thresholds.
|
|
28
|
-
#
|
|
29
|
-
# ---------------------------------------------------------------------------
|
|
30
|
-
|
|
31
|
-
import time
|
|
32
|
-
import numpy as np
|
|
33
|
-
|
|
34
|
-
import rasterio
|
|
35
|
-
from scipy import stats, ndimage
|
|
36
|
-
from geopandas import GeoDataFrame
|
|
37
|
-
from shapely import buffer
|
|
38
|
-
from rasterio import features
|
|
39
|
-
from xrspatial import convolution
|
|
40
|
-
|
|
41
|
-
import skimage
|
|
42
|
-
from skimage.morphology import *
|
|
43
|
-
from skimage.graph import MCP_Flexible
|
|
44
|
-
|
|
45
|
-
from beratools.core.constants import *
|
|
46
|
-
from beratools.core.algo_centerline import *
|
|
47
|
-
from beratools.tools.common import *
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
canopy_ht_threshold = line_df.
|
|
71
|
-
elif Side == '
|
|
72
|
-
canopy_ht_threshold =
|
|
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
|
-
def
|
|
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
|
-
nodata=BT_NODATA, filled=True)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
"
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
#
|
|
276
|
-
#
|
|
277
|
-
#
|
|
278
|
-
#
|
|
279
|
-
#
|
|
280
|
-
#
|
|
281
|
-
#
|
|
282
|
-
#
|
|
283
|
-
#
|
|
284
|
-
#
|
|
285
|
-
#
|
|
286
|
-
#
|
|
287
|
-
#
|
|
288
|
-
#
|
|
289
|
-
#
|
|
290
|
-
#
|
|
291
|
-
#
|
|
292
|
-
#
|
|
293
|
-
#
|
|
294
|
-
#
|
|
295
|
-
#
|
|
296
|
-
#
|
|
297
|
-
#
|
|
298
|
-
#
|
|
299
|
-
#
|
|
300
|
-
#
|
|
301
|
-
#
|
|
302
|
-
#
|
|
303
|
-
#
|
|
304
|
-
#
|
|
305
|
-
#
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
# [
|
|
344
|
-
#
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
#
|
|
348
|
-
|
|
349
|
-
#
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
#
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
#
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
#
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
#
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
#
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
#
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
#
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
# If
|
|
536
|
-
if '
|
|
537
|
-
print("
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
work_in_bufferR2
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
print('
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
#
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
poly_gpd.
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
print('
|
|
720
|
-
|
|
1
|
+
# ---------------------------------------------------------------------------
|
|
2
|
+
# Copyright (C) 2021 Applied Geospatial Research Group
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, version 3.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program. If not, see <https://gnu.org/licenses/gpl-3.0>.
|
|
15
|
+
#
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
#
|
|
18
|
+
# Refactor to use for produce dynamic footprint from dynamic canopy and cost raster with open source libraries
|
|
19
|
+
# Prerequisite: Line feature class must have the attribute Fields:"CorridorTh" "DynCanTh" "OLnFID"
|
|
20
|
+
# line_footprint_function.py
|
|
21
|
+
# Maverick Fong
|
|
22
|
+
# Date: 2023-Dec
|
|
23
|
+
# This script is part of the BERA toolset
|
|
24
|
+
# Webpage: https://github.com/
|
|
25
|
+
#
|
|
26
|
+
# Purpose: Creates dynamic footprint polygons for each input line based on a least
|
|
27
|
+
# cost corridor method and individual line thresholds.
|
|
28
|
+
#
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
import time
|
|
32
|
+
import numpy as np
|
|
33
|
+
|
|
34
|
+
import rasterio
|
|
35
|
+
from scipy import stats, ndimage
|
|
36
|
+
from geopandas import GeoDataFrame
|
|
37
|
+
from shapely import buffer
|
|
38
|
+
from rasterio import features
|
|
39
|
+
from xrspatial import convolution
|
|
40
|
+
|
|
41
|
+
import skimage
|
|
42
|
+
from skimage.morphology import *
|
|
43
|
+
from skimage.graph import MCP_Flexible
|
|
44
|
+
|
|
45
|
+
from beratools.core.constants import *
|
|
46
|
+
from beratools.core.algo_centerline import *
|
|
47
|
+
from beratools.tools.common import *
|
|
48
|
+
from beratools.core.tool_base import *
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def dyn_canopy_cost_raster(args):
|
|
52
|
+
# raster_obj = args[0]
|
|
53
|
+
in_chm_raster=args[0]
|
|
54
|
+
DynCanTh = args[1]
|
|
55
|
+
tree_radius = args[2]
|
|
56
|
+
max_line_dist = args[3]
|
|
57
|
+
canopy_avoid = args[4]
|
|
58
|
+
exponent = args[5]
|
|
59
|
+
res = args[6]
|
|
60
|
+
nodata = args[7]
|
|
61
|
+
line_df = args[8]
|
|
62
|
+
out_meta = args[9]
|
|
63
|
+
line_id = args[10]
|
|
64
|
+
Cut_Dist = args[11]
|
|
65
|
+
Side = args[12]
|
|
66
|
+
canopy_thresh_percentage = float(args[13]) / 100
|
|
67
|
+
line_buffer = args[14]
|
|
68
|
+
|
|
69
|
+
if Side == 'Left':
|
|
70
|
+
canopy_ht_threshold = line_df.CL_CutHt * canopy_thresh_percentage
|
|
71
|
+
elif Side == 'Right':
|
|
72
|
+
canopy_ht_threshold = line_df.CR_CutHt * canopy_thresh_percentage
|
|
73
|
+
elif Side == 'Center':
|
|
74
|
+
canopy_ht_threshold = DynCanTh * canopy_thresh_percentage
|
|
75
|
+
else:
|
|
76
|
+
|
|
77
|
+
canopy_ht_threshold = 0.5
|
|
78
|
+
|
|
79
|
+
canopy_ht_threshold = float(canopy_ht_threshold)
|
|
80
|
+
if canopy_ht_threshold <= 0:
|
|
81
|
+
canopy_ht_threshold = 0.5
|
|
82
|
+
tree_radius = float(tree_radius) # get the round up integer number for tree search radius
|
|
83
|
+
max_line_dist = float(max_line_dist)
|
|
84
|
+
canopy_avoid = float(canopy_avoid)
|
|
85
|
+
cost_raster_exponent = float(exponent)
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
|
|
89
|
+
clipped_rasterC, out_meta = clip_raster(in_chm_raster, line_buffer, 0)
|
|
90
|
+
|
|
91
|
+
#clipped_rasterC, out_transformC = rasterio.mask.mask(raster_obj, [line_buffer], crop=True,
|
|
92
|
+
# filled=False)
|
|
93
|
+
|
|
94
|
+
in_chm = np.squeeze(clipped_rasterC, axis=0)
|
|
95
|
+
|
|
96
|
+
# # make rasterio meta for saving raster later
|
|
97
|
+
# out_meta = raster_obj.meta.copy()
|
|
98
|
+
# out_meta.update({"driver": "GTiff",
|
|
99
|
+
# "height": in_chm.shape[0],
|
|
100
|
+
# "width": in_chm.shape[1],
|
|
101
|
+
# "nodata": BT_NODATA,
|
|
102
|
+
# "transform": out_transformC})
|
|
103
|
+
|
|
104
|
+
# print('Loading CHM ...')
|
|
105
|
+
cell_x, cell_y = out_meta['transform'][0], -out_meta['transform'][4]
|
|
106
|
+
|
|
107
|
+
# print('Preparing Kernel window ...')
|
|
108
|
+
kernel = convolution.circle_kernel(cell_x, cell_y, tree_radius)
|
|
109
|
+
|
|
110
|
+
# Generate Canopy Raster and return the Canopy array
|
|
111
|
+
dyn_canopy_ndarray = dyn_np_cc_map(in_chm, canopy_ht_threshold, BT_NODATA)
|
|
112
|
+
|
|
113
|
+
# Calculating focal statistic from canopy raster
|
|
114
|
+
cc_std, cc_mean = dyn_fs_raster_stdmean(dyn_canopy_ndarray, kernel, BT_NODATA)
|
|
115
|
+
|
|
116
|
+
# Smoothing raster
|
|
117
|
+
cc_smooth = dyn_smooth_cost(dyn_canopy_ndarray, max_line_dist, [cell_x, cell_y])
|
|
118
|
+
avoidance = max(min(float(canopy_avoid), 1), 0)
|
|
119
|
+
cost_clip = dyn_np_cost_raster(dyn_canopy_ndarray, cc_mean, cc_std,
|
|
120
|
+
cc_smooth, avoidance, cost_raster_exponent)
|
|
121
|
+
negative_cost_clip = np.where(np.isnan(cost_clip), -9999, cost_clip) # TODO was nodata, changed to BT_NODATA_COST
|
|
122
|
+
return (
|
|
123
|
+
line_df, dyn_canopy_ndarray, negative_cost_clip, out_meta,
|
|
124
|
+
max_line_dist, nodata, line_id, Cut_Dist, line_buffer
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
except Exception as e:
|
|
128
|
+
print("Error in createing (dynamic) cost raster @ {}: {}".format(line_id,e))
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def split_line_fc(line):
|
|
133
|
+
return list(map(LineString, zip(line.coords[:-1], line.coords[1:])))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def split_line_npart(line):
|
|
137
|
+
# Work out n parts for each line (divided by LP_SEGMENT_LENGTH)
|
|
138
|
+
n = int(np.ceil(line.length / LP_SEGMENT_LENGTH))
|
|
139
|
+
if n > 1:
|
|
140
|
+
# divided line into n-1 equal parts;
|
|
141
|
+
distances = np.linspace(0, line.length, n)
|
|
142
|
+
points = [line.interpolate(distance) for distance in distances]
|
|
143
|
+
line = LineString(points)
|
|
144
|
+
mline = split_line_fc(line)
|
|
145
|
+
else:
|
|
146
|
+
mline = line
|
|
147
|
+
return mline
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def split_into_segments(df):
|
|
151
|
+
odf = df
|
|
152
|
+
crs = odf.crs
|
|
153
|
+
if 'OLnSEG' not in odf.columns.array:
|
|
154
|
+
df['OLnSEG'] = np.nan
|
|
155
|
+
|
|
156
|
+
df = odf.assign(geometry=odf.apply(lambda x: split_line_fc(x.geometry), axis=1))
|
|
157
|
+
df = df.explode()
|
|
158
|
+
|
|
159
|
+
df['OLnSEG'] = df.groupby('OLnFID').cumcount()
|
|
160
|
+
gdf = GeoDataFrame(df, geometry=df.geometry, crs=crs)
|
|
161
|
+
gdf = gdf.sort_values(by=['OLnFID', 'OLnSEG'])
|
|
162
|
+
gdf = gdf.reset_index(drop=True)
|
|
163
|
+
return gdf
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def split_into_equal_nth_segments(df):
|
|
167
|
+
odf = df
|
|
168
|
+
crs = odf.crs
|
|
169
|
+
if 'OLnSEG' not in odf.columns.array:
|
|
170
|
+
df['OLnSEG'] = np.nan
|
|
171
|
+
df = odf.assign(geometry=odf.apply(lambda x: split_line_npart(x.geometry), axis=1))
|
|
172
|
+
df = df.explode(index_parts=True)
|
|
173
|
+
|
|
174
|
+
df['OLnSEG'] = df.groupby('OLnFID').cumcount()
|
|
175
|
+
gdf = GeoDataFrame(df, geometry=df.geometry, crs=crs)
|
|
176
|
+
gdf = gdf.sort_values(by=['OLnFID', 'OLnSEG'])
|
|
177
|
+
gdf = gdf.reset_index(drop=True)
|
|
178
|
+
return gdf
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def generate_line_args(line_seg, work_in_bufferL, work_in_bufferC, raster, tree_radius, max_line_dist,
|
|
182
|
+
canopy_avoidance, exponent, work_in_bufferR, canopy_thresh_percentage):
|
|
183
|
+
line_argsL = []
|
|
184
|
+
line_argsR = []
|
|
185
|
+
line_argsC = []
|
|
186
|
+
line_id = 0
|
|
187
|
+
for record in range(0, len(work_in_bufferL)):
|
|
188
|
+
line_bufferL = work_in_bufferL.loc[record, 'geometry']
|
|
189
|
+
line_bufferC = work_in_bufferC.loc[record, 'geometry']
|
|
190
|
+
LCut = work_in_bufferL.loc[record, 'LDist_Cut']
|
|
191
|
+
|
|
192
|
+
clipped_rasterL, out_transformL = rasterio.mask.mask(raster, [line_bufferL], crop=True,
|
|
193
|
+
nodata=BT_NODATA, filled=True)
|
|
194
|
+
clipped_rasterL = np.squeeze(clipped_rasterL, axis=0)
|
|
195
|
+
|
|
196
|
+
clipped_rasterC, out_transformC = rasterio.mask.mask(raster, [line_bufferC], crop=True,
|
|
197
|
+
nodata=BT_NODATA, filled=True)
|
|
198
|
+
|
|
199
|
+
clipped_rasterC = np.squeeze(clipped_rasterC, axis=0)
|
|
200
|
+
|
|
201
|
+
# make rasterio meta for saving raster later
|
|
202
|
+
out_metaL = raster.meta.copy()
|
|
203
|
+
out_metaL.update({"driver": "GTiff",
|
|
204
|
+
"height": clipped_rasterL.shape[0],
|
|
205
|
+
"width": clipped_rasterL.shape[1],
|
|
206
|
+
"nodata": BT_NODATA,
|
|
207
|
+
"transform": out_transformL})
|
|
208
|
+
|
|
209
|
+
out_metaC = raster.meta.copy()
|
|
210
|
+
out_metaC.update({"driver": "GTiff",
|
|
211
|
+
"height": clipped_rasterC.shape[0],
|
|
212
|
+
"width": clipped_rasterC.shape[1],
|
|
213
|
+
"nodata": BT_NODATA,
|
|
214
|
+
"transform": out_transformC})
|
|
215
|
+
|
|
216
|
+
nodata = BT_NODATA
|
|
217
|
+
line_argsL.append([clipped_rasterL, float(work_in_bufferL.loc[record, 'DynCanTh']), float(tree_radius),
|
|
218
|
+
float(max_line_dist), float(canopy_avoidance), float(exponent), raster.res, nodata,
|
|
219
|
+
line_seg.iloc[[record]], out_metaL, line_id, LCut, 'Left', canopy_thresh_percentage,
|
|
220
|
+
line_bufferC])
|
|
221
|
+
|
|
222
|
+
line_argsC.append([clipped_rasterC, float(work_in_bufferC.loc[record, 'DynCanTh']), float(tree_radius),
|
|
223
|
+
float(max_line_dist), float(canopy_avoidance), float(exponent), raster.res, nodata,
|
|
224
|
+
line_seg.iloc[[record]], out_metaC, line_id, 10, 'Center', canopy_thresh_percentage,
|
|
225
|
+
line_bufferC])
|
|
226
|
+
|
|
227
|
+
line_id += 1
|
|
228
|
+
|
|
229
|
+
line_id = 0
|
|
230
|
+
for record in range(0, len(work_in_bufferR)):
|
|
231
|
+
line_bufferR = work_in_bufferR.loc[record, 'geometry']
|
|
232
|
+
RCut = work_in_bufferR.loc[record, 'RDist_Cut']
|
|
233
|
+
clipped_rasterR, out_transformR = rasterio.mask.mask(raster, [line_bufferR], crop=True,
|
|
234
|
+
nodata=BT_NODATA, filled=True)
|
|
235
|
+
clipped_rasterR = np.squeeze(clipped_rasterR, axis=0)
|
|
236
|
+
|
|
237
|
+
# make rasterio meta for saving raster later
|
|
238
|
+
out_metaR = raster.meta.copy()
|
|
239
|
+
out_metaR.update({"driver": "GTiff",
|
|
240
|
+
"height": clipped_rasterR.shape[0],
|
|
241
|
+
"width": clipped_rasterR.shape[1],
|
|
242
|
+
"nodata": BT_NODATA,
|
|
243
|
+
"transform": out_transformR})
|
|
244
|
+
line_bufferC = work_in_bufferC.loc[record, 'geometry']
|
|
245
|
+
clipped_rasterC, out_transformC = rasterio.mask.mask(raster, [line_bufferC], crop=True,
|
|
246
|
+
nodata=BT_NODATA, filled=True)
|
|
247
|
+
|
|
248
|
+
clipped_rasterC = np.squeeze(clipped_rasterC, axis=0)
|
|
249
|
+
out_metaC = raster.meta.copy()
|
|
250
|
+
out_metaC.update({"driver": "GTiff",
|
|
251
|
+
"height": clipped_rasterC.shape[0],
|
|
252
|
+
"width": clipped_rasterC.shape[1],
|
|
253
|
+
"nodata": BT_NODATA,
|
|
254
|
+
"transform": out_transformC})
|
|
255
|
+
|
|
256
|
+
nodata = BT_NODATA
|
|
257
|
+
# TODO deal with inherited nodata and BT_NODATA_COST
|
|
258
|
+
# TODO convert nodata to BT_NODATA_COST
|
|
259
|
+
line_argsR.append([clipped_rasterR, float(work_in_bufferR.loc[record, 'DynCanTh']), float(tree_radius),
|
|
260
|
+
float(max_line_dist), float(canopy_avoidance), float(exponent), raster.res, nodata,
|
|
261
|
+
line_seg.iloc[[record]], out_metaR, line_id, RCut, 'Right', canopy_thresh_percentage,
|
|
262
|
+
line_bufferC])
|
|
263
|
+
|
|
264
|
+
print(' "PROGRESS_LABEL Preparing... {} of {}" '.format(line_id + 1 + len(work_in_bufferL),
|
|
265
|
+
len(work_in_bufferL) + len(work_in_bufferR)),
|
|
266
|
+
flush=True)
|
|
267
|
+
print(
|
|
268
|
+
' %{} '.format((line_id + 1 + len(work_in_bufferL)) / (len(work_in_bufferL) + len(work_in_bufferR)) * 100),
|
|
269
|
+
flush=True)
|
|
270
|
+
|
|
271
|
+
line_id += 1
|
|
272
|
+
|
|
273
|
+
return line_argsL, line_argsR, line_argsC
|
|
274
|
+
|
|
275
|
+
#
|
|
276
|
+
# def find_corridor_threshold_boundary(canopy_clip, least_cost_path, corridor_raster):
|
|
277
|
+
# threshold = -1
|
|
278
|
+
# thresholds = [-1] * 10
|
|
279
|
+
#
|
|
280
|
+
# # morphological filters to get polygons from canopy raster
|
|
281
|
+
# canopy_bin = np.where(np.isclose(canopy_clip, 1.0), True, False)
|
|
282
|
+
# clean_holes = remove_small_holes(canopy_bin)
|
|
283
|
+
# clean_obj = remove_small_objects(clean_holes)
|
|
284
|
+
#
|
|
285
|
+
# polys = features.shapes(skimage.img_as_ubyte(clean_obj), mask=clean_obj)
|
|
286
|
+
# polys = [shape(poly).segmentize(FP_SEGMENTIZE_LENGTH) for poly, _ in polys]
|
|
287
|
+
#
|
|
288
|
+
# # perpendicular segments intersections with polygons
|
|
289
|
+
# size = corridor_raster.shape
|
|
290
|
+
# pts = []
|
|
291
|
+
# for poly in polys:
|
|
292
|
+
# pts.extend(list(poly.exterior.coords))
|
|
293
|
+
#
|
|
294
|
+
# index_0 = []
|
|
295
|
+
# index_1 = []
|
|
296
|
+
# for pt in pts:
|
|
297
|
+
# if int(pt[0]) < size[1] and int(pt[1]) < size[0]:
|
|
298
|
+
# index_0.append(int(pt[0]))
|
|
299
|
+
# index_1.append(int(pt[1]))
|
|
300
|
+
#
|
|
301
|
+
# try:
|
|
302
|
+
# thresholds = corridor_raster[index_1, index_0]
|
|
303
|
+
# except Exception as e:
|
|
304
|
+
# print(e)
|
|
305
|
+
#
|
|
306
|
+
# # trimmed mean of values at intersections
|
|
307
|
+
# threshold = stats.trim_mean(thresholds, 0.3)
|
|
308
|
+
#
|
|
309
|
+
# return threshold
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def find_corridor_threshold(raster):
|
|
313
|
+
"""
|
|
314
|
+
Find the optimal corridor threshold by raster histogram
|
|
315
|
+
Parameters
|
|
316
|
+
----------
|
|
317
|
+
raster : corridor raster
|
|
318
|
+
|
|
319
|
+
Returns
|
|
320
|
+
-------
|
|
321
|
+
corridor_threshold : float
|
|
322
|
+
|
|
323
|
+
"""
|
|
324
|
+
corridor_threshold = -1.0
|
|
325
|
+
hist, bins = np.histogram(raster.flatten(), bins=100, range=(0, 100))
|
|
326
|
+
CostStd = np.nanstd(raster.flatten())
|
|
327
|
+
half_count = np.sum(hist) / 2
|
|
328
|
+
sub_count = 0
|
|
329
|
+
|
|
330
|
+
for count, bin_no in zip(hist, bins):
|
|
331
|
+
sub_count += count
|
|
332
|
+
if sub_count > half_count:
|
|
333
|
+
break
|
|
334
|
+
|
|
335
|
+
corridor_threshold = bin_no
|
|
336
|
+
|
|
337
|
+
return corridor_threshold
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def process_single_line_relative(segment):
|
|
341
|
+
# in_chm = rasterio.open(segment[0])
|
|
342
|
+
|
|
343
|
+
# segment[0] = in_chm
|
|
344
|
+
#DynCanTh = segment[1]
|
|
345
|
+
|
|
346
|
+
# Segment args from mulitprocessing:
|
|
347
|
+
# [clipped_chm, float(work_in_bufferR.loc[record, 'DynCanTh']), float(tree_radius),
|
|
348
|
+
# float(max_line_dist), float(canopy_avoidance), float(exponent), raster.res, nodata,
|
|
349
|
+
# line_seg.iloc[[record]], out_meta, line_id,RCut,Side,canopy_thresh_percentage,line_buffer]
|
|
350
|
+
|
|
351
|
+
# this will change segment content, and parameters will be changed
|
|
352
|
+
segment = dyn_canopy_cost_raster(segment)
|
|
353
|
+
if segment is None:
|
|
354
|
+
return None
|
|
355
|
+
# Segement after Clipped Canopy and Cost Raster
|
|
356
|
+
# line_df, dyn_canopy_ndarray, negative_cost_clip, out_meta, max_line_dist, nodata, line_id,Cut_Dist,line_buffer
|
|
357
|
+
|
|
358
|
+
# this function takes single line to work the line footprint
|
|
359
|
+
# (regardless it process the whole line or individual segment)
|
|
360
|
+
df = segment[0]
|
|
361
|
+
in_canopy_r = segment[1]
|
|
362
|
+
in_cost_r = segment[2]
|
|
363
|
+
out_meta = segment[3]
|
|
364
|
+
|
|
365
|
+
# in_transform = segment[3]
|
|
366
|
+
if np.isnan(in_canopy_r).all():
|
|
367
|
+
print("Canopy raster empty")
|
|
368
|
+
|
|
369
|
+
if np.isnan(in_cost_r).all():
|
|
370
|
+
print("Cost raster empty")
|
|
371
|
+
|
|
372
|
+
in_meta = segment[3]
|
|
373
|
+
exp_shk_cell = segment[4]
|
|
374
|
+
no_data = segment[5]
|
|
375
|
+
line_id = segment[6]
|
|
376
|
+
Cut_Dist = segment[7]
|
|
377
|
+
line_bufferR = segment[8]
|
|
378
|
+
|
|
379
|
+
shapefile_proj = df.crs
|
|
380
|
+
in_transform = in_meta['transform']
|
|
381
|
+
|
|
382
|
+
FID = df['OLnSEG'] # segment line feature ID
|
|
383
|
+
OID = df['OLnFID'] # original line ID for segment line
|
|
384
|
+
|
|
385
|
+
segment_list = []
|
|
386
|
+
|
|
387
|
+
feat = df.geometry.iloc[0]
|
|
388
|
+
for coord in feat.coords:
|
|
389
|
+
segment_list.append(coord)
|
|
390
|
+
|
|
391
|
+
cell_size_x = in_transform[0]
|
|
392
|
+
cell_size_y = -in_transform[4]
|
|
393
|
+
|
|
394
|
+
# Work out the corridor from both end of the centerline
|
|
395
|
+
try:
|
|
396
|
+
# TODO: further investigate and submit issue to skimage
|
|
397
|
+
# There is a severe bug in skimage find_costs
|
|
398
|
+
# when nan is present in clip_cost_r, find_costs cause access violation
|
|
399
|
+
# no message/exception will be caught
|
|
400
|
+
# change all nan to BT_NODATA_COST for workaround
|
|
401
|
+
if len(in_cost_r.shape) > 2:
|
|
402
|
+
in_cost_r = np.squeeze(in_cost_r, axis=0)
|
|
403
|
+
remove_nan_from_array(in_cost_r)
|
|
404
|
+
in_cost_r[in_cost_r==no_data]=np.inf
|
|
405
|
+
|
|
406
|
+
# generate 1m interval points along line
|
|
407
|
+
distance_delta = 1
|
|
408
|
+
distances = np.arange(0, feat.length, distance_delta)
|
|
409
|
+
multipoint_along_line = [feat.interpolate(distance) for distance in distances]
|
|
410
|
+
multipoint_along_line.append(Point(segment_list[-1]))
|
|
411
|
+
# Rasterize points along line
|
|
412
|
+
rasterized_points_Alongln = features.rasterize(multipoint_along_line, out_shape=in_cost_r.shape,
|
|
413
|
+
transform=in_transform,
|
|
414
|
+
fill=0, all_touched=True, default_value=1)
|
|
415
|
+
points_Alongln = np.transpose(np.nonzero(rasterized_points_Alongln))
|
|
416
|
+
|
|
417
|
+
# Find minimum cost paths through an N-d costs array.
|
|
418
|
+
mcp_flexible1 = MCP_Flexible(in_cost_r, sampling=(cell_size_x, cell_size_y), fully_connected=True)
|
|
419
|
+
flex_cost_alongLn, flex_back_alongLn = mcp_flexible1.find_costs(starts=points_Alongln)
|
|
420
|
+
|
|
421
|
+
# Generate corridor
|
|
422
|
+
# corridor = source_cost_acc + dest_cost_acc
|
|
423
|
+
corridor = flex_cost_alongLn # +flex_cost_dest #cum_cost_tosource+cum_cost_todestination
|
|
424
|
+
corridor = np.ma.masked_invalid(corridor)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
# Calculate minimum value of corridor raster
|
|
428
|
+
if not np.ma.min(corridor) is None:
|
|
429
|
+
corr_min = float(np.ma.min(corridor))
|
|
430
|
+
else:
|
|
431
|
+
corr_min = 0.5
|
|
432
|
+
|
|
433
|
+
# normalize corridor raster by deducting corr_min
|
|
434
|
+
corridor_norm = corridor - corr_min
|
|
435
|
+
|
|
436
|
+
# Set minimum as zero and save minimum file
|
|
437
|
+
# corridor_th_value = find_corridor_threshold(corridor_norm)
|
|
438
|
+
corridor_th_value = (Cut_Dist / cell_size_x)
|
|
439
|
+
if corridor_th_value < 0: # if no threshold found, use default value
|
|
440
|
+
corridor_th_value = (FP_CORRIDOR_THRESHOLD / cell_size_x)
|
|
441
|
+
|
|
442
|
+
# corridor_th_value = FP_CORRIDOR_THRESHOLD
|
|
443
|
+
corridor_thresh = np.ma.where(corridor_norm >= corridor_th_value, 1.0, 0.0)
|
|
444
|
+
corridor_thresh_cl = np.ma.where(corridor_norm >= (corridor_th_value + (5 / cell_size_x)), 1.0, 0.0)
|
|
445
|
+
|
|
446
|
+
# find contiguous corridor polygon for centerline
|
|
447
|
+
corridor_poly_gpd = find_corridor_polygon(corridor_thresh_cl, in_transform, df)
|
|
448
|
+
|
|
449
|
+
# Process: Stamp CC and Max Line Width
|
|
450
|
+
# Original code here
|
|
451
|
+
# RasterClass = SetNull(IsNull(CorridorMin),((CorridorMin) + ((Canopy_Raster) >= 1)) > 0)
|
|
452
|
+
temp1 = (corridor_thresh + in_canopy_r)
|
|
453
|
+
raster_class = np.ma.where(temp1 == 0, 1, 0).data
|
|
454
|
+
|
|
455
|
+
# BERA proposed Binary morphology
|
|
456
|
+
# RasterClass_binary=np.where(RasterClass==0,False,True)
|
|
457
|
+
if exp_shk_cell > 0 and cell_size_x < 1:
|
|
458
|
+
# Process: Expand
|
|
459
|
+
# FLM original Expand equivalent
|
|
460
|
+
cell_size = int(exp_shk_cell * 2 + 1)
|
|
461
|
+
|
|
462
|
+
expanded = ndimage.grey_dilation(raster_class, size=(cell_size, cell_size))
|
|
463
|
+
|
|
464
|
+
# BERA proposed Binary morphology Expand
|
|
465
|
+
# Expanded = ndimage.binary_dilation(RasterClass_binary, iterations=exp_shk_cell,border_value=1)
|
|
466
|
+
|
|
467
|
+
# Process: Shrink
|
|
468
|
+
# FLM original Shrink equivalent
|
|
469
|
+
file_shrink = ndimage.grey_erosion(expanded, size=(cell_size, cell_size))
|
|
470
|
+
|
|
471
|
+
# BERA proposed Binary morphology Shrink
|
|
472
|
+
# fileShrink = ndimage.binary_erosion((Expanded),iterations=Exp_Shk_cell,border_value=1)
|
|
473
|
+
else:
|
|
474
|
+
print('No Expand And Shrink cell performed.')
|
|
475
|
+
|
|
476
|
+
file_shrink = raster_class
|
|
477
|
+
|
|
478
|
+
# Process: Boundary Clean
|
|
479
|
+
clean_raster = ndimage.gaussian_filter(file_shrink, sigma=0, mode='nearest')
|
|
480
|
+
|
|
481
|
+
# creat mask for non-polygon area
|
|
482
|
+
mask = np.where(clean_raster == 1, True, False)
|
|
483
|
+
if clean_raster.dtype == np.int64:
|
|
484
|
+
clean_raster = clean_raster.astype(np.int32)
|
|
485
|
+
|
|
486
|
+
# Process: ndarray to shapely Polygon
|
|
487
|
+
out_polygon = features.shapes(clean_raster, mask=mask, transform=in_transform)
|
|
488
|
+
|
|
489
|
+
# create a shapely multipolygon
|
|
490
|
+
multi_polygon = []
|
|
491
|
+
for poly, value in out_polygon:
|
|
492
|
+
multi_polygon.append(shape(poly))
|
|
493
|
+
poly = MultiPolygon(multi_polygon)
|
|
494
|
+
|
|
495
|
+
# create a pandas dataframe for the FP
|
|
496
|
+
out_data = pd.DataFrame({'OLnFID': OID, 'OLnSEG': FID, 'CorriThresh': corridor_th_value, 'geometry': poly})
|
|
497
|
+
out_gdata = GeoDataFrame(out_data, geometry='geometry', crs=shapefile_proj)
|
|
498
|
+
|
|
499
|
+
return out_gdata, corridor_poly_gpd
|
|
500
|
+
|
|
501
|
+
except Exception as e:
|
|
502
|
+
print('Exception: {}'.format(e))
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def multiprocessing_footprint_relative(line_args, processes):
|
|
506
|
+
try:
|
|
507
|
+
total_steps = len(line_args)
|
|
508
|
+
|
|
509
|
+
feats = []
|
|
510
|
+
# chunksize = math.ceil(total_steps / processes)
|
|
511
|
+
with Pool(processes=processes) as pool:
|
|
512
|
+
step = 0
|
|
513
|
+
# execute tasks in order, process results out of order
|
|
514
|
+
for result in pool.imap_unordered(process_single_line_relative, line_args):
|
|
515
|
+
if BT_DEBUGGING:
|
|
516
|
+
print('Got result: {}'.format(result), flush=True)
|
|
517
|
+
if result != None:
|
|
518
|
+
feats.append(result)
|
|
519
|
+
step += 1
|
|
520
|
+
print(' "PROGRESS_LABEL Dynamic Segment Line Footprint {} of {}" '.format(step, total_steps),
|
|
521
|
+
flush=True)
|
|
522
|
+
print(' %{} '.format(step / total_steps * 100), flush=True)
|
|
523
|
+
return feats
|
|
524
|
+
except OperationCancelledException:
|
|
525
|
+
print("Operation cancelled")
|
|
526
|
+
return None
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def main_line_footprint_relative(callback, in_line, in_chm, max_ln_width, exp_shk_cell, out_footprint, out_centerline,
|
|
530
|
+
tree_radius, max_line_dist, canopy_avoidance, exponent, full_step,
|
|
531
|
+
canopy_thresh_percentage, processes, verbose):
|
|
532
|
+
# use_corridor_th_col = True
|
|
533
|
+
line_seg = GeoDataFrame.from_file(in_line)
|
|
534
|
+
|
|
535
|
+
# If Dynamic canopy threshold column not found, create one
|
|
536
|
+
if 'DynCanTh' not in line_seg.columns.array:
|
|
537
|
+
print("Please create field {} first".format('DynCanTh'))
|
|
538
|
+
exit()
|
|
539
|
+
if not float(canopy_thresh_percentage):
|
|
540
|
+
canopy_thresh_percentage = 50
|
|
541
|
+
else:
|
|
542
|
+
canopy_thresh_percentage = float(canopy_thresh_percentage)
|
|
543
|
+
|
|
544
|
+
if float(canopy_avoidance) <= 0.0:
|
|
545
|
+
canopy_avoidance = 0.0
|
|
546
|
+
if float(exponent) <= 0.0:
|
|
547
|
+
exponent = 1.0
|
|
548
|
+
# If OLnFID column is not found, column will be created
|
|
549
|
+
if 'OLnFID' not in line_seg.columns.array:
|
|
550
|
+
print("Created {} column in input line data.".format('OLnFID'))
|
|
551
|
+
line_seg['OLnFID'] = line_seg.index
|
|
552
|
+
|
|
553
|
+
if 'OLnSEG' not in line_seg.columns.array:
|
|
554
|
+
line_seg['OLnSEG'] = 0
|
|
555
|
+
|
|
556
|
+
print('%{}'.format(10))
|
|
557
|
+
|
|
558
|
+
# check coordinate systems between line and raster features
|
|
559
|
+
with rasterio.open(in_chm) as raster:
|
|
560
|
+
line_args = []
|
|
561
|
+
|
|
562
|
+
if compare_crs(vector_crs(in_line), raster_crs(in_chm)):
|
|
563
|
+
proc_segments = False
|
|
564
|
+
if proc_segments:
|
|
565
|
+
print("Splitting lines into segments...")
|
|
566
|
+
line_seg_split = split_into_segments(line_seg)
|
|
567
|
+
print("Splitting lines into segments...Done")
|
|
568
|
+
else:
|
|
569
|
+
if full_step:
|
|
570
|
+
print("Tool runs on input lines......")
|
|
571
|
+
line_seg_split = line_seg
|
|
572
|
+
else:
|
|
573
|
+
print("Tool runs on input segment lines......")
|
|
574
|
+
line_seg_split = split_into_Equal_Nth_segments(line_seg, 250)
|
|
575
|
+
|
|
576
|
+
print('%{}'.format(20))
|
|
577
|
+
|
|
578
|
+
work_in_bufferL1 = GeoDataFrame.copy(line_seg_split)
|
|
579
|
+
work_in_bufferL2 = GeoDataFrame.copy(line_seg_split)
|
|
580
|
+
work_in_bufferR1 = GeoDataFrame.copy(line_seg_split)
|
|
581
|
+
work_in_bufferR2 = GeoDataFrame.copy(line_seg_split)
|
|
582
|
+
work_in_bufferC = GeoDataFrame.copy(line_seg_split)
|
|
583
|
+
work_in_bufferL1['geometry'] = buffer(work_in_bufferL1['geometry'], distance=float(max_ln_width) + 1,
|
|
584
|
+
cap_style=3, single_sided=True)
|
|
585
|
+
|
|
586
|
+
work_in_bufferL2['geometry'] = buffer(work_in_bufferL2['geometry'], distance=-1,
|
|
587
|
+
cap_style=3, single_sided=True)
|
|
588
|
+
|
|
589
|
+
work_in_bufferL = GeoDataFrame(pd.concat([work_in_bufferL1, work_in_bufferL2]))
|
|
590
|
+
work_in_bufferL = work_in_bufferL.dissolve(by=['OLnFID', 'OLnSEG'], as_index=False)
|
|
591
|
+
|
|
592
|
+
work_in_bufferR1['geometry'] = buffer(work_in_bufferR1['geometry'], distance=-float(max_ln_width) - 1,
|
|
593
|
+
cap_style=3, single_sided=True)
|
|
594
|
+
work_in_bufferR2['geometry'] = buffer(work_in_bufferR2['geometry'], distance=1,
|
|
595
|
+
cap_style=3, single_sided=True)
|
|
596
|
+
|
|
597
|
+
work_in_bufferR = GeoDataFrame(pd.concat([work_in_bufferR1, work_in_bufferR2]))
|
|
598
|
+
work_in_bufferR = work_in_bufferR.dissolve(by=['OLnFID', 'OLnSEG'], as_index=False)
|
|
599
|
+
|
|
600
|
+
work_in_bufferC['geometry'] = buffer(work_in_bufferC['geometry'], distance=float(max_ln_width),
|
|
601
|
+
cap_style=3, single_sided=False)
|
|
602
|
+
print("Prepare arguments for Dynamic FP ...")
|
|
603
|
+
# line_argsL, line_argsR,line_argsC= generate_line_args(line_seg_split, work_in_bufferL,work_in_bufferC,
|
|
604
|
+
# raster, tree_radius, max_line_dist,
|
|
605
|
+
# canopy_avoidance, exponent, work_in_bufferR,
|
|
606
|
+
# canopy_thresh_percentage)
|
|
607
|
+
|
|
608
|
+
line_argsL, line_argsR, line_argsC = generate_line_args_DFP_NoClip(line_seg_split, work_in_bufferL,
|
|
609
|
+
work_in_bufferC, raster, in_chm,
|
|
610
|
+
tree_radius, max_line_dist,
|
|
611
|
+
canopy_avoidance, exponent,
|
|
612
|
+
work_in_bufferR,
|
|
613
|
+
canopy_thresh_percentage)
|
|
614
|
+
|
|
615
|
+
else:
|
|
616
|
+
print("Line and canopy raster spatial references are not same, please check.")
|
|
617
|
+
exit()
|
|
618
|
+
# pass center lines for footprint
|
|
619
|
+
print("Generating Dynamic footprint ...")
|
|
620
|
+
|
|
621
|
+
feat_listL = []
|
|
622
|
+
feat_listR = []
|
|
623
|
+
feat_listC = []
|
|
624
|
+
poly_listL = []
|
|
625
|
+
poly_listR = []
|
|
626
|
+
footprint_listL = []
|
|
627
|
+
footprint_listR = []
|
|
628
|
+
footprint_listC = []
|
|
629
|
+
# PARALLEL_MODE = ParallelMode.SEQUENTIAL
|
|
630
|
+
if PARALLEL_MODE == ParallelMode.MULTIPROCESSING:
|
|
631
|
+
# feat_listC = multiprocessing_footprint_relative(line_argsC, processes)
|
|
632
|
+
feat_listL = multiprocessing_footprint_relative(line_argsL, processes)
|
|
633
|
+
# feat_listL = execute_multiprocessing(process_single_line_relative,'Footprint',line_argsL, processes)
|
|
634
|
+
feat_listR = multiprocessing_footprint_relative(line_argsR, processes)
|
|
635
|
+
# feat_listR = execute_multiprocessing(process_single_line_relative, 'Footprint', line_argsR, processes)
|
|
636
|
+
|
|
637
|
+
elif PARALLEL_MODE == ParallelMode.SEQUENTIAL:
|
|
638
|
+
step = 1
|
|
639
|
+
total_steps = len(line_argsL)
|
|
640
|
+
print("There are {} result to process.".format(total_steps))
|
|
641
|
+
for row in line_argsL:
|
|
642
|
+
feat_listL.append(process_single_line_relative(row))
|
|
643
|
+
print("Footprint (left side) for line {} is done".format(step))
|
|
644
|
+
print(' "PROGRESS_LABEL Dynamic Line Footprint {} of {}" '.format(step, total_steps), flush=True)
|
|
645
|
+
print(' %{} '.format((step / total_steps) * 100))
|
|
646
|
+
step += 1
|
|
647
|
+
step = 1
|
|
648
|
+
total_steps = len(line_argsR)
|
|
649
|
+
for row in line_argsR:
|
|
650
|
+
feat_listR.append(process_single_line_relative(row))
|
|
651
|
+
print("Footprint for (right side) line {} is done".format(step))
|
|
652
|
+
print(' "PROGRESS_LABEL Dynamic Line Footprint {} of {}" '.format(step, total_steps), flush=True)
|
|
653
|
+
print(' %{} '.format((step / total_steps) * 100))
|
|
654
|
+
step += 1
|
|
655
|
+
|
|
656
|
+
print('%{}'.format(80))
|
|
657
|
+
print("Task done.")
|
|
658
|
+
|
|
659
|
+
for feat in feat_listL:
|
|
660
|
+
if feat:
|
|
661
|
+
footprint_listL.append(feat[0])
|
|
662
|
+
poly_listL.append(feat[1])
|
|
663
|
+
|
|
664
|
+
for feat in feat_listR:
|
|
665
|
+
if feat:
|
|
666
|
+
footprint_listR.append(feat[0])
|
|
667
|
+
poly_listR.append(feat[1])
|
|
668
|
+
|
|
669
|
+
print('Writing shapefile ...')
|
|
670
|
+
resultsL = GeoDataFrame(pd.concat(footprint_listL))
|
|
671
|
+
resultsL['geometry'] = resultsL['geometry'].buffer(0.005)
|
|
672
|
+
resultsR = GeoDataFrame(pd.concat(footprint_listR))
|
|
673
|
+
resultsR['geometry'] = resultsR['geometry'].buffer(0.005)
|
|
674
|
+
resultsL = resultsL.sort_values(by=['OLnFID', 'OLnSEG'])
|
|
675
|
+
resultsR = resultsR.sort_values(by=['OLnFID', 'OLnSEG'])
|
|
676
|
+
resultsL = resultsL.reset_index(drop=True)
|
|
677
|
+
resultsR = resultsR.reset_index(drop=True)
|
|
678
|
+
#
|
|
679
|
+
|
|
680
|
+
resultsAll = GeoDataFrame(pd.concat([resultsL, resultsR]))
|
|
681
|
+
dissolved_results = resultsAll.dissolve(by='OLnFID', as_index=False)
|
|
682
|
+
dissolved_results['geometry'] = dissolved_results['geometry'].buffer(-0.005)
|
|
683
|
+
print("Saving output ...")
|
|
684
|
+
dissolved_results.to_file(out_footprint)
|
|
685
|
+
print("Footprint file saved")
|
|
686
|
+
|
|
687
|
+
# dissolved polygon group by column 'OLnFID'
|
|
688
|
+
print("Generating centerlines from corridor polygons ...")
|
|
689
|
+
resultsCL = GeoDataFrame(pd.concat(poly_listL))
|
|
690
|
+
resultsCL['geometry'] = resultsCL['geometry'].buffer(0.005)
|
|
691
|
+
resultsCR = GeoDataFrame(pd.concat(poly_listR))
|
|
692
|
+
resultsCR['geometry'] = resultsCR['geometry'].buffer(0.005)
|
|
693
|
+
|
|
694
|
+
resultsCLR = GeoDataFrame(pd.concat([resultsCL, resultsCR]))
|
|
695
|
+
resultsCLR = resultsCLR.dissolve(by='OLnFID', as_index=False)
|
|
696
|
+
resultsCLR = resultsCLR.sort_values(by=['OLnFID', 'OLnSEG'])
|
|
697
|
+
resultsCLR = resultsCLR.reset_index(drop=True)
|
|
698
|
+
resultsCLR['geometry'] = resultsCLR['geometry'].buffer(-0.005)
|
|
699
|
+
|
|
700
|
+
# out_centerline=False
|
|
701
|
+
# save lines to file
|
|
702
|
+
if out_centerline:
|
|
703
|
+
poly_centerline_gpd = find_centerlines(resultsCLR, line_seg, processes)
|
|
704
|
+
poly_gpd = poly_centerline_gpd.copy()
|
|
705
|
+
centerline_gpd = poly_centerline_gpd.copy()
|
|
706
|
+
|
|
707
|
+
centerline_gpd = centerline_gpd.set_geometry('centerline')
|
|
708
|
+
centerline_gpd = centerline_gpd.drop(columns=['geometry'])
|
|
709
|
+
centerline_gpd.crs = poly_centerline_gpd.crs
|
|
710
|
+
centerline_gpd.to_file(out_centerline)
|
|
711
|
+
print("Centerline file saved")
|
|
712
|
+
|
|
713
|
+
# save polygons
|
|
714
|
+
path = Path(out_centerline)
|
|
715
|
+
path = path.with_stem(path.stem + '_poly')
|
|
716
|
+
poly_gpd = poly_gpd.drop(columns=['centerline'])
|
|
717
|
+
poly_gpd.to_file(path)
|
|
718
|
+
|
|
719
|
+
print('%{}'.format(100))
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
if __name__ == '__main__':
|
|
723
|
+
start_time = time.time()
|
|
724
|
+
print('Dynamic Footprint processing started')
|
|
725
|
+
print('Current time: {}'.format(time.strftime("%d %b %Y %H:%M:%S", time.localtime())))
|
|
726
|
+
|
|
727
|
+
in_args, in_verbose = check_arguments()
|
|
728
|
+
main_line_footprint_relative(print, **in_args.input, processes=int(in_args.processes), verbose=in_verbose)
|
|
729
|
+
|
|
730
|
+
print('%{}'.format(100))
|
|
731
|
+
print('Dynamic Footprint processing finished')
|
|
732
|
+
print('Current time: {}'.format(time.strftime("%d %b %Y %H:%M:%S", time.localtime())))
|
|
733
|
+
print('Total processing time (seconds): {}'.format(round(time.time() - start_time, 3)))
|