For the complete documentation index, see llms.txt. This page is also available as Markdown.

Embedding with traj2vec

Discretize AIS trajectories onto an H3 grid and train a t2vec-style encoder-decoder in PyTorch that embeds vessel tracks as fixed-length vectors.

Inspired by word2vec, traj2vec treats a vessel trajectory the way a language model treats a sentence. This tutorial discretizes raw AIS tracks onto an H3 hexagon grid so each trajectory becomes a sequence of spatial tokens, then trains a PyTorch encoder-decoder to predict the next cell, which yields a fixed-length embedding for every track as a byproduct. Trajectories that look alike end up close together in embedding space, ready for clustering, similarity search, or anomaly detection downstream.

What you will learn

  • Cleaning, segmenting, and interpolating raw AIS tracks with AISdb

  • Tokenizing trajectories into H3 cell sequences and building a vocabulary with special tokens

  • Writing NLP-style .src/.trg datasets with a train/validation/test split

  • Training a t2vec encoder-decoder with generative and triplet losses

  • Reading perplexity as a sanity check on next-cell prediction

Prerequisites

pip install aisdb torch h3 geopandas cartopy matplotlib seaborn scikit-learn tqdm nest_asyncio

The run captured on this page pulls a month of Gulf of St. Lawrence and Nova Scotia coastal traffic (January 2023) from a private PostgreSQL database, so the figures and metrics below are not reproducible without it. The pipeline itself runs against any AISdb database. For open data, download the NOAA day file for 2020-01-01 from coast.noaa.gov, decode the unzipped CSV with decode_msgs(..., source='NOAA') into a SQLiteDBConn, swap that connection for the PostgresDBConn below, and query the Gulf of Mexico window (xmin=-98, xmax=-80, ymin=24, ymax=31) for 2020-01-01 to 2020-01-02. Expect a smaller vocabulary and far fewer surviving tracks, a correctness check rather than a reproduction of the perplexity at the bottom of the page. The encoder-decoder itself comes from the t2vec reference implementation (github.com/boathit/t2vec); clone it and work from the repository root so model/, data_loader.py, and utils.py are importable.

Step 1. Query and clean the tracks

Raw AIS is noisy, so process_interval pulls a bounding-box query, drops inland and noisy points, splits tracks on three-hour gaps, filters implausible jumps and near-stationary segments, and interpolates everything to one-minute steps so each surviving segment is a clean, continuous trajectory.

import os
import json

import h3
import aisdb
import numpy as np
import geopandas as gpd
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from aisdb.database.dbconn import PostgresDBConn
from aisdb.denoising_encoder import encode_greatcircledistance, InlandDenoising
from aisdb.track_gen import min_speed_filter
from aisdb.database import sqlfcn
from datetime import datetime, timedelta
from tqdm import tqdm

import nest_asyncio
nest_asyncio.apply()

dbconn = PostgresDBConn(hostaddr='127.0.0.1', port=5432, user='postgres',
                        password=os.environ.get('POSTGRES_PASSWORD'), dbname='postgres')


def process_interval(dbconn, start, end):
    qry = aisdb.DBQuery(dbconn=dbconn, start=start, end=end,
                        xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax,
                        callback=aisdb.database.sqlfcn_callbacks.in_bbox_time_validmmsi)
    # decimate=False keeps every reported point instead of curve-decimating them
    rowgen = qry.gen_qry(fcn=sqlfcn.crawl_dynamic_static)
    tracks = aisdb.track_gen.TrackGen(rowgen, decimate=False)
    with InlandDenoising(data_dir='./data/tmp/') as remover:
        cleaned_tracks = remover.filter_noisy_points(tracks)
    # Split on time gaps, drop implausible segments, interpolate every minute.
    track_segments = aisdb.track_gen.split_timedelta(cleaned_tracks, time_split)
    tracks_encoded = encode_greatcircledistance(track_segments, distance_threshold=distance_split, speed_threshold=speed_split)
    tracks_encoded = min_speed_filter(tracks_encoded, minspeed=1)
    tracks_interpolated = aisdb.interp.interp_time(tracks_encoded, step=timedelta(minutes=1))
    return list(tracks_interpolated)

Step 2. Tokenize tracks onto the H3 grid

Each position maps to an H3 cell at resolution 6, and consecutive duplicates collapse into one token that keeps its entry timestamp, turning every trajectory into a sequence of discrete spatial tokens. The study region's bounding box comes from a hexagon-grid shapefile; on the NOAA drop-in, set the four bounds directly.

Step 3. Inspect and filter track lengths

Very short tracks carry little sequential structure and very long ones tend to be artifacts (a vessel that never left port, a corrupted timestamp), so look at the length distribution before training and keep tracks between 10 and 300 cells.

Raw AIS trajectories over the Gulf of St. Lawrence approaches and the Nova Scotia coastline, before H3 discretization or length filtering, private PostgreSQL corpus (January 2023).
Distribution of AIS track lengths in H3 cells before filtering, private PostgreSQL corpus (January 2023). Most tracks span fewer than 20 cells, with a long tail running out to several hundred.
Distribution of AIS track lengths in H3 cells after filtering to the 10-300 range, private PostgreSQL corpus (January 2023).

Step 4. Build the H3 vocabulary

Exactly as in NLP, every unique cell gets an integer index with three reserved for padding, start, and end of sequence; each track is then mapped to its integer sequence, plus lat/lon pairs recovered from the cells for later visualization.

Step 5. Split and write the dataset

The t2vec loader reads aligned text files, .src holds each sequence minus its last token, .trg the same sequence minus its first, .lat/.lon the coordinates, and _trj.t the full sequence, so we split 60/20/20 and write one line per trajectory.

Held-out test trajectories in raw lat/lon space after H3 tokenization and the train/validation/test split, private PostgreSQL corpus (January 2023).

Step 6. Train and evaluate

Training combines two objectives, a generative next-cell loss (negative log-likelihood, exactly next-word prediction in NLP) and a discriminative triplet margin loss that pulls embeddings of similar trajectories together and pushes different ones apart. The loop validates and checkpoints every save_freq iterations, a plateau scheduler decays the learning rate, and early stopping ends the run when validation stops improving. The upstream t2vec repo also ships a KL-divergence loss weighted by inter-cell distance; plain NLL is enough here.

The hyperparameters mirror the t2vec defaults scaled down to this corpus, with the vocabulary size taken from the mapping built in Step 4.

Results

The cumulative generative loss climbs across test batches simply because it is a running sum, not a per-batch average, so read only the final row. Normalized per token, the average loss came out to 0.2309, a perplexity of roughly 1.26.

Test-set log: cumulative genloss per iteration and final perplexity

A perplexity near 1 means the model is nearly certain about the next cell, which makes sense given how constrained vessel movement is by geography, channels, and traffic separation schemes. Treat it as a sanity check that the preprocessing, vocabulary, and encoder-decoder are wired together correctly, not as a benchmark of embedding quality. Nothing here has yet pulled the encoder's hidden state out and used it for similarity search, route clustering, or anomaly flagging, and that evaluation on the embedding vectors themselves is the natural next step.

Takeaway

  • H3 resolution is the main design choice. Resolution 6 cells span kilometers, so tight maneuvering collapses into a few tokens, while finer resolutions multiply the vocabulary and need more repeated visits per cell to learn from.

  • A perplexity of 1.26 on held-out tracks confirms the pipeline works, largely because shipping lanes make the next cell highly predictable.

  • The embeddings are the encoder's hidden states, and clustering, similarity search, or anomaly detection on them is where the model pays off.

  • One NOAA day validates the wiring end to end but will not reproduce month-scale metrics.

Next, Using Newtonian PINNs returns to continuous coordinate forecasting and adds physics-informed constraints so predicted tracks respect vessel kinematics.

Last updated