R Crop no-data of a raster - r

I would like to crop the no-data part of some rasters (example of the image in 1 where no-data is in black) without defining the extent manually.
Any idea?

You can use trim to remove exterior rows and columns that only have NA values:
library(raster)
r <- raster(ncols=18,nrows=18)
r[39:49] <- 1
r[205] <- 6
s <- trim(r)
To change other values to or from NA you can use reclassify. For example, to change NA to 0:
x <- reclassify(r, cbind(NA, 0))

[ subsetting and [<- replacement methods are defined for raster objects so you can simply do r[ r[] == 1 ] <- NA to get rid of the values where 1 is your nodata value (use NAvalue(r) to find out what R considers your nodata value is supposed to be if you aren't sure).
Note you have to use r[] inside the [ subsetting command to access the values. Here is a worked example...
Example
# Make a raster from system file
logo1 <- raster(system.file("external/rlogo.grd", package="raster"))
# Copy to see difference
logo2 <- logo1
# Set all values in logo2 that are > 230 to be NA
logo2[ logo2[] > 230 ] <- NA
# Observe difference
par( mfrow = c( 1,2 ) )
plot(logo1)
plot(logo2)

I have 2 slightly different solutions. The first requires to manually identify the extent but uses predefined functions. The second is more automatic, but a bit more handmade.
Create a reproducible raster for which the first 2 rows are NA
library(raster)
# Create a reproducible example
r1 <- raster(ncol=10, nrow=10)
# The first 2 rows are filled with NAs (no value)
r1[] <- c(rep(NA,20),21:100)
Solution #1
Manually get the extent from the plotted figure using drawExtent()
plot(r1)
r1CropExtent <- drawExtent()
Crop the raster using the extent selected from the figure
r2 <- crop(r1, r1CropExtent)
Plot for comparison
layout(matrix(1:2, nrow=1))
plot(r1)
plot(r2)
Solution #2
It identifies the rows and columns of the raster that only have NA values and remove the ones that are on the margin of the raster. It then calculate the extent using extent().
Transform the raster into a matrix that identifies whether the values are NA or not.
r1NaM <- is.na(as.matrix(r1))
Find the columns and rows that are not completely filled by NAs
colNotNA <- which(colSums(r1NaM) != nrow(r1))
rowNotNA <- which(rowSums(r1NaM) != ncol(r1))
Find the extent of the new raster by using the first ans last columns and rows that are not completely filled by NAs. Use crop() to crop the new raster.
r3Extent <- extent(r1, rowNotNA[1], rowNotNA[length(rowNotNA)],
colNotNA[1], colNotNA[length(colNotNA)])
r3 <- crop(r1, r3Extent)
Plot the rasters for comparison.
layout(matrix(1:2, nrow=1))
plot(r1)
plot(r3)

I have written a small function based on Marie's answer to quickly plot cropped rasters. However, there may be a memory issue if the raster is extremely large, because the computer may not have enough RAM to load the raster as a matrix.
I therefore wrote a memory safe function which will use Marie's method if the computer has enough RAM (because it is the fastest way), or a method based on raster functions if the computer does not have enough RAM (it is slower but memory-safe).
Here is the function:
plotCroppedRaster <- function(x, na.value = NA)
{
if(!is.na(na.value))
{
x[x == na.value] <- NA
}
if(canProcessInMemory(x, n = 2))
{
x.matrix <- is.na(as.matrix(x))
colNotNA <- which(colSums(x.matrix) != nrow(x))
rowNotNA <- which(rowSums(x.matrix) != ncol(x))
croppedExtent <- extent(x,
r1 = rowNotNA[1],
r2 = rowNotNA[length(rowNotNA)],
c1 = colNotNA[1],
c2 = colNotNA[length(colNotNA)])
plot(crop(x, croppedExtent))
} else
{
xNA <- is.na(x)
colNotNA <- which(colSums(xNA) != nrow(x))
rowNotNA <- which(rowSums(xNA) != ncol(x))
croppedExtent <- extent(x,
r1 = rowNotNA[1],
r2 = rowNotNA[length(rowNotNA)],
c1 = colNotNA[1],
c2 = colNotNA[length(colNotNA)])
plot(crop(x, croppedExtent))
}
}
Examples :
library(raster)
r1 <- raster(ncol=10, nrow=10)
r1[] <- c(rep(NA,20),21:100)
# Uncropped
plot(r1)
# Cropped
plotCroppedRaster(r1)
# If the no-data value is different, for example 0
r2 <- raster(ncol=10, nrow=10)
r2[] <- c(rep(0,20),21:100)
# Uncropped
plot(r2)
# Cropped
plotCroppedRaster(r2, na.value = 0)

If you use the rasterVis package (any version after Jun 25, 2021), it will automatically crop the NA values out for terra's SpatRaster
Install rasterVis development version from GitHub
if (!require("librarian")) install.packages("librarian")
librarian::shelf(raster, terra, oscarperpinan/rastervis)
# Create a reproducible example
r1 <- raster(ncol = 10, nrow = 10)
# The first 2 rows are filled with NAs (no value)
r1[] <- c(rep(NA, 20), 21:100)
levelplot() for r1
rasterVis::levelplot(r1,
margin = list(axis = TRUE))
Convert to terra's SpatRaster then plot again using levelplot()
r2 <- rast(r1)
rasterVis::levelplot(r2,
margin = list(axis = TRUE))
Created on 2021-06-26 by the reprex package (v2.0.0)

Related

How to address NA in pixel regression (local computation)with R terra package

For example, I have 5 NDVI tifs and 5 precipitation tifs.I used the code provided by Robert(Extracting p-values from lineair regression on raster image through calc function (in R)) as a reference, but it had the following error.
library(terra)
f1 <- rast(dir("D:/test",
pattern = '.tif$',full.names = T))
f2 <- rast(dir("D:/test1",
pattern = '.tif$',full.names = T))
f <- c(f1,f2)
fun <- function(x) {
if (all(is.na(x))) {
return(c(NA, NA))
}
m <- lm(x[1:5] ~ x[6:10])
summary(m)$coefficients[,4]
}
x1 <- app(f, fun,cores=8)
The error is:
***Error in checkForRemoteErrors(val) :
18 nodes produced errors; first error: 0 (non-NA) cases***
I think it is likely that NA values cause it. And I try to modify it to this from a reference:
fun_linear = function(x){
if(length(na.omit(x))<10) return(c(NA, NA, NA)) #remove the time series that contain NA
lmdt = lm(x[1:5] ~ x[6:10])
a1 = summary(lmdt)
slope = a1$coefficients[2]
intercept = a1$coefficients[1]
pvalue = a1$coefficients[8]
return(c(slope, intercept, pvalue))
}
pixellinear = app(f, fun_linear, cores=8)
It succeeded!
But I still can't figure out why it reports an error in the first method.
1.why if(length(na.omit(x))<10) return(c(NA, NA, NA)) return 3 NA instead of 10 NA(5 NDVI tifs + 5 precipitation tifs)?
2.why
if (all(is.na(x))) {
return(c(NA, NA))
return 2 NA instead of 12 NA?
(In this example,Robert create 12 layers:Extracting p-values from lineair regression on raster image through calc function (in R))
3.In my limited experience with terra::app and raster::calc , I think x is like a time series across z axis (while x axis is longitude, y axis is latitude). As the description of app in terra document(pdf version from cran),the raster data is represented as either a matrix in which each layer is a column, or a vector representing a cell.
So,the if statement in the function is hard to comprehend for me.
x <- c(1,2,NA)
y <- c(1,2,3)
z <- c(NA,NA,NA)
all(is.na(x))
all(is.na(y))
all(is.na(z))
I creat this example, and only all(is.na(z)) returns TRUE.
What if this situation?
library(terra)
r=rast(nrows=10,ncols=10,vals=1:100)
s1 <- lapply(1:10, function(i) setValues(r, rnorm(ncell(r), i, 3)))
files <- rast(s1)
files[[1]][1,1] <- NA
files[[3]][1,1] <- NA
files[[5]][1,1] <- NA
I creat a 10 layers spatraster which its first third fifth layer are set NA of [1,1].Then using terra::app function to do something(eg:trend,regression,correlation),the first thing to do is remove the pixel that contain NA.So my confusion and problems arose.
I am searching for a long time on net. But no use. Please help me to comprehend it better.Thanks in advance!

How to extract cell values from RasterStack and apply them to a new raster

I am trying to take all cell values >=1 from a RasterStack and apply them to a new, blank raster.
I am unsure whether to try this through a for loop or if there is a way to do this via simple raster calculations. So far, I have the following:
dir <- "C://Users//path"
files<- list.files(path = dir,
full.names = TRUE, pattern = ".tif$")
raster_stack <- stack(files)
s <- subset(raster_stack, 38:48)
From here, I am unsure how to proceed. Any insight would be greatly appreciated. Thank you in advance.
library(raster)
#> Loading required package: sp
x1 <- raster(ncol=10, nrow=10, xmn=-10, xmx=10, ymn=-10, ymx=10)
x2 <- raster(ncol=10, nrow=10, xmn=-10, xmx=10, ymn=-10, ymx=10)
x1[,c(1,5,9)] <- 1
x2[c(1,5,9),] <- 1
plot(x1)
plot(x2)
s <- stack(c(x1, x2))
t <- sum(s)
plot(t)
If you wish to add particular layers, then:
t <- s$layer.1 + s$layer.2
Created on 2022-01-25 by the reprex package (v2.0.1)
With these example data
library(terra)
s <- rast(system.file("ex/logo.tif", package="terra"))
s <- round(s/100)
(it is not clear what you want for the cells that are < 1, "blank" has no meaning, but you probably want NA or 0)
You can do this to get a TRUE / FALSE raster (where TRUE is equivalent to 1 and FALSE is equivalent to 0)
x <- s >= 1
plot(x)
or this to get a value (1 in this case) / NA raster
y <- ifel(s >= 1, 1, NA)
You can also use classify for that (it is more efficient)
m <- matrix(c(-Inf, 1, NA,
1, Inf, 1), ncol=3, byrow=TRUE)
z <- classify(s, m, right=FALSE)

terra::distance only reports distance to NA raster cells

I am attempting to use terra::distance as I would use raster::distanceFromPoints. However, terra::distance only reports the distance from the point(s) to NA cells. Is this the intended result? I include sample code with my workaround.
Raster plot with point for distance calculation
Plot of terra::distance for point
Desired output
r <- terra::rast(ncols=10, nrows=10)
valR <- rep(1, length = 100)
valR[c(1,12,23,34,45,56,67,78,89,100)] <- NA
terra::values(r) <- valR
xp <- c(50)
yp <- c(50)
xyp <- cbind(xp, yp)
vecP <- terra::vect(xyp)
terra::plot(r)
terra::plot(vecP, add=T)
rDist <- terra::distance(r, vecP)
terra::plot(rDist) #only NA cells have the distance value
# WORKAROUND
r1 = r*0
r1[is.na(r1)] <- 100
r1[r1<1] <- NA
r1Dist <- terra::distance(r1, vecP)
terra::plot(r1Dist)
####################
# using raster::distanceFromPoints
####################
rR <- raster::raster(ncols=10, nrows=10)
raster::values(rR) <- valR
raster::plot(rR)
rRDist <- raster::distanceFromPoints(rR, xyp)
rRDist <- raster::mask(rRDist, rR)
raster::plot(rRDist)
I tested this using distance to a SpatVector of lines rather than points. That seems to work as expected. I assume that maybe it has not been implemented for points yet.
So, another workaround you could use is to calculate distance to lines of zero length (which are basically equivalent to points):
x1 <- rbind(c(50,50), c(50,50))
colnames(x1) <- c('x', 'y')
lns <- vect(x1, "lines")
rDist <- distance(r, lns)
plot(rDist)

how to remove cluster of pixels using clump function in R

I would like to remove the pixels that form a large cluster and keep only the small cluster to analyse (means get pixels number and locations). First I apply a filter to color in white all pixels that has a value lower to 0.66. Then I use the function clump() in R. The model works but I cannot remove only the large cluster. I do not understand how clump function works.
Initial image:
Results image: plot_r is the image where the pixels with value < 0.66 are changed to 0. plot_rc is the results after clump() function. As observed I cannot remove only the large cluster of pixels (on top of the image plot_r). I changed the value (700 in the code) but not better, how to do?
Here the code:
library(magick)
library(pixmap)
library(raster)
library(igraph)
f <- "https://i.stack.imgur.com/2CjCh.jpg"
x <- image_read(f)
x <- image_convert(x, format = "pgm", depth = 8)
# Save the PGM file
f <- tempfile(fileext = ".pgm")
image_write(x, path = f, format = "pgm")
# Read in the PGM file
picture <- read.pnm(file = f, cellres = 1)
str(picture)
picture#size
mat <- picture#grey
mat[mat<0.66] <- 0; x
##############################################################
##Remove clumps of pixels in R using package Raster and igraph
#Detect clumps (patches) of connected cells
r <-raster(mat)
rc <- clump(r)
#extract IDs of clumps according to some criteria
clump9 = data.frame(freq(rc))
#remove clump observations with frequency smaller/larger than N
clump9 = clump9[ ! clump9$count > 700, ]
# record IDs from clumps which met the criteria in previous step
clump9 = as.vector(clump9$value)
#replace cells with IDs which do not belong to the group of interest
rc[rc != clump9[1] & rc != clump9[2]] = NA
# converting rasterlayer to matrix
n <- as.matrix(r)
m <- as.matrix(rc)
Perhaps something like this
library(raster)
library(igraph)
Short-cutting your approach a bit
f <- "https://i.stack.imgur.com/2CjCh.jpg"
b <- brick(f)
x <- sum(b)
r <- x > 450
rc <- clump(r)
f <- freq(rc, useNA="no")
Replace the clumps with the number of cells they consist of and then set the larger one (here more than 100 cells) to NA, and use the result to mask the original raster
rs <- subs(rc, data.frame(f))
rsc <- reclassify(rs, cbind(100,Inf,NA))
m <- mask(b, rsc)
plotRGB(m)

Eloquently change many raster cell values in R

I have a Landfire Existing Vegetation dataset (http://www.landfire.gov/), which I have projected and cropped to my study site. The raster has ~12,000,000 cells. The cell values represent a particular vegetation type, and the values range from 16:2200. All of those values are not represented in my study area (i.e. values jump from 20 to 1087).
As many of the pixels' values can be lumped together into one classification for my purposes (e.g. different shrub communities into one class), I wanted to reset the values of the raster to simpler values (1:11). This will facilitate easy extraction of data from other rasters by vegetation type and ease of plotting the classification map. I have a working code, but it requires a ton of typing to change all 61 of the values I need to change. Here's what I did:
#===============================
############Example#############
#===============================
library(raster)
r <- raster(nrows=30, ncols=10, xmn=0, xmx=10)
r[] <- rep(10:19, 30)
r.omance <- function(x){
x[x==10] <- 1; x[x==11] <- 1; x[x==12] <- 1
x[x==13] <- 1; x[x==14] <- 1; x[x==15] <- 1
x[x==16] <- 2; x[x==17] <- 2; x[x==18] <- 2
x[x==19] <- 2
return(x)}
reclass <- calc(r, fun = r.omance)
Does anyone know of an easier way to go about this? You can imagine the typing to change 61 values, especially since x[x==16:20] <- 1 was producing an error, so every value had to be typed out separately. As I said, my code works. But I just want to become a better R coder.
Thanks.
You could use %in%:
x %in% c(1,4,3:10)
This:
x[x==10] <- 1; x[x==11] <- 1; x[x==12] <- 1
x[x==13] <- 1; x[x==14] <- 1; x[x==15] <- 1
would reduce to:
x[x %in% 10:15]
I would use the reclassify function
library(raster)
r <- raster(nrows=30, ncols=10, xmn=0, xmx=10)
r[] <- rep(10:19, 30)
rc <- matrix(c(10,15,1,16,19,2), ncol=3, byrow=TRUE)
x <- reclassify(r, rc, right=NA)
You will save yourself a bit of typing using the & logical operator, e.g.
x[ x >= 10 & x <= 15 ] <- 1
x[ x >= 16 & x <= 19 ] <- 2

Resources