Creating a Real-Time Construction Fleet Planning App with TomTom Maps APIs
Victor Ikechukwu·Jan 13, 2022

Creating a Real-Time Construction Fleet Planning App with TomTom Maps APIs

Victor Ikechukwu
Jan 13, 2022 · 14 min read

Learn how to build a construction fleet planning application using TomTom’s Routing API with its Calculate Route method to calculate the possible routes, the Location History API to track a vehicle’s location, and the Geofencing API to determine when construction vehicles are close to the site.

This tutorial explores how we can seamlessly use the TomTom Maps APIs to solve our application’s location-related requirements. We’ll build a real-time tracking app to help users manage a construction company’s vehicle fleet.

We’ll see how to use:

  • The Routing API and its Calculate Route method to calculate the possible routes construction vehicles can take to sites based on specific parameters.

  • The Location History API to track the vehicle’s location.

  • The Geofencing API to determine when construction vehicles are close to the site so workers can prepare for their arrival.

We’ll discuss each app feature requirement, why the tools are relevant to the use-case we’re solving, and how to implement them. You just need to know some JavaScript, HTML, and CSS to follow along. We’ll guide you through using TomTom’s tools.

Let’s dive in!

##PREREQUISITES To start using TomTom Maps API, you need an API key. This key will authorize your request to use the service.

Don’t have one yet? Visit How to get a TomTom API key to learn more or sign up for a developer account to get one. Your free developer account provides access to thousands of daily requests and is acceptable for commercial use. Just pay-as-you-grow when your app becomes popular.

SETUP

First, we’ll need to initialize the map and its user interface (UI). The app will comprise two parts: a map on the right, and a sidebar with form controls on the left.

Create an index.html file in a folder on your computer. Then, open the file in a code editor and populate it with this code:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Construction Fleet Planning App</title>
        <!-- Includes SDK's stylesheet -->
        <link href="https://api.tomtom.com/maps-sdk-for-		web/cdn/6.x/6.15.0/maps/maps.css" rel="stylesheet" type="text/css" />
        <!-- Includes SDK's JavaScript-->
        <script src="https://api.tomtom.com/maps-sdk-for-web/cdn/6.x/6.15.0/maps/maps-web.min.js"></script>
        <script src="https://api.tomtom.com/maps-sdk-for-web/cdn/6.x/6.15.0/services/services-web.min.js"></script>
        <!-- CSS styles -->
<link rel="stylesheet" href="./styles.css" />

    </head>
    <body>
        <div class="container">
            <div class="control-panel">
                <h2 class="control-panel__title">Construction Fleet Planing App</h2>
                <form class="control-panel__controls">
                    <fieldset>
                        <label for="start-address" class="">Start Address:</label>
                        <input type="text" class="" placeholder="san diego" id="start-address" />
                    </fieldset>
                    <fieldset>
                        <label for="end-address" class="">End Address:</label>
                        <input type="text" class="" placeholder="lily avenue" id="end-address" />
                    </fieldset>
                    <fieldset>
                        <label for="arrival-time" class="">Estimated Arrival Time:</label>
                        <input type="datetime-local" id="arrival-time" />
                    </fieldset>
                    <fieldset>
                        <label for="explosives-present">Vehicle is carrying explosives:</label>
                        <input type="checkbox" id="explosives-present" value="USHazmatClass1" />
                    </fieldset>
                    <fieldset>
                        <label for="gas-present">Vehicle is carrying compressed gas:</label>
                        <input type="checkbox" id="gas-present" value="USHazmatClass2" />
                    </fieldset>
                    <fieldset>
                        <label for="is-truck">Vehicle is a truck:</label>
                        <input type="checkbox" id="is-truck" value="truck" />
                    </fieldset>
                    <button class="control-panel__btn">Track</button>
                </form>
            </div>
            <div id="map" class="map"></div>
        </div>
            </script>
        <script>
            const apiKey = "<Your_API_Key>";
            const SanJose = {lon: -121.867905, lat: 37.279518}
            const map = tt.map({
                key: apiKey,
                container: "map",
                center: SanJose,
                zoom: 10,
            });
            map.addControl(new tt.FullscreenControl());
            map.addControl(new tt.NavigationControl());
</script>

Here, the tt.map constructor creates a map. The container parameter requires the ID of an HTML element containing the map. The center parameter centers the map on San Jose, California, using its longitude and latitude coordinates.

Remember to replace <Your_API_Key> with your API key from your developer account.

Now, include your CSS styles in a styles.css file containing the following code. The top of the HTML file will link to this CSS file.

*,
*:before,
*:after {
  box-sizing: border-box;
  padding: 0;
  margin: 0;
}
body {
  color: #5f5f5f;
  font-size: 1.1rem;
}
.container {
  width: 100vw;
  height: 100vh;
  display: flex;
}
.control-panel {
  width: 30%;
  box-shadow: 1px 0px 12px 0px #00000067;
  height: 100%;
  overflow-x: hidden;
  overflow-y: auto;
}
.control-panel::-webkit-scrollbar {
  width: 0px;
  height: 0px;
}
.control-panel__title {
  font-size: 1.8rem;
  margin: 1rem 0;
  padding: 1rem;
  text-align: center;
  border-bottom: 3px solid #181616;
  box-shadow: 0px 3px 6px 0px #00000029;
}
.control-panel__controls {
  display: flex;
  flex-flow: column nowrap;
  align-items: center;
  justify-content: space-around;
}
.control-panel__controls fieldset {
  width: 95%;
  margin: 1.2rem 0;
  border: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
fieldset input {
  min-width: 58%;
  outline: none;
  font-size: 1rem;
  border: 2px solid #ccc;
  padding: 0.8rem;
  border-radius: 8px;
}
fieldset input:focus {
  border: 2px solid #007fff;
}
.control-panel__btn {
  padding: 1rem;
  font-size: 1.3rem;
  outline: none;
  border: none;
  border-radius: 8px;
  color: #fff;
  background: #007fff;
  width: 45%;
}
.map {
  width: 70%;
  height: 100%;
}
Photo 1

You should have a UI similar to the image above. With the setup complete, we can start implementing the app’s feature requirements.

CALCULATING THE ROUTE

Managers can’t identify the best routes construction vehicles should take to arrive and deliver materials on time by simply looking at the map. But early or late arrivals can ruin the materials or cause delays, so route information is vital.

TomTom’s Calculate Route service determines the possible routes between an origin and a destination location, based on specific parameters like the type of vehicle, estimated time of arrival, and the fastest route.

We can find the best routes for construction vehicles to reach the construction site using Calculate Route.

First, we add markers to the origin and destination points on the map, where the LngLat parameter is the longitude and latitude coordinates of these locations. We store each marker in the markers array. This information will come in handy when it’s time to call the calculate route service.

      //markers array
      const markers = [];
      // add marker
function addMarker(lnglat) {
        const marker = new tt.Marker().setLngLat(lnglat).addTo(map);
        markers.push(marker.getLngLat());
      }

Next, the app has two input fields to get the starting and destination addresses. The app then passes these addresses to the calculate route service. getAddress gets geographical coordinates for these addresses using the Fuzzy Search service, then passes the coordinates to the addMarker function.

// get the lnglat coordinates of an address
      async function getAddress(searchValue) {
        let address = tt.services
          .fuzzySearch({
            key: API_KEY,
            query: searchValue,
            lon:  -121.8863286,
            lat: 37.3382082,
          })
          .then((cordinates) => {
            // add marker
            addMarker(cordinates.results[0].position);
          const { lng, lat } = cordinates.results[0].position;
            return { lng, lat };
          });
return address;
      }

Now that our app has stored the geographical coordinates, we can call the calculate route service:

// get the values of the input fields for configuring the calculateRoute service
      const isTruck = document.getElementById("is-truck").checked
        ? document.getElementById("is-truck").value
        : null;
      const arrivalTime = document.getElementById("arrival-time").checked
        ? document.getElementById("arrival-time").value
        : null;
      const carriesExplosive = document.getElementById("explosives-present")
        .checked
        ? document.getElementById("explosives-present").value
        : null;
      const carriesGas = document.getElementById("gas-present").checked
        ? document.getElementById("gas-present").value
        : null;
 
    function getRoute(marker) {
        const routeOptions = {
          "key": API_KEY,
          "locations": marker,
          "arriveAt": arrivalTime,
          "travelMode": isTruck,
  "vehicleLoadType": [ carriesExplosive, carriesGas],
          "vehicleCommercial": true,
        };
 
        tt.services.calculateRoute(routeOptions).then(function (response) {
          console.log(response.toGeoJson());
          console.log(response);
          var geojson = response.toGeoJson();
          map.addLayer({
            "id": "route",
            "type": "line",
            "source": {
              "type": "geojson",
              "data": geojson,
            },
            "paint": {
              "line-color": "#4a90e2",
              "line-width": 8,
            },
          });
 
        var bounds = new tt.LngLatBounds();
        geojson.features[0].geometry.coordinates.forEach(function (point) {
        bounds.extend(tt.LngLat.convert(point)); // creates a bounding area
          });
          map.fitBounds(bounds, {
            duration: 200,
            padding: 50,
            maxZoom: 13,
          }); // zooms the map to the searched route
    });
  }

In the code above, the tt.services.calculateRoute constructor calls the calculates route service. The constructor receives the routeOptions object containing properties like the type of loads onboard, time of arrival, vehicle type, and the locations property containing the origin and destination LngLat coordinates from the markers array.

Then, the service returns a promise. We’ll tap into this promise to get its response and draw the route’s path on the map.

We need to tell our application to run the code above when the user clicks the Track button.

document.querySelector(".control-panel__btn")
        .addEventListener("click", function (e) {
          e.preventDefault();
          Promise.all([
            getAddress(document.getElementById("start-address").value),
            getAddress(document.getElementById("end-address").value),
          ])
             .then((points) => {     
              getRoute(points);
            })
            .then(() => {
              document.querySelectorAll("input").forEach((input) => {
                input.value = "";
                input.checked = false;
              });
            });

        });

See the result:

GIF 1

ENABLING LOCATION HISTORY

Construction managers need to know where their vehicles are and where they've been. This allows them to allocate assets efficiently and avoid idle equipment. TomTom’s Location History API enables us to store, retrieve, and track device location and movements.

To use the service, you need an admin key and a layer of security to complete API requests to the service.

You can generate an admin key using Postman to send a POST request to https://api.tomtom.com/location/1/register?&key=Your_API_Key with the following body:

{
“secret”: "your_secret",
}

The body of the request takes a secret parameter. This secret is a password you’ll need for making any future configurations, so use a password you can remember and ensure you don't lose it.

A successful response logs your admin key to the console:

{
adminKey: your_admin_key
}

CREATING AN OBJECT

Now, we can create an object representing the item we are tracking, such as a worker, phone, or vehicle.

To create an object, first send a post request to https://api.tomtom.com/locationHistory/1/objects/object?adminKey=Your_Admin_Key&key=Your_API_KEY, with the name of the object in the body:

{
 "name": name,
}
Photo 2

Here’s a link to the request on Postman if you’d like to try it out: Creating an object

The response will contain the object name and ID.

WORKING WITH AN OBJECT’S LOCATION

We can track an object’s location by storing and retrieving its positions from the service to know where it is and where it’s been.

To record an object’s location, you must give the service permission to store an object's position. So, send a Post request to https://api.tomtom.com/locationHistory/1/settings?key=Your_API_Key&adminKey=Your_Admin_Key with a body of:

{
  consentForStoringObjectsPositionsHistory: true,
}

This call is a one-time request enabling storing an object’s location. Now, we can start tracking objects.

To track an object’s location, first, we need to start storing its changing positions as past positions, using the Send position endpoint:

// send object's location    
function sendPostion(lnglat) {
        let cordinates = lnglat;
        // create Json Object to store location
        let position = {
          type: "Feature",
          geometry: {
            type: "Point",
            coordinates: [cordinates.lng, cordinates.lat, 0],
          },
          object: "Your_Object_ID"
        };
        // Use fetch to send the location
        fetch(
          "https://api.tomtom.com/locationHistory/1/history/positions?key=Your_Admin_Key ",
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify(position),
          }
        ).then((result) => {
          console.log(result);
        });
      }

Now, we can get its past positions for tracking using the get objects position service:

// get location history
      function getObjectLocations() {
const date = new Date().toISOString();
       fetch(
          "https://api.tomtom.com/locationHistory/1/history/positions/Your_Object_Id?key=Your_API_Key&adminKey=Your_AdminKey&from="+date+""
        )
          .then((response) => response.json())
          .then((result) => {
            const lngLatArray = result.positions.features.map(
              (feature) => feature.geometry.coordinates
            );
            console.log(lngLatArray);=
            lngLatArray.forEach((lngLat) => {
              addMarker(lngLat);
            });
          });
      }
 
 document.querySelector(".show-history").addEventListener("click", (e) => {
        e.preventDefault();
        getObjectLocations();
   });

In this code, we add a marker to the map on page load. Every time the marker drags across the map, the code calls the sendPostion function to send the marker’s LngLat position to the location history API.

Then, when the user clicks the show-history button, the app calls the getObjectLocations function to get the object’s position history and render a marker on each of its previous positions.

GIF 2

ENABLING GEOFENCING

TomTom's Geofencing service lets you define and manage virtual borders (geofences) around specified locations. When we combine this geofencing with the Location History API, we can determine an object’s location relative to these virtual barriers. We can then know whether they’re located within, outside, or close to a geofenced area.

The app can notify managers when a vehicle is approaching a geofenced site. This notification enables the managers to ensure the site is ready to receive the vehicle’s contents, such as having workers ready to unload construction materials or use the concrete before solidification.

Just like Location History, Geofencing requires an admin key when accessing the service. However, you can only register one admin key per account, so we’ll use the same admin key we registered with the Location History API.

CREATING A PROJECT

Now that we have our admin key ready, we need to create a project. A project is an object that stores our geofences. We can store hundreds of geofences in a single project.

First, we send a post request to the endpoint to create a project:

https://api.tomtom.com/geofencing/1/projects/project?adminKey<ADMIN_KEY>=&key=<API_KEY>

We include the name of the project as a parameter in the body:

{
  "name": "Project_Name"
}
Photo 3

Here’s a link to the request on Postman if you’d like to try it out: Creating a Project

A successful response logs the project’s ID and name to the console.

CREATING A GEOFENCE

Now that we have created the project, we are ready to start creating geofences. We make a geofence around the construction site to track construction vehicle arrival.

To create a fence, we send a post request to the following:

https://api.tomtom.com/geofencing/1/projects/${projectId}/fence?adminKey=Your_Admin_Key&key=Your_API_Key

Remember to replace {projectId} in the request URL with the project ID you just created.

The request body should contain the following:

 {
    "name": "Area 1",
    "type": "Feature",
    "geometry": {
        "type": "Point",
        "radius": 11361.72744452612,
        "shapeType": "Circle",
        "coordinates": [
            -121.892115,
            37.331016
        ]
    }
}

The code above contains the details needed to create a fence around a location. The coordinates parameter takes the location’s latitude and longitude, and the shapeType parameter defines the fence’s shape (in this case, a circle). The response logs the request object above to the console, including the provided fence ID.

Now, we can display the geofence on our map:

function displayFence() {
        fetch("https://api.tomtom.com/geofencing/1/fences/${fence id}?key=Your_API_Key")
          .then((response) => response.json())
          .then((result) => {
            console.log(result);
            map.addLayer({
              id: "san jose",
              type: "fill",
              source: {
                type: "geojson",
                data: result,
              },
              paint: {
                "fill-color": "blue",
                "fill-opacity": 0.4,
                "fill-outline-color": "blue",
              },
            });
          });
      }
 
      map.on("load", () => {
        displayFence();
      });

The code above calls the displayFence function when the map loads. This function displays the fence by fetching the fence details and drawing the shape on the map.

Remember to replace ${fence id} with your fence ID. The fence should now be visible on the map.

Photo 4

CREATING A TRANSITION ALERT RULE

TomTom’s Geofencing API provides an alert service to notify your app when assets enter or leave geofences. You can use the same object with the Geofencing and Location History APIs interchangeably.

First, you need to create a contact group. The contact group contains subscribed email addresses that will receive notifications from the service.

To set up a contact group, first, send a post request to https://api.tomtom/notifications/groups?key=Your_API_Key with the request body:

{
"name": "group_name",
"webhookUrls" : [],
"emails": []
}

The emails and webhooks parameters above will contain a list of URLs and email addresses to receive the messages.

A successful response logs the notification group ID to the console.

Next, send a post request to https://api.tomtom.com/geofencing/1/alerts/rules?key=Your_Admin_Key adminKey=Your_Admin_Key with the body:

{
  "name": "Name of alert rule",
  "project": "Enter id of project to watch or *",
  "fence": "Id of fence to watch or *",
  "object": "id of object to watch or *",
  "alertType": "TRANSITION",
  "alertRuleConstraints": {
    "transitionType": "ALL"
  },
  "notificationGroup": "id of your notification group",
  "enabled": true
}

Now the app will notify the email address and URLs whenever an object enters the fence.

NEXT STEPS

Our construction company users now have a handy app to plan fleet routes, track equipment locations, and receive an alert when a vehicle crosses a geofence and is on its way to the construction site. With all this information, the fleet manager can make the most efficient use of their equipment, ensure materials arrive when expected, and get workers ready to receive the materials — a boost to construction fleet efficiency with TomTom doing most of the heavy lifting.

From here, you can improve the app by including multiple construction sites, sorting equipment by vehicle type, adding authentication to restrict access to managers only, and much more.

Learn more about integrating TomTom’s Maps APIs into your applications to manage your fleets, locate vehicles, and track assets on a map in real-time. Visit the TomTom developer portal and sign up for a free developer account today. Happy mapping!