I would like to aggregate a population raster by a factor of 1.5, summing the values of the cells.
While aggregate() allows me to sum values when aggregating, its factor parameter accepts only integer values. projectRaster() and resample() allow me to adjust the resolution precisely, but (as far as I know) I am restricted to the prepackaged bilinear-interpolation and nearest-neighbor computation methods.
Is there a way to aggregate a raster by a non-integer factor AND specify the function to use when aggregating?
library(raster)
set.seed(10)
proj <- "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"
r <- raster(resolution = 1, nrow = 100, crs = proj)
r[] <- round(rnorm(ncell(r), 100, 10))
# Doesn't accept non-integer factors
aggregate(r, fact = 1.5, fun = sum)
template <- raster(extent(r), crs = crs(r), resolution = 1.5)
# Correct resolution, but incorrect / impossible values for population
projectRaster(r, to = template, method = "ngb")
projectRaster(r, to = template, method = "bilinear")
Possible workaround
So far, the only method I've been able to come up with is to coerce the template to a SpatialPoints object; extract values from the original, higher-resolution raster; and rasterize() the result:
pts <- as(template, "SpatialPoints")
vals <- extract(r, pts)
pts2 <- SpatialPointsDataFrame(pts, data.frame(vals))
rasterize(pts2, template, field = "vals", fun = sum)
However, if the points are created at the centroids of the raster cells, I'm not sure how they are handled when extracting with a resolution of 1.5x the original raster. My preferred method would be to create a SpatialPolygonsDataFrame and rasterize with fun = mean, but (in my experience) extracting raster values using polygons is very inefficient.
Here a workaround:
#first resample to higher resolution
template <- raster(extent(r), crs = crs(r), resolution = .5)
detailedRas <- projectRaster(r, to = template, method = "ngb")
#then use an integer as a factor (in this case 3)
aggRas <- aggregate(detailedRas, fact=3, fun=sum)
Note however that sum in this case won't return the sum of people who are living in a certain aggregated area.
I.e.: Let's say we have four cells and these values with a resolution of 1 m:
10 15
12 18
after resampling to 0.5 using NN:
10 10 15 15
10 10 15 15
12 12 18 18
12 12 18 18
Then aggregating by sum to 1.5m we get for the first pixel:
10+10+15+10+10+15+12+12+18 = 112
While in fact it should be something like:
10 + 15/2 + 12/2 + 18/4 = 28 (if we assume an equal population distribution over each pixel.)
I would recommend using the focal raster function with a custom / user defined function for summing up population values as you wish.
Or you divide the resampled raster by 4 and then take the sum:
2.5 2.5 3.75 3.75
2.5 2.5 3.75 3.75
3 3 4.5 4.5
3 3 4.5 4.5
2.5 + 2.5 + 3.75 + 2.5 + 2.5 + 3.75 + 3 + 3 + 4.5 = 28
Related
I'm trying to calculate Topographic Position Index (TPI) for 177 points of interest. I have their coordinates stored in a data.frame and elevation in a raster of 7.5 arc sec spatial resolution. And the TPIs I'm calculating is basically: the elevation of point of interest minus the average elevation of its surrounding cells, then the intermediate result is divided by the spatial resolution of the raster. (resolution(dem)) to account for differences in the spatial scale of the DEM and the TPI values.
And since studies usually calculate two TPIs (a small scale + a large scale), I'm also using two windows, where in the small one the surrounding 55 cells are used, and in the large one the surrounding 1010 cells are used.
I am getting this error message
Error in .focal_fun(v, w, as.integer(c(tr$nrows[1] + addr, nc)), runfun, :
Evaluation error: could not find function "resolution"
Code:
library(raster)
library(sp)
library(terra)
library(haven)
# Make a dataframe with longitudes and latitudes
df <- data.frame(lon = coords$longitude, lat = coords$latitude)
# Convert the dataframe to a SpatialPointsDataFrame
coordinates(df) <- c("lon", "lat")
proj4string(df) <- CRS("+proj=longlat +datum=WGS84")
# Extract the elevation values at the points
elev <- extract(dem, df)
# Define the scales for TPI calculation
scales <- c(5, 10)
# Loop over the scales and calculate TPI
tpi_list <- list()
for (scale in scales) {
# Define the size of the moving window
win_size <- scale * 5
# Calculate TPI
tpi <- focal(dem, w = matrix(1, win_size, win_size), fun = function(x) {
(elev - mean(x)) / resolution(dem) * 5
})
# Extract TPI values at the points
tpi_vals <- extract(tpi, df)
# Store the TPI values in a list
tpi_list[[as.character(scale)]] <- tpi_vals
}
#Error in .focal_fun(v, w, as.integer(c(tr$nrows[1] + addr, nc)), runfun, : Evaluation error: could not find function "resolution"
# Combine the TPI values for different scales into a dataframe
tpi_df <- data.frame(tpi_list, row.names = rownames(df))
The error you get is clear:
Evaluation error: could not find function "resolution"
You are using a function resolution, but R does not know about that function. It does not exist in the current workspace. I suppose you were looking for res.
Here is a working example.
library(terra)
dem <- rast(system.file("ex/elev.tif", package="terra"))
df <- data.frame(lon=c(5.9, 6.0, 6.2), lat=c(49.9, 49.6, 49.7))
tpifun <- \(x, f) x[f] - mean(x[-f], na.rm=TRUE)
scales <- c(5, 11)
tpilst <- vector("list", length(scales))
for (i in seq_along(scales)) {
win_size <- scales[i] * 5
mid <- ceiling(win_size^2 / 2)
tpi <- focal(dem, w=win_size, fun=tpifun, f=mid, wopt=list(names="tpi"))
tpilst[[i]] <- data.frame(scale=scales[i], extract(tpi, df))
}
tpi <- do.call(rbind, tpilst)
tpi$tpi <- tpi$tpi / (mean(res(dem)) * 5)
tpi
# scale ID tpi
#1 5 1 -1330.6184
#2 5 2 340.1538
#3 5 3 -135.3077
#4 11 1 -585.7952
#5 11 2 255.3344
#6 11 3 292.0155
A couple of things:
res returns two numbers, the x and y resolution. In the example above I take the mean.
What you were doing in the function supplied to focal is not possible. You supplied a data.frame with elevation data for a few points. How can focal understand what that is all about? Instead, you can compute the TPI for each cell and extract these values.
You cannot use a value of 10 for scale because the weights matrix must have odd size. Otherwise it is not clear how it should be centered on the focal cell.
You say that if scale is 5, the "surrounding 55 cells are used". But that is not the case. The number of surrounding cells used is 624.
scale <- 5
(scale * 5)^2 - 1
#[1] 624
I'd like to draw the raster countour (l) just only for the "target" categorical in x raster without considering NA values. I try to do:
# Packages
library(stars)
library(sf)
#Vectorizing a raster object to an sf object
tif = system.file("tif/L7_ETMs.tif", package = "stars")
x = read_stars(tif)[, 1:50, 1:50, 1:2]
x[[1]] = round(x[[1]]/5)
x[[1]] = ifelse(x[[1]]<10,NA,"target")
str(x[[1]])
#Polygonizing
l = st_contour(x)
plot(l[1])
Error in CPL_write_gdal(mat, file, driver, options, type, dims, from, :
Not compatible with requested type: [type=character; target=double].
But, doesn't work. Please, any help with it?
Thanks in advance,
Alexandre
There are several errors with your script, first, st_contour is indicating that it is not compatible with the character type (referring to the "target" string you set in the raster). Secondly, I would suggest using the breaks argument inside st_contour to set the target value for which you wish to obtain the contours. Additionally, you might want to use x[rule] <- NA to mask certain values in the raster. I made other modifications to your code that might help:
# Let's stay with only the first band, indicated in the final dimension
x = read_stars(tif)[, 1:50, 1:50, 1]
x = round(x/5)
# Calculate the min and max of the raster values
purrr::map(x, min)
# 10
purrr::map(x, max)
# 28
# Mask values lower than 10
# However, this does not make any change, because the lowest value is 10
x[x<10] <- NA
# Take a look at the image
plot(x)
# Obtain the contours
l = st_contour(x,
# Remove NA
na.rm = T,
# Obtain contour lines instead of polygons
contour_lines = TRUE,
# raster values at which to draw contour levels
breaks = 12)
# Plot the contours
plot(l)
I would like to count the number of spatial points (of a SpatialPointsDataFrame object) within a certain distance to every cell of a RasterLayer in R. The resulting value should replace the original value of that particular raster cell.
Here is a reproducible example:
# load library
library(raster)
# generate raster
ras <- raster(nrow=18, ncol=36)
values(ras) <- NA
# create SpatialPointsDataFrame
x <- c(-160,-155,-153,-150, 30, -45, -44, -42, -40, 100, 110, 130)
y <- c(-75,-73,-71,-60, 0, 30, 35, 40, 41, 10, -10, 60)
z <- c(seq(1, 12, 1))
df <- data.frame(x,y,z)
spdf <- SpatialPointsDataFrame(coords=df[,c(1,2)],
data=as.data.frame(df[,3]),
proj4string=CRS("+proj=longlat +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0"))
# visualize
plot(ras)
plot(spdf, add=T)
# loop over all raster cells
for(r in 1:nrow(ras)){
for(c in 1:ncol(ras)){
# duplicate raster for subsequent modification
ras_x <- ras
# define cell for which to count the number of surrounding points
ras_x[r,c] <- nrow(spdf) # some value that is impossible to be true, this is only a temporary placeholder
ras_x[ras_x != nrow(spdf)] <- NA
# convert raster cell to spatial point
spatial_point <- rasterToPoints(ras_x, spatial=T)
# calculate distance around raster cell
ras_dist <- distanceFromPoints(ras_x, spatial_point)
ras_dist <- ras_dist / 1000000 # scale values
# define circular zone by setting distance threshold (raster only with values 1 or NA)
ras_dist[ras_dist > 2] <- NA
ras_dist[ras_dist <= 2] <- 1
# create empty vector to count number of spatial points located within zone around the particular raster cell
empty_vec <- c()
# loop to check which value every point of SpatialPointsDataFrame corresponds to
for (i in 1:nrow(spdf)){
point <- extract(ras_dist, spdf[i,])
empty_vec[i] <- point
}
# sum of resulting vector is the number of points within surrounding zone around predefined raster cell
val <- sum(na.omit(empty_vec))
val
ras[r,c] <- val
# print for progress monitoring
print(paste0("sum of points within radius around cell row ", r, " and column ", c, " is ", val))
print(paste0("finished ", r, " out of ", nrow(ras)))
print(paste0("finished ", c, " out of ", ncol(ras)))
# both plots are just for visualization and progress monitoring
plot(ras)
plot(spdf, add=T)
}
}
plot(ras)
plot(spdf, add=T)
The resulting raster is exactly what I want but my way of checking the underlying raster values for each point of the SpatialPointsDataFrame seems inefficient. My real data consists of a RasterLayer with 2160, 4320, 9331200 (nrow, ncol, ncell) and a SpatialPointsDataFrame with 2664 features.
Is there a way to generate the raster of simply counting how many points are located within a certain distance around every raster cell more efficiently?
If you can work with projected coordinates this can be done fairly easily with the spatstat package.
This requires you to project your points (and grid) with e.g. sf::st_transform() and will not work
on a global scale.
Load spatstat and make 2000 random points to test against:
library(spatstat)
W <- square(1)
set.seed(42)
Y <- runifpoint(2000) # Random points in the unit square
plot(Y, main = "Random points in unit square")
Make 3000x3000 grid of points (9 million points):
xy <- gridcenters(W, 3000, 3000) # Grid of points in the unit square
X <- ppp(xy$x, xy$y, window = W, check = FALSE, checkdup = FALSE)
For each of the 9 million grid points count the number of other points within
radius 0.01 (timed on my resonably fast laptop with 16GB RAM):
system.time(counts <- crosspaircounts(X, Y, r = .01))
#> user system elapsed
#> 1.700 0.228 1.928
Convert to spatstat’s im-format (raster type format – can be converted with maptools) and plot:
rslt <- as.im(data.frame(x = xy$x, y = xy$y, counts))
plot(rslt, main = "Point counts in raster cells")
The points overlayed on the counts shows that we have done the right thing:
plot(rslt, main = "Point counts in raster cells")
plot(Y, add = TRUE, col = rgb(1,1,1,.7), pch = 3)
I’m sure you can also do something elegant and fast with raster, but I’m not the right one to ask there.
I have global data showing the predicted range of a species as points. My aim is to count the number of occurrences in cells of 0.5 degrees resolution.
I figure I can do this by creating a Raster on the same coordinate system...
rast <- raster(xmn= -180, ymn= -90, xmx = 180, ymx = 90, resolution = 0.5,
crs = '+proj=utm +zone=33 +ellps=WGS84 +datum=WGS84 +units=m +no_defs ')
I need to count the number of x/y occurrences in each cell.
Most of the examples I have read use a count value from the data but my data doesn't have a count as each row is species specific. I think I need to create some sort of grid or net of 0.5 degrees and then use that to count the x/y points?
Any help would be greatly appreciated.
Use rasterize(..., fun = "count")
Here's a reproducible example drawn from the docs (?rasterize).
library(raster)
# create a raster
r <- raster(ncols=36, nrows=18)
n <- 1000
# create some points
set.seed(123)
x <- runif(n) * 360 - 180
y <- runif(n) * 180 - 90
xy <- cbind(x, y)
# count the number of points in each raster cell
r0 <- rasterize(xy, r, fun = "count")
# visualize
plot(r0); points(xy, pch = 16, cex=0.5)
To check the resolution of a RasterLayer, use res(raster_object). To modify that resolution use assignment:
x_res <- 100 # resolution in x
y_res <- 100 # resolution in y
res(raster_object) <- c(x_res, y_res) # set the x,y resolution of the raster
Since you want 0.5 degree raster cells, first check what units your crs are in (e.g.- meters), calculate the x and y resolution in those units, and then assign that resolution to the raster. Also be aware the degrees of latitude vary slightly depending on whether you're closer to the equator or poles.
To visualize with ggplot, you can convert a RasterLayer object to a data.frame like so. Although I don't show it, you can add the points as another geom_point or geom_sf layer in the ggplot object.
# convert to data.frame and plot with ggplot
df <- as.data.frame(r0, xy=TRUE)
library(ggplot2)
ggplot(df, aes(x, y, fill = layer)) +
geom_raster() +
scale_fill_viridis_c(na.value = "white") +
labs(fill = "Count") +
theme_minimal()
I have raster and lat/long values, I want to perform focal operation on these points using 3x3 window/kernel. I am new to R.
Here is a workflow to compute the mean of a raster in 3x3 zones centered at defined lat/lon coordinates.
Rasterize the points and dilate the resulting raster to create the 3x3 zones
library(mmand)
library(raster)
# rasterize points based on lat/lon coordinates
z <- rasterize(pts[,2:3], r, field = pts$id)
# dilate z using a 3x3 box
kern <- shapeKernel(c(3,3), type="box")
z[,] <- dilate(as.matrix(z), kern)
plot(z)
Compute the mean of r values in each zone
# raster::zonal function (zonal statistics) is used
# -Inf correspond to NA values and should not be taken into account
# you can change "mean" by the stats you would like to compute
zonal(r, z, fun = "mean")
# zone mean
#[1,] -Inf 5.563607
#[2,] 1 5.000000
#[3,] 2 3.444444
#[4,] 3 5.222222
Sample data
library(raster)
set.seed(1)
# Generate raster of random values
r <- raster(crs = CRS("+proj=robin +datum=WGS84"), resolution = c(10, 10))
r[] <- round(runif(ncell(r), 1, 10))
# Generate data frame with lat/lon coordinates
pts <- data.frame(id = 1:3, lon = c(-100, 40, 120), lat = c(-45, 5, 35))
plot(r)
points(pts$lon, pts$lat, pch = 20, cex = 2)