3D GIS 지식 그래프

지리공간 기술과 프론트엔드 엔지니어링을 연결하는 인터랙티브 포트폴리오

  • 3D Tiles 스펙 — 클러스터: format, 난이도: advanced
  • 면적 계산 알고리즘 — Delaunay 삼각분할 + 벡터 외적 — 클러스터: geodesy, 난이도: expert
  • AWS Lambda → EC2 트리거 — 클러스터: infrastructure, 난이도: intermediate
  • Cesium.js 도입 결정 — 클러스터: decision, 난이도: intermediate
  • Cesium 마우스 이벤트 처리 — 클러스터: frontend, 난이도: advanced
  • Chrome DevTools 메모리 프로파일링 — 클러스터: problem, 난이도: intermediate
  • 좌표계 변환 EPSG→WGS84→ECEF — 클러스터: geodesy, 난이도: advanced
  • Docker 컨테이너 — 클러스터: infrastructure, 난이도: intermediate
  • Draco 지오메트리 압축 — 클러스터: optimization, 난이도: intermediate
  • FBXLoader 메모리 누수 — 클러스터: problem, 난이도: intermediate
  • 지오이드고 보정 — 클러스터: geodesy, 난이도: advanced
  • GPU 텍스처 BC7·ASTC·ETC2 — 클러스터: graphics, 난이도: advanced
  • IFC→3D Tiles 자동 변환 파이프라인 — 클러스터: implementation, 난이도: expert
  • KTX2 Basis Universal — 클러스터: optimization, 난이도: advanced
  • LOD 레벨 오브 디테일 — 클러스터: graphics, 난이도: advanced
  • Cesium 측정 도구 7종 — 클러스터: implementation, 난이도: advanced
  • 자오선 수차 보정 — 클러스터: geodesy, 난이도: expert
  • Mesh·BIM·Point Cloud — 클러스터: graphics, 난이도: beginner
  • modelMatrix 위치·회전·스케일 — 클러스터: implementation, 난이도: advanced
  • 낙관적 업데이트 — 클러스터: frontend, 난이도: advanced
  • 공간 분할 기법 — Quadtree와 AABB — 클러스터: graphics, 난이도: advanced
측지·좌표계

좌표계 변환 EPSG→WGS84→ECEF

한 줄 요약

건설·측량에서 쓰는 로컬 평면 좌표(EPSG 5186)를, Cesium이 사용하는 지심 직교 좌표(ECEF, EPSG 4978)로 변환하는 과정. proj4js로 좌표계를 변환하되, 변환 체인 전체에서 float64 정밀도를 유지하지 않으면 모델이 미세하게 어긋나는 문제가 발생한다.

왜 좌표계가 여러 개 존재하는가

지구는 둥근데 건설 현장은 평면으로 다룬다. 이 근본적 모순 때문에 좌표계가 여러 개 존재한다.

건설 현장에서 "이 기둥은 동쪽으로 10미터, 북쪽으로 5미터 지점에 있다"라고 말할 때, 이건 평면 좌표다. 지구의 곡률을 무시하고 현장을 평평한 종이 위의 격자로 취급한다. 현장 하나의 크기(수 km)에서는 이 근사가 충분히 정확하다.

그런데 이 기둥을 Cesium 지구본 위에 올리려면, "이 기둥은 지구상에서 경도 127.xx도, 위도 37.xx도에 있다"로 바꿔야 한다. 그리고 Cesium 내부에서는 "이 기둥은 지구 중심에서 x방향 -305만 미터, y방향 403만 미터, z방향 382만 미터에 있다"로 다시 바꿔야 한다.

이것이 좌표계 변환이 필요한 이유다.

실무에서 만나는 세 가지 좌표계

EPSG 5186 — 한국 중부 좌표계 (로컬 평면)

한국의 건설·측량에서 표준으로 사용하는 평면 직각 좌표계다. 단위는 미터이며, 한국 중부를 기준점으로 동향(Easting)과 북향(Northing)으로 위치를 표현한다.

장점은 직관적이라는 것이다. "동쪽으로 100m"라고 하면 진짜 100m다. 그러나 지구 전체를 표현할 수 없고, 한국 밖으로 나가면 왜곡이 커진다.

건설 현장의 BIM 데이터, 측량 데이터가 대부분 이 좌표계로 되어있다.

WGS84 / EPSG 4326 — 경위도 (글로벌 각도)

GPS가 사용하는 좌표계로, 경도(longitude)·위도(latitude)·높이(height)로 위치를 표현한다. 단위는 도(degree)다.

지구 전체를 표현할 수 있지만, 거리 계산이 직관적이지 않다. "경도 1도 차이"가 적도에서는 약 111km이지만, 위도 37도(서울)에서는 약 88km다. 같은 각도 차이가 위치에 따라 다른 거리를 의미한다.

EPSG 4978 — ECEF, 지심 직교 좌표 (Cesium 내부)

지구 중심을 원점으로 한 3차원 직교 좌표계다. x, y, z 단위는 미터이며, 지구 중심에서 각 방향으로의 거리로 위치를 표현한다. Cesium.js가 내부적으로 사용하는 좌표계다.

서울 부근의 ECEF 좌표는 대략 x ≈ -305만 m, y ≈ 403만 m, z ≈ 382만 m이다. 값이 수백만 단위이므로, 이 좌표계에서의 수치 정밀도가 중요해진다.

변환 체인: 로컬 → 경위도 → Cesium

