File Coverage

blib/lib/Parse/SpectrumDirect/RadioFrequency.pm
Criterion Covered Total %
statement 78 81 96.3
branch 18 22 81.8
condition n/a
subroutine 11 11 100.0
pod 4 4 100.0
total 111 118 94.0


line stmt bran cond sub pod time code
1             package Parse::SpectrumDirect::RadioFrequency;
2 2     2   26227 use warnings;
  2         6  
  2         76  
3 2     2   12 use strict;
  2         3  
  2         69  
4 2     2   47 use 5.008;
  2         12  
  2         2488  
5              
6             =head1 NAME
7              
8             Parse::SpectrumDirect::RadioFrequency - Parse Industry Canada "Spectrum Direct" radio frequency search output
9              
10             =head1 VERSION
11              
12             Version 0.100
13              
14             =cut
15              
16             our $VERSION = '0.100';
17              
18             =head1 DESCRIPTION
19              
20             This module provides a parser for the "Radio Frequency Search" text-format
21             output from Industry Canada's Spectrum Direct service. This service provides
22             information on the location of RF spectrum licensing, transmitter locations,
23             etc.
24              
25             The service is available at http://www.ic.gc.ca/eic/site/sd-sd.nsf/eng/home
26              
27             The text export is a series of fixed-width fields, with field locations and
28             descriptions present in a legend at the end of the data file.
29              
30             =head1 SYNOPSIS
31              
32             my $parser = Parse::SpectrumDirect::RadioFrequency->new();
33              
34             $parser->parse( $prefetched_output );
35              
36             my $legend_hash = $parser->get_legend();
37              
38             my $stations = $parser->get_stations();
39              
40             =head1 METHODS
41              
42             =over 4
43              
44             =item new ( )
45              
46             Creates a new parser.
47              
48             =cut
49              
50             sub new
51             {
52 1     1 1 842 my ($class) = @_;
53 1         4 return bless({},$class);
54             }
55              
56             =item parse ( $raw )
57              
58             Parses the raw data provided. Returns a true value if successful, a false if
59             not.
60              
61             Parsed data can be obtained with get_legend() and get_stations() (see below).
62              
63             =cut
64              
65             sub parse
66             {
67 4     4 1 496 my ($self, $raw) = @_;
68 4         8 delete $self->{legend};
69 4         7 delete $self->{stations};
70              
71 4 100       21 return undef unless $raw;
72              
73 2 100       8 if( ! $self->_extract_legend( $raw ) ) {
74 1         4 delete $self->{legend};
75 1         5 return undef;
76             }
77 1 50       5 if( ! $self->_extract_stations( $raw ) ) {
78 0         0 delete $self->{legend};
79 0         0 delete $self->{stations};
80 0         0 return undef;
81             }
82              
83 1         9 return 1;
84             }
85              
86             =item get_legend ()
87              
88             Returns the description of fields as parsed from the input data.
89              
90             Return value is an array reference containing one hash reference per field.
91              
92             Each hashref contains:
93              
94             =over 4
95              
96             =item name
97              
98             As in source legend, stripped of trailing spaces
99              
100             =item units
101              
102             Units for data value, if determinable from legend.
103              
104             =item key
105              
106             Key used in station hashes. Generated from name stripped of unit information, and whitespaces converted to _.
107              
108             =item start
109              
110             Column index to start extracting data value
111              
112             =item len
113              
114             Column width, used for data extraction.
115              
116             =back
117              
118             =cut
119              
120             sub get_legend
121             {
122 1     1 1 3 my ($self) = @_;
123 1         5 return $self->{legend};
124             }
125              
126             =item get_stations ()
127              
128             Returns station information as parsed from the input data.
129              
130             =back
131              
132             =cut
133              
134             sub get_stations
135             {
136 3     3 1 2246 my ($self) = @_;
137 3         18 return $self->{stations};
138             }
139              
140             # Extract legend as an arrayref of hashrefs.
141             sub _extract_legend
142             {
143 2     2   5 my ($self, $raw) = @_;
144              
145 2         201 my ($raw_legend) = $raw =~ m/Field Position Legend(.*)/sm;
146 2 100       12 return undef unless $raw_legend;
147              
148 1         5 $self->{legend} = [];
149 1         33 foreach my $line (split(/\n/, $raw_legend)) {
150              
151             # Lines are in the format of:
152             # name start - end
153             # with the columns starting at 1.
154              
155 73         530 my ($name, $start, $end) = $line =~ m/(.*?)\s+(\d+) - (\d+)/;
156 73 100       157 next unless $name;
157 69         168 $name =~ s/\s+$//;
158              
159             # Pull off units
160 69         70 my $units = undef;
161 69 100       185 if( $name =~ m/\((.*?)\)$/ ) {
162 27         49 $units = $1;
163             }
164              
165 69         77 my $key = $name;
166 69         137 $key =~ s/\(.*?\)//g;
167 69         206 $key =~ s/\s+$//;
168 69         270 $key =~ s/\s+/_/g;
169              
170 69         329 my $col = {
171             key => $key,
172             units => $units,
173             name => $name,
174             start => $start - 1,
175             len => $end - $start + 1,
176             };
177 69         84 push @{$self->{legend}},$col;
  69         210  
178              
179             }
180              
181 1         12 return $self->{legend};
182             }
183              
184             # Return station data as an arrayref of hashrefs, one per row.
185             sub _extract_stations
186             {
187 1     1   2 my ($self, $raw) = @_;
188              
189 1         590 my ($data) = $raw =~ m/\[DATA\](.*)\[\/DATA\]/sm;
190 1 50       4 return undef unless $data;
191              
192 1         3 my $regex = join('\s', map { "(.{$_->{len},$_->{len}})" } @{$self->{legend}} );
  69         148  
  1         4  
193 1         6 my @key_ary = map { $_->{key} } @{$self->{legend}};
  69         100  
  1         4  
194              
195 1         7 $self->{stations} = [];
196 1         1067 foreach my $line (split(/\n/,$data)) {
197 415         16576 my (@tmprow) = $line =~ /$regex/o;
198              
199 415         1535 my %row;
200 415         1244 @row{@key_ary} = map { s/^\s+|\s+$//g; $_ } @tmprow;
  28566         84512  
  28566         64892  
201 415         3190 push @{$self->{stations}}, \%row;
  415         3564  
202             }
203 1         100 $self->_fixup_station_data();
204              
205 1         11 return $self->{stations};
206             }
207              
208             # Fix some common stupidity with station data
209             sub _fixup_station_data
210             {
211 1     1   3 my ($self) = @_;
212              
213              
214             # Convert to decimal degrees from ddmmss. Also, force
215             # longitude to west (negative), since this is Canada we're
216             # dealing with.
217 1         3 foreach my $s (@{$self->{stations}}) {
  1         3  
218 415 50       1253 $s->{Latitude} = _dd_from_dms( $s->{Latitude} ) if exists $s->{Latitude};
219 415 50       1117 $s->{Longitude} = 0 - _dd_from_dms( $s->{Longitude} ) if exists $s->{Longitude};
220             }
221              
222             # Change units in legend, too
223 1         3 foreach my $l (@{$self->{legend}}) {
  1         4  
224 69 100       165 if( $l->{key} =~ /^(?:Latitude|Longitude)$/ ) {
225 2         5 $l->{units} = 'decimal degrees';
226             }
227             }
228             }
229              
230             sub _dd_from_dms
231             {
232 830     830   933 my ($dms) = @_;
233              
234 830 100       1283 return 0.0 unless $dms;
235              
236 816         819 my $ss = substr( $dms, -2, 2, '');
237 816         719 my $mm = substr( $dms, -2, 2, '');
238 816         684 my $dd = $dms;
239              
240 816         10391 return sprintf('%.6f', $dd + ($mm * 60 + $ss)/3600);
241             }
242              
243              
244             1;
245             __END__