logo
Tutorials Examples naver map js api v3 네이버 지도 API 마커

마커

마커는 지도 위의 한 위치를 아이콘으로 표시하는 오버레이입니다. 아이콘은 이미지를 사용하거나, 벡터 그래픽 폴리곤 등으로 사용할 수 있습니다.

원하는 위치에 마커 올리기

마커를 사용하려면 Marker 클래스의 객체를 생성해야 합니다. 생성자에는 MarkerOptions 객체 리터럴을 인수로 넘겨서 마커의 속성을 설정해야 합니다.
마커는 지도상의 한 위치를 아이콘으로 표시하는 객체입니다. 따라서 위치를 나타내는 position 속성을 반드시 입력해야 합니다. 그 외의 속성은 기본값을 제공합니다.

다음 예제는 position 속성만 설정한 기본 마커를 네이버 그린팩토리 위에 올리는 예제입니다.

var map = new naver.maps.Map('map', {
    center: new naver.maps.LatLng(37.3595704, 127.105399),
    zoom: 10
});

var marker = new naver.maps.Marker({
    position: new naver.maps.LatLng(37.3595704, 127.105399),
    map: map
});

Examples: 마커 표시하기

다음은 마커의 setPosition 메서드를 사용하여 지도에 클릭한 지점으로 마커의 위치를 옮기는 예제입니다.

var map = new naver.maps.Map('map', {
    center: new naver.maps.LatLng(37.3595704, 127.105399),
    zoom: 10
});

var marker = new naver.maps.Marker({
    position: new naver.maps.LatLng(37.3595704, 127.105399),
    map: map
});

naver.maps.Event.addListener(map, 'click', function(e) {
    marker.setPosition(e.latlng);
});

Examples: 클릭한 지점으로 마커 옮기기

원하는 이미지를 마커 아이콘으로 사용하기

마커의 icon 속성은 마커의 외형인 아이콘을 정의하는 객체입니다. icon 속성은 두 가지 타입의 객체 리터럴로 정의합니다.

  • ImageIcon: 이미지 파일을 사용하여 아이콘을 설정합니다.
  • SymbolIcon: 백터 그래픽(폴리곤)을 사용하여 아이콘을 설정합니다.

이 절에서는 ImageIcon 객체 리터럴을 활용하여 원하는 이미지의 마커를 사용하는 예제를 다룹니다.

다음은 icon 속성에 ImageIcon 객체 리터럴를 설정하여 특정 이미지의 마커를 만드는 예제입니다.

var position = new naver.maps.LatLng(37.3595704, 127.105399);

var map = new naver.maps.Map('map', {
    center: position,
    zoom: 10
});

var markerOptions = {
    position: position,
    map: map,
    icon: {
        url: './img/pin_default.png',
        size: new naver.maps.Size(22, 35),
        origin: new naver.maps.Point(0, 0),
        anchor: new naver.maps.Point(11, 35)
    }
};

var marker = new naver.maps.Marker(markerOptions);

Examples: 이미지 아이콘 사용하기

icon 속성에 ImageIcon 객체 리터럴 대신 이미지의 url만 넣어 주어도 위 예제와 동일하게 동작합니다.
icon 속성에 url 문자열을 넣으면 NAVER 지도 API v3 내부에서 자동으로 ImageIcon 객체 리터럴로 변환하고, ImageIconsize 속성은 이미지의 크기, origin 속성은 (0, 0), anchor 속성은 이미지의 가운데 아래 포인터를 기본값으로 설정합니다.

var position = new naver.maps.LatLng(37.3595704, 127.105399);

var map = new naver.maps.Map('map', {
    center: position,
    zoom: 10
});

var markerOptions = {
    position: position,
    map: map,
    icon: './img/pin_default.png'
};

var marker = new naver.maps.Marker(markerOptions);

하나의 이미지에 여러 모양의 아이콘을 합친 스프라이트 이미지를 활용하여 마커를 만들 수도 있습니다.

var MARKER_ICON_URL = './img/sp_pins_spot_v2.png';

var MARKER_SPRITE_X_OFFSET = 29;
var MARKER_SPRITE_Y_OFFSET = 50;

