Geohash
What is Geohash?
The Geohash system uses the following character set for base32 encoding:
0123456789bcdefghjkmnpqrstuvwxyz
Each character in the set represents a smaller section in a grid. Adding one more character to a Geohash increases the precision by subdividing the region further.
Example of Increasing Precision (Python)
import geohash # Initial geohash initial_geohash = "wydm" # Increase precision by one level new_geohash = geohash.encode(geohash.decode(initial_geohash), precision=len(initial_geohash) + 1) print(new_geohash)
Example of Getting a Geohash for a 4x4 Grid (Python)
# Base32 character set used in geohashes, arranged for a 4x4 grid selection base32_grid = [ "0123", "4567", "89bc", "defg", "hjk", "mnpq", "rst", "vwxyz" ] # Initial geohash initial_geohash = "wydm" # Function to get a 4x4 grid subset def get_4x4_geohashes(initial_geohash): more_precise_geohashes = [] # Iterate over the rows and columns of the grid for row in range(4): # Adjust the range to get the row block you want (e.g., first 4 rows) for col in range(4): # Adjust the range to get the column block you want (e.g., first 4 columns) more_precise_geohashes.append(initial_geohash + base32_grid[row][col]) return more_precise_geohashes # Get the 4x4 geohashes subset subset_geohashes = get_4x4_geohashes(initial_geohash) # Print the narrowed-down geohashes for geohash in subset_geohashes: print(geohash)
TypeScript Geohash Example
Getting a 4x4 Subset
// Base32 character set used in geohashes, arranged for a 4x4 grid selection const base32Grid: string[] = [ "0123", "4567", "89bc", "defg", "hjk", "mnpq", "rst", "vwxyz" ]; // Initial geohash const initialGeohash: string = "wydm"; // Function to get a 4x4 grid subset function get4x4Geohashes(initialGeohash: string): string[] { const morePreciseGeohashes: string[] = []; // Iterate over the rows and columns of the grid for (let row = 0; row < 4; row++) { // Adjust the range to get the row block you want (e.g., first 4 rows) for (let col = 0; col < 4; col++) { // Adjust the range to get the column block you want (e.g., first 4 columns) morePreciseGeohashes.push(initialGeohash + base32Grid[row][col]); } } return morePreciseGeohashes; } // Get the 4x4 geohashes subset const subsetGeohashes: string[] = get4x4Geohashes(initialGeohash); // Print the narrowed-down geohashes subsetGeohashes.forEach(geohash => console.log(geohash));
Getting a 4x8 Subset
// Base32 character set used in geohashes, arranged for a grid selection const base32Grid: string[] = [ "0123", // Row 1 "4567", // Row 2 "89bc", // Row 3 "defg", // Row 4 "hjk", // Row 5 "mnpq", // Row 6 "rst", // Row 7 "vwxyz" // Row 8 ]; // Initial geohash const initialGeohash: string = "wydm"; // Function to get a 4x8 grid subset function get4x8Geohashes(initialGeohash: string): string[] { const morePreciseGeohashes: string[] = []; // Iterate over 4 rows and 8 columns of the grid for (let row = 0; row < 4; row++) { // First 4 rows for (let col = 0; col < 8; col++) { // All 8 columns morePreciseGeohashes.push(initialGeohash + base32Grid[row][col % 4]); // Use modulus for column wrap } } return morePreciseGeohashes; } // Get the 4x8 geohashes subset const subsetGeohashes: string[] = get4x8Geohashes(initialGeohash); // Print the narrowed-down geohashes subsetGeohashes.forEach(geohash => console.log(geohash));
Useful npm Libraries for Geohash
ngeohash
- GitHub: ngeohash
This is a pure JavaScript code written in CommonJS format, making it easy to use in Node.js environments.
If you want type support, you can install
@types/ngeohash
.latlon-geohash
- GitHub: latlon-geohash
This library is written in ES6 syntax, so it might not work directly in CommonJS-based projects unless special handling for ESM is applied in the
package.json
.To use type support, install
@types/latlon-geohash
.How Geohash Works
- The Earth is divided into grid cells.
- Latitude and longitude values are inserted into these cells.
- The values are then converted into binary and encoded into base32.
- The precision is adjusted ā the longer the hash string, the more precise the location.
TypeScript Geohash Algorithm
const BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz"; // Geohash base32 map const BITS = [16, 8, 4, 2, 1]; interface LatLngBounds { minLat: number; maxLat: number; minLng: number; maxLng: number; } /** * Encodes latitude and longitude into a Geohash string * @param lat Latitude value * @param lng Longitude value * @param precision The length of the resulting Geohash string (default: 12) * @returns Geohash string */ function encode(lat: number, lng: number, precision: number = 12): string { let idx = 0; // Index in BASE32 map let bit = 0; // Current bit in BITS array let even = true; // Alternating between latitude and longitude let geohash = ''; // Resulting geohash string let latRange = { min: -90.0, max: 90.0 }; let lngRange = { min: -180.0, max: 180.0 }; while (geohash.length < precision) { if (even) { const mid = (lngRange.min + lngRange.max) / 2; if (lng >= mid) { idx = idx | BITS[bit]; lngRange.min = mid; } else { lngRange.max = mid; } } else { const mid = (latRange.min + latRange.max) / 2; if (lat >= mid) { idx = idx | BITS[bit]; latRange.min = mid; } else { latRange.max = mid; } } even = !even; if (bit < 4) { bit++; } else { geohash += BASE32[idx]; bit = 0; idx = 0; } } return geohash; } /** * Decodes a Geohash string back into latitude and longitude * @param geohash Geohash string * @returns An object containing latitude, longitude, and the bounds */ function decode(geohash: string): { lat: number, lng: number, bounds: LatLngBounds } { let even = true; let latRange = { min: -90.0, max: 90.0 }; let lngRange = { min: -180.0, max: 180.0 }; for (let i = 0; i < geohash.length; i++) { const char = geohash[i]; const idx = BASE32.indexOf(char); for (let j = 0; j < 5; j++) { const bit = (idx >> (4 - j)) & 1; if (even) { const mid = (lngRange.min + lngRange.max) / 2; if (bit === 1) { lngRange.min = mid; } else { lngRange.max = mid; } } else {