Fiddling with the GMT Open Data Portal

Author

Tin Skoric

Published

August 2026

1 Impetus

Green Mountain Transit (GMT) is a public transit operator serving routes in Northern Vermont, largely centered around the Burlington metro area. Having lived in Burlington throughout my years at the University of Vermont, and now returning post-grad school, I have had the pleasure of being at least passingly familiar with GMT. What I did not know, and what I think is pretty cool, is that GMT publishes some of its data to a nice and straightforward online portal, providing a number of cool charts and maps to pair with datasets detailing things like ridership over the past few years. The site is also obviously made with Quarto, using the default theming, and I have a hunch that the table outputs are from R with the DT package, both things naturally making the site cooler because I like R and Quarto. Now, for absolutely no reason at all, I stumbled upon this site and thought it would be fun to fiddle with the data available to present some more cool figures/charts. I certainly was not searching up GMT data sources to do something like this because there was an opening there to get to work with this stuff for money. That’s crazy; don’t think like that. As an aside, while fiddling, I came to further appreciate just how big of a role UVM (the college-attending population at-large) plays in Burlington; even down to its visible impacts on the data seen here.

2 Fiddling

2.1 Data

The datasets available include ridership, fare transactions, and estimated boardings by stop. These are broken down by month and route/group/stop (respectively), with data available for fiscal year 2026 for all three. Ridership data is available for fiscal year 2025 as well, but the others are only available for 2026 onward. The fiscal year for GMT begins on July 1st, so 2026 covers July 2025 through June 2026. All of the datasets also have data for July 2026 available as part of their 2027 releases. We can combine what periods are available across each to land on one covering July 1st, 2025 to July 31st, 2026 for all 3. Below is the R code for loading the data I snagged from the open data website (click to expand the callout block). Not all of the data loaded is even used here, I was kind of figuring out what looked cool/useful as I was going along; if you are prudish about copy-pasting the same code over and over instead of being ultra-clean and making 20 functions then avert your eyes.

# Libraries and my custom ggplot theme
source("Tools.R")
# theme_can()

# dateString is a nicely-formatted date to use in plots and the like
dateStringGMT <- function(df){
  df <- df |> group_by(month, year) |> 
    mutate(dateString = paste0(month, "\n", year)) |>
    ungroup() |> select(-month) |> rename(month = monthNum) |> 
    arrange(year, month) |> select(year, month, dateString, everything())
  return(df)
}

ridership <- read.csv("gmt-opendata/fy26_urban_ridership.csv") |>
  rowwise() |> 
  mutate(
    monthNum = which(month.name %in% month),
    year = ifelse(monthNum < 7, 2026, 2025)
  ) |> ungroup() |>
  rbind(
    read.csv("gmt-opendata/fy27_urban_ridership.csv") |>
      mutate(monthNum = 7, year = 2026)
  ) |> dateStringGMT()
fare_groups <- read.csv("gmt-opendata/fy26_fare_group_monthly_totals_by_route.csv") |> 
  rowwise() |> 
  mutate(
    monthNum = month,
    month = month.name[monthNum],
    year = ifelse(monthNum < 7, 2026, 2025)
  ) |> ungroup() |> 
  rbind(
    read.csv("gmt-opendata/fy27_fare_group_monthly_totals_by_route.csv") |> 
      mutate(monthNum = month, month = month.name[monthNum], year = 2026)
  ) |> dateStringGMT()
fare_sources <- read.csv("gmt-opendata/fy26_fare_funding_source_monthly_totals.csv") |> 
  rowwise() |> 
  mutate(
    monthNum = month,
    month = month.name[monthNum],
    year = ifelse(monthNum < 7, 2026, 2025)
  ) |> ungroup() |> 
  rbind(
    read.csv("gmt-opendata/fy27_fare_funding_source_monthly_totals.csv") |> 
      mutate(monthNum = month, month = month.name[monthNum], year = 2026)
  ) |> dateStringGMT()
fare_partners <- read.csv("gmt-opendata/partner_data.csv") |> 
  select(month_year, UVM) |> mutate(
    year = substr(month_year, 1, 4),
    monthNum = as.numeric(substr(month_year, 6, 7)),
    month = month.name[monthNum]
  ) |> dateStringGMT() |> select(-month_year)
boardings_est <- read_csv("gmt-opendata/fy26_boardings_by_stop.csv") |>
  rowwise() |> 
  mutate(
    monthNum = which(month.name %in% month),
    year = ifelse(monthNum < 7, 2026, 2025)
  ) |> ungroup() |>
  rbind(
    read.csv("gmt-opendata/fy27_boardings_by_stop.csv") |>
      mutate(monthNum = 7, year = 2026)
  ) |> dateStringGMT()

2.2 Some Basic Visualizations

Below are some basic (but fun!) charts for each of the groups of data just to see what we’re working with; later on we’ll get to some more complex ones. The charts in this tabset are just ones that were easy to do and that I didn’t notice among the many already on the site.

For these ridership charts, I constructed an extra datapoint called “Average Weekday Ridership,” which is calculated by taking the weekday ridership figures from the existing data and dividing them by 5. This, obviously, does not reflect the actual ridership trends for a given day of the week (I’d suppose that some weekdays people may ride the bus more) but it is helpful to have a general comparison of weekday ridership compared to Saturday and Sunday. Also, like elsewhere on the site, just click on the plots to expand them.

What are some easy takeaways? Well we can see ridership fluctuate pretty distinctly along the periods of the Fall and Spring semesters at UVM (and to a lesser extent, Champlain College). We can also see that ridership overall among the two most trafficked routes (1 and 2) are down over the past year when compared to the year prior; route 1 especially.

(a) Passenger Boarding Totals by Month (All Routes)
(b) Passenger Boarding Totals by Route and Year
Figure 1: Ridership Charts
# all routes
ridership_month <- ridership |> group_by(year, month) |>
  summarize(
    dateString = unique(dateString),
    total_monthly_ridership = sum(total_ridership),
    anyweekday_monthly_ridership = sum(weekday_monthly_ridership)/5,
    weekday_monthly_ridership = sum(weekday_monthly_ridership),
    saturday_monthly_ridership = sum(saturday_monthly_ridership),
    sunday_monthly_ridership = sum(sunday_monthly_ridership),
    total_monthly_vrh = sum(total_vrh),
    anyweekday_monthly_vrh = sum(weekday_monthly_vrh)/5,
    weekday_monthly_vrh = sum(weekday_monthly_vrh),
    saturday_monthly_vrh = sum(saturday_monthly_vrh),
    sunday_monthly_vrh = sum(sunday_monthly_vrh)
  ) |> ungroup() |> arrange(year, month) |> 
  pivot_longer(
    cols = total_monthly_ridership:sunday_monthly_vrh,
    names_to = "datapoint",
    values_to = "observation"
  ) |> group_by(datapoint) |> mutate(n = row_number()) |> 
  ungroup()
# by route + year
ridership_route <- ridership |> group_by(year, route) |>
  summarize(
    year = unique(year),
    total_route_ridership = sum(total_ridership),
    anyweekday_route_ridership = sum(weekday_monthly_ridership)/5,
    weekday_route_ridership = sum(weekday_monthly_ridership),
    saturday_route_ridership = sum(saturday_monthly_ridership),
    sunday_route_ridership = sum(sunday_monthly_ridership),
    total_route_vrh = sum(total_vrh),
    anyweekday_route_vrh = sum(weekday_monthly_vrh)/5,
    weekday_route_vrh = sum(weekday_monthly_vrh),
    saturday_route_vrh = sum(saturday_monthly_vrh),
    sunday_route_vrh = sum(sunday_monthly_vrh)
  ) |> ungroup() |> arrange(year, route) |> 
  pivot_longer(
    cols = total_route_ridership:sunday_route_vrh,
    names_to = "datapoint",
    values_to = "observation"
  ) |> group_by(datapoint) |> mutate(n = row_number()) |> 
  ungroup()

