Background
My lab (aka me and my advisor T. Gillespie) is collaborating with two LA-based projects (at the City and County) that are interested in using local species data to assess biodiversity/sustainability. Both projects want to use the presence of certain indicator species to reflect, to some extent, variables like habitat quality and species richness. A set of (as yet unfinalized) calculations will convert the species data to an area-based “score,” which will then be added up with several other variables/indicators.
The availability of species data is obviously key here. Because species occurrence data obtained through routine, controlled monitoring are very difficult to come by, these projects are interested in the utility of citizen science inventories like iNaturalist. Citizen science databases are extremely valuable, as they offer access to the pooled efforts and observations of many more people (a lot more frequently) than you might have otherwise. The disadvantages, however, include some degree of spatial sampling bias among the observations and potentital inaccuracies in species identifications.
We’ve been asked to help evaluate the utility and availability of iNaturalist data for 119 indicator species. We need to provide the number of accurate, LA city records for each species, and simple maps of their distributions. Once we review the coverage of the iNaturalist data with our collaborators, we’ll use this data to score LA’s non-urban patches and urban areas. Scoring methods are still being finalized, but we expect them to vary by taxa and indicator species group. There are 5 groups in total: common native fauna (found mostly in “natural” areas), common native flora found mostly in “natural” areas), native fauna that can commonly be found in urban areas, rare native species of concern, and invasive species.
Objectives
Write a script that:
-
Filters through the iNaturalist data,
-
Exports a clean .csv and .shp for each species,
-
Produces some simple maps,
-
Calculates area-based scores for different groups of species, and
-
Can be easily updated to reflect changes in the indicator species lists, units of analysis, and scoring methods
Main Steps
I am dividing the procedures into two parts. Part one (steps 1-9) will clean and visualize the data on a species-by-species basis. Part two includes the steps for scoring.
-
Identify and select records that: a) have accuracy radius <= 10m and b) Match the listed indicator species/subspecies (unless multiple species are to be used as indicators)
-
Export columns for species scientific name, common name, longitude, latitude, observation date, and iNat user info to CSV
-
Export shapefile from iNat records CSV
-
Create a subset of records using the LA City boundary
-
Count number of species records in city (total), in the patch area, and in the urban area, and write to table
-
Identify patches and/or grids that contain species points
-
Export patches and/or grids to new shapefiles
-
Update table with number of patches and urban grids that contain species records
-
Map a) species point records and b) patches/grids containing iNat data
-
Using Biodiversity Index methods, calculate Indicator score for each species group
-
Map Indicator scores
Getting Started
library(tidyverse)
library(sp)
library(rgdal)
library(rgeos)
library(raster)
library(ggplot2)
library(tmap)
Should I need to re-run this script in future, the only thing I want to have to update is this line, setting the work directory:
setwd('C:/Users/monica/Documents/Bio_Index/species_data')
One important input file is this sort of reference list, which contains some information about the indicator species. This too could potentially be updated in future. Let’s take a peek:
ind_spp <- read_csv('indicator_species_fn.csv')
Parsed with column specification:
cols(
indicator_group = col_character(),
taxa = col_character(),
scientific_name = col_character(),
common_name = col_character(),
multiple_spp = col_logical(),
ranking = col_character(),
csv_fn = col_character()
)
head(ind_spp)
# A tibble: 6 x 7
indicator_group taxa scientific_name common_name multiple_spp ranking csv_fn
<chr> <chr> <chr> <chr> <lgl> <chr> <chr>
1 common_native_flora Plant Abronia umbellata Sand Verbena FALSE 2 a b c Abronia_umbellata
2 common_native_flora Plant Artemisia californica California sagebrush FALSE 2 a b c Artemisia_californica
3 common_native_flora Plant Baccharis pilularis Coyote brush FALSE 2 a b c Baccharis_pilularis
4 common_native_flora Plant Baccharis salicifolia Mulefat FALSE 2 a b c Baccharis_salicifolia
5 common_native_flora Plant Camissoniopsis cheiranthifolia Beach evening-primrose FALSE 2 a b c Camissoniopsis_cheiranthifolia
6 common_native_flora Plant Castilleja exserta Purple owls-clover FALSE 2 a b c Castilleja_exserta
# How many unique values are there for each variable?
sapply(ind_spp, function(x) length(unique(x)))
indicator_group taxa scientific_name common_name multiple_spp ranking csv_fn
5 7 119 119 2 7 117
# How many species are in each indicator group?
spp_per_group <- ind_spp %>%
group_by(indicator_group) %>%
summarise(species = n())
print(spp_per_group)
# A tibble: 5 x 2
indicator_group species
<chr> <int>
1 common_native_fauna 25
2 common_native_flora 25
3 common_native_urban 15
4 invasive 19
5 rare_native 35
The other inputs are some shapefiles for LA City. I’ll also assign their coordinate system (WGS 84) to a variable for later use:
LAcity_boundary <- readOGR('la_shp', 'la_city_griddissolve_wgs84')
cv_patch <- readOGR('la_shp', 'calveg_patch_wgs84')
ctg_patch <- readOGR('la_shp', 'contig_patch_wgs84')
urbn_grid <- readOGR('la_shp', 'urbn_grid_wgs84')
wgs84crs <- crs(LAcity_boundary)
We’re all set up! And now that we have the shapefiles, I can show you what the extent of our study area, and the habitat “patches”, look like:
tm_shape(LAcity_boundary, projection = 32311) +
tm_fill(col = "#f0f0e4", title = "Urban area") +
tm_shape(cv_patch) +
tm_fill(col = "SI_CLASS",
palette = c("#f8da19","#ffc3af","#8bc34a","#3a554b","#970707","#de3c3c","#68bde1"),
title = "Singapore Index Class") +
tm_layout(main.title = "City of Los Angeles",
main.title.size = 0.95,
frame = FALSE,
inner.margins = 0.02,
legend.text.size = 0.55,
legend.title.size = 0.9,
legend.width = 1.5,
legend.outside = TRUE)

Part One
There are three main stages in Part One:
-
Steps 1-5, in which we will clean, filter, count, and export the records in the iNaturalist .csv’s that we (aka someone in the Grand Challenges office) have downloaded
-
Steps 6-8, in which we subset the species records using patch and urban grid polygons, count up the number of occupied patches/grids, and write those counts to a table
-
Step 9, in which we finally make some maps!
I’ll start by listing the species in each indicator group (these groups are used to organize the downloaded iNaturalist .csv’s and output files). We’ll also add new columns to the ind_spp dataframe, which will eventually be populated with the number of records in each shapefile we export.
ind_spp_groups <- list.dirs('iNat_downloads', full.names = FALSE, recursive = FALSE)
ind_spp$in_city <- 0
ind_spp$in_patches <- 0
ind_spp$in_urban <- 0
ind_spp$cvpatch_count <- 0
ind_spp$ctgpatch_count <- 0
ind_spp$urbn_count <- 0
The main component here is the big for loop, which I will not run in this R Notebook because 1) I have already exported all the files and 2) it really wouldn’t show you a very interesting output. But I’ve detailed each step of the loop in the comments. Loops are not as scary as I once told myself they were!
# Loop through the iNat downloads, one indicator_group at a time
for(ind_group in ind_spp_groups) {
# List the species in each indicator group (using the iNaturalist downloads)
iNat_csv_list <- list.files(paste0('iNat_downloads/',ind_group, collapse=""))
for(species in iNat_csv_list) {
# Read in each species csv; list the variables we will keep upon export
iNat_pts <- read_csv(paste0('iNat_downloads/',ind_group,'/',species, collapse=""))
species_nm <- gsub(".csv", "", species)
keep_cols <- c('scientific_name', 'common_name', 'longitude', 'latitude', 'positional_accuracy', 'observed_on', 'user_id', 'user_login')
# Find the row in ind_spp that lists the current species' details
refrow <- ind_spp %>%
filter(csv_fn == species_nm)
# Using the reference row, determine whether we want to filter for positional accuracy AND a specific species, or for positional accuracy alone.
if(refrow$multiple_spp == FALSE) {
new_csv <- iNat_pts %>%
filter((positional_accuracy <= 10) & (positional_accuracy >= 0)) %>%
filter(scientific_name == refrow$scientific_name) %>%
dplyr::select(keep_cols)
} else {
new_csv <- iNat_pts %>%
filter((positional_accuracy <= 10) & (positional_accuracy >= 0)) %>%
dplyr::select(keep_cols)
}
# Does the species have any accurate records to export?
if((nrow(new_csv)) > 0) {
# If yes, write new csv...
write_csv(new_csv, path = paste0('iNat_cleaned_csv/',ind_group,'/',species, collapse=""))
# Create a new folder for the shapefile, if it does not already exist (we will later create additional shapefiles for each species, so want individual folders)
dir.create(file.path(paste0('species_shp/',ind_group,'/',species_nm)), showWarnings = FALSE)
# Create shapefile from csv - unfortunately, the shapefile() function abbreviates the field names (7 characters max?!?!!? oh well)
sp_xy <- cbind(new_csv$longitude, new_csv$latitude)
sp_pts_all <- SpatialPointsDataFrame(sp_xy,
new_csv,
proj4string = wgs84crs)
shapefile(sp_pts_all, paste0('species_shp/',ind_group,'/',species_nm,'/',species, collapse=""), overwrite = TRUE)
rm(sp_xy)
# Clip spatial object to LA City boundary and print number of records that fall within LA city, the patches, and the urban area to original csv.
# NOTE ON (gIntersection): after much moanin' and groanin', I found that you can SUBSET SPATIALPOINTSDATAFRAMES. NO NEED FOR GINTERSECTS, FOR OVERLAYS, FOR NOTHIN.
sp_pts_LA <- sp_pts_all[LAcity_boundary, ]
ind_spp$in_city <- ifelse(ind_spp$csv_fn == species_nm,
length(sp_pts_LA),
ind_spp$in_city)
sp_pts_patch <- sp_pts_all[ctg_patch, ]
ind_spp$in_patches <- ifelse(ind_spp$csv_fn == species_nm,
length(sp_pts_patch),
ind_spp$in_patches)
sp_pts_urbn <- sp_pts_all[urbn_grid, ]
ind_spp$in_urban <- ifelse(ind_spp$csv_fn == species_nm,
length(sp_pts_urbn),
ind_spp$in_urban)
} else {next}
# Steps 6-8 = Subset species records using patch and urban grid polygons, write counts to table
if(length(sp_pts_LA) > 0) {
if((length(sp_pts_patch) > 0) & (length(sp_pts_urbn) > 0)) {
ctg_sub <- ctg_patch[sp_pts_LA, ]
shapefile(ctg_sub, paste0('species_shp/',ind_group,'/',species_nm,'/',species_nm,'_ctg', collapse=""), overwrite = TRUE)
ind_spp$ctgpatch_count <- ifelse(ind_spp$csv_fn == species_nm,
length(ctg_sub),
ind_spp$ctgpatch_count)
cv_sub <- cv_patch[sp_pts_LA, ]
shapefile(cv_sub, paste0('species_shp/',ind_group,'/',species_nm,'/',species_nm,'_cv', collapse=""), overwrite = TRUE)
ind_spp$cvpatch_count <- ifelse(ind_spp$csv_fn == species_nm,
length(cv_sub),
ind_spp$cvpatch_count)
urbn_sub <- urbn_grid[sp_pts_LA, ]
shapefile(urbn_sub, paste0('species_shp/',ind_group,'/',species_nm,'/',species_nm,'_urbn', collapse=""), overwrite = TRUE)
ind_spp$urbn_count <- ifelse(ind_spp$csv_fn == species_nm,
length(urbn_sub),
ind_spp$urbn_count)
} else if ((length(sp_pts_patch) > 0) & (length(sp_pts_urbn) == 0)) {
ctg_sub <- ctg_patch[sp_pts_LA, ]
shapefile(ctg_sub, paste0('species_shp/',ind_group,'/',species_nm,'/',species_nm,'_ctg', collapse=""), overwrite = TRUE)
ind_spp$ctgpatch_count <- ifelse(ind_spp$csv_fn == species_nm,
length(ctg_sub),
ind_spp$ctgpatch_count)
cv_sub <- cv_patch[sp_pts_LA, ]
shapefile(cv_sub, paste0('species_shp/',ind_group,'/',species_nm,'/',species_nm,'_cv', collapse=""), overwrite = TRUE)
ind_spp$cvpatch_count <- ifelse(ind_spp$csv_fn == species_nm,
length(cv_sub),
ind_spp$cvpatch_count)
} else {
urbn_sub <- urbn_grid[sp_pts_LA, ]
shapefile(urbn_sub, paste0('species_shp/',ind_group,'/',species_nm,'/',species_nm,'_urbn', collapse=""), overwrite = TRUE)
ind_spp$urbn_count <- ifelse(ind_spp$csv_fn == species_nm,
length(urbn_sub),
ind_spp$urbn_count)
}
# Step 9 = Map 1) species point records and 2) presence across patches/grids
# Compose map title
common <- refrow$common_name
scientific <- refrow$scientific_name
map_title <- paste0(common, "\n(",scientific, ")") #After much quality Google time, it seems that italicizing part of a text element in tmap can't be done. Normally, one would wrap the to-be-italicized text so: expression(italic(sci_name))), but tmap does not accept expressions.
# Create point records map
point_map <- tm_shape(LAcity_boundary, projection = 32311) +
tm_fill(col = "#f0f0e4") +
tm_shape(ctg_patch) +
tm_fill(col = "#D8D8CD") +
tm_shape(sp_pts_LA) +
tm_symbols(col = "#00DFB3", border.col = "#ffffff", border.lwd = 0.3, scale = 0.3, shape = 21) +
tm_layout(main.title = map_title,
title = paste("iNaturalist Records in\nCity of LA =", length(sp_pts_LA)),
title.size = 0.9,
title.position = c("left", "bottom"),
frame = FALSE,
inner.margins = 0.06)
# For presence map, choose between species contiguous and Calveg patch files, depending on taxa
if(refrow$taxa == "Mammal") {
poly_map <- tm_shape(LAcity_boundary, projection = 32311) +
tm_fill(col = "#f0f0e4") +
tm_shape(ctg_patch) +
tm_fill(col = "#D8D8CD") +
tm_shape(ctg_sub) +
tm_fill(col = "#00DFB3") +
tm_shape(urbn_sub) +
tm_fill(col = "#00DFB3") +
tm_layout(main.title = " ",
title = "Presence by Contiguous Patch/\nUrban Grid (0.25 mi)",
title.size = 0.9,
title.position = c("left", "bottom"),
frame = FALSE,
inner.margins = 0.06)
} else {
poly_map <- tm_shape(LAcity_boundary, projection = 32311) +
tm_fill(col = "#f0f0e4") +
tm_shape(ctg_patch) +
tm_fill(col = "#D8D8CD") +
tm_shape(cv_sub) +
tm_fill(col = "#00DFB3") +
tm_shape(urbn_sub) +
tm_fill(col = "#00DFB3") +
tm_layout(main.title = " \n ",
title = "Presence by CalVeg Patch/\nUrban Grid (0.25 mi)",
title.size = 0.9,
title.position = c("left", "bottom"),
frame = FALSE,
inner.margins = 0.06)
}
# Export a PDF with the point and polygon maps
pdf(paste0('pdf_previews/',ind_group,'/',species_nm,'.pdf'), paper = "USr", width = 10.3, height = 7.8)
print(tmap_arrange(point_map, poly_map))
dev.off()
} else {next}
# Write updated ind_spp to a new csv
write_csv(ind_spp, path = 'indicator_species_fn_updated.csv')
}
}
Part One Results
Since I’m not running that main chunk of Part One, I’ll demonstrate what the PDF output should look like with one of my favorite species, the red-tailed hawk. Some of the objects have been re-specified, as all of the shapefiles are already ready to go. Here’s a shot of them hanging out in their folder:
library(knitr)
include_graphics('hawk_preview.png')

