Skip to content

feat(osm-equity): uniform 2-sq-mi tile units + areal equity interpolation#29

Merged
rlemke merged 1 commit into
mainfrom
feat/osm-equity-tiles
Jul 14, 2026
Merged

feat(osm-equity): uniform 2-sq-mi tile units + areal equity interpolation#29
rlemke merged 1 commit into
mainfrom
feat/osm-equity-tiles

Conversation

@rlemke

@rlemke rlemke commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Breaks a city into a regular grid of ~2-square-mile tiles as an alternative to irregular census tracts:

  • build_tiles(bbox, sqmi=2.0) — uniform square-tile units.
  • areal_interpolate_equity — dasymetric intersection-area weighting of ACS tract equity onto tiles (tiles over no populated tract omitted, not zeroed).

Metrics + heat map work unchanged on tiles. On real SF data, 2-sq-mi tiling gives 45 tiles and raises Global Moran's I of attribute completeness from 0.33 (tracts) → 0.48 (tiles) — a cleaner spatial signal (less MAUP noise). 3 offline unit tests; 16 offline tests pass; ruff clean.

🤖 Generated with Claude Code

…nterpolation

Adds an alternative to irregular census tracts:

- build_tiles(bbox, sqmi=2.0): a regular grid of ~2-square-mile square tiles
  as tract-shaped units (uniform size => cleaner cross-area comparison, less
  modifiable-areal-unit-problem noise than variable tracts).
- areal_interpolate_equity(tiles, tract_records): dasymetric-style areal
  interpolation that intersection-area-weights ACS tract equity onto each tile
  (tiles over no populated tract are omitted, not zeroed).

The OSM-quality metrics and the MapLibre heat map work unchanged on tiles. On
real SF data the 2-sq-mi tiling gives 45 tiles and raises Global Moran's I of
attribute completeness from 0.33 (tracts) to 0.48 (tiles) - a cleaner spatial
signal. 3 offline unit tests added; 16 offline tests pass; ruff clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rlemke rlemke merged commit 075effd into main Jul 14, 2026
17 of 20 checks passed
@rlemke rlemke deleted the feat/osm-equity-tiles branch July 14, 2026 02:15

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a regular square-tile grid analysis option as an alternative to irregular census tracts, adding functions to build tiles and perform dasymetric areal interpolation of equity metrics, along with corresponding unit tests and documentation. The reviewer feedback suggests enhancing robustness by adding input validation for tile generation parameters, implementing guard checks for empty inputs, and handling potential topological errors and missing dictionary keys during spatial intersection calculations.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

"""Break a bbox into a regular grid of ~`sqmi`-square-mile tiles, as
tract-shaped analysis units (uniform size makes cross-area OSM-quality
comparison cleaner than variable census tracts)."""
xmin, ymin, xmax, ymax = bbox

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Add defensive validation for the sqmi parameter and the bounding box coordinates to prevent potential division-by-zero errors or invalid grid generation.

Suggested change
xmin, ymin, xmax, ymax = bbox
xmin, ymin, xmax, ymax = bbox
if sqmi <= 0:
raise ValueError("sqmi must be a positive number.")
if xmin >= xmax or ymin >= ymax:
raise ValueError("Invalid bounding box: xmin must be < xmax and ymin must be < ymax.")

Comment on lines +370 to +371
from shapely.strtree import STRtree

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Add a guard check at the beginning of areal_interpolate_equity to handle empty inputs gracefully and avoid unnecessary processing or potential issues with empty STRtree initialization.

Suggested change
from shapely.strtree import STRtree
if not tiles or not tract_records:
return {}
from shapely.strtree import STRtree

Comment on lines +380 to +389
eq = tract_records[idx]["equity"]
if eq["median_income"] <= 0:
continue
inter = poly.intersection(geoms[idx])
w = inter.area
if w <= 0:
continue
for f in _EQ_FIELDS:
acc[f] += eq[f] * w
wsum += w

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Improve robustness by using safe dictionary access for the equity profile and its fields, and handle potential topological errors (e.g., self-intersections in real-world TIGERweb data) during spatial intersection using a fallback .buffer(0).

Suggested change
eq = tract_records[idx]["equity"]
if eq["median_income"] <= 0:
continue
inter = poly.intersection(geoms[idx])
w = inter.area
if w <= 0:
continue
for f in _EQ_FIELDS:
acc[f] += eq[f] * w
wsum += w
eq = tract_records[idx].get("equity")
if not eq or eq.get("median_income", 0) <= 0:
continue
try:
inter = poly.intersection(geoms[idx])
except Exception:
try:
inter = poly.buffer(0).intersection(geoms[idx].buffer(0))
except Exception:
continue
w = inter.area
if w <= 0:
continue
for f in _EQ_FIELDS:
acc[f] += eq.get(f, 0.0) * w
wsum += w

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant