[네이버 지도] 마커 클러스터링
네이버 지도에서 마커를 표시할 때 지도가 축소되면 마커를 묶어서 표시
네이버에서는 마커를 묶어서 표시하기 위해 예제를 제공하고 있다.(아래 참고)
그 예제를 약간 수정하여 마커 클러스터링을 구현한 소스
준비물: jquery-3.5.1.min.js, MarkerClustering.js
[지도 html]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | //-------------------------------------- // 지도 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 > |
[지도 및 이벤트 설정]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | //----------------------------------------------- // 지도 및 이벤트 설정 //----------------------------------------------- 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); }); |
[마커 데이터]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | //----------------------------------------------- // 마커 데이터 //----------------------------------------------- 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: '은행' }, ]; |
[마커 등록 및 이벤트 설정]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | //----------------------------------------------- // 마커 등록 및 이벤트 설정 //----------------------------------------------- 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; } |
[클러스터 마커 생성, 변경]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | //----------------------------------------------- // 클러스터 마커 생성, 변경 //----------------------------------------------- // 클러스터 마커 생성(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), } } |
[클러스터 마커 설정]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | //----------------------------------------------- // 클러스터 마커 설정 //----------------------------------------------- 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 수정 부분]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | //----------------------------------------------- // 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 |
[네이버 지도] 사용자 정의 오버레이
네이버 지도 마커에 원 도형과 텍스트를 함께 표시하기 위해 사용자 정의 오버레이를 사용해보자.
[사용자 정의 오버레이 설정]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //-------------------------------------- // 사용자 정의 오버레이 설정 //-------------------------------------- // 사용자 정의 오버레이 var CustomOverlay = function (options) { this ._element = $( '' ); this ._code = options.code || null ; this ._position = options.position; this ._radius = options.radius || 50; this ._fillColor = options.fillColor || '#0385ff' ; this ._fillOpacity = options.fillOpacity || 0.6; this ._strokeColor = options.strokeColor || '#0385ff' ; this ._strokeOpacity = options.strokeOpacity || 0.6; this ._strokeWeight = 1; this ._textColor = options.textColor || 'white' ; }; // CustomOverlay는 OverlayView를 상속받습니다. CustomOverlay.prototype = new naver.maps.OverlayView(); CustomOverlay.prototype.constructor = CustomOverlay; |
[필수 메소드 재정의] onAdd(), onRemove(), draw()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | //-------------------------------------- // 필수 메소드 재정의 //-------------------------------------- CustomOverlay.prototype.onAdd = function () { // 오버레이 표시 레이어(floatPane(정보 레이어, 상단)/overlayImage(마커 레이어, 중단)/overlayLayer(도형 레이어, 하단) var overlayLayer = this .getPanes().floatPane; this ._element.appendTo(overlayLayer); }; CustomOverlay.prototype.draw = function () { // 지도 객체가 설정되지 않았으면 draw 기능을 하지 않습니다. if (! this .getMap()) { return ; } // projection 객체를 통해 LatLng 좌표를 화면 좌표로 변경합니다. var projection = this .getProjection(); var position = this .getPosition(); var pixelPosition = projection.fromCoordToOffset(position); var radius = this ._radius; this ._element.css( 'left' , pixelPosition.x - radius); this ._element.css( 'top' , pixelPosition.y - radius); var cvs = this ._element[0]; cvs.width = radius * 2; cvs.height = radius * 2; var ctx = cvs.getContext( '2d' ); // 원 그리기 ctx.beginPath(); ctx.arc(radius, radius, radius - this ._strokeWeight, 0, Math.PI * 2); // 반지름 = radius - this._strokeWeight ctx.fillStyle = hex2rgba( this ._fillColor, this ._fillOpacity); //rgba(3, 133, 255, 0.5) #0385ff ctx.fill(); ctx.lineWidth = this ._strokeWeight; // 기본값: lineWidth = 1 ctx.strokeStyle = hex2rgba( this ._strokeColor, this ._strokeOpacity); //기본값: strokeStyle = '#000000' ctx.stroke(); ctx.closePath(); // 원 내부에 텍스트 표시 ctx.textAlign = 'center' ; //ctx.textBaseline = 'middle'; // 텍스트가 1줄일 경우 사용 ctx.fillStyle = hex2rgba( this ._textColor, null ); // text color ctx.font = '14px dotum bold' ; ctx.fillText( this ._code.name, radius, radius - 4); // 글꼴의 크기에 따라 세로 출력 위치 조정 ctx.font = '20px dotum bold' ; ctx.fillText( this ._code.count, radius, radius + 20 - 4); // 글꼴의 크기에 따라 세로 출력 위치 조정 }; CustomOverlay.prototype.onRemove = function () { this ._element.remove(); // 이벤트 핸들러를 설정했다면 정리합니다. this ._element.off(); }; |
[속성 메소드 정의]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | //-------------------------------------- // 속성 메소드 정의 //-------------------------------------- CustomOverlay.prototype.getCode = function () { return this ._code; }; CustomOverlay.prototype.setCode = function (code) { this ._code = code; }; CustomOverlay.prototype.getPosition = function () { return this ._position; }; CustomOverlay.prototype.setPosition = function (position) { this ._position = position; }; CustomOverlay.prototype.getRadius = function () { return this ._radius; }; CustomOverlay.prototype.setRadius = function (radius) { this ._radius = radius; }; CustomOverlay.prototype.getFillColor = function () { return this ._fillColor; }; CustomOverlay.prototype.setFillColor = function (color) { this ._fillColor = color; }; |
[hex to rgb, colorname to hex]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | //-------------------------------------- // hex to rgb, colorname to hex //-------------------------------------- // 색깔코드를 rgba로 변경(color, opacity) function hex2rgba(c, o) { if (c.charAt(0) == '#' ) { c = c.substr(1); } else { return colorName2Hex(c); //색깔명 사용할 경우 opacity 무시 } if (o == null || o > 1.0 || o < 0) o = 1.0; if (c.length == 3) { c = c.substr(0,1) + c.substr(0,1) + c.substr(1,2) + c.substr(1,2) + c.substr(2,3) + c.substr(2,3); } var r = c.charAt(0) + '' + c.charAt(1); r = parseInt(r, 16); var g = c.charAt(2) + '' + c.charAt(3); g = parseInt(g, 16); var b = c.charAt(4) + '' + c.charAt(5); b = parseInt(b, 16); return 'rgba(' + r + ',' + g + ',' + b + ',' + o + ')' ; } // 색깔명을 색깔코드로 변경 function colorName2Hex(color) { var colors = { "aliceblue" : "#f0f8ff" , "antiquewhite" : "#faebd7" , "aqua" : "#00ffff" , "aquamarine" : "#7fffd4" , "azure" : "#f0ffff" , "beige" : "#f5f5dc" , "bisque" : "#ffe4c4" , "black" : "#000000" , "blanchedalmond" : "#ffebcd" , "blue" : "#0000ff" , "blueviolet" : "#8a2be2" , "brown" : "#a52a2a" , "burlywood" : "#deb887" , "cadetblue" : "#5f9ea0" , "chartreuse" : "#7fff00" , "chocolate" : "#d2691e" , "coral" : "#ff7f50" , "cornflowerblue" : "#6495ed" , "cornsilk" : "#fff8dc" , "crimson" : "#dc143c" , "cyan" : "#00ffff" , "darkblue" : "#00008b" , "darkcyan" : "#008b8b" , "darkgoldenrod" : "#b8860b" , "darkgray" : "#a9a9a9" , "darkgreen" : "#006400" , "darkkhaki" : "#bdb76b" , "darkmagenta" : "#8b008b" , "darkolivegreen" : "#556b2f" , "darkorange" : "#ff8c00" , "darkorchid" : "#9932cc" , "darkred" : "#8b0000" , "darksalmon" : "#e9967a" , "darkseagreen" : "#8fbc8f" , "darkslateblue" : "#483d8b" , "darkslategray" : "#2f4f4f" , "darkturquoise" : "#00ced1" , "darkviolet" : "#9400d3" , "deeppink" : "#ff1493" , "deepskyblue" : "#00bfff" , "dimgray" : "#696969" , "dodgerblue" : "#1e90ff" , "firebrick" : "#b22222" , "floralwhite" : "#fffaf0" , "forestgreen" : "#228b22" , "fuchsia" : "#ff00ff" , "gainsboro" : "#dcdcdc" , "ghostwhite" : "#f8f8ff" , "gold" : "#ffd700" , "goldenrod" : "#daa520" , "gray" : "#808080" , "green" : "#008000" , "greenyellow" : "#adff2f" , "honeydew" : "#f0fff0" , "hotpink" : "#ff69b4" , "indianred " : "#cd5c5c" , "indigo" : "#4b0082" , "ivory" : "#fffff0" , "khaki" : "#f0e68c" , "lavender" : "#e6e6fa" , "lavenderblush" : "#fff0f5" , "lawngreen" : "#7cfc00" , "lemonchiffon" : "#fffacd" , "lightblue" : "#add8e6" , "lightcoral" : "#f08080" , "lightcyan" : "#e0ffff" , "lightgoldenrodyellow" : "#fafad2" , "lightgrey" : "#d3d3d3" , "lightgreen" : "#90ee90" , "lightpink" : "#ffb6c1" , "lightsalmon" : "#ffa07a" , "lightseagreen" : "#20b2aa" , "lightskyblue" : "#87cefa" , "lightslategray" : "#778899" , "lightsteelblue" : "#b0c4de" , "lightyellow" : "#ffffe0" , "lime" : "#00ff00" , "limegreen" : "#32cd32" , "linen" : "#faf0e6" , "magenta" : "#ff00ff" , "maroon" : "#800000" , "mediumaquamarine" : "#66cdaa" , "mediumblue" : "#0000cd" , "mediumorchid" : "#ba55d3" , "mediumpurple" : "#9370d8" , "mediumseagreen" : "#3cb371" , "mediumslateblue" : "#7b68ee" , "mediumspringgreen" : "#00fa9a" , "mediumturquoise" : "#48d1cc" , "mediumvioletred" : "#c71585" , "midnightblue" : "#191970" , "mintcream" : "#f5fffa" , "mistyrose" : "#ffe4e1" , "moccasin" : "#ffe4b5" , "navajowhite" : "#ffdead" , "navy" : "#000080" , "oldlace" : "#fdf5e6" , "olive" : "#808000" , "olivedrab" : "#6b8e23" , "orange" : "#ffa500" , "orangered" : "#ff4500" , "orchid" : "#da70d6" , "palegoldenrod" : "#eee8aa" , "palegreen" : "#98fb98" , "paleturquoise" : "#afeeee" , "palevioletred" : "#d87093" , "papayawhip" : "#ffefd5" , "peachpuff" : "#ffdab9" , "peru" : "#cd853f" , "pink" : "#ffc0cb" , "plum" : "#dda0dd" , "powderblue" : "#b0e0e6" , "purple" : "#800080" , "rebeccapurple" : "#663399" , "red" : "#ff0000" , "rosybrown" : "#bc8f8f" , "royalblue" : "#4169e1" , "saddlebrown" : "#8b4513" , "salmon" : "#fa8072" , "sandybrown" : "#f4a460" , "seagreen" : "#2e8b57" , "seashell" : "#fff5ee" , "sienna" : "#a0522d" , "silver" : "#c0c0c0" , "skyblue" : "#87ceeb" , "slateblue" : "#6a5acd" , "slategray" : "#708090" , "snow" : "#fffafa" , "springgreen" : "#00ff7f" , "steelblue" : "#4682b4" , "tan" : "#d2b48c" , "teal" : "#008080" , "thistle" : "#d8bfd8" , "tomato" : "#ff6347" , "turquoise" : "#40e0d0" , "violet" : "#ee82ee" , "wheat" : "#f5deb3" , "white" : "#ffffff" , "whitesmoke" : "#f5f5f5" , "yellow" : "#ffff00" , "yellowgreen" : "#9acd32" }; if ( typeof colors[color.toLowerCase()] != 'undefined' ) return colors[color.toLowerCase()]; return false ; } |
[오버레이 표시 및 이벤트 설정]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | //-------------------------------------- // 오버레이 표시 및 이벤트 설정 //-------------------------------------- var options = [ { position: TEST1, radius: 50, code: { name: '숙소' , count: '5' , city: '11' , sig: '11290' , emd: '11200104' , text: '테스트1' } }, { position: TEST2, radius: 50, code: { name: '사무실' , count: '99' , city: '11' , sig: '11290' , emd: '11200104' , text: '테스트2' } }, { position: TEST3, radius: 50, code: { name: '맛집' , count: '3' , city: '11' , sig: '11290' , emd: '11200104' , text: '테스트3' } }, ]; var markers = []; // 오버레이 생성 for ( var idx in options) { markers.push( new CustomOverlay(options[idx])); } // 오버레이 표시, 이벤트 설정 for ( var idx in markers) { markers[idx].setMap(map); markers[idx]._element.on( 'mouseover' , changeRadius(idx, 100)); markers[idx]._element.on( 'mouseout' , changeRadius(idx, 50)); markers[idx]._element.on( 'click' , clickMarker(idx)); } function changeRadius(idx, r) { return function (e) { var marker = markers[idx]; marker.setRadius(r); marker.draw(); } } function clickMarker(idx) { return function (e) { console.log(markers[idx].getCode()); } } |
[참고]
https://navermaps.github.io/maps.js.ncp/docs/naver.maps.OverlayView.html
https://navermaps.github.io/maps.js.ncp/docs/tutorial-6-CustomOverlay.html
https://navermaps.github.io/maps.js.ncp/docs/tutorial-custom-overlay.example.html
'기타' 카테고리의 다른 글
[네이버 지도] 마커 표시 변경 (0) | 2021.01.29 |
---|---|
[네이버 지도] 마커 클러스터링 (0) | 2021.01.14 |
[네이버 지도] 행정구역 표시(shp -> geojson) (0) | 2021.01.07 |
티스토리 SyntaxHighlighter 3.0.8.3 사용법 (0) | 2018.10.25 |
택배 조회 URL (0) | 2018.09.18 |
[네이버 지도] 행정구역 표시(shp -> geojson)
준비물: 시도 shp, 시군구 shp, 읍면동 shp
프로그램: mapshaper, QGIS, python 3.9.1
국내 행정구역 shp 파일은 용량도 크고 너무 세밀하게 표현된 부분이 많아서
웹에 단순한 행정구역을 표시할 때 용량과 속도 등에서 제약사항이 발생한다.
국내 행정구역 shp 파일에서 사용된 좌표계는 GRS80 (EPSG: 5179) 이므로
네이버 지도에서 사용하는 위도와 경도 좌표계 WGS84 (EPSG: 4326)로 변환해야 한다.
변환 과정 요약
1. mapshaper 등의 프로그램으로 simplify 과정을 거쳐 단순화(shp ▶ shp)
2. QGIS 등의 프로그램을 이용하여 WGS 84 좌표로 변환(shp ▶ json(geojson))
3. python 등을 이용해 전체 geojson 파일에서 필요한 행정구역 파일로 분할 저장(json ▶ json(geojson))
[시군구 geojson 분할 저장] python 3.9.1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | / / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - / / 시군구 geojson 분할 저장 / / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #python 3.9.1 import json #json 파일 읽기 with open ( 'd:\\sig_202005_wgs84.geojson' , 'r' , encoding = 'UTF8' ) as rf: geojson = json.load(rf) #시군구 반복 for item in geojson[ 'features' ]: code = item[ 'properties' ][ 'SIG_CD' ] #행정구역코드 name = item[ 'properties' ][ 'SIG_KOR_NM' ] #행정구역명 item[ 'id' ] = code; #네이버 지도용 id 설정 #json 파일 저장 경로 path = 'd:\\sig\\'+ code +' .json' print (path + ' ' + name) #json 객체 생성 geo = dict () geo[ 'type' ] = 'FeatureCollection' geo[ 'features' ] = [] #features는 배열 geo[ 'features' ].append(item) #json 파일 저장 with open (path, 'w' , encoding = 'UTF8' ) as wf: json.dump(geo, wf, ensure_ascii = False ) #한글 유니코드 저장 방지 |
[행정구역 json 파일 로드]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | //-------------------------------------- // 행정구역 json 파일 로드 //-------------------------------------- var HOME_PATH = window.HOME_PATH || '.' , urlPrefix = HOME_PATH + '/data/region' , urlSuffix = '.json' , regionGeoJson = [], loadCount = 0; // 행정구역 json 파일 로드 naver.maps.Event.once(map, 'init_stylemap' , function () { for ( var i = 1; i < 18; i++) { var keyword = i + '' ; if (keyword.length === 1) { keyword = '0' + keyword; } $.ajax({ url: urlPrefix + keyword + urlSuffix, success: function (idx) { return function (geojson) { regionGeoJson[idx] = geojson; loadCount++; if (loadCount === 17) { startDataLayer(); } } }(i - 1) }); } }); |
[Geojson 파일 형태]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | //-------------------------------------- // Geojson 파일 형태 //-------------------------------------- // 툴팁 설정 var tooltip = $( '<div style="position: absolute; z-index: 1000; padding: 5px 10px; background-color: #fff; border: solid 2px #000; font-size: 14px; pointer-events: none; display: none;"> </div>' ); tooltip.appendTo(map.getPanes().floatPane); // 로드된 행정구역은 아래와 같은 형태 regionGeoJson.push({ "type" : "FeatureCollection" , "features" : [{ "id" : "11140" , "type" : "Feature" , "properties" : { "SIG_CD" : "11140" , "SIG_ENG_NM" : "Jung-gu" , "SIG_KOR_NM" : "중구" }, "geometry" : { "type" : "MultiPolygon" , "coordinates" : [[[ [127.0233653, 37.5719143], [127.0233917, 37.5719073], [127.0235305, 37.5671585], [127.0235359, 37.5669135], [127.0235319, 37.5668462], [127.0235326, 37.5668336], [127.0217938, 37.5575103], [127.0217328, 37.5575278], [127.0195773, 37.5574955], [127.0196807, 37.5572629], [127.0180939, 37.5568746], [127.0178901, 37.5567028], [127.0162301, 37.5528015], [127.0159829, 37.552589], [127.015938, 37.5525498], [127.015858, 37.552471], [127.0089881, 37.5441349], [127.0050173, 37.5461716], [127.0061278, 37.5481932], [127.0046885, 37.5486851], [127.0045604, 37.5498499], [127.0043733, 37.5501537], [127.0043557, 37.5501974], [127.0043362, 37.5502112], [127.0042094, 37.5501507], [127.0034213, 37.5500195], [127.0028642, 37.5496416], [127.0016841, 37.5500597], [126.9983251, 37.5496954], [126.9944593, 37.547488], [126.9787954, 37.5540872], [126.9724082, 37.5548773], [126.96571, 37.5541518], [126.963376, 37.552259], [126.962778, 37.551848], [126.9626113, 37.5517331], [126.9623428, 37.5515489], [126.9623691, 37.5516032], [126.962376, 37.5516286], [126.9624048, 37.5516477], [126.9623107, 37.55277], [126.9620066, 37.5540349], [126.9619314, 37.554448], [126.9617737, 37.5547951], [126.9617773, 37.554982], [126.9619781, 37.5551268], [126.9621418, 37.5555309], [126.9624769, 37.5559106], [126.9627196, 37.5559726], [126.9631727, 37.5565488], [126.9633575, 37.5566854], [126.9634369, 37.5568535], [126.9634273, 37.5569479], [126.9634093, 37.5569943], [126.963052, 37.5574148], [126.9630411, 37.5580298], [126.9620761, 37.5584566], [126.9615951, 37.5584184], [126.9617358, 37.5588129], [126.961711, 37.5589526], [126.9616892, 37.5589824], [126.9616726, 37.5590258], [126.962956, 37.5591763], [126.9632096, 37.5591958], [126.9633065, 37.5592155], [126.9668376, 37.561274], [126.9674439, 37.5615089], [126.9681526, 37.5616215], [126.969265, 37.5619273], [126.9694319, 37.5619679], [126.9694641, 37.5619758], [126.9693301, 37.5622305], [126.9689586, 37.5629149], [126.967562, 37.5648787], [126.9666982, 37.56582], [126.9668651, 37.5659861], [126.9668874, 37.5660078], [126.9966167, 37.5686865], [126.9968368, 37.5687099], [126.997025, 37.5687315], [127.0007881, 37.5693806], [127.0013078, 37.5694769], [127.014613, 37.5697183], [127.0147018, 37.5697206], [127.0176066, 37.5702101], [127.0177723, 37.5702912], [127.0233653, 37.5719143] ]]] } }] }); |
[행정구역 표시]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | //-------------------------------------- // 행정구역 표시 //-------------------------------------- function startDataLayer() { map.data.setStyle( function (feature) { var styleOptions = { fillColor: '#ff0000' , fillOpacity: 0.0001, strokeColor: '#ff0000' , strokeWeight: 4, strokeOpacity: 0.4 }; if (feature.getProperty( 'focus' )) { styleOptions.fillOpacity = 0.6; styleOptions.fillColor = '#0f0' ; styleOptions.strokeColor = '#0f0' ; styleOptions.strokeWeight = 4; styleOptions.strokeOpacity = 1; } return styleOptions; }); regionGeoJson.forEach( function (geojson) { map.data.addGeoJson(geojson); }); map.data.addListener( 'click' , function (e) { var feature = e.feature; if (feature.getProperty( 'focus' ) !== true ) { feature.setProperty( 'focus' , true ); } else { feature.setProperty( 'focus' , false ); } }); map.data.addListener( 'mouseover' , function (e) { var feature = e.feature; var regionName = feature.getProperty( 'SIG_KOR_NM' ); tooltip.css({ display: '' , left: e.offset.x, top: e.offset.y }).text(regionName); map.data.overrideStyle(feature, { fillOpacity: 0.6, strokeWeight: 4, strokeOpacity: 1 }); }); map.data.addListener( 'mouseout' , function (e) { tooltip.hide().empty(); map.data.revertStyle(); }); } startDataLayer(); |
참고
'기타' 카테고리의 다른 글
[네이버 지도] 마커 클러스터링 (0) | 2021.01.14 |
---|---|
[네이버 지도] 사용자 정의 오버레이 (0) | 2021.01.13 |
티스토리 SyntaxHighlighter 3.0.8.3 사용법 (0) | 2018.10.25 |
택배 조회 URL (0) | 2018.09.18 |
CSS 이미지 위에 텍스트 표시 (0) | 2018.08.20 |