[GIS] DuraMap-Xr, 타일맵 레이어

듀라맵은 3.8 버전부터 타일맵 형태의 배경지도를 인터넷을 통해 받아 사용자에게 제공할 수 있습니다. 아래는 듀라맵을 이용해 타일맵 레이어를 추가하여 실행한 화면입니다.

사용자 삽입 이미지
위 화면에 대한 예를 통해 듀라맵에서 타일맵 레이어를 추가하는 API를 살펴보도록 하겠습니다. 저는 여기서 VB를 이용하였습니다. 듀라맵은 COM 기반의 ActiveX 컴포넌트로 ActiveX 개발시 디버깅에 VB가 적합하여 사용하였습니다. 듀라맵은 C#과 델파이와 같은 COM을 지원하는 개발언어에서도 사용할 수 있습니다.

먼저 위의 화면에 보이는 것처럼 4개의 버튼과 듀라맵 컴포넌트를 화면상에 배치합니다. 4개의 버튼에 대한 Caption은 각각 Connect, Zoom In, Zoom Out, Pan Mode로 합니다. Connect 버튼은 인터넷을 통해 타일맵을 서비스 받을 수 있도록 연결하는 기능이며 Zoom In과 Zoom Out은 각각 지도 확대 레벨을 변경하여 지도를 확대하고 축소하는 기능입니다. 끝으로 Pan Mode는 지도를 마우스를 이용해 이동하는 기능입니다.

화면 UI 구성이 끝났으면 다음으로 코드를 작성합니다. 먼저 폼의 크기가 변경되면 듀라맵 컴포넌트의 크기도 폼의 크기에 맞춰 조절되도록 폼의 Resize 이벤트를 다음처럼 작성합니다.

Private Sub Form_Resize()
    Xr1.Height = Me.Height
    Xr1.Width = Me.Width
End Sub

다음으로 Connect 버튼의 Click 이벤트를 다음처럼 작성합니다.

Private Sub Command1_Click()
    Dim OK As Boolean
    OK = Xr1.Layers.AddTileMapLayer("basemap", 
        "XrTileMap://geoservice.co.kr/tilemap")
    If OK Then
        Xr1.WaitForAllConnections
        Xr1.ZoomFullExtent
        Xr1.MapScale = Scales(Current)
        Xr1.MouseMode = XrPanMode
    Else
        MsgBox "Error AddTileMapLayer"
    End If
End Sub

다음으로 Zoom In과 Zoom Out 버튼에 대한 Click 이벤트를 작성해야 하는데요. 그전에 다음과 같은 사용할 전역 변수를 선언해야 합니다.

Dim Scales(1 To 12) As Long
Dim Current As Integer

1번 코드는 타일맵의 12단계 축척을 저장할 배열이고 2번은 현재 화면상에 표시하고 있는 축척 단계를 저장하고 있는 변수입니다. 이 변수에 값을 초기화 하는 코드를 폼의 Load 이벤트에 아래와 같이 작성합니다.

Private Sub Form_Load()
    Scales(1) = 3000000
    Scales(2) = 1800000
    Scales(3) = 800000
    Scales(4) = 460000
    Scales(5) = 250000
    Scales(6) = 110000
    Scales(7) = 50000
    Scales(8) = 25000
    Scales(9) = 14000
    Scales(10) = 7500
    Scales(11) = 3500
    Scales(12) = 2000
    
    Current = 1
End Sub

이제 지도 확대와 축소를 위한 버튼의 기능을 작성합니다. 먼저 지도를 확대하는 Zoom In 버튼의 클릭 이벤트를 아래와 같이 작성합니다.

Private Sub Command2_Click()
    Current = Current + 1
    If Current > 12 Then Current = 12
    Xr1.MapScale = Scales(Current)
    Xr1.Update
End Sub

그리고 지도를 축소하는 Zoom Out 버튼의 클릭 이벤트를 아래와 같이 작성합니다.

Private Sub Command3_Click()
    Current = Current - 1
    If Current < 1 Then Current = 1
    Xr1.MapScale = Scales(Current)
    Xr1.Update
End Sub

마지막으로 지도를 마우스 드레깅을 통해 이동하는 Pan Mode 버튼의 클릭 이벤트를 아래와 같이 작성합니다.

Private Sub Command4_Click()
    Xr1.MouseMode = XrPanMode
End Sub

코드에 대한 설명은 거의 하지 않았으나 코드의 구성이 단순하고 명확하여 이해하기 어렵지 않을것으로 생각합니다.

HTML5/CSS3를 준수하는 크롬의 사용자

사용자 삽입 이미지

2012년 어느때부터를 시작으로 2013년 현재 크롬의 점유률이 IE를 앞질렀습니다. 위의 그래프는 gs.startcounter.com에서 가져온 통계자료입니다. 위의 그래프는 분석 대상 분위를 전세계로 했을때이구요. 그렇다면 한국을 대상으로만 한다면 어떨지 파악해 보니 아래와 같은 결과가 나왔습니다.
사용자 삽입 이미지
IE가 여전히 절대적으로 앞섭니다. 대상을 전세계로 했을때와 완전히 반대 상황인데요.. 언제가 될지 모르겠지만 조만간에 IE가 HTML5/CSS3를 완전하게 지원하게 될테고.. 아니면 한국에서도 전세계의 흐름처럼 크롬이 IE보다 더 많이 활용될 것으로 생각됩니다만.. 국내에서는 Windows의 사용자가 많고 기본 웹브라우져인 IE의 점유률이 높은거야 쉽게 쉽게 이해할 수 있겠는데.. 왜 세계적으로 봤을때는 크롬 사용자가 더 많을까 그 이유가 궁금해집니다.. Linux 계열의 사용자가 Windows 사용자만큼이나 많은가? 아니면 웹브라우져에 대해서 IE가 아닌 크롬을 설치하는 수고를 감내하고 사용하는가….. 참으로 그 이유가 궁금하네요..

행운의 네잎 클로버 찾기

어릴적에 흔히 봐온 클로버.. 이 클로버를 보면 가장 먼저 떠오르는 것이 행운의 네잎 클로버 찾기입니다. 클로버는 매우 많이 봐왔지만.. 나는 아직 단한번도 네잎 클로버를 찾은 적이 없습니다.

내 주위에 네잎 클로버가 없었기 때문이 아니라.. 단지 찾으려 하지 않았기 때문이라는 것을 잘 압니다.. 행운은 찾아오는 것이 아니라 스스로 찾는 것이라는 것도 잘 압니다. 행운은 다른 누군가가 만들어 주는 것이 아니라 내 자신이 만들어 가야 하는 것을 잘 압니다..

[C++] URL로부터 바이너리 데이터 다운로드

예전에 만들어 놓은 것이 있는데.. 도통 찾을 수가 없어서 다시 만들어 본 함수입니다. URL 경로에 존재하는 데이터를 다운로드하여 로컬 파일로 저장해 주는 함수입니다. 실제 개발에 사용할 요량으로 인자가 제법 복잡합니다.

DWORD Download(HINTERNET hInternet, char *pszURL, 
    char *pszFileName, BYTE *pBuffer) {
    HINTERNET hURL = InternetOpenUrl(hInternet, pszURL, NULL, 0, 0, 0);
    if(hURL == NULL) {
        InternetCloseHandle(hInternet);
        return -2;
    }

    HANDLE hFile = CreateFile(pszFileName, GENERIC_WRITE, 0, 
        NULL, CREATE_ALWAYS,     FILE_ATTRIBUTE_NORMAL, NULL);
    if(hFile == INVALID_HANDLE_VALUE) return -3;

    DWORD dwSize = 2048;
    DWORD dwRead, dwWritten, dwTotalSize;

    bool bOK = HttpQueryInfo(hURL, HTTP_QUERY_CONTENT_LENGTH, 
        pBuffer, &dwRead, NULL);
    if(!bOK) return -4;
    dwTotalSize = atoi((const char *)pBuffer);

    do {
        InternetQueryDataAvailable(hURL, &dwSize, 0, 0);
        InternetReadFile(hURL, pBuffer, dwSize, &dwRead);
        WriteFile(hFile, pBuffer, dwRead, &dwWritten, NULL);
    } while(dwRead != 0);

    InternetCloseHandle(hURL);
    CloseHandle(hFile);

    return dwTotalSize;
}

아래는 위의 함수를 직접 사용하는 코드입니다.

HINTERNET hInternet = InternetOpen("MyAGENT", INTERNET_OPEN_TYPE_PRECONFIG, 
    NULL, NULL, 0);
if(hInternet == NULL) return 0;

BYTE *pBuffer = new BYTE[1024*1024];
DWORD dwTotalSize;
 
dwTotalSize = Download(hInternet, "http://www.s.com/a.zip", "c:/a.zip", pBuffer);
printf("TotalSize: %d\n", dwTotalSize);

dwTotalSize = Download(hInternet, "http://www.s.com/b.zip", "c:/b.zip", pBuffer);
printf("TotalSize: %d\n", dwTotalSize);

delete [] pBuffer;
InternetCloseHandle(hInternet);

1번 코드에서처럼 가장먼저 hInternet 객체를 만듭니다. 이 객체를 재활용하여 다수의 URL을 통해 파일을 다운로드할 수 있습니다. 5번 코드는 파일을 다운로드하는데 사용하는 버퍼입니다. Download 함수를 여러번 사용할 것을 대비하여 버퍼를 재활용할 수 있도록 하였습니다. 또한 Downoad 함수의 결과값은 다운로드된 바이너리 데이터의 전체 바이트 수입니다. 음수인 경우 ERROR로 간주할 수 있습니다. 14번과 15번 코드처럼 사용한 리소스는 반환합니다.

