In this Notebook we’ll look for suitable places for new campsites in Yosemite that meet the following criteria:

  • within 100m of an existing trail
  • slope < 10%
  • within 100m of a stream

This is an example of a Multi Criteria Overlay Analysis (MCA) whereby each individual criteria is converted into a binary raster mask, which are then fed into a raster algebra expression to find the pixels where all criteria are met. The trick to doing this is to make sure each binary


Import Layers for Yosemite National Park

For this analysis we’ll need the park boundary, the trails network and a DEM (elevation) of the park (from which we can derive slope).

Begin by importing the vector layers:

library(sf)

## Import the park boundary
bnd_fn <- "./data/yose_boundary.shp"
file.exists(bnd_fn)
yose_bnd_ll <- st_read(bnd_fn)

## Import the trails
trails_gdb_fn <- "./data/yose_trails.gdb"
file.exists(trails_gdb_fn)
yose_trails <- st_read(trails_gdb_fn, layer="Trails")

## Import the rivers and streams
hydro_gdb_fn <- "./data/yose_hydrology.gdb"
file.exists(hydro_gdb_fn)
yose_streams <- st_read(hydro_gdb_fn, layer="Rivers")

Project Everything to UTM

Because one of our criteria involves area, we’ll project everything to UTM 11N (EPSG 32611):

epsg_utm11n_wgs84 <- 32611

yose_bnd_utm <- st_transform(yose_bnd_ll, epsg_utm11n_wgs84)

yose_trails_utm <- st_transform(yose_trails, epsg_utm11n_wgs84)

yose_streams_utm <- st_transform(yose_streams, epsg_utm11n_wgs84)

Plot them to verify it worked:

{plot(yose_bnd_utm %>% st_geometry(), border = "red", lwd = 2)
plot(yose_streams_utm %>% st_geometry(), col = "lightcyan2", add = TRUE)
plot(yose_trails_utm %>% st_geometry(), col = "grey50", add = TRUE)}

Import the DEM

We have elevation data in the form of a tile of SRTM data.

library(raster)
srtm1305_fn <- "./data/srtm_13_05.tif"
file.exists(srtm1305_fn)
srtm1305_rst <- raster(srtm1305_fn)
srtm1305_rst

Crop the DEM to the Park Boundary

Note the SRTM data is in geographic coordinates. To see how big it is we’ll overlay the park boundary on it:

{plot(srtm1305_rst)
plot(yose_bnd_ll %>% st_geometry(), border = "red", lwd = 2, add = TRUE)}
yose_dem_rst_ll <- raster::crop(srtm1305_rst, yose_bnd_ll)
yose_dem_rst_ll

Plot to make sure it worked:

{plot(yose_dem_rst_ll)
plot(yose_bnd_ll %>% st_geometry(), border = "red", lwd = 2, add = TRUE)}

Lastly, we need to project the park DEM into UTM using projectRaster() with the following arguments:

  • from: the raster we want to project
  • to: omitted in this case (because we’re supplying crs and res)
  • crs: pass a proj4 string
  • res: resolution in map units (which in the case of UTM is meters)
  • method: choose ‘blinear’ but the cell values (elevation MSL) are continuous, and we want the interpolated values to also be continuous
## Define the proj4 string for UTM 11N
utm11n_wgs84_proj4 <- "+init=epsg:32611"

yose_dem_rst_utm <- projectRaster(from = yose_dem_rst_ll, 
                              crs = CRS(utm11n_wgs84_proj4), 
                              res = 90, 
                              method = "bilinear")
yose_dem_rst_utm

Plot to make sure it worked:

{plot(yose_dem_rst_utm)
plot(yose_bnd_utm %>% st_geometry(), border = "red", lwd = 2, add = TRUE)}

Compute Slope

Slope is one of the criteria for our campsite. We can compute the slope from the DEM using the raster::terrain():

yose_slope_rst <- raster::terrain(yose_dem_rst_utm, opt="slope")
yose_slope_rst

Plot to make sure it worked. Note that slope is measured in radians.

plot(yose_slope_rst)

Create a Binary Mask for Slope

We create a binary mask of the pixels whose slope < 10%.

## To convert the percent slope to an angle, we use arctan (which returns radians)
max_slope_rad <- atan(0.1) 

## Create the TRUE / FALSE mask with a raster algebra expression
yose_lowslope_msk_rst <- (yose_slope_rst <= max_slope_rad)
yose_lowslope_msk_rst

Plot to make sure it worked:

plot(yose_lowslope_msk_rst, legend = FALSE, main = "Low Slope Areas")

Compute the Distance to Trails Surface

We’ll compute the distance to trails surface (also known as a proximity surface) using raster::distance(). The raster we pass to the function should contain ‘1’ values for all cells that we want to compute distance to (i.e., the trails), and NA for everything else.

Therefore we first need to create a rasterized version of the trails. For this we use raster::rasterize(), passing yosedem_rst_utm as the ‘template’ for the new raster layer, and field = 1 to tell it to assign a ‘1’ to cells that contain a trail.

# The raster::rasterize() function works, but takes 4-5 minutes!
# yose_trails_rst_utm <- raster::rasterize(x = yose_trails_utm, 
#                                  y = yose_dem_rst_utm,
#                                  field = 1)

## Faster method using stars
library(stars)
library(dplyr)

## Rasterize the trails as a stars object
yose_trails_stars <- stars::st_rasterize(sf = yose_trails_utm %>% transmute(field = 1),
                          template = stars::st_as_stars(setValues(yose_dem_rst_utm, NA)))
  
## Convert the stars object to a raster object
yose_trails_rst_utm <- as(yose_trails_stars, "Raster")
yose_trails_rst_utm

Plot it to make sure it looks right:

plot(yose_trails_rst_utm, col = "red", legend = FALSE, main = "Yosemite Trails Rasterized")

View how many cells have a trail:

freq(yose_trails_rst_utm)

Next we compute the distance surface. Note this can take up to a minute.

## Compute the distance surface. Note this can take up to a minute.
yose_dist2trails_rst_utm <- raster::distance(yose_trails_rst_utm)

## Plot 
{plot(yose_dist2trails_rst_utm, main = "YNP Distance to Trails (m)")
plot(yose_trails_utm %>% st_geometry(), col="red", add=TRUE)
plot(yose_bnd_utm %>%  st_geometry(), col=NA, border="black", lwd=2, add=TRUE)}

Create a Binary Mask for Distance to Trails

Our criteria state that the new campsite must be <100 m from an existing trail.

## Create the TRUE / FALSE mask with a raster algebra expression
yose_dist2trails_msk_utm <- (yose_dist2trails_rst_utm <= 100)
yose_dist2trails_msk_utm

Plot to make sure it worked:

plot(yose_dist2trails_msk_utm, legend = FALSE, main = "Areas <100m from trails")

Compute the Distance to Streams Surface

Distance to streams is computed the same way we computed distance to trails:

## Rasterize the streams using stars 
yose_streams_rst_utm <- stars::st_rasterize(sf = yose_streams_utm %>% transmute(field = 1),
                          template = stars::st_as_stars(setValues(yose_dem_rst_utm, NA))) %>%
  as(., "Raster")
yose_streams_rst_utm

# Plot it to make sure it looks right:
plot(yose_streams_rst_utm, col = "red", legend = FALSE, main = "Yosemite Streams Rasterized")

## Compute the distance surface. Note this can take a couple of minutes
yose_dist2streams_rst_utm <- raster::distance(yose_streams_rst_utm)

## Plot 
{plot(yose_dist2streams_rst_utm, main = "YNP Distance to Streams (m)")
plot(yose_streams_utm %>% st_geometry(), lwd = 0.1, cex = 0.1, col="blue", add=TRUE)
plot(yose_bnd_utm %>%  st_geometry(), col=NA, border="black", lwd=2, add=TRUE)}

Create a Binary Mask for Distance to Streams

Our criteria state that the new campsite must be <100 m from a stream.

## Create the TRUE / FALSE mask with a raster algebra expression
yose_dist2streams_msk_utm <- (yose_dist2streams_rst_utm <= 100)
yose_dist2streams_msk_utm

Plot to make sure it worked:

plot(yose_dist2streams_msk_utm, legend = FALSE, main = "Areas <100m from streams")

Combine Masks

The last step is to multiply our three masks together using raster algebra. The result will be a raster where ‘1’ in cell where all three criteria were met, and 0 everywhere else.

campsite_ok_rst <- yose_dist2trails_msk_utm * yose_lowslope_msk_rst * yose_dist2streams_msk_utm
campsite_ok_rst

Plot to make sure it worked:

{plot(campsite_ok_rst, main = "Suitable Areas for Campsites in YNP")
plot(yose_bnd_utm %>%  st_geometry(), col=NA, border="black", lwd=2, add=TRUE)}

Crop and Mask the Results to the Park Boundary

We can’t build campsites outside the park, so our last task is to crop and mask the results to the park boundary. Actually we don’t have to crop, we did that when we cropped the SRTM tile above.

campsite_ok_msk_rst <- raster::mask(campsite_ok_rst, yose_bnd_utm)
campsite_ok_msk_rst

Plot it to make sure it worked:

{plot(campsite_ok_msk_rst, main = "Suitable Areas for Campsites in YNP")
plot(yose_bnd_utm %>%  st_geometry(), col=NA, border="black", lwd=2, add=TRUE)}


CHALLENGE: What is the total area suitable for campsites?

## Find out how many pixels have a value of '1'
pixel_vals_mat <- freq(campsite_ok_msk_rst)
num_pixels_valone <- pixel_vals_mat[ which(pixel_vals_mat[,1] == 1), "count"]

## Multiple the number of pixels = 1 times the area of a single pixel
area_m2 <- (num_pixels_valone * 90 ^ 2) %>% as.numeric()
area_acres <- area_m2 / 4046.86 
area_acres


CHALLENGE: One more criteria

Add one more criteria - exclude areas within 100m of an existing campsite. Hint: campsites are included in the yose_poi.shp layer.

## Import the campsites from the points-of-interest Shapefile
poi_shp_fn <- "./data/yose_poi.shp"
file.exists(poi_shp_fn)

## Import the POI, filter to campsites, project to UTM 11N WGS84, and keep just the name and type columns
yose_poi_utm <- st_read(poi_shp_fn) %>% 
  filter(POITYPE == "Campsite") %>% 
  st_transform(epsg_utm11n_wgs84) %>% 
  select(POINAME, POITYPE)

## Plot the campsites to make sure it worked
{plot(yose_bnd_utm %>%  st_geometry(), col=NA, border="black", lwd=2, axes = TRUE)
plot(yose_poi_utm %>% st_geometry(), pch = 16, cex = 0.1, color = "brown", add = TRUE)}

## Create a buffer around the campsites
yose_poibuff_utm <- st_buffer(yose_poi_utm, dist = 100)

## Rasterize the campsite buffers using stars 
yose_poibuf_rst_utm <- stars::st_rasterize(sf = yose_poibuff_utm %>% transmute(field = 1),
                          template = stars::st_as_stars(setValues(yose_dem_rst_utm, NA))) %>%
  as(., "Raster")

# Plot the rasterized buffers to make sure it looks right:
plot(yose_poibuf_rst_utm, legend = FALSE, col = "black", main = "Existing Campsite Buffers Rasterized")

## Create a binary mask using a raster algebra expression
yose_poibufmsk_rst_utm <- (is.na(yose_poibuf_rst_utm))

## See how many pixels have a value of 1
freq(yose_poibufmsk_rst_utm)

## Plot it to make sure
plot(yose_poibufmsk_rst_utm, main = "Areas Away from Existing Campsites")

## Combine with previous masks
campsite_ok2_rst <- campsite_ok_msk_rst * yose_poibufmsk_rst_utm
campsite_ok2_rst

Plot to make sure it worked:

{plot(campsite_ok2_rst, main = "Suitable Areas for NEW Campsites in YNP")
plot(yose_bnd_utm %>%  st_geometry(), col=NA, border="black", lwd=2, add=TRUE)}

End

Save your Notebook to generate a HTML preview of the code you ran.

