BERATools 0.2.0__py3-none-any.whl → 0.2.1__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 (153) hide show
  1. beratools/__init__.py +1 -7
  2. beratools/core/algo_centerline.py +491 -351
  3. beratools/core/algo_common.py +497 -0
  4. beratools/core/algo_cost.py +192 -0
  5. beratools/core/{dijkstra_algorithm.py → algo_dijkstra.py} +503 -460
  6. beratools/core/algo_footprint_rel.py +577 -0
  7. beratools/core/algo_line_grouping.py +944 -0
  8. beratools/core/algo_merge_lines.py +214 -0
  9. beratools/core/algo_split_with_lines.py +304 -0
  10. beratools/core/algo_tiler.py +428 -0
  11. beratools/core/algo_vertex_optimization.py +469 -0
  12. beratools/core/constants.py +52 -86
  13. beratools/core/logger.py +76 -85
  14. beratools/core/tool_base.py +196 -133
  15. beratools/gui/__init__.py +11 -15
  16. beratools/gui/{beratools.json → assets/beratools.json} +2185 -2300
  17. beratools/gui/batch_processing_dlg.py +513 -463
  18. beratools/gui/bt_data.py +481 -487
  19. beratools/gui/bt_gui_main.py +710 -691
  20. beratools/gui/main.py +26 -0
  21. beratools/gui/map_window.py +162 -146
  22. beratools/gui/tool_widgets.py +725 -493
  23. beratools/tools/Beratools_r_script.r +1120 -1120
  24. beratools/tools/Ht_metrics.py +116 -116
  25. beratools/tools/__init__.py +7 -7
  26. beratools/tools/batch_processing.py +136 -132
  27. beratools/tools/canopy_threshold_relative.py +672 -670
  28. beratools/tools/canopycostraster.py +222 -222
  29. beratools/tools/centerline.py +136 -176
  30. beratools/tools/common.py +857 -885
  31. beratools/tools/fl_regen_csf.py +428 -428
  32. beratools/tools/forest_line_attributes.py +408 -408
  33. beratools/tools/line_footprint_absolute.py +213 -363
  34. beratools/tools/line_footprint_fixed.py +436 -282
  35. beratools/tools/line_footprint_functions.py +733 -720
  36. beratools/tools/line_footprint_relative.py +73 -64
  37. beratools/tools/line_grouping.py +45 -0
  38. beratools/tools/ln_relative_metrics.py +615 -615
  39. beratools/tools/r_cal_lpi_elai.r +24 -24
  40. beratools/tools/r_generate_pd_focalraster.r +100 -100
  41. beratools/tools/r_interface.py +79 -79
  42. beratools/tools/r_point_density.r +8 -8
  43. beratools/tools/rpy_chm2trees.py +86 -86
  44. beratools/tools/rpy_dsm_chm_by.py +81 -81
  45. beratools/tools/rpy_dtm_by.py +63 -63
  46. beratools/tools/rpy_find_cellsize.py +43 -43
  47. beratools/tools/rpy_gnd_csf.py +74 -74
  48. beratools/tools/rpy_hummock_hollow.py +85 -85
  49. beratools/tools/rpy_hummock_hollow_raster.py +71 -71
  50. beratools/tools/rpy_las_info.py +51 -51
  51. beratools/tools/rpy_laz2las.py +40 -40
  52. beratools/tools/rpy_lpi_elai_lascat.py +466 -466
  53. beratools/tools/rpy_normalized_lidar_by.py +56 -56
  54. beratools/tools/rpy_percent_above_dbh.py +80 -80
  55. beratools/tools/rpy_points2trees.py +88 -88
  56. beratools/tools/rpy_vegcoverage.py +94 -94
  57. beratools/tools/tiler.py +48 -206
  58. beratools/tools/tool_template.py +69 -54
  59. beratools/tools/vertex_optimization.py +61 -620
  60. beratools/tools/zonal_threshold.py +144 -144
  61. beratools-0.2.1.dist-info/METADATA +109 -0
  62. beratools-0.2.1.dist-info/RECORD +74 -0
  63. {beratools-0.2.0.dist-info → beratools-0.2.1.dist-info}/WHEEL +1 -1
  64. {beratools-0.2.0.dist-info → beratools-0.2.1.dist-info}/licenses/LICENSE +22 -22
  65. beratools/gui/cli.py +0 -18
  66. beratools/gui/gui.json +0 -8
  67. beratools/gui_tk/ASCII Banners.txt +0 -248
  68. beratools/gui_tk/__init__.py +0 -20
  69. beratools/gui_tk/beratools_main.py +0 -515
  70. beratools/gui_tk/bt_widgets.py +0 -442
  71. beratools/gui_tk/cli.py +0 -18
  72. beratools/gui_tk/img/BERALogo.png +0 -0
  73. beratools/gui_tk/img/closed.gif +0 -0
  74. beratools/gui_tk/img/closed.png +0 -0
  75. beratools/gui_tk/img/open.gif +0 -0
  76. beratools/gui_tk/img/open.png +0 -0
  77. beratools/gui_tk/img/tool.gif +0 -0
  78. beratools/gui_tk/img/tool.png +0 -0
  79. beratools/gui_tk/main.py +0 -14
  80. beratools/gui_tk/map_window.py +0 -144
  81. beratools/gui_tk/runner.py +0 -1481
  82. beratools/gui_tk/tooltip.py +0 -55
  83. beratools/third_party/pyqtlet2/__init__.py +0 -9
  84. beratools/third_party/pyqtlet2/leaflet/__init__.py +0 -26
  85. beratools/third_party/pyqtlet2/leaflet/control/__init__.py +0 -6
  86. beratools/third_party/pyqtlet2/leaflet/control/control.py +0 -59
  87. beratools/third_party/pyqtlet2/leaflet/control/draw.py +0 -52
  88. beratools/third_party/pyqtlet2/leaflet/control/layers.py +0 -20
  89. beratools/third_party/pyqtlet2/leaflet/core/Parser.py +0 -24
  90. beratools/third_party/pyqtlet2/leaflet/core/__init__.py +0 -2
  91. beratools/third_party/pyqtlet2/leaflet/core/evented.py +0 -180
  92. beratools/third_party/pyqtlet2/leaflet/layer/__init__.py +0 -5
  93. beratools/third_party/pyqtlet2/leaflet/layer/featuregroup.py +0 -34
  94. beratools/third_party/pyqtlet2/leaflet/layer/icon/__init__.py +0 -1
  95. beratools/third_party/pyqtlet2/leaflet/layer/icon/icon.py +0 -30
  96. beratools/third_party/pyqtlet2/leaflet/layer/imageoverlay.py +0 -18
  97. beratools/third_party/pyqtlet2/leaflet/layer/layer.py +0 -105
  98. beratools/third_party/pyqtlet2/leaflet/layer/layergroup.py +0 -45
  99. beratools/third_party/pyqtlet2/leaflet/layer/marker/__init__.py +0 -1
  100. beratools/third_party/pyqtlet2/leaflet/layer/marker/marker.py +0 -91
  101. beratools/third_party/pyqtlet2/leaflet/layer/tile/__init__.py +0 -2
  102. beratools/third_party/pyqtlet2/leaflet/layer/tile/gridlayer.py +0 -4
  103. beratools/third_party/pyqtlet2/leaflet/layer/tile/tilelayer.py +0 -16
  104. beratools/third_party/pyqtlet2/leaflet/layer/vector/__init__.py +0 -5
  105. beratools/third_party/pyqtlet2/leaflet/layer/vector/circle.py +0 -15
  106. beratools/third_party/pyqtlet2/leaflet/layer/vector/circlemarker.py +0 -18
  107. beratools/third_party/pyqtlet2/leaflet/layer/vector/path.py +0 -5
  108. beratools/third_party/pyqtlet2/leaflet/layer/vector/polygon.py +0 -14
  109. beratools/third_party/pyqtlet2/leaflet/layer/vector/polyline.py +0 -18
  110. beratools/third_party/pyqtlet2/leaflet/layer/vector/rectangle.py +0 -14
  111. beratools/third_party/pyqtlet2/leaflet/map/__init__.py +0 -1
  112. beratools/third_party/pyqtlet2/leaflet/map/map.py +0 -220
  113. beratools/third_party/pyqtlet2/mapwidget.py +0 -45
  114. beratools/third_party/pyqtlet2/web/custom.js +0 -43
  115. beratools/third_party/pyqtlet2/web/map.html +0 -23
  116. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/layers-2x.png +0 -0
  117. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/layers.png +0 -0
  118. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-icon-2x.png +0 -0
  119. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-icon.png +0 -0
  120. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-shadow.png +0 -0
  121. beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.css +0 -656
  122. beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.js +0 -6
  123. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.codeclimate.yml +0 -14
  124. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.editorconfig +0 -4
  125. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.gitattributes +0 -22
  126. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.travis.yml +0 -43
  127. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/LICENSE +0 -20
  128. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/layers-2x.png +0 -0
  129. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/layers.png +0 -0
  130. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-icon-2x.png +0 -0
  131. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-icon.png +0 -0
  132. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-shadow.png +0 -0
  133. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet-2x.png +0 -0
  134. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet.png +0 -0
  135. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet.svg +0 -156
  136. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.css +0 -10
  137. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.js +0 -10
  138. beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/LICENSE +0 -22
  139. beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/leaflet.rotatedMarker.js +0 -57
  140. beratools/tools/forest_line_ecosite.py +0 -216
  141. beratools/tools/lapis_all.py +0 -103
  142. beratools/tools/least_cost_path_from_chm.py +0 -152
  143. beratools-0.2.0.dist-info/METADATA +0 -63
  144. beratools-0.2.0.dist-info/RECORD +0 -142
  145. /beratools/gui/{img → assets}/BERALogo.png +0 -0
  146. /beratools/gui/{img → assets}/closed.gif +0 -0
  147. /beratools/gui/{img → assets}/closed.png +0 -0
  148. /beratools/{gui_tk → gui/assets}/gui.json +0 -0
  149. /beratools/gui/{img → assets}/open.gif +0 -0
  150. /beratools/gui/{img → assets}/open.png +0 -0
  151. /beratools/gui/{img → assets}/tool.gif +0 -0
  152. /beratools/gui/{img → assets}/tool.png +0 -0
  153. {beratools-0.2.0.dist-info → beratools-0.2.1.dist-info}/entry_points.txt +0 -0
@@ -1,1120 +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
- #####################################################################################################
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
+ #####################################################################################################