[Java] 우선순위 큐(Priority Queue) 활용 예제코드

자바에서 제공하는 컨테이너(Container) 중 어떤 데이터에 대해 우선순위 값을 부여하고, 이 우선순위를 기준으로 자동으로 정렬되어, 우선순위에 따라 데이터를 꺼내어 사용할 수 있는 우선순위 큐에 대한 예제 코드를 정리합니다.

먼저 우선순위 값을 갖는 데이터에 대한 타입 정의가 필요합니다. 아래처럼 Node라는 클래스를 추가해 타입을 정의합니다.

package tstPriorityQueue;

public class Node implements Comparable<Node> {
	private String UUID;
	private String parentUUID;
	private double G;
	private double H;
	
	public Node(String UUID, double G, double H) {
		this.UUID = UUID;
		this.parentUUID = null;
		this.G = G;
		this.H = H;
	}
	
	public double getF() { return G + H; }
	public double getG() { return G; }
	public double getH() { return H; }
	public String getNode() { return UUID; }
	public String getParentNode() { return parentUUID; }
	
	public void setG(double v) { G = v; }
	public void setH(double v) { H = v; }
	public void setParentNode(String v) { parentUUID = v; }
	
	@Override
	public int compareTo(Node target) {
	    if (this.getF() > target.getF()) {
            return 1;
        } else if (this.getF() < target.getF()) {
            return -1;
        }

	    return 0;
	}
	
	public String toString() {
		return UUID + '(' + getF() + ')';
	}
}

위의 클래스에서 중요한 부분은 우선순위값을 얻기 위한 getF() 함수입니다. 이 함수는 데이터의 상대적인 크기의 비교를 위한 인터페이스인 Comparable 구현할 때 사용되는 함수인데요. 바로 compareTo 라는 함수로써, 위의 경우에는 우선순위값이 작은 것을 먼저 꺼내어 사용하겠다는 정의입니다.

실제로, 위의 Node 클래스에 대한 타입으로 정의된 데이터를 컨테이너에 넣고, 사용하는 코드는 아래와 같습니다.

package tstPriorityQueue;

import java.util.PriorityQueue;

public class EntryMain {

	public static void main(String[] args) {
		// Create items
		Node node1 = new Node("423182c4-edb5-11e6-bc64-92361f002671", 1.0, 5.1);
		Node node2 = new Node("42318742-edb5-11e6-bc64-92361f002671", 1.0, 2.4);
		Node node3 = new Node("42318878-edb5-11e6-bc64-92361f002671", 1.0, 3.8);
		Node node4 = new Node("42318968-edb5-11e6-bc64-92361f002671", 1.0, 6.2);
		Node node5 = new Node("42318a3a-edb5-11e6-bc64-92361f002671", 1.0, 4.5);
		
		// Create priority queue
		PriorityQueue<Node> pQueue = new PriorityQueue<Node>();
		
		// Add items to queue
		pQueue.offer(node1); // same code as pQueue.add(node1)
		pQueue.offer(node2);
		pQueue.offer(node3);
		pQueue.offer(node4);
		pQueue.offer(node5);
		
		// Get items from queue
		while(!pQueue.isEmpty()) {
			Node node = pQueue.poll();
			System.out.println(node);
		}
	}

}

데이터를 5개 생성해서, 우선순위 큐 저장소에 저장하고 최종적으로 26번 코드를 통해 5개의 데이터를 우선순위에 따라 꺼내어 화면에 표시합니다. 그 결과는 아래와 같습니다.

42318742-edb5-11e6-bc64-92361f002671(3.4)
42318878-edb5-11e6-bc64-92361f002671(4.8)
42318a3a-edb5-11e6-bc64-92361f002671(5.5)
423182c4-edb5-11e6-bc64-92361f002671(6.1)
42318968-edb5-11e6-bc64-92361f002671(7.2)

[C++] binary_search 정리

메모리 기반의 방대한 데이타가 있다고 가정을 할 때.. 이 데이터 목록 중 어떤 데이타가 존재하는지의 여부를 가장 빠르게 검색해주는 방식이 binary_search인데요. 이 STL 함수를 정리해 봅니다.

먼저 메모리 기반의 방대한 데이터를 준비해 봅니다.

#include <iostream>
#include <list>
#include <algorithm>
#include <iterator>

using namespace std;

