BERATools 0.2.0__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.
Files changed (142) hide show
  1. beratools/__init__.py +9 -0
  2. beratools/core/__init__.py +0 -0
  3. beratools/core/algo_centerline.py +351 -0
  4. beratools/core/constants.py +86 -0
  5. beratools/core/dijkstra_algorithm.py +460 -0
  6. beratools/core/logger.py +85 -0
  7. beratools/core/tool_base.py +133 -0
  8. beratools/gui/__init__.py +15 -0
  9. beratools/gui/batch_processing_dlg.py +463 -0
  10. beratools/gui/beratools.json +2300 -0
  11. beratools/gui/bt_data.py +487 -0
  12. beratools/gui/bt_gui_main.py +691 -0
  13. beratools/gui/cli.py +18 -0
  14. beratools/gui/gui.json +8 -0
  15. beratools/gui/img/BERALogo.png +0 -0
  16. beratools/gui/img/closed.gif +0 -0
  17. beratools/gui/img/closed.png +0 -0
  18. beratools/gui/img/open.gif +0 -0
  19. beratools/gui/img/open.png +0 -0
  20. beratools/gui/img/tool.gif +0 -0
  21. beratools/gui/img/tool.png +0 -0
  22. beratools/gui/map_window.py +146 -0
  23. beratools/gui/tool_widgets.py +493 -0
  24. beratools/gui_tk/ASCII Banners.txt +248 -0
  25. beratools/gui_tk/__init__.py +20 -0
  26. beratools/gui_tk/beratools_main.py +515 -0
  27. beratools/gui_tk/bt_widgets.py +442 -0
  28. beratools/gui_tk/cli.py +18 -0
  29. beratools/gui_tk/gui.json +8 -0
  30. beratools/gui_tk/img/BERALogo.png +0 -0
  31. beratools/gui_tk/img/closed.gif +0 -0
  32. beratools/gui_tk/img/closed.png +0 -0
  33. beratools/gui_tk/img/open.gif +0 -0
  34. beratools/gui_tk/img/open.png +0 -0
  35. beratools/gui_tk/img/tool.gif +0 -0
  36. beratools/gui_tk/img/tool.png +0 -0
  37. beratools/gui_tk/main.py +14 -0
  38. beratools/gui_tk/map_window.py +144 -0
  39. beratools/gui_tk/runner.py +1481 -0
  40. beratools/gui_tk/tooltip.py +55 -0
  41. beratools/third_party/pyqtlet2/__init__.py +9 -0
  42. beratools/third_party/pyqtlet2/leaflet/__init__.py +26 -0
  43. beratools/third_party/pyqtlet2/leaflet/control/__init__.py +6 -0
  44. beratools/third_party/pyqtlet2/leaflet/control/control.py +59 -0
  45. beratools/third_party/pyqtlet2/leaflet/control/draw.py +52 -0
  46. beratools/third_party/pyqtlet2/leaflet/control/layers.py +20 -0
  47. beratools/third_party/pyqtlet2/leaflet/core/Parser.py +24 -0
  48. beratools/third_party/pyqtlet2/leaflet/core/__init__.py +2 -0
  49. beratools/third_party/pyqtlet2/leaflet/core/evented.py +180 -0
  50. beratools/third_party/pyqtlet2/leaflet/layer/__init__.py +5 -0
  51. beratools/third_party/pyqtlet2/leaflet/layer/featuregroup.py +34 -0
  52. beratools/third_party/pyqtlet2/leaflet/layer/icon/__init__.py +1 -0
  53. beratools/third_party/pyqtlet2/leaflet/layer/icon/icon.py +30 -0
  54. beratools/third_party/pyqtlet2/leaflet/layer/imageoverlay.py +18 -0
  55. beratools/third_party/pyqtlet2/leaflet/layer/layer.py +105 -0
  56. beratools/third_party/pyqtlet2/leaflet/layer/layergroup.py +45 -0
  57. beratools/third_party/pyqtlet2/leaflet/layer/marker/__init__.py +1 -0
  58. beratools/third_party/pyqtlet2/leaflet/layer/marker/marker.py +91 -0
  59. beratools/third_party/pyqtlet2/leaflet/layer/tile/__init__.py +2 -0
  60. beratools/third_party/pyqtlet2/leaflet/layer/tile/gridlayer.py +4 -0
  61. beratools/third_party/pyqtlet2/leaflet/layer/tile/tilelayer.py +16 -0
  62. beratools/third_party/pyqtlet2/leaflet/layer/vector/__init__.py +5 -0
  63. beratools/third_party/pyqtlet2/leaflet/layer/vector/circle.py +15 -0
  64. beratools/third_party/pyqtlet2/leaflet/layer/vector/circlemarker.py +18 -0
  65. beratools/third_party/pyqtlet2/leaflet/layer/vector/path.py +5 -0
  66. beratools/third_party/pyqtlet2/leaflet/layer/vector/polygon.py +14 -0
  67. beratools/third_party/pyqtlet2/leaflet/layer/vector/polyline.py +18 -0
  68. beratools/third_party/pyqtlet2/leaflet/layer/vector/rectangle.py +14 -0
  69. beratools/third_party/pyqtlet2/leaflet/map/__init__.py +1 -0
  70. beratools/third_party/pyqtlet2/leaflet/map/map.py +220 -0
  71. beratools/third_party/pyqtlet2/mapwidget.py +45 -0
  72. beratools/third_party/pyqtlet2/web/custom.js +43 -0
  73. beratools/third_party/pyqtlet2/web/map.html +23 -0
  74. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/layers-2x.png +0 -0
  75. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/layers.png +0 -0
  76. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-icon-2x.png +0 -0
  77. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-icon.png +0 -0
  78. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-shadow.png +0 -0
  79. beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.css +656 -0
  80. beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.js +6 -0
  81. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.codeclimate.yml +14 -0
  82. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.editorconfig +4 -0
  83. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.gitattributes +22 -0
  84. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.travis.yml +43 -0
  85. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/LICENSE +20 -0
  86. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/layers-2x.png +0 -0
  87. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/layers.png +0 -0
  88. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-icon-2x.png +0 -0
  89. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-icon.png +0 -0
  90. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-shadow.png +0 -0
  91. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet-2x.png +0 -0
  92. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet.png +0 -0
  93. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet.svg +156 -0
  94. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.css +10 -0
  95. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.js +10 -0
  96. beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/LICENSE +22 -0
  97. beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/leaflet.rotatedMarker.js +57 -0
  98. beratools/tools/Beratools_r_script.r +1120 -0
  99. beratools/tools/Ht_metrics.py +116 -0
  100. beratools/tools/__init__.py +7 -0
  101. beratools/tools/batch_processing.py +132 -0
  102. beratools/tools/canopy_threshold_relative.py +670 -0
  103. beratools/tools/canopycostraster.py +222 -0
  104. beratools/tools/centerline.py +176 -0
  105. beratools/tools/common.py +885 -0
  106. beratools/tools/fl_regen_csf.py +428 -0
  107. beratools/tools/forest_line_attributes.py +408 -0
  108. beratools/tools/forest_line_ecosite.py +216 -0
  109. beratools/tools/lapis_all.py +103 -0
  110. beratools/tools/least_cost_path_from_chm.py +152 -0
  111. beratools/tools/line_footprint_absolute.py +363 -0
  112. beratools/tools/line_footprint_fixed.py +282 -0
  113. beratools/tools/line_footprint_functions.py +720 -0
  114. beratools/tools/line_footprint_relative.py +64 -0
  115. beratools/tools/ln_relative_metrics.py +615 -0
  116. beratools/tools/r_cal_lpi_elai.r +25 -0
  117. beratools/tools/r_generate_pd_focalraster.r +101 -0
  118. beratools/tools/r_interface.py +80 -0
  119. beratools/tools/r_point_density.r +9 -0
  120. beratools/tools/rpy_chm2trees.py +86 -0
  121. beratools/tools/rpy_dsm_chm_by.py +81 -0
  122. beratools/tools/rpy_dtm_by.py +63 -0
  123. beratools/tools/rpy_find_cellsize.py +43 -0
  124. beratools/tools/rpy_gnd_csf.py +74 -0
  125. beratools/tools/rpy_hummock_hollow.py +85 -0
  126. beratools/tools/rpy_hummock_hollow_raster.py +71 -0
  127. beratools/tools/rpy_las_info.py +51 -0
  128. beratools/tools/rpy_laz2las.py +40 -0
  129. beratools/tools/rpy_lpi_elai_lascat.py +466 -0
  130. beratools/tools/rpy_normalized_lidar_by.py +56 -0
  131. beratools/tools/rpy_percent_above_dbh.py +80 -0
  132. beratools/tools/rpy_points2trees.py +88 -0
  133. beratools/tools/rpy_vegcoverage.py +94 -0
  134. beratools/tools/tiler.py +206 -0
  135. beratools/tools/tool_template.py +54 -0
  136. beratools/tools/vertex_optimization.py +620 -0
  137. beratools/tools/zonal_threshold.py +144 -0
  138. beratools-0.2.0.dist-info/METADATA +63 -0
  139. beratools-0.2.0.dist-info/RECORD +142 -0
  140. beratools-0.2.0.dist-info/WHEEL +4 -0
  141. beratools-0.2.0.dist-info/entry_points.txt +2 -0
  142. beratools-0.2.0.dist-info/licenses/LICENSE +22 -0
