library(fs)
library(glue)
library(readr)
library(dplyr)
library(tidyr)
library(knitr)
library(purrr)
library(sf)
library(ggplot2)
library(scales)

Create and define a folder for data

data_dir <- "census-pop-data"
if(!dir.exists(data_dir)){fs::dir_create(data_dir)}

Sub-county Population Data

Download 2000-2010 data

citytown_filepath <- "https://www2.census.gov/programs-surveys/popest/datasets/2000-2010/intercensal/cities/sub-est00int.csv"
citytown_file_local <- glue::glue("{data_dir}/{basename(citytown_filepath)}")
if(!file.exists(citytown_file_local)){
  download.file(citytown_filepath, destfile = citytown_file_local)
}

Download 2010-2018 data

citytown_filepath2 <- "https://www2.census.gov/programs-surveys/popest/datasets/2010-2018/cities/totals/sub-est2018_all.csv"
citytown_file2_local <- glue::glue("{data_dir}/{basename(citytown_filepath2)}")
if(!file.exists(citytown_file2_local)){
  download.file(citytown_filepath2, destfile = citytown_file2_local)
  }

Make a function to read in and reshape

reshape_pop_data <- function(citytown_file_local){
  citytown_long <- readr::read_csv(citytown_file_local) %>%
    dplyr::group_by(SUMLEV, STATE, COUNTY, PLACE, COUSUB, NAME, STNAME) %>%
    # convert columns to rows
    tidyr::pivot_longer(cols = dplyr::contains(match = c("POPESTIMATE")), 
                      names_to = "estimate", values_to = "population") %>%
    dplyr::select(SUMLEV, STATE, COUNTY, PLACE, COUSUB, NAME, STNAME, estimate, population) %>%
  # separate year and estimate type into separate columns 
  dplyr::mutate(year = stringr::str_extract(estimate, "\\d+")) %>%
  dplyr::mutate(year = as.numeric(year)) %>%
  dplyr::select(-estimate)
  return(citytown_long)
}

Download population data for metropolitan and micropolitan areas

cbsa_url <- "https://www2.census.gov/programs-surveys/popest/datasets/2010-2018/metro/totals/cbsa-est2018-alldata.csv"
cbsa_file_local <- glue::glue("{data_dir}/{basename(cbsa_url)}")
if(!file.exists(cbsa_file_local)){
  download.file(cbsa_url, destfile = cbsa_file_local)
  }
# read in and reshape to fit with subcounty data
cbsa_pop <- cbsa_file_local %>%
  readr::read_csv() %>%
  dplyr::select(1:16) %>%
  tidyr::pivot_longer(cols = dplyr::contains(match = c("POPESTIMATE")), 
                      names_to = "estimate", values_to = "population") %>%
  dplyr::mutate(year = stringr::str_extract(estimate, "\\d+")) %>%
  dplyr::mutate(year = as.numeric(year), SUMLEV = "320") %>%
  dplyr::mutate(STATE = NA, COUNTY = NA, PLACE = NA, COUSUB = NA, STNAME = NA) %>%
  dplyr::select(SUMLEV, STATE, COUNTY, PLACE, COUSUB, NAME, population, year)

Make one data frame with 3 datasets combined (CBSA, 2000-2010 subcounty, 2010-2018 subcounty)

popdata_long <- list(citytown_file_local, citytown_file2_local) %>%
  purrr::map(~reshape_pop_data(.x)) %>% 
  bind_rows() %>% 
  bind_rows(cbsa_pop)

Download spatial data

# Counties
county_shp <- glue("{data_dir}/tl_2019_us_county.shp")
if(!file.exists(county_shp)){
  county2019_url <- 'https://www2.census.gov/geo/tiger/TIGER2019/COUNTY/tl_2019_us_county.zip' 
  tmp <- tempfile()
  download.file(county2019_url, destfile = tmp)
  unzip(zipfile = tmp, exdir = data_dir)
}
# Core Based Statistical Areas
cbsa_shp <- glue("{data_dir}/tl_2019_us_cbsa.shp")
if(!file.exists(cbsa_shp)){
  cbsa2019_url <- 'https://www2.census.gov/geo/tiger/TIGER2019/CBSA/tl_2019_us_cbsa.zip' 
  tmp <- tempfile()
  download.file(cbsa2019_url, destfile = tmp)
  unzip(zipfile = tmp, exdir = data_dir)
}
# Places in MD (State code 24)
md_places_shp <- glue("{data_dir}/tl_2019_24_place.shp")
if(!file.exists(md_places_shp)){
  md_places_url <- 'https://www2.census.gov/geo/tiger/TIGER2019/PLACE/tl_2019_24_place.zip'
  tmp <- tempfile()
  download.file(md_places_url, destfile = tmp)
  unzip(zipfile = tmp, exdir = data_dir)
}
# Water Areas for MD (State code 24)
if(!any(fs::dir_ls(data_dir, recurse = TRUE, glob = "areawater"))){
  system(glue('wget -r -np -A "tl_2019_24*_areawater.zip" ftp://ftp2.census.gov/geo/tiger/TIGER2019/AREAWATER -P {data_dir}'))
  zipfiles <- fs::dir_ls(data_dir, recurse = TRUE, glob = "*areawater.zip")
  purrr::walk(zipfiles, ~unzip(zipfile = .x, exdir = data_dir))
}

