#!/usr/bin/perl
=pod
NMEA-0183ログデータと EXIF付き JPEG画像ファイルから
Google Maps APIを用いた画像付きルートマップHTMLを作成する
使い方:
あらかじめカレントディレクトリに
サムネイル画像 XXXXXs.jpg と拡大画像 XXXXXl.jpg を作成しておく。
その後、NMEA-0183ログデータファイルを引数にして実行する。
ログデータファイルは複数指定可。ただし、時系列に並んでいること。
標準出力に HTMLが出力されるので適当なファイル名でリダイレクトすること。
他の人が使う場合は Google Maps APIキーを取得して書き変えること。
タイトルや画像の名称、コメントなどは HTML作成後、手作業で書き変える。
例:
$ gpsroute 20100420.LOG 20200421.LOG > travel.html
=cut
use strict;
#use warnings;
use Time::Piece qw/localtime gmtime/;
use Image::ExifTool;
my $html_title = "%TITLE%";
my $google_api_key = "ABQIAAAAqJbV3H_KsOOCocfG42JrwhS-ttq4eYygrsJtW1dP-IBsl4Q2dxTODqZie8XdfLgYv0ra3LKYMrp8rw";
my @lat;
my @long;
my @alt;
my @time;
my @speed;
my $lat_min = 90;
my $lat_max = -90;
my $long_min = 180;
my $long_max = -180;
my $data_n = 0;
#my @image_file;
#my @image_time;
#my @image_lat;
#my @image_long;
#my @image_alt;
#my @image_title;
#my @image_width;
#my @image_height;
my @image;
my $image_n = 0;
# カレントディレクトリの画像ファイルのタイムスタンプ取得
opendir(DIR, ".") or die "Cannnot open current directory\n";
my @dir = readdir(DIR);
closedir(DIR);
foreach my $imagefile (@dir) {
if ($imagefile =~ /[^s]s\.(JPG|jpg)$/){
my $info = Image::ExifTool->new->ImageInfo($imagefile);
my $time = $$info{'CreateDate'};
my $datetime = Time::Piece->strptime($time, "%Y:%m:%d %H:%M:%S");
my @size = split(/x/, $$info{'ImageSize'});
$image[$image_n++] = {
'file' => $imagefile,
'time' => $datetime,
'width' => $size[0],
'height' => $size[1]
}
}
}
# タイムスタンプでソート
@image = sort sort_by_time @image;
my $img = 0;
my $param = 0;
# ルート情報取得
foreach my $logfile (@ARGV){
my $lat;
my $long;
my $alt;
my $time;
my $datetime;
my $speed;
my $status;
my $dir;
my $old_lat;
my $old_long;
my $old_dir;
my $old_datetime;
if ($logfile =~ /\-(\w+)/){
if ($1 eq 'raw'){
$param = 'raw';
}
next;
}
open(my $fh, '<', $logfile) or die "Cannot open $logfile : $!";
while (my $line = <$fh>){
if ($line =~ /^\$GPGGA/){
my @data = split(/,/, $line);
$alt = $data[9];
} elsif ($line =~ /^\$GPRMC/){
my @data = split(/,/, $line);
$data[1] = substr($data[1], 0, index($data[1], "."));
$datetime = Time::Piece->strptime("$data[9] $data[1]", "%d%m%y %H%M%S");
$datetime += 9 * 3600; # +09:00
$time = $datetime->ymd . " " . $datetime->hms;
$status = $data[2];
$lat = $data[3];
$long = $data[5];
if ($data[4] eq 'S'){
$lat *= -1;
}
if ($data[6] eq 'W'){
$long *= -1;
}
$lat = (int($lat/100)) + (($lat/100) - int($lat/100)) / 60 * 100;
$long = (int($long/100)) + (($long/100) - int($long/100)) / 60 * 100;
$lat = int($lat * 1000000 + 0.5) / 1000000;
$long = int($long * 1000000 + 0.5) / 1000000;
} elsif ($line =~ /^\$GPVTG/){
my @data = split(/,/, $line);
$speed = $data[7];
}
if (($alt ne undef)&&($time)&&($speed ne undef)){
# 1件分のデータがそろった
if (($old_lat != $lat)&&
($old_long != $long)){
if ($lat > $lat_max){ $lat_max = $lat; }
if ($lat < $lat_min){ $lat_min = $lat; }
if ($long > $long_max){ $long_max = $long; }
if ($long < $long_min){ $long_min = $long; }
# 画像ファイルのタイムスタンプと比較
while (($img < $image_n)&&($datetime >= $image[$img]{'time'})){
$image[$img]{'lat'} = $lat;
$image[$img]{'long'} = $long;
$image[$img]{'alt'} = $alt;
# 仮のタイトル
$image[$img]{'title'} = sprintf("画像 %d", $img+1);
$img++;
}
# 前のポイントとの距離
my $dist = &get_distance($old_lat, $old_long, $lat, $long);
#print "$lat $long $dist(m) $speed(km/h)\n";
if ((($speed < 0.5)&&($dist >= 1))|| # 0.5km/h以下 1m
(($speed < 5)&&($dist >= 10))|| # 5km/h以下 10m
(($speed < 30)&&($dist >= 200))|| # 30km/h以下 200m
(($speed < 60)&&($dist >= 500))|| # 60km/h以下 500m
($dist >= 5000)|| # 高速道 5km
($param eq 'raw')){
push(@lat, $lat);
push(@long, $long);
push(@alt, $alt);
push(@time, $time);
push(@speed, $speed);
$old_lat = $lat;
$old_long = $long;
$data_n++;
}
}
undef $alt;
undef $time;
undef $speed;
}
}
close($fh);
}
my $markers;
my $images;
for (my $i = 0; $i < $image_n; $i++){
my $tm = $image[$i]{'time'}->strftime("%Y/%m/%d %H:%M:%S");
$markers .= "\t\t\t\tmarkers[$i] = createMarker($image[$i]{'lat'}, $image[$i]{'long'}, $image[$i]{'alt'}, '$tm',\n\t\t\t\t'$image[$i]{'file'}', '$image[$i]{'title'}', '', $image[$i]{'width'}, $image[$i]{'height'});\n";
$markers .= "\t\t\t\tmap.addOverlay(markers[$i]);\n";
$images .= "\t$image[$i]{'title'}
\n";
}
my $points;
my $infos;
for (my $i = 0; $i < $data_n; $i++){
$points .= "\t\t\t\tpoints[$i] = new GLatLng($lat[$i], $long[$i]);\n";
$infos .= "\t\t\t\tinfos[$i] = new PInfo(\"$time[$i]\", $alt[$i], $speed[$i]);\n";
}
# 日本国内限定
my $lat = ($lat_max + $lat_min) / 2;
my $long = ($long_max + $long_min) / 2;
# HTMLを標準出力
print <
$html_title
【$html_title】
ZOOM:
$images
EOM
exit(0);
# 距離(m)を求める。日本国内など、狭い範囲にのみ有効
sub get_distance {
my ($lat1, $long1, $lat2, $long2) = @_;
my $dx = 6378137 * (($long2 - $long1) / 180 * 3.14159) * cos($lat1 / 180 * 3.14159);
my $dy = 6378137 * (($lat2 - $lat1) / 180 * 3.14159);
return sqrt($dx * $dx + $dy * $dy);
}
# 移動方向(°)を求める
sub get_direction {
my ($lat1, $long1, $lat2, $long2) = @_;
my $dx = 6378137 * (($long2 - $long1) / 180 * 3.14159) * cos($lat1 / 180 * 3.14159);
my $dy = 6378137 * (($lat2 - $lat1) / 180 * 3.14159);
if ($dx == 0){
if ($dy > 0){
return 0;
} else {
return 180;
}
}
my $dir = atan2($dx, $dy) / 3.14159 * 180;
return $dir;
}
# 画像データハッシュ配列のソート関数
sub sort_by_time {
return $$a{'time'} <=> $$b{'time'};
}