R - Calculate distance between two points along a polyline - r

In R - I have imported a polyline shapefile that is not straight (represents a winding interstate highway) into the environment. I have read a .csv file that contains points (latitude/longitude in decimal degrees) into the environment. All of those points lie along the polyline.
If I take two random points I can calculate the "as the crow flies" distance between them using great circle distance calculation. But what is needed is distance traveled along the polyline. See reference image:
https://i.stack.imgur.com/zhorb.png
Is anyone aware of an R package that can calculate the distance between two lat/lon points along a polyline?

Package PBSmapping has a calcLength function which directly calculates the length of a polyline or polygon (in what it terms a PolySet). The package can import ESRI shapefiles as well, if your shapefile is in that format.
Example of a simple polyline:
line = read.table(text = "X, Y, POS, PID
37.772, -122.214, 1, 1
21.291, -157.821, 2, 1
-18.142, 178.431, 3, 1
-27.467, 153.027, 4, 1", header = TRUE, sep = ",")
library(PBSmapping)
pline = as.PolySet(line, projection = 1)
calcLength(pline)
PID length
1 1 404.8539
plotLines(pline)

Related

DistanceFromPoints with multiple XY coordinates

I'm trying to use distanceFromPoints function in raster package as:
distanceFromPoints(object,xy,...)
Where, object is raster and xy is matrix of x and y coordinates
Now, if my raster has, for example, 1000 cells and xy represents one point, I get 1000 values representing distances between xy and each raster cell. my problem is when xy has multiple coordinates, e.g., 10 points. the function description indicates that xy can be multiple points but when I run this function with multiple XY points, I still get only 1000 values while I'm expecting 1000 values for each coordinate in XY. How does this work?
Thanks!
using distanceFromPoints on multiple points gives a single value for each raster cell, which is the distance to the nearest point to that cell.
To create raster layers giving the distance to each point separately, you can use apply
a reproducible example:
r = raster(matrix(nrow = 10, ncol = 10))
p = data.frame(x=runif(5), y=runif(5))
dp = apply(p, 1, function(p) distanceFromPoints(r,p))
This gives a list of raster layers, each having the distance to one point
# for example, 1st raster in the list has the distance to the 1st point
plot(dp[[1]])
points(p[1,])
For convenience, you can convert this list into a raster stack
st = stack(dp)
plot(st)
A final word of caution:
It should be noted that the raster objects thus created do not really contain any more information than the list of points from which they are generated. As such, they are a computationally- and memory-expensive way to store that information. I can't easily think of any situation in which this would be a sensible way to solve a specific question. Therefore, it may be worth thinking again about the reasons you want these raster layers, and asking whether there may be a more efficient way to solve you overall problem.

How to draw spokes from the center of a polygon to it's boundary using R

Suppose we have a polygon with five vertices. The two coordinates of the vertices are-
>x=c(1,4,6,3,-2)
>y=c(1,1,5,9,4)
We define the centre of the polygon as the point (mean(x),mean(y)).
I am struggling to draw spokes from the centre of the polygon to the boundary of the polygon such that the spokes creates same angle in the centre (i.e., two neighbouring spokes create equal angle in the centre). I also want to have the all the points on the boundary of the polygon (red circle in the following plot) in orderly manner.
Here is a rough sample plot (convex) which I want to have:
Note: The polygon I am dealing with not necessarily convex.
Sample plot (non-convex)
The output I want: 1) The coordinates of the line (i.e., the intersection points of the line through the origin and boundary segments of the polygon).
2) For each equispaced angle (theta in fig.2) I want a to draw a spoke corresponding to each theta (as in figure 2). Note that, angle lies between 0 to 360 degree.
3) In case of my second polygon (non-convex) where the same line go through two boundary segments (creating three intersecting points), I want to have three coordinates corresponding to the same angle (theta).
Could anyone help me in doing that using R? Thanks in advance.
Here you go. You need the sp and rgeos packages:
spokey <- function(xy,n=20){
xcent = mean(xy[,1])
ycent = mean(xy[,2])
cent = sp::SpatialPoints(cbind(xcent, ycent))
pts = sp::SpatialPoints(xy)
## take the furthest distance from centre to vertex, times two!
r = 2 * max(sp::spDistsN1(pts, cent))
theta=seq(0,2*pi,length=n+1)[-(n+1)]
## construct a big wheel of spoke lines
sl = sp::SpatialLines(
lapply(1:length(theta),function(id){
t = theta[id]
sp::Lines(
list(
sp::Line(
rbind(
c(xcent, ycent),
c(xcent + r * cos(t),ycent + r * sin(t))
)
)
),ID=id)
}))
## construct the polygon as a SpatialPolygons object:
pol = sp::SpatialPolygons(list(sp::Polygons(list(sp::Polygon(rbind(xy,xy[1,]))),ID=1)))
## overlay spokes on polygon as "SpatialLines" so we do line-on-line
## intersect which gets us points
spokes = rgeos::gIntersection(sl, as(pol,"SpatialLines"), byid=TRUE)
spokes
}
It takes a matrix of coordinates where the first point is not the last point:
xy1 = structure(c(4.49425847117117, 4.9161781929536, 7.95751618746858,
7.92235621065338, 9.76825499345149, 9.9616348659351, 8.04541612950659,
7.83445626861537, 6.42805719600729, 0.644241009906543, 2.40223985066665,
1.24196061576498, 2.13854002455263, 7.935927470861, 9.41043173309254,
9.33179150577352, 6.50074332228897, 7.34612576596839, 2.76533252463575,
1.07456763727692, 3.88595576393172, 1.17286792142569, 2.745672467806,
5.20317957152522, 5.81264133324759, 8.21116826647756), .Dim = c(13L,
2L))
and then:
> plot(xy1,asp=1)
> polygon(xy1)
> spokes = spokey(xy1,20) # second arg is number of spokes
> points(spokes,pch=19,col="red")
gets you:
If you don't believe it, draw the segments from the centre to the points :)
segments(mean(xy1[,1]),mean(xy1[,2]), coordinates(spokes)[,1], coordinates(spokes)[,2])
The function coordinates(spokes) will get you a two-column matrix of the spoke points - its returned as a SpatialPoints object at present.
I modified this to handle the convex case illustrated.
You will have to write code that computes the intersection of a spoke from the center to each edge line segment. Not that hard, really, but have never seen it in R. Then you will have to loop over the angles that you are interested in drawing, loop over the segments, find the ones it intersects, sort those values, and then draw the line to the intersection you are interested in.
You would then to the furthest, or some combination (maybe a dotted line between the closest and the furthest).
In pseudo-code:
for each spoke you want to draw
calculate the spoke-line from the center to some point far outside
initialize edge intersection-point list to empty
for each edge-segment
calculate the intersection-point of spoke-line and edge-segment
if the intersection-point exists
add it to the intersection list
now go through the intersections and find the furthest
draw the spoke from the center to the furthest intersection point
continue with the next spoke
This would probably take several hours to research and write, unless you write this kind of graphics code constantly.

