Convex-Hull 알고리즘 구현

Convex Hull은 볼록한 껍데기로.. 무수히 많은 포인트를 감싸는 볼록한 영역으로 정의할 수 있습니다. 입력 데이터인 포인트는 SHP 파일로부터 제공받아, 이를 듀라맵에서 그 결과를 표시하도록 하였습니다. 아래는 그 결과 화면입니다.

무수히 많은 작은 포인트를 감싸는 Convex Hull이 반투명한 빨간색 폴리곤으로 표시되어져 있습니다. 이러한 Convex-Hull에 대한 구현에 대해 C# 클래스로 정의했으며 아래와 같습니다.

using System;
using System.Collections.Generic;

namespace tstConvexHull
{
    public class ConvexHull
    {
        public class Point : IComparer
        {
            public double x;
            public double y;

            public Point()
            {
                x = 0.0;
                y = 0.0;
            }

            public Point(double x, double y)
            {
                this.x = x;
                this.y = y;
            }

            public int Compare(Point p1, Point p2)
            {
                if (p1.x == p2.x)
                {
                    return p1.y > p2.y ? 1 : p1.y < p2.y ? -1 : 0;
                }
                else
                {
                    return p1.x > p2.x ? 1 : p1.x < p2.x ? -1 : 0;
                }
            }
        }

        public static double Cross(Point O, Point A, Point B)
        {
            return (A.x - O.x) * (B.y - O.y) - (A.y - O.y) * (B.x - O.x);
        }

        public static Point[] Get(Point[] P)
        {
            if (P.Length > 1)
            {
                int n = P.Length, k = 0;
                Point[] H = new Point[2 * n];

                Array.Sort(P, new Point());

                for (int i = 0; i < n; ++i)
                {
                    while (k >= 2 && Cross(H[k - 2], H[k - 1], P[i]) <= 0)
                        k--;
                    H[k++] = P[i];
                }

                for (int i = n - 2, t = k + 1; i >= 0; i--)
                {
                    while (k >= t && Cross(H[k - 2], H[k - 1], P[i]) <= 0)
                        k--;
                    H[k++] = P[i];
                }

                if (k > 1)
                {
                    Point[] NewH = new Point[k - 1];
                    Array.Copy(H, NewH, k - 1);
                    H = NewH;
                }
                return H;
            }
            else if (P.Length <= 1)
            {
                return P;
            }
            else
            {
                return null;
            }
        }
    }
}

실제 위의 Convex-Hull 클래스에 대한 사용은 아래 코드와 같은데요. DuraMap-Xr에서 SHP 파일로부터 점 데이터를 읽어 입력 데이터를 구성하여 Convex-Hull을 실행하고 그 결과로 반투명 빨간색 폴리곤을 추가하는 코드 모두를 나타내고 있습니다.

XrMapLib.ShapeMapLayer lyr = axXr.Layers.GetLayerAsShapeMap("lyr");
XrMapLib.ShapeTable tbl = lyr.ShapeTable;
int cntRows = tbl.RowCount;

ConvexHull.Point[] pts = new ConvexHull.Point[cntRows];

