| File: | blib/lib/Geo/GeoNames.pm |
| Coverage: | 67.9% |
| line | stmt | bran | cond | sub | time | code |
|---|---|---|---|---|---|---|
| 1 | package Geo::GeoNames; | |||||
| 2 | # use utf8; | |||||
| 3 | 8 8 | 676721 11 | use v5.10; | |||
| 4 | 8 8 8 | 16 6 73 | use strict; | |||
| 5 | 8 8 8 | 14 2 132 | use warnings; | |||
| 6 | ||||||
| 7 | 8 8 8 | 12 7 186 | use Carp; | |||
| 8 | 8 8 8 | 1876 1394967 25 | use Mojo::UserAgent; | |||
| 9 | 8 8 8 | 167 8 3762 | use Scalar::Util qw/blessed/; | |||
| 10 | ||||||
| 11 | =encoding utf8 | |||||
| 12 | ||||||
| 13 - 21 | =head1 NAME Geo::GeoNames - Perform geographical queries using GeoNames Web Services =head1 VERSION Version 1.15 =cut | |||||
| 22 | ||||||
| 23 | our $VERSION = '1.15'; | |||||
| 24 | ||||||
| 25 - 69 | =head1 SYNOPSIS
use Geo::GeoNames;
my $geo = Geo::GeoNames->new(username => $ENV{'GEONAME_USER'});
# make a query based on placename
my $result = $geo->search(q => 'Fredrikstad', maxRows => 2);
# print the first result
print ' Name: ', $result->[0]->{name}, "\n";
print ' Longitude: ', $result->[0]->{lng}, "\n";
print ' Latitude: ', $result->[0]->{lat}, "\n";
# Make a query based on postcode
$result = $geo->postalcode_search(
postalcode => '1630', maxRows => 3, style => 'FULL'
);
=head1 DESCRIPTION
Before you start, get a free GeoNames account and enable it for
access to the free web service:
=over 4
=item * Get an account
Go to L<http://www.geonames.org/login>
=item * Respond to the email
=item * Login and enable your account for free access
L<http://www.geonames.org/enablefreewebservice>
=back
Provides a perl interface to the webservices found at
L<http://api.geonames.org>. That is, given a given placename or
postalcode, the module will look it up and return more information
(longitude, latitude, etc) for the given placename or postalcode.
Wikipedia lookups are also supported. If more than one match is found,
a list of locations will be returned.
=cut | |||||
| 70 | ||||||
| 71 | # use vars qw($DEBUG $CACHE); | |||||
| 72 | ||||||
| 73 | our %searches = ( | |||||
| 74 | cities => 'cities?', | |||||
| 75 | country_code => 'countrycode?type=xml&', | |||||
| 76 | country_info => 'countryInfo?', | |||||
| 77 | earthquakes => 'earthquakesJSON?', | |||||
| 78 | find_nearby_placename => 'findNearbyPlaceName?', | |||||
| 79 | find_nearby_postalcodes => 'findNearbyPostalCodes?', | |||||
| 80 | find_nearby_streets => 'findNearbyStreets?', | |||||
| 81 | find_nearby_weather => 'findNearByWeatherXML?', | |||||
| 82 | find_nearby_wikipedia => 'findNearbyWikipedia?', | |||||
| 83 | find_nearby_wikipedia_by_postalcode => 'findNearbyWikipedia?', | |||||
| 84 | find_nearest_address => 'findNearestAddress?', | |||||
| 85 | find_nearest_intersection => 'findNearestIntersection?', | |||||
| 86 | postalcode_country_info => 'postalCodeCountryInfo?', | |||||
| 87 | postalcode_search => 'postalCodeSearch?', | |||||
| 88 | search => 'search?', | |||||
| 89 | wikipedia_bounding_box => 'wikipediaBoundingBox?', | |||||
| 90 | wikipedia_search => 'wikipediaSearch?', | |||||
| 91 | get => 'get?', | |||||
| 92 | hierarchy => 'hierarchy?', | |||||
| 93 | children => 'children?', | |||||
| 94 | ); | |||||
| 95 | ||||||
| 96 | # r = required | |||||
| 97 | # o = optional | |||||
| 98 | # rc = required - only one of the fields marked with rc is allowed. At least one must be present | |||||
| 99 | # om = optional, multiple entries allowed | |||||
| 100 | # d = deprecated - will be removed in later versions | |||||
| 101 | our %valid_parameters = ( | |||||
| 102 | search => { | |||||
| 103 | 'q' => 'rc', | |||||
| 104 | name => 'rc', | |||||
| 105 | name_equals => 'rc', | |||||
| 106 | maxRows => 'o', | |||||
| 107 | startRow => 'o', | |||||
| 108 | country => 'om', | |||||
| 109 | continentCode => 'o', | |||||
| 110 | adminCode1 => 'o', | |||||
| 111 | adminCode2 => 'o', | |||||
| 112 | adminCode3 => 'o', | |||||
| 113 | fclass => 'omd', | |||||
| 114 | featureClass => 'om', | |||||
| 115 | featureCode => 'om', | |||||
| 116 | lang => 'o', | |||||
| 117 | type => 'o', | |||||
| 118 | style => 'o', | |||||
| 119 | isNameRequired => 'o', | |||||
| 120 | tag => 'o', | |||||
| 121 | username => 'r', | |||||
| 122 | name_startsWith => 'o', # TODO - should this be rc? | |||||
| 123 | countryBias => 'o', | |||||
| 124 | cities => 'om', | |||||
| 125 | operator => 'o', | |||||
| 126 | searchlang => 'o', | |||||
| 127 | charset => 'o', | |||||
| 128 | fuzzy => 'o', | |||||
| 129 | north => 'o', | |||||
| 130 | west => 'o', | |||||
| 131 | east => 'o', | |||||
| 132 | south => 'o', | |||||
| 133 | orderby => 'o', | |||||
| 134 | }, | |||||
| 135 | postalcode_search => { | |||||
| 136 | postalcode => 'rc', | |||||
| 137 | placename => 'rc', | |||||
| 138 | country => 'o', | |||||
| 139 | maxRows => 'o', | |||||
| 140 | style => 'o', | |||||
| 141 | username => 'r', | |||||
| 142 | }, | |||||
| 143 | find_nearby_postalcodes => { | |||||
| 144 | lat => 'r', | |||||
| 145 | lng => 'r', | |||||
| 146 | radius => 'o', | |||||
| 147 | maxRows => 'o', | |||||
| 148 | style => 'o', | |||||
| 149 | country => 'o', | |||||
| 150 | username => 'r', | |||||
| 151 | }, | |||||
| 152 | postalcode_country_info => { | |||||
| 153 | username => 'r', | |||||
| 154 | }, | |||||
| 155 | find_nearby_placename => { | |||||
| 156 | lat => 'r', | |||||
| 157 | lng => 'r', | |||||
| 158 | radius => 'o', | |||||
| 159 | style => 'o', | |||||
| 160 | maxRows => 'o', | |||||
| 161 | lang => 'o', | |||||
| 162 | cities => 'o', | |||||
| 163 | username => 'r', | |||||
| 164 | }, | |||||
| 165 | find_nearest_address => { | |||||
| 166 | lat => 'r', | |||||
| 167 | lng => 'r', | |||||
| 168 | username => 'r', | |||||
| 169 | }, | |||||
| 170 | find_nearest_intersection => { | |||||
| 171 | lat => 'r', | |||||
| 172 | lng => 'r', | |||||
| 173 | username => 'r', | |||||
| 174 | }, | |||||
| 175 | find_nearby_streets => { | |||||
| 176 | lat => 'r', | |||||
| 177 | lng => 'r', | |||||
| 178 | username => 'r', | |||||
| 179 | }, | |||||
| 180 | find_nearby_wikipedia => { | |||||
| 181 | lang => 'o', | |||||
| 182 | lat => 'r', | |||||
| 183 | lng => 'r', | |||||
| 184 | radius => 'o', | |||||
| 185 | maxRows => 'o', | |||||
| 186 | country => 'o', | |||||
| 187 | username => 'r', | |||||
| 188 | }, | |||||
| 189 | find_nearby_wikipedia_by_postalcode => { | |||||
| 190 | postalcode => 'r', | |||||
| 191 | country => 'r', | |||||
| 192 | radius => 'o', | |||||
| 193 | maxRows => 'o', | |||||
| 194 | username => 'r', | |||||
| 195 | }, | |||||
| 196 | wikipedia_search => { | |||||
| 197 | 'q' => 'r', | |||||
| 198 | lang => 'o', | |||||
| 199 | title => 'o', | |||||
| 200 | maxRows => 'o', | |||||
| 201 | username => 'r', | |||||
| 202 | }, | |||||
| 203 | wikipedia_bounding_box => { | |||||
| 204 | south => 'r', | |||||
| 205 | north => 'r', | |||||
| 206 | east => 'r', | |||||
| 207 | west => 'r', | |||||
| 208 | lang => 'o', | |||||
| 209 | maxRows => 'o', | |||||
| 210 | username => 'r', | |||||
| 211 | }, | |||||
| 212 | country_info => { | |||||
| 213 | country => 'o', | |||||
| 214 | lang => 'o', | |||||
| 215 | username => 'r', | |||||
| 216 | }, | |||||
| 217 | country_code => { | |||||
| 218 | lat => 'r', | |||||
| 219 | lng => 'r', | |||||
| 220 | lang => 'o', | |||||
| 221 | radius => 'o', | |||||
| 222 | username => 'r', | |||||
| 223 | }, | |||||
| 224 | find_nearby_weather => { | |||||
| 225 | lat => 'r', | |||||
| 226 | lng => 'r', | |||||
| 227 | username => 'r', | |||||
| 228 | }, | |||||
| 229 | cities => { | |||||
| 230 | north => 'r', | |||||
| 231 | south => 'r', | |||||
| 232 | east => 'r', | |||||
| 233 | west => 'r', | |||||
| 234 | lang => 'o', | |||||
| 235 | maxRows => 'o', | |||||
| 236 | username => 'r', | |||||
| 237 | }, | |||||
| 238 | earthquakes => { | |||||
| 239 | north => 'r', | |||||
| 240 | south => 'r', | |||||
| 241 | east => 'r', | |||||
| 242 | west => 'r', | |||||
| 243 | date => 'o', | |||||
| 244 | minMagnitude => 'o', | |||||
| 245 | maxRows => 'o', | |||||
| 246 | username => 'r', | |||||
| 247 | }, | |||||
| 248 | get => { | |||||
| 249 | geonameId => 'r', | |||||
| 250 | lang => 'o', | |||||
| 251 | style => 'o', | |||||
| 252 | username => 'r', | |||||
| 253 | }, | |||||
| 254 | hierarchy => { | |||||
| 255 | geonameId => 'r', | |||||
| 256 | username => 'r', | |||||
| 257 | style => 'o', | |||||
| 258 | }, | |||||
| 259 | children => { | |||||
| 260 | geonameId => 'r', | |||||
| 261 | username => 'r', | |||||
| 262 | style => 'o', | |||||
| 263 | }, | |||||
| 264 | ); | |||||
| 265 | ||||||
| 266 | sub new { | |||||
| 267 | 10 | 111082 | my $class = shift; | |||
| 268 | 10 0 | 20 0 | my %args = (ref($_[0]) eq 'HASH') ? %{$_[0]} : @_; | |||
| 269 | ||||||
| 270 | 10 | 14 | if(!defined($class)) { | |||
| 271 | # Using Geo::GeoNames->new(), not Geo::GeoNames::new() | |||||
| 272 | # carp(__PACKAGE__, ' use ->new() not ::new() to instantiate'); | |||||
| 273 | # return; | |||||
| 274 | ||||||
| 275 | # FIXME: this only works when no arguments are given | |||||
| 276 | 0 | 0 | $class = __PACKAGE__; | |||
| 277 | } elsif(ref($class)) { | |||||
| 278 | # clone the given object | |||||
| 279 | 0 0 | 0 0 | return bless { %{$class}, %args }, ref($class); | |||
| 280 | } | |||||
| 281 | ||||||
| 282 | 10 | 134 | croak <<"HERE" unless length $args{username}; | |||
| 283 | You must specify a GeoNames username to use Geo::GeoNames. | |||||
| 284 | See http://www.geonames.org/export/web-services.html | |||||
| 285 | HERE | |||||
| 286 | ||||||
| 287 | 8 | 16 | my $self = bless { _functions => \%searches, %args }, $class; | |||
| 288 | ||||||
| 289 | # $self->username( $args{username} ); | |||||
| 290 | 8 | 18 | $self->url( $args{url} // $self->default_url() ); | |||
| 291 | ||||||
| 292 | croak 'Illegal ua object, needs either a Mojo::UserAgent or an LWP::UserAgent derived object' | |||||
| 293 | 8 | 178 | if exists $args{ua} && !(ref $args{ua} && blessed($args{ua}) && ( $args{ua}->isa('Mojo::UserAgent') || $args{ua}->isa('LWP::UserAgent') ) ); | |||
| 294 | 5 | 20 | $self->ua($args{ua} || $self->default_ua ); | |||
| 295 | ||||||
| 296 | # (exists($args{debug})) ? $DEBUG = $args{debug} : 0; | |||||
| 297 | # (exists($args{cache})) ? $CACHE = $args{cache} : 0; | |||||
| 298 | # $self->{_functions} = \%searches; | |||||
| 299 | ||||||
| 300 | 5 | 9 | return $self; | |||
| 301 | } | |||||
| 302 | ||||||
| 303 | sub username { | |||||
| 304 | 1 | 1 | my( $self, $username ) = @_; | |||
| 305 | ||||||
| 306 | 1 | 1 | $self->{username} = $username if @_ == 2; | |||
| 307 | ||||||
| 308 | 1 | 10 | $self->{username}; | |||
| 309 | } | |||||
| 310 | ||||||
| 311 - 324 | =head2 ua Accessor method to get and set UserAgent object used internally. You can call I<env_proxy> for example, to get the proxy information from environment variables: $geo_coder->ua()->env_proxy(1); You can also set your own User-Agent object: use LWP::UserAgent::Throttled; $geo_coder->ua(LWP::UserAgent::Throttled->new()); =cut | |||||
| 325 | ||||||
| 326 | sub ua { | |||||
| 327 | 5 | 4 | my $self = shift; | |||
| 328 | 5 | 4 | if (@_) { | |||
| 329 | 5 | 6 | $self->{ua} = shift; | |||
| 330 | } | |||||
| 331 | 5 | 5 | $self->{ua}; | |||
| 332 | } | |||||
| 333 | ||||||
| 334 | sub default_ua | |||||
| 335 | { | |||||
| 336 | 3 | 9 | my $ua = Mojo::UserAgent->new(); | |||
| 337 | 3 0 | 15 0 | $ua->on( error => sub { carp "Can't get request" } ); | |||
| 338 | 3 | 16 | return $ua; | |||
| 339 | } | |||||
| 340 | ||||||
| 341 | 7 | 11 | sub default_url { 'http://api.geonames.org' } | |||
| 342 | ||||||
| 343 | sub url { | |||||
| 344 | 9 | 8 | my( $self, $url ) = @_; | |||
| 345 | ||||||
| 346 | 9 | 14 | $self->{url} = $url if @_ == 2; | |||
| 347 | ||||||
| 348 | 9 | 8 | $self->{url}; | |||
| 349 | } | |||||
| 350 | ||||||
| 351 | sub _build_request_url { | |||||
| 352 | 1 | 1 | my( $self, $request, @args ) = @_; | |||
| 353 | 1 | 3 | my $hash = { @args, username => $self->username }; | |||
| 354 | 1 | 1 | my $request_url = $self->url . '/' . $searches{$request}; | |||
| 355 | ||||||
| 356 | # check to see that mandatory arguments are present | |||||
| 357 | 1 | 1 | my $conditional_mandatory_flag = 0; | |||
| 358 | 1 | 1 | my $conditional_mandatory_required = 0; | |||
| 359 | 1 1 | 0 2 | foreach my $arg (keys %{$valid_parameters{$request}}) { | |||
| 360 | 31 | 17 | my $flags = $valid_parameters{$request}->{$arg}; | |||
| 361 | 31 | 21 | if($flags =~ /d/ && exists($hash->{$arg})) { | |||
| 362 | 0 | 0 | carp("Argument $arg is deprecated."); | |||
| 363 | } | |||||
| 364 | 31 | 13 | $flags =~ s/d//g; | |||
| 365 | 31 | 25 | if($flags eq 'r' && !exists($hash->{$arg})) { | |||
| 366 | 0 | 0 | carp("Mandatory argument $arg is missing!"); | |||
| 367 | } | |||||
| 368 | 31 | 29 | if($flags !~ /m/ && exists($hash->{$arg}) && ref($hash->{$arg})) { | |||
| 369 | 0 | 0 | carp("Argument $arg cannot have multiple values."); | |||
| 370 | } | |||||
| 371 | 31 | 22 | if($flags eq 'rc') { | |||
| 372 | 3 | 2 | $conditional_mandatory_required = 1; | |||
| 373 | 3 | 1 | if(exists($hash->{$arg})) { | |||
| 374 | 1 | 1 | $conditional_mandatory_flag++; | |||
| 375 | } | |||||
| 376 | } | |||||
| 377 | } | |||||
| 378 | ||||||
| 379 | 1 | 2 | if($conditional_mandatory_required == 1 && $conditional_mandatory_flag != 1) { | |||
| 380 | 0 | 0 | carp("Invalid number of mandatory arguments (there can be only one)"); | |||
| 381 | } | |||||
| 382 | 1 | 3 | foreach my $key (sort keys(%$hash)) { | |||
| 383 | 2 | 2 | carp("Invalid argument $key") if(!defined($valid_parameters{$request}->{$key})); | |||
| 384 | 2 0 | 3 0 | my @vals = ref($hash->{$key}) ? @{$hash->{$key}} : $hash->{$key}; | |||
| 385 | 8 8 8 | 24 8 4068 | no warnings 'uninitialized'; | |||
| 386 | 2 2 | 1 4 | $request_url .= join('', map { "$key=$_&" } sort @vals ); | |||
| 387 | } | |||||
| 388 | ||||||
| 389 | 1 | 3 | chop($request_url); # lose the trailing & | |||
| 390 | 1 | 1 | return $request_url; | |||
| 391 | } | |||||
| 392 | ||||||
| 393 | sub _parse_xml_result { | |||||
| 394 | 3 | 84898 | require XML::Simple; | |||
| 395 | 3 | 7325 | my( $self, $geonamesresponse, $single_result ) = @_; | |||
| 396 | 3 | 3 | my @result; | |||
| 397 | 3 | 7 | my $xmlsimple = XML::Simple->new; | |||
| 398 | 3 | 83 | my $xml = $xmlsimple->XMLin( $geonamesresponse, KeyAttr => [], ForceArray => 1 ); | |||
| 399 | ||||||
| 400 | 3 | 35230 | if ($xml->{'status'}) { | |||
| 401 | 1 | 2 | carp 'GeoNames error: ', $xml->{'status'}->[0]->{message}; | |||
| 402 | 1 | 7 | return []; | |||
| 403 | } | |||||
| 404 | ||||||
| 405 | 2 | 8 | $xml = { geoname => [ $xml ], totalResultsCount => '1' } if $single_result; | |||
| 406 | ||||||
| 407 | 2 | 2 | my $i = 0; | |||
| 408 | 2 2 | 1 4 | foreach my $element (keys %{$xml}) { | |||
| 409 | 5 | 4 | next if (ref($xml->{$element}) ne 'ARRAY'); | |||
| 410 | 3 3 | 2 3 | foreach my $list (@{$xml->{$element}}) { | |||
| 411 | 3 | 3 | next if (ref($list) ne 'HASH'); | |||
| 412 | 2 2 | 2 5 | foreach my $attribute (%{$list}) { | |||
| 413 | 96 | 87 | next if !defined($list->{$attribute}->[0]); | |||
| 414 | 48 48 | 16 55 | $result[$i]->{$attribute} = (scalar @{$list->{$attribute}} == 1 ? $list->{$attribute}->[0] : $list->{$attribute}); | |||
| 415 | } | |||||
| 416 | 2 | 3 | $i++; | |||
| 417 | } | |||||
| 418 | } | |||||
| 419 | 2 | 16 | return \@result; | |||
| 420 | } | |||||
| 421 | ||||||
| 422 | sub _parse_json_result { | |||||
| 423 | 1 | 26 | require JSON::MaybeXS; | |||
| 424 | 1 | 1 | my( $self, $geonamesresponse ) = @_; | |||
| 425 | ||||||
| 426 | 1 | 220 | return JSON::MaybeXS->new->utf8->decode($geonamesresponse); | |||
| 427 | } | |||||
| 428 | ||||||
| 429 | sub _parse_text_result { | |||||
| 430 | 0 | 0 | my( $self, $geonamesresponse ) = @_; | |||
| 431 | 0 | 0 | my @result; | |||
| 432 | 0 | 0 | $result[0]->{Result} = $geonamesresponse; | |||
| 433 | 0 | 0 | return \@result; | |||
| 434 | } | |||||
| 435 | ||||||
| 436 | sub _request { | |||||
| 437 | 1 | 0 | my ($self, $request_url) = @_; | |||
| 438 | ||||||
| 439 | 1 | 2 | if($self->{'logger'}) { | |||
| 440 | 0 | 0 | $self->{'logger'}->trace('> ', ref($self), ": _request: $request_url"); | |||
| 441 | } | |||||
| 442 | 1 | 1 | my $res = $self->{ua}->get($request_url); | |||
| 443 | ||||||
| 444 | # Handle Mojo::UserAgent response | |||||
| 445 | 1 | 610777 | if($res->can('res')) { | |||
| 446 | 1 | 5 | my $response = $res->res(); | |||
| 447 | 1 | 2 | unless($response->is_success) { | |||
| 448 | 0 | 0 | my $code = $response->code() || 'unknown'; | |||
| 449 | 0 | 0 | my $message = $response->message() || 'HTTP request failed'; | |||
| 450 | 0 | 0 | carp "HTTP request failed: $code $message for URL: $request_url"; | |||
| 451 | 0 | 0 | return undef; | |||
| 452 | } | |||||
| 453 | 1 | 7 | return $response; | |||
| 454 | } | |||||
| 455 | ||||||
| 456 | # Handle LWP::UserAgent response | |||||
| 457 | 0 | 0 | unless ($res->is_success()) { | |||
| 458 | 0 | 0 | my $code = $res->code() || 'unknown'; | |||
| 459 | 0 | 0 | my $message = $res->message() || 'HTTP request failed'; | |||
| 460 | 0 | 0 | carp "HTTP request failed: $code $message for URL: $request_url"; | |||
| 461 | 0 | 0 | return undef; | |||
| 462 | } | |||||
| 463 | ||||||
| 464 | 0 | 0 | return $res->can('res') ? $res->res() : $res; | |||
| 465 | } | |||||
| 466 | ||||||
| 467 | sub _do_search { | |||||
| 468 | 1 | 1 | my( $self, $searchtype, @args ) = @_; | |||
| 469 | ||||||
| 470 | 1 | 1 | my $request_url = $self->_build_request_url( $searchtype, @args ); | |||
| 471 | 1 | 2 | my $response = $self->_request($request_url); | |||
| 472 | ||||||
| 473 | # Return empty array if request failed | |||||
| 474 | 1 | 2 | return [] unless defined $response; | |||
| 475 | ||||||
| 476 | # Verify HTTP status code | |||||
| 477 | 1 | 1 | my $status_code = $response->code(); | |||
| 478 | 1 | 3 | unless ($status_code >= 200 && $status_code < 300) { | |||
| 479 | 0 | 0 | carp "HTTP error: received status code $status_code for URL: $request_url"; | |||
| 480 | 0 | 0 | return []; | |||
| 481 | } | |||||
| 482 | ||||||
| 483 | # check mime-type to determine which parse method to use. | |||||
| 484 | # we accept text/xml, text/plain (how do see if it is JSON or not?) | |||||
| 485 | 1 | 5 | my $mime_type = $response->headers->content_type || ''; | |||
| 486 | ||||||
| 487 | # Extract just the base MIME type without parameters (e.g., charset) | |||||
| 488 | 1 | 7 | my $base_mime_type = $mime_type; | |||
| 489 | 1 | 2 | $base_mime_type =~ s/;.*$//; # Remove everything after semicolon | |||
| 490 | 1 | 2 | $base_mime_type =~ s/^\s+|\s+$//g; # Trim whitespace | |||
| 491 | ||||||
| 492 | 1 | 4 | my $body = $response->can('body') ? $response->body() : $response->content; | |||
| 493 | ||||||
| 494 | # Check for XML response | |||||
| 495 | 1 | 8 | if($base_mime_type eq 'text/xml' || $base_mime_type eq 'application/xml') { | |||
| 496 | 1 | 2 | return $self->_parse_xml_result( $body, $searchtype eq 'get' ); | |||
| 497 | } | |||||
| 498 | ||||||
| 499 | # Check for JSON response | |||||
| 500 | 0 | 0 | if($base_mime_type eq 'application/json') { | |||
| 501 | # a JSON object always start with a left-brace { | |||||
| 502 | # according to http://json.org/ | |||||
| 503 | 0 | 0 | if( $body =~ m/\A\{/ ) { | |||
| 504 | 0 | 0 | if ($response->can('json')) { | |||
| 505 | 0 | 0 | return $response->json; | |||
| 506 | } else { | |||||
| 507 | 0 | 0 | return $self->_parse_json_result( $body ); | |||
| 508 | } | |||||
| 509 | } else { | |||||
| 510 | 0 | 0 | return $self->_parse_text_result( $body ); | |||
| 511 | } | |||||
| 512 | } | |||||
| 513 | ||||||
| 514 | # Unexpected MIME type | |||||
| 515 | 0 | 0 | if($base_mime_type eq 'text/plain') { | |||
| 516 | 0 | 0 | carp "Unexpected mime type [text/plain]. Response body: ", substr($body, 0, 200); | |||
| 517 | } elsif($base_mime_type eq 'text/html') { | |||||
| 518 | 0 | 0 | carp "Received HTML response instead of expected data format. This may indicate an error page or service unavailability."; | |||
| 519 | } else { | |||||
| 520 | 0 | 0 | carp "Unsupported mime type [$mime_type]. Expected text/xml or application/json."; | |||
| 521 | } | |||||
| 522 | } | |||||
| 523 | ||||||
| 524 | sub geocode { | |||||
| 525 | 0 | 0 | my( $self, $q ) = @_; | |||
| 526 | 0 | 0 | $self->search( 'q' => $q ); | |||
| 527 | } | |||||
| 528 | ||||||
| 529 | sub AUTOLOAD { | |||||
| 530 | 1 | 4 | my $self = shift; | |||
| 531 | # my $type = ref($self) || croak "$self is not an object"; | |||||
| 532 | 1 | 2 | ref($self) || croak "$self is not an object"; | |||
| 533 | 1 | 0 | my $name = our $AUTOLOAD; | |||
| 534 | 1 | 3 | $name =~ s/.*://; | |||
| 535 | ||||||
| 536 | 1 | 1 | unless (exists $self->{_functions}->{$name}) { | |||
| 537 | 0 | 0 | croak "No such method '$AUTOLOAD'"; | |||
| 538 | } | |||||
| 539 | ||||||
| 540 | 1 | 2 | return($self->_do_search($name, @_)); | |||
| 541 | } | |||||
| 542 | ||||||
| 543 | 8 | 1855 | sub DESTROY { 1 } | |||
| 544 | ||||||
| 545 | 1; | |||||
| 546 | ||||||