Skip to content

Commit 560831a

Browse files
author
Nathaniel Henry
committed
Polish the docs: cut AI-tell writing, lead with the SDK line, fix the default-output wording, and install from GitHub until PyPI.
1 parent 36a82ab commit 560831a

11 files changed

Lines changed: 76 additions & 59 deletions

File tree

README.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
# closecity
22

3-
Python client for the Close API. Get travel times from every US census block to
4-
nearby places, on foot, by bike, and by public transit. This is the data behind
5-
[close.city](https://close.city), read over the [Close API](https://api.close.city).
3+
This is the Python software development kit for the Close.City API. It returns
4+
travel times from every US census block to nearby places, on foot, by bike, and by
5+
public transit. The data behind [close.city](https://close.city) is served over the
6+
[Close API](https://api.close.city).
67

78
**Documentation:** https://henryspatialanalysis.github.io/closecity-python/
89

910
## Install
1011

12+
Will soon be on PyPI; install from GitHub until then:
13+
1114
```bash
12-
pip install closecity
13-
pip install "closecity[tiger]" # to auto-download census-block boundaries
15+
pip install git+https://github.com/henryspatialanalysis/closecity-python.git
16+
# add the census-block boundary downloader:
17+
pip install "closecity[tiger] @ git+https://github.com/henryspatialanalysis/closecity-python.git"
1418
```
1519

1620
This pulls in `httpx`, `pandas`, and `geopandas`, so results come back as data
@@ -55,7 +59,7 @@ A few terms come up throughout the API:
5559
- **Isochrone** or **catchment**: the area you can reach starting from a point
5660
within a time limit, by a selected travel mode.
5761

58-
## Choosing an output
62+
## Choose an output
5963

6064
Set `output` on the client, or per call:
6165

docs/getting_started.md

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ close = Client(os.environ.get("CLOSECITY_KEY"))
3232
A few terms come up throughout:
3333

3434
- **Census block.** The smallest area the Census Bureau publishes. Each one has a
35-
15-digit id, its **GEOID**. Block GEOIDs come from the census — look them up with
35+
15-digit id, its **GEOID**. Block GEOIDs come from the census. Look them up with
3636
`pygris` or the Census Bureau geocoder/API, or read them straight off Close's
3737
block routes (`blocks_query`, `place_blocks`).
3838
- **Destination type.** A category of place, such as grocery stores or libraries.
@@ -89,7 +89,7 @@ point instead of a GEOID.
8989
## Make a call and map it
9090

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

9595
```{code-cell} python
@@ -135,34 +135,34 @@ full signatures.
135135

136136
Catalog and lookups (free, no key):
137137

138-
- {py:meth}`~closecity.Client.modes`the travel modes: walk, bike, transit.
139-
- {py:meth}`~closecity.Client.destination_types` the catalog of amenity categories and their numeric ids.
140-
- {py:meth}`~closecity.Client.places` a city name to its GEOID and centre point.
141-
- {py:meth}`~closecity.Client.place_boundary` the boundary polygon of a census place.
142-
- {py:meth}`~closecity.Client.vintage` the data vintage.
143-
- {py:meth}`~closecity.Client.last_updated` when the data was last refreshed.
144-
- {py:meth}`~closecity.Client.isochrone_meta` isochrone modes, directions, and assumptions.
145-
- {py:meth}`~closecity.Client.health` a service health check.
138+
- {py:meth}`~closecity.Client.modes`: the travel modes (walk, bike, transit).
139+
- {py:meth}`~closecity.Client.destination_types`: the catalog of amenity categories and their numeric ids.
140+
- {py:meth}`~closecity.Client.places`: a city name to its GEOID and centre point.
141+
- {py:meth}`~closecity.Client.place_boundary`: the boundary polygon of a census place.
142+
- {py:meth}`~closecity.Client.vintage`: the data vintage.
143+
- {py:meth}`~closecity.Client.last_updated`: when the data was last refreshed.
144+
- {py:meth}`~closecity.Client.isochrone_meta`: isochrone modes, directions, and assumptions.
145+
- {py:meth}`~closecity.Client.health`: a service health check.
146146

147147
Travel times from a block or a point:
148148

149-
- {py:meth}`~closecity.Client.block_summary` walk/bike/transit time from a block to each amenity type.
150-
- {py:meth}`~closecity.Client.point_summary` the same, from a `lat`/`lon` point.
151-
- {py:meth}`~closecity.Client.block_pois` the individual POIs reachable from a block, each with its travel time.
152-
- {py:meth}`~closecity.Client.point_pois` the same, from a `lat`/`lon` point.
149+
- {py:meth}`~closecity.Client.block_summary`: walk/bike/transit time from a block to each amenity type.
150+
- {py:meth}`~closecity.Client.point_summary`: the same, from a `lat`/`lon` point.
151+
- {py:meth}`~closecity.Client.block_pois`: the individual POIs reachable from a block, each with its travel time.
152+
- {py:meth}`~closecity.Client.point_pois`: the same, from a `lat`/`lon` point.
153153

154154
Points of interest:
155155

156-
- {py:meth}`~closecity.Client.pois_search` search POIs by radius or bounding box.
157-
- {py:meth}`~closecity.Client.poi` the details of one POI.
158-
- {py:meth}`~closecity.Client.poi_catchment` the blocks that can walk to a POI (its catchment).
156+
- {py:meth}`~closecity.Client.pois_search`: search POIs by radius or bounding box.
157+
- {py:meth}`~closecity.Client.poi`: the details of one POI.
158+
- {py:meth}`~closecity.Client.poi_catchment`: the blocks that can walk to a POI (its catchment).
159159

160160
Whole areas:
161161

162-
- {py:meth}`~closecity.Client.blocks_query` per-block travel times for a polygon, or a centre and radius.
163-
- {py:meth}`~closecity.Client.place_blocks` per-block travel times for every block in a place.
164-
- {py:meth}`~closecity.Client.place_pois` every POI within a place's boundary.
165-
- {py:meth}`~closecity.Client.isochrone` travel-time contours from a block or a point.
162+
- {py:meth}`~closecity.Client.blocks_query`: per-block travel times for a polygon, or a centre and radius.
163+
- {py:meth}`~closecity.Client.place_blocks`: per-block travel times for every block in a place.
164+
- {py:meth}`~closecity.Client.place_pois`: every POI within a place's boundary.
165+
- {py:meth}`~closecity.Client.isochrone`: travel-time contours from a block or a point.
166166

167167
## Handling errors
168168

docs/index.rst

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
closecity
22
=========
33

4-
Python client for the Close API (`api.close.city <https://api.close.city>`_). Get
4+
This is the Python software development kit for the Close.City API. It returns
55
travel times from every US census block to nearby places, on foot, by bike, and by
6-
public transit. This is the data behind `close.city <https://close.city>`_.
6+
public transit. The data behind `close.city <https://close.city>`_ is served over
7+
`api.close.city <https://api.close.city>`_.
78

89
.. code-block:: python
910
@@ -16,10 +17,10 @@ public transit. This is the data behind `close.city <https://close.city>`_.
1617
supermarkets = close.pois_search(lat = 41.823, lon = -71.412, radius_m = 1500, type = 30)
1718
close_map(supermarkets, color = "#e8590c") # interactive map, bright hoverable points
1819
19-
Results are tabular by default: a GeoDataFrame where geometry applies, a plain
20-
DataFrame otherwise. Install with ``pip install closecity``. The catalog and
21-
lookup routes are free; the data routes need a key from
22-
`account.close.city <https://account.close.city>`_.
20+
By default, results come back as a GeoDataFrame where geometry applies (points,
21+
isochrones, and block polygons) and a plain DataFrame otherwise. Install it from GitHub for now (see
22+
:doc:`installation`); PyPI is coming. The catalog and lookup routes are free; the
23+
data routes need a key from `account.close.city <https://account.close.city>`_.
2324

2425
.. toctree::
2526
:maxdepth: 2

docs/installation.rst

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
Installation
22
============
33

4+
The package will soon be published to PyPI. Until then, install it from GitHub:
5+
46
.. code-block:: bash
57
6-
pip install closecity
8+
pip install git+https://github.com/henryspatialanalysis/closecity-python.git
79
8-
This pulls in `httpx <https://www.python-httpx.org/>`_ and
10+
Once it lands on PyPI, ``pip install closecity`` will work. Either way, this pulls
11+
in `httpx <https://www.python-httpx.org/>`_ and
912
`geopandas <https://geopandas.org/>`_, so feature methods return GeoDataFrames out of
1013
the box.
1114

@@ -18,7 +21,7 @@ download the boundaries for you, add the ``tiger`` extra:
1821

1922
.. code-block:: bash
2023
21-
pip install "closecity[tiger]"
24+
pip install "closecity[tiger] @ git+https://github.com/henryspatialanalysis/closecity-python.git"
2225
2326
You do not need it for ``output="tabular"``, which returns the same rows without
2427
geometry.

docs/tutorials/amenity_basket.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ for a in basket["amenity"]:
9999
```
100100

101101
Parks and restaurants tend to be everywhere; supermarkets and libraries are usually
102-
the hardest to reach. Map the library coverage every block shown, the covered ones
102+
the hardest to reach. Map the library coverage: every block shown, the covered ones
103103
highlighted, the library locations as points, the city boundary behind.
104104

105105
```{code-cell} python
@@ -148,7 +148,7 @@ for a in basket["amenity"]:
148148

149149
## Who is one or two amenities away
150150

151-
The residents worth targeting first are the ones *almost* there a block with five
151+
The residents worth targeting first are the ones *almost* there: a block with five
152152
of the six is a much easier win than one with none. Count how many amenities each
153153
block is missing, and for the blocks short by just one or two, break down which
154154
amenities are the gap.

docs/tutorials/competitor_walksheds.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ close_map(
7676

7777
Now split the walkshed by block. A single `block_pois` call takes the whole list of
7878
walkshed blocks and returns, for every block, each cafe its residents can walk to
79-
within 10 minutes the real routed answer, not a straight-line guess, and one
79+
within 10 minutes: the real routed answer, not a straight-line guess, and one
8080
request rather than one per block. Passing a list of GEOIDs tags every row with its
8181
origin `geoid`, so grouping by it reads two things per block: how many cafes are in
8282
reach, and which one is closest by walk time.
@@ -98,17 +98,25 @@ our_shed["closest_cafe"] = our_shed["geoid"].map(winners["dest_id"])
9898
## How many cafes each block can reach
9999

100100
Shade every block in the walkshed by the number of cafes within a 10-minute walk;
101-
blue marks the blocks with the most choice.
101+
blue marks the blocks with the most choice, and the cafes those blocks can reach are
102+
drawn on top as points.
102103

103104
```{code-cell} python
104-
close_map(our_shed, fill = "n_cafes", reverse = True, boundary = city_boundary)
105+
reachable = cafes[cafes["dest_id"].isin(reach["dest_id"].unique())]
106+
close_map(
107+
our_shed,
108+
fill = "n_cafes",
109+
reverse = True,
110+
points = reachable,
111+
boundary = city_boundary,
112+
)
105113
```
106114

107115
## Which cafe is closest
108116

109117
Give each cafe that wins at least one block a colour, then paint every block with the
110118
colour of its closest cafe. The cafe points share those colours. The result is the
111-
contested ground where our shop's catchment gives way to a competitor's.
119+
contested ground: where our shop's catchment gives way to a competitor's.
112120

113121
```{code-cell} python
114122
closest = [int(c) for c in our_shed["closest_cafe"].dropna().unique()]

docs/tutorials/first_map.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ start_lat = providence_ri["lat"]
4949

5050
## Read travel times from a starting point
5151

52-
Pick a starting pointhere the centre of Providence and ask how long it takes to
52+
Pick a starting point, here the centre of Providence, and ask how long it takes to
5353
walk to each kind of amenity. `point_summary()` takes a `lat`/`lon` instead of a block
5454
GEOID. Join the catalog's readable `name` and sort by time, so the nearest things are
5555
on top.
@@ -67,7 +67,7 @@ walk_times.sort_values("travel_time")[["name", "travel_time"]]
6767

6868
A 30-minute walk is a travel-time question, not a distance one, so let the routing
6969
answer it directly: `point_pois` returns every POI reachable from the starting point
70-
within `max_minutes`, each carrying its walk time no isochrone to overlay.
70+
within `max_minutes`, each carrying its walk time, with no isochrone to overlay.
7171
`close_map()` draws them in one line, shaded by that walk time (blue = closest),
7272
with the starting point marked by an X and the city boundary behind for context.
7373

@@ -110,7 +110,7 @@ close_map(rings, fill = "contour")
110110

111111
## Walk versus transit
112112

113-
The same starting point and the same 30-minute budget, on foot and by bus the
113+
The same starting point and the same 30-minute budget, on foot and by bus: the
114114
clearest way to see what transit buys you.
115115

116116
```{code-cell} python

docs/tutorials/home_search.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ city_boundary = close.place_boundary(geoid = city["geoid"])
5959
## See what is around
6060

6161
Look at the raw ingredients first: every supermarket, restaurant, and frequent-transit
62-
stop **within Somerville**, from `place_pois` — the city boundary, not a guessed
62+
stop **within Somerville**, from `place_pois`. The city boundary, not a guessed
6363
radius, is the edge. Give each category a colour and map them together.
6464

6565
```{code-cell} python
@@ -84,10 +84,10 @@ close_map(
8484
## Find the blocks that qualify
8585

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

9393
```{code-cell} python
@@ -165,7 +165,7 @@ close_map(
165165
```
166166

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

171171
```{code-cell} python

src/closecity/client.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ def _as_list(value: Any) -> Any:
5252
def _point_origins(points: Any) -> list[dict[str, float]]:
5353
"""Normalise a batch of point origins to ``[{"lon": .., "lat": ..}, ...]``.
5454
55-
Each item is either a ``(lat, lon)`` pair matching the ``(lat, lon)``
56-
argument order of the single-point calls or a ``{"lat": .., "lon": ..}``
55+
Each item is either a ``(lat, lon)`` pair (matching the ``(lat, lon)``
56+
argument order of the single-point calls) or a ``{"lat": .., "lon": ..}``
5757
mapping.
5858
"""
5959
out: list[dict[str, float]] = []
@@ -403,8 +403,8 @@ def point_summary(
403403
The resolved block GEOID is echoed as `resolved_block` and broadcast to a
404404
`geoid` column. A DataFrame, or a raw `Reply`.
405405
406-
Pass a **list of points** as the first argument each a `(lat, lon)` pair
407-
or a `{"lat": .., "lon": ..}` mapping to query many points in one call.
406+
Pass a **list of points** as the first argument (each a `(lat, lon)` pair
407+
or a `{"lat": .., "lon": ..}` mapping) to query many points in one call.
408408
Each row is tagged with its `origin_lat` / `origin_lon`; resolved blocks,
409409
`errors`, and `truncated` ride on `df.attrs`."""
410410
if isinstance(lat, (list, tuple)):
@@ -433,8 +433,8 @@ def point_pois(
433433
GeoDataFrame of points (a plain DataFrame under `output="tabular"`, a
434434
`Paginator` under `output="raw"`).
435435
436-
Pass a **list of points** as the first argument each a `(lat, lon)` pair
437-
or a `{"lat": .., "lon": ..}` mapping to query many points in one call.
436+
Pass a **list of points** as the first argument (each a `(lat, lon)` pair
437+
or a `{"lat": .., "lon": ..}` mapping) to query many points in one call.
438438
Each row is tagged with its `origin_lat` / `origin_lon`. The batch form is
439439
not paginated (`limit` is ignored)."""
440440
if isinstance(lat, (list, tuple)):
@@ -544,8 +544,8 @@ def place_pois(
544544
output: str | None = None,
545545
):
546546
"""Every point of interest within a census place (city or town), by
547-
place GEOID. The place analog of `pois_search`; pass `type` to get,
548-
e.g., all supermarkets in a city. Spatial only no travel times. A
547+
place GEOID. The place analogue of `pois_search`; pass `type` to get,
548+
e.g., all supermarkets in a city. Spatial only, no travel times. A
549549
GeoDataFrame of points (a plain DataFrame under `output="tabular"`, a
550550
`Paginator` under `output="raw"`)."""
551551
return self._deliver(Paginator(

src/closecity/errors.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ def __init__(
5050
if detail:
5151
message += f": {detail}"
5252
if hint:
53-
message += f" — {hint}"
53+
sep = "" if message.endswith((".", "!", "?")) else "."
54+
message += f"{sep} {hint}"
5455
if request_id:
5556
message += f" (request {request_id})"
5657
super().__init__(message)

0 commit comments

Comments
 (0)