Skip to content

Geodesic polygon coverage#1052

Open
holoskii wants to merge 28 commits intouber:masterfrom
holoskii:geodesic_coverage
Open

Geodesic polygon coverage#1052
holoskii wants to merge 28 commits intouber:masterfrom
holoskii:geodesic_coverage

Conversation

@holoskii
Copy link

@holoskii holoskii commented Oct 5, 2025

Summary

Adds the geodesic traversal mode to polygonToCellsExperimental, letting us follow true great-circle edges when filling massive polygons (think continental footprints and polar caps). The PR builds the geodesic stack from the ground up—new vector math helpers, bounding caps, iterator wiring, and plenty of validation coverage—while keeping the default planar behavior unchanged (and safely rejecting geodesic flags on the legacy API).

Highlights

  • New geodesic mode

    • Introduces FLAG_GEODESIC_MASK (with helpers) so callers can opt into geodesic coverage; validates acceptable containment modes (FULL, OVERLAPPING) at both maxPolygonToCellsSizeExperimental and polygonToCellsExperimental.
    • Dispatches a dedicated geodesic iterator path that reuses the existing traversal logic but evaluates cells using great-circle geometry.
  • Core geometry plumbing

    • Adds GeodesicPolygon acceleration structures + helpers (geodesicPolygonCreate/Destroy, containment, boundary/cap intersection tests).
    • Extends the vector toolkit (latLngToVec3, cross/dot/normalize, distance helpers) and new conversions: vec3dToCell, cellToVec3, cellToGeodesicBoundary, cellToSphereCap.
    • Pulls bounding-cap tables into sphereCapTables.h for both prod code and tests; exposes SphereCap/AABB helpers.
  • Iterator & polyfill updates

    • Refactors polygon iterators into polyfill_iterator.h (with _extra context) and wires in geodesic_iterator.c.
    • Ensures geodesic flags short-circuit the legacy planar polygonToCells path.
  • Benchmarks, fuzzers, and tests

    • Benchmarks: geodesic variants for existing benchmark.
    • Fuzzers now exercise geodesic flag permutations.
    • Added new test suites: testGeodesicPolygonToCellsExperimental, testSphereCap, testVec3d, plus extended testBBoxInternal, testPolygonToCells(Experimental) flag validation, and memory-failure coverage.
    • Hooks new tests into CMakeLists.txt/CMakeTests.cmake.
  • Documentation

    • Updates the Regions API docs to explain the geodesic flag, trade-offs, and usage tips.

Attribution

This geodesic polyfill work originated at Yellowbrick Data (where I’m currently employed). We’re upstreaming it to the H3 project so the broader community can benefit and use it for coverage.

@CLAassistant
Copy link

CLAassistant commented Oct 5, 2025

CLA assistant check
All committers have signed the CLA.

@coveralls
Copy link

coveralls commented Oct 6, 2025

Coverage Status

coverage: 98.794% (-0.2%) from 99.013%
when pulling c3f3089 on holoskii:geodesic_coverage
into 8085979 on uber:master.

@ajfriend
Copy link
Collaborator

ajfriend commented Oct 9, 2025

Thanks @holoskii for the contribution! Geodesic polyfill is an exciting addition that I think a lot of folks would use, myself included!

Please bear with us as we try to find the time to properly review this PR. Some initial questions from my end:

  • Is it fair to characterize this PR as providing two major improvements:
    1. geodesic mode for "polyfill", for polygons that would otherwise already be well-handled in the existing planar mode in polygonToCellsExperimental, and
    2. the ability to do polyfill on polygons larger or tricker (e.g. around poles or the antimeridian) than those that could be handled in the previous algorithm (and, if so, would these improvements also generalize to the existing planar mode?)
  • This may become obvious after I'm able to review more closely, but how much code can we share between the planar and geodesic algorithms?
  • Is it necessary to expose Vec3d and GeodesicCellBoundary in the public api (h3api.h.in), or can we keep those structs internal?
  • You mentioned that this mode is slower than the existing algorithm. How much slower? Do you have any example benchmarks you could share?
  • Any thoughts on what to do about the failing CI checks? Or would you like some input from the team?

Copy link
Collaborator

@isaacbrodsky isaacbrodsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your contribution and opening this PR! Please let me know if you have questions. I am beginning to review the contribution and will add more comments as I read & test.

@ajfriend ajfriend added this to the v4.5 milestone Oct 16, 2025
@holoskii
Copy link
Author

Fixes / changes

  1. Introduce an explicit epsilon for the SphereCap check. The previous iteration relied on tolerance built into precomputed cosine values; in practice, we also need an additional epsilon.
  2. Use GeodesicPolygon* instead of void* in the iterator structure.
  3. Fix the MSVC build by using a macro instead of a constant.
  4. Move GeodesicCellBoundary and Vec3d to their own headers instead of defining them in the public API header.
  5. Move internal headers to the include directory.
  6. Add a new geodesic benchmark.
  7. Rebase on the latest master.

