#!/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, 09.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 # TODO: # - detect SIRFIII- and Nemerix data automatically # - trackmaker support # - time area selection # - tag support use Getopt::Std; use strict; $| = 1; ############################################################################### # Usage ############################################################################### ( my $prg = $0 ) =~ s/^.*\/([^\/]+)$/$1/g; sub usage { die < 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 - GoogleEarth format Example: $prg csv iTu4l_20070410204511.dump or: $prg dcsv SortDB.sr ******************************************************************************* Warning: The datum and time parsing currently assumes, that the logger data file comes from a device with Nemerix chipset. If you work with data from a logger with SIRFIII chipset, the time and data values in all output file formats except kml will be incorrect. Please send me a SIRFIII DB.sr file and the corresponding .csv-File. Then I try to implement autodetection of the chipsets and this warning disappears. Do not worry about kml-Files (GoogleEarth), they make not use of date and time. ******************************************************************************* This software is published under the GPL V2.0 EOF } #my %o; #usage unless getopts('d', \%o); usage if ( $#ARGV != 1 ); ############################################################################### # Declarations and definitions ############################################################################### my $format = shift; my $srfile = shift; ( my $outfile = $srfile ) =~ s/\.\w+$/.$format/g; my $d; # main data structure Hash my $total_distance = 0; my $last_lat; my $last_lon; use constant PI => 4 * atan2(1, 1); sub distance($$$$); ############################################################################### # 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++; # parse the record, this trick took me one whole day... my ($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 # the windows tool seed 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; 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 try # 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 ) or (( $tag > 2 ) and ( $tag != 0xFF ) ) ) { 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); } $total_distance += $distance; # 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}->{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 # sort data records by time my @time = sort keys %$d; ############################################################################### # Write out csv ############################################################################### if ( $format eq "csv" ) { 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}; } printf OUTFILE "Total distance : %d Meter", $total_distance; print OUTFILE chr(0); close OUTFILE; print "Wrote " . $#time . " records to $outfile\n"; } ############################################################################### # Write out ecsv ############################################################################### elsif ( $format eq "ecsv" ) { $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),\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,\r\n", $record->{lon}, $record->{lat}, $record->{spd}, $year, $month, $day, $hour, $minute, $second, $record->{dst}; } printf OUTFILE "Total distance : %d Meter\r\n", $total_distance; close OUTFILE; print "Wrote " . $#time . " records to $outfile\n"; } ############################################################################### # Write out dcsv ############################################################################### elsif ( $format eq "dcsv" ) { $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);\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;\r\n", $lon, $lat, $record->{spd}, $day, $month, $year, $hour, $minute, $second, $record->{dst}; } printf OUTFILE "Gesamtstrecke : %d Meter\r\n", $total_distance; close OUTFILE; print "Wrote " . $#time . " records to $outfile\n"; } ############################################################################### # Write out kml ############################################################################### elsif ( $format eq "kml" ) { 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,0\n", $d->{$time[$i]}->{lon}, $d->{$time[$i]}->{lat}; } print OUTFILE < EOF close OUTFILE; print "Wrote " . $#time . " records to $outfile\n"; } ############################################################################### # Write out gpx ############################################################################### elsif ( $format eq "gpx" ) { 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]}->{lon}, $d->{$time[$i]}->{lat}; print OUTFILE " 0.000000\n"; printf OUTFILE " \n", $year, $month, $day, $hour, $minute, $second; print OUTFILE <0.000000 -0.000000 EOF } print OUTFILE < EOF close OUTFILE; print "Wrote " . $#time . " records to $outfile\n"; } ############################################################################### # Print to terminal ############################################################################### elsif ( $format eq "print" ) { print <{$time[$i]}; printf "%6d %11.6f %11.6f %4d %02d/%02d/%04d %02d:%02d:%02d %10d", $i+1, $record->{lon}, $record->{lat}, $record->{spd}, $day, $month, $year, $hour, $minute, $second, $record->{dst}; if ( $record->{tag} != 255 ) { print " $record->{tag}\n"; } else { print "\n"; } } printf "Total distance : %.2f km\r\n", $total_distance/1000; } ############################################################################### # not (yet) supported formats ############################################################################### 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])) } ############################################################################### # distance between two dicimal 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 ==> natical miles $distance *= 180 * 60 / PI; # conversation natical miles ==> meter $distance *= 1852; return $distance; }