Notebook Instructions


OpenStreetMap: Is open data built by a community of mappers that contribute and maintain data about roads, trails, cafés, railway stations, and much more, all over the world.

Load Packages in R/RStudio

We are going to use tidyverse a collection of R packages designed for data science. As well as other packages to help with data cleaning and processing.


Geocoding: OpenStreetMap API Search Functions


The geocode function below allow us to make requests to Nominatim a search engine for OpenStreetMap data.

# INPUT LOCATION VARIABLES
# [RECORD_ID], [ADDRESS], [CITY], [STATE], [ZIPCODE]


geocode <- function(record_id, address, city, state, zipcode){
  
  # NOMINATIM SEARCH API URL
  src_url <- "https://nominatim.openstreetmap.org/search?q="
  
  ###### INPUTS PREPARATION ##### 

  city <- str_replace_all(string = city, 
                          pattern = "\\s|,", 
                          replacement = "+")
  
  # CREATE A FULL ADDRESS
  addr <- paste(address, city, state, zipcode, sep = "%2C")
  
  # CREATE A SEARCH URL BASED ON NOMINATIM API TO RETURN GEOJSON
  requests <- paste0(src_url, addr, "&format=geojson")
  
  for (i in 1:length(requests)) {
    
    response <- read_html(requests[i]) %>%
      html_node("p") %>%
      html_text() %>%
      fromJSON()
    
  # FROM THE RESPONSE EXTRACT LATITUDE AND LONGITUDE COORDINATES
    lon <- response$features$geometry$coordinates[[1]][1]
    lat <- response$features$geometry$coordinates[[1]][2]
    
    # CREATE A COORDINATES DATAFRAME
    if(i == 1) {
      loc <- tibble(record_id = record_id[i], 
                    address = str_replace_all(addr[i], "%2C", ","),
                    latitude = lat, longitude = lon)
    }else{
      df <- tibble(record_id = record_id[i], 
                   address = str_replace_all(addr[i], "%2C", ","),
                   latitude = lat, longitude = lon)
      loc <- bind_rows(loc, df)
    }
  }
 return(loc)
    
}

Data Source: San Francisco Buildings Permit Data


This data set pertains to all types of structural permits. Data includes details on application/permit numbers, job addresses, supervisorial districts, and the current status of the applications.


data <- read.csv("data/building_permits.csv", check.names=FALSE)

Data Inspection

head(data)
tail(data)
colnames(data)
 [1] "Permit Number"                                                 
 [2] "Permit Type"                                                   
 [3] "Permit Type Definition"                                        
 [4] "Permit Creation Date"                                          
 [5] "Block"                                                         
 [6] "Lot"                                                           
 [7] "Street Number"                                                 
 [8] "Street Number Suffix"                                          
 [9] "Street Name"                                                   
[10] "Street Suffix"                                                 
[11] "Unit"                                                          
[12] "Unit Suffix"                                                   
[13] "Description"                                                   
[14] "Current Status"                                                
[15] "Current Status Date"                                           
[16] "Filed Date"                                                    
[17] "Issued Date"                                                   
[18] "Completed Date"                                                
[19] "First Construction Document Date"                              
[20] "Structural Notification"                                       
[21] "Number of Existing Stories"                                    
[22] "Number of Proposed Stories"                                    
[23] "Voluntary Soft-Story Retrofit"                                 
[24] "Fire Only Permit"                                              
[25] "Permit Expiration Date"                                        
[26] "Estimated Cost"                                                
[27] "Revised Cost"                                                  
[28] "Existing Use"                                                  
[29] "Existing Units"                                                
[30] "Proposed Use"                                                  
[31] "Proposed Units"                                                
[32] "Plansets"                                                      
[33] "TIDF Compliance"                                               
[34] "Existing Construction Type"                                    
[35] "Existing Construction Type Description"                        
[36] "Proposed Construction Type"                                    
[37] "Proposed Construction Type Description"                        
[38] "Site Permit"                                                   
[39] "Supervisor District"                                           
[40] "Neighborhoods - Analysis Boundaries"                           
[41] "Zipcode"                                                       
[42] "Location"                                                      
[43] "Record ID"                                                     
[44] "SF Find Neighborhoods"                                         
[45] "Current Police Districts"                                      
[46] "Current Supervisor Districts"                                  
[47] "Analysis Neighborhoods"                                        
[48] "DELETE - Zip Codes"                                            
[49] "DELETE - Fire Prevention Districts"                            
[50] "DELETE - Supervisor Districts"                                 
[51] "DELETE - Current Police Districts"                             
[52] "DELETE - Supervisorial_Districts_Waterline_data_from_7pkg_wer3"
length(colnames(data))
[1] 52

Lets change the name of some columns and remove other that are irrelevant for this use case.


colremove = c("First.Construction.Document.Date",
              "Structural.Notification",
              "Number.of.Existing.Stories",
              "Number.of.Proposed.Stories",
              "Voluntary.Soft.Story.Retrofit",
              "Fire.Only.Permit","Existing.Units",
              "Proposed.Units","Plansets",
              "TIDF.Compliance","Existing.Construction.Type",
              "Proposed.Construction Type","Site.Permit",
              "Supervisor.District","Current.Police.Districts",
              "Current.Supervisor.Districts",
              "Current.Status.Date", "Permit.Creation.Date",
              "Analysis.Neighborhoods","Lot","Location",
              "SF.Find.Neighborhoods","Unit","Block", "Permit.Type",
              "Unit.Suffix","Street.Number.Suffix",
              "Existing.Construction.Type.Description")

data <- data[1:47,!(names(data) %in% colremove)]
length(colnames(data))
[1] 19
colnames(data)
 [1] "Permit Number"                         
 [2] "Permit Type Definition"                
 [3] "Street Number"                         
 [4] "Street Name"                           
 [5] "Street Suffix"                         
 [6] "Description"                           
 [7] "Current Status"                        
 [8] "Filed Date"                            
 [9] "Issued Date"                           
[10] "Completed Date"                        
[11] "Permit Expiration Date"                
[12] "Estimated Cost"                        
[13] "Revised Cost"                          
[14] "Existing Use"                          
[15] "Proposed Use"                          
[16] "Proposed Construction Type Description"
[17] "Neighborhoods - Analysis Boundaries"   
[18] "Zipcode"                               
[19] "Record ID"                             

Now, let us rename two of the columns in the dataset:


data <- rename(data, "Neighborhoods Boundaries" = "Neighborhoods - Analysis Boundaries")

data <- rename(data, "Permit Type" = "Permit Type Definition")

Columns to lowercase


colnames(data) <- str_replace_all(tolower(colnames(data)), " ","_")

Let us see the lowercase column datas:

colnames(data)
 [1] "permit_number"                         
 [2] "permit_type"                           
 [3] "street_number"                         
 [4] "street_name"                           
 [5] "street_suffix"                         
 [6] "description"                           
 [7] "current_status"                        
 [8] "filed_date"                            
 [9] "issued_date"                           
[10] "completed_date"                        
[11] "permit_expiration_date"                
[12] "estimated_cost"                        
[13] "revised_cost"                          
[14] "existing_use"                          
[15] "proposed_use"                          
[16] "proposed_construction_type_description"
[17] "neighborhoods_boundaries"              
[18] "zipcode"                               
[19] "record_id"                             

Dataset Dimensions (num rows x num columns)


dim_desc(data)
[1] "[1,048,575 x 19]"

Select Current Status


bldg_df <- data %>% filter(current_status == "complete")

head(bldg_df)
NA

Data Dimension


dim_desc(bldg_df)
[1] "[574,003 x 19]"

Data Preparation and Cleaning


bldg_df <- na.omit(bldg_df)

dim_desc(bldg_df)
[1] "[343,237 x 19]"

String Cleaning and Pattern Replacement


bldg_df$city <- "San Francisco"
bldg_df$state <- "CA"
bldg_df$record_id <- as.character(bldg_df$record_id)

bldg_df$proposed_construction_type_description <-
  bldg_df$proposed_construction_type_description %>% 
  str_remove_all(pattern = "\\(|\\)") %>% 
  str_replace_all(" ", "_") %>% 
  as_factor()

bldg_df$street_name <- str_remove(string = bldg_df$street_name, pattern = "^0+")

bldg_df$street_name <- str_replace_all(string = bldg_df$street_name,
                                       pattern = "Bay Shore",
                                       replacement = "Bayshore")

bldg_df$street_suffix <- str_replace_all(string = bldg_df$street_suffix,
                                       pattern = "Bl",
                                       replacement = "Blvd")

bldg_df$street_suffix <- str_replace_all(string = bldg_df$street_suffix,
                                       pattern = "Tr",
                                       replacement = "Ter")

bldg_df$changed_use <- bldg_df$existing_use != bldg_df$proposed_use
bldg_df$estimated_revised_diff <- bldg_df$revised_cost - bldg_df$estimated_cost

Date Types

bldg_df$issued_date <- bldg_df$issued_date %>% mdy() %>% as_date()
bldg_df$issued_year <- bldg_df$issued_date %>% year()
bldg_df$issued_month <- bldg_df$issued_date %>% month(label = TRUE)
bldg_df$completed_date <- bldg_df$completed_date %>% mdy() %>% as_date()
bldg_df$completed_year <- bldg_df$completed_date %>% year()
bldg_df$completed_month <- bldg_df$completed_date %>% month(label = TRUE)

bldg_df$issued_completed_days <- bldg_df$completed_date - bldg_df$issued_date 
bldg_df$issued_completed_years <- bldg_df$completed_year - bldg_df$issued_year 

bldg_df$filed_date <- bldg_df$filed_date %>% mdy() %>% as_date()
bldg_df$filed_issued_days <- bldg_df$issued_date - bldg_df$filed_date

bldg_df$permit_expiration_date <- bldg_df$permit_expiration_date %>% mdy() %>% as_date()
bldg_df$issued_expiration_days <- bldg_df$permit_expiration_date - bldg_df$issued_date

Project Cost and Zoning

To look deeper into changes in zoning and type of constructions. We want to select only project that changed the building use. Also we will like to look at developments with significant capital investment in particular greater than half a million dollars


bldg_df <- bldg_df %>% arrange(desc(estimated_cost))
bldg_df <- bldg_df[bldg_df$changed_use == TRUE,]
bldg_df <- bldg_df %>% filter(revised_cost > 500000)

bldg_df$zipcode <- as_factor(bldg_df$zipcode)
bldg_df$permit_type <- as_factor(bldg_df$permit_type)
bldg_df$neighborhoods_boundaries <- as_factor(bldg_df$neighborhoods_boundaries)
bldg_df$proposed_use <- as_factor(bldg_df$proposed_use)

head(bldg_df)
NA

Final dataset dimensions n (rows) x m (columns)


dim_desc(bldg_df)
[1] "[2,407 x 31]"

Preparing the address string variable to match the OpenStreeMaps API

# ADDRESS VARIABLE MUST MATH NOMINATIM API
address <- paste0(bldg_df$street_number," ",
                  bldg_df$street_name," ",
                  bldg_df$street_suffix)

