Working with Tour Planning

By Raymond Camden | 08 February 2021

Try HERE Maps

Create a free API key to build location-aware apps and services.

Get Started

Welcome to a (hopefully) gentle introduction to HERE Tour Planning. Tour Planning is an incredibly powerful API, and as such, requires a good deal of... wait for it... planning. In this post I'll introduce you to the API and it's components, go over the items you'll need to be able to use it, and share a simple example.

Do note that Tour Planning is not currently part of our freemium plan, so to use it, you'll need to contact us. You may also request a timed evaluation.

So what exactly is tour planning? At a high level, tour planning helps you manage a set of one more more vehicles (also called a fleet) that need to do certain jobs. These jobs may have restrictions on what time they can be done as well as what order. You can imagine a package delivery company having this problem every day. They have ten vehicles. Each vehicle has a cost for it's operation. They have one hundred deliveries to make, many with specific times. How would you create a plan such that all packages are delivered, on time, at the cheapest price, and even account for traffic? This is where the tour planning API will help. The API can even consider changes to an existing plan. Lost a vehicle due to a breakdown? You can modify an existing plan to get new results.

As mentioned above, making use of this API requires quite a bit of planning ahead of time. Some of the information you'll need for a typical API call includes:

  • The makeup of your fleet, which includes the cost of vehicle operation, the times the vehicle is available, as well as where the vehicle starts it's shift and where it needs to be when done. Your fleet will be specified both as a set of vehicle types as well as the total number of each type.
  • Next, you will need to define the jobs that must be accomplished. This includes the type of job (like making a delivery or picking up an item), locations, how much time is available at that location, what times the job can be accomplished, what order the jobs must be made in, and how important a job is compared to another.

The tour planning developer guide contains full documentation for making requests and is further supported by an API guide as well.

Before getting started, one of the most important things to know is that tour planning supports both synchronous and asynchronous API calls. As you can imagine, the complexity of the work necessary to solve a tour planning problem can be quite high. In order to maintain the performance of the API itself, synchronous calls will have limits on jobs and fleet sizes. Luckily, you'll get a clear error from the response if you go over those limits. (At the time this post was written, the documentation for synchronous requests specifies a limit of 250 jobs and 35 vehicle types.)

For asynchronous calls, the API will return an identifier that lets you then poll for the status at an interval of your choosing. When the work is done, you can then download the solution.

After determining what kind of call you will need to make, you begin by defining the problem. As described above, the problem consists of two portions.

The first is the plan, which is a formal list of jobs that need to be done. Jobs will consist of the following properties:

  • location
  • duration (how much time is spent at the location)
  • demand (a free form value that lets you define custom capacities for your fleet, like the ability to hold a certain number of people or pieces of hardware)

Along with these properties, you can additionally specify times for makling a step as well as your own specific skills and tags associated with the job. Jobs are also associated with a few specific types - deliveries, pickups, and an instance of doing both. Finally, you can name jobs which makes it easier to work with the response.

The next aspect of the problem is the fleet. Again, don't overthink this - your fleet of vehicles could be a fleet of one. A fleet is a set of vehicle types where each type consists of:

  • An identifier
  • The cost to use the vehicle
  • Time shifts the vehicle is available.
  • Size capacity
  • As well as whether or not the type is a car or truck.

Additional properties are also supported. As with jobs, you can include metadata to make it easier to work with the result data, but do not send private and identifying type information like license plate numbers. Finally you'll have an amount for each type.

This is quite a bit to consider, but the documentation has a specific page just for discussing the problem, and again, the API reference will go deeper into the properties and required values.

Once the problem is submitted and sent to the API, you then get to work with your solution. Solutions consist of:

  • Statistics covering cost, distance travelled, duration, and a break down of times spent doing driving, serving jobs, and so forth.
  • A list of "tours" which represent a vehicle type, stops that need to be made, and specific statistics about that tour.
  • You also, potentially, get a list of jobs that could not be completed.

As with the problem definition, there's a great documentation that goes into detail about the solution.

All in all, the problem definition is where you'll spend the bulk of your time. Once it's defined, and once you have a proper understanding of the result, it's pretty trivial to make a call. Here's an example of a synchronous call in a simple Node.js script. It only has one car so it's incredibly trivial, but its illustrative of the "shape" of how you would use the API.

require('dotenv').config();

const fetch = require('node-fetch');
const KEY = process.env.KEY;

