const Alphabet
Alphabet is the standard geohash base32 alphabet. It omits a, i, l and o so that a hash read aloud or copied by hand stays unambiguous.
Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.
Package geohash encodes a latitude/longitude pair into the short base32 string known as a geohash, decodes one back i...
gno.land/p/moul/x/daily/geohash/v0Geohash: a latitude/longitude pair as a short base32 string with Encode,
Decode, DecodeBox, Neighbor, Neighbors, Box and Direction.
1import "gno.land/p/moul/x/daily/geohash/v0"
2
3h, _ := geohash.Encode(42.6, -5.6, 5) // "ezs42"
4b, _ := geohash.DecodeBox(h) // the cell, not a point
5n, _ := geohash.Neighbor(h, geohash.North) // "ezs48"
A geohash is a prefix code, and that is the whole point of storing one. Every character narrows the box by five bits, so a prefix is a valid geohash of the same place, zoomed out, and two places that share a long prefix are close together. An avl tree keyed by geohash therefore answers "everything near here" with a range scan over a prefix, storing no geometry and computing no distance.
A hash names a box, never a point. Decode returns the centre because the
centre is the best single point a box can give; DecodeBox returns the box
itself with HeightDeg and WidthDeg, which is what you want before claiming
two coordinates are "the same place".
Longitude wraps and latitude does not. The cell east of z is b: the
antimeridian is a line on a map, not an edge of the world. The classic
border/neighbour lookup-table implementations report nothing there, which is a
property of the tables rather than of the globe, so this package differs from
them on purpose and the tests pin both behaviours. A step past a pole is
genuinely impossible and returns ErrNoNeighbor; Neighbors returns a short
list there rather than padding it with empty strings a caller has to filter.
Deterministic by construction. Both world ranges are powers of two over
powers of two, so every box bound is exactly representable in a float64, the
one-cell step Neighbor takes lands on another centre rather than near one,
and nothing is rounded or iterated out of a map. Two identical calls cannot
disagree, which is the bar a Render has to clear.
The alphabet is the standard one and drops a, i, l and o, so a hash
survives being read aloud. Anything outside it is refused rather than skipped,
and so is a hash longer than MaxPrecision (12 characters, 60 bits).
Not included: distances, Encode from a radius, or the 3x3 prefix search a
proximity query would build on top. The box and its neighbours are the
primitives those need.
Live demo: r/moul/x/daily/geohashdemo
· render it at /r/moul/x/daily/geohashdemo/v0.
Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.
🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.
Package geohash encodes a latitude/longitude pair into the short base32 string known as a geohash, decodes one back into the cell it names, and walks from a cell to the eight around it.
A geohash is a prefix code: every character narrows the box, so two places that share a long prefix are close together, and truncating a hash zooms out. That is the whole reason to use one on chain, where an avl tree keyed by geohash gives a cheap "everything near here" range scan without storing any geometry: Get the prefix, iterate, done.
1hash, _ := geohash.Encode(42.6, -5.6, 5) // "ezs42"
2box, _ := geohash.DecodeBox("ezs42") // the cell it names
3north, _ := geohash.Neighbor("ezs42", geohash.North)
The alphabet is the standard one and deliberately drops a, i, l and o, so a hash survives being read aloud or written down. Anything outside it is refused rather than quietly skipped.
Everything here is pure arithmetic on exact binary fractions: both world ranges are powers of two divided by powers of two, so every box bound is representable in a float64 and two identical calls can never disagree. No map is iterated and no value is rounded, which is what makes a Render built on this deterministic.
A live demo of this package is at r/moul/x/daily/geohashdemo(/r/moul/x/daily/geohashdemo/v0).
Alphabet is the standard geohash base32 alphabet. It omits a, i, l and o so that a hash read aloud or copied by hand stays unambiguous.
MaxPrecision is the longest hash this package will produce or accept: 12 characters, 60 bits, a cell well under a millimetre across. Past that the extra characters say nothing a float64 can still distinguish.
The eight directions, clockwise from north.
1var (
2 // ErrPrecision is returned by Encode for a length outside 1..MaxPrecision.
3 ErrPrecision = errors.New("geohash: precision out of range")
4 // ErrLatitude is returned by Encode for a latitude outside -90..90.
5 ErrLatitude = errors.New("geohash: latitude out of range")
6 // ErrLongitude is returned by Encode for a longitude outside -180..180.
7 ErrLongitude = errors.New("geohash: longitude out of range")
8 // ErrEmpty is returned when a hash is required and none was given.
9 ErrEmpty = errors.New("geohash: empty hash")
10 // ErrTooLong is returned for a hash longer than MaxPrecision.
11 ErrTooLong = errors.New("geohash: hash longer than the maximum precision")
12 // ErrInvalidChar is returned for a character outside Alphabet, which
13 // includes the four letters the alphabet deliberately leaves out.
14 ErrInvalidChar = errors.New("geohash: character is not in the geohash alphabet")
15 // ErrDirection is returned by Neighbor for a Direction it does not know.
16 ErrDirection = errors.New("geohash: unknown direction")
17 // ErrNoNeighbor is returned by Neighbor when the step leaves the world:
18 // north of the top row, or south of the bottom one. Longitude wraps and
19 // never produces this.
20 ErrNoNeighbor = errors.New("geohash: no neighbour in that direction")
21)Decode returns the centre of the cell a geohash names. The centre is the best single point a hash can give: the cell itself is what was encoded, and DecodeBox is how to ask how big it is.
Encode returns the geohash of a point, precision characters long.
Longitude takes the first bit and the two alternate, so an odd number of characters gives longitude one bit more than latitude and the cell comes out roughly square in degrees. A point sitting exactly on a dividing line goes to the upper, or eastern, half.
Neighbor returns the geohash of the cell one step away, at the same precision.
It works by walking one whole cell from the centre and re-encoding, which is exact: every cell bound is a binary fraction, so the step lands on another centre rather than near one.
Longitude wraps, so the cell east of the last column is the first column again: the antimeridian is a line on a map, not an edge of the world. The classic lookup-table implementations report nothing there, which is a property of the tables and not of the globe. Latitude genuinely stops, and a step past a pole returns ErrNoNeighbor.
Neighbors returns the cells around a geohash, clockwise from north.
The result is short rather than padded when a direction has no cell, which only happens at the poles: a caller iterating the result never has to filter an empty string out of it.
DecodeBox returns the cell a geohash names.
Box is the rectangle of latitudes and longitudes a geohash names. A geohash is a box, never a point, and the shorter the hash the larger the box.
Center returns the midpoint of the box, which is what Decode reports.
Contains reports whether a point falls inside the box. The lower and left edges belong to the box and the upper and right ones do not, matching how Encode assigns a point sitting exactly on a midpoint.
HeightDeg is the box's north-south extent in degrees.
WidthDeg is the box's east-west extent in degrees.
Direction names one of the eight cells around a geohash. The values are ordered clockwise from north, so adding four modulo eight is the reverse.