KunYu
epsgcoordinate-conversionproj4crs

EPSG 좌표계 간 좌표 변환 방법

proj4js, pyproj, ogr2ogr를 사용하여 EPSG 좌표계 간 좌표를 변환하는 방법을 실전 예제와 함께 설명합니다. WGS 84, Web Mercator, UTM, 중국 좌표계 변환까지 다룹니다.

KunYu TeamMarch 25, 202622분 읽기

WGS 84로 수집한 GPS 측량 포인트를 UTM Zone 50N으로 투영된 현장 도면에 올려야 하는 상황을 생각해 보자. 좌표를 붙여넣으면 포인트가 대양 한가운데에 찍힌다 — 수천 킬로미터나 벗어나 있다. 숫자만 보면 비슷해서 맞는 것 같지만, 좌표계가 근본적으로 호환되지 않기 때문에 변환 없이는 사용할 수 없다. EPSG 좌표계 간 좌표 변환은 GIS에서 가장 흔한 작업 중 하나이며, 실수하면 보이지 않는 수 미터 오차부터 대륙이 바뀌는 치명적 오류까지 발생한다.

EPSG 좌표 변환은 수학적으로 정의된 데이텀 변환과 지도 투영을 사용하여 한 좌표 참조 시스템의 좌표를 다른 시스템으로 변환하는 것이다. 아래에서 JavaScript, Python, 커맨드라인에서의 변환 코드와, 데이터를 조용히 망가뜨리는 흔한 실수들을 다룬다.

좌표 변환이란 무엇이며 언제 필요한가?

좌표 변환은 포인트 데이터를 한 CRS에서 다른 CRS로 재투영하는 작업이다. 서로 다른 좌표계로 수집된 데이터셋을 결합하거나, GPS 데이터를 웹 지도에 표시하거나, 지리 좌표(도 단위)와 투영 좌표(미터 단위) 사이를 전환하거나, GCJ-02나 BD-09 같은 중국의 오프셋 좌표계를 다룰 때 필요하다.

자주 혼동되는 두 가지 작업이 있다. 좌표 변환(coordinate conversion) 은 동일한 데이텀 위에서 지도 투영만 바꾸는 것이다. 예를 들어 WGS 84 지리 좌표(EPSG:4326)를 WGS 84 / UTM Zone 50N(EPSG:32650)으로 변환하는 경우다. 데이텀 변환(coordinate transformation) 은 데이텀 자체를 바꾸는 것이다. NAD27에서 WGS 84로 변환할 때는 두 데이텀이 지구 형상을 서로 다르게 정의하기 때문에 물리적 모델 매개변수가 필요하다. 대부분의 GIS 라이브러리가 둘 다 투명하게 처리하지만, 정밀도 문제를 디버깅할 때는 이 구분이 중요하다.

좌표 변환이 필요한 다섯 가지 시나리오:

  1. 서로 다른 CRS의 데이터셋 병합 — 외주 업체가 국가 격자 좌표(예: OSGB 1936, EPSG:27700)로 측량 데이터를 납품했는데, WGS 84 기준 데이터와 합쳐야 하는 경우.
  2. GPS에서 웹 지도로 — GPS 원시 출력은 WGS 84(도 단위)이지만, 웹 지도는 Web Mercator(미터 단위)로 렌더링한다. 모든 타일 기반 지도가 내부적으로 이 변환을 수행한다.
  3. 클라이언트가 요구하는 CRS로 측량 데이터 납품 — 클라이언트의 CAD 시스템이 Lambert-93(EPSG:2154)을 요구하는데 현장 장비는 WGS 84로 기록하는 경우.
  4. 중국 지도 작업 — Amap(가오더), Tencent Maps, Baidu Maps는 각각 WGS 84와 호환되지 않는 독자적 오프셋 CRS(GCJ-02, BD-09)를 사용한다.
  5. UTM 구역 간 변환 — 프로젝트가 두 개의 UTM 구역에 걸쳐 있어서 일관된 거리 및 면적 계산을 위해 단일 투영 CRS가 필요한 경우.

EPSG:4326에서 EPSG:3857로 변환하기

