solutions 

Overview

This notebook will demonstrate how to download Cal-Adapt data as rasters.

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.6)
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(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.1, GDAL 3.2.1, PROJ 7.2.1; sf_use_s2() is TRUE
library(stars)
Loading required package: abind
library(conflicted)
conflict_prefer("filter", "dplyr", quiet = TRUE)
conflict_prefer("count", "dplyr", quiet = TRUE)
conflict_prefer("select", "dplyr", quiet = TRUE)


Downloading Rasters

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
 
sierra_cap %>% ca_preflight(check_for = "getrst")
General issues
 - none found
Issues for downloading rasters
 - none found
plot(sierra_cap, locagrid = TRUE)


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() 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)
length(sierra_stars_lst)
[1] 1
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    


Plot

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


Subset by Dimension

Subsetting stars objects by a dimension is the counterpart to doing an attribute query in GIS. You can use dplyr filter function as you would a tibble. For example, to get the precipitation for only the 1990s:

sierra_stars_lst[[1]] %>% filter(year >= 1990, year < 2000)
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.4325525 2.126165 2.905097 3.25017 4.015097 11.33755 30150
dimension(s):

To get the values of a dimension, you can use

(x_range <- sierra_stars_lst[[1]] %>% st_get_dimension_values("x"))
 [1] -121.6562 -121.5938 -121.5312 -121.4688 -121.4062 -121.3438 -121.2812 -121.2188 -121.1562 -121.0938 -121.0312
[12] -120.9688 -120.9062 -120.8438 -120.7812 -120.7188 -120.6562 -120.5938 -120.5312 -120.4688 -120.4062 -120.3438
[23] -120.2812 -120.2188 -120.1562 -120.0938 -120.0312 -119.9688 -119.9062 -119.8438 -119.7812 -119.7188 -119.6562
[34] -119.5938 -119.5312 -119.4688 -119.4062 -119.3438 -119.2812 -119.2188 -119.1562 -119.0938 -119.0312 -118.9688
[45] -118.9062 -118.8438 -118.7812 -118.7188 -118.6562 -118.5938 -118.5312 -118.4688 -118.4062 -118.3438 -118.2812
[56] -118.2188 -118.1562 -118.0938
(yr_range <- sierra_stars_lst[[1]] %>% st_get_dimension_values("year"))
 [1] 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991
[23] 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010


You can also use square bracket notation as you would an array. Just put an extra comma at the beginning of the bracket.

sierra_stars_lst[[1]][ , , , which(yr_range >= 1990 & yr_range < 2000)]
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.4325525 2.126165 2.905097 3.25017 4.015097 11.33755 30150
dimension(s):


Spatial Selection

To spatially select, you can put a SF object in the first slot of the square bracket notation. Let’s get the precipitation history for Yosemite NP:

(ynp_bnd_sf <- st_read("https://raw.githubusercontent.com/ucanr-igis/caladaptr-res/main/geoms/ynp_bnd.geojson"))
Reading layer `ynp_bnd' from data source 
  `https://raw.githubusercontent.com/ucanr-igis/caladaptr-res/main/geoms/ynp_bnd.geojson' using driver `GeoJSON'
Simple feature collection with 1 feature and 11 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: -119.8864 ymin: 37.4947 xmax: -119.1964 ymax: 38.18515
Geodetic CRS:  WGS 84
Simple feature collection with 1 feature and 11 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: -119.8864 ymin: 37.4947 xmax: -119.1964 ymax: 38.18515
Geodetic CRS:  WGS 84
  UNIT_CODE                                                                           GIS_NOTES
1      YOSE Lands - http://landsnet.nps.gov/tractsnet/documents/YOSE/METADATA/yose_metadata.xml
               UNIT_NAME  DATE_EDIT STATE REGION GNIS_ID     UNIT_TYPE CREATED_BY
1 Yosemite National Park 2016-01-27    CA     PW  255923 National Park      Lands
                                                METADATA PARKNAME                       geometry
