The PTV Timetable API provides programmatic access to public transport data for the state of Victoria, Australia.
Find below a minimal Python implementation with no external dependencies, or have a look at the javascript version. I'm happy to host here any more minimal, tested implementations; if you have one you'd like to share, send a pull request!
import requests
import hashlib
import hmac
class PTVv3:
base_url = 'https://timetableapi.ptv.vic.gov.au'
def __init__(self, ptv_id, ptv_key):
self.id = ptv_id
self.key = ptv_key.encode('utf-8')
def __call__(self, endpoint, **params):
params['devid'] = self.id
encoded = [f'{k}={v}'
for k, vs in params.items()
for v in (vs if isinstance(vs, (list, tuple)) else [vs])]
request = f'{endpoint}?{"&".join(encoded)}'
hashed = hmac.new(self.key, request.encode('utf-8'), hashlib.sha1)
url = f'{PTVv3.base_url}{request}&signature={hashed.hexdigest()}'
response = requests.get(url)
response.raise_for_status()
return response.json()
which can be used as follows:
ptv = PTVv3('your id here', 'your key here')
print(ptv('/v3/disruptions', route_types=2))
You will need to obtain your own id/key pair from PTV to use the API.
Intuitively, these are the API concepts:
- a route is an ordered collection of stops that can run in one or more directions;
- a run represents a vehicle (bus, tram, train, etc) travelling along a route in a direction;
- a departure gives the planned and predicted time (if available) of passing through a stop.
The API also provides information regarding service disruptions, fare estimates and station facilities.
A more detailed description of the relation between stops, directions and routes can be found in here.
The Jupyter notebook in this repository shows how to use the PTVv3 class to:
- compose, sign and make a request,
- discover bus stops from GPS coordinates, and
- find realtime location of buses and expected departures.
The notebook is also available in markdown and PDF formats.
What follows are unverified educated guesses. If you have any better information, ideally firsthand, I'd like to hear from you!
PTV provides each transport modality through several operators. For example, in the Melbourne area:
- trains are handled by Metro Trains and
- buses are provided by Ventura.
PTV API for live tracking of buses has been occasionally unreliable, not returning a vehicle_position
structure for otherwise active vehicles.
Ventura's own live tracker does not have an API but has been historically more dependable. It appears to either share or piggyback on tracking technology from BusMinder, which does not have a public-facing API either. Both the web tracker and the app can only display the company's services; several weekend routes are served by different operators.
The tracking devices on buses are produced by Smartrak, and transmit GPS coordinate over the 4G cellular network. The device model most likely installed on buses is a Smartrak OBD II.
The PTV API OpenAPI schema can be downloaded with:
wget http://timetableapi.ptv.vic.gov.au/swagger/docs/v3 -O ptv_api_spec.json
The OpenAPI specification version used in it is 2.0. The v3 on the website and documentation refers to the revision of the PTV API!
The schema is not valid. It can be made to pass validation by applying the following superficial patch:
cat ptv_api_spec.json | python -m json.tool > prettyprinted.json
--- prettyprinted.json 2024-03-26 01:38:49
+++ modified.json 2024-03-26 01:39:11
@@ -3571,6 +3571,23 @@
}
}
},
+ "V3.FareEstimateResponse": {
+ "type": "object",
+ "properties": {
+ "fare_estimate": {
+ "$ref": "#/definitions/V3.FareEstimate",
+ "description": "Resultant set fare estimates"
+ },
+ "status": {
+ "$ref": "#/definitions/V3.Status",
+ "description": "API Status / Metadata"
+ }
+ }
+ },
+ "V3.FareEstimate": {
+ "type": "object",
+ "properties": {}
+ },
"V3.Disruptions": {
"type": "object",
"properties": {
Using tools like openapi-core (OpenAPI v3) or Flex (OpenAPI v2) it's then possible to validate a url prior to making a request:
from requests import Request
from flex.core import load, validate_request, normalize_request
schema = load('ptv_api_spec.json')
request = Request('GET', 'https://timetableapi.ptv.vic.gov.au/v3/route_types')
validate_request(normalize_request(request.prepare()), schema)
The validation code above is supposed to check the compliance of both url path and parameters, but only works properly for the first.
In theory, this code should be able to support v2 of the Timetable API (e.g. ptv('v2/healthcheck')
), which is still advertised on PTV's website, but I had no luck with either http or https protocols. Support for v2 endpoints might have been discontinued.