Allow customer-based service durations
Optimize your tours by focusing on the number of customers you serve rather than on the number of jobs to optimize tour duration.
For example, consider a postal service use case where mail carriers deliver parcels and letters to customers. As part of that use case, the carrier might deliver or pick up an item (a package or a letter) at a certain location. In the context of Tour Planning, such delivery represents a single job, with a specific time duration assigned to it. However, real-life business scenarios might also involve multiple letters or parcels being delivered to a single customer or household.
Without a specific problem configuration, the HERE Tour Planning API treats each job independently, even if a mail carrier can deliver multiple packages to a single customer at once. In the context of the previous example, this may result in a longer tour duration. A prolonged tour duration might lead to such inefficiencies as reduced number of jobs that fit within the mail carriers' shift or requiring more resources to meet the delivery demand.
Optimizing tour duration to account for multi-delivery or pickup scenarios
To reduce potential inefficiencies resulting from prolonged tour duration in multi-delivery or multi-pickup scenarios, you can apply a specific service duration at the stop for a specific customer, irrespective of the number of pickup or delivery tasks, through the houseKeyId property.
Note
This is an ALPHA feature, which means it is new or experimental and under active development. Alpha features are provided for testing and feedback purposes. They may change significantly or might not become generally available.
For more information, see Explore experimental features.
The following snippet demonstrates how to incorporate the property as part of the plan object, in the problem specification:
{
"plan": {
"jobs": [
{
"id": "job1",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.5622847,
"lng": 13.4023099
},
"duration": 180,
"houseKeyId": "my_house_key_id_1"
}
],
"demand": [
1
]
}
]
}
},
(...) // Remaining parts of the problem omitted for brevityFor jobs with matching houseKeyId at the same location, the total time spent at that location is equal to the highest duration among those jobs. For example, consider the following jobs at the same location, with matching houseKeyId values:
| JobId | duration (seconds) | houseKeyId |
|---|---|---|
Job_1 | 300 | flat_1 |
Job_2 | 300 | flat_1 |
Job_3 | 600 | flat_1 |
The total duration for these jobs is equal to 600, which is the highest duration value among these jobs. By using this approach, you can reduce the total duration of the tour, making sure that your resources are used with maximum efficiency.
The following sections demonstrate the use of houseKeyId through a practical example.
Comparing delivery scenarios with and without customer-based service duration
The following comparison demonstrates how including the houseKeyId in your problem specification can improve the delivery time, making the tour more efficient.
Problem #1: No houseKeyId
The problem that serves as the starting point of this sample use case consists of seven jobs assigned to a single-vehicle fleet. Each job is either a letter delivery (300 second duration) or a parcel delivery (600 second duration). The vehicle assigned to serve these jobs must complete all assignments within a tight time window of two hours.
The following JSON snippet contains the full problem specification:
Click to expand/collapse the sample JSON
{
"fleet": {
"types": [
{
"id": "small",
"profile": "car",
"costs": {
"fixed": 20,
"distance": 0,
"time": 0.005
},
"shifts": [
{
"start": {
"time": "2023-05-28T08:00:00Z",
"location": {
"lat": 52.50935,
"lng": 13.41997
}
},
"end": {
"time": "2023-05-28T10:00:00Z",
"location": {
"lat": 52.50935,
"lng": 13.41997
}
}
}
],
"capacity": [
100
],
"amount": 1
}
],
"profiles": [
{
"type": "car",
"name": "car"
}
]
},
"plan": {
"jobs": [
{
"id": "Job_1",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.56182,
"lng": 13.497167
},
"duration": 300,
"tag": "letter_delivery"
}
],
"demand": [
1
]
}
]
}
},
{
"id": "Job_2",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.534553,
"lng": 13.519429
},
"duration": 600,
"tag": "parcel_delivery"
}
],
"demand": [
1
]
}
]
}
},
{
"id": "Job_3",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.463341,
"lng": 13.49061
},
"duration": 300,
"tag": "letter_delivery"
}
],
"demand": [
1
]
}
]
}
},
{
"id": "Job_4",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.463341,
"lng": 13.49061
},
"duration": 600,
"tag": "parcel_delivery"
}
],
"demand": [
1
]
}
]
}
},
{
"id": "Job_5",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.463341,
"lng": 13.49061
},
"duration": 300,
"tag": "letter_delivery"
}
],
"demand": [
1
]
}
]
}
},
{
"id": "Job_6",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.463341,
"lng": 13.49061
},
"duration": 300,
"tag": "letter_delivery"
}
],
"demand": [
1
]
}
]
}
},
{
"id": "Job_7",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.447476,
"lng": 13.433062
},
"duration": 600,
"tag": "parcel_delivery"
}
],
"demand": [
1
]
}
]
}
}
]
}
}According to the plan, jobs with IDs from 3 through 6 are to be served at the same location and distributed among two separate households (C1 and C2).
Solution
The following solution indicates that while the tour was completed within the assigned time window, it resulted in one unassigned job. Assigning this job would have otherwise violated the time window constraints for the shift. The following diagram provides a visual breakdown of the tour:
In this case, existence of two separate households at location C has no impact on the tour duration. The breakdown shows that the optimization algorithm treated each job at location C independently, assigning the full time duration to each one. However, jobs with IDs 3 and 4 for household C1, and jobs with IDs 5 and 6 for household C2, could have been completed simultaneously.
As a result, the prolonged tour duration left job 7 unassigned, which could cause such inefficiencies as the need to assign additional resources to complete less jobs within a single tour.
The following snippet provides the full solution JSON:
Click to expand/collapse the sample JSON
{
"statistic": {
"cost": 49.54,
"distance": 32750,
"duration": 5908,
"times": {
"driving": 3508,
"serving": 2400,
"waiting": 0,
"stopping": 0,
"break": 0
}
},
"tours": [
{
"vehicleId": "small_1",
"typeId": "small",
"stops": [
{
"time": {
"arrival": "2023-05-28T08:00:00Z",
"departure": "2023-05-28T08:00:00Z"
},
"load": [
6
],
"activities": [
{
"jobId": "departure",
"type": "departure",
"location": {
"lat": 52.50935,
"lng": 13.41997
},
"time": {
"start": "2023-05-28T08:00:00Z",
"end": "2023-05-28T08:00:00Z"
}
}
],
"location": {
"lat": 52.50935,
"lng": 13.41997
},
"distance": 0
},
{
"time": {
"arrival": "2023-05-28T08:19:39Z",
"departure": "2023-05-28T08:24:39Z"
},
"load": [
5
],
"activities": [
{
"jobId": "Job_1",
"type": "delivery",
"jobTag": "letter_delivery",
"location": {
"lat": 52.56182,
"lng": 13.497167
},
"time": {
"start": "2023-05-28T08:19:39Z",
"end": "2023-05-28T08:24:39Z"
}
}
],
"location": {
"lat": 52.56182,
"lng": 13.497167
},
"distance": 9971
},
{
"time": {
"arrival": "2023-05-28T08:31:16Z",
"departure": "2023-05-28T08:41:16Z"
},
"load": [
4
],
"activities": [
{
"jobId": "Job_2",
"type": "delivery",
"jobTag": "parcel_delivery",
"location": {
"lat": 52.534553,
"lng": 13.519429
},
"time": {
"start": "2023-05-28T08:31:16Z",
"end": "2023-05-28T08:41:16Z"
}
}
],
"location": {
"lat": 52.534553,
"lng": 13.519429
},
"distance": 13720
},
{
"time": {
"arrival": "2023-05-28T08:58:44Z",
"departure": "2023-05-28T09:23:44Z"
},
"load": [
0
],
"activities": [
{
"jobId": "Job_4_C1",
"type": "delivery",
"jobTag": "parcel_delivery",
"location": {
"lat": 52.463341,
"lng": 13.49061
},
"time": {
"start": "2023-05-28T08:58:44Z",
"end": "2023-05-28T09:08:44Z"
}
},
{
"jobId": "Job_3_C1",
"type": "delivery",
"jobTag": "letter_delivery",
"location": {
"lat": 52.463341,
"lng": 13.49061
},
"time": {
"start": "2023-05-28T09:08:44Z",
"end": "2023-05-28T09:13:44Z"
}
},
{
"jobId": "Job_6_C2",
"type": "delivery",
"jobTag": "letter_delivery",
"location": {
"lat": 52.463341,
"lng": 13.49061
},
"time": {
"start": "2023-05-28T09:13:44Z",
"end": "2023-05-28T09:18:44Z"
}
},
{
"jobId": "Job_5_C2",
"type": "delivery",
"jobTag": "letter_delivery",
"location": {
"lat": 52.463341,
"lng": 13.49061
},
"time": {
"start": "2023-05-28T09:18:44Z",
"end": "2023-05-28T09:23:44Z"
}
}
],
"location": {
"lat": 52.463341,
"lng": 13.49061
},
"distance": 24124
},
{
"time": {
"arrival": "2023-05-28T09:38:28Z",
"departure": "2023-05-28T09:38:28Z"
},
"load": [
0
],
"activities": [
{
"jobId": "arrival",
"type": "arrival",
"location": {
"lat": 52.50935,
"lng": 13.41997
},
"time": {
"start": "2023-05-28T09:38:28Z",
"end": "2023-05-28T09:38:28Z"
}
}
],
"location": {
"lat": 52.50935,
"lng": 13.41997
},
"distance": 32750
}
],
"statistic": {
"cost": 49.54,
"distance": 32750,
"duration": 5908,
"times": {
"driving": 3508,
"serving": 2400,
"waiting": 0,
"stopping": 0,
"break": 0
}
},
"shiftIndex": 0
}
],
"unassigned": [
{
"jobId": "Job_7",
"reasons": [
{
"code": "TIME_WINDOW_CONSTRAINT",
"description": "cannot be assigned due to violation of time window",
"details": [
{
"vehicleId": "small_1",
"shiftIndex": 0
}
]
}
]
}
]
}The following figure shows the tour visualized on a map:
In the previous figure, the arrival and departure times at stop 3 confirm the total duration of jobs at the stop as 25 minutes.
Problem #2: Optimizing tour duration with houseKeyId
To optimize the duration of the previous tour, assign houseKeyId values to jobs at location C, with each value corresponding to a household at that location. This ensures that only the duration of the longest job is taken for the total duration for all the jobs with the same houseKeyId value, irrespective of their number.
The following snippet shows the updated problem specification:
Click to expand/collapse the sample JSON
{
"fleet": {
"types": [
{
"id": "small",
"profile": "car",
"costs": {
"fixed": 20,
"distance": 0,
"time": 0.005
},
"shifts": [
{
"start": {
"time": "2023-05-28T08:00:00Z",
"location": {
"lat": 52.50935,
"lng": 13.41997
}
},
"end": {
"time": "2023-05-28T10:00:00Z",
"location": {
"lat": 52.50935,
"lng": 13.41997
}
}
}
],
"capacity": [
100
],
"amount": 1
}
],
"profiles": [
{
"type": "car",
"name": "car"
}
]
},
"plan": {
"jobs": [
{
"id": "Job_1",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.56182,
"lng": 13.497167
},
"duration": 300,
"tag": "letter_delivery"
}
],
"demand": [
1
]
}
]
}
},
{
"id": "Job_2",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.534553,
"lng": 13.519429
},
"duration": 600,
"tag": "parcel_delivery"
}
],
"demand": [
1
]
}
]
}
},
{
"id": "Job_3_C1",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.463341,
"lng": 13.49061
},
"duration": 300,
"tag": "letter_delivery",
"houseKeyId": "1"
}
],
"demand": [
1
]
}
]
}
},
{
"id": "Job_4_C1",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.463341,
"lng": 13.49061
},
"duration": 600,
"tag": "parcel_delivery",
"houseKeyId": "1"
}
],
"demand": [
1
]
}
]
}
},
{
"id": "Job_5_C2",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.463341,
"lng": 13.49061
},
"duration": 300,
"tag": "letter_delivery",
"houseKeyId": "2"
}
],
"demand": [
1
]
}
]
}
},
{
"id": "Job_6_C2",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.463341,
"lng": 13.49061
},
"duration": 300,
"tag": "letter_delivery",
"houseKeyId": "2"
}
],
"demand": [
1
]
}
]
}
},
{
"id": "Job_7",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.447476,
"lng": 13.433062
},
"duration": 600,
"tag": "parcel_delivery"
}
],
"demand": [
1
]
}
]
}
}
]
}
}Solution
In this case, as the following solution breakdown indicates, the optimization algorithm served all jobs within the tour plan:
By assigning each job at location C with a houseKeyId per target household, the total job duration at that location was reduced from 25 minutes to 15 minutes, allowing job 7 to fit within the tour time window.
The reduced total duration at that location was the result of the optimization algorithm considering only the highest duration from among the jobs with matching houseKeyIds. In the case of household C1, the parcel delivery job has the highest duration (600 seconds or 10 minutes). In the case of household C2, both jobs have the same duration (300 seconds or 5 minutes), so the algorithm considered only a single duration. Therefore, the sum of job durations per each household is now 900 seconds (or 15 minutes), as the following full solution JSON indicates:
Click to expand/collapse the sample JSON
{
"statistic": {
"cost": 53.04,
"distance": 37180,
"duration": 6608,
"times": {
"driving": 4208,
"serving": 2400,
"waiting": 0,
"stopping": 0,
"break": 0
}
},
"tours": [
{
"vehicleId": "small_1",
"typeId": "small",
"stops": [
{
"time": {
"arrival": "2023-05-28T08:00:00Z",
"departure": "2023-05-28T08:00:00Z"
},
"load": [
7
],
"activities": [
{
"jobId": "departure",
"type": "departure",
"location": {
"lat": 52.50935,
"lng": 13.41997
},
"time": {
"start": "2023-05-28T08:00:00Z",
"end": "2023-05-28T08:00:00Z"
}
}
],
"location": {
"lat": 52.50935,
"lng": 13.41997
},
"distance": 0
},
{
"time": {
"arrival": "2023-05-28T08:19:39Z",
"departure": "2023-05-28T08:24:39Z"
},
"load": [
6
],
"activities": [
{
"jobId": "Job_1",
"type": "delivery",
"jobTag": "letter_delivery",
"location": {
"lat": 52.56182,
"lng": 13.497167
},
"time": {
"start": "2023-05-28T08:19:39Z",
"end": "2023-05-28T08:24:39Z"
}
}
],
"location": {
"lat": 52.56182,
"lng": 13.497167
},
"distance": 9971
},
{
"time": {
"arrival": "2023-05-28T08:31:16Z",
"departure": "2023-05-28T08:41:16Z"
},
"load": [
5
],
"activities": [
{
"jobId": "Job_2",
"type": "delivery",
"jobTag": "parcel_delivery",
"location": {
"lat": 52.534553,
"lng": 13.519429
},
"time": {
"start": "2023-05-28T08:31:16Z",
"end": "2023-05-28T08:41:16Z"
}
}
],
"location": {
"lat": 52.534553,
"lng": 13.519429
},
"distance": 13720
},
{
"time": {
"arrival": "2023-05-28T08:58:44Z",
"departure": "2023-05-28T09:13:44Z"
},
"load": [
1
],
"activities": [
{
"jobId": "Job_6_C2",
"type": "delivery",
"jobTag": "letter_delivery",
"location": {
"lat": 52.463341,
"lng": 13.49061
},
"time": {
"start": "2023-05-28T08:58:44Z",
"end": "2023-05-28T09:03:44Z"
}
},
{
"jobId": "Job_4_C1",
"type": "delivery",
"jobTag": "parcel_delivery",
"location": {
"lat": 52.463341,
"lng": 13.49061
},
"time": {
"start": "2023-05-28T09:03:44Z",
"end": "2023-05-28T09:13:44Z"
}
},
{
"jobId": "Job_3_C1",
"type": "delivery",
"jobTag": "letter_delivery",
"location": {
"lat": 52.463341,
"lng": 13.49061
},
"time": {
"start": "2023-05-28T09:03:44Z",
"end": "2023-05-28T09:13:44Z"
}
},
{
"jobId": "Job_5_C2",
"type": "delivery",
"jobTag": "letter_delivery",
"location": {
"lat": 52.463341,
"lng": 13.49061
},
"time": {
"start": "2023-05-28T08:58:44Z",
"end": "2023-05-28T09:03:44Z"
}
}
],
"location": {
"lat": 52.463341,
"lng": 13.49061
},
"distance": 24124
},
{
"time": {
"arrival": "2023-05-28T09:23:22Z",
"departure": "2023-05-28T09:33:22Z"
},
"load": [
0
],
"activities": [
{
"jobId": "Job_7",
"type": "delivery",
"jobTag": "parcel_delivery",
"location": {
"lat": 52.447476,
"lng": 13.433062
},
"time": {
"start": "2023-05-28T09:23:22Z",
"end": "2023-05-28T09:33:22Z"
}
}
],
"location": {
"lat": 52.447476,
"lng": 13.433062
},
"distance": 29529
},
{
"time": {
"arrival": "2023-05-28T09:50:08Z",
"departure": "2023-05-28T09:50:08Z"
},
"load": [
0
],
"activities": [
{
"jobId": "arrival",
"type": "arrival",
"location": {
"lat": 52.50935,
"lng": 13.41997
},
"time": {
"start": "2023-05-28T09:50:08Z",
"end": "2023-05-28T09:50:08Z"
}
}
],
"location": {
"lat": 52.50935,
"lng": 13.41997
},
"distance": 37180
}
],
"statistic": {
"cost": 53.04,
"distance": 37180,
"duration": 6608,
"times": {
"driving": 4208,
"serving": 2400,
"waiting": 0,
"stopping": 0,
"break": 0
}
},
"shiftIndex": 0
}
]
}The following figure shows the updated tour visualized on a map:
In this case, the arrival and departure times at stop 3 point to the reduced total duration (15 minutes) as a result of application of the houseKeyId feature, as compared with the example where the optimization algorithm was not aware of separate households. In addition, the previously unassigned job now fits within the tour as stop 4.
Conclusion
This tutorial showed how to apply the houseKeyId feature to optimize tour duration in business scenarios involving multiple deliveries or pickups at the same location, placing the focus on the number of customers (households) as the base for computing the total tour duration.
Next steps
- For more information about formulating problems in the HERE Tour Planning API, see Problem.
- For an in-depth exploration of the HERE Tour Planning API methods, endpoints, and parameters, see the API Reference.
Updated 29 days ago