In this Notebook we’ll use geoprocessing functions from sf to identify Yosemite Points-of-Interest that fall within the Upper Merced Subbasin.

Setup

Load the packages we’ll need and set tmap mode to ‘plot’:

library(sf)
library(tmap)
tmap_mode("plot")

Load dplyr and set name conflict preferences:

library(dplyr)

## Load the conflicted package
library(conflicted)

# Set conflict preferences
conflict_prefer("filter", "dplyr", quiet = TRUE)
conflict_prefer("count", "dplyr", quiet = TRUE)
conflict_prefer("select", "dplyr", quiet = TRUE)
conflict_prefer("arrange", "dplyr", quiet = TRUE)


Practice Querying with Sample Data

Import Practice Data

First we import some practice data:

circles_sf <- st_read("./data/test_circles.geojson")
Reading layer `test_circles' from data source `D:\Workshops\R-Spatial\rspatial_mod\outputs\rspatial_data\data\test_circles.geojson' using driver `GeoJSON'
Simple feature collection with 3 features and 1 field
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: -0.5 ymin: -0.5 xmax: 4 ymax: 3
Projected CRS: WGS 84 / UTM zone 11N
circles_sf
Simple feature collection with 3 features and 1 field
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: -0.5 ymin: -0.5 xmax: 4 ymax: 3
Projected CRS: WGS 84 / UTM zone 11N
  circle_id                       geometry
