103
86

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

DMM WEBCAMPAdvent Calendar 2019

Day 6

RailsのGeocoderとあそぼ

Last updated at Posted at 2019-12-05

はじめに

はじめまして、DMM WEBCAMPでメンターをしております。
昨年「Rails5でGoogleMapを表示してみるまで」という記事を書きました。
それからというもの、私は地図おじさんとなり、数多の地図表示を手助けするはめになりました。
ときには、地図に関連してGeocodingの質問もいただくようになりました。
しかしながら、Geocodingはあまり触っていなかったので、この際まとめておこうと思った次第です。

Geocoding

GeoCoding(ジオコーディング)とは

Wikipediaより
ジオコーディングは、狭義には地名、住所が示す場所に対して、地理座標を与えることを言う。その場所は地点(例:市役所)では一点に定まるが、ある範囲(例:市町村)では、代表点を一点で示す場合と、領域を多角形、すなわちポリゴンで示す場合がある。代表点は応用目的により、「東西の中心、南北の中心」のように幾何的に与えるか、何らかの意味をもった地点を「市の場合は市役所の位置、川の場合は河口ないし合流点の位置」のように与える。
ジオコーディングは、また各種データ(例:地名を含む文書、ある地点を撮影した写真)に地理座標を与えることもいう。

要は、住所や地名から座標(経度緯度)を取得したり、その逆を行ったりです。
これによって、近くのお店を検索したり、2地点間の真ん中にあるスポットを検索したりが可能になります。

GemのGeoCoder

今回はRails環境で、GemのGeocoderを使用してGeocodingを行います。
https://github.com/alexreisner/geocoder
基本的な使い方と要所を示していきます。

基本

Geocoderの基本例は次のとおりです。
地名や住所を引数にして、経度緯度を返り値として取得できます。

results = Geocoder.search("Paris")
results.first.coordinates
#=> [48.856614, 2.3522219]  latitude and longitude

反対に、経度緯度を引数にして、住所などを取得できます。

results = Geocoder.search([48.856614, 2.3522219])
results.first.address
#=> "Hôtel de Ville, 75004 Paris, France"

一方で、ipアドレスから場所を取得することも可能です。

results = Geocoder.search("172.56.21.89")
results.first.coordinates
#=> [30.267153, -97.7430608]
results.first.country
#=> "United States"

モデルに対するGeocoding

ここからはActiveRecordを使用したモデルに対してGeocoderを利用する方法を紹介していきます。
この際、大事なポイントが3つあります。

  1. 地名・住所データを返すメソッドが存在している。
  2. 経度・緯度データを格納するカラムが存在している。
  3. geocoded_by もしくは reverse_geocoded_by をモデルに記述している。

1. 地名・住所データを返すメソッドが存在している

地名・住所データを返すメソッドがあればよいので、データベースのカラムとして持つか、モデル内にメソッドを用意しましょう。

サンプルコードでは、データベースのカラムとしてaddressを用意しています。
モデル内にメソッドを設ける場合は、特定の場所や、複数カラム組み合わせて返すなど工夫を凝らすことが容易になります。

class Model < ApplicationRecord
  def current_position
    #現在地を返す
  end

  def address
    [street, city, state, country].compact.join(', ')
  end
end

2. 経度・緯度データを格納するカラムが存在している。

デフォルトの設定だと、latitudelongitudeというカラムを用意する必要があります。
これは、モデルで設定を変更できます。

class Model < ApplicationRecord
  #lat, lonというカラムを設ける場合
  geocoded_by :address, latitude: :lat, longitude: :lon  # ActiveRecord
end

3. geocoded_by もしくは reverse_geocoded_by をモデルに記述している。

すでに2. 経度・緯度データを格納するカラムが存在している。で記述してしまいましたが、geocoded_byreverse_geocoded_byを記述することで、モデルを介してgeocoderのメソッドを使えるようになります。

reverse_geocoded_byは経度緯度でgeocodingしたい場合に記述します。

  obj.distance_to([43.9,-98.6])  # distance from obj to point
  obj.bearing_to([43.9,-98.6])   # bearing from obj to point
  obj.bearing_from(obj2)         # bearing from obj2 to obj

ActiveRecordのモデルに対してGeocoderを備え付ける方法はこれまでのとおりです。
ここからは例を示しながらGeocoderで使えるメソッドを紹介していきます。
ここではSpotモデルを作成し、そのモデルで遊んでいきます。

サンプルコードでは適当なデータをseed.rbに入れてあります。

ジオコーディング

geocodeメソッドでgeocodingできます。
after_validationのコールバックとしてgeocodeを行う例をよく見ます。

class Spot < ApplicatoinRecord
  geocoded_by :address
  after_validation :geocode
end
Spot.create(:address => "東京タワー")
=> #<Spot id: 1, address: "東京タワー", latitude: 35.65858645, longitude: 139.745440057962, created_at: "2019-12-05 06:06:19", updated_at: "2019-12-05 06:06:19">

距離算出

距離算出には、distance_todistance_fromが使えます。
ただし、geocoderの距離単位はデフォルトでmileになっています。

  spot1 = Spot.find(1)
  spot2 = Spot.find(2)
  spot1.distance_to(spot2)
  spot1.distance_from([35.65858645, 139.745440057962])

近いやつ取得

多くの初心者エンジニアが使ってみたい機能ですね。
geocoderだとめちゃめちゃかんたんです。

  Spot.near("新宿") #20mileで検索
  spot = Spot.find(1)
  spot.nearbys(5, units: :km) #5kmで検索

検索SQL見ると楽しいです。筋肉で押し通す感じです。
こんなものなんでしょうか。

SELECT  spots.*, (69.09332411348201 * ABS(spots.latitude - 35.6891183) * 0.7071067811865475) + (59.836573914187355 * ABS(spots.longitude - 139.7010768) * 0.7071067811865475) AS distance, CASE WHEN (spots.latitude >= 35.6891183 AND spots.longitude >= 139.7010768) THEN  45.0 WHEN (spots.latitude <  35.6891183 AND spots.longitude >= 139.7010768) THEN 135.0 WHEN (spots.latitude <  35.6891183 AND spots.longitude <  139.7010768) THEN 225.0 WHEN (spots.latitude >= 35.6891183 AND spots.longitude <  139.7010768) THEN 315.0 END AS bearing FROM "spots" WHERE (spots.latitude BETWEEN 35.3996547337783 AND 35.978581866221695 AND spots.longitude BETWEEN 139.34467987351536 AND 140.05747372648466) ORDER BY distance ASC LIMIT ? 

中間地点を取得

これぐらい自分で計算できますが、メソッドを用意してくれてます。
マッチングアプリでお互いの中間地点にあるスポット検索とかできたら便利ですね。

spot1 = Spot.find(1)
spot2 = Spot.find(2)
Geocoder::Calculations.geographic_center([spot1, spot2]) #引数は3箇所以上可能
=> #[35.68432475413425, 139.77806655172708]

Geocodingの設定

距離の単位をkmにしたり、Geocodingを外部APIにするなどの設定を行うことができます。
rails g geocoder:config
で設定ファイルを生成できます。

geocoder.rb
Geocoder.configure(
  units: :km
)

googleのgeocoding APIを使用する場合は次のように設定します。

geocoder.rb
Geocoder.configure(
  lookup: :google, 
  api_key: '---------YOUR_API_KEY---------',     
)

GoogleのGeocodingAPIを使わずとも、geocoderの標準である程度の精度は出ます。
ただ、住所検索のときにはGoogleのGeocodingAPIを通したほうがいいかなとは思います。

その他のGeocoding APIを使用する場合はAPI_GUIDEを参照してください。

おわりに

Geocoderを使うとGeocodingがとても楽ですね。
これで僕は地図おじさんから地理おじさんに昇格です。
(ニッチすぎたと反省しております。)

103
86
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
103
86

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?