Distortions when reprojecting cshapes world map - r

I am trying to plot a reprojected world map using data from the cshapes package and sp::spTransform, but the projection leads to distorted plots. How can I correctly reproject and plot a cshapes map?
Here is an example that shows that the map plots fine by itself (code adapted from this blog post):
library("cshapes")
library("ggplot2")
library("rgdal")
wmap <- cshp(date=as.Date("2012-06-30"))
wmap_df <- fortify(wmap)
ggplot(wmap_df, aes(long,lat, group=group)) +
geom_polygon() +
labs(title="World map (longlat)") +
coord_equal()
ggsave("~/Desktop/map1.png", height=4, width=7)
And here is the distorted version when I reproject to Robinson:
wmap_robin <- spTransform(wmap, CRS("+proj=robin"))
wmap_df_robin <- fortify(wmap_robin)
ggplot(wmap_df_robin, aes(long,lat, group=group)) +
geom_polygon() +
labs(title="World map (robinson)") +
coord_equal()
ggsave("~/Desktop/map2.png", height=4, width=7)
Some additional info:
I know there are other data sources for country borders, but I need maps that reflect changes in country borders, which cshapes does.
My guess is the the problem is related to issues with the underlying map polygons, but I have no idea where to begin looking and it's probably better to ask what I want to get in the end, not how to fix a hunch.
The problem is not with ggplot2, plotting the map with base graphics shows the same distortions (plot(wmap_robin)).

You can use raster::crop to remove nodes that are just smaller than -180 or larger than 180
library(cshapes)
library(raster)
wmap <- cshp(date=as.Date("2012-06-30"))
w <- crop(wmap, extent(-180, 180,-90,90))
w_robin <- spTransform(w, CRS("+proj=robin"))
plot(w_robin)

Update for 2018, using a solution with sf:
library("cshapes")
library("sf")
cshp(as.Date("2015-01-01")) %>%
st_as_sf() %>%
st_crop(ymin = -90, ymax = 90, xmin=-180, xmax=180) %>%
st_transform(crs = "+proj=robin") %>%
`[`(1) %>% plot()
The output is:

Related

Map FAO fishing areas in R

I would like to make a map in R that colours in the FAO Fishing Areas according to a data set (in my case, length data of shark species).
I would prefer to do a choropleth map in ggplot but other types of maps are also fine. Worst case scenario a base map of FAO areas that I can add bubbles to. Even just an existing base map of FAO areas would be great. Any suggestions welcome!
I went to this page and clicked through to find this link to retrieve a GeoJSON file:
download.file("http://www.fao.org/fishery/geoserver/fifao/ows?service=WFS&request=GetFeature&version=1.0.0&typeName=fifao:FAO_AREAS_CWP&outputFormat=json", dest="FAO.json")
From here on, I was following this example from the R graph gallery, with a little help from this SO question and these notes:
library(geojsonio)
library(sp)
library(broom)
library(ggplot2)
library(dplyr) ## for joining values to map
spdf <- geojson_read("FAO.json", what = "sp")
At this point, plot(spdf) will bring up a plain (base-R) plot of the regions.
spdf_fortified <- tidy(spdf)
## make up some data to go with ...
fake_fish <- data.frame(id = as.character(1:324), value = rnorm(324))
spdf2 <- spdf_fortified %>% left_join(fake_fish, by = "id")
ggplot() +
geom_polygon(data = spdf2, aes( x = long, y = lat, group = group,
fill = value), color="grey") +
scale_fill_viridis_c() +
theme_void() +
theme(plot.background = element_rect(fill = 'lightgray', colour = NA)) +
coord_map() +
coord_sf(crs = "+proj=cea +lon_0=0 +lat_ts=45") ## Gall projection
ggsave("FAO.png")
notes
some of the steps are slow, it might be worth looking up how to coarsen/lower resolution of a spatial polygons object (if you just want to show the picture, the level of resolution might be overkill)
to be honest the default sequential colour scheme might be better but all the cool kids seem to like "viridis" these days so ...
There are probably better ways to do a lot of these pieces (e.g. set map projection, fill in background colour for land masses, ... ?)

Plot pre-1990 world map in R

I am working with some global data from before 1991, so before the USSR, Yugoslavia and Czechoslovakia split up. I would like to plot the data using rworldmap or maps, but the package appears to only have the modern world map easily accessible. All the pre-1991 countries show up blank and with the boundaries dividing their post-1991 counterparts.
This code produces the historical map:
if (requireNamespace("mapdata", quietly=TRUE) && packageVersion("mapdata") >= "2.3")
{map("mapdata::worldLores", fill = TRUE, col = 1:10)}
EDIT: also, as per the helpful comment below, a historical map shapefile is easily obtained from:
library(cshapes)
cshp.data<-cshp(as.Date("1990-01-01"))
plot(cshp.data)
But I cannot figure out if it is possible to combine this with the rworldmap functions ... or if I will have to figure out how to use the maps package, which seems to work differently. (Or maybe there is a ggplot solution?)
The rworldmap code I use currently (to get the modern map) is:
#make example data including Soviet Union
country <- as.vector(c("Afghanistan","Australia","Iceland","Soviet Union",
"Zimbabwe"))
value <- as.vector(c(5,10,100,10,50))
df<-data.frame(country,value)
#make map
map1 <- joinCountryData2Map(df, joinCode = "NAME", nameJoinColumn =
"country")
mapCountryData( map1, addLegend=F, catMethod="fixedWidth",
nameColumnToPlot="value" )
#...Soviet Union is blank
Ahah, there is a ggplot solution using the old map from the mapdata package:
library(ggplot2)
library(dplyr)
library(mapdata)
df<-data.frame(country=c("Afghanistan","Australia","Iceland","USSR","Zimbabwe"),
value=c(5,10,100,10,50),stringsAsFactors=FALSE)
WorldData <- map_data('worldLores') #use the old map
WorldData <- fortify(WorldData)
mapped <- ggplot() +
geom_map(data=WorldData, map=WorldData,
aes(x=long, y=lat, group=group, map_id=region),
fill="white", colour="#7f7f7f", size=0.5) +
geom_map(data=df, map=WorldData,
aes(fill=value, map_id=country),
colour="#7f7f7f", size=0.5)
mapped
(mapping code borrowed from this post, cheers #hrbrmstr)

R: ggmap: How do I draw lines using only known angles on a ggmap? My geom_spoke method is not working

I am attempting to draw lines in polar coordinates on a ggmap. The general method works fine in ggplot:
library(ggmap)
library(rgdal)
library(ggplot2)
#####################################
## build data frame with triangulation data
t1 <- c(534325, 4858925, 338, 0955)
t2 <- c(534383, 4859261, 290, 1010)
t3 <- c(534386, 4859011, 301, 1015)
df <- as.data.frame(rbind(t1, t2, t3))
colnames(df) <- c("Easting", "Northing", "Azimuth", "Time")
df$Time <- as.character(df$Time)
## plot coordinates with triangulation
ggplot(df, aes(x=Easting, y=Northing, label=Time)) +
geom_point() +
geom_text(nudge_x = 50) +
geom_spoke(aes(angle = Azimuth, radius = -500)) +
theme_bw()
Triangulation with no base map:
This is what I want, but with no base map in the background.
However, when I attempt this method in ggmap:
## convert utms to lat long
utmcoor <- SpatialPoints(cbind(df$Easting,df$Northing),
proj4string=CRS("+proj=utm +zone=12"))
lonlatcoor <- as.data.frame(spTransform(utmcoor,CRS("+proj=longlat")))
colnames(lonlatcoor) <- c("lon", "lat")
df$lon <- lonlatcoor$lon
df$lat <- lonlatcoor$lat
## plot on base map
zoom <- 15
meanlon <- mean(df$lon)
meanlat <- mean(df$lat)
basemap <- get_googlemap(center=c(lon=meanlon, lat=meanlat), zoom=zoom,
maptype="hybrid")
ggmap(basemap) +
geom_point(aes(x=lon, y=lat), colour="red", size=2, data=df) +
geom_spoke(aes(x=lon, y=lat, angle = Azimuth), data=df, radius=-200) +
theme_void()
I get the error
Warning message:
Removed 3 rows containing missing values (geom_segment).
The code works fine without the geom_spoke line, resulting in basemap without triangulations:
I understand that this function is generally used for things like quiver plots. Does ggmap not support geom_spoke? Is there a better function altogether that I am not aware of?
Thanks in advance.
The issue is with the coordinates: You're giving geom_spoke a radius of -200, but the coordinates you're working with are several orders of magnitude smaller. I took out the theme_void just to remind myself of how small the scale is; you want a radius of something like 0.01. Mess around with the radius value I put here.
geom_spoke basically does a little trigonometry behind the scenes to calculate the endpoint of each spoke, then draws a segment. If that endpoint is out of the bounds of the plot, you get that missing values warning. I'm not sure if -200 is the radius you're sticking with, or just a dummy value, but you could use your original coordinates and angles to calculate the endpoints, then project those points to figure out the radius. Or just use those endpoints to draw the geom_segment yourself. I'll leave those calculations to you, or it might make for another question. I'm pretty partial to sf and have a feeling that could provide an easy enough way to get those points.
library(ggmap)
library(rgdal)
library(ggplot2)
# .....
# everything up until ggmap is the same as in question
ggmap(basemap) +
geom_point(aes(x=lon, y=lat), colour="red", size=2, data=df) +
geom_spoke(aes(x=lon, y=lat, angle = Azimuth), data=df, radius = -1e-2)
Created on 2018-07-18 by the reprex package (v0.2.0).

ggplot, ggsave & coord_map/quickmap: how to save large spatial objects and get the projection right?

I have a largish polyline shapefile (Bavarian rivers, which can be accessed here) which I would like to plot and save via ggplot. This can easily be done via e.g. this code:
library(ggplot2)
library(rgdal)
library(sp)
library(rgeos)
riv <- readOGR(paste0(getwd(),"\\rivers_bavaria","rivers_bavaria"))
riv1 <- subset(riv,WDM=="1310"|WDM=="1320")
riv2 <- subset(riv,WDM=="1330")
p <- ggplot() +
geom_line(data=riv1, aes(x=long, y=lat, group=group), color="dodgerblue", size=1) +
geom_line(data=riv2, aes(x=long, y=lat, group=group), color="dodgerblue")
ggsave(paste0(getwd(),"\\riv.tiff",p,device="tiff",units="cm",dpi=300)
This is not exactly efficient, due to the large file size, but it works. However, without further specifying aspect ratio or projection, the dimensions of the output file are defined by the plot window - not desirable for maps. This can be remedied by using coord_quickmap().
p1 <- ggplot() +
geom_line(data=riv1, aes(x=long, y=lat, group=group), color="dodgerblue", size=1) +
geom_line(data=riv2, aes(x=long, y=lat, group=group), color="dodgerblue") +
coord_quickmap()
p1
Unfortunately, the projection is completely off. I have tried coord_map() for a better result, but due to the large file size, it takes forever and is therefore not a realistic option. Simplifying the polyline via gLinemerge() produces a much smaller object, but cannot be handled by ggplot, as it is a SpatialLines object. Using fortify() or data.frame() to coerce it into a ggplot-friendly data frame format also produces Error: ggplot2 doesn't know how to deal with data of class SpatialLines.
I'm therefore desperately looking for a workflow that will allow me to plot and save this kind of spatial data in good quality with ggplot. Any suggestions will be much appreciated!
Here's a quick walkthrough with sf. I recommend the sf vignettes and docs to see more details of any of the functions. I'm first reading the shapefile in as an sf object using sf::st_read, then filtering, mutating, and selecting the same as you would in dplyr to get a smaller version of the shape.
library(tidyverse)
library(sf)
rivers_sf <- st_read("rivers_bavaria/rivers_bavaria.shp") %>%
filter(WDM %in% c("1310", "1320", "1330")) %>%
mutate(name2 = ifelse(WDM == "1330", "river 2", "river 1")) %>%
select(name2, NAM, geometry)
The object is pretty big, and will be very slow to plot, so I simplified it by uniting the geometries by name, then using st_simplify. There's also rmapshaper::ms_simplify, which uses Mapshaper and which I prefer for better control over how much information you keep. Then to show a CRS transformation, I picked a projection from Spatial Reference for Germany.
riv_simple <- rivers_sf %>%
group_by(name2, NAM) %>%
summarise(geometry = st_union(geometry)) %>%
ungroup() %>%
st_simplify(preserveTopology = T, dTolerance = 1e6) %>%
st_transform(31493)
The dev version of ggplot2 on GitHub has a function geom_sf for plotting different types of sf objects. To get this version, run devtools::install_github("tidyverse/ggplot2").
geom_sf has some quirks, and works a little differently from other geoms, but it's pretty versatile. I believe it's being included in the next CRAN release. geom_sf has corresponding stat_sf and coord_sf. By default, it plots graticule lines; to turn those off, add coord_sf(ndiscr = F).
ggplot(riv_simple) +
geom_sf(aes(size = name2), color = "dodgerblue", show.legend = "line") +
scale_size_manual(values = c("river 1" = 1, "river 2" = 0.5)) +
theme_minimal() +
coord_sf(ndiscr = F)
Hope that helps you get started!

R function to get a projected map centered on a point of interest

Is there an easy way to project sp object around a location of interset.
Example: I work on Russia, how can I project to have reasonable flat projection, similar to what I get when centering on that country in GoogleEarth?
library("rworldmap")
russia <- countriesCoarse[which(countriesCoarse$ADMIN=="Russia"),]
plot(russia)
Gives me that:
Quite far from the geometry of that:
Note: I'm not just interested in the map, but also in the projected coordinates themselves (as an sp object) to be able to use them in calculations, particularly with rgeos.
This is a way with ggplot2, its extension ggalt (not really needed, but geom_map is kinda broken atm) and the maps package:
library(maps)
library(ggplot2)
library(ggalt)
mapa <- map_data('world', 'Ru')
ggplot() +
geom_cartogram(map = mapa,
data = mapa,
aes(long, lat, map_id = region),
color = 'black',
fill = 'grey') +
coord_map('polyconic') +
theme_minimal()
Note the 'polyconic' projection, I just think is nice, feel free to adjust
The Extended Transverse Mercator is a nice option. To stay in R packages not dependent on ggplot representation, you can use spTransform from sp/rgdal packages. The following function is extracted from spatDataManagement package.
Project<-function(obj,objRef=obj){
xlim <- range(coordinates(obj)[,1]) # bbox(objRef)[1,]
ylim <- range(coordinates(obj)[,2]) # bbox(objRef)[2,]
cp <- c(mean(xlim),mean(ylim)) # ~baricenter of the map
stringProj <- paste0("+proj=etmerc +lat_0=",cp[2]," +lon_0=",cp[1],
" +k=1 +x_0=0 +y_0=0 +ellps=WGS84 +units=m +no_defs")
polysProj <- spTransform(x=obj,CRSobj=CRS(stringProj))
return(polysProj)
}
library("rworldmap")
russia <- countriesCoarse[which(countriesCoarse$ADMIN=="Russia"),]
projRussia <- Project(russia)
par(mfrow=c(1,2))
plot(russia,main="Lat/Long projection")
plot(projRussia,main="Projected nicely")

Resources