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   173837 use 5.006;
  7         14  
  7         247  
34 7     7   23 use strict;
  7         9  
  7         153  
35 7     7   20 use warnings;
  7         13  
  7         228  
36              
37             our $VERSION = '0.0400';
38              
39 7     7   28 use Carp qw(croak);
  7         8  
  7         312  
40 7     7   3058 use Encode;
  7         46341  
  7         410  
41 7     7   3653 use HTTP::Tiny;
  7         221787  
  7         276  
42 7     7   5164 use URI;
  7         23671  
  7         181  
43 7     7   2846 use URI::QueryParam;
  7         3362  
  7         157  
44 7     7   3703 use JSON;
  7         54471  
  7         33  
45 7     7   811 use Scalar::Util qw(blessed);
  7         10  
  7         401  
46              
47 7     7   23 use constant DEBUG => !! $ENV{PERL_GEO_CODER_GEOCODEFARM_DEBUG};
  7         8  
  7         3197  
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 4009 my ($class, %args) = @_;
79              
80 10 100 66     241 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         109 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 21271 my ($self, %args) = @_;
175              
176 8         12 my ($addr) = do {
177 8 100       28 if (defined $args{location}) {
    100          
178 5         10 $args{location};
179             }
180             elsif (defined $args{addr}) {
181 1         3 $args{addr};
182             }
183             else {
184 2         33 croak "Attribute (location) or attribute (addr) is required";
185             }
186             };
187              
188 6         23 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 17005 my ($self, %args) = @_;
223              
224 4         6 my ($lat, $lon) = do {
225 4 100 33     18 if (defined $args{latlng}) {
    50          
226 3         11 my @latlng = split ',', $args{latlng};
227 3 50       7 croak "Attribute (latlng) is invalid" unless @latlng == 2;
228 3         6 @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         14 return $self->_request('reverse', %args, lat => $lat, lon => $lon);
239             };
240              
241              
242             sub _request {
243 10     10   25 my ($self, $type, %args) = @_;
244              
245 10         97 my $url = URI->new_abs(sprintf('json/%s/', $type), $self->{url});
246              
247 10 100       36143 if ($type eq 'forward') {
    50          
248 6         44 $url->query_param_append(addr => $args{addr});
249             } elsif ($type eq 'reverse') {
250 4         27 $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       800 $url->query_param_append(key => $self->{key}) if $self->{key};
256 10         340 warn $url if DEBUG;
257              
258 10         71 my $res = $self->{ua}->get($url);
259              
260 10         427458 my $content = do {
261 10 100 66     91 if (blessed $res and $res->isa('HTTP::Response')) {
    50          
262 4 50       14 croak $res->status_line unless $res->is_success;
263 4         29 $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         9 $res->{content};
267             } else {
268 0         0 croak "Wrong response $res ";
269             }
270             };
271              
272 10         16 warn $content if DEBUG;
273 10 50       21 return unless $content;
274              
275 10         14 my $data = eval { $self->{parser}->decode($content) };
  10         276  
276 10 50       34 croak $content if $@;
277              
278 10 100 50     138 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         50 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