[OpenLayers3] 지도 위에 DOM 요소를 올려놓기(Overlay)

ol3에서 DIV 등과 같은 DOM 요소를 지도 상에 올려 놓는 내용을 설명합니다. 일단 지도 위에 올려 놨다는 것은 지도의 뷰가 변경되면, 변경된 지도에 따라 올려 놓은 DOM 요소도 함께 이동되어야 한다는 것인데요. 구현하고자 하는 내용에 대한 화면은 아래와 같습니다.


위의 지도를 보면 지도 상에 이미지와 문자열이 보입니다. 이미지는 DIV 요소 안에 IMG 요소로 표현했고, 문자열은 A 요소로 표현했습니다. A 요소로 표현했으니 문자열을 클릭하면 A 요소의 href 속성에 지정된 URL에 대한 페이지가 뜹니다.

위의 지도 기능을 위해 필요한 외부 css와 js 파일을 아래처럼 페이지에 추가합니다.




다음은 화면을 구성하는 UI 요소에 대한 코드입니다.

id가 map인 DIV에 지도가 표시될 것입니다. 또한 id가 txtlink인 A 요소와 id가 marker인 DIV 요소는 ol3의 Overlay로써 지도와 함께 원하는 지점에 표시되도록 할 것입니다. 아래는 UI 요소에 대한 스타일입니다.


이제 스크립트 코드를 작성할 것인데요. 먼저 jQuery의 ready 이벤트를 아래처럼 미리 준비해 둡니다.


위의 ….에 스크립트를 추가해 나가겠습니다. 먼저 지도에 오픈스트리트맵을 배경지도로 구성하기 위해 타일 레이어 객체를 준비합니다.

var layer = new ol.layer.Tile({
    source: new ol.source.OSM()
});

그리고 id가 map인 DIV 요소에 지도를 표시하기 위해 지도 객체를 아래처럼 추가하는데요. 이때 앞서 만들어 둔 레이어 객체를 지정합니다.

var map = new ol.Map({
    layers: [layer],
    target: 'map',
    view: new ol.View({
        center: [0, 0],
        zoom: 2
    })
});

앞서 id가 txtlink와 marker인 두 요소를 지도 상에 원하는 위치에 Overlay 한다고 하였습니다. 그 원하는 위치를 아래처럼 pos라는 변수에 담아 둡니다.

var pos = ol.proj.fromLonLat([16.3725, 48.208889]);

먼저 id가 marker인 DIV 요소에 대한 Overlay를 위한 코드입니다.

var marker = new ol.Overlay({
    position: pos,
    positioning: 'center-center',
    element: document.getElementById('marker'),
    stopEvent: false
});

map.addOverlay(marker);

Overlay 생성을 위해서 먼저 위의 코드의 1번에서처럼 ol.Overlay 클래스 타입을 생성합니다. 생성시에 Overlay될 위치와 이 위치를 기준으로 어떻게 자리를 잡을지를 위한 positioning 옵션을 지정하고, Overlay 대상이 되는 요소를 element 옵션에 지정합니다. stopEvent 옵션을 false로 지정함으로써 다른 요소와 중첩될때 그 요소의 이벤트가 발생하도록 허용 합니다. 이처럼 Overlay 객체를 생성했으면 8번 코드처럼 map 객체의 addOverlay 함수를 호출하여 Overlay 요소를 추가하면, 지도와 함께 항상 지정된 위치에 표시됩니다. 다음은 id가 txtlink인 요소에 대한 Overlay 생성 및 지도에 추가하는 코드입니다.

var txtlink = new ol.Overlay({
    position: pos,
    positioning: 'center-center',
    element: document.getElementById('txtlink')
});

map.addOverlay(txtlink);

이제 실행해 보면, 처음 살펴봤던 Overlay 지도 기능을 볼 수 있습니다. 아래의 링크는 지금까지 작성한 전체 코드에 대한 다운로드입니다.

[OpenLayers3] 지도 상에 포인트 마커 표시 및 클릭

