latlngの宗教問題

1406390_59235278.jpg

geocoderの話が予想以上にうけてびっくりしたので、ジオコーディングのくだらない話を一つ。

ジオコーディングに関するコードを書いているとたいていハマる問題が一つあって、それは緯度経度をどういう変数で表すか。
geo – Longitude の略し方。lng 派と lon 派の終わらない争い – Qiita
によると、だいたい世界には6つの派閥が存在するらしいです。

  • Lng派: lat,lng
  • Lon派: lat,lon
  • Long派: lat,long
  • Longitude(略さない)派: latitude,longitude
  • 緯度経度ペアに名前付けちゃう派: “34.12345,135.98765”
  • x,y派: latlngじゃなくてxとyで済ませちゃう人たち

個人的には、上記の他に、

[34.12345,135.98765]

とArrayで表す派閥が存在すると思っています。
例のRubyGeocoderは、もともと文字列を受け取ることになっていたのですが、最近Arrayも受け取るように修正が入ったようです。

ついでなので、Ruby界から、

{lat:34.12345,lng:135.98765}

と表す新Ruby派と、

{"lat"=>34.12345,"lng"=>135.98765}

と表す旧Ruby派なんかも参戦していただくと、問題が混沌としてきて、より宗教戦争らしくなってきますね。

609407_23691178.jpg

戦争を煽るばかりでは申し訳ないので、みんな仲良くできるコードを用意してみました。

緯度経度を表す多様なフォーマットを全部受け入れて任意のフォーマットで出力する試み
https://gist.github.com/mogya/6978002

これをつかえば、上記で上げたようなパターンをすべて扱うことが出来ます。

irb> GeoUtils::coordinates([34.12345, 135.98765])
=> [34.12345, 135.98765]
irb> GeoUtils::coordinates(["34.12345", "135.98765"])
=> [34.12345, 135.98765]
(以下、戻り値は略)
irb> GeoUtils::coordinates("34.12345", "135.98765")
irb> GeoUtils::coordinates(34.12345, 135.98765)
irb> GeoUtils::coordinates( {lat:34.12345, lng:135.98765} )
irb> GeoUtils::coordinates( {lat:34.12345, lon:135.98765} )
irb> GeoUtils::coordinates( {'lat'=>34.12345, 'long'=>135.98765} )
irb> GeoUtils::coordinates( {'x'=>34.12345, 'y'=>135.98765} )

出力側はgeocoderにあわせてarrayにしてあるけど、これも指定可能

irb> GeoUtils::coordinates([34.12345, 135.98765],:lat_lng)
=> {:lat=>34.12345, :lng=>135.98765}
irb> GeoUtils::coordinates([34.12345, 135.98765],:lat_long)
=> {:lat=>34.12345, :long=>135.98765}
irb> GeoUtils::coordinates([34.12345, 135.98765],:latitude_longitude)
=> {:latitude=>34.12345, :longitude=>135.98765}

こんなこともできます

irb> GeoUtils::coordinates(
[34.12345, 135.98765],
[:lat_lng,:lat_long,:lat_lon,:latitude_longitude,:x_y])
=> { :lat=>34.12345, :lng=>135.98765,
:long=>135.98765, :lon=>135.98765,
:latitude=>34.12345, :longitude=>135.98765}

使わなくて済んだはずのCPU時間を横着のために使うのはどうかという気もするのですが、気にならない方は自由に利用してください。

Ruby geocoderがすごい

住所を緯度経度に直したり、緯度経度から住所を求めたりする操作をgeocodingと言って、Google Maps APIを使うとまあたいていのことはできる。
ロケタッチAPIとか、Yahoo!ジオコーダAPIという手もある。

それはともかく、そのへんをパチパチ叩くコードを書いていて、「こんなのもうとっくに誰かが書いてんじゃないかなー」と思ってぐぐってみたらなんかすごいのが出てきた。


geocoder.png

Ruby Geocoder

住所と緯度経度の相互変換はもちろん、距離や範囲の扱い、Google以外のAPIへの対応、キャッシュ処理など、「実装しようかなー。でもめんどくさいよね」とか思って先送りしていたような機能がほとんど全部実装されている。

住所の取得

require 'geocoder'
# 日本語ロケールに設定
Geocoder.configure( :language  => :ja,   :units => :km )
Geocoder.address("東京タワー")
# => "日本, 〒105-0011 東京都港区芝公園4丁目4−2−8 東京タワー"

もちろん、住所しか取れないなんて馬鹿なことはなくて、GoogleMapsAPIの結果をそのままもらうこともできます

