אופטימיזציה בסיסית של הזמנות ביניים לאיסוף ולמשלוחים

התרחיש הזה מאפשר אופטימיזציה של סדר העצירות שמוקצות לרכב באמצעות פרמטרים פשוטים של עלות. זהו המצב הפשוט ביותר לביצוע אופטימיזציה של מסלולים, והוא מבטיח שכל העצירות יגיעו למסגרת הזמן שצוינה.

הדוגמה הבאה ממחישה תרחיש בסיסי שכולל רכב אחד ושלוש משלוחים, שכולם מגיעים ממיקום יחיד שנקרא תחנת טעינה.

הצגת בקשה לדוגמה

      {
        "populatePolylines": true,
        "populateTransitionPolylines": true,
        "model": {
          "globalStartTime": "2023-01-13T16:00:00-08:00",
          "globalEndTime": "2023-01-14T16:00:00-08:00",
          "shipments": [
            {
              "deliveries": [
                {
                  "arrivalLocation": {
                    "latitude": 37.789456,
                    "longitude": -122.390192
                  },
                  "duration": "250s"
                }
              ],
              "pickups": [
                {
                  "arrivalLocation": {
                    "latitude": 37.794465,
                    "longitude": -122.394839
                  },
                  "duration": "150s"
                }
              ]
            },
            {
              "deliveries": [
                {
                  "arrivalLocation": {
                    "latitude": 37.789116,
                    "longitude": -122.395080
                  },
                  "duration": "250s"
                }
              ],
              "pickups": [
                {
                  "arrivalLocation": {
                    "latitude": 37.794465,
                    "longitude": -122.394839
                  },
                  "duration": "150s"
                }
              ]
            },
            {
              "deliveries": [
                {
                  "arrivalLocation": {
                    "latitude": 37.795242,
                    "longitude": -122.399347
                  },
                  "duration": "250s"
                }
              ],
              "pickups": [
                {
                  "arrivalLocation": {
                    "latitude": 37.794465,
                    "longitude": -122.394839
                  },
                  "duration": "150s"
                }
              ]
            }
          ],
          "vehicles": [
            {
              "endLocation": {
                "latitude": 37.794465,
                "longitude": -122.394839
              },
              "startLocation": {
                "latitude": 37.794465,
                "longitude": -122.394839
              },
              "costPerKilometer": 10.0,
              "costPerHour": 40.0
            }
          ]
        }
      }
    

שדות הבקשה לאופטימיזציה של ניתוב

כמו שצוין בסקירה הכללית, הנכסים החשובים ביותר של הבקשה לאופטימיזציה של המסלול הם vehicles ו-shipments.

בנוסף לרכב ולמשלוחים, הבקשה כוללת את השדות הבאים:

קווים פוליגוניים

populatePolylines ו-populateTransitionPolylines מציינות אם האופטימיזציה של המסלולים צריכה להחזיר קווים פוליגוניים.

השירות מקודד קווים פוליגוניים באמצעות קודק ה-Polyline של מפות Google JS, שמייצג נתוני פוליקוי פוליגונים בינאריים באמצעות תווי ASCII שניתנים להדפסה. תוכלו להשתמש בכלי האינטראקטיבי למקודד Polyline Encoder כדי להציג באופן חזותי את הנתיבים שמחושבים על ידי האופטימיזציה של מסלולים. בדוגמה במדריך הזה מוגדר הערך של populatePolylines ו-populateTransitionPolylines כ-true, אבל מדריכים אחרים מגדירים אותם כ-false כדי להקטין את גודל התשובה.

לתיאור של פורמט הקידוד ראו Encoded Polyline Algorithm Format.

מגבלות זמן גלובליות

model.globalStartTime ו-model.globalEndTime מוגדרים לפרק זמן שרירותי של 24 שעות. כך קל יותר לפרש את חותמות הזמן של הפלט.

ביקור במיקומים

בבקשה לדוגמה נעשה שימוש רק ב-model.shipments[].pickups[].arrivalLocation וב-model.shipments[].deliveries[].arrivalLocation. יש גם נכס departureLocation במצבים שבהם הרכב יוצא מנקודה אחרת מזו שבה הוא מגיע, כמו מתחם חניה עם כניסה בצד אחד של הבניין ויציאה בצד אחר. במדריך הזה ובמדריכים הבאים, נקודות ההגעה והיציאה הן זהות.

שעות ההגעה והיציאה מwaypoint קיימות גם כחלופה ל-latLng. בשדות Waypoint אפשר להשתמש במזהי מקומות של Google כחלופה ל-LatLng, ואפשר גם לציין כותרות של רכבים. פרטים נוספים זמינים במשאבי העזרה (REST, gRPC).

המגבלות בדוגמה

תרחיש זה מגביל את האופטימיזציה במספר דרכים:

  1. צריך להשלים את כל הפעילות בין שעת ההתחלה הגלובלית לשעת הסיום. בתרחיש הזה, זמני ההתחלה והסיום מוגבלים מאוד עקב הקרבה בין המשלוח לבין חלון הזמן הגלובלי.
  2. צריך להשלים את כל המשלוחים. זאת התנהגות ברירת המחדל כשלא צוינו עלויות עונשין ב-shipments.
  3. costPerKilometer ו-costPerHour הוגדרו ברכב.

העלויות מפורטות בפרמטרים של מודל עלות.

מאפייני התגובה לאופטימיזציה של המסלול

הצגת תשובה לבקשה לדוגמה

    {
      "routes": [
        {
          "vehicleStartTime": "2023-01-14T00:00:00Z",
          "vehicleEndTime": "2023-01-14T00:36:41Z",
          "visits": [
            {
              "shipmentIndex": 2,
              "isPickup": true,
              "startTime": "2023-01-14T00:00:00Z",
              "detour": "0s"
            },
            {
              "shipmentIndex": 1,
              "isPickup": true,
              "startTime": "2023-01-14T00:02:30Z",
              "detour": "150s"
            },
            {
              "isPickup": true,
              "startTime": "2023-01-14T00:05:00Z",
              "detour": "300s"
            },
            {
              "startTime": "2023-01-14T00:11:25Z",
              "detour": "0s"
            },
            {
              "shipmentIndex": 1,
              "startTime": "2023-01-14T00:19:29Z",
              "detour": "503s"
            },
            {
              "shipmentIndex": 2,
              "startTime": "2023-01-14T00:29:02Z",
              "detour": "1324s"
            }
          ],
          "transitions": [
            {
              "travelDuration": "0s",
              "waitDuration": "0s",
              "totalDuration": "0s",
              "startTime": "2023-01-14T00:00:00Z",
              "routePolyline": {}
            },
            {
              "travelDuration": "0s",
              "waitDuration": "0s",
              "totalDuration": "0s",
              "startTime": "2023-01-14T00:02:30Z",
              "routePolyline": {}
            },
            {
              "travelDuration": "0s",
              "waitDuration": "0s",
              "totalDuration": "0s",
              "startTime": "2023-01-14T00:05:00Z",
              "routePolyline": {}
            },
            {
              "travelDuration": "235s",
              "travelDistanceMeters": 795,
              "waitDuration": "0s",
              "totalDuration": "235s",
              "startTime": "2023-01-14T00:07:30Z",
              "routePolyline": {
                "points": "kvteFtfjVAA?C?C@C?A?C@AFMj@s@JKb@k@Zc@LSjA}ARWDGdAxAdAvAXa@@k@AsA\\c@FKp@_A\\c@Ze@fA{ALSFGd@o@rAgBB{BZc@"
              }
            },
            {
              "travelDuration": "234s",
              "travelDistanceMeters": 793,
              "waitDuration": "0s",
              "totalDuration": "234s",
              "startTime": "2023-01-14T00:15:35Z",
              "routePolyline": {
                "points": "cwseFti_jVRWj@w@x@eAHLNRHJbApAHLX\\V^?@hA~AT\\PVFFDHDFJNp@~@NRLNNTFFUZIJY^Y^g@p@[`@KP{@fAEFSXe@l@c@h@WZY\\?BELk@v@MNa@l@"
              }
            },
            {
              "travelDuration": "323s",
              "travelDistanceMeters": 1204,
              "waitDuration": "0s",
              "totalDuration": "323s",
              "startTime": "2023-01-14T00:23:39Z",
              "routePolyline": {
                "points": "cuseFhjVSTY`@Yb@GHEDIJEF]f@IJi@r@oAbBeCfDKLaApAKNQVIPKPCDQJIBIBM@iAJeALqBVC@C?A?QBYDI@C?_@Dc@FO@a@FDp@HfAHvABVDl@Dj@PpCQDiALsALAQASKwAOgBEe@COCYEa@Es@Eg@"
              }
            },
            {
              "travelDuration": "209s",
              "travelDistanceMeters": 665,
              "waitDuration": "0s",
              "totalDuration": "209s",
              "startTime": "2023-01-14T00:33:12Z",
              "routePolyline": {
                "points": "{zteFxbajV?CAYEc@AMC_@AOAK?E?CCWAOAKCe@CY?WScDEm@d@EFA\\ENCB?XEVC^E`@EhBUVCNEB?@?\\Er@IMUe@k@k@w@AAMQa@i@SWQWMQi@u@AC?A"
              }
            }
          ],
          "routePolyline": {
            "points": "kvteFtfjVAA?C?C@C?A?C@AFMj@s@JKb@k@Zc@LSjA}ARWDGdAxAdAvAXa@@k@AsA\\c@FKp@_A\\c@Ze@fA{ALSFGd@o@rAgBB{BZc@RWj@w@x@eAHLNRHJbApAHLX\\V^?@hA~AT\\PVFFDHDFJNp@~@NRLNNTFFUZIJY^Y^g@p@[@KP{@fAEFSXe@l@c@h@WZY\\?BELk@v@MNa@l@STY@Yb@GHEDIJEF]f@IJi@r@oAbBeCfDKLaApAKNQVIPKPCDQJIBIBM@iAJeALqBVC@C?A?QBYDI@C?_@Dc@FO@a@FDp@HfAHvABVDl@Dj@PpCQDiALsALAQASKwAOgBEe@COCYEa@Es@Eg@?CAYEc@AMC_@AOAK?E?CCWAOAKCe@CY?WScDEm@d@EFA\\ENCB?XEVC^E`@EhBUVCNEB?@?\\Er@IMUe@k@k@w@AAMQa@i@SWQWMQi@u@AC?A"
          },
          "metrics": {
            "performedShipmentCount": 3,
            "travelDuration": "1001s",
            "waitDuration": "0s",
            "delayDuration": "0s",
            "breakDuration": "0s",
            "visitDuration": "1200s",
            "totalDuration": "2201s",
            "travelDistanceMeters": 3457
          },
          "travelSteps": [
            {
              "duration": "0s",
              "routePolyline": {}
            },
            {
              "duration": "0s",
              "routePolyline": {}
            },
            {
              "duration": "0s",
              "routePolyline": {}
            },
            {
              "duration": "227s",
              "distanceMeters": 794,
              "routePolyline": {
                "points": "kvteFtfjVAA?C?C@C?A?C@AFMj@s@JKb@k@Zc@LSjA}ARWDGdAxAdAvAXa@@k@AsA\\c@FKp@_A\\c@Ze@fA{ALSFGd@o@rAgBB{BZc@"
              }
            },
            {
              "duration": "233s",
              "distanceMeters": 791,
              "routePolyline": {
                "points": "cwseFti_jVRWj@w@x@eAHLNRHJbApAHLX\\V^?@hA~AT\\PVFFDHDFJNp@~@NRLNNTFFUZIJY^Y^g@p@[`@KP{@fAEFSXe@l@c@h@WZY\\?BELk@v@MNa@l@"
              }
            },
            {
              "duration": "322s",
              "distanceMeters": 1205,
              "routePolyline": {
                "points": "cuseFhjVSTY`@Yb@GHEDIJEF]f@IJi@r@oAbBeCfDKLaApAKNQVIPKPCDQJIBIBM@iAJeALqBVC@C?A?QBYDI@C?_@Dc@FO@a@FDp@HfAHvABVDl@Dj@PpCQDiALsALAQASKwAOgBEe@COCYEa@Es@Eg@"
              }
            },
            {
              "duration": "208s",
              "distanceMeters": 666,
              "routePolyline": {
                "points": "{zteFxbajV?CAYEc@AMC_@AOAK?E?CCWAOAKCe@CY?WScDEm@d@EFA\\ENCB?XEVC^E`@EhBUVCNEB?@?\\Er@IMUe@k@k@w@AAMQa@i@SWQWMQi@u@AC?A"
              }
            }
          ],
          "vehicleDetour": "2201s",
          "routeCosts": {
            "model.vehicles.cost_per_hour": 24.455555555555556,
            "model.vehicles.cost_per_kilometer": 34.57
          },
          "routeTotalCost": 59.025555555555556
        }
      ],
      "totalCost": 59.025555555555556,
      "metrics": {
        "aggregatedRouteMetrics": {
          "performedShipmentCount": 3,
          "travelDuration": "1001s",
          "waitDuration": "0s",
          "delayDuration": "0s",
          "breakDuration": "0s",
          "visitDuration": "1200s",
          "totalDuration": "2201s",
          "travelDistanceMeters": 3457
        },
        "usedVehicleCount": 1,
        "earliestVehicleStartTime": "2023-01-14T00:00:00Z",
        "latestVehicleEndTime": "2023-01-14T00:36:41Z",
        "totalCost": 59.025555555555556,
        "costs": {
          "model.vehicles.cost_per_kilometer": 34.57,
          "model.vehicles.cost_per_hour": 24.455555555555556
        }
      }
    }
    

התגובה לאופטימיזציה של המסלול כוללת שדה routes ברמה העליונה שמייצג את המסלולים המוצעים, עם מסלול אחד לכל רכב. הבקשה לדוגמה במדריך הזה מציינת רק רכב אחד, ולכן routes כוללת הודעת ShipmentRoute אחת.

ShipmentRoute מלונות

שני המאפיינים החשובים ביותר לסוג ההודעה ShipmentRoute הם visits ו-transitions.

כל Visit מייצג השלמת איסוף או משלוח מאחד מהשדות VisitRequest של הודעת הבקשה. ביקור מוקצית בפועל לעבודה על ידי רכב במקום כלשהו ובזמן כלשהו.

כל Transition מייצג את הרכב שעובר ממיקום אחד לאחר. יכולים להיות מעברים בין שתי נקודות התחלה של הרכב, מיקום ביקור ונקודת הקצה של הרכב.

כדי לשחזר את המסלול המלא של הרכב, צריך לשלב את visits ואת transitions של ShipmentRoute. שילוב השדות להתקדמות של הפעילות בכלי רכב נראה כך:

request.vehicles[0].startLocation -> transitions[0] -> visits[0] ->
transitions[1] -> visits[1] -> transitions[2] -> ... -> visits[3] ->
transitions[4] -> request.vehicles[0].endLocation

ב-ShipmentRoute תמיד יש יותר transitions מ-visits, כי הרכב צריך לנסוע מהמיקום ההתחלתי שלו לביקור הראשון בתחילת המסלול ומהביקור האחרון שלו עד למיקום הסיום בסוף המסלול. אם ברכב אין מיקום התחלה או נקודת סיום, עדיין יופיע מיקום אחד יותר transitions מאשר visits, כי המיקום של הביקור הראשון או האחרון משמש כמיקום ההתחלה או הסיום של הרכב, בהתאמה.

בדוגמה הזו, בשלושת הביקורים הראשונים באיסוף עצמי יש מעברים ביניהם, עם אפס מרחק ומשך זמן, כי לכל שלוש האיסוף יש את אותו מיקום בבקשה.

פרטים נוספים זמינים במשאבי העזרה של ShipmentRoute (REST, gRPC).

אופטימיזציה פשוטה של סדר ציון הדרך

כפי שהדוגמה הזו ממחישה, המודלים של אופטימיזציה של מסלולים מתבססים על ביקורים כמאפיינים של משלוחים, ואין להם מושג של ציוני דרך או עצירות כישות עצמאית. עם זאת, אפשר לייצג תחנות או נקודות ציון כמשלוחים עם VisitRequest אחד בדיוק כאיסוף או כמשלוח. עדיין צריך להקצות לרכב costPerHour או costPerKilometer כדי שכלי האופטימיזציה יוכל למצוא מסלול אופטימלי (בניגוד לחיפוש מסלול אפשרי).