본문 바로가기
IT & 코딩

공간 데이터 이해하기 : GeoHash란?

by 에일라거 2020. 11. 8.

해쉬의 정의를 찾아보면, 아래와 같다. 

 

해시 함수는 임의의 길이를 갖는 임의의 데이터에 대해 고정된 길이의 데이터로 매핑하는 함수를 말한다.

 

이 뜻에 따라 geohash란, 공간 상의 좌표값을 (GPS 위경도 등) 일정한 값으로 치환하는 작업 (또는 함수) 를 뜻한다. 하나씩 따라가보자.

 

 

GeoHash란?

GeoHash는 GPS를 해시함수를 통해 일정한 영문자 및 숫자 값으로 바꿔주는 일이다. 

 

예를 들어 서울시청의 위경도가 이렇게 된다고 하면, 일정한 함수를 지나면 wydm9qyc 라는 영문자로 바뀌게 된다. 큰 내용 자체는 이게 전부다.

 

그래서... 뭐 어쩌라고?

 

GeoHash로 변환

한번 생각해보자. 소수점 여섯째자리, 혹은 그 이상되는 숫자들의 조합을 GeoHash로 바꾸면, 최대 영문자 8개로 바뀌게 된다. 아니 이게 숫자가 몇자린데 겨우 8자리로 바꾼다고? 결국 이렇게 하면, 어떤 범위 내에 있는 지점은 몽땅 같은 값으로 바뀌게 된다.

 

오른쪽 아래 : 도대체 누구야?

즉, GeoHash라는 건 결국 간단하게 말해서 모자이크 처리다. 위의 사람 얼굴로 비유하자면 선명한 사진은 GPS고, 여기서 GeoHash 레벨이 낮아질수록 뭔지 알아보기가 힘들어진다. (위의 서울시청은 8자리였지만, 실제로는 1~8자리로 변환하게 된다.) 맨 아래 오른쪽 사진을 보고 원형을 유추할 수 있겠는가?

 

사람 얼굴이라면 누군지 알아보기가 힘들어지는 거고, 이를 공간 좌표에 대입한다면 어디인지 알아보기가 힘들어 진다는 거겠지? 이를 통해 얻는 장점은 결국 연산량이 줄어든다는 걸꺼다. 어떤 위치에 대해 m 단위로 하려면 GPS 좌표의 경우 소수점 6째자리까지 내려가야 하는데 이걸 우리나라 전 단위로 다 긁는다고 생각해보면... 어우야

 

어떻게 바꾸는 걸까?

GPS 좌표를 GeoHash로 바꾸는 방법은 아래와 같다.

 

1. GPS 좌표를 특정 규칙으로 이진수로 변경한다.

2. 이진수를 특정 규칙의 32진수로 변경한다. 이 32진수가 GeoHash다.

 

이렇게 큰 스텝으로 보면 엄청 간단한 작업이다. 각각의 스텝에 대해 알아보자

 

GPS 좌표를 이진수로 변경하기

1. 먼저 전 지구를 크게 한판으로 놓고, 거기에 원하는 좌표를 위치시킨다.

 

2. 경도/위도 범위를 각각 반으로 뚝 잘랐을 때, 변환하고자 하는 좌표가 어디 있는지를 보자. 작은 쪽에 있으면 이진수에 0을, 큰 쪽에 있으면 1을 할당한다. 경도 → 위도 → 경도 → 위도 ... 순으로 진행한다. 아래 그림을 보자.

 

3. 이렇게 지역을 쪼개는 작업을 어디까지 진행할 것인가가 GeoHash 레벨의 포인트다. 완전 순차적으로 (경도→위도→경도→위도...) 진행하는 게 아니라, 레벨에 따라서 진행하는 (쪼개는) 횟수가 차이가 있다.

 

즉, 1레벨이라면 경도→위도→경도→위도→경도 순으로 지역을 5번 쪼개고 끝난다. 그림으로 한 번 살펴보자.

 

Lv.1 Geohash 서울시청

