File Coverage

blib/lib/Geo/Coder/XYZ.pm
Criterion Covered Total %
statement 65 90 72.2
branch 11 30 36.6
condition 4 11 36.3
subroutine 12 14 85.7
pod 4 4 100.0
total 96 149 64.4


line stmt bran cond sub pod time code
1             package Geo::Coder::XYZ;
2              
3 7     7   1775658 use strict;
  7         13  
  7         340  
4 7     7   42 use warnings;
  7         14  
  7         361  
5              
6 7     7   62 use Carp;
  7         10  
  7         565  
7 7     7   3837 use Encode;
  7         128407  
  7         883  
8 7     7   4064 use JSON::MaybeXS;
  7         97706  
  7         577  
9 7     7   3625 use HTTP::Request;
  7         170036  
  7         355  
10 7     7   5277 use LWP::UserAgent;
  7         285733  
  7         367  
11 7     7   4794 use LWP::Protocol::https;
  7         1278809  
  7         654  
12 7     7   4827 use Params::Get;
  7         111252  
  7         536  
13 7     7   77 use URI;
  7         21  
  7         8925  
14              
15             =head1 NAME
16              
17             Geo::Coder::XYZ - Provides a Geo-Coding functionality using L
18              
19             =head1 VERSION
20              
21             Version 0.10
22              
23             =cut
24              
25             our $VERSION = '0.10';
26              
27             =head1 SYNOPSIS
28              
29             use Geo::Coder::XYZ;
30              
31             my $geo_coder = Geo::Coder::XYZ->new();
32             my $location = $geo_coder->geocode(location => '10 Downing St., London, UK');
33              
34             =head1 DESCRIPTION
35              
36             Geo::Coder::XYZ provides an interface to geocode.xyz, a free Geo-Coding database covering many countries.
37              
38             =head1 METHODS
39              
40             =head2 new
41              
42             $geo_coder = Geo::Coder::XYZ->new();
43             my $ua = LWP::UserAgent->new();
44             $ua->env_proxy(1);
45             $geo_coder = Geo::Coder::XYZ->new(ua => $ua);
46              
47             =cut
48              
49             sub new {
50 3     3 1 618996 my $proto = shift;
51 3   66     23 my $class = ref($proto) || $proto;
52              
53             # Use Geo::Coder::XYZ->new(), not Geo::Coder::XYZ::new()
54 3 100       12 if(!defined($class)) {
55 1         23 carp(__PACKAGE__, ' use ->new() not ::new() to instantiate');
56 1         1693 return;
57             }
58              
59 2         22 my $params = Params::Get::get_params(undef, \@_);
60              
61 2         81 my $ua = $params->{ua};
62 2 50       11 if(!defined($ua)) {
63 2         37 $ua = LWP::UserAgent->new(agent => __PACKAGE__ . "/$VERSION");
64 2         8194 $ua->default_header(accept_encoding => 'gzip,deflate');
65             }
66 2 50       203 if(!defined($params->{'host'})) {
67 2         12 $ua->ssl_opts(verify_hostname => 0); # Yuck
68             }
69 2   50     100 my $host = $params->{host} || 'geocode.xyz';
70              
71 2         47 return bless { ua => $ua, host => $host }, $class;
72             }
73              
74             =head2 geocode
75              
76             $location = $geo_coder->geocode(location => $location);
77              
78             print 'Latitude: ', $location->{'latt'}, "\n";
79             print 'Longitude: ', $location->{'longt'}, "\n";
80              
81             @locations = $geo_coder->geocode('Portland, USA');
82             print 'There are Portlands in ', join (', ', map { $_->{'state'} } @locations), "\n";
83              
84             =cut
85              
86             sub geocode {
87 2     2 1 4301 my $self = shift;
88 2         12 my $params = Params::Get::get_params('location', \@_);
89              
90             my $location = $params->{location}
91 1 50       75 or Carp::croak('Usage: geocode(location => $location)');
92              
93             # Fail when the input is just a set of numbers
94 1 50       9 if($params->{'location'} !~ /\D/) {
95 0         0 Carp::croak('Usage: ', __PACKAGE__, ": invalid input to geocode(), $params->{location}");
96 0         0 return;
97             }
98              
99 1 50       6 if (Encode::is_utf8($location)) {
100 0         0 $location = Encode::encode_utf8($location);
101             }
102              
103 1         18 my $uri = URI->new("https://$self->{host}/");
104 1 50       9468 if($location =~ /(.+),\s*England$/i) {
105 0         0 $location = "$1, United Kingdom"; # geocode.xyz gets confused between England and New England
106             }
107 1         8 $location =~ s/\s/+/g;
108 1         7 my %query_parameters = ('locate' => $location, 'json' => 1);
109 1 50       3 if(wantarray) {
110             # moreinfo is needed to find alternatives when the given location is ambiguous
111 0         0 $query_parameters{'moreinfo'} = 1;
112             }
113 1         11 $uri->query_form(%query_parameters);
114 1         211 my $url = $uri->as_string();
115              
116 1         15 my $res = $self->{ua}->get($url);
117              
118 1 50       626304 if ($res->is_error) {
119 0         0 Carp::carp("API returned error: on $url ", $res->status_line());
120 0         0 return { };
121             }
122              
123 1         22 my $json = JSON::MaybeXS->new()->utf8();
124 1         21 my $rc;
125 1         2 eval {
126 1         8 $rc = $json->decode($res->decoded_content());
127             };
128 1 50       1182 if(!defined($rc)) {
129 0 0       0 if($@) {
130 0         0 Carp::carp("$url: $@");
131 0         0 return { };
132             }
133 0         0 Carp::carp("$url: can't decode the JSON ", $res->content());
134 0         0 return { };
135             }
136              
137 1 0 33     5 if($rc->{'otherlocations'} && $rc->{'otherlocations'}->{'loc'} &&
      0        
138             (ref($rc->{'otherlocations'}->{'loc'}) eq 'ARRAY')) {
139 0         0 my @rc = @{$rc->{'otherlocations'}->{'loc'}};
  0         0  
140 0 0       0 if(wantarray) {
141 0         0 return @rc;
142             }
143 0         0 return $rc[0];
144             }
145 1         51 return $rc;
146              
147             # my @results = @{ $data || [] };
148             # wantarray ? @results : $results[0];
149             }
150              
151             =head2 ua
152              
153             Accessor method to get and set UserAgent object used internally. You
154             can call I for example, to get the proxy information from
155             environment variables:
156              
157             $geo_coder->ua()->env_proxy(1);
158              
159             You can also set your own User-Agent object:
160              
161             use LWP::UserAgent::Throttled;
162             my $ua = LWP::UserAgent::Throttled->new();
163             $ua->throttle({ 'geocode.xyz' => 2 });
164             $geo_coder->ua($ua);
165              
166             =cut
167              
168             sub ua {
169 0     0 1   my $self = shift;
170 0 0         if (@_) {
171 0           $self->{ua} = shift;
172             }
173 0           $self->{ua};
174             }
175              
176             =head2 reverse_geocode
177              
178             $location = $geo_coder->reverse_geocode(latlng => '37.778907,-122.39732');
179              
180             Similar to geocode except it expects a latitude/longitude parameter.
181              
182             =cut
183              
184             sub reverse_geocode
185             {
186 0     0 1   my $self = shift;
187 0           my $params = Params::Get::get_params('latlng', \@_);
188              
189 0 0         my $latlng = $params->{'latlng'}
190             or Carp::carp('Usage: reverse_geocode(latlng => $latlng)');
191              
192 0           return $self->geocode(location => $latlng, reverse => 1);
193             }
194              
195             =head1 SUPPORT
196              
197             This module is provided as-is without any warranty.
198              
199             =head1 AUTHOR
200              
201             Nigel Horne, C<< >>
202              
203             Based on L.
204              
205             This library is free software; you can redistribute it and/or modify
206             it under the same terms as Perl itself.
207              
208             Lots of thanks to the folks at geocode.xyz.
209              
210             =head1 SEE ALSO
211              
212             L, L
213              
214             =head1 LICENSE AND COPYRIGHT
215              
216             Copyright 2017-2025 Nigel Horne.
217              
218             This program is released under the following licence: GPL2
219              
220             =cut
221              
222             1;