BERATools 0.2.0__tar.gz
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-0.2.0/.gitignore +16 -0
- beratools-0.2.0/LICENSE +22 -0
- beratools-0.2.0/PKG-INFO +63 -0
- beratools-0.2.0/README.md +9 -0
- beratools-0.2.0/beratools/__init__.py +9 -0
- beratools-0.2.0/beratools/core/__init__.py +0 -0
- beratools-0.2.0/beratools/core/algo_centerline.py +351 -0
- beratools-0.2.0/beratools/core/constants.py +86 -0
- beratools-0.2.0/beratools/core/dijkstra_algorithm.py +460 -0
- beratools-0.2.0/beratools/core/logger.py +85 -0
- beratools-0.2.0/beratools/core/tool_base.py +133 -0
- beratools-0.2.0/beratools/gui/__init__.py +15 -0
- beratools-0.2.0/beratools/gui/batch_processing_dlg.py +463 -0
- beratools-0.2.0/beratools/gui/beratools.json +2300 -0
- beratools-0.2.0/beratools/gui/bt_data.py +487 -0
- beratools-0.2.0/beratools/gui/bt_gui_main.py +691 -0
- beratools-0.2.0/beratools/gui/cli.py +18 -0
- beratools-0.2.0/beratools/gui/gui.json +8 -0
- beratools-0.2.0/beratools/gui/img/BERALogo.png +0 -0
- beratools-0.2.0/beratools/gui/img/closed.gif +0 -0
- beratools-0.2.0/beratools/gui/img/closed.png +0 -0
- beratools-0.2.0/beratools/gui/img/open.gif +0 -0
- beratools-0.2.0/beratools/gui/img/open.png +0 -0
- beratools-0.2.0/beratools/gui/img/tool.gif +0 -0
- beratools-0.2.0/beratools/gui/img/tool.png +0 -0
- beratools-0.2.0/beratools/gui/map_window.py +146 -0
- beratools-0.2.0/beratools/gui/tool_widgets.py +493 -0
- beratools-0.2.0/beratools/gui_tk/ASCII Banners.txt +248 -0
- beratools-0.2.0/beratools/gui_tk/__init__.py +20 -0
- beratools-0.2.0/beratools/gui_tk/beratools_main.py +515 -0
- beratools-0.2.0/beratools/gui_tk/bt_widgets.py +442 -0
- beratools-0.2.0/beratools/gui_tk/cli.py +18 -0
- beratools-0.2.0/beratools/gui_tk/gui.json +8 -0
- beratools-0.2.0/beratools/gui_tk/img/BERALogo.png +0 -0
- beratools-0.2.0/beratools/gui_tk/img/closed.gif +0 -0
- beratools-0.2.0/beratools/gui_tk/img/closed.png +0 -0
- beratools-0.2.0/beratools/gui_tk/img/open.gif +0 -0
- beratools-0.2.0/beratools/gui_tk/img/open.png +0 -0
- beratools-0.2.0/beratools/gui_tk/img/tool.gif +0 -0
- beratools-0.2.0/beratools/gui_tk/img/tool.png +0 -0
- beratools-0.2.0/beratools/gui_tk/main.py +14 -0
- beratools-0.2.0/beratools/gui_tk/map_window.py +144 -0
- beratools-0.2.0/beratools/gui_tk/runner.py +1481 -0
- beratools-0.2.0/beratools/gui_tk/tooltip.py +55 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/__init__.py +9 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/__init__.py +26 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/control/__init__.py +6 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/control/control.py +59 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/control/draw.py +52 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/control/layers.py +20 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/core/Parser.py +24 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/core/__init__.py +2 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/core/evented.py +180 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/__init__.py +5 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/featuregroup.py +34 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/icon/__init__.py +1 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/icon/icon.py +30 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/imageoverlay.py +18 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/layer.py +105 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/layergroup.py +45 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/marker/__init__.py +1 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/marker/marker.py +91 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/tile/__init__.py +2 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/tile/gridlayer.py +4 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/tile/tilelayer.py +16 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/vector/__init__.py +5 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/vector/circle.py +15 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/vector/circlemarker.py +18 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/vector/path.py +5 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/vector/polygon.py +14 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/vector/polyline.py +18 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/vector/rectangle.py +14 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/map/__init__.py +1 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/map/map.py +220 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/mapwidget.py +45 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/custom.js +43 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/map.html +23 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/layers-2x.png +0 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/layers.png +0 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-icon-2x.png +0 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-icon.png +0 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-shadow.png +0 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.css +656 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.js +6 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.codeclimate.yml +14 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.editorconfig +4 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.gitattributes +22 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.travis.yml +43 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/LICENSE +20 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/layers-2x.png +0 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/layers.png +0 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-icon-2x.png +0 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-icon.png +0 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-shadow.png +0 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet-2x.png +0 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet.png +0 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet.svg +156 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.css +10 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.js +10 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/LICENSE +22 -0
- beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/leaflet.rotatedMarker.js +57 -0
- beratools-0.2.0/beratools/tools/Beratools_r_script.r +1120 -0
- beratools-0.2.0/beratools/tools/Ht_metrics.py +116 -0
- beratools-0.2.0/beratools/tools/__init__.py +7 -0
- beratools-0.2.0/beratools/tools/batch_processing.py +132 -0
- beratools-0.2.0/beratools/tools/canopy_threshold_relative.py +670 -0
- beratools-0.2.0/beratools/tools/canopycostraster.py +222 -0
- beratools-0.2.0/beratools/tools/centerline.py +176 -0
- beratools-0.2.0/beratools/tools/common.py +885 -0
- beratools-0.2.0/beratools/tools/fl_regen_csf.py +428 -0
- beratools-0.2.0/beratools/tools/forest_line_attributes.py +408 -0
- beratools-0.2.0/beratools/tools/forest_line_ecosite.py +216 -0
- beratools-0.2.0/beratools/tools/lapis_all.py +103 -0
- beratools-0.2.0/beratools/tools/least_cost_path_from_chm.py +152 -0
- beratools-0.2.0/beratools/tools/line_footprint_absolute.py +363 -0
- beratools-0.2.0/beratools/tools/line_footprint_fixed.py +282 -0
- beratools-0.2.0/beratools/tools/line_footprint_functions.py +720 -0
- beratools-0.2.0/beratools/tools/line_footprint_relative.py +64 -0
- beratools-0.2.0/beratools/tools/ln_relative_metrics.py +615 -0
- beratools-0.2.0/beratools/tools/r_cal_lpi_elai.r +25 -0
- beratools-0.2.0/beratools/tools/r_generate_pd_focalraster.r +101 -0
- beratools-0.2.0/beratools/tools/r_interface.py +80 -0
- beratools-0.2.0/beratools/tools/r_point_density.r +9 -0
- beratools-0.2.0/beratools/tools/rpy_chm2trees.py +86 -0
- beratools-0.2.0/beratools/tools/rpy_dsm_chm_by.py +81 -0
- beratools-0.2.0/beratools/tools/rpy_dtm_by.py +63 -0
- beratools-0.2.0/beratools/tools/rpy_find_cellsize.py +43 -0
- beratools-0.2.0/beratools/tools/rpy_gnd_csf.py +74 -0
- beratools-0.2.0/beratools/tools/rpy_hummock_hollow.py +85 -0
- beratools-0.2.0/beratools/tools/rpy_hummock_hollow_raster.py +71 -0
- beratools-0.2.0/beratools/tools/rpy_las_info.py +51 -0
- beratools-0.2.0/beratools/tools/rpy_laz2las.py +40 -0
- beratools-0.2.0/beratools/tools/rpy_lpi_elai_lascat.py +466 -0
- beratools-0.2.0/beratools/tools/rpy_normalized_lidar_by.py +56 -0
- beratools-0.2.0/beratools/tools/rpy_percent_above_dbh.py +80 -0
- beratools-0.2.0/beratools/tools/rpy_points2trees.py +88 -0
- beratools-0.2.0/beratools/tools/rpy_vegcoverage.py +94 -0
- beratools-0.2.0/beratools/tools/tiler.py +206 -0
- beratools-0.2.0/beratools/tools/tool_template.py +54 -0
- beratools-0.2.0/beratools/tools/vertex_optimization.py +620 -0
- beratools-0.2.0/beratools/tools/zonal_threshold.py +144 -0
- beratools-0.2.0/pyproject.toml +60 -0
beratools-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023, AppliedGRG
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
beratools-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: BERATools
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: An advanced forest line feature analysis platform
|
|
5
|
+
Project-URL: Homepage, https://github.com/RichardQZeng/BTools
|
|
6
|
+
Author-email: AppliedGRG <appliedgrg@gmail.com>, Richard Zeng <richardqzeng@gmail.com>
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2023, AppliedGRG
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Keywords: BERA,Line
|
|
31
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
32
|
+
Classifier: Intended Audience :: Developers
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Natural Language :: English
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Requires-Python: >=3.9
|
|
41
|
+
Requires-Dist: dask
|
|
42
|
+
Requires-Dist: distributed
|
|
43
|
+
Requires-Dist: geopandas
|
|
44
|
+
Requires-Dist: label-centerlines
|
|
45
|
+
Requires-Dist: pip
|
|
46
|
+
Requires-Dist: psutil
|
|
47
|
+
Requires-Dist: pyogrio>=0.9.0
|
|
48
|
+
Requires-Dist: pyqt5
|
|
49
|
+
Requires-Dist: rioxarray
|
|
50
|
+
Requires-Dist: rpy2
|
|
51
|
+
Requires-Dist: scikit-image>=0.24.0
|
|
52
|
+
Requires-Dist: xarray-spatial
|
|
53
|
+
Description-Content-Type: text/markdown
|
|
54
|
+
|
|
55
|
+
- HTML documentation is generated from .rst files with Sphinx
|
|
56
|
+
|
|
57
|
+
$ sphinx-build -b html ./sphinx/rst OutDir/sphinx/html
|
|
58
|
+
Or use the target doc in the cmake build system
|
|
59
|
+
$ make doc
|
|
60
|
+
|
|
61
|
+
Sphinx setup:
|
|
62
|
+
Please follow the http://sphinx-doc.org/latest/install.html guide.
|
|
63
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
- HTML documentation is generated from .rst files with Sphinx
|
|
2
|
+
|
|
3
|
+
$ sphinx-build -b html ./sphinx/rst OutDir/sphinx/html
|
|
4
|
+
Or use the target doc in the cmake build system
|
|
5
|
+
$ make doc
|
|
6
|
+
|
|
7
|
+
Sphinx setup:
|
|
8
|
+
Please follow the http://sphinx-doc.org/latest/install.html guide.
|
|
9
|
+
|
|
File without changes
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from rasterio import features
|
|
3
|
+
import shapely
|
|
4
|
+
from shapely.geometry import shape
|
|
5
|
+
from shapely.ops import unary_union, substring, linemerge, nearest_points, split
|
|
6
|
+
from shapely.geometry import Point, MultiPoint, Polygon, MultiPolygon, LineString, MultiLineString
|
|
7
|
+
# from beratools.third_party.label_centerlines import get_centerline
|
|
8
|
+
from label_centerlines import get_centerline
|
|
9
|
+
|
|
10
|
+
from beratools.core.tool_base import *
|
|
11
|
+
from beratools.core.constants import *
|
|
12
|
+
from beratools.tools.common import generate_perpendicular_line_precise
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def centerline_is_valid(centerline, input_line):
|
|
16
|
+
"""
|
|
17
|
+
Check if centerline is valid
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
centerline :
|
|
21
|
+
input_line : shapely LineString
|
|
22
|
+
This can be input seed line or least cost path. Only two end points are used.
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
if not centerline:
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
# centerline length less the half of least cost path
|
|
32
|
+
if (centerline.length < input_line.length / 2 or
|
|
33
|
+
centerline.distance(Point(input_line.coords[0])) > BT_EPSILON or
|
|
34
|
+
centerline.distance(Point(input_line.coords[-1])) > BT_EPSILON):
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
return True
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def snap_end_to_end(in_line, line_reference):
|
|
41
|
+
if type(in_line) is MultiLineString:
|
|
42
|
+
in_line = linemerge(in_line)
|
|
43
|
+
if type(in_line) is MultiLineString:
|
|
44
|
+
print(f'algo_centerline: MultiLineString found {in_line.centroid}, pass.')
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
pts = list(in_line.coords)
|
|
48
|
+
if len(pts) < 2:
|
|
49
|
+
print('snap_end_to_end: input line invalid.')
|
|
50
|
+
return in_line
|
|
51
|
+
|
|
52
|
+
line_start = Point(pts[0])
|
|
53
|
+
line_end = Point(pts[-1])
|
|
54
|
+
ref_ends = MultiPoint([line_reference.coords[0], line_reference.coords[-1]])
|
|
55
|
+
|
|
56
|
+
_, snap_start = nearest_points(line_start, ref_ends)
|
|
57
|
+
_, snap_end = nearest_points(line_end, ref_ends)
|
|
58
|
+
|
|
59
|
+
if in_line.has_z:
|
|
60
|
+
snap_start = shapely.force_3d(snap_start)
|
|
61
|
+
snap_end = shapely.force_3d(snap_end)
|
|
62
|
+
else:
|
|
63
|
+
snap_start = shapely.force_2d(snap_start)
|
|
64
|
+
snap_end = shapely.force_2d(snap_end)
|
|
65
|
+
|
|
66
|
+
pts[0] = snap_start.coords[0]
|
|
67
|
+
pts[-1] = snap_end.coords[0]
|
|
68
|
+
|
|
69
|
+
return LineString(pts)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def find_centerline(poly, input_line):
|
|
73
|
+
"""
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
poly : Polygon
|
|
77
|
+
input_line : LineString
|
|
78
|
+
Least cost path or seed line
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
|
|
83
|
+
"""
|
|
84
|
+
default_return = input_line, CenterlineStatus.FAILED
|
|
85
|
+
if not poly:
|
|
86
|
+
print('find_centerline: No polygon found')
|
|
87
|
+
return default_return
|
|
88
|
+
|
|
89
|
+
poly = shapely.segmentize(poly, max_segment_length=CL_SEGMENTIZE_LENGTH)
|
|
90
|
+
|
|
91
|
+
poly = poly.buffer(CL_POLYGON_BUFFER) # buffer polygon to reduce MultiPolygons
|
|
92
|
+
if type(poly) is MultiPolygon:
|
|
93
|
+
print('MultiPolygon encountered, skip.')
|
|
94
|
+
return default_return
|
|
95
|
+
|
|
96
|
+
exterior_pts = list(poly.exterior.coords)
|
|
97
|
+
|
|
98
|
+
if CL_DELETE_HOLES:
|
|
99
|
+
poly = Polygon(exterior_pts)
|
|
100
|
+
if CL_SIMPLIFY_POLYGON:
|
|
101
|
+
poly = poly.simplify(CL_SIMPLIFY_LENGTH)
|
|
102
|
+
|
|
103
|
+
line_coords = list(input_line.coords)
|
|
104
|
+
|
|
105
|
+
# TODO add more code to filter voronoi vertices
|
|
106
|
+
src_geom = Point(line_coords[0]).buffer(CL_BUFFER_CLIP*3).intersection(poly)
|
|
107
|
+
dst_geom = Point(line_coords[-1]).buffer(CL_BUFFER_CLIP*3).intersection(poly)
|
|
108
|
+
src_geom = None
|
|
109
|
+
dst_geom = None
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
centerline = get_centerline(poly, segmentize_maxlen=1, max_points=3000,
|
|
113
|
+
simplification=0.05, smooth_sigma=CL_SMOOTH_SIGMA, max_paths=1,
|
|
114
|
+
src_geom=src_geom, dst_geom=dst_geom)
|
|
115
|
+
except Exception as e:
|
|
116
|
+
print(e)
|
|
117
|
+
return default_return
|
|
118
|
+
|
|
119
|
+
if not centerline:
|
|
120
|
+
return default_return
|
|
121
|
+
|
|
122
|
+
if type(centerline) is MultiLineString:
|
|
123
|
+
if len(centerline.geoms) > 1:
|
|
124
|
+
print(" Multiple centerline segments detected, no further processing.")
|
|
125
|
+
return centerline, CenterlineStatus.SUCCESS # TODO: inspect
|
|
126
|
+
elif len(centerline.geoms) == 1:
|
|
127
|
+
centerline = centerline.geoms[0]
|
|
128
|
+
else:
|
|
129
|
+
return default_return
|
|
130
|
+
|
|
131
|
+
cl_coords = list(centerline.coords)
|
|
132
|
+
|
|
133
|
+
# trim centerline at two ends
|
|
134
|
+
head_buffer = Point(cl_coords[0]).buffer(CL_BUFFER_CLIP)
|
|
135
|
+
centerline = centerline.difference(head_buffer)
|
|
136
|
+
|
|
137
|
+
end_buffer = Point(cl_coords[-1]).buffer(CL_BUFFER_CLIP)
|
|
138
|
+
centerline = centerline.difference(end_buffer)
|
|
139
|
+
|
|
140
|
+
if not centerline:
|
|
141
|
+
print('No centerline detected, use input line instead.')
|
|
142
|
+
return default_return
|
|
143
|
+
try:
|
|
144
|
+
if centerline.is_empty:
|
|
145
|
+
print('Empty centerline detected, use input line instead.')
|
|
146
|
+
return default_return
|
|
147
|
+
except Exception as e:
|
|
148
|
+
print(e)
|
|
149
|
+
|
|
150
|
+
centerline = snap_end_to_end(centerline, input_line)
|
|
151
|
+
|
|
152
|
+
# Check if centerline is valid. If not, regenerate by splitting polygon into two halves.
|
|
153
|
+
if not centerline_is_valid(centerline, input_line):
|
|
154
|
+
try:
|
|
155
|
+
print(f'Regenerating line ...')
|
|
156
|
+
centerline = regenerate_centerline(poly, input_line)
|
|
157
|
+
return centerline, CenterlineStatus.REGENERATE_SUCCESS
|
|
158
|
+
except Exception as e:
|
|
159
|
+
print('find_centerline: Exception occurred. \n {}'.format(e))
|
|
160
|
+
return input_line, CenterlineStatus.REGENERATE_FAILED
|
|
161
|
+
|
|
162
|
+
return centerline, CenterlineStatus.SUCCESS
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# def find_route(array, start, end, fully_connected, geometric):
|
|
166
|
+
# route_list, cost_list = route_through_array(array, start, end, fully_connected, geometric)
|
|
167
|
+
# return route_list, cost_list
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def find_corridor_polygon(corridor_thresh, in_transform, line_gpd):
|
|
171
|
+
# Threshold corridor raster used for generating centerline
|
|
172
|
+
corridor_thresh_cl = np.ma.where(corridor_thresh == 0.0, 1, 0).data
|
|
173
|
+
corridor_mask = np.where(1 == corridor_thresh_cl, True, False)
|
|
174
|
+
poly_generator = features.shapes(corridor_thresh_cl, mask=corridor_mask, transform=in_transform)
|
|
175
|
+
corridor_polygon = []
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
for poly, value in poly_generator:
|
|
179
|
+
if shape(poly).area > 1:
|
|
180
|
+
corridor_polygon.append(shape(poly))
|
|
181
|
+
except Exception as e:
|
|
182
|
+
print(e)
|
|
183
|
+
|
|
184
|
+
if corridor_polygon:
|
|
185
|
+
corridor_polygon = (unary_union(corridor_polygon))
|
|
186
|
+
if type(corridor_polygon) is MultiPolygon:
|
|
187
|
+
poly_list = shapely.get_parts(corridor_polygon)
|
|
188
|
+
merge_poly = poly_list[0]
|
|
189
|
+
for i in range(1, len(poly_list)):
|
|
190
|
+
if shapely.intersects(merge_poly, poly_list[i]):
|
|
191
|
+
merge_poly = shapely.union(merge_poly, poly_list[i])
|
|
192
|
+
else:
|
|
193
|
+
buffer_dist = poly_list[i].distance(merge_poly) + 0.1
|
|
194
|
+
buffer_poly = poly_list[i].buffer(buffer_dist)
|
|
195
|
+
merge_poly = shapely.union(merge_poly, buffer_poly)
|
|
196
|
+
corridor_polygon = merge_poly
|
|
197
|
+
else:
|
|
198
|
+
corridor_polygon = None
|
|
199
|
+
|
|
200
|
+
# create GeoDataFrame for centerline
|
|
201
|
+
corridor_poly_gpd = gpd.GeoDataFrame.copy(line_gpd)
|
|
202
|
+
corridor_poly_gpd.geometry = [corridor_polygon]
|
|
203
|
+
|
|
204
|
+
return corridor_poly_gpd
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def process_single_centerline(row_and_path):
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
Parameters
|
|
211
|
+
----------
|
|
212
|
+
row_and_path:
|
|
213
|
+
list of row (polygon and props) and least cost path
|
|
214
|
+
first is geopandas row, second is input line, (least cost path)
|
|
215
|
+
|
|
216
|
+
Returns
|
|
217
|
+
-------
|
|
218
|
+
|
|
219
|
+
"""
|
|
220
|
+
row = row_and_path[0]
|
|
221
|
+
lc_path = row_and_path[1]
|
|
222
|
+
|
|
223
|
+
poly = row.geometry.iloc[0]
|
|
224
|
+
centerline, status = find_centerline(poly, lc_path)
|
|
225
|
+
row['centerline'] = centerline
|
|
226
|
+
|
|
227
|
+
return row
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def find_centerlines(poly_gpd, line_seg, processes):
|
|
231
|
+
centerline = None
|
|
232
|
+
centerline_gpd = []
|
|
233
|
+
rows_and_paths = []
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
for i in poly_gpd.index:
|
|
237
|
+
row = poly_gpd.loc[[i]]
|
|
238
|
+
poly = row.geometry.iloc[0]
|
|
239
|
+
if 'OLnSEG' in line_seg.columns:
|
|
240
|
+
line_id, Seg_id = row['OLnFID'].iloc[0], row['OLnSEG'].iloc[0]
|
|
241
|
+
lc_path = line_seg.loc[(line_seg.OLnFID == line_id) & (line_seg.OLnSEG == Seg_id)]['geometry'].iloc[0]
|
|
242
|
+
else:
|
|
243
|
+
line_id = row['OLnFID'].iloc[0]
|
|
244
|
+
lc_path = line_seg.loc[(line_seg.OLnFID == line_id)]['geometry'].iloc[0]
|
|
245
|
+
|
|
246
|
+
rows_and_paths.append((row, lc_path))
|
|
247
|
+
except Exception as e:
|
|
248
|
+
print(e)
|
|
249
|
+
|
|
250
|
+
total_steps = len(rows_and_paths)
|
|
251
|
+
step = 0
|
|
252
|
+
|
|
253
|
+
# if PARALLEL_MODE == ParallelMode.MULTIPROCESSING:
|
|
254
|
+
# with Pool(processes=processes) as pool:
|
|
255
|
+
# # execute tasks in order, process results out of order
|
|
256
|
+
# for result in pool.imap_unordered(process_single_centerline, rows_and_paths):
|
|
257
|
+
# centerline_gpd.append(result)
|
|
258
|
+
# step += 1
|
|
259
|
+
# print(' "PROGRESS_LABEL Centerline {} of {}" '.format(step, total_steps), flush=True)
|
|
260
|
+
# print(' %{} '.format(step / total_steps * 100))
|
|
261
|
+
# print('Centerline No. {} done'.format(step))
|
|
262
|
+
# elif PARALLEL_MODE == ParallelMode.SEQUENTIAL:
|
|
263
|
+
# for item in rows_and_paths:
|
|
264
|
+
# row_with_centerline = process_single_centerline(item)
|
|
265
|
+
# centerline_gpd.append(row_with_centerline)
|
|
266
|
+
# step += 1
|
|
267
|
+
# print(' "PROGRESS_LABEL Centerline {} of {}" '.format(step, total_steps), flush=True)
|
|
268
|
+
# print(' %{} '.format(step / total_steps * 100))
|
|
269
|
+
# print('Centerline No. {} done'.format(step))
|
|
270
|
+
centerline_gpd = execute_multiprocessing(process_single_centerline, rows_and_paths,
|
|
271
|
+
'find_centerlines', processes, 1)
|
|
272
|
+
return pd.concat(centerline_gpd)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def regenerate_centerline(poly, input_line):
|
|
276
|
+
"""
|
|
277
|
+
Regenerates centerline when initial
|
|
278
|
+
----------
|
|
279
|
+
poly : line is not valid
|
|
280
|
+
Parameters
|
|
281
|
+
input_line : shapely LineString
|
|
282
|
+
This can be input seed line or least cost path. Only two end points will be used
|
|
283
|
+
|
|
284
|
+
Returns
|
|
285
|
+
-------
|
|
286
|
+
|
|
287
|
+
"""
|
|
288
|
+
line_1 = substring(input_line, start_dist=0.0, end_dist=input_line.length / 2)
|
|
289
|
+
line_2 = substring(input_line, start_dist=input_line.length / 2, end_dist=input_line.length)
|
|
290
|
+
|
|
291
|
+
pts = shapely.force_2d([Point(list(input_line.coords)[0]),
|
|
292
|
+
Point(list(line_1.coords)[-1]),
|
|
293
|
+
Point(list(input_line.coords)[-1])])
|
|
294
|
+
perp = generate_perpendicular_line_precise(pts)
|
|
295
|
+
|
|
296
|
+
# MultiPolygon is rare, but need to be dealt with
|
|
297
|
+
# remove polygon of area less than CL_CLEANUP_POLYGON_BY_AREA
|
|
298
|
+
poly = poly.buffer(CL_POLYGON_BUFFER)
|
|
299
|
+
if type(poly) is MultiPolygon:
|
|
300
|
+
poly_geoms = list(poly.geoms)
|
|
301
|
+
poly_valid = [True] * len(poly_geoms)
|
|
302
|
+
for i, item in enumerate(poly_geoms):
|
|
303
|
+
if item.area < CL_CLEANUP_POLYGON_BY_AREA:
|
|
304
|
+
poly_valid[i] = False
|
|
305
|
+
|
|
306
|
+
poly_geoms = list(compress(poly_geoms, poly_valid))
|
|
307
|
+
if len(poly_geoms) != 1: # still multi polygon
|
|
308
|
+
print('regenerate_centerline: Multi or none polygon found, pass.')
|
|
309
|
+
|
|
310
|
+
poly = Polygon(poly_geoms[0])
|
|
311
|
+
|
|
312
|
+
poly_exterior = Polygon(poly.buffer(CL_POLYGON_BUFFER).exterior)
|
|
313
|
+
poly_split = split(poly_exterior, perp)
|
|
314
|
+
|
|
315
|
+
if len(poly_split.geoms) < 2:
|
|
316
|
+
print('regenerate_centerline: polygon split failed, pass.')
|
|
317
|
+
return None
|
|
318
|
+
|
|
319
|
+
poly_1 = poly_split.geoms[0]
|
|
320
|
+
poly_2 = poly_split.geoms[1]
|
|
321
|
+
|
|
322
|
+
# find polygon and line pairs
|
|
323
|
+
pair_line_1 = line_1
|
|
324
|
+
pair_line_2 = line_2
|
|
325
|
+
if not poly_1.intersects(line_1):
|
|
326
|
+
pair_line_1 = line_2
|
|
327
|
+
pair_line_2 = line_1
|
|
328
|
+
elif poly_1.intersection(line_1).length < line_1.length / 3:
|
|
329
|
+
pair_line_1 = line_2
|
|
330
|
+
pair_line_2 = line_1
|
|
331
|
+
|
|
332
|
+
center_line_1 = find_centerline(poly_1, pair_line_1)
|
|
333
|
+
center_line_2 = find_centerline(poly_2, pair_line_2)
|
|
334
|
+
|
|
335
|
+
center_line_1 = center_line_1[0]
|
|
336
|
+
center_line_2 = center_line_2[0]
|
|
337
|
+
|
|
338
|
+
if not center_line_1 or not center_line_2:
|
|
339
|
+
print('Regenerate line: centerline is None')
|
|
340
|
+
return None
|
|
341
|
+
|
|
342
|
+
try:
|
|
343
|
+
if center_line_1.is_empty or center_line_2.is_empty:
|
|
344
|
+
print('Regenerate line: centerline is empty')
|
|
345
|
+
return None
|
|
346
|
+
except Exception as e:
|
|
347
|
+
print(e)
|
|
348
|
+
|
|
349
|
+
print(f'Centerline is regenerated.')
|
|
350
|
+
return linemerge(MultiLineString([center_line_1, center_line_2]))
|
|
351
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from enum import Flag, Enum, IntEnum, unique
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
NADDatum = ['NAD83 Canadian Spatial Reference System', 'North American Datum 1983']
|
|
6
|
+
|
|
7
|
+
BT_DEBUGGING = False
|
|
8
|
+
BT_SHOW_ADVANCED_OPTIONS = False
|
|
9
|
+
HAS_COST_RASTER = False
|
|
10
|
+
|
|
11
|
+
BT_UID = 'BT_UID'
|
|
12
|
+
|
|
13
|
+
BT_EPSILON = np.finfo(float).eps
|
|
14
|
+
BT_NODATA_COST = np.inf
|
|
15
|
+
BT_NODATA = -9999
|
|
16
|
+
BT_MAXIMUM_CPU_CORES = 60 # multiprocessing has limit of 64, consider pathos
|
|
17
|
+
BT_BUFFER_RATIO = 0.0 # overlapping ratio of raster when clipping lines
|
|
18
|
+
BT_LABEL_MIN_WIDTH = 130
|
|
19
|
+
|
|
20
|
+
GROUPING_SEGMENT = True
|
|
21
|
+
LP_SEGMENT_LENGTH = 500
|
|
22
|
+
|
|
23
|
+
FP_CORRIDOR_THRESHOLD = 2.5
|
|
24
|
+
FP_SEGMENTIZE_LENGTH = 2.0
|
|
25
|
+
FP_FIXED_WIDTH_DEFAULT = 5.0
|
|
26
|
+
FP_PERP_LINE_OFFSET = 30.0
|
|
27
|
+
|
|
28
|
+
# centerline
|
|
29
|
+
CL_USE_SKIMAGE_GRAPH = False
|
|
30
|
+
CL_DELETE_HOLES = True
|
|
31
|
+
CL_SIMPLIFY_POLYGON = True
|
|
32
|
+
|
|
33
|
+
CL_BUFFER_CLIP = 10.0
|
|
34
|
+
CL_BUFFER_CENTROID = 3.0
|
|
35
|
+
CL_SNAP_TOLERANCE = 15.0
|
|
36
|
+
CL_SEGMENTIZE_LENGTH = 1.0
|
|
37
|
+
CL_SIMPLIFY_LENGTH = 0.5
|
|
38
|
+
CL_SMOOTH_SIGMA = 0.8
|
|
39
|
+
CL_CLEANUP_POLYGON_BY_AREA = 1.0
|
|
40
|
+
CL_POLYGON_BUFFER = 1e-6
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class FootprintParams(float, Enum):
|
|
44
|
+
FP_CORRIDOR_THRESHOLD = 2.5
|
|
45
|
+
FP_SEGMENTIZE_LENGTH = 2.0
|
|
46
|
+
FP_FIXED_WIDTH_DEFAULT = 5.0
|
|
47
|
+
FP_PERP_LINE_OFFSET = 30.0
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class CenterlineParams(float, Enum):
|
|
51
|
+
BUFFER_CLIP = 5.0
|
|
52
|
+
BUFFER_CENTROID = 3.0
|
|
53
|
+
SNAP_TOLERANCE = 15.0
|
|
54
|
+
SEGMENTIZE_LENGTH = 1.0
|
|
55
|
+
SIMPLIFY_LENGTH = 0.5
|
|
56
|
+
SMOOTH_SIGMA = 0.8
|
|
57
|
+
CLEANUP_POLYGON_BY_AREA = 1.0
|
|
58
|
+
POLYGON_BUFFER = 1e-6
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class CenterlineFlags(Flag):
|
|
62
|
+
USE_SKIMAGE_GRAPH = False
|
|
63
|
+
DELETE_HOLES = True
|
|
64
|
+
SIMPLIFY_POLYGON = True
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@unique
|
|
68
|
+
class CenterlineStatus(IntEnum):
|
|
69
|
+
SUCCESS = 1
|
|
70
|
+
FAILED = 2
|
|
71
|
+
REGENERATE_SUCCESS = 3
|
|
72
|
+
REGENERATE_FAILED = 4
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@unique
|
|
76
|
+
class ParallelMode(IntEnum):
|
|
77
|
+
SEQUENTIAL = 1
|
|
78
|
+
MULTIPROCESSING = 2
|
|
79
|
+
CONCURRENT = 3
|
|
80
|
+
DASK = 4
|
|
81
|
+
# RAY = 5
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
PARALLEL_MODE = ParallelMode.MULTIPROCESSING
|
|
85
|
+
|
|
86
|
+
|