WSL 설치 & tomcat 설치 및 배포

 

Window 10에서 Windows Subsystem for Linux(WSL) 설치와 tomcat 설치 및 배포하는 방법을 알려드리겠습니다.

 

리눅스용 윈도우 하위 시스템(Windows Subsystem for Linux, WSL)은 윈도우 10에서 네이티브로 리눅스 실행 파일(ELF)을 실행하기 위한 호환성 계층이다. WSL은 리눅스 커널 코드가 포함되지 않은, 마이크로소프트가 개발한 리눅스 호환 커널 인터페이스를 제공하며, 그 위에 리눅스 유저랜드를 실행시킬 수 있는데, 이를테면 우분투,SUSE페도라를 들 수 있다.이러한 유저랜드는 배시 셸과 명령 언어를 포함할 수 있으며, 네이티브 리눅스 명령 줄 도구(sed, awk 등)들과 프로그래밍 언어 인터프리터(루비, 파이썬 등)가 내장된다.

링크: https://ko.wikipedia.org/wiki/리눅스용_윈도우_하위_시스템

출처: 위키백과

 

요구되는 사항은 64비트 운영체제, x64 기반 프로세서, Window10 버전 1607이상 입니다.

필자의 컴퓨터는 64비트 운영체제, x64 기반 프로세서, Window10 버전 1903입니다.

 

WSL 설치

컴퓨터의 제어판 > 프로그램 > 프로그램 및 기능 화면에 'Windows 기능켜기/끄기'를 실행하여 'Linux용 Windows 하위 시스템' 항목을 활성화합니다.

 

Ubuntu(Linux) 설치

Microsfot Store에서 Ubuntu를 검색하여 설치합니다.

 

 

Java8 설치

설치한 Ubuntu를 실행합니다.

UNIX username과 UNIX password를 설정합니다.

(필자의 username은 pinkprog입니다.)

 

 

 

아래 명령어를 실행함으로써 리눅스 패키지를 업데이트, 업그레이드, 정리작업을 합니다.

sudo apt –y update

sudo apt –y upgrade

 

sudo apt –y autoremove

 

 

 

아래 명령어를 실행함으로써 자바8의 jre, jdk, maven을 설치합니다.

sudo apt -y install openjdk-8-jre-headless openjdk-8-jdk-headless maven

 

아래 명령어를 통해 Java8이 잘 설치되었는지 확인합니다.java -version



tomcat8 설치

아래 명령어를 실행함으로 tomcat8을 설치합니다.sudo apt-get install tomcat8

 

tomcat8이 잘 설치되었는지 확인합니다.sudo /usr/share/tomcat8/bin/version.sh




tomcat8 구동

먼저 톰캣의 포트가 외부 접속이 되도록 방화벽을 변경해야 합니다.

캣의 기본 포트는 8080입니다. 


sudo ufw allow 8080/tcp

 

톰캣을 실행합니다

sudo service tomcat8 start

 

 

인터넷 브라우저에서 localhost와 톰캣포트를 입력하여 정상적으로 작동되는지 확인합니다.

예:  http://localhost:8080/

 

 

 

WAR 파일 배포

WAR 파일을 /var/lib/tomcat8/webapps 경로로 옮겨야 합니다.

(필자가 tomcat으로 배포하고자 하는 war 파일은 

C:\intelliJ\stdmap_war2 에 있습니다.

stdmap_war.war파일을  /var/lib/tomcat8/webapps 경로로 옮겨야 합니다.)

 

 

먼저 관리자 계정 password를 설정합니다.

sudo passwd

 su - 명령어를 사용하여 관리자 계정으로 전환합니다.

 

war 파일이 있는 경로로 들어갑니다.cd /mnt/c/intelliJ/stdmap_war2
stdmap_war.war 파일을 

/var/lib/tomcat8/webapps 경로로 옮깁니다.

mv stdmap_war.war /var/lib/tomcat8/webapps
tomca8을 재시작합니다.sudo service tomcat8 restart

 

인터넷 브라우저에서 해당 파일이 톰캣에 잘 배포됐는지 확인합니다.

예: http://localhost:8080/stdmap_war

 

 

 

 

 

 

 

 

 

개요

WPS (Web Processing Service) 지형 공간 프로세스, 알고리즘 및 계산을 게시하기위한 OGC 서비스.
 
Geoserver에서 WPS extension을 설치하고, 추가로 OpenGXT를 설치하는 방법에 대해 설명하겠습니다.
 
 
위 링크를 타고 들어가서 'Installing the WPS extension' 영역을 확인하면 geoserver download page. 와 함께 WPS 설정 방법도 자세하게 설명하고 있다.
 

1. WPS pluging download

메뉴얼을 살펴보면 Geoserver download page 에서 extension을 다운로드 할 수 있다.

 

설치된 Geoserver의 버전을 확인하여 자신이 설치한 지오서버에 해당하는 버전을 선택한다.

버전확인은 설치된 지오서버 메인 페이지에서 확인하거나 Geoserver 설치 디렉토리 루트에 VERSION.txt 파일에 적혀있다.

 

본인은 2.14.0 버전을 사용하고 있어 해당 버전을 선택

 

geoserver 해당 버전 다운로드 화면에서 아래쪽 'Extensions' 영역에 있는 버튼을 선택한다.

 

해당 라이브러리중 geoserver-2.14.0-wps-plugin.zip 을 찾아 다운로드

 

2. jar 파일 추가 후 Geoserver 재시작

다운로드 받은 zip파일을 압축해제 하여 6개의 jar파일을 지오서버가 설치되어 있는 루트 밑 WEB-INF/lib 위치에 복사해준다.

 

Geoserver에 WPS 메뉴가 활성화되고 Web Processing Service 확인 할 수 있다.

 

