R ggmap legend/guide issues with multiple layers - r

I have been trying to create a map of membership locations from postcodes across the UK as a project in learning R. I have achieved nearly the result I wanted, but it's proving very frustrating getting the glitches sorted. This image is my current best effort:
I still want to change:
get rid of the extraneous legend (the "0.16", "0.5" squares), which are coming from the size arg to geom_point. If I remove the size=0.16 arg the guide/legend disappears, but the geom size returns to the default too. This also happens for the "black" guide -- coming from a colour obviously -- but why?
properly clip the stat_density2d polygons, which are exhibiting undesireable behaviour when clipped (see bottom-right plot near the top)
have control over the line-width of the geom_path that includes the county boundaries: it's currently too thick (would like about 1/2 thickness shown) but all I can achieve by including 'size' values is to make the lines stupidly thick - so thick that they obscure the whole map.
The R code uses revgeocode() to find the placename closest to the centre point but I don't know how to include the annotation on the map. I would like to include it in a text-box over the North Sea (top right of UK maps), maybe with a line/arrow to the point itself. A simpler option could just be some text beneath the UK map, below the x-axis ... but I don't know how to do that. geom_rect/geom_text seem fraught in this context.
Finally, I wanted to export the map to a high-res image, but when I do that everything changes again, see:
which shows the high-res (~1700x1800px) image on the left and the Rstudio version (~660x720px) on the right. The proportions of the maps have changed and the geom_text and geom_point for the centre point are now tiny. I would be happy if the gap between the two map rows was always fairly small, too (rather than just small at high res).
Code
The basics: read list of members postcodes, join with mySociety table of postcode<>OSGB locations, convert locations to Lat/long with spTransform, calculate binhex and density layers, plot with ggmap.
The code for all this is somewhat lengthy so I have uploaded it as a Gist:
https://gist.github.com/rivimey/ee4ab39a6940c0092c35
but for reference the 'guts' of the mapping code is here:
# Get a stylised base map for the whole-of-uk maps.
map.bbox = c(left = -6.5, bottom = 49.5, right = 2, top = 58)
basemap.uk <- get_stamenmap(bb = map.bbox, zoom=calc_zoom(map.bbox), maptype="watercolor")
# Calculate the density plot - a continuous approximation.
smap.den <- stat_density2d(aes(x = lat, y = lon, fill = ..level.., alpha = ..level..),
data = membs.wgs84.df, geom = "polygon",
breaks=2/(1.5^seq(0,12,by=1)), na.rm = TRUE)
# Create a point on the map representing the centroid, and label it.
cmap.p <- geom_point(aes(x = clat, y = clon), show_guide = FALSE, data = centroid.df, alpha = 1)
cmap.t1 <- geom_text(aes(x = clat, y = clon+0.22, label = "Centre", size=0.16), data = centroid.df)
cmap.t2 <- geom_text(aes(x = clat, y = clon+0.1, label = "Centre", size=0.25), data = centroid.df)
# Create an alternative presentation, as binned hexagons, which is more true to the data.
smap.bin <- geom_hex(aes(x = lat, y = lon),
data = membs.wgs84.df, binwidth = c(0.15, 0.1), alpha = 0.7, na.rm = TRUE)
# Create a path for the county and country boundaries, to help identify map regions.
bounds <- geom_path(aes(x = long, y = lat, group = group, colour = "black"), show_guide = FALSE,
data = boundaries.subset, na.rm = TRUE)
# Create the first two actual maps: a whole-uk binned map, and a whole-uk density map.
map.bin <- ggmap(basemap.uk) + smap.bin + grad + cmap.p + cmap.t1
map.den <- ggmap(basemap.uk) + smap.den + alpha + cmap.p + cmap.t1
# Create a zoomed-in map for the south-east, to show greater detail. I would like to use this
# bbox but google maps don't respect it :(
map.lon.bbox = c(left = -1, bottom = 51, right = 1, top = 52)
# Get a google terrain map for the south-east, bbox roughly (-1.7,1.7, 50.1, 53)
basemap.lon <- get_map(location = c(0,51.8), zoom = 8, maptype="terrain", color = "bw")
# Create a new hexbin with more detail than earlier.
smap.lon.bin <- geom_hex(aes(x = lat, y = lon),
data = membs.wgs84.df, bins=26, alpha = 0.7, na.rm = TRUE)
# Noe create the last two maps: binned and density maps for London and the SE.
lonmap.bin <- ggmap(basemap.lon) + bounds + smap.lon.bin + grad + cmap.p + cmap.t2
lonmap.den <- ggmap(basemap.lon) + bounds + smap.den + alpha + cmap.p + cmap.t2
# Arrange the maps in 2x2 grid, and tell the grid code to let the first row be taller than the second.
multiplot(map.bin, lonmap.bin, map.den, lonmap.den, heights = unit( c(10,7), "null"), cols=2 )

