4
votes

I have two shapefiles: points and polygons. In the code below, I use gCentroid() from the rgeos package to calculate polygon centroids, and then I plot a buffer around the centroids.

I want to create a raster layer from the polygon that represents the distance from each cell to the closest point (red) that falls within the associated polygon buffer around the centroid.

For instance, in polygon unit A I show two hypothetical raster cells and indicate the straight line distance I'm looking to calculate.

enter image description here


Update 1: Create actual buffers based on @JMT2080AD's comment. Replacing the leaflet code.

library(raster)
library(rgdal)
library(rgeos)

url <- "https://www.dropbox.com/s/25n9c5avd92b0zu/example.zip?raw=1"
download.file(url, "example.zip")
unzip("example.zip")

myPolygon <- readOGR("myPolygon.shp")
proj4string(myPolygon) <- CRS("+init=epsg:4326")
myPolygon <- spTransform(myPolygon, CRS("+proj=robin +datum=WGS84"))

myPoints <- readOGR("myPoints.shp")
proj4string(myPoints) <- CRS("+init=epsg:4326")
myPoints <- spTransform(myPoints, CRS("+proj=robin +datum=WGS84"))

centroids <- gCentroid(myPolygon, byid=TRUE)
buffer <- gBuffer(centroids, width=5000, byid=TRUE)

plot(myPolygon, col="green")
plot(buffer, col="blue", add = T)
plot(centroids, pch = 20, col = "white", add = T)
plot(myPoints, pch = 20, col = "red", add = T)

I asked this question on gis.stackexchange, but in the context of QGIS. I'm re-posting the question and a new R MRE here because I think I have a better shot figuring this out in R. I don't know if there is a better way to migrate the question to SO and change the MRE at the same time.

3
Why are you making your buffers in leaflet? Those are not buffers, but large circle symbology. You won't be able to query points with them for a distance calculation. I think that is a critical part of solving this problem.JMT2080AD
That's a good point, @JMT2080AD. I updated the example.Eric Green

3 Answers

4
votes

Here is my solution. I am using sf whenever possible. From my experience sf is not completely compatible with the raster functions yet, so there are a couple of workarounds here that aren't too ugly.

I am using different base data than what you provided.

Base Data

library(sf)
library(raster)
library(magrittr)

set.seed(1)

## We will create your polygons from points using a voronoi diagram
x <- runif(10, 640000, 641000)
y <- runif(10, 5200000, 5201000)

myPolyPoints <- data.frame(id = seq(x), x = x, y = y) %>%
    st_as_sf(coords = c("x", "y"))

## Creating the polygons here
myPolygons <- myPolyPoints$geometry %>%
    st_union %>%
    st_voronoi %>%
    st_collection_extract

myPolygons <- st_sf(data.frame(id = seq(x), geometry = myPolygons)) %>%
    st_intersection(y = st_convex_hull(st_union(myPolyPoints)))

## Creating points to query with buffers then calculate distances to
polygonExt <- extent(myPolygons)
x <- runif(50, polygonExt@xmin, polygonExt@xmax)
y <- runif(50, polygonExt@ymin, polygonExt@ymax)

myPoints <- data.frame(id = seq(x), x = x, y = y) %>%
    st_as_sf(coords = c("x", "y"))

## Set projection info
st_crs(myPoints) <- 26910
st_crs(myPolygons) <- 26910

## View base data
plot(myPolygons$geometry)
plot(myPoints$geometry, add = T, col = 'blue')

## write out data
saveRDS(list(myPolygons = myPolygons,
             myPoints = myPoints),
        "./basedata.rds")

The base data I generated looks like so:

View of base data

Distance Processing

library(sf)
library(raster)
library(magrittr)

## read in basedata
dat <- readRDS("./basedata.rds")

## makeing a grid of points at a resolution using the myPolygons extent
rast <- raster(extent(dat$myPolygons), resolution = 1, vals = 0, crs = st_crs(dat$myPoints))

