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

> Display real-time ship tracking data in CesiumJS using AISStream. Learn how to proxy WebSockets and use SampledPositionProperty for live vessel positions in this technical guide.

- Repository: [Bilawal Sidhu/gods-eye-view](https://github.com/bilawalsidhu/gods-eye-view)
- Tags: how-to-guide
- Published: 2026-09-04

---

**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](https://github.com/bilawalsidhu/gods-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`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/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`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/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`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/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.

```javascript
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:

```javascript
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`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/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.

```javascript
// 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`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/cockpitTracking.js) and [`src/hud.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/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`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/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`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/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`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/vesselLabels.js).
- **Interaction Layer** – [`cockpitTracking.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/cockpitTracking.js) enables vessel selection and camera tracking, while [`aisWatchdog.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/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`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/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`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/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.