はじめに
現代の航海術においてGPSは欠かせない重要機器です。
ですが、GPSは非常に繊細なもので、簡単にジャミングやスプーフィングの影響を受けてしまいます。
GPS以外の測位システムを導入しようとしてもコストが大きく、試験的に導入するにも心理的障壁があるかと思います。
そこで、今回は秋葉原の秋月電子通商で4000円弱で購入したGNSS受信機を使って、PC上のブラウザで現在位置と衛星情報を確認できるWebアプリ「GNSS Navigator」を作成しました。
GPS受信機 USB接続タイプ みちびき2機(194/195)対応 GU-502MGG-USB: 通信・無線モジュール・アンテナ 秋月電子通商-電子部品・ネット通販 https://akizukidenshi.com/catalog/g/g117981/
一般的なGPSロガーとは異なり、GPS、GLONASS、Galileo、BeiDouの各衛星測位システムを切り替えるための機能も搭載しています。
本記事では構成と実装上のポイントを紹介します,
注意:GNSS切替コマンドの適用可否は使用する受信機・ファームウェアに依存します。
作ったもの
Webブラウザで開くローカルアプリで、次の3タブから構成されます。
| タブ | 機能 |
|---|---|
| MAP | オフライン地図、現在位置、COG/SOG、航跡、追従表示 |
| GNSS MONITOR | Sky Plot、衛星別C/N₀、衛星数、GSAの使用衛星、DOP |
| SETTINGS | COMポート接続、ボーレート、GNSS切替、コマンド応答、デモモード |
起動直後はSETTINGSタブを表示する構成です。右上の状態表示は、有効なFixがあるときに測位システム名を表示し、未測位・Fix喪失・切断時には No fix を表示します。ただしシステム名は受信機が受理した選択モード、またはGSAの識別情報から推定しているため、実際に測位演算で使用されたすべての衛星群を保証するものではありません。
システム構成
GNSS衛星
↓
USB GNSS受信機
↓ NMEA 0183 / USB仮想COMポート
Python(server.py)
├─ pyserial:シリアル通信
├─ NMEA:RMC / GGA / GSA / GSV解析
├─ HTTP API:測位データ配信と設定コマンドの受付
└─ SQLite:ローカルMBTilesの読み込み
↓ HTTP / JSON(localhost)
Webブラウザ
├─ HTML:画面構造
├─ CSS:画面デザイン
└─ JavaScript / Canvas:地図・衛星情報の描画
PythonのHTTPサーバーは 127.0.0.1:8765 のみに待ち受けるため、外部のWebサービスは不要です。地図データもPC内に置くことでオフライン動作します。
使用技術
- Python 3
- pyserial(USBシリアル通信)
- Python標準ライブラリ
http.server、threading、sqlite3など - HTML / CSS / JavaScript
- Canvas API(地図、Sky Plot、グラフ描画)
- ラスタ形式MBTiles(オフライン地図)
1. GNSS受信機からNMEAデータを取得する
Pythonの pyserial でCOMポートを開き、受信した行を解析関数へ渡しています。概念的には次のような処理です。
import serial
with serial.Serial("COM3", 9600, timeout=0.7) as dev:
while True:
line = dev.readline().decode("ascii", "ignore").strip()
if line:
process(line)
実際のアプリでは、接続・切断や例外を処理するため、この処理を別スレッドで実行しています。COMポートを複数のアプリから同時に開こうとすると、アクセス拒否になる場合がある点に注意が必要です。
主に利用するNMEAセンテンスは次の4つです。
| センテンス | 取得する情報 |
|---|---|
| RMC | UTC、緯度・経度、対地速力(SOG)、対地針路(COG)、有効性 |
| GGA | 測位品質、高度、使用衛星数、HDOP |
| GSA | 測位モード、使用衛星ID、PDOP・HDOP・VDOP |
| GSV | 可視衛星のID、仰角、方位角、C/N₀ |
たとえばRMCセンテンスは次のような形式です。
$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A
緯度 4807.038,N は「北緯48度07.038分」を意味します。表示や計算に使う際は、次のように十進度に変換します。
48 + 7.038 / 60 = 48.1173°
受信したセンテンスは、$ と * の間の文字列についてXORチェックサムを計算し、末尾の値と照合してから解析します。
def checksum(payload):
value = 0
for ch in payload:
value ^= ord(ch)
return f"{value:02X}"
2. GNSS MONITORの衛星情報を表示する
GSVセンテンスに含まれる各衛星の仰角と方位角からSky Plotを描画します。円の中心が天頂(仰角90°)、外周が水平線(仰角0°)です。
r = R × (1 - elevation / 90)
x = centerX + r × sin(azimuth)
y = centerY - r × cos(azimuth)
また、GSVのC/N₀を衛星ごとの棒グラフにしています。GSAに記録された使用衛星IDおよびDOP値も別途表示することで、単に受信できている衛星と測位に使用されている衛星を区別しやすくしています。
衛星群はNMEAのトーカーID(GP、GL、GA、GB / BD など)を参考に分類しています。ただし、複数衛星群をまとめたGNセンテンスや、NMEAのバージョンによっては正確な分類ができない場合があります。なお、古い衛星情報の期限切れ管理などは今後の改善対象です。
3. GPS / Galileo / BeiDouなどを切り替える
今回使用を想定した受信機では、PMTK353の5パラメータ形式に対応する場合、次のコマンドで測位システムを指定できます。
| 選択モード | PMTK353コマンド |
|---|---|
| GPS | $PMTK353,1,0,0,0,0*2A |
| GLONASS | $PMTK353,0,1,0,0,0*2A |
| Galileo | $PMTK353,0,0,1,0,0*2A |
| BeiDou | $PMTK353,0,0,0,0,1*2A |
| GPS + GLONASS | $PMTK353,1,1,0,0,0*2B |
| GPS + Galileo | $PMTK353,1,0,1,0,0*2B |
| GPS + BeiDou | $PMTK353,1,0,0,0,1*2B |
5パラメータの順番は GPS、GLONASS、Galileo、予約領域、BeiDou です。予約領域は0に固定しています。
ただし、この形式がすべてのPMTK対応受信機に共通するわけではありません。古いチップセットでは異なる書式が必要な場合があります。実機での対応モードも受信機の仕様書とNMEA出力で確認する必要があります。
アプリでは任意の組み合わせを入力できるようにはせず、上記7モードのみを単一選択できるUIにしています。コマンドはPythonから受信機のシリアルポートへ送信し、PMTK001などのACKを監視します。
ACKが返っても、指定した衛星群がすべて実際の測位計算に利用されたことを意味しません。 コマンド受理と実際の使用衛星の確認は分けて扱います。
4. Fix状態と右上の表示
表示の基本条件は、connected、fix、stale の3つです。
const valid = state.connected && state.fix && !state.stale;
if (!valid) {
statusText = "No fix";
} else {
statusText = getConstellationLabel(state);
}
最新の有効な位置更新から5秒を超えると stale と判定します。したがって、未測位、受信中断、Fix喪失、切断の際に古いシステム名が右上に残らないようにしています。
PMTK353のACKによって受理された設定モードがある場合、そのモード名を優先して表示します。設定モードが不明な場合はGSA情報から推定し、判定できなければ GNSS と表示します。この部分は「設定された衛星群」と「実際に測位に使用された衛星群」をさらに明確に区別する余地があります。
5. オフライン地図の表示
地図はラスタ形式のMBTilesファイルを使用します。MBTilesはSQLiteデータベースに地図タイルを格納した形式で、Pythonで必要な画像を検索してブラウザへ返します。
SELECT tile_data
FROM tiles
WHERE zoom_level = ?
AND tile_column = ?
AND tile_row = ?;
MBTilesのタイル行はTMS形式なので、ブラウザ表示に使うXYZ形式との違いを次式で補正します。
tmsY = 2^zoom - 1 - xyzY
HTML Canvas上には地図画像に加えて、現在位置マーカー、COG方向、航跡、緯度経度グリッドなどを重ねて描画します。
地図ファイルがなくてもグリッド表示はできますが、詳細な地形図は表示されません。対応する地図はPNG・JPEG・WebPのラスタMBTilesであり、ベクターMBTiles(PBF)やS-57/S-63電子海図には対応していません。地図の利用権限やオフライン保存条件も確認が必要です。
6. ブラウザとのデータ通信
JavaScript側では約1秒ごとに /api/state を呼び出し、Pythonが保持している最新データをJSONで受け取ります。
async function refresh() {
const response = await fetch("/api/state");
const state = await response.json();
// 数値表示・衛星配置図・地図を更新
}
setInterval(refresh, 1000);
これはポーリングと呼ばれる方式です。リアルタイム配信にWebSocketを使う設計もありますが、当面の1Hz程度の表示更新であればシンプルなHTTP APIで実現できます。
7. 起動方法
Windowsでは以下のように起動できます。
- Python 3をインストールする。
-
pyserialをインストールする(初回のみインターネット接続が必要になる場合があります)。 - アプリのフォルダで
start_windows.batを実行する。 -
http://127.0.0.1:8765/を開く。 - SETTINGSタブからCOMポートと通信速度を選択して接続する。
GPS受信機が手元にない場合は、デモモードで画面を確認できます。ただし、デモ表示の位置・衛星データは実測ではありません。
地図を追加する場合は、maps/に対応するラスタMBTilesを配置し、画面から再読み込みします。
まとめ
USB接続のGNSS受信機、Python、JavaScriptを組み合わせることで、Web上で簡単にGNSSをモニタリング出来るようになりました。
GPSだけに頼らないことにより、GPSスプーフィングやジャミングを受けた際、ほかの測位システムを使用することでこれを回避することができるようになりました。