僕は発展途上技術者

Rails 7アプリ on Heroku でPostGISを使い、外部APIを使わず緯度経度から県名を導く

PostGISを扱う練習として、HerokuでRails 7アプリからPostGISを使えるように設定し、緯度経度を与えたときに、その場所を含む県名を取得して表示するようなサービスを作ってみました。

DockerでRails 7アプリを動かす

第一歩として、ローカル環境でRails 7のアプリを動かします。Docker DesktopなどをインストールしていてDockerのセットアップが済んでいることが前提です。

ryanwi/rails7-on-docker

にあるコードをgit cloneし、READMEにある通り、セットアップをおこなって動かします。

http://localhost:3000 にアクセスすれば、以下のようにトップ画面が表示されます。

PostGISを有効にする

以下の3箇所を修正してローカルのDocker環境でPostGISを有効にします。

Gemfileに以下を追加します。

gem 'activerecord-postgis-adapter'

config/database.ymlのadapterをpostgisに変更します。

default: &default
  adapter: postgis

docker-compose.ymlでdbホストのイメージを変更します。

  db:
    image: mdillon/postgis

またPostGISを有効にするためのマイグレーションファイルを作成します。

docker-compose exec web rails g migration AddPostgisExtensionToDatabase

で生成したマイグレーションファイルの中身は

class AddPostgisExtensionToDatabase < ActiveRecord::Migration[7.0]
  def change
    enable_extension 'postgis'
  end
end

とします。

これらの変更をおこなった上で、再度docker-compose buildから環境の再構築をおこなうのですが、その前にdocker volume rmで、最初に作ってしまったデータベース用のdockerボリュームを削除しておきます。

docker volume rm rails7-on-docker_pg_data

これを行った上で、docker-compose buildから環境の再構築をおこないます。

再構築が終わり、再びdocker-compose upでコンテナを起動したら、

docker-compose exec db bash

の後、

psql -U postgres railsondocker_development

でpostgresに接続後、SELECT postgis_version(); を実行して、以下のようにpostgisのバージョン情報が出力されれば、PostGISの設定が有効となっています。

postgres=# SELECT postgis_version();
            postgis_version            
---------------------------------------
 2.5 USE_GEOS=1 USE_PROJ=1 USE_STATS=1
(1 row)

Herokuで動かす

ここまでのコード変更は自分のGitHubリポジトリにコミット済みであることが前提で、Herokuで動かす手順は以下の通り。

GitHubリポジトリに接続し、

Automatic Deploysを有効にします。

config/database.ymlのproductionの部分を以下の通り書き換え、

production:
  <<: *default
  url: <%= ENV.fetch('DATABASE_URL', '').sub(/^postgres/, "postgis") %>

git pushすると、自動的にHerokuにデプロイされます。

最初、db:migrateが必要なので、

heroku run rails db:migrate -a <アプリ名>

を実行します。

heroku pg:psql -a <アプリ名>

でHerokuのDB(postgres)に接続し、ローカル環境のときと同様、SELECT postgis_version(); を実行して、PostGISの設定が有効となっていることを確認します。

緯度経度情報を保存するPointモデルを作成

緯度と経度、そして県名を保存できるPointモデルを操作するscaffoldを作成します。

docker-compose exec web rails g scaffold Point 'lat:decimal{10,2}' 'lng:decimal{10,2}' prefecture:string

migrationを実行します。

docker-compose exec web rails db:migrate 

県のポリゴンデータ

県のポリゴンデータを作成するための準備として市区町村のポリゴンデータをPostGISに取り込みます。

国土交通省の国土数値情報ダウンロードのページから「全国」のデータをダウンロードし解凍します。(令和3年のデータファイルはN03-20210101_GML.zip)

国土数値情報 | 行政区域データ

解凍したフォルダN03-20210101_GMLはアプリ直下のdbフォルダの下に置いておきます。

docker-compose.ymlを以下のように編集し、docker-compose buildで、コンテナを再構築し、上記フォルダにコンテナからアクセスできるようにしておきます。

  db:
    image: mdillon/postgis
    env_file:
      - .env/development/database
    volumes:
      - .:/usr/src/app <-- この行を追加
      - db_data:/var/lib/postgresql/data
docker-compose exec db bash

にdbホストに接続し、シェープファイルからSQLファイルを生成するためのコマンドshp2pgsqlを使って

shp2pgsql -c N03-20210101_GML\\N03-21_210101.shp shapes > shapes.sql

で全国の市区町村のポリゴンデータをPostGISに取り込むためのSQLファイルを作成します。

次に

psql -U postgres railsondocker_development < shapes.sql

を実行して、ポリゴンデータをshapesテーブルに取り込みます。

次に

psql -U postgres railsondocker_development

でpostgres接続後、

CREATE TABLE prefectures AS 
SELECT 
n03_001 AS pref_name,
ST_Multi(ST_Union(geom)) AS geom
FROM shapes
GROUP BY n03_001;

を実行して、各都道府県に属する市区町村のポリゴンデータを結合して、都道府県ごとのポリゴンデータをprefecturesテーブルに抽出します。

できあがったprefecturesテーブルをsqlファイルにエクスポートします。

pg_dump -U postgres --table prefectures railsondocker_development > prefectures.sql

Heroku 上では、このprefectures.sqlだけを読み込みます。

heroku pg:psql -a <アプリ名> < prefectures.sql

ポリゴンデータから緯度経度が所属する都道府県を導く

緯度、経度を入力し、Pointのデータを保存したときに、どの都道府県のポリゴンデータにその緯度・経度が含まれるかを調べ、その結果をprefectureにセットします。

以下の通り、Pointモデルのbefore_saveにその処理を定義します。

class Point < ApplicationRecord
  before_save :set_prefecture

  private
  def set_prefecture
    # 県名を取得
    res = ActiveRecord::Base.connection.select_all("SELECT pref_name FROM prefectures WHERE ST_Within(ST_GeomFromText('POINT(#{lng} #{lat})'), geom);")
    self.prefecture = res.first["pref_name"]
  end
end

Pointの入力フォームでprefectureを入力する必要はないので、その部分を削除します。

http://localhost:3000/points/new に削除し、たとえば以下のように緯度、経度を入力します。

Create Pointボタンをクリックすると、以下のようにその緯度、経度が属する都道府県が保存され、表示されます。

このとき、Geocodingなどの外部APIにアクセスしていないので、高速に検索できています。

参考リンク

プロフィール

株式会社まちクエスト代表、つくる社LLC代表。

Scratchで楽しく学ぶ アート&サイエンスRaspberry Piではじめる どきどきプログラミングを書きました。

オンラインコンテンツ: 大人のためのScratch

Amazonから図書館検索 Libron、iPhoneアプリ ひらがなゲーム かなぶん を作っています。

Email: webmaster at champierre dot com

Twitter @jishiha

最近のエントリー

アーカイブ