File Coverage

lib/Weather/GHCN/Station.pm
Criterion Covered Total %
statement 80 80 100.0
branch 19 26 100.0
condition 1 3 100.0
subroutine 25 25 100.0
pod 21 21 100.0
total 146 155 100.0


line stmt bran cond sub pod time code
1             # Weather::GHCN::Station.pm - class for Station metadata
2            
3             ## no critic (Documentation::RequirePodAtEnd)
4            
5             =head1 NAME
6            
7             Weather::GHCN::Station - class for Station metadata
8              
9             =head1 VERSION
10              
11             version v0.0.010
12            
13             =head1 SYNOPSIS
14            
15             use Weather::GHCN::Station;
16            
17             my $stn_obj = Weather::GHCN::Station->new (
18             id => 'CA006105976',
19             country => 'CA',
20             state => 'ON',
21             active => '1899-2022',
22             lat => '45.3833',
23             long => '-75.7167',
24             elev => 79,
25             name => 'OTTAWA CDA',
26             gsn => '',
27             );
28            
29             =head1 DESCRIPTION
30            
31             The B class is used to encapsulate the metadata for a
32             station as obtained from the NOAA Global Historical Climatology
33             Network data repository. Data is sourced from the station list and
34             the station inventory.
35            
36             The module is primarily for use by Weather::GHCN::Fetch and Weather::GHCN::StationTable.
37            
38             =cut
39            
40             # these are needed because perlcritic fails to detect that Object::Pad handles these things
41             ## no critic [ValuesAndExpressions::ProhibitVersionStrings]
42             ## no critic [TestingAndDebugging::RequireUseWarnings]
43             ## no critic [References::ProhibitDoubleSigils]
44            
45 4     4   3667 use v5.18; # minimum for Object::Pad
  4         21  
46 4     4   585 use Object::Pad 0.66 qw( :experimental(init_expr) );
  4         12150  
  4         35  
47            
48             package Weather::GHCN::Station;
49             class Weather::GHCN::Station;
50            
51             our $VERSION = 'v0.0.010';
52            
53 4     4   3766 use Weather::GHCN::Common qw( rng_new iso_date_time );
  4         8  
  4         253  
54 4     4   25 use Const::Fast;
  4         8  
  4         26  
55            
56             const my $EMPTY => q(); # empty string
57             const my $TAB => qq(\t); # tab character
58             const my $TRUE => 1; # perl's usual TRUE
59             const my $FALSE => not $TRUE; # a dual-var consisting of '' and 0
60            
61             const my $NOAA_DATA => 'https://www1.ncdc.noaa.gov/pub/data/ghcn/daily/all/';
62            
63             # below this value is an error causing stn rejection
64             const my $ERR_THRESHOLD => 50;
65            
66             # attributes
67 798     798 1 3657 field $id :reader :param; # [0]
  798         2465  
68 1     1 1 24 field $country :reader :param; # [1]
  1         8  
69 1     1 1 4 field $state :reader :param; # [2]
  1         4  
70 7878     7878 1 76845 field $active :mutator :param; # [3]
  7878         29650  
71 5     5 1 12 field $lat :reader :param; # [4]
  5         68  
72 5     5 1 16 field $long :reader :param; # [5]
  5         24  
73 5     5 1 21 field $elev :reader :param; # [6]
  5         53  
74 753     753 1 1253 field $name :reader :param; # [7]
  753         1968  
75 1     1 1 4 field $gsn :reader :param; # [8]
  1         7  
76 3033     3033 1 7351 field $elems_href :reader { {} }; # [9]
  3033         19565  
77 2565     2565 1 4488 field $idx :mutator; # [10]
  2565         6182  
78 7     7 1 1168 field $note_nrs :mutator { rng_new() }; # [11]
  7         42  
