初めて地理空間データを扱うシステムを作ることになり、色々と調べていたところ見つけたのがPostGISでした。この記事では、PostGISの導入や使い方を備忘録として残しつつ、実際にDocker上でPostGISを動かしてみるところまで実施してみます。
PostGISとは
PostgreSQL + GISなので、PostgreSQLに地理空間データ型と空間演算を追加する拡張機能です。地理空間データとは、地球上の実際の位置とリンクするデータのこと。GISとは、Geographic Information Systemの略で、地理空間情報を管理・加工・表示・分析する仕組み・技術のことを指します。
データベース上で地理空間データを扱えるようになります。
Docker上で動かしてみる
環境
- Apple Silicon Mac
- macOS Tahoe 26
今回は、postgis/postgisのDocker Imageが手元の環境では利用できなかったため、postgres:17.5-bullseyeのDocker Image上にPostGIS環境を構築します。
まずは、適当なディレクトリに下記構成のファイルを準備します。
構成
.
├── Dockerfile
├── docker-compose.yml
└── initdb
└── 01-enable-postgis.sql
FROM postgres:17.5-bullseye
# PostGIS拡張を追加する
# shp2pgsql はPostGISパッケージに含まれる
# ogr2ogr はGDALパッケージ(gdal-bin)で追加する
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
postgresql-17-postgis-3 \
postgresql-17-postgis-3-scripts \
gdal-bin \
&& rm -rf /var/lib/apt/lists/*
services:
postgis:
# 同じディレクトリにある Dockerfile から独自イメージをビルドする
# ベースは postgres:17.5-bullseye、そこへPostGISを追加したもの
build:
context: .
dockerfile: Dockerfile
# ビルド済みイメージに付けるローカル用の名前
image: oita-postgis:17.5
# コンテナ名を固定する(docker compose exec などで指定しやすくなる)
container_name: oita-postgis
environment:
# 初回起動時に作成するデータベース名
POSTGRES_DB: gis_db
# PostgreSQLの接続ユーザー名
POSTGRES_USER: gis_user
# PostgreSQLの接続パスワード
# 記事用の値。本番では .env ファイルなどで管理すること
POSTGRES_PASSWORD: change_me
ports:
# ホスト側の5432番ポートから、コンテナ内PostgreSQLの5432番へ接続する
# DBeaverでは localhost:5432 を指定する
- "5432:5432"
volumes:
# DBデータを名前付きボリュームへ永続化する
# コンテナを作り直しても、通常はデータが消えない
- postgis_data:/var/lib/postgresql/data
# 初回DB作成時に実行する初期化SQLを読み取り専用で渡す
# 例: initdb/01-enable-postgis.sql で CREATE EXTENSION postgis; を実行する
- ./initdb:/docker-entrypoint-initdb.d:ro
# GeoJSONの置き場をコンテナへ読み取り専用で渡す
# 起動時にデータを自動投入するものではない
- ./data:/data:ro
volumes:
# PostgreSQLのデータを保持する名前付きボリューム
postgis_data:
-- 初回DB作成時にだけ実行される初期化SQL
CREATE EXTENSION IF NOT EXISTS postgis;
SELECT
PostGIS_Full_Version();
initdbフォルダに格納されたsqlファイルは初回起動時データディレクトリが空の場合に実行されます。
01-enable-postgis.sqlファイルに記載のコマンドは、PostgreSQLにPostGISを追加して反映を確認するコマンドです。
実行
ターミナルを開いて作業ディレクトリに移動後、下記のコマンドを叩くことで実行できます。
docker compose up -d
動きが遅いなと感じる場合は、動作環境の命令セットアーキテクチャに対応してないDocker Imageを使っている可能性があります。特に、Apple Silicon版 Macを使用する場合は要確認です。
DBeaverで接続する
docker-compose.ymlに記載した設定情報を確認してDBeaverから接続します。
接続設定
-
DBeaverを開いたら、画面左上のコンセントマーク(New Database Connection)をクリックします
-
ウィンドウが開いたらPostgreSQLを選択します
-
接続情報を入力する
接続情報を入力後、左下の
Test Connectionボタンをクリックし接続を確認してください。確認できたらOKをクリックし設定は完了です。 -
DBeaverのConnectionsタブを確認すると、下画像のように作成したDBが表示されているはずです
初期状態では、
spatial_ref_sysテーブルが作られています。このテーブルは、地理空間データベースで利用するSRIDと空間参照系の定義を保持するテーブルです。
PostGISの操作
データの登録
今のままでは何のデータも入っていない状態なので、オープンデータからGeojsonやShapefileを取ってきます。今回は、国土数値情報(医療機関)-大分県を使用します。
出典:国土交通省国土数値情報ダウンロードサイト(https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-P04-2020.html)
作業ディレクトリにdataフォルダを作成し、その中にダウンロードしたデータを格納します。
そして、次のコマンドで格納したGeoJSONファイルを読み込みます。
docker compose exec \
-e PGPASSWORD=change_me \
postgis \
ogr2ogr \
-f PostgreSQL \
"PG:host=127.0.0.1 port=5432 dbname=gis_db user=gis_user" \
/data/P04-20_44.geojson \
-nln public.medical_facilities \
-lco GEOMETRY_NAME=geom \
-lco FID=id \
-overwrite
その後、DBeaverをリフレッシュするとmedical_facilitiesテーブルが表示されます。
それでは取り込んだデータをDBeaverを使って見ていきましょう。テーブル内のdataタブを開くとDBの中身を確認できます。また、中身の表示方法をGridからSpatialに変更すると下画像のように地図に表示することができます。
関数を使ってみる
PostGISには空間演算を行うための空間関数が用意されています。
GISとして動く様子が伝わりやすい関数をピックアップしてみました。
| 分類 | 関数・演算子 | 何ができるか |
|---|---|---|
| 確認 | ST_SRID |
ジオメトリに設定されたSRIDを取得する |
| 確認 | ST_AsText |
ジオメトリをWKT形式で表示する |
| 出力 | ST_AsGeoJSON |
ジオメトリやレコードをGeoJSON形式の文字列へ変換する |
| 変換 | ST_Transform |
ジオメトリを別の空間参照系へ座標変換する |
| 距離 | ST_Distance |
2つのジオメトリ間の距離を計算する |
| 範囲検索 | ST_DWithin |
指定した距離以内に存在するか判定する |
| 空間関係 | ST_Within |
あるジオメトリが別のジオメトリの内部にあるか判定する |
| 空間関係 | ST_Intersects |
2つのジオメトリが点を共有しているか判定する |
| 範囲作成 | ST_Buffer |
ジオメトリの周囲に指定距離の領域を作成する |
| 近傍検索 | <-> |
GiSTインデックスを利用したKNN近傍検索で距離順に並べる |
ここからは、ピックアップしたコマンドを実際に叩いてみます。
ジオメトリを読みやすいWKTで表示する
SELECT
p04_004 AS specialty,
ST_AsText(geom) AS location
FROM medical_facilities
WHERE p04_004 = '歯科' LIMIT 5;
結果として、診療科目1が『歯科』と一致する医療機関の位置情報を取得できました。
| specialty | location |
|---|---|
| 歯科 | POINT(131.515383 33.1998214) |
| 歯科 | POINT(131.4324019 33.0043422) |
| 歯科 | POINT(131.4758285 32.9724497) |
| 歯科 | POINT(131.4761782 32.97398195) |
| 歯科 | POINT(131.5822865 32.9748558) |
ジオメトリやレコードをGeoJSON形式の文字列へ変換する
ST_AsGeoJSON()でGeoJSONとして取得できます。
SELECT
p04_004 AS specialty,
ST_AsGeoJSON(geom) AS geometry
FROM medical_facilities
WHERE p04_004 = '歯科' LIMIT 1;
出力されたgeometryにGeoJSONが入ってました。
{
"type":"Point",
"coordinates":[131.515383,33.1998214]
}
大分駅付近から5km以内を探す
大分駅の座標(131.606 33.233)を基準として5kmを探します。
まず、ST_MakePointで大分駅のポイントを作成し、ST_SetSRIDで座標系をセットしています。今回取り込んだGeoJSONのSRIDは4326です。そのため、大分駅のポイントもSRID 4326で作成します。距離をメートル単位で計算するため、検索前に大分県で利用できる平面直角座標系第II系へST_Transformで変換し、ST_DWithinで範囲検索します。座標単位はメートルのため、第3引数には5000を渡します。
WITH origin AS (
SELECT ST_SetSRID(
ST_MakePoint(131.606, 33.233),
4326
) AS geom
)
SELECT
COUNT(*) AS facility_count
FROM medical_facilities AS m
CROSS JOIN origin AS o
WHERE ST_DWithin(
ST_Transform(m.geom, 6670),
ST_Transform(o.geom, 6670),
5000
);
結果として、425件がヒットしました。
| facility_count |
|---|
| 425 |
終わりに
PostGISを導入することで、PostgreSQL上で地理空間データを保存し、距離検索や範囲検索、交差判定などを実行できるようになりました。静的なマップタイルだけで情報を配信する構成と比べると、データベース側で検索条件を動的に変更したり、属性情報と組み合わせて集計したりしやすくなります。
今回は扱いませんでしたが、PostGISでは次のような処理も実行できます。
- ポリゴン内に存在する施設の検索
- ポリゴン同士の交差判定
- 重複領域の算出
- 最寄り施設の検索
- バッファ領域の作成
- 空間インデックスを利用した高速な近傍検索
PostGISには、まだ把握できていないものも含めて多数の空間関数があります。
今後も、実際のGIS開発で必要になる機能を試しながら学んでいきたいと思います。




