飛行機をリアルタイムで追跡するデータパイプラインを作ってみた — Confluent Platform × Apache Flink
同様のブログを、IBM Community Blogにも投稿しています。
https://community.ibm.com/community/user/blogs/shumpei-kubo/2026/10/21/realtime-flight-tracker-with-confluent-platform
ADS-B データを Kafka に流し、Apache Flink で SQL 分析、WebSocket でブラウザにリアルタイム配信——
FlightRadar24 風のダッシュボードを OSS とデータ技術だけで構築しました。
はじめに — なぜ「飛行機追跡」なのか?
リアルタイムデータパイプラインのデモをするとき、「どんなデータを使うか」が非常に重要です。
株価や IoT センサーはよく使われますが、今回あえて 飛行機のリアルタイム位置情報 を選びました。
理由はシンプルで、誰でも直感的に「リアルタイム」を体験できる からです。
地図上を実際に動く飛行機アイコンを見ると、データが本当にリアルタイムに流れていることが一目でわかります。
IoT や金融でも「ああ、こういう感じか」と想像してもらいやすくなります。
使用データ: ADSB.lol という無料・無認証の ADS-B 受信 API。
日本周辺4エリアを10秒ごとにポーリングすると、500〜1,000 機体のリアルタイム位置情報が取得できます。
アーキテクチャ — 5 レイヤー構成
全体の構成はシンプルです。データは一方向に流れます。
ADSB.lol API → Apache Kafka → Apache Flink → Python/WS サーバー → ブラウザ
(ADS-B 受信) (flight_positions) (SQL 分析) (WebSocket 配信) (Leaflet.js 地図)
| レイヤー | コンポーネント | 役割 |
|---|---|---|
| データ収集 | 1_fetch_flights.py |
ADSB.lol API を 10 秒ごとにポーリング。各機体を個別メッセージ(key=ICAO24)に分解して Kafka へ produce |
| メッセージング | Kafka: flight_positions |
全機体の最新スナップショットを保持するソーストピック(秒速 1,500 件以上を処理) |
| ストリーム処理 | Apache Flink |
SQL INSERT ジョブが 4 本稼働中。フィルタ・集計・ウィンドウ処理を担当し、派生トピックへ書き出す |
| バックエンド | 3_dashboard_server.py |
Kafka Consumer + WebSocket サーバー。10 秒ごとに全クライアントへブロードキャスト |
| フロントエンド | 4_dashboard_map.html |
Leaflet.js による地図 UI。WebSocket メッセージを受信するたびにアイコンをリアルタイム更新 |
Kafka の役割 — 秒速 1,500 件をさばくデータの通り道
Kafka はデータを 「一時的に溜めながら高速に流す」 パイプです。
Publisher(Producer)と Subscriber(Consumer)が完全に非同期で動き、どちらかが遅れても全体が止まりません。
このデモでは flight_positions という 1 つのトピックがソースになります。
Flink・ダッシュボードサーバーの両方がこのトピックを 独立して 消費しています。
| 指標 | 値 |
|---|---|
| Kafka 受信速度 | 〜1,500 msgs/s |
| 追跡機体数(日本周辺) | 500〜1,000 機 |
| データ更新サイクル | 10 秒 |
全世界モードにすると?
ADSB.lol の /v2/all エンドポイントに切り替えると、5,000〜20,000 機体が対象になります。
Kafka の真価(高スループット・スケーラビリティ)を見せるときはこのモードが効果的です。
Apache Flink の役割 — SQL でストリームを分析する
このデモの 一番のポイント が Flink です。
通常のデータベースは「貯まったデータ」を後から分析しますが、Flink は 流れているデータをリアルタイムに分析 できます。
川(Kafka)を流れる水(データ)を、川の途中に仕掛けた網(Flink)でリアルタイムに選別・計測するイメージです。
このデモでは 4 本の Flink SQL INSERT ジョブ が常時稼働しています。
| ジョブ名 | 出力トピック | 処理内容 |
|---|---|---|
flink-insert-japan |
flink_flight_japan |
registration LIKE 'JA%' で日本登録機をフィルタ |
flink-insert-airline |
flink_airline_stats |
航空会社別集計(60 秒タンブリングウィンドウ) |
flink-insert-altitude |
flink_altitude_stats |
高度帯別フライト数(60 秒ウィンドウ) |
flink-insert-phase |
flink_phase_stats |
フライトフェーズ別集計(60 秒ウィンドウ) |
SQL で書いたフィルタの例
日本登録機フィルタ:
INSERT INTO flink_flight_japan
SELECT *
FROM flight_positions_stream
WHERE registration LIKE 'JA%' -- JA で始まる = 日本登録機(例: JA986A)
AND on_ground = false; -- 飛行中のみ
なぜ callsign ではなく registration で判定するのか?
callsign(コールサイン)は「ANA762」のように航空会社コード+便名であり、日本の航空会社でも「JA」で始まりません。
一方 registration(登録番号)は日本で登録された機体には必ず「JA」が付きます(例: JA986A)。
callsign フィルタを使うと日本の定期便の約 71% を取りこぼしてしまいます。これは実データ約 128 機体で検証済みです。
航空会社別 60 秒タンブリングウィンドウ集計:
INSERT INTO flink_airline_stats
SELECT
SUBSTRING(callsign, 1, 3) AS airline_code,
COUNT(*) AS flight_count,
AVG(baro_altitude) AS avg_altitude_m,
AVG(velocity) AS avg_velocity_ms,
MAX(baro_altitude) AS max_altitude_m
FROM flight_positions_stream
GROUP BY
TUMBLE(TABLE flight_positions_stream, DESCRIPTOR(ts), INTERVAL '60' SECOND),
SUBSTRING(callsign, 1, 3);
コールサインの先頭 3 文字(JAL、ANA、ADA など)を航空会社コードとして識別し、60 秒ごとにフライト数・平均高度・平均速度・最高高度を自動計算します。60 秒が経過するとウィンドウがリセットされ、常に「直近 60 秒の最新統計」が更新されます。
ダッシュボードの機能
ブラウザを開くと、世界地図上に飛行機が実際に動いている様子が見えます。
地図の見方
| 表示 | 意味 |
|---|---|
| 🟢 緑のアイコン | 低高度(0〜3,000 m) |
| 🔵 青のアイコン | 中高度(3,000〜8,000 m) |
| 🩵 水色のアイコン | 高高度(8,000〜12,000 m)— 多くの旅客機が巡航する帯 |
| 🟣 紫のアイコン | 超高高度(12,000 m 以上) |
| ティール色リング付きアイコン | 日本登録機(Flink が flink_flight_japan から検出) |
| 赤いパルスアニメーション | 緊急スカワーク発信機(リアルタイム検知) |
| アイコンの向き | 飛行機の進行方向(track フィールド) |
右側サイドバーの 4 タブ
| タブ | データソース | 内容 |
|---|---|---|
| 全機体リスト | Kafka | 追跡中の全機体をコールサイン・高度・速度・機種で一覧表示。検索バーで絞り込み、行クリックで地図フォーカス |
| 日本登録機 | Flink |
flink_flight_japan トピックで抽出した JA 登録機のみ。地図上ではティール色リングで強調 |
| 緊急アラート | リアルタイム検知 | スカワーク 7700(一般緊急)・7600(通信不能)・7500(ハイジャック)を区別してリアルタイム通知 |
| 航空会社別統計 | Flink |
flink_airline_stats の 60 秒集計をフライト数降順で表示。フライト数・平均高度・平均速度を確認 |
緊急スカワークコード
| コード | 名称 | 意味 |
|---|---|---|
| 7700 | 一般緊急 | エンジントラブル・急病人など機上での緊急事態 |
| 7600 | 通信不能(NORDO) | 無線通信が使えず、管制と連絡が取れない状態 |
| 7500 | ハイジャック | 不法な乗っ取りを示す最も深刻なコード |
このシステムは実際の ADS-B データを使用しているため、現実の緊急事態もリアルタイムで検知します。
実際には誤操作によるものが多く、大半は確認後にすぐクリアされます。
追加機能 — 異常検知と接近警報
メインの地図ダッシュボード以外にも、いくつかの拡張機能を実装しています。
⚠️ 異常降下・上昇の検知
垂直速度 |vertical_rate| が 20 m/s を超えた状態が 連続 2 回以上(10 秒間隔)続いた場合にアラート化。
30 m/s 超(約 6,000 ft/min)は「緊急降下疑い」として別表示されます。
📡 機体同士の異常接近検知
5 秒ごとに全機体間の距離を計算。水平 9.26 km(5 NM)・垂直 250 m 以内に近づくと警報。
3 NM・500 ft 以内はより高い重大度で通知します。連続 2 サイクル以上で確定。
| 重大度 | 水平距離 | 垂直差 |
|---|---|---|
| 注意(caution) | < 9.26 km(5 NM) | < 250 m |
| 異常接近(emergency) | < 5.56 km(3 NM) | < 152 m(約 500 ft) |
🤖 デモ用ダミーデータ注入
外部 API に接続できない環境向けに、リアルな飛行パスを持つダミー機体を Kafka に直接投入するスクリプト(1b_inject_demo_data.py)を用意しています。
python 1b_inject_demo_data.py # 40機体を10秒間隔で投入
python 1b_inject_demo_data.py --count 80 --anomaly # 80機体 + 異常検知データも投入
python 1b_inject_demo_data.py --proximity # 機体同士の異常接近も発生させる
技術スタック
| コンポーネント | 役割 |
|---|---|
| ADSB.lol API | 無料・無認証の ADS-B 受信 API。日本周辺4エリアを 10 秒ごとにポーリング |
| Apache Kafka | 秒速 1,500 件以上のメッセージを処理するストリーミングプラットフォーム |
| Confluent Platform | Kafka のエンタープライズ版。Apache Flink の CMF(Confluent Manager for Apache Flink)管理も統合 |
| Apache Flink | SQL でストリームデータをリアルタイム分析。CMF REST API でジョブを管理 |
| Python 3 | データ取得・バックエンドサーバーの実装言語(confluent-kafka / websockets ライブラリ) |
| WebSocket | ブラウザとサーバーを常時双方向接続。データ到着と同時に画面へ反映 |
| Leaflet.js | ブラウザで地図を表示する OSS JavaScript ライブラリ |
| nginx | HTTPS リバースプロキシ。WebSocket のプロキシ設定も担当 |
実装で学んだこと・ハマりポイント
① callsign vs registration の罠
最初は「日本の飛行機 = callsign が JA で始まる」と思い込んでフィルタを実装しました。しかし実データを検証すると、JAL・ANA などの定期便のコールサインは「JAL762」「ANA302」のような形式で、先頭が航空会社コードです。
日本の飛行機かどうかを判定するには registration(機体登録番号)の先頭が JA かどうか を見る必要があります。この修正で日本機の捕捉率が約 71% 向上しました。
callsign : "JAL762" ← 「JAL」= 航空会社コード。JA で始まらない
registration: "JA986A" ← 「JA」= 日本登録番号の先頭。これが正解
② ssl.ca.location はタスクマネージャ上のパス
Flink SQL ジョブから Kafka(SSL 有効)に接続する際、ssl.ca.location に指定するファイルパスは Flink タスクマネージャ上のパス です(例: /tmp/kafka-ca.crt)。
開発マシン上のローカルパスを書いても接続できないため、タスクマネージャに事前にファイルを配置しておく必要があります。
# ❌ 間違い — ローカルのパスを書いても Flink から見えない
'ssl.ca.location': './kafka-ca.crt'
# ✅ 正しい — タスクマネージャ上にあらかじめ配置したパス
'ssl.ca.location': '/tmp/kafka-ca.crt'
③ Flink の Catalog 名は UI 表示名と異なる
CMF の UI では flink-catalog と表示されていますが、SQL ステートメントの sql.current-catalog プロパティに指定する値は _env_flink-env(アンダースコア + env + ハイフン)です。
UI 表示名をそのまま使うとジョブが失敗します。
# ❌ UI 表示名をそのまま使うと失敗する
"sql.current-catalog": "flink-catalog"
# ✅ 実際に指定すべき値
"sql.current-catalog": "_env_flink-env"
④ フォールバック設計で壊れないデモを実現
Flink 派生トピックが存在しない場合や、ジョブが FAILED になった場合でも、バックエンドサーバーは自動的に flight_positions から直接計算するフォールバックモードに切り替わります。地図表示は常に正常動作するため、デモ中に Flink が止まっても「真っ白画面」にはなりません。
Kafka データが 30 秒来ない場合はさらにフォールバックして、東京周辺を飛ぶモック機体を自動生成します。
デモ環境のネットワーク不安定でも画面が止まらないよう設計しています。
5 分で体験できるデモウォークスルー
| ステップ | 時間 | 内容 |
|---|---|---|
| Step 1 | 1 分 | ダッシュボードを開き、日本上空の飛行機が 10 秒ごとに動いていることを確認。アイコンをクリックするとコールサイン・高度・速度・機種がポップアップで表示 |
| Step 2 | 1 分 | サイドバーの「日本登録機」タブを開き、地図上のティール色リングと連動していることを確認。Flink が registration LIKE 'JA%' でリアルタイムに選別している結果がここに出る |
| Step 3 | 2 分 | 「航空会社別統計」タブを開いて 60 秒待つ。JAL・ANA・ADA などのフライト数・平均高度・速度がタンブリングウィンドウで自動更新される様子を確認 |
| Step 4 | 1 分 | 画面上部 KPI バーの「Kafka Rate」を見る。1,500 msgs/s 前後のリアルタイム受信速度が表示され、パイプラインが高スループットで動いていることを体感 |
このデモが伝えるもの
「外部 API → Kafka → Apache Flink → WebSocket → ブラウザ」という リアルタイムデータパイプラインの全レイヤー を 1 つのデモで体験できます。
IoT センサーデータ、金融取引、物流追跡など、あらゆるリアルタイムデータ活用シナリオにそのまま応用できるアーキテクチャです。
おわりに
Confluent Platform 上の Apache Flink を使うと、ストリーミングデータへの SQL 分析が驚くほど手軽に実装できます。今回のデモでは 4 本の INSERT ジョブを Python スクリプト 1 本(約 300 行)で管理・デプロイしており、CMF REST API の扱いやすさを実感しました。
飛行機データという「誰でも直感的に理解できる題材」を選んだことで、Kafka の高スループット、Flink の SQL ストリーム処理、WebSocket によるリアルタイム配信という 3 つの技術を一度に体験できるデモになりました。
コメントお待ちしております。
