生产环境注意事项
为简单起见,本指南使用 REST 和 JSON。不过,对于生产环境,总体建议是使用 gRPC 和客户端库,以获得更好的性能。如需了解详情,请参阅 Route Optimization API 客户端库。
在发出第一个请求中发送请求后,优化器会返回 OptimizeToursResponse。此响应是供司机遵循的路线方案。本指南将介绍如何读取该文件。
路线规划摘要
对于狗狗日托场景,优化器会将伯恩山犬和吉娃娃分配给您的货车,并跳过德国牧羊犬。司机的路线规划如下:
- 上午 7:55 在日托中心开始轮班。
- 上午 8:20 在科伊特塔附近接伯恩山犬。
- 上午 8:58 在南日落游乐场公园接吉娃娃。
- 在上午 9:24 返回托儿所,等待上午 9:30 的送达时间窗口开启。
- 上午 9:30 送伯恩山犬。
- 上午 9:35 送吉娃娃。
- 上午 9:40 换班。
路线规划费用为 147.22 美元,其中包括因跳过德国牧羊犬而产生的 100 美元罚款。
如需检查结果,请将您的回答与完整示例回答进行比较。 您的结果可能会略有不同,因为行程时间和距离取决于当前道路数据。
完整示例响应
{ "routes": [ { "vehicleStartTime": "2024-02-13T15:55:04Z", "vehicleEndTime": "2024-02-13T17:40:00Z", "visits": [ { "isPickup": true, "startTime": "2024-02-13T16:20:00Z", "detour": "0s", "shipmentLabel": "Bernese mountain dog", "visitType": "PICKUP_SHIPMENT" }, { "shipmentIndex": 1, "isPickup": true, "startTime": "2024-02-13T16:58:12Z", "detour": "2600s", "shipmentLabel": "Chihuahua", "visitType": "PICKUP_SHIPMENT" }, { "startTime": "2024-02-13T17:30:00Z", "detour": "2297s", "shipmentLabel": "Bernese mountain dog", "visitType": "DELIVER_SHIPMENT" }, { "shipmentIndex": 1, "startTime": "2024-02-13T17:35:00Z", "detour": "656s", "shipmentLabel": "Chihuahua", "visitType": "DELIVER_SHIPMENT" } ], "transitions": [ { "travelDuration": "1496s", "travelDistanceMeters": 7082, "waitDuration": "0s", "totalDuration": "1496s", "startTime": "2024-02-13T15:55:04Z" }, { "travelDuration": "1992s", "travelDistanceMeters": 16589, "waitDuration": "0s", "totalDuration": "1992s", "startTime": "2024-02-13T16:25:00Z" }, { "travelDuration": "1252s", "travelDistanceMeters": 7860, "waitDuration": "356s", "totalDuration": "1608s", "startTime": "2024-02-13T17:03:12Z" }, { "travelDuration": "0s", "waitDuration": "0s", "totalDuration": "0s", "startTime": "2024-02-13T17:35:00Z" }, { "travelDuration": "0s", "waitDuration": "0s", "totalDuration": "0s", "startTime": "2024-02-13T17:40:00Z" } ], "metrics": { "performedShipmentCount": 2, "travelDuration": "4740s", "waitDuration": "356s", "delayDuration": "0s", "breakDuration": "0s", "visitDuration": "1200s", "totalDuration": "6296s", "travelDistanceMeters": 31531, "performedShipmentPenaltyCostSum": 2000 }, "routeCosts": { "model.vehicles.cost_per_hour": 47.22 }, "routeTotalCost": 47.22, "vehicleFullness": { "maxFullness": 0.3497777777777778, "activeSpan": 0.3497777777777778 } } ], "skippedShipments": [ { "index": 2, "label": "German shepherd", "penaltyCost": 100 } ], "metrics": { "aggregatedRouteMetrics": { "performedShipmentCount": 2, "travelDuration": "4740s", "waitDuration": "356s", "delayDuration": "0s", "breakDuration": "0s", "visitDuration": "1200s", "totalDuration": "6296s", "travelDistanceMeters": 31531, "performedShipmentPenaltyCostSum": 2000 }, "usedVehicleCount": 1, "earliestVehicleStartTime": "2024-02-13T15:55:04Z", "latestVehicleEndTime": "2024-02-13T17:40:00Z", "totalCost": 147.22, "costs": { "model.vehicles.cost_per_hour": 47.22, "model.shipments.penalty_cost": 100 } } }
响应结构
OptimizeToursResponse 包含两个主要组件:
由于优化器会跳过德国牧羊犬,因此回答中还会包含 skippedShipments。
以下各部分介绍了在响应中何处可以找到路线方案摘要的每个详细信息。
哪辆车跑哪条路线?
优化器会生成一个路线方案(routes 数组),该方案与请求中 vehicles 的顺序相对应。
vehicleIndex 字段根据车辆的从零开始的位置来标识每辆车,对于您的货车,该位置为 0。vehicleStartTime 和 vehicleEndTime 字段用于指示司机何时开始和结束轮班。
Route Optimization API 会返回采用世界协调时间 (UTC) 的时间戳(以 Z 后缀标记),而不管请求中的偏移量如何。如需获取旧金山时间,请减去 8 小时。对于您的货车,班次从上午 7:55 运行到上午 9:40,如以下代码示例所示。
{
"routes": [
{
// "vehicleIndex": 0, // Omitted in actual JSON response because it's 0
"vehicleStartTime": "2024-02-13T15:55:04Z",
"vehicleEndTime": "2024-02-13T17:40:00Z",
...
}
]
...
}
接载和下车顺序是怎样的?
在每条路线中,visits 数组按时间顺序包含分配车辆的每次接送和送货。
每次造访的 shipmentIndex 是运单在请求中的从零开始的位置:0 是伯恩山犬,1 是吉娃娃。shipmentLabel 字段会显示您为每批货物设置的标签。
如需区分接单和落客(或送货),您可以查看 visitType 或 isPickup 字段:
visitType:明确定义访问操作。- 自提:
visitType="PICKUP_SHIPMENT" - 送货:
visitType="DELIVER_SHIPMENT"
- 自提:
isPickup:一个布尔值辅助字段。- 自提:
isPickup=true - Delivery:
isPickup=false(在 JSON 中省略)
- 自提:
如需确定确切的事件序列以及每个地点的预计到达时间,请按顺序读取 visits 数组。下表根据 shipmentIndex、isPickup 或 visitType 以及 startTime 字段的顺序和值显示了路线规划操作。
| 序列 | shipmentIndex 值 |
isPickup 或 visitType 值 |
startTime 值(世界协调时间) |
操作 |
|---|---|---|---|---|
| 首次访问 | 0(在 JSON 中省略) | True 或 PICKUP_SHIPMENT |
2024-02-13T16:20:00Z | 上午 8:20 接伯恩山犬。 |
| 第二次访问 | 1 | True 或 PICKUP_SHIPMENT |
2024-02-13T16:58:12Z | 上午 8:58 接吉娃娃。 |
| 第三次访问 | 0(在 JSON 中省略) | False(省略)或 DELIVER_SHIPMENT |
2024-02-13T17:30:00Z | 上午 9:30 把伯恩山犬送到狗狗日托中心。 |
| 第四次访问 | 1 | False(省略)或 DELIVER_SHIPMENT |
2024-02-13T17:35:00Z | 上午 9:35 把吉娃娃送到狗狗日托中心。 |
以下代码示例展示了回答如何提供此序列:
{
"routes": [
{
"visits": [
{
// "shipmentIndex": 0, // Omitted in actual JSON response because it's 0
"isPickup": true,
"startTime": "2024-02-13T16:20:00Z",
"shipmentLabel": "Bernese mountain dog",
"visitType": "PICKUP_SHIPMENT"
},
{
"shipmentIndex": 1,
"isPickup": true,
"startTime": "2024-02-13T16:58:12Z",
"shipmentLabel": "Chihuahua",
"visitType": "PICKUP_SHIPMENT"
},
{
// "shipmentIndex": 0, // Omitted in actual JSON response because it's 0
// "isPickup": false, // Omitted in actual JSON response because it's false
"startTime": "2024-02-13T17:30:00Z",
"shipmentLabel": "Bernese mountain dog",
"visitType": "DELIVER_SHIPMENT"
},
{
"shipmentIndex": 1,
// "isPickup": false, // Omitted in actual JSON response because it's false
"startTime": "2024-02-13T17:35:00Z",
"shipmentLabel": "Chihuahua",
"visitType": "DELIVER_SHIPMENT"
}
]
}
]
}
每次造访还具有一个 visitRequestIndex,即优化器为运单选择的取件或送货选项的从零开始的位置。在此场景中,每只狗只有一个接送选项和一个送达选项,因此 visitRequestIndex 始终为 0,响应会将其省略。
两次访问之间会发生什么?
在每条路线中,transitions 数组会列出各次到访之间的行程详情。虽然 visits 和 transitions 是单独的列表,但它们在时间上交错,如下所示:
transitions[0](从起点到第一个到访地点的驾车路线)visits[0](首次访问)transitions[1](促成首次光顾到第二次光顾)visits[1](第二次访问)...visits[N-1](最后一次访问,其中 N 是总访问次数)transitions[N](从最后一次到访地点驾车前往终点)
您可以通过查看两个数组中的 startTime 字段来跟踪路线的时间轴。过渡的 startTime 是指司机开始行程的时间,而访问的 startTime 是指司机开始接送的时间。
每个过渡都会显示地点之间的预计驾车时间和距离。下表将 transitions 和 visits 字段映射到狗狗日托场景的按时间顺序排列的路线操作。
| 序列 | 字段名称 | startTime 值(世界协调时间) |
详细信息 | 操作 |
|---|---|---|---|---|
| 首次转换 | transitions[0] |
2024-02-13T15:55:04Z |
travelDuration:1496stravelDistanceMeters:7082 |
从日托中心开车 7 公里(25 分钟)去接伯恩山犬。班次于上午 7:55 开始。 |
| 首次访问 | visits[0] |
2024-02-13T16:20:00Z |
shipmentIndex:0(在 JSON 中省略)isPickup:True |
上午 8:20 开始接伯恩山犬。访问用时 5 分钟。 |
| 第二次转换 | transitions[1] |
2024-02-13T16:25:00Z |
travelDuration:1992stravelDistanceMeters:16589 |
从科伊特塔出发,驾车 17 公里,用时 33 分钟,于上午 8:25 开始接吉娃娃。 |
| 第二次访问 | visits[1] |
2024-02-13T16:58:12Z |
shipmentIndex:1isPickup:True |
上午 8:58 开始接吉娃娃。访问需要 5 分钟。 |
| 第三次转换 | transitions[2] |
2024-02-13T17:03:12Z |
travelDuration:1252stravelDistanceMeters:7860waitDuration:356s |
上午 9:03 从 South Sunset Playground 出发,行驶 8 公里,用时 21 分钟,返回日托中心。上午 9:24 到达,等待 6 分钟,直到下车时间段开始。 |
| 第三次访问 | visits[2] |
2024-02-13T17:30:00Z |
shipmentIndex:0(在 JSON 中省略)isPickup:False(在 JSON 中省略) |
从上午 9:30 开始,将伯恩山犬送到狗狗日托中心。预计用时 5 分钟。 |
| 第四次转换 | transitions[3] |
2024-02-13T17:35:00Z |
travelDuration:0stravelDistanceMeters:0(在 JSON 中省略) |
没有在开车。准备在上午 9:35 将吉娃娃送到宠物托管中心。 |
| 第四次访问 | visits[3] |
2024-02-13T17:35:00Z |
shipmentIndex:1isPickup:False(在 JSON 中省略) |
从上午 9:35 开始,将吉娃娃送到狗狗日托中心。访问需要 5 分钟。 |
| 第五次转换 | transitions[4] |
2024-02-13T17:40:00Z |
travelDuration:0stravelDistanceMeters:0(在 JSON 中省略) |
没有在开车。司机在上午 9:40 结束轮班。 |
以下代码示例展示了回答如何为 transitions[0] 和 transitions[1] 提供此序列:
{
"routes": [
{
"transitions": [
{
"travelDuration": "1496s",
"travelDistanceMeters": 7082,
"waitDuration": "0s",
"totalDuration": "1496s",
"startTime": "2024-02-13T15:55:04Z"
},
{
"travelDuration": "1992s",
"travelDistanceMeters": 16589,
"waitDuration": "0s",
"totalDuration": "1992s",
"startTime": "2024-02-13T16:25:00Z"
},
...
]
}
]
}
这条路线的费用是多少?
您可以查看响应中的多个字段,具体取决于您需要特定车辆的费用还是整个车队的总费用。
下表列出了您可以在响应中找到费用信息的所有方式:
| 父级 | 参数 | 范围 | 说明 |
|---|---|---|---|
ShipmentRoute |
routeCosts |
按路由 | 该特定路线的具体费用构成明细(例如每小时费用)。 |
routeTotalCost |
按路由 | 为该特定路线计算的总费用。 | |
Metrics |
totalCost |
总计(所有路线) | 解决方案中所有路线的总费用。 |
costs |
总计(所有路线) | 解决方案中所有路线的汇总费用组成部分的明细。 |
routeCosts 和 costs 中指定的费用反映了您在请求中指定的费用限制。如需了解详情,请参阅费用模型关键概念文档。
对于此狗狗日托场景,由于您跳过了一次配送,因此每条路线的费用和汇总总费用会有所不同。路线费用仅包含与该特定路线相关的费用(司机工资),而汇总总费用还包含跳过配送的惩罚。以下代码示例展示了如何在响应中找到这些值:
{
"routes": [
{
"vehicleStartTime": "2024-02-13T15:55:04Z",
"vehicleEndTime": "2024-02-13T17:40:00Z",
...
"routeCosts": {
"model.vehicles.cost_per_hour": 47.22
},
"routeTotalCost": 47.22
}
],
"metrics": {
"totalCost": 147.22,
"costs": {
"model.shipments.penalty_cost": 100,
"model.vehicles.cost_per_hour": 47.22
},
...
}
}
根据路线规划,司机在上午 7:55 至 9:40 期间驾驶货车,大约 1 小时 45 分钟(6,296 秒)。按照您设定的每小时 27 美元的费率,路线费用为 47.22 美元。由于优化器跳过了德国牧羊犬运输,因此它会增加 100 美元的惩罚费用,使总费用达到 147.22 美元。
为什么跳过了某个配送?
优化器会在 skippedShipments 数组中列出其未执行的所有配送。跳过发货的原因取决于您是否设置了 penaltyCost:
- 强制性配送(无
penaltyCost):优化工具绝不会仅仅因为这些配送的执行成本高昂而跳过它们。只有在无法将配送任务纳入任何路线时(例如,由于其时间窗口与其他配送任务冲突),系统才会跳过该配送任务。 - 可选配送(带有
penaltyCost):如果执行这些配送的费用高于罚金,优化器也可以跳过这些配送。您可以设置不同的惩罚费用,以优先处理某些配送。
在此场景中,德国牧羊犬的接送时间段与伯恩山犬的时间段冲突。科伊特塔和兰兹角相距 25 分钟路程,因此司机无法在 20 分钟的时间范围内完成这两次接单。优化器必须跳过其中一个。由于德国牧羊犬的惩罚费用较低(100 美元,而普通客户为 1,000 美元),优化器会跳过它并产生 100 美元的惩罚。
以下代码示例展示了响应中的 skippedShipments 对象:
{
...
"skippedShipments": [
{
"index": 2,
"label": "German shepherd",
"penaltyCost": 100
}
],
...
}
响应中的 skippedShipments 对象包含以下字段:
index:请求中运单的从零开始的索引(第三个运单为 2)。label:您在请求中指定的标签(“德国牧羊犬”)。penaltyCost:您在请求中指定的惩罚费用,API 会将其添加到总费用中。
为防止优化器跳过运单,请放宽导致冲突的限制。例如,您可以要求德国牧羊犬的主人提供更灵活的接送时间范围,然后在请求中更新该接送的 timeWindows。
示例中的响应字段
下表介绍了示例场景的 OptimizeToursResponse 中返回的所有响应字段。虽然您无需使用每个字段来了解路线规划,但您可以分析这些字段,以确定如何调整 OptimizeToursRequest 来满足您的业务需求。如需了解详情,请参阅路线方案生命周期。
您可以按父级或文本搜索过滤内容。
| 父级 | 参数 | 物业类型 | 说明 |
|---|---|---|---|
OptimizeToursResponse |
routes |
对象数组 (ShipmentRoute) |
其中包含为每辆车计算的路线列表(每辆车一条路线)。对于此狗狗日托场景,此字段包含单辆货车的详细路线规划。 |
metrics |
对象 (Metrics) |
其中包含解决方案的总体指标,这些指标是针对所有路线汇总的。对于这个狗狗日托场景,这是当天的费用和运营时长汇总。 | |
skippedShipments |
对象数组 (SkippedShipment) |
其中包含解决方案中跳过的配送列表。 在这种情况下,第三只狗(德国牧羊犬)会被跳过。 | |
ShipmentRoute |
vehicleStartTime |
时间戳 | 这是车辆开始其路线的时间。对于此狗狗日托场景,司机于上午 7:55 开始驾驶货车。 |
vehicleEndTime |
时间戳 | 这是车辆完成路线的时间。对于此狗狗日托场景,司机在上午 9:40 完成了厢式货车的驾驶。 | |
visits |
对象数组 (Visit) |
这是路线上的取件和送件的有序列表。对于此宠物狗日托场景,这是接送两只宠物狗的顺序。 | |
transitions |
对象数组 (Transition) |
这是距离以及两次到访之间的行程时间和等待时间的有序列表。对于此狗狗日托场景,此信息详细说明了科伊特塔、南日落游乐场公园和日托中心之间的驾车距离和时间。 | |
metrics |
对象 (AggregatedMetrics) |
这些是此单一路线特有的指标。对于这个狗狗日托场景,这些是这辆特定货车的统计信息。这些指标基于您在请求中指定的费用。如需了解详情,请参阅费用模型关键概念文档。 | |
routeCosts |
对象(映射) | 这是此路线的具体费用明细。对于此狗狗日托场景,这显示了根据司机每小时 27 美元的工资计算得出的费用。 | |
routeTotalCost |
数字 | 这是此特定路线的总费用。对于这个狗狗日托场景,这辆货车路线的总费用为 47.22。 |
|
vehicleFullness |
对象 (VehicleFullness) |
此指标细分了车辆接近其运营限制(例如时间、距离和负载)的程度。对于此狗狗日托场景,只有班次开始和结束时间会限制司机,因此优化器会将 vehicleFullness 计算为实际路线时长与司机班次可用时间上限的比值。 |
|
Visit |
shipmentIndex |
整数 | 这是原始请求中运单的索引(从零开始)。对于此狗狗日托场景,0 表示伯恩山犬,1 表示吉娃娃。 |
isPickup |
布尔值 | 如果此到访是取货,则此字段为 true。对于此狗狗日托场景,在接狗狗时,此值为 true;在将狗狗送到日托中心时,此值为 false(或缺失)。 | |
startTime |
时间戳 | 这是预计的到访开始时间。对于这个狗狗日托场景,这就是司机敲门的时候。 | |
detour |
时长 | 这是优化工具为此特定配送行程增加的额外时间。绕行是指实际路线规划与理想直接行程之间的差异,前提是司机没有其他要完成的配送或任务。 对于此狗狗日托场景,第一次拜访(接伯恩山犬)的绕行距离为 0,因为司机直接从日托中心前往此接送点。对于吉娃娃接送,绕行时间为 43 分钟,因为司机先接伯恩山犬(并花费 5 分钟装载)。 |
|
shipmentLabel |
字符串 | 这是每个配送请求中的标签。对于此狗狗日托场景,标签为“吉娃娃”或“伯恩山犬”。 | |
visitType |
字符串(枚举) | 这表示经停点的类型,可以是 PICKUP_SHIPMENT(表示取货)或 DELIVER_SHIPMENT(表示送货)。对于此场景,该值为 PICKUP_SHIPMENT 或 DELIVER_SHIPMENT。 |
|
Transition |
travelDuration |
时长 | 这是在两次访问之间的过渡期间花费的行程时间。例如,在这个狗狗日托场景中,司机大约需要 25 分钟从日托中心开车到科伊特塔,去接伯恩山犬。 |
travelDistanceMeters |
整数 | 这是在此转换期间移动的距离(以米为单位)。例如,在此狗狗日托场景中,司机从日托中心行驶 7 公里到科伊特塔,接上伯恩山犬。 | |
waitDuration |
时长 | 这是在下次取货或送货之前等待的时间。 对于此狗狗日托场景,第三次转换的等待时间为 6 分钟(356 秒),因为司机在 9:30 AM 狗狗日托中心开门之前到达。对于其他转换,此值为 0。 | |
totalDuration |
时长 | 这是过渡的总时长,包括驾驶和等待时间。例如,司机在第一次过渡上花费 25 分钟(25 分钟驾驶,0 分钟等待),在第三次过渡(返回托儿所)上花费 27 分钟(21 分钟驾驶,6 分钟等待)。 | |
startTime |
时间戳 | 这是行程开始的时间。它与上一次造访的完成时间(对于第一次过渡,则为车辆的开始时间)一致。对于狗狗日托场景,第一次转换从上午 7:55 开始,此时司机离开日托中心;第二次转换从上午 8:25 开始,此时司机离开科伊特塔。 | |
Metrics |
aggregatedRouteMetrics |
对象 (AggregatedMetrics) |
这是所有路线的指标总和。对于这个狗狗日托场景,这些是您一辆货车的总指标。 |
usedVehicleCount |
整数 | 这是解决方案中使用的车辆数量。对于此狗狗日托场景,您只需使用一辆货车。 | |
earliestVehicleStartTime |
时间戳 | 这是所有车辆中最早的开始时间。对于此狗狗日托场景,货车的路线从上午 7:55 开始。 | |
latestVehicleEndTime |
时间戳 | 这是所有车辆中最晚的结束时间。对于此狗狗日托场景,货车的路线于上午 9:40 结束。 | |
totalCost |
数字 | 这是解决方案的总费用。对于此狗狗日托场景,整个路线规划当天的费用为 147.22(包括 47.22 司机工资和 100 跳过配送惩罚)。 |
|
costs |
对象(映射) | 这是按类别划分的总费用明细。对于此狗狗日托场景,优化器根据每小时费用和跳过德国牧羊犬的惩罚费用计算了路线费用。 | |
AggregatedMetrics |
performedShipmentCount |
整数 | 这是成功规划路线的配送数量。在此狗狗日托场景中,司机运送两只狗狗。 |
travelDuration |
时长 | 这是总行程时间。对于此狗狗日托场景,司机需要行驶 79 分钟。 | |
waitDuration |
时长 | 这是总等待时间。对于此狗狗日托场景,司机在日托中心空转 6 分钟(356 秒),等待下车时间窗口打开。 | |
delayDuration |
时长 | 这是您使用过渡属性定义的额外时长,例如预留的停车时间。对于此狗狗日托场景,您未在请求中定义任何过渡属性,因此该值为零。 | |
breakDuration |
时长 | 这是休息的总时长。对于这个狗狗日托场景,没有休息时间。 | |
visitDuration |
时长 | 这是在访问期间执行服务所花费的总时间。对于此狗狗日托场景,此值为 20 分钟(每次访问 5 分钟,共 4 次访问)。 | |
totalDuration |
时长 | 这是所有时长组成部分的总和。对于此狗狗日托场景,总时长为 105 分钟(6296 秒)。 | |
travelDistanceMeters |
整数 | 这是总行程距离(以米为单位)。对于此狗狗日托场景,司机行驶的总距离为 31.5 公里。 | |
performedShipmentPenaltyCostSum |
双精度 | 这是所有已执行配送的惩罚费用之和。对于此场景,该值为 2000,因为司机接载了伯恩山犬和吉娃娃(每只的惩罚费用为 1000)。 |
|
VehicleFullness |
maxFullness |
数字 | 这是所有 VehicleFullness 字段中的最高利用率。对于此狗狗日托场景,该值为 0.35,因为司机在整个早班(您在请求中定义为 5 小时)中仅操作货车 105 分钟。 |
activeSpan |
数字 | 这是司机的总班次时长与路线规划的营业时间的比率。数值较低表示司机在工作日内驾驶车辆的时间较短。对于此狗狗日托场景,该值为 0.35,因为司机在 5 小时轮班期间驾驶货车 105 分钟。 | |
SkippedShipment |
index |
整数 | 这是原始请求中运单的索引(从零开始)。 在此场景中,2 代表德国牧羊犬。 |
label |
字符串 | 这是您在请求中为运单指定的标签。 | |
penaltyCost |
数字 | 这是优化工具跳过此配送时您产生的惩罚费用。它与您在请求中指定的 penaltyCost 匹配。 |