Axes at minimum extent, no padding, in plots of raster* objects - r

Is there a way to ensure that the box around a plot matches the raster extents exactly? In the following there is a gap above and below or to the left and right of the raster depending on the device proportions:
require(raster)
r = raster()
r[]= 1
plot(r, xlim=c(xmin(r), xmax(r)), ylim=c(ymin(r), ymax(r)))
One element of the problem with raster objects is that asp=1 to ensure proper display. The following basic scatterplot has the same issue when asp=1:
plot(c(1:10), c(1:10), asp=1)
Try vectorplot(r) from the rasterVis package to see what I want the axes to look like.
EDIT:
Solutions need to play nice with SpatialPoints overlays, not showing points outside the specified raster limits:
require(raster)
require(maptools)
# Raster
r = raster()
r[]= 1
# Spatial points
x = c(-100, 0, 100)
y = c(100, 0, 100)
points = SpatialPoints(data.frame(x,y))
plot(r, xlim=c(xmin(r), xmax(r)), ylim=c(ymin(r), ymax(r)))
plot(points, add=T)

You'd probably do best to go with one of the lattice-based functions for plotting spatial raster objects provided by the raster and rasterVis packages. You discovered one of them in vectorplot(), but spplot() or levelplot() better match your needs in this case.
(The base graphics-based plot() method for "RasterLayer" objects just doesn't allow any easy way for you to set axes with the appropriate aspect ratio. For anyone interested, I go into more detail about why that's so in a section at the bottom of the post.)
As an example of the kind of plot that levelplot() produces:
require(raster)
require(rasterVis)
## Create a raster and a SpatialPoints object.
r <- raster()
r[] <- 1:ncell(r)
SP <- spsample(Spatial(bbox=bbox(r)), 10, type="random")
## Then plot them
levelplot(r, col.regions = rev(terrain.colors(255)), cuts=254, margin=FALSE) +
layer(sp.points(SP, col = "red"))
## Or use this, which produces the same plot.
# spplot(r, scales = list(draw=TRUE),
# col.regions = rev(terrain.colors(255)), cuts=254) +
# layer(sp.points(SP, col = "red"))
Either of these methods may still plot some portion of the symbol representing points that fall just outside of the plotted raster. If you want to avoid that possibility, you can just subset your SpatialPoints object to remove any points falling outside of the raster. Here's a simple function that'll do that for you:
## A function to test whether points fall within a raster's extent
inExtent <- function(SP_obj, r_obj) {
crds <- SP_obj#coord
ext <- extent(r_obj)
crds[,1] >= ext#xmin & crds[,1] <= ext#xmax &
crds[,2] >= ext#ymin & crds[,2] <= ext#ymax
}
## Remove any points in SP that don't fall within the extent of the raster 'r'
SP <- SP[inExtent(SP, r), ]
Additional weedy detail about why it's hard to make plot(r) produce snugly fitting axes
When plot is called on an object of type raster, the raster data is (ultimately) plotted using either rasterImage() or image(). Which path is followed depends on: (a) the type of device being plotted to; and (b) the value of the useRaster argument in the original plot() call.
In either case, the plotting region is set up in a way which produces axes that fill the plotting region, rather than in a way that gives them the appropriate aspect ratio.
Below, I show the chain of functions that's called on the way to this step, as well as the call that ultimately sets up the plotting region. In both cases, there appears to be no simple way to alter both the extent and the aspect ratio of the axes that are plotted.
useRaster=TRUE
## Chain of functions dispatched by `plot(r, useRaster=TRUE)`
getMethod("plot", c("RasterLayer", "missing"))
raster:::.plotraster2
raster:::.rasterImagePlot
## Call within .rasterImagePlot() that sets up the plotting region
plot(NA, NA, xlim = e[1:2], ylim = e[3:4], type = "n",
, xaxs = "i", yaxs = "i", asp = asp, ...)
## Example showing why the above call produces the 'wrong' y-axis limits
plot(c(-180,180), c(-90,90),
xlim = c(-180,180), ylim = c(-90,90), pch = 16,
asp = 1,
main = "plot(r, useRaster=TRUE) -> \nincorrect y-axis limits")
useRaster=FALSE
## Chain of functions dispatched by `plot(r, useRaster=FALSE)`
getMethod("plot", c("RasterLayer", "missing"))
raster:::.plotraster2
raster:::.imageplot
image.default
## Call within image.default() that sets up the plotting region
plot(NA, NA, xlim = xlim, ylim = ylim, type = "n", xaxs = xaxs,
yaxs = yaxs, xlab = xlab, ylab = ylab, ...)
## Example showing that the above call produces the wrong aspect ratio
plot(c(-180,180), c(-90,90),
xlim = c(-180,180), ylim = c(-90,90), pch = 16,
main = "plot(r,useRaster=FALSE) -> \nincorrect aspect ratio")

Man, I got stumped and ended up just turning the foreground color off to plot. Then you can take advantage of the fact that the raster plot method calls fields:::image.plot, which lets you just plot the legend (a second time, this time showing the ink!). This is inelegant, but worked in this case:
par("fg" = NA)
plot(r, xlim = c(xmin(r), xmax(r)), ylim = c(ymin(r), ymax(r)), axes = FALSE)
par(new = TRUE,"fg" = "black")
plot(r, xlim = c(xmin(r), xmax(r)), ylim = c(ymin(r), ymax(r)), axes = FALSE, legend.only = TRUE)
axis(1, pos = -90, xpd = TRUE)
rect(-180,-90,180,90,xpd = TRUE)
ticks <- (ymin(r):ymax(r))[(ymin(r):ymax(r)) %% 20 == 0]
segments(xmin(r),ticks,xmin(r)-5,ticks, xpd = TRUE)
text(xmin(r),ticks,ticks,xpd=TRUE,pos=2)
title("sorry, this could probably be done in some more elegant way")

This is way I solved this problem
require(raster)
r = raster()
# default for raster is 180 row by 360 cols = 64800 cells
# fill with some values to make more interesting
r[]= runif(64800, 1, 1000)
# Set margin for text
par(mar=c(2, 6, 6, 2))
# Set some controls for the raster cell colours and legend
MyBrks<-c(0,1,4,16,64,256,1E20)
MyLbls<-c("<1","<4","<16","<64","<256","<Max")
MyClrs<-c("blue","cyan","yellow","pink","purple","red")
# Plot raster without axes or box or legend
# Note xlim and ylim don't seem do much unless you want to trim x and y
plot(r,
col=MyClrs,
axes=FALSE,
box=FALSE,
legend=FALSE
)
# Set up the ranges and intervals for axes - you can get the min max
# using xmin(r) and ymax(r) and so on if you like
MyXFrm <- -180
MyXTo <- 180
MyXStp <- 60
MyYFrm <- -90
MyYTo <- 90
MyYStp <- 30
# Plot the axes
axis(1,tick=TRUE,pos=ymin(r),las=1,at=seq(MyXFrm,MyXTo ,MyXStp ))
axis(2,tick=TRUE,pos=xmin(r),las=1,at=seq(MyYFrm ,MyYTo ,MyYStp ))
# Plot the legend use xpd to plot the legend outside the plot region
par(xpd=TRUE)
legend(MyXTo ,MyYTo ,
legend=MyLbls[1:6],
col= MyClrs,
fill=Clrs[1:6],
bg=rgb(0,0,0,0.85),
cex=0.9,
text.col="white",
text.font=2,
border=NA
)
# Add some axis labels and a title
text(-220,0,"Y",font=2)
text(0,-130,"X",font=2)
text(0,120,"My Raster",font=4,cex=1.5)

I think the best (or simplest) solution is to use image():
library(raster)
# Raster
r = raster()
r[]= rnorm(ncell(r))
# Spatial points
x = c(-100, 0, 100)
y = c(100, 0, 100)
points = SpatialPoints(data.frame(x,y))
# plot
image(r)
plot(points, add=T, pch=16, cex=2)

Related

Is there a raster function to reverse the x and y axis?

I have a raster dataset that I created from iwd. I have plotted a filledContour plot but I want to reverse the x- and y-axis so that the numbers are decreasing, eliminate the white space and vertically exaggerate the y-axis. Setting the xlim and ylim as you would in ggplot or plot has not worked.
If there is no way to reverse the x- and y-axis of a raster dataset, how do I maintain the resolution of my s4 class dataset after converting to s3? For example, if I use filled.contour instead of filledContour.
Here is my code and plot, which is pretty basic because what I have tried has not produced any results:
idw.out <- gstat::idw(Z ~ 1, core2, grd, idp = 1.5)
r <- raster(idw.out[1])
r.contour <- filledContour(r)
r.contour
An example of the scale that I am looking for is below:
Cheers
I resolved my problem with the code listed below. Because I had S4 class data, I had to manually set the axis extent to coincide with the xlim and ylim. This step is not required for 'filled.contour' plots, but is for 'filledContour' plots.
interpolated grain-size data from core2
idw.out <- gstat::idw(Z ~ 1, core2, grd, idp = 1.5)
conversion to raster
r=raster(idw.out[1], layer = 1, values=TRUE)
add colour scheme and plot
b = c(0,5,10,15,20,30,40,50)
col = rev(bpy.colors(length(b)-1))
r.contour = filledContour(r, zlim=c(0,50), xlim=c(9,-4), ylim=c(5.3,0),
asp = NA, xaxs = "i", yaxs = "i", las = 1,
col=col, levels=b,
xlab="grain-size (phi)", ylab="core depth (m)", main="Core 2")

