fix: bound radius areas by the exact spherical cap

The longitude span divided the angular radius by the center's cosine, which
is too narrow for large radii and near the poles. Areas now use the exact
tangent bound and span every longitude when the circle reaches a pole.
This commit is contained in:
Sameh Khamis committed 2026-10-02 15:21:36 -07:00
1 parent cac5ba3b72
commit 5b73b70d19
2 files changed
+41 -3

No files matched your search

+11 -3
View File
@@ -82,10 +82,18 @@ export async function resolveArea(area, { services, signal } = {}) {
};
}
/**
* The bounding box of a spherical cap. When the cap reaches a pole it spans
* every longitude; otherwise the longitude span is the exact tangent bound,
* asin(sin(d) / cos(lat)) for angular radius d.
*/
function circleArea(lat, lon, radiusKm) {
const latSpan = (radiusKm / EARTH_RADIUS_KM) * (180 / Math.PI);
const cosLat = Math.cos((lat * Math.PI) / 180);
const lonSpan = cosLat < 1e-6 ? 180 : Math.min(180, latSpan / cosLat);
const angular = radiusKm / EARTH_RADIUS_KM;
const latSpan = angular * (180 / Math.PI);
const reachesPole = lat + latSpan >= 90 || lat - latSpan <= -90;
const ratio = Math.sin(angular) / Math.cos((lat * Math.PI) / 180);
const lonSpan =
reachesPole || ratio >= 1 ? 180 : Math.asin(ratio) * (180 / Math.PI);
const wrap = (value) => ((((value + 180) % 360) + 360) % 360) - 180;
return {
label: `${radiusKm} km around ${lat.toFixed(3)}, ${lon.toFixed(3)}`,