thermograph/grid.py

52 lines
1.9 KiB
Python
Raw Normal View History

"""Snap an arbitrary lat/lon to a stable ~4-square-mile grid cell.
The grid is defined by fixed latitude rows (~2 miles tall). Within each row the
longitude step is scaled by cos(latitude) so cells stay roughly square (~4 sq mi)
at every latitude instead of getting skinny toward the poles. Cell ids are
deterministic, so the same physical location always maps to the same cache file.
"""
import math
# 1 degree of latitude ~= 69 miles. ~2 miles -> ~0.029 deg gives a ~4 sq mi cell.
LAT_STEP = 1.0 / 34.5 # ~= 0.02899 deg (~2.0 miles)
def _lon_step(center_lat: float) -> float:
"""Longitude degrees that span ~2 miles at the given latitude."""
c = math.cos(math.radians(center_lat))
c = max(c, 0.05) # clamp near the poles to avoid a blow-up
return LAT_STEP / c
def snap(lat: float, lon: float) -> dict:
"""Return the grid cell (id + center + span) containing (lat, lon)."""
i = math.floor(lat / LAT_STEP)
center_lat = (i + 0.5) * LAT_STEP
lon_step = _lon_step(center_lat)
j = math.floor(lon / lon_step)
center_lon = (j + 0.5) * lon_step
# Approximate cell dimensions in miles for display.
height_mi = LAT_STEP * 69.0
width_mi = lon_step * 69.0 * math.cos(math.radians(center_lat))
return {
"id": f"{i}_{j}",
"center_lat": round(center_lat, 5),
"center_lon": round(center_lon, 5),
"lat_step": LAT_STEP,
"lon_step": lon_step,
"bounds": {
"south": round(i * LAT_STEP, 5),
"north": round((i + 1) * LAT_STEP, 5),
"west": round(j * lon_step, 5),
"east": round((j + 1) * lon_step, 5),
},
"area_sq_mi": round(height_mi * width_mi, 2),
}
def in_north_america(lat: float, lon: float) -> bool:
"""Rough bounding box for the US (incl. Alaska/Hawaii) and Canada."""
return 14.0 <= lat <= 84.0 and -172.0 <= lon <= -52.0