React와 CesiumJS로 3D 건물 시각화하기
디지털 트윈 프로젝트를 준비하며, 웹 브라우저 상에서 3D 지도를 구현해보려 합니다. 이를 위해 CesiumJS를 활용하여 지형, 건물, 도로 등 다양한 공간 정보를 시각화할 계획입니다. 우선 V-World 디지털트윈국토에서 지원하는 GIS건물통합정보를 기반으로, CesiumJS 위에 건물 데이터를 3D로 표현하려 합니다.
이전 글 보기
[디지털 트윈] React에서 CesiumJS 사용하기
CesiumJS는 3D 지구본과 도시 시각화를 위한 오픈소스 라이브러리입니다. 디지털 트윈 프로젝트를 준비하며, 웹 브라우저 상에서 3D 지도를 구현해보려 합니다. 본격적인 개발에 앞서 CesiumJS 환경을
dachaes-devlogs.tistory.com
개발 환경
- Node v22.16.0
- React v19.1.0 (TypeScript) + Vite
- QGIS Desktop 3.42.3
개발 순서
- 건물 정보 SHP 파일 다운받기
- QGIS로 열어서 필요 지역만 자르기
- SHP → GeoJSON 변환하기
- Cesium에서 빌딩 정보를 3D로 띄워보기
참고 사이트
- V-World 디지털트윈국토 공식 사이트 : https://www.vworld.kr/v4po_main.do
- V-World 디지털트윈국토 공간 정보 다운로드 - GIS건물통합정보 : https://www.vworld.kr/dtmk/dtmk_ntads_s002.do?svcCde=NA&dsId=18
1. 건물 정보 SHP 파일 다운받기
V-World 디지털트윈국토 공식 사이트에서 필요한 GIS건물통합정보 SHP 파일을 다운받습니다.
2. QGIS로 SHP 파일을 열어서 필요 지역만 자르기
a. QGIS에서 SHP 파일 열기 (예시 - 경기도)
QGIS를 실행합니다. 레이어 > 레이어 추가 > 벡터 레이어 추가 > SHP 파일 선택하면 지도에 전체 건물이 표시됩니다.
💡 참고 : QGIS에서 .shp 파일을 열 때 실제로는 해당 .shp와 연관된 모든 파일(.shx, .dbf, .prj 등)을 백그라운드에서 자동으로 함께 로드합니다. 사용자는 .shp만 선택하지만, 이들 파일은 각각 공간정보, 속성정보, 좌표계 정보 등을 담고 있어 정상적인 레이어 표시를 위해 모두 필요합니다.
추가한 SHP 파일을 우클릭하여 속성 > 소스 에서 데이터 소스 인코딩을 korean 으로 바꿉니다.
b. 필요한 지역 찾기
위치 좌표를 통해 찾는 방법과 직접 지도에서 찾는 방법이 있습니다.
직접 지도에서 찾기 위해 탐색기 탭의 XYZ Tiles > OpenStreetMap 를 레이어 탭으로 끌어 내립니다.
c. 필요한 지역 잘라내기
레이어 > 레이어 생성 > 새 임시 스크래치 레이어를 선택하면 새 임시 스크래치 레이어 창이 열립니다. 레이어 이름을 작성하고, 도형 유형을 선택합니다. 좌표계는 프로젝트와 파일과 동일한 좌표계를 선택합니다.
레이어 탭에 방금 만든 임시 스크래치 데이터가 생성된 것을 볼 수 있습니다.
새로 만든 레이어를 우클릭하여 편집 모드 켜고 끄기를 누릅니다. 편집 > 폴리곤 피처 추가를 선택하고, 원하는 영역을 그리고, 저장합니다.
벡터 > Geoprocessing Tools > 잘라내기(clip)를 선택하면 벡터 중첩 - 잘라내기(clip) 창이 열립니다. 입력 레이어에는 건물 정보가 있는 레이어를, 중첩 레이어에는 원하는 위치를 고른 레이어를 선택한 후 실행합니다.
경기도의 경우에는 건물 레이어가 3개 였으니 잘라내기를 3번 실행합니다. 레이어 탭에 산출물이 3개 생성됩니다.
3. SHP → GeoJSON 변환하기
레이어 탭의 산출물을 우클릭 하여 내보내기 > 피처를 다른 이름으로 저장을 누릅니다.
포맷은 GeoJson 을 선택하고, 파일 이름을 작성합니다. 좌표계는 EPSG:4326 - WGS 84를 선택합니다. Cesium은 지구를 기반으로 한 3D 뷰어이기 때문에 모든 공간 데이터가 위도/경도 기반 (WGS 84) 이어야 정확히 매핑됩니다.
4. Cesium에서 빌딩 정보를 3D로 띄워보기
C:\Users\User\Documents(문서 폴더)에 저장된 geojson 파일을 React 프로젝트의 public 폴더로 옮깁니다.
geojson 파일을 파싱하여, 건물의 위치와 높이 정보를 추출한 뒤 Cesium 위에 3D 건물로 시각화합니다.
import * as Cesium from "cesium";
/**
* GeoJSON을 로드하고, 각 폴리곤에 건물 높이(extrudedHeight)를 적용하여 3D로 시각화합니다.
*
* @param viewer - Cesium.Viewer 인스턴스
* @param url - GeoJSON 파일 경로 (예: "/data/buildings.geojson")
* @param heightField - 속성에서 건물 높이값을 가져올 키 이름 (예: "A16" = 건물 높이(m))
*/
export const loadExtrudedGeoJson = (
viewer: Cesium.Viewer | null,
url: string,
heightField: string = "A16"
) => {
// Viewer가 없으면 종료
if (!viewer) return;
// GeoJSON 파일 비동기 로드
Cesium.GeoJsonDataSource.load(url, {
clampToGround: true, // 건물을 지형에 맞게 붙일지 여부 (true = 지면에 고정)
}).then((dataSource) => {
// 로드한 데이터소스를 뷰어에 추가
viewer.dataSources.add(dataSource);
// 폴리곤 데이터만 추려서 flyTo로 카메라 이동
const validEntities = dataSource.entities.values.filter((e) => Cesium.defined(e.polygon));
if (validEntities.length > 0) viewer.flyTo(validEntities);
// 각 entity(건물)에 높이, 색상, 외곽선 등을 설정
dataSource.entities.values.forEach((entity) => {
if (!Cesium.defined(entity.polygon)) return;
let h = 10; // 기본 높이
const props = entity.properties;
// 속성값 중 heightField에 해당하는 값을 가져와 숫자로 변환
if (
Cesium.defined(props) &&
Cesium.defined(props[heightField]) &&
typeof props[heightField].getValue === "function"
) {
const val = props[heightField].getValue(Cesium.JulianDate.now());
const numVal = Number(val);
// 유효한 숫자이고 0 이상이면 사용, 아니면 기본값 30으로 설정
h = !isNaN(numVal) && numVal > 0 ? numVal : 30;
}
// 건물 높이 설정
entity.polygon.extrudedHeight = new Cesium.ConstantProperty(h);
// 건물 색상 반투명 회색
entity.polygon.material = new Cesium.ColorMaterialProperty(
Cesium.Color.GRAY.withAlpha(0.8)
);
// 외곽선 제거
entity.polygon.outline = new Cesium.ConstantProperty(false);
});
});
};
useEffect(() => {
// Cesium의 viewer 관련 코드 작성 생략
// .
// .
// .
setTimeout(() => {
loadExtrudedGeoJson(viewerRef.current, "/simulation_cesium/buildings/pangyo_buildings1.geojson", "A16");
loadExtrudedGeoJson(viewerRef.current, "/simulation_cesium/buildings/pangyo_buildings2.geojson", "A16");
loadExtrudedGeoJson(viewerRef.current, "/simulation_cesium/buildings/pangyo_buildings3.geojson", "A16");
}, 0);
return () => { if (viewer) viewer.destroy(); }
}, []);
5. 실행 결과
일부 건물은 높이 정보가 누락되어 있어 임의의 값으로 대체하였습니다. 이로 인해 정확도에 다소 차이가 있을 수 있습니다. 하지만 본 프로젝트에서는 100% 정확한 건물 데이터가 필요하지 않으므로 해당 부분은 생략하도록 하겠습니다.
'개발 기록 > [CTSG] 25.06.01-' 카테고리의 다른 글
[디지털 트윈] Unity에 3D 건물 시각화하기 (0) | 2025.06.27 |
---|---|
[디지털 트윈] Unity에 3D 지형 시각화하기 - DEM 편 (0) | 2025.06.24 |
[디지털 트윈] Unity에 3D 지형 시각화하기 - 수치 지형도/정사 영상 편 (0) | 2025.06.24 |
[디지털 트윈] React에서 CesiumJS 사용하기 (0) | 2025.06.11 |
[디지털 트윈] OSM 데이터를 Unity 3D에 불러오기 (1) | 2025.06.09 |