GeoService-Xr과 FingerEyes-Xr을 이용한 대용량 파일 업로드 및 다운로드 서비스

사용자의 요구사항을 충분히 반영할 수 있는 GIS 시스템을 개발할 때, 대용량 데이터를 서버 측에 업로드해야 할때가 있습니다. 예를 들어, 최신 지적도를 서버측에 올려 최신 지적도 서비스를 업데이트하거나, 사용자가 보유한 빅데이터를 CSV나 Excel 형태로 서버에 올려 서버측에서 분석을 한 뒤에 그 결과를 공간상에 시각화하는 등이 있는데요. 이러한 대용량 파일을 업로드할 수 있는 기능을 웹에서 쉽게 적용할 수 있다면, 웹 환경에서 어떠한 제약없이 자연스럽게 기능을 개발하고 사용자에게 제공할 수 있습니다. 특히 네트워크 속도와 하드웨어로써의 서버의 발전으로 이러한 웹 기반의 환경은 모든 클라이언트 프로그램의 기반, 기본이 될 것입니다.

이러한 대용량 데이터를 서버에 업로드할 수 있는 기능을 GeoService-Xr과 FingerEyes-Xr을 이용해 쉽게 적용할 수 있는데요. 이러한 대용량 파일 업로드에 대한 API에 대한 정리가 이 글의 주제입니다. 대용량은 아니지만 이미지 파일을 서버측에 업로드 하는 것을 목표로 글을 정리합니다. 이미지가 대용량은 아니지만 이 글에서 설명하는 방법은 서버의 OS가 허락하는 파일 시스템의 최대 용량을 지원합니다.

먼저 아래의 JS 코드로 이미지를 업로드하기 위한 UI를 구성합니다.




위의 JS 코드는 아래와 결과 화면을 생성합니다.

위의 UI에 대한 기능은 아래와 같이 사용자의 3가지 행위를 지원합니다.

  1. Select Image 버튼을 클릭해 사용자의 이미지 파일을 선택하면,
  2. 아래에 선택된 이미지가 표시됩니다.
  3. 그리고 Upload Image 버튼을 클릭하면 서버측에 이미지가 업로드된다.

위의 기능을 구현하기 위한 JS 코드는 아래가 전부입니다.

SelectImageFileUI.onclick = function () {
    ImageFileUI.click();
};

ImageFileUI.onchange = function () {
    if (ImageFileUI.files && ImageFileUI.files[0]) {
        var reader = new FileReader();

        reader.onload = function (e) {
            ImageUI.setAttribute("src", e.target.result);
        }

        reader.readAsDataURL(ImageFileUI.files[0]);
    }
};

UploadImageFileUI.onclick = function () {
    var imageFile = ImageFileUI.files[0];

    if (imageFile) {
        var args = {
            id: 0,
            server: "http://localhost:7777",
            imageFile: imageFile,
            savedFileName: Date.now() + "." + imageFile.name.split('.').pop(),
            uploadDir: "CCTV_IMAGES",

            onCompleted: function (id) {
                alert('completed');
            },

            onFailed: function (id) {
                alert('failed');
            },

            onProgress: function(id, percent) {
              //.
            }
        };

        if (false == Xr.OperationHelper.uploadFile(args)) {
            alert("인자가 옳바르지 않습니다.");
        }
    } else {
        alert("업로드할 파일을 선택하세요.");
    }
};

사용자의 3개의 행위에 대한 3개의 이벤트에 대한 코드로 구성되는데요. 먼저 Select Image 버튼을 클릭하면 실행되는 코든느 1-3번코드입니다. hidden으로 설정된 File Input 컨트를의 click 이벤트를 트리거(trigger, 개발자가 코드를 통해 이벤트를 발생해 주는 것)해주는게 전부입니다. 사용자가 이미지 파일을 선택하면 id가 ImageFileUI인 File Input 컨트롤에 onchange 이벤트가 발생하는데, 이 이벤트의 코드는 5-15번입니다. FileReader 객체를 이용해 파일의 내용을 읽고, 내용을 다 읽으면 10번 코드에서 이미지를 화면에 표시합니다. 그리고 Upload Image 버튼을 클릭하면 실행되는 코드는 17-47번입니다. 41번의 Xr.OperationHelper.uploadFile 함수 호출을 위한 인자(Argument)들을 준비하고 실행하고 있습니다. 이 uploadFile 함수를 호출하기 위해 전달되는 인자에는 업로드 Task에 대한 id, 서버의 주소(server), 사용자가 선택한 이미지 파일의 객체(imageFile), 서버측에 저장될 파일명(savedFileName), 파일이 저장될 서버측 디렉토리(uploadDir) 등 이고, 서버측에 업로드가 성공적으로 완료되면 호출되는 이벤트와 업로드가 실행하면 호출되는 이벤트인 onCompleted와 onFailed, 그리고 업로드 진행율에 대한 onProgress 콜백 함수이며, 이 이벤트는 필수가 아닌 옵션입니다.