Related

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)
})

R cran ggplot print map

I am trying to print an R map with the following function (see at bottom)
The input is a data frame that contain Longitude, Latitudes in decimal format and the errors that would be depicted as color bullet points.
This works quite okay but I want to add a new extra point, apart of what the data frame includes, that would have a different type (instead of dot, perhaps a cross or a square) and with a fixed red color, instead of following the color ramp I have for the rest of the points.
Can you please help me sort this out?
Regard
Alex
dfE<-data.frame(c(-0.1456250,-0.1442639),c(51.51476,51.51492),c(0.018878676,0.111847050))
names(dfE) <- c("Longitude", "Latitude", "Error")
stationaryPoint<-data.frame(0.1422361,51.51516)
names(stationaryPoint) <- c("Longitude", "Latitude")
data<-dfE
jet.colors <- colorRampPalette(c("#00007F", "red", "#007FFF", "yellow", "#7FFF7F", "cyan", "#FF7F00", "blue", "#7F0000"))
bbox <- c(min(data[, 1])-0.001, min(data[, 2])-0.001, max(data[, 1])+0.001, max(data[, 2])+0.001)
mp <- get_stamenmap(bbox, maptype = "toner", zoom = zoom)
ggmap(mp, darken = 0) + geom_point(aes(Longitude, Latitude, colour =Error), data = dfE, size = 3)
I tried adding the below line to add the extra point with different color and shape to the ggmap line before but I always get an error
geom_point(aes(Longitude, Latitude), data = stationaryPoint, size = 3,shape=4,color="red")
Since you didn't provide any data it is difficult to help you.
The solution to your problem could be to add another layer with geom_point:
# create new data.frame with location of point you want to plot
newdata <- data.frame(Longitude = c(40.7143528), Latitude = c(-74.0059731)) # New York
ggmap(mp, darken = 0) +
geom_point(aes(Longitude, Latitude, colour =Error), data = dfE, size = 3) +
geom_point(data = newdata, shape = 15) +
RestofyourCode

changing the units of stat_density2d

I am making a density map in R using ggmap and stat_density2d. The code looks like this:
riverside <- get_map('Riverside, IL', zoom = 14 , color = 'bw' )
RiversideMap <- ggmap(riverside, extent = 'device', legend = 'topleft')
# make the map:
RiversideMap +
stat_density2d(aes(x = lon, y = lat,
fill = ..level.. , alpha = ..level..),size = .01, bins = 16,
data = myData, geom = 'polygon') +
scale_fill_gradient(low = "yellow", high = "blue") +
scale_alpha(range = c(.0, 0.3), guide = FALSE)
The density shown in the map's color legend is normalized in stat_density2d by requiring the integral of the density over area equals 1.
In the map, the units of the x and y axes are decimal degrees. (For example, a point is specified by the coordinates lat = 41.81888 and lon = -87.84147).
For ease of interpretation, like to make two changes to the values of the density as displayed in the map legend.
First, I'd like the integral of the density to be N (the number of data points - or addresses - in the data set) rather than 1. So the values displayed in the legend need to be multiplied by N = nrow(myData).
Second, I'd like the unit of distance to be kilometers rather than decimal degrees. For the latitudes and longitudes that I am plotting, this requires dividing the values displayed in the legend by 9203.
With the default normalization of density in stat_density2d, I get these numbers in the legend: c(2000,1500,1000,500).
Taking N = 1600 and performing the above re-scalings, this becomes c(348, 261, 174, 87) (= 1600/9203 * 2000 etc). Obviously, these are not nice round numbers, so it would be even better if the legend numbers were say c(400,300,200,100) with their locations in the legend color bar adjusted accordingly.
The advantage of making these re-scalings is that the density in the map becomes easy to interpret: it is just the number of people per square km (rather than the probability density of people per square degree).
Is there an easy way to do this? I am new to ggmap and ggplot2. Thanks in advance.
In brief, use:
scale_fill_continuous(labels = scales::unit_format(unit = "k", scale = 1e-3))
This link is great help for managing scales, axes and labels: https://ggplot2-book.org/scales.html

Can you plot a table onto a ggmap similar to annotation_custom method for non- Cartesian coordinates

