LoginSignup
5
2

More than 1 year has passed since last update.

OGR de SQL

Last updated at Posted at 2021-12-19

GIS でデータベースを扱うことができるソフトといえば、そう、 OGR ですね。

MySQL や PostgreSQL のように効率よく処理するために実データが複数ファイルに分かれていることもあれば、 SQLite のように単ファイルで保存されている形式もあります。広義の定義では構造化されていないテキストファイルもデータベースに含むと考えることもできます。

つまり ESRI Shapefile や GeoJSON であっても十分にデータベースだよね。そんでもって、複数のデータベースを結合させたり SQL などで問い合わせることができるシステムなら、それはもう RDBMS だよね。

ということで RDBMS である OGR ですが、どの程度 SQL が使えるのか確認してみます。

OGR における SQL

ベクタデータを扱う OGR ですが、 ESRI Shapefile をはじめ種々のデータを読み込んだ際に、 SQLite 形式の仮想データとして扱われます。そのため ESRI Shapefile であっても何であっても、データを読み込み後 SQL を実行することができます。

使用することができる SQL の方言は -dialect オプションで指定します。デフォルトで -dialect OGRSQL-dialect SQLITE をサポートしています。

SQLite の方は SpatiaLite の空間関数 ST_* も利用することができます。

ogrinfo

ogrinfo は一般的にはベクタデータの情報を確認するためのコマンドですが、 SQL を利用してデータ挿入なども可能です。

たとえば ne_10m_airports.shp に対し、地物を追加することもできます。

(レイヤ名を個別に指定できない) ESRI Shapefile 形式なので、レイヤ名(テーブル名)はファイル名と同一である ne_10m_airports 、ジオメトリカラムは GEOMETRY として扱われます。( GeoPackage など、レイヤ名やジオメトリカラム名を指定することができる形式の場合は ogrinfo を実行し、レイヤ名やジオメトリカラム名を確認する必要があります。)

INSERT INTO ne_10m_airports (GEOMETRY, name) VALUES (ST_Point(135, 35), 'insertion test')

という INSERT 文を実行するには下記のコマンドを実行します。

$ ogrinfo -dialect SQLITE -sql "INSERT INTO ne_10m_airports (GEOMETRY, name) VALUES (ST_Point(135, 35), 'insertion test')" ne_10m_airports.shp

ちなみに INSERT がうまくいったか、地物数の確認をするには

$ ogrinfo -ro -dialect SQLITE -sql "SELECT COUNT(*) FROM ne_10m_airports" ne_10m_airports.shp
INFO: Open of `ne_10m_airports.shp'
      using driver `ESRI Shapefile' successful.

Layer name: SELECT
Geometry: None
Feature Count: 1
Layer SRS WKT:
(unknown)
COUNT(*): Integer (0.0)
OGRFeature(SELECT):0
  COUNT(*) (Integer) = 894

(こんなことをしなくても、普通に ogrinfo をたたけば地物数は確認できますが)

このほか UPDATE や、 -dialect OGRSQL だと ALTER TABLE なども対応しているようです。

ogr2ogr

ベクタデータの形式変換や座標系変換などを行う ogr2ogr ですが、 -sql を使って SELECT で問い合わせた結果をもとに出力ファイルを生成することができます。

条件によるフィルタリング

SELECT * FROM ne_10m_airports WHERE type='major'

を実行し、 WHERE 句を使ったデータの絞り込みを行うには

$  ogr2ogr -f "ESRI Shapefile" -oo ENCODING=UTF-8 -lco ENCODING=UTF-8 -dialect SQLITE -sql "SELECT * FROM ne_10m_airports WHERE type='major'" extract_airports.shp ne_10m_airports.shp

糖衣構文(?)として、 -where オプションで条件のみを記載しても同等の結果が得られます。

空間関数によるジオメトリ生成

SELECT ST_Buffer(GEOMETRY, 0.1) AS GEOMETRY, name FROM ne_10m_airports

を実行し、空港データ(点データ)に対し、バッファを得るには

$  ogr2ogr -f "ESRI Shapefile" -oo ENCODING=UTF-8 -lco ENCODING=UTF-8 -dialect SQLITE -sql "SELECT ST_Buffer(GEOMETRY, 0.1) AS GEOMETRY, name FROM ne_10m_airports" airports_buffer.shp ne_10m_airports.shp

