좌표계 변환과 pyproj¶
한 줄 요약: 같은 위치라도 좌표계마다 숫자가 다르다. H3는 위경도(EPSG:4326)만 받으므로, 한국 공공데이터의 평면 좌표는 먼저 바꿔야 한다. 그 변환을 정확히 해 주는 표준 도구가 pyproj다.
📍 만난 상황¶
- 2026-11-09 — 1번 물음 → 그날 일지
- 10:42 위치 "입구"(좌표 열, WKT, GeoJSON 등 위치가 들어오는 모양별 변환 함수)를 정리하는 중이었다. 새 형식이 오면 어떻게 할지도 막 들었다. 그 목록에 계속 나오는 pyproj를 왜 쓰는지 물었다.
- 그 뒤 흐름을 확정했다. 좌표가 있으면 pyproj로 4326 → H3. 좌표가 없으면 기준 자료와 매칭 → 4326 → H3.
🧩 쉽게 말하면¶
귤껍질을 떠올리면 된다. 지구(귤)는 둥글다. 지도(종이)는 평평하다. 껍질을 펴서 종이에 붙이는 방법은 여러 가지이고, 방법마다 눈금이 다르다.
서울의 어느 교통량 지점이 A 지도에서는 "가로 195,489m, 세로 452,136m", B 지도에서는 "북위 37.56도, 동경 126.96도"다. H3는 B 방식만 알아듣는다. 그래서 A 숫자를 B 숫자로 통역해야 한다. pyproj가 그 통역사다.
📖 제대로 알기¶
좌표계의 두 종류¶
- 지리 좌표계: 지구 위 위치를 각도(위도·경도, 단위 도)로 나타낸다. 예: EPSG:4326(WGS84 위경도). GPS가 쓰는 방식이다.
- 투영 좌표계: 둥근 지구를 평면에 펼친 뒤 미터 단위 x·y로 나타낸다. 거리·면적 계산이 쉬워 한국 공공데이터가 많이 쓴다.
EPSG 코드는 좌표계마다 붙은 국제 번호다. 숫자 하나로 "어떤 방식으로 펼쳤고, 지구를 어떤 모양으로 봤는지"가 정해진다.
측지계와 타원체¶
지구는 완전한 공이 아니라 살짝 눌린 귤 모양이다. 이 모양을 수식으로 잡은 것을 타원체라고 한다. 타원체와 기준점을 합친 것을 측지계(datum)라고 한다.
- 베셀(Bessel) 타원체: 옛 한국 측지계. 옛 좌표계(5174, 2097 등)가 쓴다.
- GRS80 / WGS84: 오늘날 기준. 둘은 사실상 같은 위치를 준다(차이 수 mm 수준).
같은 지점이라도 베셀 기준 위경도와 WGS84 기준 위경도는 수백 m 차이가 난다.
한국에서 자주 보는 좌표계¶
| EPSG | 이름(요약) |
|---|---|
| 4326 | WGS84 위경도. H3 입력 |
| 5179 | UTM-K(GRS80). 전국 한 판 |
| 5181 | 중부원점(GRS80), 북쪽 가산값 500,000 |
| 5186 | 중부원점(GRS80), 북쪽 가산값 600,000 |
| 5174 · 2097 | 중부원점(베셀). 옛 좌표계 |
가산값(false easting/northing)은 좌표가 음수가 되지 않게 원점에 더해 둔 숫자다. 5181과 5186은 이 값만 10만 m 다르다.
pyproj¶
pyproj는 PROJ(좌표 변환 엔진)를 파이썬에서 쓰게 해 주는 라이브러리다. PROJ는 PostGIS, QGIS, GDAL이 모두 쓰는 엔진이라 어느 도구로 검증해도 결과가 같다.
⚙️ 어떻게 동작하나¶
옛 좌표계(5174) 점 하나를 H3 칸으로 바꾸는 과정이다.
flowchart TD
A["원본 x·y<br/>5174 미터 좌표"] -->|"① 투영 해제"| B["베셀 기준 위경도"]
B -->|"② 측지계 변환"| C["WGS84 위경도<br/>4326"]
C -->|"③ H3 칸 계산"| D["H3 칸 번호"]
①②가 pyproj가 하는 일이다. GRS80 좌표계(5179·5181·5186)는 ②가 거의 없고 ①만 하면 된다.
- 투영 해제: 평면에 펼친 x·y를 다시 둥근 지구 위 각도로 되돌린다. 단순한 곱셈이 아니라 타원체 위의 복잡한 공식이다.
- 측지계 변환: 지구 모양을 베셀로 보던 위경도를 GRS80·WGS84 기준으로 옮긴다. 이걸 빼먹으면 수백 m가 어긋난다.
- H3 칸 계산: 지구를 육각형 칸으로 나눈 격자에서 그 위경도가 속한 칸을 찾는다.
왜 손으로 짜지 않나: 두 계산 모두 공식이 길고 매개변수가 많다. 실제로 다른 도구(polars-st)는 측지계 변환을 제대로 하지 않아 5174·2097에서 약 362m 어긋났다. 그래서 변환에는 pyproj만 쓰기로 했다.
🧪 예시¶
-
예시 1 · 일상에 빗대어: 같은 건물 주소를 옛 지번 주소와 도로명 주소로 쓸 수 있다. 둘 다 맞지만, 내비게이션이 도로명만 알아듣는다면 옛 주소를 바꿔 넣어야 한다. 번지수를 대충 짐작해서 바꾸면 옆 블록(수백 m)에 도착한다.
-
예시 2 · 실제로는: 교통량 지점 원본은
grs80tm_x = 195489,grs80tm_y = 452136(EPSG:5181)이었다.
from pyproj import Transformer
import h3
t = Transformer.from_crs("EPSG:5181", "EPSG:4326", always_xy=True) # ①
lon, lat = t.transform(195489, 452136) # ②
print(lat, lon) # → 37.56…, 126.96…
assert 37.4 < lat < 37.8 and 126.7 < lon < 127.3 # ③
cell = h3.latlng_to_cell(lat, lon, 9) # ④
always_xy=True로 항상 "경도, 위도" 순서를 쓴다.
② 미터 좌표를 넣으면 위경도가 나온다. x가 원점(가산값 200,000)보다 약 4.5km 작으니 기준 경도 127도보다 조금 서쪽이다.
③ 결과가 서울 범위 안인지 검사한다. 좌표계를 잘못 골랐을 때 잡아낸다.
④ H3에는 위도, 경도 순서로 넣는다. 해상도 9 칸은 한 변이 약 170m인 육각형이다.
⚖️ 비교 · 흔한 오해¶
| 잘못 읽은 경우 | 실측 어긋남 |
|---|---|
| 5186을 5181로 | 약 100km (가산값 차이) |
| 5174를 5181로 | 약 313m (측지계 차이) |
| 측지계 변환 생략 | 약 362m |
- 헷갈렸던 점: "좌표는 좌표 아닌가, 왜 바꾸나" — 숫자 두 개로 보이니 같은 종류처럼 느껴진다. 하지만 미터 단위 평면 좌표와 각도 단위 위경도는 단위부터 다르다. H3는 각도만 받는다.
- 헷갈렸던 점: "변환은 공식 하나면 된다" — 투영 해제만 생각하면 그렇게 보인다. 옛 좌표계는 지구 모양(타원체)까지 달라서 측지계 변환이 한 번 더 필요하다.
- 오해: "pyproj가 좌표계도 알아서 맞춰 준다" — 아니다. pyproj는 주어진 좌표계대로 정확히 바꿀 뿐이다. 원본이 5174인데 5181이라고 알려 주면, 틀린 위치를 정확하게 계산한다. 서울 범위에서는 숫자만 보고 구분이 안 되는 경우도 있다.
- 오해: "4326은 경도, 위도 순서다" — 공식 정의는 위도, 경도 순서다. 많은 프로그램은 경도, 위도(x, y)로 쓴다. 그래서
always_xy=True로 고정한다.
🛠️ 실무에서는¶
- 체크리스트
- 원본 좌표계를 문서로 확인하고 등록 카드에 EPSG 번호로 적었나.
- 변환 결과가 대상 지역(서울 경계) 안에 들어오는지 자동 검사하나.
always_xy=True를 켰나. H3에 넣을 때 위도·경도 순서를 지켰나.- 변환기(
Transformer)를 줄마다 새로 만들지 않고 한 번 만들어 재사용하나. 수만 줄이면 속도 차이가 크다. - EPSG 번호가 없는 좌표계: KATEC처럼 번호가 없으면 투영식 문자열(PROJ 문자열)로 정의해 넘긴다. 코드는 그대로다.
- 문제가 생기면 먼저 볼 것
- 점이 바다나 다른 도시에 찍힌다 → x·y 순서가 뒤바뀌었거나 가산값이 다른 좌표계(5181↔5186)다.
- 점이 수백 m씩 한쪽으로 밀린다 → 베셀 좌표계를 GRS80으로 잘못 읽었다.
❓ 자주 묻는 질문¶
- Q. 원래 4326으로 온 데이터도 pyproj를 거쳐야 하나? A. 거쳐도 그대로 통과한다. 코드를 하나로 유지하려고 같은 길로 보내는 편이 단순하다.
- Q. 좌표 없이 "강남구"만 있는 데이터는? A. pyproj로는 못 한다. "이 이름은 여기다"라고 적힌 기준 자료(경계·위치 표)와 먼저 이어 붙여 좌표를 얻어야 한다.
- Q. 면(폴리곤) 데이터도 같은가? A. 꼭짓점마다 같은 변환을 한다. 다만 면을 H3 칸 하나로 정할지, 덮는 칸 전체로 할지는 따로 정해야 한다.
🔭 더 깊이 보기¶
- H3 해상도는 어떻게 고르나? 칸 크기와 데이터 정밀도의 균형을 정하는 문제다.
- 좌표가 없는 데이터에 위치를 붙이는 법(지오코딩)은? 좌표계 변환 바로 앞 단계다.
- PostGIS의
ST_Transform은 무엇이 같고 다른가? 같은 PROJ 엔진을 DB 안에서 쓰는 방법이다. - 관련 주제: 데이터 파이프라인 실행과 갱신 — 이 변환이 정규화 파이프라인 안에서 돈다
- 관련 주제: DB 정규화와 연결 표 — 이름이 같은 "정규화"의 다른 뜻
📚 참고 자료¶
- 좌표 참조 시스템 (QGIS 문서 한국어, "GIS에 대한 친절한 소개") — 지리·투영 좌표계와 측지계의 기초 · 링크
- pyproj Documentation (영문) —
Transformer,always_xy사용법 · 링크 - EPSG.io (영문) — EPSG 번호로 좌표계 정의·범위 찾기(예: 5181, 5186) · 링크
🗓️ 이력¶
- 2026-11-09: H3에 넣기 전에 왜 pyproj로 좌표를 바꾸는지 물음 → 일지