덧붙여 GeoService-Xr은 서버 측에 파일을 올리기도 하는 기능 뿐만 아니라, 서버 측에서 파일을 내려 받기 위한 서비스도 제공 하는데요. 아래는 url 호출의 예는 서버측에 존재하는 2323123.pdf 파일을 myFile.pdf라는 파일로 다운로드 받습니다.

다운로드

또한 서버 측에 존재하는 여러개의 파일을 압축하여 하나의 zip 파일로 내려 받기 위한 url 호출 서비스의 예는 아래와 같습니다.

즉, 서버측에 존재하는 a.hwp, b.hwp, c.hwp에 대한 3개의 파일을 single.zip으로 압축하여 내려 받습니다.

FingerEyes-Xr에서 GraphicLayer의 Row 순회 및 MBR 얻기

GraphicLayer의 구성요소들을 순회하는 방법은 for 문을 통해 쉽게 해결할 수 있습니다. 즉, 아래의 예와 같습니다.

var targetGl = map.layers("sketch_grp");

for (var id in targetRows) {
    var row = targetRows[id];

    ...
}

그래픽 레이어를 구성하는 그래픽 요소를 순회하면서 해당 그래픽 요소의 MBR을 얻어야 하는 경우가 많이 생깁니다. 위의 코드에서 row 변수의 MBR 함수를 통해 쉽게 MBR을 얻을 수 있는데, FingerEyes-Xr에서는 그래픽 요소의 정확한 MBR을 얻기 위해서 약간의 특이점이 발생합니다. 이 특이점으로 인해 혼란스러움이 있어 이 글을 통해 정리합니다. 즉, Text 형태의 그래픽 요소 등과 같은 정확한 MBR을 얻기 위해서는 CoordMapper와 그래픽 요소(SVG)를 담고 있는 DOM 컨테이너를 인자로 넣어줘야 합니다. 즉, 아래의 예제 코드와 같습니다.

var targetGl = map.layers("sketch_grp");
var cm = map.coordMapper();
var svgContainer = targetGl.container();

for (var id in targetRows) {
    var row = targetRows[id];
    var mbrTarget = row.MBR(cm, svgContainer);

    ...
}

FingerEyes-Xr의 소스코드는 GitHub에서 다운로드 받으실 수 있습니다.

dat.GUI의 API 정리

dat.GUI는 값을 GUI를 통해 직관적으로 변경하여 기능을 테스트해 볼 수 있는 javascript 기반의 매우 직관적인 라이브러리입니다.

dat.gui를 js에서 사용하기 위해 간단히 CDN을 통해 아래처럼 페이지에 스크립트를 포함할 수 있습니다.


아래는 dat.GUI의 가장 간단한 예제입니다.

var MyData = function() {
    this.dataString = "Dip2K";
    this.dataNumber = 0.7;
    this.dataBoolean = false;
    this.dataFunction = function() {
        alert(
            "dataString: " + this.dataString + "\n" +
            "dataNumber: " + this.dataNumber + "\n" +
            "dataBoolean: " + this.dataBoolean
        );
    };
};

window.onload = function() {
    var myData = new MyData();
        
    var gui = new dat.GUI();
   
    gui.add(myData, "dataString");
    gui.add(myData, "dataNumber");
    gui.add(myData, "dataBoolean");
    gui.add(myData, "dataFunction");
}

관리하고자 하는 데이터는 함수에 대한 new 호출을 통한 this에 바인딩되어져야 합니다. 위의 코드를 실행해 보면 아래와 UI가 페이지의 우측 상단에 표시됩니다.

[xyz-ihs snippet=”DAT-GUI-1″]

위의 UI에서 보는 것처럼 함수의 new 호출로 this에 바인딩된 dataString, dataNumber, dataBoolean, dataFunction은 각각의 데이터 타입에 따라 적절한 UI로 표시되며 사용자가 쉽게 변경할 수 있습니다. function 타입의 데이터는 버튼 UI로 표시되며 버튼 클릭시에 해당 함수가 호출됩니다.

이 정도만 되어도 dat.gui를 적절하게 활용할 수 있지만, 여기에 덧붙여 변수값에 대한 제약 조건의 지정 등 다양한 활용이 가능합니다.

예를 들어, dataString 변수에 대해서 입력받을 수 있는 문자열을 Dip2K, James, Anold로 제한하고 싶다면 다음처럼 코드를 작성하면 됩니다.

gui.add(myData, "dataString", ["Dip2K", "James", "Anold"]);

dataNumber에 대해서 0~100 사이의 값만을 입력받으며 0.1 단위로 값의 증가, 감소하고자 한다면 아래처럼 코드를 작성하면 되구요.

gui.add(myData, "dataNumber", 0, 100).step(0.1);

