Skip to content

Pickup delivery and later than

Pickup Delivery

Pickup Delivery is for something a Driver must carry: a parcel, pallet, passenger, or similar. The same Driver picks it up and later delivers it on the same journey, and the item stays on that vehicle in between. There may be other Jobs between the pickup and the delivery.

If you only need “this stop must happen after that stop” — possibly a different Driver, a different day, or after the goods have been put down at a warehouse — use Later Than instead. Later Than is time order only; it does not keep anything on the vehicle.

Represent a Pickup Delivery as two Jobs: a Pickup Job and a Delivery Job. Just like all Jobs, each can have its own constraints. For example, you can use Time Windows to require a pickup after 10am (pickup_job.arrive_after = 10) and a delivery before 2:30pm (delivery_job.leave_by = 14.5).

Express the relationship by setting pickup_from on the Delivery Job to the uid of the Pickup Job:

delivery_job.pickup_from = pickup_job.uid

Pickup delivery — one Driver carries the item

One Driver
Start
Pickup
···
Delivery
End

Blue jobs are the pair. Dots are other stops that may sit between them. If either blue job is unserved, both are.

The referenced Job must be in the same request. The optimiser serves connected Pickup Delivery Jobs on the same Driver, with each pickup before its dependent delivery. In a single-day request that means the same Run. In multi-shift reset it also means the same Shift; in continue the pair may span Shifts on that Driver.

If any Job in a Pickup Delivery pair or chain cannot be served, none of the connected Jobs are served. That is different from Later Than, where each Job can be served or skipped on its own.

Chains and fan-out groups are supported. For example, P → D1 and P → D2 requires P before each delivery, but does not impose an order between D1 and D2.

Example

[
  {
    "uid": "job_1-pickup",
    "duration": 10,
    "parking_duration": 5,
    "location": {"lat": -33.849489, "lon": 151.127482}
  },
  {
    "uid": "job_2-delivery",
    "duration": 10,
    "location": {"lat": -33.880661, "lon": 151.183096},
    "pickup_from": "job_1-pickup"
  }
]

Pickup Delivery with Capacity

If you use the Capacity constraint with Pickup Delivery Jobs, set a positive size on the Pickup Job and the matching negative size on the Delivery Job:

size = 10                 # for example
pickup_job.size = size
delivery_job.size = -size

The Delivery Job's negative size reflects that the delivery no longer consumes space, weight, places, or other capacity in the vehicle. Load is checked at every stop on the route, not only at the end of the run.

Simple capacity PD pair

[
  {
    "uid": "job_1-pickup",
    "duration": 2,
    "location": {"lat": -33.849489, "lon": 151.127482},
    "size": 10
  },
  {
    "uid": "job_2-delivery",
    "duration": 2,
    "location": {"lat": -33.849489, "lon": 151.127482},
    "pickup_from": "job_1-pickup",
    "size": -10
  }
]

Multi-dimension capacity

[
  {
    "uid": "job_1-pickup",
    "duration": 2,
    "location": {"lat": -33.849489, "lon": 151.127482},
    "size": "boxes:3&bikes:2"
  },
  {
    "uid": "job_2-delivery",
    "duration": 2,
    "location": {"lat": -33.849489, "lon": 151.127482},
    "pickup_from": "job_1-pickup",
    "size": "boxes:-3&bikes:-2"
  }
]

Driver capacity must remain non-negative. A route that would drive load below zero (for example, a delivery without its prior pickup load) or above the vehicle capacity at any stop is infeasible.

Multi-shift

In multi-shift reset, that load check starts from empty on every Shift. In continue, a pickup that is still onboard overnight still counts against capacity the next morning.

Pickup Delivery with Parking Duration

When several pickups or drops share a warehouse (or any other site), put loading / unloading time in each Job's duration and dock or gate time in parking_duration.

The Driver pays parking once for a run of consecutive Jobs at that location, then pays each Job's service time. If they leave and come back later, parking is charged again.

This is the usual pattern for:

  • Several warehouse pickups before a delivery round
  • A cross-dock drop followed immediately by another drop at the same dock
  • Any site where getting in is expensive but serving the next Job there is not