EPSG:4326(WGS 84, 도 단위)에서 EPSG:3857(Web Mercator, 미터 단위)로 변환하려면 메르카토르 투영 공식을 적용한다. JavaScript에서는 proj4("EPSG:4326", "EPSG:3857", [lng, lat])을 사용하고, Python에서는 Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True)를 사용한다. 이 변환은 경도/위도를 미터 단위의 동향/북향 좌표로 매핑한다.

베이징을 예로 들어 보자:

// JavaScript — proj4js
import proj4 from "proj4";

const [easting, northing] = proj4(
  "EPSG:4326",
  "EPSG:3857",
  [116.4074, 39.9042] // Beijing (lng, lat)
);
console.log(easting, northing);
// → 12958175.0, 4852834.1
# Python — pyproj
from pyproj import Transformer

transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True)
easting, northing = transformer.transform(116.4074, 39.9042)
print(easting, northing)
# → 12958175.0, 4852834.1

입력은 두 개의 작은 숫자(도)이고 출력은 두 개의 큰 숫자(미터)다. 이것이 정상이다. Web Mercator는 적도와 본초자오선이 만나는 0°N, 0°E 지점을 원점으로 삼아 거리를 측정한다.

한 가지 주의할 점이 있다. EPSG:3857은 약 ±85.06° 위도에서 잘린다. 메르카토르 투영은 극점을 무한대로 매핑하기 때문에 북위 85° 또는 남위 85°를 넘는 좌표는 표현할 수 없다. 북극과 남극 데이터에는 EPSG:3413(북극) 또는 EPSG:3031(남극) 같은 극 평사 투영을 사용해야 한다.

KunYu의 좌표 변환기에서 변환 결과를 즉시 확인할 수 있다. 좌표를 붙여넣고 원본 및 대상 CRS를 선택하면 설치 없이 바로 결과를 얻을 수 있다.

임의의 두 EPSG 코드 간 변환

동일한 패턴이 모든 EPSG 쌍에 적용된다. 기본 내장되지 않은 코드의 경우 CRS 정의(proj4 문자열 또는 WKT)가 필요하다. proj4js는 사용 전에 커스텀 CRS 정의를 등록해야 하고, pyproj는 전체 EPSG 데이터베이스를 내장하고 있다.

JavaScript (proj4js)

Proj4js는 기본적으로 EPSG:4326과 EPSG:3857만 알고 있다. 다른 모든 코드에는 정의 문자열이 필요하며, epsg.io에서 찾을 수 있다.

import proj4 from "proj4";

// Register Lambert-93 (France) — definition from epsg.io/2154
proj4.defs(
  "EPSG:2154",
  "+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 " +
    "+x_0=700000 +y_0=6600000 +ellps=GRS80 +units=m +no_defs"
);

// WGS 84 → Lambert-93
const [x, y] = proj4("EPSG:4326", "EPSG:2154", [2.3522, 48.8566]);
console.log(x, y);
// → 652469.5, 6862035.9 (Paris in Lambert-93 meters)

한번은 CGCS2000 투영 데이터셋에서 3미터 오차를 디버깅하느라 한 시간을 보낸 적이 있다. 원인은 Shapefile과 함께 온 .prj 파일이 epsg.io에 등록된 것과 약간 다른 타원체 매개변수를 사용하고 있었기 때문이었다. 해결책은 EPSG 코드를 찾아서 정의가 일치할 것이라고 가정하는 대신, gdalsrsinfo input.shp.prj 내용에서 직접 proj4 문자열을 추출하는 것이었다. 정밀도가 중요할 때는 항상 원본 데이터 자체에서 정의를 가져와야 한다.

Python (pyproj)

pyproj는 전체 PROJ 데이터베이스를 내장하고 있어 수동으로 정의를 등록할 필요가 없다. 중요한 매개변수는 always_xy=True다. 이것이 없으면 pyproj는 EPSG 표준 축 순서(지리 CRS의 경우 위도가 먼저)를 따르기 때문에 좌표가 조용히 뒤바뀐다.

from pyproj import Transformer