int main()
{
    list<int> values{ 1, 2, 8, 7 ,6, 5, 4, 1, 9, 3, 5, 6, 7 };

}

데이타가 몇개 되지 않지만 방대하다고 칩시다. binary_search를 사용하기 위해서는 먼저 데이터가 정렬되어 있어야 합니다. 내림차순으로 정렬하는 코드를 추가하면.. 다음과 같습니다.

#include <iostream>
#include <list>
#include <algorithm>
#include <iterator>

using namespace std;

int main()
{
    list<int> values{ 1, 2, 8, 7 ,6, 5, 4, 1, 9, 3, 5, 6, 7 };
    auto predicate = [](int a, int b) { return a > b; };
    values.sort(predicate);

    copy(values.begin(), values.end(), ostream_iterator<double>{ std::cout, " "});
    cout << endl;
}

람다 함수로 내림차순 정렬을 위한 기준으로 삼았습니다. 그리고 잘 정렬되었는지 콘솔에 표시해 보았구요. 이제 binary_search 함수를 사용하기 위한 데이터 덩어리가 준비 되었고, 다음처럼 데이터 덩어리중 값 5가 존재하는지의 여부를 검색하는 코드는 다음과 같습니다.

#include <iostream>
#include <list>
#include <algorithm>
#include <iterator>

using namespace std;

int main()
{
    list<int> values{ 1, 2, 8, 7 ,6, 5, 4, 1, 9, 3, 5, 6, 7 };
    auto predicate = [](int a, int b) { return a > b; };
    values.sort(predicate);

    copy(values.begin(), values.end(), ostream_iterator<double>{ std::cout, " "});
    cout << endl;

    int wanted{ 5 };
    bool bSearched = binary_search(begin(values), end(values), wanted, predicate);
    if (bSearched) {
        cout << "Found !" << endl;
    } else {
        cout << "Not found." << endl;
    }
}

[OpenLayers3] 가장 가까운 포인트 추적

현재 지도 상에 표시된 원 모양의 포인트 피쳐에 대해 마우스 커서 위치에서 가장 가까운 원을 추적해 효과적으로 시각화 해주는 예제를 만들어 보겠습니다. 실행 결과는 아래와 같으니 한번 마우스를 올려 놓고 요래~요래~ 움직여 보세요~

위와 같은 예제를 만들어 보겠습니다. 먼저 필요한 외부 CSS와 JavaScript 라이브러리를 아래처럼 추가합니다.




UI를 만들어 것인데요. 보면 지도 하나 딸랑~ 있습니다. 이 지도는 div 요소에 표시되므로 아래와 같습니다. 이 div의 id를 map으로 지정해 이름을 붙여 추후 참조할 수 있도록 하였습니다.


    

자, 이제 코드를 작성해 이 UI에 영혼을 불어 넣어 보겠습니다. 수리수리마수리..

jQuery의 기능 중 페이지의 구성 요소가 모두 준비되었을때 호출되는 이벤트를 아래처럼 등록합니다.

<script>
    $(function () {
        ....
    });

앞으로 우리가 작성할 모든 코드는 위에서 …. 부분에 추가되는데요. 먼저 지도 화면에 표시할 원에 대한 피쳐(Feature)를 4000개 생성합니다. 이 피쳐의 지오메트리 타입은 포인트이므로 무작위 위치를 가지도록 ol.geom.Point로 지정하고 i와 size라는 속성값을 지정해 둡니다. size에는 i값에 따라 10 또는 20의 값이 지정되도록 하였습니다.

var count = 4000;
var features = new Array(count);
var e = 18000000;

for (var i = 0; i < count; ++i) {
    features[i] = new ol.Feature({
        'geometry': new ol.geom.Point([2 * e * Math.random() - e, 2 * e * Math.random() - e]),
        'i': i,
        'size': i % 2 ? 10 : 20
    });
}

그리고 이 4000개의 포인트에 대해 어떤 스타일로 표시할지를 지정해야 하는데요. 이때 사용하는 styles 객체를 생성해 둡니다.

var styles = {
    '10': new ol.style.Style({
        image: new ol.style.Circle({
            radius: 5,
            fill: new ol.style.Fill({ color: '#666666' }),
            stroke: new ol.style.Stroke({ color: '#bada55', width: 1 })
        })
    }),
    '20': new ol.style.Style({
        image: new ol.style.Circle({
            radius: 10,
            fill: new ol.style.Fill({ color: '#666666' }),
            stroke: new ol.style.Stroke({ color: '#bada55', width: 1 })
        })
    })
};

