How to Display Real-Time Ship Tracking Data (AISStream) in CesiumJS

God's Eye View streams live vessel positions from AISStream into CesiumJS by proxying the WebSocket through a Vite plugin that buffers 64-sample tracks per MMSI, then serves JSON snapshots to the browser where SampledPositionProperty drives entity positions in a CustomDataSource.

The open-source God's Eye View repository demonstrates a production-ready integration of the AISStream service with CesiumJS, enabling real-time visualization of global maritime traffic. This implementation handles WebSocket management server-side via a custom Vite plugin while keeping the browser lightweight through JSON snapshot polling. The architecture efficiently processes AIS messages, maintains per-vessel track history, and renders colored chevron billboards that update dynamically on the globe.

Architecture Overview

The AISStream pipeline operates through three distinct layers to ensure real-time performance without overwhelming the browser with WebSocket overhead.

  1. Vite Plugin Socket Manager – Maintains a persistent WebSocket connection to wss://stream.aisstream.io/v0/stream, parses incoming AIS envelopes, and stores per-MMSI track buffers server-side.
  2. REST Snapshot Endpoint – Serves the current vessel map at /api/ais as lightweight JSON, allowing the front-end to poll for updates rather than maintaining a direct socket connection.
  3. CesiumJS Entity Layer – Consumes snapshots to create or update Cesium.Entity objects within a Cesium.CustomDataSource, using SampledPositionProperty for smooth interpolation between position reports.

Server-Side WebSocket Configuration

According to the bilawalsidhu/gods-eye-view source code, the heavy lifting of AISStream integration resides in vite.config.js, where a custom plugin manages the socket lifecycle and data normalization.

WebSocket Connection and Proxy Settings

Lines 1338-1355 of vite.config.js configure the proxy to the AISStream endpoint. The plugin establishes a single WebSocket connection per API key and defines default bounding boxes for the initial stream subscription, message types to filter, and cache limits for memory management.

Watchdog Policy for Connection Stability

To ensure robust streaming, lines 1359-1365 implement a watchdog policy that monitors connection health. According to src/data/aisWatchdog.js, this state machine enforces silence-report timeouts, connection recycling intervals, and exponential back-off timings for reconnection attempts. The watchdog prevents resource leaks and ensures the stream recovers automatically from network interruptions.

AIS Message Processing and Track Buffering

Lines 1410-1460 handle the critical data transformation logic. As AIS messages arrive, the plugin parses each envelope and maintains an in-memory map named _aisStreamVessels. For each MMSI (Maritime Mobile Service Identity), the system preserves a rolling buffer of the last 64 position samples, creating a short-term track history for trail visualization and position interpolation.

Client-Side CesiumJS Implementation

The browser-facing code leverages CesiumJS primitives to render the buffered telemetry efficiently.

Creating the AISStream Custom Data Source

The front-end initializes a dedicated layer for maritime traffic using Cesium.CustomDataSource. This separation allows developers to toggle vessel visibility independently from other globe entities like terrain or imagery.

const viewer = new Cesium.Viewer('cesiumContainer');
const aisSource = new Cesium.CustomDataSource('AISStream');
viewer.dataSources.add(aisSource);

Entity Position Interpolation with SampledPositionProperty

Rather than snapping entities to discrete coordinates, the implementation uses Cesium.SampledPositionProperty to smoothly interpolate vessel movement between AIS updates. Each vessel entity receives a sampled property populated from the 64-point track buffer, creating fluid motion across the globe surface.

The addOrUpdateVessel function (adapted from the repository's data flow) constructs these properties by converting geographic coordinates to Cartesian3 and timestamping each sample with Julian dates:

const position = new Cesium.SampledPositionProperty();
record.track.forEach(sample => {
  const cart = Cesium.Cartesian3.fromDegrees(sample.lon, sample.lat, sample.alt ?? 0);
  position.addSample(
    Cesium.JulianDate.fromDate(new Date(sample.time * 1000)), 
    cart
  );
});

Ship Type Visualization

Visual distinction between cargo vessels, tankers, and passenger ships relies on src/data/vesselLabels.js. This module exports a vesselHue function that maps AIS ship-type codes to CSS hex colors. The Cesium entities apply these hues to their billboard properties, rendering color-coded chevrons that allow operators to identify vessel categories at a glance.

Complete Implementation Example

Below is a minimal, runnable integration that polls the snapshot endpoint and updates Cesium entities. This pattern mirrors the production implementation in the God's Eye View codebase.

// Initialize Cesium Viewer
const viewer = new Cesium.Viewer('cesiumContainer', {
  terrainProvider: Cesium.createWorldTerrain()
});

// Create the custom data source
const aisSource = new Cesium.CustomDataSource('AISStream');
viewer.dataSources.add(aisSource);

// Import or define the vessel color helper from vesselLabels.js
function vesselHue(shipType) {
  // Simplified mapping; see src/data/vesselLabels.js for complete codes
  const hues = {
    70: '#00FF00', // Cargo
    80: '#FF0000', // Tanker
    60: '#0000FF'  // Passenger
  };
  return hues[shipType] || '#FFFFFF';
}

// Helper to add or update vessel entities
function addOrUpdateVessel(record) {
  const id = `ais-${record.mmsi}`;
  let entity = aisSource.entities.getById(id);
  
  // Build sampled position from buffered track
  const position = new Cesium.SampledPositionProperty();
  record.track.forEach(sample => {
    const cart = Cesium.Cartesian3.fromDegrees(sample.lon, sample.lat, sample.alt ?? 0);
    position.addSample(
      Cesium.JulianDate.fromDate(new Date(sample.time * 1000)), 
      cart
    );
  });
  
  const color = Cesium.Color.fromCssColorString(vesselHue(record.shipType));
  
  if (!entity) {
    // Create new entity with billboard and label
    entity = aisSource.entities.add({
      id,
      name: `MMSI ${record.mmsi}`,
      position,
      billboard: {
        image: 'chevron.png', // Placeholder for chevron sprite
        color,
        verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
        scale: 0.8
      },
      label: {
        text: `MMSI ${record.mmsi}`,
        font: '12px sans-serif',
        fillColor: color,
        pixelOffset: new Cesium.Cartesian2(0, -30)
      },
      properties: record // Store raw AIS for cockpit tracking
    });
  } else {
    // Update existing entity
    entity.position = position;
    entity.billboard.color = color;
    entity.properties = record;
  }
}

// Poll the snapshot endpoint every 5 seconds
async function pollAis() {
  try {
    const response = await fetch('/api/ais');
    const { vessels } = await response.json();
    vessels.forEach(addOrUpdateVessel);
    
    // Update HUD counter if available
    const hudElement = document.getElementById('hud-ais-vessel');
    if (hudElement) hudElement.textContent = vessels.length;
  } catch (error) {
    console.warn('AIS snapshot fetch failed:', error);
  }
  setTimeout(pollAis, 5000);
}

// Start polling
pollAis();

User Interaction and Tracking

The repository extends basic visualization with interactive features defined in src/cockpitTracking.js and src/hud.js.

Cockpit Tracking Mode – Clicking a vessel entity triggers a camera lock that follows the ship's SampledPositionProperty, creating a first-person perspective from the vessel's current heading. The selection logic retrieves the entity's stored properties to display real-time metadata.

HUD Integration – The src/hud.js module maintains the #hud-ais-vessel DOM element, displaying the live count of tracked ships updated from each snapshot response.

Summary

  • WebSocket Proxying – The Vite plugin in vite.config.js manages the AISStream connection server-side, parsing messages and maintaining 64-sample track buffers per MMSI in _aisStreamVessels.
  • Snapshot API – The browser fetches lightweight JSON from /api/ais rather than handling raw WebSocket traffic, optimizing front-end performance.
  • Cesium Primitives – Vessels render as Cesium.Entity objects within a CustomDataSource, using SampledPositionProperty for smooth interpolation and color-coded billboards from vesselLabels.js.
  • Interaction Layer – cockpitTracking.js enables vessel selection and camera tracking, while aisWatchdog.js ensures connection resilience through automated health monitoring.

Frequently Asked Questions

What is AISStream and how does it provide ship tracking data?

AISStream is a public WebSocket service that broadcasts Automatic Identification System (AIS) messages from global maritime traffic. According to the implementation in vite.config.js, the service endpoint wss://stream.aisstream.io/v0/stream transmits vessel position reports, static data, and voyage information that the Vite plugin parses into structured records for Cesium visualization.

Why use a Vite plugin instead of connecting directly from the browser?

The Vite plugin architecture serves two critical purposes: it protects the AISStream API key by keeping it server-side, and it normalizes the high-frequency WebSocket feed into manageable snapshots. By buffering 64 position samples per vessel and exposing a REST endpoint at /api/ais, the plugin prevents browser memory bloat and allows the front-end to poll at 5-second intervals without maintaining a persistent socket connection.

How does CesiumJS interpolate vessel positions between AIS updates?

The implementation uses Cesium.SampledPositionProperty to create time-tagged position samples from the buffered track data. When the front-end receives a snapshot, it populates the property with the vessel's recent history, allowing Cesium's renderer to interpolate smoothly between the 15-second AIS broadcast intervals rather than displaying jerky discrete jumps.

What happens when a vessel loses AIS signal or leaves the bounding box?

The aisWatchdog.js state machine monitors connection health and enforces silence-report thresholds. If a vessel stops transmitting, the buffered track eventually clears from _aisStreamVessels after the recycle timeout, and the entity disappears from subsequent /api/ais snapshots. The Cesium front-end automatically removes entities that no longer appear in the JSON response, keeping the display synchronized with actual maritime traffic.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →