Skip to main content

Add Optimization with Full Details

|

The example covers the following features:

  • Adding an optimization with custom configuration parameters.
  • Defining orders with various fields (e.g., time windows, packages, weights).
  • Setting up multiple vehicles with specific constraints.
  • Displaying the optimized solution on a map.

When you run the example application:

  • An optimization is created and saved.
  • The optimized solution is returned and displayed on the map.

Create Customers and Orders

note

Each order must have a customer associated with it. You can either:

  • Create a new customer and assign it to the order.
  • Use an existing customer (refer to the Get Customer example).

Initializing and Adding Customers

  1. Initialize a ProgressListener and vrp::Service.
  2. Create twelve vrp::Customer objects and set the desired fields, and add them to database.
  3. Call the addCustomer() method from the vrp::Service using the vrp::Customer and ProgressListener and wait for the operation to be done.
// Initialize Service and Listener  
ProgressListener listener;
gem::vrp::Service serv;

//Initialize customers and set the desired fields.
gem::vrp::Customer c0;
c0.setCoordinates(gem::Coordinates(48.234270, -2.133208));
c0.setAlias("c0");
c0.setPhoneNumber("+12312312");
c0.setEmail("c0@yahoo.com");
int ret = serv.addCustomer(&listener, c0);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);

gem::vrp::Customer c1;
c1.setCoordinates(gem::Coordinates(45.854137, 2.853998));
c1.setAlias("c1");
c1.setEmail("c1@yahoo.com");
c1.setPhoneNumber("+12312312");
ret = serv.addCustomer(&listener, c1);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);

gem::vrp::Customer c2(gem::Coordinates(46.199373, 0.069986));
c2.setAlias("c2");
c2.setPhoneNumber("+12312312");
c2.setEmail("c2@yahoo.com");
ret = serv.addCustomer(&listener, c2);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);

gem::vrp::Customer c3(gem::Coordinates(48.052503, 0.119726));
c3.setAlias("c3");
c3.setPhoneNumber("+12312312");
c3.setEmail("c3@yahoo.com");
ret = serv.addCustomer(&listener, c3);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);

gem::vrp::Customer c4(gem::Coordinates(44.346051, 4.694878));
c4.setAlias("c4");
c4.setPhoneNumber("+12312312");
c4.setEmail("c4@yahoo.com");
ret = serv.addCustomer(&listener, c4);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);

gem::vrp::Customer c5(gem::Coordinates(44.464582, 2.455020));
c5.setAlias("c5");
c5.setPhoneNumber("+12312312");
c5.setEmail("c5@yahoo.com");
ret = serv.addCustomer(&listener, c5);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);

gem::vrp::Customer c6(gem::Coordinates(48.656644, 5.907131));
c6.setAlias("c6");
c6.setPhoneNumber("+12312312");
c6.setEmail("c6@yahoo.com");
ret = serv.addCustomer(&listener, c6);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);

gem::vrp::Customer c7(gem::Coordinates(49.161539, 0.500580));
c7.setAlias("c7");
c7.setPhoneNumber("+12312312");
c7.setEmail("c7@yahoo.com");
ret = serv.addCustomer(&listener, c7);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);

gem::vrp::Customer c8(gem::Coordinates(47.702421, 3.384226));
c8.setAlias("c8");
c8.setPhoneNumber("+12312312");
c8.setEmail("c8@yahoo.com");
ret = serv.addCustomer(&listener, c8);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);

gem::vrp::Customer c9(gem::Coordinates(47.198274, 4.630011));
c9.setAlias("c9");
c9.setPhoneNumber("+12312312");
c9.setEmail("c9@yahoo.com");
ret = serv.addCustomer(&listener, c9);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);

gem::vrp::Customer c10(gem::Coordinates(49.655296, 2.243181));
c10.setAlias("c10");
c10.setPhoneNumber("+12312312");
c10.setEmail("c10@yahoo.com");
ret = serv.addCustomer(&listener, c10);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);

gem::vrp::Customer c11(gem::Coordinates(50.719729, 2.160877));
c11.setAlias("c11");
c11.setPhoneNumber("+12312312");
c11.setEmail("c11@yahoo.com");
ret = serv.addCustomer(&listener, c11);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);

Initializing and adding Orders

  1. Create a vrp::OrderList and add orders to it. Each order must have a customer associated with it
  2. Create twelve vrp::Orders objects and associate one customer for each, set the desired fields, and add them to database.
  3. Call the addOrder() method from the vrp::Service using the vrp::Order and ProgressListener and wait for the operation to be done.
