Plotting a GeoTIFF raster in R - r

I am trying to plot a gridded population count GeoTIFF raster downloaded from here.
library(raster)
#----------------------------#
# Set your working directory #
#----------------------------#
setwd(dirname(rstudioapi::getActiveDocumentContext()$path)) # RStudio IDE preferred
getwd() # Path to your working directory
# Import the GeoTIFF file into R workspace
WorldPop <- raster("nga_ppp_2020_1km_Aggregated_UNAdj.tif")
WorldPop
#---------------------------#
# Data plotted in log-scale #
#---------------------------#
tempcol <- colorRampPalette(c("lightblue", "skyblue", "blue", "yellow", "orange", "red", "darkred"))
plot(log(WorldPop), main = "2020 UN-Adjusted Population Count (log-scale) \n (each grid cell is 1 km x 1 km)", col=tempcol(100), legend.width=2, legend.shrink=1, legend.args=list(text='log(Persons)', side=4, font=2, line=2.5, cex=0.8), axes=T)
#--------------------------------#
# Data plotted in absolute-scale #
#--------------------------------#
plot(WorldPop, main = "2020 UN-Adjusted Population Count (absolute-scale) \n (each grid cell is 1 km x 1 km)", col=tempcol(100), legend.width=2, legend.shrink=1, legend.args=list(text='Persons', side=4, font=2, line=2.5, cex=0.8), axes=T)
Plot 1 (log-scale)
Plot 2 (absolute-scale)
I like the Plot 1 (data in log-scale) but Plot 2 (data in absolute-scale) is not showing any variations in color. How can I make the plot of data in absolute-scale look similar to the one in log-scale?
I am open to using other packages (ggplot2 etc.) or other color palates as long as my plot can distinguish densely populated areas from rural areas. When I used a different GIS tool called Panoply my favorite color palate was something called seminf-haxby.cpt (found here). It looks something like this
I am trying to replicate that in R but the plot in absolute-scale isn't looking good. Any tips or advice for plotting tif rasters in R?

You can set breaks, something like this:
Example data
url <- "https://data.worldpop.org/GIS/Population/Global_2000_2020_1km_UNadj/2020/NGA/nga_ppp_2020_1km_Aggregated_UNadj.tif"
fname <- basename(url)
if (!file.exists(fname)) download.file(url, fname, mode="wb")
Solution
library(terra)
r <- rast(fname)
plot(r, col=rev(rainbow(10, end=0.7)), breaks=c(0, 10, 25, 50, 100, 250, 1000, 100000))
Now your second question (it is better to not ask two rather different questions at the same time).
The function below extracts the colors from the palette image in your question, and should also work for other palette images organized like yours.
getPal <- function(f) {
x <- rast(f)
u <- unique(values(x))
hex <- rgb(u[,1], u[,2], u[,3], maxColorValue = 255)
colorRampPalette(hex)
}
pal <- getPal("https://i.stack.imgur.com/E4d85.png")
par(mar=c(0,0,0,0))
barplot(rep(1, 25), col=pal(25), space=0)
An alternative way to apply breaks is to first use classify. I remove the first color (white)
x <- classify(r, c(0, 10, 25, 50, 100, 250, 1000, 100000))
plot(x, col=pal(8)[-1])
You could change the labels, for example like this
levs <- levels(x)[[1]]
levs[7] <- "> 1000"
levels(x) <- levs
To use this palette in your R code you can create it like this (remove '#FFFFFF' if you do not want to start with white)
ramp <- c('#FFFFFF', '#D0D8FB', '#BAC5F7', '#8FA1F1', '#617AEC', '#0027E0', '#1965F0', '#0C81F8', '#18AFFF', '#31BEFF', '#43CAFF', '#60E1F0', '#69EBE1', '#7BEBC8', '#8AECAE', '#ACF5A8', '#CDFFA2', '#DFF58D', '#F0EC78', '#F7D767', '#FFBD56', '#FFA044', '#EE4F4D')
pal <- colorRampPalette(ramp)

Related

Combining rasters' legends in R language

Sorry for the evidently stupid question I haven't be able to solve, even after googleing for a while. Let's suppose the following situation, where I have two rasters with overlapping but different value ranks.
library(raster)
# A couple of rasters from scratch
r2 <- r1 <- raster(nrows=10, ncols=10)
r1[] <- sample(c(0:99), 100, replace = F)
r2[] <- sample(c(50:149), 100, replace = F)
par(mfrow = c(1,2))
plot(r1)
plot(r2)
How may I create the same plot but with only one legend ranging from 0 to 149, that is, the combined rank of both rasters?
Based on the answer I shared in the previous comment, you can manually set the breaks of the legend and the colors. Then you just need to apply that color ramp to both plots. Here's the code and the plot.
# Adjust plot margins
par(mar=c(2,2,2,5))
# Set manually the breaks
breaks = seq(0,150,25)
pal <- colorRampPalette(c("white","orange","yellow","green","forestgreen"))
plot(r1, breaks=breaks, col = pal(length(breaks)-1))
plot(r2, breaks=breaks, col = pal(length(breaks)-1))