# pyproj knows all EPSG codes — no manual registration
transformer = Transformer.from_crs("EPSG:4326", "EPSG:2154", always_xy=True)
x, y = transformer.transform(2.3522, 48.8566)
print(x, y)
# → 652469.5, 6862035.9

커맨드라인 (ogr2ogr)

Shapefile, GeoJSON, GeoPackage 등 파일 단위 재투영에는 GDAL의 ogr2ogr가 표준 도구다. 단일 명령으로 전체 데이터셋을 일괄 변환할 수 있다.

# Reproject a GeoJSON from WGS 84 to UTM Zone 50N
ogr2ogr -t_srs EPSG:32650 output.geojson input.geojson

# Reproject a Shapefile from OSGB 1936 to WGS 84
ogr2ogr -s_srs EPSG:27700 -t_srs EPSG:4326 output.shp input.shp

입력 파일에 CRS 메타데이터(.prj 파일이나 내장 투영 정보)가 없을 때 -s_srs로 원본 CRS를 명시적으로 지정한다. 원본 CRS가 파일에 이미 정의되어 있으면 -t_srs만으로 충분하다.

중국 좌표 변환 (WGS 84, GCJ-02, BD-09)

중국은 모든 공개 지도 서비스에 좌표 오프셋을 의무화하고 있다. GCJ-02("화성 좌표")는 Krasovsky 1940 타원체 기반의 비선형 알고리즘으로 WGS 84 포인트를 100~700미터 이동시킨다. BD-09는 GCJ-02 위에 추가 오프셋을 적용한다. 이들은 EPSG에 등록된 시스템이 아니며, proj4나 pyproj 같은 표준 도구로는 변환할 수 없다. 별도의 오프셋 알고리즘이 필요하다.

이 오프셋은 중국의 측량 규정상 공개 디지털 지도에 암호화된 좌표계를 사용하도록 요구하기 때문에 존재한다. 정방향 변환(WGS 84 → GCJ-02)은 결정론적 공식이다. 역방향(GCJ-02 → WGS 84)은 폐쇄형 해가 없어서 반복 근사가 필요하며, 일반적으로 10회 반복으로 서브미터 정확도(약 0.1m)를 달성한다.

어떤 서비스가 어떤 좌표계를 사용하는가:

지도 서비스 좌표계 WGS 84와의 편차
Google Maps (중국) GCJ-02 100~700 m
Amap (가오더) GCJ-02 100~700 m
Tencent Maps GCJ-02 100~700 m
Apple Maps (중국) GCJ-02 100~700 m
Baidu Maps BD-09 100~700 m + 추가 오프셋
OpenStreetMap WGS 84 없음

변환 경로는 WGS 84 ↔ GCJ-02 ↔ BD-09이다. GCJ-02 중간 단계를 거치지 않고 WGS 84와 BD-09를 직접 변환하면 잘못된 결과가 나온다. KunYu의 좌표 변환기는 반복 역변환 알고리즘을 포함한 전체 변환 체인을 구현하고 있으며, 대부분의 해외 GIS 도구는 이를 지원하지 않는다.

실제 경험상 오프셋은 서부 중국에서 가장 크다. 우루무치 근처에서는 600미터를 넘는 편차를 확인한 적이 있는데, 이 정도면 포인트가 강 건너편이나 고속도로 반대편에 찍힐 수 있다. 상하이 같은 동부 연안 도시에서는 오프셋이 더 작지만(약 200~300미터), 무시하기에는 여전히 너무 크다. 미묘한 함정은 오프셋이 전국에 걸쳐 부드럽게 변한다는 것이다. 한 지점만 확인해서는 문제를 잡아낼 수 없다. 베이징에서 "맞아 보이는" 변환 결과가 청두에서는 눈에 띄게 어긋날 수 있다.

좌표 변환기

EPSG 시스템 간 좌표 변환. 일괄 처리 및 스마트 감지 지원.

Try it now

좌표 변환에서 흔히 저지르는 다섯 가지 실수

