As your shapefile seems to be of a country politely referred to as Persia :wink: I suggest the following code.

It downloads the shape of Iran from GADM and crops your data to the shape via raster::mask()

I had to tweak your ggplot code a bit (replacing lat / lon with x / y) but otherwise it is the same code.

library(tidyverse)
library(raster)
library(sf)

borders <- readRDS(url("https://biogeo.ucdavis.edu/data/gadm3.6/Rsf/gadm36_IRN_0_sf.rds"))

DFgrid <- data.frame(
  lon=c(59.25, 59.75, 60.25, 60.75, 61.25, 61.75, 57.25, 57.75, 58.25, 58.75, 59.25, 59.75, 60.25, 60.75, 61.25, 61.75, 62.25, 57.25, 57.75, 58.25, 58.75, 59.25, 59.75, 60.25, 60.75, 61.25, 61.75, 62.25, 62.75, 53.25),
  lat=c(25.25, 25.25, 25.25, 25.25, 25.25, 25.25, 25.75, 25.75, 25.75, 25.75, 25.75, 25.75, 25.75, 25.75, 25.75, 25.75, 25.75, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.75),
  value= rnorm(30)
)

raster_frame <- rasterFromXYZ(DFgrid) %>% 
  mask(borders) %>% 
  as("SpatialPixelsDataFrame") %>% 
  as_data_frame()

my_year <- theme_bw() + theme(panel.ontop=TRUE, panel.background=element_blank())
my_fill <- scale_fill_distiller('',palette='Spectral',
                                breaks = c(-2,0,2))

fig = ggplot() + geom_tile(data = raster_frame, aes(x=x, y=y, fill=value)) + my_year + my_fill+
  coord_quickmap(xlim=c(44.05, 63.35), ylim=c(25.05, 39.75))+
  borders('world', xlim=c(44.05, 63.35), ylim=c(25.05, 39.75), colour='black')+
  xlab('Longitude (°E)')+ ylab('Latitude (°N)')+
  theme(panel.grid=element_blank(),
        panel.border = element_rect(colour = "black", fill=NA,size=.7))
print(fig)

4 Likes

Thanks. It looks better, but some grids still straddle the border. Is it possible to cut the grids at the border to the shape fits the border?

It really depends on what you want to achieve.

  • You can either do the selection at the data level, and discard the grid cells that are not entirely contained by the country borders. This is good if you want to do some calculation on the resulting raster, but the cells look somewhat ragged.
  • Or you can keep the data as they are, and keep all the action at the plotting level = overlay the grid cells with a white mask cut to the shape of country borders. The data will remain as they were, but the plot will be nice & even.

The first option - built on raster::mask() - is in my code sample above.

The second, masking, does not require {raster} and makes do with {sf} only:

library(tidyverse)
library(sf)

borders <- readRDS(url("https://biogeo.ucdavis.edu/data/gadm3.6/Rsf/gadm36_IRN_0_sf.rds"))

DFgrid <- data.frame(
   lon=c(59.25, 59.75, 60.25, 60.75, 61.25, 61.75, 57.25, 57.75, 58.25, 58.75, 59.25, 59.75, 60.25, 60.75, 61.25, 61.75, 62.25, 57.25, 57.75, 58.25, 58.75, 59.25, 59.75, 60.25, 60.75, 61.25, 61.75, 62.25, 62.75, 53.25),
   lat=c(25.25, 25.25, 25.25, 25.25, 25.25, 25.25, 25.75, 25.75, 25.75, 25.75, 25.75, 25.75, 25.75, 25.75, 25.75, 25.75, 25.75, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.75),
   value= rnorm(30)
)


mask <- borders %>% 
   st_bbox() %>% 
   st_as_sfc() %>% 
   st_buffer(5) %>% 
   st_difference(borders) %>% 
   st_as_sf()


my_year <- theme_bw() + theme(panel.ontop=TRUE, panel.background=element_blank())
my_fill <- scale_fill_distiller('',palette='Spectral',
                                breaks = c(-2,0,2))

