sf::st_transform() returns empty geometry - r

I have transformed the rnaturalearth countries dataset for orthographic plotting using the procedure outlined here.
However, transforming with st_transform() results in at least one of the geometries (mainland Russia) being empty.
This reads and attempts to transform the problematic polygon:
polygon <- st_as_sf(data.frame(st_as_sfc(readLines("https://pastebin.com/raw/APH15G6X"), crs = 4326)))
st_transform(polygon, "+proj=ortho +lat_0=20 +lon_0=0 +x_0=0 +y_0=0 +a=6371000 +b=6371000 +units=m +no_defs")
Simple feature collection with 1 feature and 0 fields (with 1 geometry empty)
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: NA ymin: NA xmax: NA ymax: NA
CRS: +proj=ortho +lat_0=20 +lon_0=0 +x_0=0 +y_0=0 +a=6371000 +b=6371000 +units=m +no_defs
geometry
1 MULTIPOLYGON EMPTY
The geometry seems to be valid and is expected to be fully visible after orthographic projection, so I'm not sure what else could be going on.

The gist you linked is inspiring, and it has helped me a lot in the past. But it is rooted in an obsolete version of {sf} - and with release 1.0 (which introduces S2 backend for geographic CRS) things got somewhat easier.
I have updated the logic somewhat and published it a while back in this answer https://stackoverflow.com/a/70756593/7756889 - when I amend the code to your definition of Ortho projection it shows mainland Russia as expected (= the geometry is not empty, but cut somewhat to hide the invisible parts in Asia). In addition the answer uses the world dataset from Gisco instead of Natural Earth; it should have negligible impact.
library(sf)
library(giscoR) # for the countries dataset only
library(ggplot2)
# projection string used for the polygons & ocean background
crs_string <- "+proj=ortho +lat_0=20 +lon_0=0 +x_0=0 +y_0=0 +a=6371000 +b=6371000 +units=m +no_defs"
# background for the globe - center buffered by earth radius
ocean <- st_point(x = c(0,0)) %>%
st_buffer(dist = 6371000) %>%
st_sfc(crs = crs_string)
# country polygons, cut to size
world <- gisco_countries %>%
st_intersection(ocean %>% st_transform(4326)) %>% # select visible area only
st_transform(crs = crs_string) # reproject to ortho
# now the action!
ggplot(data = world) +
geom_sf(data = ocean, fill = "aliceblue", color = NA) + # background first
geom_sf(fill = "lightyellow", lwd = .1) + # now land over the oceans
theme_void()

Related

How do I get gCentroid to work at the pole?

I am struggling with gCentroid, because it doesn't seem -- to me -- to give the 'right' answer near a pole of the Earth.
For instance:
library(rgeos)
gCentroid(SpatialPoints(coords=data.frame(longitude=c(-135,-45,45,135),latitute=c(80,80,80,80)),proj4string = CRS('EPSG:4326')))
does not give me the North Pole, it gives:
> SpatialPoints:
> x y
> 1 0 80
> Coordinate Reference System (CRS) arguments: +proj=longlat +datum=WGS84 +no_defs
How do I get gCentroid to work on the surface of the Earth?
The GEOS library is limited to planar geometry operations; this can bring issues in edge cases / the poles being a notorious example.
For the centroid via GEOS to work as intended you need to transform your coordinates from WGS84 to a coordinate reference system appropriate to polar regions; for Arctic regions I suggest EPSG:3995.
library(sp)
library(dplyr)
library(rgeos)
points_sp <- SpatialPoints(coords=data.frame(longitude=c(-135,-45,45,135),latitute=c(80,80,80,80)),proj4string = CRS('EPSG:4326'))
points_updated <- points_sp %>%
spTransform(CRS("EPSG:3995")) # a projected CRS apropriate for Arctic regions
centroid <- gCentroid(points_updated) %>%
spTransform(CRS("EPSG:4326")) # back to safety of WGS84!
centroid # looks better now...
# SpatialPoints:
# x y
# 1 0 90
# Coordinate Reference System (CRS) arguments: +proj=longlat +datum=WGS84 +no_defs
Also note that your workflow - while not wrong in principle - is a bit dated, and the {rgeos} package is approaching its end of life.
It may be good time to give a strong consideration to {sf} package, which is newer, actively developed and can, via interface to s2 library from Google, handle spherical geometry operations.
For an example of {sf} based workflow consider this code; the result (centroid = North Pole) is equivalent to the sp / rgeos one.
library(sf)
points_sf <- points_sp %>% # declared earlier
st_as_sf()
centroid_sf <- points_sf %>%
st_union() %>% # unite featrues / from 4 points >> 1 multipoint
st_centroid()
centroid_sf # the North Pole in a slightly different (sf vs sp) format
# Geometry set for 1 feature
# Geometry type: POINT
# Dimension: XY
# Bounding box: xmin: 0 ymin: 90 xmax: 0 ymax: 90
# Geodetic CRS: WGS 84 (with axis order normalized for visualization)
# POINT (0 90)

