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   136878 use Moo;
  2         25767  
  2         10  
4 2     2   3981 use MooX::Aliases;
  2         5021  
  2         14  
5              
6 2     2   515 use base 'Exporter';
  2         3  
  2         124  
7 2     2   1456 use Geo::UK::Postcode::Regex;
  2         6942  
  2         114  
8              
9 2     2   13 use overload '""' => "as_string";
  2         4  
  2         15  
10              
11             our $VERSION = '0.009';
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             =head1 ATTRIBUTES
69              
70             =head2 raw
71              
72             The exact string that the object was constructed from, without formatting.
73              
74             =cut
75              
76             has raw => (
77             is => 'ro',
78             isa => sub {
79             die "Empty or invalid value passed to 'raw'" unless $_[0] && !ref $_[0];
80             },
81             );
82              
83             =for Pod::Coverage BUILDARGS BUILD components
84              
85             =cut
86              
87             # private - hashref to hold parsed components of postcode
88             has components => (
89             is => 'rwp',
90             default => sub { {} },
91             );
92              
93             around BUILDARGS => sub {
94             my ( $orig, $class, $args ) = @_;
95              
96             return $class->$orig( #
97             ref $args ? $args : { raw => $args }
98             );
99             };
100              
101             sub BUILD {
102 113     113 0 1233 my ($self) = @_;
103              
104 113         381 my $pc = uc $self->raw;
105              
106 113 100       586 my $parsed = Geo::UK::Postcode::Regex->parse( $pc, { partial => 1 } )
107             or die sprintf( "Unable to parse '%s' as a postcode", $self->raw );
108              
109 109         158271 $self->_set_components($parsed);
110             }
111              
112             =head1 METHODS
113              
114             =head2 raw
115              
116              
117             =head2 as_string
118              
119             $pc->as_string;
120              
121             # or:
122              
123             "$pc";
124              
125             Stringification of postcode object, returns postcode with a single space
126             between outcode and incode.
127              
128             =cut
129              
130 249 100   249 1 2183 sub as_string { $_[0]->outcode . ( $_[0]->incode ? ' ' . $_[0]->incode : '' ) }
131              
132             =head2 fixed_format
133              
134             my $fixed_format = $postcode->fixed_format;
135              
136             Returns the full postcode in a fixed length (8 character) format, with extra
137             padding spaces inserted as necessary.
138              
139             =cut
140              
141             sub fixed_format {
142 73     73 1 232 sprintf( "%-4s %-3s", $_[0]->outcode, $_[0]->incode );
143             }
144              
145             =head2 area, district, subdistrict, sector, unit
146              
147             Return the corresponding part of the postcode, undef if not present.
148              
149             =cut
150              
151 915     915 1 52156 sub area { shift->components->{area} }
152 745     745 1 33850 sub district { shift->components->{district} }
153 719     719 1 34112 sub subdistrict { shift->components->{subdistrict} }
154 784     784 1 27812 sub sector { shift->components->{sector} }
155 784     784 1 32710 sub unit { shift->components->{unit} }
156              
157             =head2 outcode
158              
159             The first half of the postcode, before the space - comprises of the area and
160             district.
161              
162             =head2 incode
163              
164             The second half of the postcode, after the space - comprises of the sector
165             and unit. Returns an empty string if not present.
166              
167             =cut
168              
169             sub outcode {
170 614   100 614 1 61500 $_[0]->area . $_[0]->district . ( $_[0]->subdistrict || '' );
171             }
172              
173             sub incode {
174 711   100 711 1 31612 ( $_[0]->sector // '' ) . ( $_[0]->unit || '' );
      100        
175             }
176              
177             =head2 outward, inward
178              
179             Aliases for C and C.
180              
181             =cut
182              
183             alias outward => 'outcode';
184             alias inward => 'incode';
185              
186             =head2 valid
187              
188             if ($pc->valid) {
189             ...
190             }
191              
192             Returns true if postcode has valid outcode and matches strict regex.
193              
194             =head2 partial
195              
196             if ($pc->partial) {
197             ...
198             }
199              
200             Returns true if postcode is not a full postcode, either a postcode district
201             ( e . g . AB10 )
202             or postcode sector (e.g. AB10 1).
203              
204             =head2 strict
205              
206             if ($pc->strict) {
207             ...
208             }
209              
210             Returns true if postcode matches strict regex, meaning all characters are valid
211             ( although postcode might not exist ) .
212              
213             =cut
214              
215             sub valid {
216 73 100   73 1 445 $_[0]->components->{valid} ? 1 : 0;
217             }
218              
219             sub partial {
220 73 100   73 1 30177 $_[0]->components->{partial} ? 1 : 0;
221             }
222              
223             sub strict {
224 73 100   73 1 30839 $_[0]->components->{strict} ? 1 : 0;
225             }
226              
227             =head2 non_geographical
228              
229             if ($pc->non_geographical) {
230             ...
231             }
232              
233             Returns true if the outcode is known to be non-geographical. Note that
234             geographical outcodes may have non-geographical postcodes within them.
235              
236             (Non-geographical postcodes are used for PO Boxes, or organisations
237             receiving large amounts of post).
238              
239             =cut
240              
241             sub non_geographical {
242 73 100   73 1 30232 $_[0]->components->{non_geographical} ? 1 : 0;
243             }
244              
245             =head2 bfpo
246              
247             if ($pc->bfpo) {
248             ...
249             }
250              
251             Returns true if postcode is mapped to a BFPO number (British Forces Post
252             Office).
253              
254             =cut
255              
256             sub bfpo {
257 73 100   73 1 29432 $_[0]->outcode eq 'BF1' ? 1 : 0;
258             }
259              
260             =head2 posttowns
261              
262             my (@posttowns) = $postcode->posttowns;
263              
264             Returns list of one or more posttowns that this postcode is assigned to.
265              
266             =cut
267              
268 73     73 1 30466 sub posttowns { Geo::UK::Postcode::Regex->outcode_to_posttowns( $_[0]->outcode ) }
269              
270             =head1 EXPORTABLE
271              
272             =head2 pc_sort
273              
274             my @sorted_pcs = sort pc_sort @unsorted_pcs;
275              
276             Exportable sort function, sorts postcode objects in a useful manner. The
277             sort is in the following order: area, district, subdistrict, sector, unit
278             (ascending alphabetical or numerical order as appropriate).
279              
280             =cut
281              
282             sub pc_sort($$) {
283 114 100 100 114 1 384 $_[0]->area cmp $_[1]->area
      100        
      100        
      100        
      100        
      100        
284             || $_[0]->district <=> $_[1]->district
285             || ( $_[0]->subdistrict || '' ) cmp( $_[1]->subdistrict || '' )
286             || ( $_[0]->incode || '' ) cmp( $_[1]->incode || '' );
287             }
288              
289             =head1 GEO-LOCATING POSTCODES
290              
291             Postcodes can be geolocated by obtaining the Ordnance Survey 'Code-Point' data
292             (or the free 'Code-Point Open' data).
293              
294             For full details of using this class with Code-Point data, see:
295             L.
296              
297             =head1 SEE ALSO
298              
299             =over
300              
301             =item *
302              
303             L
304              
305             =item *
306              
307             L
308              
309             =item *
310              
311             L
312              
313             =back
314              
315             =head1 SUPPORT
316              
317             =head2 Bugs / Feature Requests
318              
319             Please report any bugs or feature requests through the issue tracker
320             at L.
321             You will be notified automatically of any progress on your issue.
322              
323             =head2 Source Code
324              
325             This is open source software. The code repository is available for
326             public review and contribution under the terms of the license.
327              
328             L
329              
330             git clone git://github.com/mjemmeson/geo-uk-postcode.git
331              
332             =head1 AUTHOR
333              
334             Michael Jemmeson Emjemmeson@cpan.orgE
335              
336             =head1 COPYRIGHT
337              
338             Copyright 2014- Michael Jemmeson
339              
340             =head1 LICENSE
341              
342             This library is free software; you can redistribute it and/or modify
343             it under the same terms as Perl itself.
344              
345             =cut
346              
347             1;
348