├── .github
├── .gitignore
└── workflows
│ ├── R-CMD-check.yaml
│ └── full-check-on-release.yaml
├── docs
├── CNAME
├── _config.yml
├── reference
│ ├── Rplot001.png
│ └── figures
│ │ └── ptvapi.png
├── pkgdown.yml
├── link.svg
├── bootstrap-toc.css
├── docsearch.js
├── pkgdown.js
├── authors.html
├── bootstrap-toc.js
├── LICENSE-text.html
└── 404.html
├── LICENSE
├── .gitignore
├── tests
├── testthat.R
└── testthat
│ ├── test-patterns.R
│ ├── test-disruption-modes.R
│ ├── test-insecure-connection.R
│ ├── helper.R
│ ├── test-helper-functions.R
│ ├── test-search-routes.R
│ ├── test-fare-estimate.R
│ ├── test-outlets.R
│ ├── test-route-types.R
│ ├── test-search-stops.R
│ ├── test-search-outlets.R
│ ├── test-routes.R
│ ├── test-directions.R
│ ├── test-runs.R
│ ├── test-disruptions.R
│ ├── test-filter-departures.R
│ ├── test-departures.R
│ └── test-stops.R
├── man
├── figures
│ └── ptvapi.png
├── make_url_friendly.Rd
├── convert_to_melbourne_time.Rd
├── map_and_rbind.Rd
├── process_response.Rd
├── to_datetime.Rd
├── geopath_to_tibble.Rd
├── parse_directions_content.Rd
├── stop_to_tibble.Rd
├── generate_request_url.Rd
├── to_integer.Rd
├── route_to_tibble.Rd
├── assert_correct_attributes.Rd
├── departure_to_tibble.Rd
├── describe_route_type.Rd
├── filter_departures.Rd
├── run_to_tibble.Rd
├── disruption_to_tibble.Rd
├── route_types.Rd
├── directions_on_route.Rd
├── outlet_to_tibble.Rd
├── parse_fare_estimate_content.Rd
├── disruption_modes.Rd
├── PTVGET.Rd
├── all_disruptions_to_tibble.Rd
├── cached_route_types.Rd
├── disruption_information.Rd
├── translate_route_type.Rd
├── disruptions_at_stop.Rd
├── stops_on_route.Rd
├── directions.Rd
├── route_information.Rd
├── routes.Rd
├── stop_information.Rd
├── outlets.Rd
├── disruptions_on_route.Rd
├── runs_on_route.Rd
├── ptvapi-package.Rd
├── stops_nearby.Rd
├── add_parameters.Rd
├── add_parameter.Rd
├── disruptions.Rd
├── search_stops.Rd
├── outlets_nearby.Rd
├── ptv_search.Rd
├── search_routes.Rd
├── search_outlets.Rd
├── fare_estimate.Rd
├── patterns.Rd
├── run_information.Rd
└── departures.Rd
├── .Rbuildignore
├── R
├── zzz.R
├── globals.R
├── authentication.R
├── geopath.R
├── ptvapi-class.R
├── ptvapi.R
├── ptv-verbs.R
├── patterns.R
├── directions.R
├── outlets.R
├── helper-functions.R
├── routes.R
├── route-types.R
└── fare-estimate.R
├── ptvapi.Rproj
├── NAMESPACE
├── DESCRIPTION
├── LICENSE.md
├── NEWS.md
└── README.md
/.github/.gitignore:
--------------------------------------------------------------------------------
1 | *.html
2 |
--------------------------------------------------------------------------------
/docs/CNAME:
--------------------------------------------------------------------------------
1 | ptvapi.mdneuzerling.com
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-slate
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | YEAR: 2020
2 | COPYRIGHT HOLDER: David Neuzerling
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .Rproj.user
2 | .Rhistory
3 | .RData
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/tests/testthat.R:
--------------------------------------------------------------------------------
1 | library(testthat)
2 | library(ptvapi)
3 |
4 | test_check("ptvapi")
5 |
--------------------------------------------------------------------------------
/man/figures/ptvapi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdneuzerling/ptvapi/HEAD/man/figures/ptvapi.png
--------------------------------------------------------------------------------
/.Rbuildignore:
--------------------------------------------------------------------------------
1 | ^ptvapi\.Rproj$
2 | ^\.Rproj\.user$
3 | ^LICENSE\.md$
4 | ^\.github$
5 | ^releases$
6 | ^docs$
7 |
--------------------------------------------------------------------------------
/docs/reference/Rplot001.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdneuzerling/ptvapi/HEAD/docs/reference/Rplot001.png
--------------------------------------------------------------------------------
/docs/reference/figures/ptvapi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdneuzerling/ptvapi/HEAD/docs/reference/figures/ptvapi.png
--------------------------------------------------------------------------------
/docs/pkgdown.yml:
--------------------------------------------------------------------------------
1 | pandoc: 2.11.4
2 | pkgdown: 1.6.1
3 | pkgdown_sha: ~
4 | articles: {}
5 | last_built: 2021-05-02T07:04Z
6 |
7 |
--------------------------------------------------------------------------------
/R/zzz.R:
--------------------------------------------------------------------------------
1 | # pkg_env is used for "private" package variables.
2 | # They're not truly private, but they are more difficult to access.
3 | # In our case, this is a useful place to cache results.
4 | pkg_env <- new.env(parent = emptyenv())
5 |
--------------------------------------------------------------------------------
/R/globals.R:
--------------------------------------------------------------------------------
1 | # NSE can trip up the lintr, especially with glue.
2 | # We add those variables here so that lintr is happy.
3 | utils::globalVariables(c(
4 | "version", "conjunction", "base_url", "version", "string",
5 | "departure"
6 | ))
7 |
--------------------------------------------------------------------------------
/tests/testthat/test-patterns.R:
--------------------------------------------------------------------------------
1 | if (identical(Sys.getenv("NOT_CRAN"), "true")) {
2 |
3 | pats <- patterns(
4 | run_ref = "1",
5 | route_type = "Train",
6 | departs = morning_test_time
7 | )
8 |
9 | test_that("patterns returns list with right names", {
10 | expect_true(is.list(pats))
11 | expect_equal(
12 | names(pats),
13 | c("departures", "stops", "routes", "runs", "directions", "disruptions")
14 | )
15 | })
16 | }
17 |
--------------------------------------------------------------------------------
/ptvapi.Rproj:
--------------------------------------------------------------------------------
1 | Version: 1.0
2 |
3 | RestoreWorkspace: No
4 | SaveWorkspace: No
5 | AlwaysSaveHistory: Default
6 |
7 | EnableCodeIndexing: Yes
8 | UseSpacesForTab: Yes
9 | NumSpacesForTab: 2
10 | Encoding: UTF-8
11 |
12 | RnwWeave: Sweave
13 | LaTeX: pdfLaTeX
14 |
15 | AutoAppendNewline: Yes
16 | StripTrailingWhitespace: Yes
17 | LineEndingConversion: Posix
18 |
19 | BuildType: Package
20 | PackageUseDevtools: Yes
21 | PackageInstallArgs: --no-multiarch --with-keep.source
22 | PackageRoxygenize: rd,collate,namespace
23 |
--------------------------------------------------------------------------------
/tests/testthat/test-disruption-modes.R:
--------------------------------------------------------------------------------
1 | if (identical(Sys.getenv("NOT_CRAN"), "true")) {
2 | test_that("Disruption modes contain expected disruptions", {
3 | retrieved_disruption_modes <- disruption_modes()
4 | expect_true("metro_train" %in% retrieved_disruption_modes)
5 | expect_true("metro_tram" %in% retrieved_disruption_modes)
6 | expect_true("ferry" %in% retrieved_disruption_modes)
7 | expect_true("taxi" %in% retrieved_disruption_modes)
8 | expect_true("general" %in% retrieved_disruption_modes)
9 | })
10 | }
11 |
--------------------------------------------------------------------------------
/man/make_url_friendly.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/url-manipulation.R
3 | \name{make_url_friendly}
4 | \alias{make_url_friendly}
5 | \title{Convert an input to a form that can be used in a URL.}
6 | \usage{
7 | make_url_friendly(input)
8 | }
9 | \arguments{
10 | \item{input}{Character.}
11 | }
12 | \value{
13 | Character.
14 | }
15 | \description{
16 | Before a character can be used as part of a html, spaces must be converted to
17 | "\%20".
18 | }
19 | \keyword{internal}
20 |
--------------------------------------------------------------------------------
/man/convert_to_melbourne_time.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/helper-functions.R
3 | \name{convert_to_melbourne_time}
4 | \alias{convert_to_melbourne_time}
5 | \title{Convert a datetime returned by the PTV API into Melbourne time}
6 | \usage{
7 | convert_to_melbourne_time(datetime)
8 | }
9 | \arguments{
10 | \item{datetime}{A datetime returned by the PTV API}
11 | }
12 | \value{
13 | A datetime in the Melbourne timezone.
14 | }
15 | \description{
16 | Convert a datetime returned by the PTV API into Melbourne time
17 | }
18 | \keyword{internal}
19 |
--------------------------------------------------------------------------------
/R/authentication.R:
--------------------------------------------------------------------------------
1 | determine_user_id <- function() {
2 | user_id <- Sys.getenv("PTV_USER_ID")
3 | if (identical(user_id, "")) {
4 | stop("Could not find PTV_USER_ID environment variable. See ?ptvapi for ",
5 | "details on obtaining and providing this information.")
6 | }
7 | user_id
8 | }
9 |
10 | determine_api_key <- function() {
11 | api_key <- Sys.getenv("PTV_API_KEY")
12 | if (identical(api_key, "")) {
13 | stop("Could not find PTV_API_KEY environment variable. See ?ptvapi for ",
14 | "details on obtaining and providing this information.")
15 | }
16 | api_key
17 | }
18 |
--------------------------------------------------------------------------------
/tests/testthat/test-insecure-connection.R:
--------------------------------------------------------------------------------
1 | if (identical(Sys.getenv("NOT_CRAN"), "true")) {
2 |
3 | current_option <- getOption("use_insecure_ptv_connection")
4 | teardown(options(use_insecure_ptv_connection = current_option))
5 |
6 | test_that("secure connection uses https", {
7 | secure_request <- run_information(1)
8 | expect_true(grepl("https", attr(secure_request, "request")))
9 | })
10 |
11 | test_that("insecure connection doesn't use https", {
12 | options(use_insecure_ptv_connection = TRUE)
13 | insecure_request <- run_information(1)
14 | expect_false(grepl("https", attr(insecure_request, "request")))
15 | })
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/NAMESPACE:
--------------------------------------------------------------------------------
1 | # Generated by roxygen2: do not edit by hand
2 |
3 | S3method(print,ptvapi)
4 | export(departures)
5 | export(directions)
6 | export(directions_on_route)
7 | export(disruption_information)
8 | export(disruption_modes)
9 | export(disruptions)
10 | export(disruptions_at_stop)
11 | export(disruptions_on_route)
12 | export(fare_estimate)
13 | export(outlets)
14 | export(outlets_nearby)
15 | export(patterns)
16 | export(route_information)
17 | export(route_types)
18 | export(routes)
19 | export(run_information)
20 | export(runs_on_route)
21 | export(search_outlets)
22 | export(search_routes)
23 | export(search_stops)
24 | export(stop_information)
25 | export(stops_nearby)
26 | export(stops_on_route)
27 |
--------------------------------------------------------------------------------
/tests/testthat/helper.R:
--------------------------------------------------------------------------------
1 | # I've avoided this dependency for the package functions, because I truly
2 | # believe it's unnecessary. But we can be more liberal with dependencies for
3 | # the tests.
4 |
5 | library(dplyr, quietly = TRUE)
6 | library(lubridate, quietly = TRUE)
7 |
8 | # The API doesn't seem to keep a full history, so dates too far in the past
9 | # may return empty results. Our test times will be two days from today: one in
10 | # the morning, and one in the afternoon.
11 |
12 | morning_test_time <- paste(
13 | format(Sys.Date() + 2, format = "%Y-%m-%d", tz = "Australia/Melbourne"),
14 | "T07:48:08"
15 | )
16 |
17 | afternoon_test_time <- paste(
18 | format(Sys.Date() + 2, format = "%Y-%m-%d", tz = "Australia/Melbourne"),
19 | "16:48:08"
20 | )
21 |
--------------------------------------------------------------------------------
/man/map_and_rbind.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/helper-functions.R
3 | \name{map_and_rbind}
4 | \alias{map_and_rbind}
5 | \title{Map and rbind a list of data frames}
6 | \usage{
7 | map_and_rbind(.x, .f, ...)
8 | }
9 | \arguments{
10 | \item{.x}{A list of data frames or tibbles.}
11 |
12 | \item{.f}{A function.}
13 |
14 | \item{...}{Additional arguments passed to the function.}
15 | }
16 | \value{
17 | A data frame or tibble.
18 | }
19 | \description{
20 | This function is a simple combination of \code{purrr::map} and \code{purrr::reduce}
21 | using \code{rbind}. This differs from \code{purrr::map_dfr}, which uses
22 | \code{dplyr::map_dfr} and therefore introduces \code{dplyr} as a dependency. If the
23 | provided list is empty, then an empty tibble will be returned.
24 | }
25 | \keyword{internal}
26 |
--------------------------------------------------------------------------------
/docs/link.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/man/process_response.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/ptv-verbs.R
3 | \name{process_response}
4 | \alias{process_response}
5 | \title{Process a raw httr response and return an object of class ptv_api}
6 | \usage{
7 | process_response(response, request_url_without_auth)
8 | }
9 | \arguments{
10 | \item{response}{A raw response generated by the \code{httr} package}
11 |
12 | \item{request_url_without_auth}{Character. The request \code{url}, without \code{devid}
13 | and signature}
14 | }
15 | \value{
16 | An S3 object of class ptv_api
17 | }
18 | \description{
19 | This S3 object returned by this function contains three elements:
20 | \itemize{
21 | \item status_code, as an integer
22 | \item request, which is the request URL without authentication details
23 | \item content, the unparsed body of the response
24 | }
25 | }
26 | \keyword{internal}
27 |
--------------------------------------------------------------------------------
/man/to_datetime.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/helper-functions.R
3 | \name{to_datetime}
4 | \alias{to_datetime}
5 | \title{Convert a POSIXct or character datetime to a format ready for a URL}
6 | \usage{
7 | to_datetime(datetime)
8 | }
9 | \arguments{
10 | \item{datetime}{POSIXct or Character.}
11 | }
12 | \value{
13 | Character.
14 | }
15 | \description{
16 | Datetimes accepted by the API need to be given in UTC. This function will
17 | accept a datetime or a character with a suitable datetime format, and output
18 | a character that is suitable for a URL. All URL input and output in this
19 | package should be in Melbourne time, and the UTC conversion should happen
20 | }
21 | \details{
22 | The API seems to accept both "\%Y-\%m-\%dT\%H:\%M:\%OS" and "\%Y-\%m-\%d \%H:\%M:\%OS",
23 | but we opt for the former.
24 | }
25 | \keyword{internal}
26 |
--------------------------------------------------------------------------------
/man/geopath_to_tibble.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/geopath.R
3 | \name{geopath_to_tibble}
4 | \alias{geopath_to_tibble}
5 | \title{Convert a single geopath to a tibble}
6 | \usage{
7 | geopath_to_tibble(geopath)
8 | }
9 | \arguments{
10 | \item{geopath}{A \code{geopaths} object, as a list}
11 | }
12 | \value{
13 | A tibble of routes, with the following columns:
14 | \itemize{
15 | \item \code{direction_id}
16 | \item \code{valid_from}
17 | \item \code{valid_to}
18 | \item \code{paths}
19 | }
20 | }
21 | \description{
22 | This function is designed to parse the \code{geopath} content returned by the
23 | interior steps of some functions. If geopath data is requested, that content
24 | will contain a list of \code{geopaths} for each route. This function is designed
25 | to parse \emph{one} of those geopaths into a tibble.
26 | }
27 | \keyword{internal}
28 |
--------------------------------------------------------------------------------
/man/parse_directions_content.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/directions.R
3 | \name{parse_directions_content}
4 | \alias{parse_directions_content}
5 | \title{Parse content of directions API call}
6 | \usage{
7 | parse_directions_content(directions_content)
8 | }
9 | \arguments{
10 | \item{directions_content}{A direction, as a list, returned by the
11 | \code{directions} API call.}
12 | }
13 | \value{
14 | A tibble consisting of the following columns: \itemize{
15 | \item \code{direction_id}
16 | \item \code{direction_name},
17 | \item \code{route_id}
18 | \item \code{route_type}
19 | \item \code{route_type_description}
20 | \item \code{route_direction_description}
21 | }
22 | }
23 | \description{
24 | This function is designed to parse the content returned by the interior
25 | steps of the \code{\link{directions}} function.
26 | }
27 | \keyword{internal}
28 |
--------------------------------------------------------------------------------
/man/stop_to_tibble.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/stops.R
3 | \name{stop_to_tibble}
4 | \alias{stop_to_tibble}
5 | \title{Convert a single stop to a tibble}
6 | \usage{
7 | stop_to_tibble(stop)
8 | }
9 | \arguments{
10 | \item{stop}{A stop, as a list, returned by a stops API call.}
11 | }
12 | \value{
13 | A tibble with the following columns: \itemize{
14 | \item{\code{stop_id}}
15 | \item{\code{stop_name}}
16 | \item{\code{stop_suburb}}
17 | \item{\code{route_type}}
18 | \item{\code{route_type_description}}
19 | \item{\code{stop_sequence}}
20 | \item{\code{stop_latitude}}
21 | \item{\code{stop_longitude}}
22 | \item{\code{disruption_ids}}
23 | }
24 | }
25 | \description{
26 | This function is designed to parse the content returned by the interior
27 | steps of the \code{\link{stops_on_route}} and \code{\link{stops_nearby}}
28 | functions.
29 | }
30 | \keyword{internal}
31 |
--------------------------------------------------------------------------------
/man/generate_request_url.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/url-manipulation.R
3 | \name{generate_request_url}
4 | \alias{generate_request_url}
5 | \title{Generate a URL with devid and signature}
6 | \usage{
7 | generate_request_url(
8 | request,
9 | user_id = determine_user_id(),
10 | api_key = determine_api_key()
11 | )
12 | }
13 | \arguments{
14 | \item{request}{Character. A path without base URL or version, such as
15 | "routes" or "stop/1071".}
16 |
17 | \item{user_id}{Integer or character. A user ID or devid provided by Public
18 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
19 |
20 | \item{api_key}{Character. An API key, with dashes, provided by Public
21 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
22 | }
23 | \value{
24 | A complete URL character that can be queried with httr
25 | }
26 | \description{
27 | Generate a URL with devid and signature
28 | }
29 | \keyword{internal}
30 |
--------------------------------------------------------------------------------
/man/to_integer.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/helper-functions.R
3 | \name{to_integer}
4 | \alias{to_integer}
5 | \title{Strictly convert an object to an integer}
6 | \usage{
7 | to_integer(x)
8 | }
9 | \arguments{
10 | \item{x}{An input of any type.}
11 | }
12 | \value{
13 | An integer interpretation of \code{x}, if possible.
14 | }
15 | \description{
16 | R does not have a built-in function to determine if a value is an integer
17 | (\code{is.integer} will check if the class of an object is "integer", but
18 | \code{is.integer(3)} will return FALSE). This helper function fills that gap. It
19 | will attempt to convert the input to an integer, but will error on any input
20 | that cannot be confidently expressed as an integer. It serves as a stricter
21 | version of \code{as.integer}. For example, the function will convert \code{3} or \code{"3"}
22 | to integers, but will error on \code{3.5} or \code{"three"}.
23 | }
24 | \keyword{internal}
25 |
--------------------------------------------------------------------------------
/DESCRIPTION:
--------------------------------------------------------------------------------
1 | Package: ptvapi
2 | Title: Access the 'Public Transport Victoria' Timetable API
3 | Version: 2.0.5
4 | Authors@R: person(given = "David",
5 | family = "Neuzerling",
6 | role = c("aut", "cre", "cph"),
7 | email = "david@neuzerling.com")
8 | Description: Access the 'Public Transport Victoria' Timetable API
9 | ,
10 | with results returned as familiar R data structures. Retrieve information on
11 | stops, routes, disruptions, departures, and more.
12 | License: MIT + file LICENSE
13 | Encoding: UTF-8
14 | Roxygen: list(markdown = TRUE)
15 | RoxygenNote: 7.2.3
16 | Imports:
17 | httr,
18 | glue,
19 | digest,
20 | jsonlite,
21 | purrr,
22 | tibble,
23 | assertthat
24 | Suggests:
25 | testthat (>= 2.1.0),
26 | dplyr,
27 | lubridate
28 | URL: https://github.com/mdneuzerling/ptvapi
29 | BugReports: https://github.com/mdneuzerling/ptvapi/issues
30 |
--------------------------------------------------------------------------------
/man/route_to_tibble.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/routes.R
3 | \name{route_to_tibble}
4 | \alias{route_to_tibble}
5 | \title{Convert a single route to a tibble}
6 | \usage{
7 | route_to_tibble(route)
8 | }
9 | \arguments{
10 | \item{route}{A route, as a list, returned by the \code{\link{routes}} API
11 | call.}
12 | }
13 | \value{
14 | A tibble of routes, with the following columns:
15 | \itemize{
16 | \item \code{route_id}
17 | \item \code{route_gtfs_id}
18 | \item \code{route_name}
19 | \item \code{route_type}
20 | \item \code{route_type_description}
21 | \item \code{route_number}
22 | \item \code{geopath}
23 | \item \code{service_status}
24 | \item \code{service_status_timestamp}
25 | }
26 | }
27 | \description{
28 | This function is designed to parse the content returned by the interior steps
29 | of the \code{\link{routes}} function. Service status information may be \code{NA},
30 | depending on the API call that was used to populate the information.
31 | }
32 | \keyword{internal}
33 |
--------------------------------------------------------------------------------
/man/assert_correct_attributes.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/helper-functions.R
3 | \name{assert_correct_attributes}
4 | \alias{assert_correct_attributes}
5 | \title{Assert that the API has returned the expected attributes}
6 | \usage{
7 | assert_correct_attributes(received_attributes, expected_attributes)
8 | }
9 | \arguments{
10 | \item{received_attributes}{A character vector of attributes, in order.}
11 |
12 | \item{expected_attributes}{A character vector of expected attributes, in
13 | order.}
14 | }
15 | \value{
16 | An error if the column names are not as expected.
17 | }
18 | \description{
19 | The attributes returned by the API calls should be follow the API schema.
20 | This function compares received attributes against a vector of expected
21 | attributes, and returns an error if the two do not match. Unfortunately,
22 | there is no easy fix for this error: the package developer(s) must be
23 | notified, so that they can align the functions against the API schema.
24 | }
25 | \keyword{internal}
26 |
--------------------------------------------------------------------------------
/man/departure_to_tibble.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/departures.R
3 | \name{departure_to_tibble}
4 | \alias{departure_to_tibble}
5 | \title{Convert a single departure to a tibble}
6 | \usage{
7 | departure_to_tibble(departure)
8 | }
9 | \arguments{
10 | \item{departure}{A departure, as a list, returned by the \code{departures} API
11 | call.}
12 | }
13 | \value{
14 | A tibble consisting of the following columns: \itemize{
15 | \item \code{stop_id}
16 | \item \code{route_id}
17 | \item \code{run_id} (deprecated, use \code{run_ref} instead)
18 | \item \code{run_ref}
19 | \item \code{direction_id}
20 | \item \code{disruption_ids}
21 | \item \code{scheduled_departure}
22 | \item \code{estimated_departure}
23 | \item \code{at_platform}
24 | \item \code{platform_number}
25 | \item \code{flags}
26 | \item \code{departure_sequence}
27 | }
28 | }
29 | \description{
30 | This function is designed to parse the content returned by the interior
31 | steps of the \code{departures} functions.
32 | }
33 | \keyword{internal}
34 |
--------------------------------------------------------------------------------
/man/describe_route_type.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/route-types.R
3 | \name{describe_route_type}
4 | \alias{describe_route_type}
5 | \title{Convert a numeric route type to a human-friendly description}
6 | \usage{
7 | describe_route_type(
8 | route_type,
9 | user_id = determine_user_id(),
10 | api_key = determine_api_key()
11 | )
12 | }
13 | \arguments{
14 | \item{route_type}{Atomic integer or character.}
15 |
16 | \item{user_id}{Integer or character. A user ID or devid provided by Public
17 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
18 |
19 | \item{api_key}{Character. An API key, with dashes, provided by Public
20 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
21 | }
22 | \value{
23 | character
24 | }
25 | \description{
26 | This function effectively wraps the results of \code{\link{route_types}} to
27 | translate a route type to a human-readable form, such as translating \code{0} to
28 | \code{"Train"}. This function is \emph{not} vectorised.
29 | }
30 | \keyword{Internal}
31 |
--------------------------------------------------------------------------------
/man/filter_departures.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/departures.R
3 | \name{filter_departures}
4 | \alias{filter_departures}
5 | \title{Filter parsed departures content according to user input}
6 | \usage{
7 | filter_departures(parsed, departs = NULL, route_id = NULL, max_results = NULL)
8 | }
9 | \arguments{
10 | \item{parsed}{A tibble of parsed departures content.}
11 |
12 | \item{departs}{POSIXct in the "Australia/Melbourne" time zone.}
13 |
14 | \item{route_id}{Integer.}
15 |
16 | \item{max_results}{Integer max results.}
17 | }
18 | \value{
19 | A filtered tibble.
20 | }
21 | \description{
22 | The departures API call isn't always reliable. This function will take a
23 | tibble of parsed departures content and filter it according to the following
24 | inputs, if they are not \code{NULL}: \itemize{
25 | \item Only departures after the given \code{departs}
26 | \item Only departures on the given route ID
27 | \item The next max_results departures per route ID, if \code{max_results} is not
28 | 0.
29 | }
30 | }
31 | \keyword{internal}
32 |
--------------------------------------------------------------------------------
/man/run_to_tibble.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/runs.R
3 | \name{run_to_tibble}
4 | \alias{run_to_tibble}
5 | \title{Convert a single run to a tibble}
6 | \usage{
7 | run_to_tibble(run)
8 | }
9 | \arguments{
10 | \item{run}{A run, as a list, returned by the \code{runs} API call.}
11 | }
12 | \value{
13 | A tibble with the following columns: \itemize{
14 | \item \code{run_id} (deprecated, use \code{run_ref} instead)
15 | \item \code{run_ref}
16 | \item \code{route_id}
17 | \item \code{route_type}
18 | \item \code{route_type_description}
19 | \item \code{direction_id}
20 | \item \code{run_sequence}
21 | \item \code{final_stop_id}
22 | \item \code{destination_name}
23 | \item \code{status}
24 | \item \code{express_stop_count}
25 | \item \code{vehicle_position}
26 | \item \code{vehicle_descriptor}
27 | \item \code{geopath}
28 | }
29 | }
30 | \description{
31 | This function is designed to parse the content returned by the interior steps
32 | of the \code{\link{runs_on_route}} and \code{\link{run_information}}
33 | functions.
34 | }
35 | \keyword{internal}
36 |
--------------------------------------------------------------------------------
/man/disruption_to_tibble.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/disruptions.R
3 | \name{disruption_to_tibble}
4 | \alias{disruption_to_tibble}
5 | \title{Convert a single disruption to a tibble}
6 | \usage{
7 | disruption_to_tibble(disruption)
8 | }
9 | \arguments{
10 | \item{disruption}{A disruption, as a list, returned by the \code{disruptions} API
11 | call.}
12 | }
13 | \value{
14 | A tibble with the following columns: \itemize{
15 | \item \code{disruption_id}
16 | \item \code{title}
17 | \item \code{url}
18 | \item \code{description}
19 | \item \code{disruption_status}
20 | \item \code{disruption_type}
21 | \item \code{published_on}
22 | \item \code{last_updated}
23 | \item \code{from_date}
24 | \item \code{to_date}
25 | \item \code{routes}
26 | \item \code{stops}
27 | \item \code{colour}
28 | \item \code{display_on_board}
29 | \item \code{display_status}
30 | }
31 | }
32 | \description{
33 | This function is designed to parse the content returned by the interior
34 | steps of the \code{disruptions_on_route} and \code{disruptions_at_stop} functions.
35 | }
36 | \keyword{internal}
37 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # MIT License
2 |
3 | Copyright (c) 2020 David Neuzerling
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/R/geopath.R:
--------------------------------------------------------------------------------
1 | #' Convert a single geopath to a tibble
2 | #'
3 | #' This function is designed to parse the `geopath` content returned by the
4 | #' interior steps of some functions. If geopath data is requested, that content
5 | #' will contain a list of `geopaths` for each route. This function is designed
6 | #' to parse _one_ of those geopaths into a tibble.
7 | #'
8 | #' @param geopath A `geopaths` object, as a list
9 | #'
10 | #' @return A tibble of routes, with the following columns:
11 | #' \itemize{
12 | #' \item `direction_id`
13 | #' \item `valid_from`
14 | #' \item `valid_to`
15 | #' \item `paths`
16 | #' }
17 | #'
18 | #' @keywords internal
19 | #'
20 | geopath_to_tibble <- function(geopath) {
21 | if (is.null(geopath)) {
22 | return(
23 | tibble::tibble(
24 | direction_id = integer(),
25 | valid_from = as.Date(NA),
26 | valid_to = as.Date(NA),
27 | paths = list()
28 | )
29 | )
30 | }
31 | tibble::tibble(
32 | direction_id = geopath$direction_id,
33 | valid_from = as.Date(geopath$valid_from),
34 | valid_to = as.Date(geopath$valid_to),
35 | paths = list(geopath$paths)
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/man/route_types.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/route-types.R
3 | \name{route_types}
4 | \alias{route_types}
5 | \title{Retrieve a translation from route type number to name}
6 | \usage{
7 | route_types(user_id = determine_user_id(), api_key = determine_api_key())
8 | }
9 | \arguments{
10 | \item{user_id}{Integer or character. A user ID or devid provided by Public
11 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
12 |
13 | \item{api_key}{Character. An API key, with dashes, provided by Public
14 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
15 | }
16 | \value{
17 | A named integer vector in which the values are the route type
18 | descriptions, and the names of the vector are the route type numbers.
19 | }
20 | \description{
21 | Route types (tram, train, etc.) are provided to the PTV API as an integer
22 | code. This function retrieves a named vector in which the values are the
23 | route type descriptions, and the names of the vector are the route type
24 | numbers. Note that "Night Bus" is a separate route type.
25 | }
26 | \examples{
27 | \dontrun{
28 | route_types()
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/tests/testthat/test-helper-functions.R:
--------------------------------------------------------------------------------
1 | test_that("Can convert only integer-like things to integers", {
2 | expect_equal(to_integer(3), 3)
3 | expect_equal(to_integer(0), 0)
4 | expect_equal(to_integer(-1), -1)
5 | expect_equal(to_integer("3"), 3)
6 | expect_error(
7 | to_integer(),
8 | 'Error in to_integer() : argument "x" is missing, with no default',
9 | fixed = TRUE
10 | )
11 | expect_error(to_integer("three"), "Cannot convert three to an integer")
12 | expect_error(to_integer(3.5), "Cannot convert 3.5 to an integer")
13 | expect_error(to_integer(data.frame()), "Cannot convert list to an integer")
14 | })
15 |
16 | test_that("Can convert API-provided timestamp to Melbourne time", {
17 | raw_time <- "2020-05-09T06:38:47.3194196+00:00" # this is what the API gives
18 | expect_equal(
19 | convert_to_melbourne_time(raw_time),
20 | as.POSIXct("2020-05-09 16:38:47.3194196 AEST", tz = "Australia/Melbourne")
21 | )
22 | })
23 |
24 | test_that("converting NULL datetime to NA", {
25 | na_datetime <- as.POSIXct(NA)
26 | attr(na_datetime, "tzone") <- "Australia/Melbourne"
27 | expect_identical(convert_to_melbourne_time(NULL), na_datetime)
28 | })
29 |
--------------------------------------------------------------------------------
/man/directions_on_route.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/directions.R
3 | \name{directions_on_route}
4 | \alias{directions_on_route}
5 | \title{Directions on a given route}
6 | \usage{
7 | directions_on_route(
8 | route_id,
9 | user_id = determine_user_id(),
10 | api_key = determine_api_key()
11 | )
12 | }
13 | \arguments{
14 | \item{route_id}{Integer. These can be listed and described with the
15 | \code{\link{routes}} function.}
16 |
17 | \item{user_id}{Integer or character. A user ID or devid provided by Public
18 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
19 |
20 | \item{api_key}{Character. An API key, with dashes, provided by Public
21 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
22 | }
23 | \value{
24 | A tibble consisting of the following columns: \itemize{
25 | \item \code{direction_id}
26 | \item \code{direction_name},
27 | \item \code{route_id}
28 | \item \code{route_type}
29 | \item \code{route_type_description}
30 | \item \code{route_direction_description}
31 | }
32 | }
33 | \description{
34 | Directions on a given route
35 | }
36 | \examples{
37 | \dontrun{
38 | directions_on_route(6)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/R/ptvapi-class.R:
--------------------------------------------------------------------------------
1 | new_ptvapi_tibble <- function(response, parsed) {
2 | assertthat::assert_that(is.data.frame(parsed))
3 |
4 | ptvapi_tibble <- tibble::new_tibble(
5 | parsed,
6 | nrow = nrow(parsed),
7 | class = "ptvapi",
8 | request = response$request,
9 | retrieved = response$retrieved,
10 | status_code = response$status_code,
11 | content = response$content
12 | )
13 |
14 | tibble::validate_tibble(ptvapi_tibble)
15 | ptvapi_tibble
16 | }
17 |
18 | # This print function hides the custom attributes that we attach the objects
19 | # returned by the api functions. In particular, the "content" attribute contains
20 | # the raw content returned by the API, and is very ugly to print. The request
21 | # and status code are printed at the top, and everything else is printed as
22 | # normal.
23 | #' @export
24 | print.ptvapi <- function(x, ...) {
25 | if (!is.null(attr(x, "request"))) {
26 | cat("Request:", attr(x, "request"), "\n")
27 | }
28 | if (!is.null(attr(x, "retrieved"))) {
29 | cat("Retrieved:", attr(x, "retrieved"), "\n")
30 | }
31 | if (!is.null(attr(x, "status_code"))) {
32 | cat("Status code:", attr(x, "status_code"), "\n")
33 | }
34 | NextMethod()
35 | }
36 |
--------------------------------------------------------------------------------
/man/outlet_to_tibble.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/outlets.R
3 | \name{outlet_to_tibble}
4 | \alias{outlet_to_tibble}
5 | \title{Convert a single outlet to a tibble}
6 | \usage{
7 | outlet_to_tibble(outlet)
8 | }
9 | \arguments{
10 | \item{outlet}{An outlet, as a list, returned by the \code{\link{outlets}} API
11 | call.}
12 | }
13 | \value{
14 | A tibble with the following columns: \itemize{
15 | \item {\code{outlet_slid_spid}}
16 | \item {\code{outlet_name}}
17 | \item {\code{outlet_business}}
18 | \item {\code{outlet_latitude}}
19 | \item {\code{outlet_longitude}}
20 | \item {\code{outlet_suburb}}
21 | \item {\code{outlet_postcode}}
22 | \item {\code{outlet_business_hour_mon}}
23 | \item {\code{outlet_business_hour_tue}}
24 | \item {\code{outlet_business_hour_wed}}
25 | \item {\code{outlet_business_hour_thu}}
26 | \item {\code{outlet_business_hour_fri}}
27 | \item {\code{outlet_business_hour_sat}}
28 | \item {\code{outlet_business_hour_sun}}
29 | \item {\code{outlet_notes}}
30 | }
31 | }
32 | \description{
33 | This function is designed to parse the content returned by the interior
34 | steps of the \code{\link{outlets}} and \code{\link{outlets_nearby}}
35 | functions.
36 | }
37 | \keyword{internal}
38 |
--------------------------------------------------------------------------------
/tests/testthat/test-search-routes.R:
--------------------------------------------------------------------------------
1 | if (identical(Sys.getenv("NOT_CRAN"), "true")) {
2 |
3 | test_that("can find route number 11 with search_routes", {
4 | route_11 <- search_routes(11)
5 | expect_gte(
6 | nrow(dplyr::filter(route_11, route_number == 11)),
7 | 1
8 | )
9 | })
10 |
11 | pakenham <- search_routes("pakenham")
12 |
13 | test_that("Can find Pakenham route with search_routes", {
14 | expect_gte(
15 | nrow(dplyr::filter(pakenham, route_name == "Pakenham Line")),
16 | 1
17 | )
18 | })
19 |
20 | test_that("search_routes result has class \"ptvapi\"", {
21 | expect_s3_class(pakenham, "ptvapi")
22 | })
23 |
24 | test_that("Errors when providing incomplete location arguments", {
25 | expect_error(
26 | search_routes("Bundoora", latitude = -37.818229),
27 | "If searching near a location, both latitude and longitude must be provided"
28 | )
29 | expect_error(
30 | search_routes("Bundoora", longitude = 144.952404),
31 | "If searching near a location, both latitude and longitude must be provided"
32 | )
33 | expect_error(
34 | search_routes("Bundoora", max_distance = 100),
35 | paste0(
36 | "Trying to limit search results with a maximum distance, but a latitude ",
37 | "and longitude wasn't provided"
38 | )
39 | )
40 | })
41 | }
42 |
--------------------------------------------------------------------------------
/man/parse_fare_estimate_content.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/fare-estimate.R
3 | \name{parse_fare_estimate_content}
4 | \alias{parse_fare_estimate_content}
5 | \title{Parse content of fare estimates API call}
6 | \usage{
7 | parse_fare_estimate_content(fare_estimate_content)
8 | }
9 | \arguments{
10 | \item{fare_estimate_content}{A direction, as a list, returned by the
11 | \code{\link{fare_estimate}} API call.}
12 | }
13 | \value{
14 | A data frame consisting of one row for each \code{passenger_type}, and the
15 | following columns: \itemize{
16 | \item \code{min_zone}
17 | \item \code{max_zone}
18 | \item \code{unique_zones}
19 | \item \code{early_bird}
20 | \item \code{free_tram_zone}
21 | \item \code{weekend_journey}
22 | \item \code{passenger_type}
23 | \item \code{fare_2_hour_peak}
24 | \item \code{fare_2_hour_off_peak}
25 | \item \code{fare_daily_peak}
26 | \item \code{fare_daily_off_peak}
27 | \item \code{pass_7_days}
28 | \item \code{pass_28_to_69_day_per_day}
29 | \item \code{pass_70_plus_day_per_day}
30 | \item \code{weekend_cap}
31 | \item \code{holiday_cap}
32 | }
33 | }
34 | \description{
35 | This function is designed to parse the content returned by the interior
36 | steps of the \code{\link{fare_estimate}} function.
37 | }
38 | \keyword{internal}
39 |
--------------------------------------------------------------------------------
/man/disruption_modes.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/disruptions.R
3 | \name{disruption_modes}
4 | \alias{disruption_modes}
5 | \title{Retrieve a translation from description mode number to description mode name}
6 | \usage{
7 | disruption_modes(user_id = determine_user_id(), api_key = determine_api_key())
8 | }
9 | \arguments{
10 | \item{user_id}{Integer or character. A user ID or devid provided by Public
11 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
12 |
13 | \item{api_key}{Character. An API key, with dashes, provided by Public
14 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
15 | }
16 | \value{
17 | A named vector in which the values are the disruption mode
18 | descriptions, and the names of the vector are the description mode numbers.
19 | }
20 | \description{
21 | Disruption mode types (eg. "metro_train", "metro_tram", "school_bus", "taxi")
22 | have corresponding integer IDs. This function retrieves a named vector in
23 | which the values are the disruption mode descriptions, and the names of the
24 | vector are the description mode numbers. Note that disruption mode names are
25 | in snake case, that is, all lower case with underscores between words.
26 | }
27 | \examples{
28 | \dontrun{disruption_modes()}
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/tests/testthat/test-fare-estimate.R:
--------------------------------------------------------------------------------
1 | # I'm unsure how to test for journey_route_types. It's not clear how it affects
2 | # the results.
3 |
4 | if (identical(Sys.getenv("NOT_CRAN"), "true")) {
5 |
6 | zone12 <- fare_estimate(1, 2)
7 |
8 | test_that("fare_estimate result has class \"ptvapi\"", {
9 | expect_s3_class(zone12, "ptvapi")
10 | })
11 |
12 | test_that("basic fare estimate looks right", {
13 | expect_equal(nrow(zone12), 3)
14 | expect_equal(nrow(distinct(zone12, passenger_type)), 3)
15 | })
16 |
17 | test_that("fare_estimate picks up on the weekday early bird free travel", {
18 | diff <- 2 - wday(today())
19 | if (diff < 0) diff <- diff + 7
20 | monday <- today() + diff # Next Monday, or today, if today is Monday
21 | early_touch_on <- paste(as.character(monday), "06:00:00")
22 | early_touch_off <- paste(as.character(monday), "06:00:00")
23 | early_bird <- fare_estimate(
24 | min_zone = 1,
25 | max_zone = 1,
26 | journey_touch_on = early_touch_on,
27 | journey_touch_off = early_touch_off
28 | )
29 | expect_true(all(early_bird$early_bird))
30 | expect_false(any(early_bird$weekend_journey))
31 | })
32 |
33 | test_that("fare_estimate accepts free tram zone", {
34 | ftz <- fare_estimate(1, 1, journey_in_free_tram_zone = TRUE)
35 | expect_true(all(ftz$free_tram_zone))
36 | })
37 | }
38 |
--------------------------------------------------------------------------------
/tests/testthat/test-outlets.R:
--------------------------------------------------------------------------------
1 | if (identical(Sys.getenv("NOT_CRAN"), "true")) {
2 |
3 | all_outlets <- outlets()
4 | test_that("outlets result has class \"ptvapi\"", {
5 | expect_s3_class(all_outlets, "ptvapi")
6 | })
7 |
8 | test_that("We can find a 7-Eleven", {
9 | expect_true(
10 | any(grepl("7-Eleven", all_outlets$outlet_business))
11 | )
12 | })
13 |
14 | outlets_near_flinders <- outlets_nearby(
15 | latitude = -37.8183,
16 | longitude = 144.9671
17 | )
18 |
19 | test_that("outlets_nearby result has class \"ptvapi\"", {
20 | expect_s3_class(outlets_near_flinders, "ptvapi")
21 | })
22 |
23 | test_that("There's a 7-Eleven near Flinders Street", {
24 | expect_true(
25 | any(grepl("7-Eleven", outlets_near_flinders$outlet_business))
26 | )
27 | })
28 |
29 | test_that("outlets_nearby filtered by max_results", {
30 | one_outlet <- outlets_nearby(
31 | latitude = -37.8183,
32 | longitude = 144.9671,
33 | max_results = 1
34 | )
35 | expect_equal(nrow(one_outlet), 1)
36 | })
37 |
38 | test_that("outlets_nearby filtered by max_distance", {
39 | max_100 <- outlets_nearby(
40 | latitude = -37.8183,
41 | longitude = 144.9671,
42 | max_distance = 100
43 | )
44 | max_1000 <- outlets_nearby(
45 | latitude = -37.8183,
46 | longitude = 144.9671,
47 | max_distance = 1000
48 | )
49 | expect_gt(
50 | nrow(max_1000),
51 | nrow(max_100)
52 | )
53 | })
54 | }
55 |
--------------------------------------------------------------------------------
/man/PTVGET.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/ptv-verbs.R
3 | \name{PTVGET}
4 | \alias{PTVGET}
5 | \title{Submit a GET request to the PTV API}
6 | \usage{
7 | PTVGET(
8 | request,
9 | user_id = determine_user_id(),
10 | api_key = determine_api_key(),
11 | ...
12 | )
13 | }
14 | \arguments{
15 | \item{request}{A request or path for the API, eg. "routes".}
16 |
17 | \item{user_id}{Integer or character. A user ID or devid provided by Public
18 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
19 |
20 | \item{api_key}{Character. An API key, with dashes, provided by Public
21 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
22 |
23 | \item{...}{Additional arguments passed to \code{httr::GET}.}
24 | }
25 | \value{
26 | A HTTP response. Content can be accessed with \code{httr::content}.
27 | }
28 | \description{
29 | Submit a GET request to the PTV API
30 | }
31 | \section{Obtaining API authentication details}{
32 |
33 |
34 | You will need to obtain a user ID (also called a devid) and an API key from
35 | Public Transport Victoria. These are obtained by email. Instructions are
36 | available at \url{https://www.ptv.vic.gov.au/footer/data-and-reporting/datasets/ptv-timetable-api/}.
37 | You may pass these two pieces of information directly to the function, or you
38 | can set the PTV_USER_ID and PTV_API_KEY environment variables.
39 | }
40 |
41 | \keyword{internal}
42 |
--------------------------------------------------------------------------------
/man/all_disruptions_to_tibble.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/disruptions.R
3 | \name{all_disruptions_to_tibble}
4 | \alias{all_disruptions_to_tibble}
5 | \title{Convert the contents of a disruptions API call to a single tibble}
6 | \usage{
7 | all_disruptions_to_tibble(disruptions_content)
8 | }
9 | \arguments{
10 | \item{disruptions_content}{The raw disruptions content returned by the
11 | \code{disruptions} API call.}
12 | }
13 | \value{
14 | A tibble with the following columns: \itemize{
15 | \item \code{disruption_mode}
16 | \item \code{disruption_mode_description}
17 | \item \code{disruption_id}
18 | \item \code{title}
19 | \item \code{url}
20 | \item \code{description}
21 | \item \code{disruption_status}
22 | \item \code{disruption_type}
23 | \item \code{published_on}
24 | \item \code{last_updated}
25 | \item \code{from_date}
26 | \item \code{to_date}
27 | \item \code{routes}
28 | \item \code{stops}
29 | \item \code{colour}
30 | \item \code{display_on_board}
31 | \item \code{display_status}
32 | }
33 | }
34 | \description{
35 | Disruptions API responses contain an element for every service type, eg.
36 | metro train, taxis, Skybus. Normally we would map-reduce the content of an
37 | API call with a function analogous to \code{disruption_to_tibble}. But because of
38 | the extra layer of nesting in the response, we have to map-reduce the service
39 | types first.
40 | }
41 | \details{
42 | Note that we return an empty tibble if there are no disruptions, so that
43 | this situation is omitted.
44 | }
45 | \keyword{internal}
46 |
--------------------------------------------------------------------------------
/man/cached_route_types.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/route-types.R
3 | \name{cached_route_types}
4 | \alias{cached_route_types}
5 | \title{Retrieve route types, using cached values if possible}
6 | \usage{
7 | cached_route_types(
8 | user_id = determine_user_id(),
9 | api_key = determine_api_key()
10 | )
11 | }
12 | \arguments{
13 | \item{user_id}{Integer or character. A user ID or devid provided by Public
14 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
15 |
16 | \item{api_key}{Character. An API key, with dashes, provided by Public
17 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
18 | }
19 | \value{
20 | A named integer vector in which the values are the route type
21 | descriptions, and the names of the vector are the route type numbers.
22 | }
23 | \description{
24 | Route types will change extraordinarily rarely --- this would require PTV to
25 | add a new route type akin to "train" or "bus". To avoid querying the API too
26 | much, we prefer to use cached values for route type translation wherever
27 | possible. This function effectively wraps \code{route_types}, returning cached
28 | results if possible or caching results otherwise. Note that if a user
29 | specifically calls \code{route_types} then we do \emph{not} return cached results.
30 |
31 | We use the \code{pkg_env} as a cache, which is an environment created on package
32 | load. This is not truly private --- users could still access this as an
33 | internal value. But it's effectively "out of the way".
34 | }
35 | \keyword{internal}
36 |
--------------------------------------------------------------------------------
/man/disruption_information.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/disruptions.R
3 | \name{disruption_information}
4 | \alias{disruption_information}
5 | \title{Information on a particular disruption}
6 | \usage{
7 | disruption_information(
8 | disruption_id,
9 | user_id = determine_user_id(),
10 | api_key = determine_api_key()
11 | )
12 | }
13 | \arguments{
14 | \item{disruption_id}{Integer.}
15 |
16 | \item{user_id}{Integer or character. A user ID or devid provided by Public
17 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
18 |
19 | \item{api_key}{Character. An API key, with dashes, provided by Public
20 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
21 | }
22 | \value{
23 | A tibble with the following columns: \itemize{
24 | \item \code{disruption_mode}
25 | \item \code{disruption_mode_description}
26 | \item \code{disruption_id}
27 | \item \code{title}
28 | \item \code{url}
29 | \item \code{description}
30 | \item \code{disruption_status}
31 | \item \code{disruption_type}
32 | \item \code{published_on}
33 | \item \code{last_updated}
34 | \item \code{from_date}
35 | \item \code{to_date}
36 | \item \code{routes}
37 | \item \code{stops}
38 | \item \code{colour}
39 | \item \code{display_on_board}
40 | \item \code{display_status}
41 | }
42 | }
43 | \description{
44 | This function can be used when the integer disruption ID is already known.
45 | This can be searched for with either \code{disruptions},
46 | \code{disruptions_on_route}, or \code{disruptions_at_stop} functions.
47 | }
48 | \examples{
49 | \dontrun{
50 | disruption_information(206639)
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/man/translate_route_type.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/route-types.R
3 | \name{translate_route_type}
4 | \alias{translate_route_type}
5 | \title{Translate a route type input into a numerical route type}
6 | \usage{
7 | translate_route_type(
8 | route_type,
9 | user_id = determine_user_id(),
10 | api_key = determine_api_key()
11 | )
12 | }
13 | \arguments{
14 | \item{route_type}{A route type which can be provided either as a non-negative
15 | integer code, or as a character: "Tram", "Train", "Bus", "Vline" or "Night
16 | Bus". Character inputs are not case-sensitive. Use the
17 | \code{\link{route_types}} function to extract a vector of all route types.}
18 |
19 | \item{user_id}{Integer or character. A user ID or devid provided by Public
20 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
21 |
22 | \item{api_key}{Character. An API key, with dashes, provided by Public
23 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
24 | }
25 | \value{
26 | An integer route type code, or NULL if the input is NULL
27 | }
28 | \description{
29 | Many API calls require a route type (eg. "Tram" or "Train"). These must be
30 | provided as integers, which are translated to route type descriptions with
31 | the `route_types() function/API call. This function will: \itemize{
32 | \item Translate a case-insensitive description such as "Tram" or "Train" to
33 | the corresponding route type code
34 | \item Check a given integer to see if it is a valid route type code,
35 | returning it if so and erroring otherwise
36 | \item Return NULL on NULL input
37 | }
38 | This function is \emph{not} vectorised.
39 | }
40 | \keyword{internal}
41 |
--------------------------------------------------------------------------------
/man/disruptions_at_stop.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/disruptions.R
3 | \name{disruptions_at_stop}
4 | \alias{disruptions_at_stop}
5 | \title{Disruptions at a given stop}
6 | \usage{
7 | disruptions_at_stop(
8 | stop_id,
9 | disruption_status = NULL,
10 | user_id = determine_user_id(),
11 | api_key = determine_api_key()
12 | )
13 | }
14 | \arguments{
15 | \item{stop_id}{Integer stop ID.}
16 |
17 | \item{disruption_status}{Character. Can be used to filter to either "current"
18 | or "planned" disruptions. Defaults to NULL, in which case no filter is
19 | applied.}
20 |
21 | \item{user_id}{Integer or character. A user ID or devid provided by Public
22 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
23 |
24 | \item{api_key}{Character. An API key, with dashes, provided by Public
25 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
26 | }
27 | \value{
28 | A tibble with the following columns: \itemize{
29 | \item \code{disruption_mode}
30 | \item \code{disruption_mode_description}
31 | \item \code{disruption_id}
32 | \item \code{title}
33 | \item \code{url}
34 | \item \code{description}
35 | \item \code{disruption_status}
36 | \item \code{disruption_type}
37 | \item \code{published_on}
38 | \item \code{last_updated}
39 | \item \code{from_date}
40 | \item \code{to_date}
41 | \item \code{routes}
42 | \item \code{stops}
43 | \item \code{colour}
44 | \item \code{display_on_board}
45 | \item \code{display_status}
46 | }
47 | }
48 | \description{
49 | Disruptions at a given stop
50 | }
51 | \examples{
52 | \dontrun{
53 | disruptions_at_stop(1071)
54 | disruptions_at_stop(1071, disruption_status = "current")
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/tests/testthat/test-route-types.R:
--------------------------------------------------------------------------------
1 | if (identical(Sys.getenv("NOT_CRAN"), "true")) {
2 |
3 | teardown(pkg_env$route_types <- NULL)
4 |
5 | route_types_results <- route_types()
6 |
7 | test_that("All 5 route types present", {
8 | expect_true("Train" %in% route_types_results)
9 | expect_true("Tram" %in% route_types_results)
10 | expect_true("Bus" %in% route_types_results)
11 | expect_true("Vline" %in% route_types_results)
12 | expect_true("Night Bus" %in% route_types_results)
13 | })
14 |
15 | test_that("Route type translation is working", {
16 | expect_equal(translate_route_type(0), 0)
17 | expect_error(translate_route_type(99999))
18 |
19 | expect_error(translate_route_type("notaroutetype"))
20 | train_route_code <- translate_route_type("Train")
21 | expect_type(train_route_code, "integer")
22 | expect_equal(translate_route_type("TRAIN"), train_route_code)
23 | expect_equal(translate_route_type("train"), train_route_code)
24 | })
25 |
26 | test_that("Route type description is working", {
27 | expect_equal(describe_route_type(0), "Train")
28 | expect_equal(describe_route_type(1), "Tram")
29 | expect_equal(describe_route_type(2), "Bus")
30 | expect_equal(describe_route_type(3), "Vline")
31 | expect_equal(describe_route_type(4), "Night Bus")
32 |
33 | # This test has an alternative use --- if a new route type is added, the tests
34 | # will start failing, and I can be notified by a CICD pipeline
35 | expect_error(describe_route_type(5), "Route type 5 doesn't exist")
36 | })
37 |
38 | test_that("route types are being cached", {
39 | cached_route_types()
40 | pkg_env$route_types <- c("0" = "fish")
41 | expect_equal(describe_route_type(0), "fish")
42 | })
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/tests/testthat/test-search-stops.R:
--------------------------------------------------------------------------------
1 | if (identical(Sys.getenv("NOT_CRAN"), "true")) {
2 |
3 | ascot_vale <- search_stops("Ascot Vale")
4 |
5 | test_that("search_stops result has class \"ptvapi\"", {
6 | expect_s3_class(ascot_vale, "ptvapi")
7 | })
8 |
9 | test_that("results in search_stops can relate to stop name alone", {
10 | expect_gte(
11 | nrow(
12 | dplyr::filter(
13 | ascot_vale,
14 | grepl("Ascot Vale", stop_name, ignore.case = TRUE),
15 | !grepl("Ascot Vale", stop_suburb, ignore.case = TRUE)
16 | )
17 | ),
18 | 1
19 | )
20 | })
21 |
22 | test_that("results in search_stops can relate to stop suburb alone", {
23 | expect_gte(
24 | nrow(
25 | dplyr::filter(
26 | ascot_vale,
27 | !grepl("Ascot Vale", stop_name, ignore.case = TRUE),
28 | grepl("Ascot Vale", stop_suburb, ignore.case = TRUE)
29 | )
30 | ),
31 | 1
32 | )
33 | })
34 |
35 | test_that("all results in search_stops relate to search term somehow", {
36 | expect_equal(
37 | nrow(
38 | dplyr::filter(
39 | ascot_vale,
40 | !grepl("Ascot Vale", stop_name
41 | , ignore.case = TRUE),
42 | !grepl("Ascot Vale", stop_suburb, ignore.case = TRUE)
43 | )
44 | ),
45 | 0
46 | )
47 | })
48 |
49 | test_that("search_stops can be filtered with multiple route types", {
50 | expect_equal(
51 | search_stops("South Yarra", route_types = c(0, 1)) %>%
52 | pull(route_type) %>%
53 | unique %>%
54 | sort,
55 | c(0, 1)
56 | )
57 | expect_equal(
58 | search_stops("South Yarra", route_types = c(0, 2)) %>%
59 | pull(route_type) %>%
60 | unique %>%
61 | sort,
62 | c(0, 2)
63 | )
64 | })
65 | }
66 |
--------------------------------------------------------------------------------
/R/ptvapi.R:
--------------------------------------------------------------------------------
1 | # nolint start
2 | #' ptvapi: A package for accessing the Public Transport Victoria Timetable API
3 | #'
4 | #' @description
5 | #' \if{html}{\figure{ptvapi.png}{options: alt='logo'}}
6 | #'
7 | #' Accessing the Public Transport Victoria Timetable API reqiures a user ID
8 | #' (also called a `devid`) and an API key. These can be accessed by contacting
9 | #' Public Transport Victoria. See
10 | #' \url{https://www.ptv.vic.gov.au/footer/data-and-reporting/datasets/ptv-timetable-api/}
11 | #'
12 | #' The user ID and API key can be entered directly into all functions.
13 | #' Alternatively, all functions will pick up on the PTV_USER_ID and API_KEY
14 | #' environment variables, if defined.
15 | #'
16 | #' All API requests use SSL by default. To disable this, and to use the `http`
17 | #' API endpoints rather than the `https` API endpoints, set the option:
18 | #' ```
19 | #' options(use_insecure_ptv_connection = TRUE)
20 | #' ```
21 | #'
22 | #' @details
23 | #' This is an unofficial wrapper of the Public Transport Victoria Timetable API.
24 | #' The author(s) of this package are unaffiliated with Public Transport
25 | #' Victoria.
26 | #'
27 | #' @examples \dontrun{
28 | #' # tibble of all routes
29 | #' routes()
30 | #'
31 | #' # Search for routes by name (case insensitive, partial matching supported)
32 | #' routes(route_name = "Frankston")
33 | #'
34 | #' # All current disruptions
35 | #' disruptions(disruption_status = "current")
36 | #'
37 | #' # Train stops near Flinders Street Station
38 | #' stops_nearby(
39 | #' latitude = -37.8183,
40 | #' longitude = 144.9671,
41 | #' route_types = "Train"
42 | #' )
43 | #'
44 | #' # Upcoming train departures from Flinders Street Station
45 | #' departures(stop_id = 1071, route_type = "Train")
46 | #' }
47 | "_PACKAGE"
48 | # nolint end
49 |
--------------------------------------------------------------------------------
/man/stops_on_route.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/stops.R
3 | \name{stops_on_route}
4 | \alias{stops_on_route}
5 | \title{Stops on a given route and route type}
6 | \usage{
7 | stops_on_route(
8 | route_id,
9 | route_type,
10 | direction_id = NULL,
11 | user_id = determine_user_id(),
12 | api_key = determine_api_key()
13 | )
14 | }
15 | \arguments{
16 | \item{route_id}{Integer. These can be listed and described with the
17 | \code{\link{routes}} function.}
18 |
19 | \item{route_type}{A route type which can be provided either as a non-negative
20 | integer code, or as a character: "Tram", "Train", "Bus", "Vline" or "Night
21 | Bus". Character inputs are not case-sensitive. Use the
22 | \code{\link{route_types}} function to extract a vector of all route types.}
23 |
24 | \item{direction_id}{Optionally filter by a direction ID. These can be
25 | obtained with the \code{\link{directions_on_route}} function.}
26 |
27 | \item{user_id}{Integer or character. A user ID or devid provided by Public
28 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
29 |
30 | \item{api_key}{Character. An API key, with dashes, provided by Public
31 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
32 | }
33 | \value{
34 | A tibble with the following columns: \itemize{
35 | \item{\code{stop_id}}
36 | \item{\code{stop_name}}
37 | \item{\code{stop_suburb}}
38 | \item{\code{route_type}}
39 | \item{\code{route_type_description}}
40 | \item{\code{stop_sequence}}
41 | \item{\code{stop_latitude}}
42 | \item{\code{stop_longitude}}
43 | \item{\code{disruption_ids}}
44 | }
45 | }
46 | \description{
47 | Stops on a given route and route type
48 | }
49 | \examples{
50 | \dontrun{
51 | stops_on_route(6, route_type = "Train")
52 | stops_on_route(6, route_type = 0)
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/man/directions.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/directions.R
3 | \name{directions}
4 | \alias{directions}
5 | \title{Directions for a given direction ID}
6 | \usage{
7 | directions(
8 | direction_id,
9 | route_type = NULL,
10 | user_id = determine_user_id(),
11 | api_key = determine_api_key()
12 | )
13 | }
14 | \arguments{
15 | \item{direction_id}{Integer.}
16 |
17 | \item{route_type}{Optionally filter results by a route type. A route type can
18 | be provided either as a non-negative integer code, or as a character:
19 | "Tram", "Train", "Bus", "Vline" or "Night Bus". Character inputs are not
20 | case-sensitive. Use the \code{\link{route_types}} function to extract a
21 | vector of all route types.}
22 |
23 | \item{user_id}{Integer or character. A user ID or devid provided by Public
24 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
25 |
26 | \item{api_key}{Character. An API key, with dashes, provided by Public
27 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
28 | }
29 | \value{
30 | A tibble consisting of the following columns: \itemize{
31 | \item \code{direction_id}
32 | \item \code{direction_name},
33 | \item \code{route_id}
34 | \item \code{route_type}
35 | \item \code{route_type_description}
36 | \item \code{route_direction_description}
37 | }
38 | }
39 | \description{
40 | This function returns all directions with a given ID. Directions that share
41 | an ID are not necessarily related, especially if not filtering by route type.
42 | It's advised to use to the \code{\link{directions_on_route}} function to
43 | search for directions of interest.
44 | }
45 | \examples{
46 | \dontrun{
47 | directions(direction_id = 5)
48 | directions(direction_id = 5, route_type = "Train")
49 | directions(direction_id = 5, route_type = 0)
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/man/route_information.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/routes.R
3 | \name{route_information}
4 | \alias{route_information}
5 | \title{Information for a given route}
6 | \usage{
7 | route_information(
8 | route_id,
9 | include_geopath = FALSE,
10 | geopath_utc = NULL,
11 | user_id = determine_user_id(),
12 | api_key = determine_api_key()
13 | )
14 | }
15 | \arguments{
16 | \item{route_id}{Integer. These can be listed and described with the
17 | \code{\link{routes}} function.}
18 |
19 | \item{include_geopath}{Logical. Whether to populate the \code{geopath} column.
20 | Defaults to FALSE.}
21 |
22 | \item{geopath_utc}{Date, or character that can be converted to a date. The
23 | UTC date for which the geopaths are effective. Defaults to the current
24 | date. Has no effect if \code{include_geopath = FALSE}. It's uncertain how much
25 | historical or future-dated data is available.}
26 |
27 | \item{user_id}{Integer or character. A user ID or devid provided by Public
28 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
29 |
30 | \item{api_key}{Character. An API key, with dashes, provided by Public
31 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
32 | }
33 | \value{
34 | A tibble of routes, with the following columns:
35 | \itemize{
36 | \item \code{route_id}
37 | \item \code{route_gtfs_id}
38 | \item \code{route_name}
39 | \item \code{route_type}
40 | \item \code{route_type_description}
41 | \item \code{route_number}
42 | \item \code{geopath}
43 | \item \code{service_status}
44 | \item \code{service_status_timestamp}
45 | }
46 | }
47 | \description{
48 | Information for a given route
49 | }
50 | \examples{
51 | \dontrun{
52 | route_information(6)
53 | route_information(6, include_geopath = TRUE)
54 | route_information(6, include_geopath = TRUE, geopath_utc = "2020-07-01")
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/man/routes.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/routes.R
3 | \name{routes}
4 | \alias{routes}
5 | \title{Information for all routes}
6 | \usage{
7 | routes(
8 | route_types = NULL,
9 | route_name = NULL,
10 | user_id = determine_user_id(),
11 | api_key = determine_api_key()
12 | )
13 | }
14 | \arguments{
15 | \item{route_types}{Integer or character vector. Optionally filter by a vector
16 | of route types. A route type can be provided either as a non-negative
17 | integer code, or as a character: "Tram", "Train", "Bus", "Vline" or "Night
18 | Bus". Character inputs are not case-sensitive. Use the
19 | \code{\link{route_types}} function to extract a vector of all route types.}
20 |
21 | \item{route_name}{Character. Optionally filter by route name. Partial matches
22 | are accepted, and the matches are not case sensitive.}
23 |
24 | \item{user_id}{Integer or character. A user ID or devid provided by Public
25 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
26 |
27 | \item{api_key}{Character. An API key, with dashes, provided by Public
28 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
29 | }
30 | \value{
31 | A tibble of routes, with the following columns:
32 | \itemize{
33 | \item \code{route_id}
34 | \item \code{route_gtfs_id}
35 | \item \code{route_name}
36 | \item \code{route_type}
37 | \item \code{route_type_description}
38 | \item \code{route_number}
39 | \item \code{geopath}
40 | \item \code{service_status}
41 | \item \code{service_status_timestamp}
42 | }
43 | }
44 | \description{
45 | Information for all routes
46 | }
47 | \examples{
48 | \dontrun{
49 | routes()
50 | routes(route_types = "Train")
51 | routes(route_types = 0)
52 | routes(route_types = c("Train", "Tram"))
53 | routes(route_name = "Frankston")
54 | routes(route_name = "Craigie")
55 | routes(route_name = "werribee")
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/man/stop_information.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/stops.R
3 | \name{stop_information}
4 | \alias{stop_information}
5 | \title{Information for a given stop (metropolitan and V/Line stations only)}
6 | \usage{
7 | stop_information(
8 | stop_id,
9 | route_type,
10 | user_id = determine_user_id(),
11 | api_key = determine_api_key()
12 | )
13 | }
14 | \arguments{
15 | \item{stop_id}{Integer stop ID.}
16 |
17 | \item{route_type}{A route type which can be provided either as a non-negative
18 | integer code, or as a character: "Tram", "Train", "Bus", "Vline" or "Night
19 | Bus". Character inputs are not case-sensitive. Use the
20 | \code{\link{route_types}} function to extract a vector of all route types.}
21 |
22 | \item{user_id}{Integer or character. A user ID or devid provided by Public
23 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
24 |
25 | \item{api_key}{Character. An API key, with dashes, provided by Public
26 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
27 | }
28 | \value{
29 | A single-row tibble with the following columns: \itemize{
30 | \item{\code{stop_id}}
31 | \item{\code{stop_name}}
32 | \item{\code{route_type}}
33 | \item{\code{route_type_description}}
34 | \item{\code{station_details_id}}
35 | \item{\code{station_type}}
36 | \item{\code{station_description}}
37 | \item{\code{point_id}}
38 | \item{\code{mode_id}}
39 | \item{\code{operating_hours}}
40 | \item{\code{flexible_stop_opening_hours}}
41 | \item{\code{stop_contact}}
42 | \item{\code{stop_ticket}}
43 | \item{\code{stop_location}}
44 | \item{\code{stop_amenities}}
45 | \item{\code{stop_accessibility}}
46 | \item{\code{stop_staffing}}
47 | \item{\code{disruption_ids}}
48 | }
49 | }
50 | \description{
51 | This function can be used when integer stop ID is already known. This can be
52 | searched for with either the \code{\link{stops_on_route}} or
53 | \code{\link{stops_nearby}} functions.
54 | }
55 |
--------------------------------------------------------------------------------
/man/outlets.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/outlets.R
3 | \name{outlets}
4 | \alias{outlets}
5 | \title{Information for all outlets}
6 | \usage{
7 | outlets(user_id = determine_user_id(), api_key = determine_api_key())
8 | }
9 | \arguments{
10 | \item{user_id}{Integer or character. A user ID or devid provided by Public
11 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
12 |
13 | \item{api_key}{Character. An API key, with dashes, provided by Public
14 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
15 | }
16 | \value{
17 | A tibble with the following columns: \itemize{
18 | \item {\code{outlet_slid_spid}}
19 | \item {\code{outlet_name}}
20 | \item {\code{outlet_business}}
21 | \item {\code{outlet_latitude}}
22 | \item {\code{outlet_longitude}}
23 | \item {\code{outlet_suburb}}
24 | \item {\code{outlet_postcode}}
25 | \item {\code{outlet_business_hour_mon}}
26 | \item {\code{outlet_business_hour_tue}}
27 | \item {\code{outlet_business_hour_wed}}
28 | \item {\code{outlet_business_hour_thu}}
29 | \item {\code{outlet_business_hour_fri}}
30 | \item {\code{outlet_business_hour_sat}}
31 | \item {\code{outlet_business_hour_sun}}
32 | \item {\code{outlet_notes}}
33 | }
34 | }
35 | \description{
36 | Information for all outlets
37 | }
38 | \details{
39 | The \code{outlet_name} reported here is more accurately described as an outlet
40 | \emph{address}. We keep the \code{outlet_name} column name as this is how the PTV API
41 | describes it.
42 |
43 | The business hours are reported as characters. Usually they take on
44 | a format of "8.00AM - 10.00PM", but there variants such as "7.30AM - 11.00AM
45 | and 1.30PM - 6.00PM". For days on which an outlet is closed, the opening
46 | hours are usually reported as "CLOSED", but can also be an empty character.
47 | Some opening hours are "24 Hours". These fields are also filled with missing
48 | values and empty characters.
49 | }
50 | \examples{
51 | \dontrun{
52 | outlets()
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/man/disruptions_on_route.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/disruptions.R
3 | \name{disruptions_on_route}
4 | \alias{disruptions_on_route}
5 | \title{Disruptions on a given route}
6 | \usage{
7 | disruptions_on_route(
8 | route_id,
9 | stop_id = NULL,
10 | disruption_status = NULL,
11 | user_id = determine_user_id(),
12 | api_key = determine_api_key()
13 | )
14 | }
15 | \arguments{
16 | \item{route_id}{Integer. These can be listed and described with the
17 | \code{\link{routes}} function.}
18 |
19 | \item{stop_id}{Integer. Optionally filter results to a specific stop ID.
20 | These can be searched for with the \code{stops_on_route} and \code{stops_nearby}
21 | functions.}
22 |
23 | \item{disruption_status}{Character. Can be used to filter to either "current"
24 | or "planned" disruptions. Defaults to NULL, in which case no filter is
25 | applied.}
26 |
27 | \item{user_id}{Integer or character. A user ID or devid provided by Public
28 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
29 |
30 | \item{api_key}{Character. An API key, with dashes, provided by Public
31 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
32 | }
33 | \value{
34 | A tibble with the following columns: \itemize{
35 | \item \code{disruption_mode}
36 | \item \code{disruption_mode_description}
37 | \item \code{disruption_id}
38 | \item \code{title}
39 | \item \code{url}
40 | \item \code{description}
41 | \item \code{disruption_status}
42 | \item \code{disruption_type}
43 | \item \code{published_on}
44 | \item \code{last_updated}
45 | \item \code{from_date}
46 | \item \code{to_date}
47 | \item \code{routes}
48 | \item \code{stops}
49 | \item \code{colour}
50 | \item \code{display_on_board}
51 | \item \code{display_status}
52 | }
53 | }
54 | \description{
55 | Disruptions on a given route
56 | }
57 | \examples{
58 | \dontrun{
59 | disruptions_on_route(6)
60 | disruptions_on_route(6, stop_id = 1071)
61 | disruptions_on_route(6, disruption_status = "current")
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/docs/bootstrap-toc.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/)
3 | * Copyright 2015 Aidan Feldman
4 | * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */
5 |
6 | /* modified from https://github.com/twbs/bootstrap/blob/94b4076dd2efba9af71f0b18d4ee4b163aa9e0dd/docs/assets/css/src/docs.css#L548-L601 */
7 |
8 | /* All levels of nav */
9 | nav[data-toggle='toc'] .nav > li > a {
10 | display: block;
11 | padding: 4px 20px;
12 | font-size: 13px;
13 | font-weight: 500;
14 | color: #767676;
15 | }
16 | nav[data-toggle='toc'] .nav > li > a:hover,
17 | nav[data-toggle='toc'] .nav > li > a:focus {
18 | padding-left: 19px;
19 | color: #563d7c;
20 | text-decoration: none;
21 | background-color: transparent;
22 | border-left: 1px solid #563d7c;
23 | }
24 | nav[data-toggle='toc'] .nav > .active > a,
25 | nav[data-toggle='toc'] .nav > .active:hover > a,
26 | nav[data-toggle='toc'] .nav > .active:focus > a {
27 | padding-left: 18px;
28 | font-weight: bold;
29 | color: #563d7c;
30 | background-color: transparent;
31 | border-left: 2px solid #563d7c;
32 | }
33 |
34 | /* Nav: second level (shown on .active) */
35 | nav[data-toggle='toc'] .nav .nav {
36 | display: none; /* Hide by default, but at >768px, show it */
37 | padding-bottom: 10px;
38 | }
39 | nav[data-toggle='toc'] .nav .nav > li > a {
40 | padding-top: 1px;
41 | padding-bottom: 1px;
42 | padding-left: 30px;
43 | font-size: 12px;
44 | font-weight: normal;
45 | }
46 | nav[data-toggle='toc'] .nav .nav > li > a:hover,
47 | nav[data-toggle='toc'] .nav .nav > li > a:focus {
48 | padding-left: 29px;
49 | }
50 | nav[data-toggle='toc'] .nav .nav > .active > a,
51 | nav[data-toggle='toc'] .nav .nav > .active:hover > a,
52 | nav[data-toggle='toc'] .nav .nav > .active:focus > a {
53 | padding-left: 28px;
54 | font-weight: 500;
55 | }
56 |
57 | /* from https://github.com/twbs/bootstrap/blob/e38f066d8c203c3e032da0ff23cd2d6098ee2dd6/docs/assets/css/src/docs.css#L631-L634 */
58 | nav[data-toggle='toc'] .nav > .active > ul {
59 | display: block;
60 | }
61 |
--------------------------------------------------------------------------------
/.github/workflows/R-CMD-check.yaml:
--------------------------------------------------------------------------------
1 | # Workflow derived from https://github.com/r-lib/actions/tree/master/examples
2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
3 | on:
4 | push:
5 | branches: [main, master]
6 | pull_request:
7 | branches: [main, master]
8 |
9 | name: R-CMD-check
10 |
11 | jobs:
12 | R-CMD-check:
13 | runs-on: ubuntu-latest
14 | env:
15 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
16 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
17 | R_KEEP_PKG_SOURCE: yes
18 | NOT_CRAN: true
19 | PTV_USER_ID: ${{ secrets.PTV_USER_ID }}
20 | PTV_API_KEY: ${{ secrets.PTV_API_KEY }}
21 | steps:
22 | - uses: actions/checkout@v3
23 |
24 | - uses: r-lib/actions/setup-r@v2
25 | with:
26 | use-public-rspm: true
27 |
28 | - uses: r-lib/actions/setup-r-dependencies@v2
29 | with:
30 | extra-packages: |
31 | rcmdcheck
32 | pkgdown
33 | covr
34 | lintr
35 |
36 | - uses: r-lib/actions/check-r-package@v2
37 |
38 | - name: Show testthat output
39 | if: always()
40 | run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true
41 | shell: bash
42 |
43 | - name: Upload check results
44 | if: failure()
45 | uses: actions/upload-artifact@main
46 | with:
47 | name: ${{ runner.os }}-r${{ matrix.config.r }}-results
48 | path: check
49 |
50 | - name: Install package
51 | run: R CMD INSTALL .
52 |
53 | - name: Lint
54 | run: |
55 | lintr::lint_package()
56 | shell: Rscript {0}
57 |
58 | - name: Test coverage
59 | run: |
60 | covr::codecov()
61 | shell: Rscript {0}
62 |
63 | - name: Deploy pkgdown docs
64 | run: |
65 | git config --local user.name "$GITHUB_ACTOR"
66 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com"
67 | Rscript -e 'pkgdown::deploy_to_branch(branch = "gh-pages", new_process = FALSE)'
68 |
--------------------------------------------------------------------------------
/man/runs_on_route.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/runs.R
3 | \name{runs_on_route}
4 | \alias{runs_on_route}
5 | \title{Runs on a given route}
6 | \usage{
7 | runs_on_route(
8 | route_id,
9 | route_type = NULL,
10 | date_utc = NULL,
11 | user_id = determine_user_id(),
12 | api_key = determine_api_key()
13 | )
14 | }
15 | \arguments{
16 | \item{route_id}{Integer. These can be listed and described with the
17 | \code{\link{routes}} function.}
18 |
19 | \item{route_type}{Optionally filter results by a route type. A route type can
20 | be provided either as a non-negative integer code, or as a character:
21 | "Tram", "Train", "Bus", "Vline" or "Night Bus". Character inputs are not
22 | case-sensitive. Use the \code{\link{route_types}} function to extract a
23 | vector of all route types.}
24 |
25 | \item{date_utc}{Date, or character that can be converted to a date. The
26 | UTC date for which the results are effective. Defaults to the current date.
27 | It's uncertain how much historical or future-dated data is available. This
28 | argument is experimental and seems to not be functioning.}
29 |
30 | \item{user_id}{Integer or character. A user ID or devid provided by Public
31 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
32 |
33 | \item{api_key}{Character. An API key, with dashes, provided by Public
34 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
35 | }
36 | \value{
37 | A tibble with the following columns: \itemize{
38 | \item \code{run_id} (deprecated, use \code{run_ref} instead)
39 | \item \code{run_ref}
40 | \item \code{route_id}
41 | \item \code{route_type}
42 | \item \code{route_type_description}
43 | \item \code{direction_id}
44 | \item \code{run_sequence}
45 | \item \code{final_stop_id}
46 | \item \code{destination_name}
47 | \item \code{status}
48 | \item \code{express_stop_count}
49 | \item \code{vehicle_position}
50 | \item \code{vehicle_descriptor}
51 | \item \code{geopath}
52 | }
53 | }
54 | \description{
55 | Runs on a given route
56 | }
57 | \examples{
58 | \dontrun{
59 | runs_on_route(6)
60 | runs_on_route(6, route_type = "Train")
61 | runs_on_route(6, route_type = 0)
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/man/ptvapi-package.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/ptvapi.R
3 | \docType{package}
4 | \name{ptvapi-package}
5 | \alias{ptvapi}
6 | \alias{ptvapi-package}
7 | \title{ptvapi: A package for accessing the Public Transport Victoria Timetable API}
8 | \description{
9 | \if{html}{\figure{ptvapi.png}{options: alt='logo'}}
10 |
11 | Accessing the Public Transport Victoria Timetable API reqiures a user ID
12 | (also called a \code{devid}) and an API key. These can be accessed by contacting
13 | Public Transport Victoria. See
14 | \url{https://www.ptv.vic.gov.au/footer/data-and-reporting/datasets/ptv-timetable-api/}
15 |
16 | The user ID and API key can be entered directly into all functions.
17 | Alternatively, all functions will pick up on the PTV_USER_ID and API_KEY
18 | environment variables, if defined.
19 |
20 | All API requests use SSL by default. To disable this, and to use the \code{http}
21 | API endpoints rather than the \code{https} API endpoints, set the option:
22 |
23 | \if{html}{\out{
}}
25 | }
26 | \details{
27 | This is an unofficial wrapper of the Public Transport Victoria Timetable API.
28 | The author(s) of this package are unaffiliated with Public Transport
29 | Victoria.
30 | }
31 | \examples{
32 | \dontrun{
33 | # tibble of all routes
34 | routes()
35 |
36 | # Search for routes by name (case insensitive, partial matching supported)
37 | routes(route_name = "Frankston")
38 |
39 | # All current disruptions
40 | disruptions(disruption_status = "current")
41 |
42 | # Train stops near Flinders Street Station
43 | stops_nearby(
44 | latitude = -37.8183,
45 | longitude = 144.9671,
46 | route_types = "Train"
47 | )
48 |
49 | # Upcoming train departures from Flinders Street Station
50 | departures(stop_id = 1071, route_type = "Train")
51 | }
52 | }
53 | \seealso{
54 | Useful links:
55 | \itemize{
56 | \item \url{https://github.com/mdneuzerling/ptvapi}
57 | \item Report bugs at \url{https://github.com/mdneuzerling/ptvapi/issues}
58 | }
59 |
60 | }
61 | \author{
62 | \strong{Maintainer}: David Neuzerling \email{david@neuzerling.com} [copyright holder]
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/tests/testthat/test-search-outlets.R:
--------------------------------------------------------------------------------
1 | if (identical(Sys.getenv("NOT_CRAN"), "true")) {
2 |
3 | # Recall that "outlet_name" is more accurately outlet address
4 |
5 | st_kilda <- search_outlets("st kilda")
6 | seven_eleven <- search_outlets("7-Eleven")
7 |
8 | test_that("search_outlets result has class \"ptvapi\"", {
9 | expect_s3_class(st_kilda, "ptvapi")
10 | })
11 |
12 | test_that("results in search_outlets can relate to outlet name alone", {
13 | expect_gte(
14 | nrow(
15 | dplyr::filter(
16 | st_kilda,
17 | grepl("st kilda", outlet_name, ignore.case = TRUE),
18 | !grepl("st kilda", outlet_business, ignore.case = TRUE),
19 | !grepl("st kilda", outlet_suburb, ignore.case = TRUE)
20 | )
21 | ),
22 | 1
23 | )
24 | })
25 |
26 | test_that("results in search_outlets can relate to outlet business alone", {
27 | expect_gte(nrow(seven_eleven), 1)
28 | })
29 |
30 | test_that("results in search_outlets can relate to outlet suburb alone", {
31 | expect_gte(
32 | nrow(
33 | dplyr::filter(
34 | st_kilda,
35 | !grepl("st kilda", outlet_name, ignore.case = TRUE),
36 | !grepl("st kilda", outlet_business, ignore.case = TRUE),
37 | grepl("st kilda", outlet_suburb, ignore.case = TRUE)
38 | )
39 | ),
40 | 1
41 | )
42 | })
43 |
44 | test_that("all results in search_outlets relate to search term somehow", {
45 | expect_equal(
46 | nrow(
47 | dplyr::filter(
48 | st_kilda,
49 | !grepl("st kilda", outlet_name, ignore.case = TRUE),
50 | !grepl("st kilda", outlet_business, ignore.case = TRUE),
51 | !grepl("st kilda", outlet_suburb, ignore.case = TRUE)
52 | )
53 | ),
54 | 0
55 | )
56 | })
57 |
58 | test_that("search_outlets filtered by max_distance", {
59 | # We'll try to find a 7-Eleven between 500 and 1000 metres from Southern Cross
60 | max_500 <- search_outlets(
61 | "7-Eleven",
62 | latitude = -37.818229,
63 | longitude = 144.952404,
64 | max_distance = 500
65 | )
66 | max_1000 <- search_outlets(
67 | "7-Eleven",
68 | latitude = -37.818229,
69 | longitude = 144.952404,
70 | max_distance = 1000
71 | )
72 | difference <- max_1000 %>% anti_join(max_500, by = "outlet_name")
73 | expect_gt(nrow(difference), 0)
74 | })
75 | }
76 |
--------------------------------------------------------------------------------
/man/stops_nearby.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/stops.R
3 | \name{stops_nearby}
4 | \alias{stops_nearby}
5 | \title{Stops near a given location}
6 | \usage{
7 | stops_nearby(
8 | latitude,
9 | longitude,
10 | max_distance = NULL,
11 | route_types = NULL,
12 | user_id = determine_user_id(),
13 | api_key = determine_api_key()
14 | )
15 | }
16 | \arguments{
17 | \item{latitude}{Numeric. Latitude in decimal degrees. For example, Flinders
18 | Street Station is at approximately -37.8183 latitude.}
19 |
20 | \item{longitude}{Numeric. Longitude in decimal degrees. For example, Flinders
21 | Street Station is at approximately 144.9671 longitude.}
22 |
23 | \item{max_distance}{Integer. Optionally filter by maximum distance from the
24 | given location, in metres.}
25 |
26 | \item{route_types}{Integer or character vector. Optionally filter by a vector
27 | of route types. A route type can be provided either as a non-negative
28 | integer code, or as a character: "Tram", "Train", "Bus", "Vline" or "Night
29 | Bus". Character inputs are not case-sensitive. Use the
30 | \code{\link{route_types}} function to extract a vector of all route types.}
31 |
32 | \item{user_id}{Integer or character. A user ID or devid provided by Public
33 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
34 |
35 | \item{api_key}{Character. An API key, with dashes, provided by Public
36 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
37 | }
38 | \value{
39 | A tibble with the following columns: \itemize{
40 | \item{\code{stop_id}}
41 | \item{\code{stop_name}}
42 | \item{\code{stop_suburb}}
43 | \item{\code{route_type}}
44 | \item{\code{route_type_description}}
45 | \item{\code{stop_sequence}}
46 | \item{\code{stop_latitude}}
47 | \item{\code{stop_longitude}}
48 | \item{\code{disruption_ids}}
49 | }
50 | }
51 | \description{
52 | Stops near a given location
53 | }
54 | \examples{
55 | \dontrun{
56 | stops_nearby(latitude = -37.8183, longitude = 144.9671)
57 | stops_nearby(latitude = -37.8183, longitude = 144.9671, max_distance = 1000)
58 | stops_nearby(
59 | latitude = -37.8183,
60 | longitude = 144.9671,
61 | route_types = c("Train", "Tram")
62 | )
63 |
64 | stops_nearby(
65 | latitude = -37.8183,
66 | longitude = 144.9671,
67 | route_types = 0
68 | )
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/.github/workflows/full-check-on-release.yaml:
--------------------------------------------------------------------------------
1 | # Workflow derived from https://github.com/r-lib/actions/tree/master/examples
2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
3 | #
4 | # NOTE: This workflow is overkill for most R packages and
5 | # check-standard.yaml is likely a better choice.
6 | # usethis::use_github_action("check-standard") will install it.
7 | on:
8 | release:
9 | types:
10 | - created
11 |
12 | name: R-CMD-check
13 |
14 | jobs:
15 | R-CMD-check:
16 | runs-on: ${{ matrix.config.os }}
17 |
18 | name: ${{ matrix.config.os }} (${{ matrix.config.r }})
19 |
20 | strategy:
21 | fail-fast: false
22 | matrix:
23 | config:
24 | - {os: macOS-latest, r: 'release'}
25 |
26 | - {os: windows-latest, r: 'release'}
27 | # Use 3.6 to trigger usage of RTools35
28 | - {os: windows-latest, r: '3.6'}
29 |
30 | # Use older ubuntu to maximise backward compatibility
31 | - {os: ubuntu-20.04, r: 'devel', http-user-agent: 'release'}
32 | - {os: ubuntu-20.04, r: 'release'}
33 | - {os: ubuntu-20.04, r: 'oldrel-1'}
34 | - {os: ubuntu-20.04, r: 'oldrel-2'}
35 | - {os: ubuntu-20.04, r: 'oldrel-3'}
36 | - {os: ubuntu-20.04, r: 'oldrel-4'}
37 |
38 | env:
39 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
40 | R_KEEP_PKG_SOURCE: yes
41 |
42 | steps:
43 | - uses: actions/checkout@v3
44 |
45 | - uses: r-lib/actions/setup-pandoc@v2
46 |
47 | - uses: r-lib/actions/setup-r@v2
48 | with:
49 | r-version: ${{ matrix.config.r }}
50 | http-user-agent: ${{ matrix.config.http-user-agent }}
51 | use-public-rspm: true
52 |
53 | - uses: r-lib/actions/setup-r-dependencies@v2
54 | with:
55 | extra-packages: rcmdcheck
56 |
57 | - uses: r-lib/actions/check-r-package@v2
58 |
59 | - name: Show testthat output
60 | if: always()
61 | run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true
62 | shell: bash
63 |
64 | - name: Upload check results
65 | if: failure()
66 | uses: actions/upload-artifact@main
67 | with:
68 | name: ${{ runner.os }}-r${{ matrix.config.r }}-results
69 | path: check
70 |
--------------------------------------------------------------------------------
/docs/docsearch.js:
--------------------------------------------------------------------------------
1 | $(function() {
2 |
3 | // register a handler to move the focus to the search bar
4 | // upon pressing shift + "/" (i.e. "?")
5 | $(document).on('keydown', function(e) {
6 | if (e.shiftKey && e.keyCode == 191) {
7 | e.preventDefault();
8 | $("#search-input").focus();
9 | }
10 | });
11 |
12 | $(document).ready(function() {
13 | // do keyword highlighting
14 | /* modified from https://jsfiddle.net/julmot/bL6bb5oo/ */
15 | var mark = function() {
16 |
17 | var referrer = document.URL ;
18 | var paramKey = "q" ;
19 |
20 | if (referrer.indexOf("?") !== -1) {
21 | var qs = referrer.substr(referrer.indexOf('?') + 1);
22 | var qs_noanchor = qs.split('#')[0];
23 | var qsa = qs_noanchor.split('&');
24 | var keyword = "";
25 |
26 | for (var i = 0; i < qsa.length; i++) {
27 | var currentParam = qsa[i].split('=');
28 |
29 | if (currentParam.length !== 2) {
30 | continue;
31 | }
32 |
33 | if (currentParam[0] == paramKey) {
34 | keyword = decodeURIComponent(currentParam[1].replace(/\+/g, "%20"));
35 | }
36 | }
37 |
38 | if (keyword !== "") {
39 | $(".contents").unmark({
40 | done: function() {
41 | $(".contents").mark(keyword);
42 | }
43 | });
44 | }
45 | }
46 | };
47 |
48 | mark();
49 | });
50 | });
51 |
52 | /* Search term highlighting ------------------------------*/
53 |
54 | function matchedWords(hit) {
55 | var words = [];
56 |
57 | var hierarchy = hit._highlightResult.hierarchy;
58 | // loop to fetch from lvl0, lvl1, etc.
59 | for (var idx in hierarchy) {
60 | words = words.concat(hierarchy[idx].matchedWords);
61 | }
62 |
63 | var content = hit._highlightResult.content;
64 | if (content) {
65 | words = words.concat(content.matchedWords);
66 | }
67 |
68 | // return unique words
69 | var words_uniq = [...new Set(words)];
70 | return words_uniq;
71 | }
72 |
73 | function updateHitURL(hit) {
74 |
75 | var words = matchedWords(hit);
76 | var url = "";
77 |
78 | if (hit.anchor) {
79 | url = hit.url_without_anchor + '?q=' + escape(words.join(" ")) + '#' + hit.anchor;
80 | } else {
81 | url = hit.url + '?q=' + escape(words.join(" "));
82 | }
83 |
84 | return url;
85 | }
86 |
--------------------------------------------------------------------------------
/man/add_parameters.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/url-manipulation.R
3 | \name{add_parameters}
4 | \alias{add_parameters}
5 | \title{Suffix one or many parameters to a HTML request}
6 | \usage{
7 | add_parameters(request, ..., .combine = "repeat_name")
8 | }
9 | \arguments{
10 | \item{request}{Character. The base URL or request which will be suffixed with
11 | the parameter.}
12 |
13 | \item{...}{The parameters to be suffixed, with name/value pairs provided as
14 | arguments.}
15 |
16 | \item{.combine}{How to combine parameters with multiple values. One
17 | of "repeat_name", "with_commas", "with_hex_commas". See Details.}
18 | }
19 | \value{
20 | Character. The request with suffixed parameters.
21 | }
22 | \description{
23 | Parameters are suffixed to a URL, like so:
24 | "request?para1=value1¶2=value2". The first parameter is suffixed with "?"
25 | and all others after that "&". This function will determine the correct
26 | suffix based on the presence of "&" in the request.
27 | }
28 | \details{
29 | There is no standardised way to combine multiple values for a
30 | parameter. You should see how your API expects multiple values to be provided
31 | to the same parameter. This function allows for the following strategies. If
32 | any other value is provided, then the values will be concatenated and
33 | separated with the provided value.
34 | \itemize{
35 | \item "repeat_name" (default). The values of the parameter are repeated
36 | with the parameter name. For example, \verb{("request", "para", c(1, 2))} will
37 | return "request?para=1¶=2".
38 | \item "with_commas". The values of the parameter are concatenated and
39 | separated with commas. For example, \verb{("request", "para", c(1, 2))} will
40 | return "request?para=1,2".
41 | \item "with_commas". The values of the parameter are concatenated and
42 | separated with the ASCII keycode in hexadecimal for a comma ("\%2C"). For
43 | example, \verb{("request", "para", c(1, 2))} will return "request?para=1\%2C2".
44 | }
45 | }
46 | \examples{
47 | \dontrun{
48 | ptvapi:::add_parameters("www.example.com", animal = crocodile)
49 | ptvapi:::add_parameters(
50 | "www.example.com",
51 | animal = crocodile,
52 | food = "cherries"
53 | )
54 | ptvapi:::add_parameters(
55 | "www.example.com",
56 | animal = crocodile,
57 | numbers = c(1, 2, 3),
58 | .combine = "repeat_names"
59 | )}
60 |
61 | }
62 | \keyword{internal}
63 |
--------------------------------------------------------------------------------
/man/add_parameter.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/url-manipulation.R
3 | \name{add_parameter}
4 | \alias{add_parameter}
5 | \title{Suffix a parameter to a HTML request}
6 | \usage{
7 | add_parameter(
8 | request,
9 | parameter_name,
10 | parameter_value,
11 | .combine = "repeat_name"
12 | )
13 | }
14 | \arguments{
15 | \item{request}{Character. The base URL or request which will be suffixed with
16 | the parameter.}
17 |
18 | \item{parameter_name}{Character. Name of parameter to suffix.}
19 |
20 | \item{parameter_value}{Character, or a value that can be coerced to a
21 | character. The value of the parameter to suffix.}
22 |
23 | \item{.combine}{How to combine parameters with multiple values. One
24 | of "repeat_name", "with_commas", "with_hex_commas". See Details.}
25 | }
26 | \value{
27 | Character. The request with suffixed parameters.
28 | }
29 | \description{
30 | Parameters are suffixed to a URL, like so:
31 | "request?para1=value1¶2=value2". The first parameter is suffixed with "?"
32 | and all others after that "&". This function will determine the correct
33 | suffix based on the presence of "&" in the request.
34 | }
35 | \details{
36 | There is no standardised way to combine multiple values for a
37 | parameter. You should see how your API expects multiple values to be provided
38 | to the same parameter. This function allows for the following strategies. If
39 | any other value is provided, then the values will be concatenated and
40 | separated with the provided value.
41 | \itemize{
42 | \item "repeat_name" (default). The values of the parameter are repeated
43 | with the parameter name. For example, \verb{("request", "para", c(1, 2))} will
44 | return "request?para=1¶=2".
45 | \item "with_commas". The values of the parameter are concatenated and
46 | separated with commas. For example, \verb{("request", "para", c(1, 2))} will
47 | return "request?para=1,2".
48 | \item "with_commas". The values of the parameter are concatenated and
49 | separated with the ASCII keycode in hexadecimal for a comma ("\%2C"). For
50 | example, \verb{("request", "para", c(1, 2))} will return "request?para=1\%2C2".
51 | }
52 | }
53 | \examples{
54 | \dontrun{
55 | ptvapi:::add_parameter("www.example.com", "animal", "crocodile")
56 | ptvapi:::add_parameter(
57 | "www.example.com",
58 | "numbers",
59 | c(1, 2, 3),
60 | .combine = "repeat_names"
61 | )}
62 |
63 | }
64 | \keyword{internal}
65 |
--------------------------------------------------------------------------------
/tests/testthat/test-routes.R:
--------------------------------------------------------------------------------
1 | if (identical(Sys.getenv("NOT_CRAN"), "true")) {
2 | # ---------------------------------------------------------------------------- #
3 | # ---- Define values if they haven't already been defined by another test ---- #
4 | # ---------------------------------------------------------------------------- #
5 | if (!exists("all_routes")) {
6 | all_routes <- routes()
7 | }
8 |
9 | if (!exists("frankston_route_id")) {
10 | frankston_route_id <- all_routes %>%
11 | dplyr::filter(route_name == "Frankston") %>%
12 | pull(route_id)
13 | }
14 | # ---------------------------------------------------------------------------- #
15 |
16 | single_route <- route_information(route_id = frankston_route_id)
17 |
18 | test_that("route_information() result has class \"ptvapi\"", {
19 | expect_s3_class(single_route, "ptvapi")
20 | })
21 |
22 | test_that("we can query for a single route", {
23 | expect_equal(nrow(single_route), 1)
24 | })
25 |
26 | test_that("routes() result has class \"ptvapi\"", {
27 | expect_s3_class(all_routes, "ptvapi")
28 | })
29 |
30 | test_that("we can find the Frankston train route", {
31 | expect_true(length(frankston_route_id) == 1)
32 | })
33 |
34 | test_that("we can filter route results by name", {
35 | frankston_named_routes <- routes(route_name = "frank")
36 | expect_gt(nrow(frankston_named_routes), 0)
37 | expect_equal(
38 | nrow(dplyr::filter(
39 | frankston_named_routes,
40 | !grepl("frank", route_name, ignore.case = TRUE)
41 | )),
42 | 0
43 | )
44 | })
45 |
46 | test_that("86 tram route can be found", {
47 | eighty_six_tram_routes <- all_routes %>%
48 | dplyr::filter(
49 | route_number == 86,
50 | route_type == translate_route_type("Tram")
51 | )
52 | expect_equal(nrow(eighty_six_tram_routes), 1)
53 | })
54 |
55 | test_that("we can filter routes by multiple route types", {
56 | expect_equal(
57 | routes(route_types = c(0, 1)) %>% pull(route_type) %>% unique %>% sort,
58 | c(0, 1)
59 | )
60 | })
61 |
62 | test_that("we can download geopath data with specific route", {
63 | frankston_with_geo <- route_information(
64 | frankston_route_id,
65 | include_geopath = TRUE
66 | )
67 | geopaths <- frankston_with_geo$geopath[[1]]
68 | # One path for each of the two directions
69 | expect_gte(nrow(geopaths), 1)
70 | expect_identical(
71 | names(geopaths),
72 | c("direction_id", "valid_from", "valid_to", "paths")
73 | )
74 | })
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/man/disruptions.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/disruptions.R
3 | \name{disruptions}
4 | \alias{disruptions}
5 | \title{Information for all disruptions}
6 | \usage{
7 | disruptions(
8 | route_types = NULL,
9 | disruption_modes = NULL,
10 | disruption_status = NULL,
11 | user_id = determine_user_id(),
12 | api_key = determine_api_key()
13 | )
14 | }
15 | \arguments{
16 | \item{route_types}{Integer or character vector. Optionally filter by a vector
17 | of route types. A route type can be provided either as a non-negative
18 | integer code, or as a character: "Tram", "Train", "Bus", "Vline" or "Night
19 | Bus". Character inputs are not case-sensitive. Use the
20 | \code{\link{route_types}} function to extract a vector of all route types.
21 | The filter is applied to the disruption mode, rather than the routes that
22 | are affected by the disruption. For example, filtering by the "train" route
23 | type will restrict the disruptions returned to those with a mode
24 | corresponding to "metro_train".}
25 |
26 | \item{disruption_modes}{Integer vector. Optionally filter by disruption
27 | modes. For a full list of modes and their corresponding descriptions, use
28 | the \code{disruptions_modes} function.}
29 |
30 | \item{disruption_status}{Character. Can be used to filter to either "current"
31 | or "planned" disruptions. Defaults to NULL, in which case no filter is
32 | applied.}
33 |
34 | \item{user_id}{Integer or character. A user ID or devid provided by Public
35 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
36 |
37 | \item{api_key}{Character. An API key, with dashes, provided by Public
38 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
39 | }
40 | \value{
41 | A tibble with the following columns: \itemize{
42 | \item \code{disruption_mode}
43 | \item \code{disruption_mode_description}
44 | \item \code{disruption_id}
45 | \item \code{title}
46 | \item \code{url}
47 | \item \code{description}
48 | \item \code{disruption_status}
49 | \item \code{disruption_type}
50 | \item \code{published_on}
51 | \item \code{last_updated}
52 | \item \code{from_date}
53 | \item \code{to_date}
54 | \item \code{routes}
55 | \item \code{stops}
56 | \item \code{colour}
57 | \item \code{display_on_board}
58 | \item \code{display_status}
59 | }
60 | }
61 | \description{
62 | Information for all disruptions
63 | }
64 | \examples{
65 | \dontrun{
66 | disruptions()
67 | disruptions(route_types = c("Train", "Tram"))
68 | disruptions(disruption_modes = c(0, 1))
69 | disruptions(disruption_status = "current")
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/man/search_stops.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/search.R
3 | \name{search_stops}
4 | \alias{search_stops}
5 | \title{Search for stops using text}
6 | \usage{
7 | search_stops(
8 | search_term,
9 | latitude = NULL,
10 | longitude = NULL,
11 | max_distance = NULL,
12 | route_types = NULL,
13 | user_id = determine_user_id(),
14 | api_key = determine_api_key()
15 | )
16 | }
17 | \arguments{
18 | \item{search_term}{Character. Term used to perform search.}
19 |
20 | \item{latitude}{Numeric. Latitude in decimal degrees. For example, Flinders
21 | Street Station is at approximately -37.8183 latitude.}
22 |
23 | \item{longitude}{Numeric. Longitude in decimal degrees. For example, Flinders
24 | Street Station is at approximately 144.9671 longitude.}
25 |
26 | \item{max_distance}{Integer. Optionally filter by maximum distance from the
27 | given location, in metres.}
28 |
29 | \item{route_types}{Integer or character vector. Optionally filter by a vector
30 | of route types. A route type can be provided either as a non-negative
31 | integer code, or as a character: "Tram", "Train", "Bus", "Vline" or "Night
32 | Bus". Character inputs are not case-sensitive. Use the
33 | \code{\link{route_types}} function to extract a vector of all route types.}
34 |
35 | \item{user_id}{Integer or character. A user ID or devid provided by Public
36 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
37 |
38 | \item{api_key}{Character. An API key, with dashes, provided by Public
39 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
40 | }
41 | \value{
42 | A tibble with the following columns: \itemize{
43 | \item{\code{stop_id}}
44 | \item{\code{stop_name}}
45 | \item{\code{stop_suburb}}
46 | \item{\code{route_type}}
47 | \item{\code{route_type_description}}
48 | \item{\code{stop_sequence}}
49 | \item{\code{stop_latitude}}
50 | \item{\code{stop_longitude}}
51 | \item{\code{disruption_ids}}
52 | }
53 | }
54 | \description{
55 | This function will search stops in which the search term can be found in
56 | either the stop name or the stop suburb. The search is case-insensitive.
57 | The search term must contain at least 3 characters, and cannot be numeric.
58 | }
59 | \examples{
60 | \dontrun{
61 | search_stops("Ascot Vale")
62 | search_stops("Ascot Vale", route_types = c("Train", "Tram"))
63 | search_stops("Ascot Vale", route_types = 1)
64 |
65 | search_stops(
66 | "Ascot Vale",
67 | latitude = -37.774240,
68 | longitude = 144.915518
69 | )
70 | search_stops(
71 | "Ascot Vale",
72 | latitude = -37.774240,
73 | longitude = 144.915518,
74 | max_distance = 100
75 | )
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/man/outlets_nearby.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/outlets.R
3 | \name{outlets_nearby}
4 | \alias{outlets_nearby}
5 | \title{Information for outlets near a given location}
6 | \usage{
7 | outlets_nearby(
8 | latitude,
9 | longitude,
10 | max_distance = NULL,
11 | max_results = 30,
12 | user_id = determine_user_id(),
13 | api_key = determine_api_key()
14 | )
15 | }
16 | \arguments{
17 | \item{latitude}{Numeric. Latitude in decimal degrees. For example, Flinders
18 | Street Station is at approximately -37.8183 latitude.}
19 |
20 | \item{longitude}{Numeric. Longitude in decimal degrees. For example, Flinders
21 | Street Station is at approximately 144.9671 longitude.}
22 |
23 | \item{max_distance}{Integer. Optionally filter by maximum distance from the
24 | given location, in metres.}
25 |
26 | \item{max_results}{Integer. Defaults to 30. Caps the number of results
27 | returned.}
28 |
29 | \item{user_id}{Integer or character. A user ID or devid provided by Public
30 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
31 |
32 | \item{api_key}{Character. An API key, with dashes, provided by Public
33 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
34 | }
35 | \value{
36 | A tibble with the following columns: \itemize{
37 | \item {\code{outlet_slid_spid}}
38 | \item {\code{outlet_name}}
39 | \item {\code{outlet_business}}
40 | \item {\code{outlet_latitude}}
41 | \item {\code{outlet_longitude}}
42 | \item {\code{outlet_suburb}}
43 | \item {\code{outlet_postcode}}
44 | \item {\code{outlet_business_hour_mon}}
45 | \item {\code{outlet_business_hour_tue}}
46 | \item {\code{outlet_business_hour_wed}}
47 | \item {\code{outlet_business_hour_thu}}
48 | \item {\code{outlet_business_hour_fri}}
49 | \item {\code{outlet_business_hour_sat}}
50 | \item {\code{outlet_business_hour_sun}}
51 | \item {\code{outlet_notes}}
52 | }
53 | }
54 | \description{
55 | Information for outlets near a given location
56 | }
57 | \details{
58 | The \code{outlet_name} reported here is more accurately described as an outlet
59 | \emph{address}. We keep the \code{outlet_name} column name as this is how the PTV API
60 | describes it.
61 |
62 | The business hours are reported as characters. Usually they take on
63 | a format of "8.00AM - 10.00PM", but there variants such as "7.30AM - 11.00AM
64 | and 1.30PM - 6.00PM". For days on which an outlet is closed, the opening
65 | hours are usually reported as "CLOSED", but can also be an empty character.
66 | Some opening hours are "24 Hours". These fields are also filled with missing
67 | values and empty characters.
68 | }
69 | \examples{
70 | \dontrun{
71 | outlets_nearby(latitude = -37.8183, longitude = 144.9671)
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/tests/testthat/test-directions.R:
--------------------------------------------------------------------------------
1 | if (identical(Sys.getenv("NOT_CRAN"), "true")) {
2 | # ---------------------------------------------------------------------------- #
3 | # ---- Define values if they haven't already been defined by another test ---- #
4 | # ---------------------------------------------------------------------------- #
5 | if (!exists("all_routes")) {
6 | all_routes <- routes()
7 | }
8 |
9 | if (!exists("frankston_route_id")) {
10 | frankston_route_id <- all_routes %>%
11 | dplyr::filter(route_name == "Frankston") %>%
12 | pull(route_id)
13 | }
14 | # ---------------------------------------------------------------------------- #
15 |
16 | # Testing directions_on_route
17 | # Simply running this functions asserts that column names, etc. are as expected
18 | frankston_directions_on_route <- directions_on_route(
19 | route_id = frankston_route_id
20 | )
21 | test_that("directions_on_route result has class \"ptvapi\"", {
22 | expect_s3_class(frankston_directions_on_route, "ptvapi")
23 | })
24 |
25 | city_directions <- frankston_directions_on_route %>%
26 | filter(grepl("City", direction_name, ignore.case = TRUE))
27 |
28 | test_that("Frankston train goes to the city", {
29 | expect_equal(nrow(city_directions), 1)
30 | })
31 |
32 | frankston_directions <- frankston_directions_on_route %>%
33 | filter(grepl("Frankston", direction_name, ignore.case = TRUE))
34 |
35 | test_that("Frankston train goes to the city", {
36 | expect_equal(nrow(frankston_directions), 1)
37 | })
38 |
39 | # Testing directions
40 | # We'll run the Frankston -> City direction from above through the directions
41 | # function, and see if we can recover the Frankston route ID.
42 | if (!exists("frankston_route_id")) {
43 | frankston_route_id <- routes() %>%
44 | dplyr::filter(route_name == "Frankston") %>%
45 | pull(route_id)
46 | }
47 |
48 | recovering_frankston_direction <- city_directions$direction_id %>%
49 | directions(direction_id = .) %>%
50 | filter(
51 | grepl("Frankston", route_direction_description, ignore.case = TRUE),
52 | route_id == frankston_route_id
53 | )
54 |
55 | test_that("Frankston route can be recovered with directions call", {
56 | expect_equal(nrow(recovering_frankston_direction), 1)
57 | expect_equal(recovering_frankston_direction$route_id, frankston_route_id)
58 | })
59 |
60 |
61 | # Testing directions with route type
62 | test_that("Frankstain train directions are all train routes", {
63 | city_train_directions <- city_directions$direction_id %>%
64 | directions(direction_id = ., route_type = "Train")
65 | expect_true(
66 | all(
67 | grepl(
68 | "train",
69 | city_train_directions$route_direction_description,
70 | ignore.case = TRUE
71 | )
72 | )
73 | )
74 | })
75 | }
76 |
--------------------------------------------------------------------------------
/man/ptv_search.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/search.R
3 | \name{ptv_search}
4 | \alias{ptv_search}
5 | \title{Use a character term to search for routes, stops, and outlets.}
6 | \usage{
7 | ptv_search(
8 | search_term,
9 | latitude = NULL,
10 | longitude = NULL,
11 | max_distance = NULL,
12 | route_types = NULL,
13 | include_outlets = FALSE,
14 | match_stop_by_suburb = FALSE,
15 | match_route_by_suburb = FALSE,
16 | match_stop_by_gtfs_stop_id = FALSE,
17 | user_id = determine_user_id(),
18 | api_key = determine_api_key()
19 | )
20 | }
21 | \arguments{
22 | \item{search_term}{Character. Term used to perform search.}
23 |
24 | \item{latitude}{Numeric. Latitude in decimal degrees. For example, Flinders
25 | Street Station is at approximately -37.8183 latitude.}
26 |
27 | \item{longitude}{Numeric. Longitude in decimal degrees. For example, Flinders
28 | Street Station is at approximately 144.9671 longitude.}
29 |
30 | \item{max_distance}{Integer. Optionally filter by maximum distance from the
31 | given location, in metres.}
32 |
33 | \item{route_types}{Integer or character vector. Optionally filter by a vector
34 | of route types. A route type can be provided either as a non-negative
35 | integer code, or as a character: "Tram", "Train", "Bus", "Vline" or "Night
36 | Bus". Character inputs are not case-sensitive. Use the
37 | \code{\link{route_types}} function to extract a vector of all route types.}
38 |
39 | \item{include_outlets}{Boolean. Optional. Affects search results.}
40 |
41 | \item{match_stop_by_suburb}{Boolean. Optional. Affects search results.}
42 |
43 | \item{match_route_by_suburb}{Boolean. Optional. Affects search results.}
44 |
45 | \item{match_stop_by_gtfs_stop_id}{Boolean. Optional. Affects search results.}
46 |
47 | \item{user_id}{Integer or character. A user ID or devid provided by Public
48 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
49 |
50 | \item{api_key}{Character. An API key, with dashes, provided by Public
51 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
52 | }
53 | \value{
54 | The response of the \code{search} API call.
55 | }
56 | \description{
57 | There's only one search API call, and it covers stops, routes, and outlets.
58 | This function will return the response of this generic search. It is
59 | expected that other functions will take on of these three categories of
60 | search results, parse them, and return them to the user as a tibble.
61 | }
62 | \details{
63 | If the search term is numeric and/or less than 3 characters, the API will
64 | return only routes. By default, as little matching is done as possible, and
65 | as little as possible is returned. We rely on functions that call on this
66 | function to specify what is needed.
67 | }
68 | \keyword{internal}
69 |
--------------------------------------------------------------------------------
/man/search_routes.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/search.R
3 | \name{search_routes}
4 | \alias{search_routes}
5 | \title{Search for routes using text}
6 | \usage{
7 | search_routes(
8 | search_term,
9 | latitude = NULL,
10 | longitude = NULL,
11 | max_distance = NULL,
12 | route_types = NULL,
13 | user_id = determine_user_id(),
14 | api_key = determine_api_key()
15 | )
16 | }
17 | \arguments{
18 | \item{search_term}{Character. Term used to perform search.}
19 |
20 | \item{latitude}{Numeric. Latitude in decimal degrees. For example, Flinders
21 | Street Station is at approximately -37.8183 latitude.}
22 |
23 | \item{longitude}{Numeric. Longitude in decimal degrees. For example, Flinders
24 | Street Station is at approximately 144.9671 longitude.}
25 |
26 | \item{max_distance}{Integer. Optionally filter by maximum distance from the
27 | given location, in metres.}
28 |
29 | \item{route_types}{Integer or character vector. Optionally filter by a vector
30 | of route types. A route type can be provided either as a non-negative
31 | integer code, or as a character: "Tram", "Train", "Bus", "Vline" or "Night
32 | Bus". Character inputs are not case-sensitive. Use the
33 | \code{\link{route_types}} function to extract a vector of all route types.}
34 |
35 | \item{user_id}{Integer or character. A user ID or devid provided by Public
36 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
37 |
38 | \item{api_key}{Character. An API key, with dashes, provided by Public
39 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
40 | }
41 | \value{
42 | A tibble of routes, with the following columns:
43 | \itemize{
44 | \item \code{route_id}
45 | \item \code{route_gtfs_id}
46 | \item \code{route_name}
47 | \item \code{route_type}
48 | \item \code{route_type_description}
49 | \item \code{route_number}
50 | \item \code{geopath}
51 | \item \code{service_status}
52 | \item \code{service_status_timestamp}
53 | }
54 | }
55 | \description{
56 | This function will search routes in which the search term can be found in
57 | one of many fields, such as \code{route_id}, \code{route_gtfs_id}, or \code{route_name}.
58 | The search is case-insensitive. Unlike \code{\link{search_stops}} and
59 | \code{\link{search_outlets}}, this function supports searching for numerics,
60 | and has no minimum character requirement for \code{search_term}.
61 | }
62 | \examples{
63 | \dontrun{
64 | search_routes("Pakenham")
65 | search_routes("Pakenham", route_types = c("Train", "Tram"))
66 | search_routes("Pakenham", route_types = 1)
67 |
68 | search_routes(
69 | "Pakenham",
70 | latitude = -38.077877,
71 | longitude = 145.484751
72 | )
73 | search_routes(
74 | "Pakenham",
75 | latitude = -38.077877,
76 | longitude = 145.484751,
77 | max_distance = 100
78 | )
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/man/search_outlets.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/search.R
3 | \name{search_outlets}
4 | \alias{search_outlets}
5 | \title{Search for outlets using text}
6 | \usage{
7 | search_outlets(
8 | search_term,
9 | latitude = NULL,
10 | longitude = NULL,
11 | max_distance = NULL,
12 | route_types = NULL,
13 | user_id = determine_user_id(),
14 | api_key = determine_api_key()
15 | )
16 | }
17 | \arguments{
18 | \item{search_term}{Character. Term used to perform search.}
19 |
20 | \item{latitude}{Numeric. Latitude in decimal degrees. For example, Flinders
21 | Street Station is at approximately -37.8183 latitude.}
22 |
23 | \item{longitude}{Numeric. Longitude in decimal degrees. For example, Flinders
24 | Street Station is at approximately 144.9671 longitude.}
25 |
26 | \item{max_distance}{Integer. Optionally filter by maximum distance from the
27 | given location, in metres.}
28 |
29 | \item{route_types}{Integer or character vector. Optionally filter by a vector
30 | of route types. A route type can be provided either as a non-negative
31 | integer code, or as a character: "Tram", "Train", "Bus", "Vline" or "Night
32 | Bus". Character inputs are not case-sensitive. Use the
33 | \code{\link{route_types}} function to extract a vector of all route types.}
34 |
35 | \item{user_id}{Integer or character. A user ID or devid provided by Public
36 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
37 |
38 | \item{api_key}{Character. An API key, with dashes, provided by Public
39 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
40 | }
41 | \value{
42 | A tibble with the following columns: \itemize{
43 | \item {\code{outlet_slid_spid}}
44 | \item {\code{outlet_name}}
45 | \item {\code{outlet_business}}
46 | \item {\code{outlet_latitude}}
47 | \item {\code{outlet_longitude}}
48 | \item {\code{outlet_suburb}}
49 | \item {\code{outlet_postcode}}
50 | \item {\code{outlet_business_hour_mon}}
51 | \item {\code{outlet_business_hour_tue}}
52 | \item {\code{outlet_business_hour_wed}}
53 | \item {\code{outlet_business_hour_thu}}
54 | \item {\code{outlet_business_hour_fri}}
55 | \item {\code{outlet_business_hour_sat}}
56 | \item {\code{outlet_business_hour_sun}}
57 | \item {\code{outlet_notes}}
58 | }
59 | }
60 | \description{
61 | This function will search outlets in which the search term can be found in
62 | either the outlet name, outlet business or outlet suburb. The search is
63 | case-insensitive. The search term must contain at least 3 characters, and
64 | cannot be numeric.
65 | }
66 | \examples{
67 | \dontrun{
68 | search_outlets("St Kilda")
69 | search_outlets("St Kilda", route_types = c("Train", "Tram"))
70 | search_outlets("St Kilda", route_types = 1)
71 |
72 | search_outlets(
73 | "St Kilda",
74 | latitude = -37.867647,
75 | longitude = 144.976809
76 | )
77 | search_outlets(
78 | "St Kilda",
79 | latitude = -37.867647,
80 | longitude = 144.976809,
81 | max_distance = 100
82 | )
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/man/fare_estimate.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/fare-estimate.R
3 | \name{fare_estimate}
4 | \alias{fare_estimate}
5 | \title{Calculate a fare estimate between zones}
6 | \usage{
7 | fare_estimate(
8 | min_zone,
9 | max_zone,
10 | journey_touch_on = NULL,
11 | journey_touch_off = NULL,
12 | journey_in_free_tram_zone = FALSE,
13 | travelled_route_types = NULL,
14 | user_id = determine_user_id(),
15 | api_key = determine_api_key()
16 | )
17 | }
18 | \arguments{
19 | \item{min_zone}{Integer. Minimum zone travelled through.}
20 |
21 | \item{max_zone}{Integer. Maximum zone travelled through.}
22 |
23 | \item{journey_touch_on, journey_touch_off}{POSIXct or Character. Optionally
24 | filter results to a journey time. Values to both must be provided.
25 | Characters are automatically converted to datetimes, and are assumed to be
26 | given as Melbourne time.}
27 |
28 | \item{journey_in_free_tram_zone}{Boolean. Defaults to \code{FALSE}.}
29 |
30 | \item{travelled_route_types}{Integer or character vector. Optionally filter
31 | by a vector of route types. A route type can be provided either as a
32 | non-negative integer code, or as a character: "Tram", "Train", "Bus",
33 | "Vline" or "Night Bus". Character inputs are not case-sensitive. Use the
34 | \code{\link{route_types}} function to extract a vector of all route types.}
35 |
36 | \item{user_id}{Integer or character. A user ID or devid provided by Public
37 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
38 |
39 | \item{api_key}{Character. An API key, with dashes, provided by Public
40 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
41 | }
42 | \value{
43 | A data frame consisting of one row for each \code{passenger_type}, and the
44 | following columns: \itemize{
45 | \item \code{min_zone}
46 | \item \code{max_zone}
47 | \item \code{unique_zones}
48 | \item \code{early_bird}
49 | \item \code{free_tram_zone}
50 | \item \code{weekend_journey}
51 | \item \code{passenger_type}
52 | \item \code{fare_2_hour_peak}
53 | \item \code{fare_2_hour_off_peak}
54 | \item \code{fare_daily_peak}
55 | \item \code{fare_daily_off_peak}
56 | \item \code{pass_7_days}
57 | \item \code{pass_28_to_69_day_per_day}
58 | \item \code{pass_70_plus_day_per_day}
59 | \item \code{weekend_cap}
60 | \item \code{holiday_cap}
61 | }
62 | }
63 | \description{
64 | Retrieve fare information for a journey through the given zones. Also
65 | supports journey touch on and off times, to accommodate for discounts.
66 | }
67 | \examples{
68 | \dontrun{
69 | fare_estimate(min_zone = 1, max_zone = 2)
70 |
71 | fare_estimate(min_zone = 1, max_zone = 1, journey_in_free_tram_zone = TRUE)
72 |
73 | fare_estimate(
74 | min_zone = 1,
75 | max_zone = 2,
76 | travelled_route_types = c("Train", "Tram")
77 | )
78 |
79 | fare_estimate(
80 | min_zone = 1,
81 | max_zone = 2,
82 | journey_touch_on = "2020-06-21 07:31:00",
83 | journey_touch_off = "2020-06-21 08:45:00"
84 | )
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/man/patterns.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/patterns.R
3 | \name{patterns}
4 | \alias{patterns}
5 | \title{Stopping pattern for a given run}
6 | \usage{
7 | patterns(
8 | run_ref,
9 | route_type,
10 | stop_id = NULL,
11 | departs = Sys.time(),
12 | user_id = determine_user_id(),
13 | api_key = determine_api_key()
14 | )
15 | }
16 | \arguments{
17 | \item{run_ref}{A character run reference. This supersedes the integer
18 | \code{run_id}. For backwards compatibility and since most run references are
19 | integers, this function will attempt to convert an the argument to a
20 | character. Run references may be retrieved from the
21 | \code{\link{departures}} or \code{\link{runs_on_route}} functions.}
22 |
23 | \item{route_type}{Optionally filter results by a route type. A route type can
24 | be provided either as a non-negative integer code, or as a character:
25 | "Tram", "Train", "Bus", "Vline" or "Night Bus". Character inputs are not
26 | case-sensitive. Use the \code{\link{route_types}} function to extract a
27 | vector of all route types.}
28 |
29 | \item{stop_id}{Integer. Optionally filter results to a specific stop ID.
30 | These can be searched for with the \code{stops_on_route} and \code{stops_nearby}
31 | functions.}
32 |
33 | \item{departs}{POSIXct or character. Optionally filter by date. See Details.
34 | Characters are automatically converted to departs, and are assumed to be
35 | given as Melbourne time. The behaviour of the API is unpredictable when
36 | using this argument --- see details. Defaults to the current system time.}
37 |
38 | \item{user_id}{Integer or character. A user ID or devid provided by Public
39 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
40 |
41 | \item{api_key}{Character. An API key, with dashes, provided by Public
42 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
43 | }
44 | \value{
45 | An object of class "ptvapi", which is effectively a list with the
46 | following names: \itemize{ \item \code{departures} \item \code{stops} \item \code{routes}
47 | \item \code{runs} \item \code{directions} \item \code{disruptions} }
48 | }
49 | \description{
50 | A pattern consists of all departures, stops, routes, runs, directions and
51 | disruptions associated with a particular run ID. This is returned as a list
52 | of tibbles, with output corresponding to their respective API calls.
53 | }
54 | \details{
55 | The \code{stops} tibble has an output similar to that returned by
56 | \code{\link{stops_on_route}}. The \code{routes} tibble does not contain service
57 | status information.
58 |
59 | Departures: The API seems to return the earliest 7 departures. While
60 | the PTV Timetable API supports filtering patterns by datetimes, the
61 | behaviour of this argument is not reliable --- it appears to filter by day
62 | only, returning the earliest 7 departures of a different day. It is
63 | recommended that departures are retrieved via the \code{\link{departures}}
64 | function.
65 | }
66 | \examples{
67 | \dontrun{
68 | patterns(run_ref = "1", route_type = 0)
69 | patterns(run_ref = "1", route_type = "Train")
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/tests/testthat/test-runs.R:
--------------------------------------------------------------------------------
1 | if (identical(Sys.getenv("NOT_CRAN"), "true")) {
2 | # ---------------------------------------------------------------------------- #
3 | # ---- Define values if they haven't already been defined by another test ---- #
4 | # ---------------------------------------------------------------------------- #
5 | if (!exists("all_routes")) {
6 | all_routes <- routes()
7 | }
8 |
9 | if (!exists("frankston_route_id")) {
10 | frankston_route_id <- all_routes %>%
11 | dplyr::filter(route_name == "Frankston") %>%
12 | pull(route_id)
13 | }
14 | # ---------------------------------------------------------------------------- #
15 |
16 | # We need to be careful how we design this test. If there are planned works and
17 | # the trains aren't running, then we can expect that there will be no runs on
18 | # the Frankston line. In this case, the test should trivially pass. The
19 | # assertions within the `runs_on_route` function will still be run, and these
20 | # act as tests of sorts.
21 |
22 | runs_on_frankston_route <- runs_on_route(
23 | route_id = frankston_route_id,
24 | route_type = "Train"
25 | )
26 |
27 | test_that("runs_on_route result has class \"ptvapi\"", {
28 | expect_s3_class(runs_on_frankston_route, "ptvapi")
29 | })
30 |
31 | test_that("Runs on the Frankston route end at stops on the Frankston route", {
32 | frankston_runs_destinations <- runs_on_frankston_route %>%
33 | pull(destination_name) %>%
34 | unique
35 | frankston_route_train_stops <- stops_on_route(
36 | route_id = frankston_route_id,
37 | route_type = "Train"
38 | ) %>%
39 | pull(stop_name)
40 | for (suburb in frankston_runs_destinations) {
41 | expect_true(any(grepl(!!suburb, frankston_route_train_stops)))
42 | }
43 | })
44 |
45 | run_one <- run_information(run_ref = "1")
46 |
47 | test_that("run_information() result has class \"ptvapi\"", {
48 | expect_s3_class(run_one, "ptvapi")
49 | })
50 |
51 | test_that("Run 1 exists, and is unique up to route type", {
52 | expect_gte(nrow(run_one), 1)
53 | expect_equal(anyDuplicated(run_one$route_type), 0)
54 | })
55 |
56 | run_one_train <- run_information(run_ref = "1", route_type = 0)
57 |
58 | test_that("run_information() with route type result has class \"ptvapi\"", {
59 | expect_s3_class(run_one_train, "ptvapi")
60 | })
61 |
62 | test_that("run_information() with route type returns exactly one row", {
63 | expect_equal(nrow(run_one_train), 1)
64 | })
65 |
66 | test_that("run_information can return geopath data", {
67 | run_one_with_geo <- run_information("1", include_geopath = TRUE)
68 | geopaths <- run_one_with_geo$geopath[[1]]
69 | expect_gte(nrow(geopaths), 1)
70 | expect_identical(
71 | names(geopaths),
72 | c("direction_id", "valid_from", "valid_to", "paths")
73 | )
74 |
75 | run_one_train_with_geo <- run_information(
76 | "1",
77 | route_type = "Train",
78 | include_geopath = TRUE
79 | )
80 | geopaths <- run_one_train_with_geo$geopath[[1]]
81 | expect_gte(nrow(geopaths), 1)
82 | expect_identical(
83 | names(geopaths),
84 | c("direction_id", "valid_from", "valid_to", "paths")
85 | )
86 | })
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/man/run_information.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/runs.R
3 | \name{run_information}
4 | \alias{run_information}
5 | \title{Information for a given run}
6 | \usage{
7 | run_information(
8 | run_ref,
9 | route_type = NULL,
10 | include_geopath = FALSE,
11 | geopath_utc = NULL,
12 | date_utc = NULL,
13 | user_id = determine_user_id(),
14 | api_key = determine_api_key()
15 | )
16 | }
17 | \arguments{
18 | \item{run_ref}{A character run reference. This supersedes the integer
19 | \code{run_id}. For backwards compatibility and since most run references are
20 | integers, this function will attempt to convert an the argument to a
21 | character. Run references may be retrieved from the
22 | \code{\link{departures}} or \code{\link{runs_on_route}} functions.}
23 |
24 | \item{route_type}{Optionally filter results by a route type. A route type can
25 | be provided either as a non-negative integer code, or as a character:
26 | "Tram", "Train", "Bus", "Vline" or "Night Bus". Character inputs are not
27 | case-sensitive. Use the \code{\link{route_types}} function to extract a
28 | vector of all route types.}
29 |
30 | \item{include_geopath}{Logical. Whether to populate the \code{geopath} column.
31 | Defaults to FALSE.}
32 |
33 | \item{geopath_utc}{Date, or character that can be converted to a date. The
34 | UTC date for which the geopaths are effective. Defaults to the current
35 | date. Has no effect if \code{include_geopath = FALSE}. It's uncertain how much
36 | historical or future-dated data is available.}
37 |
38 | \item{date_utc}{Date, or character that can be converted to a date. The
39 | UTC date for which the results are effective. Defaults to the current date.
40 | It's uncertain how much historical or future-dated data is available. This
41 | argument is experimental and seems to not be functioning.}
42 |
43 | \item{user_id}{Integer or character. A user ID or devid provided by Public
44 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
45 |
46 | \item{api_key}{Character. An API key, with dashes, provided by Public
47 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
48 | }
49 | \value{
50 | A tibble with the following columns: \itemize{
51 | \item \code{run_id} (deprecated, use \code{run_ref} instead)
52 | \item \code{run_ref}
53 | \item \code{route_id}
54 | \item \code{route_type}
55 | \item \code{route_type_description}
56 | \item \code{direction_id}
57 | \item \code{run_sequence}
58 | \item \code{final_stop_id}
59 | \item \code{destination_name}
60 | \item \code{status}
61 | \item \code{express_stop_count}
62 | \item \code{vehicle_position}
63 | \item \code{vehicle_descriptor}
64 | \item \code{geopath}
65 | }
66 | }
67 | \description{
68 | Run IDs are not unique across the network. If you are interested in a
69 | specific run, consider supplying a value to the optional \code{route_type}
70 | argument.
71 | }
72 | \examples{
73 | \dontrun{
74 | run_information("100")
75 | run_information("100", include_geopath = TRUE)
76 | run_information("100", include_geopath = TRUE, geopath_utc = "2020-07-01")
77 | run_information("100", date_utc = "2020-07-01")
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/NEWS.md:
--------------------------------------------------------------------------------
1 | # ptvapi 2.0.5
2 |
3 | * Corrected argument names in documentation for some internal functions.
4 |
5 | # ptvapi 2.0.4
6 |
7 | * Documented package with "_PACKAGE" sentinel to meet new CRAN requirements.
8 |
9 | # ptvapi 2.0.3
10 |
11 | * Fixed 301 link in README
12 |
13 | # ptvapi 2.0.2
14 |
15 | * Remove HTML tags in logo unsupported in HTML5 to meet new CRAN policy.
16 |
17 | # ptvapi 2.0.1
18 |
19 | * Remove `lazyload` in DESCRIPTION to meet new CRAN policy (this package has no data)
20 |
21 | # ptvapi 2.0.0
22 |
23 | * All API request now use SSL by default. To force connections without SSL, set the option `use_insecure_ptv_connection` to `TRUE`.
24 | * In response to changes to the PTV API, `run_information` now requires a character `run_ref` instead of an integer `run_id`. Functions will attempt to convert the argument to a character, and so integer arguments will still work for runs that are not bus or nightrider runs.
25 | * Functions that return `route_type` will now also return `route_type_description`, which is a human-readable interpretation of the route type. For example, when `route_type` is 0, `route_type_description` is "Train".
26 | * In response to changes to the PTV API, run functions now support a `date_utc` argument for returning results as of a given date. This is experimental, and possibly non-functional.
27 | * Functions that internally convert between a route type and a human-readable description of a route type will use a cached version of `route_types`, in order to reduce the number of queries of the API.
28 | * Fixed failing assertion that was causing `run_information` to fail when specifying a route type.
29 | * The column `service_status_timestampe` in data returned by route functions has been changed to `service_status_timestamp` .
30 |
31 | # ptvapi 1.1.3
32 |
33 | * Changed unit tests to use lubridate instead of base for time zone declarations. The lubridate package better supports adding time zones to NA datetimes, as this was causing issues with R-devel (4.0.3).
34 |
35 | # ptvapi 1.1.2
36 |
37 | This release was the first to be accepted to CRAN.
38 |
39 | * Fixed misquoted metadata in package DESCRIPTION
40 |
41 | # ptvapi 1.1.1
42 |
43 | * Fixed invalid URL in package DESCRIPTION
44 |
45 | # ptvapi 1.1.0
46 |
47 | * Adjustments to package DESCRIPTION to meet CRAN standards
48 | * Removed internal function need_api_details, as it depended on changing the user's options. This function has been incorporated into the error messages in `determine_user_id` and `determine_api_key`.
49 | * Removed documentation for the following internal functions, which should resolve the issue of missing argument values in the respective .Rd files: `determine_user_id`, `determine_api_key`, `prefix_base_url`, `prefix_version`, `prefix_base_url_and_version`
50 | * Created `value` Rd-tag for `departures` function
51 | * Removed examples for the following internal functions: `convert_to_melbourne_time`, `PTVGET`
52 | * Added `ptvapi:::` to examples in the following internal functions: `add_parameter`, `add_parameters`
53 | * Removed internal function `route_types_cached`, and replaced all uses with `route_types`. With these changes, there are no references to user options remaining in the package.
54 |
--------------------------------------------------------------------------------
/R/ptv-verbs.R:
--------------------------------------------------------------------------------
1 | # nolint start
2 | # Currently, only the GET verb is used.
3 |
4 | #' Submit a GET request to the PTV API
5 | #'
6 | #' @section Obtaining API authentication details:
7 | #'
8 | #' You will need to obtain a user ID (also called a devid) and an API key from
9 | #' Public Transport Victoria. These are obtained by email. Instructions are
10 | #' available at \url{https://www.ptv.vic.gov.au/footer/data-and-reporting/datasets/ptv-timetable-api/}.
11 | #' You may pass these two pieces of information directly to the function, or you
12 | #' can set the PTV_USER_ID and PTV_API_KEY environment variables.
13 | #'
14 | #' @param request A request or path for the API, eg. "routes".
15 | #' @param user_id Integer or character. A user ID or devid provided by Public
16 | #' Transport Victoria. Refer to `?ptvapi` for more details.
17 | #' @param api_key Character. An API key, with dashes, provided by Public
18 | #' Transport Victoria. Refer to `?ptvapi` for more details.
19 | #' @param ... Additional arguments passed to `httr::GET`.
20 | #'
21 | #' @return A HTTP response. Content can be accessed with `httr::content`.
22 | #'
23 | #' @keywords internal
24 | #'
25 | PTVGET <- function(request,
26 | user_id = determine_user_id(),
27 | api_key = determine_api_key(),
28 | ...) {
29 | request_url <- generate_request_url(
30 | request = request,
31 | user_id = user_id,
32 | api_key = api_key
33 | )
34 | request_url_without_auth <- prefix_base_url_and_version(request)
35 | response <- httr::GET(url = request_url, ...)
36 | process_response(response, request_url_without_auth)
37 | }
38 | # nolint end
39 |
40 | #' Process a raw httr response and return an object of class ptv_api
41 | #'
42 | #' This S3 object returned by this function contains three elements:
43 | #' \itemize{
44 | #' \item status_code, as an integer
45 | #' \item request, which is the request URL without authentication details
46 | #' \item content, the unparsed body of the response
47 | #' }
48 | #'
49 | #' @param response A raw response generated by the `httr` package
50 | #' @param request_url_without_auth Character. The request `url`, without `devid`
51 | #' and signature
52 | #'
53 | #' @return An S3 object of class ptv_api
54 | #'
55 | #' @keywords internal
56 | #'
57 | process_response <- function(response, request_url_without_auth) {
58 |
59 | status_code <- httr::status_code(response)
60 |
61 | if (status_code == 404) {
62 | stop("URL not found: ", request_url_without_auth)
63 | }
64 |
65 | content <- jsonlite::fromJSON(
66 | httr::content(response, "text", encoding = "UTF-8"),
67 | simplifyVector = FALSE
68 | )
69 |
70 | if (status_code == 400) {
71 | stop("Invalid request: ", request_url_without_auth, " - ", content$message)
72 | }
73 | if (status_code == 403) {
74 | stop("Access denied.")
75 | }
76 | if (status_code != 200) {
77 | stop("Status code ", status_code)
78 | }
79 |
80 | list(
81 | request = request_url_without_auth,
82 | retrieved = format(
83 | Sys.time(),
84 | format = "%Y-%m-%d %H:%M:%OS %Z",
85 | tz = "Australia/Melbourne"
86 | ),
87 | status_code = status_code,
88 | content = content
89 | )
90 | }
91 |
--------------------------------------------------------------------------------
/tests/testthat/test-disruptions.R:
--------------------------------------------------------------------------------
1 | # Testing for disruptions is hard because there may be none! We rely on skipping
2 | # tests here if possible, but it should be rare to have no disruptions across
3 | # the network, even if we require a variety of disruption modes.
4 |
5 | if (identical(Sys.getenv("NOT_CRAN"), "true")) {
6 | # ---------------------------------------------------------------------------- #
7 | # ---- Define values if they haven't already been defined by another test ---- #
8 | # ---------------------------------------------------------------------------- #
9 | if (!exists("stops_near_flinders_street")) {
10 | stops_near_flinders_street <- stops_nearby(
11 | latitude = -37.8183,
12 | longitude = 144.9671
13 | )
14 | }
15 |
16 | if (!exists("flinders_street_stop_id")) {
17 | flinders_street_stop_id <- stops_near_flinders_street %>%
18 | filter(stop_name == "Flinders Street Railway Station") %>%
19 | pull(stop_id)
20 | }
21 | # ---------------------------------------------------------------------------- #
22 |
23 | all_disruptions <- disruptions()
24 | disruptions_exist <- (nrow(all_disruptions) > 0)
25 |
26 |
27 | test_that("Can filter disruptions to current or planned", {
28 | skip_if(nrow(all_disruptions) == 0)
29 | skip_if_not("Current" %in% all_disruptions$disruption_status)
30 | skip_if_not("Planned" %in% all_disruptions$disruption_status)
31 | current_disruptions <- disruptions(disruption_status = "current")
32 | planned_disruptions <- disruptions(disruption_status = "planned")
33 | expect_gt(nrow(current_disruptions), 0)
34 | expect_equal(unique(current_disruptions$disruption_status), "Current")
35 | expect_gt(nrow(planned_disruptions), 0)
36 | expect_equal(unique(planned_disruptions$disruption_status), "Planned")
37 | })
38 |
39 | test_that("Can filter by disruption modes", {
40 | skip_if(nrow(all_disruptions) == 0)
41 | dis_modes <- sort(unique(all_disruptions$disruption_mode))
42 |
43 | skip_if(length(dis_modes) < 2)
44 | a_dis_mode <- sample(dis_modes, 1)
45 | disruptions_with_a_dis_mode <- disruptions(disruption_modes = a_dis_mode)
46 | expect_equal(
47 | unique(disruptions_with_a_dis_mode$disruption_mode),
48 | a_dis_mode
49 | )
50 |
51 | skip_if(length(dis_modes) < 3)
52 | two_dis_modes <- sort(sample(dis_modes, 2))
53 | disruptions_with_two_dis_modes <- disruptions(
54 | disruption_modes = two_dis_modes
55 | )
56 | expect_equal(
57 | sort(unique(disruptions_with_two_dis_modes$disruption_mode)),
58 | two_dis_modes
59 | )
60 | })
61 |
62 | test_that("Can filter by route_types", {
63 | # Route types seem to correspond to disruption modes, not the contents of the
64 | # routes tibbles in the disruptions. If at least 3 disruption modes are
65 | # present, we test for the ability to filter to 2, using route_type.
66 | skip_if(nrow(all_disruptions) == 0)
67 | skip_if_not("metro_train" %in% all_disruptions$disruption_mode_description)
68 | skip_if_not("metro_tram" %in% all_disruptions$disruption_mode_description)
69 | skip_if_not("metro_bus" %in% all_disruptions$disruption_mode_description)
70 |
71 | train_route_type <- translate_route_type("Train")
72 | tram_route_type <- translate_route_type("Tram")
73 | two_route_types <- c(train_route_type, tram_route_type)
74 | disruptions_2_route_types <- disruptions(
75 | route_types = two_route_types
76 | )
77 | expect_lt(
78 | nrow(disruptions_2_route_types),
79 | nrow(all_disruptions)
80 | )
81 | })
82 | }
83 |
--------------------------------------------------------------------------------
/R/patterns.R:
--------------------------------------------------------------------------------
1 | # This function ostensibly supports a date_utc function. It appears to change
2 | # the day on which departures are returned. However, only the earliest 7
3 | # departures are shown for the day corresponding to date_utc.
4 |
5 | #' Stopping pattern for a given run
6 | #'
7 | #' A pattern consists of all departures, stops, routes, runs, directions and
8 | #' disruptions associated with a particular run ID. This is returned as a list
9 | #' of tibbles, with output corresponding to their respective API calls.
10 | #'
11 | #' The `stops` tibble has an output similar to that returned by
12 | #' \code{\link{stops_on_route}}. The `routes` tibble does not contain service
13 | #' status information.
14 | #'
15 | #' @details Departures: The API seems to return the earliest 7 departures. While
16 | #' the PTV Timetable API supports filtering patterns by datetimes, the
17 | #' behaviour of this argument is not reliable --- it appears to filter by day
18 | #' only, returning the earliest 7 departures of a different day. It is
19 | #' recommended that departures are retrieved via the \code{\link{departures}}
20 | #' function.
21 | #'
22 | #' @inheritParams run_information
23 | #' @inheritParams translate_route_type
24 | #' @inheritParams disruptions_on_route
25 | #' @param departs POSIXct or character. Optionally filter by date. See Details.
26 | #' Characters are automatically converted to departs, and are assumed to be
27 | #' given as Melbourne time. The behaviour of the API is unpredictable when
28 | #' using this argument --- see details. Defaults to the current system time.
29 | #' @inheritParams PTVGET
30 | #'
31 | #' @return An object of class "ptvapi", which is effectively a list with the
32 | #' following names: \itemize{ \item `departures` \item `stops` \item `routes`
33 | #' \item `runs` \item `directions` \item `disruptions` }
34 | #'
35 | #' @export
36 | #'
37 | #' @examples \dontrun{
38 | #' patterns(run_ref = "1", route_type = 0)
39 | #' patterns(run_ref = "1", route_type = "Train")
40 | #' }
41 | #'
42 | patterns <- function(run_ref,
43 | route_type,
44 | stop_id = NULL,
45 | departs = Sys.time(),
46 | user_id = determine_user_id(),
47 | api_key = determine_api_key()) {
48 | run_ref <- as.character(run_ref)
49 | route_type <- translate_route_type(
50 | route_type,
51 | user_id = user_id,
52 | api_key = api_key
53 | )
54 | if (!is.null(stop_id)) stop_id <- to_integer(stop_id)
55 | departs <- to_datetime(departs)
56 | url_departs <- format(departs, format = "%Y-%m-%dT%H:%M:%OS", tz = "UTC")
57 |
58 | request <- add_parameters(
59 | glue::glue("pattern/run/{run_ref}/route_type/{route_type}"),
60 | expand = "all",
61 | stop_id = stop_id,
62 | date_utc = url_departs
63 | )
64 | response <- PTVGET(request, user_id = user_id, api_key = api_key)
65 | content <- response$content
66 | assert_correct_attributes(
67 | names(content),
68 | c("disruptions", "departures", "stops", "routes", "runs", "directions",
69 | "status")
70 | )
71 |
72 | structure(
73 | list(
74 | departures = map_and_rbind(content$departures, departure_to_tibble),
75 | stops = map_and_rbind(content$stops, stop_to_tibble),
76 | routes = map_and_rbind(content$routes, route_to_tibble),
77 | runs = map_and_rbind(content$runs, run_to_tibble),
78 | directions = map_and_rbind(content$directions, tibble::as_tibble),
79 | disruptions = list(content$disruptions)
80 | ),
81 | class = "ptvapi",
82 | request = response$request,
83 | status_code = response$status_code,
84 | content = response$content
85 | )
86 | }
87 |
--------------------------------------------------------------------------------
/docs/pkgdown.js:
--------------------------------------------------------------------------------
1 | /* http://gregfranko.com/blog/jquery-best-practices/ */
2 | (function($) {
3 | $(function() {
4 |
5 | $('.navbar-fixed-top').headroom();
6 |
7 | $('body').css('padding-top', $('.navbar').height() + 10);
8 | $(window).resize(function(){
9 | $('body').css('padding-top', $('.navbar').height() + 10);
10 | });
11 |
12 | $('[data-toggle="tooltip"]').tooltip();
13 |
14 | var cur_path = paths(location.pathname);
15 | var links = $("#navbar ul li a");
16 | var max_length = -1;
17 | var pos = -1;
18 | for (var i = 0; i < links.length; i++) {
19 | if (links[i].getAttribute("href") === "#")
20 | continue;
21 | // Ignore external links
22 | if (links[i].host !== location.host)
23 | continue;
24 |
25 | var nav_path = paths(links[i].pathname);
26 |
27 | var length = prefix_length(nav_path, cur_path);
28 | if (length > max_length) {
29 | max_length = length;
30 | pos = i;
31 | }
32 | }
33 |
34 | // Add class to parent
, and enclosing
if in dropdown
35 | if (pos >= 0) {
36 | var menu_anchor = $(links[pos]);
37 | menu_anchor.parent().addClass("active");
38 | menu_anchor.closest("li.dropdown").addClass("active");
39 | }
40 | });
41 |
42 | function paths(pathname) {
43 | var pieces = pathname.split("/");
44 | pieces.shift(); // always starts with /
45 |
46 | var end = pieces[pieces.length - 1];
47 | if (end === "index.html" || end === "")
48 | pieces.pop();
49 | return(pieces);
50 | }
51 |
52 | // Returns -1 if not found
53 | function prefix_length(needle, haystack) {
54 | if (needle.length > haystack.length)
55 | return(-1);
56 |
57 | // Special case for length-0 haystack, since for loop won't run
58 | if (haystack.length === 0) {
59 | return(needle.length === 0 ? 0 : -1);
60 | }
61 |
62 | for (var i = 0; i < haystack.length; i++) {
63 | if (needle[i] != haystack[i])
64 | return(i);
65 | }
66 |
67 | return(haystack.length);
68 | }
69 |
70 | /* Clipboard --------------------------*/
71 |
72 | function changeTooltipMessage(element, msg) {
73 | var tooltipOriginalTitle=element.getAttribute('data-original-title');
74 | element.setAttribute('data-original-title', msg);
75 | $(element).tooltip('show');
76 | element.setAttribute('data-original-title', tooltipOriginalTitle);
77 | }
78 |
79 | if(ClipboardJS.isSupported()) {
80 | $(document).ready(function() {
81 | var copyButton = "";
82 |
83 | $(".examples, div.sourceCode").addClass("hasCopyButton");
84 |
85 | // Insert copy buttons:
86 | $(copyButton).prependTo(".hasCopyButton");
87 |
88 | // Initialize tooltips:
89 | $('.btn-copy-ex').tooltip({container: 'body'});
90 |
91 | // Initialize clipboard:
92 | var clipboardBtnCopies = new ClipboardJS('[data-clipboard-copy]', {
93 | text: function(trigger) {
94 | return trigger.parentNode.textContent;
95 | }
96 | });
97 |
98 | clipboardBtnCopies.on('success', function(e) {
99 | changeTooltipMessage(e.trigger, 'Copied!');
100 | e.clearSelection();
101 | });
102 |
103 | clipboardBtnCopies.on('error', function() {
104 | changeTooltipMessage(e.trigger,'Press Ctrl+C or Command+C to copy');
105 | });
106 | });
107 | }
108 | })(window.jQuery || window.$)
109 |
--------------------------------------------------------------------------------
/tests/testthat/test-filter-departures.R:
--------------------------------------------------------------------------------
1 | # Note that in all of these test cases we order the departures by departure
2 | # time: by estimated_departure (if defined), otherwise by scheduled departure.
3 | #
4 | # In the event of there being a tie, ie. the next 3 departures for a route are
5 | # all at the same time and we have max_results = 2, then the tie is arbitrarily
6 | # broken. This shouldn't be an issue because it would be highly unlikely that
7 | # there are two services of the same route departing at the same time from the
8 | # same stop. Maybe if they have different direction IDs?
9 |
10 | test_departures <- tibble::tribble(
11 | ~route_id, ~scheduled_departure, ~estimated_departure,
12 | 1, "2020-06-01 12:04:00", NA,
13 | 1, "2020-06-01 12:03:00", NA,
14 | 1, "2020-06-01 12:02:00", NA,
15 | 1, "2020-06-01 12:01:00", NA,
16 | 2, "2020-06-01 12:04:00", NA,
17 | 2, "2020-06-01 12:03:00", NA,
18 | 2, "2020-06-01 12:02:00", "2020-06-01 12:05:00",
19 | 2, "2020-06-01 12:01:00", NA,
20 | 3, "2020-06-01 12:01:00", NA,
21 | 3, "2020-06-01 12:01:00", NA,
22 | 3, "2020-06-01 12:01:00", NA,
23 | 3, "2020-06-01 12:02:00", NA
24 | ) %>%
25 | mutate_at(
26 | vars(contains("departure")),
27 | ~force_tz(as_datetime(.), tzone = "Australia/Melbourne")
28 | )
29 |
30 | test_that("Can filter departures with max_results", {
31 | expect_equal(
32 | filter_departures(test_departures, max_results = 2),
33 | tibble::tribble(
34 | ~route_id, ~scheduled_departure, ~estimated_departure,
35 | 1, "2020-06-01 12:01:00", NA,
36 | 2, "2020-06-01 12:01:00", NA,
37 | 3, "2020-06-01 12:01:00", NA,
38 | 3, "2020-06-01 12:01:00", NA,
39 | 1, "2020-06-01 12:02:00", NA,
40 | 2, "2020-06-01 12:03:00", NA
41 | ) %>%
42 | mutate_at(
43 | vars(contains("departure")),
44 | ~force_tz(as_datetime(.), tzone = "Australia/Melbourne")
45 | )
46 | )
47 | })
48 |
49 | test_that("Can filter departures with datetime", {
50 | expect_equal(
51 | filter_departures(
52 | test_departures,
53 | departs = as.POSIXct("2020-06-01 12:03:00", tz = "Australia/Melbourne")
54 | ),
55 | tibble::tribble(
56 | ~route_id, ~scheduled_departure, ~estimated_departure,
57 | 1, "2020-06-01 12:03:00", NA,
58 | 2, "2020-06-01 12:03:00", NA,
59 | 1, "2020-06-01 12:04:00", NA,
60 | 2, "2020-06-01 12:04:00", NA,
61 | 2, "2020-06-01 12:02:00", "2020-06-01 12:05:00"
62 | ) %>%
63 | mutate_at(
64 | vars(contains("departure")),
65 | ~force_tz(as_datetime(.), tzone = "Australia/Melbourne")
66 | )
67 | )
68 | })
69 |
70 | test_that("Can filter departures with route_id", {
71 | expect_equal(
72 | filter_departures(
73 | test_departures,
74 | route_id = 2
75 | ),
76 | tibble::tribble(
77 | ~route_id, ~scheduled_departure, ~estimated_departure,
78 | 2, "2020-06-01 12:01:00", NA,
79 | 2, "2020-06-01 12:03:00", NA,
80 | 2, "2020-06-01 12:04:00", NA,
81 | 2, "2020-06-01 12:02:00", "2020-06-01 12:05:00"
82 | ) %>%
83 | mutate_at(
84 | vars(contains("departure")),
85 | ~force_tz(as_datetime(.), tzone = "Australia/Melbourne")
86 | )
87 | )
88 | })
89 |
90 | test_that("Can filter departures with datetime and max_results", {
91 | expect_equal(
92 | filter_departures(
93 | test_departures,
94 | departs = as.POSIXct("2020-06-01 12:03:00", tz = "Australia/Melbourne"),
95 | max_results = 2
96 | ),
97 | tibble::tribble(
98 | ~route_id, ~scheduled_departure, ~estimated_departure,
99 | 1, "2020-06-01 12:03:00", NA,
100 | 2, "2020-06-01 12:03:00", NA,
101 | 1, "2020-06-01 12:04:00", NA,
102 | 2, "2020-06-01 12:04:00", NA,
103 | ) %>%
104 | mutate_at(
105 | vars(contains("departure")),
106 | ~force_tz(as_datetime(.), tzone = "Australia/Melbourne")
107 | )
108 | )
109 | })
110 |
--------------------------------------------------------------------------------
/R/directions.R:
--------------------------------------------------------------------------------
1 | #' Directions for a given direction ID
2 | #'
3 | #' This function returns all directions with a given ID. Directions that share
4 | #' an ID are not necessarily related, especially if not filtering by route type.
5 | #' It's advised to use to the \code{\link{directions_on_route}} function to
6 | #' search for directions of interest.
7 | #'
8 | #' @param direction_id Integer.
9 | #' @param route_type Optionally filter results by a route type. A route type can
10 | #' be provided either as a non-negative integer code, or as a character:
11 | #' "Tram", "Train", "Bus", "Vline" or "Night Bus". Character inputs are not
12 | #' case-sensitive. Use the \code{\link{route_types}} function to extract a
13 | #' vector of all route types.
14 | #' @inheritParams PTVGET
15 | #'
16 | #' @inherit parse_directions_content return
17 | #' @export
18 | #'
19 | #' @examples \dontrun{
20 | #' directions(direction_id = 5)
21 | #' directions(direction_id = 5, route_type = "Train")
22 | #' directions(direction_id = 5, route_type = 0)
23 | #' }
24 | #'
25 | directions <- function(direction_id,
26 | route_type = NULL,
27 | user_id = determine_user_id(),
28 | api_key = determine_api_key()) {
29 | direction_id <- to_integer(direction_id)
30 | request <- glue::glue("directions/{direction_id}")
31 | if (!is.null(route_type)) {
32 | route_type <- translate_route_type(
33 | route_type,
34 | user_id = user_id,
35 | api_key = api_key
36 | )
37 | request <- glue::glue(request, "/route_type/{route_type}")
38 | }
39 | response <- PTVGET(request, user_id = user_id, api_key = api_key)
40 | content <- response$content
41 | parsed <- parse_directions_content(content)
42 | parsed$route_type_description <- purrr::map_chr(
43 | parsed$route_type,
44 | describe_route_type,
45 | user_id = user_id,
46 | api_key = api_key
47 | )
48 | new_ptvapi_tibble(response, parsed)
49 | }
50 |
51 | #' Directions on a given route
52 | #'
53 | #' @param route_id Integer. These can be listed and described with the
54 | #' \code{\link{routes}} function.
55 | #' @inheritParams PTVGET
56 | #'
57 | #' @inherit parse_directions_content return
58 | #'
59 | #' @export
60 | #'
61 | #' @examples \dontrun{
62 | #' directions_on_route(6)
63 | #' }
64 | directions_on_route <- function(route_id,
65 | user_id = determine_user_id(),
66 | api_key = determine_api_key()) {
67 | route_id <- to_integer(route_id)
68 | request <- glue::glue("directions/route/{route_id}")
69 | response <- PTVGET(request, user_id = user_id, api_key = api_key)
70 | content <- response$content
71 | parsed <- parse_directions_content(content)
72 | parsed$route_type_description <- purrr::map_chr(
73 | parsed$route_type,
74 | describe_route_type,
75 | user_id = user_id,
76 | api_key = api_key
77 | )
78 | new_ptvapi_tibble(response, parsed)
79 | }
80 |
81 | #' Parse content of directions API call
82 | #'
83 | #' This function is designed to parse the content returned by the interior
84 | #' steps of the \code{\link{directions}} function.
85 | #'
86 | #' @param directions_content A direction, as a list, returned by the
87 | #' `directions` API call.
88 | #'
89 | #' @return A tibble consisting of the following columns: \itemize{
90 | #' \item `direction_id`
91 | #' \item `direction_name`,
92 | #' \item `route_id`
93 | #' \item `route_type`
94 | #' \item `route_type_description`
95 | #' \item `route_direction_description`
96 | #' }
97 | #'
98 | #' @keywords internal
99 | #'
100 | parse_directions_content <- function(directions_content) {
101 | assert_correct_attributes(
102 | names(directions_content),
103 | c("directions", "status")
104 | )
105 | parsed <- map_and_rbind(directions_content$directions, tibble::as_tibble)
106 |
107 | assert_correct_attributes(
108 | colnames(parsed),
109 | c("route_direction_description", "direction_id", "direction_name",
110 | "route_id", "route_type")
111 | )
112 |
113 | parsed$route_type_description <- NA_character_
114 |
115 | parsed[c("direction_id", "direction_name", "route_id", "route_type",
116 | "route_type_description", "route_direction_description")]
117 | }
118 |
--------------------------------------------------------------------------------
/tests/testthat/test-departures.R:
--------------------------------------------------------------------------------
1 | if (identical(Sys.getenv("NOT_CRAN"), "true")) {
2 | # ---------------------------------------------------------------------------- #
3 | # ---- Define values if they haven't already been defined by another test ---- #
4 | # ---------------------------------------------------------------------------- #
5 | if (!exists("all_routes")) {
6 | all_routes <- routes()
7 | }
8 |
9 | if (!exists("frankston_route_id")) {
10 | frankston_route_id <- all_routes %>%
11 | dplyr::filter(route_name == "Frankston") %>%
12 | pull(route_id)
13 | }
14 |
15 | if (!exists("stops_near_flinders_street")) {
16 | stops_near_flinders_street <- stops_nearby(
17 | latitude = -37.8183,
18 | longitude = 144.9671
19 | )
20 | }
21 |
22 | if (!exists("flinders_street_stop_id")) {
23 | flinders_street_stop_id <- stops_near_flinders_street %>%
24 | filter(stop_name == "Flinders Street Railway Station") %>%
25 | pull(stop_id)
26 | }
27 | # ---------------------------------------------------------------------------- #
28 |
29 | flinders_departures <- departures(
30 | stop_id = flinders_street_stop_id,
31 | route_type = "Train"
32 | )
33 |
34 | test_that("departures result has class \"ptvapi\"", {
35 | expect_s3_class(flinders_departures, "ptvapi")
36 | })
37 |
38 | flinders_street_stop_id <- stops_nearby(
39 | latitude = -37.8183,
40 | longitude = 144.9671
41 | ) %>%
42 | filter(stop_name == "Flinders Street Railway Station") %>%
43 | pull(stop_id)
44 |
45 | test_that("Frankston train departs from Flinders Street Station",
46 | expect_true(frankston_route_id %in% flinders_departures$route_id)
47 | )
48 |
49 | test_that("Departures filtered by platform_number", {
50 | platform_5_6_departures <- departures(
51 | stop_id = flinders_street_stop_id,
52 | route_type = "Train",
53 | platform_numbers = c("5", "6"), # despite the name, they are characters
54 | )
55 | expect_gt(nrow(platform_5_6_departures), 0) # must have some results
56 | expect_true(
57 | # disruptions may mean that platform 5 or 6 not active
58 | all(platform_5_6_departures$platform_number %in% c("5", "6"))
59 | )
60 | })
61 |
62 | test_that("Departures filtered by datetime", {
63 | flinders_morning_departures <- departures(
64 | stop_id = flinders_street_stop_id,
65 | route_type = "Train",
66 | departs = morning_test_time
67 | )
68 | expect_gt(nrow(flinders_morning_departures), 0) # must have some results
69 | expect_gte(
70 | min(flinders_morning_departures$scheduled_departure),
71 | as.POSIXct(morning_test_time, tz = "Australia/Melbourne")
72 | )
73 | })
74 |
75 | test_that("Departures filtered by datetime with max_results = 0", {
76 | flinders_all_day_departures <- departures(
77 | stop_id = flinders_street_stop_id,
78 | route_type = "Train",
79 | departs = afternoon_test_time,
80 | max_results = 0
81 | )
82 | expect_gt(nrow(flinders_all_day_departures), 0) # must have some results
83 |
84 | # When max_results = 0, all departures after the given datetime for the entire
85 | # day are returned. Because of how a "day" is considered in public transport,
86 | # this may include some departures in the early hours of the next morning.
87 | expect_gte(
88 | min(flinders_all_day_departures$scheduled_departure),
89 | as.POSIXct(afternoon_test_time, tz = "Australia/Melbourne")
90 | )
91 | departure_dates <- as.Date(
92 | flinders_all_day_departures$scheduled_departure,
93 | tz = "Australia/Melbourne"
94 | )
95 | expect_true(
96 | all(departure_dates %in% c(
97 | as.Date(afternoon_test_time, tz = "Australia/Melbourne"),
98 | as.Date(afternoon_test_time, tz = "Australia/Melbourne") + 1)
99 | )
100 | )
101 | })
102 |
103 | test_that("Departures filtered by datetime and max_results", {
104 | flinders_afternoon_departures <- departures(
105 | stop_id = flinders_street_stop_id,
106 | route_type = "Train",
107 | departs = afternoon_test_time,
108 | max_results = 3
109 | )
110 | expect_gt(nrow(flinders_afternoon_departures), 0) # must have some results
111 | expect_gte(
112 | min(flinders_afternoon_departures$scheduled_departure),
113 | as.POSIXct(afternoon_test_time, tz = "Australia/Melbourne")
114 | )
115 | departures_per_route_id <- count(flinders_afternoon_departures, route_id)
116 | expect_true(all(departures_per_route_id$n == 3))
117 | })
118 |
119 | test_that("Departures filtered by route_id", {
120 | flinders_route_6_departures <- departures(
121 | stop_id = flinders_street_stop_id,
122 | route_type = "Train",
123 | route_id = 6
124 | )
125 | expect_equal(
126 | distinct(flinders_route_6_departures, route_id)$route_id,
127 | 6
128 | )
129 | })
130 | }
131 |
--------------------------------------------------------------------------------
/man/departures.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/departures.R
3 | \name{departures}
4 | \alias{departures}
5 | \title{Departures from a given stop}
6 | \usage{
7 | departures(
8 | stop_id,
9 | route_type,
10 | route_id = NULL,
11 | direction_id = NULL,
12 | platform_numbers = NULL,
13 | departs = Sys.time(),
14 | look_backwards = FALSE,
15 | max_results = 5,
16 | include_cancelled = FALSE,
17 | validate_results = TRUE,
18 | user_id = determine_user_id(),
19 | api_key = determine_api_key()
20 | )
21 | }
22 | \arguments{
23 | \item{stop_id}{An integer stop ID returned by the \code{stops_on_route} or
24 | \code{stops_nearby} functions.}
25 |
26 | \item{route_type}{A route type which can be provided either as a non-negative
27 | integer code, or as a character: "Tram", "Train", "Bus", "Vline" or "Night
28 | Bus". Character inputs are not case-sensitive. Use the
29 | \code{\link{route_types}} function to extract a vector of all route types.}
30 |
31 | \item{route_id}{Optionally filter by a route ID. These can be obtained with
32 | the \code{routes} function.}
33 |
34 | \item{direction_id}{Optionally filter by a direction ID. These can be
35 | obtained with the \code{\link{directions_on_route}} function.}
36 |
37 | \item{platform_numbers}{Character vector. Optionally filter results by
38 | platform number. Despite the name, these are characters.}
39 |
40 | \item{departs}{POSIXct or Character. Optionally filter results to departures
41 | on or after the given value, according to either scheduled or estimated
42 | departure time. Characters are automatically converted to datetimes, and
43 | are assumed to be given as Melbourne time. Defaults to the current system
44 | time.}
45 |
46 | \item{look_backwards}{Boolean. Whether to look before \code{departs}. Use with
47 | caution (see Details). Defaults to \code{FALSE}.}
48 |
49 | \item{max_results}{Integer. The maximum number of departures to return for
50 | each route_id. Departures are ordered by estimated departure time, when
51 | available, and scheduled departure time otherwise. When set to 0, all
52 | departures after the given \code{departs} for the entire day are shown, and
53 | potentially some in the early hours of the next morning. Defaults to 5.}
54 |
55 | \item{include_cancelled}{Logical. Whether results should be returned if they
56 | have been cancelled. Metropolitan train services only. Defaults to FALSE.}
57 |
58 | \item{validate_results}{Boolean. If TRUE (the default), will apply additional
59 | filters to ensure that the arguments to \code{departs}, \code{max_results}, and
60 | \code{route_id} are respected if given.}
61 |
62 | \item{user_id}{Integer or character. A user ID or devid provided by Public
63 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
64 |
65 | \item{api_key}{Character. An API key, with dashes, provided by Public
66 | Transport Victoria. Refer to \code{?ptvapi} for more details.}
67 | }
68 | \value{
69 | A tibble consisting of the following columns: \itemize{
70 | \item \code{stop_id}
71 | \item \code{route_id}
72 | \item \code{run_id} (deprecated, use \code{run_ref} instead)
73 | \item \code{run_ref}
74 | \item \code{direction_id}
75 | \item \code{disruption_ids}
76 | \item \code{scheduled_departure}
77 | \item \code{estimated_departure}
78 | \item \code{at_platform}
79 | \item \code{platform_number}
80 | \item \code{flags}
81 | \item \code{departure_sequence}
82 | }
83 | }
84 | \description{
85 | \code{departures} retrieves all upcoming departures for a given stop ID and route
86 | type.
87 | }
88 | \details{
89 | Filtering departures: The API supports filtering by departure time,
90 | to show the departures after the given time. However, its behaviour is
91 | unpredictable, returning departures around the given time, both before and
92 | after. We apply an additional filter once the results are retrieved to
93 | ensure that only departures at or after the given \code{departs} datetime are
94 | shown.
95 |
96 | It's not clear what functionality \code{look_backwards} has. It's included here
97 | regardless. Moreover, it's not clear how the API treats \code{route_id} or
98 | \code{max_results}. We filter the results after retrieval, to ensure that
99 | \code{departs}, \code{max_results}, and \code{route_id} are respected. This additional
100 | validation can be disabled by setting \code{validate_results = TRUE}.
101 | }
102 | \examples{
103 | \dontrun{
104 | departures(stop_id = 1071, route_type = "Train")
105 | departures(stop_id = 1071, route_type = 0)
106 |
107 | departures(
108 | stop_id = 1071,
109 | route_type = "Train",
110 | platform_numbers = c(4, 5)
111 | )
112 |
113 | departures(
114 | stop_id = 1071,
115 | route_type = "Train",
116 | route_id = 6
117 | )
118 |
119 | departures(
120 | stop_id = 1071,
121 | route_type = "Train",
122 | departs = "2020-06-23 17:05:00"
123 | )
124 |
125 | }
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/tests/testthat/test-stops.R:
--------------------------------------------------------------------------------
1 | if (identical(Sys.getenv("NOT_CRAN"), "true")) {
2 | # ---------------------------------------------------------------------------- #
3 | # ---- Define values if they haven't already been defined by another test ---- #
4 | # ---------------------------------------------------------------------------- #
5 | if (!exists("all_routes")) {
6 | all_routes <- routes()
7 | }
8 |
9 | if (!exists("frankston_route_id")) {
10 | frankston_route_id <- all_routes %>%
11 | dplyr::filter(route_name == "Frankston") %>%
12 | pull(route_id)
13 | }
14 |
15 | if (!exists("stops_near_flinders_street")) {
16 | stops_near_flinders_street <- stops_nearby(
17 | latitude = -37.8183,
18 | longitude = 144.9671
19 | )
20 | }
21 |
22 | if (!exists("flinders_street_stop_id")) {
23 | flinders_street_stop_id <- stops_near_flinders_street %>%
24 | filter(stop_name == "Flinders Street Railway Station") %>%
25 | pull(stop_id)
26 | }
27 | # ---------------------------------------------------------------------------- #
28 |
29 | stops_on_frankston_line <- stops_on_route(
30 | route_id = frankston_route_id,
31 | route_type = "Train"
32 | )
33 |
34 | test_that("stops_on_route result has class \"ptvapi\"", {
35 | expect_s3_class(stops_on_frankston_line, "ptvapi")
36 | })
37 |
38 | test_that("Stop names along a route are unique", {
39 | expect_equal(anyDuplicated(stops_on_frankston_line$stop_name), 0)
40 | })
41 |
42 | test_that("Expected stops on Frankston train line", {
43 | expect_true("South Yarra Station" %in% stops_on_frankston_line$stop_name)
44 | expect_true("Cheltenham Station" %in% stops_on_frankston_line$stop_name)
45 | })
46 |
47 | # Stops with a direction_id parameters
48 | frankston_directions_on_route <- directions_on_route(frankston_route_id)
49 |
50 | test_that("Stop sequence when travelling City to Frankston", {
51 | frankston_direction_id <- frankston_directions_on_route %>%
52 | filter(direction_name == "Frankston") %>%
53 | pull(direction_id)
54 | city_to_frankston <- stops_on_route(
55 | route_id = frankston_route_id,
56 | route_type = "Train",
57 | direction = frankston_direction_id
58 | )
59 | city_to_frankston_ends <- city_to_frankston %>%
60 | filter(stop_sequence == max(stop_sequence))
61 | expect_equal(nrow(city_to_frankston_ends), 1)
62 | expect_equal(city_to_frankston_ends$stop_suburb, "Frankston")
63 | })
64 |
65 | test_that("Stop sequence when travelling Frankston to City", {
66 | city_direction_id <- frankston_directions_on_route %>%
67 | filter(grepl("City", direction_name)) %>%
68 | pull(direction_id)
69 | frankston_to_city <- stops_on_route(
70 | route_id = frankston_route_id,
71 | route_type = "Train",
72 | direction_id = city_direction_id
73 | )
74 | frankston_to_city_ends <- frankston_to_city %>%
75 | filter(stop_sequence == max(stop_sequence))
76 | expect_equal(nrow(frankston_to_city_ends), 1)
77 | expect_equal(frankston_to_city_ends$stop_suburb, "Melbourne City")
78 | })
79 |
80 | flinders_info <- stop_information(
81 | flinders_street_stop_id,
82 | route_type = "Train"
83 | )
84 |
85 | test_that("stop_information result has class \"ptvapi\"", {
86 | expect_s3_class(flinders_info, "ptvapi")
87 | })
88 |
89 | test_that("Flinders Street stop_information is complete", {
90 | expect_lt(mean(is.na(flinders_info)), 0.25) # max prop of NA columns
91 | })
92 |
93 | # stops_near_flinders_street defined at top of file
94 | test_that("stops_nearby result has class \"ptvapi\"", {
95 | expect_s3_class(stops_near_flinders_street, "ptvapi")
96 | })
97 |
98 | # flinders_street_stop_id defined at top of file
99 | test_that("Can find Flinders Street Station stop with latitude and longitude", {
100 | expect_true(length(flinders_street_stop_id) == 1)
101 | })
102 |
103 | test_that("stops_nearby filtered by max_distance", {
104 | max_100 <- stops_nearby(
105 | latitude = -37.8183,
106 | longitude = 144.9671,
107 | max_distance = 100
108 | )
109 | max_1000 <- stops_nearby(
110 | latitude = -37.8183,
111 | longitude = 144.9671,
112 | max_distance = 1000
113 | )
114 | expect_true("Collins St/Queen St" %in% max_1000$stop_name)
115 | expect_false("Collins St/Queen St" %in% max_100$stop_name)
116 | })
117 |
118 | test_that("stops_nearby() can be filtered with multiple route types", {
119 | # We use Southern Cross because it has a variety of route types
120 | expect_equal(
121 | stops_nearby(
122 | latitude = -37.818229,
123 | longitude = 144.952404,
124 | route_types = c(0, 1)
125 | ) %>%
126 | pull(route_type) %>%
127 | unique %>%
128 | sort,
129 | c(0, 1)
130 | )
131 | expect_equal(
132 | stops_nearby(
133 | latitude = -37.818229,
134 | longitude = 144.952404,
135 | route_types = c(0, 3)
136 | ) %>%
137 | pull(route_type) %>%
138 | unique %>%
139 | sort,
140 | c(0, 3)
141 | )
142 | })
143 | }
144 |
--------------------------------------------------------------------------------
/R/outlets.R:
--------------------------------------------------------------------------------
1 | #' Information for all outlets
2 | #'
3 | #' @details
4 | #' The `outlet_name` reported here is more accurately described as an outlet
5 | #' _address_. We keep the `outlet_name` column name as this is how the PTV API
6 | #' describes it.
7 | #'
8 | #' The business hours are reported as characters. Usually they take on
9 | #' a format of "8.00AM - 10.00PM", but there variants such as "7.30AM - 11.00AM
10 | #' and 1.30PM - 6.00PM". For days on which an outlet is closed, the opening
11 | #' hours are usually reported as "CLOSED", but can also be an empty character.
12 | #' Some opening hours are "24 Hours". These fields are also filled with missing
13 | #' values and empty characters.
14 | #'
15 | #' @inheritParams PTVGET
16 | #'
17 | #' @inherit outlet_to_tibble return
18 | #'
19 | #' @export
20 | #'
21 | #' @examples \dontrun{
22 | #' outlets()
23 | #' }
24 | #'
25 | outlets <- function(user_id = determine_user_id(),
26 | api_key = determine_api_key()) {
27 |
28 | # This API call nominally supports an `max_results` parameter, which defaults
29 | # to 30. It seems to have no impact on the results, so it hasn't been
30 | # included here. It's possibly accidentally copied from the API call used in
31 | # the `outlets_nearby` function.
32 |
33 | request <- "outlets"
34 | response <- PTVGET(request, user_id = user_id, api_key = api_key)
35 | content <- response$content
36 | assert_correct_attributes(
37 | names(content),
38 | c("outlets", "status")
39 | )
40 |
41 | parsed <- map_and_rbind(content$outlets, outlet_to_tibble)
42 | new_ptvapi_tibble(response, parsed)
43 | }
44 |
45 | #' Information for outlets near a given location
46 | #'
47 | #' @inherit outlets details
48 | #'
49 | #' @inheritParams stops_nearby
50 | #' @param max_results Integer. Defaults to 30. Caps the number of results
51 | #' returned.
52 | #' @inheritParams PTVGET
53 | #'
54 | #' @inherit outlet_to_tibble return
55 | #'
56 | #' @export
57 | #'
58 | #' @examples \dontrun{
59 | #' outlets_nearby(latitude = -37.8183, longitude = 144.9671)
60 | #' }
61 | #'
62 | outlets_nearby <- function(latitude,
63 | longitude,
64 | max_distance = NULL,
65 | max_results = 30,
66 | user_id = determine_user_id(),
67 | api_key = determine_api_key()) {
68 |
69 | assertthat::assert_that(is.numeric(latitude))
70 | assertthat::assert_that(is.numeric(longitude))
71 | if (!is.null(max_distance)) max_distance <- to_integer(max_distance)
72 | max_results <- to_integer(max_results)
73 |
74 | request <- add_parameters(
75 | glue::glue("outlets/location/{latitude},{longitude}"),
76 | max_distance = max_distance,
77 | max_results = max_results
78 | )
79 | response <- PTVGET(request, user_id = user_id, api_key = api_key)
80 | content <- response$content
81 | assert_correct_attributes(
82 | names(content),
83 | c("outlets", "status")
84 | )
85 |
86 | parsed <- map_and_rbind(content$outlets, outlet_to_tibble)
87 | new_ptvapi_tibble(response, parsed)
88 | }
89 |
90 | #' Convert a single outlet to a tibble
91 | #'
92 | #' This function is designed to parse the content returned by the interior
93 | #' steps of the \code{\link{outlets}} and \code{\link{outlets_nearby}}
94 | #' functions.
95 | #'
96 | #' @param outlet An outlet, as a list, returned by the \code{\link{outlets}} API
97 | #' call.
98 | #'
99 | #' @return A tibble with the following columns: \itemize{
100 | #' \item {`outlet_slid_spid`}
101 | #' \item {`outlet_name`}
102 | #' \item {`outlet_business`}
103 | #' \item {`outlet_latitude`}
104 | #' \item {`outlet_longitude`}
105 | #' \item {`outlet_suburb`}
106 | #' \item {`outlet_postcode`}
107 | #' \item {`outlet_business_hour_mon`}
108 | #' \item {`outlet_business_hour_tue`}
109 | #' \item {`outlet_business_hour_wed`}
110 | #' \item {`outlet_business_hour_thu`}
111 | #' \item {`outlet_business_hour_fri`}
112 | #' \item {`outlet_business_hour_sat`}
113 | #' \item {`outlet_business_hour_sun`}
114 | #' \item {`outlet_notes`}
115 | #' }
116 | #'
117 | #' @keywords internal
118 | #'
119 | outlet_to_tibble <- function(outlet) {
120 | null_to_na_char <- function(x) ifelse(is.null(x), NA_character_, x)
121 | tibble::tibble(
122 | outlet_slid_spid = outlet$outlet_slid_spid,
123 | outlet_name = outlet$outlet_name,
124 | outlet_business = outlet$outlet_business,
125 | outlet_latitude = outlet$outlet_latitude,
126 | outlet_longitude = outlet$outlet_longitude,
127 | outlet_suburb = outlet$outlet_suburb,
128 | outlet_postcode = outlet$outlet_postcode,
129 | outlet_business_hour_mon = null_to_na_char(outlet$outlet_business_hour_mon),
130 | outlet_business_hour_tue = null_to_na_char(outlet$outlet_business_hour_tue),
131 | outlet_business_hour_wed = null_to_na_char(outlet$outlet_business_hour_wed),
132 | outlet_business_hour_thu = null_to_na_char(outlet$outlet_business_hour_thu),
133 | outlet_business_hour_fri = null_to_na_char(outlet$outlet_business_hour_fri),
134 | outlet_business_hour_sat = null_to_na_char(outlet$outlet_business_hour_sat),
135 | outlet_business_hour_sun = null_to_na_char(outlet$outlet_business_hour_sun),
136 | outlet_notes = null_to_na_char(outlet$outlet_notes)
137 | )
138 | }
139 |
--------------------------------------------------------------------------------
/docs/authors.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Authors • ptvapi
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
');
64 | $li.append($a);
65 | return $li;
66 | },
67 |
68 | generateNavItem: function(headingEl) {
69 | var anchor = this.generateAnchor(headingEl);
70 | var $heading = $(headingEl);
71 | var text = $heading.data('toc-text') || $heading.text();
72 | return this.generateNavEl(anchor, text);
73 | },
74 |
75 | // Find the first heading level (`
`, then `
`, etc.) that has more than one element. Defaults to 1 (for `
`).
76 | getTopLevel: function($scope) {
77 | for (var i = 1; i <= 6; i++) {
78 | var $headings = this.findOrFilter($scope, 'h' + i);
79 | if ($headings.length > 1) {
80 | return i;
81 | }
82 | }
83 |
84 | return 1;
85 | },
86 |
87 | // returns the elements for the top level, and the next below it
88 | getHeadings: function($scope, topLevel) {
89 | var topSelector = 'h' + topLevel;
90 |
91 | var secondaryLevel = topLevel + 1;
92 | var secondarySelector = 'h' + secondaryLevel;
93 |
94 | return this.findOrFilter($scope, topSelector + ',' + secondarySelector);
95 | },
96 |
97 | getNavLevel: function(el) {
98 | return parseInt(el.tagName.charAt(1), 10);
99 | },
100 |
101 | populateNav: function($topContext, topLevel, $headings) {
102 | var $context = $topContext;
103 | var $prevNav;
104 |
105 | var helpers = this;
106 | $headings.each(function(i, el) {
107 | var $newNav = helpers.generateNavItem(el);
108 | var navLevel = helpers.getNavLevel(el);
109 |
110 | // determine the proper $context
111 | if (navLevel === topLevel) {
112 | // use top level
113 | $context = $topContext;
114 | } else if ($prevNav && $context === $topContext) {
115 | // create a new level of the tree and switch to it
116 | $context = helpers.createChildNavList($prevNav);
117 | } // else use the current $context
118 |
119 | $context.append($newNav);
120 |
121 | $prevNav = $newNav;
122 | });
123 | },
124 |
125 | parseOps: function(arg) {
126 | var opts;
127 | if (arg.jquery) {
128 | opts = {
129 | $nav: arg
130 | };
131 | } else {
132 | opts = arg;
133 | }
134 | opts.$scope = opts.$scope || $(document.body);
135 | return opts;
136 | }
137 | },
138 |
139 | // accepts a jQuery object, or an options object
140 | init: function(opts) {
141 | opts = this.helpers.parseOps(opts);
142 |
143 | // ensure that the data attribute is in place for styling
144 | opts.$nav.attr('data-toggle', 'toc');
145 |
146 | var $topContext = this.helpers.createChildNavList(opts.$nav);
147 | var topLevel = this.helpers.getTopLevel(opts.$scope);
148 | var $headings = this.helpers.getHeadings(opts.$scope, topLevel);
149 | this.helpers.populateNav($topContext, topLevel, $headings);
150 | }
151 | };
152 |
153 | $(function() {
154 | $('nav[data-toggle="toc"]').each(function(i, el) {
155 | var $nav = $(el);
156 | Toc.init($nav);
157 | });
158 | });
159 | })();
160 |
--------------------------------------------------------------------------------
/R/helper-functions.R:
--------------------------------------------------------------------------------
1 | #' Convert a datetime returned by the PTV API into Melbourne time
2 | #'
3 | #' @param datetime A datetime returned by the PTV API
4 | #'
5 | #' @return A datetime in the Melbourne timezone.
6 | #'
7 | #' @keywords internal
8 | #'
9 | convert_to_melbourne_time <- function(datetime) {
10 |
11 | if (is.null(datetime)) {
12 | na_datetime <- as.POSIXct(NA)
13 | attr(na_datetime, "tzone") <- "Australia/Melbourne"
14 | return(na_datetime)
15 | }
16 |
17 | converted <- as.POSIXct(
18 | datetime,
19 | tz = "GMT",
20 | format = "%Y-%m-%dT%H:%M:%OS"
21 | )
22 | attr(converted, "tzone") <- "Australia/Melbourne"
23 | converted
24 | }
25 |
26 | #' Convert a POSIXct or character datetime to a format ready for a URL
27 | #'
28 | #' Datetimes accepted by the API need to be given in UTC. This function will
29 | #' accept a datetime or a character with a suitable datetime format, and output
30 | #' a character that is suitable for a URL. All URL input and output in this
31 | #' package should be in Melbourne time, and the UTC conversion should happen
32 | #'
33 | #' The API seems to accept both "%Y-%m-%dT%H:%M:%OS" and "%Y-%m-%d %H:%M:%OS",
34 | #' but we opt for the former.
35 | #'
36 | #' @param datetime POSIXct or Character.
37 | #'
38 | #' @return Character.
39 | #'
40 | #' @keywords internal
41 | #'
42 | to_datetime <- function(datetime) {
43 | if (is.character(datetime)) {
44 | datetime <- as.POSIXct(
45 | datetime,
46 | tz = "Australia/Melbourne",
47 | tryFormats = c("%Y-%m-%dT%H:%M:%OS",
48 | "%Y-%m-%d %H:%M:%OS",
49 | "%Y/%m/%d %H:%M:%OS",
50 | "%Y-%m-%d %H:%M",
51 | "%Y/%m/%d %H:%M",
52 | "%Y-%m-%d",
53 | "%Y/%m/%d")
54 | )
55 | }
56 |
57 | assertthat::assert_that(
58 | inherits(datetime, "POSIXct"),
59 | msg = "datetime must be POSIXct, or character that parses as POSIXct"
60 | )
61 |
62 | datetime
63 | }
64 |
65 | #' Map and rbind a list of data frames
66 | #'
67 | #' This function is a simple combination of `purrr::map` and `purrr::reduce`
68 | #' using `rbind`. This differs from `purrr::map_dfr`, which uses
69 | #' `dplyr::map_dfr` and therefore introduces `dplyr` as a dependency. If the
70 | #' provided list is empty, then an empty tibble will be returned.
71 | #'
72 | #' @param .x A list of data frames or tibbles.
73 | #' @param .f A function.
74 | #' @param ... Additional arguments passed to the function.
75 | #'
76 | #' @return A data frame or tibble.
77 | #'
78 | #' @keywords internal
79 | #'
80 | map_and_rbind <- function(.x, .f, ...) {
81 | if (length(.x) == 0) {
82 | return(tibble::tibble())
83 | }
84 | do.call(rbind, lapply(.x, .f, ...))
85 | }
86 |
87 | #' Assert that the API has returned the expected attributes
88 | #'
89 | #' The attributes returned by the API calls should be follow the API schema.
90 | #' This function compares received attributes against a vector of expected
91 | #' attributes, and returns an error if the two do not match. Unfortunately,
92 | #' there is no easy fix for this error: the package developer(s) must be
93 | #' notified, so that they can align the functions against the API schema.
94 | #'
95 | #' @param received_attributes A character vector of attributes, in order.
96 | #' @param expected_attributes A character vector of expected attributes, in
97 | #' order.
98 | #'
99 | #' @return An error if the column names are not as expected.
100 | #'
101 | #' @keywords internal
102 | #'
103 | assert_correct_attributes <- function(received_attributes,
104 | expected_attributes) {
105 | assertthat::assert_that(
106 | identical(received_attributes, expected_attributes),
107 | msg = paste(
108 | "The attributes returned by the API were not as expected.",
109 | "Please contact the package developer.",
110 | "Expected", paste(expected_attributes, collapse = ", "),
111 | "but got", paste(received_attributes, collapse = ", ")
112 | )
113 | )
114 | }
115 |
116 | #' Strictly convert an object to an integer
117 | #'
118 | #' R does not have a built-in function to determine if a value is an integer
119 | #' (`is.integer` will check if the class of an object is "integer", but
120 | #' `is.integer(3)` will return FALSE). This helper function fills that gap. It
121 | #' will attempt to convert the input to an integer, but will error on any input
122 | #' that cannot be confidently expressed as an integer. It serves as a stricter
123 | #' version of `as.integer`. For example, the function will convert `3` or `"3"`
124 | #' to integers, but will error on `3.5` or `"three"`.
125 | #'
126 | #' @param x An input of any type.
127 | #'
128 | #' @return An integer interpretation of `x`, if possible.
129 | #'
130 | #' @keywords internal
131 | #'
132 | to_integer <- function(x) {
133 | if (missing(x)) {
134 | stop("Error in to_integer() : argument \"x\" is missing, with no default")
135 | }
136 |
137 | if (!(typeof(x) %in% c("integer", "double", "character"))) {
138 | stop(paste("Cannot convert", typeof(x), "to an integer"))
139 | }
140 |
141 | error_message <- paste("Cannot convert", x, "to an integer")
142 |
143 | # Try numeric first, then cast to integer
144 | x_numeric <- suppressWarnings(as.numeric(x))
145 | if (is.na(x_numeric)) stop(error_message)
146 | x_integer <- as.integer(x_numeric)
147 | if (x_integer != x_numeric) stop(error_message)
148 | x_integer
149 | }
150 |
--------------------------------------------------------------------------------
/docs/LICENSE-text.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | License • ptvapi
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |