BERATools 0.2.3__py3-none-any.whl → 0.2.4__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 (78) hide show
  1. beratools/__init__.py +8 -3
  2. beratools/core/{algo_footprint_rel.py → algo_canopy_footprint_exp.py} +176 -139
  3. beratools/core/algo_centerline.py +61 -77
  4. beratools/core/algo_common.py +48 -57
  5. beratools/core/algo_cost.py +18 -25
  6. beratools/core/algo_dijkstra.py +37 -45
  7. beratools/core/algo_line_grouping.py +100 -100
  8. beratools/core/algo_merge_lines.py +40 -8
  9. beratools/core/algo_split_with_lines.py +289 -304
  10. beratools/core/algo_vertex_optimization.py +25 -46
  11. beratools/core/canopy_threshold_relative.py +755 -0
  12. beratools/core/constants.py +8 -9
  13. beratools/{tools → core}/line_footprint_functions.py +411 -258
  14. beratools/core/logger.py +18 -2
  15. beratools/core/tool_base.py +17 -75
  16. beratools/gui/assets/BERALogo.ico +0 -0
  17. beratools/gui/assets/BERA_Splash.gif +0 -0
  18. beratools/gui/assets/BERA_WizardImage.png +0 -0
  19. beratools/gui/assets/beratools.json +475 -2171
  20. beratools/gui/bt_data.py +585 -234
  21. beratools/gui/bt_gui_main.py +129 -91
  22. beratools/gui/main.py +4 -7
  23. beratools/gui/tool_widgets.py +530 -354
  24. beratools/tools/__init__.py +0 -7
  25. beratools/tools/{line_footprint_absolute.py → canopy_footprint_absolute.py} +81 -56
  26. beratools/tools/canopy_footprint_exp.py +113 -0
  27. beratools/tools/centerline.py +30 -37
  28. beratools/tools/check_seed_line.py +127 -0
  29. beratools/tools/common.py +65 -586
  30. beratools/tools/{line_footprint_fixed.py → ground_footprint.py} +140 -117
  31. beratools/tools/line_footprint_relative.py +64 -35
  32. beratools/tools/tool_template.py +48 -40
  33. beratools/tools/vertex_optimization.py +20 -34
  34. beratools/utility/env_checks.py +53 -0
  35. beratools/utility/spatial_common.py +210 -0
  36. beratools/utility/tool_args.py +138 -0
  37. beratools-0.2.4.dist-info/METADATA +134 -0
  38. beratools-0.2.4.dist-info/RECORD +50 -0
  39. {beratools-0.2.3.dist-info → beratools-0.2.4.dist-info}/WHEEL +1 -1
  40. beratools-0.2.4.dist-info/entry_points.txt +3 -0
  41. beratools-0.2.4.dist-info/licenses/LICENSE +674 -0
  42. beratools/core/algo_tiler.py +0 -428
  43. beratools/gui/__init__.py +0 -11
  44. beratools/gui/batch_processing_dlg.py +0 -513
  45. beratools/gui/map_window.py +0 -162
  46. beratools/tools/Beratools_r_script.r +0 -1120
  47. beratools/tools/Ht_metrics.py +0 -116
  48. beratools/tools/batch_processing.py +0 -136
  49. beratools/tools/canopy_threshold_relative.py +0 -672
  50. beratools/tools/canopycostraster.py +0 -222
  51. beratools/tools/fl_regen_csf.py +0 -428
  52. beratools/tools/forest_line_attributes.py +0 -408
  53. beratools/tools/line_grouping.py +0 -45
  54. beratools/tools/ln_relative_metrics.py +0 -615
  55. beratools/tools/r_cal_lpi_elai.r +0 -25
  56. beratools/tools/r_generate_pd_focalraster.r +0 -101
  57. beratools/tools/r_interface.py +0 -80
  58. beratools/tools/r_point_density.r +0 -9
  59. beratools/tools/rpy_chm2trees.py +0 -86
  60. beratools/tools/rpy_dsm_chm_by.py +0 -81
  61. beratools/tools/rpy_dtm_by.py +0 -63
  62. beratools/tools/rpy_find_cellsize.py +0 -43
  63. beratools/tools/rpy_gnd_csf.py +0 -74
  64. beratools/tools/rpy_hummock_hollow.py +0 -85
  65. beratools/tools/rpy_hummock_hollow_raster.py +0 -71
  66. beratools/tools/rpy_las_info.py +0 -51
  67. beratools/tools/rpy_laz2las.py +0 -40
  68. beratools/tools/rpy_lpi_elai_lascat.py +0 -466
  69. beratools/tools/rpy_normalized_lidar_by.py +0 -56
  70. beratools/tools/rpy_percent_above_dbh.py +0 -80
  71. beratools/tools/rpy_points2trees.py +0 -88
  72. beratools/tools/rpy_vegcoverage.py +0 -94
  73. beratools/tools/tiler.py +0 -48
  74. beratools/tools/zonal_threshold.py +0 -144
  75. beratools-0.2.3.dist-info/METADATA +0 -108
  76. beratools-0.2.3.dist-info/RECORD +0 -74
  77. beratools-0.2.3.dist-info/entry_points.txt +0 -2
  78. beratools-0.2.3.dist-info/licenses/LICENSE +0 -22
@@ -28,29 +28,25 @@
28
28
  #
29
29
  # ---------------------------------------------------------------------------
30
30
 
31
- import time
32
- import numpy as np
33
-
34
- import rasterio
35
- from scipy import stats, ndimage
36
31
  from geopandas import GeoDataFrame
37
- from shapely import buffer
38
32
  from rasterio import features
39
- from xrspatial import convolution
40
-
41
- import skimage
42
- from skimage.morphology import *
33
+ from rasterio.mask import mask
34
+ from shapely import LineString, MultiPolygon, Point, buffer
35
+ from shapely.geometry import shape
43
36
  from skimage.graph import MCP_Flexible
37
+ from pathlib import Path
38
+ from xrspatial import convolution
44
39
 
45
- from beratools.core.constants import *
46
40
  from beratools.core.algo_centerline import *
47
- from beratools.tools.common import *
41
+ from beratools.core.canopy_threshold_relative import OperationCancelledException
42
+ from beratools.core.constants import *
48
43
  from beratools.core.tool_base import *
44
+ from beratools.tools.common import *
45
+ from beratools.utility.spatial_common import *
49
46
 
50
47
 
51
48
  def dyn_canopy_cost_raster(args):
52
- # raster_obj = args[0]
53
- in_chm_raster=args[0]
49
+ in_chm_raster = args[0]
54
50
  DynCanTh = args[1]
55
51
  tree_radius = args[2]
56
52
  max_line_dist = args[3]
@@ -65,15 +61,15 @@ def dyn_canopy_cost_raster(args):
65
61
  Side = args[12]
66
62
  canopy_thresh_percentage = float(args[13]) / 100
