Tutorial

This tutorial shows how to consume Rinkai's Map API REST/Json with javascript inside a browser. For a java SOAP example please see the last section.

Minimal
Geocoding
Displaying a map
Adding time windows and capacities
Dealing with errors
Further

Minimal

NOTE: The minimal section is also available as a standalone page

Let 's start with the simplest case possible. We create a VrpRequest to define the depot plus 3 locations to be visited, without any constraints such as timewindows, capacities, etc.

Request



  // define the optimization request
  var optRequest =
    {
      "depot": { 
        // the depot is always the starting point
        "location": {
          "coordinates": {
            "x": 13.6438122354029,
            "y": 50.5146082763126
          }
        }
      },
      "locationsAndOrders": [
        {
          "locationInfo": {
            "location": {
              "coordinates": {
                "label": "",
                "x": 13.5636822222738,
                "y": 50.4025882579593
              }
            }
          }
        },
        {
          "locationInfo": {
            "location": {
              "coordinates": {
                "x": 13.5431322189068,
                "y": 50.3294782459803
              }
            }
          }
        },
        {
          "locationInfo": {
            "location": {
              "coordinates": {
                "x": 13.7914225958854,
                "y": 50.3569882504888
              }
            }
          }
        }
      ]
    };

 
Next we send the optimization request to Rinkai's Map API server. The simplest way to do that is to call the getOptimization method which is synchronous, i.e. the server will not respond until the results are available. The following snippet perform the request, using jquery:

  


  
 // function to call the rest API against a given request 
  function callServer(op,request, callback) {
     $.post({
        url: "http://api.rinkai.eu/map/v1_3c/api/rest/"+op+"/?key=test",  
          contentType: "application/json",
          data: JSON.stringify(request, 2),
          context: document.body
        }).done(function (response) {
          // just dump the response to the console log
          console.log(JSON.stringify(response, null, 2));
          if (callback)
            callback(response);
        });
 }

    // call the rest API for optimization 
      function optimize(request, callback) {
        callServer("optimization/sync",request,callback);
      }


  // perform the request
  optimize(optRequest);

Response

Finally we get the VrpResponse from the server. The response contains a list of routes, and each route is assigned to a vehicle. Since we did not specify any vehicles in the request, the server assumed we have one vehicle, with default constraints and costs.

The main results is the routes' sequence. In this case, because there were no time nor capacity constrants, all the orders are assigned onto a unique route and the sequence is in the field response.plan.routes[0].stops.

Other fields which should be considered before processing the result are:

Please see the Dealing with errors section for more information.

Geocoding

Automatic geocoding

The simplest way to geocode is to take advantage of the automatic geocoding, i,e, geocoding is performed everytime a location is specified with an address but without coordinates.

Please note that although simpler, this is not the recommended way to perform geocoding (see next section).

The following request will yield the same result as the previous one, using automatic geocoding:

Request



  // define the optimization request
  var optRequest =
    {
      "depot": {
        "location": {

          // defining a location with an address but without coordinates
          // will trigger the geocoding for this location
            "address": {
                 "country": "cz",
                 "city": "Most",
                 "street": "Chomutovská",
                 "houseNumber": " 2261/2",
                 "postCode": "43401"
            }
          }
        
      },
      "locationsAndOrders": [
        {
          "locationInfo": {
            
            // With both an address and coordinates
            // the coordinates is used
            "location": {
              "address": {
                "country": "cz",
                "city": "Velemyšleves",
                "postCode": "43801"
              },
              "coordinates": {
                "label": "",
                "x": 13.5636822222738,
                "y": 50.4025882579593
              }
            }
          }
        },
        {
          "locationInfo": {
            "location": {
              "coordinates": {
                "x": 13.5431322189068,
                "y": 50.3294782459803
              }
            }
          }
        },
        {
          "locationInfo": {
            "location": {
              "address": {
                "country": "cz",
                "city": "Louny",
                "street": "Suzdalské nám. ",
                "houseNumber": "2281",
                "postCode": "44001"
              }
            }
          }
        }
      ]
    };

 

Response


Geocoding as a separate operation

Because geocoding may yields uncorrect results, mostly due to incomplete or wrongly typed addresses, it is recommended to perform geocoding as a separate operation, before the optimization. This enables to assess the quality of the geocoding results.

GeocodingResponse contains indicators of the quality, such as area, distance from the depot, whether the location is routable or not, etc. With these indicators, you may filter the results programatically. You can also choose for example to display some of them to the user for further validation. A GeocodingRequest contains a list of locations to be geocoded (Note that if the coordinates field is already populated, geocoding wont be performed) and a reference location for checking the routability of the results.

Request



  // define the geocoding request
  var geoRequest =
    {
      "refLocation": { 
        
          "coordinates": {
            "x": 13.6438122354029,
            "y": 50.5146082763126
          }
        
      },
    
      "addresses": [
        // address specified using free text
        {
          // optional id
          "id":"123abc",
          "freeText": "12,Pekarska,Praha,Czech Republic"
        },
        // address with detailed fields - yields slightly better results
        {
          "houseNumber":"12",
          "street": "Pekarska",
          "city":"prague",
          "country":"Czech Republic"
          
          
        }
        ]
      //, "basicVehicleInfo":{}, // not specified here, so a default vehicle will be used

    };

 

Using previously defined function callServer. the request is run on the server with:



    // perform the request
      callServer("locate",geoRequest);

Response


Displaying a map

Next we will display a map with the orders.

Retrieving and decoding the traces