R code that evaluates line-of-sight (LOS) between two (lat, lon) points

I'm having trouble figuring out how to calculate line-of-sight (LOS) between two (lat, lon) points, within R code. Any advice on how to approach this problem would be appreciated. I would like to use the R package - raster - for reading in the terrain elevation data. It seems the spgrass package could be leveraged (based on http://grass.osgeo.org/grass70/manuals/r.viewshed.html) but I wanted to avoid loading up a GIS. Thanks.
If you just want to know if point A can see point B then sample a large number of elevations from the line joining A to B to form a terrain profile and then see if the straight line from A to B intersects the polygon formed by that profile. If it doesn't, then A can see B. Coding that is fairly trivial. Conversely you could sample a number of points along the straight line from A to B and see if any of them have an elevation below the terrain elevation.
If you have a large number of points to compute, or if your raster is very detailed, or if you want to compute the entire area visible from a point, then that might take a while to run.
Also, unless your data is over a large part of the earth, convert to a regular metric grid (eg a UTM zone) and assume a flat earth.
I don't know of any existing package having this functionality, but using GRASS really isn't that much of a hassle.
Here's some code that uses raster and plyr:
cansee <- function(r, xy1, xy2, h1=0, h2=0){
### can xy1 see xy2 on DEM r?
### r is a DEM in same x,y, z units
### xy1 and xy2 are 2-length vectors of x,y coords
### h1 and h2 are extra height offsets
### (eg top of mast, observer on a ladder etc)
xyz = rasterprofile(r, xy1, xy2)
np = nrow(xyz)-1
h1 = xyz$z[1] + h1
h2 = xyz$z[np] + h2
hpath = h1 + (0:np)*(h2-h1)/np
return(!any(hpath < xyz$z))
}
viewTo <- function(r, xy, xy2, h1=0, h2=0, progress="none"){
## xy2 is a matrix of x,y coords (not a data frame)
require(plyr)
aaply(xy2, 1, function(d){cansee(r,xy,d,h1,h2)}, .progress=progress)
}
rasterprofile <- function(r, xy1, xy2){
### sample a raster along a straight line between two points
### try to match the sampling size to the raster resolution
dx = sqrt( (xy1[1]-xy2[1])^2 + (xy1[2]-xy2[2])^2 )
nsteps = 1 + round(dx/ min(res(r)))
xc = xy1[1] + (0:nsteps) * (xy2[1]-xy1[1])/nsteps
yc = xy1[2] + (0:nsteps) * (xy2[2]-xy1[2])/nsteps
data.frame(x=xc, y=yc, z=r[cellFromXY(r,cbind(xc,yc))])
}
Hopefully fairly self-explanatory but maybe needs some real documentation. I produced this with it:
which is a map of the points where a 50m high person can see a 2m high tower at the red dot. Yes, I got those numbers wrong when I ran it. It took about 20 mins to run on my 4 year old PC. I suspect GRASS could do this almost instantaneously and more correctly too.

