こんにちは!RITエンジニアの崎田です。
今回は、Google Geocoding APIとSQLの関数を使った構文で半径15km以内のユーザーを取得する方法について解説してみようと思います。
使用環境
- Ruby on Rails (6.1.4.1)
- MySQL (8.0.27)
使用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 APIのAPIキー設定方法などに関しては割愛させていただきます。
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 APIとSQLの関数を使った構文で半径15km以内のユーザーを取得する実装方法になります。
最後まで読んでいただきありがとうございました。