GMPRO docs: Territory optimization and route planning
How to use the costsPerVehicle parameter to build a territory optimization system with GMPRO.
In this short tutorial, I'm going to show you how to use the Google Maps Platform Route Optimization API (GMPRO) costsPerVehicle
parameter to build a territory optimization and route planning system for your logistics operation.
Part 1: GMPRO: Google Maps Platform route optimization API
Part 2: GMPRO TSP solver: Google Maps with more than 25 waypoints
Part 3: Google Maps route optimization: multi vehicle
Part 4: Fleet routing app - free Google Maps route planner for multiple stops
Part 5: GMPRO docs: Fixed vehicle costs
Part 6: GMPRO docs: Territory optimization and route planning (this article)
Part 7: GMPRO docs: Solving the VRP with route clustering and soft constraints
Part 8: GMPRO docs: Driver load balancing with soft constraints
What is territory optimization?
Territory optimization is the strategic process of dividing a geographic area or market into specific regions, or "territories". It is commonly used by sales teams, logistics companies and field service operators (e.g. telecommunications, HVAC, or utility companies) to balance workloads, maximize coverage and improve geographic efficiency so that each territory is geographically compact and easily accessible, without overlap.
Territory optimization works by assigning each stop to a specific region. To determine the most efficient sequence of stops for each vehicle, you'll still need a route optimization API, like GMPRO
How territory optimization works in GMPRO
There are two main ways to incorporate territory optimization into a route optimization system. The first is by applying hard constraints. For instance, if you want to designate a downtown
territory that only cargo bikes in your fleet can service, you should add a downtown
constraint to the loadDemands
field of shipments
located downtown:
{
"loadDemands": {
"downtown": {
"amount": "1"
}
}
}
In the corresponding vehicles
object, you would add that same constraint to the loadLimits
field like so:
{
"loadLimits": {
"downtown": {
"maxLoad": 5
}
}
}
This ensures that only vehicles
with a downtown
loadLimit
can handle downtown
deliveries.
The second way to approach territory optimization is to use soft constraints, which is the method I prefer.
Applying soft constraints to territory optimization in GMPRO
Unlike a hard constraint which will leave a shipment
unserved if there are no suitable vehicles
to deliver it, a soft constraint will try to find alternative vehicles
to do the job, even if the resulting route solution isn't optimal. It's like saying, "Well, this isn't perfect, but this will just have to do." Applied to territory optimization, this means using the costsPerVehicle
and costsPerVehicleIndices
fields to selectively apply costs to vehicles
that are unsuitable to handle the shipment
.
For example, let's say you have two regions, downtown Vancouver (downtown
) and North Vancouver (north-vancouver
) as shown in the map below.
You have a single shipment
to deliver downtown, and two vehicles
, veh-cargobike
which is a cargo bike and veh-sprintervan
which is a large Mercedes Sprinter Van. The package is quite small, so either vehicle would work but the main problem is parking - it is difficult and time consuming for the sprinter van to find curbside parking downtown, so you would rather the sprinter van not do the delivery. To model this, first attach both vehicles
to the GMPRO request like so:
{
"vehicles": [
{
"travelMode": "DRIVING",
"startLocation": {
"latitude": 49.2808422,
"longitude": -123.0827831
},
"startTimeWindows": [
{
"startTime": "2024-07-08T16:00:00Z"
}
],
"endTimeWindows": [
{
"endTime": "2024-07-08T18:00:00Z"
}
],
"loadLimits": {
"weight": {
"maxLoad": 5
}
},
"label": "veh-cargobike",
"costPerKilometer": 1
},
{
"travelMode": "DRIVING",
"startLocation": {
"latitude": 49.2658769,
"longitude": -123.0815619
},
"startTimeWindows": [
{
"startTime": "2024-07-08T16:00:00Z"
}
],
"endTimeWindows": [
{
"endTime": "2024-07-08T18:00:00Z"
}
],
"loadLimits": {
"weight": {
"maxLoad": 5
}
},
"label": "veh-sprintervan",
"costPerKilometer": 1
}
]
}
Note that veh-cargobike
is in position 0 of the vehicles
array and veh-sprintervan
is in position 1.
Similarly, on the shipments
array, we need to set up the costsPerVehicles
and costsPerVehicleIndices
array like this:
{
"costsPerVehicle": [
0,
50
],
"costsPerVehicleIndices": [
0,
1
]
}
Doing so attaches an additional cost of 50 to the vehicle
in position 1 of the vehicles
array (veh-sprintervan
), making it less likely that GMPRO will assign veh-sprintervan
to do the delivery. This is exactly what we want.
costPerVehicle
/ costPerVehicleIndices
worked example
Putting everything together, here's the full GMPRO request for our 1 shipment
/ 2 vehicle
example.
Input
{
"model": {
"shipments": [
{
"deliveries": [
{
"arrivalLocation": {
"latitude": 49.2912037,
"longitude": -123.1362474
},
"timeWindows": [
{
"startTime": "2024-07-08T16:00:00Z",
"endTime": "2024-07-08T18:00:00Z"
}
],
"duration": "600s"
}
],
"loadDemands": {
"weight": {
"amount": 1
}
},
"costsPerVehicle": [
0,
50
],
"costsPerVehicleIndices": [
0,
1
],
"label": "dt123"
}
],
"vehicles": [
{
"travelMode": "DRIVING",
"startLocation": {
"latitude": 49.2668756,
"longitude": -123.0800413
},
"startTimeWindows": [
{
"startTime": "2024-07-08T16:00:00Z"
}
],
"endTimeWindows": [
{
"endTime": "2024-07-08T18:00:00Z"
}
],
"loadLimits": {
"weight": {
"maxLoad": 5
}
},
"label": "veh-cargobike",
"costPerKilometer": 1
},
{
"travelMode": "DRIVING",
"startLocation": {
"latitude": 49.2808422,
"longitude": -123.0802082
},
"startTimeWindows": [
{
"startTime": "2024-07-08T16:00:00Z"
}
],
"endTimeWindows": [
{
"endTime": "2024-07-08T18:00:00Z"
}
],
"loadLimits": {
"weight": {
"maxLoad": 5
}
},
"label": "veh-sprintervan",
"costPerKilometer": 1
}
],
"globalStartTime": "2024-07-08T16:00:00Z",
"globalEndTime": "2024-07-09T02:59:59Z"
},
"populatePolylines": true
}
Output
{
"routes": [
{
"vehicleLabel": "veh-cargobike",
"vehicleStartTime": "2024-07-08T16:00:00Z",
"vehicleEndTime": "2024-07-08T16:27:46Z",
"visits": [
{
"startTime": "2024-07-08T16:17:46Z",
"detour": "0s",
"shipmentLabel": "dt123",
"loadDemands": {
"weight": {
"amount": "-1"
}
}
}
],
"transitions": [
{
"travelDuration": "1066s",
"travelDistanceMeters": 6141,
"waitDuration": "0s",
"totalDuration": "1066s",
"startTime": "2024-07-08T16:00:00Z",
"vehicleLoads": {
"weight": {
"amount": "1"
}
}
},
{
"travelDuration": "0s",
"waitDuration": "0s",
"totalDuration": "0s",
"startTime": "2024-07-08T16:27:46Z",
"vehicleLoads": {
"weight": {}
}
}
],
"routePolyline": {
"points": "ijukHdbfnV[pDG|@Z?J?FA|@EHAV?J?xA@`@@?TAVCXCVUfBOxAQtAOvAALOpACN?@CNQ`B]~CK|@OpAIv@QbBEXEXIt@?DCNShBQtA?BABK~@EXCXA`@A\\ATAV?@?T?N?tC?v@At@?bB?b@A\\?@?f@?J?lAAb@?`B?z@?nAA\\?xC?t@?f@A`@AVAPE`@CNCPAHI^ADEVI\\KZKZKXCBKRyA|CcAtBm@t@}@lBa@z@MTIRITKX?@ERK`@qBEw@EM?QAQ?s@Ca@?WCEAOAOEKAGCKE[Oo@YOG[OQGMCKCKAWCM?eBE]Ai@AM?m@AG?cACE?iCEkAAUAsFIsBEY?m@Aa@Au@A@b@BlB?R@~@BbB?l@@t@@v@@\\?Z?@@t@@J@t@?N@X?@?Z?X?t@?H?XAv@Al@Cv@Ad@Eh@GdAC`@OdBCLGb@GXWtAS~@Qn@IVGRA@CFWt@a@~@IV]l@[f@c@|@[h@IPMRUb@INIRIJGHIJc@t@CFA@AH_@p@i@|@s@nA_BjCINS^OXKPININMPCFMTINKPWd@_A`Bg@z@Wd@}@dBIJa@v@GJGLcAbBCFa@t@MRKRw@tACDUb@m@`Aa@t@]j@m@bAOXUb@i@|@IP{@zAc@z@M\\GREPCLAJEZGp@CXKrACTETERA@GVEJEHMVYl@_@n@INOVILYd@q@nAYd@?@Wd@gAhBIRCFyAfCaAfBCDQXGHCNAL?F@D?B@B@@@DBBBDh@~@r@jADHRZFNHNDF?@v@rAdAhBl@bA@@LRJPYd@q@lAgB~CiBbDCDc@t@EFsCbFGJ[l@yAjCa@r@o@jAiB~Cs@lAo@lAb@r@b@t@Wf@"
},
"metrics": {
"performedShipmentCount": 1,
"travelDuration": "1066s",
"waitDuration": "0s",
"delayDuration": "0s",
"breakDuration": "0s",
"visitDuration": "600s",
"totalDuration": "1666s",
"travelDistanceMeters": 6141,
"maxLoads": {
"weight": {
"amount": "1"
}
}
},
"routeCosts": {
"model.vehicles.cost_per_kilometer": 6.141
},
"routeTotalCost": 6.141
},
{
"vehicleIndex": 1,
"vehicleLabel": "veh-sprintervan"
}
],
"metrics": {
"aggregatedRouteMetrics": {
"performedShipmentCount": 1,
"travelDuration": "1066s",
"waitDuration": "0s",
"delayDuration": "0s",
"breakDuration": "0s",
"visitDuration": "600s",
"totalDuration": "1666s",
"travelDistanceMeters": 6141,
"maxLoads": {
"weight": {
"amount": "1"
}
}
},
"usedVehicleCount": 1,
"earliestVehicleStartTime": "2024-07-08T16:00:00Z",
"latestVehicleEndTime": "2024-07-08T16:27:46Z",
"totalCost": 6.141,
"costs": {
"model.vehicles.cost_per_kilometer": 6.141
}
}
}
As expected, GMPRO assigns veh-cargovan
for the delivery. Now, let's explore a more practical example of how territory optimization works in a real-world scenario.
GMPRO territory optimization worked example
Just as in the earlier example, we'll be using our 2 vehicles
, veh-cargobike
and veh-sprintervan
. But this time, we are going to give them 8 shipments
to deliver. 3 in downtown Vancouver, 3 in North Vancouver and 2 "on the way". The shipments
in downtown Vancouver and North Vancouver have had their costPerVehicle
array set up to prefer veh-cargobike
and veh-sprintervan
respectively, while the remaining 2 shipments
are "open" (they do not contain a costPerVehicle
array), and can be delivered by either vehicle.
Input
{
"model": {
"shipments": [
{
"deliveries": [
{
"arrivalLocation": {
"latitude": 49.31374169999999,
"longitude": -123.0514387
},
"timeWindows": [
{
"startTime": "2024-07-08T16:00:00Z",
"endTime": "2024-07-08T18:00:00Z"
}
],
"duration": "600s"
}
],
"loadDemands": {
"weight": {
"amount": 1
}
},
"costsPerVehicle": [
50,
0
],
"costsPerVehicleIndices": [
0,
1
],
"label": "nv789"
},
{
"deliveries": [
{
"arrivalLocation": {
"latitude": 49.343481,
"longitude": -123.0863414
},
"timeWindows": [
{
"startTime": "2024-07-08T16:00:00Z",
"endTime": "2024-07-08T18:00:00Z"
}
],
"duration": "600s"
}
],
"loadDemands": {
"weight": {
"amount": 1
}
},
"costsPerVehicle": [
50,
0
],
"costsPerVehicleIndices": [
0,
1
],
"label": "nv987"
},
{
"deliveries": [
{
"arrivalLocation": {
"latitude": 49.309186,
"longitude": -123.033168
},
"timeWindows": [
{
"startTime": "2024-07-08T16:00:00Z",
"endTime": "2024-07-08T18:00:00Z"
}
],
"duration": "600s"
}
],
"loadDemands": {
"weight": {
"amount": 1
}
},
"costsPerVehicle": [
50,
0
],
"costsPerVehicleIndices": [
0,
1
],
"label": "nv123"
},
{
"deliveries": [
{
"arrivalLocation": {
"latitude": 49.2912037,
"longitude": -123.1362474
},
"timeWindows": [
{
"startTime": "2024-07-08T16:00:00Z",
"endTime": "2024-07-08T18:00:00Z"
}
],
"duration": "600s"
}
],
"loadDemands": {
"weight": {
"amount": 1
}
},
"costsPerVehicle": [
0,
50
],
"costsPerVehicleIndices": [
0,
1
],
"label": "dt123"
},
{
"deliveries": [
{
"arrivalLocation": {
"latitude": 49.2786079,
"longitude": -123.1141059
},
"timeWindows": [
{
"startTime": "2024-07-08T16:00:00Z",
"endTime": "2024-07-08T18:00:00Z"
}
],
"duration": "600s"
}
],
"loadDemands": {
"weight": {
"amount": 1
}
},
"costsPerVehicle": [
0,
50
],
"costsPerVehicleIndices": [
0,
1
],
"label": "dt456"
},
{
"deliveries": [
{
"arrivalLocation": {
"latitude": 49.2771031,
"longitude": -123.1088116
},
"timeWindows": [
{
"startTime": "2024-07-08T16:00:00Z",
"endTime": "2024-07-08T18:00:00Z"
}
],
"duration": "600s"
}
],
"loadDemands": {
"weight": {
"amount": 1
}
},
"costsPerVehicle": [
0,
50
],
"costsPerVehicleIndices": [
0,
1
],
"label": "dt789"
},
{
"deliveries": [
{
"arrivalLocation": {
"latitude": 49.2812657,
"longitude": -123.0280715
},
"timeWindows": [
{
"startTime": "2024-07-08T16:00:00Z",
"endTime": "2024-07-08T18:00:00Z"
}
],
"duration": "600s"
}
],
"loadDemands": {
"weight": {
"amount": 1
}
},
"label": "ev123"
},
{
"deliveries": [
{
"arrivalLocation": {
"latitude": 49.2580983,
"longitude": -123.1146481
},
"timeWindows": [
{
"startTime": "2024-07-08T16:00:00Z",
"endTime": "2024-07-08T18:00:00Z"
}
],
"duration": "600s"
}
],
"loadDemands": {
"weight": {
"amount": 1
}
},
"label": "ev456"
}
],
"vehicles": [
{
"travelMode": "DRIVING",
"startLocation": {
"latitude": 49.2668756,
"longitude": -123.0800413
},
"startTimeWindows": [
{
"startTime": "2024-07-08T16:00:00Z"
}
],
"endTimeWindows": [
{
"endTime": "2024-07-08T18:00:00Z"
}
],
"loadLimits": {
"weight": {
"maxLoad": 5
}
},
"label": "veh-cargobike",
"costPerKilometer": 1
},
{
"travelMode": "DRIVING",
"startLocation": {
"latitude": 49.2808422,
"longitude": -123.0802082
},
"startTimeWindows": [
{
"startTime": "2024-07-08T16:00:00Z"
}
],
"endTimeWindows": [
{
"endTime": "2024-07-08T18:00:00Z"
}
],
"loadLimits": {
"weight": {
"maxLoad": 5
}
},
"label": "veh-sprintervan",
"costPerKilometer": 1
}
],
"globalStartTime": "2024-07-08T16:00:00Z",
"globalEndTime": "2024-07-09T02:59:59Z"
},
"populatePolylines": true
}
Output
{
"routes": [
{
"vehicleLabel": "veh-cargobike",
"vehicleStartTime": "2024-07-08T16:00:00Z",
"vehicleEndTime": "2024-07-08T17:10:29Z",
"visits": [
{
"shipmentIndex": 7,
"startTime": "2024-07-08T16:10:08Z",
"detour": "0s",
"shipmentLabel": "ev456",
"loadDemands": {
"weight": {
"amount": "-1"
}
}
},
{
"shipmentIndex": 5,
"startTime": "2024-07-08T16:25:58Z",
"detour": "1010s",
"shipmentLabel": "dt789",
"loadDemands": {
"weight": {
"amount": "-1"
}
}
},
{
"shipmentIndex": 4,
"startTime": "2024-07-08T16:40:24Z",
"detour": "1835s",
"shipmentLabel": "dt456",
"loadDemands": {
"weight": {
"amount": "-1"
}
}
},
{
"shipmentIndex": 3,
"startTime": "2024-07-08T17:00:29Z",
"detour": "2563s",
"shipmentLabel": "dt123",
"loadDemands": {
"weight": {
"amount": "-1"
}
}
}
],
"transitions": [
{
"travelDuration": "608s",
"travelDistanceMeters": 3818,
"waitDuration": "0s",
"totalDuration": "608s",
"startTime": "2024-07-08T16:00:00Z",
"vehicleLoads": {
"weight": {
"amount": "4"
}
}
},
{
"travelDuration": "350s",
"travelDistanceMeters": 2313,
"waitDuration": "0s",
"totalDuration": "350s",
"startTime": "2024-07-08T16:20:08Z",
"vehicleLoads": {
"weight": {
"amount": "3"
}
}
},
{
"travelDuration": "266s",
"travelDistanceMeters": 1355,
"waitDuration": "0s",
"totalDuration": "266s",
"startTime": "2024-07-08T16:35:58Z",
"vehicleLoads": {
"weight": {
"amount": "2"
}
}
},
{
"travelDuration": "605s",
"travelDistanceMeters": 2737,
"waitDuration": "0s",
"totalDuration": "605s",
"startTime": "2024-07-08T16:50:24Z",
"vehicleLoads": {
"weight": {
"amount": "1"
}
}
},
{
"travelDuration": "0s",
"waitDuration": "0s",
"totalDuration": "0s",
"startTime": "2024-07-08T17:10:29Z",
"vehicleLoads": {
"weight": {}
}
}
],
"routePolyline": {
"points": "ijukHdbfnV[pDG|@Z?J?FA|@EHAV?J?xA@`@@nDI?@?@?@?@@??@?@?@@??@?@@??@@??@@?@??@@?@??A@?@??A@??A@A?A@??A?A?A?A?Ab@?h@@l@?n@@xA@z@@@?`@?AdK?r@CnI?`@?J?f@?fAA^?l@@hAKZ?@?@Af@?vA?tB?jB?jAP@zA@JADA@?bA@zAAP@x@?H@zA@|A?xA@@?vA@zA@rA@VDR@Z@@@DBFBBBNHDBNHTJb@`@f@b@VPVBXD?j@CzA?bAAv@?JA`BAnAAlC?n@AtBC`D?x@CxAAjA?dAAxA?xB?bB?dDClB?zAAbC?lB?h@@hAApA?fA?LCfFAzDAvCAl@CxCEtE?V?RGvFGrFC`EAfBAbCAbCCfCEpEA^APAr@?t@{A?}AAs@@c@@@kAAjAQ?g@Ac@?qCGQCA?g@CkB@KAQ@uAAgAAaACQ?M?y@GQA[Ac@Au@AIAc@Am@CUAo@?eB?aBAsAEkAAq@DU@c@?S?m@@e@@G?I?K?cA?}@B_AB]B]@o@@o@B_BBG@q@@kA@G?e@GsDGeAA_@?C?E?E?QAQ?C?yAEqACYAW?i@Ag@E]?]AWMMEMAIAMAA?OACAKAIAEAEAKESKMGKKKKEEACACIKMWGMEKEOGQCGq@_CKWEOEMCICKGMGMGEMKq@mBIUIUACGQGM?AIQi@wAEIKU_@aAEMGOUi@O_@k@wAMa@Si@M[IQIWGQIi@I_@E[C]CYCe@[AM?E?oAC_@A[?MAU?c@Ac@CE?EREPARCRAP?N?P?N@P@NBNBT@DBNDR@@DPbB`EPb@BHBH@PFTBH?@BL@J?H@B?H@F?P@R?JAJAVCZCZAV?X?VBXBXFVNp@FVHVLXLZN\\HRJRNTBFBDHJHF@BNJBBHDPDPDr@PHBFFHHb@l@PVRXFJJPNTUJSJKHSRKLQNKPIPGJCBEJCHMTg@|@[l@k@`Ak@_AgBaD_A_BYe@_@q@e@y@d@x@^p@Xd@~@~AfB`Dj@~@MRQXa@p@Wf@[n@w@pAMX_AbB}@|Ag@|@[h@a@r@EHQXm@fAQX}@~A_A`BMVEFo@nA{@|AOVQX[f@Yd@S\\INEFEF}@~AeAnBMROREHGHEFA@GFGHIFGFIDIFIDGDE@UH_@JQHQFQHOLMJA@KLC@MRORQZdAjB\\h@Zj@NPw@pAm@hAs@lAS`@iB`Ds@lA?@Yd@_CbEGNo@jAoBhDAAAACAA??@C?AB?BAB?B?B?B@BQZWf@s@lAYf@Wf@mArB?@w@tAUZy@|Ao@jAa@r@u@rAEHm@dA}CrFeAeBgAkBk@aAYg@Wf@"
},
"metrics": {
"performedShipmentCount": 4,
"travelDuration": "1829s",
"waitDuration": "0s",
"delayDuration": "0s",
"breakDuration": "0s",
"visitDuration": "2400s",
"totalDuration": "4229s",
"travelDistanceMeters": 10223,
"maxLoads": {
"weight": {
"amount": "4"
}
}
},
"routeCosts": {
"model.vehicles.cost_per_kilometer": 10.223
},
"routeTotalCost": 10.223
},
{
"vehicleIndex": 1,
"vehicleLabel": "veh-sprintervan",
"vehicleStartTime": "2024-07-08T16:00:00Z",
"vehicleEndTime": "2024-07-08T17:09:55Z",
"visits": [
{
"shipmentIndex": 6,
"startTime": "2024-07-08T16:09:00Z",
"detour": "0s",
"shipmentLabel": "ev123",
"loadDemands": {
"weight": {
"amount": "-1"
}
}
},
{
"shipmentIndex": 2,
"startTime": "2024-07-08T16:25:32Z",
"detour": "741s",
"shipmentLabel": "nv123",
"loadDemands": {
"weight": {
"amount": "-1"
}
}
},
{
"startTime": "2024-07-08T16:40:02Z",
"detour": "1578s",
"shipmentLabel": "nv789",
"loadDemands": {
"weight": {
"amount": "-1"
}
}
},
{
"shipmentIndex": 1,
"startTime": "2024-07-08T16:59:55Z",
"detour": "2555s",
"shipmentLabel": "nv987",
"loadDemands": {
"weight": {
"amount": "-1"
}
}
}
],
"transitions": [
{
"travelDuration": "540s",
"travelDistanceMeters": 3810,
"waitDuration": "0s",
"totalDuration": "540s",
"startTime": "2024-07-08T16:00:00Z",
"vehicleLoads": {
"weight": {
"amount": "4"
}
}
},
{
"travelDuration": "392s",
"travelDistanceMeters": 5041,
"waitDuration": "0s",
"totalDuration": "392s",
"startTime": "2024-07-08T16:19:00Z",
"vehicleLoads": {
"weight": {
"amount": "3"
}
}
},
{
"travelDuration": "270s",
"travelDistanceMeters": 1902,
"waitDuration": "0s",
"totalDuration": "270s",
"startTime": "2024-07-08T16:35:32Z",
"vehicleLoads": {
"weight": {
"amount": "2"
}
}
},
{
"travelDuration": "593s",
"travelDistanceMeters": 7110,
"waitDuration": "0s",
"totalDuration": "593s",
"startTime": "2024-07-08T16:50:02Z",
"vehicleLoads": {
"weight": {
"amount": "1"
}
}
},
{
"travelDuration": "0s",
"waitDuration": "0s",
"totalDuration": "0s",
"startTime": "2024-07-08T17:09:55Z",
"vehicleLoads": {
"weight": {}
}
}
],
"routePolyline": {
"points": "kdxkH|afnVDCDG@K?Ai@S?SAICWCa@Gc@B_@EWQ}@CMAKAME[AS?IAQAc@?g@?kA?_@@_@?[BsF?C?w@@w@?iEBc@D_GBiE@eD?u@?G?_@?y@@w@?U?S?W?S?m@?O?uB?[@mC@wC?e@@eB@mBA_FA_DA_@Ki@?g@@sG?u@@sG?w@@_K@yD?s@?_@?uE@}@?w@?q@@G@K?iF?kA?aE?w@?y@?mH?_@?sA?cA?oA?G?A?w@?{EAm@DKB_@@eB@kE?qE?mB?e@?U?U@oBAqA?AIi@AcB?}A?Y?i@?Y?i@?K?_@?W?]?W?_@?oA?_A?kB?eE?w@?mB?w@?]?uE?q@?K?mBFg@@_@?E?}@?_A@}A?cB?yA?Y?[AgA@s@?sE?c@Ae@Gi@@qA?qA?]?O?N?\\?pAApAIl@?x@A~A?`BAp@Ol@IVINMP[?i@?oB@mB?}A?k@?O?U?k@?wD@i@?g@?a@@Y?A?_@C]CI?UG[ISIMGMGOGOIOKIGIGKIWSWW[][]QSQAY_@a@i@OSA?QUQUSWQUCEKMSUQUOUQUQSU[OQIMGGOSUYMOQUMOU]QSQUe@m@QUQU_@e@CCOQUUWWe@[ECOGYKUEUEk@Ee@AS?C?u@@e@?a@@_@?E?m@@{@@o@?S@[?W?Q?e@@e@?_@?E?c@@]?C?e@@g@?Y@E?a@?s@@e@?c@@a@?O?_A@eA@k@@{@?{@@u@@_@?{@@m@?W@S?M?U?W@S?cA?cEDI?c@?[@i@?W@_@?[@Q?o@?a@@Q?e@@Y@M@M@YBQBa@FKBKBODMBKD[Hc@Re@Pc@XOJYPQJSLc@XWLOHQFOFIBYJSHMDMBQDSBUBWBk@@[?Y?O?C?K?Y?G?o@AUAS?_BCO?o@AY?k@AW?U?SAi@?]Ai@AW?k@AW?S?UA[[C?EAUCEAOCSCQEMCKEOGSMSUCEACCCCGEIAG?AAAAKAA?EACAM?KAAEMEOi@sBe@eAY]QUMEECQIUQ[K?`@BjA@RFtADv@BhAD`@B\\DTNv@Lj@DNFVFVLd@Nn@FZFT?BDTFf@?p@?ZALAJCTEPITELGLEFEFGHEFABABABAD?De@TMFMFWNKLGLKPEJGPM\\IRQn@Md@CNITA@ETEJAHANAZA^^CR@^B\\@d@B`@?N?L?J@F?|A?pAEtA?`Cm@t@QPE`AU?fD?b@?c@?gDaATQDu@PaCl@uA?qAD}A?G?KAM?O?a@?e@C]A_@CSA_@BIh@El@?X?B?hA?D?X@|@A^?b@@lBC`F?X?JCbKAzAAPEr@?BAr@?@?J?j@?h@?bA?d@Ab@ArBAvA?v@?t@?~@?p@?lECbA?lA?N@HBJ?v@?X?^Av@AfBAbCAjDA`ACtD?nBAhB?ZApAAnB?v@A~@MWKSAA@@JRLV@_A?w@@oB@qA?[@iB?oBBuD@aA@kD@cC@gB@w@?_@?Y?w@BG@E@EBQ?eA?i@BeD?M?aB@mABcDAcB?K?a@@a@QCi@IWICC_@OAAGEQKEE[Wg@q@c@w@Wg@ACIOOYO[q@mAYg@_@q@k@iAi@gASVi@dACF]h@A@CFKLABY^i@p@_@d@i@d@ABABQh@_BjAoA|@aAr@}@n@}@n@{@l@e@\\UNiAx@[TWP{@l@q@f@{@l@w@j@UP}@l@i@^e@\\oA|@YREB]V[Ty@j@c@\\[Ri@`@w@h@IHOLc@^e@f@]`@UZEFYd@QZMT_@x@Sf@ELGNQd@K\\IZGRI`@A@Kh@I`@IZG\\Qz@Kh@Or@Mx@UdAI`@SdAKb@ERGXEPI^GTGTWz@KVQf@KXMZO\\[p@S`@INOXILOTKLMPORILOPQRSVQPQPWTYXA@KHSP]XQNURA@URQNONURQP_@b@Y^SVCBOROVQTEHGL]j@O\\INWh@ABM\\M\\O`@ELITGTGNQl@K`@K`@K`@G^I`@EPCPO~@CTGb@ANCL?@Gj@C`@CPAPEh@A\\ATAZ?^?DA\\?`@@d@?b@@d@?^@L?X@~@BlA@bA@R@v@@jABhA?N?T@^@h@?`@@d@@`@?x@?d@?b@?r@?~@?^?n@AD?v@?l@?d@?zAAvA?F?z@@fA?j@?dA@bBAbA?lAAzA?r@?n@?R?H?h@?@A`A?l@?bA?j@A\\?V?n@?|@?l@AjA?j@?\\ArD?fA?p@?TAfA?hBCnBAlAAnAAz@AdAApAAp@?B?l@?^?fA?h@@X@tC@t@?P?P?H@t@@fA?x@@f@?p@OZA@?BExB?DCtACx@?@?n@A\\?B?X?nA?B@l@?p@?j@?XATA|@Ox@CLCDCDEHQTi@?cCDQPkA@wBCeAAA?iACO?w@?iB?_BCeACA?cBCS?A?wC@U?wAAq@Be@@]AMAIAICGCIEICGEGEIGiCcBuDcCcBkAkAu@gAo@qAy@On@It@Iz@INCH"
},
"metrics": {
"performedShipmentCount": 4,
"travelDuration": "1795s",
"waitDuration": "0s",
"delayDuration": "0s",
"breakDuration": "0s",
"visitDuration": "2400s",
"totalDuration": "4195s",
"travelDistanceMeters": 17863,
"maxLoads": {
"weight": {
"amount": "4"
}
}
},
"routeCosts": {
"model.vehicles.cost_per_kilometer": 17.863
},
"routeTotalCost": 17.863
}
],
"metrics": {
"aggregatedRouteMetrics": {
"performedShipmentCount": 8,
"travelDuration": "3624s",
"waitDuration": "0s",
"delayDuration": "0s",
"breakDuration": "0s",
"visitDuration": "4800s",
"totalDuration": "8424s",
"travelDistanceMeters": 28086,
"maxLoads": {
"weight": {
"amount": "4"
}
}
},
"usedVehicleCount": 2,
"earliestVehicleStartTime": "2024-07-08T16:00:00Z",
"latestVehicleEndTime": "2024-07-08T17:10:29Z",
"totalCost": 28.086,
"costs": {
"model.vehicles.cost_per_kilometer": 28.086
}
}
}
As we hoped, both the downtown
and north-vancouver
regions are served by veh-cargobike
and veh-sprintervan
respectively. Of the remaining 2 deliveries in East Vancouver, one was assigned to veh-cargobike
and the other to veh-sprintervan
, depending on which was passing through.
Testing territory optimization soft constraints in GMPRO
At the beginning of this post, I mentioned that I prefer territory optimization with soft constraints because it allows drivers to assist in other territories when needed. Here's the same 8 shipment
/ 2 vehicle
example but with a capacity constraint on veh-cargobike
so that it can only do 2 deliveries instead of the original 5.
{
"loadLimits": {
"weight": {
"maxLoad": 2
}
},
"label": "veh-cargobike"
}
Input
{
"model": {
"shipments": [
{
"deliveries": [
{
"arrivalLocation": {
"latitude": 49.31374169999999,
"longitude": -123.0514387
},
"timeWindows": [
{
"startTime": "2024-07-08T16:00:00Z",
"endTime": "2024-07-08T18:00:00Z"
}
],
"duration": "600s"
}
],
"loadDemands": {
"weight": {
"amount": 1
}
},
"costsPerVehicle": [
50,
0
],
"costsPerVehicleIndices": [
0,
1
],
"label": "nv789"
},
{
"deliveries": [
{
"arrivalLocation": {
"latitude": 49.343481,
"longitude": -123.0863414
},
"timeWindows": [
{
"startTime": "2024-07-08T16:00:00Z",
"endTime": "2024-07-08T18:00:00Z"
}
],
"duration": "600s"
}
],
"loadDemands": {
"weight": {
"amount": 1
}
},
"costsPerVehicle": [
50,
0
],
"costsPerVehicleIndices": [
0,
1
],
"label": "nv987"
},
{
"deliveries": [
{
"arrivalLocation": {
"latitude": 49.309186,
"longitude": -123.033168
},
"timeWindows": [
{
"startTime": "2024-07-08T16:00:00Z",
"endTime": "2024-07-08T18:00:00Z"
}
],
"duration": "600s"
}
],
"loadDemands": {
"weight": {
"amount": 1
}
},
"costsPerVehicle": [
50,
0
],
"costsPerVehicleIndices": [
0,
1
],
"label": "nv123"
},
{
"deliveries": [
{
"arrivalLocation": {
"latitude": 49.2912037,
"longitude": -123.1362474
},
"timeWindows": [
{
"startTime": "2024-07-08T16:00:00Z",
"endTime": "2024-07-08T18:00:00Z"
}
],
"duration": "600s"
}
],
"loadDemands": {
"weight": {
"amount": 1
}
},
"costsPerVehicle": [
0,
50
],
"costsPerVehicleIndices": [
0,
1
],
"label": "dt123"
},
{
"deliveries": [
{
"arrivalLocation": {
"latitude": 49.2786079,
"longitude": -123.1141059
},
"timeWindows": [
{
"startTime": "2024-07-08T16:00:00Z",
"endTime": "2024-07-08T18:00:00Z"
}
],
"duration": "600s"
}
],
"loadDemands": {
"weight": {
"amount": 1
}
},
"costsPerVehicle": [
0,
50
],
"costsPerVehicleIndices": [
0,
1
],
"label": "dt456"
},
{
"deliveries": [
{
"arrivalLocation": {
"latitude": 49.2771031,
"longitude": -123.1088116
},
"timeWindows": [
{
"startTime": "2024-07-08T16:00:00Z",
"endTime": "2024-07-08T18:00:00Z"
}
],
"duration": "600s"
}
],
"loadDemands": {
"weight": {
"amount": 1
}
},
"costsPerVehicle": [
0,
50
],
"costsPerVehicleIndices": [
0,
1
],
"label": "dt789"
},
{
"deliveries": [
{
"arrivalLocation": {
"latitude": 49.2812657,
"longitude": -123.0280715
},
"timeWindows": [
{
"startTime": "2024-07-08T16:00:00Z",
"endTime": "2024-07-08T18:00:00Z"
}
],
"duration": "600s"
}
],
"loadDemands": {
"weight": {
"amount": 1
}
},
"label": "ev123"
},
{
"deliveries": [
{
"arrivalLocation": {
"latitude": 49.2580983,
"longitude": -123.1146481
},
"timeWindows": [
{
"startTime": "2024-07-08T16:00:00Z",
"endTime": "2024-07-08T18:00:00Z"
}
],
"duration": "600s"
}
],
"loadDemands": {
"weight": {
"amount": 1
}
},
"label": "ev456"
}
],
"vehicles": [
{
"travelMode": "DRIVING",
"startLocation": {
"latitude": 49.2668756,
"longitude": -123.0800413
},
"startTimeWindows": [
{
"startTime": "2024-07-08T16:00:00Z"
}
],
"endTimeWindows": [
{
"endTime": "2024-07-08T18:00:00Z"
}
],
"loadLimits": {
"weight": {
"maxLoad": 2
}
},
"label": "veh-cargobike",
"costPerKilometer": 1
},
{
"travelMode": "DRIVING",
"startLocation": {
"latitude": 49.2808422,
"longitude": -123.0802082
},
"startTimeWindows": [
{
"startTime": "2024-07-08T16:00:00Z"
}
],
"endTimeWindows": [
{
"endTime": "2024-07-08T18:00:00Z"
}
],
"loadLimits": {
"weight": {
"maxLoad": 6
}
},
"label": "veh-sprintervan",
"costPerKilometer": 1
}
],
"globalStartTime": "2024-07-08T16:00:00Z",
"globalEndTime": "2024-07-09T02:59:59Z"
},
"populatePolylines": true
}
Output
{
"routes": [
{
"vehicleLabel": "veh-cargobike",
"vehicleStartTime": "2024-07-08T16:00:00Z",
"vehicleEndTime": "2024-07-08T16:39:54Z",
"visits": [
{
"shipmentIndex": 4,
"startTime": "2024-07-08T16:09:49Z",
"detour": "0s",
"shipmentLabel": "dt456",
"loadDemands": {
"weight": {
"amount": "-1"
}
}
},
{
"shipmentIndex": 3,
"startTime": "2024-07-08T16:29:54Z",
"detour": "728s",
"shipmentLabel": "dt123",
"loadDemands": {
"weight": {
"amount": "-1"
}
}
}
],
"transitions": [
{
"travelDuration": "589s",
"travelDistanceMeters": 3955,
"waitDuration": "0s",
"totalDuration": "589s",
"startTime": "2024-07-08T16:00:00Z",
"vehicleLoads": {
"weight": {
"amount": "2"
}
}
},
{
"travelDuration": "605s",
"travelDistanceMeters": 2737,
"waitDuration": "0s",
"totalDuration": "605s",
"startTime": "2024-07-08T16:19:49Z",
"vehicleLoads": {
"weight": {
"amount": "1"
}
}
},
{
"travelDuration": "0s",
"waitDuration": "0s",
"totalDuration": "0s",
"startTime": "2024-07-08T16:39:54Z",
"vehicleLoads": {
"weight": {}
}
}
],
"routePolyline": {
"points": "ijukHdbfnV[pDG|@Z?J?FA|@EHAV?J?xA@`@@?TAVCXCVUfBOxAQtAOvAALOpACN?@CNQ`B]~CK|@OpAIv@QbBEXEXIt@?DCNShBQtA?BABK~@EXCXA`@A\\ATAV?@?T?N?tC?v@At@?bB?b@A\\?@?f@?J?lAAb@?`B?z@?nAA\\?xC?t@?f@A`@AVAPE`@CNCPAHI^ADEVI\\KZKZKXCBKRyA|CcAtBm@t@}@lBa@z@MTIRITKX?@ERK`@qBEw@EM?QAQ?s@Ca@?WCEAOAOEKAGCKE[Oo@YOG[OQGMCKCKAWCM?eBE]Ai@AM?m@AG?cACE?iCEkAAUAsFIsBEY?m@Aa@Au@A@b@BlB?R@~@BbB?l@@t@@v@@\\?Z?@@t@@J@t@?N@X?@?Z?X?t@?H?XAv@Al@Cv@Ad@Eh@GdAC`@OdBCLGb@GXWtAS~@Qn@IVGRA@CFWt@a@~@IV]l@[f@c@|@[h@IPMRUb@INIRIJGHIJc@t@CFA@AH_@p@i@|@s@nAfAnB~AnCZf@\\h@d@x@DHXf@x@tAd@x@^p@Xd@~@~AfB`Dj@~@MRQXa@p@Wf@[n@w@pAMX_AbB}@|Ag@|@[h@a@r@EHQXm@fAQX}@~A_A`BMVEFo@nA{@|AOVQX[f@Yd@S\\INEFEF}@~AeAnBMROREHGHEFA@GFGHIFGFIDIFIDGDE@UH_@JQHQFQHOLMJA@KLC@MRORQZdAjB\\h@Zj@NPw@pAm@hAs@lAS`@iB`Ds@lA?@Yd@_CbEGNo@jAoBhDAAAACAA??@C?AB?BAB?B?B?B@BQZWf@s@lAYf@Wf@mArB?@w@tAUZy@|Ao@jAa@r@u@rAEHm@dA}CrFeAeBgAkBk@aAYg@Wf@"
},
"metrics": {
"performedShipmentCount": 2,
"travelDuration": "1194s",
"waitDuration": "0s",
"delayDuration": "0s",
"breakDuration": "0s",
"visitDuration": "1200s",
"totalDuration": "2394s",
"travelDistanceMeters": 6692,
"maxLoads": {
"weight": {
"amount": "2"
}
}
},
"routeCosts": {
"model.vehicles.cost_per_kilometer": 6.692
},
"routeTotalCost": 6.692
},
{
"vehicleIndex": 1,
"vehicleLabel": "veh-sprintervan",
"vehicleStartTime": "2024-07-08T16:00:00Z",
"vehicleEndTime": "2024-07-08T17:53:47Z",
"visits": [
{
"shipmentIndex": 7,
"startTime": "2024-07-08T16:14:02Z",
"detour": "0s",
"shipmentLabel": "ev456",
"loadDemands": {
"weight": {
"amount": "-1"
}
}
},
{
"shipmentIndex": 5,
"startTime": "2024-07-08T16:29:52Z",
"detour": "1339s",
"shipmentLabel": "dt789",
"loadDemands": {
"weight": {
"amount": "-1"
}
}
},
{
"shipmentIndex": 6,
"startTime": "2024-07-08T16:52:52Z",
"detour": "2632s",
"shipmentLabel": "ev123",
"loadDemands": {
"weight": {
"amount": "-1"
}
}
},
{
"shipmentIndex": 2,
"startTime": "2024-07-08T17:09:24Z",
"detour": "3373s",
"shipmentLabel": "nv123",
"loadDemands": {
"weight": {
"amount": "-1"
}
}
},
{
"startTime": "2024-07-08T17:23:54Z",
"detour": "4210s",
"shipmentLabel": "nv789",
"loadDemands": {
"weight": {
"amount": "-1"
}
}
},
{
"shipmentIndex": 1,
"startTime": "2024-07-08T17:43:47Z",
"detour": "5187s",
"shipmentLabel": "nv987",
"loadDemands": {
"weight": {
"amount": "-1"
}
}
}
],
"transitions": [
{
"travelDuration": "842s",
"travelDistanceMeters": 5856,
"waitDuration": "0s",
"totalDuration": "842s",
"startTime": "2024-07-08T16:00:00Z",
"vehicleLoads": {
"weight": {
"amount": "6"
}
}
},
{
"travelDuration": "350s",
"travelDistanceMeters": 2313,
"waitDuration": "0s",
"totalDuration": "350s",
"startTime": "2024-07-08T16:24:02Z",
"vehicleLoads": {
"weight": {
"amount": "5"
}
}
},
{
"travelDuration": "780s",
"travelDistanceMeters": 6366,
"waitDuration": "0s",
"totalDuration": "780s",
"startTime": "2024-07-08T16:39:52Z",
"vehicleLoads": {
"weight": {
"amount": "4"
}
}
},
{
"travelDuration": "392s",
"travelDistanceMeters": 5041,
"waitDuration": "0s",
"totalDuration": "392s",
"startTime": "2024-07-08T17:02:52Z",
"vehicleLoads": {
"weight": {
"amount": "3"
}
}
},
{
"travelDuration": "270s",
"travelDistanceMeters": 1902,
"waitDuration": "0s",
"totalDuration": "270s",
"startTime": "2024-07-08T17:19:24Z",
"vehicleLoads": {
"weight": {
"amount": "2"
}
}
},
{
"travelDuration": "593s",
"travelDistanceMeters": 7110,
"waitDuration": "0s",
"totalDuration": "593s",
"startTime": "2024-07-08T17:33:54Z",
"vehicleLoads": {
"weight": {
"amount": "1"
}
}
},
{
"travelDuration": "0s",
"waitDuration": "0s",
"totalDuration": "0s",
"startTime": "2024-07-08T17:53:47Z",
"vehicleLoads": {
"weight": {}
}
}
],
"routePolyline": {
"points": "kdxkH|afnVDCDG@K?A?G@GDIBAJ?b@A?eA@eABCBC@C@G@E@E@_B@w@BeD?u@vA@|@?ZBvA@vAB|A@|A?vA?vAD|A@rA@xDBd@?t@@tA?jB?lA@tEBjB@R@fA@f@BRFNBV@h@Bh@B|@@^@vA@vA?r@ERALCPI`@?j@?xD@`@@r@@f@@Z?^@R?T?Z?J@N?V?n@@v@BpABr@?\\?H?PHn@BfA@zADBAX@x@??nAAtB?`D?n@Fh@Ar@?r@C`EAdK?r@CnI?`@?J?f@?fAA^?l@@hAKZ?@?@Af@?vA?tB?jB?jAP@zA@JADA@?bA@zAAP@x@?H@zA@|A?xA@@?vA@zA@rA@VDR@Z@@@DBFBBBNHDBNHTJb@`@f@b@VPVBXD?j@CzA?bAAv@?JA`BAnAAlC?n@AtBC`D?x@CxAAjA?dAAxA?xB?bB?dDClB?zAAbC?lB?h@@hAApA?fA?LCfFAzDAvCAl@CxCEtE?V?RGvFGrFC`EAfBAbCAbCCfCEpEA^APAr@?t@{A?}AAs@@c@@@kAAjAQ?g@Ac@?qCGQCA?g@CkB@KAQ@uAAgAAaACQ?M?y@GQA[Ac@Au@AIAc@Am@CUAo@?eB?aBAsAEkAAq@DU@c@?S?m@@e@@G?I?K?cA?}@B_AB]B]@o@@o@B_BBG@q@@kA@G?e@GsDGeAA_@?C?E?E?QAQ?C?yAEqACYAW?i@Ag@E]?]AWMMEMAIAMAA?OACAKAIAEAEAKESKMGKKKKEEACACIKMWGMEKEOGQCGq@_CKWEOEMCICKGMGMGEMKq@mBIUIUACGQGM?AIQi@wAEIKU_@aAEMGOUi@O_@k@wAMa@Si@M[IQIWGQkAN?IBe@?]@O?O?W@w@DyF?I?K?_@@_@?u@@mB?e@@O@yD@w@?K@i@?Y?{@?i@?e@?kA@k@?Y@gA@oA?o@?I@I@cD?a@?c@@S?_@?q@@]@]?A@[@]B]D[D]D[ZsBD_@D]D_@D]@eB?_@A]@qADmK@mB@w@B{E@mB@mB@w@@sB?u@B}C?G@mB@qB?Y@YD_M?Q?M?K?M?I?IAK?EAI?OCSCSCS?AEQESEQCMESCOAGCOAEAMAGAI?GAI?IAK?G?KAK?K?K?M?QBwD?_ABaC?kC@kC@gC@{B?u@?cB@mBBkDByG?g@?k@@_C@wB?y@@kE@eEByD?U?u@?w@@cB?_@@gA?}@?qG?]?W?[?YuAAA?yA?wA?oA@G?]?}@Ay@Ae@?eACO?gA?S@_ACW?wACA_FA_DA_@Ki@?g@@sG?u@@sG?w@@_K@yD?s@?_@?uE@}@?w@?q@@G@K?iF?kA?aE?w@?y@?mH?_@?sA?cA?oA?G?A?w@?{EAm@DKB_@@eB@kE?qE?mB?e@?U?U@oBAqA?AIi@AcB?}A?Y?i@?Y?i@?K?_@?W?]?W?_@?oA?_A?kB?eE?w@?mB?w@?]?uE?q@?K?mBFg@@_@?E?}@?_A@}A?cB?yA?Y?[AgA@s@?sE?c@Ae@Gi@@qA?qA?]?O?N?\\?pAApAIl@?x@A~A?`BAp@Ol@IVINMP[?i@?oB@mB?}A?k@?O?U?k@?wD@i@?g@?a@@Y?A?_@C]CI?UG[ISIMGMGOGOIOKIGIGKIWSWW[][]QSQAY_@a@i@OSA?QUQUSWQUCEKMSUQUOUQUQSU[OQIMGGOSUYMOQUMOU]QSQUe@m@QUQU_@e@CCOQUUWWe@[ECOGYKUEUEk@Ee@AS?C?u@@e@?a@@_@?E?m@@{@@o@?S@[?W?Q?e@@e@?_@?E?c@@]?C?e@@g@?Y@E?a@?s@@e@?c@@a@?O?_A@eA@k@@{@?{@@u@@_@?{@@m@?W@S?M?U?W@S?cA?cEDI?c@?[@i@?W@_@?[@Q?o@?a@@Q?e@@Y@M@M@YBQBa@FKBKBODMBKD[Hc@Re@Pc@XOJYPQJSLc@XWLOHQFOFIBYJSHMDMBQDSBUBWBk@@[?Y?O?C?K?Y?G?o@AUAS?_BCO?o@AY?k@AW?U?SAi@?]Ai@AW?k@AW?S?UA[[C?EAUCEAOCSCQEMCKEOGSMSUCEACCCCGEIAG?AAAAKAA?EACAM?KAAEMEOi@sBe@eAY]QUMEECQIUQ[K?`@BjA@RFtADv@BhAD`@B\\DTNv@Lj@DNFVFVLd@Nn@FZFT?BDTFf@?p@?ZALAJCTEPITELGLEFEFGHEFABABABAD?De@TMFMFWNKLGLKPEJGPM\\IRQn@Md@CNITA@ETEJAHANAZA^^CR@^B\\@d@B`@?N?L?J@F?|A?pAEtA?`Cm@t@QPE`AU?fD?b@?c@?gDaATQDu@PaCl@uA?qAD}A?G?KAM?O?a@?e@C]A_@CSA_@BIh@El@?X?B?hA?D?X@|@A^?b@@lBC`F?X?JCbKAzAAPEr@?BAr@?@?J?j@?h@?bA?d@Ab@ArBAvA?v@?t@?~@?p@?lECbA?lA?N@HBJ?v@?X?^Av@AfBAbCAjDA`ACtD?nBAhB?ZApAAnB?v@A~@MWKSAA@@JRLV@_A?w@@oB@qA?[@iB?oBBuD@aA@kD@cC@gB@w@?_@?Y?w@BG@E@EBQ?eA?i@BeD?M?aB@mABcDAcB?K?a@@a@QCi@IWICC_@OAAGEQKEE[Wg@q@c@w@Wg@ACIOOYO[q@mAYg@_@q@k@iAi@gASVi@dACF]h@A@CFKLABY^i@p@_@d@i@d@ABABQh@_BjAoA|@aAr@}@n@}@n@{@l@e@\\UNiAx@[TWP{@l@q@f@{@l@w@j@UP}@l@i@^e@\\oA|@YREB]V[Ty@j@c@\\[Ri@`@w@h@IHOLc@^e@f@]`@UZEFYd@QZMT_@x@Sf@ELGNQd@K\\IZGRI`@A@Kh@I`@IZG\\Qz@Kh@Or@Mx@UdAI`@SdAKb@ERGXEPI^GTGTWz@KVQf@KXMZO\\[p@S`@INOXILOTKLMPORILOPQRSVQPQPWTYXA@KHSP]XQNURA@URQNONURQP_@b@Y^SVCBOROVQTEHGL]j@O\\INWh@ABM\\M\\O`@ELITGTGNQl@K`@K`@K`@G^I`@EPCPO~@CTGb@ANCL?@Gj@C`@CPAPEh@A\\ATAZ?^?DA\\?`@@d@?b@@d@?^@L?X@~@BlA@bA@R@v@@jABhA?N?T@^@h@?`@@d@@`@?x@?d@?b@?r@?~@?^?n@AD?v@?l@?d@?zAAvA?F?z@@fA?j@?dA@bBAbA?lAAzA?r@?n@?R?H?h@?@A`A?l@?bA?j@A\\?V?n@?|@?l@AjA?j@?\\ArD?fA?p@?TAfA?hBCnBAlAAnAAz@AdAApAAp@?B?l@?^?fA?h@@X@tC@t@?P?P?H@t@@fA?x@@f@?p@OZA@?BExB?DCtACx@?@?n@A\\?B?X?nA?B@l@?p@?j@?XATA|@Ox@CLCDCDEHQTi@?cCDQPkA@wBCeAAA?iACO?w@?iB?_BCeACA?cBCS?A?wC@U?wAAq@Be@@]AMAIAICGCIEICGEGEIGiCcBuDcCcBkAkAu@gAo@qAy@On@It@Iz@INCH"
},
"metrics": {
"performedShipmentCount": 6,
"travelDuration": "3227s",
"waitDuration": "0s",
"delayDuration": "0s",
"breakDuration": "0s",
"visitDuration": "3600s",
"totalDuration": "6827s",
"travelDistanceMeters": 28588,
"maxLoads": {
"weight": {
"amount": "6"
}
}
},
"routeCosts": {
"model.shipments.costs_per_vehicle": 50,
"model.vehicles.cost_per_kilometer": 28.588
},
"routeTotalCost": 78.588
}
],
"metrics": {
"aggregatedRouteMetrics": {
"performedShipmentCount": 8,
"travelDuration": "4421s",
"waitDuration": "0s",
"delayDuration": "0s",
"breakDuration": "0s",
"visitDuration": "4800s",
"totalDuration": "9221s",
"travelDistanceMeters": 35280,
"maxLoads": {
"weight": {
"amount": "6"
}
}
},
"usedVehicleCount": 2,
"earliestVehicleStartTime": "2024-07-08T16:00:00Z",
"latestVehicleEndTime": "2024-07-08T17:53:47Z",
"totalCost": 85.28,
"costs": {
"model.vehicles.cost_per_kilometer": 35.28,
"model.shipments.costs_per_vehicle": 50
}
}
}
Due to the capacity constraint, veh-sprintervan
(orange) is assigned to make its first two stops in the downtown
area to assist with deliveries that veh-cargobike
cannot handle.
Conclusion
So that's how you build a territory optimization system in GMPRO. The same soft constraint approach can be used to preferentially assign bookings to drivers with high ratings (ride share), prioritize high margin deliveries (third party logistics) as well as match jobs to technicians optimally (field services). Please contact [email protected] if you need specific help modelling your business operations with GMPRO.
👋 As always, if you have any questions or suggestions for me, please reach out or say hello on LinkedIn.
Next: Part 7: GMPRO docs: Solving the VRP with route clustering and soft constraints