Historical Flight Pattern Analysis
Historical analysis transforms raw flight data into actionable intelligence about deportation operations. This guide covers FOIA strategies, pattern detection, and geospatial visualization.
FOIA Data Acquisition
The ARTS Database
The Alien Repatriation Tracking System (ARTS) is ICE's internal database for deportation logistics. Access requires FOIA litigation.
| Attribute | Description |
|---|---|
| Custodian | ICE Enforcement and Removal Operations (ERO) |
| Content | Flight manifests, passenger counts, destinations |
| Access | FOIA (often requires litigation) |
| Key success | UWCHR obtained massive ARTS extracts |
Effective FOIA Requests
| Target Document | Agency | Request Language |
|---|---|---|
| Flight manifests | ICE ERO | "All flight manifests for removal operations..." |
| Contractor invoices | ICE | "Invoices from aviation contractors..." |
| End of Mission reports | ICE Air | "All EOM reports for charter operations..." |
| Flight logs | ICE Air | "Pilot logs for aircraft N-XXXXX..." |
Sample FOIA Request
Pursuant to the Freedom of Information Act, 5 U.S.C. § 552,
I request copies of the following records:
1. All flight manifests for ICE Air Operations removal and
transfer flights from [start date] to [end date]
2. All "End of Mission" reports for charter aircraft used
in ICE removal operations during the same period
3. All records identifying aircraft tail numbers (N-numbers)
and contractors used for removal flights
4. All invoices from CSI Aviation, GlobalX Airlines, and
Air Wisconsin related to ICE Air Operations
I request a fee waiver pursuant to 5 U.S.C. § 552(a)(4)(A)(iii)
as disclosure serves the public interest in government transparency.
Handling Delays and Denials
| Agency Response | Counter-Strategy |
|---|---|
| No responsive records | Reframe request, cite known operations |
| Exemption (b)(7)(E) | Appeal; routine logistics are not techniques |
| Fee estimate | Narrow scope, request fee waiver |
| Indefinite delay | File lawsuit (often the only path to ARTS) |
Historical Data Sources
Public Repositories
| Source | Description | Access |
|---|---|---|
| UWCHR ARTS data | FOIA-obtained manifests | Published datasets |
| ADS-B Exchange historical | Past 14 days of flights | API access |
| OpenSky Network | Academic research access | Research agreement |
| Witness at the Border | Analyzed flight records | Publications |
Bulk Historical ADS-B Data
| Provider | Retention | Access |
|---|---|---|
| ADS-B Exchange | 14 days via API | Subscription |
| OpenSky Network | Years of archived data | Research access |
| Local receiver logs | As configured | Self-hosted |
Archiving Your Own Data
import requests
import json
from datetime import datetime
import sqlite3
def archive_flight_data():
"""Archive current aircraft positions to SQLite"""
conn = sqlite3.connect('flight_archive.db')
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS positions (
timestamp DATETIME,
hex TEXT,
flight TEXT,
lat REAL,
lon REAL,
altitude INTEGER,
ground_speed REAL,
track REAL
)
''')
response = requests.get("http://localhost/tar1090/data/aircraft.json")
data = response.json()
timestamp = datetime.utcnow()
for ac in data.get("aircraft", []):
cursor.execute('''
INSERT INTO positions VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', (
timestamp,
ac.get("hex"),
ac.get("flight"),
ac.get("lat"),
ac.get("lon"),
ac.get("alt_baro"),
ac.get("gs"),
ac.get("track")
))
conn.commit()
conn.close()
Flight Categorization
Operational Categories
| Category | Definition | Identification |
|---|---|---|
| Removal | Direct international deportation | Origin: US hub → Destination: Foreign |
| Removal Return | Empty repositioning flight | Destination: US hub, fast/high profile |
| Removal Connection | Domestic leg before removal | Same aircraft, same day as removal |
| Shuffle | Domestic inter-facility transfer | Both endpoints domestic |
Category Identification Algorithm
def categorize_flight(origin, destination, same_day_flights):
"""Categorize a flight segment"""
US_STAGING_HUBS = ["AEX", "BRO", "SAT", "IWA", "MIA"]
FOREIGN_AIRPORTS = ["PAP", "SJO", "GUA", "MEX", "SAL"]
origin_is_us = origin in US_STAGING_HUBS
dest_is_foreign = destination in FOREIGN_AIRPORTS
dest_is_us = destination in US_STAGING_HUBS
if origin_is_us and dest_is_foreign:
return "REMOVAL"
elif dest_is_us and not origin_is_us:
# Check if this is a return leg
for flight in same_day_flights:
if flight["category"] == "REMOVAL":
return "REMOVAL_RETURN"
return "SHUFFLE"
elif origin_is_us and dest_is_us:
# Check if there's a removal later that day
for flight in same_day_flights:
if flight["origin"] == destination and flight["category"] == "REMOVAL":
return "REMOVAL_CONNECTION"
return "SHUFFLE"
return "UNKNOWN"
Pattern Analysis Methods
Temporal Patterns
| Pattern | Indicator | Analysis Method |
|---|---|---|
| Daily rhythm | Peak departure times | Time-series histograms |
| Weekly patterns | Day-of-week variations | Weekly aggregation |
| Surge detection | Sudden volume increases | Standard deviation analysis |
| Seasonal trends | Long-term variations | Moving averages |
Surge Detection Algorithm
import numpy as np
from collections import defaultdict
def detect_surge(flight_data, baseline_days=30, threshold_std=2):
"""Detect enforcement surges by comparing to baseline"""
# Group flights by date
daily_counts = defaultdict(int)
for flight in flight_data:
date = flight["timestamp"].date()
daily_counts[date] += 1
# Calculate baseline statistics
counts = list(daily_counts.values())
baseline_mean = np.mean(counts[-baseline_days:-1])
baseline_std = np.std(counts[-baseline_days:-1])
# Check most recent day
latest_count = counts[-1]
z_score = (latest_count - baseline_mean) / baseline_std
if z_score > threshold_std:
return {
"surge_detected": True,
"current_count": latest_count,
"baseline_mean": baseline_mean,
"deviation": z_score
}
return {"surge_detected": False}
Route Pattern Analysis
| Pattern | Description | Detection |
|---|---|---|
| Direct routes | Nonstop to destination | Single origin-destination pair |
| Layover routes | Intermediate stops | Multiple legs, same aircraft |
| Third-country transfers | Indirect deportation | Non-standard destination sequencing |
| Hub concentration | Staging pattern | Geographic clustering |
Geospatial Analysis
Tools and Libraries
| Tool | Use Case | Access |
|---|---|---|
| QGIS | Desktop GIS analysis | Open source |
| PostGIS | Spatial database | Open source |
| Geopandas | Python geospatial | Python library |
| Folium | Interactive web maps | Python library |
| Kepler.gl | Large-scale visualization | Web platform |
Creating Flight Path Maps
import folium
import pandas as pd
def create_flight_map(flight_positions):
"""Generate interactive map of flight paths"""
# Create base map centered on US
m = folium.Map(location=[39.8283, -98.5795], zoom_start=4)
# Group positions by flight
for hex_code in flight_positions["hex"].unique():
flight_data = flight_positions[flight_positions["hex"] == hex_code]
# Create path from positions
coordinates = list(zip(
flight_data["lat"],
flight_data["lon"]
))
# Add flight path
folium.PolyLine(
coordinates,
weight=2,
color='red',
opacity=0.8,
popup=f"Flight: {hex_code}"
).add_to(m)
return m
Heatmap Generation
from folium.plugins import HeatMap
def create_activity_heatmap(positions):
"""Generate heatmap of flight activity"""
m = folium.Map(location=[39.8283, -98.5795], zoom_start=4)
# Extract coordinates
heat_data = [[row["lat"], row["lon"]] for _, row in positions.iterrows()]
# Add heatmap layer
HeatMap(heat_data).add_to(m)
return m
Staging Hub Analysis
| Analysis | Method |
|---|---|
| Hub identification | Cluster analysis of departure points |
| Throughput | Count departures per airport per period |
| Routing patterns | Origin-destination matrices |
| Capacity utilization | Compare to known fleet size |
Correlation Analysis
Linking Flights to Events
| Data Source | Correlation Target |
|---|---|
| News reports | Raid announcements, policy changes |
| Court filings | Emergency stay denials |
| Rapid response logs | Community reports of arrests |
| Government announcements | Operation declarations |
Example: Operation Surge Detection
def correlate_with_events(flight_data, events):
"""Correlate flight surges with known events"""
correlations = []
for event in events:
event_date = event["date"]
# Count flights 3 days before/after event
window_flights = [
f for f in flight_data
if abs((f["timestamp"].date() - event_date).days) <= 3
]
# Check for elevated activity
if len(window_flights) > baseline_threshold:
correlations.append({
"event": event["description"],
"date": event_date,
"flight_count": len(window_flights),
"aircraft": list(set(f["hex"] for f in window_flights))
})
return correlations
Visualization for Advocacy
Effective Presentations
| Visualization Type | Best For |
|---|---|
| Route maps | Showing deportation destinations |
| Time series | Demonstrating surge patterns |
| Heatmaps | Geographic concentration |
| Infographics | Public education |
| Interactive dashboards | Ongoing monitoring |
Key Metrics to Display
| Metric | Description |
|---|---|
| Total flights | Volume over time period |
| Destinations | Countries receiving deportees |
| Surge magnitude | Percentage increase vs. baseline |
| Hub activity | Departures per staging airport |
| Route changes | New or discontinued destinations |
Report Structure
| Section | Content |
|---|---|
| Executive summary | Key findings in 2-3 sentences |
| Methodology | Data sources and analysis methods |
| Volume analysis | Flight counts and trends |
| Route analysis | Destinations and patterns |
| Correlation | Links to policy/enforcement events |
| Recommendations | Advocacy implications |
Data Management
Database Schema
CREATE TABLE flights (
id SERIAL PRIMARY KEY,
hex VARCHAR(6) NOT NULL,
callsign VARCHAR(8),
origin VARCHAR(4),
destination VARCHAR(4),
departure_time TIMESTAMP,
arrival_time TIMESTAMP,
category VARCHAR(20),
verified BOOLEAN DEFAULT FALSE
);
CREATE TABLE positions (
id SERIAL PRIMARY KEY,
flight_id INTEGER REFERENCES flights(id),
timestamp TIMESTAMP,
lat DECIMAL(9,6),
lon DECIMAL(9,6),
altitude INTEGER,
ground_speed DECIMAL(6,1)
);
CREATE TABLE aircraft (
hex VARCHAR(6) PRIMARY KEY,
n_number VARCHAR(10),
operator VARCHAR(100),
aircraft_type VARCHAR(50),
confidence VARCHAR(10),
last_verified DATE
);
CREATE INDEX idx_positions_flight ON positions(flight_id);
CREATE INDEX idx_positions_timestamp ON positions(timestamp);
CREATE INDEX idx_flights_hex ON flights(hex);
Data Retention
| Data Type | Retention | Storage |
|---|---|---|
| Position data | 30 days rolling | Compressed archive |
| Flight summaries | Indefinite | Primary database |
| Verified flights | Indefinite | Primary database |
| FOIA documents | Indefinite | Document store |
Related Resources
Last updated: March 25, 2026