Repository navigation
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 🔧
- the spherical inverse projection (
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 builds - fixes a codegen regression on
net10.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 🎉
- new zero-allocation span/buffer-fill overloads across the traversal, hierarchy and set APIs:
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 results - new
ParallelFillfor 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 thanFill
Performance 🚀
- the forward and inverse spherical projections (
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 loop - aperture-7 digit-pair down-steps in the resolution walk are fused into a single normalization, and
ToFaceIJKfuses the aperture-7 down-step with the neighbour walk;GetDirectNeighbourfast-paths the common no-base-cell-crossing tail and skips the identity rotation reorient - polyfill flood fill replaces NTS's per-call allocating point-in-area locator with an allocation-free inline ray-crossing locator, replaces its
HashSet/Stackworking sets with pooled open-addressed structures presized from the geometry's area, and hoists per-cell invariants out of the neighbour walk - grid disk, cell boundary and edge length drop iterator and intermediate-array allocations via eager span fills and
stackallocbuffers;CompactCellsuses a presized bucket and a primitiveulongsort, andUncompactCellsuses a pooled open-addressed dedup table and an inlined child walk
Testing
- geometry parity is gated per operation against authoritative libh3 v4.5.0: per-cell outputs within 1 ULP and the whole-sphere cell-area sum within 3 ULP, with a persisted manifest recording the worst per-op ULP observed (index and cell outputs match exactly). Added adversarial
Vec3dedge-case coverage and per-op ULP diagnostics