Spaces:
Sleeping
Sleeping
File size: 3,786 Bytes
cd6f412 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | import logging
import requests
from typing import Optional, Dict, Any, List
logger = logging.getLogger(__name__)
class ZipCodeData(object):
"""A simple data class to hold the results of our geolocation lookup."""
def __init__(self, state: str, state_abbr: str, city: str, county: str):
self.state = state
self.state_abbr = state_abbr
self.city = city
self.county = county
def to_dict(self) -> Dict[str, Any]:
return {
"state": self.state,
"state_abbreviation": self.state_abbr,
"city": self.city,
"county": self.county
}
def get_lat_lon_from_zip(zip_code: str) -> Optional[Dict[str, float]]:
"""
Step 1: Get latitude and longitude from a ZIP code using a simple API.
We'll use zippopotam.us for this first step.
"""
url = f"https://api.zippopotam.us/us/{zip_code}"
logger.info(f"Fetching lat/lon for ZIP code: {zip_code} from {url}")
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
if not data.get("places"):
logger.warning(f"No places found for ZIP code {zip_code}")
return None
place = data["places"][0]
return {
"latitude": float(place["latitude"]),
"longitude": float(place["longitude"]),
"state": place["state"],
"state_abbr": place["state abbreviation"],
"city": place["place name"]
}
except (requests.RequestException, KeyError, ValueError) as e:
logger.error(f"Failed to get lat/lon for ZIP {zip_code}: {e}")
return None
def get_county_from_lat_lon(lat: float, lon: float) -> Optional[str]:
"""
Step 2: Get county information from latitude and longitude using the
U.S. Census Bureau's Geocoding API.
"""
url = "https://geocoding.geo.census.gov/geocoder/geographies/coordinates"
params = {
'x': lon,
'y': lat,
'benchmark': 'Public_AR_Current',
'vintage': 'Current_Current',
'format': 'json'
}
logger.info(f"Fetching county for coordinates: (lat={lat}, lon={lon}) from Census Bureau API")
try:
response = requests.get(url, params=params, timeout=15)
response.raise_for_status()
data = response.json()
geographies = data.get("result", {}).get("geographies", {})
counties = geographies.get("Counties", [])
if counties:
county_name = counties[0].get("NAME")
logger.info(f"Found county: {county_name}")
return county_name
else:
logger.warning(f"No county found for coordinates (lat={lat}, lon={lon})")
return None
except (requests.RequestException, KeyError, ValueError) as e:
logger.error(f"Failed to get county from coordinates: {e}")
return None
def get_geo_data_from_zip(zip_code: str) -> Optional[ZipCodeData]:
"""
Orchestrates the two-step process to get state, city, and county from a ZIP code.
"""
# Step 1: Get Lat/Lon and basic info
geo_basics = get_lat_lon_from_zip(zip_code)
if not geo_basics:
return None
# Step 2: Get County from Lat/Lon
county = get_county_from_lat_lon(geo_basics["latitude"], geo_basics["longitude"])
if not county:
# Fallback: sometimes county info is not available, but we can proceed without it
logger.warning(f"Could not determine county for ZIP {zip_code}, proceeding without it.")
county = "Unknown"
return ZipCodeData(
state=geo_basics["state"],
state_abbr=geo_basics["state_abbr"],
city=geo_basics["city"],
county=county
) |