#!/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 # TODO: # - detect SIRFIII- and Nemerix data automatically # - trackmaker support # - tag support # - export altitude in gpx format (how ?) use Getopt::Std; use strict; $| = 1; ############################################################################### # Usage ############################################################################### ( my $prg = $0 ) =~ s/^.*\/([^\/]+)$/$1/g; sub usage { die < Options: -d split outfiles into daily pieces -b begin at YYYYMMDDHHMMSS (ignore trackpoints before this date), combined with option -z the local time is affected -e end at YYYYMMDDHHMMSS (ignore trackpoints after this date), combined with option -z the local time is affected -z add value to the GMT timestamps from the logger to convert loggers GMT to local time, value can be negative too. This conversation is sloooooow because we use the system command `date` for every timestamp to convert it to epoch time. -i select format of the sr-file: iTrackU-Nemerix (defaut) iTrackU-SIRFIII PhotoTrackr Currently the following output formates 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 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 -i PhotoTrackr dcsv SortDB.sr This software is published under the GPL V2.0 EOF } my %o; usage unless getopts('db:e:z:i:', \%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'} =~ /^\d{14}$/ or die "Date format wrong, use YYYYMMDDHHMMSS\n"; $o{'e'} =~ /^\d{14}$/ or die "Date format wrong, use YYYYMMDDHHMMSS\n"; $o{'i'} = "iTrackU" unless defined $o{'i'}; ############################################################################### # 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 print_to_terminal($$); sub write_csv($$$); sub write_ecsv($$$); sub write_dcsv($$$); sub write_kml($$$); sub write_gpx($$$); ############################################################################### # 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) != 15 ) { # count the records readed $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' ) ) { # 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 lc( 'iTrackU-SIRFIII' ) ) ){ ($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, must be either iTrackU or PhotoTrackr"; } # the windows tool see end of data if lon is 0xFFFFFFFF if ( $lon == 0xFFFFFFFF ) { printf "End of data found at byte 0x%s (record %d)\n", join( ":", unpack( "A4 A4", uc sprintf( "%08x", $record_no * 16 - 1))), $record_no; $record_no--; last; } # west lon starts from 0x80000000 # Nemerix specific ??? if ( $lon >= 0x80000000 ) { $lon -= 0x80000000; $lon *= -1; } # south lat starts from 0x80000000 # Nemerix specific ??? if ( $lat >= 0x80000000 ) { $lat -= 0x80000000; $lat *= -1; } # detect and skip scrambeld 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++; die " Giving up\n" if $skipped_bytes > 68; # go back one record, skip one byte and try again sysseek SRFILE, -15, 1; 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); } # convert timestamps to localtime, if required if ( $o{'z'} != 0 ) { ($second,$minute,$hour,$day,$month,$year,$_,$_,$_) = localtime( `date -d "$year/$month/$day $hour:$minute:$second" +%s` + $o{'z'} * 3600); $year += 1900; $month++; } # 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 tis position for next distance calculation $last_lon = $lon; $last_lat = $lat; } close SRFILE; warn < 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 { ( my $std_outfile = $srfile ) =~ s/\.\w+$//g; @{$outfile{$std_outfile}} = @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 "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])) } ############################################################################### # Write out csv ############################################################################### sub write_csv($$$) { my $time = shift; my $d = shift; my $outfile = shift; my $total_distance = 0; stat $outfile and die "$outfile already exists - Abort\n"; 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 ) = unpack "A4A2A2A2A2A2", $$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 die "$outfile already exists - Abort\n"; 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 ) = unpack "A4A2A2A2A2A2", $$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 die "$outfile already exists - Abort\n"; 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 ) = unpack "A4A2A2A2A2A2", $$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 kml ############################################################################### sub write_kml($$$) { my $time = shift; my $d = shift; my $outfile = shift; my $total_distance = 0; stat $outfile and die "$outfile already exists - Abort\n"; 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 < $ENV{USER}'s GPS Logger XAiOX iTrackU 1 Tracklogsi 1 $track_name EOF for ( my $i = 0; $i <= $#$time; $i++ ) { my ( $year, $month, $day, $hour, $minute, $second ) = unpack "A4A2A2A2A2A2", $$time[$i]; 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 close OUTFILE; print "Wrote " . $#$time . " records to $outfile\n"; } ############################################################################### # Write out gpx ############################################################################### sub write_gpx($$$) { my $time = shift; my $d = shift; my $outfile = shift; my $total_distance = 0; stat $outfile and die "$outfile already exists - Abort\n"; 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 # Generate date in gpx header as date from ( $year, $month, $day, $hour, $minute, $second ) = unpack "A4A2A2A2A2A2", $$time[1]; printf OUTFILE " \n", $year, $month, $day, $hour, $minute, $second; print OUTFILE < EOF for ( my $i = 0; $i <= $#$time; $i++ ) { my ( $year, $month, $day, $hour, $minute, $second ) = unpack "A4A2A2A2A2A2", $$time[$i]; printf OUTFILE " \n", $d->{$$time[$i]}->{lat}, $d->{$$time[$i]}->{lon}; printf OUTFILE " %.1f\n", $d->{$$time[$i]}->{altitude}; printf OUTFILE " \n", $year, $month, $day, $hour, $minute, $second; print OUTFILE <0.000000 -0.000000 EOF $total_distance += $d->{$$time[$i]}->{dst}; } print OUTFILE < 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 <{$$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; } ############################################################################### # 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; }