Can't limit boundaries of a plotted shapefile properly

I can't really set the boundaries of my plotted shapefile. I'm plotting the shapefile first to get nice x- and y- labels in degrees first, plotting the data afterwards. In the end, I'm plotting my shapefile over the data again. ylim is changeable, but xlim seems to be solely dependend on ylim changes, because I cant vary xlim itself. It only varies, when I change ylim without changing xlim, like as it is an aspect ratio issue.
I want to limit the x-axis between 8.5 and 11.5 degrees.
A link to the shapefile and raster in question: https://www.dropbox.com/sh/l42mty01mwtm8qc/AADqjNbGkmNwx3o9aFceGrkya?dl=0
My code:
library(rgeos)
library(rgdal)
library(raster)
Sys.setlocale(category = "LC_ALL", locale = "C") # In the case German Umlauts are a problem while reading the shapefile
map <- readOGR("C:\\Path\\NATRAUM_MR_utm32.shp")
# Exclude unnecessary regions
map <- subset(map, NATREGNR != 3)
map <- subset(map, NATREGNR != 4)
map <- subset(map, NATREGNR != 9)
map_wgs84 <- spTransform(map, crs(raster.percent.change.rcp85.1971_2005.2071_2100))
# Pixelplot
par(mar = c(3, 3, 2, 1)) # For saving pictures through a device like pdf
m <- plot(map_wgs84, axes=TRUE, xlim = c(8.5, 11.5), ylim=c(51.35, 53.15), cex = .5,
bty = "n")
# Colortable for legend
colTab <- c("#0033CC", "#3366FF", "#6699FF","#99FFFF", "#FFCC99","#FF9933", "#FF4D00","#660000")
N <- length(colTab)
breaks <- seq(-2, 2, length.out= N+1 )
plot(raster.percent.change.rcp85.1971_2005.2071_2100, col = colTab, breaks = breaks,
axis.args = list(cex.axis = 1, at = breaks, labels = breaks, mgp = c(1, 0, 0), tck = 0.1),
legend.args = list(text='Change [%]', side=4, font=2, line=1.75, cex=0.7),
add = TRUE
)
plot(map_wgs84, add = TRUE) # Plotting shapefile over data
You need to resize your plotting window. You can use par(fin=c(x,y)), or png() to set a ratio that works, and fix that in code.
This is because for maps, the correct aspect (ratio of vertical and horizontal extent) is enforced. For planar data the aspect is 1. For angular data (longitude/latitude) it varies by latitude.
With some data.
library(raster)
p <- shapefile(system.file("external/lux.shp", package="raster"))
Compare:
par(fin=c(6,6))
plot(p, axes=T, xlim=c(6,6.2), ylim=c(49.6, 49.8))
par(fin=c(4, 6))
plot(p, axes=T, xlim=c(6,6.2), ylim=c(49.6, 49.8))

R Two graphs with lines going from one to the other

