ogr2ogr 備忘録

# 備忘録

GIS のベクタデータの形式や座標系（投影方法）の相互変換を行う [GDAL/OGR](https://gdal.org/) の `ogr2ogr` に関するメモ。

基本構文は以下のとおりで、出力形式を `-f` オプションで[サポートするベクタ形式の Code](https://gdal.org/drivers/vector/index.html) を記述する。

```
ogr2ogr -f 出力形式 出力データセット 入力データセット [入力レイヤ]
```

CSV や GeoJSON といった一部の形式では、入力データセットのファイル名に `CSV:data.txt` や `GeoJSON:data.json` といった記述が可能で、これにより `*.txt` の CSV や、 GeoJSON と TopoJSON など拡張子から判別困難な形式を指定することができる。

## [OGR のデータ構造](https://gdal.org/user/vector_data_model.html)

* データセット
  * ファイルやデータベースなど。ひとつ以上のレイヤを含む（レイヤをひとつしか持てない形式もある）。
* レイヤ
  * 地物の集まり。
  * OGR で使われる「レイヤ」とは、他の GIS で一般的に使われるレイヤ（重ね合わせたり、スタイル変えたり）よりももっと抽象的な地物の集合データ。
  * ただし QGIS 等、他の GIS で読み込んだときにひとつのレイヤとして扱われる単位でもある。
* 地物
  * 1つ（1行）の GIS データ。
  * ひとつのジオメトリ情報と一連の属性情報を含む。
* ジオメトリ
  * 図形情報（座標値）。

たとえば ESRI Shapefile は1つのデータセット（ファイル）で1つのレイヤしか持つことができないが、 SpatiaLite は1つにデータセット（ファイル）に複数のレイヤ（テーブル）を持つことができる。また、 PostGIS のようにデータセットはデータベースのコネクションという場合もある。

## 読み込み／書き込みに関するオプション

データの読み込みあるいは書き込みのオプションを `-dsco`, `-lco`, `-oo`, `-doo` で指定できる。それぞれ

* `-dsco NAME=VALUE`
  * データセット作成オプション (Dataset creation option)
* `-lco NAME=VALUE`
  * レイヤ作成オプション (Layer creation option)
* `-oo NAME=VALUE`
  * 入力データセット読込みオプション (Input dataset open option)
* `-doo NAME=VALUE`
  * 更新データセット読込みオプション (Destination dataset open option)

を意味する。指定できるものは形式によって大きく異なるため[サポートするベクタ形式](https://gdal.org/drivers/vector/index.html)から目的の形式（ドライバ）のページを参照のこと。

## 各形式の主なオプション
### [ESRI Shapefile](https://gdal.org/drivers/vector/shapefile.html)
#### 出力ファイル形式（コード）
* `-f "ESRI Shapefile"`

#### 作成に関するオプション（出力データセット・レイヤに対するオプション）
* `-lco ENCODING=`
  * 作成するレイヤの文字エンコーディングを指定する。
  * デフォルトは `LDID/89` (ISO-8859-1) なので、日本語が含まれる場合は実質指定が必要。
  * 日本で主に使われているエンコーディングとして `CP932` あるいは `UTF-8` がある。 `CP932` は Shift_JIS （正確には亜種）。
* `-lco SPATIAL_INDEX=`
  * `YES` を指定すると、空間インデックスファイル（ *.qix ）が作成される。

#### 読込みに関するオプション（入力データセット・レイヤに対するオプション）
* `-oo ENCODING=`
  * 読み込むレイヤの文字エンコーディングを指定する。
  * 省略された場合、 `*.cpg` ファイル（エンコーディングファイル／コードページファイル）があれば読み込み、そのエンコーディングで処理される。

### [GeoJSON](https://gdal.org/drivers/vector/geojson.html)
#### 出力ファイル形式（コード）
* `-f GeoJSON`

#### 作成に関するオプション（出力データセット・レイヤに対するオプション）
* `-lco COORDINATE_PRECISION=`
  * 座標値の精度を整数で指定する。デフォルトは `15` 。たとえば `-lco COORDINATE_PRECISION=3` の場合、小数点以下3桁まで出力される。単位が度のときももちろん、単位がメートルのときは特に指定しておくと容量の削減になる。
  * ただし GeoJSON の現在の仕様 (RFC 7946) では常に EPSG:4326 (WGS84) とし、任意の座標系を指定することはできない。2008年の古い仕様では可能。
* `-lco WRITE_BBOX=`
  * `YES` を指定すると、範囲（ bounding box ）情報を書き込む。

### [SQLite/SpatiaLite](https://gdal.org/drivers/vector/sqlite.html)
#### 出力ファイル形式（コード）
* `-f SQLite`

#### 作成に関するオプション（出力データセット・レイヤに対するオプション）
* `-nln` *layer_name*
  * 出力レイヤ名を指定する。
* `-dsco SPATIALITE=`
  * `YES` を指定すると、 SQLite の拡張である SpatiaLite として作成する。
* `-lco GEOMETRY_NAME=`
  * ジオメトリ（座標）を格納するフィールド名を指定する。デフォルトは `GEOMETRY` 。
* `-lco FID=`
  * 主キーとなるフィールド名を指定する。デフォルトは `OGC_FID` 。
* `-lco SPATIAL_INDEX=`
  * `NO` を指定すると、空間インデックスを作成しないことができる。たとえば順次地物を追加する場合、あとでまとめて空間インデックス作成を行った方がよい場合がある。その他、パフォーマンス向上については [SQLite/SpatiaLite の Performance hints](https://gdal.org/drivers/vector/sqlite.html#performance-hints) を参照。

### [GeoPackage](https://gdal.org/drivers/vector/gpkg.html)
#### 出力ファイル形式（コード）
* `-f GPKG`

#### 作成に関するオプション（出力データセット・レイヤに対するオプション）
* `-nln` *layer_name*
  * 出力レイヤ名を指定する。
* `-lco GEOMETRY_NAME=`
  * ジオメトリ（座標）を格納するフィールド名を指定する。デフォルトは `geom` 。
* `-lco FID=`
  * 主キーとなるフィールド名を指定する。デフォルトは `fid` 。
* `-lco SPATIAL_INDEX=`
  * `NO` を指定すると、空間インデックスを作成しないことができる。

### [CSV](https://gdal.org/drivers/vector/csv.html)
#### 出力ファイル形式（コード）
* `-f CSV`

#### 作成に関するオプション（出力データセット・レイヤに対するオプション）
* `-lco SEPARATOR=`
  * 区切り文字を指定する。取ることの出来る値は `COMMA`, `SEMICOLON`, `TAB`, `SPACE` のいずれか。デフォルトは `COMMA` 。
* `-lco GEOMETRY=`
  * ジオメトリ（座標）の格納形式を指定する。取ることの出来る値は `AS_WKT` あるいはジオメトリタイプが点のときは `AS_XYZ`, `AS_XY`, `AS_YX` のいずれか。デフォルトはジオメトリ情報は保存されない。
* `-lco CREATE_CSVT=`
  * `YES` を指定すると、フィールド形式情報ファイル（ `*.csvt` ）が作成される。
* `-lco WRITE_BOM=`
  * `YES` を指定すると、 UTF-8 の BOM (Byte Order Mark) が付与された状態で出力される。
  * OGR で CSV を扱う際の文字エンコーディングは UTF-8 しか対応していない。
  * UTF-8 でエンコーディングされた CSV をエクセルで直接開く場合、 BOM が付与されていないと（ UTF-8 として開かれず）文字化けが発生する。一方、テキストエディタで開いたりプログラムで処理する場合は BOM なしの方がおそらく使い勝手がよい。

#### 読込みに関するオプション（入力データセット・レイヤに対するオプション）
* `-oo GEOM_POSSIBLE_NAMES=`
  * WKT 、16進数 WKB 、あるいは GeoJSON 形式で格納されたフィールド名を指定する。
* `-oo X_POSSIBLE_NAMES=`
* `-oo Y_POSSIBLE_NAMES=`
* `-oo Z_POSSIBLE_NAMES=`
  * X, Y, Z の座標値が格納されたフィールド名を指定する。
* `-oo KEEP_GEOM_COLUMNS=`
  * `NO` を指定すると、座標値として使用したフィールドを通常のフィールドから削除する（属性一覧に含めない）。

`_POSSIBLE_NAMES` はそれぞれカンマ区切り、あるいは `prefix*`, `*suffix`, `*middle*` の形式で記述することができる。（これにより複数のフィールドが該当した場合、どのような処理となるか未調査）。

また、 `_POSSIBLE_NAMES` でジオメトリカラムを指定しない場合も `*.csvt` ファイル内でフィールド形式に `WKT`, `CoordX`, `Point(X)`, `CoordY`, `Point(Y)`, `CoordZ`, `Point(Z)` が記載された場合は自動的に座標値とみなされる。

座標系に関しては、 `*.prj` ファイルがあれば読み込み適用される。

## 追加と更新

* `-update`
  * 既に存在する出力データセットを削除せず、更新する。
* `-append`
  * 既に存在する出力レイヤを削除せず、データの追加を行う。
* `-overwrite`
  * 既に存在する出力レイヤを削除して、新たにレイヤを作成する。

たとえば、既存のレイヤにデータを追加していくときは `-update -append` を指定する。

## 座標系（投影方法）の変換

OGR では座標系のことを SRS (Spatial Reference System) と略する。

* `-s_srs` *srs*
  * 指定した SRS で入力レイヤの SRS を上書きする。（ `*.prj` 等のすでに存在する座標系情報を無視し、 `-s_srs` オプションを優先する。）
* `-t_srs` *srs*
  * 指定した SRS で座標変換を行い、出力する。
* `-a_srs` *srs*
  * 指定した SRS で出力レイヤを適用する。（座標の変換は行わず、**座標系情報のみを書き換える**。）

座標系の変換を行う際、入力レイヤは一般に座標系情報が付与されている。この場合、 `-s_srs` を省略することができ、単に出力先の座標系を示す `-t_srs` だけあればよい。ただし入力レイヤが `*.prj` なしの Shapefile や CSV など、座標系情報が付与されていないとき、または座標系情報が間違っているときは `-s_srs` を付け、入力座標系を明示する必要がある。

`-a_srs` はたとえば「平面直角座標系第5系の X=0, Y=0」を「平面直角座標系第7系の X=0, Y=0」としたいときに使用する。

## その他のオプション

その他、入出力の形式に関わらず指定できる `ogr2ogr` [のオプション](https://gdal.org/programs/ogr2ogr.html)。

* `-nlt` *type*
  * 出力のジオメトリタイプを指定する。
  * たとえばマルチポリゴンを持った Shapefile から SpatiaLite への変換。 [Shapefile の仕様](https://www.esri.com/library/whitepapers/pdfs/shapefile.pdf)上 MULTIPOLYGON 形式は存在していない（対応していない）が、 POLYGON 形式として1つの地物で複数のポリゴン（外環）を記述ことができてしまう仕様となっている。
  * しかし、仕様上あくまでもジオメトリタイプは POLYGON であるため、 `-nlt` を使わずに変換しようとすると POLYGON として出力しようとする。
一方、 SpatiaLite では複数のポリゴン（外環）を持つ地物は、厳密に MULTIPOLYGON として扱うべきとされているため、そのようなマルチポリゴンを POLYGON タイプとして保存した場合、エラーが発生する。
  * `-nlt MULTIPOLYGON` あるいは `-nlt PROMOTE_TO_MULTI` とし、出力を MULTIPOLYGON とすることでエラーを回避できる。

## 例
### 各形式へ変換
```shell-session
$ # Shapefile の文字エンコーディングを CP932 から UTF-8 に変換
$ ogr2ogr -f "ESRI Shapefile" -lco ENCODING=UTF-8 -oo ENCODING=CP932 test01/N03-18_13_180101_UTF8.shp N03-18_13_180101.shp

$ # Shapefile の文字エンコーディングに注意しつつ GeoJSON に変換
$ ogr2ogr -f GeoJSON -oo ENCODING=CP932 test02/N03-18_13_180101.geojson N03-18_13_180101.shp

$ # Shapefile の文字エンコーディングに注意しつつ SpatiaLite に変換
$ # その際、レイヤ名を tokyo とする
$ ogr2ogr -f SQLite -dsco SPATIALITE=YES -nln tokyo -oo ENCODING=CP932 test03/N03-18_13_180101.sqlite N03-18_13_180101.shp

$ # Shapefile の文字エンコーディングに注意しつつ GeoPackage に変換
$ ogr2ogr -f GPKG -nln tokyo -oo ENCODING=CP932 test04/N03-18_13_180101.gpkg N03-18_13_180101.shp

$ # Shapefile の文字エンコーディングに注意しつつ CSV に変換
$ # この際、タブ区切り、ジオメトリ形式 WKT 、 CSVT ファイルを作成する
$ ogr2ogr -f CSV -lco SEPARATOR=TAB -lco GEOMETRY=AS_WKT -lco CREATE_CSVT=YES -oo ENCODING=CP932 test05/N03-18_13_180101.csv N03-18_13_180101.shp
```

### CSV のジオメトリについて
```shell-session
$ # テスト用にジオメトリフィールド名を g とした CSV を用意する
$ ogr2ogr -f CSV -lco SEPARATOR=TAB -lco GEOMETRY=AS_WKT -lco CREATE_CSVT=YES -lco GEOMETRY_NAME=g -oo ENCODING=CP932 test06/N03-18_13_180101.csv N03-18_13_180101.shp

$ # 出力されたファイルは csv のほか、 csvt と prj
$ ls test06
N03-18_13_180101.csv	N03-18_13_180101.csvt	N03-18_13_180101.prj

$ # csvt ファイルの中身はこんな感じ
$ cat test06/N03-18_13_180101.csvt
WKT,String(10),String(20),String(20),String(20),String(5)

$ # prj / csvt ありの CSV から GeoJSON を作成する
$ # csvt からジオメトリフィールドがわかるので、オプション指定は不要
$ ogr2ogr -f GeoJSON test07/N03-18_13_180101_from_csv.geojson test06/N03-18_13_180101.csv

$ # csvt ファイルを削除する
$ rm test06/N03-18_13_180101.csvt

$ # prj あり csvt なしの CSV から GeoJSON を作成する
$ # ここではジオメトリフィールド名を g としているためジオメトリフィールドを類推できず、ジオメトリなしの GeoJSON が作成される
$ ogr2ogr -f GeoJSON test08/N03-18_13_180101_from_csv.geojson test06/N03-18_13_180101.csv

$ # prj あり csvt なしの CSV から GeoJSON を作成する
$ # オプションでジオメトリフィールドを指定する方法
$ ogr2ogr -f GeoJSON -oo GEOM_POSSIBLE_NAMES=g test09/N03-18_13_180101_from_csv.geojson test06/N03-18_13_180101.csv
```

### 複数データの処理
```shell-session
$ # 複数の Shapefile のデータをまとめた Shapefile を作成する
$ # 二回目以降の -lco ENCODING=UTF-8 は警告がでるため記載しない
$ ogr2ogr -f "ESRI Shapefile" -lco ENCODING=UTF-8 -oo ENCODING=CP932 -nln kanto test10/kanto.shp N03-18_13_180101.shp
$ ogr2ogr -f "ESRI Shapefile" -update -append -oo ENCODING=CP932 -nln kanto test10/kanto.shp N03-18_14_180101.shp

$ # 複数の Shapefile から、複数のレイヤをもった SpatiaLite を作成する
$ # 二回目以降の -dsco SPATIALITE=YES は警告がでるため記載しない
$ ogr2ogr -f SQLite -dsco SPATIALITE=YES -oo ENCODING=CP932 -nln tokyo test11/kanto.sqlite N03-18_13_180101.shp
$ ogr2ogr -f SQLite -update -oo ENCODING=CP932 -nln kanagawa test11/kanto.sqlite N03-18_14_180101.shp

$ # 複数のレイヤをもった SpatiaLite の tokyo レイヤから GeoJSON に変換する
$ ogr2ogr -f GeoJSON test12/tokyo.geojson test11/kanto.sqlite tokyo
```

### 座標系の変換
```shell-session
$ # 経緯度座標系から平面直角座標系に変換する
$ # この際、出力 GeoJSON の座標精度は3桁にする
$ ogr2ogr -f GeoJSON -lco COORDINATE_PRECISION=3 -oo ENCODING=CP932 -t_srs EPSG:6676 test13/N03-18_13_180101_6676.geojson N03-18_13_180101.shp

$ # 平面直角座標系から別の平面直角座標系に座標変換は行わず、座標系情報を変換する
$ ogr2ogr -f GeoJSON -lco COORDINATE_PRECISION=3 -a_srs EPSG:6674 test14/N03-18_13_180101_6674.geojson test13/N03-18_13_180101_6676.geojson

$ # 誤った座標系 (EPSG:6674) を持ったデータに正しい座標系 (EPSG:6676) を明示して経緯度座標系に変換する
$ ogr2ogr -f GeoJSON -s_srs EPSG:6676 -t_srs EPSG:6668 test15/N03-18_13_180101_6668.geojson test14/N03-18_13_180101_6674.geojson
```