Frontend

[Frontend] 3D 건물 모델링 기능 구현 (feat. MSA)

teddy bear 2026. 7. 6. 20:37
반응형

3D 건물 모델링 마이크로프론트엔드

Cesium 기반 3D GIS 플랫폼 위에서 동작하는 건물 생성·층 분할·텍스처 매핑 마이크로 앱의 아키텍처와 구현을 깊이 있게 다룹니다.

React 18Webpack 5 UMDQiankunCesium 1.115ReduxFME Server3D TilesNginx Gateway

민감 정보 안내
본 문서의 IP 주소, 내부 서비스명, 조직 식별자는 <span class="blur">마스킹 처리</span> 되어 있습니다. 실제 운영 환경 값은 공개하지 않습니다.


목차

  1. 프로젝트 개요
  2. 모노레포 & 마이크로프론트엔드 아키텍처
  3. Webpack 멀티 엔트리 & UMD 번들링
  4. Qiankun 생명주기 통합
  5. 핵심 기능: 3D 건물 모델링 파이프라인
  6. Cesium 렌더링 & 지형 샘플링
  7. 백엔드 API & FME 변환 체인
  8. 환경별 빌드 전략
  9. Nginx 리버스 프록시 & 배포
  10. Architecture vs Architecture2
  11. 기술적 인사이트 & 설계 결정

1. 프로젝트 개요

이 프로젝트는 3D GIS 플랫폼의 마이크로프론트엔드(Micro-Frontend) 모듈로, 사용자가 지도 위에서 건물 Footprint를 그리고, 층별로 분할하며, 텍스처를 입힌 뒤 3D Tiles 형태로 변환·등록하는 End-to-End 워크플로우를 제공합니다.

2마이크로 앱 엔트리

UMD번들 출력 포맷

8081Dev Server Port

5174좌표계 (EPSG)

해결하는 문제

문제접근 방식

대형 3D GIS 앱의 독립 배포 Qiankun 기반 MF + Webpack UMD
SHP → CityGML → 3D Tiles 변환 FME Server 파이프라인 연동
지형 위 정확한 건물 배치 Cesium sampleTerrainMostDetailed
층별 텍스처 매핑 ImageMaterialProperty + CustomDataSource
다국어 UI react-intl (ko / en / zh)

2. 모노레포 & 마이크로프론트엔드 아키텍처

프로젝트는 Yarn Workspaces + Turbo 기반 모노레포의 projects/building3D 워크스페이스입니다. 공유 패키지를 통해 UI·서비스·유틸리티를 재사용합니다.

monorepo/
├── packages/
│   ├── uicomponents/     # @component/ui  — 공통 UI (MyModelPanel, Table, MyButton)
│   ├── servicecomponents/ # @component/service — Texture3D 등 3D 서비스 컴포넌트
│   └── utils/             # @packages/utils — Draw, download, removeTileset
├── projects/
│   └── building3D/        # ★ 본 마이크로 앱
│       ├── src/pages/
│       │   ├── Architecture/    # v1 — 기본 건물 모델링
│       │   └── Architecture2/   # v2 — 3D Tiles·레이어 등록 확장
│       ├── webpack.config.js
│       └── dist/
├── default.conf           # Nginx 게이트웨이 설정
├── Dockerfile
└── turbo.json

시스템 아키텍처

flowchart TB
    subgraph Host["메인 프레임워크 (Qiankun Host)"]
        Shell["3D Map Shell App"]
    end

    subgraph MF["building3D Micro-Frontend"]
        Entry["index.tsx<br/>bootstrap / mount / unmount"]
        App["app.tsx"]
        Arch["Architecture 컴포넌트"]
        Floor["FloorDivision"]
        Upload["UploadData"]
        Layer["LayerRegis (v2)"]
    end

    subgraph Shared["공유 레이어"]
        UI["@component/ui"]
        SVC["@component/service"]
        UTIL["@packages/utils"]
    end

    subgraph Render["3D 렌더링"]
        Cesium["Cesium Viewer<br/>(window.cviewer)"]
        Tiles["3D Tileset"]
        Entities["CustomDataSource / Entities"]
    end

    subgraph Backend["백엔드 서비스"]
        DTAPI["/dtapi/rhino-3dbld"]
        FME["/fmeserver — FME Transform"]
        FileSvr["File Server / 3D Buildings Tiler"]
        GeoSvr["GeoServer"]
    end

    Shell -->|"loadMicroApp()"| Entry
    Entry --> App --> Arch & Floor & Upload & Layer
    App --> UI & SVC & UTIL
    Arch & Floor --> Cesium
    Cesium --> Tiles & Entities
    Arch --> DTAPI
    Floor --> FME
    FME --> FileSvr --> Tiles
    Layer --> DTAPI

3. Webpack 멀티 엔트리 & UMD 번들링

마이크로프론트엔드의 핵심은 독립 번들 + 런타임 로드입니다. Webpack 설정은 src/pages/*/index.tsx 패턴을 glob으로 탐색하여 자동 멀티 엔트리를 생성합니다.

엔트리 자동 탐색

const ENTRY_ROOT_DIR = "./src/pages";
let matches = glob.sync(`${ENTRY_ROOT_DIR}/*/index.tsx`);
matches.map(f => {
  let name = f.match(/.\/src\/pages\/(\S*)\/index.tsx/)[1];
  entries[name] = f;  // Architecture, Architecture2
});

UMD 출력 — Qiankun 호환

output: {
  path: path.resolve(__dirname, "dist"),
  filename: 'static/[name].[chunkhash].js',
  library: `[name]`,           // 전역 변수명 = 엔트리명
  libraryTarget: 'umd'         // Universal Module Definition
}

설정목적

libraryTarget: 'umd' Host 앱이 <script> 또는 import()로 로드 가능
library: [name] Architecture, Architecture2 각각 독립 전역
chunkhash 캐시 무효화 & CDN 배포
Access-Control-Allow-Origin: * Dev Server CORS — 크로스 오리진 MF 로드

Externals — 번들 크기 최적화

Production / Portal 빌드 시 무거운 라이브러리를 Host가 제공하는 CDN 전역 변수로 외부화합니다.

externals: {
  "@cesiumgs/cesium-analytics": "Cesium",
  react: 'React',
  'react-dom': 'ReactDOM',
  moment: 'moment',
  '@turf/turf': 'turf',
  echarts: 'echarts',
  proj4: 'proj4',
  wellknown: 'wellknown',
}

Cesium 단일 번들은 수 MB에 달합니다. External 처리로 MF 번들을 수백 KB 수준으로 유지할 수 있습니다.

CSS Modules & Ant Design 프리픽스

// CSS Modules — 컴포넌트 스코프 격리
localIdentName: `building3D-[name]-[local]-[hash:base64:20]`

// Ant Design — MF 간 스타일 충돌 방지
modifyVars: { '@ant-prefix': 'building3D' }

4. Qiankun 생명주기 통합

각 페이지 엔트리(Architecture/index.tsx)는 Qiankun이 요구하는 3개의 필수 export를 구현합니다.

export async function bootstrap() {
  // 앱 초기화 (1회)
}

export async function mount(props: any) {
  // Host로부터 user, container, lang, onCloseMicroApp 등 수신
  render(props);
}

export async function unmount(props: any) {
  root.unmount();  // React 18 createRoot 정리
}

Host ↔ MF Props 계약

Prop타입역할

user { userid, ... } API 호출 시 사용자 식별
container HTMLElement Shadow DOM / Portal 내 마운트 지점
lang string react-intl 로케일 (ko_KR / en_US / zh_CN)
onCloseMicroApp () => void MF 종료 콜백 — Cesium Entity 정리 후 호출

마운트 DOM 구조

<!-- Architecture/index.html -->
<div id="microapp-building3D-Architecture"></div>

<!-- Architecture2/index.html -->
<div id="microapp-building3D-Architecture2"></div>

React 18 createRoot는 Host가 제공한 container 내부의 #microapp-building3D-* 엘리먼트를 타겟으로 합니다.


5. 핵심 기능: 3D 건물 모델링 파이프라인

5.1 전체 워크플로우

sequenceDiagram
    actor User
    participant UI as Architecture UI
    participant Cesium as Cesium Viewer
    participant API as DT API
    participant FME as FME Server
    participant Tiler as 3D Buildings Tiler

    User->>UI: 건물 목록 조회
    UI->>API: GET /dtapi/rhino-3dbld/list/{page}/{size}
    API-->>UI: 건물 목록 (status: 0~3)

    User->>UI: 신규 건물 생성
    UI->>UI: FloorDivision — Footprint 그리기
    User->>Cesium: Draw Tool로 Polygon/Wall 그리기
    Cesium->>Cesium: sampleTerrainMostDetailed (지형 고도)
    User->>UI: 층별 높이·텍스처 설정
    UI->>API: POST /dtapi/rhino-3dbld (GeoJSON 저장)

    User->>UI: SHP 업로드 (v2)
    UI->>FME: SHP → CityGML (EPSG:5174)
    FME-->>UI: Job ID
    UI->>FME: SHP → 3D Tiles
    FME-->>UI: 변환 완료
    UI->>Tiler: GML → 3D Tiles Tiling
    Tiler-->>UI: tileset.json URL

    User->>UI: 레이어 등록 (v2)
    UI->>API: POST /dtapi/rhino-3dbld/layer

5.2 Architecture — 건물 CRUD

메인 패널에서 건물 목록을 페이지네이션으로 관리합니다.

APIMethod설명

/dtapi/rhino-3dbld/list/{pageNum}/{pageSize} GET 목록 조회 (카테고리·건물명 필터)
/dtapi/rhino-3dbld POST 신규 3D 건물 저장
/dtapi/rhino-3dbld/{id} GET 단건 조회
/dtapi/rhino-3dbld PUT 수정
/dtapi/rhino-3dbld/delete POST 삭제
/dtapi/rhino-3dbld/thumbnail PATCH 썸네일 갱신
/dtapi/rhino-3dbld/upload/geojson POST GeoJSON 업로드
/dtapi/rhino-3dbld/upload POST 3D 데이터 업로드
/dtapi/rhino-3dbld/file POST/DELETE SHP ZIP 업·삭제
/dtapi/rhino-3dbld/layer POST 레이어 등록

상태 코드 (status 필드)

값UI 표시의미

0 대기중 변환 Job 대기
1 진행중 FME / Tiler 처리 중
2 성공 3D Tiles 생성 완료
3 실패 변환 오류

5.3 FloorDivision — 층 분할 & 3D 프리뷰

가장 기술 밀도가 높은 컴포넌트입니다.

  1. Draw Tool (@packages/utils/Draw)로 Cesium 위 Polygon/Wall 그리기
  2. Turf.js — Footprint 기하 연산
  3. 지형 샘플링 — 각 꼭짓점의 실제 고도 획득
  4. CustomDataSource — 층별 Entity (Wall / Polygon) 생성
  5. ImageMaterialProperty — 층별 텍스처 실시간 프리뷰
// 지형 고도 샘플링 → 건물 바닥면 정렬
let result = await sampleTerrainMostDetailed(
  view?.terrainProvider as TerrainProvider,
  polygon  // Cartographic[]
);

// 층별 Wall Entity 생성
item.wall.material = new ImageMaterialProperty({
  image: AppConfig.fileUrl + obj.imgUrl,
  repeat: new Cartesian3(10, 2)
});

5.4 UploadData — 썸네일 생성

Ant Design Dragger 컴포넌트로 썸네일 이미지를 업로드하고, change3DBuilding API로 메타데이터를 갱신합니다.

const formData = new FormData();
formData.append('multipartFile', file);
formData.append('userAccount', user.userid);
formData.append('path', changeNewData[0].bldgFilePath);
await upload3DData(formData);

5.5 Texture3D — 텍스처 선택 UI

@component/service의 Texture3D 컴포넌트를 통해 텍스처 라이브러리에서 이미지를 선택하면 imgClickCallback으로 Cesium Entity Material이 실시간 교체됩니다.


6. Cesium 렌더링 & 지형 샘플링

CustomDataSource 패턴

dataSource = new CustomDataSource('wall');
view?.dataSources.add(dataSource);

// MF 종료 시 정리 (Architecture2)
view.dataSources.remove(dataSource);
view.entities.remove(entity);
view.scene.primitives.remove(tileSet);

GeoJSON → 3D Entity 변환

저장된 GeoJSON을 불러올 때 combineData() 함수가 FeatureCollection을 순회하며:

  1. WGS84 좌표 → Cartographic 변환
  2. sampleTerrainMostDetailed로 지형 고도 반영
  3. PolygonHierarchy + Extruded Height로 3D Wall/Polygon Entity 생성

Draw Action 관리

draw = new Draw(view);
// DrawType.Polygon, DrawEventType.Complete 등
// action 완료 시 GeoJSON Feature 생성 → Redux store 저장

7. 백엔드 API & FME 변환 체인

FME Server 변환 파이프라인

SHP 파일을 EPSG:5174 (Korea 2000 / Central Belt) 좌표계 기준으로 변환합니다.

flowchart LR
    SHP["SHP (ZIP)"] --> FME1["FME: SHP → CityGML"]
    SHP --> FME2["FME: SHP → 3D Tiles"]
    FME1 --> GML["CityGML"]
    GML --> Tiler["3D Buildings Tiler"]
    Tiler --> Tiles["tileset.json + b3dm"]
    FME2 --> Tiles

변환 유형FME Workspace (마스킹)출력

내부 도로 SHP_to_CityGML_internal_***.fmw CityGML (5174)
실내 이동 경로 SHP_to_CityGML_indoor_***.fmw CityGML (5174)
입체 도로 SHP_to_CityGML_dimension_***.fmw CityGML (5174)
3D Tiles (도로명) SHP_to_3DTiles_roadname_***.fmw 3D Tiles
3D Tiles (입체) SHP_to_3DTiles_dimension_***.fmw 3D Tiles

HTTP Request 인터셉터

umi-request 기반의 공통 HTTP 클라이언트는 다음을 자동 처리합니다.

// FME Server 인증
if (url.startsWith('/fmeserver')) {
  options.headers['Authorization'] = 'fmetoken token=' + AppConfig.fmetoken;
}

// 사용자 정보 자동 주입
Object.assign(options.data, {
  userId: userInfo.account,
  userAccount: userInfo.account
});

// JWT 토큰
options.headers['accessToken'] = accessToken;
options.headers['refreshToken'] = refreshToken;

보안 참고: FME Token, API URL 등은 AppConfig에서 환경별로 분기됩니다. 블로그 목적상 토큰 값은 공개하지 않으며, 운영 환경에서는 반드시 환경 변수 / Secret Manager로 관리해야 합니다.


8. 환경별 빌드 전략

Webpack은 process.env.appConfigGbn을 DefinePlugin으로 주입하여 런타임 환경 분기를 구현합니다.

빌드 명령appConfigGbn용도

yarn start "" (development) 로컬 개발
yarn build "production" Production
yarn build:lx-portal "lx-portal" Portal 환경
yarn start:lx-portal "lx-portal" Portal 로컬

AppConfig 분기 예시 (민감 정보 마스킹)

if (process.env.appConfigGbn === "lx-portal") {
  tempAppConfig = {
    fileUrl: '/wastomcat/uav-model/',
    uploadUrl: 'https://<span class="blur">internal-portal-host</span>/server/other/fileInfo/readSHP',
    apiUrl: 'https://<span class="blur">internal-portal-host</span>/server',
    downLoadUrl: 'https://<span class="blur">internal-portal-host</span>',
    fmetoken: '<span class="blur">***REDACTED***</span>',
  };
} else {
  tempAppConfig = {
    fileUrl: './wastomcat/uav-model',
    uploadUrl: 'http://<span class="blur">10.x.x.x</span>:8087/server/other/fileInfo/readSHP',
    apiUrl: 'http://<span class="blur">10.x.x.x</span>:8087/server',
    downLoadUrl: 'http://<span class="blur">10.x.x.x</span>:8087',
    fmetoken: '<span class="blur">***REDACTED***</span>',
  };
}

Portal 환경에서는 Request Interceptor가 URL 앞에 .을 붙여 상대 경로 프록시를 활성화합니다.

if (process.env.appConfigGbn === "lx-portal" || "op-portal") {
  if (url.startsWith('/')) {
    url = '.' + url;  // Nginx 프록시 경유
  }
}

9. Nginx 리버스 프록시 & 배포

Docker 이미지

FROM <span class="blur">internal-registry</span>/nginx_unprivileged:1.0
COPY projects/building3D/dist /usr/share/nginx/html/building3D
COPY default.conf /etc/nginx/conf.d/
EXPOSE 80

Nginx 게이트웨이 역할

default.conf는 단일 진입점(Single Entry Point) 으로 수십 개의 백엔드 서비스를 프록시합니다.

Location프록시 대상 (마스킹)역할

/dtapi OCP Route / Back-end Pod 3D 건물 DT API
/fmeserver/ 10.x.x.x:8050 FME 변환 서버
/server 10.x.x.x:8000 공통 Admin API
/geosvr 10.x.x.x:20080 GeoServer
/fileServer/api/cesium/3d-buildings-tiler File Server 3D Tiles 생성
/share-files /dt_nas/SHARE NAS 공유 파일
/vworld_api/ api.vworld.kr 브이월드 API
/microapp-*/ 10.x.x.x:8080 타 MF 앱 프록시

Kubernetes 배포

# deploy/all-in-one/devops-*-web-master.yaml (마스킹)
spec:
  containers:
    - image: __IMAGE__
      name: <span class="blur">web-master</span>
      ports:
        - containerPort: 8081

10. Architecture vs Architecture2

동일 도메인의 두 버전이 공존하며, Architecture2가 확장 기능을 포함합니다.

기능Architecture (v1)Architecture2 (v2)

건물 CRUD
FloorDivision (층 분할)
Texture3D (텍스처)
UploadData (썸네일)
SHP → 3D Tiles 파이프라인
LayerRegis (레이어 등록)
Tileset / Entity 정리 (unmount) 기본 ✅ 고급 cleanup
deleteShpZip / deleteTilesFolder
Custom ModelPanel

Architecture2의 handleClose()는 MF 종료 시 Cesium Scene을 완전히 정리합니다.

const handleClose = () => {
  if (entity) window.cviewer.entities.remove(entity);
  if (polygon) window.cviewer.entities.remove(polygon);
  if (dataSource) window.cviewer.dataSources.remove(dataSource);
  if (geoJson) window.cviewer.dataSources.remove(geoJson);
  if (tileSet) window.cviewer.scene.primitives.remove(tileSet);
  if (shpUrl) deleteShpZip({ id: shpUrl });
  if (tilePath) deleteTilesFolder({ inpath: tilePath });
  onCloseMicroApp();
};

11. 기술적 인사이트 & 설계 결정

왜 Webpack UMD인가?

대안채택하지 않은 이유

Module Federation Host와 강한 Webpack 버전 종속
iframe Cesium Viewer 공유 불가, 성능 저하
Single SPA Cesium 생태계와 React 18 호환 이슈
UMD + Qiankun ✅ Host CDN 공유, 독립 배포, Cesium 글로벌 공유

Redux 단순화 패턴

복잡한 Redux Toolkit 대신 단일 SET_STATE 액션으로 모든 상태를 병합합니다.

case SET_STATE:
  return { ...state, ...action.payload };

MF 규모에서는 Boilerplate 최소화가 유지보수성에 유리합니다.

Cesium External 전략

Host 앱이 Cesium을 **글로벌 window.Cesium**으로 로드하면, MF는 @cesiumgs/cesium-analytics를 import하되 번들에는 포함되지 않습니다. window.cviewer는 Host가 생성한 Viewer 인스턴스를 MF가 공유합니다.

다국어 (i18n)

// locales/ko_KR.ts, en_US.ts, zh_CN.ts
<IntlProvider messages={message} locale={locale}>
  {intl.formatMessage({ id: 'FloorDivision.name9' })}
</IntlProvider>

Host로부터 lang prop을 받아 로케일을 동적으로 전환합니다.



기술 스택 요약

레이어기술버전

UI Framework React 18.2
UI Library Ant Design 4.24.7
State Redux + react-redux 5.0 / 9.1
3D Engine Cesium Analytics 1.115.0
Bundler Webpack 5.75
MF Framework Qiankun (via Umi plugin) 2.x
HTTP Client umi-request 1.4
GIS Utils Turf.js, proj4, wellknown 6.5 / 2.7 / 0.5
Charts ECharts, Ant Design Charts 5.4 / 1.3
i18n react-intl 6.2
Build Orchestration Turbo + Yarn Workspaces 1.9 / 1.22
Web Server Nginx (unprivileged) 1.0
Container Docker + Kubernetes
PWA Workbox (GenerateSW) 6.5

로컬 개발 가이드

# 모노레포 루트 — 의존성 설치
yarn install

# building3D 개발 서버 (port 8081)
cd projects/building3D
yarn start

# Production 빌드
yarn build

# Portal 환경 빌드
yarn build:lx-portal

Host 앱 연동 팁: 단독 실행 시 Cesium Viewer(window.cviewer)가 없으므로 일부 3D 기능은 동작하지 않습니다. Qiankun Host 앱 위에서 loadMicroApp으로 로드하거나, Dev 환경에서 Mock Viewer를 주입해야 합니다.


마치며

이 프로젝트는 3D GIS 플랫폼의 마이크로프론트엔드 모듈로서, Webpack UMD + Qiankun 패턴으로 독립 배포 가능성을 확보하면서, Cesium 기반 실시간 3D 프리뷰와 FME Server 기반 대용량 공간 데이터 변환 파이프라인을 하나의 UX로 통합했습니다.

특히 지형 샘플링 기반 건물 배치, 층별 ImageMaterialProperty 텍스처 매핑, SHP → CityGML → 3D Tiles End-to-End 변환은 공간정보 프론트엔드에서 재현 가능한 패턴으로, 유사 프로젝트의 아키텍처 레퍼런스로 활용할 수 있습니다.