Where is SESYNC?

counties_sf <- sf::st_read(county_shp)
counties_md <- dplyr::filter(counties_sf, STATEFP == "24")
cbsa_sf <- sf::st_read(cbsa_shp)
places_sf <- sf::st_read(md_places_shp)

sesync <- st_sfc(st_point(
  c(-76.503394, 38.976546)),
  crs = st_crs(counties_sf))

sesync_county_sf <- counties_sf[sesync,]
sesync_cbsa_sf <- cbsa_sf[sesync,]
sesync_place_sf <- places_sf[sesync,]

sesync_county <- sesync_county_sf[["NAMELSAD"]] %>% as.character()
sesync_place <- sesync_place_sf[["NAMELSAD"]] %>% as.character()
sesync_cbsa <- sesync_cbsa_sf[["NAME"]] %>% as.character()

sesync_ids <- data.frame(SUMLEV = c("050", "157", "320"),
                         NAME = c(sesync_county, sesync_place, sesync_cbsa),
                         stringsAsFactors = FALSE)

Use this info to filter the population data

# The key for SUMLEV is as follows:
# 040 = State
# 050 = County
# 061 = Minor Civil Division
# 071 = Minor Civil Division place part
# 157 = County place part
# 162 = Incorporated place
# 170 = Consolidated city
# 172 = Consolidated city -- place within consolidated city

sesync_ids <- data.frame(SUMLEV = c("050", "157", "320"),
                         NAME = c(as.character(sesync_county_sf[["NAMELSAD"]]),
                                  as.character(sesync_place_sf[["NAMELSAD"]]),
                                  as.character(sesync_cbsa_sf[["NAME"]])))
sesync_pops <- popdata_long %>% 
    dplyr::filter(SUMLEV %in% sesync_ids[["SUMLEV"]]) %>%
    ungroup() %>% 
    dplyr::group_split(SUMLEV) %>%
    purrr::map(~dplyr::inner_join(.x, sesync_ids)) %>% 
  bind_rows() %>% 
  # get rid of Annapolis city, Missouri!
  dplyr::filter(STATE == "24" | is.na(STATE)) 

Plots

# pop_trends <-
  sesync_pops %>%
  ggplot(aes(x = year, y = population, group = NAME)) +
  geom_line(aes(col = NAME), lwd = 2) +
  facet_wrap(vars(NAME), scales = "free_y", ncol = 1) +
  theme_dark() +
  theme(legend.position = "none") +
  xlab(element_blank()) +
  ylab(element_blank()) +
  scale_y_continuous(labels = scales::comma) +
  scale_color_manual(values = c(
    `Baltimore-Columbia-Towson, MD` = "purple",
    `Anne Arundel County` = "red",
    `Annapolis city` = "black"
  )) +
  theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
  ggtitle("Population Estimates")


# ggsave("popdata.png", 
#        pop_trends, 
#        bg = "transparent", 
#        width = 4.5, height = 5, units = "in")

Maps

# make list of water area polygons intersecting county
water_sfs_list <- list.files(recursive = TRUE, full.names = TRUE, pattern = "_areawater.shp$") %>%
  purrr::map(~st_read(.x, quiet = TRUE)) %>% 
  purrr::map(~st_union(.x)) %>%
  purrr::map(~st_intersection(.x, counties_md))
md_map <-
  sesync_cbsa_sf %>%
  ggplot() +
  geom_sf(data = counties_md, fill = "white") +
  geom_sf(fill = "purple", alpha = 0.5, col = "purple", lwd = 2) +
  geom_sf(data = sesync, pch = 4, col = "green") +
  theme_minimal()  +
  theme(axis.text = element_blank(),
        panel.grid.major = element_blank(), 
        panel.grid.minor = element_blank(),
        panel.background = element_rect(fill = "transparent",colour = NA),
        plot.background = element_rect(fill = "transparent",colour = NA))

for (i in 1:length(water_sfs_list)) { 
  md_map <- md_map + geom_sf(data = water_sfs_list[[i]], fill = "dodger blue", lwd = 0)
}

md_map <- md_map + geom_sf(data = sesync_cbsa_sf, fill = NA, col = "purple", lwd = 2)
md_map


# ggsave("md.png", md_map, bg = "transparent")
cbsa_map <-
  sesync_county_sf %>%
  ggplot() +
  geom_sf(data = counties_md, fill = "white") +
  geom_sf(fill = "red", col = "red", lwd = 2, alpha = 0.5) +
  geom_sf(data = sesync, pch = 4, col = "green") +
  theme_minimal()  +
  theme(axis.text = element_blank(),
        panel.grid.major = element_blank(), 
        panel.grid.minor = element_blank(),
        panel.background = element_rect(fill = "transparent",colour = NA),
        plot.background = element_rect(fill = "transparent",colour = NA))

for (i in 1:length(water_sfs_list)) { 
  cbsa_map <- cbsa_map + geom_sf(data = water_sfs_list[[i]], fill = "dodger blue", lwd = 0)
}

