File Coverage

blib/lib/GPSD/Parse.pm
Criterion Covered Total %
statement 149 165 90.3
branch 64 82 78.0
condition 4 6 66.6
subroutine 33 36 91.6
pod 20 20 100.0
total 270 309 87.3


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