# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Inventário com dados LiDAR na Fazenda Modelo ~~~~~~~~~~~~~~~~~~~~~~~~
#
# Autor: Luiz Carlos Estraviz Rodriguez
#        Departamento de Ciências Florestais
#        ESALQ/USP - 02/Dez/2023
#
# Inventário florestal da Fazenda Modelo
#   - download dos dados mantidos em um repositório github público
#      - shape files dos talhões florestais
#      - LiDAR multitemporal (2013 e 2014)
#   - sugestão de pastas locais
#        C:/LiDAR/PRJ_Modelo/NUVENS/     como pasta para os dados LidAR
#        C:/GitRepo/PRJ_Modelo/     sendo GitRepo a pasta onde ficam os
#                                      projetos sincronizados no github
#
# Linguagem de programação:
#       R (v 4.3)
#       RStudio (v. 2023.09.1 Build 494)
#       Pacote lidR* (v 4.0.3 March 11th, 2023) Jean Romain Roussel
#
# Bibliografia complementar: 
#  https://github.com/r-lidar/lidR/wiki/Area-based-approach-from-A-to-Z
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
rm(list=ls(all=TRUE))                                   # Limpa memória
gc()


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 1. Carrega pacotes, organiza pastas e define funções locais
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ----
if(!require(tidyverse))    # Para melhor manipulação de dados e funções
  install.packages("tidyverse")
library(tidyverse)

if(!require(sf))            # Para manipulação de shapes e outros tipos
  install.packages("sf")                      # de dados espacializados 
library(sf)

if(!require(tidyterra))           # Package para processar mapas raster
  install.packages("tidyterra")
library(tidyterra)

if(!require(terra))           # Package para processar mapas raster
  install.packages("terra")
library(terra)

if(!require(stars))                  # Package que permite rasterização
  install.packages("stars")
library(stars)

if(!require(tools))           # Package para manipular nomes de aquivos
  install.packages("tools")
library(tools)

if(!require(RColorBrewer))          # Package com mais paletas de cores
  install.packages("RColorBrewer")
library(RColorBrewer)

if(!require(progress))          # Package com mais paletas de cores
  install.packages("progress")
library(progress)

if(!require(reshape2))              # Usado na função que gera gráficos
  install.packages("reshape2")         # mais caprichados de correlação
library(reshape2)

if(!require(mapview))             # Package para incluir mapas de fundo
  install.packages("mapview")
library(mapview)

# devtools::install_github("Jean-Romain/rlas", dependencies=TRUE)
# devtools::install_github("Jean-Romain/lidR")
if(!require(lidR))                     # PARA MANPULAÇÃO DE DADOS LiDAR
  install.packages("lidR") # Se preferir instalar a versão mais recente 
library(lidR)              # e tiver o package devtools instalado, rode
                                # alternativamente as duas linhas acima

if(!require(RCSF))    #        Pacote complementar do lidR e necessário
  install.packages("RCSF")           # rodar a função classify_ground()
library(RCSF)

if(!require(future))         # Package que permite ao lidR rodar usando
  install.packages("future")          # processamento paralelo de dados
library(future)
cores  <- as.integer(parallel::detectCores() - 1)
plan(multisession, workers = cores)

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Define pastas e local de trabalho
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
prjNam <- "PRJ_Modelo"                               # Nomeia o projeto

dirProjet <- str_c('C:/GitRepo/',prjNam) # Define diretório de trabalho
if (!dir.exists(dirProjet)) {          # Cria diretório caso não exista
  dir.create('C:/GitRepo/', showWarnings = F)
  dir.create(dirProjet, showWarnings = F)
}
setwd(dirProjet)                     # Define pasta default de trabalho

datDir <- str_c('C:/LiDAR/', prjNam)    # Define raiz da pasta de dados
if (!dir.exists(datDir)) {           # Cria diretórios caso não existam
  dir.create('C:/LiDAR/', showWarnings = F)
  dir.create(datDir, showWarnings = F)
}

lidDir <- str_c(datDir, '/NUVENS/')     # Cria sub-pasta p/ dados LiDAR
if (!dir.exists(lidDir)) {
  dir.create(lidDir, showWarnings = F)
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Este bloco cria funções que permitem produzir gráficos esteticamente
# caprichados para apresentar os índices de correlação de Pearson
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Parâmetros dos gráficos  
txt.size = 6
thm.size = (14/5) * txt.size

Tema <- theme(
  axis.line    = element_line(size = .5, colour = "black"),
  axis.text.x  = element_text(angle = 45, size = thm.size, vjust = 1, hjust = 1),
  axis.title.y = element_blank(),
  axis.text.y  = element_text(angle = 45, size = thm.size),
  axis.ticks   = element_blank(),
  legend.justification = c(1, 0),
  legend.position  = c(1, 0.5),
  legend.text      = element_text(size = 12, colour="black"),
  legend.title = element_blank(),
  panel.border     = element_blank(),
  panel.background = element_blank(),
  panel.grid.major = element_line(colour = "grey"),
  title = element_text(size = thm.size, colour="black", face="bold"),
  plot.background = element_rect(colour = "black", fill=NA, size=1)
) 

# Função para reordenamento da cormat (correlation matrix)
reorder_cormat <- function(cormat){
  # Use correlation between variables as distance
  dd <- as.dist((1-cormat)/2)
  hc <- hclust(dd)
  cormat <-cormat[hc$order, hc$order]
}

# Função para definição do triângulo superior da cormat
get_upper_tri <- function(cormat){
  cormat[lower.tri(cormat)]<- NA
  return(cormat)
}

# Função para embelazamento da cormat
graphCormat <- function(cormat){
  # Resets font size for correlation matrix graph
  txt.size = 6
  thm.size = (14/5) * txt.size
  upper_tri <- get_upper_tri(cormat)
  # Melt the correlation matrix
  melted_cormat <- melt(upper_tri, na.rm = TRUE)
  p <- ggplot(melted_cormat, aes(Var2, Var1, fill = value))+
    geom_tile(color = "white")+
    scale_fill_gradient2(low = "blue", high = "red", mid = "white",
                         midpoint = 0, limit = c(-1,1), space = "Lab", 
                         name="Pearson\nCorrelation") +
    coord_fixed() +
    geom_text(aes(Var2, Var1, label = value), color = "black", size = 0.7*txt.size) +
    theme(
      axis.line    = element_line(size = .5, colour = "black"),
      axis.title.x = element_blank(),
      axis.text.x  = element_text(angle = 45, size = thm.size, vjust = 1, hjust = 1),
      axis.title.y = element_blank(),
      axis.text.y  = element_text(angle = 45, size = thm.size),
      axis.ticks   = element_blank(),
      legend.direction = "horizontal",
      legend.justification = c(1, 0),
      legend.position  = c(0.6, 0.7),
      legend.text      = element_text(size = 12, colour="black"),
      panel.border     = element_blank(),
      panel.background = element_blank(),
      panel.grid.major = element_line(colour = "grey"),
      title = element_text(size = thm.size, colour="black", face="bold"),
      plot.background = element_rect(colour = "black", fill=NA, size=1)
    ) +
    guides(fill = guide_colorbar(barwidth = 7, barheight = 1,
                                 title.position = "top", title.hjust = 0.5))
  return(p)
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Estimativas. e estatísticas de qualidade da inferência, resultantes
# da amostragem dupla (double sampling)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dsStats = function(y, x, xLarge, alpha=.05){
  n = length(y)
  beta = ( sum(y*x, na.rm=T) - ( sum(x, na.rm=T)*sum(y, na.rm=T) / n ))  / ( sum(x^2, na.rm=T) - (sum(x, na.rm=T)^2 / n) )
  
  rho = cor(y,x)
  N = length(xLarge)
  
  ydsr = mean(y, na.rm=T) + beta * ( mean(xLarge, na.rm=T) - mean(x, na.rm=T) )
  vardsr = (var(y, na.rm=T)/n)*(1 - (rho^2)*(N-n)/N)
  stderr = sqrt(vardsr)
  ci     = calcCI(stderr, n, alpha)
  
  out = c(media = ydsr, var = vardsr, dp = stderr, ic = ci, erro = 100*ci/ydsr, n=n, rho=rho)
  
  return(out)
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Cálculo do número ideal de unidades amostrais em double sampling, ou
# seja, da intensidade amostral necessária para gerar o erro admissível
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dsNumberOfPlots = function(y, x, xLarge, Cpg = 300, 
                           errDesired = .05, alpha = .05){
  
  rho = cor(x,y)
  a = var(y) * (1 - rho^2)
  b = var(y) * rho^2
  
  B = errDesired * mean(y)
  qt = qt(1 - alpha/2, length(y)-1)
  
  nG = (sqrt( a*b*Cpg ) + b) / (B^2 / qt^2)
  nP = (sqrt( a*b/Cpg ) + a) / (B^2 / qt^2)
  
  return(nP)
}

# ----


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 2. Leitura de dados
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ----

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Define o nome do arquivo com os dados espacializados disponíveis para
# a área de estudo, faz o download e salva esses dados na devida pasta
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gitOnde <-
 "https://github.com/FlorestaR/dados/blob/main/5_LIDARF/Modelo/CLOUDS/"
gitNome <-c("L0004-C0005.laz", "L0004-C0006.laz",
            "L0005-C0005.laz", "L0005-C0006.laz",      # arquivos LiDAR
            "L0006-C0005.laz", "L0006-C0006.laz")    
anoData <- c("A13")                  # Lê apenas os dados LiDAR de 2013
# anoData <- c("A13", "A14")       # Se quiser baixar os dois conjuntos

# Download dos tiles
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
options(timeout=1000) # Reset timeout oferecendo mais tempo de download
for (ano in anoData) {     # guarda na sub-pasta de dados LiDAR por ano
    dirLAZ <- str_c(lidDir, ano)
  dir.create(dirLAZ, showWarnings = T)
  for (nome in gitNome){         # acrescenta "?raw=true" no fim do URL
    gitFile <- str_c(gitOnde, ano, "/", ano, nome, "?raw=true")
    localFl <- str_c(dirLAZ, "/", nome)
    if(!file.exists(localFl))           # garante download binário (wb)
      download.file(gitFile, mode="wb", destfile = localFl)
  }
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Define o nome do arquivo com os dados espacializados disponíveis para
# a área de estudo, faz o download e salva esses dados na devida pasta
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gitOnde <-
  "https://github.com/FlorestaR/dados/blob/main/5_LIDARF/Modelo/SHAPES"
gitNome <- "fazmodelo.zip"                   # shapes da fazenda modelo
gitArqv <- file.path(gitOnde, gitNome) %>% str_c("?raw=true")

tmpd <- tempdir(check = TRUE)                    # diretório temporário
zipf <- file.path(tmpd, "shapes.zip")              # arquivo temporário

# Download e unzip do coonteúdo do arquivo zipado
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
options(timeout=1000)
if(!file.exists(zipf)) {
  download.file(gitArqv, mode="wb", destfile = zipf) 
}

shpDir <- str_c(dirProjet, '/SHAPES') # Define diretório para os shapes
if (!dir.exists(shpDir)) {             # Cria diretório caso não exista
  dir.create(shpDir, showWarnings = F)
}

unzip(zipf, exdir = shpDir)   # unzipa shps e guarda na pasta de shapes
unlink(zipf)                                  # deleta o arquivo zipado
# ----


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 3. Processamento da variável auxiliar LiDAR e estudo da correlação
#    das métricas LiDAR com os parâmetros de interesse
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ----

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Leitura do catálogo de dados LiDAR                     (2013 ou 2014)
# Exibe conteúdo do catálogo e confere se os mapas de tiles são iguais
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dirLAZ  <- str_c(lidDir, anoData[1])   #  Define local das nuvens LiDAR
ctg_orig <- readLAScatalog(dirLAZ) # LEITURA DAS NUVENS DE PONTOS LiDAR
opt_select(ctg_orig) <- "xyzic" # Só atributos xyz intensid. e classif.

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Lê shape e atributos dos talhoes
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
shpArq  <- str_c(shpDir, "/Modelo_talhoes.shp")      # shape de talhões
tal_shp <- st_read(shpArq)
st_is_valid(tal_shp)        # confere se os elementos do shape estão OK

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Os tiles de dados originais abrangem uma área maior do que a
# necessária. Para reduzir a informação LiDAR que será usada, uma 
# alternativa é usar os limites dos talhões mais um buffer de 10m
# como "tesoura" para clipar as nuvens.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dirTal <- str_c(dirLAZ, '/TALHOES')        # Pasta p/ nuvens por talhao
if (!dir.exists(dirTal)) {
  dir.create(dirTal, showWarnings = F)
}
opt_chunk_buffer(ctg_orig) <- 10                          # 10 m buffer
opt_output_files(ctg_orig) <- str_c(dirTal, "/{SUBTALHAO}")
opt_laz_compression(ctg_orig) <- TRUE              # Mantém formato LAZ
ctg_talh = clip_roi(ctg_orig, tal_shp)       # Usa shape como "tesoura"

# Para plotar a nuvem LiDAR de um dos talhões no catálogo ctg_tal,
# basta ler o respectivo arquivo salvo pela função clip_roi(), 
# como no exemplo abaixo:
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# las <- readLAS(ctg_tal$filename[3])
# plot(las)

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Classifica pontos de solo             (este passo pode ser bem lento)
#       ... espere até que o sinal "stop" desapareça na janela de "log"
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dirNoN <- str_c(dirTal, '/NoNORM') # Nuvens c/ ground não normalizadas
if (!dir.exists(dirNoN)) {
  dir.create(dirNoN, showWarnings = F)
}
opt_output_files(ctg_talh) <- str_c(dirNoN, "/{*}_g")
ctg_gNoN                   <- classify_ground(ctg_talh, csf())

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Normaliza as nuvens de pontos dos talhões          (igualmente lento)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dirNrm <- str_c(dirTal, '/SiNORM')       # Pasta p/ nuvens normalizadas
if (!dir.exists(dirNrm)) {
  dir.create(dirNrm, showWarnings = F)
}
opt_output_files(ctg_gNoN ) <- str_c(dirNrm, "/{*}SiN")
ctg_gSiN                    <- normalize_height(ctg_gNoN, tin())

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Retile dos talhões normalizados para geração de um conjunto de núvens
# mais apropriado para a correta clipagem das parcelas
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dirTNrm <- str_c(dirNrm, '/TILES')        # Pasta p/ tiles normalizados
if (!dir.exists(dirTNrm)) {
  dir.create(dirTNrm, showWarnings = F)
}
# ctg_gSiN <- readLAScatalog(dirTNrm)
opt_output_files(ctg_gSiN) <-     # Onde guardar as nuvens normalizadas
  str_c(dirTNrm, "/FzMod_{XLEFT}_{YBOTTOM}")    # renomeadas com coords
opt_chunk_buffer(ctg_gSiN) <- 0     # sem buffers ao redor de cada tile
opt_chunk_size(ctg_gSiN) <- 300                     # em tiles de 300 m
opt_laz_compression(ctg_gSiN) <- TRUE              # Mantém formato LAZ
ctg_tile <- catalog_retile(ctg_gSiN)                        # e executa
rm(ctg_gNoN, ctg_gSiN, ctg_orig, ctg_talh) # limpa da memória catálogos

# Lê shape e atributos das parcelas p/ acessar parâmetros de interesse
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
shpPar  <- str_c(shpDir, "/Modelo_parcentr.shp")    # shape de parcelas
par_shp <- st_read(shpPar)
st_is_valid(par_shp)        # confere se os elementos do shape estão OK

dirParc <- str_c(dirNrm, '/PARCELAS')      # Pasta p/ parcelas clipadas
if (!dir.exists(dirParc)) {
  dir.create(dirParc, showWarnings = F)
}

# Exibe localização dos tiles, talhoes e parcelas
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
plot(ctg_tile, mapview = TRUE, map.type = "Esri.WorldImagery")
plot(ctg_tile)
plot(tal_shp, add = TRUE, col="black")
plot(par_shp, add = TRUE, col="white")

# Clipa e salva as núvens das parcelas, e calcula
# métricas para cada "voxel" clipado com o shape de parcelas no tiles
# normalizados usando a função .stdmetrics_z de métricas genéricas
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
opt_output_files(ctg_tile) <-     # Onde guardar as nuvens das parcelas
  str_c(dirParc, "/P_{NUMPARCELA}") # renomeadas pelo respectivo número
opt_select(ctg_tile) <- "xyz"   # Carrega na memória apenas coordenadas   
opt_filter(ctg_tile) <- "-drop_z_below 0"     # Ignora pontos com z < 0
D <- plot_metrics(ctg_tile, .stdmetrics_z, par_shp, radius = 11.28)

# Escolhe um subgrupo de métricas e dados para estudo da correlação
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
X <- tibble(D) %>% select(VTCC, MHDOM, IDINV, zmean, 
                          zq45, zq75, zq95, zpcum2, zpcum4, zpcum6,
                          pzabovezmean, pzabove2)

# Prepara o gráfico para análise das correlações
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cormat <- X %>% cor()
cormat <- round(cor(cormat), 2) %>% reorder_cormat()
p      <- graphCormat(cormat)

grfDir <- str_c(dirProjet, '/RESULTADOS/') # Define pasta p/ resultados
if (!dir.exists(grfDir)) {                # Cria pasta, caso não exista
  dir.create(grfDir, showWarnings = F)
}

fileGrf <- str_c(grfDir, "MatrizDeCorrelacoes.jpg")   # Arq para matriz
tituGrf <- "Matriz de Correlações de Pearson"        # Define um título

# Abre "impressora" para gerar e salvar o gráfico no formato JPEG
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
jpeg(fileGrf, width = 1000, height = 1000, units = "px", quality = 200)
plot(p + labs(title=tituGrf))
dev.off()                                        # Fecha a "impressora"

# Análise de regressão por quadrados mínimos ordinários (OLS) para
# determinação do modelo de predição.  Testando: VTCC ~f(zq90,IDINV)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
m <- lm(VTCC ~ zq95 + IDINV, data = X)    # Análise de Regressão Linear
summary(m)                          # Mostra os resultados da regressão
plot(X$VTCC, predict(m))              # Gráfico de observado vs predito
abline(0,1)

# ----


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 4. Criação de mapas raster com as métricas mais promissoras
#    É possível processar a partir deste bloco, desde que já tenham
#    sido processadas as linhas 1 a 229 (Bloco 1.)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ----
prjNam    <- "PRJ_Modelo"                             # Nome do projeto
dirRaizNuvens <- str_c('C:/LiDAR/',prjNam, '/NUVENS') # Raiz das nuvens
dirDadosLiDAR <- str_c(dirRaizNuvens, '/A13/TALHOES/SiNORM')

# Lê catálogo de com as núvens por talhão normalizadas
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ctg_taln <- readLAScatalog(dirDadosLiDAR)
nNuvens  <- length(ctg_taln$filename)    # Número de núvens no catálogo

# Criação de mapas raster formato tif para as métricas de interesse
#   Atribua à variável "interesse" uma das métricas calculadas pela
#   função padrão ".stdmetrics_z", dentre as várias: zmax, zmean, 
#   zsd, zskew, zkurt, zentropy, pzabovezmean, pzabove2, zq5, zq10,
#   zq15, zq20, zq25, zq30, zq35, zq40, zq45, zq50, zq55, zq60, zq65,
#   zq70, zq75, zq80, zq85, zq90, zq95, zpcum1, zpcum2, zpcum3, zpcum4,
#   zpcum5, zpcum6, zpcum7, zpcum8, zpcum9
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
interesse <- "zq95"

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Criação de mapas raster para a métrica de interesse  (PIXEL QUADRADO)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dirDadosRaster <- str_c(dirDadosLiDAR, '/RSTR_qua/')        # Quadrados
if (!dir.exists(dirDadosRaster)) {
  dir.create(dirDadosRaster, showWarnings = F)
}

pb <- progress_bar$new(total = 2*nNuvens) # Reset da barra de progresso
for (i in 1:nNuvens) {
  nuvem   <- ctg_taln$filename[i]
  talhao  <- ctg_taln$filename[i] %>% basename() %>% file_path_sans_ext()
  
  las <- readLAS(nuvem, select = "xyz", 
                 filter = "-drop_z_below 2")    # lê apenas pontos > 2m
  
  pb$tick()                                 # Avança barra de progresso
  inteRaster <- (las %>%   # Cria objeto raster da métrica de interesse
                   pixel_metrics(.stdmetrics_z, res = 20))[[interesse]]
  
  rstNome <- str_c(dirDadosRaster, talhao,'_sqr_', interesse, '.tif')
  writeRaster(inteRaster, rstNome, overwrite=TRUE)  # Salva arquivo tif
  
  pb$tick()                                 # Avança barra de progresso
  rstNome <- str_c(dirDadosRaster, talhao,'_sqr_', interesse, '.png')
  # Cria parâmetros para melhorar a legenda do gráfico raster
  minL = round(minmax(inteRaster)[1])-1         # Menor valor no raster
  maxL = round(minmax(inteRaster)[2])+1         # Maior valor no raster
  brkL = round((maxL - minL) / 5)            # Legenda com cinco breaks
  intL = c(minL + brkL, minL + 2*brkL, minL + 3*brkL, minL + 4*brkL)
  limL = c(minL, maxL)
  titulo  <- str_c('Talhão: ', talhao, ' | Métrica: ', interesse, 
                   ' | Mín: ', minL, ' | Máx: ', maxL)
  p <- ggplot() + geom_spatraster(data = inteRaster, aes()) +
    guides(fill = guide_legend(reverse=F)) +  
    coord_sf(datum=st_crs(31983)) +       # Gráfico numa certa projeção
    scale_fill_whitebox_c(palette = "atlas", direction = -1,
                          breaks = intL, limits = limL)+ggtitle(titulo)
  png(rstNome, 30, 20, 'cm', res = 200)              # Abre "impressão"
  print(p)
  dev.off()                               # Fecha "impressão" do aquivo
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# !!! Método alternativo:
# Criação de mapas raster para a métrica de interesse (PIXEL HEXAGONAL)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# dirDadosRaster <- str_c(dirDadosLiDAR, '/RSTR_hex/')       # Hexagonais
# if (!dir.exists(dirDadosRaster)) {
#   dir.create(dirDadosRaster, showWarnings = F)
# }
# 
# pb <- progress_bar$new(total = 2*nNuvens) # Reset da barra de progresso
# for (i in 1:nNuvens) {
#   nuvem   <- ctg_taln$filename[i]
#   talhao  <- ctg_taln$filename[i] %>% basename() %>% file_path_sans_ext()
# 
#   las <- readLAS(nuvem, select = "xyz",
#                  filter = "-drop_z_below 2")    # lê apenas pontos > 2m
# 
#   pb$tick()                                 # Avança barra de progresso
#   inteRaster <- las %>%
#     hexagon_metrics(.stdmetrics_z, area = 400) %>%
#     select(contains(c(interesse, "geometry"))) %>%
#     st_rasterize()
#   
#   rstNome <- str_c(dirDadosRaster, talhao,'_hex_', interesse, '.tif')
#   write_stars(inteRaster, rstNome)
#   
#   pb$tick()                                 # Avança barra de progresso
#   rstNome <- str_c(dirDadosRaster, talhao,'_hex_', interesse, '.png')
#   titulo  <- str_c('Talhão: ', talhao, '  /  Métrica: ', interesse)
#   inteRaster <- as(inteRaster, "SpatRaster")
#   # Cria parâmetros para melhorar a legenda do gráfico raster
#   minL = round(minmax(inteRaster)[1])-1         # Menor valor no raster
#   maxL = round(minmax(inteRaster)[2])+1         # Maior valor no raster
#   brkL = round((maxL - minL) / 5)            # Legenda com cinco breaks
#   intL = c(minL + brkL, minL + 2*brkL, minL + 3*brkL, minL + 4*brkL)
#   limL = c(minL, maxL)
#   titulo  <- str_c('Talhão: ', talhao, ' | Métrica: ', interesse, 
#                    ' | Mín: ', minL, ' | Máx: ', maxL)
#   p <- ggplot() + geom_spatraster(data = inteRaster, aes()) +
#     guides(fill = guide_legend(reverse=F)) +  
#     coord_sf(datum=st_crs(31983)) +       # Gráfico numa certa projeção
#     scale_fill_whitebox_c(palette = "atlas", direction = -1,
#                           breaks = intL, limits = limL)+ggtitle(titulo) 
#   png(rstNome, 30, 20, 'cm', res = 200)              # Abre "impressão"
#   print(p)
#   dev.off()                               # Fecha "impressão" do aquivo
# }

# Cálculo de mapas raster como função dos mapas rasters de métricas
# criados nos passos anteriores.  Por exemplo, MAPA DE VOLUME
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ctg_taln <- readLAScatalog(dirDadosLiDAR)
nNuvens  <- length(ctg_taln$filename)    # Número de núvens no catálogo

# Criação de mapas raster formato tif para as métricas de interesse
#   Atribua à variável "interesse" uma das métricas calculadas pela
#   função .stdmetrics_z: zmax, zmean, zsd, zskew, zkurt, zentropy,
#   pzabovezmean, pzabove2, zq5, zq10, zq15, zq20, zq25, zq30, zq35, 
#   zq40, zq45, zq50, zq55, zq60, zq65, zq70, zq75, zq80, zq85, zq90,
#   zq95, zpcum1, zpcum2, zpcum3, zpcum4, zpcum5, zpcum6, zpcum7,
#   zpcum8, zpcum9
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
interesse <- "zq95"

# Criação de lista de mapas raster criados nos passos anteriores
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
listaRasters <- list()
for (i in 1:nNuvens) {
  nuvem   <- ctg_taln$filename[i]
  talhao  <- ctg_taln$filename[i] %>% basename() %>% file_path_sans_ext()
  rstNome <- str_c(dirDadosRaster, talhao,'_sqr_', interesse, '.tif')
  
  listaRasters[[i]] <- rast(rstNome)
}

# Junta os rasters para gerar um mapa único com a métrica de interesse
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
rastersJuntos <- do.call(merge, listaRasters)

# Cria gráfico caprichado da métrica de interesse com todos os rasters  
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
minL = round(minmax(rastersJuntos)[1])-1        # Menor valor no raster
maxL = round(minmax(rastersJuntos)[2])+1        # Maior valor no raster
brkL = round((maxL - minL) / 5)              # Legenda com cinco breaks
intL = c(minL + brkL, minL + 2*brkL, minL + 3*brkL, minL + 4*brkL)
limL = c(minL, maxL)
titulo  <- str_c('Métrica: ', names(rastersJuntos),
                 ' | Mín: ', minL, ' | Máx: ', maxL)
p <- ggplot() + geom_spatraster(data = rastersJuntos, aes()) +
  guides(fill = guide_legend(reverse=F)) +  
  coord_sf(datum=st_crs(31983)) +       # Gráfico numa certa projeção
  scale_fill_whitebox_c(palette = "atlas", direction = -1,
                        breaks = intL, limits = limL)+ggtitle(titulo)
rstNome <- str_c(dirDadosRaster, 'talhoes_', names(rastersJuntos), '.png')
png(rstNome, 30, 20, 'cm', res = 200)              # Abre "impressão"
print(p)
dev.off()                               # Fecha "impressão" do aquivo

# Lê o arquivo shape com os limites e atributos dos talhoes
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dirShape <- str_c('C:/GitRepo/',prjNam, '/SHAPES')   # Pasta com shapes
arqShape <- str_c(dirShape, '/Modelo_talhoes.shp')      # Nome do shape

# Cria estrutura de dados (data frame) com os atributos dos talhoes
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
poligonos <- vect(arqShape)

# Plota mapa de rasters juntados sobreposto com os limites dos talhões
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
plot(rastersJuntos)
plot(poligonos, add = TRUE, col = "white")
# ----


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 5 . Cria um novo raster com as estimativas de VOLUME para produção
#     do mapa de VTCC. A esrimativa será obtida a partir do modelo
#     ajustado na fase anterior:
#                           VTCC = -444.142 + 24.63 zq95 + 20.133 IDINV
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ----

# A lista poligonos$SUBTALHAO contém os nomes dos talhões da Fazenda
# Modelo:  "302c" "301a" "301d" "302a". Criação de um vetor com as
# correspondentes idades na mesma ordem, para inclusão no objeto 
# com informações sobres os poligonos
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Idades <- as.numeric(c(5.2, 3.7, 3.7, 5.2))
poligonos$IDINV <- Idades

valoresAtrib <- poligonos$IDINV

# Cria um novo raster com a idade do respectivo polígono atribuída ao
# respectivo pixel sobreposto
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
rastersIdade <- rasterize(poligonos, rastersJuntos,
                          field=valoresAtrib, ext=ext(rastersJuntos), 
                          res=res(rastersJuntos)) %>% rename(IDINV = layer)

# Junta rasters, mantendo os respecivos valores em layers separados,
# renomeia as colunas e assim, agora rastersJuntos tem as varíaveis
# necessárias para estimar o parâmetro de interesse, uma em cada layer.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
rastersFinal <- c(rastersJuntos, rastersIdade)

# Usa a equação ajustada (final do bloco 3 deste script) para estimar
# o parâmetro de interesse
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
rastersVolume <- -444.142 + 24.630 * rastersFinal["zq95"] +
                            20.133 * rastersFinal["IDINV"]
rastersVolume <- rastersVolume %>% rename(VTCC = zq95)

# Junta layer VTCC como novo layer no raster final
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
rastersFinal = c(rastersFinal, rastersVolume) 

# Cria novo raster de volumes mudando para zero valores negativos
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
vtcc <- ifel(rastersFinal[["VTCC"]] < 0, 0, rastersFinal)[["VTCC"]]
minmax(vtcc)[1]
minmax(vtcc)[2]

# Apaga rasters intermediários
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
rm(inteRaster, rastersIdade, rastersJuntos, rastersVolume)

# /////////////////////////////////////////////////////////////////////
#       MAPA DE ESTIMATIVAS DE VOLUME TOTAL COM CASCA (VTCC)
# /////////////////////////////////////////////////////////////////////
minL = round(minmax(vtcc)[1])                   # Menor valor no raster
maxL = round(minmax(vtcc)[2])                   # Maior valor no raster
brkL = round((maxL - minL) / 6)              # Legenda com cinco breaks
intL = c(minL+brkL, minL+2*brkL, minL+3*brkL, minL+4*brkL, minL+5*brkL)
limL = c(minL, maxL)
titulo  <- str_c('Mapa de estimativas de VTCC | Mín: ', minL, 
                 ' | Máx: ', maxL)
p <- ggplot() + geom_spatraster(data = vtcc, aes()) +
  guides(fill = guide_legend(reverse=F)) +  
  coord_sf(datum=st_crs(31983)) +       # Gráfico numa certa projeção
  scale_fill_whitebox_c(palette = "atlas", direction = -1,
                        breaks = intL, limits = limL)+ggtitle(titulo)
rstNome <- str_c(dirDadosRaster, 'talhoes_VTCC.png')
png(rstNome, 30, 20, 'cm', res = 200)              # Abre "impressão"
print(p)
dev.off()                               # Fecha "impressão" do aquivo


# ----

# /////////////////////////////////////////////////////////////////////
#    TENTE AGORA CRIAR A TABELA COM A ESTIMATIVA DE VOLUME CALCULADA
#    PELA AMOSTRAGEM DUPLA (SEMELHANTE À QUE FIZEMOS PARA ACS E ACE)
# /////////////////////////////////////////////////////////////////////


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Este script foi atualizado em Dezembro/2023 depois de instalar,
# na seguinte sequência, as mais recentes versões do pacote lidR*:
#
#   devtools::install_github("Jean-Romain/rlas", dependencies=TRUE)
#   devtools::install_github("Jean-Romain/lidR")
#
#   * lidR latest development version
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#  ----

Referências sobre o pacote lidR para processamento de dados LiDAR:

e sobre os pacotes terra e sf para processamento de dados espaciais: