Unity에서 SUMO 도로 네트워크(.net.xml) 시각화하기
SUMO로 도로네트워크 시뮬레이션을 위해 도로 정보를 lane(차선) 단위로 분석·시각화하려고 합니다. 하지만 SUMO에서 자체적으로 변환한 GeoJSON 파일은 edge(도로 중심선)만 추출해주고, 실제 각 차선(lane) 단위의 라인은 표현되지 않습니다. 그래서 net.xml을 직접 파싱해서 lane별 GeoJSON을 만드려고 합니다.
이전 글 보기
[디지털 트윈] Unity에서 지형을 3D로 시각화하기 - 수치 지형도/정사 영상 편
Unity에서 지형을 3D로 시각화하기 - 수치 지형도/정사 영상 편 디지털 트윈 프로젝트를 준비하며, 웹 브라우저 상에서 3D 지형을 구현해보려 합니다. 이를 위해 지형, 건물, 도로 등 다양한 공간 정
dachaes-devlogs.tistory.com
[디지털 트윈] Unity에서 건물을 3D로 시각화하기
Unity에서 건물을 3D로 시각화하기 디지털 트윈 프로젝트를 준비하며, 웹 브라우저 상에서 3D 지도를 구현해보려 합니다. 이를 위해 지형, 건물, 도로 등 다양한 공간 정보를 시각화할 계획입니다.
dachaes-devlogs.tistory.com
[디지털 트윈] Unity에서 SUMO 차량(fcd.xml) 시뮬레이션하기
Unity에서 SUMO 차량(fcd.xml) 시뮬레이션하기 앞서 도시 모빌리티·환경 시뮬레이션을 위해 QGIS를 이용해 지형/건물/도로 데이터를 가상 환경에 올리는 작업을 진행했습니다. 이번에는 SUMO에서 제공
dachaes-devlogs.tistory.com
개발 환경
- Node v22.16.0
- React v19.1.0 (TypeScript) + Vite
- QGIS Desktop 3.44.0
- Unity 6000.0.50f1 (LTS)
개발 순서
- SUMO net.xml 파일 파싱하기
- 도로 레이어 위치 맞추기
- 도로 레이어 꾸미기
- glTF 파일로 내보내기 (export)
1. SUMO net.xml 파일 파싱하기
SUMO net.xml에는 lane(차선) 정보와 각 차선의 shape 좌표가 들어 있습니다. 이 좌표를 각각 LineString으로 변환하여GeoJSON 파일로 저장할 수 있습니다. SUMO에서 GeoJSON 파일로 변환해 주는 기능이 있으나, 이 파일은 edge(도로 중심선)만 추출해주고, 실제 각 차선(lane) 단위의 라인은 표현되지 않습니다.
a. net.xml의 좌표계 정보 읽기
net.xml에는 보통 아래와 같은 location 정보가 있습니다.
<location netOffset="-295519.28,-4128274.41" projParameter="+proj=utm +zone=52 +ellps=WGS84 +datum=WGS84 +units=m +no_defs"/>
- projParameter를 보면 이 도로네트워크의 좌표계는 UTM 52N (EPSG:32652)임을 알 수 있습니다.
- netOffset은 모든 shape 좌표에 더하거나 빼서 실제 UTM 52N 좌표계로 변환해주는 값입니다.
b. pyproj를 활용한 좌표계 변환
Python의 pyproj 라이브러리를 활용하여 UTM 52N(32652) → WGS84(4326) 또는 UTM 52N(32652) → TM서부(5186) 등으로 변환합니다.
c. 파싱 코드 (임시)
import xml.etree.ElementTree as ET
import json
import os
def netxml_to_geojson_file(xml_file_path, geojson_file_path):
from pyproj import CRS, Transformer
SRC_EPSG = 32652 # UTM 52N
DST_EPSG = 4326 # WGS84(경위도)
src_crs = CRS.from_epsg(SRC_EPSG)
dst_crs = CRS.from_epsg(DST_EPSG)
transformer = Transformer.from_crs(src_crs, dst_crs, always_xy=True)
tree = ET.parse(xml_file_path)
root = tree.getroot()
# 1. netOffset 추출
loc_tag = root.find('.//location')
netOffset_x, netOffset_y = 0.0, 0.0
if loc_tag is not None and 'netOffset' in loc_tag.attrib:
netOffset_x, netOffset_y = map(float, loc_tag.get('netOffset').split(','))
# 2. 모든 Lane 순회 후 저장
lane_features = []
for lane in root.findall('.//lane'):
# 2-1. id, shape만 파싱
id = lane.get('id')
shape = lane.get('shape')
if not shape:
continue
# 2-2. id 변환: 맨 앞 ':' 제거, 마지막 _숫자 제거
converted_id = id.lstrip(':')
if '_' in converted_id:
converted_id = '_'.join(converted_id.split('_')[:-1])
# 2-3. shape 변환
coords = []
for pt in shape.strip().split(' '):
parts = pt.split(',')
x = float(parts[0])
y = float(parts[1])
z = float(parts[2]) if len(parts) > 2 else 0.0
# netOffset 적용
real_x = x - netOffset_x
real_y = y - netOffset_y
lon, lat = transformer.transform(real_x, real_y)
coords.append([lon, lat, z])
# 2-4. 저장
lane_features.append({
"type": "Feature",
"geometry": {"type": "LineString", "coordinates": coords},
"properties": {
"id": converted_id,
"index": lane.get('index'),
"length": lane.get('length'),
"speed": lane.get('speed')
}
})
with open(geojson_file_path, 'w', encoding='utf-8') as f:
json.dump({
"type": "FeatureCollection",
"features": lane_features
}, f, ensure_ascii=False, indent=2)
if __name__ == '__main__':
xml_file = './250709/Ansan_HaeAnRo.net.xml'
geojson_file = './250709/Ansan_HaeAnRo.geojson'
if not os.path.isfile(xml_file):
print(f"입력 파일({xml_file})이 현재 폴더에 없습니다.")
else:
print("변환 중입니다!")
netxml_to_geojson_file(xml_file, geojson_file)
print(f"변환 완료! 결과: {geojson_file}")
SUMO의 netconvert로 만든 GeoJSON은 보통 EPSG:4326(경위도) 좌표계를 사용하기 때문에 QGIS 같은 GIS 소프트웨어에 바로 올려도 위치가 잘 맞습니다. 하지만 net.xml을 직접 파싱해서 GeoJSON을 만들면, 결과가 엉뚱한 위치로 나오는 경우가 많습니다.
이런 문제가 발생하는 이유는 net.xml에서 사용하는 좌표계가 UTM이나 TM 같은 지역 좌표계인 데다, 각 도로 shape 좌표가 netOffset만큼 평행이동되어 저장되기 때문입니다. SUMO의 내부 변환툴(netconvert)은 이 오프셋(netOffset)과 좌표계 변환을 자동으로 처리해주지만, 직접 파싱할 때는 오프셋 보정이 빠져서 위치가 어긋나는 일이 흔하게 생깁니다. 따라서 net.xml을 직접 파싱해서 GeoJSON을 만들 때는 반드시 netOffset 값을 빼준 뒤, pyproj 같은 도구로 EPSG:4326(경위도)나 EPSG:5186(한국 TM서부) 등 원하는 좌표계로 변환해줘야 합니다.
2. 도로 레이어 위치 맞추기
도로 레이어를 확대해서 자세히 보면, 파싱된 도로 선이 한 축을 중심으로 미묘하게 틀어져있습니다. 아마 SUMO와 QGIS의 OSM 차이 때문에 발생하는 문제인 것 같습니다.
공간 처리 툴박스 > 벡터 지오메트리 > 회전(rotate) 을 눌러서 벡터 지오메트리 - 회전(rotate) 창을 엽니다. 입력 레이어에 도로 네트워크를, 회전 기준 포인트는 원하는 회전 축 위치를 고릅니다. 회전량은 여러번의 테스트를 통해 알맞는 값을 찾습니다. 산출물을 저장하고 싶다면, 파일로 저장으로 저장합니다.
3. 도로 레이어 꾸미기
도로 레이어를 정말 도로로 보이도록 속성 창에 들어가서 심볼의 색상(#686f6e)과 투명도를 조절하고 너비와 단위를 변경해줍니다. 수치는 각자 프로젝트의 정사 영상 사진(또는 위성 사진)에 맞추어 조정합니다.
도로의 윤곽에 흰색 라인(#f1f1f1)을 표현해주기 위해, 심볼에 라인을 두 개 더 추가합니다.
4. glTF 파일로 내보내기 (export)
상단 탭의 플러그인 > 플러그인 관리 및 설치 창을 엽니다. 플러그인 창에서 Qgis2threejs을 설치합니다. 설치가 완료되면 상단 탭의 웹 > Qgis2threejs > Qgis2threejs Exporter 를 열고, DEM을 선택합니다.
원하는 구획만 export하고 싶다면, Scene > Scene Settings 를 누르고, Base Extent > Fixed extent > Select > Use Layer Extent 에서 원하는 크기의 레이어를 선택합니다. 또는 Base Extent > Fixed extent > Select > Select Extent on Canvas 를 선택하여 원하는 크기를 직접 드래그합니다.
DEM을 우클릭하여 properties 창을 엽니다. 배경으로 쓸 DEM과 정사 영상 레이어, 도로 레이어를 선택합니다. 그리고 Polygon에 건물 레이어도 선택합니다.
원하는 레이어를 모두 설정했다면, building되는 것을 완전히 기다리고 export를 합니다. export는 File > Save Scene As > glTF (.gltf, .glb) 로 할 수 있습니다.
5. 유니티에 불러오기
우선 유니티 프로젝트를 열고, 상단 탭에서 Window > Package Manager 를 엽니다. 추가(+) > 패키지 이름으로 설치(install package by name) 를 선택하여 com.unity.cloud gltfast를 설치합니다.
폴더에 gltf 파일을 드래그하고, 변환된 파일을 씬에 드래그하면 유니티에 성공적으로 올라간 것을 확인할 수 있습니다.
'개발 기록 > [CTSG] 25.06.01-' 카테고리의 다른 글
[디지털 트윈] Unity에서 SUMO 차량(fcd.xml) 시뮬레이션하기 (4) | 2025.07.01 |
---|---|
[디지털 트윈] Unity에서 건물을 3D로 시각화하기 (1) | 2025.06.27 |
[디지털 트윈] Unity에서 지형을 3D로 시각화하기 - DEM 편 (0) | 2025.06.24 |
[디지털 트윈] Unity에서 지형을 3D로 시각화하기 - 수치 지형도/정사 영상 편 (0) | 2025.06.24 |
[디지털 트윈] React와 CesiumJS로 3D 건물 시각화하기 (0) | 2025.06.11 |