Create Distance to Shore (km) Variable from Lat Lon data? - r

I have a data-frame with 3k + data points spread throughout the northern Gulf of Mexico (here I only provide 6). I am trying to create a new variable for is distance to shore (km). I have a shape-file (gulf.shape) which I would like to use but I'm not clear on how.
Here is some data
require(maptools)
require(sp)
library(rgdal)
library(lubridate)
df <- data.frame(Lat = c(26.84853, 28.38329, 28.00364,
29.53840, 29.32030, 26.81622, 25.28146),
Lon = c(-96.55716, -94.29307, -91.21581,
-88.42556, -84.20031, -83.89737, -82.95665))
and I load the shapefile (provided here).
gulf.shape <- "Shape\\stateshigh.shp"
gulf.shape <- maptools::readShapePoly(gulf.shape)
and a quick plot to visualize what I have.
plot(df$Lon, df$Lat,
xlim = c(-97.5, -80.7), ylim = c(25, 30.5),
xlab ="Latitude", ylab = "Longitude",
pch = 20, col="red", cex=1.5)
par(new=T)
sp::plot(gulf.shape, add= T,
xlim = c(-97.5, -80.7), ylim = c(25, 30.5),
xlab ="Latitude", ylab = "Longitude",
col = "gray")
I found a stack overflow post (here), which allowed me to get an answer using the code below. The shape file they use is available here.
require(rgdal) # for readOGR(...); loads package sp as well
require(rgeos) # for gDistance(...)
require(parallel) # for detect cores
require(foreach) # for foreach(...)
require(snow) # for makeCluster(...)
require(doSNOW) # for resisterDoSNOW(...)
wgs.84 <- "+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0"
mollweide <- "+proj=moll +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs"
sp.points <- SpatialPoints(df[,c("Lon","Lat")], proj4string=CRS(wgs.84))
coast <- rgdal::readOGR(dsn=".",layer="ne_10m_coastline",p4s=wgs.84);str(coast)
coast.moll <- spTransform(coast,CRS(mollweide))
point.moll <- spTransform(sp.points,CRS(mollweide))
no_cores <- detectCores()
cl <- makeCluster(no_cores,type="SOCK") # create a 4-processor cluster
registerDoSNOW(cl) # register the cluster
get.dist.parallel <- function(n) {
foreach(i=1:n, .combine=c, .packages="rgeos", .inorder=TRUE,
.export=c("point.moll","coast.moll")) %dopar% gDistance(point.moll[i],coast.moll)
}
df$Dis.to.SHORE <- get.dist.parallel(length(sp.points))
df$Dis.to.SHORE <- df$Dis.to.SHORE/1000
df
plot(coast)
points(sp.points,pch=20,col="red")
However, I do not understand the CRS code used in wgs.84 and mollweide and this makes me uneasy about using the data generated with this code. I would also like to use just the gulf.shape file and not the whole world, since there was some suggestion in the previously mentioned stack overflow post that this would be better.
So my questions are:
Are the values I'm getting for distance to shore reasonably accurate (i.e. within 5 - 10m)?
How can I modify the code to utilize the gulf.shape file rather than the whole world?
Can anyone explain the CRS code or point me toward a good reference?
Note that I use parallel computing to speed things up since I have more than 6 data points in reality.

I'll try to answer your 3rd question.
CRS (Coordinate Reference System) is quite important in mapping as it defines the coordinate system of your points. Here's a helpful overview.
https://www.nceas.ucsb.edu/~frazier/RSpatialGuides/OverviewCoordinateReferenceSystems.pdf
For your particular situation, when you change to a different shapefile, you'll need to (1) find out what the CRS is for your shapefile (gulf.shape). Usually, it's in the .prj file or metadata that comes with the shapefile. (2) pick a CRS that's suitable for your goal. You are calculating distance, so an equidistant projection likely is most helpful to you. (3) transform the original crs to the target crs before calculating the distance.
The code you cited was also doing this. The world shapefile came with wgs84 crs; the chosen target crs was mollweide; and it converted wgs84 to mollweide using the spTransform() function.
On another note, related to your 1st question, the accuracy of your calculation is related to the crs you use, but is also related to the scale of your shapefile, and precision of your points (lat/long).

Related

Issue with making environmental raster layers identical for Maxent

