I want to estimate the gradient (slope and aspect) of an undefined surface (i.e., the function is unknown). To test my methods, here is the test data:
require(raster); require(rasterVis)
set.seed(123)
x <- runif(100, min = 0, max = 1)
y <- runif(100, min = 0, max = 1)
e <- 0.5 * rnorm(100)
test <- expand.grid(sort(x),sort(y))
names(test)<-c('X','Y')
z1 <- (5 * test$X^3 + sin(3*pi*test$Y))
realy <- matrix(z1, 100, 100, byrow = F)
# And a few plots for demonstration #
persp(sort(x), sort(y), realy,
xlab = 'X', ylab = "Y", zlab = 'Z',
main = 'Real function (3d)', theta = 30,
phi = 30, ticktype = "simple", cex=1.4)
contour(sort(x), sort(y), realy,
xlab = 'X', ylab = "Y",
main = 'Real function (contours)', cex=1.4)
I convert everything into a raster and plot using rasterVis::vectorplot
. Everything looks fine. The vector field indicates the direction of the highest magnitude of change and the shading is similar to my contour plot...
test.rast <- raster(t(realy), xmn = 0, xmx = 1,
ymn = 0, ymx = 1, crs = CRS("+proj"))
vectorplot(test.rast, par.settings=RdBuTheme, narrow = 100, reverse = T)
However, I need a matrix of slope values. As I understand vectorplot, it uses the raster::terrain function:
terr.mast <- list("slope" = matrix(nrow = 100,
ncol = 100,
terrain(test.rast,
opt = "slope",
unit = "degrees",
reverse = TRUE,
neighbors = 8)@data@values,
byrow = T),
"aspect" = matrix(nrow = 100,
ncol = 100,
terrain(test.rast,
opt = "aspect",
unit = "degrees",
reverse = TRUE,
neighbors = 8)@data@values,
byrow = T))
however, the slope seem really high... ( 90 degrees would be vertical, right?!)
terr.mast$slope[2:6,2:6]
# [,1] [,2] [,3] [,4] [,5]
#[1,] 87.96546 87.96546 87.96546 87.96550 87.96551
#[2,] 84.68628 84.68628 84.68627 84.68702 84.68709
#[3,] 84.41349 84.41350 84.41349 84.41436 84.41444
#[4,] 84.71757 84.71757 84.71756 84.71830 84.71837
#[5,] 79.48740 79.48741 79.48735 79.49315 79.49367
and if I plot the slope and aspect, they don't seem to agree with the vectorplot graphic.
plot(terrain(test.rast, opt = c("slope", "aspect"), unit = "degrees",
reverse = TRUE, neighbors = 8))
My thoughts:
raster::terrain
is using a roving-window method to calculate slope. Perhaps the window is too small... can this be expanded?I build a RasterLayer
with your data using functions from raster
:
library(raster)
library(rasterVis)
test.rast <- raster(ncol=100, nrow=100, xmn = 0, xmx = 1, ymn = 0, ymx = 1)
xy <- xyFromCell(test.rast, 1:ncell(test.rast))
test.rast[] <- 5*xy[,1] + sin(3*pi*xy[,2])
Let's display this object with levelplot
:
levelplot(test.rast)
and the gradient vector field with vectorplot
:
vectorplot(test.rast)
If you only need the slope you get it with terrain
:
slope <- terrain(test.rast, unit='degrees')
levelplot(slope, par.settings=BTCTheme())
However, if I understand you right, you really need the gradient, so you should compute both the slope and the aspect:
sa <- terrain(test.rast, opt=c('slope', 'aspect'))
In order to understand the way vectorplot
is drawing the arrows,
here I show the part of its (modified) code where the horizontal
and vertical components of the arrows are calculated:
dXY <- overlay(sa, fun=function(slope, aspect, ...){
dx <- slope*sin(aspect) ##sin due to the angular definition of aspect
dy <- slope*cos(aspect)
c(dx, dy)
})
Because of the structure of the original RasterLayer
, the
horizontal component is almost constant, so let's draw our
attention on the vertical component. The next code overlays the
arrows of the vector field over this vertical component.
levelplot(dXY, layers=2, par.settings=RdBuTheme()) +
vectorplot(test.rast, region=FALSE)
Finally, if you need the values of the slope and aspect use
getValues
:
saVals <- getValues(sa)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With