ggplot() +
  geom_col(
    data = ridership_month |>
      filter(
        datapoint %in% c(
          "anyweekday_monthly_ridership",
          "saturday_monthly_ridership",
          "sunday_monthly_ridership"
        )
      ),
    mapping = aes(x = n, y = observation, fill = datapoint),
    position = "stack"
  ) +
  theme_can() + 
  scale_x_continuous(
    "",
    breaks = seq(1, 13, 2),
    labels = unique(ridership_month$dateString)[seq(1, 13, 2)]
  ) +
  scale_y_continuous(
    "Ridership",
    limits = c(0, 60000),
    breaks = seq(0, 60000, 5000),
    expand = c(0, 0)
  ) +
  scale_fill_manual(
    "",
    breaks = c(
      "anyweekday_monthly_ridership",
      "saturday_monthly_ridership",
      "sunday_monthly_ridership" 
    ),
    labels = c(
      "Average Weekday\nRidership",
      "Saturday Ridership",
      "Sunday Ridership"
    ),
    values = unlist(unname(canned_colors_list()$compliments))
  ) +
  labs(
    caption = "Source: GMT Ridership Tracker"
  ) +
  theme(
    legend.position = "top",
    axis.text.y = element_text(family = "Space Grotesk", size = 12, color = "#4c48a3", hjust = 1)
  )

ggplot() +
  geom_col(
    data = ridership_route |>
      filter(
        datapoint %in% c(
          "anyweekday_route_ridership",
          "saturday_route_ridership",
          "sunday_route_ridership"
        ),
        route %in% c(1, 2)
      ) |> mutate(route = paste0("Route ", route)),
    mapping = aes(x = factor(year), y = observation, fill = datapoint),
    position = "stack"
  ) +
  facet_grid(. ~ factor(route)) +
  theme_can() + 
  scale_x_discrete("") +
  scale_y_continuous(
    "Ridership",
    limits = c(0, 100000),
    breaks = seq(0, 100000, 25000),
    expand = c(0, 0)
  ) +
  scale_fill_manual(
    "",
    breaks = c(
      "anyweekday_route_ridership",
      "saturday_route_ridership",
      "sunday_route_ridership" 
    ),
    labels = c(
      "Average Weekday\nRidership",
      "Saturday Ridership",
      "Sunday Ridership"
    ),
    values = unlist(unname(canned_colors_list()$compliments))
  ) +
  labs(
    caption = "Source: GMT Ridership Tracker"
  ) +
  theme(
    legend.position = "top",
    strip.text = element_text(color = canned_colors_get(,"softpurple"), size = 16),
    strip.background = element_rect(fill = "transparent", color = canned_colors_get(,"transparent")),
    axis.text.y = element_text(family = "Space Grotesk", size = 12, color = "#4c48a3", hjust = 1)
  )

We see a notable change in the proportions of fare groups and funding sources that is mirrored by the Fall and Spring semesters; specifically seeing a rise in sponsored fares and partnered funding, respectively. If we look at the data for partner fare transactions via UVM, we see this same thing.

(a) Proportion of Fare Groups by Route and Year
(b) Passenger Boarding Totals by Route (By Year)
(c) Monthly Partner Fare Transactions (UVM)
Figure 2: Fare Transactions
fare_groups_chart_1 <- fare_groups |> group_by(route, fare_group) |> 
      mutate(n = row_number()) |> ungroup() |>  
      filter(route %in% c(1, 2)) |> mutate(route = paste0("Route ", route))

fare_sources_chart_1 <- fare_sources |> group_by(fare_funding_source) |> 
      mutate(n = row_number()) |> ungroup()

ggplot() +
  geom_col(
    data = fare_groups_chart_1,
    mapping = aes(x = n, y = count, fill = fare_group),
    position = "fill", width = 0.9
  ) +
  facet_grid(. ~ factor(route)) +
  theme_can() + 
  scale_x_continuous(
    "",
    breaks = seq(1, 13, 2),
    labels = unique(fare_groups_chart_1$dateString)[seq(1, 13, 2)],
    expand = c(0, 0)
  ) +
  scale_y_continuous(
    "Proportion of Fare Transactions",
    expand = c(0, 0)
  ) +
  scale_fill_manual(
    "",
    breaks = c(
      "Sponsored Fare",
      "Discounted Fare",
      "Regular Fare" 
    ),
    labels = c(
      "Sponsored Fare",
      "Discounted Fare",
      "Regular Fare" 
    ),
    values = unlist(unname(canned_colors_list()$compliments))
  ) +
  labs(
    caption = "Source: GMT Ridership Tracker"
  ) +
  theme(
    legend.position = "top",
    strip.text = element_text(color = canned_colors_get(,"softpurple"), size = 16),
    strip.background = element_rect(fill = "transparent", color = canned_colors_get(,"transparent")),
    axis.text.y = element_text(family = "Space Grotesk", size = 12, color = "#4c48a3", hjust = 1)
  )

