Get started

closecity reads the Close API: travel times from every US census block to nearby places, on foot, by bike, and by public transit. This page is a short tour; the three tutorials go further. The full list of query methods is on the API reference, and the wider API is documented at docs.close.city.

Key terms

A few terms come up throughout:

  • Census block. The smallest area the Census Bureau publishes. Each one has a 15-digit id, its GEOID. Block GEOIDs come from the census. Look them up with pygris or the Census Bureau geocoder/API, or read them straight off Close’s block routes (blocks_query, place_blocks).

  • Destination type. A category of place, such as grocery stores or libraries. Every type has a numeric id.

  • Mode. How someone travels: walk, bike, or transit.

  • Isochrone or catchment: the area you can reach starting from a point within a time limit, by a selected travel mode.

Travel times

Times to nearby places are capped at 30 minutes for each mode, and recorded in whole minutes. A missing time means the place is not reachable within the cap, not that it is zero. Isochrones are the exception: they are available for any budget up to an hour.

Build a client

You make every request through a client.

from closecity import Client, close_map

close = Client("ck_live_your_key")   # use your own key here

The catalog and lookup routes are free, so Client() with no key also works for those.

close.modes()
mode_id mode description
0 1 walk Walking
1 2 bike Biking
2 3 transit Public transit

Look things up instead of guessing

Two free calls save you from memorising codes. Both come back as data frames, so you filter and index them the usual way: read the numeric id for a category from the catalog, and turn a city name into a GEOID and a centre point.

amenity_types = close.destination_types()
supermarket_type = amenity_types.loc[amenity_types["label"] == "grocery_stores",
                                     "dest_type_id"].iloc[0]

providence_ri = close.places(q = "Providence").iloc[0]
providence_ri[["name", "state", "geoid"]]
name     Providence
state            RI
geoid       4459000
Name: 0, dtype: object

The catalog’s name column is the readable label (“Grocery stores”); the underscored label is the internal key you match on. A place lookup carries a state, so you can tell Providence, RI from the one in Utah. When you have a point rather than a block, point_summary(lat = , lon = ) reads the same travel times for a lat/lon starting point instead of a GEOID.

Make a call and map it

Routes with geometry return a GeoDataFrame. close_map() draws it on an interactive basemap in one line: bright, hoverable points here, with the city boundary behind them and the view zoomed to fit.

supermarkets = close.place_pois(geoid = providence_ri["geoid"], type = supermarket_type)
city_boundary = close.place_boundary(geoid = providence_ri["geoid"])
close_map(supermarkets, color = "#e8590c", boundary = city_boundary, label = "name")

Choose an output

Set output on the client, or per call:

  • output = "spatial" (the default) returns a GeoDataFrame for inherently spatial data and a DataFrame otherwise. Block routes join census-block boundaries with pygris (the tiger extra), downloaded once and cached.

  • output = "tabular" returns a plain DataFrame for every route and never downloads boundaries. Reach for it when you only want the numbers.

  • output = "raw" returns the underlying Reply / Paginator, with the parsed body on .data and the token counts alongside.

A block summary, with the readable category names merged on and sorted by time:

walk_times = close.block_summary(geoid = "440070008001068", mode = "walk")
walk_times = walk_times.merge(
    amenity_types[["dest_type_id", "name"]],
    on = "dest_type_id"
)
walk_times.sort_values("travel_time")[["name", "travel_time"]]
name travel_time
27 Non-frequent other transit stops 2.0
26 Non-frequent transit stops 2.0
28 Other transit stops 2.0
17 All transit stops 2.0
5 Bars 3.0
4 Restaurants 3.0
8 Cafes and coffee shops 3.0
31 Public libraries 3.0
22 Parks (<0.5 acres) 3.0
18 Parks 3.0
16 Libraries 3.0
29 Parks (>0.5 acres) 4.0
19 Parks (0.5–1 acres) 4.0
7 Grocery stores 6.0
30 Parks (>1 acre) 7.0
20 Parks (1–10 acres) 7.0
14 Bookstores 8.0
6 Convenience stores 9.0
15 Bike shops 9.0
11 Pharmacies 9.0
32 University/private libraries 9.0
23 Playgrounds 10.0
3 High schools 10.0
0 Public schools 10.0
21 Parks (>10 acres) 13.0
12 Preschools 13.0
24 Bakeries 14.0
9 Dentists 15.0
13 Community centers 17.0
10 Gyms and exercise studios 18.0
2 Middle schools 22.0
25 Hardware stores 24.0
1 Elementary schools 26.0

…and the same call as the raw reply, whose parsed results you can inspect yourself:

raw = close.block_summary(geoid = "440070008001068", mode = "walk", output = "raw")
raw.data["results"][:3]
[{'dest_type_id': 1, 'mode': 'walk', 'travel_time': 10.0},
 {'dest_type_id': 5, 'mode': 'walk', 'travel_time': 26.0},
 {'dest_type_id': 6, 'mode': 'walk', 'travel_time': 22.0}]

The client methods

Every data-getting method lives on the client; see the API reference for full signatures.

Catalog and lookups (free, no key):

Travel times from a block or a point:

Points of interest:

Whole areas:

  • blocks_query(): per-block travel times for a polygon, or a centre and radius.

  • place_blocks(): per-block travel times for every block in a place.

  • place_pois(): every POI within a place’s boundary.

  • isochrone(): travel-time contours from a block or a point.

Handling errors

Problem responses become typed exceptions. Catch a specific one, or the CloseAPIError base.

from closecity import TokensExhaustedError, CloseAPIError

try:
    close.block_summary(geoid = "000000000000000")
except TokensExhaustedError:
    ...
except CloseAPIError as err:
    print(err.status, err.slug)