#!/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 0.000000 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 < Use - as filename for the memory dump to read from STDIN. 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: n or iTrackU-Nemerix (defaut) s or iTrackU-SIRFIII p or PhotoTrackr -o 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 <$srfile" or die $!; @_ = ; 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 < 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" . ($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 < $ENV{USER}'s GPS Logger XAiOX iTrackU 1 Tracklogs 1 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 $inList = 0; } if ($inList == 0) { &open_kml_placemark($$time[$i]); print OUTFILE <#track> 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 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 < $coord EOF } print OUTFILE < 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 < $year/$month/$day $hour:$minute:$second 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 # Generate date in gpx header as date of first trackpoint ( $year, $month, $day, $hour, $minute, $second ) = unpack "A4A2A2A2A2A2", $$time[1]; printf OUTFILE " \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 " \n"; print OUTFILE " \n"; } # start new track print OUTFILE " \n"; if ( $d->{$$time[$i]}->{tag} == 0xFF ) { printf OUTFILE " Default %.1d\n", $defaultTrkNo++; } else { printf OUTFILE " Tag %.1d\n", $d->{$$time[$i]}->{tag}; } printf OUTFILE " %.1d\n", $trkNo++; print OUTFILE " \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 " \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 < 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; }