67
63
  line_buffer = args[14]
64
+ exp_shk_cell=args[15]
68
65
 
69
- if Side == 'Left':
70
- canopy_ht_threshold = line_df.CL_CutHt * canopy_thresh_percentage
71
- elif Side == 'Right':
72
- canopy_ht_threshold = line_df.CR_CutHt * canopy_thresh_percentage
73
- elif Side == 'Center':
66
+ if Side == "Left":
67
+ canopy_ht_threshold = line_df.CL_CutHt.iloc[0] * canopy_thresh_percentage
68
+ elif Side == "Right":
69
+ canopy_ht_threshold = line_df.CR_CutHt.iloc[0] * canopy_thresh_percentage
70
+ elif Side == "Center":
74
71
  canopy_ht_threshold = DynCanTh * canopy_thresh_percentage
75
72
  else:
76
-
77
73
  canopy_ht_threshold = 0.5
78
74
 
79
75
  canopy_ht_threshold = float(canopy_ht_threshold)
@@ -85,47 +81,58 @@ def dyn_canopy_cost_raster(args):
85
81
  cost_raster_exponent = float(exponent)
86
82
 
87
83
  try:
88
-
89
84
  clipped_rasterC, out_meta = clip_raster(in_chm_raster, line_buffer, 0)
90
85
 
91
- #clipped_rasterC, out_transformC = rasterio.mask.mask(raster_obj, [line_buffer], crop=True,
92
- # filled=False)
93
-
94
86
  in_chm = np.squeeze(clipped_rasterC, axis=0)
87
+ if BT_DEBUGGING:
88
+ print('[Debug]: Loading CHM ...')
89
+ cell_x, cell_y = out_meta["transform"][0], -out_meta["transform"][4]
95
90
 
96
- # # make rasterio meta for saving raster later
97
- # out_meta = raster_obj.meta.copy()
98
- # out_meta.update({"driver": "GTiff",
99
- # "height": in_chm.shape[0],
100
- # "width": in_chm.shape[1],
101
- # "nodata": BT_NODATA,
102
- # "transform": out_transformC})
103
-
104
- # print('Loading CHM ...')
105
- cell_x, cell_y = out_meta['transform'][0], -out_meta['transform'][4]
106
-
107
- # print('Preparing Kernel window ...')
108
- kernel = convolution.circle_kernel(cell_x, cell_y, tree_radius)
91
+ if BT_DEBUGGING:
92
+ print('[Debug]: Preparing Kernel window ...')
93
+ kernel = convolution.circle_kernel(cell_x, cell_y, int(tree_radius))
109
94
 
110
95
  # Generate Canopy Raster and return the Canopy array
96
+ if BT_DEBUGGING:
97
+ print('[Debug]: Generate Canopy Raster ...')
111
98
  dyn_canopy_ndarray = dyn_np_cc_map(in_chm, canopy_ht_threshold, BT_NODATA)
112
99
 
113
100
  # Calculating focal statistic from canopy raster
101
+ if BT_DEBUGGING:
102
+ print('[Debug]: Calculating focal statistic from Canopy Raster ...')
114
103
  cc_std, cc_mean = dyn_fs_raster_stdmean(dyn_canopy_ndarray, kernel, BT_NODATA)
115
104
 
116
105
  # Smoothing raster
106
+ if BT_DEBUGGING:
107
+ print('[Debug]: Generating smoothed cost raster ...')
117
108
  cc_smooth = dyn_smooth_cost(dyn_canopy_ndarray, max_line_dist, [cell_x, cell_y])
118
109
  avoidance = max(min(float(canopy_avoid), 1), 0)
119
- cost_clip = dyn_np_cost_raster(dyn_canopy_ndarray, cc_mean, cc_std,
120
- cc_smooth, avoidance, cost_raster_exponent)
121
- negative_cost_clip = np.where(np.isnan(cost_clip), -9999, cost_clip) # TODO was nodata, changed to BT_NODATA_COST
110
+ cost_clip = dyn_np_cost_raster(
111
+ dyn_canopy_ndarray,
112
+ cc_mean,
113
+ cc_std,
114
+ cc_smooth,
115
+ avoidance,
116
+ cost_raster_exponent,
117
+ )
118
+ negative_cost_clip = np.where(
119
+ np.isnan(cost_clip), -9999, cost_clip
120
+ ) # TODO was nodata, changed to BT_NODATA_COST
122
121
  return (
123
- line_df, dyn_canopy_ndarray, negative_cost_clip, out_meta,
124
- max_line_dist, nodata, line_id, Cut_Dist, line_buffer
122
+ line_df,
123
+ dyn_canopy_ndarray,
124
+ negative_cost_clip,
125
+ out_meta,
126
+ max_line_dist,
127
+ nodata,
128
+ line_id,
129
+ Cut_Dist,
130
+ line_buffer,
131
+ exp_shk_cell
125
132
  )
126
133
 
127
134
  except Exception as e:
128
- print("Error in createing (dynamic) cost raster @ {}: {}".format(line_id,e))
135
+ print("[Error]: Error in creating (dynamic) cost raster @ {}: {}".format(line_id, e))
129
136
  return None
130
137
 
131
138
 
@@ -150,15 +157,15 @@ def split_line_npart(line):
150
157
  def split_into_segments(df):
151
158
  odf = df
152
159
  crs = odf.crs
153
- if 'OLnSEG' not in odf.columns.array:
154
- df['OLnSEG'] = np.nan
160
+ if "OLnSEG" not in odf.columns.array:
161
+ df["OLnSEG"] = np.nan
155
162
 
156
163
  df = odf.assign(geometry=odf.apply(lambda x: split_line_fc(x.geometry), axis=1))
157
164
  df = df.explode()
158
165
 
159
- df['OLnSEG'] = df.groupby('OLnFID').cumcount()
166
+ df["OLnSEG"] = df.groupby("OLnFID").cumcount()
160
167
  gdf = GeoDataFrame(df, geometry=df.geometry, crs=crs)
161
- gdf = gdf.sort_values(by=['OLnFID', 'OLnSEG'])
168
+ gdf = gdf.sort_values(by=["OLnFID", "OLnSEG"])
162
169
  gdf = gdf.reset_index(drop=True)
163
170
  return gdf
164
171
 
@@ -166,112 +173,202 @@ def split_into_segments(df):
166
173
  def split_into_equal_nth_segments(df):
167
174
  odf = df
168
175
  crs = odf.crs
169
- if 'OLnSEG' not in odf.columns.array:
170
- df['OLnSEG'] = np.nan
176
+ if "OLnSEG" not in odf.columns.array:
177
+ df["OLnSEG"] = np.nan
171
178
  df = odf.assign(geometry=odf.apply(lambda x: split_line_npart(x.geometry), axis=1))
172
179
  df = df.explode(index_parts=True)
173
180
 
