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,1120 @@
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
+ #####################################################################################################