cbsa_map <- cbsa_map + geom_sf(data = sesync_county_sf, fill = NA, col = "red", lwd = 2)
cbsa_map

# ggsave("cbsa_map..png", cbsa_map, bg = "transparent")
county_bbox <- sesync_county_sf %>% st_bbox()

map1 <- sesync_county_sf %>%
  ggplot() +
  geom_sf(fill = "white", col = "red") +
  geom_sf(data = sesync_place_sf, fill = "black", col = "black", alpha = 0.5) +
  geom_sf(data = sesync, pch = 4, col = "green") +
  theme_minimal()  +
  theme(axis.text = element_blank(),
        panel.grid.major = element_blank(), 
        panel.grid.minor = element_blank())

for (i in 1:length(water_sfs_list)) { 
  map1 <- map1 + geom_sf(data = water_sfs_list[[i]], fill = "dodger blue", lwd = 0)
}

# aa_county <- 
map1 +  
  geom_sf(data = sesync_place_sf, fill = NA, col = "black", lwd = 1.5) +
  coord_sf(xlim = c(county_bbox[1], county_bbox[3]),
           ylim = c(county_bbox[2], county_bbox[4])) +
  theme(panel.grid.major = element_blank(), 
    panel.grid.minor = element_blank(),
    panel.background = element_rect(fill = "transparent",colour = NA),
    plot.background = element_rect(fill = "transparent",colour = NA))


# ggsave("aacounty.png", aa_county, bg = "transparent")

Interactive map

LS0tCnRpdGxlOiAiTXVsdGktc2NhbGUgUG9wdWxhdGlvbiBFc3RpbWF0ZXMiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdG9jOiB5ZXMKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSkKYGBgCgpgYGB7cn0KbGlicmFyeShmcykKbGlicmFyeShnbHVlKQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KHB1cnJyKQpsaWJyYXJ5KHNmKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoc2NhbGVzKQpgYGAKCkNyZWF0ZSBhbmQgZGVmaW5lIGEgZm9sZGVyIGZvciBkYXRhIAoKYGBge3J9CmRhdGFfZGlyIDwtICJjZW5zdXMtcG9wLWRhdGEiCmlmKCFkaXIuZXhpc3RzKGRhdGFfZGlyKSl7ZnM6OmRpcl9jcmVhdGUoZGF0YV9kaXIpfQpgYGAKCiMgU3ViLWNvdW50eSBQb3B1bGF0aW9uIERhdGEKCkRvd25sb2FkIDIwMDAtMjAxMCBkYXRhCgpgYGB7cn0KY2l0eXRvd25fZmlsZXBhdGggPC0gImh0dHBzOi8vd3d3Mi5jZW5zdXMuZ292L3Byb2dyYW1zLXN1cnZleXMvcG9wZXN0L2RhdGFzZXRzLzIwMDAtMjAxMC9pbnRlcmNlbnNhbC9jaXRpZXMvc3ViLWVzdDAwaW50LmNzdiIKY2l0eXRvd25fZmlsZV9sb2NhbCA8LSBnbHVlOjpnbHVlKCJ7ZGF0YV9kaXJ9L3tiYXNlbmFtZShjaXR5dG93bl9maWxlcGF0aCl9IikKaWYoIWZpbGUuZXhpc3RzKGNpdHl0b3duX2ZpbGVfbG9jYWwpKXsKICBkb3dubG9hZC5maWxlKGNpdHl0b3duX2ZpbGVwYXRoLCBkZXN0ZmlsZSA9IGNpdHl0b3duX2ZpbGVfbG9jYWwpCn0KYGBgCgpEb3dubG9hZCAyMDEwLTIwMTggZGF0YQoKYGBge3J9CmNpdHl0b3duX2ZpbGVwYXRoMiA8LSAiaHR0cHM6Ly93d3cyLmNlbnN1cy5nb3YvcHJvZ3JhbXMtc3VydmV5cy9wb3Blc3QvZGF0YXNldHMvMjAxMC0yMDE4L2NpdGllcy90b3RhbHMvc3ViLWVzdDIwMThfYWxsLmNzdiIKY2l0eXRvd25fZmlsZTJfbG9jYWwgPC0gZ2x1ZTo6Z2x1ZSgie2RhdGFfZGlyfS97YmFzZW5hbWUoY2l0eXRvd25fZmlsZXBhdGgyKX0iKQppZighZmlsZS5leGlzdHMoY2l0eXRvd25fZmlsZTJfbG9jYWwpKXsKICBkb3dubG9hZC5maWxlKGNpdHl0b3duX2ZpbGVwYXRoMiwgZGVzdGZpbGUgPSBjaXR5dG93bl9maWxlMl9sb2NhbCkKICB9CmBgYAoKTWFrZSBhIGZ1bmN0aW9uIHRvIHJlYWQgaW4gYW5kIHJlc2hhcGUgCgpgYGB7cn0KcmVzaGFwZV9wb3BfZGF0YSA8LSBmdW5jdGlvbihjaXR5dG93bl9maWxlX2xvY2FsKXsKICBjaXR5dG93bl9sb25nIDwtIHJlYWRyOjpyZWFkX2NzdihjaXR5dG93bl9maWxlX2xvY2FsKSAlPiUKICAgIGRwbHlyOjpncm91cF9ieShTVU1MRVYsIFNUQVRFLCBDT1VOVFksIFBMQUNFLCBDT1VTVUIsIE5BTUUsIFNUTkFNRSkgJT4lCiAgICAjIGNvbnZlcnQgY29sdW1ucyB0byByb3dzCiAgICB0aWR5cjo6cGl2b3RfbG9uZ2VyKGNvbHMgPSBkcGx5cjo6Y29udGFpbnMobWF0Y2ggPSBjKCJQT1BFU1RJTUFURSIpKSwgCiAgICAgICAgICAgICAgICAgICAgICBuYW1lc190byA9ICJlc3RpbWF0ZSIsIHZhbHVlc190byA9ICJwb3B1bGF0aW9uIikgJT4lCiAgICBkcGx5cjo6c2VsZWN0KFNVTUxFViwgU1RBVEUsIENPVU5UWSwgUExBQ0UsIENPVVNVQiwgTkFNRSwgU1ROQU1FLCBlc3RpbWF0ZSwgcG9wdWxhdGlvbikgJT4lCiAgIyBzZXBhcmF0ZSB5ZWFyIGFuZCBlc3RpbWF0ZSB0eXBlIGludG8gc2VwYXJhdGUgY29sdW1ucyAKICBkcGx5cjo6bXV0YXRlKHllYXIgPSBzdHJpbmdyOjpzdHJfZXh0cmFjdChlc3RpbWF0ZSwgIlxcZCsiKSkgJT4lCiAgZHBseXI6Om11dGF0ZSh5ZWFyID0gYXMubnVtZXJpYyh5ZWFyKSkgJT4lCiAgZHBseXI6OnNlbGVjdCgtZXN0aW1hdGUpCiAgcmV0dXJuKGNpdHl0b3duX2xvbmcpCn0KCmBgYAoKRG93bmxvYWQgcG9wdWxhdGlvbiBkYXRhIGZvciBtZXRyb3BvbGl0YW4gYW5kIG1pY3JvcG9saXRhbiBhcmVhcwoKYGBge3J9CmNic2FfdXJsIDwtICJodHRwczovL3d3dzIuY2Vuc3VzLmdvdi9wcm9ncmFtcy1zdXJ2ZXlzL3BvcGVzdC9kYXRhc2V0cy8yMDEwLTIwMTgvbWV0cm8vdG90YWxzL2Nic2EtZXN0MjAxOC1hbGxkYXRhLmNzdiIKY2JzYV9maWxlX2xvY2FsIDwtIGdsdWU6OmdsdWUoIntkYXRhX2Rpcn0ve2Jhc2VuYW1lKGNic2FfdXJsKX0iKQppZighZmlsZS5leGlzdHMoY2JzYV9maWxlX2xvY2FsKSl7CiAgZG93bmxvYWQuZmlsZShjYnNhX3VybCwgZGVzdGZpbGUgPSBjYnNhX2ZpbGVfbG9jYWwpCiAgfQpgYGAKYGBge3J9CiMgcmVhZCBpbiBhbmQgcmVzaGFwZSB0byBmaXQgd2l0aCBzdWJjb3VudHkgZGF0YQpjYnNhX3BvcCA8LSBjYnNhX2ZpbGVfbG9jYWwgJT4lCiAgcmVhZHI6OnJlYWRfY3N2KCkgJT4lCiAgZHBseXI6OnNlbGVjdCgxOjE2KSAlPiUKICB0aWR5cjo6cGl2b3RfbG9uZ2VyKGNvbHMgPSBkcGx5cjo6Y29udGFpbnMobWF0Y2ggPSBjKCJQT1BFU1RJTUFURSIpKSwgCiAgICAgICAgICAgICAgICAgICAgICBuYW1lc190byA9ICJlc3RpbWF0ZSIsIHZhbHVlc190byA9ICJwb3B1bGF0aW9uIikgJT4lCiAgZHBseXI6Om11dGF0ZSh5ZWFyID0gc3RyaW5ncjo6c3RyX2V4dHJhY3QoZXN0aW1hdGUsICJcXGQrIikpICU+JQogIGRwbHlyOjptdXRhdGUoeWVhciA9IGFzLm51bWVyaWMoeWVhciksIFNVTUxFViA9ICIzMjAiKSAlPiUKICBkcGx5cjo6bXV0YXRlKFNUQVRFID0gTkEsIENPVU5UWSA9IE5BLCBQTEFDRSA9IE5BLCBDT1VTVUIgPSBOQSwgU1ROQU1FID0gTkEpICU+JQogIGRwbHlyOjpzZWxlY3QoU1VNTEVWLCBTVEFURSwgQ09VTlRZLCBQTEFDRSwgQ09VU1VCLCBOQU1FLCBwb3B1bGF0aW9uLCB5ZWFyKQpgYGAKCgpNYWtlIG9uZSBkYXRhIGZyYW1lIHdpdGggMyBkYXRhc2V0cyBjb21iaW5lZCAoQ0JTQSwgMjAwMC0yMDEwIHN1YmNvdW50eSwgMjAxMC0yMDE4IHN1YmNvdW50eSkKCmBgYHtyfQpwb3BkYXRhX2xvbmcgPC0gbGlzdChjaXR5dG93bl9maWxlX2xvY2FsLCBjaXR5dG93bl9maWxlMl9sb2NhbCkgJT4lCiAgcHVycnI6Om1hcCh+cmVzaGFwZV9wb3BfZGF0YSgueCkpICU+JSAKICBiaW5kX3Jvd3MoKSAlPiUgCiAgYmluZF9yb3dzKGNic2FfcG9wKQpgYGAKCmBgYHtyLCBpbmNsdWRlPUZBTFNFLCBlY2hvPUZBTFNFLCByZXN1bHRzPSJhc2lzIn0KaGVhZChwb3BkYXRhX2xvbmcpICU+JSBrbml0cjo6a2FibGUoKQpgYGAKCiMgRG93bmxvYWQgc3BhdGlhbCBkYXRhCgpgYGB7cn0KIyBDb3VudGllcwpjb3VudHlfc2hwIDwtIGdsdWUoIntkYXRhX2Rpcn0vdGxfMjAxOV91c19jb3VudHkuc2hwIikKaWYoIWZpbGUuZXhpc3RzKGNvdW50eV9zaHApKXsKICBjb3VudHkyMDE5X3VybCA8LSAnaHR0cHM6Ly93d3cyLmNlbnN1cy5nb3YvZ2VvL3RpZ2VyL1RJR0VSMjAxOS9DT1VOVFkvdGxfMjAxOV91c19jb3VudHkuemlwJyAKICB0bXAgPC0gdGVtcGZpbGUoKQogIGRvd25sb2FkLmZpbGUoY291bnR5MjAxOV91cmwsIGRlc3RmaWxlID0gdG1wKQogIHVuemlwKHppcGZpbGUgPSB0bXAsIGV4ZGlyID0gZGF0YV9kaXIpCn0KIyBDb3JlIEJhc2VkIFN0YXRpc3RpY2FsIEFyZWFzCmNic2Ffc2hwIDwtIGdsdWUoIntkYXRhX2Rpcn0vdGxfMjAxOV91c19jYnNhLnNocCIpCmlmKCFmaWxlLmV4aXN0cyhjYnNhX3NocCkpewogIGNic2EyMDE5X3VybCA8LSAnaHR0cHM6Ly93d3cyLmNlbnN1cy5nb3YvZ2VvL3RpZ2VyL1RJR0VSMjAxOS9DQlNBL3RsXzIwMTlfdXNfY2JzYS56aXAnIAogIHRtcCA8LSB0ZW1wZmlsZSgpCiAgZG93bmxvYWQuZmlsZShjYnNhMjAxOV91cmwsIGRlc3RmaWxlID0gdG1wKQogIHVuemlwKHppcGZpbGUgPSB0bXAsIGV4ZGlyID0gZGF0YV9kaXIpCn0KIyBQbGFjZXMgaW4gTUQgKFN0YXRlIGNvZGUgMjQpCm1kX3BsYWNlc19zaHAgPC0gZ2x1ZSgie2RhdGFfZGlyfS90bF8yMDE5XzI0X3BsYWNlLnNocCIpCmlmKCFmaWxlLmV4aXN0cyhtZF9wbGFjZXNfc2hwKSl7CiAgbWRfcGxhY2VzX3VybCA8LSAnaHR0cHM6Ly93d3cyLmNlbnN1cy5nb3YvZ2VvL3RpZ2VyL1RJR0VSMjAxOS9QTEFDRS90bF8yMDE5XzI0X3BsYWNlLnppcCcKICB0bXAgPC0gdGVtcGZpbGUoKQogIGRvd25sb2FkLmZpbGUobWRfcGxhY2VzX3VybCwgZGVzdGZpbGUgPSB0bXApCiAgdW56aXAoemlwZmlsZSA9IHRtcCwgZXhkaXIgPSBkYXRhX2RpcikKfQojIFdhdGVyIEFyZWFzIGZvciBNRCAoU3RhdGUgY29kZSAyNCkKaWYoIWFueShmczo6ZGlyX2xzKGRhdGFfZGlyLCByZWN1cnNlID0gVFJVRSwgZ2xvYiA9ICJhcmVhd2F0ZXIiKSkpewogIHN5c3RlbShnbHVlKCd3Z2V0IC1yIC1ucCAtQSAidGxfMjAxOV8yNCpfYXJlYXdhdGVyLnppcCIgZnRwOi8vZnRwMi5jZW5zdXMuZ292L2dlby90aWdlci9USUdFUjIwMTkvQVJFQVdBVEVSIC1QIHtkYXRhX2Rpcn0nKSkKICB6aXBmaWxlcyA8LSBmczo6ZGlyX2xzKGRhdGFfZGlyLCByZWN1cnNlID0gVFJVRSwgZ2xvYiA9ICIqYXJlYXdhdGVyLnppcCIpCiAgcHVycnI6OndhbGsoemlwZmlsZXMsIH51bnppcCh6aXBmaWxlID0gLngsIGV4ZGlyID0gZGF0YV9kaXIpKQp9CgpgYGAKCiMgV2hlcmUgaXMgU0VTWU5DPwoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmNvdW50aWVzX3NmIDwtIHNmOjpzdF9yZWFkKGNvdW50eV9zaHApCmNvdW50aWVzX21kIDwtIGRwbHlyOjpmaWx0ZXIoY291bnRpZXNfc2YsIFNUQVRFRlAgPT0gIjI0IikKY2JzYV9zZiA8LSBzZjo6c3RfcmVhZChjYnNhX3NocCkKcGxhY2VzX3NmIDwtIHNmOjpzdF9yZWFkKG1kX3BsYWNlc19zaHApCgpzZXN5bmMgPC0gc3Rfc2ZjKHN0X3BvaW50KAogIGMoLTc2LjUwMzM5NCwgMzguOTc2NTQ2KSksCiAgY3JzID0gc3RfY3JzKGNvdW50aWVzX3NmKSkKCnNlc3luY19jb3VudHlfc2YgPC0gY291bnRpZXNfc2Zbc2VzeW5jLF0Kc2VzeW5jX2Nic2Ffc2YgPC0gY2JzYV9zZltzZXN5bmMsXQpzZXN5bmNfcGxhY2Vfc2YgPC0gcGxhY2VzX3NmW3Nlc3luYyxdCgpzZXN5bmNfY291bnR5IDwtIHNlc3luY19jb3VudHlfc2ZbWyJOQU1FTFNBRCJdXSAlPiUgYXMuY2hhcmFjdGVyKCkKc2VzeW5jX3BsYWNlIDwtIHNlc3luY19wbGFjZV9zZltbIk5BTUVMU0FEIl1dICU+JSBhcy5jaGFyYWN0ZXIoKQpzZXN5bmNfY2JzYSA8LSBzZXN5bmNfY2JzYV9zZltbIk5BTUUiXV0gJT4lIGFzLmNoYXJhY3RlcigpCgpzZXN5bmNfaWRzIDwtIGRhdGEuZnJhbWUoU1VNTEVWID0gYygiMDUwIiwgIjE1NyIsICIzMjAiKSwKICAgICAgICAgICAgICAgICAgICAgICAgIE5BTUUgPSBjKHNlc3luY19jb3VudHksIHNlc3luY19wbGFjZSwgc2VzeW5jX2Nic2EpLAogICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpgYGAKCmBgYHtyLCBpbmNsdWRlPUZBTFNFLCBlY2hvPUZBTFNFLCByZXN1bHRzPSJhc2lzIn0Ka25pdHI6OmthYmxlKHNlc3luY19pZHMpCmBgYAoKVXNlIHRoaXMgaW5mbyB0byBmaWx0ZXIgdGhlIHBvcHVsYXRpb24gZGF0YQoKYGBge3J9CiMgVGhlIGtleSBmb3IgU1VNTEVWIGlzIGFzIGZvbGxvd3M6CiMgMDQwID0gU3RhdGUKIyAwNTAgPSBDb3VudHkKIyAwNjEgPSBNaW5vciBDaXZpbCBEaXZpc2lvbgojIDA3MSA9IE1pbm9yIENpdmlsIERpdmlzaW9uIHBsYWNlIHBhcnQKIyAxNTcgPSBDb3VudHkgcGxhY2UgcGFydAojIDE2MiA9IEluY29ycG9yYXRlZCBwbGFjZQojIDE3MCA9IENvbnNvbGlkYXRlZCBjaXR5CiMgMTcyID0gQ29uc29saWRhdGVkIGNpdHkgLS0gcGxhY2Ugd2l0aGluIGNvbnNvbGlkYXRlZCBjaXR5CiMgMzIwID0gQ29yZSBiYXNlZCBzdGF0aXN0aWNhbCBhcmVhcwoKc2VzeW5jX2lkcyA8LSBkYXRhLmZyYW1lKFNVTUxFViA9IGMoIjA1MCIsICIxNTciLCAiMzIwIiksCiAgICAgICAgICAgICAgICAgICAgICAgICBOQU1FID0gYyhhcy5jaGFyYWN0ZXIoc2VzeW5jX2NvdW50eV9zZltbIk5BTUVMU0FEIl1dKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLmNoYXJhY3RlcihzZXN5bmNfcGxhY2Vfc2ZbWyJOQU1FTFNBRCJdXSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5jaGFyYWN0ZXIoc2VzeW5jX2Nic2Ffc2ZbWyJOQU1FIl1dKSkpCnNlc3luY19wb3BzIDwtIHBvcGRhdGFfbG9uZyAlPiUgCiAgICBkcGx5cjo6ZmlsdGVyKFNVTUxFViAlaW4lIHNlc3luY19pZHNbWyJTVU1MRVYiXV0pICU+JQogICAgdW5ncm91cCgpICU+JSAKICAgIGRwbHlyOjpncm91cF9zcGxpdChTVU1MRVYpICU+JQogICAgcHVycnI6Om1hcCh+ZHBseXI6OmlubmVyX2pvaW4oLngsIHNlc3luY19pZHMpKSAlPiUgCiAgYmluZF9yb3dzKCkgJT4lIAogICMgZ2V0IHJpZCBvZiBBbm5hcG9saXMgY2l0eSwgTWlzc291cmkhCiAgZHBseXI6OmZpbHRlcihTVEFURSA9PSAiMjQiIHwgaXMubmEoU1RBVEUpKSAKYGBgCgojIFBsb3RzIAoKYGBge3J9CiMgcG9wX3RyZW5kcyA8LQogIHNlc3luY19wb3BzICU+JQogIGdncGxvdChhZXMoeCA9IHllYXIsIHkgPSBwb3B1bGF0aW9uLCBncm91cCA9IE5BTUUpKSArCiAgZ2VvbV9saW5lKGFlcyhjb2wgPSBOQU1FKSwgbHdkID0gMikgKwogIGZhY2V0X3dyYXAodmFycyhOQU1FKSwgc2NhbGVzID0gImZyZWVfeSIsIG5jb2wgPSAxKSArCiAgdGhlbWVfZGFyaygpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICB4bGFiKGVsZW1lbnRfYmxhbmsoKSkgKwogIHlsYWIoZWxlbWVudF9ibGFuaygpKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6Y29tbWEpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygKICAgIGBCYWx0aW1vcmUtQ29sdW1iaWEtVG93c29uLCBNRGAgPSAicHVycGxlIiwKICAgIGBBbm5lIEFydW5kZWwgQ291bnR5YCA9ICJyZWQiLAogICAgYEFubmFwb2xpcyBjaXR5YCA9ICJibGFjayIKICApKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkgKwogIGdndGl0bGUoIlBvcHVsYXRpb24gRXN0aW1hdGVzIikKCiMgZ2dzYXZlKCJwb3BkYXRhLnBuZyIsIAojICAgICAgICBwb3BfdHJlbmRzLCAKIyAgICAgICAgYmcgPSAidHJhbnNwYXJlbnQiLCAKIyAgICAgICAgd2lkdGggPSA0LjUsIGhlaWdodCA9IDUsIHVuaXRzID0gImluIikKYGBgCgojIE1hcHMKCmBgYHtyfQojIG1ha2UgbGlzdCBvZiB3YXRlciBhcmVhIHBvbHlnb25zIGludGVyc2VjdGluZyBNYXJ5bGFuZAp3YXRlcl9zZnNfbGlzdCA8LSBsaXN0LmZpbGVzKHJlY3Vyc2l2ZSA9IFRSVUUsIGZ1bGwubmFtZXMgPSBUUlVFLCBwYXR0ZXJuID0gIl9hcmVhd2F0ZXIuc2hwJCIpICU+JQogIHB1cnJyOjptYXAofnN0X3JlYWQoLngsIHF1aWV0ID0gVFJVRSkpICU+JSAKICBwdXJycjo6bWFwKH5zdF91bmlvbigueCkpICU+JQogIHB1cnJyOjptYXAofnN0X2ludGVyc2VjdGlvbigueCwgY291bnRpZXNfbWQpKQpgYGAKCmBgYHtyfQptZF9tYXAgPC0KICBzZXN5bmNfY2JzYV9zZiAlPiUKICBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gY291bnRpZXNfbWQsIGZpbGwgPSAid2hpdGUiKSArCiAgZ2VvbV9zZihmaWxsID0gInB1cnBsZSIsIGFscGhhID0gMC41LCBjb2wgPSAicHVycGxlIiwgbHdkID0gMikgKwogIGdlb21fc2YoZGF0YSA9IHNlc3luYywgcGNoID0gNCwgY29sID0gImdyZWVuIikgKwogIHRoZW1lX21pbmltYWwoKSAgKwogIHRoZW1lKGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsY29sb3VyID0gTkEpLAogICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50Iixjb2xvdXIgPSBOQSkpCgpmb3IgKGkgaW4gMTpsZW5ndGgod2F0ZXJfc2ZzX2xpc3QpKSB7IAogIG1kX21hcCA8LSBtZF9tYXAgKyBnZW9tX3NmKGRhdGEgPSB3YXRlcl9zZnNfbGlzdFtbaV1dLCBmaWxsID0gImRvZGdlciBibHVlIiwgbHdkID0gMCkKfQoKbWRfbWFwIDwtIG1kX21hcCArIGdlb21fc2YoZGF0YSA9IHNlc3luY19jYnNhX3NmLCBmaWxsID0gTkEsIGNvbCA9ICJwdXJwbGUiLCBsd2QgPSAyKQptZF9tYXAKCiMgZ2dzYXZlKCJtZC5wbmciLCBtZF9tYXAsIGJnID0gInRyYW5zcGFyZW50IikKCmBgYAoKYGBge3J9CmNic2FfbWFwIDwtCiAgc2VzeW5jX2NvdW50eV9zZiAlPiUKICBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gY291bnRpZXNfbWQsIGZpbGwgPSAid2hpdGUiKSArCiAgZ2VvbV9zZihmaWxsID0gInJlZCIsIGNvbCA9ICJyZWQiLCBsd2QgPSAyLCBhbHBoYSA9IDAuNSkgKwogIGdlb21fc2YoZGF0YSA9IHNlc3luYywgcGNoID0gNCwgY29sID0gImdyZWVuIikgKwogIHRoZW1lX21pbmltYWwoKSAgKwogIHRoZW1lKGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsY29sb3VyID0gTkEpLAogICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50Iixjb2xvdXIgPSBOQSkpCgpmb3IgKGkgaW4gMTpsZW5ndGgod2F0ZXJfc2ZzX2xpc3QpKSB7IAogIGNic2FfbWFwIDwtIGNic2FfbWFwICsgZ2VvbV9zZihkYXRhID0gd2F0ZXJfc2ZzX2xpc3RbW2ldXSwgZmlsbCA9ICJkb2RnZXIgYmx1ZSIsIGx3ZCA9IDApCn0KCmNic2FfbWFwIDwtIGNic2FfbWFwICsgZ2VvbV9zZihkYXRhID0gc2VzeW5jX2NvdW50eV9zZiwgZmlsbCA9IE5BLCBjb2wgPSAicmVkIiwgbHdkID0gMikKY2JzYV9tYXAKIyBnZ3NhdmUoImNic2FfbWFwLi5wbmciLCBjYnNhX21hcCwgYmcgPSAidHJhbnNwYXJlbnQiKQpgYGAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpjb3VudHlfYmJveCA8LSBzZXN5bmNfY291bnR5X3NmICU+JSBzdF9iYm94KCkKCm1hcDEgPC0gc2VzeW5jX2NvdW50eV9zZiAlPiUKICBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihmaWxsID0gIndoaXRlIiwgY29sID0gInJlZCIpICsKICBnZW9tX3NmKGRhdGEgPSBzZXN5bmNfcGxhY2Vfc2YsIGZpbGwgPSAiYmxhY2siLCBjb2wgPSAiYmxhY2siLCBhbHBoYSA9IDAuNSkgKwogIGdlb21fc2YoZGF0YSA9IHNlc3luYywgcGNoID0gNCwgY29sID0gImdyZWVuIikgKwogIHRoZW1lX21pbmltYWwoKSAgKwogIHRoZW1lKGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpKQoKZm9yIChpIGluIDE6bGVuZ3RoKHdhdGVyX3Nmc19saXN0KSkgeyAKICBtYXAxIDwtIG1hcDEgKyBnZW9tX3NmKGRhdGEgPSB3YXRlcl9zZnNfbGlzdFtbaV1dLCBmaWxsID0gImRvZGdlciBibHVlIiwgbHdkID0gMCkKfQoKIyBhYV9jb3VudHkgPC0gCm1hcDEgKyAgCiAgZ2VvbV9zZihkYXRhID0gc2VzeW5jX3BsYWNlX3NmLCBmaWxsID0gTkEsIGNvbCA9ICJibGFjayIsIGx3ZCA9IDEuNSkgKwogIGNvb3JkX3NmKHhsaW0gPSBjKGNvdW50eV9iYm94WzFdLCBjb3VudHlfYmJveFszXSksCiAgICAgICAgICAgeWxpbSA9IGMoY291bnR5X2Jib3hbMl0sIGNvdW50eV9iYm94WzRdKSkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksIAogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsY29sb3VyID0gTkEpLAogICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAidHJhbnNwYXJlbnQiLGNvbG91ciA9IE5BKSkKCiMgZ2dzYXZlKCJhYWNvdW50eS5wbmciLCBhYV9jb3VudHksIGJnID0gInRyYW5zcGFyZW50IikKYGBgCgoKIyBJbnRlcmFjdGl2ZSBtYXAKCmBgYHtyfQpsaWJyYXJ5KGxlYWZsZXQpCgp0aWdlcl93bXNfdXJsIDwtICJodHRwczovL3RpZ2Vyd2ViLmdlby5jZW5zdXMuZ292L2FyY2dpcy9zZXJ2aWNlcy9USUdFUndlYi90aWdlcldNU19DdXJyZW50L01hcFNlcnZlci9XTVNTZXJ2ZXIiCgpsZWFmbGV0KCkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkRXNyaS5Xb3JsZEltYWdlcnkpICU+JQogIHNldFZpZXcobG5nID0gLTc2LjUsIGxhdCA9IDM4Ljk3LCB6b29tID0gOCkgJT4lCiAgYWRkV01TVGlsZXMoCiAgICB0aWdlcl93bXNfdXJsLAogICAgbGF5ZXJzID0gYygiTWV0cm9wb2xpdGFuIFN0YXRpc3RpY2FsIEFyZWFzIiwKICAgICAgICAgICAgICAgIk1ldHJvcG9saXRhbiBTdGF0aXN0aWNhbCBBcmVhcyBMYWJlbHMiLAogICAgICAgICAgICAgICAiQ291bnRpZXMiLCAiQ291bnRpZXMgTGFiZWxzIiwKICAgICAgICAgICAgICAgIkluY29ycG9yYXRlZCBQbGFjZXMiLCAiSW5jb3Jwb3JhdGVkIFBsYWNlcyBMYWJlbHMiKSwKICAgIG9wdGlvbnMgPSBXTVNUaWxlT3B0aW9ucyhmb3JtYXQgPSAiaW1hZ2UvcG5nIiwgdHJhbnNwYXJlbnQgPSBUUlVFKQogICkKCmBgYAoKCgo=