Using Resources of Neighboring Depots to Avoid Order Overflow
This use case describes how to use to execute orders of depots other than their home depot in case of limited vehicle resources (number- and/or capacity-wise).
In this use case the capacity of vehicles located at a certain depot is not sufficient to execute all orders from this depot. Vehicles from neighboring depots should be used to execute the remaining orders.
Example | The following picture shows an example of the desired tour structure
In this example the available fleet capacity of Depot B is too small to execute the current amount of orders. Therefore, vehicles of a neighboring Depot A shall help to master the situation.
Vehicle A2 approaches Depot B running empty. After that it performs delivery and pick-up orders of Depot B before heading back to the vehicle location at Depot A. Vehicle A3 is supposed to perform some of the deliveries of Depot B as well as deliveries and pick-ups of Depot A. In order to avoid empty , the vehicle is loaded at Depot A with some delivery orders before approaching Depot B for further loading. Since there are no delivery orders to Depot B involved, there is no need to return back there, in contrast to vehicle A2, and the vehicle A3 heads directly to its final location at Depot A at the end of the journey.
Benefits
- Users learn how to parametrize the current use case.
- Basic insight into how to influence the course of a tour.
Prerequisites
Please ensure following prerequisites are fulfilled before you start with the use case:
- Installed and licensed PTV xTour service
- License for as many vehicles as the plan should contain
Concepts
Programming Guide
This example explains how the request has to look like in order to influence the tour structure as mentioned in the use-case description above.
First we define our locations as OffRoadRouteLocations
in the default EPSG:4326 format (see RequestBase.coordinateFormat
).
You could also use OnRoadRouteLocations
, see the technical concept on waypoints and route legs for more information on the different types of .
Since the use case describes deliveries from and to several depots, we define some locations as depot and some as customer sites, see Orders, Locations, and Stops for more details.
The orders need to have specific quantities
set, which may lead to multiple tours if a vehicles maximumQuantityScenario
is not high enough to load the combined quantities
of all orders.
The fleet in this example consists of two types of vehicle
, one containing three available instances and the other one containing only one available instance (defined by the number of vehicle ids
).
The two types of vehicle only differ in their start and end locations which are located at the different depots. To simplify the example code, we assume that the vehicles use direct distance
(can be set in the distance mode of the request
).
In this example we do not want to return to the depot(s) in between stops at customers, so we need to activate the tour restriction singleTripPerTour
. This restriction could also be omitted if other tour structures should be allowed.
The last part of the example request are the orders
, in this case only consisting of pickup and delivery orders
.
We pass the request on to planTours
. Once xTour has processed the request a callback is invoked which gives us access to the result of the calculation in form of a ToursResponse
object.
var DepotA = {
"$type": "DepotSite",
"id": "DepotA",
"routeLocation": {
"$type": "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.113033,
"y": 49.608040
}
}
};
var DepotB = {
"$type": "DepotSite",
"id": "DepotB",
"routeLocation": {
"$type": "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.159553,
"y": 49.595135
}
}
};
var Customer1 = {
"$type": "CustomerSite",
"id": "Customer1",
"routeLocation": {
"$type": "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.097412,
"y": 49.609597
}
}
};
var Customer2 = {
"$type": "CustomerSite",
"id": "Customer2",
"routeLocation": {
"$type": "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.097755,
"y": 49.614325
}
}
};
var Customer3 = {
"$type": "CustomerSite",
"id": "Customer3",
"routeLocation": {
"$type": "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.098742,
"y": 49.618829
}
}
};
var Customer4 = {
"$type": "CustomerSite",
"id": "Customer4",
"routeLocation": {
"$type": "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.115694,
"y": 49.619941
}
}
};
var Customer5 = {
"$type": "CustomerSite",
"id": "Customer5",
"routeLocation": {
"$type": "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.114835,
"y": 49.616771
}
}
};
var Customer6 = {
"$type": "CustomerSite",
"id": "Customer6",
"routeLocation": {
"$type": "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.155176,
"y": 49.607094
}
}
};
var Customer7 = {
"$type": "CustomerSite",
"id": "Customer7",
"routeLocation": {
"$type": "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.159896,
"y": 49.605704
}
}
};
var Customer8 = {
"$type": "CustomerSite",
"id": "Customer8",
"routeLocation": {
"$type": "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.169338,
"y": 49.605926
}
}
};
var Customer9 = {
"$type": "CustomerSite",
"id": "Customer9",
"routeLocation": {
"$type": "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.173543,
"y": 49.598973
}
}
};
var Customer10 = {
"$type": "CustomerSite",
"id": "Customer10",
"routeLocation": {
"$type": "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.165390,
"y": 49.591852
}
}
};
var Customer11 = {
"$type": "CustomerSite",
"id": "Customer11",
"routeLocation": {
"$type": "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.155605,
"y": 49.588736
}
}
};
var Customer12 = {
"$type": "CustomerSite",
"id": "Customer12",
"routeLocation": {
"$type": "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.138353,
"y": 49.593632
}
}
};
var Customer13 = {
"$type": "CustomerSite",
"id": "Customer13",
"routeLocation": {
"$type": "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.121702,
"y": 49.588736
}
}
};
var Customer14 = {
"$type": "CustomerSite",
"id": "Customer14",
"routeLocation": {
"$type": "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.113033,
"y": 49.593632
}
}
};
var Customer15 = {
"$type": "CustomerSite",
"id": "Customer15",
"routeLocation": {
"$type": "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.097755,
"y": 49.595135
}
}
};
var locations = [DepotA, DepotB, Customer1, Customer2, Customer3, Customer4, Customer5, Customer6, Customer7, Customer8, Customer9, Customer10, Customer11, Customer12, Customer13, Customer14, Customer15];
var map = new L.Map('map', {
center: [49.61, 6.125],
zoom: 13
});
// Add tile layer to map
var tileUrl = xServerUrl + '/services/rest/XMap/tile/{z}/{x}/{y}';
var tileLayer = new L.TileLayer(tileUrl, {
minZoom: 3,
maxZoom: 18,
noWrap: true
}).addTo(map);
var markers = L.layerGroup().addTo(map);
function planSpecificTours() {
xtour.planTours({
"locations": locations,
"orders": [{
"$type": "PickupDeliveryOrder",
"id": "PDOrder1",
"quantities": [110.0],
"pickupLocationId": "Customer11",
"deliveryLocationId": "DepotB"
}, {
"$type": "PickupDeliveryOrder",
"id": "PDOrder2",
"quantities": [100.0],
"pickupLocationId": "Customer14",
"deliveryLocationId": "DepotA"
}, {
"$type": "PickupDeliveryOrder",
"id": "PDOrder3",
"quantities": [40.0],
"pickupLocationId": "DepotA",
"deliveryLocationId": "Customer1"
}, {
"$type": "PickupDeliveryOrder",
"id": "PDOrder4",
"quantities": [20.0],
"pickupLocationId": "DepotA",
"deliveryLocationId": "Customer2"
}, {
"$type": "PickupDeliveryOrder",
"id": "PDOrder5",
"quantities": [40.0],
"pickupLocationId": "DepotA",
"deliveryLocationId": "Customer3"
}, {
"$type": "PickupDeliveryOrder",
"id": "PDOrder6",
"quantities": [60.0],
"pickupLocationId": "DepotA",
"deliveryLocationId": "Customer4"
}, {
"$type": "PickupDeliveryOrder",
"id": "PDOrder7",
"quantities": [40.0],
"pickupLocationId": "DepotA",
"deliveryLocationId": "Customer5"
}, {
"$type": "PickupDeliveryOrder",
"id": "PDOrder8",
"quantities": [100.0],
"pickupLocationId": "DepotA",
"deliveryLocationId": "Customer15"
}, {
"$type": "PickupDeliveryOrder",
"id": "PDOrder9",
"quantities": [45.0],
"pickupLocationId": "DepotB",
"deliveryLocationId": "Customer6"
}, {
"$type": "PickupDeliveryOrder",
"id": "PDOrder10",
"quantities": [10.0],
"pickupLocationId": "DepotB",
"deliveryLocationId": "Customer7"
}, {
"$type": "PickupDeliveryOrder",
"id": "PDOrder11",
"quantities": [30.0],
"pickupLocationId": "DepotB",
"deliveryLocationId": "Customer8"
}, {
"$type": "PickupDeliveryOrder",
"id": "PDOrder12",
"quantities": [15.0],
"pickupLocationId": "DepotB",
"deliveryLocationId": "Customer9"
}, {
"$type": "PickupDeliveryOrder",
"id": "PDOrder13",
"quantities": [110.0],
"pickupLocationId": "DepotB",
"deliveryLocationId": "Customer10"
}, {
"$type": "PickupDeliveryOrder",
"id": "PDOrder14",
"quantities": [90.0],
"pickupLocationId": "DepotB",
"deliveryLocationId": "Customer12"
}, {
"$type": "PickupDeliveryOrder",
"id": "PDOrder15",
"quantities": [100.0],
"pickupLocationId": "DepotB",
"deliveryLocationId": "Customer13"
}],
"fleet": {
"vehicles": [{
"ids": ["vehicleA2", "vehicleA1", "vehicleA3"],
"maximumQuantityScenarios": [{
"quantities": [200.0]
}],
"startLocationId": "DepotA",
"endLocationId": "DepotA"
}, {
"ids": ["vehicleB1"],
"maximumQuantityScenarios": [{
"quantities": [100.0]
}],
"startLocationId": "DepotB",
"endLocationId": "DepotB"
}]
},
"distanceMode": {
"$type": "DirectDistance"
},
"planToursOptions": {
"restrictions": {
"singleTripPerTour": true
}
}
}, function(toursResponse, exception) {
displayLocations();
displayTours(toursResponse.tours);
});
}
function displayLocations() {
markers.clearLayers();
for (var i = 0; i < locations.length; i++) {
var location = locations[i];
switch (location.$type) {
case "CustomerSite":
displayCustomerSite(location);
break;
case "DepotSite":
displayDepotSite(location);
break;
default: // VehicleLocation should not be existent in this use case
break;
}
}
}
function displayCustomerSite(location) {
var point = getLatLngOfLocation(location);
var marker = L.marker(point, {
icon: getCircleIcon(24, xServerUrl + '/dashboard/Content/Resources/Images/Showcases/circle_gray.png'),
title: location.id
}).addTo(map);
markers.addLayer(marker);
}
function displayDepotSite(location) {
var point = getLatLngOfLocation(location);
var marker = L.marker(point, {
icon: getCircleIcon(24, xServerUrl + '/dashboard/Content/Resources/Images/Showcases/circle_orange.png'),
title: location.id
}).addTo(map);
markers.addLayer(marker);
}
function getLatLngOfLocation(location) { // Only OffRoadRouteLocations are supported in this use case
var coordinateX = location.routeLocation.offRoadCoordinate.x;
var coordinateY = location.routeLocation.offRoadCoordinate.y;
var coordinate = L.latLng(coordinateY, coordinateX);
return coordinate;
}
function getCircleIcon(size, colorUrl) {
return L.icon({
iconUrl: colorUrl,
iconSize: [size, size],
iconAnchor: [Math.floor(size / 2), Math.floor(size / 2)]
});
}
function displayTours(tours) {
var bounds = new L.latLngBounds();
var layer = null;
for (var i = 0; i < tours.length; i++) {
var tour = tours[i];
var latLongsOfTour = getLatLongsOfTour(tour);
if (i == 0) {
layer = L.polyline(latLongsOfTour, {
color: "#575757",
weight: 8
}).addTo(map);
} else if (i == 1) {
layer = L.polyline(latLongsOfTour, {
color: "#2882C8",
weight: 8
}).addTo(map);
} else if (i == 2) {
layer = L.polyline(latLongsOfTour, {
color: "#e02129",
weight: 8
}).addTo(map);
} else {
layer = L.polyline(latLongsOfTour, {
color: "#00accd",
weight: 8
}).addTo(map);
}
layer.bindTooltip(tour.vehicleId, {
direction: 'top'
});
bounds.extend(layer.getBounds());
}
map.fitBounds(bounds);
}
function getLatLongsOfTour(tour) {
var trips = tour.trips;
var latLongsOfTour = [];
var locationLatLong = null;
if (tour.vehicleStartLocationId != null) {
locationLatLong = getLocationLatLongOfLocationId(tour.vehicleStartLocationId);
latLongsOfTour.push(locationLatLong);
}
for (var tripIndex = 0; tripIndex < trips.length; tripIndex++) {
var stopSequence = trips[tripIndex].stops;
for (var i = 0; i < stopSequence.length; i++) {
var locationIdOfStop = stopSequence[i].locationId;
locationLatLong = getLocationLatLongOfLocationId(locationIdOfStop);
latLongsOfTour.push(locationLatLong);
}
}
if (tour.vehicleEndLocationId != null) {
locationLatLong = getLocationLatLongOfLocationId(tour.vehicleEndLocationId);
latLongsOfTour.push(locationLatLong);
}
return latLongsOfTour;
}
function getLocationLatLongOfLocationId(locationId) {
for (var i = 0; i < locations.length; i++) {
var location = locations[i];
if (locationId == location.id) {
var coordinate = getLatLngOfLocation(location);
return coordinate;
}
}
}
planSpecificTours();
The customer sites of the request are displayed in gray, the depot sites in orange. Since the ToursResponse
only contains references to the given locations, the corresponding coordinates are taken from the request. The first tour of the result is displayed in gray, the second one in blue, the third one in red and the fourth one in cyan.
Each vehicle starts from its start depot. The maximum quantities of the vehicles of DepotB is not enough to execute all orders of DepotB. Therefore the vehicles vehicleA2 and vehicleA3 have to execute orders of DepotB although their start depot is DepotA.