Skip to content

How World Monitor Tracks Military Activity: Aircraft, Vessels, and Theater Posture

Open-source military tracking used to mean scrolling through Twitter threads and piecing together fragments from enthusiast forums. When I set out to build World Monitor’s military tracking capabilities, I wanted something different: a system that could aggregate ADS-B aircraft data, AIS vessel positions, and strategic analysis into a coherent operational picture - all without classified sources.

Here’s how I built it, where I hit walls, and what the final system actually does.

The Problem: Fragmented OSINT

Military tracking through open sources has three main pillars:

  1. Aircraft tracking via ADS-B transponders
  2. Vessel tracking via AIS transponders
  3. Strategic analysis - what does all this data mean?

Each pillar has its own data sources, formats, and limitations. OpenSky Network provides ADS-B data but doesn’t tell you if a flight is military. AIS gives you vessel positions but not what those vessels are doing. And theater posture assessment? That’s usually locked behind paywalls or requires painstaking manual analysis.

I needed to combine all three into something usable.

Starting with ADS-B: Aircraft Tracking

ADS-B (Automatic Dependent Surveillance–Broadcast) is how modern aircraft broadcast their position, altitude, and velocity. Most military aircraft transmit ADS-B when not in sensitive operations, making OpenSky Network a goldmine for tracking.

The Basic Setup

First, I needed OpenSky credentials:

OPENSKY_CLIENT_ID=your_client_id
OPENSKY_CLIENT_SECRET=your_client_secret

OpenSky’s API returns raw ADS-B data - hex codes, callsigns, positions. But raw data doesn’t tell you “this is a KC-135 tanker” or “that’s a P-8 Poseidon.”

The Enrichment Problem

I initially tried matching hex codes against a static database. That failed fast - aircraft registrations change, military callsigns rotate, and I was constantly behind.

Then I discovered Wingbits. They maintain a crowd-sourced aircraft database with operator information. The integration looks like this:

# Simplified enrichment flow
def enrich_aircraft(hex_code: str, callsign: str):
# Query Wingbits for aircraft details
wingbits_data = wingbits_api.get_aircraft(hex_code)
# Determine military confidence
if wingbits_data.operator in KNOWN_MILITARY_OPERATORS:
return "Confirmed"
elif wingbits_data.type in MILITARY_ONLY_TYPES:
return "Likely" # e.g., KC-135, E-3 AWACS
elif wingbits_data.registration.startswith("N") and in_military_airspace:
return "Possible"
else:
return "Civilian"

The Classification Matrix

After much iteration, I settled on this confidence classification:

ConfidenceCriteriaExample
ConfirmedOperator matches known military branch/contractor”USAF” in operator field
LikelyAircraft type exclusively militaryKC-135, E-3A, P-8
PossibleGovernment-registered in military areaState aircraft near bases
CivilianNo military indicatorsCommercial airliners

The key insight: military aircraft often fly with transponders on during training, tanker missions, and routine operations. They go dark for sensitive missions - which is itself intelligence.

Vessel Tracking: AIS and the USNI Problem

AIS (Automatic Identification System) is the maritime equivalent of ADS-B. Vessels over 300 tons broadcast their position, course, and speed. Military vessels often transmit AIS, especially in congested waters.

Getting AIS Data

I integrated AISStream.io for real-time vessel positions:

# AISStream connection
async def stream_ais_data():
async with aisstream.connect(API_KEY) as stream:
async for message in stream:
if is_military_mmsi(message.mmsi):
await process_military_vessel(message)

But AIS alone misses a lot. Submarines don’t transmit. Warships in sensitive operations go dark. And AIS gaps themselves are signals.

Enter USNI Fleet Reports

The U.S. Naval Institute publishes weekly fleet deployment reports. These are manually-curated intelligence summaries listing carrier groups, amphibious ready groups, and their operating areas.

The problem: they’re text. Unstructured text.

I built an ingestion pipeline that:

  1. Fetches the weekly report
  2. Extracts hull numbers and vessel names
  3. Matches against AIS positions
  4. Generates “synthetic positions” for unmatched entries (submarines, dark vessels)
def process_usni_report(report_text: str):
vessels = extract_vessel_mentions(report_text)
for vessel in vessels:
# Try to match with live AIS
ais_match = find_ais_position(vessel.hull_number)
if ais_match:
merge_tracking_data(vessel, ais_match)
else:
# Generate synthetic position for dark vessel
create_synthetic_position(
vessel,
region=vessel.reported_region,
confidence="synthetic"
)

This dual-source approach (AIS + USNI) dramatically improved coverage. Carrier groups that went dark for operations still appeared in USNI reports, giving me approximate positions.

Theater Posture Assessment: Making Sense of the Data

Raw tracking data is noise. I needed to answer: is the situation escalating?

I defined nine operational theaters based on strategic importance:

