package WWW::MySociety::Gaze;
use warnings;
use strict;
use Carp;
use LWP::UserAgent;
use HTML::Tiny;
use Text::CSV;
use constant SERVICE => 'http://gaze.mysociety.org/gaze-rest';
=head1 NAME
WWW::MySociety::Gaze - An interface to MySociety.org's Gazetteer service
=head1 VERSION
This document describes WWW::MySociety::Gaze version 0.02
=cut
our $VERSION = '0.02';
=head1 SYNOPSIS
use WWW::MySociety::Gaze;
=head1 DESCRIPTION
MySociety.org Gaze is a REST based gazetteer service. You can find out
more about it here:
L
C is a Perl wrapper around Gaze.
=head1 INTERFACE
=head2 C<< new >>
Create a new C.
=cut
sub new {
my $class = shift;
return bless {}, $class;
}
=head2 C<< get_country_from_ip( $ip ) >>
Guess the country of location of a host from its dotted quad IP address.
Returns an ISO country code or C if the country code is unknown.
my $gaze = WWW::MySociety::Gaze->new;
my $country = $gaze->get_country_from_ip( '82.152.157.85' );
=cut
sub get_country_from_ip {
my $self = shift;
croak "Need an IP address"
unless @_ == 1;
my $ip = shift;
croak "IP address must be a dotted quad"
unless $ip =~ /^(?:\d{1,3}\.){3}\d{1,3}$/;
my $answer = $self->_request( 'get_country_from_ip', ip => $ip );
chomp $answer;
return $answer eq '' ? undef : $answer;
}
=head2 C<< get_find_places_countries >>
Return the list of countries for which C has a gazetteer
available.
Takes no arguments, returns a list of ISO country codes.
my $gaze = WWW::MySociety::Gaze->new;
my @countries = $gaze->get_find_places_countries;
=cut
sub get_find_places_countries {
my $self = shift;
return $self->_lines(
$self->_request( 'get_find_places_countries' ) );
}
=head2 C<< find_places >>
Lookup a location in the gazetteer. Takes a number of key, value pairs
as follows:
=head3 Parameters
=over
=item C
ISO country code of country in which to search for places
=item C
State in which to search for places; presently this is only meaningful
for country=US (United States), in which case it should be a
conventional two-letter state code (AZ, CA, NY etc.); optional
=item C
Query term input by the user; must be at least two characters long
=item C
Largest number of results to return, from 1 to 100 inclusive; optional;
default 10
=item C
Minimum match score of returned results, from 1 to 100 inclusive;
optional; default 0
=back
Returns a list of hash references. Each hash has the following fields:
=over
=item C
Name of the place described by this row
=item C
Blank, or the name of an administrative region in which this place lies
(for instance, a county)
=item C
A reference to a (possibly empty) array of nearby placenames.
=item C
WGS-84 latitude of place in decimal degrees, north-positive
=item C
WGS-84 longitude of place in decimal degrees, east-positive
=item C
Blank, or containing state code for US
=item C
Match score for this place, from 0 to 100 inclusive
=back
=cut
sub find_places {
my $self = shift;
croak "Need arguments as key, value pairs"
unless @_ and ( @_ % 2 == 0 );
return $self->_csv_to_hashes(
$self->_request( 'find_places', @_ ),
sub {
my $rec = shift;
$rec->{Near} = [ split /\s*,\s*/, $rec->{Near} ];
return $rec;
}
);
}
=head2 C<< get_population_density( $lat, $lon ) >>
Given a latitude, longitude pair return an estimate of the population
density at (lat, lon), in persons per square kilometer.
=cut
sub get_population_density {
my ( $self, $lat, $lon ) = @_;
my @density = $self->_lines(
$self->_request(
'get_population_density',
lat => $lat,
lon => $lon
)
);
return shift @density;
}
=head2 C<< get_radius_containing_population >>
Return an estimate of the smallest radius around (lat, lon) containing
at least number persons, or maximum, if that value is smaller. Takes key
value parameters:
=over
=item C
WGS84 latitude, in decimal degrees
=item C
WGS84 longitude, in decimal degrees
=item C
number of persons
=item C
largest radius returned, in kilometers; optional; default 150
=back
=cut
sub get_radius_containing_population {
my $self = shift;
croak "Need arguments as key, value pairs"
unless @_ and ( @_ % 2 == 0 );
my @radius = $self->_lines(
$self->_request( 'get_population_density', @_ ) );
return shift @radius;
}
=head2 C<< get_country_bounding_coords >>
Get the bounding box of a country given its ISO country code. Returns a
four element list containing max_lat, min_lat, max_lon, min_lon.
my @bb = $gaze->get_country_bounding_coords( 'GB' );
=cut
sub get_country_bounding_coords {
my ( $self, $country ) = @_;
my @bb = $self->_lines(
$self->_request(
'get_country_bounding_coords', country => $country
)
);
return split /\s+/, shift @bb;
}
=head2 C<< get_places_near >>
Get a list of places near a specific location. Takes a list of name,
value pairs like this:
=over
=item C
WGS84 latitude, in north-positive decimal degrees
=item C
WGS84 longitude, in east-positive decimal degrees
=item C
distance in kilometres
=item C
number of persons to calculate circle radius
=item C
maximum radius to return (default 150km)
=item C
ISO country code of country to limit results to (optional)
=back
Returns a list of hash references like this:
=over
=item C
Name of the nearby place.
=item C
Distance from the base place.
=item C
Latitude of the nearby place.
=item C
Longitude of the nearby place.
=item C
Country of the nearby place.
=item C
State of the nearby place (currently US only).
=back
=cut
sub get_places_near {
my $self = shift;
croak "Need arguments as key, value pairs"
unless @_ and ( @_ % 2 == 0 );
return $self->_csv_to_hashes(
$self->_request( 'get_places_near', @_ ),
sub {
my $rec = shift;
$rec->{Distance} ||= 0;
return $rec;
}
);
}
sub _request {
my $self = shift;
croak
"Need a verb and optionally a list of argument key value pairs"
unless @_ >= 1 and @_ % 2;
my ( $verb, %args ) = @_;
my $ua = $self->{_ua} ||= LWP::UserAgent->new;
my $uri = SERVICE . '?'
. HTML::Tiny->new->query_encode( { %args, f => $verb } );
my $resp = $ua->get( $uri );
croak $resp->status_line if $resp->is_error;
return $resp->content;
}
sub _lines {
my ( $self, $text ) = @_;
$text =~ s/\r//g;
chomp $text;
return split /\n/, $text;
}
sub _csv_to_hashes {
my ( $self, $text, $cook ) = @_;
my @lines = $self->_lines( $text );
my $csv = Text::CSV->new;
my $csv_line = sub {
return unless @lines;
my $line = shift @lines;
my $status = $csv->parse( $line );
croak "Can't parse $line: " . $csv->error_diag
unless $status;
return $csv->fields;
};
$cook ||= sub { shift };
my @names = $csv_line->();
my @data = ();
while ( my @fields = $csv_line->() ) {
my %row;
@row{@names} = @fields;
push @data, $cook->( \%row );
}
return @data;
}
1;
__END__
=head1 CONFIGURATION AND ENVIRONMENT
WWW::MySociety::Gaze requires no configuration files or environment variables.
=head1 DEPENDENCIES
None.
=head1 INCOMPATIBILITIES
None reported.
=head1 BUGS AND LIMITATIONS
No bugs have been reported.
Please report any bugs or feature requests to
C, or through the web interface at
L.
=head1 AUTHOR
Andy Armstrong C<< >>
=begin html
=end html
=head1 LICENCE AND COPYRIGHT
Copyright (c) 2008, Andy Armstrong C<< >>.
This module is free software; you can redistribute it and/or
modify it under the same terms as Perl itself. See L.