You can easily use the TsCloud API using only javascript. It is very
easy to perform ajax requests with any popular javascript framework. We
will show in this chapter how to do it with JQuery.
First of all, you will need to include Jquery and tsCloudApi.js :
<html>
<head>
<title>TsCloud Api Tester</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js" />
<script src="https://api.geoconcept.com/ToursolverCloud/combo/toursolver/tsCloudApi.js" />
</head>
<body>
...
</body>
</html>
WarningDepending on your geographical area, the ToursolverCloud url may be different.
You will then use de tsCloudApi object to communicate with TsCloud servers. Before sending any request, you must initialize the object with your api key :
<script>
var myApiKey = "this is a fake key";
tsCloudApi.init(myApiKey);
</script>Launch an optimisation
To start an optimization, you will have use the optimize method of the
tsCloudApi object :
<script>
var myApiKey = "this is a fake key";
tsCloudApi.init(myApiKey);
tsCloudApi.optimize(optimRequest,startOptimSuccessHandler,errorHandler);
</script>tsCloudApi performs asynchronous ajax requests. Therefore, you have to implement handler functions that will be called when the response will be available (startOptimSuccessHandler and errorHandler in the above example). optimRequest is the object containing the list of depots (optional), the list of resources, the list of orders and the optimization options (please refer to the definitions chapter for more details).
Here is a minimal example with one resource and two orders :
<script>
var optimRequest = {
"depots": [],
"resources": [{
"x": 2.33683,
"y": 48.86255,
"id": "Robert",
"workStartTime": "08:00",
"workEndTime": "18:00",
"lunch": {
"start": "12:00",
"end": "14:00",
"duration": "01:00",
},
"workingDays": "1-5",
"capacities": [1000.0],
"workPenalty": 20.0,
"overtimePenalties": [0.0],
"travelPenalty": 2.0,
}],
"orders": [{
"id": "ORDER-1",
"label": "HOSPITAL",
"quantities": [2.0],
"fixedVisitDuration": "00:30",
"timeWindows": [{
"beginTime": "08:00",
"endTime": "10:00"
}],
"x": 2.348433,
"y": 48.853661
}, {
"id": "ORDER-2",
"label": "EMERGENCIES",
"quantities": [2.0],
"fixedVisitDuration": "00:25",
"timeWindows": [{
"beginTime": "10:00",
"endTime": "12:00"
}],
"x": 2.347802,
"y": 48.854687
}],
,
"options": {
"vehicleCode": "car",
"stopTime": "00:01",
"stopTimeWithoutImprovement": "00:01",
"maxOptimDuration": "00:01",
"stopCondition": 1,
"reloadDuration": "00:45"
},
"countryCode":"FR"
};
</script>Here is how you could write your error handler :
<script>
function errorHandler(jqxhr,status,errorThrown) {
if (jqxhr.status == 403) {
console.log("Bad key code ?");
} else if (jqxhr.status == 429) {
console.log("oups, slow down please : " + jqxhr.statusText);
} else if (jqxhr.status != 200) {
console.log("sorry, an error occured : " + jqxhr.statusText);
try {
if (jqxhr.responseText) {
var resp = JSON.parse(jqxhr.responseText);
if (resp.message) {
console.log(resp.message);
}
}
} catch (e) {
console.log("could not find details about the error");
}
}
}
</script>If your api key is not valid, you will get a 403 http code. As explained in authentication chapter, you may received a 429 http code if you send to many requests in a short time. If you misspelled an object attribut or for any other technical problem, you may receive a 500 http code.
Here is how you could write your success handler, which must retrieve the taskId and start the status polling process that we will detail in the next section :
<script>
var taskId = null;
function startOptimSuccessHandler(data,status,xhr) {
if (data.status == "OK") {
taskId = data.taskId;
console.log("optim launched. taskId is " + taskId);
getStatus();
} else {
console.log(data.message);
}
}
</script>Optimization status polling
During the optimization process, you will be able to follow the cost
evolution and the state of the optimization by calling the getStatus
method of the tsCloudApi object :
<script>
tsCloudApi.getStatus(taskId,getStatusSuccessHandler,errorHandler);
</script>You can use the same errorHandler function we used for the optimization request. Your getStatusSuccessHandler should mainly check the optimization state, but it can be used to track the costs evolution. It is quite easy to build graphs to show this evolution. Here is an example using canvasjs. First of all, you must include canvasjs :
<html>
<head>
<title>TsCloud Api Tester</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/canvasjs/1.7.0/canvasjs.min.js"></script>
<script src="https://api.geoconcept.com/ToursolverCloud/combo/toursolver/tsCloudApi.js"></script>
</head>
<body>
...
</body>
</html>Then, here is an example of getStatus command called every second, with
the success handler used to build graphs, and to trigger the result
download (that we will detail in a further section) :
<script>
var getStatusTimeout = null;
var graphIndex;
var graphCost = null;
var graphDist = null;
function getStatus() {
if (getStatusTimeout != null) {
clearTimeout(getStatusTimeout);
}
getStatusTimeout = setTimeout(function() {
tsCloudApi.getStatus(taskId,getStatusSuccessHandler,errorHandler);
},1000);
}
function appendGraphData(data) {
if (graphCost == null) {
graphIndex = 0;
var options = {
title: {
text: "Cost evolution"
},
animationEnabled: true,
data: [
{
type: "spline", //change it to line, area, column, pie, etc
dataPoints: [
{ x: graphIndex, y: data.currentCost }
]
}
]
};
graphCost = new CanvasJS.Chart("chartContainerCost",options);
options = {
title: {
text: "Distance evolution"
},
animationEnabled: true,
data: [
{
type: "spline", //change it to line, area, column, pie, etc
dataPoints: [
{ x: graphIndex, y: data.currentDriveDistance / 1000 }
]
}
]
};
graphDist = new CanvasJS.Chart("chartContainerDist",options);
} else {
graphIndex++;
graphCost.options.data[0].dataPoints.push({ x: graphIndex, y: data.currentCost });
graphDist.options.data[0].dataPoints.push({ x: graphIndex, y: data.currentDriveDistance/1000 });
}
graphCost.render();
graphDist.render();
}
function getStatusSuccessHandler(data,status,xhr) {
if (data.optimizeStatus == "ERROR") {
console.log("sorry, an error occured");
} else if (data.optimizeStatus == "TERMINATED") {
console.log("optimization done");
setTimeout("getResult()",10);
} else {
if (data.status == "OK") {
console.log(data.optimizeStatus + " | current cost : " + data.currentCost + " | current distance : " + data.currentDriveDistance);
if (data.optimizeStatus == "RUNNING") {
appendGraphData(data);
}
} else {
console.log("sorry, an error occured : " + data.message);
}
getStatus();
}
}
</script>Stopping an optimization
Even if you must specify a maximum optimization time in the options
objects sent in the optimization command, you can stop an optimization
at any time. You can do this easily using the stop method of the
tsCloudApi object :
<script>
tsCloudApi.stop(taskId,stopSuccessHandler,errorHandler);
</script>Stopping an optimization may take a few seconds and when stopSuccessHandler is called, it only means that your stopping request as been aknowleged. Therefore, you still have to call the getStatus method until the status says that the optimization is terminated.
<script>
function stopSuccessHandler(data,status,xhr) {
if (data.optimizeStatus == "ERROR") {
console.log("sorry, an error occured");
} else {
getStatus();
}
}
</script>Here we use the getStatus function detailed in the above section.
Retrieving the optimization result
Once your optimization is terminated, you can retrieve the result using
the getResult method of the tsCloudApi object :
\<script>\
function getResult() \{
tsCloudApi.getResult(taskId,showResult,errorHandler);
}
\</script>The result will contain the resource and time assignment for each planned order, the list of unplanned orders and a list of warnings.
<script>
var lastResult;
function showResult(data,status,xhr) {
if (data.status == "OK") {
lastResult = data; //let's store the result for later ...
displayResult(data);
} else {
appendTrace("sorry, an error occured : " + data.message);
}
}
function displayResult(data) {
$('#resultStats').append("<p>nb of unplanned orders : " + data.unplannedOrders.length+"</p>");
if (data.warnings && data.warnings.length > 0) {
for (var i=0;i<data.warnings.length;i++) {
$('#resultStats').append("<p>warning on " + data.warnings[i].id + " : " + data.warnings[i].message + " (" + data.warnings[i].value + ")"+"</p>");
}
}
var order = null;
for (var i=0;i<data.plannedOrders.length;i++) {
order = data.plannedOrders[i];
$('#resultPlanning').append("<p>order " + order.stopId + " as been planned on resource " + order.resourceId + " on day " + order.dayId + " at " + order.stopStartTime +"</p>");
}
}
</script>Exporting result to operational planning
You can also export your optimization result to operational planning so that mobile resources could browse it on the field through a mobile app. Note that you must have previously created your mobile identifiers through TsCloud app. If you are trying to export to a period that already contains data, you will get an error and have to force the export, which will erase previous data first.
<script>
function doExportToOperational(taskId,exportStartDate,force) {
var operationalExportParams = {
resourceMapping : [
{
id:"Robert",
operationalId:"[email protected]"
}
],
startDate : (exportStartDate)?exportStartDate:new Date(),
force : force,
taskId : taskId
};
tsCloudApi.exportToOperationalPlanning(operationalExportParams,showExportResult,errorHandler);
}
</script>Integrating TourSolver result page in your app
Once your optimization is finished, you can use Toursolver to show the result. To to this, you will have to retrieve a temporary login token and then open TsCloud in an iframe with this token and the simulationId found in the optimization result.
You can choose to show the full Toursolver UI, or only the result page just passing standalone=true as a parameter of the url.
<iframe id="integrationFrame" style="width:100%;height:800px;border:none;"></iframe>
<script>
var doStandaloneIntegration = false;
var lastResult; //we assume here that you have stored the result obtained earlier in this var
function startIntegration(login,standaloneIntegration) {
doStandaloneIntegration = standaloneIntegration;
//Here we retrieve a temporary token
tsCloudApi.getGatewayToken(login,startIntegrationHandleFunc, errorHandler);
}
function startIntegrationHandleFunc(data,status,xhr) {
//We have received our token, let's build the url to display the simulation result
let url = 'https://app.geoconcept.com/ToursolverCloud/ts/login?';
url += 'token='+data.token;
url += '&simulationId='+ lastResult.simulationId;
if (doStandaloneIntegration) {
url += '&standalone=true';
}
//Here we will set the src of an existing iframe (having id='integrationFrame') to display Toursolver result page in it
$('#integrationFrame').attr('src', url)
}
</script>If you can also specify the day and the resource to be shown :
https://app.geoconcept.com/ToursolverCloud/ts/login?token=xxx&standalone=true&day=1&resource=Robert
You can open a result in read only mode, all actions are disabled in this mode :
Url parameters description :
| Type | Parameter | Default | Description |
|---|---|---|---|
| Token | string | token obtained through the gatewayToken webservice | |
| Standalone | boolean | false | if true, the Toursolver header and footer are hidden |
| readOnly | readOnly | false | if true, result page is in read only mode, only export actions are available |
| day | number | if provided, data will be filtered to show only the specified day. Use day=1 to see only the data of the first day (works only with readOnly=true) | |
| resource | string | if provided, data will be filtered to show only the specified resource. Use resource=Robert to see only the data of the resource called Robert (works only with readOnly=true) |
Integrating Toursolver fulfillment page in your app
The process is the same, you must generate a token calling the gatewayToken webservice, and call this url in your iframe : https://app.geoconcept.com/ToursolverCloud/ts/login?token=xxx&fulfillment=true
standalone and readonly parameters are also supported for fulfillment page integration.
Url parameters description :
| Parameter | Type | Default | Description |
|---|---|---|---|
| token | string | token obtained through the gatewayToken webservice | |
| standalone | boolean | false | if true, the Toursolver header and footer are hidden |
| readOnly | boolean | false | if true, result page is in read only mode, only export actions are available |
| organization | string | organization identifier. if provided, only data of the specified organization will be visible | |
| resource | string | if provided, data will be filtered to show only the specified resource. Use resource=robert%40mycompany.com to see only the data of the mobile user [email protected] |