Sorry, you need to enable JavaScript to visit this website.

Creating an App for Efficient and Convenient Delivery Services

In this article we'll look at the details of how you could create a real-world delivery management application based on TomTom technology.


Food and grocery delivery has become an important convenience to the busy lives people lead. But the demand for quality has never been higher. Customers want their food hot, and companies need to maximize their drivers' efficiency.

TomTom enables a business to optimize its delivery process by providing:

  • Precise location of each delivery address through geocoding, including exact coordinates and information on various entry points.
  • Reverse geocoding for tracking and understanding driver behavior, as well as tracking deliveries en-route.
  • The most efficient route using all available, including traffic information.
  • An accurate estimate of arrival time at each waypoint along the route.
  • Geofencing alerts that allow the app to react to events such as when a driver enters an area, when a driver exits an area, when they have stayed within the area for too long.

Sometimes the most reliable route is the route with the best estimated time of arrival (ETA) — the "fastest" route. Sometimes even if the route looks short, there may be more variability that can cause the ETA to deviate. Customer satisfaction related to travel time is a combination of meeting the ETAs and the actual travel time duration. For instance, in the case of driving to the airport, an accurate ETA is more important than the risk of delay due to a shorter but more variable route. TomTom’s routing algorithm takes all these into consideration.

This article will explore how you could create a real-world delivery management application using TomTom technology.

Location Functions to Drive a Delivery Business

Our delivery app needs to support the following functions:

Geocoding

Geocoding is one operation that will be used repeatedly. It translates an address to a GPS coordinate. 


The Geocode endpoint of the Search API is very tolerant of typos and incomplete information. For example, using the query “Times Square”, these are the top three results ordered by a confidence score are:

  1. Times Square NY, as a neighborhood
  2. Times Square, Friendship, NY
  3. Times Square, Harlington, TX

There are many properties available to narrow down the search using parameters such as coordinates, bounds, country sets and so on. You can run some tests using the handy API Explorer.

Alongside the coordinate of the building self is a set of entry points, indicating the positions of possible entrance ways.

For example, there are two listed entry points for the Moscone Center (747 Howard St, San Francisco).

"entryPoints": [
{
    "type": "main",
    "position": {
        "lat": 37.78424,
        "lon": -122.40147
        }
 },
 {
     "type": "minor",
         "position": {
             "lat": 37.78417,
             "lon": -122.40156
          }
 }]

Using the coordinate, we can then perform the next set of functions.

Routing

Using the GPS coordinates of where the driver is, the pickup location, and the customer location, we are able to calculate the most efficient route using all available information.

TomTom's routing algorithm provides several types of routing including but not limited to: fastest route, shortest route, and economic route.

Note the distinction between fastest and shortest route. Even though one route is shorter, it could have a speed limit of 60kph, but the longer path includes a section on a motorway with a speed limit of 100kph. By default, the TomTom Routing API would choose the fastest route available. 

Better yet, TomTom is able to optimize travel time using its industry-leading traffic data for immediate departure or through analysis of historical patterns using its extensive historical traffic data for future departure. 

Using the routing information, we can determine when a driver needs to leave to pick up the order and when a customer can expect arrival.

Search

TomTom has a massive database of Point of Interests (POIs), including restaurants, grocery stores, parking lots, gas stations and charging stations for electric vehicles just to name a few. You can even get opening and closing times for businesses.

With a given coordinate, we can find such POIs near the destination using Nearby Search.

With a calculated route, we can even find POIs along the route using a maximum allowable detour time via the Along Route Search API.

Geofencing and Notifications

Geofences allow us to monitor an area and respond to events such as when a driver is within range, when they leave the area, and when they have dwelled within the area for a certain amount of time.

The Geofences Creator is an application that enables you to easily create and edit geofences interactively, right in a browser, using the TomTom Geofencing API.

Used in conjunction with the notification API, we could send out an alert through emails or web hook urls when a driver crosses the fence, notifying the customer to get ready for pick up.

We could also send “dwell” alerts if a driver stays too long within a geofence. This matters to businesses and consumers because it enables follow up if there are any issues — the driver can’t find the entrance, the driver is waiting for the delivery, and so on.

Getting Started Building a Delivery App

Before we begin, you will need an API key. You can obtain one by signing up for a free TomTom developer account.

The focus of this article is on the implementations of the following endpoints:

  • POST {appUrl}/order/accept/{driverId}/{orderId} – A driver looks at an order and decides they can take it on
  • POST {appUrl}/driver/{driverId}/position?lat={lat}&lng={lng} – A driver reporting their current location

The code in the rest of this article was created for an Express.js server, but the core logic is contained in a single, which can be ported elsewhere including a client application.

You can also find the latest TomTom Maps SDK for Web on CDN as a minified JS file, or a downloadable zip package. 

The SDK can be installed via npm:

npm i @tomtom-international/web-sdk-services

Since we are implementing a server in node.js, we would like to use the node version in our file:

import * as tt from '@tomtom-international/web-sdk-services/dist/services-node.min.js'

Geocoding Pickup and Delivery Addresses

Geocoding is an important operation in our app. By translating the pickup and delivery address into coordinates, we can then determine the best route for the driver, create geofences, and find POIs along the way.

Here is the full implementation of the getGeoCode call, which we use as a utility function throughout the app:

async function getGeoCode(query){
    return await tt.services.geocode({
        key: key,
        query: query}).go().catch((e)=>{console.error(e)};
}

We do not need to narrow the search any further as we would have the full addresses of both the pickup location and the delivery location.

Now we can move on to the next step: when a driver clicks on an order and accepts the job.

Accepting an Order

In our scenario, driver one will go to the pickup location and deliver it to a customer at the destination.

The order will be ready for pick up in 30 minutes.

Let's start with calculating the estimated time from the driver's current location to the pick-up location.

image1

Since we know when the pickup will be ready, we can also calculate the time it takes to go to the pickup location to the final destination where the customer is.

const pickup = await getGeoCode(order.pickup.address)
const customer = await getGeoCode(order.destination.address)

const pickupLocation = pickup.results[0].position
const deliveryLocation = customer.results[0].position

To calculate the route and obtain an estimate, build up a request like so:

const path = {locations:`${driver.location.lng},${driver.location.lat}:${pickupLocation.lng},${pickupLocation.lat}`}

const eta = await tt.services.calculateRoute({
    key: key,,
    locations: path
})
.go()
.catch((e)=>{console.log(e)});

Making the same call for the two trips seems inefficient, we can batch them together.

By combining the request into an array, and setting batch mode to "sync", you can expect an array of responses back:

const eta = await tt.services.calculateRoute({
    key: key,
    batchMode: 'sync',
    batchItems: [path, toDestination]})
.go()
.catch((e)=>{console.log(e)});

The complete version of the function:

async function doConfirmOrder(driver, order) {
    const pickup = await getGeoCode(order.pickup.address);
    const customer = await getGeoCode(order.destination.address);

    const pickupLocation = pickup.results[0].position;
    const deliveryLocation = customer.results[0].position;
    
    // To be explained in the next section
    await createGeofence(order, driver, deliveryLocation);

    const path = { locations: `${driver.location.lng},${driver.location.lat}:${pickupLocation.lng},${pickupLocation.lat}` };

    const toDestination = {
        departAt: `${order.pickup.readyTime}`, 
        locations: `${pickupLocation.lng},${pickupLocation.lat}:${deliveryLocation.lng},${deliveryLocation.lat}`
};

    const eta = await tt.services.calculateRoute({
        key: key, 
        batchMode: 'sync',
        batchItems: [path, toDestination]
    })
    .go()
    .catch((e) => { console.log(e); });

    return { pickup: eta[0].toGeoJson(), dropOff: eta[1].toGeoJson() };
}

Bonus: Help a driver find parking.

export async function getParkingNear(lng, lat){

    const parking = await tt.services.nearbySearch({
        key:key,
        // 7369 is the category for open parking area
        categorySet: '7369',
        center: [lng, lat],
        // find parking within a 300 meter radius
        radius: 300}).go();

    return parking;
}

Geofencing and Alerts

To set up geofences and alerts, we are going to create a project for the order, create a geofence for the project, then a geofencing object that represents the driver.

First, we need to generate an admin key for geofences by making a POST call to:

https://api.tomtom.com/geofencing/1/register

You will need to provide a secret in the body of the request

curl -XPOST "Content-type: application/json" -d
'{
   "secret": "your_secret"
 }'

'https://api.tomtom.com/geofencing/1/register?key=Your_API_Key'

This key will be used in conjunction with the API key when making calls to the geofencing APIs.

Next, we need to give TomTom permission to record object transitions. These calls only need to be made once. I have included it in the sample, but you only need to make that call once through curl or Postman as outlined here, or through JavaScript:

export async function consent(){

    const requestUrl = (baseUrl) =>
        { return  `${baseUrl}?key=${key}&adminKey=${adminKey}`};

    const geofencingUrl = "https://api.tomtom.com/geofencing/1/settings";

    const geofencingConsent = {

        consentForStoringTransitionsHistory: true,

        consentForStoringObjectsPositionsHistory: true};

    await fetch(requestUrl(geofencingUrl), makePostJsonRequest(geofencingConsent));
}

Here are the methods for each of the operations. First, the utility method for building API requests:

const requestOptions = (body) => {
    return { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(body);}

const baseUrl = "https://api.tomtom.com/geofencing/1";
const keyPhrase =`key=${key}&adminKey=${adminKey}`;

Creating a project:

async function createProject() {
    const projectUrl = `${baseUrl}/projects/project?${keyPhrase}`;
    const projectResponse = await fetch(projectUrl, requestOptions({ name: order.orderId.toString() }));
    const project = await projectResponse.json();

    return project;
}

Creating a geofence:

async function createFence(projectId, identifier, position) {
    const fenceUrl = `${baseUrl}/projects/${project.id}/fence?${keyPhrase}`;
    const fence = {
       name: `fence_${identifier}`,
       type: 'Feature',
       geometry: {
           radius: 1000,
           type: 'Point',
           shapeType: 'Circle',
           coordinates: [position.lng, position.lat]
    };
}

Creating an object for the project:

const objectUrl = `${baseUrl}/objects/object?${keyPhrase}`
const objectConfig = {name: `driver_${driver.driverId}`, defaultProject: project.id.toString()}
const constructedObject = await (await fetch(objectUrl, requestOptions(objectConfig))).json()

Now that we have a geofence. Let’s add an alert rule so when a driver enters the area, we can send an email to the customer.

Let us first create a notification group for a customer.

async function getNotificationGroup() {
    const url = `https://api.tomtom.com/notifications/1/groups?key=${key}`
    const customerContact = getCustomerContact(order.orderId)

    if (customerContact.notificationGroup){
        return {id: customerContact.notificationGroup};
    }

    // if customer does not already have a notification group, 

    //execute the following,       else
    const notificationResponse = await fetch(url, requestOptions({
        name: customerContact.name,
        // if web hooks are available, we can use webhooks
        // webhookUrls:[]
        emails: [customerContact.email]}));

    const notification = await notificationResponse.json();
    return notification;
}

With a notification group, we can create an alert that connects the object (driver), the geofence, and the customer.

When the object (driver) is in the area (entering the geofence), an alert will trigger and notify the customer (via the notification group).

Note there is a different set of alertRuleConstraints for each of the alert types. For TRANSITION alerts, transitionType is required.

const alertConfig = {
    // create a unique name for the alert
    name: `alert_${objectConfig.name}_t`,
    project: project.id,
    fence: constructedFence.id,
    alertType: 'TRANSITION',
    alertRuleConstraints:{ transitionType: "ENTER" },
    object: constructedObject.id,
    notificationGroup: notificationGroup.id.toString()}

To create a dwelling alert, simply replace alertType and alertRuleConstraints like so:

alertConfig.name = `alert_${objectConfig.name}_d`;
alertConfig.alertType = 'DWELL';

// 60 seconds * 5 = 5 minutes
alertConfig.alertRuleConstraints = { maxDwellTime: 300}
const dwellingAlert = await (await fetch(alertUrl, requestOptions(alertConfig))).json()

This will send out an alert if the driver stays within the fence for more than five minutes.

Now we have two alerts set up for the geofence. All we need is someone to trigger it. We still need a way for a driver to report their GPS locations to trigger these alerts.

export async function reportPosition(driverId, lng, lat){
    const baseUrl = "https://api.tomtom.com/geofencing/1/report";
    const point = `${lng},${lat}`;

    const objectId = getDriver(driverId).objectId;
    const url = `${baseUrl}?point=${point}&object=${objectId.toString()}&key=${key}`;

    await fetch(url, requestOptions(null));
}

Next Steps

We have explored how to create an API endpoint to provide routing information for a delivery service using TomTom Map APIs.

Using a combination of the search, routing and geofencing API. We can provide timely route information to both the driver and customer.

Its routing API calculates the best route using both historical and live traffic data, providing reliable estimates while the powerful Geofencing API allows an app to respond to entry, exits, and dwelling events through alert rules.

There are still a few things left that we can do to improve this app.

For example, you can use the Geofence API to get a number of objects within a geofence or the number of transitions that have happened for a given object to monitor the current status of the object (driver).

There are opportunities to cache the project and geofence information to improve the performance of the app.

There are many parameters available for each of the API calls. Check out the documentation and the API Explorers at TomTom Developers Site.

Want to get started? Sign up for a free Developer account.

First published: 
Monday, November 16, 2020 - 18:45
Last edited: 
Wednesday, November 18, 2020 - 19:44