Changing Stored Tours

This use case describes how to manipulate parts of a calculated tour in a user-defined way.

Benefits

Prerequisites

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

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, '../../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();