Finding the Current Temperature
There are several options for obtaining current weather information,
including
- Open Weather Map
- The National Weather Service
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 in format XML
format 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 some years ago .
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:
Sometimes the server may either be overloaded or be rejecting
requests from the same address that are too close together, so it may
not be 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)
findTempGovJSON <- function(lat, lon) {
fmt <-
"http://forecast.weather.gov/MapClick.php?lat=%f&lon=%f&FcstType=json"
icjson <- fromJSON(sprintf(fmt, lat, lon))
as.numeric(icjson$currentobservation$Temp)
}
findTempGovJSON(41.7, -91.5)
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 33.82
## 2 IOWA CITY IA 41.65789 -91.53309 41.56
## 3 MARSHALLTOWN IA 42.03610 -92.99887 38.66
## 4 CEDAR RAPIDS IA 41.97666 -91.67315 39.47
## 5 BURLINGTON IA 40.89841 -91.16439 41.52
## 6 MASON CITY IA 43.15272 -93.19944 34.41
Remove any rows with missing temperatures:
temploc <- filter(temploc, ! is.na(temp))
Creating the Map
We can use the annotate_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() +
annotation_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, OpenStreatMap, and Stadia.
library(ggmap)
## ℹ Google's Terms of Service: <https://mapsplatform.google.com>
## Stadia Maps' Terms of Service: <https://stadiamaps.com/terms-of-service>
## OpenStreetMap's Tile Usage Policy: <https://operations.osmfoundation.org/policies/tiles>
## ℹ Please cite ggmap if you use it! Use `citation("ggmap")` for details.
Stamen maps are hosted at Stadia Maps. An API key is required, but no
credit card is necessary to sign up and there is a free tier for
non-commercial use.
register_stadiamaps(StadiaKey, write = FALSE)
maptype <- "stamen_terrain"
map <- get_stadiamap(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) + theme_map()

Add the county borders, contours, and temperatures.
ggmap(map) +
annotation_borders("county", "iowa") +
tconts +
tpoints +
theme_map()

LS0tCnRpdGxlOiAiQ3JlYXRpbmcgYSBDdXJyZW50IFRlbXBlcmF0dXJlIE1hcCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgY29kZV9mb2xkaW5nOiAiaGlkZSIKZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJWQgJUIsICVZICVIOiVNJylgIgotLS0KCmBgYHtyIGdsb2JhbF9vcHRpb25zLCBpbmNsdWRlID0gRkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChjb2xsYXBzZSA9IFRSVUUsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLAogICAgICAgICAgICAgICAgICAgICAgZmlnLmFsaWduID0gImNlbnRlciIpCmBgYAoKIyMgRmluZGluZyB0aGUgQ3VycmVudCBUZW1wZXJhdHVyZQoKVGhlcmUgYXJlIHNldmVyYWwgb3B0aW9ucyBmb3Igb2J0YWluaW5nIGN1cnJlbnQgd2VhdGhlciBpbmZvcm1hdGlvbiwKaW5jbHVkaW5nCgotIF9PcGVuIFdlYXRoZXIgTWFwXwotIFRoZSBfTmF0aW9uYWwgV2VhdGhlciBTZXJ2aWNlXwoKIyMjIE9wZW4gV2VhdGhlciBNYXAKCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9CiMjIGtleXMgYXJlIHN0b3JlZCBpbiB0aGUgcGFyZW50IGRpcmVjdG9yeQprZXlzIDwtIGFzLmRhdGEuZnJhbWUocmVhZC5kY2YoIi4uL0FQSUtFWVMiKSkKT1dNa2V5IDwtIGtleXMka2V5W21hdGNoKCJPV00iLCBrZXlzJG5hbWUpXQpTdGFkaWFLZXkgPC0ga2V5cyRrZXlbbWF0Y2goIlN0YWRpYSIsIGtleXMkbmFtZSldCmBgYAoKW19PcGVuIFdlYXRoZXIgTWFwX10oaHR0cHM6Ly9vcGVud2VhdGhlcm1hcC5vcmcvKSBwcm92aWRlcyBhbgpbQVBJXShodHRwczovL29wZW53ZWF0aGVybWFwLm9yZy9hcGkpIGZvciByZXR1cm5pbmcgd2VhdGhlciBpbmZvcm1hdGlvbgppbiBKU09OIG9yIFhNTCBmb3JtYXQuCgpBIHF1ZXJ5IHJlcXVlc3RpbmcgdGhlIGN1cnJlbnQgdGVtcGVyYXR1cmUgaW4gSW93YSBDaXR5IGluIGZvcm1hdCBYTUwKZm9ybWF0IHdvdWxkIHVzZSBhIFVSTCBvZiB0aGUgZm9ybQoKPGh0dHBzOi8vYXBpLm9wZW53ZWF0aGVybWFwLm9yZy9kYXRhLzIuNS93ZWF0aGVyP3E9SW93YStDaXR5LElBJm1vZGU9eG1sJmFwcGlkPT5fa2V5XwoKb3IKCjxodHRwczovL2FwaS5vcGVud2VhdGhlcm1hcC5vcmcvZGF0YS8yLjUvd2VhdGhlcj9sYXQ9NDEuNjYmbG9uPS05MS41MyZtb2RlPXhtbCZhcHBpZD0+X2tleV8KCndpdGggX2tleV8gcmVwbGFjZWQgYnkgeW91ciBBUEkga2V5IChmcmVlLCBidXQgcmVxdWlyZXMgcmVnaXN0cmF0aW9uKS4KCkhlcmUgaXMgYSBzaW1wbGUgZnVuY3Rpb24gdG8gb2J0YWluIHRoZSBjdXJyZW50IHRlbXBlcmF0dXJlIGZvciBmcm9tIF9PcGVuCldlYXRoZXIgTWFwXyBiYXNlZCBvbiBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlOgoKYGBge3J9CmxpYnJhcnkoeG1sMikKZmluZFRlbXBPV00gPC0gZnVuY3Rpb24obGF0LCBsb24pIHsKICAgIGJhc2UgPC0gImh0dHBzOi8vYXBpLm9wZW53ZWF0aGVybWFwLm9yZy9kYXRhLzIuNS93ZWF0aGVyIgogICAgdXJsIDwtIHNwcmludGYoIiVzP2xhdD0lZiZsb249JWYmbW9kZT14bWwmdW5pdHM9SW1wZXJpYWwmYXBwaWQ9JXMiLAogICAgICAgICAgICAgICAgICAgYmFzZSwgbGF0LCBsb24sIE9XTWtleSkKICAgIHBhZ2UgPC0gcmVhZF94bWwodXJsKQogICAgYXMubnVtZXJpYyh4bWxfdGV4dCh4bWxfZmluZF9maXJzdChwYWdlLCAiLy90ZW1wZXJhdHVyZS9AdmFsdWUiKSkpCn0KYGBgCgpGb3IgSW93YSBDaXR5IHlvdSB3b3VsZCB1c2UKCmBgYHtyLCBldmFsID0gRkFMU0V9CmZpbmRUZW1wT1dNKDQxLjcsIC05MS41KQpgYGAKClRoaXMgZnVuY3Rpb24gc2hvdWxkIGJlIHJvYnVzdCBzaW5jZSB0aGUgZm9ybWF0IG9mIHRoZSByZXNwb25zZSBpcwpkb2N1bWVudGVkIGFuZCBzaG91bGQgbm90IGNoYW5nZS4KClVzaW5nIGNvbW1lcmNpYWwgd2ViIHNlcnZpY2VzIHNob3VsZCBiZSBkb25lIHdpdGggY2FyZSBhcyB0aGVyZSBhcmUKdHlwaWNhbGx5IGxpbWl0YXRpb25zIGFuZCBsaWNlbnNlIHRlcm1zIHRvIGJlIGNvbnNpZGVyZWQuCgpUaGV5IG1heSBhbHNvIGNvbWUgYW5kIGdvOiBHb29nbGXigJlzIEFQSSB3YXMgc2h1dCBkb3duIGluIDIwMTIuCgoKIyMjIE5hdGlvbmFsIFdlYXRoZXIgU2VydmljZQoKVGhlIF9OYXRpb25hbCBXZWF0aGVyIFNlcnZpY2VfIHByb3ZpZGVzIGEgc2l0ZSB0aGF0IHByb2R1Y2VzIGZvcmVjYXN0cwppbiBhIHdlYiBwYWdlIGZvciBhIFVSTCBsaWtlIHRoaXM6Cgo8aHR0cHM6Ly9mb3JlY2FzdC53ZWF0aGVyLmdvdi96aXBjaXR5LnBocD9pbnB1dHN0cmluZz1Jb3dhK0NpdHksSUE+CgpUaGlzIGZ1bmN0aW9uIHVzZXMgdGhlIF9OYXRpb25hbCBXZWF0aGVyIFNlcnZpY2VfIHNpdGUgdG8gZmluZCB0aGUKY3VycmVudCB0ZW1wZXJhdHVyZSBieSBwYXJzaW5nIHRoZSBIVE1MIGRhdGEgaW4gdGhlIHJlc3BvbnNlOgoKYGBge3J9CmZpbmRUZW1wR292IDwtIGZ1bmN0aW9uKGNpdHlzdGF0ZSkgewogICAgdXJsIDwtIHBhc3RlKCJodHRwczovL2ZvcmVjYXN0LndlYXRoZXIuZ292L3ppcGNpdHkucGhwP2lucHV0c3RyaW5nIiwKICAgICAgICAgICAgICAgICB1cmxfZXNjYXBlKGNpdHlzdGF0ZSksCiAgICAgICAgICAgICAgICAgc2VwID0gIj0iKQogICAgcGFnZSA8LSByZWFkX2h0bWwodXJsKQogICAgeHBhdGggPC0gIi8vcFtAY2xhc3M9XCJteWZvcmVjYXN0LWN1cnJlbnQtbHJnXCJdIgogICAgdGVtcE5vZGUgPC0geG1sX2ZpbmRfZmlyc3QocGFnZSwgeHBhdGgpCiAgICBub2RlVGV4dCA8LSB4bWxfdGV4dCh0ZW1wTm9kZSkKICAgIGFzLm51bWVyaWMoc3ViKCIoWy0rXT9bWzpkaWdpdDpdXSspLioiLCAiXFwxIiwgbm9kZVRleHQpKQp9CmBgYAoKRm9yIElvd2EgQ2l0eSB5b3UgY2FuIHVzZQoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZmluZFRlbXBHb3YoIklvd2EgQ2l0eSwgSUEiKQpgYGAKIApUaGlzIHdpbGwgbmVlZCB0byBiZSByZXZpc2VkIHdoZW5ldmVyIHRoZSBmb3JtYXQgb2YgdGhlIHBhZ2UgY2hhbmdlcywKYXMgaGFwcGVuZWQgc29tZSB5ZWFycyBhZ28gPCEtLSBzb21ldGltZSBpbiAyMDEyIC0tPi4KCk11cnJlbGzigJlzIERhdGEgVGVjaG5vbG9naWVzIGJvb2sgZGlzY3Vzc2VzIFhNTCwgWFBBVEggcXVlcmllcywKcmVndWxhciBleHByZXNzaW9ucywgYW5kIGhvdyB0byB3b3JrIHdpdGggdGhlc2UgaW4gUi4KClNvbWUgb3RoZXIgcmVzb3VyY2VzIGZvciByZWd1bGFyIGV4cHJlc3Npb25zOgoKKiBbUmVndWxhci1FeHByZXNzaW9ucy5pbmZvXShodHRwczovL3d3dy5yZWd1bGFyLWV4cHJlc3Npb25zLmluZm8vKQoKKiBbX1IgZm9yIERhdGEgU2NpZW5jZV9dKGh0dHBzOi8vcjRkcy5oYWRsZXkubnovKQoKU29tZXRpbWVzIHRoZSBzZXJ2ZXIgbWF5IGVpdGhlciBiZSBvdmVybG9hZGVkIG9yIGJlIHJlamVjdGluZyByZXF1ZXN0cwpmcm9tIHRoZSBzYW1lIGFkZHJlc3MgdGhhdCBhcmUgdG9vIGNsb3NlIHRvZ2V0aGVyLCBzbyBpdCBtYXkgbm90IGJlCnVzZWZ1bCBmb3IgY29sbGVjdGluZyB0ZW1wZXJhdHVyZXMgYXQgbXVsdGlsZSBsb2NhdGlvbnMuCgpUaGUgX05hdGlvbmFsIFdlYXRoZXIgU2VydmljZV8gYWxzbyBwcm92aWRlcyBzZXZlcmFsIEFQSXMsIGluY2x1ZGluZyBhClJFU1QgQVBJIHRoYXQgcmV0dXJucyBKU09OIChYTUwgbWF5IGJlIGF2YWlsYWJsZSBidXQgZGlkbid0IHNlZW0gdG8Kd29yayByaWdodCBmb3IgbWUpIHRoYXQgdXNlIGEgdXJsIGxpa2UgdGhpczoKICAKICA8aHR0cHM6Ly9mb3JlY2FzdC53ZWF0aGVyLmdvdi9NYXBDbGljay5waHA/bGF0PTQxLjcmbG9uPS05MS41JkZjc3RUeXBlPWpzb24+CgpZb3UgY2FuIHJlYWQgdGhlIHRlbXBlcmF0dXJlIGZyb20gdGhpcyBBUEkgdXNpbmcgdGhlIGBmcm9tSlNPTmAKZnVuY3Rpb24gZnJvbSB0aGUgYGpzb25saXRlYCBwYWNrYWdlOgoKYGBge3IsIGV2YWw9IEZBTFNFfQpsaWJyYXJ5KGpzb25saXRlKQpmaW5kVGVtcEdvdkpTT04gPC0gZnVuY3Rpb24obGF0LCBsb24pIHsKICAgIGZtdCA8LQogICAgICAgICJodHRwOi8vZm9yZWNhc3Qud2VhdGhlci5nb3YvTWFwQ2xpY2sucGhwP2xhdD0lZiZsb249JWYmRmNzdFR5cGU9anNvbiIKICAgIGljanNvbiA8LSBmcm9tSlNPTihzcHJpbnRmKGZtdCwgbGF0LCBsb24pKQogICAgYXMubnVtZXJpYyhpY2pzb24kY3VycmVudG9ic2VydmF0aW9uJFRlbXApCn0KZmluZFRlbXBHb3ZKU09OKDQxLjcsIC05MS41KQpgYGAKCiMjIFRlbXBlcmF0dXJlcyBhbmQgTG9jYXRpb25zIGZvciBTb21lIElvd2EgQ2l0aWVzCgpBIHNtYWxsIHNlbGVjdGlvbiBvZiBJb3dhIGNpdGllczoKCmBgYHtyfQpwbGFjZXMgPC0gYygiQW1lcyIsICJCdXJsaW5ndG9uIiwgIkNlZGFyIFJhcGlkcyIsICJDbGludG9uIiwKICAgICAgICAgICAgIkNvdW5jaWwgQmx1ZmZzIiwgIkRlcyBNb2luZXMiLCAiRHVidXF1ZSIsICJGb3J0IERvZGdlIiwKICAgICAgICAgICAgIklvd2EgQ2l0eSIsICJLZW9rdWsiLCAiTWFyc2hhbGx0b3duIiwgIk1hc29uIENpdHkiLAogICAgICAgICAgICAiTmV3dG9uIiwgIk90dHVtd2EiLCAiU2lvdXggQ2l0eSIsICJXYXRlcmxvbyIpCmBgYAoKQ3VycmVudGx5IHRoZSBPV00gQVBJIHNlZW1zIG1vc3QgcmVsaWFibGUuCgpUbyBsb29rIHVwIHRlbXBlcmF0dXJlcyB3aXRoIHRoYXQgQVBJLCBhbmQgdG8gcGxvdCB0aGVtIG9uIGEgbWFwLCB3ZQpuZWVkIHRvIGZpbmQgdGhlIGxvY2F0aW9ucyBvZiB0aGVzZSBjaXRpZXMuCgpXZSBjYW4gb2J0YWluIGEgZmlsZSBvZiBnZW9jb2RlZCBjaXRpZXMgYW5kIHJlYWQgaXQgaW50byBSOgoKPCEtLSBPcmlnaW5hbGx5IGZyb20KaHR0cDovL3d3dy5zdWplZS5uZXQvdGVjaC9hcnRpY2xlcy9nZW9jb2RlZC9jaXRpZXMuY3N2LnppcAotLT4KCmBgYHtyfQppZiAoISBmaWxlLmV4aXN0cygiY2l0aWVzLmNzdiIpKSB7CiAgICBkb3dubG9hZC5maWxlKCJodHRwOi8vd3d3LnN0YXQudWlvd2EuZWR1L35sdWtlL2RhdGEvY2l0aWVzLmNzdi56aXAiLAogICAgICAgICAgICAgICAgICAiY2l0aWVzLmNzdi56aXAiKQogICAgdW56aXAoImNpdGllcy5jc3YuemlwIikKfQoKY2l0aWVzIDwtIHJlYWQuY3N2KCJjaXRpZXMuY3N2IiwgaGVhZGVyID0gRkFMU0UpCm5hbWVzKGNpdGllcykgPC0gYygiQ2l0eSIsICJTdGF0ZSIsICJMYXQiLCAiTG9uIikKaGVhZChjaXRpZXMpCmBgYAoKU2VsZWN0IHRoZSBjaXRpZXMgd2Ugd2FudCBhbmQgZmluZCB0aGVpciB0ZW1wZXJhdHVyZXM6CgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQpsaWJyYXJ5KGRwbHlyKQpgYGAKYGBge3J9CnRlbXBsb2MgPC0gZmlsdGVyKGNpdGllcywgQ2l0eSAlaW4lIHRvdXBwZXIocGxhY2VzKSwgU3RhdGUgPT0gIklBIikgfD4KICAgIG11dGF0ZSh0ZW1wID0gbWFwcGx5KGZpbmRUZW1wT1dNLCBMYXQsIExvbikpCmhlYWQodGVtcGxvYykKYGBgCmBgYHtyLCBldmFsID0gRkFMU0UsIGVjaG8gPSBGQUxTRX0KdGVtcGxvYyA8LSBmaWx0ZXIoY2l0aWVzLCBDaXR5ICVpbiUgdG91cHBlcihwbGFjZXMpLCBTdGF0ZSA9PSAiSUEiKSB8PgogICAgbXV0YXRlKHRlbXAgPSBtYXBwbHkoZmluZFRlbXBHb3ZKU09OLCBMYXQsIExvbikpCmhlYWQodGVtcGxvYykKYGBgCgpSZW1vdmUgYW55IHJvd3Mgd2l0aCBtaXNzaW5nIHRlbXBlcmF0dXJlczoKCmBgYHtyfQp0ZW1wbG9jIDwtIGZpbHRlcih0ZW1wbG9jLCAhIGlzLm5hKHRlbXApKQpgYGAKCgojIyBDcmVhdGluZyB0aGUgTWFwCgpXZSBjYW4gdXNlIHRoZSBgYW5ub3RhdGVfYm9yZGVyc2AgZnVuY3Rpb24gZnJvbSBgZ2dwbG90MmAgYWxvbmcgd2l0aApgZ2VvbV90ZXh0YCB0byBzaG93IHRoZSByZXN1bHRzOgoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShnZ3RoZW1lcykKdHBvaW50cyA8LSBnZW9tX3RleHQoYWVzKHggPSBMb24sIHkgPSBMYXQsIGxhYmVsID0gcm91bmQodGVtcCkpLAogICAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJyZWQiLAogICAgICAgICAgICAgICAgICAgICBmb250ZmFjZSA9ICJib2xkIiwKICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRlbXBsb2MpCgpwIDwtIGdncGxvdCgpICsKICAgIGFubm90YXRpb25fYm9yZGVycygiY291bnR5IiwgImlvd2EiKSArCiAgICB0cG9pbnRzICsKICAgIGNvb3JkX21hcCgpICsKICAgIHRoZW1lX21hcCgpCgpwCmBgYAoKVG8gYWRkIGNvbnRvdXJzIHdlIGNhbiB1c2UgYGludGVycGAgZnJvbSB0aGUgYGludGVycGAgcGFja2FnZSBhbmQgdGhlCmBnZW9tX2NvbnRvdXJgIGZ1bmN0aW9uOgoKYGBge3J9CmxpYnJhcnkoaW50ZXJwKQpzdXJmYWNlIDwtIHdpdGgodGVtcGxvYywgaW50ZXJwKExvbiwgTGF0LCB0ZW1wLCBsaW5lYXIgPSBGQUxTRSkpCgpzcmZjIDwtIGV4cGFuZC5ncmlkKExvbiA9IHN1cmZhY2UkeCwgTGF0ID0gc3VyZmFjZSR5KQpzcmZjJHRlbXAgPC0gYXMudmVjdG9yKHN1cmZhY2UkeikKdGNvbnRzIDwtIGdlb21fY29udG91cihhZXMoeCA9IExvbiwgeSA9IExhdCwgeiA9IHRlbXApLAogICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBzcmZjLCBuYS5ybSA9IFRSVUUsIGJpbnMgPSA2KQoKcCArIHRjb250cyArIHRwb2ludHMKYGBgCgpUaGUgYGdnbWFwYCBwYWNrYWdlIHByb3ZpZGVzIGZ1cnRoZXIgbWFwIHN1cHBvcnQsIGluY2x1ZGluZyBiYWNrZ3JvdW5kCm1hcHMgZnJvbSBbR29vZ2xlXShodHRwOi8vbWFwcy5nb29nbGUuY29tKSwKW1N0YW1lbl0oaHR0cHM6Ly9tYXBzLnN0YW1lbi5jb20vKSwKW09wZW5TdHJlYXRNYXBdKGh0dHBzOi8vd3d3Lm9wZW5zdHJlZXRtYXAub3JnLyksIGFuZApbU3RhZGlhXShodHRwczovL3N0YWRpYW1hcHMuY29tLykuCgpgYGB7cn0KbGlicmFyeShnZ21hcCkKYGBgCgpTdGFtZW4gbWFwcyBhcmUgaG9zdGVkIGF0IFN0YWRpYSBNYXBzLiBBbiBBUEkga2V5IGlzIHJlcXVpcmVkLCBidXQgbm8KY3JlZGl0IGNhcmQgaXMgbmVjZXNzYXJ5IHRvIHNpZ24gdXAgYW5kIHRoZXJlIGlzIGEgZnJlZSB0aWVyIGZvcgpub24tY29tbWVyY2lhbCB1c2UuCgpgYGB7ciwgY2FjaGUgPSBUUlVFLCBtZXNzYWdlID0gRkFMU0V9CnJlZ2lzdGVyX3N0YWRpYW1hcHMoU3RhZGlhS2V5LCB3cml0ZSA9IEZBTFNFKQptYXB0eXBlIDwtICJzdGFtZW5fdGVycmFpbiIKbWFwIDwtIGdldF9zdGFkaWFtYXAoYygtOTcuMiwgNDAuNCwgLTg5LjksIDQzLjYpLCB6b29tID0gOCwgbWFwdHlwZSA9IG1hcHR5cGUpCmBgYAoKYGdnbWFwYCBjcmVhdGVzIGEgYGdncGxvdGAgb2JqZWN0LCBzZXRzIHRoZSBjb29yZGluYXRlIHN5c3RlbSwgYW5kCm1ha2VzIHNvbWUgdGhlbWUgYWRqdXN0bWVudHM6CgpgYGB7cn0KZ2dtYXAobWFwKSArIHRoZW1lX21hcCgpCmBgYAoKQWRkIHRoZSBjb3VudHkgYm9yZGVycywgY29udG91cnMsIGFuZCB0ZW1wZXJhdHVyZXMuCgpgYGB7cn0KZ2dtYXAobWFwKSArCiAgICBhbm5vdGF0aW9uX2JvcmRlcnMoImNvdW50eSIsICJpb3dhIikgKwogICAgdGNvbnRzICsKICAgIHRwb2ludHMgKwogICAgdGhlbWVfbWFwKCkKYGBgCg==