Skip to content

feat(rust/sedona-functions): implement ST_GeoHash - #1163

Open
james-willis wants to merge 9 commits into
apache:mainfrom
james-willis:james/st-geohash
Open

feat(rust/sedona-functions): implement ST_GeoHash#1163
james-willis wants to merge 9 commits into
apache:mainfrom
james-willis:james/st-geohash

Conversation

@james-willis

Copy link
Copy Markdown
Contributor

What changes were proposed in this PR?

Implement ST_GeoHash(geometry, precision) returning the base-32 geohash string of the geometry, matching Apache Sedona Spark semantics:

  • Non-point geometries hash the center of the bounding box (GeometryGeoHashEncoder.java:40-41)
  • Returns null when the bbox falls outside [-180,180]×[-90,90] (GeometryGeoHashEncoder.java:33-38)
  • precision <= 0 returns an empty string; precision caps at 20 (PointGeoHashEncoder.java:29-32)
  • Standard lon-first bit interleaving with the 0123456789bcdefghjkmnpqrstuvwxyz alphabet

The encoder is implemented by hand (~40 lines) — no new crate dependency.

Follows crate conventions: SedonaScalarUDF::new + ItemCrsKernel::wrap_impl, WkbExecutor, ArgMatcher::is_geometry() + is_integer() with per-row Int64-cast precision (modeled on st_geometryn), Utf8 return via StringBuilder (modeled on st_geometrytype).

Two deliberate deviations from Sedona Spark, documented in code:

  1. Empty geometries return null — Sedona Java hits a JTS null-envelope artifact (minx=0, maxx=-1) and accidentally hashes empty geometries as (-0.5, -0.5); returning null is PostGIS-consistent.
  2. Geometry-only (no geography kernel), matching Sedona Spark's signature; precedent: st_xmin/st_ymin are geometry-only for the same planar-bbox reason.

Why are the changes needed?

ST_GeoHash is used in Sedona Spark pipelines (e.g. for spatial bucketing/partitioning keys); sedona-db currently has no implementation, so such SQL fails with an unknown-function error.

How was this patch tested?

8 new tests in st_geohash.rs with expected values taken from Sedona Spark's TestStGeoHash.scala (each vector cited in a comment), including WKB_VIEW_GEOMETRY and ITEM_CRS variants, precision capping, out-of-range nulls, and pole/antimeridian edge cases. cargo test -p sedona-functions passes (513 tests), clippy and fmt clean.

Did this PR include necessary documentation updates?

Function-level rustdoc included; happy to add a SQL reference doc page if maintainers point me at the right template.

Adds a native st_geohash(geometry, precision) scalar UDF that returns the
base32 geohash string of a geometry, matching Apache Sedona Spark's
ST_GeoHash semantics:

- Non-point geometries hash the center of their bounding box
  (GeometryGeoHashEncoder.calculate)
- Returns null when the bounding box is not fully contained in
  [-180, 180] x [-90, 90]
- Precision <= 0 returns an empty string; precision is capped at 20
  (PointGeoHashEncoder.calculateGeoHash)

Expected test values are taken from Sedona's TestStGeoHash.scala.
@github-actions
github-actions Bot requested a review from prantogg August 13, 2026 22:07
@james-willis
james-willis marked this pull request as draft August 13, 2026 22:44
The R package generates roxygen docs from the qmd description; bare
square brackets become Rd \link{} targets and fail R CMD check.
@james-willis
james-willis marked this pull request as ready for review August 13, 2026 23:56

@paleolimbot paleolimbot left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you!

Can you add integration tests to Python that verify this returns the same values as PostGIS?

Comment thread rust/sedona-functions/src/st_geohash.rs Outdated
Comment on lines +115 to +118
// Longitude can take values in [-180, 180]; latitude can take values in [-90, 90]
if x.lo() < -180.0 || y.lo() < -90.0 || x.hi() > 180.0 || y.hi() > 90.0 {
return Ok(None);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This would be better normalizing to the -180, 180 range and validating that we have lon/lat CRS (you could check what PostGIS does for this case...we have a "get geographic params" to check if this is a CRS we know is in longitude/latitude units, or maybe we only want this if it's bona-fide WGS84). This is easy for type level CRS but sort of a pain for item level CRS.

@james-willis james-willis Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

postgis will error here, sedona spark wil return null. I kept the spark behavior here

I believe we already check for lon/lat CRS before this point

Comment thread rust/sedona-functions/src/st_geohash.rs Outdated
Comment thread rust/sedona-functions/src/st_geohash.rs Outdated
Comment thread python/sedonadb/tests/functions/test_functions.py
james-willis added a commit to james-willis/sail that referenced this pull request Aug 20, 2026
Repoint the sedona-db git dependencies from apache main @ 1245541b to
james-willis/sedona-db @ d18b1dee (same base commit plus the ST_GeoHash
implementation; still DataFusion 52.5.0). The fork URL keeps the pin
reachable if upstream squash-merges. Verified end to end: ST_GeoHash
output matches pygeohash at precisions 5 and 12.
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.

2 participants