Skip to content
This repository has been archived by the owner on May 14, 2024. It is now read-only.

Commit

Permalink
Get forecasts for all states at once or any one state
Browse files Browse the repository at this point in the history
  • Loading branch information
adamhsparks committed May 11, 2017
1 parent 5853005 commit 25bc99d
Show file tree
Hide file tree
Showing 6 changed files with 322 additions and 95 deletions.
234 changes: 147 additions & 87 deletions R/get_BOM_forecast.R
Original file line number Diff line number Diff line change
@@ -1,136 +1,196 @@

#' Get BOM Forecast
#'
#'Fetch the BOM forecast and create a data frame object that can be used for
#'interpolating.
#'Fetch the BOM forecast and create a tidy data frame of the six day forecast
#'
#' @param state Australian state or territory, either as full name or abbreviation
#' @param state Australian state or territory as postal code, see details for
#' instruction.
#'
#' @details Allowed state and territory postal codes, only one state per request
#' or all using \code{AUS}.
#' \itemize{
#' \item{ACT - Australian Capital Territory}
#' \item{NSW - New South Wales}
#' \item{NT - Northern Territory}
#' \item{QLD - Queensland}
#' \item{SA - South Australia}
#' \item{TAS - Tasmania}
#' \item{VIC - Tasmania}
#' \item{WA - Western Australia}
#' \item{AUS - Australia, returns forecast for all states}
#' }
#'
#' @return
#' Data frame of a Australia BOM forecast for max temperature, min temperature
#' and corresponding locations with lat/lon values for the next six days.
#'
#' @examples
#' \dontrun{
#' BOM_forecast <- get_forecast(state = "Qld")
#' BOM_forecast <- get_forecast(state = "QLD")
#' }
#'
#' @importFrom dplyr %>%
#'
#'
#' @export
get_forecast <- function(state) {
# Load BOM location data
utils::data("AAC_codes", package = "BOMRang")
AAC_codes <- AAC_codes
.validate_state(state)

# ftp server
ftp_base <- "ftp://ftp.bom.gov.au/anon/gen/fwo/"

# Select state/territory forecast and fetch
# State/territory forecast files
NT <- "IDD10207.xml"
NSW <- "IDN11060.xml"
QLD <- "IDN11060.xml"
SA <- "IDS10044.xml"
TAS <- "IDT16710.xml"
VIC <- "IDV10753.xml"
WA <- "IDW14199.xml"

if (state == "NT") {
xmlforecast <-
xml2::read_xml("ftp://ftp.bom.gov.au/anon/gen/fwo/IDD10207.xml") # nt
paste0(ftp_base, NT) # nt
}
else if (state == "NSW") {
else if (state == "NSW" | state == "ACT") {
xmlforecast <-
xml2::read_xml("ftp://ftp.bom.gov.au/anon/gen/fwo/IDN11060.xml") # nsw
paste0(ftp_base, NSW) # nsw
}
else if (state == "QLD") {
xmlforecast <-
xml2::read_xml("ftp://ftp.bom.gov.au/anon/gen/fwo/IDN11060.xml") # qld
paste0(ftp_base, QLD) # qld
}
else if (state == "SA") {
xmlforecast <-
xml2::read_xml("ftp://ftp.bom.gov.au/anon/gen/fwo/IDS10044.xml") # sa
paste0(ftp_base, SA) # sa
}
else if (state == "TAS")
{
xmlforecast <-
xml2::read_xml("ftp://ftp.bom.gov.au/anon/gen/fwo/IDT16710.xml") # tas
paste0(ftp_base, TAS) # tas
}
else if (state == "VIC") {
xmlforecast <-
xml2::read_xml("ftp://ftp.bom.gov.au/anon/gen/fwo/IDV10753.xml") # vic
paste0(ftp_base, VIC) # vic
}
else if (state == "WA") {
xmlforecast <-
xml2::read_xml("ftp://ftp.bom.gov.au/anon/gen/fwo/IDW14199.xml") # wa
paste0(ftp_base, WA) # wa
}
else if (state == "AUS") {
AUS <- list(NT, NSW, QLD, SA, TAS, VIC, WA)
file_list <- paste0(ftp_base, AUS)
Map(
function(ftp, dest)
utils::download.file(url = ftp, destfile = dest),
file_list,
file.path(tempdir(), basename(file_list))
)

} else
stop(state, " not recognised as a valid state or territory")


# remove index=0 (today's "forecast"), it varies and we're not interested anyway
xml2::xml_find_all(xmlforecast, ".//*[@index='0']") %>%
xml2::xml_remove()

# extract locations from forecast
areas <- xml2::xml_find_all(xmlforecast, ".//*[@type='location']")
forecast_locations <-
dplyr::bind_rows(lapply(xml2::xml_attrs(areas), as.list))

# join locations with lat/lon values for mapping and interpolation
forecast_locations <- dplyr::left_join(forecast_locations,
AAC_codes,
by = c("aac" = "AAC",
"description" = "PT_NAME"))

# unlist and add the locations aac code
forecasts <-
lapply(xml2::xml_find_all(xmlforecast, ".//*[@type='location']"),
xml2::as_list)

forecasts <- plyr::llply(forecasts, unlist)
names(forecasts) <- forecast_locations$aac

# get all the <element> and <text> tags (the forecast)
eltext <- xml2::xml_find_all(xmlforecast, "//element | //text")

# extract and clean (if needed) (the labels for the forecast)
labs <- trimws(xml2::xml_attrs(eltext, "type"))

# use a loop to turn list of named character elements into a list of dataframes
# with the location aac code for each line of the data frame
y <- vector("list")
for (i in unique(names(forecasts))) {
x <- data.frame(
keyName = names(forecasts[[i]]),
value = forecasts[[i]],
row.names = NULL
)
z <- names(forecasts[i])
x <- data.frame(rep(as.character(z), nrow(x)), x)
y[[i]] <- x
if (state != "AUS") {
tibble::as_tibble(.parse_forecast(xmlforecast))
}
else if (state == "AUS") {
xml_list <-
list.files(tempdir(), pattern = ".xml$", full.names = TRUE)
tibble::as_tibble(plyr::ldply(.data = xml_list,
.fun = .parse_forecast,
.progress = "text"))
}
}

# combind list into a single dataframe
y <- data.table::rbindlist(y, fill = TRUE)
.parse_forecast <- function(xmlforecast) {

# add the forecast description to the dataframe
forecast <- data.frame(y, labs, rep(NA, length(labs)))
names(forecast) <- c("aac", "keyName", "value", "labs", "element")

# add dates to the new object
forecast$date <- c(rep(seq(
lubridate::ymd(Sys.Date() + 1),
lubridate::ymd(Sys.Date() + 7),
by = "1 day"
),
each = 2))

# label for min/max temperature in a new col to use for sorting in next step
forecast$element <-
as.character(stringr::str_match(forecast$labs,
"air_temperature_[[:graph:]]{7}"))

# convert object to tibble and remove rows we don't need, e.g., precip
# keep only max and min temp
forecast <-
tibble::as_tibble(stats::na.omit(forecast[, c(1, 3, 5:6)]))

# convert forecast_locations$aac to factor for merging
forecast$aac <- as.character(forecast$aac)
# Load BOM location data
utils::data("AAC_codes", package = "BOMRang")
AAC_codes <- AAC_codes

# merge the forecast with the locations
forecast <-
dplyr::left_join(forecast, forecast_locations, by = "aac")
xmlforecast <- xml2::read_xml(xmlforecast)

# remove index=0 (today's "forecast"), it varies and we're not interested anyway
xml2::xml_find_all(xmlforecast, ".//*[@index='0']") %>%
xml2::xml_remove()

# extract locations from forecast
areas <- xml2::xml_find_all(xmlforecast, ".//*[@type='location']")
forecast_locations <-
dplyr::bind_rows(lapply(xml2::xml_attrs(areas), as.list))

# join locations with lat/lon values for mapping and interpolation
forecast_locations <- dplyr::left_join(forecast_locations,
AAC_codes,
by = c("aac" = "AAC",
"description" = "PT_NAME"))

# unlist and add the locations aac code
forecasts <-
lapply(xml2::xml_find_all(xmlforecast, ".//*[@type='location']"),
xml2::as_list)

forecasts <- plyr::llply(forecasts, unlist)
names(forecasts) <- forecast_locations$aac

# get all the <element> and <text> tags (the forecast)
eltext <- xml2::xml_find_all(xmlforecast, "//element | //text")

# extract and clean (if needed) (the labels for the forecast)
labs <- trimws(xml2::xml_attrs(eltext, "type"))

# use a loop to turn list of named character elements into a list of dataframes
# with the location aac code for each line of the data frame
y <- vector("list")
for (i in unique(names(forecasts))) {
x <- data.frame(
keyName = names(forecasts[[i]]),
value = forecasts[[i]],
row.names = NULL
)
z <- names(forecasts[i])
x <- data.frame(rep(as.character(z), nrow(x)), x)
y[[i]] <- x
}

# combind list into a single dataframe
y <- data.table::rbindlist(y, fill = TRUE)

# add the forecast description to the dataframe
forecast <- data.frame(y, labs, rep(NA, length(labs)))
names(forecast) <- c("aac", "keyName", "value", "labs", "element")

# add dates to the new object
forecast$date <- c(rep(seq(
lubridate::ymd(Sys.Date() + 1),
lubridate::ymd(Sys.Date() + 7),
by = "1 day"
),
each = 2))

# label for min/max temperature in a new col to use for sorting in next step
forecast$element <-
as.character(stringr::str_match(forecast$labs,
"air_temperature_[[:graph:]]{7}"))

# convert object to tibble and remove rows we don't need, e.g., precip
# keep only max and min temp
forecast <-
tibble::as_tibble(stats::na.omit(forecast[, c(1, 3, 5:6)]))

# convert forecast_locations$aac to factor for merging
forecast$aac <- as.character(forecast$aac)

# merge the forecast with the locations
forecast <-
dplyr::left_join(forecast, forecast_locations, by = "aac")
}

#' @noRd
.validate_state <-
function(state) {
if (!is.null(state)) {
state <- toupper(trimws(state))
} else
stop("\nPlease provide a valid 2 or 3 letter state or territory postal code abbreviation")
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*BOMRang*: Fetch Australian Government Bureau of Meteorology Data
=================================================================

[![Travis-CI Build Status](https://travis-ci.org/ToowoombaTrio/BOMRang.svg?branch=master)](https://travis-ci.org/ToowoombaTrio/BOMRang) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/ToowoombaTrio/BOMRang?branch=master&svg=true)](https://ci.appveyor.com/project/ToowoombaTrio/BOMRang) [![Last-changedate](https://img.shields.io/badge/last%20change-2017--05--03-brightgreen.svg)](https://github.com/toowoombatrio/BOMRang/commits/master) [![minimal R version](https://img.shields.io/badge/R%3E%3D-3.4.0-brightgreen.svg)](https://cran.r-project.org/)
[![Travis-CI Build Status](https://travis-ci.org/ToowoombaTrio/BOMRang.svg?branch=master)](https://travis-ci.org/ToowoombaTrio/BOMRang) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/ToowoombaTrio/BOMRang?branch=master&svg=true)](https://ci.appveyor.com/project/ToowoombaTrio/BOMRang) [![Last-changedate](https://img.shields.io/badge/last%20change-2017--05--11-brightgreen.svg)](https://github.com/toowoombatrio/BOMRang/commits/master) [![minimal R version](https://img.shields.io/badge/R%3E%3D-3.4.0-brightgreen.svg)](https://cran.r-project.org/)

Fetches Fetch Australian Government Bureau of Meteorology Weather forecasts and returns a tidy data frame in a [*Tibble*](http://tibble.tidyverse.org) of the current and next six days weather.

Expand Down
2 changes: 1 addition & 1 deletion docs/index.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 25bc99d

Please sign in to comment.