transcript.md
↑ ContentsState Planner chat · https://stateplanner.com
- State: Massachusetts (MA)
- Started: November 15, 2026, 2:04 AM UTC
- Runtime: 1 min 1 sec
- Status: completed
Prompt
How many people and homes are within half a mile of each Massachusetts commuter rail station? Map the census blocks.
Model: Anthropic: Claude Opus 5.5 (anthropic/claude-opus-5.5, via openrouter), high effort
Response
About 699,000 people and 307,000 homes are within half a mile of at least one of Massachusetts's 144 MBTA commuter rail stations. That's roughly 10% of the state's 2020 population (7.03 million) and 10% of its housing units (3.0 million). The counts are from the 2020 census, by block, and are counted once even where two stations' circles overlap. Of those homes, 285,000 were occupied.

Map legend
- Shaded blocks: the 8,441 census blocks within half a mile of a station, colored by homes per acre: light grey for no homes, then pale peach (under 2 per acre) through deep plum (40 or more per acre). Click a block for its nearest station, distance, population, homes and households.
- Blue outlines: a half-mile (805 m) circle around each station.
- Purple dots: the stations. Click one for its own half-mile totals.
Most people within half a mile (2020 census)
| Station | People | Housing units | Households |
|---|---|---|---|
| Back Bay | 28,783 | 17,490 | 15,680 |
| Ruggles | 28,180 | 8,224 | 7,228 |
| Lansdowne | 23,328 | 9,235 | 8,393 |
| North Station | 21,808 | 14,158 | 12,891 |
| Four Corners/Geneva | 20,011 | 7,045 | 6,572 |
| Porter | 18,455 | 8,876 | 8,393 |
| Lynn Interim | 18,172 | 7,548 | 7,185 |
| South Station | 17,212 | 10,472 | 8,580 |
| Uphams Corner | 16,491 | 6,067 | 5,670 |
| Talbot Avenue | 15,466 | 5,497 | 5,168 |
| Morton Street | 15,085 | 5,527 | 5,157 |
| Malden Center | 14,246 | 6,849 | 6,342 |
| JFK/UMass | 13,019 | 6,192 | 5,664 |
| Boston Landing | 12,094 | 5,207 | 4,910 |
| Haverhill | 11,420 | 4,969 | 4,625 |
Fewest people within half a mile (2020 census)
| Station | People | Housing units |
|---|---|---|
| Freetown | 57 | 24 |
| East Taunton | 110 | 24 |
| Grafton | 146 | 19 |
| Cohasset | 152 | 60 |
| Wachusett | 215 | 108 |
| Bourne | 247 | 153 |
| Kingston | 286 | 102 |
| Rowley | 287 | 116 |
| Foxboro | 306 | 106 |
| Hanson | 314 | 136 |
What the numbers show
- The highest totals are at stations in Boston and the inner ring: Back Bay, the Fairmount Line stops in Dorchester and Mattapan, Lynn, Malden and Porter. Outside Boston, Haverhill, Lowell, Waltham and Lawrence each have 9,500–11,400 people nearby. The lowest totals are mostly at park-and-ride stops near highways and at outer-end stations.
- Some totals include a lot of people in dorms. Across all the catchments, 42,700 people live in group quarters, 32,100 of them in college housing. That's why Ruggles (Northeastern area) and Bridgewater (4,079 people but only 480 homes) have far more people than homes.
How these were counted
- Stations: the 144 commuter rail stations in the MBTA's feed in the National Transit Map. They include the newer South Coast Rail stations (Fall River Depot, New Bedford, East Taunton, Freetown, Church Street) and the CapeFLYER stops (Bourne, Buzzards Bay, Hyannis). Rhode Island stations aren't included, and neither are Amtrak-only stops. The data has no schedules, so these figures say nothing about how often trains stop.
- Half a mile is a straight-line 805 m from the station point, not a walking route. A block counts in full if the Census Bureau's interior point for it is inside that distance, so blocks cut by the edge are counted all or nothing.
- Accuracy: the housing unit counts are exact. The population and household counts have statistical noise added by the census for privacy, so the per-station totals are reliable but a single block's figure isn't.
- Overlap: per-station totals overlap where stations are close together, such as Back Bay and South Station or the Fairmount Line stops. Adding up the station rows would count the same people more than once; the ~699,000 total above counts each block once.
If you'd prefer walking distance along the street network, a different radius, or each block split between its nearest stations, I can rerun it that way.
Exploratory Queries
Query 1 — See which feeds and stop types carry rail (route type 2) service.
SELECT feed_id,
ntd_id,
location_type,
count(*),
min(stop_name),
max(stop_name)
FROM ma_ntm_stops
WHERE 2 = ANY (route_types)
GROUP BY 1,
2,
3
ORDER BY 1,
3
| feed_id | ntd_id | location_type | count | min | max |
|---|---|---|---|---|---|
| 10003_1 | 10003 | 0 | 232 | Abington | Wyoming Hill |
| 10003_1 | 10003 | 1 | 144 | Abington | Wyoming Hill |
| 10003_1 | 10003 | 2 | 79 | 1 | Atlantic Ave Dewey Sq Summer St | Woodland Rd |
| 10003_1 | 10003 | 3 | 463 | Auburndale | West Newton |
| 10115_1 | 10115 | 3 | Boston | Woburn Amtrak Station |
Query 2 — List the MBTA commuter rail stations and check which fall outside Massachusetts.
SELECT s.stop_id,
s.stop_name,
s.modes,
s.wheelchair_boarding,
st.stusps
FROM ma_ntm_stops s
LEFT JOIN us_census_states st ON ST_Intersects(st.geom, s.geom)
WHERE s.feed_id = '10003_1'
AND s.location_type = 1
AND 2 = ANY (s.route_types)
ORDER BY st.stusps NULLS FIRST,
s.stop_name
| stop_id | stop_name | modes | wheelchair_boarding | stusps |
|---|---|---|---|---|
| place-PB-0194 | Abington | ["Rail"] | 1 | MA |
| place-NHRML-0127 | Anderson/Woburn | ["Rail","Bus"] | 1 | MA |
| place-WR-0228 | Andover | ["Rail","Bus"] | 1 | MA |
| place-WML-0252 | Ashland | ["Rail","Bus"] | 1 | MA |
| place-NEC-1969 | Attleboro | ["Rail","Bus"] | 1 | MA |
| place-WML-0102 | Auburndale | ["Rail"] | 2 | MA |
| place-FR-0361 | Ayer | ["Rail","Bus"] | 2 | MA |
| place-bbsta | Back Bay | ["Subway, Metro","Rail","Bus"] | 1 | MA |
| place-WR-0205 | Ballardvale | ["Rail","Bus"] | 1 | MA |
| place-NB-0072 | Bellevue | ["Rail","Bus"] | 1 | MA |
| place-FR-0064 | Belmont | ["Rail","Bus"] | 2 | MA |
| place-ER-0183 | Beverly | ["Rail","Bus"] | 1 | MA |
| place-GB-0229 | Beverly Farms | ["Rail","Bus"] | 1 | MA |
| place-DB-2222 | Blue Hill Avenue | ["Rail","Bus"] | 1 | MA |
| place-WML-0035 | Boston Landing | ["Rail","Bus"] | 1 | MA |
| place-CM-0564 | Bourne | ["Rail"] | 1 | MA |
| place-WR-0325 | Bradford | ["Rail","Bus"] | 1 | MA |
| place-brntn | Braintree | ["Subway, Metro","Rail","Bus"] | 1 | MA |
| place-FR-0115 | Brandeis/Roberts | ["Rail","Bus"] | 1 | MA |
| place-MM-0277 | Bridgewater | ["Rail"] | 1 | MA |
| place-MM-0200 | Brockton | ["Rail"] | 1 | MA |
| place-CM-0547 | Buzzards Bay | ["Rail"] | 1 | MA |
| place-MM-0219 | Campello | ["Rail"] | 1 | MA |
| place-SB-0156 | Canton Center | ["Rail"] | 1 | MA |
| place-NEC-2139 | Canton Junction | ["Rail","Bus"] | 1 | MA |
25 out of 144 are listed here.
Query 3 — Count 2020 census people and homes in blocks within half a mile of each commuter rail station.
WITH st AS MATERIALIZED(
SELECT stop_id,
stop_name,
geom
FROM ma_ntm_stops
WHERE feed_id = '10003_1'
AND location_type = 1
AND 2 = ANY (route_types)
)
SELECT st.stop_name,
count(b.geoid) AS blocks,
coalesce(sum(b.pop_total), 0) AS pop,
coalesce(sum(b.housing_units), 0) AS hu,
coalesce(sum(b.housing_occupied), 0) AS hh
FROM st
LEFT JOIN ma_census_blocks b ON ST_DWithin(b.internal_point, st.geom, 0.012)
AND ST_DWithin(b.internal_point::geography, st.geom::geography, 805)
GROUP BY st.stop_name
ORDER BY pop DESC
| stop_name | blocks | pop | hu | hh |
|---|---|---|---|---|
| Back Bay | 227 | 28783 | 17490 | 15680 |
| Ruggles | 159 | 28180 | 8224 | 7228 |
| Lansdowne | 158 | 23328 | 9235 | 8393 |
| North Station | 184 | 21808 | 14158 | 12891 |
| Four Corners/Geneva | 148 | 20011 | 7045 | 6572 |
| Porter | 177 | 18455 | 8876 | 8393 |
| Lynn Interim | 115 | 18172 | 7548 | 7185 |
| South Station | 212 | 17212 | 10472 | 8580 |
| Uphams Corner | 151 | 16491 | 6067 | 5670 |
| Talbot Avenue | 120 | 15466 | 5497 | 5168 |
| Morton Street | 127 | 15085 | 5527 | 5157 |
| Malden Center | 124 | 14246 | 6849 | 6342 |
| JFK/UMass | 89 | 13019 | 6192 | 5664 |
| Boston Landing | 110 | 12094 | 5207 | 4910 |
| Haverhill | 118 | 11420 | 4969 | 4625 |
| Chelsea | 129 | 11337 | 4240 | 3975 |
| Lowell | 133 | 11111 | 4378 | 4030 |
| Waltham | 112 | 10907 | 5296 | 4955 |
| Roslindale Village | 96 | 10523 | 4612 | 4381 |
| Lawrence | 83 | 9516 | 3470 | 3285 |
| Swampscott | 108 | 9443 | 3753 | 3562 |
| Oak Grove | 76 | 9052 | 4084 | 3847 |
| Blue Hill Avenue | 96 | 9025 | 3576 | 3362 |
| Wyoming Hill | 98 | 8832 | 4140 | 3965 |
| Salem | 132 | 8457 | 4804 | 4462 |
25 out of 144 are listed here.
Query 4 — Total the unique people and homes within half a mile of any station, without double counting overlapping catchments.
WITH st AS MATERIALIZED(
SELECT geom
FROM ma_ntm_stops
WHERE feed_id = '10003_1'
AND location_type = 1
AND 2 = ANY (route_types)
),
blk AS (
SELECT DISTINCT b.geoid,
b.pop_total,
b.housing_units,
b.housing_occupied,
b.pop_group_quarters,
b.pop_gq_college
FROM st
JOIN ma_census_blocks b ON ST_DWithin(b.internal_point, st.geom, 0.012)
AND ST_DWithin(b.internal_point::geography, st.geom::geography, 805)
)
SELECT count(*) blocks,
sum(pop_total) pop,
sum(housing_units) hu,
sum(housing_occupied) hh,
sum(pop_group_quarters) gq,
sum(pop_gq_college) college,
(
SELECT sum(pop_total)
FROM ma_census_blocks
) state_pop,
(
SELECT sum(housing_units)
FROM ma_census_blocks
) state_hu
FROM blk
| blocks | pop | hu | hh | gq | college | state_pop | state_hu |
|---|---|---|---|---|---|---|---|
| 8441 | 699167 | 307312 | 285192 | 42656 | 32140 | 7029917 | 2998537 |
Map Layer Queries
Census blocks within ½ mile (homes per acre, 2020) — colored by "cls": No homes (#e6e6e6), Under 2 homes/acre (#fde0c5), 2–5 homes/acre (#facba6), 5–15 homes/acre (#f59e72), 15–40 homes/acre (#e05c5c), 40+ homes/acre (#8b1e5b)
WITH st AS MATERIALIZED(
SELECT stop_name,
geom
FROM ma_ntm_stops
WHERE feed_id = '10003_1'
AND location_type = 1
AND 2 = ANY (route_types)
),
pairs AS (
SELECT DISTINCT ON (b.geoid) b.geoid,
b.geom,
b.pop_total,
b.housing_units,
b.housing_occupied,
b.aland_m2,
st.stop_name,
ST_Distance(b.internal_point::geography, st.geom::geography) AS d
FROM st
JOIN ma_census_blocks b ON ST_DWithin(b.internal_point, st.geom, 0.012)
AND ST_DWithin(b.internal_point::geography, st.geom::geography, 805)
ORDER BY b.geoid,
d
)
SELECT geom,
geoid AS "Block",
stop_name AS "Nearest station",
round(d) AS "Distance to station(m)",
pop_total AS "Population 2020 (noise-protected)",
housing_units AS "Housing units",
housing_occupied AS "Households",
round((housing_units::numeric / NULLIF(aland_m2, 0) * 4046.86), 1) AS "Homes per acre",
CASE
WHEN housing_units = 0 THEN 'none'
WHEN housing_units::numeric / NULLIF(aland_m2, 0) * 4046.86 < 2 THEN 'lt2'
WHEN housing_units::numeric / NULLIF(aland_m2, 0) * 4046.86 < 5 THEN '2to5'
WHEN housing_units::numeric / NULLIF(aland_m2, 0) * 4046.86 < 15 THEN '5to15'
WHEN housing_units::numeric / NULLIF(aland_m2, 0) * 4046.86 < 40 THEN '15to40'
ELSE '40plus'
END AS cls
FROM pairs
Picture: maps/turn-1-commuter-rail-half-mile-blocks.png
½-mile (805 m) station radius
SELECT ST_Buffer(geom::geography, 805)::geometry AS geom,
stop_name AS "Station"
FROM ma_ntm_stops
WHERE feed_id = '10003_1'
AND location_type = 1
AND 2 = ANY (route_types)
Picture: maps/turn-1-commuter-rail-half-mile-blocks.png
Commuter rail stations (MBTA)
WITH st AS MATERIALIZED(
SELECT stop_name,
wheelchair_boarding,
geom
FROM ma_ntm_stops
WHERE feed_id = '10003_1'
AND location_type = 1
AND 2 = ANY (route_types)
)
SELECT st.geom,
st.stop_name AS "Station",
s.pop AS "People within ½ mile(2020)",
s.hu AS "Housing units within ½ mile",
s.hh AS "Households within ½ mile"
FROM st
CROSS JOIN LATERAL (
SELECT coalesce(sum(b.pop_total), 0) pop,
coalesce(sum(b.housing_units), 0) hu,
coalesce(sum(b.housing_occupied), 0) hh
FROM ma_census_blocks b
WHERE ST_DWithin(b.internal_point, st.geom, 0.012)
AND ST_DWithin(b.internal_point::geography, st.geom::geography, 805)
) s
Picture: maps/turn-1-commuter-rail-half-mile-blocks.png
Used Data Sets
ma_census_blocks— last synced 2026-11-14 · 107,278 rowsma_ntm_stops— last synced 2026-10-21 · 19,713 rowsus_census_states— last synced 2026-10-15 · 51 rows