dataNumber가 비록 수치 타입이지만, 항목으로써 선택 받도록 하고 각 항목에 대한 수치값을 지정하는 방식도 가능한데, 아래와 같습니다.

gui.add(myData, "dataNumber", { A: 0, B:50, C:100 });

색상값에 대한 직관적인 입력도 가능합니다. 그 예는 아래와 같습니다.

var MyData = function() {
    this.dataString = "Dip2K";
    this.dataNumber = 50;
    this.dataBoolean = false;

    this.dataColor1 = "#ff0000";
    this.dataColor2 = [ 0, 255, 0 ];
    this.dataColor3 = { h: 350, s: 0.5, v: 0.7 };

    this.dataFunction = function() {
        alert(
            "dataString: " + this.dataString + "\n" +
            "dataNumber: " + this.dataNumber + "\n" +
            "dataBoolean: " + this.dataBoolean + "\n" +
            "dataColor1: " + this.dataColor1 + "\n" +
            "dataColor2: " + this.dataColor2 + "\n" +
            "dataColor3: " + this.dataColor3
        );
    };
};

window.onload = function() {
    var myData = new MyData();

    var gui = new dat.GUI();

    gui.add(myData, "dataString", ["Dip2K", "James", "Anold"]);
    gui.add(myData, "dataNumber", 0, 100).step(0.1);
    gui.add(myData, "dataBoolean");

    gui.addColor(myData, "dataColor1");
    gui.addColor(myData, "dataColor2");
    gui.addColor(myData, "dataColor3");
            
    gui.add(myData, "dataFunction");
}

그런데 만약, 값의 변경이 dat.gui가 아닌 다른 곳에서 이루어진 다면, 변경된 값을 dat.gui에서 반영해 줘야 할 것입니다. 이는 매우 간단합니다. 즉, 아래처럼 listen을 호출해 주면 됩니다. 이 listen 함수는 타이머를 생성하고 이 타이머에서 값의 변경 여부를 검사하여 dat.gui에 반영해주게 됩니다.

gui.add(myData, "dataNumber").listen();

끝으로 dat.gui를 통한 값의 변경이 발생하면, 이에 대한 이벤트가 호출되도록 할 수 있는데, 그 예는 아래와 같습니다.

gui.add(myData, "dataNumber").onChange(
    function(v) {
        //alert(v);
    }).onFinishChange(
    function(v) {
        alert(v);
    });

onChange는 값 변경 중의 매 순간 발생하는 이벤트이고, onFinishChange는 최종적인 값의 변경이 발생할 때 호출되는 이벤트입니다.

이외에도 dat.gui는 설정된 값을 localStorage에 저장할 수 있는 기능도 있다는 것도 알아두시면 유용할듯합니다.

FingerEyes-Xr의 Hotspot 기능

지도 상에 특정한 지점을 시각적으로 두드러지게 표현하고 싶을 경우 FignerEyes-Xr에서는 Hotspot 기능을 활용할 수 있습니다. 아래는 이러한 Hotspot 기능을 활용하여 원하는 위치를 강조하는 기능에 대한 동영상입니다.

코드는 아래와 같이 간단합니다.

var pt = new Xr.PointD(102033, 231233);
var hotSpot = new Xr.ui.HotSpotControl(id, map, pt);
map.userControls().add(hotSpot);

위의 코드에 덧붙여 만약 사용자가 hotSpot을 클릭하면 지도에서 사라지도록 하는 추가 코드는 아래와 같습니다.

hotSpot.addEventListener("click", 
    function() {
        map.userControls().remove(id);
    }
);

FingerEyes-Xr의 소스코드는 GitHub에서 다운로드 받으실 수 있습니다.

git 명령어 정리

참고 url : https://rogerdudler.github.io/git-guide/index.ko.html

// 현재 디릭토리를 빈 local repo 만들고 github에 연결하기
git init
git remote add origin https://github.com/GEOSERVICE/test.git

// github로부터 local repo 만들기
git clone https://github.com/GEOSERVIC/test.git

// 추가, 삭제, 변경된 파일에 대한 반영 및 커밋
git add -A // WORKING TREE -> INDEX TREE
git commit -m “add all” // INDEX TREE -> HEAD TREE

// file1.txt의 추가 및 커밋
git add file1.txt
git commit -m “add file1.txt”

// file1의 삭제 및 커밋
git rm file1.txt
git commit -m “remove file1.txt”

// commit하기전의 지운 파일
git ls-files –deleted

// commit하기전의 지운 파일을 복원
git checkout — plugin/density.js

// commit하기전의 지운 전체 파일 복원
git checkout — *

// github로 반영
git push -u origin master // master는 가지의 이름임, master는 최초의 기본 가지임

// 원격 repo의 변경 사항을 local repo에 반영하기
git pull

// 내가 작업한 것 버리고 원격 repo에 맞추기
git fetch origin
git reset –hard origin/master