File Coverage

blib/lib/Geo/UK/Postcode.pm
Criterion Covered Total %
statement 35 35 100.0
branch 16 16 100.0
condition 20 20 100.0
subroutine 22 22 100.0
pod 16 17 94.1
total 109 110 99.0


line stmt bran cond sub pod time code
1             package Geo::UK::Postcode;
2              
3 2     2   141997 use Moo;
  2         29033  
  2         8  
4 2     2   3949 use MooX::Aliases;
  2         4537  
  2         12  
5              
6 2     2   504 use base 'Exporter';
  2         3  
  2         104  
7 2     2   1344 use Geo::UK::Postcode::Regex;
  2         6303  
  2         91  
8              
9 2     2   12 use overload '""' => "as_string";
  2         2  
  2         13  
10              
11             our $VERSION = '0.010';
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             $pc->bfpo; # true if postcode is for a BFPO address
51              
52             my @posttowns = $pc->posttowns; # list of one or more 'post towns'
53             # associated with this postcode
54              
55             # Sort Postcode objects:
56             use Geo::UK::Postcode qw/ pc_sort /;
57              
58             my @sorted_pcs = sort pc_sort @unsorted_pcs;
59              
60             =head1 DESCRIPTION
61              
62             An object to represent a British postcode.
63              
64             For matching and parsing postcodes in a non-OO manner without the L
65             dependency (for form validation, for example), see L
66             or L.
67              
68             For geo-location (finding latitude and longitude) see
69             L.
70              
71             =head1 ATTRIBUTES
72              
73             =head2 raw
74              
75             The exact string that the object was constructed from, without formatting.
76              
77             =cut
78              
79             has raw => (
80             is => 'ro',
81             isa => sub {
82             die "Empty or invalid value passed to 'raw'" unless $_[0] && !ref $_[0];
83             },
84             );
85              
86             =for Pod::Coverage BUILDARGS BUILD components
87              
88             =cut
89              
90             # private - hashref to hold parsed components of postcode
91             has components => (
92             is => 'rwp',
93             default => sub { {} },
94             );
95              
96             around BUILDARGS => sub {
97             my ( $orig, $class, $args ) = @_;
98              
99             return $class->$orig( #
100             ref $args ? $args : { raw => $args }
101             );
102             };
103              
104             sub BUILD {
105 113     113 0 1126 my ($self) = @_;
106              
107 113         402 my $pc = uc $self->raw;
108              
109 113 100       535 my $parsed = Geo::UK::Postcode::Regex->parse( $pc, { partial => 1 } )
110             or die sprintf( "Unable to parse '%s' as a postcode", $self->raw );
111              
112 109         138180 $self->_set_components($parsed);
113             }
114              
115             =head1 METHODS
116              
117             =head2 raw
118              
119              
120             =head2 as_string
121              
122             $pc->as_string;
123              
124             # or:
125              
126             "$pc";
127              
128             Stringification of postcode object, returns postcode with a single space
129             between outcode and incode.
130              
131             =cut
132              
133 249 100   249 1 2262 sub as_string { $_[0]->outcode . ( $_[0]->incode ? ' ' . $_[0]->incode : '' ) }
134              
135             =head2 fixed_format
136              
137             my $fixed_format = $postcode->fixed_format;
138              
139             Returns the full postcode in a fixed length (8 character) format, with extra
140             padding spaces inserted as necessary.
141              
142             =cut
143              
144             sub fixed_format {
145 73     73 1 271 sprintf( "%-4s %-3s", $_[0]->outcode, $_[0]->incode );
146             }
147              
148             =head2 area, district, subdistrict, sector, unit
149              
150             Return the corresponding part of the postcode, undef if not present.
151              
152             =cut
153              
154 917     917 1 53638 sub area { shift->components->{area} }
155 747     747 1 34300 sub district { shift->components->{district} }
156 719     719 1 34766 sub subdistrict { shift->components->{subdistrict} }
157 784     784 1 27933 sub sector { shift->components->{sector} }
158 784     784 1 33166 sub unit { shift->components->{unit} }
159              
160             =head2 outcode
161              
162             The first half of the postcode, before the space - comprises of the area and
163             district.
164              
165             =head2 incode
166              
167             The second half of the postcode, after the space - comprises of the sector
168             and unit. Returns an empty string if not present.
169              
170             =cut
171              
172             sub outcode {
173 614   100 614 1 61943 $_[0]->area . $_[0]->district . ( $_[0]->subdistrict || '' );
174             }
175              
176             sub incode {
177 711   100 711 1 31346 ( $_[0]->sector // '' ) . ( $_[0]->unit || '' );
      100        
178             }
179              
180             =head2 outward, inward
181              
182             Aliases for C and C.
183              
184             =cut
185              
186             alias outward => 'outcode';
187             alias inward => 'incode';
188              
189             =head2 valid
190              
191             if ($pc->valid) {
192             ...
193             }
194              
195             Returns true if postcode has valid outcode and matches strict regex.
196              
197             =head2 partial
198              
199             if ($pc->partial) {
200             ...
201             }
202              
203             Returns true if postcode is not a full postcode, either a postcode district
204             ( e . g . AB10 )
205             or postcode sector (e.g. AB10 1).
206              
207             =head2 strict
208              
209             if ($pc->strict) {
210             ...
211             }
212              
213             Returns true if postcode matches strict regex, meaning all characters are valid
214             ( although postcode might not exist ) .
215              
216             =cut
217              
218             sub valid {
219 73 100   73 1 358 $_[0]->components->{valid} ? 1 : 0;
220             }
221              
222             sub partial {
223 73 100   73 1 29854 $_[0]->components->{partial} ? 1 : 0;
224             }
225              
226             sub strict {
227 73 100   73 1 29780 $_[0]->components->{strict} ? 1 : 0;
228             }
229              
230             =head2 non_geographical
231              
232             if ($pc->non_geographical) {
233             ...
234             }
235              
236             Returns true if the outcode is known to be non-geographical. Note that
237             geographical outcodes may have non-geographical postcodes within them.
238              
239             (Non-geographical postcodes are used for PO Boxes, or organisations
240             receiving large amounts of post).
241              
242             =cut
243              
244             sub non_geographical {
245 73 100   73 1 29854 $_[0]->components->{non_geographical} ? 1 : 0;
246             }
247              
248             =head2 bfpo
249              
250             if ($pc->bfpo) {
251             ...
252             }
253              
254             Returns true if postcode is mapped to a BFPO number (British Forces Post
255             Office).
256              
257             =cut
258              
259             sub bfpo {
260 73 100   73 1 29771 $_[0]->outcode eq 'BF1' ? 1 : 0;
261             }
262              
263             =head2 posttowns
264              
265             my (@posttowns) = $postcode->posttowns;
266              
267             Returns list of one or more 'post towns' that this postcode is assigned to.
268              
269             Post towns are rarely used today, and are no longer required in a postal address
270             but are included with the postcode data, so provided here.
271              
272             =cut
273              
274             sub posttowns {
275 73     73 1 30418 Geo::UK::Postcode::Regex->outcode_to_posttowns( $_[0]->outcode );
276             }
277              
278             =head1 EXPORTABLE
279              
280             =head2 pc_sort
281              
282             my @sorted_pcs = sort pc_sort @unsorted_pcs;
283              
284             Exportable sort function, sorts postcode objects in a useful manner. The
285             sort is in the following order: area, district, subdistrict, sector, unit
286             (ascending alphabetical or numerical order as appropriate).
287              
288             =cut
289              
290             sub pc_sort($$) {
291 115 100 100 115 1 353 $_[0]->area cmp $_[1]->area
      100        
      100        
      100        
      100        
      100        
292             || $_[0]->district <=> $_[1]->district
293             || ( $_[0]->subdistrict || '' ) cmp( $_[1]->subdistrict || '' )
294             || ( $_[0]->incode || '' ) cmp( $_[1]->incode || '' );
295             }
296              
297             1;
298              
299             __END__