위 그림에서 보듯이 GeoHash 레벨 1의 서울시청의 범위는 한국을 넘어서 중국과 러시아와 일본, 동남아시아까지 포함하는 거대한 건물(?)이 된다. 이런 식으로 해상도가 떨어져서는 쓸 수가 없으니, 같은 방식으로 레벨을 2~8까지 쭉 높이게 되고 레벨 8까지 이 작업을 진행하면 다음의 40자리 숫자가 얻어진다.

 

서울시청 예이~

 

이진수를 해시값으로 변경하기

위에서 얻어진 숫자 나부랭이들을 이제 어쩌란 말인가?

 

이건 너무너무 쉽다. 위에서 한 작업을 자세히 보면 레벨이 올라갈 때마다 자리가 5자리씩 늘어나고, 숫자도 딱 5개씩 짝지어뒀다. 그러니까...

 

1. 5개의 이진수를 10진수로 변경 (0~31)

2. 이에 맞게 매핑된 문자로 치환 (base32 인코딩)

 

1번 과정을 거치면 숫자가 아래와 같이 변한다.

 

2번을 하기 위해 다음의 표를 이용한다. 이를 base32 인코딩이라 하더라고.

 

이진수 변환 코드

 

그냥 각 숫자에 해당하는 문자를 기입하면 된다. 다음과 같다.

 

서울시청 8레벨 GeoHash : wydm9qyc

GeoHash를 만드는 특성상, 1레벨에서 8레벨까지 단순히 문자만 하나씩 더해서 기입하면 된다.

 

1레벨 : w

2레벨 : wy

3레벨 : wyd

...

 

이런 식으로 진행되게 된다.

 

코드는 없나요?

있습니다. 네. 아래의 코드는, 특정 좌표를 집어넣을 경우 이를 GeoHash로 바꿔주는 코드입니다.

 

#-*- coding:utf-8 -*-

# 2진수(문자열)을 10진수(숫자)로 변경
def bin2dec(b):
    ans = 0
    for i, bb in enumerate(b[::-1]):
        ans += int(bb)*(2**i)
    return ans

# 변환하려는 위치 
pos = [126.978419, 37.566671] # 서울시청
print(f'변환하려는 GPS 좌표 : {pos} (경/위도)\n')

base32 = '0123456789bcdefghjkmnpqrstuvwxyz' # Base32 인코딩

# 경/위도 (lon/lat) 순서
pair = [[3,2],[5,5],[8,7],[10,10],[13,12],[15,15],[18,17],[20,20]] 

for hash_len in range(1,9):
    lon_range=[-180,180] # 경도 범위
    lat_range=[-90,90] # 위도 범위
    bits = ''

    cnt = [0,0]
    for i in range(20):
        lat_avg = (lat_range[0]+lat_range[1])/2
        lon_avg = (lon_range[0]+lon_range[1])/2

        # 경도
        if cnt[0] < pair[hash_len-1][0]:
            if pos[0] >= lon_avg:
                bits = bits + '1'
                lon_range[0] = lon_avg
            else:
                bits = bits + '0'
                lon_range[1] = lon_avg
            cnt[0] += 1

        # 위도
        if cnt[1] < pair[hash_len-1][1]:
            if pos[1] >= lat_avg:
                bits = bits + '1'
                lat_range[0] = lat_avg
            else:
                bits = bits + '0'
                lat_range[1] = lat_avg
            cnt[1] += 1

    geohash = ''
    for i in range(len(bits)//5):
        geohash += base32[bin2dec(bits[5*i:5*i+5])]

    print(f'Hash length: {hash_len} | 비트 정보: {bits} | Geohash : {geohash}\n좌표범위: {lon_range}(경도), {lat_range}(위도)\n')
    

실제 코드는 아래 쪽에서 받아가세요

 

gps_to_geohash.py
0.00MB

 

다음 시간에는 아마도(?) GeoHash를 QGIS에 표현하는 방법에 대해 글을 쓸 거 같습니다. 일단 오늘은 여기까지~

댓글