diff --git a/src/apps/testapps/testH3CellArea.c b/src/apps/testapps/testH3CellArea.c index 4189dfc7d..9b0ab6ac0 100644 --- a/src/apps/testapps/testH3CellArea.c +++ b/src/apps/testapps/testH3CellArea.c @@ -47,6 +47,23 @@ SUITE(h3CellArea) { } } + TEST(cell_area_antimeridian) { + // Cells straddling the antimeridian at fine resolutions, paired with + // an adjacent cell that does not. Their areas must agree closely; a + // naive longitude difference across +-180 degrees loses precision + // and makes the straddling cell up to several percent off. + H3Index pairs[][2] = {{0x8d056898db628bfULL, 0x8d056898db62d7fULL}, + {0x8e32b605c51e067ULL, 0x8e32b605c51e15fULL}, + {0x8f7f809893b5b9dULL, 0x8f7f809893b5b9cULL}}; + for (int i = 0; i < 3; i++) { + double a, b; + t_assertSuccess(H3_EXPORT(cellAreaRads2)(pairs[i][0], &a)); + t_assertSuccess(H3_EXPORT(cellAreaRads2)(pairs[i][1], &b)); + t_assert(fabs(a / b - 1.0) < 1e-5, + "antimeridian cell area should match neighbor"); + } + } + TEST(cell_area_invalid) { H3Index invalid = 0xFFFFFFFFFFFFFFFF; double area; diff --git a/src/h3lib/lib/area.c b/src/h3lib/lib/area.c index cebd605c4..f46e86dff 100644 --- a/src/h3lib/lib/area.c +++ b/src/h3lib/lib/area.c @@ -37,7 +37,15 @@ static inline double cagnoli(LatLng x, LatLng y) { double sa = sin(x.lat) * sin(y.lat); double ca = cos(x.lat) * cos(y.lat); + // For an edge crossing the antimeridian, the raw difference is close to + // +-2*pi, and the true (tiny) longitude step is lost to the ulp of 2*pi. + // Shift both longitudes to the same side of the antimeridian first so + // that the subtraction is between two nearby values; sin and cos are + // 2*pi-periodic, so the result is mathematically unchanged. double d = y.lng - x.lng; + if (d > M_PI || d < -M_PI) { + d = (y.lng - copysign(M_PI, y.lng)) - (x.lng - copysign(M_PI, x.lng)); + } double sd = sin(d); double cd = cos(d);