174
- df['OLnSEG'] = df.groupby('OLnFID').cumcount()
181
+ df["OLnSEG"] = df.groupby("OLnFID").cumcount()
175
182
  gdf = GeoDataFrame(df, geometry=df.geometry, crs=crs)
176
- gdf = gdf.sort_values(by=['OLnFID', 'OLnSEG'])
183
+ gdf = gdf.sort_values(by=["OLnFID", "OLnSEG"])
177
184
  gdf = gdf.reset_index(drop=True)
178
185
  return gdf
179
186
 
180
187
 
181
- def generate_line_args(line_seg, work_in_bufferL, work_in_bufferC, raster, tree_radius, max_line_dist,
182
- canopy_avoidance, exponent, work_in_bufferR, canopy_thresh_percentage):
188
+ def generate_line_args(
189
+ line_seg,
190
+ work_in_bufferL,
191
+ work_in_bufferC,
192
+ raster,
193
+ tree_radius,
194
+ max_line_dist,
195
+ canopy_avoidance,
196
+ exponent,
197
+ work_in_bufferR,
198
+ canopy_thresh_percentage,
199
+ ):
183
200
  line_argsL = []
184
201
  line_argsR = []
185
202
  line_argsC = []
186
203
  line_id = 0
187
204
  for record in range(0, len(work_in_bufferL)):
188
- line_bufferL = work_in_bufferL.loc[record, 'geometry']
189
- line_bufferC = work_in_bufferC.loc[record, 'geometry']
190
- LCut = work_in_bufferL.loc[record, 'LDist_Cut']
205
+ line_bufferL = work_in_bufferL.loc[record, "geometry"]
206
+ line_bufferC = work_in_bufferC.loc[record, "geometry"]
207
+ LCut = work_in_bufferL.loc[record, "LDist_Cut"]
191
208
 
192
- clipped_rasterL, out_transformL = rasterio.mask.mask(raster, [line_bufferL], crop=True,
193
- nodata=BT_NODATA, filled=True)
209
+ clipped_rasterL, out_transformL = mask(
210
+ raster, [line_bufferL], crop=True, nodata=BT_NODATA, filled=True
211
+ )
194
212
  clipped_rasterL = np.squeeze(clipped_rasterL, axis=0)
195
213
 
196
- clipped_rasterC, out_transformC = rasterio.mask.mask(raster, [line_bufferC], crop=True,
197
- nodata=BT_NODATA, filled=True)
214
+ clipped_rasterC, out_transformC = mask(
215
+ raster, [line_bufferC], crop=True, nodata=BT_NODATA, filled=True
216
+ )
198
217
 
199
218
  clipped_rasterC = np.squeeze(clipped_rasterC, axis=0)
200
219
 
201
220
  # make rasterio meta for saving raster later
202
221
  out_metaL = raster.meta.copy()
203
- out_metaL.update({"driver": "GTiff",
204
- "height": clipped_rasterL.shape[0],
205
- "width": clipped_rasterL.shape[1],
206
- "nodata": BT_NODATA,
207
- "transform": out_transformL})
222
+ out_metaL.update(
223
+ {
224
+ "driver": "GTiff",
225
+ "height": clipped_rasterL.shape[0],
226
+ "width": clipped_rasterL.shape[1],
227
+ "nodata": BT_NODATA,
228
+ "transform": out_transformL,
229
+ }
230
+ )
208
231
 
209
232
  out_metaC = raster.meta.copy()
210
- out_metaC.update({"driver": "GTiff",
211
- "height": clipped_rasterC.shape[0],
212
- "width": clipped_rasterC.shape[1],
213
- "nodata": BT_NODATA,
214
- "transform": out_transformC})
233
+ out_metaC.update(
234
+ {
235
+ "driver": "GTiff",
236
+ "height": clipped_rasterC.shape[0],
237
+ "width": clipped_rasterC.shape[1],
238
+ "nodata": BT_NODATA,
239
+ "transform": out_transformC,
240
+ }
241
+ )
215
242
 
216
243
  nodata = BT_NODATA
217
- line_argsL.append([clipped_rasterL, float(work_in_bufferL.loc[record, 'DynCanTh']), float(tree_radius),
218
- float(max_line_dist), float(canopy_avoidance), float(exponent), raster.res, nodata,
219
- line_seg.iloc[[record]], out_metaL, line_id, LCut, 'Left', canopy_thresh_percentage,
220
- line_bufferC])
244
+ line_argsL.append(
245
+ [
246
+ clipped_rasterL,
247
+ float(work_in_bufferL.loc[record, "DynCanTh"]),
248
+ float(tree_radius),
249
+ float(max_line_dist),
250
+ float(canopy_avoidance),
251
+ float(exponent),
252
+ raster.res,
253
+ nodata,
254
+ line_seg.iloc[[record]],
255
+ out_metaL,
256
+ line_id,
257
+ LCut,
258
+ "Left",
259
+ canopy_thresh_percentage,
260
+ line_bufferC,
261
+ ]
262
+ )
221
263
 
222
- line_argsC.append([clipped_rasterC, float(work_in_bufferC.loc[record, 'DynCanTh']), float(tree_radius),
223
- float(max_line_dist), float(canopy_avoidance), float(exponent), raster.res, nodata,
224
- line_seg.iloc[[record]], out_metaC, line_id, 10, 'Center', canopy_thresh_percentage,
225
- line_bufferC])
264
+ line_argsC.append(
265
+ [
266
+ clipped_rasterC,
267
+ float(work_in_bufferC.loc[record, "DynCanTh"]),
268
+ float(tree_radius),
269
+ float(max_line_dist),
270
+ float(canopy_avoidance),
271
+ float(exponent),
272
+ raster.res,
273
+ nodata,
274
+ line_seg.iloc[[record]],
275
+ out_metaC,
276
+ line_id,
277
+ 10,
278
+ "Center",
279
+ canopy_thresh_percentage,
280
+ line_bufferC,
281
+ ]
282
+ )
283
+
284
+ print(' "PROGRESS_LABEL Preparing line arguments... {} of {}" '.format(
285
+ line_id ,len(work_in_bufferL) + len(work_in_bufferR)),
286
+ flush=True )
287
+ print(" {}% ".format(
288
+ (line_id / (len(work_in_bufferL) + len(work_in_bufferR))) * 100)
289
+ ,flush=True )
226
290
 
227
291
  line_id += 1
228
292
 
229
293
  line_id = 0
230
294
  for record in range(0, len(work_in_bufferR)):
231
- line_bufferR = work_in_bufferR.loc[record, 'geometry']
232
- RCut = work_in_bufferR.loc[record, 'RDist_Cut']
233
- clipped_rasterR, out_transformR = rasterio.mask.mask(raster, [line_bufferR], crop=True,
234
- nodata=BT_NODATA, filled=True)
295
+ line_bufferR = work_in_bufferR.loc[record, "geometry"]
296
+ RCut = work_in_bufferR.loc[record, "RDist_Cut"]
297
+ clipped_rasterR, out_transformR = mask(
298
+ raster, [line_bufferR], crop=True, nodata=BT_NODATA, filled=True
299
+ )
235
300
  clipped_rasterR = np.squeeze(clipped_rasterR, axis=0)