var MARKER_SPRITE_POSITION = {

    "A0": [0, 0],
    "B0": [MARKER_SPRITE_X_OFFSET, 0],
    "C0": [MARKER_SPRITE_X_OFFSET*2, 0],
    "D0": [MARKER_SPRITE_X_OFFSET*3, 0],
    "E0": [MARKER_SPRITE_X_OFFSET*4, 0],
    "F0": [MARKER_SPRITE_X_OFFSET*5, 0],
    "G0": [MARKER_SPRITE_X_OFFSET*6, 0],
    "H0": [MARKER_SPRITE_X_OFFSET*7, 0],
    "I0": [MARKER_SPRITE_X_OFFSET*8, 0],

    "A1": [0, MARKER_SPRITE_Y_OFFSET],
    "B1": [MARKER_SPRITE_X_OFFSET, MARKER_SPRITE_Y_OFFSET],
    "C1": [MARKER_SPRITE_X_OFFSET*2, MARKER_SPRITE_Y_OFFSET],
    "D1": [MARKER_SPRITE_X_OFFSET*3, MARKER_SPRITE_Y_OFFSET],
    "E1": [MARKER_SPRITE_X_OFFSET*4, MARKER_SPRITE_Y_OFFSET],
    "F1": [MARKER_SPRITE_X_OFFSET*5, MARKER_SPRITE_Y_OFFSET],
    "G1": [MARKER_SPRITE_X_OFFSET*6, MARKER_SPRITE_Y_OFFSET],
    "H1": [MARKER_SPRITE_X_OFFSET*7, MARKER_SPRITE_Y_OFFSET],
    "I1": [MARKER_SPRITE_X_OFFSET*8, MARKER_SPRITE_Y_OFFSET],

    "A2": [0, MARKER_SPRITE_Y_OFFSET*2],
    "B2": [MARKER_SPRITE_X_OFFSET, MARKER_SPRITE_Y_OFFSET*2],
    "C2": [MARKER_SPRITE_X_OFFSET*2, MARKER_SPRITE_Y_OFFSET*2],
    "D2": [MARKER_SPRITE_X_OFFSET*3, MARKER_SPRITE_Y_OFFSET*2],
    "E2": [MARKER_SPRITE_X_OFFSET*4, MARKER_SPRITE_Y_OFFSET*2],
    "F2": [MARKER_SPRITE_X_OFFSET*5, MARKER_SPRITE_Y_OFFSET*2],
    "G2": [MARKER_SPRITE_X_OFFSET*6, MARKER_SPRITE_Y_OFFSET*2],
    "H2": [MARKER_SPRITE_X_OFFSET*7, MARKER_SPRITE_Y_OFFSET*2],
    "I2": [MARKER_SPRITE_X_OFFSET*8, MARKER_SPRITE_Y_OFFSET*2]
};

var myLatlng = new naver.maps.LatLng(37.3614483, 129.1114883);

var mapOptions = {
    zoom: 11,
    center: myLatlng
};

var map = new naver.maps.Map(document.getElementById('map'), mapOptions);

var bounds = map.getBounds(),
    southWest = bounds.getSW(),
    northEast = bounds.getNE(),
    lngSpan = northEast.lng() - southWest.lng(),
    latSpan = northEast.lat() - southWest.lat();

var markers = [], marker, position;

for (var key in MARKER_SPRITE_POSITION) {

    position = new naver.maps.LatLng(
        southWest.lat() + latSpan * Math.random(),
        southWest.lng() + lngSpan * Math.random());

    marker = new naver.maps.Marker({
        map: map,
        position: position,
        title: key,
        icon: {
            url: MARKER_ICON_URL,
            size: new naver.maps.Size(24, 37),
            anchor: new naver.maps.Point(12, 37),
            origin: new naver.maps.Point(MARKER_SPRITE_POSITION[key][0], MARKER_SPRITE_POSITION[key][1])
        }
    });
};

Examples: 스프라이트 이미지 아이콘 사용하기

이미지 아이콘 마커 사용 시 레티나 디스플레이 대응하기

ImageIcon 객체 리터럴에 scaledSize 속성이 있습니다. scaledSize는 아이콘으로 사용하는 이미지의 크기를 강제로 조절하는 속성입니다. scaledSize를 입력하지 않으면 이미지의 원본 크기가 scaledSize에 기본값으로 입력됩니다.

레티나 디스플레이에 대응하려면 화면에 보이는 이미지보다 두 배 큰 이미지를 준비하고 scaledSize를 원본 이미지 크기의 반으로 입력합니다. 큰 이미지를 작게 보여주어 레티나 디스플레이에서도 정상적으로 보이게 하는 원리입니다.

다음은 레티나 디스플레이에 대응하는 단일 이미지 마커 예제입니다.
50 x 68 크기의 이미지를 25 x 34 크기의 마커 아이콘으로 사용하여 레티나 디스플레이에 대응합니다.


var position = new naver.maps.LatLng(37.3595704, 127.105399);

var map = new naver.maps.Map('map', {
    center: position,
    zoom: 10
});

var marker = new naver.maps.Marker({
    position: position,
    map: map,
    icon: {
        url: './img/ico_pin.jpg',
        size: new naver.maps.Size(25, 34),
        scaledSize: new naver.maps.Size(25, 34),
        origin: new naver.maps.Point(0, 0),
        anchor: new naver.maps.Point(12, 34)
    }
});

Examples: 이미지 아이콘 레티나 대응하기

다음은 레티나 디스플레이에 대응하는 스프라이트 이미지 마커 예제입니다.


var map = new naver.maps.Map('map', {
    center: new naver.maps.LatLng(37.3595704, 127.105399),
    zoom: 10
});

makeMarker(map, new naver.maps.LatLng(37.3595704, 127.105399), 0);
makeMarker(map, new naver.maps.LatLng(37.3618025, 127.1153248), 1);
makeMarker(map, new naver.maps.LatLng(37.3561936, 127.0983706), 2);

function makeMarker(map, position, index) {

    var ICON_GAP = 31;
    var ICON_SPRITE_IMAGE_URL = './img/sp_pin_hd.png';
    var iconSpritePositionX = (index * ICON_GAP) + 1;
    var iconSpritePositionY = 1;

    var marker = new naver.maps.Marker({
        map: map,
        position: position,
        icon: {
          url: ICON_SPRITE_IMAGE_URL,
          size: new naver.maps.Size(26, 36), // 이미지 크기
          origin: new naver.maps.Point(iconSpritePositionX, iconSpritePositionY), // 스프라이트 이미지에서 클리핑 위치
          anchor: new naver.maps.Point(13, 36), // 지도상 위치에서 이미지 위치의 offset값
          scaledSize: new naver.maps.Size(395, 79)
        }
    });

    return marker;
}

Examples: 스프라이트 이미지 아이콘 레티나 대응하기

폴리곤을 마커 아이콘으로 사용하기

지금까지 MarkerOptionsicon 속성에 ImageIcon 객체 리터럴을 설정하여 이미지 파일을 마커 아이콘으로 활용하는 예제를 보았습니다.
icon 속성에 SymbolIcon 객체 리터럴을 설정하면 이미지 파일 대신 폴리곤을 그려 마커의 아이콘으로 활용할 수 있습니다.


var position = new naver.maps.LatLng(37.3595704, 127.105399);

var map = new naver.maps.Map('map', {
    center: position,
    zoom: 10
});

var marker = new naver.maps.Marker({
    map: map,
    position: position,
    icon: {
        path: [
            new naver.maps.Point(0, 70), new naver.maps.Point(20, 100), new naver.maps.Point(40, 70),
            new naver.maps.Point(30, 70), new naver.maps.Point(70, 0), new naver.maps.Point(10, 70)
        ],
        anchor: new naver.maps.Point(23, 103),
        fillColor: '#ff0000',
        fillOpacity: 1,
        strokeColor: '#000000',
        strokeStyle: 'solid',
        strokeWeight: 3
    },
    shadow: {
        url: "./img/shadow-arrow.png",
        size: new naver.maps.Size(193, 128),
        origin: new naver.maps.Point(0, 0),
        anchor: new naver.maps.Point(62, 120)
    }
});

Examples: 심벌 아이콘 사용하기

HTML 마크업을 마커 아이콘으로 사용하기

이미지 또는 심벌 외에도 HTML 마크업을 이용해 마커를 생성할 수 있습니다. 'icon' 속성에 HtmlIcon 객체 리터럴을 설정하면 설정한 HTML을 노출하는 마커를 생성할 수 있습니다.

var boundary = naver.maps.LatLngBounds.bounds(new naver.maps.LatLng(37.3724620, 127.1051714), new naver.maps.LatLng(37.3542795, 127.1174332));
var unarySpot = new naver.maps.LatLng(37.3637770, 127.1174332);

var map = new naver.maps.Map(document.getElementById('map'), { zoom: 10 });
    map.fitBounds(boundary);

var unarySpotMarker = new naver.maps.Marker({
    position: unarySpot,
    map: map,
    title: 'Unary Spot!!',
    icon: {
        content: '<img src="./img/pin_default.png" alt="" style="margin: 0px; padding: 0px; border: 0px solid transparent; display: block; max-width: none; max-height: none; -webkit-user-select: none; position: absolute; width: 22px; height: 35px; left: 0px; top: 0px;">',
        size: new naver.maps.Size(22, 35),
        anchor: new naver.maps.Point(11, 35)
    }
});

Examples: HTML 아이콘 사용하기

마커의 인터랙션 영역 정의하기

MarkerOptionsshape 속성은 사용자 인터랙션의 영역(마우스, 터치 이벤트)을 정의하는 속성입니다.
기본값은 null이고, shapenull인 경우는 다음과 같이 사용자 인터랙션의 영역을 설정합니다.

  • iconImageIcon이면 마커 크기만큼의 사각 영역을 사용자 인터랙션 영역으로 설정합니다.
  • iconSymbolIcon이면 폴리곤의 모양 그대로 사용자 인터랙션 영역으로 설정합니다.

마커의 사용자 인터랙션의 영역을 임의로 설정하고 싶은 경우 shape 속성에 MarkerShape 객체 리터럴을 설정하여 원하는 모양의 좌표를 입력합니다.
다음 예제는 마커의 모양대로 사용자 인터랙션을 설정하기 위해 shape 속성을 활용한 예제입니다.

var position = new naver.maps.LatLng(37.3595704, 127.105399);

var map = new naver.maps.Map('map', {
    center: position,
    zoom: 10
});

var markerOptions = {
    position: position,
    map: map,
    icon: {
        url: './img/pin_default.png',
        size: new naver.maps.Size(22, 35),
        origin: new naver.maps.Point(0, 0),
        anchor: new naver.maps.Point(11, 35)
    },
    shadow: {
        url: './img/shadow-pin_default.png',
        size: new naver.maps.Size(40, 35),
        origin: new naver.maps.Point(0, 0),
        anchor: new naver.maps.Point(11, 35)
    },
    shape: {
        coords: [11, 0, 9, 0, 6, 1, 4, 2, 2, 4,
                0, 8, 0, 12, 1, 14, 2, 16, 5, 19,
                5, 20, 6, 23, 8, 26, 9, 30, 9, 34,
                13, 34, 13, 30, 14, 26, 16, 23, 17, 20,
                17, 19, 20, 16, 21, 14, 22, 12, 22, 12,
                22, 8, 20, 4, 18, 2, 16, 1, 13, 0],
        type: 'poly'
    }
};

var marker = new naver.maps.Marker(markerOptions);

Examples: 마커 인터랙션 영역 설정하기

마커의 애니메이션 정의하기

MarkerOptionsanimation 속성은 마커의 애니메이션을 정의하는 속성입니다. 제공하는 애니메이션은 다음과 같습니다.

  • BOUNCE: 마커가 지도에 노출되면 마커 위치에서 튀기는 애니메이션을 수행합니다. 이 애니메이션은 animation 속성을 null로 설정해 애니메이션을 중지할 때까지 계속 수행됩니다.
  • DROP: 마커를 지도에 노출할 때 지도 위에서 떨어지는 애니메이션을 수행합니다. 최종적으로 떨어지는 위치는 마커의 위치입니다. 이 애니메이션은 한 번만 수행되며, 수행 후 animation 속성은 null로 바뀝니다.

다음은 마커를 지도에 표시할 때 DROP 애니메이션을 적용한 예제입니다.

var mapOptions = {
    zoom: 11
};

var map = new naver.maps.Map(document.getElementById('map'), mapOptions);
map.fitBounds(naver.maps.LatLngBounds.bounds(new naver.maps.LatLng(37.3724620, 127.1051714), new naver.maps.LatLng(37.3542795, 127.1174332)));

var urlMarker = new naver.maps.Marker({
    position: new naver.maps.LatLng(37.3542795, 127.1072556),
    map: map,
    title: 'urlMarker',
    icon: "./img/pin_default.png",
    animation: naver.maps.Animation.DROP
});

Examples: 애니메이션 마커 표시하기

현재 화면에 보이는 마커만 표시하기

지도 위에 오버레이가 많이 올라갈수록 브라우저의 그래픽 리소스를 많이 사용하게 되고 그만큼 성능이 느려질 수 밖에 없습니다. 따라서 가능한 한 적은 수의 오버레이만 지도 위에 올리는 것이 성능에 도움이 됩니다.

가장 쉽게 생각할 수 있는 방법은 화면에 보이는 오버레이만 지도에 올리고 보이지 않는 오버레이는 지도에서 빼는 것입니다. LatLngBounds, PointBounds 객체의 hasLatLng, hasPoint 메서드를 사용하면 이를 어렵지 않게 구현할 수 있습니다.

LatLngBounds, PointBounds은 특정 영역을 표현하기 위한 클래스입니다. 특정 영역이 정의되면 그 영역 내에 특정 위치가 포함되는지 그렇지 않은지를 판단할 수 있고 이는 hasLatLng, hasPoint 메서드를 이용해 확인할 수 있습니다.

Map 객체는 getBounds 메서드를 제공하여 현재 화면에 표시되고 있는 지도 영역을 LatLngBounds 객체로 반환합니다. 각 마커의 위치가 Map 객체의 getBounds 메서드를 이용해 받은 현재 화면의 지도 영역에 포함되는지 확인하여, 화면에 보이면 지도에 올리고 그렇지 않다면 지도에서 뺄 수 있습니다.

다음은 위에서 설명한 내용을 구현한 예제입니다. 지도를 초기화한 후 getBounds를 이용해 지도 영역을 구하고 해당 영역 내에서 임의의 위치에 여러 개의 마커를 올립니다. 그 후 지도 영역이 변경되는 drag_end, zoom_changed 이벤트에서 각 마커들이 현재 지도 영역에 포함되는지 여부에 따라 지도에 올리거나 뺍니다.

var MARKER_ICON_URL = './img/sp_pins_spot_v3.png';

var MARKER_SPRITE_X_OFFSET = 29;
var MARKER_SPRITE_Y_OFFSET = 50;

var MARKER_SPRITE_POSITION = {

    "A0": [0, 0],
    "B0": [MARKER_SPRITE_X_OFFSET, 0],
    "C0": [MARKER_SPRITE_X_OFFSET*2, 0],
    "D0": [MARKER_SPRITE_X_OFFSET*3, 0],
    "E0": [MARKER_SPRITE_X_OFFSET*4, 0],
    "F0": [MARKER_SPRITE_X_OFFSET*5, 0],
    "G0": [MARKER_SPRITE_X_OFFSET*6, 0],
    "H0": [MARKER_SPRITE_X_OFFSET*7, 0],
    "I0": [MARKER_SPRITE_X_OFFSET*8, 0],

    "A1": [0, MARKER_SPRITE_Y_OFFSET],
    "B1": [MARKER_SPRITE_X_OFFSET, MARKER_SPRITE_Y_OFFSET],
    "C1": [MARKER_SPRITE_X_OFFSET*2, MARKER_SPRITE_Y_OFFSET],
    "D1": [MARKER_SPRITE_X_OFFSET*3, MARKER_SPRITE_Y_OFFSET],
    "E1": [MARKER_SPRITE_X_OFFSET*4, MARKER_SPRITE_Y_OFFSET],
    "F1": [MARKER_SPRITE_X_OFFSET*5, MARKER_SPRITE_Y_OFFSET],
    "G1": [MARKER_SPRITE_X_OFFSET*6, MARKER_SPRITE_Y_OFFSET],
    "H1": [MARKER_SPRITE_X_OFFSET*7, MARKER_SPRITE_Y_OFFSET],
    "I1": [MARKER_SPRITE_X_OFFSET*8, MARKER_SPRITE_Y_OFFSET],

    "A2": [0, MARKER_SPRITE_Y_OFFSET*2],
    "B2": [MARKER_SPRITE_X_OFFSET, MARKER_SPRITE_Y_OFFSET*2],
    "C2": [MARKER_SPRITE_X_OFFSET*2, MARKER_SPRITE_Y_OFFSET*2],
    "D2": [MARKER_SPRITE_X_OFFSET*3, MARKER_SPRITE_Y_OFFSET*2],
    "E2": [MARKER_SPRITE_X_OFFSET*4, MARKER_SPRITE_Y_OFFSET*2],
    "F2": [MARKER_SPRITE_X_OFFSET*5, MARKER_SPRITE_Y_OFFSET*2],
    "G2": [MARKER_SPRITE_X_OFFSET*6, MARKER_SPRITE_Y_OFFSET*2],
    "H2": [MARKER_SPRITE_X_OFFSET*7, MARKER_SPRITE_Y_OFFSET*2],
    "I2": [MARKER_SPRITE_X_OFFSET*8, MARKER_SPRITE_Y_OFFSET*2]
};

var map = new naver.maps.Map('map', {
    center: new naver.maps.LatLng(37.3595704, 127.105399),
    zoom: 10
});

var bounds = map.getBounds(),
    southWest = bounds.getSW(),
    northEast = bounds.getNE(),
    lngSpan = northEast.lng() - southWest.lng(),
    latSpan = northEast.lat() - southWest.lat();

var markers = [];

for (var key in MARKER_SPRITE_POSITION) {

    var position = new naver.maps.LatLng(
        southWest.lat() + (latSpan * Math.random()),
        southWest.lng() + (lngSpan * Math.random())
    );

    var marker = new naver.maps.Marker({
        map: map,
        position: position,
        title: key,
        icon: {
            url: MARKER_ICON_URL,
            size: new naver.maps.Size(24, 37),
            anchor: new naver.maps.Point(12, 37),
            origin: new naver.maps.Point(MARKER_SPRITE_POSITION[key][0], MARKER_SPRITE_POSITION[key][1])
        },
        zIndex: 100
    });

    markers.push(marker);
};

naver.maps.Event.addListener(map, 'zoom_changed', function() {
    updateMarkers(map, markers);

});

naver.maps.Event.addListener(map, 'dragend', function() {
    updateMarkers(map, markers);
});

function updateMarkers(map, markers) {

    var mapBounds = map.getBounds();
    var marker, position;

    for (var i = 0; i < markers.length; i++) {

        marker = markers[i]
        position = marker.getPosition();

        if (mapBounds.hasLatLng(position)) {
            showMarker(map, marker);
        } else {
            hideMarker(map, marker);
        }
    }
}

function showMarker(map, marker) {

    if (marker.setMap()) return;
    marker.setMap(map);
}

function hideMarker(map, marker) {

    if (!marker.setMap()) return;
    marker.setMap(null);
}

Examples: 보이는 지도 영역의 마커만 표시하기

getDrawingRect 메서드를 활용하여 겹침 마커 구분하기

NAVER 지도 API v3에서 제공하는 오버레이 중 Marker, Rectangle, Circle, Polyline, PolygongetDrawingRect 메서드를 제공합니다. 이 메서드는 현재 지도 상태에서 오버레이가 그려지는 영역을 구합니다. 반환값은 PointBounds 객체이고 단위는 화면 픽셀을 기준으로 합니다.

앞서 주어진 영역에 특정 위치가 포함되는지 여부를 확인할 수 있는 LatLngBounds, PointBoundshasPoint 메서드를 설명했습니다. 이 절에서는 intersects 메서드를 설명합니다. LatLngBounds, PointBoundsintersects 메서드는 두 영역이 겹치는지 여부를 검사하는 메서드입니다. 이 메서드를 사용하면 두 개의 마커가 현재 겹쳐진 상태인지 그렇지 않은지를 쉽게 알 수 있습니다. getDrawingRect 메서드로 두 마커의 그리기 영역을 구하고 두 영역이 겹치는지 검사하면 됩니다. 다음은 이를 간단하게 구현한 코드입니다.

var marker1 = new naver.maps.Marker({...});
var marker2 = new naver.maps.Marker({...});

marker1.setMap(map);
marker2.setMap(map);

function intersects() {
    var marker1Rect = marker1.getDrawingRect();
    var marker2Rect = marker2.getDrawingRect();

    // 두 마커가 겹치는지 여부를 true 또는 false로 반환한다.
    return marker1Rect.intersects(marker2Rect);
}

다음 예제는 지도 위에 불특정 다수의 마커를 올리고 zoom_changed 이벤트 핸들러에서 각 마커의 겸침 상태를 확인해 화면에 표시하는 예제입니다. 이 예제는 위치가 변경되지 않는 마커이므로 줌 레벨이 변경되는 경우 겹침 여부를 검사합니다.

지도 초기화 후 현재 보이는 지도 영역의 임의의 위치에 마커를 올립니다. 줌 레벨이 변경되면 각 마커는 다른 마커와의 겹침 여부를 확인하고 인접한 마커들을 모으는 작업을 합니다. 겹쳐진 마커는 하이라이트 처리를 한 후 인접한 마커들의 그리기 영역을 합쳐 지도 좌표계 단위의 LatLngBounds 객체로 변환하고 Rectangle을 그려 겹쳐진 마커들을 눈으로 확인할 수 있도록 합니다.

var MARKER_ICON_URL = './img/sp_pins_spot_v3.png';
var MARKER_HIGHLIGHT_ICON_URL = './img/sp_pins_spot_v3_over.png';
var COLORS = ['#45ABD9', '#6154B6', '#E43736', '#44AE3F', '#F6D200', '#344554'];

var MARKER_SPRITE_X_OFFSET = 29;
var MARKER_SPRITE_Y_OFFSET = 50;

var MARKER_SPRITE_POSITION = {

    "A0": [0, 0],
    "B0": [MARKER_SPRITE_X_OFFSET, 0],
    "C0": [MARKER_SPRITE_X_OFFSET*2, 0],
    "D0": [MARKER_SPRITE_X_OFFSET*3, 0],
    "E0": [MARKER_SPRITE_X_OFFSET*4, 0],
    "F0": [MARKER_SPRITE_X_OFFSET*5, 0],
    "G0": [MARKER_SPRITE_X_OFFSET*6, 0],
    "H0": [MARKER_SPRITE_X_OFFSET*7, 0],
    "I0": [MARKER_SPRITE_X_OFFSET*8, 0],

    "A1": [0, MARKER_SPRITE_Y_OFFSET],
    "B1": [MARKER_SPRITE_X_OFFSET, MARKER_SPRITE_Y_OFFSET],
    "C1": [MARKER_SPRITE_X_OFFSET*2, MARKER_SPRITE_Y_OFFSET],
    "D1": [MARKER_SPRITE_X_OFFSET*3, MARKER_SPRITE_Y_OFFSET],
    "E1": [MARKER_SPRITE_X_OFFSET*4, MARKER_SPRITE_Y_OFFSET],
    "F1": [MARKER_SPRITE_X_OFFSET*5, MARKER_SPRITE_Y_OFFSET],
    "G1": [MARKER_SPRITE_X_OFFSET*6, MARKER_SPRITE_Y_OFFSET],
    "H1": [MARKER_SPRITE_X_OFFSET*7, MARKER_SPRITE_Y_OFFSET],
    "I1": [MARKER_SPRITE_X_OFFSET*8, MARKER_SPRITE_Y_OFFSET]

};

var map = new naver.maps.Map('map', {
    center: new naver.maps.LatLng(37.3595704, 127.105399),
    zoom: 10
});

var bounds = map.getBounds(),
    southWest = bounds.getSW(),
    northEast = bounds.getNE(),
    lngSpan = northEast.lng() - southWest.lng(),
    latSpan = northEast.lat() - southWest.lat();

var markers = [], markersMap = {};
var rectangles = [], colorIndex = 0;

for (var key in MARKER_SPRITE_POSITION) {

    var position = new naver.maps.LatLng(
        southWest.lat() + latSpan * Math.random(),
        southWest.lng() + lngSpan * Math.random());

    var marker = new naver.maps.Marker({
        map: map,
        position: position,
        title: key,
        icon: {
            url: MARKER_ICON_URL,
            size: new naver.maps.Size(24, 37),
            anchor: new naver.maps.Point(12, 37),
            origin: new naver.maps.Point(MARKER_SPRITE_POSITION[key][0], MARKER_SPRITE_POSITION[key][1])
        },
        zIndex: 100
    });

    markers.push(marker);
    markersMap[key] = marker;
};

naver.maps.Event.addListener(map, 'zoom_changed', function() {

    updateMarkersIntersectState();
});

function updateMarkersIntersectState() {

    var store = getIntersectMarkerStore(markers);
    var intersectMarkers = getMarkersByStore(store);
    var boundsList = getIntersectMarkerBounds(intersectMarkers);

    resetMarkers(markers);
    highlightMarkers(intersectMarkers);

    resetRectangles(rectangles);
    rectangles = drawRectangles(boundsList);
}

function resetRectangles(rectangles) {
    for (var i = 0; i < rectangles.length; i++) {
        rectangles[i].setMap(null);
    }
}

