When extracting values of a raster along a SpatialLine in R, how to relate these values to the actual distance along this line?
Suppose I want to extract the value of the R logo along the following line:
library(raster)
r <- raster(system.file("external/rlogo.grd", package="raster"))
x=c(5, 95)
y=c(20, 50)
line = SpatialLines(list(Lines(Line(cbind(x,y)), ID="a")))
plot(r)
plot(line, add=TRUE)
I can extract the values and plot them - but how to replace the x values (1:length(vals) below) by the actual distance (starting e.g. at 0 from the left side of the line)?
vals <- extract(r, line)[[1]]
plot(1:length(vals), vals, type='o')
I could combine the extraction of the cells with xyFromCell to get the coordinates of the extracted cells as suggested here, but it is not clear to me how to go further.
I'm not sure what you're exactly asking, but if you looking for distances between the leftmost coordinate of the line segment and the centres of the cells which the line passes through, then you can find the distances like this:
x <- extract(r, l, cellnumbers=TRUE)[[1]]
xy <- xyFromCell(r, x[,1]) # get cell coordinates where the line passes
start <- xy[which.min(xy[,1]),] # leftmost coordinate of the line
d <- apply(xy, 1, function(x, start) sqrt(sum((x-start)^2)), start=start) # find distances between the line segment start and the cells
plot(1:length(d), d, type='o')
Here is a solution (partly on the basis of #jvj's input) through an attempt to compute the orthogonal projections of the cell centres provided by raster::extract on the line and then compute the distances along the line.
(This is an R-beginners script, likely easily improvable, but seems to work (and is of course only for rasters with projection respecting distances))
vals <- extract(r, line, cellnumbers=TRUE)[[1]]
cellsxy <- xyFromCell(r, vals[,1]) # coordinates of intersected cells (likely not ON the line)
linexy = spsample(line, 1000, "regular") # get the line as points
linexy <- matrix(cbind(linexy$x, linexy$y), ncol=2) # easier than Spatial object for later
orthoproj <- c() # to store the orthogonal projections of cells centres on the line
for (i in 1:nrow(cellsxy)) {
xypt = cellsxy[i,]
min.index <- which.min(spDistsN1(linexy, xypt))
orthopt <- linexy[min.index, ] # orthogonal projections = smaller distance
orthoproj <- c(orthoproj, c(orthopt[1], orthopt[2]))
}
orthoproj <- matrix(orthoproj, ncol=2, byrow=T)
orthoproj <- data.frame(x=orthoproj[,1], y=orthoproj[,2])
orthoproj <- orthoproj[order(orthoproj[,1]),] # reorder with increasing distance
orthoproj <- data.frame(x=orthoproj$x, y=orthoproj$y)
start <- linexy[which.min(linexy[,1]),] # leftmost coordinate of the line
dists <- apply(orthoproj, 1,
function(xy, start) sqrt(sum((xy-start)^2)),
start=start) # distances between 'start' and the orthogonal projections
plot(dists, rev(vals[,2]), type='o') # !! beware: order of 'vals' and 'dists'
# depending on the order in which cellnumbers are returned
# in raster::extract and the shape of your line !!
Related
I'm trying to calculate a measure akin to the moment of inertia using a raster layer and I am struggling to figure out how to get the distance of each cell to a patch's centroid and then extracting both that distance and the cell's value.
I want to calculate the moment of inertia (get the squared distance of each cell to its patches centroid, multiply by value of cell, sum these values by patch, and then divide by the sum of all values per patch). I provide a simplified set-up below. The code creates a simple raster layer, patches clusters of cells, and gets their centroids. I know that the function in question to use next is probably terra::distance (maybe in combination with terra::zonal?!) -- how do I calculate the distance by patch?
#lonlat
library(terra)
r <- rast(ncols=36, nrows=18, crs="+proj=longlat +datum=WGS84")
r[498:500] <- 1
r[3:6] <- 1
r[111:116] <- 8
r[388:342] <- 1
r[345:349] <- 3
r_patched <- patches(r, directions = 8, allowGaps = F)
testvector <- terra::as.polygons(r_patched, trunc=T, dissolve = T)
p_centr <- geom(centroids(testvector), df=T)
##next steps
#1. get distance of each cell from patch's centroid
#r <- distance(r)
#2. multiply cell value by squared distance to centroid
I think you need to loop over the patches. Something like this:
p_centr <- centroids(testvector)
v <- rep(NA, length(p_centr))
for (i in 1:length(p_centr)) {
x <- ifel(r_patched == p_centr$patches[i], i, NA)
x <- trim(x)
d <- distance(x, p_centr[i,])
d <- mask(d, x)
# square distance and multiply with cell values
d <- d^2 * crop(r, d)
v[i] <- global(d, "sum", na.rm=TRUE)[[1]]
}
v / sum(v)
#[1] 1.213209e-05 1.324495e-02 9.864759e-01 2.669833e-04
I am attempting to create a disc for each point in a pattern; each disc will have the same radius. Then for each disc, I want to count the number of points falling within the disc. Each pattern has 100-400 points. I have written code to do this, but it is quite slow. The code is below. I cannot provide the shapefile and points as that would be very difficult, but I could create some dummy data if need be.
W <- as.owin(shape)
#Converts created .shp file into a "window"
#in which everything is plotted and calculated
SPDF <- SpatialPointsDataFrame(P[,1:2], P)
#Converts data frame to spatial points data frame
SP <- as(SPDF, "SpatialPoints") #Converts SPDF to spatial points
SP1 <- as.ppp(coordinates(SP), W)
SP2 <- as.ppp(SP1)
attr(SP1, "rejects")
attr(SP2, "rejects")
aw <- area.owin(W) #Area, in pixels squared, of leaf window created earlier
#awm <- aw * (meas)^2 * 100 #Area window in millimeters squared
# Trichome_Density_Count-----------------------------------------------------------------------------------------------
TC <- nrow(P) #Counts number of rows in XY data points file,
#this is number of trichomes from ImageJ
TD <- TC/awm #Trichome density, trichomes per mm^2
#SPDF2 <- as.SpatialPoints.ppp(SP2)
#kg <- knn.graph(SPDF2, k = 1)
#Creates the lines connecting each NND pairwise connection
#dfkg <- data.frame(kg) #Converts lines into a data frame
#dfkgl <- dfkg$length
meanlength <- 78
discstest <- discs(SP2, radii = meanlength,
separate = TRUE, mask = FALSE, trim = FALSE,
delta = NULL, npoly=NULL)
#Function creates discs for each trichome
#Using nearest neighbor lengths as radii
#NEED TO ADD CLIPPING
ratiolist <- c()
for (i in 1:length(discstest)) {
ow2sp <- owin2SP(discstest[[i]])
leafsp <- owin2SP(W)
tic("gIntersection")
intersect <- rgeos::gIntersection(ow2sp, leafsp)
Sys.sleep(1)
toc()
tic("over")
res <- as.data.frame(sp::over(SP, intersect, returnList = FALSE))
Sys.sleep(1)
toc()
res[is.na(res)] <- 0
newowin <- as.owin(intersect)
circarea <- area.owin(newowin)
trichactual <- sum(res)
trichexpect <- (TC / aw) * circarea
ratio <- trichactual / trichexpect
ratiolist[[i]] <- ratio
}
If I understand you correctly you want to loop through each point and check how many points fall within a disc of radius R centered in that point. This is done very efficiently in spatstat with the function closepaircounts:
closepaircounts(SP2, r = meanlength)
This simply returns a vector with the number of points contained in the disc of radius r for each point in SP2.
I have just tried this for 100,000 points where each point on average had almost 3000 other points in the disc around it, and it took 8 seconds on my laptop. If you have many more points or in particular if the disc radius is so big that each disc contains many more points it may become very slow to calculate this.
I'm trying to find the euclidean distance between two points, confined by an irregular polygon. (ie. the distance would have to be calculated as a route through the window given)
Here is an reproducible example:
library(spatstat)
#Simple example of a polygon and points.
ex.poly <- data.frame(x=c(0,5,5,2.5,0), y=c(0,0,5,2.5,5))
points <- data.frame(x=c(0.5, 2.5, 4.5), y=c(4,1,4))
bound <- owin(poly=data.frame(x=ex.poly$x, y=ex.poly$y))
test.ppp <- ppp(x=points$x, y=points$y, window=bound)
pairdist.ppp(test.ppp)#distance between every point
#The distance result from this function between point 1 and point 3, is given as 4.0
However we know just from plotting the points
plot(test.ppp)
that the distance when the route is confined to the polygon should be greater (in this case, 5.00).
Is there another function that I am not aware of in {spatstat} that would do this? Or does anybody have any other suggestions for another package that could do this?
I'm trying to find the distance between two points in a water body, so the irregular polygon in my actual data is more complex.
Any help is greatly appreciated!
Cheers
OK, here's the gdistance-based approach I mentioned in comments yesterday. It's not perfect, since the segments of the paths it computes are all constrained to occur in one of 16 directions on a chessboard (king's moves plus knight's moves). That said, it gets within 2% of the correct values (always slightly overestimating) for each of the three pairwise distances in your example.
library(maptools) ## To convert spatstat objects to sp objects
library(gdistance) ## Loads raster and provides cost-surface functions
## Convert *.ppp points to SpatialPoints object
Pts <- as(test.ppp, "SpatialPoints")
## Convert the lake's boundary to a raster, with values of 1 for
## cells within the lake and values of 0 for cells on land
Poly <- as(bound, "SpatialPolygons") ## 1st to SpatialPolygons-object
R <- raster(extent(Poly), nrow=100, ncol=100) ## 2nd to RasterLayer ...
RR <- rasterize(Poly, R) ## ...
RR[is.na(RR)]<-0 ## Set cells on land to "0"
## gdistance requires that you 1st prepare a sparse "transition matrix"
## whose values give the "conductance" of movement between pairs of
## adjacent and next-to-adjacent cells (when using directions=16)
tr1 <- transition(RR, transitionFunction=mean, directions=16)
tr1 <- geoCorrection(tr1,type="c")
## Compute a matrix of pairwise distances between points
## (These should be 5.00 and 3.605; all are within 2% of actual value).
costDistance(tr1, Pts)
## 1 2
## 2 3.650282
## 3 5.005259 3.650282
## View the selected paths
plot(RR)
plot(Pts, pch=16, col="gold", cex=1.5, add=TRUE)
SL12 <- shortestPath(tr1, Pts[1,], Pts[2,], output="SpatialLines")
SL13 <- shortestPath(tr1, Pts[1,], Pts[3,], output="SpatialLines")
SL23 <- shortestPath(tr1, Pts[2,], Pts[3,], output="SpatialLines")
lapply(list(SL12, SL13, SL23), function(X) plot(X, col="red", add=TRUE, lwd=2))
I'have a SpatialPointsDataFrame load with
pst<-readOGR("/data_spatial/coast/","points_coast")
And I would like to get a SpatialLines in output, I have find somthing
coord<-as.data.frame(coordinates(pst))
Slo1<-Line(coord)
Sli1<-Lines(list(Slo1),ID="coastLine")
coastline <- SpatialLines(list(Sli1))
class(coastline)
it seems to work but when I try plot(coastline) , I have a line that should not be there ...
Some one can help me ? The shapefile is here !
I have looked at the shapefile. There is an id column, but if you plot the data, it seems that the id is not ordered north-south or something. The extra lines are created because the point order is not perfect, connecting points that are next to each other in the table, but far from each other in terms of space. You could try to figure out the correct ordering of the data by calculating distances between points and then ordering on distance.
A workaround is to remove those lines that are longer than a certain distance, e.g. 500 m.. First, find out where distance between consecutive coordinates is larger than this distance: the breaks. Then take a subset of coordinates between two breaks and lastly create Lines for that subset. You end up with a coastline consisting of several (breaks-1) segments and without the erroneous ones.
# read data
library(rgdal)
pst<-readOGR("/data_spatial/coast/","points_coast")
coord<-as.data.frame(coordinates(pst))
colnames(coord) <- c('X','Y')
# determine distance between consective coordinates
linelength = LineLength(as.matrix(coord),sum=F)
# 'id' of long lines, plus first and last item of dataset
breaks = c(1,which(linelength>500),nrow(coord))
# check position of breaks
breaks = c(1,which(linelength>500),nrow(coord))
# plot extent of coords and check breaks
plot(coord,type='n')
points(coord[breaks,], pch=16,cex=1)
# create vector to be filled with lines of each subset
ll <- vector("list", length(breaks)-1)
for (i in 1: (length(breaks)-1)){
subcoord = coord[(breaks[i]+1):(breaks[i+1]),]
# check if subset contains more than 2 coordinates
if (nrow(subcoord) >= 2){
Slo1<-Line(subcoord)
Sli1<-Lines(list(Slo1),ID=paste0('section',i))
ll[[i]] = Sli1
}
}
# remove any invalid lines
nulls = which(unlist(lapply(ll,is.null)))
ll = ll[-nulls]
lin = SpatialLines(ll)
# add result to plot
lines(lin,col=2)
# write shapefile
df = data.frame(row.names=names(lin),id=1:length(names(lin)))
lin2 = SpatialLinesDataFrame(sl=lin, data=df)
proj4string(lin2) <- proj4string(pst)
writeOGR(obj=lin2, layer='coastline', dsn='/data_spatial/coast', driver='ESRI Shapefile')
I am trying to find the orthogonal distance between a set of location coordinates and a set of lines (roads or rivers). The set of points are in the form of latitude/longitude pairs, and the lines are in a shapefile (.shp). Plotting them on a map is not a problem, using either maptools or PBSmapping. But my basic problem is to find the minimum distance one has to travel from a location to reach a road or a river. Is there any way to do this in R?
If I understand correctly, you can do this simply enough with gDistance in the rgeos package.
Read in the lines as SpatialLines/DataFrame and points as SpatialPoints/DataFrame and then loop over each point calculating the distance each time:
require(rgeos)
## untested code
shortest.dists <- numeric(nrow(sp.pts))
for (i in seq_len(nrow(sp.pts)) {
shortest.dists[i] <- gDistance(sp.pts[i,], sp.lns)
}
Here sp.pts is the Spatial points object, and sp.lns is the Spatial lines object.
You must loop so that you only compare a single coordinate in sp.pts with the entirety of all lines geometries in sp.lns, otherwise you get the distance from an aggregate value across all points.
Since your data are in latitude/longitude you should transform both the lines and points to a suitable projection since the gDistance function assumes Cartesian distance.
MORE DISCUSSION AND EXAMPLE (edit)
It would be neat to get the nearest point on the line/s rather than just the distance, but this opens another option which is whether you need the nearest coordinate along a line, or an actual intersection with a line segment that is closer than any existing vertex. If your vertices are dense enough that the difference doesn't matter, then use spDistsN1 in the sp package. You'd have to extract all the coordinates from every line in the set (not hard, but a bit ugly) and then loop over each point of interest calculating the distance to the line vertices - then you can find which is the shortest and select that coordinate from the set of vertices, so you can have the distance and the coordinate easily. There's no need to project either since the function can use ellipsoidal distances with longlat = TRUE argument.
library(maptools)
## simple global data set, which we coerce to Lines
data(wrld_simpl)
wrld_lines <- as(wrld_simpl, "SpatialLinesDataFrame")
## get every coordinate as a simple matrix (scary but quick)
wrld_coords <- do.call("rbind", lapply(wrld_lines#lines, function(x1) do.call("rbind", lapply(x1#Lines, function(x2) x2#coords[-nrow(x2#coords), ]))))
Check it out interactively, you'll have to modify this to save the coords or minimum distances. This will plot up the lines and wait for you to click anywhere in the plot, then it will draw a line from your click to the nearest vertex on a line.
## no out of bounds clicking . . .
par(mar = c(0, 0, 0, 0), xaxs = "i", yaxs = "i")
plot(wrld_lines, asp = "")
n <- 5
for (i in seq_len(n)) {
xy <- matrix(unlist(locator(1)), ncol = 2)
all.dists <- spDistsN1(wrld_coords, xy, longlat = TRUE)
min.index <- which.min(all.dists)
points(xy, pch = "X")
lines(rbind(xy, wrld_coords[min.index, , drop = FALSE]), col = "green", lwd = 2)
}
The geosphere package has the dist2line function that does this for lon/lat data. It can use Spatial* objects or matrices.
line <- rbind(c(-180,-20), c(-150,-10), c(-140,55), c(10, 0), c(-140,-60))
pnts <- rbind(c(-170,0), c(-75,0), c(-70,-10), c(-80,20), c(-100,-50),
c(-100,-60), c(-100,-40), c(-100,-20), c(-100,-10), c(-100,0))
d <- dist2Line(pnts, line)
d
Illustration of the results
plot( makeLine(line), type='l')
points(line)
points(pnts, col='blue', pch=20)
points(d[,2], d[,3], col='red', pch='x')
for (i in 1:nrow(d)) lines(gcIntermediate(pnts[i,], d[i,2:3], 10), lwd=2)
Looks like this can be done in the sf package using the st_distance function.
You pass your two sf objects to the function. Same issue as with the other solutions in that you need to iterate over your points so that the function calculates the distance between every point to every point on the roadways. Then take the minimum of the resulting vector for the shortest distance.
# Solution for one point
min(st_distance(roads_sf, points_sf[1, ]))
# Iterate over all points using sapply
sapply(1:nrow(points_sf), function(x) min(st_distance(roads_sf, points_sf[x, ])))