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 {
ā Go home