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[].isPickupdistingue 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-Shipmentsne 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.jsonLe 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.