배경지도 위의 원하는 위치에 마커를 표시하고, 표시된 마커를 클릭하면 저장된 속성을 표시하는 내용에 대해 살펴보겠습니다. 먼저 아래의 웹 페이지를 살펴보시기 바랍니다.

먼저 필요한 외부 css와 js 파일을 포함하는 코드를 추가합니다.




다음은 UI 코드인데요. 아래처럼 지도를 표시하는 div 하나가 필요합니다.

다음은 스크립트 코드입니다. 웹페이지가 로딩되고 준비가 완료되면 호출되는 jQuery의 ready 이벤트를 아래처럼 준비합니다.

$(function () {
    ....
});

위의 코드 중 …에 앞으로 설명하는 코드를 추가합니다. 먼저 배경지도 위에 표시될 마커에 대한 Feature 변수를 정의해야 하는데요. Feature는 Geometry와 Attribute로 구성됩니다. 아래처럼 iconFeature라는 변수로 정의합니다.

var iconFeature = new ol.Feature({
    geometry: new ol.geom.Point([0, 0]),
    name: '보이지 않는 꿈의 섬',
    population: 4000,
    rainfall: 500
});

위의 코드는 좌표 (0, 0)인 ol.geom.Point 타입의 지오메트리와 name, population, rainfall 속성을 갖는 Feature를 정의하고 있습니다. 다음은 이 Feature를 아이콘으로 표시할 스타일을 정의할 객체 변수입니다.

var iconStyle = new ol.style.Style({
    image: new ol.style.Icon(/** @type {olx.style.IconOptions} */({
        anchor: [0.5, 46],
        anchorXUnits: 'fraction',
        anchorYUnits: 'pixels',
        src: 'https://openlayers.org/en/v3.19.1/examples/data/icon.png'
    }))
});

위의 코드는 src로 지정된 url의 png 이미지로 포인트를 표시하며, 표시될때의 (X, Y)에 대한 기준점으로 (0.5, 46)으로 하는데요. X 축에 대한 값인 0.5의 단위를 anchorXUnits 속성을 통해 백분률(여기서는 0.5이므로 50%)로 지정하고, Y 축에 대한 값인 46의 단위는 anchorYUnits 속성을 통해 픽셀 단위로 지정한다는 의미입니다. 이렇게 하면 아이콘의 뽑족한 밑단이 정확히 가로로 가운데에 표시되게 됩니다. 이렇게 정의된 스타일을 해당 피쳐에 지정되도록 아래의 코드를 실행합니다.

iconFeature.setStyle(iconStyle);

Feature는 ol.source.Vector 클래스 타입의 데이터 소스로부터 담겨져 레이어가 필요로 하는 데이터를 제공하는데요. 아래처럼 데이터 소스와 이 데이터 소스를 이용하는 벡터 레이어 객체를 각각 정의합니다.

var vectorSource = new ol.source.Vector({
    features: [iconFeature]
});

var vectorLayer = new ol.layer.Vector({
    source: vectorSource
});

위의 코드 중 2번을 보면 데이터 소스 객체를 정의할때 features 속성에 앞서 정의한 Feature 객체를 배열의 요소로 담고 있습니다. 다음 코드는 배경맵을 위한 레이어 객체 정의입니다.

var rasterLayer = new ol.layer.Tile({
    source: new ol.source.TileJSON({
        url: 'https://api.tiles.mapbox.com/v3/mapbox.geography-class.json?secure',
        crossOrigin: ''
    })
});

이제 지도 객체를 생성하기 위한 필요한 모든 것이 준비되었는데요. 아래처럼 지도 객체를 생성합니다.

var map = new ol.Map({
    layers: [rasterLayer, vectorLayer],
    target: document.getElementById('map'),
    view: new ol.View({
        center: [0, 0],
        zoom: 3
    })
});

여기까지하면 배경지도 위에 우리가 표시하고자 하는 마커가 원하는 위치에 원하는 아이콘 이미지로 표시됩니다. 이제 이 아이콘을 클릭하면 피쳐에 저장된 속성 중 name의 값을 alert 창으로 표시하는 코드를 아래처럼 작성합니다.

map.on('click', function (evt) {
    var feature = map.forEachFeatureAtPixel(evt.pixel,
        function (feature) {
            return feature;
        });
        
    if (feature) {
        alert(feature.get('name'));
    }
});

클릭한 좌표는 evt.pixel로 얻을 수 있습니다. 이 evt.pixel의 값을 map의 forEachFeatureAtPixel 함수의 첫번재 인자로 전달하면 해당 클릭 좌표에 존재하는 피쳐를 forEachFeatureAtPixel의 두번째 인자로 지정한 함수가 호출되며, 이때 이 호출되는 함수의 인자로 피쳐의 객체가 전달됩니다. 4번 처럼 이 피쳐 객체를 그대로 반환하여 우리가 클릭한 피쳐를 얻을 수 있습니다. 7번에서 클릭한 지점에 feature 객체가 있다면 feature의 name 속성의 값을 alert 창으로 표시합니다.

여기서 한가지 더 추가하여, 우리가 어떤 아이콘을 클릭하기 전에 이 아이콘을 클릭하면 어떤 정보가 제공될것이라는 피드백(Feedback)을 제공하면 더욱 좋을 것인데요. 아래의 코드를 통해 이러한 피드백 효과를 제공할 수 있습니다.

map.on('pointermove', function (e) {
    if (!e.dragging) {
        var pixel = map.getEventPixel(e.originalEvent);
        var hit = map.hasFeatureAtPixel(pixel);
        map.getTarget().style.cursor = hit ? 'pointer' : '';
    }
});

지도를 이동하는 마우스 Dragging이 아닌 경우에 현재 마우스의 좌표 지점에 어떤 피쳐가 존재하는지를 검사해서 만약 마우스 커서 위치에 피쳐가 있다면 마우스 커서를 ‘pointer’ 타입으로 변경하도록 하면 앞서 언급한 피드백이 완성됩니다.

위는 이제까지 작성한 소스 코드의 다운로드입니다.

[OpenLayer3] postcompose 이벤트를 이용한 동적 지도 표현

ol3에서는 PostCompose라는 매서드를 제공하는데요. 이 매서드는 지도를 모두 그린(구성) 후에 발생하는 이벤트로, 이 이벤트를 활용하면 지도 상에서 다양한 동적인 그래픽 표현이 가능합니다. 이러한 지도 상의 동적인 그래픽 표현에 대한 맵 페이지를 살펴보면 아래와 같습니다.

위의 웹 페이지를 보면 Open Street Map을 기본 배경지도로 하고, 이 배경지도 위에 노란색 원이 움직이는 애니메이션을 볼 수 있습니다. 위의 지도 페이지를 어떻게 구현하였는지 코드를 살펴보면…

먼저 필요한 외부 css 및 js 라이브러리를 페이지에 포함합니다.




다음으로 UI를 살펴 보겠는데요. 다음처럼 지도를 표시하기 위한 div 하나가 필요합니다.

이제 필요한 라이브러리와 UI가 준비되었으므로, 스크립트 코드를 살펴 보겠습니다. 웹 페이지 로딩이 완료되면 호출되는 jQuery의 ready 이벤트를 아래처럼 만들어 둡니다.

$(function() {
    ....
});

위의 코드 중 ….에 앞으로 설명할 코드를 추가합니다. 먼저 아래의 코드를 추가하여 지도 표시를 위한 지도 객체를 생성하고, 이 지도를 구성하는 레이어로 ol.source.OSM 클래스를 통해 오픈스트리트맵을 추가합니다. 또한 초기 지도뷰 좌표를 (0,0)으로 하고 줌 레벨을 2로 설정합니다.

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

다음은 지도 상에 원들이 움직이고 있는데.. 이 원에 대한 그리기(draw) 스타일을 위한 스타일 객체를 생성해 둡니다.

var imageStyle = new ol.style.Style({
    image: new ol.style.Circle({
        radius: 5,
        fill: new ol.style.Fill({ color: 'yellow' }),
        stroke: new ol.style.Stroke({ color: 'red', width: 1 })
    })
});

지도를 그리는 렌더링(render)이 완료되면 postcompose라는 이벤트가 발생합니다. 이 이벤트는 벡터 데이터를 구성하여 표현할 수 있는 context를 이벤트 인자로 전달하는데요. 이 vectorContext 객체를 통해 다양한 지오메트리(Geometry)를 구성할 수 있습니다. 아래의 postcompose 이벤트 함수로 등록된 코드를 먼저 살펴보겠습니다.

map.on('postcompose', function (event) {
    var n = 178;
    var omegaTheta = 30000; // Rotation period in ms
    var R = 7e6;
    var r = 2e6;
    var p = 2e6;
    var vectorContext = event.vectorContext;
    var frameState = event.frameState;
    var theta = 2 * Math.PI * frameState.time / omegaTheta;
    var coordinates = [];
                
    for (var i = 0; i < n; ++i) {
        var t = theta + 2 * Math.PI * i / n;
        var x = (R + r) * Math.cos(t) + p * Math.cos((R + r) * t / r);
        var y = (R + r) * Math.sin(t) + p * Math.sin((R + r) * t / r);
        coordinates.push([x, y]);
    }

    vectorContext.setStyle(imageStyle);
    vectorContext.drawGeometry(new ol.geom.MultiPoint(coordinates));

    map.render();
});

먼저 7번 코드의 vectorContext 객체는 앞서 언급한 지오메트리를 그리기 위한 API를 제공합니다. 19번 코드에서 setStyle 함수에 앞서 정의해 둔 스타일 객체인 imageStyle을 인자로 전달해 스타일을 지정하고 20번 코드에서 지오메트리의 구성 좌표와 지오메트리의 타입에 대한 객체를 drawGeometry 함수에 전달하면 화면에 해당 지오메트리가 그려지게 됩니다. 애니메이션은 시간의 흐름에 따라 그 상태가 변경되어야 하는데, 시간의 흐름은 8번과 9번 코드의 frameState의 time으로 제어할 수 있습니다. 중요한 키 포인트는 시간의 흐름에 따라 지오메트리의 구성 좌표를 변경하여 다시 그려주기를 반복하면 애니메이션 효과가 나타나게 됩니다. 다시 그려주라는 명령은 22번 코드의 map.render() 함수입니다.

다음은 지금까지 작성된 전체 코드에 대한 다운로드입니다.

[OpenLayers3] 히트맵(HeatMap)

ol3에서 포인트 데이터를 받아 포인트 데이터가 많이 모여 있는 곳을 효과적으로 시각화 해주는 히트맵에 대한 예를 살펴 보겠습니다. 먼저 히트맵에 대해 구현하고조 하는 결과를 아래의 웹 페이지에서 먼저 확인해 봅시다.

위의 실행 결과는 지진이 발생하 지점을 히트맵으로 표시하는 것인데요. 지진 발생이 밀집된 곳은 빨간색으로 표시됨으로써 지진 발생 빈도가 많은 지점을 시각적으로 빠르고 효과적으로 판단할 수 있습니다. 히트맵 결과를 얻기 위해서는 포인트 데이터뿐만 아니라 반경 크기(Radius Size)나 블러링 크기(Blur Size) 값이 필요합니다. 각각의 포인트 지점에서 주어진 반경 크기 안에 포함되는 다른 포인트를 찾아 히트맵의 결과에 반영함으로 반경 크기가 클수록 더 많은 포인트가 그 결과에 반영됨으로써 빨간색의 결과가 더 많이 표시됩니다. 그리고 블러링 크기가 클수록 색상으로 더 흐리게 표시하게 됩니다.

자, 이제 위의 웹페이지에 대한 코드를 살펴볼텐데요. 먼저 필요한 외부 CSS와 스크립트를 포함합니다.




OpenLayers3에 대한 CSS와 js 파일 및 jQuery에 대한 js 파일이 포함되었습니다. 다음은 UI에 대한 코드입니다.

