I want to make a global raster map in the rounded shape (hatano projection). I have raster from ERA5 data which is in GCS coordinate system.
I applied following code:
library(raster)
library(ggplot2)
library(viridis)
library(tidyterra)
library(terra)
setwd("C:/Users/usman/Desktop/a")
r= raster("SH.tif")
p = projectRaster(r, crs = "+proj=hatano",method = "bilinear")
g = graticule(60, 45, "+proj=hatano")
plot(g, background="azure", mar=c(.2,.2,.2,4), lab.cex=0.5, col="light gray")
myplot = plot(p, add=TRUE, axes=FALSE, plg=list(shrink=.8), col=viridis(25))
ggsave("tile_plot2.png", plot=myplot, height=4, width=6.5, dpi=150)`
And my output is
However, I want this
part of the code and the reference image is taken from the following thread
R ggplot plotting map raster with rounded shape - How to remove data outside projected area?
Also, when I tried to save using a blank file is generated.
Here is a reproducible example
library(terra)
library(geodata)
library(viridis)
tavg <- worldclim_global("tavg", 10, ".")
tavg <- mean(tavg)
tavg <- project(tavg, "+proj=hatano", mask=TRUE)
g <- graticule(60, 45, "+proj=hatano")
plot(g, background="azure", mar=c(.2,.2,.2,4), lab.cex=0.5, col="light gray")
plot(tavg, add=TRUE, axes=FALSE, plg=list(shrink=.8), col=viridis(25))
If you want to save this to a png file you can do
png("test.png", 1000, 450, pointsize=24)
plot(g, background="azure", mar=c(.2,.2,.2,4), lab.cex=0.5, col="light gray")
plot(tavg, add=TRUE, axes=FALSE, plg=list(shrink=.8), col=viridis(25))
dev.off()
One thing to keep in mind is that you need to size the canvas to match the size of the map you are making.
Your question and code are not very clear. You mention ggplot2 but you do not use it. See the original post for how to do the above with ggplot2
You use raster::projectRaster where terra::project should be used. You create a graticule but you do not use it.
My take on this. For some reason ggplot2 works specially bad when labelling the axis of +proj=hatano (ggplot2 axis labels for world maps are specially messy, see this question), so I needed to place them manually at the starting point of the graticule (computed with lwgeom::st_startpoint():
library(terra)
library(tidyterra)
library(ggplot2)
library(geodata)
# For graticules, etc
library(sf)
r <- worldclim_global("tavg", 10, ".") |>
mean() |>
# Crop
project("+proj=hatano", mask = TRUE)
# Discretize for better plotting after projection
g <- st_graticule(ndiscr = 500) |> st_transform(st_crs(r))
border <- st_graticule() |>
st_bbox() |>
st_as_sfc() |>
st_transform(3857) |>
st_segmentize(500000) |>
st_transform(st_crs(r)) |>
st_cast("POLYGON")
# Get label placement,
# This is the hardest part
library(dplyr)
labels_x_init <- g %>%
filter(type == "N") %>%
mutate(lab = paste0(degree, "°"))
labels_x <- st_as_sf(st_drop_geometry(labels_x_init), lwgeom::st_startpoint(labels_x_init))
labels_y_init <- g %>%
filter(type == "E") %>%
mutate(lab = paste0(degree, "°"))
labels_y <- st_as_sf(st_drop_geometry(labels_y_init), lwgeom::st_startpoint(labels_y_init))
# Plot
ggplot() +
geom_sf(data = border, fill = "azure", color = "lightgray", linewidth = 1) +
geom_sf(data = g, color = "lightgray") +
geom_spatraster(data = r) +
scale_fill_whitebox_c(palette = "viridi") +
geom_sf_text(data = labels_x, aes(label = lab), nudge_x = -1000000, size = 3) +
geom_sf_text(data = labels_y, aes(label = lab), nudge_y = -1000000, size = 3) +
theme_void() +
labs(x = "", y = "", fill = "Temp")
Related
I would like an official source for drawing the relief over a map of Switzerland.
I downloaded https://cms.geo.admin.ch/ogd/topography/DHM25_BM_SHP.zip to use the dhm25_p.shp-File
Now using the code
aux <- st_read(dsn="dhm25_p.shp")
auxx <- as_Spatial(aux)
auxxx <- as.data.frame(auxx)
ggplot() +
# draw the relief
geom_raster(
data = auxxx,
inherit.aes = FALSE,
aes(
x = coords.x1,
y = coords.x2,
alpha = coords.x3
)
)
I'll get the error
Error in `geom_raster()`:
! Problem while converting geom to grob.
ℹ Error occurred in the 1st layer.
Caused by error:
! cannot allocate vector of size 6539542.5 Gb
A shapefile of points is generally not a good source for mapping continuous phenomena such as elevation. It is better to use raster data.
Here is a simple way to make an elevation map for Switzerland
library(terra)
library(geodata)
x <- geodata::elevation_30s("Switzerland", ".")
plot(x)
Add contour lines
v <- as.contour(x, levels=c(500,1000,3000))
lines(v)
Or show shaded relief
slope <- terrain(x, "slope", unit="radians")
aspect <- terrain(x, "aspect", unit="radians")
hill <- shade(slope, aspect, 40, 270)
plot(hill, col=gray(seq(0,1,.01)), legend=F, axes=F, mar=1)
To show relief and elevation
plot(hill, col=gray(seq(0,1,.01)), legend=F, axes=F)
plot(x, col=terrain.colors(25, alpha=.5), add=T, axes=F, legend=T)
You can do the same things with the Swiss government data from the website you point to in your (now hidden) answer. But it takes some more work if you want to remove the areas outside of Switzerland.
y <- rast("DHM200.asc")
# Assign the coordinate reference system (Landesvermessung 1903)
crs(y) <- "EPSG:21781"
# get the outline of the country and project it to the crs of the raster data
swz <- geodata::gadm("Switzerland", level=1, path=".")
pswz <- project(swz, y)
# remove values for areas outside of Switzerland
y <- mask(y, pswz)
plot(y)
lines (pswz)
And with ggplot you can use geom_spatraster
library(tidyterra)
library(ggplot2)
ggplot() + geom_spatraster(data = y)
For posterity, leveranging also on MPB_2022 and Robert, see how you can replicate the same map using ggplot2 + tidyterra (as explained in detail in https://dieghernan.github.io/202210_tidyterra-hillshade/):
library(terra)
library(geodata)
library(tidyterra)
library(ggplot2)
x <- geodata::elevation_30s("Switzerland", ".")
slope <- terrain(x, "slope", unit = "radians")
aspect <- terrain(x, "aspect", unit = "radians")
hill <- shade(slope, aspect, 40, 270)
# Hillshading, but we need a vector of colors
pal_greys <- hcl.colors(1000, "Grays")
# Get a vector of colors based on the value of shades
# Create index
hill_col <- hill %>%
# Rename layer
select(shades = 1) %>%
mutate(index_col = round(scales::rescale(shades, to = c(1, length(pal_greys))))) %>%
pull(index_col) %>%
pal_greys[.]
head(unique(hill_col), 10)
# Plot
ggplot() +
# Add our hill shade and use fill with the vector color
geom_spatraster(data = hill, fill = hill_col, maxcell = Inf, alpha = 1) +
# And add the initial raster with some alpha
geom_spatraster(data = x) +
# Add an hypstometric tint
scale_fill_hypso_tint_c(
palette = "dem_poster",
# Need alpha to show the hill
alpha = 0.5 ) +
theme_minimal() +
labs(fill="Elevation")
I am trying to plot a raster in a projected in a coordinated system which follows the curvature of the earth like most projections that are not WGS84. The problem is that the places were the globe wraps around the data should not be plotted outside the globe. I realize that ggplot cannot do a rounded/elliptical plot but how do I mask or remove automatically the data outside the globe? I have to plot more than 100 maps and I can't do this manually especially if I want to change to a different projection.
There's an answer here but it's hackish and doesn't seem to apply to every case, is there function or package that deals with this problem? I don't think R users only plot maps in WGS84?
I am attaching a file and code to quickly plot the map. I cannot use xlim because it would cut some parts of the map since the borders are not straight.
#netcdf file
https://ufile.io/fy08x33d
library(terra);library(tidyterra)
r=rast('Beck_KG_V1_present_0p5.tif')
#background map
r[r==0]=NA
ggplot() +geom_spatraster(data=r)+scale_fill_viridis_c(na.value='transparent') +coord_sf(crs=st_crs("+proj=hatano"),expand=FALSE)
With these data
library(terra)
library(tidyterra)
r1 <- rast('Beck_KG_V1_present_0p5.tif')
r <- subst(r1, 0, NA)
You can do
library(ggplot2)
p <- project(r, method="near", "+proj=hatano", mask=TRUE)
ggplot() +geom_spatraster(data=p)+scale_fill_viridis_c(na.value='transparent')
And here are two alternatives with base plot
First with your own color palette and a legend
library(viridis)
g <- graticule(60, 45, "+proj=hatano")
plot(g, background="azure", mar=c(.2,.2,.2,4), lab.cex=0.5, col="light gray")
plot(p, add=TRUE, axes=FALSE, plg=list(shrink=.8), col=viridis(25))
With the colors that came with the file:
coltab(p) <- coltab(r1)
plot(g, background="azure", mar=.5, lab.cex=0.5, col="light gray")
plot(p, add=TRUE, axes=FALSE, col=viridis(25))
I would go with one of Robert Hijman's options here, but if you want to create a mask in ggplot, you could do something like this:
library(grid)
y <- seq(0, 1, length = 100)
x <- ifelse(y < 0.5,
-cos(pi/2 * (2 * y - 1)) * 0.125 + 0.125,
-cos(pi/2 * (2 * y - 1)) * 0.175 + 0.175)
y <- c(0, y, 1, 0)
x <- c(0, x, 0, 0)
ggplot() +
geom_spatraster(data=r)+
scale_fill_viridis_c(na.value = 'transparent') +
coord_sf(crs = st_crs("+proj=hatano"), expand = FALSE) +
annotation_custom(polygonGrob(x = x, y = y,
gp = gpar(col = "white", lwd = 1))) +
annotation_custom(polygonGrob(x = 1-x, y = y,
gp = gpar(col = "white", lwd = 1)))
I am trying to plot in R a raster layer with lines/polygon objects in R and each time I fail miserably with errors. I tried to do this in base R, ggplot2 and using levelplot but can't get the right result.
Source data can be found here.
What I need to do in the plot (all in one plot) is to:
1) zoom in a certain area defined as NIG. T
2) Display raster r values on a scale with cuts intervals.
3) Plot the country boundaries(shpAfr in base R and ggplot2 or world.outlines.spin levelplot). 4) Finally, include shpWater polygon layer (with col="blue" fill and contours).
library(raster)
library(maptools)
library(rasterVis)
library(viridis)
library(sf)
library(rgdal)
library(ggplot2)
r <- raster("raster_example.tif")
crs(r) <- "+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +to wgs84=0,0,0"
NIG <- c(2,14.5,4,14)
Reg_name <- "Nigeria"
shpAfr <- readOGR(dsn="Africa.shp")
proj4string(shpAfr) # describes data’s current coordinate reference system
#st_read(system.file("shape/nc.shp", package="sf"))
# Import water polygon
shpWater <- readOGR(dsn="waterbodies_africa.shp")
shpWater.count <- nrow(shpWater#data)
shpWater$id <- 1:shpWater.count
shpWater.fort <- fortify(shpWater, region='id')
# Import Africa admin map
shpAfr <- readOGR(dsn="Africa.shp")
shpAfr.count <- nrow(shpAfr#data)
shpAfr$id <- 1:shpAfr.count
shpAfr.fort <- fortify(shpAfr, region='id')
# Set colour intervals for plotting:
cuts=seq(0,1,0.1) #set breaks
Trying in base R, my problem is I can get the water shape fill in the right colour (fill and contour should be blue). If I try to plot both wrld_simpl and shpWater as polygon() I get into even bigger troubles.
plot(r, xlim = NIG[1:2], ylim = NIG[3:4],
breaks=cuts, col = rev(plasma(11)))
lines(wrld_simpl,lwd = 1.5)
lines(shpWater, col="blue") # works but cannot fill the polygon
polygon(shpWater, col = "blue", border = "blue") # getting error here
Error in as.double(y) :
cannot coerce type 'S4' to vector of type 'double'
Ok, so now I try ggplot2, but I can't find a way to include a raster here without getting an error.
lon <- seq(r#extent#xmin,r#extent#xmax,
(r#extent#xmax-r#extent#xmin)/r#ncols)
lat <- seq(r#extent#ymin,r#extent#ymax,
(r#extent#ymax-r#extent#ymin)/r#nrows)
Plot1 <- ggplot()+
geom_polygon(aes(x = long, y = lat, group=id),
data = shpAfr.fort, color ="grey27", fill ="grey",
alpha = .4, size = .2)+
geom_raster(data = test, aes(fill=values))+ ## here it goes bad
#geom_tile(data=test_df, aes(x=x, y=y, fill=value), alpha=0.8) +
#scale_fill_viridis() +
geom_polygon(aes(x = long, y = lat, group=id),
data = shpWater.fort, color ="lightskyblue2", fill ="lightskyblue2",
size = .2)+coord_equal()+
theme_minimal()+
coord_map(xlim = Region[[3]][1:2],ylim = Region[[3]][3:4])
plot(Plot1)
Finally, I tried the levelplot and AGAIN failed.
mapTheme <- rasterTheme(region = rev(brewer.pal(10, "RdBu")))
# Get world outlines:
world.outlines <- map("world", plot=FALSE)
world.outlines.sp <- map2SpatialLines(world.outlines, proj4string = CRS("+proj=longlat"))
# Plot raster and polygon:
Plot2 <- levelplot(r,par.settings = mapTheme,pretty=TRUE,margin = F,
xlim = NIG[1:2],ylim = NIG[3:4],
col.regions=colorRampPalette(c("light blue","blue", "red")),
main=paste0("test")) + layer(sp.lines(world.outlines.sp, col = "black", lwd = 0.5))
plot(Plot2 + layer(sp.lines(world.outlines.sp, col = "black", lwd = 0.5))
#Error: Attempted to create layer with no stat.
My results so far:
1) first image does not have the polygons filled with blue
2) second image has clearly world outlines not in the right location
:
You would have probably have had answers a lot earlier if you had made a simple reprex, e.g. like this
library(raster)
r <- raster(res=1/12)
values(r) <- sample(100, ncell(r), replace=TRUE)
filename <- system.file("external/lux.shp", package="raster")
v <- shapefile(filename)
zoom in a certain area
One way to zoom is to use crop (alternatively use the ext argument in plot)
x <- crop(r, v)
Display raster r values on a scale with cuts intervals
cuts <- c(0,20,60,100)
plot(x, breaks=cuts, col=rainbow(3))
or
y <- cut(x, cuts)
Plot the country boundaries
lines(v)
Finally, include polygon layer (with col="blue" fill and contours).
plot(v[c(1,3),], col="blue", border="red", lwd=2, add=TRUE)
6 months later but I feel this question. My two thoughts are (1) I have had luck with plotting geom_sf and geom_stars together. You have to change your raster to a df before changing to a geom_stars. and (2) regardless of method, you need all datasets in the same projection - check with crs() and set all to the same with st_transform()
I didn't actually test this with your data but something like:
make raster into a df
test.df = as.data.frame (test, xy=TRUE) # Convert to data.frame, keeping the
coordinates
class(test.df)
convert to geom_stars
test.stars = st_as_stars(test.df)
try your plot
Plot1 <- ggplot()+
geom_stars(data = test, aes(fill=values))+ #need to plot raster first I think?
scale_fill_identity( name = "", breaks = cuts,labels = "")+
geom_sf(data = shpAfr.fort, color ="grey27", size = .2)+
geom_sf(data = shpWater.fort, color ="lightskyblue2", fill
="lightskyblue2", size = .2)+
theme_minimal()+
coord_sf( xlim = NIG[1:2], ylim = NIG[3:4]),expand = FALSE)
Plot1
I have a fairly simple and probably common task, plotting a raster dataset with countour lines and adding country borders together in one plot, however I did not find a solution anywhere. There are a a few hints available (such as this one), but no raster dataset is used there and I can't get it to work.
The dataset I am using is actually in netcdf format and available here (15mb in size) and contains about 40 years of gridded precipitation data.
Here is my line of code:
setwd("...netcdf Data/GPCP")
library("raster")
library("maps")
nc_brick79_17 <- brick("precip.mon.mean.nc") # load in the ncdf data as a
raster brick
newextent <- c(85, 125, -20, 20) # specify region of interest
SEA_brick <- crop(nc_brick79_17, newextent) # crop the region
day1 <- SEA_brick[[1]] # select very first day as example
colfunc<-colorRampPalette(c("white","lightblue","yellow","red","purple")) # colorscale for plotting
So it works of course when I just plot the raster data together with a map overlaid:
plot(day1, col=(colfunc(100)), interpolate=F, main="day1",legend.args=list(text='mm/hr', side=4,font=1, line=2.5, cex=1.1))
map("world", add=TRUE, lwd=0.5, interior = FALSE, col = "black")
We get this plot (Raster Plot with country borders added)
Now the code I use to generate the contour plot is the following:
filledContour(day1,zlim=c(0,20),color=colorRampPalette(c("white","lightblue","yellow","red","purple")),
xlab = "Longitude (°)", ylab = "Latitude (°)")
map("world", add=TRUE, lwd=0.5, interior = FALSE, col = "black") # add map overlay
I end up with a plot where obviously the country borders do not align and are even covering the colorbar.
Contour plot with map overlay shifted
In this last part I am trying to add the country boundaries to the contour plot, but it does not work, even though it should I assume. The map is simply not there, no error though:
filledContour(day1, zlim=c(0,20),
color.palette = colorRampPalette(c("white","lightblue","yellow","red","purple")),
xlab = "Longitude (°)", ylab = "Latitude (°)",
xlim = c(90, 120), ylim = c(-20, 20), nlevels = 25,
plot.axes = {axis(1); axis(2);
map('world', xlim = c(90, 120), ylim = c(-20, 20), add = TRUE, lwd=0.5, col = "black")})
From that line of code I get this plot.
Contour plot but no country borders added
What could I improve or is there any mistake somewhere? Thank you!
I chose to use ggplot here. I leave two maps for you. The first one is the one you created. This is a replication with ggplot. The second one is the one you could not produce. There are many things to explain. But I am afraid I do not have enough time to write all. But I left some comments in my code below. Please check this question to learn more about the second graphic. Finally, I'd like to give credit to hrbrmstr who wrote a great answer in the linked question.
library(maptools)
library(akima)
library(raster)
library(ggplot2)
# This is a data set from the maptools package
data(wrld_simpl)
# Create a data.frame object for ggplot. ggplot requires a data frame.
mymap <- fortify(wrld_simpl)
# This part is your code.
nc_brick79_17 <- brick("precip.mon.mean.nc")
newextent <- c(85, 125, -20, 20)
SEA_brick <- crop(nc_brick79_17, newextent)
day1 <- SEA_brick[[1]]
# Create a data frame with a raster object. This is a spatial class
# data frame, not a regular data frame. Then, convert it to a data frame.
spdf <- as(day1, "SpatialPixelsDataFrame")
mydf <- as.data.frame(spdf)
colnames(mydf) <- c("value", "x", "y")
# This part creates the first graphic that you drew. You draw a map.
# Then, you add tiles on it. Then, you add colors as you wish.
# Since we have a world map data set, we trim it at the end.
ggplot() +
geom_map(data = mymap, map = mymap, aes(x = long, y = lat, map_id = id), fill = "white", color = "black") +
geom_tile(data = mydf, aes(x = x, y = y, fill = value), alpha = 0.4) +
scale_fill_gradientn(colors = c("white", "lightblue", "yellow", "red", "purple")) +
scale_x_continuous(limits = c(85, 125), expand = c(0, 0)) +
scale_y_continuous(limits = c( -20, 20), expand = c(0, 0)) +
coord_equal()
ggplot version of filled.contour()
# As I mentioned above, you want to study the linked question for this part.
mydf2 <- with(mydf, interp(x = x,
y = y,
z = value,
xo = seq(min(x), max(x), length = 400),
duplicate = "mean"))
gdat <- interp2xyz(mydf2, data.frame = TRUE)
# You need to draw countries as lines here. You gotta do that after you draw
# the contours. Otherwise, you will not see the map.
ggplot(data = gdat, aes(x = x, y = y, z = z)) +
geom_tile(aes(fill = z)) +
stat_contour(aes(fill = ..level..), geom = "polygon", binwidth = 0.007) +
geom_contour(color = "white") +
geom_path(data = mymap, aes(x = long, y = lat, group = group), inherit.aes = FALSE) +
scale_x_continuous(limits = c(85, 125), expand = c(0, 0)) +
scale_y_continuous(limits = c(-20, 20), expand = c(0, 0)) +
scale_fill_gradientn(colors = c("white", "lightblue", "yellow", "red", "purple")) +
coord_equal() +
theme_bw()
I am trying to create an image showing a scatter plot and a heat map side by side. I create the scatter plot with geom_point and the heatmap with heatmap.2. I then use grid.draw to put them in the same image HOWEVER I cannot get the images to be the same size. How can I make sure they are the same height (this is important as they are ordered the same way and match each other)?
The code I have is:
grab_grob <- function(){
grid.echo()
grid.grab()
}
g1 <- ggplot(x, aes(x=VIPscore, y=reorder(metabolite, VIPscore))) + geom_point(colour="blue") + labs(y="", x="VIP score")
heatmap.2(xhm, cexRow=0.5, cexCol=1, Colv=FALSE, Rowv = FALSE, keep.dendro = FALSE, trace="none", key=FALSE, lwid = c(0.5, 0.5), col=heat.colors(ncol(xhm)))
g2 <- grab_grob()
grid.newpage()
lay <- grid.layout(nrow = 1, ncol=2)
pushViewport(viewport(layout = lay))
print(g1,vp=viewport(layout.pos.row = 1, layout.pos.col = 1))
grid.draw(editGrob(g2, vp=viewport(layout.pos.row = 1, layout.pos.col = 2, clip=TRUE)))
upViewport(1)
I have also tried the geom_tile (instead of heatmap.2) followed by grid.arrange; although the images now match in size colors are awful - they look flat across my data set.
A package called plotly might be of help here. Check out their API docs
library(plotly)
df <- data.frame(x = 1:1000,
y = rnorm(1000))
p1 <- plot_ly(df, x = x, y = y, mode = "markers")
p2 <- plot_ly(z = volcano, type = "heatmap")%>% layout(title = "Scatterplot and Heatmap Subplot")
subplot(p1, p2)
A drop-in solution could be to use the package "ComplexHeatmap".
https://bioconductor.org/packages/release/bioc/vignettes/ComplexHeatmap/inst/doc/s4.heatmap_annotation.html