국가지점번호 지도서비스

> 서비스 바로가기

국내의 공공 부문 지도서비스, 대형포털의 지도서비스, 주소정보시스템, 공공개방포털 어디에서도 국가지점번호 전자지도를 찾을 수 없었습니다. 직접 제작했습니다. 그리고 최초로(현재 서비스하는 곳이 없는 것 같습니다) 지도 서비스로 개방합니다.


Progworks가 개방하는 국가지점번호 지도 서비스는 어떤 비즈니스 목적을 가진 사용자를 대상으로 하는 서비스는 아닙니다. 국가지점번호가 홍보 되고 있는 만큼 활용되지 않는 부족한 부분을 채우기 위한 단순한 지점 번호 형상 조회 서비스 입니다.
국가지점번호 데이터를 만드는 일은 그리 어렵지 않았지만 실제 서비스로 이행하는 부분은 수고를 필요로 합니다.

국가지점번호는 우리나라를 포함하는 가로 700Km, 세로 800Km 영역 내에 정의되는 10m 격자이지만, 지도서비스 및 활용 편의를 위하여 아래와 같이 격자크기를 구분하여 제작하였습니다.

데이터를 제작하고 PostGIS에 적재하고 보니 DB 크기가 무려 900 Gigabytes 입니다. 결국 목표로 하는 응답시간을 준수하도록 구성하는 것이 가장 어려운 작업이었습니다.

지점번호의 해당 지역 식별을 위하여 배경지도는 OSM(OpenStreetMap)을 설정했습니다. Vworld 배경 지도보다 지원하는 Scale 범위가 넓어 10m 그리드의 식별이 용이합니다. 대신 배경지도를 불러오는 속도가 느린 점은 양해 부탁드립니다.


국가지점번호 공간정보를 제작하고 서비스로 발행하는 일련의 과정에서 얻은 몇 가지는 다음과 같습니다.

첫번째, 공개SW인 PostGIS가 보인 성능에 놀랐습니다. 특히 국가지점번호 10m 격자의 경우 41억건의 데이터에 용량만 900Gb에 육박하는 빅테이블임에도 충분한 조회 성능을 보였습니다.


두번째, 클라우드 인프라가 가지는 유연성과 확장성의 편안함을 만끽했습니다. 최초 DBMS서버의 용량은 150Gb 였지만, 작업을 진행하며, 볼륨을 생성하고 연결(Attach)하는 간단한 작업만으로 부족한 디스크 용량을 확보할 수 있었습니다.

세번째, 클라우드를 통해 잘 설정된 개발환경 만으로 서비스 배포가 쉽다는 것입니다. 저희는 이미 제작된 소스에 간단한 레이어 추가 작업 후 클라우드 파운드리가 제공하는 배포도구를 통해 간단하게 지도서버를 배포할 수 있었습니다.


블로그 내 국가지점번호 관련 자료(하위 제목 클릭 시 바로가기)


> 서비스 바로가기

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

이번시간은 Openlayers3(OL3)와 Leaflet에 User Control(거리재기)을 올려보겠습니다.

그전에 결과화면부터 보겠습니다.

서비스바로가기


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

OpenLayers3 :  https://openlayers.org/ 

Leaflet : http://leafletjs.com/

VWORLD :  http://map.vworld.kr



거리재기 기능을 이용하기 위해서는 Openlayers3의 경우 별도의 라이브러리 추가가 필요없지만, Leaflet의 경우 거리재기를 이용하기 위해 별도의 라이브러리가 필요합니다. 


1. Openlayers3 User Control(거리재기 & 면적재기) 추가

활용라이브러리 : ol.Map, ol.layer.Tile, ol.source.Vector, ol.layer.Vector, ol.geom.Polygon, ol.geom.LinString, ol.View, ol.interaction.Draw, ol.style.Style, ol.style.Fill, ol.style.Stroke, ol.style.Circle, ol.Overlay


먼저 Openlayers3(이하 OL3)  <script></script>부분입니다. 참고로 OL3를 이용하기 위해서는 라이브러리 추가합니다.


1
2
3
4
 
<link type="text/css" rel="stylesheet" href="<c:url value='/css/openlayers/ol.css'/>" />
 
<script src="<c:url value='/js/openlayers/ol.js'/>"></script>
cs


OL3 지도 띄우기


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
<script type="text/javascript">
//openlayers 지도 띄우기
    //레스터 지도
    var raster = new ol.layer.Tile({
        source: new ol.source.XYZ({
               url: 'http://xdworld.vworld.kr:8080/2d/Base/201802/{z}/{x}/{y}.png'
           })
    });
    
    var vectorSource = new ol.source.Vector();
    //벡터스타일
    var vector = new ol.layer.Vector({
        source: vectorSource,
        style: new ol.style.Style({
            fill: new ol.style.Fill({
                color: 'rgba(255, 255, 255, 0.2)'
            }),
            stroke: new ol.style.Stroke({
                color: 'rgba(0, 0, 255, 1.0)',
                width: 2
            }),
            image: new ol.style.Circle({
                radius: 7,
                fill: new ol.style.Fill({
                    color: '#ffcc33'
                })
            })
        })
    });
    
    //지도띄우기
    var map = new ol.Map({
        
        layers: [raster,vector],
       
        target: document.getElementById('map'),
       
        view: new ol.View({
          center: [14128579.824512570.74],
          maxZoom: 19,
          zoom: 14
        })
    });
</script>
cs