function drawRectangles(boundsList) {

    var rectangles = []

    for (var i = 0; i < boundsList.length; i++) {
        rectangles.push(new naver.maps.Rectangle({
            map: map,
            bounds: boundsList[i],
            strokeColor: COLORS[colorIndex % COLORS.length],
            strokeWeight: 2,
            strokeOpacity: 0.8,
            fillColor: COLORS[colorIndex % COLORS.length],
            fillOpacity: 0.4
        }));
        colorIndex++;
    }

    return rectangles;
}

function getIntersectMarkerStore(markers) {

    var store = [];
    var target, checked, targetBounds, checkedBounds;

    for (var i = 0; i < markers.length; i++) {

        target = markers[i];

        for (var j = 0; j < markers.length; j++) {

            checked = markers[j];

            if (target === checked) continue;

            targetBounds = target.getDrawingRect();
            checkedBounds = checked.getDrawingRect();

            if (targetBounds.intersects(checkedBounds)) {

                _inertToIntersectStore(store, target.getTitle(), checked.getTitle());
            }
        }
    }

    return store;
}

function getMarkersByStore(store) {

    var intersectMarkers = [];

    for (var i = 0; i < store.length; i++) {

        intersectMarkers[i] = [];

        for (var j = 0; j < store[i].length; j++) {
            intersectMarkers[i].push(markersMap[store[i][j]]);
        }
    }

    return intersectMarkers;
}

function getIntersectMarkerBounds(intersectMarkers) {

    var boundsList = [], bounds;

    for (var i = 0; i < intersectMarkers.length; i++) {

        for (var j = 0; j < intersectMarkers[i].length; j++) {

            if (j === 0) {
                bounds = intersectMarkers[i][j].getDrawingRect();
            } else {
                bounds = bounds.union(intersectMarkers[i][j].getDrawingRect());
            }
        }

        bounds = _pixelBoundsToLatLngBounds(map, bounds);
        boundsList.push(bounds);
    }

    return boundsList;
}

function resetMarkers(markers) {

    for (var i = 0; i < markers.length; i++) {
        var icon = markers[i].getIcon();
        icon.url = MARKER_ICON_URL;
        markers[i].setIcon(icon);
    }
}

function highlightMarkers(intersectMarkers) {

    for (var i = 0; i < intersectMarkers.length; i++) {

        for (var j = 0; j < intersectMarkers[i].length; j++) {

            var icon = intersectMarkers[i][j].getIcon();
            icon.url = MARKER_HIGHLIGHT_ICON_URL;
            intersectMarkers[i][j].setIcon(icon);
        }
    }
}

function _pixelBoundsToLatLngBounds(map, pixelBounds) {

    var zoom = map.getZoom();
    var proj = map.getProjection();

    var min = pixelBounds.getMin();
    var max = pixelBounds.getMax();

    min = proj.scaleDown(min, zoom);
    max = proj.scaleDown(max, zoom);

    min = proj.fromPointToCoord(min);
    max = proj.fromPointToCoord(max);

    return new naver.maps.LatLngBounds(min, max);
}

function _uniqueArray(a) {
    return a.reduce(function(p, c) {
        if (p.indexOf(c) < 0) p.push(c);
        return p;
    }, []);
}

function _inertToIntersectStore(store, value1, value2) {

    var index1 = _has(store, value1);
    var index2 = _has(store, value2);

    if (index1 !== -1 && index2 === -1) {
        index2 = index1;
    } else if (index1 === -1 && index2 !== -1) {
        index1 = index2;
    }

    if (index1 === -1 && index2 === -1) {
        store.push([value1, value2]);
    } else {
        store[index1].push(value1);
        store[index2].push(value2);

        if (index1 !== index2) {

            var low = Math.min(index1, index2);
            var high = Math.max(index1, index2);

            store[low] = store[low].concat(store[high]);
            store.splice(high, 1);
            store[low] = _uniqueArray(store[low]);
        } else {

            store[index1] = _uniqueArray(store[index1]);
        }
    }
}

function _has(store, value) {

    var array;

    for (var i = 0; i < store.length; i++) {

        array = store[i];

        if (array.indexOf(value) !== -1) {

            return i;
        }
    }

    return -1;
}

Examples: 겹침 마커 처리하기

기타 마커 구현 예제

위에서 설명한 내용 외에도 다양한 방식으로 마커를 활용할 수 있습니다. 다음의 마커 구현 예제를 참고하세요.

Examples: 마커 클러스터화하기

Examples: 다수의 마커에 이벤트 핸들러 사용하기