Skip to content

Release 4.5.0

Choose a tag to compare

@pocketken pocketken released this 10 Jul 18:32
· 15 commits to main since this release
1c60237

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.1 and netstandard2.0; EOL net6.0/net7.0 targets have been dropped (netstandard2.0 remains for Unity and .NET Framework consumers) #98, #140
  • EdgeLengthRadians/EdgeLengthKilometers/EdgeLengthMeters and GetDirectedEdgeBoundaryVertices now throw ArgumentException when the index is not a valid directed edge instead of silently returning 0 / an empty enumerable, matching upstream's E_DIR_EDGE_INVALID #109
  • GridRing is 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 as GridRingUnsafe (= upstream gridRingUnsafe), which the obsolete GetHexRing now aliases. GridRing also now validates its inputs
  • CellAreaInRadiansSquared (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, FaceIJK and LatLng are now structs (completing CoordIJ, Vec2d, Vec3d, FaceOrientIJK from earlier work), eliminating per-index/per-vertex heap allocations throughout: FromLatLng/FromPoint and ToLatLng/ToPoint are measured allocation-free (previously 131 and 96 B/op), grid disk allocations drop ~2.3x and children iteration ~17x #93. Value semantics apply: null is no longer a valid index (use H3Index.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.ToFaceWithInitializedFijk takes the FaceIJK by ref
  • the obsolete GeoCoord type is removed (structs cannot be subclassed) along with the obsolete members producing it (H3Index.ToGeoCoord/FromGeoCoord, VertexToGeoCoord, GetUnidirectionalEdgeBoundaryVertices — the latter two threw InvalidCastException at runtime anyway); FaceIJK/Vec2d/Vec3d members named *GeoCoord* are renamed to *LatLng*
  • the netstandard2.0/netstandard2.1 targets no longer depend on System.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 on H3Index) are now only available on net8.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 via JsonSerializerOptions.Converters. The (accidentally public) IsExternalInit shim 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.Data source generator) and flattened, substantially reducing static initialization cost and steady-state heap usage. This slims some (implementation-oriented) public surface: BaseCells is now a static class; the per-instance BaseCell.NeighbouringCells/NeighbourRotations arrays and NeighbourDirections dictionary are replaced by shared flattened tables (see the new BaseCells.IsPentagonCellNumber(int) helper); LookupTables.NewDirectionClass2/NewDirectionClass3/NewAdjustmentClass2/NewAdjustmentClass3 and the DirectionToUnitVector/UnitVectorToDirection dictionaries are removed (the traversal data lives in generated packed tables; unit vectors remain available via LookupTables.UnitVectors); LookupTables.PentagonIndexesPerResolution is now a generated H3Index[][] instead of Dictionary<int, H3Index[]> (indexing by resolution is source compatible); and LookupTables.FaceIjkBaseCells is now a lazily-built property (the library itself uses the flat generated tables)
  • CompactCells now 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 IsValidCell rejecting 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 faster isValidCell #111
  • fixes Vec2d.Intersect to use double instead of float, improving the precision of geographic coordinate output (upstream #652) #86
  • fixes possible overflows in GetDirectNeighbour rotations, LocalIjToCell/LocalIjkToCell coordinate conversions and IsNeighbour digit lookups (upstream #706, #707, #677, #684)
  • GridPathCells is 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 8 dropped 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
  • GetCellBoundaries now passes the caller's GeometryFactory through 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, ChildPosToCell and CellToChildrenSize (upstream 4.1.0) #102
  • new H3Index.IsValidIndex property validating any index mode (upstream 4.4.0's isValidIndex)
  • new directed edge extensions ReverseDirectedEdge (upstream 4.4.0) and DestinationToDirectedEdges
  • new CellsToMultiPolygon set 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/InMSquared and GetHexagonEdgeLengthAverageInKm/InM
  • new LatLng.GetLoopAreaInRadiansSquared spherical loop area helper
  • polyfill improvements #53: Fill now supports Point, LineString, MultiPoint, MultiLineString and (nested) GeometryCollection inputs; polygonal fills seed from every component, fixing fills of MultiPolygons with disjoint components; and a new Fill(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, IsCanonicalCells and CanonicalCellsContain (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.0 constructCell)
  • performance: allocation-free CoordIJK -> Direction conversion (~10x faster; benefits FromLatLng/FromPoint), allocation-free IsNeighbour shared-parent fast path (~2.7x faster), presized traversal materialization, faster IsValidCell, 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/CellToLocalIj are ~2x faster and GridPathCells ~1.6x faster (flat base cell neighbour tables replace per-cell dictionaries); CompactCells is ~2x faster with ~2x fewer allocations (sorted sibling-run scan replaces nested dictionaries/sets); GridDiskDistancesSafe allocates ~2x less and is ~20% faster (array-based BFS with an open-addressed dedup table); CellToVertexes is ~1.4x faster (vertex rotations computed once per cell); polyfill flood fills allocate ~20% less and LineHexEstimate caches the per-resolution pentagon radius (large constant win for TraceCoordinates/LineString fills); plus smaller wins in GetParentForResolution, GetCellBoundary and direction rotations
  • NetTopologySuite 2.6.0; source-only Polyfill for netstandard backfills. The netstandard targets now depend only on NetTopologySuite (plus Microsoft.Bcl.HashCode on netstandard2.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.CompareBenchmarks suite 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 applicable (exceptions carry failure detail); buffer-sizing functions (maxGridDiskSize et al.) are unnecessary with lazy enumerables.