## define a function that masks out the raster with each polygon, then
## generate a distance grid to each point with the masked raster
rastPolyInterDist <- function(maskPolygon, buffDist){
    maskPolygon <- st_sf(st_sfc(maskPolygon), crs = st_crs(dat$myPoints))
    mRas <- mask(rast, maskPolygon)
    cent <- st_centroid(maskPolygon)
    buff <- st_buffer(cent, buffDist)
    pSel <- st_intersection(dat$myPoints$geometry, buff)

    if(length(pSel) > 0){
        dRas <- distanceFromPoints(mRas, as(pSel, "Spatial"))
        return(dRas + mRas)
    }
    return(mRas)
}

dat$distRasts <- lapply(dat$myPolygons$geometry,
                        rastPolyInterDist,
                        buffDist = 100)

## merge all rasters back into a single raster
outRast <- dat$distRasts[[1]]

mergeFun <- function(mRast){
     outRast <<- merge(outRast, mRast)
}

lapply(dat$distRasts[2:length(dat$distRasts)], mergeFun)

## view output
plot(outRast)
plot(dat$myPoints$geometry, add = T)
dat$myPolygons$geometry %>%
    st_centroid %>%
    st_buffer(dist = 100) %>%
    plot(add = T)

The results can be seen below. You can see that there is a condition handled when the buffered centroid does not intersect any locations found in its polygon.

View of results

Using your base data, I have made the following edits to how your data is read and processed in R.

OP Base Data

library(raster)
library(sf)
library(magrittr)

url <- "https://www.dropbox.com/s/25n9c5avd92b0zu/example.zip?raw=1"
download.file(url, "example.zip")
unzip("example.zip")

myPolygons <- st_read("myPolygon.shp") %>%
    st_transform(st_crs("+proj=robin +datum=WGS84"))

myPoints <- st_read("myPoints.shp") %>%
    st_transform(st_crs("+proj=robin +datum=WGS84"))

centroids <- st_centroid(myPolygons)
buffer <- st_buffer(centroids, 5000)

plot(myPolygons, col="green")
plot(buffer, col="blue", add = T)
plot(centroids, pch = 20, col = "white", add = T)
plot(myPoints, pch = 20, col = "red", add = T)

saveRDS(list(myPoints = myPoints, myPolygons = myPolygons), "op_basedata.rds")

Distance Processing Using OP Data

To use the calculation routine I have suggested, you just need to modify the resolution of the starting raster, and the buffer distance input. Othewise it should behave the same once you read your data into R as I have outlined above.

library(sf)
library(raster)
library(magrittr)

## read in basedata
dat <- readRDS("./op_basedata.rds")

## makeing a grid of points at a resolution using the myPolygons extent
rast <- raster(extent(dat$myPolygons), resolution = 100, vals = 0, crs = st_crs(dat$myPoints))

## define a function that masks out the raster with each polygon, then
## generate a distance grid to each point with the masked raster
rastPolyInterDist <- function(maskPolygon, buffDist){
    maskPolygon <- st_sf(st_sfc(maskPolygon), crs = st_crs(dat$myPoints))
    mRas <- mask(rast, maskPolygon)
    cent <- st_centroid(maskPolygon)
    buff <- st_buffer(cent, buffDist)
    pSel <- st_intersection(dat$myPoints$geometry, buff)

    if(length(pSel) > 0){
        dRas <- distanceFromPoints(mRas, as(pSel, "Spatial"))
        return(dRas + mRas)
    }
    return(mRas)
}

dat$distRasts <- lapply(dat$myPolygons$geometry,
                        rastPolyInterDist,
                        buffDist = 5000)

## merge all rasters back into a single raster
outRast <- dat$distRasts[[1]]

mergeFun <- function(mRast){
     outRast <<- merge(outRast, mRast)
}

lapply(dat$distRasts[2:length(dat$distRasts)], mergeFun)

## view output
plot(outRast)
plot(dat$myPoints$geometry, add = T)
dat$myPolygons$geometry %>%
    st_centroid %>%
    st_buffer(dist = 5000) %>%
    plot(add = T)

View of OP results

4
votes

Here's another solution using sf. I am approaching this in a somewhat different way. I am doing all calculations using vector data representation and only rasterize the results. I am doing this to highlight that it actually matters from where within the raster cells you measure the distance to the points. The code below provides two ways of measuring distance between each raster cell and the target points. a) from the closest vertex (dist_pol), b) from the centroid (dist_ctr). Depending on the target resolution these differences can be huge or negligible. In the case below, with a cell size of about 100m x 100m the differences are, on average, close to the cell-edge length.

library(sf)
# library(mapview)
library(data.table)
library(raster)
# devtools::install_github("ecohealthalliance/fasterize")
library(fasterize)

url <- "https://www.dropbox.com/s/25n9c5avd92b0zu/example.zip?raw=1"
download.file(url, "/home/ede/Desktop/example.zip")
unzip("/home/ede/Desktop/example.zip")

pls = st_read("/home/ede/Desktop/example/myPolygon.shp")
pts = st_read("/home/ede/Desktop/example/myPoints.shp")
buf = st_read("/home/ede/Desktop/example/myBuffer.shp")

### extract target points within buffers
trgt_pts = st_intersection(pts, buf)

# mapview(pls) + buf + trgt_pts

### make grid and extract only those cells that intersect with the polygons in myPolygon.shp
grd_full = st_make_grid(pls, cellsize = 0.001) # 0.001 degrees is about 100 m longitude in Uganda
grd = grd_full[lengths(st_intersects(grd_full, pls)) > 0]

### do the distance calculations (throughing in some data.rable for the performance & just because)
### dist_pol is distance to nearest polygon vertex
### dist_ctr is distance to polygon centroid
grd = as.data.table(grd)
grd[, pol_id := sapply(st_intersects(grd$geometry, pls$geometry), "[", 1)]
grd[, dist_pol := apply(st_distance(geometry, trgt_pts$geometry[trgt_pts$id.1 %in% pol_id]), 1, min), by = "pol_id"]
grd[, dist_ctr := apply(st_distance(st_centroid(geometry), trgt_pts$geometry[trgt_pts$id.1 %in% pol_id]), 1, min), by = "pol_id"]

### convert data.table back to sf object
grd_sf = st_as_sf(grd)

### finally rasterize sf object using fasterize (again, very fast)
rast = raster(grd_sf, res = 0.001)
rst_pol_dist = fasterize(grd_sf, rast, "dist_pol", fun = "first")
rst_ctr_dist = fasterize(grd_sf, rast, "dist_ctr", fun = "first")

# mapview(rst_ctr_dist)

plot(rst_ctr_dist)
plot(stack(rst_pol_dist, rst_ctr_dist)) # there are no differences visually

### check differences between distances from nearest vertex and centroid
summary(grd_sf$dist_pol - grd_sf$dist_ctr)
1
votes

I am working through a possible answer.

# rasterize polygon
  r <- raster(ncol=300, nrow=300)  # not sure what is best
  extent(r) <- extent(myPolygon)
  rp <- rasterize(myPolygon, r)
# select points in buffer
  myPointsInBuffer <- myPoints[!is.na(over(myPoints, buffer)),]
# distance from points
  d <- distanceFromPoints(rp, myPointsInBuffer)
  plot(d)
  plot(myPolygon, col="transparent", add = T)
  plot(buffer, col="transparent", add = T)
  plot(centroids, pch = 20, col = "white", add = T)
  plot(myPoints, pch = 20, col = "red", add = T)

It looks close, but not quite right. I need to get each polygon cell distance to be relative to the closest point inside the buffer which is inside the polygon. As shown in the plot below, there are cells in B that are closer to points in A, but I want to calculate the distance to the closest buffer point in B.

enter image description here