for(int i=0; i

코드를 살짝 설명하면, 1~15번 코드는 SHP 파일로부터 포인트 데이터를 읽어와 Convex-Hull 알고리즘에 입력할 데이터를 준비하는 것이고, 17번 코드가 바로 Convex-Hull의 실행이며 19번 코드부터는 Convex-Hull의 결과를 그래픽 객체로 가시화시켜주는 코드들입니다.

크로스 도메인(Cross Domain)을 허용하는 OpenAPI 개발을 위한 JSONP

AJAX 방식은 서로 다른 도메인간의 데이터를 받아 오는 것을 기본적으로 막고 있지만, JSONP라는 방법을 이용해 가능하게 됩니다. 이에 대해 정리해 봅니다. JSONP 방식의 OpenAPI를 제공하는 Java 서블릿 중 doGet 함수는 다음과 같습니다.

protected void doGet(HttpServletRequest request, HttpServletResponse response) 
    throws IOException
{
    String name = request.getParameter("name");
    String nameDecoded = URLDecoder.decode(name, "UTF-8");
    String callbackFunction = request.getParameter("callback");

    String responseData = "({\"Value\": \"Hello, " + nameDecoded + "\"})";
		
    String result = callbackFunction + "("+ responseData + ");";

    response.setContentType("application/json;charset=UTF-8");
    response.setCharacterEncoding("UTF-8");
    response.setStatus(HttpServletResponse.SC_OK);
		
    response.setContentType("text/javascript");
    PrintWriter out = response.getWriter();
    out.println(result);
}

위의 OpenAPI는 클라이언트 측에서 name과 callback이라는 파라메터를 전달해 준다라는 전제 조건을 갖습니다. 서버는 자신에게 전달된 name을 이용해 클라이언트에게 다시 Hello로 구성된 문자열을 결과로 전달해 줍니다. 여기서 전달 방식이 중요한데요. 그 전달방식이 바로 JavaScript 함수호출에 대한 코드입니다. 이 코드 문자열을 클라이언트가 받아 실행해 준다라는 것입니다. 실제로 위의 서블릿에 대해 웹 브러우저를 통해 호출해 보면 다음과 같습니다.

위의 URL 호출을 좀더 설명하면, name 파라메터에는 Dip2K를 지정했고, callback 파라메터에는 Test를 지정했습니다. 그 호출 결과는 Value를 키로 하고 Hello, Dip2K를 값으로 하는 객체를 인자로한 Test 함수의 호출 코드에 대한 문자열입니다. 클라이언트에서 이 문자열을 코드화하여 실행해주면 성공적으로 서버에서 전달한 데이터를 받아 처리할 수 있게 되는 것입니다.

실제로 아래의 코드는 jQuery를 이용해 JSONP 방식으로 크로스도메인 문제를 깔끔하게 해결해 데이터를 주고 받는 코드입니다.

var url = 'http://www.gisdeveloper.co.kr:8079/OpenAPI?name=김형준&callback=?';

$.ajax({
    url: url,
    type: 'get',
    dataType: 'jsonp',
    success: function (data) {
        alert(data.Value);
    }
});

실행 결과는 ‘Hello, 김형준’을 표시하는 Alert 창 입니다.

[JavaScript] 한글인가?, 천단위로 컴마(,) 찍기

자바스크립트로 된 두가지 기능에 대한 함수를 정리해 봅니다. 제 스스로에게는 평소 궁금했던 기능이였고, 매번 구글링을 통해 찾아 정신없이 적용했던 기능이군요.

먼저 해당 문자열이 한글이냐를 판별해 주는 함수입니다.

/* boolean */ function isKoreaWord(/* string */ v) {
    var regExp = /([가-힣])/g;
    var replacedString = v.replace(regExp, '');

    if (replacedString.length == 0) {
        return true;
    } else {
        return false;
    }
}

위의 함수는 정규표현식이라는, 제가 요즘 푹 빠져 살펴보고 있는 기능을 활용한 것입니다. 정규표현식 기능은 상당한 부하(Load)를 가지는 기능인데, 아래의 함수가 더 가볍고 빠르고 적당해 보이는군요.

/* boolean */ function isKoreaWord(/* string */ v) {
    var len = v.length;
    for(var i=0; i<len; i++) {
        var c = v.charAt(i);
        if(c < '가' || c > '힣') return false;
    }

    return true;
}

다음은 숫자에 대해서 천단위로 컴마(,)를 찍어 주는 함수입니다.

/* string */ function addThousandComma(/* number */ v) {
    var regExp = /(^[+-]?\d+)(\d{3})/;
    var sv = String(v);

    while (regExp.test(sv)) {
        sv = sv.replace(regExp, '$1,$2');
    }

    return sv;
}

위 함수들의 출처는 제가 요즘 jQuery 학습을 위해 읽고 또 읽고 오늘도 읽은 윤인성님이 지은 “모던 웹을 위한 JavaScript jQuery 입문”이라는 책입니다.

PostgreSQL에서 초성 얻기

PostgreSQL에서 한글 단어에 대해 초성을 얻는 방법입니다.

prepare 초성(text) as 
  select case octet_length($1::char(1)) when 3 then 
    case ((((get_byte(decode($1::char(1), 'escape'), 0) & 15) << 12) 
    | ((get_byte(decode($1::char(1), 'escape'), 1) & 63) << 6) 
    | ((get_byte(decode($1::char(1), 'escape'), 2) & 63))) - 44032) / 588 
      when  0 then E'\xe3\x84\xb1' 
      when  1 then E'\xe3\x84\xb2' 
      when  2 then E'\xe3\x84\xb4' 
      when  3 then E'\xe3\x84\xb7' 
      when  4 then E'\xe3\x84\xb8' 
      when  5 then E'\xe3\x84\xb9' 
      when  6 then E'\xe3\x85\x81' 
      when  7 then E'\xe3\x85\x82' 
      when  8 then E'\xe3\x85\x84' 
      when  9 then E'\xe3\x85\x85' 
      when 10 then E'\xe3\x85\x86' 
      when 11 then E'\xe3\x85\x87' 
      when 12 then E'\xe3\x85\x88' 
      when 13 then E'\xe3\x85\x89' 
      when 14 then E'\xe3\x85\x8a' 
      when 15 then E'\xe3\x85\x8b' 
      when 16 then E'\xe3\x85\x8c' 
      when 17 then E'\xe3\x85\x8d' 
      when 18 then E'\xe3\x85\x8e' 
      when -53 then $1::char(1) 
      else '??' 
    end 
  else $1::char(1) 
end as initial; 

실행은 execute 명령을 활용하며 예를 들어 아래와 같습니다.

execute 초성('가나다');

결과는 'ㄱ'으로 나옵니다.

PostgreSQL의 timestamp 필드에 값 INSERT

PostgreSQL에서 년/월/일과 시간을 모두 저장할 수 있는 필드 타입인 timestamp에 값을 넣는 Java 코드입니다.

Calendar cal = Calendar.getInstance();
Timestamp timestamp = new Timestamp(cal.getTimeInMillis());

코드를 호출하는 시점에서의 시간 값을 얻어 오는 코드이고, 이를 INSERT 문으로 추가할때는 문자열 형태로 넣어 주면 됩니다. 즉, 아래처럼 말입니다.

Calendar cal = Calendar.getInstance();  
Timestamp timestamp = new Timestamp(cal.getTimeInMillis());

StringBuilder sb = new StringBuilder();

sb.append("INSERT INTO log (call_time)");
sb.append("VALUES (");
sb.append("'");
sb.append(timestamp);
sb.append("')");

log라는 테이블에 timestamp 필드 타입의 call_time 필드에 값을 저장하는 코드입니다.