웹개발(HTML,JS,PHP)

[230225] Naver maps에서 경로, 마커, 정보 표시하기

Choi Jaekuk 2023. 2. 25. 13:37

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);