Create buffer from centroid to 20 m from edges in polyon R - r

I have a shapefile that consist of sea and river polygons within a state boundary (cropped and masked from landcover map (raster) and state boundaries (polygon shapefile)). Within a state boundary, I have multiple polygons (in my case over 1500 different polygons) and I would like to extract the area from the edges and 20 m inward (like an inner buffer). But for my case, all my polygons aren't perfectly shaped, some are rivers, some are just squares (10*10 m from rasterfile) and some are big lakes. My problem is how to create this "inner buffer" for polygons since all polygons, or even not all parts of polygon need to have the buffer (since they are smaller than 40 m, buffer 20m on each side)).
Let me show you my code and how far I have come.
poly <- jvk_18[53,]
cropped <- crop(mark_data, poly, snap = "near")
masked <- mask(cropped, poly)
sjo <- (masked == 61)
sjo <- clump(sjo, directions = 8, gaps = T)
So here is just the extraction, cropping, masking and clumping sea and rivers within a state boundary.
Further I have transformed my data to MULTIPOLYGON
test <- st_as_stars(sjo)
test <- st_as_sf(test, merge = TRUE)
test <- st_cast(test, "MULTIPOLYGON")
After this, I found the st_centroid function to detect the middle point for the polygon. Here my next question is if it is possible to create the buffer from the centroid to 20 m from the edges? Is this possible, and in that case, how?
pol <- test[904, ]
cent <- st_centroid(pol)
Lastly, as mentioned at the beginning, I would like to remove this "inner part", to in the end only get the 20 m from edges and inward.

Instead of working from the center out, couldn't you work from the edges in, by as you said using an inner buffer?
It would be easier to help you with a reproducible example, but given an sf object poly containing your polygons, I would do something like:
core <- st_buffer(poly, -20) %>% st_union() # this will shrink your edges by your desired amount, 20m (and union to tidy up)
diff <- st_difference(poly, core) # difference will leave you with the 20 m ring, removing the core
Of course that will make small polygons disappear... you could probably set a conditional buffer size, but please post an example if you want help with it!

Related

Trying to find neighbouring buffers in R with poly2nb()

I want to get a list of neighbours for each buffer. However the returning nb list is empty.
require(sf)
require(spdep)
us <- tidycensus::county_laea
us <- st_sf(county_laea) %>% st_transform(, crs = 3857)
us$cent <- st_as_sf(st_centroid(us$geometry))
us$buff <- st_as_sf(st_buffer(us$cent, dist = 100000)) # buffer setting is 100km
plot(st_geometry(us$geometry))
plot(st_geometry(us$cent), pch =4, add=T)
plot(st_geometry(us$buff), add=T, border = "red")
nb <- spdep::poly2nb(us$buff, queen = FALSE)
>nb
Neighbour list object:
Number of regions: 3143
Number of nonzero links: 0
Percentage nonzero weights: 0
Average number of links: 0
When I run poly2nb() on us$geometry everything is fine:
us <- tidycensus::county_laea
us <- st_sf(county_laea) %>% st_transform(, crs = 3857)
nb <- spdep::poly2nb(us$geometry, queen = FALSE)
> nb
Neighbour list object:
Number of regions: 3143
Number of nonzero links: 17572
Percentage nonzero weights: 0.1778822
Average number of links: 5.590837
7 regions with no links:
2788 2836 2995 3135 3140 3141 3143
Since you are running a rook (not queen) style neighborhood list on a bunch of circular buffers getting an empty list result is expected behaviour.
This is what your us$buff object looks like when zoomed in:
Now think again about the definition of rook style neighborhood: two polygons are neighbors if & when they share a boundary consisting of more than one point (one point would be sufficient for queen). When in doubt I always think of Colorado and Arizona - they are queen type neighbors, but not rook type ones.
Given that all your buffer objects are circles they can touch their neighbor at most in a single point. Overlap does not make a boundary, and touching in a single point is ruled out by the rook settings.
On the other hand when you look at the original counties / the us$geometry object you will see a plenty of touching lines, and few rare occasions of touching points.
Which is why the queen vs. rook settings rarely makes a noticeable difference for organically grown admin areas, but a big one for grid based ones.

R: How do I loop through spatial points with a specific buffer?

So my problem is quite difficult to describe so I hope I can make my question as clear as possible.
I use the rLiDAR package to load a .las file into R and afterwards convert it into a SpatialPointsDataFrame using the sp package.
So my SpatialPointsDataFrame is quite dense.
Now I want to define a buffer of 0.5 meters and loop (iterate) with him (the buffer) through the points, choosing always the point with the highest Z value within the buffer, as the next point to jump to.This should be repeated until there isn't any point within the buffer with an higher Z value as the current. All values (or perhaps the X and Y values) of this "found" point should then be written into a list/dataframe and the process should be repeated until all such highest points are found.
Thats the code I got so far:
>library(rLiDAR)
>library(sp)
>rLAS<-readLAS("Test.las",short=FALSE)
>PointCloud<- data.frame(rLAS)
>coordinates(PointCloud) <- c("X", "Y")
Well I googled extensively but I could not find any clues how to proceed further...
I dont even know which packages could be of help, I guess perhaps spatstat as my question would probably go into the spatial point pattern analysis.
Does anyone have some ideas how to archive something like that in R? Or is something like that not possible? (Do I perhaps have to skip to python to make something like this work?)
Help would gladly be appreciated.
If you want to get the set of points which are the local maxima within a 0.5m radius circle around each point, this should work. The gist of it is:
Convert the LAS points to a SpatialPointsDataFrame
Create a buffered polygon set with overlapping polygons
Loop through all buffered polygons and find the desired element within the buffer -- in your case, it's the one with the maximum height.
Code below:
library(rLiDAR)
library(sp)
library(rgeos)
rLAS <- readLAS("Test.las",short=FALSE)
PointCloud <- data.frame(rLAS)
coordinates(PointCloud) <- c("X", "Y")
Finish creating the SpatialPointsDataFrame from the LAS source. I'm assuming the field with the point height is PointCloud$value
pointCloudSpdf <- SpatialPointsDataFrame(data=PointCloud,xy)
Use rgeos library for intersection. It's important to have byid=TRUE or the polygons will get merged where they intersect
bufferedPoints <- gBuffer(pointCloudSpdf,width=0.5,byid=TRUE)
# Save our local maxima state (this will be updated)
localMaxes <- rep(FALSE,nrow(PointCloud))
i=0
for (buff in 1:nrow(bufferedPoint#data)){
i <- i+1
bufPolygons <- bufferedPoints#polygons[[i]]
bufSpPolygons <- SpatialPolygons(list(bufPolygons))
bufSpPolygonDf <-patialPolygonsDataFrame(bufSpPolygons,bufferedPoints#data[i,])
ptsInBuffer <- which(!is.na(over(pointCloudSpdf,spPolygonDf)))
# I'm assuming `value` is the field name containing the point height
localMax <- order(pointCloudSpdf#data$value[ptsInBuffer],decreasing=TRUE)[1]
localMaxes[localMax] <- TRUE
}
localMaxPointCloudDf <- pointCloudSpdf#data[localMaxes,]
Now localMaxPointCloudDf should contain the data from the original points if they are a local maximum. Just a warning -- this isn't going to be super fast if you have a lot of points. If that ends up being a concern you may be smarter about pre-filtering your points using a smaller grid and extract from the raster package.
That would look something like this:
Make the cell size small enough so that each 0.5m buffer will intersect at least 4 raster cells -- err on smaller since we are comparing circles to squares.
library(raster)
numRows <- extent(pointCloudSpdf)#ymax-extent(pointCloudSpdf)#ymin/0.2
numCols <- extent(pointCloudSpdf)#xmax-extent(pointCloudSpdf)#xmin/0.2
emptyRaster <- raster(nrow=numRows,ncol=numCols)
rasterize will create a grid with the maximum value of the given field within a cell. Because of the square/circle mismatch this is only a starting point to filter out obvious non-maxima. After this we will have a raster in which all the local maxima are represented by cells. However, we won't know which cells are maxima in the 0.5m radius and we don't know which point in the original feature layer they came from.
r <- rasterize(pointCloudSpdf,emptyRaster,"value",fun="max")
extract will give us raster values (i.e., the highest value for each cell) that each point intersects. Recall from above that all the local maxima will be in this set, although some values will not be 0.5m radius local maxima.
rasterMaxes <- extract(r,pointCloudSpdf)
To match up the original points with the raster maxes, just subtract the raster value at each point from that point's value. If the value is 0, then the values are the same and we have a point with a potential maximum. Note that at this point we are only merging the points back to the raster -- we will have to throw some of these out because they are "under" a 0.5m radius with a higher local max even though they are the max in their 0.2m x 0.2m cell.
potentialMaxima <- which(pointCloudSpdf#data$value-rasterMaxes==0)
Next, just subset the original SpatialPointsDataFrame and we'll do the more exhaustive and accurate iteration over this subset of points since we should have thrown out a bunch of points which could not have been maxima.
potentialMaximaCoords <- coordinates(pointCloudSpdf#coords[potentialMaxima,])
# using the data.frame() constructor because my example has only one column
potentialMaximaDf <- data.frame(pointCloudSpdf#data[potentialMaxima,])
potentialMaximaSpdf <-SpatialPointsDataFrame(potentialMaximaCoords,potentialMaximaDf)
The rest of the algorithm is the same but we are buffering the smaller dataset and iterating over it:
bufferedPoints <- gBuffer(potentialMaximaSpdf, width=0.5, byid=TRUE)
# Save our local maxima state (this will be updated)
localMaxes <- rep(FALSE, nrow(PointCloud))
i=0
for (buff in 1:nrow(bufferedPoint#data)){
i <- i+1
bufPolygons <- bufferedPoints#polygons[[i]]
bufSpPolygons <- SpatialPolygons(list(bufPolygons))
bufSpPolygonDf <-patialPolygonsDataFrame(bufSpPolygons,bufferedPoints#data[i,])
ptsInBuffer <- which(!is.na(over(pointCloudSpdf, spPolygonDf)))
localMax <- order(pointCloudSpdf#data$value[ptsInBuffer], decreasing=TRUE)[1]
localMaxes[localMax] <- TRUE
}
localMaxPointCloudDf <- pointCloudSpdf#data[localMaxes,]

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.

Testing if geometries have at least one boundary point in common regardless of interior points (not like `gTouches`)

the gTouches function in the rgeos package tests whether "geometries have at least one boundary point in common, but no interior points". I am looking for a way to test whether "geometries have at least one boundary point in common" without the criteria related to interior points.
Here is the basic setup: I have two shapefiles that are mostly embedded in each other. I want to find the polygons in the file with the smaller areas that are at the border of the larger areas. Here is a graph to describe what I am trying to do:
plot(map2, col=NA, border='black', lwd=0.4)
plot(map1, col=NA, border='#666666', lwd=0.2, add=TRUE)
The figure shows census blocks in Staten Island, NY. The green highlighting in one of the larger areas illustrates the blocks I want to identify. Only those that share or cross a border of the larger areas (thick lines). Not the blocks that are in the middle of the larger areas. I tried to do this with with gTouches(map2,map1, byid=TRUE) and other function in the rgeos package but without success. gTouches only returns FALSE probably because the criteria is that "geometries have at least one boundary point in common, but no interior points". Basically, I am looking for a function that tests whether "geometries have at least one boundary point in common" regardless of the interior.
A follow-up question is whether I can get the length of the mutual border?
Data: You can download the two maps here and here. Both are rds files so you can open them like this:
library('rgdal')
library('rgeos')
library('sp')
map1 = readRDS('map1.rds')
map2 = readRDS('map2.rds')
You can use a combo of gIntersects() (to find all little polygons that intersect any part of the school district) and gContainsProperly() (to find all little polygons that are fully contained within and not intersecting the boundary of the school district). Then simply combine the two resulting logical matrices to identify the polygons you're after.
## Identify polygons that intersect but aren't fully contained within the
## school district whose polygon is given by SD = map2[13,]
SD <- map2[13,]
ii <- gIntersects(SD, map1, byid=TRUE) &
!gContainsProperly(SD, map1, byid=TRUE)
ii <- apply(ii, 1, any) ## Handy construct if both layers contain >1 polygon
## Plot that area, to show that results are correct
plot(SD, col=NA, border='black') ## Establish plotted area
plot(map1, col=NA, border='#666666', lwd=0.2, add=TRUE)
plot(map1[ii,], col="lightgreen", add=TRUE)
plot(SD, col=NA, border='black', lwd=2, add=TRUE) ## Put SD boundary on top
EDIT :
That's not quite right, however. As can be seen in the map above, many small polygons along the SW and SE interiors of the school district which should have been identified have not been. Outcomes like this occur pretty frequently with rgeos operations, and arise from tiny misregistrations of the pair of layers (or of their intermediate representations by the GEOS engine).
The solution is to use gBuffer() to buffer out one of the layers by a small amount before performing the topological queries. Here, the coordinates are in meters, and a bit of trial and error showed that a 20-meter buffer turns out to be mostly sufficient to fix the problem:
## Expand every polygon in map1 by a 20-meter wide buffer
map1_buff <- gBuffer(map1, byid=TRUE, width=20)
## and then use the buffered version of map1 in the topological queries
ii <- gIntersects(SD, map1_buff, byid=TRUE) &
!gContainsProperly(SD, map1_buff, byid=TRUE)
ii <- apply(ii, 1, any) ## Handy construct if both layers contain >1 polygon
## Plot that area, to show that results are correct
plot(SD, col=NA, border='black') ## Establish plotted area
plot(map1, col=NA, border='#666666', lwd=0.2, add=TRUE)
plot(map1[ii,], col="lightgreen", add=TRUE)
plot(SD, col=NA, border='black', lwd=2, add=TRUE)
This still misses a couple of polygons along the coast, but at some point a complete solution may have to involve getting a pair of maps that match up better in their level of detail. If the buffer size is made much larger, this analysis will start to produce false positives, picking up, for example, a few of the truly interior polygons in the NW corner of the school district.

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