236
301
 
237
302
  # make rasterio meta for saving raster later
238
303
  out_metaR = raster.meta.copy()
239
- out_metaR.update({"driver": "GTiff",
240
- "height": clipped_rasterR.shape[0],
241
- "width": clipped_rasterR.shape[1],
242
- "nodata": BT_NODATA,
243
- "transform": out_transformR})
244
- line_bufferC = work_in_bufferC.loc[record, 'geometry']
245
- clipped_rasterC, out_transformC = rasterio.mask.mask(raster, [line_bufferC], crop=True,
246
- nodata=BT_NODATA, filled=True)
304
+ out_metaR.update(
305
+ {
306
+ "driver": "GTiff",
307
+ "height": clipped_rasterR.shape[0],
308
+ "width": clipped_rasterR.shape[1],
309
+ "nodata": BT_NODATA,
310
+ "transform": out_transformR,
311
+ }
312
+ )
313
+ line_bufferC = work_in_bufferC.loc[record, "geometry"]
314
+ clipped_rasterC, out_transformC = mask(
315
+ raster, [line_bufferC], crop=True, nodata=BT_NODATA, filled=True
316
+ )
247
317
 
248
318
  clipped_rasterC = np.squeeze(clipped_rasterC, axis=0)
249
319
  out_metaC = raster.meta.copy()
250
- out_metaC.update({"driver": "GTiff",
251
- "height": clipped_rasterC.shape[0],
252
- "width": clipped_rasterC.shape[1],
253
- "nodata": BT_NODATA,
254
- "transform": out_transformC})
320
+ out_metaC.update(
321
+ {
322
+ "driver": "GTiff",
323
+ "height": clipped_rasterC.shape[0],
324
+ "width": clipped_rasterC.shape[1],
325
+ "nodata": BT_NODATA,
326
+ "transform": out_transformC,
327
+ }
328
+ )
255
329
 
256
330
  nodata = BT_NODATA
257
331
  # TODO deal with inherited nodata and BT_NODATA_COST
258
332
  # TODO convert nodata to BT_NODATA_COST
259
- line_argsR.append([clipped_rasterR, float(work_in_bufferR.loc[record, 'DynCanTh']), float(tree_radius),
260
- float(max_line_dist), float(canopy_avoidance), float(exponent), raster.res, nodata,
261
- line_seg.iloc[[record]], out_metaR, line_id, RCut, 'Right', canopy_thresh_percentage,
262
- line_bufferC])
263
-
264
- print(' "PROGRESS_LABEL Preparing... {} of {}" '.format(line_id + 1 + len(work_in_bufferL),
265
- len(work_in_bufferL) + len(work_in_bufferR)),
266
- flush=True)
333
+ line_argsR.append(
334
+ [
335
+ clipped_rasterR,
336
+ float(work_in_bufferR.loc[record, "DynCanTh"]),
337
+ float(tree_radius),
338
+ float(max_line_dist),
339
+ float(canopy_avoidance),
340
+ float(exponent),
341
+ raster.res,
342
+ nodata,
343
+ line_seg.iloc[[record]],
344
+ out_metaR,
345
+ line_id,
346
+ RCut,
347
+ "Right",
348
+ canopy_thresh_percentage,
349
+ line_bufferC,
350
+ ]
351
+ )
352
+
353
+ print(
354
+ ' "PROGRESS_LABEL Preparing line arguments... {} of {}" '.format(
355
+ line_id + 1 + len(work_in_bufferL),
356
+ len(work_in_bufferL) + len(work_in_bufferR),
357
+ ),
358
+ flush=True,
359
+ )
267
360
  print(
268
- ' %{} '.format((line_id + 1 + len(work_in_bufferL)) / (len(work_in_bufferL) + len(work_in_bufferR)) * 100),
269
- flush=True)
361
+ " {}% ".format(
362
+ (line_id + 1 + len(work_in_bufferL)) / (len(work_in_bufferL) + len(work_in_bufferR)) * 100
363
+ ),
364
+ flush=True,
365
+ )
270
366
 
271
367
  line_id += 1
272
368
 
273
369
  return line_argsL, line_argsR, line_argsC
274
370
 
371
+
275
372
  #
276
373
  # def find_corridor_threshold_boundary(canopy_clip, least_cost_path, corridor_raster):
277
374
  # threshold = -1
@@ -338,13 +435,9 @@ def find_corridor_threshold(raster):
338
435
 
339
436
 
340
437
  def process_single_line_relative(segment):
341
- # in_chm = rasterio.open(segment[0])
342
-
343
- # segment[0] = in_chm
344
- #DynCanTh = segment[1]
345
-
346
- # Segment args from mulitprocessing:
347
- # [clipped_chm, float(work_in_bufferR.loc[record, 'DynCanTh']), float(tree_radius),
438
+ # this function takes single line to generate the line footprint
439
+ # Input segment arguments:
440
+ # [in_chm, float(work_in_bufferR.loc[record, 'DynCanTh']), float(tree_radius),
348
441
  # float(max_line_dist), float(canopy_avoidance), float(exponent), raster.res, nodata,
349
442
  # line_seg.iloc[[record]], out_meta, line_id,RCut,Side,canopy_thresh_percentage,line_buffer]
350
443
 
@@ -352,35 +445,39 @@ def process_single_line_relative(segment):
352
445
  segment = dyn_canopy_cost_raster(segment)
353
446
  if segment is None:
354
447
  return None
355
- # Segement after Clipped Canopy and Cost Raster
356
- # line_df, dyn_canopy_ndarray, negative_cost_clip, out_meta, max_line_dist, nodata, line_id,Cut_Dist,line_buffer
448
+ # segment:
449
+ """
450
+ 0:line_df,
451
+ 1:dyn_canopy_ndarray,
452
+ 2:negative_cost_clip,
453
+ 3:out_meta,
454
+ 4:max_line_dist,
455
+ 5:nodata,
456
+ 6:line_id,
457
+ 7:Cut_Dist,
458
+ 8: line_buffer,
459
+ 9: exp_shk_cell
460
+ """
357
461
 
358
- # this function takes single line to work the line footprint
359
462
  # (regardless it process the whole line or individual segment)
360
463
  df = segment[0]
361
464
  in_canopy_r = segment[1]
362
465
  in_cost_r = segment[2]
363
- out_meta = segment[3]
466
+ in_meta = segment[3]
467
+ no_data = segment[5]
468
+ Cut_Dist = segment[7]
469
+ exp_shk_cell=segment[9]
470
+ shapefile_proj = df.crs
471
+ in_transform = in_meta["transform"]
364
472
 