위의 코드를 보면 10과 20에 대한 원형 스타일 2개를 준비하고 있습니다. 이는 이후의 코드에서 살펴보겠지만, 피쳐의 size 속성에 따라 스타일을 결정하게 됩니다.

ol3에서는 지도 상에 표시되는 것은 레이어(layer)라는 개념이 필요합니다. 이 예제의 경우, 좀더 정확하게는 ol.layer.Vector 레이어 타입인데요. Vector는 도형에 대한 형상을 표현하기 위한 레이어입니다. 레이어는 자신이 표현해야할 데이터를 제공해 주는 데이터소스가 필요합니다. 이 데이터 소스에 대한 객체를 아래처럼 추가합니다.

var vectorSource = new ol.source.Vector({
    features: features,
    wrapX: false
});

위의 코드를 보면 4000개의 포인트 피쳐를 담고 있는 features를 통해 ol.source.Vector 타입의 데이터소스 객체를 생성하고 있습니다. 이제 ol.layer.Vector 레이어를 위한 모든 것이 준비되었으니 레이어를 생성합니다.

var vector = new ol.layer.Vector({
    source: vectorSource,
    style: function (feature) {
        return styles[feature.get('size')];
    }
});

앞서 만들어 두었던 데이터소스를 지정하고 있구요. style 속성에 함수를 지정해 표시해야할 피쳐에 대한 size 속성값에 따라 스타일을 결정하도록 하였습니다.

이제 이렇게 생성된 레이어를 지도 객체에 지정합니다.

var map = new ol.Map({
    layers: [
        new ol.layer.Tile({ source: new ol.source.OSM() }),
        vector
    ],
    target: document.getElementById('map'),
    view: new ol.View({
        center: [0, 0],
        zoom: 6
    })
});

위의 코드 중 2번 줄에 Open Street Map와 앞서 생성한 벡터 레이어를 구성 레이어들로 지정하고 있습니다. 6번은 지도를 표현할 div 요소를 지정하는 것이고, 7번은 지도의 초기 중심 좌표와 줌 레벨을 지정하고 있습니다.

지금까지의 지도 객체에 벡터 레이어를 지정하기 위해 생성한 객체들의 관계를 클래스 다이어그램으로 표현하면 아래와 같습니다.

자, 이제 마우스 커서의 위치에서 가장 가까운 원을 추적할 모든 준비가 끝났습니다. 가장 가까운 원을 추적할때 추적된 원과 커서 사이에 피드백을 시각화할 것인데요. 이를 위해 아래처럼 2개의 전역 변수와 1개의 함수가 필요합니다.

var point = null;
var line = null;

var displaySnap = function (coordinate) {
    var closestFeature = vectorSource.getClosestFeatureToCoordinate(coordinate);

    if (closestFeature === null) {
        point = null;
        line = null;
    } else {
        var geometry = closestFeature.getGeometry();
        var closestPoint = geometry.getClosestPoint(coordinate);

        if (point === null) {
            point = new ol.geom.Point(closestPoint);
        } else {
            point.setCoordinates(closestPoint);
        }

        if (line === null) {
            line = new ol.geom.LineString([coordinate, closestPoint]);
        } else {
            line.setCoordinates([coordinate, closestPoint]);
        }
    }

    map.render();
};

displaySnap이라는 함수를 설명하면, 함수의 인자로 마우스 커서 위치에 대한 지도 좌표를 받습니다. 5번 코드에서 앞서 생성해 두었던 벡터 레이어의 데이터소스의 getCloestFeatureToCoordinate라는 매우 설명에 충실한 함수를 통해서 데이터 소스의 피쳐들 중 현재 마우스 커서 위치에서 가장 가까운 피쳐를 검사해 존재한다면 반환합니다. 만약 7번의 if 문을 통해 가까운 피쳐가 존재하지 않는다면 point와 line 변수를 null로 지정하고, 가까운 피쳐가 존재한다면 point 변수는 가장 가까운 포인트 피쳐의 위치를 가지는 ol.geom.Point 객체를 생성해 지정하고 line에는 커서의 지도 좌표 및 가장 가까운 포인트 피쳐의 위치 좌표로 구성된 ol.geom.LineString 객체를 생성해 지정합니다. 이 point와 line을 지도 상에 그려주면 피드백 시각화가 완성되는 것입니다. 이러한 시각화를 위해 일단 28번 코드에서 map.render() 함수를 호출하는데요. 이 함수가 호출되면 map에서 postcompose 이벤트가 호출되므로 이 이벤트에 대한 코드를 살펴보면..

