DRT 자유노선배차 생성 흐름

iksanDrT · wonju · goyangDrT — jsprit 기반 배차 최적화 비교

1단계 : 배차 요청
📞
시작
승객이 예약을 요청합니다
콜센터 관리자가 승객의 출발지, 도착지, 탑승 시간, 인원을 시스템에 입력합니다.
개발자 상세
POST /reserve/insertReserve → ReserveC → ReserveS.insertReserve() → AllocS.unfixedRouteService(allocId)
2단계 : 배차에 필요한 정보 수집
🚗
차량 정보
투입 가능한 차량 목록을 불러옵니다
각 차량의 최대 탑승 인원, 현재 위치(차고지), 차량 유형 정보를 데이터베이스에서 가져옵니다.
iksan은 여러 대의 차량여러 차량 유형을 동시에 처리합니다.
개발자 상세
Step 6: get_vehicletype() → costPerTime[], costPerDist[], carCapacity[] / Step 7: get_vehicle() → carName[], car_vehtype[], carloc[]
👤
승객/예약 정보
배차 대상 승객 목록을 불러옵니다
각 승객의 출발지, 도착지, 인원수, 희망 승차/하차 시간대를 가져옵니다.
시간대는 4가지 조합이 가능합니다: 승차시간만, 하차시간만, 둘다, 또는 하차만(승차없이)
개발자 상세
Step 8-9: get_shipment() + get_Project_shipment() → passenger_index[], pickup/delivery time windows, sizeDimension[], pickup/delivery loc[]
3단계 : 도로 네트워크에서 이동 비용 계산
🗺
pgRouting
모든 정류장 사이의 이동 거리와 시간을 계산합니다
실제 도로 지도 위에서 정류장 A에서 B까지 몇 km, 몇 분인지를 모든 조합에 대해 미리 계산합니다.
일방통행 등을 고려하여 A→B와 B→A의 비용이 다를 수 있습니다.
이 결과가 "비용 행렬"이 되어 다음 단계에서 jsprit에 전달됩니다.
개발자 상세
Step 3-5: get_matrixcnt() → get_loc_position() → get_matrix() / PostgreSQL pgr_withPointsCostMatrix() 사용 / VehicleRoutingTransportCostsMatrix.Builder.newInstance(false) — 비대칭
4단계 : jsprit 최적화 엔진에 문제 입력
jsprit 설정
최적화 문제를 구성합니다
jsprit 엔진에 다음 정보를 전달합니다:
🚗 차량 정보 — 각 차량의 좌석 수, 출발 위치, 거리/시간 비용 가중치
👤 승객 정보 — 각 예약의 승차지, 하차지, 인원수, 탑승 소요시간, 희망 시간대
🗺 이동 비용표 — 모든 정류장 간 거리/시간 (pgRouting에서 계산)
🔧 제약 조건 — 차량 수 제한(보유 차량만 사용), 반드시 승차 먼저 → 하차
개발자 상세
VehicleTypeImpl.Builder: capacityDimension(0), costPerDistance, costPerTime / VehicleImpl.Builder: type, startLocation / Shipment.Builder: pickupLocation, deliveryLocation, sizeDimension, serviceTime, timeWindows (4 cases) / VehicleRoutingProblem.Builder: FleetSize.FINITE, setRoutingCost(costMatrix)
5단계 : 최적 경로 탐색
🧠
알고리즘 실행
jsprit이 가장 효율적인 배차 경로를 찾습니다
"어떤 차량이, 어떤 순서로, 어떤 승객을 태우고 내려줄지"를 자동으로 결정합니다.
목표: 총 이동거리 + 총 이동시간을 최소화
방법: 여러 경로 조합을 반복적으로 시도하며 점점 더 나은 해를 찾는 방식 (LNS 알고리즘)
설정: 기본값 사용 (반복 횟수 미지정)
개발자 상세
Jsprit.createAlgorithm(problem) → 기본 LNS / algorithm.searchSolutions() → Solutions.bestOf(solutions)
6단계 : 결과 저장
💾
결과 처리
최적화 결과를 데이터베이스에 저장합니다
각 차량별로 "몇 번 차량이 → A정류장에서 김OO 승차 → B정류장에서 박OO 승차 → C정류장에서 김OO 하차 → ..." 형태의 운행 일정이 생성됩니다.
각 정류장의 예상 도착 시간순서가 함께 저장됩니다.
개발자 상세
bestSolution.getRoutes() → VehicleRoute → TourActivity 순회 / insResultData → project_result 테이블 / make_unfixed_alc() 스토어드 프로시저로 최종 확정
특수 기능 : 중간 배차
🔄
중간 배차
운행 도중 새 예약이 들어오면 경로를 다시 계산합니다
이미 탑승한 승객은 제외하고, 나머지 승객 + 새 승객을 포함하여 jsprit을 다시 실행합니다.
위 2~6단계를 동일하게 반복하되, 별도 데이터 테이블(_middle)을 사용합니다.
1단계 : 자동 배차 실행
시작 (자동)
매 1분마다 자동으로 배차 대상을 확인합니다
시스템이 매분 자동으로 "지금 배차해야 할 건이 있는지" 확인합니다.
배차 시간이 된 예약건이 있으면 자동으로 최적화를 시작합니다.
(iksan과 달리 사람이 수동으로 실행하지 않음)
개발자 상세
@Scheduled(cron = "0 * * * * *") → Scheduler.unfixed_route_alc_scheduler() → RouteService.unfixedRouteService(alc_id)
2단계 : 배차에 필요한 정보 수집
🚗
차량 정보
투입 가능한 차량 목록을 불러옵니다
여러 대의 차량여러 차량 유형의 좌석 수, 위치, 비용 가중치를 가져옵니다.
(iksan과 동일한 구조)
👤
승객/예약 정보
배차 대상 승객 목록을 불러옵니다
출발지, 도착지, 인원수, 희망 시간대 (iksan과 동일한 4가지 시간대 조합)
3단계 : 이동 비용 계산
🗺
pgRouting
정류장 간 이동 거리와 시간을 계산합니다
iksan과 동일한 방식으로 실제 도로 기반 이동 비용을 계산합니다.
(30km/h 가정 시간 변환)
4단계 : jsprit에 문제 입력
jsprit 설정
최적화 문제를 구성합니다
차량 정보 + 승객 정보 + 이동 비용표 + 제약 조건
(iksan과 동일한 구조)
5단계 : 최적 경로 탐색
🧠
알고리즘 실행
jsprit이 가장 효율적인 배차 경로를 찾습니다
기본 설정 사용 (iksan과 동일)
6단계 : 결과 저장
💾
결과 처리
최적화 결과를 저장합니다
각 차량의 운행 경로, 예상 도착 시간, 순서를 데이터베이스에 저장합니다.
특수 기능 : SMS 알림
📩
알림
배차 실패 시 승객에게 SMS를 보냅니다
차량이 부족하거나 시간대가 맞지 않아 배정이 안 된 승객에게 SMS 문자로 알려줍니다.
(iksan에는 없는 wonju만의 기능)
1단계 : 배차 요청 (2가지 방법)
💻
방법 1: 수동 실행
관리자가 특정 날짜의 배차를 수동 실행합니다
관리 화면에서 날짜를 선택하고 "자동배차" 버튼을 누르면 해당 날짜의 모든 FLEX 배차건을 한번에 처리합니다.
개발자 상세
POST /api/dispatch/auto → DispatchController → DispatchService.autoDispatch(date)
방법 2: 자동 실행
마감 시간이 되면 자동으로 배차됩니다
매분 확인하여 예약 마감 시간에 도달한 배차건을 자동 실행합니다.
개발자 상세
@Scheduled(cron = "0 * * * * *") → VrpScheduler.autoOptimizeFlexDispatches()
2단계 : 배차에 필요한 정보 수집
🚗
차량 + 승객 정보
차량 1대와 예약 승객 목록을 불러옵니다
goyang은 배차건 당 차량 1대를 사용합니다.
차량의 좌석 수, 출발 위치와 각 승객의 승차지/하차지, 인원수를 가져옵니다.
별도 설정 테이블에서 거리:시간 비용 비율(0.7:0.3), 정차 시간(2분), 알고리즘 반복 횟수(2000) 등을 로드합니다.
개발자 상세
buildVrpInput(dispatchId) → VrpVehicleInfo + List<VrpShipmentInfo> / loadVrpConfig() → vrp_config 테이블 (cost_per_time:0.3, cost_per_distance:0.7, default_service_time:2.0, max_iterations:2000)
3단계 : 이동 비용 계산
🗺
pgRouting (회전제한 반영)
정류장 간 이동 비용을 계산합니다 (좌회전 금지 등 반영)
iksan/wonju와 달리 좌회전 금지, U턴 금지 같은 회전 제한까지 반영합니다.
도심(고양시)에서 더 현실적인 경로 비용을 산출합니다.
도달 불가능한 구간에는 매우 큰 페널티(999999)를 부여하여 해당 경로를 회피하게 합니다.
개발자 상세
PgRoutingCostMatrixProvider.calculateCostMatrix() / pgr_trsp() + v_turn_restriction 테이블 / 누락 쌍: 999999.0 할당
4단계 : jsprit에 문제 입력
jsprit 설정
최적화 문제를 구성합니다
iksan/wonju와의 차이점:
✓ 차량 1대, 단일 유형만 사용
시간대(TimeWindow) 미사용 — 시간 제약 없이 비용 최소화만 추구
✓ 정차 시간은 설정 테이블에서 고정값(2분) 사용
✓ 차고지 복귀 여부(returnToDepot) 설정 가능
개발자 상세
JspritVrpSolver.solve() / VehicleType: capacity, costPerTime:0.3, costPerDist:0.7 / Shipment: no TimeWindow, serviceTime:2.0 / FleetSize.FINITE, setReturnToDepot(config)
5단계 : 최적 경로 탐색
🧠
알고리즘 실행
jsprit이 2,000번 반복하며 최적 경로를 찾습니다
iksan/wonju와 달리 반복 횟수를 2,000회로 명시 설정합니다.
반복이 많을수록 더 좋은 경로를 찾을 수 있지만 시간이 더 걸립니다.
이 설정값은 관리 DB에서 변경 가능합니다.
개발자 상세
Jsprit.Builder.newInstance(problem).setProperty(ITERATIONS, "2000").buildAlgorithm() / searchSolutions() → bestOf()
6단계 : 결과 저장 (다단계)
📋
정류장 단위 정리
노드 결과를 정류장 단위로 묶어 정리합니다
같은 정류장에서의 활동들을 합쳐서 "A정류장: 2명 승차, 1명 하차" 형태로 만듭니다.
각 정류장의 예상 도착 시간을 계산합니다.
🛰
경로 시각화 데이터
운전자 앱에 표시할 경로 지도 데이터를 생성합니다
연속된 정류장 사이의 실제 도로 경로 형상(geometry)을 저장합니다.
운전자 앱에서 지도 위에 경로를 그려서 보여주기 위한 데이터입니다.
(iksan/wonju에는 없는 goyang만의 기능)
상태 업데이트
예약 상태를 확정/취소로 변경합니다
배정된 예약: 확정(CONFIRMED) / 미배정 예약: 취소(CANCELED) / 배차 상태: 배차완료(DISPATCHED)
특수 기능
🔔
운전자 앱 알림 (MQTT)
배차 완료 시 운전자 앱에 실시간 알림을 보냅니다
SMS가 아닌 앱 푸시 알림 방식으로, 운전자가 즉시 새 배차 정보를 확인할 수 있습니다.
🔄
중간 삽입 (Cheapest Insertion)
운행 중 새 예약이 들어오면 기존 경로에 끼워넣습니다
iksan처럼 jsprit을 다시 실행하지 않고, 자체 알고리즘으로 빠르게 처리합니다.
기존 경로에서 "어디에 끼워넣으면 추가 비용이 가장 적은지" 모든 위치를 테스트합니다.
추가 비용이 허용 범위 이내면 수락, 초과하면 거절합니다.

