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가 타일 요청의 중심입니다.
- URL에서 {tileset}, {z}, {x}, {y} 파싱
- 파일시스템 스토어에서 {root}/{tileset}/{z}/{x}/{y}.terrain 읽기
- 성공 시 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]
- DEM GeoTIFF 준비
- Cesium Terrain Builder의 ctb-tile로 타일 생성
- Cesium Terrain Server로 타일셋 서빙
- 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을 바이너리에 임베드합니다. 빈 루트 타일 기능이 별도 파일 없이도 동작하는 이유입니다.
설계에서 배울 점
장점
- 단일 책임 — terrain 타일 서빙에만 집중, 코드베이스가 작고 이해하기 쉬움
- 스토리지 추상화 — Storer 인터페이스로 S3, DB 등 다른 백엔드 확장 가능
- 실용적 편의 기능 — 루트 타일 자동 보완, 기본 layer.json 생성
- 역프록시 친화적 캐시 — Nginx + Memcached 조합으로 수평 확장 가능
- CORS 기본 지원 — 개발 환경에서 별도 프록시 없이 바로 테스트 가능
한계
- 파일시스템 전용 — 대규모 프로덕션에는 S3/타일 CDN이 더 적합
- 인증/권한 없음 — 공개 서빙 전제
- 구형 Docker 베이스 — Ubuntu 14.04 / phusion/baseimage (레거시)
- 단일 프로세스 — 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 데이터 파이프라인의 마지막 한 조각으로 기억해 두면 유용합니다.
참고 링크
- Cesium Terrain Server (GitHub)
- Cesium Terrain Builder
- CesiumTerrainProvider API
- Docker Hub — geodata/cesium-terrain-server
License: Apache 2.0 | Author: Homme Zwaagstra (geo-data)
'3D' 카테고리의 다른 글
| [Frontend] CSS의 Layout, Paint, Composite 단계와 리플로우 최적화 (0) | 2026.07.07 |
|---|---|
| [3D] 3D GIS 기능 구현 (feat. MSA) (0) | 2026.07.06 |
| [3D] Cesium Terrain Builder (0) | 2026.07.04 |
| [3D] Gaussian Splatting 기술 학습 (0) | 2026.07.03 |
| [3D] ml-sharp 기술 학습 (0) | 2026.07.03 |