Performance

I added a dedicated geodesic benchmark that exercises small, medium, and large shapes.

  • Small cells: up to 60× slower.
  • Medium cells / large shapes: ~20×.
  • “Primary” geodesic use case (huge shapes + large cells, e.g., NY ↔ London great-circle path): ~2.5×.

Notes:

  • At Yellowbrick Data, we’ve seen ~10× performance improvements by pushing heavy inlining (by moving code to headers), branch hints, etc. I avoided invasive changes here to keep code style intact; the current PR aims for minimal intrusion.

@holoskii
Copy link
Author

Thanks @holoskii for the contribution! Geodesic polyfill is an exciting addition that I think a lot of folks would use, myself included!

Please bear with us as we try to find the time to properly review this PR. Some initial questions from my end:

* Is it fair to characterize this PR as providing two major improvements:
  
  1. geodesic mode for "polyfill", for polygons that would otherwise already be well-handled in the existing planar mode in `polygonToCellsExperimental`, and
  2. the ability to do polyfill on polygons larger or tricker (e.g. around poles or the antimeridian) than those that could be handled in the previous algorithm (and, if so, would these improvements also generalize to the existing planar mode?)

* This may become obvious after I'm able to review more closely, but how much code can we share between the planar and geodesic algorithms?

* Is it necessary to expose `Vec3d` and `GeodesicCellBoundary` in the public api (`h3api.h.in`), or can we keep those structs internal?

* You mentioned that this mode is slower than the existing algorithm. How much slower? Do you have any example benchmarks you could share?

* Any thoughts on what to do about the failing CI checks? Or would you like some input from the team?

1.1 - Yes.

1.2 - The geodesic approach behaves better for large polygons and near poles/antimeridian due to great-circle behavior, but it’s architecturally different from the planar path; there isn’t a practical feature we can “back-port” without re-implementing substantial pieces.

2 - We only share FaceIjk and iterator code between the two implementations. Intersection/containment logic and related optimization structures are written from scratch for the geodesic mode.

3 - Performance metrics are now included in the main PR comment (see “Performance” above) with the new benchmark.

@holoskii holoskii requested a review from isaacbrodsky October 27, 2025 09:48
@holoskii
Copy link
Author

A few small fixes for the CI:

  • examples did not compile because of "constants.h" included in h3api.h. Removed that include to keep api header standalone
  • fuzzer hit a legitimate timeout. Fixed this by limiting resolution and number of points for geodesic runs

@holoskii
Copy link
Author

holoskii commented Nov 3, 2025

@isaacbrodsky was right, separate fuzzer is a way to go. Old fuzzers left not enough time until timeout in each iteration. In the last commit I've added a separate geodesic fuzzer. PR is now fully ready for review

@holoskii
Copy link
Author

Hi @nrabinowitz @dfellis @isaacbrodsky - friendly bump on this.
Current state: fixed CI issues, added benchmarks, answered questions and addressed the comments.

@isaacbrodsky
Copy link
Collaborator

Hi @nrabinowitz @dfellis @isaacbrodsky - friendly bump on this. Current state: fixed CI issues, added benchmarks, answered questions and addressed the comments.

Thanks for updating the PR and for the bump! We appreciate the contribution and know we need to review this, so it's just a matter of finding time to read through it in depth. We would like to bring this in for the next release of H3.

@ajfriend ajfriend modified the milestones: v4.5, v4.6 Feb 11, 2026
- move test to appropriate file
- move comment to appropriate file
@isaacbrodsky
Copy link
Collaborator

@holoskii Thanks for the update! I think CI is failing on the formatting assertion (needs make format with clang-format-14)

@holoskii
Copy link
Author

@holoskii Thanks for the update! I think CI is failing on the formatting assertion (needs make format with clang-format-14)

My update is a bit overdue, this year has been very busy so far 😅
I will address a few more comments (missed them in my first push), and then will use the correct formatter (I probably used wrong version)

@holoskii
Copy link
Author

Resolved final issues, formatted with version 14. Should be ready for the CI

2 small issues to raise after this PR is merged:

@holoskii
Copy link
Author

Windows workflow failed because on 32-bit Windows systems, the math library doesn't handle NaN inputs correctly. Instead of ignoring NaN data when building a bounding box, it lets the NaN poison the BBOX. This leads to us hitting NaN down the road and failing later.

I added a check at the very beginning, during polygon creation to catch invalid coordinates (NaN or Infinity). Now, we reject bad input right away with a clear error code (E_DOMAIN) instead of letting it break things further down the line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants