静岡市でフリーランスでWeb開発しているkazuomatzです。
Rails + MySQLで位置情報を扱う需要は、少なからずあるかと思います。
Rails + MySQLで位置情報を扱う場合、activerecord-mysql2spatial-adapterが定番のGemかと思うのですが、このGemの開発が止まっていて、Railsのバージョンアップのたびに、有志の開発者の方が本家からBranchしたバージョンを独自リリースしている状態というエントリーを記事にしたのですが、あまり反響がなかったので、そういう需要はもはやないのかなと思っておりました。
そんな中、この記事を見た方から、そもそもactiverecord-mysql2spatial-adapterの使い方がまとめれているところがあまりないという問い合わせがあったので、簡単に書き留めておきます。
こちらの記事に、Rails 4.2, 5.0, 5.1, 6.0に対応したGemfileの書き方を記述してあります(Rails6.0の対応については、僕がやりました)。こちらを参照にGemfileを記述して、bundle installします。
config/database.yml
adapterをmysql2spatialにする。ここがポイントです。
default: &default
adapter: mysql2spatial
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: xxxx
password: xxxxx
socket: /tmp/mysql.sock
:
:
:
マイグレーションファイル
位置情報を含むモデルを作成するためのマイグレーションファイルです。
t.point (t.geometry でもOK)型として宣言します。nullは許容しないようにします。
また、インデックスを作成する場合は、spatial: trueとします。
MySQL 5.6以前の場合、spatial: trueの指定をする場合は、ストレージエンジンがMyISAMである必要があります。MySQL 5.7以降であればInnoDBでOKです。
class CreateLocations < ActiveRecord::Migration[6.0]
def change
create_table :locations do |t|
t.string :name, comment: 'スポット名称'
t.point :geom, null: false, comment: '緯度経度'
end
add_index :locations, :geom, spatial: true
end
Pointクラスを作る
Geometry型に緯度経度を設定するために便利なPointクラスを作っておきます。
# Geometry型に緯度経度を設定するためのPointクラス
class Point
def self.from_x_y(x, y)
x.present? && y.present? ? "POINT(#{x} #{y})" : nil
end
end
緯度経度の設定と参照
あとは、以下のようにすれば、設定と参照が可能になります。
location = Location.new
location.name = '東京駅'
location.geom = Point.from_x_y( 139.767125, 35.681236 )
location.save!
# 緯度経度参照
p location.geom.x # -> 139.767125
p location.geom.y # -> 35.681236
2023年9月更新
ことの発端は、AWSのRDSがMySQL5.7を年内にサポートを打ち切りにするという話から。
これまで、いろいろなWebサービスをactiverecord-mysql2spatial-adapterを使って開発してきましたが、MySQL5.7をいよいよMySQL8.0に移行する必要が出てきました。
Rails6.0については、こちらに書きましたが、本家からforkさらたリポジトリをさらにforkして何とか対応した経緯があります。
今回、Rails4.2、Rails5.0、Rails5.1で開発されたプロジェクトのデータベースをMySQL8.0に移行して検証したら、位置情報系のカラムの参照や更新でエラーが発生することが判明しました。
Mysql2::Error: FUNCTION some_scheme.GeomFromWKB does not exist.
この件についてはこちらに詳しく書きました。