1         A POLYGON ((1.5 0.5, 1.499391...
2         B POLYGON ((4 0.5, 3.999391 0...
3         C POLYGON ((4 2, 3.999391 2.0...
pts_sf <- st_read("./data/test_pts.geojson")
Reading layer `test_pts' from data source `D:\Workshops\R-Spatial\rspatial_mod\outputs\rspatial_data\data\test_pts.geojson' using driver `GeoJSON'
Simple feature collection with 120 features and 1 field
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: -0.47835 ymin: -0.4040069 xmax: 3.933032 ymax: 2.985598
Projected CRS: WGS 84 / UTM zone 11N
pts_sf
Simple feature collection with 120 features and 1 field
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: -0.47835 ymin: -0.4040069 xmax: 3.933032 ymax: 2.985598
Projected CRS: WGS 84 / UTM zone 11N
First 10 features:
   pt_id                     geometry
1      1 POINT (-0.4430744 0.9685033)
2      2  POINT (0.3676537 0.2035441)
3      3   POINT (1.535087 0.9823107)
4      4   POINT (0.8340309 2.645155)
5      5     POINT (2.901864 1.73253)
6      6   POINT (3.663181 0.7940655)
7      7   POINT (0.8618392 2.640156)
8      8    POINT (2.832866 2.870026)
9      9   POINT (3.046212 0.8546275)
10    10  POINT (-0.349015 0.7595088)


Plot the points on top of the circles:

tm_shape(circles_sf) +
  tm_borders(col = palette()[2:4] ) +
  tm_text("circle_id") +
tm_shape(pts_sf) +
  tm_dots(col = "dimgray") +
tm_grid(labels.show = TRUE, lines = FALSE)

Identify the points in Circle A

Next we identify the points in circle A using a spatial predicate function (st_intersects). We could also copy the points in Cirlce A with st_intersection(), but there are times when you don’t need or want to make copies of the data.

circle_a_sf <- circles_sf %>% filter(circle_id == "A")

pt_in_circle_a_yn_mat <- pts_sf %>% 
  st_intersects(circle_a_sf,
                sparse = FALSE)
  
head(pt_in_circle_a_yn_mat)
      [,1]
[1,] FALSE
[2,]  TRUE
[3,] FALSE
[4,] FALSE
[5,] FALSE
[6,] FALSE

Since we have a column of TRUE/FALSE values, we can subset those features using the dplyr filter function:

## Copy the points in circle A to a new object. Note in the filter expression
## we use square bracket notation to pull out the first column of the matrix 

a_pts_sf <- pts_sf %>% 
  filter(pt_in_circle_a_yn_mat[,1])

## Plot to verify
tm_shape(circles_sf) +
  tm_borders(col = palette()[2:4] ) +
  tm_text("circle_id") +
tm_shape(a_pts_sf) +
  tm_dots(col = "red", size = 0.1) +
tm_grid(labels.show = TRUE, lines = FALSE)


CHALLENGE: How many points in cirlce B?

Answer

## Your answer here


CHALLENGE: Plot the points that fall within Circle B and Circle C

Answer

## Your answer here


CHALLENGE: How many points don’t fall in any circle?

Answer

## Your answer here


CHALLENGE: Plot the points that lie within 0.25 map units of a circle, but are not contained within the circle

Answer

## Your answer here


Plot the Points of Interest that Fall within the Upper Merced Subbasin

Next, we’ll apply what we learned to find the Yosemite Points-of-Interest that fall within the Upper Merced HUB-8 Subbasin.

Import the Watersheds

Start by importing the planning watershed units from calw221:

## Import the planning watersheds
gpkg_watershd_fn <- "./data/yose_watersheds.gpkg"
yose_watersheds_sf <- st_read(gpkg_watershd_fn, layer="calw221") 
Reading layer `calw221' from data source `D:\Workshops\R-Spatial\rspatial_mod\outputs\rspatial_data\data\yose_watersheds.gpkg' using driver `GPKG'
Simple feature collection with 127 features and 10 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: 1383.82 ymin: -61442.93 xmax: 81596.71 ymax: 26405.66
Projected CRS: NAD83 / California Albers
## View attribute table
yose_watersheds_sf %>% st_drop_geometry() %>% slice(1:6)

## Plot results
tmap_mode("plot")
tmap mode set to plotting
tm_shape(yose_watersheds_sf) + 
  tm_polygons("MAP_COLORS", palette = "Pastel1") 

Note the keyword MAP_COLORS tells tmap to select colors at random such that adjacent polygons have different colors.


Lump the Planning Watersheds into HUC-8 Subbasins

Next we’ll group the little planning watersheds into bigger “HUC-8” subbasins. This is easy because there is a column for the HUC 8 id number (HUC_8) and name (HUC_8_NAME).

yose_huc8_sf <- yose_watersheds_sf %>% 
  group_by(HUC_8) %>% 
  summarise(HUC_8_NAME = first(HUC_8_NAME), num_pws = n())

yose_huc8_sf
Simple feature collection with 6 features and 3 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: 1383.82 ymin: -61442.93 xmax: 81596.71 ymax: 26405.66
Projected CRS: NAD83 / California Albers

A convenient feature of group_by() is that when applied to a simple feature data frame it will also spatially aggregate (i.e., union) the features based on common values in the grouping column. Plot to verify:

epsg_utm11n_nad83 <- 26911
yose_bnd_utm <- st_read(dsn="./data", layer="yose_boundary") %>% 
  st_transform(epsg_utm11n_nad83)
Reading layer `yose_boundary' from data source `D:\Workshops\R-Spatial\rspatial_mod\outputs\rspatial_data\data' using driver `ESRI Shapefile'
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:  North_American_Datum_1983
tm_shape(yose_huc8_sf) + 
  tm_polygons("MAP_COLORS", palette = "Pastel1") +
  tm_text("HUC_8_NAME", size = 0.7) +
tm_shape(yose_bnd_utm) +
  tm_borders(col = "red", lwd = 2)

Extract Upper Merced HUC-8 Subbasin

Next, pull out just the Upper Merced subbasin and save it as a separate object:

## Filter out just the Upper Merced Subbasin
merced_huc8_sf <- yose_huc8_sf %>% 
  filter(HUC_8_NAME == "UPPER_MERCED")
merced_huc8_sf
Simple feature collection with 1 feature and 3 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: 2242.555 ymin: -61305.46 xmax: 65367.14 ymax: -12528.6
Projected CRS: NAD83 / California Albers


Import the Points-of-Interest

Import the Yosemite POIs:

## Import points of interest
yose_poi_utm <- st_read(dsn="./data", layer="yose_poi") %>% 
  select(OBJECTID, POINAME, POILABEL, POITYPE)
Reading layer `yose_poi' from data source `D:\Workshops\R-Spatial\rspatial_mod\outputs\rspatial_data\data' using driver `ESRI Shapefile'
Simple feature collection with 2720 features and 30 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: 246416.2 ymin: 4153717 xmax: 301510.7 ymax: 4208419
Projected CRS: NAD83 / UTM zone 11N


Identify Intersecting Points-of-Interest

Find out which POIs intersect the Upper Merced subbasin with st_intersects():

try(merced_poi <- yose_poi_utm %>% st_intersects(merced_watershed))
Error in st_geos_binop("intersects", x, y, sparse = sparse, prepared = prepared,  : 
  object 'merced_watershed' not found

Oh no - ERROR message! Spatial querying requires features to be in the same CRS!


To fix this, we can project the Merced HUC-8 layer (which is in CA Albers) to match the POIs (which are UTM):

merced_huc8_utm_sf <- merced_huc8_sf %>% 
  st_transform(st_crs(yose_poi_utm))


Try the intersection test again:

yose_poi_merced_mat <- yose_poi_utm %>% st_intersects(merced_huc8_utm_sf, sparse=FALSE)
head(yose_poi_merced_mat)
     [,1]
[1,] TRUE
[2,] TRUE
[3,] TRUE
[4,] TRUE
[5,] TRUE
[6,] TRUE


CHALLENGE: How many points-of-interest fall within the Upper Merced subbasin?

Hint 1: This is equivalent to asking how many TRUE values there are in the first column of yose_poi_merced_mat.

Hint 2: To get the first column of a matrix x, use x[ , 1].

Answer

## Your answer here


Subset the POIs that fall within the Upper Merced Subbasin

To extract the POIs in the Upper Merced subbasin, we can feed the first column of yose_poi_merced_mat into filter() (which expects TRUE/FALSE values):

## Extract the points that intersect the subbasin to a new sf object
merced_poi_utm <- yose_poi_utm %>% 
  filter(yose_poi_merced_mat[,1])


Plot the Intersection

Plot to visually verify the results:

## Plot
tm_shape(merced_huc8_utm_sf) +
  tm_polygons(col = "khaki") +
tm_shape(yose_poi_utm) +
  tm_dots(size = 0.1, col = "gray30") +
tm_shape(merced_poi_utm) +
  tm_dots(size = 0.1, col = "dodgerblue")


CHALLENGE: How many YNP POIs fall witin the Upper Tuolumne HUC-8 subbasin?

Answer

## Your answer here

End

Congratulations, you’ve completed another Notebook!

To view your Notebook at HTML, save it (again), then click the ‘Preview’ button in the RStudio toolbar.

LS0tDQp0aXRsZTogIlNwYXRpYWwgUXVlcmllczogRmluZCBZb3NlbWl0ZSBQT0lzIGluIHRoZSBVcGVlciBNZXJjZWQgU3ViYmFzaW4iDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCi0tLQ0KDQpgYGB7Y3NzIGVjaG8gPSBGQUxTRX0NCmgxLGgyIHtmb250LXdlaWdodDpib2xkO30NCmBgYA0KDQoNCkluIHRoaXMgTm90ZWJvb2sgd2UnbGwgdXNlIGdlb3Byb2Nlc3NpbmcgZnVuY3Rpb25zIGZyb20gYHNmYCB0byBpZGVudGlmeSBZb3NlbWl0ZSBQb2ludHMtb2YtSW50ZXJlc3QgdGhhdCBmYWxsIHdpdGhpbiB0aGUgKipVcHBlciBNZXJjZWQgU3ViYmFzaW4qKi4gDQoNCiMjIFNldHVwDQoNCkxvYWQgdGhlIHBhY2thZ2VzIHdlJ2xsIG5lZWQgYW5kIHNldCB0bWFwIG1vZGUgdG8gJ3Bsb3QnOg0KDQpgYGB7ciBjaHVuazAxLCBtZXNzYWdlID0gRkFMU0V9DQpsaWJyYXJ5KHNmKQ0KbGlicmFyeSh0bWFwKQ0KdG1hcF9tb2RlKCJwbG90IikNCmBgYA0KDQpMb2FkIGBkcGx5cmAgYW5kIHNldCBuYW1lIGNvbmZsaWN0IHByZWZlcmVuY2VzOg0KDQpgYGB7ciBjaHVuazAyLCBtZXNzYWdlID0gRkFMU0V9DQpsaWJyYXJ5KGRwbHlyKQ0KDQojIyBMb2FkIHRoZSBjb25mbGljdGVkIHBhY2thZ2UNCmxpYnJhcnkoY29uZmxpY3RlZCkNCg0KIyBTZXQgY29uZmxpY3QgcHJlZmVyZW5jZXMNCmNvbmZsaWN0X3ByZWZlcigiZmlsdGVyIiwgImRwbHlyIiwgcXVpZXQgPSBUUlVFKQ0KY29uZmxpY3RfcHJlZmVyKCJjb3VudCIsICJkcGx5ciIsIHF1aWV0ID0gVFJVRSkNCmNvbmZsaWN0X3ByZWZlcigic2VsZWN0IiwgImRwbHlyIiwgcXVpZXQgPSBUUlVFKQ0KY29uZmxpY3RfcHJlZmVyKCJhcnJhbmdlIiwgImRwbHlyIiwgcXVpZXQgPSBUUlVFKQ0KYGBgDQoNClwNCg0KIyBQcmFjdGljZSBRdWVyeWluZyB3aXRoIFNhbXBsZSBEYXRhDQoNCiMjIEltcG9ydCBQcmFjdGljZSBEYXRhDQoNCkZpcnN0IHdlIGltcG9ydCBzb21lIHByYWN0aWNlIGRhdGE6DQoNCmBgYHtyIGNodW5rMDN9DQpjaXJjbGVzX3NmIDwtIHN0X3JlYWQoIi4vZGF0YS90ZXN0X2NpcmNsZXMuZ2VvanNvbiIpDQpjaXJjbGVzX3NmDQoNCnB0c19zZiA8LSBzdF9yZWFkKCIuL2RhdGEvdGVzdF9wdHMuZ2VvanNvbiIpDQpwdHNfc2YNCmBgYA0KDQpcDQoNClBsb3QgdGhlIHBvaW50cyBvbiB0b3Agb2YgdGhlIGNpcmNsZXM6DQoNCmBgYHtyIGNodW5rMDQsIHdhcm5pbmcgPSBGQUxTRX0NCnRtX3NoYXBlKGNpcmNsZXNfc2YpICsNCiAgdG1fYm9yZGVycyhjb2wgPSBwYWxldHRlKClbMjo0XSApICsNCiAgdG1fdGV4dCgiY2lyY2xlX2lkIikgKw0KdG1fc2hhcGUocHRzX3NmKSArDQogIHRtX2RvdHMoY29sID0gImRpbWdyYXkiKSArDQp0bV9ncmlkKGxhYmVscy5zaG93ID0gVFJVRSwgbGluZXMgPSBGQUxTRSkNCmBgYA0KDQojIyBJZGVudGlmeSB0aGUgcG9pbnRzIGluIENpcmNsZSBBDQoNCk5leHQgd2UgaWRlbnRpZnkgdGhlIHBvaW50cyBpbiBjaXJjbGUgQSB1c2luZyBhIHNwYXRpYWwgcHJlZGljYXRlIGZ1bmN0aW9uIChzdF9pbnRlcnNlY3RzKS4gV2UgY291bGQgYWxzbyBjb3B5IHRoZSBwb2ludHMgaW4gQ2lybGNlIEEgd2l0aCBzdF9pbnRlcnNlY3Rpb24oKSwgYnV0IHRoZXJlIGFyZSB0aW1lcyB3aGVuIHlvdSBkb24ndCBuZWVkIG9yIHdhbnQgdG8gbWFrZSBjb3BpZXMgb2YgdGhlIGRhdGEuDQoNCmBgYHtyIGNodW5rMDV9DQpjaXJjbGVfYV9zZiA8LSBjaXJjbGVzX3NmICU+JSBmaWx0ZXIoY2lyY2xlX2lkID09ICJBIikNCg0KcHRfaW5fY2lyY2xlX2FfeW5fbWF0IDwtIHB0c19zZiAlPiUgDQogIHN0X2ludGVyc2VjdHMoY2lyY2xlX2Ffc2YsDQogICAgICAgICAgICAgICAgc3BhcnNlID0gRkFMU0UpDQogIA0KaGVhZChwdF9pbl9jaXJjbGVfYV95bl9tYXQpDQpgYGANCg0KU2luY2Ugd2UgaGF2ZSBhIGNvbHVtbiBvZiBUUlVFL0ZBTFNFIHZhbHVlcywgd2UgY2FuIHN1YnNldCB0aG9zZSBmZWF0dXJlcyB1c2luZyB0aGUgZHBseXIgYGZpbHRlcmAgZnVuY3Rpb246DQoNCmBgYHtyIGNodW5rMDYsIHdhcm5pbmcgPSBGQUxTRX0NCiMjIENvcHkgdGhlIHBvaW50cyBpbiBjaXJjbGUgQSB0byBhIG5ldyBvYmplY3QuIE5vdGUgaW4gdGhlIGZpbHRlciBleHByZXNzaW9uDQojIyB3ZSB1c2Ugc3F1YXJlIGJyYWNrZXQgbm90YXRpb24gdG8gcHVsbCBvdXQgdGhlIGZpcnN0IGNvbHVtbiBvZiB0aGUgbWF0cml4IA0KDQphX3B0c19zZiA8LSBwdHNfc2YgJT4lIA0KICBmaWx0ZXIocHRfaW5fY2lyY2xlX2FfeW5fbWF0WywxXSkNCg0KIyMgUGxvdCB0byB2ZXJpZnkNCnRtX3NoYXBlKGNpcmNsZXNfc2YpICsNCiAgdG1fYm9yZGVycyhjb2wgPSBwYWxldHRlKClbMjo0XSApICsNCiAgdG1fdGV4dCgiY2lyY2xlX2lkIikgKw0KdG1fc2hhcGUoYV9wdHNfc2YpICsNCiAgdG1fZG90cyhjb2wgPSAicmVkIiwgc2l6ZSA9IDAuMSkgKw0KdG1fZ3JpZChsYWJlbHMuc2hvdyA9IFRSVUUsIGxpbmVzID0gRkFMU0UpDQpgYGANCg0KXA0KDQojIyBDSEFMTEVOR0U6IEhvdyBtYW55IHBvaW50cyBpbiBjaXJsY2UgQj8NCg0KW0Fuc3dlcl0oaHR0cHM6Ly9iaXQubHkvM3h3azB4VikNCg0KYGBge3IgY2h1bmswN30NCiMjIFlvdXIgYW5zd2VyIGhlcmUNCmBgYA0KDQpcDQoNCiMjIENIQUxMRU5HRTogUGxvdCB0aGUgcG9pbnRzIHRoYXQgZmFsbCB3aXRoaW4gQ2lyY2xlIEIgKmFuZCogQ2lyY2xlIEMNCg0KW0Fuc3dlcl0oaHR0cHM6Ly9iaXQubHkvM3l2Sms4SCkNCg0KYGBge3IgY2h1bmswOH0NCiMjIFlvdXIgYW5zd2VyIGhlcmUNCmBgYA0KDQpcDQoNCiMjIENIQUxMRU5HRTogSG93IG1hbnkgcG9pbnRzIGRvbid0IGZhbGwgaW4gYW55IGNpcmNsZT8NCg0KW0Fuc3dlcl0oaHR0cHM6Ly9iaXQubHkvMlZDVWZ6MykNCg0KYGBge3IgY2h1bmswOX0NCiMjIFlvdXIgYW5zd2VyIGhlcmUNCmBgYA0KDQpcDQoNCiMjIENIQUxMRU5HRTogUGxvdCB0aGUgcG9pbnRzIHRoYXQgbGllIHdpdGhpbiAwLjI1IG1hcCB1bml0cyBvZiBhIGNpcmNsZSwgYnV0IGFyZSBub3QgY29udGFpbmVkIHdpdGhpbiB0aGUgY2lyY2xlDQoNCltBbnN3ZXJdKGh0dHBzOi8vYml0Lmx5LzN4c1hneVIpDQoNCmBgYHtyIGNodW5rMTF9DQojIyBZb3VyIGFuc3dlciBoZXJlDQpgYGANCg0KXA0KDQojIFBsb3QgdGhlIFBvaW50cyBvZiBJbnRlcmVzdCB0aGF0IEZhbGwgd2l0aGluIHRoZSBVcHBlciBNZXJjZWQgU3ViYmFzaW4NCg0KTmV4dCwgd2UnbGwgYXBwbHkgd2hhdCB3ZSBsZWFybmVkIHRvIGZpbmQgdGhlIFlvc2VtaXRlIFBvaW50cy1vZi1JbnRlcmVzdCB0aGF0IGZhbGwgd2l0aGluIHRoZSBVcHBlciBNZXJjZWQgSFVCLTggU3ViYmFzaW4uDQoNCiMjIEltcG9ydCB0aGUgV2F0ZXJzaGVkcw0KDQpTdGFydCBieSBpbXBvcnRpbmcgdGhlIHBsYW5uaW5nIHdhdGVyc2hlZCB1bml0cyBmcm9tIFtjYWx3MjIxXShodHRwczovL2ZyYXAuZmlyZS5jYS5nb3YvbWFwcGluZy9naXMtZGF0YS8pe3RhcmdldD0iX2JsYW5rIiByZWw9Im5vb3BlbmVyIn06DQoNCmBgYHtyIGNodW5rMTN9DQojIyBJbXBvcnQgdGhlIHBsYW5uaW5nIHdhdGVyc2hlZHMNCmdwa2dfd2F0ZXJzaGRfZm4gPC0gIi4vZGF0YS95b3NlX3dhdGVyc2hlZHMuZ3BrZyINCnlvc2Vfd2F0ZXJzaGVkc19zZiA8LSBzdF9yZWFkKGdwa2dfd2F0ZXJzaGRfZm4sIGxheWVyPSJjYWx3MjIxIikgDQoNCiMjIFZpZXcgYXR0cmlidXRlIHRhYmxlDQp5b3NlX3dhdGVyc2hlZHNfc2YgJT4lIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUgc2xpY2UoMTo2KQ0KDQojIyBQbG90IHJlc3VsdHMNCnRtYXBfbW9kZSgicGxvdCIpDQp0bV9zaGFwZSh5b3NlX3dhdGVyc2hlZHNfc2YpICsgDQogIHRtX3BvbHlnb25zKCJNQVBfQ09MT1JTIiwgcGFsZXR0ZSA9ICJQYXN0ZWwxIikgDQpgYGANCg0KTm90ZSB0aGUga2V5d29yZCBgTUFQX0NPTE9SU2AgdGVsbHMgdG1hcCB0byBzZWxlY3QgY29sb3JzIGF0IHJhbmRvbSBzdWNoIHRoYXQgYWRqYWNlbnQgcG9seWdvbnMgaGF2ZSBkaWZmZXJlbnQgY29sb3JzLg0KDQpcDQoNCiMjIEx1bXAgdGhlIFBsYW5uaW5nIFdhdGVyc2hlZHMgaW50byBIVUMtOCBTdWJiYXNpbnMNCg0KTmV4dCB3ZSdsbCBncm91cCB0aGUgbGl0dGxlIHBsYW5uaW5nIHdhdGVyc2hlZHMgaW50byBiaWdnZXIgIkhVQy04IiBzdWJiYXNpbnMuIFRoaXMgaXMgZWFzeSBiZWNhdXNlIHRoZXJlIGlzIGEgY29sdW1uIGZvciB0aGUgSFVDIDggaWQgbnVtYmVyIChgSFVDXzhgKSBhbmQgbmFtZSAoYEhVQ184X05BTUVgKS4NCg0KYGBge3IgY2h1bmsxNH0NCnlvc2VfaHVjOF9zZiA8LSB5b3NlX3dhdGVyc2hlZHNfc2YgJT4lIA0KICBncm91cF9ieShIVUNfOCkgJT4lIA0KICBzdW1tYXJpc2UoSFVDXzhfTkFNRSA9IGZpcnN0KEhVQ184X05BTUUpLCBudW1fcHdzID0gbigpKQ0KDQp5b3NlX2h1Yzhfc2YNCmBgYA0KDQpBIGNvbnZlbmllbnQgZmVhdHVyZSBvZiBgZ3JvdXBfYnkoKWAgaXMgdGhhdCB3aGVuIGFwcGxpZWQgdG8gYSBzaW1wbGUgZmVhdHVyZSBkYXRhIGZyYW1lIGl0IHdpbGwgYWxzbyBzcGF0aWFsbHkgYWdncmVnYXRlIChpLmUuLCB1bmlvbikgdGhlIGZlYXR1cmVzIGJhc2VkIG9uIGNvbW1vbiB2YWx1ZXMgaW4gdGhlIGdyb3VwaW5nIGNvbHVtbi4gUGxvdCB0byB2ZXJpZnk6DQoNCmBgYHtyIGNodW5rMTV9DQplcHNnX3V0bTExbl9uYWQ4MyA8LSAyNjkxMQ0KeW9zZV9ibmRfdXRtIDwtIHN0X3JlYWQoZHNuPSIuL2RhdGEiLCBsYXllcj0ieW9zZV9ib3VuZGFyeSIpICU+JSANCiAgc3RfdHJhbnNmb3JtKGVwc2dfdXRtMTFuX25hZDgzKQ0KDQp0bV9zaGFwZSh5b3NlX2h1Yzhfc2YpICsgDQogIHRtX3BvbHlnb25zKCJNQVBfQ09MT1JTIiwgcGFsZXR0ZSA9ICJQYXN0ZWwxIikgKw0KICB0bV90ZXh0KCJIVUNfOF9OQU1FIiwgc2l6ZSA9IDAuNykgKw0KdG1fc2hhcGUoeW9zZV9ibmRfdXRtKSArDQogIHRtX2JvcmRlcnMoY29sID0gInJlZCIsIGx3ZCA9IDIpDQpgYGANCg0KIyMgRXh0cmFjdCBVcHBlciBNZXJjZWQgSFVDLTggU3ViYmFzaW4NCg0KTmV4dCwgcHVsbCBvdXQganVzdCB0aGUgVXBwZXIgTWVyY2VkIHN1YmJhc2luIGFuZCBzYXZlIGl0IGFzIGEgc2VwYXJhdGUgb2JqZWN0Og0KDQpgYGB7ciBjaHVuazE2fQ0KIyMgRmlsdGVyIG91dCBqdXN0IHRoZSBVcHBlciBNZXJjZWQgU3ViYmFzaW4NCm1lcmNlZF9odWM4X3NmIDwtIHlvc2VfaHVjOF9zZiAlPiUgDQogIGZpbHRlcihIVUNfOF9OQU1FID09ICJVUFBFUl9NRVJDRUQiKQ0KbWVyY2VkX2h1Yzhfc2YNCmBgYA0KDQpcDQoNCiMjIEltcG9ydCB0aGUgUG9pbnRzLW9mLUludGVyZXN0DQoNCkltcG9ydCB0aGUgWW9zZW1pdGUgUE9JczoNCg0KYGBge3IgY2h1bmsxN30NCiMjIEltcG9ydCBwb2ludHMgb2YgaW50ZXJlc3QNCnlvc2VfcG9pX3V0bSA8LSBzdF9yZWFkKGRzbj0iLi9kYXRhIiwgbGF5ZXI9Inlvc2VfcG9pIikgJT4lIA0KICBzZWxlY3QoT0JKRUNUSUQsIFBPSU5BTUUsIFBPSUxBQkVMLCBQT0lUWVBFKQ0KYGBgDQoNClwNCg0KIyMgSWRlbnRpZnkgSW50ZXJzZWN0aW5nIFBvaW50cy1vZi1JbnRlcmVzdA0KDQpGaW5kIG91dCB3aGljaCBQT0lzIGludGVyc2VjdCB0aGUgVXBwZXIgTWVyY2VkIHN1YmJhc2luIHdpdGggYHN0X2ludGVyc2VjdHMoKWA6DQoNCmBgYHtyIGNodW5rMTh9DQp0cnkobWVyY2VkX3BvaSA8LSB5b3NlX3BvaV91dG0gJT4lIHN0X2ludGVyc2VjdHMobWVyY2VkX3dhdGVyc2hlZCkpDQpgYGANCg0KT2ggbm8gLSAqKkVSUk9SIG1lc3NhZ2UqKiEgU3BhdGlhbCBxdWVyeWluZyByZXF1aXJlcyBmZWF0dXJlcyB0byBiZSBpbiB0aGUgc2FtZSBDUlMhDQoNClwNCg0KVG8gZml4IHRoaXMsIHdlIGNhbiBwcm9qZWN0IHRoZSBNZXJjZWQgSFVDLTggbGF5ZXIgKHdoaWNoIGlzIGluIENBIEFsYmVycykgdG8gbWF0Y2ggdGhlIFBPSXMgKHdoaWNoIGFyZSBVVE0pOg0KDQpgYGB7ciBjaHVuazE5fQ0KbWVyY2VkX2h1YzhfdXRtX3NmIDwtIG1lcmNlZF9odWM4X3NmICU+JSANCiAgc3RfdHJhbnNmb3JtKHN0X2Nycyh5b3NlX3BvaV91dG0pKQ0KYGBgDQoNClwNCg0KVHJ5IHRoZSBpbnRlcnNlY3Rpb24gdGVzdCBhZ2FpbjoNCg0KYGBge3IgY2h1bmsyMH0NCnlvc2VfcG9pX21lcmNlZF9tYXQgPC0geW9zZV9wb2lfdXRtICU+JSBzdF9pbnRlcnNlY3RzKG1lcmNlZF9odWM4X3V0bV9zZiwgc3BhcnNlPUZBTFNFKQ0KaGVhZCh5b3NlX3BvaV9tZXJjZWRfbWF0KQ0KYGBgDQoNClwNCg0KIyMgQ0hBTExFTkdFOiBIb3cgbWFueSBwb2ludHMtb2YtaW50ZXJlc3QgZmFsbCB3aXRoaW4gdGhlIFVwcGVyIE1lcmNlZCBzdWJiYXNpbj8NCg0KKkhpbnQgMSo6IFRoaXMgaXMgZXF1aXZhbGVudCB0byBhc2tpbmcgaG93IG1hbnkgVFJVRSB2YWx1ZXMgdGhlcmUgYXJlIGluIHRoZSBmaXJzdCBjb2x1bW4gb2YgYHlvc2VfcG9pX21lcmNlZF9tYXRgLg0KDQoqSGludCAyKjogVG8gZ2V0IHRoZSBmaXJzdCBjb2x1bW4gb2YgYSBtYXRyaXggYHhgLCB1c2UgYHhbICwgMV1gLg0KDQpbQW5zd2VyXShodHRwczovL2JpdC5seS8zckVPbnpuKQ0KDQpgYGB7ciBjaHVuazIxfQ0KIyMgWW91ciBhbnN3ZXIgaGVyZQ0KYGBgDQoNClwNCg0KIyMgU3Vic2V0IHRoZSBQT0lzIHRoYXQgZmFsbCB3aXRoaW4gdGhlIFVwcGVyIE1lcmNlZCBTdWJiYXNpbg0KDQpUbyBleHRyYWN0IHRoZSBQT0lzIGluIHRoZSBVcHBlciBNZXJjZWQgc3ViYmFzaW4sIHdlIGNhbiBmZWVkIHRoZSBmaXJzdCBjb2x1bW4gb2YgYHlvc2VfcG9pX21lcmNlZF9tYXRgIGludG8gIGBmaWx0ZXIoKWAgKHdoaWNoIGV4cGVjdHMgVFJVRS9GQUxTRSB2YWx1ZXMpOg0KDQpgYGB7ciBjaHVuazIyfQ0KIyMgRXh0cmFjdCB0aGUgcG9pbnRzIHRoYXQgaW50ZXJzZWN0IHRoZSBzdWJiYXNpbiB0byBhIG5ldyBzZiBvYmplY3QNCm1lcmNlZF9wb2lfdXRtIDwtIHlvc2VfcG9pX3V0bSAlPiUgDQogIGZpbHRlcih5b3NlX3BvaV9tZXJjZWRfbWF0WywxXSkNCmBgYA0KDQpcDQoNCiMjIFBsb3QgdGhlIEludGVyc2VjdGlvbg0KDQpQbG90IHRvIHZpc3VhbGx5IHZlcmlmeSB0aGUgcmVzdWx0czoNCg0KYGBge3IgY2h1bmsyM30NCiMjIFBsb3QNCnRtX3NoYXBlKG1lcmNlZF9odWM4X3V0bV9zZikgKw0KICB0bV9wb2x5Z29ucyhjb2wgPSAia2hha2kiKSArDQp0bV9zaGFwZSh5b3NlX3BvaV91dG0pICsNCiAgdG1fZG90cyhzaXplID0gMC4xLCBjb2wgPSAiZ3JheTMwIikgKw0KdG1fc2hhcGUobWVyY2VkX3BvaV91dG0pICsNCiAgdG1fZG90cyhzaXplID0gMC4xLCBjb2wgPSAiZG9kZ2VyYmx1ZSIpDQpgYGANCg0KXA0KDQojIyBDSEFMTEVOR0U6IEhvdyBtYW55IFlOUCBQT0lzIGZhbGwgd2l0aW4gdGhlIFVwcGVyIFR1b2x1bW5lIEhVQy04IHN1YmJhc2luPw0KDQpbQW5zd2VyXShodHRwczovL2JpdC5seS8zQWRhc0s2KQ0KDQpgYGB7ciBjaHVuazI0fQ0KIyMgWW91ciBhbnN3ZXIgaGVyZQ0KYGBgDQoNCg0KIyMgRW5kDQoNCkNvbmdyYXR1bGF0aW9ucywgeW91J3ZlIGNvbXBsZXRlZCBhbm90aGVyIE5vdGVib29rISANCg0KVG8gdmlldyB5b3VyIE5vdGVib29rIGF0IEhUTUwsIHNhdmUgaXQgKGFnYWluKSwgdGhlbiBjbGljayB0aGUgJ1ByZXZpZXcnIGJ1dHRvbiBpbiB0aGUgUlN0dWRpbyB0b29sYmFyLg0KDQo=