GMPRO docs: Driver breaks

How to add driver breaks to a route using the Google Maps Platform Route Optimization API.

GMPRO docs: Driver breaks

Ever take a long road trip and feel like you need a short nap at a highway rest stop before getting back behind the wheel? Those little breaks aren't just nice - they're essential for keeping you refreshed and ready for the road ahead. Professional drivers feel the same way too, which is why the Federal Motor Carrier Safety Administration (FMCSA), part of the United States Department of Transportation, requires a 30-minute break for drivers after 8 hours of work. The European Union has similar rules, and mandates a 45-minute break after 4.5 hours of driving.

In this blog post, I’ll show you how to incorporate driver breaks into a route plan, ensuring they are accounted for when using the Google Maps Platform Route Optimization API (GMPRO).

Driver breaks (coffee icon) shown on a gantt chart
Driver breaks (coffee icon) shown on a gantt chart

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
Part 7: GMPRO docs: Solving the VRP with route clustering and soft constraints
Part 8: GMPRO docs: Driver load balancing with soft constraints
Part 9: GMPRO docs: Driver breaks (this article)
Part 10: GMPRO docs: Complete deliveries before pickups in cargo bike logistics

What is a driver break?

A driver break is a planned pause in a route. The GMPRO documentation is even more specific, defining it as a contiguous period of time during which the vehicle remains idle at its current position and cannot perform any visit.

Driver breaks shown as a scheduled pause in a driver's route
Driver breaks shown as a scheduled pause in a driver's route

How do I add driver breaks to my route?

In GMPRO, adding driver breaks to a route is as simple as specifying a breakRule in the vehicle object like this:

{
    "breakRule": {
        "breakRequests": [
            {
                "earliestStartTime": "2024-07-08T17:00:00Z",
                "latestStartTime": "2024-07-08T17:00:00Z",
                "minDuration": "1800s"
            }
        ]
    }
}

The breakRequests[] array contains breakRequest (break) objects, each specifying the start time and duration of a break. To schedule multiple breaks, you can add additional breakRequest objects to the array.

earliestStartTime is the earliest time that the break should start by, in UTC "Zulu" format.

latestStartTime specifies the latest possible time for the break to begin, provided in UTC "Zulu" format. To schedule a break at a fixed time, set both earliestStartTime and latestStartTime to the same value.

minDuration is the total time spent by the driver on break, in string format e.g. "minDuration": "600s" is 600 seconds, or 10 minutes.

👨‍💻
Screenshots in this blog post were taken with GMPRO-viewer , using data imported via GMPRO-json-converter. Both tools are free to use.

GMPRO driver breaks example

Here's an example of a 30-minute break scheduled to start at 10:00 AM PST, incorporated into a delivery route in Vancouver, BC.

Driver breaks shown as part of a route on the GMPRO-viewer app
Driver breaks shown as part of a route on the GMPRO-viewer app

Input

{
    "model": {
        "shipments": [
            {
                "deliveries": [
                    {
                        "arrivalLocation": {
                            "latitude": 49.3208826,
                            "longitude": -123.0710097
                        },
                        "duration": "600s",
                        "timeWindows": [
                            {
                                "startTime": "2024-07-08T16:00:00Z",
                                "endTime": "2024-07-08T19:00:00Z"
                            }
                        ]
                    }
                ],
                "loadDemands": {
                    "weight": {
                        "amount": "1"
                    }
                },
                "label": "yvr123"
            },
            {
                "deliveries": [
                    {
                        "arrivalLocation": {
                            "latitude": 49.313724,
                            "longitude": -123.0514561
                        },
                        "duration": "600s",
                        "timeWindows": [
                            {
                                "startTime": "2024-07-08T16:00:00Z",
                                "endTime": "2024-07-08T19:00:00Z"
                            }
                        ]
                    }
                ],
                "loadDemands": {
                    "weight": {
                        "amount": "1"
                    }
                },
                "label": "yvr789"
            },
            {
                "deliveries": [
                    {
                        "arrivalLocation": {
                            "latitude": 49.343481,
                            "longitude": -123.0863414
                        },
                        "duration": "600s",
                        "timeWindows": [
                            {
                                "startTime": "2024-07-08T16:00:00Z",
                                "endTime": "2024-07-08T19:00:00Z"
                            }
                        ]
                    }
                ],
                "loadDemands": {
                    "weight": {
                        "amount": "1"
                    }
                },
                "label": "yvr987"
            },
            {
                "deliveries": [
                    {
                        "arrivalLocation": {
                            "latitude": 49.3352081,
                            "longitude": -123.1453979
                        },
                        "duration": "600s",
                        "timeWindows": [
                            {
                                "startTime": "2024-07-08T16:00:00Z",
                                "endTime": "2024-07-08T19:00:00Z"
                            }
                        ]
                    }
                ],
                "loadDemands": {
                    "weight": {
                        "amount": "1"
                    }
                },
                "label": "yvr654"
            },
            {
                "deliveries": [
                    {
                        "arrivalLocation": {
                            "latitude": 49.3513846,
                            "longitude": -123.2631103
                        },
                        "duration": "600s",
                        "timeWindows": [
                            {
                                "startTime": "2024-07-08T16:00:00Z",
                                "endTime": "2024-07-08T19:00:00Z"
                            }
                        ]
                    }
                ],
                "loadDemands": {
                    "weight": {
                        "amount": "1"
                    }
                },
                "label": "yvr321"
            }
        ],
        "vehicles": [
            {
                "startLocation": {
                    "latitude": 49.3191441,
                    "longitude": -122.9522224
                },
                "loadLimits": {
                    "weight": {
                        "maxLoad": 5
                    }
                },
                "startTimeWindows": [
                    {
                        "startTime": "2024-07-08T16:00:00Z"
                    }
                ],
                "endTimeWindows": [
                    {
                        "endTime": "2024-07-08T19:00:00Z"
                    }
                ],
                "breakRule": {
                    "breakRequests": [
                        {
                            "earliestStartTime": "2024-07-08T17:00:00Z",
                            "latestStartTime": "2024-07-08T17:00:00Z",
                            "minDuration": "1800s"
                        }
                    ]
                },
                "label": "will-yvr",
                "costPerKilometer": 1
            }
        ],
        "globalStartTime": "2024-07-08T07:00:00Z",
        "globalEndTime": "2024-07-09T06:59:00Z"
    },
    "populatePolylines": true
}

Output

