📦

Spatialite 5.1 の ImportGeoJSON() の変な動作の紹介

に公開

はじめに

突然ですが、以下に GeoJSON ファイルが二つあります。

a.geojson
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
          "Prop1": "Test"
      },
      "geometry": {
        "type": "Point",
        "coordinates": [
          140,
          40
        ]
      }
    }
  ]
}

b.geojson
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
          "Prop1": "Test"
      },
      "geometry": {
        "coordinates": [
          140,
          40
        ],
        "type": "Point"
      }
    }
  ]
}

どちらも、Point が一つで Point のプロパティも一つというシンプルな GeoJSON のファイルです。唯一の違いは、geometry プロパティの中身で a.geojson は type プロパティが先に来るのに対し、b.geojson は coordinates プロパティが先に来ます。

ここで問題です。
Spatialite 5.1.0 で ImportGeoJSON() で GeoJSON ファイルをインポートする SQL 文を二つの GeoJSON ファイルに対して行うとそれぞれどうなるでしょう?

SELECT ImportGeoJSON('./a.geojson', 'test1');

SELECT ImportGeoJSON('./b.geojson', 'test2');
答え

a.geojson は正しくインポートできて、b.geojson は GeoJSON parser: invalid Geometry (fid=1) によりインポートできない。

おそらく、意外な結果となったと思います。

仕様の確認

GeoJSON の仕様によって、JSON の項目の順番がデータに影響があるというのはあり得るのかを確認するため、GeoJSON の仕様書 RFC 7946 を確認してみましたが、1.2 節 に、

The ordering of the members of any JSON object defined in this
document MUST be considered irrelevant, as specified by [RFC7159].

と書いてあるように、仕様上はデータの順番による影響はなさそうです。

ソースコードからの推測

Spatialite は OSS なので、ソースコード側を確認してみます。
Spatialite の GeoJSON パーサのソースコードの 670〜828 行目に字句解析で使用する、ルール一覧が列挙されています。この中から、XY のみの Point に絞って抜粋すると以下のようになります。

 /*  14 */ "point ::= GEOJSON_OPEN_BRACE GEOJSON_TYPE GEOJSON_COLON GEOJSON_POINT GEOJSON_COMMA GEOJSON_COORDS GEOJSON_COLON point_coordxy GEOJSON_CLOSE_BRACE",
 /*  15 */ "point ::= GEOJSON_OPEN_BRACE GEOJSON_TYPE GEOJSON_COLON GEOJSON_POINT GEOJSON_COMMA GEOJSON_BBOX GEOJSON_COLON GEOJSON_OPEN_BRACKET bbox GEOJSON_CLOSE_BRACKET GEOJSON_COMMA GEOJSON_COORDS GEOJSON_COLON point_coordxy GEOJSON_CLOSE_BRACE",
 /*  16 */ "point ::= GEOJSON_OPEN_BRACE GEOJSON_TYPE GEOJSON_COLON GEOJSON_POINT GEOJSON_COMMA GEOJSON_CRS GEOJSON_COLON short_crs GEOJSON_COMMA GEOJSON_COORDS GEOJSON_COLON point_coordxy GEOJSON_CLOSE_BRACE",
 /*  17 */ "point ::= GEOJSON_OPEN_BRACE GEOJSON_TYPE GEOJSON_COLON GEOJSON_POINT GEOJSON_COMMA GEOJSON_CRS GEOJSON_COLON long_crs GEOJSON_COMMA GEOJSON_COORDS GEOJSON_COLON point_coordxy GEOJSON_CLOSE_BRACE",
 /*  18 */ "point ::= GEOJSON_OPEN_BRACE GEOJSON_TYPE GEOJSON_COLON GEOJSON_POINT GEOJSON_COMMA GEOJSON_CRS GEOJSON_COLON short_crs GEOJSON_COMMA GEOJSON_BBOX GEOJSON_COLON GEOJSON_OPEN_BRACKET bbox GEOJSON_CLOSE_BRACKET GEOJSON_COMMA GEOJSON_COORDS GEOJSON_COLON point_coordxy GEOJSON_CLOSE_BRACE",
 /*  19 */ "point ::= GEOJSON_OPEN_BRACE GEOJSON_TYPE GEOJSON_COLON GEOJSON_POINT GEOJSON_COMMA GEOJSON_CRS GEOJSON_COLON long_crs GEOJSON_COMMA GEOJSON_BBOX GEOJSON_COLON GEOJSON_OPEN_BRACKET bbox GEOJSON_CLOSE_BRACKET GEOJSON_COMMA GEOJSON_COORDS GEOJSON_COLON point_coordxy GEOJSON_CLOSE_BRACE",
 /*  24 */ "point ::= GEOJSON_OPEN_BRACE GEOJSON_TYPE GEOJSON_COLON GEOJSON_POINT GEOJSON_COMMA GEOJSON_CRS GEOJSON_COLON short_crs GEOJSON_COMMA GEOJSON_BBOX GEOJSON_COLON GEOJSON_OPEN_BRACKET bbox GEOJSON_CLOSE_BRACKET GEOJSON_COMMA GEOJSON_COORDS GEOJSON_COLON point_coordxyz GEOJSON_CLOSE_BRACE",
 /*  25 */ "point ::= GEOJSON_OPEN_BRACE GEOJSON_TYPE GEOJSON_COLON GEOJSON_POINT GEOJSON_COMMA GEOJSON_CRS GEOJSON_COLON long_crs GEOJSON_COMMA GEOJSON_BBOX GEOJSON_COLON GEOJSON_OPEN_BRACKET bbox GEOJSON_CLOSE_BRACKET GEOJSON_COMMA GEOJSON_COORDS GEOJSON_COLON point_coordxyz GEOJSON_CLOSE_BRACE",

ここで、注目すべきなのは全てのルールGEOJSON_OPEN_BRACE GEOJSON_TYPE GEOJSON_COLON GEOJSON_POINT GEOJSON_COMMA GEOJSON_COORDS で始まっていることです。この並びの順は以下の通りです。(詳しい対応表はこちらを参照

  1. 文字列の {
  2. 文字列の "type"
  3. 文字列の :
  4. 文字列の "Point"
  5. 文字列の ,
  6. 文字列の :
  7. 文字列の "coordinates"

この順序は字句解析で GeoJSON を読むときにどの順序で出てくるかのルールです。全てのルールがこの順序で始まるということは、GeoJSON で coordinates プロパティが先に来ているのは実装上は想定しておらずエラーが出たと推測されます
他の字句解析のルールも見てみても、Z 座標つきの Point も同様であり、その他の型のデータであっても同様です。

おわりに

この記事では、Spatialite 5.1 において、GeoJSON の type プロパティと coordinates プロパティの順序によっては、GeoJSON の仕様を満たしていても、インポート時にパースできずにエラーになってしまう例を紹介しました。確認した環境は、brew を使って macOS にインストールした Spatialite ですが、パーサ側の実装に原因がある可能性が高いので他の環境でも同じ問題が出ると思います。

現状の対策としては、Spatialite を使う限りは type プロパティが必ず先に来ているかを確認するしかないと思います。幸い、QGIS では coordinates プロパティが先に来ている GeoJSON も取り扱えますし、QGIS からのエクスポートによって type プロパティが先に来るようなので当座はこういう方法で対応すれば良いと思います。

Spatialite の issue 一覧では、まだこの事象に関して立っていないようなので、issue を立てればプロパティの順序を気にしなくても良くなる日が来るかもしれません。

Discussion