Files
classifieds/app/services/geo.py
T
2026-06-15 11:23:05 -04:00

36 lines
1.3 KiB
Python

"""Geo helpers: ZIP geocoding, haversine distance, bounding-box radius filter.
Portable across MySQL and SQLite (no spatial extension needed). For large-scale
deployments, see README for the MySQL POINT + SPATIAL INDEX upgrade.
"""
import math
from app.models.geo import ZipGeo
EARTH_MI = 3958.7613 # mean earth radius, miles
def geocode_zip(zip_code):
"""Return (lat, lng, city, state, metro) or None."""
row = ZipGeo.query.get((zip_code or "").strip())
if row is None:
return None
return row.lat, row.lng, row.city, row.state, row.metro
def haversine_mi(lat1, lng1, lat2, lng2):
rlat1, rlat2 = math.radians(lat1), math.radians(lat2)
dlat = math.radians(lat2 - lat1)
dlng = math.radians(lng2 - lng1)
a = (math.sin(dlat / 2) ** 2
+ math.cos(rlat1) * math.cos(rlat2) * math.sin(dlng / 2) ** 2)
return EARTH_MI * 2 * math.asin(math.sqrt(a))
def bounding_box(lat, lng, radius_mi):
"""Lat/lng min/max box enclosing the radius. Cheap indexed prefilter."""
lat_delta = radius_mi / 69.0
# guard against poles; cos(lat) shrinks lng degrees-per-mile
cos_lat = max(math.cos(math.radians(lat)), 0.01)
lng_delta = radius_mi / (69.172 * cos_lat)
return (lat - lat_delta, lat + lat_delta, lng - lng_delta, lng + lng_delta)