LS0tDQp0aXRsZTogIlNpdGUgYSBOZXcgQ2FtcHNpdGUgaW4gWU5QIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQotLS0NCg0KSW4gdGhpcyBOb3RlYm9vayB3ZSdsbCBsb29rIGZvciBzdWl0YWJsZSBwbGFjZXMgZm9yIG5ldyBjYW1wc2l0ZXMgaW4gWW9zZW1pdGUgdGhhdCBtZWV0IHRoZSBmb2xsb3dpbmcgY3JpdGVyaWE6DQoNCi0gd2l0aGluIDEwMG0gb2YgYW4gZXhpc3RpbmcgdHJhaWwgIA0KLSBzbG9wZSA8IDEwJSAgDQotIHdpdGhpbiAxMDBtIG9mIGEgc3RyZWFtICANCg0KVGhpcyBpcyBhbiBleGFtcGxlIG9mIGEgKk11bHRpIENyaXRlcmlhIE92ZXJsYXkgQW5hbHlzaXMqIChNQ0EpIHdoZXJlYnkgZWFjaCBpbmRpdmlkdWFsIGNyaXRlcmlhIGlzIGNvbnZlcnRlZCBpbnRvIGEgYmluYXJ5IHJhc3RlciBtYXNrLCB3aGljaCBhcmUgdGhlbiBmZWQgaW50byBhIHJhc3RlciBhbGdlYnJhIGV4cHJlc3Npb24gdG8gZmluZCB0aGUgcGl4ZWxzIHdoZXJlIGFsbCBjcml0ZXJpYSBhcmUgbWV0LiBUaGUgdHJpY2sgdG8gZG9pbmcgdGhpcyBpcyB0byBtYWtlIHN1cmUgZWFjaCBiaW5hcnkgDQoNClwNCg0KIyMgSW1wb3J0IExheWVycyBmb3IgWW9zZW1pdGUgTmF0aW9uYWwgUGFyaw0KDQpGb3IgdGhpcyBhbmFseXNpcyB3ZSdsbCBuZWVkIHRoZSBwYXJrIGJvdW5kYXJ5LCB0aGUgdHJhaWxzIG5ldHdvcmsgYW5kIGEgREVNIChlbGV2YXRpb24pIG9mIHRoZSBwYXJrIChmcm9tIHdoaWNoIHdlIGNhbiBkZXJpdmUgc2xvcGUpLg0KDQpCZWdpbiBieSBpbXBvcnRpbmcgdGhlIHZlY3RvciBsYXllcnM6DQoNCmBgYHtyIGNodW5rMDF9DQpsaWJyYXJ5KHNmKQ0KDQojIyBJbXBvcnQgdGhlIHBhcmsgYm91bmRhcnkNCmJuZF9mbiA8LSAiLi9kYXRhL3lvc2VfYm91bmRhcnkuc2hwIg0KZmlsZS5leGlzdHMoYm5kX2ZuKQ0KeW9zZV9ibmRfbGwgPC0gc3RfcmVhZChibmRfZm4pDQoNCiMjIEltcG9ydCB0aGUgdHJhaWxzDQp0cmFpbHNfZ2RiX2ZuIDwtICIuL2RhdGEveW9zZV90cmFpbHMuZ2RiIg0KZmlsZS5leGlzdHModHJhaWxzX2dkYl9mbikNCnlvc2VfdHJhaWxzIDwtIHN0X3JlYWQodHJhaWxzX2dkYl9mbiwgbGF5ZXI9IlRyYWlscyIpDQoNCiMjIEltcG9ydCB0aGUgcml2ZXJzIGFuZCBzdHJlYW1zDQpoeWRyb19nZGJfZm4gPC0gIi4vZGF0YS95b3NlX2h5ZHJvbG9neS5nZGIiDQpmaWxlLmV4aXN0cyhoeWRyb19nZGJfZm4pDQp5b3NlX3N0cmVhbXMgPC0gc3RfcmVhZChoeWRyb19nZGJfZm4sIGxheWVyPSJSaXZlcnMiKQ0KYGBgDQoNCiMjIFByb2plY3QgRXZlcnl0aGluZyB0byBVVE0NCg0KQmVjYXVzZSBvbmUgb2Ygb3VyIGNyaXRlcmlhIGludm9sdmVzIGFyZWEsIHdlJ2xsIHByb2plY3QgZXZlcnl0aGluZyB0byBVVE0gMTFOIChFUFNHIDMyNjExKToNCg0KYGBge3IgY2h1bmswMn0NCmVwc2dfdXRtMTFuX3dnczg0IDwtIDMyNjExDQoNCnlvc2VfYm5kX3V0bSA8LSBzdF90cmFuc2Zvcm0oeW9zZV9ibmRfbGwsIGVwc2dfdXRtMTFuX3dnczg0KQ0KDQp5b3NlX3RyYWlsc191dG0gPC0gc3RfdHJhbnNmb3JtKHlvc2VfdHJhaWxzLCBlcHNnX3V0bTExbl93Z3M4NCkNCg0KeW9zZV9zdHJlYW1zX3V0bSA8LSBzdF90cmFuc2Zvcm0oeW9zZV9zdHJlYW1zLCBlcHNnX3V0bTExbl93Z3M4NCkNCg0KYGBgDQoNClBsb3QgdGhlbSB0byB2ZXJpZnkgaXQgd29ya2VkOg0KDQpgYGB7ciBjaHVuazAzfQ0Ke3Bsb3QoeW9zZV9ibmRfdXRtICU+JSBzdF9nZW9tZXRyeSgpLCBib3JkZXIgPSAicmVkIiwgbHdkID0gMikNCnBsb3QoeW9zZV9zdHJlYW1zX3V0bSAlPiUgc3RfZ2VvbWV0cnkoKSwgY29sID0gImxpZ2h0Y3lhbjIiLCBhZGQgPSBUUlVFKQ0KcGxvdCh5b3NlX3RyYWlsc191dG0gJT4lIHN0X2dlb21ldHJ5KCksIGNvbCA9ICJncmV5NTAiLCBhZGQgPSBUUlVFKX0NCmBgYA0KDQojIyBJbXBvcnQgdGhlIERFTQ0KDQpXZSBoYXZlIGVsZXZhdGlvbiBkYXRhIGluIHRoZSBmb3JtIG9mIGEgdGlsZSBvZiBTUlRNIGRhdGEuDQoNCmBgYHtyIGNodW5rMDR9DQpsaWJyYXJ5KHJhc3RlcikNCnNydG0xMzA1X2ZuIDwtICIuL2RhdGEvc3J0bV8xM18wNS50aWYiDQpmaWxlLmV4aXN0cyhzcnRtMTMwNV9mbikNCnNydG0xMzA1X3JzdCA8LSByYXN0ZXIoc3J0bTEzMDVfZm4pDQpzcnRtMTMwNV9yc3QNCmBgYA0KDQojIyBDcm9wIHRoZSBERU0gdG8gdGhlIFBhcmsgQm91bmRhcnkNCg0KTm90ZSB0aGUgU1JUTSBkYXRhIGlzIGluIGdlb2dyYXBoaWMgY29vcmRpbmF0ZXMuIFRvIHNlZSBob3cgYmlnIGl0IGlzIHdlJ2xsIG92ZXJsYXkgdGhlIHBhcmsgYm91bmRhcnkgb24gaXQ6DQoNCmBgYHtyIGNodW5rMDV9DQp7cGxvdChzcnRtMTMwNV9yc3QpDQpwbG90KHlvc2VfYm5kX2xsICU+JSBzdF9nZW9tZXRyeSgpLCBib3JkZXIgPSAicmVkIiwgbHdkID0gMiwgYWRkID0gVFJVRSl9DQpgYGANCg0KYGBge3IgY2h1bmswNn0NCnlvc2VfZGVtX3JzdF9sbCA8LSByYXN0ZXI6OmNyb3Aoc3J0bTEzMDVfcnN0LCB5b3NlX2JuZF9sbCkNCnlvc2VfZGVtX3JzdF9sbA0KYGBgDQoNClBsb3QgdG8gbWFrZSBzdXJlIGl0IHdvcmtlZDoNCg0KYGBge3IgY2h1bmswN30NCntwbG90KHlvc2VfZGVtX3JzdF9sbCkNCnBsb3QoeW9zZV9ibmRfbGwgJT4lIHN0X2dlb21ldHJ5KCksIGJvcmRlciA9ICJyZWQiLCBsd2QgPSAyLCBhZGQgPSBUUlVFKX0NCmBgYA0KDQpMYXN0bHksIHdlIG5lZWQgdG8gcHJvamVjdCB0aGUgcGFyayBERU0gaW50byBVVE0gdXNpbmcgYHByb2plY3RSYXN0ZXIoKWAgd2l0aCB0aGUgZm9sbG93aW5nIGFyZ3VtZW50czoNCg0KLSBgZnJvbWA6IHRoZSByYXN0ZXIgd2Ugd2FudCB0byBwcm9qZWN0IA0KLSBgdG9gOiBvbWl0dGVkIGluIHRoaXMgY2FzZSAoYmVjYXVzZSB3ZSdyZSBzdXBwbHlpbmcgYGNyc2AgYW5kIGByZXNgKSAgDQotIGBjcnNgOiBwYXNzIGEgcHJvajQgc3RyaW5nICANCi0gYHJlc2A6IHJlc29sdXRpb24gaW4gbWFwIHVuaXRzICh3aGljaCBpbiB0aGUgY2FzZSBvZiBVVE0gaXMgbWV0ZXJzKSAgDQotIGBtZXRob2RgOiBjaG9vc2UgJ2JsaW5lYXInIGJ1dCB0aGUgY2VsbCB2YWx1ZXMgKGVsZXZhdGlvbiBNU0wpIGFyZSBjb250aW51b3VzLCBhbmQgd2Ugd2FudCB0aGUgaW50ZXJwb2xhdGVkIHZhbHVlcyB0byBhbHNvIGJlIGNvbnRpbnVvdXMgIA0KDQpgYGB7ciBjaHVuazA4fQ0KIyMgRGVmaW5lIHRoZSBwcm9qNCBzdHJpbmcgZm9yIFVUTSAxMU4NCnV0bTExbl93Z3M4NF9wcm9qNCA8LSAiK2luaXQ9ZXBzZzozMjYxMSINCg0KeW9zZV9kZW1fcnN0X3V0bSA8LSBwcm9qZWN0UmFzdGVyKGZyb20gPSB5b3NlX2RlbV9yc3RfbGwsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY3JzID0gQ1JTKHV0bTExbl93Z3M4NF9wcm9qNCksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVzID0gOTAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImJpbGluZWFyIikNCnlvc2VfZGVtX3JzdF91dG0NCmBgYA0KDQpQbG90IHRvIG1ha2Ugc3VyZSBpdCB3b3JrZWQ6DQoNCmBgYHtyIGNodW5rMDl9DQp7cGxvdCh5b3NlX2RlbV9yc3RfdXRtKQ0KcGxvdCh5b3NlX2JuZF91dG0gJT4lIHN0X2dlb21ldHJ5KCksIGJvcmRlciA9ICJyZWQiLCBsd2QgPSAyLCBhZGQgPSBUUlVFKX0NCmBgYA0KDQojIyBDb21wdXRlIFNsb3BlDQoNClNsb3BlIGlzIG9uZSBvZiB0aGUgY3JpdGVyaWEgZm9yIG91ciBjYW1wc2l0ZS4gV2UgY2FuIGNvbXB1dGUgdGhlIHNsb3BlIGZyb20gdGhlIERFTSB1c2luZyB0aGUgYHJhc3Rlcjo6dGVycmFpbigpYDoNCg0KYGBge3IgY2h1bmsxMH0NCnlvc2Vfc2xvcGVfcnN0IDwtIHJhc3Rlcjo6dGVycmFpbih5b3NlX2RlbV9yc3RfdXRtLCBvcHQ9InNsb3BlIikNCnlvc2Vfc2xvcGVfcnN0DQpgYGANCg0KUGxvdCB0byBtYWtlIHN1cmUgaXQgd29ya2VkLiBOb3RlIHRoYXQgc2xvcGUgaXMgbWVhc3VyZWQgaW4gcmFkaWFucy4NCg0KYGBge3IgY2h1bmsxMX0NCnBsb3QoeW9zZV9zbG9wZV9yc3QpDQpgYGANCg0KIyMgQ3JlYXRlIGEgQmluYXJ5IE1hc2sgZm9yIFNsb3BlDQoNCldlIGNyZWF0ZSBhIGJpbmFyeSBtYXNrIG9mIHRoZSBwaXhlbHMgd2hvc2Ugc2xvcGUgPCAxMCUuIA0KDQpgYGB7ciBjaHVuazEyfQ0KIyMgVG8gY29udmVydCB0aGUgcGVyY2VudCBzbG9wZSB0byBhbiBhbmdsZSwgd2UgdXNlIGFyY3RhbiAod2hpY2ggcmV0dXJucyByYWRpYW5zKQ0KbWF4X3Nsb3BlX3JhZCA8LSBhdGFuKDAuMSkgDQoNCiMjIENyZWF0ZSB0aGUgVFJVRSAvIEZBTFNFIG1hc2sgd2l0aCBhIHJhc3RlciBhbGdlYnJhIGV4cHJlc3Npb24NCnlvc2VfbG93c2xvcGVfbXNrX3JzdCA8LSAoeW9zZV9zbG9wZV9yc3QgPD0gbWF4X3Nsb3BlX3JhZCkNCnlvc2VfbG93c2xvcGVfbXNrX3JzdA0KYGBgDQoNClBsb3QgdG8gbWFrZSBzdXJlIGl0IHdvcmtlZDoNCg0KYGBge3IgY2h1bmsxM30NCnBsb3QoeW9zZV9sb3dzbG9wZV9tc2tfcnN0LCBsZWdlbmQgPSBGQUxTRSwgbWFpbiA9ICJMb3cgU2xvcGUgQXJlYXMiKQ0KYGBgDQoNCiMjIENvbXB1dGUgdGhlIERpc3RhbmNlIHRvIFRyYWlscyBTdXJmYWNlDQoNCldlJ2xsIGNvbXB1dGUgdGhlIGRpc3RhbmNlIHRvIHRyYWlscyBzdXJmYWNlIChhbHNvIGtub3duIGFzIGEgcHJveGltaXR5IHN1cmZhY2UpIHVzaW5nIGByYXN0ZXI6OmRpc3RhbmNlKClgLiBUaGUgcmFzdGVyIHdlIHBhc3MgdG8gdGhlIGZ1bmN0aW9uIHNob3VsZCBjb250YWluICcxJyB2YWx1ZXMgZm9yIGFsbCBjZWxscyB0aGF0IHdlIHdhbnQgdG8gY29tcHV0ZSBkaXN0YW5jZSB0byAoaS5lLiwgdGhlIHRyYWlscyksIGFuZCBgTkFgIGZvciBldmVyeXRoaW5nIGVsc2UuICANCg0KVGhlcmVmb3JlIHdlIGZpcnN0IG5lZWQgdG8gY3JlYXRlIGEgcmFzdGVyaXplZCB2ZXJzaW9uIG9mIHRoZSB0cmFpbHMuIEZvciB0aGlzIHdlIHVzZSBgcmFzdGVyOjpyYXN0ZXJpemUoKWAsIHBhc3NpbmcgIGB5b3NlZGVtX3JzdF91dG1gIGFzIHRoZSAndGVtcGxhdGUnIGZvciB0aGUgbmV3IHJhc3RlciBsYXllciwgYW5kIGZpZWxkID0gMSB0byB0ZWxsIGl0IHRvIGFzc2lnbiBhICcxJyB0byBjZWxscyB0aGF0IGNvbnRhaW4gYSB0cmFpbC4NCg0KYGBge3IgY2h1bmsxNH0NCiMgVGhlIHJhc3Rlcjo6cmFzdGVyaXplKCkgZnVuY3Rpb24gd29ya3MsIGJ1dCB0YWtlcyA0LTUgbWludXRlcyENCiMgeW9zZV90cmFpbHNfcnN0X3V0bSA8LSByYXN0ZXI6OnJhc3Rlcml6ZSh4ID0geW9zZV90cmFpbHNfdXRtLCANCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IHlvc2VfZGVtX3JzdF91dG0sDQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpZWxkID0gMSkNCg0KIyMgRmFzdGVyIG1ldGhvZCB1c2luZyBzdGFycw0KbGlicmFyeShzdGFycykNCmxpYnJhcnkoZHBseXIpDQoNCiMjIFJhc3Rlcml6ZSB0aGUgdHJhaWxzIGFzIGEgc3RhcnMgb2JqZWN0DQp5b3NlX3RyYWlsc19zdGFycyA8LSBzdGFyczo6c3RfcmFzdGVyaXplKHNmID0geW9zZV90cmFpbHNfdXRtICU+JSB0cmFuc211dGUoZmllbGQgPSAxKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgdGVtcGxhdGUgPSBzdGFyczo6c3RfYXNfc3RhcnMoc2V0VmFsdWVzKHlvc2VfZGVtX3JzdF91dG0sIE5BKSkpDQogIA0KIyMgQ29udmVydCB0aGUgc3RhcnMgb2JqZWN0IHRvIGEgcmFzdGVyIG9iamVjdA0KeW9zZV90cmFpbHNfcnN0X3V0bSA8LSBhcyh5b3NlX3RyYWlsc19zdGFycywgIlJhc3RlciIpDQp5b3NlX3RyYWlsc19yc3RfdXRtDQpgYGANCg0KUGxvdCBpdCB0byBtYWtlIHN1cmUgaXQgbG9va3MgcmlnaHQ6DQoNCmBgYHtyIGNodW5rMTV9DQpwbG90KHlvc2VfdHJhaWxzX3JzdF91dG0sIGNvbCA9ICJyZWQiLCBsZWdlbmQgPSBGQUxTRSwgbWFpbiA9ICJZb3NlbWl0ZSBUcmFpbHMgUmFzdGVyaXplZCIpDQpgYGANCg0KVmlldyBob3cgbWFueSBjZWxscyBoYXZlIGEgdHJhaWw6DQoNCmBgYHtyIGNodW5rMTZ9DQpmcmVxKHlvc2VfdHJhaWxzX3JzdF91dG0pDQpgYGANCg0KTmV4dCB3ZSBjb21wdXRlIHRoZSBkaXN0YW5jZSBzdXJmYWNlLiBOb3RlIHRoaXMgY2FuIHRha2UgdXAgdG8gYSBtaW51dGUuDQoNCmBgYHtyIGNodW5rMTcsIGNhY2hlPVRSVUV9DQojIyBDb21wdXRlIHRoZSBkaXN0YW5jZSBzdXJmYWNlLiBOb3RlIHRoaXMgY2FuIHRha2UgdXAgdG8gYSBtaW51dGUuDQp5b3NlX2Rpc3QydHJhaWxzX3JzdF91dG0gPC0gcmFzdGVyOjpkaXN0YW5jZSh5b3NlX3RyYWlsc19yc3RfdXRtKQ0KDQojIyBQbG90IA0Ke3Bsb3QoeW9zZV9kaXN0MnRyYWlsc19yc3RfdXRtLCBtYWluID0gIllOUCBEaXN0YW5jZSB0byBUcmFpbHMgKG0pIikNCnBsb3QoeW9zZV90cmFpbHNfdXRtICU+JSBzdF9nZW9tZXRyeSgpLCBjb2w9InJlZCIsIGFkZD1UUlVFKQ0KcGxvdCh5b3NlX2JuZF91dG0gJT4lICBzdF9nZW9tZXRyeSgpLCBjb2w9TkEsIGJvcmRlcj0iYmxhY2siLCBsd2Q9MiwgYWRkPVRSVUUpfQ0KYGBgDQoNCiMjIENyZWF0ZSBhIEJpbmFyeSBNYXNrIGZvciBEaXN0YW5jZSB0byBUcmFpbHMNCg0KT3VyIGNyaXRlcmlhIHN0YXRlIHRoYXQgdGhlIG5ldyBjYW1wc2l0ZSBtdXN0IGJlIDwxMDAgbSBmcm9tIGFuIGV4aXN0aW5nIHRyYWlsLg0KDQpgYGB7ciBjaHVuazE4fQ0KIyMgQ3JlYXRlIHRoZSBUUlVFIC8gRkFMU0UgbWFzayB3aXRoIGEgcmFzdGVyIGFsZ2VicmEgZXhwcmVzc2lvbg0KeW9zZV9kaXN0MnRyYWlsc19tc2tfdXRtIDwtICh5b3NlX2Rpc3QydHJhaWxzX3JzdF91dG0gPD0gMTAwKQ0KeW9zZV9kaXN0MnRyYWlsc19tc2tfdXRtDQpgYGANCg0KUGxvdCB0byBtYWtlIHN1cmUgaXQgd29ya2VkOg0KDQpgYGB7ciBjaHVuazE5fQ0KcGxvdCh5b3NlX2Rpc3QydHJhaWxzX21za191dG0sIGxlZ2VuZCA9IEZBTFNFLCBtYWluID0gIkFyZWFzIDwxMDBtIGZyb20gdHJhaWxzIikNCmBgYA0KDQoNCg0KDQojIyBDb21wdXRlIHRoZSBEaXN0YW5jZSB0byBTdHJlYW1zIFN1cmZhY2UNCg0KRGlzdGFuY2UgdG8gc3RyZWFtcyBpcyBjb21wdXRlZCB0aGUgc2FtZSB3YXkgd2UgY29tcHV0ZWQgZGlzdGFuY2UgdG8gdHJhaWxzOg0KDQpgYGB7ciBjaHVuazIwfQ0KIyMgUmFzdGVyaXplIHRoZSBzdHJlYW1zIHVzaW5nIHN0YXJzIA0KeW9zZV9zdHJlYW1zX3JzdF91dG0gPC0gc3RhcnM6OnN0X3Jhc3Rlcml6ZShzZiA9IHlvc2Vfc3RyZWFtc191dG0gJT4lIHRyYW5zbXV0ZShmaWVsZCA9IDEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICB0ZW1wbGF0ZSA9IHN0YXJzOjpzdF9hc19zdGFycyhzZXRWYWx1ZXMoeW9zZV9kZW1fcnN0X3V0bSwgTkEpKSkgJT4lDQogIGFzKC4sICJSYXN0ZXIiKQ0KeW9zZV9zdHJlYW1zX3JzdF91dG0NCg0KIyBQbG90IGl0IHRvIG1ha2Ugc3VyZSBpdCBsb29rcyByaWdodDoNCnBsb3QoeW9zZV9zdHJlYW1zX3JzdF91dG0sIGNvbCA9ICJyZWQiLCBsZWdlbmQgPSBGQUxTRSwgbWFpbiA9ICJZb3NlbWl0ZSBTdHJlYW1zIFJhc3Rlcml6ZWQiKQ0KDQojIyBDb21wdXRlIHRoZSBkaXN0YW5jZSBzdXJmYWNlLiBOb3RlIHRoaXMgY2FuIHRha2UgYSBjb3VwbGUgb2YgbWludXRlcw0KeW9zZV9kaXN0MnN0cmVhbXNfcnN0X3V0bSA8LSByYXN0ZXI6OmRpc3RhbmNlKHlvc2Vfc3RyZWFtc19yc3RfdXRtKQ0KDQojIyBQbG90IA0Ke3Bsb3QoeW9zZV9kaXN0MnN0cmVhbXNfcnN0X3V0bSwgbWFpbiA9ICJZTlAgRGlzdGFuY2UgdG8gU3RyZWFtcyAobSkiKQ0KcGxvdCh5b3NlX3N0cmVhbXNfdXRtICU+JSBzdF9nZW9tZXRyeSgpLCBsd2QgPSAwLjEsIGNleCA9IDAuMSwgY29sPSJibHVlIiwgYWRkPVRSVUUpDQpwbG90KHlvc2VfYm5kX3V0bSAlPiUgIHN0X2dlb21ldHJ5KCksIGNvbD1OQSwgYm9yZGVyPSJibGFjayIsIGx3ZD0yLCBhZGQ9VFJVRSl9DQpgYGANCg0KIyMgQ3JlYXRlIGEgQmluYXJ5IE1hc2sgZm9yIERpc3RhbmNlIHRvIFN0cmVhbXMNCg0KT3VyIGNyaXRlcmlhIHN0YXRlIHRoYXQgdGhlIG5ldyBjYW1wc2l0ZSBtdXN0IGJlIDwxMDAgbSBmcm9tIGEgc3RyZWFtLg0KDQpgYGB7ciBjaHVuazIxfQ0KIyMgQ3JlYXRlIHRoZSBUUlVFIC8gRkFMU0UgbWFzayB3aXRoIGEgcmFzdGVyIGFsZ2VicmEgZXhwcmVzc2lvbg0KeW9zZV9kaXN0MnN0cmVhbXNfbXNrX3V0bSA8LSAoeW9zZV9kaXN0MnN0cmVhbXNfcnN0X3V0bSA8PSAxMDApDQp5b3NlX2Rpc3Qyc3RyZWFtc19tc2tfdXRtDQpgYGANCg0KUGxvdCB0byBtYWtlIHN1cmUgaXQgd29ya2VkOg0KDQpgYGB7ciBjaHVuazIyfQ0KcGxvdCh5b3NlX2Rpc3Qyc3RyZWFtc19tc2tfdXRtLCBsZWdlbmQgPSBGQUxTRSwgbWFpbiA9ICJBcmVhcyA8MTAwbSBmcm9tIHN0cmVhbXMiKQ0KYGBgDQoNCg0KIyMgQ29tYmluZSBNYXNrcw0KDQpUaGUgbGFzdCBzdGVwIGlzIHRvIG11bHRpcGx5IG91ciB0aHJlZSBtYXNrcyB0b2dldGhlciB1c2luZyByYXN0ZXIgYWxnZWJyYS4gVGhlIHJlc3VsdCB3aWxsIGJlIGEgcmFzdGVyIHdoZXJlICcxJyBpbiBjZWxsIHdoZXJlIGFsbCB0aHJlZSBjcml0ZXJpYSB3ZXJlIG1ldCwgYW5kIDAgZXZlcnl3aGVyZSBlbHNlLg0KDQpgYGB7ciBjaHVuazIzfQ0KY2FtcHNpdGVfb2tfcnN0IDwtIHlvc2VfZGlzdDJ0cmFpbHNfbXNrX3V0bSAqIHlvc2VfbG93c2xvcGVfbXNrX3JzdCAqIHlvc2VfZGlzdDJzdHJlYW1zX21za191dG0NCmNhbXBzaXRlX29rX3JzdA0KYGBgDQoNClBsb3QgdG8gbWFrZSBzdXJlIGl0IHdvcmtlZDoNCg0KYGBge3IgY2h1bmsyNH0NCntwbG90KGNhbXBzaXRlX29rX3JzdCwgbWFpbiA9ICJTdWl0YWJsZSBBcmVhcyBmb3IgQ2FtcHNpdGVzIGluIFlOUCIpDQpwbG90KHlvc2VfYm5kX3V0bSAlPiUgIHN0X2dlb21ldHJ5KCksIGNvbD1OQSwgYm9yZGVyPSJibGFjayIsIGx3ZD0yLCBhZGQ9VFJVRSl9DQpgYGANCg0KIyMgQ3JvcCBhbmQgTWFzayB0aGUgUmVzdWx0cyB0byB0aGUgUGFyayBCb3VuZGFyeQ0KDQpXZSBjYW4ndCBidWlsZCBjYW1wc2l0ZXMgb3V0c2lkZSB0aGUgcGFyaywgc28gb3VyIGxhc3QgdGFzayBpcyB0byBjcm9wIGFuZCBtYXNrIHRoZSByZXN1bHRzIHRvIHRoZSBwYXJrIGJvdW5kYXJ5LiBBY3R1YWxseSB3ZSBkb24ndCBoYXZlIHRvIGNyb3AsIHdlIGRpZCB0aGF0IHdoZW4gd2UgY3JvcHBlZCB0aGUgU1JUTSB0aWxlIGFib3ZlLiANCg0KYGBge3IgY2h1bmsyNX0NCmNhbXBzaXRlX29rX21za19yc3QgPC0gcmFzdGVyOjptYXNrKGNhbXBzaXRlX29rX3JzdCwgeW9zZV9ibmRfdXRtKQ0KY2FtcHNpdGVfb2tfbXNrX3JzdA0KYGBgDQoNClBsb3QgaXQgdG8gbWFrZSBzdXJlIGl0IHdvcmtlZDoNCg0KYGBge3IgY2h1bmsyNn0NCntwbG90KGNhbXBzaXRlX29rX21za19yc3QsIG1haW4gPSAiU3VpdGFibGUgQXJlYXMgZm9yIENhbXBzaXRlcyBpbiBZTlAiKQ0KcGxvdCh5b3NlX2JuZF91dG0gJT4lICBzdF9nZW9tZXRyeSgpLCBjb2w9TkEsIGJvcmRlcj0iYmxhY2siLCBsd2Q9MiwgYWRkPVRSVUUpfQ0KYGBgDQoNClwNCg0KKipDSEFMTEVOR0UqKjogV2hhdCBpcyB0aGUgdG90YWwgYXJlYSBzdWl0YWJsZSBmb3IgY2FtcHNpdGVzPw0KDQpgYGB7ciBjaHVuazI3fQ0KIyMgRmluZCBvdXQgaG93IG1hbnkgcGl4ZWxzIGhhdmUgYSB2YWx1ZSBvZiAnMScNCnBpeGVsX3ZhbHNfbWF0IDwtIGZyZXEoY2FtcHNpdGVfb2tfbXNrX3JzdCkNCm51bV9waXhlbHNfdmFsb25lIDwtIHBpeGVsX3ZhbHNfbWF0WyB3aGljaChwaXhlbF92YWxzX21hdFssMV0gPT0gMSksICJjb3VudCJdDQoNCiMjIE11bHRpcGxlIHRoZSBudW1iZXIgb2YgcGl4ZWxzID0gMSB0aW1lcyB0aGUgYXJlYSBvZiBhIHNpbmdsZSBwaXhlbA0KYXJlYV9tMiA8LSAobnVtX3BpeGVsc192YWxvbmUgKiA5MCBeIDIpICU+JSBhcy5udW1lcmljKCkNCmFyZWFfYWNyZXMgPC0gYXJlYV9tMiAvIDQwNDYuODYgDQphcmVhX2FjcmVzDQpgYGANCg0KXA0KDQojIyBDSEFMTEVOR0U6IE9uZSBtb3JlIGNyaXRlcmlhDQoNCkFkZCBvbmUgbW9yZSBjcml0ZXJpYSAtIGV4Y2x1ZGUgYXJlYXMgd2l0aGluIDEwMG0gb2YgYW4gZXhpc3RpbmcgY2FtcHNpdGUuIEhpbnQ6IGNhbXBzaXRlcyBhcmUgaW5jbHVkZWQgaW4gdGhlIHlvc2VfcG9pLnNocCBsYXllci4NCg0KYGBge3IgY2h1bmsyOH0NCiMjIEltcG9ydCB0aGUgY2FtcHNpdGVzIGZyb20gdGhlIHBvaW50cy1vZi1pbnRlcmVzdCBTaGFwZWZpbGUNCnBvaV9zaHBfZm4gPC0gIi4vZGF0YS95b3NlX3BvaS5zaHAiDQpmaWxlLmV4aXN0cyhwb2lfc2hwX2ZuKQ0KDQojIyBJbXBvcnQgdGhlIFBPSSwgZmlsdGVyIHRvIGNhbXBzaXRlcywgcHJvamVjdCB0byBVVE0gMTFOIFdHUzg0LCBhbmQga2VlcCBqdXN0IHRoZSBuYW1lIGFuZCB0eXBlIGNvbHVtbnMNCnlvc2VfcG9pX3V0bSA8LSBzdF9yZWFkKHBvaV9zaHBfZm4pICU+JSANCiAgZmlsdGVyKFBPSVRZUEUgPT0gIkNhbXBzaXRlIikgJT4lIA0KICBzdF90cmFuc2Zvcm0oZXBzZ191dG0xMW5fd2dzODQpICU+JSANCiAgc2VsZWN0KFBPSU5BTUUsIFBPSVRZUEUpDQoNCiMjIFBsb3QgdGhlIGNhbXBzaXRlcyB0byBtYWtlIHN1cmUgaXQgd29ya2VkDQp7cGxvdCh5b3NlX2JuZF91dG0gJT4lICBzdF9nZW9tZXRyeSgpLCBjb2w9TkEsIGJvcmRlcj0iYmxhY2siLCBsd2Q9MiwgYXhlcyA9IFRSVUUpDQpwbG90KHlvc2VfcG9pX3V0bSAlPiUgc3RfZ2VvbWV0cnkoKSwgcGNoID0gMTYsIGNleCA9IDAuMSwgY29sb3IgPSAiYnJvd24iLCBhZGQgPSBUUlVFKX0NCg0KIyMgQ3JlYXRlIGEgYnVmZmVyIGFyb3VuZCB0aGUgY2FtcHNpdGVzDQp5b3NlX3BvaWJ1ZmZfdXRtIDwtIHN0X2J1ZmZlcih5b3NlX3BvaV91dG0sIGRpc3QgPSAxMDApDQoNCiMjIFJhc3Rlcml6ZSB0aGUgY2FtcHNpdGUgYnVmZmVycyB1c2luZyBzdGFycyANCnlvc2VfcG9pYnVmX3JzdF91dG0gPC0gc3RhcnM6OnN0X3Jhc3Rlcml6ZShzZiA9IHlvc2VfcG9pYnVmZl91dG0gJT4lIHRyYW5zbXV0ZShmaWVsZCA9IDEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICB0ZW1wbGF0ZSA9IHN0YXJzOjpzdF9hc19zdGFycyhzZXRWYWx1ZXMoeW9zZV9kZW1fcnN0X3V0bSwgTkEpKSkgJT4lDQogIGFzKC4sICJSYXN0ZXIiKQ0KDQojIFBsb3QgdGhlIHJhc3Rlcml6ZWQgYnVmZmVycyB0byBtYWtlIHN1cmUgaXQgbG9va3MgcmlnaHQ6DQpwbG90KHlvc2VfcG9pYnVmX3JzdF91dG0sIGxlZ2VuZCA9IEZBTFNFLCBjb2wgPSAiYmxhY2siLCBtYWluID0gIkV4aXN0aW5nIENhbXBzaXRlIEJ1ZmZlcnMgUmFzdGVyaXplZCIpDQoNCiMjIENyZWF0ZSBhIGJpbmFyeSBtYXNrIHVzaW5nIGEgcmFzdGVyIGFsZ2VicmEgZXhwcmVzc2lvbg0KeW9zZV9wb2lidWZtc2tfcnN0X3V0bSA8LSAoaXMubmEoeW9zZV9wb2lidWZfcnN0X3V0bSkpDQoNCiMjIFNlZSBob3cgbWFueSBwaXhlbHMgaGF2ZSBhIHZhbHVlIG9mIDENCmZyZXEoeW9zZV9wb2lidWZtc2tfcnN0X3V0bSkNCg0KIyMgUGxvdCBpdCB0byBtYWtlIHN1cmUNCnBsb3QoeW9zZV9wb2lidWZtc2tfcnN0X3V0bSwgbWFpbiA9ICJBcmVhcyBBd2F5IGZyb20gRXhpc3RpbmcgQ2FtcHNpdGVzIikNCg0KIyMgQ29tYmluZSB3aXRoIHByZXZpb3VzIG1hc2tzDQpjYW1wc2l0ZV9vazJfcnN0IDwtIGNhbXBzaXRlX29rX21za19yc3QgKiB5b3NlX3BvaWJ1Zm1za19yc3RfdXRtDQpjYW1wc2l0ZV9vazJfcnN0DQpgYGANCg0KUGxvdCB0byBtYWtlIHN1cmUgaXQgd29ya2VkOg0KDQpgYGB7ciBjaHVuazI5fQ0Ke3Bsb3QoY2FtcHNpdGVfb2syX3JzdCwgbWFpbiA9ICJTdWl0YWJsZSBBcmVhcyBmb3IgTkVXIENhbXBzaXRlcyBpbiBZTlAiKQ0KcGxvdCh5b3NlX2JuZF91dG0gJT4lICBzdF9nZW9tZXRyeSgpLCBjb2w9TkEsIGJvcmRlcj0iYmxhY2siLCBsd2Q9MiwgYWRkPVRSVUUpfQ0KYGBgDQoNCiMjIEVuZA0KDQpTYXZlIHlvdXIgTm90ZWJvb2sgdG8gZ2VuZXJhdGUgYSBIVE1MIHByZXZpZXcgb2YgdGhlIGNvZGUgeW91IHJhbi4NCg0K