3D

[3D] Cesium Terrain Server 학습

teddy bear 2026. 7. 4. 10:29
반응형

Cesium Terrain Server 프로젝트 학습 정리

Cesium.js 기반 3D 지형(terrain) 타일셋을 파일시스템에서 서빙하는 Go HTTP 서버를 분석한 내용입니다.


들어가며

3D 지도를 웹에서 구현할 때 Cesium.js는 사실상 표준에 가깝습니다. 하지만 DEM(디지털 고도 모델) 데이터를 Cesium이 읽을 수 있는 terrain 타일로 변환한 뒤, 이를 HTTP로 안정적으로 서빙하는 과정은 별도의 인프라가 필요합니다.

Cesium Terrain Server는 바로 그 역할을 하는 경량 Go 서버입니다. Cesium Terrain Builder(CTB)로 생성한 타일셋을 개발·테스트 환경에서 빠르게 올려볼 수 있도록 설계되었습니다.


프로젝트 개요

항목내용

언어 Go 1.24+
라이선스 Apache 2.0
원저자 geo-data (Southampton)
목적 파일시스템 기반 terrain 타일셋을 CesiumTerrainProvider가 소비할 수 있게 HTTP로 제공
배포 단일 바이너리 또는 Docker 이미지 (geodata/cesium-terrain-server)

핵심 의존성은 다음과 같습니다.

  • gorilla/mux — URL 라우팅
  • gorilla/handlers — HTTP 액세스 로그
  • bradfitz/gomemcache — Memcached 연동 (선택)

Cesium Terrain이란?

Cesium 클라이언트는 지형 데이터를 Quantized Mesh 또는 Heightmap 형식의 .terrain 파일로 요청합니다. 타일셋 하나는 대략 이런 디렉터리 구조를 가집니다.

/data/tilesets/terrain/srtm/
├── layer.json          # 타일셋 메타데이터 (필수)
├── 0/0/0.terrain       # 루트 타일 (zoom 0)
├── 1/1/1.terrain
├── 2/3/3.terrain
└── ...

클라이언트는 먼저 layer.json을 요청해 타일셋의 스킴·포맷·타일 URL 패턴을 파악한 뒤, {z}/{x}/{y}.terrain 경로로 개별 타일을 순차적으로 가져옵니다.


아키텍처

전체 구조는 얇은 HTTP 레이어 + 스토리지 추상화로 단순하게 나뉩니다.

┌─────────────────────────────────────────────────┐
│  HTTP Client (Cesium.js / 브라우저)              │
└────────────────────┬────────────────────────────┘
                     │
┌────────────────────▼────────────────────────────┐
│  Middleware Chain                                │
│  ├─ CORS (Access-Control-Allow-Origin: *)       │
│  ├─ Memcached Cache (선택)                       │
│  └─ Combined Logging (선택)                      │
└────────────────────┬────────────────────────────┘
                     │
┌────────────────────▼────────────────────────────┐
│  Handlers (gorilla/mux)                          │
│  ├─ LayerHandler  → layer.json                  │
│  └─ TerrainHandler → *.terrain                   │
└────────────────────┬────────────────────────────┘
                     │
┌────────────────────▼────────────────────────────┐
│  Store Interface (stores.Storer)                 │
│  └─ fs.Store — 파일시스템 구현                   │
└─────────────────────────────────────────────────┘

디렉터리 구조

cesium-terrain-server/
├── cmd/cesium-terrain-server/   # 진입점, CLI 플래그
├── handlers/                    # HTTP 핸들러, 캐시, CORS
├── stores/                      # 스토리지 인터페이스 + fs 구현
├── assets/                      # go-bindata로 임베드된 빈 타일
├── log/                         # 우선순위 기반 로깅
├── data/                        # smallterrain-blank.terrain
└── docker/                      # Docker 이미지 빌드 + Cesium.js 번들

API 엔드포인트

기본 URL 접두사는 /tilesets이며, -base-terrain-url 플래그로 변경할 수 있습니다.

메서드경로설명

GET /tilesets/{tileset}/layer.json 타일셋 메타데이터
GET /tilesets/{tileset}/{z}/{x}/{y}.terrain 개별 terrain 타일

예시:

http://localhost:8000/tilesets/srtm/layer.json
http://localhost:8000/tilesets/srtm/0/0/0.terrain

-web-dir 옵션을 지정하면 정적 파일(HTML, Cesium.js 등)도 함께 서빙할 수 있습니다.


핵심 로직 분석

1. TerrainHandler — 타일 서빙

handlers/terrain.go가 타일 요청의 중심입니다.

  1. URL에서 {tileset}, {z}, {x}, {y} 파싱
  2. 파일시스템 스토어에서 {root}/{tileset}/{z}/{x}/{y}.terrain 읽기
  3. 성공 시 Content-Type: application/octet-stream, Content-Encoding: gzip 헤더와 함께 응답

루트 타일 자동 보완이 이 서버의 중요한 편의 기능입니다. Cesium은 zoom level 0에서 0/0/0.terrain과 0/1/0.terrain 두 타일을 항상 요청하는데, CTB의 ctb-tile은 본初子午선과 교차하지 않는 데이터셋에서는 하나만 생성합니다. 이 서버는 누락된 루트 타일에 대해 내장된 빈 terrain 타일(data/smallterrain-blank.terrain)을 대신 반환합니다.

if t.IsRoot() {
    // serve up a blank tile as it is a missing root tile
    data, err := assets.Asset("data/smallterrain-blank.terrain")
    // ...
}

2. LayerHandler — layer.json

CesiumTerrainProvider는 layer.json 없이는 동작하지 않습니다. 타일셋 루트에 layer.json이 있으면 그대로 반환하고, 없으면 기본 JSON을 생성해 응답합니다(원본 README 기준).

기본값 예시:

{
  "tilejson": "2.1.0",
  "format": "heightmap-1.0",
  "version": "1.0.0",
  "scheme": "tms",
  "tiles": ["{z}/{x}/{y}.terrain"]
}

3. 파일시스템 스토어

stores/fs/fs.go는 Storer 인터페이스의 유일한 구현체입니다.

type Storer interface {
    Tile(tileset string, tile *Terrain) error
    Layer(tileset string) ([]byte, error)
    TilesetStatus(tileset string) TilesetStatus
}

타일셋 디렉터리 존재 여부로 NOT_FOUND / FOUND를 구분해, 존재하지 않는 타일셋에 대해 404를 반환합니다.

4. Memcached 캐시 (역프록시 패턴)

흥미로운 설계 포인트입니다. 이 서버는 캐시에서 읽지 않고, 캐시에만 씁니다.

Client → Nginx (memcached 조회) → 캐시 HIT → 응답
                                → 캐시 MISS → Terrain Server → Memcached SET

-memcached 옵션으로 활성화하면, 200 응답을 ResponseRecorder로 복제 기록한 뒤 Memcached에 저장합니다. X-Memcache-Key 헤더가 있으면 그 값을 키로 사용합니다.

-cache-limit(기본 1MB)보다 큰 응답은 캐시하지 않습니다. LimitResponse가 바이트 수를 추적해 한도를 초과하면 캐시를 건너뜁니다.


CLI 옵션

cesium-terrain-server \
  -port=8000 \
  -dir="/data/tilesets/terrain" \
  -web-dir="/var/www/cesium" \
  -base-terrain-url="/tilesets" \
  -memcached="localhost:11211" \
  -cache-limit=1MB \
  -log-level=notice \
  -no-request-log=false

플래그기본값설명

-port 8000 리스닝 포트
-dir . 타일셋 루트 디렉터리
-web-dir (없음) 정적 파일 서빙 경로
-base-terrain-url /tilesets 타일 URL 접두사
-memcached (없음) Memcached 주소
-cache-limit 1MB Memcached 캐시 최대 응답 크기
-log-level notice crit / err / notice / debug
-no-request-log false HTTP 액세스 로그 비활성화

Docker 배포

Docker 이미지는 phusion/baseimage 기반 Ubuntu 14.04 위에 Go, terrain server, Cesium.js를 번들합니다.

docker run -p 8080:8000 \
  -v /data/tilesets/terrain:/data/tilesets/terrain \
  geodata/cesium-terrain-server

환경 변수

변수기본값설명

