In this Notebook we’ll use geoprocessing functions from sf to:

  • generate 500 randomly distributed sample points within YNP
  • identify the vegetation class for sample point and save it in the attribute table
  • find all sample points within 2km of each campground, and compute their distances to the campground

Setup

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

library(sf)
library(tibble)
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)


Import the Park Boundary

First, we import the boundary:

## Define a convenience variable for UTM Zone 11
epsg_utm11n_nad83 <- 26911

## Import the YNP border
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


Generate Random Points

Next, generate uniformly distributed random numbers between the x and y ranges of the park’s extent (i.e., bounding box). We’ll generate more points than we actually needed knowing that some of them will fall outside the park boundary.

n <- 500

## Get the Extent of the park boundary
yose_ext <- st_bbox(yose_bnd_utm)
yose_ext
     xmin      ymin      xmax      ymax 
 246310.4 4152875.3  306742.6 4229525.8 
## Generate uniformly distributed random numbers
xs <- runif(n * 3, min = yose_ext[1], max = yose_ext[3])
ys <- runif(n * 3, min = yose_ext[2], max = yose_ext[4])


Create a sf object from the random points

rand_pts_sf <- st_as_sf(data.frame(lon = xs, lat = ys, ptid = 1:length(xs)), 
                     coords = c("lon", "lat"), 
                     crs = st_crs(yose_bnd_utm))

## Plot to verify
tm_shape(rand_pts_sf) + 
  tm_dots(col = "dimgray") +
tm_shape(yose_bnd_utm) +
  tm_borders(col = "red", lwd = 2)

Next, test which points fall within the park boundary, and save those to a new object.

## Test which points are within the YNP boundary
in_yose_mat <- st_within(rand_pts_sf, yose_bnd_utm, sparse = FALSE)
head(in_yose_mat)
      [,1]
[1,] FALSE
[2,]  TRUE
[3,]  TRUE
[4,] FALSE
[5,]  TRUE
[6,]  TRUE
## Save those in the park to a new sf object
pts_in_park_sf <- rand_pts_sf %>% filter(in_yose_mat[,1])
nrow(pts_in_park_sf)
[1] 973


Plot the results:

tm_shape(yose_bnd_utm) + 
  tm_borders(col="palegreen4", lwd = 3) +
tm_shape(pts_in_park_sf) + 
  tm_dots(col="dimgray")


Lastly, all we need is 500 points, so take a random sample:

## Take a random sample of the points 
yose_sample_pts_utm <- pts_in_park_sf %>% 
  sample_n(n)

## Plot
tm_shape(yose_bnd_utm) + 
  tm_borders(col="palegreen4", lwd = 4) +
tm_shape(yose_sample_pts_utm) + 
  tm_dots(col="dimgray")


Find and Record the Vegetation Class for Each Sample Point

Next we’ll use a spatial join to do two things at once: 1) find the vegetation type for each sample point, and ii) save it in the attribute table.

First import the vegetation class layer and the legend:

yose_veg37_utm <- st_read(dsn="./data", layer="veg37") %>% 
  st_transform(epsg_utm11n_nad83) %>% 
  select(DOMINANT, ALLIANCE, RIM_CODE)
Reading layer `veg37' from data source `D:\Workshops\R-Spatial\rspatial_mod\outputs\rspatial_data\data' using driver `ESRI Shapefile'
Simple feature collection with 6512 features and 26 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: 246268.6 ymin: 4152706 xmax: 306269.9 ymax: 4229672
Projected CRS: NAD83 / UTM zone 11N
## Import the legend for the vegetation layer (saved separately)
yose_veg37_legend_df <- foreign::read.dbf("./data/veg37_alliances.dbf")

## Join the legend to the sf data frame on the 'ALLIANCE' column
yose_veg37leg_utm <- yose_veg37_utm %>% 
  left_join(yose_veg37_legend_df, by = "ALLIANCE")

yose_veg37leg_utm %>% slice(1:10) %>% as_tibble()


Plot the vegetation layer:

tmap_options(max.categories = 31)

tm_shape(yose_bnd_utm) + 
  tm_borders(col="palegreen4", lwd = 4) +