{
    "routes": [
        {
            "vehicleLabel": "will-yvr",
            "vehicleStartTime": "2024-07-08T16:00:00Z",
            "vehicleEndTime": "2024-07-08T18:10:29Z",
            "visits": [
                {
                    "shipmentIndex": 1,
                    "startTime": "2024-07-08T16:12:11Z",
                    "detour": "0s",
                    "shipmentLabel": "yvr789",
                    "loadDemands": {
                        "weight": {
                            "amount": "-1"
                        }
                    }
                },
                {
                    "startTime": "2024-07-08T16:27:57Z",
                    "detour": "614s",
                    "shipmentLabel": "yvr123",
                    "loadDemands": {
                        "weight": {
                            "amount": "-1"
                        }
                    }
                },
                {
                    "shipmentIndex": 2,
                    "startTime": "2024-07-08T16:47:46Z",
                    "detour": "1908s",
                    "shipmentLabel": "yvr987",
                    "loadDemands": {
                        "weight": {
                            "amount": "-1"
                        }
                    }
                },
                {
                    "shipmentIndex": 3,
                    "startTime": "2024-07-08T17:37:27Z",
                    "detour": "4706s",
                    "shipmentLabel": "yvr654",
                    "loadDemands": {
                        "weight": {
                            "amount": "-1"
                        }
                    }
                },
                {
                    "shipmentIndex": 4,
                    "startTime": "2024-07-08T18:00:29Z",
                    "detour": "5660s",
                    "shipmentLabel": "yvr321",
                    "loadDemands": {
                        "weight": {
                            "amount": "-1"
                        }
                    }
                }
            ],
            "transitions": [
                {
                    "travelDuration": "731s",
                    "travelDistanceMeters": 7966,
                    "waitDuration": "0s",
                    "totalDuration": "731s",
                    "startTime": "2024-07-08T16:00:00Z",
                    "vehicleLoads": {
                        "weight": {
                            "amount": "5"
                        }
                    }
                },
                {
                    "travelDuration": "346s",
                    "travelDistanceMeters": 2383,
                    "waitDuration": "0s",
                    "totalDuration": "346s",
                    "startTime": "2024-07-08T16:22:11Z",
                    "vehicleLoads": {
                        "weight": {
                            "amount": "4"
                        }
                    }
                },
                {
                    "travelDuration": "589s",
                    "travelDistanceMeters": 4089,
                    "waitDuration": "0s",
                    "totalDuration": "589s",
                    "startTime": "2024-07-08T16:37:57Z",
                    "vehicleLoads": {
                        "weight": {
                            "amount": "3"
                        }
                    }
                },
                {
                    "travelDuration": "581s",
                    "travelDistanceMeters": 6408,
                    "breakDuration": "1800s",
                    "waitDuration": "0s",
                    "totalDuration": "2381s",
                    "startTime": "2024-07-08T16:57:46Z",
                    "vehicleLoads": {
                        "weight": {
                            "amount": "2"
                        }
                    }
                },
                {
                    "travelDuration": "782s",
                    "travelDistanceMeters": 12161,
                    "waitDuration": "0s",
                    "totalDuration": "782s",
                    "startTime": "2024-07-08T17:47:27Z",
                    "vehicleLoads": {
                        "weight": {
                            "amount": "1"
                        }
                    }
                },
                {
                    "travelDuration": "0s",
                    "waitDuration": "0s",
                    "totalDuration": "0s",
                    "startTime": "2024-07-08T18:10:29Z",
                    "vehicleLoads": {
                        "weight": {}
                    }
                }
            ],
            "routePolyline": {
                "points": "ks_lHbfmmV?l@s@??l@?LAp@L?F?rA@~@@x@A~AGJ?dAFdB?j@Az@AzAC?\\At@?v@?rAAlB@p@AdF?lB?b@CpC?lB?lBAv@?dD?zC?T?hC?PAdD?H?r@An@ArC?hA?t@?v@A`KAvB?f@Av@?fA?~@Gb@?^?N?pB@x@?XBjB@Z?h@@t@@lA@vA@v@BxBL|HH|H?LBlB@lBD~ED~E@n@@jAAnD?lB?r@?z@AdC?t@Av@?v@?lBAt@?nBCjBAnB?t@?vAAdBAtBC|EAdDAbDAtCChB?nA?|BAjBAzE?\\AdDAdB?p@AhBAtB?BAt@?v@?hB?B?j@?D?P?T@V@VDp@Ff@D^Ff@F\\BJF\\H\\FRDR`@zAj@|ATp@L\\?@L\\HVVr@t@xBL^JXNb@n@bBHTLTZl@bArBZj@pA~BnAxBTb@Vb@p@tAh@`A`@r@r@lANZr@bBTp@Np@TlAHdABl@@n@CbACh@ALGd@EZGh@G\\Mv@Kp@Kj@AFCP?DE`@Ej@?BEn@AZ?N?T?R?Z?NBj@@^@T@B@TH`ALlABVFv@JdA@HFt@Hz@Dd@@HFr@?@Db@B\\@^@\\?\\?B?@AVA\\CXCPMj@?DI\\GPCFGN]t@CFCDs@rAi@fAm@jAO\\M\\GLCFIVENENKd@W~AE^CXEh@Eh@?BEfA?@Ah@?f@AjAAd@?N?T?l@?l@?`@BjA@RFtADv@BdA?BD`@B\\DTNv@Lj@DNFVFVLd@Nn@FZFT?BDTFf@?p@?ZALAJCTEPITELGLEFEFGHEFABABABAD?De@TMFMFWNKLGLKPEJGPM\\IRQn@Md@CNITA@ETEJAHANAZA^Ih@El@?X?B?hA?D?X@|@A^?b@@lBAlAArC?X?JCbKAzAAPEr@?BAr@?@?J?j@?N?X?bA?d@Ab@ArBAvA?v@?t@?~@?p@?lECbA?lA?N@HBJ?v@?X?^Av@AfBAbCAjDA`ACtD?nBAhB?ZApAAnB?v@A~@MWKSJRLV?hB?lAAdD?v@?~@AlB?vAAh@AbBAt@Av@?t@Af@?nB?BAbB@RA\\?lA?D?n@AxA?zAAlCAz@AnBAlA?b@AtCCdDCfKC`BAJAJAFEJQf@GTA@Ql@Sj@A?e@zAOd@K^M`@e@vAUj@ADe@tAAD{BxGM`@KKKM{@w@OOk@k@[YSMGCSGA?MCa@?eA?kBCcBAC?_@?gAAuAAO?k@AK?e@AM?sACK?aAAI?a@?UAI?O?Y?[A?t@?R?x@?t@?r@?\\AF?N?j@?BAv@?D?p@D?d@@z@B?cB?mA?cA?e@@iDUAI?O?Y?[A[?kACU?sAEc@A{@AkBAiBAsB?}A@cBEiBAeBAiBAeBCmB?yACeBEoA@M?_BAAtA?v@AdD?t@Aj@?H?h@?dA?rAiAEc@CmAEI?o@Ai@?M?W?E?E?S?S@_@AaAAcCBc@@O@QFK?gACS?eBCc@?kBAs@Ac@?c@AQ?E?MAeAC@v@@|@?dD?v@AdD?vB?zE?JAzF?nA?^?dCApD?hA?lB?dDAlBAzA?tA?t@?v@@lB?v@?t@?pD?`D?@?t@A|E?v@?v@eACA?cBCS?A?wC@U?wAAq@Be@@]AMAIAICGCIEICGEGEIGiCcBuDcCcBkAkAu@gAo@qAy@On@It@Iz@INCHBIHOH{@Hu@No@pAx@fAn@jAt@bBjAtDbChCbBHFFDFDHBHDFBHBH@L@\\@d@Ap@CvA@T?vCA@?R?bBB@?dAB~ABhB?v@?N?hAB@?dA@vBBjAAPFjBB\\@lA@@l@?N?f@?F?F@d@?h@@T@\\@`@Bh@?BD|@@j@Bf@HzC?B?@HPAxA?vAAt@?x@A~A?TA`@?J?`@AR?p@ChBA^CpBAf@C~@An@A~@?XAd@?L?XA^?p@A~@Al@Ab@?ZAv@?LAf@?@Ab@Ad@Aj@Al@?PChACnAAX?`@Aj@A@A~@Ab@Ab@Af@?h@CdAA`AAp@A`@Ad@?^Af@C`BAh@AxAAvB?`A?r@?f@A|A?rA?\\?R?|E?f@?L?d@?z@?b@?j@?h@?|@?l@?Z?lA?~@?`AAlC?N?dC?rB?fB?nA?`@?hA?v@?hA?z@?~A?pA?|@?\\?h@?|@?j@?Z?^?d@?N?T?b@A`@?h@AZAFA`@Eb@?FEXG`@GZADI^K\\M\\EHIPM\\MTOZa@t@k@hACBOVQ\\]n@S`@U`@GL]n@QZOXQZOXOXQ\\O\\MVOXIPEJOZEFIP]r@MVMVKTEHWh@EFMZOVUNA@EHINa@r@i@`AQZKROZ]v@Qb@CFEJUp@Od@[dAM`@Mj@Mv@Gj@SxACZA^Cj@Ch@ClAChA?JCz@A|@AZ?`@AN?`AChBAr@CzCA`A^?r@@T@f@@pAEfAEP?PAh@?LA@?XIvB@H?|@@l@@z@@@x@?z@Az@?NAf@?J?h@A`@?FAD?DCDADE@C@C@Q?S@O?_@@I?[@S?O@G@S@G@UFSHURA?EHEDOPEHIJGLENG^I\\CTCRCTAXA^Af@?@?P?rA?tB?j@@H?J?H@HDh@B^@D?F?D?|@?F?p@?|@?n@?jA?lB?lB?lBAv@?lB?t@AlD?n@xA?zA??u@?o@?n@?t@{A?yA?}ACwA?_BC[@U?c@?@PAb@?l@?`B?T?vAAdE?J?F?xA?R?xAArB?t@?t@?@?L?X?N?L?h@@r@Al@?z@?pA?nA?Z?j@?^?^?h@?B?Z?`AAlA@H?H?~@?D?r@Aj@?P?n@?bB?J?lBS?gAAe@?s@?cA?U?cA?EAQAwA?{A?w@?c@?k@?q@?OAKAIAGAECGCIAICIEKEIGQOKKKKIMKOKOGMWw@AEKa@IWK_@CUAEEUEYAYM?E?C@A?EBA@A@A@A@CDCFADAD?@AB?D?HAN?H?`@?T?f@?F?n@?X?D?t@?VA`@?R@`@?v@AH?^?\\?|@?^ATAb@?VAH?@?BFXGfAAPCf@C`@Ch@C^Ch@APAHC`@A^Ch@Cb@AHARCf@AZEh@Cb@A\\?@Cd@CZ?F?BC\\Cf@Cb@AP?@AJA`@C\\Cd@Cd@C^Cf@A^Cd@ATEh@A^A`@?BAXCl@?XAh@Ab@Af@?\\Ah@?h@?X@`@?b@@^?J@\\@d@@b@?L@L@L@\\@d@Bd@@X?@FnABz@Bd@FdB@X@l@?@B`ABhA@b@@fA@f@?X?b@?^?H?Z?h@?^?@Af@?`@A`@?TAl@Af@A^A^Ch@A\\Ad@CV?DCd@Ab@Cb@C^IdAGbAGbAIhAGbACh@AXCf@A^AVCl@Aj@AXAb@Af@A\\A`@Aj@CdAAt@Ad@A\\C`BChAAn@Cp@Az@AHAbAAH?@Ap@?FAVAZ?@Cb@A\\ALATCb@Eb@?FCXGd@EXEXGb@I\\I^Mf@GVOd@O`@M^O^Q^MXS\\KT]p@S^Q\\Yj@S^CFEJO\\MVO\\MZKZM^GREPK\\I^CNCLK`@In@Kr@C`@E^Cb@Cf@Cb@A`@A`@Cb@Ad@?^Ab@?JEhCCjB?@?d@AbAAbA?B?|A?fB?B?n@?`@@h@?`@?`@@fA@b@?b@?B@^@hAB~@?JBz@@f@@b@@T@j@B`@B`ABd@@d@DhAB`@HhB@b@Dl@Bv@?FF|@@f@?@Bb@Bf@@ZFdA@b@FfA@ZB`@Bn@DdAFdABz@@FD~@D|@Bz@@BB|@FpAD`A@DBn@ZvGD`A?H@L@Z@L?LBd@Bh@@b@@^?DB^@b@@bA@fA@`@?N?N@h@?l@?T?d@A`@?d@?h@Ab@?JAX?^Ad@Ah@C`AAf@AZCx@?HCpAE~BAd@Cd@Av@Av@EhBGnBA^Ab@C`@A`@Cd@C^Cd@C\\?BE^APAPGx@Gh@KfAE\\Ed@ABE\\E\\Gd@E\\G`@G^EVCHE\\GXAFIb@EVAFG^ShAG`@I`@G^G\\G\\G^Ib@Kh@G`@Ib@Ib@AFCJCPG`@I`@G^G^ANCNAFEXIf@AFE\\Gd@CTCNGd@K`AG`@Ed@E^C`@Gb@Eb@Ed@C`@C^Ed@C\\Cb@Eb@C`@GhACb@A^Ef@Ab@Cb@A`@A^Cd@Ab@Aj@AZ?FAh@Ah@Af@Ad@?f@?d@Aj@?b@?f@?p@?\\?H?b@?h@BlC?Z?l@@x@?X?L?b@@`@?b@?`@@b@?b@?d@@b@?`@?f@@`@?`@?f@@`A?f@A`@?b@AZAd@Ad@C`@Cb@Eb@E\\G`@G`@?BGXCLCRCHGTI`@IZM^ITOJA??BEJ]|@Qb@g@pA?@gApCMb@Qj@[fA[nA[bBCJO|@G`@ENGJCDCBCBE@G@C@C?I?ME?VKtCEnB?NSjDC\\Gn@ABKr@a@|CMt@Ib@UdA?@CJUz@Qj@KXCJA?O^U^CBGHILONOLIBIDSFg@LC?E@YFGBMBo@JI@m@J{@De@D]A[Gw@MaBYsAYa@IOCSGSGMGWOGGg@i@kAyAYa@A?m@w@i@c@YUECi@]s@SG?iAGgAFcAXE@m@b@_@n@Ud@M`@E\\CTAZ?JDbAB\\Fp@?BF^PtAF|@@bAARC|@CrA@f@B`@Dt@@L@V@N@DBZ?D?L@p@?DA`@?BANANCXOv@If@G`@Gd@ANATIpA?BEh@AJIh@CHQh@M\\CJAFAD?J?LBFFJFHJHHDPDP@RARKNKHSLa@Hm@Hc@HQDIPOPMRGFATCD?d@A^Cx@Bb@@R@N@b@@b@@X@H@D@JHJJHNRv@FT@DLRJHVNRPJB^LZNFD^Td@f@Z`@TXJPJN\\ZXVNJB@\\J@?ZBh@?XYR]VeANi@Pm@DKLc@@ARi@J]@?h@mALUFKNSBAVSFALCR?TD`@LVLLFHJHSHKFCL?LBD@PHn@`@Z^RPHJVXt@z@HJVb@@@Rl@Rl@@DNr@Dj@D^?V@t@@rA?X@v@?x@@j@"
            },
            "breaks": [
                {
                    "startTime": "2024-07-08T17:00:00Z",
                    "duration": "1800s"
                }
            ],
            "metrics": {
                "performedShipmentCount": 5,
                "travelDuration": "3029s",
                "waitDuration": "0s",
                "delayDuration": "0s",
                "breakDuration": "1800s",
                "visitDuration": "3000s",
                "totalDuration": "7829s",
                "travelDistanceMeters": 33007,
                "maxLoads": {
                    "weight": {
                        "amount": "5"
                    }
                }
            },
            "routeCosts": {
                "model.vehicles.cost_per_kilometer": 33.007
            },
            "routeTotalCost": 33.007
        }
    ],
    "metrics": {
        "aggregatedRouteMetrics": {
            "performedShipmentCount": 5,
            "travelDuration": "3029s",
            "waitDuration": "0s",
            "delayDuration": "0s",
            "breakDuration": "1800s",
            "visitDuration": "3000s",
            "totalDuration": "7829s",
            "travelDistanceMeters": 33007,
            "maxLoads": {
                "weight": {
                    "amount": "5"
                }
            }
        },
        "usedVehicleCount": 1,
        "earliestVehicleStartTime": "2024-07-08T16:00:00Z",
        "latestVehicleEndTime": "2024-07-08T18:10:29Z",
        "totalCost": 33.007,
        "costs": {
            "model.vehicles.cost_per_kilometer": 33.007
        }
    }
}

