C#からSpatiaLiteを利用する

SpatiaLiteとはSQLite上でGISを扱えるようにする拡張モジュールです。
PostgreSQLに対するPostGISのような位置づけです。

さて、こいつをC#で動かそうとすると、なかなか大変だったので、忘れないように手順をメモしておきます。

## 前提

今回は、

* Visual Studio (VS)でC#のプロジェクトを作る。
* そのプロジェクトのSystem.Data.SQLite上でSpatiaLiteの拡張モジュールをロードし使う
* x64向けビルドを作る

という前提で話を進めます。

## System.Data.SQLiteの準備

まずは、VSでSQLiteが使えるように、C#のプロジェクトのNuGetでSystem.Data.SQLiteをインストールします。
また、構成マネージャを使ってx64ビルドを作れるようにしておきます。

## SpatiaLiteのダウンロード

SpatiaLite本家サイト：http://www.gaia-gis.it/gaia-sins/

こちらのページの最下部にMS Windows binariesというコーナーがあるので、そこのcurrent stable versionのamd64を選択します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/122210/8f292509-24b9-be92-539a-9c35885ac319.png)



ファイル一覧ページに遷移したら、mod_spatialite-4.3.0a-win-amd64.7zを選択してダウンロード。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/122210/d5db4830-4260-ef95-bd00-113397534994.png)



で、この7zファイルを解凍すると、mod_spatialite.dll以下、依存するdll群がまとめて入っているわけですが、こいつが曲者。
このモジュールたちをそのまま使おうとすると、mod_spatialite.dllのロード時にlibstdc++_64-6.dllでアクセスバイオレーションが発生して落ちます。

そこで、アクセスバイオレーションの発生しないものに置き換える必要があります。

## libstdc++_64-6.dllの置き換え

libstdc++_64-6.dllは、MinGW64に同梱されています。

MinGW64: https://sourceforge.net/projects/mingw-w64/?source=typ_redirect

上記サイトからダウンロードし、インストールします。こんな感じの設定でインストールしました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/122210/ed7c1835-74dd-8dd7-4f5c-961f2642e4b3.png)


インストールが完了したら、インストール先ディレクトリのbin以下の「libstdc++-6.dll」「libgcc_s_seh-1.dll」の2つのファイルをコピーし、前節のspatialiteのdll群のものと入れ替えます。

この2つのファイルをコピーします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/122210/c1afdba1-2032-2fb5-b687-720801f1e670.png)


置き換える際、ファイル名は以下のようにしてください。

* 「libstdc++-6.dll」はmod_spatialiteに同梱されているほうに合わせて「libstdc++_64-6.dll」にリネームする。
* 「libgcc_s_seh-1.dll」は「libgcc_s_seh-1.dll」のままにする。

「libgcc_s_seh-1.dll」のほうもmod_spatialite同梱版に合わせた名前にリネームすると、私の環境では正しく動作しませんでした。
まぎらわしいので、もともと同梱されていた「libgcc_s_seh_64-1.dll」は削除してしまいましょう。

dll入れ替え後のディレクトリはこんな感じになります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/122210/f3454283-a340-91b8-7d74-be5e1c7be410.png)
図中、選択されている2ファイルが入れ替えたものです。



なお、このdll入れ替えについては以下を参考にしました。
http://blog.jrg.com.br/2016/04/25/Fixing-spatialite-loading-problem/


## ビルドイベントの設定

mod_spatialite.dll以下関連dll群を、VSのC#プロジェクトが出力するexeと同じディレクトリにコピーするようビルドイベントを設定します。

プロジェクトのプロパティの「ビルドイベント」から、「ビルド後イベントのコマンドライン」に以下のような感じで書いておきましょう。

```
copy <mod_spatialiteのあるディレクトリ>\*.dll $(ProjectDir)$(OutDir)
```

## 動作確認

さて、ではいよいよ動作確認です。以下のようなコードを実行してみましょう。

```C#
using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace cstx64
{
    class Program
    {
        private static void ExecuteNonQuery(string sql, SQLiteConnection conn)
        {
            using (var command = new SQLiteCommand(sql, conn))
            {
                command.ExecuteNonQuery();
            }
        }

        private static void ExecuteScalar(string sql, SQLiteConnection conn)
        {
            using (var command = new SQLiteCommand(sql, conn))
            {
                command.ExecuteScalar();
            }
        }

        private static void LoadExtension(SQLiteConnection conn)
        {
            var modulePath = @"mod_spatialite";
            conn.EnableExtensions(true);
            conn.LoadExtension(modulePath);
        }

        public static void Main(string[] args)
        {
            string fileName = @".\test.db";
            if (System.IO.File.Exists(fileName))
            {
                System.IO.File.Delete(fileName);
            }

            using (var conn = new SQLiteConnection(string.Format("Data Source={0};Version=3", fileName)))
            {
                conn.Open();

                LoadExtension(conn);
                string sql = " CREATE TABLE IF NOT EXISTS t (id, name, point)";
                ExecuteNonQuery(sql, conn);

                sql = "INSERT INTO t VALUES(1, 'some', ST_GeomFromText('POINTZ(1.0 2.0 3.0)'))";
                ExecuteNonQuery(sql, conn);

                sql = "SELECT id, name, ST_AsText(point) AS pt FROM t";
                using (var command = new SQLiteCommand(sql, conn))
                {
                    using (var dr = command.ExecuteReader())
                    {
                        while (dr.Read())
                        {
                            int id = Convert.ToInt32(dr["id"].ToString());
                            string name = dr["name"].ToString();
                            string pt = dr["pt"].ToString(); ;
                            Console.WriteLine(String.Format("{0}, {1}, {2}", id, name, pt));
                        }
                    }
                }
                conn.Close();
            }
        }
    }
}


```

`LoadExtension`メソッドで、SpatiaLiteの拡張モジュールをロードしています。
ロードする前に`EnableExtensions`で拡張を有効化しなければならないので注意です。

（さらにいうと、この`EnableExtensions`と`LoadExtension`の操作は、`SQLiteConnection`インスタンスごとに実行する必要があります。）


ロードが正しく行われると、その後のINSERT文、SELECT文にあるような`ST_xxx`の関数が利用できるようになります。
ここでは適当に座標が(1,2,3)の点をpointカラムに投入し、それが取れていることを確認しています。

```
1, some, POINT Z(1 2 3)
```

これで動作確認完了です。お疲れさまでした。