diff --git a/R/inputs.R b/R/inputs.R index 89f1066..d7c68a7 100644 --- a/R/inputs.R +++ b/R/inputs.R @@ -635,14 +635,16 @@ select_input_386 <- function( inputId, label, choices, selected = NULL, multiple = FALSE, selectize = FALSE, width = NULL, size = NULL ) { - args <- as.list(match.call())[-1] - defaults <- formals(sys.function()) - defaults <- defaults[!(names(defaults) %in% names(args))] - htmltools::tagQuery( - do.call( - shiny::selectInput, - dropNulls(c(args, defaults)) + shiny::selectInput( + inputId, + label, + choices, + selected, + multiple, + selectize = FALSE, + width, + size ) )$ find("select")$ diff --git a/inst/examples/gallery/R/gfts.R b/inst/examples/gallery/R/gfts.R new file mode 100644 index 0000000..96ab071 --- /dev/null +++ b/inst/examples/gallery/R/gfts.R @@ -0,0 +1,10 @@ +get_transit_gtfs <- memoise::memoise(function() { + download_and_read( + url = "https://svc.metrotransit.org/mtgtfs/gtfs.zip", + # Use consistent location for caching within R session + destfile = file.path(tempdir(), "gtfs.zip"), + read_fn = function(x) { + gtfstools::read_gtfs(x, c("trips", "shapes")) + } + ) +}) diff --git a/inst/examples/gallery/R/realtime.R b/inst/examples/gallery/R/realtime.R new file mode 100644 index 0000000..ca003c6 --- /dev/null +++ b/inst/examples/gallery/R/realtime.R @@ -0,0 +1,77 @@ +library(RProtoBuf) +library(dplyr) +library(purrr) + + +realtime_info <- memoise::memoise( + function() { + + # Altered from https://stackoverflow.com/a/71552368 + # to support https://svc.metrotransit.org/ + # Only add shapes if needed + if (! ("transit_realtime.VehiclePosition" %in% ls("RProtoBuf:DescriptorPool"))) { + download_and_read( + url = "https://gtfs.org/realtime/gtfs-realtime.proto", + # Use consistent location for caching within R session + destfile = file.path(tempdir(), "gtfs-realtime.proto"), + # Load proto shapes into RProtoBuf + read_fn = readProtoFiles + ) + # # View proto shapes + # ls("RProtoBuf:DescriptorPool") + } + + download_and_read( + url = "https://svc.metrotransit.org/mtgtfs/vehiclepositions.pb", + destfile = tempfile(fileext = ".pb"), + clean = TRUE, + read_fn = function(vehicle_positions) { + read( + transit_realtime.FeedMessage, + vehicle_positions + )[["entity"]] + } + ) + }, + # Cache for 10 seconds (then website will refresh data every 15s) + # Helps avoid duplicate work during startup + cache = cachem::cache_mem(max_age = 10) +) + + +# Route +# Direction +# VehicleLongitude +# VehicleLatitude +realtime_locations <- function(..., veh_info = realtime_info(), gtfs = get_transit_gtfs()) { + locations <- + veh_info %>% + map_dfr(~ { + vehicle <- .x$vehicle + trip <- vehicle$trip + position <- vehicle$position + tibble( + Route = trip$route_id, + trip_id = trip$trip_id, + VehicleLongitude = position$longitude, + VehicleLatitude = position$latitude + ) + }) %>% + # Remove vehicles with no location + filter(VehicleLatitude > 1) + + locations %>% + left_join( + gtfs$trips %>% + select(trip_id, direction), + by = "trip_id" + ) %>% + # Add route names + # 1=South, 2=East, 3=West, 4=North + mutate( + Direction = c("SB" = 1, "EB" = 2, "WB" = 3, "NB" = 4)[direction], + # Remove unnecessary columns + trip_id = NULL, + direction = NULL + ) +} diff --git a/inst/examples/gallery/R/utils.R b/inst/examples/gallery/R/utils.R new file mode 100644 index 0000000..5d3fc8f --- /dev/null +++ b/inst/examples/gallery/R/utils.R @@ -0,0 +1,30 @@ + + + +download_file <- function(url, destfile) { + download.file(url, destfile, quiet = TRUE) +} + +download_and_read <- function(url, destfile, read_fn, clean = FALSE) { + if (!file.exists(destfile)) { + download_file(url, destfile) + } + if (clean) { + on.exit(unlink(destfile), add = TRUE) + } + + read_fn(destfile) +} + + +# Method to recursively convert proto objects to lists +# Great for debugging +as_list <- function(x) { + if (inherits(x, "Message")) { + x <- as.list(x) + } + if (is.list(x)) { + return(lapply(x, as_list)) + } + x +} diff --git a/inst/examples/gallery/app.R b/inst/examples/gallery/app.R index 517ffd9..ad8042a 100644 --- a/inst/examples/gallery/app.R +++ b/inst/examples/gallery/app.R @@ -1,5 +1,4 @@ # packages ---------------------------------------------------------------- - library(shiny) library(shiny386) library(leaflet) @@ -7,46 +6,33 @@ library(dplyr) library(curl) # make the jsonlite suggested dependency explicit library(jsonlite) - -# utils ------------------------------------------------------------------- - # 1=South, 2=East, 3=West, 4=North -dirColors <- c("1" = "#595490", "2" = "#527525", "3" = "#A93F35", "4" = "#BA48AA") - -# Download data from the Twin Cities Metro Transit API -# http://svc.metrotransit.org/NexTrip/help -getMetroData <- function(path) { - url <- paste0("http://svc.metrotransit.org/NexTrip/", path, "?format=json") - jsonlite::fromJSON(url) -} - -# Load static trip and shape data -trips <- readRDS("metrotransit-data/rds/trips.rds") -shapes <- readRDS("metrotransit-data/rds/shapes.rds") +dirColors <-c("1"="#595490", "2"="#527525", "3"="#A93F35", "4"="#BA48AA") +# Dynamically load trip and shape data +gtfs <- get_transit_gtfs() # Get the shape for a particular route. This isn't perfect. Each route has a # large number of different trips, and each trip can have a different shape. # This function simply returns the most commonly-used shape across all trips for # a particular route. -get_route_shape <- function(route) { - routeid <- paste0(route, "-75") +get_route_shape <- function(routeid) { + # routeid <- paste0(route, "-75") - # For this route, get all the shape_ids listed in trips, and a count of how - # many times each shape is used. We'll just pick the most commonly-used shape. - shape_counts <- trips %>% - filter(route_id == routeid) %>% - group_by(shape_id) %>% - summarise(n = n()) %>% - arrange(-n) + # For this route, get all the shape_ids listed in trips, and a count of how + # many times each shape is used. We'll just pick the most commonly-used shape. + shape_counts <- gtfs$trips %>% + filter(route_id == routeid) %>% + group_by(shape_id) %>% + summarise(n = n()) %>% + arrange(-n) - shapeid <- shape_counts$shape_id[1] + shapeid <- shape_counts$shape_id[1] - # Get the coordinates for the shape_id - shapes %>% filter(shape_id == shapeid) + # Get the coordinates for the shape_id + gtfs$shapes %>% filter(shape_id == shapeid) } - # UI ---------------------------------------------------------------------- ui <- page_386( h1("Twin Cities Buses"), @@ -114,169 +100,166 @@ ui <- page_386( # server ------------------------------------------------------------------ - server <- function(input, output, session) { - # Route select input box - output$routeSelect <- renderUI({ - live_vehicles <- getMetroData("VehicleLocations/0") - - routeNums <- sort(unique(as.numeric(live_vehicles$Route))) - # Add names, so that we can add all=0 - names(routeNums) <- routeNums - routeNums <- c(All = 0, routeNums) - selectInput("routeNum", "Route", choices = routeNums, selected = routeNums[2]) - }) - - # Locations of all active vehicles - vehicleLocations <- reactive({ - input$refresh # Refresh if button clicked - - # Get interval (minimum 30) - interval <- max(as.numeric(input$interval), 30) - # Invalidate this reactive after the interval has passed, so that data is - # fetched again. - invalidateLater(interval * 1000, session) - - getMetroData("VehicleLocations/0") - }) - - # Locations of vehicles for a particular route - routeVehicleLocations <- reactive({ - if (is.null(input$routeNum)) { - return() - } - - locations <- vehicleLocations() - - if (as.numeric(input$routeNum) == 0) { - return(locations) - } - - locations[locations$Route == input$routeNum, ] - }) - - # Get time that vehicles locations were updated - lastUpdateTime <- reactive({ - vehicleLocations() # Trigger this reactive when vehicles locations are updated - Sys.time() - }) - - # Number of seconds since last update - output$timeSinceLastUpdate <- renderUI({ - # Trigger this every 5 seconds - invalidateLater(5000, session) - p( - class = "text-muted", - "Data refreshed ", - round(difftime(Sys.time(), lastUpdateTime(), units = "secs")), - " seconds ago." - ) - }) - - output$numVehiclesTable <- renderUI({ - locations <- routeVehicleLocations() - if (length(locations) == 0 || nrow(locations) == 0) { - return(NULL) - } - - # Create a Bootstrap-styled table - tags$table( - class = "table", - tags$thead(tags$tr( - tags$th("Color"), - tags$th("Direction"), - tags$th("Number of vehicles") - )), - tags$tbody( - tags$tr( - tags$td(span(style = sprintf( - "width:1.1em; height:1.1em; background-color:%s; display:inline-block;", - dirColors[4] - ))), - tags$td("Northbound"), - tags$td(nrow(locations[locations$Direction == "4", ])) - ), - tags$tr( - tags$td(span(style = sprintf( - "width:1.1em; height:1.1em; background-color:%s; display:inline-block;", - dirColors[1] - ))), - tags$td("Southbound"), - tags$td(nrow(locations[locations$Direction == "1", ])) - ), - tags$tr( - tags$td(span(style = sprintf( - "width:1.1em; height:1.1em; background-color:%s; display:inline-block;", - dirColors[2] - ))), - tags$td("Eastbound"), - tags$td(nrow(locations[locations$Direction == "2", ])) - ), - tags$tr( - tags$td(span(style = sprintf( - "width:1.1em; height:1.1em; background-color:%s; display:inline-block;", - dirColors[3] - ))), - tags$td("Westbound"), - tags$td(nrow(locations[locations$Direction == "3", ])) - ), - tags$tr( - style = "background-color: grey", - tags$td(), - tags$td("Total"), - tags$td(nrow(locations)) - ) - ) - ) - }) - - # Store last zoom button value so we can detect when it's clicked - lastZoomButtonValue <- NULL - - output$busmap <- renderLeaflet({ - locations <- routeVehicleLocations() - if (length(locations) == 0) { - return(NULL) - } - - # Show only selected directions - locations <- filter(locations, Direction %in% as.numeric(input$directions)) - - # Four possible directions for bus routes - dirPal <- colorFactor(dirColors, names(dirColors)) - - map <- leaflet(locations) %>% - addTiles("http://{s}.tile.thunderforest.com/transport/{z}/{x}/{y}.png") %>% - addCircleMarkers( - ~VehicleLongitude, - ~VehicleLatitude, - color = ~ dirPal(Direction), - opacity = 0.8, - radius = 8 - ) - - if (as.numeric(input$routeNum) != 0) { - route_shape <- get_route_shape(input$routeNum) - - map <- addPolylines(map, - route_shape$shape_pt_lon, - route_shape$shape_pt_lat, - fill = FALSE - ) - } - - rezoom <- "first" - # If zoom button was clicked this time, and store the value, and rezoom - if (!identical(lastZoomButtonValue, input$zoomButton)) { - lastZoomButtonValue <<- input$zoomButton - rezoom <- "always" - } - - map <- map %>% mapOptions(zoomToLimits = rezoom) - - map - }) + # Route select input box + output$routeSelect <- renderUI({ + # live_vehicles <- getMetroData("VehicleLocations/0") + + routeNums <- + sort(unique(as.numeric( + realtime_locations(gtfs = gtfs)$Route + ))) + + # Add names, so that we can add all=0 + names(routeNums) <- routeNums + routeNums <- c(All = 0, routeNums) + select_input_386("routeNum", "Route", choices = routeNums, selected = routeNums[2]) + }) + + # Locations of all active vehicles + vehicleLocations <- reactive({ + input$refresh # Refresh if button clicked + + # Get interval (minimum 30) + interval <- max(as.numeric(input$interval), 30) + # Invalidate this reactive after the interval has passed, so that data is + # fetched again. + invalidateLater(interval * 1000, session) + + realtime_locations(gtfs = gtfs) + }) + + # Data frame of vehicle locations for a particular route + routeVehicleLocations <- reactive({ + if (is.null(input$routeNum)) + return() + + locations <- vehicleLocations() + + if (as.numeric(input$routeNum) == 0) + return(locations) + + locations[locations$Route == input$routeNum, ] + }) + + # Get time that vehicles locations were updated + lastUpdateTime <- reactive({ + vehicleLocations() # Trigger this reactive when vehicles locations are updated + Sys.time() + }) + + # Number of seconds since last update + output$timeSinceLastUpdate <- renderUI({ + # Trigger this every 5 seconds + invalidateLater(5000, session) + p( + class = "text-muted", + "Data refreshed ", + round(difftime(Sys.time(), lastUpdateTime(), units="secs")), + " seconds ago." + ) + }) + + output$numVehiclesTable <- renderUI({ + locations <- routeVehicleLocations() + if (length(locations) == 0 || nrow(locations) == 0) + return(NULL) + + # Create a Bootstrap-styled table + tags$table(class = "table", + tags$thead(tags$tr( + tags$th("Color"), + tags$th("Direction"), + tags$th("Number of vehicles") + )), + tags$tbody( + tags$tr( + tags$td(span(style = sprintf( + "width:1.1em; height:1.1em; background-color:%s; display:inline-block;", + dirColors[4] + ))), + tags$td("Northbound"), + tags$td(nrow(locations[locations$Direction == "4",])) + ), + tags$tr( + tags$td(span(style = sprintf( + "width:1.1em; height:1.1em; background-color:%s; display:inline-block;", + dirColors[1] + ))), + tags$td("Southbound"), + tags$td(nrow(locations[locations$Direction == "1",])) + ), + tags$tr( + tags$td(span(style = sprintf( + "width:1.1em; height:1.1em; background-color:%s; display:inline-block;", + dirColors[2] + ))), + tags$td("Eastbound"), + tags$td(nrow(locations[locations$Direction == "2",])) + ), + tags$tr( + tags$td(span(style = sprintf( + "width:1.1em; height:1.1em; background-color:%s; display:inline-block;", + dirColors[3] + ))), + tags$td("Westbound"), + tags$td(nrow(locations[locations$Direction == "3",])) + ), + tags$tr(class = "active", + tags$td(), + tags$td("Total"), + tags$td(nrow(locations)) + ) + ) + ) + }) + + # Store last zoom button value so we can detect when it's clicked + lastZoomButtonValue <- NULL + + output$busmap <- renderLeaflet({ + locations <- routeVehicleLocations() + if (length(locations) == 0) + return(NULL) + + # Show only selected directions + locations <- filter(locations, Direction %in% as.numeric(input$directions)) + + # Four possible directions for bus routes + dirPal <- colorFactor(dirColors, names(dirColors)) + + map <- leaflet(locations) %>% + addTiles('http://{s}.tile.thunderforest.com/transport/{z}/{x}/{y}.png') %>% + addCircleMarkers( + ~VehicleLongitude, + ~VehicleLatitude, + color = ~dirPal(Direction), + opacity = 0.8, + radius = 8 + ) + + if (as.numeric(input$routeNum) != 0) { + route_shape <- get_route_shape(input$routeNum) + + map <- addPolylines(map, + route_shape$shape_pt_lon, + route_shape$shape_pt_lat, + fill = FALSE + ) + } + + rezoom <- "first" + # If zoom button was clicked this time, and store the value, and rezoom + if (!identical(lastZoomButtonValue, input$zoomButton)) { + lastZoomButtonValue <<- input$zoomButton + rezoom <- "always" + } + + map <- map %>% mapOptions(zoomToLimits = rezoom) + + map + }) } shinyApp(ui, server) diff --git a/inst/examples/gallery/metrotransit-data/00-fetch-data.R b/inst/examples/gallery/metrotransit-data/00-fetch-data.R deleted file mode 100755 index 85201d8..0000000 --- a/inst/examples/gallery/metrotransit-data/00-fetch-data.R +++ /dev/null @@ -1,41 +0,0 @@ -# Information about data source at: -# http://datafinder.org/metadata/transit_schedule_google_feed.html - -# Data reference: -# https://developers.google.com/transit/gtfs/reference?csw=1 - -# These are the data files we want to use -datafiles <- c("shapes.txt", "trips.txt") - -# ============================================================================= -# Download and unzip data -# ============================================================================= -download.file("ftp://gisftp.metc.state.mn.us/google_transit.zip", - "google_transit.zip") - -dir.create("raw", showWarnings = FALSE) - -# Extract just the specified data files -unzip("google_transit.zip", files = datafiles, exdir = "raw") -unlink("google_transit.zip") - -# ============================================================================= -# Read in each of the data objects and save to an RDS file -# ============================================================================= -# Clean out old files -unlink("rds", recursive = TRUE) - -dir.create("rds", showWarnings = FALSE) - -for (datafile in datafiles) { - infile <- file.path("raw", datafile) - outfile <- file.path("rds", sub("\\.txt$", ".rds", datafile)) - - cat("Converting ", infile, " to ", outfile, ".\n", sep = "") - - obj <- read.csv(infile, stringsAsFactors = FALSE) - saveRDS(obj, outfile) -} - -# Remove raw data files -unlink("raw", recursive = TRUE) diff --git a/inst/examples/gallery/metrotransit-data/metrotransit-data.Rproj b/inst/examples/gallery/metrotransit-data/metrotransit-data.Rproj deleted file mode 100755 index a213108..0000000 --- a/inst/examples/gallery/metrotransit-data/metrotransit-data.Rproj +++ /dev/null @@ -1,15 +0,0 @@ -Version: 1.0 - -RestoreWorkspace: Default -SaveWorkspace: Default -AlwaysSaveHistory: Default - -EnableCodeIndexing: Yes -UseSpacesForTab: Yes -NumSpacesForTab: 2 -Encoding: UTF-8 - -RnwWeave: Sweave -LaTeX: pdfLaTeX - -AutoAppendNewline: Yes diff --git a/inst/examples/gallery/metrotransit-data/rds/shapes.rds b/inst/examples/gallery/metrotransit-data/rds/shapes.rds deleted file mode 100755 index fefbd07..0000000 Binary files a/inst/examples/gallery/metrotransit-data/rds/shapes.rds and /dev/null differ diff --git a/inst/examples/gallery/metrotransit-data/rds/trips.rds b/inst/examples/gallery/metrotransit-data/rds/trips.rds deleted file mode 100755 index 587df04..0000000 Binary files a/inst/examples/gallery/metrotransit-data/rds/trips.rds and /dev/null differ