File Coverage

blib/lib/Geo/Coder/GeocodeFarm.pm
Criterion Covered Total %
statement 73 77 94.8
branch 24 32 75.0
condition 11 19 57.8
subroutine 15 15 100.0
pod 3 3 100.0
total 126 146 86.3


line stmt bran cond sub pod time code
1             package Geo::Coder::GeocodeFarm;
2              
3             =head1 NAME
4              
5             Geo::Coder::GeocodeFarm - Geocode addresses with the GeocodeFarm API
6              
7             =head1 SYNOPSIS
8              
9             use Geo::Coder::GeocodeFarm;
10              
11             my $geocoder = Geo::Coder::GeocodeFarm->new(
12             key => '3d517dd448a5ce1c2874637145fed69903bc252a',
13             );
14             my $result = $geocoder->geocode(
15             location => '530 W Main St Anoka MN 55303 US',
16             lang => 'en',
17             count => 1,
18             );
19             printf "%f,%f",
20             $result->{RESULTS}{COORDINATES}{latitude},
21             $result->{RESULTS}{COORDINATES}{longitude};
22              
23             =head1 DESCRIPTION
24              
25             The C module provides an interface to the geocoding
26             functionality of the GeocodeFarm API v3.
27              
28             =for readme stop
29              
30             =cut
31              
32              
33 8     8   233717 use 5.006;
  8         22  
  8         242  
34 8     8   33 use strict;
  8         10  
  8         207  
35 8     8   29 use warnings;
  8         15  
  8         305  
36              
37             our $VERSION = '0.0402';
38              
39 8     8   34 use Carp qw(croak);
  8         8  
  8         386  
40 8     8   4069 use Encode;
  8         84639  
  8         577  
41 8     8   5031 use HTTP::Tiny;
  8         287839  
  8         280  
42 8     8   3910 use URI;
  8         29013  
  8         197  
43 8     8   3199 use URI::QueryParam;
  8         3934  
  8         168  
44 8     8   4360 use JSON;
  8         66765  
  8         39  
45 8     8   1031 use Scalar::Util qw(blessed);
  8         10  
  8         501  
46              
47 8     8   27 use constant DEBUG => !! $ENV{PERL_GEO_CODER_GEOCODEFARM_DEBUG};
  8         10  
  8         3984  