Information about the break(s) can be found in the route object in the response. In the breaks array, you'll find:

{
    "breaks": [
        {
            "startTime": "2024-07-08T17:00:00Z",
            "duration": "1800s"
        }
    ]
}

Structurally, breaks are a type of transition (time interval in between visits), so they are also included in the transitions array of each route object.

{
    "travelDuration": "581s",
    "travelDistanceMeters": 6408,
    "breakDuration": "1800s",
    "waitDuration": "0s",
    "totalDuration": "2381s",
    "startTime": "2024-07-08T16:57:46Z",
    "vehicleLoads": {
        "weight": {
            "amount": "2"
        }
    }
}

Sequencing breaks and visits

While a break is not technically a visit (it does not have a fixed location), it is often helpful to treat it as one when presenting routes to drivers.

Driver breaks shown on the Routific (left), Parcelizer (middle) and Sparqle (right) driver apps
Driver breaks shown in apps for Routific (left), Parcelizer (middle) and Sparqle (right)

To achieve this, we simply combine breaks and visits into a single list and sort them by their startTime. Here's how to do this for one route. First, create a new folder /route_printer, and save gmpro_driver_breaks_output.json to it. This file contains the response from an API call to the GMPRO /optimizeTours endpoint.

In your favorite text editor, create a new file printRoute.js. Add the code below and save the file in the same folder as gmpro_driver_breaks_output.json.