ggplot() +
  geom_col(
    data = fare_sources_chart_1,
    mapping = aes(x = n, y = count, fill = fare_funding_source),
    position = "fill", width = 0.9
  ) +
  theme_can() + 
  scale_x_continuous(
    "",
    breaks = seq(1, 13, 2),
    labels = unique(fare_groups_chart_1$dateString)[seq(1, 13, 2)],
    expand = c(0, 0)
  ) +
  scale_y_continuous(
    "Proportion of Fare Sources",
    expand = c(0, 0)
  ) +
  scale_fill_manual(
    "",
    breaks = c(
      "Agency",
      "Fare",
      "Fare Cap",
      "Partner"
    ),
    labels = c(
      "Agency",
      "Fare",
      "Fare Cap",
      "Partner"
    ),
    values = unlist(unname(canned_colors_list()$compliments))
  ) +
  labs(
    caption = "Source: GMT Ridership Tracker"
  ) +
  theme(
    legend.position = "top",
    strip.text = element_text(color = canned_colors_get(,"softpurple"), size = 16),
    strip.background = element_rect(fill = "transparent", color = canned_colors_get(,"transparent")),
    axis.text.y = element_text(family = "Space Grotesk", size = 12, color = "#4c48a3", hjust = 1)
  )

ggplot() +
  geom_col(
    data = fare_partners |> mutate(n = row_number()),
    mapping = aes(x = n, y = UVM),
    position = "identity", fill = canned_colors_get(, "danger")
  ) +
  theme_can() + 
  scale_x_continuous(
    "",
    breaks = seq(1, 12, 2),
    labels = unique((fare_partners |> mutate(n = row_number()))$dateString)[seq(1, 12, 2)]
  ) +
  scale_y_continuous(
    "Ridership",
    limits = c(0, 80000),
    breaks = seq(0, 80000, 10000),
    expand = c(0, 0)
  ) +
  labs(
    caption = "Source: GMT Ridership Tracker"
  ) +
  theme(
    legend.position = "top",
    axis.text.y = element_text(family = "Space Grotesk", size = 12, color = "#4c48a3", hjust = 1)
  )

This dataset in particular is fantastic because and has some lovely stuff that I can and will use later for cooler visualizations1, but for now let’s keep it simple. Below is a single chart to show an interesting thing. We’ve grouped by stop_id and split it over by year to find the difference in sum total annual estimated boardings among stops. We drop stop_id values with less than 1000 boardings in a whole year and/or are only seen in one year to keep things sane. We take this, and identify the 20 stops with the largest decline in estimated boardings from 2025 to 2026. Instead of putting the full addresses of those stops in the chart, I’ve added a table to the right for reference.

Figure 3: Total Annual Boarding Estimates by Stop
Table 1: Table of Stops Seen in Figure 3
stop_id stop_name
805484 S. Winooski Avenue at Bank Street
805490 University Heights
805492 Dorset Street at Peoples United Bank
805529 Dorset Street at Barnes & Noble
805531 University Heights
805947 Dorset Street at Blue Mall
805756 Colchester Avenue at Fleming Museum
805815 College Parkway at Campus Rd. (Saint Mikes West Entrance)
805606 S Union Street at Opposite King Street
4255578 South Burlington City Center
boardings_chart_1 <- boardings_est |> group_by(year, stop_id) |> 
  summarize(
    year = unique(year),
    total_boardings = sum(total_boardings, na.rm = TRUE)
  ) |> ungroup() |> filter(total_boardings > 999) |> 
  pivot_wider(
    names_from = year,
    values_from = total_boardings
  ) |> 
  mutate(diff_from_2025 = `2026` - `2025`) |> 
  filter(!is.na(diff_from_2025)) |> 
  pivot_longer(
    cols = c(`2025`, `2026`),
    names_to = "year",
    values_to = "observation",
  )

ggplot() +
  geom_col(
    data = boardings_chart_1 |> arrange(diff_from_2025) |> head(20) |> mutate(stop_id = paste0("Stop\n", stop_id)),
    mapping = aes(x = reorder(stop_id, observation), y = observation, fill = year),
    position = "identity"
  ) +
  theme_can() + 
  scale_x_discrete(
    ""
  ) +
  scale_y_continuous(
    "Estimated Boardings",
    limits = c(0, 50000),
    breaks = seq(0, 50000, 10000),
    expand = c(0, 0)
  ) +
  scale_fill_manual(
    "",
    breaks = c(
      "2025",
      "2026"
    ),
    labels = c(
      "Estimated Boardings Across 2025",
      "Estimated Boardings Across 2026"
    ),
    values = c(canned_colors_get(,"danger"), canned_colors_get(,"info"))
  ) +
  labs(
    caption = "Source: GMT Ridership by Stop"
  ) +
  theme(
    legend.position = "top",
    axis.text.y = element_text(family = "Space Grotesk", size = 12, color = "#4c48a3", hjust = 1)
  )

boardings_est |> select(stop_id, stop_name) |> 
  filter(stop_id %in% (boardings_chart_1 |> arrange(diff_from_2025) |> head(20) |> arrange(stop_id))[[1]]) |> 
  unique() |> mutate(stop_id = cell_spec(stop_id, color = "#191715"), stop_name = cell_spec(stop_name, color = "#191715")) |>  kable(format = "html", escape = FALSE) |> 
  row_spec(0, color = "#191715", bold = TRUE)

2.3 Cool Stuff

2.3.1 A Map!

Pretty self-explanatory; below is a map that is somewhat similar to the “Ridership by Stop” ArcGIS dashboard from GMT (seen here), but focuses on a greater crop of stop_ids. We take the data for our whole period (July 2025 to July 2026) and plot all stop_id observations with greater than 999 total boardings in that year. Hovering over any one stop will reveal a tooltip detailing the estimated boardings by route at that stop. You can select routes to filter for across stops and you can pan/zoom to your heart’s content; you probably will want to do so because I made the dots pretty small.

basemap_land <- counties("Vermont")
basemap_water <- area_water("Vermont", c("Addison", "Chittenden", "Franklin", "Grand Isle", "Lamoille", "Orleans", "Washington"))
boardings_est_fy2026 <- boardings_est |> 
  select(-year) |> group_by(route, stop_id) |> 
  summarize(
    total_boardings = sum(total_boardings)
  ) |> ungroup() |> 
  left_join(
    boardings_est |> filter(year == 2026) |> select(stop_id, stop_name, municipality, latitude, longitude),
    by = "stop_id"
  ) |> distinct()
boardings_est_fy2026_tooltip <- boardings_est_fy2026 |> 
  group_by(stop_id, stop_name) |> 
  summarize(
    stop_tooltip = paste0(
      "<p>", stop_name[1], "</p>",
      "<p>ID: ", stop_id[1], "</p>",
      "<p>Boardings across 7/2025 through 7/2026: ", paste0("<br>&emsp;&emsp; • Route ", route, ": ", total_boardings, collapse = ""), "</p>"
    )
  )
boardings_est_fy2026 <- boardings_est_fy2026 |> 
  left_join(
    boardings_est_fy2026_tooltip |> select(stop_id, stop_tooltip),
    by = "stop_id"
  )
boardings_est_fy2026_sf <- st_as_sf(boardings_est_fy2026 |> filter(!is.na(longitude), !is.na(longitude)), coords = c("longitude", "latitude"), crs = 4326)
gmt_service_area <- st_bbox(boardings_est_fy2026_sf)
roads_gmt_service_area <- roads("VT", c("Addison", "Chittenden", "Franklin", "Grand Isle", "Lamoille", "Orleans", "Washington"))

