#!/usr/bin/perl -w
###############################################################################
# THIS SOFTWARE IS FREE SOFTWARE! IT IS LICENCED UNDER THE GNU PUBLIC LICENCE
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; version 2 of the License
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
###############################################################################
# (c) Hartmut Schimmel, 07.10.2007

# 07.10.2007 sml first release
# 08.10.2007 sml resume for scrambled data added
# 12.10.2007 sml support for southern hemisphere and western lon added
# 12.10.2007 sml support for gpx format added
# 13.10.2007 sml time selection and daily split added
# 14.10.2007 sml time zone conversation added
# 23.10.2007 SH  add GiSTEQ PhotoTrackr support
# 29.10.2007 sml removed tag as scramble criteria
#                added GiSTEQ = iTrackU-SIRFIII support
# 29.10.2007 SH  add altitude to GPX
# 02.01.2008 sml never give up on scrambled data, write out what was readed
#                added timezone conversation acceleration by Module Date::Calc
#                added Backup for existing outfiles
#                added option -o for output file basename
#                added speed in GPX
#                added 00:00:00 time assumtion if HHMMSS is omitted in -b and
#                -e
# 04.01.2008 sml fixed fallback to date -d if Date::Calc is not available
# 28.01.2008 sml added tag support for gpx, thanks to Steffen Moldaner
#                added short arguments for option -i
#                added memory dump read from STDIN
#                removed <course>0.000000</course> in gpx because it is optional
# 03.12.2008 sml added Postgres SQL as export format (thanks to B.W.H. van
#                Beest)
# 10.12.2008 sml applied Denis N. Antonioli's patch, which added tag-based
#                waypoints in kml and fixed some typos. Thanks, Denis.
# 27.12.2008 dna kml waypoints tag bug fixed by Denis N. Antonioli
# 16.04.2009 dna time zone handling changed: kml and GPX is now GMT, csv, sql
#                and print is still local time
# 14.05.2009 sml altitude value separator in dcsv export changed from , to ;
#                (thanks to Holger Kirsch)
# 03.11.2009 sml skip empty 0xFFFFFFFF records instead of recognize eof because
#                there appear data after such empty records
#
# TODO:
# - detect SIRFIII- and Nemerix data automatically


use Getopt::Std;
use strict;
$| = 1;


###############################################################################
# Usage
###############################################################################
( my $prg = $0 ) =~ s/^.*\/([^\/]+)$/$1/g;

sub usage {
  die <<EOF;

$prg converts memory dumps of the XAIOX iTrackU GPS logger to several
formats. You can process .sr-Files from the XAiOX Windows tool too.

(c) Hartmut Schimmel, $prg\@schimmelnetz.de, 2007/10/13

Usage: $prg [options] <Format> <memory_dump.sr>

Use - as filename for the memory dump to read from STDIN.


Options: -d                    split outfiles into daily pieces

         -b <YYYYMMDD[HHMMSS]> begin at YYYYMMDD[HHMMSS] (ignore trackpoints
                               before this date), combined with option -z the
                               local time is affected

         -e <YYYYMMDD[HHMMSS]> end at YYYYMMDD[HHMMSS] (ignore trackpoints
                               after this date), combined with option -z the
                               local time is affected

         -z <value>            add value to the GMT timestamps from the logger
                               to convert loggers GMT to local time, value can
                               be negative too.

         -i <value>            select format of the sr-file:
                                n or iTrackU-Nemerix (defaut)
                                s or iTrackU-SIRFIII
                                p or PhotoTrackr

         -o <outfile>          Basename of the output file without extension
                               instead of the default sr-file basename. This
                               option is ignored if option -d is given.
                               You must give this option if you read the
                               input file fron STDIN


Currently the following output formats are supported:

 print - values on the screen, no file writing

 csv   - comma separated value for Excel
         (exact as the original csv for applications which rely on this)

 ecsv  - comma separated value for Excel, extension will be .csv too
         (fit better than csv into _e_nglish Excel settings)

 dcsv  - semicolon separated values for Excel, extension will be .csv too
         (fit better than csv into german (_d_eutsche) Excel settings)

 track - Trackmaker format, extension will be .txt

 kml   - GoogleEarth format

 gpx   - GPX format

 sql   - SQL format

Example: $prg csv iTu4l_20070410204511.sr

         $prg dcsv SortDB.sr

         $prg -d kml iTu4l_20070410204511.sr

         $prg -z 2 gpx iTu4l_20070410204511.sr

         $prg -b 20071012000000 -e 20071012090000 print 09-13102007.sr
         $prg -b 20080102       -e 20080102       print 09-13102007.sr

         $prg -i PhotoTrackr dcsv SortDB.sr

         $prg -d -i PhotoTrackr sql SortDB.sr

This software is published under the GPL V2.0

EOF
}