@@ -0,0 +1,222 @@
1
+ import os
2
+ import sys
3
+ import time
4
+ import numpy as np
5
+ from numpy.lib.stride_tricks import as_strided
6
+
7
+ import rasterio
8
+ import xarray as xr
9
+ from xrspatial import convolution, focal
10
+ from scipy import ndimage
11
+
12
+ from common import *
13
+
14
+
15
+ # TODO: Rolling Statistics for grid data... an alternative
16
+ # by Dan Patterson
17
+
18
+
19
+ def _check(a, r_c, subok=False):
20
+ """Performs the array checks necessary for stride and block.
21
+ : in_array - Array or list.
22
+ : r_c - tuple/list/array of rows x cols.
23
+ : subok - from numpy 1.12 added, keep for now
24
+ :Returns:
25
+ :------
26
+ :Attempts will be made to ...
27
+ : produce a shape at least (1*c). For a scalar, the
28
+ : minimum shape will be (1*r) for 1D array or (1*c) for 2D
29
+ : array if r<c. Be aware
30
+ """
31
+ if isinstance(r_c, (int, float)):
32
+ r_c = (1, int(r_c))
33
+
34
+ r, c = r_c
35
+ if a.ndim == 1:
36
+ a = np.atleast_2d(a)
37
+
38
+ r, c = r_c = (min(r, a.shape[0]), min(c, a.shape[1]))
39
+ a = np.array(a, copy=False, subok=subok)
40
+ return a, r, c, tuple(r_c)
41
+
42
+
43
+ def _pad(in_array, kernel):
44
+ """Pad a sliding array to allow for stats"""
45
+ pad_x = int(kernel.shape[0] / 2)
46
+ pad_y = int(kernel.shape[0] / 2)
47
+ result = np.pad(in_array, pad_width=(pad_x, pad_y), mode="constant", constant_values=(np.NaN, np.NaN))
48
+
49
+ return result
50
+
51
+
52
+ def stride(a, r_c):
53
+ """Provide a 2D sliding/moving view of an array.
54
+ : There is no edge correction for outputs.
55
+ :
56
+ :Requires:
57
+ :--------
58
+ : _check(a, r_c) ... Runs the checks on the inputs.
59
+ : a - array or list, usually a 2D array. Assumes rows is >=1,
60
+ : it is corrected as is the number of columns.
61
+ : r_c - tuple/list/array of rows x cols. Attempts to
62
+ : produce a shape at least (1*c). For a scalar, the
63
+ : minimum shape will be (1*r) for 1D array or 2D
64
+ : array if r<c. Be aware
65
+ """
66
+ a, r, c, r_c = _check(a, r_c)
67
+ shape = (a.shape[0] - r + 1, a.shape[1] - c + 1) + r_c
68
+ strides = a.strides * 2
69
+ a_s = (as_strided(a, shape=shape, strides=strides)).squeeze()
70
+ return a_s
71
+
72
+
73
+ def normalize_chm(raster):
74
+ n_raster = np.where(raster >= 0, raster, 0)
75
+ return n_raster
76
+
77
+
78
+ def np_cc_map(out_canopy_r, chm, in_array, min_ht):
79
+ print('Generating Canopy Closure Raster ...')
80
+
81
+ # canopy_ndarray = np.where(in_array >= min_ht, 1., 0.).astype(float)
82
+ canopy_ndarray = np.ma.where(in_array > min_ht, 1., 0.).astype(float)
83
+ canopy_ndarray = np.ma.filled(canopy_ndarray, chm.nodata)
84
+ try:
85
+ write_canopy = rasterio.open(out_canopy_r, 'w', **chm.profile)
86
+ write_canopy.write(canopy_ndarray, 1)
87
+ write_canopy.close()
88
+ print('Generating Canopy Closure (CC) Raster ... Done')
89
+ except Exception as e:
90
+ print(sys.exc_info())
91
+ del in_array
92
+ return canopy_ndarray
93
+
94
+
95
+ def fs_raster(in_ndarray, kernel):
96
+ print('Generating Canopy Closure Focal Statistic ...')
97
+ padded_array = _pad(in_ndarray, kernel)
98
+ a_s = stride(padded_array, kernel.shape)
99
+
100
+ # TODO: np.where on large ndarray fail (allocate memory error)
101
+ a_s_masked = np.where(kernel == 1, a_s, np.NaN)
102
+ print("Calculating Canopy Closure's Focal Statistic-Mean ...")
103
+ mean_result = np.nanmean(a_s_masked, axis=(2, 3))
104
+ print("Calculating Canopy Closure's Focal Statistic-Stand Deviation Raster ...")
105
+ stdev_result = np.nanstd(a_s_masked, axis=(2, 3))
106
+ del a_s
107
+ return mean_result, stdev_result
108
+
109
+
110
+ def fs_raster_stdmean(in_ndarray, kernel, nodata):
111
+ # This function uses xrspatial whcih can handle large data but slow
112
+ in_ndarray[in_ndarray == nodata] = np.nan
113
+ result_ndarray = focal.focal_stats(xr.DataArray(in_ndarray), kernel, stats_funcs=['std', 'mean'])
114
+
115
+ # Flattening the array
116
+ flatten_std_result_ndarray = result_ndarray[0].data.reshape(-1)
117
+ flatten_mean_result_ndarray = result_ndarray[1].data.reshape(-1)
118
+
119
+ # Re-shaping the array
120
+ reshape_std_ndarray = flatten_std_result_ndarray.reshape(in_ndarray.shape[0], in_ndarray.shape[1])
121
+ reshape_mean_ndarray = flatten_mean_result_ndarray.reshape(in_ndarray.shape[0], in_ndarray.shape[1])
122
+ return reshape_std_ndarray, reshape_mean_ndarray
123
+
124
+
125
+ def smooth_cost(in_raster, search_dist, sampling):
126
+ print('Generating Cost Raster ...')
127
+ from tempfile import mkdtemp
128
+ import os.path as path
129
+ import shutil
130
+
131
+ euc_dist_array = None
132
+ row, col = in_raster.shape
133
+ if row * col >= 30000 * 30000:
134
+ filename = path.join(mkdtemp(), 'tempmmemap.dat')
135
+ a_in_mat = np.memmap(filename, in_raster.dtype, 'w+', shape=in_raster.shape)
136
+ a_in_mat[:] = in_raster[:]
137
+ a_in_mat.flush()
138
+ euc_dist_array = ndimage.distance_transform_edt(np.logical_not(a_in_mat), sampling=sampling)
139
+ del a_in_mat, in_raster
140
+ shutil.rmtree(path.dirname(filename))
141
+ else:
142
+ euc_dist_array = ndimage.distance_transform_edt(np.logical_not(in_raster), sampling=sampling)
143
+
144
+ smooth1 = float(search_dist) - euc_dist_array
145
+ smooth1[smooth1 <= 0.0] = 0.0
146
+ smooth_cost_array = smooth1 / float(search_dist)
147
+
148
+ return smooth_cost_array
149
+
150
+
151
+ def np_cost_raster(canopy_ndarray, cc_mean, cc_std, cc_smooth, chm, avoidance, cost_raster_exponent, out_cost_r):
152
+ print('Generating Smoothed Cost Raster ...')
153
+ aM1a = (cc_mean - cc_std)
154
+ aM1b = (cc_mean + cc_std)
155
+ aM1 = np.divide(aM1a, aM1b, where=aM1b != 0, out=np.zeros(aM1a.shape, dtype=float))
156
+ aM = (1 + aM1) / 2
157
+ aaM = (cc_mean + cc_std)
158
+ bM = np.where(aaM <= 0, 0, aM)
159
+ cM = bM * (1 - avoidance) + (cc_smooth * avoidance)
160
+ dM = np.where(canopy_ndarray == 1, 1, cM)
161
+ eM = np.exp(dM)
162
+ result = np.power(eM, float(cost_raster_exponent))
163
+ write_cost = rasterio.open(out_cost_r, 'w+', driver='GTiff', height=chm.shape[0], width=chm.shape[1],
164
+ count=1, dtype=chm.read(1).dtype, crs=chm.crs, transform=chm.transform)
165
+ write_cost.write(result, 1)
166
+ write_cost.close()
167
+ print('Generating Smoothed Cost Raster ... Done')
168
+ return
169
+
170
+
171
+ # TODO: deal with NODATA
172
+ def canopy_cost_raster(callback, in_chm, canopy_ht_threshold, tree_radius, max_line_dist,
173
+ canopy_avoidance, exponent, out_canopy, out_cost, processes, verbose):
174
+ canopy_ht_threshold = float(canopy_ht_threshold)
175
+ tree_radius = float(tree_radius)
176
+ max_line_dist = float(max_line_dist)
177
+ canopy_avoidance = float(canopy_avoidance)
178
+ cost_raster_exponent = float(exponent)
179
+
180
+ print('In CHM: ' + in_chm)
181
+ chm = rasterio.open(in_chm)
182
+ (cell_x, cell_y) = chm.res
183
+
184
+ print('Loading CHM ...')
185
+ band1_ndarray = chm.read(1, masked=True)
186
+ print('%{}'.format(10))
187
+
188
+ print('Preparing Kernel window ...')
189
+ kernel = convolution.circle_kernel(cell_x, cell_y, tree_radius)
190
+ print('%{}'.format(20))
191
+
192
+ # Generate Canopy Raster and return the Canopy array
193
+ canopy_ndarray = np_cc_map(out_canopy, chm, band1_ndarray, canopy_ht_threshold)
194
+ print('%{}'.format(40))
195
+ print('Apply focal statistic on raster ...')
196
+
197
+ # Calculating focal statistic from canopy raster
198
+ # Alternative: (only work on large cell size
199
+ if cell_y > 1 and cell_x > 1:
200
+ cc_mean, cc_std = fs_raster(canopy_ndarray, kernel)
201
+ else:
202
+ cc_std, cc_mean = fs_raster_stdmean(canopy_ndarray, kernel, chm.nodata)
203
+ print('%{}'.format(60))
204
+ print('Apply focal statistic on raster ... Done')
205
+
206
+ # Smoothing raster
207
+ cc_smooth = smooth_cost(canopy_ndarray, max_line_dist, [cell_x, cell_y])
208
+ avoidance = max(min(float(canopy_avoidance), 1), 0)
209
+ np_cost_raster(canopy_ndarray, cc_mean, cc_std, cc_smooth, chm, avoidance, cost_raster_exponent, out_cost)
210
+ print('%{}'.format(100))
211
+
212
+
213
+ if __name__ == '__main__':
214
+ in_args, in_verbose = check_arguments()
215
+
216
+ start_time = time.time()
217
+ print('Starting Canopy and Cost Raster processing\n @ {}'
218
+ .format(time.strftime("%d %b %Y %H:%M:%S", time.localtime())))
219
+
220
+ canopy_cost_raster(print, **in_args.input, processes=int(in_args.processes), verbose=in_verbose)
221
+ print('Canopy and Cost Raster processing is done in {} seconds)'
222
+ .format(round(time.time() - start_time, 5)))
@@ -0,0 +1,176 @@
1
+ import logging
2
+ import time
3
+
4
+ import sys
5
+ from pathlib import Path
6
+ from inspect import getsourcefile
7
+
8
+ if __name__ == "__main__":
9
+ current_file = Path(getsourcefile(lambda: 0)).resolve()
10
+ btool_dir = current_file.parents[2]
11
+ sys.path.insert(0, btool_dir.as_posix())
12
+
13
+ from beratools.core.logger import Logger
14
+ from beratools.core.algo_centerline import *
15
+ from beratools.core.dijkstra_algorithm import *
16
+ from beratools.core.constants import *
17
+ from common import *
18
+
19
+ log = Logger('centerline', file_level=logging.INFO)
20
+ logger = log.get_logger()
21
+ print = log.print
22
+
23
+
24
+ def process_single_line(line_args):
25
+ line = line_args[0][0]
26
+ prop = line_args[0][1]
27
+ line_radius = line_args[1]
28
+ in_raster = line_args[2]
29
+ line_id = line_args[3]
30
+ seed_line = shape(line) # LineString
31
+ line_radius = float(line_radius)
32
+
33
+ default_return = (seed_line, seed_line, prop, None)
34
+
35
+ cost_clip, out_meta = clip_raster(in_raster, seed_line, line_radius)
36
+
37
+ if not HAS_COST_RASTER:
38
+ cost_clip, _ = cost_raster(cost_clip, out_meta)
39
+
40
+ lc_path = line
41
+ try:
42
+ if CL_USE_SKIMAGE_GRAPH:
43
+ # skimage shortest path
44
+ lc_path = find_least_cost_path_skimage(cost_clip, out_meta, seed_line)
45
+ else:
46
+ lc_path = find_least_cost_path(cost_clip, out_meta, seed_line)
47
+ except Exception as e:
48
+ print(e)
49
+ return default_return
50
+
51
+ if lc_path:
52
+ lc_path_coords = lc_path.coords
53
+ else:
54
+ lc_path_coords = []
55
+
56
+ # search for centerline
57
+ if len(lc_path_coords) < 2:
58
+ print('No least cost path detected, use input line.')
59
+ prop['status'] = CenterlineStatus.FAILED.value
60
+ return default_return
61
+
62
+ # get corridor raster
63
+ lc_path = LineString(lc_path_coords)
64
+ cost_clip, out_meta = clip_raster(in_raster, lc_path, line_radius * 0.9)
65
+ if not HAS_COST_RASTER:
66
+ cost_clip, _ = cost_raster(cost_clip, out_meta)
67
+
68
+ out_transform = out_meta['transform']
69
+ transformer = rasterio.transform.AffineTransformer(out_transform)
70
+ cell_size = (out_transform[0], -out_transform[4])
71
+
72
+ x1, y1 = lc_path_coords[0]
73
+ x2, y2 = lc_path_coords[-1]
74
+ source = [transformer.rowcol(x1, y1)]
75
+ destination = [transformer.rowcol(x2, y2)]
76
+ corridor_thresh_cl = corridor_raster(cost_clip, out_meta, source, destination,
77
+ cell_size, FP_CORRIDOR_THRESHOLD)
78
+
79
+ # find contiguous corridor polygon and extract centerline
80
+ df = gpd.GeoDataFrame(geometry=[seed_line], crs=out_meta['crs'])
81
+ corridor_poly_gpd = find_corridor_polygon(corridor_thresh_cl, out_transform, df)
82
+ center_line, status = find_centerline(corridor_poly_gpd.geometry.iloc[0], lc_path)
83
+ prop['status'] = status.value
84
+
85
+ print(" Searching centerline: line {} ".format(line_id), flush=True)
86
+ return center_line, lc_path, prop, corridor_poly_gpd
87
+
88
+
89
+ def centerline(callback, in_line, in_raster, line_radius,
90
+ proc_segments, out_line, processes, verbose):
91
+ if not compare_crs(vector_crs(in_line), raster_crs(in_raster)):
92
+ print("Line and CHM have different spatial references, please check.")
93
+ return
94
+
95
+ # Read input line features
96
+ layer_crs = None
97
+ schema = None
98
+ input_lines = []
99
+
100
+ with fiona.open(in_line) as open_line_file:
101
+ layer_crs = open_line_file.crs
102
+ schema = open_line_file.meta['schema']
103
+ for line in open_line_file:
104
+ if line.geometry:
105
+ if line.geometry['type'] != 'MultiLineString':
106
+ input_lines.append([line.geometry, line.properties])
107
+ else:
108
+ print('MultiLineString found.')
109
+ geoms = shape(line['geometry']).geoms
110
+ for item in geoms:
111
+ line_part = fiona.Geometry.from_dict(item)
112
+ if line_part:
113
+ input_lines.append([line_part, line.properties])
114
+ else:
115
+ print(f'Line {line.id} has empty geometry.')
116
+
117
+ if proc_segments:
118
+ # split line segments at vertices
119
+ input_lines_temp = []
120
+ for line in input_lines:
121
+ line_seg = line[0]
122
+ line_prop = line[1]
123
+ line_segs = segments(line_seg.coordinates)
124
+ line_feats = [(line, line_prop) for line in line_segs]
125
+ if line_segs:
126
+ input_lines_temp.extend(line_feats)
127
+
128
+ input_lines = input_lines_temp
129
+
130
+ # Process lines
131
+ all_lines = []
132
+ i = 0
133
+ for line in input_lines:
134
+ all_lines.append((line, line_radius, in_raster, i))
135
+ i += 1
136
+
137
+ print('{} lines to be processed.'.format(len(all_lines)))
138
+
139
+ lc_path_geoms = []
140
+ feat_props = []
141
+ center_line_geoms = []
142
+ corridor_poly_list = []
143
+ result = execute_multiprocessing(process_single_line, all_lines, 'Centerline',
144
+ processes, 1, verbose=verbose)
145
+
146
+ for item in result:
147
+ center_line = item[0]
148
+ lc_path = item[1]
149
+ prop = item[2]
150
+ corridor_poly = item[3]
151
+
152
+ if lc_path and prop:
153
+ lc_path_geoms.append(lc_path)
154
+ feat_props.append(prop)
155
+ center_line_geoms.append(center_line)
156
+ corridor_poly_list.append(corridor_poly)
157
+
158
+ out_centerline_path = Path(out_line)
159
+ schema['properties']['status'] = 'int'
160
+ save_features_to_shapefile(out_centerline_path.as_posix(), layer_crs, center_line_geoms, feat_props, schema)
161
+
162
+ out_least_cost_path = out_centerline_path.with_stem(out_centerline_path.stem + '_least_cost_path')
163
+ save_features_to_shapefile(out_least_cost_path.as_posix(), layer_crs, lc_path_geoms, feat_props, schema)
164
+
165
+ # save corridor polygons
166
+ corridor_polys = pd.concat(corridor_poly_list)
167
+ out_corridor_poly_path = Path(out_line)
168
+ out_corridor_poly_path = out_corridor_poly_path.with_stem(out_corridor_poly_path.stem + '_corridor_poly')
169
+ corridor_polys.to_file(out_corridor_poly_path.as_posix())
170
+
171
+
172
+ if __name__ == '__main__':
173
+ in_args, in_verbose = check_arguments()
174
+ start_time = time.time()
175
+ centerline(print, **in_args.input, processes=int(in_args.processes), verbose=in_verbose)
176
+ print('Elapsed time: {}'.format(time.time() - start_time))