Example: Download Daily Data for Congressional Disticts
In this example we’ll download daily temperature data for 16 Congressional Districts in the LA region.
Setup
Load caladaptR and the other package we’re going to need. (If you haven’t installed these yet, see this setup script).
library(caladaptr)
Registered S3 methods overwritten by 'dbplyr':
method from
print.tbl_lazy
print.tbl_sql
Registered S3 methods overwritten by 'htmltools':
method from
print.html tools:rstudio
print.shiny.tag tools:rstudio
print.shiny.tag.list tools:rstudio
Registered S3 method overwritten by 'htmlwidgets':
method from
print.htmlwidget tools:rstudio
caladaptr (version 0.6.1)
URL: https://ucanr-igis.github.io/caladaptr
Bug reports: https://github.com/ucanr-igis/caladaptr/issues
library(units)
udunits database from C:/Users/Andy/Documents/R/win-library/4.1/units/share/udunits/udunits2.xml
library(ggplot2)
library(dplyr)
Attaching package: ‘dplyr’
The following objects are masked from ‘package:stats’:
filter, lag
The following objects are masked from ‘package:base’:
intersect, setdiff, setequal, union
library(lubridate)
Attaching package: ‘lubridate’
The following objects are masked from ‘package:base’:
date, intersect, setdiff, union
library(sf)
Linking to GEOS 3.9.0, GDAL 3.2.1, PROJ 7.2.1
library(tidyr)
library(tmap)
library(conflicted)
conflict_prefer("filter", "dplyr", quiet = TRUE)
conflict_prefer("count", "dplyr", quiet = TRUE)
conflict_prefer("select", "dplyr", quiet = TRUE)
Import the Congressional Districts Boundaries
The LA Congressional district boundaries are saved in the ‘data’ folder as a geopackage:
cdist_la_fn <- "./data/cdistricts_la.gpkg"
file.exists(cdist_la_fn)
[1] TRUE
cdist_la_sf <- st_read(cdist_la_fn)
Reading layer `cdistricts_la' from data source
`D:\GitHub\cal-adapt\caladaptr-res\docs\workshops\ca_intro_oct21\notebooks\data\cdistricts_la.gpkg'
using driver `GPKG'
Simple feature collection with 15 features and 7 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: -118.9449 ymin: 33.70403 xmax: -117.3529 ymax: 34.8233
Geodetic CRS: WGS 84
tmap_mode("view")
tmap mode set to interactive viewing
tm_shape(cdist_la_sf) + tm_borders()
Pro Tip:
- to use a sf object the location for an API Request, it must be in geographic coordinates (EPSG 4326)
Create the API Request
Here we use ca_loc_sf()
as the location function for an API request for 30-years of modeled daily temperature data (minimum and maximum) for 4 GCMs and 2 RCPs:
cdist_la_cap <- ca_loc_sf(loc = cdist_la_sf, idfld = "geoid") %>%
ca_gcm(gcms[1:4]) %>%
ca_scenario(c("rcp45", "rcp85")) %>%
ca_period("day") %>%
ca_years(start = 2070, end = 2099) %>%
ca_cvar(c("tasmin", "tasmax")) %>%
ca_options(spatial_ag = "mean")
cdist_la_cap
Cal-Adapt API Request
Location(s):
Simple Feature MULTIPOLYGON (15 feature(s))
ID field: geoid
Variable(s): tasmin, tasmax
Temporal aggregration period(s): day
GCM(s): HadGEM2-ES, CNRM-CM5, CanESM2, MIROC5
Scenario(s): rcp45, rcp85
Dates: 2070-01-01 to 2099-12-31
Options:
spatial ag: mean
Do the standard plotting and preflight checks:
plot(cdist_la_cap, locagrid = TRUE)
cdist_la_cap %>% ca_preflight()
General issues
- none found
Issues for querying values
- none found
Issues for downloading rasters
- none found
Fetch Data
To copy downloaded data into a database, use ca_getvals_db()
instead of ca_getvals_tbl()
. ca_getvals_db()
has two arguments which are mandatory:
db_fn
- the file name of a SQLite database (will be created if it doesn’t exist)
db_tbl
- the name of a table inside the database where the values should be saved
my_database_fn <- "./data/cdist_la_temp_data.sqlite"
cdist_la_rtbl <- cdist_la_cap %>%
ca_getvals_db(db_fn = my_database_fn,
db_tbl = "temp_data",
quiet = TRUE)
Inspect the results:
cdist_la_rtbl %>% head()
The number of rows we retrieved:
cdist_la_rtbl %>% count() %>% pull(n)
[1] 2629680
Wrangling a Remote Tibble
Many of the base R operations that work with in-memory tibbles may or many not work with remote tibbles. For example as we saw above cdist_la_rtbl %>% count()
works, but:
dim(cdist_la_rtbl)
[1] NA 8
nrow(cdist_la_rtbl)
[1] NA
In general, the best way to work with remote tibbles is with
- dplyr functions, or
- SQL statements passed using the
DBI
package
Simple filtering, sorting, grouping and simple numeric summaries generally work fine with dplyr verbs:
cdist_la_rtbl %>%
filter(geoid == "0632") %>%
group_by(scenario, gcm, cvar) %>%
summarize(mean_temp = mean(val, na.rm = TRUE))
`summarise()` has grouped output by 'scenario', 'gcm'. You can override using the `.groups` argument.
Pro Tip:
- You can ‘convert’ a Remote Tibble to a regular in-memory Tibble by tacking on
collect()
at the end of a dplyr expression.
If your wrangling workflow involves a lot steps that are difficult or impossible to do with remote tibbles, a good strategy is to do your filtering and grouping with dplyr statements on the remote tibble, and then convert the results to a regular tibble with collect()
.
Below we convert the grouped summary table into a tibble so we can use pivot_wider()
(which doesn’t work on remote tibbles):
temp_long_tbl <- cdist_la_rtbl %>%
filter(geoid == "0632") %>%
group_by(scenario, gcm, cvar) %>%
summarize(mean_temp = mean(val, na.rm = TRUE)) %>%
collect()
`summarise()` has grouped output by 'scenario', 'gcm'. You can override using the `.groups` argument.
class(temp_long_tbl)
[1] "grouped_df" "tbl_df" "tbl" "data.frame"
temp_wide_tbl <- temp_long_tbl %>%
pivot_wider(names_from = cvar, values_from = mean_temp) %>%
mutate(tas_range = tasmax - tasmin)
temp_wide_tbl %>% head()
Your Turn
Create a histogram of the mean daily temperatures for one Congressional District and one emissions scenario, grouping the data by GCM. Does the distribution of mean average temperature look the same across GCMs?
## Your answer here
Downloading Rasters
Another way to deal with large data needs is to download the data as raster or TIF files. The same API Request object can be used to get raster data if you feed it into ca_getrst_stars()
.
For additional info on downloading and analyzing rasters, see the 3 articles on Downloading Rasters.
Below we get a raster of observed historic temperature data for the Sierra climate region:
sierra_cap <- ca_loc_aoipreset(type = "climregions", idfld = "name", idval = "Sierra") %>%
ca_livneh(TRUE) %>%
ca_period("year") %>%
ca_cvar("pr") %>%
ca_years(start = 1970, end = 2010)
sierra_cap
Cal-Adapt API Request
Location(s):
AOI Preset: climregions
name(s): Sierra
Variable(s): pr
Temporal aggregration period(s): year
Livneh data: TRUE
Dates: 1970-01-01 to 2010-12-31
plot(sierra_cap, locagrid = TRUE)
sierra_cap %>% ca_preflight()
General issues
- none found
Issues for querying values
- A spatial aggregation function is required to query values from polygon areas. See `ca_options`.
Issues for downloading rasters
- none found
To fetch the data as TIFs, use :
tiff_dir <- "./data"
sierra_tiff_fn <- sierra_cap %>%
ca_getrst_stars(out_dir = tiff_dir, mask = TRUE, quiet = TRUE, overwrite = FALSE)
Pro Tip:
- to avoid downloading the same TIF multiple times, use the same output directory and set
overwrite = FALSE
ca_getrst_stars()
works differently than retrieving tabular climate values. It returns a vector of TIF files that were downloaded. To work with them, you next have to load them back into R as stars objects (space-time arrays) using ca_stars_read()
:
sierra_stars_lst <- ca_stars_read(sierra_tiff_fn)
sierra_stars_lst[[1]]
stars object with 3 dimensions and 1 attribute
attribute(s):
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
pr_year_livneh_name-Sierra 0.2009817 2.037323 2.821092 3.106411 3.863086 12.43626 123615
dimension(s):
from to offset delta refsys point values x/y
x 1 58 -121.688 0.0625 WGS 84 FALSE NULL [x]
y 1 71 40.125 -0.0625 WGS 84 FALSE NULL [y]
year 1 41 1970 1 NA NA NULL
To plot a stars objects, you have to decide which layer(s) to plot. In this case, each layer represents a year from 1970 to 2010. Below we plot 4 of the 40 years:
plot(sierra_stars_lst[[1]] %>% slice(index = seq(1,40,length.out =4), along = "year"),
axes = TRUE,
main = attributes(sierra_stars_lst[[1]])$ca_metadata$slug)
downsample set to c(0,0,1)
Not sure what the units are? You can double-check by viewing the metadata for the slug from the catalog:
ca_catalog_search("pr_year_livneh")
pr_year_livneh
name: Livneh yearly average precipitation historical
url: https://api.cal-adapt.org/api/series/pr_year_livneh/
tres: annual
begin: 1950-01-01T00:00:00Z
end: 2013-12-31T00:00:00Z
units: mm
num_rast: 64
id: 410
xmin: -124.5625
xmax: -113.375
ymin: 31.5625
ymax: 43.75
There is a lot more you can do with rasters, including pixel summaries, combining them into higher dimensional data cubes, spatially mosaicing them, etc. For more info, see the Rasters articles on the website.
Your Turn
Download historic precipitation data for the county where you live or work. [Answer]
## Example: Mendocino County
ca_aoipreset_geom("counties", quiet = TRUE) %>%
st_drop_geometry() %>%
filter(name == "Mendocino") %>%
select(name, state_name, fips)
name state_name fips
1 Mendocino California 06045
mendocino_cap <- ca_loc_aoipreset(type = "counties", idfld = "fips", idval = "06045") %>%
ca_livneh(TRUE) %>%
ca_period("year") %>%
ca_cvar("pr") %>%
ca_years(start = 1970, end = 2010)
plot(mendocino_cap)
mendocino_fn <- mendocino_cap %>%
ca_getrst_stars(out_dir = tempdir(), mask = TRUE, quiet = TRUE, overwrite = FALSE)
mendocino_stars_lst <- ca_stars_read(mendocino_fn)
mendocino_stars_lst[[1]]
stars object with 3 dimensions and 1 attribute
attribute(s):
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
pr_year_livneh_fips-06045 0.9326811 2.792288 3.627976 3.892968 4.744806 11.229 7134
dimension(s):
from to offset delta refsys point values x/y
x 1 20 -124.062 0.0625 WGS 84 FALSE NULL [x]
y 1 21 40.0625 -0.0625 WGS 84 FALSE NULL [y]
year 1 41 1970 1 NA NA NULL
plot(mendocino_stars_lst[[1]] %>% slice(index = seq(1,40,length.out =4), along = "year"),
axes = TRUE,
main = attributes(mendocino_stars_lst[[1]])$ca_metadata$slug)
downsample set to c(0,0,1)
LS0tDQp0aXRsZTogIkxhcmdlIFF1ZXJpZXMgYW5kIENhY2hpbmcgUmVzdWx0cyINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazogDQogICAgY3NzOiBodHRwczovL3VjYW5yLWlnaXMuZ2l0aHViLmlvL2NhbGFkYXB0ci1yZXMvYXNzZXRzL25iX2NzczAxLmNzcw0KICAgIGluY2x1ZGVzOg0KICAgICAgYmVmb3JlX2JvZHk6IGh0dHBzOi8vdWNhbnItaWdpcy5naXRodWIuaW8vY2FsYWRhcHRyLXJlcy9hc3NldHMvbmJfaGRyc29sbi5odG1sDQogICAgICBhZnRlcl9ib2R5OiBodHRwczovL3VjYW5yLWlnaXMuZ2l0aHViLmlvL2NhbGFkYXB0ci1yZXMvYXNzZXRzL25iX2Zvb3RlcjAxLmh0bWwNCi0tLQ0KDQojIE92ZXJ2aWV3DQoNClRoaXMgbm90ZWJvb2sgd2lsbCBkZW1vbnN0cmF0ZSB0d28gd2F5cyBvZiBkZWFsaW5nIHdpdGggbGFyZ2VyIHZvbHVtZXMgb2YgZGF0YToNCg0KLSBjYWNoaW5nIHJldHJpZXZlZCBpbnRvIGEgU1FMaXRlIGRhdGFiYXNlICANCi0gZG93bmxvYWRpbmcgZGF0YSBhcyBUSUZGcw0KDQpBIGZ1bmN0aW9uYWwgZGVmaW5pdGlvbiBvZiAibGFyZ2UgZGF0YSIgbWlnaHQgaW5jbHVkZToNCg0KLSBhbnkgdm9sdW1lIG9mIGRhdGEgdGhhdCB5b3Ugd291bGRuJ3Qgd2FudCB0byBkb3dubG9hZCB0d2ljZSAgDQotIGFueSB2b2x1bWUgb2YgZGF0YSB0aGF0IG1pZ2h0IGJvZyBkb3duIChvciB3b3JzZSwgY3Jhc2gpIHRoZSBDYWwtQWRhcHQgc2VydmVyLCBhbmQgYW5ub3kgdGhlIHN5c3RlbSBhZG1pbmlzdHJhdG9ycyAgDQoNCiMjIFNhdmluZyBDYWwtQWRhcHQgRGF0YSB0byBTUUxpdGUgRGF0YWJhc2UNCg0KVG8gbWFuYWdlIGxhcmdlIGRvd25sb2FkcyBhbmQgYXZvaWQgZG93bmxvYWRpbmcgZGF0YSB0d2ljZSwgY2FsYWRhcHRSIHByb3ZpZGVzIGBjYV9nZXR2YWxzX2RiKClgLiBgY2FfZ2V0dmFsc19kYigpYCBpcyB2ZXJ5IHNpbWlsYXIgdG8gYGNhX2dldHZhbHNfdGJsKClgLCBidXQgc2F2ZXMgdGhlIGRhdGEgaW50byBhIFNRTGl0ZSBkYXRhYmFzZSBmaWxlIG9uIHlvdXIgY29tcHV0ZXIgYXMgaXQgY29tZXMgaW4uIEJlZm9yZSBtYWtpbmcgYW4gQVBJIHJlcXVlc3QsIGl0IGNoZWNrcyB0byBzZWUgaWYgdGhlIGRhdGEgaGF2ZSBhbHJlYWR5IGJlZW4gZG93bmxvYWRlZCwgYW5kIGlmIHNvIHNraXBzIGl0LiBJZiB5b3UgZ2V0IGRpc2Nvbm5lY3RlZCBkdXJpbmcgYSBkb3dubG9hZCwgeW91IGNhbiBydW4gdGhlIGNvbW1hbmQgYWdhaW4gYW5kIGl0J2xsIGp1c3QgcGljayB1cCB3aGVyZSBpdCBsZWZ0IG9mZi4gYGNhX2dldHZhbHNfZGIoKWAgcmV0dXJucyBhICdyZW1vdGUgdGliYmxlJywgd2hpY2ggZnVuY3Rpb25zIHZlcnkgc2ltaWxhciB0byBhIHJlZ3VsYXIgJ2luLW1lbW9yeScgdGliYmxlIGhvd2V2ZXIgaXQgcG9pbnRzIHRvIHRoZSBTUUxpdGUgZGF0YWJhc2UgZmlsZSBvbiBkaXNrLg0KDQpGb3IgYWRkaXRpb25hbCBpbmZvIG9uIHdvcmtpbmcgd2l0aCBTUUxpdGUgZGF0YWJhc2VzLCBzZWUgdGhlIEFydGljbGUgb24gbWFraW5nIFtMYXJnZSBRdWVyaWVzXShodHRwczovL3VjYW5yLWlnaXMuZ2l0aHViLmlvL2NhbGFkYXB0ci9hcnRpY2xlcy9sYXJnZS1xdWVyaWVzLmh0bWwpLg0KDQojIEV4YW1wbGU6IERvd25sb2FkIERhaWx5IERhdGEgZm9yIENvbmdyZXNzaW9uYWwgRGlzdGljdHMNCg0KSW4gdGhpcyBleGFtcGxlIHdlJ2xsIGRvd25sb2FkIGRhaWx5IHRlbXBlcmF0dXJlIGRhdGEgZm9yIDE2IENvbmdyZXNzaW9uYWwgRGlzdHJpY3RzIGluIHRoZSBMQSByZWdpb24uIA0KDQpcDQoNCiMjIFNldHVwDQoNCkxvYWQgY2FsYWRhcHRSIGFuZCB0aGUgb3RoZXIgcGFja2FnZSB3ZSdyZSBnb2luZyB0byBuZWVkLiAoSWYgeW91IGhhdmVuJ3QgaW5zdGFsbGVkIHRoZXNlIHlldCwgc2VlIHRoaXMgW3NldHVwIHNjcmlwdF0oaHR0cHM6Ly9naXRodWIuY29tL3VjYW5yLWlnaXMvY2FsYWRhcHRyLXJlcy9ibG9iL21haW4vZG9jcy93b3Jrc2hvcHMvY2FfaW50cm9fb2N0MjEvc2NyaXB0cy9jYWxhZGFwdHJfc2V0dXAuUikpLiANCg0KYGBge3IgY2h1bmswMSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0naG9sZCd9DQpsaWJyYXJ5KGNhbGFkYXB0cikNCmxpYnJhcnkodW5pdHMpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShsdWJyaWRhdGUpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkodG1hcCkNCmxpYnJhcnkoY29uZmxpY3RlZCkNCmNvbmZsaWN0X3ByZWZlcigiZmlsdGVyIiwgImRwbHlyIiwgcXVpZXQgPSBUUlVFKQ0KY29uZmxpY3RfcHJlZmVyKCJjb3VudCIsICJkcGx5ciIsIHF1aWV0ID0gVFJVRSkNCmNvbmZsaWN0X3ByZWZlcigic2VsZWN0IiwgImRwbHlyIiwgcXVpZXQgPSBUUlVFKQ0KYGBgDQoNClwNCg0KIyMgSW1wb3J0IHRoZSBDb25ncmVzc2lvbmFsIERpc3RyaWN0cyBCb3VuZGFyaWVzDQoNClRoZSBMQSBDb25ncmVzc2lvbmFsIGRpc3RyaWN0IGJvdW5kYXJpZXMgYXJlIHNhdmVkIGluIHRoZSAnZGF0YScgZm9sZGVyIGFzIGEgZ2VvcGFja2FnZToNCg0KYGBge3IgY2h1bmswMn0NCmNkaXN0X2xhX2ZuIDwtICIuL2RhdGEvY2Rpc3RyaWN0c19sYS5ncGtnIg0KZmlsZS5leGlzdHMoY2Rpc3RfbGFfZm4pDQoNCmNkaXN0X2xhX3NmIDwtIHN0X3JlYWQoY2Rpc3RfbGFfZm4pDQoNCnRtYXBfbW9kZSgidmlldyIpDQp0bV9zaGFwZShjZGlzdF9sYV9zZikgKyB0bV9ib3JkZXJzKCkNCmBgYA0KDQoqKlBybyBUaXA6KioNCg0KLSB0byB1c2UgYSBzZiBvYmplY3QgdGhlIGxvY2F0aW9uIGZvciBhbiBBUEkgUmVxdWVzdCwgaXQgbXVzdCBiZSBpbiBnZW9ncmFwaGljIGNvb3JkaW5hdGVzIChFUFNHIDQzMjYpDQoNCg0KXA0KDQojIyBDcmVhdGUgdGhlIEFQSSBSZXF1ZXN0DQoNCkhlcmUgd2UgdXNlIGBjYV9sb2Nfc2YoKWAgYXMgdGhlIGxvY2F0aW9uIGZ1bmN0aW9uIGZvciBhbiBBUEkgcmVxdWVzdCBmb3IgMzAteWVhcnMgb2YgbW9kZWxlZCBkYWlseSB0ZW1wZXJhdHVyZSBkYXRhIChtaW5pbXVtIGFuZCBtYXhpbXVtKSBmb3IgNCBHQ01zIGFuZCAyIFJDUHM6DQoNCmBgYHtyIGNodW5rMDN9DQpjZGlzdF9sYV9jYXAgPC0gY2FfbG9jX3NmKGxvYyA9IGNkaXN0X2xhX3NmLCBpZGZsZCA9ICJnZW9pZCIpICU+JSANCiAgY2FfZ2NtKGdjbXNbMTo0XSkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogIGNhX3NjZW5hcmlvKGMoInJjcDQ1IiwgInJjcDg1IikpICU+JQ0KICBjYV9wZXJpb2QoImRheSIpICU+JQ0KICBjYV95ZWFycyhzdGFydCA9IDIwNzAsIGVuZCA9IDIwOTkpICU+JQ0KICBjYV9jdmFyKGMoInRhc21pbiIsICJ0YXNtYXgiKSkgJT4lIA0KICBjYV9vcHRpb25zKHNwYXRpYWxfYWcgPSAibWVhbiIpDQoNCmNkaXN0X2xhX2NhcA0KYGBgDQoNClwNCg0KRG8gdGhlIHN0YW5kYXJkIHBsb3R0aW5nIGFuZCBwcmVmbGlnaHQgY2hlY2tzOg0KDQpgYGB7ciBjaHVuazA0fQ0KcGxvdChjZGlzdF9sYV9jYXAsIGxvY2FncmlkID0gVFJVRSkNCmBgYA0KDQoNCmBgYHtyIGNodW5rMDV9DQpjZGlzdF9sYV9jYXAgJT4lIGNhX3ByZWZsaWdodCgpDQpgYGANCg0KXA0KDQojIyBGZXRjaCBEYXRhDQoNClRvIGNvcHkgZG93bmxvYWRlZCBkYXRhIGludG8gYSBkYXRhYmFzZSwgdXNlIGBjYV9nZXR2YWxzX2RiKClgIGluc3RlYWQgb2YgYGNhX2dldHZhbHNfdGJsKClgLiBgY2FfZ2V0dmFsc19kYigpYCBoYXMgdHdvIGFyZ3VtZW50cyB3aGljaCBhcmUgbWFuZGF0b3J5Og0KDQpgZGJfZm5gIC0gdGhlIGZpbGUgbmFtZSBvZiBhIFNRTGl0ZSBkYXRhYmFzZSAod2lsbCBiZSBjcmVhdGVkIGlmIGl0IGRvZXNuJ3QgZXhpc3QpDQoNCmBkYl90YmxgIC0gdGhlIG5hbWUgb2YgYSB0YWJsZSBpbnNpZGUgdGhlIGRhdGFiYXNlIHdoZXJlIHRoZSB2YWx1ZXMgc2hvdWxkIGJlIHNhdmVkDQoNCmBgYHtyIGNodW5rMDZ9DQpteV9kYXRhYmFzZV9mbiA8LSAiLi9kYXRhL2NkaXN0X2xhX3RlbXBfZGF0YS5zcWxpdGUiDQogIA0KY2Rpc3RfbGFfcnRibCA8LSBjZGlzdF9sYV9jYXAgJT4lIA0KICBjYV9nZXR2YWxzX2RiKGRiX2ZuID0gbXlfZGF0YWJhc2VfZm4sIA0KICAgICAgICAgICAgICAgIGRiX3RibCA9ICJ0ZW1wX2RhdGEiLA0KICAgICAgICAgICAgICAgIHF1aWV0ID0gVFJVRSkNCmBgYA0KDQpcDQoNCkluc3BlY3QgdGhlIHJlc3VsdHM6DQoNCmBgYHtyIGNodW5rMDd9DQpjZGlzdF9sYV9ydGJsICU+JSBoZWFkKCkNCmBgYA0KDQpcDQoNClRoZSBudW1iZXIgb2Ygcm93cyB3ZSByZXRyaWV2ZWQ6DQoNCmBgYHtyIGNodW5rMDh9DQpjZGlzdF9sYV9ydGJsICU+JSBjb3VudCgpICU+JSBwdWxsKG4pDQpgYGANCg0KXA0KDQojIyBXcmFuZ2xpbmcgYSBSZW1vdGUgVGliYmxlDQoNCk1hbnkgb2YgdGhlIGJhc2UgUiBvcGVyYXRpb25zIHRoYXQgd29yayB3aXRoIGluLW1lbW9yeSB0aWJibGVzIG1heSBvciBtYW55IG5vdCB3b3JrIHdpdGggcmVtb3RlIHRpYmJsZXMuIEZvciBleGFtcGxlIGFzIHdlIHNhdyBhYm92ZSBgY2Rpc3RfbGFfcnRibCAlPiUgY291bnQoKWAgd29ya3MsIGJ1dDoNCg0KYGBge3IgY2h1bmswOX0NCmRpbShjZGlzdF9sYV9ydGJsKQ0KbnJvdyhjZGlzdF9sYV9ydGJsKQ0KYGBgDQoNClwNCg0KSW4gZ2VuZXJhbCwgdGhlIGJlc3Qgd2F5IHRvIHdvcmsgd2l0aCByZW1vdGUgdGliYmxlcyBpcyB3aXRoIA0KDQppKSBkcGx5ciBmdW5jdGlvbnMsIG9yICAgDQppaSkgU1FMIHN0YXRlbWVudHMgcGFzc2VkIHVzaW5nIHRoZSBgREJJYCBwYWNrYWdlIA0KDQpTaW1wbGUgZmlsdGVyaW5nLCBzb3J0aW5nLCBncm91cGluZyBhbmQgc2ltcGxlIG51bWVyaWMgc3VtbWFyaWVzIGdlbmVyYWxseSB3b3JrIGZpbmUgd2l0aCBkcGx5ciB2ZXJiczoNCg0KYGBge3IgY2h1bmsxMH0NCmNkaXN0X2xhX3J0YmwgJT4lIA0KICBmaWx0ZXIoZ2VvaWQgPT0gIjA2MzIiKSAlPiUgDQogIGdyb3VwX2J5KHNjZW5hcmlvLCBnY20sIGN2YXIpICU+JSANCiAgc3VtbWFyaXplKG1lYW5fdGVtcCA9IG1lYW4odmFsLCBuYS5ybSA9IFRSVUUpKQ0KYGBgDQoNClwNCg0KDQoqKlBybyBUaXA6KioNCg0KLSBZb3UgY2FuICdjb252ZXJ0JyBhIFJlbW90ZSBUaWJibGUgdG8gYSByZWd1bGFyIGluLW1lbW9yeSBUaWJibGUgYnkgdGFja2luZyBvbiBgY29sbGVjdCgpYCBhdCB0aGUgZW5kIG9mIGEgZHBseXIgZXhwcmVzc2lvbi4NCg0KXA0KDQpJZiB5b3VyIHdyYW5nbGluZyB3b3JrZmxvdyBpbnZvbHZlcyBhIGxvdCBzdGVwcyB0aGF0IGFyZSBkaWZmaWN1bHQgb3IgaW1wb3NzaWJsZSB0byBkbyB3aXRoIHJlbW90ZSB0aWJibGVzLCBhIGdvb2Qgc3RyYXRlZ3kgaXMgdG8gZG8geW91ciBmaWx0ZXJpbmcgYW5kIGdyb3VwaW5nIHdpdGggZHBseXIgc3RhdGVtZW50cyBvbiB0aGUgcmVtb3RlIHRpYmJsZSwgYW5kIHRoZW4gY29udmVydCB0aGUgcmVzdWx0cyB0byBhIHJlZ3VsYXIgdGliYmxlIHdpdGggYGNvbGxlY3QoKWAuDQoNCkJlbG93IHdlIGNvbnZlcnQgdGhlIGdyb3VwZWQgc3VtbWFyeSB0YWJsZSBpbnRvIGEgdGliYmxlIHNvIHdlIGNhbiB1c2UgYHBpdm90X3dpZGVyKClgICh3aGljaCBkb2Vzbid0IHdvcmsgb24gcmVtb3RlIHRpYmJsZXMpOg0KDQpgYGB7ciBjaHVuazExfQ0KdGVtcF9sb25nX3RibCA8LSBjZGlzdF9sYV9ydGJsICU+JSANCiAgZmlsdGVyKGdlb2lkID09ICIwNjMyIikgJT4lIA0KICBncm91cF9ieShzY2VuYXJpbywgZ2NtLCBjdmFyKSAlPiUgDQogIHN1bW1hcml6ZShtZWFuX3RlbXAgPSBtZWFuKHZhbCwgbmEucm0gPSBUUlVFKSkgJT4lIA0KICBjb2xsZWN0KCkNCg0KY2xhc3ModGVtcF9sb25nX3RibCkNCg0KdGVtcF93aWRlX3RibCA8LSB0ZW1wX2xvbmdfdGJsICU+JSANCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IGN2YXIsIHZhbHVlc19mcm9tID0gbWVhbl90ZW1wKSAlPiUgDQogIG11dGF0ZSh0YXNfcmFuZ2UgPSB0YXNtYXggLSB0YXNtaW4pDQoNCnRlbXBfd2lkZV90YmwgJT4lIGhlYWQoKQ0KYGBgDQoNClwNCg0KIyMjIyBZb3VyIFR1cm4NCg0KQ3JlYXRlIGEgaGlzdG9ncmFtIG9mIHRoZSBtZWFuIGRhaWx5IHRlbXBlcmF0dXJlcyBmb3Igb25lIENvbmdyZXNzaW9uYWwgRGlzdHJpY3QgYW5kIG9uZSBlbWlzc2lvbnMgc2NlbmFyaW8sIGdyb3VwaW5nIHRoZSBkYXRhIGJ5IEdDTS4gRG9lcyB0aGUgZGlzdHJpYnV0aW9uIG9mIG1lYW4gYXZlcmFnZSB0ZW1wZXJhdHVyZSBsb29rIHRoZSBzYW1lIGFjcm9zcyBHQ01zPyANCg0KYGBge3IgY2h1bmsxMn0NCiMjIFlvdXIgYW5zd2VyIGhlcmUNCmBgYA0KDQpcDQoNCiMgRG93bmxvYWRpbmcgUmFzdGVycw0KDQpBbm90aGVyIHdheSB0byBkZWFsIHdpdGggbGFyZ2UgZGF0YSBuZWVkcyBpcyB0byBkb3dubG9hZCB0aGUgZGF0YSBhcyByYXN0ZXIgb3IgVElGIGZpbGVzLiBUaGUgc2FtZSBBUEkgUmVxdWVzdCBvYmplY3QgY2FuIGJlIHVzZWQgdG8gZ2V0IHJhc3RlciBkYXRhIGlmIHlvdSBmZWVkIGl0IGludG8gYGNhX2dldHJzdF9zdGFycygpYC4NCg0KRm9yIGFkZGl0aW9uYWwgaW5mbyBvbiBkb3dubG9hZGluZyBhbmQgYW5hbHl6aW5nIHJhc3RlcnMsIHNlZSB0aGUgMyBhcnRpY2xlcyBvbiBbRG93bmxvYWRpbmcgUmFzdGVyc10oaHR0cHM6Ly91Y2Fuci1pZ2lzLmdpdGh1Yi5pby9jYWxhZGFwdHIvYXJ0aWNsZXMvcmFzdGVycy1wdDEuaHRtbCkuDQoNCkJlbG93IHdlIGdldCBhIHJhc3RlciBvZiBvYnNlcnZlZCBoaXN0b3JpYyB0ZW1wZXJhdHVyZSBkYXRhIGZvciB0aGUgU2llcnJhIGNsaW1hdGUgcmVnaW9uOg0KDQpgYGB7ciBjaHVuazEzfQ0Kc2llcnJhX2NhcCA8LSBjYV9sb2NfYW9pcHJlc2V0KHR5cGUgPSAiY2xpbXJlZ2lvbnMiLCBpZGZsZCA9ICJuYW1lIiwgaWR2YWwgPSAiU2llcnJhIikgJT4lIA0KICBjYV9saXZuZWgoVFJVRSkgJT4lIA0KICBjYV9wZXJpb2QoInllYXIiKSAlPiUgDQogIGNhX2N2YXIoInByIikgJT4lIA0KICBjYV95ZWFycyhzdGFydCA9IDE5NzAsIGVuZCA9IDIwMTApDQoNCnNpZXJyYV9jYXANCg0KcGxvdChzaWVycmFfY2FwLCBsb2NhZ3JpZCA9IFRSVUUpDQoNCnNpZXJyYV9jYXAgJT4lIGNhX3ByZWZsaWdodCgpDQpgYGANCg0KXA0KDQpUbyBmZXRjaCB0aGUgZGF0YSBhcyBUSUZzLCB1c2UgOg0KDQpgYGB7ciBjaHVuazE0fQ0KdGlmZl9kaXIgPC0gIi4vZGF0YSINCg0Kc2llcnJhX3RpZmZfZm4gPC0gc2llcnJhX2NhcCAlPiUgDQogIGNhX2dldHJzdF9zdGFycyhvdXRfZGlyID0gdGlmZl9kaXIsIG1hc2sgPSBUUlVFLCBxdWlldCA9IFRSVUUsIG92ZXJ3cml0ZSA9IEZBTFNFKQ0KYGBgDQoNCioqUHJvIFRpcDoqKg0KDQogLSB0byBhdm9pZCBkb3dubG9hZGluZyB0aGUgc2FtZSBUSUYgbXVsdGlwbGUgdGltZXMsIHVzZSB0aGUgc2FtZSBvdXRwdXQgZGlyZWN0b3J5IGFuZCBzZXQgYG92ZXJ3cml0ZSA9IEZBTFNFYA0KIA0KXA0KIA0KYGNhX2dldHJzdF9zdGFycygpYCB3b3JrcyBkaWZmZXJlbnRseSB0aGFuIHJldHJpZXZpbmcgdGFidWxhciBjbGltYXRlIHZhbHVlcy4gSXQgcmV0dXJucyBhIHZlY3RvciBvZiBUSUYgZmlsZXMgdGhhdCB3ZXJlIGRvd25sb2FkZWQuIFRvIHdvcmsgd2l0aCB0aGVtLCB5b3UgbmV4dCBoYXZlIHRvIGxvYWQgdGhlbSBiYWNrIGludG8gUiBhcyBzdGFycyBvYmplY3RzIChzcGFjZS10aW1lIGFycmF5cykgdXNpbmcgYGNhX3N0YXJzX3JlYWQoKWA6DQoNCmBgYHtyIGNodW5rMTUsIHBhZ2VkLnByaW50ID0gRkFMU0V9DQpzaWVycmFfc3RhcnNfbHN0IDwtIGNhX3N0YXJzX3JlYWQoc2llcnJhX3RpZmZfZm4pDQpzaWVycmFfc3RhcnNfbHN0W1sxXV0NCmBgYA0KDQpcDQoNClRvIHBsb3QgYSBzdGFycyBvYmplY3RzLCB5b3UgaGF2ZSB0byBkZWNpZGUgd2hpY2ggbGF5ZXIocykgdG8gcGxvdC4gSW4gdGhpcyBjYXNlLCBlYWNoIGxheWVyIHJlcHJlc2VudHMgYSB5ZWFyIGZyb20gMTk3MCB0byAyMDEwLiBCZWxvdyB3ZSBwbG90IDQgb2YgdGhlIDQwIHllYXJzOg0KDQpgYGB7ciBjaHVuazE2fQ0KcGxvdChzaWVycmFfc3RhcnNfbHN0W1sxXV0gJT4lIHNsaWNlKGluZGV4ID0gc2VxKDEsNDAsbGVuZ3RoLm91dCA9NCksIGFsb25nID0gInllYXIiKSwgDQogICAgIGF4ZXMgPSBUUlVFLA0KICAgICBtYWluID0gYXR0cmlidXRlcyhzaWVycmFfc3RhcnNfbHN0W1sxXV0pJGNhX21ldGFkYXRhJHNsdWcpDQpgYGANCg0KTm90IHN1cmUgd2hhdCB0aGUgdW5pdHMgYXJlPyBZb3UgY2FuIGRvdWJsZS1jaGVjayBieSB2aWV3aW5nIHRoZSBtZXRhZGF0YSBmb3IgdGhlIHNsdWcgZnJvbSB0aGUgY2F0YWxvZzoNCg0KYGBge3IgY2h1bmsxN30NCmNhX2NhdGFsb2dfc2VhcmNoKCJwcl95ZWFyX2xpdm5laCIpDQpgYGANCg0KXA0KDQpUaGVyZSBpcyBhICoqbG90KiogbW9yZSB5b3UgY2FuIGRvIHdpdGggcmFzdGVycywgaW5jbHVkaW5nIHBpeGVsIHN1bW1hcmllcywgY29tYmluaW5nIHRoZW0gaW50byBoaWdoZXIgZGltZW5zaW9uYWwgZGF0YSBjdWJlcywgc3BhdGlhbGx5IG1vc2FpY2luZyB0aGVtLCBldGMuIEZvciBtb3JlIGluZm8sIHNlZSB0aGUgUmFzdGVycyBhcnRpY2xlcyBvbiB0aGUgW3dlYnNpdGVdKGh0dHBzOi8vdWNhbnItaWdpcy5naXRodWIuaW8vY2FsYWRhcHRyLykuDQogDQpcDQoNCiMjIyMgWW91ciBUdXJuDQoNCkRvd25sb2FkIGhpc3RvcmljIHByZWNpcGl0YXRpb24gZGF0YSBmb3IgdGhlIGNvdW50eSB3aGVyZSB5b3UgbGl2ZSBvciB3b3JrLiBbW0Fuc3dlcl0oaHR0cHM6Ly9iaXQubHkvM203QmNXTSldDQogDQpgYGB7ciBjaHVuazE4LCBwYWdlZC5wcmludCA9IEZBTFNFfQ0KIyMgRXhhbXBsZTogTWVuZG9jaW5vIENvdW50eQ0KDQpjYV9hb2lwcmVzZXRfZ2VvbSgiY291bnRpZXMiLCBxdWlldCA9IFRSVUUpICU+JSANCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JSANCiAgZmlsdGVyKG5hbWUgPT0gIk1lbmRvY2lubyIpICU+JSANCiAgc2VsZWN0KG5hbWUsIHN0YXRlX25hbWUsIGZpcHMpDQoNCm1lbmRvY2lub19jYXAgPC0gY2FfbG9jX2FvaXByZXNldCh0eXBlID0gImNvdW50aWVzIiwgaWRmbGQgPSAiZmlwcyIsIGlkdmFsID0gIjA2MDQ1IikgJT4lIA0KICBjYV9saXZuZWgoVFJVRSkgJT4lIA0KICBjYV9wZXJpb2QoInllYXIiKSAlPiUgDQogIGNhX2N2YXIoInByIikgJT4lIA0KICBjYV95ZWFycyhzdGFydCA9IDE5NzAsIGVuZCA9IDIwMTApDQoNCnBsb3QobWVuZG9jaW5vX2NhcCkNCg0KbWVuZG9jaW5vX2ZuIDwtIG1lbmRvY2lub19jYXAgJT4lIA0KICBjYV9nZXRyc3Rfc3RhcnMob3V0X2RpciA9IHRlbXBkaXIoKSwgbWFzayA9IFRSVUUsIHF1aWV0ID0gVFJVRSwgb3ZlcndyaXRlID0gRkFMU0UpDQoNCm1lbmRvY2lub19zdGFyc19sc3QgPC0gY2Ffc3RhcnNfcmVhZChtZW5kb2Npbm9fZm4pIA0KICANCm1lbmRvY2lub19zdGFyc19sc3RbWzFdXQ0KDQpwbG90KG1lbmRvY2lub19zdGFyc19sc3RbWzFdXSAlPiUgc2xpY2UoaW5kZXggPSBzZXEoMSw0MCxsZW5ndGgub3V0ID00KSwgYWxvbmcgPSAieWVhciIpLCANCiAgICAgYXhlcyA9IFRSVUUsDQogICAgIG1haW4gPSBhdHRyaWJ1dGVzKG1lbmRvY2lub19zdGFyc19sc3RbWzFdXSkkY2FfbWV0YWRhdGEkc2x1ZykNCmBgYA0KIA0KDQo=