BERATools 0.2.3__py3-none-any.whl → 0.2.5__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.5.dist-info/METADATA +134 -0
  38. beratools-0.2.5.dist-info/RECORD +50 -0
  39. {beratools-0.2.3.dist-info → beratools-0.2.5.dist-info}/WHEEL +1 -1
  40. beratools-0.2.5.dist-info/entry_points.txt +3 -0
  41. beratools-0.2.5.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
@@ -1,1120 +0,0 @@
1
- chm2trees<-function(in_chm,Min_ws,hmin,out_folder,rprocesses)
2
- {
3
- # update.packages(list('terra','lidR','future'))
4
- library(lidR)
5
- library(terra)
6
- library(future)
7
-
8
-
9
- plan(multisession, workers = rprocesses)
10
- set_lidr_threads(rprocesses)
11
-
12
- #read Las file and drop any noise from the point cloud
13
- current_chm <- rast(in_chm)
14
- cell_size <- res(current_chm)[1]
15
- # find the highest point of CHM
16
- tallest_ht = minmax(current_chm)[2]
17
-
18
- #Reforestation Standard of Alberta 2018
19
- #(https://www1.agric.gov.ab.ca/$department/deptdocs.nsf/all/formain15749/$FILE/reforestation-standard-alberta-may1-2018.pdf, p.53)
20
- #Live crown ratio is the proportion of total stem length that is covered by living branches. It is expressed as a percentage or decimal of the total tree height. Live crown ratio is a useful indicator of the status of the tree in relation to vigor, photosynthetic leaf area, and is inversely related to stocking density. It is assumed that live crown ratio must be greater than 0.3 (30%) in order for the tree to release well
21
-
22
- if (Min_ws >= (0.3 * hmin)) {
23
- (Min_ws <- Min_ws) }else {
24
- (Min_ws <- 0.3 * hmin) }
25
-
26
- f <- function(x) {
27
- y <- (x * 0.3) + Min_ws
28
- y[x < hmin] <- (Min_ws) # Smallest Crown
29
- y[x > tallest_ht] <- (tallest_ht * 0.3) # Largest Crown
30
- return(y)
31
- }
32
-
33
- out_ttop_filename = paste0(out_folder, "/", substr(basename(in_chm), 1, nchar(basename(in_chm)) - 4), ".shp")
34
-
35
- ttop <- locate_trees(current_chm, lmf(ws = f, hmin = hmin, shape = "circular"), uniqueness = "bitmerge")
36
-
37
- x <- vect(ttop)
38
- writeVector(x, out_ttop_filename, overwrite = TRUE)
39
- #st_write(ttop,out_ttop_filename)
40
-
41
- }
42
-
43
- ##################################################################################################################
44
- #create a 'generate_pd' function
45
- generate_pd <- function(ctg, radius_fr_CHM, focal_radius, cell_size, cache_folder,
46
- cut_ht, PD_Ground_folder, PD_Total_folder, rprocesses) {
47
- # update.packages(list('terra','lidR','future'))
48
- library(terra)
49
- library(lidR)
50
- library(future)
51
-
52
- plan(multisession, workers = rprocesses)
53
- set_lidr_threads(rprocesses)
54
-
55
- opts <- paste0("-drop_class 7")
56
-
57
- print("Processing using R packages.")
58
-
59
- folder <- paste0(cache_folder, "/nlidar/n_{*}")
60
- opt_output_files(ctg) <- opt_output_files(ctg) <- folder
61
- opt_laz_compression(ctg) <- FALSE
62
- opt_filter(ctg) <- "-drop_class 7"
63
- opt_chunk_alignment(ctg) <- c(0, 0)
64
-
65
- catalog_laxindex = function(ctg)
66
- {
67
- stopifnot(is(ctg, "LAScatalog"))
68
-
69
- opt_chunk_size(ctg) <- 0
70
- opt_chunk_buffer(ctg) <- 0
71
- opt_wall_to_wall(ctg) <- FALSE
72
- opt_output_files(ctg) <- ""
73
-
74
- create_lax_file = function(cluster) {
75
- rlas::writelax(cluster@files)
76
- return(0)
77
- }
78
-
79
- options <- list(need_buffer = FALSE, drop_null = FALSE)
80
-
81
- catalog_apply(ctg, create_lax_file, .options = options)
82
- return(invisible())
83
- }
84
-
85
- #normalized LAS with pulse info
86
- print("Indexing LAS Tiles...")
87
- #lidR:::catalog_laxindex(ctg)
88
- catalog_laxindex(ctg)
89
- print("Normalize point cloud using K-nearest neighbour IDW ...")
90
- normalize_height(ctg, algorithm = knnidw())
91
-
92
- print("Generating point density (total focal sum) raster ...")
93
-
94
- pd_total <- function(chunk, radius, cell_size)
95
- {
96
- las <- readLAS(chunk)
97
- if (is.empty(las)) return(NULL)
98
-
99
- las_1 <- filter_poi(readLAS(chunk), buffer == 0)
100
- hull <- st_convex_hull(las_1)
101
- # bbox <- ext(las_1)
102
-
103
- # convert to SpatialPolygons
104
- bbox <- vect(hull)
105
-
106
- las <- filter_poi(las, Classification != 7L)
107
- #las <- retrieve_pulses(las)
108
- density_raster_total <- rasterize_density(las, res = cell_size, pkg = "terra")[[1]]
109
-
110
- tfw <- focalMat(density_raster_total, radius, "circle")
111
-
112
- tfw[tfw > 0] = 1
113
- tfw[tfw == 0] = NA
114
-
115
- Total_focal <- focal(density_raster_total, w = tfw, fun = "sum", na.rm = TRUE, na.policy = "omit", fillvalue = NA, expand = FALSE)
116
- Total_focal <- crop(Total_focal, bbox)
117
- }
118
-
119
- opt <- list(need_output_file = TRUE, autocrop = TRUE)
120
- opt_chunk_alignment(ctg) <- c(0, 0)
121
- ctg@output_options$
122
- drivers$
123
- SpatRaster$
124
- param$
125
- overwrite <- TRUE
126
- opt_output_files(ctg) <- paste0(PD_Total_folder, "/{*}_PD_Tfocalsum")
127
- opt_stop_early(ctg) <- FALSE
128
- catalog_apply(ctg, pd_total, radius = focal_radius, cell_size = cell_size, .options = opt)
129
-
130
- #load normalized LAS for ground point density
131
- ht <- paste0("-drop_class 7 -drop_z_above ", cut_ht)
132
- ctg2 <- readLAScatalog(paste0(cache_folder, "/nlidar"), filter = ht)
133
- #lidR:::catalog_laxindex(ctg2)
134
- catalog_laxindex(ctg2)
135
-
136
- print("Generating point density (ground focal sum) raster ...")
137
-
138
- pd_ground <- function(chunk, radius, cell_size, cut_ht)
139
- {
140
- las <- readLAS(chunk)
141
- if (is.empty(las)) return(NULL)
142
-
143
- las_1 <- filter_poi(readLAS(chunk), buffer == 0)
144
- hull <- st_convex_hull(las_1)
145
-
146
- # convert to SpatialPolygons
147
- bbox <- vect(hull)
148
- # bbox <- ext(las_1)
149
-
150
- #las <- retrieve_pulses(las)
151
- density_raster_ground <- rasterize_density(las, res = cell_size, pkg = "terra")[[1]]
152
-
153
-
154
- gfw <- focalMat(density_raster_ground, radius, "circle")
155
- gfw[gfw > 0] = 1
156
- gfw[gfw == 0] = NA
157
-
158
- Ground_focal <- focal(density_raster_ground, w = gfw, fun = "sum", na.policy = "omit", na.rm = TRUE, fillvalue = NA, expand = FALSE)
159
- ground_focal <- crop(Ground_focal, bbox)
160
-
161
- }
162
-
163
- opt <- list(need_output_file = TRUE, autocrop = TRUE)
164
- opt_chunk_alignment(ctg2) <- c(0, 0)
165
- ctg2@output_options$
166
- drivers$
167
- SpatRaster$
168
- param$
169
- overwrite <- TRUE
170
- opt_output_files(ctg2) <- paste0(PD_Ground_folder, "/{*}_PD_Gfocalsum")
171
- opt_stop_early(ctg2) <- FALSE
172
- catalog_apply(ctg2, pd_ground, radius = focal_radius, cell_size = cell_size, cut_ht = cut_ht, .options = opt)
173
- # reset R mutilsession back to default
174
- plan(sequential)
175
- }
176
-
177
- #########################################################################################################################
178
- hh_function <- function(in_las_folder, cell_size, Smooth_by, Min_ws, lawn_range, out_folder, rprocesses) {
179
- # update.packages(list('terra','lidR','future','sf'))
180
- library(lidR)
181
- library(terra)
182
- library(sf)
183
- library(future)
184
-
185
- plan(multisession, workers = rprocesses)
186
- set_lidr_threads(rprocesses)
187
-
188
-
189
- print('Generating Hummock/ Hollow Raster ...')
190
- ctg <- readLAScatalog(in_las_folder, select = 'xyzc', filter = '-drop_class 7')
191
-
192
- HH_raster <- function(chunk, radius, cell_size, lawn_range, Smooth_by)
193
- {
194
- las <- readLAS(chunk)
195
- if (is.empty(las)) return(NULL)
196
-
197
- las_1 <- filter_poi(readLAS(chunk), buffer == 0)
198
- hull <- st_convex_hull(las_1)
199
-
200
- # convert to SpatialPolygons
201
- bbox <- vect(hull)
202
- # bbox <- ext(las_1)
203
-
204
- #las to DTM
205
- dtm <- rasterize_terrain(las, res = cell_size, algorithm = tin())
206
-
207
-
208
- gfw <- focalMat(dtm, radius, "circle")
209
- gfw[gfw > 0] = 1
210
- gfw[gfw == 0] = NA
211
-
212
- rdtm <- focal(dtm, w = gfw, fun = Smooth_by, na.policy = "omit", na.rm = TRUE, fillvalue = NA, expand = TRUE)
213
- cond_raster <- (rdtm - dtm)
214
- positive <- abs(lawn_range)
215
- negative <- positive * -1
216
-
217
- HH <- ifel(cond_raster < negative, 1, ifel(cond_raster > positive, -1, 0))
218
-
219
- cont_hh <- (crop(cond_raster, ext(bbox))) * -1
220
- hh <- crop(HH, ext(bbox))
221
-
222
- return(list(cont_hh, hh, radius, Smooth_by))
223
- }
224
-
225
- MultiWrite = function(output_list, file) {
226
- chh = output_list[[1]]
227
- hh = output_list[[2]]
228
- radius = output_list[[3]]
229
- Smooth_by = output_list[[4]]
230
- path1 = gsub("@@@_", "CHH_", file)
231
- path2 = gsub("@@@_", "HH_", file)
232
-
233
- path1 = paste0(path1, "_", Smooth_by, "_", radius, "m.tif")
234
- path2 = paste0(path2, "_", Smooth_by, "_", radius, "m.tif")
235
-
236
- terra::writeRaster(chh, path1, overwrite = TRUE)
237
- terra::writeRaster(hh, path2, overwrite = TRUE)
238
-
239
- }
240
-
241
- MultiWriteDriver = list(
242
- write = MultiWrite,
243
- extension = "",
244
- object = "output_list",
245
- path = "file",
246
- param = list())
247
-
248
- ctg@output_options$drivers$list <- MultiWriteDriver
249
-
250
- opt_chunk_alignment(ctg) <- c(0, 0)
251
- opt_output_files(ctg) <- paste0(out_folder, "/CHH_{*}_", Smooth_by, "_", Min_ws, "m")
252
- ctg@output_options$
253
- drivers$
254
- SpatRaster$
255
- param$
256
- overwrite <- TRUE
257
- opt_stop_early(ctg) <- TRUE
258
- out <- catalog_apply(ctg, HH_raster, radius = Min_ws, cell_size = cell_size, lawn_range = lawn_range, Smooth_by = Smooth_by)
259
-
260
- # reset R mutilsession back to default
261
- plan(sequential)
262
-
263
- }
264
-
265
- #########################################################################################################################
266
- hh_function_byraster <- function(in_raster,cell_size, Min_ws, lawn_range, out_folder,rprocesses){
267
- # update.packages(list('terra'))
268
-
269
- library(terra)
270
- library(utils)
271
- library(base)
272
- library(terra)
273
-
274
- print('Generating Hummock/ Hollow Raster ...')
275
- in_dtm <- rast(in_raster)
276
- filename <- substr(basename(in_raster), 1, nchar(basename(in_raster)) - 4)
277
-
278
- gfw <- focalMat(in_dtm, Min_ws, "circle")
279
- gfw[gfw > 0] = 1
280
- gfw[gfw == 0] = NA
281
-
282
- rdtm <- focal(in_dtm, w = gfw, fun = Smooth_by, na.policy = "omit", na.rm = TRUE, fillvalue = NA, expand = TRUE)
283
- # writeRaster(rdtm,paste0(out_folder,"/rdtm_",filename,".tif"),overwrite=TRUE)
284
- cond_raster <- (rdtm - in_dtm)
285
- writeRaster(cond_raster, paste0(out_folder, "/CHH_", filename, ".tif"), overwrite = TRUE)
286
- positive <- abs(lawn_range)
287
- negative <- positive * -1
288
-
289
- HH <- ifel(cond_raster < negative, 1, ifel(cond_raster > positive, -1, 0))
290
- writeRaster(HH, paste0(out_folder, "/HH_", filename, ".tif"), overwrite = TRUE)
291
-
292
-
293
- }
294
-
295
-
296
- ###################################################################################################################################
297
- pd2cellsize <- function(in_las_folder, rprocesses) {
298
- # update.packages(list('lidR','future'))
299
- library(lidR)
300
- library(future)
301
-
302
- plan(multisession, workers = rprocesses)
303
- set_lidr_threads(rprocesses)
304
-
305
-
306
- print("Calculate output's raster average cell size from point density...")
307
- if (is(in_las_folder, "LAS") || is(in_las_folder, "LAScatalog"))
308
- { ctg <- in_las_folder }
309
- else { ctg <- readLAScatalog(in_las_folder, filter = '-drop_class 7') }
310
-
311
-
312
- point_density <- sum(ctg@data$Number.of.point.records) / st_area(ctg)
313
- mean_pd = (3 / point_density)^(1 / 2)
314
- cell_size = round(0.05 * round(mean_pd / 0.05), 2)
315
- return(cell_size)
316
- }
317
-
318
- ##################################################################################
319
-
320
- points2trees <- function(in_folder, is_normalized, hmin, out_folder, rprocesses, CHMcell_size, cell_size)
321
- {
322
- # update.packages(list('terra','lidR','future'))
323
- library(lidR)
324
- library(terra)
325
- library(future)
326
-
327
- plan(multisession, workers = rprocesses)
328
- set_lidr_threads(rprocesses)
329
-
330
- #normailize point cloud using K-nearest neighbour IDW
331
- if (is_normalized) {
332
- n_las <- readLAScatalog(in_folder, filter = '-drop_class 7 -drop_z_below 0')
333
- }
334
- else {
335
- #read Las file and drop any noise from the point cloud
336
- ctg <- readLAScatalog(in_folder, filter = '-drop_class 7')
337
- opt_output_files(ctg) <- opt_output_files(ctg) <- paste0(out_folder, "/normalized/n_{*}")
338
- print("Normalize lidar data...")
339
- opt_progress(ctg) <- TRUE
340
- n_las <- normalize_height(ctg, algorithm = knnidw())
341
- opt_filter(n_las) <- '-drop_class 7 -drop_z_below 0' }
342
-
343
- # # create a CHM from point cloud for visualization
344
- if (CHMcell_size != -999) {
345
- print("Generating normalized CHM ...")
346
- opt_output_files(n_las) <- opt_output_files(n_las) <- paste0(out_folder, "/chm/{*}_chm")
347
- n_las@output_options$
348
- drivers$
349
- SpatRaster$
350
- param$
351
- overwrite <- TRUE
352
- n_las@output_options$
353
- drivers$
354
- Raster$
355
- param$
356
- overwrite <- TRUE
357
- opt_progress(n_las) <- TRUE
358
- # chm <- rasterize_canopy(n_las, cell_size, pitfree(thresholds = c(0,3,10,15,22,30,38), max_edge = c(0, 1.5)), pkg = "terra")
359
- chm <- rasterize_canopy(n_las, CHMcell_size, dsmtin(max_edge = (3 * CHMcell_size)), pkg = "terra") }
360
-
361
-
362
- print("Compute approximate tree positions ...")
363
-
364
- ctg_detect_tree <- function(chunk, hmin, out_folder, cell_size) {
365
- las <- readLAS(chunk) # read the chunk
366
- if (is.empty(las)) return(NULL) # exit if empty
367
- # quarter_ht<- ((las@header@PHB$`Max Z` + las@header@PHB$`Min Z`)/4)
368
-
369
- f <- function(x) {
370
- # y = 0.4443*(x^0.7874)
371
- y = 0.478676 * (x^0.695289) #base on Plot4209, 4207 and 4203
372
- y[x < hmin] <- 0.478676 * (hmin^0.695289) # Min_ws # smallest window
373
- # y[x > (quarter_ht)] <- 0.478676*(quarter_ht^0.695289) # largest window
374
- # y= 0.39328*x
375
- # y[x <hmin ] <- 0.39328*hmin # largest window
376
- # y[x > (quarter_ht)] <- 0.39328*quarter_ht # smallest window
377
-
378
- return(y) }
379
-
380
- # dynamic searching window is based on the function of (tree height x 0.3)
381
- # dynamic window
382
- ttop <- locate_trees(las, lmf(ws = f, hmin = hmin, shape = "circular"), uniqueness = "gpstime")
383
- # Fix searching window (Testing only)
384
- # ttop <- locate_trees(las, lmf(ws = 3,hmin=hmin,shape="circular"),uniqueness = "gpstime")
385
-
386
- ttop <- crop(vect(ttop), ext(chunk)) # remove the buffer
387
- # generating number of trees per ha raster
388
- # sum_map<-terra::rasterize(ttop,rast(ext(chunk),resolution=cell_size,crs=crs(ttop)),fun=sum)
389
- # sum_map<- classify(sum_map, cbind(NA, 0))
390
-
391
- # return(list(ttop,sum_map))
392
- }
393
-
394
- options <- list(automerge = TRUE, autocrop = TRUE)
395
- # opt_output_files(n_las)<-opt_output_files(n_las)<-paste0(out_folder,"/@@@_{*}")
396
- opt_output_files(n_las) <- paste0(out_folder, "/{*}_tree_min_", hmin, "_m")
397
- n_las@output_options$drivers$sf$param$append <- FALSE
398
- n_las@output_options$
399
- drivers$
400
- SpatVector$
401
- param$
402
- overwrite <- TRUE
403
- opt_progress(n_las) <- TRUE
404
- # MultiWrite = function(output_list, file){
405
- # extent = output_list[[1]]
406
- # sum_map = output_list[[2]]
407
- # path1 = gsub("@@@_","", file)
408
- # path2 = gsub("@@@_","", file)
409
- #
410
- # path1 = paste0(path1, "_trees_above",hmin,"m.shp")
411
- # path2 = paste0(path2, "_Trees_counts_above",hmin,"m.tif")
412
- #
413
- # terra::writeVector(extent, path1, overwrite = TRUE)
414
- # terra::writeRaster(sum_map,path2,overwrite=TRUE)
415
- #
416
- # }
417
- # MultiWriteDriver = list(
418
- # write = MultiWrite,
419
- # extension = "",
420
- # object = "output_list",
421
- # path = "file",
422
- # param = list())
423
-
424
- # n_las@output_options$drivers$list <- MultiWriteDriver
425
-
426
- out <- catalog_apply(n_las, ctg_detect_tree, hmin, out_folder, cell_size, .options = options)
427
- shmin <- as.character(hmin)
428
- shmin <- gsub("\\.", "p", shmin)
429
- writeVector(out, paste0(out_folder, "/Merged_ApproxTrees_above_", shmin, "m.shp", overwrite = TRUE))
430
- # reset R mutilsession back to default
431
- plan(sequential)
432
- }
433
-
434
- #########################################################################################################################################
435
- ht_metrics_lite <- function(in_las_folder, cell_size, out_folder, rprocesses)
436
- {
437
- # update.packages(list('terra','lidR','future'))
438
- library(lidR)
439
- library(terra)
440
- library(future)
441
-
442
- plan(multisession, workers = rprocesses)
443
- set_lidr_threads(rprocesses)
444
-
445
- ctg <- readLAScatalog(in_las_folder, filter = '-drop_class 7 -drop_z_below 0')
446
- opt_output_files(ctg) <- paste0(out_folder, "/{*}_lite_metrics_z")
447
- ctg@output_options$
448
- drivers$
449
- SpatRaster$
450
- param$
451
- overwrite <- TRUE
452
- opt_progress(ctg) <- TRUE
453
- print('Generating height metrics ...')
454
- zmetrics_f <- ~list(
455
- zmax = max(Z),
456
- zmin = min(Z),
457
- zsd = sd(Z),
458
- # zq25 = quantile(Z, probs = 0.25),
459
- zq30 = quantile(Z, probs = 0.30),
460
- # zq35 = quantile(Z, probs = 0.35),
461
- zq40 = quantile(Z, probs = 0.40),
462
- # zq45 = quantile(Z, probs = 0.45),
463
- zq50 = quantile(Z, probs = 0.50),
464
- # zq55 = quantile(Z, probs = 0.55),
465
- zq60 = quantile(Z, probs = 0.60),
466
- # zq65 = quantile(Z, probs = 0.65),
467
- zq70 = quantile(Z, probs = 0.70),
468
- # zq75 = quantile(Z, probs = 0.75),
469
- zq80 = quantile(Z, probs = 0.80),
470
- # zq85 = quantile(Z, probs = 0.85),
471
- zq90 = quantile(Z, probs = 0.90),
472
- # zq95 = quantile(Z, probs = 0.95),
473
- zq99 = quantile(Z, probs = 0.99)
474
- )
475
-
476
- m <- pixel_metrics(ctg, func = zmetrics_f, res = cell_size)
477
- writeRaster(m, paste0(out_folder, "/Merged_metricsZ.tif"), overwrite = TRUE)
478
-
479
- # reset R mutilsession back to default
480
- plan(sequential)
481
- }
482
-
483
- ######################################################################################
484
- veg_cover_percentage <- function(in_las_folder, is_normalized, out_folder, hmin, hmax, cell_size, rprocesses)
485
- {
486
- # update.packages(list('terra','lidR','future'))
487
- library(lidR)
488
- library(terra)
489
- library(future)
490
-
491
- plan(multisession, workers = rprocesses)
492
- set_lidr_threads(rprocesses)
493
-
494
- if (!(is_normalized)) {
495
- ctg <- readLAScatalog(in_las_folder, filter = '-drop_class 7')
496
- opt_output_files(ctg) <- paste0(out_folder, '/normalized/n_{*}')
497
- opt_progress(ctg) <- TRUE
498
- print('Normalize point cloud...')
499
- n_ctg <- normalize_height(ctg, algorithm = knnidw()) }
500
- else {
501
- n_ctg <- readLAScatalog(in_las_folder, filter = '-drop_class 7 -drop_z_below 0')
502
- }
503
-
504
- print('Calculating vegetation coverage ...')
505
-
506
- veg_cover_pmetric <- function(chunk, hmin, hmax, out_folder, cell_size)
507
- {
508
- las <- readLAS(chunk)
509
-
510
- if (is.empty(las)) return(NULL)
511
-
512
- total_pcount <- pixel_metrics(las, func = ~length(Z), pkg = "terra", res = cell_size, start = c(0, 0))
513
- # replace NA with 0
514
- total_pcount <- classify(total_pcount, cbind(NA, 0))
515
- set.names(total_pcount, "Total_Ncount", index = 1)
516
-
517
-
518
- Veg_pcount <- pixel_metrics(las, func = ~length(Z), filter = ~Z >= hmin & Z <= hmax, pkg = "terra", res = cell_size, start = c(0, 0))
519
- # replace NA with 0
520
- Veg_pcount <- classify(Veg_pcount, cbind(NA, 0))
521
- set.names(Veg_pcount, "Veg_Ncount", index = 1)
522
-
523
- veg_percetage <- Veg_pcount / total_pcount
524
- # replace NA with 0
525
- veg_percetage <- classify(veg_percetage, cbind(NA, 0))
526
- set.names(veg_percetage, "Veg_CovPer", index = 1)
527
-
528
- total_pcount <- crop(total_pcount, ext(chunk))
529
- Veg_pcount <- crop(Veg_pcount, ext(chunk))
530
- veg_percetage <- crop(veg_percetage, ext(chunk))
531
-
532
- x <- c(total_pcount, Veg_pcount, veg_percetage)
533
-
534
- }
535
-
536
- #
537
- # MultiWrite = function(output_list, file)
538
- # {
539
- # total_pcount = output_list[[1]]
540
- # Veg_pcount = output_list[[2]]
541
- # veg_CovPer=output_list[[3]]
542
- # path1 = gsub("_@@@","_Total_Ncount", file)
543
- # path2 = gsub("_@@@","_Veg_Ncount", file)
544
- # path3 = gsub("_@@@","_Veg_CovPer", file)
545
- # path1 = paste0(path1, ".tif")
546
- # path2 = paste0(path2, ".tif")
547
- # path3 = paste0(path3, ".tif")
548
- #
549
- # terra::writeRaster(total_pcount,path1,overwrite=TRUE)
550
- # terra::writeRaster(Veg_pcount,path2,overwrite=TRUE)
551
- # terra::writeRaster(veg_CovPer,path3,overwrite=TRUE)
552
- #
553
- #
554
- # }
555
- # MultiWriteDiver = list(
556
- # write = MultiWrite,
557
- # extension = "",
558
- # object = "output_list",
559
- # path = "file",
560
- # param = list())
561
-
562
- opt_output_files(n_ctg) <- paste0(out_folder, "/result/{*}_veg_cover_percentage")
563
- n_ctg@output_options$
564
- drivers$
565
- SpatRaster$
566
- param$
567
- overwrite <- TRUE
568
- # n_ctg@output_options$drivers$list <- MultiWriteDiver
569
- out <- catalog_apply(n_ctg, veg_cover_pmetric, hmin, hmax, out_folder, cell_size)
570
-
571
- # reset R mutilsession back to default
572
- plan(sequential)
573
-
574
- }
575
-
576
- #########################################################################################
577
- percentage_aboveDBH <- function(in_las_folder, is_normalized, out_folder, DBH, cell_size, rprocesses)
578
- {
579
- # update.packages(list('terra','lidR','future'))
580
- library(lidR)
581
- library(terra)
582
- library(future)
583
-
584
- plan(multisession, workers = rprocesses)
585
- set_lidr_threads(rprocesses)
586
- sDBH <- DBH
587
- if (is_normalized) {
588
- print('Loading normalize point cloud...')
589
- n_ctg <- readLAScatalog(in_las_folder, filter = '-drop_class 7 -drop_z_below 0') }
590
- else {
591
- ctg <- readLAScatalog(in_las_folder, filter = '-drop_class 7')
592
- opt_output_files(ctg) <- paste0(out_folder, '/normalized/n_{*}')
593
- opt_progress(ctg) <- TRUE
594
- print('Normalize point cloud...')
595
- n_ctg <- normalize_height(ctg, algorithm = knnidw())
596
- }
597
-
598
- print('Calculating percentage returns above DBH ...')
599
-
600
- compute_aboveDBH <- function(chunk, DBH, out_folder, cell_size)
601
- {
602
- las <- readLAS(chunk)
603
-
604
- if (is.empty(las)) return(NULL)
605
-
606
- total_pcount <- pixel_metrics(las, func = ~length(NumberOfReturns), pkg = "terra", res = cell_size, start = c(0, 0))
607
-
608
- abvDBH_pcount <- pixel_metrics(las, func = ~length(NumberOfReturns), filter = ~Z >= DBH, pkg = "terra", res = cell_size, start = c(0, 0))
609
-
610
- abvDBH_percetage <- abvDBH_pcount / total_pcount
611
- set.names(abvDBH_percetage, "Per_abvDBH", index = 1)
612
- # replace NA with 0
613
- abvDBH_percetage <- classify(abvDBH_percetage, cbind(NA, 0))
614
- abvDBH_percetage <- crop(abvDBH_percetage, ext(chunk))
615
- }
616
-
617
- sDBH <- as.character(sDBH)
618
- sDBH <- gsub("\\.", "p", sDBH)
619
-
620
- opt_output_files(n_ctg) <- paste0(out_folder, "/{*}_return_above_", sDBH, 'm')
621
- n_ctg@output_options$
622
- drivers$
623
- SpatRaster$
624
- param$
625
- overwrite <- TRUE
626
- out <- catalog_apply(n_ctg, compute_aboveDBH, DBH, out_folder, cell_size)
627
- in_file_list = list.files(path = out_folder, pattern = ".tif", all.files = TRUE, full.names = TRUE)
628
- rast_list <- list()
629
- for (i in 1:length(in_file_list)) {
630
- rast_obj <- rast(in_file_list[[i]])
631
- rast_list <- c(rast_list, rast_obj)
632
- }
633
- terra::mosaic(terra::sprc(rast_list), fun = "first", filename = paste0(out_folder, "/Merged__return_above_", sDBH, 'm'), overwrite = TRUE)
634
-
635
-
636
- # reset R mutilsession back to default
637
- plan(sequential)
638
- }
639
-
640
- #########################################################################################
641
- normalized_lidar_knnidw <- function(in_las_folder, out_folder, rprocesses) {
642
- # update.packages(list('lidR','future'))
643
- library(lidR)
644
- library(future)
645
-
646
- plan(multisession, workers = rprocesses)
647
- set_lidr_threads(rprocesses)
648
-
649
- #read Las file and drop any noise from the point cloud
650
- ctg <- readLAScatalog(in_las_folder, filter = '-drop_class 7')
651
- opt_output_files(ctg) <- opt_output_files(ctg) <- paste0(out_folder, "/normalized/n_{*}")
652
- print("Normalize lidar data...")
653
- opt_progress(ctg) <- TRUE
654
- n_las <- normalize_height(ctg, algorithm = knnidw())
655
- # reset R mutilsession back to default
656
- plan(sequential)
657
- }
658
-
659
- ##########################################################################
660
- normalized_lidar_tin <- function(in_las_folder, out_folder, rprocesses) {
661
- # update.packages(list('lidR','future'))
662
- library(lidR)
663
- library(future)
664
-
665
- plan(multisession, workers = rprocesses)
666
- set_lidr_threads(rprocesses)
667
-
668
- #read Las file and drop any noise from the point cloud
669
- ctg <- readLAScatalog(in_las_folder, filter = '-drop_class 7')
670
- opt_output_files(ctg) <- opt_output_files(ctg) <- paste0(out_folder, "/normalized/n_{*}")
671
- print("Normalize lidar data...")
672
- opt_progress(ctg) <- TRUE
673
-
674
- n_las <- normalize_height(ctg, algorithm = tin())
675
- # reset R mutilsession back to default
676
- plan(sequential)
677
- }
678
-
679
- ##########################################################################
680
- normalized_lidar_kriging <- function(in_las_folder, out_folder, rprocesses) {
681
- # update.packages(list('lidR','future'))
682
- library(lidR)
683
- library(future)
684
-
685
- plan(multisession, workers = rprocesses)
686
- set_lidr_threads(rprocesses)
687
-
688
- #read Las file and drop any noise from the point cloud
689
- ctg <- readLAScatalog(in_las_folder, filter = '-drop_class 7')
690
- opt_output_files(ctg) <- opt_output_files(ctg) <- paste0(out_folder, "/normalized/n_{*}")
691
- print("Normalize lidar data...")
692
- opt_progress(ctg) <- TRUE
693
- n_las <- normalize_height(ctg, algorithm = kriging())
694
- # reset R mutilsession back to default
695
- plan(sequential)
696
- }
697
-
698
- #########################################################################################
699
- chm_by_dsmtin <- function(in_las_folder, out_folder, cell_size, is_normalized, rprocesses) {
700
- # update.packages(list('lidR','future'))
701
- library(lidR)
702
- library(future)
703
- if (cell_size < 1.0) { rprocesses = rprocesses / 2 }
704
- plan(multisession, workers = rprocesses)
705
- set_lidr_threads(rprocesses)
706
-
707
- ctg <- readLAScatalog(in_las_folder, filter = '-drop_class 7')
708
- if (is_normalized) {
709
- print("Generating CHM using TIN...")
710
- opt_output_files(ctg) <- opt_output_files(ctg) <- paste0(out_folder, "/{*}_chm") }
711
- else {
712
- print("Generating DSM using TIN...")
713
- opt_output_files(ctg) <- opt_output_files(ctg) <- paste0(out_folder, "/{*}_dsm") }
714
-
715
- ctg@output_options$
716
- drivers$
717
- SpatRaster$
718
- param$
719
- overwrite <- TRUE
720
- ctg@output_options$drivers$Raster$param$overwrite <- TRUE
721
- opt_progress(ctg) <- TRUE
722
- chm <- rasterize_canopy(ctg, cell_size, dsmtin(max_edge = (3 * cell_size)), pkg = "terra")
723
- # reset R mutilsession back to default
724
- plan(sequential)
725
- }
726
-
727
- #########################################################################################
728
- chm_by_pitfree <- function(in_las_folder, out_folder, cell_size, is_normalized, rprocesses) {
729
- # update.packages(list('lidR','future'))
730
- library(lidR)
731
- library(future)
732
- if (cell_size < 1.0) { rprocesses = rprocesses / 2 }
733
- plan(multisession, workers = rprocesses)
734
- set_lidr_threads(rprocesses)
735
-
736
- ctg <- readLAScatalog(in_las_folder, filter = '-drop_class 7 -drop_overlap')
737
-
738
- if (is_normalized) {
739
- print("Generate CHM using Pit-free...")
740
- opt_output_files(ctg) <- opt_output_files(ctg) <- paste0(out_folder, "/{*}_chm") }
741
- else {
742
- print("Generate DSM using Pit-free...")
743
- opt_output_files(ctg) <- opt_output_files(ctg) <- paste0(out_folder, "/{*}_dsm") }
744
-
745
- ctg@output_options$
746
- drivers$
747
- SpatRaster$
748
- param$
749
- overwrite <- TRUE
750
- ctg@output_options$drivers$Raster$param$overwrite <- TRUE
751
- opt_progress(ctg) <- TRUE
752
- chm <- rasterize_canopy(ctg, cell_size, pitfree(thresholds = c(0, 3, 5, 10, 15, 20), max_edge = c(0, 3 * cell_size), subcircle = (cell_size)))
753
- # reset R mutilsession back to default
754
-
755
- rast_list <- list()
756
- for (i in 1:length(chm)) {
757
- rast_obj <- terra::rast(chm[[i]])
758
- rast_list <- c(rast_list, rast_obj)
759
- }
760
- mosaic(sprc(rast_list), fun = "first", filename = paste0(out_folder, "/Merged_CHM.tif"), overwrite = TRUE)
761
-
762
- plan(sequential)
763
-
764
- }
765
-
766
- #########################################################################################
767
- dtm_by_knnidw <- function(in_las_folder, out_folder, cell_size, rprocesses) {
768
- # update.packages(list('lidR','future'))
769
- library(lidR)
770
- library(future)
771
- if (cell_size < 1.0) { rprocesses = rprocesses / 2 }
772
- plan(multisession, workers = rprocesses)
773
- set_lidr_threads(rprocesses)
774
-
775
- ctg <- readLAScatalog(in_las_folder, filter = '-drop_class 7')
776
- print("Generate DTM...")
777
- opt_output_files(ctg) <- opt_output_files(ctg) <- paste0(out_folder, "/{*}_DTM")
778
- ctg@output_options$
779
- drivers$
780
- SpatRaster$
781
- param$
782
- overwrite <- TRUE
783
- ctg@output_options$drivers$Raster$param$overwrite <- TRUE
784
- opt_progress(ctg) <- TRUE
785
- dtm <- rasterize_terrain(ctg, res = cell_size, algorithm = knnidw())
786
- # reset R mutilsession back to default
787
- plan(sequential)
788
- }
789
-
790
- #########################################################################################
791
- dtm_by_kriging <- function(in_las_folder, out_folder, cell_size, rprocesses) {
792
- # update.packages(list('lidR','future'))
793
- library(lidR)
794
- library(future)
795
- if (cell_size < 1.0) { rprocesses = rprocesses / 2 }
796
- plan(multisession, workers = rprocesses)
797
- set_lidr_threads(rprocesses)
798
-
799
- ctg <- readLAScatalog(in_las_folder, filter = '-drop_class 7')
800
- print("Generate DTM...")
801
- opt_output_files(ctg) <- opt_output_files(ctg) <- paste0(out_folder, "/{*}_DTM")
802
- ctg@output_options$
803
- drivers$
804
- SpatRaster$
805
- param$
806
- overwrite <- TRUE
807
- ctg@output_options$drivers$Raster$param$overwrite <- TRUE
808
- opt_progress(ctg) <- TRUE
809
- dtm <- rasterize_terrain(ctg, res = cell_size, algorithm = kriging())
810
- # reset R mutilsession back to default
811
- plan(sequential)
812
- }
813
-
814
- #########################################################################################
815
- dtm_by_tin <- function(in_las_folder, out_folder, cell_size, rprocesses) {
816
- # update.packages(list('lidR','future'))
817
- library(lidR)
818
- library(future)
819
- library(terra)
820
-
821
- if (cell_size < 1.0) { rprocesses = rprocesses / 2 }
822
- plan(multisession, workers = rprocesses)
823
- set_lidr_threads(rprocesses)
824
-
825
- ctg <- readLAScatalog(in_las_folder, filter = '-drop_class 7')
826
- print("Generate DTM...")
827
- opt_output_files(ctg) <- opt_output_files(ctg) <- paste0(out_folder, "/{*}_DTM")
828
- ctg@output_options$
829
- drivers$
830
- SpatRaster$
831
- param$
832
- overwrite <- TRUE
833
- # ctg@output_options$drivers$Raster$param$overwrite <- TRUE
834
- opt_progress(ctg) <- TRUE
835
- dtm <- rasterize_terrain(ctg, res = cell_size, algorithm = tin())
836
- # reset R mutilsession back to default
837
- plan(sequential)
838
- }
839
-
840
- ###########################################################################################
841
- laz2las <- function(in_las_folder, out_folder, rprocesses) {
842
- # update.packages(list('lidR','future'))
843
- library(lidR)
844
- library(future)
845
-
846
- plan(multisession, workers = rprocesses)
847
- set_lidr_threads(rprocesses)
848
-
849
- mywriteLAS = function(chunk) {
850
- las <- readLAS(chunk)
851
-
852
- if (is.empty(las)) return(NULL)
853
- las <- filter_poi(las, buffer == 0)
854
- return(las) }
855
-
856
- #read Laz file and drop any noise from the point cloud
857
- ctg <- readLAScatalog(in_las_folder)
858
- opt_output_files(ctg) <- opt_output_files(ctg) <- paste0(out_folder, "/las/{*}")
859
- opt_laz_compression(ctg) <- FALSE
860
- print("Saving zipped lidar data into *.las format...")
861
- opt_progress(ctg) <- TRUE
862
- catalog_apply(ctg, mywriteLAS)
863
- # reset R mutilsession back to default
864
- plan(sequential)
865
- }
866
-
867
- #############################################################
868
- las_info <- function(in_las_folder, rprocesses) {
869
- library(lidR)
870
- library(future)
871
- # update.packages(list('lidR','future'))
872
-
873
- plan(multisession, workers = rprocesses)
874
- set_lidr_threads(rprocesses)
875
- print("loading LiDAR Data")
876
- ctg <- readLAScatalog(in_las_folder, filter = '-drop_class 7')
877
- print(paste0("Data format: v", (ctg@data$Version.Major[1]), ".", (ctg@data$Version.Minor[1])))
878
- print(paste0("Extent: ", min(ctg@data$Min.X), " ", max(ctg@data$Max.X), " ", min(ctg@data$Min.Y), " ", max(ctg@data$Max.Y)))
879
- print(paste0("Area: ", round(st_area(ctg) / (1000 * 1000), 2), " units²"))
880
- print(paste0("Total Pts: ", sum(ctg@data$Number.of.point.records)))
881
- print(paste0("Density: ", round(sum(ctg@data$Number.of.point.records) / st_area(ctg), 0), " pts/units²"))
882
- print(paste0("Total num. files: ", length(ctg@data$filename)))
883
-
884
-
885
- }
886
-
887
- #######################################################################################################################################
888
- classify_gnd <- function(in_las_folder, out_folder, slope, class_threshold, cloth_resolution, rigidness) {
889
- library(lidR)
890
- library(future)
891
- library(RCSF)
892
-
893
- print("loading LiDAR Data")
894
- plan(multisession, workers = 4)
895
- set_lidr_threads(4)
896
-
897
- ctg <- readLAScatalog(in_las_folder, filter = '-drop_class_7 -drop_overlap')
898
- opt_output_files(ctg) <- paste0(out_folder, "/{*}_gnd_classified")
899
- opt_laz_compression(ctg) <- FALSE
900
- gnd_csf <- csf(slope, class_threshold = class_threshold, cloth_resolution = cloth_resolution, rigidness = rigidness, iterations = 500, time_step = 0.65)
901
- print("Classify ground start....")
902
- c_ctg <- classify_ground(ctg, gnd_csf)
903
- }
904
-
905
- #############################################################################################
906
- conduct_raster <- function(in_las_folder, out_folder, cell_size, rprocesses) {
907
-
908
- library(terra)
909
- library(lidR)
910
- library(future)
911
- library(sf)
912
-
913
- plan(multisession, workers = rprocesses)
914
- set_lidr_threads(rprocesses)
915
-
916
- #normalized LAS with pulse info
917
-
918
- ctg <- readLAScatalog(in_las_folder, filter = '-drop_class 7')
919
- opt_progress(ctg) <- TRUE
920
-
921
- print("Generating multiple conductivity raster on:")
922
- print("CHM, Slope, Roughness, ground point density, intensity raster.")
923
- print("Idea from Correction, update, and enhancement of vectorial forestry line maps using LiDAR data, a pathfinder, and seven metrics, Jean-Romain Roussel, etl 2022.")
924
-
925
- Q_raster <- function(chunk, cell_size)
926
- {
927
- las <- readLAS(chunk)
928
- if (is.empty(las)) return(NULL)
929
-
930
- las_1 <- filter_poi(readLAS(chunk), buffer == 0)
931
- hull <- st_convex_hull(las_1)
932
- bbox <- vect(hull)
933
-
934
-
935
- # message('Generate DTM using Triangulation ...')
936
- dtm <- rasterize_terrain(las, res = cell_size, algorithm = tin(max_edge = (3 * cell_size)))
937
-
938
- n_las <- normalize_height(las, dtm)
939
-
940
- #message("Generating slope conductivity raster...")
941
- slope <- terrain(dtm, "slope", 8)
942
- slope_range = slope@ptr$range_max - slope@ptr$range_min
943
- Qslope <- ifel(slope <= slope_range * 0.1, 1, ifel(slope > slope_range * 0.75, 0, (1 - ((slope - slope@ptr$range_min) / slope_range))))
944
- Qslope[is.na(Qslope)] = 0
945
- Qslope <- terra::crop(Qslope, bbox)
946
-
947
-
948
- # message("Generating roughness conductivity raster...")
949
- roughness <- terrain(dtm, "roughness")
950
- roughness_range = roughness@ptr$range_max - roughness@ptr$range_min
951
- Qrough <- ifel(roughness <= roughness_range * 0.1, 1, ifel(roughness > roughness_range * 0.8, 0, (1 - ((roughness - roughness@ptr$range_min) / roughness_range))))
952
- Qrough[is.na(Qrough)] = 0
953
- Qrough <- terra::crop(Qrough, bbox)
954
-
955
- # message("Generating edge conductivity raster...")
956
- #sobel filter
957
- fx = matrix(c(-1, -2, -1, 0, 0, 0, 1, 2, 1), nrow = 3)
958
- fy = matrix(c(1, 0, -1, 2, 0, -2, 1, 0, -1), nrow = 3)
959
-
960
- dtm_sobelx = focal(dtm, fx, na.policy = "omit")
961
- dtm_sobely = focal(dtm, fy, na.policy = "omit")
962
-
963
- dtm_sobel = sqrt(dtm_sobelx**2 + dtm_sobely**2)
964
- dtm_sobel_range = dtm_sobel@ptr$range_max - dtm_sobel@ptr$range_min
965
- Qedge <- ifel(dtm_sobel <= dtm_sobel_range * 0.15, 1, ifel(dtm_sobel > dtm_sobel_range * 0.85, 0, (1 - ((dtm_sobel - dtm_sobel@ptr$range_min) / dtm_sobel_range))))
966
- Qedge[is.na(Qedge)] = 0
967
- Qedge <- terra::crop(Qedge, bbox)
968
-
969
- # message('Generate CHM...')
970
- chm <- rasterize_canopy(n_las, cell_size, dsmtin(max_edge = (3 * cell_size)), pkg = "terra")
971
- chm_range = chm@ptr$range_max - chm@ptr$range_min
972
- Qchm <- ifel(chm <= chm_range * 0.1, 1, ifel(chm > chm_range * 0.75, 0, (1 - ((chm - chm@ptr$range_min) / chm_range))))
973
- Qchm[is.na(Qchm)] = 0
974
- Qchm <- terra::crop(Qchm, bbox)
975
-
976
- # message("Generating intensity conductivity raster...")
977
- # sensor <- track_sensor(las, Roussel2020(pmin=15))
978
- # las <- normalize_intensity(las, range_correction(sensor,Rs=1800 ))
979
- int_max <- pixel_metrics(las, (~max(Intensity)), cell_size) #,filter = ~ReturnNumber == 1L)
980
- int_min <- pixel_metrics(las, (~min(Intensity)), cell_size) #,filter = ~ReturnNumber == 1L)
981
- irange_map <- int_max - int_min
982
- irange_map[is.na(irange_map)] = 0
983
- iq2 <- global(irange_map, quantile, probs = 0.05, na.rm = TRUE)[[1]]
984
- int_map_range <- irange_map@ptr$range_max - irange_map@ptr$range_min
985
- Qint <- ifel(irange_map <= iq2, 1, ifel(irange_map > int_map_range * 0.75, 0, (1 - ((irange_map - irange_map@ptr$range_min) / int_map_range))))
986
- Qint[is.na(Qint)] = 0
987
- Qint <- terra::crop(Qint, bbox)
988
-
989
- # message("Generating ground point density conductivity raster...")
990
- g = filter_poi(las, Classification == 2L)
991
- gpd <- rasterize_density(g, res = cell_size, pkg = "terra")
992
- # gpd <- pixel_metrics(las, ~list(length(Z)/0.35**2),res=cell_size,filter=~Classification == 2L)
993
- gq2 <- global(gpd, quantile, probs = 0.02, na.rm = TRUE)[[1]]
994
- gpd_range = gpd@ptr$range_max - gpd@ptr$range_min
995
- Qgpd <- ifel(gpd <= gq2, 0, (gpd - gpd@ptr$range_min) / gpd_range)
996
- Qgpd[is.na(Qgpd)] = 0
997
- Qgpd <- terra::crop(Qgpd, bbox)
998
-
999
-
1000
- # message("Generating low vegetation density conductivity raster...")
1001
- l = filter_poi(n_las, Z >= 1.0 &
1002
- Z <= 3 &
1003
- !(Classification %in% c(LASWATER, LASGROUND, LASBUILDING)))
1004
- lower_density <- rasterize_density(l, res = cell_size, pkg = "terra")
1005
- # lower_density <- pixel_metrics(n_las, ~list(length(Z)/0.35**2), cell_size,filter=~(Z>= 0.5 & Z<=3))
1006
- lq2 <- global(lower_density, quantile, probs = 0.02, na.rm = TRUE)[[1]]
1007
- lower_range = lower_density@ptr$range_max - lower_density@ptr$range_min
1008
- Qlower <- ifel(lower_density > lq2, 0, 1)
1009
- Qlower[is.na(Qlower)] = 0
1010
- Qlower <- terra::crop(Qlower, bbox)
1011
-
1012
- # message("Generating combined conductivity raster...")
1013
- Conduct <- (Qslope * Qlower * Qedge) * (0.25 * Qchm +
1014
- 0.25 * Qgpd +
1015
- 0.25 * Qrough +
1016
- 0.25 * Qint)
1017
- cost <- Conduct * -1 + Conduct@ptr$range_max
1018
- cost[is.na(cost)] = 1
1019
-
1020
- dtm <- terra::crop(dtm, bbox)
1021
- dtm[is.na(dtm)] = NaN
1022
- chm <- terra::crop(chm, bbox)
1023
- chm[is.na(chm)] = NaN
1024
-
1025
-
1026
- lower_canopy <- -ifel(lower_density > lq2, 1, 0)
1027
- lower_canopy <- ifel(lower_canopy == -1, 1, lower_canopy)
1028
- upper_canopy <- ifel(chm > 3, 1, 0)
1029
-
1030
- lower_canopy <- extend(lower_canopy, ext(bbox))
1031
- upper_canopy <- extend(upper_canopy, ext(bbox))
1032
-
1033
- canopy <- ifel(upper_canopy == 1, upper_canopy * lower_canopy, upper_canopy + lower_canopy)
1034
- canopy[is.na(canopy)] = 0
1035
-
1036
-
1037
- return(list(Qchm, Qslope, Qrough, Qgpd, Qint, Qedge, Qlower, Conduct, cost, dtm, chm, canopy))
1038
-
1039
- }
1040
-
1041
- MultiWrite = function(output_list, file) {
1042
- Qchm = output_list[[1]]
1043
- Qslope = output_list[[2]]
1044
- Qrough = output_list[[3]]
1045
- Qgpd = output_list[[4]]
1046
- Qint = output_list[[5]]
1047
- Qedge = output_list[[6]]
1048
- Qlower = output_list[[7]]
1049
- Conductivity = output_list[[8]]
1050
- Cost = output_list[[9]]
1051
- dtm = output_list[[10]]
1052
- chm = output_list[[11]]
1053
- canopy = output_list[[12]]
1054
- path1 = gsub("@@@", "Qchm", file)
1055
- path2 = gsub("@@@", "Qslope", file)
1056
- path3 = gsub("@@@", "Qrough", file)
1057
- path4 = gsub("@@@", "Qgpd", file)
1058
- path5 = gsub("@@@", "Qint", file)
1059
- path6 = gsub("@@@", "Qedge", file)
1060
- path7 = gsub("@@@", "Qlower", file)
1061
- path8 = gsub("@@@", "Conductivity", file)
1062
- path9 = gsub("@@@", "Cost", file)
1063
- path10 = gsub("@@@", "DTM", file)
1064
- path11 = gsub("@@@", "CHM", file)
1065
- path12 = gsub("@@@", "Canopy", file)
1066
-
1067
- path1 = paste0(path1, ".tif")
1068
- path2 = paste0(path2, ".tif")
1069
- path3 = paste0(path3, ".tif")
1070
- path4 = paste0(path4, ".tif")
1071
- path5 = paste0(path5, ".tif")
1072
- path6 = paste0(path6, ".tif")
1073
- path7 = paste0(path7, ".tif")
1074
- path8 = paste0(path8, ".tif")
1075
- path9 = paste0(path9, ".tif")
1076
- path10 = paste0(path10, ".tif")
1077
- path11 = paste0(path11, ".tif")
1078
- path12 = paste0(path12, ".tif")
1079
-
1080
- terra::writeRaster(Qchm, path1, overwrite = TRUE)
1081
- terra::writeRaster(Qslope, path2, overwrite = TRUE)
1082
- terra::writeRaster(Qrough, path3, overwrite = TRUE)
1083
- terra::writeRaster(Qgpd, path4, overwrite = TRUE)
1084
- terra::writeRaster(Qint, path5, overwrite = TRUE)
1085
- terra::writeRaster(Qedge, path6, overwrite = TRUE)
1086
- terra::writeRaster(Qlower, path7, overwrite = TRUE)
1087
- terra::writeRaster(Conductivity, path8, overwrite = TRUE)
1088
- terra::writeRaster(Cost, path9, overwrite = TRUE)
1089
- terra::writeRaster(dtm, path10, overwrite = TRUE)
1090
- terra::writeRaster(chm, path11, overwrite = TRUE)
1091
- terra::writeRaster(canopy, path12, overwrite = TRUE)
1092
-
1093
- }
1094
-
1095
- MultiWriteDriver = list(
1096
- write = MultiWrite,
1097
- extension = "",
1098
- object = "output_list",
1099
- path = "file",
1100
- param = list())
1101
-
1102
-
1103
- ctg@output_options$drivers$list <- MultiWriteDriver
1104
- opt_output_files(ctg) <- opt_output_files(ctg) <- paste0(out_folder, "/{*}_@@@")
1105
- opt_laz_compression(ctg) <- FALSE
1106
- opt_progress(ctg) <- TRUE
1107
- opt <- list(need_output_file = TRUE, autocrop = TRUE)
1108
- opt_chunk_alignment(ctg) <- c(0, 0)
1109
- ctg@output_options$
1110
- drivers$
1111
- SpatRaster$
1112
- param$
1113
- overwrite <- TRUE
1114
- opt_stop_early(ctg) <- TRUE
1115
- catalog_apply(ctg, Q_raster, cell_size = cell_size, .options = opt)
1116
- # reset R mutilsession back to default
1117
- plan(sequential)
1118
- }
1119
-
1120
- #####################################################################################################