I have nine raster layers (.tif) and each needs to have the same extent, resolution and CRS in order to work in Maxent.
I have tried converting each layer to the same CRS and translating them to .asc format in QGIS.
After that I tried to resample the layers in R to match one of the layers, but this resulted in errors, such as that the extents do not overlap.
My question is how do I match all these layers in order to proceed with Maxent and also to use the 'stack' function in R?
Here is the zip-file with the rasters: https://drive.google.com/file/d/1lle95SPdQ7FyQSbFoFvmAzyuO2HUt7-L/view?usp=sharing
So the initial problem is to set the crs using the 'crs' function from the raster package (I haven't used the new terra package yet). Then you need to reproject into the same crs. The next step is to resample the rasters so they all have the same cell resolution and size. Last you can put them in a stack. I was in a rush, so I didn't comment very well, but let me know if you have questions. The last point is the bedrock file. You'll need to use QGIS or another program to georeference it first. Try to find a map with a known projection that looks similar to it.
library(raster)
ls = list.files(".",pattern ="tif")
ls = ls[-which(ls == "bedrock.tif")]
r = lapply(ls,raster)
names(r) = ls
wgs84 = "+proj=longlat +datum=WGS84 +no_defs"
ETRS = "+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +units=m +no_defs"
crs(r$wc2.1_2.5m_bio_1.tif) = wgs84
crs(r$wc2.1_2.5m_bio_12.tif) = wgs84
crs(r$wc2.1_2.5m_elev.tif) = wgs84
crs(r$SBPC1.tif) = ETRS
crs(r$SBPC2.tif) = ETRS
crs(r$SPPC1.tif) = ETRS
crs(r$SPPC2.tif) = ETRS
crs(r$U2018_CLC2018_V2020_20u1.tif) = ETRS
# aggregate for faster processing -- you'll want to change this, but my machine couldn't process it
ra <- lapply(r,aggregate,fact=10, fun=max)
# not all need to be reprojected - this is me being lazy
rp = lapply(ra,projectRaster, crs = ETRS)
# resample rasters to match
sapply(rp,area)
rpr = lapply(rp,resample, y = rp$SBPC1.tif)
sapply(rpr,area)
rs = stack(rpr)
plot(rs)

Is there a better way for handling SpatialPolygons that cross the antimeridian (date line)?

TL;DR
What is the best way in R to handle SpatialPolygons intersecting/overlapping the anti meridian at +/-180° of latitude and cut them into two sections along that meridian?
Preface
This is going to be a long one, but only because I'm going to include a lot of code and figures for illustration. I'll show you what my goal is and how I normally achieve that and then demonstrate how it all breaks together in a literal edge case. As the title suggests, I already found one possible solution to my problem, so I'll include that too. But it is not 100% clean and I'd like to see if somebody can come up with something more elegant. In any case I think this is an interesting problem, as only a couple of days ago I wouldn't have have suspected in my wildest dreams that this could even be an issue in 2019.
Regular work flow in R
First, create an example data set that works
library(sp)
library(rgdal)
library(rgeos)
library(dismo)
library(maptools) # this is just for plotting a simple world map in the background
data("wrld_simpl")
# create a set of locations
locations <- SpatialPoints(coords=cbind(c(50,0,0,0), c(10, 30, 50, 70)), proj4string = CRS("+proj=longlat +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0"))
plot(wrld_simpl, border="grey50")
points(locations, pch=19, col="blue")
Looks like this:
Then, I use circles() from the dismo package to create circular buffers around those locations. I use this function, because it takes into account that the Earth is not flat:
buffr <- circles(p = locations, d = 1500000, lonlat=TRUE, dissolve=FALSE)
plot(wrld_simpl, border="grey50")
plot(buffr, add=TRUE, border="red", lwd=2)
points(locations, pch=19, col="blue")
That looks like this:
Then, merge the single buffers into one big (multi-) polygon:
buffr <- buffr#polygons # extract the SpatialPolygons object from the "CirclesRange" object
buffr <- gUnaryUnion(buffr) # merge
plot(wrld_simpl, border="grey50")
plot(buffr, add=TRUE, border="red", lwd=2)
points(locations, pch=19, col="blue")
This is exactly what I need:
The problem
Now observe what happens when we introduce locations that so are close to the anti-meridian (+/-180° of longitude) that the buffer has to cross that line:
locations <- SpatialPoints(coords=cbind(c(50,0,0,0, 175, -170), c(10, 30, 50, 70,0,-10)), proj4string = CRS("+proj=longlat +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0"))
buffr <- circles(p = locations, d = 1500000, lonlat=TRUE, dissolve=FALSE)
plot(wrld_simpl, border="grey50")
plot(buffr, add=TRUE, border="red", lwd=2)
points(locations, pch=19, col="blue")
The circles() command does manage to create polygon segments on the other side of the antimeridian (if dissolve=FALSE):
but the polygon crosses the entire globe instead of wrapping around properly (intersecting with 0° instead of 180°). That leads to self-intersections and
buffr <- gUnaryUnion(buffr#polygons)
will fail with
Error in gUnaryUnion(buffr#polygons) : TopologyException: Input
geom 0 is invalid: Self-intersection at or near point
170.08604674698876 12.562175561621103 at 170.08604674698876 12.562175561621103
The quick and slightly dirty solution
First, we need to detect whether a polygon crosses the anti meridian. However, none of them actually intersects +/-180°. Instead, I'm using two pseudo anti meridians that lie close to the real one, but far enough to the east and west to probably intersect the polygons in question. If a polygon intersects both of them, it must also cross the anti meridian.
antimeridian <- SpatialLines(list(Lines(slinelist=list(Line(coords=cbind(c(179,179), c(90,-90)))), ID="1"),
Lines(slinelist=list(Line(coords=cbind(c(-179,-179), c(90,-90)))), ID="2")),
proj4string = CRS("+proj=longlat +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0"))
intrscts <- gIntersects(antimeridian, buffr, byid = TRUE)
any(intrscts[,1] & intrscts[,2])
intrscts <- which(intrscts[,1] & intrscts[,2])
buffr.bad <- buffr[intrscts,]
buffr.good <- buffr[-intrscts,]
plot(wrld_simpl)
plot(buffr.good, border="blue", add=TRUE)
plot(buffr.bad, border="red", add=TRUE)
After having detected and separated the "bad" polygons I simply split them into two separate sections by looking at the longitudinal coordinates. Every coordinate pair that has a negative value there goes into the new western polygon, positive ones into the eastern one. Then I just merge it all back together, do my gUnaryUnion and have pretty much what I need:
buffr.fixed <- buffr.good
for(i in 1:length(buffr.bad)){
thispoly <- buffr.bad[i,] # select first problematic polygon
crds <- thispoly#polygons[[1]]#Polygons[[1]]#coords # extract coordinates
crds.west <- subset(crds, crds[,1] < 0) # western half of the polygon
crds.east<- subset(crds, crds[,1] > 0)
# turn into Spatial*, merge back together, re-add original crs
sppol.east <- SpatialPolygons(list(Polygons(list(Polygon(crds.east)), paste0("east_", i))))
sppol.west <- SpatialPolygons(list(Polygons(list(Polygon(crds.west)), paste0("west_", i))))
sppol <- spRbind(sppol.east, sppol.west)
proj4string(sppol) <- proj4string(thispoly)
buffr.fixed <- spRbind(buffr.fixed, sppol)
}
buffr.final <- gUnaryUnion(buffr.fixed)
plot(wrld_simpl, border="grey50")
points(locations, pch=19, col="blue")
plot(buffr.final, add=TRUE, border="red", lwd=2)
The final outcome:
The actual question
So, this solution works for me for my current use case, but it has some issues:
It will probably break completely as soon as one of the buffers crosses both the anti- and the prime-meridian (which is not so unlikely if the original point locations lie close to the poles).
it is not quite exact, as the two polygon sections are not cut at +/-180° but at the highest negative/positive values of latitude that were present in the original polygon.
I find it hard to believe that there is no "proper" way of doing this.
So the question all this boils down to is: Is there a better way of doing this?
While I was trying to figure this out, I came across the nowrapRecenter() and nowrapSpatialPolygons() functions from the maptools package, which at first sight looked like they do exactly what I want. Upon closer inspection, they are aimed at pretty much the opposite use case (centering a map on the anti meridian and thus cutting polygons along the prime meridian). I played around with them, but failed to make them work for me – in fact, they only managed to make things worse.
Thanks for you attention!
You're right, it's the current year and there is a solution for your problem. The sf-package has the function st_wrap_dateline(), which is exactly what you need.
library(dismo)
library(sf)
locations <- SpatialPoints(coords=cbind(c(50,0,0,0, 175, -170), c(10, 30, 50, 70,0,-10)), proj4string = CRS("+proj=longlat +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0"))
buffr <- circles(p = locations, d = 1500000, lonlat=TRUE, dissolve=FALSE)
buffr2 <- as(buffr#polygons, Class = "sf") %>%
st_wrap_dateline(options = c("WRAPDATELINE=YES")) %>%
st_union()
plot(wrld_simpl, border="grey50")
plot(buffr2, add=TRUE, border="red", lwd=2)
points(locations, pch=19, col="blue")
st_wrap_dateline converts the polygons which cross the international date line, or "antimeridian", into MULTIPOLYGON. And that's about it.
Does that solve your problem? At least it shortens the way quite a bit, to get where you are now. ^^

Create topographic map in R

I am trying to create a script that will generate a 2d topographic or contour map for a given set of coordinates. My goal is something similar to what is produced by
contour(volcano)
but for any location set by the user. This has proved surprisingly challenging! I have tried:
library(elevatr)
library(tidyr)
# Generate a data frame of lat/long coordinates.
ex.df <- data.frame(x=seq(from=-73, to=-71, length.out=10),
y=seq(from=41, to=45, length.out=10))
# Specify projection.
prj_dd <- "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"
# Use elevatr package to get elevation data for each point.
df.sp <- get_elev_point(ex.df, prj = prj_dd, src = "epqs")
# Convert from spatial to regular data frame, remove extra column.
# Use tidyr to convert to lat x lon table with elevation as fill.
# Sorry for the terrible code, I know this is sloppy.
df <- as.data.frame(df.sp)
df$elev_units <- NULL
df.w <- df %>% spread(y, elevation)
df.w <- as.matrix(df.w)
This creates a matrix similar to the volcano dataset but filled with NAs except for the 10 lat/lon pairs with elevation data. contour can handle NAs, but the result of contour(df.w) has only a single tiny line on it. I'm not sure where to go from here. Do I simply need more points? Thanks in advance for any help--I'm pretty new to R and I think I've bitten off more than I can chew with this project.
Sorry for delay in responding. I suppose I need to check SO for elevatr questions!
I would use elevatr::get_elev_raster(), which returns a raster object which can be plotted directly with raster::contour().
Code example below grabs a smaller area and at a pretty coarse resolution. Resultant contour looks decent though.
library(elevatr)
library(raster)
# Generate a data frame of lat/long coordinates.
ex.df <- data.frame(x=seq(from=-73, to=-72.5, length.out=10),
y=seq(from=41, to=41.5, length.out=10))
# Specify projection.
prj_dd <- "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"
# Use elevatr package to get elevation data for each point.
elev <- get_elev_raster(ex.df, prj = prj_dd, z = 10, clip = "bbox")
raster::contour(elev)
If it is a requirement to use graphic::contour(), you'll need to convert the raster object to a matrix first with raster::as.matrix(elev). That flips the coords though and I haven't spent enough time to try and get that part figured out... Hopefully the raster solution works for you.

R - transition function for modelling surface water flow with gdistance

I am trying to model overland (surface) water flow from specified origin points to a single downslope goal point using the gdistance shortestPath function. I need help with defining the appropriate transitionFunction for this, as I need to make sure the least cost path only allows water to flow along the path to elevation cells of equal or lesser value than the previous cell. The transitionFunction in the example below selects the minimum elevation cell but, based on the transitionFunction I have defined, this value may still be greater than the previous cell value.
I realize that, when the above is defined as I want it, the path may terminate before reaching the goal point. This is fine, although I would ideally like to be able to preserve the path from the origin to wherever it terminates if possible.
Also, if anyone knows of a different R package capable of modelling this kind of thing, please let me know.
library(gdistance)
library(raster)
library(elevatr)
library(sp)
#load example DEM raster
data(lake)
elevation <- get_elev_raster(lake, z = 9)
#remove negative elevation values from raster
elevation[elevation < 0] <- NA
#create origin and goal points with same projection as elevation raster
origin <- SpatialPoints(cbind(1790000, 640000), proj4string = CRS("+proj=aea +lat_1=20 +lat_2=60 +lat_0=40 +lon_0=-96 +x_0=0 +y_0=0 +datum=NAD83 +units=m +no_defs +ellps=GRS80 +towgs84=0,0,0"))
goal <- SpatialPoints(cbind(1820000, 540000), proj4string = CRS("+proj=aea +lat_1=20 +lat_2=60 +lat_0=40 +lon_0=-96 +x_0=0 +y_0=0 +datum=NAD83 +units=m +no_defs +ellps=GRS80 +towgs84=0,0,0"))
#create df data and convert to SpatialPointsDataFrame
odf <- data.frame("flowreg" = 1)
gdf <- data.frame("flowreg" = 2)
origindf <- SpatialPointsDataFrame(origin, odf)
goaldf <- SpatialPointsDataFrame(goal, gdf)
trCost1 <- transition(elevation, transitionFunction=function(x) 1/min(x), directions=8)
trCost1gc <- geoCorrection(trCost1, type="c")
plot(raster(trCost1))
sPath1 <- shortestPath(trCost1, origin, goal,
output="SpatialLines")
plot(elevation)
plot(origindf, add = TRUE, col='red', cex = 5)
plot(goaldf, add = TRUE, col='green', cex = 5)
lines(sPath1)
I have found the GRASS GIS (accessed in R using rgrass7) r.drain function OR raster::flowPath achieve what I am trying to do in the above question.

Plotting a shapefile on a raster layer in R

I want to plot a raster layer with points from a shapefile on top.
I have checked previous answers on this, but i still have a problem.
I can plot the point shapefile and the raster layer separately without problem, but not together.
As far as I can see they should be in same projection and location.
require(maptools)
myproj <- "+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0"
shape <- readShapeSpatial("directory/mypoints.shp", proj4string = CRS(myproj))
plot(r <- raster(listVI[200]))
plot(shape)
I found the answer, I will put it here for others who may encounter same problem.
The solution is simply: (as long as raster and shapefile is in same CRS)
plot(r)
plot(shape, add = TRUE)

Resources