Cesium JS에서 User Control 구현하기

이번시간에는 Cesium JS에서 User Controll을 구현하는 시간을 가져보도록 하겠습니다.

기능을 구현하여 라인과 폴리곤을 생성해보도록 하겠습니다.

 

먼저 결과 화면부터 확인하세요.

서비스바로가기

 

* 구현을 위해 작성한 소스코드입니다.

 
var handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
 
handler.setInputAction(function(event) {
    var earthPosition = viewer.scene.pickPosition(event.position);
        
        if (Cesium.defined(earthPosition)) {
            if (activeShapePoints.length === 0) {
                floatingPoint = createPoint(earthPosition);
                activeShapePoints.push(earthPosition);
                var dynamicPositions = new Cesium.CallbackProperty(function () {
                    return activeShapePoints;
                }, false);
                activeShape = drawShape(dynamicPositions);
            }
            activeShapePoints.push(earthPosition);
            createPoint(earthPosition);
        }
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
cs

EventHandler를 위한 handler 변수를 지정하였습니다.

마우스 좌클릭 이벤트를 통해 shape를 그릴 position을 지정합니다. 

 

 
handler.setInputAction(function(event) {
        if (Cesium.defined(floatingPoint)) {
            var newPosition = viewer.scene.pickPosition(event.endPosition);
            if (Cesium.defined(newPosition)) {
                floatingPoint.position.setValue(newPosition);
                activeShapePoints.pop();
                activeShapePoints.push(newPosition);
            }
        }
    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
cs

mouse move 이벤트로 포인트와 포인트 간 shape이 생성될 수 있게끔 하였습니다.

 

 
handler.setInputAction(function(event) {
        terminateShape();
    }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
cs

마우스 우클릭 이벤트를 통해 shape 그리기를 종료하고 새로운 shape를 생성할 수 있게끔 하였습니다. 

 

shape 그리기를 종료하고 새로운 shape를 그리기 위해 아래와 같은 function을 구현하였습니다.

 
function terminateShape() {
        activeShapePoints.pop();
        drawShape(activeShapePoints);
        viewer.entities.remove(floatingPoint);
        viewer.entities.remove(activeShape);
        floatingPoint = undefined;
        activeShape = undefined;
        activeShapePoints = [];
    }
cs

생성되고있던 shape와 찍혀있던 point를 remove함수로 삭제하였습니다. 

 

아래는 포인트와 포인트를 통해 생성되는 Line과 Polygon에 대한 function 입니다.

 
// 포인트 생성 
    function createPoint(worldPosition) {
        var point = viewer.entities.add({
            position : worldPosition,
            point : {
                color : Cesium.Color.RED,
                pixelSize : 10,
                heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
            }
        });
        return point;
    }
 
// 라인과 폴리곤 생성
    function drawShape(positionData) {
        var shape;
        if (drawingMode === 'line') {
            shape = viewer.entities.add({
                polyline : {
                    positions : positionData,
                    clampToGround : true,
                    width : 3
                }
            });
        }
        else if (drawingMode === 'polygon') {
            shape = viewer.entities.add({
                polygon: {
                    hierarchy: positionData,
                    material: new Cesium.ColorMaterialProperty(Cesium.Color.AQUA.withAlpha(0.15))
                }
            });
        }
        return shape;
    }
cs

포인트 생성을 위한 function 내에 point 옵션값 중 heightReference: Cesium.HeightReference.CLAMP_TO_GROUND 옵션은

포인트가 TerrainMap(지형형상 지도)를 인식하여 평면이 아닌 Terrain에 맞게 움직일 수 있게끔 하는 옵션입니다.

물론 라인과 폴리곤도 Terrain에 맞추어 그려집니다.

 

다음은 실행화면 입니다.

처음 실행화면 입니다. 서울 지역 중 평지와 산지가 공존하는 지역을 선정하였습니다.

 

라인과 폴리곤을 선택할 수 있는 select Box를 구현하였습니다.

각 option을 통해 라인과 폴리곤을 생성할 수 있습니다.

 

먼저 라인 그리기를 통해 라인을 그리는 모습입니다.

일직선으로 라인을 생성했지만 사진과 같이 선이 울퉁불퉁한 것을 느끼실 수 있을겁니다.

 

라인이 생성될 때 Terrain에 맞춰 생성되었기 때문이죠

 

폴리곤 또한 마찬가지 입니다.

폴리곤도 각 면이 울퉁불퉁하게 보입니다.

 

폴리곤도 Terrain에 맞춰 생성되었습니다.

 

shape를 만들었으니 삭제하는 기능도 있어야겠죠?

라인 그리기와 폴리곤 그리기로 shape를 생성한 후 화면정리를 선택하면 생성하였던 shape가 삭제됩니다.

select Box 안에 화면정리 기능도 넣어두었습니다.

 

화면정리를 선택하여 shape들을 삭제한 모습입니다.

 

Cesium JS에서 Camera FlyTo 기능 활용하기

이번 시간에는 FlyTo기능을 활용하여 대한민국 곳곳을 탐방하는 시뮬레이션을 구현해보도록 하겠습니다.

 * 본 게시물은 3D 모델들이 적용되어있습니다. 독자분들이 이와 같은 개발을 진행하시거나 둘러보실때에는 고사양의 PC를 활용하심이 훨씬 좋습니다.

   특히 그래픽카드가 고사양일 록 원활한 진행이 가능합니다.

 

먼저 결과 화면부터 확인하세요.

서비스바로가기

 

* Camera FlyTo에 대한 설명은 아래와 같습니다. (CesiumDocument 바로가기)

카메라가 기존 위치에서 새로운 위치로 비행한다 라는 설명을 하고있습니다. 

 

* 위 설명을 토대로 구현된 예제 소스코드 입니다.

* 첫번째 예제는 해당 위치로 비행하여 내려다 보겠다는 의미입니다. 

* 두번째 예제는 해당 위치를 정하지 않고 범위를 지정하여 그 범위를 내려다 보겠다는 의미입니다.

* 세번째 예제는 카메라가 지정 위치로 비행하기 위해 orientation을 활용하게 되고 direction과 up 옵션을 사용합니다.

   - 간략히 설명해 두 옵션을 이용해 카메라의 방향과 위치를 지정하는 것입니다.

* 네번째 예제는 orientation에 heading, pitch, roll 값을 지정하여 카메라의 각도와 방향을 지정하는 것입니다. 

 

=> 필자는 본 게시물(시뮬레이션)을 구현하기 위해 1번과 4번 예제를 참조하였습니다.

 

* 구현을 위해 사용한 소스코드

필자는 시뮬레이션 구현을 위해 아래와 같은 소스코드를 적용시켰습니다. 간단합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function tour(){
    setTimeout(function(){
        viewer.scene.camera.flyTo({
            destination : Cesium.Cartesian3.fromDegrees(127.78675436.643957600000.0)
        });
    },2000);
    
    setTimeout(function(){
        viewer.scene.camera.flyTo({
            destination : Cesium.Cartesian3.fromDegrees(128.07592933.014948500000.0),
            orientation : {
                heading : Cesium.Math.toRadians(0),
                pitch : Cesium.Math.toRadians(-420),
                roll : Cesium.Math.toRadians(0)
            }
        }); 
    },7000);
}
cs

setTimeout함수 내에 위에 설명하였던 flyTo 함수를 사용하였습니다. 

setTimeout은 시간지연함수로써 지정된 시간이 되었을 때, 실행이되게끔 하는 함수입니다. 

 

* 시뮬레이션 구현을 위한 사전준비

시뮬레이션 전, 필자는 시뮬레이션의 View를 위해 3D건물 모델과 DEM모델을 제작하였습니다. 

(각 모델들을 구현하기 위해 사용한 원천 데이터는 전부 "국가공간정보포털"에서 취득하였음을 알립니다.)

3D건물은 여의도와 성남시에, DEM모델은 제주도에 배치하였습니다.

아래 게시물 바로가기를 통해 각 제작방법을 살펴보시길 바랍니다.

1. Cesium Js와 Qgis를 활용한 3D 건물 구현하기

   > 바로가기

2. DEM - 국가공간정보포털 DEM 자료 활용하기 

   > 바로가기

 

* 구현된 시뮬레이션 확인

시작 화면의 모습입니다. 하단 둘러보기 버튼을 클릭하여 시뮬레이션을 시작할 수 있습니다.

 

시뮬레이션의 순서는 대한민국에서 출발하여 성남시, 여의도, 제주도, 마지막으로 최초 위치로 돌아오게 됩니다.

* 대한민국 전체를 살펴보는 모습입니다.

 


* 성남시를 살펴보는 모습입니다. 사진과 같이 3D 모델로 구현된 건물들을 볼 수 있습니다.

 

* 여의도를 살펴보는 모습입니다.

 

* 제주도를 살펴보는 모습입니다. 제주도에는 필자가 직접 제작한 DEM모델을 적용시켰습니다.

이번시간에는 Qgis를 통해 구축한 3D 건물데이터를 Cesium JS를 통해 맵 상에 구현해보도록 하겠습니다.
결과화면 보겠습니다.


서비스바로가기

먼저, Qgis를 통해 데이터를 생성하도록 하겠습니다. 
활용 데이터 : 국가공간정보포털 - 건물통합정보마스터 (업로드 용량제한으로 인해 예제파일을 첨부 못하는 점 양해바랍니다.)

데이터 중 여의도 지역만 추출하도록 하겠습니다. 
추출 방법은 다음과 같습니다. 

shift키를 누른 채 마우스 클릭 및 끌기를 하여 지역을 다중선택 합니다.

레이어 트리에서 해당 레이어를 "선택한 객체를 다른이름으로 저장"으로 저장합니다. (좌표계는 EPSG:4326)

플러그인을 이용해 추출한 여의도 지역을 3D로 변환시켜보겠습니다.
먼저, Qgis2threejs 플러그인을 설치해주세요. 설치 후 플러그인을 실행시킵니다.

앞서 추출하였던 여의도 지역이 3D로 변환된것을 볼 수 있습니다.

해당 레이어를 우클릭하여 properties를 연 후, 옵션을 설정합니다. 필자는 아래와 같이 설정하였습니다.

옵션 설정 후 그림과 같이 레이어를 GLTF 포맷으로 저장합니다. (Cesium JS는 기본적으로 GLTF 포맷을 사용합니다.)

 

이제 Cesium JS에 데이터를 적용시켜보겠습니다. 

데이터를 올렸지만 위와 같은 현상이 발생했습니다.

여기서 필자는 2가지 방법을 활용하여 데이터의 위치를 조정할 수 있었습니다.

* Cesium ION 활용


Cesium ION 사이트에 접속 후 Add data 버튼을 눌러 여의도 데이터를 Upload합니다.

Upload 버튼을 누릅니다.

업로드가 완료된 데이터를 확인하니 Tileset의 위치가 설정되어있지않다는 문구가 표출됩니다.
데이터 화면 위에 Adjust Tileset Location 버튼을 눌러 데이터의 위치 및 옵션을 설정해야 합니다.

도구들을 통해 설정을 해주도록 합니다.
search를 통해 데이터를 위치시켜주고 Rotation 메뉴를 통해 데이터의 방위를 맞춘 후 Scale을 통해 데이터의 크기를 설정하여 줍니다.
그리고 데이터에 위치해있는 파랑, 빨강, 초록선들을 활용하여 세부 위치를 조절합니다.

파란선은 높이를 빨간선과 초록선은 X, Y축이라 생각하시면 됩니다. 설정 후 Save 버튼을 눌러 저장 및 완료 합니다.

Cesium ION에서 작업 한 데이터를 맵 상에 올려보겠습니다. 사용한 소스코드는 아래와 같습니다.

var position = Cesium.Cartesian3.fromDegrees(126.926094, 37.526077, 0);

var tileset = viewer.scene.primitives.add(
	new Cesium.Cesium3DTileset({
		url: Cesium.IonResource.fromAssetId(*****),
		position : position
	})
); 

실행 화면입니다.

3D 건물이 맵 상에 표출된 모습입니다.

 

* 소스코드상에서 3D 모델의 위치를 조정

var headingPitchRoll = new Cesium.HeadingPitchRoll();
var fixedFrameTransform = Cesium.Transforms.localFrameToFixedFrameGenerator('south', 'east');

var model = scene.primitives.add(Cesium.Model.fromGltf({
	url : 'js/image/test.gltf',
	modelMatrix : Cesium.Transforms.headingPitchRollToFixedFrame(position, headingPitchRoll, Cesium.Ellipsoid.WGS84, fixedFrameTransform),
	scale : 37.5
	})
);

localFrameToFixedFrameGenerator 함수를 사용하였습니다.

요약하자면 원점의 프레임을 기준으로 firstAxis, secondAxis를 참고하여 프레임을 변환한다는 의미입니다.
firstAxis와 secondAxis의 의미는 east, north는 올리고 west, south는 내린다는 말로 둘의 설명이 같습니다.
즉, firstAxis와 secondAxis의 값에 따라 프레임의 위치가 변경되는 것입니다.

실행화면입니다.

위 사진과 같이 건물 데이터가 맵상에 올바르게 위치하였습니다.

Cesium JS를 활용한 운석 낙하 시뮬레이션 

이번 시간에는 Cesium JS를 활용하여 운석 낙하 시 피해 반경을 시뮬레이션 하겠습니다.

운석 낙하는 전 세계에서 가끔 일어나는 자연현상 입니다.  과거 2013년 러시아 첼랴빈스크 지역에 운석이 낙하하여 1600여명이 부상당하고 원화 기준으로 약 350억 원의 피해가 발생 했습니다.  떨어진 운석의 크기는 직경17m, 무게 1만 톤으로 추정되었고 대기권 돌입시 추정 속도는 초속 32.5km이었습니다. 

또한 2014년 03월 경상남도 진주시에 운석이 낙하 중 폭발하였고 그 조각들이 다음날로부터 발견된 사례가 있었습니다. 

<러시아 첼랴빈스크 지역에 운석이 낙하 중인 모습과 발견된 운석의 일부 (출처-Google image검색)>

<경남 진주시에 운석 조각이 낙하중인 모습과 발견된 운석 조각 (출처-Google image검색)>

이러한 사례와 참고자료를 발판삼아 가상으로 운석이 낙하하여 충돌할 시 피해 반경을 가늠하여 보았습니다.  

샘플 프로그램 제작 당시 여의도 지역을 운석 낙하 지점으로 설정했습니다. 막상 만들고 보니 저희 나라에 운석이 떨어지는 것이 별로 좋지 않더라구요. 그래서 일본의 모 지역으로 낙하 지점을 변경했습니다. 정확한 시뮬레이션도 아니고 그저 CesiumJS를 활용하는 한 가지 사례로써 어떤 의도가 존재하지도 않습니다.

먼저 결과화면 보겠습니다.

서비스바로가기

운석은 50m급, 100m급 두 등급으로 정했습니다. 각 등급에 근접한 피해 반경을 구하기 위해 여러가지 공식과 참고자료를 활용했습니다. 하지만 참고자료 또한 추정치여서 실제와는 차이가 있습니다.
우주환경 감시기관(한국천문연구원)에서 참고한 운석의 크기와 그에 따른 충돌에너지에 대한 자료입니다.


(출처 - 우주환경 감시기관)

운석 시작 높이는 지상 100km로 설정했습니다. 대부분의 유성 및 운석이 고도 80~100km 부분 열권에서 연소가 되며 연소되지못한 운석들이 지구로 추락합니다. 

낙하속도 산출은 자유낙하 속도 공식을 활용했습니다. (v: 속도 / g: 중력가속도 / t: 시간 / s: 높이) 

 

 

시뮬레이션

시뮬레이션은 총 3단계로 진행 됩니다. 

단계1. 운석 시뮬레이션 버튼을 클릭하여 시뮬레이션 시작
단계2. 운석이 지면에 추락한 후 피해반경 표출
단계3. 피해반경 전체를 가시화

먼저 시뮬레이션 동작을 위한 버튼입니다. 50m, 100m급 시뮬레이션 버튼으로 시뮬레이션을 진행 할 수 있으며 RESET버튼으로 각 시뮬레이션을 마친 후 맵을 초기화 할 수있습니다. (하나의 시뮬레이션 진행 후 RESET기능을 통해 맵을 초기화 하세요.)

 

50m급 시뮬레이션 장면입니다. (50m급의 피해 반경은 약 11km 입니다.)

 

운석이 낙하 지점을 향해 추락하는 모습입니다.

 

 

운석이 낙하지점에 추락 후 피해반경이 표출 된 모습입니다. (50m급 운석)

 

운석 피해 반경 전체를 볼 수 있게 가시화 된 모습입니다. 100m급 시뮬레이션 장면입니다. (100m급의 피해 반경은 약 1.100km입니다.) 

 

 

프로그램 소스

시작점과 도착점을 지정하여 운석이 움직일 수 있도록 하였습니다. 이번 시뮬레이션의 기초 및 핵심이 되는 부분입니다.

 
/*  
 pos1 - 출발점 지정
 pos2 - 도착점 지정
 */
var pos1 = Cesium.Cartesian3.fromDegrees(126.50874937.369581100000.0);
var pos2 = Cesium.Cartesian3.fromDegrees(126.91362037.5192960.0);
var position = new Cesium.SampledPositionProperty();
 
position.addSample(start, pos1);
position.addSample(stop, pos2);
cs

setTimeout 콜백함수를 활용하여 시간에따른 시뮬레이션을 진행하는 부분입니다.

 
// 운석이 도착점에 도달할 시 피해반경 표출
setTimeout(function(){
    viewer.entities.add({
        position : meteorPosition,
        ellipse : {
            semiMinorAxis : 11000.0
            semiMajorAxis : 11000.0
            material : Cesium.Color.RED.withAlpha(0.5)
        }
    });
},23000); 
// 피해반경 가시화 
setTimeout(function(){
    if(particleSystem != null){
        scene.primitives.remove(particleSystem);
    };
    var staticPosition = Cesium.Cartesian3.fromDegrees(139.74390335.69436360000);
    var orientation = new Cesium.HeadingPitchRange(0,300,0); 
    viewer.scene.camera.setView({
        destination : staticPosition,
        orientation : orientation,
        endTransform : Cesium.Matrix4.IDENTITY
    });
},26000);
cs

semiMinorAxis 와 semiMajorAxis는 지름을 지정하는 파라미터입니다. 단위는 m입니다.

 

마치며

본 시뮬레이션을 구현하는 작업은 흥미로웠습니다. 하지만 운석낙하에 대한 자료가 한참 부족하고 대부분 추정치만 존재하다보니 실제의 값에 근접한 값을 도출하는데 수 일의 노력을 기울였습니다.

하지만 프로그웍스와 독자분들을 위한 유령개구리의 노력은 멈추지 않을것입니다. 감사합니다~!

 

 

 

 

 

 

 

* 전체 소스코드 내용은 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<!DOCTYPE html>
<html lang='en'>
<meta charset="UTF-8">
<head>
<script src="js/Cesium-1.54/Build/Cesium/Cesium.js"></script>
<link href="js/Cesium-1.54/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
<style>
    @import url(js/Cesium-1.54/Apps/Sandcastle/templates/bucket.css);
</style>
<div id="cesiumContainer" style="width:auto; height:700px"></div>
<script>
var extent = Cesium.Rectangle.fromDegrees(117.89628431.499028139.59738043.311528);
 
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = extent;
Cesium.Camera.DEFAULT_VIEW_FACTOR = 0.7;
 
var viewer = new Cesium.Viewer('cesiumContainer',{
    timeline : false,
    animation : false,
    selectionIndicator : false,
    navigationHelpButton : false,
    infoBox : false,
    navigationInstructionsInitiallyVisible : false
});
 
var scene = viewer.scene;
// 위,경도 표출될 라벨의 옵션을 파라미터로 지정
var entity = viewer.entities.add({
    label : {
        show : true,
        showBackground : true,
        backgroundColor : Cesium.Color.BLACK,
        font : '25px sans-serif',
        horizontalOrigin : Cesium.HorizontalOrigin.LEFT,
        //verticalOrigin을 top으로 지정 
        verticalOrigin : Cesium.VerticalOrigin.TOP,
        //pixelOffset을 통해 label의 상세 위치를 지정 / Cartesian2 사용(x,y)
        pixelOffset : new Cesium.Cartesian2(150)
    }
});
// eventhandler 변수에 screenspacehandler를 담음
var eventhandler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
// movement가 발생하면 위,경도 값을 표출 
eventhandler.setInputAction(function(movement){
    var cartesian = viewer.camera.pickEllipsoid(movement.endPosition, scene.globe.ellipsoid);
    if(cartesian){
        var cartographic = Cesium.Cartographic.fromCartesian(cartesian);
        var longitude = Cesium.Math.toDegrees(cartographic.longitude).toFixed(2);
        var latitude = Cesium.Math.toDegrees(cartographic.latitude).toFixed(2);
        
        entity.position = cartesian;
        entity.label.show = true;
        entity.label.text = '경도 : '+('' + longitude).slice(-7+ '\u00B0' + '\n위도 : '+('' + latitude).slice(-7+ '\u00B0';
    }else{
        entity.label.show = false;
    }
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
</script>
</head>
<body>
</body>
</html>
cs

 

* 28 ~ 40행은 위,경도를 표출할 라벨의 옵션을 key : value 값, 파라미터로 지정한 부분입니다.

 
var entity = viewer.entities.add({
    label : {
        show : true,
        showBackground : true,
        backgroundColor : Cesium.Color.BLACK,
        font : '25px sans-serif',
        horizontalOrigin : Cesium.HorizontalOrigin.LEFT,
        //verticalOrigin을 top으로 지정 
        verticalOrigin : Cesium.VerticalOrigin.TOP,
        //pixelOffset을 통해 label의 상세 위치를 지정 / Cartesian2 사용(x,y)
        pixelOffset : new Cesium.Cartesian2(150)
    }
});
cs

 

* VerticalOrigin에 대한 설명입니다. (자세한 내용은 CesiumDocument를 참고하세요.)

각 포지션을 통해 라벨이 표출되는 위치를 조정할 수 있습니다.

 

* Cartesian2에 대한 설명입니다. (자세한 내용은 CesiumDocument를 참고하세요.)

Cartesian2는 2차원적 개념으로 x, y 값을 갖게됩니다.

 

* 44 ~ 57행은 MouseMove 이벤트를 활용하여 movement가 발생함에 따라 위,경도 값이 표출되게끔 하는 부분입니다.

 
eventhandler.setInputAction(function(movement){
    var cartesian = viewer.camera.pickEllipsoid(movement.endPosition, scene.globe.ellipsoid);
    if(cartesian){
        var cartographic = Cesium.Cartographic.fromCartesian(cartesian);
        var longitude = Cesium.Math.toDegrees(cartographic.longitude).toFixed(2);
        var latitude = Cesium.Math.toDegrees(cartographic.latitude).toFixed(2);
        
        entity.position = cartesian;
        entity.label.show = true;
        entity.label.text = '경도 : '+('' + longitude).slice(-7+ '\u00B0' + '\n위도 : '+('' + latitude).slice(-7+ '\u00B0';
    }else{
        entity.label.show = false;
    }
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
cs

cartographic변수에 cartesian을 활용한 위,경도 값을 담아줍니다.

 

* Cartographic에 대한 설명입니다. (자세한 내용은 CesiumDocument를 참고하세요.)

기본적으로 Cartesian3(x,y,z)값을 갖게되며 기본 투영체 및 좌표계로 WGS84를 사용한다는 것을 알 수 있습니다.

 

결과 화면 보겠습니다.

 

라벨이 마우스를 따라다니며 위,경도를 알려주는 것을 볼 수 있습니다. 제주 한라산의 위,경도는 사진과 같군요 

이번 시간에는 Cesium JS에서 gltf 포맷을 이용한 가상의 시나리오를 만들어 보겠습니다.

시나리오는 마포대교 위에 비행하는 드론이 실시간으로 교통상황을 알리는 내용입니다.

 

HeadingPitchRange 설명 (CesiumDocument)

HeadingPitchRange는 시각 및 관점에 대한 값을 정의하는것입니다.

Heading은 방향을 나타냅니다. 360'와 0' 그리고 프레임 중심을 기준으로 양의 값일때에는 동쪽, 음의 값일때에는 서쪽으로 각이 변경됩니다.

 

Pitch는 각을 나타냅니다. 여기서의 각은 평면(x,y)을 기준으로 양의 값일때에는 올라가고 음의 값일때에는 내려가게됩니다.

 

 

Range는 거리를 나타냅니다. 프레임 중심으로부터의 거리를 조절할 수 있습니다. 

 

상기 내용은 필자가 기본값(0.0)을 토대로 heading, pitch, roll 각각 임의의 값을 넣어서 테스트 한 것임을 알려드립니다.  

 

결과부터 보시겠습니다.

서비스바로가기

 

* 이번 게시물에서 필자가 중요하다고 생각하는 소스코드입니다. 

 
/*************
    heading: Heading변경 슬라이드 값 - 0~360까지의 값 
    pitch: Pitch변경 슬라이드 값- 0~150까지의 값
    range: Range변경 슬라이드 값 - 0~60까지의 값
    **************/    
    var staticPosition = Cesium.Cartesian3.fromDegrees(126.93320937.515165,2200);
    var orientation = new Cesium.HeadingPitchRange(heading, pitch, range);
    viewer.scene.camera.setView({
        // staticPosition -- (126.933209, 37.515165, 2200) <- 마포대교를 바라보기 위한 위치(좌표) 
        destination : staticPosition,    
        // orientation -- heading, pitch, range 값 
        orientation : orientation                
    });
cs

staticPosition이라는 변수에 마포대교를 바라보는 관점 즉, 위치를 지정합니다.

유저 컨트롤을 통해 획득한 값을 활용하여 관점을 변경할 수 있습니다.

 

* 결과화면을 보겠습니다. 마포대교 모델 상공에 드론 모델이 비행하는 모습을 볼 수 있습니다.

 

 

마포대교를 클릭 했을 시 차량 수와 교통 수준 정도가 조건을 토대로 랜덤하게 표출되는것을 볼 수 있습니다.

 

유저 컨트롤을 통해 heading, pitch, range 값을 조절할 수 있습니다. 슬라이드를 통해 각 값을 조절하고 변경 버튼을 통해 관점을 변경할 수 있습니다.

 

heading에 임의의 값을 주었더니 관점이 변경된 것을 볼 수 있습니다. 독자 여러분도 슬라이드와 버튼을 통해 관점을 변경하여 보세요.

Tip. 관점의 변경은 구체(원)를 생각하시면 이해하기 쉽습니다. 

Cesium JS Feature Picking 활용하기

이번 시간에는 CesiumJS에서 glTF 형식의 3D모델을 불러온 후 마우스 이벤트를 연동하겠습니다. 먼저 결과를 확인해보세요. (여의도로 가세요.)

서비스바로가기

 

* 전체 소스코드 내용은 다음과 같습니다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
<!DOCTYPE html>
<html lang='en'>
<meta charset="UTF-8">
<head>
<script src="js/Cesium-1.54/Build/Cesium/Cesium.js"></script>
<link href="js/Cesium-1.54/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
<style>
    @import url(js/Cesium-1.54/Apps/Sandcastle/templates/bucket.css);
</style>
<div id="cesiumContainer" style="width:auto; height:700px;"></div>
<script>
var extent = Cesium.Rectangle.fromDegrees(117.89628431.499028139.59738043.311528);
 
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = extent;
Cesium.Camera.DEFAULT_VIEW_FACTOR = 0;
 
var viewer = new Cesium.Viewer('cesiumContainer', {
     timeline : false,
     animation : false,
     selectionIndicator : false,
     navigationHelpButton : false,
     infoBox : false,
     navigationInstructionsInitiallyVisible : false   
});
 
// Gltf포맷 활용가능하게함.
var scene = viewer.scene;
var position = Cesium.Cartesian3.fromDegrees(126.93451137.5210040);
var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(position);
// fromGltf 함수를 사용하여 key : value 값으로 요소를 지정
var name = '프로그APT'
var model = viewer.scene.primitives.add(Cesium.Model.fromGltf({
    url : 'js/image/cesium.gltf',
    modelMatrix : modelMatrix,
    scale : 5000.0,
    name : name
}));
viewer.scene.primitives.add(model);
 
// nameoverlay를 위한 설정
var nameOverlay = document.createElement('div');
viewer.container.appendChild(nameOverlay);
nameOverlay.className = 'backdrop';
nameOverlay.style.display = 'none';
nameOverlay.style.position = 'absolute';
nameOverlay.style.bottom = '0';
nameOverlay.style.left = '0';
nameOverlay.style['pointer-events'= 'none';
nameOverlay.style.padding = '4px';
nameOverlay.style.backgroundColor = 'black';
 
// feature select 
var selected = {
    feature : undefined,
    originalColor : new Cesium.Color()
};
 
// 모델을 select하기위한 부분
var selectedEntity = new Cesium.Entity(model);
 
// 클릭핸들러 변수선언
var clickHandler = viewer.screenSpaceEventHandler.getInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
 
if(Cesium.PostProcessStageLibrary.isSilhouetteSupported(viewer.scene)){
    // 블루, 그린 실루엣 지원
    var silhouetteBlue = Cesium.PostProcessStageLibrary.createEdgeDetectionStage();
    silhouetteBlue.uniforms.color = Cesium.Color.BLUE;
    silhouetteBlue.uniforms.length = 0.01;
    silhouetteBlue.selected = [];
    
    var silhouetteGreen = Cesium.PostProcessStageLibrary.createEdgeDetectionStage();
    silhouetteGreen.uniforms.color = Cesium.Color.LIME;
    silhouetteGreen.uniforms.length = 0.01;
    silhouetteGreen.selected = [];
    
    viewer.scene.postProcessStages.add(Cesium.PostProcessStageLibrary.createSilhouetteStage([silhouetteBlue, silhouetteGreen]));
    // mousemove function을 이용하여 모델을 select 후 배열에 담음
    viewer.screenSpaceEventHandler.setInputAction(function onMouseMove(movement){
        silhouetteBlue.selected = [];
    // 모델에 마우스가 move될 시 nameoverlay 출현    
    var pickedFeature = viewer.scene.pick(movement.endPosition);
    if(!Cesium.defined(pickedFeature)){
        nameOverlay.style.display = 'none';
        return;
    }
    // nameOverlay 설정
    nameOverlay.style.display = 'block';
    nameOverlay.style.bottom = viewer.canvas.clientHeight - movement.endPosition.y + 'px';
    nameOverlay.style.left = movement.endPosition.x +'px';
    
    nameOverlay.textContent = name;
    
    if(pickedFeature !== selected.feature){
        silhouetteBlue.selected = [model];
    }
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
    
    // 모델을 클릭하여 알럿창을 띄우기위한 부분 / function onLeftClick
    viewer.screenSpaceEventHandler.setInputAction(function onLeftClick(movement){
        // silhouetteGreen 변수에 담을것
        silhouetteGreen.selected = [];
        
        // 새로운 모델 선택
        var pickedFeature = viewer.scene.pick(movement.position);
        if(!Cesium.defined(model)){
            clickHandler(movement);
            return;
        }
        
        // 선택된 모델이 없으면 새로운 모델 선택 가능
         if(silhouetteGreen.selected[model] === pickedFeature){
             return;
        } 
         alert("이곳은" +name+ "입니다." +'\n'+ "높이 : "+"68.7"+"m" +'\n'+ "넓이 : "+"30.2"+"m" +'\n'+ "가구 수 : " +"1000"+ "세대");
         
        // 샌택된 모델의 기존색상 저장(파란색)
        var highlightedFeature = silhouetteBlue.selected[model];
        if(model === highlightedFeature){
            silhouetteBlue.selected = [];
        }
        
        // 새로 선택된 모델 highlight
        silhouetteGreen.selected = [model];
        
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
    
}
</script>
</head>
<body>
</body>
</html>
cs

 

40 ~ 50 행 : 모델로 마우스가 이동했을 시 모델의 이름을 표출할 nameOverlay에 대한 설정입니다.

 
// nameoverlay를 위한 설정
var nameOverlay = document.createElement('div');
viewer.container.appendChild(nameOverlay);
nameOverlay.className = 'backdrop';
nameOverlay.style.display = 'none';
nameOverlay.style.position = 'absolute';
nameOverlay.style.bottom = '0';
nameOverlay.style.left = '0';
nameOverlay.style['pointer-events'= 'none';
nameOverlay.style.padding = '4px';
nameOverlay.style.backgroundColor = 'black';
cs

 

65 ~ 96행 : 블루, 그린 실루엣을 사용하기위한 변수를 선언하고 onMouseMove function을 활용하기위한 부분입니다. 

 
// 블루, 그린 실루엣 지원
    var silhouetteBlue = Cesium.PostProcessStageLibrary.createEdgeDetectionStage();
    silhouetteBlue.uniforms.color = Cesium.Color.BLUE;
    silhouetteBlue.uniforms.length = 0.01;
    silhouetteBlue.selected = [];
    
    var silhouetteGreen = Cesium.PostProcessStageLibrary.createEdgeDetectionStage();
    silhouetteGreen.uniforms.color = Cesium.Color.LIME;
    silhouetteGreen.uniforms.length = 0.01;
    silhouetteGreen.selected = [];
    
    viewer.scene.postProcessStages.add(Cesium.PostProcessStageLibrary.createSilhouetteStage([silhouetteBlue, silhouetteGreen]));
    // mousemove function을 이용하여 모델을 select 후 배열에 담음
    viewer.screenSpaceEventHandler.setInputAction(function onMouseMove(movement){
        silhouetteBlue.selected = [];
    // 모델에 마우스가 move될 시 nameoverlay 출현    
    var pickedFeature = viewer.scene.pick(movement.endPosition);
    if(!Cesium.defined(pickedFeature)){
        nameOverlay.style.display = 'none';
        return;
    }
    // nameOverlay 설정
    nameOverlay.style.display = 'block';
    nameOverlay.style.bottom = viewer.canvas.clientHeight - movement.endPosition.y + 'px';
    nameOverlay.style.left = movement.endPosition.x +'px';
    
    nameOverlay.textContent = name;
    
    if(pickedFeature !== selected.feature){
        silhouetteBlue.selected = [model];
    }
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
cs

* MouseMove 이벤트에 대한 설명 (Cesium Document를 통해 자세한 내용을 살펴보세요.)

마우스의 움직임으로 이벤트를 구현할 수 있습니다.

 

98 ~ 125행 : onLeftClick 함수를 사용하여 새로운 모델 선택 및 모델 실루엣 색상을 변경하기위한 부분입니다. 

 
// 모델을 클릭하여 알럿창을 띄우기위한 부분 / function onLeftClick
    viewer.screenSpaceEventHandler.setInputAction(function onLeftClick(movement){
        // silhouetteGreen 변수에 담을것
        silhouetteGreen.selected = [];
        
        // 새로운 모델 선택
        var pickedFeature = viewer.scene.pick(movement.position);
        if(!Cesium.defined(model)){
            clickHandler(movement);
            return;
        }
        
        // 선택된 모델이 없으면 새로운 모델 선택 가능
         if(silhouetteGreen.selected[model] === pickedFeature){
             return;
        } 
         alert("이곳은" +name+ "입니다." +'\n'+ "높이 : "+"68.7"+"m" +'\n'+ "넓이 : "+"30.2"+"m" +'\n'+ "가구 수 : " +"1000"+ "세대");
         
        // 샌택된 모델의 기존색상 저장(파란색)
        var highlightedFeature = silhouetteBlue.selected[model];
        if(model === highlightedFeature){
            silhouetteBlue.selected = [];
        }
        
        // 새로 선택된 모델 highlight
        silhouetteGreen.selected = [model];
        
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
cs

* LeftClick 이벤트에 대한 설명 (Cesium Document를 통해 자세한 내용을 살펴보세요.)

마우스 좌클릭을 통해 선택, 드래그 등의 이벤트를 구현할 수 있습니다.

 

결과 화면을 보겠습니다. 

마우스를 모델에 올렸을 시 모델의 테두리 색상이 파란색으로 변경되고 nameoverlay가 활성화 된 것을 볼 수 있습니다.

 

 

모델을 클릭했을 시 Alert창에 모델의 이름과 임의로 지정한 값을 알립니다.

 

모델을 클릭 후 테두리 색상에 변경된것을 확인할 수 있습니다. 선택했던 모델에 재차 마우스를 올려도 nameOverlay가 활성화됩니다.

 

이번 시간에는 3D Builder를 통한 GLTF포맷 데이터 생성 및 이를 Cesium JS에 활용하는 방법을 공유하겠습니다.
먼저 결과를 확인해보세요. (여의도로 가세요.)

서비스바로가기

 

* glTF 포맷이란?

JSON 표준을 사용하는 3D장면 및 모델의 파일 형식이며 3D장면의 크기를 축소하고 런타임 처리를 최소화하는 

효율적이고 상호 운용 가능한 자산 전달 형식입니다. 

> 바로가기 : glTF Overview (https://www.khronos.org/gltf/)

 

* 3D Builder 사용하기

1. Window '검색'을 통해 3D Builder를 검색하세요. 

Tip. Window에 3D Builder가 기본 제공 되어있지 않은 경우 "MicroSoft Stroe"를 통해 다운로드 받으시길 바랍니다.

2. 3D Builder를 실행한 후 새 장면을 클릭합니다.

Tip. 각 기능에 대한 설명은 "3D Builder 사용자 가이드"를 참고하세요.

3. 3D 모델 생성 및 텍스쳐 입히기

우선, 정육면체 1개를 추가해봅시다. 

 

 

그리고 그리기 메뉴에서 텍스쳐 탭을 클릭 후 임의의 텍스쳐를 입혀봅니다.

 

 

저장을 합니다.

Tip. 저장시에는 파일형식을 "glTF format"형식으로 해야합니다. 

사진과 같이 gltf, bin 형식의 파일 그리고 설정한 텍스쳐 파일이 저장되었습니다.

 

필자는 아래 사진과 같은 3D Model을 생성하였습니다.

 

 

* 소스코드 작성하기 

glTF 포맷을 사용하기 위한 소스코드

 
// Gltf포맷 사용을 위한 변수들 선언
var scene = viewer.scene;
var position = Cesium.Cartesian3.fromDegrees(126.93451137.5210040);
var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(position);
 
// fromGltf 함수를 사용하여 key : value 값으로 요소를 지정
scene.primitives.add(Cesium.Model.fromGltf({
    url : 'js/image/cesium.gltf'// gltf포맷의 위치 
    modelMatrix : modelMatrix,
    scale : 5000.0
}));
cs

 

전체 소스코드는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!DOCTYPE html>
<html lang='en'>
<meta charset="UTF-8">
<head>
<script src="js/Cesium-1.54/Build/Cesium/Cesium.js"></script>
<link href="js/Cesium-1.54/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
<title>Make Building</title>
<div id="cesiumContainer" style="width:auto; height:925px;"></div>
<script>
var extent = Cesium.Rectangle.fromDegrees(117.89628431.499028139.59738043.311528);
 
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = extent;
Cesium.Camera.DEFAULT_VIEW_FACTOR = 0;
 
var viewer = new Cesium.Viewer('cesiumContainer', {
     timeline : false,
     animation : false,
     selectionIndicator : false,
     navigationHelpButton : false,
     infoBox : false,
     navigationInstructionsInitiallyVisible : false   
});
 
// Gltf포맷 사용을 위한 변수들 선언
var scene = viewer.scene;
var position = Cesium.Cartesian3.fromDegrees(126.93451137.5210040);
var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(position);
// fromGltf 함수를 사용하여 key : value 값으로 요소를 지정
scene.primitives.add(Cesium.Model.fromGltf({
    url : 'js/image/cesium.gltf'// Gltf포맷의 위치
    modelMatrix : modelMatrix,
    scale : 5000.0
}));
</script>
</head>
<body>
</body>
</html>
cs

 

결과 화면은 다음과 같습니다. 

필자는 여의도 지역의 일부를 생성하였습니다.

이번 시간에는 Cesium JS를 활용한 비행 애니메이션을 활용하도록 하겠습니다.

 

결과를 먼저 확인해보세요.

서비스바로가기

 

1.  애니메이션 기능의 비행시간과 항공기가 비행할 비행구역을 생성하기위한 소스코드입니다.

 
viewer.scene.globe.enableLighting = true;
 
viewer.scene.globe.depthTestAgainstTerrain = true//지형 생성 관련
 
Cesium.Math.setRandomNumberSeed(3);  
 
// 시작, 종료 날짜 설정
var start = Cesium.JulianDate.fromDate(new Date(20192810)); 
var stop = Cesium.JulianDate.addSeconds(start, 360new Cesium.JulianDate());
 
// viewer 작동 시간 확인
viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = start.clone();
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; // loop 종료
viewer.clock.multiplier = 10;
 
// 항공기 비행 구역 설정 
function computeCircularFlight(lon, lat, radius){
    var property = new Cesium.SampledPositionProperty();
     
    for(var i=0; i<=360; i+=45){
        var radians = Cesium.Math.toRadians(i); // 범위 설정
        var time = Cesium.JulianDate.addSeconds(start, i, new Cesium.JulianDate()); // 시간 설정
        var position = Cesium.Cartesian3.fromDegrees(lon + (radius * 1.5 * Math.cos(radians)), // 비행구역 위치 설정 
                       lat + (radius * Math.sin(radians)),
                       Cesium.Math.nextRandomNumber() * 500+1000);
        property.addSample(time, position);
        // 비행구역 구분점 표시
        viewer.entities.add({
            position : position, // position = 비행구역 위치
            point : {
                pixelSize : 8,
                color : Cesium.Color.YELLOW,
                outlinecolor : Cesium.Color.YELLOW,
                outlineWidth : 3
            }
        });
    }
    return property;
}
cs

* for문 내에 비행 시간과 비행 구역을 설정하는 계산은 매우 복잡합니다. Cesium Document를 통해 자세한 내용을 살펴보세요.

 

2. 항공기를 생성하고 비행구역의 색상 및 두께 등의 세부 설정을 합니다.

 
// 항공기 생성
var entity = viewer.entities.add({
    availability : new Cesium.TimeIntervalCollection([new Cesium.TimeInterval({
        start : start,
        stop : stop
    })]),
    
    position : position, 
    
    orientation : new Cesium.VelocityOrientationProperty(position),
    model : {
        uri : "js/Cesium-1.53/Apps/SampleData/models/CesiumAir/Cesium_Air.gltf",
        minimumPixelSize : 64
    },
    
    path : {
        resolution : 1,
        material : new Cesium.PolylineGlowMaterialProperty({
            glowPower : 0.1,
            color : Cesium.Color.YELLOW
        }),
        width : 10
    }
});
cs

 

3. <body>태그 내에 항공기 관점을 변경하기 위한 버튼과 onclick 이벤트를 구현합니다.

 
<body>
<div id="toolbar">    
    <h5>항공기 관점 변경하기</h5>
    <div id="camcontrol">
        <input type="button" class="cesium-button" value="상공에서 바라보기" onclick="lookSkyview();">
        <input type="button" class="cesium-button" value="측면에서 바라보기" onclick="lookSideview();">
        <input type="button" class="cesium-button" value="항공기 바라보기" onclick="lookAirport();">
    </div>        
</div>
</body>
cs

 

4. onclick 이벤트를 구현할 function들을 작성합니다.

 
// ====================== 카메라 시점 변경 부분========================== 
// 상공에서 바라보기
function lookSkyview(){
    var transform = Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(126.92440337.524624));
    var camera = viewer.camera;
    
    camera.constraintedAxis = Cesium.Cartesian3.UNIT_Z;
    camera.lookAtTransform(transform, new Cesium.Cartesian3(-10000.0-10000.025000.0));
    
    viewer.trackedEntity = undefined;
    viewer.zoomTo(viewer.entities, new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-90)));
}
 
// 측면에서 바라보기
function lookSideview(){    
    viewer.trackedEntity = undefined;
    viewer.zoomTo(viewer.entities, new Cesium.HeadingPitchRange(Cesium.Math.toRadians(-90),
    Cesium.Math.toRadians(-15), 8000));
}
 
// 항공기 바라보기
function lookAirport(){
    viewer.trackedEntity = entity;
}
cs

 

전체 소스코드는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
<!DOCTYPE>
<html>
<meta charset="UTF-8">
<head>
<script src="js/Cesium-1.53/Build/Cesium/Cesium.js"></script>
<link href="js/Cesium-1.53/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
 
<style>
    #toolbar{
        padding : 1px;
        border-redius : 4px;
    }
    #toolbar input{
        vertical-align : middle;
        padding-top : 2px;
        padding-bottom : 2px;
    }
    #toolbar .header{
        font-weight : bold;
    }
</style>
 
<div id="cesiumContainer" style="width:auto; height:620px;"></div>
<script>
var extent = Cesium.Rectangle.fromDegrees(117.89628431.499028139.59738043.311528);
 
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = extent;
Cesium.Camera.DEFAULT_VIEW_FACTOR = 0;
 
var viewer = new Cesium.Viewer('cesiumContainer',{
    timeline : false,
    selectionIndicator : false,
    navigationHelpButton : false,
    infoBox : false,
    navigationInstructionInitiallyVisible : false,
    baseLayerPicker : true,
    shouldAnimate : true//animation 가능 
    terrainProvider : Cesium.createWorldTerrain() // 기본 지도를 지형지도로 셋팅
});    
 
viewer.scene.globe.enableLighting = true;
 
viewer.scene.globe.depthTestAgainstTerrain = true//지형 생성 관련
 
Cesium.Math.setRandomNumberSeed(3);  
 
// 시작, 종료 날짜 설정
var start = Cesium.JulianDate.fromDate(new Date(20192810)); 
var stop = Cesium.JulianDate.addSeconds(start, 360new Cesium.JulianDate());
 
// viewer 작동 시간 확인
viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = start.clone();
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; // loop 종료
viewer.clock.multiplier = 10;
 
// 항공기 비행 구역 설정 
function computeCircularFlight(lon, lat, radius){
    var property = new Cesium.SampledPositionProperty();
     
    for(var i=0; i<=360; i+=45){
        var radians = Cesium.Math.toRadians(i); // 범위 설정
        var time = Cesium.JulianDate.addSeconds(start, i, new Cesium.JulianDate()); // 시간 설정
        var position = Cesium.Cartesian3.fromDegrees(lon + (radius * 1.5 * Math.cos(radians)), // 비행구역 위치 설정 
                       lat + (radius * Math.sin(radians)),
                       Cesium.Math.nextRandomNumber() * 500+1000);
        property.addSample(time, position);
        // 비행구역 구분점 표시
        viewer.entities.add({
            position : position, // position = 비행구역 위치
            point : {
                pixelSize : 8,
                color : Cesium.Color.YELLOW,
                outlinecolor : Cesium.Color.YELLOW,
                outlineWidth : 3
            }
        });
    }
    return property;
}
 
var position = computeCircularFlight(126.92440337.5246240.03); // 비행구역 중간점 설정
 
var entity = viewer.entities.add({availability : new Cesium.TimeIntervalCollection([new Cesium.TimeInterval({
        start : start,
        stop : stop
    })]),
    
    position : position, 
    
    orientation : new Cesium.VelocityOrientationProperty(position),
    model : {
        uri : "js/Cesium-1.53/Apps/SampleData/models/CesiumAir/Cesium_Air.gltf",
        minimumPixelSize : 64
    },
    
    path : {
        resolution : 1,
        material : new Cesium.PolylineGlowMaterialProperty({
            glowPower : 0.1,
            color : Cesium.Color.YELLOW
        }),
        width : 10
    }
});
 
// ====================== 카메라 시점 변경 부분========================== 
// 상공에서 바라보기
function lookSkyview(){
    var transform = Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(126.92440337.524624));
    var camera = viewer.camera;
    
    camera.constraintedAxis = Cesium.Cartesian3.UNIT_Z;
    camera.lookAtTransform(transform, new Cesium.Cartesian3(-10000.0-10000.025000.0));
    
    viewer.trackedEntity = undefined;
    viewer.zoomTo(viewer.entities, new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-90)));
}
 
// 측면에서 바라보기
function lookSideview(){    
    viewer.trackedEntity = undefined;
    viewer.zoomTo(viewer.entities, new Cesium.HeadingPitchRange(Cesium.Math.toRadians(-90),
    Cesium.Math.toRadians(-15), 8000));
}
 
// 항공기 바라보기
function lookAirport(){
    viewer.trackedEntity = entity;
}
 
</script>
</head>
<body>
<div id="toolbar">    
    <h5>항공기 관점 변경하기</h5>
    <div id="camcontrol">
        <input type="button" class="cesium-button" value="상공에서 바라보기" onclick="lookSkyview();">
        <input type="button" class="cesium-button" value="측면에서 바라보기" onclick="lookSideview();">
        <input type="button" class="cesium-button" value="항공기 바라보기" onclick="lookAirport();">
    </div>        
</div>
</body>
</html>
cs

 

소스코드 작성이 완료되었으니 실행시켜보겠습니다. (비행구역은 여의도 상공으로 지정하였습니다.)

1. 최초 실행 시의 모습입니다. 

화면 좌하단에 애니메이션 기능이 추가되었으며 각 버튼을 통해 실행, 중지가 가능합니다.  

또한 애니메이션 사이드 부분 화살표를 활용하여 항공기의 속도를 조절할 수 있습니다.

 

2. 상공에서 바라본 모습입니다.

 

3. 측면에서 바라 본 모습입니다.

 

4. 항공기를 바라 본 모습입니다.

 

Cesium JS에서 시점 변경 기능 활용하기

이번엔 Cesium JS를 활용하여 시점 변경 기능을 만들어 보도록하겠습니다.

서비스바로가기


1. 카메라 시점 변경 기능을 위한 "range" Element 구성     


<!DOCTYPE html>
<html lang="en">
<meta charset = "UTF-8">
<head>
    <script src="js/Cesium-1.53/Build/Cesium/Cesium.js"></script>
    <link href="js/Cesium-1.53/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body>
<div id="cesiumContainer" style="width:auto; height:600px;"></div>
 
<div>
    <h2>카메라 시점 변경하기</h2>
    <input type="range" id="controllbar" name="range_value" min="500" max="15000" step="100" onchange="lookAtTransform(value)">
</div>
 
</body>
</html>
cs

- 최소값을 500 , 최대값을 15000으로 설정하고 step을 100으로 잡아 한번의 이동 시 100만큼의 값이 조절되도록 하였습니다.

- onchange함수를 설정하여줍니다.


실행화면은 다음과 같습니다.


2. script 태그 안에 아래와 같은 소스코드를 적용합니다.


<script type="text/javascript">
    var extent = Cesium.Rectangle.fromDegrees(117.89628431.499028139.59738043.311528);
  
    Cesium.Camera.DEFAULT_VIEW_RECTANGLE = extent;
    Cesium.Camera.DEFAULT_VIEW_FACTOR = 0.7;
 
    Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkOGMxZjMzNy01N2FkLTQ3YTctODU0NS05NGY0MmE3MGJiOWEiLCJpZCI6NjExNywic2NvcGVzIjpbImFzciIsImdjIl0sImlhdCI6MTU0NTI3OTE3NH0.6VdcqK6vL7faWx_vYkkOuNNa8dapTn1geCi7qYBhGCw';
    var viewer = new Cesium.Viewer('cesiumContainer',
        {
            timeline : false,
            animation : false,
            selectionIndicator : false,
            navigationHelpButton : false,
            infoBox : false,
            navigationInstructionsInitiallyVisible : false   
        });
    
    //카메라 시점 조절하기 
    function lookAtTransform(value){
        var transform = Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(126.92342837.524969)); // 카메라 시점이 적용될 위,경도         
        var camera = viewer.camera; //camera 선언
        
        camera.constrainedAxis = Cesium.Cartesian3.UNIT_Z;
        camera.lookAtTransform(transform, new Cesium.Cartesian3(-10000.0-10000.0, value)); // 시점 변경을 위한 초기범위 설정    
    }
</script>
cs

"value"는 input태그 안 controllbar의 움직임에 따라 고도(각도)값이 변경됩니다. 


전체 소스코드는 아래와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html>
<html lang="en">
<meta charset = "UTF-8">
<head>
    <script src="js/Cesium-1.53/Build/Cesium/Cesium.js"></script>
    <link href="js/Cesium-1.53/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body>
<div id="cesiumContainer" style="width:auto; height:600px;"></div>
 
<div>
    <h2>카메라 시점 변경하기</h2>
    <input type="range" id="controllbar" name="range_value" min="500" max="15000" step="100" onchange="lookAtTransform(value)">
</div>
 
<script type="text/javascript">
    var extent = Cesium.Rectangle.fromDegrees(117.89628431.499028139.59738043.311528);
  
    Cesium.Camera.DEFAULT_VIEW_RECTANGLE = extent;
    Cesium.Camera.DEFAULT_VIEW_FACTOR = 0.7;
 
    Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkOGMxZjMzNy01N2FkLTQ3YTctODU0NS05NGY0MmE3MGJiOWEiLCJpZCI6NjExNywic2NvcGVzIjpbImFzciIsImdjIl0sImlhdCI6MTU0NTI3OTE3NH0.6VdcqK6vL7faWx_vYkkOuNNa8dapTn1geCi7qYBhGCw';
    var viewer = new Cesium.Viewer('cesiumContainer',
        {
            timeline : false,
            animation : false,
            selectionIndicator : false,
            navigationHelpButton : false,
            infoBox : false,
            navigationInstructionsInitiallyVisible : false   
        });
    
    //카메라 시점 조절하기 
    function lookAtTransform(value){
        var transform = Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(126.92342837.524969));          
        var camera = viewer.camera;
        
        camera.constrainedAxis = Cesium.Cartesian3.UNIT_Z;
        camera.lookAtTransform(transform, new Cesium.Cartesian3(-10000.0-10000.0, value));        
    }
</script>
</body>
</html>        
cs


완성된 최저, 중간, 최상으로 움직였을때의 화면입니다.

 - 자세한 기능을 보기위해 실행 화면 우 상단 baseLayer Map을 "Terrain Map"으로 변경합니다.

사진과 같이 controllbar 조절에 따라 시점이 변경되는 것을 볼 수 있습니다.

서비스바로가기

+ Recent posts