Skip to content

feat: add pure-Go LatLngToCell drop-in in x/purego#122

Open
justinhwang wants to merge 1 commit into
masterfrom
justinhwang/latlngcell
Open

feat: add pure-Go LatLngToCell drop-in in x/purego#122
justinhwang wants to merge 1 commit into
masterfrom
justinhwang/latlngcell

Conversation

@justinhwang

@justinhwang justinhwang commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Add x/h3go, a zero-allocation pure-Go implementation of LatLngToCell. It
ports the entire latLngToCell pipeline (vec3d projection, face lookup,
IJK coordinate transforms, H3 index encoding) to Go, so the per-call
compute path runs entirely in Go: no cgo call and no allocations.

x/h3go defines its own Cell, LatLng and Err* values rather than aliasing
the cgo h3 package. Owning these types lets the package define its own
methods (Cell.String, Cell.Resolution, ...) and grow into a full pure-Go
reimplementation of H3 that can drop-in replace the h3 package. The
underlying int64 cell encoding is identical to h3.Cell, so values remain
interconvertible via a plain numeric conversion. The main h3 package
keeps its cgo implementation unchanged.

Because it no longer imports the cgo h3 package, x/h3go is itself
cgo-free: it depends only on the cgo-free internal/h3core package and
builds with CGO_ENABLED=0. Shared H3 index-format primitives (the
bit-layout offsets/masks and the pentagon base-cell table) live in
internal/h3core so the constants are declared once instead of duplicated
across packages.

The package is covered at 100% of statements. An external suite
(h3go_test.go) asserts bit-for-bit equality and error-domain parity
against the cgo reference across a structured grid (poles/antimeridian),
a 100k seeded-random sweep, and all 12 pentagon base cells, at every
resolution, and checks Cell.String/Cell.Resolution against the reference;
an internal white-box test (internal_test.go) exercises the defensive
overflow guards and the at-face-center projection branch that valid
geographic inputs cannot reach.

The pure-Go path trades ~6% higher per-call latency (Go's math trig vs
C's libm) for zero allocations, cutting GC pressure in workloads that
call LatLngToCell millions of times:

h3.LatLngToCell (cgo): ~270 ns/op 24 B/op 2 allocs/op
h3go.LatLngToCell (pure Go): ~289 ns/op 0 B/op 0 allocs/op

@justinhwang justinhwang requested review from jogly and zachcoleman June 5, 2026 18:09
@coveralls

coveralls commented Jun 5, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 27372399066

Coverage remained the same at 100.0%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: 391 of 391 lines across 2 files are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 1260
Covered Lines: 1260
Line Coverage: 100.0%
Coverage Strength: 640043.91 hits per line

💛 - Coveralls

@justinhwang justinhwang force-pushed the justinhwang/latlngcell branch from a97aecf to 5795a22 Compare June 5, 2026 18:25
Comment thread x/h3go/h3go.go Outdated
// LatLngToCell returns the Cell at res for a geographic coordinate, computed
// entirely in Go. It is a drop-in, bit-for-bit compatible replacement for the
// cgo-backed h3.LatLngToCell.
func LatLngToCell(latLng h3.LatLng, res int) (h3.Cell, error) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Hmm I was debating whether we should completely redefine things here.. By returning h3.Cell, I can't actually override any of the methods on cell (like .Resolution(), .Parent(), etc.)

@jogly

jogly commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

I like the approach, we can extend it to become fully isolated over time and eventually drop the CGO requirement entirely. My only hesitation on this PR is purego is not an idiomatic package name. Even though it stutters in the import, maybe h3go could work nicely?

@justinhwang

Copy link
Copy Markdown
Collaborator Author

I like the approach, we can extend it to become fully isolated over time and eventually drop the CGO requirement entirely. My only hesitation on this PR is purego is not an idiomatic package name. Even though it stutters in the import, maybe h3go could work nicely?

Yeah I flagged on Slack, but one draw back of returning a h3.Cell means that I can't override the methods that are defined against h3.Cell, like Parent(), Resolution(), etc. So maybe that means we need to define it separately (i.e. h3go.Cell) and then when x/h3go is at a point where we're fine with it, we just pull the whole thing over to main?

@justinhwang justinhwang force-pushed the justinhwang/latlngcell branch from 5795a22 to e7bde84 Compare June 9, 2026 23:20
@jogly

jogly commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Yeah I flagged on Slack, but one draw back of returning a h3.Cell means that I can't override the methods that are defined against h3.Cell, like Parent(), Resolution(), etc. So maybe that means we need to define it separately (i.e. h3go.Cell) and then when x/h3go is at a point where we're fine with it, we just pull the whole thing over to main?

Yea that sounds good! I'm all for redefining as much as you need to get a clean API surface

Add x/h3go, a zero-allocation pure-Go implementation of LatLngToCell. It
ports the entire latLngToCell pipeline (vec3d projection, face lookup,
IJK coordinate transforms, H3 index encoding) to Go, so the per-call
compute path runs entirely in Go: no cgo call and no allocations.

x/h3go defines its own Cell, LatLng and Err* values rather than aliasing
the cgo h3 package. Owning these types lets the package define its own
methods (Cell.String, Cell.Resolution, ...) and grow into a full pure-Go
reimplementation of H3 that can drop-in replace the h3 package. The
underlying int64 cell encoding is identical to h3.Cell, so values remain
interconvertible via a plain numeric conversion. The main h3 package
keeps its cgo implementation unchanged.

Because it no longer imports the cgo h3 package, x/h3go is itself
cgo-free: it depends only on the cgo-free internal/h3core package and
builds with CGO_ENABLED=0. Shared H3 index-format primitives (the
bit-layout offsets/masks and the pentagon base-cell table) live in
internal/h3core so the constants are declared once instead of duplicated
across packages.

The package is covered at 100% of statements. An external suite
(h3go_test.go) asserts bit-for-bit equality and error-domain parity
against the cgo reference across a structured grid (poles/antimeridian),
a 100k seeded-random sweep, and all 12 pentagon base cells, at every
resolution, and checks Cell.String/Cell.Resolution against the reference;
an internal white-box test (internal_test.go) exercises the defensive
overflow guards and the at-face-center projection branch that valid
geographic inputs cannot reach.

The pure-Go path trades ~6% higher per-call latency (Go's math trig vs
C's libm) for zero allocations, cutting GC pressure in workloads that
call LatLngToCell millions of times:

  h3.LatLngToCell   (cgo):     ~270 ns/op   24 B/op   2 allocs/op
  h3go.LatLngToCell (pure Go): ~289 ns/op    0 B/op   0 allocs/op

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@justinhwang justinhwang force-pushed the justinhwang/latlngcell branch from e7bde84 to 72e3434 Compare June 11, 2026 19:34
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.

3 participants