/*** printRoute.js ***/
const fs = require('fs').promises;

async function loadJsonFile(filePath) {
    try {
        const fileData = await fs.readFile(filePath, 'utf8');
        const jsonObject = JSON.parse(fileData);
        
        // Print visits and driver breaks for a single driver
        console.log("*** " + jsonObject.routes[0].vehicleLabel + " ***");
        const visitsAndBreaks = sortVisitsAndBreaks(jsonObject.routes[0].visits, jsonObject.routes[0].breaks);
        printVisitsAndBreaks(visitsAndBreaks);

        return jsonObject;
    } catch (error) {
        console.error('Error loading JSON file:', error);
        return null;
    }
}

function sortVisitsAndBreaks(visits, breaks) {

    const visitsAndBreaks = [...visits, ...breaks];
    return visitsAndBreaks.sort((a, b) => new Date(a.startTime) - new Date(b.startTime));

}

function printVisitsAndBreaks(arr) {
    for (const obj of arr) {
        if (obj.hasOwnProperty('shipmentLabel')) {
            console.log(`${obj.shipmentLabel} : ${obj.startTime}`);
        } else {
            console.log(`break : ${obj.startTime}`);
        }
    }
}

// Call the function with the file path
loadJsonFile('gmpro_driver_breaks_output.json');

