#!/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 # TODO: # - implement and test W-long and S-lati # - detect SIRFIII- and Nemerix data automatically # - trackmaker support # - time area selection use strict; ############################################################################### # Usage ############################################################################### ( my $prg = $0 ) =~ s/^.*\/([^\/]+)$/$1/g; 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 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 ############################################################################### # 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; 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; while( sysread( SRFILE, $_, 16) != 15 ) { # parse the record, this trick took me one whole day... my ($lon, $lat, $year, $month, $day, $hour, $minute, $second, $speed, $e) = unpack( " V V C C C C C C C C", $_ ); # _Longitude_ _Latitude__ YY MM DD HH MM SS Spd # bf f3 b7 00 c1 df 02 03 07 09 1d 06 01 2a 00 ff # last if empty memory area found, detected by 0xFF last if $lon > 360000000; # count the records readed $record_no++; # this should never happen die "Unexpected last byte in record no. $record_no, abort.\n" if $e ne 0xFF; # 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; # remember tis position for next distance calculation $last_lon = $lon; $last_lat = $lat; } close SRFILE; # 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"; } ############################################################################### # Print to terminal ############################################################################### elsif ( $format eq "print" ) { print <{$time[$i]}; printf "%11.6f %11.6f %4d %02d/%02d/%04d %02d:%02d:%02d %d\n", $record->{lon}, $record->{lat}, $record->{spd}, $day, $month, $year, $hour, $minute, $second, $record->{dst}; } printf "Total distance : %d Meter\r\n", $total_distance; } ############################################################################### # 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; }