3개 프로젝트 비교 요약

iksanDrT (익산)

  • 콜센터 관리자가 수동으로 예약 입력 시 배차
  • 한번에 여러 대의 차량에 배정
  • 승객의 희망 시간대를 반영 (4가지 조합)
  • 운행 중 새 예약 시 jsprit을 다시 실행
  • 알림 기능 없음

wonju (원주)

  • 매분 자동으로 배차 대상을 확인하고 실행
  • 한번에 여러 대의 차량에 배정
  • 승객의 희망 시간대를 반영 (4가지 조합)
  • 중간 배차 기능 없음
  • 배차 실패 시 SMS 문자 알림

goyangDrT (고양)

  • 수동 + 자동 이중 실행 방식
  • 배차건 당 차량 1대에 배정
  • 시간대 제약 없음 (비용 최소화만 추구)
  • 중간 삽입은 자체 알고리즘 사용 (더 빠름)
  • 운전자 앱 알림 + 경로 지도 제공

상세 비교표

항목iksanDrTwonjugoyangDrT
배차 실행 방식예약 입력 시 수동매분 자동 (스케줄러)수동 + 자동 (이중)
동시 차량 수여러 대여러 대1대 (배차건 당)
차량 유형여러 유형여러 유형단일 유형
시간대 제약있음 (4가지)있음 (4가지)없음
비용 가중치 설정차량 유형별 DB 설정차량 유형별 DB 설정설정 테이블 (거리70:시간30)
알고리즘 반복 횟수기본값 (미지정)기본값 (미지정)2,000회 (설정 가능)
도로 비용 계산pgRouting (기본)pgRouting (기본)pgRouting (회전제한 반영)
중간 배차jsprit 재실행없음자체 알고리즘 (빠름)
알림없음SMS (실패 시)앱 푸시 (MQTT)
경로 지도 데이터없음없음있음 (운전자 앱용)
예약 상태 관리스토어드 프로시저스토어드 프로시저확정/취소 직접 업데이트
코드 구조단일 클래스 (절차적)단일 클래스 (절차적)역할별 분리 (모듈화)

jsprit에 입력하는 핵심 변수들

jsprit은 "차량 경로 최적화 엔진"입니다. 아래 변수들을 입력하면, 가장 효율적인 배차 경로를 계산해줍니다.

🚗 차량 유형 설정

설정 항목쉬운 설명익산원주고양
좌석 수
capacityDimension
차량에 최대 몇 명까지 탈 수 있는지. 이 숫자를 넘으면 더 태울 수 없음 차량별 다름 (DB)차량별 다름 (DB)차량별 다름 (DB)
거리 비용 가중치
costPerDistance
이 값이 클수록 "총 이동거리를 줄이는 것"을 우선시함.
예: 0.7이면 거리 절감에 70% 비중
차량 유형별 DB 설정
(예: 소형 0.6, 대형 0.7)
차량 유형별 DB 설정
(예: 소형 0.6, 대형 0.7)
0.7 (고정)
시간 비용 가중치
costPerTime
이 값이 클수록 "총 이동시간을 줄이는 것"을 우선시함.
예: 0.3이면 시간 절감에 30% 비중
차량 유형별 DB 설정
(예: 소형 0.4, 대형 0.3)
차량 유형별 DB 설정
(예: 소형 0.4, 대형 0.3)
0.3 (고정)
거리 vs 시간 가중치: 두 값의 비율이 최적화 방향을 결정합니다. 고양(0.7:0.3)은 "가급적 짧은 거리로 돌자"에 70%, "빨리 돌자"에 30% 비중을 둡니다. 익산/원주는 차량 유형별로 이 비율을 DB에서 다르게 설정할 수 있습니다 (예: 소형버스는 시간 우선, 대형버스는 거리 우선).

🚗 개별 차량 설정

설정 항목쉬운 설명익산원주고양
차량 번호
id
결과에서 "이 승객은 몇 번 차량에 배정"인지 식별하는 이름 차량번호차량번호차량ID
출발 위치
startLocation
차량이 지금 있는 곳 (차고지). 첫 승객까지의 거리 계산에 사용 차고지 위치차고지 위치출발 노드
차고지 복귀
returnToDepot
마지막 승객 하차 후 차고지로 돌아가야 하는지 여부 항상 복귀항상 복귀설정 가능

👤 승객(예약) 설정

설정 항목쉬운 설명익산원주고양
예약 번호
id
각 예약을 구분하는 고유 번호 승객 인덱스승객 인덱스예약ID
탑승 인원
sizeDimension
이 예약으로 타는 사람 수. 승차 시 좌석을 차지하고 하차 시 비워짐 DB에서 로드DB에서 로드예약 인원수
승차 장소
pickupLocation
승객을 태우는 정류장. 반드시 하차 전에 방문해야 함 승차 노드승차 노드승차 노드
하차 장소
deliveryLocation
승객을 내려주는 정류장. 반드시 승차 후에 방문 하차 노드하차 노드하차 노드
정차 시간
serviceTime
승차/하차 시 차량이 정류장에 머무는 시간 (승객 탑승/하차 소요) 예약별 다름예약별 다름2분 (고정)
희망 승차 시간대
pickupTimeWindow
"이 시간~저 시간 사이에 태워주세요" — 이 범위 밖에 도착하면 안 됨 있음있음없음
희망 하차 시간대
deliveryTimeWindow
"이 시간~저 시간 사이에 도착하고 싶어요" — 도착 보장 시간대 있음있음없음
시간대(TimeWindow) 4가지 조합 (익산/원주):
1. 승차 시간대 + 하차 시간대 둘 다 지정 — "10시~10시30분에 태우고, 11시~11시30분에 내려주세요"
2. 하차 시간대만 지정 — "11시까지 도착하면 됩니다"
3. 승차 시간대만 지정 — "10시에 태워주세요"
4. 하차 시간대만 (다른 형태) — 도착 시간만 중요한 경우

