Overview

This notebook will demonstrate two ways of dealing with larger volumes of data:

A functional definition of “large data” might include:

Saving Cal-Adapt Data to SQLite Database

To manage large downloads and avoid downloading data twice, caladaptR provides ca_getvals_db(). ca_getvals_db() is very similar to ca_getvals_tbl(), but saves the data into a SQLite database file on your computer as it comes in. Before making an API request, it checks to see if the data have already been downloaded, and if so skips it. If you get disconnected during a download, you can run the command again and it’ll just pick up where it left off. ca_getvals_db() returns a ‘remote tibble’, which functions very similar to a regular ‘in-memory’ tibble however it points to the SQLite database file on disk.

For additional info on working with SQLite databases, see the Article on making Large Queries.

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)
library(units)
library(ggplot2)
library(dplyr)
library(lubridate)
library(sf)
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_feb22\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

  1. dplyr functions, or
  2. 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:


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)

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: 382
  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]

## Your answer here


LS0tDQp0aXRsZTogIkxhcmdlIFF1ZXJpZXMgYW5kIENhY2hpbmcgUmVzdWx0cyINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazogDQogICAgY3NzOiBodHRwczovL3VjYW5yLWlnaXMuZ2l0aHViLmlvL2NhbGFkYXB0ci1yZXMvYXNzZXRzL25iX2NzczAxLmNzcw0KICAgIGluY2x1ZGVzOg0KICAgICAgYWZ0ZXJfYm9keTogaHR0cHM6Ly91Y2Fuci1pZ2lzLmdpdGh1Yi5pby9jYWxhZGFwdHItcmVzL2Fzc2V0cy9uYl9mb290ZXIwMS5odG1sDQotLS0NCg0KIyBPdmVydmlldw0KDQpUaGlzIG5vdGVib29rIHdpbGwgZGVtb25zdHJhdGUgdHdvIHdheXMgb2YgZGVhbGluZyB3aXRoIGxhcmdlciB2b2x1bWVzIG9mIGRhdGE6DQoNCi0gY2FjaGluZyByZXRyaWV2ZWQgaW50byBhIFNRTGl0ZSBkYXRhYmFzZSAgDQotIGRvd25sb2FkaW5nIGRhdGEgYXMgVElGRnMNCg0KQSBmdW5jdGlvbmFsIGRlZmluaXRpb24gb2YgImxhcmdlIGRhdGEiIG1pZ2h0IGluY2x1ZGU6DQoNCi0gYW55IHZvbHVtZSBvZiBkYXRhIHRoYXQgeW91IHdvdWxkbid0IHdhbnQgdG8gZG93bmxvYWQgdHdpY2UgIA0KLSBhbnkgdm9sdW1lIG9mIGRhdGEgdGhhdCBtaWdodCBib2cgZG93biAob3Igd29yc2UsIGNyYXNoKSB0aGUgQ2FsLUFkYXB0IHNlcnZlciwgYW5kIGFubm95IHRoZSBzeXN0ZW0gYWRtaW5pc3RyYXRvcnMgIA0KDQojIyBTYXZpbmcgQ2FsLUFkYXB0IERhdGEgdG8gU1FMaXRlIERhdGFiYXNlDQoNClRvIG1hbmFnZSBsYXJnZSBkb3dubG9hZHMgYW5kIGF2b2lkIGRvd25sb2FkaW5nIGRhdGEgdHdpY2UsIGNhbGFkYXB0UiBwcm92aWRlcyBgY2FfZ2V0dmFsc19kYigpYC4gYGNhX2dldHZhbHNfZGIoKWAgaXMgdmVyeSBzaW1pbGFyIHRvIGBjYV9nZXR2YWxzX3RibCgpYCwgYnV0IHNhdmVzIHRoZSBkYXRhIGludG8gYSBTUUxpdGUgZGF0YWJhc2UgZmlsZSBvbiB5b3VyIGNvbXB1dGVyIGFzIGl0IGNvbWVzIGluLiBCZWZvcmUgbWFraW5nIGFuIEFQSSByZXF1ZXN0LCBpdCBjaGVja3MgdG8gc2VlIGlmIHRoZSBkYXRhIGhhdmUgYWxyZWFkeSBiZWVuIGRvd25sb2FkZWQsIGFuZCBpZiBzbyBza2lwcyBpdC4gSWYgeW91IGdldCBkaXNjb25uZWN0ZWQgZHVyaW5nIGEgZG93bmxvYWQsIHlvdSBjYW4gcnVuIHRoZSBjb21tYW5kIGFnYWluIGFuZCBpdCdsbCBqdXN0IHBpY2sgdXAgd2hlcmUgaXQgbGVmdCBvZmYuIGBjYV9nZXR2YWxzX2RiKClgIHJldHVybnMgYSAncmVtb3RlIHRpYmJsZScsIHdoaWNoIGZ1bmN0aW9ucyB2ZXJ5IHNpbWlsYXIgdG8gYSByZWd1bGFyICdpbi1tZW1vcnknIHRpYmJsZSBob3dldmVyIGl0IHBvaW50cyB0byB0aGUgU1FMaXRlIGRhdGFiYXNlIGZpbGUgb24gZGlzay4NCg0KRm9yIGFkZGl0aW9uYWwgaW5mbyBvbiB3b3JraW5nIHdpdGggU1FMaXRlIGRhdGFiYXNlcywgc2VlIHRoZSBBcnRpY2xlIG9uIG1ha2luZyBbTGFyZ2UgUXVlcmllc10oaHR0cHM6Ly91Y2Fuci1pZ2lzLmdpdGh1Yi5pby9jYWxhZGFwdHIvYXJ0aWNsZXMvbGFyZ2UtcXVlcmllcy5odG1sKS4NCg0KIyBFeGFtcGxlOiBEb3dubG9hZCBEYWlseSBEYXRhIGZvciBDb25ncmVzc2lvbmFsIERpc3RpY3RzDQoNCkluIHRoaXMgZXhhbXBsZSB3ZSdsbCBkb3dubG9hZCBkYWlseSB0ZW1wZXJhdHVyZSBkYXRhIGZvciAxNiBDb25ncmVzc2lvbmFsIERpc3RyaWN0cyBpbiB0aGUgTEEgcmVnaW9uLiANCg0KXA0KDQojIyBTZXR1cA0KDQpMb2FkIGNhbGFkYXB0UiBhbmQgdGhlIG90aGVyIHBhY2thZ2Ugd2UncmUgZ29pbmcgdG8gbmVlZC4gKElmIHlvdSBoYXZlbid0IGluc3RhbGxlZCB0aGVzZSB5ZXQsIHNlZSB0aGlzIFtzZXR1cCBzY3JpcHRdKGh0dHBzOi8vZ2l0aHViLmNvbS91Y2Fuci1pZ2lzL2NhbGFkYXB0ci1yZXMvYmxvYi9tYWluL2RvY3Mvd29ya3Nob3BzL2NhX2ludHJvX29jdDIxL3NjcmlwdHMvY2FsYWRhcHRyX3NldHVwLlIpKS4gDQoNCmBgYHtyIGNodW5rMDEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hvbGQnfQ0KbGlicmFyeShjYWxhZGFwdHIpDQpsaWJyYXJ5KHVuaXRzKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShzZikNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KHRtYXApDQpsaWJyYXJ5KGNvbmZsaWN0ZWQpDQpjb25mbGljdF9wcmVmZXIoImZpbHRlciIsICJkcGx5ciIsIHF1aWV0ID0gVFJVRSkNCmNvbmZsaWN0X3ByZWZlcigiY291bnQiLCAiZHBseXIiLCBxdWlldCA9IFRSVUUpDQpjb25mbGljdF9wcmVmZXIoInNlbGVjdCIsICJkcGx5ciIsIHF1aWV0ID0gVFJVRSkNCmBgYA0KDQpcDQoNCiMjIEltcG9ydCB0aGUgQ29uZ3Jlc3Npb25hbCBEaXN0cmljdHMgQm91bmRhcmllcw0KDQpUaGUgTEEgQ29uZ3Jlc3Npb25hbCBkaXN0cmljdCBib3VuZGFyaWVzIGFyZSBzYXZlZCBpbiB0aGUgJ2RhdGEnIGZvbGRlciBhcyBhIGdlb3BhY2thZ2U6DQoNCmBgYHtyIGNodW5rMDJ9DQpjZGlzdF9sYV9mbiA8LSAiLi9kYXRhL2NkaXN0cmljdHNfbGEuZ3BrZyINCmZpbGUuZXhpc3RzKGNkaXN0X2xhX2ZuKQ0KDQpjZGlzdF9sYV9zZiA8LSBzdF9yZWFkKGNkaXN0X2xhX2ZuKQ0KDQp0bWFwX21vZGUoInZpZXciKQ0KdG1fc2hhcGUoY2Rpc3RfbGFfc2YpICsgdG1fYm9yZGVycygpDQpgYGANCg0KKipQcm8gVGlwOioqDQoNCi0gdG8gdXNlIGEgc2Ygb2JqZWN0IHRoZSBsb2NhdGlvbiBmb3IgYW4gQVBJIFJlcXVlc3QsIGl0IG11c3QgYmUgaW4gZ2VvZ3JhcGhpYyBjb29yZGluYXRlcyAoRVBTRyA0MzI2KQ0KDQoNClwNCg0KIyMgQ3JlYXRlIHRoZSBBUEkgUmVxdWVzdA0KDQpIZXJlIHdlIHVzZSBgY2FfbG9jX3NmKClgIGFzIHRoZSBsb2NhdGlvbiBmdW5jdGlvbiBmb3IgYW4gQVBJIHJlcXVlc3QgZm9yIDMwLXllYXJzIG9mIG1vZGVsZWQgZGFpbHkgdGVtcGVyYXR1cmUgZGF0YSAobWluaW11bSBhbmQgbWF4aW11bSkgZm9yIDQgR0NNcyBhbmQgMiBSQ1BzOg0KDQpgYGB7ciBjaHVuazAzfQ0KY2Rpc3RfbGFfY2FwIDwtIGNhX2xvY19zZihsb2MgPSBjZGlzdF9sYV9zZiwgaWRmbGQgPSAiZ2VvaWQiKSAlPiUgDQogIGNhX2djbShnY21zWzE6NF0pICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICBjYV9zY2VuYXJpbyhjKCJyY3A0NSIsICJyY3A4NSIpKSAlPiUNCiAgY2FfcGVyaW9kKCJkYXkiKSAlPiUNCiAgY2FfeWVhcnMoc3RhcnQgPSAyMDcwLCBlbmQgPSAyMDk5KSAlPiUNCiAgY2FfY3ZhcihjKCJ0YXNtaW4iLCAidGFzbWF4IikpICU+JSANCiAgY2Ffb3B0aW9ucyhzcGF0aWFsX2FnID0gIm1lYW4iKQ0KDQpjZGlzdF9sYV9jYXANCmBgYA0KDQpcDQoNCkRvIHRoZSBzdGFuZGFyZCBwbG90dGluZyBhbmQgcHJlZmxpZ2h0IGNoZWNrczoNCg0KYGBge3IgY2h1bmswNCwgY2FjaGUgPSBGQUxTRX0NCnBsb3QoY2Rpc3RfbGFfY2FwLCBsb2NhZ3JpZCA9IFRSVUUpDQpgYGANCg0KDQpgYGB7ciBjaHVuazA1fQ0KY2Rpc3RfbGFfY2FwICU+JSBjYV9wcmVmbGlnaHQoKQ0KYGBgDQoNClwNCg0KIyMgRmV0Y2ggRGF0YQ0KDQpUbyBjb3B5IGRvd25sb2FkZWQgZGF0YSBpbnRvIGEgZGF0YWJhc2UsIHVzZSBgY2FfZ2V0dmFsc19kYigpYCBpbnN0ZWFkIG9mIGBjYV9nZXR2YWxzX3RibCgpYC4gYGNhX2dldHZhbHNfZGIoKWAgaGFzIHR3byBhcmd1bWVudHMgd2hpY2ggYXJlIG1hbmRhdG9yeToNCg0KYGRiX2ZuYCAtIHRoZSBmaWxlIG5hbWUgb2YgYSBTUUxpdGUgZGF0YWJhc2UgKHdpbGwgYmUgY3JlYXRlZCBpZiBpdCBkb2Vzbid0IGV4aXN0KQ0KDQpgZGJfdGJsYCAtIHRoZSBuYW1lIG9mIGEgdGFibGUgaW5zaWRlIHRoZSBkYXRhYmFzZSB3aGVyZSB0aGUgdmFsdWVzIHNob3VsZCBiZSBzYXZlZA0KDQpgYGB7ciBjaHVuazA2fQ0KbXlfZGF0YWJhc2VfZm4gPC0gIi4vZGF0YS9jZGlzdF9sYV90ZW1wX2RhdGEuc3FsaXRlIg0KICANCmNkaXN0X2xhX3J0YmwgPC0gY2Rpc3RfbGFfY2FwICU+JSANCiAgY2FfZ2V0dmFsc19kYihkYl9mbiA9IG15X2RhdGFiYXNlX2ZuLCANCiAgICAgICAgICAgICAgICBkYl90YmwgPSAidGVtcF9kYXRhIiwNCiAgICAgICAgICAgICAgICBxdWlldCA9IFRSVUUpDQpgYGANCg0KXA0KDQpJbnNwZWN0IHRoZSByZXN1bHRzOg0KDQpgYGB7ciBjaHVuazA3fQ0KY2Rpc3RfbGFfcnRibCAlPiUgaGVhZCgpDQpgYGANCg0KXA0KDQpUaGUgbnVtYmVyIG9mIHJvd3Mgd2UgcmV0cmlldmVkOg0KDQpgYGB7ciBjaHVuazA4fQ0KY2Rpc3RfbGFfcnRibCAlPiUgY291bnQoKSAlPiUgcHVsbChuKQ0KYGBgDQoNClwNCg0KIyMgV3JhbmdsaW5nIGEgUmVtb3RlIFRpYmJsZQ0KDQpNYW55IG9mIHRoZSBiYXNlIFIgb3BlcmF0aW9ucyB0aGF0IHdvcmsgd2l0aCBpbi1tZW1vcnkgdGliYmxlcyBtYXkgb3IgbWFueSBub3Qgd29yayB3aXRoIHJlbW90ZSB0aWJibGVzLiBGb3IgZXhhbXBsZSBhcyB3ZSBzYXcgYWJvdmUgYGNkaXN0X2xhX3J0YmwgJT4lIGNvdW50KClgIHdvcmtzLCBidXQ6DQoNCmBgYHtyIGNodW5rMDl9DQpkaW0oY2Rpc3RfbGFfcnRibCkNCm5yb3coY2Rpc3RfbGFfcnRibCkNCmBgYA0KDQpcDQoNCkluIGdlbmVyYWwsIHRoZSBiZXN0IHdheSB0byB3b3JrIHdpdGggcmVtb3RlIHRpYmJsZXMgaXMgd2l0aCANCg0KaSkgZHBseXIgZnVuY3Rpb25zLCBvciAgIA0KaWkpIFNRTCBzdGF0ZW1lbnRzIHBhc3NlZCB1c2luZyB0aGUgYERCSWAgcGFja2FnZSANCg0KU2ltcGxlIGZpbHRlcmluZywgc29ydGluZywgZ3JvdXBpbmcgYW5kIHNpbXBsZSBudW1lcmljIHN1bW1hcmllcyBnZW5lcmFsbHkgd29yayBmaW5lIHdpdGggZHBseXIgdmVyYnM6DQoNCmBgYHtyIGNodW5rMTB9DQpjZGlzdF9sYV9ydGJsICU+JSANCiAgZmlsdGVyKGdlb2lkID09ICIwNjMyIikgJT4lIA0KICBncm91cF9ieShzY2VuYXJpbywgZ2NtLCBjdmFyKSAlPiUgDQogIHN1bW1hcml6ZShtZWFuX3RlbXAgPSBtZWFuKHZhbCwgbmEucm0gPSBUUlVFKSkNCmBgYA0KDQpcDQoNCg0KKipQcm8gVGlwOioqDQoNCi0gWW91IGNhbiAnY29udmVydCcgYSBSZW1vdGUgVGliYmxlIHRvIGEgcmVndWxhciBpbi1tZW1vcnkgVGliYmxlIGJ5IHRhY2tpbmcgb24gYGNvbGxlY3QoKWAgYXQgdGhlIGVuZCBvZiBhIGRwbHlyIGV4cHJlc3Npb24uDQoNClwNCg0KSWYgeW91ciB3cmFuZ2xpbmcgd29ya2Zsb3cgaW52b2x2ZXMgYSBsb3Qgc3RlcHMgdGhhdCBhcmUgZGlmZmljdWx0IG9yIGltcG9zc2libGUgdG8gZG8gd2l0aCByZW1vdGUgdGliYmxlcywgYSBnb29kIHN0cmF0ZWd5IGlzIHRvIGRvIHlvdXIgZmlsdGVyaW5nIGFuZCBncm91cGluZyB3aXRoIGRwbHlyIHN0YXRlbWVudHMgb24gdGhlIHJlbW90ZSB0aWJibGUsIGFuZCB0aGVuIGNvbnZlcnQgdGhlIHJlc3VsdHMgdG8gYSByZWd1bGFyIHRpYmJsZSB3aXRoIGBjb2xsZWN0KClgLg0KDQpCZWxvdyB3ZSBjb252ZXJ0IHRoZSBncm91cGVkIHN1bW1hcnkgdGFibGUgaW50byBhIHRpYmJsZSBzbyB3ZSBjYW4gdXNlIGBwaXZvdF93aWRlcigpYCAod2hpY2ggZG9lc24ndCB3b3JrIG9uIHJlbW90ZSB0aWJibGVzKToNCg0KYGBge3IgY2h1bmsxMX0NCnRlbXBfbG9uZ190YmwgPC0gY2Rpc3RfbGFfcnRibCAlPiUgDQogIGZpbHRlcihnZW9pZCA9PSAiMDYzMiIpICU+JSANCiAgZ3JvdXBfYnkoc2NlbmFyaW8sIGdjbSwgY3ZhcikgJT4lIA0KICBzdW1tYXJpemUobWVhbl90ZW1wID0gbWVhbih2YWwsIG5hLnJtID0gVFJVRSkpICU+JSANCiAgY29sbGVjdCgpDQoNCmNsYXNzKHRlbXBfbG9uZ190YmwpDQoNCnRlbXBfd2lkZV90YmwgPC0gdGVtcF9sb25nX3RibCAlPiUgDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBjdmFyLCB2YWx1ZXNfZnJvbSA9IG1lYW5fdGVtcCkgJT4lIA0KICBtdXRhdGUodGFzX3JhbmdlID0gdGFzbWF4IC0gdGFzbWluKQ0KDQp0ZW1wX3dpZGVfdGJsICU+JSBoZWFkKCkNCmBgYA0KDQpcDQoNCiMjIyMgWW91ciBUdXJuDQoNCkNyZWF0ZSBhIGhpc3RvZ3JhbSBvZiB0aGUgbWVhbiBkYWlseSB0ZW1wZXJhdHVyZXMgZm9yIG9uZSBDb25ncmVzc2lvbmFsIERpc3RyaWN0IGFuZCBvbmUgZW1pc3Npb25zIHNjZW5hcmlvLCBncm91cGluZyB0aGUgZGF0YSBieSBHQ00uIERvZXMgdGhlIGRpc3RyaWJ1dGlvbiBvZiBtZWFuIGF2ZXJhZ2UgdGVtcGVyYXR1cmUgbG9vayB0aGUgc2FtZSBhY3Jvc3MgR0NNcz8gDQoNCmBgYHtyIGNodW5rMTJ9DQojIyBZb3VyIGFuc3dlciBoZXJlDQpgYGANCg0KXA0KDQojIERvd25sb2FkaW5nIFJhc3RlcnMNCg0KQW5vdGhlciB3YXkgdG8gZGVhbCB3aXRoIGxhcmdlIGRhdGEgbmVlZHMgaXMgdG8gZG93bmxvYWQgdGhlIGRhdGEgYXMgcmFzdGVyIG9yIFRJRiBmaWxlcy4gVGhlIHNhbWUgQVBJIFJlcXVlc3Qgb2JqZWN0IGNhbiBiZSB1c2VkIHRvIGdldCByYXN0ZXIgZGF0YSBpZiB5b3UgZmVlZCBpdCBpbnRvIGBjYV9nZXRyc3Rfc3RhcnMoKWAuDQoNCkZvciBhZGRpdGlvbmFsIGluZm8gb24gZG93bmxvYWRpbmcgYW5kIGFuYWx5emluZyByYXN0ZXJzLCBzZWUgdGhlIDMgYXJ0aWNsZXMgb24gW0Rvd25sb2FkaW5nIFJhc3RlcnNdKGh0dHBzOi8vdWNhbnItaWdpcy5naXRodWIuaW8vY2FsYWRhcHRyL2FydGljbGVzL3Jhc3RlcnMtcHQxLmh0bWwpLg0KDQpCZWxvdyB3ZSBnZXQgYSByYXN0ZXIgb2Ygb2JzZXJ2ZWQgaGlzdG9yaWMgdGVtcGVyYXR1cmUgZGF0YSBmb3IgdGhlIFNpZXJyYSBjbGltYXRlIHJlZ2lvbjoNCg0KYGBge3IgY2h1bmsxMywgY2FjaGUgPSBGQUxTRX0NCnNpZXJyYV9jYXAgPC0gY2FfbG9jX2FvaXByZXNldCh0eXBlID0gImNsaW1yZWdpb25zIiwgaWRmbGQgPSAibmFtZSIsIGlkdmFsID0gIlNpZXJyYSIpICU+JSANCiAgY2FfbGl2bmVoKFRSVUUpICU+JSANCiAgY2FfcGVyaW9kKCJ5ZWFyIikgJT4lIA0KICBjYV9jdmFyKCJwciIpICU+JSANCiAgY2FfeWVhcnMoc3RhcnQgPSAxOTcwLCBlbmQgPSAyMDEwKQ0KDQpzaWVycmFfY2FwDQoNCnBsb3Qoc2llcnJhX2NhcCwgbG9jYWdyaWQgPSBUUlVFKQ0KDQpzaWVycmFfY2FwICU+JSBjYV9wcmVmbGlnaHQoKQ0KYGBgDQoNClwNCg0KVG8gZmV0Y2ggdGhlIGRhdGEgYXMgVElGcywgdXNlIDoNCg0KYGBge3IgY2h1bmsxNH0NCnRpZmZfZGlyIDwtICIuL2RhdGEiDQoNCnNpZXJyYV90aWZmX2ZuIDwtIHNpZXJyYV9jYXAgJT4lIA0KICBjYV9nZXRyc3Rfc3RhcnMob3V0X2RpciA9IHRpZmZfZGlyLCBtYXNrID0gVFJVRSwgcXVpZXQgPSBUUlVFLCBvdmVyd3JpdGUgPSBGQUxTRSkNCmBgYA0KDQoqKlBybyBUaXA6KioNCg0KIC0gdG8gYXZvaWQgZG93bmxvYWRpbmcgdGhlIHNhbWUgVElGIG11bHRpcGxlIHRpbWVzLCB1c2UgdGhlIHNhbWUgb3V0cHV0IGRpcmVjdG9yeSBhbmQgc2V0IGBvdmVyd3JpdGUgPSBGQUxTRWANCiANClwNCiANCmBjYV9nZXRyc3Rfc3RhcnMoKWAgd29ya3MgZGlmZmVyZW50bHkgdGhhbiByZXRyaWV2aW5nIHRhYnVsYXIgY2xpbWF0ZSB2YWx1ZXMuIEl0IHJldHVybnMgYSB2ZWN0b3Igb2YgVElGIGZpbGVzIHRoYXQgd2VyZSBkb3dubG9hZGVkLiBUbyB3b3JrIHdpdGggdGhlbSwgeW91IG5leHQgaGF2ZSB0byBsb2FkIHRoZW0gYmFjayBpbnRvIFIgYXMgc3RhcnMgb2JqZWN0cyAoc3BhY2UtdGltZSBhcnJheXMpIHVzaW5nIGBjYV9zdGFyc19yZWFkKClgOg0KDQpgYGB7ciBjaHVuazE1LCBwYWdlZC5wcmludCA9IEZBTFNFfQ0Kc2llcnJhX3N0YXJzX2xzdCA8LSBjYV9zdGFyc19yZWFkKHNpZXJyYV90aWZmX2ZuKQ0Kc2llcnJhX3N0YXJzX2xzdFtbMV1dDQpgYGANCg0KXA0KDQpUbyBwbG90IGEgc3RhcnMgb2JqZWN0cywgeW91IGhhdmUgdG8gZGVjaWRlIHdoaWNoIGxheWVyKHMpIHRvIHBsb3QuIEluIHRoaXMgY2FzZSwgZWFjaCBsYXllciByZXByZXNlbnRzIGEgeWVhciBmcm9tIDE5NzAgdG8gMjAxMC4gQmVsb3cgd2UgcGxvdCA0IG9mIHRoZSA0MCB5ZWFyczoNCg0KYGBge3IgY2h1bmsxNiwgY2FjaGUgPSBUUlVFfQ0KcGxvdChzaWVycmFfc3RhcnNfbHN0W1sxXV0gJT4lIHNsaWNlKGluZGV4ID0gc2VxKDEsNDAsbGVuZ3RoLm91dCA9NCksIGFsb25nID0gInllYXIiKSwgDQogICAgIGF4ZXMgPSBUUlVFLA0KICAgICBtYWluID0gYXR0cmlidXRlcyhzaWVycmFfc3RhcnNfbHN0W1sxXV0pJGNhX21ldGFkYXRhJHNsdWcpDQpgYGANCg0KTm90IHN1cmUgd2hhdCB0aGUgdW5pdHMgYXJlPyBZb3UgY2FuIGRvdWJsZS1jaGVjayBieSB2aWV3aW5nIHRoZSBtZXRhZGF0YSBmb3IgdGhlIHNsdWcgZnJvbSB0aGUgY2F0YWxvZzoNCg0KYGBge3IgY2h1bmsxN30NCmNhX2NhdGFsb2dfc2VhcmNoKCJwcl95ZWFyX2xpdm5laCIpDQpgYGANCg0KXA0KDQpUaGVyZSBpcyBhICoqbG90KiogbW9yZSB5b3UgY2FuIGRvIHdpdGggcmFzdGVycywgaW5jbHVkaW5nIHBpeGVsIHN1bW1hcmllcywgY29tYmluaW5nIHRoZW0gaW50byBoaWdoZXIgZGltZW5zaW9uYWwgZGF0YSBjdWJlcywgc3BhdGlhbGx5IG1vc2FpY2luZyB0aGVtLCBldGMuIEZvciBtb3JlIGluZm8sIHNlZSB0aGUgUmFzdGVycyBhcnRpY2xlcyBvbiB0aGUgW3dlYnNpdGVdKGh0dHBzOi8vdWNhbnItaWdpcy5naXRodWIuaW8vY2FsYWRhcHRyLykuDQogDQpcDQoNCiMjIyMgWW91ciBUdXJuDQoNCkRvd25sb2FkIGhpc3RvcmljIHByZWNpcGl0YXRpb24gZGF0YSBmb3IgdGhlIGNvdW50eSB3aGVyZSB5b3UgbGl2ZSBvciB3b3JrLiBbW0Fuc3dlcl0oaHR0cHM6Ly9iaXQubHkvM203QmNXTSldDQogDQpgYGB7ciBjaHVuazE4LCBwYWdlZC5wcmludCA9IEZBTFNFfQ0KIyMgWW91ciBhbnN3ZXIgaGVyZQ0KYGBgDQoNClwNCg==