【PR】この記事には広告が含まれています。

飛行機の音が聞こえたとき、「この機体はどこへ行くのだろう」と気になったことはありませんか?

この記事では、Raspberry Piで航空機信号用の受信機を使う方法と「レーダー風表示装置」の作り方を紹介します。リアルタイムで頭上を飛ぶ航空機の位置情報を画面に表示し、自宅で小さな管制塔気分が楽しめます。
ラズパイで飛行機の信号を受信
多くの飛行機は、現在位置・高度・速度・機体番号などの情報を送信しています。そうした通信手段の中でも、旅客機をはじめとする多くの航空機で使われているのが、ADS-B(Automatic Dependent Surveillance–Broadcast) という仕組みです。

ADS-Bの信号は、受信機を使えばRaspberry Piでも受信できます。
ADS-B受信機を使用

今回はNooelec社のNESDR MiniというUSBスティック型のADS-B受信機を使用します。Raspberry Piに接続し、専用のソフトウェアをインストールすることで、航空機が発信する1090MHz帯の信号を受信できます。

アンテナを室内に置いた状態で、約50km離れた航空機の信号を受信できました。
専用アンテナも付属しているため、すぐに使い始められます。ただし、Raspberry Pi Zeroシリーズを使う場合は、別売りのUSB microBコネクタ用のOTGケーブルが必要です。
Flightradar24にデータ提供も可能
世界中の航空機をリアルタイムで表示するサービスFlightradar24も、ADS-B信号をもとにしています。

Flightradar24は基本機能を無料で使えますが、30分で自動的にセッションが終了する制限があります。一方、ADS-B受信機を設置すれば、設置場所の周辺に限定されますが、航空機の情報を制限なく受信・表示できます。
チェックポイント
Raspberry PiとADS-B受信機を使ってFlightradar24にデータを提供することで、通常は年間499.99ドル(約7万5千円)かかるBusinessプランを無償で利用できます。Flightradar24へのデータ提供方法については、記事の後半で紹介しています。
ADS-B受信機の使用準備
今回使用したADS-B受信機は、以下のモデルで動作を確認しました。
Raspberry Pi OSについては、ADS-B受信機だけを使う場合は最新のBookwormでも動作します。僕はHyperPixel 2.1 Round(円形ディスプレイ)を使いたかったので、前世代のOSであるBullseye(Legacy版)を使用しました。
Raspberry PiのOSをインストールする方法は、以下の記事で詳しく解説しています。
≫【2025年最新版】OSインストールから初期設定まで|セットアップ手順のすべて
ラズパイとADS-B受信機の接続

ADS-B受信機をRaspberry PiのUSBポートに挿すだけで接続できます。Raspberry Pi Zeroシリーズを使う場合は、別売りのUSB microBコネクタ用のOTGケーブルが必要です。
ソフトウェアの設定
ここからはRaspberry Piを起動して、ADS-B受信機を使える状態にします。
航空機の発信するADS-B信号を受信・表示するために、FlightAwareが提供するdump1090-faというソフトを使います。
dump1090-faを使うために、FlightAwareのパッケージリポジトリをRaspberry Piに追加します。

リポジトリは、データを管理する「インターネット上の倉庫」のようなイメージです。その倉庫の場所をRaspberry Piに教える作業をします。
ターミナルを開いて、次のコマンドを実行します。これによりFlightAwareが提供するリポジトリ登録用の.debパッケージ(設定ファイル)をインターネットからダウンロードします。
wget https://www.flightaware.com/adsb/piaware/files/packages/pool/piaware/f/flightaware-apt-repository/flightaware-apt-repository_1.2_all.deb

次のコマンドで、先ほどダウンロードした .debパッケージをインストールします。
sudo dpkg -i flightaware-apt-repository_1.2_all.deb
Raspberry Piのパッケージリストを更新します。これにより、dump1090-faがインストール候補として認識されるようになります。

パッケージリストとは、利用可能なソフトウェアの一覧情報のことです。
sudo apt update
dump1090-faをインストールします。-y
オプションは「続行しますか? [Y/n]」の確認をスキップして、自動的に「yes」と答えるためのものです。
sudo apt install -y dump1090-fa
以下はRaspberry Piの起動時に dump1090-fa
を自動で起動するよう設定するコマンドです。
sudo systemctl enable dump1090-fa
レーダー表示を自作するために、Pythonで画像や映像を扱えるOpenCVライブラリをインストールします。
sudo apt install -y python3-opencv
Raspberry Piを再起動します。
sudo reboot
再起動後にdump1090-faが自動で起動します。
ADS-B信号の受信を確認
dump1090-faが正常に動作しているかを確認します。Raspberry Piのブラウザを開いて、「http://localhost:8080/」をアドレスバーに入力します。すると、dump1090のリアルタイム航空機マップが表示されます。自宅周辺のマップを確認すると、航空機の情報がキャッチされていることがわかります。

画面の右側には、受信した航空機のリアルタイムデータが表形式で表示されます。各列の意味は以下のとおりです。
ICAO | 航空機ごとに割り当てられた識別コード(16進数形式) |
Ident | 航空会社名と便名(例:ANA536) |
Squawk | 航空機が送信している4桁の識別コード(航空交通管制で使用) |
Altitude (ft) | 高度(フィート単位) |
Speed (kt) | 速度(ノット単位=海里/時) |
Heading | 進行方向(度) |
Msgs | 受信したメッセージの回数 |
Age | 最新データを受信してからの経過時間(秒) |
この画面は同じネットワークに接続されたPCやスマホからも閲覧可能です。もしRaspberry PiのIPアドレスが 192.168.1.100 であれば、「http://192.168.1.100:8080/」にアクセスすることで確認できます。
航空機の動きをリアルタイムで見るだけでもワクワクします。ですが、せっかくRaspberry Piを使っているので、受信したデータを活用して、さらに面白いことに挑戦したくなるものです。
Pythonで受信データを処理する
ADS-B信号を活用して何かを作りたいとき、Pythonでデータを取得できると便利です。dump1090-faは受信した航空機の情報を http://localhost:8080/data/aircraft.json
というURLでJSON形式として公開しています。このJSONには航空機ごとの緯度・経度・高度・速度・方位などのデータが含まれており、Pythonからアクセスできます。
Thonnyを使ってPythonプログラムを実行する
Raspberry Piでは「Thonny(ソニー)」という初心者向けのPython開発環境が標準でインストールされています。以下の手順でプログラムを実行できます。
デスクトップ画面左上の「ラズベリーメニュー」から「プログラミング」→「Thonny」を開く。

画面中央のエリアに、プログラムをコピー&ペーストして、「実行」ボタンを押す。

下部の「シェル」エリアに、実行結果(航空機の情報)が表示されます。
航空機データの取得テスト
以下のプログラムを実行して、航空機のデータが表示されるかを確認します。
import time
import requests
# 航空機データを取得する関数
def get_aircraft_positions():
try:
res = requests.get("http://localhost:8080/data/aircraft.json", timeout=1)
return [a for a in res.json().get("aircraft", []) if 'lat' in a and 'lon' in a]
except:
return []
# 10秒ごとに航空機データを表示
while True:
aircraft_list = get_aircraft_positions()
print(f"現在受信中の航空機数: {len(aircraft_list)} 機")
for a in aircraft_list:
hex_id = a.get('hex', '不明')
flight = a.get('flight', hex_id).strip()
lat = a.get('lat')
lon = a.get('lon')
alt = a.get('alt_baro', '不明')
speed = a.get('gs', '不明')
track = a.get('track', '不明')
print(f"便名: {flight} | 緯度: {lat} | 経度: {lon} | 高度: {alt} ft | 速度: {speed} kt | 方位: {track}°")
# 10秒待機
time.sleep(10)
このプログラムでは、まずURLにアクセスして航空機データを取得し、位置情報(緯度・経度)を持つものだけを抽出しています。そして、10秒ごとにその情報をシェルに表示します。表示される内容には、便名、緯度・経度、高度(フィート)、速度(ノット)、進行方向(度)が含まれており、上空を飛ぶ航空機の状況がリアルタイムに確認できます。

これでPythonでデータを取得できることが確認できました。ここからは、このデータを処理するプログラムを作成していきます。
航空機と測定地点の距離を計算
緯度や経度の数値だけを見ても、近いのか遠いのかを判断できません。航空機と測定地点との距離を計算すると、接近しているのか遠ざかっているのかがわかります。