The code above reads a JSON file gmpro_driver_breaks_output.json and converts its contents into a Javascript object.

/*** printRoute.js ***/
const fs = require('fs').promises;

const fileData = await fs.readFile(filePath, 'utf8');
const jsonObject = JSON.parse(fileData);

Next, we extract the visits and breaks array from the output JSON and use the ... spread operator to merge them into a single array. This array is then sorted by their startTime in ascending order.

/*** printRoute.js ***/
function sortVisitsAndBreaks(visits, breaks) {

    const visitsAndBreaks = [...visits, ...breaks];
    return visitsAndBreaks.sort((a, b) => new Date(a.startTime) - new Date(b.startTime));

}

Finally, the array is specially formatted and printed out to show both breaks and visits, together with their respective startTimes.

*** will-yvr ***
yvr789 : 2024-07-08T16:12:11Z
yvr123 : 2024-07-08T16:27:57Z
yvr987 : 2024-07-08T16:47:46Z
break : 2024-07-08T17:00:00Z
yvr654 : 2024-07-08T17:37:27Z
yvr321 : 2024-07-08T18:00:29Z

Scheduling a driver break at a specific location

If you need a driver to take his break at a specific location e.g. a truck rest stop or a gas station, you should instead a create a dummy shipment with a well defined time window and add a high penaltyCost (the cost of not delivering this shipment) to it and a much lower penaltyCost everywhere else. This will force the GMPRO solver to include the break in the final route solution.

{
    "deliveries": [
        {
            "arrivalLocation": {
                "latitude": 49.6312767,
                "longitude": -121.0725622
            },
            "duration": "1800s",
            "timeWindows": [
                {
                    "startTime": "2024-07-08T16:00:00Z",
                    "endTime": "2024-07-08T16:30:00Z"
                }
            ]
        }
    ],
    "penaltyCost": 9001,
    "label": "lunch-break@coquihalla-lakes-rest-area"
}

Have a driver break, have a Kit Kat

Driver breaks are crucial for two main reasons: they ensure drivers remain well-rested and help your fleet stay compliant with national regulations. After reading this post, you should know how to include driver breaks in a GMPRO API call and integrate them optimally into driver schedules. They can also be used to plan multi day routes with overnight stays, but that is a topic for a different blog post altogether.

👋 As always, if you have any questions or suggestions for me, please reach out or say hello on LinkedIn.