Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
| # Build per-state ZCTA (ZIP) TopoJSON tiles for the census drilldown ZIP tier. | |
| # | |
| # Output: web_app/public/data/zctas/state-XX.json (one per state FIPS) | |
| # - TopoJSON with objects.zctas | |
| # - feature.id = ZCTA5 GEOID (5-digit string) | |
| # - Lng/lat coords (the page's Albers projection is applied client-side) | |
| # | |
| # Why a spatial join: TIGER ZCTA shapefiles don't carry STATEFP β ZCTAs cross | |
| # state lines, so Census doesn't pre-assign one. We assign each ZCTA to the | |
| # state whose polygon contains its centroid (mapshaper's default behavior for | |
| # polygon-to-polygon -join). | |
| # | |
| # Why TopoJSON: ~70% smaller than raw GeoJSON; matches the existing us-atlas | |
| # state/county tiles. | |
| # | |
| # One-time prep β re-run only when a new TIGER vintage drops. | |
| # | |
| # Inputs (configurable via env): | |
| # ZCTA_SHP TIGER ZCTA shapefile (default: data/cache/census/shapefiles/2025/tl_2025_us_zcta520/tl_2025_us_zcta520.shp) | |
| # STATES_SHP Census state shapefile (default: data/cache/census/shapefiles/2025/cb_2025_us_state_500k/cb_2025_us_state_500k.shp) | |
| # OUT_DIR Output dir (default: web_app/public/data/zctas) | |
| # SIMPLIFY Mapshaper simplification percent (default: 5%) | |
| # | |
| # Source shapefiles (2025 vintage, fetched by the project's census loader): | |
| # ZCTA β TIGER/Line: https://www2.census.gov/geo/tiger/TIGER2025/ZCTA520/tl_2025_us_zcta520.zip | |
| # STATE β Cartographic Boundary 1:500k: https://www2.census.gov/geo/tiger/GENZ2025/shp/cb_2025_us_state_500k.zip | |
| # (cb_500k is generalized; fine here since we only use it for the | |
| # ZCTA-centroid β STATEFP spatial join, not for rendering) | |
| # | |
| # Heads-up: the original user-supplied command used `tl_2025_us_zcta510.shp` | |
| # and `-split STATEFP` directly. That fails because (a) the 510 ZCTA delineation | |
| # was retired in 2020 (current files are zcta520), and (b) STATEFP isn't on | |
| # ZCTA features. This script fixes both. | |
| set -euo pipefail | |
| CACHE_DIR="data/cache/census/shapefiles/2025" | |
| ZCTA_SHP="${ZCTA_SHP:-$CACHE_DIR/tl_2025_us_zcta520/tl_2025_us_zcta520.shp}" | |
| STATES_SHP="${STATES_SHP:-$CACHE_DIR/cb_2025_us_state_500k/cb_2025_us_state_500k.shp}" | |
| OUT_DIR="${OUT_DIR:-web_app/public/data/zctas}" | |
| SIMPLIFY="${SIMPLIFY:-5%}" | |
| command -v mapshaper >/dev/null 2>&1 || { | |
| echo "ERR: mapshaper not on PATH. Install with: npm install -g mapshaper" >&2 | |
| exit 1 | |
| } | |
| [[ -f "$ZCTA_SHP" ]] || { | |
| echo "ERR: missing $ZCTA_SHP" >&2 | |
| echo " Download: https://www2.census.gov/geo/tiger/TIGER2025/ZCTA520/tl_2025_us_zcta520.zip" >&2 | |
| echo " Unzip to: $CACHE_DIR/tl_2025_us_zcta520/" >&2 | |
| exit 1 | |
| } | |
| [[ -f "$STATES_SHP" ]] || { | |
| echo "ERR: missing $STATES_SHP" >&2 | |
| echo " Download: https://www2.census.gov/geo/tiger/GENZ2025/shp/cb_2025_us_state_500k.zip" >&2 | |
| echo " Unzip to: $CACHE_DIR/cb_2025_us_state_500k/" >&2 | |
| exit 1 | |
| } | |
| mkdir -p "$OUT_DIR" | |
| TMP_DIR="$(mktemp -d)" | |
| trap 'rm -rf "$TMP_DIR"' EXIT | |
| echo ">> Pass 1: simplify ($SIMPLIFY) + spatial-join ZCTAs β states β $TMP_DIR/joined.json" | |
| echo " (this is the slow step β full national ZCTA set, ~5-10 min)" | |
| mapshaper -i "$ZCTA_SHP" \ | |
| -simplify "$SIMPLIFY" keep-shapes \ | |
| -join "$STATES_SHP" calc='STATEFP=first(STATEFP)' fields=STATEFP \ | |
| -filter '!!STATEFP' \ | |
| -rename-layers zctas \ | |
| -o "$TMP_DIR/joined.json" format=geojson | |
| # 50 states + DC. Territories (60-78) skipped β no ACS data flowing through them. | |
| STATE_FIPS=( | |
| 01 02 04 05 06 08 09 10 11 12 13 15 16 17 18 19 20 21 22 23 | |
| 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 44 | |
| 45 46 47 48 49 50 51 53 54 55 56 | |
| ) | |
| echo ">> Pass 2: split into per-state TopoJSON (${#STATE_FIPS[@]} files)" | |
| written=0 | |
| empty=0 | |
| for FIPS in "${STATE_FIPS[@]}"; do | |
| OUT="$OUT_DIR/state-$FIPS.json" | |
| if mapshaper -i "$TMP_DIR/joined.json" \ | |
| -filter "STATEFP === '$FIPS'" \ | |
| -rename-layers zctas \ | |
| -o "$OUT" format=topojson id-field=GEOID20 2>/dev/null \ | |
| && [[ -s "$OUT" ]]; then | |
| size=$(du -h "$OUT" | cut -f1) | |
| printf " %s %s\n" "$FIPS" "$size" | |
| written=$((written + 1)) | |
| else | |
| rm -f "$OUT" | |
| empty=$((empty + 1)) | |
| fi | |
| done | |
| echo | |
| echo "Done. $written state file(s) in $OUT_DIR, $empty empty/failed." | |
| du -sh "$OUT_DIR" 2>/dev/null || true | |