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

Taxi dispatcher

Intro

This tutorial shows how to create a web application for a taxi company using the TomTom Maps SDK for web.

It's not easy to identify which taxi can get the fastest to the customer by just looking at the map. Using TomTom Routing API you can obtain the travel time of each individual taxi to a particular point, in this case, a passenger. In this application you will use this service so that the dispatcher will pick the taxi that reaches the customer fastest.

In this tutorial you will create a map with markers pointing at taxis and passenger's locations. The application calculates each route and chooses the fastest one.

Prerequisites:

To start using the TomTom Maps SDK for Web, you need the following:

  • An SDK that can be downloaded from here: Downloads. Unzip it and save it in a folder. This tutorial assumes your folder is named ‘sdk’. If you choose another name, remember to change the provided paths to match.

    Check out the current file structure

    .
    └── sdk-tutorial
        └── sdk
            ├── LICENSE.txt
            ├── README.md
            ├── glyphs
            ├── images
            ├── map.css
            ├── mapbox-gl-js
            ├── sprites
            ├── styles
            ├── tomtom.min.js
            └── tomtom.min.js.map
        
  • API key

    Create your API key now

    To create a new API key you need to first register on the portal. Once you are logged in there are only two steps remaining:

    Create a new application in your Dashboard:

    Choose all the APIs:

    All set up. You can now click on your newly created app and copy your API key:

Map initialization

To display a TomTom map use the code shown below. You can just copy and paste it to your favorite code editor. Infobox: Place your HTML file in the same directory as the sdk folder that contains SDK files.

<!DOCTYPE html>
<html>
  <head> 
    <title>Taxi dispatcher</title> 
    <meta charset="UTF-8"> 
    <link rel="stylesheet" type="text/css" href="sdk/map.css"/> 
    <script src="sdk/tomtom.min.js"></script> 
    <style> 
      body {
        margin: 0;
      }
      #map { 
        height: 100vh; 
        width: 100vw; 
      } 
    </style> 
  </head> 
  <body> 
    <div id="map"> 
    </div>
    <script> 
      var map = tomtom.L.map('map', {
          key: '<your-api-key>'
      }); 
    </script> 
  </body> 
</html> 

Note that the first parameter of tomtom.L.map constructor requires the same value as the id of a div HTML element where the map will be embedded. Replace placeholder with your API key.

The custom markers used for this tutorial, you can find on GitHub.

Passenger

Add a passenger marker to the map.

To add the passenger marker into the map,first you need to add variables to the JavaScript section: passengerInitCoordinates, passengerIcon and passengerMarker. The passengerInitCoordinates variable needs to be declared before the map initialization.

   var passengerInitCoordinates = [52.360306, 4.876935];
    var passengerIcon = tomtom.L.icon({
      iconUrl: 'img/man-waving-arm_32.png',
      iconSize: [30, 30],
      iconAnchor: [15, 30],
      popupAnchor: [0, -30]
    });
    var passengerMarker;

To center the map on the passenger, replace the map definition like the one in the following example:

      var map = tomtom.L.map('map', {
          key: '<your-api-key>',
          center: passengerInitCoordinates,
          zoom: 14
      }); 

Ensure that the placeholder has been replaced by your API key.

The code below puts the passenger marker on the map.

    passengerMarker = tomtom.L.marker(passengerInitCoordinates, {
      icon: passengerIcon
    }).addTo(map);

Change the passenger marker position.

Now you can add a feature that allows users to click on the map to move a passenger. To do so, add an event listener on map click. In the handler function, call the reverseGeocode method with a position parameter from the event’s property latlng. In the then method define a callback that executes a drawPassengerMarkerOnMap function.

The coordinates provided from the API are wrapped using a convertToLatLon function.

    function convertToLatLon(coordinateString) {
      var result = coordinateString.split(',');
      return {
        lat: parseFloat(result[0]),
        lng: parseFloat(result[1])
      };
    }

Add a conditional statement in the drawPassengerMarkerOnMap function: if the reverse geocoding response contains an address, then a marker with the previous position is removed and a new one is created at the indicated location.

    function drawPassengerMarkerOnMap(geoResponse) { 
      if (geoResponse && geoResponse.address && geoResponse.address.freeformAddress) { 
        map.removeLayer(passengerMarker); 
        var popupContent = geoResponse.address.freeformAddress; 
        passengerMarker = tomtom.L.marker(convertToLatLon(geoResponse.position), { 
          icon: passengerIcon 
        }).addTo(map).bindPopup(popupContent).openPopup(); 
      } 
    } 

Now you can add an event that draw the passenger marker each time you click on the map area.

    map.on('click', function(event) { 
      var position = event.latlng; 
      tomtom.reverseGeocode().position(position).go().then(function(results) {drawPassengerMarkerOnMap(results)}); 
    }); 

Taxi cabs

Add taxi markers definition, the coordinates for taxi cabs are fixed for simplicity.

    var taxiCoordinates = [ [ 52.373627, 4.902642 ], [ 52.3659, 4.927198 ], [ 52.347878, 4.893488 ], [ 52.349447, 4.858433 ] ]; 
    var carIcons = [ tomtom.L.icon({ 
      iconUrl: 'img/cab1.png', 
      iconSize: [ 55, 55 ] 
    }), tomtom.L.icon({ 
      iconUrl: 'img/cab2.png', 
      iconSize: [ 55, 55 ] 
    }), tomtom.L.icon({ 
      iconUrl: 'img/cab3.png', 
      iconSize: [ 55, 55 ] 
    }), tomtom.L.icon({ 
      iconUrl: 'img/cab4.png', 
      iconSize: [ 55, 55 ] 
    }) ]; 

Next, add the config where the route colors for individual taxis are saved.

    function setInitConfig() {
      config = [
        {
          name: 'CAR #1',
          color: '#00e2ff'
        },
        {
          name: 'CAR #2',
          color: '#0c95e8'
        },
        {
          name: 'CAR #3',
          color: '#0d64ff'
        },
        {
          name: 'CAR #4',
          color: '#0ce8c5'
        }
      ];
    }

Finally, add the taxi cabs into the map and an initialization of the setInitConfig function.

    taxiCoordinates.forEach(function (child, index) {
      tomtom.L.marker(child, {
        icon: carIcons[index]
      }).addTo(map);
    });

    setInitConfig();

Submit button

To add the submit button the map div tag needs to be updated, with the following code:

    <div id="map">
      <div id="labels-container">
        <label>Find the taxi that will arrive fastest</label>
        <div id="route-labels"></div>
        <input type="button" id="submit-button" value="Submit">
      </div>
    </div>

To see the button on the map, you need to add controlPanel.

    tomtom.controlPanel({
      position: 'topright',
      collapsed: false,
      close: null,
      closeOnMapClick: false
    }).addTo(map).addContent(document.getElementById('labels-container'));

Clear routes

To clear routes we need first declare a variable.

var routes = [];

The clear function removes routes from the map, clears the routes array, and calls setInitConfig.

    function clear() {
      routes.forEach(function (child) {
        child[0].remove();
        child[1].remove();
      });
      routes = [];
      setInitConfig();
      passengerMarker.closePopup();
    }

Next, the clear function can be embedded into submitButtonHandler.

    function submitButtonHandler() {
      clear();
    }

Don’t forget to assigned the submitButtonHandler listener to the button in the HTML document.

    document.getElementById('submit-button').addEventListener('click', submitButtonHandler);

Now you can go to the next chapter, where the clear function can be used.

Draw routes

The variables: routeBackgroundWeight and routeWeight need to be initialized like in the following example.

    var routeBackgroundWeight = 12;
    var routeWeight = 9;

The drawRoute function uses the routing method to get a JSON object with that route’s geometry coordinates. The JSON is then used to create a geoJson object to add to the map.

    function drawRoute(locations, color, index) {
      tomtom.routing().locations(locations).go().then(function (routeJson) {
        var route = [];
        route[0] = tomtom.L.geoJson(routeJson, {
          style: {
            color: 'black',
            weight: routeBackgroundWeight
          }
        }).addTo(map);
        route[1] = tomtom.L.geoJson(routeJson, {
          style: {
            color: color,
            weight: routeWeight
          }
        }).addTo(map);
        routes[index] = route;
      });
    }

The route is drawn twice to create an outline:

  1. route[0] in black, with a thick stroke.
  2. route[1] using the color from config and a normal stroke.

Add a drawAllRoutes function that picks each car position from the taxiCoordinates array and executes the drawRoute function to draw a route between that car and a passenger.

    function drawAllRoutes() {
      taxiCoordinates.forEach(function (child, index) {
        drawRoute(child + ':' + passengerMarker.getLatLng().lat + ','
          + passengerMarker.getLatLng().lng, config[index].color, index);
      });
    }

Add a processMatrixResponse function. It extracts 3 elements from each response and uses them to populate 3 separate arrays:

  • travel times
  • route lengths
  • traffic delays
    function processMatrixResponse(result) {
      var travelTimeInSecondsArray = [];
      var lengthInMetersArray = [];
      var trafficDelayInSecondsArray = [];
      result.forEach(function (child) {
        travelTimeInSecondsArray.push(child[0].routeSummary.travelTimeInSeconds);
        lengthInMetersArray.push(child[0].routeSummary.lengthInMeters);
        trafficDelayInSecondsArray.push(child[0].routeSummary.trafficDelayInSeconds);
      });
      drawAllRoutes();
    }

Next, add the functions: convertToPoint, buildDestinationsParameter and buildOriginsParameter.

    function convertToPoint(lat, long) {
      return {
        point: {
          latitude: lat,
          longitude: long
        }
      };
    }

    function buildDestinationsParameter() {
      return [convertToPoint(passengerMarker.getLatLng().lat, passengerMarker.getLatLng().lng)];
    }

    function buildOriginsParameter() {
      var origins = [];
      taxiCoordinates.forEach(function (child) {
        origins.push(convertToPoint(child[0], child[1]));
      });
      return origins;
    }

The callMatrix function uses a matrixRouting method to create a request for the Matrix Routing API. The go method then calls the API asynchronously, to allow for service processing time. Use the then function to define a callback that processes the response, including travelTime in seconds and distance in meters.

To deal with the volume of route plans that can be required, this implementation uses the Matrix Routing service rather than individual calls to the Routing API. In a simple implementation with one passenger and four taxis, the service would calculate a small matrix of 4 x 1 routes. But in a real life application, there can be many passengers requesting a taxi at the same time and many available taxis. A scenario with 3 passengers and 20 taxis creates a matrix of 20 x 3 = 60 routes. For more information on the API, see Matrix Routing.

    function callMatrix() {
      tomtom.matrixRouting().departAt('now')
        .origins(buildOriginsParameter())
        .destinations(buildDestinationsParameter())
        .go().then(processMatrixResponse);
    }

Finally, you can add the callMatrix function call and update submitButtonHandler.

    function submitButtonHandler() {
      clear();
      callMatrix();
    }

And the winner is …

Create a modal to inform the user which route is the fastest one. The modal appears when all calculation is done. Put a div with modal id in the body tag, bellow the map div tag.

    <div id="modal">
      <div id="modal-content"></div>
    </div>

Add the CSS for the modal to your style section:

    #modal {
        display: none;
        position: fixed;
        z-index: 1100;
        left: 0;
        top: 0;
        width: 100%;
        height: 100%;
        overflow: auto;
        background-color: rgba(0,0,0,0.5);
      }
      #modal-content {
        background-color: lightgray;
        color: #555;
        font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
        font-weight: bold;
        text-align: center;
        margin: 15% auto;
        padding: 20px;
        border: 1px solid #888;
        width: 20%;
      }

Next, add the necessary variables.

    var modal = document.getElementById('modal');
    var modalContent = document.getElementById('modal-content');
    var fastestRouteColor = '#0dff94';
    var bestRouteIndex;

To display and remove modal from screen, add the bellow code:

    modal.addEventListener('click', function () {
      modal.style.display = 'none';
    });
    function displayModal() {
      modalContent.innerText = 'Dispatch car number ' + String(bestRouteIndex + 1);
      modal.style.display = 'block';
    }

The user can click anywhere to hide the modal.

Now Is a time to choose the winner in a modifyFastestRouteColor function. If it is the fastest route, it is displayed above all other routes. The function copies the array travelTimeInSecondsArray to a new sortedTab array and sorts it. Because the default comparison function is alphabetic (meaning that '11' is less than '2'), the function uses a custom comparison function. The one below takes two values (such as 7 and 9) and subtracts one from the other. If the result is negative (-2), the function sorts 7 as lower than 9.

    function modifyFastestRouteColor(travelTimeInSecondsArray) {
      var sortedTab = travelTimeInSecondsArray.slice();
      sortedTab.sort(function(a, b) {return a - b});
      bestRouteIndex = travelTimeInSecondsArray.indexOf(sortedTab[0]);
      config[bestRouteIndex].color = fastestRouteColor;
    }

What's left is updating the processMatrixResponse, like the following code:

    function processMatrixResponse(result) {
      var travelTimeInSecondsArray = [];
      var lengthInMetersArray = [];
      var trafficDelayInSecondsArray = [];
      result.forEach(function (child) {
        travelTimeInSecondsArray.push(child[0].routeSummary.travelTimeInSeconds);
        lengthInMetersArray.push(child[0].routeSummary.lengthInMeters);
        trafficDelayInSecondsArray.push(child[0].routeSummary.trafficDelayInSeconds);
      });
      modifyFastestRouteColor(travelTimeInSecondsArray);
      drawAllRoutes();
      displayModal();
    }

The full application, including additional changes and improvements, is visible below.

Summary

This tutorial has demonstrated:

  1. How to do a Matrix Routing query.
  2. How to draw a route on the map.
  3. How to work with a route by setting event listeners, setting a style, and bringing the route to the front or back.

All the source code of that application can be found on GitHub.