Changing Stored Tours
This use case describes how to manipulate parts of a calculated tour in a user-defined way.
Benefits
- Users learn how to use a ChangeToursRequest.
- Users can adapt tour plans created by the PTV xTour service according to their personal needs.
Prerequisites
Please ensure following prerequisites are fulfilled before you start with the use case:
- Installed and licensed PTV xTour service
- License for as many as the plan should contain
Concepts
Programming Guide
This example provides the information on how to manipulate parts of a calculated tour according to your preferences.
First, we store tours by passing a PlanToursRequest
to the planTours
operation of the xTour service.
In order to focus on changing the tour in a second step, the request is kept simple: just visit orders, no opening interval restrictions, just one vehicle using direct distance, and so on. Have a look at other Integration Samples (e.g. Planning a Round Trip) for more realistic scenarios.
Since we want to manipulate the initial tour plan later on, we set the field storeRequest
to true. Once xTour has processed the request, a callback is invoked which gives us access the the calculation result in form of a ToursResponse
object. The PlanToursRequest (including the current tour plan as input plan) is stored in a session storage and the storedRequestId
can be found in the response.
This storedRequestId can be used in a ChangeToursRequest
to change the current tour plan according to own preferences.
In the comments at the end of the sample code below you find some hints on how to modify the code to apply and observe changes in the displayed tour. Set the local boolean variable applyChangeToursRequest
of function planAndChangeSpecificTours()
to true
to activate the changeTours
call. In function getChangeToursRequest()
two sample ChangeToursActions
are prepared. You can switch between both actions, modify them and add new ones. Note that each change action requires a new ChangeToursRequest.
The changeTours operation used to process the ChangeToursRequest also returns a ToursResponse. The modified tour plan is automatically stored in the session storage and can be the basis for further modifications. If you want to further modify the already changed tour, copy the current storedRequestId to the ChangeToursAction. Have a look at Change Tours
for details.
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 getPlanToursRequest() {
var visitLocation1 = {
"$type": "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.1035919189453125,
"y": 49.61070993807422
}
};
var visitLocation2 = {
"$type" : "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.115522384643555,
"y": 49.603701773184035
}
};
var visitLocation3 = {
"$type" : "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.147022247314454,
"y": 49.59685949756711
}
};
var visitLocation4 = {
"$type" : "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.156635284423828,
"y": 49.616048816070446
}
};
var visitLocation5 = {
"$type" : "OffRoadRouteLocation",
"offRoadCoordinate": {
"x": 6.12539291381836,
"y": 49.624056036556915
}
};
var locations = [
{
"$type": "CustomerSite",
"id": "Visit1",
"routeLocation" : visitLocation1
},
{
"$type": "CustomerSite",
"id": "Visit2",
"routeLocation" : visitLocation2
},
{
"$type": "CustomerSite",
"id": "Visit3",
"routeLocation" : visitLocation3
},
{
"$type": "CustomerSite",
"id": "Visit4",
"routeLocation" : visitLocation4
},
{
"$type": "CustomerSite",
"id": "Visit5",
"routeLocation" : visitLocation5
}
];
var orders = [
{
"$type": "VisitOrder",
"id": 1,
"locationId": "Visit1"
},
{
"$type": "VisitOrder",
"id": 2,
"locationId": "Visit2"
},
{
"$type": "VisitOrder",
"id": 3,
"locationId": "Visit3"
},
{
"$type": "VisitOrder",
"id": 4,
"locationId": "Visit4"
},
{
"$type": "VisitOrder",
"id": 5,
"locationId": "Visit5"
}
];
var planToursRequest = {
"locations": locations,
"orders": orders,
"fleet": {
"vehicles": [
{
"ids": ["vehicle1"]
}
]
},
"distanceMode": {
"$type": "DirectDistance"
},
"storeRequest": true
};
return planToursRequest;
}
function displayLocations(locations) {
for (var i = 0; i < locations.length; i++ ){
var location = locations[i];
displayLocation(location, 'Location id: ' + location.id, -1);
}
}
function displayLocation(location, tooltip, stopIndex) {
var point = getLatLngOfLocation(location);
var marker = L.marker(point, {
icon: getCircleIcon(24, xServerUrl + '/dashboard/Content/Resources/Images/Showcases/circle_gray.png'),
title: tooltip
}).addTo(map);
if(stopIndex >= 0) {
marker.bindTooltip('Stop ' + stopIndex, {permanent: true});
}
markers.addLayer(marker);
}
function displayStops(tours, locations) {
for(var tourIndex = 0; tourIndex < tours.length; tourIndex++ ){
var tour = tours[tourIndex];
for(var tripIndex = 0; tripIndex < tour.trips.length; tripIndex++ ){
var trip = tour.trips[tripIndex];
for(var stopIndex = 0; stopIndex < trip.stops.length; stopIndex++ ){
var stop = trip.stops[stopIndex];
var tooltip = "Location id: " + stop.locationId + "\nStop index: " + stopIndex + "\nTrip id: " + trip.id;
var location = getLocationById(locations, stop.locationId);
displayLocation(location, tooltip, stopIndex);
}
}
}
}
function getLatLngOfLocation(location) {
return getLatLngOfRouteLocation(location.routeLocation);
}
function getLatLngOfRouteLocation(routeLocation) {
var coordinateX = routeLocation.offRoadCoordinate.x;
var coordinateY = 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, locations){
var bounds = new L.latLngBounds();
var layer = null;
for(var i = 0; i < tours.length; i++){
var tour = tours[i];
var latLongsOfTour = getLatLongsOfTour(tour, locations);
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 of " + tour.vehicleId, {direction: 'top'});
bounds.extend(layer.getBounds());
}
displayStops(tours, locations);
map.fitBounds(bounds);
}
function getLatLongsOfTour(tour, locations) {
var trips = tour.trips;
var latLongsOfTour = [];
var locationLatLong = null;
if (tour.vehicleStartLocationId != null){
locationLatLong = getLocationLatLongOfLocationId(locations, 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(locations, locationIdOfStop);
latLongsOfTour.push(locationLatLong);
}
}
if (tour.vehicleEndLocationId != null){
locationLatLong = getLocationLatLongOfLocationId(locations, tour.vehicleEndLocationId);
latLongsOfTour.push(locationLatLong);
}
return latLongsOfTour;
}
function getLocationLatLongOfLocationId(locations, locationId){
var location = getLocationById(locations, locationId);
var coordinate = getLatLngOfLocation(location);
return coordinate;
}
function getLocationById(locations, locationId){
for(var i = 0; i < locations.length; i++){
var location = locations[i];
if(locationId == location.id){
return location;
}
}
}
function planAndChangeSpecificTours() {
var planToursRequest = getPlanToursRequest();
xtour.planTours(
planToursRequest,
function(planToursResponse, exception) {
displayLocations(planToursRequest.locations)
var applyChangeToursRequest = false; //HINT: Set to true to change tours with changeToursRequest.
if (applyChangeToursRequest) {
changeSpecificTours(planToursRequest, planToursResponse);
}else{
displayTours(planToursResponse.tours, planToursRequest.locations);
print('Stored request id: "' + planToursResponse.storedRequestId + '"');
}
}
);
}
function getChangeToursRequest(planToursRequest, planToursResponse) {
var insertionBeforeSpecifiedPosition = {
"$type": "InsertionBeforeSpecifiedPosition",
"position": 0 //HINT: Change to any other 0-based stop index within the trip ( < number of stops in trip).
};
var insertionAfterSpecifiedPosition = {
"$type": "InsertionAfterSpecifiedPosition",
"position": 0 //HINT: Change to any other 0-based stop index ( < number of stops in trip).
};
var moveOrdersAction = {
"$type": "MoveOrdersAction",
"storedRequestId": planToursResponse.storedRequestId, //HINT: Change to current storedRequestId to further modify the tour.
"orderIds": [planToursRequest.orders[3].id],
"targetTripId": planToursResponse.tours[0].trips[0].id,
"visitInsertionPosition": insertionAfterSpecifiedPosition //HINT: Change to insertionBeforeSpecifiedPosition, e.g. if you want to move order to start of trip.
};
var removeOrdersAction = {
"$type": "RemoveOrdersAction",
"storedRequestId": planToursResponse.storedRequestId, //HINT: Change to current storedRequestId to further modify the tour.
"orderIds": [planToursRequest.orders[0].id] //HINT: Change to any other order of request.
};
var changeToursRequest = {
"changeToursAction": moveOrdersAction //HINT: Change to removeOrdersAction.
};
return changeToursRequest;
}
function changeSpecificTours(planToursRequest, planToursResponse) {
var changeToursRequest = getChangeToursRequest(planToursRequest, planToursResponse);
xtour.changeTours(
changeToursRequest,
function(changeToursResponse, exception) {
displayTours(changeToursResponse.tours, planToursRequest.locations);
print('Stored request id: "' + changeToursResponse.storedRequestId + '"');
}
);
}
planAndChangeSpecificTours();