Iran / Persian Gulf -> Carrier groups, tanker activity, AWACS
Taiwan Strait -> PLAAF sorties, USN carrier presence
Baltic / Kaliningrad -> Russian Western Military District flights
Korean Peninsula -> B-52/B-1 deployments, DPRK missile activity
Eastern Mediterranean -> Multi-national naval exercises
Horn of Africa -> Anti-piracy patrols, drone activity
South China Sea -> Freedom of navigation operations
Arctic -> Long-range aviation patrols
Black Sea -> ISR flights, naval movements

The Escalation Algorithm

Each theater has baseline “normal” activity. Deviations trigger escalation:

def assess_theater_posture(theater: Theater):
score = 0
# Aircraft count deviation
aircraft_score = calculate_deviation(
current=theater.military_aircraft_count,
baseline=theater.baseline_aircraft
)
# Strike capability detection
# Tankers + AWACS + Fighters = strike package
has_strike_package = (
theater.tanker_count > 0 and
theater.awacs_count > 0 and
theater.fighter_count >= 4
)
if has_strike_package:
score += 15 # Significant escalation indicator
# Naval presence
score += theater.carrier_count * 10
score += theater.combatant_count * 3
# Country instability amplification
if theater.host_country.instability_index > 0.6:
score *= 1.25
# Map to posture level
if score >= 30:
return PostureLevel.CRITICAL
elif score >= 15:
return PostureLevel.ELEVATED
else:
return PostureLevel.NORMAL

The strike package detection was a breakthrough. A tanker orbiting with AWACS and fighters nearby isn’t just “activity” - it’s preparation. That combination gets weighted heavily.

Surge Detection

Beyond posture, I needed to detect surges - rapid deployments:

SURGE_PATTERNS = {
"carrier_group": lambda vessels: vessels.carrier + vessels.escorts >= 5,
"amphibious_ready_group": lambda vessels: vessels.lhd + vessels.landing >= 3,
"exercise": lambda vessels: vessels.combatants_in_formation >= 4,
"transit": lambda vessels: vessels.course_variance < 0.2 # Moving together
}

When a surge is detected, I flag it. Multiple surges in a theater correlate with higher escalation risk.

Infrastructure Correlation: Where Are They Going?

Aircraft and vessels don’t operate in a vacuum. They need bases, ports, and support infrastructure.

I built a static database of:

  • 226 military bases from 9 operators (US, UK, France, Japan, etc.)
  • Nuclear facilities - power plants, weapons labs, enrichment sites
  • 12 major spaceports - relevant for missile test detection
  • 62 strategic ports - chokepoint monitoring

When I detect a surge near a base, I can immediately surface:

Surge Detected: Eastern Mediterranean
- USS Eisenhower CSG operating near:
- Incirlik Air Base (USAF)
- Souda Bay Naval Base (USN)
- Escalation indicators:
- 12x confirmed military aircraft in theater
- 3x KC-135 tankers orbiting
- Strike package probability: HIGH

The Alert System

All this tracking feeds into alerts. Not every military flight is interesting, but certain combinations are:

  1. Strike package assembly - fighters, tankers, AWACS converging
  2. Carrier group deployment - CSG leaving homeport
  3. Theater escalation - posture shift from NORMAL to ELEVATED
  4. AIS gap detection - military vessel stops transmitting in sensitive area
  5. Foreign presence - non-NATO military activity near alliance territory

Each alert type has different urgency and different notification channels.

The Coverage Problem

No system is perfect. AIS has significant gaps:

Strong coverage: European waters, Atlantic, major ports
Weak coverage: Middle East, open ocean, remote regions
No coverage: Submarines, vessels with AIS disabled

Satellite AIS exists but requires commercial licenses. For World Monitor, I’m transparent about these limitations. An AIS gap near a known submarine base isn’t missing data - it’s a signal.

What This Enables

After building all this, what can you actually do?

Regional Monitoring: Watch specific theaters for escalation indicators. Is the Persian Gulf heating up? Are PLAAF sorties increasing near Taiwan?

Crisis Response: Detect deployments early. Carrier groups take days to reach operating areas - early detection provides warning time.

Trade Analysis: Correlate naval movements with shipping disruptions. Military activity often precedes commercial impacts.

Research: Study patterns over time. How often does the US deploy carrier groups to the Mediterranean? What’s the seasonal pattern of Arctic flights?

Key Takeaways

Building this taught me several lessons:

  1. Combine sources: No single source gives the full picture. ADS-B + AIS + USNI reports = comprehensive coverage.

  2. Transparency matters: Acknowledge gaps. AIS gaps are data, not failures.

  3. Classification is hard: Military vs. civilian isn’t binary. Confidence levels (Confirmed/Likely/Possible) capture the uncertainty.

  4. Context is everything: A KC-135 orbiting alone is training. A KC-135 with AWACS and fighters is preparation. Contextual analysis matters more than individual tracks.

  5. Open-source works: You don’t need classified access to build meaningful intelligence. Aggregation and correlation create value.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments