Smartlane API

The Smartlane API Developer Hub

Welcome to the Smartlane API developer hub. You'll find comprehensive guides and documentation to help you start working with the Smartlane API as quickly as possible, as well as support if you get stuck. Let's jump right in!

Get Started    

3. Optimizing Routes

There we are - optimizing routes - a major feature of our api. While we offer solutions for different routing variants, in this tutorial we focus on a basic one, the vehicle routing problem with time windows.

In the last chapter we discussed deliveries, the basic entities or stops, that define a route. But don't worry you're fine to start reading right here, the first two chapters just provided some background information of our API.

So let's start with an easy example request to /calcroute/optimized/timewindow. Please note the async=False parameter in the url (more on this below).

curl --request POST \  
     --url 'https://dispatch.smartlane.io/your_company_name/api/calcroute/optimized/timewindow?async=False' \
     --header 'Content-Type: application/json' \
     --header 'Authorization: JWT your.Long_Randomfghxlcnebem.Secure_AccessToken' \
     --data  '{"deliverydata": [{"street": "Grafinger Straße",
                             "housenumber": "6",
                                 "postalcode": "81671",
                                 "city": "München"}]}'
import requests

auth_header = {"Authorization": "JWT your.Long_Randomfghxlcnebem.Secure_AccessToken"}
api_url = "https://dispatch.smartlane.io/your_company_name/api"

url = api_url + "calcroute/optimized/timewindow"
data = {"deliveryids": [2, 9]}

response = requests.post(url, headers=auth_header, json=data)

You should get a response that looks something like this:

{"success": true,
 "route_ids": [1],
 "route_objects": [],
 "messages": [],
 "status": 200}

For now, it's only important that you receive a "success": true and list of route_ids, which - for a single delivery - of course only contains a single route. If your deliveries cannot be planned as a single route, it's automatically split up to multiple routes.

Having the route's id, we can now request the detailed information with a GET to /route/route_id or some basic information with a call to /routemetadata/route_id. You'll get back quite a bit of information on these ones, and we won't discuss the details at this point. But if you're curious there is some documentation of the individual properties in the API reference. It's also worth checking out the Datatypes site

Now I don't need to mention, that there isn't much to optimize for route with a single delivery as in this dummy example. So let's get started with the real deal, and discuss how to start an asynchronous routing process.

Asynchronous routing

In the above example, we set the async parameter to false. But this parameter is basically just there for testing purposes and not well maintained, so we absolutely don't recommend using it
and you should always stick to the default async=true value.

