RIT Tech Blog

株式会社RITのエンジニアが知見を共有する技術ブログです。

Google Geocoding APIとSQLの関数を使った構文で半径15km以内のユーザーを取得する

こんにちは!RITエンジニアの崎田です。

今回は、Google Geocoding APISQLの関数を使った構文で半径15km以内のユーザーを取得する方法について解説してみようと思います。

使用環境

使用API

作成済みのテーブル

前提としてユーザーがそれぞれ以下のテーブル情報(名前/住所/緯度/経度)を持っていることとします

カラム名
name string
address string
latitude float
longitude float

どのようにして半径15km以内のユーザーを取得するか?

球面三角法の余弦定理を用いて、探す際に基準とする住所の緯度経度と他のユーザーの住所の緯度経度から2点間の距離を算出した上で、15km以内の距離に該当する住所を持つユーザーをSQLで抽出します

球面三角法の余弦定理について

6371 * acos( sin(radians(a地点の緯度)) * sin(radians(b地点の緯度)) + cos(radians(a地点の緯度) * cos(radians(b地点の緯度)) * cos(radians(b地点の軽度) - radians(a地点の経度))

上記の式でa地点とb地点の距離を算出できます (※6371kmは地球のおおよその半径)

以下がUserモデルに記述した球面三角法の余弦定理を用いた半径15km以内のユーザーを取得するSQL文を含むスコープ

class Users < ApplicationRecord
  # 中略
  scope :within_15km, lambda { |lat, lng|
    query = "SELECT id, latitude, longitude, user_id,
               (
                 6371 * acos(
                   sin(radians(:lat)) * sin(radians(latitude))
                   +
                   cos(radians(:lat)) * cos(radians(latitude)) 
                   * cos(radians(longitude) - radians(:lng))
                 )
               ) AS distance
             FROM addresses HAVING distance <= 15 ORDER BY distance"
    User.find_by_sql([query, { lat: lat, lng: lng }])
  }
  # 中略
end


上で作成したスコープをUsersコントローラーで以下のように使用することで、指定した住所から半径15km以内のユーザー一覧を返すアクションを実装できます

注) GoogleのGeocoding APIAPIキー設定方法などに関しては割愛させていただきます。

  def index
    return area_search if params.key?(:area_name)

    users = User.all
    render json: users
  end

  # 中略

  def area_search
    # params[:area_name] には住所が入っている想定
    users = users_within_15km(params[:area_name])
    render json: users
  end

  # 中略

  # 住所をGeocoding APIのレスポンスに変換するメソッド
  def response_from_geocoding_api(address)
    escaped_address = CGI.escape(address)
    api_key = Rails.application.config.geocoding_api_key
    # APIを叩くためにgemとしてhttpartyを使用
    response = HTTParty.get("https://maps.googleapis.com/maps/api/geocode/json?address=#{escaped_address}&key=#{api_key}")
    raise StandardError, 'データの取得に失敗しました' unless response.code == 200

    response
  end

  # Geocoding APIのレスポンスのJSONをパースして緯度経度のハッシュにして返すメソッド
  def response_to_geocode(response_body)
    parsed_response = JSON.parse(response_body)
    raise StandardError, 'レスポンスのステータスがOKではありません' unless parsed_response['status'] == 'OK'

    latitude = parsed_response['results'][0]['geometry']['location']['lat']
    longitude = parsed_response['results'][0]['geometry']['location']['lng']
    { latitude: latitude, longitude: longitude }
  end

  # 引数の住所から半径15km以内のユーザーを返すメソッド
  def users_within_15km(address)
    # 同じ住所のレスポンスはキャッシュしておくことで無駄なAPIへのリクエストを減らす
    response_body = Rails.cache.fetch(address, expires_in: 1.day) do
      response = response_from_geocoding_api(address)
      response.body
    end
    geocode = response_to_geocode(response_body)
    User.within_15km(geocode[:latitude], geocode[:longitude])
  end


[参考にさせていただいたサイト]
大円距離 - Wikipedia
[MySQL]指定した緯度経度を中心に指定半径内のスポットデータを近い順に取得する(geometry型不使用編) - Qiita


以上が、Google Geocoding APISQLの関数を使った構文で半径15km以内のユーザーを取得する実装方法になります。

最後まで読んでいただきありがとうございました。