# Downtown Explorer: plan

## Purpose

This is a shared, checkable playground for planning Northbrook's downtown. It answers two questions in order:

1. **Is Northbrook's downtown really low-density?** Measured the same way in every town, Northbrook comes out mid-pack, not low.
2. **Does nearby density decide whether a downtown thrives?** Mostly no. Even in the densest small downtowns, most restaurant spending has to come from visitors, which makes parking, access and the mix of stores the bigger levers.

Anyone should be able to open the map, change one setting, and see the number move.

## Architecture

- A static site with no build step and no server: `index.html`, `styles.css`, `app.js`, `engine.js`, `data/*.json`.
- `engine.js` contains no DOM code and runs in both the browser and Node (for tests). It does three things:
  - `loadArea()` fetches 2020 Census blocks and TIGER streets for a box about 4.3 km around a town and builds a street graph (largest connected piece only).
  - `measure(area, origin, mode, distM)` measures by straight line (haversine, or distance to a polygon's edge) or by walking (Dijkstra over the street graph; a polygon origin seeds every street node inside it). It attaches each block's interior point to its nearest street segment.
  - `distanceTo()` measures the same distance for a single project location.
- `app.js` handles the MapLibre map, the controls, polygon drawing, the project marker, town comparison and the restaurant check. URL hash state (`#town=…&from=…&mode=…&d=…`) makes any view shareable.
- Live data comes straight from Census TIGERweb in the visitor's browser (CORS is allowed). Areas are cached per session.

## Method notes (for anyone checking the numbers)

- **Homes** means Census housing units, which counts each apartment or condo, rather than buildings or addresses. The village's Northbrook figure (2,818) doesn't match a unit count; its figures for the other towns do. That inconsistency is the core finding. The slide's own map shows address-style dots inside a circle, which points to Northbrook being an address count (a walking count lands at a similar number by coincidence).
- **Walking:** each block's interior point is attached to the nearest street segment (or any street within 25 m of that), never to a street on the far side of tracks or a river that the walk reached by a long detour. The origin is attached the same way. Tested in test/engine.test.js with a two-grid barrier case.
- **Straight line:** a block counts if its interior point is within the distance. Counting every block that merely touches the circle runs 10–29% higher, so these counts are the conservative end.
- **Checks (Oct 9, 2026), 1 mile from the station:**

  | Town | Village slide | Straight line | Walking |
  |---|---|---|---|
  | Northbrook | 2,818 | 4,106 | 2,737 |
  | Highland Park | 4,525 | 4,083 | 3,263 |
  | Deerfield | 5,164 | 5,185 | 3,369 |
  | Glenview | 5,775 | 5,617 | 2,564 |
  | Winnetka | 6,858 (two stations) | 3,342 (one station) | 2,712 |

  Highland Park, Deerfield and Glenview on the slide match straight-line Census counts within 0–11%. Northbrook's matches walking. Most likely Northbrook was measured differently from the towns it was compared with.
- **Slide check, three ways to count edge blocks (Oct 9, 2026; data/slidecheck.json):** block center inside (what the app uses) / share of land inside / any block touching the circle.

  | Slide row | Slide | Centers | Share | Touching |
  |---|---|---|---|---|
  | Northbrook | 2,818 | 4,106 | 4,067 | 5,290 |
  | Highland Park | 4,525 | 4,083 | 4,225 | 4,821 |
  | Deerfield | 5,164 | 5,185 | 5,115 | 5,768 |
  | Glenview | 5,775 | 5,617 | 5,584 | 7,029 |
  | Winnetka, Elm St only | 6,858 | 3,342 | 3,352 | 3,940 |
  | Winnetka, Hubbard Woods + Indian Hill | 6,858 | 7,030 | 6,925 | 7,764 |
  | Winnetka + Hubbard Woods | 6,858 | 5,282 | 5,271 | 5,864 |
  | Winnetka + Indian Hill | 6,858 | 5,543 | 5,479 | 6,254 |
  | All three Winnetka stations | 6,858 | 7,483 | 7,398 | 8,178 |

  **Update (Oct 10):** the slide's own map draws Winnetka's two circles about 1.8 km apart, NW–SE, which matches the Winnetka (Elm St) and Hubbard Woods stations (1.79 km apart). Measured that way, the Census count is 5,282, so the slide is 30% higher.
  - The Northbrook map's legend reads "Metra Stations Half Mile Radius – 2818 Residential Units" under a title that says 1 mile. The circle as drawn is about 1 mile.
  - The same deck's "Community Data Comparison" (CMAP) uses Census housing units for every town, including Northbrook at 13,296.
  - Building counts don't explain 2,818 either. The Cook County Assessor shows about 3,660 residential properties within 1 mile.

    Deerfield and Glenview match within 3%. Highland Park's slide figure sits between "share" (+7%) and "touching" (−6%). Winnetka's "two stations" figure is closest to Hubbard Woods + Indian Hill (which leaves out the main station). Northbrook's is below all three methods. The compare table's Slide column is only comparable at 1 mile, straight line, from the station; at other settings it's shown in gray italics with a note.
- **Town sales:** from Oct 9 (v.14) every category uses the 1% municipal share: sales = MT ÷ 0.01. This matches ST ÷ 0.05 for restaurants (Highwood exactly 5.00×; others 4.7–5.0×, so up to ~7% higher than the earlier restaurant figures) and also works for groceries and drugs, which are taxed at the 1% low rate. Sales are for the whole municipality, not just downtown.
- Edge blocks can swing a count by tens of homes, so treat differences under about 5% as noise.

## Interface (v17)

- Panel order: 1 Choose what to measure (inputs) → 2 Results → 3 What if… (Advanced; each tool's inputs, then a green Result box) → Share or print.
- Basic hides advanced tools (`[data-adv]`). Shared links that use advanced tools open in Advanced. Leftover advanced scenarios are listed in Basic with "Clear them".
- Show/hide choices for compare columns, side-by-side rows and printout sections are remembered per browser.
- Accessibility: larger-text toggle, sticky answer bar, map-click tools also work from the keyboard (Enter = map center, Esc = cancel), table captions and row headers, ≥44px touch targets on touch screens.
- Reviewed Oct 9, 2026 by four adversarial UI agents (first-time user, accessibility/mobile, data skeptic, state/QA); their findings were fixed in v17.

## Roadmap

Done (Oct 9, 2026): preset downtown outlines, new-crossing scenarios, parking lots and garage walk rings, storefront calculator, side-by-side towns, printable one-pager.

1. **Deploy** to Cloudflare Pages and share with a few friendly testers.
2. **Visitor share:** add visitor-origin data if it comes through (Placer.ai or the village's FOIA response); otherwise a business zip-code survey. Show the share of visits from within ½, 1 and 1½ miles.
3. **Train and freight:** a Dundee Rd crossing-delay layer from FRA Blocked Crossing Incident Reporter data.
4. **Cost tiers for crossings** (only with a sourced cost table).

## Multi-unit check (v27, data/multiunit.json)

- Source: ACS 2020–2024 5-year, table B25024, by block group (Census API, with a key used once to pull the data; the site itself needs no key).
- Each block group is weighted by the share of its 2020 block homes inside the 1-mile station circle.
- Share of homes in buildings with 2 or more units (block group; tract estimate in parentheses):

  | Town | 2+ units |
  |---|---|
  | Northbrook | 15% (19%) |
  | Highland Park | 41% (36%) |
  | Glenview | 26% (26%) |
  | Deerfield | 14% (13%) |
  | Winnetka, both stops | 8% (10%) |
  | Highwood | 33% |
  | Lake Forest | 31% |
  | Wilmette | 27% |
  | Naperville | 35% |
  | Glencoe | 8% |

- The village's "about 20%" for Northbrook is in the right range, a little high. "35–45% elsewhere" fits only Highland Park among the slide towns.

## Density table check (v26, data/landuse.json)

- The deck's "Community Data Comparison" (CMAP) divides all homes by all village land.
- **Its Highland Park area is wrong.** It lists 7.3 sq mi; the Census land area is 12.2 sq mi. With the correct area, Highland Park has about 961 homes per sq mi by the slide's own count, not 1,611, and is below Northbrook's 1,000.
- The tool adds net residential density (homes per residential acre) and land shares from CMAP LUI 2023, clipped to 2020 Census village boundaries and to the 1-mile station circles.
  - Northbrook's net density is 3.8 homes per residential acre, against 3.1 for Highland Park and Winnetka, 4.3 for Deerfield and 4.7 for Glenview.
  - 52% of Northbrook's land is residential, 17% is business (Highland Park: 5%) and 17% is golf and parks.
- v28: three views (whole village, 1 mile from the station, and "Your settings", measured live from CMAP with the step 1 area as a straight-line circle or the drawn outline). Rank column, sortable headings, the slide's own figure as a column, and bar charts drawn by the same helper as Compare towns (`barPanels`, `rankOf`, `ordinal`).
  - "= Not homes" is 100% minus land in homes; the "schools, churches, utilities, vacant" column is the remainder, so business + golf & parks + that column always equals "Not homes".
  - v29: "Your settings" follows Walking too. Land counts if a point on it can be reached on foot within the distance by the same rule as homes (street network to the nearest street, then straight in), tested on a 40 m lattice inside the straight-line area (walking is always a subset). Crossing what-ifs are left out, as in Compare towns.
  - Northbrook shows 3.8 homes per residential acre in both the whole-village and 1-mile views by coincidence: 14,209 / 3,776 = 3.76 and 4,106 / 1,069 = 3.84.
- Shares exclude CMAP code 6000 (street right-of-way; it isn't in the code list, and its huge polygon with holes clips badly) and Lake Michigan.

## Data downloads (v26)

- Every results table downloads as CSV from Share → Download the data.
- Source JSON files are linked there too. No account is needed for anything.

## Scenario method notes

- **Crossings:** each end must be within 40 m of a street, or the crossing is ignored and flagged. Crossings are walkable but homes never attach to them, so adding one can never make any home farther (tested).
- **Garage coverage:** the share of street length in and along the storefront district (within 40 m of the outline, since land-use outlines stop at the curb) within each walking time, at 80 m a minute (3 mph). Times leave out getting out of the garage and waiting at lights.
- **Parking acreage:** CMAP 2023 parking parcels (code 1540) + OpenStreetMap parking lots + OpenStreetMap parking aisles widened to a standard 60 ft bay (shopping-center lots are usually coded commercial by CMAP and often not drawn as lots in OSM, but their aisles are). Pieces are merged so nothing is counted twice; only the part within 400 m (straight line) of the district counts. Spaces ≈ area ÷ 330 sq ft. Public and private together; unmapped parking is missed, so it's a floor.
- **Storefronts:** sales needed = space × sales per sq ft (default $300; published restaurant benchmarks run about $250–400). Nearby households = homes × 95% occupied. Restaurant and shop shares are compared separately with nearby households' eating-out and shopping money (BLS 2024 categories; shopping includes online purchases, so real shares needed are higher). Shopping includes alcoholic beverages ($643 / $974 / $1,668).

## What Pulte's own studies say (read Oct 9, 2026)

From the village's Meadow Plaza page (https://www.northbrook.il.us/1287): Melaniphy & Associates' Retail Market Memorandum (Oct 1, 2026), Gruen Gruen + Associates' Fiscal Study (Sept 2026) and Gardner Advisory's Professional Assessment (Sept 30, 2026), all prepared for Pulte.

- **Same sales data as ours.** Melaniphy uses the same IDOR 2025 category figures (restaurants $121M, food $170M, apparel $66M, furniture $83M, hardware $54M, drugs & misc $443M, general merchandise $21M), which independently confirms data/sales.json.
- **Online sales.** Melaniphy attributes most of the drugs & misc growth ($104M in 2008 to $443M in 2025) to e-commerce and calls e-commerce "devastating" for shopping centers. That cuts against the idea that new residents will shop locally.
- **Annual sales per sq ft disagree across Pulte's own studies** (all figures are per square foot per year).
  - Melaniphy says new space would likely do $400–500.
  - Gruen's fiscal study assumes $750 ($8.25M on 11,000 sq ft), which drives its $165,000 on-site sales tax.
  - Gardner's rent math ($35–45 rent at 8% occupancy cost) implies $440–560.
  - The storefronts tool now has $300 / $450 / $750 presets.
- **Trade area.** Melaniphy uses a 3-mile primary trade area (65,269 people), 5-mile secondary and 7-mile total, plus a daytime population of 122,118 within 3 miles. Gardner says downtown "businesses draw on the entire Village and beyond, so no single project will support downtown by itself." Both point past the 1-mile walk-in market.
- **Parking.** Melaniphy cites "the limited parking in the downtown area and lack of a municipal parking garage."
- **Household thresholds.** Gardner cites Gibbs: about 2,000 households for a 10,000–30,000 sq ft convenience center, and 6,000–8,000 within 1–2 miles for a grocery-anchored center. This is now noted in the shop check.
- **Spending.** Gruen models household spending as a share of income: food at home 6% (90% local) and other retail 20% (66.7% local). It assumes only 10% is captured on site, about $34,000 per household in total. Gardner compares the resulting $5.16M of village-wide resident spending with the $8.25M of on-site sales; those aren't the same thing.
- **Ideas we haven't used yet:**
  - daytime (worker) population within the distance (Census LEHD/OnTheMap)
  - traffic counts (IDOT AADT: Waukegan 18,700; Shermer 10,000; Church St 3,000–4,000; Cherry & Meadow 4,700)
  - a 3-mile trade-area view

## Open data questions

- Lake Forest uses the Union Pacific North station (Western Ave, by Market Square). Lake Forest also has a Milwaukee District North station on Northbrook's line.
- Station points checked against Wikipedia/OpenStreetMap: Northbrook, Naperville, Glenview, Wilmette, Deerfield, Winnetka, Highwood, Glencoe, Highland Park (all within ~30 m). Still unchecked: Lake Forest, Evanston (Davis St).
- 2020 Census baseline: buildings finished since 2020 aren't counted in any town.
- Shopping-center points for Glenview and Wilmette aren't set yet; they currently use the station.
- Address points (Cook 78yw-iddh) joined to Assessor property class (pabr-t5kh, 2026) by PIN, 1-mile circle from Northbrook station (Oct 9, 2026): 3,922 points; 3,664 residential (3,474 houses, 164 condo buildings, 21 apartment buildings, 5 two-to-six-flats), 196 commercial, 62 other. Residential parcels in the circle: 4,352 (including 632 condo unit PINs). None of these reproduces 2,818, so the slide's exact method is still unknown; FOIA requested.