365
- # in_transform = segment[3]
366
473
  if np.isnan(in_canopy_r).all():
367
474
  print("Canopy raster empty")
368
475
 
369
476
  if np.isnan(in_cost_r).all():
370
477
  print("Cost raster empty")
371
478
 
372
- in_meta = segment[3]
373
- exp_shk_cell = segment[4]
374
- no_data = segment[5]
375
- line_id = segment[6]
376
- Cut_Dist = segment[7]
377
- line_bufferR = segment[8]
378
-
379
- shapefile_proj = df.crs
380
- in_transform = in_meta['transform']
381
-
382
- FID = df['OLnSEG'] # segment line feature ID
383
- OID = df['OLnFID'] # original line ID for segment line
479
+ FID = df["OLnSEG"] # segment line feature ID
480
+ OID = df["OLnFID"] # original line ID for segment line
384
481
 
385
482
  segment_list = []
386
483
 
@@ -401,7 +498,7 @@ def process_single_line_relative(segment):
401
498
  if len(in_cost_r.shape) > 2:
402
499
  in_cost_r = np.squeeze(in_cost_r, axis=0)
403
500
  remove_nan_from_array(in_cost_r)
404
- in_cost_r[in_cost_r==no_data]=np.inf
501
+ in_cost_r[in_cost_r == no_data] = np.inf
405
502
 
406
503
  # generate 1m interval points along line
407
504
  distance_delta = 1
@@ -409,9 +506,14 @@ def process_single_line_relative(segment):
409
506
  multipoint_along_line = [feat.interpolate(distance) for distance in distances]
410
507
  multipoint_along_line.append(Point(segment_list[-1]))
411
508
  # Rasterize points along line
412
- rasterized_points_Alongln = features.rasterize(multipoint_along_line, out_shape=in_cost_r.shape,
413
- transform=in_transform,
414
- fill=0, all_touched=True, default_value=1)
509
+ rasterized_points_Alongln = features.rasterize(
510
+ multipoint_along_line,
511
+ out_shape=in_cost_r.shape,
512
+ transform=in_transform,
513
+ fill=0,
514
+ all_touched=True,
515
+ default_value=1,
516
+ )
415
517
  points_Alongln = np.transpose(np.nonzero(rasterized_points_Alongln))
416
518
 
417
519
  # Find minimum cost paths through an N-d costs array.
@@ -419,11 +521,9 @@ def process_single_line_relative(segment):
419
521
  flex_cost_alongLn, flex_back_alongLn = mcp_flexible1.find_costs(starts=points_Alongln)
420
522
 
421
523
  # Generate corridor
422
- # corridor = source_cost_acc + dest_cost_acc
423
524
  corridor = flex_cost_alongLn # +flex_cost_dest #cum_cost_tosource+cum_cost_todestination
424
525
  corridor = np.ma.masked_invalid(corridor)
425
526
 
426
-
427
527
  # Calculate minimum value of corridor raster
428
528
  if not np.ma.min(corridor) is None:
429
529
  corr_min = float(np.ma.min(corridor))
@@ -435,9 +535,9 @@ def process_single_line_relative(segment):
435
535
 
436
536
  # Set minimum as zero and save minimum file
437
537
  # corridor_th_value = find_corridor_threshold(corridor_norm)
438
- corridor_th_value = (Cut_Dist / cell_size_x)
538
+ corridor_th_value = Cut_Dist / cell_size_x
439
539
  if corridor_th_value < 0: # if no threshold found, use default value
440
- corridor_th_value = (FP_CORRIDOR_THRESHOLD / cell_size_x)
540
+ corridor_th_value = FP_CORRIDOR_THRESHOLD / cell_size_x
441
541
 
442
542
  # corridor_th_value = FP_CORRIDOR_THRESHOLD
443
543
  corridor_thresh = np.ma.where(corridor_norm >= corridor_th_value, 1.0, 0.0)
@@ -449,7 +549,7 @@ def process_single_line_relative(segment):
449
549
  # Process: Stamp CC and Max Line Width
450
550
  # Original code here
451
551
  # RasterClass = SetNull(IsNull(CorridorMin),((CorridorMin) + ((Canopy_Raster) >= 1)) > 0)
452
- temp1 = (corridor_thresh + in_canopy_r)
552
+ temp1 = corridor_thresh + in_canopy_r
453
553
  raster_class = np.ma.where(temp1 == 0, 1, 0).data
454
554
 
455
555
  # BERA proposed Binary morphology
@@ -471,20 +571,20 @@ def process_single_line_relative(segment):
471
571
  # BERA proposed Binary morphology Shrink
472
572
  # fileShrink = ndimage.binary_erosion((Expanded),iterations=Exp_Shk_cell,border_value=1)
473
573
  else:
474
- print('No Expand And Shrink cell performed.')
574
+ print("No Expand And Shrink cell performed.")
475
575
 
476
576
  file_shrink = raster_class
477
577
 
478
578
  # Process: Boundary Clean
479
- clean_raster = ndimage.gaussian_filter(file_shrink, sigma=0, mode='nearest')
579
+ clean_raster = ndimage.gaussian_filter(file_shrink, sigma=0, mode="nearest")
480
580
 
481
581
  # creat mask for non-polygon area
482
- mask = np.where(clean_raster == 1, True, False)
582
+ polygon_mask = np.where(clean_raster == 1, True, False)
483
583
  if clean_raster.dtype == np.int64:
484
584
  clean_raster = clean_raster.astype(np.int32)
485
585
 
486
586
  # Process: ndarray to shapely Polygon
487
- out_polygon = features.shapes(clean_raster, mask=mask, transform=in_transform)
587
+ out_polygon = features.shapes(clean_raster, mask=polygon_mask, transform=in_transform)
488
588
 
489
589
  # create a shapely multipolygon
490
590
  multi_polygon = []
@@ -493,13 +593,20 @@ def process_single_line_relative(segment):
493
593
  poly = MultiPolygon(multi_polygon)
494
594
 
495
595
  # create a pandas dataframe for the FP
496
- out_data = pd.DataFrame({'OLnFID': OID, 'OLnSEG': FID, 'CorriThresh': corridor_th_value, 'geometry': poly})
497
- out_gdata = GeoDataFrame(out_data, geometry='geometry', crs=shapefile_proj)
596
+ out_data = pd.DataFrame(
597
+ {
598
+ "OLnFID": OID,
599
+ "OLnSEG": FID,
600
+ "CorriThresh": corridor_th_value,
601
+ "geometry": poly,
602
+ }
603
+ )
604
+ out_gdata = GeoDataFrame(out_data, geometry="geometry", crs=shapefile_proj)
498
605
 
499
606
  return out_gdata, corridor_poly_gpd
500
607
 
501
608
  except Exception as e:
502
- print('Exception: {}'.format(e))
609
+ print("Exception: {}".format(e))
503
610
 
504
611
 