3D 모델을 Cesium 지구본 위에 정확히 배치하려면, 다음 세 단계의 변환을 거친다.

1단계: 로컬 좌표 → 경위도 (proj4js)

EPSG 5186의 Easting/Northing 값을 WGS84의 경도/위도로 변환한다. 이 변환은 proj4js 라이브러리를 사용한다. proj4js는 수천 개의 좌표계 간 변환을 지원하는 JavaScript 라이브러리로, 좌표 변환의 사실상 표준이다.

로컬 좌표 (953887, 1952345) → 경위도 (127.xxx, 37.xxx)

2단계: 경위도 → Cesium 내부 좌표

Cesium의 Cartesian3.fromDegrees(경도, 위도, 높이) API로 경위도를 ECEF 직교 좌표(Cartesian3)로 변환한다. 이 함수가 내부적으로 WGS84 타원체 모델을 사용하여 경위도를 지심 직교 좌표로 계산한다.

경위도 (127.xxx, 37.xxx, height) → ECEF (-3058887, 4039784, 3828223)

3단계: 모델 배치

변환된 Cartesian3 좌표를 기반으로 modelMatrix를 구성하여 3D 모델을 지구본 위에 배치한다. 이 과정에서 회전(Heading-Pitch-Roll)과 스케일도 함께 적용된다.

float64 정밀도 — 좌표 변환의 숨은 함정

이 변환 체인에서 가장 어렵고 발견하기 힘든 문제는 수치 정밀도였다.

왜 float64가 필요한가

JavaScript의 Number는 기본적으로 float64(64비트 부동소수점)이므로, 순수 JavaScript 연산에서는 문제가 발생하지 않는다. 그러나 WebGL 셰이더, TypedArray(Float32Array), 또는 일부 라이브러리 내부에서 float32(32비트)로 연산이 이루어지면 정밀도가 손실된다.

실제로 각 좌표계에서 float32의 정밀도 손실을 측정해보면:

좌표계값의 크기float32 단일 단계 오차
EPSG 518695만195만 m1~5 cm
경위도127도~18 cm
ECEF300만400만 m~10 cm

단일 단계의 오차는 크지 않아 보인다. 그러나 변환 체인에서 각 단계마다 정밀도 손실이 누적되고, 특히 modelMatrix 구성 시 행렬 연산(큰 값끼리의 뺄셈, 곱셈)에서 오차가 증폭될 수 있다.

미세하지만 치명적

이 문제가 특히 까다로운 이유는, 오차가 미세해서 원인을 특정하기 어렵다는 것이다. 모델이 지구 반대편에 나타나는 극적인 오류가 아니라, 건물이 실제 위치에서 수십 cm~수 m 어긋나거나, 모델의 형상이 미세하게 깨지는 현상으로 나타난다. 이런 미세한 오차는 "좌표 변환 로직의 버그인가, 원본 데이터의 문제인가, 아니면 수치 정밀도인가"를 구분하기 어렵다.

실무에서 이 문제의 원인을 찾는 데 상당한 시간이 소요됐다. 최종적으로 변환 체인 전체에서 float64 정밀도를 일관되게 유지하는 것으로 해결했다. 중간 단계에서 float32로 떨어지는 지점이 단 하나라도 있으면 정밀도 손실이 발생할 수 있다.

핵심 교훈

좌표 변환에서는 "연산 결과가 맞는지"만 확인하는 것이 아니라, "어떤 정밀도로 연산되고 있는지"를 확인해야 한다. 특히 GIS 도메인에서 다루는 좌표는 값의 크기(수백만 미터)와 요구 정밀도(센티미터)의 차이가 크기 때문에, float32의 유효 자릿수 약 7자리로는 부족한 경우가 많다. float64의 유효 자릿수 약 15자리가 필요하다.

기존 뷰어에서는 왜 문제가 없었는가

기존 뷰어(Potree 기반)에서는 좌표 변환이 필요 없었다. 뷰어가 (0, 0, 0)을 원점으로 사용했고, 모델들은 EPSG 5186으로 이루어져 있었기 때문에 로컬 좌표를 그대로 사용할 수 있었다. 지구본이 아닌 로컬 공간에 모델을 배치했으므로 좌표 변환 자체가 불필요했다.

Cesium은 지구본 기반이므로 ECEF를 사용한다. 이것이 Cesium 도입 결정 노드의 PoC에서 "기뻤지만 좌표계가 문제였다"로 이어진 직접적인 원인이다.

이 경험에서 추출한 원칙

  1. 좌표 변환은 "값을 바꾸는 것"이 아니라 "세계관을 바꾸는 것"이다. 로컬 평면, 경위도, 지심 직교는 각각 다른 전제(평면 vs 구면 vs 3D 직교) 위에 서 있다. 단순히 숫자를 변환하는 것이 아니라, 각 좌표계가 어떤 전제 위에 있는지를 이해해야 올바른 변환을 할 수 있다.

  2. 정밀도 문제는 "값이 틀렸는가"보다 "어떤 정밀도로 연산되고 있는가"가 핵심이다. float32와 float64의 차이가 만드는 오차는 미세하지만, GIS 도메인에서는 그 미세함이 실제 위치 오차로 직결된다.

  3. 변환 체인의 모든 단계에서 정밀도를 일관되게 유지하라. 중간에 float32로 떨어지는 지점이 하나라도 있으면 최종 결과의 정밀도가 훼손된다.