# DATA CLEANING SPECIAL CASES (e.g. 3333-3339 N CLARK)
query <- stri_replace(str = address, 
                      replacement = "", 
                      regex = "(-[0-9]+\\s)")

# REPLACE SPACES (\\s) OR COMMAS (,) WITH + SIGN
query <- str_replace_all(string = query, 
                         pattern = "\\s|,", 
                         replacement = "+")

head(query)
[1] "250+Howard+St"       "245+1st+St"          "1351+3rd+St"        
[4] "1245+3rd+St"         "1251+3rd+St"         "449+Mission+Rock+St"

Data Collection: OpenStreeMaps API Request

df <- geocode(record_id = bldg_df$record_id,
              address = query,
              city = bldg_df$city, 
              state = bldg_df$state,
              zipcode = bldg_df$zipcode)

Create a main dataset with all the relevant information

Finally lets save the new dataset containing the restaurants Geolocation (latitude, longitude).

write_csv(bldg_df, "data/building_geocoord.csv")
LS0tDQp0aXRsZTogIkdlb2NvZGluZzogT3BlblN0cmVldE1hcCArIFIgKyBUYWJsZWF1Ig0Kc3VidGl0bGU6ICJNYWRlIGZvciBSUHVicyINCmF1dGhvcjogIkF2aXBhcm5hIEJpc3dhcyINCmRhdGU6ICIwNi0xMC0yMDIwIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KICBodG1sX2RvY3VtZW50OiBkZWZhdWx0DQotLS0NCg0KLS0tLS0tLS0tLS0tLQ0KDQojIyBOb3RlYm9vayBJbnN0cnVjdGlvbnMNCg0KLS0tLS0tLS0tLS0tLQ0KDQoqIEluIG1hbnkgY2FzZXMgd2UgaGF2ZSBhY2Nlc3MgdG8gZGF0YSB3aXRoIGxvY2F0aW9uIGluZm9ybWF0aW9uIGUuZy4gQWRkcmVzcywgQ2l0eSAsQ291bnR5ICxaaXAgQ29kZSAsIENvdW50cnkuIFRvIHZpc3VhbGl6ZSB0aGlzIGRhdGEgd2l0aCBtb3JlIGRldGFpbCB3ZSBuZWVkIHRvIHVzZSBnZW9sb2NhdGlvbi4NCiogSW4gb3JkZXIgZ2V0IHRoZSBhY3R1YWwgZ2VvbG9jYXRpb24gY29vcmRpbmF0ZXMgKExvbmdpdHV0ZSxMYXRpdHVkZSkgZm9yIHRoZSBsb2NhdGlvbiBkYXRhLCB3ZSBuZWVkIHRvIGNhbGN1bGF0ZSB0aGlzIGNvb3JkZW5hdGVzLg0KKiBVc2luZyBub21pbmF0aW0gYSBmcmVlIHNlYXJjaCBlbmdpbmUgZm9yIE9wZW5TdHJlZXRNYXAgZGF0YSB0byBlYXNpbHkgZmluZCBvdXQgZ2VvbG9jYXRpb24gY29vcmRpbmF0ZXMuDQoNCioqT3BlblN0cmVldE1hcDoqKiBJcyBvcGVuIGRhdGEgYnVpbHQgYnkgYSBjb21tdW5pdHkgb2YgbWFwcGVycyB0aGF0IGNvbnRyaWJ1dGUgYW5kIG1haW50YWluIGRhdGEgYWJvdXQgcm9hZHMsIHRyYWlscywgY2Fmw4PCqXMsIHJhaWx3YXkgc3RhdGlvbnMsIGFuZCBtdWNoIG1vcmUsIGFsbCBvdmVyIHRoZSB3b3JsZC4NCg0KKiBodHRwczovL3d3dy5vcGVuc3RyZWV0bWFwLm9yZy9hYm91dA0KDQojIyMgTG9hZCBQYWNrYWdlcyBpbiBSL1JTdHVkaW8gDQoNCldlIGFyZSBnb2luZyB0byB1c2UgdGlkeXZlcnNlIGEgY29sbGVjdGlvbiBvZiBSIHBhY2thZ2VzIGRlc2lnbmVkIGZvciBkYXRhIHNjaWVuY2UuIEFzIHdlbGwgYXMgb3RoZXIgcGFja2FnZXMgdG8gaGVscCB3aXRoIGRhdGEgY2xlYW5pbmcgYW5kIHByb2Nlc3NpbmcuIA0KDQoqIHRpZHl2ZXJzZTogaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy8NCiogcnZlc3Q6IGh0dHBzOi8vZ2l0aHViLmNvbS90aWR5dmVyc2UvcnZlc3QNCiogc3RyaW5naTogaHR0cHM6Ly9naXRodWIuY29tL2dhZ29sZXdzL3N0cmluZ2kNCioganNvbmxpdGU6IGh0dHBzOi8vZ2l0aHViLmNvbS9qZXJvZW4vanNvbmxpdGUNCg0KDQpgYGB7ciwgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0Kb3B0aW9ucyhzY2lwZW4gPSA5OTk5KQ0KDQojIEhlcmUgd2UgYXJlIGNoZWNraW5nIGlmIHRoZSBwYWNrYWdlIGlzIGluc3RhbGxlZA0KaWYoIXJlcXVpcmUoInRpZHl2ZXJzZSIpKXsNCiAgIyBJZiB0aGUgcGFja2FnZSBpcyBub3QgaW4gdGhlIHN5c3RlbSB0aGVuIGl0IHdpbGwgYmUgaW5zdGFsbA0KICBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKQ0KICAjIEhlcmUgd2UgYXJlIGxvYWRpbmcgdGhlIHBhY2thZ2UNCiAgbGlicmFyeSgidGlkeXZlcnNlIikNCn0NCg0KaWYoIXJlcXVpcmUoInJ2ZXN0Iikpew0KICBpbnN0YWxsLnBhY2thZ2VzKCJydmVzdCIsIGRlcGVuZGVuY2llcyA9IFRSVUUpDQogIGxpYnJhcnkoInJ2ZXN0IikNCn0NCg0KaWYoIXJlcXVpcmUoInN0cmluZ2kiKSl7DQogIGluc3RhbGwucGFja2FnZXMoInN0cmluZ2kiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKQ0KICBsaWJyYXJ5KCJzdHJpbmdpIikNCn0NCg0KaWYoIXJlcXVpcmUoImx1YnJpZGF0ZSIpKXsNCiAgaW5zdGFsbC5wYWNrYWdlcygibHVicmlkYXRlIiwgZGVwZW5kZW5jaWVzID0gVFJVRSkNCiAgbGlicmFyeSgibHVicmlkYXRlIikNCn0NCg0KaWYoIXJlcXVpcmUoImpzb25saXRlIikpew0KICBpbnN0YWxsLnBhY2thZ2VzKCJqc29ubGl0ZSIsIGRlcGVuZGVuY2llcyA9IFRSVUUpDQogIGxpYnJhcnkoImpzb25saXRlIikNCn0NCmBgYA0KDQotLS0tLS0tLS0tLS0tDQoNCiMjIEdlb2NvZGluZzogT3BlblN0cmVldE1hcCBBUEkgU2VhcmNoIEZ1bmN0aW9ucw0KDQotLS0tLS0tLS0tLS0tDQoNClRoZSBnZW9jb2RlIGZ1bmN0aW9uIGJlbG93IGFsbG93IHVzIHRvIG1ha2UgcmVxdWVzdHMgdG8gTm9taW5hdGltIGEgc2VhcmNoIGVuZ2luZSBmb3IgT3BlblN0cmVldE1hcCBkYXRhLg0KDQoqIE5vbWluYXRpbTogaHR0cHM6Ly9ub21pbmF0aW0ub3BlbnN0cmVldG1hcC5vcmcvDQoqIFJlc291cmNlczogaHR0cHM6Ly9ub21pbmF0aW0ub3JnL3JlbGVhc2UtZG9jcy9kZXZlbG9wL2FwaS9TZWFyY2gNCg0KYGBge3J9DQojIElOUFVUIExPQ0FUSU9OIFZBUklBQkxFUw0KIyBbUkVDT1JEX0lEXSwgW0FERFJFU1NdLCBbQ0lUWV0sIFtTVEFURV0sIFtaSVBDT0RFXQ0KDQoNCmdlb2NvZGUgPC0gZnVuY3Rpb24ocmVjb3JkX2lkLCBhZGRyZXNzLCBjaXR5LCBzdGF0ZSwgemlwY29kZSl7DQogIA0KICAjIE5PTUlOQVRJTSBTRUFSQ0ggQVBJIFVSTA0KICBzcmNfdXJsIDwtICJodHRwczovL25vbWluYXRpbS5vcGVuc3RyZWV0bWFwLm9yZy9zZWFyY2g/cT0iDQogIA0KICAjIyMjIyMgSU5QVVRTIFBSRVBBUkFUSU9OICMjIyMjIA0KDQogIGNpdHkgPC0gc3RyX3JlcGxhY2VfYWxsKHN0cmluZyA9IGNpdHksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBwYXR0ZXJuID0gIlxcc3wsIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIHJlcGxhY2VtZW50ID0gIisiKQ0KICANCiAgIyBDUkVBVEUgQSBGVUxMIEFERFJFU1MNCiAgYWRkciA8LSBwYXN0ZShhZGRyZXNzLCBjaXR5LCBzdGF0ZSwgemlwY29kZSwgc2VwID0gIiUyQyIpDQogIA0KICAjIENSRUFURSBBIFNFQVJDSCBVUkwgQkFTRUQgT04gTk9NSU5BVElNIEFQSSBUTyBSRVRVUk4gR0VPSlNPTg0KICByZXF1ZXN0cyA8LSBwYXN0ZTAoc3JjX3VybCwgYWRkciwgIiZmb3JtYXQ9Z2VvanNvbiIpDQogIA0KICBmb3IgKGkgaW4gMTpsZW5ndGgocmVxdWVzdHMpKSB7DQogICAgDQogICAgcmVzcG9uc2UgPC0gcmVhZF9odG1sKHJlcXVlc3RzW2ldKSAlPiUNCiAgICAgIGh0bWxfbm9kZSgicCIpICU+JQ0KICAgICAgaHRtbF90ZXh0KCkgJT4lDQogICAgICBmcm9tSlNPTigpDQogICAgDQogICMgRlJPTSBUSEUgUkVTUE9OU0UgRVhUUkFDVCBMQVRJVFVERSBBTkQgTE9OR0lUVURFIENPT1JESU5BVEVTDQogICAgbG9uIDwtIHJlc3BvbnNlJGZlYXR1cmVzJGdlb21ldHJ5JGNvb3JkaW5hdGVzW1sxXV1bMV0NCiAgICBsYXQgPC0gcmVzcG9uc2UkZmVhdHVyZXMkZ2VvbWV0cnkkY29vcmRpbmF0ZXNbWzFdXVsyXQ0KICAgIA0KICAgICMgQ1JFQVRFIEEgQ09PUkRJTkFURVMgREFUQUZSQU1FDQogICAgaWYoaSA9PSAxKSB7DQogICAgICBsb2MgPC0gdGliYmxlKHJlY29yZF9pZCA9IHJlY29yZF9pZFtpXSwgDQogICAgICAgICAgICAgICAgICAgIGFkZHJlc3MgPSBzdHJfcmVwbGFjZV9hbGwoYWRkcltpXSwgIiUyQyIsICIsIiksDQogICAgICAgICAgICAgICAgICAgIGxhdGl0dWRlID0gbGF0LCBsb25naXR1ZGUgPSBsb24pDQogICAgfWVsc2V7DQogICAgICBkZiA8LSB0aWJibGUocmVjb3JkX2lkID0gcmVjb3JkX2lkW2ldLCANCiAgICAgICAgICAgICAgICAgICBhZGRyZXNzID0gc3RyX3JlcGxhY2VfYWxsKGFkZHJbaV0sICIlMkMiLCAiLCIpLA0KICAgICAgICAgICAgICAgICAgIGxhdGl0dWRlID0gbGF0LCBsb25naXR1ZGUgPSBsb24pDQogICAgICBsb2MgPC0gYmluZF9yb3dzKGxvYywgZGYpDQogICAgfQ0KICB9DQogcmV0dXJuKGxvYykNCiAgICANCn0NCg0KYGBgDQoNCi0tLS0tLS0tLS0tLS0NCg0KIyMgRGF0YSBTb3VyY2U6IFNhbiBGcmFuY2lzY28gQnVpbGRpbmdzIFBlcm1pdCBEYXRhDQoNCi0tLS0tLS0tLS0tLS0NCg0KVGhpcyBkYXRhIHNldCBwZXJ0YWlucyB0byBhbGwgdHlwZXMgb2Ygc3RydWN0dXJhbCBwZXJtaXRzLiBEYXRhIGluY2x1ZGVzIGRldGFpbHMgb24gYXBwbGljYXRpb24vcGVybWl0IG51bWJlcnMsIGpvYiBhZGRyZXNzZXMsIHN1cGVydmlzb3JpYWwgZGlzdHJpY3RzLCBhbmQgdGhlIGN1cnJlbnQgc3RhdHVzIG9mIHRoZSBhcHBsaWNhdGlvbnMuDQoNCiogU291cmNlOiBodHRwczovL2RhdGEuc2Znb3Yub3JnL0hvdXNpbmctYW5kLUJ1aWxkaW5ncy9CdWlsZGluZy1QZXJtaXRzL2k5OGUtZGpwOQ0KDQpgYGB7cn0NCg0KZGF0YSA8LSByZWFkLmNzdigiZGF0YS9idWlsZGluZ19wZXJtaXRzLmNzdiIsIGNoZWNrLm5hbWVzPUZBTFNFKQ0KYGBgDQoNCiMjIyBEYXRhIEluc3BlY3Rpb24NCg0KYGBge3J9DQpoZWFkKGRhdGEpDQpgYGANCg0KYGBge3J9DQp0YWlsKGRhdGEpDQpgYGANCg0KYGBge3J9DQpjb2xuYW1lcyhkYXRhKQ0KYGBgDQpgYGB7cn0NCmxlbmd0aChjb2xuYW1lcyhkYXRhKSkNCmBgYA0KDQojIyMgTGV0cyBjaGFuZ2UgdGhlIG5hbWUgb2Ygc29tZSBjb2x1bW5zIGFuZCByZW1vdmUgb3RoZXIgdGhhdCBhcmUgaXJyZWxldmFudCBmb3IgdGhpcyB1c2UgY2FzZS4NCg0KYGBge3J9DQoNCmNvbHJlbW92ZSA9IGMoIkZpcnN0IENvbnN0cnVjdGlvbiBEb2N1bWVudCBEYXRlIiwNCiAgICAgICAgICAgICAgIlN0cnVjdHVyYWwgTm90aWZpY2F0aW9uIiwNCiAgICAgICAgICAgICAgIk51bWJlciBvZiBFeGlzdGluZyBTdG9yaWVzIiwNCiAgICAgICAgICAgICAgIk51bWJlciBvZiBQcm9wb3NlZCBTdG9yaWVzIiwNCiAgICAgICAgICAgICAgIlZvbHVudGFyeSBTb2Z0LVN0b3J5IFJldHJvZml0IiwNCiAgICAgICAgICAgICAgIkZpcmUgT25seSBQZXJtaXQiLCJFeGlzdGluZyBVbml0cyIsDQogICAgICAgICAgICAgICJQcm9wb3NlZCBVbml0cyIsIlBsYW5zZXRzIiwNCiAgICAgICAgICAgICAgIlRJREYgQ29tcGxpYW5jZSIsIkV4aXN0aW5nIENvbnN0cnVjdGlvbiBUeXBlIiwNCiAgICAgICAgICAgICAgIlByb3Bvc2VkIENvbnN0cnVjdGlvbiBUeXBlIiwiU2l0ZSBQZXJtaXQiLA0KICAgICAgICAgICAgICAiU3VwZXJ2aXNvciBEaXN0cmljdCIsIkN1cnJlbnQgUG9saWNlIERpc3RyaWN0cyIsDQogICAgICAgICAgICAgICJDdXJyZW50IFN1cGVydmlzb3IgRGlzdHJpY3RzIiwNCiAgICAgICAgICAgICAgIkN1cnJlbnQgU3RhdHVzIERhdGUiLCAiUGVybWl0IENyZWF0aW9uIERhdGUiLA0KICAgICAgICAgICAgICAiQW5hbHlzaXMgTmVpZ2hib3Job29kcyIsIkxvdCIsIkxvY2F0aW9uIiwNCiAgICAgICAgICAgICAgIlNGIEZpbmQgTmVpZ2hib3Job29kcyIsIlVuaXQiLCJCbG9jayIsICJQZXJtaXQgVHlwZSIsDQogICAgICAgICAgICAgICJVbml0IFN1ZmZpeCIsIlN0cmVldCBOdW1iZXIgU3VmZml4IiwNCiAgICAgICAgICAgICAgIkV4aXN0aW5nIENvbnN0cnVjdGlvbiBUeXBlIERlc2NyaXB0aW9uIikNCg0KZGF0YSA8LSBkYXRhW2NvbG5hbWVzKGRhdGEpWzE6NDddXSAlPiUgc2VsZWN0KC1hbGxfb2YoY29scmVtb3ZlKSkNCmBgYA0KDQoNCmBgYHtyfQ0KbGVuZ3RoKGNvbG5hbWVzKGRhdGEpKQ0KYGBgDQpgYGB7cn0NCmNvbG5hbWVzKGRhdGEpDQpgYGANCk5vdywgbGV0IHVzIHJlbmFtZSB0d28gb2YgdGhlIGNvbHVtbnMgaW4gdGhlIGRhdGFzZXQ6DQpgYGB7cn0NCg0KZGF0YSA8LSByZW5hbWUoZGF0YSwgIk5laWdoYm9yaG9vZHMgQm91bmRhcmllcyIgPSAiTmVpZ2hib3Job29kcyAtIEFuYWx5c2lzIEJvdW5kYXJpZXMiKQ0KDQpkYXRhIDwtIHJlbmFtZShkYXRhLCAiUGVybWl0IFR5cGUiID0gIlBlcm1pdCBUeXBlIERlZmluaXRpb24iKQ0KDQpgYGANCg0KIyMjIENvbHVtbnMgdG8gbG93ZXJjYXNlDQoNCmBgYHtyfQ0KDQpjb2xuYW1lcyhkYXRhKSA8LSBzdHJfcmVwbGFjZV9hbGwodG9sb3dlcihjb2xuYW1lcyhkYXRhKSksICIgIiwiXyIpDQoNCmBgYA0KDQpMZXQgdXMgc2VlIHRoZSBsb3dlcmNhc2UgY29sdW1uIGRhdGFzOg0KYGBge3J9DQpjb2xuYW1lcyhkYXRhKQ0KYGBgDQoNCiMjIyBEYXRhc2V0IERpbWVuc2lvbnMgKG51bSByb3dzIHggbnVtIGNvbHVtbnMpDQpgYGB7cn0NCg0KZGltX2Rlc2MoZGF0YSkNCg0KYGBgDQoNCiMjIyBTZWxlY3QgQ3VycmVudCBTdGF0dXMNCg0KYGBge3J9DQoNCmJsZGdfZGYgPC0gZGF0YSAlPiUgZmlsdGVyKGN1cnJlbnRfc3RhdHVzID09ICJjb21wbGV0ZSIpDQoNCmhlYWQoYmxkZ19kZikNCg0KYGBgDQoNCiMjIyBEYXRhIERpbWVuc2lvbg0KYGBge3J9DQoNCmRpbV9kZXNjKGJsZGdfZGYpDQoNCmBgYA0KIyMjIERhdGEgUHJlcGFyYXRpb24gYW5kIENsZWFuaW5nDQpgYGB7cn0NCg0KYmxkZ19kZiA8LSBuYS5vbWl0KGJsZGdfZGYpDQoNCmRpbV9kZXNjKGJsZGdfZGYpDQoNCmBgYA0KDQojIyMgU3RyaW5nIENsZWFuaW5nIGFuZCBQYXR0ZXJuIFJlcGxhY2VtZW50DQoNCmBgYHtyfQ0KDQpibGRnX2RmJGNpdHkgPC0gIlNhbiBGcmFuY2lzY28iDQpibGRnX2RmJHN0YXRlIDwtICJDQSINCmJsZGdfZGYkcmVjb3JkX2lkIDwtIGFzLmNoYXJhY3RlcihibGRnX2RmJHJlY29yZF9pZCkNCg0KYmxkZ19kZiRwcm9wb3NlZF9jb25zdHJ1Y3Rpb25fdHlwZV9kZXNjcmlwdGlvbiA8LQ0KICBibGRnX2RmJHByb3Bvc2VkX2NvbnN0cnVjdGlvbl90eXBlX2Rlc2NyaXB0aW9uICU+JSANCiAgc3RyX3JlbW92ZV9hbGwocGF0dGVybiA9ICJcXCh8XFwpIikgJT4lIA0KICBzdHJfcmVwbGFjZV9hbGwoIiAiLCAiXyIpICU+JSANCiAgYXNfZmFjdG9yKCkNCg0KYmxkZ19kZiRzdHJlZXRfbmFtZSA8LSBzdHJfcmVtb3ZlKHN0cmluZyA9IGJsZGdfZGYkc3RyZWV0X25hbWUsIHBhdHRlcm4gPSAiXjArIikNCg0KYmxkZ19kZiRzdHJlZXRfbmFtZSA8LSBzdHJfcmVwbGFjZV9hbGwoc3RyaW5nID0gYmxkZ19kZiRzdHJlZXRfbmFtZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhdHRlcm4gPSAiQmF5IFNob3JlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcGxhY2VtZW50ID0gIkJheXNob3JlIikNCg0KYmxkZ19kZiRzdHJlZXRfc3VmZml4IDwtIHN0cl9yZXBsYWNlX2FsbChzdHJpbmcgPSBibGRnX2RmJHN0cmVldF9zdWZmaXgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXR0ZXJuID0gIkJsIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcGxhY2VtZW50ID0gIkJsdmQiKQ0KDQpibGRnX2RmJHN0cmVldF9zdWZmaXggPC0gc3RyX3JlcGxhY2VfYWxsKHN0cmluZyA9IGJsZGdfZGYkc3RyZWV0X3N1ZmZpeCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhdHRlcm4gPSAiVHIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwbGFjZW1lbnQgPSAiVGVyIikNCg0KYmxkZ19kZiRjaGFuZ2VkX3VzZSA8LSBibGRnX2RmJGV4aXN0aW5nX3VzZSAhPSBibGRnX2RmJHByb3Bvc2VkX3VzZQ0KYmxkZ19kZiRlc3RpbWF0ZWRfcmV2aXNlZF9kaWZmIDwtIGJsZGdfZGYkcmV2aXNlZF9jb3N0IC0gYmxkZ19kZiRlc3RpbWF0ZWRfY29zdA0KYGBgDQoNCiMjIyBEYXRlIFR5cGVzDQoNCmBgYHtyfQ0KYmxkZ19kZiRpc3N1ZWRfZGF0ZSA8LSBibGRnX2RmJGlzc3VlZF9kYXRlICU+JSBtZHkoKSAlPiUgYXNfZGF0ZSgpDQpibGRnX2RmJGlzc3VlZF95ZWFyIDwtIGJsZGdfZGYkaXNzdWVkX2RhdGUgJT4lIHllYXIoKQ0KYmxkZ19kZiRpc3N1ZWRfbW9udGggPC0gYmxkZ19kZiRpc3N1ZWRfZGF0ZSAlPiUgbW9udGgobGFiZWwgPSBUUlVFKQ0KYmxkZ19kZiRjb21wbGV0ZWRfZGF0ZSA8LSBibGRnX2RmJGNvbXBsZXRlZF9kYXRlICU+JSBtZHkoKSAlPiUgYXNfZGF0ZSgpDQpibGRnX2RmJGNvbXBsZXRlZF95ZWFyIDwtIGJsZGdfZGYkY29tcGxldGVkX2RhdGUgJT4lIHllYXIoKQ0KYmxkZ19kZiRjb21wbGV0ZWRfbW9udGggPC0gYmxkZ19kZiRjb21wbGV0ZWRfZGF0ZSAlPiUgbW9udGgobGFiZWwgPSBUUlVFKQ0KDQpibGRnX2RmJGlzc3VlZF9jb21wbGV0ZWRfZGF5cyA8LSBibGRnX2RmJGNvbXBsZXRlZF9kYXRlIC0gYmxkZ19kZiRpc3N1ZWRfZGF0ZSANCmJsZGdfZGYkaXNzdWVkX2NvbXBsZXRlZF95ZWFycyA8LSBibGRnX2RmJGNvbXBsZXRlZF95ZWFyIC0gYmxkZ19kZiRpc3N1ZWRfeWVhciANCg0KYmxkZ19kZiRmaWxlZF9kYXRlIDwtIGJsZGdfZGYkZmlsZWRfZGF0ZSAlPiUgbWR5KCkgJT4lIGFzX2RhdGUoKQ0KYmxkZ19kZiRmaWxlZF9pc3N1ZWRfZGF5cyA8LSBibGRnX2RmJGlzc3VlZF9kYXRlIC0gYmxkZ19kZiRmaWxlZF9kYXRlDQoNCmJsZGdfZGYkcGVybWl0X2V4cGlyYXRpb25fZGF0ZSA8LSBibGRnX2RmJHBlcm1pdF9leHBpcmF0aW9uX2RhdGUgJT4lIG1keSgpICU+JSBhc19kYXRlKCkNCmJsZGdfZGYkaXNzdWVkX2V4cGlyYXRpb25fZGF5cyA8LSBibGRnX2RmJHBlcm1pdF9leHBpcmF0aW9uX2RhdGUgLSBibGRnX2RmJGlzc3VlZF9kYXRlDQpgYGANCg0KIyMjIFByb2plY3QgQ29zdCBhbmQgWm9uaW5nIA0KDQpUbyBsb29rIGRlZXBlciBpbnRvIGNoYW5nZXMgaW4gem9uaW5nIGFuZCB0eXBlIG9mIGNvbnN0cnVjdGlvbnMuIFdlIHdhbnQgdG8gc2VsZWN0IG9ubHkgcHJvamVjdCB0aGF0IGNoYW5nZWQgdGhlIGJ1aWxkaW5nIHVzZS4gQWxzbyB3ZSB3aWxsIGxpa2UgdG8gbG9vayBhdCBkZXZlbG9wbWVudHMgd2l0aCBzaWduaWZpY2FudCBjYXBpdGFsIGludmVzdG1lbnQgaW4gcGFydGljdWxhciBncmVhdGVyIHRoYW4gaGFsZiBhIG1pbGxpb24gZG9sbGFycyANCg0KYGBge3J9DQoNCmJsZGdfZGYgPC0gYmxkZ19kZiAlPiUgYXJyYW5nZShkZXNjKGVzdGltYXRlZF9jb3N0KSkNCmJsZGdfZGYgPC0gYmxkZ19kZltibGRnX2RmJGNoYW5nZWRfdXNlID09IFRSVUUsXQ0KYmxkZ19kZiA8LSBibGRnX2RmICU+JSBmaWx0ZXIocmV2aXNlZF9jb3N0ID4gNTAwMDAwKQ0KDQpibGRnX2RmJHppcGNvZGUgPC0gYXNfZmFjdG9yKGJsZGdfZGYkemlwY29kZSkNCmJsZGdfZGYkcGVybWl0X3R5cGUgPC0gYXNfZmFjdG9yKGJsZGdfZGYkcGVybWl0X3R5cGUpDQpibGRnX2RmJG5laWdoYm9yaG9vZHNfYm91bmRhcmllcyA8LSBhc19mYWN0b3IoYmxkZ19kZiRuZWlnaGJvcmhvb2RzX2JvdW5kYXJpZXMpDQpibGRnX2RmJHByb3Bvc2VkX3VzZSA8LSBhc19mYWN0b3IoYmxkZ19kZiRwcm9wb3NlZF91c2UpDQoNCmhlYWQoYmxkZ19kZikNCg0KYGBgDQoNCiMjIyBGaW5hbCBkYXRhc2V0IGRpbWVuc2lvbnMgbiAocm93cykgeCBtIChjb2x1bW5zKQ0KDQpgYGB7cn0NCg0KZGltX2Rlc2MoYmxkZ19kZikNCg0KYGBgDQoNCiMjIyBQcmVwYXJpbmcgdGhlIGFkZHJlc3Mgc3RyaW5nIHZhcmlhYmxlIHRvIG1hdGNoIHRoZSBPcGVuU3RyZWVNYXBzIEFQSQ0KDQpgYGB7cn0NCiMgQUREUkVTUyBWQVJJQUJMRSBNVVNUIE1BVEggTk9NSU5BVElNIEFQSQ0KYWRkcmVzcyA8LSBwYXN0ZTAoYmxkZ19kZiRzdHJlZXRfbnVtYmVyLCIgIiwNCiAgICAgICAgICAgICAgICAgIGJsZGdfZGYkc3RyZWV0X25hbWUsIiAiLA0KICAgICAgICAgICAgICAgICAgYmxkZ19kZiRzdHJlZXRfc3VmZml4KQ0KDQojIERBVEEgQ0xFQU5JTkcgU1BFQ0lBTCBDQVNFUyAoZS5nLiAzMzMzLTMzMzkgTiBDTEFSSykNCnF1ZXJ5IDwtIHN0cmlfcmVwbGFjZShzdHIgPSBhZGRyZXNzLCANCiAgICAgICAgICAgICAgICAgICAgICByZXBsYWNlbWVudCA9ICIiLCANCiAgICAgICAgICAgICAgICAgICAgICByZWdleCA9ICIoLVswLTldK1xccykiKQ0KDQojIFJFUExBQ0UgU1BBQ0VTIChcXHMpIE9SIENPTU1BUyAoLCkgV0lUSCArIFNJR04NCnF1ZXJ5IDwtIHN0cl9yZXBsYWNlX2FsbChzdHJpbmcgPSBxdWVyeSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgcGF0dGVybiA9ICJcXHN8LCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIHJlcGxhY2VtZW50ID0gIisiKQ0KDQpoZWFkKHF1ZXJ5KQ0KYGBgDQoNCiMjIERhdGEgQ29sbGVjdGlvbjogT3BlblN0cmVlTWFwcyBBUEkgUmVxdWVzdCANCg0KYGBge3J9DQpkZiA8LSBnZW9jb2RlKHJlY29yZF9pZCA9IGJsZGdfZGYkcmVjb3JkX2lkLA0KICAgICAgICAgICAgICBhZGRyZXNzID0gcXVlcnksDQogICAgICAgICAgICAgIGNpdHkgPSBibGRnX2RmJGNpdHksIA0KICAgICAgICAgICAgICBzdGF0ZSA9IGJsZGdfZGYkc3RhdGUsDQogICAgICAgICAgICAgIHppcGNvZGUgPSBibGRnX2RmJHppcGNvZGUpDQpgYGANCg0KQ3JlYXRlIGEgbWFpbiBkYXRhc2V0IHdpdGggYWxsIHRoZSByZWxldmFudCBpbmZvcm1hdGlvbg0KDQpgYGB7ciBlY2hvID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNvcHk9VFJVRX0NCg0KYmxkZ19kZiA8LSBsZWZ0X2pvaW4oYmxkZ19kZiwgZGYsIGJ5PSJyZWNvcmRfaWQiKQ0KDQpibGRnX2RmJGFkZHJlc3MgPC0gc3RyX3JlcGxhY2VfYWxsKHN0cmluZyA9IGJsZGdfZGYkYWRkcmVzcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGF0dGVybiA9ICJcXCsiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXBsYWNlbWVudCA9ICIgIikNCg0KY29scmVtb3ZlIDwtIGMoInN0cmVldF9udW1iZXIiLCJzdHJlZXRfbmFtZSIsIA0KICAgICAgICAgICAgICAgICJzdHJlZXRfc3VmZml4IiwgImN1cnJlbnRfc3RhdHVzIikNCg0KYmxkZ19kZiA8LSBibGRnX2RmICU+JSBzZWxlY3QoLW9uZV9vZihjb2xyZW1vdmUpKQ0KDQpoZWFkKGJsZGdfZGYpDQoNCmBgYA0KDQpGaW5hbGx5IGxldHMgc2F2ZSB0aGUgbmV3IGRhdGFzZXQgY29udGFpbmluZyB0aGUgcmVzdGF1cmFudHMgR2VvbG9jYXRpb24gKGxhdGl0dWRlLCBsb25naXR1ZGUpLg0KDQpgYGB7cn0NCndyaXRlX2NzdihibGRnX2RmLCAiZGF0YS9idWlsZGluZ19nZW9jb29yZC5jc3YiKQ0KYGBgDQoNCg==