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

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

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

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

      {
        "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
            }
          ]
        }
      }
    

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

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

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

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

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

השירות מקודד את Polylines באמצעות קודק פוליגונים של מפות JS, שמייצג נתונים פוליגוניים בינאריים באמצעות תווי ASCII ניתנים להדפסה. אפשר להשתמש ב-Interactive Polyline Encoder Tool כדי להמחיש את הנתיבים שמחושבים על ידי Route Optimization. בדוגמה במדריך הזה, הערכים populatePolylines ו-populateTransitionPolylines מוגדרים כ-True, אבל במדריכים אחרים אפשר להגדיר את הערכים כ-FALSE כדי לצמצם את גודל התשובה.

לתיאור של פורמט הקידוד, ראו את המאמר פורמט אלגוריתם פוליגוני מקודד.

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

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 כדי שכלי האופטימיזציה יוכל למצוא מסלול אופטימלי (במקום למצוא כל מסלול אפשרי).