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,460 +1,503 @@
|
|
|
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
|
-
@staticmethod
|
|
41
|
-
def _point_to_row_col(
|
|
42
|
-
col, row = ras_transform.rowcol(
|
|
43
|
-
|
|
44
|
-
return row, col
|
|
45
|
-
|
|
46
|
-
@staticmethod
|
|
47
|
-
def _row_col_to_point(row_col, ras_transform):
|
|
48
|
-
x, y = ras_transform.xy(row_col[0], row_col[1])
|
|
49
|
-
return x, y
|
|
50
|
-
|
|
51
|
-
@staticmethod
|
|
52
|
-
def create_points_from_path(ras_transform, min_cost_path, start_point, end_point):
|
|
53
|
-
path_points = list(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return
|
|
64
|
-
|
|
65
|
-
@staticmethod
|
|
66
|
-
def
|
|
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
|
-
return
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return
|
|
136
|
-
|
|
137
|
-
@staticmethod
|
|
138
|
-
def
|
|
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
|
-
if
|
|
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
|
-
matrix,
|
|
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
|
-
|
|
1
|
+
"""
|
|
2
|
+
Least Cost Path Algorithm.
|
|
3
|
+
|
|
4
|
+
This algorithm is adapted from the QGIS plugin:
|
|
5
|
+
Find the least cost path with given cost raster and points
|
|
6
|
+
Original author: FlowMap Group@SESS.PKU
|
|
7
|
+
Source code repository: https://github.com/Gooong/LeastCostPath
|
|
8
|
+
|
|
9
|
+
Copyright (C) 2023 by AppliedGRG
|
|
10
|
+
Author: Richard Zeng
|
|
11
|
+
Date: 2023-03-01
|
|
12
|
+
|
|
13
|
+
This program is free software; you can redistribute it and/or modify
|
|
14
|
+
it under the terms of the GNU General Public License as published by
|
|
15
|
+
the Free Software Foundation; either version 2 of the License, or
|
|
16
|
+
(at your option) any later version.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# This will get replaced with a git SHA1 when you do a git archive
|
|
20
|
+
__revision__ = '$Format:%H$'
|
|
21
|
+
|
|
22
|
+
import math
|
|
23
|
+
import queue
|
|
24
|
+
from collections import defaultdict
|
|
25
|
+
|
|
26
|
+
import numpy as np
|
|
27
|
+
import rasterio
|
|
28
|
+
import shapely.geometry as sh_geom
|
|
29
|
+
import skimage.graph as sk_graph
|
|
30
|
+
|
|
31
|
+
import beratools.core.constants as bt_const
|
|
32
|
+
|
|
33
|
+
sqrt2 = math.sqrt(2)
|
|
34
|
+
USE_NUMPY_FOR_DIJKSTRA = True
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class MinCostPathHelper:
|
|
38
|
+
"""Helper class for the cost matrix."""
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def _point_to_row_col(point_xy, ras_transform):
|
|
42
|
+
col, row = ras_transform.rowcol(point_xy.x(), point_xy.y())
|
|
43
|
+
|
|
44
|
+
return row, col
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def _row_col_to_point(row_col, ras_transform):
|
|
48
|
+
x, y = ras_transform.xy(row_col[0], row_col[1])
|
|
49
|
+
return x, y
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def create_points_from_path(ras_transform, min_cost_path, start_point, end_point):
|
|
53
|
+
path_points = list(
|
|
54
|
+
map(
|
|
55
|
+
lambda row_col: MinCostPathHelper._row_col_to_point(
|
|
56
|
+
row_col, ras_transform
|
|
57
|
+
),
|
|
58
|
+
min_cost_path,
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
path_points[0] = (start_point.x, start_point.y)
|
|
62
|
+
path_points[-1] = (end_point.x, end_point.y)
|
|
63
|
+
return path_points
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def create_path_feature_from_points(path_points, attr_vals):
|
|
67
|
+
path_points_raw = [[pt.x, pt.y] for pt in path_points]
|
|
68
|
+
|
|
69
|
+
return sh_geom.LineString(path_points_raw), attr_vals
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def block2matrix_numpy(block, nodata):
|
|
73
|
+
contains_negative = False
|
|
74
|
+
with np.nditer(block, flags=["refs_ok"], op_flags=['readwrite']) as it:
|
|
75
|
+
for x in it:
|
|
76
|
+
# TODO: this speeds up a lot, but need further inspection
|
|
77
|
+
# if np.isclose(x, nodata) or np.isnan(x):
|
|
78
|
+
if x <= nodata or np.isnan(x):
|
|
79
|
+
x[...] = 9999.0
|
|
80
|
+
elif x < 0:
|
|
81
|
+
contains_negative = True
|
|
82
|
+
|
|
83
|
+
return block, contains_negative
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def block2matrix(block, nodata):
|
|
87
|
+
contains_negative = False
|
|
88
|
+
width, height = block.shape
|
|
89
|
+
# TODO: deal with nodata
|
|
90
|
+
matrix = [
|
|
91
|
+
[
|
|
92
|
+
None
|
|
93
|
+
if np.isclose(block[i][j], nodata)
|
|
94
|
+
or np.isclose(block[i][j], bt_const.BT_NODATA)
|
|
95
|
+
else block[i][j]
|
|
96
|
+
for j in range(height)
|
|
97
|
+
]
|
|
98
|
+
for i in range(width)
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
for row in matrix:
|
|
102
|
+
for v in row:
|
|
103
|
+
if v is not None:
|
|
104
|
+
if v < 0 and not np.isclose(v, bt_const.BT_NODATA):
|
|
105
|
+
contains_negative = True
|
|
106
|
+
|
|
107
|
+
return matrix, contains_negative
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def dijkstra(start_tuple, end_tuples, block, find_nearest, feedback=None):
|
|
111
|
+
class Grid:
|
|
112
|
+
def __init__(self, matrix):
|
|
113
|
+
self.map = matrix
|
|
114
|
+
self.h = len(matrix)
|
|
115
|
+
self.w = len(matrix[0])
|
|
116
|
+
self.manhattan_boundary = None
|
|
117
|
+
self.curr_boundary = None
|
|
118
|
+
|
|
119
|
+
def _in_bounds(self, id):
|
|
120
|
+
x, y = id
|
|
121
|
+
return 0 <= x < self.h and 0 <= y < self.w
|
|
122
|
+
|
|
123
|
+
def _passable(self, id):
|
|
124
|
+
x, y = id
|
|
125
|
+
return self.map[x][y] is not None
|
|
126
|
+
|
|
127
|
+
def is_valid(self, id):
|
|
128
|
+
return self._in_bounds(id) and self._passable(id)
|
|
129
|
+
|
|
130
|
+
def neighbors(self, id):
|
|
131
|
+
x, y = id
|
|
132
|
+
results = [(x + 1, y), (x, y - 1), (x - 1, y), (x, y + 1),
|
|
133
|
+
(x + 1, y - 1), (x + 1, y + 1), (x - 1, y - 1), (x - 1, y + 1)]
|
|
134
|
+
results = list(filter(self.is_valid, results))
|
|
135
|
+
return results
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
def manhattan_distance(id1, id2):
|
|
139
|
+
x1, y1 = id1
|
|
140
|
+
x2, y2 = id2
|
|
141
|
+
return abs(x1 - x2) + abs(y1 - y2)
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def min_manhattan(curr_node, end_nodes):
|
|
145
|
+
return min(
|
|
146
|
+
map(lambda node: Grid.manhattan_distance(curr_node, node), end_nodes)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
def max_manhattan(curr_node, end_nodes):
|
|
151
|
+
return max(
|
|
152
|
+
map(lambda node: Grid.manhattan_distance(curr_node, node), end_nodes)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def all_manhattan(curr_node, end_nodes):
|
|
157
|
+
return {
|
|
158
|
+
end_node: Grid.manhattan_distance(curr_node, end_node)
|
|
159
|
+
for end_node in end_nodes
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
def simple_cost(self, cur, nex):
|
|
163
|
+
cx, cy = cur
|
|
164
|
+
nx, ny = nex
|
|
165
|
+
currV = self.map[cx][cy]
|
|
166
|
+
offsetV = self.map[nx][ny]
|
|
167
|
+
if cx == nx or cy == ny:
|
|
168
|
+
return (currV + offsetV) / 2
|
|
169
|
+
else:
|
|
170
|
+
return sqrt2 * (currV + offsetV) / 2
|
|
171
|
+
|
|
172
|
+
result = []
|
|
173
|
+
grid = Grid(block)
|
|
174
|
+
|
|
175
|
+
end_dict = defaultdict(list)
|
|
176
|
+
for end_tuple in end_tuples:
|
|
177
|
+
end_dict[end_tuple[0]].append(end_tuple)
|
|
178
|
+
end_row_cols = set(end_dict.keys())
|
|
179
|
+
end_row_col_list = list(end_row_cols)
|
|
180
|
+
start_row_col = start_tuple[0]
|
|
181
|
+
|
|
182
|
+
frontier = queue.PriorityQueue()
|
|
183
|
+
frontier.put((0, start_row_col))
|
|
184
|
+
came_from = {}
|
|
185
|
+
cost_so_far = {}
|
|
186
|
+
decided = set()
|
|
187
|
+
|
|
188
|
+
if not grid.is_valid(start_row_col):
|
|
189
|
+
return result
|
|
190
|
+
|
|
191
|
+
# init progress
|
|
192
|
+
index = 0
|
|
193
|
+
distance_dic = grid.all_manhattan(start_row_col, end_row_cols)
|
|
194
|
+
if find_nearest:
|
|
195
|
+
total_manhattan = min(distance_dic.values())
|
|
196
|
+
else:
|
|
197
|
+
total_manhattan = sum(distance_dic.values())
|
|
198
|
+
|
|
199
|
+
total_manhattan = total_manhattan + 1
|
|
200
|
+
bound = total_manhattan
|
|
201
|
+
if feedback:
|
|
202
|
+
feedback.setProgress(1 + 100 * (1 - bound / total_manhattan))
|
|
203
|
+
|
|
204
|
+
came_from[start_row_col] = None
|
|
205
|
+
cost_so_far[start_row_col] = 0
|
|
206
|
+
|
|
207
|
+
while not frontier.empty():
|
|
208
|
+
_, current_node = frontier.get()
|
|
209
|
+
if current_node in decided:
|
|
210
|
+
continue
|
|
211
|
+
decided.add(current_node)
|
|
212
|
+
|
|
213
|
+
# update the progress bar
|
|
214
|
+
if feedback:
|
|
215
|
+
if feedback.isCanceled():
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
index = (index + 1) % len(end_row_col_list)
|
|
219
|
+
target_node = end_row_col_list[index]
|
|
220
|
+
new_manhattan = grid.manhattan_distance(current_node, target_node)
|
|
221
|
+
if new_manhattan < distance_dic[target_node]:
|
|
222
|
+
if find_nearest:
|
|
223
|
+
curr_bound = new_manhattan
|
|
224
|
+
else:
|
|
225
|
+
curr_bound = bound - (distance_dic[target_node] - new_manhattan)
|
|
226
|
+
|
|
227
|
+
distance_dic[target_node] = new_manhattan
|
|
228
|
+
|
|
229
|
+
if curr_bound < bound:
|
|
230
|
+
bound = curr_bound
|
|
231
|
+
if feedback:
|
|
232
|
+
feedback.setProgress(
|
|
233
|
+
1
|
|
234
|
+
+ 100
|
|
235
|
+
* (1 - bound / total_manhattan)
|
|
236
|
+
* (1 - bound / total_manhattan)
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
# destination
|
|
240
|
+
if current_node in end_row_cols:
|
|
241
|
+
path = []
|
|
242
|
+
costs = []
|
|
243
|
+
traverse_node = current_node
|
|
244
|
+
while traverse_node is not None:
|
|
245
|
+
path.append(traverse_node)
|
|
246
|
+
costs.append(cost_so_far[traverse_node])
|
|
247
|
+
traverse_node = came_from[traverse_node]
|
|
248
|
+
|
|
249
|
+
# start point and end point overlaps
|
|
250
|
+
if len(path) == 1:
|
|
251
|
+
path.append(start_row_col)
|
|
252
|
+
costs.append(0.0)
|
|
253
|
+
path.reverse()
|
|
254
|
+
costs.reverse()
|
|
255
|
+
result.append((path, costs, end_dict[current_node]))
|
|
256
|
+
|
|
257
|
+
end_row_cols.remove(current_node)
|
|
258
|
+
end_row_col_list.remove(current_node)
|
|
259
|
+
if len(end_row_cols) == 0 or find_nearest:
|
|
260
|
+
break
|
|
261
|
+
|
|
262
|
+
# relax distance
|
|
263
|
+
for nex in grid.neighbors(current_node):
|
|
264
|
+
new_cost = cost_so_far[current_node] + grid.simple_cost(current_node, nex)
|
|
265
|
+
if nex not in cost_so_far or new_cost < cost_so_far[nex]:
|
|
266
|
+
cost_so_far[nex] = new_cost
|
|
267
|
+
frontier.put((new_cost, nex))
|
|
268
|
+
came_from[nex] = current_node
|
|
269
|
+
|
|
270
|
+
return result
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def valid_node(node, size_of_grid):
|
|
274
|
+
"""Check if node is within the grid boundaries."""
|
|
275
|
+
if node[0] < 0 or node[0] >= size_of_grid:
|
|
276
|
+
return False
|
|
277
|
+
if node[1] < 0 or node[1] >= size_of_grid:
|
|
278
|
+
return False
|
|
279
|
+
return True
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def up(node):
|
|
283
|
+
return node[0] - 1, node[1]
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def down(node):
|
|
287
|
+
return node[0] + 1, node[1]
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def left(node):
|
|
291
|
+
return node[0], node[1] - 1
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def right(node):
|
|
295
|
+
return node[0], node[1] + 1
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def backtrack(initial_node, desired_node, distances):
|
|
299
|
+
# idea start at the last node then choose the least number of steps to go back
|
|
300
|
+
# last node
|
|
301
|
+
path = [desired_node]
|
|
302
|
+
|
|
303
|
+
size_of_grid = distances.shape[0]
|
|
304
|
+
|
|
305
|
+
while True:
|
|
306
|
+
# check up down left right - choose the direction that has the least distance
|
|
307
|
+
potential_distances = []
|
|
308
|
+
potential_nodes = []
|
|
309
|
+
|
|
310
|
+
directions = [up, down, left, right]
|
|
311
|
+
|
|
312
|
+
for direction in directions:
|
|
313
|
+
node = direction(path[-1])
|
|
314
|
+
if valid_node(node, size_of_grid):
|
|
315
|
+
potential_nodes.append(node)
|
|
316
|
+
potential_distances.append(distances[node[0], node[1]])
|
|
317
|
+
|
|
318
|
+
print(potential_nodes)
|
|
319
|
+
|
|
320
|
+
least_distance_index = np.argsort(potential_distances)
|
|
321
|
+
|
|
322
|
+
pt_added = False
|
|
323
|
+
for index in least_distance_index:
|
|
324
|
+
p_point = potential_nodes[index]
|
|
325
|
+
if p_point == (1, 6):
|
|
326
|
+
pass
|
|
327
|
+
if p_point not in path:
|
|
328
|
+
path.append(p_point)
|
|
329
|
+
pt_added = True
|
|
330
|
+
break
|
|
331
|
+
|
|
332
|
+
if index >= len(potential_distances) - 1 and not pt_added:
|
|
333
|
+
print("No best path found.")
|
|
334
|
+
return
|
|
335
|
+
|
|
336
|
+
if path[-1][0] == initial_node[0] and path[-1][1] == initial_node[1]:
|
|
337
|
+
break
|
|
338
|
+
|
|
339
|
+
return list(reversed(path))
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def dijkstra_np(start_tuple, end_tuple, matrix):
|
|
343
|
+
"""
|
|
344
|
+
Dijkstra's algorithm for finding the shortest path between two nodes in a graph.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
start_node (list): [row,col] coordinates of the initial node
|
|
348
|
+
end_node (list): [row,col] coordinates of the desired node
|
|
349
|
+
matrix (array 2d): numpy array that contains matrix as 1s and free space as 0s
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
list[list]: list of list of nodes that form the shortest path
|
|
353
|
+
|
|
354
|
+
"""
|
|
355
|
+
# source and destination are free
|
|
356
|
+
start_node = start_tuple[0]
|
|
357
|
+
end_node = end_tuple[0]
|
|
358
|
+
path = None
|
|
359
|
+
costs = None
|
|
360
|
+
|
|
361
|
+
try:
|
|
362
|
+
matrix[start_node[0], start_node[1]] = 0
|
|
363
|
+
matrix[end_node[0], end_node[1]] = 0
|
|
364
|
+
|
|
365
|
+
path, cost = sk_graph.route_through_array(matrix, start_node, end_node)
|
|
366
|
+
costs = [0.0 for i in range(len(path))]
|
|
367
|
+
except Exception as e:
|
|
368
|
+
print(f"dijkstra_np: {e}")
|
|
369
|
+
return None
|
|
370
|
+
|
|
371
|
+
return [(path, costs, end_tuple)]
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def find_least_cost_path(
|
|
375
|
+
out_image, in_meta, line, find_nearest=True, output_linear_reference=False
|
|
376
|
+
):
|
|
377
|
+
default_return = None
|
|
378
|
+
ras_nodata = in_meta['nodata']
|
|
379
|
+
|
|
380
|
+
pt_start = line.coords[0]
|
|
381
|
+
pt_end = line.coords[-1]
|
|
382
|
+
|
|
383
|
+
out_image = np.where(out_image < 0, np.nan, out_image) # set negative value to nan
|
|
384
|
+
if len(out_image.shape) > 2:
|
|
385
|
+
out_image = np.squeeze(out_image, axis=0)
|
|
386
|
+
|
|
387
|
+
if USE_NUMPY_FOR_DIJKSTRA:
|
|
388
|
+
matrix, contains_negative = MinCostPathHelper.block2matrix_numpy(
|
|
389
|
+
out_image, ras_nodata
|
|
390
|
+
)
|
|
391
|
+
else:
|
|
392
|
+
matrix, contains_negative = MinCostPathHelper.block2matrix(
|
|
393
|
+
out_image, ras_nodata
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
if contains_negative:
|
|
397
|
+
print('ERROR: Raster has negative values.')
|
|
398
|
+
return default_return
|
|
399
|
+
|
|
400
|
+
transformer = rasterio.transform.AffineTransformer(in_meta['transform'])
|
|
401
|
+
|
|
402
|
+
if (type(pt_start[0]) is tuple or
|
|
403
|
+
type(pt_start[1]) is tuple or
|
|
404
|
+
type(pt_end[0]) is tuple or
|
|
405
|
+
type(pt_end[1]) is tuple):
|
|
406
|
+
print("Point initialization error. Input is tuple.")
|
|
407
|
+
return default_return
|
|
408
|
+
|
|
409
|
+
start_tuples = []
|
|
410
|
+
end_tuples = []
|
|
411
|
+
start_tuple = []
|
|
412
|
+
try:
|
|
413
|
+
start_tuples = [
|
|
414
|
+
(
|
|
415
|
+
transformer.rowcol(pt_start[0], pt_start[1]),
|
|
416
|
+
sh_geom.Point(pt_start[0], pt_start[1]),
|
|
417
|
+
0,
|
|
418
|
+
)
|
|
419
|
+
]
|
|
420
|
+
end_tuples = [
|
|
421
|
+
(
|
|
422
|
+
transformer.rowcol(pt_end[0], pt_end[1]),
|
|
423
|
+
sh_geom.Point(pt_end[0], pt_end[1]),
|
|
424
|
+
1,
|
|
425
|
+
)
|
|
426
|
+
]
|
|
427
|
+
start_tuple = start_tuples[0]
|
|
428
|
+
end_tuple = end_tuples[0]
|
|
429
|
+
|
|
430
|
+
# regulate end point coords in case they are out of index of matrix
|
|
431
|
+
mat_size = matrix.shape
|
|
432
|
+
mat_size = (mat_size[0] - 1, mat_size[0] - 1)
|
|
433
|
+
start_tuple = (min(start_tuple[0], mat_size), start_tuple[1], start_tuple[2])
|
|
434
|
+
end_tuple = (min(end_tuple[0], mat_size), end_tuple[1], end_tuple[2])
|
|
435
|
+
|
|
436
|
+
except Exception as e:
|
|
437
|
+
print(f"find_least_cost_path: {e}")
|
|
438
|
+
|
|
439
|
+
if USE_NUMPY_FOR_DIJKSTRA:
|
|
440
|
+
result = dijkstra_np(start_tuple, end_tuple, matrix)
|
|
441
|
+
else:
|
|
442
|
+
# TODO: change end_tuples to end_tuple
|
|
443
|
+
result = dijkstra(start_tuple, end_tuples, matrix, find_nearest)
|
|
444
|
+
|
|
445
|
+
if result is None:
|
|
446
|
+
return default_return
|
|
447
|
+
|
|
448
|
+
if len(result) == 0:
|
|
449
|
+
print('No result returned.')
|
|
450
|
+
return default_return
|
|
451
|
+
|
|
452
|
+
path_points = None
|
|
453
|
+
for path, costs, end_tuple in result:
|
|
454
|
+
path_points = MinCostPathHelper.create_points_from_path(
|
|
455
|
+
transformer, path, start_tuple[1], end_tuple[1]
|
|
456
|
+
)
|
|
457
|
+
if output_linear_reference:
|
|
458
|
+
# TODO: code not reached
|
|
459
|
+
# add linear reference
|
|
460
|
+
for point, cost in zip(path_points, costs):
|
|
461
|
+
point.addMValue(cost)
|
|
462
|
+
|
|
463
|
+
# feat_attr = (start_tuple[2], end_tuple[2], total_cost)
|
|
464
|
+
lc_path = None
|
|
465
|
+
if len(path_points) >= 2:
|
|
466
|
+
lc_path = sh_geom.LineString(path_points)
|
|
467
|
+
|
|
468
|
+
return lc_path
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def find_least_cost_path_skimage(cost_clip, in_meta, seed_line):
|
|
472
|
+
lc_path_new = []
|
|
473
|
+
if len(cost_clip.shape) > 2:
|
|
474
|
+
cost_clip = np.squeeze(cost_clip, axis=0)
|
|
475
|
+
|
|
476
|
+
out_transform = in_meta['transform']
|
|
477
|
+
transformer = rasterio.transform.AffineTransformer(out_transform)
|
|
478
|
+
|
|
479
|
+
x1, y1 = list(seed_line.coords)[0][:2]
|
|
480
|
+
x2, y2 = list(seed_line.coords)[-1][:2]
|
|
481
|
+
row1, col1 = transformer.rowcol(x1, y1)
|
|
482
|
+
row2, col2 = transformer.rowcol(x2, y2)
|
|
483
|
+
|
|
484
|
+
try:
|
|
485
|
+
path_new = sk_graph.route_through_array(
|
|
486
|
+
cost_clip[0], [row1, col1], [row2, col2]
|
|
487
|
+
)
|
|
488
|
+
except Exception as e:
|
|
489
|
+
print(f"find_least_cost_path_skimage: {e}")
|
|
490
|
+
return None
|
|
491
|
+
|
|
492
|
+
if path_new[0]:
|
|
493
|
+
for row, col in path_new[0]:
|
|
494
|
+
x, y = transformer.xy(row, col)
|
|
495
|
+
lc_path_new.append((x, y))
|
|
496
|
+
|
|
497
|
+
if len(lc_path_new) < 2:
|
|
498
|
+
print('No least cost path detected, pass.')
|
|
499
|
+
return None
|
|
500
|
+
else:
|
|
501
|
+
lc_path_new = sh_geom.LineString(lc_path_new)
|
|
502
|
+
|
|
503
|
+
return lc_path_new
|