<< Workshop Home

Geocoding

What is Geocoding?

GPS coordinates can easily be located on a map. However, sometimes our spatial reference is a street address or only the name of a place. It turns out that we can often use these data to find a location on a map. The process of converting an address or place name into map coordinates is called geocoding. This is what Google Maps does when you type in an address and it shows a location on a map. Within R, you can use Google’s mapping database to geocode your data.

Extracting Geographic Data from PDF Reports

You can find lots of useful spatial data online, but rarely will you be lucky enough to find it in shapefile format. More often, it will be embedded in a PDF. You will need to do some work to first get it into a table or spreadsheet that can be read by R. From there, you can geocode.

A PDF report of the National Survey of HIV and Syphilis Prevalence among Women attending Antenatal Clinics in Zimbabwe 2012 is available online. The report includes geographic data on each site, including the name of the site and the district it is located within. However, there are no GPS coordinates or maps included in the report. We’ll see how R can be used to geocode the locations of these facilities and place them on a map.

Of course, first you need to convert the PDF into a spreadsheet. If you have an Adobe license this can be done fairly easily. Otherwise it requires some work: highlight and copy the table, and paste it into an Excel spreadsheet. You’ll see that all the data ends up into a single column. To separate the data, click the Data tab, then click Text to Columns. Choose Delimited. Check Space. Click Finish.

Text to Columns in Excel

Text to Columns in Excel

The data still will not be properly organized, so you’ll have to manually move things around until you get the data sorted in columns. Luckily, I already did this for you! I extracted all of the 2012 data from the report and organized it in ANC2012.xlsx.

Geocoding in R with Google Maps API

Now we are ready to geocode in R. We’ll need the ggmap package to do this. We also need a package for reading Excel files into R. If you’ve closed R since starting this tutorial, you’ll also need to load the rgdal package again.

# Set working directory

# Load packages
library(rgdal) # 'rgdal' automatically loads 'sp' package along with it
library(ggmap) # has a geocoding function
library(readxl) # for reading excel files

# Make sure prettymapr is NOT loaded; it also has a geocode() function
# Our code is written for ggmap's geocode() function
detach(package:prettymapr)

# Options
options(stringsAsFactors = F)

Import the ANC data using the read_excel() function from the readxl package and assign the data to an object called ANC2012.

# Import ANC data
ANC2012 <- read_excel("ANC2012.xlsx")
# Examine the data
class(ANC2012)
head(ANC2012)

Check out the geocode() functionality.

# Read the manual for the geocode function
?geocode

From the Help documentation, we learn that the Google Maps limits to 2500 queries per day. We see that we can use geocodeQueryCheck() to determine how many queries remain today.

#Check how many free geocodes we have 
geocodeQueryCheck() #2500 per day limit

The Help documentation also informed us that we need to give the geocode() function a single “character vector” for the location input. It will be helpful to include not just the site, but also the province and country. Let’s string those together using the paste0() function.

# Make a new string that has all the place details together
place<- paste0(ANC2012$site, ", ", ANC2012$province, ", ", "Zimbabwe")
head(place)

Note that we are using the site variable to locate the place. Alternatively, we could include the name variable to get the more precise location of the health facility. Google knows the locations of many health facilities in Zimbabwe by name. However, Google might not find the location if it is too specific, so we would have to do some cleaning and run a few rounds of geocoding until all locations could be found. For now, let’s just use site to get the approximate location. This will be adequate for a national-level static map.

# Now try geocoding
geocode_result <- geocode(place, output = "more")

You’ll see some red text start to appear in the Console. This is nothing to worry about; it just shows that queries are running. When the red text stops appearing, you’ll know the geocoding is finished. Check the red text results to make sure no errors occurred. View the results.

head(geocode_result)

We see that Google reported back numerous details. If we only wanted the longitude and latitude, we could have left out the “more” option in our geocode query. However, details are useful for checking the accuracy of the geocoding results. Let’s check whether Google located these points within the same districts that were reported in the ANC survey report.

# Quality check
cbind(ANC2012$district, geocode_result$administrative_area_level_2)

Looks pretty good! There are a few NA results where Google didn’t report the district, but if you further inspect the other fields you’ll see that they seem to be in the right area.

