Brisbane city is home to a vast array of food trucks. There are some dedicated venues which have several trucks at a time, but there is also a body of people who follow their favourite trucks. This visual app is designed to allow people to follow their favourite trucks or their favourite types of food, termed here, truck watchers. The data is freely available via the creative commons 4.0 licence (https://creativecommons.org/licenses/by/4.0/).
The initial data scrape included the 6 .csv files for the years 2017 to 2022. These were compiled into a larger dataset, food.trucks.csv. Once several steps of manipulation and map design were performed, R was unable to process the data. There was too much of it. So, the same manipulations and processes were performed with the independent .csv files, one at a time, creating 6 different maps.
A further scrape of the website, https://www.bnefoodtrucks.com.au/, was performed. This was to gather the details of the “Category” for the food truck (Dessert, Italian, etc.). This was used to colour code the map dots so as to afford better tracking for truck watchers.
Future maps will afford finer selections for trucks. These selections will include type, drinks vs food, spicy food (a special category), or even single truck watching.
NOTE: It was only after several iterations of the code were performed that it was noticed that the website https://www.bnefoodtrucks.com.au/ had several of the features being attempted with the code herein. Please disregard that website. No code was taken from that website other than a string of information in the form:
Dessert / Sweet
discovered upon inspection of the page “https://www.bnefoodtrucks.com.au/food-trucks”.
# install.packages("leaflet")
# install.packages("leaflet.extras")
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.2.3
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(leaflet)
## Warning: package 'leaflet' was built under R version 4.2.3
library(leaflet.extras)
## Warning: package 'leaflet.extras' was built under R version 4.2.3
library(plotly)
## Warning: package 'plotly' was built under R version 4.2.3
## Loading required package: ggplot2
## Warning: package 'ggplot2' was built under R version 4.2.3
##
## Attaching package: 'plotly'
## The following object is masked from 'package:ggplot2':
##
## last_plot
## The following object is masked from 'package:stats':
##
## filter
## The following object is masked from 'package:graphics':
##
## layout
library(lubridate)
## Warning: package 'lubridate' was built under R version 4.2.3
##
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
##
## date, intersect, setdiff, union
# Read in the data
data2017 <- read.csv("brisbane-food-trucks-historical-bookings-2017.csv")
data2018 <- read.csv("brisbane-food-trucks-historical-bookings-2018.csv")
data2019 <- read.csv("brisbane-food-trucks-historical-bookings-2019.csv")
data2020 <- read.csv("brisbane-food-trucks-historical-bookings-2020.csv")
data2021 <- read.csv("brisbane-food-trucks-historical-bookings-2021.csv")
data2022 <- read.csv("brisbane-food-trucks-historical-bookings-2022.csv")
# Combine the data into one dataframe
food_trucks <- bind_rows(data2017, data2018, data2019, data2020, data2021, data2022)
# Convert time slot to datetime format
food_trucks$Booking.created <- ymd_hms(food_trucks$Booking.created)
# Filter table for all nul values of latitude, longitude, time slot
food_trucks <- food_trucks %>% filter(!is.na(Latitude) & !is.na(Longitude))
# Convert "Longitude" column to numeric
food_trucks$Longitude <- as.numeric(food_trucks$Longitude)
food_trucks$Booking.created <- as.character(food_trucks$Booking.created)
food_trucks$Booking.created <- as.character(food_trucks$Booking.created)
food_trucks$Booking.created <- as.POSIXct(food_trucks$Booking.created, format="%Y-%m-%d %H:%M:%S")
# replacement error in the 'Food.truck' column, replaceable with details in the ~Title variable
food_trucks$Title <- gsub(" - .*", "", food_trucks$Title)
library(rvest)
# URL of the website to scrape
url <- "https://www.bnefoodtrucks.com.au/food-trucks"
# Scrape the truck names and categories
food_trucks_scrape <- url %>%
read_html() %>%
html_nodes(".content") %>%
lapply(function(x) {
name <- html_node(x, ".truck-name") %>% html_text(trim = TRUE)
category <- html_node(x, ".field-name-field-category") %>% html_text(trim = TRUE)
c(name, category)
}) %>%
simplify2array() %>%
t() %>%
as.data.frame(stringsAsFactors = FALSE) %>%
setNames(c("Truck_Name", "Category"))
# Clean the data
food_trucks_scrape$Truck_Name <- gsub("\n *", "", food_trucks_scrape$Truck_Name)
food_trucks_scrape$Category <- gsub("\n *", "", food_trucks_scrape$Category)
food_trucks_scrape$Truck_Name <- gsub("^[0-9]+- ", "", food_trucks_scrape$Truck_Name)
# adjusting variable name to match needed output
food_trucks_scrape <- food_trucks_scrape %>%
rename(Title = Truck_Name)
# combine the cleaned food_trucks and _scrape by the variable ~Truck.name
food_trucks_merged <- merge(food_trucks, food_trucks_scrape, by = "Title", all.x = TRUE)
truck_categorylist <- unique(food_trucks_merged$Category)
print(truck_categorylist)
## [1] "American" NA "Italian"
## [4] "Japanese" "Dessert / Sweet" "German"
## [7] "Healthy" "Burger" "Vegan"
## [10] "Seafood" "Sri Lankan" "Korean"
## [13] "Juice" "Fusion" "Asian"
## [16] "Pizza" "French" "Greek"
## [19] "BBQ / Meat Enthusiast" "Indian" "Turkish"
## [22] "Bavarian" "South American" "Vietnamese"
## [25] "Gluten free" "Lebanese"
# creating a colour coding variable to make the plot clearer
food_trucks_merged <- food_trucks_merged %>%
mutate(category_color = case_when(
Category == "Dessert / Sweet" ~ "Dessert/Sweet",
Category %in% c("Lebanese", "Vietnamese", "South American", "Bavarian", "Turkish", "Indian", "Greek", "French", "Asian", "Korean", "Sri Lankan", "German", "Japanese", "Italian", "American", "Fusion") ~ "Cultural",
Category %in% c("BBQ / Meat Enthusiast", "Vegan", "Gluten free", "Healthy") ~ "Dietary",
Category %in% c("Pizza", "Juice", "Seafood", "Burger") ~ "Food type",
is.na(Category) ~ "Other",
TRUE ~ NA_character_
))
adjusting the initial idea to several single food truck csv, each adding the ‘category’ and ‘category_colour’ variables.
# replacement error in the 'Food.truck' column, replaceable with details in the ~Title variable
food_trucks$Title <- gsub(" - .*", "", food_trucks$Title)
data2017$Title <- gsub(" - .*", "", data2017$Title)
data2018$Title <- gsub(" - .*", "", data2018$Title)
data2019$Title <- gsub(" - .*", "", data2019$Title)
data2020$Title <- gsub(" - .*", "", data2020$Title)
data2021$Title <- gsub(" - .*", "", data2021$Title)
data2022$Title <- gsub(" - .*", "", data2022$Title)
# combine the cleaned food_trucks and _scrape by the variable ~Truck.name
data2017_merged <- merge(data2017, food_trucks_scrape, by = "Title", all.x = TRUE)
data2018_merged <- merge(data2018, food_trucks_scrape, by = "Title", all.x = TRUE)
data2019_merged <- merge(data2019, food_trucks_scrape, by = "Title", all.x = TRUE)
data2020_merged <- merge(data2020, food_trucks_scrape, by = "Title", all.x = TRUE)
data2021_merged <- merge(data2021, food_trucks_scrape, by = "Title", all.x = TRUE)
data2022_merged <- merge(data2022, food_trucks_scrape, by = "Title", all.x = TRUE)
# adding colour variable as per above examples
data2017_merged <- data2017_merged %>%
mutate(category_color = case_when(
Category == "Dessert / Sweet" ~ "Dessert/Sweet",
Category %in% c("Lebanese", "Vietnamese", "South American", "Bavarian", "Turkish", "Indian", "Greek", "French", "Asian", "Korean", "Sri Lankan", "German", "Japanese", "Italian", "American", "Fusion") ~ "Cultural",
Category %in% c("BBQ / Meat Enthusiast", "Vegan", "Gluten free", "Healthy") ~ "Dietary",
Category %in% c("Pizza", "Juice", "Seafood", "Burger") ~ "Food type",
is.na(Category) ~ "Other",
TRUE ~ NA_character_
))
data2018_merged <- data2018_merged %>%
mutate(category_color = case_when(
Category == "Dessert / Sweet" ~ "Dessert/Sweet",
Category %in% c("Lebanese", "Vietnamese", "South American", "Bavarian", "Turkish", "Indian", "Greek", "French", "Asian", "Korean", "Sri Lankan", "German", "Japanese", "Italian", "American", "Fusion") ~ "Cultural",
Category %in% c("BBQ / Meat Enthusiast", "Vegan", "Gluten free", "Healthy") ~ "Dietary",
Category %in% c("Pizza", "Juice", "Seafood", "Burger") ~ "Food type",
is.na(Category) ~ "Other",
TRUE ~ NA_character_
))
data2019_merged <- data2019_merged %>%
mutate(category_color = case_when(
Category == "Dessert / Sweet" ~ "Dessert/Sweet",
Category %in% c("Lebanese", "Vietnamese", "South American", "Bavarian", "Turkish", "Indian", "Greek", "French", "Asian", "Korean", "Sri Lankan", "German", "Japanese", "Italian", "American", "Fusion") ~ "Cultural",
Category %in% c("BBQ / Meat Enthusiast", "Vegan", "Gluten free", "Healthy") ~ "Dietary",
Category %in% c("Pizza", "Juice", "Seafood", "Burger") ~ "Food type",
is.na(Category) ~ "Other",
TRUE ~ NA_character_
))
data2020_merged <- data2020_merged %>%
mutate(category_color = case_when(
Category == "Dessert / Sweet" ~ "Dessert/Sweet",
Category %in% c("Lebanese", "Vietnamese", "South American", "Bavarian", "Turkish", "Indian", "Greek", "French", "Asian", "Korean", "Sri Lankan", "German", "Japanese", "Italian", "American", "Fusion") ~ "Cultural",
Category %in% c("BBQ / Meat Enthusiast", "Vegan", "Gluten free", "Healthy") ~ "Dietary",
Category %in% c("Pizza", "Juice", "Seafood", "Burger") ~ "Food type",
is.na(Category) ~ "Other",
TRUE ~ NA_character_
))
data2021_merged <- data2021_merged %>%
mutate(category_color = case_when(
Category == "Dessert / Sweet" ~ "Dessert/Sweet",
Category %in% c("Lebanese", "Vietnamese", "South American", "Bavarian", "Turkish", "Indian", "Greek", "French", "Asian", "Korean", "Sri Lankan", "German", "Japanese", "Italian", "American", "Fusion") ~ "Cultural",
Category %in% c("BBQ / Meat Enthusiast", "Vegan", "Gluten free", "Healthy") ~ "Dietary",
Category %in% c("Pizza", "Juice", "Seafood", "Burger") ~ "Food type",
is.na(Category) ~ "Other",
TRUE ~ NA_character_
))
data2022_merged <- data2022_merged %>%
mutate(category_color = case_when(
Category == "Dessert / Sweet" ~ "Dessert/Sweet",
Category %in% c("Lebanese", "Vietnamese", "South American", "Bavarian", "Turkish", "Indian", "Greek", "French", "Asian", "Korean", "Sri Lankan", "German", "Japanese", "Italian", "American", "Fusion") ~ "Cultural",
Category %in% c("BBQ / Meat Enthusiast", "Vegan", "Gluten free", "Healthy") ~ "Dietary",
Category %in% c("Pizza", "Juice", "Seafood", "Burger") ~ "Food type",
is.na(Category) ~ "Other",
TRUE ~ NA_character_
))
# cleaning for Lat and Lon errors
data2017_merged_clean <- data2017_merged[complete.cases(data2017_merged[, c("Latitude", "Longitude")]), ]
data2018_merged_clean <- data2017_merged[complete.cases(data2018_merged[, c("Latitude", "Longitude")]), ]
data2019_merged_clean <- data2017_merged[complete.cases(data2019_merged[, c("Latitude", "Longitude")]), ]
data2020_merged_clean <- data2017_merged[complete.cases(data2020_merged[, c("Latitude", "Longitude")]), ]
data2021_merged_clean <- data2017_merged[complete.cases(data2021_merged[, c("Latitude", "Longitude")]), ]
data2022_merged_clean <- data2017_merged[complete.cases(data2022_merged[, c("Latitude", "Longitude")]), ]
# Load required packages
library(leaflet)
library(leaflet.extras)
#install.packages("fontawesome")
library(fontawesome)
# install.packages("shinyWidgets")
library(shinyWidgets)
## Warning: package 'shinyWidgets' was built under R version 4.2.3
library(shiny)
## Warning: package 'shiny' was built under R version 4.2.3
library(dbplyr)
## Warning: package 'dbplyr' was built under R version 4.2.3
##
## Attaching package: 'dbplyr'
## The following objects are masked from 'package:dplyr':
##
## ident, sql
# Define the category colors
category_colors <- c(
"Dessert/Sweet" = "#FFA07A",
"Cultural" = "#B0C4DE",
"Dietary" = "#98FB98",
"Food type" = "#87CEFA",
"Other" = "#D3D3D3"
)
# Define custom icons for each category
icons <- awesomeIcons(
icon = "coffee",
iconColor = "black",
library = "ion",
markerColor = category_colors
)
# generate a map using the 2017 data
map1 <- leaflet(data = data2017_merged_clean) %>%
addTiles() %>%
addAwesomeMarkers(
lat = ~Latitude,
lng = ~Longitude,
icon = icons[data2017_merged_clean$Category],
popup = ~paste(Title, "<br>Location: ", Site.address, "<br>Booking Time: ", ifelse(is.na(Booking.created), "N/A", Booking.created))
) %>%
addLegend(position = "bottomright", colors = category_colors, labels = names(category_colors)) %>%
setView(lng = 153.0251, lat = -27.4698, zoom = 10)
# Create the slider
slider <- sliderTextInput(
inputId = "date",
label = "Select date:",
choices = c("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"),
selected = "Jan"
)
# Create the UI
ui <- fluidPage(
tags$head(tags$style("#map {height: calc(100vh - 80px) !important;}")),
fluidRow(
column(3, slider),
column(9, leafletOutput("map"))
)
)
# Create the server
server <- function(input, output, session) {
output$map <- renderLeaflet({
map1 %>%
filter(substr(data2017_merged_clean$Booking.created, 6, 7) == match(input$date, month.abb))
})
}
# Run the app
shinyApp(ui, server)
##
## Listening on http://127.0.0.1:4321
## Warning: Error in UseMethod: no applicable method for 'filter' applied to an
## object of class "c('leaflet', 'htmlwidget')"
### error thrown out: no applicable method for ‘filter’ applied to an
object of class “c(‘leaflet’, ‘htmlwidget’)”
library(shiny)
library(leaflet)
library(shinyWidgets)
library(dplyr)
# generate a map using the 2017 data
map1 <- leaflet(data = data2017_merged_clean) %>%
addTiles() %>%
addAwesomeMarkers(
lat = ~Latitude,
lng = ~Longitude,
icon = icons[data2017_merged_clean$Category],
popup = ~paste(Title, "<br>Location: ", Site.address, "<br>Booking Time: ", ifelse(is.na(Booking.created), "N/A", Booking.created))
) %>%
addLegend(position = "bottomright", colors = category_colors, labels = names(category_colors)) %>%
setView(lng = 153.0251, lat = -27.4698, zoom = 10)
# Create the slider
slider <- sliderTextInput(
inputId = "date",
label = "Select date:",
choices = c("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"),
selected = "Jan"
)
# Create the UI
ui <- fluidPage(
tags$head(tags$style("#map {height: calc(100vh - 80px) !important;}")),
fluidRow(
column(3, slider),
column(9, leafletOutput("map"))
)
)
# Create the server
server <- function(input, output, session) {
map_data <- reactive({
data2017_merged_clean %>%
filter(substr(Booking.created, 6, 7) == match(input$date, month.abb))
})
output$map <- renderLeaflet({
leaflet(data = map_data()) %>%
addTiles() %>%
addAwesomeMarkers(
lat = ~Latitude,
lng = ~Longitude,
icon = icons[map_data()$Category],
popup = ~paste(Title, "<br>Location: ", Site.address, "<br>Booking Time: ", ifelse(is.na(Booking.created), "N/A", Booking.created))
) %>%
addLegend(position = "bottomright", colors = category_colors, labels = names(category_colors)) %>%
setView(lng = 153.0251, lat = -27.4698, zoom = 10)
})
}
# Run the app
shinyApp(ui, server)
##
## Listening on http://127.0.0.1:8140
### map generated. iterate this for each dataset. but, object
‘leaflet_html’ not found (problem with knitting)
# Create blank map
map <- leaflet() %>%
addTiles() %>%
setView(lng = 153.0251, lat = -27.4698, zoom = 10)
# Add markers for each year
map <- addAwesomeMarkers(
map = map, data = data2017_merged_clean,
lat = ~Latitude,
lng = ~Longitude,
icon = icons[data2017_merged_clean$Category],
popup = ~paste(Title, "<br>Location: ", Site.address, "<br>Booking Time: ", ifelse(is.na(Booking.created), "N/A", Booking.created))
)
map <- addAwesomeMarkers(
map = map, data = data2018_merged_clean,
lat = ~Latitude,
lng = ~Longitude,
icon = icons[data2018_merged_clean$Category],
popup = ~paste(Title, "<br>Location: ", Site.address, "<br>Booking Time: ", ifelse(is.na(Booking.created), "N/A", Booking.created))
)
## Warning in validateCoords(lng, lat, funcName): Data contains 386 rows with
## either missing or invalid lat/lon values and will be ignored
map <- addAwesomeMarkers(
map = map, data = data2019_merged_clean,
lat = ~Latitude,
lng = ~Longitude,
icon = icons[data2019_merged_clean$Category],
popup = ~paste(Title, "<br>Location: ", Site.address, "<br>Booking Time: ", ifelse(is.na(Booking.created), "N/A", Booking.created))
)
## Warning in validateCoords(lng, lat, funcName): Data contains 667 rows with
## either missing or invalid lat/lon values and will be ignored
map <- addAwesomeMarkers(
map = map, data = data2020_merged_clean,
lat = ~Latitude,
lng = ~Longitude,
icon = icons[data2020_merged_clean$Category],
popup = ~paste(Title, "<br>Location: ", Site.address, "<br>Booking Time: ", ifelse(is.na(Booking.created), "N/A", Booking.created))
)
## Warning in validateCoords(lng, lat, funcName): Data contains 81 rows with
## either missing or invalid lat/lon values and will be ignored
map <- addAwesomeMarkers(
map = map, data = data2021_merged_clean,
lat = ~Latitude,
lng = ~Longitude,
icon = icons[data2021_merged_clean$Category],
popup = ~paste(Title, "<br>Location: ", Site.address, "<br>Booking Time: ", ifelse(is.na(Booking.created), "N/A", Booking.created))
)
## Warning in validateCoords(lng, lat, funcName): Data contains 39 rows with
## either missing or invalid lat/lon values and will be ignored
map <- addAwesomeMarkers(
map = map, data = data2022_merged_clean,
lat = ~Latitude,
lng = ~Longitude,
icon = icons[data2022_merged_clean$Category],
popup = ~paste(Title, "<br>Location: ", Site.address, "<br>Booking Time: ", ifelse(is.na(Booking.created), "N/A", Booking.created))
)
## Warning in validateCoords(lng, lat, funcName): Data contains 39 rows with
## either missing or invalid lat/lon values and will be ignored
# Add control to toggle between years
map <- addLayersControl(
map = map,
baseGroups = c("2017", "2018", "2019", "2020", "2021", "2022"),
overlayGroups = NULL,
options = layersControlOptions(
collapsed = FALSE,
position = "topright"
)
)
# Show map
map
References: BNE Food Trucks (2018) Brisbane Food Trucks: an initiative of Brisbane City Council, BFT website, accessed 10 April, 2023. https://www.bnefoodtrucks.com.au/food-trucks
Brisbane City Council (2022) Brisbane Food Trucks — Historical Bookings — 2017 — CSV [data set], BCC website, accessed 3 April 2023. https://www.data.brisbane.qld.gov.au/data/dataset/brisbane-food-trucks-historical-bookings
Brisbane City Council (2022) Brisbane Food Trucks — Historical Bookings — 2018 — CSV [data set], BCC website, accessed 3 April 2023. https://www.data.brisbane.qld.gov.au/data/dataset/brisbane-food-trucks-historical-bookings
Brisbane City Council (2022) Brisbane Food Trucks — Historical Bookings — 2019 — CSV [data set], BCC website, accessed 3 April 2023. https://www.data.brisbane.qld.gov.au/data/dataset/brisbane-food-trucks-historical-bookings
Brisbane City Council (2022) Brisbane Food Trucks — Historical Bookings — 2020 — CSV [data set], BCC website, accessed 3 April 2023. https://www.data.brisbane.qld.gov.au/data/dataset/brisbane-food-trucks-historical-bookings
Brisbane City Council (2022) Brisbane Food Trucks — Historical Bookings — 2021 — CSV [data set], BCC website, accessed 3 April 2023. https://www.data.brisbane.qld.gov.au/data/dataset/brisbane-food-trucks-historical-bookings
Brisbane City Council (2022) Brisbane Food Trucks — Historical Bookings — 2022 — CSV [data set], BCC website, accessed 3 April 2023. https://www.data.brisbane.qld.gov.au/data/dataset/brisbane-food-trucks-historical-bookings