지난 글에서는 경로 검색 기능까지 제작해 보았다.
이번 글에서는 현위치를 가장 가까운 건물(시설물)과 연동해서 보여주고,
현위치에 관한 세부 기능들을 완성해보자.
현위치를 건물이름과 연동하기
var current_lat = current_point[0];
var current_lng = current_point[1];
//현재 주소 반환
getNearestBuilding(current_lat, current_lng);
// 역지오코딩으로 가까운 건물 찾기
function getNearestBuilding(lat, lng) {
var url = `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lng}&format=json`;
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok ' + response.statusText);
}
return response.json();
})
.then(data => {
if (data && data.display_name) {
var city = data.address.city;
var borough = data.address.borough;
var road = data.address.road;
var address = city + ' ' + borough + ' ' + road
document.getElementById('start').value = address; // 출발지 입력란에 설정
} else {
alert('가장 가까운 위치를 찾을 수 없습니다.');
}
})
.catch(error => {
console.error('Error:', error);
alert('위치를 찾는 중 오류가 발생했습니다.');
});
}
[결과 화면]
주소 배열이 뒤죽박죽이라서 흔히 쓰는 주소명이 나올 수 있도록 배치했다.
ISSUE
1) 찾기 버튼을 여러번 누를 경우 데이터 베이스 요청이 배로 늘어나는 이슈가 있었음
[원인]
찾기 버튼을 여러 번 누를 경우 데이터베이스 요청이 배로 늘어나는 문제는 getRoute 함수 내에서 이벤트 리스너가 중복으로 추가되기 때문이다.
control.on('routesfound', function(e) {...}); 블록이 getRoute 함수 내부에서 호출될 때마다 새로운 이벤트 리스너가 추가됨이로 인해 경로를 찾을 때마다 동일한 작업이 여러 번 수행되어 데이터베이스 요청이 중복 발생함
[해결]
이벤트 리스너를 추가하기 전에 기존의 리스너를 제거하거나, 리스너를 한 번만 추가하도록 수정해야함
// 경로 찾기 함수
function getRoute() {
var start = document.getElementById('start').value;
var end = document.getElementById('end').value;
if (start && end) {
L.Control.Geocoder.nominatim().geocode(start, function(results) {
if (results.length > 0) {
var startLatLng = results[0].center;
L.Control.Geocoder.nominatim().geocode(end, function(results) {
if (results.length > 0) {
var endLatLng = results[0].center;
// 출발지와 도착지 설정
control.setWaypoints([
L.latLng(startLatLng.lat, startLatLng.lng),
L.latLng(endLatLng.lat, endLatLng.lng)
]);
// 경로 정보 요청
// 이벤트 리스너가 중복 추가되지 않도록 설정
control.once('routesfound', function(e) {
var routes = e.routes;
var all = routes[0].coordinates.map(coord => `"${coord.lat}, ${coord.lng}"`).join(',');
var distance = routes[0].summary.totalDistance;
var routeTime = routes[0].summary.totalTime;
var index = routes[0].instructions.map(instruction => instruction.index);
var locations = index.map(index => {
var coord = routes[0].coordinates[index];
return coord ? `${coord.lat}, ${coord.lng}` : null;
}).filter(coord => coord !== null);
//경로 발견시 지도 위치 조정
map.on('locationfound', function(e) {
marker.setLatLng(e.latlng);
map.setView(e.latlng, 16);
closePopup(); // 위치가 발견되면 팝업 닫기
});
map.on('locationerror', function(e) {
alert(e.message);
});
// AJAX 요청으로 데이터베이스 업데이트
updateDatabase(routes, start, end, locations, distance, routeTime, index, all);
});
} else {
alert('도착지를 찾을 수 없습니다.');
}
});
} else {
alert('출발지를 찾을 수 없습니다.');
}
});
} else {
alert('출발지와 도착지를 모두 입력하세요.');
}
}
2) 경로를 찾았을 때 지도 뷰가 재배치되지 않음
[원인]
- 경로가 발견된 후 지도 뷰 설정: control.once('routesfound', ...) 내에서 경로가 발견된 후에 지도 뷰를 설정하도록 변경
- 중복 이벤트 리스너 제거: 경로에 대한 이벤트 리스너를 한 번만 설정하도록 변경
[해결]
// 경로 정보 요청
// 이벤트 리스너가 중복 추가되지 않도록 설정
control.once('routesfound', function(e) {
var routes = e.routes;
// 경로 정보를 가져온 후 지도 뷰를 설정
var firstRoute = routes[0];
var waypoints = firstRoute.coordinates;
var bounds = L.latLngBounds(waypoints); // 모든 경로의 경계 계산
map.fitBounds(bounds); // 경로에 맞게 지도를 조정
var all = waypoints.map(coord => `"${coord.lat}, ${coord.lng}"`).join(',');
var distance = firstRoute.summary.totalDistance;
var routeTime = firstRoute.summary.totalTime;
var index = firstRoute.instructions.map(instruction => instruction.index);
var locations = index.map(index => {
var coord = waypoints[index];
return coord ? `${coord.lat}, ${coord.lng}` : null;
}).filter(coord => coord !== null);
// AJAX 요청으로 데이터베이스 업데이트
updateDatabase(routes, start, end, locations, distance, routeTime, index, all);
});
[결과 화면]
지도가 경로에 맞게 재배치 되는 것을 볼 수 있다!
출발지와 도착지에 마커 찍기
출발지와 도착지에 각각 표시될 마커를 찍어주자.
// 출발지와 도착지 설정
control.setWaypoints([
L.latLng(startLatLng.lat, startLatLng.lng),
L.latLng(endLatLng.lat, endLatLng.lng)
]);
// 출발지 마커 추가
var startIcon = L.icon({
iconUrl: '/h/img/start.png', // start.png의 경로
iconSize: [25, 41], // 아이콘 크기
iconAnchor: [12, 41], // 아이콘 기준점
});
L.marker(startLatLng, {
icon: startIcon
}).addTo(map).bindPopup("출발지: " + start);
// 도착지 마커 추가
var endIcon = L.icon({
iconUrl: '/h/img/end.png', // end.png의 경로
iconSize: [25, 41], // 아이콘 크기
iconAnchor: [12, 41], // 아이콘 기준점
});
L.marker(endLatLng, {
icon: endIcon
}).addTo(map).bindPopup("도착지: " + end);
대충 PNG 이미지 만들어서 넣어줬다
첫 화면에서도 현재 위치를 마커로 띄워주고, 아이콘 위치도 자연스럽게 조정했다.
전체 코드
<?php
include($_SERVER['DOCUMENT_ROOT'] . "/h/db/db.php");
$user_id = $_SESSION['id'];
$sql = "SELECT * FROM `route` WHERE `id` = " . $user_id . "";
$user_id;
$res = mysqli_query($conn, $sql);
$row = mysqli_fetch_array($res);
($row['current_point'] != null) ? $current_point = $row['current_point'] : $current_point = '1';
?>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>노드 확인용</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet-routing-machine/dist/leaflet-routing-machine.css" />
<link rel="stylesheet" href="/h/css/index.css">
</head>
<body>
<div id="popup">
<div class="search_left">
<button class="btn btn_change" onclick="">↑↓</button>
</div>
<div class="search">
<input type="text" id="current" value='<?php echo $current_point; ?>' style='display: none;'>
<input type="text" id="start" placeholder="출발지 입력">
<input type="text" id="end" placeholder="도착지 입력">
</div>
<div class="search_right">
<button class="btn btn_current" onclick="test()">현위치</button>
<button class="btn btn_search" onclick="getRoute()">찾기</button>
</div>
</div>
<div id="map"></div>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-routing-machine/dist/leaflet-routing-machine.js"></script>
<script src="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js"></script>
<script>
// current_value --> 위도, 경도 (문자열 형식)
var current_value = document.getElementById('current').value;
// current_point --> [위도, 경도] (배열로 변환)
var current_point = current_value.split(',').map(Number); // 문자열을 배열로 변환하고 숫자로 변환
var current_lat = current_point[0];
var current_lng = current_point[1];
// 현재 위치 마커 변수
var currentMarker;
// 출발지 마커 변수
var startMarker;
// 도착지 마커 변수
var endMarker;
// 지도 초기화
var map = L.map('map').setView(current_point, 16); // 지도의 맨 처음 위치를 현위치로 설정
// OpenStreetMap 타일 레이어 추가
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
}).addTo(map);
// 현재 위치 마커 추가
var currentIcon = L.icon({
iconUrl: '/h/img/current.png', // current.png의 경로
iconSize: [15, 15], // 아이콘 크기
iconAnchor: [7.5, 7.5], // 아이콘 기준점
});
currentMarker = L.marker([current_lat, current_lng], {
icon: currentIcon
})
.addTo(map)
.bindPopup("현재 위치");
var control = L.Routing.control({
waypoints: [],
routeWhileDragging: true,
geocoder: L.Control.Geocoder.nominatim(),
createMarker: function() {
return null;
} // 기본 마커 비활성화
}).addTo(map);
//길찾기 팝업 숨기기 !!
control.getContainer().style.display = 'none';
// 확대/축소 컨트롤 숨기기
var zoomControl = map.zoomControl; // 기본 줌 컨트롤 가져오기
if (zoomControl) {
zoomControl.remove(); // 줌 컨트롤 제거
}
//현재 주소 반환
getNearestBuilding(current_lat, current_lng);
// 역지오코딩으로 가까운 건물 찾기
function getNearestBuilding(lat, lng) {
var url = `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lng}&format=json`;
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok ' + response.statusText);
}
return response.json();
})
.then(data => {
if (data && data.display_name) {
var city = data.address.city;
var borough = data.address.borough;
var road = data.address.road;
var address = city + ' ' + borough + ' ' + road
document.getElementById('start').value = address; // 출발지 입력란에 설정
} else {
alert('가장 가까운 위치를 찾을 수 없습니다.');
}
})
.catch(error => {
console.error('Error:', error);
alert('위치를 찾는 중 오류가 발생했습니다.');
});
}
// 경로 찾기 함수
function getRoute() {
var start = document.getElementById('start').value;
var end = document.getElementById('end').value;
if (start && end) {
L.Control.Geocoder.nominatim().geocode(start, function(results) {
if (results.length > 0) {
var startLatLng = results[0].center;
L.Control.Geocoder.nominatim().geocode(end, function(results) {
if (results.length > 0) {
var endLatLng = results[0].center;
// 출발지와 도착지 설정
control.setWaypoints([
L.latLng(startLatLng.lat, startLatLng.lng),
L.latLng(endLatLng.lat, endLatLng.lng)
]);
// 기존 마커 제거
if (startMarker) {
map.removeLayer(startMarker);
}
if (endMarker) {
map.removeLayer(endMarker);
}
// 출발지 마커 추가
var startIcon = L.icon({
iconUrl: '/h/img/start.png', // start.png의 경로
iconSize: [30, 41], // 아이콘 크기
iconAnchor: [15, 41], // 아이콘 기준점
});
startMarker = L.marker(startLatLng, {
icon: startIcon
})
.addTo(map)
.bindPopup("출발지: " + start);
// 도착지 마커 추가
var endIcon = L.icon({
iconUrl: '/h/img/end.png', // end.png의 경로
iconSize: [30, 41], // 아이콘 크기
iconAnchor: [15, 41], // 아이콘 기준점
});
endMarker = L.marker(endLatLng, {
icon: endIcon
}).addTo(map).bindPopup("도착지: " + end);
// 경로 정보 요청
// 이벤트 리스너가 중복 추가되지 않도록 설정
control.once('routesfound', function(e) {
var routes = e.routes;
// 경로 정보를 가져온 후 지도 뷰를 설정
var firstRoute = routes[0];
var waypoints = firstRoute.coordinates;
var bounds = L.latLngBounds(waypoints); // 모든 경로의 경계 계산
map.fitBounds(bounds); // 경로에 맞게 지도를 조정
var all = waypoints.map(coord => `"${coord.lat}, ${coord.lng}"`).join(',');
var distance = firstRoute.summary.totalDistance;
var routeTime = firstRoute.summary.totalTime;
var index = firstRoute.instructions.map(instruction => instruction.index);
var locations = index.map(index => {
var coord = waypoints[index];
return coord ? `${coord.lat}, ${coord.lng}` : null;
}).filter(coord => coord !== null);
// AJAX 요청으로 데이터베이스 업데이트
updateDatabase(routes, start, end, locations, distance, routeTime, index, all);
});
} else {
alert('도착지를 찾을 수 없습니다.');
}
});
} else {
alert('출발지를 찾을 수 없습니다.');
}
});
} else {
alert('출발지와 도착지를 모두 입력하세요.');
}
}
// 데이터베이스 업데이트 함수
function updateDatabase(routes, start, end, locations, distance, routeTime, index, all) {
var xhr = new XMLHttpRequest();
xhr.open("POST", "update_route.php", true);
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
console.log('데이터베이스 업데이트 성공:', xhr.responseText);
} else {
console.error('업데이트 실패:', xhr.status, xhr.statusText);
}
}
};
xhr.send(JSON.stringify({
start_point: start,
end_point: end,
locations: JSON.stringify(locations).slice(1, -1),
distance: distance,
route_time: routeTime,
all: all
}));
// console.log(routes);
// console.log(index);
// console.log(all);
// console.log(JSON.stringify(locations).slice(1, -1));
}
</script>
</body>
</html>
짜잔ㅎㅎ 경로가 아주 만족스럽다!
다음 게시물에서는 현재 위치를 실시간으로 업데이트 해보자
'Project > 내비게이션' 카테고리의 다른 글
[OSM] 내비게이션 어플리케이션 개발 (1) (1) | 2024.09.23 |
---|---|
[OSM]OpenStreetMap을 이용한 내비게이션 만들기 - 4 (1) | 2024.09.20 |
[OSM]OpenStreetMap을 이용한 내비게이션 만들기 - 2 (2) | 2024.09.13 |
[OSM]OpenStreetMap을 이용한 내비게이션 만들기 - 1 (2) | 2024.09.09 |