Aller au contenu
Tous les exemplesItinéraires

Planifier des livraisons du dernier kilomètre

Un entrepôt dans le sud-est de Calgary, deux fourgonnettes, douze colis. Certains clients veulent une livraison le matin (de 9 h à 12 h), d'autres l'après-midi (de 13 h à 17 h), et les autres acceptent n'importe quelle heure. Chaque colis a une taille en parcels sous loadDemands, et chaque fourgonnette en transporte au plus 20 : aucune ne peut tout prendre.

Ce qu'il faut lire dans la réponse :

  • Quelle fourgonnette a reçu quels colis, et dans quel ordre : routes[].visits[], par shipmentLabel.
  • visits[].startTime est l'heure où le chauffeur commence à remettre le colis, dans sa fenêtre.
  • transitions[].waitDuration est le temps qu'une fourgonnette passe en avance à un arrêt, à attendre l'ouverture de la fenêtre.
  • metrics.totalCost vaut costPerHour fois la journée de chaque fourgonnette, plus costPerKilometer fois sa distance. Le solveur le réduit au minimum, d'où des tournées compactes et des journées courtes.

L'envoyer

Enregistrez le corps ci-dessous dans last-mile-deliveries.json et envoyez-le à /optimize. Donnez à UNMAP_API_KEY la valeur de votre clé d'API.

curl -X POST "https://api.unmap.dev/optimize" \
  -H "X-API-Key: $UNMAP_API_KEY" \
  -H "Content-Type: application/json" \
  --data @last-mile-deliveries.json

Le corps utilise des trajets en ligne droite à vitesse fixe (useGeodesicDistances avec geodesicMetersPerSecond) et n'a donc pas besoin du réseau routier. Supprimez ces deux champs pour planifier plutôt avec les temps de conduite du réseau routier canadien. Les heures sont en UTC : 14:00:00Z est 8 h à Calgary en octobre. "timeout": "10s" est la résolution la plus longue du forfait Hobby, et la requête reste dans les limites de Hobby (25 envois, 5 véhicules). Le solveur peut renvoyer un ordre de visite différent d'une exécution à l'autre.

Ce corps est un fichier de la suite de conformité d'Unmap, qui le valide et vérifie que les réponses du solveur sont réalisables. Après l'avoir modifié, envoyez-le avec "solvingMode": "VALIDATE_ONLY" pour vérifier ses champs, puis résolvez-le pour vérifier que vos contraintes tiennent ensemble.

