Releases: pocketken/H3.net
Releases · pocketken/H3.net
Release list
Release 4.5.0.1
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
Release 4.5.0
Aligns the port with upstream H3 4.5.0 and modernizes the supported .NET targets.
Breaking Changes 📣
- retargets the library to
net10.0,net8.0,netstandard2.1andnetstandard2.0; EOLnet6.0/net7.0targets have been dropped (netstandard2.0remains for Unity and .NET Framework consumers) #98, #140 EdgeLengthRadians/EdgeLengthKilometers/EdgeLengthMetersandGetDirectedEdgeBoundaryVerticesnow throwArgumentExceptionwhen the index is not a valid directed edge instead of silently returning 0 / an empty enumerable, matching upstream'sE_DIR_EDGE_INVALID#109GridRingis now pentagon safe, matching upstream 4.3.0+gridRing: it no longer throws on pentagonal distortion, transparently falling back to a slower-but-correct traversal. The previous throwing behaviour is available asGridRingUnsafe(= upstreamgridRingUnsafe), which the obsoleteGetHexRingnow aliases.GridRingalso now validates its inputsCellAreaInRadiansSquared(and the Km²/m² variants) use upstream 4.5.0's Cagnoli formula with compensated summation; areas differ very slightly from previous releases (cell areas at a given resolution now sum to the area of the sphere to within 1e-14)H3Index,CoordIJK,FaceIJKandLatLngare now structs (completingCoordIJ,Vec2d,Vec3d,FaceOrientIJKfrom earlier work), eliminating per-index/per-vertex heap allocations throughout:FromLatLng/FromPointandToLatLng/ToPointare measured allocation-free (previously 131 and 96 B/op), grid disk allocations drop ~2.3x and children iteration ~17x #93. Value semantics apply:nullis no longer a valid index (useH3Index.Invalid;default(H3Index)==Invalid), mutations affect only the local copy, and scratch-buffer/reuse parameters have been removed (LocalCoordIJK.ToH3Index,CoordIJK.FromVec2d/CubeRound,ToFaceIJK,ToCoordinate,GetCellBoundaryVertices,Coordinate.ToH3Index,FaceIJK.FromLatLng,Vec3d.FromLonLat);H3Index.ToFaceWithInitializedFijktakes theFaceIJKbyref- the obsolete
GeoCoordtype is removed (structs cannot be subclassed) along with the obsolete members producing it (H3Index.ToGeoCoord/FromGeoCoord,VertexToGeoCoord,GetUnidirectionalEdgeBoundaryVertices— the latter two threwInvalidCastExceptionat runtime anyway);FaceIJK/Vec2d/Vec3dmembers named*GeoCoord*are renamed to*LatLng* - the
netstandard2.0/netstandard2.1targets no longer depend onSystem.Text.Json, making the assembly consumable from Unity without the STJ dependency chain (this is what existing Unity-focused forks patch out) #140.H3IndexJsonConverter(and the[JsonConverter]attribute onH3Index) are now only available onnet8.0+, where STJ is part of the framework. If you consume the netstandard targets from .NET Framework and rely on JSON serialization, copy the previous converter class into your project and register it viaJsonSerializerOptions.Converters. The (accidentallypublic)IsExternalInitshim is also no longer part of the netstandard assemblies (replaced by the internal, source-only Polyfill equivalent) - internal lookup tables are now compile-time generated (single source of truth in the
H3.Datasource generator) and flattened, substantially reducing static initialization cost and steady-state heap usage. This slims some (implementation-oriented) public surface:BaseCellsis now astaticclass; the per-instanceBaseCell.NeighbouringCells/NeighbourRotationsarrays andNeighbourDirectionsdictionary are replaced by shared flattened tables (see the newBaseCells.IsPentagonCellNumber(int)helper);LookupTables.NewDirectionClass2/NewDirectionClass3/NewAdjustmentClass2/NewAdjustmentClass3and theDirectionToUnitVector/UnitVectorToDirectiondictionaries are removed (the traversal data lives in generated packed tables; unit vectors remain available viaLookupTables.UnitVectors);LookupTables.PentagonIndexesPerResolutionis now a generatedH3Index[][]instead ofDictionary<int, H3Index[]>(indexing by resolution is source compatible); andLookupTables.FaceIjkBaseCellsis now a lazily-built property (the library itself uses the flat generated tables) CompactCellsnow produces deterministically ordered output (sorted by index value within each resolution, coarsest resolutions last) instead of hash-set ordering; as before, the only guarantee is that the result is a minimal covering set
Fixes 🔧
- fixes
IsValidCellrejecting valid cells at resolutions >= 11 (and accepting some invalid low-resolution indexes) due to 32-bit shift wraparound in its bit masks; validation is now a port of upstream's fasterisValidCell#111 - fixes
Vec2d.Intersectto usedoubleinstead offloat, improving the precision of geographic coordinate output (upstream #652) #86 - fixes possible overflows in
GetDirectNeighbourrotations,LocalIjToCell/LocalIjkToCellcoordinate conversions andIsNeighbourdigit lookups (upstream #706, #707, #677, #684) GridPathCellsis now bidirectional per upstream 4.5.0: paths that cross pentagon distortion relative to the origin's local coordinate chart are retried anchored at the destination (upstream #1111)- fixes a pattern-matching precedence bug in
GetDirectNeighbour's polar pentagon handling (is not 118 or 8dropped the base cell 8 check) - updates the average hexagon edge-length/area lookup tables from stale 3.x values to the upstream 4.x recomputed values
GetCellBoundariesnow passes the caller'sGeometryFactorythrough to the individual cell boundary polygons instead of only to the outer MultiPolygon- fixes an off-by-one in the base cell range used to build
LookupTables.PentagonIndexesPerResolution
Enhancements 🎉
- new hierarchy extensions
CellToChildPos,ChildPosToCellandCellToChildrenSize(upstream 4.1.0) #102 - new
H3Index.IsValidIndexproperty validating any index mode (upstream 4.4.0'sisValidIndex) - new directed edge extensions
ReverseDirectedEdge(upstream 4.4.0) andDestinationToDirectedEdges - new
CellsToMultiPolygonset extension producing the dissolved outline(s) of a cell set, with upstream 4.5.0 validation semantics (=cellsToMultiPolygon) - new grid statistics on
H3Index:GetNumberOfCells,GetRes0Cells,GetPentagons,GetHexagonAreaAverageInKmSquared/InMSquaredandGetHexagonEdgeLengthAverageInKm/InM - new
LatLng.GetLoopAreaInRadiansSquaredspherical loop area helper - polyfill improvements #53:
Fillnow supportsPoint,LineString,MultiPoint,MultiLineStringand (nested)GeometryCollectioninputs; polygonal fills seed from every component, fixing fills ofMultiPolygons with disjoint components; and a newFill(geometry, resolution, Func<H3Index, bool>)overload flood fills with a caller-provided containment predicate (e.g. for containment checks in a non-WGS84 CRS) - canonical cell set operations #61:
CanonicalizeCells,IsCanonicalCellsandCanonicalCellsContain(binary-search point-in-coverage against possibly-compacted sets, without uncompacting) - new component-based
H3Index.Create(resolution, baseCellNumber, digits)with full validation (upstream 4.5.0constructCell) - performance: allocation-free
CoordIJK->Directionconversion (~10x faster; benefitsFromLatLng/FromPoint), allocation-freeIsNeighbourshared-parent fast path (~2.7x faster), presized traversal materialization, fasterIsValidCell, and faster bitwise index rotations - performance: static initialization allocates ~6x less (130 KB -> 22 KB; ~half the steady-state heap) via compile-time generated, flattened lookup tables; pentagon checks use a bitmask instead of object lookups;
GridDistance/CellToLocalIjare ~2x faster andGridPathCells~1.6x faster (flat base cell neighbour tables replace per-cell dictionaries);CompactCellsis ~2x faster with ~2x fewer allocations (sorted sibling-run scan replaces nested dictionaries/sets);GridDiskDistancesSafeallocates ~2x less and is ~20% faster (array-based BFS with an open-addressed dedup table);CellToVertexesis ~1.4x faster (vertex rotations computed once per cell); polyfill flood fills allocate ~20% less andLineHexEstimatecaches the per-resolution pentagon radius (large constant win forTraceCoordinates/LineStringfills); plus smaller wins inGetParentForResolution,GetCellBoundaryand direction rotations NetTopologySuite2.6.0; source-only Polyfill for netstandard backfills. The netstandard targets now depend only onNetTopologySuite(plusMicrosoft.Bcl.HashCodeonnetstandard2.0)- XML documentation is now generated (and correctly named) for all targets
- pure struct members are annotated
readonly(C# 8; no runtime/netstandard impact) - new standalone
test/H3.CompareBenchmarkssuite comparing the published 4.0.0 package against the current tree (see its README; the two versions are not ABI compatible, so each is compiled separately)
Notes relative to upstream 4.5.0: getIndexDigit is covered by the existing GetDirectionForResolution; describeH3Error is not a...
Release 4.0.0
Breaking Changes 📣
- some terminology and naming changes to classes (e.g.
GeoCoord), methods, etc. to hopefully better align with upstream 4.0.0. Have tried to simplify upgrade w/[Obsolete]stubs, but, sorry in advance if anything breaks! #64 - removes support for .NET 5 #75
- fixes assembly naming -- the version is no longer included within the assembly name #68
Enhancements 🎉
- minor IJK math optimizations #78
3.7.2.1
Breaking Changes 📣
- Visibility on several classes + methods changed from
publictointernalin an effort to clean up the public-facing API. Let me know if I've changed anything you're using sealedadded to all classes
Enhancements 🎉
3.7.2.0
Breaking Changes 📣
- Changed
Mode.HexagontoMode.Cellto align with changes in upstream #37
Enhancements 🎉
- Faster
H3Index.IsValidbased on upstream PR 496
Fixes 🔧
- implements bugfix for
h3NeighborRotations(H3HierarchyExtensions.GetDirectNeighbour) from upstream release 3.7.2 - fixes JSON converter so that we don't swallow non-H3 hex strings when deserializing #36
3.7.1.3
3.7.1.2
3.7.1.1
Initial Release 3.7.1.0
Initial release of pocketken.H3 based on upstream 3.7.1.