File Coverage

blib/lib/GPSD/Parse.pm
Criterion Covered Total %
statement 149 166 89.7
branch 64 82 78.0
condition 4 6 66.6
subroutine 33 36 91.6
pod 20 20 100.0
total 270 310 87.1


line stmt bran cond sub pod time code
1             package GPSD::Parse;
2              
3 14     14   82378 use strict;
  14         90  
  14         349  
4 14     14   57 use warnings;
  14         21  
  14         331  
5              
6 14     14   60 use Carp qw(croak);
  14         21  
  14         1022  
7 14     14   10459 use IO::Socket::INET;
  14         311059  
  14         69  
8              
9             our $VERSION = '1.03';
10              
11             BEGIN {
12              
13             # look for JSON::XS, and if not available, fall
14             # back to JSON::PP to avoid requiring non-core modules
15              
16 14     14   6238 my $json_ok = eval {
17 14         1898 require JSON::XS;
18 0         0 JSON::XS->import;
19 0         0 1;
20             };
21 14 50       85 if (! $json_ok){
22 14         9682 require JSON::PP;
23 14         250087 JSON::PP->import;
24             }
25             }
26              
27             sub new {
28 26     26 1 6021 my ($class, %args) = @_;
29 26         62 my $self = bless {}, $class;
30              
31 26         124 $self->_file($args{file});
32 26         216 $self->_is_metric($args{metric});
33 26         125 $self->_is_signed($args{signed});
34              
35 26 100       60 if (! $self->_file) {
36 8         38 $self->_port($args{port});
37 8         37 $self->_host($args{host});
38 8         36 $self->_socket;
39 0         0 $self->_is_socket(1);
40 0         0 $self->on;
41             }
42              
43 18         55 return $self;
44             }
45             sub on {
46 0     0 1 0 $_[0]->_socket->send('?WATCH={"enable": true}' . "\n");
47             }
48             sub off {
49 0     0 1 0 $_[0]->_socket->send('?WATCH={"enable": false}' . "\n");
50             }
51             sub poll {
52 27     27 1 3910 my ($self, %args) = @_;
53            
54 27         104 $self->_file($args{file});
55              
56 27         41 my $gps_json_data;
57              
58 27 50       65 if ($self->_file){
59 27         53 my $fname = $self->_file;
60              
61 27 100       1317 open my $fh, '<', $fname or croak "can't open file '$fname': $!";
62              
63             {
64 26         64 local $/;
  26         109  
65 26         690 $gps_json_data = <$fh>;
66 26 50       412 close $fh or croak "can't close file '$fname': $!";
67             }
68             }
69             else {
70 0         0 $self->_socket->send("?POLL;\n");
71 0         0 local $/ = "\r\n";
72 0         0 while (my $line = $self->_socket->getline){
73 0         0 chomp $line;
74 0         0 my $data = decode_json $line;
75 0 0       0 if ($data->{class} eq 'POLL'){
76 0         0 $gps_json_data = $line;
77 0         0 last;
78             }
79             }
80             }
81              
82 26 50       84 die "no JSON data returned from the GPS" if ! defined $gps_json_data;
83              
84 26         123 my $gps_perl_data = decode_json $gps_json_data;
85              
86 26 50       317974 if (! defined $gps_perl_data->{tpv}[0]){
87 0         0 warn "\n\nWaiting for valid GPS signal...\n\n";
88 0         0 return;
89             }
90              
91 26         134 $self->_parse($gps_perl_data);
92              
93 26 100 66     114 return $gps_json_data if defined $args{return} && $args{return} eq 'json';
94 25         163 return $gps_perl_data;
95             }
96              
97             # tpv methods
98              
99             sub tpv {
100 78     78 1 16034 my ($self, $stat) = @_;
101              
102 78 100       169 if (defined $stat){
103 77 100       173 return '' if ! defined $self->{tpv}{$stat};
104 76         325 return $self->{tpv}{$stat};
105             }
106 1         3 return $self->{tpv};
107             }
108             sub lon {
109 2     2 1 445 return $_[0]->tpv('lon');
110             }
111             sub lat {
112 2     2 1 789 return $_[0]->tpv('lat');
113             }
114             sub alt {
115 2     2 1 454 return $_[0]->tpv('alt');
116             }
117             sub climb {
118 2     2 1 422 return $_[0]->tpv('climb');
119             }
120             sub speed {
121 2     2 1 491 return $_[0]->tpv('speed');
122             }
123             sub track {
124 2     2 1 431 return $_[0]->tpv('track');
125             }
126              
127             # sky methods
128              
129             sub sky {
130 1     1 1 7 return shift->{sky};
131             }
132             sub satellites {
133 68     68 1 103824 my ($self, $sat_num, $stat) = @_;
134              
135 68 100       148 if (defined $sat_num){
136 66 100       150 return undef if ! defined $self->{satellites}{$sat_num};
137             }
138              
139 67 100 66     219 if (defined $sat_num && defined $stat){
140 65 100       167 return undef if ! defined $self->{satellites}{$sat_num}{$stat};
141 52         108 return $self->{satellites}{$sat_num}{$stat};
142             }
143 2         9 return $self->{satellites};
144             }
145              
146             # device methods
147              
148             sub device {
149 1     1 1 7 return shift->{device};
150             }
151             sub time {
152 1     1 1 6 return shift->{time};
153             }
154              
155             # helper/convenience methods
156              
157             sub direction {
158 32 50   32 1 149 shift if @_ > 1;
159              
160 32         43 my ($deg) = @_;
161              
162 32         97 my @directions = qw(
163             N NNE NE ENE E ESE SE SSE S SSW SW WSW W WNW NW NNW N
164             );
165              
166 32         64 my $calc = (($deg % 360) / 22.5) + .5;
167              
168 32         144 return $directions[$calc];
169             }
170             sub feet {
171 3     3 1 9 return $_[0]->_is_metric(0);
172             }
173             sub metres {
174 4     4 1 13 return $_[0]->_is_metric(1);
175             }
176             sub signed {
177 28     28 1 5959 my $self = shift;
178              
179 28 100       85 if (! @_){
180             # caller just wants to set is_signed
181 1         4 return $self->_is_signed(1);
182             }
183              
184 27         60 my ($lat, $lon) = @_;
185              
186 27 100       360 return ($lat, $lon) if $lat !~ /[NESW]$/;
187 4 50       10 return ($lat, $lon) if $lon !~ /[NESW]$/;
188              
189 4         15 my %directions = (
190             W => '-',
191             E => '',
192             N => '',
193             S => '-',
194             );
195              
196 4         6 for ($lat, $lon){
197 8         20 my ($dir) = $_ =~ /([NESW])$/;
198 8         17 s/([NESW])$//;
199 8         16 $_ = $directions{$dir} . $_;
200             }
201              
202 4         15 return ($lat, $lon);
203             }
204             sub unsigned {
205 8     8 1 3501 my $self = shift;
206              
207 8 100       71 if (! @_){
208             # caller just wants to set unsigned
209 1         3 return $self->_is_signed(0);
210             }
211 7         19 my ($lat, $lon) = @_;
212              
213 7 50       63 return ($lat, $lon) if $lat =~ /[NESW]$/;
214 7 50       155 return ($lat, $lon) if $lon =~ /[NESW]$/;
215              
216 7 100       32 if ($lat =~ /^-/) {
217 2         83 $lat =~ s/-(.*)/${1}S/;
218             }
219             else {
220 5         18 $lat .= 'N';
221             }
222              
223 7 100       34 if ($lon =~ /^-/) {
224 5         52 $lon =~ s/-(.*)/${1}W/;
225             }
226             else {
227 2         5 $lon .= 'E';
228             }
229              
230 7         34 return ($lat, $lon);
231             }
232              
233             # private methods
234              
235             sub _convert {
236 26     26   46 my $self = shift;
237              
238 26         105 my @convertable_stats = qw(alt climb speed);
239              
240 26 100       84 if (! $self->_is_metric){
241 5         14 for (@convertable_stats) {
242 15         22 my $num = $self->{tpv}{$_};
243 15         22 $num = $num * 3.28084;
244 15         92 $self->{tpv}{$_} = substr($num, 0, index($num, '.') + 1 + 3);
245             }
246             }
247             }
248             sub _file {
249 141     141   223 my ($self, $file) = @_;
250 141 100       542 $self->{file} = $file if defined $file;
251 141         299 return $self->{file};
252             }
253             sub _host {
254 24     24   50 my ($self, $host) = @_;
255 24 50       48 $self->{host} = $host if defined $host;
256 24 100       52 $self->{host} = '127.0.0.1' if ! defined $self->{host};
257 24         51 return $self->{host};
258             }
259             sub _is_metric {
260             # whether we're in feet or metres mode
261 59     59   149 my ($self, $metric) = @_;
262 59 100       139 $self->{metric} = $metric if defined $metric;
263 59 100       164 $self->{metric} = 1 if ! defined $self->{metric};
264 59         153 return $self->{metric};
265             }
266             sub _is_signed {
267             # set whether we're in signed or unsigned mode
268 54     54   119 my ($self, $signed) = @_;
269 54 100       117 $self->{signed} = $signed if defined $signed;
270 54 100       131 $self->{signed} = 1 if ! defined $self->{signed};
271 54         150 return $self->{signed};
272             }
273             sub _port {
274 24     24   72 my ($self, $port) = @_;
275 24 50       48 $self->{port} = $port if defined $port;
276 24 100       62 $self->{port} = 2947 if ! defined $self->{port};
277 24         83 return $self->{port};
278             }
279             sub _parse {
280             # parse the GPS data and populate the object
281 26     26   65 my ($self, $data) = @_;
282              
283 26         113 $self->{tpv} = $data->{tpv}[0];
284 26         61 $self->{time} = $self->{tpv}{time};
285 26         94 $self->{device} = $self->{tpv}{device};
286 26         78 $self->{sky} = $data->{sky}[0];
287              
288             # perform conversions on metric/standard if necessary
289              
290 26         102 $self->_convert;
291              
292             # perform conversions on the lat/long if necessary
293              
294 26         204 my ($lat, $lon) = ($self->{tpv}{lat}, $self->{tpv}{lon});
295              
296 26 100       102 ($self->{tpv}{lat}, $self->{tpv}{lon}) = $self->_is_signed
297             ? $self->signed($lat, $lon)
298             : $self->unsigned($lat, $lon);
299              
300 26         55 my %sats;
301              
302 26         44 for my $sat (@{ $self->{sky}{satellites} }){
  26         103  
303 338         498 my $prn = $sat->{PRN};
304 338         371 delete $sat->{PRN};
305 338 100       1112 $sat->{used} = $sat->{used} ? 1 : 0;
306 338         2359 $sats{$prn} = $sat;
307             }
308 26         130 $self->{satellites} = \%sats;
309             }
310             sub _is_socket {
311             # check if we're in socket mode
312 26     26   61 my ($self, $status) = @_;
313 26 50       82 $self->{is_socket} = $status if defined $status;
314 26         1242 return $self->{is_socket};
315             }
316             sub _socket {
317 8     8   19 my ($self) = @_;
318              
319 8 50       19 return undef if $self->_file;
320              
321 8 50       26 if (! defined $self->{socket}){
322 8         18 $self->{"socket"}=IO::Socket::INET->new(
323             PeerAddr => $self->_host,
324             PeerPort => $self->_port,
325             );
326             }
327              
328 8         4812 my ($h, $p) = ($self->_host, $self->_port);
329              
330 8 50       1311 croak "can't connect to gpsd://$h:$p" if ! defined $self->{socket};
331            
332 0         0 return $self->{'socket'};
333             }
334             sub DESTROY {
335 26     26   42049 my $self = shift;
336 26 50       93 $self->off if $self->_is_socket;
337             }
338       0     sub _vim {} # fold placeholder
339              
340             1;
341              
342             =head1 NAME
343              
344             GPSD::Parse - Parse, extract use the JSON output from GPS units
345              
346             =for html
347            
348             Coverage Status
349              
350             =head1 SYNOPSIS
351              
352             use GPSD::Parse;
353             my $gps = GPSD::Parse->new;
354              
355             # poll for data
356              
357             $gps->poll;
358              
359             # get all TPV data in an href
360              
361             my $tpv_href = $gps->tpv;
362              
363             # get individual TPV stats
364              
365             print $gps->tpv('lat');
366             print $gps->tpv('lon');
367              
368             # ...or
369              
370             print $gps->lat;
371             print $gps->lon;
372              
373             # timestamp of the most recent poll
374              
375             print $gps->time;
376              
377             # get all satellites in an href of hrefs
378              
379             my $sats = $gps->satellites;
380              
381             # get an individual piece of info from a single sattelite
382              
383             print $gps->satellites(16, 'ss');
384              
385             # check which serial device the GPS is connected to
386              
387             print $gps->device;
388              
389             # toggle between metres and feet (metres by default)
390              
391             $gps->feet;
392             $gps->metres;
393              
394             =head1 DESCRIPTION
395              
396             Simple, lightweight (core only) distribution that polls C for data
397             received from a UART (serial/USB) connected GPS receiver over a TCP connection.
398              
399             The data is fetched in JSON, and returned as Perl data.
400              
401             =head1 NOTES
402              
403             =head2 Requirements
404              
405             A version of L that returns results in
406             JSON format is required to have been previously installed. It should be started
407             at system startup, with the following flags with system-specific serial port.
408             See the above link for information on changing the listen IP and port.
409              
410             sudo gpsd /dev/ttyS0 -n -F /var/log/gpsd.sock
411              
412             NOTE: The C<-n> flag *must* be present when running C. If not, this
413             software will stay in an endless loop of "Waiting for a valid GPS signal", even
414             if the GPS device has been triangulated. See your Operating Systems startup
415             system to add this flag to the startup if necessary (on Ubuntu, add "-n" to the
416             C section in C).
417              
418             =head2 Available Data
419              
420             Each of the methods that return data have a table in their respective
421             documentation within the L section. Specifically, look at the
422             L, L and the more broad L method
423             sections to understand what available data attributes you can extract.
424              
425             =head2 Conversions
426              
427             All output where applicable defaults to metric (metres). See the C
428             parameter in the L method to change this to use imperial/standard
429             measurements. You can also toggle this at runtime with the L and
430             L methods.
431              
432             For latitude and longitude, we default to using the signed notation. You can
433             disable this with the C parameter in L, along with the
434             L and L methods to toggle this conversion at runtime.
435              
436             =head1 METHODS
437              
438             =head2 new(%args)
439              
440             Instantiates and returns a new L object instance.
441              
442             Parameters:
443              
444             host => 127.0.0.1
445              
446             Optional, String: An IP address or fully qualified domain name of the C
447             server. Defaults to the localhost (C<127.0.0.1>) if not supplied.
448              
449             port => 2947
450              
451             Optional, Integer: The TCP port number that the C daemon is running on.
452             Defaults to C<2947> if not sent in.
453              
454             metric => Bool
455              
456             Optional, Integer: By default, we return measurements in metric (metres). Send
457             in a false value (C<0>) to use imperial/standard measurement conversions
458             (ie. feet). Note that if returning the raw *JSON* data from the L
459             method, the conversions will not be done. The default raw Perl return will have
460             been converted however.
461              
462             signed => Bool
463              
464             Optional, Integer: By default, we use the signed notation for latitude and
465             longitude. Send in a false value (C<0>) to disable this. Here's an example:
466              
467             enabled (default) disabled
468             ----------------- --------
469              
470             lat: 51.12345678 51.12345678N
471             lon: -114.123456 114.123456W
472              
473             We add the letter notation at the end of the result if C is disabled.
474              
475             NOTE: You can toggle this at runtime by calling the L and
476             L methods. The data returned at the next poll will reflect any
477             change.
478              
479             file => 'filename.ext'
480              
481             Optional, String: For testing purposes. Instead of reading from a socket, send
482             in a filename that contains legitimate JSON data saved from a previous C
483             output and we'll operate on that. Useful also for re-running previous output.
484              
485             =head2 poll(%args)
486              
487             Does a poll of C for data, and configures the object with that data.
488              
489             Parameters:
490              
491             All parameters are sent in as a hash.
492              
493             file => $filename
494              
495             Optional, String: Used for testing, you can send in the name of a JSON file
496             that contains C JSON data and we'll work with that instead of polling
497             the GPS device directly. Note that you *must* instantiate the object with the
498             C parameter in new for this to have any effect and to bypass the socket
499             creation.
500              
501             return => 'json'
502              
503             Optional, String: By default, after configuring the object, we will return the
504             polled raw data as a Perl hash reference. Send this param in with the value of
505             C<'json'> and we'll return the data exactly as we received it from C.
506              
507             Returns:
508              
509             The raw poll data as either a Perl hash reference structure or as the
510             original JSON string.
511              
512             =head2 lon
513              
514             Returns the longitude. Alias for C<< $gps->tpv('lon') >>.
515              
516             =head2 lat
517              
518             Returns the latitude. Alias for C<< $gps->tpv('lat') >>.
519              
520             =head2 alt
521              
522             Returns the altitude. Alias for C<< $gps->tpv('alt') >>.
523              
524             =head2 climb
525              
526             Returns the rate of ascent/decent. Alias for C<< $gps->tpv('climb') >>.
527              
528             =head2 speed
529              
530             Returns the rate of movement. Alias for C<< $gps->tpv('speed') >>.
531              
532             =head2 track
533              
534             Returns the direction of movement, in degrees. Alias for
535             C<< $gps->tpv('track') >>.
536              
537             =head2 tpv($stat)
538              
539             C stands for "Time Position Velocity". This is the data that represents
540             your location and other vital statistics.
541              
542             By default, we return a hash reference. The format of the hash is depicted
543             below. Note also that the most frequently used stats also have their own
544             methods that can be called on the object as opposed to having to reach into
545             a hash reference.
546              
547             Parameters:
548              
549             $stat
550              
551             Optional, String. You can extract individual statistics of the TPV data by
552             sending in the name of the stat you wish to fetch. This will then return the
553             string value if available. Returns an empty string if the statistic doesn't
554             exist.
555              
556             Available statistic/info name, example value, description. This is the default
557             raw result:
558              
559             time => '2017-05-16T22:29:29.000Z' # date/time in UTC
560             lon => '-114.000000000' # longitude
561             lat => '51.000000' # latitude
562             alt => '1084.9' # altitude (metres)
563             climb => '0' # rate of ascent/decent (metres/sec)
564             speed => '0' # rate of movement (metres/sec)
565             track => '279.85' # heading (degrees from true north)
566             device => '/dev/ttyS0' # GPS serial interface
567             mode => 3 # NMEA mode
568             epx => '3.636' # longitude error estimate (metres)
569             epy => '4.676' # latitude error estimate (metres)
570             epc => '8.16' # ascent/decent error estimate (meters)
571             ept => '0.005' # timestamp error (sec)
572             epv => '4.082' # altitude error estimate (meters)
573             eps => '9.35' # speed error estimate (metres/sec)
574             class => 'TPV' # data type (fixed as TPV)
575             tag => 'ZDA' # identifier
576              
577             =head2 satellites($num, $stat)
578              
579             This method returns a hash reference of hash references, where the key is the
580             satellite number, and the value is a hashref that contains the various
581             information related to the specific numbered satellite.
582              
583             Note that the data returned by this function has been manipuated and is not
584             exactly equivalent of that returned by C. To get the raw data, see
585             L.
586              
587             Parameters:
588              
589             $num
590              
591             Optional, Integer: Send in the satellite number and we'll return the relevant
592             information in a hash reference for the specific satellite requested, as
593             opposed to returning data for all the satellites. Returns C if a
594             satellite by that number doesn't exist.
595              
596             $stat
597              
598             Optional, String: Like C, you can request an individual piece of
599             information for a satellite. This parameter is only valid if you've sent in
600             the C<$num> param, and the specified satellite exists.
601              
602             Available statistic/information items available for each satellite, including
603             the name, an example value and a description:
604              
605             NOTE: The PRN attribute will not appear unless you're using raw data. The PRN
606             can be found as the satellite hash reference key after we've processed the
607             data.
608              
609             PRN => 16 # PRN ID of the satellite
610              
611             # 1-63 are GNSS satellites
612             # 64-96 are GLONASS satellites
613             # 100-164 are SBAS satellites
614              
615             ss => 20 # signal strength (dB)
616             az => 161 # azimuth (degrees from true north)
617             used => 1 # currently being used in calculations
618             el => 88 # elevation in degrees
619              
620             =head2 sky
621              
622             Returns a hash reference containing all of the data that was pulled from the
623             C information returned by C. This information contains satellite
624             info and other related statistics.
625              
626             Available information, with the attribute, example value and description:
627              
628             satellites => [] # array of satellite hashrefs
629             xdop => '0.97' # longitudinal dilution of precision
630             ydop => '1.25' # latitudinal dilution of precision
631             pdop => '1.16' # spherical dilution of precision
632             tdop => '2.2' # time dilution of precision
633             vdop => '0.71' # altitude dilution of precision
634             gdop => '3.87' # hyperspherical dilution of precision
635             hdop => '0.92' # horizontal dilution of precision
636             class => 'SKY' # object class, hardcoded to SKY
637             tag => 'ZDA' # object ID
638             device => '/dev/ttyS0' # serial port connected to the GPS
639              
640             =head2 direction($degree)
641              
642             Converts a degree from true north into a direction (eg: ESE, SW etc).
643              
644             Parameters:
645              
646             $degree
647              
648             Mandatory, Ineger/Decimal: A decimal ranging from 0-360. Returns the direction
649             representing the degree from true north. A common example would be:
650              
651             my $heading = $gps->direction($gps->track);
652              
653             Degree/direction map:
654              
655             N 348.75 - 11.25
656             NNE 11.25 - 33.75
657             NE 33.75 - 56.25
658             ENE 56.25 - 78.75
659              
660             E 78.75 - 101.25
661             ESE 101.25 - 123.75
662             SE 123.75 - 146.25
663             SSE 146.25 - 168.75
664              
665             S 168.75 - 191.25
666             SSW 191.25 - 213.75
667             SW 213.75 - 236.25
668             WSW 236.25 - 258.75
669              
670             W 258.75 - 281.25
671             WNW 281.25 - 303.75
672             NW 303.75 - 326.25
673             NNW 326.25 - 348.75
674              
675             =head2 device
676              
677             Returns a string containing the actual device the GPS is connected to
678             (eg: C).
679              
680             =head2 time
681              
682             Returns a string of the date and time of the most recent poll, in UTC.
683              
684             =head2 signed
685              
686             This method works on the latitude and longitude output view. By default, we use
687             signed notation, eg:
688              
689             -114.1111111111 # lon
690             51.111111111111 # lat
691              
692             If you've switched to L, calling this method will toggle it back,
693             and the results will be visible after the next L
694              
695             You can optionally use this method to convert values in a manual way. Simply
696             send in the latitude and longitude in that order as parameters, and we'll return
697             a list containing them both after modification, if it was necessary.
698              
699             =head2 unsigned
700              
701             This method works on the latitude and longitude output view. By default, we use
702             signed notation, eg:
703              
704             -114.1111111111 # lon
705             51.111111111111 # lat
706              
707             Calling this method will convert those to:
708              
709             114.1111111111W # lon
710             51.11111111111N # lat
711              
712             If you've switched to L calling this method will toggle it back,
713             and the results will be visible after the next L.
714              
715             You can optionally use this method to convert values in a manual way. Simply
716             send in the latitude and longitude in that order as parameters, and we'll return
717             a list containing them both after modification, if it was necessary.
718              
719             =head2 feet
720              
721             By default, we use metres as the measurement for any attribute that is measured
722             in distance. Call this method to have all attributes converted into feet
723             commencing at the next call to L. Use L to revert back.
724              
725             =head2 metres
726              
727             We measure in metres by default. If you've switched to using feet as the
728             measurement unit, a call to this method will revert back to the default.
729              
730             =head2 on
731              
732             Puts C in listening mode, ready to poll data from.
733              
734             We call this method internally when the object is instantiated with L if
735             we're not in file mode. Likewise, when the object is destroyed (end of program
736             run), we call the subsequent L method.
737              
738             If you have long periods of a program run where you don't need the GPS, you can
739             manually run the L and L methods to disable and re-enable the GPS.
740              
741             =head2 off
742              
743             Turns off C listening mode.
744              
745             Not necessary to call, but it will help preserve battery life if running on a
746             portable device for long program runs where the GPS is used infrequently. Use in
747             conjunction with L. We call L automatically when the object goes
748             out of scope (program end for example).
749              
750             =head1 EXAMPLES
751              
752             =head2 Basic Features and Options
753              
754             Here's a simple example using some of the basic features and options. Please
755             read through the documentation of the methods (particularly L and
756             L to get a good grasp on what can be fetched).
757              
758             use warnings;
759             use strict;
760             use feature 'say';
761              
762             use GPSD::Parse;
763              
764             my $gps = GPSD::Parse->new(signed => 0);
765              
766             $gps->poll;
767              
768             my $lat = $gps->lat;
769             my $lon = $gps->lon;
770              
771             my $heading = $gps->track; # degrees
772             my $direction = $gps->direction($heading); # ENE etc
773              
774             my $altitude = $gps->alt;
775              
776             my $speed = $gps->speed;
777              
778             say "latitude: $lat";
779             say "longitude: $lon\n";
780              
781             say "heading: $heading degrees";
782             say "direction: $direction\n";
783              
784             say "altitude: $altitude metres\n";
785              
786             say "speed: $speed metres/sec";
787              
788             Output:
789              
790             latitude: 51.1111111N
791             longitude: 114.11111111W
792              
793             heading: 31.23 degrees
794             direction: NNE
795              
796             altitude: 1080.9 metres
797              
798             speed: 0.333 metres/sec
799              
800             =head2 Displaying Satellite Information
801              
802             Here's a rough example that displays the status of tracked satellites, along
803             with the information on the ones we're currently using.
804              
805             use warnings;
806             use strict;
807              
808             use GPSD::Parse;
809              
810             my $gps = GPSD::Parse->new;
811              
812             while (1){
813             $gps->poll;
814             my $sats = $gps->satellites;
815              
816             for my $sat (keys %$sats){
817             if (! $gps->satellites($sat, 'used')){
818             print "$sat: unused\n";
819             }
820             else {
821             print "$sat: used\n";
822             for (keys %{ $sats->{$sat} }){
823             print "\t$_: $sats->{$sat}{$_}\n";
824             }
825             }
826             }
827             sleep 3;
828             }
829              
830             Output:
831              
832             7: used
833             ss: 20
834             used: 1
835             az: 244
836             el: 20
837             29: unused
838             31: used
839             el: 12
840             az: 64
841             used: 1
842             ss: 17
843             6: unused
844             138: unused
845             16: used
846             ss: 17
847             el: 53
848             used: 1
849             az: 119
850             26: used
851             az: 71
852             used: 1
853             el: 46
854             ss: 27
855             22: used
856             ss: 28
857             el: 17
858             used: 1
859             az: 175
860             3: used
861             ss: 24
862             az: 192
863             used: 1
864             el: 40
865             9: unused
866             23: unused
867             2: unused
868              
869             =head1 TESTING
870              
871             Please note that we init and disable the GPS device on construction and
872             deconstruction of the object respectively. It takes a few seconds for the GPS
873             unit to initialize itself and then lock on the satellites before we can get
874             readings. For this reason, please understand that one test sweep may pass while
875             the next fails.
876              
877             I am considering adding specific checks, but considering that it's a timing
878             thing (seconds, not microseconds that everyone is in a hurry for nowadays) I am
879             going to wait until I get a chance to take the kit into the field before I do
880             anything drastic.
881              
882             For now. I'll leave it as is; expect failure if you ram on things too quickly.
883              
884             =head1 SEE ALSO
885              
886             A very similar distribution is L. However, it has a long line of
887             prerequisite distributions that didn't always install easily on my primary
888             target platform, the Raspberry Pi.
889              
890             This distribution isn't meant to replace that one, it's just a much simpler and
891             more lightweight piece of software that pretty much does the same thing.
892              
893             =head1 AUTHOR
894              
895             Steve Bertrand, C<< >>
896              
897             =head1 LICENSE AND COPYRIGHT
898              
899             Copyright 2019 Steve Bertrand.
900              
901             This program is free software; you can redistribute it and/or modify it
902             under the terms of either: the GNU General Public License as published
903             by the Free Software Foundation; or the Artistic License.
904              
905             See L for more information.