お疲れ!最近、PostGISで空間データいじってるって聞いたけど、なかなか面白いだろ?あれ、一度使い始めると手放せなくなるんだよな。
今日は、PostGISを使う上で「これ、ちゃんと理解しとけよ!」っていう超重要なポイント、`Geometry`型と`Geography`型の違いについて、じっくり話しておこうか。この二つの型、見た目は似てるけど、その裏にある思想と計算の仕方が全然違うんだ。実務で使うなら、この違いを把握してないと、思わぬ落とし穴にはまったり、非効率なシステムを作っちゃったりするからな。
現場の先輩として、実践的な視点からその使い分けと注意点を伝授するぞ!
—
空間データ、PostGIS、その無限の可能性
そもそもPostGISってなんだっけ?ってところから、軽くおさらいしとこうか。PostGISは、PostgreSQLを「地理空間データベース」に変身させる強力な拡張機能だ。点、線、多角形といった地理情報をデータベースで扱えるようにして、距離計算、面積計算、包含関係のチェックとか、もう何でもござれ。
地図アプリ、ロケーションベースサービス、物流管理、都市計画…挙げればキリがないくらい、世の中のいろんなシステムでPostGISが裏側を支えてるんだ。お前が普段使ってるあのサービスも、実はPostGISのお世話になってるかもしれないぞ。
さて、そのPostGISで空間データを扱うとき、まず直面するのが「どのデータ型を使うか?」って問題だ。そこで出てくるのが、今日の主役である`Geometry`型と`Geography`型ってわけ。
Geometry型とGeography型、この二つの違い、ちゃんとわかってる?
結論から言うと、一番大きな違いは「地球が丸いことを考慮するか、しないか」だ。
1. Geometry型:平面の世界で爆速処理!
まず`Geometry`型。これはな、簡単に言えば「平面の世界」の話だ。中学校で習ったデカルト座標系、X軸とY軸で点を表すアレだよ。
- 特徴:
- 平面座標系 (デカルト座標系): 地球の表面をあたかも平らな紙のように扱って、XとY(場合によってはZやMも)で位置を表現する。
- 距離計算: ユークリッド距離(直線距離)が基本。ピタゴラスの定理で計算する、あの最短距離だ。
- SRID (Spatial Reference System Identifier): これが超重要!どの座標系を使うかを指定するIDだ。日本の測量でよく使われるJGD2011/UTMとか、Web地図でよく使われるWeb Mercator (SRID: 3857) とか、色々なSRIDがある。SRIDが異なると、同じ座標値でも全く違う場所を指したり、計算結果がおかしくなったりするから、マジで注意が必要だ。
- 用途:
- 狭い範囲の解析: 市区町村レベル、建物内、敷地内など、地球の曲率を無視しても問題ないくらいの狭いエリアでの空間処理。
- 地図表示 (Web Mercator): Web地図サービス(Google MapsとかOpenStreetMapとか)で使われるタイル表示の多くは、このGeometry型をWeb Mercator (SRID: 3857) で扱っていることが多い。
- CAD/GISデスクトップアプリ: 比較的狭いエリアの図面作成や解析。
- メリット:
- 高速: 計算がシンプルな分、処理が爆速だ。大量のデータを扱う際にパフォーマンスが出やすい。
- 柔軟なSRID: 用途に応じて様々な座標系を選べる。
- デメリット:
- 広範囲だと歪む: 地球規模の距離や面積を計算しようとすると、地球の丸みを無視するから、とんでもなく不正確な結果になる。例えば、日本とブラジルの直線距離を計算しても、それは宇宙から見た直線距離であって、地面を移動する距離とは全く違うだろ?
Geometry型のコード例
SRID 3857 (Web Mercator) を使って、東京タワーとその近くの建物の距離を測ってみよう。
— テーブル作成
CREATE TABLE locations_geometry (
id SERIAL PRIMARY KEY,
name VARCHAR(255),
geom GEOMETRY(Point, 3857) — Point型でSRID 3857を指定
);
— データ挿入(緯度経度から3857に変換して挿入)
— 東京タワー (35.658581, 139.745433)
— 増上寺 (35.6572, 139.7483)
INSERT INTO locations_geometry (name, geom) VALUES
(‘Tokyo Tower’, ST_Transform(ST_SetSRID(ST_MakePoint(139.745433, 35.658581), 4326), 3857)),
(‘Zojoji Temple’, ST_Transform(ST_SetSRID(ST_MakePoint(139.7483, 35.6572), 4326), 3857));
— 距離計算 (単位はメートル)
SELECT
ST_Distance(
(SELECT geom FROM locations_geometry WHERE name = ‘Tokyo Tower’),
(SELECT geom FROM locations_geometry WHERE name = ‘Zojoji Temple’)
) AS distance_meters;
— 結果例: 約346メートル
`ST_Transform`と`ST_SetSRID`を使って、緯度経度からWeb Mercatorに変換しているのがポイントだ。同じSRID内での距離計算だから、これでOK。
2. Geography型:地球の丸みを考慮した正確さ!
で、問題は`Geography`型だ。こっちはな、地球が丸いってことをちゃんと考慮してくれる、賢いやつだ。
- 特徴:
- 測地座標系 (緯度経度): 地球を楕円体(WGS84という国際標準のモデルがよく使われる)と見なして、緯度と経度で位置を表現する。
- 距離計算: 球面距離(大円距離)が基本。地球の表面を這うような最短距離を計算する。飛行機が飛ぶルートとか、船が航海するルートをイメージすると分かりやすいな。
- SRID: 基本的にWGS84の緯度経度を表す SRID 4326 に固定される。このSRID 4326は、GPSデータなどでも一般的に使われている標準的な座標系だ。
- 用途:
- 広範囲の解析: 国際的な距離計算、広域のロケーションベースサービス、GPSデータ解析、物流ルート最適化など、地球の曲率を無視できないようなケース。
- 正確な距離・面積計算: 緯度経度ベースで、より正確な距離や面積を求めたい場合。
- メリット:
- 高い精度: 地球の丸みを考慮するため、広範囲での距離や面積計算が非常に正確だ。
- 標準的なSRID: SRID 4326に固定されるため、SRIDの変換で悩むことが少ない。
- デメリット:
- 計算コストが高い: 球面での計算は平面での計算より複雑なので、どうしても処理に時間がかかる。特に大規模データに対して複雑な空間演算を行うと、パフォーマンスに影響が出やすい。
- 利用可能な関数が限られる: `Geometry`型に比べて、利用できる空間関数が少し少ない傾向にある(とはいえ、主要なものは揃ってるから実用上は問題ないことも多い)。
Geography型のコード例
同じく東京タワーと増上寺の距離を、`Geography`型で測ってみよう。
— テーブル作成
CREATE TABLE locations_geography (
id SERIAL PRIMARY KEY,
name VARCHAR(255),
geog GEOGRAPHY(Point, 4326) — Point型でSRID 4326を指定
);
— データ挿入(緯度経度をそのまま挿入)
— 東京タワー (35.658581, 139.745433)
— 増上寺 (35.6572, 139.7483)
INSERT INTO locations_geography (name, geog) VALUES
(‘Tokyo Tower’, ST_SetSRID(ST_MakePoint(139.745433, 35.658581), 4326)),
(‘Zojoji Temple’, ST_SetSRID(ST_MakePoint(139.7483, 35.6572), 4326));
— 距離計算 (ST_Distance関数はGeography型の場合、自動的にメートル単位で球面距離を計算)
SELECT
ST_Distance(
(SELECT geog FROM locations_geography WHERE name = ‘Tokyo Tower’),
(SELECT geog FROM locations_geography WHERE name = ‘Zojoji Temple’)
) AS distance_meters;
— 結果例: 約346メートル
あれ?結果が同じじゃないか、と思ったか?そう、東京タワーと増上寺くらいの距離(数百メートルレベル)だと、地球の曲率の影響はほとんど出ないから、Geometry型もGeography型も同じような結果になるんだ。
じゃあ、もっと広範囲の例を見てみよう。東京タワーから、はるか遠くのハワイのダイアモンドヘッドまでの距離を測ってみる。
— ハワイのダイアモンドヘッド (21.268595, -157.805245)
INSERT INTO locations_geography (name, geog) VALUES
(‘Diamond Head’, ST_SetSRID(ST_MakePoint(-157.805245, 21.268595), 4326));
INSERT INTO locations_geometry (name, geom) VALUES
(‘Diamond Head’, ST_Transform(ST_SetSRID(ST_MakePoint(-157.805245, 21.268595), 4326), 3857));
— Geometry型での距離計算
SELECT
ST_Distance(
(SELECT geom FROM locations_geometry WHERE name = ‘Tokyo Tower’),
(SELECT geom FROM locations_geometry WHERE name = ‘Diamond Head’)
) AS distance_geometry_meters;
— 結果例: 約8,767,000メートル (約8767km)
— Geography型での距離計算
SELECT
ST_Distance(
(SELECT geog FROM locations_geography WHERE name = ‘Tokyo Tower’),
(SELECT geog FROM locations_geography WHERE name = ‘Diamond Head’)
) AS distance_geography_meters;
— 結果例: 約6,215,000メートル (約6215km)
どうだ?全然違うだろ?
Geometry型(平面)での計算だと、地球の丸みを無視して、とんでもない直線距離が出てしまう。一方、Geography型(測地)だと、地球の表面を這う現実的な距離が算出される。この違いが、広範囲の空間解析において決定的に重要になるってわけだ。
ぶっちゃけどっちを使えばいいの?判断基準とコストの話
じゃあ、結局どっちを使えばいいんだって話になるよな。これはもう、ユースケース次第としか言いようがない。
1. 精度 vs. 性能のトレードオフ:
- 正確さ最優先、広範囲を扱うなら `Geography`: GPSデータ、国際的な物流、世界地図レベルの分析など、地球の丸みを無視できない場合は迷わず`Geography`型だ。多少計算コストがかかっても、正確性が何よりも重要だからな。
- 性能最優先、狭い範囲を扱うなら `Geometry`: 特定の都市内、建物内など、地球の曲率の影響が無視できる範囲で、高速な検索や処理が求められるなら`Geometry`型が有利だ。地図の描画処理なんかは、Geometry型をうまく使ってパフォーマンスを出してる場合が多い。
2. SRIDの管理:
- `Geometry`型を使う場合、SRIDの選択と管理はめちゃくちゃ重要だ。システム内で複数の座標系が混在すると、SRIDの変換(`ST_Transform`)が必要になったり、変換忘れで誤った結果が出たりするリスクがある。一貫したSRIDを使うか、適切に変換を行う設計が不可欠だ。
- `Geography`型は基本的にSRID 4326に固定されているから、この点での悩みは少ない。
計算コストの話
ここが一番大事なところかもしれない。`Geography`型はな、地球の曲率を考慮する分、計算が複雑になる。そりゃそうだよな、ただの直線距離じゃなくて、地球の表面を這う曲線距離を計算しなきゃいけないんだから。だから、`Geometry`型に比べて、どうしても計算コストは高くなりがちだ。
- `Geometry`: 単純な数学演算が多いため、CPU負荷が低い。
- `Geography`: 楕円体上の複雑な三角関数や数値積分などを使うため、CPU負荷が高い。
そのため、大量の空間データに対して頻繁に距離計算や範囲検索を行うようなシステムでは、`Geography`型を選ぶとパフォーマンスボトルネックになる可能性がある。
まずは`Geometry`型で実装してみて、精度が問題になるようなら`Geography`型に切り替える、というアプローチも有効だ。
インデックスの活用は必須!
どちらの型を使うにしても、空間データを扱うならGiSTインデックスは必須だ。これは空間データの高速検索に特化したインデックスで、これを貼るのと貼らないのとでは、検索速度が桁違いに変わる。
— Geometry型にGiSTインデックスを貼る例
CREATE INDEX locations_geometry_geom_idx ON locations_geometry USING GIST (geom);
— Geography型にGiSTインデックスを貼る例
CREATE INDEX locations_geography_geog_idx ON locations_geography USING GIST (geog);
特に、`ST_DWithin`(指定距離内にあるオブジェクトを検索)のような関数を使う場合は、GiSTインデックスが強力な効果を発揮するぞ。
実践的な注意点と「罠」
1. SRIDの不一致:
`Geometry`型で一番多いミスがこれだ。異なるSRIDのジオメトリ同士で計算しようとすると、エラーになったり、意味不明な結果になったりする。必ず同じSRIDに揃えてから計算すること。`ST_Transform()`を使いこなせるようになれ。
2. 単位の問題:
- `Geometry`型: 距離や面積の単位は、そのSRIDの単位に依存する。例えば、SRID 4326 (緯度経度) のGeometry型で距離を計算すると「度」単位になる。メートルで欲しいなら、メートル単位のSRID (例: 3857, 2446) に変換してから計算するか、Geography型を使う必要がある。
- `Geography`型: 距離は常にメートル、面積は常に平方メートルで返される。この一貫性は非常に便利だ。
3. パフォーマンスチューニング:
- `Geography`型は重い、という意識を常に持っておけ。広範囲の複雑なクエリは、まずバウンディングボックス(おおよその矩形範囲)で絞り込んでから詳細な空間演算を行うなど、工夫が必要になる場合もある。
- GiSTインデックスは当然として、`EXPLAIN ANALYZE`を使ってクエリの実行計画を定期的に確認する習慣をつけろ。
4. NULL値の扱い:
空間データがNULLの場合、多くのPostGIS関数はNULLを返す。NULLを許容するカラムにする場合は、アプリケーション側でのハンドリングも考慮しておこう。
まとめ
どうだった?`Geometry`型と`Geography`型、それぞれに得意なことと苦手なことがあるってことが、これでよく分かっただろ?
- `Geometry`型: 平面の世界。速い!狭い範囲ならこれでOK。ただしSRIDに気をつけろ。
- `Geography`型: 地球が丸い世界。正確!広範囲ならこれ一択。ただし計算は重いぞ。
結局のところ、要件に応じて適切な型を選ぶのが「良い設計」ってやつだ。性能要件、精度要件、データの範囲、これらを総合的に判断して選択するんだ。
PostGISは奥が深くて面白いから、これからも色々試してみてくれ。困ったらいつでも相談に乗るぞ!頑張れよ!
コメント