505
612
  def multiprocessing_footprint_relative(line_args, processes):
@@ -507,35 +614,71 @@ def multiprocessing_footprint_relative(line_args, processes):
507
614
  total_steps = len(line_args)
508
615
 
509
616
  feats = []
510
- # chunksize = math.ceil(total_steps / processes)
511
617
  with Pool(processes=processes) as pool:
512
618
  step = 0
513
619
  # execute tasks in order, process results out of order
514
620
  for result in pool.imap_unordered(process_single_line_relative, line_args):
515
621
  if BT_DEBUGGING:
516
- print('Got result: {}'.format(result), flush=True)
622
+ print("Got result: {}".format(result), flush=True)
517
623
  if result != None:
518
624
  feats.append(result)
519
625
  step += 1
520
- print(' "PROGRESS_LABEL Dynamic Segment Line Footprint {} of {}" '.format(step, total_steps),
521
- flush=True)
522
- print(' %{} '.format(step / total_steps * 100), flush=True)
626
+ print(
627
+ ' "PROGRESS_LABEL Dynamic Segment Line Footprint {} of {}" '.format(step, total_steps),
628
+ flush=True,
629
+ )
630
+ print(" {}% ".format(step / total_steps * 100), flush=True)
523
631
  return feats
524
632
  except OperationCancelledException:
525
633
  print("Operation cancelled")
526
634
  return None
527
635
 
528
636
 
529
- def main_line_footprint_relative(callback, in_line, in_chm, max_ln_width, exp_shk_cell, out_footprint, out_centerline,
530
- tree_radius, max_line_dist, canopy_avoidance, exponent, full_step,
531
- canopy_thresh_percentage, processes, verbose):
532
- # use_corridor_th_col = True
533
- line_seg = GeoDataFrame.from_file(in_line)
637
+ def main_line_footprint_relative(
638
+ in_line:str,
639
+ in_chm: str,
640
+ max_ln_width: float,
641
+ out_footprint:str,
642
+ out_centerline:str,
643
+ exp_shk_cell:int,
644
+ tree_radius:float,
645
+ max_line_dist:float,
646
+ canopy_avoidance:float,
647
+ exponent:float,
648
+ full_step:bool,
649
+ canopy_thresh_percentage:int,
650
+ processes:int,
651
+ verbose:bool,
652
+ debug_mode:bool=BT_DEBUGGING,
653
+ )-> None:
654
+ """
655
+ This function take the centerlines with forest canopy height and distance to edge to generate
656
+ dynamic footprint from input CHM. An option smooth centerlines will be generated.
657
+ Args:
658
+ in_line: Path like string for input centerlines
659
+ in_chm: Path like string input CHM
660
+ max_ln_width: Maximum processing width for input lines
661
+ out_footprint: Path like string output footprint
662
+ out_centerline: (Option) Path like string output centerline
663
+ exp_shk_cell: Range as integer used for cell erosion
664
+ tree_radius: Radius of trees influence in the cost raster
665
+ max_line_dist: Maximum Euclidean distance from canopy
666
+ canopy_avoidance: 0 < Ratio < 1 of importance between canopy search radius and Euclidean distance
667
+ exponent: Affects the cost of vegetated areas in an exponential fashion
668
+ canopy_thresh_percentage: Percentage as integer range 1-100
669
+
670
+ Returns:
671
+ New saved footprints and/or with smoothed centerlines dataset(s).
672
+ """
673
+ in_file, layer = decode_file_layer(in_line)
674
+ line_seg = GeoDataFrame.from_file(in_file, layer=layer)
675
+ _, processes = parallel_mode(processes)
534
676
 
535
677
  # If Dynamic canopy threshold column not found, create one
536
- if 'DynCanTh' not in line_seg.columns.array:
537
- print("Please create field {} first".format('DynCanTh'))
538
- exit()
678
+ if "DynCanTh" not in line_seg.columns.array:
679
+ print("[Warning]: Field {} is not found and will be populated default values.".format("DynCanTh"))
680
+ line_seg['DynCanTh']=3.0
681
+
539
682
  if not float(canopy_thresh_percentage):
540
683
  canopy_thresh_percentage = 50
541
684
  else:
@@ -546,115 +689,136 @@ def main_line_footprint_relative(callback, in_line, in_chm, max_ln_width, exp_sh
546
689
  if float(exponent) <= 0.0:
547
690
  exponent = 1.0
548
691
  # If OLnFID column is not found, column will be created
549
- if 'OLnFID' not in line_seg.columns.array:
550
- print("Created {} column in input line data.".format('OLnFID'))
551
- line_seg['OLnFID'] = line_seg.index
692
+ if "OLnFID" not in line_seg.columns.array:
693
+ print("[info]: Created {} column in input line data.".format("OLnFID"))
694
+ line_seg["OLnFID"] = line_seg.index
552
695
 
553
- if 'OLnSEG' not in line_seg.columns.array:
554
- line_seg['OLnSEG'] = 0
696
+ if "OLnSEG" not in line_seg.columns.array:
697
+ line_seg["OLnSEG"] = 0
555
698
 
556
- print('%{}'.format(10))
699
+ print("{}%".format(10))
557
700
 
558
701
  # check coordinate systems between line and raster features
559
702
  with rasterio.open(in_chm) as raster:
560
703
  line_args = []
561
704
 
562
- if compare_crs(vector_crs(in_line), raster_crs(in_chm)):
705
+ if compare_crs(vector_crs(in_file), raster_crs(in_chm)):
563
706
  proc_segments = False
564
707
  if proc_segments:
565
- print("Splitting lines into segments...")
708
+ print("[Info]: Splitting lines into segments...")
566
709
  line_seg_split = split_into_segments(line_seg)
567
- print("Splitting lines into segments...Done")
710
+ print("[info]: Splitting lines into segments...Done")
568
711
  else:
569
712
  if full_step:
570
- print("Tool runs on input lines......")
713
+ print("[info]: Tool runs on input lines......")
571
714
  line_seg_split = line_seg
572
715
  else:
573
- print("Tool runs on input segment lines......")
574
- line_seg_split = split_into_Equal_Nth_segments(line_seg, 250)
716
+ print("[info]: Tool runs on input segment lines......")
717
+ line_seg_split = split_into_equal_Nth_segments(line_seg, 250)
575
718
 
576
- print('%{}'.format(20))
719
+ print("{}%".format(20))
577
720
 
578
721
  work_in_bufferL1 = GeoDataFrame.copy(line_seg_split)
579
722
  work_in_bufferL2 = GeoDataFrame.copy(line_seg_split)
580
723
  work_in_bufferR1 = GeoDataFrame.copy(line_seg_split)
581
724
  work_in_bufferR2 = GeoDataFrame.copy(line_seg_split)
582
725
  work_in_bufferC = GeoDataFrame.copy(line_seg_split)
583
- work_in_bufferL1['geometry'] = buffer(work_in_bufferL1['geometry'], distance=float(max_ln_width) + 1,
584
- cap_style=3, single_sided=True)
585
-
586
- work_in_bufferL2['geometry'] = buffer(work_in_bufferL2['geometry'], distance=-1,
587
- cap_style=3, single_sided=True)
726
+ work_in_bufferL1["geometry"] = buffer(
727
+ work_in_bufferL1["geometry"],
728
+ distance=float(max_ln_width) + 1,
729
+ cap_style=3,
730
+ single_sided=True,
731
+ )
732
+
733
+ work_in_bufferL2["geometry"] = buffer(
734
+ work_in_bufferL2["geometry"],
735
+ distance=-1,
736
+ cap_style=3,
737
+ single_sided=True,
738
+ )
588
739
 
589
740
  work_in_bufferL = GeoDataFrame(pd.concat([work_in_bufferL1, work_in_bufferL2]))
590
- work_in_bufferL = work_in_bufferL.dissolve(by=['OLnFID', 'OLnSEG'], as_index=False)
591
-
592
- work_in_bufferR1['geometry'] = buffer(work_in_bufferR1['geometry'], distance=-float(max_ln_width) - 1,
593
- cap_style=3, single_sided=True)
594
- work_in_bufferR2['geometry'] = buffer(work_in_bufferR2['geometry'], distance=1,
595
- cap_style=3, single_sided=True)
741
+ work_in_bufferL = work_in_bufferL.dissolve(by=["OLnFID", "OLnSEG"], as_index=False)
742
+
743
+ work_in_bufferR1["geometry"] = buffer(
744
+ work_in_bufferR1["geometry"],
745
+ distance=-float(max_ln_width) - 1,
746
+ cap_style=3,
747
+ single_sided=True,
748
+ )
749
+ work_in_bufferR2["geometry"] = buffer(
750
+ work_in_bufferR2["geometry"], distance=1, cap_style=3, single_sided=True
751
+ )
596
752
 
597
753
  work_in_bufferR = GeoDataFrame(pd.concat([work_in_bufferR1, work_in_bufferR2]))
598
- work_in_bufferR = work_in_bufferR.dissolve(by=['OLnFID', 'OLnSEG'], as_index=False)
599
-
600
- work_in_bufferC['geometry'] = buffer(work_in_bufferC['geometry'], distance=float(max_ln_width),
601
- cap_style=3, single_sided=False)
602
- print("Prepare arguments for Dynamic FP ...")
603
- # line_argsL, line_argsR,line_argsC= generate_line_args(line_seg_split, work_in_bufferL,work_in_bufferC,
604
- # raster, tree_radius, max_line_dist,
605
- # canopy_avoidance, exponent, work_in_bufferR,
606
- # canopy_thresh_percentage)
607
-
608
- line_argsL, line_argsR, line_argsC = generate_line_args_DFP_NoClip(line_seg_split, work_in_bufferL,
609
- work_in_bufferC, raster, in_chm,
610
- tree_radius, max_line_dist,
611
- canopy_avoidance, exponent,
612
- work_in_bufferR,
613
- canopy_thresh_percentage)
754
+ work_in_bufferR = work_in_bufferR.dissolve(by=["OLnFID", "OLnSEG"], as_index=False)
755
+
756
+ work_in_bufferC["geometry"] = buffer(
757
+ work_in_bufferC["geometry"],
758
+ distance=float(max_ln_width),
759
+ cap_style=3,
760
+ single_sided=False,
761
+ )
762
+ print("[info]: Prepare arguments for Dynamic FP ...")
763
+ # Just prepare arguments for multiprocessing
764
+ line_argsL, line_argsR, line_argsC = generate_line_args_DFP_NoClip(
765
+ line_seg_split,
766
+ work_in_bufferL,
767
+ work_in_bufferC,
768
+ raster,
769
+ in_chm,
770
+ tree_radius,
771
+ max_line_dist,
772
+ canopy_avoidance,
773
+ exponent,
774
+ work_in_bufferR,
775
+ canopy_thresh_percentage,
776
+ exp_shk_cell,
777
+ )
614
778
 
615
779
  else:
616
- print("Line and canopy raster spatial references are not same, please check.")
780
+ print("[info]: Line and canopy raster spatial references are not same, please check.")
617
781
  exit()
618
782
  # pass center lines for footprint
619
- print("Generating Dynamic footprint ...")
783
+ print("[info]: Generating Dynamic footprint ...")
620
784
 
621
785
  feat_listL = []
622
786
  feat_listR = []
623
- feat_listC = []
624
787
  poly_listL = []
625
788
  poly_listR = []
626
789
  footprint_listL = []
627
790
  footprint_listR = []
628
- footprint_listC = []
629
- # PARALLEL_MODE = ParallelMode.SEQUENTIAL
630
791
  if PARALLEL_MODE == ParallelMode.MULTIPROCESSING:
631
- # feat_listC = multiprocessing_footprint_relative(line_argsC, processes)
632
792
  feat_listL = multiprocessing_footprint_relative(line_argsL, processes)
633
- # feat_listL = execute_multiprocessing(process_single_line_relative,'Footprint',line_argsL, processes)
634
793
  feat_listR = multiprocessing_footprint_relative(line_argsR, processes)
635
- # feat_listR = execute_multiprocessing(process_single_line_relative, 'Footprint', line_argsR, processes)
636
794
 
637
795
  elif PARALLEL_MODE == ParallelMode.SEQUENTIAL:
638
796
  step = 1
639
797
  total_steps = len(line_argsL)
640
- print("There are {} result to process.".format(total_steps))
798
+ print("[info]: There are {} result to process.".format(total_steps))
641
799
  for row in line_argsL:
642
800
  feat_listL.append(process_single_line_relative(row))
643
- print("Footprint (left side) for line {} is done".format(step))
644
- print(' "PROGRESS_LABEL Dynamic Line Footprint {} of {}" '.format(step, total_steps), flush=True)
645
- print(' %{} '.format((step / total_steps) * 100))
801
+ print("[info]: Footprint (left side) for line {} is done".format(step))
802
+ print(
803
+ ' "PROGRESS_LABEL Dynamic Line Footprint {} of {}" '.format(step, total_steps),
804
+ flush=True,
805
+ )
806
+ print(" {}% ".format((step / total_steps) * 100))
646
807
  step += 1
647
808
  step = 1
648
809
  total_steps = len(line_argsR)
649
810
  for row in line_argsR:
650
811
  feat_listR.append(process_single_line_relative(row))
651
- print("Footprint for (right side) line {} is done".format(step))
652
- print(' "PROGRESS_LABEL Dynamic Line Footprint {} of {}" '.format(step, total_steps), flush=True)
653
- print(' %{} '.format((step / total_steps) * 100))
812
+ print("[info]: Footprint for (right side) line {} is done".format(step))
813
+ print(
814
+ ' "PROGRESS_LABEL Dynamic Line Footprint {} of {}" '.format(step, total_steps),
815
+ flush=True,
816
+ )
817
+ print(" {}% ".format((step / total_steps) * 100))
654
818
  step += 1
655
819
 
656
- print('%{}'.format(80))
657
- print("Task done.")
820
+ print("{}%".format(80))
821
+
658
822
 
659
823
  for feat in feat_listL:
660
824
  if feat:
@@ -666,68 +830,57 @@ def main_line_footprint_relative(callback, in_line, in_chm, max_ln_width, exp_sh
666
830
  footprint_listR.append(feat[0])
667
831
  poly_listR.append(feat[1])
668
832
 
669
- print('Writing shapefile ...')
670
- resultsL = GeoDataFrame(pd.concat(footprint_listL))
671
- resultsL['geometry'] = resultsL['geometry'].buffer(0.005)
672
- resultsR = GeoDataFrame(pd.concat(footprint_listR))
673
- resultsR['geometry'] = resultsR['geometry'].buffer(0.005)
674
- resultsL = resultsL.sort_values(by=['OLnFID', 'OLnSEG'])
675
- resultsR = resultsR.sort_values(by=['OLnFID', 'OLnSEG'])
833
+ resultsL = GeoDataFrame(pd.concat(footprint_listL,ignore_index=True))
834
+ resultsL["geometry"] = resultsL["geometry"].buffer(0.005)
835
+ resultsR = GeoDataFrame(pd.concat(footprint_listR,ignore_index=True))
836
+ resultsR["geometry"] = resultsR["geometry"].buffer(0.005)
837
+ resultsL = resultsL.sort_values(by=["OLnFID", "OLnSEG"])
838
+ resultsR = resultsR.sort_values(by=["OLnFID", "OLnSEG"])
676
839
  resultsL = resultsL.reset_index(drop=True)
677
840
  resultsR = resultsR.reset_index(drop=True)
678
841
  #
679
842
 
680
- resultsAll = GeoDataFrame(pd.concat([resultsL, resultsR]))
681
- dissolved_results = resultsAll.dissolve(by='OLnFID', as_index=False)
682
- dissolved_results['geometry'] = dissolved_results['geometry'].buffer(-0.005)
683
- print("Saving output ...")
684
- dissolved_results.to_file(out_footprint)
685
- print("Footprint file saved")
843
+ resultsAll = GeoDataFrame(pd.concat([resultsL, resultsR],ignore_index=True))
844
+ dissolved_results = resultsAll.dissolve(by="OLnFID", as_index=False)
845
+ dissolved_results["geometry"] = dissolved_results["geometry"].buffer(-0.005)
846
+ print("[info]: Saving output ...")
847
+ out_ft_file, layer = decode_file_layer(out_footprint)
848
+ dissolved_results.to_file(out_ft_file, layer=layer)
849
+ print("[info]: Footprint file saved")
686
850
 
687
851
  # dissolved polygon group by column 'OLnFID'
688
- print("Generating centerlines from corridor polygons ...")
852
+ print("[info]: Generating centerlines from corridor polygons ...")
689
853
  resultsCL = GeoDataFrame(pd.concat(poly_listL))
690
- resultsCL['geometry'] = resultsCL['geometry'].buffer(0.005)
854
+ resultsCL["geometry"] = resultsCL["geometry"].buffer(0.005)
691
855
  resultsCR = GeoDataFrame(pd.concat(poly_listR))
692
- resultsCR['geometry'] = resultsCR['geometry'].buffer(0.005)
856
+ resultsCR["geometry"] = resultsCR["geometry"].buffer(0.005)
693
857
 
694
858
  resultsCLR = GeoDataFrame(pd.concat([resultsCL, resultsCR]))
695
- resultsCLR = resultsCLR.dissolve(by='OLnFID', as_index=False)
696
- resultsCLR = resultsCLR.sort_values(by=['OLnFID', 'OLnSEG'])
859
+ resultsCLR = resultsCLR.dissolve(by="OLnFID", as_index=False)
860
+ resultsCLR = resultsCLR.sort_values(by=["OLnFID", "OLnSEG"])
697
861
  resultsCLR = resultsCLR.reset_index(drop=True)
698
- resultsCLR['geometry'] = resultsCLR['geometry'].buffer(-0.005)
862
+ resultsCLR["geometry"] = resultsCLR["geometry"].buffer(-0.005)
699
863
 
700
- # out_centerline=False
701
864
  # save lines to file
702
865
  if out_centerline:
866
+ out_cl_file, layer = decode_file_layer(out_centerline)
703
867
  poly_centerline_gpd = find_centerlines(resultsCLR, line_seg, processes)
704
868
  poly_gpd = poly_centerline_gpd.copy()
705
869
  centerline_gpd = poly_centerline_gpd.copy()
706
870
 
707
- centerline_gpd = centerline_gpd.set_geometry('centerline')
708
- centerline_gpd = centerline_gpd.drop(columns=['geometry'])
871
+ centerline_gpd = centerline_gpd.set_geometry("centerline")
872
+ centerline_gpd = centerline_gpd.drop(columns=["geometry"])
709
873
  centerline_gpd.crs = poly_centerline_gpd.crs
710
- centerline_gpd.to_file(out_centerline)
711
- print("Centerline file saved")
712
-
713
- # save polygons
714
- path = Path(out_centerline)
715
- path = path.with_stem(path.stem + '_poly')
716
- poly_gpd = poly_gpd.drop(columns=['centerline'])
717
- poly_gpd.to_file(path)
718
-
719
- print('%{}'.format(100))
720
-
721
874
 
722
- if __name__ == '__main__':
723
- start_time = time.time()
724
- print('Dynamic Footprint processing started')
725
- print('Current time: {}'.format(time.strftime("%d %b %Y %H:%M:%S", time.localtime())))
875
+ # save polygons from smooth centerline for debug
876
+ if debug_mode:
877
+ path = Path(out_cl_file)
878
+ path = path.with_stem(path.stem + "_poly")
879
+ poly_gpd = poly_gpd.drop(columns=["centerline"])
880
+ poly_gpd.to_file(path,layer='smoothedCL_poly')
881
+ print("[info]: Polygon from smoothed centerline file saved")
726
882
 
727
- in_args, in_verbose = check_arguments()
728
- main_line_footprint_relative(print, **in_args.input, processes=int(in_args.processes), verbose=in_verbose)
883
+ centerline_gpd.to_file(out_cl_file, layer=layer)
884
+ print("[info]: Smoothed centerline file saved")
729
885
 
730
- print('%{}'.format(100))
731
- print('Dynamic Footprint processing finished')
732
- print('Current time: {}'.format(time.strftime("%d %b %Y %H:%M:%S", time.localtime())))
733
- print('Total processing time (seconds): {}'.format(round(time.time() - start_time, 3)))
886
+ print("{}%".format(100))