Geohash
Geohash란?
Geohash 시스템은 다음과 같은 캐릭터셋으로 base32 인코딩을 사용한다
0123456789bcdefghjkmnpqrstuvwxyz
각각의 문자들은 그리드 격자에서 더 작은 섹션을 나타낸다. 문자를 하나 더 추가하는 것은 해당 영역에 precision을 증가시켜서 영역을 나누는 것을 의미한다.
preicision을 증가시키는 예제 (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)
4x4 그리드 격자를 나타내는 geohash를 구하는 예제(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 예제
4x4 서브셋 구하기
// 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));
4x8 서브셋 구하기
// 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));
geohash 관련 유용한 npm library
ngeohash
https://github.com/sunng87/node-geohash
commonjs 로 작성된 pure js 코드라서, 노드에서 사용하기 편리하다
만약 타입을 지원하고 싶다면, @types/ngeohash 를 인스톨 하면 될 것이다
latlon-geohash
https://github.com/chrisveness/latlon-geohash
ES6 문법으로 작성되어 있어서, commonjs 로 이루어진 프로젝트라면 바로 사용이 불가능 하다
package.json에서 따로 ESM 처리가 되어있지 않다
마찬가지로 타입 지원을 원한다면 @types/latlon-geohash 를 인스톨 하면 될 것이다
geohash 가 동작하는 방식
지구를 grid cell로 나눈다
위도와 경도 (latitude, longitude) 값을 끼워넣는다
binary를 base32 로 컨버팅한다
preicision을 조정한다. 더 긴 hash의 문자열 길이 값일 수록 더 정확함을 의미한다
typescript geohash 알고리즘
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 {
const mid = (latRange.min + latRange.max) / 2;
if (bit === 1) {
latRange.min = mid;
} else {
latRange.max = mid;
}
}
even = !even;
}
}
const lat = (latRange.min + latRange.max) / 2;
const lng = (lngRange.min + lngRange.max) / 2;
return {
lat: lat,
lng: lng,
bounds: {
minLat: latRange.min,
maxLat: latRange.max,
minLng: lngRange.min,
maxLng: lngRange.max
}
};
}
// Example usage
const geohash = encode(37.7749, -122.4194); // Encode San Francisco coordinates
console.log(`Geohash: ${geohash}`);
const location = decode(geohash); // Decode the geohash back into coordinates
console.log(`Decoded Location: Lat ${location.lat}, Lng ${location.lng}`);
참고) BITS 변수는 base32 와 연관 되어있다
유용한 웹사이트
- geohash explorer: https://geohash.softeng.co/wydm6
- geohash visualizer
- geohash converter: http://geohash.co/
- convert geohash by lat, lng, geohash and precision
출처
- ChatGPT