Pgrouting

moon.kick·2025년 5월 2일

여기에서 PgRouting용 도로 그래프 테이블 SQL을 다운로드하세요

다음 단계로 OpenStreetMap 도보 도로 데이터를 가져와 ways/nodes 테이블에 삽입하는 방법을 설명해드릴게요. 계속할까요? 

좋습니다! 아래는 PgRouting 기반 경로 탐색을 위한 PostGIS + ways/nodes 테이블 구조 SQL 전체 코드를 직접 보여드리는 형식으로 정리한 것입니다.

✅ PgRouting 도로 그래프 테이블 생성 SQL

  1. ways 테이블 – 도로(간선) 정보

CREATE TABLE ways (
id SERIAL PRIMARY KEY,
source BIGINT, -- 시작 노드 ID
target BIGINT, -- 도착 노드 ID
cost DOUBLE PRECISION, -- 정방향 이동 비용 (예: 거리, 시간)
reverse_cost DOUBLE PRECISION, -- 역방향 이동 비용
x1 DOUBLE PRECISION, -- 시작점 x좌표 (경도)
y1 DOUBLE PRECISION, -- 시작점 y좌표 (위도)
x2 DOUBLE PRECISION, -- 끝점 x좌표 (경도)
y2 DOUBLE PRECISION, -- 끝점 y좌표 (위도)
geom GEOMETRY(LineString, 4326) -- 실제 도로 라인
);

  1. nodes 테이블 – 도로 교차점(노드) 정보

CREATE TABLE nodes (
id BIGINT PRIMARY KEY,
geom GEOMETRY(Point, 4326) -- 위도/경도 정보
);

  1. places 테이블에 도로노드 매핑 필드 추가

ALTER TABLE places
ADD COLUMN nearest_node BIGINT; -- 장소에서 가장 가까운 도로 노드 ID

✅ 사용 목적 요약
• ways는 도로 라인이며, PgRouting에서 실제 경로 탐색할 대상
• nodes는 도로 그래프 상의 꼭짓점
• places는 내가 추천하고 싶은 장소이며, 이를 가장 가까운 node에 매핑해야 경로 탐색이 가능함

✅ 다음 단계 예고
• OpenStreetMap → ways/nodes 자동 생성 도구: osm2pgrouting
• 또는 PostGIS 내에서 ST_DWithin + ST_Distance로 장소 ↔ 노드 매핑

바로 이어서 SELECT로 장소에서 가장 가까운 노드 ID 찾는 쿼리도 보여드릴까요?
예: UPDATE places SET nearest_node = ... 형식입니다.

좋습니다! 아래는 places 테이블의 각 장소에 대해 가장 가까운 도로 노드(nodes.id)를 찾아 연결하는 SQL 쿼리입니다.
이 작업은 PgRouting에서 경로 탐색을 가능하게 만드는 핵심 연결고리입니다.

✅ 장소 → 가장 가까운 노드 매핑 쿼리

UPDATE places
SET nearest_node = sub.id
FROM (
SELECT n.id, n.geom
FROM nodes n
ORDER BY places.geom <-> n.geom -- KNN 정렬 (거리 짧은 순)
LIMIT 1
) AS sub
WHERE ST_DWithin(places.geom, sub.geom, 0.005); -- 약 500m 이내 제한

✅ 설명

요소 의미
places.geom <-> n.geom PostGIS의 KNN(가장 가까운 이웃) 연산자
ST_DWithin() 거리 제한 (예: 500m 이내 노드만 탐색)
LIMIT 1 가장 가까운 노드 1개만 매핑

✅ 전체 적용 예시 (모든 장소에 대해 반복)

UPDATE places
SET nearest_node = (
SELECT n.id
FROM nodes n
ORDER BY places.geom <-> n.geom
LIMIT 1
);

위 쿼리를 실행하면 각 장소(place)에 대해 가장 가까운 node가 자동으로 지정됩니다.
이 nearest_node를 출발지/도착지로 사용하여 PgRouting 경로를 탐색할 수 있습니다.

이제 다음으로는 경로 탐색 SQL (예: A 알고리즘) 을 보여드릴까요?
예: SELECT
FROM pgr_astar(...) 형태로 연결되는 실제 쿼리입니다.

좋습니다! 이제 아래는 PgRouting의 핵심 알고리즘 중 하나인 A*를 활용하여
장소 간 최적 경로를 찾는 SQL 쿼리 예시입니다.

✅ A* 알고리즘을 이용한 최단경로 탐색 SQL (pgr_astar)

SELECT seq, id1 AS node, id2 AS edge, cost, geom
FROM pgr_astar(
'SELECT id, source, target, cost, reverse_cost, x1, y1, x2, y2 FROM ways',
start_node_id, -- 출발지 노드 ID
end_node_id, -- 도착지 노드 ID
directed := false
)
JOIN ways ON ways.id = id2;