tm_shape(yose_veg37leg_utm) + 
  tm_fill("ALLIANCE") +
tm_layout(legend.outside = TRUE)

Now we’re ready to spatially join the sample points to the vegetation layer using st_join(). This will add the columns from the vegetation layer to the attribute table of the sample points.

yose_sample_pts_veg_utm <- yose_sample_pts_utm %>% 
  st_join(yose_veg37leg_utm)

yose_sample_pts_veg_utm %>% slice(1:10) %>% as_tibble()


CHALLENGE: Plot the sample points

Plot the sample points according to their vegetation class.

Answer

tm_shape(yose_bnd_utm) + 
  tm_borders(col="palegreen4", lwd = 4) +
tm_shape(yose_sample_pts_veg_utm) + 
  tm_dots("ALLIANCE", size = 0.3) +
tm_layout(legend.outside = TRUE)


Find All Sample Points within 2km of Each Campground

First we import the campgrounds:

## Import the campground
yose_campgrounds_utm <- st_read("./data", layer="yose_poi") %>% 
  st_transform(epsg_utm11n_nad83) %>% 
  filter(POITYPE == 'Campground') %>% 
  select(POINAME)
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
yose_campgrounds_utm
Simple feature collection with 15 features and 1 field
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: 247625.8 ymin: 4158885 xmax: 292815.1 ymax: 4204389
Projected CRS: NAD83 / UTM zone 11N
First 10 features:
                       POINAME                 geometry
1    Hodgdon Meadow Campground POINT (247625.8 4187296)
2        Crane Flat Campground POINT (253315.9 4181287)
3     Tamarack Flat Campground POINT (258936.4 4181679)
4        White Wolf Campground POINT (267142.6 4194768)
5    Yosemite Creek Campground POINT (271702.9 4189756)
6    Porcupine Flat Campground POINT (273987.5 4187789)
7  Tuolumne Meadows Campground POINT (292815.1 4194103)
8  Bridalveil Creek Campground POINT (268815.7 4171688)
9            Wawona Campground POINT (263419.9 4158885)
10           Camp 4 Campground   POINT (270612 4180317)


To identify the sample points within 2km of each campground, we could construct a buffer around each campground and take the intersection. But there’s an easier way - we can do the spatial query directly using st_is_within_distance().

The number of sample points near each campground will vary. Hence we’ll save the results in a list (sparse = TRUE):

samp_pts_near_each_campgrnd_lst <- yose_campgrounds_utm  %>% 
  st_is_within_distance(yose_sample_pts_utm, 2000, sparse = TRUE)

samp_pts_near_each_campgrnd_lst
Sparse geometry binary predicate list of length 15, where the predicate was `is_within_distance'
first 10 elements:
 1: (empty)
 2: 362
 3: 70
 4: 333
 5: (empty)
 6: 211, 375, 383, 416
 7: 97, 125
 8: 39, 102, 111, 143, 393
 9: 14
 10: 18, 55, 75, 327, 346, 358, 445


Compute Distances Between each Campground and its Nearby Sample Points

Finally, We’ll compute the distance between each campground and the sample points that lie within 2km.

To do this, we’ll loop through the campgrounds, and for each one we’ll use st_distance() to find the distance between the campground and the nearby points (whose row numbers are saved in samp_pts_near_each_campgrnd_lst) . To complile all the results, we’ll append the distances to a data frame each iteration of the loop.

## Initialize campgrnd_samppts_dist_df to NULL
## (we'll use it below to compile and save the results of a loop)
campgrnd_samppts_dist_df <- NULL

for (i in 1:nrow(yose_campgrounds_utm)) {
  
  if (length(samp_pts_near_each_campgrnd_lst[[i]]) > 0) {
    dist_mat <- st_distance(x = yose_campgrounds_utm %>% slice(i),
                          y = yose_sample_pts_utm %>% 
                            slice(samp_pts_near_each_campgrnd_lst[[i]]))
    
    ## Create a data frame with the campground rown number, the sample point row numbers, and the distances
    dist2samppts_df <- data.frame(campgrnd_idx = i,
                                  samp_pt_idx = samp_pts_near_each_campgrnd_lst[[i]],
                                  dist = as.numeric(dist_mat[1,]))
    
    ## Add these rows to campgrnd_samppts_dist_df
    campgrnd_samppts_dist_df <- campgrnd_samppts_dist_df %>% 
      bind_rows(dist2samppts_df)
    
  }

}

## View results
campgrnd_samppts_dist_df

End

Congratulations, you’ve completed another Notebook!

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

LS0tDQp0aXRsZTogIlNwYXRpYWwgUXVlcmllcyAtIFNhbXBsZSBQYXJrIFZlZ2V0YXRpb24gd2l0aCBSYW5kb20gUG9pbnRzIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQotLS0NCg0KYGBge2NzcyBlY2hvID0gRkFMU0V9DQpoMSxoMiB7Zm9udC13ZWlnaHQ6Ym9sZDt9DQpgYGANCg0KSW4gdGhpcyBOb3RlYm9vayB3ZSdsbCB1c2UgZ2VvcHJvY2Vzc2luZyBmdW5jdGlvbnMgIGZyb20gYHNmYCB0bzoNCg0KLSBnZW5lcmF0ZSA1MDAgcmFuZG9tbHkgZGlzdHJpYnV0ZWQgc2FtcGxlIHBvaW50cyB3aXRoaW4gWU5QDQotIGlkZW50aWZ5IHRoZSB2ZWdldGF0aW9uIGNsYXNzIGZvciBzYW1wbGUgcG9pbnQgYW5kIHNhdmUgaXQgaW4gdGhlIGF0dHJpYnV0ZSB0YWJsZSAgDQotIGZpbmQgYWxsIHNhbXBsZSBwb2ludHMgd2l0aGluIDJrbSBvZiBlYWNoIGNhbXBncm91bmQsIGFuZCBjb21wdXRlIHRoZWlyIGRpc3RhbmNlcyB0byB0aGUgY2FtcGdyb3VuZA0KDQojIyBTZXR1cA0KDQpMb2FkIHRoZSBwYWNrYWdlcyB3ZSdsbCBuZWVkIGFuZCBzZXQgdG1hcCBtb2RlIHRvICdwbG90JzoNCg0KYGBge3IgY2h1bmswMSwgbWVzc2FnZSA9IEZBTFNFfQ0KbGlicmFyeShzZikNCmxpYnJhcnkodGliYmxlKQ0KbGlicmFyeSh0bWFwKQ0KdG1hcF9tb2RlKCJwbG90IikNCmBgYA0KDQpMb2FkIGBkcGx5cmAgYW5kIHNldCBuYW1lIGNvbmZsaWN0IHByZWZlcmVuY2VzOg0KDQpgYGB7ciBjaHVuazAyLCBtZXNzYWdlID0gRkFMU0V9DQpsaWJyYXJ5KGRwbHlyKQ0KDQojIyBMb2FkIHRoZSBjb25mbGljdGVkIHBhY2thZ2UNCmxpYnJhcnkoY29uZmxpY3RlZCkNCg0KIyBTZXQgY29uZmxpY3QgcHJlZmVyZW5jZXMNCmNvbmZsaWN0X3ByZWZlcigiZmlsdGVyIiwgImRwbHlyIiwgcXVpZXQgPSBUUlVFKQ0KY29uZmxpY3RfcHJlZmVyKCJjb3VudCIsICJkcGx5ciIsIHF1aWV0ID0gVFJVRSkNCmNvbmZsaWN0X3ByZWZlcigic2VsZWN0IiwgImRwbHlyIiwgcXVpZXQgPSBUUlVFKQ0KY29uZmxpY3RfcHJlZmVyKCJhcnJhbmdlIiwgImRwbHlyIiwgcXVpZXQgPSBUUlVFKQ0KYGBgDQoNClwNCg0KIyMgSW1wb3J0IHRoZSBQYXJrIEJvdW5kYXJ5DQoNCkZpcnN0LCB3ZSBpbXBvcnQgdGhlIGJvdW5kYXJ5Og0KDQpgYGB7ciBjaHVuazAzfQ0KIyMgRGVmaW5lIGEgY29udmVuaWVuY2UgdmFyaWFibGUgZm9yIFVUTSBab25lIDExDQplcHNnX3V0bTExbl9uYWQ4MyA8LSAyNjkxMQ0KDQojIyBJbXBvcnQgdGhlIFlOUCBib3JkZXINCnlvc2VfYm5kX3V0bSA8LSBzdF9yZWFkKGRzbj0iLi9kYXRhIiwgbGF5ZXI9Inlvc2VfYm91bmRhcnkiKSAlPiUgDQogIHN0X3RyYW5zZm9ybShlcHNnX3V0bTExbl9uYWQ4MykNCmBgYA0KDQpcDQoNCiMjIEdlbmVyYXRlIFJhbmRvbSBQb2ludHMNCg0KTmV4dCwgZ2VuZXJhdGUgdW5pZm9ybWx5IGRpc3RyaWJ1dGVkIHJhbmRvbSBudW1iZXJzIGJldHdlZW4gdGhlIHggYW5kIHkgcmFuZ2VzIG9mIHRoZSBwYXJrJ3MgZXh0ZW50IChpLmUuLCBib3VuZGluZyBib3gpLiBXZSdsbCBnZW5lcmF0ZSBtb3JlIHBvaW50cyB0aGFuIHdlIGFjdHVhbGx5IG5lZWRlZCBrbm93aW5nIHRoYXQgc29tZSBvZiB0aGVtIHdpbGwgZmFsbCBvdXRzaWRlIHRoZSBwYXJrIGJvdW5kYXJ5LiANCg0KYGBge3IgY2h1bmswNH0NCm4gPC0gNTAwDQoNCiMjIEdldCB0aGUgRXh0ZW50IG9mIHRoZSBwYXJrIGJvdW5kYXJ5DQp5b3NlX2V4dCA8LSBzdF9iYm94KHlvc2VfYm5kX3V0bSkNCnlvc2VfZXh0DQoNCiMjIEdlbmVyYXRlIHVuaWZvcm1seSBkaXN0cmlidXRlZCByYW5kb20gbnVtYmVycw0KeHMgPC0gcnVuaWYobiAqIDMsIG1pbiA9IHlvc2VfZXh0WzFdLCBtYXggPSB5b3NlX2V4dFszXSkNCnlzIDwtIHJ1bmlmKG4gKiAzLCBtaW4gPSB5b3NlX2V4dFsyXSwgbWF4ID0geW9zZV9leHRbNF0pDQpgYGANCg0KXA0KDQpDcmVhdGUgYSBzZiBvYmplY3QgZnJvbSB0aGUgcmFuZG9tIHBvaW50cw0KDQpgYGB7ciBjaHVuazA1fQ0KcmFuZF9wdHNfc2YgPC0gc3RfYXNfc2YoZGF0YS5mcmFtZShsb24gPSB4cywgbGF0ID0geXMsIHB0aWQgPSAxOmxlbmd0aCh4cykpLCANCiAgICAgICAgICAgICAgICAgICAgIGNvb3JkcyA9IGMoImxvbiIsICJsYXQiKSwgDQogICAgICAgICAgICAgICAgICAgICBjcnMgPSBzdF9jcnMoeW9zZV9ibmRfdXRtKSkNCg0KIyMgUGxvdCB0byB2ZXJpZnkNCnRtX3NoYXBlKHJhbmRfcHRzX3NmKSArIA0KICB0bV9kb3RzKGNvbCA9ICJkaW1ncmF5IikgKw0KdG1fc2hhcGUoeW9zZV9ibmRfdXRtKSArDQogIHRtX2JvcmRlcnMoY29sID0gInJlZCIsIGx3ZCA9IDIpDQpgYGANCg0KTmV4dCwgdGVzdCB3aGljaCBwb2ludHMgZmFsbCB3aXRoaW4gdGhlIHBhcmsgYm91bmRhcnksIGFuZCBzYXZlIHRob3NlIHRvIGEgbmV3IG9iamVjdC4NCg0KYGBge3IgY2h1bmswNn0NCiMjIFRlc3Qgd2hpY2ggcG9pbnRzIGFyZSB3aXRoaW4gdGhlIFlOUCBib3VuZGFyeQ0KaW5feW9zZV9tYXQgPC0gc3Rfd2l0aGluKHJhbmRfcHRzX3NmLCB5b3NlX2JuZF91dG0sIHNwYXJzZSA9IEZBTFNFKQ0KaGVhZChpbl95b3NlX21hdCkNCg0KIyMgU2F2ZSB0aG9zZSBpbiB0aGUgcGFyayB0byBhIG5ldyBzZiBvYmplY3QNCnB0c19pbl9wYXJrX3NmIDwtIHJhbmRfcHRzX3NmICU+JSBmaWx0ZXIoaW5feW9zZV9tYXRbLDFdKQ0KbnJvdyhwdHNfaW5fcGFya19zZikNCmBgYA0KDQpcDQoNClBsb3QgdGhlIHJlc3VsdHM6DQoNCmBgYHtyIGNodW5rMDd9DQp0bV9zaGFwZSh5b3NlX2JuZF91dG0pICsgDQogIHRtX2JvcmRlcnMoY29sPSJwYWxlZ3JlZW40IiwgbHdkID0gMykgKw0KdG1fc2hhcGUocHRzX2luX3Bhcmtfc2YpICsgDQogIHRtX2RvdHMoY29sPSJkaW1ncmF5IikNCmBgYA0KDQpcDQoNCkxhc3RseSwgYWxsIHdlIG5lZWQgaXMgNTAwIHBvaW50cywgc28gdGFrZSBhIHJhbmRvbSBzYW1wbGU6DQoNCmBgYHtyIGNodW5rMDh9DQojIyBUYWtlIGEgcmFuZG9tIHNhbXBsZSBvZiB0aGUgcG9pbnRzIA0KeW9zZV9zYW1wbGVfcHRzX3V0bSA8LSBwdHNfaW5fcGFya19zZiAlPiUgDQogIHNhbXBsZV9uKG4pDQoNCiMjIFBsb3QNCnRtX3NoYXBlKHlvc2VfYm5kX3V0bSkgKyANCiAgdG1fYm9yZGVycyhjb2w9InBhbGVncmVlbjQiLCBsd2QgPSA0KSArDQp0bV9zaGFwZSh5b3NlX3NhbXBsZV9wdHNfdXRtKSArIA0KICB0bV9kb3RzKGNvbD0iZGltZ3JheSIpDQpgYGANCg0KDQpcDQoNCiMgRmluZCBhbmQgUmVjb3JkIHRoZSBWZWdldGF0aW9uIENsYXNzIGZvciBFYWNoIFNhbXBsZSBQb2ludA0KDQpOZXh0IHdlJ2xsIHVzZSBhICoqc3BhdGlhbCBqb2luKiogdG8gZG8gdHdvIHRoaW5ncyBhdCBvbmNlOiAxKSBmaW5kIHRoZSB2ZWdldGF0aW9uIHR5cGUgZm9yIGVhY2ggc2FtcGxlIHBvaW50LCBhbmQgaWkpIHNhdmUgaXQgaW4gdGhlIGF0dHJpYnV0ZSB0YWJsZS4NCg0KRmlyc3QgaW1wb3J0IHRoZSB2ZWdldGF0aW9uIGNsYXNzIGxheWVyIGFuZCB0aGUgbGVnZW5kOg0KDQpgYGB7ciBjaHVuazA5fQ0KeW9zZV92ZWczN191dG0gPC0gc3RfcmVhZChkc249Ii4vZGF0YSIsIGxheWVyPSJ2ZWczNyIpICU+JSANCiAgc3RfdHJhbnNmb3JtKGVwc2dfdXRtMTFuX25hZDgzKSAlPiUgDQogIHNlbGVjdChET01JTkFOVCwgQUxMSUFOQ0UsIFJJTV9DT0RFKQ0KDQojIyBJbXBvcnQgdGhlIGxlZ2VuZCBmb3IgdGhlIHZlZ2V0YXRpb24gbGF5ZXIgKHNhdmVkIHNlcGFyYXRlbHkpDQp5b3NlX3ZlZzM3X2xlZ2VuZF9kZiA8LSBmb3JlaWduOjpyZWFkLmRiZigiLi9kYXRhL3ZlZzM3X2FsbGlhbmNlcy5kYmYiKQ0KDQojIyBKb2luIHRoZSBsZWdlbmQgdG8gdGhlIHNmIGRhdGEgZnJhbWUgb24gdGhlICdBTExJQU5DRScgY29sdW1uDQp5b3NlX3ZlZzM3bGVnX3V0bSA8LSB5b3NlX3ZlZzM3X3V0bSAlPiUgDQogIGxlZnRfam9pbih5b3NlX3ZlZzM3X2xlZ2VuZF9kZiwgYnkgPSAiQUxMSUFOQ0UiKQ0KDQp5b3NlX3ZlZzM3bGVnX3V0bSAlPiUgc2xpY2UoMToxMCkgJT4lIGFzX3RpYmJsZSgpDQpgYGANCg0KXA0KDQpQbG90IHRoZSB2ZWdldGF0aW9uIGxheWVyOg0KDQpgYGB7ciBjaHVuazEwfQ0KdG1hcF9vcHRpb25zKG1heC5jYXRlZ29yaWVzID0gMzEpDQoNCnRtX3NoYXBlKHlvc2VfYm5kX3V0bSkgKyANCiAgdG1fYm9yZGVycyhjb2w9InBhbGVncmVlbjQiLCBsd2QgPSA0KSArDQp0bV9zaGFwZSh5b3NlX3ZlZzM3bGVnX3V0bSkgKyANCiAgdG1fZmlsbCgiQUxMSUFOQ0UiKSArDQp0bV9sYXlvdXQobGVnZW5kLm91dHNpZGUgPSBUUlVFKQ0KDQpgYGANCg0KTm93IHdlJ3JlIHJlYWR5IHRvIHNwYXRpYWxseSBqb2luIHRoZSBzYW1wbGUgcG9pbnRzIHRvIHRoZSB2ZWdldGF0aW9uIGxheWVyIHVzaW5nIGBzdF9qb2luKClgLiBUaGlzIHdpbGwgYWRkIHRoZSBjb2x1bW5zIGZyb20gdGhlIHZlZ2V0YXRpb24gbGF5ZXIgdG8gdGhlIGF0dHJpYnV0ZSB0YWJsZSBvZiB0aGUgc2FtcGxlIHBvaW50cy4NCg0KYGBge3IgY2h1bmsxMX0NCnlvc2Vfc2FtcGxlX3B0c192ZWdfdXRtIDwtIHlvc2Vfc2FtcGxlX3B0c191dG0gJT4lIA0KICBzdF9qb2luKHlvc2VfdmVnMzdsZWdfdXRtKQ0KDQp5b3NlX3NhbXBsZV9wdHNfdmVnX3V0bSAlPiUgc2xpY2UoMToxMCkgJT4lIGFzX3RpYmJsZSgpDQpgYGANCg0KXA0KDQojIyBDSEFMTEVOR0U6IFBsb3QgdGhlIHNhbXBsZSBwb2ludHMNCg0KUGxvdCB0aGUgc2FtcGxlIHBvaW50cyBhY2NvcmRpbmcgdG8gdGhlaXIgdmVnZXRhdGlvbiBjbGFzcy4gDQoNCltBbnN3ZXJdKGh0dHBzOi8vYml0Lmx5LzN1OEp2VU0pDQoNCmBgYHtyIGNodW5rMTJ9DQp0bV9zaGFwZSh5b3NlX2JuZF91dG0pICsgDQogIHRtX2JvcmRlcnMoY29sPSJwYWxlZ3JlZW40IiwgbHdkID0gNCkgKw0KdG1fc2hhcGUoeW9zZV9zYW1wbGVfcHRzX3ZlZ191dG0pICsgDQogIHRtX2RvdHMoIkFMTElBTkNFIiwgc2l6ZSA9IDAuMykgKw0KdG1fbGF5b3V0KGxlZ2VuZC5vdXRzaWRlID0gVFJVRSkNCmBgYA0KDQpcDQoNCiMjIEZpbmQgQWxsIFNhbXBsZSBQb2ludHMgd2l0aGluIDJrbSBvZiBFYWNoIENhbXBncm91bmQNCg0KRmlyc3Qgd2UgaW1wb3J0IHRoZSBjYW1wZ3JvdW5kczoNCg0KYGBge3IgY2h1bmsxM30NCiMjIEltcG9ydCB0aGUgY2FtcGdyb3VuZA0KeW9zZV9jYW1wZ3JvdW5kc191dG0gPC0gc3RfcmVhZCgiLi9kYXRhIiwgbGF5ZXI9Inlvc2VfcG9pIikgJT4lIA0KICBzdF90cmFuc2Zvcm0oZXBzZ191dG0xMW5fbmFkODMpICU+JSANCiAgZmlsdGVyKFBPSVRZUEUgPT0gJ0NhbXBncm91bmQnKSAlPiUgDQogIHNlbGVjdChQT0lOQU1FKQ0KDQp5b3NlX2NhbXBncm91bmRzX3V0bQ0KYGBgDQoNClwNCg0KVG8gaWRlbnRpZnkgdGhlIHNhbXBsZSBwb2ludHMgd2l0aGluIDJrbSBvZiBlYWNoIGNhbXBncm91bmQsIHdlIGNvdWxkIGNvbnN0cnVjdCBhIGJ1ZmZlciBhcm91bmQgZWFjaCBjYW1wZ3JvdW5kIGFuZCB0YWtlIHRoZSBpbnRlcnNlY3Rpb24uIEJ1dCB0aGVyZSdzIGFuIGVhc2llciB3YXkgLSB3ZSBjYW4gZG8gdGhlIHNwYXRpYWwgcXVlcnkgZGlyZWN0bHkgdXNpbmcgYHN0X2lzX3dpdGhpbl9kaXN0YW5jZSgpYC4NCg0KVGhlIG51bWJlciBvZiBzYW1wbGUgcG9pbnRzIG5lYXIgZWFjaCBjYW1wZ3JvdW5kIHdpbGwgdmFyeS4gSGVuY2Ugd2UnbGwgc2F2ZSB0aGUgcmVzdWx0cyBpbiBhIGxpc3QgKGBzcGFyc2UgPSBUUlVFYCk6DQoNCmBgYHtyIGNodW5rMTR9DQpzYW1wX3B0c19uZWFyX2VhY2hfY2FtcGdybmRfbHN0IDwtIHlvc2VfY2FtcGdyb3VuZHNfdXRtICAlPiUgDQogIHN0X2lzX3dpdGhpbl9kaXN0YW5jZSh5b3NlX3NhbXBsZV9wdHNfdXRtLCAyMDAwLCBzcGFyc2UgPSBUUlVFKQ0KDQpzYW1wX3B0c19uZWFyX2VhY2hfY2FtcGdybmRfbHN0DQpgYGANCg0KXA0KDQojIyBDb21wdXRlIERpc3RhbmNlcyBCZXR3ZWVuIGVhY2ggQ2FtcGdyb3VuZCBhbmQgaXRzIE5lYXJieSBTYW1wbGUgUG9pbnRzDQoNCkZpbmFsbHksIFdlJ2xsIGNvbXB1dGUgdGhlIGRpc3RhbmNlIGJldHdlZW4gZWFjaCBjYW1wZ3JvdW5kIGFuZCB0aGUgc2FtcGxlIHBvaW50cyB0aGF0IGxpZSB3aXRoaW4gMmttLiANCg0KVG8gZG8gdGhpcywgd2UnbGwgbG9vcCB0aHJvdWdoIHRoZSBjYW1wZ3JvdW5kcywgYW5kIGZvciBlYWNoIG9uZSB3ZSdsbCB1c2UgYHN0X2Rpc3RhbmNlKClgIHRvIGZpbmQgdGhlIGRpc3RhbmNlIGJldHdlZW4gdGhlIGNhbXBncm91bmQgYW5kIHRoZSBuZWFyYnkgcG9pbnRzICh3aG9zZSByb3cgbnVtYmVycyBhcmUgc2F2ZWQgaW4gYHNhbXBfcHRzX25lYXJfZWFjaF9jYW1wZ3JuZF9sc3RgKSAuIFRvIGNvbXBsaWxlIGFsbCB0aGUgcmVzdWx0cywgd2UnbGwgYXBwZW5kIHRoZSBkaXN0YW5jZXMgdG8gYSBkYXRhIGZyYW1lIGVhY2ggaXRlcmF0aW9uIG9mIHRoZSBsb29wLiANCg0KYGBge3IgY2h1bmsxNX0NCiMjIEluaXRpYWxpemUgY2FtcGdybmRfc2FtcHB0c19kaXN0X2RmIHRvIE5VTEwNCiMjICh3ZSdsbCB1c2UgaXQgYmVsb3cgdG8gY29tcGlsZSBhbmQgc2F2ZSB0aGUgcmVzdWx0cyBvZiBhIGxvb3ApDQpjYW1wZ3JuZF9zYW1wcHRzX2Rpc3RfZGYgPC0gTlVMTA0KDQpmb3IgKGkgaW4gMTpucm93KHlvc2VfY2FtcGdyb3VuZHNfdXRtKSkgew0KICANCiAgaWYgKGxlbmd0aChzYW1wX3B0c19uZWFyX2VhY2hfY2FtcGdybmRfbHN0W1tpXV0pID4gMCkgew0KICAgIGRpc3RfbWF0IDwtIHN0X2Rpc3RhbmNlKHggPSB5b3NlX2NhbXBncm91bmRzX3V0bSAlPiUgc2xpY2UoaSksDQogICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSB5b3NlX3NhbXBsZV9wdHNfdXRtICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzbGljZShzYW1wX3B0c19uZWFyX2VhY2hfY2FtcGdybmRfbHN0W1tpXV0pKQ0KICAgIA0KICAgICMjIENyZWF0ZSBhIGRhdGEgZnJhbWUgd2l0aCB0aGUgY2FtcGdyb3VuZCByb3duIG51bWJlciwgdGhlIHNhbXBsZSBwb2ludCByb3cgbnVtYmVycywgYW5kIHRoZSBkaXN0YW5jZXMNCiAgICBkaXN0MnNhbXBwdHNfZGYgPC0gZGF0YS5mcmFtZShjYW1wZ3JuZF9pZHggPSBpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBfcHRfaWR4ID0gc2FtcF9wdHNfbmVhcl9lYWNoX2NhbXBncm5kX2xzdFtbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpc3QgPSBhcy5udW1lcmljKGRpc3RfbWF0WzEsXSkpDQogICAgDQogICAgIyMgQWRkIHRoZXNlIHJvd3MgdG8gY2FtcGdybmRfc2FtcHB0c19kaXN0X2RmDQogICAgY2FtcGdybmRfc2FtcHB0c19kaXN0X2RmIDwtIGNhbXBncm5kX3NhbXBwdHNfZGlzdF9kZiAlPiUgDQogICAgICBiaW5kX3Jvd3MoZGlzdDJzYW1wcHRzX2RmKQ0KICAgIA0KICB9DQoNCn0NCg0KIyMgVmlldyByZXN1bHRzDQpjYW1wZ3JuZF9zYW1wcHRzX2Rpc3RfZGYNCmBgYA0KDQojIyBFbmQNCg0KQ29uZ3JhdHVsYXRpb25zLCB5b3UndmUgY29tcGxldGVkIGFub3RoZXIgTm90ZWJvb2shIA0KDQpUbyB2aWV3IHlvdXIgTm90ZWJvb2sgYXMgSFRNTCwgc2F2ZSBpdCAoYWdhaW4pLCB0aGVuIGNsaWNrIHRoZSAnUHJldmlldycgYnV0dG9uIGluIHRoZSBSU3R1ZGlvIHRvb2xiYXIuDQoNCg==