Aller au contenu
Tous les exemplesItinéraires

Organiser une messagerie avec ramassage et livraison

Deux messagers à Montréal, qui partent d'un point de distribution au centre-ville, avec une matinée à couvrir : de 9 h à 11 h. Six commandes, chacune ramassée dans un quartier et livrée dans un autre. Un ramassage et sa livraison forment un seul envoi, avec pickups et deliveries : le même messager transporte la commande de l'un à l'autre, ramassage d'abord.

Chaque commande compte un certain nombre de boîtes (loadDemands), et un messager en transporte au plus quatre à la fois (loadLimits). C'est ce qui force les tournées à s'entrelacer : un messager ramasse, livre et ramasse de nouveau plutôt que de tout collecter d'abord. Trois commandes ont une fenêtre horaire de livraison. order-306 a un penaltyCost de 40, qui la rend facultative : le solveur peut la laisser de côté si la servir coûtait plus de 40, et elle figurerait alors dans skippedShipments.

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

  • visits[].isPickup distingue la visite de ramassage d'une commande de sa livraison.
  • metrics.performedShipmentCount, comparé aux six commandes, dit si quelque chose a été laissé de côté.
  • X-Unmap-Billable-Shipments ne compte que les commandes livrées.

L'envoyer

Enregistrez le corps ci-dessous dans pickup-delivery-courier.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 @pickup-delivery-courier.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 : 13:00:00Z est 9 h à Montréal 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": 7,
  "timeout": "10s",
  "model": {
    "globalStartTime": "2026-10-13T13:00:00Z",
    "globalEndTime": "2026-10-13T23:00:00Z",
    "shipments": [
      {
        "label": "order-301",
        "pickups": [
          {
            "arrivalLocation": { "latitude": 45.522, "longitude": -73.58 },
            "duration": "180s"
          }
        ],
        "deliveries": [
          {
            "arrivalLocation": { "latitude": 45.507, "longitude": -73.554 },
            "duration": "240s",
            "timeWindows": [{ "endTime": "2026-10-13T14:00:00Z" }]
          }
        ],
        "loadDemands": {
          "boxes": { "amount": "2" }
        }
      },
      {
        "label": "order-302",
        "pickups": [
          {
            "arrivalLocation": { "latitude": 45.525, "longitude": -73.6 },
            "duration": "180s"
          }
        ],
        "deliveries": [
          {
            "arrivalLocation": { "latitude": 45.492, "longitude": -73.56 },
            "duration": "240s"
          }
        ],
        "loadDemands": {
          "boxes": { "amount": "1" }
        }
      },
      {
        "label": "order-303",
        "pickups": [
          {
            "arrivalLocation": { "latitude": 45.543, "longitude": -73.585 },
            "duration": "180s"
          }
        ],
        "deliveries": [
          {
            "arrivalLocation": { "latitude": 45.478, "longitude": -73.585 },
            "duration": "240s",
            "timeWindows": [{ "endTime": "2026-10-13T14:30:00Z" }]
          }
        ],
        "loadDemands": {
          "boxes": { "amount": "3" }
        }
      },
      {
        "label": "order-304",
        "pickups": [
          {
            "arrivalLocation": { "latitude": 45.545, "longitude": -73.62 },
            "duration": "180s"
          }
        ],
        "deliveries": [
          {
            "arrivalLocation": { "latitude": 45.544, "longitude": -73.54 },
            "duration": "240s"
          }
        ],
        "loadDemands": {
          "boxes": { "amount": "1" }
        }
      },
      {
        "label": "order-305",
        "pickups": [
          {
            "arrivalLocation": { "latitude": 45.52, "longitude": -73.61 },
            "duration": "180s"
          }
        ],
        "deliveries": [
          {
            "arrivalLocation": { "latitude": 45.46, "longitude": -73.57 },
            "duration": "240s",
            "timeWindows": [{ "startTime": "2026-10-13T14:00:00Z", "endTime": "2026-10-13T15:00:00Z" }]
          }
        ],
        "loadDemands": {
          "boxes": { "amount": "2" }
        }
      },
      {
        "label": "order-306",
        "pickups": [
          {
            "arrivalLocation": { "latitude": 45.473, "longitude": -73.617 },
            "duration": "180s"
          }
        ],
        "deliveries": [
          {
            "arrivalLocation": { "latitude": 45.5017, "longitude": -73.5673 },
            "duration": "240s"
          }
        ],
        "loadDemands": {
          "boxes": { "amount": "2" }
        },
        "penaltyCost": 40
      }
    ],
    "vehicles": [
      {
        "label": "courier-1",
        "startLocation": { "latitude": 45.5, "longitude": -73.57 },
        "endLocation": { "latitude": 45.5, "longitude": -73.57 },
        "startTimeWindows": [{ "startTime": "2026-10-13T13:00:00Z", "endTime": "2026-10-13T13:30:00Z" }],
        "endTimeWindows": [{ "endTime": "2026-10-13T15:00:00Z" }],
        "loadLimits": {
          "boxes": { "maxLoad": "4" }
        },
        "costPerHour": 25,
        "costPerKilometer": 0.5
      },
      {
        "label": "courier-2",
        "startLocation": { "latitude": 45.5, "longitude": -73.57 },
        "endLocation": { "latitude": 45.5, "longitude": -73.57 },
        "startTimeWindows": [{ "startTime": "2026-10-13T13:00:00Z", "endTime": "2026-10-13T13:30:00Z" }],
        "endTimeWindows": [{ "endTime": "2026-10-13T15:00:00Z" }],
        "loadLimits": {
          "boxes": { "maxLoad": "4" }
        },
        "costPerHour": 25,
        "costPerKilometer": 0.5
      }
    ]
  }
}

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.