[230225] Naver maps에서 경로, 마커, 정보 표시하기
Web, App 개발에서 지도기능을 계속 활용해오다가 이번에 경로 표시 기능도 추가하게되서
한번 정리할겸 포스팅 하게되었다.
먼저 구현된 페이지는 다음과 같다
https://cjk09083.cafe24.com/example/roverMap.html
개발 목적은
1) Rover(이동체) 2대를 Rover1, Rover2라 명명하고 이들의 경로를 지도에 나타내는 것이다.
2) 경로는 서울시청을 중심으로 1분마다 30분간 즉 30개의 경로를 표시하도록 한다.
3) 마커를 클릭하면 마커위치의 위도/경도/시간을 표시해주는 정보(infowindow)를 나타내고 다른곳을 클릭하면 닫는다.
4) 지도 오른쪽엔 데이터 목록을 Rover1/2 따로 표시하고 라디오 버튼을 눌러 전환 가능하게한다.
5) 데이터 목록에서 보기 원하는 데이터를 클릭시 지도에서 해당 위치를 포커스하고 마커와 정보창을 다시 생성한다.
소스 코드는 아래에서 확인 가능하다.
https://github.com/cjk09083/Code/tree/main/example/roverMap
이제 부분별로 분석을 해보자.
1. Html
1) 테이블과 버튼 CSS 선언 (생략)
2) Map과 데이터목록 창 생성
- 화면을 둘로 나누어 왼쪽은 Map(Id:map), 오른쪽은 데이터 목록 Table(id:dataScroll)을 생성한다.
<div style="width: 100%; height: 100%;">
<div style="display: flex; padding: 20px; border: 2px solid #333; border-radius: 5px; height: 720px;">
<div id="map" style="width:100%;height:100%; float: left;"></div>
<div id="dataScroll" style="width:650px;height:100%; margin-left: 10px; float: right;">
<div class="button-group" style="margin-bottom: 20px;">
<button id="button1" class="active">Rover1</button>
<button id="button2">Rover2</button>
</div>
<div style=" height: 90%; overflow: scroll;">
<table>
<colgroup>
<col width="10%">
<col width="50%">
<col width="20%">
<col width="20%">
</colgroup>
<thead>
<tr>
<th style="padding: 0px; text-align: center; font-weight: bold;">번호</th>
<th style="padding: 0px; text-align: center; font-weight: bold;">시간</th>
<th style="padding: 0px; text-align: center; font-weight: bold;">위도</th>
<th style="padding: 0px; text-align: center; font-weight: bold;">경도</th>
</tr>
</thead>
<tbody id="dataBody" style="text-align: center;">
</tbody>
</table>
</div>
</div>
</div>
</div>
2. Javascript
1) 변수 선언 (설명 생략)
let lineData = {}; // lineData 객체 생성
const init_lon = 126.9781553; // 초기 경도값 상수
const init_lat = 37.5666454; // 초기 위도값 상수
let zoom = 18; // zoom level 상수
let r1Path = []; // Rover1의 경로를 저장할 배열
let r2Path = []; // Rover2의 경로를 저장할 배열
let r1Polyline = ""; // Rover1의 Polyline을 저장할 변수
let r1Marker = ""; // Rover1의 Marker을 저장할 변수
let r1Info = ""; // Rover1의 정보창을 저장할 변수
let r2Polyline = ""; // Rover2의 Polyline을 저장할 변수
let r2Marker = ""; // Rover2의 Marker을 저장할 변수
let r2Info = ""; // Rover2의 정보창을 저장할 변수
let r1Tbody = ""; // Rover1의 위치 데이터를 저장할 tbody
let r2Tbody = ""; // Rover2의 위치 데이터를 저장할 tbody
2) Naver maps 생성 [initMap()]
- https://console.ncloud.com/naver-service/application 에서 Web Dynamic Map 을 선택해 API KEY를 발급받는다.
- 아래와 같이 api 선언을 해준다 <?=naverkey?> 부분은 php로 당겨온 환경 변수
<script type="text/javascript" src="https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=<?=naverkey?>"></script>
- 아까 생성한 div 에 map을 생성시켜준다.
+ 초기화 시 init lat, lon, zoom을 입력 (서울시청)
+ 줌 컨트롤, 맵 타입 컨트롤을 추가
+ 위에서 생성한 Radio button의 리스너도 여기서 선언
3) Rover 좌표 데이터 업데이트 [updateMap()]
- 서버쪽에서 로버들의 좌표 데이터를 가져온다.
- 로버의 좌표 데이터는 JSON 형태로 다음과 같은 구조를 갖는다.
{
'id': 데이터의 id
'r1Lat': Rover1의 Latitude
'r1Lon': Rover1의 Longitude
'r1Status': Rover1의 상태 (1:ON, 0:OFF)
'r2Lat': Rover2의 Latitude
'r2Lon': Rover2의 Longitude
'r2Status': Rover2의 상태 (1:ON, 0:OFF)
'created_at': 데이터 기록 시간
}
4) 마커, 경로, 정보창 추가 [addLine()]
- 가져온 JSON 데이터들을 조회하며 유효한 데이터 (Rover가 ON 상태이고 좌표가 0보다 클때) 들을 경로에 추가한다.
- 동시에 데이터 목록 테이블에도 tr 객체를 추가시킨다.
for (let key in lineData) {
const item = lineData[key];
if (item.r1Lat > 0 && item.r1Status > 0){
r1Path.push(new naver.maps.LatLng(item.r1Lat, item.r1Lon))
r1Last = item;
r1Tbody = "<tr onclick=\"focusMarker(\'"+item.created_at+"\',\'"+item.r1Lat+"\',\'"+item.r1Lon+"\',\'Rover1\')\"><td>"+
item.id+"</td><td>"+item.created_at+"</td><td>"+item.r1Lat+"</td><td>"+item.r1Lon+"</td></tr>" + r1Tbody
}
if (item.r2Lat > 0 && item.r2Status > 0){
r2Path.push(new naver.maps.LatLng(item.r2Lat, item.r2Lon))
r2Last = item;
r2Tbody = "<tr onclick=\"focusMarker(\'"+item.created_at+"\',\'"+item.r2Lat+"\',\'"+item.r2Lon+"\',\'Rover2\')\"><td>"+
item.id+"</td><td>"+item.created_at+"</td><td>"+item.r2Lat+"</td><td>"+item.r2Lon+"</td></tr>" + r2Tbody
}
}
- 경로가 0개 이상이면 경로의 마지막 위치에 마커와 정보창을 생성한다.
+ 정보창은 html div로 직접 content를 생성하였다.
const contentString = "<div style='letter-spacing: 1px;line-height: 25px; border: 2px solid black; border-radius: 10px; padding: 10px;'>"+
"<b>Rover2 좌표</b></br>"+
"위도: "+r2Last.r2Lat+"</br>"+
"경도: "+r2Last.r2Lon+"</br>"+
r2Last.created_at+"</br>"+
"</div>"
r2Info = new naver.maps.InfoWindow({
content: contentString, // HTML content 추가
borderWidth: 0, // 기존 테두리 제거
disableAnchor: true, // 기존 앵커 제거
backgroundColor: 'rgba(255, 255, 255, 0.8)', // 불투명한 배경으로 설정
pixelOffset: new naver.maps.Point(0, -5), // 마커와 겹치지 않게 위에 생성
});
+ 생성한 마커, 경로, 정보창을 지도에 연동해준다.
//위의 배열을 이용해 라인 그리기
r2Polyline = new naver.maps.Polyline({
path: r2Path, //선 위치 변수배열
strokeColor: '#0000FF', //선 색 빨강 #빨강,초록,파랑
strokeOpacity: 0.8, //선 투명도 0 ~ 1
strokeWeight: 3, //선 두께
map: map //오버레이할 지도
});
// 배열 마지막 위치를 마커로 표시함
r2Marker = new naver.maps.Marker({
position: r2Path[r2Path.length-1],
map: map,
icon: {
content: '<svg xmlns="http://www.w3.org/2000/svg" width="22" height="35"><path fill="#0072b2" d="M11,34.1C5.6,24.2,0,14.7,0,9.6c0-6,4.8-10.8,10.8-10.8c6,0,10.8,4.8,10.8,10.8C21.6,14.7,16,24.2,11,34.1z M10.8,5.4C7.2,5.4,4.5,8.1,4.5,11.7s2.7,6.3,6.3,6.3s6.3-2.7,6.3-6.3S14.4,5.4,10.8,5.4z"/></svg>',
anchor: new naver.maps.Point(11, 35)
}
});
r2Polyline.setMap(map);
r2Marker.setMap(map);
naver.maps.Event.addListener(r2Marker, 'click', function() {
r2Info.open(map, r2Marker)
});
- 마지막 경로 위치로 지도를 포커싱한다.
map.setCenter(r1Path[r1Path.length-1]); // 중심 좌표 이동
map.setZoom(zoom);