#USBのGPS/GNSSセンサーから緯度経度を抜き取る
GPS/GNSSをC/C++のMFCアプリケーション上またはwin32で使用する場合を想定しています。
聞き慣れないGNSSとは、Global Navigation Satellite Systemの略で、GPS(米国)、GLONASS(ロシア)、Galileo(EU)、北斗(Beidou・中国)、みちびき(QZSS・日本)等の衛星測位システムの総称です(太字は全世界で使用可能)。当初、一般のGPSセンサーは米国のGPSのみ測位対象としていましたが、現在ではそれらすべてを利用して測位しているセンサーがほとんどでGNSSが正しい呼び方となりつつあります。ちなみにQZSS(みちびき)は日本とオーストラリアの領域のみでしか利用できません。
GPS/GNSSからのデーターはASCII文字でシリアル通信により受信、COMポートから受け取ります。したがって、USBのGPSセンサーであればCOMとして接続できるものが必要になります。シリアル接続できるPCは現在ではほとんど目にすることがなくなりましたが、仮想COMポートを持つことで従来通りのシリアル接続によるデーターのやり取りができるようになります。
まずGPS/GNSSセンサーはアマゾンにて「GR-8013U GNSSレシーバー」を購入しました。U-bloxという会社のGNSSセンサーを搭載しているようです。みちびき、GPS、GLONASS、Galileo、BeiDouに加え、SBAS、GAGAN、IMES(日本・屋内用位置サービス)がサポートされているとのこと。
同社のホームページでUSBドライバーをダウンロードしインストールすると、COM4として認識しました。
誤差は2.5mです。一応、みちびきのQZSSデーターを得ることができますが、みちびきの超精度の2.5cmの誤差は、個人で買うにはたいへん高額な受信装置でなければ実現できません。もちろん2.5cmの精度は、特別な用途を除いて普通のアプリケーションには必要ありません。
一般的なGPS/GNSSでは、データーはNMEAというプロトコルで送信されます。なのでNMEAとして送信される長文のデーターから不要な部分を削って、緯度と経度の二つのデーターのみ抜き出します。これはC言語でいう strtok
やstrstr
などを使うと容易にできます。
GPSを使うにはまずポートを開けてデーターを受け取ることから始めます。CreateFileでポートのハンドルを得ます。このとき、第5引数はOPEN_EXISTING
でなければいけません。第一引数は、ポートの名前です。型はLPCTSTRで、ポート名を「 \\\\.\\COM4
」と書きます。この表記を省略して、「COM4
」だけにしても問題ありませんが、ポートが10番以上になると読めないため、省略しないほうを使用するように常に心がけます。関数が成功するとハンドルが帰り、失敗するとINVALID_HANDLE_VALUE
が帰ります。
h_comPort = CreateFile(
portName, // LPCTSTR portName = _T("\\\\.\\COM4");
GENERIC_READ | GENERIC_WRITE,
0, NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
次にボーレートなどの設定です。これらの設定は定形文ですので特に勉強する必要はありません。ボーレートなどは接続されているGPSのプロパティに表示される数値をそのまま入力します。通常下の画像のような設定になっています。
//ボーレート、ストップビットなどの設定
DCB gpsDcb;
GetCommState(h_comPort, &gpsDcb);
gpsDcb.BaudRate = CBR_9600;
gpsDcb.DCBlength = sizeof(DCB);
gpsDcb.wReserved = 0;
gpsDcb.Parity = NOPARITY;
gpsDcb.fParity = FALSE;
gpsDcb.ByteSize = 8;
gpsDcb.StopBits = ONESTOPBIT;
dcbState = SetCommState(h_comPort, &gpsDcb);
GPSデーターを読み込むにはReadFileを使い、メモリーにたまったデーターを一文字づつWhileなどでループさせて、charのアドレスに書き込んでいきます。下の例では、"chRead"がcharとして格納するアドレスです。これも定形文ですので深く考えないようにします。whileもしくはforでループさせるかの違いしかありません。
BOOL state = ReadFile(h_comPort, &chRead, 1, &dwRead, NULL);
//引数は、ハンドル名、charのバッファ、読込対象のバイト数、読み込むバイト数、NULL指定です。
char readBuff[5000];
char chRead;
DWORD dwRead;
DWORD errors;
COMSTAT comStat;
BOOL errCheck = ClearCommError(h_comPort, &errors, &comStat);//受信したデーターの情報を読む。
if (errCheck == FALSE) {
break;
}
else {
OutputDebugString(_T("No errors in clearCommError.\n"));
}
memset(readBuff, 0, sizeof(readBuff)); //ゼロでリセット
int length = comStat.cbInQue; //受信データーの長さを求める。
if (length > 0) {
for (int i = 0; i < length; i++) { //受信した長さ分ループさせる
state = ReadFile(h_comPort, &chRead, 1, &dwRead, NULL);//state はBOOL型で省略可能。
if (state == FALSE) {
break;
}
strncat_s(readBuff, &chRead, 1); //バッファに一文字づつ書き込む
}
}
GPS/GNSSのセンテンスは決まっています。例えば、$GPRMCという構文では以下のようになります。各情報は、カンマで区切られ、緯度、経度、高度、速度、北緯か南緯か、西経か東経かなどの情報が含まれています。緯度は左から4番目、経度は6番目になるので、GPRMCと\nの間の文字列を抜き出したあと、デリミタの","を使ってその部分だけ抜き出します。ちなみに、GPRMCのGPは米国のGPSの略で、RMCはRecommended Minimum Navigation Informationの意味で、必要最小限のデーターが入っているセンテンスであることを表しています。GNSSの場合は、GPがGNになります。通常のアプリケーションで位置情報を使用するのであれば、GPRMC/GNRMCからデータを取ります。
$GPRMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68
上のGPRMC情報では4916.45が緯度になります。その次がNになっているので、北緯49度です。グーグルマップなどで使用するにはその下の16.45を60で割って49にプラスします。経度も西経123度でそれより右の数値を60で割って123度にプラスさせます。計算結果から、北緯49.27416,西経123.18533が正しい読み方になります。
アプリケーション上でシリアル通信する場合は、別スレッドを作ってその上で実行する必要があります。そうしないとアプリケーション上でほかの操作ができなくなるからです。
DWORD WINAPI ThreadFunc(LPVOID lpParam) {
//ループ開始。
Sleep(2000);
//ループ終了。
}
スレッド上でループ作業を行いますが、ループの待機時間を2秒にしました。GPSの更新はこの程度で十分です。また、この2秒間を使って、GPSデーターをメモリーに蓄積させるための"間"を持たせます。
#米国GPSの更新
米国のGPSは今後、GPS Block IIIA(GPSIII)として更新される予定です。衛星の打ち上げに遅延が起きていますが、2022年ごろには新システムに移行します。
これにより測位精度・セキュリティ性能が向上し、軍事用として特定地域での利用制限が可能なる予定。また、敵対勢力の信号妨害行為の対策措置が改善されるそうです。