You can probably guess why this is the case, but let's sum it up quickly. The calculation of routes for real world scenarios with hundreds of deliveries can take some time. First of all we need to gather a lot of geoinformation from external sources. And second, quite a bit of processing power is needed for the optimization process. Therefore it runs as a background process in our process scheduler, as we don't want to risk timeouts (even tough, when the number of deliveries stays reasonable it's usually done in a couple of seconds). We have experimented with Websockets, and basically provide this solution. But it turns out, that most of our customers are happy with a solution often referred to as long polling. Which might be described in short as: The routing request responds with a process_id, which will the be used to compose a URL, which you can keep on polling (like every couple of seconds) until it provides the actual results are provided.

📘

Long Polling

Our standard method for obtaining the results of our routing algorithm is often referred to as long polling. By searching the web, you're likely to find a cookbook solution for your programming language and framework.

So please try the above request to /calcroute/optimized/timewindow again without the async parameter (or set it to true). The response should now look something like this:

{"status": "started",
 "message": "routing process is started",
 "process_id": "d0063042-cb15-11e8-9707-0242ac130003"}

The relevant part here is the process_id, which can now be used to get the actual result of the process with a call to /process/status/process_id/current (like in api/process/status/d0063042-cb15-11e8-9707-0242ac130003/current). This returns the status of the process, it should look something like this

{'process_id': 'd0063042-cb15-11e8-9707-0242ac130003',
 'progress': 0.1,
 'state': 'PENDING',
 'task': None}

when the process is finished, the state is either 'SUCCESS' or 'FAILURE'. So let's take a look at a successful response first

{'data': {'failed_addresses': [],
             'message': '',
             'min_tour_start': '2018-10-12T15:57:39.727+02:00',
             'notes': [],
             'route_ids': [1],
             'route_objects': [],
             'success': True,
             'successful_routes': 1,
             'unsuccessful_deliveries': []},
 'process_id': 'd0063042-cb15-11e8-9707-0242ac130003',
 'progress': 1.0,
 'state': 'SUCCESS',
 'task': None}

Again, the most important information is the list of route_ids which you can use to request the full information about the routes via the /route/route_id endpoint. Another important bit of information considers particular deliveries that could not be taken into account for the route planing. On the one hand this can be due to failed geocoding of the addresses due to wrong wrong or incomplete information listed in failed_addresses, on the the other hand if something else went wrong with a particular delivery listed in unsuccessful_deliveries. The result_parameters give information about assumptions that were necessary to provide a solution at all, like the timewindows_relaxed, which soften the time constrains of the deliveries (only if configured).

Unsuccessful Routing

Sometimes the routing process cannot provide a proper solution and is therefore considered a FAILURE. This can be due to different reasons.

Sometimes, especially when users are just starting to get familiar with the API, errors arise because unprocessable input is provided. We're putting in quite a bit effort, to make our API as fault tolerant as possible. But sometimes rigor is required to avoid ambiguity. In this case we usually provide an err_id with a corresponding human readable message.

Sometimes it's not possible to calculate a route under the given constraints, even if formally valid input is provided. Just think about an obvious example: If two deliveries are supposed to be shipped to two cities hours away from each other within very narrow time windows, the driver just cannot arrive in time. Our policy here is generally to rather not provide a route, than one that is not feasible in practice. In this case it's hard for the algorithm to decide, which constrains are the cause of the failure, but do our best to provide hints in a message as well as structured data, about the constrains that should be reconsidered (no doubt, with enough vehicles everything schedule is possible).

Yet, every once in a while, errors emerge that we haven't even considered. In this case we have to fall back to rather general error messages, but we're always happy provide support and learn more about your specific use case.

Vehicle configuration

An important consideration to take into account, when optimizing routes is the fleet of vehicles that are available when planing a route. This information can be given to the API as RESTful parameter (see below), in the calcroute endpoint, but let's first consider the data structure in isolation and add a vehicle to the database via the /vehicle endpoint, with the content looking something like this:

{"name": "vehname1",
 "plate": "MUC1",
 "driver_id": null,

 "capacity_1": 500,
 "capacity_2": 300,
 "capacity_3": 200,
 "daily_rate": 50.0,
 "km_rate": 0.40,
 "traveltime_factor": 1.0,

 "departure_time_from": "09:30:00+00:00",
 "departure_time_to":   "10:00:00+00:00",
 "return_time_to":      "21:00:00+00:00",
 "max_tour_duration": 600,
 "depot_location_id": 1
}

First of all, the vehicle has a mandatory unique name, for easier identification by humans.
The first conceptually important property is the depot_location_id, where the tour starts for this particular vehicle, in case it's not assigned the routing parameter startaddress_id or the default depot is used as a fallback.

The 3 capacities capacity_1, capacity_2 and capacity_3 define the maximum loads of a vehicle corresponding to the 3 load types load, load_2 and `load_3, given in the individual deliveries. They can be of any dimension of your choice, e.g. weight in grams or kg, volume or loading meter, just be aware that they need to be integers, as required by the routing algorithm,
so please choose a unit that is small enough to resolve your required precision, as digits after the comma gets cutoff.

The daily_rate and km_rate define two important parameters used in the cost function, we are optimizing, i.e. is supposed to be as small a possible, taking into account the given constraints. The daily_rate defines the fixed cost of the vehicle, which is basically the cost for a vehicle to be used at all. The km_rate defines the variable cost of the vehicle, payed for each kilometer driven. To be correct, we actually optimize routes for the shortest time, not driving distance. This often also results in a more ecological footprint, but the two not necessarily relate one to one. But the total cost of a tour will be calculated from this. It might be worth noting, that the ration between the daily_rate and the km_rate can in some circumstances have strong effect, on how many vehicles are used for a tour. They are in arbitrary units, may it be $ or € just make sure you use the same one here.

The traveltime_factor factor multiplied to the vehicle speed. The base speed depends on the vehicle_class, which can be one of car, truck, van, bike, walk. It also defines which roads can be driven (e.g. no trucks on a bike lane ...).

The next set of parameters define the time frame of a tour. departure_time_from and departure_time_to set the time-window between which a driver is supposed to leave at the startaddress/depot. The max_tour_duration and return_time_to define the length of the tour. You're right on point if you spot some redundancy here, but the two are well distinct: while the former defines the actual length of the tour, the latter defines the latest point in time of the end of the tour. With a varying start time, the two can turn out quite different. If both are given, the more restrictive is used.

There are two slight incoherences here: the return_time which is a bit of a misnomer in case there isn't a return to the depot (it's the same in a route response), and the missing return_time_from, which seems rather unnecessary.

A full example

So let's try posting an example to /calcroute/optimized/timewindow that contains everything we've discussed so far

{
  "deliverydata": [
    {
      "postalcode": "80337",
      "city": "München",
      "street": "Lindwurmstr.",
      "housenumber": "74",
      "load": 30,
      "load_2": 8,
      "load_3": 80,
      "pdt_from": "2020-07-03T11:30:00+00:00",
      "pdt_to": "2020-07-03T12:30:00+00:00"

    },
    {
      "postalcode": "80337",
      "city": "München",
      "street": "Lindwurmstr.",
      "housenumber": "77",
      "load": 40,
      "load_2": 7,
      "load_3": 60,
      "pdt_from": ["2020-07-03T10:30:00+00:00",
           "2020-07-03T12:30:00+00:00"],
      "pdt_to": ["2020-07-03T11:30:00+00:00",
         "2020-07-03T13:30:00+00:00"]
    }
  ],
  "vehicles": [
    {
      "name": "Vehicle 1",
      "plate": "MUC1",
      "vehicle_class": "car",
      "driver_id": null,

      "capacity_1": 500,
      "capacity_2": 300,
      "capacity_3": 200,
      "daily_rate": 50.0,
      "km_rate": 0.40,
      "traveltime_factor": 1.0,

      "departure_time_from": "09:30:00+00:00",
      "departure_time_to":   "10:00:00+00:00",
      "return_time_to":         "21:00:00+00:00",
      "max_tour_duration": 600,
      "depot_location": {
        "street": "Grafinger Str.",
        "housenumber": "6",
        "postalcode": "81671",
        "city": "München"}
    }
  ],
  "departure_time_from": "2020-07-03T8:30:00+00:00",
  "departure_time_to":   "2020-07-03T11:00:00+00:00",
  "return_time_to":      "2020-07-03T21:00:00+00:00"
}

After the calculation is finished the response from /process/process_id/current should look like

{'data': {'failed_addresses': [],
             'message': '',
             'min_tour_start': '2020-07-03T12:16:00.273942+02:00',
             'notes': [],
             'route_ids': [1],
             'route_objects': [],
             'success': True,
             'successful_routes': 1,
             'unsuccessful_deliveries': []},
 'process_id': '4457f0b8-4968-11e9-b801-0242ac120003',
 'progress': 1.0,
 'state': 'SUCCESS',
 'task': None}

Routing Parameters and Configurations

We provide a large set of configurations and parameters, to customize our algorithms for your specific needs. A detailed description would be beyond the scope of this tutorial, for the beginning we hope our defaults are reasonable.

Accounting

Please contact our sales team for detailed information on our pricing models. The models usually include an accounting by individual delivery units, which are easy to understand in terms of the API.

The unit count increases in case of a newly posted delivery. The same is true for deliveries entering the system via the /calcroute or /upload excel endpoints, as well as in case of changes in the time window of a delivery (by patching the pdts of a delivery /delivery, sorry recycling of old deliveries isn't allowed).

We also provide an option, especially useful for API-users, that accounting units only increase for successfully routed deliveries. In this case, deliveries from an unsuccessful routing attempt (via '/calcroute' or '/upload' endpoints) will be removed from the system.

The current accounting status can be obtained via the /accounting endpoint.

Updated about a year ago

3. Optimizing Routes


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.