Setting map limits in ggplot2 with Mercator projection - r

Very much related to this question I am trying to plot some world regions, now using a Mercator projection, but keep getting into trouble when adding x and y limits:
ggplot(world, mapping = aes(x = long, y = lat, group = group)) +
geom_polygon(fill = "black", colour = "black") +
coord_map(projection = "mercator", xlim = c(-125, -30), ylim = c(-60, 35))
Obviously not great. When I use coord_cartesian (as suggested here) to set the limits, I loose the Mercator projection:
ggplot(world, mapping = aes(x = long, y = lat, group = group)) +
geom_polygon(fill = "black", colour = "black") +
coord_map(projection = "mercator") +
coord_cartesian(xlim = c(-125, -30), ylim = c(-60, 35))
When I use lims I get what I want for Latin America:
ggplot(world, mapping = aes(x = long, y = lat, group = group)) +
geom_polygon(fill = "black", colour = "black") +
coord_map(projection = "mercator") +
lims(x = c(-125, -30), y = c(-60, 35))
Problem is, this approach does not always work, for example for Africa or Asia I start to get some crazy behaviour towards the plot border:
ggplot(world, mapping = aes(x = long, y = lat, group = group)) +
geom_polygon(fill = "black", colour = "black") +
coord_map(projection = "mercator") +
lims(x = c(-20, 45), y = c(-50, 40))
# lims(x = c(40, 150), y = c(-10, 55))