1 http://nrdata.nps.gov/programs/Lands/YOSE_METADATA.xml Yosemite POLYGON ((-119.8456 37.8327...
plot(ynp_bnd_sf$geometry, axes = TRUE)


To do a spatial selection, put the sf object in the first slot in the square brackets:

ynp_pr_stars <- sierra_stars_lst[[1]][ynp_bnd_sf , , , ]
ynp_pr_stars
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.9827738 2.46228 3.08287 3.32722 3.922521 8.374605 2747
dimension(s):


Plot to verify:

{plot(ynp_pr_stars %>% slice(index = 1, along = "year"), 
     axes = TRUE, main = "Average Daily Precipitation in YNP (mm), 1970", reset = FALSE)
plot(ynp_bnd_sf$geometry, border = "red", lwd = 2, add = TRUE)}


Convert Values / Raster Algebra

Math operators (like * / - +) do ‘pixelwise’ operations on stars objects, just like regular arrays.

To compute the yearly summary in inches, rather than daily average in mm, we just multiply the stars object by a conversion constant. A mm is 0.0393701 inches, so to go from daily averages in mm to annual total in inches:

(ynp_prtotal_stars <- ynp_pr_stars * 0.0393701 * 365)
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  14.12254 35.38317 44.30111 47.81244 56.36696 120.3438 2747
dimension(s):


Plot the first year to verify:

{plot(ynp_prtotal_stars %>% slice(index = 1, along = "year"), 
     axes = TRUE, main = "Total Precipitation in YNP (in), 1970", reset = FALSE)
plot(ynp_bnd_sf$geometry, border = "red", lwd = 2, add = TRUE)}


Aggregate Across Time and Space

To take the average precip per pixel for the entire time range, use st_apply(). The MARGIN argument should be the indices of the dimensions you want to keep:

ynp_prtotal_stars %>% 
  st_apply(MARGIN = 1:2, FUN = mean) %>% 
  plot(axes = TRUE, main = "Average Total Annual Precipitation in YNP (in), 1970-2010")


To take the average total precip of all pixels in the park per year, we simply change the value of MARGIN to 3 (because year is the third dimension). We also have to add na.rm = TRUE to tell the mean function to ignore all the NA values outside the park boundary:

(ynp_pr_annual_avg_stars <- ynp_prtotal_stars %>% 
  st_apply(MARGIN = 3, FUN = mean, na.rm = TRUE))
stars object with 1 dimensions and 1 attribute
attribute(s):
          Min.  1st Qu.  Median     Mean  3rd Qu.     Max.
mean  19.96104 37.36484 41.9291 47.81244 53.77562 90.80345
dimension(s):


stars objects can be converted to data frames:

ynp_pr_annual_avg_df <- ynp_pr_annual_avg_stars %>% as.data.frame
plot(ynp_pr_annual_avg_df, type="b", main = "YNP Total Precip Averaged Across Park (in)")


To plot the distribution of values, we can grab the individual values with [] (just like an array) and make a histogram:

hist(ynp_prtotal_stars, main = "Distribution of Total Annual Precip in YNP")

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)

