ggplot2mappingnetcdf

Make a map from a netCDF file in R using original grid size


I would like to map some data from a netCDF file in R using ggplot.

I've been looking around and haven't found a viable answer. You can see, for example, here.

I want to make a map using a netCDF file that retains the anticipated gridded data from the source (~1x1 degree grids for global data). However, I'm running into an issue plotting using ggplot, where I end up needing to use geom_point() to plot, which makes the sizing of the grids incorrect.

I'll take the data from my linked example above. Though I'm going to adjust the code a bit because I've been using the ncdf4 package.

Below is the adaptation of the linked example, with the file "air.1999.nc" downloaded from the same source here.

#Example
library(tidyverse)
library(ncdf4)
library(here)
library(fields)

#Grab the values
temp.nc <- nc_open(here("air.1999.nc"))
temp <- ncvar_get(temp.nc,"air")
ilon <- ncvar_get(temp.nc, 'lon')
ilat <- ncvar_get(temp.nc, 'lat')

#Just pull out a slice for a visual
sample <- temp[,,1,1]

#Check dimensions
dim(sample)

#Take a look
image.plot(sample)

enter image description here

Ok so that generally works and you can see the data in the anticipated grid form. You could superimpose a map or something similar to what the answer had from the linked example.

However, I want to make the maps look a bit different and play around with the visual. So I want to use ggplot.

So I found an interesting solution on YouTube. That code is below with the link to the YouTube channel where I got the code that I have modified below.

#Function from: https://www.youtube.com/watch?v=OqcYTdSKNYg&t=495s
mapCDF <- function(lat, lon, idat) 
{
  
  #Create a df to plot
  expand.grid(lon, lat) %>%
    dplyr::rename(lon = Var1, lat = Var2) %>%
    mutate(lon = ifelse(lon > 180, -(360 - lon), lon),
           idat = as.vector(idat)) %>% 
    
    #Start plot, feeding in the previous df from expand.grid
    ggplot()+
    geom_point(aes(x = lon, y = lat, color = idat), shape = 'square')+
    scale_color_continuous(name = 'Test Map', na.value = 'transparent')+
  
    #Add the map layer
    borders('world', colour = 'grey15', fill = 'NA')+
    
    #Basic theme settings for background color, text size etc. 
    theme_bw()+
    
    #Map settings, adjusting the lat/long and projection
    coord_map(xlim = c(-170, 170), ylim = c(39, 75), projection = 'orthographic')+ 

    #Remove labels
    ggtitle('')+
    xlab('')+
    ylab('')
  
}

mapCDF(ilat, ilon, sample)

enter image description here

I think you can probably see the issue. I've adjusted the shape of the points from geom_point() to "squares" to mimic the grid, but they are sized and spaced incorrectly. They should fill in the map completely, but they don't. From the default size, I get a concentric ring effect. If you adjust the size to, say, size = 5, it fills in a bit more, but that's not what I'm looking for.

Probably, geom_point is not the correct function to use, but I've had no luck using geom_raster or anything similar, as in this example.

An additional note: I used an orthographic projection because the actual data that I'm using is only for the Northern Hemisphere.

Any advice is greatly appreciated.


Solution

  • You are correct that geom_point isn't well-suited for this purpose. geom_raster, on the other hand, does not work with projections that are not cartesian (because it draws all points as the same size and shape), so won't work in your case. geom_tile does what I think you are looking for. Replace this in your code:

    geom_point(aes(x = lon, y = lat, color = idat), shape = 'square')+
    scale_color_continuous(name = 'Test Map', na.value = 'transparent')+
    

    with this:

    geom_tile(
      aes(x = lon, y = lat, fill = idat, color = idat),
      linewidth = 1
    ) +
    scale_fill_continuous(name = 'Test Map', na.value = 'transparent') +
    scale_color_continuous(guide = "none", na.value = 'transparent') +
    

    enter image description here

    We've changed scale_color_continuous to scale_fill_continuous since fill is the color of the tiles. The color = idat aesthetic and linewidth = 1 argument of geom_tile are to draw borders over the few pixels of whitespace that otherwise show up between tiles. For these borders to match the tiles, we need to add a scale_color_continuous that matches the scale_fill_continuous, but has no legend (guide = "none").