fig = ggplot() + geom_tile(data = DFgrid, aes(y=lat, x=lon, fill=value)) + my_year + my_fill +
   geom_sf(data = mask, fill = "white", color = NA) +
   coord_sf(xlim=c(44.05, 63.35), ylim=c(25.05, 39.75)) +
   borders('world', xlim=c(44.05, 63.35), ylim=c(25.05, 39.75), colour='black')+
   xlab('Longitude (°E)')+ ylab('Latitude (°N)')+
   theme(panel.grid=element_blank(),
         panel.border = element_rect(colour = "black", fill=NA,size=.7))

print(fig)

The final choice is up to you :slight_smile:

4 Likes

Thanks, I want to achieve the second option. But I got the error message when making the 'mask' file. How to solve this? Thanks.

mask <- borders %>%
st_bbox() %>%
st_as_sfc() %>%
st_buffer(5) %>%
st_difference(borders) %>%
st_as_sf()

dist is assumed to be in decimal degrees (arc_degrees).
although coordinates are longitude/latitude, st_difference assumes that they are planar
Error in UseMethod("st_as_sf") :
no applicable method for 'st_as_sf' applied to an object of class "c('sfc_POLYGON', 'sfc')"
In addition: Warning message:
In st_buffer.sfc(., 5) :
st_buffer does not correctly buffer longitude/latitude data

The messages are more like warnings than errors. They basically tell you that your map is flat, while the world is not.

At this level of detail they can be safely ignored; they should have no effect on the plot (there might be a minor inconsistency on edges if the GADM shapefile is simplified differently than the one used by ggplot2::borders ())

1 Like

There is problem, actually. When I ran the script to make the ggplot, it could not go through:

Error in geom_sf(data = mask, fill = "white", color = NA) :
object 'mask' not found

That is strange... It implies there is something with your {sf} package (or dependencies). Try updating it.
Are you on Linux by chance?

No, I'm not on Linux. I tried both on mac and windows, and got the same error. I will update first. This updating method seems not the right way.

update.packages('sf')

Thanks.

If update.packages("sf") does not help you might reinstall it - remove.packages("sf") and then install.packages("sf").

Yes, there is a problem with the package itself. For example, I tried to remove it and then install it, but it says:

Warning in install.packages :
installation of package ‘sf’ had non-zero exit status

So I suspected :frowning:

What error message did you get? Something about required library missing?

Here is the process below. I am not very aware of the problems...

> install.packages("sf")
also installing the dependency ‘classInt’


  There are binary versions available but the source versions
  are later:
         binary source needs_compilation
classInt  0.3-1  0.4-1              TRUE
sf        0.7-3  0.8-0              TRUE

Do you want to install from sources the packages which need compilation?
y/n: y
installing the source packages ‘classInt’, ‘sf’

trying URL 'https://cloud.r-project.org/src/contrib/classInt_0.4-1.tar.gz'
Content type 'application/x-gzip' length 19048 bytes (18 KB)
==================================================
downloaded 18 KB

trying URL 'https://cloud.r-project.org/src/contrib/sf_0.8-0.tar.gz'
Content type 'application/x-gzip' length 8607770 bytes (8.2 MB)
==================================================
downloaded 8.2 MB

* installing *source* package ‘classInt’ ...
** package ‘classInt’ successfully unpacked and MD5 sums checked
** libs
gfortran   -fPIC  -g -O2  -c fish1.f -o fish1.o
make: gfortran: No such file or directory
make: *** [fish1.o] Error 1
ERROR: compilation failed for package ‘classInt’
* removing ‘/Library/Frameworks/R.framework/Versions/3.4/Resources/library/classInt’
* restoring previous ‘/Library/Frameworks/R.framework/Versions/3.4/Resources/library/classInt’
Warning in install.packages :
  installation of package ‘classInt’ had non-zero exit status
