Search for content

Track Matching with Toll Calculation

Track Matching Tutorial

In this tutorial you will learn how a basic Track Matching workflow works. First, you use Track Matching with the calculationMode parameter as a POST request and pass the positions in JSON format. If the request succeeds, the response contains an ID. Using this ID you can request the results of the match track calculation. 

With a GET request to the calculateRoute endpoint of the Routing API you can recalculate the matched track. Therefore you have to provide the route ID of the getMatchedTrack response as query parameter to the request.

Try Trackmatching Showcase! 

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 guide you like a cooking recipe. This tutorial will focus on how to call the Track Matching service and what you can do with the results.  

Getting started

Get the track ID by requesting calculationMode and pass the positions. 

let positions = [
    {
        "latitude": 49.60297865121701,
        "longitude": 6.089665889739991,
        "heading": 120,
        "timestamp": "2024-10-24T08:00:00+02:00"
    },
    {
        "latitude": 49.60534266402004,
        "longitude": 6.098291873931885,
        "heading": 120,
        "timestamp": "2024-10-24T08:10:00+02:00"
    },
    {
        "latitude": 49.60694526044603,
        "longitude": 6.108415201306344,
        "heading": 120,
        "timestamp": "2024-10-24T08:15:00+02:00"
    },
    {
        "latitude": 49.6087633266626,
        "longitude": 6.116981506347656,
        "heading": 120,
        "timestamp": "2024-10-24T08:18:00+02:00"
    },
    {
        "latitude": 49.60970188115495,
        "longitude": 6.120007038116456,
        "heading": 120,
        "timestamp": "2024-10-24T08:20:00+02:00"
    }
];

         
async function startTrackMatching() {
    return fetch(
        "https://api.myptv.com/mapmatch/v1/tracks?calculationMode=STANDARD",
        {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'apiKey': "api_key";
            },
        body: JSON.stringify({ "positions" : positions })
    }
    ).then((response) => response.ok ? response.json() : console.log(response));
}

Explanation of the code

The code defines the method startTrackMatching. The calculationMode parameter can be assigned different values that affect the quality and performance of the matching. The positions are passed in JSON format. The track positions can contain timestamps. Note that either all positions contain timestamps or none of the positions contain timestamps. The coordinates are given in WGS84 format (latitude, longitude). The positions must contain at least latitude and longitude. All other parameters are optional. If the request is successful you get the track ID. 

The URL consists of

  • the protocol "https",
  • the host name "api.myptv.com",
  • the path to the endpoint "mapmatch/v1/tracks",
  • calculationMode with following possible values "STANDARD", "QUALITY" and "PERFORMANCE",
  • positions in JSON format,
  • the ApiKey, also as a request parameter.

Pass the track id and get the track results. 

If the request with calculationMode succeeds, the response contains an ID. 

{
   "id": "1e05ad60-35c1-48f9-be4d-1ead39b3d0c6"
}

Using this ID you can request the results of the match track calculation:

async function getTrackResponse(requestId) {
    var status = "RUNNING";
    await sleep(25);
    while (status == "RUNNING") {
        fetch(
            "https://api.myptv.com/mapmatch/v1/tracks/" + trackId + "?results=GEOMETRY,PATHS,TRACK_POSITIONS,ROUTE_ID",
            {
                method: 'GET',
                headers: {
                    'Content-Type': 'application/json',
                    'apiKey': api_key
                }
            }
        )
        .then((response) => response.json()
            .then((result) => {
                status = result.status;
                if (status == "SUCCEEDED") {
                    console.log(result);   
                }
            })
            .catch((error) => {
                console.log(error.message); 
                status = "error";
            })
        );
        if (status == "RUNNING") {
            await sleep(100);
        }
    }
    return status;
}

Explanation of the code

The method getTrackResponse defines the request to fetch the results of the calculation. Therefore you have to provide the track ID of the createMatchedTrack response as path parameter. By using the results query parameter you can also specify which kind of result you want to get. In the response you will get the route ID. The route ID is required as a result parameter in order to be able to calculate the toll afterwards.

The URL consists of

  • the protocol "https",
  • the host name "api.myptv.com",
  • the path to the endpoint "mapmatch/v1/tracks",
  • track ID of the createMatchedTrack response as path parameter,
  • results with following possible values "GEOMETRY", "TRACK_POSITIONS", "PATHS", "SEGMENT_ATTRIBUTES", "ROUTE_ID",
  • the ApiKey, also as a request parameter.

 

If the request was successful the response looks like this:

{
  "status": "SUCCEEDED",
  "matchedTrack": {
    "id": "b5770be0-bc5f-43ca-8db6-b2b44f2b71dd",
    "distance": 1592,
    "paths": [
      {
        "distance": 695,
        "startTime": "2024-10-24T08:00:00.000+02:00",
        "startTrackPositionIndex": 0,
        "endTime": "2024-10-24T08:10:00.000+02:00",
        "endTrackPositionIndex": 1,
        "routeId": "d08aac06-a50a-4676-b637-2b68fffe3503"
      },
      {
        "distance": 897,
        "startTime": "2024-10-24T08:15:00.000+02:00",
        "startTrackPositionIndex": 2,
        "endTime": "2024-10-24T08:20:00.000+02:00",
        "endTrackPositionIndex": 4,
        "routeId": "3bc38d66-c597-4d19-b925-0378e5b61958"
      }
    ],
    "geometry": "{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"LineString\",\"coordinates\":[[6.0896605903,49.603064353],[6.0903124822,49.603081252],[6.0906152551,49.603109283],[6.0910045344,49.603137315],[6.0910910409,49.603137315],[6.0913073072,49.603193378],[6.0916533333,49.603305504],[6.0922156257,49.603585817],[6.0923021322,49.603613848],[6.0932104506,49.604118407],[6.0934267169,49.6042025],[6.0936429832,49.604342654],[6.0945945549,49.604819176],[6.0947243147,49.604875237],[6.0948540745,49.604931298],[6.0952001006,49.605043419],[6.0954163669,49.60509948],[6.0974060168,49.605351753],[6.0979683092,49.605407813],[6.0982653947,49.605441296]]},{\"type\":\"LineString\",\"coordinates\":[[6.108397971,49.606978694],[6.1086518642,49.607033537],[6.1093006631,49.607229741],[6.1100792218,49.607453974],[6.1108577804,49.607678206],[6.1114200728,49.607846379],[6.1117228456,49.607930465],[6.1120688717,49.608014551],[6.1126744173,49.608210751],[6.1128906836,49.608266809],[6.1135827358,49.608378923],[6.1154858792,49.60860315],[6.115615639,49.60860315],[6.115961665,49.608659207],[6.1168699835,49.608883433],[6.1170862498,49.608939489],[6.1172592628,49.608995545],[6.1176917954,49.609107658],[6.1185136073,49.609387937],[6.1193354193,49.609640188],[6.1193786725,49.609640188],[6.1199976016,49.609729312]]}]}",
    "trackPositions": [
      {
        "matchType": "MATCH_SUCCESSFUL",
        "timestamp": "2024-10-24T08:00:00.000+02:00",
        "distanceFromPreviousMatch": 0,
        "latitude": 49.603064353,
        "longitude": 6.0896605903,
        "segmentGeometry": "{\"type\":\"LineString\",\"coordinates\":[[6.0892311508,49.60305322],[6.0903124822,49.603081252]]}",
        "matchDistance": 10,
        "angleDifference": 32.29,
        "segmentAttributes": {
          "bridge": false,
          "tunnel": false,
          "ramp": false,
          "frontage": false,
          "paved": true,
          "requiresFourWheelDrive": false,
          "privatelyManaged": false,
          "publicAccess": true,
          "parkingLot": false,
          "priorityRoad": true,
          "builtUpArea": true,
          "speedLimit": 50
        }
      },
      {
        "matchType": "MATCH_SUCCESSFUL",
        "timestamp": "2024-10-24T08:10:00.000+02:00",
        "distanceFromPreviousMatch": 695,
        "latitude": 49.605441296,
        "longitude": 6.0982653947,
        "segmentGeometry": "{\"type\":\"LineString\",\"coordinates\":[[6.0979683092,49.605407813],[6.0989631341,49.605519934]]}",
        "matchDistance": 11,
        "angleDifference": 39.87,
        "segmentAttributes": {
          "bridge": false,
          "tunnel": false,
          "ramp": false,
          "frontage": false,
          "paved": true,
          "requiresFourWheelDrive": false,
          "privatelyManaged": false,
          "publicAccess": true,
          "parkingLot": false,
          "priorityRoad": true,
          "builtUpArea": true,
          "speedLimit": 50
        }
      },
      {
        "matchType": "MATCH_SUCCESSFUL",
        "timestamp": "2024-10-24T08:15:00.000+02:00",
        "distanceFromPreviousMatch": 0,
        "latitude": 49.606978694,
        "longitude": 6.108397971,
        "segmentGeometry": "{\"type\":\"LineString\",\"coordinates\":[[6.1083058381,49.606949449],[6.1083923447,49.606977478],[6.1086518642,49.607033537]]}",
        "matchDistance": 4,
        "angleDifference": 48.43,
        "segmentAttributes": {
          "bridge": false,
          "tunnel": false,
          "ramp": false,
          "frontage": false,
          "paved": true,
          "requiresFourWheelDrive": false,
          "privatelyManaged": false,
          "publicAccess": true,
          "parkingLot": false,
          "priorityRoad": true,
          "builtUpArea": true,
          "speedLimit": 50
        }
      },
      {
        "matchType": "MATCH_SUCCESSFUL",
        "timestamp": "2024-10-24T08:18:00.000+02:00",
        "distanceFromPreviousMatch": 654,
        "latitude": 49.608891784,
        "longitude": 6.116902202,
        "segmentGeometry": "{\"type\":\"LineString\",\"coordinates\":[[6.1168699835,49.608883433],[6.1170862498,49.608939489]]}",
        "matchDistance": 15,
        "angleDifference": 51.8,
        "segmentAttributes": {
          "bridge": false,
          "tunnel": false,
          "ramp": false,
          "frontage": false,
          "paved": true,
          "requiresFourWheelDrive": false,
          "privatelyManaged": false,
          "publicAccess": true,
          "parkingLot": false,
          "priorityRoad": true,
          "builtUpArea": true,
          "speedLimit": 50
        }
      },
      {
        "matchType": "MATCH_SUCCESSFUL",
        "timestamp": "2024-10-24T08:20:00.000+02:00",
        "distanceFromPreviousMatch": 243,
        "latitude": 49.609729312,
        "longitude": 6.1199976016,
        "segmentGeometry": "{\"type\":\"LineString\",\"coordinates\":[[6.1193354193,49.609640188],[6.1193786725,49.609640188],[6.1205465105,49.609808354]]}",
        "matchDistance": 3,
        "angleDifference": 42.53,
        "segmentAttributes": {
          "bridge": false,
          "tunnel": false,
          "ramp": false,
          "frontage": false,
          "paved": true,
          "requiresFourWheelDrive": false,
          "privatelyManaged": false,
          "publicAccess": true,
          "parkingLot": false,
          "priorityRoad": true,
          "builtUpArea": true,
          "speedLimit": 50
        }
      }
    ]
  }

Calculate Toll

You can calculate toll or emissions for a driven route if you combine the PTV Developer Map Matching API and PTV Developer Routing API. You can use this for compliance measurements like a toll cost validation. For each path in the Get request you have to take the respective value for the route ID and (for time-dependent tolls) also the respective startTime from the TrackResponse. The toll for the entire track is the sum of the toll for the individual paths. 

The following steps describe how to recalculate a driven route by using a matched track in order to get toll costs and/or emissions.

 async function getTollResponse() {
    fetch(
        "https://api.myptv.com/routing/v1/routes?routeId=6912c1b1-f31b-4e34-b006-4a3d41f8e24f&results=TOLL_COSTS&options[startTime]=2024-10-24T06:00:00.000Z&apiKey=YOUR_API_KEY";
        {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
                'apiKey': api_key
            }
        }
    )
    .then((response) => response.json()
        .then((result) => {
            console.log((result);
        })
        .catch((error) => {
            console.log(error.message); 
        })
    );
}

Explanation of the code

The code defines the function getTollResponse(), which is responsible to construct the URL for the position matching request. The URL consists of

  • the protocol "https",
  • the host name "api.myptv.com",
  • the path to the endpoint "routing/v1/routes",
  • ID of the track getting by calculationMode,
  • the result parameter with the value "TOLL_COSTS", 
  • options[startTime]=<time value> and
  • the ApiKey, also as a request parameter.

Next steps to try

As a next step you might want to display more properties of the result (like e.g. the country), or extend the request parameter results to request additional result fields, like segment attributes or geometry. In order to learn more about that, please refer to the PTV Developer Map Matching API documentation and the  PTV Developer Track Matching concept.