空間関数によるフィルタリング

SELECT * FROM ne_10m_airports
WHERE ST_intersects(GEOMETRY, (
    SELECT nation.GEOMETRY
    FROM 'ne_10m_admin_0_countries.shp'.'ne_10m_admin_0_countries' AS nation
    WHERE nation.NAME='Japan'
))

を実行し、複数データ(空港データと国データ)から日本国内にある空港を抽出してみます。

$ ogr2ogr -f "ESRI Shapefile" -oo ENCODING=UTF-8 -lco ENCODING=UTF-8 -dialect SQLITE -sql "SELECT * FROM ne_10m_airports WHERE ST_intersects(GEOMETRY, (SELECT nation.GEOMETRY FROM 'ne_10m_admin_0_countries.shp'.'ne_10m_admin_0_countries' AS nation WHERE nation.NAME='Japan'))" japan_airports.shp ne_10m_airports.shp

ogr2ogr の構文上は指定できる入力データソースはひとつですが、 SQL 文中でファイル名、レイヤ名(テーブル名)を指定することができます。

空間演算

SELECT ST_Intersection(nation.GEOMETRY, tz.GEOMETRY) AS GEOMETRY,
    nation.NAME, nation.ISO_A3, tz.time_zone
FROM ne_10m_admin_0_countries AS nation,
   'ne_10m_time_zones.shp'.'ne_10m_time_zones' AS tz
WHERE nation.ISO_A3='USA' AND ST_Intersects(nation.GEOMETRY, tz.GEOMETRY)

を実行し、複数データ(タイムゾーンデータと国データ)からアメリカのタイムゾーン分割を行ってみます。

$ ogr2ogr -f "ESRI Shapefile" -oo ENCODING=UTF-8 -lco ENCODING=UTF-8 -dialect SQLITE -sql "SELECT ST_Intersection(nation.GEOMETRY, tz.GEOMETRY) AS GEOMETRY, nation.NAME, nation.ISO_A3, tz.time_zone FROM ne_10m_admin_0_countries AS nation, 'ne_10m_time_zones.shp'.'ne_10m_time_zones' AS tz WHERE nation.ISO_A3='USA' AND ST_Intersects(nation.GEOMETRY, tz.GEOMETRY)" USA_tz_split.shp ne_10m_admin_0_countries.shp

空間集計

SELECT nation.GEOMETRY, nation.NAME,
    COUNT(ap.NAME) AS n_airports,
    MIN(ap.scalerank) AS min_scale
FROM ne_10m_admin_0_countries AS nation,
    'ne_10m_airports.shp'.'ne_10m_airports' AS ap
WHERE ST_Intersects(nation.GEOMETRY, ap.GEOMETRY)
GROUP BY nation.GEOMETRY

を実行し、複数データ(空港データと国データ)から各国ごとに空港がいくつあるか集計し、もっとも小さな scalerank の値を得るには。

$ ogr2ogr -f "ESRI Shapefile" -oo ENCODING=UTF-8 -lco ENCODING=UTF-8 -dialect SQLITE -sql "SELECT nation.GEOMETRY, nation.NAME, COUNT(ap.NAME) AS n_airports, MIN(ap.scalerank) AS min_scale FROM ne_10m_admin_0_countries AS nation, 'ne_10m_airports.shp'.'ne_10m_airports' AS ap WHERE ST_Intersects(nation.GEOMETRY, ap.GEOMETRY) GROUP BY nation.GEOMETRY" aggregate_airports.shp ne_10m_admin_0_countries.shp

空間インデックスが張られていないこともあるのかもしれませんが、処理に結構時間がかかりました。

まとめ

普段使いませんが今回は敢えて ESRI Shapefile のデータを利用し、任意の GIS データに対し SQL 文で処理を行ってみました。

パフォーマンス的にはあまりよくないとは思いますが、手元に Shapefile 形式などのデータがあり、わざわざ PostGIS や SpatiaLite に変換するのもちょっと面倒。だけど条件で抽出したい。とかそういったケースで便利かもしれません。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2