Snap to Roads Ownership Rebates in Australia and New Zealand
TomTom Developer Relations·Jan 20, 2023

Snap to Roads Ownership Rebates in Australia and New Zealand

TomTom Developer Relations
Jan 20, 2023 · 8 min read

New Zealand and Australia reimburse businesses for vehicles used offroad for business purposes. TomTom’s Snap to Roads API can be incorporated into a Python application that tracks the distance and fuel spent off public roads for businesses looking to claim this rebate.

In New Zealand and Australia, a government program reimburses businesses for fuel-operated equipment, such as vehicles that are used on private roads for business purposes. To claim this rebate, businesses must track the fuel spent while operating off public roads. TomTom APIs are perfect for this task.

In this piece, we’ll explore the capabilities of the Snap to Roads API in retrieving data for the distance covered by vehicles on private and public roads — and developing a Python application that does just that. Our application will feed GPS data to the Snap to Roads API, examine the returned output, extract the distances, and input the returned data into a CSV file.

Prerequisites

To follow along with this piece, you will need the following:

  • An understanding of the Python programming language

  • Access to a Google Colab environment, Jupyter Notebook, or another Notebook-based environment or Python IDE.

  • A TomTom developer account to get an API key for our API calls

Developing the Program

In a new code cell, paste the following snippet to import the required modules.

import urllib.parse as urlparse
import pandas as pd
import requests as rq
from tabulate import tabulate

urllib.parse will be used for encoding and decoding URLs to make them fit for use with the API. [pandas](https://pandas.pydata.org/) and tabulate will be used to easily display the data returned without needing many loops. Also, pandas has a built-in method for converting data frames into a CSV format. As the name suggests, requests will be used to make HTTP requests.

In another cell, create a variable for storing your API key:

__API_KEY__ = "" #Enter your API key here

The API documentation states that there is an optional parameter called fields, which specifies what the response should return. To be sure we have captured everything we want, we must set the fields parameter to make the API return everything in its response. Note that we have assigned it to a variable called fields. The last line removes the empty spaces, making the fields parameter ready for consumption by the Snap to Roads API.

This is the fields object we will be using:

fields = "{
  projectedPoints{
    type,
    geometry{
      type,
      Coordinates
    },
    properties{
      routeIndex,
      SnapResult
     }
  },
  route{
    type,
    geometry{
      type,
      Coordinates
    },
    properties{
      id,
      linearReference,
      speedLimits{
        value,
        unit,
        Type
      },
      address{
        roadName,
        roadNumbers,
        municipality,
        countryName,
        countryCode,
        CountrySubdivision
      },
      traveledDistance{
        value,
        Unit
      },
      privateRoad,
      partOfTunnel,
      urbanArea,
      elementType,
      frc,
      formOfWay,
      roadUse,
      laneInfo{
        NumberOfLanes
      },
      heightInfo{
        height,
        chainage,
        Unit
      },
      trafficSigns{
        signType,
        chainage,
        Unit
      },
      TrafficLight
    }
  },
  distances{
    total,
    ferry,
    road,
    privateRoad,
    publicRoad,
    offRoad,
    Unit
  }
}"

fields = fields.replace(" ", "")   

We now need to URL encode the field using the following code:

processed_fields = urlparse.quote(fields)
processed_fields

Next, we need to add random coordinates from a route in New Zealand. More specifically, the route starts from Verissimo Drive, then follows Auckland Airport Pack & Ride, Nixon Road, George Bolt Memorial Drive, and finally, Tom Pearce Drive, stopping at Harriman Signs Shop.

Route in New Zealand

In a real-world situation, these coordinates would come automatically from a tool like a GPS-enabled app. Here’s the list of coordinates in longitude-latitude format.

coordinate_list = ['174.789024,-36.988238', '174.788684,-36.989756', '174.788243,-36.990205', '174.788326,-36.990326', '174.788570,-36.990669', 
                   '174.788751,-36.990944', '174.789341,-36.991844', '174.789742,-36.992466', '174.790107,-36.993006', '174.790122,-36.993085', 
                   '174.790886,-36.993205', '174.791565,-36.993344', '174.792321,-36.993631', '174.793032,-36.993897', '174.793657,-36.994120', 
                   '174.794266,-36.994328', '174.795366,-36.994549', '174.796619,-36.994746', '174.797885,-36.994971', '174.799229,-36.995434', 
                   '174.799852,-36.996107', '174.800091,-36.996842', '174.800275,-36.998212', '174.800203,-36.998767', '174.799956,-36.998953', 
                   '174.800069,-36.998707', '174.800149,-36.997668', '174.800010,-36.996728', '174.799690,-36.995976', '174.798969,-36.995380', 
                   '174.798102,-36.995089', '174.796528,-36.994798', '174.795047,-36.994558', '174.793784,-36.994245', '174.792529,-36.993768', 
                   '174.790874,-36.993269', '174.788680,-36.992958', '174.787039,-36.992714', '174.786566,-36.992682', '174.786341,-36.993357', 
                   '174.786078,-36.994524', '174.786094,-36.995133', '174.786234,-36.995820', '174.786907,-36.997213', '174.787580,-36.998592', 
                   '174.788578,-37.000330', '174.789111,-37.001356', '174.789511,-37.001598', '174.790109,-37.001467', '174.791160,-37.001169', 
                   '174.793296,-37.000653']

Next, we separate them with semicolons using the code snippet below, for example:

174.795047,-36.994558;174.793784,-36.994245. 

coordinates_to_string = ";".join([x for x in coordinate_list])

We use list comprehension to avoid many lines of code.

Next, we URL-encode the coordinates:

processed_coordinates = urlparse.quote(coordinates_to_string)

The next step is setting the base URL while passing in the fields and the coordinates. This URL can be found on the API Explorer page after trying out the Snap to Roads API.

base_url = "https://api.tomtom.com/snapToRoads/1?points="+processed_coordinates+"&vehicleType=PassengerCar&fields="+processed_fields+"&key="+__API_KEY__

We then initiate a request to the API and obtain the route information. We will need the length of the route object to extract data from the output.

json_response = rq.get(base_url).json()
routes_obj = json_response['route']
obj_length = len(routes_obj)

Now let’s take a quick look at the output.

The Output of the API

After executing a request, the output can be downloaded from the API Explorer page by clicking Download. We downloaded and opened this particular route using a JSON tool.

At a glance, we find three objects:

  • route contains the details of the path returned by the API.

  • projectedPoints are the points on the road network taken from the source coordinate projections as passed in the original input sequence.

  • distances contain the summary of the distances covered by the route.

We can see that the total distance is 4869 meters. The distance for private roads is 4830 meters, and the distance for public roads is 39 meters.

distances for roads

Each segment of the route object contains several other objects. For our purposes, we’re interested in traveledDistance and privateRoad, both under the properties object. These represent the distance covered by each route segment and whether the road is a private or a public one.

If the privateRoad property is true, then the road is private, and if it is false, the road is public. In the next steps, we programmatically access these values and display them.

route objects

Accessing the Values Programmatically

Let’s take a moment to check the output. Two lists for storing the private and public road distances are needed:

private_road_list = []
public_road_list = []

To avoid repetition and a long line of code, a function for appending the extracted values to the lists will come in handy:

def list_appender(list_param, idx):
  list_param.append(routes_obj[idx]['properties']['traveledDistance']['value'])

The line below uses for loops to check whether the value in a segment belongs to a private or public road and append it to a list.

for i in range(obj_length): list_appender(private_road_list, i) if routes_obj[i]['properties']['privateRoad'] == True else list_appender(public_road_list, i)

We can validate if the values are correct using Python’s sum method:

print(sum(private_road_list))
print(sum(public_road_list))

Per the screenshots shared above, these will return 4830 and 39, respectively.

print sum

For the off-road distance, it is only included in the distances object and not the routes object. So we access it in this manner.

offroad_distance = json_response['distances']['offRoad']

To display the data, we need three dictionaries for the sum of the three road type distances:

sum_dict = {
  "Private Roads": [sum(private_road_list)],
  "Public Roads": [sum(public_road_list)],
  "Off roads": [offroad_distance]
}

private_df = pd.DataFrame(private_road_list)
public_df = pd.DataFrame(public_road_list)
off_road_df = pd.DataFrame([offroad_distance])

Also, we need a helper function for displaying the data frames in tabular form using the tabulate module:

def display_dataframe(arg,header_vals):
  print(tabulate(arg, headers=header_vals, tablefmt='psql'))

In individual cells, run the following code snippets to see the displayed tables:

display_dataframe(public_df,["Positions",'Distances(m)'])
display_dataframe(private_df,["Positions",'Distances(m)'])
display_dataframe(off_road_df,["Positions",'Distances(m)'])
display_dataframe(distance_df,["Private roads",'Public roads'])

The last one displays this:

displayed tables

The final steps are accessing the file system, creating a CSV file called private-roads.csv in writable form, and adding the private road distance to the CSV file. We do that using the following code:


def create_csv():
  file_path = open('private-roads.csv', 'w')
  file_path.close()
  private_df.to_csv('private-roads.csv')

Run the method in a new cell:

create_csv()

The file will be created in the current working directory:

current directory

On opening it, we can see the data:

data tables within directory

Conclusion

This short tutorial demonstrates how you can use TomTom’s Snap to Roads API to obtain road usage information, which helps calculate tax rebates in New Zealand and Australia.

We started by preparing the needed data for the API. Next, we invoked the API, visually examined the response, and extracted the information programmatically. Finally, we displayed the data in tabular form and added it to a CSV file.

We hope you gained some valuable insights and a kick-start on using the API. Now try the API for yourself!