본문 바로가기
Project/내비게이션

[OSM]OpenStreetMap을 이용한 내비게이션 만들기 - 2

by 알파쿼카 2024. 9. 13.

[DB 생성]

전 게시물에서 작성한 DB를 추가했다.

 

[SQL]

-- 데이터베이스 생성
-- CREATE 테이블명;

-- 데이터베이스 사용
-- USE 테이블명;

-- 사용자 테이블 생성
CREATE TABLE `user` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `name` VARCHAR(255) NULL,
    `password` VARCHAR(255) NULL,
    `email` VARCHAR(255) NULL,
    `create` DATETIME,
    `update` DATETIME
);

-- 차량 동작 로그 테이블 생성
CREATE TABLE `operation` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `user` INT NULL,
    `date` DATE NULL,
    `time` TIME NULL,
    `content` TEXT NULL,
    `location` VARCHAR(255) NULL
);

-- 음성 테이블 생성
CREATE TABLE `voice` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `user` INT NULL,
    `text` TEXT NULL,
    `voice` TEXT NULL,
    `read` VARCHAR(255) NULL
);

-- 경로 테이블 생성
CREATE TABLE `route` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `user` INT NULL,
    `current_point` VARCHAR(255) NULL,
    `start_point` VARCHAR(255) NULL,
    `end_point` VARCHAR(255) NULL,
    `locations` LONGTEXT NULL,
    `distance` VARCHAR(255) NULL,
    `route_time` INT NULL,
    `all` LONGTEXT NULL
);

-- 교통 정보 테이블 생성
CREATE TABLE `traffic` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `date` DATE NULL,
    `time` TIME NULL,
    `start_point` VARCHAR(255) NULL,
    `end_point` VARCHAR(255) NULL,
    `content` TEXT NULL,
    `rank` INT NULL
);

-- 설정 테이블 생성
CREATE TABLE `set` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `user` INT NULL,
    `transport` INT NULL,
    `toll` INT NULL,
    `highway` INT NULL
);

-- POI 테이블 생성
CREATE TABLE `poi` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `location` VARCHAR(255) NULL,
    `name` VARCHAR(255) NULL,
    `content` TEXT NULL,
    `category` VARCHAR(255) NULL,
    `hour` VARCHAR(255) NULL,
    `type` INT NULL
);

-- 알림 테이블 생성
CREATE TABLE `alert` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `user` INT NULL,
    `date` DATE NULL,
    `time` TIME NULL,
    `content` TEXT NULL,
    `read` VARCHAR(255) NULL,
    `location` VARCHAR(255) NULL,
    `rank` INT NULL
);

-- 목록 테이블 생성
CREATE TABLE `list` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `user` INT NULL,
    `question` TEXT NULL,
    `result` TEXT NULL
);

-- 마크 테이블 생성
CREATE TABLE `mark` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `start_point` VARCHAR(255) NULL,
    `end_point` VARCHAR(255) NULL,
    `content` TEXT NULL
);

 


[수동 노드 확인 코드]

데이터베이스에 경로를 저장하기 위해 경로의 노드를 확인해보자 (길 안내 노드만 추가함)

아래는 모든 경로를 마커로 찍어보는 노드 확인용 코드이다.

노드를 수동으로 추가해서 위치를 확인할 수 있다.

<?php
include($_SERVER['DOCUMENT_ROOT'] . "/db/db.php");
?>
<!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" />
    <style>
        #map {
            height: 600px;
        }
    </style>
</head>

<body>
    <div id="map"></div>
    <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
    <script>
        // 지도 초기화
        var map = L.map('map').setView([35.85799, 128.46495], 13); // 중심 좌표 설정

        // OpenStreetMap 타일 레이어 추가
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            maxZoom: 19,
        }).addTo(map);

        // 노드를 추가하는 부분
        var nodes = [
            [35.85799, 128.46495],
            [35.85858, 128.46433],
            [35.85266, 128.48114],
            [35.85323, 128.47743]
        ];

        // 노드 마커 표시
        nodes.forEach(function(node) {
            L.marker(node).addTo(map);
        });
    </script>
</body>

</html>

 

노드 확인용

 

이동할 경로가 잘 찍혀있는 것을 볼 수 있다!


이제 위 코드에 추가해서 내비게이션 같은 모양새를 좀 내보도록 하자.

일단 내비게이션에 들어가면 보통 길 찾기 기능을 제공한다.

 

[길찾기 UI 추가, 현 위치 띄워주기]

길찾기를 할 수 있도록 윗부분에 출발지와 도착지를 입력할 수 있는 칸을 두고

출발지와 도착지를 바꾸는 버튼, 현 위치를 넣는 버튼을 추가했다. (이건 나중에 만들자)

 

초기 위치는 현재 위치로 띄워주고, 출발지에는 현재 위치를 넣어주었다.

 

<?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="/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="start" placeholder="출발지 입력" value='<?php echo $current_point; ?>'>
            <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>
        //current_value --> 위도, 경도 (문자열 형식)
        var current_value = document.getElementById('start').value;

        //current_point --> [위도, 경도] (배열로 변환)
        var current_point = current_value.split(',').map(Number); // 문자열을 배열로 변환하고 숫자로 변환

        // 지도 초기화
        var map = L.map('map').setView(current_point, 13); // 지도의 맨 처음 위치 설정

        // OpenStreetMap 타일 레이어 추가
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            maxZoom: 19,
        }).addTo(map);

        // 노드를 추가하는 부분
        var nodes = [
            [35.85799, 128.46495],
            [35.85858, 128.46433],
            [35.85266, 128.48114],
            [35.85323, 128.47743]
        ];

        // 노드 마커 표시
        nodes.forEach(function(node) {
            L.marker(node).addTo(map);
        });
    </script>
