This R Notebook demonstrates how to work with the R packages sf and ggplot2 to create basic maps. For this demo, we will create two maps showing the distribution of yellowjacket occurrences across the contiguous 48 United States.

Disclaimer: I’m still very new to working with geospatial data. Take this demo with a grain of salt, and consult more detailed tutorials and GIS courses to get a more thorough udnerstanding of principles. My aim with this demo is to demonstrate two common tasks I’ve encountered working with museum data: plotting latitude/longitude points on a map, and creating heatmaps to show how counts vary across states or countries.

Import packages

library(tidyverse)
Registered S3 method overwritten by 'dplyr':
  method           from
  print.rowwise_df     
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
-- Attaching packages --------------------------------------- tidyverse 1.3.0 --
v ggplot2 3.2.1     v purrr   0.3.3
v tibble  2.1.3     v dplyr   0.8.3
v tidyr   1.0.0     v stringr 1.4.0
v readr   1.3.1     v forcats 0.4.0
-- Conflicts ------------------------------------------ tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
library(data.table)     # Read/write large tabular data files
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
data.table 1.12.6 using 4 threads (see ?getDTthreads).  Latest news: r-datatable.com

Attaching package: 㤼㸱data.table㤼㸲

The following objects are masked from 㤼㸱package:dplyr㤼㸲:

    between, first, last

The following object is masked from 㤼㸱package:purrr㤼㸲:

    transpose
library(sf)             # Classes and functions for vector mapping data
package 㤼㸱sf㤼㸲 was built under R version 3.6.2Linking to GEOS 3.6.1, GDAL 2.2.3, PROJ 4.9.3

Geographic data and R objects

For this demo, we will be working with two geographic datasets: a shapefile containing data on the US states, and a CSV containing georeferenced occurrence data for yellowjackets in the United States.

A shapefile is a geospatial vector data format commonly used with GIS software (like ArcGIS). For this demo, we can think of it as a set of related files containing data on set of spatial features (geographic places) and their associated geometrical representations (points, lines, polygons).

There are different packages and different R objects for working with geospatial data. We will be using a package called sf, which creates a type of R object called a simple feature object. This object acts much like a data frame, where every row contains data on a geographic place (or feature). In addition, each row has an associated geometry, which contains the geometric representation of that place on a map (as a point, line, or polygon). Simple feature objects can be read into R from existing geospatial data sources (like shapefiles), or created from other file types containing geospatial data (like CSV files).

To plot our maps, we will use the ggplot2package and its built-in geom geom_sf(). This geom is designed to plot sf object data.

Coordinate Reference Systems (CRS)

Coordinate reference systems (CRS) are systems of equations and visual representations cartographers use to represent the 3D spherical Earth as 2D images. For this demo, we will use a CRS called WGS 84. We need to make sure that all our spatial data are using the WGS 84 CRS, or else our different datasets will not align properly.

WGS 84 is represented with the EPSG code 4326.

wgs84  <- 4326   # WGS 84, used in GPS. https://epsg.io/4326

For a good explanation of CRSes:

United States state geographic data

The US Census Bureau publishes shapefiles containing spatial data for different United States regions. These shapefiles are available here: https://www.census.gov/geographies/mapping-files/time-series/geo/carto-boundary-file.html

For this demo, we will be using the file cb_2018_us_state_20m.zip, which contains the names and low-resolution geographic boundaries for the US states and territories.

This file was unzipped and placed in a folder called data. The shapefile itself uses the file extension .shp, but all the files in the zip archive are required in order to successfully access the data.

Read shapefile data as sf object

We can use the sf function st_read() to read the data in as an sf object.

us_states <- st_read('data/cb_2018_us_state_20m.shp')
Reading layer `cb_2018_us_state_20m' from data source `C:\Users\Amanda\GitHub\cbb-r-mapping\data\cb_2018_us_state_20m.shp' using driver `ESRI Shapefile'
Simple feature collection with 52 features and 9 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: -179.1743 ymin: 17.91377 xmax: 179.7739 ymax: 71.35256
epsg (SRID):    4269
proj4string:    +proj=longlat +datum=NAD83 +no_defs

When we read the shapefile, we see that the current CRS (+datum) is NAD83, which is a projected coordinate system that centers on North America. We will convert the us_states data to the WGS 84 instead.

us_states <- st_transform(us_states, crs = wgs84)

Plot state data

We can use ggplot2 to do a quick plot to see how our state data are looking. When we plot sf data, we will use a geom called geom_sf(), which plots the geometry for each row in our data.

ggplot()+ geom_sf(data = us_states)

For this demo, we will focus on just the contiguous 48 states. We can simply filter our sf object as we would a normal data frame.

non_contiguous <- c('Alaska', 'Hawaii', 'Puerto Rico')
us_states_con48 <- filter(us_states, !(NAME %in% non_contiguous))

We can take a look again to see our filtered US states data.

ggplot()+ geom_sf(data = us_states_con48)

We can also create an object containing some theme settings for our map. We can then include this object for future ggplot calls.

map_theme <- theme_bw() +
    theme(axis.line = element_blank(), axis.text.x = element_blank(),
         axis.text.y = element_blank(), axis.ticks = element_blank(),
         axis.title.x = element_blank(), axis.title.y = element_blank(),
         panel.border = element_blank(), panel.grid.major = element_blank(),
         panel.grid.minor = element_blank(),
         text = element_text(size = 16), legend.text = element_text(size = 16),
         legend.title = element_text(face = "bold"))
ggplot() + 
    geom_sf(data = us_states_con48) +
    map_theme

GBIF Occurrence data

Our data on yellowjacket occurrences were downloaded from the Global Biodiversity Information Facility (GBIF). Each occurrence record contains taxonomic information, occurrence type information, and a georeferenced coordinate.

Dataset citation: GBIF.org (29 April 2020) GBIF Occurrence Download https://doi.org/10.15468/dl.44t6at

Import GBIF data

We can import the GBIF data as a standard data frame. I like to use the data.table package (and its fread() function), which works really well for parsing large files. I will also filter out records where decimalLatitude or decimalLongitude were not supplied.

gbif <- fread('data/gbif_yellowjackets.csv') %>%
    filter(!is.na(decimalLatitude), 
           !is.na(decimalLongitude))

Convert GBIF data to sf

We next need to convert this data frame to a geospatial sf object. The st_as_sf() function requires that the coordinate columns be specified (x = longitude, y = latitude). The CRS can also be specified. Most GBIF data is assumed to use the WGS 84 coordinate system.

gbif_sf <- st_as_sf(gbif, 
                    coords = c("decimalLongitude", "decimalLatitude"), 
                    crs = wgs84, 
                    remove = FALSE) # retains original lat/long columns
