R raster image distorted - r

I'm trying to create a map which includes a raster and a polygon shapefile. Since I would also like to add a legend I'm working with the layout() command. However, I found out that plotting a raster does overwrite the layout() so it is recommended to use image() instead of plot() for the Raster ("R - plotting multiple rasters using matrix layout". It works totally fine with the layout() command and the overlaying of the polygon shapefile is no problem either. But, the map is totally distorted, there are compressions in y and y direction (see Image). On the left the layers are plotted proportional, on the right the map is distorted:
Here is part of my code:
#loading shapes and raster datasets
CityMap <- raster("CityMap.tif")
RISE <- readShapeSpatial("RISE")
setwd("G:/maps")
pdf(paste("map",i,".pdf", sep=""), width=16.53, height=11.69)
#set the layout for the map
mat <- matrix(c(1,2), 1,2)
layout(mat, c(0.75, 0.25))
par(mar=c(0.5,0.5,2.5,0.5), oma=c(0,3,1,0))
#set map extent
maxxlim <- max((data$UTM_x) + 800)
maxylim <- max((data$UTM_y) + 800)
minxlim <- min((data$UTM_x) - 800)
minylim <- min((data$UTM_y) - 800)
#plot Raster
image(Stadtkarte, xlim=c(minxlim, maxxlim), ylim=c(minylim, maxylim), maxpixels = 3000000, axes=FALSE, col = gray.colors(10, start = 0.3, end = 0.9, gamma = 2.2, alpha = NULL), useRaster = TRUE, bty='n')
#plot shape
plot(RISE, xlim=c(minxlim, maxxlim), ylim=c(minylim, maxylim), col=alpha(c("green", "yellow", "orange", "red")[RISE$z_Werte_21], 0.3), border="white", lwd=0.001, add=T)
I am pretty new to R and I don't know how to solve the problem, when plotting the raster the layers do not overlap properly, when imaging the raster the map gets distorted. Is there anybody who had this problem before or who has a solution for the problem?

Related

Scale bar using RGL

is there a way I could get scale when I run a plot in RGL in R Studio for a point cloud?
I currently have a point cloud from a las file and when I plot the data it process with a different color corresponding to changing height in the point cloud. Is there a way to get a sale bar that shows the corresponding color in RGL?
Here's one way: divide the plot region into two parts, one for the plot, one for the scale. Plot your points in one region, then use bgplot3d() in the other region to plot a scale.
For example:
library(rgl) # for the plot
library(plotrix) # for the scale
x <- rnorm(1000); y <- rnorm(1000); z <- seq(-3, 3, len=1000)
open3d(windowRect = c(10, 10, 500, 500))
layout3d(matrix(1:2, 1,2), c(0.8, 0.2), 1)
plot3d(x, y, z, col=rainbow(1000)[rank(z)])
next3d()
bgplot3d({
plot.new()
color.legend(0.1, 0.1, 0.9, 0.9,
rect.col=rainbow(1000),
legend=(-3):3, gradient="y", cex = 1.5)
})
This produces
One problem with this method of drawing a scale is that it is a bitmap drawing, so if you resize the plot, it will tend to look bad. If you want one that will scale itself, you might want to investigate the plot3Drgl package.

How to reduce the space between the plot and the border for geographic maps?

I am trying to plot a bathymetry map of the the northeast US using the marmap library. The following code loads the correct extent but when I plot the map I have blank space between the border and the map either at the top/bottom or left/right of the map. This also occurs when exporting the plots. If I drag the plot viewer screen size the plot adjusts and I can remove almost all of the empty space but I will be running this script in a loop so its not practical to solve this problem this way. Because of the loop I also can't hard code any dimensions into the plot because it will change for each new extent. How can I set the border of the plot to match the extent of the bathymetry?
library(marmap)
library(maps)
atl<- getNOAA.bathy(-80.93645,-41.61417,30.2 ,60.905 ,resolution=4)
blues <- colorRampPalette(c("darkblue", "cyan"))
greys <- colorRampPalette(c(grey(0.4),grey(0.99)))
plot(atl, image = TRUE, land = TRUE, n=0,
bpal = list(c(0, max(atl), greys(100)),
c(min(atl), 0, blues(100))))
map(database= "state", col="black", fill=FALSE, add=TRUE)
text(x=state.center$x, y=state.center$y, state.abb, cex=0.5)
This behavior is caused by the asp argument of plot.bathy(). By default, it is fixed as asp = 1 to ensure that the scales on both axes are the same (one degree of longitude equals one degree of latitude). An unwelcome consequence of this default, is the white bands appearing either on the left/right sides of the graph, or on the top/bottom sides depending on the dimensions of your bathymetric map and the plotting device.
So I suppose you have 2 options:
If you don't mind having a slightly distorted perspective, you can set asp = NA in your call to plot.bathy()
If you want to have the correct aspect ratio but need to use the default size for your plotting region, then you have to download a bathymetric region that covers the whole plotting region of your active device. For instance, you could call plot.bathy() once to create a "default" plot, then, use par("usr") to determine the limits of the bathymetry needed to fill the entire plotting area. You would then download a second bathymetry with the appropriate ranges in longitude and latitude. Which is maybe not desirable.
Here is what the code would look like for the second option:
atl <- getNOAA.bathy(-80.93645, -41.61417, 30.2, 60.905, resolution = 4)
blues <- colorRampPalette(c("darkblue", "cyan"))
greys <- colorRampPalette(c(grey(0.4), grey(0.99)))
plot(atl, image = TRUE, land = TRUE, n = 0,
bpal = list(c(0, max(atl), greys(100)),
c(min(atl), 0, blues(100))))
coord <- par("usr")
atl2 <- getNOAA.bathy(coord[1], coord[2], coord[3], coord[4], res = 4)
plot(atl2, image = TRUE, land = TRUE, lwd = 0.2,
bpal = list(c(0, max(atl2), greys(100)),
c(min(atl2), 0, blues(100))))
map(database = "state", col = "black", fill = FALSE, add = TRUE)
text(x = state.center$x, y = state.center$y, state.abb, cex = 0.5)
I suppose the solution proposed by Roman Luštrik works too, but it has the inconvenience of leaving the white bands visible on both sides of the plot.
As an aside, if you have a lot of bathymetric regions to plot, you should maybe consider using the keep = TRUE argument of getNOAA.bathy() to avoid querying the NOAA servers each time you need to re-execute your code (and it is much faster to load local data than remote ones). And you could also download once and for all the global 4Go ETOPO1 and use subset.bathy() to, well, subset the bathymetry you need for each plot.
Here is a proposal using a workaround. The idea is to convert the bathy object into raster object and then make the plot using levelplot from rasterVisthat correctly fits the plotting area to the raster extent. Note that using raster allows having a defined pixel size and, therefore, a correct width/height ratio that you don't seem to have with marmap::plot method.
library(raster)
library(rasterVis)
r <- marmap::as.raster(atl)
state <- map('state', plot = FALSE)
state <- data.frame(lon = state$x, lat = state$y)
state.lab <- data.frame(lon = state.center$x, lat = state.center$y,
label = state.abb)
# you can remove the color legend by adding colorkey = FALSE in levelplot()
levelplot(r,
at = c(seq(min(atl), 0, length.out = 100),
seq(0, max(atl), length.out = 100)[-1]),
col.regions = c(blues(100), greys(100)),
margin = FALSE) +
xyplot(lat ~ lon, state, type = 'l',
col = 'black') +
xyplot(lat ~ lon, data = state.lab,
panel = function(y, x, ...) {
ltext(x = x, y = y, labels = state.lab$label, cex = 0.75)
})

Why does my map plot remain the same size (and not resize accordingly) when I expand the plot?

I have plotted two sets of points on a world map in RStudio using maps, and when I try to maximise the plot, the map remains the same size.
I feel that I am missing something.
In Rstudio:
Expanded plot:
I would like the world map to maximise to fit my screen in order to better-distribute the points but it remains the same size.
Where am I going wrong?
Or is there a better package to do this in?
My code:
library(tidyverse)
library(maps)
library(geosphere)
par(mar=c(0,0,0,0))
map('world')
points(x = geochats$origin_lon, y = geochats$origin_lat, col = "green", cex = 1, pch = 20)
points(x = geochats$end_lon, y = geochats$end_lat, col = "red", cex = 1, pch = 20)
map() uses a rather archaic method to fix the plot window and aspect ratio. It doesn't behave well after rescaling the plot window (also outside of Rstudio). Not sure about Rstudio, but I suppose you can avoid this problem by using a standard plot command:
mymap <- map("world", plot=FALSE)
plot(mymap,asp=1,type="l",frame=0,axes=0,xlab="",ylab="")

Add grid over 3D surface using persp3D (plot3D package)

I'm trying to add a grid on top of a 3D surface created with persp3D (package plot3D), however I can't sort a way of doing it without causing a deformation on the grid.
library(plot3D)
data("volcano")
volcano is a 3D matrix that can be used to create a 3D plot by simply calling:
persp3D(z=volcano)
What I intend to do is create a new grid using the dimensions of the 3D matrix and than add it to the 3D plot.
# new grid
x.seq <- seq(1, dim(volcano)[1], length = 20)
y.seq <- seq(1, dim(volcano)[2], length = 20)
# Visualize grid
plot(x=c(0,length(volcano[,1])), y=c(0,length(volcano[1,])), type='n')
abline(v=x.seq, h=y.seq)
I got close to it by subsetting the matrix volcano by the new sequences created and then plot the new 3D matrix over the original 3D surface.
# New matrix using sequences created
mtx.sub <- volcano[time.seq, freq.seq]
# Plot new matrix on top of original surface
persp3D(z=volcano)
persp3D(z=amp.sub, border="black", facets=NA, add=T, colkey=list(plot=F))
Even though the result is close to what I expected, a closer look will show that the grid is not really on top of the existing surface, it is a whole new surface that do not match the original one (which is quite obvious, given that it is a different matrix).
What I'm looking for is a way to add a 2D grid that will go over the original surface, something similar to abline, but for a 3D plot.
I had a look at plot3D documentation and searched on the web, but none of the solutions apply to persp3D().
Any thoughts on a way around this?
You can add the grid directly within the call to persp3D:
persp3D(z=volcano, border="black", lwd=0.3)
In response to your comment, you could plot at lower resolution to get wider borders, however, the surface will also be at lower resolution (see below). It would be nice to be able to plot the surface at full resolution and then have a sparser net of border lines that still matches the high-resolution surface, for example, by plotting the border lines only on every other facet, but I'm not sure how to do that without hacking persp3D (or one of the functions called by persp3D).
persp3D(z=volcano[seq(1,nrow(volcano),2), seq(1,ncol(volcano),2)],
border="black", lwd=0.4)
A work around is to use ribbon3D. It requires some more lines of code and messing around with parameters, but it does work reasonable.
require(plot3D)
### create data from 3D plotting
###
### x1, x2 a grid
### x the value of a normal distribution
x1 <- seq(-4,4,0.025)
x2 <- seq(-4,4,0.025)
mu = 0
z <- matrix(rep(0,length(x1)*length(x2)),length(x1))
for (i in 1:length(x1)) {
for(j in 1:length(x2)) {
z[i,j] <- dnorm(x1[i],mu,1)*dnorm(x2[j],mu,1)
}
}
### plot 3D
sel = 1+c(1:32)*10 ### selection of the grid lines to plot
persp3D(x1,x2,z,
border = NA, facets = TRUE, col = rgb(1,1,1,0.5),
theta = 30, phi = 30,
zlim = c(0,0.26))
ribbon3D(x1[sel],x2,z[sel,],
border = NA, facets = NA, col = 1, width = 0.02,
along = "y", space = 0.9, add = TRUE)
ribbon3D(x1[],x2[sel],z[,sel],
border = NA, facets = NA, col = 1, width = 0.02,
along = "x", space = 0.9, add = TRUE)
Example with the volcano data
require(plot3D)
### create data from 3D plotting
x <- 1:length(volcano[,1])
y <- 1:length(volcano[1,])
z <- volcano
### plot 3D
selx = seq(1,max(x),4)
sely = seq(1,max(y),4)
persp3D(x,y,z,
border = NA, facets = TRUE, lwd = 0.03,
theta = 30, phi = 30)
ribbon3D(x[selx],y,z[selx,],
border = 1, facets = 1, col = 1, width = 0.1,
along = "y", space = 0.9, add = TRUE)
ribbon3D(x[],y[sely],z[,sely],
border = 1, facets = 1, col = 1, width = 0.1,
along = "x", space = 0.9, add = TRUE)

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

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)

Resources