Plot ggplot polygons with holes with geom_polygon - r

There are questions out there about the fact that ggplot2 can't plot polygon shapes that have holes.
That is because, if the order of points is not OK, the end graph looks bad, usually with clipping/trimming lines inside the donut shape.
I have read a lot about how order matters, but I am not able to step forward.
I have a SpatialPolygonsDataFrame with 26 features (comes from raster::rasterToPolygons(dissolve=T)) and I want to plot it with ggplot.
Here's what happens -
r3.pol <- rasterToPolygons(r3, dissolve=T)
r3.df <- fortify(r3.pol)
names(r3.df) <- c('x','y','order','hole','piece','ID','group')
p <- ggplot(r3.df)
p <- p + geom_polygon(mapping=aes(x=x,y=y,group=ID), fill='red')
p <- p + coord_equal()
I see this output:
While it should be like so, with plot(r3.pol):
How can I make this work?
I tried for hours but I am not able to reorder r3.df.
Also, can the information in r3.df$hole be helpful? It is returned by the function fortify for points that are holes (I think).
Side question: how can I give you my r3.pol SpatialPolygonsDataFrame, so that you can try yourself? I remember seeing long, reproducible "dumps" of objects here, but I don't know how to do it.
I saved the polygons data frame here. Was not able to save it using dput, sorry. You can fetch it using load.

I suggest to install the package "ggpolypath" and use geom_polypath instead of geom_polygon. Works for me.

My temporary solution is: ##$% polygons, and use the raster package.
Namely:
r <- raster(x=extent(r3.pol), crs=crs(r3.pol)) # empty raster from r3.pol
res(r) <- 250 # set a decent resolution (depends on your extent)
r <- setValues(r, 1) # fill r with ones
r <- mask(r, r3.pol) # clip r with the shape polygons
And now plot it as you would do with any raster with ggplot. The rasterVis package might come helpful here, but I'm not using it, so:
rdf <- data.frame(rasterToPoints(r))
p <- ggplot(rdf) + geom_raster(mapping=aes(x=x, y=y), fill='red')
p <- p + coord_equal()
And here it goes.
Alternatively, you can create the raster with rasterize, so the raster will hold the polygons values (in my case, just an integer):
r <- raster(x=extent(r3.pol), crs=crs(r3.pol))
res(r) <- 250
r <- rasterize(r3.pol, r)
rdf <- data.frame(rasterToPoints(r))
p <- ggplot(rdf) + geom_raster(mapping=aes(x=x, y=y, fill=factor(layer)))
p <- p + coord_equal()
If someone comes up with a decent solution for geom_polygon, probably involving re-ordering of the polygons data frame, I'll be glad to consider it.

Related

how to show raster cell values with tmap?

The code below plots raster cell values. How can I do the same with package tmap?
library(terra)
pai_sim <- rast(ncols=6, nrows=6,
xmin=1, xmax=60,
ymin=1, ymax=60,
res=10)
values(pai_sim) <- 1
plot(pai_sim)
text(pai_sim)
The code below results in errors (incorrect number of dimensions and can´t find object). I have searched using different terms e.g. tmap tm_raster show cell values, but have not found a solution. This is a teaching example so I want to avoid additional steps e.g to not convert to polygon on the fly if at all possible.
library(tmap)
tm_shape(pai_sim) +
tm_raster() +
tm_text("lyr.1")
tm_shape(pai_sim) +
tm_raster() +
tm_text(lyr.1)

Trying to plot in tmap shapefile with attribute