my %o;
usage unless getopts('db:e:z:i:o:', \%o);
usage if ( $#ARGV != 1 );

$o{'z'} = 0 unless defined $o{'z'};

$o{'b'} = '00000000000000' unless defined $o{'b'};
$o{'e'} = '99999999999999' unless defined $o{'e'};

$o{'b'} .= "000000" if $o{'b'} =~ /^\d{8}$/;
$o{'e'} .= "000000" if $o{'e'} =~ /^\d{8}$/;
$o{'b'} =~ /^\d{14}$/ or die "Date format wrong, use YYYYMMDD[HHMMSS]\n";
$o{'e'} =~ /^\d{14}$/ or die "Date format wrong, use YYYYMMDD[HHMMSS]\n";

$o{'i'} = "iTrackU-Nemerix" unless defined $o{'i'};

if ( defined( $o{'d'} ) and defined( $o{'o'}) ) {
  warn "Ignoring option -o because option -d is given\n";
}



###############################################################################
# Declarations and definitions
###############################################################################
my $format = shift;
my $srfile = shift;


my $d; # main data structure Hash
my $last_lat;
my $last_lon;

use constant PI => 4 * atan2(1, 1);

sub distance($$$$);
sub backup_existing_outfile($);
sub print_to_terminal($$);
sub write_csv($$$);
sub write_ecsv($$$);
sub write_dcsv($$$);
sub write_sql($$$);
sub write_kml($$$);
sub write_gpx($$$);



###############################################################################
# Check for Date::Calc Perl Module
###############################################################################
my $have_Date_Calc = 0;

if ( $o{'z'} != 0 ) {
  die "Arument to Option -z has to be an integer value, not $o{'z'} - Abort.\n"
                                                if ( $o{'z'} != int( $o{'z'}));
  eval "use Date::Calc";
  if ( $@ ) {
    warn <<EOF;
Warning: Falling back to the extremly slow syscall date method to convert
         timezone because Perl module Date::Calc not found. You can accelerate
         the conversation by installing the Perl's Date::Calc module.
EOF
  }
  else {
    $have_Date_Calc = 1;
  }
}


###############################################################################
# Convert begin and end time from local time to UTC
###############################################################################
if ( $o{'z'} != 0 ) {
  $o{'z'} *= -1;
  $o{'b'} = sprintf "%04d%02d%02d%02d%02d%02d", &convertToLocalTime($o{'b'}) unless $o{'b'} == '00000000000000';
  $o{'e'} = sprintf "%04d%02d%02d%02d%02d%02d", &convertToLocalTime($o{'e'}) unless $o{'e'} == '99999999999999';
  $o{'z'} *= -1;
}

###############################################################################
# Save STDIN to a temprorary file, if nessesary
###############################################################################
my $delete_srfile = 0;
if ( $srfile eq "-" ) {
  die <<EOF unless defined $o{'o'};
You have to specify the output file name with option -o if you read the sr-file
from STDIN - Abort.
EOF

  $srfile = "$o{'o'}" . ".tmp";
  open SRFILE, ">$srfile" or die $!;
  @_ = <STDIN>;
  print SRFILE @_;
  close SRFILE;
  $delete_srfile = 1;
}
( $o{'o'} = $srfile ) =~ s/\.\w+$//g unless defined $o{'o'};



###############################################################################
# Read sr-File
###############################################################################
open SRFILE, "$srfile" or die $!;


# each record is 16 byte long
my $record_no;
my $scrambled_data = 0;
my $skipped_bytes = 0;

while( sysread( SRFILE, $_, 16) == 16 ) {
  # count the records read
  $record_no++;


  my ($lon, $lat, $year, $month, $day, $hour, $minute, $second,
      $speed, $tag, $date, $altitude );
  $altitude = 0;


  #############################################################################
  # parse XAIOX iTrackU with Nemerix chipset data
  #############################################################################
  if (( lc( $o{'i'} ) eq lc( 'iTrackU-Nemerix' ) ) or 
      ( lc( $o{'i'} ) eq 'n' ) ) {

    # parse the record, this trick took me one whole day...
    ($lon, $lat, $year, $month, $day, $hour, $minute, $second, $speed, $tag) =
             unpack( "     V           V        C  C  C  C  C  C  C  C", $_ );
    #                 _Longitude_ _Latitude__  YY MM DD HH MM SS SpdTag
    #                 ef f3 b7 00 d1 df 02 03  07 09 1d 06 01 2a 00 ff
  }


  #############################################################################
  # parse GiSTEQ PhotoTrackr data
  # thanks to Sven Haberer for GiSTEQ PhotoTrackr support
  # format is the same like XAiOX iTrackU with SIRFIII chipset
  # thanks to Erhard Schmidt for iTrackU-SIRFIII support
  #############################################################################
  elsif (( lc( $o{'i'} ) eq lc( 'PhotoTrackr' ) ) or
         ( lc( $o{'i'} ) eq 'p' ) or
         ( lc( $o{'i'} ) eq lc( 'iTrackU-SIRFIII' ) ) or
         ( lc( $o{'i'} ) eq 's' ) ) {

    ($lon, $lat, $date, $altitude, $speed, $tag) =
             unpack( "     V           V           V         s   C  C", $_ ); 
    #                 _Longitude_ _Latitude__  ___Date____ _Alt_ SpdTag
    #                 bd 49 90 00 09 0c 1d 03  b4 eb a8 1e 3b 00 02 63

    $second =  $date        & 63;
    $minute = ($date >> 6)  & 63;
    $hour   = ($date >> 12) & 31;
    $day    = ($date >> 17) & 31;
    $month  = ($date >> 22) & 15;
    $year   = ($date >> 26) & 63;
  }


  else {
    die "undefined input format";
  }


  # the windows tool see end of data if lon is 0xFFFFFFFF
  # but at least on my XAiOX iTrackU Nemerix there are data records behind
  # empty 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF records
  # so we simply skip those empty records
  # sml, 2009/11/03
  if ( $lon == 0xFFFFFFFF ) {
    $record_no--;
    next;
  }



  # west lon starts from 0x80000000
  if ( $lon >= 0x80000000 ) {
    $lon -= 0x80000000;
    $lon *= -1;
  }


  # south lat starts from 0x80000000
  if ( $lat >= 0x80000000 ) {
    $lat -= 0x80000000;
    $lat *= -1;
  }



  # detect and skip scrambled data records
  # TODO: detection not yet perfect, there could go scrambled records thru
  # but not yet seen so in practice
  if (( abs($lon) > 180000000 ) or
      ( abs($lat) >  90000000 ) or
      ( $year     > 30 ) or
      ( $month    > 12 ) or
      ( $day      > 31 ) or
      ( $hour     > 23 ) or
      ( $minute   > 59 ) or
      ( $second   > 59 ) ) {

    if ( $skipped_bytes == 0 ) {
      printf "Data scrambled at byte 0x%s (record %d), trying to resume\n .",
     join( ":", unpack( "A4 A4", uc sprintf( "%08x", ($record_no - 1) * 16 ))),
                                                                    $record_no;
    }
    else {
      print ".";
    }


    # count new scrambling events only once
    $scrambled_data++ if $skipped_bytes == 0;


    # count skipped bytes;
    $skipped_bytes++;


    # go back one record, skip one byte and try again
    sysseek SRFILE, -15, 1;
    $record_no--;
    next;
  }
 
  print " Success after $skipped_bytes skipped bytes\n" if $skipped_bytes > 0;
  $skipped_bytes = 0;


  # conversations
  $year += 2000;
  $speed = sprintf "%3d", $speed * 1.852; # convert speed from kt to kph


  # convert from degree.minutes to decimal degree
  $lon = sprintf("%.6f", int($lon/1000000)
                            + ($lon/1000000 - int($lon/1000000)) * 100 / 60);
  $lat = sprintf("%.6f", int($lat/1000000)
                            + ($lat/1000000 - int($lat/1000000)) * 100 / 60);



  # distance beween this and the last previus point
  my $distance = 0;
  if (( defined $last_lon ) and ( defined $last_lat )) {
    $distance = distance( $lat, $lon, $last_lat, $last_lon);
  }


  # Save data structure, master key is YYYYMMDDHHMMSS
  my $time = sprintf "%04d%02d%02d%02d%02d%02d",
                                  $year, $month, $day, $hour, $minute, $second;
  $d->{$time}->{lon} = $lon;
  $d->{$time}->{lat} = $lat;
  $d->{$time}->{spd} = $speed;
  $d->{$time}->{dst} = $distance;
  $d->{$time}->{altitude} = $altitude;
  $d->{$time}->{tag} = $tag;



  # remember this position for next distance calculation
  $last_lon = $lon;
  $last_lat = $lat;
}
close SRFILE;
unlink $srfile if $delete_srfile;


warn <<EOF if $scrambled_data > 0;

WARNING:
Data was $scrambled_data times scrambled. Try to reread the tracks from the
logger. Sometimes then they are not scrambled anymore. However,
you can still use this data because the scrambled trackpoints where
deleted.

EOF



###############################################################################
# find the times wanted
###############################################################################
my @time_to_output;

foreach ( sort keys %$d ) {
  next if $_ < $o{'b'};
  last if $_ > $o{'e'};
  push @time_to_output, $_;
}



###############################################################################
# write outfile(s)
###############################################################################
my %outfile;


# one file for each day
if ( defined $o{'d'} ) {

  # generate an HoA with all date_times for each day
  # HoA{"10"}[0...n] = ("20071009174949", "20071009174954", "20071009175000" ...)
  foreach ( @time_to_output ){ push @{$outfile{ substr( $_, 0, 8)}}, $_; };
}

# all trackpoints into one file
else {
  @{$outfile{$o{'o'}}} = @time_to_output;
}


foreach my $day ( sort keys %outfile ) {

  my @time = @{$outfile{$day}};
  my $outfile = $day . "." . $format;


  if ( $format eq "csv" ) {
    write_csv(\@time, $d, $outfile);
  }
  elsif ( $format eq "ecsv" ) {
    write_ecsv(\@time, $d, $outfile);
  }
  elsif ( $format eq "dcsv" ) {
    write_dcsv(\@time, $d, $outfile);
  }
  elsif ( $format eq "sql" ) {
    write_sql(\@time, $d, $outfile);
  }
  elsif ( $format eq "kml" ) {
    write_kml(\@time, $d, $outfile);
  }
  elsif ( $format eq "gpx" ) {
    write_gpx(\@time, $d, $outfile);
  }
  elsif ( $format eq "print" ) {
    print_to_terminal(\@time, $d);
  }
  elsif ( $format eq "track" ) {
    $outfile =~ s/\.track$/.txt/g;
    die "Format $format not __yet__ supported.\n";
  }
  elsif ( $format eq "txt" ) {
    die "Format $format not supported, did you mean \"track\" ?\n";
  }
  else {
    die "Format $format not supported.\n";
  }
}

# all done
exit 0;



###############################################################################
###############################################################################
###############################################################################
# Subroutines
###############################################################################
###############################################################################
###############################################################################
sub asin { atan2($_[0], sqrt(1 - $_[0] * $_[0])) }



###############################################################################
# convertToLocalTime() by Denis N. Antonioli
###############################################################################
sub convertToLocalTime() {
  my ($year,$month,$day, $hour,$minute,$second) = unpack "A4A2A2A2A2A2", $_[0];

  # convert timestamps to localtime, if required
  if ( $o{'z'} != 0 ) {
    if ( $have_Date_Calc ) {
      ($year,$month,$day, $hour,$minute,$second) =
        Date::Calc::Add_Delta_DHMS($year,$month,$day, $hour,$minute,$second,0,$o{'z'},0,0);
    } else {
      ($second,$minute,$hour,$day,$month,$year,$_,$_,$_) = 
        localtime( `date -d "$year/$month/$day $hour:$minute:$second" +%s` + $o{'z'} * 3600);
    $year += 1900; $month++;
    }
  }

  return ($year,$month,$day, $hour,$minute,$second);
}



###############################################################################
# Write out csv
###############################################################################
sub write_csv($$$) {
  my $time = shift;
  my $d = shift;
  my $outfile = shift;

  my $total_distance = 0;
  
  stat $outfile and backup_existing_outfile( $outfile);
  open OUTFILE, ">$outfile" or die $!;

  print OUTFILE "Longitude,Latitude,Speed(kilometer),DD/MM/YY,HH:MM:SS," .
                                                         "Distance(meter)\r\n";

  for ( my $i = 0; $i <= $#$time; $i++ ) {

    my ( $year, $month, $day, $hour, $minute, $second ) = &convertToLocalTime( $$time[$i] );

    my $record = $d->{$$time[$i]};
    printf OUTFILE "%.6f,%.6f,%d,%d/%d/%d,%d:%d:%d,%d,\r\n",
            $record->{lon}, $record->{lat}, $record->{spd},
            $day, $month, $year, $hour, $minute, $second, $record->{dst};

    $total_distance += $record->{dst};
  }


  printf OUTFILE "Total distance : %d Meter", $total_distance;
  print  OUTFILE  chr(0);

  close OUTFILE;
  print "Wrote " . $#$time . " records to $outfile\n";
}



###############################################################################
# Write out ecsv
###############################################################################
sub write_ecsv($$$) {
  my $time = shift;
  my $d = shift;
  my $outfile = shift;

  my $total_distance = 0;

  $outfile =~ s/\.ecsv$/.csv/g;
  stat $outfile and backup_existing_outfile( $outfile);
  open OUTFILE, ">$outfile" or die $!;

  print OUTFILE "Longitude,Latitude,Speed(kph),YYYY/MM/DD,HH:MM:SS," .
                                         "Distance(meter),Altitude(meter)\r\n";

  for ( my $i = 0; $i <= $#$time; $i++ ) {

    my ( $year, $month, $day, $hour, $minute, $second ) = &convertToLocalTime( $$time[$i] );

    my $record = $d->{$$time[$i]};
    printf OUTFILE "%.6f,%.6f,%d,%04d/%02d/%02d,%02d:%02d:%02d,%d,%d\r\n",
            $record->{lon}, $record->{lat}, $record->{spd},
            $year, $month, $day, $hour, $minute, $second,
            $record->{dst}, $record->{altitude};

    $total_distance += $record->{dst};
  }


  printf OUTFILE "Total distance : %d Meter\r\n", $total_distance;

  close OUTFILE;
  print "Wrote " . $#$time . " records to $outfile\n";
}



###############################################################################
# Write out dcsv
###############################################################################
sub write_dcsv($$$) {
  my $time = shift;
  my $d = shift;
  my $outfile = shift;

  my $total_distance = 0;
  
  $outfile =~ s/\.dcsv$/.csv/g;
  stat $outfile and backup_existing_outfile( $outfile);
  open OUTFILE, ">$outfile" or die $!;

  print OUTFILE "Longitude;Latitude;Geschwindigkeit(kmh);DD/MM/YYYY;HH:MM:SS;" .
                                            "Strecke(Meter);Hoehe(Meter);\r\n";

  for ( my $i = 0; $i <= $#$time; $i++ ) {

    my ( $year, $month, $day, $hour, $minute, $second ) = &convertToLocalTime( $$time[$i] );

    my $record = $d->{$$time[$i]};
    ( my $lon = $record->{lon} ) =~ s/\./,/g;
    ( my $lat = $record->{lat} ) =~ s/\./,/g;
    printf OUTFILE "%s;%s;%d;%02d.%02d.%04d;%02d:%02d:%02d;%d;%d;\r\n",
            $lon, $lat, $record->{spd},
            $day, $month, $year, $hour, $minute, $second,
            $record->{dst}, $record->{altitude};

    $total_distance += $record->{dst};
  }


  printf OUTFILE "Gesamtstrecke : %d Meter\r\n", $total_distance;

  close OUTFILE;
  print "Wrote " . $#$time . " records to $outfile\n";
}


###############################################################################
# Write out sql (Postgres format) 
###############################################################################
sub write_sql($$$) 
{
  my $time = shift;
  my $d = shift;
  my $outfile = shift;

  my $tracktbl;
  if ( defined $o{'d'} )
  {
#    The trackpoints will be stored in table "trck" . <basename of outfile>
     ($tracktbl = "trck" . $outfile) =~ s/.sql//g;
  }
  else 
  {
      $tracktbl = "trackpoints";
  }

  stat $outfile and backup_existing_outfile( $outfile);
  open OUTFILE, ">$outfile" or die $!;

# Write the SQL CREATE command
  print OUTFILE "CREATE TABLE $tracktbl ( " 
                       . "time TIMESTAMP PRIMARY KEY, longitude FLOAT, latitude FLOAT, altitude FLOAT"
                       . ", distance FLOAT, speed FLOAT );\n";

  for ( my $i = 0; $i <= $#$time; $i++ ) 
  {
    # Faster alternative: just unpack and use a time zone in the sql timestamp
    my ( $year, $month, $day, $hour, $minute, $second ) = &convertToLocalTime($$time[$i]);
    my $record = $d->{$$time[$i]};
    printf OUTFILE "INSERT INTO $tracktbl VALUES ( '%4d-%02d-%02d %02d:%02d:%02d', %10s, %10s, %4d, %4d, %4d);\n",
            $year, $month, $day, $hour, $minute, $second,
            $record->{lon}, $record->{lat}, $record->{altitude}, $record->{dst}, $record->{spd};
  }

  close OUTFILE;
  print "Wrote " . $#$time . " records to $outfile\n";
}



###############################################################################
# Write out kml
###############################################################################
sub write_kml($$$) {  
  my $time = shift;
  my $d = shift;
  my $outfile = shift;

  my $total_distance = 0;
  
  stat $outfile and backup_existing_outfile( $outfile);
  open OUTFILE, ">$outfile" or die $!;

  print OUTFILE <<EOF;
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="http://earth.google.com/kml/2.0">
 <name>$ENV{USER}'s GPS Logger XAiOX iTrackU</name>
 <visibility>1</visibility>

 <Style id="waypoint">
  <icon>
   <href>root://icons/bitmap-4.png?x=32&amp;y=160&amp;w=32&amp;h=32</href>
  </icon>
 </Style>

 <Style id="route">
  <icon>
   <href>root://icons/bitmap-4.png?x=160&amp;y=0&amp;w=32&amp;h=32</href>
  </icon>
 </Style>

 <Style id="track">
  <LineStyle>
   <color>FFFF0000</color>
   <width>1</width>
  </LineStyle>
 </Style>

 <Folder>
  <name>Tracklogs</name>
  <visibility>1</visibility>
EOF

  my @tags;
  my $inList = 0;
  for ( my $i = 0; $i <= $#$time; $i++ ) {
    # 254 is an individual waypoint
    push @tags, $i if $d->{$$time[$i]}->{tag} == 254;
    # 99 is a wake up from sleep, start a new placemark
    if ($inList && $d->{$$time[$i]}->{tag} == 99) {
      print OUTFILE <<EOF;
    </coordinates>
   </LineString>
  </Placemark>
EOF
      $inList = 0;
    }
    if ($inList == 0) {
      &open_kml_placemark($$time[$i]);
      print OUTFILE <<EOF;
   <styleUrl>#track</styleUrl>>
   <LineString>
    <coordinates>
EOF
      $inList = 1;
    }

    printf OUTFILE "     %f,%f,%d\n",
                              $d->{$$time[$i]}->{lon}, $d->{$$time[$i]}->{lat},
                              $d->{$$time[$i]}->{altitude};

    $total_distance += $d->{$$time[$i]}->{dst};
  }

  print OUTFILE <<EOF;
    </coordinates>
   </LineString>
  </Placemark>
EOF

  for my $i (@tags) {
    &open_kml_placemark($$time[$i]);
    my $coord = sprintf("%f,%f,%d",
                              $d->{$$time[$i]}->{lon}, $d->{$$time[$i]}->{lat},
                              $d->{$$time[$i]}->{altitude});
  print OUTFILE <<EOF;
    <Point>
      <coordinates>$coord</coordinates>
    </Point>
  </Placemark>
EOF
  }

  print OUTFILE <<EOF;
 </Folder>

</Document>
EOF

  close OUTFILE;
  print "Wrote " . $#$time . " records to $outfile\n";
}

sub open_kml_placemark($) {
  my $record = shift;
  my ( $year, $month, $day, $hour, $minute, $second ) = unpack "A4A2A2A2A2A2", $record;
  print OUTFILE <<EOF;
  <Placemark>
   <name>$year/$month/$day $hour:$minute:$second</name>
EOF
  }

###############################################################################
# Write out gpx
###############################################################################
sub write_gpx($$$) {  
  my $time = shift;
  my $d = shift;
  my $outfile = shift;

  my $total_distance = 0;
  
  stat $outfile and backup_existing_outfile( $outfile);
  open OUTFILE, ">$outfile" or die $!;



  # Generate the name of this track as date from - to
  my ( $year, $month, $day, $hour, $minute, $second ) =
                                              unpack "A4A2A2A2A2A2", $$time[1];
  my $track_name = sprintf "%02d.%02d.%04d %02d:%02d:%02d-",
                                  $day, $month, $year, $hour, $minute, $second;
  ( $year, $month, $day, $hour, $minute, $second ) =
                                             unpack "A4A2A2A2A2A2", $$time[-1];
  $track_name = sprintf "$track_name%02d.%02d.%04d %02d:%02d:%02d",
                                  $day, $month, $year, $hour, $minute, $second;



  print OUTFILE <<EOF;
<?xml version="1.0"?>
<gpx version="1.0"
     creator="sr2x.pl http://www.schimmelnetz.de/projekte/iTU4l"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns="http://www.topografix.com/GPX/1/0"
     xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd">
EOF

  # Generate date in gpx header as date of first trackpoint
  ( $year, $month, $day, $hour, $minute, $second ) =
                                              unpack "A4A2A2A2A2A2", $$time[1];
  printf OUTFILE " <time>%04d-%02d-%02dT%02d:%02d:%02dZ</time>\n",
                                  $year, $month, $day, $hour, $minute, $second;


  # trigger new tag for overall first trackpoint because the 8-bit tag can
  # never be greater than 0xFF
  my $previousTag = 0xFF + 1;
  my $trkNo = 1;
  my $defaultTrkNo = 1;
  # $o{'z'} is n int, but timeZone could contain minute!

  # for each trackpoint we have
  for ( my $i = 0; $i <= $#$time; $i++ ) {


    # new tag ?
    if ($previousTag != $d->{$$time[$i]}->{tag} ) {


      # close last track if this is not the overall first trackpoint
      unless ( $i == 0 ) {
        print  OUTFILE "  </trkseg>\n";
        print  OUTFILE " </trk>\n";
      }


      # start new track
      print  OUTFILE " <trk>\n";
      if ( $d->{$$time[$i]}->{tag} == 0xFF ) {
        printf OUTFILE "  <name>Default %.1d</name>\n", $defaultTrkNo++;
      }
      else {
        printf OUTFILE "  <name>Tag %.1d</name>\n", $d->{$$time[$i]}->{tag};
      }
      printf OUTFILE "  <number>%.1d</number>\n", $trkNo++;
      print  OUTFILE "  <trkseg>\n";
    }


    # remember this tag
    $previousTag = $d->{$$time[$i]}->{tag};


    # print out the trackpount data
    my ( $year, $month, $day, $hour, $minute, $second ) =
                                             unpack "A4A2A2A2A2A2", $$time[$i];

    printf OUTFILE "   <trkpt lat=\"%f\" lon=\"%f\">\n",
                              $d->{$$time[$i]}->{lat}, $d->{$$time[$i]}->{lon};
    printf OUTFILE "    <ele>%.1f</ele>\n", 
                              $d->{$$time[$i]}->{altitude};
    printf OUTFILE "    <time>%04d-%02d-%02dT%02d:%02d:%02dZ</time>\n",
                                  $year, $month, $day, $hour, $minute, $second;
    printf OUTFILE "    <speed>%.2f</speed>\n", 
                              $d->{$$time[$i]}->{spd} / 3.6;
    print OUTFILE <<EOF;
   </trkpt>
EOF

    $total_distance += $d->{$$time[$i]}->{dst};
  }


  print OUTFILE <<EOF;
  </trkseg>
 </trk>
</gpx>
EOF

  close OUTFILE;
  print "Wrote " . $#$time . " records to $outfile\n";
}



###############################################################################
# Print to terminal
###############################################################################
sub print_to_terminal($$) {
  my $time = shift;
  my $d = shift;

  my $total_distance = 0;
  
  print <<EOF;
No.     Longitude   Latitude   Speed  Date       Time        Distance  Altitude Tag
        decdegree   decdegree  kph    DD/MM/YYYY HH:MM:SS    Metre     Metre
EOF

  for ( my $i = 0; $i <= $#$time; $i++ ) {

    my ( $year, $month, $day, $hour, $minute, $second ) = &convertToLocalTime( $$time[$i] );

    my $record = $d->{$$time[$i]};
    printf "%6d %11.6f %11.6f  %4d  %02d/%02d/%04d %02d:%02d:%02d  %10d %6d",
            $i+1, $record->{lon}, $record->{lat}, $record->{spd},
            $day, $month, $year, $hour, $minute, $second,
            $record->{dst}, $record->{altitude};

    if ( $record->{tag} != 255 ) { 
      print "  $record->{tag}\n";
    }
    else {
      print "\n";
    }

    $total_distance += $record->{dst};
  }
  printf "Total distance : %.2f km\r\n", $total_distance/1000;
}



###############################################################################
# Backup existing outfiles
###############################################################################
sub backup_existing_outfile($) {
  my $outfile = shift;
  my $backupfile = "${outfile}.bak";
  warn "$outfile already exists and was moved to $backupfile\n";
  stat $backupfile and backup_existing_outfile( $backupfile);
  rename $outfile, $backupfile or die "Error: $!\n";
}



###############################################################################
# distance between two decimal coordinates
# http://williams.best.vwh.net/avform.htm
###############################################################################
sub distance($$$$) {
  my $lat1 = shift; 
  my $lon1 = shift; 
  my $lat2 = shift; 
  my $lon2 = shift;


  # convert decimal degree ==> radians
  $lat1 *= PI / 180;
  $lon1 *= PI / 180;
  $lat2 *= PI / 180;
  $lon2 *= PI / 180;


  # calulate the distance using radiants
  my $distance = 2 * asin( sqrt( ( sin(($lat1-$lat2)/2) )**2 + 
                        cos($lat1)*cos($lat2)*(  sin(($lon1-$lon2)/2)  )**2 ));


  # conversation distance_radians ==> nautical miles
  $distance *= 180 * 60 / PI;


  # conversation nautical miles ==> meter
  $distance *= 1852;


  return $distance;
}

