/*******************************************************************************
* Copyright (c) 2014 Open Door Logistics (www.opendoorlogistics.com)
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v3
* which accompanies this distribution, and is available at http://www.gnu.org/licenses/lgpl.txt
******************************************************************************/
package com.opendoorlogistics.components.jsprit.tabledefinitions;
import static com.opendoorlogistics.api.components.PredefinedTags.CAPACITY;
import static com.opendoorlogistics.api.components.PredefinedTags.NUMBER_OF_VEHICLES;
import static com.opendoorlogistics.api.components.PredefinedTags.VEHICLE_ID;
import static com.opendoorlogistics.api.components.PredefinedTags.VEHICLE_NAME;
import static com.opendoorlogistics.api.components.PredefinedTags.SPEED_MULTIPLIER;
import java.util.Map;
import com.opendoorlogistics.api.ODLApi;
import com.opendoorlogistics.api.components.PredefinedTags;
import com.opendoorlogistics.api.geometry.LatLong;
import com.opendoorlogistics.api.tables.ODLColumnType;
import com.opendoorlogistics.api.tables.ODLDatastoreAlterable;
import com.opendoorlogistics.api.tables.ODLTableDefinitionAlterable;
import com.opendoorlogistics.api.tables.ODLTableReadOnly;
import com.opendoorlogistics.api.tables.ODLTime;
import com.opendoorlogistics.api.tables.TableFlags;
import com.opendoorlogistics.components.jsprit.VRPConfig;
import com.opendoorlogistics.components.jsprit.VRPUtils;
public class VehiclesTableDfn extends TableDfn{
public static final String VEHICLE_TYPES_TABLE_NAME ="VehicleTypes";
private final ODLApi api;
public final int vehicleName;
public final int id;
public final LatLongDfn start;
public final LatLongDfn end;
public final TimeWindowDfn tw;
public final int [] capacities;
public final int number;
public final int [] costs;
public final int skills;
public final int speedMultiplier;
public enum CostType{
COST_PER_KM(0.001),
COST_PER_HOUR(1),
WAITING_COST_PER_HOUR(0.5),
FIXED_COST(100),
PARKING_COST(0);
private CostType(double defaultVal) {
this.defaultVal = defaultVal;
}
public final double defaultVal;
public String fieldname(){
return name().toLowerCase().replace('_', '-');
}
}
public VehiclesTableDfn(ODLApi api,ODLDatastoreAlterable<? extends ODLTableDefinitionAlterable> ds, VRPConfig conf) {
super(ds, VEHICLE_TYPES_TABLE_NAME);
this.api = api;
vehicleName = addStrColumn(VEHICLE_NAME);
api.tables().setColumnIsOptional(table, vehicleName, true);
id = addStrColumn(VEHICLE_ID);
start = new LatLongDfn(api,table,"start-");
end = new LatLongDfn(api,table, "end-");
tw = new TimeWindowDfn(table, "");
capacities = addQuantities(CAPACITY, conf);
speedMultiplier = addDblColumn(1, SPEED_MULTIPLIER);
table.setColumnFlags(speedMultiplier, table.getColumnFlags(speedMultiplier) | TableFlags.FLAG_IS_OPTIONAL);
costs = new int[CostType.values().length];
for(CostType ct:CostType.values()){
costs[ct.ordinal()] = addDblColumn(ct.defaultVal,ct.fieldname());
// make the new parking cost optional
if(ct == CostType.PARKING_COST){
table.setColumnFlags(costs[ct.ordinal()], table.getColumnFlags(costs[ct.ordinal()]) | TableFlags.FLAG_IS_OPTIONAL);
}
}
// costs[CostType.FIXED_COST.ordinal()] = addDblColumn(100,CostType.FIXED_COST.fieldname());
// costs[CostType.COST_PER_HOUR.ordinal()] = addDblColumn(1,CostType.COST_PER_HOUR.fieldname());
// costs[CostType.WAITING_COST_PER_HOUR.ordinal()] = addDblColumn(0.5,CostType.WAITING_COST_PER_HOUR.fieldname());
// costs[CostType.COST_PER_KM.ordinal()] = addDblColumn(0,CostType.COST_PER_KM.fieldname());
skills= addStrColumn("skills");
table.setColumnFlags(skills, table.getColumnFlags(skills)|TableFlags.FLAG_IS_OPTIONAL);
number = addColumn(ODLColumnType.LONG, NUMBER_OF_VEHICLES);
table.setColumnDefaultValue(number, new Long(1));
api.tables().setColumnIsOptional(table, number, true);
table.setColumnDefaultValue(number, new Long(1));
}
public int getNumberOfVehiclesInType(ODLTableReadOnly table,int row){
Long val= (Long)table.getValueAt(row, number);
if(val==null){
return 1;
}
if(val<0 || val> Integer.MAX_VALUE){
onRowException("Invalid number of vehicles", row);
}
return val.intValue();
}
/**
* Get cost and validate it
* @param table
* @param row
* @param type
* @return
*/
public double getCost(ODLTableReadOnly table,int row, CostType type){
Double val = (Double)table.getValueAt(row, costs[type.ordinal()]);
if(val==null){
return 0;
}
if (Double.isNaN(val) || Double.isInfinite(val) || val < 0) {
onRowException("Found invalid vehicle cost " + type.fieldname(), row);
}
return val;
}
/**
* Get capacity and validate it
* @param table
* @param row
* @param quantityIndex
* @return
*/
public int getCapacity(ODLTableReadOnly table,int row, int quantityIndex){
Long capacity = (Long) table.getValueAt(row, capacities[quantityIndex]);
if (!VRPUtils.isOkQuantity(capacity)) {
onRowException( "Invalid vehicle capacity", row);
}
if(capacity==null){
// infinite...
return Integer.MAX_VALUE;
}
return capacity.intValue();
}
public void onRowException(String messagePrefix, int row) {
throw new RuntimeException(messagePrefix + " on vehicles table row " + (row + 1) + ".");
}
public String getId(ODLTableReadOnly table,int row, int vehicleIndex){
int totalNumber = getNumberOfVehiclesInType(table, row);
String base = getBaseId(table, row);
return api.stringConventions().getVehicleId(base, totalNumber, vehicleIndex);
}
public String getBaseId(ODLTableReadOnly table, int row) {
String base = (String)table.getValueAt(row, id);
if(api.stringConventions().isEmptyString(base)){
onRowException("Empty vehicle id",row);
}
return base;
}
/**
* Get and validate start and end
* @param table
* @param row
* @return
*/
public LatLong [] getStartAndEnd(ODLTableReadOnly table,int row){
// if (this.start.getNullCount(table, row) > 0) {
// onRowException("Empty start latitude or longitude for vehicle",row);
// }
LatLong [] ret = new LatLong[2];
ret[0]= this.start.getLatLong(table, row,true);
ret[1] = this.end.getLatLong(table, row, true);
return ret;
}
public String getName(ODLTableReadOnly table,int row, int vehicleIndx){
return api.stringConventions().getVehicleName((String)table.getValueAt(row, vehicleName), getNumberOfVehiclesInType(table, row), vehicleIndx);
}
public ODLTime[]getTimeWindow(ODLTableReadOnly table, int row){
if(tw==null){
// none set in problem..
return null;
}
return tw.get(table, row);
}
public Double getSpeedMultiplier(ODLTableReadOnly table, int row)
{
Double val = (Double) table.getValueAt(row, speedMultiplier);
if (val == null)
{
return 1.0;
}
if (val <= 0)
{
onRowException("Invalid speed multiplier", row);
}
return val;
}
public static class RowVehicleIndex{
final public int row;
final public int vehicleIndex;
final public boolean vehicleExceedsMaximumInVehicleType;
final public boolean isSingleInstanceVehicleType;
public String id;
public RowVehicleIndex(int row, int vehicleIndex,boolean vehicleExceedsMaximumInVehicleType, boolean isSingleInstanceVehicleType) {
super();
this.row = row;
this.vehicleIndex = vehicleIndex;
this.vehicleExceedsMaximumInVehicleType = vehicleExceedsMaximumInVehicleType;
this.isSingleInstanceVehicleType = isSingleInstanceVehicleType;
}
}
public Map<String,RowVehicleIndex> getVehicleIdToRowIndex(ODLTableReadOnly vehiclesTable){
int n = vehiclesTable.getRowCount();
Map<String,RowVehicleIndex> ret = api.stringConventions().createStandardisedMap();
for(int row =0 ; row<n;row++){
int n2 = getNumberOfVehiclesInType(vehiclesTable, row);
boolean isSingleInstanceVehicleType = vehiclesTable.getValueAt(row, number)==null || n2==1;
for(int i =0 ; i<n2;i++){
String id = getId(vehiclesTable, row, i);
if(ret.get(id)!=null){
onRowException("Duplicate " + PredefinedTags.VEHICLE_ID, row);
}
ret.put(id, new RowVehicleIndex(row,i, false, isSingleInstanceVehicleType));
}
}
return ret;
}
}