[
  {
    "uid": "wh_pick_a",
    "duration": 8,
    "parking_duration": 10,
    "location": {"lat": -33.867798, "lon": 151.166256},
    "size": 4
  },
  {
    "uid": "wh_pick_b",
    "duration": 6,
    "parking_duration": 10,
    "location": {"lat": -33.867798, "lon": 151.166256},
    "size": 3
  },
  {
    "uid": "customer_a",
    "duration": 5,
    "parking_duration": 3,
    "location": {"lat": -33.849489, "lon": 151.127482},
    "pickup_from": "wh_pick_a",
    "size": -4
  },
  {
    "uid": "customer_b",
    "duration": 5,
    "parking_duration": 3,
    "location": {"lat": -33.880661, "lon": 151.183096},
    "pickup_from": "wh_pick_b",
    "size": -3
  }
]

If the optimiser serves wh_pick_a then wh_pick_b before leaving the warehouse, parking is applied on wh_pick_a only (unless the Driver started there, in which case it is waived on the first Job too). Each pickup still has its own loading time.

Parking still makes sense without Pickup Delivery. Use it on any Job where arriving at the site takes time; see Parking Duration.

Later Than

later_than is a time-only precedence constraint. Set it on the Job that must happen second, and point it at the uid of the Job that must happen first:

later_job.later_than = earlier_job.uid

Later than — two Drivers, time order only

Driver A
Start
Inspect
Other jobs
End
Driver B
Start
Other jobs
Install
End

Install starts after Inspect has arrived. Different routes are fine. Serving one Job does not force the other.

When both Jobs are served, the later Job's ETA is strictly after the earlier Job's ETA. The two Jobs do not have to use the same Driver, the same Run, or the same day. Nothing is treated as being carried between them, and capacity is not coupled.

If either Job is left unserved, the constraint is ignored. Serving one does not force the other to be served, and skipping one does not skip the other.

Use Later Than when:

  • Goods are dropped at a warehouse and later picked up again — possibly by another Driver, possibly another day
  • An inspection must finish before an install can start, but they can be different people
  • A return visit cannot start until an earlier visit has happened

Use Pickup Delivery when:

  • The same Driver must carry the item from A to B
  • Vehicle capacity should go up at the pickup and down at the delivery
  • Both stops should be skipped together if either cannot be served
Rule Pickup Delivery (pickup_from) Later Than (later_than)
What it means Carry this item from A to B B may not start until A has arrived
Same Driver? Yes No
Same Shift? Yes in reset; same Driver in continue No
Capacity / load Yes — positive size on the pickup, negative on the delivery No
If one cannot be served The whole connected group is unserved The other Job can still be served
Typical use Courier parcel, onboard pallet Cross-dock, “install after inspect”

The referenced Job must be in the same request.

Example — inspect, then install

Two field Jobs at different sites. They can be different Drivers. The install simply cannot start until the inspection has arrived.

[
  {
    "uid": "inspect_site",
    "duration": 20,
    "location": {"lat": -33.849489, "lon": 151.127482}
  },
  {
    "uid": "install_site",
    "duration": 40,
    "location": {"lat": -33.880661, "lon": 151.183096},
    "later_than": "inspect_site" // (1)
  }
]
  1. Time order only. These Jobs need not share a Driver, a Run, or a day.

Example — cross-dock (Pickup Delivery + Later Than)

An inbound pair carries goods from the supplier to the warehouse. An outbound pair carries them from the warehouse to the customer. Those two pairs can be different Drivers and different days. The outbound warehouse pickup must wait until the inbound drop has arrived — that link is Later Than, not Pickup Delivery.

[
  {
    "uid": "supplier_pick",
    "duration": 10,
    "parking_duration": 8,
    "location": {"lat": -33.849489, "lon": 151.127482},
    "size": 3
  },
  {
    "uid": "warehouse_drop",
    "duration": 5,
    "parking_duration": 10,
    "location": {"lat": -33.867798, "lon": 151.166256},
    "pickup_from": "supplier_pick",
    "size": -3
  },
  {
    "uid": "warehouse_pick",
    "duration": 5,
    "parking_duration": 10,
    "location": {"lat": -33.867798, "lon": 151.166256},
    "size": 3,
    "later_than": "warehouse_drop"
  },
  {
    "uid": "customer_drop",
    "duration": 10,
    "parking_duration": 3,
    "location": {"lat": -33.880661, "lon": 151.183096},
    "pickup_from": "warehouse_pick",
    "size": -3
  }
]

If you had set warehouse_pick.pickup_from = "warehouse_drop", the same Driver would have been forced to keep the goods and finish both pairs in the same Shift (in reset). That is the opposite of a cross-dock.