A solution here could be to convert the lat/lon coordinates to "proper" web mercator coordinates (here I'm using epsg 3857, which is the "google" projection), and then plotting using those "new" coordinates.
Assuming that original coordinates are latlon wgs84 (epsg 4326), this can be achieved like this:
worldmerc <- SpatialPointsDataFrame(coords = data_frame(x = world$long, y = world$lat),
data = world, proj4string = CRS("+proj=longlat +datum=WGS84")) %>%
subset((lat < 90 & lat > -90)) %>% # needed because transform not defined at the poles !!!!
spTransform(CRS("+init=epsg:3857"))
worldmerc <- mutate(worldmerc#data, longmerc = coordinates(worldmerc)[,1], latmerc = coordinates(worldmerc)[,2])
Plotting the whole data gives you this (Note the use of coord_fixed to preserve aspect ratio !:
ggplot(worldmerc, mapping = aes(x = longmerc, y = latmerc, group = group)) +
geom_polygon(fill = "black", colour = "black") +coord_fixed()
Now, the problem is that to do subsetting you would now need to enter "map" coordinates instead than lat long, but also that can be tweaked:
#For South America
xlim = c(-125, -30)
ylim = c(-60, 35)
lims = SpatialPoints(coords = data_frame(x = xlim, y = ylim), proj4string = CRS("+proj=longlat +datum=WGS84"))%>%
spTransform(CRS("+init=epsg:3857"))
ggplot(worldmerc, mapping = aes(x = longmerc, y = latmerc, group = group)) +
geom_polygon(fill = "black", colour = "black")+
coord_fixed(xlim = coordinates(lims)[,1], ylim = coordinates(lims)[,2])
#for africa
xlim = c(-20,45)
ylim = c(-50,40)
lims = SpatialPoints(coords = data_frame(x = xlim, y = ylim), proj4string = CRS("+proj=longlat +datum=WGS84"))%>%
spTransform(CRS("+init=epsg:3857"))
ggplot(worldmerc, mapping = aes(x = longmerc, y = latmerc, group = group)) +
geom_polygon(fill = "black", colour = "black")+
coord_fixed(xlim = coordinates(lims)[,1], ylim = coordinates(lims)[,2])
As you can see, in both cases you get "correct" maps.
Now, last thing you may want to do is maybe have "lat/lon" coordinates on the axis. That's a bit of a hack but can be done like this:
library(magrittr)
xlim = c(-125, -30)
ylim = c(-60, 35)
# Get the coordinates of the limits in mercator projection
lims = SpatialPoints(coords = data_frame(x = xlim, y = ylim),
proj4string = CRS("+proj=longlat +datum=WGS84"))%>%
spTransform(CRS("+init=epsg:3857"))
# Create regular "grids" of latlon coordinates and find points
# within xlim/ylim - will be our labels
majgrid_wid_lat = 20
majgrid_wid_lon = 30
majbreaks_lon = data_frame(x=seq(-180, 180, majgrid_wid_lon)) %>%
filter(x >= xlim[1] & x <= xlim[2]) %>%
as.data.frame()
majbreaks_lat = data_frame(x=seq(-90, 90, majgrid_wid_lat)) %>%
filter(x >= ylim[1] & x <= ylim[2]) %>%
as.data.frame()
#Find corresponding mercator coordinates
mercbreaks_lat = SpatialPoints(coords = expand.grid(x = majbreaks_lon$x, y = majbreaks_lat$x), proj4string = CRS("+init=epsg:4326"))%>%
spTransform(CRS("+init=epsg:3857")) %>% coordinates() %>% extract(,2) %>% unique()
mercbreaks_lon = SpatialPoints(coords = expand.grid(x = majbreaks_lon$x, y = majbreaks_lat$x), proj4string = CRS("+init=epsg:4326"))%>%
spTransform(CRS("+init=epsg:3857")) %>% coordinates() %>% extract(,1) %>% unique()
# Plot using mercator coordinates, but latlon labels
ggplot(worldmerc, mapping = aes(x = longmerc, y = latmerc, group = group)) +
geom_polygon(fill = "black", colour = "black") +
coord_fixed(xlim = coordinates(lims)[,1], ylim = coordinates(lims)[,2])+
scale_x_continuous("lon", breaks = mercbreaks_lon, labels = signif(majbreaks_lon$x, 2)) +
scale_y_continuous("lat", breaks = mercbreaks_lat, labels = signif(majbreaks_lat$x,2))+theme_bw()
, which gives:
It's a bit convoluted and there could be better ways, but it does the trick, and could be easily transformed in a function.
HTH,
Lorenzo

Related

Plot the legend at the bottom in different way when we deal with discrete color filling in ggplot

Suppose we want to plot this data:
library(ggplot2)
library(sf)
library(raster)
library(colorRamps)
min_lon <- 10
max_lon <- 17
min_lat <- 8
max_lat <- 17
grid_size <- 0.5
lon_grids <- 1 + ((max_lon - min_lon)/grid_size)
lat_grids <- 1 + ((max_lat - min_lat)/grid_size)
points <- data.frame(lon = rep(seq(min_lon, max_lon, grid_size), lat_grids), lat = rep(seq(min_lat, max_lat, grid_size), each = lon_grids))
points$Var <- runif(min= 10, max = 48, 285)
points$value <-cut(points$Var, breaks= seq(10.08, 47.80, length.out = 13), dig.lab = 1)
ggplot() +
coord_sf(xlim = c(min_lon, max_lon), ylim = c(min_lat, max_lat)) +
theme_bw()+
geom_raster(data = points, aes(x = lon, y = lat, fill = value), interpolate = FALSE) +
labs(x="Longitude", y="Latitude")+
scale_fill_manual(values = matlab.like(n = 13), name = "[m]",
labels = sprintf("%.2f", seq(10.08, 47.80, length.out = 13)),
guide = guide_legend(reverse = TRUE))+theme(legend.position = "bottom")
This code produces the following graph:
Two problems I am facing here:
To make it discrete, I used the cut function. I chose the breaks= seq(10.08, 47.80, length.out = 13) arbitrary based on the minimum and maximum values with a random length of 13. Is there any criteria to decide the correct range?
Is there any way to make the legend look like this?
One option would be to use e.g. scale_fill_stepsn with guide_binswhich does not require to manually discretize the variable mapped on fill. Additionally I use a custom function to set the breaks of the legend instead of the default mechanism to set the number of breaks.
set.seed(123)
library(ggplot2)
library(colorRamps)
base <- ggplot() +
coord_sf(xlim = c(min_lon, max_lon), ylim = c(min_lat, max_lat)) +
theme_bw() +
geom_raster(data = points, aes(x = lon, y = lat), interpolate = FALSE) +
labs(x = "Longitude", y = "Latitude") +
theme(legend.position = "bottom")
base +
aes(fill = Var) +
scale_fill_stepsn(colors = matlab.like(n = 13), name = "[m]",
breaks = function(x) seq(x[[1]], x[[2]], length.out = 13),
labels = ~ sprintf("%.0f", .x),
guide = guide_bins(axis = FALSE,
show.limits = TRUE))

How to draw an arc on top of a plot map overlay made with ggmap in R

I created a multi-layer map using ggmap() in R. I would like to add an arc between two points on the map. I tried doing so with geom_curve(). This doesn't work. The two maps in the overlay are then no longer properly superimposed. I suspect this is due to the different projections within the map overlay, but I don't fully understand this.
Is there a way to specify a coordinate reference system (CRS) for an arc that matches the CRS in use in the map already? Or a totally different way I could add one arc between two points in my exisiting map?
A similar question with an accepted solution was already asked on stack overflow ("Draw curved lines in ggmap, geom_curve not working"). However, as noted there, the solution doesn't work for larger map areas.
Here is a minimal working example with some comments:
My system
I'm using R 4.2.0 with RStudio version 2022.02.3 Build 492.
Required Packages
install.packages("rnaturalearth")
install.packages("devtools") # Needed for rnaturalearthhires
devtools::install_github("ropensci/rnaturalearthhires") # Ditto
install.packages("ggmap")
install.packages("ggplot2")
install.packages("dplyr")
install.packages("PBSmapping") #For clipping polygons
install.packages("ggsn") #For the scale bar
library(rnaturalearth)
library(rnaturalearthhires) # Needed for scale = large in ne_countries()
library(ggmap)
library(ggplot2)
library(dplyr)
library(PBSmapping)
library(ggsn)
Plot a map that includes Germany and Saudi Arabia
Define the map for the region including Germany and Saudi Arabia (KSA) with
latitude and longitude (x min, y min, x max, y max)
GermanyKSA_map_coordinates <- c(5, 16, 56, 56)
Set unique values for the two countries for coloring them on the larger map
GermanyKSA_values <- data.frame(region = factor(c("Germany", "Saudi Arabia")), data = c(15, 15))
Get an existing map of the region including both countries
GermanyKSA_map <- get_map(location = GermanyKSA_map_coordinates, source = "stamen", zoom = 6)
Get country polygon data and join these to the unique values
world <- ne_countries(scale = "large", returnclass = "sf")
mapdata <- map_data("world")
GermanyKSA_mapdata <- left_join(mapdata, GermanyKSA_values, by = "region")`
Create a bounding box for the region map
GermanyKSA_map_bb <- attr(GermanyKSA_map, "bb")
GermanyKSA_ylim <- c(GermanyKSA_map_bb$ll.lat, GermanyKSA_map_bb$ur.lat)
GermanyKSA_xlim <- c(GermanyKSA_map_bb$ll.lon, GermanyKSA_map_bb$ur.lon)`
Clip polygons to match the map
colnames(GermanyKSA_mapdata)[1:6] <- c("X","Y","PID","POS","region","subregion")
GermanyKSA_mapdata <- clipPolys(GermanyKSA_mapdata, xlim = GermanyKSA_xlim, ylim = GermanyKSA_ylim, keepExtra = TRUE)`
Add dots for the cities Regensburg, Germany, and Al-Hofuf, Saudi Arabia
Regensburg_Dot <- c(12.1015461, 49.0204469)
AlHofuf_Dot <- c(49.401569, 25.363091)`
Create a data frame with the dot coordinates
Dots <- rbind(Regensburg_Dot, AlHofuf_Dot) %>% as.data.frame()
Rename the columns
colnames(Dots) <- c("long", "lat")
Plot map overlay. Provides exactly what I need, only an arc between the two cities is missing
ggmap(GermanyKSA_map) +
coord_map(xlim = GermanyKSA_xlim, ylim = GermanyKSA_ylim) +
geom_polygon(data = GermanyKSA_mapdata, aes(x = X, y = Y, group = PID, fill = data),
color = "black", alpha=0.5) +
geom_point(data = Dots, aes(x = long, y = lat), col = "#990022", size = 4) +
scalebar(x.min = 5, x.max = 56, y.min = 16, y.max = 56, dist = 500,
dist_unit = "km", transform = TRUE, anchor = c(x = 52, y = 54),
box.fill = c("#990022", 'grey'), box.color = c("#990022", 'grey'),
st.dist = .025, st.size = 3) +
theme(legend.position = "none") +
theme(axis.title.x = element_blank(), axis.title.y = element_blank()) +
ggtitle("Regensburg–Al-Hofuf: 4,093 km")`
Unsuccessful attempt to add an arc between both points (Regensburg and Al-Hofuf)
Create data frame for drawing an arc
RegensburgToAlHofuf <- data.frame(Regensburg_x = 12.1015461,
Alhofuf_x = 49.401569, Regensburg_y = 49.0204469, Alhofuf_y = 25.363091)`
Plot map overlay, now with an attempt to draw an arc with geom_curve(). Resulting warning: "geom_curve is not implemented for non-linear coordinates"
ggmap(GermanyKSA_map) +
coord_map(xlim = GermanyKSA_xlim, ylim = GermanyKSA_ylim) +
geom_polygon(data = GermanyKSA_mapdata, aes(x = X, y = Y, group = PID,
fill = data), color = "black", alpha=0.5) +
geom_point(data = Dots, aes(x = long, y = lat), col = "#990022", size = 4) +
geom_curve(data = RegensburgToAlHofuf,
aes(x = Regensburg_x, y = Regensburg_y, xend = Alhofuf_x, yend = Alhofuf_y),
size = 1.5, color = "#990022", curvature = 0.15, inherit.aes = TRUE) +
scalebar(x.min = 5, x.max = 56, y.min = 16, y.max = 56, dist = 500,
dist_unit = "km", transform = TRUE, anchor = c(x = 52, y = 54),
box.fill = c("#990022", 'grey'), box.color = c("#990022", 'grey'),
st.dist = .025, st.size = 3) +
theme(legend.position = "none") +
theme(axis.title.x = element_blank(), axis.title.y = element_blank()) +
ggtitle("Regensburg–Al-Hofuf: 4,093 km")`
I added a call to a map projection, coord_cartesian(), following suggestion in "Draw curved lines in ggmap, geom_curve not working". However, plots are not correctly superimposed. I also tried coord_quickmap() instead of coord_cartesian(), which also didn't work.
ggmap(GermanyKSA_map) +
coord_map(xlim = GermanyKSA_xlim, ylim = GermanyKSA_ylim) +
geom_polygon(data = GermanyKSA_mapdata, aes(x = X, y = Y, group = PID,
fill = data), color = "black", alpha=0.5) +
geom_point(data = Dots, aes(x = long, y = lat), col = "#990022", size = 4) +
geom_curve(data = RegensburgToAlHofuf,
aes(x = Regensburg_x, y = Regensburg_y, xend = Alhofuf_x, yend = Alhofuf_y),
size = 1.5, color = "#990022", curvature = 0.15, inherit.aes = TRUE) +
scalebar(x.min = 5, x.max = 56, y.min = 16, y.max = 56, dist = 500,
dist_unit = "km", transform = TRUE, anchor = c(x = 52, y = 54),
box.fill = c("#990022", 'grey'), box.color = c("#990022", 'grey'),
st.dist = .025, st.size = 3) +
theme(legend.position = "none") +
theme(axis.title.x = element_blank(), axis.title.y = element_blank()) +
ggtitle("Regensburg–Al-Hofuf: 4,093 km") +
coord_cartesian()`

How to draw a line on the 66th parallel north in ggplot when using a polar (epsg:3995) projection and ggspatial

I am trying to draw a blue circle on the 66th parallel north. on a map I am making.
I usually fortify() a SpatialPolygonsDataFrame and then use the combination geom_polygon(), coord_map("ortho" , ylim = c(50, 90)) and geom_hline() to achieve this.
However, for a new project I will have to use ggspatial::layer_spatial() on an object that already has a CRS date, so this straight line drawing and automatic re-projection does not work.
This is a demonstration of my usual approach, which works:
library(tidyverse)
map_data <- rnaturalearth::ne_countries()
map_data_df <- fortify(map_data)
ggplot() +
geom_polygon(data = map_data_df,
aes(x = long,
y = lat,
group = group),
col = "black",
fill = "lightgrey") +
coord_map("ortho" , ylim = c(50, 90)) +
geom_hline(yintercept = 66, col = "blue")
and I am trying to replicate it in this code, but that obviously does not work because the geom_hline() does not have the right projection:
map_data_transformed <- sp::spTransform(map_data, sp::CRS("+init=epsg:3995"))
# needs some fixing to close all the polygons
map_data_transformed <- rgeos::gBuffer(map_data_transformed, byid=TRUE, width=0)
ggplot() +
ggspatial::layer_spatial(data = map_data_transformed,
fill = "lightgrey", col = "black", size = .2)+
coord_sf( xlim = c(-4500000 , 4500000),
ylim = c(-4500000 , 4500000)) +
geom_hline(yintercept = 66, col = "blue")
See if this works for you?
# define parallel north line as a spatial polygon with latitude / longitude projection
new_line <- sp::Polygon(coords = matrix(c(seq(-180, 180),
rep(66, times = 361)), # change this for other
# intercept values
ncol = 2))
new_line <- sp::SpatialPolygons(Srl = list(sp::Polygons(srl = list(new_line),
ID = "new.line")),
proj4string = sp::CRS("+proj=longlat +datum=WGS84"))
# change projection to match that of data
new_line <- sp::spTransform(new_line, sp::CRS("+init=epsg:3995"))
# plot
ggplot() +
ggspatial::layer_spatial(data = map_data_transformed,
fill = "lightgrey", col = "black", size = .2) +
ggspatial::layer_spatial(data = new_line,
fill = NA, col = "blue") +
coord_sf( xlim = c(-4500000 , 4500000),
ylim = c(-4500000 , 4500000))

Plot bathymetry and coastline using ggplot2 and marmap

I looked around and I have not found a nice solution for my goal.
I want to plot some data on a longitude/ latitude plot using ggplot2 and the coastline plus bathymetry with marmap, everything in one single plot.
This script is to plot mydata
ggplot(data = ctd, aes(x = Longitude, y = Latitude)) +
geom_raster(aes(fill = Temp)) +
scale_fill_gradientn(colours = rev(my_colours)) +
geom_contour(aes(z = Temp), binwidth = 2, colour = "black", alpha = 0.2) +
#plot stations locations
geom_point(data = ctd, aes(x = Longitude, y = Latitude),
colour = 'black', size = 3, alpha = 1, shape = 15) +
#plot legends
labs(y = "Latitude", x = "Longitude", fill = "Temp (°C)") +
coord_cartesian(expand = 0)+
ggtitle("Temperature distribution")
Using marmap I download the bathymetry
library(marmap)
Bathy <- getNOAA.bathy(lon1 = 37, lon2 = 38.7,
lat1 = -45.5, lat2 = -47.3, resolution = 1)
The result I would like to obtain is the distribution of mydata on Lon/Lat with the land colored in black plus grey lines for the bathymetry.
Well, there's a marmap function for that. It's called autoplot.bathy(). Have you checked it's help file?
library(marmap) ; library(ggplot2)
library(marmap)
Bathy <- getNOAA.bathy(lon1 = 37, lon2 = 38.7,
lat1 = -45.5, lat2 = -47.3, resolution = 1)
ctd <- data.frame(Longitude = c(37.5, 38, 38.5), Latitude = c(-47, -46.5, -46))
autoplot.bathy(Bathy, geom=c("tile","contour")) +
scale_fill_gradient2(low="dodgerblue4", mid="gainsboro", high="darkgreen") +
geom_point(data = ctd, aes(x = Longitude, y = Latitude),
colour = 'black', size = 3, alpha = 1, shape = 15) +
labs(y = "Latitude", x = "Longitude", fill = "Elevation") +
coord_cartesian(expand = 0)+
ggtitle("A marmap map with ggplot2")
Or, with base graphics (and the correct aspect ratio):
# Creating color palettes
blues <- c("lightsteelblue4", "lightsteelblue3", "lightsteelblue2", "lightsteelblue1")
greys <- c(grey(0.6), grey(0.93), grey(0.99))
# Plot
plot(Bathy, image = TRUE, land = TRUE, n=30, lwd = 0.1, bpal = list(c(0, max(Bathy), greys), c(min(Bathy), 0, blues)), drawlabels = TRUE)
# Add coastline
plot(Bathy, deep = 0, shallow = 0, step = 0, lwd=2, add = TRUE)
# Add stations
points(ctd, pch=15, cex=1.5)
Here is an approach:
get bathy data:
library(marmap)
Bathy <- getNOAA.bathy(lon1 = 37, lon2 = 38.7,
lat1 = -45.5, lat2 = -47.3, resolution = 1)
convert it to matrix:
Bathy <- as.matrix(Bathy)
class(Bathy) <- "matrix"
now reshape it to long format and plot
library(tidyverse)
Bathy %>%
as.data.frame() %>%
rownames_to_column(var = "lon") %>%
gather(lat, value, -1) %>%
mutate_all(funs(as.numeric)) %>%
ggplot()+
geom_contour(aes(x = lon, y = lat, z = value), bins = 10, colour = "black") +
coord_map()

Labeling locations on Google Maps using R

I have a dataframe which has three columns - Places, lat, and long. Places is a list of strings, and the lat and long values are obtained by geocoding the list.
I can plot the lat long values using
map + geom_point(data = lat_long_vec, aes(x = lat_long_vec$lon, y = lat_long_vec$lat), size = 3, colour = "blue", shape = 19)
where map = map = qmap(location=someplacename, zoom = somezoomvalue)
However, I would like to label the points using the Places column of the dataframe. I have tried the following which hasn't worked.
map + geom_point(data = lat_long_vec, aes(x = lat_long_vec$lon, y = lat_long_vec$lat), size = 3, colour = "blue", shape = 19) + geom_text(aes(label=lat_long_vec$Places),hjust=0, vjust=0)
Can someone help? Thanks
I would use geom_text like this.
library(ggmap)
mydf <- data.frame(lat = 17.245088, lon = 78.299744, places = c('My place name'))
ggmap(get_googlemap(center = paste(mydf$lat[1], mydf$lon[1]),
maptype = 'roadmap',
zoom = 10,
color = 'bw',
scale = 2),
extent = 'panel') +
geom_point(data = mydf,
aes(x = lon, y = lat),
fill = "red",
colour = "black",
size = 3,
shape = 21) +
geom_text(data = mydf,
aes(x = lon,
y = lat,
label = places),
color = 'blue')

Resources