4. Optimizing Routes

We support the following modes of shipments:

  • Deliveries only
  • Pickups only
  • Deliveries before pickups
  • Mixed deliveries and pickups
  • All the above variants in a depot reload mode, i.e. a vehicle can have multiple routes with reloads at the depot
  • Directs: The delivery is picked up at a stop not at the depot

Routes optimization considers the following parameters:

  • The fix costs per vehicles as well as the variable costs per km
  • The time windows of the shipments
  • The expected length of stops calculated from fixed time per vehicle and variable times per shipment
  • The velocity profiles of the vehicles

We also consider:

  • Load capacity of the vehicles (net load, pallet places, load meters, ADR)
  • Maximal duration of the route including breaks
  • Available vehicles (vehicles types) at a stop

Optional:

  • You can distinguish between load classes
  • You can prioritize shipments with high, medium, low priority

Start optimizing

Now that we have addresses, vehicles and shipments, we can start optimizing routes.

This process is started with a call to /calcroute/optimized/timewindow.

curl --request POST \  
     --url 'https://<your company name>.prd.smartlane.io/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://<your company name>.prd.smartlane.io/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, provided you have enough vehicles.

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.

Asynchronous routing

In the above example, we set the async parameter to false, but we recommend to use the default setting: async=true.
Why is that so? 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 geo-information from external sources. A lot of processing power is needed for the optimization process. Therefore it runs as a background process. In order to get the result you start polling our API with the process ID until you get the result.

You get the process ID if do the above call with the async parameter set 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.

Successful Routing

{'data': {'failed_addresses': [],
             'message': '',
             'min_tour_start': '2022-07-03TT15: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.

The data sections provides details of the optimization result:

failed_addresses: This can happen e.g. due to failed geo-coding of the addresses

unsuccessful_deliveries: Is the container for shipments that have been dropped because they did not meet the constraints for various reasons.

notes: Will help you understand why shipments failed.

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.

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": "2022-07-03T11:30:00+00:00",
      "pdt_to": "2022-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": ["2022-07-03T10:30:00+00:00",
           "2022-07-03T12:30:00+00:00"],
      "pdt_to": ["2022-07-03T11:30:00+00:00",
         "2022-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": "2022-07-03T09:30:00+00:00",
      "departure_time_to":   "2022-07-03T10:00:00+00:00",
      "return_time_to":         "2022-07-03T21:00:00+00:00",
      "max_tour_duration": 600,
      "depot_location": {
        "street": "Grafinger Str.",
        "housenumber": "6",
        "postalcode": "81671",
        "city": "München"}
    }
  ],
  "departure_time_from": "2022-07-03T08:30:00+00:00",
  "departure_time_to":   "2022-07-03T11:00:00+00:00",
  "return_time_to":      "2022-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': '2022-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}