Sequel で GeoHash 検索の実験をするために神奈川県の街区&大字レベル位置参照情報を SQLite3 に入れたらマッチングに苦労した
GeoHash を用いてプレインな RDBMS で点情報の検索をしてみたいと思い、街区&大字レベル位置参照情報を SQLite3 に入れようとしています。Mac & Ruby 縛りでやっています。コードの一致を意識していたら、いろいろと落とし穴にはまったので、このエントリでは、データベースに神奈川県のデータを入れるところで終わっています。
http://togetter.com/li/44975 などでまとめられているように、点情報の空間検索で GeoHash を使い、RDBMS の空間拡張を使用しないという話があります。RDBMS の空間拡張には「空間インデクスを使用した検索」と「空間拡張したSQL関数による幾何処理」の2つが期待されますが、GeoHash 検索によって、前者の代替が行えることになります。
私もこの技術を実験してみようと思い、街区レベル位置参照情報と大字・町丁目レベル位置参照情報( http://nlftp.mlit.go.jp/isj/ )を SQLite3 に格納してみました。
GeoHash という「コード」によって検索するのだなあと意識しすぎて、大字・町丁目レベル位置参照情報の「大字町丁目コード」が持つ魅力にとりつかれたため、街区レベル位置参照情報と大字・町丁目レベル位置参照情報とのマッチングにはまってしまいました。
コード
# -*- coding: utf-8 -*- # load.rb $KCODE = 'u' require 'iconv' require 'rubygems' require 'sequel' require 'pr_geohash' require 'zip/zipfilesystem' DB = Sequel::connect('sqlite://14.sqlite') DB.create_table!(:prefs) do String :code, :unique => true String :name end DB.create_table!(:cities) do String :code, :unique => true String :name end DB.create_table!(:azachos) do String :code, :unique => true String :name String :geohash end DB.create_table!(:gaikus) do String :code String :fugo String :geohash end s2u = Iconv.new('UTF-8', 'Shift_JIS') class Array def cache(value) self.shift self.push(value) end end pref_codes_inserted = [nil, nil, nil] city_codes_inserted = [nil, nil, nil] # 神奈川県の大字・町丁目レベル位置参照情報をロード Zip::ZipFile.open('src/14000-03.0b.zip') {|zip| p zip.file.size('14_2009.csv') first = true zip.file.foreach('14_2009.csv') {|l| if first print s2u.iconv(l) first = false next end (pref_code, pref_name, city_code, city_name, azacho_code, azacho_name, ido, keido) = s2u.iconv(l).gsub('"', '').strip.split(',') city_name.sub!(/ヶ/, 'ケ') azacho_name.sub!(/ヶ/, 'ケ') unless pref_codes_inserted.include?(pref_code) DB[:prefs] << {:code => pref_code, :name => pref_name} if DB[:prefs].filter(:code => pref_code).count == 0 pref_codes_inserted.cache(pref_code) end unless city_codes_inserted.include?(city_code) DB[:cities] << {:code => city_code, :name => city_name} if DB[:cities].filter(:code => city_code).count == 0 city_codes_inserted.cache(city_code) end DB[:azachos] << {:code => azacho_code, :name => azacho_name, :geohash => GeoHash::encode(ido.to_f, keido.to_f, 12)} if DB[:azachos].filter(:code => azacho_code).count == 0 } } # 神奈川県の街区レベル位置参照情報をロード Zip::ZipFile.open('src/14000-08.0a.zip') {|zip| p zip.file.size('14_2009.csv') first = true zip.file.foreach('14_2009.csv') {|l| if first print s2u.iconv(l) first = false next end (pref_name, city_name, azacho_name, fugo, kei, x, y, ido, keido) = s2u.iconv(l).gsub('"', '').strip.split(',') city_name.sub!(/ヶ/, 'ケ') azacho_name.sub!(/ヶ/, 'ケ') city_name = $2.to_s if city_name.match(/(.*郡)(.*(町|村))/) pref_code = DB[:prefs].filter(:name => pref_name).first[:code] begin city_code = DB[:cities].filter(:name => city_name).filter(:code.like("#{pref_code}%")).first[:code] rescue p city_name end azacho_ds = DB[:azachos].filter(:name => azacho_name).filter(:code.like("#{city_code}%")) if azacho_ds.count == 1 azacho_code = azacho_ds.first[:code] DB[:gaikus] << {:code => azacho_code, :fugo => fugo, :geohash => GeoHash::encode(ido.to_f, keido.to_f, 12)} else print "azacho_code for #{azacho_name} (#{pref_name}#{city_name}) not found.\n" end } } DB.disconnect
ポイント
- 大字・町丁目レベル位置参照情報の市区町村名には郡名はなく、街区レベル位置参照情報には郡名がはいっているので、さっくりと正規表現を使って郡名を取り除こうとしています。しかし、この正規表現、町村名に「郡」と入っているときにうまく動いてくれない気がします。
- 大字・町丁目レベル位置参照情報では「保土ケ谷区」ですが、街区レベル位置参照情報では「保土ヶ谷区」です。 http://ja.wikipedia.org/wiki/保土ケ谷区 を参考に、「保土ケ谷区」に合わせる場当たり的な処理を入れています。茅ヶ崎については「ヶ」が正しいのですが、大字・町丁目にあわせてとりあえず「茅ケ崎」に揃えています。その他、市区町村名についても大字・町丁目名についても、とりあえず「ヶ」をすべて「ケ」に置き換えています。
- その他、町又は字の名前のレベルでのミスマッチが発生しています(例:棚沢 vs 棚澤)が、これについてはあとで考えることにします。
- 処理の効率性をあまり考えていません。ユニークであるべきレコードを入れるときに、いちいちデータベースを検索しています。