Buffer (geo)spatial points in R with gbuffer - r

I'm trying to buffer the points in my dataset with a radius of 100km. I'm using the function gBuffer from the package rgeos. Here's what I have so far:
head( sampledf )
# postalcode lat lon city province
#1 A0A0A0 47.05564 -53.20198 Gander NL
#4 A0A1C0 47.31741 -52.81218 St. John's NL
coordinates( sampledf ) <- c( "lon", "lat" )
proj4string( sampledf ) <- CRS( "+proj=longlat +datum=WGS84" )
distInMeters <- 1000
pc100km <- gBuffer( sampledf, width=100*distInMeters, byid=TRUE )
I get the following warning:
In gBuffer(sampledf, width = 100 * distInMeters, byid = TRUE) :
Spatial object is not projected; GEOS expects planar coordinates
From what I understand/read, I need to change the Coordinate Reference System (CRS),
in particular the projection, of the dataset from 'geographic' to 'projected'.
I'm not sure sure how to change this. These are all Canadian addresses, I might add.
So NAD83 seems to me a natural projection to choose but I may be wrong.
Any/all help would be greatly appreciated.

With a little bit more digging, it turns out that using a 'projected' coordinates reference system is as simple as
# To get Statscan CRS, see here:
# http://spatialreference.org/ref/epsg/3347/
pc <- spTransform( sampledf, CRS( "+init=epsg:3347" ) )
EPSG3347, used by STATSCAN (adequate for Canadian addresses), uses a lambert conformal conic projection. Note that NAD83 is inappropriate: it is a 'geographic', rather than a 'projected' CRS. To buffer the points
pc100km <- gBuffer( pc, width=100*distm, byid=TRUE )
# Add data, and write to shapefile
pc100km <- SpatialPolygonsDataFrame( pc100km, data=pc100km#data )
writeOGR( pc100km, "pc100km", "pc100km", driver="ESRI Shapefile" )

As #MichaelChirico pointed out, projecting your data to usergeos::gBuffer() should be applied with care. I am not an expert in geodesy, but as far I understood from this ESRI article (Understanding Geodesic Buffering), projecting and then applying gBuffer means actually producing Euclidean buffers as opposed to Geodesic ones. Euclidean buffers are affected by the distortions introduced by projected coordinate systems. These distortions might be something to worry about if your analysis involves wide buffers especially with a wider range of latitudes across big areas (I presume Canada is a good candidate).
I came across the same issue some time ago and I targeted my question towards gis.stackexchange - Euclidean and Geodesic Buffering in R. I think the R code that I proposed then and also the given answer are relevant to this question here as well.
The main idea is to make use of geosphere::destPoint(). For more details and a faster alternative, see the mentioned gis.stackexchange link above. Here is my older attempt applied on your two points:
library(geosphere)
library(sp)
pts <- data.frame(lon = c(-53.20198, -52.81218),
lat = c(47.05564, 47.31741))
pts
#> lon lat
#> 1 -53.20198 47.05564
#> 2 -52.81218 47.31741
make_GeodesicBuffer <- function(pts, width) {
# A) Construct buffers as points at given distance and bearing ---------------
dg <- seq(from = 0, to = 360, by = 5)
# Construct equidistant points defining circle shapes (the "buffer points")
buff.XY <- geosphere::destPoint(p = pts,
b = rep(dg, each = length(pts)),
d = width)
# B) Make SpatialPolygons -------------------------------------------------
# Group (split) "buffer points" by id
buff.XY <- as.data.frame(buff.XY)
id <- rep(1:dim(pts)[1], times = length(dg))
lst <- split(buff.XY, id)
# Make SpatialPolygons out of the list of coordinates
poly <- lapply(lst, sp::Polygon, hole = FALSE)
polys <- lapply(list(poly), sp::Polygons, ID = NA)
spolys <- sp::SpatialPolygons(Srl = polys,
proj4string = CRS("+proj=longlat +ellps=WGS84 +datum=WGS84"))
# Disaggregate (split in unique polygons)
spolys <- sp::disaggregate(spolys)
return(spolys)
}
pts_buf_100km <- make_GeodesicBuffer(as.matrix(pts), width = 100*10^3)
# Make a kml file and check the results on Google Earth
library(plotKML)
#> plotKML version 0.5-9 (2019-01-04)
#> URL: http://plotkml.r-forge.r-project.org/
kml(pts_buf_100km, file.name = "pts_buf_100km.kml")
#> KML file opened for writing...
#> Writing to KML...
#> Closing pts_buf_100km.kml
Created on 2019-02-11 by the reprex package (v0.2.1)
And to toy around, I wrapped the function in a package - geobuffer
Here is an example:
# install.packages("devtools") # if you do not have devtools, then install it
devtools::install_github("valentinitnelav/geobuffer")
library(geobuffer)
pts <- data.frame(lon = c(-53.20198, -52.81218),
lat = c(47.05564, 47.31741))
pts_buf_100km <- geobuffer_pts(xy = pts, dist_m = 100*10^3)
Created on 2019-02-11 by the reprex package (v0.2.1)
Others might come up with better solutions, but for now, this worked well for my problems and hopefully can solve other's problems as well.

Related

How to make an equal area grid in r using the terra package

I want to make an equal area grid (400 square miles per grid cell) over Wisconsin. I am doing this using the code from this link: Creating an equal distance spatial grid in R.
But, this code isn't very flexible, and I also need the grid to be more than just polygons. I need it to be a shapefile. I like the Terra package, but am unable to figure out how to do this in the terra package. The WI shapefile can be downloaded from https://data-wi-dnr.opendata.arcgis.com/datasets/wi-dnr::wisconsin-state-boundary-24k/explore.
My code looks like this:
library(sf)
library(terra)
library(tidyverse)
wi_shape <- vect('C:\\Users\\ruben\\Downloads\\Wisconsin_State_Boundary_24K\\Wisconsin_State_Boundary_24K.shp')
plot(wi_shape)
wi_grid <- st_make_grid(wi_shape, square = T, cellsize = c(20 * 1609.344, 20 * 1609.344))
plot(wi_grid, add = T)
How do I define a grid that is centered on a lat/lon point, where the output is a shapefile that contains attributes for each grid cell? I'm not sure why this is so confusing to me. Thank you.
If your goal is to make a raster based on the extent of another spatial dataset (polygons in this case) you can do
library(terra)
wi <- vect('Wisconsin_State_Boundary_24K.shp')
r <- rast(wi, res=(20 * 1609.344))
You can turn these into polygons and write them to a file with
v <- as.polygons(r)
writeVector(v, "test.shp")
To define a lon/lat center for the grid, you could do the following.
Coordinates of an example lon/lat point projected to the crs of your polygons (Wisconsin Transverse Mercator).
center <- cbind(-90, 45) |> vect(crs="+proj=longlat")
cprj <- crds(project(center, wi))
res <- 20 * 1609.344
Create a single cells around that point and expand the raster:
e <- rep(cprj, each=2) + c(-res, res) / 2
x <- rast(ext(e), crs=crs(wi), ncol=1, nrow=1)
x <- extend(x, wi, snap="out")
The result
plot(as.polygons(x), border="blue")
lines(wi, col="red")
points(cprj, pch="x", cex=2)
I should also mention that you are not using an equal-area coordinate reference system. You can see the variation in cell sizes with
a <- cellSize(x)
But it is very small (less than 1%) relative to the average cell size
diff(minmax(a))
# area
#max 1690441
global(a, mean)
# mean
#area 1036257046
Let's try to tidy this a little bit.
[...] and I also need the grid to be more than just polygons. I need it to be a shapefile.
It's exactly the other way around from my point of view. Once you obtained a proper representation of a polygon, you can export it in whatever format you like (which is supported), e.g. an ESRI Shapefile.
I like the Terra package, but am unable to figure out how to do this in the terra package.
Maybe you did not notice, but actually you are not really using {terra} to create your grid, but {sf} (with SpatVector input from terra, which is accepted here).
library(sf)
#> Linking to GEOS 3.9.3, GDAL 3.5.2, PROJ 8.2.1; sf_use_s2() is TRUE
library(terra)
#> terra 1.6.33
wi_shape <- vect('Wisconsin_State_Boundary_24K.shp')
class(wi_shape)
#> [1] "SpatVector"
#> attr(,"package")
#> [1] "terra"
wi_grid <- st_make_grid(wi_shape, square = T, cellsize = c(20 * 1609.344, 20 * 1609.344))
class(wi_grid)
#> [1] "sfc_POLYGON" "sfc"
It's a minor adjustment, but basically, you can cut this dependency here for now. Also - although I'm not sure is this is the type of flexibility you are looking for - I found it very pleasing to work with {units} recently if you are about to do some conversion stuff like square miles in meters. In the end, once your code is running properly, you can substitute your hardcoded values by variables step by step and wrap a function out of this. This should not be a big deal in the end.
In order to shift your grid to be centered on a specific lat/lon point, you can leverage the offset attribute of st_make_grid(). However, since this only shifts the grid based on the original extent, you might lose coverage with this approach:
library(sf)
#> Linking to GEOS 3.9.3, GDAL 3.5.2, PROJ 8.2.1; sf_use_s2() is TRUE
wi_shape <- read_sf("Wisconsin_State_Boundary_24K.shp")
# area of 400 square miles
A <- units::as_units(400, "mi^2")
# boundary length in square meters to fit the metric projection
b <- sqrt(A)
units(b) <- "m"
# let's assume you wanted your grid to be centered on 45.5° N / 89.5° W
p <- c(-89.5, 45.5) |>
st_point() |>
st_sfc(crs = "epsg:4326") |>
st_transform("epsg:3071") |>
st_coordinates()
p
#> X Y
#> 1 559063.9 558617.2
# create an initial grid for centroid determination
wi_grid <- st_make_grid(wi_shape, cellsize = c(b, b), square = TRUE)
# determine the centroid of your grid created
wi_grid_centroid <- wi_grid |>
st_union() |>
st_centroid() |>
st_coordinates()
wi_grid_centroid
#> X Y
#> 1 536240.6 482603.9
# this should be your vector of displacement, expressed as the difference
delta <- wi_grid_centroid - p
delta
#> X Y
#> 1 -22823.31 -76013.3
# `st_make_grid(offset = ...)` requires lower left corner coordinates (x, y) of the grid,
# so you need some extent information which you can acquire via `st_bbox()`
bbox <- st_bbox(wi_grid)
# compute the adjusted lower left corner
llc_new <- c(st_bbox(wi_grid)["xmin"] + delta[1], st_bbox(wi_grid)["ymin"] + delta[2])
# create your grid with an offset
wi_grid_offset <- st_make_grid(wi_shape, cellsize = c(b, b), square = TRUE, offset = llc_new) |>
st_as_sf()
# append attributes
n <- dim(wi_grid_offset)[1]
wi_grid_offset[["id"]] <- paste0("A", 1:n)
wi_grid_offset[["area"]] <- st_area(wi_grid_offset) |> as.numeric()
# inspect
plot(st_geometry(wi_shape))
plot(st_geometry(wi_grid_offset), border = "red", add = TRUE)
If you wanted to export your polygon features ("grid") in shapefile format, simply make use of st_write(wi_grid_sf, "wi_grid_sf.shp").
PS: For this example you need none of the tidyverse stuff, so there is no need to load it.

adehabitatHR: In proj4string(xy) : CRS object has comment, which is lost in output

I'm trying to create kernel density map from point data (available as .shp and .csv) for 5 different species,at global scale. For some individual, there is only one point locality, but for the others there are few to several points. I will use the output map of KDE to identify the hotspots. I'm using adehabitatHR to produce KDE map as below:
library("sp")
library("rgdal")
library("rgeos")
# load the data layer
data.Points <- readOGR("D:/FGH/merged","data")
#Defining a CRS with sp cause coordinate reference systems are represented differently when PROJ < 6 and PROJ >= 6.
crs_wgs84 <- CRS(SRS_string = "EPSG:4326") # WGS 84 has EPSG code 4326
class(crs_wgs84)
wkt_wgs84 <- wkt(crs_wgs84)
cat(wkt_wgs84)
#Set the CRS of a Spatial* object in sp
data.Points2 <- data.Points
coordinates(data.Points2) <- ~ x + y
slot(data.Points2, "proj4string") <- crs_wgs84
library(raster)
library(adehabitatHR)
# Define the Domain
x <- seq(-179.8300, 179.2461, by=0.083333333) # resolution is the pixel size you desire
y <- seq(-42, 52, by=0.083333333)
xy <- expand.grid(x=x,y=y)
coordinates(xy) <- ~x+y
gridded(xy) <- TRUE
class(xy)
# runs the kernel density estimation
kde.output <- kernelUD(data.Points2, h="href", grid = xy)
However, I got this error when either using .shp or .csv input file:
In proj4string(xy) : CRS object has comment, which is lost in output
I went through https://cran.r-project.org/web/packages/sp/vignettes/CRS_warnings.html and https://inbo.github.io/tutorials/tutorials/spatial_crs_coding/, and did some modifies as above but the warning message is remained!
So, how can I get rid of this warning message? Is there any wrong command used?
Thanks for your help.

sp::over(). Does the dot belong to one of the polygons identified with an OGRGeoJSON file?

I'm trying to get a boolleans vector, where for example, v[i] =1 tells me if an i-th point (latitude longitude pair, present inside a train dataframe) falls within one of the geographical areas identified by an OGRGeoJSON file.
The OGR file is structured roughly like this:
District 1: 24 polygonal
District 2: 4 polygonal
District 3: 27 polygonal
District 4: 18 polygonal
District 5: 34 polygonal
That's what I tried to do.
However, the results obtained are not correct because the polygonal that is generated is a mix of all the various areas present in the OGR file.
library(rgdal)
library(httr)
library(sp)
r <- GET('https://data.cityofnewyork.us/api/geospatial/tqmj-j8zm?method=export&format=GeoJSON')
nyc_neighborhoods <- readOGR(content(r,'text'), 'OGRGeoJSON', verbose = F)
#New York City polygonal
pol_lat <- c(nyc_neighborhoods_df$lat)
pol_long <- c(nyc_neighborhoods_df$long)
xy <- cbind(pol_lat, pol_long)
p = Polygon(xy)
ps = Polygons(list(p),1)
pol = SpatialPolygons(list(ps))
#Points to analyse (pair of coordinates)
ny_lat <- c(train$pickup_latitude, train$dropoff_latitude)
ny_long <- c(train$pickup_longitude, train$dropoff_longitude)
ny_coord <- cbind(ny_lat, ny_long)
pts <- SpatialPoints(ny_coord)
#Query: Does the point to analyze fall in or out NYC?
over(pts, pol, returnList = TRUE)
How can I fix this to get the correct result?
sp is an older package which is being phased out in favor of the newer "Simple Features" sf package. Let me know if you are open to using the pipe operator %>% from the magrittr package, as it works nicely with the sf package (as does dplyr and purrr).
Using sf, you could do:
library(sf)
# Replace this with the path to the geojson file
geojson_path <- "path/to/file.geojson"
boroughs <- sf::st_read(dsn = geojson_path, stringsAsFactors = FALSE)
Now making a very simple spatial point object to stand in for the "trains" data.
# Make test data.frame
test_df <-
data.frame(
# Random test point I chose, a couple of blocks from Central Park
a = "manhattan_point",
y = 40.771959,
x = -73.964128,
stringsAsFactors = FALSE)
# Turn the test_df into a spatial object
test_point <-
sf::st_as_sf(
test_df,
# The coords argument tells the st_as_sf function
# what columns store the longitude and latitude data
# which it uses to associate a spatial point to each
# row in the data.frame
coords = c("x", "y"),
crs = 4326 # WGS84
)
Now we are ready to determine what polygon(s) our point falls in:
# Get the sparse binary predicate. This will give a list with as
# many elements as there are spatial objects in the first argument,
# in this case, test_point, which has 1 element.
# It also has attributes which detail what the relationship is
# (intersection, in our case)
sparse_bin_pred <- sf::st_intersects(test_point, boroughs)
# Output the boro_name that matched. I think the package purrr
# offers some more intuitive ways to do this, but
lapply(
sparse_bin_pred,
function(x) boroughs$boro_name[x]
)
That last part outputs:
[[1]]
[1] "Manhattan"

Create square grids and export them as shapefile or table

I would like to use the Google Earth Engine in order to extract data for certain countries. I need the data in the form of square grids, so I would like to create those square grids for a certain country, add them to the shapefile and then import the shapefile into the Earth Engine. I already found some code to create the square grids (Create a grid inside a shapefile), but now I have two problems.
First, I need to export the square grids so that I can import them to the Earth Engine. I'm very open to alternatives to the shapefile.
Second, the subsequent code works for some countries (like France), but not for others (like Thailand).
library(raster)
shp = getData(country = "FRA", level = 0)
shp = spTransform(shp, CRSobj = "+proj=utm +zone=32 +datum=WGS84 +units=m +no_defs +ellps=WGS84 +towgs84=0,0,0")
plot(shp)
cs = c(10000, 10000)
grdpts = makegrid(shp, cellsize = cs)
spgrd = SpatialPoints(grdpts, proj4string = CRS(proj4string(shp)))
spgrdWithin = SpatialPixels(spgrd[shp,])
plot(spgrdWithin, add = T)
Replacing "FRA" by "THA" in line 2 leads to an error in spTransform.
That fails because you are using utm zone 32. You need to use the zone based on the longitude of the country. You can see them here
You can automate finding a zone with ceiling((longitude+180)/6)
library(raster)
s <- getData(country = "FRA", level = 0)
Get the centroid. In this case you can do
centr <- coordinates(s)
If there are multiple polygons, you can do something like this
centr <- apply(coordinates(s), 2, mean)
Compute the UTM zone. (note that you had 32 for France, which is not good)
zone <- ceiling((centr[1] + 180)/6)
zone
#[1] 31
And then use it like this
crs <- paste0("+proj=utm +datum=WGS84 +unit=m +zone=", zone)
st <- spTransform(s, crs)
For Thailand you would get
s <- getData(country = "THA", level = 0)
centr <- apply(coordinates(s), 2, mean)
zone <- ceiling((centr[1] + 180)/6)
zone
#[1] 47
However, this is not an approach that would work for all countries. UTM zones are 6 degrees wide, and many countries span multiple zones (Russia takes the cake with 28 zones). So depending on your goals, you may want to use another coordinate reference system (crs).
After that, an alternative way to get square polygons is to create a RasterLayer with the extent of s, and a resolution of choice. But I doubt that this is the best way to get data out of GEE. I would suggest uploading the country outline instead.
r <- raster(st, res=10000)
r <- rasterize(st, r, 1)
x <- as(r, "SpatialPolygons")
# write to file
shapefile(x, "test.shp")
# view
plot(x)

Distance between a set of points and a polygon with sf in R

I have a dataframe of points on map and an area of interest described as a polygon of points. I want to calculate the distance between each of the points to the polygon, ideally using the sf package.
library("tidyverse")
library("sf")
# area of interest
area <-
"POLYGON ((121863.900623145 486546.136633659, 121830.369032584 486624.24942906, 121742.202408334 486680.476675484, 121626.493982203 486692.384434804, 121415.359596921 486693.816446951, 121116.219703244 486773.748535465, 120965.69439283 486674.642759986, 121168.798757601 486495.217550029, 121542.879304342 486414.780364836, 121870.487595417 486512.71203006, 121863.900623145 486546.136633659))"
# convert to sf and project on a projected coord system
area <- st_as_sfc(area, crs = 7415L)
# points with long/lat coords
pnts <-
data.frame(
id = 1:3,
long = c(4.85558, 4.89904, 4.91073),
lat = c(52.39707, 52.36612, 52.36255)
)
# convert to sf with the same crs
pnts_sf <- st_as_sf(pnts, crs = 7415L, coords = c("long", "lat"))
# check if crs are equal
all.equal(st_crs(pnts_sf),st_crs(area))
I am wondering why the following approaches do not give me the correct answer.
1.Simply using the st_distance fun-doesn't work, wrong answer
st_distance(pnts_sf, area)
2.In a mutate call - all wrong answers
pnts_sf %>%
mutate(
distance = st_distance(area, by_element = TRUE),
distance2 = st_distance(area, by_element = FALSE),
distance3 = st_distance(geometry, area, by_element = TRUE)
)
However this approach seems to work and gives correct distances.
3.map over the long/lat - works correctly
pnts_geoms <-
map2(
pnts$long,
pnts$lat,
~ st_sfc(st_point(c(.x, .y)) , crs = 4326L)
) %>%
map(st_transform, crs = 7415L)
map_dbl(pnts_geoms, st_distance, y = area)
I'm new to spatial data and I'm trying to learn the sf package so I'm wondering what is going wrong here. As far as i can tell, the first 2 approaches somehow end up considering the points "as a whole" (one of the points is inside the area polygon so i guess that's why one of the wrong answers is 0). The third approach is considering a point at a time which is my intention.
Any ideas how can i get the mutate call to work as well?
I'm on R 3.4.1 with
> packageVersion("dplyr")
[1] ‘0.7.3’
> packageVersion("sf")
[1] ‘0.5.5’
So it turns out that the whole confusion was caused by a small silly oversight on my part. Here's the breakdown:
The points dataframe comes from a different source (!) than the area polygon.
Overseeing this I kept trying to set them to crs 7415 which is a legal but incorrect move and led eventually to the wrong answers.
The right approach is to convert them to sf objects in the crs they originate from, transform them to the one the area object is in and then proceed to compute the distances.
Putting it all together:
# this part was wrong, crs was supposed to be the one they were
# originally coded in
pnts_sf <- st_as_sf(pnts, crs = 4326L, coords = c("long", "lat"))
# then apply the transformation to another crs
pnts_sf <- st_transform(pnts_sf, crs = 7415L)
st_distance(pnts_sf, area)
--------------------------
Units: m
[,1]
[1,] 3998.5701
[2,] 0.0000
[3,] 751.8097

Resources