File Coverage

blib/lib/Geo/Coder/GeocodeFarm.pm
Criterion Covered Total %
statement 72 76 94.7
branch 24 32 75.0
condition 11 19 57.8
subroutine 15 15 100.0
pod 3 3 100.0
total 125 145 86.2


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 7     7   351969 use 5.006;
  7         21  
  7         203  
34 7     7   28 use strict;
  7         8  
  7         174  
35 7     7   23 use warnings;
  7         16  
  7         229  
36              
37             our $VERSION = '0.0401';
38              
39 7     7   27 use Carp qw(croak);
  7         6  
  7         326  
40 7     7   3286 use Encode;
  7         49237  
  7         422  
41 7     7   3872 use HTTP::Tiny;
  7         259316  
  7         256  
42 7     7   3413 use URI;
  7         25184  
  7         165  
43 7     7   2823 use URI::QueryParam;
  7         3437  
  7         150  
44 7     7   4084 use JSON;
  7         57087  
  7         37  
45 7     7   969 use Scalar::Util qw(blessed);
  7         9  
  7         402  
46              
47 7     7   27 use constant DEBUG => !! $ENV{PERL_GEO_CODER_GEOCODEFARM_DEBUG};
  7         8  
  7         3488  
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 10     10 1 4046 my ($class, %args) = @_;
79              
80 10 100 66     277 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 10         125 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 20778 my ($self, %args) = @_;
175              
176 8         12 my ($addr) = do {
177 8 100       32 if (defined $args{location}) {
    100          
178 5         10 $args{location};
179             }
180             elsif (defined $args{addr}) {
181 1         2 $args{addr};
182             }
183             else {
184 2         35 croak "Attribute (location) or attribute (addr) is required";
185             }
186             };
187              
188 6         25 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 4     4 1 19253 my ($self, %args) = @_;
223              
224 4         6 my ($lat, $lon) = do {
225 4 100 33     19 if (defined $args{latlng}) {
    50          
226 3         23 my @latlng = split ',', $args{latlng};
227 3 50       9 croak "Attribute (latlng) is invalid" unless @latlng == 2;
228 3         8 @latlng;
229             }
230             elsif (defined $args{lat} and defined $args{lon}) {
231 1         3 @args{qw(lat lon)};
232             }
233             else {
234 0         0 croak "Attribute (latlng) or attributes (lat) and (lon) are required";
235             }
236             };
237              
238 4         16 return $self->_request('reverse', %args, lat => $lat, lon => $lon);
239             };
240              
241              
242             sub _request {
243 10     10   22 my ($self, $type, %args) = @_;
244              
245 10         98 my $url = URI->new_abs(sprintf('json/%s/', $type), $self->{url});
246              
247 10 100       38263 if ($type eq 'forward') {
    50          
248 6         48 $url->query_param_append(addr => $args{addr});
249             } elsif ($type eq 'reverse') {
250 4         29 $url->query_param_append(lat => $args{lat}, lon => $args{lon});
251             } else {
252 0         0 croak "Unknown type for request";
253             }
254              
255 10 100       948 $url->query_param_append(key => $self->{key}) if $self->{key};
256 10         443 warn $url if DEBUG;
257              
258 10         73 my $res = $self->{ua}->get($url);
259              
260 10         183340 my $content = do {
261 10 100 66     100 if (blessed $res and $res->isa('HTTP::Response')) {
    50          
262 4 50       15 croak $res->status_line unless $res->is_success;
263 4         34 $res->decoded_content;
264             } elsif (ref $res eq 'HASH') {
265 6 50       16 croak "@{[$res->{status}, $res->{reason}]}" unless $res->{success};
  0         0  
266 6         13 $res->{content};
267             } else {
268 0         0 croak "Wrong response $res ";
269             }
270             };
271              
272 10         19 warn $content if DEBUG;
273 10 50       24 return unless $content;
274              
275 10         14 my $data = eval { $self->{parser}->decode($content) };
  10         328  
276 10 50       40 croak $content if $@;
277              
278 10 100 50     170 croak "GeocodeFarm API returned status: ", $data->{geocoding_results}{STATUS}{status}
      100        
279             if ($self->{raise_failure} and ($data->{geocoding_results}{STATUS}{status}||'') ne 'SUCCESS');
280              
281 8         55 return $data->{geocoding_results};
282             };
283              
284              
285             1;
286              
287              
288             =for readme continue
289              
290             =head1 SEE ALSO
291              
292             L
293              
294             =head1 BUGS
295              
296             If you find the bug or want to implement new features, please report it at
297             L
298              
299             The code repository is available at
300             L
301              
302             =head1 AUTHOR
303              
304             Piotr Roszatycki
305              
306             =head1 LICENSE
307              
308             Copyright (c) 2013, 2015 Piotr Roszatycki .
309              
310             This is free software; you can redistribute it and/or modify it under
311             the same terms as perl itself.
312              
313             See L