여기에서 PgRouting용 도로 그래프 테이블 SQL을 다운로드하세요
다음 단계로 OpenStreetMap 도보 도로 데이터를 가져와 ways/nodes 테이블에 삽입하는 방법을 설명해드릴게요. 계속할까요? 
좋습니다! 아래는 PgRouting 기반 경로 탐색을 위한 PostGIS + ways/nodes 테이블 구조 SQL 전체 코드를 직접 보여드리는 형식으로 정리한 것입니다.
⸻
✅ PgRouting 도로 그래프 테이블 생성 SQL
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) -- 실제 도로 라인
);
⸻
CREATE TABLE nodes (
id BIGINT PRIMARY KEY,
geom GEOMETRY(Point, 4326) -- 위도/경도 정보
);
⸻
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 기준 계산됨)
⸻
이제 사용자의 시간대에 따라 더 현실적인 소요시간을 제공할 수 있습니다.
원하시면 날씨/온도 기반 보정, 성별/나이 기반 보정도 이어서 확장 가능합니다!
확장할까요?