Planning a Tour using Freight Forwarding

In this use case several transport orders are planned by a freight forwarding agent without any own fleet and the resulting schedule is executed by a subcontracting carrier. No goods have to be delivered from or to any depots, just transport orders between pairs of customer sites have to be executed.

Example | The following picture shows an example of the order structure

Geographical overview of single orders planned by a freight forwarding agent.

Benefits

Prerequisites

Please ensure following prerequisites are fulfilled before you start with the use case:

Concepts

The next sections explain the resulting tour structures depending on how the locations are modeled. Due to the definition of tours and trips, the structure changes if locations are modeled as depot sites instead of customer sites.

Modeling every site as customer

Depot sites are currently the only available trip delimiters. Therefore the resulting xTour schedule (no matter, which algorithm settings were applied) consists of a single trip, since no depot sites are involved. However, this trip contains a lot of empty drives on route legsA route leg is a part of a route between two consecutive waypoints., since there is no central location where the most of the goods are picked up or have to be delivered to.

Geographical tour structure and stop sequence of pickup-delivery orders with all sites modelled as customers.

The execution of the schedule is outsourced to subcontracting carriers and, depending on a payment tariff structure, several short trips might be in favor compared to one long trip with a lot of empty legs.

Modeling every site as depot

There is a workaround to achieve several short trips instead of a large one: call the planning function with customer sites modeled as depot sites and do not restrict the tour structure to single trips or single depots. This would allow the resulting tour to be split into several trips. However, the downside of this workaround is, that there are more restrictions with regard to tasks, which can be carried out at a depot site. As soon as a vehicle carries out a single delivery task at a depot site, it is not allowed to pick up anything or approach customer sites until the vehicle is empty. This limitation results in trip structures where at first all pick-up tasks are carried out, followed by all delivery tasks.

Geographical tour structure and stop sequence for pickup-delivery orders with all sites modelled as depots.

So there is the choice between

Programming Guide

This example explains how the request should 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 route locationsA route location is the position of an object to be routed to or from, which contains additional information on how to link this position to the road network and whether or or not this position is actually reached. Route locations are used as the input for route calculation and optimization throughout PTV xServer.. Depending on the choice of desired tour structure, as explained in the concept section, locations are either modeled as depot or as customer sites.

The fleet in this example consists of one type of vehicle, with a single available instance (defined by the number of vehicle ids). This vehicle has undefined start or end locations because it does not matter to the planning use case. To simplify the example code, we assume that the vehicle uses direct distance (can be set in the distance mode of the request).

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.

In order to achieve the first example of tour structure, one long trip, all locations are modeled as customer sites. Change the type of all the locations to depot sites if you want to achieve the second example of tour structure, several short trips.

var Location1 = { "$type": "CustomerSite", // change to "$type": "DepotSite" for the second concept "id": "Location1", "routeLocation" : { "$type" : "OffRoadRouteLocation", "offRoadCoordinate": { "x": 6.098742, "y": 49.618829 } } }; var Location2 = { "$type": "CustomerSite", // change to "$type": "DepotSite" for the second concept "id": "Location2", "routeLocation" : { "$type" : "OffRoadRouteLocation", "offRoadCoordinate": { "x": 6.115694, "y": 49.619941 } } }; var Location3 = { "$type": "CustomerSite", // change to "$type": "DepotSite" for the second concept "id": "Location3", "routeLocation" : { "$type" : "OffRoadRouteLocation", "offRoadCoordinate": { "x": 6.114835, "y": 49.616771 } } }; var Location4 = { "$type": "CustomerSite", // change to "$type": "DepotSite" for the second concept "id": "Location4", "routeLocation": { "$type": "OffRoadRouteLocation", "offRoadCoordinate": { "x": 6.138353, "y": 49.608040 } } }; var Location5 = { "$type": "CustomerSite", // change to "$type": "DepotSite" for the second concept "id": "Location5", "routeLocation": { "$type": "OffRoadRouteLocation", "offRoadCoordinate": { "x": 6.135353, "y": 49.593632 } } }; var Location6 = { "$type": "CustomerSite", // change to "$type": "DepotSite" for the second concept "id": "Location6", "routeLocation": { "$type": "OffRoadRouteLocation", "offRoadCoordinate": { "x": 6.097755, "y": 49.608040 } } }; var locations = [Location1, Location2, Location3, Location4, Location5, Location6]; 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": [150.0], "pickupLocationId": "Location6", "deliveryLocationId": "Location1" }, { "$type": "PickupDeliveryOrder", "id": "PDOrder2", "quantities": [300.0], "pickupLocationId": "Location2", "deliveryLocationId": "Location3" }, { "$type": "PickupDeliveryOrder", "id": "PDOrder3", "quantities": [50.0], "pickupLocationId": "Location4", "deliveryLocationId": "Location5" }], "fleet": { "vehicles": [ { "ids": ["vehicle1"], "maximumQuantityScenarios": [ { "quantities": [500.0] } ] } ] }, "distanceMode": { "$type": "DirectDistance" } }, 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, '../../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, '../../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{ layer = L.polyline(latLongsOfTour, {color: "#2882C8", 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 tour of the result is displayed in gray.