//Initialize orders
gem::vrp::OrderList orders;
gem::vrp::Order order0(c0);
order0.setNumberOfPackages(5);
order0.setWeight(15.7);
order0.setCube(0.2);
order0.setServiceTime(600);
order0.setTimeWindow(std::make_pair(gem::Time(1596783600000), gem::Time(1596870000000))); // August 7, 2020 7:00:00 AM - August 8, 2020 7:00:00 AM
order0.setType(gem::vrp::EOrderType::OT_PickUp);
ret = serv.addOrder(&listener, order0, false);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);
orders.push_back(order0);
gem::vrp::Order order1(c1);
order1.setNumberOfPackages(4);
order1.setWeight(15.5);
order1.setCube(0.9);
order1.setTimeWindow(std::make_pair(gem::Time(1596783600000), gem::Time(1596870000000))); // August 7, 2020 7:00:00 AM - August 8, 2020 7:00:00 AM
order1.setType(gem::vrp::EOrderType::OT_PickUp);
ret = serv.addOrder(&listener, order1, false);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);
orders.push_back(order1);
gem::vrp::Order order2(c2);
order2.setNumberOfPackages(8);
order2.setWeight(5.5);
order2.setCube(0.3);
order2.setServiceTime(600);
order2.setTimeWindow(std::make_pair(gem::Time(1596798000000), gem::Time(1596839600000))); // August 7, 2020 11:00:00 AM - 10:33:20 PM
order2.setType(gem::vrp::EOrderType::OT_Delivery);
ret = serv.addOrder(&listener, order2, false);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);
orders.push_back(order2);
gem::vrp::Order order3(c3);
order3.setTimeWindow(std::make_pair(gem::Time(1596803600000), gem::Time(1596870000000))); // August 7, 2020 12:33:20 PM - August 8, 2020 7:00:00 AM
order3.setType(gem::vrp::EOrderType::OT_Delivery);
ret = serv.addOrder(&listener, order3, false);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);
orders.push_back(order3);
gem::vrp::Order order4(c4);
order4.setNumberOfPackages(8);
order4.setWeight(5.1);
order4.setCube(0.2);
order4.setServiceTime(600);
order4.setTimeWindow(std::make_pair(gem::Time(1596823600000), gem::Time(1596865900000))); // August 7, 6:06:40 PM - August 8, 2020 5:51:40 AM
order4.setType(gem::vrp::EOrderType::OT_PickUp);
ret = serv.addOrder(&listener, order4, false);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);
orders.push_back(order4);
gem::vrp::Order order5(c5);
order5.setNumberOfPackages(11);
order5.setWeight(6.5);
order5.setCube(0.1);
order5.setServiceTime(900);
order5.setTimeWindow(std::make_pair(gem::Time(1596821600000), gem::Time(1596870000000))); // August 7, 2020 5:33:20 PM - August 8, 2020 7:00:00 AM
order5.setRevenue(25);
order5.setType(gem::vrp::EOrderType::OT_Delivery);
ret = serv.addOrder(&listener, order5, false);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);
orders.push_back(order5);
gem::vrp::Order order6(c6);
order6.setNumberOfPackages(4);
order6.setWeight(1.5);
order6.setCube(0.5);
order6.setServiceTime(500);
order6.setTimeWindow(std::make_pair(gem::Time(1596808900000), gem::Time(1596859600000))); // August 7, 2020 2:01:40 PM - August 8, 2020 4:06:40 AM
order6.setType(gem::vrp::EOrderType::OT_PickUp);
ret = serv.addOrder(&listener, order6, false);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);
orders.push_back(order6);
gem::vrp::Order order7(c7);
order7.setNumberOfPackages(12);
order7.setWeight(6.1);
order7.setCube(0.4);
order7.setServiceTime(750);
order7.setTimeWindow(std::make_pair(gem::Time(1596823600000), gem::Time(1596861500000))); // August 7, 2020 6:06:40 PM - August 8, 2020 4:38:20 AM
order7.setRevenue(75);
order7.setType(gem::vrp::EOrderType::OT_Delivery);
ret = serv.addOrder(&listener, order7, false);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);
orders.push_back(order7);
gem::vrp::Order order8(c8);
order8.setNumberOfPackages(7);
order8.setWeight(2.5);
order8.setCube(0.3);
order8.setServiceTime(800);
order8.setTimeWindow(std::make_pair(gem::Time(1596804600000), gem::Time(1596826800000))); // August 7, 2020 12:50:00 PM - 7:00:00 PM
order8.setType(gem::vrp::EOrderType::OT_Delivery);
order8.setRevenue(110);
ret = serv.addOrder(&listener, order8, false);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);
orders.push_back(order8);
gem::vrp::Order order9(c9);
order9.setNumberOfPackages(12);
order9.setWeight(0.7);
order9.setCube(0.5);
order9.setServiceTime(1000);
order9.setTimeWindow(std::make_pair(gem::Time(1596808600000), gem::Time(1596842900000))); // August 7, 2020 1:56:40 PM - 11:28:20 PM
order9.setType(gem::vrp::EOrderType::OT_PickUp);
ret = serv.addOrder(&listener, order9, false);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);
orders.push_back(order9);
gem::vrp::Order order10(c10);
order10.setNumberOfPackages(9);
order10.setWeight(4.3);
order10.setCube(0.6);
order10.setServiceTime(850);
order10.setTimeWindow(std::make_pair(gem::Time(1596812600000), gem::Time(1596849600000))); // August 7, 2020 3:03:20 PM - August 8, 2020 1:20:00 AM
order10.setType(gem::vrp::EOrderType::OT_PickUp);
ret = serv.addOrder(&listener, order10, false);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);
orders.push_back(order10);
gem::vrp::Order order11(c11);
order11.setNumberOfPackages(5);
order11.setWeight(4.1);
order11.setCube(0.4);
order11.setServiceTime(600);
order11.setTimeWindow(std::make_pair(gem::Time(1596800600000), gem::Time(1596830600000))); // August 7, 2020 11:43:20 AM - 8:03:20 PM
order11.setType(gem::vrp::EOrderType::OT_PickUp);
ret = serv.addOrder(&listener, order11, false);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);
orders.push_back(order11);