79            
80             =head1 FIELD ACCESSORS
81            
82             =over 4
83            
84             =item id
85            
86             Returns the Station id.
87            
88             =item country
89            
90             Returns the two-character GEC country code for the station.
91            
92             =item state
93            
94             Returns the two-character state code for US stations, or province code for
95             Canadian stations.
96            
97             =item active
98            
99             Returns the active range of the station.
100            
101             =item lat
102            
103             Returns the station latitude.
104            
105             =item long
106            
107             Returns the station longitude.
108            
109             =item elev
110            
111             Returns the station elevation.
112            
113             =item name
114            
115             Returns the station name.
116            
117             =item gsn
118            
119             Returns the boolean indicating whether this is a GSN station.
120            
121             =item elems_href
122            
123             Returns a hash reference for the measurements available from the
124             station daily data records.
125            
126             =item idx
127            
128             Returns an index number which is shared by stations that have the
129             same physical location; i.e. latitude and longitude. When station
130             instrumentation is changed or there are other significant changes,
131             a new station id may be assigned. This index enables you to group
132             together stations that represent the same spot but which may have
133             different active time periods.
134            
135             =item note_nrs
136            
137             Returns a number range string that represents notes which are errors
138             or warnings about the station.
139            
140             =back
141            
142             =cut
143             ######################################################################
144             # Class (:common) methods
145             ######################################################################
146            
147             =head1 CLASS METHODS
148            
149             =head2 new ()
150            
151             Create a new Station object.
152            
153            
154             =head2 Headings ()
155            
156             Column headings may be needed prior to creating any Station instances,
157             for example to print them before any data is loaded. This Headings
158             class method is provided to handle that situation. There is a
159             corresponding instance method.
160            
161             If called in list context, a list is return; in scalar context, a
162             list reference is returned.
163            
164             =cut
165            
166             method Headings :common {
167             my @h = qw(StationId Country State Active Lat Long Elev Location Notes StnIdx Grid );
168             return wantarray ? @h : \@h;
169             }
170            
171             ######################################################################
172             # Instance methods
173             ######################################################################
174            
175             =head1 INSTANCE METHODS
176            
177             =head2 headings
178            
179             See Headings in Class Methods.
180            
181             =cut
182            
183 2     2 1 1320 method headings {
184 2         7 my @h = Headings;
185 2 100       11 return wantarray ? @h : \@h;
186             }
187            
188             =head2 add_note ($note_id, $msg=undef, $verbose=$FALSE)
189            
190             This method allows you to add numbered notes to each station object.
191             It is up to the caller to assign meaning to the numbers. They are
192             stored in a range list (i.e. a Set::IntSpan::Fast object) so that
193             they can be printed compactly even if there a gaps in the number ranges.
194             For example, notes 1, 5 and 20 through 25 will print as 1,5,20-25.
195            
196             The message argument is optional, but if you provide a message then
197             it will be display on STDERR if it is considered an error message.
198             The threshold is 50 (as established by the hard coded constant
199             $ERR_THRESHOLD). Anything below that is considered an error. Otherwise
200             it's considered a warning.
201            
202             When the $VERBOSE argument is true, it causes warnings to also print
203             to STDERR.
204            
205             =cut
206            
207 3013     3013 1 38317 method add_note ($note_id, $msg=undef, $verbose=$FALSE) {
  3013         4362  
  3013         4779  
  3013         4660  
  3013         4840  
  3013         3801  
208 3013         9225 $note_nrs->add_from_string($note_id);
209            
210 3013 100       325829 return unless $msg;
211            
212 12 100       63 if ($note_id < $ERR_THRESHOLD) {
213 2         8 say {*STDERR} '*E* ', $msg;
  2         80  
214             } else {
215 10 100       39 say {*STDERR} '*W* ', $msg if $verbose;
  1         6  
216             }
217            
218 12         58 return;
219             }
220            
221             =head2 row
222            
223             Returns the station object as a list of field values. The fields
224             are:
225            
226             $id, $country, $state, $active, $lat, $long, $elev, $name,
227             $note_text, $idx, $grid>
228            
229             The second last value, $idx is a unique serial index number provided
230             by GHCH::StationTable during load_stations(). Unless set by a consumer
231             of this module, its value will be B.
232            
233             The last value, $grid, is a string derived by truncating $lat and
234             $long to 1 decimal place, converting them to N/S W/E format, and
235             concatenting them. This results in a box that defines an area within
236             which there may be other nearby stations of interest.
237            
238             If called in list context, a list is return; in scalar context, a
239             list reference is returned.
240            
241             =cut
242            
243 49     49 1 1146 method row {
244            
245 49 100       146 my $note_text = $note_nrs->cardinality ? '[' . $note_nrs->as_string . ']' : $EMPTY;
246            
247 49         2366 my @row = (
248             $id,
249             $country,
250             $state,
251             $active,
252             $lat,
253             $long,
254             $elev,
255             $name,
256             $note_text,
257             $idx,
258             $self->grid,
259             );
260            
261 49 100       224 return wantarray ? @row : \@row;
262             }
263            
264             =head2 coordinates
265            
266             Returns the latitude and longitude coordinates of the station in
267             decimal format as a space-separated string.
268            
269             =cut
270            
271 3669     3669 1 9717 method coordinates {
272             # uncoverable branch true
273             # uncoverable condition left
274             # uncoverable condition right
275 3669 50 33     12346 return $EMPTY unless $lat and $long;
276 3669         33983 return sprintf '%f %f', $lat, $long;
277             }
278            
279             =head2 description
280            
281             Return lines of text which describe the station in attribute: value
282             format.
283            
284             =cut
285            
286 5     5 1 604 method description {
287             # uncoverable branch true
288 5 50       18 my $is_gsn = $gsn ? 'yes' : 'no';
289 5         37 my $elems = join q( ), sort keys $elems_href->%*;
290            
291             # note: those the values are lined up here, they won't be when
292             # imported into Google Earth
293 5         90 my $text = <<~"_EOT_";
294             Id: $id
295             Name: $name
296             Country: $country
297             State: $state
298             Active: $active
299             Coord: $lat $long
300             Elev: $elev
301             Is GSN? $is_gsn
302             Metrics: $elems
303             _EOT_
304            
305 5         19 return $text;
306             }
307            
308             =head2 error_count
309            
310             Returns a count of the number of errors that were flagged for this
311             station. Errors generally make the station unsuitable for use.
312             Warnings are not included.
313            
314             =cut
315            
316 3676     3676 1 7887 method error_count {
317 3676         7367 my $err_count = grep { $_ < $ERR_THRESHOLD } $note_nrs->as_array;
  6019         114228  
318            
319 3676         14567 return $err_count;
320             }
321            
322             =head2 grid
323            
324             Returns the latitude and longitude to a single decimal place and using
325             cardinal (N/S E/W) notation. This value can be used for grouping
326             together stations that are within a 1/10th degree grid and which
327             may be assumed to experience similar weather conditions.
328            
329             =cut
330            
331 790     790 1 1491 method grid {
332 790         1603 my ($x, $y) = ($lat, $long);
333             # uncoverable branch true
334 790 50       1761 my $xh = $x < 0 ? 'S' : 'N';
335             # uncoverable branch false
336 790 50       1541 my $yh = $y < 0 ? 'W' : 'E';
337            
338             ## no critic [ProhibitMagicNumbers]
339             # uncoverable branch true
340 790 50       1484 $x *= $x < 0 ? -1 : 1;
341             # uncoverable branch false
342 790 50       1284 $y *= $y < 0 ? -1 : 1;
343            
344 790         4540 my $grid = sprintf '%4.1f%s %4.1f%s', $x, $xh, $y, $yh;
345            
346 790         2275 return $grid;
347             }
348            
349             =head2 selected
350            
351             Returns a boolean indicating whether the station was selected for
352             data loading. Selected stations are those that meet the filtering
353             criteria (station id, station name, active range etc.) and that
354             are not flagged with errors.
355            
356             =cut
357            
358 1     1 1 4 method selected {
359             # uncoverable branch true
360 1 50       5 return $note_nrs->cardinality == 0 ? $TRUE : $FALSE;
361             }
362            
363             =head2 url
364            
365             Returns the URL for the station's daily data web page in the NOAA
366             GHCN data repository.
367            
368             =cut
369            
370 1     1 1 4 method url {
371 1         10 return $NOAA_DATA . $id . '.dly';
372             }
373            
374            
375             =head2 DOES
376            
377             Defined by Object::Pad. Included for POD::Coverage.
378            
379             =head2 META
380            
381             Defined by Object::Pad. Included for POD::Coverage.
382            
383             =head1 AUTHOR
384            
385             Gary Puckering (jgpuckering@rogers.com)
386            
387             =head1 LICENSE AND COPYRIGHT
388            
389             Copyright 2022, Gary Puckering
390            
391             =cut
392            
393             1;