Closed8

空間インデックスについて

tamaco489tamaco489

🔍 空間インデックスの概要(spatial index)

  • 地理的または空間的なデータ(位置情報や図形)を高速に検索・操作するためのインデックス。
  • 地図、座標、形状、範囲などを扱うアプリケーションで用いられる。
tamaco489tamaco489
項目 内容 例・備考
📌 対象 緯度・経度、点、線、多角形などのジオメトリデータ GPS位置情報、地図データなど
🎯 目的 空間演算を高速化 距離検索、範囲検索、重なり判定など
⚙️ インデックス構造 R-Tree、Quadtree、GiST など 多次元データ向けの最適化構造
🗂 対応RDBMS PostgreSQL(PostGIS)、MySQL、Oracle Spatial など 空間型をサポートするDBMS
tamaco489tamaco489

🧭 なぜ空間インデックスが必要?

通常のB-treeインデックスは線形なキー(数値や文字列)に最適ですが、空間データは2次元または多次元になっている。

例えば:

  • 「この地点から半径5km以内のレストラン」
  • 「このポリゴンと重なる建物の範囲」

↑上記のような空間的な条件では、単純な数値比較ではなく、空間的な関係(距離、包含、交差) を考慮する必要があり、空間インデックスはこれを効率化することを目的としている。

tamaco489tamaco489

🧱 空間インデックスの例(MySQL)

MySQLでは、 SPATIAL INDEX を使って以下のように定義する。

latitudeとlongtitudeを表した位置情報を管理するテーブル

CREATE TABLE `location_lat_lng` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255),
  `lat` double NOT NULL,
  `lng` double NOT NULL,
  PRIMARY KEY (`id`),
  KEY `lat` (`lat`,`lng`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

このテーブルに対して以下のようなクエリを投げる

SELECT
    *
FROM location_lat_lng
WHERE lat BETWEEN 35.681 AND 35.691
  AND lng between 139.757 AND 139.767;

これだとインデックスがそこまで有効に使われない。
MySQLは普通のインデックスの複数の範囲指定には弱い。
検索を高速に行うには Spatial Data Types を利用するのが良い。※内部的にR-Treeインデックスが使われる

CREATE TABLE `location` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255),
  `location` point NOT NULL, -- location_lat_lng テーブルのlatとlngを合わせて1つにしたようなデータ型、つまり座標を示すカラム
  PRIMARY KEY (`id`),
  SPATIAL KEY `loc` (`location`) -- これ
) ENGINE=InnoDB DEFAULT CHARSET=utf8
  • location は POINT 型のカラム。(例: 緯度・経度)
  • SPATIAL INDEX により空間検索を高速化する。
  • [注意] InnoDB では、空間インデックスを利用するにはカラムに NOT NULL 制約 が必要。

ここでは POINT 型のカラムに空間インデックスを作成し、ST_Distance_Sphere() や ST_Within() などの関数で効率的に検索できるようにしている。

tamaco489tamaco489

✅ 空間インデックスの利点と注意点

利点 注意点 備考
空間検索が高速になる インデックス作成に時間がかかる 大量データでは初期構築コストあり
ジオメトリ間の演算が最適化される 複雑な形状には制限がある場合も 実装依存の制限が存在することも
範囲クエリや近接検索に強い 対応関数を使わないと利用されない ST_WithinST_DWithin など
tamaco489tamaco489

📌 範囲検索のクエリ例:指定した中心点から半径5km以内

例えば以下のようなデータが存在するとして、

INSERT INTO location (name, location)
VALUES
  ('渋谷駅', ST_GeomFromText('POINT(139.7013 35.6580)')),
  ('新宿駅', ST_GeomFromText('POINT(139.7004 35.6900)')),
  ('東京駅', ST_GeomFromText('POINT(139.7671 35.6812)'));

このテーブルに対して以下のようなクエリを実行する

SELECT name,
       ST_Distance_Sphere(location, ST_GeomFromText('POINT(139.7000 35.6895)')) AS distance_m
FROM location
WHERE ST_Distance_Sphere(location, ST_GeomFromText('POINT(139.7000 35.6895)')) <= 5000;

memo: 新宿駅付近を中心とした5km圏内の地点を検索、 ST_Distance_Sphere() で地球上の球面距離(メートル)を求める。

tamaco489tamaco489

⚠️ パフォーマンス改善:MBRで候補を先に絞る

ST_Distance_Sphere() はフルスキャンになってしまう。
よって、まずは バウンディングボックス(最小外接矩形) による簡易検索で候補を絞り込んだ上で、厳密な距離フィルタを行うのが推奨されるパターンとなる。
※要するに MBRWithin()MBRContains で候補を絞り、その上で ST_Distance_Sphere() で厳密にフィルタするのが良い。

https://www.ibm.com/docs/ja/netezza?topic=scf-st-mbr-1
https://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_contours/py_contour_features/py_contour_features.html

SELECT name,
       ST_Distance_Sphere(location, ST_GeomFromText('POINT(139.7000 35.6895)')) AS distance_m
FROM location
WHERE MBRWithin(
          location,
          ST_GeomFromText('POLYGON((139.66 35.64, 139.74 35.64, 139.74 35.74, 139.66 35.74, 139.66 35.64))')
      )
  AND ST_Distance_Sphere(location, ST_GeomFromText('POINT(139.7000 35.6895)')) <= 5000;
  • MBRWithin(): 緯度経度による矩形(ポリゴン)範囲内のデータのみを抽出(インデックス使用可)
  • ST_Distance_Sphere(): 抽出した候補から、実際の球面距離を計算してフィルタ
このスクラップは4ヶ月前にクローズされました