This document introduces various techniques for making staic maps from spatial data.

Base R Graphics

Pure Base R

Vector Data in sp Format

Example 1

A map with Spatial Points and Spatial Polygons, where the size of the point is scaled to the ‘zinc’ level in the soil at that point.

suppressPackageStartupMessages(library(sp))
demo(meuse, ask = FALSE, echo = FALSE) 
crs.longlat = CRS("+init=epsg:4326")
meuse.longlat = spTransform(meuse, crs.longlat)
meuse.riv.longlat <- spTransform(meuse.riv, crs.longlat)
grid.lines <- gridlines(meuse.longlat)

par(mar = c(1, 1, 1, 1))
plot(methods::as(meuse.longlat, "Spatial"), expandBB=c(0.05,0,0.1,0))
plot(grid.lines, add = TRUE, col = grey(.8))
plot(meuse.longlat, pch=1, cex = sqrt(meuse$zinc)/12, add = TRUE)
text(labels(grid.lines, side=2:3), col = grey(.7), offset=1.5)
v = c(100,200,400,800,1600)
legend("bottomright", legend = v, pch = 1, pt.cex = sqrt(v)/12,
       text.col =grey(.8), box.col=grey(0.8), title='Zinc Conc. (ppm)')
plot(meuse.riv.longlat, add = TRUE, col = grey(.9, alpha = .5))

Example 2

World map in Winkel-triple projection showing all cities with population > 1Million. The size of the circle representing the city is proportional to its population.

library(maps)
data(world.cities)
world.cities <- world.cities[world.cities$pop>1000000,]
coordinates(world.cities) <- ~long+lat 
proj4string(world.cities) <- '+init=epsg:4326'

world <- rnaturalearth::countries110
world <- world[world$name != 'Antarctica',]

grid.lines.mj <- gridlines(world,easts = seq(-180,180,by=30), norths = seq(-90,90,by=30))
grid.lines.mi <- gridlines(world,easts = seq(-165,195,by=15), norths = seq(-90,90,by=15))
world.cities <- spTransform(world.cities, CRS("+proj=wintri"))
world <- spTransform(world, CRS("+proj=wintri"))
grid.lines.mj <- spTransform(grid.lines.mj,CRS("+proj=wintri"))
grid.lines.mi <- spTransform(grid.lines.mi,CRS("+proj=wintri"))

par(mar = c(8, 0.1, 0.1, 0.1))
plot(methods::as(world, 'Spatial'), expandBB=c(0,0,0.05,0.05))

plot(grid.lines.mi, col=grey(0.95), add=T)
plot(grid.lines.mj, col=grey(0.9), add=T)
text(labels(grid.lines.mj, side=1:2, labelCRS = CRS("+init=epsg:4326")), col = grey(.6), offset=0.3)

plot(world, add=TRUE, border=grey(0.2), col=grey(0.9))
plot(world.cities, add=TRUE, col='#FF5A0088', pch=20,
     cex=world.cities$pop/2000000)

v = c(1,4,8,12)
legend("topright", legend = v, pch = 20, pt.cex = v/2,
       text.col =grey(.7), box.col=grey(0.9),
       col = '#FF5A0088',
       title='Pop. (Millions)', horiz =T)

Raster Data

Mesus data set.

suppressPackageStartupMessages(library(raster))
r <- raster(system.file("external/test.grd", package="raster"))
plot(r)
plot(meuse, add=T) # Add Vector Data
box(); title('Raster + Vector')

Using cartography Package

Plot of Internet usage as percentage of the population of Afrincan Countries.

suppressPackageStartupMessages(library(cartography))

world <- sf::st_as_sf(rnaturalearth::countries110)
internet_usage <- suppressMessages(readr::read_csv(
  system.file(
    'extdata', 'africa-internet_usage-2015.csv',
    package = 'user2017.geodataviz')))

africa <- dplyr::filter(world, region_un=='Africa') %>%
  dplyr::left_join(internet_usage %>% dplyr::select(
    `Country Code`, `2015 [YR2015]`
  ) %>% dplyr::rename(iso_a3=`Country Code`, internet.usage.2015=`2015 [YR2015]`),
  by = 'iso_a3') %>%
  st_transform(crs="+proj=laea +lon_0=18.984375")

par(mar = c(0.5, 0.5, 0.5, 0.5))
plot(st_geometry(africa), border=grey(0.2), col=grey(0.9))
plot(st_centroid(africa), add=TRUE, col='black', pch=20)
{{propSymbolsLayer(x=africa, var='internet.usage.2015', inches = 0.3, col = '#FF5A0088')}}

Plotting using ggplot2

Just ggplot2

Vector Data in sp Format

Choropleth from estimated median GDP of European countries.

world <- rnaturalearth::countries110
europe <- world[world$region_un=="Europe"&world$name!='Russia',]
# plot(europe)

# Let's add a unique ID column to our data.
{{europe@data$id <- row.names(europe@data)}}

# A bounding box for continental Europe.
europe.bbox <- SpatialPolygons(list(Polygons(list(Polygon(
  matrix(c(-25,29,45,29,45,75,-25,75,-25,29),byrow = T,ncol = 2)
)), ID = 1)), proj4string = CRS(proj4string(europe)))

# Get polygons that are only in continental Europe.
europe.clipped <-
{{  rgeos::gIntersection(europe, europe.bbox, byid = TRUE, id=europe$id)}}

# tidy up the data for ggplot2
europe.tidy <- broom::tidy(europe.clipped)
europe.tidy <- dplyr::left_join(europe.tidy, europe@data, by='id')
library(ggplot2)
ggplot(europe.tidy, aes(long,lat, group=group,fill=gdp_md_est/1000)) +
  geom_polygon(alpha=0.8,color='black') +
  coord_map("azequalarea") +
  hrbrthemes::theme_ipsum_rc() +
  viridis::scale_fill_viridis(
    name='Median GDP \n(in Billions)', direction = -1, labels=scales::dollar) +
  labs(x=NULL, y=NULL, 
       title='Median GDP Estimates of\nContinental Europe & Iceland',
       caption='Source: http://www.naturalearthdata.com/')

