The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Geo::Yandex;

use vars qw ($VERSION);
$VERSION = '0.3';

my $API = 'geocode-maps.yandex.ru/1.x';

use strict;
use utf8;
use Geo::Yandex::Location;
use LWP::UserAgent;
use URI::Escape;
use XML::LibXML;

sub new {
    my ($class, $key) = @_;
    
    unless ($key) {
        warn "API key is not specified\n";
        return undef;
    }

    my $this = {
        key => $key,
        error => '',
    };
    
    bless $this, $class;
    
    return $this;
}

sub location {
    my ($this, %data) = @_;

    $this->{'error'} = '';

    unless ($data{'address'} && $this->{'key'}) {
        $this->{'error'} = "Either address or API key are not specified\n";
        return undef;
    }
    
    my $uri
        = "http://$API/?key=" . uri_escape_utf8($this->{'key'})
        . '&geocode=' . uri_escape_utf8($data{'address'});
        
    $uri .= '&ll='      . $data{'center'}   if $data{'center'};
    $uri .= '&spn='     . $data{'size'}     if $data{'size'};
    $uri .= '&results=' . $data{'results'}  if $data{'results'};
    $uri .= '&skip='    . $data{'skip'}     if $data{'skip'};

    my $useragent = new LWP::UserAgent;
    my $request = new HTTP::Request(GET => $uri);
    my $response = $useragent->request($request);

    if ($response->is_success) {
        return $this->parse_response($response->content);        
    }
    else {
        $this->{'error'} = "HTTP request error: " . $response->status_line . "\n";
        return ();
    }    
}

sub parse_response {
    my ($this, $content) = @_;

    my $parser = new XML::LibXML;
    my $xml = $parser->parse_string($content);
    
    my $context = new XML::LibXML::XPathContext;
    $context->registerNs('ymaps', 'http://maps.yandex.ru/ymaps/1.x');
    $context->registerNs('gml', 'http://www.opengis.net/gml');
    $context->registerNs('xal', 'urn:oasis:names:tc:ciq:xsdschema:xAL:2.0');
    $context->registerNs('ygeo', 'http://maps.yandex.ru/geocoder/1.x');
    
    my $ymaps = ${$context->findnodes('/ymaps:ymaps', $xml)}[0];
    my $request = ${$context->findnodes('.//ygeo:request', $ymaps)}[0]->textContent;
    my $found = ${$context->findnodes('.//ygeo:found', $ymaps)}[0]->textContent;

    return () unless $found;

    my @featureMembers = $context->findnodes('.//gml:featureMember', $ymaps);
    my @ret;
    foreach my $featureMember (@featureMembers) {
        push @ret, new Geo::Yandex::Location($context, $featureMember);
    }
    
    return @ret;
}

1;

__END__

=encoding utf-8

=head1 NAME

Geo::Yandex - Performs geographical queries using Yandex Maps API

=head1 SYNOPSIS

    use Geo::Yandex;

    # Address to search
    my $addr = 'Москва, Красная площадь, 1';
    
    # Personal API key, should be obtained at http://api.yandex.ru/maps/form.xml
    my $key = '. . .';
    
    # New geo object, note to use the key
    my $geo = new Geo::Yandex($key);
    
    # Search locations with a given address
    my @locations = $geo->location(address => $addr);
    
    # Or specify query in more details
    my @locations = $geo->location(
        address => $addr,
        results => 3,
        skip    => 2,
        center  => '37.618920,55.756994',
        size    => '0.552069,0.400552'
    );    
    
    # Locations are an array of Geo::Yandex::Location elements
    for my $item (@locations) {
        say $item->address . ' (' . $item->kind .') ' .
            $item->latitude . ',' . $item->longitude;
    }

    
=head1 ABSTRACT

Geo::Yandex is a Perl interface for the part of Yandex Maps API which retrieves geographical data for text query.

=head1 DESCRIPTION

Yandex Maps API is a set of tools for working with http://maps.yandex.ru website both with JavaScript queries and HTTP negotiations. Full description of the interface can be found at http://api.yandex.ru/maps/doc/ (in Russian).

All the work is done by an instance of Geo::Yandex class.

=head2 new
 
Creates a new Geo::Yandex object. The only argument, which is required, is a personal key that should be issued by Yandex before using API. To obtain the key you need to fill the form at http://api.yandex.ru/maps/form.xml.

    my $geo = new Geo::Yandex($key);
    
=head2 location
    
Launches search query to Yandex and returns the list of locations which match to the given address (passed in C<address> parameter).
    
    my @locations = $geo->location(address => $addr);
    
The list returned by this method is combined of elements of the type Geo::Yandex::Location. If no results were found, return is an empty list.

=head2 parameters of location method

=head3 address

This is the only parameter which is requered for performing the search. It is a text string containing the address of the location being searched. May be in less or more free form.

=head3 results

Optional parameter which sets the limit of search. No more results will appear than a number set by this parameter.

=head3 skip

Optional parameter to skip several first results. Useful in pair with C<results> parameter for organizing paginated output.

=head3 center, size

    my @locations = $geo->location(
        address => $addr,
        center  => '37.618920,55.756994',
        size    => '0.552069,0.400552'
    );    

These two parameters restrict the search area with a boundary located withing (longitude, latitude) pair set in C<center>. This point will be located in the center of search area. To set width and height of the search block use C<size> parameter. Both parameters are optional. Each should contain a pair of numbers (geographical measure - degree) separated by a comma.

Please note that you should not expect that C<center> and C<size> parameters will bring rigid boundaries of the area; those are just hints for the search engine. Seems to be weird stuff.

=head1 AUTHOR

Andrew Shitov, <andy@shitov.ru>

=head1 COPYRIGHT AND LICENCE

Geo::Yandex module is a free software.
You may redistribute and (or) modify it under the same terms as Perl, whichever version it is.

=cut