Qgis 사용자 정의 좌표계 사용하기 

이번 시간에는 shp파일을 사용하여 사용자 정의 좌표계를 지정하는 방법을 공유하겠습니다.

필자는 Qgis 3.4 버전에서 작업하였음을 알립니다.

 

작업에 활용한 데이터는 국가공간정보포털에서 다운로드 한 데이터 입니다. (국가공간정보포털-오픈마켓 바로가기)

데이터 - (연속주제)_환경/기타용도지역지구  :  

LSMD_CONT_UM101_강원.zip
다운로드

 

다운로드한 데이터를 Qgis에 업로드 하고 좌표계를 EPSG:5174로 지정합니다. 

* 좌표계 변환 방법 참고 

서비스바로가기

 

좌표계를 지정한 데이터를 확인합니다.

사진과 같이 데이터와 지도 간 이격이 있는 것을 볼 수 있습니다.

 

사용자 지정 좌표계 생성하기 

EPSG:5174 좌표계로 지정하였음에도 불구하고 이격이 발견된것은 5174좌표계의 투영체계수값을 추가하지 않아서 입니다.

링크된 게시물을 참고하여 EPSG:5174의 투영체계수를 추가하세요.

* 사용자 지정 좌표계 설정 참고

서비스바로가기

 

 

tpwgs84 추가 proj4text 

+proj=tmerc +lat_0=38 +lon_0=127.0028902777778 +k=1 +x_0=200000 +y_0=500000 +ellps=bessel +units=m +no_defs +towgs84=-115.80,474.99,674.11,1.16,-2.31,-1.63,6.43

 

사용자 지정 좌표계 정의 방법은 아래의 순서로 진행됩니다.

우선, 메뉴 -> 설정 -> 사용자 지정 투영체 의 순서를 진행합니다.

그리고, "+버튼"을 눌러 새로운 좌표계를 생성 후 투영체계수를 넣어주고 확인을 누릅니다. 

 

사용자 지정 좌표계를 활용하여 다시 좌표계를 지정합니다.

사진과 같이 이격이 사라진것을 볼 수 있습니다.

이번 시간에는 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. 항공기를 바라 본 모습입니다.

 

안녕하세요 캡틴개구리입니다.

이번 D3 라이브러리를 이용하기위해 구글링을 하던 중 제가 느낀점은 한국에서 제공하는 데이터를 이용한 예제가 찾을 수 없었다는 점입니다.... 대부분 미국이었습니다. 

그래서 이번에 국가공간정보포털에서 제공하는 데이터와 VWORLD 지도를 이용하여 한국 예제를 만들었습니다. 

먼저 Openlayers3와 Leaflet에서 D3 TopoJson 이용하기 결과화면부터 보겠습니다.

서비스바로가기


Openlayers3, Leaflet, D3, D3 TopoJson 라이브러리를 받을 수 있는 곳은 아래와 같습니다. 

필수 라이브러리

OpenLayers3 :  https://openlayers.org/ 

Leaflet : http://leafletjs.com/

D3 : https://d3js.org/d3.v3.min.js

TopoJson : https://d3js.org/topojson.v1.min.js


참고사항 : D3 라이브러리 상위버전이 존재합니다만... 상위버전 라이브러리를 이용할 경우... openlayers3에서 TopoJson을 제대로 가져오지 못하였습니다......머리속에 물음표만 나오지만 저는Openlayers3 example에서 사용한 버전의 라이브러리를 이용하였습니다. (위에 표시된 라이브러리 이용하시면 정상적으로 지도에 데이터를 표출 하실 수 있습니다.)


선택 라이브러리

VWORLD(배경지도) :  http://map.vworld.kr



D3.js는 Data-Driven Documents의 약어로 데이터를 렌더링할 수 있는(그릴 수 있는, 다룰수 있는) 웹 기반 문서 라이브러리로 간단하게 설명하면 데이터 시각화 라이브러리입니다. 또한 D3에서는 SVG, GeoJSON, TopoJSON 등의 포맷을 이용하실 수 있습니다.


오늘은 TOPOJSON을 이용하여 Openlayers3(이하 Ol3)와 Leaflet 지도위에 데이터를 올려보도록 하겠습니다. 



1. TOPOJSON 데이터 생성


D3라이브러리를 이용하기 위해서는 TOPOJSON 형태의 데이터가 필요합니다.


