--- AnyEvent-GPSD/GPSD.pm 2008/07/18 01:31:12 1.4 +++ AnyEvent-GPSD/GPSD.pm 2008/07/26 05:34:58 1.7 @@ -59,6 +59,12 @@ The port to connect to, default is C<2947>. +=item min_speed => $speed_in_m_per_s + +Sets the mininum speed (default: 0) that is considered real for the +purposes of replay compression or estimate. Speeds below this value will +be considered 0. + =item on_error => $cb->($gps) Called on every connection or protocol failure, reason is in C<$!> @@ -142,10 +148,16 @@ $self } +sub DESTROY { + my ($self) = @_; + + $self->record_log; +} + sub event { my $event = splice @_, 1, 1, (); - warn "event<$event,@_>\n";#d# + #warn "event<$event,@_>\n";#d# if ($event = $_[0]{"on_$event"}) { &$event; } @@ -216,7 +228,8 @@ $_[0]{rbuf} =~ s/^([^\015\012]*)\015\012// or return; - $self->feed ($1); + $self->feed ($1) + unless $self->{replay_cb}; }, ; @@ -285,6 +298,19 @@ $fix->{$_} = $type eq "?" ? undef : $type; } + if (my $s = $self->{stretch}) { + $s = 1 / $s; + + $fix->{herr} *= $s; # ? + $fix->{verr} *= $s; # ? + $fix->{berr} *= $s; # ? + $fix->{serr} *= $s; # ? + $fix->{vserr} *= $s; # ? + + $fix->{speed} *= $s; + $fix->{vspeed} *= $s; + } + $fix->{mode} = 2 if $fix->{mode} eq "?"; # arbitrary choice } else { $fix->{mode} = 1; @@ -324,9 +350,13 @@ =item ($lat, $lon) = $gps->estimate ([$max_seconds]) This returns an estimate of the current position based on the last fix and -the time passed since then. Useful for interactive applications where you -want more frequent updates, but not very useful to store, as the next fix -might well be totally off. +the time passed since then. + +Useful for interactive applications where you want more frequent updates, +but not very useful to store, as the next fix might well be totally +off. For example, when displaying a real-time map, you could simply call +C ten times a second and update the cursor or map position, but +you should use C to actually gather data to plot the course itself. If the fix is older then C<$max_seconds> (default: C<1.9> times the update interval, i.e. usually C<1.9> seconds) or if no fix is available, returns @@ -348,7 +378,7 @@ $diff <= $max or return; - if ($fix->{speed} > $fix->{serr}) { + if ($fix->{speed} >= $self->{min_speed}) { my ($lat, $lon) = $geo->forward ($fix->{lat}, $fix->{lon}, $fix->{bearing}, $fix->{speed} * $diff); ($lat, $lon) @@ -365,15 +395,130 @@ if $self->{logfh}; } +=item $gps->record_log ($path) + +If C<$path> is defined, then that file will be created or truncated and a +log of all (raw) packets received will be written to it. This log file can +later be replayed by calling C<< $gps->replay_log ($path) >>. + +If C<$path> is undefined then the log will be closed. + +=cut + sub record_log { - my ($self, $path) = @_, + my ($self, $path) = @_; + + if (defined $path) { + $self->record_log; + + require JSON; + + open $self->{logfh}, ">:perlio", $path + or Carp::croak "$path: $!"; + + $self->log (start => $VERSION, 0, 0, { interval => $self->{interval} }); + } elsif ($self->{logfh}) { + $self->log ("stop"); + delete $self->{logfh}; + } +} + +=item $gps->replay_log ($path, %options) + +Replays a log file written using C (or stops replaying when +C<$path> is undefined). While the log file replays, real GPS events will +be ignored. This comes in handy when testing. + +Please note that replaying a log will change configuration options that +will not be restored, so it's best not to reuse a gpsd object after a +replay. + +The C distribution comes with an example log +(F) that you can replay for testing or enjoyment +purposes. + +The options include: - require JSON; +=over 4 - open $self->{logfh}, ">", $path - or Carp::croak "$path: $!"; +=item compress => 1 - $self->log (start => $VERSION); +If set to a true value (default: false), then passages without fix will be +replayed much faster than passages with fix. The same happens for passages +without much movement. + +=item stretch => $factor + +Multiplies all times by the given factor. Values < 1 make the log replay +faster, values > 1 slower. Note that the frequency of fixes will not be +increased, o stretch factors > 1 do not work well. + +A stretch factor of zero is not allowed, but if you want to replay a log +instantly you may speicfy a very low value (e.g. 1e-10). + +=back + +=cut + +sub replay_log { + my ($self, $path, %option) = @_; + + if (defined $path) { + $self->replay_log; + + require JSON; + + open my $fh, "<:perlio", $path + or Carp::croak "$path: $!"; + + $self->{stretch} = $option{stretch} || 1; + $self->{compress} = $option{compress}; + + $self->{imterval} /= $self->{stretch}; + + Scalar::Util::weaken $self; + + $self->{replay_cb} = sub { + my $line = <$fh>; + + if (2 > length $line) { + $self->replay_log; + } else { + my ($time, $type, @data) = @{ JSON::decode_json ($line) }; + + $time *= $self->{stretch}; + + if ($type eq "start") { + my ($module_version, $major_version, $minor_version, $args) = @data; + + $self->{interval} = ($args->{interval} || 1) / $self->{stretch}; + } + + if ( + $type eq "start" + or ($self->{compress} + and $self->{fix} && ($self->{fix}{mode} < 2 || $self->{fix}{speed} < $self->{min_speed})) + ) { + $self->{replay_now} = $time; + } + + $self->{replay_timer} = AnyEvent->timer (after => $time - $self->{replay_now}, cb => sub { + $self->{replay_now} = $time; + $self->{command} = []; # no can do + $self->feed ($data[0]) if $type eq "raw"; + $self->{replay_cb}(); + }); + } + }; + + $self->{replay_cb}(); + + } else { + delete $self->{stretch}; + delete $self->{compress}; + delete $self->{replay_timer}; + delete $self->{replay_cb}; + } } =back