울릉도 지역의 지적도와 건물통합데이터의 좌표계 문제

우리가 GIS를 활용해 무언가를 할 때 공간 데이터들 간의 좌표를 맞춰 레이어 단위로 중첩하여 하나의 지도를 구성한 뒤, 시각적 비교와 다양한 분석을 처리하게 됩니다. 이번에 경상북도 지역 중 내륙과 상당히 멀리 떨어져 있는 울릉도 지역에 대한 좌표계에 상당한 문제가 있다는 것을 파악했습니다. 독도는 더 심각!! -_-; 경상북도의 울릉도를 제외한 다른 지역은 그 좌표계가 매우 정확히 일치하는 것을 볼 때 울릉도 지역에 대한 좌표 불일치는 데이터 구축단계에서 원점 처리에 있어 상당한 문제가 있고, 근본적인 해결을 국가공간정보포털의 운영 주체인 정부 차원에서 조속히 해결해줘야 할 부분으로 생각합니다. 아래는 GRS80 UTM-K 좌표계로 변환하여 지적도와 행정경계를 중첩한 지도입니다.

앞서 언급하였듯이, 을릉도 이외의 다른 경상북도 지역은 GRS80 UTM-K 좌표계로 변환하여 중첩했을 경우 정확히 일치합니다. 제대로 구축된 데이터를 구할 수 있다면 좋았겠지만, 그럴 수 없는 상황에서 이처럼 구축 단계에서 근본적으로 잘못 되어진 공간 데이터의 좌표계를 일치시키는 방법으로 Georeferencing 기능을 활용하는 방법밖에 없었고, Georeferencing 기능을 수행할 수 있는 GDAL의 ogr2ogr.exe 콘솔 프로그램을 사용하고자 하였습니다. 그런데 이 프로그램은 콘솔인지라, 시각적으로 GCP를 잡을 수 없다는 문제가 있습니다. QGIS을 활용해 보려고 하였으나, 관련된 기능에 오류가 발생합니다. QGIS 역시 GDAL의 기능을 그대로 활용하면서 단지 시각적으로 GCP를 잡는 기능을 제공하는 것으로 예상됩니다. 여튼.. ogr2ogr을 통한 Georeferencing 기능을 위해 시각적으로 GCP를 효과적을 취득할 수 있는 툴을 별도로 개발하였고 아래의 화면과 같습니다.

위의 프로그램을 통해 19개의 GCP 좌표를 입력하여 Georeferencing 기능을 수행하여 얻은 결과는 아래와 같습니다.

그러나 이러한 수치지도에 대한 Georeferencing 방식을 활용하여 좌표계 일치하는 방법은 최선의 방법이 아닙니다. Georeferencing은 원래 목적은 항공영상과 같은 이미지를 수치지도에 맵핑시키기 위한 방법입니다. 결과적으로 수치지도를 또 다른 수치지도와 맵핑시키는 Georeferencing 방식은 그 결과면에서 정확도가 매우 떨어집니다. 근본적인 방법은 공간 데이터 구축을 처음부터 올바르게 해서 제공해야 한다는 것입니다. 조속히 관련 기관에서 해결해 주기를 바랄 뿐입니다.

FingerEyes-Xr에서 회전 Label에 대한 반복 표현

선형(Polyline)에 대한 라벨을 표시할 때, 선형의 방향에 따라 회전이 되도록 표시되는데.. 이때 선형이 긴 경우 라벨을 하나만 표시하게 되면 시인성이 떨어지는 경우가 있습니다. 예를 들어 도로명의 표현이 그러한데요. 아래의 화면이 그 예입니다.

선형이 짧은 관망(Network)과 같은 경우에 선형의 중심에 하나의 라벨을 표현해 주는 것이 적합한 반면, 위의 경우처럼 선형이 긴 경우에는 라벨을 일정한 기준에 따라 반복적으로 표현해 줘야 합니다. 아래의 화면이 라벨을 반복적으로 표현해 주는 예입니다.

위처럼 라벨을 일정한 간격마다(Pixel 단위) 반복적으로 표현해주는 FingerEyes-Xr의 코드는 아래와 같습니다.

var label = lyr.label();
label.repeatMode(true).repeatLength(300);
...

라벨의 repeatMode 함수에 true 인자를 전달하여 라벨의 반복을 지정하고, 300px 간격으로 반복되도록 repeatLength 함수에 300 인자를 지정하였습니다.

웹에서 Javascript 만으로 텍스트 파일 읽기

웹에서 JS 언어만으로 로컬에 저장된 텍스트 파일을 읽어 오는 코드를 정리한 글이다. 먼저 아래는 예제 코드 실행을 위한 DOM 구성에 대한 코드이다.


...

Open 버튼을 클릭하면 텍스트 파일을 선택할 수 있는 대화상자가 표시되도록 하며, 여기서 읽고자 하는 파일을 사용자가 선택하면 id가 output인 div에 텍스트 파일의 내용을 출력한다. 이에 대한 코드는 아래와 같다.

