Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

v0 source pure

Package geohash encodes a latitude/longitude pair into the short base32 string known as a geohash, decodes one back i...

Readme View source

gno.land/p/moul/x/daily/geohash/v0

Geohash: 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.

Overview

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.

Example
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).

Constants 3

const Alphabet

1const Alphabet = "0123456789bcdefghjkmnpqrstuvwxyz"
source

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.

const MaxPrecision

1const MaxPrecision = 12
source

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.

Variables 1

var ErrPrecision, ErrLatitude, ErrLongitude, ErrEmpty, ErrTooLong, ErrInvalidChar, ErrDirection, ErrNoNeighbor

 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)
source

Functions 5

func Decode

1func Decode(hash string) (lat, lon float64, err error)
source

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.

func Encode

1func Encode(lat, lon float64, precision int) (string, error)
source

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.

func Neighbor

1func Neighbor(hash string, d Direction) (string, error)
source

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.

func Neighbors

1func Neighbors(hash string) ([]string, error)
source

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.

func DecodeBox

1func DecodeBox(hash string) (Box, error)
source

DecodeBox returns the cell a geohash names.

Types 2

type Box

struct
1type Box struct {
2	MinLat, MaxLat float64
3	MinLon, MaxLon float64
4}
source

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.

Methods on Box

func Center

method on Box
1func (b Box) Center() (lat, lon float64)
source

Center returns the midpoint of the box, which is what Decode reports.

func Contains

method on Box
1func (b Box) Contains(lat, lon float64) bool
source

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.

func HeightDeg

method on Box
1func (b Box) HeightDeg() float64
source

HeightDeg is the box's north-south extent in degrees.

func WidthDeg

method on Box
1func (b Box) WidthDeg() float64
source

WidthDeg is the box's east-west extent in degrees.

type Direction

ident
1type Direction int
source

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.

Methods on Direction

func String

method on Direction
1func (d Direction) String() string
source

String returns the lowercase hyphenated name, or "unknown" for a value that is not one of the eight.

Dependencies

Imports 1

/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/x/daily/geohash/v0

Imported by indexer

Source Files 4