Skip to contents

Say you are moving to a new city and want to live near the amenities that are important to you. In this tutorial, we find the blocks that are within a 10-minute walk of a supermarket, a 5-minute walk of a restaurant, and a 20-minute walk of a frequent-transit stop. Then, we narrow those blocks to the overlap of two commutes. The example city is Somerville, Massachusetts.

Running this tutorial uses about 2,600 tokens.

Set up

Build a client, then read the pieces you need from the free catalog instead of memorising codes.

library(closecity)
library(sf)
close <- closecity::close_client(api_key = "ck_live_your_key")   # use your own key here
# The catalog lists every category with its numeric id. Pull the ids you need.
amenity_types <- close$destination_types()
ids <- setNames(amenity_types$dest_type_id, amenity_types$label)

supermarket_dest_id <- ids[["grocery_stores"]]
restaurant_dest_id <- ids[["restaurants"]]
freq_transit_stop_dest_id <- ids[["frequent_transit"]]

# Turn the city name into a GEOID and pull its boundary for context.
city <- close$places(q = "Somerville")[1, ]
city_boundary <- close$place_boundary(geoid = city$geoid)

See what is around

Look at the raw ingredients first: every supermarket, restaurant, and frequent-transit stop within Somerville, from $place_pois(). The city boundary, not a guessed radius, is the edge. Give each category a colour and map them together.

supermarkets <- close$place_pois(geoid = city$geoid, type = supermarket_dest_id)
restaurants <- close$place_pois(geoid = city$geoid, type = restaurant_dest_id)
stops <- close$place_pois(geoid = city$geoid, type = freq_transit_stop_dest_id)

supermarkets$kind <- "Supermarket"
restaurants$kind <- "Restaurant"
stops$kind <- "Transit stop"
around <- rbind(supermarkets, restaurants, stops)

palette <- c(Supermarket = "#058040", Restaurant = "#c6cbe0", `Transit stop` = "#f36e21")
closecity::close_map(
  x = around,
  color = palette[around$kind],
  label = "kind",
  boundary = city_boundary
)

Find the blocks that qualify

Somerville is a census place, so one call by place GEOID pulls the per-block walk times for every block in the city. $place_blocks() reads every page and returns one sf row per (block, category); block boundaries come from tigris, downloaded once and cached. (To search an arbitrary area instead, use $blocks_query() with a centre and radius or a polygon. We do that with a radius in the other tutorials only to keep their token cost low; a place GEOID pulls the whole city.)

blocks <- close$place_blocks(
  geoid = city$geoid,
  mode = "walk",
  type = c(supermarket_dest_id, restaurant_dest_id, freq_transit_stop_dest_id)
)

Reshape to one row per block, with a walk-time column for each amenity, so a block carries all three times at once (and the hover on the map shows them). Then flag the blocks that pass every rule.

city_blocks <- blocks[!duplicated(blocks$geoid), "geoid"]
time_to <- function(type_id) {
  sub <- blocks[blocks$dest_type_id == type_id, ]
  setNames(sub$travel_time, sub$geoid)[city_blocks$geoid]
}
city_blocks$supermarket_min <- time_to(supermarket_dest_id)
city_blocks$restaurant_min <- time_to(restaurant_dest_id)
city_blocks$transit_min <- time_to(freq_transit_stop_dest_id)

city_blocks$qualifies <- (city_blocks$supermarket_min <= 10 &
                          city_blocks$restaurant_min <= 5 &
                          city_blocks$transit_min <= 20)
city_blocks$qualifies[is.na(city_blocks$qualifies)] <- FALSE

Show every block in the city, highlight the ones that qualify, and hover any block to read its walk time to each amenity.

closecity::close_map(
  x = city_blocks,
  highlight = "qualifies",
  color = "#f36e21",
  boundary = city_boundary
)

Narrow to a shared commute

Suppose two of you work in different places. A transit isochrone from each workplace shows how far each commute reaches; drawn together, half-transparent, you can see both at once.

work_a <- close$isochrone(
  lon = -71.0865,
  lat = 42.3625,
  mode = "transit",
  direction = "from",
  minutes = 20,
  format = "geojson"
)
work_b <- close$isochrone(
  lon = -71.0589,
  lat = 42.3555,
  mode = "transit",
  direction = "from",
  minutes = 20,
  format = "geojson"
)

closecity::close_map(
  x = work_a,
  color = "#058040",
  opacity = 0.5,
  background = work_b,
  background_color = "#f36e21",
  background_opacity = 0.5
)

Keep the qualifying blocks that also sit inside both commutes. The final map shows those winning blocks, with the shortlist (inside both commutes) highlighted, over the two commute walksheds.

both_commutes <- sf::st_intersection(sf::st_union(work_a), sf::st_union(work_b))
winners <- city_blocks[city_blocks$qualifies, ]
winners$shortlist <- sf::st_intersects(winners, both_commutes, sparse = FALSE)[, 1]

closecity::close_map(
  x = winners,
  highlight = "shortlist",
  color = "#1f78b4",
  boundary = city_boundary,
  background = list(work_a, work_b),
  background_color = c("#058040", "#f36e21"),
  background_fill = FALSE
)