ESP32[22]  NMEAの加工

NMEAは、National Marine Electronics Association(全米海洋電子協会)の略称です。NMEAは、船舶や航空機などの移動体における位置情報やセンサーデータの標準化されたフォーマットを提供する非営利団体です。

NMEA規格は、GPS(Global Positioning System)やGNSS(Global Navigation Satellite System)などの位置情報ソースから得られたデータを含むさまざまな情報を伝送するためのデータ形式を定義しています。この規格は、センサーデータ、航行情報、速度、方位、深度などの情報を含む、さまざまなタイプのデータを含むメッセージ形式を提供します。

NMEAメッセージは、テキストベースの形式であり、ASCII文字で構成されます。例えば、$GPRMC、$GPGLL、$GPGGAなどのようなプリフィックスと特定のデータフィールドで構成されたメッセージがあります。これらのメッセージは、位置情報や時間情報、速度、方位などのデータを含み、船舶や航空機などの移動体の位置追跡やナビゲーションシステムで使用されます。

NMEA規格は、GPSやGNSSなどの位置情報ソースと受信機、表示装置、センサー、コンピュータなどの機器が互換性を持つための標準化された方法を提供します。これにより、異なるメーカーやモデルの機器が相互に通信し、位置情報やセンサーデータを共有することができます。

GPSを扱っていると、このNMEAを加工したい時が良くあります。例えば、NMEAで受信したデータをGoogle Mapで表示させたいとか、逆に、Google Mapで集めた緯度経度からNMEAを作りたいとかしたくなります。今回は、直接、ESP32とは関係ありませんが、これらの変換プログラムをPERLで作ったので載せておこうと思います。

まずは、NMEAのGNGGAから緯度経度を取り出して、Google Map形式に変えます。NMEAの緯度経度は、dddmm.mmmm
60分で1度なので、分数を60で割ると度数になります。Googleマップ等で用いられる ddd.dddd度表記は、(度数 + 分数/60) で得ることができます。

下記がそのコードになります。

#!/usr/bin/perl

my @ row = ();

$filename = $ARGV[0];
open (IN, "./$filename")
     or die ("> error : $!");
while (<IN>){
	chomp;
	push (@row, $_);
	$num_line = $num_line +1;
}
close (IN);

my @ array = ();

$i = 0;
$tc = 0;

#$data_evn = 0

#print "$num_line\n";

for($i = 0;$i < $num_line;$i++){
	my @column = split (/,/, $row[$i]);
        if($column[0] eq "\$GNGGA") {
		$lat2 = int($column[2]/100);
		$lng2 = int($column[4]/100);
		$lat_shosu = $column[2] - $lat2 * 100;
		$lng_shosu = $column[4] - $lng2 * 100;
                $lat_shosu_rad = $lat_shosu / 60;
                $lng_shosu_rad = $lng_shosu / 60;
                $lat = $lat2 + $lat_shosu_rad;
                $lng = $lng2 + $lng_shosu_rad;
		if($lat > 30){
	 		print "$i $lat,$lng:$row[$i]\n";
		}
	}
}

次に、連続した複数のNMEAの緯度経度からNMEAの$GNGGAを作ります。緯度経度、時刻とサム値は固定値ではダメなので作りなおしています。その他は固定値を使用しています。これで、TinyGPSPlusのライブラリのチェックには引っ掛かりません。

#!/usr/bin/perl

sub convert_longitude {
    my ($input) = @_;

    # 入力値の整数部を取得
    my $degrees = int($input);

    # 入力値の小数部を取得
    my $decimal_part = $input - $degrees;

    # 小数部を分に変換
    my $minutes = sprintf("%.9f", $decimal_part * 60);

    # 整数部と小数部を結合して経度を生成
    my $converted_longitude = sprintf("%3d%s", $degrees, $minutes);
#	print "$degrees','$converted_longitude\n";
    return $converted_longitude;
}
sub convert_latitude {
    my ($input) = @_;

    # 入力値の整数部を取得
    my $degrees = int($input);

    # 入力値の小数部を取得
    my $decimal_part = $input - $degrees;

    # 小数部を分に変換
    my $minutes = sprintf("%.9f", $decimal_part * 60);

    # 整数部と小数部を結合して緯度を生成
    my $converted_latitude = sprintf("%02d%s", $degrees, $minutes);

    return $converted_latitude;
}

sub calculate_nmea_checksum {
    my ($sentence) = @_;
    my $checksum = 0;
	
	    # チェックサム計算対象の文字列から$の部分を取り除く
	$sentence =~ s/\$//;
    
    # 文字列内の各文字のASCIIコードを加算する
    foreach my $char (split //, $sentence) {
        $checksum ^= ord($char);
    }
    
    # チェックサムを16進数に変換して返す
    return sprintf("%02X", $checksum);
}

my @ row = ();

# ファイルフォーマット:緯度,経度 ddd.dddd度
# GNGGAにするときには、ddmm.mmmm に直します。
$filename = $ARGV[0];
open (IN, "./$filename")
     or die ("> error : $!");
while (<IN>){
	chomp;
	push (@row, $_);
	$num_line = $num_line +1;
}
close (IN);

my @ array = ();

$i = 0;
$tc = 0;
$time = 63441.20;

#$data_evn = 0

#print "$num_line\n";



for($i = 0;$i < $num_line;$i++){
	my @column = split (/,/, $row[$i]);
#colum[0] は 緯度 colum[1] 経度

# print "$column[0] $column[1]\n";

my $converted_latitude = convert_latitude($column[0]);
my $converted_longitude = convert_longitude($column[1]);
$conv_time = sprintf("%09.2f", $time);

# print "変換前の緯度: $column[0] ";
# print "変換後の緯度: $converted_latitude ";
# print "変換前の経度: $column[1] ";
# print "変換後の経度: $converted_longitude\n";

my $sentence = '$GNGGA,'.$conv_time.','.$converted_latitude.',N,'.$converted_longitude.
',E,4,13,0.91,205.863,M,41.731,M,4.2,2108';

my $checksum = calculate_nmea_checksum($sentence);

#print "$converted_latitude,$converted_longitude\n";
print "$sentence*$checksum\n";
# print "Checksum: $checksum\n";

$time = $time + 0.1;

}