Skip to main content

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.

A vehicle's stops connected as a route, with orange spokes from the group's centroid to each stop showing how far each sits from the centre.

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.

A route from a DC through several stops, with one long leg highlighted in orange and labelled as penalized.

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

coordinates rather than travel time and distance. That makes it the right tool when travel-cost shaping alone can't break a tie between a dispersed route and a tight one that happen to look similar in travel time and distance.

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.

factorBehavior
0Disabled. Pure time/distance optimization.
5–10Mild bias. Small travel impact.
10–20Visibly tight clusters. Recommended starting range.
> 20Aggressive. 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.

PATCH /api/v2/simulation/{id}
{
"data": {
"logistics_api_settings": {
"cluster_cost": {
"use_cluster_radius": true,
"factor": 10
}
}
}
}

Parameters​

ParameterTypeDefaultDescription
use_cluster_radiusbooleanfalseMaster switch. Must be true to enable.
factorinteger0Clustering strength. Must be > 0. Squared internally.
ignore_node_typeslist 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_latlonslist 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​

PATCH /api/v2/simulation/{id}
{
"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: 1 charges the travel value as-is; 2 doubles it; 0.5 halves it.
  • polylinear — optional step surcharges. Once a leg crosses a threshold, it adds a fixed base penalty plus a rate for 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.

thresholdslope = linearbaserate × excesspenaltycost without penaltyleg value (time or distance) →leg cost
Below the threshold, cost follows the normal travel value (slope = 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 distanceRules activeSlope (per meter)
under 5 km (< 5000)nonelinear = 1
5–10 km (5000–10000)first onlylinear + 0.2 = 1.2
over 10 km (> 10000)bothlinear + 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
threshold 1threshold 2slope = linear+ base+ baseleg value (distance) →leg cost
Two 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:

PATCH /api/v2/simulation/{id}
{
"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: 1 makes total travel time the main cost.
  • the polylinear rule: a trip over 3600 s (1 hour) incurs a base: 5000 penalty plus rate: 2 per 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, default 0): scaling factor on the raw value. 1 adds it as-is; 2 doubles it.
  • polylinear (array): step-surcharge rules. Each rule:
FieldTypeDefaultDescription
thresholdnumber0Value at which the surcharge triggers. Units match the matrix — seconds for time, meters for distance.
relationstringrequired"greater" or "less".
penalty.basenumber0Fixed penalty added when the rule triggers.
penalty.ratenumber0Extra penalty per unit beyond the threshold.
ignore_firstbooleantrueSkip the leg from the start location to the first stop.
ignore_lastbooleantrueSkip the leg from the last stop back to the end location.
note
  • For relation: "less", use a negative rate so the penalty stays positive.
  • Multiple polylinear rules 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.

Loading...