🗺️
地理空間データとPostGIS
位置情報の格納・距離計算・範囲検索・経路探索をPostgreSQLで実現するPostGISの設計。空間インデックス(GiST/R-Tree)とGeoJSONの扱いを理解する
定義
PostGIS:PostgreSQLに地理空間機能を追加する拡張機能。点・線・ポリゴンなどの幾何データを格納し、距離計算・包含判定・空間結合などを効率的に実行できる。
なぜ通常のカラムでは不十分か
-- ナイーブな実装(アンチパターン)
CREATE TABLE stores (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
latitude DECIMAL(10, 8),
longitude DECIMAL(11, 8)
);
-- 「現在地から1km以内のお店」
SELECT *
FROM stores
WHERE
-- ピタゴラスの定理(地球が平面と仮定)
SQRT(POWER(latitude - 35.6895, 2) + POWER(longitude - 139.6917, 2)) < 0.009;
-- 問題1: 地球は球体なのでこの計算は不正確(高緯度で誤差が大きくなる)
-- 問題2: インデックスが使えない(SQRT関数のため)→ フルスキャン
PostGISのセットアップ
CREATE EXTENSION postgis;
CREATE EXTENSION postgis_topology; -- トポロジー演算が必要な場合
-- バージョン確認
SELECT PostGIS_Version();
基本的なデータ型と座標系
-- POINT型(経度・緯度の点)
-- SRID 4326 = WGS84(GPS・GoogleMapsが使う座標系)
CREATE TABLE stores (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
location GEOMETRY(POINT, 4326) -- 経度・緯度の点
);
-- データ挿入
INSERT INTO stores (name, location) VALUES
('新宿店', ST_SetSRID(ST_MakePoint(139.6917, 35.6895), 4326)),
('渋谷店', ST_SetSRID(ST_MakePoint(139.7020, 35.6580), 4326)),
('池袋店', ST_SetSRID(ST_MakePoint(139.7109, 35.7295), 4326));
-- GeoJSONからも作成できる
INSERT INTO stores (name, location) VALUES
('銀座店', ST_SetSRID(ST_GeomFromGeoJSON('{"type":"Point","coordinates":[139.7651, 35.6717]}'), 4326));
距離計算
-- ST_Distance: 度単位(不正確)
SELECT name, ST_Distance(location, ST_MakePoint(139.7020, 35.6580)::geometry)
FROM stores;
-- ST_DistanceSphere: メートル単位(球面近似)
SELECT
name,
ROUND(ST_DistanceSphere(
location,
ST_SetSRID(ST_MakePoint(139.7020, 35.6580), 4326) -- 渋谷駅
)::numeric) AS distance_m
FROM stores
ORDER BY distance_m;
-- Geography型を使う(より正確、日本全土でも精度が高い)
ALTER TABLE stores ADD COLUMN location_geo GEOGRAPHY(POINT, 4326);
UPDATE stores SET location_geo = location::geography;
SELECT name, ST_Distance(location_geo, ST_MakePoint(139.7020, 35.6580)::geography) AS distance_m
FROM stores
ORDER BY distance_m;
範囲検索(半径N km以内)
-- 渋谷駅から1km以内のお店(Geographyで正確な距離)
SELECT name, ST_Distance(location_geo, ST_MakePoint(139.7020, 35.6580)::geography) AS distance_m
FROM stores
WHERE ST_DWithin(
location_geo,
ST_MakePoint(139.7020, 35.6580)::geography,
1000 -- 1000メートル = 1km
)
ORDER BY distance_m;
空間インデックス(GiSTインデックス)
-- GiSTインデックスの作成(R-Treeベース)
CREATE INDEX idx_stores_location ON stores USING GIST (location);
CREATE INDEX idx_stores_location_geo ON stores USING GIST (location_geo);
-- インデックスが使われる確認
EXPLAIN SELECT * FROM stores
WHERE ST_DWithin(location_geo, ST_MakePoint(139.7020, 35.6580)::geography, 1000);
-- → Index Scan using idx_stores_location_geo が表示されれば OK
R-Treeの仕組み
GiSTインデックスはR-Tree(Rectangle Tree)を使用:
各ノードが「バウンディングボックス(MBR)」を持つ
ノード1: [東京都内の全店舗のMBR]
ノード2: [新宿・渋谷エリアのMBR]
→ 新宿店(点)
→ 渋谷店(点)
ノード3: [池袋エリアのMBR]
→ 池袋店(点)
検索: クエリの円とMBRが重なるノードだけ探索
対象外のノードは全部スキップ
ポリゴン操作
-- 配達エリア(ポリゴン)
CREATE TABLE delivery_zones (
id SERIAL PRIMARY KEY,
store_id INT REFERENCES stores(id),
zone GEOMETRY(POLYGON, 4326)
);
INSERT INTO delivery_zones (store_id, zone) VALUES (
1,
ST_GeomFromText('POLYGON((
139.68 35.67,
139.70 35.67,
139.70 35.70,
139.68 35.70,
139.68 35.67
))', 4326)
);
-- 現在地がどの配達エリアに含まれるか
SELECT s.name
FROM stores s
JOIN delivery_zones dz ON dz.store_id = s.id
WHERE ST_Contains(
dz.zone,
ST_SetSRID(ST_MakePoint(139.6950, 35.6850), 4326) -- ユーザーの現在地
);
-- 2つのエリアの重なり
SELECT ST_Intersection(zone1, zone2) FROM ...;
-- エリアの面積(平方メートル)
SELECT ST_Area(zone::geography) AS area_sq_m FROM delivery_zones;
GeoJSONとの連携
-- PostGIS → GeoJSON(フロントエンドへの送信)
SELECT
name,
ST_AsGeoJSON(location)::json AS geojson
FROM stores;
-- 結果: {"type":"Point","coordinates":[139.6917,35.6895]}
-- GeoJSON → PostGIS(フロントエンドからの受信)
SELECT ST_GeomFromGeoJSON($1) AS geometry;
// TypeScriptでのGeoJSON操作
const result = await db.query(`
SELECT name, ST_AsGeoJSON(location)::json AS geometry
FROM stores
WHERE ST_DWithin(location_geo, ST_MakePoint($1, $2)::geography, $3)
ORDER BY location_geo <-> ST_MakePoint($1, $2)::geography
LIMIT 10
`, [longitude, latitude, radiusMeters]);
// GeoJSON Feature Collectionとして返す
const features = result.rows.map(row => ({
type: 'Feature',
geometry: row.geometry,
properties: { name: row.name },
}));
KNN(K近傍)検索
-- <-> 演算子でインデックスを使ったKNN検索
-- 渋谷駅に最も近い5店舗
SELECT
name,
ROUND(location_geo <-> ST_MakePoint(139.7020, 35.6580)::geography) AS distance_m
FROM stores
ORDER BY location_geo <-> ST_MakePoint(139.7020, 35.6580)::geography
LIMIT 5;
-- → GiSTインデックスを使って効率的に検索(全件スキャンなし)
クラスタリング(ヒートマップ用)
-- グリッドベースの集計(ヒートマップ用)
SELECT
ST_SnapToGrid(location, 0.01) AS grid_point, -- 約1km格子
COUNT(*) AS count
FROM stores
GROUP BY grid_point;
主な用途
| 用途 | 使う関数 |
|---|---|
| 近くのお店を探す | ST_DWithin, <-> |
| 配達エリア判定 | ST_Contains, ST_Within |
| 2点間の距離 | ST_Distance, ST_DistanceSphere |
| エリアの重なり | ST_Intersects, ST_Intersection |
| ジオコーディング | Nominatim + PostGIS |
| ルート検索 | pgRouting(別拡張) |
関連概念
- → ストレージとインデックス(R-TreeとBツリーの違い)
- → 全文検索と転置インデックス(GiSTインデックスの別の応用)
- → パーティショニング(地理的なパーティション分割)
出典・参考文献
- PostGIS Documentation — postgis.net/docs
- OGC Simple Features Specification — ogc.org
- Boundless, “Introduction to PostGIS” — workshops.boundlessgeo.com/postgis-intro
- 1. 🗄️データ志向アプリケーション設計:概要
- 2. 🧩データモデルとクエリ言語
- 3. 💾ストレージエンジンとインデックス
- 4. 🔁レプリケーション
- 5. 🍕パーティショニング(シャーディング)
- 6. 🔒トランザクションとACID
- 7. ⚡分散システムの本質的な問題
- 8. 🤝一貫性と分散合意
- 9. 📦バッチ処理
- 10. 🌊ストリーム処理
- 11. 📋エンコーディングとスキーマ進化
- 12. 🔗Sagaパターンと分散トランザクション
- 13. 🏗️データシステムの統合設計
- 14. 📸MVCC(多版型同時実行制御)
- 15. 📊列指向ストレージとOLAP設計
- 16. 🕰️ベクタークロックと因果順序
- 17. 🔀CRDT(競合なし複製データ型)
- 18. 🔍クエリオプティマイザーと実行計画
- 19. ⚡キャッシュ戦略とRedis設計
- 20. 🔎全文検索と転置インデックス
- 21. 🌐NewSQL(分散ACIDデータベース)
- 22. 📝WALと論理レプリケーション
- 23. 🔌コネクションプーリング
- 24. 🚧ゼロダウンタイムマイグレーション
- 25. 🆔分散ID生成
- 26. 🔄N+1問題とDataLoaderパターン
- 27. 📈タイムシリーズDB
- 28. 🛡️Row Level Security(行レベルセキュリティ)
- 29. 📤Outboxパターン(トランザクショナルアウトボックス)
- 30. 💾DBバックアップとPITR
- 31. ⚠️データベース設計アンチパターン
- 32. 🕸️グラフDB深掘り
- 33. 🔋バックプレッシャーとサーキットブレーカー
- 34. 🔵コンシステントハッシング
- 35. 📋マテリアライズドビュー
- 36. 📡DBモニタリングとオブザーバビリティ
- 37. 🔐データプライバシーとCrypto-Shredding
- 38. ✂️垂直分割(Vertical Partitioning)
- 39. 🪟ウィンドウ関数
- 40. 🧲ベクトルDBとpgvector
- 41. 🔧dbtとデータ変換パイプライン
- 42. 📬ジョブキューの設計
- 43. 📐正規化理論(1NF〜BCNF)
- 44. ☁️クラウドDBサービスの設計思想と選択基準
- 45. 🗺️地理空間データとPostGIS
- 46. 🔑DBセキュリティと権限管理
- 47. 🏔️Lakehouse(Apache Iceberg / Delta Lake)
- 48. 📜データコントラクト
- 49. 🔭OpenTelemetryとDBトレーシング
出典: PostGIS Documentation / OGC Simple Features Specification