✅ 파라미터 설명

항목 설명
id1 경로 순서대로 방문하는 노드 ID
id2 해당 노드로 이어지는 간선(도로)의 ID
cost 이동 비용 (거리 등)
geom 도로 구간(선형 지오메트리)

✅ 실전 예시 (서울숲공원 → 팝업스토어)

SELECT seq, id1, id2, cost, w.geom
FROM pgr_astar(
'SELECT id, source, target, cost, reverse_cost, x1, y1, x2, y2 FROM ways',
23141, -- 서울숲공원에 연결된 노드 ID
19488, -- 디깅팝업스토어에 연결된 노드 ID
false
) AS r
JOIN ways w ON r.id2 = w.id;

✅ 결과 활용 방식
• 반환된 ways.geom 데이터를 React로 전달
• Kakao Maps에서 선(Polyline)으로 이어서 실제 도보 길 표시
• 경로 총 거리, 소요 시간은 SUM(cost)로 계산 가능

✅ 다음 단계 제안
1. 다중 경로 (K-Shortest Paths)
2. 장소 여러 개 → TSP or 경유지 최적화
3. Spring Boot ↔ 이 SQL 연동해서 API로 만들기

이어서 어떤 기능으로 확장할까요? (예: 여러 장소 최적 순서 찾기, Spring 연동하기 등)

좋습니다! 가장 쉽게 시작할 수 있는 다음 단계는 바로:

✅ A* 기반 단일 경로 탐색을 Spring Boot에서 API로 호출하기

목표
• 프론트에서 startPlaceId, endPlaceId를 넘기면
• 백엔드(Spring Boot)가 places → nearest_node → pgr_astar() 쿼리 실행
• 결과를 List 또는 LineString으로 반환 → 지도에 표시 가능

✅ 구현 흐름
1. GET /api/path?from=1&to=2
2. places 테이블에서 from/to의 nearest_node를 조회
3. 아래 SQL 실행:

SELECT ST_AsText(w.geom)
FROM pgr_astar(
'SELECT id, source, target, cost, reverse_cost, x1, y1, x2, y2 FROM ways',
:fromNode, :toNode, false
) AS r
JOIN ways w ON r.id2 = w.id;

4.	List<LineString> 또는 좌표 배열로 응답

✅ Java Controller 예시

@GetMapping("/path")
public List getPath(@RequestParam Long from, @RequestParam Long to) {
Long fromNode = placeRepository.findNearestNodeByPlaceId(from);
Long toNode = placeRepository.findNearestNodeByPlaceId(to);
return routingService.getPathGeometry(fromNode, toNode);
}

이 방법은 PgRouting이 설치된 DB에 쿼리만 연결하면 바로 작동하므로,
도보 경로 기반 시각화의 가장 쉬운 출발점입니다.

바로 코드로 만들어드릴까요? (Spring Controller + SQL 연동 서비스)

여기에서 PgRouting 기반 경로 탐색 API 코드 파일들을 다운로드하세요

이제 /api/path?from=1&to=2 요청 시, Spring Boot가 PostGIS + PgRouting을 활용해
실제 도보 가능한 도로 경로를 반환할 수 있습니다.

이걸 기반으로 Kakao Map에 폴리라인을 그리는 React 코드도 바로 도와드릴 수 있어요! 원하실까요?

물론입니다!
아래는 PgRouting 기반 Spring Boot API 구현 전체 코드를 직접 보기 형식으로 정리한 것입니다.

✅ 1. RoutingController.java

package com.routy.controller;

import com.routy.service.RoutingService;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/api/path")
public class RoutingController {

private final RoutingService routingService;

public RoutingController(RoutingService routingService) {
    this.routingService = routingService;
}

@GetMapping
public List<String> getPath(@RequestParam Long from, @RequestParam Long to) {
    return routingService.findPathWKT(from, to);
}

}

✅ 2. RoutingService.java

package com.routy.service;

import java.util.List;

public interface RoutingService {
List findPathWKT(Long fromPlaceId, Long toPlaceId);
}

✅ 3. RoutingServiceImpl.java

package com.routy.service;

import com.routy.repository.PlaceRepository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Query;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class RoutingServiceImpl implements RoutingService {

@PersistenceContext
private EntityManager entityManager;

private final PlaceRepository placeRepo;

public RoutingServiceImpl(PlaceRepository placeRepo) {
    this.placeRepo = placeRepo;
}

@Override
public List<String> findPathWKT(Long fromPlaceId, Long toPlaceId) {
    Long fromNode = placeRepo.findNearestNodeId(fromPlaceId);
    Long toNode = placeRepo.findNearestNodeId(toPlaceId);

    String sql = "SELECT ST_AsText(w.geom) " +
                 "FROM pgr_astar(" +
                 "'SELECT id, source, target, cost, reverse_cost, x1, y1, x2, y2 FROM ways'," +
                 ":from, :to, false) AS r " +
                 "JOIN ways w ON r.id2 = w.id";

    Query query = entityManager.createNativeQuery(sql);
    query.setParameter("from", fromNode);
    query.setParameter("to", toNode);

    List<?> results = query.getResultList();
    List<String> pathWKT = new ArrayList<>();
    for (Object row : results) {
        pathWKT.add((String) row);
    }
    return pathWKT;
}

}

✅ 전제 조건 요약
• places 테이블에 nearest_node 필드가 있어야 함
• ways 테이블은 PgRouting용 간선 그래프 구조
• PlaceRepository에 다음 메서드 필요:

@Query("SELECT p.nearestNode FROM Place p WHERE p.id = :id")
Long findNearestNodeId(@Param("id") Long id);

이제 이 API를 React에서 호출하면, LineString 형식의 도로 경로를 받아올 수 있습니다.
원하시면 이걸 기반으로 Kakao Map에 실제 경로 그리기(폴리라인 파싱) 코드도 보여드릴게요!

좋습니다! 이제 Spring Boot API에서 받은 LineString 경로 데이터를 Kakao Maps에서 폴리라인으로 시각화하는 React 코드를 직접 보여드리겠습니다.

✅ 1. 예상 백엔드 응답 (Spring Boot /api/path?from=1&to=2)

[
"LINESTRING(127.03512 37.54390,127.03562 37.54421)",
"LINESTRING(127.03562 37.54421,127.03601 37.54458)",
...
]

✅ 2. React 코드 (PathMap.jsx) – LineString을 지도 위에 그리기

import React, { useEffect } from 'react';
import axios from 'axios';

const PathMap = ({ fromPlaceId, toPlaceId }) => {
useEffect(() => {
const script = document.createElement('script');
script.src = //dapi.kakao.com/v2/maps/sdk.js?appkey=YOUR_KAKAO_KEY&autoload=false;
script.async = true;
script.onload = () => {
window.kakao.maps.load(async () => {
const map = new window.kakao.maps.Map(document.getElementById('map'), {
center: new window.kakao.maps.LatLng(37.544, 127.036),
level: 5
});

    // API 요청
    const res = await axios.get(`/api/path?from=${fromPlaceId}&to=${toPlaceId}`);
    const lines = res.data; // LineString 배열

    const allCoords = [];

    lines.forEach(wkt => {
      const coords = parseLineString(wkt);
      allCoords.push(...coords);
      const polyline = new window.kakao.maps.Polyline({
        path: coords,
        strokeWeight: 4,
        strokeColor: '#0077FF',
        strokeOpacity: 0.8,
        strokeStyle: 'solid'
      });
      polyline.setMap(map);
    });

    if (allCoords.length > 0) {
      map.setCenter(allCoords[0]);
    }
  });
};
document.head.appendChild(script);

}, [fromPlaceId, toPlaceId]);

const parseLineString = (wkt) => {
// "LINESTRING(lon lat, lon lat, ...)"
const coords = wkt
.replace("LINESTRING(", "")
.replace(")", "")
.split(",")
.map(pair => {
const [x, y] = pair.trim().split(" ");
return new window.kakao.maps.LatLng(parseFloat(y), parseFloat(x));
});
return coords;
};

return (

<div id="map" style={{ width: '100%', height: '400px' }}></div>

);
};

export default PathMap;

✅ 사용 예시

✅ 설명 요약

구성 요소 역할
axios.get('/api/path') Spring Boot + PgRouting에서 경로 좌표 가져오기
LINESTRING() 파싱 좌표를 kakao.maps.LatLng()로 변환
Polyline 실제 지도를 따라 경로 선 그리기

필요하시면 경로 거리 표시, 마커 + 인포윈도우, 도착시간 예측, 애니메이션 경로 재생 같은 기능도 확장 가능합니다!
원하시나요? (예: “경로 따라 거리 + 시간 표시해줘”)

좋습니다!
PgRouting 경로를 기반으로 총 거리와 예상 소요 시간을 계산하여 지도 아래에 함께 표시해드릴 수 있습니다.

✅ 구현 목표
• Spring Boot에서 반환된 LineString 경로들의 길이를 계산
• 총 이동 거리(m)와 소요 시간(분)을 화면에 출력
• 도보 기준: 4.5km/h → 약 75m/min

✅ React 코드 수정 (PathMap.jsx 업데이트)

import React, { useEffect, useState } from 'react';
import axios from 'axios';

const PathMap = ({ fromPlaceId, toPlaceId }) => {
const [totalDistance, setTotalDistance] = useState(0);

useEffect(() => {
const script = document.createElement('script');
script.src = //dapi.kakao.com/v2/maps/sdk.js?appkey=YOUR_KAKAO_KEY&autoload=false;
script.async = true;
script.onload = () => {
window.kakao.maps.load(async () => {
const map = new window.kakao.maps.Map(document.getElementById('map'), {
center: new window.kakao.maps.LatLng(37.544, 127.036),
level: 5
});

    const res = await axios.get(`/api/path?from=${fromPlaceId}&to=${toPlaceId}`);
    const lines = res.data;

    let total = 0;
    const allCoords = [];

    lines.forEach(wkt => {
      const coords = parseLineString(wkt);
      allCoords.push(...coords);

      const polyline = new window.kakao.maps.Polyline({
        path: coords,
        strokeWeight: 4,
        strokeColor: '#0077FF',
        strokeOpacity: 0.8,
        strokeStyle: 'solid'
      });
      polyline.setMap(map);
      total += calcPolylineLength(polyline);
    });

    setTotalDistance(total);
    if (allCoords.length > 0) {
      map.setCenter(allCoords[0]);
    }
  });
};
document.head.appendChild(script);

}, [fromPlaceId, toPlaceId]);

const parseLineString = (wkt) => {
return wkt
.replace("LINESTRING(", "")
.replace(")", "")
.split(",")
.map(pair => {
const [x, y] = pair.trim().split(" ");
return new window.kakao.maps.LatLng(parseFloat(y), parseFloat(x));
});
};

const calcPolylineLength = (polyline) => {
const path = polyline.getPath();
let dist = 0;
for (let i = 0; i < path.length - 1; i++) {
dist += window.kakao.maps.geometry.getDistance(path[i], path[i + 1]);
}
return dist; // meters
};

const km = (totalDistance / 1000).toFixed(2);
const minutes = Math.ceil((totalDistance / 1000) / 4.5 * 60); // 4.5km/h → 분 단위

return (

<div>
  <div id="map" style={{ width: '100%', height: '400px' }}></div>
  <p style={{ marginTop: 12, fontWeight: 600 }}>
    총 거리: {km}km / 예상 소요 시간: 약 {minutes}분
  </p>
</div>

);
};

export default PathMap;

✅ 결과 예시 (실행 시)

총 거리: 1.85km / 예상 소요 시간: 약 25분

추가 확장 가능 기능

기능 설명
각 구간별 거리 장소 간 거리도 표시 가능 (1→2, 2→3…)
애니메이션 이동 선을 따라 움직이는 마커 연출
시간대별 속도 반영 출근/야간 시간대별 소요시간 보정

원하시면 그 중 하나도 구현해드릴게요!

훌륭한 선택이에요! 이제 시간대별 도보 속도 보정 기능을 반영해
경로 예상 소요 시간을 더욱 정밀하게 계산해보겠습니다.

✅ 목표
• 시간대에 따라 평균 도보 속도 변경
• 출근/퇴근 시간: 혼잡도 고려 → 속도 저하
• 낮 시간대: 기본 속도 유지
• 저녁 산책 시간대: 속도 약간 저하

✅ 시간대별 도보 속도 설정 예시

시간대 도보 속도 (km/h) 설명
06:00~09:00 3.8 출근길 혼잡 (속도↓)
09:00~17:00 4.5 기본 속도
17:00~20:00 4.0 퇴근길 + 산책 겸용
20:00~24:00 3.7 저녁 산책 (느리게 걷는 경향)
00:00~06:00 3.5 느린 이동 or 보행 적음

✅ React 계산 함수에 시간대별 속도 반영

const getSpeedByHour = () => {
const hour = new Date().getHours();
if (hour >= 6 && hour < 9) return 3.8;
if (hour >= 9 && hour < 17) return 4.5;
if (hour >= 17 && hour < 20) return 4.0;
if (hour >= 20 && hour < 24) return 3.7;
return 3.5;
};

✅ 적용 방식 (기존 minutes 계산식 대체)

const speed = getSpeedByHour(); // km/h
const minutes = Math.ceil((totalDistance / 1000) / speed * 60);

✅ 출력 예시

총 거리: 1.92km / 예상 소요 시간: 약 30분
(현재 시간 {speed}km/h 기준 계산됨)

이제 사용자의 시간대에 따라 더 현실적인 소요시간을 제공할 수 있습니다.
원하시면 날씨/온도 기반 보정, 성별/나이 기반 보정도 이어서 확장 가능합니다!
확장할까요?

profile
@mgkick

0개의 댓글