I am trying to work with municipality data in Norway, and I'm totally new to QGIS, shapefiles and plotting this in R. I download the municipalities from here:
Administrative enheter kommuner / Administrative units municipalities
Reproducible files are here:
Joanna's github
I have downloaded QGIS, so I can open the GEOJson file there and convert it to a shapefile. I am able to do this, and read the data into R:
library(sf)
test=st_read("C:/municipality_shape.shp")
head(test)
I have on my own given the different municipalities different values/ranks that I call faktor, and I have stored this classification in a dataframe that I call df_new. I wish to merge this "classification" on to my "test" object above, and wish to plot the map with the classification attribute onto the map:
test33=merge(test, df_new[,c("Kommunekode_str","faktor")],
by=c("Kommunekode_str"), all.x=TRUE)
This works, but when I am to plot this with tmap,
library(tmap)
tmap_mode("view")
tm_shape(test33) +
tm_fill(col="faktor", alpha=0.6, n=20, palette=c("wheat3","red3")) +
tm_borders(col="#000000", lwd=0.2)
it throws this error:
Error in object[-omit, , drop = FALSE] : incorrect number of
dimensions
If I just use base plot,
plot(test33)
I get the picture:
You see I get three plots. Does this has something to do with my error above?
I think the main issue here is that the shapes you are trying to plot are too complex so tmap is struggling to load all of this data. ggplot also fails to load the polygons.
You probably don't need so much accuracy in your polygons if you are making a choropleth map so I would suggest first simplifying your polygons. In my experience the best way to do this is using the package rmapshaper:
# keep = 0.02 will keep just 2% of the points in your polygons.
test_33_simple <- rmapshaper::ms_simplify(test33, keep = 0.02)
I can now use your code to produce the following:
tmap_mode("view")
tm_shape(test_33_simple) +
tm_fill(col="faktor", alpha=0.6, n=20, palette=c("wheat3","red3")) +
tm_borders(col="#000000", lwd=0.2)
This produces an interactive map and the colour scheme is not ideal to tell differences between municipalities.
static version
Since you say in the comments that you are not sure if you want an interactive map or a static one, I will give an example with a static map and some example colour schemes.
The below uses the classInt package to set up breaks for your map. A popular break scheme is 'fisher' which uses the fisher-jenks algorithm. Make sure you research the various different options to pick one that suits your scenario:
library(ggplot2)
library(dplyr)
library(sf)
library(classInt)
breaks <- classIntervals(test_33_simple$faktor, n = 6, style = 'fisher')
#label breaks
lab_vec <- vector(length = length(breaks$brks)-1)
rounded_breaks <- round(breaks$brks,2)
lab_vec[1] <- paste0('[', rounded_breaks[1],' - ', rounded_breaks[2],']')
for(i in 2:(length(breaks$brks) - 1)){
lab_vec[i] <- paste0('(',rounded_breaks[i], ' - ', rounded_breaks[i+1], ']')
}
test_33_simple <- test_33_simple %>%
mutate(faktor_class = factor(cut(faktor, breaks$brks, include.lowest = T), labels = lab_vec))
# map
ggplot(test_33_simple) +
geom_sf(aes(fill = faktor_class), size= 0.2) +
scale_fill_viridis_d() +
theme_minimal()

Using a raster attribute from a multi-attribute raster for colour levels in a plot in R

I have a raster object with a large number of attributes, and I would like to plot the spatial data in R and colour code it by a certain attribute. I have not been able to work out how to use the information of a particular attribute to achieve this. So far I have successfully extracted the attribute of choice using factorValues(), but I cannot determine how to now incorporate this information into the plot() function. I tried using the ratify() and level() functions mentioned in the raster package documentation, but I don’t understand how the simplified online examples can be adapted for a raster with multiple attributes.
Any advice on how to achieve this would be greatly appreciated.
# read in shapefile
shp = readOGR(".", "grid")
#convert to raster
r = raster(extent(shp))
res(r) = c(1,0.5)
ra = rasterize(shp, r)
#crop raster to desired extent
rcrop = crop(ra, extent(-12, 2, 29, 51))
# extract attribute value of interest
f = factorValues(rcrop, 1:420, layer=1, att=17, append.names=FALSE)
# here there are 420 cells in the raster and I am interested in plotting values of attribute 17 of the raster (this is currently a numeric attribute, not a factor)
#extra code to set attribute as the level to use for plotting colours???
rcrop = ratify(rcrop)
rat = levels(rcrop)[[1]] #this just extras row IDs..not what I want
#…
### plot: I want to plot the grid using 7 colours (I would ideally like to specify the breaks myself)
require(RColorBrewer)
cols = brewer.pal(7,"YlGnBu")
#set breaks
brks = seq(min(minValue(rcrop)),max(maxValue(rcrop),7))
#plot
plot(rcrop, breaks=brks, col=cols, axis.arg=arg)
The following is pretty hacky (and may perform poorly for large rasters), but I'm not sure if there's a way to link col.regions to a specified attribute.
rasterVis::levelplot does a nice job of labelling colour ramps corresponding to factor rasters, and while it provides an att argument allowing you to specify which attribute you're interested in, this seems to only modify the labelling of the ramp. Raster cell values control how the colour ramp is mapped to the raster, so it seems to me that we need to modify the cell values themselves. Maybe #OscarPerpiñán will chime in here to prove me wrong :)
We can create a simple function to substitute the original cell values with whichever attribute we want:
switch_att <- function(r, att) {
r[] <- levels(r)[[1]][values(r), att]
r
}
Let's download and import a small example polygon dataset from Natural Earth:
library(rasterVis)
library(rgdal)
require(RColorBrewer)
download.file(file.path('http://www.naturalearthdata.com',
'http//www.naturalearthdata.com/download/110m/cultural',
'ne_110m_admin_0_countries.zip'),
f <- tempfile())
unzip(f, exdir=tempdir())
shp <- readOGR(tempdir(), 'ne_110m_admin_0_countries')
rasterize the vector data:
r <- rasterize(shp, raster(raster(extent(shp), res=c(1, 1))))
And create some plots with levelplot:
levelplot(switch_att(r, 'continent'), col.regions=brewer.pal(8, 'Set2')) +
layer(sp.polygons(shp, lwd=0.5))
levelplot(switch_att(r, 'economy'), par.settings=BuRdTheme) +
layer(sp.polygons(shp, lwd=0.5))
EDIT
With Oscar's update to rasterVis, the switch_att hack above is no longer necessary.
devtools::install_github('oscarperpinan/rastervis')
levelplot(r, att='continent', col.regions=brewer.pal(8, 'Set2')) +
layer(sp.polygons(shp, lwd=0.5))
will produce the same figure as the first one above.