gbif_sf
Simple feature collection with 19814 features and 50 fields
geometry type:  POINT
dimension:      XY
bbox:           xmin: -166.5367 ymin: 19.3417 xmax: -67.2844 ymax: 71.3875
epsg (SRID):    4326
proj4string:    +proj=longlat +datum=WGS84 +no_defs
First 10 features:
       gbifID                           datasetKey
1  2603556438 50c9509d-22c7-4a22-a47d-8c48425ef4a7
2  2603542509 50c9509d-22c7-4a22-a47d-8c48425ef4a7
3  2603527237 50c9509d-22c7-4a22-a47d-8c48425ef4a7
4  2603520270 50c9509d-22c7-4a22-a47d-8c48425ef4a7
5  2603515579 50c9509d-22c7-4a22-a47d-8c48425ef4a7
6  2603514616 50c9509d-22c7-4a22-a47d-8c48425ef4a7
7  2603469718 50c9509d-22c7-4a22-a47d-8c48425ef4a7
8  2603464579 50c9509d-22c7-4a22-a47d-8c48425ef4a7
9  2603462426 50c9509d-22c7-4a22-a47d-8c48425ef4a7
10 2603460129 50c9509d-22c7-4a22-a47d-8c48425ef4a7
                                        occurrenceID  kingdom
1  https://www.inaturalist.org/observations/42252062 Animalia
2  https://www.inaturalist.org/observations/42288189 Animalia
3  https://www.inaturalist.org/observations/42190277 Animalia
4  https://www.inaturalist.org/observations/42172468 Animalia
5  https://www.inaturalist.org/observations/42347183 Animalia
6  https://www.inaturalist.org/observations/42334838 Animalia
7  https://www.inaturalist.org/observations/42043468 Animalia
8  https://www.inaturalist.org/observations/41991262 Animalia
9  https://www.inaturalist.org/observations/41919239 Animalia
10 https://www.inaturalist.org/observations/40430108 Animalia
       phylum   class       order   family          genus
1  Arthropoda Insecta Hymenoptera Vespidae        Vespula
2  Arthropoda Insecta Hymenoptera Vespidae        Vespula
3  Arthropoda Insecta Hymenoptera Vespidae        Vespula
4  Arthropoda Insecta Hymenoptera Vespidae Dolichovespula
5  Arthropoda Insecta Hymenoptera Vespidae Dolichovespula
6  Arthropoda Insecta Hymenoptera Vespidae        Vespula
7  Arthropoda Insecta Hymenoptera Vespidae        Vespula
8  Arthropoda Insecta Hymenoptera Vespidae Dolichovespula
9  Arthropoda Insecta Hymenoptera Vespidae        Vespula
10 Arthropoda Insecta Hymenoptera Vespidae Dolichovespula
                   species infraspecificEpithet taxonRank
1         Vespula squamosa                   NA   SPECIES
2     Vespula pensylvanica                   NA   SPECIES
3      Vespula maculifrons                   NA   SPECIES
4  Dolichovespula maculata                   NA   SPECIES
5  Dolichovespula maculata                   NA   SPECIES
6          Vespula acadica                   NA   SPECIES
7      Vespula maculifrons                   NA   SPECIES
8  Dolichovespula maculata                   NA   SPECIES
9      Vespula maculifrons                   NA   SPECIES
10 Dolichovespula maculata                   NA   SPECIES
                             scientificName  verbatimScientificName
1            Vespula squamosa (Drury, 1770)        Vespula squamosa
2  Vespula pensylvanica (de Saussure, 1857)    Vespula pensylvanica
3       Vespula maculifrons (Buysson, 1905)     Vespula maculifrons
4  Dolichovespula maculata (Linnaeus, 1763) Dolichovespula maculata
5  Dolichovespula maculata (Linnaeus, 1763) Dolichovespula maculata
6            Vespula acadica (Sladen, 1918)         Vespula acadica
7       Vespula maculifrons (Buysson, 1905)     Vespula maculifrons
8  Dolichovespula maculata (Linnaeus, 1763) Dolichovespula maculata
9       Vespula maculifrons (Buysson, 1905)     Vespula maculifrons
10 Dolichovespula maculata (Linnaeus, 1763) Dolichovespula maculata
   verbatimScientificNameAuthorship countryCode locality
1                                            US         
2                                            US         
3                                            US         
4                                            US         
5                                            US         
6                                            US         
7                                            US         
8                                            US         
9                                            US         
10                                           US         
    stateProvince occurrenceStatus individualCount
1  North Carolina                               NA
2         Arizona                               NA
3        Maryland                               NA
4  North Carolina                               NA
5  North Carolina                               NA
6          Alaska                               NA
7      New Jersey                               NA
8        Maryland                               NA
9        Virginia                               NA
10       New York                               NA
                       publishingOrgKey decimalLatitude
1  28eb1a3f-1c15-4a95-931a-4af90ecb574d        34.96565
2  28eb1a3f-1c15-4a95-931a-4af90ecb574d        31.72451
3  28eb1a3f-1c15-4a95-931a-4af90ecb574d        38.54904
4  28eb1a3f-1c15-4a95-931a-4af90ecb574d        35.27257
5  28eb1a3f-1c15-4a95-931a-4af90ecb574d        36.12257
6  28eb1a3f-1c15-4a95-931a-4af90ecb574d        57.03464
7  28eb1a3f-1c15-4a95-931a-4af90ecb574d        40.49301
8  28eb1a3f-1c15-4a95-931a-4af90ecb574d        38.60010
9  28eb1a3f-1c15-4a95-931a-4af90ecb574d        38.91710
10 28eb1a3f-1c15-4a95-931a-4af90ecb574d        42.38316
   decimalLongitude coordinateUncertaintyInMeters coordinatePrecision
1         -79.40654                         28762                  NA
2        -110.83710                            10                  NA
3         -77.18601                             2                  NA
4         -80.96621                          1407                  NA
5         -79.52758                          1080                  NA
6        -135.31406                            NA                  NA
7         -74.41784                           162                  NA
8         -76.76374                         28210                  NA
9         -77.27886                            14                  NA
10        -76.56134                            NA                  NA
   elevation elevationAccuracy depth depthAccuracy
1         NA                NA    NA            NA
2         NA                NA    NA            NA
3         NA                NA    NA            NA
4         NA                NA    NA            NA
5         NA                NA    NA            NA
6         NA                NA    NA            NA
7         NA                NA    NA            NA
8         NA                NA    NA            NA
9         NA                NA    NA            NA
10        NA                NA    NA            NA
             eventDate day month year taxonKey speciesKey
