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.
Files changed (142) hide show
  1. beratools-0.2.0/.gitignore +16 -0
  2. beratools-0.2.0/LICENSE +22 -0
  3. beratools-0.2.0/PKG-INFO +63 -0
  4. beratools-0.2.0/README.md +9 -0
  5. beratools-0.2.0/beratools/__init__.py +9 -0
  6. beratools-0.2.0/beratools/core/__init__.py +0 -0
  7. beratools-0.2.0/beratools/core/algo_centerline.py +351 -0
  8. beratools-0.2.0/beratools/core/constants.py +86 -0
  9. beratools-0.2.0/beratools/core/dijkstra_algorithm.py +460 -0
  10. beratools-0.2.0/beratools/core/logger.py +85 -0
  11. beratools-0.2.0/beratools/core/tool_base.py +133 -0
  12. beratools-0.2.0/beratools/gui/__init__.py +15 -0
  13. beratools-0.2.0/beratools/gui/batch_processing_dlg.py +463 -0
  14. beratools-0.2.0/beratools/gui/beratools.json +2300 -0
  15. beratools-0.2.0/beratools/gui/bt_data.py +487 -0
  16. beratools-0.2.0/beratools/gui/bt_gui_main.py +691 -0
  17. beratools-0.2.0/beratools/gui/cli.py +18 -0
  18. beratools-0.2.0/beratools/gui/gui.json +8 -0
  19. beratools-0.2.0/beratools/gui/img/BERALogo.png +0 -0
  20. beratools-0.2.0/beratools/gui/img/closed.gif +0 -0
  21. beratools-0.2.0/beratools/gui/img/closed.png +0 -0
  22. beratools-0.2.0/beratools/gui/img/open.gif +0 -0
  23. beratools-0.2.0/beratools/gui/img/open.png +0 -0
  24. beratools-0.2.0/beratools/gui/img/tool.gif +0 -0
  25. beratools-0.2.0/beratools/gui/img/tool.png +0 -0
  26. beratools-0.2.0/beratools/gui/map_window.py +146 -0
  27. beratools-0.2.0/beratools/gui/tool_widgets.py +493 -0
  28. beratools-0.2.0/beratools/gui_tk/ASCII Banners.txt +248 -0
  29. beratools-0.2.0/beratools/gui_tk/__init__.py +20 -0
  30. beratools-0.2.0/beratools/gui_tk/beratools_main.py +515 -0
  31. beratools-0.2.0/beratools/gui_tk/bt_widgets.py +442 -0
  32. beratools-0.2.0/beratools/gui_tk/cli.py +18 -0
  33. beratools-0.2.0/beratools/gui_tk/gui.json +8 -0
  34. beratools-0.2.0/beratools/gui_tk/img/BERALogo.png +0 -0
  35. beratools-0.2.0/beratools/gui_tk/img/closed.gif +0 -0
  36. beratools-0.2.0/beratools/gui_tk/img/closed.png +0 -0
  37. beratools-0.2.0/beratools/gui_tk/img/open.gif +0 -0
  38. beratools-0.2.0/beratools/gui_tk/img/open.png +0 -0
  39. beratools-0.2.0/beratools/gui_tk/img/tool.gif +0 -0
  40. beratools-0.2.0/beratools/gui_tk/img/tool.png +0 -0
  41. beratools-0.2.0/beratools/gui_tk/main.py +14 -0
  42. beratools-0.2.0/beratools/gui_tk/map_window.py +144 -0
  43. beratools-0.2.0/beratools/gui_tk/runner.py +1481 -0
  44. beratools-0.2.0/beratools/gui_tk/tooltip.py +55 -0
  45. beratools-0.2.0/beratools/third_party/pyqtlet2/__init__.py +9 -0
  46. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/__init__.py +26 -0
  47. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/control/__init__.py +6 -0
  48. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/control/control.py +59 -0
  49. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/control/draw.py +52 -0
  50. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/control/layers.py +20 -0
  51. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/core/Parser.py +24 -0
  52. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/core/__init__.py +2 -0
  53. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/core/evented.py +180 -0
  54. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/__init__.py +5 -0
  55. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/featuregroup.py +34 -0
  56. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/icon/__init__.py +1 -0
  57. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/icon/icon.py +30 -0
  58. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/imageoverlay.py +18 -0
  59. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/layer.py +105 -0
  60. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/layergroup.py +45 -0
  61. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/marker/__init__.py +1 -0
  62. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/marker/marker.py +91 -0
  63. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/tile/__init__.py +2 -0
  64. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/tile/gridlayer.py +4 -0
  65. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/tile/tilelayer.py +16 -0
  66. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/vector/__init__.py +5 -0
  67. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/vector/circle.py +15 -0
  68. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/vector/circlemarker.py +18 -0
  69. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/vector/path.py +5 -0
  70. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/vector/polygon.py +14 -0
  71. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/vector/polyline.py +18 -0
  72. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/layer/vector/rectangle.py +14 -0
  73. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/map/__init__.py +1 -0
  74. beratools-0.2.0/beratools/third_party/pyqtlet2/leaflet/map/map.py +220 -0
  75. beratools-0.2.0/beratools/third_party/pyqtlet2/mapwidget.py +45 -0
  76. beratools-0.2.0/beratools/third_party/pyqtlet2/web/custom.js +43 -0
  77. beratools-0.2.0/beratools/third_party/pyqtlet2/web/map.html +23 -0
  78. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/layers-2x.png +0 -0
  79. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/layers.png +0 -0
  80. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-icon-2x.png +0 -0
  81. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-icon.png +0 -0
  82. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-shadow.png +0 -0
  83. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.css +656 -0
  84. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.js +6 -0
  85. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.codeclimate.yml +14 -0
  86. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.editorconfig +4 -0
  87. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.gitattributes +22 -0
  88. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.travis.yml +43 -0
  89. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/LICENSE +20 -0
  90. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/layers-2x.png +0 -0
  91. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/layers.png +0 -0
  92. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-icon-2x.png +0 -0
  93. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-icon.png +0 -0
  94. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-shadow.png +0 -0
  95. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet-2x.png +0 -0
  96. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet.png +0 -0
  97. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet.svg +156 -0
  98. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.css +10 -0
  99. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.js +10 -0
  100. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/LICENSE +22 -0
  101. beratools-0.2.0/beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/leaflet.rotatedMarker.js +57 -0
  102. beratools-0.2.0/beratools/tools/Beratools_r_script.r +1120 -0
  103. beratools-0.2.0/beratools/tools/Ht_metrics.py +116 -0
  104. beratools-0.2.0/beratools/tools/__init__.py +7 -0
  105. beratools-0.2.0/beratools/tools/batch_processing.py +132 -0
  106. beratools-0.2.0/beratools/tools/canopy_threshold_relative.py +670 -0
  107. beratools-0.2.0/beratools/tools/canopycostraster.py +222 -0
  108. beratools-0.2.0/beratools/tools/centerline.py +176 -0
  109. beratools-0.2.0/beratools/tools/common.py +885 -0
  110. beratools-0.2.0/beratools/tools/fl_regen_csf.py +428 -0
  111. beratools-0.2.0/beratools/tools/forest_line_attributes.py +408 -0
  112. beratools-0.2.0/beratools/tools/forest_line_ecosite.py +216 -0
  113. beratools-0.2.0/beratools/tools/lapis_all.py +103 -0
  114. beratools-0.2.0/beratools/tools/least_cost_path_from_chm.py +152 -0
  115. beratools-0.2.0/beratools/tools/line_footprint_absolute.py +363 -0
  116. beratools-0.2.0/beratools/tools/line_footprint_fixed.py +282 -0
  117. beratools-0.2.0/beratools/tools/line_footprint_functions.py +720 -0
  118. beratools-0.2.0/beratools/tools/line_footprint_relative.py +64 -0
  119. beratools-0.2.0/beratools/tools/ln_relative_metrics.py +615 -0
  120. beratools-0.2.0/beratools/tools/r_cal_lpi_elai.r +25 -0
  121. beratools-0.2.0/beratools/tools/r_generate_pd_focalraster.r +101 -0
  122. beratools-0.2.0/beratools/tools/r_interface.py +80 -0
  123. beratools-0.2.0/beratools/tools/r_point_density.r +9 -0
  124. beratools-0.2.0/beratools/tools/rpy_chm2trees.py +86 -0
  125. beratools-0.2.0/beratools/tools/rpy_dsm_chm_by.py +81 -0
  126. beratools-0.2.0/beratools/tools/rpy_dtm_by.py +63 -0
  127. beratools-0.2.0/beratools/tools/rpy_find_cellsize.py +43 -0
  128. beratools-0.2.0/beratools/tools/rpy_gnd_csf.py +74 -0
  129. beratools-0.2.0/beratools/tools/rpy_hummock_hollow.py +85 -0
  130. beratools-0.2.0/beratools/tools/rpy_hummock_hollow_raster.py +71 -0
  131. beratools-0.2.0/beratools/tools/rpy_las_info.py +51 -0
  132. beratools-0.2.0/beratools/tools/rpy_laz2las.py +40 -0
  133. beratools-0.2.0/beratools/tools/rpy_lpi_elai_lascat.py +466 -0
  134. beratools-0.2.0/beratools/tools/rpy_normalized_lidar_by.py +56 -0
  135. beratools-0.2.0/beratools/tools/rpy_percent_above_dbh.py +80 -0
  136. beratools-0.2.0/beratools/tools/rpy_points2trees.py +88 -0
  137. beratools-0.2.0/beratools/tools/rpy_vegcoverage.py +94 -0
  138. beratools-0.2.0/beratools/tools/tiler.py +206 -0
  139. beratools-0.2.0/beratools/tools/tool_template.py +54 -0
  140. beratools-0.2.0/beratools/tools/vertex_optimization.py +620 -0
  141. beratools-0.2.0/beratools/tools/zonal_threshold.py +144 -0
  142. beratools-0.2.0/pyproject.toml +60 -0
@@ -0,0 +1,16 @@
1
+ /.idea
2
+ /.vscode
3
+ /.spyproject
4
+ /data
5
+ .data
6
+ /beratools/gui/icons/grass
7
+ /beratools/__pycache__
8
+ /beratools/tools/__pycache__
9
+ /beratools/gui/__pycache__
10
+ *.pyc
11
+ *.whl
12
+ 0.5.1
13
+ /dist
14
+ .project
15
+ .pydevproject
16
+ BERATools.bat
@@ -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
+
@@ -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
+
@@ -0,0 +1,9 @@
1
+ from beratools.core import *
2
+ from beratools.tools import *
3
+ from beratools.gui import *
4
+ from beratools.third_party import *
5
+ from beratools.core import logger
6
+
7
+ __author__ = """AppliedGRG"""
8
+ __email__ = 'appliedgrg@gmail.com'
9
+ __version__ = '0.2.0'
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
+