File Coverage

blib/lib/WWW/MySociety/Gaze.pm
Criterion Covered Total %
statement 83 84 98.8
branch 11 20 55.0
condition 9 19 47.3
subroutine 21 22 95.4
pod 8 8 100.0
total 132 153 86.2


line stmt bran cond sub pod time code
1             package WWW::MySociety::Gaze;
2              
3 2     2   135273 use warnings;
  2         5  
  2         74  
4 2     2   12 use strict;
  2         5  
  2         74  
5 2     2   13 use Carp;
  2         10  
  2         201  
6 2     2   2525 use LWP::UserAgent;
  2         125245  
  2         71  
7 2     2   2056 use HTML::Tiny;
  2         5973  
  2         61  
8 2     2   2078 use Text::CSV;
  2         27081  
  2         14  
9              
10 2     2   96 use constant SERVICE => 'http://gaze.mysociety.org/gaze-rest';
  2         4  
  2         2286  
11              
12             =head1 NAME
13              
14             WWW::MySociety::Gaze - An interface to MySociety.org's Gazetteer service
15              
16             =head1 VERSION
17              
18             This document describes WWW::MySociety::Gaze version 0.02
19              
20             =cut
21              
22             our $VERSION = '0.02';
23              
24             =head1 SYNOPSIS
25              
26             use WWW::MySociety::Gaze;
27              
28             =head1 DESCRIPTION
29              
30             MySociety.org Gaze is a REST based gazetteer service. You can find out
31             more about it here:
32              
33             L
34              
35             C is a Perl wrapper around Gaze.
36              
37             =head1 INTERFACE
38              
39             =head2 C<< new >>
40              
41             Create a new C.
42              
43             =cut
44              
45             sub new {
46 1     1 1 17 my $class = shift;
47 1         18 return bless {}, $class;
48             }
49              
50             =head2 C<< get_country_from_ip( $ip ) >>
51              
52             Guess the country of location of a host from its dotted quad IP address.
53             Returns an ISO country code or C if the country code is unknown.
54              
55             my $gaze = WWW::MySociety::Gaze->new;
56             my $country = $gaze->get_country_from_ip( '82.152.157.85' );
57              
58             =cut
59              
60             sub get_country_from_ip {
61 1     1 1 3 my $self = shift;
62              
63 1 50       7 croak "Need an IP address"
64             unless @_ == 1;
65              
66 1         3 my $ip = shift;
67 1 50       11 croak "IP address must be a dotted quad"
68             unless $ip =~ /^(?:\d{1,3}\.){3}\d{1,3}$/;
69              
70 1         7 my $answer = $self->_request( 'get_country_from_ip', ip => $ip );
71 1         32 chomp $answer;
72 1 50       11 return $answer eq '' ? undef : $answer;
73             }
74              
75             =head2 C<< get_find_places_countries >>
76              
77             Return the list of countries for which C has a gazetteer
78             available.
79              
80             Takes no arguments, returns a list of ISO country codes.
81              
82             my $gaze = WWW::MySociety::Gaze->new;
83             my @countries = $gaze->get_find_places_countries;
84              
85             =cut
86              
87             sub get_find_places_countries {
88 1     1 1 586 my $self = shift;
89 1         7 return $self->_lines(
90             $self->_request( 'get_find_places_countries' ) );
91             }
92              
93             =head2 C<< find_places >>
94              
95             Lookup a location in the gazetteer. Takes a number of key, value pairs
96             as follows:
97              
98             =head3 Parameters
99              
100             =over
101              
102             =item C
103              
104             ISO country code of country in which to search for places
105              
106             =item C
107              
108             State in which to search for places; presently this is only meaningful
109             for country=US (United States), in which case it should be a
110             conventional two-letter state code (AZ, CA, NY etc.); optional
111              
112             =item C
113              
114             Query term input by the user; must be at least two characters long
115              
116             =item C
117              
118             Largest number of results to return, from 1 to 100 inclusive; optional;
119             default 10
120              
121             =item C
122              
123             Minimum match score of returned results, from 1 to 100 inclusive;
124             optional; default 0
125              
126             =back
127              
128             Returns a list of hash references. Each hash has the following fields:
129              
130             =over
131              
132             =item C
133              
134             Name of the place described by this row
135              
136             =item C
137              
138             Blank, or the name of an administrative region in which this place lies
139             (for instance, a county)
140              
141             =item C
142              
143             A reference to a (possibly empty) array of nearby placenames.
144              
145             =item C
146              
147             WGS-84 latitude of place in decimal degrees, north-positive
148              
149             =item C
150              
151             WGS-84 longitude of place in decimal degrees, east-positive
152              
153             =item C
154              
155             Blank, or containing state code for US
156              
157             =item C
158              
159             Match score for this place, from 0 to 100 inclusive
160              
161             =back
162              
163             =cut
164              
165             sub find_places {
166 1     1 1 3076 my $self = shift;
167 1 50 33     12 croak "Need arguments as key, value pairs"
168             unless @_ and ( @_ % 2 == 0 );
169             return $self->_csv_to_hashes(
170             $self->_request( 'find_places', @_ ),
171             sub {
172 10     10   13 my $rec = shift;
173 10         26 $rec->{Near} = [ split /\s*,\s*/, $rec->{Near} ];
174 10         45 return $rec;
175             }
176 1         5 );
177             }
178              
179             =head2 C<< get_population_density( $lat, $lon ) >>
180              
181             Given a latitude, longitude pair return an estimate of the population
182             density at (lat, lon), in persons per square kilometer.
183              
184             =cut
185              
186             sub get_population_density {
187 1     1 1 894 my ( $self, $lat, $lon ) = @_;
188              
189 1         6 my @density = $self->_lines(
190             $self->_request(
191             'get_population_density',
192             lat => $lat,
193             lon => $lon
194             )
195             );
196              
197 1         5 return shift @density;
198             }
199              
200             =head2 C<< get_radius_containing_population >>
201              
202             Return an estimate of the smallest radius around (lat, lon) containing
203             at least number persons, or maximum, if that value is smaller. Takes key
204             value parameters:
205              
206             =over
207              
208             =item C
209              
210             WGS84 latitude, in decimal degrees
211              
212             =item C
213              
214             WGS84 longitude, in decimal degrees
215              
216             =item C
217              
218             number of persons
219              
220             =item C
221              
222             largest radius returned, in kilometers; optional; default 150
223              
224             =back
225              
226             =cut
227              
228             sub get_radius_containing_population {
229 1     1 1 684 my $self = shift;
230 1 50 33     11 croak "Need arguments as key, value pairs"
231             unless @_ and ( @_ % 2 == 0 );
232              
233 1         6 my @radius = $self->_lines(
234             $self->_request( 'get_population_density', @_ ) );
235              
236 1         6 return shift @radius;
237             }
238              
239             =head2 C<< get_country_bounding_coords >>
240              
241             Get the bounding box of a country given its ISO country code. Returns a
242             four element list containing max_lat, min_lat, max_lon, min_lon.
243              
244             my @bb = $gaze->get_country_bounding_coords( 'GB' );
245              
246             =cut
247              
248             sub get_country_bounding_coords {
249 1     1 1 741 my ( $self, $country ) = @_;
250 1         6 my @bb = $self->_lines(
251             $self->_request(
252             'get_country_bounding_coords', country => $country
253             )
254             );
255              
256 1         7 return split /\s+/, shift @bb;
257             }
258              
259             =head2 C<< get_places_near >>
260              
261             Get a list of places near a specific location. Takes a list of name,
262             value pairs like this:
263              
264             =over
265              
266             =item C
267              
268             WGS84 latitude, in north-positive decimal degrees
269              
270             =item C
271              
272             WGS84 longitude, in east-positive decimal degrees
273              
274             =item C
275              
276             distance in kilometres
277              
278             =item C
279              
280             number of persons to calculate circle radius
281              
282             =item C
283              
284             maximum radius to return (default 150km)
285              
286             =item C
287              
288             ISO country code of country to limit results to (optional)
289              
290             =back
291              
292             Returns a list of hash references like this:
293              
294             =over
295              
296             =item C
297              
298             Name of the nearby place.
299              
300             =item C
301              
302             Distance from the base place.
303              
304             =item C
305              
306             Latitude of the nearby place.
307              
308             =item C
309              
310             Longitude of the nearby place.
311              
312             =item C
313              
314             Country of the nearby place.
315              
316             =item C
317              
318             State of the nearby place (currently US only).
319              
320             =back
321              
322             =cut
323              
324             sub get_places_near {
325 1     1 1 1302 my $self = shift;
326 1 50 33     13 croak "Need arguments as key, value pairs"
327             unless @_ and ( @_ % 2 == 0 );
328              
329             return $self->_csv_to_hashes(
330             $self->_request( 'get_places_near', @_ ),
331             sub {
332 145     145   172 my $rec = shift;
333 145   100     323 $rec->{Distance} ||= 0;
334 145         638 return $rec;
335             }
336 1         7 );
337             }
338              
339             sub _request {
340 7     7   18 my $self = shift;
341 7 50 33     70 croak
342             "Need a verb and optionally a list of argument key value pairs"
343             unless @_ >= 1 and @_ % 2;
344 7         35 my ( $verb, %args ) = @_;
345 7   66     41 my $ua = $self->{_ua} ||= LWP::UserAgent->new;
346 7         3827 my $uri = SERVICE . '?'
347             . HTML::Tiny->new->query_encode( { %args, f => $verb } );
348 7         1377 my $resp = $ua->get( $uri );
349 7 50       1354782 croak $resp->status_line if $resp->is_error;
350 7         121 return $resp->content;
351             }
352              
353             sub _lines {
354 6     6   761 my ( $self, $text ) = @_;
355 6         91 $text =~ s/\r//g;
356 6         20 chomp $text;
357 6         176 return split /\n/, $text;
358             }
359              
360             sub _csv_to_hashes {
361 2     2   76 my ( $self, $text, $cook ) = @_;
362 2         10 my @lines = $self->_lines( $text );
363 2         28 my $csv = Text::CSV->new;
364              
365             my $csv_line = sub {
366 159 100   159   352 return unless @lines;
367 157         264 my $line = shift @lines;
368 157         422 my $status = $csv->parse( $line );
369 157 50       42330 croak "Can't parse $line: " . $csv->error_diag
370             unless $status;
371 157         431 return $csv->fields;
372 2         233 };
373              
374 2   50 0   10 $cook ||= sub { shift };
  0         0  
375              
376 2         32 my @names = $csv_line->();
377 2         24 my @data = ();
378              
379 2         6 while ( my @fields = $csv_line->() ) {
380 155         1528 my %row;
381 155         771 @row{@names} = @fields;
382 155         411 push @data, $cook->( \%row );
383             }
384              
385 2         99 return @data;
386             }
387              
388             1;
389             __END__