Configure Optimization Parameters

note

Configuration Parameters define key settings that influence the behavior of the route optimization process. These settings determine aspects such as optimization goals, search time limits, and flexibility in handling orders.

  1. Create a vrp::ConfigurationParameters object and set the desired parameters.
  2. Create a gem::vrp::OrdersSequenceMap this is not a mandatory field, specifies the association between different orders that should be visited in a certain order. In our example will be a fixed sequence of orders between orders from position 2,8,6.
gem::vrp::OrdersSequenceMap ordersSequence;
gem::LargeIntListList fixedSequence = gem::LargeIntListList{ gem::LargeIntList{orders[2].getId(), orders[8].getId(), orders[6].getId()} };
ordersSequence.insert(std::make_pair(gem::vrp::EOrdersSequenceOption::OSO_InFixedSequence, fixedSequence));

gem::vrp::ConfigurationParameters configParams;
configParams.setName("France optimization");
configParams.setIgnoreTimeWindow(false);
configParams.setOptimizationCriterion(gem::vrp::EOptimizationCriterion::OC_Distance);
configParams.setOptimizationQuality(gem::vrp::EOptimizationQuality::OQ_Optimized);
configParams.setMaxWaitTime(18000); // A vehicle can wait maximum 5 hours between a order and the next one, in order to visit the next one within its time window
configParams.setRouteType(gem::vrp::ERouteType::RT_CustomEnd);
configParams.setRestrictions(gem::vrp::ERoadRestrictions::RR_None);
configParams.setDistanceUnit(gem::vrp::EDistanceUnit::DU_Kilometers);
configParams.setOrderSequenceOptions(ordersSequence);

Create Vehicles and difine Vehicle Constraints

note

Vehicle constraints define the limitations and requirements applied to a vehicle during the route optimization process. Ensure that the vehicle operates within its capabilities, such as time windows, capacity, distance, revenue. There are two ways of defining the constraints. Each vehicle will have a different contraints or we set only one vehicle constraints that will apply to all vehicles.

Initializing and adding vehicles

  1. Create a gem::vrp::VehicleList and add vehicles to it.
  2. Create two vrp::Vehicle objects and set the desired fields, and add them to database.
  3. Call the addVehicle() method from the vrp::Service using the vrp::Vehicle and ProgressListener and wait for the operation to be done.
gem::vrp::VehicleList vehicles;
gem::vrp::Vehicle vehicle1;
vehicle1.setName("Vehicle 1");
vehicle1.setType(gem::vrp::EVehicleType::VT_Car);
vehicle1.setStatus(gem::vrp::EVehicleStatus::VS_Available);
vehicle1.setManufacturer("Kia");
vehicle1.setModel("Ceed");
vehicle1.setFuelType(gem::vrp::EFuelType::FT_GasolinePremium);
vehicle1.setConsumption(6.5);
vehicle1.setLicensePlate("BV01ASD");
vehicle1.setMaxWeight(300);
vehicle1.setMaxCube(15);
vehicle1.setStartTime(420); // 7:00:00 AM
vehicle1.setEndTime(1860); // 7:00:00 AM next day

int res = serv.addVehicle(&listener, vehicle1);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);
vehicles.push_back(vehicle1);

gem::vrp::Vehicle vehicle2;
vehicle2.setName("Vehicle 2");
vehicle2.setType(gem::vrp::EVehicleType::VT_Car);
vehicle2.setStatus(gem::vrp::EVehicleStatus::VS_Available);
vehicle2.setManufacturer("Kia");
vehicle2.setModel("Ceed");
vehicle2.setFuelType(gem::vrp::EFuelType::FT_GasolinePremium);
vehicle2.setConsumption(6.5);
vehicle2.setLicensePlate("BV02ASD");
vehicle2.setMaxWeight(300);
vehicle2.setMaxCube(15);
vehicle2.setStartTime(480); // 8:00:00 AM
vehicle2.setEndTime(2520); // 6:00:00 PM next day

res = serv.addVehicle(&listener, vehicle2);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 5000);
vehicles.push_back(vehicle2);

Define Vehicle Constraints

  1. Create a vrp::VehicleConstraints object for each vehicle.
  2. Add these constraints to a vrp::VehicleConstraintsList.
gem::vrp::VehicleConstraintsList vehConstraintsList;
gem::vrp::VehicleConstraints vehConstr1;
vehConstr1.setMaxNumberOfPackages(100);
vehConstr1.setMaxRevenue(2000);
vehConstr1.setStartDate(gem::Time(2020, 8, 7)); // August 7, 2020
vehConstr1.setMinNumberOfOrders(1);
vehConstr1.setMaxNumberOfOrders(50);
vehConstr1.setMinDistance(1);
vehConstr1.setMaxDistance(19000);
vehConstraintsList.push_back(vehConstr1);
gem::vrp::VehicleConstraints vehConstr2;
vehConstr2.setMaxNumberOfPackages(100);
vehConstr2.setMaxRevenue(2000);
vehConstr2.setStartDate(gem::Time(2020, 8, 7)); // August 7, 2020
vehConstr2.setMinNumberOfOrders(2);
vehConstr2.setMaxNumberOfOrders(60);
vehConstr2.setMinDistance(2);
vehConstr2.setMaxDistance(20000);
vehConstraintsList.push_back(vehConstr2);

Create the Departures and Destinations

note

Departures in define the starting points for vehicle routes. These locations serve as the origin of a route and can impact optimization by influencing travel distance and time. Destinations define the final stop for a vehicle route. These locations mark the endpoint of a route and play a key role in optimizing route efficiency.

Initializing deparures and destinations

  1. Create two vrp::Departure objects one for each vehicle.
  2. Create a vrp::Destination object, both vehicles will end their routes at the same destination.
gem::vrp::Departure departure1;
departure1.setAlias("Depot 1");
departure1.setCoordinates(gem::Coordinates(48.618893, -1.353635));
gem::vrp::Departure departure2;
departure2.setAlias("Depot 2");
departure2.setCoordinates(gem::Coordinates(46.213984, 1.693113));

gem::vrp::Destination destination;
destination.setAlias("Destination");
destination.setCoordinates(gem::Coordinates(47.617484, 1.152466));

Create the Optimization

Note

An optimization represents a set of orders, vehicles, constraints, and other parameters that define a routing problem.

  1. Create a vrp::Optimization object.
  2. Assign the OrderList, ConfigurationParameters, VehicleList, VehicleConstraintsList, Departures, Destinations to the optimization.
gem::vrp::Optimization optimization;
optimization.setConfigurationParameters(configParams);
optimization.setVehicles(vehicles);
optimization.setDepartures({departure1,departure2});
optimization.setDestinations({destination}); // both vehicles will end their routes at the same destination
optimization.setOrders(orders);
optimization.setVehiclesConstraints(vehConstraintsList);
optimization.setMatrixBuildType(gem::vrp::EMatrixBuildType::MBT_Real);

Displaying Orders on the Map

Once the orders have been added, we can display them on the map.

Initialize Map Components

Create a MapServiceListener, OpenGLContext, and MapView:

MapViewListenerImpl mapListener;
auto oglContext = session.produceOpenGLContext(Environment::WindowFrameworks::Available, "AddFullOptimization");
gem::StrongPointer<gem::MapView> mapView = gem::MapView::produce(oglContext, &mapListener);

Highlight Orders and Departures

  1. Create a LandmarkList and CoordinatesList using the OrderList, Departures and Destinations.
  2. Instruct the MapView to highlight the landmarks (orders, departures, and destinations)
  3. For a better visibility create a PolygonGeographicArea from the CoordinatesList, center the MapView on this area.
gem::LandmarkList lmks;
gem::CoordinatesList coords;

for (int i = 0; i < optimization.getDepartures().size(); i++)
{
gem::Landmark landmark;
landmark.setName(optimization.getDepartures()[i].getAlias());
landmark.setCoordinates(optimization.getDepartures()[i].getCoordinates());
landmark.setImage(gem::Icon::Core::GreenBall);

lmks.push_back(landmark);
coords.push_back(optimization.getDepartures()[i].getCoordinates());
}

for (int i = 0; i < orders.size(); i++)
{
gem::Landmark landmark;
landmark.setName(orders[i].getAlias());
landmark.setCoordinates(orders[i].getCoordinates());
landmark.setImage(gem::Icon::Core::BlueBall);

lmks.push_back(landmark);
coords.push_back(orders[i].getCoordinates());
}

for (int i = 0; i < optimization.getDestinations().size(); i++)
{
gem::Landmark landmark;
landmark.setName(optimization.getDestinations()[i].getAlias());
landmark.setCoordinates(optimization.getDestinations()[i].getCoordinates());
landmark.setImage(gem::Icon::Core::RedBall);

lmks.push_back(landmark);
coords.push_back(optimization.getDestinations()[i].getCoordinates());
}

mapView->activateHighlight(lmks);
gem::PolygonGeographicArea polyArea(coords);
mapView->centerOnArea(polyArea);

ret = WAIT_UNTIL(std::bind(&MapViewListenerImpl::IsFinished, &mapListener), 15000);

Run the Optimization

  1. Call the addOptimization() method from vrp::Service, passing the Optimization object and the ProgressListener.
  2. After the operation is finished, a solution for optimization will be generated. To view the solution, you need to call the getSolution method from the optimization, which will return a vrp::RouteList containing the optimization results.
std::shared_ptr<gem::vrp::Request> request = std::make_shared<gem::vrp::Request>();
ret = serv.addOptimization(&listener, optimization, request);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 60000);

WAIT_UNTIL([&]() {
serv.getRequest(&listener, request, request->id);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 7000);
return request->status == gem::vrp::ERequestStatus::eFinished;
}, 550000);

gem::vrp::RouteList routes;
ret = optimization.getSolution(&listener, routes);
WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &listener), 60000);

Display Routes on map

Once the optimization is complete and a solution has been found, we can display the solution on the map

  1. Ensure that operation was done, and a solution was found.
  2. Create a MarkerCollection of type Polyline for each route.
  3. Add the route shapes to the MarkerCollection.
  4. Set the MarkerCollection in the map view preferences.
  5. After hiliting on the map, center the screen over the routes.
if (listener.IsFinished() && listener.GetError() == gem::KNoError && ret == gem::KNoError)
{
std::cout << "Problem optimized successfully" << std::endl;

gem::CoordinatesList shape0 = routes[0].getShape();
gem::CoordinatesList shape1 = routes[1].getShape();

// display routes shapes on map
auto col1 = gem::MarkerCollection(gem::EMarkerType::MT_Polyline, "shape0");
col1.add(gem::Marker(shape0));
mapView->preferences().markers().add(col1);

auto col2 = gem::MarkerCollection(gem::EMarkerType::MT_Polyline, "shape1");
col2.add(gem::Marker(shape1));
gem::MarkerCollectionRenderSettings markerCollDisplaySettings;
markerCollDisplaySettings.polylineInnerColor = gem::Rgba(0, 0, 255, 0);
mapView->preferences().markers().add(col2, markerCollDisplaySettings);
ret = WAIT_UNTIL(std::bind(&MapViewListenerImpl::IsFinished, &mapListener), 15000);

gem::CoordinatesList shapesCoordinates;
shapesCoordinates.insert(shapesCoordinates.end(), shape0.begin(), shape0.end());
shapesCoordinates.insert(shapesCoordinates.end(), shape1.begin(), shape1.end());

gem::PolygonGeographicArea polyArea(shapesCoordinates);
mapView->centerOnArea(polyArea);
ret = WAIT_UNTIL(std::bind(&MapViewListenerImpl::IsFinished, &mapListener), 15000);

WAIT_UNTIL_WINDOW_CLOSE();
}
else
std::cout << "Problem couldn't be optimized" << std::endl;