end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

google map api とゼンリン地図で geocoding (住所→座標(緯度経度))

google map apiによるgeocoding概要

http://code.google.com/intl/ja_ALL/apis/maps/documentation/services.html#Geocoding

google map api を使用すれば、geocoding、すなわち、住所→座標(緯度 経度)を行うことができます。例えば、次のようなグーグル株式会社のgeocodingをxmlで取得するには次のように行います。

http://maps.google.co.jp/maps/geo?key=&output=xml&q=東京都渋谷区桜丘町26-1

※実際にはkey(ApiKey)も指定して下さい。

google map apiのレスポンスには、statusコードもある為、指定住所の取得失敗等も判断することができます。

http://code.google.com/intl/ja/apis/maps/documentation/reference.html

http://maps.google.co.jp/maps/geo?key=&output=xml&q=ありえない住所

この他にstatus=200であるにも関わらず、複数の候補が得られる場合もあります。

http://maps.google.co.jp/maps/geo?key=&output=xml&q=久留米


google map apiでgeocodingできない分をゼンリン地図でフォロー

ただし、実際にある住所にもかかわらず、google map apiでgeocodingできない場合がいくつかあるようです。
最近では、いろいろ地図サービスがありますが、変換精度や使い勝手の点でゼンリン地図がよさそうだったので、google map apiでgeocodingできない分は、ゼンリン地図でフォローすることにしました。

http://www.its-mo.com/

ゼンリン地図の場合、apiを公開しているわけではない為、出力されるhtmlをparseする必要がありますが、次のように行えば、google map api & ゼンリン地図によるgeocodingを実現することができます。

#!/usr/local/bin/perl

use strict;
use warnings;
use Encode;
use Encode::Guess qw/shift-jis euc-jp utf-8/;
use LWP::UserAgent;
use XML::Simple;
use Data::Dumper;

#http://maps.google.co.jp/maps/geo?key=&output=xml&q=東京都渋谷区桜丘町26-1
my $GMAP_API = "http://maps.google.co.jp/maps/geo?output=xml";
my $GMAP_KEY = "";
#http://free.its-mo.com/?freewd=東京都渋谷区桜丘町26-1
my $ZENRIN_API = "http://free.its-mo.com/";

my @ZENRIN_GEO_RET;


sub main {
    my ($address_file) = @_;

    open my $fh, $address_file or die "can't open file:$address_file:$!";
    my @adrs_lines = <$fh>;
    close($fh) or die "can't open file:$address_file:$!";

    for my $line ( @adrs_lines ){
	$line =~ s/\s+$//o;
	$line = decode("shift-jis", $line);
	$line = encode("utf-8", $line);

	#「名前<tab>住所org」の形式
	my @cols = split("\t",$line);

	#sys      : 「gmap api」 or 「zenrin」
	#adrs_new : geocoderによるparse済住所
	#coord    : 世界測地系d形式の緯度経度
	my ($sys,$adrs_new,$coord) = geocode( $cols[1] );

	$sys ||= "";
	$adrs_new ||= "";
	$coord ||= "";
	print "$cols[0]\t$cols[1]\t$sys\t$adrs_new\t$coord\n";
	sleep(1);
    }
}


sub geocode {
    my ($adrs_org) = @_;

    my $ua  = LWP::UserAgent->new();
    my $xml = XML::Simple->new();

    #まずは、google map apiで「住所→緯度経度」
    my $res = $ua->get("$GMAP_API&key=$GMAP_KEY&q=$adrs_org");
    unless ( $res->is_success ){
	print STDERR "fail http get:$adrs_org\n";
	return undef;
    }

    my $xml_str = $res->content;
    $xml_str = decode("Guess", $xml_str);
    my $geo = $xml->XMLin( $xml_str );

    if ( $geo->{Response}->{Status}->{code} eq "200" and
	 defined($geo->{Response}->{Placemark}->{address}) ){
	my $adrs = encode("utf-8", "$geo->{Response}->{Placemark}->{address}");
	my @co =
	    split(/,/,"$geo->{Response}->{Placemark}->{Point}->{coordinates}");
	return ("google",$adrs,"$co[0] $co[1]");
    }

    #google map apiで取得できない場合、zenrinで「住所→緯度経度」
    $res = $ua->get("$ZENRIN_API?freewd=$adrs_org");
    unless ( $res->is_success ){
	print STDERR "fail http get:$adrs_org\n";
	return undef;
    }
    my $html_str = $res->content;
    my $parser =
	HTML::Parser->new(api_version => 3,
			  start_h =>[\&read_zenrin_html,
				     "self, tagname, attr, text"],
			  marked_sections => 1);
    $parser->parse($html_str);
    return ("zenrin",@ZENRIN_GEO_RET) if $ZENRIN_GEO_RET[0];


    print STDERR "fail geocode: $adrs_org\n";
    return undef;
}

sub read_zenrin_html {
    my ($self, $tagname, $attr, $text) = @_;
    return undef if ($tagname ne "input");
    return undef if (not defined($attr->{name}) or
		     $attr->{name} ne "link" );

    my $val = decode("utf-8", $attr->{value});

    @ZENRIN_GEO_RET = ();
    my ($adrs,$lat,$lon) = $val =~ /^fw:.+\,(.+):(\d+):(\d+)$/o;
    ($lon,$lat) = conv_co_tokyo_to_wgs84($lon,$lat);
    @ZENRIN_GEO_RET = ( encode("utf-8",$adrs),"$lon $lat");
    return 1;
}

#日本測地系→世界測地系
sub conv_co_tokyo_to_wgs84 {
    my ($lon,$lat) =@_;
    $lon = $lon / 1000 / 3600;
    $lat = $lat / 1000 / 3600;
    return ($lon - $lat*0.000046038 - $lon*0.000083043 + 0.010040,
	    $lat - $lat*0.00010695 + $lon*0.000017464 + 0.0046017);
}

main(@ARGV);