R Spatial: Mismatch of latitude / longitude between 2 shapefiles with same CRS

my goal is to count the frequency of extreme weather events within subnational African regions. To do so, I have set up a shapefile containing African provinces, using mostly GADM data and the new geocoded EMDAT GDIS dataset for point-data on weather events.
This is how the region shapefile looks like:
library(sf)
st_geometry(africa_map)
Geometry set for 796 features
Geometry type: GEOMETRY
Dimension: XY
Bounding box: xmin: -25.36042 ymin: -46.96575 xmax: 63.49391 ymax: 37.3452
Geodetic CRS: WGS 84
First 5 geometries:
MULTIPOLYGON (((-4.821226 24.99475, -4.821355 2...
MULTIPOLYGON (((1.853562 35.8605, 1.8424 35.865...
MULTIPOLYGON (((-1.361976 35.3199, -1.358957 35...
MULTIPOLYGON (((2.984874 36.81497, 3.014171 36....
MULTIPOLYGON (((7.262677 37.076, 7.266449 37.07..
And the GDIS dataset after converting longitude and latitude to WGS 84:
gdis_africa_sf <- st_as_sf(x = gdis_africa,
coords = c("longitude", "latitude"),
crs = "+proj=longlat +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0")
st_geometry(gdis_africa_sf)
Geometry set for 5171 features
Geometry type: POINT
Dimension: XY
Bounding box: xmin: -34.04233 ymin: -25.19619 xmax: 37.08849 ymax: 63.4228
CRS: +proj=longlat +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0
First 5 geometries:
POINT (-17.09348 15.66576)
POINT (-16.53153 15.77399)
POINT (-16.20006 15.84419)
POINT (-17.09348 15.66576)
POINT (-16.53153 15.77399)
By now, you can already tell that something's off because the bounding boxes do not correspond at all, even though the projections seem to fit.
st_crs(africa_map)==st_crs(gdis_africa_sf)
[1] TRUE
When plotting the two next to each other, the issue becomes clearer, no matter if I use the new shapefile or just apply longitude and latitude of the data frame.
ggplot() +
geom_sf(data = africa_map) +
geom_sf(data = gdis_africa_sf)
Plot 1
ggplot(data = africa_map) +
geom_sf() +
geom_point(data = gdis_africa, aes(x = longitude, y = latitude),
color = "red",
alpha = 0.3,
size = 2,
shape = 1)
Plot 2
It seems like the weather event coordinates are shifted some thousand kilometers to the North West - but what's the source? And how can I fix the issue and make my two geographical data compatible? Any hints would be much appreciated.
It seems like you've mis-diagnosed the problem, it's not a translation, but a reflection in a diagonal line - you've swapped latitude and longitude in your station points. Try:
gdis_africa_sf <- st_as_sf(x = gdis_africa,
coords = c("latitude", "longitude"),
crs = "+proj=longlat +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0")

Convert SpatialPolygonsDataFrame to projected coordinates using spTransform

Im trying to do a point pattern analysis. To do this I have to convert a SpatialPolygonsDataFrame so it contains projected coordinates instead of curved coordinates. However I keep getting the same error:
Error in as.owin.SpatialPolygons(Netherlands_flat) :
Only projected coordinates may be converted to spatstat class objects
this is the data I used for a border:
download.file("http://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_1_states_provinces.zip",destfile="ne_10m_admin_1_states_provinces.zip")
unzip("ne_10m_admin_1_states_provinces.zip",exdir="NaturalEarth")
border <- shapefile("NaturalEarth/ne_10m_admin_1_states_provinces.shp")
#extract the border of the Netherlands
Netherlands <- border[paste(border$iso_a2)=="NL",]
Im able to plot the plot the Netherlands with the events.
#Plot
plot(babesia$Longitude, babesia$Latitude, pch="+",cex=0.5, xlim=c(3.360782, 7.227095), ylim = c(50.723492, 53.554584))
plot(Netherlands, add = T)
Netherlands with events
But upon using the Spatstat package I keep running into this error.
I tried this code to transform the coordinates
coord_netherlands <- coordinates(Netherlands)
proj4string(Netherlands)
summary(Netherlands)
Netherlands_flat <- spTransform(coord_netherlands, CRS("+proj=longlat +datum=WGS84 +no_defs"))
Netherlands <- as.owin(Netherlands_flat)
Error in as.owin.SpatialPolygons(Netherlands_flat) :
Only projected coordinates may be converted to spatstat class objects
Does anyone know how to solve this? Thank you very much in advance!
You are almost there. You just need to project to another coordinate system when you call spTransform. You currently ask for geographic coordinates on a spheriod model of the earth (long,lat). Instead you should ask for a flat (x,y) coordinate system. This could be utm coordinates in the appropriate zone for the Netherlands or there might well be a better alternative. Your events also need to be transformed from (long,lat) to the same coordinate system. Maybe you can look at the shapefile vignette of the spatstat package for an example. Or look under the spatstat tag on this site. I'm on my phone do I can't give detailed help.
Good luck.
If your events are in a data.frame called xy you can project to UTM zone 31N like this:
xy <- data.frame(lon = 1:2, lat = 1:2)
coordinates(xy) <- ~lon+lat
proj4string(xy) <- CRS("+proj=longlat +datum=WGS84")
xy
# SpatialPoints:
# lon lat
# [1,] 1 1
# [2,] 2 2
# Coordinate Reference System (CRS) arguments: +proj=longlat +datum=WGS84
# +ellps=WGS84 +towgs84=0,0,0
events.utm <- spTransform(xy, CRS("+proj=utm +zone=31N +datum=WGS84"))
events.utm
# SpatialPoints:
# lon lat
# [1,] 277438.3 110598.0
# [2,] 388786.7 221094.9
# Coordinate Reference System (CRS) arguments: +proj=utm +zone=31N
# +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0

Visual bug when changing Robinson projection's central meridian with ggplot2

I am attempting to project a world map in a Robinson projection where the central meridian is different from 0. According to this StackOverFlow thread, it should be an easy thing (albeit the example uses sp).
Here is my reproducible code:
library(sf)
library(ggplot2)
library(rnaturalearth)
world <- ne_countries(scale = 'small', returnclass = 'sf')
# Notice +lon_0=180 instead of 0
world_robinson <- st_transform(world, crs = '+proj=robin +lon_0=180 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs')
ggplot() +
geom_sf(data = world_robinson)
This is the result. Polygons are closing themselves from one side to the other of the projection.
Trying with sp gives the same effect. I also tried with a shapefile including only polygons from coastlines (no political borders) from http://www.naturalearthdata.com/ and the effect is similar.
I tried to run my snippet on two independent R installations on Mac OS X and Ubuntu 18.04.
Polygons that straddle the meridian line get stretched all the way across the map, after the transformation. One way to get around this is to split these polygons down the middle, so that all polygons are either completely to the west or east of the line.
# define a long & slim polygon that overlaps the meridian line & set its CRS to match
# that of world
polygon <- st_polygon(x = list(rbind(c(-0.0001, 90),
c(0, 90),
c(0, -90),
c(-0.0001, -90),
c(-0.0001, 90)))) %>%
st_sfc() %>%
st_set_crs(4326)
# modify world dataset to remove overlapping portions with world's polygons
world2 <- world %>% st_difference(polygon)
# perform transformation on modified version of world dataset
world_robinson <- st_transform(world2,
crs = '+proj=robin +lon_0=180 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs')
# plot
ggplot() +
geom_sf(data = world_robinson)
This is an extension to Z.lin's answer (i.e. use that answer first to calculate world_robinson). However, there is another useful step that can be added. After projecting, regions that were comprised of more than one polygon because they cross from one side of the map to the other in the original projection (see Antarctica, Fiji and Russia) still have this split after reprojection. For example, here is a close up of Antarctica where we can see that it has a boundary on the prime meridian where none should be:
To stitch these regions back togther, we can first find out which polygons are the problems by finding those that cross a the prime meridian:
bbox = st_bbox(world_robinson)
bbox[c(1,3)] = c(-1e-5,1e-5)
polygon2 <- st_as_sfc(bbox)
crosses = world_robinson %>%
st_intersects(polygon2) %>%
sapply(length) %>%
as.logical %>%
which
Now we can select those polygons and set their buffer size to zero:
library(magrittr)
world_robinson[crosses,] %<>%
st_buffer(0)
ggplot(world_robinson) + geom_sf()
As we can see, the map no longer has splits down the prime meridian:

Map raw data and mean data based on the shapefile

sI have the dataset (pts) like this:
x <- seq(-124.25,length=115,by=0.5)
y <- seq(26.25,length=46,by=0.5)
z = 1:5290
longlat <- expand.grid(x = x, y = y) # Create an X,Y grid
pts=data.frame(longlat,z)
names(pts) <- c( "x","y","data")
I knew that I can map the dataframe (pts) into a map by doing:
library(sp)
library(rgdal)
library(raster)
library(maps)
coordinates(pts)=~x+y
proj4string(pts)=CRS("+init=epsg:4326") # set it to long, lat
pts = spTransform(pts,CRS(" +init=epsg:4326 +proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs +towgs84=0,0,0"))
pts <- as(pts, "SpatialPixelsDataFrame")
r = raster(pts)
projection(r) = CRS(" +init=epsg:4326 +proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs +towgs84=0,0,0")
plot(r)
map("usa",add=T)
Now I would like to create a separate map which shows the means of pts across different regions. The shapefile I want to use is from ftp://ftp.epa.gov/wed/ecoregions/cec_na/NA_CEC_Eco_Level2.zip , however, this is a north america map. How can I create the map showing only US based on this north america map? Or is there another better way to do this? thanks so much.
I think that cutting out the non-US data based on the data in the shapefile alone would be hard, since the regions do not correspond to political boundaries - that could be done with rgeos though.
Assuming that "eco" is a SpatialPolygonsDataFrame read in by rgdal::readOGR or maptools::readShapeSpatial, see the available key data for indexing:
sapply(as.data.frame(eco), function(x) if(!is.numeric(x)) unique(x) else NULL)
If you just want to plot it, set up a map with only the US region to start with and then overplot.
library(maps)
map("usa", col = "transparent")
We see that the data is in Lambert Azimuthal Equal Area:
proj4string(eco)
[1] " +proj=laea +lat_0=45 +lon_0=-100 +x_0=0 +y_0=0 +a=6370997 +b=6370997 +units=m +no_defs"
So
require(rgdal)
eco.laea <- spTransform(eco, CRS("+proj=longlat +ellpse=WGS84"))
plot(eco.laea, add = TRUE)
If you want to plot in the original Lambert Azimuthal Equal Area you'll need to get the bounding box in that projection and start the plot based on that, I just used existing data to make an easy example. I'm pretty sure the data could also be cropped with rgeos against another boundary too, but depends what you actually want.

Resources