When our team at a maritime logistics startup set out to build Navio-Petroleiro, we weren't just stitching together a few map pins. We were engineering a real-time vessel tracking mobile application that had to ingest, process. And display thousands of oil tanker positions per second-across spotty satellite links and offshore dead zones. The app needed to feel instant, even when the user's connection wasn't.
Behind every blinking dot on a maritime map lies a torrent of real-time AIS data, a Kafka cluster. And a battle-tested resilience pattern we learned the hard way. In this deep-dive, I'll walk through the architectural choices, the parsing nightmares. And the infrastructure decisions that turned a simple idea into a production-grade mobile platform for monitoring navios petroleiros worldwide.
This article is for senior engineers and technical readers who want to see how a modern mobile app can handle high-frequency geospatial data, offline synchronization. And security at sea-not just another "how to use an API" tutorial. Let's pull back the curtain on the stack behind Navio-Petroleiro.
Architectural Decisions for a High-Frequency Data Pipeline
Tracking oil tankers-the enormous ships that carry crude oil across oceans-requires consuming ITU-R M. 1371 AIS messages from terrestrial receivers and satellite constellations. During peak hours, we saw bursts of over 8,000 position reports per minute from just the global fleet of navios petroleiros. A straightforward REST polling architecture would collapse under that load. So we built an event-driven pipeline with Apache Kafka and Protocol Buffers.
On the ingestion side, a lightweight Go service acts as a TCP server for raw NMEA 0183 streams from multiple AIS aggregators. It decodes the sentences, normalizes MMSI identifiers. And publishes to Kafka topics sharded by geographical quadrant. This partitioning is critical: it lets downstream consumers process only the vessels in their region of interest, dramatically cutting latency. For the mobile client of Navio-Petroleiro, a dedicated WebSocket gateway (written in Elixir using Phoenix Channels) subscribes to relevant Kafka partitions and pushes updates with sub-second delay.
Parsing NMEA 0183 Messages: Lessons from the Field
Early on, we underestimated the ugliness of real-world AIS data. The NMEA 0183 sentences that carry vessel position reports are supposed to follow strict checksum rules, but we encountered missing fields, corrupted talker IDs. And timestamp rollovers from poorly maintained onboard transponders. Our first Python parser threw exceptions so frequently that we had to redesign the ingestion pipeline with a "parse and forward" approach, never blocking the stream.
We adopted a stateful parser implemented in Rust, Using the nom combinator library to handle the bit-level packing of AIS message types 1, 2, 3. and 5-the ones crucial for identifying and locating a navio-petroleiro. The parser emits structured records enriched with metadata such as data source quality - receiver altitude. And a confidence score derived from signal strength. This allowed Navio-Petroleiro's backend to discard spoofed or low-quality AIS entries before they ever reached a user's map. Explore our internal guide on AIS data quality scoring.
Geospatial Indexing and Real-Time Fleet Visualization
Rendering thousands of oil tanker icons on a mobile map is a classic performance bottleneck. Navio-Petroleiro uses Mapbox GL Native with a custom vector tile server built on PostGIS and ST_AsMVT. When a user zooms into the Persian Gulf or the Strait of Malacca-areas dense with tanker traffic-the tile server dynamically aggregates vessel positions into hexagonal bins, returning only the cluster centroids and a count. This reduces the draw calls on the GPU from thousands to dozens.
For individual ship identification, we implemented a client-side quadtree based on supercluster (a fast geospatial point clustering library) so the app can quickly find all navios petroleiros under a user's tap. The quadtree is rebuilt on every WebSocket update frame, roughly 10 times per second, without jank because the computation runs on a background isolate (in Flutter) or a concurrent queue (on native iOS/Android). This combination of server-side aggregation and client-side indexing keeps the interface responsive even on older devices.
Offline-First Design with Conflict-Free Replicated Data Types
Maritime crews often lose connectivity for hours while transiting remote waters. Yet they need to review the last known positions of nearby tankers. Navio-Petroleiro treats vessel state as a cooperative data structure that can be merged across devices without central coordination. We embedded Automerge (a CRDT library) into the local SQLite database layer, so that updates to ship attributes-like destination, ETA. Or draught-are conflict-free replicated when the app reconnects.
The core trick: each AIS report is converted into a CRDT "list entry" keyed by MMSI and timestamp. Even if two users edit a ship's alias simultaneously (a feature for fleet managers), the app merges the last-write-wins register with a vector clock. This offline capability isn't just a UX nicety; it made Navio-Petroleiro the primary tool for tanker tracking during a major submarine cable cut in Southeast Asia, when land-based internet failed for 36 hours but vessel-to-vessel mesh networks kept data flowing. Read our case study on resilient networking at sea.
Securing Vessel Data and Preventing AIS Spoofing
AIS was never designed with authentication, making it trivial to inject fake ship positions. A malicious actor could spoof a navio-petroleiro's location to create confusion or disrupt shipping routes. To combat this, we integrated multiple integrity layers. First, our Kafka stream processor cross-references each AIS message against three independent data sources: satellite Doppler geolocation, coastal radar feeds (when available), and historical trajectory models using a Kalman filter.
Second, we deployed a lightweight zero-knowledge proof (ZKP) verification circuit for critical ship identity attributes. While not yet mandated by IMO, we worked with a consortium to pilot draft-ietf-lamps-aikm, extending vessel certificates for AIS messages. This experimental layer allows Navio-Petroleiro to display a "verified" badge for oil tankers that have cryptographically bound their public key to the MMSI, significantly raising the bar for spoofing.
Scalable Push Notifications for Geofence and Alerting
Fleet managers need instant alerts when a navio-petroleiro enters a restricted area or deviates from its planned route. We built a rules engine on top of Apache Flink, evaluating complex event patterns such as "vessel speed
To avoid notification storms during geopolitical crises, we implemented a deduplication mechanism using a Redis-backed Bloom filter with a sliding 30โminute window. This ensures that a drifting tanker off the coast of Venezuela only triggers one push per user, not a continuous flood. The architecture was loadโtested to 50,000 concurrent geofences and held steady, thanks to Flink's stateful stream processing and fineโgrained checkpointing every 5 seconds.
Monitoring and Observability with OpenTelemetry
A mobile app that depends on realโtime external data sources is only as reliable as its observability. We instrumented Navio-Petroleiro's entire backend-from the Kafka brokers to the mobile client itself-with OpenTelemetry traces, metrics, and structured logs. Each user tap that requests details of a navio petroleiro generates a trace that spans the WebSocket gateway, the PostGIS query. And the Mapbox tile fetch, all correlated via W3C Trace Context headers.
We built a custom Grafana dashboard that overlays backend latency with clientโside rendering time. Which let us pinpoint a regression where a new algorithm for calculating collision risk quadrupled CPU usage on midโrange Android devices. Exposing those metrics directly in the CI/CD pipeline with Lighthouseโstyle thresholds prevented similar regressions from reaching production.
Performance Optimizations for Rendering Thousands of Vessel Markers
The screen of Navio-Petroleiro can easily show 3,000 tanker icons when zoomed out. We learned the hard way that even with Mapbox's symbol layer, too many markers cause frame drops. The solution was a hybrid approach: use a custom WebGL heatmap layer for the density overview at low zoom levels, then switch to individual glyphs only when the viewport contains fewer than 200 vessels. We built this layer with deck gl and its HeatmapLayer, passing preโaggregated quadkey tiles from the server.
On iOS, we leveraged Metal performance shaders to blur the heatmap in real time. While on Android we used Vulkan compute via a thin wrapper around RenderScript. This dualโrendering path means that even when the global fleet of navios petroleiros is in view, the app maintains a smooth 60 fps. One unexpected win: the heatmap doubled as a visual indicator of shipping lane congestion. Which captains now use to plan routes.
CI/CD Pipeline and Automated Testing for Maritime Data
Ship tracking isn't a domain where you want to discover a bug after deploying to the App Store. We built a simulation framework that replays 72 hours of historical AIS traffic through the entire pipeline, from ingestion to push notification. The framework runs inside a GitHub Actions workflow, spinning up Kafka, PostgreSQL. And Redis containers using a Docker Compose environment that mirrors production.
We defined propertyโbased tests using Hypothesis (Python) that generate corrupted NMEA sentences and verify that the parser never panics or slows the stream
.Need a Custom App Built?
Let's discuss your project and bring your ideas to life.
Contact Me Today โ