SERVE_STATIC (활성) 0이면 Cesium.js 정적 파일 비활성화
WEB_DIR /var/www/cesium 정적 파일 경로
BASE_TERRAIN_PATH /tilesets terrain URL 접두사
LOG_LEVEL notice 로그 레벨
LOG_REQUESTS (활성) 0이면 요청 로그 비활성화
MEMCACHED (없음) Memcached 주소

컨테이너 내부에서는 runit으로 terrain server 프로세스가 관리되며, 로그는 /var/log/terrain-server에 기록됩니다.


CTB와의 워크플로우

전형적인 terrain 파이프라인은 다음과 같습니다.

flowchart LR
    A[GeoTIFF DEM] -->|ctb-tile| B[.terrain 타일셋]
    B --> C[Cesium Terrain Server]
    C --> D[Cesium.js Viewer]
  1. DEM GeoTIFF 준비
  2. Cesium Terrain Builder의 ctb-tile로 타일 생성
  3. Cesium Terrain Server로 타일셋 서빙
  4. Cesium.js CesiumTerrainProvider로 뷰어에 연결
const terrainProvider = new Cesium.CesiumTerrainProvider({
  url: 'http://localhost:8000/tilesets/my-dem'
});
viewer.scene.terrainProvider = terrainProvider;

Docker 환경 예시:

# 1. 타일 생성
docker run -v /data/docker:/data homme/cesium-terrain-builder \
  ctb-tile --output-dir /data/tilesets/terrain/test /data/rasters/DEM.tif

# 2. 서빙
docker run -p 8080:8000 \
  -v /data/docker/tilesets/terrain:/data/tilesets/terrain \
  geodata/cesium-terrain-server

빌드 방법

# 바이너리 빌드
make          # → ./bin/cesium-terrain-server

# Docker 로컬 이미지
make docker-local   # → geodata/cesium-terrain-server:local

assets/assets.go는 go-bindata로 data/smallterrain-blank.terrain을 바이너리에 임베드합니다. 빈 루트 타일 기능이 별도 파일 없이도 동작하는 이유입니다.


설계에서 배울 점

장점

  1. 단일 책임 — terrain 타일 서빙에만 집중, 코드베이스가 작고 이해하기 쉬움
  2. 스토리지 추상화 — Storer 인터페이스로 S3, DB 등 다른 백엔드 확장 가능
  3. 실용적 편의 기능 — 루트 타일 자동 보완, 기본 layer.json 생성
  4. 역프록시 친화적 캐시 — Nginx + Memcached 조합으로 수평 확장 가능
  5. CORS 기본 지원 — 개발 환경에서 별도 프록시 없이 바로 테스트 가능

한계

  1. 파일시스템 전용 — 대규모 프로덕션에는 S3/타일 CDN이 더 적합
  2. 인증/권한 없음 — 공개 서빙 전제
  3. 구형 Docker 베이스 — Ubuntu 14.04 / phusion/baseimage (레거시)
  4. 단일 프로세스 — Go net/http 기본 서버, 고부하 시 튜닝 필요

로컬 워크스페이스 커스터마이징

현재 워크스페이스에는 원본 대비 몇 가지 로컬 수정이 있습니다.

  • main.go: 시작 시 설정값 디버그 출력, -cache-limit 기본값을 1GB로 변경
  • layer.go: 디버그 로그 추가, 기본 layer.json 폴백 로직 주석 처리
  • index.html: koreanational 타일셋을 localhost:8066에서 로드하도록 설정

프로덕션 배포 전에는 디버그 출력 제거와 layer.json 폴백 복원 여부를 검토하는 것이 좋습니다.


마치며

Cesium Terrain Server는 **"CTB로 만든 terrain 타일을 Cesium.js에 빠르게 연결"**하는 데 최적화된 도구입니다. 복잡한 타일 서버 프레임워크 대신, Go로 작성된 ~1,000줄 미만의 코드로 핵심 문제—terrain 타일 HTTP 서빙 + Cesium 호환성—을 해결합니다.

3D GIS 웹 애플리케이션을 만들 때 DEM 데이터 파이프라인의 마지막 한 조각으로 기억해 두면 유용합니다.


참고 링크


License: Apache 2.0 | Author: Homme Zwaagstra (geo-data)