GetCapabilities는 작업이 가능한 프로세스를 설명하는 서비스 메타 데이터 및 메타 데이터를 포함하여 서비스 제공의 세부 사항을 요청합니다.

http://localhost:8080/geoserver/ows?service=WPS&version=1.0.0&request=GetCapabilities 

 

3. OpenGTX download

https://sourceforge.net/projects/mango-spatialstatistics/

위 링크를 타고 들어가서 본인의 Geoserver 버전과 같은 버전을 다운로드합니다.

 

다운로드 받은 zip파일을 압축해제 하여 2개의 jar파일을 지오서버가 설치되어 있는 루트 밑 WEB-INF/lib 위치에 복사해준다.

 

이후 Geoserver 를 재시작하면, 데모의 WPS 요청 빌더에 statistics 프로세스들이 추가된 것들 확인 할 수 있다.

 

OpenGXT 설치전에 Geoserver WPS가 사전에 설치되어 있어야 정상적으로 설치된다.

이후 다음포스팅에서 WPS에 사용방법에 대해 설명하도록 하겠습니다.

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들을 삭제한 모습입니다.

 

Openlayers3에서 사용자가 지정한 위치를 표시하고 Map을 PDF로 저장하는 방법을 알아보겠습니다.

먼저 결과화면을 확인해보겠습니다.

서비스바로가기

 
 
배경지도는 Openlayers3를 이용하였으며, 지도를 PDF로 저장하기 위해 아래 모듈을 사용하였습니다.

1. mapExport.jsp

 

위에 첨부한 파일은 원하시는 이클립스 디렉토리에 저장하셔서 사용하시면 되겠습니다.

1
<script src="<c:url value='/js/FileSaver/fileSaver.js'/>"></script>
cs

<script></script>부분부터 확인해보도록 하겠습니다. 

    • 3행 : 사용자가 원하는 위치를 클릭시 사용할 마커 이미지 및 정보를 iconStyle에 등록합니다.
    • 11행 : 클릭한 위치의 정보와 마커이미지를 vectorSource에 담아두기 위해 선언합니다.
    • 13행 : 지도에 표시하기 위해 vectorLayer에 vectorSource를 담아두겠습니다.
    • 16행 : 지도 불러오기
    • 24행 : 화면에 표출되는 지도의 좌표정보를 등록하는 곳입니다.
    • 32행 : 지도 클릭 이벤트
    • 38행 : iconFeature에 클릭한 위치의 좌표정보를 등록합니다.
    • 42행 : iconFeature에 3행에서 등록한 이미지 및 정보를 넣어줍니다.
    • 44행 : 42행를 통해 등록한 정보를 vectorSource에 담아줍니다. ==> 담아준 정보는 지도 불러오기 중 지도의 layers부분(21행)을 통해 지도에 표시됩니다.
    • 48행 : 지도부분을 PDF로 Export하는 부분입니다. Export를 위해서 위쪽에 올려드린 첨부파일이 필요합니다.
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
<script type="text/javascript">
$(document).ready(function(){
    var iconStyle = new ol.style.Style({
        image: new ol.style.Icon(({
            anchorXUnits: 'fraction',
            anchorYUnits: 'pixels',
            src: '/images/common/waypoint.png',
            scale: 0.5
        }))
    });
    var vectorSource = new ol.source.Vector();
    
    var vectorLayer = new ol.layer.Vector({
        source: vectorSource
    });
    var map = new ol.Map({
        layers: [
            new ol.layer.Tile({
                source: new ol.source.OSM()
            }),
            vectorLayer
        ],
        target: 'map',
        view: new ol.View({
            center: [1987706.31,4522199.26],
            zoom: 3,
            minZoom: 3,
            maxZoom: 19
        })
    });
    
    map.on('click',function(evt){
        var coordinate = evt.coordinate;
        
        var coordLon = coordinate[0];
        var coordLat = coordinate[1];
        
        var iconFeature = new ol.Feature({
            geometry: new ol.geom.Point([coordLon,coordLat])
          });
        
        iconFeature.setStyle(iconStyle);
        
        vectorSource.addFeature(iconFeature);
        
    });
    
    document.getElementById('export-png').addEventListener('click'function() {
        map.once('postcompose'function(event) {
            var canvas = event.context.canvas;
            canvas.toBlob(function(blob) {
                saveAs(blob, 'UserMap.png');
            });
        });
        map.renderSync();
    });
});
 
</script>
cs

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

1
2
3
4
<body>
    <div id="map"></div>
    <a id="export-png" class="btn btn-default"><i class="fa fa-download"></i> Download PNG</a>
</body>
cs

2. 결과화면

QGIS 표현식을 이용하여 원하는 객체를 선택하는 방법을 알려드리겠습니다.

해당 기능은 QGIS를 이용하여 데이터 오픈 시, 원하는 객체를 별도로 저장하거나 확인하는데 유용합니다.

활용 데이터

    • 국가공간정보포털 > 오픈마켓 > 행정구역시군구_경계 데이터

시나리오

목적 : 행정구역시군구_경계(경북) 데이터에서 법정동 코드가 478로 시작되는 지역의 객체만 추출

    • QGIS를 통해 데이터의 속성테이블을 확인
    • 행정표준코드관리시스템 법정동 코드 조회를 통해 법정동 코드 및 위치 확인 (선택 객체 추출 후, 확인용)
    • 표현식을 이용한 객체선택 표현식으로 478로 시작하는 법정동 코드의 객체 추출 

 

1. 행정구역시군구_경계 데이터(경북)를 QGIS에서 오픈합니다. 

 

2. QGIS에서 속성테이블을 확인합니다.

    • 레이어 > 우클릭 > 속성 테이블 열기

 

    • 속성테이블의 법정동 코드 컬럼 확인

속성테이블을 보게되면 첫번째 컬럼 ADM_SECT_C가 법정동 코드로 확인됩니다. 법정동 코드가 맞는지 확인은 행정표준코드관리시스템 법정동 코드 조회를 통해 확인합니다 (https://www.code.go.kr/stdcode/regCodeL.do)

 

3. [표현식을 이용한 객체선택] 버튼 클릭 및 객체 선택 표현식 입력

    • 표현식을 이용한 객체선택 아이콘을 클릭

 

여기부터 핵심입니다 집중!!!!!!!!!!!!!!!!!!

첫 번째로 컬럼을 선택하기 위해 위 그림에 표시된 [필드와 값]부분을 선택하여 법정동 코드 등록되어있는 컬럼을 더블 클릭합니다!!

그러면 좌측화면에 "ADM_SECT_C"라고 표시됩니다.

두 번째로 [연산자]부분을 선택하여 LIKE를 더블 클릭합니다. 

"[필드명]" LIKE '찾을 값' 형태로 생각하시면 좋겠습니다. 

그리고!!! 중요한 부분!!!!!!!!! [필드명]부분은 "" 요렇게!!! 찾을 값은 '' 요렇게 작성합니다. 저도 이유는 잘 모르겠네요;;;;

뒤에 %표시는 전체를 의미합니다. 저는 법정동 코드가 478로 시작하는 모든 법정동 코드를 찾기 위해 '478%'이렇게 입력했습니다. 만약 '%478%' 이렇게 앞뒤로 넣게되면 중간부분에 478 숫자가 들어간 모든 값을 찾을겁니다.

 

마지막으로 우측 하단에 객체 선택이라는 버튼이 있습니다. [객체 선택]버튼을 클릭합니다.

3. 선택된 객체로 이동

화면 뒤쪽이 보이시나요??? 법정동 코드 478로 시작하는 데이터가 4개이고 속성테이블 뒤편 화면을 보시면 선택된 객체가 노란색으로 나타납니다.

선택된 객체 우클릭 시, 위 그림처럼 나타납니다. 3번째 [Zoom to Feature]를 클릭합니다. 그러면 선택한 객체의 위치로 이동합니다.

 

4. 확인

법정동 코드가 478로 시작하는 4개 지역이 선택되어 있고, 그 지역으로 이동하여 QGIS상에서 보여줍니다.

만약 선택한 객체를 별로의 shp파일로 저장을 원하신다면 아래 그림처럼 진행하시면 되겠습니다.

선택한 객체를 다른이름으로 저장(shp, dxf 등...) 방법은 국가공간정보포털 전문가iN에 있는 이미지를 이용하여 설명하겠습니다.

 

<이미지 출처 : 국가공간정보포털 전문가iN>

<이미지 출처 : 국가공간정보포털 전문가iN>

 

그리고 선택한 객체만 저장하기 위해 위 체크박스를 선택하시면 표현식을 이용해 선택한 객체를 별도의 shp파일로 저장할 수 있습니다.

 

 

Openlayers3에서 전세계 공항의 위치 정보를 이용한 지도 표출을 알아보겠습니다. 먼저 결과화면을 확인해보겠습니다.

 

배경지도는 Openlayers3를 이용하였으며, 공항 위치정보 데이터 또한 Openlayers에서 제공하는 데이터를 이용하였습니다.
원래는 국내공항의 위치정보를 이용하여 기능을 보여드리려 하였지만 비행이동이 화려하게 표현되지 못해 Openlayers에서 제공하는 데이터를 그대로 사용하였습니다. 
 
전세계 공항 정보 .json 데이터는 아래에 첨부파일 다운로드 받으시면 됩니다.
flights.json
다운로드

그리고 위 화면처럼 타원을 그리며 비행기가 날아가도록 하기위해 별도의 모듈을 이용했습니다.

arc.js
다운로드

 

1. Flights.jsp

1
2
3
4
5
6
7
8
9
10
<head>
<title>flights</title>
<meta charset="utf-8">
 
<link type="text/css" rel="stylesheet" href="<c:url value='/css/openlayers/ol.css'/>" />
 
<script src="<c:url value='/js/openlayers/ol.js'/>"></script>
<script src="<c:url value='/js/arc/arc.js'/>"></script>
 
</head>
cs

지금까지 등록된 Openlayers 예제를 보셨다면 소스는 쉽게 이해하실 수 있으실 겁니다. 먼저 <script></script>부분을 살펴보도록하겠습니다.

    • 3행~21행 : Openlayers3 지도 로드
    • 23행 : 비행 이동 경로(라인) 스타일
    • 31행 : 66행을 통해 비행 이동 시작 및 flightsSource에 feature 정보 넣기
    • 40행 : 66행을 통해 비행 이동 도착
    • 66행 : 공항 위치정보 json에 있는 위치정보를 이용하여 시작점/도착점 설정
    • 100행 : 비행이동 경로 지도에 표출
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
<script type="text/javascript">
$(document).ready(function(){
    var map = new ol.Map({
        target: 'map',
        layers: [
            new ol.layer.Tile({
                source: new ol.source.Stamen({
                    wrapX: false,
                    layer: 'toner'
                })
            })
        ],
        view: new ol.View({
            //경도,위도
            center: [1987706.31,4522199.26],
            zoom: 3,
            minZoom: 3,
            maxZoom: 19
        }),
        
      });
    
    var style = new ol.style.Style({
        stroke: new ol.style.Stroke({
            color: '#FF4233',
            width: 2
        })
    });
    
    var flightsSource;
    var addLater = function(feature, timeout) {
        window.setTimeout(function() {
            feature.set('start'new Date().getTime());
            flightsSource.addFeature(feature);
        }, timeout);
    };
    
    var pointsPerMs = 0.1;
    
    var animateFlights = function(event) {
        var vectorContext = event.vectorContext;
        var frameState = event.frameState;
        vectorContext.setStyle(style);
        
        var features = flightsSource.getFeatures();
        for (var i = 0; i < features.length; i++) {
            var feature = features[i];
            if (!feature.get('finished')) {
                var coords = feature.getGeometry().getCoordinates();
                var elapsedTime = frameState.time - feature.get('start');
                var elapsedPoints = elapsedTime * pointsPerMs;
                
                if (elapsedPoints >= coords.length) {
                    feature.set('finished'true);
                }
                
                var maxIndex = Math.min(elapsedPoints, coords.length);
                var currentLine = new ol.geom.LineString(coords.slice(0, maxIndex));
            
                vectorContext.drawGeometry(currentLine);
            }
        }
        map.render();
    };
    
    flightsSource = new ol.source.Vector({
        wrapX: false,
        loader: function() {
            var url = "/js/data/geojson/flights.json";
            fetch(url).then(function(response) {
                return response.json();
            }).then(function(json) {
                var flightsData = json.flights;
                for (var i = 0; i < flightsData.length; i++) {
                    var flight = flightsData[i];
                    var from = flight[0];
                    var to = flight[1];
                    
                    var arcGenerator = new arc.GreatCircle(
                        {x: from[1], y: from[0]},
                        {x: to[1], y: to[0]});
                    
                    var arcLine = arcGenerator.Arc(100, {offset: 10});
                    if (arcLine.geometries.length === 1) {
                        var line = new ol.geom.LineString(arcLine.geometries[0].coords);
                        line.transform(ol.proj.get('EPSG:4326'), ol.proj.get('EPSG:3857'));
                        
                        var feature = new ol.Feature({
                            geometry: line,
                            finished: false
                        });
                        addLater(feature, i * 50);
                    }
                }
            map.on('postcompose', animateFlights);
            });
        }
    });
 
    var flightsLayer = new ol.layer.Vector({
        source: flightsSource,
        style: function(feature) {
            if (feature.get('finished')) {
                return style;
            } else {
              return null;
            }
        }
    });
    map.addLayer(flightsLayer);
});
 
</script>
 
cs

<body></body>및 <style></style>부분입니다.

1
2
3
4
5
6
7
8
9
<style>
#map {
    height:730px;
}
</style>
 
<body>
    <div id="map"></div>
</body>
cs

2. 결과화면

Qgis를 이용한 shape 파일 dxf파일 변환

국가공간정보 포털에서 연속지적도를 다운로드 받아 shape파일을 dxf파일로 변환하는 작업을 하려고 합니다.

  • Qgis 버전 2.18 
  • 국가공간정보포털 연속지적도 서울 서초구 데이터

 

1. 국가공간정보포털에서 연속지적도_서울 서초구 데이터 취득

국가공간정보포털 오픈마켓에서 연속지적도_서울 데이터셋 중 서초구 데이터를 다운로드 받는다.

 

2. 서울 서초구 데이터 동단위로 저장하기

Qgis에 다운로드 받은 데이터를 추가한 후에 동단위 데이터로 저장하려고 합니다. 대상 동은 임의로 방배동으로 하고, 레이어 속성테이블에서 표현식을 통해 방배동의 데이터를 선택한후 내보내는 작업을 진행합니다.

표현식은 "pnu"  LIKE '1165010100%' 으로 방배동의 pnu코드에 해당하는 데이터를 선택하였습니다.

 

다음으로 선택한 객체내보내기를 통하여 방배동의 데이터만 저장하도록 하겠습니다.

빨간 박스에 있는 '선택된 객체만 저장' 을 체크하시고 저장하셔야 방배동 객체만 저장이됩니다.

 

 

3. DXF 내보내기

상단메뉴 > 프로젝트 > DXF 내보내기 선택

  • 심볼 유형 모드 : 심볼 레이어 심볼 유형 
  • 심볼 모드 스캐일 : 1:1,000
  • 인코딩 cp949
  • output layer attribute : jibun

 

4. DXF Import 하기

Qgis 플러그인 중 'Another DXF Importer / DXF2Shape Converter' 를 사용하여 새로 만든 DXF파일을 Import 하겠습니다.

상단메뉴 플러그인 > 플러그인 관리 및 설치 를 선택하여 DXF로 검색하시면 해당 플러그인이 검색됩니다.

 

플러그인 설치하기 버튼을 선택하여 플러그인을 설치하면 상단메뉴 벡터 > DXF Import / Convert 메뉴가 활성화되며 해당 버튼을 선택하시면

아래의 내용과같이 DXF 자료를 Qgis에 Import 하실 수 있습니다.

시각적 효과를 위해 국가공간정보포털에서 제공하는 수치지형도를 Import 하겠습니다.

수치지형도v1.0(1:5000)_Bessel_서울 자료 다운로드 

  • 도엽번호 : 37612010
  • Charset : cp949
  • 좌표계 : EPSG:5174 (투영체 계수 포함)

 

DXF Import 결과

leaflet 배경지도에 Shape File파일을 서버측 코드 없이 웹 브라우저를 통해 GeoJson으로 변환하여 올리는 방법을 알아보겠습니다. 

 

먼저 결과화면을 확인해보겠습니다.

서비스바로가기

 

※ 배경지도 라이브러리

※ shape File을 지도에 올리기 위한 js 파일

(zip포맷으로 올린 Shape File의 압축을 풀고 JavaScript의 인코딩으로 데이터를 서버 측 코드 없이 브라우저를 통해 GeoJson으로 변환 및 표출 모듈)

 

preview.js : Shap File을 GeoJson으로 변환

dbf.js : .Shape File파일의 속성부분 정의
 

1. index.jsp

    • 37행 : leaflet 지도 올리기
    • 40행 : leaflet 배경지도 Vworld로 변경
    • 42행~71행 : 속성정보를 표출
    • 72행~104행 : zip파일 형태의 Shape File을 geojson으로 변경 및 지도에 표출하는 부분
    • 77행 : loadshp 기능 실행 -> preview.js  및 perprocess.js 실행(GeoJson으로 변환 및 Shape File의 .dbf 파일 속성정보 모듈)
 

 

 

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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<!doctype html>
<html lang="ko">
<head>
<title>shp loading</title>
<meta http-equiv="Content-Type" content="charset=UTF-8" />
<meta name="author" content="Gipong">
<meta name="keywords" content="shapefile,dbf,geojson,preview">
 
<link rel="stylesheet" href="<c:url value='/resources/css/leaflet/leaflet.css'/>" />
<link rel="stylesheet" href="<c:url value='/resources/css/semactic/semantic.min.css'/>" />
<link rel="stylesheet" href="<c:url value='/resources/css/demo/demo.css'/>" />
<link rel="stylesheet" href="<c:url value='/resources/css/jquery/jquery-ui.min.css'/>" />
 
<script src="<c:url value='/resources/js/pro4j/pro4j.js'/>"></script>
 
<script src="<c:url value='/resources/js/jquery/jquery-2.1.1.min.js'/>"></script>
<script src="<c:url value='/resources/js/jquery/jquery-ui.min.js'/>"></script>
<script src="<c:url value='/resources/js/leaflet/leaflet.js'/>"></script>
 
<script src="<c:url value='/resources/js/lib/jszip.js'/>"></script>
<script src="<c:url value='/resources/js/lib/jszip-utils.js'/>"></script>
<script src="<c:url value='/resources/js/lib/jszip-utils-ie.js'/>"></script>
 
<script src="<c:url value='/resources/js/semantic/semantic.min.js'/>"></script>
<script src="<c:url value='/resources/js/preprocess/dbf.js'/>"></script>
<script src="<c:url value='/resources/js/preview/preview.js'/>"></script>
 
