1

I'm looking for a ggplot2 equivalent for eqscplot (package MASS) that can fill the entire plot window while maintaining the scale within a map in a ggplot.

There is coord_fixed where cartesian coordinates can be set with fixed ratios (argument aspect =), but it doesn't work if I'm using geom_sf(); geom_sf() requires coord_sf for a plot to be drawn and can't be a ggplot layer if coord_sf is a layer.

There is this answer (ggplot2: Flip axes and maintain aspect ratio of data) but it's not automatic, which is a problem when there are a lot of maps to be made and where automated map making is the ideal workflow. It also doesn't zoom into the points like eqscplot does.

eqscplot is able to maintain an aspect ratio when the range of x and y values are widely different, which is helpful for plotting maps.

library(rnaturalearth)
library(rnaturalearthdata)
library(MASS)
library(ggplot2)

world <- ne_countries(scale = 'medium', returnclass = 'sf',
                      country = c('India', 'Sri Lanka', 'Pakistan', 'Myanmar', 
                                  'Malaysia', 'Indonesia', 'Thailand', 
                                  'Nepal', 'Bhutan', 'Laos', 'China', 'Iran',
                                  'Maldives'))

RNGkind('Mers')
set.seed(42)

lonlat <- data.frame(lon = rnorm(10, 80, 5),
                     lat = rnorm(10, 12, 5))
apply(lonlat, 2, range)
          lon       lat
[1,] 77.17651  9.343545
[2,] 90.09212 14.286645

eqscplot(lonlat$lon, lonlat$lat, type = 'n')
plot(world, add = T, col = NA)
points(lonlat$lon, lonlat$lat, col = 'red', pch = 16)
#Compare the lon/lat ranges on the map with the ranges of the data's longitude and latitude

enter image description here

I was was looking to see if there was a way to do it ggplot2, but if I don't specify the limits of the longitude and latitude, it takes the extent of the countries I specified in the world object.

ggplot(data = world) +
  geom_sf() +
  geom_point(data = lonlat, aes(x = lon, y = lat))

enter image description here

I've got a few hundred maps to make so automating the process with apply family of functions or for loops is preferred. But I need something that can automatically zoom into the points that I'm plotting while maintaining the the aspect ratio. I'm looking to do it in ggplot2 because my collaborators aren't as experienced with R and were taught how to use the tidyverse, so anything I can do to make the code easier for them to understand can go a long way.

My question is: Is there an eqscplot equivalent in ggplot2? Something where the aspect ratio can be set automatically, zooms into where the points are instead of the extent of the world polygon, and fills the entire plot window while maintaining the scales?

Lalochezia
  • 497
  • 4
  • 15

2 Answers2

0

Using geom_sf it looks like the plots will adjust to show the extent of the largest object. You can use st_crop to adjust the data that is too large. It might help to make all of your objects sf collections, rather than using both geom_sf and geom_point.

Below is a quick example that you can adjust to change the bounding box of the maps. A good post by @Jindra-Lacko on adjusting bounding boxes can be found here: https://www.jla-data.net/eng/adjusting-bounding-box-of-a-tmap-map/

Though most of it is using tmap, it should apply to ggplot2 as well.

library(rnaturalearth)
library(rnaturalearthdata)
library(MASS)
library(ggplot2)
library(tidyverse)
library(sf)
#> Linking to GEOS 3.6.2, GDAL 2.2.3, PROJ 4.9.3

world <- ne_countries(scale = 'medium', returnclass = 'sf',
                      country = c('India', 'Sri Lanka', 'Pakistan', 'Myanmar', 
                                  'Malaysia', 'Indonesia', 'Thailand', 
                                  'Nepal', 'Bhutan', 'Laos', 'China', 'Iran',
                                  'Maldives'))

RNGkind('Mers')
set.seed(42)

lonlat <- data.frame(lon = rnorm(10, 80, 5),
                     lat = rnorm(10, 12, 5))
#apply(lonlat, 2, range)
# lon       lat
# [1,] 77.17651  9.343545
# [2,] 90.09212 14.286645

# eqscplot(lonlat$lon, lonlat$lat, type = 'n')
# plot(world, add = T, col = NA)
#points(lonlat$lon, lonlat$lat, col = 'red', pch = 16)
#Compare the lon/lat ranges on the map with the ranges of the data's longitude and latitude

#make lonlat an sf object
lonlat_sf <- st_as_sf(lonlat, 
                      coords = c('lon', 'lat')) %>%
  st_set_crs(st_crs(world))

ggplot() +
  geom_sf(data = lonlat_sf, color = 'red') +
  geom_sf(data = world %>% st_crop(lonlat_sf, world ), fill = NA) +
  coord_sf()
#> although coordinates are longitude/latitude, st_intersection assumes that they are planar
#> Warning: attribute variables are assumed to be spatially constant throughout all
#> geometries

Created on 2020-04-14 by the reprex package (v0.3.0)

mrhellmann
  • 5,069
  • 11
  • 38
0

When I have this issue with different aspect ratios. I create a buffer for the output dimension i want and save using the same scales similar to below..

library(buffers)

#Find bbox and bbox_centroid
bbox <- st_as_sfc(st_bbox(df))
bbox_centroid<-st_centroid(bbox)

#Calculate Max distance from centroid
max_dist=as.numeric(max(st_distance(bbox_centroid,bbox)))

#Create buffer (In this case I wanted a rectangular image output)
#Notice I'm using a 1.5 ratio for x_length to y_length, 3/2=1.5)
rectangle_buf=buffer_rectangle(bbox_centroid,x_length=max_dist*2,y_length=max_dist*3)

#Find bbox minumum and maximum X and Y values
bbox_rect<-st_as_sf(rectangle_buf)
bbox<-st_bbox(bbox_rect)

#Create ggplot2 image and save with correct dimensions
ggplot(df)+geom_sf()+
#Important part of code
coord_sf(xlim = c(bbox[1], bbox[3]), ylim = c(bbox[2], bbox[4]), expand = T)

##Save ggplot with correct dimensions (In my case this is would be a 1 to 1.5 ratio)
ggsave(plot=last_plot(),width=3,height= 3* 1.5)
jsimpsno
  • 448
  • 4
  • 19