How to plot additional raster with spplot?

I want to plot SpatialPolygonsDataFrame as a semi-transparent main object (with legend on the right), but I want to plot yet additional raster (hillshade) as a background - just to make nicer map. I would need something like:
spplot(polygons, sp.layout = list(list("raster", myRaster)))
but looking at ?spplot, it doesn't seem to be possible to specify the raster in sp.layout. I can't specify the raster as the main object, because the main object are the polygons dataFrame, whose value scale I want to plot in the legend on the right side.
How is it possible to plot an additional raster in spplot?
Here's one way to do it. There's probably a neater way to achieve it without plotting the polygon object twice, though...
library(sp)
library(rasterVis)
r <- raster(nrow=18, ncol=36)
r[] <- runif(ncell(r)) * 10
r[r > 8] <- NA
pol <- rasterToPolygons(r, function(x) x > 6)
spplot(pol) + levelplot(r) + spplot(pol)
Or alternatively:
library(latticeExtra)
spplot(pol) + spplot(r) + spplot(pol)
EDIT
As per the comment by #OscarPerpiñán, a better way to do this is:
spplot(pol) + as.layer(spplot(r), under = TRUE)

plot raster with discrete colors using rasterVis

I have a few rasters I would like to plot using gplot in the rasterVis package. I just discovered gplot (which is fantastic and so much faster than doing data.frame(rasterToPoints(r))). However, I can't get a discrete image to show. Normally if r is a raster, I'd do:
rdf=data.frame(rasterToPoints(r))
rdf$cuts=cut(rdf$value,breaks=seq(0,max(rdf$value),length.out=5))
ggplot(rdf)+geom_raster(aes(x,y,fill=cuts))
But is there a way to avoid the call to rasterToPoints? It is very slow with large rasters. I did find I could do:
cuts=cut_interval(r#data#values,n=5)
but if you set the fill to cuts it plots the integer representation of the factors.
Here is some reproducible data:
x=seq(-107,-106,.1)
y=seq(33,34,.1)
coords=expand.grid(x,y)
rdf=data.frame(coords,depth=runif(nrow(coords),0,2)))
names(rdf)=c('x','y','value')
r=rasterFromXYZ(rdf)
Thanks
gplot is a very simple wrapper around ggplot so don't expect too
much from it. Instead, you can use part of its code to build your own
solution. The main point here is to use sampleRegular to reduce the
number of points to be displayed.
library(raster)
library(ggplot2)
x <- sampleRegular(r, size=5000, asRaster = TRUE)
dat <- as.data.frame(r, xy=TRUE)
dat$cuts <- cut(dat$value,
breaks=seq(0, max(dat$value), length.out=5))
ggplot(aes(x = x, y = y), data = dat) +
geom_raster(aes(x, y, fill=cuts))
However, if you are open to plot without ggplot2 you may find useful
this other
answer.

Resources