먼저 국가공간정보포털 오픈마켓(http://data.nsdi.go.kr/dataset)에서 연속지적도_서울 데이터를 다운로드 후, 여의도 부분만을 GeoJSON으로 변환하였습니다. 


데이터  : 연속지적도_전국 > LSMD_CONT_LDREG_서울.zip


좌표계 : EPSG : 5174 

 * 참고사항 : 좌표계를 정확하게 하기위해 저는 QGIS에서 제공하는 5174를 사용하지 않고 사용자정의 5174를 생성하여 사용하였습니다.(https://www.osgeo.kr/17) QGIS에서 제공하는 좌표계와 OSGeo 지부에서 제공하는 좌표계의 임계값이 미세하게 차이가 있어서 사용자정의로 좌표계를 생성하였습니다.


GeoJSON 추출 좌표계 : WGS84 EPSG 4326


간단한 QGIS 사용법은 국가공간정보포털 > 자료실에 매뉴얼이 존재합니다.




QGIS를 이용하여 저장한 GeoJSON을 위에서 언급한 TopoJSON으로 변경합니다. 구글에서 GeoJSON to TopoJSON을 검색하셔서 이용하셔도  되고 위에 언급해드린 링크를 이용하여 바로 접속하셔도 됩니다. 개인적으로 변경하는 방법이 있으시다면 그것도 좋습니다.


TopoJSON으로 데이터를 변경하셨다면 기본작업은 끝이났습니다.


2. 라이브러리 추가


이제 OL3와 Leaflet에서 D3를 이용하기 위해 라이브러리를 추가해보도록 하겠습니다. 추가하는 라이브러리는 아래와 같습니다. 


1
2
3
4
5
6
<link type="text/css" rel="stylesheet" href="<c:url value='/css/openlayers/ol.css'/>" />
<link type="text/css" rel="stylesheet" href="<c:url value='/css/leaflet/leaflet.css'/>" />
<script src="<c:url value='/js/openlayers/ol.js'/>"></script>
<script src="<c:url value='/js/leaflet/leaflet.js'/>"></script>
<script src="<c:url value='/js/D3/d3.v3.min.js'/>"></script>
<script src="<c:url value='/js/D3/topojson.v1.min.js'/>"></script>
cs


3. Openlayers3


OL3에서 D3를 이용하기위해서 제가 참고한 자료는 Openlayers3 example에 있는 d3 Integration입니다. 먼저 <script></script>부분을 먼저 보겠습니다.


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
<script>
    //지도띄우기
    var olMap8 = new ol.Map({
        layers: [
            new ol.layer.Tile({
                source: new ol.source.XYZ({
                    url: 'http://xdworld.vworld.kr:8080/2d/Base/201802/{z}/{x}/{y}.png'
                })
            })
        ],
       
        target: 'olMap8',
       
        view: new ol.View({
             center: [14129600.824512500.74],
            maxZoom: 19,
            zoom: 14
        })
    });
    var topoUrl8 = "/js/data/topojson/progworks_yeouido_4326.json";
    
    d3.json(topoUrl8, function(error, topology) {
        var features = topojson.feature(topology, topology.objects.collection);
  
        console.log("Ol3_topojson", topology)
        
        var canvasFunction = function(extent, resolution, pixelRatio,size, projection) {
            var canvasWidth = size[0];
            var canvasHeight = size[1];
 
            var canvas = d3.select(document.createElement('canvas'));
            canvas.attr('width', canvasWidth).attr('height', canvasHeight);
 
            var context = canvas.node().getContext('2d');
 
            var d3Projection = d3.geo.mercator().scale(1).translate([00]);
            var d3Path = d3.geo.path().projection(d3Projection);
 
            var pixelBounds = d3Path.bounds(features);
            var pixelBoundsWidth = pixelBounds[1][0- pixelBounds[0][0];
            var pixelBoundsHeight = pixelBounds[1][1- pixelBounds[0][1];
 
            var geoBounds = d3.geo.bounds(features);
            var geoBoundsLeftBottom = ol.proj.transform(geoBounds[0], 'EPSG:4326', projection);
            var geoBoundsRightTop = ol.proj.transform(geoBounds[1], 'EPSG:4326', projection);
            var geoBoundsWidth = geoBoundsRightTop[0- geoBoundsLeftBottom[0];
            if (geoBoundsWidth < 0) {
                geoBoundsWidth += ol.extent.getWidth(projection.getExtent());
            }
            var geoBoundsHeight = geoBoundsRightTop[1- geoBoundsLeftBottom[1];
 
            var widthResolution = geoBoundsWidth / pixelBoundsWidth;
            var heightResolution = geoBoundsHeight / pixelBoundsHeight;
            var r = Math.max(widthResolution, heightResolution);
            var scale = r / (resolution / pixelRatio);
 
            var center = ol.proj.transform(ol.extent.getCenter(extent),projection, 'EPSG:4326');
            d3Projection.scale(scale).center(center).translate([canvasWidth / 2, canvasHeight / 2]);
            d3Path = d3Path.projection(d3Projection).context(context);
            d3Path(features);
            context.stroke();
 
            return canvas[0][0];
        };
 
        var layer = new ol.layer.Image({
            source: new ol.source.ImageCanvas({
                canvasFunction: canvasFunction,
                projection: 'EPSG:3857'
            })
        });
        
        olMap8.addLayer(layer);
        
    });
</script>
cs


3행 ~ 19행 : OL3 지도부분입니다.

4행 : 저는 WVORLD 배경지도 사용

14행 ~17행 : 지도 중심, 최대확대 및 지도 로딩 레벨 설정

20행 : TopoJSON 데이터의 경로

22행 : d3.json( TopoJSON경로, function(error, TopoJSON의 Type)


위 그림이 제가 사용한 TopoJSON 데이터입니다. "type" : "Topology" 부분을 TopoJSON Type부분에 입력하시면됩니다. 

몇몇 다른 예제에서는 설명이 없이 값이 다 다르게 들어가 있어서 무엇을 입력해야되나 고민하다가 제가 생각한 결론은 TopoJSON의 Type 이름을 넣자 였습니다... 


27행 ~ 64행 : OL3에서 D3의 TopoJSON을 사용할 수 있도록 변경 및 canvasFunction 변수에 값 지정

66행 ~ 73행 : D3 라이브러리를 통해 변경한 데이터를 OL3 라이브러리를 이용하여 OL3 map에 add

OL3에서 D3 라이브러리를 이용하실때 가장 신경쓰셔야할 부분이 22행 부분이라 생각됩니다. 나머지는 example에 있는부분이라 별도의 변경이 필요없습니다만 22행 부분은 사용자에 맡게 변경이 필요한 부분이기 때문입니다.


다음은 <body></body>입니다.

1
2
3
<body>
    <div class="olMap" id="olMap8"></div>
</body>
cs


저의 경우, 여러개의 OL3와 Leaflet 작업을 하나의 HTML에서 진행하기 때문에 클래스를 넣었지만 <div></div>안에 id만 넣으셔도 무방합니다.


4. Leaflet

Leaflet 홈페이지에 가시면 geoJSON을 바로 이용할 수 있는 방법도 있습니다. 참고해보세요 Leaflet은 OL3와 비교하여 소스가 간단합니다. 먼저 <script></script>부분을 살펴보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script>
//leaflet 지도 띄우기
    var letMap8 = L.map('letMap8').setView([37.52470308242787126.9234],14)
    
    //Vworld Tile 변경
    L.tileLayer('http://xdworld.vworld.kr:8080/2d/Base/201802/{z}/{x}/{y}.png').addTo(letMap8);
    
    //geoJson
    layer8 = L.geoJson(null, { style: { color: '#333', weight: 1 }})
    //geoJson map add
    letMap8.addLayer(layer8)
    
    //TopoJSON 
    var topoUrl8 = "/js/data/topojson/progworks_yeouido_4326.json";
 
    //d3활용 topoJson 파일 불러오기 및 geoJson 전달
    d3.json(topoUrl8, function(error, topology) {
        var topoYeouido8 = topojson.feature(topology, topology.objects.collection)
        layer8.addData(topoYeouido8);
        console.log("Leaflet_topojson", topology)
    })
</script>
cs


Leaflet에서는 TopoJSON 데이터를 적용하기위해서는 결론적으로 GeoJSON으로 변경하여 add시킵니다. 다시말해 굳이 TopoJSON으로 변경할 필요가 없다는겁니다.... 전 OL3를 다 끝내고 알았습니다.......

3행 : Leaflet 지도 올리기

6행 : VWORLD로 지도 변경

9행 : GeoJSON 선언 및 스타일 지정

11행 : Leaflet 지도에 GeoJSON 데이터 add

14행 : TopoJSON 데이터 경로

17행 ~ 21행 : D3 라이브러리를 이용하여 TopoJSON 데이터를 읽어오고 9행으로 값 전달

17행 : OL3 부분에서 언급한것과 동일합니다. d3.json( TopoJSON경로, function(error, TopoJSON의 Type)


다음은 <body></body>부분입니다.

1
2
3
<body>
    <div class="letMap" id="letMap8"></div>
</body>
cs


5. OL3와 Leaflet에서 D3 TopoJSON 올리기 결과화면

서비스바로가기



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 조절에 따라 시점이 변경되는 것을 볼 수 있습니다.

서비스바로가기

Cesium JS에서 3D 모델 생성하기

오늘은 Cesium JS를 활용하여 3D 모델을 생성해 보도록 하겠습니다.

서비스바로가기


1. 3D 모델을 생성 할 버튼 및 onclick 이벤트 생성


<!DOCTYPE html>
<html lang="en">
<meta charset = "UTF-8">
<head>
    <script src="js/Cesium-1.53/Build/Cesium/Cesium.js"></script>
    <script src="js/jquery/jquery-3.3.1.min.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>3차원 모델 만들기</h2>
    <input type="button" id="boxbutton" value="3D 상자" onclick="makeBox();">
    <input type="button" id="cylinderbutton" value="3D 원기둥" onclick="makeCylinder();">
</div>
 
</body>
</html>
cs


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


2. body태그 안 script 태그 안에 makeBox 함수 생성


<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       
        });
    
    // 3D 상자 생성하기
    function makeBox(){
         var box = viewer.entities.add({
                name : 'Box',
                position : Cesium.Cartesian3.fromDegrees(126.92440337.524624255.0), //상자가 생성될 위,경도 및 지면과의 이격(클수록 이격이 큼)
                box : {
                    dimensions : new Cesium.Cartesian3(500.0500.0500.0), // 상자의 크기 설정
                    material : Cesium.Color.WHITE, // 상자의 색 설정
                    outline : false// 외곽선 설정
                    outlinecolor : Cesium.Color.BLACK // 외곽선 색 설정
                }
         }); 
         // 상자 생성 시 확대 함   
         viewer.zoomTo(viewer.entities);
         alert('3D 상자를 생성합니다.');
    }
</script>
</body>
</html>
cs


3D 상자 버튼을 눌렀을 시의 화면 입니다.


3. body태그 안 script 태그 안에 makeCylinder 함수 생성


<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       
        });
    
     //3D 원기둥 생성하기
    function makeCylinder(){
        var cylinder = viewer.entities.add({
            name : 'Cylinder',
            position : Cesium.Cartesian3.fromDegrees(126.90719537.526650255.0),
            cylinder : {
                length : 490// 원기둥의 길이 설정
                topRadius : 200
                bottomRadius : 200//원기둥의 위,아래 넓이를 설정
                material : Cesium.Color.WHITE,
                outline : false,
                outlinecolor : Cesium.Color.BLACK
            }
        });
        viewer.zoomTo(viewer.entities);
        alert('3D 원기둥을 생성합니다.');
    }
</script>
</body>
</html>
cs


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
<!DOCTYPE html>
<html lang="en">
<meta charset = "UTF-8">
<head>
    <script src="js/Cesium-1.53/Build/Cesium/Cesium.js"></script>
    <script src="js/jquery/jquery-3.3.1.min.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>3차원 모델 만들기</h2>
    <input type="button" id="boxbutton" value="3D 상자" onclick="makeBox();">
    <input type="button" id="cylinderbutton" value="3D 원기둥" onclick="makeCylinder();">
</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       
        });
    
    // 3D 상자 생성하기
    function makeBox(){
         var box = viewer.entities.add({
                name : 'Box',
                position : Cesium.Cartesian3.fromDegrees(126.92440337.524624255.0),
                box : {
                    dimensions : new Cesium.Cartesian3(500.0500.0500.0),
                    material : Cesium.Color.WHITE,
                    outline : false,
                    outlinecolor : Cesium.Color.BLACK
                }
         });     
         viewer.zoomTo(viewer.entities);
         alert('3D 상자를 생성합니다.');
    }
    
    //3D 원기둥 생성하기
    function makeCylinder(){
        var cylinder = viewer.entities.add({
            name : 'Cylinder',
            position : Cesium.Cartesian3.fromDegrees(126.90719537.526650255.0),
            cylinder : {
                length : 490,
                topRadius : 200,
                bottomRadius : 200,
                material : Cesium.Color.WHITE,
                outline : false,
                outlinecolor : Cesium.Color.BLACK
            }
        });
        viewer.zoomTo(viewer.entities);
        alert('3D 원기둥을 생성합니다.');
    }
    
</script>
</body>
</html>
 
cs


완성된 실행화면입니다.

서비스바로가기

공공 부문 클라우드 컴퓨팅 필요성

비용 효율성이 높기 때문이 아닌 클라우드라서 가능한 사용사례를 제시합니다.


1. 공간정보 및 공간정보 활용 도구를 제공하는 수단

  • 공간정보(지적 등 측량정보 포함)는 초기 구축 비용이 비교적 많이 소요되고 공공부문에서 주도적으로 생산될 수 밖에 자료 특성을 가지고 있음
    ==> 클라우드를 통해 즉시 이용 가능한 형식으로 수요처에 제공

  • 공간정보 응용 시스템의 경우 일반 정보시스템 대비 기반환경 구축비용이 비교적 많이 소요됨
    ( 데이터 프로바이더 : Spatial Data Extension 등, 자료 처리: 비교적 많은 컴퓨팅 자원을 사용)
    ==> 사용자 비즈니스 검증 및 공간정보 활용을 위한 제반 도구를 제공

  • 정보제공방법으로  OpenAPI를 통하여 제공되는 서비스의 경우 일일 이용횟수 제한 및 네트웍을 통해 제한적으로 전송되는 패킷 제한으로 대용량 데이터 취득에 부적합
    ==> 클라우드를 통해 가상머신(서버)에 수요자가 원하는 데이터를 전체 세트로 제공

  • 정보제공 방법으로 파일 다운로드는 최신성 유지 및 활용을을 위한 전처리에 비교적 많은 수작업이 요구
    ==> 클라우드를 통해 Spatial DBMS에 탑재되는 Table 형식으로 즉시 이용가능하도록 제공

  • 클라우드 환경에서 공간정보데이터셋(ex: file, Spatial dbms) 및 활용도구(ex: OpenAPI, Quantum GIS, 전자정부프레임워크, EndUser App.)를 가상머신으로 제공한다면 진입장벽을 낮추는 효과 및 다양한 응용을 기대할 수 있음


2. SW 생명주기를 관리하는 시스템

  • SW는 장기적 관점에서 기획되고 운영되는 것과 특정 이벤트가 원인이 되어 단기적으로 운영되는 SW가 존재함
    (메르스, 구제역 등 특정 시점에 운영되는 SW에 자원을 할당, 이 후 상황 해제에 따라 자원을 회수 및 백업)

  • 단기간 운영되는 SW의 물리적 서버환경에 대한 체계적 관리 방안이 요구
    ==> 통합된 클라우드 인프라 환경 위에 개별 SW를 가상환경에서 운영한다면 물리적 인프라 자원을 효율적으로 사용할 수 있음

  • SW 생명주기가 관리를 위한 기준 정보로 활용됨으로써 거시적 관점에서 운영계획을 수립할 수 있음


3. 업무 중요도에 따라 컴퓨팅 자원의 활용이 자유로운 플랫폼

  • 비즈니스를 위한 의사결정은 환경적 요인의 영향을 많이 받음으로써 의사결정에 요구되는 정보의 우선 순위는 항상 바뀔 수 밖에 없음
    ==> 수요자의 선호도 및 업무 중요도에 따라 시스템 자원 점유의 우선 순위를 가지도록 하는 것은 무엇보다 중요함

  • 시장의 다양한 요구를 수용하는 서비스 제공은 원칙적으로 불가능함 
    ==> 정보 수요에 따라 개발과 전개가 자유로운 서비스 모델이 요구되며 이는 플랫폼이 지향해야하는 최고 가치임

  • 클라우드는 유연한 확장 및 조정이라는 특징을 기반으로 데이터 수용 능력, 적시에 어플리케이션을 제공하는 능력 등 비즈니스 환경변화에 대응할 수 있는 기반 기술임


언제쯤 정확한 필지 정보를 볼 수 있을

ICBM(IoT, Cloud, BigData, Mobile의 약칭)으로 설명되는 스마트 기술의 발전에 따라 4차 산업혁명이 만들어 갈 새로운 세상을 기대하고 있다. 공간정보는 새로운 세상을 만드는 기반 지식재로 그 가치와 중요성이 더욱 커지고 있다. 공간정보 분야의 정책 방향은 스마트시티의 확산과 디지털 트윈(Digital Twin) 기술의 확보를 통해 다양한 산업분야에 영향력을 확대하고자 한다.
디지털 트윈은 미국 제너럴 일렉트릭(GE)이 주창한 개념으로, 컴퓨터에 현실 속 사물의 쌍둥이를 만들고 현실에서 발생할 수 있는 상황을 컴퓨터로 시뮬레이션하여 결과를 미리 예측하는 기술이다. 디지털 트윈은 제조업뿐 아니라 다양한 산업·사회 문제를 해결할 수 있는 기술로 주목 받는다. (위키피디아)

가상공간이 정교할수록 얻을 수 있는 가치는 더욱 커지겠지만 과연 그렇게 될 수 있을지 의문이 드는 것도 사실이다. 기술이 문제가 아니라 콘텐츠로써 공간정보가 가지는 품질이 그리 좋지 못하다는 것이다. 본 글에서는 간단한 육안 점검을 통해 현재 국가가 개방하는 연속지적도의 품질을 살펴보고자 한다. 연속지적도는 국가가 개방하는 공간정보 중 가장 널리 활용되는 데이터로 토지거래 등 국민 실생활과 밀접한 관련이 있는 데이터이다.

연속지적도 품질 점검

1. 서울시에 주인이 없는 땅이 존재할 수 있을까?

대한민국은 국민이 가진 재산의 상당 부분이 부동산에 묶여있는 나라이다. 그만큼 정확한 토지 경계의 보유가 중요하지만 우리나라의 지적도는 종이도면에 점과 선으로 표현되어있는 것을 디지털화한 지적도를 기본으로 사용한다(도해지적).
정부는 도해지적이 가지는 근본적 문제 (품질 등, 원도의 제작시기가 일제강점기)의 해결을 위해 "도해지적 수치화 추진계획"을 통해 도해지적을 수치지적(측량을 통해 수치화한 정확한 지적)으로 전환하는 중이다. 이를 2030년까지 완료할 계획이고 전국 약 3,800만 필지 중 약 30%에 해당하는 1,140만 필지를 대상으로 한다.
결국 나머지 2,000만 필지가 넘는 70%는 부족한 품질 문제를 가지고 갈 수 밖에 없다는 것, 완료시기 또한 늦어 과연 가상 국토에 정확한 지적이 담길 수 있을지 걱정이 된다.

> 바로보기 : 도해지적 수치화 추진 계획

아래 그림은 국가공간정보포털이 개방하는 연속지적도(서울특별시 서초구)로, 표시된 지역은 필지가 어긋나거나 누락된 경우이다. 실제 누락이 아닌 개방데이터의 문제이겠지만 민간의 지도서비스는 해당 지역에 누락이 없음을 알 수 있다. 


2. 대구시는 어떻게 설명할 까요?

도해지적이 가지는 문제점은 개별 시군구로 작업을 진행하고 이를 통합해 연속지적을 만든다는 것이다. 여기서 개별 시군구에 적용되는 작업절차에 문제가 있었는지 또는 작업자의 단순 실수였는지 개방되는 연속지적도의 대구시는 심각한 오류를 가지고 있어 사용 하지 말 것을 권장하고 싶다.
대구시 연속지적의 가장 큰 문제는 시군구 별 나타나는 위치 오차 패턴이 서로 다르다는 것이다. 어쨌든 이런 저품질의 데이터를 편집하여 지적도 조회서비스를 제공하는 다음, 네이버 등 대형 포털의 기업 역량에 새삼 감탄하게 된다.
가장 많이 애용되는 개방 데이터라면 개방 품질에 조금 더 신경을 써 주었으면 하는 바람이 있다.


맺음 말

필자는 4차 산업혁명도 기대하지만 그 전에 현재 개방하고 있는 공간정보의 품질 관련 문제를 먼저 해결하기를 간절히 바라고 있다.
정부가 개방하는 공간정보는 활용을 위한 전처리에 많은 비용이 든다. 연속지적도로 한정한다고 해도 정확한 지적경계를 조회하고 싶다. 이를 위해 지적측량을 담당하는 한국국토정보공사만으로는 부족하다는 생각이다. 한국국토정보공사를 지원하는 민간 기업의 역할 확대를 통해 정확한 토지 정보를 충분히 가질 수 있다.

도로, 항만, 공항을 건설하고 하는 것만이 SOC(사회간접자본, Social Overhead Capital)범주에 포함되는 것은 아니다. 공익 목적에서 보면 정확한 토지정보 또한 Digital SOC라고 정의할 수 있다. 정밀한 토지정보를 만드는 정부의 의지를 통해 관련업계의 수익성이 개선되고 새로운 일자리를 만들 수 있다.
일제강점기에도 토지재조사사업을 통해  지적을 만들었는데 국민소득 3만불이 넘은 지금 하지 못할 이유는 없다. 공간정보 분야에 종사하는 많은 업체가 다른 것은 몰라도 수치 지도를 만드는 기술 역량은 충분히 가지고 있다. 

과거 전자정부 초창기 시절 행정DB 구축 사업처럼 정부 의지만 있다면 정확한 지적을 가지는 것이 그리 어렵지 않을 것이다.

 

개요

구현된 Earth 사이트 링크

Node.js 와 D3.js 기반으로 구현된 Earth 모듈(nullschool) 에 GFS(Global Forecast System)으로 부터 6시간 간격으로 업데이트되는 전세계 기상예보 데이터를 이용하여 지도(구) 기반으로 표출하는 시스템입니다.

현재는 지상데이터(Surface)를 이용하여 지도 상에 위치에 따른 풍향, 풍속을 나타내며, 흐름을 표출합니다. 미국 기상청으로부터 주기적으로 기상정보를 수집한 후 서비스를 위한 변환을 자동화하는 작업을 진행 중입니다. 향후 게시되는 링크를 통해 다양한 기상정보의 조회 서비스를 제공할 예정입니다.

NullSchool 활용을 위한 설정 등은 하단에 기술된 단락을 참조하십시오. 

 

 

NullSchool 설치 및 셋팅

리눅스 서버에 node.js 와 npm 을 설치한 후에, github 로 부터 "earth" 를 복사하고 관련된 라이브러리 등을 설치합니다. 설치 및 환경 셋팅을 위한 명령어는 다음과 같습니다. 

git clone https://github.com/cambecc/earth cd earth npm install

※ 참조 사이트: https://github.com/cambecc/earth 

earth 모듈 설치가 성공적으로 완료되면, <earth 설치 디렉토리> 로 이동하여 다음과 같은 명령어를 실행하여 웹서버를 실행합니다.

node dev-server.js 8080 

웹서버가 실행되면, 다음과 같은 주소로 접속하여 서비스를 확인할 수 있습니다.

http://localhost:8080

서버는 정적 S3 버킷 호스팅을위한 스탠드 인 역할을 하기 때문에 거의 서버 측 로직을 포함하지 않으며,  "earth/public" 디렉토리에 있는 모든 파일들이 주로 사용됩니다. 주요 소스코드는 "public/index.html" 및 "public/libs/earth/*.js" 를 참조하십시오. 데이터 파일은 "public/data" 디렉토리에 있으며, "data/weather/current" 로 실시간으로 변환되어 전송된 날씨 레이어가 표출됩니니다. 

 

기상 데이터 생성 방법

기상 데이터는 미국 기상청 (National Weather Service)에서 운영하는 GFS (Global Forecast System)에 의해 생성된 데이터를 이용합니다. 예보는 매일 4 번 생성되며 NOMADS (http://nomads.ncep.noaa.gov/)에서 다운로드 할 수 있습니다. 파일은 GRIB2 형식이며 300 개가 넘는 레코드가 있으며, 특정 등온선에서 풍력 데이터를 시각화하기 위해 이러한 기록 중 일부만 필요합니다. 다음과 같은 명령어는 1000 hPa 바람 벡터를 다운로드하고 grib2json 유틸리티를 사용하여 JSON 형식으로 변환합니다. 
YYYYMMDD=<a date, for example: 20190115>
curl "http://nomads.ncep.noaa.gov/cgi-bin/filter_gfs_1p00.pl?file=gfs.t00z.pgrb2.1p00.f000&lev_10_m_above_ground=on&var_UGRD=on&var_VGRD=on&leftlon=0&rightlon=360&toplat=90&bottomlat=-90&dir=%2Fgfs.${YYYYMMDD}00" -o gfs.t00z.pgrb2.1p00.f000

 

grib2json -d -n -o current-wind-surface-level-gfs-1.0.json gfs.t00z.pgrb2.1p00.f000

 

cp current-wind-surface-level-gfs-1.0.json <earth-git-repository>/public/data/weather/current

 ※ grib2json 모듈 참조 사이트: https://github.com/cambecc/grib2json

 

구현된 Earth 사이트 링크

 

CesiumJS에서 OGC Web Service이용하기 

WMS 서비스

서비스바로가기

WFS 서비스

서비스바로가기


CesiumJS 위에 OGC WMS 인터페이스가 제공되는 지도서비스를 Overlay 합니다.

WMS (Web Map Service)는 인터넷을 통해 지도이미지(Raster Image)를 제공하는 서비스 입니다.
WFS (Web Feature Service)는  인터넷을 통해 벡터데이터(Vector Graphics)를 제공하는 서비스 입니다.

참조 : WMS와 WFS의 효율적 사용 (Vector Graphics와 Raster Image의 비교)

                                     

1. WMS 활용하기 

index.html 파일을 생성한 후 아래의 소스코드를 입력합니다.

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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <script src="js/jquery/jquery-3.3.1.min.js"></script>
    <script src="js/Cesium-1.50/Build/Cesium/Cesium.js"></script>
    <link href="js/Cesium-1.50/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<script type="text/javascript">
    var baseMap, mapViewer;
 
    $(document)
        .ready(
            function() {
                //실행 시 처음 보여질 범위를 설정
                var extent = Cesium.Rectangle.fromDegrees(117.89628431.499028139.59738043.311528);
                //처음 보여질 범위 중 거리를 설정
                Cesium.Camera.DEFAULT_VIEW_RECTANGLE = extent;
                Cesium.Camera.DEFAULT_VIEW_FACTOR = 5;
 
                Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkOGMxZjMzNy01N2FkLTQ3YTctODU0NS05NGY0MmE3MGJiOWEiLCJpZCI6NjExNywic2NvcGVzIjpbImFzciIsImdjIl0sImlhdCI6MTU0NTI3OTE3NH0.6VdcqK6vL7faWx_vYkkOuNNa8dapTn1geCi7qYBhGCw';
 
                var mapViewer = new Cesium.Viewer(
                    'cesiumContainer',
                    {    // 하단 위젯 제거
                        timeline : false,
                        animation : false,
                        selectionIndicator : false,
                        navigationHelpButton : false,
                        infoBox : false,
                        navigationInstructionsInitiallyVisible : false,
                        vaseLayerPicker : true
                    }
);
 
                var wms = new Cesium.WebMapServiceImageryProvider({
                    // 제작한 국가지점번호 데이터 가져오기
                    url: 'http://***.***.***.***/geoserver/progworks/wms',
                    parameters: {
                        format:'image/png',
                        transparent:'true'
                    },
                    layers : 'progworks:national_point_num_5179_to_4326',
                    maximumLevel : 12
                });
                // 가져온 데이터 올리기 
                var imageryLayers = mapViewer.imageryLayers;
                imageryLayers.addImageryProvider(wms);
 
            })
</script>
<body>
<div id="cesiumContainer" style="width: 100%; height: 710px"></div>
</body>
</html>
cs


geoserver에서 자체 제작한 국가지점번호 레이어를 불러옵니다. 

var wms = new Cesium.WebMapServiceImageryProvider({
                    // 제작한 국가지점번호 레이어 가져오기
                    url: 'http://***.***.***.***/geoserver/progworks/wms',
                    parameters: {
                        format:'image/png',
                        transparent:'true'
                    },
                    layers : 'progworks:national_point_num_5179_to_4326',
                    maximumLevel : 12
                });
                // 가져온 데이터 올리기 
                var imageryLayers = mapViewer.imageryLayers;
                imageryLayers.addImageryProvider(wms);
 


프로젝트를 실행합니다.  

서비스바로가기


2. WFS 활용하기

* GeoServer 에서 자체 제작한 국가지점번호 레이어를 가져옵니다. GeoJson 형식으로 레이어를 사용했습니다.
  line 36을 아래와 같이 변경합니다.

// 제작한 국가지점번호 레이어 가져오기 
mapViewer.dataSources.add(Cesium.GeoJsonDataSource.load('http://*.***.**.***:8180/geoserver/progworks/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=progworks:national_point_num_5179_to_4326&outputFormat=application/json', {
    // 선 색을 설정
    stroke: Cesium.Color.WHITE,
    // 선의 두께 설정
    strokeWidth: 5
}));                         
cs

* 프로젝트를 실행합니다. 



Cesium JS 시작하기

Node JS 환경 

서비스바로가기

ApacheTomcat 환경

서비스바로가기


* CesiumJS 란? 

플러그인없이 웹 브라우저에서 3D지도를 만들기위한 오픈소스 JavaScript 라이브러리 입니다. 


* CesiumJS 시작하기

1. 가입하기

 CesiumJS를 이용하기 위해서는 먼저 Cesium ion에 가입을 하여야 합니다. (AccessToken 발급)

  -> Cesium ion 바로가기


1. Node JS 환경에서 Cesium JS 활용하기

* Express 프레임워크 활용하기

Node JS의 모듈 중 Express 프레임워크를 기반으로 작업을 진행하겠습니다.

Express를 사용하는 이유는 대중적이고 인기가 많을뿐만아니라 소스코드의 간소화로 인해 향후 유지보수를 쉽게 하기 위함입니다.


기본 서버 구성하기

  1. server라는 이름의 js 파일을 생성합니다.

  2. 아래의 소스코드를 입력합니다. (콘솔과 웹상에 Hello Cesium이라는 문구를 표출하여 확인을 해봅니다.) 

1
2
3
4
5
6
7
8
9
10
const express = require('express');
const app = express();
 
app.use('/'function (req, res) {
    res.send("Hello Cesium");
});
 
app.listen(8080function () {
    console.log("Hello Cesium");
});
cs

node 서버를 실행시킨 후 콘솔과 웹상에 아래와 같이 표출된다면 서버구성 성공입니다!

웹상에 표출할 HTML 구성하기

  1. 먼저 html이라는 이름의 폴더를 생성합니다.

  2. html 폴더안에 testmap이라는 이름의 html파일을 생성합니다.

  3. 아래의 소스코드를 입력합니다.

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
$(function(){
    var baseMap, mapViewer;
 
    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 mapViewer = new Cesium.Viewer(
        'cesiumContainer',
        {
            timeline : false,
            animation : false,
            selectionIndicator : false,
            navigationHelpButton : false,
            infoBox : false,
            navigationInstructionsInitiallyVisible : false,
 
            imageryProvider : Cesium
                .createOpenStreetMapImageryProvider({
                    url : 'https://a.tile.openstreetmap.org/'
                }),
 
            baseLayerPicker :true
        });
});
cs

server.js와 testmap.html 연결하기

1. server.js의 코드를 아래와 같이 변경하여 줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var express = require('express');
var app = express();
var path = require('path');
 
app.use(express.static(path.join(__dirname, 'html')));
 
app.get('/'function (req,res) {
    res.sendFile(path.join(__dirname, 'html''testmap.html'));
});
 
app.use('/'function (req, res) {
    res.send("Hello Cesium");
});
 
app.listen(8081function () {
    console.log("Hello Cesium");
});
cs

html과 같은 정적 파일을 제공하기위한 소스코드

1
app.use(express.static(path.join(__dirname, 'html')));
cs

웹상에 표출할 html파일을 바라보기위한 소스코드 

1
2
3
app.get('/'function (req,res) {
    res.sendFile(path.join(__dirname, 'html''testmap.html'));
});
cs

최초 시작 시 web에 표출될 지역의 범위와 높이를 설정

1
2
3
4
var extent = Cesium.Rectangle.fromDegrees(117.89628431.499028139.59738043.311528);
 
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = extent;
Cesium.Camera.DEFAULT_VIEW_FACTOR = 0.7;
cs


Open Street Map을 불러옵니다.  

1
2
3
imageryProvider : Cesium.createOpenStreetMapImageryProvider({
                    url : 'https://a.tile.openstreetmap.org/'
                }),
cs

* Tip - baseLayerPicker : true 하면 오른쪽 상단 메뉴에 베이스레이어를 선택하는 버튼이 생성됩니다.

* Tip - 세슘 Rectangle document 정보 https://cesiumjs.org/Cesium/Build/Documentation/Rectangle.html

staticCesium.Rectangle.fromDegrees(westsoutheastnorthresult) → Rectangle

Creates a rectangle given the boundary longitude and latitude in degrees.
NameTypeDefaultDescription
westNumber0.0optionalThe westernmost longitude in degrees in the range [-180.0, 180.0].
southNumber0.0optionalThe southernmost latitude in degrees in the range [-90.0, 90.0].
eastNumber0.0optionalThe easternmost longitude in degrees in the range [-180.0, 180.0].
northNumber0.0optionalThe northernmost latitude in degrees in the range [-90.0, 90.0].
resultRectangleoptionalThe object onto which to store the result, or undefined if a new instance should be created.
Returns:

The modified result parameter or a new Rectangle instance if none was provided. 


소스코드 구성을 마쳤으니 실행해보도록 하겠습니다. 

서비스바로가기



2. Apache Tomcat 환경에서 Cesium JS 활용하기

필자는 Eclipse IDE를 활용하여 작업을 진행하였습니다.


Cesium JS 다운받기 

해당 사이트로 접속하여 Cesium JS를 다운받습니다. (https://cesiumjs.org/downloads/)


프로젝트 생성하기

Dynamic Web Projcet를 이용하여 프로젝트를 생성합니다. 


Apache Tomcat 서버 구성하기 (Apache-tomcat Download)

톰캣 사이트에 들어가 본인 운영체제 환경에 맞게 다운로드를 합니다.


서버 구성 순서 ( Window -> Preferences -> Server -> Runtime Enviroments -> Add -> Next -> Finish) 


HTML 구성하기 

기본적인 HTML 구성 후 로컬상에서 서버를 실행하여 제대로 구동되는지 확인해보겠습니다.

start라는 이름의 html파일을 생성 후 아래의 소스코드를 입력합니다.

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
</head>
<body>
<h1>Hello Cesium</h1>
</body>
</html>
cs

사진과 같이 작동되는것을 확인할 수 있습니다.


다운받은 Cesium JS 파일 업로드 하기 

다운받은 Cesium JS 폴더를 압축 해제 후 WebContent 디렉토리 하단 또는 본인 임의의 디렉토리 내부로 이동시킵니다.


Cesium JS 활용하기 

앞서 기본적으로 구성하였던 start.html의 소스코드를 아래와 같이 교체합니다.

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
<!DOCTYPE html>
<html lang="en">
<meta charset = "UTF-8">
<head>
    <script src="js/Cesium-1.50/Build/Cesium/Cesium.js"></script>
    <link href="js/Cesium-1.50/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body>
<div id="cesiumContainer" style="width:100%; height:710px"></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,
            
            imageryProvider : Cesium.createOpenStreetMapImageryProvider({
                url : 'http://a.tile.openstreetmap.org/'
            }),
            
            baseLayerPicker : true
        });
</script>
</body>
</html>
 
cs


서버와 소스코드 구성을 완료 하였으니 실행해보도록 하겠습니다.

서비스바로가기

+ Recent posts