1  2020-04-14T10:59:53  14     4 2020  1311662    1311662
2  2020-04-15T12:31:55  15     4 2020  1311698    1311698
3  2020-04-11T15:27:00  11     4 2020  1311693    1311693
4  2020-04-14T13:12:07  14     4 2020  1311815    1311815
5  2020-04-16T17:51:16  16     4 2020  1311815    1311815
6  2020-03-23T14:35:00  23     3 2020  1311640    1311640
7  2015-09-24T08:33:52  24     9 2015  1311693    1311693
8  2020-04-12T08:32:06  12     4 2020  1311815    1311815
9  2020-04-11T11:23:35  11     4 2020  1311693    1311693
10 2020-02-28T15:12:00  28     2 2020  1311815    1311815
       basisOfRecord institutionCode collectionCode catalogNumber
1  HUMAN_OBSERVATION     iNaturalist   Observations      42252062
2  HUMAN_OBSERVATION     iNaturalist   Observations      42288189
3  HUMAN_OBSERVATION     iNaturalist   Observations      42190277
4  HUMAN_OBSERVATION     iNaturalist   Observations      42172468
5  HUMAN_OBSERVATION     iNaturalist   Observations      42347183
6  HUMAN_OBSERVATION     iNaturalist   Observations      42334838
7  HUMAN_OBSERVATION     iNaturalist   Observations      42043468
8  HUMAN_OBSERVATION     iNaturalist   Observations      41991262
9  HUMAN_OBSERVATION     iNaturalist   Observations      41919239
10 HUMAN_OBSERVATION     iNaturalist   Observations      40430108
   recordNumber identifiedBy      dateIdentified      license
1                            2020-04-15T18:26:11 CC_BY_NC_4_0
2                            2020-04-16T02:43:21 CC_BY_NC_4_0
3                            2020-04-14T21:05:06 CC_BY_NC_4_0
4                            2020-04-15T11:36:59 CC_BY_NC_4_0
5                            2020-04-16T22:00:44 CC_BY_NC_4_0
6                            2020-04-17T04:43:12 CC_BY_NC_4_0
7                            2020-04-12T22:47:23    CC_BY_4_0
8                            2020-04-12T12:46:29 CC_BY_NC_4_0
9                            2020-04-11T17:29:13 CC_BY_NC_4_0
10                           2020-03-22T00:38:00 CC_BY_NC_4_0
       rightsHolder       recordedBy typeStatus establishmentMeans
1       snail_hiker      snail_hiker                              
2       Tony Palmer      Tony Palmer                              
3  spyingnaturalist spyingnaturalist                              
4       karyncook79      karyncook79                              
5       kristin0107      kristin0107                              
6       rolandwirth      rolandwirth                              
7      John Beetham     John Beetham                              
8        klanderson       klanderson                              
9     Izabella Farr    Izabella Farr                              
10 Jason Dombroskie Jason Dombroskie                              
            lastInterpreted  mediaType
1  2020-04-22T02:25:24.859Z StillImage
2  2020-04-22T02:23:43.478Z StillImage
3  2020-04-22T02:23:37.213Z StillImage
4  2020-04-22T02:25:22.709Z StillImage
5  2020-04-22T02:25:26.216Z StillImage
6  2020-04-22T02:25:24.884Z           
7  2020-04-22T02:23:12.788Z StillImage
8  2020-04-22T02:24:56.760Z StillImage
9  2020-04-22T02:25:10.318Z StillImage
10 2020-04-22T02:24:48.416Z StillImage
                                             issue
1  COORDINATE_ROUNDED;GEODETIC_DATUM_ASSUMED_WGS84
2  COORDINATE_ROUNDED;GEODETIC_DATUM_ASSUMED_WGS84
3  COORDINATE_ROUNDED;GEODETIC_DATUM_ASSUMED_WGS84
4  COORDINATE_ROUNDED;GEODETIC_DATUM_ASSUMED_WGS84
5  COORDINATE_ROUNDED;GEODETIC_DATUM_ASSUMED_WGS84
6  COORDINATE_ROUNDED;GEODETIC_DATUM_ASSUMED_WGS84
7                     GEODETIC_DATUM_ASSUMED_WGS84
8  COORDINATE_ROUNDED;GEODETIC_DATUM_ASSUMED_WGS84
9  COORDINATE_ROUNDED;GEODETIC_DATUM_ASSUMED_WGS84
10                    GEODETIC_DATUM_ASSUMED_WGS84
                     geometry
1  POINT (-79.40654 34.96565)
2  POINT (-110.8371 31.72451)
3  POINT (-77.18601 38.54904)
4  POINT (-80.96621 35.27257)
5  POINT (-79.52758 36.12257)
6  POINT (-135.3141 57.03464)
7  POINT (-74.41784 40.49301)
8   POINT (-76.76374 38.6001)
9   POINT (-77.27886 38.9171)
10 POINT (-76.56134 42.38316)

Plot GBIF data

We can now plot our GBIF data. We’ll use two geom_sf() calls - the first will plot our US state data, and then second will plot our GBIF data on top of it.

ggplot() + 
    geom_sf(data = us_states_con48) + 
    geom_sf(data = gbif_sf) +
    map_theme

We can see some data points that seem to be occurring in Alaska and Hawaii. Since we’re limiting our map to the contiguous 48 states, we’d like to remove these data points.

Geospatially join sf objects to filter features

We can use the us_states_48con sf object to filter our GBIF data, returning only the GBIF points that occur within one of the contiguous 48 states. We can do this with a function called st_join(), which joins the data from two sf objects according to their geometries and retains the geometry for the first sf object. Here, we take each GBIF point and join the corresponding US state data in which it occurs. left = FALSE specifies that we are performing an inner join, so any point that does not find a corresponding state is removed.

gbif_con48 <- st_join(gbif_sf, us_states_con48, left = FALSE)
although coordinates are longitude/latitude, st_intersects assumes that they are planar
although coordinates are longitude/latitude, st_intersects assumes that they are planar
ggplot() + 
    geom_sf(data = us_states_con48) +
    geom_sf(data = gbif_con48) +
    map_theme

Aesthetic mapping

Like in any ggplot figure, we can use aesthetic mapping to provide additional information. For example, we could color our GBIF data by species of yellowjacket.

species_map <- ggplot() + 
    geom_sf(data = us_states_con48, fill = "white") +
    geom_sf(data = gbif_con48, 
            mapping = aes(color = species), 
            alpha = 0.4,
            size = 2,
            show.legend = FALSE) +
    map_theme +
    labs(title = 'Yellowjacket records by species',
         subtitle = "GBIF data for Vespula and Dolichovespula, April 2020")
species_map

Create heatmap

We can also create heatmaps (or choropleth maps) of our states, coloring each state according to one of its corresponding counts. For example, we might be interested in the number of occurrences found in each state.

To do this, we can again join our US state and GBIF sf objects. This time, we will join them in the reverse order, retaining the geographic information for our states and attaching GBIF data for each corresponding record. Then we can group and summarize (as we would with a normal data frame) to get the occurrence counts for each area.

I created two summary columns - records (the count of records), and the square root of record count, which we can use for plotting.

state_records <- st_join(us_states_con48, gbif_sf) %>%
    group_by(STUSPS) %>%
    summarize(records = n(),
              records_sqrt = sqrt(records))
although coordinates are longitude/latitude, st_intersects assumes that they are planar

This time when we plot our map, we will only use one geom_sf() call, because we only need to plot our state data. We will also call geom_sf_text(), which will allow us to put labels on each of our states.

state_heatmap <- ggplot(state_records, aes(fill = records_sqrt, label = records)) + 
    geom_sf(show.legend = FALSE) +
    geom_sf_text(size = 4) +
    map_theme + 
    scale_fill_gradient(low = 'white', high = 'goldenrod1') +
    labs(title = "Yellowjacket records by US state",
         subtitle = "GBIF data for Vespula and Dolichovespula, April 2020")
state_heatmap

Save maps as files

Once we are happy with our maps, we can save them as image files with ggplot’s ggsave().

ggsave(
    'figures/records_species_map.png', 
    plot = species_map, 
    width = 12, height = 7, units = 'in')
ggsave(
    'figures/state_counts_map.png', 
    plot = state_heatmap, 
    width = 12, height = 7, units = 'in')
LS0tDQp0aXRsZTogQ3JlYXRlIHNpbXBsZSBtYXBzIGluIFIgd2l0aCBnZ3Bsb3QgYW5kIHNmDQphdXRob3I6IEFtYW5kYSBEZXZpbmUNCmRhdGU6IDI5IEFwcmlsIDIwMjANCm91dHB1dDogDQogICAgaHRtbF9ub3RlYm9vazoNCiAgICAgICAgdG9jOiB0cnVlDQogICAgICAgIHRvY19kZXB0aDogMg0KICAgICAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICAgICAgY29sbGFwc2VkOiBmYWxzZQ0KICAgICAgICB0aGVtZTogY29zbW8NCi0tLQ0KDQpUaGlzIFIgTm90ZWJvb2sgZGVtb25zdHJhdGVzIGhvdyB0byB3b3JrIHdpdGggdGhlIFIgcGFja2FnZXMgYHNmYCBhbmQgYGdncGxvdDJgIHRvIGNyZWF0ZSBiYXNpYyBtYXBzLiBGb3IgdGhpcyBkZW1vLCB3ZSB3aWxsIGNyZWF0ZSB0d28gbWFwcyBzaG93aW5nIHRoZSBkaXN0cmlidXRpb24gb2YgeWVsbG93amFja2V0IG9jY3VycmVuY2VzIGFjcm9zcyB0aGUgY29udGlndW91cyA0OCBVbml0ZWQgU3RhdGVzLiANCg0KRGlzY2xhaW1lcjogSSdtIHN0aWxsIHZlcnkgbmV3IHRvIHdvcmtpbmcgd2l0aCBnZW9zcGF0aWFsIGRhdGEuIFRha2UgdGhpcyBkZW1vIHdpdGggYSBncmFpbiBvZiBzYWx0LCBhbmQgY29uc3VsdCBtb3JlIGRldGFpbGVkIHR1dG9yaWFscyBhbmQgR0lTIGNvdXJzZXMgdG8gZ2V0IGEgbW9yZSB0aG9yb3VnaCB1ZG5lcnN0YW5kaW5nIG9mIHByaW5jaXBsZXMuIE15IGFpbSB3aXRoIHRoaXMgZGVtbyBpcyB0byBkZW1vbnN0cmF0ZSB0d28gY29tbW9uIHRhc2tzIEkndmUgZW5jb3VudGVyZWQgd29ya2luZyB3aXRoIG11c2V1bSBkYXRhOiBwbG90dGluZyBsYXRpdHVkZS9sb25naXR1ZGUgcG9pbnRzIG9uIGEgbWFwLCBhbmQgY3JlYXRpbmcgaGVhdG1hcHMgdG8gc2hvdyBob3cgY291bnRzIHZhcnkgYWNyb3NzIHN0YXRlcyBvciBjb3VudHJpZXMuDQoNCiMjIEltcG9ydCBwYWNrYWdlcw0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShkYXRhLnRhYmxlKSAgICAgIyBSZWFkL3dyaXRlIGxhcmdlIHRhYnVsYXIgZGF0YSBmaWxlcw0KbGlicmFyeShzZikgICAgICAgICAgICAgIyBDbGFzc2VzIGFuZCBmdW5jdGlvbnMgZm9yIHZlY3RvciBtYXBwaW5nIGRhdGENCmBgYA0KDQojIyBHZW9ncmFwaGljIGRhdGEgYW5kIFIgb2JqZWN0cw0KDQpGb3IgdGhpcyBkZW1vLCB3ZSB3aWxsIGJlIHdvcmtpbmcgd2l0aCB0d28gZ2VvZ3JhcGhpYyBkYXRhc2V0czogYSBzaGFwZWZpbGUgY29udGFpbmluZyBkYXRhIG9uIHRoZSBVUyBzdGF0ZXMsIGFuZCBhIENTViBjb250YWluaW5nIGdlb3JlZmVyZW5jZWQgb2NjdXJyZW5jZSBkYXRhIGZvciB5ZWxsb3dqYWNrZXRzIGluIHRoZSBVbml0ZWQgU3RhdGVzLg0KDQpBICoqc2hhcGVmaWxlKiogaXMgYSBnZW9zcGF0aWFsIHZlY3RvciBkYXRhIGZvcm1hdCBjb21tb25seSB1c2VkIHdpdGggR0lTIHNvZnR3YXJlIChsaWtlIEFyY0dJUykuIEZvciB0aGlzIGRlbW8sIHdlIGNhbiB0aGluayBvZiBpdCBhcyBhIHNldCBvZiByZWxhdGVkIGZpbGVzIGNvbnRhaW5pbmcgZGF0YSBvbiBzZXQgb2Ygc3BhdGlhbCBmZWF0dXJlcyAoZ2VvZ3JhcGhpYyBwbGFjZXMpIGFuZCB0aGVpciBhc3NvY2lhdGVkIGdlb21ldHJpY2FsIHJlcHJlc2VudGF0aW9ucyAocG9pbnRzLCBsaW5lcywgcG9seWdvbnMpLiANCg0KVGhlcmUgYXJlIGRpZmZlcmVudCBwYWNrYWdlcyBhbmQgZGlmZmVyZW50IFIgb2JqZWN0cyBmb3Igd29ya2luZyB3aXRoIGdlb3NwYXRpYWwgZGF0YS4gV2Ugd2lsbCBiZSB1c2luZyBhIHBhY2thZ2UgY2FsbGVkIGBzZmAsIHdoaWNoIGNyZWF0ZXMgYSB0eXBlIG9mIFIgb2JqZWN0IGNhbGxlZCBhICoqc2ltcGxlIGZlYXR1cmUqKiBvYmplY3QuIFRoaXMgb2JqZWN0IGFjdHMgbXVjaCBsaWtlIGEgZGF0YSBmcmFtZSwgd2hlcmUgZXZlcnkgcm93IGNvbnRhaW5zIGRhdGEgb24gYSBnZW9ncmFwaGljIHBsYWNlIChvciAqKmZlYXR1cmUqKikuIEluIGFkZGl0aW9uLCBlYWNoIHJvdyBoYXMgYW4gYXNzb2NpYXRlZCAqKmdlb21ldHJ5KiosIHdoaWNoIGNvbnRhaW5zIHRoZSBnZW9tZXRyaWMgcmVwcmVzZW50YXRpb24gb2YgdGhhdCBwbGFjZSBvbiBhIG1hcCAoYXMgYSBwb2ludCwgbGluZSwgb3IgcG9seWdvbikuIFNpbXBsZSBmZWF0dXJlIG9iamVjdHMgY2FuIGJlIHJlYWQgaW50byBSIGZyb20gZXhpc3RpbmcgZ2Vvc3BhdGlhbCBkYXRhIHNvdXJjZXMgKGxpa2Ugc2hhcGVmaWxlcyksIG9yIGNyZWF0ZWQgZnJvbSBvdGhlciBmaWxlIHR5cGVzIGNvbnRhaW5pbmcgZ2Vvc3BhdGlhbCBkYXRhIChsaWtlIENTViBmaWxlcykuDQoNClRvIHBsb3Qgb3VyIG1hcHMsIHdlIHdpbGwgdXNlIHRoZSBgZ2dwbG90MmBwYWNrYWdlIGFuZCBpdHMgYnVpbHQtaW4gZ2VvbSBgZ2VvbV9zZigpYC4gVGhpcyBnZW9tIGlzIGRlc2lnbmVkIHRvIHBsb3Qgc2Ygb2JqZWN0IGRhdGEuDQoNCiMjIyBDb29yZGluYXRlIFJlZmVyZW5jZSBTeXN0ZW1zIChDUlMpDQoNCioqQ29vcmRpbmF0ZSByZWZlcmVuY2Ugc3lzdGVtcyAoQ1JTKSoqIGFyZSBzeXN0ZW1zIG9mIGVxdWF0aW9ucyBhbmQgdmlzdWFsIHJlcHJlc2VudGF0aW9ucyBjYXJ0b2dyYXBoZXJzIHVzZSB0byByZXByZXNlbnQgdGhlIDNEIHNwaGVyaWNhbCBFYXJ0aCBhcyAyRCBpbWFnZXMuIEZvciB0aGlzIGRlbW8sIHdlIHdpbGwgdXNlIGEgQ1JTIGNhbGxlZCAqKldHUyA4NCoqLiBXZSBuZWVkIHRvIG1ha2Ugc3VyZSB0aGF0IGFsbCBvdXIgc3BhdGlhbCBkYXRhIGFyZSB1c2luZyB0aGUgV0dTIDg0IENSUywgb3IgZWxzZSBvdXIgZGlmZmVyZW50IGRhdGFzZXRzIHdpbGwgbm90IGFsaWduIHByb3Blcmx5Lg0KDQpXR1MgODQgaXMgcmVwcmVzZW50ZWQgd2l0aCB0aGUgRVBTRyBjb2RlIDQzMjYuDQoNCmBgYHtyfQ0Kd2dzODQgIDwtIDQzMjYgICAjIFdHUyA4NCwgdXNlZCBpbiBHUFMuIGh0dHBzOi8vZXBzZy5pby80MzI2DQpgYGANCg0KRm9yIGEgZ29vZCBleHBsYW5hdGlvbiBvZiBDUlNlczoNCg0KLSBodHRwczovL3d3dy5lYXJ0aGRhdGFzY2llbmNlLm9yZy9jb3Vyc2VzL2VhcnRoLWFuYWx5dGljcy9zcGF0aWFsLWRhdGEtci9pbnRyby10by1jb29yZGluYXRlLXJlZmVyZW5jZS1zeXN0ZW1zLw0KLSBodHRwczovL3d3dy5lYXJ0aGRhdGFzY2llbmNlLm9yZy9jb3Vyc2VzL2VhcnRoLWFuYWx5dGljcy9zcGF0aWFsLWRhdGEtci9nZW9ncmFwaGljLXZzLXByb2plY3RlZC1jb29yZGluYXRlLXJlZmVyZW5jZS1zeXN0ZW1zLVVUTS8NCg0KIyMgVW5pdGVkIFN0YXRlcyBzdGF0ZSBnZW9ncmFwaGljIGRhdGENCg0KVGhlIFVTIENlbnN1cyBCdXJlYXUgcHVibGlzaGVzIHNoYXBlZmlsZXMgY29udGFpbmluZyBzcGF0aWFsIGRhdGEgZm9yIGRpZmZlcmVudCBVbml0ZWQgU3RhdGVzIHJlZ2lvbnMuIFRoZXNlIHNoYXBlZmlsZXMgYXJlIGF2YWlsYWJsZSBoZXJlOiBodHRwczovL3d3dy5jZW5zdXMuZ292L2dlb2dyYXBoaWVzL21hcHBpbmctZmlsZXMvdGltZS1zZXJpZXMvZ2VvL2NhcnRvLWJvdW5kYXJ5LWZpbGUuaHRtbA0KDQpGb3IgdGhpcyBkZW1vLCB3ZSB3aWxsIGJlIHVzaW5nIHRoZSBmaWxlICoqY2JfMjAxOF91c19zdGF0ZV8yMG0uemlwKiosIHdoaWNoIGNvbnRhaW5zIHRoZSBuYW1lcyBhbmQgbG93LXJlc29sdXRpb24gZ2VvZ3JhcGhpYyBib3VuZGFyaWVzIGZvciB0aGUgVVMgc3RhdGVzIGFuZCB0ZXJyaXRvcmllcy4NCg0KVGhpcyBmaWxlIHdhcyB1bnppcHBlZCBhbmQgcGxhY2VkIGluIGEgZm9sZGVyIGNhbGxlZCBgZGF0YWAuIFRoZSBzaGFwZWZpbGUgaXRzZWxmIHVzZXMgdGhlIGZpbGUgZXh0ZW5zaW9uIGAuc2hwYCwgYnV0IGFsbCB0aGUgZmlsZXMgaW4gdGhlIHppcCBhcmNoaXZlIGFyZSByZXF1aXJlZCBpbiBvcmRlciB0byBzdWNjZXNzZnVsbHkgYWNjZXNzIHRoZSBkYXRhLg0KDQojIyMgUmVhZCBzaGFwZWZpbGUgZGF0YSBhcyBzZiBvYmplY3QNCg0KV2UgY2FuIHVzZSB0aGUgc2YgZnVuY3Rpb24gYHN0X3JlYWQoKWAgdG8gcmVhZCB0aGUgZGF0YSBpbiBhcyBhbiBzZiBvYmplY3QuDQoNCmBgYHtyfQ0KdXNfc3RhdGVzIDwtIHN0X3JlYWQoJ2RhdGEvY2JfMjAxOF91c19zdGF0ZV8yMG0uc2hwJykNCmBgYA0KDQpXaGVuIHdlIHJlYWQgdGhlIHNoYXBlZmlsZSwgd2Ugc2VlIHRoYXQgdGhlIGN1cnJlbnQgQ1JTICgrZGF0dW0pIGlzIE5BRDgzLCB3aGljaCBpcyBhIHByb2plY3RlZCBjb29yZGluYXRlIHN5c3RlbSB0aGF0IGNlbnRlcnMgb24gTm9ydGggQW1lcmljYS4gV2Ugd2lsbCBjb252ZXJ0IHRoZSBgdXNfc3RhdGVzYCBkYXRhIHRvIHRoZSBXR1MgODQgaW5zdGVhZC4NCg0KYGBge3J9DQp1c19zdGF0ZXMgPC0gc3RfdHJhbnNmb3JtKHVzX3N0YXRlcywgY3JzID0gd2dzODQpDQpgYGANCg0KIyMjIFBsb3Qgc3RhdGUgZGF0YQ0KDQpXZSBjYW4gdXNlIGdncGxvdDIgdG8gZG8gYSBxdWljayBwbG90IHRvIHNlZSBob3cgb3VyIHN0YXRlIGRhdGEgYXJlIGxvb2tpbmcuIFdoZW4gd2UgcGxvdCBzZiBkYXRhLCB3ZSB3aWxsIHVzZSBhIGdlb20gY2FsbGVkIGBnZW9tX3NmKClgLCB3aGljaCBwbG90cyB0aGUgZ2VvbWV0cnkgZm9yIGVhY2ggcm93IGluIG91ciBkYXRhLg0KDQpgYGB7cn0NCmdncGxvdCgpKyBnZW9tX3NmKGRhdGEgPSB1c19zdGF0ZXMpDQpgYGANCg0KRm9yIHRoaXMgZGVtbywgd2Ugd2lsbCBmb2N1cyBvbiBqdXN0IHRoZSBjb250aWd1b3VzIDQ4IHN0YXRlcy4gV2UgY2FuIHNpbXBseSBmaWx0ZXIgb3VyIHNmIG9iamVjdCBhcyB3ZSB3b3VsZCBhIG5vcm1hbCBkYXRhIGZyYW1lLg0KDQpgYGB7cn0NCm5vbl9jb250aWd1b3VzIDwtIGMoJ0FsYXNrYScsICdIYXdhaWknLCAnUHVlcnRvIFJpY28nKQ0KdXNfc3RhdGVzX2NvbjQ4IDwtIGZpbHRlcih1c19zdGF0ZXMsICEoTkFNRSAlaW4lIG5vbl9jb250aWd1b3VzKSkNCmBgYA0KDQpXZSBjYW4gdGFrZSBhIGxvb2sgYWdhaW4gdG8gc2VlIG91ciBmaWx0ZXJlZCBVUyBzdGF0ZXMgZGF0YS4NCg0KYGBge3J9DQpnZ3Bsb3QoKSsgZ2VvbV9zZihkYXRhID0gdXNfc3RhdGVzX2NvbjQ4KQ0KYGBgDQoNCldlIGNhbiBhbHNvIGNyZWF0ZSBhbiBvYmplY3QgY29udGFpbmluZyBzb21lIHRoZW1lIHNldHRpbmdzIGZvciBvdXIgbWFwLiBXZSBjYW4gdGhlbiBpbmNsdWRlIHRoaXMgb2JqZWN0IGZvciBmdXR1cmUgZ2dwbG90IGNhbGxzLg0KDQpgYGB7cn0NCm1hcF90aGVtZSA8LSB0aGVtZV9idygpICsNCiAgICB0aGVtZShheGlzLmxpbmUgPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiksIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiksDQogICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIikpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoKSArIA0KICAgIGdlb21fc2YoZGF0YSA9IHVzX3N0YXRlc19jb240OCkgKw0KICAgIG1hcF90aGVtZQ0KYGBgDQoNCg0KIyMgR0JJRiBPY2N1cnJlbmNlIGRhdGENCg0KT3VyIGRhdGEgb24geWVsbG93amFja2V0IG9jY3VycmVuY2VzIHdlcmUgZG93bmxvYWRlZCBmcm9tIHRoZSBHbG9iYWwgQmlvZGl2ZXJzaXR5IEluZm9ybWF0aW9uIEZhY2lsaXR5IChHQklGKS4gRWFjaCBvY2N1cnJlbmNlIHJlY29yZCBjb250YWlucyB0YXhvbm9taWMgaW5mb3JtYXRpb24sIG9jY3VycmVuY2UgdHlwZSBpbmZvcm1hdGlvbiwgYW5kIGEgZ2VvcmVmZXJlbmNlZCBjb29yZGluYXRlLg0KDQo+IERhdGFzZXQgY2l0YXRpb246IEdCSUYub3JnICgyOSBBcHJpbCAyMDIwKSBHQklGIE9jY3VycmVuY2UgRG93bmxvYWQgaHR0cHM6Ly9kb2kub3JnLzEwLjE1NDY4L2RsLjQ0dDZhdA0KDQojIyMgSW1wb3J0IEdCSUYgZGF0YQ0KDQpXZSBjYW4gaW1wb3J0IHRoZSBHQklGIGRhdGEgYXMgYSBzdGFuZGFyZCBkYXRhIGZyYW1lLiBJIGxpa2UgdG8gdXNlIHRoZSBgZGF0YS50YWJsZWAgcGFja2FnZSAoYW5kIGl0cyBgZnJlYWQoKWAgZnVuY3Rpb24pLCB3aGljaCB3b3JrcyByZWFsbHkgd2VsbCBmb3IgcGFyc2luZyBsYXJnZSBmaWxlcy4gSSB3aWxsIGFsc28gZmlsdGVyIG91dCByZWNvcmRzIHdoZXJlIGBkZWNpbWFsTGF0aXR1ZGVgIG9yIGBkZWNpbWFsTG9uZ2l0dWRlYCB3ZXJlIG5vdCBzdXBwbGllZC4NCg0KYGBge3J9DQpnYmlmIDwtIGZyZWFkKCdkYXRhL2diaWZfeWVsbG93amFja2V0cy5jc3YnKSAlPiUNCiAgICBmaWx0ZXIoIWlzLm5hKGRlY2ltYWxMYXRpdHVkZSksIA0KICAgICAgICAgICAhaXMubmEoZGVjaW1hbExvbmdpdHVkZSkpDQpgYGANCg0KIyMjIENvbnZlcnQgR0JJRiBkYXRhIHRvIHNmDQoNCldlIG5leHQgbmVlZCB0byBjb252ZXJ0IHRoaXMgZGF0YSBmcmFtZSB0byBhIGdlb3NwYXRpYWwgYHNmYCBvYmplY3QuIFRoZSBgc3RfYXNfc2YoKWAgZnVuY3Rpb24gcmVxdWlyZXMgdGhhdCB0aGUgY29vcmRpbmF0ZSBjb2x1bW5zIGJlIHNwZWNpZmllZCAoeCA9IGxvbmdpdHVkZSwgeSA9IGxhdGl0dWRlKS4gVGhlIENSUyBjYW4gYWxzbyBiZSBzcGVjaWZpZWQuIE1vc3QgR0JJRiBkYXRhIGlzIGFzc3VtZWQgdG8gdXNlIHRoZSBXR1MgODQgY29vcmRpbmF0ZSBzeXN0ZW0uDQoNCmBgYHtyfQ0KZ2JpZl9zZiA8LSBzdF9hc19zZihnYmlmLCANCiAgICAgICAgICAgICAgICAgICAgY29vcmRzID0gYygiZGVjaW1hbExvbmdpdHVkZSIsICJkZWNpbWFsTGF0aXR1ZGUiKSwgDQogICAgICAgICAgICAgICAgICAgIGNycyA9IHdnczg0LCANCiAgICAgICAgICAgICAgICAgICAgcmVtb3ZlID0gRkFMU0UpICMgcmV0YWlucyBvcmlnaW5hbCBsYXQvbG9uZyBjb2x1bW5zDQpnYmlmX3NmDQpgYGANCg0KIyMjIFBsb3QgR0JJRiBkYXRhDQoNCldlIGNhbiBub3cgcGxvdCBvdXIgR0JJRiBkYXRhLiBXZSdsbCB1c2UgdHdvIGBnZW9tX3NmKClgIGNhbGxzIC0gdGhlIGZpcnN0IHdpbGwgcGxvdCBvdXIgVVMgc3RhdGUgZGF0YSwgYW5kIHRoZW4gc2Vjb25kIHdpbGwgcGxvdCBvdXIgR0JJRiBkYXRhIG9uIHRvcCBvZiBpdC4NCg0KYGBge3J9DQpnZ3Bsb3QoKSArIA0KICAgIGdlb21fc2YoZGF0YSA9IHVzX3N0YXRlc19jb240OCkgKyANCiAgICBnZW9tX3NmKGRhdGEgPSBnYmlmX3NmKSArDQogICAgbWFwX3RoZW1lDQpgYGANCg0KV2UgY2FuIHNlZSBzb21lIGRhdGEgcG9pbnRzIHRoYXQgc2VlbSB0byBiZSBvY2N1cnJpbmcgaW4gQWxhc2thIGFuZCBIYXdhaWkuIFNpbmNlIHdlJ3JlIGxpbWl0aW5nIG91ciBtYXAgdG8gdGhlIGNvbnRpZ3VvdXMgNDggc3RhdGVzLCB3ZSdkIGxpa2UgdG8gcmVtb3ZlIHRoZXNlIGRhdGEgcG9pbnRzLiANCg0KIyMgR2Vvc3BhdGlhbGx5IGpvaW4gc2Ygb2JqZWN0cyB0byBmaWx0ZXIgZmVhdHVyZXMNCg0KV2UgY2FuIHVzZSB0aGUgYHVzX3N0YXRlc180OGNvbmAgc2Ygb2JqZWN0IHRvIGZpbHRlciBvdXIgR0JJRiBkYXRhLCByZXR1cm5pbmcgb25seSB0aGUgR0JJRiBwb2ludHMgdGhhdCBvY2N1ciB3aXRoaW4gb25lIG9mIHRoZSBjb250aWd1b3VzIDQ4IHN0YXRlcy4gV2UgY2FuIGRvIHRoaXMgd2l0aCBhIGZ1bmN0aW9uIGNhbGxlZCBgc3Rfam9pbigpYCwgd2hpY2ggam9pbnMgdGhlIGRhdGEgZnJvbSB0d28gc2Ygb2JqZWN0cyBhY2NvcmRpbmcgdG8gdGhlaXIgZ2VvbWV0cmllcyBhbmQgcmV0YWlucyB0aGUgZ2VvbWV0cnkgZm9yIHRoZSBmaXJzdCBzZiBvYmplY3QuIEhlcmUsIHdlIHRha2UgZWFjaCBHQklGIHBvaW50IGFuZCBqb2luIHRoZSBjb3JyZXNwb25kaW5nIFVTIHN0YXRlIGRhdGEgaW4gd2hpY2ggaXQgb2NjdXJzLiBgbGVmdCA9IEZBTFNFYCBzcGVjaWZpZXMgdGhhdCB3ZSBhcmUgcGVyZm9ybWluZyBhbiBpbm5lciBqb2luLCBzbyBhbnkgcG9pbnQgdGhhdCBkb2VzIG5vdCBmaW5kIGEgY29ycmVzcG9uZGluZyBzdGF0ZSBpcyByZW1vdmVkLg0KDQpgYGB7cn0NCmdiaWZfY29uNDggPC0gc3Rfam9pbihnYmlmX3NmLCB1c19zdGF0ZXNfY29uNDgsIGxlZnQgPSBGQUxTRSkNCmBgYA0KDQpgYGB7cn0NCmdncGxvdCgpICsgDQogICAgZ2VvbV9zZihkYXRhID0gdXNfc3RhdGVzX2NvbjQ4KSArDQogICAgZ2VvbV9zZihkYXRhID0gZ2JpZl9jb240OCkgKw0KICAgIG1hcF90aGVtZQ0KYGBgDQoNCiMjIEFlc3RoZXRpYyBtYXBwaW5nDQoNCkxpa2UgaW4gYW55IGdncGxvdCBmaWd1cmUsIHdlIGNhbiB1c2UgYWVzdGhldGljIG1hcHBpbmcgdG8gcHJvdmlkZSBhZGRpdGlvbmFsIGluZm9ybWF0aW9uLiBGb3IgZXhhbXBsZSwgd2UgY291bGQgY29sb3Igb3VyIEdCSUYgZGF0YSBieSBzcGVjaWVzIG9mIHllbGxvd2phY2tldC4NCg0KYGBge3J9DQpzcGVjaWVzX21hcCA8LSBnZ3Bsb3QoKSArIA0KICAgIGdlb21fc2YoZGF0YSA9IHVzX3N0YXRlc19jb240OCwgZmlsbCA9ICJ3aGl0ZSIpICsNCiAgICBnZW9tX3NmKGRhdGEgPSBnYmlmX2NvbjQ4LCANCiAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoY29sb3IgPSBzcGVjaWVzKSwgDQogICAgICAgICAgICBhbHBoYSA9IDAuNCwNCiAgICAgICAgICAgIHNpemUgPSAyLA0KICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICAgIG1hcF90aGVtZSArDQogICAgbGFicyh0aXRsZSA9ICdZZWxsb3dqYWNrZXQgcmVjb3JkcyBieSBzcGVjaWVzJywNCiAgICAgICAgIHN1YnRpdGxlID0gIkdCSUYgZGF0YSBmb3IgVmVzcHVsYSBhbmQgRG9saWNob3Zlc3B1bGEsIEFwcmlsIDIwMjAiKQ0Kc3BlY2llc19tYXANCmBgYA0KDQojIyBDcmVhdGUgaGVhdG1hcA0KDQpXZSBjYW4gYWxzbyBjcmVhdGUgaGVhdG1hcHMgKG9yIGNob3JvcGxldGggbWFwcykgb2Ygb3VyIHN0YXRlcywgY29sb3JpbmcgZWFjaCBzdGF0ZSBhY2NvcmRpbmcgdG8gb25lIG9mIGl0cyBjb3JyZXNwb25kaW5nIGNvdW50cy4gRm9yIGV4YW1wbGUsIHdlIG1pZ2h0IGJlIGludGVyZXN0ZWQgaW4gdGhlIG51bWJlciBvZiBvY2N1cnJlbmNlcyBmb3VuZCBpbiBlYWNoIHN0YXRlLg0KDQpUbyBkbyB0aGlzLCB3ZSBjYW4gYWdhaW4gam9pbiBvdXIgVVMgc3RhdGUgYW5kIEdCSUYgc2Ygb2JqZWN0cy4gVGhpcyB0aW1lLCB3ZSB3aWxsIGpvaW4gdGhlbSBpbiB0aGUgcmV2ZXJzZSBvcmRlciwgcmV0YWluaW5nIHRoZSBnZW9ncmFwaGljIGluZm9ybWF0aW9uIGZvciBvdXIgc3RhdGVzIGFuZCBhdHRhY2hpbmcgR0JJRiBkYXRhIGZvciBlYWNoIGNvcnJlc3BvbmRpbmcgcmVjb3JkLiBUaGVuIHdlIGNhbiBncm91cCBhbmQgc3VtbWFyaXplIChhcyB3ZSB3b3VsZCB3aXRoIGEgbm9ybWFsIGRhdGEgZnJhbWUpIHRvIGdldCB0aGUgb2NjdXJyZW5jZSBjb3VudHMgZm9yIGVhY2ggYXJlYS4NCg0KSSBjcmVhdGVkIHR3byBzdW1tYXJ5IGNvbHVtbnMgLSByZWNvcmRzICh0aGUgY291bnQgb2YgcmVjb3JkcyksIGFuZCB0aGUgc3F1YXJlIHJvb3Qgb2YgcmVjb3JkIGNvdW50LCB3aGljaCB3ZSBjYW4gdXNlIGZvciBwbG90dGluZy4NCg0KYGBge3J9DQpzdGF0ZV9yZWNvcmRzIDwtIHN0X2pvaW4odXNfc3RhdGVzX2NvbjQ4LCBnYmlmX3NmKSAlPiUNCiAgICBncm91cF9ieShTVFVTUFMpICU+JQ0KICAgIHN1bW1hcml6ZShyZWNvcmRzID0gbigpLA0KICAgICAgICAgICAgICByZWNvcmRzX3NxcnQgPSBzcXJ0KHJlY29yZHMpKQ0KYGBgDQoNClRoaXMgdGltZSB3aGVuIHdlIHBsb3Qgb3VyIG1hcCwgd2Ugd2lsbCBvbmx5IHVzZSBvbmUgYGdlb21fc2YoKWAgY2FsbCwgYmVjYXVzZSB3ZSBvbmx5IG5lZWQgdG8gcGxvdCBvdXIgc3RhdGUgZGF0YS4gV2Ugd2lsbCBhbHNvIGNhbGwgYGdlb21fc2ZfdGV4dCgpYCwgd2hpY2ggd2lsbCBhbGxvdyB1cyB0byBwdXQgbGFiZWxzIG9uIGVhY2ggb2Ygb3VyIHN0YXRlcy4NCg0KYGBge3J9DQpzdGF0ZV9oZWF0bWFwIDwtIGdncGxvdChzdGF0ZV9yZWNvcmRzLCBhZXMoZmlsbCA9IHJlY29yZHNfc3FydCwgbGFiZWwgPSByZWNvcmRzKSkgKyANCiAgICBnZW9tX3NmKHNob3cubGVnZW5kID0gRkFMU0UpICsNCiAgICBnZW9tX3NmX3RleHQoc2l6ZSA9IDQpICsNCiAgICBtYXBfdGhlbWUgKyANCiAgICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICd3aGl0ZScsIGhpZ2ggPSAnZ29sZGVucm9kMScpICsNCiAgICBsYWJzKHRpdGxlID0gIlllbGxvd2phY2tldCByZWNvcmRzIGJ5IFVTIHN0YXRlIiwNCiAgICAgICAgIHN1YnRpdGxlID0gIkdCSUYgZGF0YSBmb3IgVmVzcHVsYSBhbmQgRG9saWNob3Zlc3B1bGEsIEFwcmlsIDIwMjAiKQ0Kc3RhdGVfaGVhdG1hcA0KYGBgDQoNCiMjIFNhdmUgbWFwcyBhcyBmaWxlcw0KDQpPbmNlIHdlIGFyZSBoYXBweSB3aXRoIG91ciBtYXBzLCB3ZSBjYW4gc2F2ZSB0aGVtIGFzIGltYWdlIGZpbGVzIHdpdGggZ2dwbG90J3MgYGdnc2F2ZSgpYC4NCg0KYGBge3J9DQpnZ3NhdmUoDQogICAgJ2ZpZ3VyZXMvcmVjb3Jkc19zcGVjaWVzX21hcC5wbmcnLCANCiAgICBwbG90ID0gc3BlY2llc19tYXAsIA0KICAgIHdpZHRoID0gMTIsIGhlaWdodCA9IDcsIHVuaXRzID0gJ2luJykNCmBgYA0KDQpgYGB7cn0NCmdnc2F2ZSgNCiAgICAnZmlndXJlcy9zdGF0ZV9jb3VudHNfbWFwLnBuZycsIA0KICAgIHBsb3QgPSBzdGF0ZV9oZWF0bWFwLCANCiAgICB3aWR0aCA9IDEyLCBoZWlnaHQgPSA3LCB1bml0cyA9ICdpbicpDQpgYGANCg0K