Base R Choropleth: colors aren't being applied to the map according to the order of the interval/breaks which makes the map hard to read

I created a choropleth with base R but I'm struggling with the colors. First, the colors don't follow the same order as the intervals and second, two of the intervals are using the same color, all of which makes the graph hard to read. This happens regardless of how many colors I use. It also doesn't matter whether I'm using brewer.pal or base colors.Here is a map with its respective legend illustrating the issue.
Below are the statements that I use to create the graph once data has been downloaded:
#Relevant packages:
library(dplyr)
library(RColorBrewer)
library(rgdal)
#create colors vector
pop_colors <- brewer.pal(8,"Purples")
#create breaks/intervals
pop_breaks <- c(0,20000,40000,60000,80000,100000,120000)
#apply breaks to population
cuts <- cut(cal_pop$Pop2016, pop_breaks, dig.lab = 6)
#create a vector with colors by population according to the interval they belong to:
color_breaks <- pop_colors[findInterval(cal_pop$Pop2016,vec = pop_breaks)]
Create choropleth
plot(cal_pop,col = color_breaks, main = "Calgary Population (2016)")
#create legend
legend("topleft", fill = color_breaks, legend = levels(cuts), title = "Population")
I used readOGR() command to read the shape file, which I'm linking here in case anybody is interested in taking a look at the data.
I'd appreciate any advice you could give me.
Thanks!
Your error is in this line:
color_breaks <- pop_colors[findInterval(cal_pop$Pop2016,vec = pop_breaks)]
I can't read your data file, so I'll use a built-in one from the sf package.
library(sf)
nc <- readOGR(system.file("shapes/", package="maptools"), "sids")
str(nc#data)
colors <- brewer.pal(8,"Purples")
#create breaks/intervals
sid_breaks <- c(0,2,4,6,8,10,12,20,60)
#apply breaks to population
sid_cuts <- cut(nc$SID79, sid_breaks, dig.lab = 6, include=TRUE)
#create a vector with colors by population according to the interval they belong to:
sid_colors <- colors[sid_cuts]
#Create choropleth
par(mar=c(0,0,0,0))
plot(nc, col = sid_colors)
legend("bottomleft", fill = colors, legend = levels(sid_cuts), nc=2, title = "SID (1979)", bty="n")

Changing descriptive statistics parameters in a map. R

I create a UK map representing some info by downloading an Spatial Polygons Data Frame from GADM.org and the following script.
lat<-c(51.5163,52.4847,51.4544,53.5933,51.481389,51.367778,55.953056,55.864167,51.482778)
lon<-c(-0.061389,-1.89,-2.587778,-2.296389,-3.178889,-0.07,-3.188056,-4.251667,-0.388056)
fr<-c(0.004278509,0.004111901,0.004150415,0.00421649,0.004221205,0.004191472,0.004507773,0.004314193,0.004098154)
uk<-data.frame(cbind(lat,lon,fr))
plotvar<-uk$fr
nclr<-4
plotclr <- brewer.pal(nclr,"Blues")
max.symbol.size=6
min.symbol.size=1
class <- classIntervals(plotvar, nclr, style="quantile")
colcode <- findColours(class, plotclr)
symbol.size <- ((plotvar-min(plotvar))/
(max(plotvar)-min(plotvar))*(max.symbol.size-min.symbol.size)
+min.symbol.size)
windows()
par(mai=c(0,0,0,0))
plot(UnK, col = 'lightgrey', border = 'darkgrey',xlim=c(-6,0),ylim=c(50,60)) #Unk is the map downloaded from GADM
points(uk$lon, uk$lat, col=2, pch=18)
points(uk$lon, uk$lat, pch=16, col=colcode, cex=symbol.size)
points(uk$lon, uk$lat, cex = symbol.size)
text(-120, 46.5, "Area: Frho")
legend(locator(1), legend=names(attr(colcode, "table")),
fill=attr(colcode, "palette"), cex=1, bty="n")
The following figure is the outcome of the above script.
Now, my problem is that I'm not happy with the colors and the breaks of the variable uk$fr. I need to change then in order to be able to compare this map with others, but I dont know how to do the following. My intention is to break this variable in 3 different classes like this (0-0.0125],(0.0125-0.0625],(0.0625-0.125]. And represent this classes by "Blues" and by different sizes circles. Also I want to force the legend to include these three classes.
One last question, how can I put title to the legend?
Thanks.

Adding texture to certain states when plotting maps with R

I'm getting started with maps in R, and I'm facing a problem which I'm not being able to solve.
Suppose the following script:
tmp_dir = tempdir()
url_data = "http://www.sharegeo.ac.uk/download/10672/50/English%20Government%20Office%20Network%20Regions%20(GOR).zip"
zip_file = sprintf("%s/shpfile.zip", tmp_dir)
download.file(url_data, zip_file)
unzip(zip_file, exdir = tmp_dir)
library(maptools)
gor=readShapeSpatial(sprintf('%s/Regions.shp', tmp_dir))
col=gray(gor$NUMBER/sum(gor$NUMBER))
col[5] = NA
plot(gor, col=col)
I would like a way to add a texture to the state with missing data on the "col" vector, instead of just leaving it white.
So in this case for example, I'm looking for something like that:
How can I add textures to specific parts of my plot, specially when working with maps?
I've read about functions like add.texture, but I couldn't use them in such a flexible way.
plot.SpatialPolygons() is capable of using line texture. If the argument density isn't NA, plot.SpatialPolygons() uses line texture.
: # (skip)
library(maptools)
col=gray(gor$NUMBER/sum(gor$NUMBER))
col[5] = NA
plot(gor, col=col) # It's easy to use the argument `add=T`
plot(gor[5,], add=T, density=10, angle=90, col="blue") # Left map
## Of cource, you can draw the map at once without `add=T`
col2 <- col
col2[4:5] <- c("red", "blue")
plot(gor, col=col2, density=c(rep(NA,3), 30, 10, rep(NA,4)),
angle=c(rep(NA,3), 0, 90, rep(NA,4))) # Right map

How to add continuous color legend to an R map made with maps

I'm using the R code shown below, which loads libraries maps and RColorBrewer, to create a map of the world with countries color-coded by population rank. As you can see in the image below, I'm using a green palette in which the darker the green, the larger the population.
I'd like to add a continuous color legend showing the full palette to denote that light green = small population and dark green = large population, but I can't find a way to do it via maps. Could you tell me what is the easiest way to add a continuous color legend (or color key/color scale) to my map?
# Load libraries
library(maps)
library(RColorBrewer)
# Load world data
data(world.cities)
# Calculate world population by country
world.pop = aggregate(x=world.cities$pop, by=list(world.cities$country.etc),
FUN=sum)
world.pop = setNames(world.pop, c('Country', 'Population'))
# Create a color palette
palette = colorRampPalette(brewer.pal(n=9, name='Greens'))(nrow(world.pop))
# Sort the colors in the same order as the countries' populations
palette = palette[rank(-world.pop$Population)]
# Draw a map of the world
map(database='world', fill=T, col=palette, bg='light blue')
The world map in the maps package is about 30 years old (e.g., has USSR & Yugoslavia).
Plus you have a glitch in your code that causes the overpopulated Greenland that #Jealie noticed (and India is less populated than Antarctica).
You can create a continuousish legend with a modern world using rworldmap.
library(rworldmap)
library(RColorBrewer)
#get a coarse resolution map
sPDF <- getMap()
#using your green colours
mapDevice('x11') #create a map shaped device
numCats <- 100 #set number of categories to use
palette = colorRampPalette(brewer.pal(n=9, name='Greens'))(numCats)
mapCountryData(sPDF,
nameColumnToPlot="POP_EST",
catMethod="fixedWidth",
numCats=numCats,
colourPalette=palette)
You can alter the legend adding more labels etc. by doing something like this :
mapParams <- mapCountryData(sPDF, nameColumnToPlot="POP_EST", catMethod="pretty", numCats=100, colourPalette=palette, addLegend=FALSE)
#add a modified legend using the same initial parameters as mapCountryData
do.call( addMapLegend, c( mapParams
, legendLabels="all"
, legendWidth=0.5
))
Just briefly to explore the glitch in your code. It occurs because you create a palette for the number of countries in world.cities (239) and then apply it to the number of polygons in the world database from maps (2026). So it probably gets recycled and the colours of your countries have no relation to population. The code below demonstrates the source of your problem.
#find the countries used in the maps world map
mapCountries <- unique( map('world',namesonly=TRUE) )
length(mapCountries)
#[1] 2026
#exclude those containing ':' e.g. "USA:Alaska:Baranof Island"
mapCountries2 <- mapCountries[-grep(':',mapCountries)]
length(mapCountries2)
#[1] 186
#which don't match between the map and world.cities ?
#cityCountries <- unique( world.cities$country.etc )
cityCountries <- world.pop$Country
length(cityCountries)
#[1] 239
#which countries are in the map but not in world.cities ?
mapCountries2[ is.na(match(mapCountries2,cityCountries)) ]
#includes USSR, Yugoslavia & Czechoslovakia
Within the library SDMTools there is the function legend.gradient
adding this code to the end of your code should give the desired result:
# Draw a map of the world
map(database='world', fill=T, col=palette, bg='light blue')
x = c(-20, -15, -15, -20)
y = c(0, 60, 60, 0)
legend.gradient(cbind(x = x - 150, y = y - 30),
cols = brewer.pal(n=9, name='Greens'), title = "TITLE", limits = "")
You will need to fiddle with the x & y coordinates to get the legend into the desired location however.
EDIT
The x and y coordinates also adjust the shape of the box so I changed the code so that the box shape would not change if you only alter the numbers within the legend.gradient function. Below is what this code should produce

Resources