Rails で PostGIS を使う方法(Docker, Heroku)

More than 1 year has passed since last update.

Rails アプリケーションで、経度緯度をつかって位置情報を使い時がありました。

PostGIS は PostgreSQL の拡張です。これを使うと、その経度緯度をデータベースのカラムに入れることができて、また 2 点間の距離を計算したりすることができるようです。

この記事では、


  • ローカル開発環境を Docker でつくる

  • heroku で動くように設定する

の2つをかきます。


ローカル開発環境を Docker でつくる

やることは


  1. postgis のイメージをつかっちゃう

  2. activerecord-postgis-adapter という gem つかう

  3. database.yml の設定


  4. rake db:create を実行する

  5. model と migration をつくって動かしてみる

イメージとして、とても簡単な以下のテーブルをつくってみます。

stations テーブル

name
lonlat

渋谷
経度緯度の情報

新宿
経度緯度の情報

感想: PoistGIS を知る前は、 longitude(経度)latitude(緯度) をそれぞれカラムで持つのかなぁと思っていたのですが。 PostGIS を使うと、上のように lonlat というひとまとめにしたデータを持つことができます。


Step.1 Image

ローカル開発環境は、docker-compose で用意しています。

なので、端折って書きますが。以下のようにイメージを指定すると完了です。


docker-compose.yml

version '3'

services:
app:
db:
- image: postgres:10
+ image: mdillon/postgis:10
kvs:

PostGIS のイメージは こちら にまとまっていますが、その中から今回は上を選択しました。


Step.2 Gem


Gemfile

gem 'activerecord-postgis-adapter'


こちらの gem を使います。

https://github.com/rgeo/activerecord-postgis-adapter

以降の Step.3-5 は基本的に activerecord-postgis-adapter のドキュメントに書いてある通りですので、詳細はそちらを参照ください。


Step.3 database.yml


database.yml

default: &default

- adapter: postgresql
+ adapter: postgis


Step.4 Command

今回は、すでに PostgreSQL で諸々環境を用意したあとに、PostGIS に切り替えていますので、ドキュメントにある通り以下のコマンドを叩きました。

$ rake db:gis:setup


Step.5 Migration

簡単な migration をつくります。


create_stations.rb

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


Station.rb

class Station < ApplicationRecord

end

これで、 rails console で、動作を確認してみます。


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 の書き換え

これに気づかなくて大変でした。


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 は出会ったばかりで、距離計算とかの仕様をこれから勉強する状況ですが、とても楽しそうです 🍏

ありがとうございました。