고양은 시간대 제약을 사용하지 않아서, 순수하게 이동 비용 최소화만 추구합니다.

⚙ 최적화 엔진 설정

설정 항목쉬운 설명익산원주고양
차량 수 제한
fleetSize
FINITE = 실제 보유 차량만 사용. 차량이 부족하면 일부 승객이 배정되지 않을 수 있음 FINITEFINITEFINITE
비용 행렬
costMatrix
비대칭 = A→B 이동비용과 B→A 이동비용이 다름 (일방통행 등 반영) 비대칭비대칭비대칭
알고리즘 반복 횟수
iterations
경로 조합을 몇 번이나 시도할지. 많을수록 더 좋은 결과, 더 긴 시간 기본값기본값2,000회 (설정 가능)

📈 최적화 결과 (출력)

결과 항목쉬운 설명공통/차이
차량별 경로"1번 차량: A정류장 → B정류장 → C정류장 → ..." 순서3 프로젝트 공통
활동 유형각 정류장에서 "승차" 또는 "하차" 어느 활동인지3 프로젝트 공통
예상 도착 시간차량이 각 정류장에 몇 분 후 도착하는지3 프로젝트 공통
미배정 승객차량이 부족하거나 제약 조건에 맞지 않아 배정 못한 예약iksan: "no result" 기록 / goyang: "취소" 처리

pgRouting이 필요한 이유

핵심 요약: jsprit은 "어떤 순서로 방문하면 효율적인지"만 계산합니다. 하지만 "A에서 B까지 실제로 몇 km이고 몇 분 걸리는지"는 모릅니다.
실제 이동 거리/시간을 도로 지도에서 계산해주는 것이 pgRouting입니다.

비유로 이해하기

택배 기사에게 배달 순서를 짜주는 상황을 생각해보세요:

pgRouting 역할 (네비게이션): "A마트에서 B아파트까지 3.2km, 8분 / B아파트에서 C학교까지 1.5km, 4분 / ..." 모든 배달지 간 거리표를 만듦

jsprit 역할 (배달 순서 최적화): 위 거리표를 보고 "A → C → B 순서로 돌면 총 거리가 가장 짧다!"고 결정

둘을 합쳐야 비로소 실제 도로를 고려한 최적의 배차가 완성됩니다.

pgRouting → jsprit 연결 순서

1
도로 지도 데이터 준비
PostgreSQL에 저장된 실제 도로 네트워크 (교차로, 도로 구간, 거리, 속도제한 등)
2
정류장을 도로에 연결
승하차 정류장의 위치를 도로 네트워크의 가장 가까운 지점에 매핑
3
모든 정류장 쌍의 이동 비용 계산
정류장이 5개면 5x5=25개 조합의 최단 경로 거리/시간을 한번에 계산.
A→B와 B→A가 다를 수 있음 (일방통행, 좌회전 금지 등)
4
jsprit에 비용 행렬 전달
계산된 거리/시간 표를 jsprit이 이해하는 형식으로 변환하여 전달
5
jsprit이 비용표를 보고 최적 경로 탐색
jsprit은 도로를 직접 알지 못하고, 숫자(비용)만 보고 가장 효율적인 순서를 결정

프로젝트별 pgRouting 함수 비교

항목익산 / 원주고양
사용 함수pgr_withPointsCostMatrixpgr_trsp
쉬운 설명정류장 좌표에서 가장 가까운 도로를 찾아 자동으로 거리표 생성좌회전 금지, U턴 금지 같은 회전 제한까지 반영하여 더 현실적인 거리 계산
왜 이 함수를?비교적 단순한 도로 환경 (농촌/소도시)도심(고양시)에서 회전제한이 많아 더 정확한 경로가 필요
도달 불가 처리오류 발생매우 큰 비용(999999) 부여 → 알고리즘이 자연스럽게 회피
경로 시각화없음있음 — 운전자 앱에 지도로 경로를 보여주기 위한 geometry 데이터도 별도 생성