[네이버 지도] 마커 클러스터링
기타2021. 1. 14. 13:33
네이버 지도에서 마커를 표시할 때 지도가 축소되면 마커를 묶어서 표시
네이버에서는 마커를 묶어서 표시하기 위해 예제를 제공하고 있다.(아래 참고)
그 예제를 약간 수정하여 마커 클러스터링을 구현한 소스
준비물: jquery-3.5.1.min.js, MarkerClustering.js
[지도 html]
//-------------------------------------- // 지도 html //-------------------------------------- <html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>마커 클러스터링</title> <script type="text/javascript" src="jquery-3.5.1.min.js"></script> <script type="text/javascript" src="marker-clustering-custom.js"></script> <style type="text/css"> body { font-size: 12px; font-family: dotum; } input { height: 22px; background-color: #efefef; border: 1px solid #333333; border-radius: 4px; } .div_map { display:flex; flex-direction: row; } .map { width: 1000px; height: 600px; border: 1px solid #c0c0c0; border-radius: 4px; } .div_list { height: 600px; } .list_title { height: 30px; line-height: 30px; background-color: #efefef; border: 1px solid #c0c0c0; border-radius: 4px; margin: 0 0 5px 5px; font-weight: bold; padding-left: 10px; } .list { width: 320px; height: 563px; border: 1px solid #c0c0c0; border-radius: 4px; margin-left: 5px; overflow-y: auto; } .list > ol { line-height: 1.7em; } .ctl { display: flex; flex-direction: row; justify-content: space-between; width:1000px; margin-top: 5px; } .pos { display: flex; flex-direction: row; justify-content: space-between; } .ctl input { text-align: center; } .pos button { height: 26px; border: 1px solid #333333; border-radius: 4px; } .pos > div:nth-child(2) { margin-left: 6px; } </style> </head> <body> <div> <div class="div_map"> <div id="map" class="map"> </div> <div class="div_list"> <div class="list_title">마커 목록</div> <div class="list"> </div> </div> </div> <div class="ctl"> <div><input id="info" readonly="readonly" size="40" type="text" value="" /></div> <div class="pos"> <div> <input id="lat" readonly="readonly" size="20" type="text" value="" /> <input id="lng" readonly="readonly" size="20" type="text" value="" /> </div> <div> <button type="button">좌표 복사</button> <button type="button">원위치</button> </div> </div> </div> </div> </body> </html>
[지도 및 이벤트 설정]
//----------------------------------------------- // 지도 및 이벤트 설정 //----------------------------------------------- var _BASE = new naver.maps.LatLng(37.566605, 126.9783931); // 서울시청 var _ZOOM = 16; // 지도 표시 레벨 var _list = []; // 현재 화면에 표시된 마커 목록 var marker1 = null; // 좌표 조회용 마커 // 지도 옵션 var mapOptions = { center: _BASE, zoom: _ZOOM, zoomControl: true, // 확대,축소 zoomControlOptions: { position : naver.maps.Position.TOP_RIGHT }, //logoControlOptions: { position : naver.maps.Position.BOTTOM_RIGHT }, // 네이버로고 scaleControl: true, // 축척 scaleControlOptions: { position : naver.maps.Position.BOTTOM_RIGHT }, mapDataControl: false, // 저작권 }; // 지도 표시 var map = new naver.maps.Map('map', mapOptions); // 지도 이벤트 설정 naver.maps.Event.addListener(map, 'zoom_changed', function(e) { if (e > 15) { marker1.setMap(map); // 마커1 표시 } else { marker1.setMap(null); // 마커1 삭제 } }); // 마커 목록 삭제 function clearList() { $('#list').empty(); } // 마커 목록 표시 function showList() { _list.forEach(function(item) { showMarkerInfo(item); }); _list = []; } // 현재 위치 표시 showPosition(_BASE); // 위도 경도 표시 function showPosition(latlng) { $('#lat').val(latlng.lat()); $('#lng').val(latlng.lng()); } // 원위치 버튼 클릭 function resetPosition() { map.setCenter(_BASE); map.setZoom(_ZOOM); if (marker1) { marker1.setPosition(_BASE); } showPosition(_BASE); $('#info').val(null); } // 좌표를 클립보드에 복사 function copyPosition() { var info = $('#info'); info.val($('#lat').val() +', '+ $('#lng').val()); info.select(); document.execCommand('copy'); } // 주소 변경 function changeAddr(code) { if (code == '') { map.setCenter(_BASE); } else { showArea(code); } } // 마커1(기본 마커) marker1 = new naver.maps.Marker({ position: _BASE, map: map, zIndex: 200, draggable: true, // 마커1 드래그 허용 }); // 마커1 드래그 허용 //marker1.setDraggable(true); // 마커1 드래그 이벤트 설정 naver.maps.Event.addListener(marker1, 'drag', function(e) { showPosition(e.coord); });
[마커 데이터]
//----------------------------------------------- // 마커 데이터 //----------------------------------------------- var options = [ { pos: [ 37.5675575, 126.9778996 ], info: "code: '1001', name: '1001 사무실'", type: '사무실' }, { pos: [ 37.5674628, 126.9777064 ], info: "code: '1002', name: '1002 사무실'", type: '사무실' }, { pos: [ 37.56749, 126.9779417 ], info: "code: '1003', name: '1003 사무실'", type: '사무실' }, { pos: [ 37.5673746, 126.9778567 ], info: "code: '1004', name: '1004 사무실'", type: '사무실' }, { pos: [ 37.5671748, 126.9780283 ], info: "code: '1005', name: '1005 사무실'", type: '사무실' }, { pos: [ 37.5679912, 126.9779639 ], info: "code: '1006', name: '1006 사무실'", type: '사무실' }, { pos: [ 37.567515, 126.9781677 ], info: "code: '1007', name: '1007 사무실'", type: '사무실' }, { pos: [ 37.5696834, 126.980453 ], info: "code: '1011', name: '1011 사무실'", type: '사무실' }, { pos: [ 37.5698195, 126.9811396 ], info: "code: '1012', name: '1012 사무실'", type: '사무실' }, { pos: [ 37.5693093, 126.9813327 ], info: "code: '1013', name: '1013 사무실'", type: '사무실' }, { pos: [ 37.5698535, 126.9739299 ], info: "code: '1021', name: '1021 공실'", type: '공실' }, { pos: [ 37.56976, 126.9731896 ], info: "code: '1022', name: '1022 공실'", type: '공실' }, { pos: [ 37.5683143, 126.9765907 ], info: "code: '1031', name: '1031 호텔'", type: '호텔' }, { pos: [ 37.5685609, 126.9760328 ], info: "code: '1032', name: '1032 호텔'", type: '호텔' }, { pos: [ 37.5686204, 126.9764405 ], info: "code: '1033', name: '1033 호텔'", type: '호텔' }, { pos: [ 37.5680676, 126.9758611 ], info: "code: '1034', name: '1034 호텔'", type: '호텔' }, { pos: [ 37.5679315, 126.9765048 ], info: "code: '1035', name: '1035 호텔'", type: '호텔' }, { pos: [ 37.5670727, 126.9817405 ], info: "code: '1041', name: '1041 맛집'", type: '맛집' }, { pos: [ 37.5677275, 126.9823842 ], info: "code: '1042', name: '1042 맛집'", type: '맛집' }, { pos: [ 37.5678721, 126.9819122 ], info: "code: '1043', name: '1043 맛집'", type: '맛집' }, { pos: [ 37.5643939, 126.9755392 ], info: "code: '1051', name: '1051 공공기관'", type: '공공기관' }, { pos: [ 37.5643939, 126.9750028 ], info: "code: '1052', name: '1052 공공기관'", type: '공공기관' }, { pos: [ 37.5649382, 126.9818692 ], info: "code: '1061', name: '1061 쇼핑'", type: '쇼핑' }, { pos: [ 37.5647001, 126.9830279 ], info: "code: '1062', name: '1062 쇼핑'", type: '쇼핑' }, { pos: [ 37.5647001, 126.9838862 ], info: "code: '1063', name: '1063 쇼핑'", type: '쇼핑' }, { pos: [ 37.5651763, 126.9813972 ], info: "code: '1064', name: '1064 쇼핑'", type: '쇼핑' }, { pos: [ 37.5651678, 126.983146 ], info: "code: '1065', name: '1065 쇼핑'", type: '쇼핑' }, { pos: [ 37.5653294, 126.9837575 ], info: "code: '1066', name: '1066 쇼핑'", type: '쇼핑' }, { pos: [ 37.5657886, 126.9713335 ], info: "code: '1071', name: '1071 학교'", type: '학교' }, { pos: [ 37.5661118, 126.9706469 ], info: "code: '1072', name: '1072 학교'", type: '학교' }, { pos: [ 37.5656355, 126.9718056 ], info: "code: '1073', name: '1073 학교'", type: '학교' }, { pos: [ 37.5694623, 126.9856243 ], info: "code: '1081', name: '1081 은행'", type: '은행' }, { pos: [ 37.5693178, 126.9850128 ], info: "code: '1082', name: '1082 은행'", type: '은행' }, { pos: [ 37.5695133, 126.9862465 ], info: "code: '1083', name: '1083 은행'", type: '은행' }, { pos: [ 37.568782, 126.9859676 ], info: "code: '1084', name: '1084 은행'", type: '은행' }, ];
[마커 등록 및 이벤트 설정]
//----------------------------------------------- // 마커 등록 및 이벤트 설정 //----------------------------------------------- var markers = []; for (var i = 0, ii = options.length; i < ii; i++) { // info, text 속성을 넣기 위해 HTML 마커 사용 markers.push(new naver.maps.Marker({ position: new naver.maps.LatLng(options[i].pos[0], options[i].pos[1]), zIndex: 100, icon: { content: '', anchor: new naver.maps.Point(11, 33), } })); } // 마커 클릭 이벤트 설정 for (var i = 0, ii = markers.length; i < ii; i++) { naver.maps.Event.addListener(markers[i], 'click', getClickHandler(i)); } // 마커 클릭 이벤트 function getClickHandler(seq) { return function(e) { clearList(); showMarkerInfo(markers[seq]); } } // html 속성값을 json 변환 function attr2json(attr) { var json = {}; var s = attr.trim().split(','); for (var i = 0, ii = s.length; i < ii; i++) { var a = s[i].split(':'); json[a[0].trim()] = a[1].trim().replace(/''/g,''); } return json; }
[클러스터 마커 생성, 변경]
//----------------------------------------------- // 클러스터 마커 생성, 변경 //----------------------------------------------- // 클러스터 마커 생성(HTML 마커) function newMarker(radius, count, name) { var cvs = document.createElement('canvas'); return changeMarker(cvs, radius, count, name); } // 클러스터 마커 변경(HTML 마커) function changeMarker(cvs, radius, count, name) { cvs.width = radius * 2; cvs.height = radius * 2; var ctx = cvs.getContext('2d'); // 원 표시 ctx.beginPath(); ctx.arc(radius, radius, radius, 0, Math.PI * 2); ctx.fillStyle = 'rgba(3, 133, 255, 0.5)'; //#0385ff //ctx.fillStyle = hex2rgba(fillColor, fillOpacity); ctx.fill(); ctx.closePath(); // 텍스트 표시 ctx.textAlign = 'center'; ctx.fillStyle = 'white'; // text color if (name != null) { ctx.font = '16px dotum bold'; ctx.fillText(name, radius, radius - 4); y = radius + 20 - 4; } else { ctx.textBaseline = 'middle'; // 텍스트가 1줄일 경우 사용 y = radius; } if (count != null) { ctx.font = '20px dotum bold'; ctx.fillText(count, radius, y); } return { content: cvs, anchor: naver.maps.Point(radius, radius), } }
[클러스터 마커 설정]
//----------------------------------------------- // 클러스터 마커 설정 //----------------------------------------------- var cMarker1 = newMarker(50, 'm1', 0); var cMarker2 = newMarker(50, 'm2', 0); var cMarker3 = newMarker(50, 'm3', 0); // 마커 클러스터링 var markerClustering = new MarkerClustering({ minClusterSize: 2, // 클러스터 마커를 표시할 최소 마커 개수 maxZoom: 17, // 최대 지도 확대 레벨(maxZoom > map zoom, 클러스터 마커 표시) map: map, // 클러스터 마커 표시할 지도 markers: markers, // 클러스터 마커에서 사용할 마커 목록 disableClickZoom: true, // 클러스터 마커 클릭 시 지도 확대여부 //averageCenter: true, // 마커들의 중간 좌표를 계산하여 클러스터 마커 표시여부 gridSize: 100, // 클러스터 마커 그리드 크기(단위: 픽셀) icons: [cMarker1, cMarker2, cMarker3], // 클러스터 마커용 아이콘 indexGenerator: [10, 50, 100], // 아이콘 표시용 마커 개수 설정 stylingFunction: function(clusterMarker, count, name) { // 클러스터 마커 갱신 시 호출 _list = _list.concat(members); var radius = getRadius(count); // 클러스터 마커 표시 clusterMarker.setIcon(newMarker(radius, count, name)); // 클러스터 마커 이벤트 설정 naver.maps.Event.addListener(clusterMarker, 'mouseover', function(e) { var cvs = clusterMarker.getIcon().content; clusterMarker.setIcon(changeMarker(cvs, radius + 15, count, name)); }); naver.maps.Event.addListener(clusterMarker, 'mouseout', function(e) { var cvs = clusterMarker.getIcon().content; clusterMarker.setIcon(changeMarker(cvs, radius, count, name)); }); naver.maps.Event.addListener(clusterMarker, 'click', function(e) { clearList(); members.forEach(function(item) { showMarkerInfo(item); }); }); } }); // 마커 개수에 따라 반지름 변경 function getRadius(c) { var r = 50; switch (true) { case (c < 3): r = 30; break; case (c < 5): r = 40; break; case (c < 10): r = 50; break; case (c < 20): r = 60; break; case (c < 50): r = 70; break; case (c <100): r = 80; break; } return r; } // 마커 info 속성 표시 function showMarkerInfo(marker) { var info = $(marker.getIcon().content).attr('info'); var json = attr2json(info); $('#list').append('마커 : code = '+ json.code +', name = '+ json.name +''); }
[MarkerClustering.js 수정 부분]
//----------------------------------------------- // MarkerClustering.js 수정 부분 //----------------------------------------------- /** * 클러스터의 아이콘, 텍스트를 갱신합니다. * @private */ _updateClusters: function() { var clusters = this._clusters; clearList(); //추가 for (var i = 0, ii = clusters.length; i < ii; i++) { clusters[i].updateCluster(); } showList(); //추가 }, /** * 클러스터를 구성하는 마커 수를 갱신합니다. */ updateCount: function() { var stylingFunction = this._markerClusterer.getStylingFunction(); //추가 var name = null; if (this._clusterMember.length > 0) { var cZoom = this._markerClusterer.getMaxZoom(); var mZoom = this._markerClusterer.getMap().getZoom(); //지도의 줌 레벨이 클러스터 마커 표시 최대 레벨일 때만 명칭 표시 if (cZoom - 1 == mZoom) { //첫번째 HTML 마커의 text 속성 조회 //문제점: 마커의 명칭이 여러 종류일 경우 명칭 표기에 혼선 name = $(this._clusterMember[0].getIcon().content).attr('text'); } } //추가 stylingFunction && stylingFunction(this._clusterMarker, this.getCount(), name, this._clusterMember); //추가 },
[참고]
'기타' 카테고리의 다른 글
[CSS] 버튼 클릭 시 테두리 및 효과 삭제 (0) | 2021.04.13 |
---|---|
[네이버 지도] 마커 표시 변경 (0) | 2021.01.29 |
[네이버 지도] 사용자 정의 오버레이 (0) | 2021.01.13 |
[네이버 지도] 행정구역 표시(shp -> geojson) (0) | 2021.01.07 |
티스토리 SyntaxHighlighter 3.0.8.3 사용법 (0) | 2018.10.25 |