redtail_pts <- readOGR('species_shp/common_native_fauna/Buteo_jamaicensis', 'Buteo_jamaicensis')
OGR data source with driver: ESRI Shapefile
Source: "C:\Users\monica\Documents\Bio_Index\species_data\species_shp\common_native_fauna\Buteo_jamaicensis", layer: "Buteo_jamaicensis"
with 916 features
It has 8 fields
ctg_sub <- readOGR('species_shp/common_native_fauna/Buteo_jamaicensis', 'Buteo_jamaicensis_ctg')
OGR data source with driver: ESRI Shapefile
Source: "C:\Users\monica\Documents\Bio_Index\species_data\species_shp\common_native_fauna\Buteo_jamaicensis", layer: "Buteo_jamaicensis_ctg"
with 22 features
It has 1 fields
cv_sub <- readOGR('species_shp/common_native_fauna/Buteo_jamaicensis', 'Buteo_jamaicensis_cv')
OGR data source with driver: ESRI Shapefile
Source: "C:\Users\monica\Documents\Bio_Index\species_data\species_shp\common_native_fauna\Buteo_jamaicensis", layer: "Buteo_jamaicensis_cv"
with 45 features
It has 1 fields
urbn_sub <- readOGR('species_shp/common_native_fauna/Buteo_jamaicensis', 'Buteo_jamaicensis_urbn')
OGR data source with driver: ESRI Shapefile
Source: "C:\Users\monica\Documents\Bio_Index\species_data\species_shp\common_native_fauna\Buteo_jamaicensis", layer: "Buteo_jamaicensis_urbn"
with 41 features
It has 1 fields
sp_pts_LA <- redtail_pts[LAcity_boundary, ]
refrow <- ind_spp %>%
filter(csv_fn == "Buteo_jamaicensis")
common <- refrow$common_name
scientific <- refrow$scientific_name
map_title <- paste0(common, "\n(",scientific, ")")
# Create point records map
point_map <- tm_shape(LAcity_boundary, projection = 32311) +
tm_fill(col = "#f0f0e4") +
tm_shape(ctg_patch) +
tm_fill(col = "#D8D8CD") +
tm_shape(sp_pts_LA) +
tm_symbols(col = "#00DFB3", border.col = "#ffffff", border.lwd = 0.3, scale = 0.3, shape = 21) +
tm_layout(main.title = map_title,
title = paste("iNaturalist Records in\nCity of LA =", length(sp_pts_LA)),
title.size = 0.9,
title.position = c("left", "bottom"),
frame = FALSE,
inner.margins = 0.06)
# For presence map, choose between species contiguous and Calveg patch files, depending on taxonomic group
if(refrow$taxa == "Mammal") {
poly_map <- tm_shape(LAcity_boundary, projection = 32311) +
tm_fill(col = "#f0f0e4") +
tm_shape(ctg_patch) +
tm_fill(col = "#D8D8CD") +
tm_shape(ctg_sub) +
tm_fill(col = "#00DFB3") +
tm_shape(urbn_sub) +
tm_fill(col = "#00DFB3") +
tm_layout(main.title = " ",
title = "Presence by Contiguous Patch/\nUrban Grid (0.25 mi)",
title.size = 0.9,
title.position = c("left", "bottom"),
frame = FALSE,
inner.margins = 0.06)
} else {
poly_map <- tm_shape(LAcity_boundary, projection = 32311) +
tm_fill(col = "#f0f0e4") +
tm_shape(ctg_patch) +
tm_fill(col = "#D8D8CD") +
tm_shape(cv_sub) +
tm_fill(col = "#00DFB3") +
tm_shape(urbn_sub) +
tm_fill(col = "#00DFB3") +
tm_layout(main.title = " \n ",
title = "Presence by CalVeg Patch/\nUrban Grid (0.25 mi)",
title.size = 0.9,
title.position = c("left", "bottom"),
frame = FALSE,
inner.margins = 0.06)
}
tmap_arrange(point_map, poly_map)

And as proof that the whole process worked - here are the common native fauna PDFs, all happily nestled away in the appropriate directory:
include_graphics('pdf_preview.png')

Let’s also take a look at some of the data that we added to the ind_spp dataframe using ggplot. A preview of those new columns:
ind_spp_2 <- read_csv('indicator_species_fn_updated.csv')
Parsed with column specification:
cols(
indicator_group = col_character(),
taxa = col_character(),
scientific_name = col_character(),
common_name = col_character(),
multiple_spp = col_logical(),
ranking = col_character(),
csv_fn = col_character(),
in_city = col_integer(),
in_patches = col_integer(),
in_urban = col_integer(),
cvpatch_count = col_integer(),
ctgpatch_count = col_integer(),
urbn_count = col_integer()
)
head(ind_spp_2)
# A tibble: 6 x 13
indicator_group taxa scientific_name common_name multiple_spp ranking csv_fn in_city in_patches in_urban cvpatch_count ctgpatch_count
<chr> <chr> <chr> <chr> <lgl> <chr> <chr> <int> <int> <int> <int> <int>
1 common_native_~ Plant Abronia umbell~ Sand Verbe~ FALSE 2 a b c Abron~ 6 5 1 1 1
2 common_native_~ Plant Artemisia cali~ California~ FALSE 2 a b c Artem~ 182 118 63 74 22
3 common_native_~ Plant Baccharis pilu~ Coyote bru~ FALSE 2 a b c Bacch~ 77 60 18 35 13
4 common_native_~ Plant Baccharis sali~ Mulefat FALSE 2 a b c Bacch~ 0 0 0 0 0
5 common_native_~ Plant Camissoniopsis~ Beach even~ FALSE 2 a b c Camis~ 2 1 1 1 1
6 common_native_~ Plant Castilleja exs~ Purple owl~ FALSE 2 a b c Casti~ 2 1 1 1 1
# ... with 1 more variable: urbn_count <int>
Which indicator species groups do we have the most data for?
available <- ind_spp_2 %>%
mutate(records = ifelse(in_city > 0, "Yes", "No"))
ggplot(available, aes(x = indicator_group, fill = records)) +
geom_bar(position = "fill", width = 0.27) +
geom_text(stat='count', aes(label=..count..), size = 3.5, position = position_fill(vjust = 0.5)) +
scale_fill_discrete(name = "Records available?") +
coord_flip() +
scale_x_discrete(labels = c("Common\nnative fauna", "Common\nnative flora", "Common\nurban sp.", "Invasive", "Rare native")) +
labs(title = "iNaturalist Records in City of LA", subtitle = "Availability by species; labels indicate number of species", x = "", y = "% of species") +
theme_minimal() +
theme(plot.title = element_text(vjust = 2, face = "bold"),
plot.subtitle = element_text(color = "gray60"),
axis.title.x = element_text(vjust = -1, color = "gray60"))

Availability is lower for rare natives and invasive species, which is not unexpected (some of the invasive species are relatively newer to the region or harder to observe). Which taxa are most represented in the zero records group? We’ll lump common native flora and fauna together here for a little more visual clarity.
no_la_records <- ind_spp_2 %>%
filter(in_city == 0) %>%
mutate(group_2 = indicator_group) %>%
mutate(group_2 = sub("_fauna", "_ff", group_2)) %>%
mutate(group_2 = sub("_flora", "_ff", group_2))
ggplot(no_la_records, aes(x = taxa, fill = group_2)) +
geom_bar(position = position_dodge2(preserve = "single")) +
coord_flip() +
scale_y_continuous(breaks = seq(0, 10, by = 2)) +
scale_fill_manual(values = c("#53d397","#cce490", "#ff5f5f","#fbd341"),
labels = c("Common native flora/fauna", "Common urban sp.", "Invasive", "Rare native"),
name = "Indicator\nspecies group") +
labs(title = "Which indicator species are missing iNaturalist data in the City of LA?",
subtitle = "By taxonomic group",
x = "", y = "No. of species") +
theme_minimal() +
theme(plot.title = element_text(vjust = 2, face = "bold"),
plot.subtitle = element_text(color = "gray60"),
axis.title.x = element_text(vjust = -1, color = "gray60"))

Invertebrates are the toughest species to get records for (1 out of 1 algae species also has no records) - surprise! People would much rather chronicle their encounters with bobcats than with crickets.
Of the species we do have records for, how many are being observed in the urban areas vs. habitat patches?
la_records <- available %>%
filter(records == "Yes") %>%
group_by(taxa) %>%
summarise(in_patches = sum(in_patches, na.rm = TRUE),
in_urban = sum(in_urban, na.rm = TRUE)) %>%
gather(key = "location", value = "records", 2:3)
ggplot(la_records, aes(x = taxa, y = records, fill = location)) +
geom_col(position = "fill", width = 0.3) +
geom_text(aes(label = records), size = 3.5, position = position_fill(vjust = 0.5)) +
scale_fill_manual(name = "",
labels = c("Habitat patches", "Urban areas"),
values = c("#7dc383", "#D8D8CD")) +
coord_flip() +
labs(title = "Where are species spotted in the City of LA?", subtitle = "Labels indicate total iNaturalist records per taxonomic group", x = "", y = "% of species") +
theme_minimal() +
theme(plot.title = element_text(vjust = 2, face = "bold"),
plot.subtitle = element_text(color = "gray60"),
axis.title.x = element_text(vjust = -1, color = "gray60"))

Interesting! Observers spot more mammals, invertebrates, and birds in urban areas than they do in habitat patches. There are a few reasonable explanations for this, one obviously being that 12% of the indicator species are in the “common native in urban area” group. Also, some mammals, like the invasive fox squirrel, truly are more common in urban areas. But for other species, this difference between urban and patch observations could be a reflection of sampling bias. Meanwhile, amphibians were more likely to be observed in habitat patches - probably something to do with their very specific habitat needs (I believe all of LA’s riparian resources are all classified as habitat patches). The low proportion of plants recorded in urban areas could be due to the dominance of non-native ornamentals in managed landscapes, or our exclusion of records of “captive” species, or both.
What’s Next
Part two is next! Once we have some scoring methods, I have a pretty good idea of how I might put together the script. The analysis itself probably won’t be so complex, but like the procedures in Part One, it will likely be tested and changed multiple times as the methods are developed. Writing the script so far has been very educational, and I think it will help the rest of the process go much smoother! Conversely, the more I work with the loops, the more I understand all the little places where the code can be pruned and streamlined. So I hope that working through the next part of this project will enable me to go back and improve the first half.
There are things to improve on the maps themselves as well. Certain issues that feel relatively small, but still escape me. These include italicizing just part of a title in a tmap object, and customizing a pdf layout that includes tmaps (e.g. adding a title and some text). I would really like to make the scored maps interactive - though I’m not totally sure how I would share them if I continue to use tmap (maybe just the HTML page?). That brings me to the next item on my wish list: expanding my mapping-in-R skillset beyond tmap. Thus far, it’s been my favorite because it so easily handles shapefiles. ggplot seems not so straightforward when it comes to polygons, and gganimate is intimidating. But tmap is not without its limitations, so I’m excited to try and wrangle the other methods!
All species data from iNaturalist. http://www.inaturalist.org
LS0tDQp0aXRsZTogIkNsZWFuaW5nLCBTY29yaW5nLCBhbmQgTWFwcGluZyBpTmF0dXJhbGlzdCBEYXRhIg0Kc3VidGl0bGU6ICJNb25pY2EgRGltc29uIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KPGI+QmFja2dyb3VuZDwvYj4NCg0KTXkgbGFiIChha2EgbWUgYW5kIG15IGFkdmlzb3IgVC4gR2lsbGVzcGllKSBpcyBjb2xsYWJvcmF0aW5nIHdpdGggdHdvIExBLWJhc2VkIHByb2plY3RzIChhdCB0aGUgQ2l0eSBhbmQgQ291bnR5KSB0aGF0IGFyZSBpbnRlcmVzdGVkIGluIHVzaW5nIGxvY2FsIHNwZWNpZXMgZGF0YSB0byBhc3Nlc3MgYmlvZGl2ZXJzaXR5L3N1c3RhaW5hYmlsaXR5LiBCb3RoIHByb2plY3RzIHdhbnQgdG8gdXNlIHRoZSBwcmVzZW5jZSBvZiBjZXJ0YWluIGluZGljYXRvciBzcGVjaWVzIHRvIHJlZmxlY3QsIHRvIHNvbWUgZXh0ZW50LCB2YXJpYWJsZXMgbGlrZSBoYWJpdGF0IHF1YWxpdHkgYW5kIHNwZWNpZXMgcmljaG5lc3MuIEEgc2V0IG9mIChhcyB5ZXQgdW5maW5hbGl6ZWQpIGNhbGN1bGF0aW9ucyB3aWxsIGNvbnZlcnQgdGhlIHNwZWNpZXMgZGF0YSB0byBhbiBhcmVhLWJhc2VkICJzY29yZSwiIHdoaWNoIHdpbGwgdGhlbiBiZSBhZGRlZCB1cCB3aXRoIHNldmVyYWwgb3RoZXIgdmFyaWFibGVzL2luZGljYXRvcnMuDQoNClRoZSBhdmFpbGFiaWxpdHkgb2Ygc3BlY2llcyBkYXRhIGlzIG9idmlvdXNseSBrZXkgaGVyZS4gQmVjYXVzZSBzcGVjaWVzIG9jY3VycmVuY2UgZGF0YSBvYnRhaW5lZCB0aHJvdWdoIHJvdXRpbmUsIGNvbnRyb2xsZWQgbW9uaXRvcmluZyBhcmUgdmVyeSBkaWZmaWN1bHQgdG8gY29tZSBieSwgdGhlc2UgcHJvamVjdHMgYXJlIGludGVyZXN0ZWQgaW4gdGhlIHV0aWxpdHkgb2YgY2l0aXplbiBzY2llbmNlIGludmVudG9yaWVzIGxpa2UgaU5hdHVyYWxpc3QuIENpdGl6ZW4gc2NpZW5jZSBkYXRhYmFzZXMgYXJlIGV4dHJlbWVseSB2YWx1YWJsZSwgYXMgdGhleSBvZmZlciBhY2Nlc3MgdG8gdGhlIHBvb2xlZCBlZmZvcnRzIGFuZCBvYnNlcnZhdGlvbnMgb2YgbWFueSBtb3JlIHBlb3BsZSAoYSBsb3QgbW9yZSBmcmVxdWVudGx5KSB0aGFuIHlvdSBtaWdodCBoYXZlIG90aGVyd2lzZS4gVGhlIGRpc2FkdmFudGFnZXMsIGhvd2V2ZXIsIGluY2x1ZGUgc29tZSBkZWdyZWUgb2Ygc3BhdGlhbCBzYW1wbGluZyBiaWFzIGFtb25nIHRoZSBvYnNlcnZhdGlvbnMgYW5kIHBvdGVudGl0YWwgaW5hY2N1cmFjaWVzIGluIHNwZWNpZXMgaWRlbnRpZmljYXRpb25zLiANCg0KV2UndmUgYmVlbiBhc2tlZCB0byBoZWxwIGV2YWx1YXRlIHRoZSB1dGlsaXR5IGFuZCBhdmFpbGFiaWxpdHkgb2YgaU5hdHVyYWxpc3QgZGF0YSBmb3IgMTE5IGluZGljYXRvciBzcGVjaWVzLiBXZSBuZWVkIHRvIHByb3ZpZGUgdGhlIG51bWJlciBvZiBhY2N1cmF0ZSwgTEEgY2l0eSByZWNvcmRzIGZvciBlYWNoIHNwZWNpZXMsIGFuZCBzaW1wbGUgbWFwcyBvZiB0aGVpciBkaXN0cmlidXRpb25zLiBPbmNlIHdlIHJldmlldyB0aGUgY292ZXJhZ2Ugb2YgdGhlIGlOYXR1cmFsaXN0IGRhdGEgd2l0aCBvdXIgY29sbGFib3JhdG9ycywgd2UnbGwgdXNlIHRoaXMgZGF0YSB0byBzY29yZSBMQSdzIG5vbi11cmJhbiBwYXRjaGVzIGFuZCB1cmJhbiBhcmVhcy4gU2NvcmluZyBtZXRob2RzIGFyZSBzdGlsbCBiZWluZyBmaW5hbGl6ZWQsIGJ1dCB3ZSBleHBlY3QgdGhlbSB0byB2YXJ5IGJ5IHRheGEgYW5kIGluZGljYXRvciBzcGVjaWVzIGdyb3VwLiBUaGVyZSBhcmUgNSBncm91cHMgaW4gdG90YWw6IGNvbW1vbiBuYXRpdmUgZmF1bmEgKGZvdW5kIG1vc3RseSBpbiAibmF0dXJhbCIgYXJlYXMpLCBjb21tb24gbmF0aXZlIGZsb3JhIGZvdW5kIG1vc3RseSBpbiAibmF0dXJhbCIgYXJlYXMpLCBuYXRpdmUgZmF1bmEgdGhhdCBjYW4gY29tbW9ubHkgYmUgZm91bmQgaW4gdXJiYW4gYXJlYXMsIHJhcmUgbmF0aXZlIHNwZWNpZXMgb2YgY29uY2VybiwgYW5kIGludmFzaXZlIHNwZWNpZXMuIA0KIA0KPGhyPg0KPGI+T2JqZWN0aXZlczwvYj4NCg0KV3JpdGUgYSBzY3JpcHQgdGhhdDoNCjx1bD4NCjxsaT5GaWx0ZXJzIHRocm91Z2ggdGhlIGlOYXR1cmFsaXN0IGRhdGEsPC9saT4NCjxsaT5FeHBvcnRzIGEgY2xlYW4gLmNzdiBhbmQgLnNocCBmb3IgZWFjaCBzcGVjaWVzLDwvbGk+DQo8bGk+UHJvZHVjZXMgc29tZSBzaW1wbGUgbWFwcyw8L2xpPg0KPGxpPkNhbGN1bGF0ZXMgYXJlYS1iYXNlZCBzY29yZXMgZm9yIGRpZmZlcmVudCBncm91cHMgb2Ygc3BlY2llcywgYW5kPC9saT4NCjxsaT5DYW4gYmUgZWFzaWx5IHVwZGF0ZWQgdG8gcmVmbGVjdCBjaGFuZ2VzIGluIHRoZSBpbmRpY2F0b3Igc3BlY2llcyBsaXN0cywgdW5pdHMgb2YgYW5hbHlzaXMsIGFuZCBzY29yaW5nIG1ldGhvZHM8L2xpPg0KPC91bD4NCg0KPGhyPg0KPGI+TWFpbiBTdGVwczwvYj4NCg0KSSBhbSBkaXZpZGluZyB0aGUgcHJvY2VkdXJlcyBpbnRvIHR3byBwYXJ0cy4gUGFydCBvbmUgKHN0ZXBzIDEtOSkgd2lsbCBjbGVhbiBhbmQgdmlzdWFsaXplIHRoZSBkYXRhIG9uIGEgc3BlY2llcy1ieS1zcGVjaWVzIGJhc2lzLiBQYXJ0IHR3byBpbmNsdWRlcyB0aGUgc3RlcHMgZm9yIHNjb3JpbmcuIA0KPG9sPg0KPGxpPklkZW50aWZ5IGFuZCBzZWxlY3QgcmVjb3JkcyB0aGF0OiBhKSBoYXZlIGFjY3VyYWN5IHJhZGl1cyA8PSAxMG0gYW5kIGIpIE1hdGNoIHRoZSBsaXN0ZWQgaW5kaWNhdG9yIHNwZWNpZXMvc3Vic3BlY2llcyAodW5sZXNzIG11bHRpcGxlIHNwZWNpZXMgYXJlIHRvIGJlIHVzZWQgYXMgaW5kaWNhdG9ycyk8L2xpPg0KPGxpPkV4cG9ydCBjb2x1bW5zIGZvciBzcGVjaWVzIHNjaWVudGlmaWMgbmFtZSwgY29tbW9uIG5hbWUsIGxvbmdpdHVkZSwgbGF0aXR1ZGUsIG9ic2VydmF0aW9uIGRhdGUsIGFuZCBpTmF0IHVzZXIgaW5mbyB0byBDU1Y8L2xpPg0KPGxpPkV4cG9ydCBzaGFwZWZpbGUgZnJvbSBpTmF0IHJlY29yZHMgQ1NWPC9saT4NCjxsaT5DcmVhdGUgYSBzdWJzZXQgb2YgcmVjb3JkcyB1c2luZyB0aGUgTEEgQ2l0eSBib3VuZGFyeTwvbGk+DQo8bGk+Q291bnQgbnVtYmVyIG9mIHNwZWNpZXMgcmVjb3JkcyBpbiBjaXR5ICh0b3RhbCksIGluIHRoZSBwYXRjaCBhcmVhLCBhbmQgaW4gdGhlIHVyYmFuIGFyZWEsIGFuZCB3cml0ZSB0byB0YWJsZTwvbGk+DQo8bGk+SWRlbnRpZnkgcGF0Y2hlcyBhbmQvb3IgZ3JpZHMgdGhhdCBjb250YWluIHNwZWNpZXMgcG9pbnRzPC9saT4NCjxsaT5FeHBvcnQgcGF0Y2hlcyBhbmQvb3IgZ3JpZHMgdG8gbmV3IHNoYXBlZmlsZXM8L2xpPg0KPGxpPlVwZGF0ZSB0YWJsZSB3aXRoIG51bWJlciBvZiBwYXRjaGVzIGFuZCB1cmJhbiBncmlkcyB0aGF0IGNvbnRhaW4gc3BlY2llcyByZWNvcmRzPC9saT4NCjxsaT5NYXAgYSkgc3BlY2llcyBwb2ludCByZWNvcmRzIGFuZCBiKSBwYXRjaGVzL2dyaWRzIGNvbnRhaW5pbmcgaU5hdCBkYXRhPC9saT4NCjxsaT5Vc2luZyBCaW9kaXZlcnNpdHkgSW5kZXggbWV0aG9kcywgY2FsY3VsYXRlIEluZGljYXRvciBzY29yZSBmb3IgZWFjaCBzcGVjaWVzIGdyb3VwPC9saT4NCjxsaT5NYXAgSW5kaWNhdG9yIHNjb3JlczwvbGk+DQo8L29sPg0KDQo8aHI+DQo8Yj5HZXR0aW5nIFN0YXJ0ZWQ8L2I+DQoNCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoc3ApDQpsaWJyYXJ5KHJnZGFsKQ0KbGlicmFyeShyZ2VvcykNCmxpYnJhcnkocmFzdGVyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeSh0bWFwKQ0KYGBgDQoNClNob3VsZCBJIG5lZWQgdG8gcmUtcnVuIHRoaXMgc2NyaXB0IGluIGZ1dHVyZSwgdGhlIG9ubHkgdGhpbmcgSSB3YW50IHRvICpoYXZlKiB0byB1cGRhdGUgaXMgdGhpcyBsaW5lLCBzZXR0aW5nIHRoZSB3b3JrIGRpcmVjdG9yeTogDQoNCmBgYHtyfQ0Kc2V0d2QoJ0M6L1VzZXJzL21vbmljYS9Eb2N1bWVudHMvQmlvX0luZGV4L3NwZWNpZXNfZGF0YScpDQpgYGANCg0KT25lIGltcG9ydGFudCBpbnB1dCBmaWxlIGlzIHRoaXMgc29ydCBvZiByZWZlcmVuY2UgbGlzdCwgd2hpY2ggY29udGFpbnMgc29tZSBpbmZvcm1hdGlvbiBhYm91dCB0aGUgaW5kaWNhdG9yIHNwZWNpZXMuIFRoaXMgdG9vIGNvdWxkIHBvdGVudGlhbGx5IGJlIHVwZGF0ZWQgaW4gZnV0dXJlLiBMZXQncyB0YWtlIGEgcGVlazoNCg0KYGBge3J9DQppbmRfc3BwIDwtIHJlYWRfY3N2KCdpbmRpY2F0b3Jfc3BlY2llc19mbi5jc3YnKQ0KaGVhZChpbmRfc3BwKQ0KIyBIb3cgbWFueSB1bmlxdWUgdmFsdWVzIGFyZSB0aGVyZSBmb3IgZWFjaCB2YXJpYWJsZT8NCnNhcHBseShpbmRfc3BwLCBmdW5jdGlvbih4KSBsZW5ndGgodW5pcXVlKHgpKSkNCiMgSG93IG1hbnkgc3BlY2llcyBhcmUgaW4gZWFjaCBpbmRpY2F0b3IgZ3JvdXA/DQpzcHBfcGVyX2dyb3VwIDwtIGluZF9zcHAgJT4lDQogIGdyb3VwX2J5KGluZGljYXRvcl9ncm91cCkgJT4lDQogIHN1bW1hcmlzZShzcGVjaWVzID0gbigpKQ0KcHJpbnQoc3BwX3Blcl9ncm91cCkNCmBgYA0KDQpUaGUgb3RoZXIgaW5wdXRzIGFyZSBzb21lIHNoYXBlZmlsZXMgZm9yIExBIENpdHkuIEknbGwgYWxzbyBhc3NpZ24gdGhlaXIgY29vcmRpbmF0ZSBzeXN0ZW0gKFdHUyA4NCkgdG8gYSB2YXJpYWJsZSBmb3IgbGF0ZXIgdXNlOg0KDQpgYGB7cn0NCkxBY2l0eV9ib3VuZGFyeSA8LSByZWFkT0dSKCdsYV9zaHAnLCAnbGFfY2l0eV9ncmlkZGlzc29sdmVfd2dzODQnKQ0KY3ZfcGF0Y2ggPC0gcmVhZE9HUignbGFfc2hwJywgJ2NhbHZlZ19wYXRjaF93Z3M4NCcpDQpjdGdfcGF0Y2ggPC0gcmVhZE9HUignbGFfc2hwJywgJ2NvbnRpZ19wYXRjaF93Z3M4NCcpDQp1cmJuX2dyaWQgPC0gcmVhZE9HUignbGFfc2hwJywgJ3VyYm5fZ3JpZF93Z3M4NCcpDQoNCndnczg0Y3JzIDwtIGNycyhMQWNpdHlfYm91bmRhcnkpDQpgYGANCg0KV2UncmUgYWxsIHNldCB1cCEgQW5kIG5vdyB0aGF0IHdlIGhhdmUgdGhlIHNoYXBlZmlsZXMsIEkgY2FuIHNob3cgeW91IHdoYXQgdGhlIGV4dGVudCBvZiBvdXIgc3R1ZHkgYXJlYSwgYW5kIHRoZSBoYWJpdGF0ICJwYXRjaGVzIiwgbG9vayBsaWtlOg0KDQpgYGB7cn0NCnRtX3NoYXBlKExBY2l0eV9ib3VuZGFyeSwgcHJvamVjdGlvbiA9IDMyMzExKSArDQogIHRtX2ZpbGwoY29sID0gIiNmMGYwZTQiLCB0aXRsZSA9ICJVcmJhbiBhcmVhIikgKw0KdG1fc2hhcGUoY3ZfcGF0Y2gpICsNCiAgdG1fZmlsbChjb2wgPSAiU0lfQ0xBU1MiLA0KICAgICAgICAgIHBhbGV0dGUgPSBjKCIjZjhkYTE5IiwiI2ZmYzNhZiIsIiM4YmMzNGEiLCIjM2E1NTRiIiwiIzk3MDcwNyIsIiNkZTNjM2MiLCIjNjhiZGUxIiksDQogICAgICAgICAgdGl0bGUgPSAiU2luZ2Fwb3JlIEluZGV4IENsYXNzIikgKw0KICB0bV9sYXlvdXQobWFpbi50aXRsZSA9ICJDaXR5IG9mIExvcyBBbmdlbGVzIiwNCiAgICAgICAgICAgIG1haW4udGl0bGUuc2l6ZSA9IDAuOTUsDQogICAgICAgICAgICBmcmFtZSA9IEZBTFNFLA0KICAgICAgICAgICAgaW5uZXIubWFyZ2lucyA9IDAuMDIsDQogICAgICAgICAgICBsZWdlbmQudGV4dC5zaXplID0gMC41NSwNCiAgICAgICAgICAgIGxlZ2VuZC50aXRsZS5zaXplID0gMC45LA0KICAgICAgICAgICAgbGVnZW5kLndpZHRoID0gMS41LA0KICAgICAgICAgICAgbGVnZW5kLm91dHNpZGUgPSBUUlVFKQ0KYGBgDQoNCg0KPGhyPg0KPGI+UGFydCBPbmU8L2I+DQoNClRoZXJlIGFyZSB0aHJlZSBtYWluIHN0YWdlcyBpbiBQYXJ0IE9uZToNCjx1bD4NCjxsaT48Yj5TdGVwcyAxLTUsPC9iPiBpbiB3aGljaCB3ZSB3aWxsIGNsZWFuLCBmaWx0ZXIsIGNvdW50LCBhbmQgZXhwb3J0IHRoZSByZWNvcmRzIGluIHRoZSBpTmF0dXJhbGlzdCAuY3N2J3MgdGhhdCB3ZSAoYWthIHNvbWVvbmUgaW4gdGhlIEdyYW5kIENoYWxsZW5nZXMgb2ZmaWNlKSBoYXZlIGRvd25sb2FkZWQ8L2xpPg0KPGxpPjxiPiBTdGVwcyA2LTgsPC9iPiBpbiB3aGljaCB3ZSBzdWJzZXQgdGhlIHNwZWNpZXMgcmVjb3JkcyB1c2luZyBwYXRjaCBhbmQgdXJiYW4gZ3JpZCBwb2x5Z29ucywgY291bnQgdXAgdGhlIG51bWJlciBvZiBvY2N1cGllZCBwYXRjaGVzL2dyaWRzLCBhbmQgd3JpdGUgdGhvc2UgY291bnRzIHRvIGEgdGFibGU8L2xpPg0KPGxpPjxiPlN0ZXAgOSw8L2I+IGluIHdoaWNoIHdlIGZpbmFsbHkgbWFrZSBzb21lIG1hcHMhPC9saT4NCjwvdWw+DQoNCkknbGwgc3RhcnQgYnkgbGlzdGluZyB0aGUgc3BlY2llcyBpbiBlYWNoIGluZGljYXRvciBncm91cCAodGhlc2UgZ3JvdXBzIGFyZSB1c2VkIHRvIG9yZ2FuaXplIHRoZSBkb3dubG9hZGVkIGlOYXR1cmFsaXN0IC5jc3YncyBhbmQgb3V0cHV0IGZpbGVzKS4gV2UnbGwgYWxzbyBhZGQgbmV3IGNvbHVtbnMgdG8gdGhlIGluZF9zcHAgZGF0YWZyYW1lLCB3aGljaCB3aWxsIGV2ZW50dWFsbHkgYmUgcG9wdWxhdGVkIHdpdGggdGhlIG51bWJlciBvZiByZWNvcmRzIGluIGVhY2ggc2hhcGVmaWxlIHdlIGV4cG9ydC4NCg0KYGBge3J9DQppbmRfc3BwX2dyb3VwcyA8LSBsaXN0LmRpcnMoJ2lOYXRfZG93bmxvYWRzJywgZnVsbC5uYW1lcyA9IEZBTFNFLCByZWN1cnNpdmUgPSBGQUxTRSkNCmluZF9zcHAkaW5fY2l0eSA8LSAwDQppbmRfc3BwJGluX3BhdGNoZXMgPC0gMA0KaW5kX3NwcCRpbl91cmJhbiA8LSAwDQppbmRfc3BwJGN2cGF0Y2hfY291bnQgPC0gMA0KaW5kX3NwcCRjdGdwYXRjaF9jb3VudCA8LSAwDQppbmRfc3BwJHVyYm5fY291bnQgPC0gMA0KYGBgDQoNClRoZSBtYWluIGNvbXBvbmVudCBoZXJlIGlzIHRoZSBiaWcgZm9yIGxvb3AsIHdoaWNoIEkgd2lsbCBub3QgcnVuIGluIHRoaXMgUiBOb3RlYm9vayBiZWNhdXNlIDEpIEkgaGF2ZSBhbHJlYWR5IGV4cG9ydGVkIGFsbCB0aGUgZmlsZXMgYW5kIDIpIGl0IHJlYWxseSB3b3VsZG4ndCBzaG93IHlvdSBhIHZlcnkgaW50ZXJlc3Rpbmcgb3V0cHV0LiBCdXQgSSd2ZSBkZXRhaWxlZCBlYWNoIHN0ZXAgb2YgdGhlIGxvb3AgaW4gdGhlIGNvbW1lbnRzLiBMb29wcyBhcmUgbm90IGFzIHNjYXJ5IGFzIEkgb25jZSB0b2xkIG15c2VsZiB0aGV5IHdlcmUhDQoNCmBgYHtyfQ0KIyBMb29wIHRocm91Z2ggdGhlIGlOYXQgZG93bmxvYWRzLCBvbmUgaW5kaWNhdG9yX2dyb3VwIGF0IGEgdGltZQ0KZm9yKGluZF9ncm91cCBpbiBpbmRfc3BwX2dyb3Vwcykgew0KICANCiAgIyBMaXN0IHRoZSBzcGVjaWVzIGluIGVhY2ggaW5kaWNhdG9yIGdyb3VwICh1c2luZyB0aGUgaU5hdHVyYWxpc3QgZG93bmxvYWRzKQ0KICBpTmF0X2Nzdl9saXN0IDwtIGxpc3QuZmlsZXMocGFzdGUwKCdpTmF0X2Rvd25sb2Fkcy8nLGluZF9ncm91cCwgY29sbGFwc2U9IiIpKQ0KICANCiAgZm9yKHNwZWNpZXMgaW4gaU5hdF9jc3ZfbGlzdCkgew0KICAgIA0KICAgICMgUmVhZCBpbiBlYWNoIHNwZWNpZXMgY3N2OyBsaXN0IHRoZSB2YXJpYWJsZXMgd2Ugd2lsbCBrZWVwIHVwb24gZXhwb3J0DQogICAgaU5hdF9wdHMgPC0gcmVhZF9jc3YocGFzdGUwKCdpTmF0X2Rvd25sb2Fkcy8nLGluZF9ncm91cCwnLycsc3BlY2llcywgY29sbGFwc2U9IiIpKQ0KICAgIHNwZWNpZXNfbm0gPC0gZ3N1YigiLmNzdiIsICIiLCBzcGVjaWVzKQ0KICAgIGtlZXBfY29scyA8LSBjKCdzY2llbnRpZmljX25hbWUnLCAnY29tbW9uX25hbWUnLCAnbG9uZ2l0dWRlJywgJ2xhdGl0dWRlJywgJ3Bvc2l0aW9uYWxfYWNjdXJhY3knLCAnb2JzZXJ2ZWRfb24nLCAndXNlcl9pZCcsICd1c2VyX2xvZ2luJykNCiAgICANCiAgICAjIEZpbmQgdGhlIHJvdyBpbiBpbmRfc3BwIHRoYXQgbGlzdHMgdGhlIGN1cnJlbnQgc3BlY2llcycgZGV0YWlscw0KICAgIHJlZnJvdyA8LSBpbmRfc3BwICU+JQ0KICAgICAgZmlsdGVyKGNzdl9mbiA9PSBzcGVjaWVzX25tKQ0KICAgIA0KICAgICMgVXNpbmcgdGhlIHJlZmVyZW5jZSByb3csIGRldGVybWluZSB3aGV0aGVyIHdlIHdhbnQgdG8gZmlsdGVyIGZvciBwb3NpdGlvbmFsIGFjY3VyYWN5IEFORCBhIHNwZWNpZmljIHNwZWNpZXMsIG9yIGZvciBwb3NpdGlvbmFsIGFjY3VyYWN5IGFsb25lLg0KICAgIGlmKHJlZnJvdyRtdWx0aXBsZV9zcHAgPT0gRkFMU0UpIHsNCiAgICAgIG5ld19jc3YgPC0gaU5hdF9wdHMgJT4lDQogICAgICAgIGZpbHRlcigocG9zaXRpb25hbF9hY2N1cmFjeSA8PSAxMCkgJiAocG9zaXRpb25hbF9hY2N1cmFjeSA+PSAwKSkgJT4lDQogICAgICAgIGZpbHRlcihzY2llbnRpZmljX25hbWUgPT0gcmVmcm93JHNjaWVudGlmaWNfbmFtZSkgJT4lDQogICAgICAgIGRwbHlyOjpzZWxlY3Qoa2VlcF9jb2xzKQ0KICAgIH0gZWxzZSB7DQogICAgICBuZXdfY3N2IDwtIGlOYXRfcHRzICU+JQ0KICAgICAgICBmaWx0ZXIoKHBvc2l0aW9uYWxfYWNjdXJhY3kgPD0gMTApICYgKHBvc2l0aW9uYWxfYWNjdXJhY3kgPj0gMCkpICU+JQ0KICAgICAgICBkcGx5cjo6c2VsZWN0KGtlZXBfY29scykNCiAgICB9DQogICAgICANCiAgICAjIERvZXMgdGhlIHNwZWNpZXMgaGF2ZSBhbnkgYWNjdXJhdGUgcmVjb3JkcyB0byBleHBvcnQ/DQogICAgaWYoKG5yb3cobmV3X2NzdikpID4gMCkgew0KDQogICAgICAjIElmIHllcywgd3JpdGUgbmV3IGNzdi4uLg0KICAgICAgd3JpdGVfY3N2KG5ld19jc3YsIHBhdGggPSBwYXN0ZTAoJ2lOYXRfY2xlYW5lZF9jc3YvJyxpbmRfZ3JvdXAsJy8nLHNwZWNpZXMsIGNvbGxhcHNlPSIiKSkNCiAgICAgIA0KICAgICAgIyBDcmVhdGUgYSBuZXcgZm9sZGVyIGZvciB0aGUgc2hhcGVmaWxlLCBpZiBpdCBkb2VzIG5vdCBhbHJlYWR5IGV4aXN0ICh3ZSB3aWxsIGxhdGVyIGNyZWF0ZSBhZGRpdGlvbmFsIHNoYXBlZmlsZXMgZm9yIGVhY2ggc3BlY2llcywgc28gd2FudCBpbmRpdmlkdWFsIGZvbGRlcnMpDQogICAgICBkaXIuY3JlYXRlKGZpbGUucGF0aChwYXN0ZTAoJ3NwZWNpZXNfc2hwLycsaW5kX2dyb3VwLCcvJyxzcGVjaWVzX25tKSksIHNob3dXYXJuaW5ncyA9IEZBTFNFKQ0KICAgICAgDQogICAgICAjIENyZWF0ZSBzaGFwZWZpbGUgZnJvbSBjc3YgLSB1bmZvcnR1bmF0ZWx5LCB0aGUgc2hhcGVmaWxlKCkgZnVuY3Rpb24gYWJicmV2aWF0ZXMgdGhlIGZpZWxkIG5hbWVzICg3IGNoYXJhY3RlcnMgbWF4PyE/ISE/IG9oIHdlbGwpDQogICAgICBzcF94eSA8LSBjYmluZChuZXdfY3N2JGxvbmdpdHVkZSwgbmV3X2NzdiRsYXRpdHVkZSkNCiAgICAgIHNwX3B0c19hbGwgPC0gU3BhdGlhbFBvaW50c0RhdGFGcmFtZShzcF94eSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdfY3N2LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2o0c3RyaW5nID0gd2dzODRjcnMpDQogICAgICBzaGFwZWZpbGUoc3BfcHRzX2FsbCwgcGFzdGUwKCdzcGVjaWVzX3NocC8nLGluZF9ncm91cCwnLycsc3BlY2llc19ubSwnLycsc3BlY2llcywgY29sbGFwc2U9IiIpLCBvdmVyd3JpdGUgPSBUUlVFKQ0KICAgICAgcm0oc3BfeHkpDQogICAgICANCiAgICAgICMgQ2xpcCBzcGF0aWFsIG9iamVjdCB0byBMQSBDaXR5IGJvdW5kYXJ5IGFuZCBwcmludCBudW1iZXIgb2YgcmVjb3JkcyB0aGF0IGZhbGwgd2l0aGluIExBIGNpdHksIHRoZSBwYXRjaGVzLCBhbmQgdGhlIHVyYmFuIGFyZWEgdG8gb3JpZ2luYWwgY3N2Lg0KICAgICAgIyBOT1RFIE9OIChnSW50ZXJzZWN0aW9uKTogYWZ0ZXIgbXVjaCBtb2FuaW4nIGFuZCBncm9hbmluJywgSSBmb3VuZCB0aGF0IHlvdSBjYW4gU1VCU0VUIFNQQVRJQUxQT0lOVFNEQVRBRlJBTUVTLiBOTyBORUVEIEZPUiBHSU5URVJTRUNUUywgRk9SIE9WRVJMQVlTLCBGT1IgTk9USElOLg0KICAgICAgc3BfcHRzX0xBIDwtIHNwX3B0c19hbGxbTEFjaXR5X2JvdW5kYXJ5LCBdDQogICAgICBpbmRfc3BwJGluX2NpdHkgPC0gaWZlbHNlKGluZF9zcHAkY3N2X2ZuID09IHNwZWNpZXNfbm0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlbmd0aChzcF9wdHNfTEEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmRfc3BwJGluX2NpdHkpDQogICAgICBzcF9wdHNfcGF0Y2ggPC0gc3BfcHRzX2FsbFtjdGdfcGF0Y2gsIF0NCiAgICAgIGluZF9zcHAkaW5fcGF0Y2hlcyA8LSBpZmVsc2UoaW5kX3NwcCRjc3ZfZm4gPT0gc3BlY2llc19ubSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoKHNwX3B0c19wYXRjaCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZF9zcHAkaW5fcGF0Y2hlcykNCiAgICAgIHNwX3B0c191cmJuIDwtIHNwX3B0c19hbGxbdXJibl9ncmlkLCBdDQogICAgICBpbmRfc3BwJGluX3VyYmFuIDwtIGlmZWxzZShpbmRfc3BwJGNzdl9mbiA9PSBzcGVjaWVzX25tLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGgoc3BfcHRzX3VyYm4pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmRfc3BwJGluX3VyYmFuKQ0KICAgIH0gZWxzZSB7bmV4dH0NCiAgICANCiMgU3RlcHMgNi04ID0gU3Vic2V0IHNwZWNpZXMgcmVjb3JkcyB1c2luZyBwYXRjaCBhbmQgdXJiYW4gZ3JpZCBwb2x5Z29ucywgd3JpdGUgY291bnRzIHRvIHRhYmxlDQogICAgDQogICAgaWYobGVuZ3RoKHNwX3B0c19MQSkgPiAwKSB7DQogICAgICANCiAgICAgIGlmKChsZW5ndGgoc3BfcHRzX3BhdGNoKSA+IDApICYgKGxlbmd0aChzcF9wdHNfdXJibikgPiAwKSkgew0KICAgICAgICBjdGdfc3ViIDwtIGN0Z19wYXRjaFtzcF9wdHNfTEEsIF0NCiAgICAgICAgc2hhcGVmaWxlKGN0Z19zdWIsIHBhc3RlMCgnc3BlY2llc19zaHAvJyxpbmRfZ3JvdXAsJy8nLHNwZWNpZXNfbm0sJy8nLHNwZWNpZXNfbm0sJ19jdGcnLCBjb2xsYXBzZT0iIiksIG92ZXJ3cml0ZSA9IFRSVUUpDQogICAgICAgIGluZF9zcHAkY3RncGF0Y2hfY291bnQgPC0gaWZlbHNlKGluZF9zcHAkY3N2X2ZuID09IHNwZWNpZXNfbm0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlbmd0aChjdGdfc3ViKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5kX3NwcCRjdGdwYXRjaF9jb3VudCkNCiAgICAgICAgY3Zfc3ViIDwtIGN2X3BhdGNoW3NwX3B0c19MQSwgXQ0KICAgICAgICBzaGFwZWZpbGUoY3Zfc3ViLCBwYXN0ZTAoJ3NwZWNpZXNfc2hwLycsaW5kX2dyb3VwLCcvJyxzcGVjaWVzX25tLCcvJyxzcGVjaWVzX25tLCdfY3YnLCBjb2xsYXBzZT0iIiksIG92ZXJ3cml0ZSA9IFRSVUUpDQogICAgICAgIGluZF9zcHAkY3ZwYXRjaF9jb3VudCA8LSBpZmVsc2UoaW5kX3NwcCRjc3ZfZm4gPT0gc3BlY2llc19ubSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGgoY3Zfc3ViKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmRfc3BwJGN2cGF0Y2hfY291bnQpDQogICAgICAgIA0KICAgICAgICB1cmJuX3N1YiA8LSB1cmJuX2dyaWRbc3BfcHRzX0xBLCBdDQogICAgICAgIHNoYXBlZmlsZSh1cmJuX3N1YiwgcGFzdGUwKCdzcGVjaWVzX3NocC8nLGluZF9ncm91cCwnLycsc3BlY2llc19ubSwnLycsc3BlY2llc19ubSwnX3VyYm4nLCBjb2xsYXBzZT0iIiksIG92ZXJ3cml0ZSA9IFRSVUUpDQogICAgICAgIGluZF9zcHAkdXJibl9jb3VudCA8LSBpZmVsc2UoaW5kX3NwcCRjc3ZfZm4gPT0gc3BlY2llc19ubSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGgodXJibl9zdWIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZF9zcHAkdXJibl9jb3VudCkNCiAgICAgICAgDQogICAgICB9IGVsc2UgaWYgKChsZW5ndGgoc3BfcHRzX3BhdGNoKSA+IDApICYgKGxlbmd0aChzcF9wdHNfdXJibikgPT0gMCkpIHsNCiAgICAgICAgY3RnX3N1YiA8LSBjdGdfcGF0Y2hbc3BfcHRzX0xBLCBdDQogICAgICAgIHNoYXBlZmlsZShjdGdfc3ViLCBwYXN0ZTAoJ3NwZWNpZXNfc2hwLycsaW5kX2dyb3VwLCcvJyxzcGVjaWVzX25tLCcvJyxzcGVjaWVzX25tLCdfY3RnJywgY29sbGFwc2U9IiIpLCBvdmVyd3JpdGUgPSBUUlVFKQ0KICAgICAgICBpbmRfc3BwJGN0Z3BhdGNoX2NvdW50IDwtIGlmZWxzZShpbmRfc3BwJGNzdl9mbiA9PSBzcGVjaWVzX25tLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGgoY3RnX3N1YiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZF9zcHAkY3RncGF0Y2hfY291bnQpDQogICAgICAgIGN2X3N1YiA8LSBjdl9wYXRjaFtzcF9wdHNfTEEsIF0NCiAgICAgICAgc2hhcGVmaWxlKGN2X3N1YiwgcGFzdGUwKCdzcGVjaWVzX3NocC8nLGluZF9ncm91cCwnLycsc3BlY2llc19ubSwnLycsc3BlY2llc19ubSwnX2N2JywgY29sbGFwc2U9IiIpLCBvdmVyd3JpdGUgPSBUUlVFKQ0KICAgICAgICBpbmRfc3BwJGN2cGF0Y2hfY291bnQgPC0gaWZlbHNlKGluZF9zcHAkY3N2X2ZuID09IHNwZWNpZXNfbm0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoKGN2X3N1YiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5kX3NwcCRjdnBhdGNoX2NvdW50KQ0KICAgICAgfSBlbHNlIHsNCiAgICAgICAgdXJibl9zdWIgPC0gdXJibl9ncmlkW3NwX3B0c19MQSwgXQ0KICAgICAgICBzaGFwZWZpbGUodXJibl9zdWIsIHBhc3RlMCgnc3BlY2llc19zaHAvJyxpbmRfZ3JvdXAsJy8nLHNwZWNpZXNfbm0sJy8nLHNwZWNpZXNfbm0sJ191cmJuJywgY29sbGFwc2U9IiIpLCBvdmVyd3JpdGUgPSBUUlVFKQ0KICAgICAgICBpbmRfc3BwJHVyYm5fY291bnQgPC0gaWZlbHNlKGluZF9zcHAkY3N2X2ZuID09IHNwZWNpZXNfbm0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoKHVyYm5fc3ViKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmRfc3BwJHVyYm5fY291bnQpDQogICAgICB9DQogICAgICANCiMgU3RlcCA5ID0gTWFwIDEpIHNwZWNpZXMgcG9pbnQgcmVjb3JkcyBhbmQgMikgcHJlc2VuY2UgYWNyb3NzIHBhdGNoZXMvZ3JpZHMNCiAgICAgIA0KICAgICAgIyBDb21wb3NlIG1hcCB0aXRsZQ0KICAgICAgY29tbW9uIDwtIHJlZnJvdyRjb21tb25fbmFtZQ0KICAgICAgc2NpZW50aWZpYyA8LSByZWZyb3ckc2NpZW50aWZpY19uYW1lDQogICAgICBtYXBfdGl0bGUgPC0gcGFzdGUwKGNvbW1vbiwgIlxuKCIsc2NpZW50aWZpYywgIikiKSAjQWZ0ZXIgbXVjaCBxdWFsaXR5IEdvb2dsZSB0aW1lLCBpdCBzZWVtcyB0aGF0IGl0YWxpY2l6aW5nIHBhcnQgb2YgYSB0ZXh0IGVsZW1lbnQgaW4gdG1hcCBjYW4ndCBiZSBkb25lLiBOb3JtYWxseSwgb25lIHdvdWxkIHdyYXAgdGhlIHRvLWJlLWl0YWxpY2l6ZWQgdGV4dCBzbzogZXhwcmVzc2lvbihpdGFsaWMoc2NpX25hbWUpKSksIGJ1dCB0bWFwIGRvZXMgbm90IGFjY2VwdCBleHByZXNzaW9ucy4NCiAgICAgIA0KICAgICAgIyBDcmVhdGUgcG9pbnQgcmVjb3JkcyBtYXANCiAgICAgIHBvaW50X21hcCA8LSB0bV9zaGFwZShMQWNpdHlfYm91bmRhcnksIHByb2plY3Rpb24gPSAzMjMxMSkgKw0KICAgICAgICB0bV9maWxsKGNvbCA9ICIjZjBmMGU0IikgKw0KICAgICAgICB0bV9zaGFwZShjdGdfcGF0Y2gpICsNCiAgICAgICAgdG1fZmlsbChjb2wgPSAiI0Q4RDhDRCIpICsNCiAgICAgICAgdG1fc2hhcGUoc3BfcHRzX0xBKSArDQogICAgICAgIHRtX3N5bWJvbHMoY29sID0gIiMwMERGQjMiLCBib3JkZXIuY29sID0gIiNmZmZmZmYiLCBib3JkZXIubHdkID0gMC4zLCBzY2FsZSA9IDAuMywgc2hhcGUgPSAyMSkgKw0KICAgICAgICB0bV9sYXlvdXQobWFpbi50aXRsZSA9IG1hcF90aXRsZSwNCiAgICAgICAgICAgICAgICAgIHRpdGxlID0gcGFzdGUoImlOYXR1cmFsaXN0IFJlY29yZHMgaW5cbkNpdHkgb2YgTEEgPSIsIGxlbmd0aChzcF9wdHNfTEEpKSwNCiAgICAgICAgICAgICAgICAgIHRpdGxlLnNpemUgPSAwLjksDQogICAgICAgICAgICAgICAgICB0aXRsZS5wb3NpdGlvbiA9IGMoImxlZnQiLCAiYm90dG9tIiksDQogICAgICAgICAgICAgICAgICBmcmFtZSA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgaW5uZXIubWFyZ2lucyA9IDAuMDYpDQogICAgICANCiAgICAgICMgRm9yIHByZXNlbmNlIG1hcCwgY2hvb3NlIGJldHdlZW4gc3BlY2llcyBjb250aWd1b3VzIGFuZCBDYWx2ZWcgcGF0Y2ggZmlsZXMsIGRlcGVuZGluZyBvbiB0YXhhDQogICAgICBpZihyZWZyb3ckdGF4YSA9PSAiTWFtbWFsIikgew0KICAgICAgICBwb2x5X21hcCA8LSB0bV9zaGFwZShMQWNpdHlfYm91bmRhcnksIHByb2plY3Rpb24gPSAzMjMxMSkgKw0KICAgICAgICAgIHRtX2ZpbGwoY29sID0gIiNmMGYwZTQiKSArDQogICAgICAgICAgdG1fc2hhcGUoY3RnX3BhdGNoKSArDQogICAgICAgICAgdG1fZmlsbChjb2wgPSAiI0Q4RDhDRCIpICsNCiAgICAgICAgICB0bV9zaGFwZShjdGdfc3ViKSArDQogICAgICAgICAgdG1fZmlsbChjb2wgPSAiIzAwREZCMyIpICsNCiAgICAgICAgICB0bV9zaGFwZSh1cmJuX3N1YikgKw0KICAgICAgICAgIHRtX2ZpbGwoY29sID0gIiMwMERGQjMiKSArDQogICAgICAgICAgdG1fbGF5b3V0KG1haW4udGl0bGUgPSAiICIsDQogICAgICAgICAgICAgICAgICAgIHRpdGxlID0gIlByZXNlbmNlIGJ5IENvbnRpZ3VvdXMgUGF0Y2gvXG5VcmJhbiBHcmlkICgwLjI1IG1pKSIsDQogICAgICAgICAgICAgICAgICAgIHRpdGxlLnNpemUgPSAwLjksDQogICAgICAgICAgICAgICAgICAgIHRpdGxlLnBvc2l0aW9uID0gYygibGVmdCIsICJib3R0b20iKSwNCiAgICAgICAgICAgICAgICAgICAgZnJhbWUgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgaW5uZXIubWFyZ2lucyA9IDAuMDYpDQogICAgICB9IGVsc2Ugew0KICAgICAgICBwb2x5X21hcCA8LSB0bV9zaGFwZShMQWNpdHlfYm91bmRhcnksIHByb2plY3Rpb24gPSAzMjMxMSkgKw0KICAgICAgICAgIHRtX2ZpbGwoY29sID0gIiNmMGYwZTQiKSArDQogICAgICAgICAgdG1fc2hhcGUoY3RnX3BhdGNoKSArDQogICAgICAgICAgdG1fZmlsbChjb2wgPSAiI0Q4RDhDRCIpICsNCiAgICAgICAgICB0bV9zaGFwZShjdl9zdWIpICsNCiAgICAgICAgICB0bV9maWxsKGNvbCA9ICIjMDBERkIzIikgKw0KICAgICAgICAgIHRtX3NoYXBlKHVyYm5fc3ViKSArDQogICAgICAgICAgdG1fZmlsbChjb2wgPSAiIzAwREZCMyIpICsNCiAgICAgICAgICB0bV9sYXlvdXQobWFpbi50aXRsZSA9ICIgXG4gIiwNCiAgICAgICAgICAgICAgICAgICAgdGl0bGUgPSAiUHJlc2VuY2UgYnkgQ2FsVmVnIFBhdGNoL1xuVXJiYW4gR3JpZCAoMC4yNSBtaSkiLA0KICAgICAgICAgICAgICAgICAgICB0aXRsZS5zaXplID0gMC45LA0KICAgICAgICAgICAgICAgICAgICB0aXRsZS5wb3NpdGlvbiA9IGMoImxlZnQiLCAiYm90dG9tIiksDQogICAgICAgICAgICAgICAgICAgIGZyYW1lID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgIGlubmVyLm1hcmdpbnMgPSAwLjA2KQ0KICAgICAgfQ0KICAgICAgDQogICAgICAjIEV4cG9ydCBhIFBERiB3aXRoIHRoZSBwb2ludCBhbmQgcG9seWdvbiBtYXBzDQogICAgICBwZGYocGFzdGUwKCdwZGZfcHJldmlld3MvJyxpbmRfZ3JvdXAsJy8nLHNwZWNpZXNfbm0sJy5wZGYnKSwgcGFwZXIgPSAiVVNyIiwgd2lkdGggPSAxMC4zLCBoZWlnaHQgPSA3LjgpDQogICAgICBwcmludCh0bWFwX2FycmFuZ2UocG9pbnRfbWFwLCBwb2x5X21hcCkpDQogICAgICBkZXYub2ZmKCkNCg0KICAgIH0gZWxzZSB7bmV4dH0NCiAgICANCiAgICAjIFdyaXRlIHVwZGF0ZWQgaW5kX3NwcCB0byBhIG5ldyBjc3YNCiAgICB3cml0ZV9jc3YoaW5kX3NwcCwgcGF0aCA9ICdpbmRpY2F0b3Jfc3BlY2llc19mbl91cGRhdGVkLmNzdicpDQogICAgDQogIH0NCn0NCmBgYA0KDQo8aHI+DQo8Yj5QYXJ0IE9uZSBSZXN1bHRzPC9iPg0KDQpTaW5jZSBJJ20gbm90IHJ1bm5pbmcgdGhhdCBtYWluIGNodW5rIG9mIFBhcnQgT25lLCBJJ2xsIGRlbW9uc3RyYXRlIHdoYXQgdGhlIFBERiBvdXRwdXQgc2hvdWxkIGxvb2sgbGlrZSB3aXRoIG9uZSBvZiBteSBmYXZvcml0ZSBzcGVjaWVzLCB0aGUgcmVkLXRhaWxlZCBoYXdrLiBTb21lIG9mIHRoZSBvYmplY3RzIGhhdmUgYmVlbiByZS1zcGVjaWZpZWQsIGFzIGFsbCBvZiB0aGUgc2hhcGVmaWxlcyBhcmUgYWxyZWFkeSByZWFkeSB0byBnby4gSGVyZSdzIGEgc2hvdCBvZiB0aGVtIGhhbmdpbmcgb3V0IGluIHRoZWlyIGZvbGRlcjoNCg0KYGBge3J9DQpsaWJyYXJ5KGtuaXRyKQ0KaW5jbHVkZV9ncmFwaGljcygnaGF3a19wcmV2aWV3LnBuZycpDQpgYGANCg0KDQpgYGB7cn0NCnJlZHRhaWxfcHRzIDwtIHJlYWRPR1IoJ3NwZWNpZXNfc2hwL2NvbW1vbl9uYXRpdmVfZmF1bmEvQnV0ZW9famFtYWljZW5zaXMnLCAnQnV0ZW9famFtYWljZW5zaXMnKQ0KY3RnX3N1YiA8LSByZWFkT0dSKCdzcGVjaWVzX3NocC9jb21tb25fbmF0aXZlX2ZhdW5hL0J1dGVvX2phbWFpY2Vuc2lzJywgJ0J1dGVvX2phbWFpY2Vuc2lzX2N0ZycpDQpjdl9zdWIgPC0gcmVhZE9HUignc3BlY2llc19zaHAvY29tbW9uX25hdGl2ZV9mYXVuYS9CdXRlb19qYW1haWNlbnNpcycsICdCdXRlb19qYW1haWNlbnNpc19jdicpDQp1cmJuX3N1YiA8LSByZWFkT0dSKCdzcGVjaWVzX3NocC9jb21tb25fbmF0aXZlX2ZhdW5hL0J1dGVvX2phbWFpY2Vuc2lzJywgJ0J1dGVvX2phbWFpY2Vuc2lzX3VyYm4nKQ0Kc3BfcHRzX0xBIDwtIHJlZHRhaWxfcHRzW0xBY2l0eV9ib3VuZGFyeSwgXQ0KDQpyZWZyb3cgPC0gaW5kX3NwcCAlPiUNCiAgZmlsdGVyKGNzdl9mbiA9PSAiQnV0ZW9famFtYWljZW5zaXMiKQ0KDQpjb21tb24gPC0gcmVmcm93JGNvbW1vbl9uYW1lDQpzY2llbnRpZmljIDwtIHJlZnJvdyRzY2llbnRpZmljX25hbWUNCm1hcF90aXRsZSA8LSBwYXN0ZTAoY29tbW9uLCAiXG4oIixzY2llbnRpZmljLCAiKSIpDQogICAgICANCiMgQ3JlYXRlIHBvaW50IHJlY29yZHMgbWFwDQpwb2ludF9tYXAgPC0gdG1fc2hhcGUoTEFjaXR5X2JvdW5kYXJ5LCBwcm9qZWN0aW9uID0gMzIzMTEpICsNCiAgdG1fZmlsbChjb2wgPSAiI2YwZjBlNCIpICsNCiAgdG1fc2hhcGUoY3RnX3BhdGNoKSArDQogIHRtX2ZpbGwoY29sID0gIiNEOEQ4Q0QiKSArDQogIHRtX3NoYXBlKHNwX3B0c19MQSkgKw0KICB0bV9zeW1ib2xzKGNvbCA9ICIjMDBERkIzIiwgYm9yZGVyLmNvbCA9ICIjZmZmZmZmIiwgYm9yZGVyLmx3ZCA9IDAuMywgc2NhbGUgPSAwLjMsIHNoYXBlID0gMjEpICsNCiAgdG1fbGF5b3V0KG1haW4udGl0bGUgPSBtYXBfdGl0bGUsDQogICAgICAgICAgICB0aXRsZSA9IHBhc3RlKCJpTmF0dXJhbGlzdCBSZWNvcmRzIGluXG5DaXR5IG9mIExBID0iLCBsZW5ndGgoc3BfcHRzX0xBKSksDQogICAgICAgICAgICB0aXRsZS5zaXplID0gMC45LA0KICAgICAgICAgICAgdGl0bGUucG9zaXRpb24gPSBjKCJsZWZ0IiwgImJvdHRvbSIpLA0KICAgICAgICAgICAgZnJhbWUgPSBGQUxTRSwNCiAgICAgICAgICAgIGlubmVyLm1hcmdpbnMgPSAwLjA2KQ0KICAgICAgDQojIEZvciBwcmVzZW5jZSBtYXAsIGNob29zZSBiZXR3ZWVuIHNwZWNpZXMgY29udGlndW91cyBhbmQgQ2FsdmVnIHBhdGNoIGZpbGVzLCBkZXBlbmRpbmcgb24gdGF4b25vbWljIGdyb3VwDQppZihyZWZyb3ckdGF4YSA9PSAiTWFtbWFsIikgew0KICBwb2x5X21hcCA8LSB0bV9zaGFwZShMQWNpdHlfYm91bmRhcnksIHByb2plY3Rpb24gPSAzMjMxMSkgKw0KICAgIHRtX2ZpbGwoY29sID0gIiNmMGYwZTQiKSArDQogICAgdG1fc2hhcGUoY3RnX3BhdGNoKSArDQogICAgdG1fZmlsbChjb2wgPSAiI0Q4RDhDRCIpICsNCiAgICB0bV9zaGFwZShjdGdfc3ViKSArDQogICAgdG1fZmlsbChjb2wgPSAiIzAwREZCMyIpICsNCiAgICB0bV9zaGFwZSh1cmJuX3N1YikgKw0KICAgIHRtX2ZpbGwoY29sID0gIiMwMERGQjMiKSArDQogICAgdG1fbGF5b3V0KG1haW4udGl0bGUgPSAiICIsDQogICAgICAgICAgICAgIHRpdGxlID0gIlByZXNlbmNlIGJ5IENvbnRpZ3VvdXMgUGF0Y2gvXG5VcmJhbiBHcmlkICgwLjI1IG1pKSIsDQogICAgICAgICAgICAgIHRpdGxlLnNpemUgPSAwLjksDQogICAgICAgICAgICAgIHRpdGxlLnBvc2l0aW9uID0gYygibGVmdCIsICJib3R0b20iKSwNCiAgICAgICAgICAgICAgZnJhbWUgPSBGQUxTRSwNCiAgICAgICAgICAgICAgaW5uZXIubWFyZ2lucyA9IDAuMDYpDQogIH0gZWxzZSB7DQogICAgcG9seV9tYXAgPC0gdG1fc2hhcGUoTEFjaXR5X2JvdW5kYXJ5LCBwcm9qZWN0aW9uID0gMzIzMTEpICsNCiAgICAgIHRtX2ZpbGwoY29sID0gIiNmMGYwZTQiKSArDQogICAgICB0bV9zaGFwZShjdGdfcGF0Y2gpICsNCiAgICAgIHRtX2ZpbGwoY29sID0gIiNEOEQ4Q0QiKSArDQogICAgICB0bV9zaGFwZShjdl9zdWIpICsNCiAgICAgIHRtX2ZpbGwoY29sID0gIiMwMERGQjMiKSArDQogICAgICB0bV9zaGFwZSh1cmJuX3N1YikgKw0KICAgICAgdG1fZmlsbChjb2wgPSAiIzAwREZCMyIpICsNCiAgICAgIHRtX2xheW91dChtYWluLnRpdGxlID0gIiBcbiAiLA0KICAgICAgICAgICAgICAgIHRpdGxlID0gIlByZXNlbmNlIGJ5IENhbFZlZyBQYXRjaC9cblVyYmFuIEdyaWQgKDAuMjUgbWkpIiwNCiAgICAgICAgICAgICAgICB0aXRsZS5zaXplID0gMC45LA0KICAgICAgICAgICAgICAgIHRpdGxlLnBvc2l0aW9uID0gYygibGVmdCIsICJib3R0b20iKSwNCiAgICAgICAgICAgICAgICBmcmFtZSA9IEZBTFNFLA0KICAgICAgICAgICAgICAgIGlubmVyLm1hcmdpbnMgPSAwLjA2KQ0KICAgIH0NCiAgICAgIA0KdG1hcF9hcnJhbmdlKHBvaW50X21hcCwgcG9seV9tYXApDQoNCmBgYA0KDQpBbmQgYXMgcHJvb2YgdGhhdCB0aGUgd2hvbGUgcHJvY2VzcyB3b3JrZWQgLSBoZXJlIGFyZSB0aGUgY29tbW9uIG5hdGl2ZSBmYXVuYSBQREZzLCBhbGwgaGFwcGlseSBuZXN0bGVkIGF3YXkgaW4gdGhlIGFwcHJvcHJpYXRlIGRpcmVjdG9yeToNCg0KYGBge3J9DQppbmNsdWRlX2dyYXBoaWNzKCdwZGZfcHJldmlldy5wbmcnKQ0KYGBgDQoNCg0KTGV0J3MgYWxzbyB0YWtlIGEgbG9vayBhdCBzb21lIG9mIHRoZSBkYXRhIHRoYXQgd2UgYWRkZWQgdG8gdGhlIGluZF9zcHAgZGF0YWZyYW1lIHVzaW5nIGdncGxvdC4gQSBwcmV2aWV3IG9mIHRob3NlIG5ldyBjb2x1bW5zOg0KDQpgYGB7cn0NCmluZF9zcHBfMiA8LSByZWFkX2NzdignaW5kaWNhdG9yX3NwZWNpZXNfZm5fdXBkYXRlZC5jc3YnKQ0KaGVhZChpbmRfc3BwXzIpDQpgYGANCg0KV2hpY2ggaW5kaWNhdG9yIHNwZWNpZXMgZ3JvdXBzIGRvIHdlIGhhdmUgdGhlIG1vc3QgZGF0YSBmb3I/DQoNCmBgYHtyfQ0KYXZhaWxhYmxlIDwtIGluZF9zcHBfMiAlPiUNCiAgbXV0YXRlKHJlY29yZHMgPSBpZmVsc2UoaW5fY2l0eSA+IDAsICJZZXMiLCAiTm8iKSkNCg0KZ2dwbG90KGF2YWlsYWJsZSwgYWVzKHggPSBpbmRpY2F0b3JfZ3JvdXAsIGZpbGwgPSByZWNvcmRzKSkgKw0KICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIiwgd2lkdGggPSAwLjI3KSArDQogIGdlb21fdGV4dChzdGF0PSdjb3VudCcsIGFlcyhsYWJlbD0uLmNvdW50Li4pLCBzaXplID0gMy41LCBwb3NpdGlvbiA9IHBvc2l0aW9uX2ZpbGwodmp1c3QgPSAwLjUpKSArDQogIHNjYWxlX2ZpbGxfZGlzY3JldGUobmFtZSA9ICJSZWNvcmRzIGF2YWlsYWJsZT8iKSArDQogIGNvb3JkX2ZsaXAoKSArDQogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gYygiQ29tbW9uXG5uYXRpdmUgZmF1bmEiLCAiQ29tbW9uXG5uYXRpdmUgZmxvcmEiLCAiQ29tbW9uXG51cmJhbiBzcC4iLCAiSW52YXNpdmUiLCAiUmFyZSBuYXRpdmUiKSkgKw0KICBsYWJzKHRpdGxlID0gImlOYXR1cmFsaXN0IFJlY29yZHMgaW4gQ2l0eSBvZiBMQSIsIHN1YnRpdGxlID0gIkF2YWlsYWJpbGl0eSBieSBzcGVjaWVzOyBsYWJlbHMgaW5kaWNhdGUgbnVtYmVyIG9mIHNwZWNpZXMiLCB4ID0gIiIsIHkgPSAiJSBvZiBzcGVjaWVzIikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHZqdXN0ID0gMiwgZmFjZSA9ICJib2xkIiksDQogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoY29sb3IgPSAiZ3JheTYwIiksDQogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dCh2anVzdCA9IC0xLCBjb2xvciA9ICJncmF5NjAiKSkNCmBgYA0KDQpBdmFpbGFiaWxpdHkgaXMgbG93ZXIgZm9yIHJhcmUgbmF0aXZlcyBhbmQgaW52YXNpdmUgc3BlY2llcywgd2hpY2ggaXMgbm90IHVuZXhwZWN0ZWQgKHNvbWUgb2YgdGhlIGludmFzaXZlIHNwZWNpZXMgYXJlIHJlbGF0aXZlbHkgbmV3ZXIgdG8gdGhlIHJlZ2lvbiBvciBoYXJkZXIgdG8gb2JzZXJ2ZSkuIFdoaWNoIHRheGEgYXJlIG1vc3QgcmVwcmVzZW50ZWQgaW4gdGhlIHplcm8gcmVjb3JkcyBncm91cD8gV2UnbGwgbHVtcCBjb21tb24gbmF0aXZlIGZsb3JhIGFuZCBmYXVuYSB0b2dldGhlciBoZXJlIGZvciBhIGxpdHRsZSBtb3JlIHZpc3VhbCBjbGFyaXR5Lg0KDQpgYGB7cn0NCm5vX2xhX3JlY29yZHMgPC0gaW5kX3NwcF8yICU+JQ0KICBmaWx0ZXIoaW5fY2l0eSA9PSAwKSAlPiUNCiAgbXV0YXRlKGdyb3VwXzIgPSBpbmRpY2F0b3JfZ3JvdXApICU+JQ0KICBtdXRhdGUoZ3JvdXBfMiA9IHN1YigiX2ZhdW5hIiwgIl9mZiIsIGdyb3VwXzIpKSAlPiUNCiAgbXV0YXRlKGdyb3VwXzIgPSBzdWIoIl9mbG9yYSIsICJfZmYiLCBncm91cF8yKSkNCg0KZ2dwbG90KG5vX2xhX3JlY29yZHMsIGFlcyh4ID0gdGF4YSwgZmlsbCA9IGdyb3VwXzIpKSArDQogIGdlb21fYmFyKHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UyKHByZXNlcnZlID0gInNpbmdsZSIpKSArDQogIGNvb3JkX2ZsaXAoKSArDQogIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMTAsIGJ5ID0gMikpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiIzUzZDM5NyIsIiNjY2U0OTAiLCAiI2ZmNWY1ZiIsIiNmYmQzNDEiKSwNCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiQ29tbW9uIG5hdGl2ZSBmbG9yYS9mYXVuYSIsICJDb21tb24gdXJiYW4gc3AuIiwgIkludmFzaXZlIiwgIlJhcmUgbmF0aXZlIiksDQogICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiSW5kaWNhdG9yXG5zcGVjaWVzIGdyb3VwIikgKw0KICBsYWJzKHRpdGxlID0gIldoaWNoIGluZGljYXRvciBzcGVjaWVzIGFyZSBtaXNzaW5nIGlOYXR1cmFsaXN0IGRhdGEgaW4gdGhlIENpdHkgb2YgTEE/IiwNCiAgICAgICBzdWJ0aXRsZSA9ICJCeSB0YXhvbm9taWMgZ3JvdXAiLA0KICAgICAgIHggPSAiIiwgeSA9ICJOby4gb2Ygc3BlY2llcyIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dCh2anVzdCA9IDIsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KGNvbG9yID0gImdyYXk2MCIpLA0KICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQodmp1c3QgPSAtMSwgY29sb3IgPSAiZ3JheTYwIikpDQpgYGANCg0KSW52ZXJ0ZWJyYXRlcyBhcmUgdGhlIHRvdWdoZXN0IHNwZWNpZXMgdG8gZ2V0IHJlY29yZHMgZm9yICgxIG91dCBvZiAxIGFsZ2FlIHNwZWNpZXMgYWxzbyBoYXMgbm8gcmVjb3JkcykgLSBzdXJwcmlzZSEgUGVvcGxlIHdvdWxkIG11Y2ggcmF0aGVyIGNocm9uaWNsZSB0aGVpciBlbmNvdW50ZXJzIHdpdGggYm9iY2F0cyB0aGFuIHdpdGggY3JpY2tldHMuDQoNCk9mIHRoZSBzcGVjaWVzIHdlIGRvIGhhdmUgcmVjb3JkcyBmb3IsIGhvdyBtYW55IGFyZSBiZWluZyBvYnNlcnZlZCBpbiB0aGUgdXJiYW4gYXJlYXMgdnMuIGhhYml0YXQgcGF0Y2hlcz8NCg0KYGBge3J9DQpsYV9yZWNvcmRzIDwtIGF2YWlsYWJsZSAlPiUgDQogIGZpbHRlcihyZWNvcmRzID09ICJZZXMiKSAlPiUNCiAgZ3JvdXBfYnkodGF4YSkgJT4lDQogIHN1bW1hcmlzZShpbl9wYXRjaGVzID0gc3VtKGluX3BhdGNoZXMsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICBpbl91cmJhbiA9IHN1bShpbl91cmJhbiwgbmEucm0gPSBUUlVFKSkgJT4lDQogIGdhdGhlcihrZXkgPSAibG9jYXRpb24iLCB2YWx1ZSA9ICJyZWNvcmRzIiwgMjozKQ0KDQogIA0KZ2dwbG90KGxhX3JlY29yZHMsIGFlcyh4ID0gdGF4YSwgeSA9IHJlY29yZHMsIGZpbGwgPSBsb2NhdGlvbikpICsNCiAgZ2VvbV9jb2wocG9zaXRpb24gPSAiZmlsbCIsIHdpZHRoID0gMC4zKSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSByZWNvcmRzKSwgc2l6ZSA9IDMuNSwgcG9zaXRpb24gPSBwb3NpdGlvbl9maWxsKHZqdXN0ID0gMC41KSkgKw0KICBzY2FsZV9maWxsX21hbnVhbChuYW1lID0gIiIsDQogICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIkhhYml0YXQgcGF0Y2hlcyIsICJVcmJhbiBhcmVhcyIpLA0KICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBjKCIjN2RjMzgzIiwgIiNEOEQ4Q0QiKSkgKw0KICBjb29yZF9mbGlwKCkgKw0KICBsYWJzKHRpdGxlID0gIldoZXJlIGFyZSBzcGVjaWVzIHNwb3R0ZWQgaW4gdGhlIENpdHkgb2YgTEE/Iiwgc3VidGl0bGUgPSAiTGFiZWxzIGluZGljYXRlIHRvdGFsIGlOYXR1cmFsaXN0IHJlY29yZHMgcGVyIHRheG9ub21pYyBncm91cCIsIHggPSAiIiwgeSA9ICIlIG9mIHNwZWNpZXMiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQodmp1c3QgPSAyLCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJncmF5NjAiKSwNCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHZqdXN0ID0gLTEsIGNvbG9yID0gImdyYXk2MCIpKQ0KDQpgYGANCg0KSW50ZXJlc3RpbmchIE9ic2VydmVycyBzcG90IG1vcmUgbWFtbWFscywgaW52ZXJ0ZWJyYXRlcywgYW5kIGJpcmRzIGluIHVyYmFuIGFyZWFzIHRoYW4gdGhleSBkbyBpbiBoYWJpdGF0IHBhdGNoZXMuIFRoZXJlIGFyZSBhIGZldyByZWFzb25hYmxlIGV4cGxhbmF0aW9ucyBmb3IgdGhpcywgb25lIG9idmlvdXNseSBiZWluZyB0aGF0IDEyJSBvZiB0aGUgaW5kaWNhdG9yIHNwZWNpZXMgYXJlIGluIHRoZSAiY29tbW9uIG5hdGl2ZSBpbiB1cmJhbiBhcmVhIiBncm91cC4gQWxzbywgc29tZSBtYW1tYWxzLCBsaWtlIHRoZSBpbnZhc2l2ZSBmb3ggc3F1aXJyZWwsIHRydWx5IGFyZSBtb3JlIGNvbW1vbiBpbiB1cmJhbiBhcmVhcy4gQnV0IGZvciBvdGhlciBzcGVjaWVzLCB0aGlzIGRpZmZlcmVuY2UgYmV0d2VlbiB1cmJhbiBhbmQgcGF0Y2ggb2JzZXJ2YXRpb25zIGNvdWxkIGJlIGEgcmVmbGVjdGlvbiBvZiBzYW1wbGluZyBiaWFzLiBNZWFud2hpbGUsIGFtcGhpYmlhbnMgd2VyZSBtb3JlIGxpa2VseSB0byBiZSBvYnNlcnZlZCBpbiBoYWJpdGF0IHBhdGNoZXMgLSBwcm9iYWJseSBzb21ldGhpbmcgdG8gZG8gd2l0aCB0aGVpciB2ZXJ5IHNwZWNpZmljIGhhYml0YXQgbmVlZHMgKEkgYmVsaWV2ZSBhbGwgb2YgTEEncyByaXBhcmlhbiByZXNvdXJjZXMgYXJlIGFsbCBjbGFzc2lmaWVkIGFzIGhhYml0YXQgcGF0Y2hlcykuIFRoZSBsb3cgcHJvcG9ydGlvbiBvZiBwbGFudHMgcmVjb3JkZWQgaW4gdXJiYW4gYXJlYXMgY291bGQgYmUgZHVlIHRvIHRoZSBkb21pbmFuY2Ugb2Ygbm9uLW5hdGl2ZSBvcm5hbWVudGFscyBpbiBtYW5hZ2VkIGxhbmRzY2FwZXMsIG9yIG91ciBleGNsdXNpb24gb2YgcmVjb3JkcyBvZiAiY2FwdGl2ZSIgc3BlY2llcywgb3IgYm90aC4NCg0KDQo8aHI+DQo8Yj5XaGF0J3MgTmV4dDwvYj4NCg0KUGFydCB0d28gaXMgbmV4dCEgT25jZSB3ZSBoYXZlIHNvbWUgc2NvcmluZyBtZXRob2RzLCBJIGhhdmUgYSBwcmV0dHkgZ29vZCBpZGVhIG9mIGhvdyBJIG1pZ2h0IHB1dCB0b2dldGhlciB0aGUgc2NyaXB0LiBUaGUgYW5hbHlzaXMgaXRzZWxmIHByb2JhYmx5IHdvbid0IGJlIHNvIGNvbXBsZXgsIGJ1dCBsaWtlIHRoZSBwcm9jZWR1cmVzIGluIFBhcnQgT25lLCBpdCB3aWxsIGxpa2VseSBiZSB0ZXN0ZWQgYW5kIGNoYW5nZWQgbXVsdGlwbGUgdGltZXMgYXMgdGhlIG1ldGhvZHMgYXJlIGRldmVsb3BlZC4gV3JpdGluZyB0aGUgc2NyaXB0IHNvIGZhciBoYXMgYmVlbiB2ZXJ5IGVkdWNhdGlvbmFsLCBhbmQgSSB0aGluayBpdCB3aWxsIGhlbHAgdGhlIHJlc3Qgb2YgdGhlIHByb2Nlc3MgZ28gbXVjaCBzbW9vdGhlciEgQ29udmVyc2VseSwgdGhlIG1vcmUgSSB3b3JrIHdpdGggdGhlIGxvb3BzLCB0aGUgbW9yZSBJIHVuZGVyc3RhbmQgYWxsIHRoZSBsaXR0bGUgcGxhY2VzIHdoZXJlIHRoZSBjb2RlIGNhbiBiZSBwcnVuZWQgYW5kIHN0cmVhbWxpbmVkLiBTbyBJIGhvcGUgdGhhdCB3b3JraW5nIHRocm91Z2ggdGhlIG5leHQgcGFydCBvZiB0aGlzIHByb2plY3Qgd2lsbCBlbmFibGUgbWUgdG8gZ28gYmFjayBhbmQgaW1wcm92ZSB0aGUgZmlyc3QgaGFsZi4NCg0KVGhlcmUgYXJlIHRoaW5ncyB0byBpbXByb3ZlIG9uIHRoZSBtYXBzIHRoZW1zZWx2ZXMgYXMgd2VsbC4gQ2VydGFpbiBpc3N1ZXMgdGhhdCBmZWVsIHJlbGF0aXZlbHkgc21hbGwsIGJ1dCBzdGlsbCBlc2NhcGUgbWUuIFRoZXNlIGluY2x1ZGUgaXRhbGljaXppbmcganVzdCBwYXJ0IG9mIGEgdGl0bGUgaW4gYSB0bWFwIG9iamVjdCwgYW5kIGN1c3RvbWl6aW5nIGEgcGRmIGxheW91dCB0aGF0IGluY2x1ZGVzIHRtYXBzIChlLmcuIGFkZGluZyBhIHRpdGxlIGFuZCBzb21lIHRleHQpLiBJIHdvdWxkIHJlYWxseSBsaWtlIHRvIG1ha2UgdGhlIHNjb3JlZCBtYXBzIGludGVyYWN0aXZlIC0gdGhvdWdoIEknbSBub3QgdG90YWxseSBzdXJlIGhvdyBJIHdvdWxkIHNoYXJlIHRoZW0gaWYgSSBjb250aW51ZSB0byB1c2UgdG1hcCAobWF5YmUganVzdCB0aGUgSFRNTCBwYWdlPykuIFRoYXQgYnJpbmdzIG1lIHRvIHRoZSBuZXh0IGl0ZW0gb24gbXkgd2lzaCBsaXN0OiBleHBhbmRpbmcgbXkgbWFwcGluZy1pbi1SIHNraWxsc2V0IGJleW9uZCB0bWFwLiBUaHVzIGZhciwgaXQncyBiZWVuIG15IGZhdm9yaXRlIGJlY2F1c2UgaXQgc28gZWFzaWx5IGhhbmRsZXMgc2hhcGVmaWxlcy4gZ2dwbG90IHNlZW1zIG5vdCBzbyBzdHJhaWdodGZvcndhcmQgd2hlbiBpdCBjb21lcyB0byBwb2x5Z29ucywgYW5kIGdnYW5pbWF0ZSBpcyBpbnRpbWlkYXRpbmcuIEJ1dCB0bWFwIGlzIG5vdCB3aXRob3V0IGl0cyBsaW1pdGF0aW9ucywgc28gSSdtIGV4Y2l0ZWQgdG8gdHJ5IGFuZCB3cmFuZ2xlIHRoZSBvdGhlciBtZXRob2RzISANCg0KDQo8aHI+DQo8aT5BbGwgc3BlY2llcyBkYXRhIGZyb20gaU5hdHVyYWxpc3QuPC9pPiBodHRwOi8vd3d3LmluYXR1cmFsaXN0Lm9yZw0K