I have been playing around with ggplot2 a bunch and found Adding table within the plotting region of a ggplot in r
I was wondering is there any method for this for plotting using non cartesian coordinates, eg if map coordinates were used for the positioning of the table. I had some maps and thought it would be cool if they could have their corresponding data in a table for points to show more detail.
If anyone knows a work around for annotation_custom for non cartesian coordinates it would be greatly appreciated.
EDIT:Here is a image of what my map looks like, I was just thinking is there another way to plot the table on the left side of this.
EDIT: here is what Im attempting to do
EDIT: Here is the basic code structure for the plot
library(ggplot2)
library(ggmap)
plotdata <- read.csv("WellSummary_All_SE_NRM.csv", header = T)
plotdata <- na.omit(plotdata)
plotdata <- plotdata[1:20, c("Unit_No","neg_decimal_lat", "decimal_long", "max_drill_depth", "max_drill_date")]
map.plot<- get_map(location = c(min(plotdata$decimal_long),
min(plotdata$neg_decimal_lat),
max(plotdata$decimal_long),
max(plotdata$neg_decimal_lat)),
maptype ="hybrid",source = "google", zoom=8)
theme_set(theme_bw(base_size = 8))
colormap <- c("darkblue","blue","lightblue", "green", "yellow", "orange","darkorange", "red", "darkred")
myBreaks <- c(0,2, 10, 50, 250, 1250, 2000, 2500)
static.map <- ggmap(map.plot) %+% plotdata +
aes(x = decimal_long,
y = neg_decimal_lat,
z= max_drill_depth)+
stat_summary2d(fun = median, binwidth = c(.03, .03),alpha = 0.7) +
scale_fill_gradientn(name = "depth", colours= colormap, breaks=myBreaks,labels = format(myBreaks),
limits= c(0,2600), space = "Lab") +
labs(x = "Longitude",y = "Latitude")+
geom_text(aes(label=Unit_No),hjust=0, vjust=0,size=2,
position = position_dodge(width=0.9), angle = 45)+
coord_map()
#Creates image of the plot in file to Working Directory
filename=paste("2dmap",".png", sep="")
cat("\t",filename,"file created, saving...\n")
print(static.map)
cat("\tpassed mapping, file now being made\n")
ggsave(filename=filename,
plot = static.map,
scale = 1,
width = 6, height = 4,
dpi = 300)
I will try to upload the data today, cheers for some of the pointers already!
I have uploaded the data, dont worry about the positioning of the gradient values and text tags as I can fix them later I will also link the current ggmap code but I am using a very large loop for the data to be sorted.
https://drive.google.com/file/d/0B8qOIJ-nPp9rM1U1dkEzMUM0Znc/edit?usp=sharing
try this,
library(gridExtra)
grid.arrange(tableGrob(head(iris)), qplot(1,1), ncol=2)
annotation_custom wouldn't help, it's meant for adding things inside the plot panel, not to the side.

How to have widescreen resolution with ggmap

I have a data like this:
YEAR-STORM-DATETIME-NORTH-WEST-PRESSURE-WIND-SPEED-TRACKDATE
2011-arlene-6/28/2011 6:00-19.9-92.8-1007-30-NA-6/28/2011
2011-arlene-6/28/2011 12:00-20.3-93.1-1006-35-4-6/28/2011
2011-arlene-6/28/2011 18:00-20.7-93.5-1006-40-5-6/28/2011
so on..
I am new to R and I am plotting a density-plot over ggmap. I am also using shiny R to display them in website. The problem is the output are all non-widescreen (squared) maps. I want to have a rectangular map, like google maps provided by Openlayers or KML.
My code is :
library(ggplot2)
library(ggmap)
mydata <- read.csv("C:/R Data/Analytics/dMetrics.csv")
slice_year <- mydata[mydata$YEAR=='2009',]
map <- get_map(c(lon = -55.3632715, lat = 31.7632836), zoom = 3,
source = 'google', maptype = c("terrain"), messaging = FALSE,
color = 'color')
world <- ggmap(map) #extent = 'device'
world <- world +
stat_density2d(data = slice_year,
aes(x = WEST, y = NORTH, fill = ..level.., alpha = ..level..),
show_guide = FALSE, geom = "polygon", na.rm = TRUE) +
scale_fill_gradient(name = "Density", low = "maroon", high = "yellow",
guide = 'colorbar')
world
Please guide me through to create a widescreen resolution map, possibly a high resolution.
To save the image as widescreen, add this to the end: ggsave(file="map.pdf", width=8, height=4.5)
To open a widescreen window, add this before calling world: windows(800,450)
Edit
It looks like ggmap just doesn't support non-sqaure aspect ratios.
The documentation claims that a bounding box can be passed into the location property, but it appears to just ignore it.
scale <- 5
ratio <- 16/9
size <- c(ratio, 1) * scale
latlongCenter <- c(0, 45)
latlongBox <- c(latlongCenter - size/2, latlongCenter + size/2)
map <- get_map(location = latlongBox)
ggmap(map)
One solution would be to produce a bigger map, and then crop the piece you don't need.
But I'm still trying to figure how to produce a big, high-resolution map (the map I'm getting is 1280x1280 - that's enough for most needs, but not to print a big map). I think there's no function for that, or bots could occupy all of Google's bandwidth. A simpler solution is to get many square maps and assemble them together, but then you'll have the Google logo in all of them.
I think the only way to do that is to produce the small square maps and cut the bottom, where the logo and copyright information are. To add their logo again in the borders of the final map (cutting them differently) would give even more work...

Resources