File Coverage

blib/lib/Geo/UK/Postcode.pm
Criterion Covered Total %
statement 34 36 94.4
branch 13 16 81.2
condition 12 20 60.0
subroutine 20 22 90.9
pod 16 17 94.1
total 95 111 85.5


line stmt bran cond sub pod time code
1             package Geo::UK::Postcode;
2              
3 2     2   192103 use Moo;
  2         35400  
  2         13  
4 2     2   5265 use MooX::Aliases;
  2         12342  
  2         17  
5              
6 2     2   602 use base 'Exporter';
  2         4  
  2         134  
7 2     2   2722 use Geo::UK::Postcode::Regex;
  2         10418  
  2         117  
8              
9 2     2   20 use overload '""' => "as_string";
  2         4  
  2         19  
10              
11             our $VERSION = '0.008';
12              
13             our @EXPORT_OK = qw/ pc_sort /;
14              
15             =encoding utf-8
16              
17             =head1 NAME
18              
19             Geo::UK::Postcode - Object and class methods for working with British postcodes.
20              
21             =head1 SYNOPSIS
22              
23             # See Geo::UK::Postcode::Regex for parsing/matching postcodes
24              
25             use Geo::UK::Postcode;
26              
27             my $pc = Geo::UK::Postcode->new("wc1h9eb");
28              
29             $pc->raw; # wc1h9eb - as entered
30             $pc->as_string; # WC1H 9EB - output in correct format
31             "$pc"; # stringifies, same output as '->as_string'
32             $pc->fixed_format; # 8 characters, the incode always last three
33              
34             $pc->area; # WC
35             $pc->district; # 1
36             $pc->subdistrict; # H
37             $pc->sector; # 9
38             $pc->unit; # EB
39              
40             $pc->outcode; # WC1H
41             $pc->incode; # 9EB
42              
43             $pc->strict; # true if matches strict regex
44             $pc->valid; # true if matches strict regex and has a valid outcode
45             $pc->partial; # true if postcode is for a district or sector only
46              
47             $pc->non_geographical; # true if outcode is known to be
48             # non-geographical
49              
50             # Sort Postcode objects:
51             use Geo::UK::Postcode qw/ pc_sort /;
52              
53             my @sorted_pcs = sort pc_sort @unsorted_pcs;
54              
55             =head1 DESCRIPTION
56              
57             An object to represent a British postcode.
58              
59             For matching and parsing postcodes in a non-OO manner (for form validation, for
60             example), see L
61              
62             For geo-location (finding latitude and longitude) see
63             L.
64              
65             Currently undef development - feedback welcome. Basic API unlikely to change
66             greatly, just more features/more postcodes supported - see L list.
67              
68             =cut
69              
70             has raw => ( is => 'ro' ); # Str
71             has components => (
72             is => 'rwp',
73             default => sub { {} },
74             );
75              
76             =for Pod::Coverage BUILDARGS BUILD
77              
78             =cut
79              
80             around BUILDARGS => sub {
81             my ( $orig, $class, $args ) = @_;
82              
83             unless ( ref $args ) {
84             $args = { raw => $args };
85             }
86              
87             return $class->$orig($args);
88             };
89              
90             sub BUILD {
91 72     72 0 691 my ($self) = @_;
92              
93 72 100       323 die "No raw postcode supplied" unless $self->raw;
94              
95 71         188 my $pc = uc $self->raw;
96              
97 71 100       552 my $parsed = Geo::UK::Postcode::Regex->parse( $pc, { partial => 1 } )
98             or die sprintf "Unable to parse '%s' as a postcode", $self->raw ;
99              
100 70         207252 $self->_set_components($parsed);
101             }
102              
103             =head1 METHODS
104              
105             =head2 raw
106              
107             Returns exact string that the object was constructed from.
108              
109             =head2 as_string
110              
111             $pc->as_string;
112              
113             # or:
114              
115             "$pc";
116              
117             Stringification of postcode object, returns postcode with a single space
118             between outcode and incode.
119              
120             =cut
121              
122 188     188 1 2319 sub as_string { $_[0]->outcode . ' ' . $_[0]->incode }
123              
124             =head2 fixed_format
125              
126             my $fixed_format = $postcode->fixed_format;
127              
128             Returns the full postcode in a fixed length (8 character) format, with extra
129             padding spaces inserted as necessary.
130              
131             =cut
132              
133             sub fixed_format {
134 60     60 1 188 sprintf( "%-4s %-3s", $_[0]->outcode, $_[0]->incode );
135             }
136              
137             =head2 area, district, subdistrict, sector, unit
138              
139             Return the corresponding part of the postcode, undef if not present.
140              
141             =cut
142              
143 444     444 1 49230 sub area { shift->components->{area} }
144 444     444 1 34027 sub district { shift->components->{district} }
145 440     440 1 50007 sub subdistrict { shift->components->{subdistrict} }
146 440     440 1 23528 sub sector { shift->components->{sector} }
147 440     440 1 28574 sub unit { shift->components->{unit} }
148              
149             =head2 outcode
150              
151             The first half of the postcode, before the space - comprises of the area and
152             district.
153              
154             =head2 incode
155              
156             The second half of the postcode, after the space - comprises of the sector
157             and unit. Returns an empty string if not present.
158              
159             =cut
160              
161             sub outcode {
162 368   100 368 1 53021 $_[0]->area . $_[0]->district . ( $_[0]->subdistrict || '' );
163             }
164              
165             sub incode {
166 380   50 380 1 36214 ( $_[0]->sector // '' ) . ( $_[0]->unit || '' );
      50        
167             }
168              
169             =head2 outward, inward
170              
171             Aliases for C and C.
172              
173             =cut
174              
175             alias outward => 'outcode';
176             alias inward => 'incode';
177              
178             =head2 valid
179              
180             if ($pc->valid) {
181             ...
182             }
183              
184             Returns true if postcode has valid outcode and matches strict regex.
185              
186             =head2 partial
187              
188             if ($pc->partial) {
189             ...
190             }
191              
192             Returns true if postcode is not a full postcode, either a postcode district
193             ( e . g . AB10 )
194             or postcode sector (e.g. AB10 1).
195              
196             =head2 strict
197              
198             if ($pc->strict) {
199             ...
200             }
201              
202             Returns true if postcode matches strict regex, meaning all characters are valid
203             ( although postcode might not exist ) .
204              
205             =cut
206              
207             sub valid {
208 60 100   60 1 362 $_[0]->components->{valid} ? 1 : 0;
209             }
210              
211             sub partial {
212 60 50   60 1 34033 $_[0]->components->{partial} ? 1 : 0;
213             }
214              
215             sub strict {
216 60 100   60 1 28457 $_[0]->components->{strict} ? 1 : 0;
217             }
218              
219             =head2 non_geographical
220              
221             if ($pc->non_geographical) {
222             ...
223             }
224              
225             Returns true if the outcode is known to be non-geographical. Note that
226             geographical outcodes may have non-geographical postcodes within them.
227              
228             (Non-geographical postcodes are used for PO Boxes, or organisations
229             receiving large amounts of post).
230              
231             =cut
232              
233             sub non_geographical {
234 60 100   60 1 27498 $_[0]->components->{non_geographical} ? 1 : 0;
235             }
236              
237             =head2 bfpo
238              
239             if ($pc->bfpo) {
240             ...
241             }
242              
243             Returns true if postcode is mapped to a BFPO number (British Forces Post
244             Office).
245              
246             =cut
247              
248             sub bfpo {
249 0 0   0 1 0 $_[0]->outcode eq 'BF1' ? 1 : 0;
250             }
251              
252             =head2 posttowns
253              
254             my (@posttowns) = $postcode->posttowns;
255              
256             Returns list of one or more posttowns that this postcode is assigned to.
257              
258             =cut
259              
260 0     0 1 0 sub posttowns { Geo::UK::Postcode::Regex->posttowns( $_[0]->outcode ) }
261              
262             =head1 EXPORTABLE
263              
264             =head2 pc_sort
265              
266             my @sorted_pcs = sort pc_sort @unsorted_pcs;
267              
268             Exportable sort function, sorts postcode objects in a useful manner. The
269             sort is in the following order: area, district, subdistrict, sector, unit
270             (ascending alphabetical or numerical order as appropriate).
271              
272             =cut
273              
274             sub pc_sort($$) {
275 8 100 50 8 1 118 $_[0]->area cmp $_[1]->area
      50        
      66        
      50        
      50        
      66        
276             || $_[0]->district <=> $_[1]->district
277             || ( $_[0]->subdistrict || '' ) cmp( $_[1]->subdistrict || '' )
278             || ( $_[0]->incode || '' ) cmp( $_[1]->incode || '' );
279             }
280              
281             =head1 GEO-LOCATING POSTCODES
282              
283             Postcodes can be geolocated by obtaining the Ordnance Survey 'Code-Point' data
284             (or the free 'Code-Point Open' data).
285              
286             For full details of using this class with Code-Point data, see:
287             L.
288              
289             =head1 SEE ALSO
290              
291             =over
292              
293             =item *
294              
295             L
296              
297             =item *
298              
299             L
300              
301             =item *
302              
303             L
304              
305             =back
306              
307             =head1 AUTHOR
308              
309             Michael Jemmeson Emjemmeson@cpan.orgE
310              
311             =head1 COPYRIGHT
312              
313             Copyright 2014- Michael Jemmeson
314              
315             =head1 LICENSE
316              
317             This library is free software; you can redistribute it and/or modify
318             it under the same terms as Perl itself.
319              
320             =cut
321              
322             1;
323