Geocoder.search("東京タワー")
# => [#
[{"long_name"=>"東京タワー",
"short_name"=>"東京タワー",
"types"=>["point_of_interest", "establishment"]},
:中略
{"long_name"=>"芝公園",
"short_name"=>"芝公園",
"types"=>["sublocality_level_1", "sublocality", "political"]},
{"long_name"=>"港区",
"short_name"=>"港区",
"types"=>["locality", "political"]},
{"long_name"=>"東京都",
"short_name"=>"東京都",
"types"=>["administrative_area_level_1", "political"]},
{"long_name"=>"日本",
"short_name"=>"JP",
"types"=>["country", "political"]},
{"long_name"=>"105-0011",
"short_name"=>"105-0011",
"types"=>["postal_code"]}],
"formatted_address"=>"日本, 〒105-0011 東京都港区芝公園4丁目4−2−8 東京タワー",
"geometry"=>
{"location"=>{"lat"=>35.6585805, "lng"=>139.7454329},
"location_type"=>"APPROXIMATE",
"viewport"=>
{"northeast"=>{"lat"=>35.6676459, "lng"=>139.7614403},
"southwest"=>{"lat"=>35.6495141, "lng"=>139.7294255}}},
"types"=>["point_of_interest", "establishment"]}>]

緯度経度から住所へ(リバースジオコーディング)

Geocoder.address("39.70150773,141.13672256")
# => "日本, 〒020-0034 岩手県盛岡市盛岡駅前通 県道2号線"
Geocoder.search("39.70150773,141.13672256")
[#"日本, 〒020-0034 岩手県盛岡市盛岡駅前通 県道2号線",
:(中略)
"types"=>["route"]}>,
#"日本, 盛岡駅前(バス)(岩手)",
:(中略)
"types"=>["bus_station", "transit_station", "establishment"]}>,
#"〒020-0034, 日本",
:(中略)
"types"=>["postal_code"]}>,
#"日本, 盛岡駅(岩手)",
:(中略)
"types"=>["train_station", "transit_station", "establishment"]}>,
:(以下略)

IPアドレスの場所を取得

Geocoder.search("210.194.198.60")
[#"210.194.198.60",
"country_code"=>"JP",
"country_name"=>"Japan",
"region_code"=>"19",
"region_name"=>"Kanagawa",
"city"=>"Yokohama",
"zipcode"=>"",
"latitude"=>35.4478,
"longitude"=>139.6425,
"metro_code"=>"",
"areacode"=>""}>]
Geocoder.address("106.187.50.98")
# => "Japan" # IPアドレスによってはこうなってしまう(笑)

距離や角度の演算

t1 = Geocoder.search("東京タワー")[0].geometry['location'].values.join(',')
t2 = Geocoder.search("東京スカイツリー")[0].geometry['location'].values.join(',')
Geocoder::Calculations.distance_between(t1,t2)
# => 8.20771162081327 # km
Geocoder::Calculations.bearing_between(t1,t2)
# => 51.834180520275936 # degree
Geocoder::Calculations.compass_point Geocoder::Calculations.bearing_between(t1,t2)
=> "NE"
# railsと組み合わせるとobj.distance_to みたいなこともできるらしい。

Bing APIを使ってみる

# APIキーが必要。http://www.bingmapsportal.com
Geocoder.configure( :lookup => :bing, :api_key=>'*****' )
Geocoder.search("東京タワー")
# => [#"Location:http://schemas.microsoft.com/search/local/ws/rest/v1", "bbox"=>[35.62964091461487, 139.69803606567407, 35.687581680599976, 139.79308331909155], "name"=>"Tokyo Tower, Japan", "point"=>{"type"=>"Point", "coordinates"=>[35.65861129760742, 139.7455596923828]}, "address"=>{"countryRegion"=>"Japan", "formattedAddress"=>"Tokyo Tower, Japan", "landmark"=>"Tokyo Tower"}, "confidence"=>"High", "entityType"=>"LandmarkBuilding", "geocodePoints"=>[{"type"=>"Point", "coordinates"=>[35.65861129760742, 139.7455596923828], "calculationMethod"=>"Rooftop", "usageTypes"=>["Display"]}], "matchCodes"=>["Good"]}, @cache_hit=nil>]
Geocoder.address("東京タワー")
# => "Tokyo Tower, Japan"

ほかにも、API呼び出しをキャッシュさせたり、Railsと組み合わせることで Hotel.near(“Vancouver, Canada”) みたいなこともできるようになるらしいです。

もっと早く探しておけばよかったなー。