{
  "useGeodesicDistances": true,
  "geodesicMetersPerSecond": 8,
  "timeout": "10s",
  "model": {
    "globalStartTime": "2026-10-13T14:00:00Z",
    "globalEndTime": "2026-10-14T02:00:00Z",
    "shipments": [
      {
        "label": "parcel-101",
        "deliveries": [
          {
            "arrivalLocation": { "latitude": 51.039, "longitude": -114.079 },
            "duration": "300s",
            "timeWindows": [{ "startTime": "2026-10-13T15:00:00Z", "endTime": "2026-10-13T18:00:00Z" }]
          }
        ],
        "loadDemands": {
          "parcels": { "amount": "2" }
        }
      },
      {
        "label": "parcel-102",
        "deliveries": [
          {
            "arrivalLocation": { "latitude": 51.033, "longitude": -114.07 },
            "duration": "240s"
          }
        ],
        "loadDemands": {
          "parcels": { "amount": "1" }
        }
      },
      {
        "label": "parcel-103",
        "deliveries": [
          {
            "arrivalLocation": { "latitude": 51.036, "longitude": -114.019 },
            "duration": "360s",
            "timeWindows": [{ "startTime": "2026-10-13T15:00:00Z", "endTime": "2026-10-13T18:00:00Z" }]
          }
        ],
        "loadDemands": {
          "parcels": { "amount": "4" }
        }
      },
      {
        "label": "parcel-104",
        "deliveries": [
          {
            "arrivalLocation": { "latitude": 51.053, "longitude": -114.04 },
            "duration": "300s",
            "timeWindows": [{ "startTime": "2026-10-13T19:00:00Z", "endTime": "2026-10-13T23:00:00Z" }]
          }
        ],
        "loadDemands": {
          "parcels": { "amount": "3" }
        }
      },
      {
        "label": "parcel-105",
        "deliveries": [
          {
            "arrivalLocation": { "latitude": 51.053, "longitude": -114.087 },
            "duration": "240s"
          }
        ],
        "loadDemands": {
          "parcels": { "amount": "2" }
        }
      },
      {
        "label": "parcel-106",
        "deliveries": [
          {
            "arrivalLocation": { "latitude": 51.057, "longitude": -114.099 },
            "duration": "420s",
            "timeWindows": [{ "startTime": "2026-10-13T19:00:00Z", "endTime": "2026-10-13T23:00:00Z" }]
          }
        ],
        "loadDemands": {
          "parcels": { "amount": "5" }
        }
      },
      {
        "label": "parcel-107",
        "deliveries": [
          {
            "arrivalLocation": { "latitude": 51.02, "longitude": -114.106 },
            "duration": "240s",
            "timeWindows": [{ "startTime": "2026-10-13T15:00:00Z", "endTime": "2026-10-13T18:00:00Z" }]
          }
        ],
        "loadDemands": {
          "parcels": { "amount": "1" }
        }
      },
      {
        "label": "parcel-108",
        "deliveries": [
          {
            "arrivalLocation": { "latitude": 51.013, "longitude": -114.095 },
            "duration": "300s"
          }
        ],
        "loadDemands": {
          "parcels": { "amount": "3" }
        }
      },
      {
        "label": "parcel-109",
        "deliveries": [
          {
            "arrivalLocation": { "latitude": 51.033, "longitude": -114.043 },
            "duration": "300s",
            "timeWindows": [{ "startTime": "2026-10-13T19:00:00Z", "endTime": "2026-10-13T23:00:00Z" }]
          }
        ],
        "loadDemands": {
          "parcels": { "amount": "2" }
        }
      },
      {
        "label": "parcel-110",
        "deliveries": [
          {
            "arrivalLocation": { "latitude": 51.065, "longitude": -114.037 },
            "duration": "420s",
            "timeWindows": [{ "startTime": "2026-10-13T15:00:00Z", "endTime": "2026-10-13T18:00:00Z" }]
          }
        ],
        "loadDemands": {
          "parcels": { "amount": "6" }
        }
      },
      {
        "label": "parcel-111",
        "deliveries": [
          {
            "arrivalLocation": { "latitude": 51.074, "longitude": -114.056 },
            "duration": "300s"
          }
        ],
        "loadDemands": {
          "parcels": { "amount": "2" }
        }
      },
      {
        "label": "parcel-112",
        "deliveries": [
          {
            "arrivalLocation": { "latitude": 51.032, "longitude": -114.11 },
            "duration": "240s",
            "timeWindows": [{ "startTime": "2026-10-13T19:00:00Z", "endTime": "2026-10-13T23:00:00Z" }]
          }
        ],
        "loadDemands": {
          "parcels": { "amount": "1" }
        }
      }
    ],
    "vehicles": [
      {
        "label": "van-1",
        "startLocation": { "latitude": 51.001, "longitude": -114.007 },
        "endLocation": { "latitude": 51.001, "longitude": -114.007 },
        "startTimeWindows": [{ "startTime": "2026-10-13T14:00:00Z", "endTime": "2026-10-13T14:30:00Z" }],
        "endTimeWindows": [{ "endTime": "2026-10-14T00:00:00Z" }],
        "loadLimits": {
          "parcels": { "maxLoad": "20" }
        },
        "costPerHour": 30,
        "costPerKilometer": 0.6
      },
      {
        "label": "van-2",
        "startLocation": { "latitude": 51.001, "longitude": -114.007 },
        "endLocation": { "latitude": 51.001, "longitude": -114.007 },
        "startTimeWindows": [{ "startTime": "2026-10-13T14:00:00Z", "endTime": "2026-10-13T14:30:00Z" }],
        "endTimeWindows": [{ "endTime": "2026-10-14T00:00:00Z" }],
        "loadLimits": {
          "parcels": { "maxLoad": "20" }
        },
        "costPerHour": 30,
        "costPerKilometer": 0.6
      }
    ]
  }
}

Prochaines étapes

  • API Optimize : points de terminaison, champs pris en charge, limites et tarification.
  • Erreurs d'Optimize : le sens de la raison d'un envoi laissé de côté, et chaque erreur et avertissement.
  • banc d'essai Optimize : exécutez cette requête avec votre clé et voyez les tournées sur une carte.