Rails アプリケーションで、経度緯度をつかって位置情報を使い時がありました。
PostGIS は PostgreSQL の拡張です。これを使うと、その経度緯度をデータベースのカラムに入れることができて、また 2 点間の距離を計算したりすることができるようです。
この記事では、
- ローカル開発環境を Docker でつくる
- heroku で動くように設定する
の2つをかきます。
ローカル開発環境を Docker でつくる
やることは
- postgis のイメージをつかっちゃう
- activerecord-postgis-adapter という gem つかう
- database.yml の設定
-
rake db:create
を実行する - model と migration をつくって動かしてみる
イメージとして、とても簡単な以下のテーブルをつくってみます。
stations テーブル
name | lonlat |
---|---|
渋谷 | 経度緯度の情報 |
新宿 | 経度緯度の情報 |
感想: PoistGIS を知る前は、 longitude(経度)
と latitude(緯度)
をそれぞれカラムで持つのかなぁと思っていたのですが。 PostGIS を使うと、上のように lonlat
というひとまとめにしたデータを持つことができます。
Step.1 Image
ローカル開発環境は、docker-compose で用意しています。
なので、端折って書きますが。以下のようにイメージを指定すると完了です。
version '3'
services:
app:
db:
- image: postgres:10
+ image: mdillon/postgis:10
kvs:
PostGIS のイメージは こちら にまとまっていますが、その中から今回は上を選択しました。
Step.2 Gem
gem 'activerecord-postgis-adapter'
こちらの gem を使います。
https://github.com/rgeo/activerecord-postgis-adapter
以降の Step.3-5 は基本的に activerecord-postgis-adapter
のドキュメントに書いてある通りですので、詳細はそちらを参照ください。
Step.3 database.yml
default: &default
- adapter: postgresql
+ adapter: postgis
Step.4 Command
今回は、すでに PostgreSQL で諸々環境を用意したあとに、PostGIS に切り替えていますので、ドキュメントにある通り以下のコマンドを叩きました。
$ rake db:gis:setup
Step.5 Migration
簡単な migration をつくります。
class CreateStations < ActiveRecord::Migration[5.1]
def change
create_table :stations do |t|
t.string :name
t.st_point :lonlat, geographic: true
t.timestamps
end
end
end
class Station < ApplicationRecord
end
これで、 rails console
で、動作を確認してみます。
# データ作成は、直感的です。
pry(main)> Station.create(name: "hoge", lonlat: 'POINT(30 40)')
# こんな感じで、ある場所から近いところを取れることを確認しました。
pry(main)> Station.where("ST_Distance(lonlat, 'POINT(30 50)') < 100000000").order("ST_Distance(lonlat, 'POINT(30 50)')")
=> Station Load (0.8ms) SELECT "stations".* FROM "stations" WHERE (ST_Distance(lonlat2, 'POINT(30 50)') < 100000000) ORDER BY ST_Distance(lonlat2, 'POINT(30 50)')
[#<Station:0x0000000004fe0840
id: 2,
name: "piyo",
lonlat1: #<RGeo::Cartesian::PointImpl:0x283f714 "POINT (30.0 40.0)">,
lonlat2: #<RGeo::Geographic::SphericalPointImpl:0x2830f84 "POINT (30.0 40.0)">,
created_at: Mon, 15 Jan 2018 19:03:43 JST +09:00,
updated_at: Mon, 15 Jan 2018 19:03:43 JST +09:00>,
#<Station:0x0000000004fe0688
id: 1,
name: "hoge",
lonlat1: #<RGeo::Cartesian::PointImpl:0x2882474 "POINT (-122.0 46.0)">,
lonlat2: #<RGeo::Geographic::SphericalPointImpl:0x28a2878 "POINT (50.0 1.0)">,
created_at: Mon, 15 Jan 2018 19:02:49 JST +09:00,
updated_at: Mon, 15 Jan 2018 19:02:49 JST +09:00>]
以上で、ローカル環境は整ったと思います。
heroku で動かす
cf. heroku document
https://devcenter.heroku.com/articles/postgis
heroku で動かすには、(すこしはまったのですが)以下の対応をすると動きました。
- postgres を Add-on でいれておく
- heroku-geo-buildpack という Buildpack を入れる
- database.yml を書き換える (<= これにハマりましたが @namitop が解決策を教えてくれました 😁
Add-on と Buildpack は普通に入ると思います。
一応 heroku-geo-buildpack には以下の順番で入っていたので、その順番にあわせてあります。
$ heroku buildpacks
=== sushi Buildpack URLs
1. https://github.com/cyberdelia/heroku-geo-buildpack.git
2. heroku/ruby
database.yml の書き換え
これに気づかなくて大変でした。
production:
<<: *default
database: eikaiwa_production
+ url: <%= ENV.fetch('DATABASE_URL', '').sub(/^postgres/, "postgis") %>
該当の heroku document はこちらです。
https://devcenter.heroku.com/articles/rails-database-connection-behavior#active-record-4-1-escape-valve
以上で、heroku でも動きました。
まだ、PostGIS は出会ったばかりで、距離計算とかの仕様をこれから勉強する状況ですが、とても楽しそうです 🍏
ありがとうございました。