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

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:

Related

R: Convert sf polygon defined using lat/long to UTM

I have the following polygon, defined using degrees latitude/longitude:
## Define latitude/longitude
lats <- c(64.25086, 64.24937, 63.24105, 63.22868)
lons <- c(-140.9985, -136.9171, -137.0050, -141.0260)
df <- data.frame(lon = lons, lat = lats)
polygon <- df %>%
## EPSG 3578; Yukon Albers projection
st_as_sf(coords = c('lon', 'lat'), crs = 3578) %>%
summarise(geometry = st_combine(geometry)) %>%
st_cast('POLYGON')
When I plot it on a map using Tmap, it appears in the Pacific Ocean off the coast of British Columbia, rather than in the middle of the Yukon:
library(sf)
library(sp)
library(tmap)
library(dplyr)
library(magrittr)
library(leaflet)
m <- tm_shape(data$study_boundary) + tm_borders(col = 'black',
lwd = 5,
zindex = 1000)
m
I am guessing that the problem is in using lat/long rather than UTMs because I have other polygons defined using UTMs that do appear where they (and the polygon defined above) are supposed to be. I found several other posts going the other way (UTM to lat/long) using spTransform, but I haven't been able to go lat/long to UTM with spTransform. I tried the code below:
poly_utm <- st_transform(polygon, crs = "+proj=utm+7")
But that didn't work either.
Thanks!
This (which I've improved by removing the pipe):
st_as_sf(df, coords = c('lon', 'lat'), crs = 3578)
creates a spatial points data frame using the numbers in the data frame for the coordinates, and the crs code of 3578 as the label for what those numbers represent. It does not change the numbers.
It looks like those numbers are actually lat-long coordinates, which means they are probable crs code 4326, the lat-long system used for GPS, also known as WGS 84. But it might not be. But probably is. Do check. So anyway, you should do:
df_unprojected = st_as_sf(df, coords = c('lon', 'lat'), crs = 4326)
df_projected = st_transform(df_unprojected, 3578)
The st_transform function does the actual change of the coordinate numbers and assigns the new CRS code to the spatial data metadata. That should give you a set of points you can then plot and check they are in the right place before you throw it into summarise and st_cast.

Misalignment between POSGAR94 polygons and WGS84 leaflet map

I need to draw a bunch of polygons from this dataset on a leaflet map on R:
The coordinates are in POSGAR94, but I need them in WGS84 to plot on leaflet (over a OpenStreetMap layer) and to compare them with other data (already on WGS84):
library(rgdal)
library(magrittr)
library(leaflet)
complete_data <- readOGR("data_folder",
GDAL1_integer64_policy = TRUE)
complete_data <- spTransform(bsas,
CRS("+proj=longlat +datum=WGS84 +no_defs"))
I filter the data to keep only a section of it:
int_data <- complete_data[grep("^0604219|^0604201|060421102|060421103", complete_data#data$link), ]
And I plot:
leaflet(int_data, options = leafletOptions(minZoom = 12, maxZoom = 18)) %>%
setMaxBounds(lat1 = -37.1815, lng1 = -58.5581, lat2 = -37.1197, lng2 = -58.4297) %>%
addTiles()%>%
addPolygons(color = "#3498db", weight = 2, smoothFactor = 0.5,
opacity = 0.5, fillOpacity = 0.1,
highlightOptions = highlightOptions(color = "black", weight = 3,
bringToFront = TRUE))
The current result looks like:
All the polygons are offset by a block, its mostly visible on the city perimeter. Here's how that polygon should look like:
My questions are:
Am I making a mistake with the proyection? Or does spTransform introduce an error in the coordinates?
or
Is my code ok, but the data is wrong?
EDIT: This is the output of st_crs before and after the conversion:
BEFORE
Coordinate Reference System:
User input: +proj=tmerc +lat_0=-90 +lon_0=-66 +k=1 +x_0=3500000 +y_0=0 +ellps=WGS84 +units=m +no_defs
wkt:
PROJCS["unnamed",
GEOGCS["WGS 84",
DATUM["unknown",
SPHEROID["WGS84",6378137,298.257223563]],
PRIMEM["Greenwich",0],
UNIT["degree",0.0174532925199433]],
PROJECTION["Transverse_Mercator"],
PARAMETER["latitude_of_origin",-90],
PARAMETER["central_meridian",-66],
PARAMETER["scale_factor",1],
PARAMETER["false_easting",3500000],
PARAMETER["false_northing",0],
UNIT["Meter",1]]
AFTER
Coordinate Reference System:
User input: +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0
wkt:
GEOGCS["WGS 84",
DATUM["WGS_1984",
SPHEROID["WGS 84",6378137,298.257223563,
AUTHORITY["EPSG","7030"]],
AUTHORITY["EPSG","6326"]],
PRIMEM["Greenwich",0,
AUTHORITY["EPSG","8901"]],
UNIT["degree",0.0174532925199433,
AUTHORITY["EPSG","9122"]],
AUTHORITY["EPSG","4326"]]
This looks like an issue with the dataset. I'm looking at it in QGIS, along with some OSM basemap, and around Buenos Aires everything seems to fit the road network nicely:
But going a bit south (e.g. around Mar del Plata coastline) shows an obvious misalignment:
Is my code ok, but the data is wrong?
Since the same problem can be seen using a completely different method, it's safe to say that your code is OK, and working as expected.
We can say that the dataset mismatches when reprojected over a OSM basemap. However, we cannot say that the data is wrong. If we load your dataset along with some reference data from ide.ign.gob.ar (department limits), the data also doesn't match:
In fact, superimposing OSM, IDE-IGN and INDEC data means three different data sources which don't match:
This is normal. There is no easy definition of "right" when it comes to alignment of GIS datasets, and there are a lot of factors in play: collection criteria, orthorectification parameters, datum shifts, projection warping and even continental drift, among others.

How to map raster correct projection in ggplot?

I want to plot USA raster using an Albers coordination,codes as follows:
#both shp_f and ras_f are WGS84 coordination
shp_f <- 'USA.shp'
ras_f <- 'USA.tif'
Albers <- crs('+proj=aea +lat_1=29.5 +lat_2=45.5 +lat_0=23 +lon_0=-96
+x_0=0 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs' )
shp <- readOGR(shp_f)
#convert raster cells as dataframe
ras_df <- ras_f %>% raster %>% rasterToPoints %>% as.data.frame
colnames(ras_df) <- c('x','y','val')
bm <- ggplot()+
geom_tile(data=ras_df,aes(x=x,y=y,fill=val))+
geom_polygon(data=shp,aes(x=long,y=lat,group=group),colour='grey',
fill=NA,linetype='solid',size=0.1)+
# convert plot coordination
ggalt::coord_proj(Albers)
This is a common Albers projection map with arc-shaped latitude lines, and
the xy labels are lon/lat degree. I want plot maps like it ,but it takes too much time to plot because convert each cell coordiantion in ggplot is very slow especially when the high raster resolution. So I consider that change the raster projection first ,then plot it. hence the second section codes as follows:
#convert shape polygon coordination from WGS84 to Albers
shp_albers <- spTransform(shp, Albers)
ras_df <- ras_f %>% raster
#convert the raster coordination from WGS84 to Albers
%>% projectRaster(., res=50000,crs=Albers)
%>% rasterToPoints %>% as.data.frame
colnames(ras_df) <- c('x','y','val')
bm <- ggplot()+
geom_tile(data=ras_df,aes(x=x,y=y,fill=val))+
geom_polygon(data=shp_albers,aes(x=long,y=lat,group=group),colour='grey',
fill=NA,linetype='solid',size=0.1)
But this is a Cartesian graphic that xy is orthogonal (not a albers shape map).
So the question is that:
In general ,how to plot a proper shape map with it's projeciton (not a simple orthogonal graphic)? Meanwhile how to change the xy labels to lon/lat degree and plot arc-shape lon/lat lines (not use orthogonal lines)? The first figure is expected,but it plot too slow.
The raster and shp file is here

Plotting points on a map in the rgdal package with R

Borrowing code from Rob Berry (http://rob-barry.com/2015/06/14/Mapping-in-R/), I make a map of NY city. I have many lat long points I wish to plot on the map. The problem is that plotting a map like this results in plot area way outside of reasonable lat long ranges, so I assume there must either be a way to convert my points to the map scale, or rescale the map so the plot space can lay down lat lon points with the points() function.
Here is the code from Rob Berry:
download.file(destfile = "nypp_15b.zip")
unzip(zipfile = "nypp_15b.zip")
library("rgdal")
nypp <- readOGR("nypp_15b", "nypp")
plot(nypp)
Nice map! But now notice the plot extents:
par(“usr”)
The plots space numbers look like 888196.7, 1092361.0, 114013.0, 278953.2, so clearly lat lon points like the ones below won't show up on the map. So how do I get my points to plot correctly on the map?
lat <- c(40.75002, 40.74317)
lon <- c(-73.96905 -74.00366)
The following doesn't work because the scale is so different:
points(lat,lon, col = “red”)
Thank you very much.
nypp is in projected coordinate system, so you need to change to coordinate system of your points or of nypp. You can do something like this :-
nypp <- readOGR("nypp_15b", "nypp")
## Check the CRS of nypp
crs(nypp)
## CRS arguments:
+proj=lcc +lat_1=40.66666666666666 +lat_2=41.03333333333333 +lat_0=40.16666666666666 +lon_0=-74 +x_0=300000
+y_0=0 +datum=NAD83 +units=us-ft +no_defs +ellps=GRS80 +towgs84=0,0,0
plot(nypp)
lat <- c(40.75002, 40.74317)
lon <- c(-73.96905, -74.00366)
df <- data.frame(lat, lon)
## Convert to spatial dataframe and change the coordinates of the points
coordinates(df) <- ~lon + lat
crs(df) <- CRS("+proj=longlat +datum=WGS84")
df <- spTransform(df, crs(nypp))
## Add points to the plot
points(df$lon, df$lat, col = "red", pch =19)
Result:

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