# Copyright (c) 2008 bivio Software, Inc. All Rights Reserved.
#
# Visit http://www.bivio.biz for more info.
#
# This library is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 2.1 of the
# License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; If not, you may get a copy from:
# http://www.opensource.org/licenses/lgpl-license.html
#
# $Id: MapQuest.pm,v 1.8 2008/05/26 23:08:30 nagler Exp $
package Bivio::GIS::MapQuest;
use strict;
use Bivio::Base 'Collection.Attributes';
use XML::Simple ();
use Bivio::IO::Trace;
our($VERSION) = sprintf('%d.%02d', q$Revision: 1.8 $ =~ /\d+/g);
our($_TRACE);
my($_AUTH_KEYS) = [qw(ClientId Password)];
my($_C) = __PACKAGE__->use('IO.Config');
$_C->register(my $_CFG = {
map(($_ => $_C->REQUIRED), @$_AUTH_KEYS, 'Referer'),
access => 'dev',
});
my($_S) = __PACKAGE__->use('HTML.Scraper');
my($_HTML) = __PACKAGE__->use('Bivio.HTML');
my($_Z9) = __PACKAGE__->use('Type.USZipCode9');
my($_TE) = __PACKAGE__->use('Bivio.TypeError');
my($_AUTH);
my($_SERVER) = {
'Geocode Version="1"' => 'geocode',
'DoRoute Version="2"' => 'route',
};
my($_ROUTE_OPTIONS) = {
FASTEST => [RouteType => 0],
SHORTEST => [RouteType => 1],
PEDESTRIAN => [RouteType => 2],
OPTIMIZED => [RouteType => 3],
DEFAULT => [NarrativeType => 0],
HTML => [NarrativeType => 1],
NONE => [NarrativeType => -1],
};
sub maneuvers_to_distance {
my($self, $maneuvers) = @_;
my($distance) = 0;
foreach my $m (@$maneuvers) {
$distance += $m->{Distance} || b_die($m, ': invalid maneuver');
}
return sprintf('%.2f', $distance);
}
sub address_to_model_properties {
my($proto, $address) = @_;
return {
street1 => $address->{Street},
street2 => '',
city => $address->{AdminArea5},
state => $address->{AdminArea3},
zip => $address->{PostalCode},
country => $address->{AdminArea1},
};
}
sub geocode_to_address {
my($res, $err) = _from_xml(shift->geocode_to_xml(@_));
return $err ? (undef, $err) : $res->{LocationCollection}->{GeoAddress};
}
sub geocode_to_xml {
my($self, $address_or_zip) = @_;
my($z) = $_Z9->from_literal($address_or_zip);
$address_or_zip = {PostalCode => $z}
if $z;
my($res) = $self->http_get(
'Geocode Version="1"'
=> ref($address_or_zip) ? {Address => $address_or_zip}
: {SingleLineAddress => {Address => $address_or_zip}},
);
_trace($address_or_zip, ' => ', $res) if $_TRACE;
return (undef, $_TE->NOT_FOUND)
if $res =~ m{\Q39.527596-99.141968\E};
return (undef, $_TE->TOO_MANY)
unless $res =~ m{\QCount="1"\E};
return $res;
}
sub handle_config {
my(undef, $cfg) = @_;
$_CFG = $cfg;
$_AUTH = {'Authentication Version="2"' => {map(($_ => $_CFG->{$_}), @$_AUTH_KEYS)}};
return;
}
sub http_get {
my($self, $type, $attrs) = @_;
my($server) = $_SERVER->{$type} || b_die($type, ': unhandled type');
my($s) = $_S->new({Referer => $_CFG->{Referer}});
return ${$s->extract_content(
$s->http_get(
qq{http://$server.$_CFG->{access}.mapquest.com/mq/mqserver.dll?e=5&}
. $_HTML->escape_query(
''
. $self->to_xml({$type => [$attrs, $_AUTH]}),
),
),
)};
}
sub route_options {
my($self, $options) = @_;
return {
RouteOptions => {map(
@{$_ROUTE_OPTIONS->{$_} || b_die($_, ': invalid')},
ref($options) ? @$options : $options ? $options : (),
)},
};
}
sub route_to_maneuvers {
my($tr) = _from_xml(shift->route_to_xml(@_))->{RouteResults}->{TrekRoutes};
b_die($tr, ': incorrect number of TrekRoutes')
unless $tr->{Count} == 1;
my($m) = $tr->{TrekRoute}->{Maneuvers}->{Maneuver};
b_die($m, ': too few Maneuvers')
unless @$m >= 1;
return $m;
}
sub route_to_xml {
my($self, $locations, $options) = @_;
my($res) = $self->http_get(
'DoRoute Version="2"' => [
{qq{LocationCollection Count="@{[scalar(@$locations)]}"} => [
map(
{GeoAddress => $self->geocode_to_address($_)},
@$locations,
),
]},
$self->route_options($options),
],
);
b_die(CLIENT_ERROR => {entity => [$locations, $options], result => $res})
unless $res =~ /\QResultCode>0<\E/;
return $res;
}
sub to_xml {
my($self, $value) = @_;
return !ref($value) ? $value
: join('',
ref($value) eq 'ARRAY' ? map($self->to_xml($_), @$value)
: map({
my($k, $a) = split(/ /, $_, 2);
$a = $a ? " $a" : '';
qq{<$k$a>} . $self->to_xml($value->{$_}) . "$k>";
} sort(keys(%$value))),
);
}
sub _from_xml {
return XML::Simple::xml_in(@_);
}
1;