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

http://api.openweathermap.org/data/2.5/weather?q=Iowa+City,IA&mode=xml&appid=key

or

http://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 <- "http://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:

http://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("http://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))
}
  • 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:

  • 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:

http://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

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")

We can find the current temepratures with

temp <- sapply(paste(places, "IA", sep = ", "), findTempGov, USE.NAMES = FALSE)
## Warning in FUN(X[[i]], ...): NAs introduced by coercion
temp
##  [1] 53 NA 52 53 50 54 51 49 55 61 52 48 54 55 47 53

To show these on a map we need their locations. 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", stringsAsFactors=FALSE, 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

Form the temperature data into a data frame and use left_join to merge in the locations from the cities data frame:

library(dplyr)
tframe <- data.frame(City = toupper(places), State = "IA", Temp = temp,
                     stringsAsFactors = FALSE)
head(tframe)
##             City State Temp
## 1           AMES    IA   53
## 2     BURLINGTON    IA   NA
## 3   CEDAR RAPIDS    IA   52
## 4        CLINTON    IA   53
## 5 COUNCIL BLUFFS    IA   50
## 6     DES MOINES    IA   54
temploc <- left_join(tframe, cities, c("City", "State"))
head(temploc)
##             City State Temp      Lat       Lon
## 1           AMES    IA   53 42.02286 -93.62679
## 2     BURLINGTON    IA   NA 40.89841 -91.16439
## 3   CEDAR RAPIDS    IA   52 41.97666 -91.67315
## 4        CLINTON    IA   53 41.84451 -90.23973
## 5 COUNCIL BLUFFS    IA   50 41.26194 -95.88186
## 6     DES MOINES    IA   54 41.58882 -93.62031

Remove any rows with missing temeratures:

temploc <- filter(temploc, ! is.na(temp))

Creating the Map

We can use the map function from the maps package along with the text function to show the results:

library(maps)
map("county", "iowa", col = "lightgrey")
map("state", "iowa", add = TRUE)
with(temploc, text(Lon, Lat, Temp, col = "blue"))

To add contours we can use interp from the akima package and the contour function:

library(akima)
map("county", "iowa", col = "lightgrey")
map("state", "iowa", add = TRUE)
surface <- with(temploc, interp(Lon, Lat, Temp, linear = FALSE))
contour(surface, add = TRUE)
with(temploc, text(Lon, Lat, Temp, col = "blue"))

A Version Using ggplot and ggmap

We will need the ggplot2, ggthemes, and ggmap packages.

library(ggplot2)
library(ggthemes)
library(ggmap)
## Google's Terms of Service: https://cloud.google.com/maps-platform/terms/.
## Please cite ggmap if you use it! See citation("ggmap") for details.

The map_data function converts the data provided by the maps package to a form that works well with ggplot.

iowa_counties <- map_data("county", "iowa")
head(iowa_counties)
##        long      lat group order region subregion
## 1 -94.24583 41.50506     1     1   iowa     adair
## 2 -94.24583 41.16129     1     2   iowa     adair
## 3 -94.24583 41.16129     1     3   iowa     adair
## 4 -94.48647 41.16129     1     4   iowa     adair
## 5 -94.70992 41.16129     1     5   iowa     adair
## 6 -94.70992 41.50506     1     6   iowa     adair

The basic county map can then be drawn using geom_polygon:

clines <- geom_polygon(aes(long, lat, group = group),
                       fill = NA, col = "lightgray", data = iowa_counties)
p <- ggplot() + clines + coord_quickmap() + theme_map()
p

Adding the temperatures for the cities:

tpoints <- geom_text(aes(x = Lon, y = Lat, label = Temp),
                     color = "red", data = temploc)
p + tpoints

And adding contour lines:

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)
p + tconts + tpoints

The ggmap package provides further map support, including background maps from Google, Stamen, and OpenStreatMap.

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)
## Source : http://tile.stamen.com/toner/8/58/93.png
## Source : http://tile.stamen.com/toner/8/59/93.png
## Source : http://tile.stamen.com/toner/8/60/93.png
## Source : http://tile.stamen.com/toner/8/61/93.png
## Source : http://tile.stamen.com/toner/8/62/93.png
## Source : http://tile.stamen.com/toner/8/63/93.png
## Source : http://tile.stamen.com/toner/8/64/93.png
## Source : http://tile.stamen.com/toner/8/58/94.png
## Source : http://tile.stamen.com/toner/8/59/94.png
## Source : http://tile.stamen.com/toner/8/60/94.png
## Source : http://tile.stamen.com/toner/8/61/94.png
## Source : http://tile.stamen.com/toner/8/62/94.png
## Source : http://tile.stamen.com/toner/8/63/94.png
## Source : http://tile.stamen.com/toner/8/64/94.png
## Source : http://tile.stamen.com/toner/8/58/95.png
## Source : http://tile.stamen.com/toner/8/59/95.png
## Source : http://tile.stamen.com/toner/8/60/95.png
## Source : http://tile.stamen.com/toner/8/61/95.png
## Source : http://tile.stamen.com/toner/8/62/95.png
## Source : http://tile.stamen.com/toner/8/63/95.png
## Source : http://tile.stamen.com/toner/8/64/95.png
## Source : http://tile.stamen.com/toner/8/58/96.png
## Source : http://tile.stamen.com/toner/8/59/96.png
## Source : http://tile.stamen.com/toner/8/60/96.png
## Source : http://tile.stamen.com/toner/8/61/96.png
## Source : http://tile.stamen.com/toner/8/62/96.png
## Source : http://tile.stamen.com/toner/8/63/96.png
## Source : http://tile.stamen.com/toner/8/64/96.png

ggmap creates a ggplot object and sets the coordinate system.

ggmap(map)

Adding a map theme removes the axes:

p1 <- ggmap(map) + theme_map()

Finally, add the county borders, contours, and temperatures.

p1 + clines + tconts + tpoints