* installing *source* package ‘sf’ ...
** package ‘sf’ successfully unpacked and MD5 sums checked
configure: CC: clang
configure: CXX: clang++ -std=gnu++11
checking for gdal-config... no
no
configure: error: gdal-config not found or not executable.
ERROR: configuration failed for package ‘sf’
* removing ‘/Library/Frameworks/R.framework/Versions/3.4/Resources/library/sf’
Warning in install.packages :
  installation of package ‘sf’ had non-zero exit status

The downloaded source packages are in
	‘/private/var/folders/rs/dzg78fb95339szp7hlnrrc6m0000gn/T/RtmpPnp2fc/downloaded_packages’
> library(sf)
Error in library(sf) : there is no package called ‘sf’

Try installing the binaries; you don't need the very latest features and compiling from source can be tricky.
(that is why I asked about Linux; you have to compile from source on Linux)

The package is installed, but the same problem appeared again and I could not make the 'mask' object :worried:
dist is assumed to be in decimal degrees (arc_degrees).
although coordinates are longitude/latitude, st_difference assumes that they are planar
Error in UseMethod("st_as_sf") :
no applicable method for 'st_as_sf' applied to an object of class "c('sfc_POLYGON', 'sfc')"
In addition: Warning message:
In st_buffer.sfc(., 5) :
st_buffer does not correctly buffer longitude/latitude data

The warnings are okay, but the failure of st_as_sf signals a problem.

What versions of geos, gdal and proj libraries are reported when you load {sf} package?

This may give you the information:
Linking to GEOS 3.6.1, GDAL 2.1.3, PROJ 4.9.3
Warning message:
package ‘sf’ was built under R version 3.4.4

Weird... Try this, it should work as well - it has the offending st_as_sf() eliminated (the mask does not need to be of sf class, sfc is good enough for masking).

library(tidyverse)
library(sf)

borders <- readRDS(url("https://biogeo.ucdavis.edu/data/gadm3.6/Rsf/gadm36_IRN_0_sf.rds"))

DFgrid <- data.frame(
  lon=c(59.25, 59.75, 60.25, 60.75, 61.25, 61.75, 57.25, 57.75, 58.25, 58.75, 59.25, 59.75, 60.25, 60.75, 61.25, 61.75, 62.25, 57.25, 57.75, 58.25, 58.75, 59.25, 59.75, 60.25, 60.75, 61.25, 61.75, 62.25, 62.75, 53.25),
  lat=c(25.25, 25.25, 25.25, 25.25, 25.25, 25.25, 25.75, 25.75, 25.75, 25.75, 25.75, 25.75, 25.75, 25.75, 25.75, 25.75, 25.75, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.25, 26.75),
  value= rnorm(30)
)


mask <- borders %>% 
  st_bbox() %>% 
  st_as_sfc() %>% 
  st_buffer(5) %>% 
  st_difference(borders)


my_year <- theme_bw() + theme(panel.ontop=TRUE, panel.background=element_blank())
my_fill <- scale_fill_distiller('',palette='Spectral',
                                breaks = c(-2,0,2))

fig = ggplot() + geom_tile(data = DFgrid, aes(y=lat, x=lon, fill=value)) + my_year + my_fill +
  geom_sf(data = mask, fill = "white", color = NA) +
  coord_sf(xlim=c(44.05, 63.35), ylim=c(25.05, 39.75)) +
  borders('world', xlim=c(44.05, 63.35), ylim=c(25.05, 39.75), colour='black')+
  xlab('Longitude (°E)')+ ylab('Latitude (°N)')+
  theme(panel.grid=element_blank(),
        panel.border = element_rect(colour = "black", fill=NA,size=.7))

print(fig)

Thanks a lot, it worked perfectly.
One more question is, the cropped data may be different from (has a smaller range than) the whole data, so is it possible to adapt the color bar to the cropped data. And the crop here uses the second option you gave above.

This might be a problem; if you avoid cropping on the data level and "just" mask the output at presentation level all the data features (such as range of values) are retained.

Your best option I can think of is:

  • first crop at data level via raster::mask() first, to see the range of values
  • then do the masking at ggplot2 level, setting the range of legend manually to the values found in step one

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.