const body = {
  "plan": {
    "jobs": [
      {
        "id": "myJob",
        "places": {
          "deliveries": [
            {
              "location": {"lat": 52.46642, "lng": 13.28124},
              "times": [["2020-07-04T10:00:00.000Z","2020-07-04T12:00:00.000Z"]],
              "duration": 180,
              "demand": [0]
            }
          ]
        }
      },
      {
        "id": "myJob2",
        "places": {
          "deliveries": [
            {
              "location": {"lat": 53.46642, "lng": 13.10124},
              "times": [["2020-07-04T10:00:00.000Z","2020-07-04T12:00:00.000Z"]],
              "duration": 180,
              "demand": [0]
            }
          ]
        }
      },
    ]
  },
  "fleet": {
    "types": [
      {
        "id": "myVehicle",
        "profile": "normal_car",
        "costs": {
          "distance": 0.0002,
          "time": 0.004806,
          "fixed": 22
        },
        "shifts": [{
          "start": {
            "time": "2020-07-04T09:00:00Z",
            "location": {"lat": 52.52568, "lng": 13.45345}
          },
          "end": {
            "time": "2020-07-04T18:00:00Z",
            "location": {"lat": 52.52568, "lng": 13.45345}
          }
        }],
        "capacity": [10],
        "amount": 1
      }
    ],
    "profiles": [{
      "name": "normal_car",
      "type": "car"
     }]
  }
};

fetch('https://tourplanning.hereapi.com/v2/problems?apikey='+KEY, {
    method:'POST', 
    body:JSON.stringify(body),
    headers: {
        'Content-Type':'application/json',
        'Content-Accepts':'application/json'
    }
}).then(res => res.json())
.then(res => {
    console.log(JSON.stringify(res, null, '\t'));
});

As you can see, nearly all of the code is the definition of the problem. Of course, in a real application you would do more with the solution than just dump it out to the console. Here's how the result looks:

{
    "statistic": {
        "cost": 153.383426,
        "distance": 270731,
        "duration": 16071,
        "times": {
            "driving": 15711,
            "serving": 360,
            "waiting": 0,
            "break": 0
        }
    },
    "tours": [
        {
            "vehicleId": "myVehicle_1",
            "typeId": "myVehicle",
            "stops": [
                {
                    "location": {
                        "lat": 52.52568,
                        "lng": 13.45345
                    },
                    "time": {
                        "arrival": "2020-07-04T09:31:17Z",
                        "departure": "2020-07-04T09:31:17Z"
                    },
                    "load": [
                        0
                    ],
                    "activities": [
                        {
                            "jobId": "departure",
                            "type": "departure"
                        }
                    ]
                },
                {
                    "location": {
                        "lat": 52.46642,
                        "lng": 13.28124
                    },
                    "time": {
                        "arrival": "2020-07-04T10:00:00Z",
                        "departure": "2020-07-04T10:03:00Z"
                    },
                    "load": [
                        0
                    ],
                    "activities": [
                        {
                            "jobId": "myJob",
                            "type": "delivery"
                        }
                    ]
                },
                {
                    "location": {
                        "lat": 53.46642,
                        "lng": 13.10124
                    },
                    "time": {
                        "arrival": "2020-07-04T11:58:11Z",
                        "departure": "2020-07-04T12:01:11Z"
                    },
                    "load": [
                        0
                    ],
                    "activities": [
                        {
                            "jobId": "myJob2",
                            "type": "delivery"
                        }
                    ]
                },
                {
                    "location": {
                        "lat": 52.52568,
                        "lng": 13.45345
                    },
                    "time": {
                        "arrival": "2020-07-04T13:59:08Z",
                        "departure": "2020-07-04T13:59:08Z"
                    },
                    "load": [
                        0
                    ],
                    "activities": [
                        {
                            "jobId": "arrival",
                            "type": "arrival"
                        }
                    ]
                }
            ],
            "statistic": {
                "cost": 153.38342600000001,
                "distance": 270731,
                "duration": 16071,
                "times": {
                    "driving": 15711,
                    "serving": 360,
                    "waiting": 0,
                    "break": 0
                }
            }
        }
    ]
}

Yep, that's a lot of data for a simple job. As your problem grows into more real world scenarios, you can expect the size of the result to grow accordingly.

We hope this introduction gets you excited about the power of this API. We're definitely understand how complex this API is and we're currently making updates to the documentation to make things a bit easier to use. For a more academic look at the problem, you can read Wikipedia's detailed entry on what's called the "vehicle routing problem". (Warning - expect some high level math!)