緯度・経度から距離を計算する方法については、ChatGPTに相談しながらアルゴリズムを作成しました。
以下のプログラムは自宅と航空機の緯度・経度をもとに直線距離を計算し、最も近い航空機の便名とその距離を表示します。この距離は測定地点と航空機の水平距離であり、高度は考慮していません。
import time
import requests
import math
# 自宅の緯度・経度に変更してください(例:東京駅付近)
HOME_LAT = 35.681236
HOME_LON = 139.767125
# 航空機データを取得する関数
def get_aircraft_positions():
try:
res = requests.get("http://localhost:8080/data/aircraft.json", timeout=1)
return [a for a in res.json().get("aircraft", []) if 'lat' in a and 'lon' in a]
except:
return []
# ハーサイン距離計算(地球上の2点間の距離をkmで返す)
def haversine(lat1, lon1, lat2, lon2):
R = 6371.0 # 地球の半径 (km)
lat1_rad = math.radians(lat1)
lon1_rad = math.radians(lon1)
lat2_rad = math.radians(lat2)
lon2_rad = math.radians(lon2)
dlat = lat2_rad - lat1_rad
dlon = lon2_rad - lon1_rad
a = math.sin(dlat/2)**2 + math.cos(lat1_rad) * math.cos(lat2_rad) * math.sin(dlon/2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
return R * c
# 10秒ごとに一番近い航空機を表示
while True:
aircraft_list = get_aircraft_positions()
closest = None
min_distance = float('inf')
for a in aircraft_list:
lat = a.get('lat')
lon = a.get('lon')
if lat is not None and lon is not None:
distance = haversine(HOME_LAT, HOME_LON, lat, lon)
if distance < min_distance:
min_distance = distance
closest = a
if closest:
flight = closest.get('flight', closest.get('hex', '不明')).strip()
print(f"最も近い便名: {flight} | 自宅からの距離: {min_distance:.2f} km")
else:
print("航空機データが取得できませんでした。")
time.sleep(10)
測定地点の座標は、プログラム内6行目の HOME_LAT
(緯度)と HOME_LON
(経度)という変数に設定されています。東京駅の座標が入力されていますが、この値は自由に変更できます。
チェックポイント
緯度と経度はGoogleマップで調べることができます。地図上で自宅などの位置を右クリックすると、小さなメニューが表示されます。その一番上に、「35.○○○○○○, 139.○○○○○○」のような緯度と経度の数値が出てきます。
このプログラムは10秒ごとに航空機のデータを取得し、各機体との距離を順に計算。その中でもっとも距離が近い機体を特定し、便名と距離を表示します。

LCD 1602表示する
コードを応用すれば、以下のようなLCD表示も可能です。

このLCDは「I2C接続の16文字2行キャラクタLCD(LCD1602)」と呼ばれています。もともとは並列通信で制御するタイプですが、背面にI2Cインターフェースモジュールが取り付けられており、4本の配線で使用できます。
ラズパイとLCD1602は以下のように接続します。

LCD1602を使うためには、Raspberry PiでI2C通信を有効にする必要があります。以下のコマンドを実行後に再起動すると、I2Cを有効にできます。
sudo raspi-config nonint do_i2c 0
次のコードは自宅の位置を基準にして、近くを飛んでいる航空機の情報を取得し、LCDに表示するものです。
import smbus
import time
import requests
import math
ADDR = 0x27 # LCDのI2Cアドレス(例: 0x27や0x3F、使用前にi2cdetectで確認)
bus = smbus.SMBus(1)
# 自宅の緯度・経度に変更してください(例:東京駅付近)
HOME_LAT = 35.681236
HOME_LON = 139.767125
def write(data, mode=0):
for val in [(data & 0xF0), ((data << 4) & 0xF0)]:
b = val | mode | 0x08
bus.write_byte(ADDR, b | 0x04)
time.sleep(0.0005)
bus.write_byte(ADDR, b)
def init():
for cmd in [0x33, 0x32, 0x06, 0x0C, 0x28, 0x01]:
write(cmd)
time.sleep(0.001)
def message(text, row=0, col=0):
addr = 0x80 + col if row == 0 else 0xC0 + col
write(addr)
for c in text.ljust(16 - col):
write(ord(c), 1)
# 航空機データを取得する関数
def get_aircraft_positions():
try:
res = requests.get("http://localhost:8080/data/aircraft.json", timeout=1)
return [a for a in res.json().get("aircraft", []) if 'lat' in a and 'lon' in a]
except:
return []
# ハーサイン距離計算(地球上の2点間の距離をkmで返す)
def haversine(lat1, lon1, lat2, lon2):
R = 6371.0 # 地球の半径 (km)
lat1_rad = math.radians(lat1)
lon1_rad = math.radians(lon1)
lat2_rad = math.radians(lat2)
lon2_rad = math.radians(lon2)
dlat = lat2_rad - lat1_rad
dlon = lon2_rad - lon1_rad
a = math.sin(dlat/2)**2 + math.cos(lat1_rad) * math.cos(lat2_rad) * math.sin(dlon/2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
return R * c
prev_line1 = ""
prev_line2 = ""
while True:
aircraft_list = get_aircraft_positions()
closest = None
min_distance = float('inf')
for a in aircraft_list:
lat = a.get('lat')
lon = a.get('lon')
if lat is not None and lon is not None:
distance = haversine(HOME_LAT, HOME_LON, lat, lon)
if distance < min_distance:
min_distance = distance
closest = a
if closest:
flight = closest.get('flight', closest.get('hex', 'UNKNOWN')).strip()
line1 = flight
line2 = "Dist: {:.2f} km".format(min_distance)
else:
line1 = "No aircraft"
line2 = "in range"
# 表示内容に変化があるときだけ更新
if line1 != prev_line1 or line2 != prev_line2:
print(f"Updating display:\n{line1}\n{line2}")
init()
message(line1, row=0, col=0)
message(line2, row=1, col=2)
prev_line1 = line1
prev_line2 = line2
time.sleep(2)
表示内容が前回と同じであればLCDを更新せず、画面のちらつき防止を図ります。航空機がいない場合は「No aircraft」「in range」と表示します。
距離の数値が少しずつ縮まっていくのを見ると、ワクワクして思わず窓を開け、飛行機の音に耳を澄ませてしまいます。
簡易的なレーダー風表示の作成
航空機の緯度経度情報をもとに、他の機体や測定地点との位置関係をプロットする方法を模索します。以下のコードは取得した航空機の位置データをリアルタイムで画面に可視化するものです。
import cv2
import requests
import numpy as np
import math
import time
# 自宅の緯度経度(例:東京駅周辺)
HOME_LAT = 35.6812
HOME_LON = 139.7671
# ウィンドウサイズと中心座標
WIDTH = 480
HEIGHT = 480
CENTER_X = WIDTH // 2
CENTER_Y = HEIGHT // 2
# 縮尺(緯度経度 → ピクセル)
SCALE = 400
# 航空機データ取得関数
def get_aircraft_positions():
try:
res = requests.get("http://localhost:8080/data/aircraft.json", timeout=1)
return [a for a in res.json().get("aircraft", []) if 'lat' in a and 'lon' in a]
except:
return []
# 点線円を描く関数
def draw_dotted_circle(img, center, radius, color=(0, 255, 0), spacing=2, dot_length=1):
cx, cy = center
circumference = 2 * math.pi * radius
steps = int(circumference / (spacing + dot_length))
for i in range(steps):
angle_start = 2 * math.pi * (i * (spacing + dot_length)) / circumference
angle_end = angle_start + (dot_length / radius)
x1 = int(cx + radius * math.cos(angle_start))
y1 = int(cy + radius * math.sin(angle_start))
x2 = int(cx + radius * math.cos(angle_end))
y2 = int(cy + radius * math.sin(angle_end))
cv2.line(img, (x1, y1), (x2, y2), color, 1)
# 三角形で航空機を描画する関数
def draw_triangle(img, center, angle_deg, color=(0, 255, 0)):
x, y = center
length = 15
width = 5
angle_rad = math.radians(angle_deg)
tip = (int(x + length * math.sin(angle_rad)), int(y - length * math.cos(angle_rad)))
left = (int(x - width * math.cos(angle_rad)), int(y - width * math.sin(angle_rad)))
right = (int(x + width * math.cos(angle_rad)), int(y + width * math.sin(angle_rad)))
points = np.array([tip, left, right], dtype=np.int32)
cv2.fillPoly(img, [points], color)
cv2.namedWindow("Radar Display")
cv2.resizeWindow("Radar Display", WIDTH, HEIGHT)
last_update = 0
aircraft_list = []
while True:
frame = np.zeros((HEIGHT, WIDTH, 3), dtype=np.uint8)
# 中心(自宅)のマーカー
cv2.circle(frame, (CENTER_X, CENTER_Y), 2, (0, 255, 0), -1)
# 緑の点線の中央縦線
for y in range(0, HEIGHT, 10):
if (y // 10) % 2 == 0:
cv2.line(frame, (CENTER_X, y), (CENTER_X, y + 5), (0, 255, 0), 1)
# 緑の点線の中央横線
for x in range(0, WIDTH, 10):
if (x // 10) % 2 == 0:
cv2.line(frame, (x, CENTER_Y), (x + 5, CENTER_Y), (0, 255, 0), 1)
# 点線円を3本描く
for r in [60, 140, 220]:
draw_dotted_circle(frame, (CENTER_X, CENTER_Y), r)
# 1秒ごとにデータ更新
if time.time() - last_update > 1.0:
aircraft_list = get_aircraft_positions()
last_update = time.time()
# 各航空機を描画
for a in aircraft_list:
dx = (a['lon'] - HOME_LON) * SCALE
dy = (a['lat'] - HOME_LAT) * SCALE
x = int(CENTER_X + dx)
y = int(CENTER_Y - dy)
if 0 <= x < WIDTH and 0 <= y < HEIGHT:
if 'track' in a and isinstance(a['track'], (int, float)):
draw_triangle(frame, (x, y), a['track'])
else:
cv2.circle(frame, (x, y), 5, (0, 255, 0), -1)
cv2.imshow("Radar Display", frame)
if cv2.waitKey(30) & 0xFF == 27: # ESCキーで終了
break
cv2.destroyAllWindows()
まず、自宅の緯度経度を基準としてウィンドウの中心に固定し、各航空機の緯度・経度の差をピクセル換算(SCALE
)して、画面上に位置を描画します。

この部分も、自分の環境に応じた緯度経度に変更してください。
18行目のSCALE = 400
は、緯度・経度の差を画面上のピクセルに変換するための倍率です。この値を大きくすると表示範囲が狭くなり、航空機が大きく動くように見えます。逆に小さくすると広範囲を表示できます。表示範囲や航空機の密度に応じて、SCALE
の数値は適宜調整してください。
進行中の機体には「track」と呼ばれる方位情報が含まれており、これは航空機がどの方角へ向かっているか(真北を0度として時計回り)を示します。
この方位情報をもとに、航空機のアイコンを進行方向に向かって回転させて表示します。アイコンは三角形を使って機体の向きを表現しており、先端が進行方向を指すように描画します。trackの値が取得できない場合には、代わりに緑色の円で機体の位置を示します。
1秒ごとにデータ取得・描画を繰り返すことで、リアルタイムな位置関係を描き出しています。

飛行機がどの方向へ向かっているか、測定地点から見てどこに位置しているかを、視覚的に把握することが可能になりました。
ミニレーダー風表示装置の作り方

いよいよミニレーダーの作成に着手します。ここからの内容は、以下の構成で作成しています。
モデル | Raspberry Pi Zero 2 W |
OS | Raspberry Pi OS Bullseye (Legacy, 32-bit) |
ディスプレイ | HyperPixel 2.1 Round |
HyperPixel 2.1 Roundの設定

ディスプレイはHyperPixel 2.1 Roundを使用しました。小型の円形ディスプレイで、ミニレーダーにはぴったりなアイテムです。GPIOに接続し、設定を行うことで、Raspberry Piのデスクトップ画面をそのまま表示できます。
ラズパイはディスプレイの裏側にすっきり収まるサイズのRaspberry Pi Zero 2 Wを使用しました。

Raspberry Piとディスプレイを接続します。40本のピンヘッダーをまるごと差し込みます。

HyperPixel 2.1 RoundでRaspberry Piの画面を表示するには、カーネルドライバを有効にする必要があります。
ターミナルを開いて以下のコマンドを1行ずつ順に実行します。
git clone https://github.com/pimoroni/hyperpixel2r
cd hyperpixel2r
sudo ./install.sh
ディスプレイを正しく動作させるために、/boot/config.txt
の中にある「dtoverlay=vc4-kms-v3d」の行を無効化します。
sudo nano /boot/config.txt
開いたファイルの中で、「dtoverlay=vc4-kms-v3d」の行を探して、先頭に #
を追加します。

編集が終わったら、Ctrl + O で保存し、Ctrl + X でエディタを終了します。
以下のコマンドでI2Cを無効化します。
sudo raspi-config nonint do_i2c 1
再起動すると、画面が表示されます。ディスプレイは円形のため、画面の四隅にあるスタートメニューなどは表示されません。

タッチ操作にも対応していますが、このサイズではGUI操作が難しいため、基本的にはVNCでの操作をおすすめします。
画面の自動暗転機能を無効化する

「Raspberry Piの設定」を開いて「画面のブランク」という項目をオフにします。これは、一定時間何も操作しないと画面が暗くなる機能です。この機能が有効になっていると、レーダーを長時間表示し続けることができません。

スタンドの作成

機器を設置するためのスタンドを、Tinkercadで設計して3Dプリンターで作りました。

デザインは、戦闘機のコックピットにある計器をイメージしています。

設計が完成したらプリント開始。およそ2時間で印刷が終わりました。

スタンドに機器を取り付けた様子。

デザインやサイズを調整しながら、5回ほど試作を重ねました。
レーダー画像と飛行機アイコンの準備
回転するレーダーの動画は、Pixabayで公開されているものをダウンロードして使用します。Pixabayは著作権フリーの画像、動画、音声などを共有するサイトです。サイズは640×360のものをダウンロードしました。

飛行機の位置を示すためのアイコン画像を作成しました。透過PNG形式の飛行機アイコン画像は、ChatGPTに作ってもらい、サイズはパソコンで48ピクセルに調整しました。

動画とアイコンはファイル名をradar.mp4とplane_icon.pngに変更して、Raspberry Piの「/home/pi」ディレクトリ、または実行するプログラムを保存するディレクトリに保存します。WindowsパソコンからRaspberry Piにファイルを移動する場合は、WinSCPを利用すると便利です。


ファイル名や保存場所が正しくないと、プログラムから呼び出せなくなります。
ミニレーダー風ディスプレイのプログラム
以下のプログラムでは、先ほど保存した radar.mp4
を背景動画として表示し、plane_icon.png
の飛行機アイコンを航空機の位置に合わせて画面上に描画します。
import cv2
import requests
import numpy as np
import math
import time
# 自宅の緯度経度に変更してください(以下は東京駅周辺)
HOME_LAT = 35.6812
HOME_LON = 139.7671
# 画面サイズと中心座標
WIDTH = 480
HEIGHT = 480
CENTER_X = WIDTH // 2
CENTER_Y = HEIGHT // 2
# 緯度経度→ピクセル変換倍率
SCALE = 300
# 飛行機アイコンの読み込み(アルファチャンネル付き)
icon = cv2.imread("plane_icon.png", cv2.IMREAD_UNCHANGED)
icon_h, icon_w = icon.shape[:2]
# 背景レーダー動画の読み込み
cap = cv2.VideoCapture("radar.mp4")
fps = cap.get(cv2.CAP_PROP_FPS)
delay = int(1000 / fps) if fps > 0 else 33 # フレーム遅延時間(ms)
# 航空機データ取得関数
def get_aircraft_positions():
try:
res = requests.get("http://localhost:8080/data/aircraft.json", timeout=1)
return [a for a in res.json().get("aircraft", []) if 'lat' in a and 'lon' in a]
except:
return []
# 飛行機アイコンを回転描画する関数
def draw_icon_rotated(img, icon, center, angle_deg):
x, y = center
M = cv2.getRotationMatrix2D((icon_w // 2, icon_h // 2), -angle_deg, 1.0)
rotated = cv2.warpAffine(
icon, M, (icon_w, icon_h),
flags=cv2.INTER_NEAREST,
borderMode=cv2.BORDER_CONSTANT,
borderValue=(0, 0, 0, 0)
)
# アイコン貼り付け位置の計算
x1 = x - icon_w // 2
y1 = y - icon_h // 2
x2 = x1 + icon_w
y2 = y1 + icon_h
# 画面外ならスキップ
if x1 < 0 or y1 < 0 or x2 > img.shape[1] or y2 > img.shape[0]:
return
# ROI合成(アルファブレンド)
roi = img[y1:y2, x1:x2]
if rotated.shape[2] == 4:
alpha = rotated[:, :, 3] / 255.0
for c in range(3):
roi[:, :, c] = (1 - alpha) * roi[:, :, c] + alpha * rotated[:, :, c]
# ウィンドウを全画面表示
cv2.namedWindow("Radar Display", cv2.WINDOW_NORMAL)
cv2.setWindowProperty("Radar Display", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
# 初期化
aircraft_list = []
last_update = 0
while True:
now = time.time()
if now - last_update >= 1.0:
aircraft_list = get_aircraft_positions()
last_update = now
# 動画フレームの読み込み
ret, video_frame = cap.read()
if not ret:
cap.set(cv2.CAP_PROP_POS_FRAMES, 0) # 動画を最初に戻す
continue
# 動画のリサイズ(1.8倍ズーム+アスペクト比維持)
video_h, video_w = video_frame.shape[:2]
scale = min(WIDTH / video_w, HEIGHT / video_h) * 1.8
new_w = int(video_w * scale)
new_h = int(video_h * scale)
resized = cv2.resize(video_frame, (new_w, new_h), interpolation=cv2.INTER_NEAREST)
resized = cv2.convertScaleAbs(resized, alpha=0.6, beta=0) # 薄く表示
# 最終描画フレーム(黒背景)
frame = np.zeros((HEIGHT, WIDTH, 3), dtype=np.uint8)
# 動画の貼り付け位置計算(中央寄せ)
x_offset = (WIDTH - new_w) // 2
y_offset = (HEIGHT - new_h) // 2
x1_dst = max(x_offset, 0)
y1_dst = max(y_offset, 0)
x1_src = max(-x_offset, 0)
y1_src = max(-y_offset, 0)
x2_dst = min(x_offset + new_w, WIDTH)
y2_dst = min(y_offset + new_h, HEIGHT)
x2_src = x1_src + (x2_dst - x1_dst)
y2_src = y1_src + (y2_dst - y1_dst)
# 動画フレームを合成
frame[y1_dst:y2_dst, x1_dst:x2_dst] = resized[y1_src:y2_src, x1_src:x2_src]
# 航空機データ取得
aircraft_list = get_aircraft_positions()
# 航空機ごとにアイコンを描画
for a in aircraft_list:
dx = (a['lon'] - HOME_LON) * SCALE
dy = (a['lat'] - HOME_LAT) * SCALE
x = int(CENTER_X + dx)
y = int(CENTER_Y - dy)
# 画面内に収まっているか判定
if 0 <= x < WIDTH and 0 <= y < HEIGHT:
if 'track' in a and isinstance(a['track'], (int, float)):
draw_icon_rotated(frame, icon, (x, y), a['track'])
else:
cv2.circle(frame, (x, y), 5, (0, 255, 0), -1)
# フレーム表示
cv2.imshow("Radar Display", frame)
# ESCキーで終了
key = cv2.waitKey(delay) & 0xFF
if key == 27:
break
# 終了処理
cap.release()
cv2.destroyAllWindows()
HOME_LAT
と HOME_LON
は、レーダー画面の中心となる自宅の位置を表す緯度・経度です(この例では東京駅付近を指定しています)。航空機の位置は、この基準点からの相対的な距離をもとに計算され、画面上に配置されます。

自分の環境に応じた緯度と経度に変更してください。
SCALE = 300
は、緯度・経度の差を画面上のピクセルに変換するための倍率です。この値を大きくすると表示範囲が狭くなり、航空機が大きく動くように見えます。逆に小さくすると広範囲を表示できます。表示範囲や航空機の密度に応じて、SCALE
の数値は適宜調整してください。
プログラムの終了は、ESC
キーを押すことでウィンドウが閉じられ、処理が終了します。画面はフルスクリーンで表示されるため、実際のレーダー装置のような見た目に仕上がります。
ESCキーでプログラムを終了できます。

無事にミニレーダーが完成しました。飛行機の動きを眺めているだけでも飽きません。
【改良版】便名を表示する

以下は飛行機アイコンのすぐ下に便名(flight)を表示するコードを追加したものです。
import cv2
import requests
import numpy as np
import math
import time
# 自宅の緯度経度に変更してください(以下は東京駅周辺)
HOME_LAT = 35.6812
HOME_LON = 139.7671
# 画面サイズと中心座標
WIDTH = 480
HEIGHT = 480
CENTER_X = WIDTH // 2
CENTER_Y = HEIGHT // 2
# 緯度経度→ピクセル変換倍率
SCALE = 600
# 飛行機アイコンの読み込み(アルファチャンネル付き)
icon = cv2.imread("plane_icon.png", cv2.IMREAD_UNCHANGED)
icon_h, icon_w = icon.shape[:2]
# 背景レーダー動画の読み込み
cap = cv2.VideoCapture("radar.mp4")
fps = cap.get(cv2.CAP_PROP_FPS)
delay = int(1000 / fps) if fps > 0 else 33 # フレーム遅延時間(ms)
# 航空機データ取得関数
def get_aircraft_positions():
try:
res = requests.get("http://localhost:8080/data/aircraft.json", timeout=1)
return [a for a in res.json().get("aircraft", []) if 'lat' in a and 'lon' in a]
except:
return []
# 飛行機アイコンを回転描画する関数
def draw_icon_rotated(img, icon, center, angle_deg):
x, y = center
M = cv2.getRotationMatrix2D((icon_w // 2, icon_h // 2), -angle_deg, 1.0)
rotated = cv2.warpAffine(
icon, M, (icon_w, icon_h),
flags=cv2.INTER_NEAREST,
borderMode=cv2.BORDER_CONSTANT,
borderValue=(0, 0, 0, 0)
)
# アイコン貼り付け位置の計算
x1 = x - icon_w // 2
y1 = y - icon_h // 2
x2 = x1 + icon_w
y2 = y1 + icon_h
# 画面外ならスキップ
if x1 < 0 or y1 < 0 or x2 > img.shape[1] or y2 > img.shape[0]:
return
# ROI合成(アルファブレンド)
roi = img[y1:y2, x1:x2]
if rotated.shape[2] == 4:
alpha = rotated[:, :, 3] / 255.0
for c in range(3):
roi[:, :, c] = (1 - alpha) * roi[:, :, c] + alpha * rotated[:, :, c]
# ウィンドウを全画面表示
cv2.namedWindow("Radar Display", cv2.WINDOW_NORMAL)
cv2.setWindowProperty("Radar Display", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
# 初期化
aircraft_list = []
last_update = 0
while True:
now = time.time()
if now - last_update >= 1.0:
aircraft_list = get_aircraft_positions()
last_update = now
# 動画フレームの読み込み
ret, video_frame = cap.read()
if not ret:
cap.set(cv2.CAP_PROP_POS_FRAMES, 0) # 動画を最初に戻す
continue
# 動画のリサイズ(1.8倍ズーム+アスペクト比維持)
video_h, video_w = video_frame.shape[:2]
scale = min(WIDTH / video_w, HEIGHT / video_h) * 1.8
new_w = int(video_w * scale)
new_h = int(video_h * scale)
resized = cv2.resize(video_frame, (new_w, new_h), interpolation=cv2.INTER_NEAREST)
resized = cv2.convertScaleAbs(resized, alpha=0.6, beta=0) # 薄く表示
# 最終描画フレーム(黒背景)
frame = np.zeros((HEIGHT, WIDTH, 3), dtype=np.uint8)
# 動画の貼り付け位置計算(中央寄せ)
x_offset = (WIDTH - new_w) // 2
y_offset = (HEIGHT - new_h) // 2
x1_dst = max(x_offset, 0)
y1_dst = max(y_offset, 0)
x1_src = max(-x_offset, 0)
y1_src = max(-y_offset, 0)
x2_dst = min(x_offset + new_w, WIDTH)
y2_dst = min(y_offset + new_h, HEIGHT)
x2_src = x1_src + (x2_dst - x1_dst)
y2_src = y1_src + (y2_dst - y1_dst)
# 動画フレームを合成
frame[y1_dst:y2_dst, x1_dst:x2_dst] = resized[y1_src:y2_src, x1_src:x2_src]
# 航空機データ取得
aircraft_list = get_aircraft_positions()
# 航空機ごとにアイコンを描画
for a in aircraft_list:
dx = (a['lon'] - HOME_LON) * SCALE
dy = (a['lat'] - HOME_LAT) * SCALE
x = int(CENTER_X + dx)
y = int(CENTER_Y - dy)
# 画面内に収まっているか判定
if 0 <= x < WIDTH and 0 <= y < HEIGHT:
if 'track' in a and isinstance(a['track'], (int, float)):
draw_icon_rotated(frame, icon, (x, y), a['track'])
else:
cv2.circle(frame, (x, y), 5, (0, 255, 0), -1)
# --- 便名を描画 ---
if 'flight' in a and a['flight']:
flight_text = a['flight']
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 0.5
thickness = 1
text_size, _ = cv2.getTextSize(flight_text, font, font_scale, thickness)
text_x = x - text_size[0] // 2 + 6
text_y = y + icon_h // 2 + text_size[1] - 2 # アイコン下
cv2.putText(frame, flight_text, (text_x, text_y), font, font_scale, (0, 255, 0), thickness, cv2.LINE_AA)
# フレーム表示
cv2.imshow("Radar Display", frame)
# ESCキーで終了
key = cv2.waitKey(delay) & 0xFF
if key == 27:
break
# 終了処理
cap.release()
cv2.destroyAllWindows()
Flightradar24へデータ提供する
自宅で受信した航空機のADS-B信号をFlightradar24に提供することで、航空ネットワークに貢献できます。その見返りとして、通常は有料のプレミアム機能が無料で使えるようになります。
ここでは、データ提供するメリットとデメリットを整理します。
メリット
- Flightradar24のBusinessプランが無料になる
- いつでもやめられる(データ送信を停止しても金銭的・法的な不利益なし)
デメリット
- Raspberry Piのリソースを使用(低スペック機では他の作業に影響)
- ラズパイの位置情報をFlightradar24に提供する必要あり
Flightradar24のアカウントを作成する
データを提供するには、Flightradar24のアカウントが必要です。
Flightradar24のウェブサイトにアクセスし、画面右上の「Login」をクリックします。
表示されたウィンドウの下部にある “Learn more | Create account” をクリックします。

表示されたこのプラン比較画面で、左端の「Free」プランの下にある「Create account」ボタンをクリックします。

メールアドレス・パスワードを入力し、アカウントを作成します。
登録したメールアドレスに確認メールが届くので、リンクをクリックして認証します。
fr24feed(フィーダーソフト)をインストールする
次に、Flightradar24にデータを送るためのソフト「fr24feed」をRaspberry Piにインストールします。

このソフトを使うには、dump1090-faがインストールされていて、起動している必要があります。
dump1090-faのインストールや起動がまだの場合は、この記事の前半にある「ソフトウェアの設定」パートの手順を先に行ってください。
ターミナルで以下のコマンドを実行
sudo bash -c "$(wget -O - https://repo-feed.flightradar24.com/install_fr24_rpi.sh)"
このコマンドは、Flightradar24のデータ送信ソフト「fr24feed」をRaspberry Piにダウンロードしてインストールするためのものです。

インストール後、自動的に設定ウィザードが始まります。ターミナルにいくつかの質問が表示されるので、それぞれに答えてエンターキーを押してください。以下のように回答します。
Step | 質問内容 | 回答の例 |
---|---|---|
1.1 | Flightradar24アカウントのメールアドレス | 登録したメールアドレスを入力 |
1.2 | Sharing keyの入力 | 以前使用したキーがあれば入力 なければそのままEnter |
1.3 | MLAT(Multilateration)に参加するか? ※複数の受信機が同時に受け取った信号の到達時間差からADS-B信号のない航空機の位置を推定する技術 | yes または no を入力してEnter (基本は yes でOK) |
3.A | アンテナ設置位置の緯度 | 例:35.6812 Googleマップ等で調査可 |
3.B | アンテナ設置位置の経度 | 例:139.7671 |
3.C | アンテナ設置位置の高度(フィート) | 例: 108 地面の標高+アンテナ設置位置までの高さ(m)× 3.28084(フィート換算) 標高は国土地理院地図で調査可 |
入力内容の確認 | yes または no を入力してEnter | |
「autoconfig 機能」を使うかどうかの確認 | 基本は yes でOK |
すべて正常に完了すると、「Installation and configuration completed!」と表示されます。
データ提供の確認
以下のコマンドでfr24feedの動作状況を確認します。
fr24feed-status

最下行の「FR24 MLAT: not running … failed!」は、MLAT(複数の受信機で航空機の位置を推定する機能)が正常に動作していないことを示しています。
申し訳ありません
「FR24 MLAT: not running … failed!」について、以下の記事を参考に設定を見直しましたが、解決には至っていません。https://intaa.net/archives/21762
Flightradar24(FR24)の公式フォーラムによると、MLATが動作するには、同じ航空機の信号を複数の受信局が同時に受信している必要があります。今回はMLATだけが停止しているため、この受信条件が満たされていない可能性があると推測されますが、現時点で断定はできません。
なお、ADS-B信号の受信とFlightradar24へのデータ提供は正常に行われており、Businessプランの適用にも影響はありません。

今後、MLATが正常に動作する方法が分かり次第、この記事に追記します。
Raspberry Piのブラウザで http://localhost:8754
にアクセスすると、fr24feedの動作状況(受信状態、送信状態、MLATの状態など)をリアルタイムで確認できます。
Flightradar24のウェブサイトにアクセスし、画面右上のユーザーアイコンに「Business」と表示されていれば、Businessプランが適用済みです。

Flightradar24へのデータ提供を中止する
データ提供はいつでも停止することができます。中止しても料金が発生することはありません。
データ提供を中止するには、以下を1行ずつ実行します。
sudo systemctl stop fr24feed
sudo systemctl disable fr24feed
これにより、fr24feedが停止し、次回以降の起動時にも自動実行されなくなります。
まとめ

ADS-B受信機は買って大正解のアイテムでした。Raspberry Piとの相性も抜群で、ラズパイユーザーには特におすすめです。
受信したデータを自動で処理したり、リアルタイムに表示したりといった処理を、小型で省電力なRaspberry Piだけで完結できるからです。

本記事で紹介したプログラムは、ChatGPTの力を借りながら作成しました。
初心者の方でも、AIを活用すれば比較的ハードルを感じずに取り組めると思います。
ADS-B受信機はほかにもいろいろな活用ができそうなので、試行錯誤を楽しんでいるところです。新しいものを作ったらまた紹介します。
ドラゴンレーダー風ミニレーダーの作成

飛行機レーダーのノウハウと手頃な円形ディスプレイがあれば、「あのドラゴンレーダー」を作ってみたくなるものです。
円形ディスプレイは1.28inch Touch LCDを使用しました。

円形ディスプレイを収納するケースは3Dプリンターで作成。Thingiverseで公開されているRadar Dragon Ballをベースに、サイズやデザインを調整しています。

レーダーの背景となる画像はCanvaで作成しました。

レーダーを表示するだけではおもしろくないので、方位がわかる地磁気センサーを使ってみました。レーダー本体が向いている方向にあわせて画面が回るようにして、飛行機の位置が感覚的にわかるようにしました。

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import os
import sys
import time
import math
import smbus2
import requests
import numpy as np
import cv2
from PIL import Image
sys.path.append("..")
from lib import LCD_1inch28
# ── QMC5883L 設定 ──
QMC5883L_ADDRESS = 0x0D
X_OFFSET = -110.0
Y_OFFSET = -587.5
X_SCALE = 1.0234375
Y_SCALE = 0.95
DECLINATION = -8.0
HEADING_OFFSET = 76.4
# ── 自宅座標とスケール ──
HOME_LAT = 35.6812
HOME_LON = 139.7671
SCALE = 250 #350
WIDTH = 240
HEIGHT = 240
CENTER_X = WIDTH // 2
CENTER_Y = HEIGHT // 2
# ── LCD 初期化 ──
disp = LCD_1inch28.LCD_1inch28()
disp.Init()
disp.clear()
# ── I2C 初期化 ──
bus = smbus2.SMBus(1)
bus.write_byte_data(QMC5883L_ADDRESS, 0x0B, 0x01)
time.sleep(0.1)
bus.write_byte_data(QMC5883L_ADDRESS, 0x09, 0x1D)
time.sleep(0.1)
# ── 方位取得 ──
def read_axis(reg_lsb):
lsb = bus.read_byte_data(QMC5883L_ADDRESS, reg_lsb)
msb = bus.read_byte_data(QMC5883L_ADDRESS, reg_lsb + 1)
value = (msb << 8) | lsb
if value >= 0x8000:
value -= 0x10000
return value
def get_heading():
raw_x = read_axis(0x00)
raw_y = read_axis(0x02)
adj_x = (raw_x - X_OFFSET) * X_SCALE
adj_y = (raw_y - Y_OFFSET) * Y_SCALE
raw_heading = math.degrees(math.atan2(adj_y, adj_x))
if raw_heading < 0:
raw_heading += 360.0
heading = (raw_heading - HEADING_OFFSET + DECLINATION) % 360.0
return heading
# ── 航空機データ取得 ──
def get_aircraft_positions():
try:
res = requests.get("http://192.168.1.46:8080/data/aircraft.json", timeout=1)
return [a for a in res.json().get("aircraft", []) if 'lat' in a and 'lon' in a]
except:
return []
# ── 背景画像の読み込み ──
original = Image.open("dragon2.png").convert("RGB")
original = original.resize((240, 240))
original_cv = cv2.cvtColor(np.array(original), cv2.COLOR_RGB2BGR)
# ── メインループ ──
last_update = 0
aircraft_list = []
while True:
# 背景画像をコピー(毎回初期化)
frame = original_cv.copy()
# 1秒ごとに航空機情報を更新
if time.time() - last_update > 2.0:
aircraft_list = get_aircraft_positions()
last_update = time.time()
print("航空機数: {}".format(len(aircraft_list)))
for a in aircraft_list:
print(" LAT: {:.4f}, LON: {:.4f}".format(a['lat'], a['lon']))
# 航空機を黄色い円で描画
for a in aircraft_list:
dx = (a['lon'] - HOME_LON) * SCALE
dy = (a['lat'] - HOME_LAT) * SCALE
x = int(CENTER_X + dx)
y = int(CENTER_Y - dy)
if 0 <= x < WIDTH and 0 <= y < HEIGHT:
cv2.circle(frame, (x, y), 6, (0, 255, 255), -1) # BGRで黄色
# OpenCV → PIL に変換して回転
image_pil = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
# ── センサー読み取り ──
raw_x = read_axis(0x00)
raw_y = read_axis(0x02)
raw_z = read_axis(0x04)
adj_x = (raw_x - X_OFFSET) * X_SCALE
adj_y = (raw_y - Y_OFFSET) * Y_SCALE
raw_heading = math.degrees(math.atan2(adj_y, adj_x))
if raw_heading < 0:
raw_heading += 360.0
heading = raw_heading - HEADING_OFFSET + DECLINATION
heading %= 360.0
# ── デバッグ表示 ──
#print("raw_x = {:6d}, raw_y = {:6d}, heading = {:.2f}°".format(raw_x, raw_y, heading))
rotated = image_pil.rotate(heading, expand=True)
# 黒背景に中央配置
background = Image.new("RGB", (240, 240), (0, 0, 0))
x_offset = (240 - rotated.width) // 2
y_offset = (240 - rotated.height) // 2
background.paste(rotated, (x_offset, y_offset))
# LCDに表示
disp.ShowImage(background)
time.sleep(0.3)
今回は本記事の前半で紹介したRaspberry Pi Zero 2 Wから、ネットワーク経由で航空機データを取得しています。「http://192.168.1.46:8080/data/aircraft.json」のようにZero 2 WのIPアドレスを指定することで、周囲を飛行中の航空機の情報(緯度・経度など)を含むデータを取得できます。
取得した情報をもとに、ディスプレイ上に航空機の位置をプロットします。さらに、内蔵の地磁気センサー(QMC5883L)で取得した方位に応じて画面を回転させることで、レーダーの向いている方向に合わせた直感的な表示を実現します。

くるくる進行方向表示装置

これまではディスプレイを使い航空機の情報を可視化してきましたが、今回はその手法を変え、動きで示す装置を作りました。

この装置は最も近くを飛んでいる航空機の進行方向をリアルタイムで取得し、模型の飛行機をその方向に回転させます。便名や距離はLCDに表示され、動く模型とテキスト情報が重なることで、空で起きていることをよりリアルに感じ取ることが可能です。画面を見るだけでは味わえなかった臨場感が、そこに生まれます。

今回の装置はRaspberry Pi Pico 2 Wを使用しました。ADS-B受信機を接続したRaspberry PiからWi-Fi経由で航空機のデータを取得する仕組みです。LCDは、本記事の前半でも紹介したI2C接続の16文字2行キャラクタLCD(LCD1602)を使用しています。

回転の制御にはSG90というサーボモーターを使用しています。

本来であれば180°しか回転できないこのモーターにギヤを組み合わせることで、模型が360度回転できるように工夫しました。

各パーツを収納する筐体やギヤの部分は、3Dプリンターで作成しています。

起動後、Raspberry Pi Pico 2 WはWi-Fiに接続し、ローカルネットワーク内のRaspberry PiからADS-Bデータを定期的に取得します。その中から最も近い航空機を特定し、自宅からの相対位置と進行方向を計算します。取得した便名と距離はLCDに表示され、進行方向に応じてサーボモーターが駆動します。

進行方向のデータ(0〜360度)は、南を基準に±90度の範囲へ変換します。これはサーボモーターの可動範囲に収めるためです。そのうえで、サーボの出力をギヤで拡大し、模型が1回転できるようにしました。
空港の案内表示風画面を作成

自宅の上空を飛ぶ航空機の情報を、空港の案内板のように表示してみました。使用したディスプレイは、Raspberry Pi公式 Touch Display 2(5インチ)です。このプロジェクトでは、航空会社のロゴとともに3便分の「便名」「到着地」「到着時刻」を表示します。
記事の前半部分で紹介しているADS-B受信機のデータには、到着地や到着予定時刻は含まれていません。そこで、それらを補うために 「AeroDataBox API」 を使います。
実現したいことは次の通りです。
- ADS-B受信機からリアルタイムの航空機位置情報を取得
- AeroDataBox API で便名から到着地と到着予定時刻を取得
- 航空会社のロゴとともに3便分表示
チェックポイント
AeroDataBoxの無料プランでは、月あたり最大 1,000回までリクエスト可能という制限があります。そのため、短い間隔で何度もデータを取得するような「常時更新」には向いていません。
AeroDataBox APIを使用

AeroDataBox API は、便名を指定するだけでフライト情報を取得できる便利なAPIです。このAPIは、RapidAPIというAPIマーケットプレイスで提供されています。利用するには次の手順でAPIキーを取得します。
RapidAPI公式サイト にアクセスして無料アカウントを作成します。

「AeroDataBox」で検索し、APIページを開きます。
「Subscribe to Test」を選択します。

Basic(無料)プランを選択します。

表示された X-RapidAPI-Key が、コードで使用するAPIキーです。
完成したコード

import requests
import math
from datetime import datetime
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO
import cv2
import numpy as np
import re
# ===============================
# 設定
# ===============================
# 自宅の緯度経度に変更してください(以下は東京駅周辺)
HOME_LAT = 35.6812
HOME_LON = 139.7671
# RapidAPI(AeroDataBox)キー
RAPIDAPI_KEY = "RapidAPIキーを入力してください"
# dump1090-fa のローカルURL
DUMP1090_URL = "http://localhost:8080/data/aircraft.json"
# ===============================
# 航空機データ取得関数
# ===============================
def get_aircraft_positions_dump1090():
"""dump1090-fa から航空機位置データを取得"""
try:
res = requests.get(DUMP1090_URL, timeout=1)
data = res.json()
return [a for a in data.get("aircraft", []) if 'lat' in a and 'lon' in a]
except:
return []
# ===============================
# 距離計算
# ===============================
def haversine(lat1, lon1, lat2, lon2):
"""2点間の距離(km)をハーサイン式で計算"""
R = 6371.0
dlat = math.radians(lat2 - lat1)
dlon = math.radians(lon2 - lon1)
a = math.sin(dlat / 2)**2 + math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) * math.sin(dlon / 2)**2
return R * 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
# ===============================
# AeroDataBox から便情報取得
# ===============================
def get_flight_info(flight_number):
"""便名から到着地と到着時刻を取得"""
date = datetime.today().strftime('%Y-%m-%d')
url = f"https://aerodatabox.p.rapidapi.com/flights/number/{flight_number}/{date}"
headers = {
"X-RapidAPI-Key": RAPIDAPI_KEY,
"X-RapidAPI-Host": "aerodatabox.p.rapidapi.com"
}
try:
res = requests.get(url, headers=headers, timeout=3)
data = res.json()
if isinstance(data, list) and data:
f = data[0]
flight = f['number']
airport = f['arrival']['airport']['name']
time_str = f['arrival']['scheduledTime']['local']
arrival_time = datetime.fromisoformat(time_str).strftime('%H:%M')
return flight, airport, arrival_time
except:
pass
return None, None, None
# ===============================
# メイン処理
# ===============================
# 航空機データを取得
aircraft_list = get_aircraft_positions_dump1090()
if not aircraft_list:
print("航空機が見つかりませんでした。")
exit()
# ===============================
# 距離順にソートして3機選択
# ===============================
for a in aircraft_list:
# 各航空機について、自宅(HOME_LAT, HOME_LON)からの距離を計算して追加
a["distance"] = haversine(HOME_LAT, HOME_LON, a["lat"], a["lon"])
# 距離("distance")の値を基準に昇順ソート(最も近い機体が先頭に来る)
aircraft_list.sort(key=lambda x: x["distance"])
# 最も近い3機だけを抽出
closest_aircrafts = aircraft_list[:3]
# ===============================
# 表示処理(3機を縦に並べて描画)
# ===============================
icao_to_iata = {
"ANA": "NH", "JAL": "JL", "ADO": "HD", "SNA": "7G", "SKY": "BC",
"APJ": "MM", "FDA": "JH", "CPA": "CX", "UAE": "EK", "THA": "TG",
"MAS": "MH", "CES": "MU", "CSN": "CZ", "CCA": "CA", "PAL": "PR",
"TGW": "TR", "SIA": "SQ", "JST": "JQ", "JJP": "GK", "TNA": "9C",
"BAW": "BA", "AAL": "AA", "UAL": "UA", "DAL": "DL", "KLM": "KL",
"AFR": "AF", "DLH": "LH", "QFA": "QF",
}
base = Image.new("RGB", (1280, 720), (0, 0, 80))
draw = ImageDraw.Draw(base)
font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
font = ImageFont.truetype(font_path, 60)
y_base = 60 # 1段目(最初の航空機情報)の描画開始位置(上から60pxの位置)
block_height = 220 # 各段の高さ(1つの航空機情報を描く縦スペース)
# ==== 近い3機を順に処理して、便情報を取得 ====
for idx, ac in enumerate(closest_aircrafts):
# 航空機の便名(flight)を取得。空白やNoneの場合は空文字に置き換え
ident = ac.get("flight", "").strip()
# 便名が空、または先頭が英字でない(例: 数字のみや不正データ)の場合はスキップ
if not ident or not ident[0].isalpha():
continue
# AeroDataBox APIを使って、便名から到着地と到着時刻を取得
flight, airport, arrival_time = get_flight_info(ident)
if not all([flight, airport, arrival_time]):
continue
print(f"[{idx}] 便名: {flight}")
print(f" 距離: {ac['distance']:.1f} km")
print(f" 到着地: {airport}")
print(f" 到着時刻: {arrival_time}")
print()
# 便名の先頭3文字を抽出(例: "JAL124" → "JAL")
airline_code = flight.strip()[:3].upper()
# ICAOコード(3文字)をIATAコード(2文字)に変換
# 変換できない場合は、先頭2文字をそのまま使う
iata = icao_to_iata.get(airline_code, airline_code[:2])
# 便名から数字部分だけを抽出(例: "JAL124" → "124")
flight_number = ''.join(re.findall(r'\d+', flight))
# 到着地の文字列を最大9文字までに制限(長すぎる地名対策)
destination = airport[:9]
# ==== 航空会社ロゴの取得 ====
# Daisycon APIを利用して、航空会社のロゴ画像を取得
# IATAコードを使ってURLを生成(白色ロゴ・横幅300px)
if iata == "BC":
# スカイマーク(IATA: BC)は正しいロゴが取得できないため、例外的に Airhex(https://content.airhex.com/)のロゴを使用
url = "https://content.airhex.com/content/logos/airlines_BC_300_100_s.png"
else:
# それ以外の航空会社は Daisycon から白色ロゴを取得
url = f"https://images.daisycon.io/airline/?width=300&height=95&color=ffffff&iata={iata}"
img_data = requests.get(url, timeout=5).content# 画像データをダウンロード(バイナリとして取得)
logo = Image.open(BytesIO(img_data)).convert("RGBA")# 取得したロゴをRGBA形式(透過対応)で読み込み
white_bg = Image.new("RGBA", logo.size, (255,255,255,255))# 白背景を作成(透過部分を白に塗りつぶすため)
logo_with_white = Image.alpha_composite(white_bg, logo)# 白背景とロゴを合成(透過部分が白になる)
logo_w, logo_h = logo_with_white.size# ロゴ画像の幅と高さを取得(後で配置位置を計算するため)
# 配置位置
y_offset = y_base + idx * block_height
base.paste(logo_with_white.convert("RGB"), (40, y_offset), mask=logo_with_white.split()[3])
# テキスト描画
text_start_x = 360 # テキストの描画開始位置(左からのオフセット)
# 便名(数字部分)を描画
draw.text((text_start_x, y_offset+10), flight_number, fill=(255,255,255), font=font)
# 到着地(空港名)を描画
draw.text((text_start_x+190, y_offset+10), destination, fill=(255,255,255), font=font)
# 到着予定時刻を描画
draw.text((text_start_x+630, y_offset+10), arrival_time, fill=(255,255,255), font=font)
# ===============================
# OpenCVで全画面表示
# ===============================
cv2.namedWindow("Flight Info", cv2.WINDOW_NORMAL)
cv2.setWindowProperty("Flight Info", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
img_cv = cv2.cvtColor(np.array(base), cv2.COLOR_RGB2BGR)
cv2.imshow("Flight Info", img_cv)
while True:
if cv2.waitKey(10) == 27:
break
cv2.destroyAllWindows()
dump1090-fa
のJSONデータを取得し、自宅位置(HOME_LAT
, HOME_LON
)からの距離を計算し、距離が近い3機を選んで表示対象にします。取得した便名を使って AeroDataBox API に問い合わせ、「到着地」と「到着予定時刻」を取得します。
航空会社名はシステムによって呼び方が異なります。dump1090-fa では「ANA」「JAL」などの ICAOコード(3文字)を返しますが、航空会社ロゴを提供するAPIは「NH」「JL」などの IATAコード(2文字)で指定する必要があります。そのため、icao_to_iata
という対応表を用意して、コードを変換しています。
ロゴ画像は主にDaisycon(航空会社ロゴを無料で提供するAPI)から取得しますが、スカイマーク(BC)など一部の航空会社は正しいロゴが取得できないため、代わりに航空会社ロゴデータベース のAirhexから取得したロゴを使用しています。
最後にcv2.namedWindow
と setWindowProperty
でウィンドウを全画面モードにし、imshow
で描いた画像を表示します。空港の案内板のように見せるために全画面にしていますが、操作できなくなると困るので、Escキーを押すと終了できるようにしています。
コメント一覧
すばらしいです!!
部屋に置きたくなりました。
公開してくださりありがとうございます。
MLATのエラーはこちらが参考になるかもです
https://intaa.net/archives/21762
ワクワクする記事、ありがとうございます!raspberry piって何かやったら次何に使おうか悩むんっですよね~これからも楽しみにしています!
こんな楽しみ方もあるのかと知りワクワクしています。ありがとうございます。
このワクワクは分かる人にしか分からないですね。ディスプレイを早速注文しました。
私は昨年からpiawareとfr24feedを併用して運用しており、Flightradar24とFlightAwareのどちらもMLATは正常に運用にできていました。←過去形です。
最初はこのサイトでも紹介されているリンク先の起動タイミングをずらす方法で解決していましたが、いつからか(恐らく今年になってから)「FR24 MLAT: not running … failed!」がまた出るようになりました。FlightAwareは今も正常にMLATの運用できてます。色々試していますがお手上げ状態になってます。
まあMLATは一旦おいといて、ディスプレイが到着したらミニレーダーを作るのが楽しみにしています!!
素敵な情報ありがとうございます。ミニレーダー風ディスプレイのプログラムを無事動かすことができました。すごく気に入りました。
ところでラズパイの電源オンで自動起動するにはどうしたら良いんでしょう?
https://www.pc-koubou.jp/magazine/52061?srsltid=AfmBOopezSbmz2AKGC-me8Z_c5OSMWme1fxreypRAmKyAV6yqQ0_kINJ
ここを参考にsystemdにserviceファイルを作成して、pyファイルを自動起動するようにしてみたんですけど、statusではactive(running)になっていますがレーダーが表示されないです。
serviceファイルはこんな感じです。
[Unit]
Description=レーダー
[Service]
ExecStart=/usr/bin/python3 /home/pi/FR24RADER.py
[Install]
WantedBy=multi-user.target
どなたかご助言お願いしますm(..)m
実践していただきありがとうございます。
FR24RADER.py はGUIで表示するプログラムなので、systemd で起動しても画面に表示されません。
GUIが立ち上がったあとにプログラムを起動させる必要があります。
以下の手順をお試しください。
①autostartディレクトリを作成
mkdir -p /home/pi/.config/autostart
②起動設定ファイルを作成
nano /home/pi/.config/autostart/fr24rader.desktop
③以下の内容を貼り付け:
[Desktop Entry]
Type=Application
Name=FR24RADER
Exec=python3 /home/pi/FR24RADER.py
X-GNOME-Autostart-enabled=true
④保存して再起動。
ログイン後のGUI表示されたあとにレーダーが開始されると思います。
そぞらさん、ご丁寧に教えてくださりありがとうございます。
バッチリ動作しました。毎日レーダーを見て楽しめます。知識がまた増えました。
いつも興味津々の楽しい情報を初心者にも分かりやすく掲載いただいてありがとうございます。
無線にもラズパイにも興味があるので本当に楽しくできました。機能向上などあれば是非また紹介お願いします。
このサイトのおかげで本当に楽しみながらプログラムの知識も向上しながら少しずつ成長ができています。
ラズパイPico W かんたんIoT電子工作レシピの書籍も購入します。
レーダーにフライト番号を表示させるとこは可能ででしょうか?
レーダーの飛行機アイコンの下に便名(flight)を表示することはできました。
プログラムを記事に追加しています。
以下の見出し部分をご確認いただけますと幸いです。
【改良版】便名を表示する
早々に、ご確認、ならにび返信いただきまして、ありがとうございます
改良版も早々に掲載して頂き感謝でしかありません。
頑張ってみます。
大変に詳細で丁寧な作業説明と改良版まで早々に作成いただき、おかげさまで航空機レーダーを完成することができました。
無事、完璧に動作しています。
ありがとうございました
また、いろいろとご教示ください。
ADSB受信に興味を持ち、ネットで検索していたらこちらのサイトに辿り着き、ADSB受信や航空機と測定地点の距離を計算プログラムを実践させていただきました。
可能であれば、航空機と測定地点の距離を計算しlcd 1602に表示するプログラムも公開していただけないでしょうか?プログラム初心者で分からないのでお願いします。
LCD 1602に航空機の情報を表示するコードを追加しました。
お時間のある時に見ていただければと思います。
ラズパイPico W かんたんIoT電子工作レシピを購入して楽しませてもらってます。。 やっと、Nooelec社のNESDR MiniというUSBスティック型のADS-B受信機が買えたので試してみました。
途中までは上手くいきましたが、ラズパイのブラウザを開いて、「http://localhost:8080/」をアドレスバーに入力します。すると、dump1090のリアルタイム航空機マップが表示されましたが、ADS-B Message Rate 0.1/secになってしまい、地図上に飛行機の情報がキャッチされません。
プログラム・電子工作も初心者で分からないので教えて下さい。宜しくお願いします。
返信が遅くなり申し訳ございません。
Message Rate 0.1/secが出ているなら、ADS-B受信機の認識はできていると思います。
自分の環境でADS-B受信機を外してみたら、Message Rateの表示自体が消えました。
Flightradar24などで付近に飛行機があるにもかかわらず、検出できないのであれば
アンテナ位置の調整くらいでしょうか。
念のため「sudo systemctl status dump1090-fa」コマンドを実行(dump1090-faの動作状況や直近のログが表示されます)して
「active (running)」があるか確認してみてください。
出力の下部にあるログ部分で[error] や [warning] といった警告が出ていないか
も確認お願いします。
そぞらさんのツイートがタイムラインに流れてきてラズパイを知り、右も左もわからないまま、ちまちま購入して見様見真似で何とか受信できるところまで辿り着けました。導入方法からこちらの受信のやり方までとても細かく丁寧に書いて頂き本当に感謝で一杯です。
タイムラインでお見かけした各社のロゴと行き先表示のやり方も、差し支えなければそのうち追加して頂けますと嬉しいです。
わざわざありがとうございます。
私の投稿をきっかけにラズパイを始められたとのこと、とても嬉しく拝見しました。
各社のロゴや行き先表示の方法については、いくつか課題があり、すぐに公開できるかは分かりませんが
今後の検討項目に入れさせていただきます。
航空会社のロゴや行き先表示の方法を追加しました。
APIの利用回数に制限があるため、リアルタイムでの高頻度な更新はできません。
この点はご了承お願いします。
https://sozorablog.com/flightradar/#toc28
わざわざお返事と公開、ありがとうございました!早速チャレンジしてみます!
Raspberry PiでFlightRader Feederになる記事を見かけ、色々と調べていたところ「ラズパイとADS-B受信機でミニ飛行機レーダーを作ろう」の記事を見かけて、欲しいなぁとRaspberry Pi 4Bを購入しました。サイトを見ていると簡単に出来そうな記事が多かったですが、初使用の為にセットアップも初めは苦労しました。 「【2025年最新版】OSインストールから初期設定まで|ラズベリーパイ(Raspberry Pi)セットアップ手順のすべて」が一番見やすく丁寧に書かれていて良かったです。
本題ですが「ラズパイとADS-B受信機でミニ飛行機レーダーを作ろう」を参考にインストールを進めて行きましたが、上手く入りません。
初めは64bit OSでしたのですが、海外のサイトで何年も前ですが64bitには対応していないとの記事を見かけて、32bitに入れなおしました、
それでも同様で、関係ないとは思うが32bit FULL VERも入れてみましたが同様でお手上げ状態です。
状況を下記に記載しますのでご教授頂ければと思います。
404エラーはサイトの問題なのかもしれませんが公式サイトにも何も情報が見当たりませんでした、「FlightAware does not provide packages for your distribution/architecture, the generated apt configuration may not work」については何か初歩的な間違いが有るのかなと感じています。
●「ラズパイとADS-B受信機でミニ飛行機レーダーを作ろう」で実施した事
wget https://www.flightaware.com/adsb/piaware/files/packages/pool/piaware/f/flightaware-apt-repository/flightaware-apt-repository_1.2_all.deb
ダウンロードは出来ました。
sudo dpkg -i flightaware-apt-repository_1.2_all.deb
インストールをすると、
flightaware-apt-repository_1.2_all.deb を展開する準備をしています
flightaware-apt-repository (1.2) で(1.2 に)上書き展開しています <何度もしているからかと思います
flightaware-apt-repository (1.2) を設定しています
flightaware-apt-repository: FlightAware does not provide packages for your distribution/architecture, the generated apt configuration may not work
作動しないよ的な表示がされますが、アップデートを行なうと、FlightAwareにReleaseファイルが無いと言うか 404エラーになってしまいます。
sudo apt update
ヒット:2 http://archive.raspberrypi.com/debian trixie InRelease
ヒット:3 http://raspbian.raspberrypi.com/raspbian trixie InRelease
無視:1 https://www.flightaware.com/adsb/piaware/files/packages trixie InRelease
エラー:4 https://www.flightaware.com/adsb/piaware/files/packages trixie Release
404 Not Found [IP: 2a06:98c1:3104::ac40:964d 443]
Error: リポジトリ http://flightaware.com/adsb/piaware/files/packages trixie Release には Release ファイルがありません
Notice: このようなリポジトリから更新を安全に行うことができないので、デフォルトでは更新が無効になっています。
Notice: リポジトリの作成とユーザ設定の詳細は、apt-secure(8) man ページを参照してください。
●FlightAwareのサイトから、piwareをいれてみようと見てみると同じコマンドが記載されていて、試しても当然同じ結果になりました。
https://www.flightaware.com/adsb/piaware/install
wget https://www.flightaware.com/adsb/piaware/files/packages/pool/piaware/f/flightaware-apt-repository/flightaware-apt-repository_1.2_all.deb
sudo dpkg -i flightaware-apt-repository_1.2_all.deb
sudo apt update
上記は当然同じ結果になりました
sudo apt install piaware
については
Error: パッケージ piaware が見つかりません
となります。
以上、長くなってしまいましたがよろしくお願いいたします。
こちらでも同じ状況を確認しましたが、現在 FlightAware のAPTリポジトリは「Bookworm(Debian 12)」までしか対応していないようです。
Trixie(Debian 13)ではリポジトリ自体が存在しないため、「FlightAware does not provide packages for your distribution」といったメッセージが出てしまいます。
とりあえずは、Raspberry Pi OS Bookworm版のイメージを使って試すのが一番早いと思います。
Bookworm環境では sudo apt update も正常に通り、piaware パッケージもインストール可能でした。
Trixie対応のリポジトリが公開されれば、そちらでも動作するようになると思います。
早々のご返答ありがとうございます
10/01のアップデートで変わったみたいですね、古いRP OSを入れる事で無事にインストールと作動させる事が出来ました。
ありがとうございました。
これからも、サイトを活用させて頂きます、
ご活躍を期待していますので、よろしくおねがいいたします。
RaspberryPI 学び始めた高齢者ですが、夢ある学びです。
是非、実現できるよう関連記事も拝読して学びの深掘りします。
ユメをありがとうございました。