77 lines
2.6 KiB
Python
Executable file
77 lines
2.6 KiB
Python
Executable file
#!/usr/bin/env nix-shell
|
|
#!nix-shell -i "python3 -i" -p python3Packages.shapely python3Packages.fiona python3Packages.requests
|
|
|
|
print("Starting…")
|
|
|
|
from collections import defaultdict
|
|
import csv
|
|
import json
|
|
import os
|
|
import io
|
|
|
|
import requests
|
|
import fiona
|
|
from shapely.geometry import shape, mapping
|
|
from shapely.ops import unary_union
|
|
|
|
print("Completed imports")
|
|
|
|
# Create lists of zip codes per center and centers per state
|
|
print("parsing URC255V.csv…", end="", flush=True)
|
|
if not os.path.exists("URC255V.csv"):
|
|
print("Warning: URC255V.csv not found. Please download it from UPS and place it in this directory.")
|
|
exit(1)
|
|
# Read URC255V.csv into a dict
|
|
reader = csv.DictReader(open("URC255V.csv", encoding="utf-8"))
|
|
centers_by_state = defaultdict(set)
|
|
zips_by_center = defaultdict(list)
|
|
# "CountryCode","PostalLow","PostalHigh","URC25.5V","06/2023"
|
|
# "US","55336","55336"," MN 553 0-01"
|
|
# "US","55337","55337"," MN 551 9-02"
|
|
for row in reader:
|
|
if row["CountryCode"] == "US":
|
|
center = row["URC25.5V"].strip().split('-')[0]
|
|
state = center.split(" ")[0]
|
|
centers_by_state[state].add(center)
|
|
for zip in range(int(row["PostalLow"]), int(row["PostalHigh"]) + 1):
|
|
zips_by_center[center].append(str(zip).zfill(5))
|
|
print("complete.")
|
|
|
|
# Fetch and parse zip code geometries
|
|
# TODO: could also get as geopackage or kml file I think? Not sure if either of those is easier to open.
|
|
print("fetching zip code data…", end="", flush=True)
|
|
res = requests.get("https://www2.census.gov/geo/tiger/GENZ2020/shp/cb_2020_us_zcta520_500k.zip")
|
|
res.raise_for_status()
|
|
print("complete.")
|
|
print("parsing zip code data…", end="", flush=True)
|
|
# Yeah, this loads into memory, but the file is only 60ish MiB.
|
|
zip_data = list(fiona.io.ZipMemoryFile(io.BytesIO(res.content)).open())
|
|
zips = {}
|
|
for zip in zip_data: # PIVOT! PIVOT!
|
|
zips[zip.properties["NAME20"]] = zip.geometry
|
|
print("complete.")
|
|
|
|
# Save output
|
|
print("writing output files…")
|
|
os.makedirs("states", exist_ok=True)
|
|
for state, centers in centers_by_state.items():
|
|
print(" "+ state)
|
|
features = []
|
|
for center in centers:
|
|
center_zips = zips_by_center[center]
|
|
features.append({
|
|
"type": "Feature",
|
|
"properties": {
|
|
"center": center,
|
|
},
|
|
"geometry": mapping(unary_union([shape(zips[zip]) for zip in center_zips if zip in zips])),
|
|
})
|
|
with open(f"states/{state}.geojson", "w", encoding="utf-8") as f:
|
|
json.dump(
|
|
{
|
|
"type": "FeatureCollection",
|
|
"features": features,
|
|
},
|
|
f,
|
|
)
|
|
print("complete.")
|