netcdf 다차원 정보를 Cesium에서 3차원 시각화하기

DHL
7 min readJun 26, 2023

--

개요

NetCDF나 Grib2 데이터에는 다차원 정보를 담을 수 있습니다.

https://thlee33.medium.com/xarray%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%8B%A4%EC%B0%A8%EC%9B%90-%EC%8B%9C%EA%B3%B5%EA%B0%84-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%8B%9C%EA%B0%81%ED%99%94-202303-31120618e0d3

이번에는 netcdf의 3차원(경도, 위도, level-높이) 정보를 Cesium.js를 이용하여 3차원 시각화 테스트해봤습니다.

데이터

좌표와 높이, 측정값이 포함된 데이터를 구글링하다가 경도, 위도, 높이 Level, 측정값이 들어있는 netcdf를 찾았습니다.

데이터 가공

Cesium.js 시각화 및 이를 위한 데이터 가공에서는 ChatGPT의 도움을 많이 받았습니다.

Cesium.js에서 netcdf를 바로 쓰기 어렵고 json으로 변환된 정보를 이용하는 게 좋겠다는 권고와 제안 코드를 이용하여 파이썬에서 netcdf를 json으로 변환하였습니다.

# 필요한 패키지 로딩 
import numpy as np
import xarray as xr
import json

# NetCDF 또는 GRIB2 파일을 로드합니다.
ds = xr.open_dataset("data/ta_Amon_GFDL-ESM2G_historical_r1i1p1_200101-200512.nc")

# 각 차원 및 온도 값들을 1차원 배열 형태로 받음
lon = ds.variables['lon'][:]
lat = ds.variables['lat'][:]
level = ds.variables['plev'][:]
air = ds.variables['ta'][:][0, :, :]

# 데이터를 JSON 형식으로 변환
data = []

for i in range(len(level)):
for j in range(len(lat)):
for k in range(len(lon)):
air_value = air[i,j,k]
# NaN 값을 걸러냄
if not np.isnan(air_value):
data_point = {"level": int(level[i]), "lat": float(lat[j]), "lon": float(lon[k]), "air": float(air_value)}
data.append(data_point)

# JSON 파일로 저장
with open('air_data.json', 'w') as outfile:
json.dump(data, outfile)

시각화

아래와 같이 html을 작성합니다. head에서는 cesium.js를 로딩합니다. 아래는 body 부분만 담았습니다.

<body>
<div id="cesiumContainer"></div>
<script>

Cesium.Ion.defaultAccessToken = '세슘 토큰';

var viewer = new Cesium.Viewer('cesiumContainer', {
});

// JSON 파일에서 데이터 불러오기
fetch('data/air_data.json')
.then(function(response) {
return response.json();
})
.then(function(jsonData) {
// 최소, 최대 air 값을 구하기
var minAir = jsonData[0].air;
var maxAir = jsonData[0].air;
for (var i = 1; i < jsonData.length; i++) {
var airValue = jsonData[i].air;
if (airValue < minAir) {
minAir = airValue;
} else if (airValue > maxAir) {
maxAir = airValue;
}
}

// 각 데이터 포인트를 Cesium의 Entity로 추가
for (var i = 0; i < jsonData.length; i++) {
var dataPoint = jsonData[i];

// air 값에 따른 색상 계산
var airRatio = (dataPoint.air - minAir) / (maxAir - minAir);
var color = new Cesium.Color(airRatio, 0, 1 - airRatio, 1);

viewer.entities.add({
position : Cesium.Cartesian3.fromDegrees(dataPoint.lon, dataPoint.lat, dataPoint.level),
point : {
pixelSize : 20,
color : color,
outlineColor : Cesium.Color.BLACK,
outlineWidth : 0,
heightReference : Cesium.HeightReference.RELATIVE_TO_GROUND,
material: new Cesium.ColorMaterialProperty(color)
},
air: dataPoint.air
});

}
});

viewer.camera.flyTo({
destination : Cesium.Cartesian3.fromDegrees(123, 31, 150000),
orientation : {
heading : Cesium.Math.toRadians(40), // 동서방향 각도, 동쪽부터 시계방향으로 각도 지정
pitch : Cesium.Math.toRadians(-20.0), // 위아래 각도, 수평선 위가 양수, 아래가 음수
roll : 0.0 // 카메라 회전 각도, 시계방향이 양수
}
});

</script>
</div>
</body>
netcdf의 경도, 위도, 레벨별 측정값을 3차원 포인트로 시각화

동일한 데이터를 3D Box를 이용하면 cube와 유사한 형태로 표시할 수도 있습니다.

동일한 데이터를 3D Box로 시각화

정리

netcdf를 세슘에서 3차원 시각화하는 것을 테스트해봤습니다. 더 나아가서는 시간 차원(time)의 변화까지 반영하여 4차원 시각화도 가능할 것으로 보여집니다.

--

--