</body>

</html>

 

[길찾기 기능 추가]

다음으로는 사용자가 출발지와 도착지를 입력하면 경로를 찾아주는 기능을 넣어보자.

 

[HTML 코드] (CSS는 이전 게시물의 CSS와 동일하다)

<?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="start" placeholder="출발지 입력" value='<?php echo $current_point; ?>'>
            <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('start').value;

        //current_point --> [위도, 경도] (배열로 변환)
        var current_point = current_value.split(',').map(Number); // 문자열을 배열로 변환하고 숫자로 변환

        // 지도 초기화
        var map = L.map('map').setView(current_point, 13); // 지도의 맨 처음 위치를 현위치로 설정

        // OpenStreetMap 타일 레이어 추가
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            maxZoom: 19,
        }).addTo(map);

        // // 노드를 추가
        // var nodes = [
        //     [35.85799, 128.46495],
        //     [35.85858, 128.46433],
        //     [35.85266, 128.48114],
        //     [35.85323, 128.47743]
        // ];

        // // 노드 마커 표시
        // nodes.forEach(function(node) {
        //     L.marker(node).addTo(map);
        // });

        map.on('locationfound', function(e) {
            marker.setLatLng(e.latlng);
            map.setView(e.latlng, 16);
            closePopup(); // 위치가 발견되면 팝업 닫기
        });

        map.on('locationerror', function(e) {
            alert(e.message);
        });

        var control = L.Routing.control({
            waypoints: [],
            routeWhileDragging: true,
            geocoder: L.Control.Geocoder.nominatim(),
            createMarker: function() {
                return null;
            } // 기본 마커 비활성화
        }).addTo(map);

        //길찾기 팝업 숨기기 !!
        control.getContainer().style.display = 'none';

        // 경로 찾기 함수
        function getRoute() {
            var start = document.getElementById('start').value;
            var end = document.getElementById('end').value;

            if (start && end) {

                // Geocoding을 통해 위치를 찾기
                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.on('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; // coord가 존재할 경우에만 반환
                                    }).filter(coord => coord !== null); // null 값 제거

                                });
                            } else {
                                alert('도착지를 찾을 수 없습니다.');
                            }
                        });
                    } else {
                        alert('출발지를 찾을 수 없습니다.');
                    }
                });
            } else {
                alert('출발지와 도착지를 모두 입력하세요.');
            }
        }
    </script>
</body>

</html>

 

[결과 화면]

 


[경로 데이터 DB 저장]

경로 정보를 요청한 후 데이터베이스에 업데이트 해줄 것이므로 경로 정보 요청 마지막에 아래 코드를 넣는다.

				// 경로 정보 요청
                                control.on('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; // coord가 존재할 경우에만 반환
                                    }).filter(coord => coord !== null); // null 값 제거
                                    
                                    // AJAX 요청으로 데이터베이스 업데이트
                                    updateDatabase(routes, start, end, locations, distance, routeTime, index, all);
                                });

 

 

SCRIPT 코드 하단에는 데이터를 PHP파일로 전송하는 코드를 넣어준다.

콘솔에서 들어갈 데이터를 확인할 수 있다.

// 데이터베이스 업데이트 함수
        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({
                // current_point: marker.getLatLng().lat + ',' + marker.getLatLng().lng,
                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));
        }


[update_route.php]

<?php
include($_SERVER['DOCUMENT_ROOT'] . "/h/db/db.php");

// JSON 형식의 POST 데이터 읽기
$data = json_decode(file_get_contents("php://input"), true);

// 로그 파일에 데이터 기록
file_put_contents('log.txt', print_r($data, true), FILE_APPEND);

// POST 데이터 받기
$user_id = $_SESSION['id'];
$current_point = $data['current_point'];
$start_point = $data['start_point'];
$end_point = $data['end_point'];
$locations = $data['locations'];
$distance = $data['distance'];
$route_time = (int)$data['route_time'];
$all = $data['all'];

// 먼저 ID가 1인 레코드가 존재하는지 확인
$sql_check = "SELECT * FROM `route` WHERE `id` = '" . $user_id . "'";
$result = $conn->query($sql_check);

if ($result->num_rows > 0) {
    $sql_up = "UPDATE `route` set
            `user` = '" . $user_id . "',
            `start_point` = '" . $start_point . "',
            `end_point` = '" . $end_point . "',
            `locations` = '" . $locations . "',
            `distance` = '" . $distance . "',
            `route_time` = '" . $route_time . "',
            `all` = '" . $all . "'
            WHERE id= '" . $user_id . "'
            ";
    $res_up = mysqli_query($conn, $sql_up);

    if ($res_up) {
        echo "업데이트 성공";
    } else {
        echo "업데이트 실패: " . mysqli_error($conn);
    }
} else {
    // ID가 1인 레코드가 없을 경우 새 레코드 생성
    $sql_in = "INSERT INTO `route`
    (
    `user`,
    `start_point`,
    `end_point`,
    `locations`,
    `distance`,
    `route_time`,
    `all`
    )
    VALUES 
    (
    '" . $user_id . "',
    '" . $start_point . "',
    '" . $end_point . "',
    '" . $locations . "',
    '" . $distance . "',
    '" . $route_time . "',
    '" . $all . "'
        )
    ';
    ";
    $res_in = mysqli_query($conn, $sql_in);

    if ($res_in) {
        echo "생성 성공";
    } else {
        echo "생성 실패: " . mysqli_error($conn);
    }
}

$conn->close();

 

[결과 화면]