The following tutorial is a simplified version based on a previous geo-mapping tutorial (http://rpubs.com/cosmopolitanvan/224741). Consult with the older version for ways to remove errors in coordinate data.

You can use this tutorial to map Twitter users with coordinates obtained from Google Map API.

Disclaimer: this tutorial uses part of the codes in http://lucaspuente.github.io/notes/2016/04/05/Mapping-Twitter-Followers.

The required R libraries

require(twitteR)
require(data.table)
require(RJSONIO)
require(leaflet)

A quick demo of the location information on Twitter bio

#Get the location of @UMassPoll. To run the following code, you must first set up Twitter API. 
user<-getUser("UMassPoll")
user$location
[1] "Amherst, MA"

We will map the tweets mentioning @realdonaldtrump.For this task, we have a pre-collected data (tweets_mentioning_dt.csv). We will load the CSV file and name it tweets.

tweets <- read.csv("tweets_mentioning_dt.csv")

Create two new columns; one for storing the location information disclosed on a user’s Twitter bio, and the other column for storing the URL to a user’s profile image.

tweets$user_location_on_twitter_bio <- NA
tweets$profile_image <- NA

We will now loop over each tweet, finding who sent it and the location information from the user’s Twitter bio. Let’s first grab the location info for the first 200 tweets (That is why we put 1:200). You can of course change the number to cover all tweets (the tweets dataframe have 500 tweets, so you can put 1:500).

Because we will use the Twitter API, make sure you run the Twitter API authorization code before trying out the code below.

for (user in tweets$screenName[1:200]){  
  print(c("finding the profile for:",user))
  Sys.sleep(3) #build in a sleeper to prevent Twitter API rate limit error. 
  try(tweets[tweets$screenName==user,]$user_location_on_twitter_bio <- getUser(user)$location)
  try(tweets[tweets$screenName==user,]$profile_image <- getUser(user)$profileImageUrl)
}

While running the previous code, you likely have noticed some errors returned from the Twitter API (such as Not Found (HTTP 404)). When a tweet gives you an error, the code will ignore the error and proceed to the next tweet. There are many reasons for errors from the Twitter API. One common reason is that Twitter user could be suspended or deleted and therefore there is no location information to be found for the user.

The location information is stored in the column named tweets$user_location_on_twitter_bio. We can match the cities and states in the column with the exact coordinates through Google Map API. To do that, obtain a key from Google Maps Geocoding API. (https://developers.google.com/maps/documentation/geocoding/get-api-key). There is a limit of 2,500 coordinates per day if you are a standard Google Map API user.

#create a function for getting coordinates from Google Map API.We use the code published by Lucas Puente (http://lucaspuente.github.io/notes/2016/04/05/Mapping-Twitter-Followers)
tweets$lat <-NA
tweets$lng <-NA

source("https://raw.githubusercontent.com/LucasPuente/geocoding/master/geocode_helpers.R")
source("https://raw.githubusercontent.com/LucasPuente/geocoding/master/modified_geocode.R")
geocode_apply<-function(x){
  geocode(x, source = "google", output = "all", api_key="xxx")
}

We will create a separate dataframe ONLY for tweets containing geo-info. This dataframe is named tweets_withgeo. We will then loop over the user_location_on_twitter_bio column in tweets_withgeo and find corresponding coordinates for each location.

tweets_withgeo <- tweets[tweets$user_location_on_twitter_bio != "" & !is.na(tweets$user_location_on_twitter_bio),]

for (name in tweets_withgeo$user_location_on_twitter_bio[1:149]){ #get the coordinate data for the first 100 tweets via Google Map API.
  rowid<-which(tweets_withgeo$user_location_on_twitter_bio == name)
  print(paste0("getting the coordinates for:",name,", rowid is:",rowid))
  Sys.sleep(1)
  try(geodata <- geocode_apply(name))
  
  if (geodata$status=="OK" & length(geodata$results)=="1") {
    print(c("the lat is:",geodata$results[[1]]$geometry$location[[1]]))
    print(c("the lngis:", geodata$results[[1]]$geometry$location[[2]]))
    tweets_withgeo[rowid,]$lat <- geodata$results[[1]]$geometry$location[[1]]
    tweets_withgeo[rowid,]$lng <- geodata$results[[1]]$geometry$location[[2]]
  }else {
    print ("skipping")
  }
}
#create a separate dataframe called tweets_withgeo_show. This dataframe contains only complete coordinates. 
tweets_withgeo_show <- tweets_withgeo[!is.na(tweets_withgeo$lat),c("lat","lng", "user_location_on_twitter_bio", "profile_image", "text")]
map1 <- leaflet() %>% setView(lng = -98.35, lat = 39.50, zoom = 3)
map1 <- leaflet(data = tweets_withgeo_show) %>% 
  addTiles() %>%
  setView(lng = -98.35, lat = 39.50, zoom = 4) %>% 
  addCircleMarkers(lng = ~lng, lat = ~lat, popup = ~ as.character(user_location_on_twitter_bio)) %>% 
  addProviderTiles("CartoDB.Positron") %>%
  addCircleMarkers(
    stroke = FALSE, fillOpacity = 0.5
  ) 
Assuming 'lng' and 'lat' are longitude and latitude, respectively
map1

Try a different style? Show users’ profile images on the map

usericon <- makeIcon(
  iconUrl = tweets_withgeo_show$profile_image,
  iconWidth = 15, iconHeight = 15
)
map2 <- leaflet(data = tweets_withgeo_show) %>% 
  addTiles() %>%
  setView(lng = -98.35, lat = 39.50, zoom = 4) %>% 
  addMarkers(lng = ~lng, lat = ~lat, popup = ~ as.character(user_location_on_twitter_bio),icon = usericon,data = tweets_withgeo_show) %>% 
  addProviderTiles(providers$Esri.NatGeoWorldMap) 
map2

This tutorial is developed for COMM497DB Fall 2017, taught at UMass-Amherst.

If you find this tutorial helpful and would like to use it in your projects, please acknowledge the source:

Xu, Weiai W. (2017). How to Detect Sentiments from Donald Trump’s Tweets?. Amherst, MA: http://curiositybits.com

LS0tCnRpdGxlOiAiR2VvLU1hcHBpbmcgVHdpdHRlciBVc2VycyIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKICBodG1sX2RvY3VtZW50OiBkZWZhdWx0Ci0tLQpUaGUgZm9sbG93aW5nIHR1dG9yaWFsIGlzIGEgc2ltcGxpZmllZCB2ZXJzaW9uIGJhc2VkIG9uIGEgcHJldmlvdXMgZ2VvLW1hcHBpbmcgdHV0b3JpYWwgKGh0dHA6Ly9ycHVicy5jb20vY29zbW9wb2xpdGFudmFuLzIyNDc0MSkuIENvbnN1bHQgd2l0aCB0aGUgb2xkZXIgdmVyc2lvbiBmb3Igd2F5cyB0byByZW1vdmUgZXJyb3JzIGluIGNvb3JkaW5hdGUgZGF0YS4KCllvdSBjYW4gdXNlIHRoaXMgdHV0b3JpYWwgdG8gbWFwIFR3aXR0ZXIgdXNlcnMgd2l0aCBjb29yZGluYXRlcyBvYnRhaW5lZCBmcm9tIEdvb2dsZSBNYXAgQVBJLiAKCkRpc2NsYWltZXI6IHRoaXMgdHV0b3JpYWwgdXNlcyBwYXJ0IG9mIHRoZSBjb2RlcyBpbiAgaHR0cDovL2x1Y2FzcHVlbnRlLmdpdGh1Yi5pby9ub3Rlcy8yMDE2LzA0LzA1L01hcHBpbmctVHdpdHRlci1Gb2xsb3dlcnMuIAoKVGhlIHJlcXVpcmVkIFIgbGlicmFyaWVzCmBgYHtyIHdhcm5pbmcgPSBGQUxTRSwgcmVzdWx0cz0iaGlkZSJ9CmxpYnJhcnkodHdpdHRlUikKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KFJKU09OSU8pCmxpYnJhcnkobGVhZmxldCkKYGBgCgpBIHF1aWNrIGRlbW8gb2YgdGhlIGxvY2F0aW9uIGluZm9ybWF0aW9uIG9uIFR3aXR0ZXIgYmlvCmBgYHtyfQojR2V0IHRoZSBsb2NhdGlvbiBvZiBAVU1hc3NQb2xsLiBUbyBydW4gdGhlIGZvbGxvd2luZyBjb2RlLCB5b3UgbXVzdCBmaXJzdCBzZXQgdXAgVHdpdHRlciBBUEkuIAp1c2VyPC1nZXRVc2VyKCJVTWFzc1BvbGwiKQp1c2VyJGxvY2F0aW9uCmBgYAoKV2Ugd2lsbCBtYXAgdGhlIHR3ZWV0cyBtZW50aW9uaW5nIEByZWFsZG9uYWxkdHJ1bXAuRm9yIHRoaXMgdGFzaywgd2UgaGF2ZSBhIHByZS1jb2xsZWN0ZWQgZGF0YSAodHdlZXRzX21lbnRpb25pbmdfZHQuY3N2KS4gV2Ugd2lsbCBsb2FkIHRoZSBDU1YgZmlsZSBhbmQgbmFtZSBpdCB0d2VldHMuIAoKYGBge3IsIGV2YWw9RkFMU0V9CnR3ZWV0cyA8LSByZWFkLmNzdigidHdlZXRzX21lbnRpb25pbmdfZHQuY3N2IikKYGBgCgpDcmVhdGUgdHdvIG5ldyBjb2x1bW5zOyBvbmUgZm9yIHN0b3JpbmcgdGhlIGxvY2F0aW9uIGluZm9ybWF0aW9uIGRpc2Nsb3NlZCBvbiBhIHVzZXIncyBUd2l0dGVyIGJpbywgYW5kIHRoZSBvdGhlciBjb2x1bW4gZm9yIHN0b3JpbmcgdGhlIFVSTCB0byBhIHVzZXIncyBwcm9maWxlIGltYWdlLiAKYGBge3IsIGV2YWw9RkFMU0V9CnR3ZWV0cyR1c2VyX2xvY2F0aW9uX29uX3R3aXR0ZXJfYmlvIDwtIE5BCnR3ZWV0cyRwcm9maWxlX2ltYWdlIDwtIE5BCmBgYAoKV2Ugd2lsbCBub3cgbG9vcCBvdmVyIGVhY2ggdHdlZXQsIGZpbmRpbmcgd2hvIHNlbnQgaXQgYW5kIHRoZSBsb2NhdGlvbiBpbmZvcm1hdGlvbiBmcm9tIHRoZSB1c2VyJ3MgVHdpdHRlciBiaW8uIExldCdzIGZpcnN0IGdyYWIgdGhlIGxvY2F0aW9uIGluZm8gZm9yIHRoZSBmaXJzdCAyMDAgdHdlZXRzIChUaGF0IGlzIHdoeSB3ZSBwdXQgMToyMDApLiBZb3UgY2FuIG9mIGNvdXJzZSBjaGFuZ2UgdGhlIG51bWJlciB0byBjb3ZlciBhbGwgdHdlZXRzICh0aGUgdHdlZXRzIGRhdGFmcmFtZSBoYXZlIDUwMCB0d2VldHMsIHNvIHlvdSBjYW4gcHV0IDE6NTAwKS4KCkJlY2F1c2Ugd2Ugd2lsbCB1c2UgdGhlIFR3aXR0ZXIgQVBJLCBtYWtlIHN1cmUgeW91IHJ1biB0aGUgVHdpdHRlciBBUEkgYXV0aG9yaXphdGlvbiBjb2RlIGJlZm9yZSB0cnlpbmcgb3V0IHRoZSBjb2RlIGJlbG93LiAKYGBge3IsIGV2YWw9RkFMU0V9CmZvciAodXNlciBpbiB0d2VldHMkc2NyZWVuTmFtZVsxOjIwMF0peyAgCiAgcHJpbnQoYygiZmluZGluZyB0aGUgcHJvZmlsZSBmb3I6Iix1c2VyKSkKICBTeXMuc2xlZXAoMykgI2J1aWxkIGluIGEgc2xlZXBlciB0byBwcmV2ZW50IFR3aXR0ZXIgQVBJIHJhdGUgbGltaXQgZXJyb3IuIAogIHRyeSh0d2VldHNbdHdlZXRzJHNjcmVlbk5hbWU9PXVzZXIsXSR1c2VyX2xvY2F0aW9uX29uX3R3aXR0ZXJfYmlvIDwtIGdldFVzZXIodXNlcikkbG9jYXRpb24pCiAgdHJ5KHR3ZWV0c1t0d2VldHMkc2NyZWVuTmFtZT09dXNlcixdJHByb2ZpbGVfaW1hZ2UgPC0gZ2V0VXNlcih1c2VyKSRwcm9maWxlSW1hZ2VVcmwpCn0KYGBgCgpXaGlsZSBydW5uaW5nIHRoZSBwcmV2aW91cyBjb2RlLCB5b3UgbGlrZWx5IGhhdmUgbm90aWNlZCBzb21lIGVycm9ycyByZXR1cm5lZCBmcm9tIHRoZSBUd2l0dGVyIEFQSSAoc3VjaCBhcyBOb3QgRm91bmQgKEhUVFAgNDA0KSkuIFdoZW4gYSB0d2VldCBnaXZlcyB5b3UgYW4gZXJyb3IsIHRoZSBjb2RlIHdpbGwgaWdub3JlIHRoZSBlcnJvciBhbmQgcHJvY2VlZCB0byB0aGUgbmV4dCB0d2VldC4gVGhlcmUgYXJlIG1hbnkgcmVhc29ucyBmb3IgZXJyb3JzIGZyb20gdGhlIFR3aXR0ZXIgQVBJLiBPbmUgY29tbW9uIHJlYXNvbiBpcyB0aGF0IFR3aXR0ZXIgdXNlciBjb3VsZCBiZSBzdXNwZW5kZWQgb3IgZGVsZXRlZCBhbmQgdGhlcmVmb3JlIHRoZXJlIGlzIG5vIGxvY2F0aW9uIGluZm9ybWF0aW9uIHRvIGJlIGZvdW5kIGZvciB0aGUgdXNlci4gCgpUaGUgbG9jYXRpb24gaW5mb3JtYXRpb24gaXMgc3RvcmVkIGluIHRoZSBjb2x1bW4gbmFtZWQgdHdlZXRzJHVzZXJfbG9jYXRpb25fb25fdHdpdHRlcl9iaW8uIFdlIGNhbiBtYXRjaCB0aGUgY2l0aWVzIGFuZCBzdGF0ZXMgaW4gdGhlIGNvbHVtbiB3aXRoIHRoZSBleGFjdCBjb29yZGluYXRlcyB0aHJvdWdoIEdvb2dsZSBNYXAgQVBJLiBUbyBkbyB0aGF0LCBvYnRhaW4gYSBrZXkgZnJvbSBHb29nbGUgTWFwcyBHZW9jb2RpbmcgQVBJLiAoaHR0cHM6Ly9kZXZlbG9wZXJzLmdvb2dsZS5jb20vbWFwcy9kb2N1bWVudGF0aW9uL2dlb2NvZGluZy9nZXQtYXBpLWtleSkuIFRoZXJlIGlzIGEgbGltaXQgb2YgMiw1MDAgY29vcmRpbmF0ZXMgcGVyIGRheSBpZiB5b3UgYXJlIGEgc3RhbmRhcmQgR29vZ2xlIE1hcCBBUEkgdXNlci4gCmBgYHtyLCBlY2hvPVRSVUUsIGV2YWw9RkFMU0V9CiNjcmVhdGUgYSBmdW5jdGlvbiBmb3IgZ2V0dGluZyBjb29yZGluYXRlcyBmcm9tIEdvb2dsZSBNYXAgQVBJLldlIHVzZSB0aGUgY29kZSBwdWJsaXNoZWQgYnkgTHVjYXMgUHVlbnRlIChodHRwOi8vbHVjYXNwdWVudGUuZ2l0aHViLmlvL25vdGVzLzIwMTYvMDQvMDUvTWFwcGluZy1Ud2l0dGVyLUZvbGxvd2VycykKdHdlZXRzJGxhdCA8LU5BCnR3ZWV0cyRsbmcgPC1OQQoKc291cmNlKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vTHVjYXNQdWVudGUvZ2VvY29kaW5nL21hc3Rlci9nZW9jb2RlX2hlbHBlcnMuUiIpCnNvdXJjZSgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL0x1Y2FzUHVlbnRlL2dlb2NvZGluZy9tYXN0ZXIvbW9kaWZpZWRfZ2VvY29kZS5SIikKZ2VvY29kZV9hcHBseTwtZnVuY3Rpb24oeCl7CiAgZ2VvY29kZSh4LCBzb3VyY2UgPSAiZ29vZ2xlIiwgb3V0cHV0ID0gImFsbCIsIGFwaV9rZXk9Inh4eCIpCn0KYGBgCgpXZSB3aWxsIGNyZWF0ZSBhIHNlcGFyYXRlIGRhdGFmcmFtZSBPTkxZIGZvciB0d2VldHMgY29udGFpbmluZyBnZW8taW5mby4gVGhpcyBkYXRhZnJhbWUgaXMgbmFtZWQgdHdlZXRzX3dpdGhnZW8uIFdlIHdpbGwgdGhlbiBsb29wIG92ZXIgdGhlIHVzZXJfbG9jYXRpb25fb25fdHdpdHRlcl9iaW8gY29sdW1uIGluIHR3ZWV0c193aXRoZ2VvIGFuZCBmaW5kIGNvcnJlc3BvbmRpbmcgY29vcmRpbmF0ZXMgZm9yIGVhY2ggbG9jYXRpb24uCmBgYHtyfQp0d2VldHNfd2l0aGdlbyA8LSB0d2VldHNbdHdlZXRzJHVzZXJfbG9jYXRpb25fb25fdHdpdHRlcl9iaW8gIT0gIiIgJiAhaXMubmEodHdlZXRzJHVzZXJfbG9jYXRpb25fb25fdHdpdHRlcl9iaW8pLF0KCmZvciAobmFtZSBpbiB0d2VldHNfd2l0aGdlbyR1c2VyX2xvY2F0aW9uX29uX3R3aXR0ZXJfYmlvWzE6MTQ5XSl7ICNnZXQgdGhlIGNvb3JkaW5hdGUgZGF0YSBmb3IgdGhlIGZpcnN0IDEwMCB0d2VldHMgdmlhIEdvb2dsZSBNYXAgQVBJLgogIHJvd2lkPC13aGljaCh0d2VldHNfd2l0aGdlbyR1c2VyX2xvY2F0aW9uX29uX3R3aXR0ZXJfYmlvID09IG5hbWUpCiAgcHJpbnQocGFzdGUwKCJnZXR0aW5nIHRoZSBjb29yZGluYXRlcyBmb3I6IixuYW1lLCIsIHJvd2lkIGlzOiIscm93aWQpKQogIFN5cy5zbGVlcCgxKQogIHRyeShnZW9kYXRhIDwtIGdlb2NvZGVfYXBwbHkobmFtZSkpCiAgCiAgaWYgKGdlb2RhdGEkc3RhdHVzPT0iT0siICYgbGVuZ3RoKGdlb2RhdGEkcmVzdWx0cyk9PSIxIikgewogICAgcHJpbnQoYygidGhlIGxhdCBpczoiLGdlb2RhdGEkcmVzdWx0c1tbMV1dJGdlb21ldHJ5JGxvY2F0aW9uW1sxXV0pKQogICAgcHJpbnQoYygidGhlIGxuZ2lzOiIsIGdlb2RhdGEkcmVzdWx0c1tbMV1dJGdlb21ldHJ5JGxvY2F0aW9uW1syXV0pKQogICAgdHdlZXRzX3dpdGhnZW9bcm93aWQsXSRsYXQgPC0gZ2VvZGF0YSRyZXN1bHRzW1sxXV0kZ2VvbWV0cnkkbG9jYXRpb25bWzFdXQogICAgdHdlZXRzX3dpdGhnZW9bcm93aWQsXSRsbmcgPC0gZ2VvZGF0YSRyZXN1bHRzW1sxXV0kZ2VvbWV0cnkkbG9jYXRpb25bWzJdXQogIH1lbHNlIHsKICAgIHByaW50ICgic2tpcHBpbmciKQogIH0KfQpgYGAKCgpgYGB7cn0KI2NyZWF0ZSBhIHNlcGFyYXRlIGRhdGFmcmFtZSBjYWxsZWQgdHdlZXRzX3dpdGhnZW9fc2hvdy4gVGhpcyBkYXRhZnJhbWUgY29udGFpbnMgb25seSBjb21wbGV0ZSBjb29yZGluYXRlcy4gCgp0d2VldHNfd2l0aGdlb19zaG93IDwtIHR3ZWV0c193aXRoZ2VvWyFpcy5uYSh0d2VldHNfd2l0aGdlbyRsYXQpLGMoImxhdCIsImxuZyIsICJ1c2VyX2xvY2F0aW9uX29uX3R3aXR0ZXJfYmlvIiwgInByb2ZpbGVfaW1hZ2UiLCAidGV4dCIpXQoKbWFwMSA8LSBsZWFmbGV0KCkgJT4lIHNldFZpZXcobG5nID0gLTk4LjM1LCBsYXQgPSAzOS41MCwgem9vbSA9IDMpCm1hcDEgPC0gbGVhZmxldChkYXRhID0gdHdlZXRzX3dpdGhnZW9fc2hvdykgJT4lIAogIGFkZFRpbGVzKCkgJT4lCiAgc2V0VmlldyhsbmcgPSAtOTguMzUsIGxhdCA9IDM5LjUwLCB6b29tID0gNCkgJT4lIAogIGFkZENpcmNsZU1hcmtlcnMobG5nID0gfmxuZywgbGF0ID0gfmxhdCwgcG9wdXAgPSB+IGFzLmNoYXJhY3Rlcih1c2VyX2xvY2F0aW9uX29uX3R3aXR0ZXJfYmlvKSkgJT4lIAogIGFkZFByb3ZpZGVyVGlsZXMoIkNhcnRvREIuUG9zaXRyb24iKSAlPiUKICBhZGRDaXJjbGVNYXJrZXJzKAogICAgc3Ryb2tlID0gRkFMU0UsIGZpbGxPcGFjaXR5ID0gMC41CiAgKSAKbWFwMQpgYGAKClRyeSBhIGRpZmZlcmVudCBzdHlsZT8gU2hvdyB1c2VycycgcHJvZmlsZSBpbWFnZXMgb24gdGhlIG1hcApgYGB7cn0KdXNlcmljb24gPC0gbWFrZUljb24oCiAgaWNvblVybCA9IHR3ZWV0c193aXRoZ2VvX3Nob3ckcHJvZmlsZV9pbWFnZSwKICBpY29uV2lkdGggPSAxNSwgaWNvbkhlaWdodCA9IDE1CikKCm1hcDIgPC0gbGVhZmxldChkYXRhID0gdHdlZXRzX3dpdGhnZW9fc2hvdykgJT4lIAogIGFkZFRpbGVzKCkgJT4lCiAgc2V0VmlldyhsbmcgPSAtOTguMzUsIGxhdCA9IDM5LjUwLCB6b29tID0gNCkgJT4lIAogIGFkZE1hcmtlcnMobG5nID0gfmxuZywgbGF0ID0gfmxhdCwgcG9wdXAgPSB+IGFzLmNoYXJhY3Rlcih1c2VyX2xvY2F0aW9uX29uX3R3aXR0ZXJfYmlvKSxpY29uID0gdXNlcmljb24sZGF0YSA9IHR3ZWV0c193aXRoZ2VvX3Nob3cpICU+JSAKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRFc3JpLk5hdEdlb1dvcmxkTWFwKSAKbWFwMgoKYGBgCgoKVGhpcyB0dXRvcmlhbCBpcyBkZXZlbG9wZWQgZm9yIENPTU00OTdEQiBGYWxsIDIwMTcsIHRhdWdodCBhdCBVTWFzcy1BbWhlcnN0LiAKCklmIHlvdSBmaW5kIHRoaXMgdHV0b3JpYWwgaGVscGZ1bCBhbmQgd291bGQgbGlrZSB0byB1c2UgaXQgaW4geW91ciBwcm9qZWN0cywgcGxlYXNlIGFja25vd2xlZGdlIHRoZSBzb3VyY2U6CgpYdSwgV2VpYWkgVy4gKDIwMTcpLiBIb3cgdG8gRGV0ZWN0IFNlbnRpbWVudHMgZnJvbSBEb25hbGQgVHJ1bXAncyBUd2VldHM/LiBBbWhlcnN0LCBNQTogaHR0cDovL2N1cmlvc2l0eWJpdHMuY29tCgoK