LS0tDQp0aXRsZTogIlJhc3RlcnMiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IA0KICAgIGNzczogaHR0cHM6Ly91Y2Fuci1pZ2lzLmdpdGh1Yi5pby9jYWxhZGFwdHItcmVzL2Fzc2V0cy9uYl9jc3MwMS5jc3MNCiAgICBpbmNsdWRlczoNCiAgICAgIGJlZm9yZV9ib2R5OiBodHRwczovL3VjYW5yLWlnaXMuZ2l0aHViLmlvL2NhbGFkYXB0ci1yZXMvYXNzZXRzL25iX2hkcnNvbG4uaHRtbA0KICAgICAgYWZ0ZXJfYm9keTogaHR0cHM6Ly91Y2Fuci1pZ2lzLmdpdGh1Yi5pby9jYWxhZGFwdHItcmVzL2Fzc2V0cy9uYl9mb290ZXIwMS5odG1sDQotLS0NCg0KIyBPdmVydmlldw0KDQpUaGlzIG5vdGVib29rIHdpbGwgZGVtb25zdHJhdGUgaG93IHRvIGRvd25sb2FkIENhbC1BZGFwdCBkYXRhIGFzIHJhc3RlcnMuDQoNCiMgU2V0dXANCg0KTG9hZCBjYWxhZGFwdFIgYW5kIHRoZSBvdGhlciBwYWNrYWdlIHdlJ3JlIGdvaW5nIHRvIG5lZWQuIChJZiB5b3UgaGF2ZW4ndCBpbnN0YWxsZWQgdGhlc2UgeWV0LCBzZWUgdGhpcyBbc2V0dXAgc2NyaXB0XShodHRwczovL2dpdGh1Yi5jb20vdWNhbnItaWdpcy9jYWxhZGFwdHItcmVzL2Jsb2IvbWFpbi9kb2NzL3dvcmtzaG9wcy9jYV9pbnRyb19hcHIyMi9zY3JpcHRzL2NhbGFkYXB0cl9zZXR1cC5SKSkuIA0KDQpgYGB7ciBjaHVuazAxLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSdob2xkJ30NCmxpYnJhcnkoY2FsYWRhcHRyKQ0KbGlicmFyeSh1bml0cykNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxpYnJhcnkoc2YpDQpsaWJyYXJ5KHN0YXJzKQ0KbGlicmFyeShjb25mbGljdGVkKQ0KY29uZmxpY3RfcHJlZmVyKCJmaWx0ZXIiLCAiZHBseXIiLCBxdWlldCA9IFRSVUUpDQpjb25mbGljdF9wcmVmZXIoImNvdW50IiwgImRwbHlyIiwgcXVpZXQgPSBUUlVFKQ0KY29uZmxpY3RfcHJlZmVyKCJzZWxlY3QiLCAiZHBseXIiLCBxdWlldCA9IFRSVUUpDQpgYGANCg0KXA0KDQojIERvd25sb2FkaW5nIFJhc3RlcnMNCg0KVGhlIHNhbWUgQVBJIFJlcXVlc3Qgb2JqZWN0IGNhbiBiZSB1c2VkIHRvIGdldCByYXN0ZXIgZGF0YSBpZiB5b3UgZmVlZCBpdCBpbnRvIGBjYV9nZXRyc3Rfc3RhcnMoKWAuDQoNCkZvciBhZGRpdGlvbmFsIGluZm8gb24gZG93bmxvYWRpbmcgYW5kIGFuYWx5emluZyByYXN0ZXJzLCBzZWUgdGhlIDMgYXJ0aWNsZXMgb24gW0Rvd25sb2FkaW5nIFJhc3RlcnNdKGh0dHBzOi8vdWNhbnItaWdpcy5naXRodWIuaW8vY2FsYWRhcHRyL2FydGljbGVzL3Jhc3RlcnMtcHQxLmh0bWwpLg0KDQpCZWxvdyB3ZSBnZXQgYSByYXN0ZXIgb2Ygb2JzZXJ2ZWQgaGlzdG9yaWMgdGVtcGVyYXR1cmUgZGF0YSBmb3IgdGhlIFNpZXJyYSBjbGltYXRlIHJlZ2lvbjoNCg0KYGBge3IgY2h1bmswMiwgY2FjaGUgPSBGQUxTRX0NCnNpZXJyYV9jYXAgPC0gY2FfbG9jX2FvaXByZXNldCh0eXBlID0gImNsaW1yZWdpb25zIiwgaWRmbGQgPSAibmFtZSIsIGlkdmFsID0gIlNpZXJyYSIpICU+JSANCiAgY2FfbGl2bmVoKFRSVUUpICU+JSANCiAgY2FfcGVyaW9kKCJ5ZWFyIikgJT4lIA0KICBjYV9jdmFyKCJwciIpICU+JSANCiAgY2FfeWVhcnMoc3RhcnQgPSAxOTcwLCBlbmQgPSAyMDEwKQ0KDQpzaWVycmFfY2FwDQoNCnNpZXJyYV9jYXAgJT4lIGNhX3ByZWZsaWdodChjaGVja19mb3IgPSAiZ2V0cnN0IikNCg0KcGxvdChzaWVycmFfY2FwLCBsb2NhZ3JpZCA9IFRSVUUpDQpgYGANCg0KXA0KDQpUbyBmZXRjaCB0aGUgZGF0YSBhcyBUSUZzLCB1c2UgOg0KDQpgYGB7ciBjaHVuazAzfQ0KdGlmZl9kaXIgPC0gIi4vZGF0YSINCg0Kc2llcnJhX3RpZmZfZm4gPC0gc2llcnJhX2NhcCAlPiUgDQogIGNhX2dldHJzdF9zdGFycyhvdXRfZGlyID0gdGlmZl9kaXIsIG1hc2sgPSBUUlVFLCBxdWlldCA9IFRSVUUsIG92ZXJ3cml0ZSA9IEZBTFNFKQ0KYGBgDQoNClwNCg0KKipQcm8gVGlwOioqDQoNCiAtIHRvIGF2b2lkIGRvd25sb2FkaW5nIHRoZSBzYW1lIFRJRiBtdWx0aXBsZSB0aW1lcywgdXNlIHRoZSBzYW1lIG91dHB1dCBkaXJlY3RvcnkgYW5kIHNldCBgb3ZlcndyaXRlID0gRkFMU0VgDQogDQpcDQogDQpgY2FfZ2V0cnN0X3N0YXJzKClgIHJldHVybnMgYSB2ZWN0b3Igb2YgVElGIGZpbGVzIHRoYXQgd2VyZSBkb3dubG9hZGVkLiBUbyB3b3JrIHdpdGggdGhlbSwgeW91IG5leHQgaGF2ZSB0byBsb2FkIHRoZW0gYmFjayBpbnRvIFIgYXMgc3RhcnMgb2JqZWN0cyAoc3BhY2UtdGltZSBhcnJheXMpIHVzaW5nIGBjYV9zdGFyc19yZWFkKClgOg0KDQpgYGB7ciBjaHVuazA0LCBwYWdlZC5wcmludCA9IEZBTFNFfQ0Kc2llcnJhX3N0YXJzX2xzdCA8LSBjYV9zdGFyc19yZWFkKHNpZXJyYV90aWZmX2ZuKQ0KbGVuZ3RoKHNpZXJyYV9zdGFyc19sc3QpDQpzaWVycmFfc3RhcnNfbHN0W1sxXV0NCmBgYA0KDQpcDQoNCiMgUGxvdA0KDQpUbyBwbG90IGEgc3RhcnMgb2JqZWN0cywgeW91IGhhdmUgdG8gZGVjaWRlIHdoaWNoIGxheWVyKHMpIHRvIHBsb3QuIEluIHRoaXMgY2FzZSwgZWFjaCBsYXllciByZXByZXNlbnRzIGEgeWVhciBmcm9tIDE5NzAgdG8gMjAxMC4gQmVsb3cgd2UgcGxvdCA0IG9mIHRoZSA0MCB5ZWFyczoNCg0KYGBge3IgY2h1bmswNSwgY2FjaGUgPSBGQUxTRX0NCnBsb3Qoc2llcnJhX3N0YXJzX2xzdFtbMV1dICU+JSBzbGljZShpbmRleCA9IHNlcSgxLDQwLGxlbmd0aC5vdXQgPTQpLCBhbG9uZyA9ICJ5ZWFyIiksIA0KICAgICBheGVzID0gVFJVRSwNCiAgICAgbWFpbiA9IGF0dHJpYnV0ZXMoc2llcnJhX3N0YXJzX2xzdFtbMV1dKSRjYV9tZXRhZGF0YSRzbHVnKQ0KYGBgDQoNCk5vdCBzdXJlIHdoYXQgdGhlIHVuaXRzIGFyZT8gWW91IGNhbiBkb3VibGUtY2hlY2sgYnkgdmlld2luZyB0aGUgbWV0YWRhdGEgZm9yIHRoZSBzbHVnIGZyb20gdGhlIGNhdGFsb2c6DQoNCmBgYHtyIGNodW5rMDZ9DQpjYV9jYXRhbG9nX3NlYXJjaCgicHJfeWVhcl9saXZuZWgiKQ0KYGBgDQoNClwNCg0KIyBTdWJzZXQgYnkgRGltZW5zaW9uDQoNClN1YnNldHRpbmcgc3RhcnMgb2JqZWN0cyBieSBhIGRpbWVuc2lvbiBpcyB0aGUgY291bnRlcnBhcnQgdG8gZG9pbmcgYW4gYXR0cmlidXRlIHF1ZXJ5IGluIEdJUy4gWW91IGNhbiB1c2UgZHBseXIgZmlsdGVyIGZ1bmN0aW9uIGFzIHlvdSB3b3VsZCBhIHRpYmJsZS4gRm9yIGV4YW1wbGUsIHRvIGdldCB0aGUgcHJlY2lwaXRhdGlvbiBmb3Igb25seSB0aGUgMTk5MHM6DQoNCmBgYHtyIGNodW5rMDd9DQpzaWVycmFfc3RhcnNfbHN0W1sxXV0gJT4lIGZpbHRlcih5ZWFyID49IDE5OTAsIHllYXIgPCAyMDAwKQ0KYGBgDQoNClRvIGdldCB0aGUgdmFsdWVzIG9mIGEgZGltZW5zaW9uLCB5b3UgY2FuIHVzZQ0KDQpgYGB7ciBjaHVuazA4fQ0KKHhfcmFuZ2UgPC0gc2llcnJhX3N0YXJzX2xzdFtbMV1dICU+JSBzdF9nZXRfZGltZW5zaW9uX3ZhbHVlcygieCIpKQ0KKHlyX3JhbmdlIDwtIHNpZXJyYV9zdGFyc19sc3RbWzFdXSAlPiUgc3RfZ2V0X2RpbWVuc2lvbl92YWx1ZXMoInllYXIiKSkNCmBgYA0KDQpcDQoNCllvdSBjYW4gYWxzbyB1c2Ugc3F1YXJlIGJyYWNrZXQgbm90YXRpb24gYXMgeW91IHdvdWxkIGFuIGFycmF5LiBKdXN0IHB1dCBhbiBleHRyYSBjb21tYSBhdCB0aGUgYmVnaW5uaW5nIG9mIHRoZSBicmFja2V0Lg0KDQpgYGB7ciBjaHVuazA5fQ0Kc2llcnJhX3N0YXJzX2xzdFtbMV1dWyAsICwgLCB3aGljaCh5cl9yYW5nZSA+PSAxOTkwICYgeXJfcmFuZ2UgPCAyMDAwKV0NCmBgYA0KDQpcDQoNCiMgU3BhdGlhbCBTZWxlY3Rpb24NCg0KVG8gc3BhdGlhbGx5IHNlbGVjdCwgeW91IGNhbiBwdXQgYSBTRiBvYmplY3QgaW4gdGhlIGZpcnN0IHNsb3Qgb2YgdGhlIHNxdWFyZSBicmFja2V0IG5vdGF0aW9uLiBMZXQncyBnZXQgdGhlIHByZWNpcGl0YXRpb24gaGlzdG9yeSBmb3IgWW9zZW1pdGUgTlA6DQoNCmBgYHtyIGNodW5rMTB9DQooeW5wX2JuZF9zZiA8LSBzdF9yZWFkKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vdWNhbnItaWdpcy9jYWxhZGFwdHItcmVzL21haW4vZ2VvbXMveW5wX2JuZC5nZW9qc29uIikpDQpwbG90KHlucF9ibmRfc2YkZ2VvbWV0cnksIGF4ZXMgPSBUUlVFKQ0KYGBgDQoNClwNCg0KVG8gZG8gYSBzcGF0aWFsIHNlbGVjdGlvbiwgcHV0IHRoZSBzZiBvYmplY3QgaW4gdGhlIGZpcnN0IHNsb3QgaW4gdGhlIHNxdWFyZSBicmFja2V0czoNCg0KYGBge3IgY2h1bmsxMX0NCnlucF9wcl9zdGFycyA8LSBzaWVycmFfc3RhcnNfbHN0W1sxXV1beW5wX2JuZF9zZiAsICwgLCBdDQp5bnBfcHJfc3RhcnMNCmBgYA0KDQpcDQoNClBsb3QgdG8gdmVyaWZ5Og0KDQpgYGB7ciBjaHVuazEyfQ0Ke3Bsb3QoeW5wX3ByX3N0YXJzICU+JSBzbGljZShpbmRleCA9IDEsIGFsb25nID0gInllYXIiKSwgDQogICAgIGF4ZXMgPSBUUlVFLCBtYWluID0gIkF2ZXJhZ2UgRGFpbHkgUHJlY2lwaXRhdGlvbiBpbiBZTlAgKG1tKSwgMTk3MCIsIHJlc2V0ID0gRkFMU0UpDQpwbG90KHlucF9ibmRfc2YkZ2VvbWV0cnksIGJvcmRlciA9ICJyZWQiLCBsd2QgPSAyLCBhZGQgPSBUUlVFKX0NCmBgYA0KDQpcDQoNCiMgQ29udmVydCBWYWx1ZXMgLyBSYXN0ZXIgQWxnZWJyYQ0KDQpNYXRoIG9wZXJhdG9ycyAobGlrZSBgKiAvIC0gK2ApIGRvICdwaXhlbHdpc2UnIG9wZXJhdGlvbnMgb24gc3RhcnMgb2JqZWN0cywganVzdCBsaWtlIHJlZ3VsYXIgYXJyYXlzLg0KDQpUbyBjb21wdXRlIHRoZSB5ZWFybHkgc3VtbWFyeSBpbiBpbmNoZXMsIHJhdGhlciB0aGFuIGRhaWx5IGF2ZXJhZ2UgaW4gbW0sIHdlIGp1c3QgbXVsdGlwbHkgdGhlIHN0YXJzIG9iamVjdCBieSBhIGNvbnZlcnNpb24gY29uc3RhbnQuIEEgbW0gaXMgMC4wMzkzNzAxIGluY2hlcywgc28gdG8gZ28gZnJvbSBkYWlseSBhdmVyYWdlcyBpbiBtbSB0byBhbm51YWwgdG90YWwgaW4gaW5jaGVzOg0KDQpgYGB7ciBjaHVuazEzfQ0KKHlucF9wcnRvdGFsX3N0YXJzIDwtIHlucF9wcl9zdGFycyAqIDAuMDM5MzcwMSAqIDM2NSkNCmBgYA0KDQpcDQoNClBsb3QgdGhlIGZpcnN0IHllYXIgdG8gdmVyaWZ5Og0KDQpgYGB7ciBjaHVuazE0fQ0Ke3Bsb3QoeW5wX3BydG90YWxfc3RhcnMgJT4lIHNsaWNlKGluZGV4ID0gMSwgYWxvbmcgPSAieWVhciIpLCANCiAgICAgYXhlcyA9IFRSVUUsIG1haW4gPSAiVG90YWwgUHJlY2lwaXRhdGlvbiBpbiBZTlAgKGluKSwgMTk3MCIsIHJlc2V0ID0gRkFMU0UpDQpwbG90KHlucF9ibmRfc2YkZ2VvbWV0cnksIGJvcmRlciA9ICJyZWQiLCBsd2QgPSAyLCBhZGQgPSBUUlVFKX0NCg0KYGBgDQoNClwNCg0KIyBBZ2dyZWdhdGUgQWNyb3NzIFRpbWUgYW5kIFNwYWNlDQoNClRvIHRha2UgdGhlIGF2ZXJhZ2UgcHJlY2lwIHBlciBwaXhlbCBmb3IgdGhlIGVudGlyZSB0aW1lIHJhbmdlLCB1c2UgYHN0X2FwcGx5KClgLiBUaGUgYE1BUkdJTmAgYXJndW1lbnQgc2hvdWxkIGJlIHRoZSBpbmRpY2VzIG9mIHRoZSBkaW1lbnNpb25zIHlvdSB3YW50IHRvICprZWVwKjoNCg0KYGBge3IgY2h1bmsxNX0NCnlucF9wcnRvdGFsX3N0YXJzICU+JSANCiAgc3RfYXBwbHkoTUFSR0lOID0gMToyLCBGVU4gPSBtZWFuKSAlPiUgDQogIHBsb3QoYXhlcyA9IFRSVUUsIG1haW4gPSAiQXZlcmFnZSBUb3RhbCBBbm51YWwgUHJlY2lwaXRhdGlvbiBpbiBZTlAgKGluKSwgMTk3MC0yMDEwIikNCmBgYA0KDQpcDQoNClRvIHRha2UgdGhlIGF2ZXJhZ2UgdG90YWwgcHJlY2lwIG9mIGFsbCBwaXhlbHMgaW4gdGhlIHBhcmsgcGVyIHllYXIsIHdlIHNpbXBseSBjaGFuZ2UgdGhlIHZhbHVlIG9mIGBNQVJHSU5gIHRvIDMgKGJlY2F1c2UgeWVhciBpcyB0aGUgdGhpcmQgZGltZW5zaW9uKS4gV2UgYWxzbyBoYXZlIHRvIGFkZCBgbmEucm0gPSBUUlVFYCB0byB0ZWxsIHRoZSBgbWVhbmAgZnVuY3Rpb24gdG8gaWdub3JlIGFsbCB0aGUgTkEgdmFsdWVzIG91dHNpZGUgdGhlIHBhcmsgYm91bmRhcnk6DQoNCmBgYHtyIGNodW5rMTZ9DQooeW5wX3ByX2FubnVhbF9hdmdfc3RhcnMgPC0geW5wX3BydG90YWxfc3RhcnMgJT4lIA0KICBzdF9hcHBseShNQVJHSU4gPSAzLCBGVU4gPSBtZWFuLCBuYS5ybSA9IFRSVUUpKQ0KYGBgDQoNClwNCg0Kc3RhcnMgb2JqZWN0cyBjYW4gYmUgY29udmVydGVkIHRvIGRhdGEgZnJhbWVzOg0KDQpgYGB7ciBjaHVuazE3fQ0KeW5wX3ByX2FubnVhbF9hdmdfZGYgPC0geW5wX3ByX2FubnVhbF9hdmdfc3RhcnMgJT4lIGFzLmRhdGEuZnJhbWUNCnBsb3QoeW5wX3ByX2FubnVhbF9hdmdfZGYsIHR5cGU9ImIiLCBtYWluID0gIllOUCBUb3RhbCBQcmVjaXAgQXZlcmFnZWQgQWNyb3NzIFBhcmsgKGluKSIpDQpgYGANCg0KXA0KDQpUbyBwbG90IHRoZSBkaXN0cmlidXRpb24gb2YgdmFsdWVzLCB3ZSBjYW4gZ3JhYiB0aGUgaW5kaXZpZHVhbCB2YWx1ZXMgd2l0aCBgW11gIChqdXN0IGxpa2UgYW4gYXJyYXkpIGFuZCBtYWtlIGEgaGlzdG9ncmFtOg0KDQpgYGB7ciBjaHVuazE4fQ0KaGlzdCh5bnBfcHJ0b3RhbF9zdGFycywgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgVG90YWwgQW5udWFsIFByZWNpcCBpbiBZTlAiKQ0KYGBgDQoNCg0KVGhlcmUgaXMgYSAqKmxvdCoqIG1vcmUgeW91IGNhbiBkbyB3aXRoIHJhc3RlcnMsIGluY2x1ZGluZyBwaXhlbCBzdW1tYXJpZXMsIGNvbWJpbmluZyB0aGVtIGludG8gaGlnaGVyIGRpbWVuc2lvbmFsIGRhdGEgY3ViZXMsIHNwYXRpYWxseSBtb3NhaWNpbmcgdGhlbSwgZXRjLiBGb3IgbW9yZSBpbmZvLCBzZWUgdGhlIFJhc3RlcnMgYXJ0aWNsZXMgb24gdGhlIFt3ZWJzaXRlXShodHRwczovL3VjYW5yLWlnaXMuZ2l0aHViLmlvL2NhbGFkYXB0ci8pLg0KIA0KXA0KDQojIyMjIFlvdXIgVHVybg0KDQpEb3dubG9hZCBoaXN0b3JpYyBwcmVjaXBpdGF0aW9uIGRhdGEgZm9yIHRoZSBjb3VudHkgd2hlcmUgeW91IGxpdmUgb3Igd29yay4gW1tBbnN3ZXJdKGh0dHBzOi8vYml0Lmx5LzNtN0JjV00pXQ0KIA0KYGBge3IgY2h1bmsxOSwgcGFnZWQucHJpbnQgPSBGQUxTRSwgY2FjaGU9RkFMU0V9DQojIyBFeGFtcGxlOiBNZW5kb2Npbm8gQ291bnR5DQoNCmNhX2FvaXByZXNldF9nZW9tKCJjb3VudGllcyIsIHF1aWV0ID0gVFJVRSkgJT4lIA0KICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lIA0KICBmaWx0ZXIobmFtZSA9PSAiTWVuZG9jaW5vIikgJT4lIA0KICBzZWxlY3QobmFtZSwgc3RhdGVfbmFtZSwgZmlwcykNCg0KbWVuZG9jaW5vX2NhcCA8LSBjYV9sb2NfYW9pcHJlc2V0KHR5cGUgPSAiY291bnRpZXMiLCBpZGZsZCA9ICJmaXBzIiwgaWR2YWwgPSAiMDYwNDUiKSAlPiUgDQogIGNhX2xpdm5laChUUlVFKSAlPiUgDQogIGNhX3BlcmlvZCgieWVhciIpICU+JSANCiAgY2FfY3ZhcigicHIiKSAlPiUgDQogIGNhX3llYXJzKHN0YXJ0ID0gMTk3MCwgZW5kID0gMjAxMCkNCg0KcGxvdChtZW5kb2Npbm9fY2FwKQ0KDQptZW5kb2Npbm9fZm4gPC0gbWVuZG9jaW5vX2NhcCAlPiUgDQogIGNhX2dldHJzdF9zdGFycyhvdXRfZGlyID0gdGVtcGRpcigpLCBtYXNrID0gVFJVRSwgcXVpZXQgPSBUUlVFLCBvdmVyd3JpdGUgPSBGQUxTRSkNCg0KbWVuZG9jaW5vX3N0YXJzX2xzdCA8LSBjYV9zdGFyc19yZWFkKG1lbmRvY2lub19mbikgDQogIA0KbWVuZG9jaW5vX3N0YXJzX2xzdFtbMV1dDQoNCnBsb3QobWVuZG9jaW5vX3N0YXJzX2xzdFtbMV1dICU+JSBzbGljZShpbmRleCA9IHNlcSgxLDQwLGxlbmd0aC5vdXQgPTQpLCBhbG9uZyA9ICJ5ZWFyIiksIA0KICAgICBheGVzID0gVFJVRSwNCiAgICAgbWFpbiA9IGF0dHJpYnV0ZXMobWVuZG9jaW5vX3N0YXJzX2xzdFtbMV1dKSRjYV9tZXRhZGF0YSRzbHVnKQ0KYGBgDQogDQoNCg==