学习需求
matsim中动态线路场景模拟中核心的是三个功能:
1、拼车,生成新的插入点
2、生成接乘客上车的任务
3、生成送乘客下车的任务
本次学习第2个功能:接乘客上车的任务
学习笔记
接乘客上车在matsim中的代码是在扩展包
org.matsim.contrib.drt.scheduler
今天学习默认的调度器
DefaultRequestInsertionScheduler
接乘客上车的方法
var pickupTask = insertPickup(request, insertion);
下面是对insertPickUp方法的代码分析
insertPickUp
输入参数:
request:当前应答成功的订单请求
insertion:当前应答成功的订单的插入对象
输出结果
drtStopTask:接当前乘客的停车任务
源代码:
//用于在DVRP或DRT系统中安排一个上客任务(返回接乘客上车前的stopTask,即停车接乘客上车的任务)
DrtStopTask insertPickup(AcceptedDrtRequest request, InsertionWithDetourData insertionWithDetourData) {
//当前订单的插入点,包含在哪上车,在哪下车
var insertion = insertionWithDetourData.insertion;
//当前订单的车辆
VehicleEntry vehicleEntry = insertion.vehicleEntry;
//当前订单的车辆的调度
Schedule schedule = vehicleEntry.vehicle.getSchedule();
//当前车辆的停靠点
List<Waypoint.Stop> stops = vehicleEntry.stops;
int pickupIdx = insertion.pickup.index;
int dropoffIdx = insertion.dropoff.index;
//绕路信息
var detourData = insertionWithDetourData.detourData;
//根据行程的当前状态scheduleStatus,确定是否需要创建新的上客任务
ScheduleStatus scheduleStatus = schedule.getStatus();
Task currentTask = scheduleStatus == ScheduleStatus.PLANNED ? null : schedule.getCurrentTask();
//接乘客之前的任务
Task beforePickupTask;
//***************处理已经开始的行程****************************//
//如果行程已经开始并且当前任务是上客或下客任务,
//找当前路径的改道点,如果找到改道点,重新规划路径
//如果没有找到改造点,则会创建一个新的行驶任务。
if (pickupIdx == 0 && scheduleStatus != ScheduleStatus.PLANNED && DRIVE.isBaseTypeOf(currentTask)) {
LinkTimePair diversion = ((OnlineDriveTaskTracker)currentTask.getTaskTracker()).getDiversionPoint();
if (diversion != null) { // divert vehicle
beforePickupTask = currentTask;
VrpPathWithTravelData vrpPath = VrpPaths.createPath(vehicleEntry.start.link, request.getFromLink(),
vehicleEntry.start.time, detourData.detourToPickup, travelTime);
((OnlineDriveTaskTracker)beforePickupTask.getTaskTracker()).divertPath(vrpPath);
} else { // too late for diversion
if (request.getFromLink() != vehicleEntry.start.link) { // add a new drive task
VrpPathWithTravelData vrpPath = VrpPaths.createPath(vehicleEntry.start.link, request.getFromLink(),
vehicleEntry.start.time, detourData.detourToPickup, travelTime);
beforePickupTask = taskFactory.createDriveTask(vehicleEntry.vehicle, vrpPath, DrtDriveTask.TYPE);
schedule.addTask(currentTask.getTaskIdx() + 1, beforePickupTask);
} else { // no need for a new drive task
beforePickupTask = currentTask;
}
}
} else { // 如果行程已经开始并且当前任务是停留任务,会根据当前时间和请求的最早开始时间调整停留任务的结束时间
DrtStayTask stayTask = null;
DrtStopTask stopTask = null;
if (pickupIdx == 0) {
if (scheduleStatus == ScheduleStatus.PLANNED) {// PLANNED 表示车辆当前没有进行中的任务
//从行程中获取第一个停留任务(DrtStayTask),初始化时的stay任务
stayTask = (DrtStayTask)schedule.getTasks().get(0);
//将停留任务的结束时间设置为其开始时间,这意味着停留任务将立即结束
stayTask.setEndTime(stayTask.getBeginTime());
} else if (STAY.isBaseTypeOf(currentTask)) {//中间过程中的持续进行的stay任务
stayTask = (DrtStayTask)currentTask;
double now = timer.getTimeOfDay();
//如果停留任务的结束时间大于当前时间,将停留任务的结束时间设置为当前时间,这样新的任务就可以插入。
if (stayTask.getEndTime() > now) {
stayTask.setEndTime(now);
}
} else {//处理正在进行中的停车任务
stopTask = (DrtStopTask)currentTask;
}
} else {//处理非第一个任务的上客
stopTask = stops.get(pickupIdx - 1).task;
}
if (stopTask != null && request.getFromLink() == stopTask.getLink()) { // no detour; no new stop task
// add pickup request to stop task
stopTask.addPickupRequest(request);
double stopDuration = stopDurationEstimator.calcDuration(vehicleEntry.vehicle, stopTask.getDropoffRequests().values(), stopTask.getPickupRequests().values());
stopTask.setEndTime(Math.max(stopTask.getBeginTime() + stopDuration, request.getEarliestStartTime()));
if (pickupIdx == dropoffIdx) {
// remove drive i->i+1 (if there is one)
if (pickupIdx < stops.size()) {// there is at least one following stop
DrtStopTask nextStopTask = stops.get(pickupIdx).task;
if (stopTask.getTaskIdx() + 2 != nextStopTask.getTaskIdx()) {// there must a drive task in
// between
throw new RuntimeException();
}
if (stopTask.getTaskIdx() + 2 == nextStopTask.getTaskIdx()) {// there must a drive task in
// between
int driveTaskIdx = stopTask.getTaskIdx() + 1;
schedule.removeTask(schedule.getTasks().get(driveTaskIdx));
}
}
Link toLink = request.getToLink(); // pickup->dropoff
VrpPathWithTravelData vrpPath = VrpPaths.createPath(request.getFromLink(), toLink,
stopTask.getEndTime(), detourData.detourFromPickup, travelTime);
Task driveFromPickupTask = taskFactory.createDriveTask(vehicleEntry.vehicle, vrpPath,
DrtDriveTask.TYPE);
schedule.addTask(stopTask.getTaskIdx() + 1, driveFromPickupTask);
// 更新后续任务的开始和结束时间
scheduleTimingUpdater.updateTimingsStartingFromTaskIdx(vehicleEntry.vehicle,
stopTask.getTaskIdx() + 2, driveFromPickupTask.getEndTime());
}
return stopTask;
} else {
StayTask stayOrStopTask = stayTask != null ? stayTask : stopTask;
//检查是否有后续停车点
// remove drive i->i+1 (if there is one)
if (pickupIdx < stops.size()) {// there is at least one following stop
//获取下一个停车任务
DrtStopTask nextStopTask = stops.get(pickupIdx).task;
// check: if there is at most one drive task in between
if (stayOrStopTask.getTaskIdx() + 2 != nextStopTask.getTaskIdx() //
&& stayTask != null && stayTask.getTaskIdx() + 1 != nextStopTask.getTaskIdx()) {
throw new RuntimeException();
}
//检查是否有行驶任务需要移除
if (stayOrStopTask.getTaskIdx() + 2 == nextStopTask.getTaskIdx()) {
// removing the drive task that is in between
int driveTaskIdx = stayOrStopTask.getTaskIdx() + 1;
//移除中间的行驶任务
schedule.removeTask(schedule.getTasks().get(driveTaskIdx));
}
}
//处理两种情况:一种是车辆已经在上客点(无需额外行驶),另一种是车辆需要行驶到上客点。
if (stayTask != null && request.getFromLink() == stayTask.getLink()) {
// the bus stays where it is
beforePickupTask = stayTask;
} else {// add drive task to pickup location
// insert drive i->pickup
// 需要创建一个新的行驶任务以将车辆从当前位置行驶到上客点
VrpPathWithTravelData vrpPath = VrpPaths.createPath(stayOrStopTask.getLink(), request.getFromLink(),
stayOrStopTask.getEndTime(), detourData.detourToPickup, travelTime);
beforePickupTask = taskFactory.createDriveTask(vehicleEntry.vehicle, vrpPath, DrtDriveTask.TYPE);
schedule.addTask(stayOrStopTask.getTaskIdx() + 1, beforePickupTask);
}
}
}
// insert pickup stop task
double startTime = beforePickupTask.getEndTime();
int taskIdx = beforePickupTask.getTaskIdx() + 1;
double stopDuration = stopDurationEstimator.calcDuration(vehicleEntry.vehicle, Collections.emptySet(), Collections.singleton(request));
DrtStopTask pickupStopTask = taskFactory.createStopTask(vehicleEntry.vehicle, startTime,
Math.max(startTime + stopDuration, request.getEarliestStartTime()), request.getFromLink());
schedule.addTask(taskIdx, pickupStopTask);
pickupStopTask.addPickupRequest(request);
// add drive from pickup
Link toLink = pickupIdx == dropoffIdx ? request.getToLink() // pickup->dropoff
: stops.get(pickupIdx).task.getLink(); // pickup->i+1
VrpPathWithTravelData vrpPath = VrpPaths.createPath(request.getFromLink(), toLink, pickupStopTask.getEndTime(),
detourData.detourFromPickup, travelTime);
Task driveFromPickupTask = taskFactory.createDriveTask(vehicleEntry.vehicle, vrpPath, DrtDriveTask.TYPE);
schedule.addTask(taskIdx + 1, driveFromPickupTask);
// update timings
scheduleTimingUpdater.updateTimingsStartingFromTaskIdx(vehicleEntry.vehicle, taskIdx + 2,
driveFromPickupTask.getEndTime());
return pickupStopTask;
}
代码的整体逻辑
insertPickup用于在DVRP或DRT系统中安排一个上客任务。这个方法相当复杂,涉及多个步骤和条件判断,以下是它的逻辑概述:
上客逻辑
-
提取参数:从方法参数中提取插入数据
insertionWithDetourData
和请求request
。 -
获取车辆信息:从插入数据中获取
VehicleEntry
对象,进而获取车辆的行程schedule
和停车点列表stops
。 -
确定上客索引和绕路数据:从插入数据中获取上客的索引
pickupIdx
和绕路数据detourData
。 -
检查行程状态:根据行程的当前状态
scheduleStatus
,确定是否需要创建新的上客任务。 -
处理已经开始的行程:
- 如果行程已经开始并且当前任务是上客或下客任务,并且车辆可以改道,那么会创建一个改道任务。
- 如果行程已经开始并且当前任务是停留任务,会根据当前时间和请求的最早开始时间调整停留任务的结束时间。
-
创建上客任务:
- 如果上客点与当前任务或停留任务的地点相同,不需要创建新的上客任务,而是将请求添加到现有任务。
- 如果需要创建新的上客任务,会计算开始时间并使用
taskFactory
创建一个新的DrtStopTask
。
-
添加上客请求:将接受的DRT请求添加到相应的上客任务。
-
创建从上客点出发的行驶任务:计算从上客点到下一个目的地的路径,并创建一个新的行驶任务。
-
更新行程:将新创建的上客任务和行驶任务添加到行程中。
-
更新时间:调用
scheduleTimingUpdater
更新行程中的任务时间。 -
返回结果:方法返回创建的
DrtStopTask
上客任务。关键点解释:
AcceptedDrtRequest
:代表已接受的DRT请求。InsertionWithDetourData
:包含有关任务插入和绕路时间的数据结构。VehicleEntry
:包含车辆信息和停车点列表的数据结构。Schedule
:代表车辆行程的类。Task
:代表行程中的任务,可以是行驶任务、停留任务或上/下客任务。DrtStopTask
:特定类型的任务,代表DRT中的上客或下客任务。taskFactory
:用于创建任务的工厂类。scheduleTimingUpdater
:用于更新行程时间的类。
这段代码展示了在DRT系统中如何根据当前车辆状态和请求要求,安排新的上客任务,并相应地更新车辆的行程。代码中包含了多个条件分支和异常处理,确保了在不同情况下都能正确安排任务。