Finding the Current Temperature
Open Weather Map
Open Weather Map provides an API for returning weather information in JSON or XML format.
A query requesting the current temperature in Iowa City format is XML format would would use a URL of the form
https://api.openweathermap.org/data/2.5/weather?q=Iowa+City,IA&mode=xml&appid=key
or
https://api.openweathermap.org/data/2.5/weather?lat=41.66&lon=-91.53&mode=xml&appid=key
with key replaced by your API key (free, but requires registration).
Here is a simple function to obtain the current temperature for from Open Weather Map based on latitude and longitude:
library(xml2)
findTempOWM <- function(lat, lon) {
base <- "https://api.openweathermap.org/data/2.5/weather"
url <- sprintf("%s?lat=%f&lon=%f&mode=xml&units=Imperial&appid=%s",
base, lat, lon, OWMkey)
page <- read_xml(url)
as.numeric(xml_text(xml_find_first(page, "//temperature/@value")))
}
For Iowa City you would use
findTempOWM(41.7, -91.5)
This function should be robust since the format of the response is documented and should not change.
Using commercial web services should be done with care as there are typically limitations and license terms to be considered.
They may also come and go: Google’s API was shut down in 2012.
National Weather Service
The National Weather Service provides a site that produces forecasts in a web page for a URL like this:
https://forecast.weather.gov/zipcity.php?inputstring=Iowa+City,IA
This function uses the National Weather Service site to find the current temperature by parsing the HTML data in the response:
findTempGov <- function(citystate) {
url <- paste("https://forecast.weather.gov/zipcity.php?inputstring",
url_escape(citystate),
sep = "=")
page <- read_html(url)
xpath <- "//p[@class=\"myforecast-current-lrg\"]"
tempNode <- xml_find_first(page, xpath)
nodeText <- xml_text(tempNode)
as.numeric(sub("([-+]?[[:digit:]]+).*", "\\1", nodeText))
}
For Iowa City you can use
findTempGov("Iowa City, IA")
This will need to be revised whenever the format of the page changes, as happened sometime in 2012.
Murrell’s Data Technologies book discusses XML, XPATH queries, regular expressions, and how to work with these in R.
Some other resources for regular expressions:
Recently the server has either been overloaded or modified to reject requests from the same address that are too close together so it is not currently useful for collecting temperatures at multile locations.
The National Weather Service also provides several APIs, including a REST API that returns JSON (XML may be available but didn’t seem to work right for me) that use a url like this:
https://forecast.weather.gov/MapClick.php?lat=41.7&lon=-91.5&FcstType=json
You can read the temperature from this API using the fromJSON
function from the jsonlite
package:
library(jsonlite)
icurl <-
"http://forecast.weather.gov/MapClick.php?lat=41.7&lon=-91.5&FcstType=json"
icjson <- fromJSON(icurl)
icjson$currentobservation$Temp
Uncortunately this also seems unreliable at the moment.
Temperatures and Locations for Some Iowa Cities
A small selection of Iowa cities:
places <- c("Ames", "Burlington", "Cedar Rapids", "Clinton",
"Council Bluffs", "Des Moines", "Dubuque", "Fort Dodge",
"Iowa City", "Keokuk", "Marshalltown", "Mason City",
"Newton", "Ottumwa", "Sioux City", "Waterloo")
Currently the OWM API seems most reliable.
To look up temperatures with that API, and to plot them on a map, we need to find the locations of these cities.
We can obtain a file of geocoded cities and read it into R:
if (! file.exists("cities.csv")) {
download.file("http://www.stat.uiowa.edu/~luke/data/cities.csv.zip",
"cities.csv.zip")
unzip("cities.csv.zip")
}
cities <- read.csv("cities.csv", header = FALSE)
names(cities) <- c("City", "State", "Lat", "Lon")
head(cities)
## City State Lat Lon
## 1 ROBERTS ID 43.69892 -112.17320
## 2 HODGENVILLE KY 37.55932 -85.70727
## 3 WILLIAMSPORT TN 35.72263 -87.21270
## 4 MAUMEE OH 41.57125 -83.68504
## 5 BERNE NY 42.60224 -74.15462
## 6 MONCLOVA OH 41.57725 -83.77238
Select the cities we want and find their temperatures:
library(dplyr)
temploc <- filter(cities, City %in% toupper(places), State == "IA") %>%
mutate(temp = mapply(findTempOWM, Lat, Lon))
head(temploc)
## City State Lat Lon temp
## 1 FORT DODGE IA 42.43860 -94.16659 53.56
## 2 IOWA CITY IA 41.65789 -91.53309 54.46
## 3 MARSHALLTOWN IA 42.03610 -92.99887 54.28
## 4 CEDAR RAPIDS IA 41.97666 -91.67315 53.74
## 5 BURLINGTON IA 40.89841 -91.16439 54.95
## 6 MASON CITY IA 43.15272 -93.19944 51.75
Remove any rows with missing temeratures:
temploc <- filter(temploc, ! is.na(temp))
Creating the Map
We can use the borders
function from ggplot2
along with geom_text
to show the results:
library(ggplot2)
library(ggthemes)
tpoints <- geom_text(aes(x = Lon, y = Lat, label = round(temp)),
color = "red",
fontface = "bold",
data = temploc)
p <- ggplot() +
borders("county", "iowa") +
tpoints +
coord_map() +
theme_map()
p
To add contours we can use interp
from the interp
package and the geom_contour
function:
library(interp)
surface <- with(temploc, interp(Lon, Lat, temp, linear = FALSE))
srfc <- expand.grid(Lon = surface$x, Lat = surface$y)
srfc$temp <- as.vector(surface$z)
tconts <- geom_contour(aes(x = Lon, y = Lat, z = temp),
data = srfc, na.rm = TRUE, bins = 6)
p + tconts + tpoints
The ggmap
package provides further map support, including background maps from Google, Stamen, and OpenStreatMap.
library(ggmap)
## ℹ Google's Terms of Service: <https://mapsplatform.google.com>
## ℹ Please cite ggmap if you use it! Use `citation("ggmap")` for details.
Using Google maps now requires an API key.
Downloading a Stamen map:
maptype <- "terrain"
maptype <- "toner"
map <- get_stamenmap(c(-97.2, 40.4, -89.9, 43.6), zoom = 8, maptype = maptype)
ggmap
creates a ggplot
object, sets the coordinate system, and makes some theme adjustments:
ggmap(map)
Add the county borders, contours, and temperatures.
ggmap(map) + borders("county", "iowa") + tconts + tpoints
LS0tCnRpdGxlOiAiQ3JlYXRpbmcgYSBDdXJyZW50IFRlbXBlcmF0dXJlIE1hcCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgY29kZV9mb2xkaW5nOiAiaGlkZSIKZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJWQgJUIsICVZICVIOiVNJylgIgotLS0KCmBgYHtyIGdsb2JhbF9vcHRpb25zLCBpbmNsdWRlID0gRkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChjb2xsYXBzZSA9IFRSVUUsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLAogICAgICAgICAgICAgICAgICAgICAgZmlnLmFsaWduID0gImNlbnRlciIpCmBgYAoKIyMgRmluZGluZyB0aGUgQ3VycmVudCBUZW1wZXJhdHVyZQoKIyMjIE9wZW4gV2VhdGhlciBNYXAKCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9CiMjIGtleXMgYXJlIHN0b3JlZCBpbiB0aGUgcGFyZW50IGRpcmVjdG9yeQprZXlzIDwtIGFzLmRhdGEuZnJhbWUocmVhZC5kY2YoIi4uL0FQSUtFWVMiKSkKT1dNa2V5IDwtIGtleXMka2V5W21hdGNoKCJPV00iLCBrZXlzJG5hbWUpXQpgYGAKCltPcGVuIFdlYXRoZXIgTWFwXShodHRwczovL29wZW53ZWF0aGVybWFwLm9yZy8pIHByb3ZpZGVzIGFuCltBUEldKGh0dHBzOi8vb3BlbndlYXRoZXJtYXAub3JnL2FwaSkgZm9yIHJldHVybmluZyB3ZWF0aGVyIGluZm9ybWF0aW9uCmluIEpTT04gb3IgWE1MIGZvcm1hdC4KCkEgcXVlcnkgcmVxdWVzdGluZyB0aGUgY3VycmVudCB0ZW1wZXJhdHVyZSBpbiBJb3dhIENpdHkgZm9ybWF0IGlzIFhNTApmb3JtYXQgd291bGQgd291bGQgdXNlIGEgVVJMIG9mIHRoZSBmb3JtCgo8aHR0cHM6Ly9hcGkub3BlbndlYXRoZXJtYXAub3JnL2RhdGEvMi41L3dlYXRoZXI/cT1Jb3dhK0NpdHksSUEmbW9kZT14bWwmYXBwaWQ9Pl9rZXlfCgpvcgoKPGh0dHBzOi8vYXBpLm9wZW53ZWF0aGVybWFwLm9yZy9kYXRhLzIuNS93ZWF0aGVyP2xhdD00MS42NiZsb249LTkxLjUzJm1vZGU9eG1sJmFwcGlkPT5fa2V5XwoKd2l0aCBfa2V5XyByZXBsYWNlZCBieSB5b3VyIEFQSSBrZXkgKGZyZWUsIGJ1dCByZXF1aXJlcyByZWdpc3RyYXRpb24pLgoKSGVyZSBpcyBhIHNpbXBsZSBmdW5jdGlvbiB0byBvYnRhaW4gdGhlIGN1cnJlbnQgdGVtcGVyYXR1cmUgZm9yIGZyb20gT3BlbgpXZWF0aGVyIE1hcCBiYXNlZCBvbiBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlOgoKYGBge3J9CmxpYnJhcnkoeG1sMikKZmluZFRlbXBPV00gPC0gZnVuY3Rpb24obGF0LCBsb24pIHsKICAgIGJhc2UgPC0gImh0dHBzOi8vYXBpLm9wZW53ZWF0aGVybWFwLm9yZy9kYXRhLzIuNS93ZWF0aGVyIgogICAgdXJsIDwtIHNwcmludGYoIiVzP2xhdD0lZiZsb249JWYmbW9kZT14bWwmdW5pdHM9SW1wZXJpYWwmYXBwaWQ9JXMiLAogICAgICAgICAgICAgICAgICAgYmFzZSwgbGF0LCBsb24sIE9XTWtleSkKICAgIHBhZ2UgPC0gcmVhZF94bWwodXJsKQogICAgYXMubnVtZXJpYyh4bWxfdGV4dCh4bWxfZmluZF9maXJzdChwYWdlLCAiLy90ZW1wZXJhdHVyZS9AdmFsdWUiKSkpCn0KYGBgCgpGb3IgSW93YSBDaXR5IHlvdSB3b3VsZCB1c2UKCmBgYHtyLCBldmFsID0gRkFMU0V9CmZpbmRUZW1wT1dNKDQxLjcsIC05MS41KQpgYGAKClRoaXMgZnVuY3Rpb24gc2hvdWxkIGJlIHJvYnVzdCBzaW5jZSB0aGUgZm9ybWF0IG9mIHRoZSByZXNwb25zZSBpcwpkb2N1bWVudGVkIGFuZCBzaG91bGQgbm90IGNoYW5nZS4KClVzaW5nIGNvbW1lcmNpYWwgd2ViIHNlcnZpY2VzIHNob3VsZCBiZSBkb25lIHdpdGggY2FyZSBhcyB0aGVyZSBhcmUKdHlwaWNhbGx5IGxpbWl0YXRpb25zIGFuZCBsaWNlbnNlIHRlcm1zIHRvIGJlIGNvbnNpZGVyZWQuCgpUaGV5IG1heSBhbHNvIGNvbWUgYW5kIGdvOiBHb29nbGXigJlzIEFQSSB3YXMgc2h1dCBkb3duIGluIDIwMTIuCgoKIyMjIE5hdGlvbmFsIFdlYXRoZXIgU2VydmljZQoKVGhlIE5hdGlvbmFsIFdlYXRoZXIgU2VydmljZSBwcm92aWRlcyBhIHNpdGUgdGhhdCBwcm9kdWNlcyBmb3JlY2FzdHMKaW4gYSB3ZWIgcGFnZSBmb3IgYSBVUkwgbGlrZSB0aGlzOgoKPGh0dHBzOi8vZm9yZWNhc3Qud2VhdGhlci5nb3YvemlwY2l0eS5waHA/aW5wdXRzdHJpbmc9SW93YStDaXR5LElBPgoKVGhpcyBmdW5jdGlvbiB1c2VzIHRoZSBOYXRpb25hbCBXZWF0aGVyIFNlcnZpY2Ugc2l0ZSB0byBmaW5kIHRoZQpjdXJyZW50IHRlbXBlcmF0dXJlIGJ5IHBhcnNpbmcgdGhlIEhUTUwgZGF0YSBpbiB0aGUgcmVzcG9uc2U6CgpgYGB7cn0KZmluZFRlbXBHb3YgPC0gZnVuY3Rpb24oY2l0eXN0YXRlKSB7CiAgICB1cmwgPC0gcGFzdGUoImh0dHBzOi8vZm9yZWNhc3Qud2VhdGhlci5nb3YvemlwY2l0eS5waHA/aW5wdXRzdHJpbmciLAogICAgICAgICAgICAgICAgIHVybF9lc2NhcGUoY2l0eXN0YXRlKSwKICAgICAgICAgICAgICAgICBzZXAgPSAiPSIpCiAgICBwYWdlIDwtIHJlYWRfaHRtbCh1cmwpCiAgICB4cGF0aCA8LSAiLy9wW0BjbGFzcz1cIm15Zm9yZWNhc3QtY3VycmVudC1scmdcIl0iCiAgICB0ZW1wTm9kZSA8LSB4bWxfZmluZF9maXJzdChwYWdlLCB4cGF0aCkKICAgIG5vZGVUZXh0IDwtIHhtbF90ZXh0KHRlbXBOb2RlKQogICAgYXMubnVtZXJpYyhzdWIoIihbLStdP1tbOmRpZ2l0Ol1dKykuKiIsICJcXDEiLCBub2RlVGV4dCkpCn0KYGBgCgpGb3IgSW93YSBDaXR5IHlvdSBjYW4gdXNlCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpmaW5kVGVtcEdvdigiSW93YSBDaXR5LCBJQSIpCmBgYAogClRoaXMgd2lsbCBuZWVkIHRvIGJlIHJldmlzZWQgd2hlbmV2ZXIgdGhlIGZvcm1hdCBvZiB0aGUgcGFnZSBjaGFuZ2VzLAphcyBoYXBwZW5lZCBzb21ldGltZSBpbiAyMDEyLgoKTXVycmVsbOKAmXMgRGF0YSBUZWNobm9sb2dpZXMgYm9vayBkaXNjdXNzZXMgWE1MLCBYUEFUSCBxdWVyaWVzLApyZWd1bGFyIGV4cHJlc3Npb25zLCBhbmQgaG93IHRvIHdvcmsgd2l0aCB0aGVzZSBpbiBSLgoKU29tZSBvdGhlciByZXNvdXJjZXMgZm9yIHJlZ3VsYXIgZXhwcmVzc2lvbnM6CgoqIFtSZWd1bGFyLUV4cHJlc3Npb25zLmluZm9dKGh0dHBzOi8vd3d3LnJlZ3VsYXItZXhwcmVzc2lvbnMuaW5mby8pCgoqIFtfUiBmb3IgRGF0YSBTY2llbmNlX10oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei8pCgpSZWNlbnRseSB0aGUgc2VydmVyIGhhcyBlaXRoZXIgYmVlbiBvdmVybG9hZGVkIG9yIG1vZGlmaWVkIHRvIHJlamVjdApyZXF1ZXN0cyBmcm9tIHRoZSBzYW1lIGFkZHJlc3MgdGhhdCBhcmUgdG9vIGNsb3NlIHRvZ2V0aGVyIHNvIGl0IGlzIG5vdApjdXJyZW50bHkgdXNlZnVsIGZvciBjb2xsZWN0aW5nIHRlbXBlcmF0dXJlcyBhdCBtdWx0aWxlIGxvY2F0aW9ucy4KClRoZSBOYXRpb25hbCBXZWF0aGVyIFNlcnZpY2UgYWxzbyBwcm92aWRlcyBzZXZlcmFsIEFQSXMsIGluY2x1ZGluZyBhClJFU1QgQVBJIHRoYXQgcmV0dXJucyBKU09OIChYTUwgbWF5IGJlIGF2YWlsYWJsZSBidXQgZGlkbid0IHNlZW0gdG8Kd29yayByaWdodCBmb3IgbWUpIHRoYXQgdXNlIGEgdXJsIGxpa2UgdGhpczoKICAKICA8aHR0cHM6Ly9mb3JlY2FzdC53ZWF0aGVyLmdvdi9NYXBDbGljay5waHA/bGF0PTQxLjcmbG9uPS05MS41JkZjc3RUeXBlPWpzb24+CgpZb3UgY2FuIHJlYWQgdGhlIHRlbXBlcmF0dXJlIGZyb20gdGhpcyBBUEkgdXNpbmcgdGhlIGBmcm9tSlNPTmAKZnVuY3Rpb24gZnJvbSB0aGUgYGpzb25saXRlYCBwYWNrYWdlOgoKYGBge3IsIGV2YWw9IEZBTFNFfQpsaWJyYXJ5KGpzb25saXRlKQppY3VybCA8LQogICAgImh0dHA6Ly9mb3JlY2FzdC53ZWF0aGVyLmdvdi9NYXBDbGljay5waHA/bGF0PTQxLjcmbG9uPS05MS41JkZjc3RUeXBlPWpzb24iCmljanNvbiA8LSBmcm9tSlNPTihpY3VybCkKaWNqc29uJGN1cnJlbnRvYnNlcnZhdGlvbiRUZW1wCmBgYAoKVW5jb3J0dW5hdGVseSB0aGlzIGFsc28gc2VlbXMgdW5yZWxpYWJsZSBhdCB0aGUgbW9tZW50LgoKCiMjIFRlbXBlcmF0dXJlcyBhbmQgTG9jYXRpb25zIGZvciBTb21lIElvd2EgQ2l0aWVzCgpBIHNtYWxsIHNlbGVjdGlvbiBvZiBJb3dhIGNpdGllczoKCmBgYHtyfQpwbGFjZXMgPC0gYygiQW1lcyIsICJCdXJsaW5ndG9uIiwgIkNlZGFyIFJhcGlkcyIsICJDbGludG9uIiwKICAgICAgICAgICAgIkNvdW5jaWwgQmx1ZmZzIiwgIkRlcyBNb2luZXMiLCAiRHVidXF1ZSIsICJGb3J0IERvZGdlIiwKICAgICAgICAgICAgIklvd2EgQ2l0eSIsICJLZW9rdWsiLCAiTWFyc2hhbGx0b3duIiwgIk1hc29uIENpdHkiLAogICAgICAgICAgICAiTmV3dG9uIiwgIk90dHVtd2EiLCAiU2lvdXggQ2l0eSIsICJXYXRlcmxvbyIpCmBgYAoKQ3VycmVudGx5IHRoZSBPV00gQVBJIHNlZW1zIG1vc3QgcmVsaWFibGUuCgpUbyBsb29rIHVwIHRlbXBlcmF0dXJlcyB3aXRoIHRoYXQgQVBJLCBhbmQgdG8gcGxvdCB0aGVtIG9uIGEgbWFwLCB3ZQpuZWVkIHRvIGZpbmQgdGhlIGxvY2F0aW9ucyBvZiB0aGVzZSBjaXRpZXMuCgpXZSBjYW4gb2J0YWluIGEgZmlsZSBvZiBnZW9jb2RlZCBjaXRpZXMgYW5kIHJlYWQgaXQgaW50byBSOgoKPCEtLSBPcmlnaW5hbGx5IGZyb20KaHR0cDovL3d3dy5zdWplZS5uZXQvdGVjaC9hcnRpY2xlcy9nZW9jb2RlZC9jaXRpZXMuY3N2LnppcAotLT4KCmBgYHtyfQppZiAoISBmaWxlLmV4aXN0cygiY2l0aWVzLmNzdiIpKSB7CiAgICBkb3dubG9hZC5maWxlKCJodHRwOi8vd3d3LnN0YXQudWlvd2EuZWR1L35sdWtlL2RhdGEvY2l0aWVzLmNzdi56aXAiLAogICAgICAgICAgICAgICAgICAiY2l0aWVzLmNzdi56aXAiKQogICAgdW56aXAoImNpdGllcy5jc3YuemlwIikKfQoKY2l0aWVzIDwtIHJlYWQuY3N2KCJjaXRpZXMuY3N2IiwgaGVhZGVyID0gRkFMU0UpCm5hbWVzKGNpdGllcykgPC0gYygiQ2l0eSIsICJTdGF0ZSIsICJMYXQiLCAiTG9uIikKaGVhZChjaXRpZXMpCmBgYAoKU2VsZWN0IHRoZSBjaXRpZXMgd2Ugd2FudCBhbmQgZmluZCB0aGVpciB0ZW1wZXJhdHVyZXM6CgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQpsaWJyYXJ5KGRwbHlyKQpgYGAKYGBge3J9CnRlbXBsb2MgPC0gZmlsdGVyKGNpdGllcywgQ2l0eSAlaW4lIHRvdXBwZXIocGxhY2VzKSwgU3RhdGUgPT0gIklBIikgJT4lCiAgICBtdXRhdGUodGVtcCA9IG1hcHBseShmaW5kVGVtcE9XTSwgTGF0LCBMb24pKQpoZWFkKHRlbXBsb2MpCmBgYAoKUmVtb3ZlIGFueSByb3dzIHdpdGggbWlzc2luZyB0ZW1lcmF0dXJlczoKCmBgYHtyfQp0ZW1wbG9jIDwtIGZpbHRlcih0ZW1wbG9jLCAhIGlzLm5hKHRlbXApKQpgYGAKCgojIyBDcmVhdGluZyB0aGUgTWFwCgpXZSBjYW4gdXNlIHRoZSBgYm9yZGVyc2AgZnVuY3Rpb24gZnJvbSBgZ2dwbG90MmAgYWxvbmcgd2l0aCAgYGdlb21fdGV4dGAKdG8gc2hvdyB0aGUgcmVzdWx0czoKCmBgYHtyfQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2d0aGVtZXMpCnRwb2ludHMgPC0gZ2VvbV90ZXh0KGFlcyh4ID0gTG9uLCB5ID0gTGF0LCBsYWJlbCA9IHJvdW5kKHRlbXApKSwKICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSAicmVkIiwKICAgICAgICAgICAgICAgICAgICAgZm9udGZhY2UgPSAiYm9sZCIsCiAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0ZW1wbG9jKQoKcCA8LSBnZ3Bsb3QoKSArCiAgICBib3JkZXJzKCJjb3VudHkiLCAiaW93YSIpICsKICAgIHRwb2ludHMgKwogICAgY29vcmRfbWFwKCkgKwogICAgdGhlbWVfbWFwKCkKCnAKYGBgCgpUbyBhZGQgY29udG91cnMgd2UgY2FuIHVzZSBgaW50ZXJwYCBmcm9tIHRoZSBgaW50ZXJwYCBwYWNrYWdlIGFuZCB0aGUKYGdlb21fY29udG91cmAgZnVuY3Rpb246CgpgYGB7cn0KbGlicmFyeShpbnRlcnApCnN1cmZhY2UgPC0gd2l0aCh0ZW1wbG9jLCBpbnRlcnAoTG9uLCBMYXQsIHRlbXAsIGxpbmVhciA9IEZBTFNFKSkKCnNyZmMgPC0gZXhwYW5kLmdyaWQoTG9uID0gc3VyZmFjZSR4LCBMYXQgPSBzdXJmYWNlJHkpCnNyZmMkdGVtcCA8LSBhcy52ZWN0b3Ioc3VyZmFjZSR6KQp0Y29udHMgPC0gZ2VvbV9jb250b3VyKGFlcyh4ID0gTG9uLCB5ID0gTGF0LCB6ID0gdGVtcCksCiAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHNyZmMsIG5hLnJtID0gVFJVRSwgYmlucyA9IDYpCgpwICsgdGNvbnRzICsgdHBvaW50cwpgYGAKClRoZSBgZ2dtYXBgIHBhY2thZ2UgcHJvdmlkZXMgZnVydGhlciBtYXAgc3VwcG9ydCwgaW5jbHVkaW5nIGJhY2tncm91bmQKbWFwcyBmcm9tIFtHb29nbGVdKGh0dHA6Ly9tYXBzLmdvb2dsZS5jb20pLCBbU3RhbWVuXShodHRwOi8vbWFwcy5zdGFtZW4uY29tKSwKYW5kIFtPcGVuU3RyZWF0TWFwXShodHRwczovL3d3dy5vcGVuc3RyZWV0bWFwLm9yZy8pLgoKYGBge3J9CmxpYnJhcnkoZ2dtYXApCmBgYAoKVXNpbmcgR29vZ2xlIG1hcHMgbm93IHJlcXVpcmVzIGFuIEFQSSBrZXkuCgpgYGB7ciwgaW5jbHVkZSA9IEZBTFNFLCBldmFsID0gRkFMU0V9Cm1hcCA8LSBnZXRfZ29vZ2xlbWFwKGMoLTkzLjMsIDQxLjcpLCB6b29tID0gNywgbWFwdHlwZSA9ICJ0ZXJyYWluIikKYGBgCgpEb3dubG9hZGluZyBhIFN0YW1lbiBtYXA6CgpgYGB7ciwgY2FjaGUgPSBUUlVFLCBtZXNzYWdlID0gRkFMU0V9Cm1hcHR5cGUgPC0gInRlcnJhaW4iCm1hcHR5cGUgPC0gInRvbmVyIgptYXAgPC0gZ2V0X3N0YW1lbm1hcChjKC05Ny4yLCA0MC40LCAtODkuOSwgNDMuNiksIHpvb20gPSA4LCBtYXB0eXBlID0gbWFwdHlwZSkKYGBgCgpgZ2dtYXBgIGNyZWF0ZXMgYSBgZ2dwbG90YCBvYmplY3QsIHNldHMgdGhlIGNvb3JkaW5hdGUgc3lzdGVtLCBhbmQKbWFrZXMgc29tZSB0aGVtZSBhZGp1c3RtZW50czoKCmBgYHtyfQpnZ21hcChtYXApCmBgYAoKQWRkIHRoZSBjb3VudHkgYm9yZGVycywgY29udG91cnMsIGFuZCB0ZW1wZXJhdHVyZXMuCgpgYGB7cn0KZ2dtYXAobWFwKSArIGJvcmRlcnMoImNvdW50eSIsICJpb3dhIikgKyB0Y29udHMgKyB0cG9pbnRzCmBgYAo=