Volume LTL Shipping Implementation

This technical notebook details the implementation of Volume Less-Than-Truckload (LTL) shipping using various NextBillion.ai products. The primary APIs utilized include the Route Optimization API, the Flexible Directions API, and the Web Maps SDK.

Problem Statement

The challenge at hand involves efficiently delivering bulk store orders from appliance manufacturers in Columbus to three distinct cities in the US: Los Angeles, Austin, and Detroit. The complexity arises from the requirement of using trucks equipped with liftgates for certain products during the final delivery, imposing a constraint on the delivery process. The objective is to optimize the transportation process, ensuring the efficient and timely delivery of products to the respective stores.

The following diagram illustrates this LTL Shipping problem.

Volume LTL Shipping diagram

Solution Approach

  1. Selection of Cities and Hubs
  • Four cities, namely Columbus, Los Angeles, Detroit, and Austin, are arbitrarily chosen.
  • Columbus is designated as the origin city.
  • Each city is associated with a shipping hub, and multiple manufacturers are selected for each hub.
  1. Manufacturing and Product Specification
  • Each manufacturer produces a single type of item (fridge, washing machine, AC, or dishwasher).
  • Products like fridges and washing machines necessitate liftgate-equipped trucks for unloading.
  1. Store and Order Generation
  • Each destination city has four electronics stores.
  • Stores place orders ranging from 0 to 5 products from each manufacturer.
  1. Route Optimization
  • Using the Flexible Directions API, optimal paths for trucks from manufacturers to the Columbus Shipping Hub are determined.
  • Allocation of 10 long-haul trucks to the Columbus Shipping Hub.
  • Utilization of the Route Optimization API to find optimal paths and item distributions for each long-haul truck (capacity: 30,000 pounds).
  1. Short-Haul Logistics
  • Allocation of 10 short-haul trucks to each Destination Hub.
  • Short-haul truck capacity is set at 5,000 pounds, with random assignment of liftgates.
  • Calling the Route Optimization API once again to distribute items from Destination Hubs to short-haul trucks.
  1. Final Delivery to Stores
  • Use of Route Optimization API for the last leg of the journey, distributing products from short-haul trucks to respective stores.
  • Taking care of a specification that 20% of short-haul trucks lack liftgates, and orders involving fridges or washing machines require one.

Implementation Steps

In this technical notebook, we will guide you through the steps required to build, solve, and visualize a complex multi-vehicle routing problem tailored to this specific use case.

Step 1: Define Constraints

For simplicity, certain assumptions are made:

Step 2: Enter NextBillion.ai API Key

To access NextBillion.ai APIs in this technical notebook, enter your assigned API key. Contact [email protected] to obtain your API key.

Step 3: Define Capacity Parameters

In this step, we establish specific capacity parameters to guide the logistics of the transportation system. The following values are set:

  • Number of Long-Haul Trucks at Origin Hub:10
  • Number of Short-Haul Trucks at Each Destination Hub: 10
  • Long-Haul Truck Capacity: 30,000 pounds
  • Final Delivery Truck (Short-Haul) Capacity: 5,000 pounds

These parameters play a crucial role in optimizing the distribution and delivery process, ensuring efficient utilization of resources and meeting the requirements of the shipping operation.

Step 4: Data Preparation and Mapping

Manufacturers

The manufacturers’ information is captured in the manufacturersTable array, providing a clear representation of key details. The data mapping is performed using the following structure.

manufacturersTable = MANUFACTURERS.map((manufacturer) => ({
  Id: manufacturer.id,
  Name: manufacturer.name,
  Item: PRODUCTS[manufacturer.item].name,
  City: CITIES[manufacturer.city].name,
  Coordinates: manufacturer.location.join(", "),
  "Postal Code": `${manufacturer.postalCode}`
}))

The following table represents manufacturer information. 

 

ID

Name

Item

City

Coordinates

Postal Code

0

Best AC Company

AC

Columbus

-82.92288, 40.09587

43081

1

Cool Fridges

Fridge

Columbus

-82.84959, 39.89277

43110

2

Dishy Dish Washers Corp

Dishwasher

Columbus

-83.1163, 39.90666

43123

3

Wishing Machines

Washing Machine

Columbus

-83.34013, 40.1381

43064

Orders

The ordersTable is an Array of 12 objects representing store orders.


ordersTable = Array(12) [
 0: Object {Store: "Smitham Inc", City: "Austin", ac: 0, fridge: 4, dish_washer: 4, washing_machine: 4}
 1: Object {Store: "Goyette, Stroman and Schiller", City: "Austin", ac: 3, fridge: 2, dish_washer: 2, washing_machine: 4}
 2: Object {Store: "Feest and Hermann", City: "Austin", ac: 5, fridge: 3, dish_washer: 5, washing_machine: 1}
 3: Object {Store: "Bashirian and Sons", City: "Austin", ac: 5, fridge: 4, dish_washer: 4, washing_machine: 3}
 4: Object {Store: "Jones - Durgan", City: "Los Angeles", ac: 3, fridge: 0, dish_washer: 4, washing_machine: 0}
 5: Object {Store: "Larson - Schowalter", City: "Los Angeles", ac: 0, fridge: 1, dish_washer: 0, washing_machine: 0}
 6: Object {Store: "Baumbach, Gutkowski and Schinner", City: "Los Angeles", ac: 4, fridge: 4, dish_washer: 4, washing_machine: 3}
 7: Object {Store: "Stark Inc", City: "Los Angeles", ac: 0, fridge: 3, dish_washer: 2, washing_machine: 2}
 8: Object {Store: "Casper, Bashirian and Bogisich", City: "Detroit", ac: 4, fridge: 5, dish_washer: 2, washing_machine: 3}
 9: Object {Store: "Keeling Inc", City: "Detroit", ac: 3, fridge: 3, dish_washer: 0, washing_machine: 0}
 10: Object {Store: "Mueller - Bosco", City: "Detroit", ac: 0, fridge: 4, dish_washer: 3, washing_machine: 2}
 11: Object {Store: "Rice - MacGyver", City: "Detroit", ac: 4, fridge: 3, dish_washer: 3, washing_machine: 4}
]

The following table represents order information in a structured format.

Id

Store

AC

Fridge

Dish Washers

Washing Machine

City

0

Smitham Inc

0

4

4

4

Austin

1

Goyette, Stroman and Schiller

3

2

2

4

Austin

2

Feest and Hermann

5

3

5

1

Austin

3

Bashirian and Sons

5

4

4

3

Austin

4

Jones – Durgan

3

0

4

0

Los Angeles

5

Larson – Schowalter

0

1

0

0

Los Angeles

6

Baumbach, Gutkowski and Schinner

4

4

4

3

Los Angeles

7

Stark Inc

0

3

2

2

Los Angeles

8

Casper, Bashirian and Bogisich

4

5

2

3

Detroit

9

Keeling Inc

3

3

0

0

Detroit

10

Mueller – Bosco

0

4

3

2

Detroit

11

Rice – MacGyver

4

3

3

4

Detroit

Step 5: Generating Routes from Manufacturers to Origin Hub

In this step, we call NextBillin.ai’s Directions API to generate transportation routes from each manufacturer’s location to the origin hub in Columbus. The following code snippet demonstrates this asynchronous calculation using JavaScript’s Promise.all  functionality:


companiesToOriginHubDirections = Promise.all(
  MANUFACTURERS.map((manufacturer) =>
    getDirection(manufacturer.location, ORIGIN_HUB.location)
  )
);
Process Explanation

1. Mapping Manufacturers

  • The `MANUFACTURERS` array is iterated using the map function, addressing each manufacturer’s data.

2. Asynchronous Direction Calculation

  • For each manufacturer, the `getDirection` function is invoked. This function is presumed to be a Promise-based operation that calculates the optimal route between two geographical locations.

3. Promise.all Integration

  • The `Promise.all` function is employed to handle the array of promises generated in the mapping process.
  • It concurrently executes all the promises, ensuring efficient parallelization of the direction calculations.

4. companiesToOriginHubDirections Promise

  • The result is encapsulated in the `companiesToOriginHubDirections` promise.
  • This promise will resolve to an array containing the calculated directions for each manufacturer’s route to the origin hub.

The following image summarizes the transportation routes from each manufacturer to the origin hub.

To visualize the result on a map, use the following code snippet to render a map using NextBillin.ai’s Web Maps SDK.


const nbmap = new nextbillion.maps.Map({
    container: document.getElementById("map-2"),
    center: {
      lat: CITIES.columbus.centroid[1],
      lng: CITIES.columbus.centroid[0]
    },
    zoom: 10,
    maxZoom: 15,
    minZoom: 0,
    style: "https://api.nextbillion.io/maps/streets/style.json",
    scrollZoom: true
  });

  nbmap.on("load", () => {
    drawHub(nbmap.map, ORIGIN_HUB);
    MANUFACTURERS.forEach((manufacturer) =>
      drawManufacturer(nbmap.map, manufacturer)
    );
    companiesToOriginHubDirections.forEach((directions, i) => {
      drawRoute(nbmap.map, `route-${i}`, directions[0], COLORS[i]);
    });
  });
}

The following image visualizes the routes assigned to each long-haul trucks from manufacturers to the central hub.

Step 6: Generating Routes from Origin Hub to Destination Hubs

In this step, we solve the multi-vehicle routing from the origin hub to various destination hubs, considering the specific capacities and constraints of each vehicle.

1. Truck Configuration at Origin Hub

The following array represents the details of 10 long-haul trucks with unique identifiers, names, VINs, and a common capacity of 30,000 pounds.


TRUCKS_AT_ORIGIN_HUB = Array(10) [
 0: Object {id: 0, name: "T680", vin: "Z730OM67K1GJ52751", capacity: 30000}
 1: Object {id: 1, name: "579", vin: "0S1DL9T5O5GK52021", capacity: 30000}
 2: Object {id: 2, name: "Cascadia", vin: "LPQJIV8H1RF898603", capacity: 30000}
 3: Object {id: 3, name: "VNL", vin: "D61S2MWH3MOG89872", capacity: 30000}
 4: Object {id: 4, name: "LT", vin: "GVKIU2MJXEG528568", capacity: 30000}
 5: Object {id: 5, name: "Anthem", vin: "39AM3EVHJZSP65749", capacity: 30000}
 6: Object {id: 6, name: "5700XE", vin: "NN74QJHRXGA984149", capacity: 30000}
 7: Object {id: 7, name: "T880", vin: "A4FMSP1QQAUI40255", capacity: 30000}
 8: Object {id: 8, name: "389", vin: "Y5R8PKDFT3VK51262", capacity: 30000}
 9: Object {id: 9, name: "Coronado", vin: "3NFZN8VJRFZ541838", capacity: 30000}
]

2. MVRP Input Configuration

Input data is configured using the following code snippet.


mvrpInputs = Object {
  depots: Array(1) [
    0: Object {id: 0, location_index: 0}
  ]
  locations: Object {
    id: 1
    location: "39.9869,-82.99876|30.32101,-97.73751|34.04014,-118.24741|42.38504,-83.07939"
  }
  jobs: Array(115) [
    // Details of 115 jobs representing delivery tasks, including location indices and pickup weights
  ]
  vehicles: Array(10) [
    // Details of 10 vehicles (trucks) with unique identifiers, capacity configurations, and maximum task limits
  ]
}

3. MVRP Input Generation Function.

The following function dynamically generates MVRP input based on hub locations, orders, and truck configurations.


mvrpInputs = {
  const locations = [];
  const pickupLocationIndex = 0;
  locations.push(transformPoint(ORIGIN_HUB.location));

  let locationIndex = 1;
  const jobs = [];
  const shipments = [];

  const hubLocationIndices = {};
  for (const hub of DESTINATION_HUBS) {
    hubLocationIndices[hub.city] = locationIndex++;
    locations.push(transformPoint(hub.location));
  }

  for (let orderId = 0; orderId < ORDERS.length; orderId++) {
    const order = ORDERS[orderId];
    const deliveryLocationIndex = hubLocationIndices[order.city];
    jobs.push({
      id: orderId,
      location_index: deliveryLocationIndex,
      // delivery: [PRODUCTS[order.itemId].weight],
      pickup: [PRODUCTS[order.itemId].weight]
    });
  }

  const vehicles = [];
  for (const truck of TRUCKS_AT_ORIGIN_HUB) {
    vehicles.push({
      id: truck.id,
      capacity: [truck.capacity],
      start_index: 0,
      max_tasks: 20
    });
  }

  return {
    depots: [{ id: 0, location_index: 0 }],
    locations: {
      id: 1,
      location: locations.join("|")
    },
    jobs,
    // shipments,
    vehicles
  };
}

4. Execute Route Optimization and Retrieve Results

After successfully executing the MVRP for the long-haul segment, the result is encapsulated in the following mvrpLongHaul object. 

The `result` object gives a brief summary of the routes. The `routes` array represents individual routes for each long-haul truck.


mvrpLongHaul = Object {
 result: Object {
 code: 0
 summary: Object {
 cost: 12193937
 routes: 7
 unassigned: 0
 setup: 0
 service: 0
 duration: 394303
 waiting_time: 0
 priority: 0
 delivery: Array(1) [0]
 pickup: Array(1) [76800]
 distance: 12193937
}
 routes: Array(7) [
 0: Object {vehicle: 0, cost: 328277, steps: Array(22), service: 0, duration: 11703, waiting_time: 0, priority: 0, delivery: Array(1), pickup: Array(1), distance: 328277, geometry: "{|`sFfuqyN?E?E@_ADuCBkA@w@@q@BcBBcBBmB@qA@o@?G@o@D…AiFxAy@?A????????????????????????????????????????", long_vehicle_id: "0"}
 1: Object {vehicle: 1, cost: 328277, steps: Array(22), service: 0, duration: 11703, waiting_time: 0, priority: 0, delivery: Array(1), pickup: Array(1), distance: 328277, geometry: "{|`sFfuqyN?E?E@_ADuCBkA@w@@q@BcBBcBBmB@qA@o@?G@o@D…AiFxAy@?A????????????????????????????????????????", long_vehicle_id: "1"}
 2: Object {vehicle: 2, cost: 328277, steps: Array(5), service: 0, duration: 11703, waiting_time: 0, priority: 0, delivery: Array(1), pickup: Array(1), distance: 328277, geometry: "{|`sFfuqyN?E?E@_ADuCBkA@w@@q@BcBBcBBmB@qA@o@?G@o@D…_@Tg@ZqAp@MHYPMc@Me@y@sCQo@Mg@Uy@wAiFxAy@?A??????", long_vehicle_id: "2"}
 3: Object {vehicle: 3, cost: 1988558, steps: Array(18), service: 0, duration: 64366, waiting_time: 0, priority: 0, delivery: Array(1), pickup: Array(1), distance: 1988558, geometry: "{|`sFfuqyNAh@EzBAj@AlAAb@AZAx@?HA`@CfBAh@A|@F?L?d@…BKFORi@nCmHn@^t@^????????????????????????????????", long_vehicle_id: "3"}
 4: Object {vehicle: 4, cost: 3615995, steps: Array(22), service: 0, duration: 115231, waiting_time: 0, priority: 0, delivery: Array(1), pickup: Array(1), distance: 3615995, geometry: "{|`sFfuqyNAh@EzBAj@AlAAb@AZAx@?HA`@CfBAh@A|@F?L?d@…@UWiBmBKM????????????????????????????????????????", long_vehicle_id: "4"}
 5: Object {vehicle: 5, cost: 3615995, steps: Array(20), service: 0, duration: 115231, waiting_time: 0, priority: 0, delivery: Array(1), pickup: Array(1), distance: 3615995, geometry: "{|`sFfuqyNAh@EzBAj@AlAAb@AZAx@?HA`@CfBAh@A|@F?L?d@…@_@a@UWiBmBKM????????????????????????????????????", long_vehicle_id: "5"}
 6: Object {vehicle: 6, cost: 1988558, steps: Array(20), service: 0, duration: 64366, waiting_time: 0, priority: 0, delivery: Array(1), pickup: Array(1), distance: 1988558, geometry: "{|`sFfuqyNAh@EzBAj@AlAAb@AZAx@?HA`@CfBAh@A|@F?L?d@…Ri@nCmHn@^t@^????????????????????????????????????", long_vehicle_id: "6"}
]
}
 status: "Ok"
message: ""
}

The following function is used to execute MVRP with the generated inputs and retrieve the results.


mvrpLongHaul = {
  const result = await MVRP(mvrpInputs);
  return result;
}

The following table summarizes the route optimization results for long-haul trucks.

ID

Vehicle

Duration(in hours)

Distance (in KM)

Fridges

ACs

Dish Washers

Washing Machines

City

0

T680

3.3

328.3

7

4

3

6

Detroit

1

579

3.3

328.3

4

5

2

9

Detroit

2

Cascadia

3.3

328.3

0

0

3

0

Detroit

3

VNL

17.9

1,988.6

6

2

7

1

Austin

4

LT

32

3,616

6

3

5

6

Los Angeles

5

Anthem

32

3,616

4

8

4

2

Los Angeles

6

5700XE

17.9

1,988.6

4

4

4

6

Austin


Select the Vehicle for which you want to visualize the route result. The subsequent image visualizes the routes for the truck ‘Cascadia’.

long-haul truck cascadia route

Step 7: Last-mile Delivery from Destination Hubs to Stores

This step focuses on the last-mile delivery of orders from the destination hubs to individual stores. The process involves optimizing routes for short-haul trucks based on specific city requirements. Let’s break down the key components:

1. Short-Haul Truck Allocation

The shortHaulTrucks object contains arrays of short-haul trucks for each destination city. The object has the following structure.


shortHaulTrucks = Object {
 austin: Array(10) [Object, Object, Object, Object, Object, Object, Object, Object, Object, Object]
 la: Array(10) [Object, Object, Object, Object, Object, Object, Object, Object, Object, Object]
 detroit: Array(10) [Object, Object, Object, Object, Object, Object, Object, Object, Object, Object]
}

2. MVRP Execution for Last-Mile Delivery

The following code asynchronously executes MVRP for the last-mile delivery in a specified city.

{
 mvrpFromDestinationHubs = {
  async function performMVRP(cityCode) {
    const orders = ORDERS.filter((order) => order.city === cityCode);
    const locations = [
      transformPoint(
        DESTINATION_HUBS.find((hub) => hub.city === cityCode).location
      )
    ];
    const hubLocationIndex = 0;

    let jobId = 0;
    const jobs = [];
    let locationIndex = 1;

    for (let i = 0; i < orders?.length; i++) {
      const order = orders[i];
      const store = STORES[order.storeId];
      jobs.push({
        id: jobId++,
        delivery: [PRODUCTS[order.itemId].weight],
        location_index: locationIndex++,
        skills: PRODUCTS[order.itemId].skills
      });
      locations.push(transformPoint(store.location));
    }

    let vehicles = [];
    for (const vehicle of shortHaulTrucks[cityCode]) {
      vehicles.push({
        id: vehicle.id,
        skills: _.random() < 0.2 ? [] : [0],
        start_index: hubLocationIndex,
        capacity: [inputs.shortHaulTruckCapacity],
        end_index: hubLocationIndex,
        description: vehicle.description
      });
    }
const result = await MVRP({
      locations: {
        id: 1,
        location: locations.join("|")
      },
      jobs,
      vehicles
    });
    return {
      cityCode,
      ...result
    };
  }

  return Promise.all(
    _.map(
      DESTINATION_HUBS.map((hub) => hub.city),
      performMVRP
    )
  );
}
}

Select the city for which you want to visualize the short-haul trucking routes. 

For this notebook, we select the city of Austin.

 

Post the route optimization process, all 34 orders received at the Austin hub were successfully delivered, employing an efficient distribution strategy across 5 out of the available 10 trucks.

The following table summarizes the optimized route result for the short-haul trucks in Austin.

ID

Name

Has Lift Gate

Orders

0

Truck 0

Yes

10

1

Truck 1

No

5

2

Truck 2

Yes

6

3

Truck 3

Yes

5

4

Truck 4

No

8

5

Truck 5

Yes

0

6

Truck 6

Yes

0

7

Truck 7

Yes

0

8

Truck 8

No

0

9

Truck 9

No

0

Any of the trucks can be selected to visualize its individual route. The following image showcases the routes for all five trucks which are assigned orders. 

Volume LTL Shipping

This step finalizes the delivery process, optimizing routes for short-haul trucks to transport orders from destination hubs to individual stores efficiently. The result visualization provides insights into the distribution of orders among trucks and facilitates further analysis of the routing solution.