지도가 표시될 div 요소가 있는데, id가 map으로 지정되어 있구요. 히트맵을 위한 입력값을 위한 input 요소가 2개가 있으며 각각에 대한 id를 radius와 blur로 지정되어 있습니다. 이 input 요소는 앞서 설명드렸듯이 각각 검색 반경 크기와 블러링 크기값으로 사용됩니다. 이제 UI에 영혼을 불어 넣을 스크립트를 살펴보겠습니다.

먼저 jQuery의 ready 이벤트를 아래처럼 준비해 둡니다.


위의 …. 부분에 코드를 추가할 것인데요. jQuery의 ready 이벤트는 웹페이지에 대한 모든 준비가 끝났을때 호출됩니다.

먼저 히트맵 표시를 위한 검색반경 및 블러링 크기값을 UI로 부터 얻기 위한 객체를 미리 정의합니다.

var blur = $('#blur');
var radius = $('#radius');

히트맵도 레이어의 종류 중 하나인데요. 다음처럼 히트맵을 위한 레이어 변수를 정의합니다.

var vector = new ol.layer.Heatmap({
    source: new ol.source.Vector({
        url: 'https://openlayers.org/en/v3.19.1/examples/data/kml/2012_Earthquakes_Mag5.kml',
        format: new ol.format.KML({
            extractStyles: false
        })
    }),
    blur: parseInt(blur.val(), 10),
    radius: parseInt(radius.val(), 10),                
    weight: function (feature) {
        var magnitude = parseFloat(feature.get('magnitude'));
        return magnitude - 5;
    }
});

히트맵을 위해 포인트 데이터를 입력 받아야 하는데요. 이를 위해 데이터 소스가 필요합니다. 2번 코드에 ol.source.Vector 클래스로 데이터 소스 객체를 생성하는데.. 실제 데이터 경로를 3번 코드에서 url 문자열로 지정하고 있고, 이 url에서 받는 리소스의 형식은 4번 코드를 통해 지정합니다. 그리고 히트맵의 블러링 크기와 검색 반경의 크기 지정을 위해 각각 8번과 9번에서 지정하고 있습니다. 또한 옵션으로 가중치값을 10번 코드에서 지정하고 있는데요. 함수 형태로 각 포인트에 대한 가중치 값을 계산할 수 있는 함수를 지정할 수 있는데, 가중치의 값이 범위가 0~1 이므로 이 함수 역시 0~1 사이의 값으로 떨어지도록 반환해야 합니다. 위의 코드는 포인트에 대한 속성값 중 maginitude 값을 얻어 이 값에 5를 뺀값으로 하고 있습니다. kml 데이터를 보면 모든 포인트의 magnitude 값은 5~6 사이의 소수값이므로 5를 빼면 0~1사이의 값이 됩니다.

다음은 히트맵 밑에 표시될 배경맵으로 Stamen 서비스 중 toner 레이어에 대한 객체 정의입니다.

var raster = new ol.layer.Tile({
    source: new ol.source.Stamen({
        layer: 'toner'
    })
});

이제 히트맵에 대한 레이어와 배경맵에 대한 레이어 객체가 준비되었으므로, 지도 객체를 생성하고 이 두개의 레이어를 추가해 히트맵과 배경맵을 원하는 div에 표시합니다. 아래의 코드를 통해서 말입니다.

var map = new ol.Map({
    layers: [raster, vector],
    target: 'map',
    view: new ol.View({
        center: [13527858.897415451, 1822818.8878542576],
        zoom: 3
    })
});

2번 코드가 앞서 정의해둔 2개의 레이어 객체에 대한 추가이고, 3번은 지도를 표시할 div의 id이며, 4번은 처음 지도가 표시될 좌표와 줌 레벨값입니다.

이제 마지막으로 검색 반경과 블러링 크기를 설정할 수 있는 input UI에 대해 change 이벤트를 지정해서 사용자가 설정값을 변경하면 바로 지도에 반영될 수 있도록 합니다.

radius.on('change', function () {
    vector.setRadius(parseInt(radius.val(), 10));
});

blur.on('change', function () {
    vector.setBlur(parseInt(blur.val(), 10));
});

아래는 위에서 설명한 전체 코드에 대한 다운로드입니다.