I want to plot some point in a normal graph and link those points to a map displayed under it. What I would like to have basically is that (here I added manually the links):
Somehow I should use segments with pdt=T to write outside the margins, but I am not sure what mathematical transformation I need to do in order to set the right coordinates for the segment extremity that go into the map.
And I would prefere to use the traditional plot function and not ggplot2
Here the source used to draw the exemple (warning it may take time to load the open street map):
library(OpenStreetMap)
#Random point to plot in the graph
fdata=cbind.data.frame(runif(12),runif(12),c(rep("A",4),rep("B",4),rep("C",4)))
colnames(fdata)=c("x","y","city")
#random coordinate to plot in the map
cities=cbind.data.frame(runif(3,4.8,5),runif(3,50.95,51),c("A","B","C"))
colnames(cities)=c("long","lat","name")
#city to color correspondance
color=1:length(cities$name)
names(color)=cities$name
maxlat=max(cities$lat)
maxlong=max(cities$long)
minlat=min(cities$lat)
minlong=min(cities$long)
#get some open street map
map = openmap(c(lat=maxlat+0.02,long=minlong-0.04 ) ,
c(lat=minlat-0.02,long=maxlong+.04) ,
minNumTiles=9,type="osm")
longlat=openproj(map) #Change coordinate projection
par(mfrow=c(2,1),mar=c(0,5,4,6))
plot( fdata$y ~ fdata$x ,xaxt="n",ylab="Comp.2",xlab="",col=color[fdata$city],pch=20)
axis(3)
mtext(side=3,"-Comp.1",line=3)
par(mar=rep(1,4))
#plot the map
plot(longlat,removeMargin=F)
points(cities$lat ~ cities$long, col= color[cities$name],cex=1,pch=20)
text(cities$long,cities$lat-0.005,labels=cities$name)
The grid graphical system (which underlies both the lattice and ggplot2 graphics packages) is much better suited to this sort of operation than is R's base graphical system. Unfortunately, both of your plots use the base graphical system. Fortunately, though, the superb gridBase package supplies functions that allow one to translate between the two systems.
In the following (which starts with your call to par(mfrow=c(2,1),...)), I've marked the lines I added with comments indicating that they are My addition. For another, somewhat simpler example of this strategy in action, see here.
library(grid) ## <-- My addition
library(gridBase) ## <-- My addition
par(mfrow=c(2,1),mar=c(0,5,4,6))
plot(fdata$y ~ fdata$x, xaxt = "n", ylab = "Comp.2", xlab = "",
col = color[fdata$city],pch=20)
vps1 <- do.call(vpStack, baseViewports()) ## <-- My addition
axis(3)
mtext(side = 3,"-Comp.1",line=3)
par(mar = rep(1,4))
#plot the map
plot(longlat,removeMargin=F)
vps2 <- do.call(vpStack, baseViewports()) ## <-- My addition
points(cities$lat ~ cities$long, col= color[cities$name],cex=1,pch=20)
text(cities$long,cities$lat-0.005,labels=cities$name)
## My addition from here on out...
## A function that draws a line segment between two points (each a
## length two vector of x-y coordinates), the first point in the top
## plot and the second in the bottom plot.
drawBetween <- function(ptA, ptB, gp = gpar()) {
## Find coordinates of ptA in "Normalized Parent Coordinates"
pushViewport(vps1)
X1 <- convertX(unit(ptA[1],"native"), "npc")
Y1 <- convertY(unit(ptA[2],"native"), "npc")
popViewport(3)
## Find coordinates of ptB in "Normalized Parent Coordinates"
pushViewport(vps2)
X2 <- convertX(unit(ptB[1],"native"), "npc")
Y2 <- convertY(unit(ptB[2],"native"), "npc")
popViewport(3)
## Plot line between the two points
grid.move.to(x = X1, y = Y1, vp = vps1)
grid.line.to(x = X2, y = Y2, vp = vps2, gp = gp)
}
## Try the function out on one pair of points
ptA <- fdata[1, c("x", "y")]
ptB <- cities[1, c("long", "lat")]
drawBetween(ptA, ptB, gp = gpar(col = "gold"))
## Using a loop, draw lines from each point in `fdata` to its
## corresponding city in `cities`
for(i in seq_len(nrow(fdata))) {
ptA <- fdata[i, c("x", "y")]
ptB <- cities[match(fdata[i,"city"], cities$name), c("long", "lat")]
drawBetween(ptA, ptB, gp = gpar(col = color[fdata[i,"city"]]))
}
You can create a new plot area over your plots and then add the lines:
#New plot area
par(new=T, mfrow = c(1,1))
plot(0:1, type = "n", xaxt='n', ann=FALSE, axes=FALSE, frame.plot=TRUE, bty="n")
The problem of this is that you need do the mapping between yours plot and the new plot area, if you ever use the same area you can get some references (see locator) and then interpolate all the other point.
For example, in mi plot B is {1.751671, 0.1046729} and 8th point is {1.320507, 0.6892523}:
points(c(1.320507, 1.751671), c(0.6892523, 0.1046729), col = "red", type = "l")
UPDATE (Plots mapping):
X11(7, 7)
par(mfrow=c(2,1),mar=c(0,5,4,6))
plot( fdata$y ~ fdata$x ,xaxt="n",ylab="Comp.2",xlab="",col=color[fdata$city],pch=20)
axis(3)
mtext(side=3,"-Comp.1",line=3)
usr1 <- par("usr")
#plot the map
par(mar=rep(1,4))
plot(longlat,removeMargin=F)
points(cities$lat ~ cities$long, col= color[cities$name],cex=1,pch=20)
text(cities$long,cities$lat-0.005,labels=cities$name)
usr2 <- par("usr")
par(new=T, mfrow = c(1,1))
plot(0:1, type = "n", xaxt='n', ann=FALSE, axes=FALSE, frame.plot=TRUE, bty="n")
# Position of the corners (0, 0) and (1, 1) of the two graphs in the window X11(7, 7)
#ref <- locator()
ref <- list(x = c(1.09261365729382, 1.8750001444129, 1.06363637999312, 1.93636379046146),
y = c(0.501704460496285, 0.941477257177598,
-0.0335228967050026, 0.45909081740701))
fdata$x_map <- approxfun(usr1[1:2], ref$x[1:2])(fdata$x)
fdata$y_map <- approxfun(usr1[3:4], ref$y[1:2])(fdata$y)
points(fdata$y_map ~ fdata$x_map ,pch=6)
Keep in mind that the interpolation of the map must consider the projection, the linear projection can only be used with UTM coordinates.

