###############################################################################
## Create a world cities map in Eckert IV projection with labeled graticules using ggplot.
## Repel overlapping text labels with ggrepel.
## Eckert IV is a nice looking equal-area projection :)
## https://upload.wikimedia.org/wikipedia/commons/c/c5/Ecker_IV_projection_SW.jpg
###############################################################################
library(rgdal) # for spTransform() & project()
## Warning: package 'rgdal' was built under R version 4.0.5
## Loading required package: sp
## rgdal: version: 1.5-23, (SVN revision 1121)
## Geospatial Data Abstraction Library extensions to R successfully loaded
## Loaded GDAL runtime: GDAL 3.2.1, released 2020/12/29
## Path to GDAL shared files: C:/Users/liyix/Documents/R/win-library/4.0/rgdal/gdal
## GDAL binary built with GEOS: TRUE
## Loaded PROJ runtime: Rel. 7.2.1, January 1st, 2021, [PJ_VERSION: 721]
## Path to PROJ shared files: C:/Users/liyix/Documents/R/win-library/4.0/rgdal/proj
## PROJ CDN enabled: FALSE
## Linking to sp version:1.4-5
## To mute warnings of possible GDAL/OSR exportToProj4() degradation,
## use options("rgdal_show_exportToProj4_warnings"="none") before loading rgdal.
## Overwritten PROJ_LIB was C:/Users/liyix/Documents/R/win-library/4.0/rgdal/proj
library(ggplot2) # for ggplot()
library(ggrepel) # for geom_text_repel() - repel overlapping text labels
library(data.table)
## Warning: package 'data.table' was built under R version 4.0.5
# =============================================================================
# Load ready to use data from GitHub
# =============================================================================
load(url("https://github.com/valentinitnelav/RandomScripts/blob/master/NaturalEarth.RData?raw=true"))
# This will load 6 objects:
# xbl.X & lbl.Y are two data.frames that contain labels for graticule lines
# They can be created with the code at this link:
# https://gist.github.com/valentinitnelav/8992f09b4c7e206d39d00e813d2bddb1
# NE_box is a SpatialPolygonsDataFrame object and represents a bounding box for Earth
# NE_countries is a SpatialPolygonsDataFrame object representing countries
# NE_graticules is a SpatialLinesDataFrame object that represents 10 dg latitude lines and 20 dg longitude lines
# (for creating graticules check also the graticule package or gridlines fun. from sp package)
# (or check this gist: https://gist.github.com/valentinitnelav/a7871128d58097e9d227f7a04e00134f)
# NE_places - SpatialPointsDataFrame with city and town points
# NOTE: data downloaded from http://www.naturalearthdata.com/
# here is a sample script how to download, unzip and read such shapefiles:
# https://gist.github.com/valentinitnelav/a415f3fbfd90f72ea06b5411fb16df16
# =============================================================================
# Project from long-lat to Eckert IV projection
# =============================================================================
# spTransform() is used for shapefiles and project() in the case of data frame
# for more PROJ.4 strings check the followings
# http://proj4.org/projections/index.html
# https://epsg.io/
# __ give the PORJ.4 string for Eckert IV projection
PROJ <- "+proj=eck4 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs"
# or use the short form "+proj=eck4"
# __ project the shapefiles
NE_countries.prj <- spTransform(NE_countries, CRSobj = PROJ)
## Warning in spTransform(xSP, CRSobj, ...): NULL source CRS comment, falling back
## to PROJ string
## Warning in wkt(obj): CRS object has no comment
NE_graticules.prj <- spTransform(NE_graticules, CRSobj = PROJ)
## Warning in spTransform(xSP, CRSobj, ...): NULL source CRS comment, falling back
## to PROJ string
## Warning in spTransform(xSP, CRSobj, ...): CRS object has no comment
NE_box.prj <- spTransform(NE_box, CRSobj = PROJ)
## Warning in spTransform(xSP, CRSobj, ...): NULL source CRS comment, falling back
## to PROJ string
## Warning in spTransform(xSP, CRSobj, ...): CRS object has no comment
# __ project long-lat coordinates columns for data frames
# (two extra columns with projected XY are created)
prj.coord <- project(cbind(lbl.Y$lon, lbl.Y$lat), proj = PROJ)
lbl.Y.prj <- cbind(prj.coord, lbl.Y)
names(lbl.Y.prj)[1:2] <- c("X.prj","Y.prj")
prj.coord <- project(cbind(lbl.X$lon, lbl.X$lat), proj = PROJ)
lbl.X.prj <- cbind(prj.coord, lbl.X)
names(lbl.X.prj)[1:2] <- c("X.prj","Y.prj")
# before projecting, transform NE_places to data frame to use it inside ggplot()
NE_places.df <- cbind(NE_places@coords, NE_places@data)
names(NE_places.df)[1:2] <- c("lon", "lat")
prj.coord <- project(cbind(NE_places.df$lon, NE_places.df$lat), proj = PROJ)
NE_places.df.prj <- cbind(prj.coord, NE_places.df)
names(NE_places.df.prj)[1:2] <- c("X.prj","Y.prj")
# =============================================================================
# Prepare the point-places table for plotting
# =============================================================================
# add some helpful columns
# transform to data.table (easier to work with)
NE_places.dt.prj <- data.table(NE_places.df.prj)
# keep only desired columns (is easier to read)
NE_places.dt.prj <- NE_places.dt.prj[, .(X.prj, Y.prj, NAME, POP_MAX)]
# cities below threshold will not be displayed
# this will play a role for labeling and coloring
pop.lim <- 3*10^6 # set a population threshold
NE_places.dt.prj[, lbl := ifelse(POP_MAX < pop.lim, "", NAME)]
NE_places.dt.prj[, big.city := ifelse(POP_MAX < pop.lim, 0, 1)]
# split population in desired intervals/classes
# this will act as point size
NE_places.dt.prj[, POP_MAX.cls := cut(POP_MAX,
labels=c(1:5),
breaks=c(min(POP_MAX)-1,
1*10^6,
3*10^6,
5*10^6,
10*10^6,
max(POP_MAX)),
include.lowest=FALSE,
right=TRUE)]
NE_places.dt.prj[, POP_MAX.cls := as.integer(POP_MAX.cls)] # transform to integer
# =============================================================================
# Plot, edit layers and add legend
# =============================================================================
ggplot() +
# __ add layers and labels
# add projected countries
geom_polygon(data = NE_countries.prj,
aes(long,lat, group = group),
colour = "gray70", fill = "gray90", size = .25) +
# Note: "Regions defined for each Polygons" warning has to do with fortify transformation.
# fortify might get deprecated in future!
# alternatively, use use map_data(NE_countries) to transform to data frame and then use project() to change to desired projection.
# add projected bounding box
geom_polygon(data = NE_box.prj,
aes(x = long, y = lat),
colour = "black", fill = "transparent", size = .25) +
# add locations (points); add opacity with "alpha" argument
geom_point(data = NE_places.dt.prj,
aes(x = X.prj, y = Y.prj, size = POP_MAX.cls, colour = factor(big.city)),
alpha = .5) +
# add labels - cities above given threshold (repel overlapping text labels)
# more adjustments here: https://cran.r-project.org/web/packages/ggrepel/vignettes/ggrepel.html
geom_text_repel(data = NE_places.dt.prj,
aes(x = X.prj, y = Y.prj, label = lbl, colour = factor(big.city)),
# label size
size = 2,
# color of the line segments
segment.colour = "#336699",
# line segment transparency
segment.alpha = .5,
# line segment thickness
segment.size = .25,
# the force of repulsion between overlapping text labels
force = 3,
# maximum number of iterations to attempt to resolve overlaps
max.iter = 10e3,
# turn labels off in the legend (otherwise you get something like https://goo.gl/fW7I7P)
show.legend = FALSE) +
# add graticules
geom_path(data = NE_graticules.prj,
aes(long, lat, group = group),
linetype = "dotted", colour = "grey50", size = .25) +
# add graticule labels - latitude and longitude
geom_text(data = lbl.Y.prj, # latitude
aes(x = X.prj, y = Y.prj, label = lbl),
colour = "grey50", size = 2) +
geom_text(data = lbl.X.prj, # longitude
aes(x = X.prj, y = Y.prj, label = lbl),
colour = "grey50", size = 2) +
# __ Set aspect ratio
# the default, ratio = 1 in coord_fixed ensures that one unit on the x-axis is the same length as one unit on the y-axis
coord_fixed(ratio = 1) +
# __ Set empty theme
theme_void() + # remove the default background, gridlines & default gray color around legend's symbols
# __ Set legend & adjust margins
# for "city size" leged - give title and labels
# this is related to size = POP_MAX.cls in geom_point() above
scale_size_continuous(name = "City size:",
labels = c("below 1 M",
"1 M - 3 M",
"3 M - 5 M",
"5 M - 10 M",
"above 10 M")) +
# for "Is labeled? legend - set city color
# this is related to colour = factor(big.city) in geom_point() and geom_text_repel() above
scale_colour_manual(name = "Is labeled?",
values = c("0"="#666666", "1"="#336699"), # #666666=dove gray & #336699=azure blue
labels = c("no (below 3 M)", "yes (above 3 M)")) +
# adjust city size point and add corresponding color
guides(size = guide_legend(override.aes = list(size=sort(unique(NE_places.dt.prj$POP_MAX.cls)),
colour=c(rep("#666666",2), rep("#336699",3)))),
# this will affect the "Is labeled?" legend section (modify size and symbol type)
color = guide_legend(override.aes = list(size=5, pch=15))) +
# final theme tweaks
theme(legend.title = element_text(colour="black", size=10, face="bold"), # adjust legend title
legend.position = c(1.01, 0.25), # relative position of legend
plot.margin = unit(c(t=0, r=2, b=0, l=0), unit="cm")) # adjust margins
## Regions defined for each Polygons
## Regions defined for each Polygons
## Warning: ggrepel: 18 unlabeled data points (too many overlaps). Consider
## increasing max.overlaps

# Save as png and pdf file
ggsave("gallery_world cities map in Eckert IV ggplot_ggrepel.png",
width=30, height=14, units="cm", dpi=75)
ggsave("gallery_world cities map in Eckert IV ggplot_ggrepel.pdf",
width=30, height=14, units="cm")
# =============================================================================
# Some helpful links:
# =============================================================================
#https://github.com/valentinitnelav/valentinitnelav.github.io/blob/master/gallery/world%20cities%20map%20in%20Eckert%20IV%20ggplot%20%26%20ggrepel.R
# This link was useful for graticule idea
# http://stackoverflow.com/questions/38532070/how-to-add-lines-of-longitude-and-latitude-on-a-map-using-ggplot2
# Working with ggplot()
# http://r4ds.had.co.nz/visualize.html
# http://zevross.com/blog/2014/08/04/beautiful-plotting-in-r-a-ggplot2-cheatsheet-3/#working-with-the-legend