# geohash-java geohash-java is a pure Java implementation of the Geohash spatial indexing algorithm. It encodes latitude/longitude coordinates into compact base32 strings or 64-bit long values, enabling efficient spatial queries and proximity searches. The library is fully compatible with the reference implementation at geohash.org when using character precision (multiples of 5 bits), but also supports encoding down to the full 64-bit precision for higher accuracy applications. The library provides core geohash encoding/decoding functionality along with spatial query capabilities including bounding box queries, circle queries (radius searches), and neighbor calculations. It uses the WGS84 ellipsoid and Vincenty's geodesy algorithm for accurate distance calculations. Key features include serializable classes for distributed systems, iteration over geohash grids, and support for bounding boxes that cross the 180-degree meridian. ## Core Classes and APIs ### GeoHash.withCharacterPrecision - Create GeoHash with Character Precision Creates a GeoHash using a specified number of base32 characters (1-12). Each character represents 5 bits of precision. This is the most common method for creating geohashes compatible with other implementations. ```java import ch.hsr.geohash.GeoHash; import ch.hsr.geohash.WGS84Point; import ch.hsr.geohash.BoundingBox; // Create a 12-character geohash (60 bits) for the Eiffel Tower double latitude = 48.8584; double longitude = 2.2945; GeoHash hash = GeoHash.withCharacterPrecision(latitude, longitude, 12); System.out.println(hash.toBase32()); // Output: "u09tunqu04ne" System.out.println(hash.significantBits()); // Output: 60 System.out.println(hash.longValue()); // Long representation // Get the center point and bounding box WGS84Point center = hash.getBoundingBoxCenter(); BoundingBox bbox = hash.getBoundingBox(); System.out.println("Center: " + center); // Output: (48.8584..., 2.2945...) System.out.println("BBox: " + bbox); // Output: NW corner -> SE corner // Static convenience method for quick string generation String geoHashString = GeoHash.geoHashStringWithCharacterPrecision(48.8584, 2.2945, 8); System.out.println(geoHashString); // Output: "u09tunqu" ``` ### GeoHash.withBitPrecision - Create GeoHash with Bit Precision Creates a GeoHash with exact bit precision (0-64 bits). Useful when you need fine-grained control over precision or non-standard precision values that aren't multiples of 5. ```java import ch.hsr.geohash.GeoHash; // Create hashes with different bit precisions GeoHash hash10 = GeoHash.withBitPrecision(45.0, 120.0, 10); // Very coarse GeoHash hash35 = GeoHash.withBitPrecision(45.0, 120.0, 35); // Medium precision GeoHash hash64 = GeoHash.withBitPrecision(45.0, 120.0, 64); // Maximum precision System.out.println("10 bits: " + hash10.significantBits()); // Output: 10 System.out.println("35 bits: " + hash35.significantBits()); // Output: 35 System.out.println("64 bits: " + hash64.significantBits()); // Output: 64 // Check containment - higher precision hash is within lower precision boolean isWithin = hash64.within(hash10); System.out.println("64-bit within 10-bit: " + isWithin); // Output: true // Note: toBase32() only works when bits are multiple of 5 GeoHash hash60 = GeoHash.withBitPrecision(45.0, 120.0, 60); String base32 = hash60.toBase32(); // Works - 60 is multiple of 5 // hash35.toBase32() would throw IllegalStateException - 35 is not multiple of 5 ``` ### GeoHash.fromGeohashString - Decode GeoHash from Base32 String Decodes a base32-encoded geohash string back into a GeoHash object with full bounding box information. ```java import ch.hsr.geohash.GeoHash; import ch.hsr.geohash.WGS84Point; // Decode a geohash string GeoHash hash = GeoHash.fromGeohashString("ezs42"); // Get the decoded center point WGS84Point center = hash.getBoundingBoxCenter(); System.out.println("Latitude: " + center.getLatitude()); // Output: ~42.6 System.out.println("Longitude: " + center.getLongitude()); // Output: ~-5.6 // Access bounding box corners System.out.println("North: " + hash.getBoundingBox().getNorthLatitude()); System.out.println("South: " + hash.getBoundingBox().getSouthLatitude()); System.out.println("East: " + hash.getBoundingBox().getEastLongitude()); System.out.println("West: " + hash.getBoundingBox().getWestLongitude()); // Round-trip encoding/decoding GeoHash original = GeoHash.withCharacterPrecision(40.390943, -75.9375, 12); GeoHash decoded = GeoHash.fromGeohashString(original.toBase32()); System.out.println(original.equals(decoded)); // Output: true ``` ### GeoHash Neighbor Operations - Get Adjacent Hashes Retrieves neighboring geohashes in all eight compass directions. Essential for proximity searches and spatial queries. ```java import ch.hsr.geohash.GeoHash; GeoHash hash = GeoHash.fromGeohashString("u1pb"); // Get individual neighbors GeoHash north = hash.getNorthernNeighbour(); GeoHash south = hash.getSouthernNeighbour(); GeoHash east = hash.getEasternNeighbour(); GeoHash west = hash.getWesternNeighbour(); System.out.println("North: " + north.toBase32()); // Output: "u1pc" System.out.println("South: " + south.toBase32()); // Output: "u0zz" System.out.println("East: " + east.toBase32()); // Output: "u300" System.out.println("West: " + west.toBase32()); // Output: "u1p8" // Get all 8 adjacent hashes at once (N, NE, E, SE, S, SW, W, NW) GeoHash[] adjacent = hash.getAdjacent(); System.out.println("Adjacent count: " + adjacent.length); // Output: 8 for (GeoHash neighbor : adjacent) { System.out.println(neighbor.toBase32()); } // Verify circular navigation returns to start GeoHash start = GeoHash.withCharacterPrecision(34.2, -45.123, 12); GeoHash end = start.getEasternNeighbour() .getSouthernNeighbour() .getWesternNeighbour() .getNorthernNeighbour(); System.out.println(start.equals(end)); // Output: true ``` ### GeoHash.within and contains - Spatial Containment Tests Tests whether a geohash or point is contained within another geohash's bounding box. ```java import ch.hsr.geohash.GeoHash; import ch.hsr.geohash.WGS84Point; // Create a coarse bounding hash and a precise location hash GeoHash boundingHash = GeoHash.withBitPrecision(45.0, 120.0, 12); GeoHash preciseHash = GeoHash.withBitPrecision(45.1, 120.1, 64); // Check if precise hash is within the bounding hash boolean isWithin = preciseHash.within(boundingHash); System.out.println("Is within: " + isWithin); // Output: true // Check if a point is contained in a geohash's bounding box GeoHash hash = GeoHash.fromGeohashString("ezs42"); WGS84Point insidePoint = new WGS84Point(42.6, -5.6); WGS84Point outsidePoint = new WGS84Point(50.0, 10.0); System.out.println("Inside: " + hash.contains(insidePoint)); // Output: true System.out.println("Outside: " + hash.contains(outsidePoint)); // Output: false // Use within() for prefix-based database queries GeoHash queryHash = GeoHash.fromGeohashString("dqcw4"); GeoHash candidateHash = GeoHash.fromGeohashString("dqcw4b"); System.out.println(candidateHash.within(queryHash)); // Output: true ``` ### GeoHash Iterator Operations - next, prev, stepsBetween Navigate through the geohash space sequentially and calculate distances between hashes. ```java import ch.hsr.geohash.GeoHash; GeoHash hash = GeoHash.withBitPrecision(37.7, -122.52, 35); // Move to next/previous geohash in sequence GeoHash next = hash.next(); GeoHash prev = hash.prev(); GeoHash skip = hash.next(5); // Jump 5 hashes forward System.out.println("Current ord: " + hash.ord()); System.out.println("Next ord: " + next.ord()); // hash.ord() + 1 System.out.println("Prev ord: " + prev.ord()); // hash.ord() - 1 System.out.println("Skip ord: " + skip.ord()); // hash.ord() + 5 // Calculate steps between two geohashes GeoHash start = GeoHash.withBitPrecision(37.7, -122.52, 35); GeoHash end = GeoHash.withBitPrecision(37.84, -122.35, 35); long steps = GeoHash.stepsBetween(start, end); System.out.println("Steps between: " + steps); // Output: 48472 // Compare geohashes (lexicographic ordering) System.out.println(hash.compareTo(next) < 0); // Output: true (hash < next) System.out.println(next.compareTo(prev) > 0); // Output: true (next > prev) ``` ### BoundingBox - Geographic Rectangle Operations Represents a geographic rectangle defined by latitude/longitude bounds. Supports operations including containment checks, intersection tests, and expansion. ```java import ch.hsr.geohash.BoundingBox; import ch.hsr.geohash.WGS84Point; // Create bounding box from south/north latitude and west/east longitude BoundingBox bbox = new BoundingBox(40.0, 42.0, -75.0, -73.0); // NYC area // Or create from corner points WGS84Point sw = new WGS84Point(40.0, -75.0); WGS84Point ne = new WGS84Point(42.0, -73.0); BoundingBox bbox2 = new BoundingBox(sw, ne); // Get dimensions and corners System.out.println("Lat size: " + bbox.getLatitudeSize()); // Output: 2.0 System.out.println("Lon size: " + bbox.getLongitudeSize()); // Output: 2.0 System.out.println("Center: " + bbox.getCenter()); // Output: (41.0, -74.0) // Access all four corners System.out.println("NW: " + bbox.getNorthWestCorner()); System.out.println("NE: " + bbox.getNorthEastCorner()); System.out.println("SW: " + bbox.getSouthWestCorner()); System.out.println("SE: " + bbox.getSouthEastCorner()); // Containment and intersection tests WGS84Point manhattan = new WGS84Point(40.7831, -73.9712); System.out.println("Contains Manhattan: " + bbox.contains(manhattan)); // Output: true BoundingBox overlapping = new BoundingBox(41.0, 43.0, -74.0, -72.0); System.out.println("Intersects: " + bbox.intersects(overlapping)); // Output: true // Expand bounding box to include a point bbox.expandToInclude(new WGS84Point(43.0, -76.0)); System.out.println("New north: " + bbox.getNorthLatitude()); // Output: 43.0 // Support for 180-meridian crossing BoundingBox pacificBox = new BoundingBox(-10.0, 10.0, 170.0, -170.0); System.out.println("Crosses 180: " + pacificBox.intersects180Meridian()); // Output: true ``` ### GeoHashBoundingBoxQuery - Spatial Query by Rectangle Finds all geohashes that cover a bounding box area. Returns 1-8 geohashes depending on how the bounding box aligns with the geohash grid. ```java import ch.hsr.geohash.BoundingBox; import ch.hsr.geohash.GeoHash; import ch.hsr.geohash.WGS84Point; import ch.hsr.geohash.queries.GeoHashBoundingBoxQuery; import java.util.List; // Define search area BoundingBox searchArea = new BoundingBox(40.7, 40.8, -74.1, -73.9); // Manhattan // Create query GeoHashBoundingBoxQuery query = new GeoHashBoundingBoxQuery(searchArea); // Get the covering geohashes List searchHashes = query.getSearchHashes(); System.out.println("Covering hashes: " + searchHashes.size()); for (GeoHash hash : searchHashes) { System.out.println("Hash: " + hash.toBase32() + " (" + hash.significantBits() + " bits)"); } // Check if a specific hash/point is within the query area GeoHash testHash = GeoHash.withCharacterPrecision(40.75, -74.0, 12); System.out.println("Contains hash: " + query.contains(testHash)); WGS84Point testPoint = new WGS84Point(40.75, -74.0); System.out.println("Contains point: " + query.contains(testPoint)); // Get WKT representation for database queries String wktBox = query.getWktBox(); System.out.println("WKT: " + wktBox); // Output: BOX(-74.1 40.7,-73.9 40.8) // Works with bounding boxes crossing the 180 meridian BoundingBox pacificSearch = new BoundingBox(-10.0, 10.0, 170.0, -170.0); GeoHashBoundingBoxQuery pacificQuery = new GeoHashBoundingBoxQuery(pacificSearch); System.out.println("Pacific hashes: " + pacificQuery.getSearchHashes().size()); ``` ### GeoHashCircleQuery - Radius Search Performs a radius search around a center point. Internally approximates the circle as a square bounding box. ```java import ch.hsr.geohash.GeoHash; import ch.hsr.geohash.WGS84Point; import ch.hsr.geohash.queries.GeoHashCircleQuery; import java.util.List; // Search within 5km of Times Square WGS84Point timesSquare = new WGS84Point(40.758, -73.9855); double radiusMeters = 5000; // 5 km GeoHashCircleQuery query = new GeoHashCircleQuery(timesSquare, radiusMeters); // Get covering geohashes for database prefix query List searchHashes = query.getSearchHashes(); System.out.println("Search hashes count: " + searchHashes.size()); for (GeoHash hash : searchHashes) { System.out.println("Use prefix: " + hash.toBase32()); } // Test if points/hashes fall within the search area WGS84Point nearbyPoint = new WGS84Point(40.76, -73.98); WGS84Point farPoint = new WGS84Point(41.0, -74.5); System.out.println("Nearby in query: " + query.contains(nearbyPoint)); // true System.out.println("Far in query: " + query.contains(farPoint)); // false // Check geohash containment GeoHash nearbyHash = GeoHash.withCharacterPrecision(40.76, -73.98, 8); System.out.println("Hash in query: " + query.contains(nearbyHash)); // Get WKT for visualization System.out.println("Query area: " + query.getWktBox()); System.out.println(query); // Output: Circle Query [center=(40.758,-73.9855), radius=5km] ``` ### VincentyGeodesy - Distance and Movement Calculations Provides accurate geodesic calculations using Vincenty's formula on the WGS84 ellipsoid. ```java import ch.hsr.geohash.WGS84Point; import ch.hsr.geohash.util.VincentyGeodesy; // Calculate distance between two points WGS84Point newYork = new WGS84Point(40.7128, -74.0060); WGS84Point london = new WGS84Point(51.5074, -0.1278); double distanceMeters = VincentyGeodesy.distanceInMeters(newYork, london); System.out.println("NY to London: " + (distanceMeters / 1000) + " km"); // Output: ~5570 km // Move from a point in a given direction WGS84Point start = new WGS84Point(40.7128, -74.0060); double bearingDegrees = 90; // East (0=N, 90=E, 180=S, 270=W) double distanceToMove = 10000; // 10 km WGS84Point destination = VincentyGeodesy.moveInDirection(start, bearingDegrees, distanceToMove); System.out.println("10km East of NYC: " + destination); // Create a bounding box by moving in cardinal directions WGS84Point center = new WGS84Point(48.8584, 2.2945); // Eiffel Tower double radius = 1000; // 1 km WGS84Point north = VincentyGeodesy.moveInDirection(center, 0, radius); WGS84Point east = VincentyGeodesy.moveInDirection(center, 90, radius); WGS84Point south = VincentyGeodesy.moveInDirection(center, 180, radius); WGS84Point west = VincentyGeodesy.moveInDirection(center, 270, radius); System.out.println("1km North: " + north.getLatitude()); System.out.println("1km East: " + east.getLongitude()); ``` ### TwoGeoHashBoundingBox and BoundingBoxGeoHashIterator - Grid Iteration Iterate over all geohashes within a bounding box at a specified precision level. ```java import ch.hsr.geohash.BoundingBox; import ch.hsr.geohash.GeoHash; import ch.hsr.geohash.util.BoundingBoxGeoHashIterator; import ch.hsr.geohash.util.TwoGeoHashBoundingBox; // Define the area and precision BoundingBox bbox = new BoundingBox(37.7, 37.84, -122.52, -122.35); // San Francisco int characterPrecision = 7; // 35 bits // Create the two-hash bounding box TwoGeoHashBoundingBox twoHashBox = TwoGeoHashBoundingBox.withCharacterPrecision( bbox, characterPrecision); System.out.println("SW corner hash: " + twoHashBox.getSouthWestCorner().toBase32()); System.out.println("NE corner hash: " + twoHashBox.getNorthEastCorner().toBase32()); // Iterate over all geohashes in the region BoundingBoxGeoHashIterator iterator = new BoundingBoxGeoHashIterator(twoHashBox); int count = 0; while (iterator.hasNext()) { GeoHash hash = iterator.next(); if (count < 5) { System.out.println("Hash " + count + ": " + hash.toBase32()); } count++; } System.out.println("Total hashes in region: " + count); // Alternative: use bit precision TwoGeoHashBoundingBox bitBox = TwoGeoHashBoundingBox.withBitPrecision(bbox, 35); System.out.println("Combined base32: " + bitBox.toBase32()); // Calculate approximate hash count using stepsBetween GeoHash sw = GeoHash.withBitPrecision(bbox.getSouthLatitude(), bbox.getWestLongitude(), 35); GeoHash ne = GeoHash.withBitPrecision(bbox.getNorthLatitude(), bbox.getEastLongitude(), 35); long steps = GeoHash.stepsBetween(sw, ne); System.out.println("Max possible steps: " + steps); ``` ### Binary String and Long Value Conversions Convert geohashes to/from binary strings and long values for storage and transmission. ```java import ch.hsr.geohash.GeoHash; // Create a geohash GeoHash hash = GeoHash.withCharacterPrecision(40.390943, -75.9375, 10); // Convert to various formats String base32 = hash.toBase32(); String binary = hash.toBinaryString(); long longValue = hash.longValue(); long ordinal = hash.ord(); System.out.println("Base32: " + base32); // Output: "dr4jb0bn21" System.out.println("Binary: " + binary); // 50 binary digits System.out.println("Long value: " + longValue); // Full 64-bit representation System.out.println("Ordinal: " + ordinal); // Right-aligned value System.out.println("Bits: " + hash.significantBits()); // Output: 50 // Reconstruct from long value GeoHash fromLong = GeoHash.fromLongValue(longValue, hash.significantBits()); System.out.println("Reconstructed: " + fromLong.toBase32()); // Output: "dr4jb0bn21" System.out.println("Equal: " + hash.equals(fromLong)); // Output: true // Reconstruct from binary string GeoHash fromBinary = GeoHash.fromBinaryString(binary); System.out.println("From binary: " + fromBinary.toBase32()); // Reconstruct from ordinal GeoHash fromOrd = GeoHash.fromOrd(ordinal, hash.significantBits()); System.out.println("From ord: " + fromOrd.toBase32()); // Store in database as long (more efficient than string) // Store both longValue and significantBits to reconstruct later int bits = hash.significantBits(); GeoHash restored = GeoHash.fromLongValue(longValue, bits); ``` ## Summary geohash-java provides a robust foundation for location-based applications requiring spatial indexing and proximity searches. The primary use cases include storing and querying geographic points in databases using geohash prefixes, implementing "nearby" searches with radius queries, clustering locations by geographic proximity, and creating spatial indexes for large datasets. The library's base32 encoding produces human-readable strings that maintain lexicographic ordering by proximity, making prefix queries highly efficient. Integration typically follows a pattern where locations are encoded to geohashes at storage time, then queries use the `GeoHashBoundingBoxQuery` or `GeoHashCircleQuery` classes to generate covering hashes for database lookups. For databases supporting prefix queries (Redis, Cassandra, DynamoDB), you can query using the geohash string prefixes directly. For SQL databases, store the long value and significant bits, then use range queries on the ordinal value. The serializable nature of all core classes makes the library well-suited for distributed systems and caching layers. When precision matters, use the bit-precision APIs; for compatibility with other geohash implementations, use character precision with values 1-12.