# dropdown for the map
route_i <- split((boardings_est |> distinct(stop_id, route))$stop_id,(boardings_est |> distinct(stop_id, route))$route)
dropdown_js <- paste0('
function(map_element, x) {
    var routes = ', jsonlite::toJSON(route_i), ';
    var select_input = document.createElement("select");
    var selected_option = document.createElement("option");
    selected_option.value = ""; selected_option.textContent = "All Stops";
    select_input.appendChild(selected_option);
    Object.keys(routes).forEach(function(r) {
      var o = document.createElement("option");
      o.value = r;
      o.textContent = r;
      select_input.appendChild(o);
    });
    select_input.style.margin = "2px";
    select_input.style.fontFamily = "Space Grotesk";
    select_input.style.backgroundColor = "#3a355f";
    select_input.style.color = "#faebd7";
    map_element.insertBefore(select_input, map_element.firstChild);
    map_element.addEventListener("change", function(new_selection) {
      if (new_selection.target !== select_input) return;
      var raw = routes[select_input.value];
      var set = raw ? new Set(raw.map(String)) : null;
      map_element.querySelectorAll("[data-id]").forEach(function(node) {
        node.style.visibility =
          (!set || set.has(node.getAttribute("data-id"))) ? "" : "hidden";
      });
    });
}')
(ggplot() +
  geom_sf(
    data = basemap_land,
    mapping = aes(),
    size = 0, fill = "#191715", color = "#191715"
  ) +
  geom_sf(
    data = basemap_water,
    mapping = aes(),
    size = 0.1, fill = "#2624d1", color = "#2624d1"
  ) +
  geom_sf(
    data = roads_gmt_service_area,
    mapping = aes(),
    size = 1, fill = "#faebd7", color = "#faebd7"
  ) +
  geom_sf_interactive(
    data = boardings_est_fy2026_sf,
    mapping = aes(
      tooltip = stop_tooltip,
      data_id = stop_id
    ),
    color = canned_colors_get(,"danger"), size = 24
  ) + 
  coord_sf(
    xlim = c(gmt_service_area[["xmin"]] - 0.3, gmt_service_area[["xmax"]] + 0.05),
    ylim = c(gmt_service_area[["ymin"]], gmt_service_area[["ymax"]] + 0.1)
  ) +
  theme_can_map() + theme(legend.position = "none")) |> 
    girafe(
      bg = "transparent",
      width_svg = 200, height_svg = 200,
      options = list(
        opts_zoom(max = 5, default_on = TRUE),
        opts_toolbar(
          position = "topleft",
          hidden = c(
            "selections", "lasso_select", "lasso_deselect",
            "zoom_onoff", "zoom_rect", "fullscreen"
          )
        ),
        opts_tooltip(
          css = "background: #191715; color: #faebd7; padding: 1px;"
        )
      )
    ) |> 
  htmlwidgets::onRender(dropdown_js)

2.3.2 A Visnet of Major Stops

Below is a pretty barebones (it was late and I was tired) visnet of the same data we were looking at before, but here we are truncating things down to stops with at least 30,000 estimated boardings across all routes. You may notice that other than the Downtown Transit Center (the literal heart of GMT operations by pure presence), these are almost all UVM-related places… UVM is pretty big! Sidenote: there are two “University Heights” here because they are different stop_ids; I am guessing University Heights North and South.

visnet_nodes <- boardings_est_fy2026 |> 
  select(route, stop_id, total_boardings, stop_name) |> 
  group_by(stop_id) |> 
  summarize(value = sum(total_boardings), label = unique(stop_name)) |> 
  ungroup() |> filter(value > 29999) |> 
  mutate(
    id = stop_id,
    label = label,
    value = value,
    title = paste0(
      "<p>", label, "</p>",
      "<p>ID: ", id, "</p>",
      "<p>Total Boardings across 7/2025 through 7/2026: ", value, "</p>"
    )
  )
visnet_edges <- boardings_est_fy2026 |> group_by(route) |> 
  group_split() |> lapply(\(grp) as.data.frame(t(combn(grp$stop_id, 2)))) |> 
  do.call(what = rbind) |> mutate(from = V1, to = V2) |> group_by(from, to) |> 
  mutate(weight = n()) |> ungroup() |> distinct(from, to, .keep_all = TRUE)
visNetwork(visnet_nodes, visnet_edges) |>
  visNodes() |>
  visOptions(
    highlightNearest = TRUE, nodesIdSelection = list(
      enabled = FALSE,
      style = "width: 25%; height: 30px; background: transparent;
    color: #1a124e; border: none; outline: none;" 
    )
  ) |> 
  visEdges(smooth = FALSE) |>
  visPhysics(stabilization = TRUE) |>
  visInteraction(hover = TRUE, tooltipDelay = 0, tooltipStay = Inf)

3 UVM is Pretty Big

As a short, last, note on this whole thing, this affair was obviously not a very deep dive and mostly served as an excuse to have some fun with data but it should be pretty clear even from what we have done that UVM continues to play a big role in Burlington; even for GMT. You can see the students come and go in the ridership numbers, and many of the biggest stops are places the students are.

Footnotes

  1. It is very nice that the data already has the lat/long coordinates there instead of forcing me to use nominatim with the address column. Thank you to whoever prepared this dataset for release and know that a lot of important housing data published in the state of Vermont DOESN’T EVEN DO THIS!↩︎