【Ruby on Rails】緯度経度から 2 点間の距離を算出する

概要

最近では企業が多くの API を公開しており、それらを利用して簡単にアプリケーションを作成できるようになりました。

その中には、店舗情報を扱う API も数多く存在します。

店舗情報を扱う API の多くは、店舗の位置情報を表すため緯度経度を利用しています。

位置情報を利用したアプリケーションを作成する場合、緯度経度から 2 点間の距離を求めたいと思うことがあるかもしれません。

例えば、店舗情報 API と組み合わせて、『現在地周辺の半径 ○○ m 以内のレストランを近い順に表示したい』と思った場合、これに該当します。

今回、緯度経度から 2 点間の距離を算出するプログラムを作成する機会があったので、その時のことをまとめました。

 

計算式

地球は球体なので、地球上の 2 点間の距離を算出するには、大円距離を求める必要があります。

大円距離 - Wikipedia

大円距離の計算式はいくつかあるようですが、全ての距離に対して用いることのできる Vincenty 法を利用することとします。

Vincenty法 - Wikipedia

 

プログラムを作成

Vincenty 法に当てはめていきます。

def distance(lat1, lng1, lat2, lng2)
  # ラジアン単位に変換
  x1 = lat1.to_f * Math::PI / 180
  y1 = lng1.to_f * Math::PI / 180
  x2 = lat2.to_f * Math::PI / 180
  y2 = lng2.to_f * Math::PI / 180
  
# 地球の半径 (km) radius = 6378.137
# 差の絶対値 diff_y = (y1 - y2).abs
calc1 = Math.cos(x2) * Math.sin(diff_y) calc2 = Math.cos(x1) * Math.sin(x2) - Math.sin(x1) * Math.cos(x2) * Math.cos(diff_y)
# 分子 numerator = Math.sqrt(calc1 ** 2 + calc2 ** 2)
# 分母 denominator = Math.sin(x1) * Math.sin(x2) + Math.cos(x1) * Math.cos(x2) * Math.cos(diff_y)
# 弧度 degree = Math.atan2(numerator, denominator)
# 大円距離 (km) degree * radius end

 

abs は数値の絶対値を取得するメソッドです。

絶対値を取得する - 数値(Numeric)クラス - Ruby入門

数値計算用のメソッドはこちらを参考にしてください。

module Math (Ruby 2.4.0)

 

算出結果の確認

先ほどのプログラムで新宿駅と渋谷駅の距離を求めます。

# 新宿駅
lat1 = 35.689407
lng1 = 139.700306

# 渋谷駅
lat2 = 35.658034
lng2 = 139.701636

distance = distance(lat1, lng1, lat2, lng2)

# 小数点 6 桁で四捨五入
puts "#{distance.round(6)} km"

プログラムを実行します。

$ ruby distance.rb
3.494497 km

 

下記サイトで 2 点間の緯度経度を入力した結果と同じになりました。

2地点間の距離と方位角 - 高精度計算サイト

 

まとめ

緯度経度から 2 点間の距離を算出する場合、大円距離を求める必要があります。

プログラムは Vincenty 法の公式に当てはめれば実装できます。

 

最後に

Ruby on Rails には位置情報を扱う gem がいくつか用意されています。

github.com

github.com

今回、作成した 2 点間距離を求めるプログラムも機能として盛り込まれています。

他にも多くの機能が用意されているようなので gem を使ってみるのも有りだと思います。

自分のアプリケーションにあった方法を採用してください。

【Ruby on Rails】 gem を使わないで環境毎に定数管理をする

概要

Ruby on Rails には定数管理用の gem がたくさん存在します。

有名な gem だと以下の名前が上がるかと思います。

github.com

github.com

  • config は環境毎に yaml ファイルを用意して定数を管理するので、大規模アプリケーション向けのイメージがあります。管理する定数が多い場合にオススメです。
  • settingslogic は 1 つの yaml ファイルで環境毎に定数を管理するので、中小規模アプリケーション向けのイメージがあります。管理する定数が少ないのであれば見やすくてオススメです。

ただ、ここで『定数管理ぐらい自分で実装できないのか?』と疑問に思ったことがあります。

そこで今回、自分で簡単に実装できる環境毎に定数を管理する方法をまとめました。

 

はじめに

gem を使わない定数管理方法だと、以下の 2 つがオーソドックスな方法かと思います。

  • application_controller.rb で定数を管理する方法
  • config/initializers/constants.rb のようなファイルを用意して、そこで定数を管理する方法

application_controller.rb はアプリケーション共通の処理を管理する場所なので、ここで定数を管理するのはあまり相応しくないと思います。

config/initializers/constants.rb のようにファイルを用意して、そこで定数を管理する方法は賛成です。

ただし ruby ファイルではなく、環境毎に設定を管理しやすい yaml ファイルを利用しようと思います。

 

Yaml を利用した定数管理

用意するファイルは以下の2 つです。

  • config/constants.yml
  • config/initializers/00_load_config.rb

config/constants.yml

これが定数を管理するためのファイルです。

yaml の機能であるアンカーとエイリアスを利用して、環境毎に定数を出し分けています。

common の設定をベースに development と production で設定を上書きしている感じです。

# アプリケーション共通の設定を記載します
common: &common
  domain:
    example: http://example.com/
    port:
      - 80
      - 443
  message: This is application common setting

# 開発環境の設定を記載します
development:
  <<: *common
  message: This is development environment setting

# 本番環境の設定を記載します
production:
  <<: *common
  message: This is production environment setting

config/initializers/00_load_config.rb

これは先程の /config/constants.yml に記載した設定をアプリケーション起動時に読み込んで、ruby で扱える定数に変換しています。

file = "#{Rails.root}/config/constants.yml"

# 再帰的にオブジェクトを凍結します
def deep_freeze(hash)
  hash.freeze.each_value do |i|
    i.kind_of?(Hash) ? deep_freeze(i) : i.freeze
  end
end

CONFIG = deep_freeze(YAML.load_file(file)[Rails.env].deep_symbolize_keys)

 

deep_symbolize_keys は再帰的にシンボル形式に変換してくれます。

https://apidock.com/rails/v4.0.2/Hash/deep_symbolize_keys

freeze は オブジェクトを凍結、つまり変更不可 (定数) にします。

http://ref.xaio.jp/ruby/classes/object/freeze

 

動作確認

期待通り定数が取得出来ることをコンソール経由で試してみます。

設定した定数は以下のように CONFIG で呼び出す事が出来ます。

$ rails c
Loading development environment (Rails 5.0.2)
irb(main):001:0> CONFIG
=> {:domain=>{:example=>"http://example.com/", :port=>[80, 443]}, :message=>"This is development environment setting"}

# 開発環境用の設定に上書きされています
irb(main):002:0> CONFIG[:message]
=> "This is development environment setting"

# 凍結されています
irb(main):003:0> CONFIG.frozen?
=> true

# ネストした値も凍結されています
irb(main):004:0> CONFIG[:domain][:exapmle].frozen?
=> true 

# 変更しようとするとエラーになります
irb(main):005:0> CONFIG[:message] << 'Hello'
RuntimeError: can't modify frozen String

 

まとめ

gem を使わずに環境毎に定数管理する方法をまとめました。

割と簡単に出来たかなと思います。

自分で実装することのメリットはこの 2 つかなと思います。

  • 仕様を把握できる点
  • 自分で拡張できる点

あとは gem の依存関係などでどうしても定数管理用の gem をインストール出来ない場合などは使えると思います。

ただし、1 つの yaml ファイルで全ての定数を管理するので、中小規模アプリケーション向けの方法だと思います。

Ruby on Rails の定数管理方法に答えはないと思います。

どの方法も一長一短なので、開発しているアプリケーションに合った方法を見極めて採用するのがいいと思います。