Route compactness
Route compactness refers to the geographic density of nodes (customers) in a single route. A compact route keeps its stops close together, so each vehicle serves one focused territory instead of criss-crossing the map. Picture a catering company with 100 orders and 5 vans: left unconstrained, the vans overlap — one drives north-to-south while another does the reverse; with compactness, each van owns a single neighbourhood (Downtown, West Suburbs, …) and stays there.
That difference compounds across the operation:
- Driver familiarity — drivers learn their own zones, which means faster service, fewer wrong turns, more accurate time estimates, and easier handoffs between shifts.
- More reliable service — smaller, predictable service areas make delivery windows and response times easier to promise and hit.
- Better executability and driver acceptance — some subcontractors are paid per trip on the distance from the DC (pickup) to the farthest stop only, ignoring total route distance. Compact routes keep each vehicle's stops in one nearby zone, so drivers aren't asked to cover long, sprawling routes for the same flat pay — making plans more executable and more readily accepted.
- Cleaner scaling — new customers slot into the nearest existing cluster and new vehicles open new clusters, so plans stay stable and easy to reason about as volume grows.
- Simpler operations — well-defined territories make it easier to assign drivers, balance workload, and explain a plan to dispatchers and customers.
Two ways to encourage compactness
The solver offers two mechanisms. They can be used independently or together, but they pursue compactness through different signals: one looks at where the stops are, the other at how far a vehicle actually travels between them.
Spatial dispersion penalty — cluster_cost
Looks at the positions of a vehicle's stops. It finds their geographic centre (the centroid) and penalizes how far each stop sits from it — the wider the point cloud, the bigger the penalty. It ignores the travel order and the road network; it only cares that the stops form a tight group. Operates directly on node coordinates.
Travel-cost shaping — route_cost_modification_primary
Looks at the connections a vehicle drives between consecutive stops. It reshapes the cost of each travel leg, adding penalties to legs that are long or expensive on the time/distance matrix. It cares about the actual path travelled, not the overall shape of the cluster. Operates on the time/distance matrices.
In short: cluster_cost tightens the shape of each territory, while route cost modification prices the travel between stops. Reach for cluster_cost to stop territories overlapping, and route cost modification to discourage specific long or costly hops — or combine them, tuning one at a time.
Cluster cost penalty
cluster_cost works directly on node
How it works
For each vehicle, the solver finds the geographic centre of all its assigned stops, then measures how far each stop sits from that centre. The further apart the stops are from one another, the larger the penalty added to the route cost — so the solver naturally prefers solutions where each vehicle's stops cluster tightly together. The factor parameter controls how strongly this penalty competes with the usual travel-time and distance costs.
Effect of factor
Disabled by default — both use_cluster_radius: true and factor > 0 are required to activate.
factor | Behavior |
|---|---|
0 | Disabled. Pure time/distance optimization. |
5–10 | Mild bias. Small travel impact. |
10–20 | Visibly tight clusters. Recommended starting range. |
> 20 | Aggressive. Solver accepts noticeably longer travel for tighter clusters. |
factor = 0 — clustering disabled. Territories overlap and a few routes stray into each other.
factor = 20 — each vehicle owns a compact, less-overlapping territory.
Each colour is one vehicle; the square is the depot.
Configuration (Integration API)
Configure under simulation.data.logistics_api_settings.cluster_cost. Set to null (or omit) to disable.
{
"data": {
"logistics_api_settings": {
"cluster_cost": {
"use_cluster_radius": true,
"factor": 10
}
}
}
}
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
use_cluster_radius | boolean | false | Master switch. Must be true to enable. |
factor | integer | 0 | Clustering strength. Must be > 0. Squared internally. |
ignore_node_types | list of string | ["depot", "vehicle_position", "point"] | Node types excluded from clustering. Replaces the default — re-include "depot", "vehicle_position", and "point" if you extend the list. |
ignore_latlons | list of [lat, lon] | [] | Specific coordinates to exclude (e.g. a common collection point many vehicles visit, which would otherwise skew centroids). |
Common node types you can list in ignore_node_types:
vehicle_position— a vehicle's current location, e.g. its live GPS position mid-shift. (ignored by default)point— a vehicle's start or end location. (ignored by default)pickup/dropoff— pickup and drop-off stops. Not ignored by default; add them here if you want to exclude them from clustering too.
Example: exclude a common collection point
{
"data": {
"logistics_api_settings": {
"cluster_cost": {
"use_cluster_radius": true,
"factor": 12,
"ignore_latlons": [[1.290270, 103.851959]]
}
}
}
}
Biases the solver toward compact territories (factor: 12) while keeping a known common collection point out of the centroid math, so it doesn't pull every vehicle's centre toward itself.
Route cost modification
Configured in the Logistics API under simulation.data.logistics_api_settings, via route_cost_modification_primary.
In plain terms
By default the solver prices a route purely on its raw travel time and distance. Route cost modification lets you reshape that price so the optimizer steers away from the legs you care about — for example, making any single hop longer than an hour disproportionately expensive.
Think of it as a toll you attach to travel: a flat per-unit rate you can raise or lower, plus optional surcharges that kick in once a leg crosses a limit you set.
When to use it
Use route cost modification when you want to penalize specific kinds of legs — trips over a time limit, long distances, or travel measured on a particular matrix. If your goal is simply to keep each vehicle's stops tightly grouped, reach for the cluster cost penalty instead. The two can also be combined.
How a leg's cost is built
A leg is the trip between two consecutive stops. Route cost modification builds each leg's cost from two parts:
linear— scales the raw value.linear: 1charges the travel value as-is;2doubles it;0.5halves it.polylinear— optional step surcharges. Once a leg crosses athreshold, it adds a fixedbasepenalty plus aratefor every unit beyond the threshold.
Worked example. A leg takes 4000 seconds, with linear: 1 and one rule — threshold: 3600, base: 5000, rate: 10. Because 4000 is over the 3600 threshold, the surcharge applies:
leg cost = (4000 × 1) + (5000 + (4000 − 3600) × 10)
= 4000 + 9000
= 13000
A shorter leg of 3000 s stays under the threshold, so it costs just 3000 — no surcharge.
linear). Past it, the shaded penalty is added on top of the normal cost — a flat base plus rate for every unit over the threshold — so the solver strongly avoids legs past the limit.Stacking thresholds. polylinear takes a list of rules, so you can escalate the surcharge in tiers. A leg triggers every rule it crosses, and their penalties add up — each greater threshold contributes its own base jump and steepens the slope by its rate. Here it's applied to distance (in meters):
"route_cost_modification_primary": {
"distance": {
"linear": 1,
"polylinear": [
{ "threshold": 5000, "relation": "greater", "penalty": { "base": 2000, "rate": 0.2 } },
{ "threshold": 10000, "relation": "greater", "penalty": { "base": 5000, "rate": 0.5 } }
]
}
}
This creates three tiers:
| Leg distance | Rules active | Slope (per meter) |
|---|---|---|
under 5 km (< 5000) | none | linear = 1 |
5–10 km (5000–10000) | first only | linear + 0.2 = 1.2 |
over 10 km (> 10000) | both | linear + 0.2 + 0.5 = 1.7 |
For a 12 000-meter leg, both rules fire:
leg cost = (12000 × 1) + [2000 + (12000 − 5000) × 0.2] + [5000 + (12000 − 10000) × 0.5]
= 12000 + 3400 + 6000
= 21400
greater thresholds create three tiers. Below the first, cost rises at linear; each threshold adds a base jump and a steeper slope (+ rate), so the penalty compounds the farther a leg runs.Configuration
Route cost modification is applied through route_cost_modification_primary, which shapes the cost of the routing engine's time and distance. It accepts a time and a distance block. This example minimizes travel time and penalizes any trip over 1 hour:
{
"data": {
"logistics_api_settings": {
"route_cost_modification_primary": {
"time": {
"linear": 1,
"polylinear": [
{
"threshold": 3600,
"relation": "greater",
"penalty": { "base": 5000, "rate": 2 }
}
]
}
}
}
}
}
What this config does:
route_cost_modification_primary.time.linear: 1makes total travel time the main cost.- the
polylinearrule: a trip over 3600 s (1 hour) incurs abase: 5000penalty plusrate: 2per second over.
Parameters
route_cost_modification_primary accepts a time and a distance block, applied to the routing engine's time and distance matrices respectively.
Inside a time or distance block:
linear(number, default0): scaling factor on the raw value.1adds it as-is;2doubles it.polylinear(array): step-surcharge rules. Each rule:
| Field | Type | Default | Description |
|---|---|---|---|
threshold | number | 0 | Value at which the surcharge triggers. Units match the matrix — seconds for time, meters for distance. |
relation | string | required | "greater" or "less". |
penalty.base | number | 0 | Fixed penalty added when the rule triggers. |
penalty.rate | number | 0 | Extra penalty per unit beyond the threshold. |
ignore_first | boolean | true | Skip the leg from the start location to the first stop. |
ignore_last | boolean | true | Skip the leg from the last stop back to the end location. |
- For
relation: "less", use a negativerateso the penalty stays positive. - Multiple
polylinearrules add up independently. - A leg's total cost is never negative (it's clamped at 0).
Common recipes
Each snippet is the contents of logistics_api_settings — drop it into your PATCH /api/v2/simulation/{id} body.
Cap trips over 1 hour — heavily penalize any single leg longer than 3600 s.
{ "route_cost_modification_primary": { "time": { "linear": 1, "polylinear": [
{ "threshold": 3600, "relation": "greater", "penalty": { "base": 5000, "rate": 2 } }
] } } }
Discourage very long distance legs — surcharge legs over 10 km.
{ "route_cost_modification_primary": { "distance": { "linear": 0.1, "polylinear": [
{ "threshold": 10000, "relation": "greater", "penalty": { "base": 1000, "rate": 0.2 } }
] } } }
See Logistics API Settings for the full list.
Playground
Try the Route Compactness concept with the playground below.