가장 빈번한 좌표 변환 오류는 잘못된 원본 CRS 선택, 축 순서 혼동, Web Mercator를 측정에 사용, 데이텀 변환 누락, 그리고 모든 도 단위 좌표가 WGS 84라고 가정하는 것이다. 각각 미묘하게 잘못된 결과를 만들어내며, 후속 작업에서 실제 문제가 될 때까지 알아채지 못할 수 있다.

잘못된 원본 CRS 선택

원본 데이터가 NAD83(EPSG:4269)인데 변환기에 WGS 84(EPSG:4326)라고 지정하면, 북미 대부분 지역에서 차이는 약 12미터에 불과하다. 테스트에서는 놓치기 쉽지만 측량급 작업에서는 유의미하다. NAD27과 WGS 84를 혼동하면 더 심각하다. 위치에 따라 10200미터 오프셋이 발생하며, 건물이 필지 경계 반대편에 놓일 수 있다.

축 순서 혼동 (lat/lon vs lon/lat)

EPSG:4326의 공식 정의는 위도가 먼저(lat, lng)다. 하지만 GeoJSON, proj4js, Leaflet의 L.latlng() 생성자(이름과 달리), 그리고 대부분의 웹 매핑 라이브러리는 경도가 먼저(lng, lat)를 기대한다. 변환한 포인트가 지도 위에 무작위로 흩어지지만 대략 맞는 축척이라면, lat과 lon을 뒤바꿔 넣었을 가능성이 높다. pyproj의 always_xy=True 매개변수가 바로 이 함정을 피하기 위해 존재한다.

Web Mercator를 측정에 사용

EPSG:3857은 표시용이지 계산용이 아니다. Web Mercator에서 면적을 계산하면 고위도로 갈수록 결과가 부풀려진다. 그린란드가 실제보다 14배 크게 보이는 것이 그 예다. 거리와 면적 계산에는 로컬 투영 CRS(UTM 구역이나 국가 격자)로 먼저 변환한 뒤 측정해야 한다.

데이텀 변환 누락

서로 다른 데이텀을 사용하는 CRS 간 변환에는 특정 매개변수가 포함된 데이텀 변환이 필요하다. 예를 들어 유럽에서 ED50을 ETRS89로 변환하는 경우다. 일반적이거나 기본 변환을 사용하면 수 미터의 오차가 발생할 수 있다. GDAL과 pyproj는 격자 이동 파일이 있으면 자동으로 처리하지만, 사용된 변환 방법을 확인하는 것이 좋다.

모든 도 단위 좌표가 WGS 84라고 가정

도 단위를 사용하는 지리 CRS는 여러 가지가 있다: EPSG:4326(WGS 84), EPSG:4269(NAD83), EPSG:4490(CGCS2000), EPSG:4612(JGD2000). 139.6917, 35.6895 같은 포인트가 이 중 어느 것이든 될 수 있다. 형식은 동일하게 생겼지만 사용하는 데이텀이 다르다. 변환 전에 반드시 원본 메타데이터를 확인해야 한다.

좌표 변환에 적합한 도구 선택

단일 포인트나 소량 변환에는 브라우저 기반 도구가 가장 빠르다. 설치도 코드도 필요 없다. 파일 재투영에는 ogr2ogr나 QGIS를 사용한다. 프로그래밍 파이프라인에는 proj4js(JavaScript) 또는 pyproj(Python)를 코드에 직접 통합한다. 중국 CRS의 경우 대부분의 해외 도구가 부족하다.

시나리오 추천 도구 이유
단일 포인트 빠른 확인 KunYu 좌표 변환기 브라우저 기반, 6000+ EPSG, 중국 CRS 포함
파일 재투영 (SHP/GeoJSON) ogr2ogr (GDAL) 모든 포맷, 일괄 처리, 스크립트 가능
QGIS 프로젝트 레이어 재투영 도구 GUI 워크플로, 속성과 스타일 유지
JavaScript 웹 앱 proj4js 클라이언트 사이드, 약 100KB, 서버 불필요
Python 데이터 파이프라인 pyproj 전체 PROJ 데이터베이스, NumPy 호환
중국 CRS (GCJ-02/BD-09) KunYu 좌표 변환기 대부분의 도구에 없는 오프셋 알고리즘 내장