R/GIS: Find orthogonal distance between a location and nearest line

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, ])))

Identify a linear feature on a raster map and return a linear shape object using R

I would like to identify linear features, such as roads and rivers, on raster maps and convert them to a linear spatial object (SpatialLines class) using R.
The raster and sp packages can be used to convert features from rasters to polygon vector objects (SpatialPolygons class). rasterToPolygons() will extract cells of a certain value from a raster and return a polygon object. The product can be simplified using the dissolve=TRUE option, which calls routines in the rgeos package to do this.
This all works just fine, but I would prefer it to be a SpatialLines object. How can I do this?
Consider this example:
## Produce a sinuous linear feature on a raster as an example
library(raster)
r <- raster(nrow=400, ncol=400, xmn=0, ymn=0, xmx=400, ymx=400)
r[] <- NA
x <-seq(1, 100, by=0.01)
r[cellFromRowCol(r, round((sin(0.2*x) + cos(0.06*x)+2)*100), round(x*4))] <- 1
## Quick trick to make it three cells wide
r[edge(r, type="outer")] <- 1
## Plot
plot(r, legend=FALSE, axes=FALSE)
## Convert linear feature to a SpatialPolygons object
library(rgeos)
rPoly <- rasterToPolygons(r, fun=function(x) x==1, dissolve=TRUE)
plot(rPoly)
Would the best approach be to find a centre line through the polygon?
Or is there existing code available to do this?
EDIT: Thanks to #mdsumner for pointing out that this is called skeletonization.
Here's my effort. The plan is:
densify the lines
compute a delaunay triangulation
take the midpoints, and take those points that are in the polygon
build a distance-weighted minimum spanning tree
find its graph diameter path
The densifying code for starters:
densify <- function(xy,n=5){
## densify a 2-col matrix
cbind(dens(xy[,1],n=n),dens(xy[,2],n=n))
}
dens <- function(x,n=5){
## densify a vector
out = rep(NA,1+(length(x)-1)*(n+1))
ss = seq(1,length(out),by=(n+1))
out[ss]=x
for(s in 1:(length(x)-1)){
out[(1+ss[s]):(ss[s+1]-1)]=seq(x[s],x[s+1],len=(n+2))[-c(1,n+2)]
}
out
}
And now the main course:
simplecentre <- function(xyP,dense){
require(deldir)
require(splancs)
require(igraph)
require(rgeos)
### optionally add extra points
if(!missing(dense)){
xy = densify(xyP,dense)
} else {
xy = xyP
}
### compute triangulation
d=deldir(xy[,1],xy[,2])
### find midpoints of triangle sides
mids=cbind((d$delsgs[,'x1']+d$delsgs[,'x2'])/2,
(d$delsgs[,'y1']+d$delsgs[,'y2'])/2)
### get points that are inside the polygon
sr = SpatialPolygons(list(Polygons(list(Polygon(xyP)),ID=1)))
ins = over(SpatialPoints(mids),sr)
### select the points
pts = mids[!is.na(ins),]
dPoly = gDistance(as(sr,"SpatialLines"),SpatialPoints(pts),byid=TRUE)
pts = pts[dPoly > max(dPoly/1.5),]
### now build a minimum spanning tree weighted on the distance
G = graph.adjacency(as.matrix(dist(pts)),weighted=TRUE,mode="upper")
T = minimum.spanning.tree(G,weighted=TRUE)
### get a diameter
path = get.diameter(T)
if(length(path)!=vcount(T)){
stop("Path not linear - try increasing dens parameter")
}
### path should be the sequence of points in order
list(pts=pts[path+1,],tree=T)
}
Instead of the buffering of the earlier version I compute the distance from each midpoint to the line of the polygon, and only take points that are a) inside, and b) further from the edge than 1.5 of the distance of the inside point that is furthest from the edge.
Problems can arise if the polygon kinks back on itself, with long segments, and no densification. In this case the graph is a tree and the code reports it.
As a test, I digitized a line (s, SpatialLines object), buffered it (p), then computed the centreline and superimposed them:
s = capture()
p = gBuffer(s,width=0.2)
plot(p,col="#cdeaff")
plot(s,add=TRUE,lwd=3,col="red")
scp = simplecentre(onering(p))
lines(scp$pts,col="white")
The 'onering' function just gets the coordinates of one ring from a SpatialPolygons thing that should only be one ring:
onering=function(p){p#polygons[[1]]#Polygons[[1]]#coords}
Capture spatial lines features with the 'capture' function:
capture = function(){p=locator(type="l")
SpatialLines(list(Lines(list(Line(cbind(p$x,p$y))),ID=1)))}
Thanks to #klewis at gis.stackexchange.com for linking to this elegant algorithm for finding the centre line (in response to a related question I asked there).
The process requires finding the coordinates on the edge of a polygon describing the linear feature and performing a Voronoi tessellation of those points. The coordinates of the Voronoi tiles that fall within the polygon of the linear feature fall on the centre line. Turn these points into a line.
Voronoi tessellation is done really efficiently in R using the deldir package, and intersections of polygons and points with the rgeos package.
## Find points on boundary of rPoly (see question)
rPolyPts <- coordinates(as(as(rPoly, "SpatialLinesDataFrame"),
"SpatialPointsDataFrame"))
## Perform Voronoi tessellation of those points and extract coordinates of tiles
library(deldir)
rVoronoi <- tile.list(deldir(rPolyPts[, 1], rPolyPts[,2]))
rVoronoiPts <- SpatialPoints(do.call(rbind,
lapply(rVoronoi, function(x) cbind(x$x, x$y))))
## Find the points on the Voronoi tiles that fall inside
## the linear feature polygon
## N.B. That the width parameter may need to be adjusted if coordinate
## system is fractional (i.e. if longlat), but must be negative, and less
## than the dimension of a cell on the original raster.
library(rgeos)
rLinePts <- gIntersection(gBuffer(rPoly, width=-1), rVoronoiPts)
## Create SpatialLines object
rLine <- SpatialLines(list(Lines(Line(rLinePts), ID="1")))
The resulting SpatialLines object:
You can get the boundary of that polygon as SpatialLines by direct coercion:
rLines <- as(rPoly, "SpatialLinesDataFrame")
Summarizing the coordinates down to a single "centre line" would be possible, but nothing immediate that I know of. I think that process is generally called "skeletonization":
http://en.wikipedia.org/wiki/Topological_skeleton
I think ideal solution would be to build such negative buffer which dynamically reach the minimum width and doesn't break when value is too large; keeps continued object and eventually, draws a line if the value is reached. But unfortunately, this may be very compute demanding because this would be done probably in steps and checks if the value for particular point is enough to have a point (of our middle line). Possible it's ne need to have infinitive number of steps, or at least, some parametrized value.
I don't know how to implement this for now.

Resources