fix: correctness fixes; performance regressions - #142
Merged
Conversation
pocketpickleken
approved these changes
Jul 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A performance and allocation pass on top of 4.5.0, plus new zero-allocation span APIs and a parallel polyfill. No upstream H3 API changes; cell and index results remain identical to 4.5.0 (the projection fix below shifts geometry by a few ULP to match libh3 exactly).
Fixes 🔧
ToLatLng,GetCellBoundaryand its vertices, directed-edge boundaries, and everything built on them) now matches upstream libh3 v4.5.0 bit-for-bit. v4.5.0 replaced the spherical-law-of-cosines inverse with a 3D-vector construction (tangent basis + linear combination + normalize); the port now follows it in the same operation order. Center and boundary coordinates shift by a few ULP from earlier 4.5.0 buildsnet10.0inGetDirectNeighbour/GetDirectNeighbourWithoutRotations: over-aggressive inlining of the generated per-digit rotation tables gave the method a large zero-initialized stack frame, making grid disk, grid ring,IsNeighbour, directed-edge and polyfill operations several times slower under the net10 JIT than under net8. net10 is now at parity with net8 on these paths (net8 was unaffected)Fillnow traces the polygon boundary in cells before flooding inward, matching upstreampolygonToCellsseeding. Thin/narrow features, holes and antimeridian-crossing shapes that are contained but only reachable through non-contained neighbours (and so were previously dropped from a single interior seed) are now returned. Locked to libh3's exact cell set by a differential corpus (thin sliver, concave L, box-with-hole, antimeridian quad, disjoint multipolygon)Enhancements 🎉
GridDisk,GridDiskDistances,GridRingUnsafe,GridPathCells,GetChildrenForResolution,CompactCells,UncompactCellsandGetCellBoundaryVerticesgain overloads that fill a caller-ownedSpan<T>and return the number of cells written, with sizing helpersMaxGridDiskSize,MaxGridRingSize,GridPathCellsSize,CellToChildrenSizeandUncompactCellsSize. Allocation-free on a warmArrayPool; the streamingIEnumerableAPIs are unchanged and produce identical resultsParallelFillfor large polygon fills: shards the polygon's envelope into horizontal strips, fills each concurrently and unions the result. Produces the same (unordered) cell set asFill. It is opt-in and only pays off on large fills: it trades the sequential fill's flat allocation for wall-clock, and below a few thousand output cells the setup cost makes it slower thanFillPerformance 🚀
FromLatLng/ToLatLng, cell boundaries, areas, edge lengths) drop a large amount of redundant transcendental work while staying bit-for-bit with the reference: precomputed per-face-center sin/cos and axis-azimuth tables, angle-subtraction identities in place of per-callatan2/sin/cos, a cancelledcos(latitude)in the longitude solve, a collapsed planar-radiussqrt, precomputedM_SQRT7powers, integer round-div-by-7 on the aperture-7 up-scaling chain, and per-vertex trig cached across the Cagnoli area loopToFaceIJKfuses the aperture-7 down-step with the neighbour walk;GetDirectNeighbourfast-paths the common no-base-cell-crossing tail and skips the identity rotation reorientHashSet/Stackworking sets with pooled open-addressed structures presized from the geometry's area, and hoists per-cell invariants out of the neighbour walkstackallocbuffers;CompactCellsuses a presized bucket and a primitiveulongsort, andUncompactCellsuses a pooled open-addressed dedup table and an inlined child walkTesting
Vec3dedge-case coverage and per-op ULP diagnostics