Maritimo

A distributed platform for receiving, processing and displaying real-time vessel movement data.

Screenshot showing a packed Rotterdam port
Screenshot showing a packed Rotterdam port

Role: Full-stack developer and project author
Focus: Distributed services, real-time data and geospatial interfaces
Technologies: React, Leaflet, C#, ASP.NET, Rust, Ruby, PostgreSQL, RabbitMQ, SignalR and Docker

Overview

Maritimo is a collection of applications that receive, decode, store and display Automatic Identification System data.

AIS transmitters broadcast information about vessels and other maritime objects, including their position, heading, speed and identity. Maritime authorities and vessel operators use this information for traffic monitoring, navigation and collision avoidance.

I created Maritimo to revisit a problem domain I had worked with throughout my career and explore how I would approach it using smaller, independently focused services and multiple programming languages.

Lisbon ferries sailing in the Tagus
Lisbon ferries sailing in the Tagus

The challenge

Developing an AIS platform requires more than displaying markers on a map. The system must maintain connections to external receiver stations, process a continuous stream of encoded messages, distribute the resulting data between services, persist historical information and update connected users in real time.

Access to representative live data was another important challenge. To test the system under realistic conditions, I integrated public feeds from the Norwegian Coastal Administration and contacted independent station operators willing to share their AIS data. Their contributions allowed me to test the decoder with a wider variety of real-world messages and observe how the system behaved under greater load.

Andøy AIS station page
The Andøy AIS station page

Architecture

I divided the platform into services with clearly defined responsibilities. RabbitMQ connected them through worker queues and publish-subscribe exchanges, allowing each component to process information independently while keeping the overall data flow loosely coupled.

Service
Responsibility
Station
Receives AIS messages from remote stations over TCP or UDP
Decoder
Decodes NMEA VDM/VDO messages
Persister
Stores decoded vessel and position data in PostgreSQL
Transmitter
Aggregates updates and delivers them to connected clients through SignalR
Web API
Exposes stored information through a REST API
Frontend
Displays live and historical vessel information using React and Leaflet

The decoder was written in Rust and supported ten AIS message types. The station service supplied it with encoded messages through a RabbitMQ worker queue, and the decoder published the resulting data for the other applications to consume. Rust provided predictable performance and strong compile-time guarantees for the most processing-intensive part of the platform.

The persistence, API and real-time services were developed using C# and ASP.NET. The persister stored decoded data in PostgreSQL, while the transmitter accumulated information about vessels and forwarded live updates to the frontend through SignalR. A separate Web API exposed historical information using REST.

The Bergensfjord vessel page
The Bergensfjord vessel page, showing its latest location data

Frontend

The React frontend combined historical information from the REST API with live updates received through SignalR. Leaflet provided the interactive map used to display vessels, stations and their latest known positions.

The interface allowed users to inspect individual vessels and receiver stations while continuing to receive position updates without refreshing the page.

Testing and delivery

The services were supported by automated tests and CI/CD pipelines. Docker and Docker Compose provided consistent local environments and allowed the full collection of services and infrastructure dependencies to be started together.

What I learned

Maritimo gave me an opportunity to explore service boundaries, asynchronous messaging and interoperability between different programming languages in a realistic, data-intensive application.

It also reinforced the importance of testing distributed workflows with representative live data rather than treating each service as an isolated component.