QGISにpandas&geopandasをインストールしたくないけど、機能は使いたい

# はじめに
この記事ではQGISのプラグイン開発を行うにあたって、
**「geopandasの機能が使いたいけど、ユーザーに煩雑なインストール作業を強いたくない！」**
という願いを叶えるための記事です。

ユーザーがインストール作業することに抵抗がない場合は、
以下の記事の方がスマートだと思います。

https://qiita.com/ozo360/items/87334983e7e9b95d50e9

# まず結論
先に結論を言いますが、この記事では
**pandas&geopandasの機能を使った処理のみをexe化して、プラグインからexeを実行させる**
という方法で実現しています。

「exe化はしたくない」という縛りがある場合、この記事では解決しません。
あしからず。

# 実行環境
- Windows10
- QGIS 3.16

# geopandasの処理をexe化する
### geopandas処理を含む部分だけを別ファイルにする
exe化をするにあたって、まずはgeopandasの機能を使っている処理を
関数にしておき、別のpyファイルに保存します。
この時、**関数に読み込みたい引数はコマンドライン引数で与える**ようにしておきます。

正直、文章だけだと意味が分からないと思うので、
サンプルプログラムとして*'extract.py'*を作ってみました。

「[国土数値情報 道の駅データ](https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-P35.html)から温泉施設がある道の駅を抽出する」
という機能を*'extract_hotspring'*という関数として作っています。
~~（使用例を見せたいだけなので、実用性とか考えなくていいよね）~~

```python:extract.py
import sys
import pandas as pd
import geopandas as gpd

def extract_hotspring(args):

    # argsの中身は以下の通り
    # args = [extract.py, shpファイルのパス]

    # shpファイルのパスを取得
    shp_file = args[1]

    # shpファイルからDataframeを生成
    df = gpd.read_file(shp_file, encoding='Shift-JIS')

    # 温泉施設がある（P35_018が1になっている）データを抽出
    df = df[df['P35_018'] == 1]

    # 出力ファイルを指定
    export_file = shp_file.replace('.shp', '_onsen.shp')

    # Dataframeをファイルに出力
    df.to_file(export_file)

    return 0

if __name__ == '__main__':

    # コマンドライン引数の読み込み
    args = sys.argv

    # 温泉抽出関数の呼び出し
    extract_hotspring(args)

    print('end')
```

上記のファイルでは、*'extract_hotspring'*にはshpファイルを直接渡さずに
コマンドライン引数*'args'*を渡すようになっていますね。

### 作成したpyファイルをexe化する
pyファイルのexe化は色々なやり方があると思いますが、
ここではcx_freezeを使ってexeファイルする方法にします。
（pyinstallerはexeファイル作るのにやたらと時間がかかったので不採用です）

cx_freezeの使い方は以下の記事が分かりやすいので
変に説明せずにリンクを貼っておきますね。

https://self-development.info/cx_freeze%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9%E3%80%90python%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%AEexe%E5%8C%96%E3%80%91/

サンプルプログラムで動作確認したい方は、
cx-freezeのインストール後にコマンドプロンプトにて、以下のコマンドを実行してください。
（念のため書きますが、*'extract.py'*があるフォルダで実行してくださいね）


```
cxfreeze -c extract.py --target-dir exe_file
```

無事に動けば以下の様に、指定した名前（今回は*'exe_file'*）のフォルダが作られます。
![geopandas_exe.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1147458/77880332-a6f8-a864-7207-2559e9567978.png)

ちなみに*'data'*とか*'downloads'*はzipとかshpファイルを
保存したフォルダなので気にしなくてOKです。

### pyqgisでexeファイルを実行する
pyqgisのコード内でexeファイルを指定して、実行させるようにすればOKです。

```python
subprocess.run([exeファイル, 引数])
```

...が、この際exeファイルは**フォルダごとプラグインフォルダに移す**ことをお勧めします。

理由は以下二点です。

- exeファイルの格納場所は統一しておく必要がある
- ユーザー視点で、スクリプトとexeを別の場所にコピーするのは面倒

これもイメージがつきにくいと思うので、具体例を示しながら補足説明します。

pyqgisでプラグインを作成する場合、以下のフォルダ内に配置すると思います。
（この辺は詳しくないので、自信ありませんが）

```
C:\Program Files\QGIS 3.16\apps\qgis\python\plugins
```

*'plugins'*フォルダの中に自作プラグイン（今回は*'plugin_hotspring'*としました）を格納します。
![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1147458/6d9181d0-a394-158e-b296-9df197c72764.png)
この時、**自作プラグインのフォルダ内にexeフォルダを入れておきます**。
![図2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1147458/e61bb4ff-96c2-7b63-c836-c07490e21ac2.png)
こうしておくと、プラグインの導入と同時にexeファイルのパスも確定するので
*'plugin.py'*の中でexeファイルが指定しやすくなります。

```python:plugin.py(一部抜粋)
def main_plugin(argument):

    # exeファイルのパスを指定する（パスが固定されるので引数にしなくて済む）
    exe_file = r"C:\\Program Files\\QGIS 3.16\\apps\\qgis\\python\\plugins\\plugin_hotspring\\exe_file\\extract.exe"

    # exeファイルを実行する
    subprocess.run([exe_file, shp_file])

    return 0
```

# 終わりに
今回はQGISのプラグイン開発で使いたくなるであろうgeopandasに
フォーカスを当てて書いてみました。

試してはいませんが、他のライブラリでも同様の手順で対応できると思うので
QGISのpython環境に入っていないライブラリを使いたい時はお試しください。

まあ個人的には「QGISのpythonならpandas&geopandasはデフォルトで入れておいてよ」
というのが本音ですけどね...