プログラマでありたい

おっさんになっても、プログラマでありつづけたい

郵便番号データに緯度経度を付加する手順

 一昔前に、GoogleMapsから郵便番号に経度緯度情報に変換するプログラムを書きました。そのデータを使おうと思ったのですが、見当たりません。確かブログにコード書いていたなぁと思って、過去の記事「郵便番号から緯度経度情報を取得する」を見たのですが、我ながらこれはひどいw

 緯度経度を取るには、住所から検索する必要があります。郵便番号直では出来ないので注意してください。一連のデータを作る方法は次回書きます。

 続きを書いていませんね。と言う訳で、その当時どんなソースを書いたか謎ですが、さくっともう一度作りました。以下手順とプログラムです。


 まずは郵便番号データをダウンロードしてきて、必要な形に整形します。
utf8に変換して、必要な項目のみピックアップします。ついでに、少しだけ項目の補正をします。補正処理はもっともっと色々しないといけないことがあるのですが、ここでは割愛します。

nkf -w KEN_ALL.CSV > ken_all.csv
cat ken_all.csv | awk -F,  '{print $3","$7","$8","$9}' | sed -e s/以下に掲載がない場合// > zip_cds.csv


 次はデータベースにインポートします。ここではDBはMySQLを使用しています。
テーブルを作って、データの流し込みを行います。

mysql yourdatabase
CREATE TABLE zip_cds (
        id INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
        zip_cd CHAR(7),
        pref_name_kanji VARCHAR(21),
        city_name_kanji VARCHAR(234),
        town_name_kanji VARCHAR(234),
        longitude FLOAT,
        latitude FLOAT
)
DEFAULT CHARSET=utf8
;
CREATE INDEX idx_zip_cd ON zip_cds (
        zip_cd
);

load data local infile '/foo/var/zip_cds.csv'
into table zip_cds
fields terminated by ',' enclosed by '"'
(zip_cd,pref_name_kanji,city_name_kanji,town_name_kanji);


結果は次のようになります。
番地の辺りに改善の余地がありそうですが、とりあえず放置してあります。

mysql> select * from zip_cds limit 10;

                                                                                                                                                                                                                                              • +
id zip_cd pref_name_kanji city_name_kanji town_name_kanji longitude latitude
                                                                                                                                                                                                                                              • +
1 0600000 北海道 札幌市中央区 NULL NULL
2 0640941 北海道 札幌市中央区 旭ケ丘 NULL NULL
3 0600041 北海道 札幌市中央区 大通東 NULL NULL
4 0600042 北海道 札幌市中央区 大通西(1〜19丁目) NULL NULL
5 0640820 北海道 札幌市中央区 大通西(20〜28丁目) NULL NULL
6 0600031 北海道 札幌市中央区 北一条東 NULL NULL
7 0600001 北海道 札幌市中央区 北一条西(1〜19丁目) NULL NULL
8 0640821 北海道 札幌市中央区 北一条西(20〜28丁目) NULL NULL
9 0600032 北海道 札幌市中央区 北二条東 NULL NULL
10 0600002 北海道 札幌市中央区 北二条西(1〜19丁目) NULL NULL
                                                                                                                                                                                                                                              • +

10 rows in set (0.00 sec)


 最後にperlのコードです。下準備としてCPANのGeo::Coder::GoogleMapsをインストールしておく必要があります。また、GoogleMapsAPIのキーも必要です。

#!/usr/bin/perl

use strict;
use warnings;

use DBI;
use Data::Dumper;
use Geo::Coder::GoogleMaps;

my $apikey = 'yourapikey';
my $gmap = Geo::Coder::GoogleMaps->new( apikey => $apikey, output => 'xml', host => 'maps.google.co.jp');

my $user = 'db_user';
my $passwd = 'db_password';

my $dbh = DBI->connect('DBI:mysql:test:localhost', $user, $passwd);
$dbh->do("set character set utf8");



my $sql;
$sql = "SELECT count(*) cnt FROM zip_cds WHERE latitude is null";
my $count = $dbh->selectrow_array($sql);
print $count,"\n";


my $i = 0;
my $increment = 1000;
my $update = "UPDATE zip_cds SET latitude=?,longitude=? WHERE id = ?";
my $sth = $dbh->prepare($update);
while ($i <= $count) {
  $sql = "SELECT * FROM zip_cds WHERE latitude is null limit $i,$increment";
  my $rows = $dbh->selectall_arrayref( $sql,{ Columns => {} });
  foreach my $row (@{$rows}) {
    my $address = $row->{'pref_name_kanji'}.$row->{'city_name_kanji'}.$row->{'town_name_kanji'};
    print $address,"\n";
    my $location = $gmap->geocode(location => $address);
    if (defined($location)) {
      print $location->latitude,',',$location->longitude,"\n";
      $sth->execute($location->latitude,$location->longitude,$row->{'id'});
    }
    sleep 1;
  }

  $i = $i + $increment;
}

 1件ごとにスリープを入れているのは、あまり連続でやるとGoogleさんに怒られるからです。郵便番号データが12万件くらいで、1秒のスリープを入れているので最低33時間以上かかります。GoogleMapsAPIの返事も1件あたり0.5秒くらい掛かっているので、全件終了するのに二日以上掛かるでしょう。実行速度を上げたければ、複数のAPIキーを取って多重に実行すれば多分いけると思います。(追記2を参照してください)
 こんなことを皆がしても無駄なので、著作権のあるデータでもないことですし、作成後にどこかで公開しようかと思います。


 ちなみに、上記の処理で経度緯度情報がとれない住所も結構あります。そこは後で対策しようかと考えています。とりあえずの郵便番号から緯度経度への変換ということで。


追記:
※書いた後で気がついたのですが、GoogleMapsAPIの制約で、1日のリクエストの上限がありますね。1万5千。


追記2:
Google Japan Blog: ジオコーディングの制限を IP アドレス単位に変更します
APIの使用制限は、APIキー単位ではなくIPアドレス単位のようです。


追記3:
位置参照情報データと郵便番号データのマッチング
 位置参照情報データといって、住所と位置情報のマッピングをしているデータがありました。これと郵便番号データをマッチングしたら、だいたい郵便番号データの半分に一気に位置情報に変換できます。これをした後に、上の対応をしたら時間短縮につながりますね。