개인적으로 반복 작업에는 ogr2ogr를 기본으로 쓴다. 한번은 지자체 데이터셋에서 200개 넘는 GeoJSON 파일(전부 로컬 Lambert 투영)을 웹 앱용 WGS 84로 재투영해야 했다. QGIS에서 하면 200번의 "내보내기 → 다른 이름으로 저장 → CRS 설정" 클릭이 필요하다. ogr2ogr로는 셸 루프 한 줄이면 된다: for f in *.geojson; do ogr2ogr -t_srs EPSG:4326 out/"$f" "$f"; done. 1분도 걸리지 않았다. QGIS는 재투영 결과를 시각적으로 확인하거나, 원본 CRS가 파일에 내장되어 있지 않아서 여러 옵션을 대화식으로 시도해봐야 할 때 더 낫다.

EPSG 검색

EPSG 좌표계 데이터베이스 검색 및 탐색.

Try it now

FAQ

EPSG:4326에서 UTM으로 어떻게 변환하나요?

먼저 좌표가 어느 UTM 구역에 속하는지 결정한다: zone = floor((longitude + 180) / 6) + 1. 그런 다음 해당 EPSG 코드로 변환한다. 북반구는 EPSG:326xx, 남반구는 EPSG:327xx이며, xx는 구역 번호다. 예를 들어 동경 116.4°의 베이징은 UTM Zone 50N에 속하며, EPSG:32650이다.

좌표 변환(conversion)과 데이텀 변환(transformation)의 차이는 무엇인가요?

좌표 변환은 동일한 데이텀 위에서 지도 투영만 바꾸는 것이다(예: WGS 84 지리 좌표를 WGS 84 / UTM으로). 데이텀 변환은 데이텀 자체를 바꾸는 것으로(예: NAD27에서 WGS 84로), 물리적 모델 매개변수가 필요하다. pyproj와 GDAL 같은 GIS 도구는 둘 다 투명하게 처리하며, 이 구분은 주로 정밀도 문제를 디버깅할 때 중요하다.

변환한 좌표가 수백 미터 어긋나는 이유는 무엇인가요?

가장 흔한 원인은 잘못된 원본 CRS다. 중국에서 WGS 84와 GCJ-02를 혼용하면 100~700미터 오프셋이 발생하는데, 이것은 버그가 아니라 설계된 동작이다. 중국 외 지역에서는 NAD27과 WGS 84를 혼동하거나 잘못된 데이텀 변환 매개변수를 사용하면 비슷한 오류가 발생한다. 변환 전에 반드시 원본 CRS를 확인해야 한다.

코딩 없이 좌표를 일괄 변환할 수 있나요?

가능하다. KunYu의 좌표 변환기는 여러 좌표(줄당 하나)를 브라우저에서 직접 일괄 변환할 수 있다. 가입이 필요 없고 데이터가 기기를 떠나지 않는다. Shapefile이나 GeoJSON 파일 단위 일괄 변환에는 포맷 변환기 도구나 커맨드라인의 ogr2ogr를 사용하면 된다.

proj4js와 PROJ는 같은 건가요?

아니다. PROJ는 OSGeo 커뮤니티가 관리하는 C/C++ 좌표 변환 라이브러리로, 10,000개 이상의 CRS 정의와 격자 기반 데이텀 변환을 지원하는 데이터베이스를 포함한다. Proj4js는 동일한 핵심 알고리즘을 구현한 JavaScript 포트이지만, 기본적으로 EPSG:4326과 EPSG:3857만 포함하며 다른 모든 CRS 정의는 수동으로 등록해야 한다.

핵심 정리

패턴은 항상 같다: 원본 CRS를 확인하고, 대상 CRS를 확인하고, 변환을 적용한다. 빠른 확인에는 온라인 변환기를 사용하고, 파일 단위 재투영에는 ogr2ogr를, 애플리케이션 코드에는 proj4js나 pyproj를 내장한다. 어떤 EPSG 코드가 필요한지 모르겠다면 EPSG 코드 가이드에서 시작하거나 EPSG 검색 도구로 직접 찾아보자.