var stroke = new ol.style.Stroke({
    color: 'rgba(255,255,0,0.9)',
    width: 3
});

var style = new ol.style.Style({
    stroke: stroke,
    image: new ol.style.Circle({
        radius: 10,
        stroke: stroke
    })
});

map.on('postcompose', function (evt) {
    var vectorContext = evt.vectorContext; // ol.render.VectorContext 
    vectorContext.setStyle(style);

    if (point !== null) {
        vectorContext.drawGeometry(point);
    }

    if (line !== null) {
        vectorContext.drawGeometry(line);
    }
});

위의 코드를 살펴보면, 1번과 6번의 코드에서 앞서 point 객체와 line 객체를 그리기 위해 사용되는 스타일 객체를 생성해 둡니다. 그리고 14번에서 map에 대해 postcompose 이벤트를 등록하는데요. 보시면 앞서 생생해둔 point, line을 drawGeometry 함수를 이용해 그려주고 있습니다.

자, 이제 마지막으로 displaySnap 함수를 적정한 시점에서 호출해 주기만 하면 되는데요. 그 시점은 지도 상에 마우스 커스를 이동하거나 클릭할때 정도가 될것 같습니다. 이 마우스 이동과 클릭에 대한 이벤트 코드를 아래처럼 지정해 둡니다.

map.on('pointermove', function (evt) {
    if (evt.dragging) {
        return;
    }

    var coordinate = map.getEventCoordinate(evt.originalEvent);
    displaySnap(coordinate);
});

map.on('click', function (evt) {
    displaySnap(evt.coordinate);
});

이상으로 지금까지 작성된 코드는 아래의 링크를 통해 다운로드 받을 수 있습니다.

DuraMap-Xr을 이용한 좌표 변환 코드 샘플

DuraMap-Xr은 proj.4 기반의 문자열을 토대로 좌표계를 변환할 수 있는데요. 특히 좌표 변환시 10 파라메터에 해당하는 Molodensky-Badekas 모델을 지원합니다.

아래의 코드는 “대한민국 TM 중부원점(Bessel 타원체) – 10.405 보정”의 좌표계의 한 좌표인 (492385.95, 188096.47)를 “대한민국 TM 중부원점(GRS80 타원체)” 좌표계로 변환하는 코드 예입니다.

XrMapLib.Coord Pt;

Pt.X = 492385.95;
Pt.Y = 188096.47;

XrMapLib.Projection proj = new XrMapLib.Projection();

// 대한민국 TM 중부원점(Bessel 타원체) - 10.405 보정
proj.SourceProj4String = "+proj=tmerc +lat_0=38N +lon_0=127.0028902777778E +ellps=bessel +x_0=200000 +y_0=600000 +k=1 +units=m +no_defs";

// 대한민국 TM 중부원점(GRS80 타원체)
proj.TargetProj4String = "+proj=tmerc +lat_0=38N +lon_0=127E +ellps=GRS80 +x_0=200000 +y_0=600000 +k=1 +units=m +no_defs";

// 10 파라메터 적용(towgs84 파라메터)
proj.Set10Parameters(-145.907, 505.034, 685.756, -1.162, 2.347, 1.592, 6.342, -3159521.31, 4068151.32, 3748113.85);

proj.Transform(Pt);

코드를 보면 좌표계 지정을 위해서 사용한 proj.4 문자열에 towgs84에 해당하는 값을 14번째 코드처럼 별도의 매서드(Set10Parameters)로 지정한다는 것입니다. 3개 또는 7개의 파라메터 지정을 위해서는 각각 Set3Parameters, Set7Parameters 매서드를 사용합니다.

최종 좌표변환은 16번 코드의 Transform 매서드로 수행되는데요. 이 매서드는 변환할 입력 좌표 객체를 필요로 하고, 변환된 좌표는 다시 이 입력 좌표 객체에 저장되므로, 입력 좌표를 계속 유지하려면 복사본을 가지고 있어야 합니다.