추가로 아래의 Download2 함수는 다운로드된 데이터를 파일로 기록하지 않고 메모리 버퍼에 저장하는 함수입니다. 다운로드된 데이터를 파일에 저장하지 않고 바로 메모리 상에서 사용하고자 할때 사용할 수 있습니다.

DWORD Download2(HINTERNET hInternet, char *pszURL, BYTE *pBuffer) {
    HINTERNET hURL = InternetOpenUrl(hInternet, pszURL, NULL, 0, 0, 0);
    if(hURL == NULL) {
        InternetCloseHandle(hInternet);
        return -2;
    }

    DWORD dwSize;
    DWORD dwRead, dwWritten, dwTotalSize;
    DWORD dwCursor = 0;

    bool bOK = HttpQueryInfo(hURL, HTTP_QUERY_CONTENT_LENGTH, pBuffer, 
        &dwRead, NULL);
    if(!bOK) return -4;
    dwTotalSize = atoi((const char *)pBuffer);

    do {
        InternetQueryDataAvailable(hURL, &dwSize, 0, 0);
        InternetReadFile(hURL, (LPVOID)(pBuffer + dwCursor), dwSize, &dwRead);
        dwCursor += dwRead;
    } while(dwRead != 0);

    InternetCloseHandle(hURL);

    return dwTotalSize;
}

끝으로 이 함수 사용을 위해 헤더 파일로 wininet.h와 라이브러리 파일로 wininet.lib가 필요합니다.

[JavaScript] 클래스 정의 API ㅡ 3/3

이제 끝으로 Rectangle를 상속받는 PositionedRectangle 클래스를 정의해 보겠습니다. Rectangle 클래스는 단순히 가로와 세로 크기값만을 가지고 있습니다. 여기에 위치값까지 갖도록 한 클래스가 바로 PositionedRectangle 클래스입니다.

var PositionedRectangle = Class({
    name: "PositionedRectangle",
    extend: Rectangle,
    construct: function(x,y,w,h) {
        this.superclass(w, h);
        this._x = x;
        this._y = y;
    },
    methods: {
        draw: function() {
            alert("draw PositionedRectangle");
        },
        getPosition: function() {
            return new Coordinate(this._x, this._y);
        }
    },
    requires: [aShape],
    statics: {
        isSame: function(a, b) {
            return a._x == b._x && a._y == b._y && 
                a._width == b._width && a._height == b._height;
        }
    }
});

중요한 부분은 바로 3번 코드로 상속받고자 하는 클래스를 지정하는 extend입니다. 바로 이 extend로 지정된 클래스의 모든 속성값을 상속받게 되는데 이 PositionedRectangle 클래스는 Rectangle 클래스의 모든 속성(변수와 매서드)를 상속받게 됩니다.

추가로 18번 코드는 정적 속성(변수와 매서드)를 정의하는 방법입니다. 이는 C++과 자바와 같은 언어에서 클래스 차원에서 제공하는 바로 그 기능입니다.

이제 이렇게 정의한 클래스들을 직접 사용하는 예를 보이면 다음과 같습니다.

var c = new Circle(100, 100, 30);
var r = new PositionedRectangle(10, 10, 100, 100)

alert(r instanceof Rectangle);
alert(r instanceof PositionedRectangle);
alert(r instanceof Object);
alert(r instanceof Circle);

c.draw();
r.draw();

alert(c.getCenter().toString());
alert(PositionedRectangle.isSame(r, r));

1번과 2번 코드는 클래스를 통해 객체를 생성하고 있습니다. 그리고 4~7번 코드에서 각 객체가 어떤 클래스의 인스턴스인지를 검사하고 있습니다. 결과값은 순서대로 true, true, true, false입니다. 그리고 9~10번 코드는 draw 함수를 호출하고 있습니다. 마친가지로 12번 ~ 13번 코드는 정의한 매서드를 사용하고 있습니다.

이상으로 클래스 정의 API에 대한 내용을 마무리합니다. 중요한 것은 자바스크립트는 결코 완벽한 클래스 정의를 제공해주지 못합니다. 이는 언어적 한계입니다. 이러한 한계를 해결한 버전이 자바스크립트 2.0입니다. 자바스크립트 2.0이 나오기 전까지는 이러한 클래스 정의 API를 개발자가 직접 만들고 이해하여 사용해야 하며 스스로에 대한 약속을 정해 반드시 준수해야 합니다. 이러한 약속은 private 변수의 경우 변수명 앞에 밑줄(_)을 넣는 등에 대한 것입니다. 가장 중요한 것은 문서화 입니다. 자바스크립트는 매우 유연한 언어이므로 문서화를 통해 약속을 분명히 기술하여 지킬 수 있도록 해야합니다.