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
pygrisor 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 withpygris(thetigerextra), 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 underlyingReply/Paginator, with the parsed body on.dataand 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):
modes(): the travel modes (walk, bike, transit).destination_types(): the catalog of amenity categories and their numeric ids.places(): a city name to its GEOID and centre point.place_boundary(): the boundary polygon of a census place.vintage(): the data vintage.last_updated(): when the data was last refreshed.isochrone_meta(): isochrone modes, directions, and assumptions.health(): a service health check.
Travel times from a block or a point:
block_summary(): walk/bike/transit time from a block to each amenity type.point_summary(): the same, from alat/lonpoint.block_pois(): the individual POIs reachable from a block, each with its travel time.point_pois(): the same, from alat/lonpoint.
Points of interest:
pois_search(): search POIs by radius or bounding box.poi(): the details of one POI.poi_catchment(): the blocks that can walk to a POI (its catchment).
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)