File: [tkperl] / igc_tool.pl (download)
Revision 1.0, Mon Jul 2 02:12:37 2007 UTC (16 years, 10 months ago) by philip
Branch: MAIN
Initial load
|
#! /usr/bin/perl -w
use Tk;
use Tk::widgets qw/Dialog ErrorDialog ROText/;
use Time::Local qw/timegm_nocheck timelocal/;
use strict;
my $mw = MainWindow->new;
$mw->CmdLine;
$mw->title("IGC log tool");
$mw->configure(-menu => my $menubar = $mw->Menu);
my $file = $menubar->cascade(qw/-label File -underline 0 -menuitems/ =>
[
[command => 'Open', -command => [\&load_igc]],
[command => 'Exit', -command => [\&exit]],
]);
my $igc = $menubar->cascade(qw/-label Log -underline 0 -menuitems/ =>
[
[command => 'Barograph', -command => [\&baro]],
[command => 'Track map', -command => [\&map]],
[command => 'Generate Mapfile', -command => [\&generate_tab]],
[command => 'Split log', -command => [\&logsplit]],
]);
my $help = $menubar->cascade(qw/-label Help -underline 0 -menuitems/ =>
[
[command => 'About'],
[command => 'Usage'],
[command => 'Barograph'],
[command => 'Track map'],
[command => 'Split log'],
]);
my $DIALOG_ABOUT = $mw->Dialog(
-title => 'About IGC tool',
-bitmap => 'info',
-default_button => 'OK',
-buttons => ['OK'],
-text => "IGC log tool\nWindows version\nPhilip Plane\n" .
"6 March 2006\n\nPerl Version $]" .
"\nTk Version $Tk::VERSION",
);
$help->cget(-menu)->entryconfigure('About',
-command => [$DIALOG_ABOUT => 'Show'],
);
my $DIALOG_USAGE = $mw->Dialog(
-title => 'Using IGC tool',
-bitmap => 'info',
-default_button => 'OK',
-buttons => ['OK'],
-text => "To use the IGC tool, first open an IGC log file " .
"from the File menu. The text of the log will " .
"be displayed in the main window. " .
"When a file has been loaded the actions in the Log " .
"menu can be applied to the log.",
);
$help->cget(-menu)->entryconfigure('Usage',
-command => [$DIALOG_USAGE => 'Show'],
);
my $DIALOG_BARO = $mw->Dialog(
-title => 'Barograph',
-bitmap => 'info',
-default_button => 'OK',
-buttons => ['OK'],
-text => "The barograph trace shows height in feet, " .
"and various information from the log. " ,
);
$help->cget(-menu)->entryconfigure('Barograph',
-command => [$DIALOG_BARO => 'Show'],
);
my $DIALOG_MAP = $mw->Dialog(
-title => 'Track map',
-bitmap => 'info',
-default_button => 'OK',
-buttons => ['OK'],
-text => "The track map shows the track from the log file, " .
"any turnpoints in the log file, " .
"and any task in the log file. " .
"Projection is straight xy." .
"Turnpoints are shown as 500 meter circles",
);
$help->cget(-menu)->entryconfigure('Track map',
-command => [$DIALOG_MAP => 'Show'],
);
my $DIALOG_SPLIT = $mw->Dialog(
-title => 'Split log',
-bitmap => 'info',
-default_button => 'OK',
-buttons => ['OK'],
-text => "Some IGC loggers and their software produce logs " .
"that have more than one flight in the log. " .
"The log splitter scans through the log and attempts " .
"to seperate the flights into individual logs. " .
"If the number of logs it finds sounds reasonable, " .
"save the new logs to disk. The log files are named " .
"using the original file name with the eighth character " .
"replaced with a sequence number. ",
);
$help->cget(-menu)->entryconfigure('Split log',
-command => [$DIALOG_SPLIT => 'Show'],
);
my $text = $mw->Scrolled("ROText")->pack();
# Globals from IGC file
my $maxalt;
my $minalt;
my $maxpres;
my $minpres;
my $minlat;
my $maxlat;
my $minlon;
my $maxlon;
my $mday;
my $month;
my $mon;
my $year;
my $time;
my $glider;
my $cid;
my $pilot;
my $recorder;
my $igcfile;
my @log;
my @tp;
my @task;
my @track;
sub load_igc {
my $types = [
['IGC Files','.igc'],
['IGC Files','.IGC'],
];
my $igc = $mw->getOpenFile(-initialdir=>'/cygwin/home/philip',
-filetypes=>$types);
if ( defined $igc ){
my @igc = split /\//,$igc;
$igcfile = pop @igc;
open IGC, $igc or die "Can't open $igc : $!\n";
my @log = <IGC>;
close IGC;
$text->delete("1.0",'end');
@tp = ();
@task = ();
@track = ();
$maxalt = 0;
$minalt = 99999;
$maxpres = 0;
$minpres = 99999;
$minlat = 90;
$maxlat = -90;
$minlon = 180;
$maxlon = 0;
my $oh = 0;
while ( $_ = shift @log ){
s/\r//;
SWITCH: {
if ( /^HFDTE/ ){
my $date = substr($_,5);
$mday = substr($date,0,2);
$month = substr($date,2,2);
$year = substr($date,-2,2);
$mon = $month - 1;
last SWITCH;
}
if ( /^H.GID/ ){
$glider = substr($_,5);
last SWITCH;
}
if ( /^H.CID/ ){
$cid = substr($_,5);
last SWITCH;
}
if ( /^H.PLT/ ){
$pilot = substr($_,5);
last SWITCH;
}
if ( /^H.FTY/ ){
$recorder = substr($_,5);
chop $recorder;
last SWITCH;
}
if ( /^L\D{4}?(\d{5}?)(\d{7}?[NS])(\d{8}?[WE])(\d{6})(.{8}?)(.*)/ ){
# [ lat, lon, id, name, type ]
# turnpoints
my $lat = latitude($2);
my $lon = longitude($3);
push @tp, [ $lat, $lon, $1, $6, $5 ];
last SWITCH;
}
if ( /^C(\d{7}?[NS])(\d{8}?[WE])(.*)/ ){
# [ lat, lon, name ]
# task
my $lat = latitude($1);
my $lon = longitude($2);
if ( $lat == 0 ){
last SWITCH;
}
push @task, [ $lat, $lon, $3 ];
last SWITCH;
}
if ( /^B(\d{6})(\d{7}[NS])(\d{8}?[WE]).{1}(\d{5})(\d{5})/ ){
$time = $1;
my $lat = latitude($2);
my $lon = longitude($3);
my $alt = $4;
my $pres = $5;
# $time is HHMMSS
$time =~ /(\d{2})(\d{2})(\d{2})/;
my $hour = $1;
my $min = $2;
my $sec = $3;
if ( $hour < $oh ) {
$mday++;
}
$oh = $hour;
my $gt = timegm_nocheck($sec,$min,$hour,$mday,$mon,$year);
if ( $lat > $maxlat ) {
$maxlat = $lat;
}
if ( $lat < $minlat ) {
$minlat = $lat;
}
if ( $lon > $maxlon ) {
$maxlon = $lon;
}
if ( $lon < $minlon ) {
$minlon = $lon;
}
if ( $alt < $minalt ){
$minalt = $alt;
}
if ( $alt > $maxalt ){
$maxalt = $alt;
}
if ( $pres < $minpres ){
$minpres = $pres;
}
if ( $pres > $maxpres ){
$maxpres = $pres;
}
push @track, [ $gt, $lat, $lon, $alt, $pres ];
last SWITCH;
}
}
$text->insert('end',$_);
}
}
}
sub baro {
my $maxrec = scalar @track;
if ( $maxrec > 0 ) {
my $bwin = $mw->Toplevel();
$bwin->title($igcfile . " Barograph");
my $bg = $bwin->Scrolled('Canvas', -width => 850, -height => 500, -scrollregion => [-10,-10,1000,1000])->pack();
my $id;
my $date = sprintf("%02d/%02d/20%02d",$mday,$month,$year);
if ( not defined $glider ) {
$glider = $cid;
}
#clean up pilot name, remove extra whitespace
if ( defined $pilot ){
chop $pilot;
while ( $pilot =~ m/ / ){
$pilot =~ s/ / /;
}
}
if ( defined $glider ){
chop $glider;
}
my $gps = sprintf("GPS Low point: %5d ft High point: %5d ft",
feet($minalt),feet($maxalt));
my $pres = sprintf("Alt Low point: %5d ft High point: %5d ft",
feet($minpres),feet($maxpres));
my @t = localtime($track[0][0]);
my $start = sprintf("Log started at %02d:%02d:%02d",$t[2],$t[1],$t[0]);
my @f = localtime($track[$maxrec - 1][0]);
my $finish = sprintf("Log ended at %02d:%02d:%02d",$f[2],$f[1],$f[0]);
my $duration = sprintf("Log duration %s",
sec2time($track[$maxrec - 1][0] - $track[0][0]));
$id = $bg->createText(10,10, -text => $pilot, -anchor => "w");
$id = $bg->createText(410,10, -text => $recorder, -anchor => "w");
$id = $bg->createText(10,25, -text => $glider, -anchor => "w");
$id = $bg->createText(10,40, -text => $pres,-fill => "red", -anchor => "w");
$id = $bg->createText(10,55, -text => $gps, -fill => "blue", -anchor => "w");
$id = $bg->createText(10,70, -text => $date, -anchor => "w");
$id = $bg->createText(10,85, -text => $start, -anchor => "w");
$id = $bg->createText(10,100, -text => $finish, -anchor => "w");
$id = $bg->createText(10,115, -text => $duration, -anchor => "w");
my $width = 800;
my $height = 450;
my $hscale = ($track[$maxrec-1][0] - $track[0][0])/($width - 50);
my $vscale = 25;
if ($maxalt < 2000){
$vscale = 10;
}
elsif ($maxalt < 4000){
$vscale = 15;
}
my $m = $maxalt / 305;
# Draw lines at 1000ft (305m)
for(my $mil = 0;$mil < $m;$mil++){
my $ml = 400 - (($mil * 305)/$vscale);
$id = $bg->createLine(40,$ml,$width ,$ml, -dash => [6,4]);
$id = $bg->createText(35,$ml, -text => sprintf("%5d", 1000 * $mil), -anchor => "e");
}
my $or = 400 - ( $track[0][4] / $vscale);
my $ob = 400 - ( $track[0][3] / $vscale);
my $ox = 30;
# munch through the track array [ $gt, $lat, $lon, $alt, $pres ]
my $osec = $track[0][0];
my $olat = $track[0][1];
my $olon = $track[0][2];
my $oalt = $track[0][3];
my $opres = $track[0][4];
my $launch = 0;
for (my $i = 0; $i < $maxrec; $i++) {
my $t_sec = $track[$i][0];
$t_sec = $t_sec - $track[0][0];
my $x = 50 + int($t_sec / $hscale);
my $pres = $track[$i][4];
my $alt = $track[$i][3];
my $dlat = abs($track[$i][1] - $olat);
my $dlng = abs($track[$i][2] - $olon);
my $dist = sqrt(($dlat*$dlat) + ($dlng*$dlng));
my $speed = 0;
my $dsec = $t_sec - $osec;
$osec = $t_sec;
if ( ($dsec > 0) and ($dist > 0)){
$speed = $dist / $dsec;
}
if ( ($speed > 0.000100) and ($oalt < $alt)){
$launch++;
}
if ( $launch == 3 ) {
$id = $bg->createLine($x,400,$x,150, -fill => "black");
$id = $bg->createText($x,140, -text => "takeoff");
$launch++;
}
if ( ($speed < 0.000100) and ($oalt == $alt) and ($launch > 0)){
$launch = 0;
$id = $bg->createLine($x,400,$x,150,-fill => "black");
$id = $bg->createText($x,140, -text => "land");
}
my $y = 400 - ( $pres/$vscale);
$id = $bg->createLine($x,$y,$ox,$or, -fill => "red");
$or = $y;
$y = 400 - ($alt/$vscale);
$id = $bg->createLine($x,$y,$ox,$ob, -fill => "blue");
$ob = $y;
$ox = $x;
$olat = $track[$i][1];
$olon = $track[$i][2];
$opres = $pres;
$oalt = $alt;
}
# add the timeticks
my @start = localtime($track[0][0]);
my @finish = localtime($track[$maxrec-1][0]);
for ( my $h = $start[2]+1;
$h <= $finish[2];
$h++ ){
@t = @start;
$t[2] = $h;
$t[0] = 0;
$t[1] = 0;
my $t = timelocal(@t);
my $x = 50 + int(($t - $track[0][0]) / $hscale);
$id = $bg->createText($x,410, -text => sprintf("%02d:00",$h));
$id = $bg->createLine($x,400,$x,350, -dash => [3,2],
-fill => "black");
}
}
}
sub map {
my $maxrec = scalar @track;
if ( $maxrec > 0 ) {
my $zone;
my $ox;
my $oy;
#use max vertical to set scale as most tracks are taller than wider.
my $scale = $maxlat - $minlat;
my $bwin = $mw->Toplevel();
$bwin->title($igcfile . " Flight Track");
my $bg = $bwin->Scrolled('Canvas', -width => 850, -height => 600, -scrollregion => [-500,-500,1500,1500])->pack();
my $width = 1000;
my $height = 1000;
$scale = $height / $scale;
# 500 meter radius circle
my $r = .004 * $scale;
#show the turnpoints
my $maxtp = scalar @tp;
for (my $t = 0; $t < $maxtp; $t++) {
my $y = $height - (($tp[$t][0] - $minlat) * $scale);
my $x = ($tp[$t][1] - $minlon) * $scale;
my $id = $bg->createText($x, $y + 10 + $r, -text => $tp[$t][3]);
$id = $bg->createOval($x - $r, $y - $r, $x + $r, $y + $r);
}
#show the task
my $maxtask = scalar @task;
if ( $maxtask > 0 ){
$oy = $height - (($task[0][0] - $minlat) * $scale);
$ox = ($task[0][1] - $minlon) * $scale;
}
for (my $t = 0; $t < $maxtask; $t++) {
my $y = $height - (($task[$t][0] - $minlat) * $scale);
my $x = ($task[$t][1] - $minlon) * $scale;
my $id = $bg->createText($x, $y + 10 + $r, -text => $task[$t][2]);
$id = $bg->createOval($x - $r, $y - $r, $x + $r, $y + $r, -outline => "green");
$id = $bg->createLine($x,$y,$ox,$oy, -dash => [6,4], -fill => "green");
$ox = $x;
$oy = $y;
}
#prime the pump
$oy = $height - (($track[0][1] - $minlat) * $scale);
$ox = ($track[0][2] - $minlon) * $scale;
#show the track
for (my $i = 0; $i < $maxrec; $i++) {
my $y = $height - (($track[$i][1] - $minlat) * $scale);
my $x = ($track[$i][2] - $minlon) * $scale;
my $id = $bg->createLine($ox,$oy,$x,$y, -fill => "blue");
$ox = $x;
$oy = $y;
}
}
}
sub generate_tab {
my $header = "sTrackDescr\tiTrkID\tiColor\tsTimestamp\tfLat\tfLong\tfAlt\tfEasting\tfNorthing\n";
#IGClog 123 3:21 44.500000 170.200000 123.0
my $tab = $mw->getSaveFile(-initialdir=>'/',
-initialfile=>'tracklog.txt');
if ( defined $tab ){
open TXT, "> " . $tab or die "Couldn't open file $tab: $!\n";
print TXT $header;
my $maxrec = scalar @track;
for (my $i = 0; $i < $maxrec; $i++) {
my @now = localtime($track[$i][0]);
print TXT "IGCLog\t001\t\t";
printf TXT ("%02d:%02d:%02d\t",$now[2], $now[1], $now[0]);
printf TXT ("%6f\t%6f\n",$track[$i][1], $track[$i][2]);
}
close TXT;
}
}
sub logsplit {
my $maxrec = scalar @log;
my $osec = 0;
my $igccore;
my $igccount = 0;
my @logs =();
for (my $i = 0; $i < $maxrec; $i++) {
if ( $log[$i] =~ /^B(\d{6})(\d{7}[NS])(\d{8}?[WE]).{1}(\d{5})(\d{5})/ ){
my $alt = $4;
my $time = $1;
$time =~ /(\d{2})(\d{2})(\d{2})/;
my $sec = ($1 *3600) + ($2 * 60) + $3;
# if the current second is less than the previous, time
# has gone backwards. This indicates you've crossed the
# boundry of a new day.
if ( $sec < $osec ) {
$osec -= 86400;
}
# if the gap in the log is more than sixty seconds assume that
# it's a new flight
if (( $sec - $osec ) > 60){
$logs[$igccount++] = $igccore;
$igccore = "";
}
$osec = $sec;
}
$igccore .= $log[$i];
}
$logs[$igccount] = $igccore;
my $split = $mw->Dialog(
-title => 'Split IGC log file',
-bitmap => 'question',
-default_button => 'OK',
-buttons => ['OK','Cancel'],
-text => "Found $igccount logs.\nWant to create seperate log files for them?\n",
)->Show();
if ($split eq 'OK'){
for (my $i = 1; $i <= $igccount; $i++) {
substr($igcfile,7,1) = $i;
my $frag = $mw->getSaveFile(-initialdir=>'/',
-initialfile=>$igcfile);
if ( defined $frag ){
open TEMP, ">" . $frag or die "Couldn't open $frag: $!\n";
print TEMP $logs[0], $logs[$i];
close TEMP;
}
}
}
}
sub feet{
my $meters = shift @_;
my $feet = $meters * 3.2808399;
return int($feet);
}
sub duration{
# expects 2 parameters, start time and finish time
my $start = shift @_;
my $end = shift @_;
my $ss = seconds($start);
my $se = seconds($end);
# if we finished before we started, we crossed the day boundry
if ( $se < $ss ) {
$se += 60 * 60 * 24;
}
return $se - $ss;
}
sub seconds{
my $time = shift @_;
my $hours = substr($time,0,2);
my $minutes = substr($time,2,2);
my $seconds = substr($time,4,2);
return $seconds + ($minutes * 60) + ($hours * 60 * 60);
}
sub sec2time{
my $sec = shift @_;
my $hours = int($sec / 3600);
$sec = $sec % 3600;
my $minutes = int($sec / 60);
$sec = $sec % 60;
return sprintf("%02d:%02d:%02d",$hours,$minutes,$sec);
}
sub latitude{
my $lat = shift @_;
my $degrees = substr($lat,0,2);
my $min = '0.' . substr($lat,2,5);
$min = $min * 1.66666667;
$degrees = $degrees + $min;
my $hemi = substr($lat,-1);
if ( $hemi eq 'S' ){
$degrees = 0 - $degrees;
}
return $degrees;
}
sub longitude{
my $lon = shift @_;
my $degrees = substr($lon,0,3);
my $min = '0.' . substr($lon,3,5);
$min = $min * 1.66666667;
$degrees = $degrees + $min;
my $hemi = substr($lon,-1);
if ( $hemi eq 'W' ){
$degrees = 0 - $degrees;
}
return $degrees;
}
MainLoop;