Skip to main content

From Waves to Data Streams: Unleashing Vessel Tracking using Voltmetrix ⚓️

· 7 min read

Alt text

Photo by Ian Taylor on Unsplash


Tracking vessels is a crucial aspect of the maritime industry as it provides information about a vessel's position, speed, heading, and more. In this article, we will explore how to track vessels using Voltmetrix.

As someone who is passionate about tracking and fascinated by movement, I decided to track vessels using a public API, store the data in a TimescaleDB database, and visualize it on a map using Grafana.


For this project, I discovered the [](https:// API, which offers free access to information about specific Geoboxes or individual vessels. The API is user-friendly; you only need an API Key, which can be generated on their website.

Tracking Choices

I chose to track vessels in two distinct locations: the port of Buenos Aires, Argentina, and the port of San Francisco, United States. These ports were selected due to their high activity levels and my personal experience visiting them.

The database

For this project, I opted for TimescaleDB. My familiarity with the database and its integration with PostgreSQL drove this choice. TimescaleDB is a time-series database that builds upon PostgreSQL's capabilities, making it a powerful choice.

To deploy TimescaleDB on Voltmetrix, I followed these steps:

voltmetrix deploy \
--cloud gcp \
--database timescaledb \
--org_id your-org_id \
--token your-awesome-token \
--size e2-small \
--region us-east1

Data visualization

I selected Grafana for data visualization due to its user-friendly interface and powerful capabilities. To deploy Grafana on Voltmetrix, I executed the following command:

voltmetrix deploy \
--cloud gcp \
--database grafana \
--org_id your-org_id \
--token your-awesome-token \
--size e2-small \
--region us-east1

Once the database and visualization tool are deployed, they need to be connected. To achieve this, create a datasource in Grafana by navigating to the settings, clicking on "Data Sources," selecting PostgreSQL as the database type, and completing the form with the necessary details.

The code

The code responsible for this project is written in Python and is relatively simple. The code performs two primary tasks: establishing a WebSocket connection to the API and inserting the retrieved data into the TimescaleDB database.

Create a requirements.txt file with the following contents:


Then you should have a file called with the following content:


Remember to change the values of the variables PASSWORD, and AISAPIKEY with your own values.

import asyncio
import websockets
import json
from datetime import datetime, timezone
import psycopg as pg
import os

conn_str = f'user=postgres password={os.environ.get("PASSWORD")} port=5432 dbname=tsdb'

conn = pg.connect(conn_str)

# Create table

create_table_query = '''CREATE TABLE IF NOT EXISTS ais_data (
ts timestamp with time zone NOT NULL,
ship_id INTEGER,
latitude REAL,
longitude REAL,
speed REAL,
heading REAL,
nav_status TEXT


# Create hypertable

create_hypertable_query = "SELECT create_hypertable('ais_data', 'ts', create_default_indexes => FALSE);"

# Connect to AIS stream and insert data into timescaledb
async def connect_ais_stream():

async with websockets.connect("wss://") as websocket:
subscribe_message = {
"APIKey": os.environ.get("AISAPIKEY"), # Required !
"BoundingBoxes": [
# Buenos Aires, Argentina
[[-34.811548,-58.537903], [-34.284453,-57.749634]],
# San Francisco, USA
[[36.989391,-123.832397], [38.449287,-121.744995]],
"FilterMessageTypes": ["PositionReport"],

subscribe_message_json = json.dumps(subscribe_message)
await websocket.send(subscribe_message_json)

async for message_json in websocket:
message = json.loads(message_json)
message_type = message["MessageType"]

if message_type == "PositionReport":
# the message parameter contains a key of the message type which contains the message itself
ais_message = message["Message"]["PositionReport"]
print(f"[{}] ShipId: {ais_message['UserID']} Latitude: {ais_message['Latitude']} Longitude: {ais_message['Longitude']} Speed: {ais_message['Sog']} Heading: {ais_message['Cog']} NavStatus: {ais_message['NavigationalStatus']}")
# Insert data into timescaledb
insert_query = '''INSERT INTO ais_data (ts, ship_id, latitude, longitude, speed, heading, nav_status) VALUES (%s, %s, %s, %s, %s, %s, %s);'''
conn.cursor().execute(insert_query, (, ais_message['UserID'], ais_message['Latitude'], ais_message['Longitude'], ais_message['Sog'], ais_message['Cog'], ais_message['NavigationalStatus']))

if __name__ == "__main__":

Before running the code, you need to convert the table into a hypertable. For doing that you need to run the following command:

psql -h -p 5432 -U postgres -d tsdb -c "SELECT create_hypertable('ais_data', 'ts', create_default_indexes => FALSE);"

Now, you can run the code with the following command:


If everything goes well, you can see the data in the database. First connect to the database with the following command:

psql -h -p 5432 -U postgres -d tsdb -c "Select * from ais_data;"

The result should be something like this:

              ts               |  ship_id  |  latitude  |  longitude  | speed | heading | nav_status
2023-08-05 14:24:26.800649+00 | 367425230 | 37.781433 | -122.38651 | 0 | 14.9 | 0
2023-08-05 14:24:29.347571+00 | 563001700 | 37.39222 | -123.14578 | 9.5 | 352.4 | 0
2023-08-05 14:24:29.838122+00 | 538004682 | 37.75017 | -122.344345 | 0 | 201.4 | 1
2023-08-05 14:24:30.550313+00 | 538009135 | 37.770645 | -122.35081 | 0 | 23.3 | 1
2023-08-05 14:24:32.866905+00 | 367085940 | 37.870697 | -122.494446 | 5.9 | 145.4 | 15
2023-08-05 14:24:33.326184+00 | 366862670 | 37.775505 | -122.39174 | 0.3 | 50.4 | 0

Now, we can create a dashboard in Grafana to visualize the data. For doing that we need to go to the settings of Grafana and then click on the option "Dashboards" and then click on the button "Manage". Once we are there we need to click on the button "New Dashboard" and then click on the button "Add new panel". Once we are there we need to click on the button "Add query" and then we need to select the database that we created before. Then we need to write the following query:

SELECT ship_id, latitude, longitude, heading, speed, nav_status, ts 
FROM ais_data
WHERE ts >= NOW() - INTERVAL '5 minutes'
GROUP BY ship_id, latitude, longitude, heading, speed, nav_status, ts;

This is sample query that is going to graph in a Map the position of the ships that are in the database. The result should be something like this:

Alt text

Have in mind that I'm taking the data from the last 5 minutes. That's why you can see the "breadcrumbs" of the ships in the map. If you don't want to see that, you can reduce the time to one minute or less.

The dashboard

In my case, I created a dashboard with two panels, one watching the data from San Francisco and the other one watching the data from Buenos Aires. The result is the following:

Alt text

You can notice that I'm using two types of maps, this is only for showing you the two types of maps that you can use. The first one is a map that is using the "Worldmap Panel" plugin and the second one is a map that is using the "OpenStreetMap" plugin that draw maritime routes. You can use the one that you like the most.


If you select a ship you can see the ship id, if you select that and do a right click and do a search in google, you can find more information about that vessel.


This article provided a comprehensive guide on using Voltmetrix for real-time AIS data analysis. You learned how to create a database, connect to an AIS stream, and insert data into the database using Python. Additionally, you discovered how to create an informative Grafana dashboard for data visualization. I hope this article inspires you to explore the possibilities of Voltmetrix further. If you have any questions, feel free to contact me through social media or our contact form.

Subscribe for product updates

By subscribing, you agree with our Terms and Privacy Policy.