function openTextFile() {
    var input = document.createElement("input");

    input.type = "file";
    input.accept = "text/plain"; // 확장자가 xxx, yyy 일때, ".xxx, .yyy"

    input.onchange = function (event) {
        processFile(event.target.files[0]);
    };

    input.click();
}

function processFile(file) {
    var reader = new FileReader();

    reader.onload = function () {
        output.innerText = reader.result;
    };

    reader.readAsText(file, /* optional */ "euc-kr");
}

openTextFile은 Open 버튼 클릭시 호출하는 함수이다. processFile은 openTextFile에서 호출되는 함수로 선택된 파일을 읽어 div에 그 내용을 출력한다. IE와 Chrome 모두에서 정상적으로 작동하는 것을 확인했다.

웹에서 Javascript 만으로 텍스트 파일 생성

웹에서 스크립트만으로 텍스트 파일을 생성하기 위한 코드를 정리한다. 주로 사용하는 웹브라우져가 IE와 Chrome인데, 텍스트 파일을 생성하는 방식이 서로 다르다. 먼저 크롬의 경우에는 아래와 같다.

function saveToFile_Chrome(fileName, content) {
    var blob = new Blob([content], { type: 'text/plain' });

    objURL = window.URL.createObjectURL(blob);
            
    // 이전에 생성된 메모리 해제
    if (window.__Xr_objURL_forCreatingFile__) {
        window.URL.revokeObjectURL(window.__Xr_objURL_forCreatingFile__);
    }
    window.__Xr_objURL_forCreatingFile__ = objURL;

    var a = document.createElement('a');

    a.download = fileName;
    a.href = objURL;
    a.click();
}

다음은 IE에서 작동하는 코드이다.

function saveToFile_IE(fileName, content) {
    var blob = new Blob([content], { type: "text/plain", endings: "native" });

    window.navigator.msSaveBlob(blob, fileName);
    //window.navigator.msSaveOrOpenBlob(blob, fileName);
}

아래는 현재 사용하는 웹브라우저가 IE인지를 식별하는 함수이다.

function isIE() {
    return (navigator.appName === 'Netscape' && navigator.userAgent.search('Trident') !== -1) ||
        navigator.userAgent.toLowerCase().indexOf("msie") !== -1;
}

[Java] 원하는 시간에 원하는 기능을 실행해 주는 Scheduler 라이브러리, Quartz v2.2.3

원하는 시간에 원하는 기능을 실행해 주는 스케줄러 기능을 Java에서 실행하기 위해서 가장 많이 사용되는 라이브러리가 Quartz입니다. Quartz는 기능을 안정적으로 실행할 수 있다는 장점과 함께, 특히 실행할 시간을 매우 유연하게 지정할 수 있습니다.

아래의 코드는 매 순간의 시간이 5초일때(5초마다가 아님) 특정한 기능, 즉 Job을 실행해 주는 코드입니다.

package quartz;

import static org.quartz.CronScheduleBuilder.cronSchedule;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;

import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;

public class MainEntry {
    public static void main(String[] args) {
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
		
        try {
            Scheduler scheduler = schedulerFactory.getScheduler();
			
            JobDetail job = newJob(TestJob.class)
                .withIdentity("jobName", Scheduler.DEFAULT_GROUP)
                .build();
			
            Trigger trigger = newTrigger()
                .withIdentity("trggerName", Scheduler.DEFAULT_GROUP)
                .withSchedule(cronSchedule("5 * * * * ?"))
                .build();
						
            scheduler.scheduleJob(job, trigger);
            scheduler.start();
        } catch(Exception e) {
            e.printStackTrace();
        }		
    }
}

26번째 코드를 보면 cronSchedule 함수를 통해 Job을 실행할 시간을 정하고 있습니다. 시간을 정하는 방식을 5 * * * * ?인데요. 순서대로 초 분 시 일 월 요일 년도(옵션)입니다. 즉 초를 5로 하고 나머지에 대해서는 * 또는 ?로 지정함으로써 매순간의 시간이 5초일때 Job을 실행하게 됩니다. 그럼, 실행할 Job은 어떻게 지정할까요? 20번 코드의 newJob 함수를 통해 지정하고 있습니다. Job으로 TestJob을 사용하고 있는데.. 이 TestJob의 코드는 아래와 같습니다.

package quartz;

import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class TestJob implements Job {
    @Override
    public void execute(JobExecutionContext arg0) throws JobExecutionException {
        System.out.println("Job Executed [" + new Date(System.currentTimeMillis()) + "]"); 
    }
}

위의 예에서는 간단히 Job이 실행되는 시간과 Job Executed라는 문자열을 표시하는 일만 합니다.

Quartz 라이브러리는 http://www.quartz-scheduler.org/downloads 에서 받을 수 있으며, 다운로드 받은 jar 중 최소한 quartz-{version}.jar와 slf4j-api-{version}.jar를 참조해줘야 합니다.