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=