</head>
<script type="text/javascript" charset="UTF-8">
$(document).ready(function() {
 
    var map = L.map('map').setView([36.3991782860765514129048.150024414], 7),
    file,
    vector;
    L.tileLayer('http://xdworld.vworld.kr:8080/2d/Base/201802/{z}/{x}/{y}.png').addTo(map);
    
    function initVector () {
        vector = L.geoJson([], {
            style: function (feature) {
                return feature.properties.style;
            },
            onEachFeature: function (feature, layer) {
 
                layer.on({click: function(e) {
                    vector.eachLayer(function(l) {
                        vector.resetStyle(l);
                    });
 
                    $('.tbodyContent').remove();
                    var tbody = '<tbody class="tbodyContent">';
                    for (var key in e.target.feature.properties) {
                        tbody +=
                            ('<tr class="center aligned"><td>'+ key + '</td><td>' + e.target.feature.properties[key] + '</td></tr>');
                    }
                    $('#attribute').append(tbody + '</tbody>');
                    $('#attr').fadeIn(300);
                    map.panTo(e.latlng);
 
                    if('setStyle' in e.target) e.target.setStyle({
                        fillColor: '#FF0',
                        fillOpacity: 0.6
                    });
                }});
            }
        }).addTo(map);
    }
    function loadShpZip() {
        var epsg = ($('#epsg').val() == '') ? 4326 : $('#epsg').val(),
        encoding = ($('#encoding').val() == '') ? 'UTF-8' : $('#encoding').val();
        if(file.name.split('.')[1== 'zip') {
            if(file) $('.dimmer').addClass('active');
            loadshp({
                url: file,
                encoding: encoding,
                EPSG: epsg
            }, function(data) {
                var URL = window.URL || window.webkitURL || window.mozURL || window.msURL,
                url = URL.createObjectURL(new Blob([JSON.stringify(data)], {type: "application/json"}));
 
                $('.shp-modal').toggleClass('effect');
                $('.overlay').toggleClass('effect');
                $('#wrap').toggleClass('blur');
 
                vector.addData(data);
                map.fitBounds([
                    [data.bbox[1], data.bbox[0]], [data.bbox[3], data.bbox[2]]
                ]);
                $('.dimmer').removeClass('active');
                $('#preview').addClass('disabled');
                $('#epsg').val('');
                $('#encoding').val('');
                $('#info').addClass('picInfo');
                $('#option').slideUp(500);
            });
        } else {
            alert("ZIP 포맷으로 사용해주십시오.")
            return;
        }
    }
    initVector();
 
    $("#file").change(function(evt) {
        file = evt.target.files[0];
        var fileSize = Math.round(file.size);
        console.log(fileSize);
        if(file.size > 600000) {
            alert("600KB 이햐의 데이터를 이용하여 주십시오.")
            return;
        }else{
            $('#dataInfo').text(' ').append(file.name+' , '+file.size+' kb');
            $('#option').slideDown(500);
            $('#preview').removeClass('disabled');
        }
    });
 
    $('#preview').click(function() {
        loadShpZip();
    });
 
    $('#addZipfile').click(function() {
        $('.shp-modal').toggleClass('effect');
        $('.overlay').toggleClass('effect');
        $('#wrap').toggleClass('blur');
    });
    $('#cancel').click(function() {
        $('.shp-modal').toggleClass('effect');
        $('.overlay').toggleClass('effect');
        $('#wrap').toggleClass('blur');
    });
    $('#removeLayer').click(function() {
        $('#attr').fadeOut(300);
        window.location.reload();
    });
    
    $('#encoding').dropdown();
    
    $("#attr").draggable({ containment: $(this).parent().parent(), scrollfalse, cursor: "move" });
    $('#cancelAttr').click(function(){ $('#attr').slideUp(300); });
});
</script>
    
<body>
    <div id="wrap" class="wrap">
        <div id="map" ></div>
        
        <div id="attr">
            <div class="tableDisplay">
                <table id="attribute" class="ui small celled unstackable table">
                    <thead id="attrHead">
                        <tr class="center aligned">
                            <th>Attribute</th>
                            <th>Value</th>
                        </tr>
                    </thead>
                    <tbody class="tbodyContent">
                    </tbody>
                </table>
            </div>
            <div class="ui red icon button" id="cancelAttr">닫기</div>
        </div>
 
        <footer id="footer">
            <div class="ui page grid">
                <div class="sixteen wide column center aligned">
                    <div id="addZipfile" class="tips large ui positive right labeled icon button">레이어 추가(zip)</div>
                    <div id="removeLayer" class="negative large ui button">레이어 초기화</div>
                </div>
            </div>
        </footer>
    </div>
 
    <div id="shp" class="shp-modal">
        <div class="ui page grid">
            <div class="sixteen wide aligned column">
                <div class="ui form segment">
                    <div class="field">
                        <div class="ui teal fluid labeled icon button upload" id="zipfile" data-variation="inverted large">레이어 불러오기
                            <input type="file" id="file" accept=".zip">
                        </div>
                    </div>
                    <div class="field" id="dataInfo"></div>
    
                    <div id="option">
                        <div class="field ui labeled input" id="epsgField">
                            <div class="ui label">EPSG</div>
                            <input type="text" placeholder="Default : 4326" id="epsg" class="v" onfocus="this.value='';">
                        </div>
                        <div class="field ui labeled input" style="top:-7px;">
                            <div class="ui label" >Encoding</div>
                            <input type="text" placeholder="Encoding UTF-8, EUC-KR, Big5, Big5-HKSCS ... " id="encoding" class="v" onfocus="this.value='';">
                        </div>
                    </div>
                    <div class="two fields" style="padding: 0px 97px;">
                        <div class="field">
                            <div class="ui teal fluid labeled icon disabled button" id="preview"    style="margin: 5px 40px;">레이어 등록</div>
                        </div>
                        <div id="cancel" class="negative right ui button"    style="margin: 5px 40px;">닫기</div>
                    </div>
                </div>
                <div class="ui inverted dimmer">
                    <div class="ui large text loader">데이터를 불러오고있습니다...</div>
                </div>
            </div>
         </div>
     </div>
    <div class="overlay"></div>
 
 
</body>
</html>
 
cs

 


2. 실행 조건

    • Shape File 용량 제한

=> 서버를 이용하지 않고 웹 브라우저를 통해 shapeFile을 GeoJson 형태로 올리기 위해서 Shape File의 용량을 제한

    • EPSG 코드 입력
=>Default로 4326을 설정하였으며, Zip파일 내에 .prj파일이 존재하는 경우, 사용자가 입력한 EPSG로 변경되지 않음 (.prj파일 우선순위)
    • Encoding
=> Encoding을 정확히 입력하여야 속성정보 깨짐이 없음
 
 
3. 결과화면
 
 

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모델을 적용시켰습니다.

Leaflet에서 D3를 이용한 TSP(Traveling Salesman Problem) 활용

서비스바로가기

 

Leaflet에서 D3 라이브러리를 이용하여 TSP를 구현 후, 지도상에 사용자가 지정한 위치를 단 한번만 방문하고 시작점으로 돌아오는 TSP를 구현하였다. 

1.TSP(traveling salesman problem)란?

 

여러 도시들이 있고 한 도시에서 다른 도시로 이동하는 비용이 모두 주어졌을때, 모든 도시들을 단 한번만 방문하고 원래 시작점으로 돌아오는 최소 비용의 이동 순서를 구하는 것이다. 이는 조합 최적화 문제의 일종으로 이 문제는 NP-난해에 속하며, 흔히 계산 복잡도 이론에서 해를 구하기 어려운 문제의 대표적인 예로 많이 다룬다.

참조한 소스가 성능적으로 우수하지는 않습니다. 지도 조회 도구(Leaflet)과 시각화 라이브러리(D3)를 함께 사용하는 예시로 참조하십시오. 인터넷에는 TSP를 해결하는 다양한 응용 사례가 존재합니다.

(참조 : 위키백과 https://ko.wikipedia.org/wiki/%EC%99%B8%ED%8C%90%EC%9B%90_%EB%AC%B8%EC%A0%9C)

 

2. 구현

2.1 leaflet

leaflet을 이용한 웹페이지 지도 발행은 [꿀팁-OpenLayers, Leaflet] > [OpenLayers와 Leaflet에서 Vworld 배경지도 이용하기]을 참고하세요.

D3 라이브러리를 이용하기 위해 leaflet의 지도에 svg 추가가 필요합니다. 프로그웍스 Openlayers 및 Leaflet에서 제공하는 데이터 및 배경지도는  국가공간정보포털 오픈마켓 데이터 및 VWorld를 이용하고 있습니다. <script></script>부분은 아래와 같습니다.

    • 3행 : leaflet 지도 불러오기
    • 4행 : 배경지도 Vworld 활용
    • 12행 : leaflet Map에 svg 추가(D3.js 라이브러리를 사용하기 위해 반드시 추가 필요)
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script type="text/javascript">
$(document).ready(function(){
    var    letMap = L.map('letMap').setView([37.52470308242787126.9234],12);
        L.tileLayer('http://xdworld.vworld.kr:8080/2d/Base/201802/{z}/{x}/{y}.png').addTo(letMap);
        letMap.options.minZoom = 12;
        letMap.options.maxZoom = 12;
        letMap.dragging.disable();
        letMap.touchZoom.disable();
        letMap.doubleClickZoom.disable();
        letMap.scrollWheelZoom.disable();
        
        L.svg().addTo(letMap);
            
});
</script>
cs

 

2.2 D3(Data Drivened Document).js + TSP

D3(Data Drivened Document’).js는 웹에서 데이터를 표현하기에 적합한 도구로 특히, 인터랙티브 데이터 시각화에 많이 사용되고 있는 자바스크립트 기반 라이브러리입니다. D3.js는 HTML, SVG(Scalable Vector Graphics), CSS를 사용해 데이터를 시각적 결과물로 나타낸다. 위 leaflet <script></script> 12행에 leaflet Map에 svg를 추가한 이유가 D3.js 라이브러리를 이용하기 위함이었습니다.

다른방법을 알고 계신다면 알고계신 방법을 사용하셔도 무방하지만 저는 svg를 사용하겠습니다. 아래 <script></script>부분은 d3.js를 이용하여 TSP를 구현한 부분입니다. 소스가 상당히 길지만 정리된 내용을 보시면 금방 이해를 하실 수 있으실 겁니다.

D3.js 및 TSP분석을 위해 underscore.min.js 및 d3.v3.min.js파일을 추가합니다. leaflet map을 이용하기 위해서는 leaflet.js 및 .css파일로 추가하셔야합니다. (참고하기 : 

[꿀팁-OpenLayers, Leaflet] > [OpenLayers와 Leaflet에서 Vworld 배경지도 이용하기])

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

D3.js 및 TSP 구현부분을 확인하기 전 알아야 할 부분이 있다.

TSP는 NP-hard 문제에 속하는 것으로 완전 탐색 연결을 위해서는 0(N!)이라는 시간 복잡도가 나온다(Factorial). 이는 10개만 탐색해도 3,628,800개 라는 값이 나오며, 웹페이지에서 구현 시, 페이지가 멈추는 현상이 생긴다.... 그렇기 때문에 포인트 개수에 제한을 두고 개발하였다.(최대 9개 지점 선택 가능)

- 참조 : Factorial( https://ko.wikipedia.org/wiki/%EA%B3%84%EC%8A%B9 )

- NP-Hard란?

비 결정론적 튜링머신으로 다항시간 내에 풀 수 있는 문제로 특정 상태에서 움직일 수 있는 상태의 개수가 하나로 정해져 있지 않은 경우를 말한다.

지금부터 <script></script>부분을 살펴보겠습니다.

    • 2행~12행 : d3.js에서 tsp를 구현하기 위해 사용한 변수 선언
    • 14행 : d3.js를 이용한 TSP 구현 부분
    • 23행 : leaflet map하위의 svg부분을 변수 svg에 선언
    • 23행~42행 : marker와 path 부분 선언
    • 44행 : d3 click 이벤트 선언
    • 60행 : 지도 클릭에 따른 위치정보 추출 및 지도 위 포인트 표시 function 실행
    • 65행 : TSP 실행(RUN)
    • 81행 : TSP 초기화(RESET)
    • 90행 : 지도 클릭에 따른 포인트 표시(시작점은 빨강/그외 지점은 검은색)
    • 119행 : TSP 분석을 통한 path 그리기
    • 137행~151행 : 선택된 위치를 이용한 TSP 비용 계산(Cost)
    • 151행 : 배열에 담은 선택한 포인트를 랜덤으로 순서 생성 및 Cost 계산 function 실행
    • 163행 : TSP 분석에 필요한 최소 비용 및 경로 추출 반복 횟수 만큼(팩토리얼:factorial) 새로운 방문 순서 및 비용 계산(RandomPath 및 Cost부분 function 실행)
    • 176행 : TSP 옵션 정보를 이용한 path, cost 값 정의 및 TSP 완전 탐색이 진행되는 부분
    • 214행~230행 : TSP 반복 횟수 지정 부분(완전탐색 횟수 지정 부분)
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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
<script type="text/javascript">
    var tspCount,
        infoText,
        echoText,
        clickCoords,
        featureElement;
    var resultCost = [];    
    var checkRun = false;
    
    var dotscale = 12,
        nodes = []
        cities = [];
        
    (function (d3, _) {
        "use strict"
        
        drawFeatures();  
        
        function drawFeatures() {
            
            var path = d3.geo.path();
            var svg = d3.select("#letMap").select("svg");
            svg.append("svg:defs")
                .append("svg:marker")
                  .attr("id""arrow")
                  .attr("viewBox""0 0 10 10")
                  .attr("refX"15)
                  .attr("refY"5)
                  .attr("markerWidth"6)
                  .attr("markerHeight"6)
                  .attr("orient""auto")
                .append("svg:path")
                  .attr("d""M0,0L10,5L0,10");
    
              svg.append("rect")
                  .attr("class""background")
                .attr("height","686")
                .attr("width","100%")
                  .on('click', clickMap);
              
              var g = svg.append("g")
                .attr("id""baseSVG");
              
              function clickMap () {
                  if(checkRun == true){
                      cities = [];
                    tspCount = null;
                    $('#countInfo').html('0');
                    svg.selectAll('circle').remove();
                    svg.selectAll('path.connection').remove();
                    document.getElementById("echoText").value = "";
                    checkRun = false;
                  }
                  
                  if(cities.length >= 9){
                      alert("위치선택은 9개까지 가능합니다.");
                      return;
                  }
                  
                  cities.push(d3.mouse(this));
                clickCoords = d3.mouse(this);
                drawCities();
            }
              
              function run() {
                if(cities.length<2){
                    alert("2점 이상의 위치를 지도에 표시하세요.");
                    return;
                }
                
                  tspCount = null;
                $('#countInfo').html("TSP 0 번 분석");
                optsNum();
                svg.selectAll('path.connection').remove();
                var answer = sanTsp(cities, {});
                drawPaths(answer.initial.path);
                setTimeout(function () { drawPaths(answer.final.path); }, 1000);
                checkRun = true;
            }
              
            function reset () {
                cities = [];
                tspCount = null;
                $('#countInfo').html('0');
                svg.selectAll('circle').remove();
                svg.selectAll('path.connection').remove();
                document.getElementById("echoText").value = "";
            }
            
            function drawCities() {
                if($(".city").attr("cx"== null && $(".city").attr("cx"== null){
                    svg.selectAll('circle').data(cities).enter()
                    .append('circle')
                        .attr('cx'function (d) { return d[0]; })
                        .attr('cy'function (d) { return d[1]; })
                        .attr('r', dotscale)
                        .attr('fill','red')
                        .attr('stroke','black')
                        .attr('opacity','0.7')                        
                        .attr('class''city');
                }else{
                    
                    svg.selectAll('circle').data(cities).enter()
                        .append('circle')
                            .attr('cx'function (d) { return d[0]; })
                            .attr('cy'function (d) { return d[1]; })
                            .attr('r', dotscale)
                            .attr('fill','black')
                            .attr('stroke','black')
                            .attr('opacity','0.7')
                            .attr('class''city');
                }
                
                echoText = document.getElementById("echoText");
                
                echoText.value += cities.length+"번째 위치를 선택하였습니다. (위치정보"+clickCoords+") \n";
            }
            
            function drawPaths(ipath) {
                var paths = _.map(_.zip(ipath.slice(0,ipath.length-1), ipath.slice(1)), function (pair) {
                    return [cities[pair[0]], cities[pair[1]]]
                }).slice();
    
                svg.selectAll('path.connection').remove();
                svg.selectAll('path.connection').data(paths).enter()
                    .append('path')
                        .attr('d'function(d) {
                        var dx = d[1][0- d[0][0],
                            dy = d[1][1- d[0][1],
                            dr = Math.sqrt(dx * dx + dy * dy);
                        return "M" + d[0][0+ "," + d[0][1+ "A" + dr + "," + dr + " 0 0,1 " + d[1][0+ "," + d[1][1];
                      })
                        .attr('class''connection')
                        .attr("marker-end""url(#arrow)");
            }
 
            function ccCost(c1, c2) {
                return Math.sqrt(Math.pow(c1[0- c2[0], 2+ Math.pow(c1[1- c2[1], 2));
            }
            function sum(arr) {
                return _.reduce(arr, function (x,y){ return x+y; }, 0);
            }
            function pathCost(path) {
                var zipped = _.zip(path.slice(0,path.length-1), path.slice(1));
                
                return sum(_.map(zipped, function (pair) {
                    return ccCost(cities[pair[0]], cities[pair[1]]);
                }));
            }
            
            function randomPath() {
                var n = cities.length
                    ,    path = [0// wlog, begin with 0
                    , rest = _.range(1, n);
 
                while (rest.length > 0) {
                    var i = Math.floor(Math.random() * rest.length);
                    path.push(rest[i]);
                    rest.splice(i, 1);
                }
                return path.concat([0]);
            }
            function doRound(cur) {
                var newpath = randomPath(),
                    newcost = pathCost(newpath);
                if ((newcost < cur.cost)) {
                    return {
                        path: newpath,
                        cost: newcost
                    };
                } else {
                    return cur;
                }
            }
            
            function san(opts) {
                var path = randomPath(),
                    cost = pathCost(path),
                    cur = {
                        path: path,
                        cost: pathCost(path)
                    },
                    answer = {
                        initial: cur
                    },
                    i;
                
                console.log('Starting SAN-TPS', cur);
                var firstCur = cur
                
                if(cities.length == 9){
                    opts.N = opts.N*0.75;
                }
                for (i = 1; i <= opts.N; i++) {
                    
                    cur = doRound(cur);
                    if(cur.cost < firstCur.cost){
                        cur = cur;
                    }else{
                        cur = firstCur;
                    }
                    if(opts.N >= 40320){
                        opts.N = opts.N-100;
                    }
                    
                    document.getElementById("countInfo").innerHTML = "TSP " + i + "번 분석 ";
                }
                
                console.log('Finished SAN-TPS', cur);
                answer.final = cur;
                return answer;
            }
            
            function optsNum(){
                for (var i = cities.length; i >= 1; i--) {
                    if(tspCount == null){
                        tspCount = i;
                    }else{
                        tspCount = tspCount * i;
                    }
                }
            }
            
            function sanTsp(cities, opts) {
                opts = opts || {};
                opts.N = tspCount;
                return san(opts);
            }
    
            d3.select('#run').on('click', run);
            d3.select('#reset').on('click', reset);
    
            console.log('Loaded!');
            
        }
        
    })(d3, _);
</script>
cs

 

2.3 Leaflet + D3(Data Drivened Document).js + TSP <Body></Body>부분

    • 2행 : L.map에서 선언한 Id와 div태그에 사용한 아이디는 동일하여야 한다.
1
2
3
4
5
6
7
8
<body>
    <div id="letMap"></div>
    <h1>Location Info</h1>
    <div id ="countInfo">TSP 0 번 분석</div>
    <textarea id="echoText" rows="10" cols="310"></textarea>
    <button id="run">Run</button>
    <button id="reset">Reset</button>            
</body>
cs

 

3. 테스트 및 결과

테스트 시나리오는 사용자가 임의의 지점은 선택하고 그에 따른 TSP 결과 도출로 진행

구현된 TSP는 별도의 가중치가 없으며, 오직 거리를 이용하여 TSP를 구현하였으며, 직선으로 거리를 표시하고 계산하였다.

3.1 테스트 

1. 임의지점 선택에 따른 위치정보 표시 및 선택 개수 제한 유무

(임의 지점 : 부천시청,.가톨릭대학교성심교정, 매봉산, 서울역, 동덕여자대학교, 한양대학교 서울캠퍼스, 대공원입구, 영등포역, 광명시청 총 9개 지점)  

2. TSP 실행 및 화면 표출

3. TSP 분석 횟수 표시

4. 초기화

 

3.2 결과화면

 

 

+ Recent posts