다음은 User Control 거리재기를 가져오도록 하겠습니다. 거리재기의 경우, OL3에서 기본적으로 제공하는 기능이며 또한 거리재기와 함께 면적을 구하는 기능을 함께 보여드리겠습니다.

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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
<script type="text/javascript">
 
    //거리재기
    var sketch;
    var helpTooltipElement;
    var helpTooltip;
    var measureTooltipElement;
    var measureTooltip;
    var continuePolygonMsg = 'Click to continue drawing the polygon';
    var continueLineMsg = 'Click to continue drawing the line';
    
    var pointerMoveHandler = function(evt) {
      if (evt.dragging) {
        return;
      }
      /** @type {string} */
      var helpMsg = 'Click to start drawing';
    
      if (sketch) {
        var geom = (sketch.getGeometry());
        if (geom instanceof ol.geom.Polygon) {
          helpMsg = continuePolygonMsg;
        } else if (geom instanceof ol.geom.LineString) {
          helpMsg = continueLineMsg;
        }
      }
    
      helpTooltipElement.innerHTML = helpMsg;
      helpTooltip.setPosition(evt.coordinate);
    
      helpTooltipElement.classList.remove('hidden');
    };
      
    map.on('pointermove', pointerMoveHandler);
 
    map.getViewport().addEventListener('mouseout'function() {
      helpTooltipElement.classList.add('hidden');
    });
 
    var typeSelect = document.getElementById('type');
 
    var draw;
    var formatLength = function(line) {
        var length;
        length = Math.round(line.getLength() * 100/ 100;
        console.log(length);
        var output;
        if (length > 100) {
          output = (Math.round(length / 1000 * 100/ 100+
            ' ' + 'km';
        } else {
          output = (Math.round(length * 100/ 100+
            ' ' + 'm';
        }
        console.log(output);
        return output;
    };
    
    var formatArea = function(polygon) {
        var area;
        area = polygon.getArea();
        var output;
        if (area > 10000) {
          output = (Math.round(area / 1000000 * 100/ 100+
              ' ' + 'km<sup>2</sup>';
        } else {
          output = (Math.round(area * 100/ 100+
              ' ' + 'm<sup>2</sup>';
        }
        return output;
    };
      
    function addInteraction() {
        var type = (typeSelect.value == 'area' ? 'Polygon' : 'LineString');
        draw = new ol.interaction.Draw({
            source: vectorSource,
            type: (type),
            style: new ol.style.Style({
                fill: new ol.style.Fill({
                color: 'rgba(255, 255, 255, 0.2)'
                }),
                stroke: new ol.style.Stroke({
                    color: 'rgba(0, 0, 0, 0.5)',
                    lineDash: [1010],
                    width: 2
                }),
                image: new ol.style.Circle({
                    radius: 5,
                    stroke: new ol.style.Stroke({
                        color: 'rgba(0, 0, 0, 0.7)'
                    }),
                    fill: new ol.style.Fill({
                    color: 'rgba(255, 255, 255, 0.2)'
                    })
                })
            })
        });
        map.addInteraction(draw);
 
    
        createMeasureTooltip();
        createHelpTooltip();
        
        var listener;
        draw.on('drawstart'function(evt) {
            sketch = evt.feature;
            var tooltipCoord = evt.coordinate;
            
            listener = sketch.getGeometry().on('change'function(evt) {
                var geom = evt.target;
                var output;
                if (geom instanceof ol.geom.Polygon) {
                    output = formatArea(geom);
                    tooltipCoord = geom.getInteriorPoint().getCoordinates();
                } else if (geom instanceof ol.geom.LineString) {
                    output = formatLength(geom);
                    tooltipCoord = geom.getLastCoordinate();
                }
                measureTooltipElement.innerHTML = output;
                measureTooltip.setPosition(tooltipCoord);
            });
        }, this);
        
        draw.on('drawend'function() {
            measureTooltipElement.className = 'tooltip tooltip-static';
            measureTooltip.setOffset([0-7]);
 
            sketch = null;
 
            measureTooltipElement = null;
 
            createMeasureTooltip();
            ol.Observable.unByKey(listener);
        }, this);
    }
    
    function createHelpTooltip() {
        if (helpTooltipElement) {
          helpTooltipElement.parentNode.removeChild(helpTooltipElement);
        }
        helpTooltipElement = document.createElement('div');
        helpTooltipElement.className = 'tooltip hidden';
        helpTooltip = new ol.Overlay({
          element: helpTooltipElement,
          offset: [150],
          positioning: 'center-left'
        });
        map.addOverlay(helpTooltip);
    }
    
    function createMeasureTooltip() {
        if (measureTooltipElement) {
          measureTooltipElement.parentNode.removeChild(measureTooltipElement);
        }
        measureTooltipElement = document.createElement('div');
        measureTooltipElement.className = 'tooltip tooltip-measure';
        measureTooltip = new ol.Overlay({
          element: measureTooltipElement,
          offset: [0-15],
          positioning: 'bottom-center'
        });
        map.addOverlay(measureTooltip);
    }
    
    /* function removeInteractionMeasure() {
        vector.getSource().clear(measureTooltipElement);
        map.removeInteraction(draw);
    }     */
 
    typeSelect.onchange = function() {
        vector.getSource().clear(measureTooltipElement);
        map.removeInteraction(draw);
        map.removeOverlay(measureTooltip);
        $('.tooltip-static').remove();
        addInteraction();
    };
    addInteraction();
 
</script>
 
 
 
cs

12번행~38행 pointerMoveHandler는 거리재기 및 면적재기 기능이 활성화 되었을 경우, 그에 맞는 ToolTip 메세지를 제공하는 부분

40행 : id를 이용하여 선택된 기능의 Type 확인

43행~ 54행 : 거리재기 단위 변경 및 output 제공 

59행~71행  : 면적재기 단위 변경 및 output 제공 

73행~135행 : 거리재기 및 면적재기 기능 구현 부분

137행~176행 : 기능에 따른 해당 Tooltip 제공

OL3 User Control 거리재기 및 면적재기 <body></body>입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<body>
    <div class="container-fluid">
        <div class="row content">
            <div class="col-sm-6" style="border-right: 1px solid; height: 700px;">
                <h1>Oepnlayers 3</h1>
                <div id="map"></div>
            </div>
        </div>
        <div class="row content">
            <form class="form-inline" style="padding: 13px;">
                <label>Measurement type &nbsp;</label>
                    <select id="type">
                      <option value="length">거리재기</option>
                      <option value="area">면적재기</option>
                    </select>
            </form>            
        </div>
    </div>    
</body>
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
<style>
/* 거리재기 스타일_openlayers3 */
.tooltip {
  position: relative;
  background: rgba(0, 0, 0, 0.5);
  border-radius: 4px;
  color: white;
  padding: 4px 8px;
  opacity: 0.7;
  white-space: nowrap;
}
.tooltip-measure {
  opacity: 1;
  font-weight: bold;
}
.tooltip-static {
  background-color: #ffcc33;
  color: black;
  border: 1px solid white;
}
.tooltip-measure:before,
.tooltip-static:before {
  border-top: 6px solid rgba(0, 0, 0, 0.5);
  border-right: 6px solid transparent;
  border-left: 6px solid transparent;
  content: "";
  position: absolute;
  bottom: -6px;
  margin-left: -7px;
  left: 50%;
}
.tooltip-static:before {
  border-top-color: #ffcc33;
}
    
</style>
cs

2. Leaflet User Control(거리재기) 추가

활용라이브러리 : L.map, L.tileLayer, L.control.measure


위에서 언급드린것 처럼 Leaflet에서 거리재기를 이용하기 위해서는 라이브러리가 필요합니다. 

leaflet.measure.css

leaflet.measure.js


1
2
3
<script src="<c:url value='/js/leaflet/leaflet.js'/>"></script>
<script src="<c:url value='/js/leaflet/leaflet.measure.js'/>"></script>
<link type="text/css" rel="stylesheet" href="<c:url value='/css/leaflet/leaflet.measure.css'/>" />
cs


먼저 leaflet  <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
 <script>
//leaflet 지도 띄우기
var leafletMap = L.map('leafletMap').setView([37.5247030824278714129046.928310394],14)    
//Vworld Tile 변경
L.tileLayer('http://xdworld.vworld.kr:8080/2d/Base/201802/{z}/{x}/{y}.png').addTo(leafletMap);
    
var plugin = L.control.measure({
    //  control position
    position: 'topleft',
    //  weather to use keyboard control for this plugin
    keyboard: true,
    //  shortcut to activate measure
    activeKeyCode: 'M'.charCodeAt(0),
    //거리재기 초기화 코드  //shortcut to cancel measure, defaults to 'Esc'
    cancelKeyCode: 27,
    //  line color
    lineColor: 'red',
    //  line weight
    lineWeight: 5,
    //  line dash
    lineDashArray: '6, 6',
    //  line opacity
    lineOpacity: 1,
    //  distance formatter
    // formatDistance: function (val) {
    //   return Math.round(1000 * val / 1609.344) / 1000 + 'mile';
    // }
}).addTo(leafletMap)
</script>
cs


OL3에 비해 매우 간략합니다. 이유는 라이브러리를 이용하였기 때문이라 생각됩니다. 

이번에도 지도는 VWORLD를 이용했습니다.

3행~ 5행 : 지도 띄우기

7행~28행 : 거리재기를 위한 기능 부분으로 <script>부분에서는 옵션값 부분만 설정합니다. 그리고 Leaflet에서는 KeyCode라는것을 사용하는데 

15행에 보시면 cancelKeyCode : 27은 ESC를 이용하여 기능을 초기화 시키는 코드입니다. 

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<body>
    <div class="container-fluid">
        <div class="row content">
            <div class="col-sm-6" style="border-left: 1px solid; height: 700px;">
                <h1>leaflet</h1>
                <div id="leafletMap"></div>
            </div>
        </div>
    </div>
</body>
<style>
/* 거리재기_leaflet */
#leafletDt{
    text-align: center;
    padding-left: 283px;
}  
    
</style>
cs


결과화면은 아래와 같습니다.


서비스바로가기

1. 개요

품질 좋은 소프트웨어는 수많은 테스트 수행을 통해 제작됩니다. 그러나 외주 용역 사업이 대부분을 차지하는 국내 SW개발 환경에서 충분한 테스트를 보장하는 기간을 사업계획에 반영하기 어려운 것이 사실입니다. 이런 현실적인 이유로 공공정보화사업을 통해 구축된 웹사이트가 일부 만족할 만한 평가를 받지 못합니다. 

제작된 어플리케이션을 이용하는 사용자의 체감 성능을 향상시키고, 잠재적 성능 저하 요인을 제거하고, 사용자 증가에 따른 올바른 확장 전략 수립을 위해 어플리케이션의 성능점검은 무엇보다 중요합니다. GIS 분야가 아닌 모든 분야에 적용되는 공통사항으로써 본 글을 통해 필자가 적용하는 기법을 공유합니다. 완벽한 성능평가는 아니지만 웹을 통해 게시되는 어플리케이션에 일반적으로 적용될 수 있는 방법으로 제한된 기간을 극복하면서 사용자의 체감성능을 충분히 향상시킬 수 있습니다.

적용되는 성능평가는 크게 두부분으로 나누어 진행이 됩니다. 
첫번째는 서버와 클라이언트 사이의 요청/응답 성능 평가입니다. Apache JMeter를 이용합니다.
두번째는 서버로부터 응답이 도달한 후 클라이언트의 브라우저에 표시되는 부분의 성능평가 입니다. Google Page Insights를 이용합니다.

> 바로가기 : Apache JMeter
> 바로가기 : Google Page Insights

2. Apache JMeter를 이용한 서버, 네트워크 성능 평가

Geoserver를 통해 전개되는 WMS를 대상으로 성능평가를 진행합니다.

이 후 실행되는 Apache JMeter 성능 평가는 다음의 URL을 테스트 합니다. 테스트 환경에 따라 클라이언트에 도달하는 시간에 차이가 있겠으나 필자의 네트워크 환경에서 단건 기준 약 0.2초내에 응답이 완료되고 있습니다.

http://1.250.62.223:8180/geoserver/progworks/wms?service=WMS&version=1.1.0&request=GetMap&layers=progworks:group_uq&bbox=126.9092,37.4978,126.9689,37.5437&width=700&height=500&srs=EPSG:4326&format=image/png 


2.1 성능평가 준비 및 설정

Apache JMeter의 설치 및 기본 사용은 인터넷에 좋은 자료가 많이 있습니다. 별도 설명하지 않겠습니다. 간단한 아래 그림을 참고하십시오. 아래 그림의 Thread Group을 통해 사용자(요청) 개수, 반복횟수(Loop Count), 실행통제시간(Ramp-up period) 을 지정하고
HTTP Request 를 통해 성능평가 대상 WMS의 서버 및 입력파라미터를 설정하면 Test Listener를 통해 결과 값을 확인합니다.


2.2 사용자 수용 능력 평가
예측되는 전체 쓰레드 갯수( 사용자 요청 갯수) 가 지정된 시간 내에 실행되는지 여부를 평가합니다. 쉽게 말하면 사이트의 특정 페이지에 1분에 60번의 요청을 처리하는 것이 목표라면, 
Apache JMeter의 Thread Group에서 1분(Ramp-Up Period)에 60번의 요청(Number of Threads)을 처리하는 것을 테스트하면 됩니다.
또는 Thread Group에서 Ramp-Up Period : 1분, Number of Threads: 6, Loop Count: 10 으로 설정해도 됩니다. 차이점은 전자의 경우 1초에 한 번씩 60개의 요청이 수행이 되고, 후자의 경우 6개의 요청이 동시에 이루어지고 10초간격으로 1분간 반복이 됩니다.

위의 첨부 그림의 분당 60개의 요청처리는 실제 테스트 결과로 반영하기에는 무리가 있습니다. 실제 필드에 적용하는 경우라면 피크타임의 전체 시간(1시간, 2시간 등)을 Ramp-up Period로 설정한 후 실행 쓰레드 개수를 지정해야 할 것입니다. 
위 시험의 목표는 사이트 구축 시 수용하고자 하는 사용자 수를 계량화하고, 목표로 하는 시간 내에 해당 사용자의 요청을 처리하는지 점검합니다. 


2.3 과부하 점검

사용자를 지속적으로 증가시켜 개별 응답이 지정된 시간 내에 응답하는지 여부를 평가합니다. 쉽게 말해 개별 요청의 처리 제한 시간이 1초일 경우 1초에 응답할 수 있는 동시요청개수를 찾는 것입니다. 이를 통해 테스트 대상 서버의 물리적 수용한계점을 찾을 수 있고 시스템 확장이 필요한 기준을 수립할 수 있습니다.
과부하 점검에서 Ramp-Up Period는 크게 의미 없습니다.
필자의 경우 Loop Count를 10, 20 등으로 고정하고 Number Of Threads의 갯수를 1, 5, 10, 15, 20, 30 과 같은 형식으로 지속적으로 늘려가며 테스트를 수행합니다. 만일 개별 요청의 응답 제한 시간이 1초라고 할 때 Number Of Threads 개수가 얼마일 때 응답제한시간을 초과하는지 측정하고, 실제 처리되는 초당 Throughput이 언제 감소하는지를 살펴봅니다.

위의 테스트 결과 예시는 동시 사용자를 30명, 40명, 50명 지정하고 각 10회씩 동시 요청한 후 결과 값을 표시합니다.
먼저 평균응답시간을 살펴보면 동시 사용자 30일 때 1초 내에 있었으나 40명일 경우 1초를 초과하고 있습니다. 50명일 경우 거의 2초에 가까운 평균응답시간을 보여주고 있습니다. Throuhput을 보면 3명일 경우 초당 37건을 처리하고 있으나 40명일 경우 오히려 초당 35건으로 처리 능력이 떨어짐을 알 수 있습니다.

일반적으로 잘 짜여진 코드라고 하면 동시 사용자 증가에 따라 평균응답시간은 선형으로 지속적으로 증가합니다. 증가하는 평균응답시간이 제한된 시간을 넘어서는 경우, 사용자 증가 시점에 따라 Throughput이 오히려 감소하는 경우는 테스트 대상 서버가 한계치에 도달한 경우입니다.
주의할 점은 과부하 테스트 시 필수적으로 테스트 계통에 걸치는 서버 자원을 모니터링 해야 합니다.

만일 WAS 자원은 여유가 있는데, DBMS 자원에 여유가 없다면 DBMS의 확장(수직확장 우선)이 우선적으로 고려되어야 합니다.
DBMS는 안정적이나 WAS 자원에 여유가 없다면 WAS의 확장(수평확장 우선)이 우선적으로 고려되어야 합니다. 드문 경우이긴 하지만 모든 자원은 여유가 있는 상태인데 사용자의 증가에 따라 전송되는 패킷(Received Byte)량이 증가하지 않는다면, 테스트 서버와 연결되는 네트워크 설정 점검 또는 증설이 요구됩니다.


3. Google Page Insights를 이용한 페이지 최적화

서버가 충분한 성능을 발휘하고 있는데 실제 브라우저에 표출이 늦다면 웹 페이지 레벨의 최적화가 요구됩니다. 필자가 애용하는 방법은 첫 번째 구글 개발자 도구를 이용하여 페이지의 로딩속도를 점검하고 그 시간이 늦는 경우 페이지와 함께 전송되는 리소스 등을 점검합니다. 이 때 이용하는 것이 "Goolge Page Insights"입니다.
서버 성능 점검은 개별페이지를 테스트하지 실제 브라우저 표출의 대상이 되는 JS, CSS 등 리소스에 대한 다운로드는 포함하지 않습니다. 따라서 성능점검 시 꼭 페이지 표출 속도도 점검해야 합니다.

Google Page Insights는 친절하게도 어떤 방식으로 수정해야하는지 친절히 알려줍니다. 단순한 수고가 서비스의 만족도를 높인다면 보람있는 일입니다.
위의 예시는 국가공간정보포털 오픈마켓을 측정한 것으로 평균정도의 응답속도를 보이고 있습니다.

4. 맺음말

제가 겪어본 바에 의하면 공간정보분야 공공 부문 서비스에 대한 평판이 그리 좋은 편은 아닙니다. 오직 국가정보화사업을 통해 먹고사는 공간정보업체는 특히 더욱 성능부분에 충실할 필요가 있습니다. 비교적 영세한 업체로 이루어진 산업계의 특성상 아키텍처그룹, PM 그룹을 별도로 운영할 수 없는 형편도 이해하지만 정보화사업이 아니면 망할 수 밖에 없는 형편에서 고객이 만족할 수 있도록 더욱 노력해야 합니다. 전 고객이 떠나는 것이 두렵습니다.

마지막으로 프로그웍스는 앞장 서서 테스트를 철저히 하겠음을 다짐하며 마무리 합니다.

정부, 공공기관이 생산한 대부분의 공간정보는 생산주체 및 제작시기에 따라 적용된 좌표계에 차이가 존재한다. 개별적으로 운영되는 정보시스템에서 이 들 공간정보의 사용은 큰 문제가 없지만, 서로 다른 공간정보와의 매쉬업, 융합 등을 위해 필수적으로 서비스 좌표계를 통일하는 것이 어플리케이션의 관리 편의성, 서비스 응답 성능 개선, 공개SW의 효율적 사용을 위해 요구된다.
GIS 기반 서버로써 지오서버를 사용한다면 서비스 타일을 생성하는 경우 EPSG:4326, EPSG:3857(EPSG:900913)만을 지원한다. 

따라서 본 글을 통해 PostGIS에 등록된 공간테이블을 EPSG:4326좌표계를 가지도록 변환하는 과정을 설명한다.

1. Table Copy

사용하려는 데이터의 좌표계는 EPSG:5181이고 위내용과 같이 지오서버를 사용하여 서비스 타일을 생성하기 위해 좌표계를 변경해야 한다.

이를 위해 기존 데이터를 복사하는 작업을 진행한다.

Query

 CREATE TABLE NEW_TABLE AS SELECT * FROM Z_NGII_UNSPT;


2. 좌표계 정보 업데이트

해당 테이블의 공간정보컬럼의 타입정보를 확인해 보면


geometry(MultiPoint,5179) 으로 되어 있다. 따라서 해당 좌표계정보 변경 쿼리를 아래와 같이 실행한다.

Query

 ALTER TABLE NEW_TABLE

ALTER COLUMN geom TYPE geometry(MultiPoint,4326) USING ST_Transform(ST_SetSRID(geom,5179), 4326);

위의 쿼리 내용은 https://postgis.net/2013/08/30/tip_ST_Set_or_Transform/ 에서 확인 할 수 있다.


최종적으로 해당 좌표가 변경된 것을 알 수 있다.


EPSG:5179                                                                                                                                                        EPSG:4326

Query

SELECT ST_ASText(GEOM) from NEW_TABLE;




국가지점번호 격자 파일을 제작하며, 실제 작성된 국가지점번호 파일의 위치 유효성 검증을 위해 산행길에 나섰다.

목적지는 남한산성 남문, 코스는 산성역에서 시작해서 도로 옆을 따라가는 등산로를 선택했다. 산행 중 발견되는 모든 국가지점번호판을 직접 확인하기로 했다. 그리 좋지 않은 체력으로 최초 2개만 확인하고 그 이후는 숨이 차서 발견 할 수 없었다. 어쩌면 없었을 수도 있다.

개인적 견해는 사람이 빈번하게 다니는 일반적 등산로 상에 굳이 설치할 필요가 있었을까하는 의문이 든다. 필자는 잘 정리된 등산로를 따라 걸었으며, 이 곳은 실제 긴급구조가 필요한 상황이 발생할 확률이 비교적 적은 곳이었다. 또한 실제 도로가 바로 옆에 있고 사람의 왕래도 빈번하고 통신 감도도 훌륭한 곳으로 설치된 국가지점번호판의 효용성이 그리 높아보이지는 않았다.
오히려 사람이 자주 다니지 않고, 일반적 등산를 벗어난 지역에 우선적으로 설치하는 것이 좋을 것 같아 보인다. 

발견한 국가지점번호는 아래 그림과 같으며 이 후 작성된 내용은 국가지점번호 설치 장소의 정확도를 검증하는 것이다.


지점번호판 설치장소 정확도 검증

사무실에 출근하여 산행을 하며 촬영한 국가지점번호의 위치 정확도 검증 작업을 진행하였다. 검증 절차는 국가지점번호를 5179좌표계(UTM-K)로 치환하고 치환된 좌표를 4326좌표계(WGS84 경위도) 로 좌표변환한 후 각각을 구글 지도와 QGIS (배경은 OpenStreetMap)에서 위치를 확인하였다. 확인 결과 설치된 위치는 비교적 정확할 것으로 추정할 수 있었다.
첫번째 지점번호의 경우 산림이 비교적 우거진 지역으로 어렵게 나마 주변 건물을 확인할 수 있긴 하지만 실제 번호판 설치를 위한 기준점 정의를 위한 지형지물로 활용하기에는 어려움이 있었을 것으로 추정된다.  두번째 지점번호는 도로에 인접한 지점으로 첫번째 지점번호보다는 설치 위치 확정 작업이 쉬웠을 것으로 보인다.
국가지점번호가 도로명주소가 부여되지 않은 산, 임야 등의 식별에 이용되다고 할 때 대부분의 지점번호는 측량이 불가능한 산악지역에 설치될 것이다. 이 때 10m 격자단위를 정확하게 측정하여 해당 장소에 국가지점번호 번호판을 설치할 수 있을 것으로 보이진 않는다.


  지점번호 

  EPSG:5179 좌표

 EPSG:4326좌표

  다사 6925 4045

  969255, 1940455

 127.1520, 37.4628

  다사 7049 4118

  970495, 1941185

 127.1663, 37.4694


직접제작한 국가지점번호 정확도 검증

프로그웍스가 제작한 국가지점번호를 PostGIS 에 등록한 후 SQL 질의(공간질의)를 통해 해당위치의 정확한 지점번호가 출력되는지 확인한 결과 위의 사진에 나타나는 지점번호가 정확히 조회됨을 확인함

SELECT num_name, st_astext(geom) FROM national_point_num_5179_26 WHERE st_intersects(geom, st_pointfromtext('point(969255 1940455)', 5179));
==> 질의 결과 : 다사69254045, POLYGON((969250 1940450,969250 1940460,969260 1940460,969260 1940450,969250 1940450))

SELECT num_name, st_astext(geom) FROM national_point_num_5179_26 WHERE st_intersects(geom, st_pointfromtext('point(970495 1941185)', 5179));
==> 질의 결과 : 다사70494118, POLYGON((970490 1941180,970490 1941190,970500 1941190,970500 1941180,970490 1941180))


제공파일 내역
-->국가지점번호 10m 인덱스 "다사"지역 EPSG 5179좌표계 : national_point_num_da_sa_5179.zip



배포 형식은 "GEOJSON" 입니다.
ESRI Shape 파일을 구성하는 각각의 구성요소(*.shp, *.dbf, *.shx) 파일은 2G 보다 클 수 없습니다. 빅데이터 시대에 맞지 않는 포맷인데 아직도 표준인양 사용하네요.


"가가"로 식별되는 100km 격자 인덱스 데이터를 제작한 후 실제 국가지점번호가 부여되는 10m 격자 파일을 제작했습니다. 제작지역은 위 그림을 참조하십시오.
100km 단위 하나의 격자는 10m x 10m 격자가 1억개 존재합니다. 제작한 자료는 PostGIS에 등록 후 Geoserver에서 서비스로 발행하여 육안 검증 및 성능 검증을 완료하였습니다.

최초 공개하는 격자 파일은 국가지점번호에 대한 이해 및 활용을 돕고자 무료로 개방합니다.
제작된 격자파일은 압축되지 않은 상태에서 약 17G 의 크기입니다. 실제 QGIS 등 보통의 어플리케이션에서는 열 수 없는 크기입니다. 데이터 변환도구 등을 이용하여 공간DBMS에 사용하시는것 추천합니다.

공개되는 "다사" 지역을 제외하고 다른 지역의 10m 개방은 무료로 개방하지 않음을 양해 부탁드립니다. 

작성된 10m 격자의 위치정확성 검증을 위하여 "서울시 서초구 방배동"의 저희가 잘아는 건물의 경위도 좌표를 구글지도에서 취득한 후, 취득한 좌표를 국가지점번호 부여의 기준이 되는 EPSG:5179 좌표계에서 해당 좌표로 변환한 후 부여된 번호가 맞는지 확인하였습니다.
아래 그림을 참조하면 방배로와 효령로33길 사이에 정확히 위치하는 것을 확인 할 수 있습니다.



제공파일 내역
-->국가지점번호 10m 인덱스 "다사"지역 EPSG 5179좌표계 : national_point_num_da_sa_5179.zip

What's metadata?

 일반적으로 메타데이터는 "데이터에 대한 데이터"라고 정의하지만, WIS (WMO Information System)에서는 데이터 카탈로그 또는 카탈로그의 요소(Element)라고 정의합니다.

  

WIS Discovery Metadata? 

 데이터 카탈로그는 메타데이터의 수집이며, 실제 (기상) 데이터 (SYNOP, TEMP, GRIB, 이미지 등)를 기술합니다.

  

ISO Geographical Metadata 

  • ISO 19115:2003 – conceptual UML
  • ISO/TS 19139:2007 – actual encoding in XML 

  

전형적인 Book Card (예시)

 도서관의 경우, 많은 책들을 쉽고 빠르게 찾기 위하여 카탈로그를 만들어서 관리하고 있습니다. 북 카탈로그 역시 메타데이터의 수집이며, 이는 실제 도서관에 있는 책들에 대한 설명을 다음과 같이 할 수 있습니다.

  • Title:            An Introduction to Dynamic Meteorology, 3rd Edition
  • Author:         James R. Holton
  • Subject:         Atmospheric Dynamics
  • Published:      April 12, 1992
  • Identifier:       ISBN 978-0123543554
  • Language:      English
  • Shelf:            1st floor, 551.51-H838i-3

 

WCMP(WMO Core Metadata Profile) 1.3 소개 

ISO 19115 (지리 정보 메타데이터 표준) 기반의 WMO Core Profile 은 WIS (WMO Information System)를 통해 사용 가능한 모든 정보에 대한 카탈로그를 작성하기 위해서 사용되며, WIS에서 활용되고 있습니다. 현재 최신 WCMP 버전은 1.3이며, 이는 2013년 5월 WMO 집행 이사회에서 승인되었습니다.

이 프로파일은 다양한 WMO 데이터 셋에 적용할 수있는 디렉토리 검색 및 교환에 대한 일반적인 정의를 제공합니다.

 

WMO Core Profile의 문서는 아래와 같이 2개의 파트로 구분됩니다.

 

WMO Core Profile에서 사용되는 코드 리스트(code lists) 는 다음과 같이 제공됩니다. 
 

 

 

WCMP 1.3의 철학

컴퓨터를 사용하여 유효성을 체크할 수 있도록 하며, ISO19115 스키마 모델만 16 페이지가 넘는 너무 광대한 지리정보 메타데이터를 최소화하며, 보다 자유로운 텍스트를 사용하고, 데이터 링크 및 씨맨틱 웹을 사용하고자 개발되었습니다.

 

WCMP 1.3의 요구사항  

  • ISO 19139의 XML 스키마 유효성 검사
  • ISO 19139 부록 A에 있는 모든 제약조건을 충족 
  • 기본 네임 스페이스 없음
  • GML 네임 스페이스 : http://www.opengis.net/3.2 사용
  • 필수항목: gmd:fileIdentifier 이며, WIS에서 유일해야 함
  • "WMO_CategoryCode" 에서 "theme" 키워드는 최소한 한번 사용해야 함
  • 하나의 시소러스에서 키워드는 하나의 "MD_Keyword" 로 이동되어야 함
  • 지리 데이터를 위한 경계 박스 (Bounding box) 가 있어야 함

         글로벌 교환을 위한 데이터를 위하여 다음과 같은 사항이 요구됨

  • 키워드 "GlobalExchange" 사용
  • fileIdentifier 의 prefix 로 "urn:x-wmo:md:int.wmo.wis::" 사용
  • resourceConstraints/*/otherConstraints 에 "하나의 데이터 정책 코드" 와 "하나의 GTS 우선순위 코드" 사용

 

WCMP 1.3의 권장사항  

  • "gmd:fileIdentifier" 는 URI 형태의 구문을 사용
  • MD_Metadata/contact/*/role = pointOfContact
  • "Abstract" 의 내용은 명확하고 간결한 설명을 제공하야 함
  • "identificationInfo/*/pointOfContact" 는 최소한 다음과 같은 내용이 포함되어야 함
    – A name (person, organisation, or position)
    – Email address
  • "citation/*/date/*/date" 의 날짜 표기는 "YYYY-MM-DD" 형식으로 사용
  • 글로벌 데이터를 위한 "resourceConstraints" 는:
    – "accessConstraints" 와 "useConstraints" 모두 사용해야 함
  • 단일 "resourceConstraints" 는 단일 소스에 대한 제약조건 사용

 

WCMP 1.3에 대한 필요한 안내  

  • Contact points

- 메타데이터 저자

- 데이터 originator / archiver / distributor

- WMO 프로그램 또는 국제 프로젝트

  • Keywords 에 대한 시소러스

- 물리적인 양 또는 변수명

- WMO 프로그램명

- 지리적인 영역

 

  • 날짜 범위
  • 업데이트 주기
  • 데이터에 대한 접근방법
  • 관측지점 정보
  • 그리드 데이터 속성

- 수직적 레벨

- 예보 시간

- 공간 해상도

 

WCMP 참조 템플릿

 - 메타데이터 예시: https://wiswiki.wmo.int/tiki-download_file.php?fileId=3289 

- 메타데이터 템플릿: https://wiswiki.wmo.int/tiki-download_file.php?fileId=3290

  

 

WIS (WMO Information System) 개요 및 현황 

WIS는 세계기상기구(WMO)의 새로운 정보시스템체계로서, 향후 GTS (Global Telecomunication System)을 개선 및 대체할 전 세계 기상자료 수집, 공유, 분배, 교환 체계를 의미하며, 초고속 네트워크, VPN, 인터넷 웹 기술 등 다양한 신기술과 기존 GTS 체계를 모두 포함하며 신뢰성, 유연성 및 확장성 등과 함께 기상자료의 정규수집/분배, 비정규적 자료제공 서비스 기능을 모두 제공합니다.

 

[WIS 체계도]

 

WIS 시스템은 각 센터에서 생산 및 수집하는 기상자료에 대하여 ISO19115 (지리정보) 기반의 WCMP 표준 메타데이터를 작성하여 보유하고, 타 WIS 시스템과 교환 및 동기화를 위해 OAI-PMH (Open Archives Initiative Protocol for Metadata Harvesting) 프로토콜을 이용합니다.

 

국내에서 생산되는 기상자료에 대한 메타데이터 뿐만 아니라, 전세계 기상센터에서 생산된 자료에 대한 메타데이터는 GISC서울 에서 보유하고 있으며, 사용자는 웹포털에서 메타데이터를 검색할 수 있습니다.

 - GISC서울 URL : http://gisc.kma.go.kr 


다음의 그림은 GISC서울 웹포털에서 지상데이터 (surface) 를 검색하는 예시화면과 해당 메타데이터가 조회되어 표출된 결과화면입니다.

GISC서울은 뷰어 형태로 지리정보 기반의 메타데이터를 표출할 수 있으며, XML 형태로 전체 메타데이터의 내용을 표출 또는 다운로드할 수 있습니다.

 

[지상 메타데이터 검색 및 결과화면(예시)]

  

 [지리정보 메타데이터를 뷰어로 표출]

  

[지리정보 메타데이터를 XML 형태로 표출]

 

제공파일 내역
-->국가지점번호 100Km 인덱스 EPSG 5179좌표계 : national_point_num_5179.zip

national_point_num_5179.zip

--> 국가지점번호 100Km 인덱스 EPSG 4326좌표계 : national_point_num_5179_to_4326.zip

national_point_num_5179_to_4326.zip


필자는 국가지점번호 격자를 이용하여 공간분석을 통해 국가지점번호를 알려주는 번호판 설치가 필요한 지역, 통신 음영 지역 중 비교적 사람의 왕래가 있어 무선기지국의 설치가 필요한 지역 추출을 위해 분석작업을 수행하는 목적을 가지고 있다. 

국가지점번호가 "산악, 해양 등 비거주지역에서의 기관마다 서로 다른 위치표시체계를 일원화하여 소방·경찰·국립공원 등 관계기관이 정보공유·협업을 통해 신속한 구조·구급 등 취약지역의 안전관리체계 강화 "하는 목적을 가지고 있다면 이의 활용을 위한 정보의 제공이 무엇보다도 중요할 것이지만 안타깝게도 홍보하는 자료 외에 활용을 위한 자료를 필자의 능력으로는 찾을 수 없었다.

도로명주소 사이트, 국가공간정보포털, 공공데이터포털, 공간정보오픈플랫폼 등 다양한 공공개방포털 어디에서도 국가지점번호를 정의하는 파일을 찾을 수 없었다.

왜 이렇게 자료찾기가 어려운가 고민해보니 다음과 같은 사유를 추정할 수 있었다.

1. 파일은 있으되 어떤 사유로 비공개
2. 공개해야하는 파일의 크기가 너무 크다 (실제 10m*10m 격자가 41억개 존재)
--> "가가"처럼 식별되는 100Km * 100Km 격자가 41개 존재
--> 각각의 격자는 10m * 10m 격자가 1억개 존재
--> 결국 서비스로 이행하여 공간 연산을 적용하기에 성능 문제 발생 가능성
3. "마다", "마라" 지역이 일본 영토를 포함(혹시 외교적인 문제!!)

결국 국가지점번호의 활용을 위해 직접 제작하기로 결정하고, EPSG:5179 좌표계를 기준으로 "가가"와 같이 식별되는 대표 인덱스 파일을 제작하고, 각각의 인덱스 파일을 별도로 제작하기로 결정함

"가나"로 식별되는 메인인덱스 파일은 41개의 ROW만 가짐으로 제작하는데 큰 어려움은 없었으나, 개별 10m x 10m 격자는 쉽지 않음을 알 수있었다.
시범적으로 서울을 포함하는 "다사"지역을 제작해 보니 파일크기만 무려 20G 이상으로 제작됨. 격자 조각 하나에 5쌍의 좌표로 구성된 Polygon이 존재함으로 80byte의 크기를 가지고 이게 1억건이니 좌표크기만으로 물리적 크기가 7.5G 정도 되는 것이 확인됨. 

단순하지만 제작할 수 있는 업무 절차는 정립되었고 이를 효율적으로 진행할 방법론을 재정립하기로 함. 분명히 크기 문제는 제작 및 활용의 제약사항이 되었을 것이고 해결법은 직접 찾기로 결정함.

제작된 인덱스 파일을 Geoserver를 이용하여 서비스로 발행하여 QGIS에서 확인하고, Google Earth 위에서도 확인해 보니 행자부에서 배포하는 국가지점번호 (위 그림) 와 차이가 없음을 확인함

 

우선적으로 "가나"로 식별되는 메인 인덱스 파일을 필요한 분들이 이용할 수 있도록 무상으로 공개합니다. 

추후 세부 10m x 10m 격자 파일도 공개할 예정이나 프로그웍스는 공공기관이 아니므로, 무료개방은 하지 않겠습니다. 그러나 직접 제작하는 수고와 기간을 생각하면 납득할 수 있는 비용으로 과금하겠습니다.

제공파일 내역
-->국가지점번호 100Km 인덱스 EPSG 5179좌표계 : national_point_num_5179.zip

national_point_num_5179.zip

--> 국가지점번호 100Km 인덱스 EPSG 4326좌표계 : national_point_num_5179_to_4326.zip

national_point_num_5179_to_4326.zip


자세한 내용이나 문의는 방명록에 연락처 남겨주세요.

Qgis를 이용한 레이어 병합

필자는 Qgis 3.4버전에서 작업을 진행하였습니다.

작업에 활용한 데이터는 대한민국 국립, 군립 등의 공원 데이터 입니다. (

국립, 군립 공원 데이터.zip
다운로드

)

1. 데이터 업로드 하기 

작업할 데이터중 shp파일들을 Qgis에 업로드 합니다.

 

2. 병합하기 

다음과 같은 순서로 레이어 병합을 진행합니다.

백터 -> 데이터 관리도구 -> 백터레이어 병합  

 

* 입력 레이어 선택버튼을 눌러 전체 선택을 하고 확인을 클릭합니다.   

* 지정할 좌표계를 선택합니다.

 

* 병합한 산출물 버튼을 누르고 파일로 저장합니다. 

 

3. 병합된 레이어 확인하기 

병합이 완료된 레이어를 Qgis에 업로드 합니다. 

개요

며칠 전 리얼타임테크의 카이로스를 데이터 프로바이더로 설정하여 지도서비스를 발행하고, Disk 기반 DBMS 대비 응답 속도가 확실히 빠른 것을 확인할 수 있었다. 이에 동일한 환경에서 각각 어느정도 성능을 보이는지 Apache JMeter를 이용하여 시험을 진행

> 이전 포스트 확인 : 메인 메모리 기반 고성능 웹 맵 서비스 (WoW! Kairos)


목적

메인메모리 DBMS와 일반 DBMS가 보이는 성능 차이를 정량적으로 확인함


환경구성

1. Server : Linux Container, 가상서버 위에 설치되는 DBMS
2. DBMS : Kairos (제조사:리얼타임테크),  PostGIS
3. WAS(Web Application Server) : Linux Container(메모리 512M 할당) 에 설치된 Apache Tomcat

4. Test 대상 App : 자체 제작한 간단한 WMS
5. 테스트 도구 : Apache JMeter
6. 테스트 방법 : 동사시용자를 1명에서 10명까지 늘려가며 각 10번씩 호출하고 나온 계측 값을 평가


시험결과

1. 메인메모리 DBMS가 빠른 처리 성능으로 단일 테이블에 접근하는 WMS의 경우 동시에 수용할 수 있는 사용자가 더 많음을 확인 
2. 제한적인 경량 컨테이너 환경에서 카이로스와 공개SW인 PostGIS 모두 만족할 만한 성능을 보임
3. 경량컨테이너로 구성되는 개별 서비스를 Proxy로 웹서버 계층에서 통합한다면 저비용으로 고성능 서비스가 가능함
4. PostGIS의 경우 동시사용자 7명을 기준으로 초당 쓰루풋이 감소하여, 성능향상을 위한 확장이 요구되었고, Kairos의 경우 9명을 기준으로 성능향상을 위한 확장이 요구됨





서비스 바로가기 (Kairos 맵 서비스)

서비스 바로가기 (PostGIS 맵 서비스)

+ Recent posts