Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
df1b842
area.c
ajfriend Nov 27, 2025
b6d9ddb
adding in more stuff
ajfriend Nov 27, 2025
8080e56
merp
ajfriend Nov 27, 2025
5c8950a
kahan accumulator
ajfriend Nov 27, 2025
b82cab2
neumaier_add
ajfriend Nov 27, 2025
ec26cb8
notes
ajfriend Nov 27, 2025
74e277f
benchmark area
ajfriend Nov 27, 2025
c915b51
turn it to 11.
ajfriend Nov 28, 2025
a74b9d9
sadly, no improvement from reusing some trig
ajfriend Nov 28, 2025
98ecbee
back to original
ajfriend Nov 28, 2025
db21eb0
simplify
ajfriend Nov 28, 2025
04640ef
try adding constants
ajfriend Nov 29, 2025
2349ab4
format, we must
ajfriend Nov 29, 2025
0dd6a5e
Adder docs
ajfriend Nov 29, 2025
7528234
bench
ajfriend Nov 29, 2025
4a4fb07
adder docs
ajfriend Nov 29, 2025
6bd3299
Settled on Kahan implementation
ajfriend Nov 29, 2025
1b21bed
Tighten tolerances in testH3CellAreaExhaustive.c due to compensated sum
ajfriend Nov 29, 2025
64853b3
6
ajfriend Nov 29, 2025
3547c47
notes
ajfriend Nov 29, 2025
0c73921
starting new tests
ajfriend Nov 29, 2025
31fd416
tests work
ajfriend Nov 29, 2025
61702f1
more tests
ajfriend Nov 30, 2025
4b33b02
comments
ajfriend Nov 30, 2025
26eb143
docstring
ajfriend Nov 30, 2025
06b2a6b
better docstrings
ajfriend Nov 30, 2025
f55e1b1
Adder note
ajfriend Nov 30, 2025
0c939fc
don't need these imports
ajfriend Nov 30, 2025
a292924
clean a few more imports
ajfriend Nov 30, 2025
00f45a2
format
ajfriend Nov 30, 2025
39be84c
needs constants
ajfriend Nov 30, 2025
53e5be9
format
ajfriend Nov 30, 2025
139f483
try fuzzer
ajfriend Nov 30, 2025
afa64f1
.mdx
ajfriend Nov 30, 2025
f855776
docs in h3api.h.in
ajfriend Nov 30, 2025
e48b735
rads2
ajfriend Nov 30, 2025
11b277a
python plan
ajfriend Nov 30, 2025
226da8c
show, don't tell
ajfriend Nov 30, 2025
c274bff
benchmark clean up
ajfriend Nov 30, 2025
43f19a9
never say never again
ajfriend Nov 30, 2025
fffd723
ugh, would have been cooler if it compiled the first time
ajfriend Nov 30, 2025
efcbea0
remove justfile
ajfriend Nov 30, 2025
6e955c9
lighten the h3api.h.in description, as per usual in that file
ajfriend Nov 30, 2025
fdcbc17
Merge branch 'master' into cagnoli_area
ajfriend Nov 30, 2025
dfeb6d8
simplify adder initialization
ajfriend Nov 30, 2025
6db33a3
drop numVerts < 3
ajfriend Dec 1, 2025
e2c20b0
degenerate loop tests
ajfriend Dec 1, 2025
eeb3046
try _compareArea(NULL, 0, 0.0);
ajfriend Dec 1, 2025
28105c3
remove docs
ajfriend Dec 1, 2025
a3ee94d
drop H3_EXPORT
ajfriend Dec 1, 2025
571d72e
move geoLoopAreaRads2 out of public API for now
ajfriend Dec 1, 2025
7d0f56a
slim down benchmark
ajfriend Dec 1, 2025
77278a6
clear these comments, maybe?
ajfriend Dec 1, 2025
fa555e3
try adding area.h to APP_SOURCE_FILES
ajfriend Dec 2, 2025
64e9ad8
can't fail if you don't try!
ajfriend Dec 2, 2025
a059397
minor
ajfriend Dec 2, 2025
b24442f
remove justfile
ajfriend Dec 2, 2025
dd316e0
Adder adder = {};
ajfriend Dec 2, 2025
c727543
(multi)polygon area functions
ajfriend Dec 2, 2025
8c12e2f
degenerate loop note
ajfriend Dec 2, 2025
861bc81
destroy sigs
ajfriend Dec 2, 2025
59fefdc
destroy implementations
ajfriend Dec 2, 2025
246f2f4
createGlobalMultiPolygon
ajfriend Dec 2, 2025
4164d37
testGeoMultiPolygon.c
ajfriend Dec 2, 2025
808c398
try constants
ajfriend Dec 2, 2025
8f1c085
H3_EXPORT
ajfriend Dec 2, 2025
e6bbf55
rename to createGlobeMultiPolygon
ajfriend Dec 2, 2025
c24fffa
handle errors. add function docs
ajfriend Dec 2, 2025
a154a82
test for geoMultiPolygonAreaRads2
ajfriend Dec 2, 2025
fb79132
better test; more coverage
ajfriend Dec 2, 2025
741470d
Merge branch 'master' into cagnoli_area2
ajfriend Dec 17, 2025
bedf3fa
fix missing brace from github merge
ajfriend Dec 17, 2025
bdb0521
remove destroyGeoLoop and destroyGeoPolygon from public API
ajfriend Dec 18, 2025
ff56f5a
move global poly function
ajfriend Dec 18, 2025
82fdb50
remove unnecessary import
ajfriend Dec 18, 2025
f65a365
remove justfile
ajfriend Dec 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ set(OTHER_SOURCE_FILES
src/apps/testapps/testMathExtensionsInternal.c
src/apps/testapps/testDescribeH3Error.c
src/apps/testapps/testGeoLoopArea.c
src/apps/testapps/testGeoMultiPolygon.c
src/apps/miscapps/cellToBoundaryHier.c
src/apps/miscapps/cellToLatLngHier.c
src/apps/miscapps/generateBaseCellNeighbors.c
Expand Down
1 change: 1 addition & 0 deletions CMakeTests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ add_h3_test(testMathExtensionsInternal
src/apps/testapps/testMathExtensionsInternal.c)
add_h3_test(testDescribeH3Error src/apps/testapps/testDescribeH3Error.c)
add_h3_test(testGeoLoopArea src/apps/testapps/testGeoLoopArea.c)
add_h3_test(testGeoMultiPolygon src/apps/testapps/testGeoMultiPolygon.c)

add_h3_test_with_arg(testH3NeighborRotations
src/apps/testapps/testH3NeighborRotations.c 0)
Expand Down
104 changes: 104 additions & 0 deletions src/apps/testapps/testGeoMultiPolygon.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright 2025 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/** @file
* @brief tests for GeoMultiPolygon, GeoPolygon, and GeoLoop
*
* usage: `testGeoMultiPolygon`
*/

#include <math.h>
#include <string.h>

#include "algos.h"
#include "alloc.h"
#include "area.h"
#include "h3api.h"
#include "test.h"
#include "utility.h"

SUITE(geoMultiPolygon) {
TEST(globalMultiPolygonArea) {
double tol = 1e-14;
double out;

GeoMultiPolygon mpoly = createGlobeMultiPolygon();
t_assertSuccess(geoMultiPolygonAreaRads2(mpoly, &out));
t_assert(fabs(out - 4 * M_PI) < tol, "area should match");

H3_EXPORT(destroyGeoMultiPolygon)(&mpoly);
}

TEST(holeSameAsOuter) {
/**
* TODO: Replace with simpler test.
*
* I needed a test to exercize the "hole" branches of
* `destroyGeoMultiPolygon` and `geoMultiPolygonAreaRads2`.
*
* This is a verbose test because we have to allocate the
* `GeoMultiPolygon`. When we add in the `cellsToMultiPolygon` function,
* I can replace this with a much shorter, clearer test.
*/

// Create a polygon with a triangle outer and a hole of exactly
// the same size, so the resulting polygon and multipolygons should
// have 0 area.
LatLng _outer[] = {
// Counter-clockwise points
{M_PI_2, 0},
{0, 0},
{0, M_PI_2},
};
LatLng _hole[] = {
// Same as above, but clockwise points
{M_PI_2, 0},
{0, M_PI_2},
{0, 0},
};

GeoLoop outer = {
.numVerts = 3,
.verts = H3_MEMORY(malloc)(3 * sizeof(LatLng)),
};
GeoLoop hole = {
.numVerts = 3,
.verts = H3_MEMORY(malloc)(3 * sizeof(LatLng)),
};

memcpy(outer.verts, _outer, 3 * sizeof(LatLng));
memcpy(hole.verts, _hole, 3 * sizeof(LatLng));

GeoPolygon poly = {
.geoloop = outer,
.numHoles = 1,
.holes = H3_MEMORY(malloc)(sizeof(GeoLoop)),
};
poly.holes[0] = hole;

GeoMultiPolygon mpoly = {
.numPolygons = 1,
.polygons = H3_MEMORY(malloc)(sizeof(GeoPolygon)),
};
mpoly.polygons[0] = poly;

double out;
t_assertSuccess(geoMultiPolygonAreaRads2(mpoly, &out));
t_assert(fabs(out) < 1e-14, "Area should be 0");

H3_EXPORT(destroyGeoMultiPolygon)(&mpoly);
}
}
3 changes: 3 additions & 0 deletions src/h3lib/include/algos.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,7 @@ H3Error _gridDiskDistancesInternal(H3Index origin, int k, H3Index *out,

// The safe gridRing algorithm.
H3Error _gridRingInternal(H3Index origin, int k, H3Index *out);

// Create a GeoMultiPolygon covering the entire globe
GeoMultiPolygon createGlobeMultiPolygon();
#endif
2 changes: 2 additions & 0 deletions src/h3lib/include/area.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,7 @@
#include "h3api.h"

H3Error geoLoopAreaRads2(GeoLoop loop, double *out);
H3Error geoPolygonAreaRads2(GeoPolygon poly, double *out);
H3Error geoMultiPolygonAreaRads2(GeoMultiPolygon mpoly, double *out);

#endif
3 changes: 3 additions & 0 deletions src/h3lib/include/h3api.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,9 @@ DECLSPEC H3Error H3_EXPORT(cellsToLinkedMultiPolygon)(const H3Index *h3Set,

/** @brief Free all memory created for a LinkedGeoPolygon */
DECLSPEC void H3_EXPORT(destroyLinkedMultiPolygon)(LinkedGeoPolygon *polygon);

/** @brief Free all memory created for a GeoMultiPolygon */
DECLSPEC void H3_EXPORT(destroyGeoMultiPolygon)(GeoMultiPolygon *mpoly);
Comment thread
ajfriend marked this conversation as resolved.
/** @} */

/** @defgroup degsToRads degsToRads
Expand Down
79 changes: 79 additions & 0 deletions src/h3lib/lib/algos.c
Original file line number Diff line number Diff line change
Expand Up @@ -1280,3 +1280,82 @@ H3Error H3_EXPORT(cellsToLinkedMultiPolygon)(const H3Index *h3Set,
}
return normalizeResult;
}

/**
* Allocate a GeoMultiPolygon representing the entire globe.
* The globe is represented using 8 triangular polygons, with
* all edge arcs of exactly 90 degrees (i.e., pi/2 radians).
* Memory should be freed with `destroyGeoMultiPolygon`.
*
* @return GeoMultiPolygon covering entire globe
*/
GeoMultiPolygon createGlobeMultiPolygon() {
const int numPolygons = 8;
const int numVerts = 3;
const LatLng verts[8][3] = {
{{M_PI_2, 0.0}, {0.0, 0.0}, {0.0, M_PI_2}},
{{M_PI_2, 0.0}, {0.0, M_PI_2}, {0.0, M_PI}},
{{M_PI_2, 0.0}, {0.0, M_PI}, {0.0, -M_PI_2}},
{{M_PI_2, 0.0}, {0.0, -M_PI_2}, {0.0, 0.0}},
{{-M_PI_2, 0.0}, {0.0, 0.0}, {0.0, -M_PI_2}},
{{-M_PI_2, 0.0}, {0.0, -M_PI_2}, {0.0, -M_PI}},
{{-M_PI_2, 0.0}, {0.0, -M_PI}, {0.0, M_PI_2}},
{{-M_PI_2, 0.0}, {0.0, M_PI_2}, {0.0, 0.0}},
};

GeoMultiPolygon mpoly = {
.numPolygons = numPolygons,
.polygons = H3_MEMORY(malloc)(sizeof(GeoPolygon) * numPolygons),
};

for (int i = 0; i < numPolygons; i++) {
GeoPolygon *poly = &mpoly.polygons[i];
poly->numHoles = 0;
poly->holes = NULL;
poly->geoloop.numVerts = numVerts;
poly->geoloop.verts = H3_MEMORY(malloc)(sizeof(LatLng) * numVerts);

for (int j = 0; j < numVerts; j++) {
poly->geoloop.verts[j] = verts[i][j];
}
}

return mpoly;
}

/**
* Free all allocated memory for a GeoLoop. The caller is
* responsible for freeing memory allocated to input GeoLoop struct.
*/
void destroyGeoLoop(GeoLoop *loop) {
H3_MEMORY(free)(loop->verts);
loop->verts = NULL;
loop->numVerts = 0;
}

/**
* Free all allocated memory for a GeoPolygon. The caller is
* responsible for freeing memory allocated to input GeoPolygon struct.
*/
void destroyGeoPolygon(GeoPolygon *poly) {
destroyGeoLoop(&poly->geoloop);
for (int i = 0; i < poly->numHoles; i++) {
destroyGeoLoop(&poly->holes[i]);
}
H3_MEMORY(free)(poly->holes);
poly->holes = NULL;
poly->numHoles = 0;
}

/**
* Free all allocated memory for a GeoMultiPolygon. The caller is
* responsible for freeing memory allocated to input GeoMultiPolygon struct.
*/
void H3_EXPORT(destroyGeoMultiPolygon)(GeoMultiPolygon *mpoly) {
for (int i = 0; i < mpoly->numPolygons; i++) {
destroyGeoPolygon(&mpoly->polygons[i]);
}
H3_MEMORY(free)(mpoly->polygons);
mpoly->polygons = NULL;
mpoly->numPolygons = 0;
}
67 changes: 67 additions & 0 deletions src/h3lib/lib/area.c
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,73 @@ H3Error H3_EXPORT(cellAreaRads2)(H3Index cell, double *out) {
return E_SUCCESS;
}

/**
* Area of GeoPolygon in radians^2.
*
* Outer GeoLoop vertices should be in counter-clockwise order.
* Hole GeoLoop vertices should be in clockwise order.
* Returned area is the area contained by the outer loop, minus
* the areas of the holes. See `geoLoopAreaRads2` for the expected
* form of GeoLoops.
*
* No check is made to ensure holes are disjoint or are contained
* within the outer GeoLoop.
*
* @param poly GeoPolygon
* @param out GeoPolygon area in radians^2
* @return E_SUCCESS on success; error code otherwise
*/
H3Error geoPolygonAreaRads2(GeoPolygon poly, double *out) {
H3Error err;
Adder adder = {};
double term;

err = geoLoopAreaRads2(poly.geoloop, &term);
if (err) return err;
kadd(&adder, term);

for (int i = 0; i < poly.numHoles; i++) {
err = geoLoopAreaRads2(poly.holes[i], &term);
if (err) return err;

// Due to clockwise order, holes will contribute area
// of "everything except the hole", so adjust with -4*pi term.
kadd(&adder, term);
kadd(&adder, -4.0 * M_PI);
}

*out = adder.sum;

return E_SUCCESS;
}

/**
* Area of GeoMultiPolygon in radians^2.
*
* Area is the sum of the areas of the polygons contained by `mpoly`.
* See `geoPolygonAreaRads2` for expected polygon format.
* No check is made to ensure polygons are disjoint.
*
* @param mpoly GeoMultiPolygon
* @param out GeoMultiPolygon area in radians^2
* @return E_SUCCESS on success; error code otherwise
*/
H3Error geoMultiPolygonAreaRads2(GeoMultiPolygon mpoly, double *out) {
H3Error err;
Adder adder = {};
double term;

for (int i = 0; i < mpoly.numPolygons; i++) {
err = geoPolygonAreaRads2(mpoly.polygons[i], &term);
if (err) return err;
kadd(&adder, term);
}

*out = adder.sum;

return E_SUCCESS;
}

/**
* Area of H3 cell in kilometers^2.
*
Expand Down