[OpenLayers] 지도 상에 마우스로 사각 영역 그리기

지도 상에 마우스로 사각영역을 그리는 행위는 그려진 사각영역에 포함되는 항목을 선택하거나, 사각영역으로 지도를 확대하는 등에 대해 활용될 수 있다. ol에서는 이러한 사용자의 마우스나 키보드에 대한 행위를 Interaction이라는 개념을 통해 제공한다. 또한 마우스로 사각영역을 그리는 특정한 인터렉션에 대해서도 이미 ExtentInteraction 클래스를 만들어 제공함으로 이를 이용해 ol에서 쉽게 사각영역을 그릴 수 있다.

이 글은 ol에서 마우스로 사각영역을 그리는 행위에 대한 API를 간단히 설명한다. 먼저 html 요소는 다음과 같다.



    
        
        OpenLayers
        
    
    
        

index.js에 대한 코드에서 map에 대한 다양한 스크립트 코드가 존재하는데, 하나씩 살펴보자.

먼저 필요한 모듈을 import 하는 코드과 지도 객체를 생성하는 코드이다.

import Map from 'ol/Map.js';
import View from 'ol/View.js';
import ExtentInteraction from 'ol/interaction/Extent.js';
import {Tile as TileLayer} from 'ol/layer.js';
import {OSM} from 'ol/source.js';

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

그리고 마우스를 이용해 사각영역을 그리는 인터렉션인 ExtentInteraction과 관련된 코드이다.

var extent = new ExtentInteraction();

map.addInteraction(extent);
extent.setActive(false);

window.addEventListener('keydown', function(event) {
    if (event.keyCode == 17) {
        extent.setActive(true);
    }
});

window.addEventListener('keyup', function(event) {
    if (event.keyCode == 17) {
        extent.setActive(false);
    }
});

ExtentInteraction 객체를 생성한 후 바로 setActive(false)를 호출하여 마우스를 통한 사각영역을 그리는 행위가 작동하지 않도록 하고 있다. 키보드의 Ctrl 키를 누르고 있을 경우에 사각영역을 그리도록 6~15번 코드에서 window의 keydown과 keyup 이벤트를 처리하고 있는 것을 볼 수 있다. 참고로 Ctrl은 keyCode 값이 17이다.

그려진 사각영역에 대한 MBR 값은 extent.getExtent() 과 같은 형태로 얻을 수 있다는 것도 알아둘만 하다.

[OpenLayers] 사용자 정의 Interaction

ol에서 마우스나 키보드 등을 통한 사용자의 행위에 대한 처리를 위한 Interaction를 정의하는 내용에 대한 정리이다. 이 글은 [OpenLayers] 직접 좌표를 지정하여 구성하는 벡터 레이어에서 작성된 코드를 기반으로 작성되었으므로, 실제 실행하기 위해서는 해당 글에 대한 코드를 먼저 작성한 후 이 글의 코드를 반영하기 바란다.

사용자의 조작에 대한 Interaction을 정의하기 위한 예로써 사용자가 지도에 표시된 Feature를 마우스로 드레그하여 이동하는 경우에 대한 코드를 작성한다.

먼저 아래처럼 필요한 모듈을 하나 추가한다.

import {defaults as defaultInteractions, Pointer as PointerInteraction} from 'ol/interaction.js';

PointInteraction 클래스를 상속받도록 하여, Drag라는 클래스로 하여 아래처럼 생성한다.

/**
 * @constructor
 * @extends {module:ol/interaction/Pointer}
 */
var Drag = (function (PointerInteraction) {
    function Drag() {
        PointerInteraction.call(this, {
            handleDownEvent: handleDownEvent,
            handleDragEvent: handleDragEvent,
            handleMoveEvent: handleMoveEvent,
            handleUpEvent: handleUpEvent
        });

        this.coordinate_ = null;
        this.cursor_ = 'pointer';
        this.feature_ = null;
        this.previousCursor_ = undefined;
    }

    if ( PointerInteraction ) Drag.__proto__ = PointerInteraction;
    Drag.prototype = Object.create( PointerInteraction && PointerInteraction.prototype );
    Drag.prototype.constructor = Drag;

    return Drag;
}(PointerInteraction));

14~17번 코드에서 정의된 private 변수는 사용자가 마우스로 Feature를 이동할때 필요한 변수들이다. Interaction은 사용자가 마우스나 키보드 등과 같은 입력 장치을 통한 상호작용으로 6번 코드의 생성자에서 4개의 마우스 이벤트에 대한 호출 함수를 지정하고 있다. 4개의 마우스 이벤트는 각각 버튼 Down 이벤트, Drag 이벤트, Move 이벤트, 버튼 Up 이벤트이다. 이들 호출 함수는 아래와 같으며
14~17번 코드에서 정의된 private 변수들에 대해 처리하고 있는 것을 볼 수 있다.

function handleDownEvent(evt) {
    var map = evt.map;

    var feature = map.forEachFeatureAtPixel(evt.pixel,
        function(feature) { return feature; });

    if (feature) {
        this.coordinate_ = evt.coordinate;
        this.feature_ = feature;
    }

    return !!feature;
}

function handleDragEvent(evt) {
    var deltaX = evt.coordinate[0] - this.coordinate_[0];
    var deltaY = evt.coordinate[1] - this.coordinate_[1];

    var geometry = this.feature_.getGeometry();
    geometry.translate(deltaX, deltaY);

    this.coordinate_[0] = evt.coordinate[0];
    this.coordinate_[1] = evt.coordinate[1];
}

function handleMoveEvent(evt) {
    if (this.cursor_) {
        var map = evt.map;
        var feature = map.forEachFeatureAtPixel(evt.pixel,
        
            function(feature) {
            return feature;
        });

        var element = evt.map.getTargetElement();
        
        if (feature) {
            if (element.style.cursor != this.cursor_) {
                this.previousCursor_ = element.style.cursor;
                element.style.cursor = this.cursor_;
            }
        } else if (this.previousCursor_ !== undefined) {
            element.style.cursor = this.previousCursor_;
            this.previousCursor_ = undefined;
        }
    }
}

function handleUpEvent() {
    this.coordinate_ = null;
    this.feature_ = null;
    return false;
}

지도에 대해 위에서 정의한 Interaction을 반영하기 위해 지도를 생성 코드에서 아래처럼 반영한다.

var map = new Map({
    // 아래의 한줄이 반영된 코드임
    interactions: defaultInteractions().extend([new Drag()]),
    target: 'map',

    ...

});

실행하여 Feature를 마우스로 드레그 하면 이동되는 것을 확인할 수 있다.

[OpenLayers] 직접 좌표를 지정하여 구성하는 벡터 레이어

ol의 레이어 종류 중 벡터 레이어는 좌표를 통해 클라이언트 단에서 직접 레이어를 그려주는 방식이다. 여기서 좌표는 DBMS에서, 또는 GPX, GeoJSON, IGC, KML, TopoJSON 포맷의 데이터소스에서 받을 수 있는데.. 여기서는 개발자가 직접 좌표를 지정하여 구성하는 내용에 대해 정리해 본다. 이러한 방식은 내가 원하는 데이터 포맷으로부터 벡터 레이어를 구성하기 위한 기반이 되는 내용이기도 하다.

먼저 HTML 구성은 아래와 같다.



    
        
        OpenLayers
        
    
    
        

index.js에 필요한 코드가 들어가는데.. 먼저 필요한 모듈을 아래처럼 추가한다.

import Feature from 'ol/Feature.js';
import Map from 'ol/Map.js';
import View from 'ol/View.js';
import {LineString, Point, Polygon} from 'ol/geom.js';
import {Tile as TileLayer, Vector as VectorLayer} from 'ol/layer.js';
import {TileJSON, Vector as VectorSource} from 'ol/source.js';
import {Fill, Icon, Stroke, Style} from 'ol/style.js';

벡터 레이어는 벡터 소스를 통해 구성되는데.. 벡터 소스는 Feature들의 집합이기도 하다. 이 Feature 요소를 3개 생성해보자. 아래처럼..

var pointFeature = new Feature(new Point([0, 0]));

var lineFeature = new Feature(new LineString([[-1e7, 1e6], [-1e6, 3e6]]));

var polygonFeature = new Feature(
    new Polygon([[[-3e6, -1e6], [-3e6, 1e6], [-1e6, 1e6], [-1e6, -1e6], [-3e6, -1e6]]]));

포인트 피쳐, 라인 피쳐, 폴리곤 피쳐를 생성하고 있다. 이 3개의 피쳐로 구성된 데이터소스는 아래처럼 생성된다.

var vectorsource = new VectorSource({
    features: [pointFeature, lineFeature, polygonFeature]
});

데이터소스가 정의되었음으로 이제 시각화를 위한 레이어 객체를 아래처럼 생성한다.

var vectorlayer = new VectorLayer({
    source: vectorsource,
    style: new Style({
        image: new Icon(({
            anchor: [0.5, 46],
            anchorXUnits: 'fraction',
            anchorYUnits: 'pixels',
            opacity: 0.95,
            src: 'data/icon.png'
        })),
        stroke: new Stroke({
            width: 3,
            color: [255, 0, 0, 1]
        }),
        fill: new Fill({
            color: [0, 0, 255, 0.6]
        })
    })
});

레이어는 시각화를 위한 개념이 가장 중요함으로 시각화를 위한 스타일을 상세하기 지정하고 있는 것을 알 수 있다. 이제 앞서 생성한 레이어를 구성하여 지도 객체를 생성하면 끝이다.

var map = new Map({
    target: 'map',
    layers: [
        new TileLayer({
            source: new TileJSON({
                url: 'https://api.tiles.mapbox.com/v3/mapbox.geography-class.json?secure'
            })
        }),
        vectorlayer
    ],
    view: new View({
        center: [0, 0],
        zoom: 2
    })
});

실행 결과의 화면은 아래와 같다.

[OpenLayers] 좌표 Array(배열)을 통한 Feature 생성

OpenLayer의 Feature는 좌료값을 저장하는 Geometry와 속성값을 저장하는 Property를 갖습니다. 이 중 Feature의 Geometry는 Feature 클래스 생성자의 인자로 받으며, 생성된 Feature 객체의 getGeometry 매서드로 얻을 수 있습니다. Geometry는 Point, LineString, Polygon의 부모 클래스로써 각 자식 클래스를 생성할 때 좌표값을 배열로 지정할 수 있습니다.

Point, LineString, Polygon에 대한 각각의 Feature 생성에 대한 코드의 예시는 아래와 같습니다.

var pointFeature = new Feature(
    new Point(
        [0, 0]
    )
);

var lineFeature = new Feature(
    new LineString(
        [
            [-1e7, 1e6], [-1e6, 3e6]
        ]
    )
);

var polygonFeature = new Feature(
    new Polygon(
        [
            [
                [-3e6, -1e6], [-3e6, 1e6], [-1e6, 1e6], [-1e6, -1e6], [-3e6, -1e6]
            ]
        ]
    )
);