How to properly plot projected gridded data in ggplot2? How to properly plot projected gridded data in ggplot2? r r

How to properly plot projected gridded data in ggplot2?


After digging a bit more, it seems that your model is based on a 50Km regular grid in "lambert conical" projection. However, the coordinates you have in the netcdf are lat-lon WGS84 coordinates of the center of the "cells".

Given this, a simpler approach is to reconstruct the cells in the original projection and then plot the polygons after converting to an sf object, eventually after reprojection. Something like this should work (notice that you'll need to install the devel version of ggplot2 from github for it to work):

load(url('https://files.fm/down.php?i=kew5pxw7&n=loadme.Rdata'))library(raster)library(sf)library(tidyverse)library(maps)devtools::install_github("hadley/ggplot2")#   ____________________________________________________________________________#   Transform original data to a SpatialPointsDataFrame in 4326 proj        ####coords = data.frame(lat = values(s[[2]]), lon = values(s[[3]]))spPoints <- SpatialPointsDataFrame(coords,                                    data = data.frame(data = values(s[[1]])),                                    proj4string = CRS("+init=epsg:4326"))#   ____________________________________________________________________________#   Convert back the lat-lon coordinates of the points to the original      ####   projection of the model (lcc), then convert the points to polygons in lcc#   projection and convert to an `sf` object to facilitate plottingorig_grid = spTransform(spPoints, projection(s))polys = as(SpatialPixelsDataFrame(orig_grid, orig_grid@data, tolerance = 0.149842),"SpatialPolygonsDataFrame")polys_sf = as(polys, "sf")points_sf = as(orig_grid, "sf")#   ____________________________________________________________________________#   Plot using ggplot - note that now you can reproject on the fly to any    ####   projection using `coord_sf`# Plot in original  projection (note that in this case the cells are squared): my_theme <- theme_bw() + theme(panel.ontop=TRUE, panel.background=element_blank())ggplot(polys_sf) +  geom_sf(aes(fill = data)) +   scale_fill_distiller(palette='Spectral') +  ggtitle("Precipitations") +  coord_sf() +   my_theme 

enter image description here

# Now Plot in WGS84 latlon projection and add borders: ggplot(polys_sf) +  geom_sf(aes(fill = data)) +   scale_fill_distiller(palette='Spectral') +  ggtitle("Precipitations")  +  borders('world', colour='black')+  coord_sf(crs = st_crs(4326), xlim = c(-60, 80), ylim = c(15, 75))+  my_theme 

enter image description here

To add borders also wen plotting in the original projection, however, you'll have to provide the loygon boundaries as an sf object. Borrowing from here:

Converting a "map" object to a "SpatialPolygon" object

Something like this will work:

library(maptools)borders  <- map("world", fill = T, plot = F)IDs      <- seq(1,1627,1)borders  <- map2SpatialPolygons(borders, IDs=borders$names,                                proj4string=CRS("+proj=longlat +datum=WGS84")) %>%             as("sf")ggplot(polys_sf) +  geom_sf(aes(fill = data), color = "transparent") +   geom_sf(data = borders, fill = "transparent", color = "black") +  scale_fill_distiller(palette='Spectral') +  ggtitle("Precipitations") +  coord_sf(crs = st_crs(projection(s)),            xlim = st_bbox(polys_sf)[c(1,3)],           ylim = st_bbox(polys_sf)[c(2,4)]) +  my_theme

enter image description here

As a sidenote, now that we "recovered" the correct spatial reference, it is also possible to build a correct raster dataset. For example:

r <- s[[1]]extent(r) <- extent(orig_grid) + 50000

will give you a proper raster in r:

rclass       : RasterLayer band        : 1  (of  36  bands)dimensions  : 125, 125, 15625  (nrow, ncol, ncell)resolution  : 50000, 50000  (x, y)extent      : -3150000, 3100000, -3150000, 3100000  (xmin, xmax, ymin, ymax)coord. ref. : +proj=lcc +lat_1=30. +lat_2=65. +lat_0=48. +lon_0=9.75 +x_0=-25000. +y_0=-25000. +ellps=sphere +a=6371229. +b=6371229. +units=m +no_defs data source : in memorynames       : Total.precipitation.flux values      : 0, 0.0002373317  (min, max)z-value     : 1998-01-16 10:30:00 zvar        : pr 

See that now the resolution is 50Km, and the extent is in metric coordinates. You could thus plot/work with r using functions for raster data, such as:

library(rasterVis)gplot(r) + geom_tile(aes(fill = value)) +   scale_fill_distiller(palette="Spectral", na.value = "transparent") +  my_theme  library(mapview)mapview(r, legend = TRUE)  


"Zooming in" to see the points that are the cell centres. You can see they are in rectangular grid.

Wales Points

I calculated the vertices of the polygons as follows.

  • Convert 125x125 latitudes & longitudes to a matrix

  • Initialize 126x126 matrix for cell vertices (corners).

  • Calculate cell vertices as being mean position of each 2x2 group ofpoints.

  • Add cell vertices for edges & corners (assume cell width & height isequal to width & height of adjacent cells).

  • Generate data.frame with each cell having four vertices, so we end upwith 4x125x125 rows.

Code becomes

 pr <- s[[1]]lon <- s[[2]]lat <- s[[3]]#Lets get the data into data.frames#Gridded in model units:#Projected points:lat_m <- as.matrix(lat)lon_m <- as.matrix(lon)pr_m <- as.matrix(pr)#Initialize emptry matrix for verticeslat_mv <- matrix(,nrow = 126,ncol = 126)lon_mv <- matrix(,nrow = 126,ncol = 126)#Calculate centre of each set of (2x2) points to use as verticeslat_mv[2:125,2:125] <- (lat_m[1:124,1:124] + lat_m[2:125,1:124] + lat_m[2:125,2:125] + lat_m[1:124,2:125])/4lon_mv[2:125,2:125] <- (lon_m[1:124,1:124] + lon_m[2:125,1:124] + lon_m[2:125,2:125] + lon_m[1:124,2:125])/4#Top edgelat_mv[1,2:125] <- lat_mv[2,2:125] - (lat_mv[3,2:125] - lat_mv[2,2:125])lon_mv[1,2:125] <- lon_mv[2,2:125] - (lon_mv[3,2:125] - lon_mv[2,2:125])#Bottom Edgelat_mv[126,2:125] <- lat_mv[125,2:125] + (lat_mv[125,2:125] - lat_mv[124,2:125])lon_mv[126,2:125] <- lon_mv[125,2:125] + (lon_mv[125,2:125] - lon_mv[124,2:125])#Left Edgelat_mv[2:125,1] <- lat_mv[2:125,2] + (lat_mv[2:125,2] - lat_mv[2:125,3])lon_mv[2:125,1] <- lon_mv[2:125,2] + (lon_mv[2:125,2] - lon_mv[2:125,3])#Right Edgelat_mv[2:125,126] <- lat_mv[2:125,125] + (lat_mv[2:125,125] - lat_mv[2:125,124])lon_mv[2:125,126] <- lon_mv[2:125,125] + (lon_mv[2:125,125] - lon_mv[2:125,124])#Cornerslat_mv[c(1,126),1] <- lat_mv[c(1,126),2] + (lat_mv[c(1,126),2] - lat_mv[c(1,126),3])lon_mv[c(1,126),1] <- lon_mv[c(1,126),2] + (lon_mv[c(1,126),2] - lon_mv[c(1,126),3])lat_mv[c(1,126),126] <- lat_mv[c(1,126),125] + (lat_mv[c(1,126),125] - lat_mv[c(1,126),124])lon_mv[c(1,126),126] <- lon_mv[c(1,126),125] + (lon_mv[c(1,126),125] - lon_mv[c(1,126),124])pr_df_orig <- data.frame(lat=lat[], lon=lon[], pr=pr[])pr_df <- data.frame(lat=as.vector(lat_mv[1:125,1:125]), lon=as.vector(lon_mv[1:125,1:125]), pr=as.vector(pr_m))pr_df$id <- row.names(pr_df)pr_df <- rbind(pr_df,               data.frame(lat=as.vector(lat_mv[1:125,2:126]), lon=as.vector(lon_mv[1:125,2:126]), pr = pr_df$pr, id = pr_df$id),               data.frame(lat=as.vector(lat_mv[2:126,2:126]), lon=as.vector(lon_mv[2:126,2:126]), pr = pr_df$pr, id = pr_df$id),               data.frame(lat=as.vector(lat_mv[2:126,1:125]), lon=as.vector(lon_mv[2:126,1:125]), pr = pr_df$pr, id= pr_df$id))

Same zoomed image with polygon cells

Wales Cells

Labels Fix

ewbrks <- seq(-180,180,20)nsbrks <- seq(-90,90,10)ewlbls <- unlist(lapply(ewbrks, function(x) ifelse(x < 0, paste(abs(x), "°W"), ifelse(x > 0, paste(abs(x), "°E"),x))))nslbls <- unlist(lapply(nsbrks, function(x) ifelse(x < 0, paste(abs(x), "°S"), ifelse(x > 0, paste(abs(x), "°N"),x))))

Replacing geom_tile & geom_point with geom_polygon

ggplot(pr_df, aes(y=lat, x=lon, fill=pr, group = id)) + geom_polygon() +    borders('world', xlim=range(pr_df$lon), ylim=range(pr_df$lat), colour='black') + my_theme + my_fill +    coord_quickmap(xlim=range(pr_df$lon), ylim=range(pr_df$lat)) +scale_x_continuous(breaks = ewbrks, labels = ewlbls, expand = c(0, 0)) +scale_y_continuous(breaks = nsbrks, labels = nslbls, expand = c(0, 0)) + labs(x = "Longitude", y = "Latitude")

enter image description here

ggplot(pr_df, aes(y=lat, x=lon, fill=pr, group = id)) + geom_polygon() +    borders('world', xlim=range(pr_df$lon), ylim=range(pr_df$lat), colour='black') + my_theme + my_fill +    coord_map('lambert', lat0=30, lat1=65, xlim=c(-20, 39), ylim=c(19, 75)) +scale_x_continuous(breaks = ewbrks, labels = ewlbls, expand = c(0, 0)) +scale_y_continuous(breaks = nsbrks, labels = nslbls, expand = c(0, 0)) + labs(x = "Longitude", y = "Latitude")

enter image description here

Edit - work around for axis ticks

I've been unable to find any quick solution for the grid lines and labels for the latitude. There is probably an R package out there somewhere which will solve your problem with far less code!

Manually setting nsbreaks required and creating data.frame

ewbrks <- seq(-180,180,20)nsbrks <- c(20,30,40,50,60,70)nsbrks_posn <- c(-16,-17,-16,-15,-14.5,-13)ewlbls <- unlist(lapply(ewbrks, function(x) ifelse(x < 0, paste0(abs(x), "° W"), ifelse(x > 0, paste0(abs(x), "° E"),x))))nslbls <- unlist(lapply(nsbrks, function(x) ifelse(x < 0, paste0(abs(x), "° S"), ifelse(x > 0, paste0(abs(x), "° N"),x))))latsdf <- data.frame(lon = rep(c(-100,100),length(nsbrks)), lat = rep(nsbrks, each =2), label = rep(nslbls, each =2), posn = rep(nsbrks_posn, each =2))

Remove the y axis tick labels and corresponding gridlines and then add back in "manually" using geom_line and geom_text

ggplot(pr_df, aes(y=lat, x=lon, fill=pr, group = id)) + geom_polygon() +    borders('world', xlim=range(pr_df$lon), ylim=range(pr_df$lat), colour='black') + my_theme + my_fill +    coord_map('lambert', lat0=30, lat1=65, xlim=c(-20, 40), ylim=c(19, 75)) +    scale_x_continuous(breaks = ewbrks, labels = ewlbls, expand = c(0, 0)) +    scale_y_continuous(expand = c(0, 0), breaks = NULL) +     geom_line(data = latsdf, aes(x=lon, y=lat, group = lat), colour = "white", size = 0.5, inherit.aes = FALSE) +    geom_text(data = latsdf, aes(x = posn, y = (lat-1), label = label), angle = -13, size = 4, inherit.aes = FALSE) +    labs(x = "Longitude", y = "Latitude") +    theme( axis.text.y=element_blank(),axis.ticks.y=element_blank())

enter image description here