R: Plotting a scatterplot over a filled.contour plot

I am very new to R and have made a filled.contour plot using interpolated data like the data found in Plotting contours on an irregular grid . Using some sample data from Plotting contours on an irregular grid , I made a filled.contour and simple scatterplot using the following codes
x <- datr$Lat
y <- datr$Lon
z <- datr$Rain
require(akima)
fld <- interp(x,y,z)
filled.contour(fld)
plot(x,y)
Is there a way to make the plot(x,y) and filled.contour(fld) on the same plot (overlaying)? I have tried the points(x,y), but this doesn't match the x and y axes. In Matlab, I believe I would do this with hold, but I am unsure how to do it on R.
Thanks!
You could use the arguments plot.title or plot.axes for that:
x <- 10*1:nrow(volcano)
y <- 10*1:ncol(volcano)
filled.contour(x, y, volcano, plot.title = {
points(x = 200, y = 200)
})
(via)
One way is to read the code for filled.contour, and do a
little hacking like so:
Make your figure:
filled.contour(fld)
Define these constants by copying them from the arguments list.
nlevels = 20
zlim = range(z, finite = TRUE)
las = 1
levels = pretty(zlim, nlevels)
xlim = range(x, finite = TRUE)
ylim = range(y, finite = TRUE)
xaxs = "i"
yaxs = "i"
asp = NA
Calculate these values by copying code from the function body
mar.orig <- (par.orig <- par(c("mar", "las", "mfrow")))$mar
w <- (3 + mar.orig[2L]) * par("csi") * 2.54
Set the layout by copying code from the function body
layout(matrix(c(2, 1), ncol = 2L), widths = c(1, lcm(w)))
Noteice that the figure is actually plotted after the color scale,
but we don't wnat to reverse the order of the layout because layout
actually sets the 'current' region as the last region because the
first call to plot.new will cause the current region to wrap around
to the first region. Hence, when you set the plot window and plot the points via:
plot.window(ylim=ylim,xlim=xlim)
points(x,y)
title(main='title',
sub='Sub-Title',
xlab='This is the x axis',
ylab='This is the y axis')
They overlay figure as desired.

R: change background color of plot for specific area only (based on x-values)

How do I change the background color for a plot, only for a specific area?
For example, from x=2 to x=4?
Bonus question: is it also possible for a combination of x and y coordinates? (for example from (1,2) to (3,4))?
Many thanks!
This can be achieved by thinking about the plot somewhat differently to your description. Basically, you want to draw a coloured rectangle between the desired positions on the x-axis, filling the entire y-axis limit range. This can be achieved using rect(), and note how, in the example below, I grab the user (usr) coordinates of the current plot to give me the limits on the y-axis and that we draw beyond these limits to ensure the full range is covered in the plot.
plot(1:10, 1:10, type = "n", axes = FALSE) ## no axes
lim <- par("usr")
rect(2, lim[3]-1, 4, lim[4]+1, border = "red", col = "red")
axis(1) ## add axes back
axis(2)
box() ## and the plot frame
rect() can draw a sequence of rectangles if we provide a vector of coordinates, and it can easily handle the case for the arbitrary x,y coordinates of your bonus, but for the latter it is easier to avoid mistakes if you start with a vector of X coordinates and another for the Y coordinates as below:
X <- c(1,3)
Y <- c(2,4)
plot(1:10, 1:10, type = "n", axes = FALSE) ## no axes
lim <- par("usr")
rect(X[1], Y[1], X[2], Y[2], border = "red", col = "red")
axis(1) ## add axes back
axis(2)
box() ## and the plot frame
You could just as easily have the data as you have it in the bonus:
botleft <- c(1,2)
topright <- c(3,4)
plot(1:10, 1:10, type = "n", axes = FALSE) ## no axes
lim <- par("usr")
rect(botleft[1], botleft[2], topright[1], topright[2], border = "red",
col = "red")
axis(1) ## add axes back
axis(2)
box() ## and the plot frame

Resources