class: center, middle, title-slide .title[ # Creating a Current Temperature Map ] .author[ ### Luke Tierney ] .institute[ ### University of Iowa ] .date[ ### 2026-05-08 ] --- <link rel="stylesheet" href="stat4580.css" type="text/css" /> <style type="text/css"> .remark-code { font-size: 85%; } </style> ## Finding the Current Temperature There are several options for obtaining current weather information, including - _Open Weather Map_ - The _National Weather Service_ --- layout: true ### Open Weather Map --- [_Open Weather Map_](https://openweathermap.org/) provides an [API](https://openweathermap.org/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: ``` r 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 ``` r findTempOWM(41.7, -91.5) ## [1] 50.4 ``` -- 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. --- layout: true ### 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: ``` r 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 ``` r findTempGov("Iowa City, IA") ## [1] 47 ``` -- This will need to be revised whenever the format of the page changes, as happened some years ago <!-- 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: * [Regular-Expressions.info](https://www.regular-expressions.info/) * [_R for Data Science_](https://r4ds.hadley.nz/) -- 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: ``` r findTempGovJSON <- function(lat, lon) { fmt <- "http://forecast.weather.gov/MapClick.php?lat=%f&lon=%f&FcstType=json" icjson <- jsonlite::fromJSON(sprintf(fmt, lat, lon)) as.numeric(icjson$currentobservation$Temp) } findTempGovJSON(41.7, -91.5) ## [1] 47 ``` --- layout: true ## Temperatures and Locations for Some Iowa Cities --- A small selection of Iowa cities: ``` r 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: <!-- Originally from http://www.sujee.net/tech/articles/geocoded/cities.csv.zip --> ``` 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: ``` r library(dplyr) ``` ``` r 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 39.22 ## 2 IOWA CITY IA 41.65789 -91.53309 49.05 ## 3 MARSHALLTOWN IA 42.03610 -92.99887 49.14 ## 4 CEDAR RAPIDS IA 41.97666 -91.67315 49.59 ## 5 BURLINGTON IA 40.89841 -91.16439 50.88 ## 6 MASON CITY IA 43.15272 -93.19944 48.88 ``` -- Remove any rows with missing temperatures: ``` r temploc <- filter(temploc, ! is.na(temp)) ``` --- layout: true ## Creating the Map --- We can use the `annotate_borders()` function from `ggplot2` along with `geom_text()` to show the results: .pull-left.small-code[ ``` r 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 ``` ] .pull-right[ <!-- --> ] --- To add contours we can use `interp()` from the `interp` package and the `geom_contour()` function: .pull-left.small-code[ ``` r 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 ``` ] .pull-right[ <!-- --> ] --- The `ggmap` package provides further map support, including background maps from [Google](http://maps.google.com), [Stamen](https://maps.stamen.com/), [OpenStreatMap](https://www.openstreetmap.org/), and [Stadia](https://stadiamaps.com/). ``` r library(ggmap) ``` -- 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. ``` r 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: .pull-left[ ``` r ggmap(map) + theme_map() ``` ] .pull-right[ <!-- --> ] --- Add the county borders, contours, and temperatures. .pull-left[ ``` r ggmap(map) + annotation_borders("county", "iowa") + tconts + tpoints + theme_map() ``` ] .pull-right[ <!-- --> ]
//adapted from Emi Tanaka's gist at //https://gist.github.com/emitanaka/eaa258bb8471c041797ff377704c8505