48              
49              
50             =head1 METHODS
51              
52             =head2 new
53              
54             $geocoder = Geo::Coder::GeocodeFarm->new(
55             key => '3d517dd448a5ce1c2874637145fed69903bc252a',
56             url => 'https://www.geocode.farm/v3/',
57             ua => HTTP::Tiny->new,
58             parser => JSON->new->utf8,
59             raise_failure => 1,
60             );
61              
62             Creates a new geocoding object with optional arguments.
63              
64             An API key is optional and can be obtained at
65             L
66              
67             C argument is optional and then the default address is http-based if
68             C argument is missing and https-based if C is provided.
69              
70             C argument is a L object by default and can be also set to
71             L object.
72              
73             New account can be registered at L
74              
75             =cut
76              
77             sub new {
78 11     11 1 4436 my ($class, %args) = @_;
79              
80 11 100 66     284 my $self = bless +{
      33        
      50        
81             ua => $args{ua} || HTTP::Tiny->new(
82             agent => __PACKAGE__ . "/$VERSION",
83             ),
84             url => sprintf('%s://www.geocode.farm/v3/', $args{key} ? 'https' : 'http'),
85             parser => $args{parser} || JSON->new->utf8,
86             raise_failure => $args{raise_failure} || 1,
87             %args,
88             } => $class;
89              
90 11         235 return $self;
91             }
92              
93              
94             =head2 geocode
95              
96             $result = $geocoder->geocode(
97             location => $location,
98             lang => 'en', # optional: 'en' or 'de'
99             country => 'US', # optional
100             count => 1, # optional
101             )
102              
103             Forward geocoding takes a provided address or location and returns the
104             coordinate set for the requested location as a nested list:
105              
106             {
107             "geocoding_results": {
108             "LEGAL_COPYRIGHT": {
109             "copyright_notice": "Copyright (c) 2015 Geocode.Farm - All Rights Reserved.",
110             "copyright_logo": "https:\/\/www.geocode.farm\/images\/logo.png",
111             "terms_of_service": "https:\/\/www.geocode.farm\/policies\/terms-of-service\/",
112             "privacy_policy": "https:\/\/www.geocode.farm\/policies\/privacy-policy\/"
113             },
114             "STATUS": {
115             "access": "FREE_USER, ACCESS_GRANTED",
116             "status": "SUCCESS",
117             "address_provided": "530 W Main St Anoka MN 55303 US",
118             "result_count": 1
119             },
120             "ACCOUNT": {
121             "ip_address": "1.2.3.4",
122             "distribution_license": "NONE, UNLICENSED",
123             "usage_limit": "250",
124             "used_today": "26",
125             "used_total": "26",
126             "first_used": "26 Mar 2015"
127             },
128             "RESULTS": [
129             {
130             "result_number": 1,
131             "formatted_address": "530 West Main Street, Anoka, MN 55303, USA",
132             "accuracy": "EXACT_MATCH",
133             "ADDRESS": {
134             "street_number": "530",
135             "street_name": "West Main Street",
136             "locality": "Anoka",
137             "admin_2": "Anoka County",
138             "admin_1": "Minnesota",
139             "postal_code": "55303",
140             "country": "United States"
141             },
142             "LOCATION_DETAILS": {
143             "elevation": "UNAVAILABLE",
144             "timezone_long": "UNAVAILABLE",
145             "timezone_short": "America\/Menominee"
146             },
147             "COORDINATES": {
148             "latitude": "45.2041251174690",
149             "longitude": "-93.4003513528652"
150             },
151             "BOUNDARIES": {
152             "northeast_latitude": "45.2041251778513",
153             "northeast_longitude": "-93.4003513845523",
154             "southwest_latitude": "45.2027761197097",
155             "southwest_longitude": "-93.4017002802923"
156             }
157             }
158             ],
159             "STATISTICS": {
160             "https_ssl": "DISABLED, INSECURE"
161             }
162             }
163             }
164              
165             Method throws an error (or returns failure as nested list if raise_failure
166             argument is false) if the service failed to find coordinates or wrong key was
167             used.
168              
169             Methods throws an error if there was an other problem.
170              
171             =cut
172              
173             sub geocode {
174 8     8 1 25294 my ($self, %args) = @_;
175              
176 8         13 my ($addr) = do {
177 8 100       32 if (defined $args{location}) {
    100          
178 5         12 $args{location};
179             }
180             elsif (defined $args{addr}) {
181 1         3 $args{addr};
182             }
183             else {
184 2         35 croak "Attribute (location) or attribute (addr) is required";
185             }
186             };
187              
188 6         27 return $self->_request('forward', %args, addr => $addr);
189             };
190              
191              
192             =head2 reverse_geocode
193              
194             $result = $geocoder->reverse_geocode(
195             lat => $latitude,
196             lon => $longtitude,
197             lang => 'en', # optional: 'en' or 'de'
198             country => 'US', # optional
199             count => 1, # optional
200             )
201              
202             or
203              
204             $result = $geocoder->reverse_geocode(
205             latlng => "$latitude,$longtitude",
206             # ... optional args
207             )
208              
209             Reverse geocoding takes a provided coordinate set and returns the address for
210             the requested coordinates as a nested list. Its format is the same as for
211             L method.
212              
213             Method throws an error (or returns failure as nested list if raise_failure
214             argument is false) if the service failed to find coordinates or wrong key was
215             used.
216              
217             Method throws an error if there was an other problem.
218              
219             =cut
220              
221             sub reverse_geocode {
222 5     5 1 22651 my ($self, %args) = @_;
223              
224 5         8 my ($lat, $lon) = do {
225 5 100 33     24 if (defined $args{latlng}) {
    50          
226 3         11 my @latlng = split ',', $args{latlng};
227 3 50       9 croak "Attribute (latlng) is invalid" unless @latlng == 2;
228 3         6 @latlng;
229             }
230             elsif (defined $args{lat} and defined $args{lon}) {
231 2         6 @args{qw(lat lon)};
232             }
233             else {
234 0         0 croak "Attribute (latlng) or attributes (lat) and (lon) are required";
235             }
236             };
237              
238 5         21 return $self->_request('reverse', %args, lat => $lat, lon => $lon);
239             };
240              
241              
242             sub _request {
243 11     11   29 my ($self, $type, %args) = @_;
244              
245 11         112 my $url = URI->new_abs(sprintf('json/%s/', $type), $self->{url});
246              
247 11 100       45716 if ($type eq 'forward') {
    50          
248 6         49 $url->query_param_append(addr => $args{addr});
249             } elsif ($type eq 'reverse') {
250 5         37 $url->query_param_append(lat => $args{lat});
251 5         406 $url->query_param_append(lon => $args{lon});
252             } else {
253 0         0 croak "Unknown type for request";
254             }
255              
256 11 100       995 $url->query_param_append(key => $self->{key}) if $self->{key};
257 11         329 warn $url if DEBUG;
258              
259 11         111 my $res = $self->{ua}->get($url);
260              
261 11         263622 my $content = do {
262 11 100 66     117 if (blessed $res and $res->isa('HTTP::Response')) {
    50          
263 4 50       26 croak $res->status_line unless $res->is_success;
264 4         21 $res->decoded_content;
265             } elsif (ref $res eq 'HASH') {
266 7 50       22 croak "@{[$res->{status}, $res->{reason}]}" unless $res->{success};
  0         0  
267 7         15 $res->{content};
268             } else {
269 0         0 croak "Wrong response $res ";
270             }
271             };
272              
273 11         22 warn $content if DEBUG;
274 11 50       29 return unless $content;
275              
276 11         48 my $data = eval { $self->{parser}->decode($content) };
  11         427  
277 11 50       73 croak $content if $@;
278              
279 11 100 50     188 croak "GeocodeFarm API returned status: ", $data->{geocoding_results}{STATUS}{status}
      100        
280             if ($self->{raise_failure} and ($data->{geocoding_results}{STATUS}{status}||'') ne 'SUCCESS');
281              
282 9         72 return $data->{geocoding_results};
283             };
284              
285              
286             1;
287              
288              
289             =for readme continue
290              
291             =head1 SEE ALSO
292              
293             L
294              
295             =head1 BUGS
296              
297             If you find the bug or want to implement new features, please report it at
298             L
299              
300             The code repository is available at
301             L
302              
303             =head1 AUTHOR
304              
305             Piotr Roszatycki
306              
307             =head1 LICENSE
308              
309             Copyright (c) 2013, 2015 Piotr Roszatycki .
310              
311             This is free software; you can redistribute it and/or modify it under
312             the same terms as perl itself.
313              
314             See L