Search for content

A-B Routing

A-B Routing

In this tutorial you will learn how a basic Routing App is implemented. It will allow to display a map, enter addresses for start and destination and find the optimal route between these waypoints. To concentrate on the basic principles, all other important features like error treatment or type ahead address search are neglected. The aim of the code examples is to illustrate the use of the PTV Developer Routing API

Try it! Download from GitHub

Prerequisites

  • Basic JavaScript knowledge.
  • Basic knowledge of JavaScript asynchronous programming using Promises.
  • Helpful: Basic knowledge of jQuery. A page like the W3 Schools jQuery tutorial is sufficient.

How to use this tutorial

The tutorial will start similar to a cooking recipe. After the basic steps how to create the app are explained, the tutorial will focus on the major building blocks.

Getting started

Create an HTML page to display a map

Create a new HTML file called index.html and initialize a Leaflet map using the code below.

 

<html>
  <head>
    <title>Basic Routing Tutorial</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" />
    <link rel="stylesheet" href="style.css" />
    <script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="basicRoutingDemo.js"></script>
    </head>
      <body>
          <!-- The control elements will be added later here -->
          <p>Travel Time: <span id="travelResult"></span></p>
          <div style="width: 100%; height: 600px;" id="map"></div>
      </body>
</html>

 

JavaScript code

Create a new JavaScript file called basicRoutingDemo.js. Add the code below and replace the string "YOUR_API_KEY" with your PTV Developer API key. After loading the index.html file into a web browser, you should see a panable and zoomable map.

$(document).ready(function() {
    const api_key = "YOUR_API_KEY";
    const tileURL = `https://api.myptv.com/rastermaps/v1/image-tiles/{z}/{x}/{y}?apiKey=${api_key}`;
    const geoURL = "https://api.myptv.com/geocoding/v1/locations/by-text";
    const routingURL = "https://api.myptv.com/routing/v1/routes";
    const coordinates = L.latLng(49.0, 8.4);

    var startPosition = null;
    var destinationPosition = null;

    var routingURLObj = new URL(routingURL);
    var geoURLObj = new URL(geoURL);

    var map = new L.Map('map', {
        center: coordinates,
        zoom: 13,
        zoomControl: false
    });

    L.control.zoom({
        position: 'bottomright'
    }).addTo(map);

    var tileLayer = new L.tileLayer(tileURL, {
        attribution: '©2023, PTV Group, HERE'
    }).addTo(map);
});

 

Explanation of the important code lines

This jQuery expression indicates that the code inside the curly brackets is executed only if the root index.html file is completely loaded.

$(document).ready(function() {
    ...
});

The first argument given to the L.Map constructor references the DOM element with the id map. The following lines show how a leaflet map control like zooming is created.

var map = new L.Map('map', {
    center: coordinates,
    zoom: 13,
    zoomControl: false
});

L.control.zoom({
    position: 'bottomright'
}).addTo(map);

The expressions {z}/{x}/{y} in the tileURL are placeholders which are replaced by default.

const tileURL = `https://api.myptv.com/rastermaps/v1/image-tiles/{z}/{x}/{y}?apiKey=${api_key}`;

More information on this can be found in the Raster Maps API Documentation

All other replacements have to be defined in the constructor parameters of L.tileLayer.

var tileLayer = new L.tileLayer(tileURL, {
    attribution: '©2023, PTV Group, HERE'
}).addTo(map);

The rendered HTML page constitutes already an interactive zoom-able and pan-able map. The next section shows how to create input controls for start and destination.

Adding input forms to geocode a start and destination address

Beginning from this step, we will focus on the most important chunks of the source code.

The following code has to be added into index.html where the comment is. It will add input forms for the start and destination address. In this basic tutorial no type ahead features are included.

<div id="controls">
    <input id="startFormId" type="text" size="25" placeholder="Enter start address" autocomplete="off" />
    <input id="destinationFormId" type="text" size="25" placeholder="Enter destination address" autocomplete="off" />
    <button type="button" id="btnSubmit" class="calc-btn">Calculate Route</button>
</div>

The JavaScript code below shows how to react to a button click event. It may be added to the basicRoutingDemo.js file for demonstration purposes. The implementaion of the start position is similar to the destination position. To visualize the start and destination position of the route, we set markers to the map by using the custom function setMarkers().

$("#btnSubmit").click(function () {
    geocodeAdress($("#startFormId").val(), geoURLObj, api_key, (coord) => {
        // Extract the start position from the response.
        startPosition = [coord.latitude, coord.longitude];
        geocodeAdress($("#destinationFormId").val(), geoURLObj, api_key, (coord) => {
            // Extract the start position from the response.
            destinationPosition = [coord.latitude, coord.longitude];
            setMarkers();
            // This function calls the Routing API to get the calculated route.
            fetchRoute(routingURLObj);
        });
    });
})

 

Explanation of the important code lines

This statement adds a click event to the button with the id btnSumbit in the index.html file.

$("#btnSubmit").click(function () {
    ...
})

If the button Calculate Route is clicked, the following lines are triggered:

geocodeAddress($("#startFormId").val(), geoURLObj, api_key, (coord) => {
    // There is the implementation of the start and destination position
    });
});

The input value of the start address with id startFormId from index.html and the coordinates are given as arguments to the geocodeAddress function. This function sends a REST call with the appropriate parameters and headers to the Geocoding API. The function parameter action is called when a result was received successfully from the server.

function geocodeAdress(strInput, action) {
    // Define the parameters needed for the REST query. See https://developer.myptv.com/Documentation/Geocoding%20API/API%20Reference.htm
    var queryParams = {
        searchText: strInput,
        language: "en"
    };
    geoURLObj.search = new URLSearchParams(queryParams).toString();

    // Send the REST call with the appropriate parameters and headers.
    fetch(geoURLObj,
        {
            method: "GET",
            headers: {
                "ApiKey": api_key,
                "Content-Type": "application/json"
            }
        })
        .then(response => response.json()
            .then(result => {
                action(result.locations[0].referencePosition);
            }));
}

If the geocodeAddress function is finished, the start and destination positions are extracted from the respective answer. After that, the markers will be set with setMarkers() and the route will be calculated in fetchRoute being explained in the next section.

setMarkers();
// This function calls the Routing API to get the calculated route.
fetchRoute(routingURLObj);

Call the PTV Routing API

The PTV Routing API call is done in the same manner like the geocoding. In our basic scenario we send only start and destination waypoints together with the result option to acquire a polyline to the server. A productive version will require additional request parameters like the start or arrival time or vehicle properties.

With fetchRoute(), our first call to the PTV Routing API is made.

function fetchRoute() {
    let searchParams = new URLSearchParams();
    searchParams.append("waypoints", startPosition[0].toString() + "," + startPosition[1].toString());
    searchParams.append("waypoints", destinationPosition[0].toString() + "," + destinationPosition[1].toString());
    searchParams.append("results", "POLYLINE");
    routingURLObj.search = searchParams.toString();

    fetch(routingURLObj,
        {
            method: "GET",
            headers: {
                "ApiKey": api_key,
                "Content-Type": "application/json"
            }
        })
        .then(response => response.json()
            .then(result => {
                document.getElementById("travelResult").innerHTML = convertTime(result.travelTime) + ' for ' + convertDistance(result.distance);
                displayPolyline(map, JSON.parse(result.polyline));
            }));
}

 

Explanation of the important code lines

First of all, we need to build an URL with query parameters to send the required parameters to the server. The start and destination positions are each stored in the parameter waypoints with longitude and latitude. In this example, the parameter result is POLYLINE. The HTTP method is GET and the headers contains the API key and the needed content type.

function fetchRoute() {
    let searchParams = new URLSearchParams();
    searchParams.append("waypoints", startPosition[0].toString() + "," + startPosition[1].toString());
    searchParams.append("waypoints", destinationPosition[0].toString() + "," + destinationPosition[1].toString());
    searchParams.append("results", "POLYLINE");
    routingURLObj.search = searchParams.toString();

    fetch(routingURLObj,
        {
            method: "GET",
            headers: {
                "ApiKey": api_key,
                "Content-Type": "application/json"
            }
        })
        ...
}

After the fetch method is finished, the response will then be converted to JSON. To show the travel time and distance, the travelTime of result has to be converted by a custom function convertTime. The distance of result is converted by convertDistance. In the next line, the polyline will be displayed on the map.

.then(response => response.json()
    .then(result => {
        document.getElementById("travelResult").innerHTML = convertTime(result.travelTime) + ' for ' + convertDistance(result.distance);
        displayPolyline(map, JSON.parse(result.polyline));
    }));

The function displayPolyline contains the style of the polyline. We use the polyline in the geoJSON format as provided by the response of the routing API call.

function displayPolyline(map,poly) {
    if (polylineLayer !== null) {
        map.removeLayer(polylineLayer);
    }

    var myStyle = {
        "color": '#2882C8',
        "weight": 5,
        "opacity": 0.65
    };

    polylineLayer = L.geoJSON(poly, {
        style: myStyle
    }).addTo(map);

    map.fitBounds(polylineLayer.getBounds());
}

Now you have learned how a basic routing app is being created using the PTV Developer Routing API.