#!/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 # TODO: # - detect SIRFIII- and Nemerix data automatically # - trackmaker support # - tag support 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 YYYYMMDD[HHMMSS] (ignore trackpoints before this date), combined with option -z the local time is affected -e end at YYYYMMDD[HHMMSS] (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. -i select format of the sr-file: iTrackU-Nemerix (defaut) iTrackU-SIRFIII PhotoTrackr -o Basename of the output file without extension instead of the default sr-file basename. This option is ignored in if option -d is given. 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 -b 20080102 -e 20080102 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:', \%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; ( $o{'o'} = $srfile ) =~ s/\.\w+$//g unless defined $o{'o'}; 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_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 <> 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 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++; # 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); } # 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++; } } # 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; 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 { @{$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 "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 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 ) = 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 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 ) = 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 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 ) = 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 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 < $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 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 # 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; printf OUTFILE " %.2f\n", $d->{$$time[$i]}->{spd} / 3.6; print OUTFILE <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; } ############################################################################### # 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; }