package com.opendoorlogistics.components.jsprit;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import com.graphhopper.jsprit.core.analysis.SolutionAnalyser;
import com.graphhopper.jsprit.core.problem.Capacity;
import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.cost.TransportDistance;
import com.graphhopper.jsprit.core.problem.job.Delivery;
import com.graphhopper.jsprit.core.problem.job.Job;
import com.graphhopper.jsprit.core.problem.job.Pickup;
import com.graphhopper.jsprit.core.problem.job.Service;
import com.graphhopper.jsprit.core.problem.job.Shipment;
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
import com.graphhopper.jsprit.core.problem.solution.route.VehicleRoute;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity.JobActivity;
import com.graphhopper.jsprit.core.problem.vehicle.Vehicle;
import com.opendoorlogistics.api.ODLApi;
import com.opendoorlogistics.api.components.ComponentExecutionApi;
import com.opendoorlogistics.api.geometry.LatLong;
import com.opendoorlogistics.api.geometry.ODLGeom;
import com.opendoorlogistics.api.tables.ODLDatastore;
import com.opendoorlogistics.api.tables.ODLTable;
import com.opendoorlogistics.api.tables.ODLTableReadOnly;
import com.opendoorlogistics.api.tables.ODLTime;
import com.opendoorlogistics.components.jsprit.VRPBuilder.BuiltStopRec;
import com.opendoorlogistics.components.jsprit.VRPBuilder.TravelCostType;
import com.opendoorlogistics.components.jsprit.VRPConfig.BooleanOptions;
import com.opendoorlogistics.components.jsprit.solution.RouteDetail;
import com.opendoorlogistics.components.jsprit.solution.SolutionDetail;
import com.opendoorlogistics.components.jsprit.solution.StopDetail;
import com.opendoorlogistics.components.jsprit.tabledefinitions.InputTablesDfn;
import com.opendoorlogistics.components.jsprit.tabledefinitions.StopsTableDefn;
import com.opendoorlogistics.components.jsprit.tabledefinitions.VehiclesTableDfn.CostType;
import com.opendoorlogistics.components.jsprit.tabledefinitions.VehiclesTableDfn.RowVehicleIndex;
public class CalculateRouteDetailsV2 {
private final ComponentExecutionApi componentApi;
private final ODLApi odlApi;
private final InputTablesDfn dfn;
private final VRPConfig config;
private final ODLTableReadOnly stopsTable;
private final ODLTableReadOnly vehiclesTable;
private final ODLTableReadOnly stopOrderTable;
private final VRPBuilder builtProblem;
// private final Map<String, Integer> stopIdMap;
private final Map<String, RouteDetail> vehicleIdToRouteDetails;
private final Map<String,StopDetail> loadedStopsByStopId;
private final VehicleRoutingProblemSolution jspritSol;
private final SolutionAnalyser jspritSA;
private final SolutionDetail sd;
public CalculateRouteDetailsV2(VRPConfig conf, ComponentExecutionApi api, ODLDatastore<? extends ODLTable> ioDb) {
componentApi = api;
odlApi = api.getApi();
dfn = new InputTablesDfn(api.getApi(), conf);
config = conf;
stopsTable = ioDb.getTableByImmutableId(dfn.stops.tableId);
vehiclesTable = ioDb.getTableByImmutableId(dfn.vehicles.tableId);
stopOrderTable = ioDb.getTableByImmutableId(dfn.stopOrder.tableId);
sd = new SolutionDetail(config.getNbQuantities());
// build map of route details
vehicleIdToRouteDetails = buildEmptyRouteDetails();
// build VRP
builtProblem = buildVRPProblem(conf, api, dfn, ioDb);
// get the empty (i.e. without stats) stop objects
loadedStopsByStopId = buildEmptyStopDetails();
// add stops for the depots
buildDepotStops();
// build jsprit solution using the built problem etc
Map.Entry<VehicleRoutingProblemSolution, SolutionAnalyser> tmp = buildJspritSolution();
jspritSol = tmp.getKey();
jspritSA = tmp.getValue();
fillInStopStats();
fillInRouteStats();
fillInSolutionStats();
}
private void fillInStopStats() {
for (final RouteDetail routeDetail : vehicleIdToRouteDetails.values()) {
VehicleRoute vr = routeDetail.temp.jspritRoute;
int n = routeDetail.stops.size();
for (int i = 0; i < n; i++) {
StopDetail stopDetail = routeDetail.stops.get(i);
TourActivity ta = stopDetail.temporary.jspritTourActivity;
if (ta == null) {
continue;
}
// times
stopDetail.arrivalTime = ta.getArrTime();
stopDetail.leaveTime = ta.getEndTime();
stopDetail.waitingTime = jspritSA.getWaitingTimeAtActivity(ta, vr);
stopDetail.timeWindowViolation = jspritSA.getTimeWindowViolationAtActivity(ta, vr);
// quantities
Capacity before = jspritSA.getLoadJustBeforeActivity(ta, vr);
copyQuantities(before, stopDetail.arrivalQuantities);
Capacity violationBefore = Capacity.max(Capacity.Builder.newInstance().build(),
Capacity.subtract(before, routeDetail.temp.jspritVehicle.getType().getCapacityDimensions()));
copyQuantities(violationBefore, stopDetail.arrivalCapacityViolation);
Capacity after = jspritSA.getLoadRightAfterActivity(ta, vr);
copyQuantities(after, stopDetail.leaveQuantities);
copyQuantities(Capacity.subtract(after, before), stopDetail.stopQuantities);
copyQuantities(jspritSA.getCapacityViolationAfterActivity(ta, vr), stopDetail.leaveCapacityViolation);
// check for quantity violations
for (long cv : stopDetail.leaveCapacityViolation) {
if (cv > 0) {
stopDetail.hasViolation = 1;
}
}
// travel costs
stopDetail.travelCost[TravelCostType.TIME.ordinal()] = jspritSA.getLastTransportTimeAtActivity(ta, vr);
stopDetail.travelCost[TravelCostType.DISTANCE_KM.ordinal()] = jspritSA.getLastTransportDistanceAtActivity(ta, vr);
stopDetail.travelCost[TravelCostType.COST.ordinal()] = jspritSA.getLastTransportCostAtActivity(ta, vr);
// total travel costs
stopDetail.totalTravelCost[TravelCostType.TIME.ordinal()] = jspritSA.getTransportTimeAtActivity(ta,vr);
stopDetail.totalTravelCost[TravelCostType.DISTANCE_KM.ordinal()] = jspritSA.getDistanceAtActivity(ta, vr) ;
stopDetail.totalTravelCost[TravelCostType.COST.ordinal()] = jspritSA.getVariableTransportCostsAtActivity(ta, vr);
// check for violations
if ((config.isDeliveriesBeforePickups() && jspritSA.hasBackhaulConstraintViolationAtActivity(ta, vr)) || jspritSA.hasShipmentConstraintViolationAtActivity(ta, vr)
|| jspritSA.hasSkillConstraintViolationAtActivity(ta, vr) || stopDetail.timeWindowViolation > 0) {
stopDetail.hasViolation = 1;
}
if(stopDetail.type.equals(VRPConstants.DEPOT)){
// Depot stops by default from jsprit arrive at the start depot at midnight and leave
// the end depot at their final time window, which makes the Gantt chart look odd.
// We therefore change this behaviour.
if(i==0){
stopDetail.arrivalTime = stopDetail.leaveTime;
}else{
stopDetail.leaveTime = stopDetail.arrivalTime;
}
stopDetail.waitingTime=0;
}
}
// calculate incoming and outgoing paths
for (int i = 0; i < n; i++) {
for (int j = 0; j <= 1; j++) {
boolean outgoing = j == 0;
int prevIndx = outgoing ? i : i - 1;
int nextIndx = outgoing ? i + 1 : i;
if (prevIndx >= 0 && nextIndx < n) {
LatLong prevLL = routeDetail.stops.get(prevIndx).stopLatLong;
LatLong nextLL = routeDetail.stops.get(nextIndx).stopLatLong;
if (prevLL != null && nextLL != null) {
ODLGeom geom = null;
if (config.getBool(BooleanOptions.OUTPUT_STRAIGHT_LINES_BETWEEN_STOPS)) {
geom = odlApi.geometry().createLineGeometry(prevLL, nextLL);
} else {
geom = componentApi.calculateRouteGeom(config.getDistances(), prevLL, nextLL);
}
if (outgoing) {
routeDetail.stops.get(i).outgoingPath = geom;
} else {
routeDetail.stops.get(i).incomingPath = geom;
}
}
}
}
}
}
}
private void fillInRouteStats() {
for (final RouteDetail rd : vehicleIdToRouteDetails.values()) {
VehicleRoute vr = rd.temp.jspritRoute;
rd.travelCosts[TravelCostType.DISTANCE_KM.ordinal()] = jspritSA.getDistance(vr);
rd.travelCosts[TravelCostType.TIME.ordinal()] = jspritSA.getTransportTime(vr);
rd.travelCosts[TravelCostType.COST.ordinal()] = jspritSA.getVariableTransportCosts(vr);
rd.waitingTime = jspritSA.getWaitingTime(vr);
rd.timeWindowViolation = jspritSA.getTimeWindowViolation(vr);
copyQuantities(jspritSA.getCapacityViolation(vr), rd.capacityViolation);
copyQuantities(jspritSA.getLoadDelivered(vr), rd.deliveredQuantities);
copyQuantities(jspritSA.getLoadPickedUp(vr), rd.pickedUpQuantities);
copyQuantities(jspritSA.getLoadAtBeginning(vr), rd.startQuantities);
rd.pickupsCount = jspritSA.getNumberOfPickups(vr);
rd.deliveriesCount = jspritSA.getNumberOfDeliveries(vr);
// start and end times
if(vr.getStart()!=null){
rd.startTime = vr.getStart().getEndTime();
}
if(vr.getEnd()!=null){
rd.endTime = vr.getEnd().getArrTime();
}
rd.time = rd.endTime - rd.startTime;
// update hasViolation
for (StopDetail sd : rd.stops) {
if (sd.hasViolation == 1) {
rd.hasViolation = 1;
}
}
if ((config.isDeliveriesBeforePickups() && jspritSA.hasBackhaulConstraintViolation(vr)) || jspritSA.hasShipmentConstraintViolation(vr) || jspritSA.hasSkillConstraintViolation(vr)) {
rd.hasViolation = 1;
}
}
}
private void fillInSolutionStats() {
sd.routesCount = sd.routes.size();
copyQuantities(jspritSA.getCapacityViolation(), sd.capacityViolation);
sd.travelCosts[TravelCostType.DISTANCE_KM.ordinal()] = jspritSA.getDistance();
sd.travelCosts[TravelCostType.TIME.ordinal()] = jspritSA.getTransportTime();
sd.travelCosts[TravelCostType.COST.ordinal()] = jspritSA.getVariableTransportCosts();
sd.waitingTime = jspritSA.getWaitingTime();
sd.timeWindowViolation = jspritSA.getTimeWindowViolation();
// total time
sd.time=0;
for(RouteDetail rd:sd.routes){
sd.time += rd.time;
}
copyQuantities(jspritSA.getLoadDelivered(), sd.deliveredQuantities);
copyQuantities(jspritSA.getLoadPickedUp(), sd.pickedUpQuantities);
sd.pickupsCount = jspritSA.getNumberOfPickups();
sd.deliveriesCount = jspritSA.getNumberOfDeliveries();
for (RouteDetail rd : sd.routes) {
if (rd.hasViolation == 1) {
sd.hasViolation = 1;
}
}
if ((config.isDeliveriesBeforePickups() & jspritSA.hasBackhaulConstraintViolation()) || jspritSA.hasShipmentConstraintViolation() || jspritSA.hasSkillConstraintViolation()) {
sd.hasViolation = 1;
}
// assigned and unassigned stops
sd.assignedStopsCount = loadedStopsByStopId.size();
sd.unassignedStops=0;
for(BuiltStopRec stop:builtProblem.getBuiltStops()){
if(!loadedStopsByStopId.containsKey(stop.getStopIdInStopsTable())){
sd.unassignedStops++;
}
}
}
private static void copyQuantities(Capacity from, long[] to) {
for (int i = 0; i < Math.min(from.getNuOfDimensions(), to.length); i++) {
to[i] = from.get(i);
}
}
private void buildDepotStops() {
int nq = config.getNbQuantities();
for (final RouteDetail route : vehicleIdToRouteDetails.values()) {
int vRow = route.temp.rvi.row;
// add start depot
LatLong[] ends = dfn.vehicles.getStartAndEnd(vehiclesTable, vRow);
if(ends[0]!=null){
StopDetail startDepot = new StopDetail(nq);
startDepot.stopLatLong = ends[0];
startDepot.type = VRPConstants.DEPOT;
startDepot.stopId = VRPConstants.VEHICLE_START_ID + "_" + dfn.vehicles.getBaseId(vehiclesTable, vRow);
startDepot.stopName = startDepot.stopId;
startDepot.stopNumber = 0;
startDepot.startTimeWindow = route.startTimeWindow;
startDepot.endTimeWindow = route.endTimeWindow;
startDepot.vehicleId = route.vehicleId;
startDepot.vehicleName = route.vehicleName;
startDepot.temporary.rowVehicleIndex = route.temp.rvi;
route.stops.add(0, startDepot);
}
// add end depot
if (ends[1] != null) {
StopDetail endDepot = new StopDetail(nq);
endDepot.stopLatLong = ends[1];
endDepot.type = VRPConstants.DEPOT;
endDepot.stopId = VRPConstants.VEHICLE_END_ID + "_" + dfn.vehicles.getBaseId(vehiclesTable, vRow);
endDepot.stopName = endDepot.stopId;
endDepot.stopNumber = route.stops.size();
endDepot.startTimeWindow = route.startTimeWindow;
endDepot.endTimeWindow = route.endTimeWindow;
endDepot.vehicleId = route.vehicleId;
endDepot.vehicleName = route.vehicleName;
endDepot.temporary.rowVehicleIndex = route.temp.rvi;
route.stops.add(endDepot);
}
}
}
private Map.Entry<VehicleRoutingProblemSolution, SolutionAnalyser> buildJspritSolution() {
// build the vehicle routes
List<VehicleRoute> vehicleRoutes = new ArrayList<VehicleRoute>();
for (final RouteDetail routeDetail : vehicleIdToRouteDetails.values()) {
VehicleRoute.Builder vehicleRouteBuilder = VehicleRoute.Builder.newInstance(routeDetail.temp.jspritVehicle);
vehicleRouteBuilder.setJobActivityFactory(builtProblem.getJspritProblem().getJobActivityFactory());
Map<String, List<StopDetail>> stopDetailsByJobId = odlApi.stringConventions().createStandardisedMap();
// Go through all the stops on a route and add these to the vehicle
// route builder as service/job.
for (StopDetail stopDetail : routeDetail.stops) {
// // don't add an unbalanced pickup / delivery to the jsprit
// // solution
// if (stopDetail.temporary.isUnbalancedPickupDelivery) {
// continue;
// }
// check we have a stop record (could be a depot stop otherwise)
BuiltStopRec stopRec = builtProblem.getBuiltStop(stopDetail.stopId);
if (stopRec == null) {
continue;
}
// add the stop to the jsprit vehicle route builder
Job job = stopRec.getJSpritJob();
switch (stopRec.getType()) {
case LINKED_DELIVERY:
vehicleRouteBuilder.addDelivery((Shipment) job);
break;
case LINKED_PICKUP:
vehicleRouteBuilder.addPickup((Shipment) job);
break;
case UNLINKED_PICKUP:
vehicleRouteBuilder.addPickup((Pickup) job);
break;
case UNLINKED_DELIVERY:
vehicleRouteBuilder.addDelivery((Delivery) job);
break;
}
// also save the stop to our 'by job id' structure
String jobId = stopRec.getJSpritJob().getId();
List<StopDetail> sameId = stopDetailsByJobId.get(jobId);
if (sameId == null) {
sameId = new ArrayList<StopDetail>();
stopDetailsByJobId.put(jobId, sameId);
}
sameId.add(stopDetail);
}
routeDetail.temp.jspritRoute = vehicleRouteBuilder.build();
vehicleRoutes.add(routeDetail.temp.jspritRoute);
// Save the tour activity objects to the stop details
int n = routeDetail.temp.jspritRoute.getActivities().size();
for (int i = 0; i < n; i++) {
TourActivity activity = routeDetail.temp.jspritRoute.getActivities().get(i);
if (JobActivity.class.isInstance(activity)) {
JobActivity ja = (JobActivity) activity;
List<StopDetail> stops = stopDetailsByJobId.get(ja.getJob().getId());
if (stops != null) {
for (StopDetail sd : stops) {
if (sd.temporary.builtStopRec == builtProblem.getBuiltStop(ja)) {
sd.temporary.jspritTourActivity = ja;
break;
}
}
}
}
}
// fill in start
int nsd = routeDetail.stops.size();
if (nsd> 0) {
StopDetail sd = routeDetail.stops.get(0);
if (sd.type.equals(VRPConstants.DEPOT)) {
sd.temporary.jspritTourActivity = routeDetail.temp.jspritRoute.getStart();
}
}
// fill in end
if(nsd>1){
StopDetail sd = routeDetail.stops.get(nsd-1);
if (sd.type.equals(VRPConstants.DEPOT)) {
sd.temporary.jspritTourActivity = routeDetail.temp.jspritRoute.getEnd();
}
}
}
// get all loaded job ids (note an pickup-deliver with only one end
// loaded is still counted here (and possibly shouldn't be)
Set<String> loadedJobIds = odlApi.stringConventions().createStandardisedSet();
for (final RouteDetail routeDetail : vehicleIdToRouteDetails.values()) {
for (StopDetail stopDetail : routeDetail.stops) {
if (stopDetail.temporary.builtStopRec != null) {
loadedJobIds.add(stopDetail.temporary.builtStopRec.getJSpritJob().getId());
}
}
}
// find out which jobs are unassigned - including any partially assigned
// pds
List<Job> unassignedJobs = new ArrayList<>();
for (Job job : builtProblem.getJspritProblem().getJobs().values()) {
if (!loadedJobIds.contains(job.getId())) {
unassignedJobs.add(job);
}
}
// build the jsprit problem
VehicleRoutingProblemSolution sol = new VehicleRoutingProblemSolution(vehicleRoutes, unassignedJobs, 0);
// Create a solution analyser from the solution.
SolutionAnalyser analyser = new SolutionAnalyser(builtProblem.getJspritProblem(), sol, new TransportDistance() {
@Override
public double getDistance(Location fromLocationId, Location toLocationId,double departureTime, Vehicle vehicle) {
return builtProblem.getTravelDistanceKM(fromLocationId.getId(), toLocationId.getId());
}
});
return new AbstractMap.SimpleEntry<VehicleRoutingProblemSolution, SolutionAnalyser>(sol, analyser);
}
// private void findUnbalancedPickupDelivers() {
// // build a map of all the loaded pds
// Map<String, List<StopDetail>> pdsByJobId =
// odlApi.stringConventions().createStandardisedMap();
// for (RouteDetail rd : vehicleIdToRouteDetails.values()) {
// for (StopDetail sd : rd.stops) {
// BuiltStopRec rec = sd.temporary.builtStopRec;
// if (rec != null) {
// if (rec.getType() == StopType.LINKED_DELIVERY || rec.getType() ==
// StopType.LINKED_PICKUP) {
// String jobId = rec.getJSpritJob().getId();
// List<StopDetail> list = pdsByJobId.get(jobId);
// if (list == null) {
// list = new ArrayList<StopDetail>();
// pdsByJobId.put(jobId, list);
// }
//
// if (rec.getType() == StopType.LINKED_PICKUP) {
// list.add(0, sd);
// } else {
// list.add(list.size(), sd);
// }
// }
// }
// }
// }
//
// // parse the grouped pds and check for invalid
// for (List<StopDetail> list : pdsByJobId.values()) {
// boolean invalid = false;
//
// // check for only one loaded
// if (list.size() == 1) {
// invalid = true;
// }
//
// // check for different vehicle
// if (!invalid) {
// if (!odlApi.stringConventions().equalStandardised(list.get(0).vehicleId,
// list.get(1).vehicleId)) {
// invalid = true;
// }
// }
//
// // check for delivery before pickup
// if (!invalid) {
// if (list.get(0).temporary.rowNumberInStopOrderTable >=
// list.get(1).temporary.rowNumberInStopOrderTable) {
// invalid = true;
// }
// }
//
// if (invalid) {
// for (StopDetail detail : list) {
// detail.temporary.isUnbalancedPickupDelivery = true;
// detail.hasViolation = 1;
// }
// }
// }
// }
private Map<String,StopDetail> buildEmptyStopDetails() {
Map<String,StopDetail> ret = odlApi.stringConventions().createStandardisedMap();
// parse route order table getting records for each stop in a list for
// each route
int n = stopOrderTable.getRowCount();
for (int stopOrderRow = 0; stopOrderRow < n; stopOrderRow++) {
StopDetail stopDetail = new StopDetail(config.getNbQuantities());
stopDetail.temporary.rowNumberInStopOrderTable = stopOrderRow;
stopDetail.stopId = dfn.stopOrder.getStopId(stopOrderTable, stopOrderRow);
// identify stop from the built problem
stopDetail.temporary.builtStopRec = builtProblem.getBuiltStop(stopDetail.stopId);
if (stopDetail.temporary.builtStopRec == null) {
throw new RuntimeException("Failed to build or could not find stop record for stop id " + stopDetail.stopId + " in stop-order table on row " + (stopOrderRow + 1)
+ ".");
}
// get vehicleid and routedetails record - if its unknown an
// exception would have been thrown already
stopDetail.vehicleId = dfn.stopOrder.getVehicleId(stopOrderTable, stopOrderRow);
RouteDetail routeDetail = vehicleIdToRouteDetails.get(stopDetail.vehicleId);
stopDetail.temporary.rowVehicleIndex = routeDetail.temp.rvi;
stopDetail.vehicleName = routeDetail.vehicleName;
// fill in stop details
StopsTableDefn stopDfn = dfn.stops;
stopDetail.jobId = stopDetail.temporary.builtStopRec.getJSpritJob().getId();
int stopRow = stopDetail.temporary.builtStopRec.getRowNbInStopsTable();
stopDetail.stopName = (String) stopsTable.getValueAt(stopRow, stopDfn.name);
stopDetail.stopNumber = routeDetail.stops.size() + 1;
stopDetail.stopAddress = (String) stopsTable.getValueAt(stopRow, stopDfn.address);
stopDetail.stopLatLong = stopDfn.latLong.getLatLong(stopsTable, stopRow, false);
stopDetail.stopDuration = stopDfn.getDuration(stopsTable, stopRow).getTotalMilliseconds();
stopDetail.type = stopDfn.getStopType(stopsTable, stopRow).getPrimaryCode();
stopDetail.requiredSkills = (String)stopsTable.getValueAt(stopRow, stopDfn.requiredSkills);
ODLTime[] tw = stopDfn.getTW(stopsTable, stopRow);
if (tw != null) {
stopDetail.startTimeWindow = (double) tw[0].getTotalMilliseconds();
stopDetail.endTimeWindow = (double) tw[1].getTotalMilliseconds();
}
// // get stop quantities
// for (int i = 0; i < conf.getNbQuantities(); i++) {
// int value = dfn.stops.getQuantity(jobs, stopRow, i);
// switch (detail.temporary.stopType) {
// case UNLINKED_DELIVERY:
// case NORMAL_STOP:
// // loaded at the depot
// currentRoute.startQuantities[i] += value;
//
// // unloaded on the route
// detail.stopQuantities[i] = -value;
// break;
//
// case LINKED_PICKUP:
// case UNLINKED_PICKUP:
// // loaded on the route, not at the depot
// detail.stopQuantities[i] = value;
// break;
//
// case LINKED_DELIVERY:
// // unloaded on the route
// detail.stopQuantities[i] = -value;
// break;
//
// }
// }
// add route details for this stop to the current route
routeDetail.stops.add(stopDetail);
ret.put(stopDetail.stopId, stopDetail);
}
// fill in stops count
for (final RouteDetail routeDetail : vehicleIdToRouteDetails.values()) {
routeDetail.stopsCount = routeDetail.stops.size();
}
return ret;
}
private Map<String, RouteDetail> buildEmptyRouteDetails() {
// Create the object which identifies vehicle ids
VehicleIds vehicleIds = new VehicleIds(odlApi, config, dfn, vehiclesTable);
Map<String, RouteDetail> ret = odlApi.stringConventions().createStandardisedMap();
for (int row = 0; row < stopOrderTable.getRowCount(); row++) {
// check if routedetails object already built
String vehicleId = dfn.stopOrder.getVehicleId(stopOrderTable, row);
if (ret.containsKey(vehicleId)) {
continue;
}
// identifyVehicle throws an exception if not identified
RouteDetail detail = new RouteDetail(config.getNbQuantities());
detail.temp.rvi = vehicleIds.identifyVehicle(row, vehicleId);
detail.temp.rvi.id = vehicleId;
detail.vehicleId = vehicleId;
// fill in any vehicle details we have initially
int vehicleTypeRow = detail.temp.rvi.row;
detail.vehicleName = dfn.vehicles.getName(vehiclesTable, vehicleTypeRow, detail.temp.rvi.vehicleIndex);
for (int q = 0; q < config.getNbQuantities(); q++) {
detail.capacity[q] = dfn.vehicles.getCapacity(vehiclesTable, vehicleTypeRow, q);
}
detail.costPerKm = dfn.vehicles.getCost(vehiclesTable, vehicleTypeRow, CostType.COST_PER_KM);
detail.costPerHour = dfn.vehicles.getCost(vehiclesTable, vehicleTypeRow, CostType.COST_PER_HOUR);
// route time windows
ODLTime[] tw = dfn.vehicles.getTimeWindow(vehiclesTable, vehicleTypeRow);
if (tw != null) {
detail.startTimeWindow = (double) tw[0].getTotalMilliseconds();
detail.endTimeWindow = (double) tw[1].getTotalMilliseconds();
}
// skills
detail.skills = (String)vehiclesTable.getValueAt(vehicleTypeRow, dfn.vehicles.skills);
// speed multiplier
detail.speedMultiplier = (double)vehiclesTable.getValueAt(vehicleTypeRow, dfn.vehicles.speedMultiplier);
// save
ret.put(vehicleId, detail);
}
// add all routes to the solution details object in sorted orded...
for(RouteDetail rd : ret.values()){
sd.routes.add(rd);
}
return ret;
}
private VRPBuilder buildVRPProblem(VRPConfig conf, ComponentExecutionApi api, InputTablesDfn dfn, ODLDatastore<? extends ODLTable> ioDb) {
// Get vehicle ids in the format expected by the VRP builder
TreeMap<Integer, List<RowVehicleIndex>> vehiclesToBuild = new TreeMap<>();
for (RouteDetail rd : vehicleIdToRouteDetails.values()) {
List<RowVehicleIndex> withinType = vehiclesToBuild.get(rd.temp.rvi.row);
if (withinType == null) {
withinType = new ArrayList<>();
vehiclesToBuild.put(rd.temp.rvi.row, withinType);
}
withinType.add(rd.temp.rvi);
}
// build the VRP to (a) call matrix generation and (b) create the exact
// needed vehicles
VRPBuilder built = VRPBuilder.build(ioDb, conf, vehiclesToBuild, api);
// set the jsprit object onto the vehicle record
for (Vehicle vehicle : built.getJspritProblem().getVehicles()) {
vehicleIdToRouteDetails.get(vehicle.getId()).temp.jspritVehicle = vehicle;
}
return built;
}
public SolutionDetail getSolutionDetail() {
return sd;
}
}