Vector Data in sf Format

Choropleth from estimated median GDP of European countries.

suppressPackageStartupMessages(library(sf))

world <- st_as_sf(rnaturalearth::countries110)
europe <- dplyr::filter(world, region_un=="Europe" & name!='Russia')

# A bounding box for continental Europe.
europe.bbox <- st_polygon(list(
  matrix(c(-25,29,45,29,45,75,-25,75,-25,29),byrow = T,ncol = 2)))

europe.clipped <- suppressWarnings(st_intersection(europe, st_sfc(europe.bbox, crs=st_crs(europe))))


ggplot(europe.clipped, aes(fill=gdp_md_est/1000)) +
  geom_sf(alpha=0.8,col='white') +
  coord_sf(crs="+proj=aea +lat_1=36.333333333333336 +lat_2=65.66666666666667 +lon_0=14") +
  hrbrthemes::theme_ipsum_rc() +
  viridis::scale_fill_viridis(
    name='Median GDP \n(in Billions)', direction = -1, labels=scales::dollar) +
  labs(x=NULL, y=NULL, title=NULL,
       caption='Source: http://www.naturalearthdata.com/')

Raster Data

suppressPackageStartupMessages(library(raster))

r <- raster(system.file("external/test.grd", package="raster"))

{{rasterVis::gplot(r) + geom_tile(aes(fill = value)) }} +
  viridis::scale_fill_viridis(direction = -1, na.value='#FFFFFF00') +
  coord_equal() + hrbrthemes::theme_ipsum()

Using ggspatial Package

ggspatial package avoids the need to tidy spatial data, by providing geom_spatial() which works natively with spatial data. It also provides ggosm() function to show a basemap from tile map providers such as Open Street Map (OSM).

library(sp)
library(ggplot2)
library(ggspatial)
demo(meuse,ask=F, echo = F)
ggosm(type = "cartolight",quiet = TRUE) +
  geom_spatial(meuse)
Converting coordinates to lat/lon
Zoom: 14
Fetching 12 missing tiles

  |                                                                       
  |                                                                 |   0%
  |                                                                       
  |=====                                                            |   8%
  |                                                                       
  |===========                                                      |  17%
  |                                                                       
  |================                                                 |  25%
  |                                                                       
  |======================                                           |  33%
  |                                                                       
  |===========================                                      |  42%
  |                                                                       
  |================================                                 |  50%
  |                                                                       
  |======================================                           |  58%
  |                                                                       
  |===========================================                      |  67%
  |                                                                       
  |=================================================                |  75%
  |                                                                       
  |======================================================           |  83%
  |                                                                       
  |============================================================     |  92%
  |                                                                       
  |=================================================================| 100%
...complete!

Using ggmap Package

ggmap provides a myrid of add-on functionality for plotting maps using ggplot2.

suppressPackageStartupMessages(library(ggplot2))
suppressPackageStartupMessages(library(ggmap))

paste0("2016-0",1:7) %>%
  purrr::map(function(month) {
    suppressMessages(readr::read_csv(
      system.file(
        sprintf("examples/data/London-Crimes/%s/%s-city-of-london-street.csv.zip",
                month,month),
        package='leaflet.extras')
    ))
  }) %>%
  dplyr::bind_rows() -> crimes

suppressMessages(suppressWarnings(
  qmplot(Longitude, Latitude,
         data = crimes %>% dplyr::filter(!is.na(Latitude)),
         geom="blank", zoom=15,
         maptype = "toner-lite", facets = ~Month) +
  stat_density_2d(aes(fill = ..level..), geom = "polygon", alpha = .3) +
    scale_fill_gradient2("Crime Heatmap", low = "white", mid = "yellow", high = "red")  + theme(legend.position = 'hide')
))

Plotting using tmap

tmap provides quick and easy thematic mapping. tmap output cannot be combined either base R plot() output or ggplot2 output. tmap has its own API similar to ggplot2, but a few subtle differences.

usa_pop_history <- suppressMessages(
  readr::read_tsv(system.file(
    'extdata','usa_pop_history.tsv', package='user2017.geodataviz')))
usa <- suppressWarnings(dplyr::left_join(albersusa::usa_sf(), usa_pop_history, by=c('name'='State')))
usa <- st_transform(usa, crs=albersusa::us_laea_proj)
years <-  c(1900,1950,1990,2000,2010,2015)
print(tmap::qtm(usa, fill=paste0("p.",years), title=years, 
          layout.title.color='red',  layout.title.position=c('center','top'),
          layout.main.title = 'U.S.A. Population by State',
          style = 'col_blind', facets.nrow=2))

Animations

Using animation

library(ggplot2)

usa <- albersusa::usa_sf()

usa_pop_history <- readr::read_tsv(system.file(
'extdata','usa_pop_history.tsv', package='user2017.geodataviz'))

usa <- dplyr::left_join(usa, usa_pop_history, by=c('name'='State'))

g <- ggplot(data=usa) + ggthemes::theme_map() + theme(legend.position = 'bottom') +
  viridis::scale_fill_viridis(direction = -1, option = 'A')

g.2015 <- g + geom_sf(aes(fill=p.2015)) + coord_sf(crs = albersusa::us_aeqd_proj)
g.2010 <- g + geom_sf(aes(fill=p.2010)) + coord_sf(crs = albersusa::us_aeqd_proj)
g.2000 <- g + geom_sf(aes(fill=p.2000)) + coord_sf(crs = albersusa::us_aeqd_proj)
g.1990 <- g + geom_sf(aes(fill=p.1990)) + coord_sf(crs = albersusa::us_aeqd_proj)
g.1950 <- g + geom_sf(aes(fill=p.1950)) + coord_sf(crs = albersusa::us_aeqd_proj)
g.1900 <- g + geom_sf(aes(fill=p.1900)) + coord_sf(crs = albersusa::us_aeqd_proj)

# Save each file
ggsave(g.2015,filename = '/tmp/g_2015.png')
ggsave(g.2010,filename = '/tmp/g_2010.png')
ggsave(g.2000,filename = '/tmp/g_2000.png')
ggsave(g.1990,filename = '/tmp/g_1990.png')
ggsave(g.1950,filename = '/tmp/g_1950.png')
ggsave(g.1900,filename = '/tmp/g_1900.png')

animation::im.convert(files = '/tmp/g_*.png', output =  'animation-01.gif')
unlink('/tmp/g_*.png', force = TRUE)
LS0tCnRpdGxlOiAiU3RhdGljIE1hcHMgaW4gUiIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IFRSVUUKICAgIHRvY19mbG9hdDogVFJVRQplZGl0b3Jfb3B0aW9uczogCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGNvbnNvbGUKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Kb3B0aW9ucyhodG1sdG9vbHMuZGlyLnZlcnNpb24gPSBGQUxTRSkKa25pdHI6Om9wdHNfY2h1bmskc2V0KGNhY2hlID0gVFJVRSkKa25pdHI6Om9wdHNfY2h1bmskc2V0KGRldiA9ICdzdmcnKQpvcHRpb25zKGRldmljZSA9IGZ1bmN0aW9uKGZpbGUsIHdpZHRoLCBoZWlnaHQpIHsKICBzdmcodGVtcGZpbGUoKSwgd2lkdGggPSB3aWR0aCwgaGVpZ2h0ID0gaGVpZ2h0KQp9KQpsaWJyYXJ5KG1hZ3JpdHRyKQpgYGAKClRoaXMgZG9jdW1lbnQgaW50cm9kdWNlcyB2YXJpb3VzIHRlY2huaXF1ZXMgZm9yIG1ha2luZyBzdGFpYyBtYXBzIGZyb20gc3BhdGlhbCBkYXRhLgoKIyBCYXNlIFIgR3JhcGhpY3MKCiMjIFB1cmUgQmFzZSBSIAoKIyMjIFZlY3RvciBEYXRhIGluIGBzcGAgRm9ybWF0CgojIyMjIEV4YW1wbGUgMQoKQSBtYXAgd2l0aCBTcGF0aWFsIFBvaW50cyBhbmQgU3BhdGlhbCBQb2x5Z29ucywgd2hlcmUgdGhlIHNpemUgb2YgdGhlIHBvaW50IGlzIHNjYWxlZCB0byB0aGUgJ3ppbmMnIGxldmVsIGluIHRoZSBzb2lsIGF0IHRoYXQgcG9pbnQuCgpgYGB7ciAwMSwgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NCwgZmlnLmFsaWduPSdjZW50ZXInfQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeShzcCkpCmRlbW8obWV1c2UsIGFzayA9IEZBTFNFLCBlY2hvID0gRkFMU0UpIApjcnMubG9uZ2xhdCA9IENSUygiK2luaXQ9ZXBzZzo0MzI2IikKbWV1c2UubG9uZ2xhdCA9IHNwVHJhbnNmb3JtKG1ldXNlLCBjcnMubG9uZ2xhdCkKbWV1c2Uucml2LmxvbmdsYXQgPC0gc3BUcmFuc2Zvcm0obWV1c2Uucml2LCBjcnMubG9uZ2xhdCkKZ3JpZC5saW5lcyA8LSBncmlkbGluZXMobWV1c2UubG9uZ2xhdCkKCnBhcihtYXIgPSBjKDEsIDEsIDEsIDEpKQpwbG90KG1ldGhvZHM6OmFzKG1ldXNlLmxvbmdsYXQsICJTcGF0aWFsIiksIGV4cGFuZEJCPWMoMC4wNSwwLDAuMSwwKSkKcGxvdChncmlkLmxpbmVzLCBhZGQgPSBUUlVFLCBjb2wgPSBncmV5KC44KSkKcGxvdChtZXVzZS5sb25nbGF0LCBwY2g9MSwgY2V4ID0gc3FydChtZXVzZSR6aW5jKS8xMiwgYWRkID0gVFJVRSkKdGV4dChsYWJlbHMoZ3JpZC5saW5lcywgc2lkZT0yOjMpLCBjb2wgPSBncmV5KC43KSwgb2Zmc2V0PTEuNSkKdiA9IGMoMTAwLDIwMCw0MDAsODAwLDE2MDApCmxlZ2VuZCgiYm90dG9tcmlnaHQiLCBsZWdlbmQgPSB2LCBwY2ggPSAxLCBwdC5jZXggPSBzcXJ0KHYpLzEyLAogICAgICAgdGV4dC5jb2wgPWdyZXkoLjgpLCBib3guY29sPWdyZXkoMC44KSwgdGl0bGU9J1ppbmMgQ29uYy4gKHBwbSknKQpwbG90KG1ldXNlLnJpdi5sb25nbGF0LCBhZGQgPSBUUlVFLCBjb2wgPSBncmV5KC45LCBhbHBoYSA9IC41KSkKYGBgCgojIyMjIEV4YW1wbGUgMgoKV29ybGQgbWFwIGluIFdpbmtlbC10cmlwbGUgcHJvamVjdGlvbiBzaG93aW5nIGFsbCBjaXRpZXMgd2l0aCBwb3B1bGF0aW9uID4gMU1pbGxpb24uIFRoZSBzaXplIG9mIHRoZSBjaXJjbGUgcmVwcmVzZW50aW5nIHRoZSBjaXR5IGlzIHByb3BvcnRpb25hbCB0byBpdHMgcG9wdWxhdGlvbi4KCgpgYGB7ciAwMiwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NiwgZmlnLmFsaWduPSdjZW50ZXInfQpsaWJyYXJ5KG1hcHMpCmRhdGEod29ybGQuY2l0aWVzKQp3b3JsZC5jaXRpZXMgPC0gd29ybGQuY2l0aWVzW3dvcmxkLmNpdGllcyRwb3A+MTAwMDAwMCxdCmNvb3JkaW5hdGVzKHdvcmxkLmNpdGllcykgPC0gfmxvbmcrbGF0IApwcm9qNHN0cmluZyh3b3JsZC5jaXRpZXMpIDwtICcraW5pdD1lcHNnOjQzMjYnCgp3b3JsZCA8LSBybmF0dXJhbGVhcnRoOjpjb3VudHJpZXMxMTAKd29ybGQgPC0gd29ybGRbd29ybGQkbmFtZSAhPSAnQW50YXJjdGljYScsXQoKZ3JpZC5saW5lcy5taiA8LSBncmlkbGluZXMod29ybGQsZWFzdHMgPSBzZXEoLTE4MCwxODAsYnk9MzApLCBub3J0aHMgPSBzZXEoLTkwLDkwLGJ5PTMwKSkKZ3JpZC5saW5lcy5taSA8LSBncmlkbGluZXMod29ybGQsZWFzdHMgPSBzZXEoLTE2NSwxOTUsYnk9MTUpLCBub3J0aHMgPSBzZXEoLTkwLDkwLGJ5PTE1KSkKd29ybGQuY2l0aWVzIDwtIHNwVHJhbnNmb3JtKHdvcmxkLmNpdGllcywgQ1JTKCIrcHJvaj13aW50cmkiKSkKd29ybGQgPC0gc3BUcmFuc2Zvcm0od29ybGQsIENSUygiK3Byb2o9d2ludHJpIikpCmdyaWQubGluZXMubWogPC0gc3BUcmFuc2Zvcm0oZ3JpZC5saW5lcy5taixDUlMoIitwcm9qPXdpbnRyaSIpKQpncmlkLmxpbmVzLm1pIDwtIHNwVHJhbnNmb3JtKGdyaWQubGluZXMubWksQ1JTKCIrcHJvaj13aW50cmkiKSkKCnBhcihtYXIgPSBjKDgsIDAuMSwgMC4xLCAwLjEpKQpwbG90KG1ldGhvZHM6OmFzKHdvcmxkLCAnU3BhdGlhbCcpLCBleHBhbmRCQj1jKDAsMCwwLjA1LDAuMDUpKQoKcGxvdChncmlkLmxpbmVzLm1pLCBjb2w9Z3JleSgwLjk1KSwgYWRkPVQpCnBsb3QoZ3JpZC5saW5lcy5taiwgY29sPWdyZXkoMC45KSwgYWRkPVQpCnRleHQobGFiZWxzKGdyaWQubGluZXMubWosIHNpZGU9MToyLCBsYWJlbENSUyA9IENSUygiK2luaXQ9ZXBzZzo0MzI2IikpLCBjb2wgPSBncmV5KC42KSwgb2Zmc2V0PTAuMykKCnBsb3Qod29ybGQsIGFkZD1UUlVFLCBib3JkZXI9Z3JleSgwLjIpLCBjb2w9Z3JleSgwLjkpKQpwbG90KHdvcmxkLmNpdGllcywgYWRkPVRSVUUsIGNvbD0nI0ZGNUEwMDg4JywgcGNoPTIwLAogICAgIGNleD13b3JsZC5jaXRpZXMkcG9wLzIwMDAwMDApCgp2ID0gYygxLDQsOCwxMikKbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZCA9IHYsIHBjaCA9IDIwLCBwdC5jZXggPSB2LzIsCiAgICAgICB0ZXh0LmNvbCA9Z3JleSguNyksIGJveC5jb2w9Z3JleSgwLjkpLAogICAgICAgY29sID0gJyNGRjVBMDA4OCcsCiAgICAgICB0aXRsZT0nUG9wLiAoTWlsbGlvbnMpJywgaG9yaXogPVQpCmBgYAoKIyMjIFJhc3RlciBEYXRhCgpNZXN1cyBkYXRhIHNldC4KCmBgYHtyIDAzLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01LCBmaWcuYWxpZ249J2NlbnRlcid9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KHJhc3RlcikpCnIgPC0gcmFzdGVyKHN5c3RlbS5maWxlKCJleHRlcm5hbC90ZXN0LmdyZCIsIHBhY2thZ2U9InJhc3RlciIpKQpwbG90KHIpCnBsb3QobWV1c2UsIGFkZD1UKSAjIEFkZCBWZWN0b3IgRGF0YQpib3goKTsgdGl0bGUoJ1Jhc3RlciArIFZlY3RvcicpCmBgYAoKIyMgVXNpbmcgYGNhcnRvZ3JhcGh5YCBQYWNrYWdlCgpQbG90IG9mIEludGVybmV0IHVzYWdlIGFzIHBlcmNlbnRhZ2Ugb2YgdGhlIHBvcHVsYXRpb24gb2YgQWZyaW5jYW4gQ291bnRyaWVzLgoKYGBge3IgMDQsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTUsIGZpZy5hbGlnbj0nY2VudGVyJ30Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkoY2FydG9ncmFwaHkpKQoKd29ybGQgPC0gc2Y6OnN0X2FzX3NmKHJuYXR1cmFsZWFydGg6OmNvdW50cmllczExMCkKaW50ZXJuZXRfdXNhZ2UgPC0gc3VwcHJlc3NNZXNzYWdlcyhyZWFkcjo6cmVhZF9jc3YoCiAgc3lzdGVtLmZpbGUoCiAgICAnZXh0ZGF0YScsICdhZnJpY2EtaW50ZXJuZXRfdXNhZ2UtMjAxNS5jc3YnLAogICAgcGFja2FnZSA9ICd1c2VyMjAxNy5nZW9kYXRhdml6JykpKQoKYWZyaWNhIDwtIGRwbHlyOjpmaWx0ZXIod29ybGQsIHJlZ2lvbl91bj09J0FmcmljYScpICU+JQogIGRwbHlyOjpsZWZ0X2pvaW4oaW50ZXJuZXRfdXNhZ2UgJT4lIGRwbHlyOjpzZWxlY3QoCiAgICBgQ291bnRyeSBDb2RlYCwgYDIwMTUgW1lSMjAxNV1gCiAgKSAlPiUgZHBseXI6OnJlbmFtZShpc29fYTM9YENvdW50cnkgQ29kZWAsIGludGVybmV0LnVzYWdlLjIwMTU9YDIwMTUgW1lSMjAxNV1gKSwKICBieSA9ICdpc29fYTMnKSAlPiUKICBzdF90cmFuc2Zvcm0oY3JzPSIrcHJvaj1sYWVhICtsb25fMD0xOC45ODQzNzUiKQoKcGFyKG1hciA9IGMoMC41LCAwLjUsIDAuNSwgMC41KSkKcGxvdChzdF9nZW9tZXRyeShhZnJpY2EpLCBib3JkZXI9Z3JleSgwLjIpLCBjb2w9Z3JleSgwLjkpKQpwbG90KHN0X2NlbnRyb2lkKGFmcmljYSksIGFkZD1UUlVFLCBjb2w9J2JsYWNrJywgcGNoPTIwKQp7e3Byb3BTeW1ib2xzTGF5ZXIoeD1hZnJpY2EsIHZhcj0naW50ZXJuZXQudXNhZ2UuMjAxNScsIGluY2hlcyA9IDAuMywgY29sID0gJyNGRjVBMDA4OCcpfX0KYGBgCgojIFBsb3R0aW5nIHVzaW5nIGBnZ3Bsb3QyYAoKIyMgSnVzdCBgZ2dwbG90MmAKCiMjIyBWZWN0b3IgRGF0YSBpbiBgc3BgIEZvcm1hdAoKQ2hvcm9wbGV0aCBmcm9tIGVzdGltYXRlZCBtZWRpYW4gR0RQIG9mIEV1cm9wZWFuIGNvdW50cmllcy4KCmBgYHtyIDA1LCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD01LCBmaWcuYWxpZ249J2NlbnRlcid9CndvcmxkIDwtIHJuYXR1cmFsZWFydGg6OmNvdW50cmllczExMApldXJvcGUgPC0gd29ybGRbd29ybGQkcmVnaW9uX3VuPT0iRXVyb3BlIiZ3b3JsZCRuYW1lIT0nUnVzc2lhJyxdCiMgcGxvdChldXJvcGUpCgojIExldCdzIGFkZCBhIHVuaXF1ZSBJRCBjb2x1bW4gdG8gb3VyIGRhdGEuCnt7ZXVyb3BlQGRhdGEkaWQgPC0gcm93Lm5hbWVzKGV1cm9wZUBkYXRhKX19CgojIEEgYm91bmRpbmcgYm94IGZvciBjb250aW5lbnRhbCBFdXJvcGUuCmV1cm9wZS5iYm94IDwtIFNwYXRpYWxQb2x5Z29ucyhsaXN0KFBvbHlnb25zKGxpc3QoUG9seWdvbigKICBtYXRyaXgoYygtMjUsMjksNDUsMjksNDUsNzUsLTI1LDc1LC0yNSwyOSksYnlyb3cgPSBULG5jb2wgPSAyKQopKSwgSUQgPSAxKSksIHByb2o0c3RyaW5nID0gQ1JTKHByb2o0c3RyaW5nKGV1cm9wZSkpKQoKIyBHZXQgcG9seWdvbnMgdGhhdCBhcmUgb25seSBpbiBjb250aW5lbnRhbCBFdXJvcGUuCmV1cm9wZS5jbGlwcGVkIDwtCnt7ICByZ2Vvczo6Z0ludGVyc2VjdGlvbihldXJvcGUsIGV1cm9wZS5iYm94LCBieWlkID0gVFJVRSwgaWQ9ZXVyb3BlJGlkKX19CgojIHRpZHkgdXAgdGhlIGRhdGEgZm9yIGdncGxvdDIKZXVyb3BlLnRpZHkgPC0gYnJvb206OnRpZHkoZXVyb3BlLmNsaXBwZWQpCmV1cm9wZS50aWR5IDwtIGRwbHlyOjpsZWZ0X2pvaW4oZXVyb3BlLnRpZHksIGV1cm9wZUBkYXRhLCBieT0naWQnKQpsaWJyYXJ5KGdncGxvdDIpCmdncGxvdChldXJvcGUudGlkeSwgYWVzKGxvbmcsbGF0LCBncm91cD1ncm91cCxmaWxsPWdkcF9tZF9lc3QvMTAwMCkpICsKICBnZW9tX3BvbHlnb24oYWxwaGE9MC44LGNvbG9yPSdibGFjaycpICsKICBjb29yZF9tYXAoImF6ZXF1YWxhcmVhIikgKwogIGhyYnJ0aGVtZXM6OnRoZW1lX2lwc3VtX3JjKCkgKwogIHZpcmlkaXM6OnNjYWxlX2ZpbGxfdmlyaWRpcygKICAgIG5hbWU9J01lZGlhbiBHRFAgXG4oaW4gQmlsbGlvbnMpJywgZGlyZWN0aW9uID0gLTEsIGxhYmVscz1zY2FsZXM6OmRvbGxhcikgKwogIGxhYnMoeD1OVUxMLCB5PU5VTEwsIAogICAgICAgdGl0bGU9J01lZGlhbiBHRFAgRXN0aW1hdGVzIG9mXG5Db250aW5lbnRhbCBFdXJvcGUgJiBJY2VsYW5kJywKICAgICAgIGNhcHRpb249J1NvdXJjZTogaHR0cDovL3d3dy5uYXR1cmFsZWFydGhkYXRhLmNvbS8nKQoKYGBgCgojIyMgVmVjdG9yIERhdGEgaW4gYHNmYCBGb3JtYXQKCkNob3JvcGxldGggZnJvbSBlc3RpbWF0ZWQgbWVkaWFuIEdEUCBvZiBFdXJvcGVhbiBjb3VudHJpZXMuCgpgYGB7ciAwNiwgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9NSwgZmlnLmFsaWduPSdjZW50ZXInfQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeShzZikpCgp3b3JsZCA8LSBzdF9hc19zZihybmF0dXJhbGVhcnRoOjpjb3VudHJpZXMxMTApCmV1cm9wZSA8LSBkcGx5cjo6ZmlsdGVyKHdvcmxkLCByZWdpb25fdW49PSJFdXJvcGUiICYgbmFtZSE9J1J1c3NpYScpCgojIEEgYm91bmRpbmcgYm94IGZvciBjb250aW5lbnRhbCBFdXJvcGUuCmV1cm9wZS5iYm94IDwtIHN0X3BvbHlnb24obGlzdCgKICBtYXRyaXgoYygtMjUsMjksNDUsMjksNDUsNzUsLTI1LDc1LC0yNSwyOSksYnlyb3cgPSBULG5jb2wgPSAyKSkpCgpldXJvcGUuY2xpcHBlZCA8LSBzdXBwcmVzc1dhcm5pbmdzKHN0X2ludGVyc2VjdGlvbihldXJvcGUsIHN0X3NmYyhldXJvcGUuYmJveCwgY3JzPXN0X2NycyhldXJvcGUpKSkpCgoKZ2dwbG90KGV1cm9wZS5jbGlwcGVkLCBhZXMoZmlsbD1nZHBfbWRfZXN0LzEwMDApKSArCiAgZ2VvbV9zZihhbHBoYT0wLjgsY29sPSd3aGl0ZScpICsKICBjb29yZF9zZihjcnM9Iitwcm9qPWFlYSArbGF0XzE9MzYuMzMzMzMzMzMzMzMzMzM2ICtsYXRfMj02NS42NjY2NjY2NjY2NjY2NyArbG9uXzA9MTQiKSArCiAgaHJicnRoZW1lczo6dGhlbWVfaXBzdW1fcmMoKSArCiAgdmlyaWRpczo6c2NhbGVfZmlsbF92aXJpZGlzKAogICAgbmFtZT0nTWVkaWFuIEdEUCBcbihpbiBCaWxsaW9ucyknLCBkaXJlY3Rpb24gPSAtMSwgbGFiZWxzPXNjYWxlczo6ZG9sbGFyKSArCiAgbGFicyh4PU5VTEwsIHk9TlVMTCwgdGl0bGU9TlVMTCwKICAgICAgIGNhcHRpb249J1NvdXJjZTogaHR0cDovL3d3dy5uYXR1cmFsZWFydGhkYXRhLmNvbS8nKQpgYGAKCiMjIyBSYXN0ZXIgRGF0YQoKYGBge3IgMDcsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTUsIGZpZy5hbGlnbj0nY2VudGVyJ30KCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KHJhc3RlcikpCgpyIDwtIHJhc3RlcihzeXN0ZW0uZmlsZSgiZXh0ZXJuYWwvdGVzdC5ncmQiLCBwYWNrYWdlPSJyYXN0ZXIiKSkKCnt7cmFzdGVyVmlzOjpncGxvdChyKSArIGdlb21fdGlsZShhZXMoZmlsbCA9IHZhbHVlKSkgfX0gKwogIHZpcmlkaXM6OnNjYWxlX2ZpbGxfdmlyaWRpcyhkaXJlY3Rpb24gPSAtMSwgbmEudmFsdWU9JyNGRkZGRkYwMCcpICsKICBjb29yZF9lcXVhbCgpICsgaHJicnRoZW1lczo6dGhlbWVfaXBzdW0oKQpgYGAKCiMjIFVzaW5nIGBnZ3NwYXRpYWxgIFBhY2thZ2UKCmBnZ3NwYXRpYWxgIHBhY2thZ2UgYXZvaWRzIHRoZSBuZWVkIHRvIHRpZHkgc3BhdGlhbCBkYXRhLCBieSBwcm92aWRpbmcgYGdlb21fc3BhdGlhbCgpYCB3aGljaCB3b3JrcyBuYXRpdmVseSB3aXRoIHNwYXRpYWwgZGF0YS4gSXQgYWxzbyBwcm92aWRlcyBgZ2dvc20oKWAgZnVuY3Rpb24gdG8gc2hvdyBhIGJhc2VtYXAgZnJvbSB0aWxlIG1hcCBwcm92aWRlcnMgc3VjaCBhcyBPcGVuIFN0cmVldCBNYXAgKE9TTSkuCgpgYGB7ciAwOCwgZmlnLndpZHRoPTMsIGZpZy5oZWlnaHQ9NCwgZmlnLmFsaWduPSdjZW50ZXInfQpsaWJyYXJ5KHNwKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dzcGF0aWFsKQpkZW1vKG1ldXNlLGFzaz1GLCBlY2hvID0gRikKZ2dvc20odHlwZSA9ICJjYXJ0b2xpZ2h0IixxdWlldCA9IFRSVUUpICsKICBnZW9tX3NwYXRpYWwobWV1c2UpCgpgYGAKCiMjIFVzaW5nIGBnZ21hcGAgUGFja2FnZQoKYGdnbWFwYCBwcm92aWRlcyBhIG15cmlkIG9mIGFkZC1vbiBmdW5jdGlvbmFsaXR5IGZvciBwbG90dGluZyBtYXBzIHVzaW5nIGBnZ3Bsb3QyYC4KCmBgYHtyIDA5LCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NiwgZmlnLmFsaWduPSdjZW50ZXInfQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeShnZ3Bsb3QyKSkKc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkoZ2dtYXApKQoKcGFzdGUwKCIyMDE2LTAiLDE6NykgJT4lCiAgcHVycnI6Om1hcChmdW5jdGlvbihtb250aCkgewogICAgc3VwcHJlc3NNZXNzYWdlcyhyZWFkcjo6cmVhZF9jc3YoCiAgICAgIHN5c3RlbS5maWxlKAogICAgICAgIHNwcmludGYoImV4YW1wbGVzL2RhdGEvTG9uZG9uLUNyaW1lcy8lcy8lcy1jaXR5LW9mLWxvbmRvbi1zdHJlZXQuY3N2LnppcCIsCiAgICAgICAgICAgICAgICBtb250aCxtb250aCksCiAgICAgICAgcGFja2FnZT0nbGVhZmxldC5leHRyYXMnKQogICAgKSkKICB9KSAlPiUKICBkcGx5cjo6YmluZF9yb3dzKCkgLT4gY3JpbWVzCgpzdXBwcmVzc01lc3NhZ2VzKHN1cHByZXNzV2FybmluZ3MoCiAgcW1wbG90KExvbmdpdHVkZSwgTGF0aXR1ZGUsCiAgICAgICAgIGRhdGEgPSBjcmltZXMgJT4lIGRwbHlyOjpmaWx0ZXIoIWlzLm5hKExhdGl0dWRlKSksCiAgICAgICAgIGdlb209ImJsYW5rIiwgem9vbT0xNSwKICAgICAgICAgbWFwdHlwZSA9ICJ0b25lci1saXRlIiwgZmFjZXRzID0gfk1vbnRoKSArCiAgc3RhdF9kZW5zaXR5XzJkKGFlcyhmaWxsID0gLi5sZXZlbC4uKSwgZ2VvbSA9ICJwb2x5Z29uIiwgYWxwaGEgPSAuMykgKwogICAgc2NhbGVfZmlsbF9ncmFkaWVudDIoIkNyaW1lIEhlYXRtYXAiLCBsb3cgPSAid2hpdGUiLCBtaWQgPSAieWVsbG93IiwgaGlnaCA9ICJyZWQiKSAgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnaGlkZScpCikpCmBgYAoKIyBQbG90dGluZyB1c2luZyBgdG1hcGAKCmB0bWFwYCBwcm92aWRlcyBxdWljayBhbmQgZWFzeSB0aGVtYXRpYyBtYXBwaW5nLiBgdG1hcGAgb3V0cHV0IGNhbm5vdCBiZSBjb21iaW5lZCBlaXRoZXIgYmFzZSBSIGBwbG90KClgIG91dHB1dCBvciBgZ2dwbG90MmAgb3V0cHV0LiBgdG1hcGAgaGFzIGl0cyBvd24gQVBJIHNpbWlsYXIgdG8gYGdncGxvdDJgLCBidXQgYSBmZXcgc3VidGxlIGRpZmZlcmVuY2VzLgoKCmBgYHtyIDEwLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9NiwgZmlnLmFsaWduPSdjZW50ZXInfQp1c2FfcG9wX2hpc3RvcnkgPC0gc3VwcHJlc3NNZXNzYWdlcygKICByZWFkcjo6cmVhZF90c3Yoc3lzdGVtLmZpbGUoCiAgICAnZXh0ZGF0YScsJ3VzYV9wb3BfaGlzdG9yeS50c3YnLCBwYWNrYWdlPSd1c2VyMjAxNy5nZW9kYXRhdml6JykpKQp1c2EgPC0gc3VwcHJlc3NXYXJuaW5ncyhkcGx5cjo6bGVmdF9qb2luKGFsYmVyc3VzYTo6dXNhX3NmKCksIHVzYV9wb3BfaGlzdG9yeSwgYnk9YygnbmFtZSc9J1N0YXRlJykpKQp1c2EgPC0gc3RfdHJhbnNmb3JtKHVzYSwgY3JzPWFsYmVyc3VzYTo6dXNfbGFlYV9wcm9qKQp5ZWFycyA8LSAgYygxOTAwLDE5NTAsMTk5MCwyMDAwLDIwMTAsMjAxNSkKcHJpbnQodG1hcDo6cXRtKHVzYSwgZmlsbD1wYXN0ZTAoInAuIix5ZWFycyksIHRpdGxlPXllYXJzLCAKICAgICAgICAgIGxheW91dC50aXRsZS5jb2xvcj0ncmVkJywgIGxheW91dC50aXRsZS5wb3NpdGlvbj1jKCdjZW50ZXInLCd0b3AnKSwKICAgICAgICAgIGxheW91dC5tYWluLnRpdGxlID0gJ1UuUy5BLiBQb3B1bGF0aW9uIGJ5IFN0YXRlJywKICAgICAgICAgIHN0eWxlID0gJ2NvbF9ibGluZCcsIGZhY2V0cy5ucm93PTIpKQpgYGAKCgojIEFuaW1hdGlvbnMKCiMjIFVzaW5nIGBhbmltYXRpb25gCgpgYGB7ciAxMSwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeShnZ3Bsb3QyKQoKdXNhIDwtIGFsYmVyc3VzYTo6dXNhX3NmKCkKCnVzYV9wb3BfaGlzdG9yeSA8LSByZWFkcjo6cmVhZF90c3Yoc3lzdGVtLmZpbGUoCidleHRkYXRhJywndXNhX3BvcF9oaXN0b3J5LnRzdicsIHBhY2thZ2U9J3VzZXIyMDE3Lmdlb2RhdGF2aXonKSkKCnVzYSA8LSBkcGx5cjo6bGVmdF9qb2luKHVzYSwgdXNhX3BvcF9oaXN0b3J5LCBieT1jKCduYW1lJz0nU3RhdGUnKSkKCmcgPC0gZ2dwbG90KGRhdGE9dXNhKSArIGdndGhlbWVzOjp0aGVtZV9tYXAoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdib3R0b20nKSArCiAgdmlyaWRpczo6c2NhbGVfZmlsbF92aXJpZGlzKGRpcmVjdGlvbiA9IC0xLCBvcHRpb24gPSAnQScpCgpnLjIwMTUgPC0gZyArIGdlb21fc2YoYWVzKGZpbGw9cC4yMDE1KSkgKyBjb29yZF9zZihjcnMgPSBhbGJlcnN1c2E6OnVzX2FlcWRfcHJvaikKZy4yMDEwIDwtIGcgKyBnZW9tX3NmKGFlcyhmaWxsPXAuMjAxMCkpICsgY29vcmRfc2YoY3JzID0gYWxiZXJzdXNhOjp1c19hZXFkX3Byb2opCmcuMjAwMCA8LSBnICsgZ2VvbV9zZihhZXMoZmlsbD1wLjIwMDApKSArIGNvb3JkX3NmKGNycyA9IGFsYmVyc3VzYTo6dXNfYWVxZF9wcm9qKQpnLjE5OTAgPC0gZyArIGdlb21fc2YoYWVzKGZpbGw9cC4xOTkwKSkgKyBjb29yZF9zZihjcnMgPSBhbGJlcnN1c2E6OnVzX2FlcWRfcHJvaikKZy4xOTUwIDwtIGcgKyBnZW9tX3NmKGFlcyhmaWxsPXAuMTk1MCkpICsgY29vcmRfc2YoY3JzID0gYWxiZXJzdXNhOjp1c19hZXFkX3Byb2opCmcuMTkwMCA8LSBnICsgZ2VvbV9zZihhZXMoZmlsbD1wLjE5MDApKSArIGNvb3JkX3NmKGNycyA9IGFsYmVyc3VzYTo6dXNfYWVxZF9wcm9qKQoKIyBTYXZlIGVhY2ggZmlsZQpnZ3NhdmUoZy4yMDE1LGZpbGVuYW1lID0gJy90bXAvZ18yMDE1LnBuZycpCmdnc2F2ZShnLjIwMTAsZmlsZW5hbWUgPSAnL3RtcC9nXzIwMTAucG5nJykKZ2dzYXZlKGcuMjAwMCxmaWxlbmFtZSA9ICcvdG1wL2dfMjAwMC5wbmcnKQpnZ3NhdmUoZy4xOTkwLGZpbGVuYW1lID0gJy90bXAvZ18xOTkwLnBuZycpCmdnc2F2ZShnLjE5NTAsZmlsZW5hbWUgPSAnL3RtcC9nXzE5NTAucG5nJykKZ2dzYXZlKGcuMTkwMCxmaWxlbmFtZSA9ICcvdG1wL2dfMTkwMC5wbmcnKQoKYW5pbWF0aW9uOjppbS5jb252ZXJ0KGZpbGVzID0gJy90bXAvZ18qLnBuZycsIG91dHB1dCA9ICAnYW5pbWF0aW9uLTAxLmdpZicpCnVubGluaygnL3RtcC9nXyoucG5nJywgZm9yY2UgPSBUUlVFKQpgYGAKCgpgYGB7ciAxMiwgZXZhbD1GQUxTRX0KbGlicmFyeShnZ3Bsb3QyKQoKdXNhIDwtIGFsYmVyc3VzYTo6dXNhX3NmKCkKCnVzYV9wb3BfaGlzdG9yeSA8LSByZWFkcjo6cmVhZF90c3Yoc3lzdGVtLmZpbGUoCidleHRkYXRhJywndXNhX3BvcF9oaXN0b3J5LnRzdicsIHBhY2thZ2U9J3VzZXIyMDE3Lmdlb2RhdGF2aXonKSkKCnVzYSA8LSBkcGx5cjo6bGVmdF9qb2luKHVzYSwgdXNhX3BvcF9oaXN0b3J5LCBieT1jKCduYW1lJz0nU3RhdGUnKSkKCmcgPC0gZ2dwbG90KGRhdGE9dXNhKSArIGdndGhlbWVzOjp0aGVtZV9tYXAoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdib3R0b20nKSArCiAgdmlyaWRpczo6c2NhbGVfZmlsbF92aXJpZGlzKGRpcmVjdGlvbiA9IC0xLCBvcHRpb24gPSAnQScpCgpnLjIwMTUgPC0gZyArIGdlb21fc2YoYWVzKGZpbGw9cC4yMDE1KSkgKyBjb29yZF9zZihjcnMgPSBhbGJlcnN1c2E6OnVzX2FlcWRfcHJvaikKZy4yMDEwIDwtIGcgKyBnZW9tX3NmKGFlcyhmaWxsPXAuMjAxMCkpICsgY29vcmRfc2YoY3JzID0gYWxiZXJzdXNhOjp1c19hZXFkX3Byb2opCmcuMjAwMCA8LSBnICsgZ2VvbV9zZihhZXMoZmlsbD1wLjIwMDApKSArIGNvb3JkX3NmKGNycyA9IGFsYmVyc3VzYTo6dXNfYWVxZF9wcm9qKQpnLjE5OTAgPC0gZyArIGdlb21fc2YoYWVzKGZpbGw9cC4xOTkwKSkgKyBjb29yZF9zZihjcnMgPSBhbGJlcnN1c2E6OnVzX2FlcWRfcHJvaikKZy4xOTUwIDwtIGcgKyBnZW9tX3NmKGFlcyhmaWxsPXAuMTk1MCkpICsgY29vcmRfc2YoY3JzID0gYWxiZXJzdXNhOjp1c19hZXFkX3Byb2opCmcuMTkwMCA8LSBnICsgZ2VvbV9zZihhZXMoZmlsbD1wLjE5MDApKSArIGNvb3JkX3NmKGNycyA9IGFsYmVyc3VzYTo6dXNfYWVxZF9wcm9qKQoKIyBTYXZlIGVhY2ggZmlsZQpnZ3NhdmUoZy4yMDE1LGZpbGVuYW1lID0gJy90bXAvZ18yMDE1LnBuZycpCmdnc2F2ZShnLjIwMTAsZmlsZW5hbWUgPSAnL3RtcC9nXzIwMTAucG5nJykKZ2dzYXZlKGcuMjAwMCxmaWxlbmFtZSA9ICcvdG1wL2dfMjAwMC5wbmcnKQpnZ3NhdmUoZy4xOTkwLGZpbGVuYW1lID0gJy90bXAvZ18xOTkwLnBuZycpCmdnc2F2ZShnLjE5NTAsZmlsZW5hbWUgPSAnL3RtcC9nXzE5NTAucG5nJykKZ2dzYXZlKGcuMTkwMCxmaWxlbmFtZSA9ICcvdG1wL2dfMTkwMC5wbmcnKQoKYW5pbWF0aW9uOjppbS5jb252ZXJ0KGZpbGVzID0gJy90bXAvZ18qLnBuZycsIG91dHB1dCA9ICAnYW5pbWF0aW9uLTAxLmdpZicpCnVubGluaygnL3RtcC9nXyoucG5nJywgZm9yY2UgPSBUUlVFKQpgYGAKCiFbXShhbmltYXRpb24tMDEuZ2lmKQo=