To also display the road planned from one order to another, we set the field optRequest.detailedTraces=true. This will cause the server to include in the optimalization response the full list of points that form the traces on the map.

The actual traces are returned encoded in the google polyline format. In order to encode/decode this format onto Coordinates which can be used in Rinkai's Map API, please include the polyline.js snippet in your code, which will define the top functions decodePolyline() and encodePolyline()

Choose a map library

In order to display a map you can either: Here we show the steps to display the map with Leaflet. We define a bunch of helpers to translate data from Rinkai's Map API to Leaflet, and to get a textual representation from an order location.

/**
 * Transform an array of Coordinates (from Rinkai's Map API) into an array of coordinates for leaflets
 */
function coordinates2leaflets(coords) {
  return coords.map(function (coords) {
    return  [coords.y, coords.x]
  });
}

/**
 * Transform Coordinates (from Rinkai's Map API) into coordinates for leaflets
 */
function coordinates2leaflet(coords) {
  return [coords.y, coords.x];
}



/**
 * return a short string representation of the location, using fields of the address if defined, otherwise using the coordinates 
 */
function location2string(location) {
  return location.address ? (location.address.street + "," + location.address.city) 
  : ( location.coordinates.y.toFixed(3)+","+location.coordinates.x.toFixed(3))
}

Now the following function will create a Leaflet map showing the results from the optimalization response:



  // hack: keep a reference to the leaflet map, so that we dont instantiate it twice
  var dynamicMap;


function leafletMap( optResponse) {
  console.log("leafletMap()")
  if (dynamicMap) dynamicMap.remove();

  dynamicMap = L.map('leafletmap');
 

  // add the tiles, that form the background map, with roads, cities, rivers, etc
  L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors'
  }).addTo(dynamicMap);

  // add stops as waypoints
  optResponse.plan.routes.forEach(function (route) {
    route.stops.forEach(function (stop) {
      console.log(stop);
      L.marker(coordinates2leaflet(stop.location.coordinates)).addTo(dynamicMap).bindPopup(location2string(stop.location));
      //,{autoClose:false}).openPopup()
    });
  });

  // add depot as waypoint
  L.marker(coordinates2leaflet(optResponse.request.depot.location.coordinates)).addTo(dynamicMap).bindPopup("DEPOT", { autoClose: false }).openPopup();

  // add polylines
  if (optResponse.request.detailedTraces) {
    optResponse.plan.routes.forEach(function (route, idx) {
      var latlngs = route.legs.reduce(function (a, b) {
        return a.concat(coordinates2leaflets(decodePolyline(b.coordinatesPolyline)));
      }, []);
      var polyline = L.polyline(latlngs, { color: "red"}).addTo(dynamicMap);
    });
  }

  // zoom the map to the polyline
  console.log(optResponse.plan);
  if (optResponse.plan.bbox) 
      dynamicMap.fitBounds([coordinates2leaflet(optResponse.plan.bbox.topLeft), coordinates2leaflet(optResponse.plan.bbox.bottomRight)], { padding: [50, 50] });

}
   

Leaflet map

Adding time windows and capacities

Time windows

TimeWindows have a start and end time and are specified in hours/minutes/seconds since midnight. It is also possible to specify them using only seconds since midnight, e.g. {"hour":1,"minutes":0,"seconds":0} is the same as {"seconds":3600}

Time windows can be specified on each order's locationInfo , to specify the Depot opening times, and each Vehicle 's availability.

By default, a time window is available the whole day. The exception is the field locationInfo.timeWindow2 which by default is considered as not available.

Workload of vehicles

A VehicleLoad object can be used inside the vehicle.maxLoad field to specify the vehicle maximum load (i.e. maximum Volume, Weight and a 3rd arbitrary dimension)

On the order side, the load is specified on the OrderInfo object using the fields load and loadBack.

Please note that if you define an order with a dimension such as weight or volume, you will also need to specify the vehicles' maxLoad.

Special requirements

You can also specify that an order should only be picked up/delivered with a vehicle with a specific ability, e.g. 'crane' or 'fridge'.

{
   ...
  "vehicles":[{
      "id": ...
      "vehicleTypes":["crane","fridge"],

      ...

  "locationsAndOrders": [
    {
      "orderId": ...
      "vehicleTypeRestrictions":["fridge"]
      ...

Dealing with errors

There are 3 kind of errors:

Low Level

Low level error will cause the server to reply with a 4xx or 5xx code. For example, sending a request which is missing required fields, has extra unknown fields, or wrongly typed, will cause such an error. In this case, the response header 'exception' will be populated with the error message. Note that this messag is just provided as a help during development and is not meant to be displayed to end users. When using asynchronous optimization, some Low Level errors are instead returned as General Errors.

General Error

This error occurs when the optimization plan cannot be generated at all. The response will have a status 200, and will contain a VrpResponse object with the field generalError populated

Non planned Orders

This error occurs when some of the orders could not be included into the optimization plan. As for the general error, the response will have a status 200, and contain a VrpResponse. The field invalidLocations will contain an array of LocationAndOrderErrorMessages for the non planned orders.

Going further

Using asynchronous optimization

Calling the optimization asynchronously provides 2 benefits: You can look at the source of the sample page to see an example of asynchronous usage.

Improving computation speed

Try to limit the number of different BasicVehicleInfo . This is because for each vehicle a distance matrix must be calculated prior to the optimization process. When two vehicles have identical vehicle.basicVehicleInfo field, then the distance matrix is calculated only once.

Using SOAP

Rinkai's map API is also available as a SOAP endpoint. For a simple java/mvn sample please unzip this archive, and run with:
mvn package exec:java