Now that we have latitude and longitude, we can convert to a shapefile and map! First, we need to merge the geocoding results with the ANC data. Let’s also export this data frame to a CSV (comma separated values) spreadsheet file so we can access the geocoded data outside of R.

# Merge ANC data with geocoding results
ANC2012_geocoded <- cbind(ANC2012, geocode_result)
head(ANC2012_geocoded)
class(ANC2012_geocoded)
# Export as a CSV to access outside of R
write.csv(ANC2012_geocoded, file="ANC2012_geocoded.csv")

NOTE: If you were NOT able to successfully geocode, you should download this file to use for the remainder of the workshop. Save it in your Spatial Workshop folder on your Desktop and import it with the following code: ANC2012_geocoded <- read.csv("ANC2012_geocoded.csv")

# Convert to shapefile
ANC_shp <- ANC2012_geocoded
coordinates(ANC_shp) <- ~lon + lat
class(ANC_shp)
# Import provinces shapefile
provinces_shp <- readOGR("zwe_polbnda_adm1_250k_cso.shp")

# Plot ANC sites with provinces
plot(provinces_shp, col = "azure", main = "Zimbabwe ANC Sentinel Sites 2012")
plot(ANC_shp, add = T, pch = 15)
text(ANC_shp$lon, ANC_shp$lat - 0.1, labels = ANC_shp$site, cex = 0.8)

It’s difficult to make out all of the points clustered in Harare, so let’s create a map of just the Harare province. We can use square brackets to indicate the subset of provinces_shp we want to select. For the labels, subtract a small value (0.01) from the latitude so that the label will appear just below the point.

# Plot only Harare
plot(provinces_shp[provinces_shp$PROVINCE=="Harare", ],
     col = "azure", main = "Harare ANC Sentinel Sites 2012")
plot(ANC_shp, add = T, pch = 15)
text(ANC_shp$lon, ANC_shp$lat - 0.01, labels = ANC_shp$site, cex = 0.8)

<< Workshop Home

LS0tDQp0aXRsZTogIkludHJvZHVjdGlvbiB0byBNYXBwaW5nIFNwYXRpYWwgRGF0YSBpbiBSIg0Kc3VidGl0bGU6ICJQYXJ0IDM6IEdlb2NvZGluZyINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiANCiAgICAgIGNvbGxhcHNlZDogZmFsc2UNCiAgICAgIHNtb290aF9zY3JvbGw6IGZhbHNlDQogICAgdG9jX2RlcHRoOiAyDQogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZSANCiAgICB0aGVtZTogbHVtZW4NCiAgICBoaWdobGlnaHQ6IHRleHRtYXRlDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiANCiAgICAgIGNvbGxhcHNlZDogZmFsc2UNCiAgICAgIHNtb290aF9zY3JvbGw6IGZhbHNlDQogICAgdG9jX2RlcHRoOiAyDQogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZSANCiAgICB0aGVtZTogbHVtZW4NCiAgICBoaWdobGlnaHQ6IHRleHRtYXRlICANCg0KLS0tDQogIA0KYGBge2Nzc30NCmNvZGUgew0KICB3aGl0ZS1zcGFjZTogcHJlICFpbXBvcnRhbnQ7DQogIG92ZXJmbG93LXg6IHNjcm9sbCAhaW1wb3J0YW50Ow0KICB3b3JkLWJyZWFrOiBrZWVwLWFsbCAhaW1wb3J0YW50Ow0KICB3b3JkLXdyYXA6IGluaXRpYWwgIWltcG9ydGFudDsNCn0NCmJvZHl7IC8qIE5vcm1hbCAgKi8NCiAgICAgIGZvbnQtc2l6ZTogMTZweDsNCiAgfQ0KDQpgYGANCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UpDQpvcHRpb25zKHdpZHRoPTUwMCkNCg0KIyBTZXQgd29ya2luZyBkaXJlY3RvcnkNCnNldHdkKCIvVXNlcnMvQ2FycmllL0JveCBTeW5jL0dJUy9TcGF0aWFsIFdvcmtzaG9wIikNCmBgYA0KDQpbKio8PCBXb3Jrc2hvcCBIb21lKipdKGh0dHBzOi8vY2FmYWhleS5naXRodWIuaW8vc3BhdGlhbCkNCg0KDQojR2VvY29kaW5nDQoNCiMjV2hhdCBpcyBHZW9jb2Rpbmc/DQoNCkdQUyBjb29yZGluYXRlcyBjYW4gZWFzaWx5IGJlIGxvY2F0ZWQgb24gYSBtYXAuIEhvd2V2ZXIsIHNvbWV0aW1lcyBvdXIgc3BhdGlhbCByZWZlcmVuY2UgaXMgYSBzdHJlZXQgYWRkcmVzcyBvciBvbmx5IHRoZSBuYW1lIG9mIGEgcGxhY2UuIEl0IHR1cm5zIG91dCB0aGF0IHdlIGNhbiBvZnRlbiB1c2UgdGhlc2UgZGF0YSB0byBmaW5kIGEgbG9jYXRpb24gb24gYSBtYXAuIFRoZSBwcm9jZXNzIG9mIGNvbnZlcnRpbmcgYW4gYWRkcmVzcyBvciBwbGFjZSBuYW1lIGludG8gbWFwIGNvb3JkaW5hdGVzIGlzIGNhbGxlZCBnZW9jb2RpbmcuIFRoaXMgaXMgd2hhdCBHb29nbGUgTWFwcyBkb2VzIHdoZW4geW91IHR5cGUgaW4gYW4gYWRkcmVzcyBhbmQgaXQgc2hvd3MgYSBsb2NhdGlvbiBvbiBhIG1hcC4gV2l0aGluIFIsIHlvdSBjYW4gdXNlIEdvb2dsZSdzIG1hcHBpbmcgZGF0YWJhc2UgdG8gZ2VvY29kZSB5b3VyIGRhdGEuDQoNCiMjRXh0cmFjdGluZyBHZW9ncmFwaGljIERhdGEgZnJvbSBQREYgUmVwb3J0cw0KDQpZb3UgY2FuIGZpbmQgbG90cyBvZiB1c2VmdWwgc3BhdGlhbCBkYXRhIG9ubGluZSwgYnV0IHJhcmVseSB3aWxsIHlvdSBiZSBsdWNreSBlbm91Z2ggdG8gZmluZCBpdCBpbiBzaGFwZWZpbGUgZm9ybWF0LiBNb3JlIG9mdGVuLCBpdCB3aWxsIGJlIGVtYmVkZGVkIGluIGEgUERGLiBZb3Ugd2lsbCBuZWVkIHRvIGRvIHNvbWUgd29yayB0byBmaXJzdCBnZXQgaXQgaW50byBhIHRhYmxlIG9yIHNwcmVhZHNoZWV0IHRoYXQgY2FuIGJlIHJlYWQgYnkgUi4gRnJvbSB0aGVyZSwgeW91IGNhbiBnZW9jb2RlLg0KDQpBIFBERiByZXBvcnQgb2YgdGhlIFtOYXRpb25hbCBTdXJ2ZXkgb2YgSElWIGFuZCBTeXBoaWxpcyBQcmV2YWxlbmNlIGFtb25nIFdvbWVuIGF0dGVuZGluZyBBbnRlbmF0YWwgQ2xpbmljcyBpbiBaaW1iYWJ3ZSAyMDEyXShodHRwOi8vY2F0YWxvZ3VlLnNhZmFpZHMubmV0L3B1YmxpY2F0aW9ucy9uYXRpb25hbC1zdXJ2ZXktaGl2LWFuZC1zeXBoaWxpcy1wcmV2YWxlbmNlLWFtb25nLXdvbWVuLWF0dGVuZGluZy1hbnRlbmF0YWwtY2xpbmljcy16LTIpIGlzIGF2YWlsYWJsZSBvbmxpbmUuIFRoZSByZXBvcnQgaW5jbHVkZXMgZ2VvZ3JhcGhpYyBkYXRhIG9uIGVhY2ggc2l0ZSwgaW5jbHVkaW5nIHRoZSBuYW1lIG9mIHRoZSBzaXRlIGFuZCB0aGUgZGlzdHJpY3QgaXQgaXMgbG9jYXRlZCB3aXRoaW4uIEhvd2V2ZXIsIHRoZXJlIGFyZSBubyBHUFMgY29vcmRpbmF0ZXMgb3IgbWFwcyBpbmNsdWRlZCBpbiB0aGUgcmVwb3J0LiBXZSdsbCBzZWUgaG93IFIgY2FuIGJlIHVzZWQgdG8gZ2VvY29kZSB0aGUgbG9jYXRpb25zIG9mIHRoZXNlIGZhY2lsaXRpZXMgYW5kIHBsYWNlIHRoZW0gb24gYSBtYXAuDQoNCiFbaHR0cDovL2NhdGFsb2d1ZS5zYWZhaWRzLm5ldC9wdWJsaWNhdGlvbnMvbmF0aW9uYWwtc3VydmV5LWhpdi1hbmQtc3lwaGlsaXMtcHJldmFsZW5jZS1hbW9uZy13b21lbi1hdHRlbmRpbmctYW50ZW5hdGFsLWNsaW5pY3Mtei0yXShBTkMgMjAxMiBUYWJsZSA1LnBuZykNCg0KT2YgY291cnNlLCBmaXJzdCB5b3UgbmVlZCB0byBjb252ZXJ0IHRoZSBQREYgaW50byBhIHNwcmVhZHNoZWV0LiBJZiB5b3UgaGF2ZSBhbiBBZG9iZSBsaWNlbnNlIHRoaXMgY2FuIGJlIGRvbmUgZmFpcmx5IGVhc2lseS4gT3RoZXJ3aXNlIGl0IHJlcXVpcmVzIHNvbWUgd29yazogaGlnaGxpZ2h0IGFuZCBjb3B5IHRoZSB0YWJsZSwgYW5kIHBhc3RlIGl0IGludG8gYW4gRXhjZWwgc3ByZWFkc2hlZXQuIFlvdSdsbCBzZWUgdGhhdCBhbGwgdGhlIGRhdGEgZW5kcyB1cCBpbnRvIGEgc2luZ2xlIGNvbHVtbi4gVG8gc2VwYXJhdGUgdGhlIGRhdGEsIGNsaWNrIHRoZSAqKkRhdGEgdGFiKiosIHRoZW4gY2xpY2sgKipUZXh0IHRvIENvbHVtbnMqKi4gQ2hvb3NlICoqRGVsaW1pdGVkKiouIENoZWNrICoqU3BhY2UqKi4gQ2xpY2sgKipGaW5pc2gqKi4NCg0KIVsqVGV4dCB0byBDb2x1bW5zIGluIEV4Y2VsKl0oUHJlc2VudGF0aW9ucy90ZXh0dG9jb2x1bW5zLmpwZykNCg0KVGhlIGRhdGEgc3RpbGwgd2lsbCBub3QgYmUgcHJvcGVybHkgb3JnYW5pemVkLCBzbyB5b3UnbGwgaGF2ZSB0byBtYW51YWxseSBtb3ZlIHRoaW5ncyBhcm91bmQgdW50aWwgeW91IGdldCB0aGUgZGF0YSBzb3J0ZWQgaW4gY29sdW1ucy4gTHVja2lseSwgSSBhbHJlYWR5IGRpZCB0aGlzIGZvciB5b3UhIEkgZXh0cmFjdGVkIGFsbCBvZiB0aGUgMjAxMiBkYXRhIGZyb20gdGhlIHJlcG9ydCBhbmQgb3JnYW5pemVkIGl0IGluIFtBTkMyMDEyLnhsc3hdKGh0dHBzOi8vY2FmYWhleS5naXRodWIuaW8vQU5DMjAxMi54bHN4KS4NCg0KIyNHZW9jb2RpbmcgaW4gUiB3aXRoIEdvb2dsZSBNYXBzIEFQSQ0KDQpOb3cgd2UgYXJlIHJlYWR5IHRvIGdlb2NvZGUgaW4gUi4gV2UnbGwgbmVlZCB0aGUgYGdnbWFwYCBwYWNrYWdlIHRvIGRvIHRoaXMuIFdlIGFsc28gbmVlZCBhIHBhY2thZ2UgZm9yIHJlYWRpbmcgRXhjZWwgZmlsZXMgaW50byBSLiBJZiB5b3UndmUgY2xvc2VkIFIgc2luY2Ugc3RhcnRpbmcgdGhpcyB0dXRvcmlhbCwgeW91J2xsIGFsc28gbmVlZCB0byBsb2FkIHRoZSBgcmdkYWxgIHBhY2thZ2UgYWdhaW4uDQpgYGB7ciwgaW5jbHVkZT1GQUxTRX0NCmxpYnJhcnkocHJldHR5bWFwcikNCmBgYA0KDQpgYGB7cn0NCiMgU2V0IHdvcmtpbmcgZGlyZWN0b3J5DQoNCiMgTG9hZCBwYWNrYWdlcw0KbGlicmFyeShyZ2RhbCkgIyAncmdkYWwnIGF1dG9tYXRpY2FsbHkgbG9hZHMgJ3NwJyBwYWNrYWdlIGFsb25nIHdpdGggaXQNCmxpYnJhcnkoZ2dtYXApICMgaGFzIGEgZ2VvY29kaW5nIGZ1bmN0aW9uDQpsaWJyYXJ5KHJlYWR4bCkgIyBmb3IgcmVhZGluZyBleGNlbCBmaWxlcw0KDQojIE1ha2Ugc3VyZSBwcmV0dHltYXByIGlzIE5PVCBsb2FkZWQ7IGl0IGFsc28gaGFzIGEgZ2VvY29kZSgpIGZ1bmN0aW9uDQojIE91ciBjb2RlIGlzIHdyaXR0ZW4gZm9yIGdnbWFwJ3MgZ2VvY29kZSgpIGZ1bmN0aW9uDQpkZXRhY2gocGFja2FnZTpwcmV0dHltYXByKQ0KDQojIE9wdGlvbnMNCm9wdGlvbnMoc3RyaW5nc0FzRmFjdG9ycyA9IEYpDQpgYGANCg0KSW1wb3J0IHRoZSBBTkMgZGF0YSB1c2luZyB0aGUgYHJlYWRfZXhjZWwoKWAgZnVuY3Rpb24gZnJvbSB0aGUgYHJlYWR4bGAgcGFja2FnZSBhbmQgYXNzaWduIHRoZSBkYXRhIHRvIGFuIG9iamVjdCBjYWxsZWQgYEFOQzIwMTJgLg0KDQpgYGB7cn0NCiMgSW1wb3J0IEFOQyBkYXRhDQpBTkMyMDEyIDwtIHJlYWRfZXhjZWwoIkFOQzIwMTIueGxzeCIpDQojIEV4YW1pbmUgdGhlIGRhdGENCmNsYXNzKEFOQzIwMTIpDQpoZWFkKEFOQzIwMTIpDQpgYGANCg0KQ2hlY2sgb3V0IHRoZSBgZ2VvY29kZSgpYCBmdW5jdGlvbmFsaXR5Lg0KDQpgYGB7ciBldmFsPUZBTFNFfQ0KIyBSZWFkIHRoZSBtYW51YWwgZm9yIHRoZSBnZW9jb2RlIGZ1bmN0aW9uDQo/Z2VvY29kZQ0KYGBgDQoNCkZyb20gdGhlIEhlbHAgZG9jdW1lbnRhdGlvbiwgd2UgbGVhcm4gdGhhdCB0aGUgR29vZ2xlIE1hcHMgbGltaXRzIHRvIDI1MDAgcXVlcmllcyBwZXIgZGF5LiBXZSBzZWUgdGhhdCB3ZSBjYW4gdXNlIGBnZW9jb2RlUXVlcnlDaGVjaygpYCB0byBkZXRlcm1pbmUgaG93IG1hbnkgcXVlcmllcyByZW1haW4gdG9kYXkuDQoNCmBgYHtyLCBtZXNzYWdlPVRSVUV9DQojQ2hlY2sgaG93IG1hbnkgZnJlZSBnZW9jb2RlcyB3ZSBoYXZlIA0KZ2VvY29kZVF1ZXJ5Q2hlY2soKSAjMjUwMCBwZXIgZGF5IGxpbWl0DQpgYGANCg0KVGhlIEhlbHAgZG9jdW1lbnRhdGlvbiBhbHNvIGluZm9ybWVkIHVzIHRoYXQgd2UgbmVlZCB0byBnaXZlIHRoZSBgZ2VvY29kZSgpYCBmdW5jdGlvbiBhIHNpbmdsZSAiY2hhcmFjdGVyIHZlY3RvciIgZm9yIHRoZSBgbG9jYXRpb25gIGlucHV0LiBJdCB3aWxsIGJlIGhlbHBmdWwgdG8gaW5jbHVkZSBub3QganVzdCB0aGUgc2l0ZSwgYnV0IGFsc28gdGhlIHByb3ZpbmNlIGFuZCBjb3VudHJ5LiBMZXQncyBzdHJpbmcgdGhvc2UgdG9nZXRoZXIgdXNpbmcgdGhlIGBwYXN0ZTAoKWAgZnVuY3Rpb24uDQoNCmBgYHtyfQ0KIyBNYWtlIGEgbmV3IHN0cmluZyB0aGF0IGhhcyBhbGwgdGhlIHBsYWNlIGRldGFpbHMgdG9nZXRoZXINCnBsYWNlPC0gcGFzdGUwKEFOQzIwMTIkc2l0ZSwgIiwgIiwgQU5DMjAxMiRwcm92aW5jZSwgIiwgIiwgIlppbWJhYndlIikNCmhlYWQocGxhY2UpDQpgYGANCg0KTm90ZSB0aGF0IHdlIGFyZSB1c2luZyB0aGUgYHNpdGVgIHZhcmlhYmxlIHRvIGxvY2F0ZSB0aGUgcGxhY2UuIEFsdGVybmF0aXZlbHksIHdlIGNvdWxkIGluY2x1ZGUgdGhlIGBuYW1lYCB2YXJpYWJsZSB0byBnZXQgdGhlIG1vcmUgcHJlY2lzZSBsb2NhdGlvbiBvZiB0aGUgaGVhbHRoIGZhY2lsaXR5LiBHb29nbGUga25vd3MgdGhlIGxvY2F0aW9ucyBvZiBtYW55IGhlYWx0aCBmYWNpbGl0aWVzIGluIFppbWJhYndlIGJ5IG5hbWUuIEhvd2V2ZXIsIEdvb2dsZSBtaWdodCBub3QgZmluZCB0aGUgbG9jYXRpb24gaWYgaXQgaXMgdG9vIHNwZWNpZmljLCBzbyB3ZSB3b3VsZCBoYXZlIHRvIGRvIHNvbWUgY2xlYW5pbmcgYW5kIHJ1biBhIGZldyByb3VuZHMgb2YgZ2VvY29kaW5nIHVudGlsIGFsbCBsb2NhdGlvbnMgY291bGQgYmUgZm91bmQuIEZvciBub3csIGxldCdzIGp1c3QgdXNlIGBzaXRlYCB0byBnZXQgdGhlIGFwcHJveGltYXRlIGxvY2F0aW9uLiBUaGlzIHdpbGwgYmUgYWRlcXVhdGUgZm9yIGEgbmF0aW9uYWwtbGV2ZWwgc3RhdGljIG1hcC4NCg0KYGBge3IgZXZhbD1GQUxTRX0NCiMgTm93IHRyeSBnZW9jb2RpbmcNCmdlb2NvZGVfcmVzdWx0IDwtIGdlb2NvZGUocGxhY2UsIG91dHB1dCA9ICJtb3JlIikNCmBgYA0KDQpZb3UnbGwgc2VlIHNvbWUgcmVkIHRleHQgc3RhcnQgdG8gYXBwZWFyIGluIHRoZSBDb25zb2xlLiBUaGlzIGlzIG5vdGhpbmcgdG8gd29ycnkgYWJvdXQ7IGl0IGp1c3Qgc2hvd3MgdGhhdCBxdWVyaWVzIGFyZSBydW5uaW5nLiBXaGVuIHRoZSByZWQgdGV4dCBzdG9wcyBhcHBlYXJpbmcsIHlvdSdsbCBrbm93IHRoZSBnZW9jb2RpbmcgaXMgZmluaXNoZWQuIENoZWNrIHRoZSByZWQgdGV4dCByZXN1bHRzIHRvIG1ha2Ugc3VyZSBubyBlcnJvcnMgb2NjdXJyZWQuIFZpZXcgdGhlIHJlc3VsdHMuDQoNCmBgYHtyLCBpbmNsdWRlPUZBTFNFfQ0KZ2VvY29kZV9yZXN1bHQgPC0gcmVhZC5jc3YoIkFOQzIwMTJfZ2VvY29kZV9yZXN1bHQuY3N2IikgDQpgYGANCg0KYGBge3J9DQpoZWFkKGdlb2NvZGVfcmVzdWx0KQ0KYGBgDQoNCldlIHNlZSB0aGF0IEdvb2dsZSByZXBvcnRlZCBiYWNrIG51bWVyb3VzIGRldGFpbHMuIElmIHdlIG9ubHkgd2FudGVkIHRoZSBsb25naXR1ZGUgYW5kIGxhdGl0dWRlLCB3ZSBjb3VsZCBoYXZlIGxlZnQgb3V0IHRoZSAibW9yZSIgb3B0aW9uIGluIG91ciBnZW9jb2RlIHF1ZXJ5LiBIb3dldmVyLCBkZXRhaWxzIGFyZSB1c2VmdWwgZm9yIGNoZWNraW5nIHRoZSBhY2N1cmFjeSBvZiB0aGUgZ2VvY29kaW5nIHJlc3VsdHMuIExldCdzIGNoZWNrIHdoZXRoZXIgR29vZ2xlIGxvY2F0ZWQgdGhlc2UgcG9pbnRzIHdpdGhpbiB0aGUgc2FtZSBkaXN0cmljdHMgdGhhdCB3ZXJlIHJlcG9ydGVkIGluIHRoZSBBTkMgc3VydmV5IHJlcG9ydC4NCg0KYGBge3J9DQojIFF1YWxpdHkgY2hlY2sNCmNiaW5kKEFOQzIwMTIkZGlzdHJpY3QsIGdlb2NvZGVfcmVzdWx0JGFkbWluaXN0cmF0aXZlX2FyZWFfbGV2ZWxfMikNCmBgYA0KDQpMb29rcyBwcmV0dHkgZ29vZCEgVGhlcmUgYXJlIGEgZmV3IGBOQWAgcmVzdWx0cyB3aGVyZSBHb29nbGUgZGlkbid0IHJlcG9ydCB0aGUgZGlzdHJpY3QsIGJ1dCBpZiB5b3UgZnVydGhlciBpbnNwZWN0IHRoZSBvdGhlciBmaWVsZHMgeW91J2xsIHNlZSB0aGF0IHRoZXkgc2VlbSB0byBiZSBpbiB0aGUgcmlnaHQgYXJlYS4NCg0KTm93IHRoYXQgd2UgaGF2ZSBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlLCB3ZSBjYW4gY29udmVydCB0byBhIHNoYXBlZmlsZSBhbmQgbWFwISBGaXJzdCwgd2UgbmVlZCB0byBtZXJnZSB0aGUgZ2VvY29kaW5nIHJlc3VsdHMgd2l0aCB0aGUgQU5DIGRhdGEuIExldCdzIGFsc28gZXhwb3J0IHRoaXMgZGF0YSBmcmFtZSB0byBhIENTViAoY29tbWEgc2VwYXJhdGVkIHZhbHVlcykgc3ByZWFkc2hlZXQgZmlsZSBzbyB3ZSBjYW4gYWNjZXNzIHRoZSBnZW9jb2RlZCBkYXRhIG91dHNpZGUgb2YgUi4NCg0KYGBge3IgfQ0KIyBNZXJnZSBBTkMgZGF0YSB3aXRoIGdlb2NvZGluZyByZXN1bHRzDQpBTkMyMDEyX2dlb2NvZGVkIDwtIGNiaW5kKEFOQzIwMTIsIGdlb2NvZGVfcmVzdWx0KQ0KYGBgDQoNCmBgYHtyfQ0KaGVhZChBTkMyMDEyX2dlb2NvZGVkKQ0KY2xhc3MoQU5DMjAxMl9nZW9jb2RlZCkNCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFfQ0KIyBFeHBvcnQgYXMgYSBDU1YgdG8gYWNjZXNzIG91dHNpZGUgb2YgUg0Kd3JpdGUuY3N2KEFOQzIwMTJfZ2VvY29kZWQsIGZpbGU9IkFOQzIwMTJfZ2VvY29kZWQuY3N2IikNCmBgYA0KDQoqKk5PVEU6KiogSWYgeW91IHdlcmUgTk9UIGFibGUgdG8gc3VjY2Vzc2Z1bGx5IGdlb2NvZGUsIHlvdSBzaG91bGQgZG93bmxvYWQgW3RoaXMgZmlsZV0oaHR0cHM6Ly9jYWZhaGV5LmdpdGh1Yi5pby9BTkMyMDEyX2dlb2NvZGVkLmNzdikgdG8gdXNlIGZvciB0aGUgcmVtYWluZGVyIG9mIHRoZSB3b3Jrc2hvcC4gU2F2ZSBpdCBpbiB5b3VyIFNwYXRpYWwgV29ya3Nob3AgZm9sZGVyIG9uIHlvdXIgRGVza3RvcCBhbmQgaW1wb3J0IGl0IHdpdGggdGhlIGZvbGxvd2luZyBjb2RlOiBgQU5DMjAxMl9nZW9jb2RlZCA8LSByZWFkLmNzdigiQU5DMjAxMl9nZW9jb2RlZC5jc3YiKWAgDQoNCmBgYHtyfQ0KIyBDb252ZXJ0IHRvIHNoYXBlZmlsZQ0KQU5DX3NocCA8LSBBTkMyMDEyX2dlb2NvZGVkDQpjb29yZGluYXRlcyhBTkNfc2hwKSA8LSB+bG9uICsgbGF0DQpjbGFzcyhBTkNfc2hwKQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9MTB9DQojIEltcG9ydCBwcm92aW5jZXMgc2hhcGVmaWxlDQpwcm92aW5jZXNfc2hwIDwtIHJlYWRPR1IoInp3ZV9wb2xibmRhX2FkbTFfMjUwa19jc28uc2hwIikNCg0KIyBQbG90IEFOQyBzaXRlcyB3aXRoIHByb3ZpbmNlcw0KcGxvdChwcm92aW5jZXNfc2hwLCBjb2wgPSAiYXp1cmUiLCBtYWluID0gIlppbWJhYndlIEFOQyBTZW50aW5lbCBTaXRlcyAyMDEyIikNCnBsb3QoQU5DX3NocCwgYWRkID0gVCwgcGNoID0gMTUpDQp0ZXh0KEFOQ19zaHAkbG9uLCBBTkNfc2hwJGxhdCAtIDAuMSwgbGFiZWxzID0gQU5DX3NocCRzaXRlLCBjZXggPSAwLjgpDQpgYGANCg0KSXQncyBkaWZmaWN1bHQgdG8gbWFrZSBvdXQgYWxsIG9mIHRoZSBwb2ludHMgY2x1c3RlcmVkIGluIEhhcmFyZSwgc28gbGV0J3MgY3JlYXRlIGEgbWFwIG9mIGp1c3QgdGhlIEhhcmFyZSBwcm92aW5jZS4gV2UgY2FuIHVzZSBzcXVhcmUgYnJhY2tldHMgdG8gaW5kaWNhdGUgdGhlIHN1YnNldCBvZiBwcm92aW5jZXNfc2hwIHdlIHdhbnQgdG8gc2VsZWN0LiBGb3IgdGhlIGxhYmVscywgc3VidHJhY3QgYSBzbWFsbCB2YWx1ZSAoMC4wMSkgZnJvbSB0aGUgbGF0aXR1ZGUgc28gdGhhdCB0aGUgbGFiZWwgd2lsbCBhcHBlYXIganVzdCBiZWxvdyB0aGUgcG9pbnQuDQoNCmBgYHtyfQ0KIyBQbG90IG9ubHkgSGFyYXJlDQpwbG90KHByb3ZpbmNlc19zaHBbcHJvdmluY2VzX3NocCRQUk9WSU5DRT09IkhhcmFyZSIsIF0sDQogICAgIGNvbCA9ICJhenVyZSIsIG1haW4gPSAiSGFyYXJlIEFOQyBTZW50aW5lbCBTaXRlcyAyMDEyIikNCnBsb3QoQU5DX3NocCwgYWRkID0gVCwgcGNoID0gMTUpDQp0ZXh0KEFOQ19zaHAkbG9uLCBBTkNfc2hwJGxhdCAtIDAuMDEsIGxhYmVscyA9IEFOQ19zaHAkc2l0ZSwgY2V4ID0gMC44KQ0KYGBgDQoNCg0KDQoNCg0KWyoqPDwgV29ya3Nob3AgSG9tZSoqXShodHRwczovL2NhZmFoZXkuZ2l0aHViLmlvL3NwYXRpYWwpDQo=