Blog, COLDSURF

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

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

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

  1. The Earth is divided into grid cells.
  1. Latitude and longitude values are inserted into these cells.
  1. The values are then converted into binary and encoded into base32.
  1. 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 {
ā† Go home