dim+FIWARE(StarSeeker)+opdutilで自治体オープンデータを一気に地図化する

# TL;DR

* [自治体(文京区)オープンデータ](https://www.city.bunkyo.lg.jp/kusejoho/opendata/portalsite.html)から緯度、経度入っているデータセットを抽出しすべて地図上に可視化した
  ![文京区緊急避難場所.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21963/c966376a-b775-e00c-a318-5b273f2fba2b.png)
* 試しにインプリしたサイト
  * 地図: https://bunkyo.starseeker.blackaplysia.com/
  * Orion (GET以外は405を返す):
    * entity 100件取得: https://bunkyo-orion.starseeker.blackaplysia.com/v2/entities?limit=100
    * 特定のentity取得: https://bunkyo-orion.starseeker.blackaplysia.com/v2/entities/50016
    * 特定のentityeをkey-valueで取得: https://bunkyo-orion.starseeker.blackaplysia.com/v2/entities/50016?options=keyValues
* 利用したおもなツール
  * [StarSeeker](https://github.com/c-3lab/StarSeeker): Next.js+PostgreSQL+FIWAREによりJSONで登録するデータを地図上に表示するソフトウェア
  * [FIWARE Orion](https://github.com/telefonicaid/fiware-orion): NGSIをインターフェースとするメッセージブローカー(StarSeekerに同梱)
  * [dim](https://github.com/c-3lab/dim): オープンデータを管理するパッケージマネージャー
  * [opdutil](https://github.com/mkyutani/opdutil): オープンデータのCSVを解析するために今回作成した汎用ツール

# おおまなか手順

1. StarSeekerをインストールし地図上にデータを可視化するしくみを構築
1. `dim`および`opdutil`により、オープンデータからStarSeekerのデータを作成
1. StarSeekerにデータを投入

# StarSeekerインストール

StarSeekerの説明に沿って地図の表示までを行う。

まず、作業ディレクトリ`/work`に`git clone`したのち、環境ファイル`~/StarSeeker/StarSeeker/.env`を編集する。(`MAP_DEFAULT_*`は文京区役所の位置を設定)

```
MONGOUSERNAME=mongouser
MONGOPASSWORD=mongopassword
POSTGREUSER=pguser
POSTGREPASSWORD=pgpassword
MAP_DEFAULT_LATITUDE=35.7079305
MAP_DEFAULT_LONGITUDE=139.7524542
MAP_DEFAULT_ZOOM=14
```

StarSeekerのサーバ群を起動する。

```
~/StarSeeker/StarSeeker$ docker-compose up -d
```

ブラウザで`http://ホスト名:3000`に接続し地図が表示されることを確認(ハンバーガーアイコンからデータセットを選択しようとするエラーが出る)

SiteSeekerのoperatorを準備し起動する。

```
~/StarSeeker/StarSeeker$ cd operator
~/StarSeeker/StarSeeker/operator$ cp -r samples work
~/StarSeeker/StarSeeker/operator$ ln -s ../.env .env
~/StarSeeker/StarSeeker/operator$ docker-compose up -d
```

# オープンデータの取得と加工

以下は、StarSeekerのoperator端末(op)で行うため、`git`用の`id_rsa`をdockerホストから転送後、operator端末のシェルに入る。なお、ホスト側がrootでない場合は`chown`でownerを変更する。

```
~/StarSeeker/StarSeeker/operator$ sudo docker exec --user root:root op /bin/mkdir /root/.ssh
~/StarSeeker/StarSeeker/operator$ sudo docker cp ~/.ssh/id_rsa op:/root/.ssh
~/StarSeeker/StarSeeker/operator$ sudo docker exec op /bin/chown root:root /root/.ssh/id_rsa_github
~/StarSeeker/StarSeeker/operator$ docker exec -it op /bin/bash
root@op:/work# 
```

## `git`の初期設定

`dim`および`opdutil`を`git clone`して使用するため`git`の初期設定を行う。

### SSH (`~/.ssh/config`)の設定

```
root@op:/work# echo 'Host github.comLF  Hostname github.comLF  User my.githubuserLF  IdentityFile "/root/.ssh/id_rsa"' | sed 's/LF/\n/g' >>/root/.ssh/config
```

### `git config`の設定

```
root@op:/work# git config --global user.name my.name
root@op:/work# git config --global user.email my@email
```

## `dim`の準備

オープンデータパッケージマネージャーの`dim`を導入する。`dim`は[deno](https://deno.land/)環境で動作するため、まず`deno`、その後`dim`をインストールする。

### `deno`のインストール

```
root@op:/work# cd ~
root@op:/root# curl -fsSL https://deno.land/install.sh | sh
root@op:/root# echo 'export DENO_INSTALL="/root/.deno"' >>~/.bashrc
root@op:/root# echo 'export PATH="$DENO_INSTALL/bin:$PATH"' >>~/.bashrc
root@op:/root# source ~/.bashrc
```

### `dim`のインストール

作業ディレクトリ`/work`に戻り、`dim`をインストールする。

```
root@op:/root# cd /work
root@op:/work# git clone git@github.com:c-3lab/dim.git
root@op:/work# cd dim
root@op:/work/dim# deno install --unstable --allow-read --allow-write --allow-run --allow-net dim.ts
```

# 文京区のオープンデータからStarSeeker用ファイルを生成

## `opdutil`のインストール

作業ディレクトリにオープンデータ加工ユーティリティ`opdutil`をインストールする。

```
root@op:/work/dim# cd /work
root@op:/work# git clone git@github.com:mkyutani/opdutil.git
root@op:/work# cd opdutil
root@op:/work/opdutil# pip3 install .
```

## `dim`の初期化

`opdutil`のディレクトリでdimを初期化する。

```
root@op:/work/opdutil# dim init
```

## 文京区オープンデータの一括取得

`opdlist`にオープンデータHTMLページのURLを渡すと、そのHTML中に含まれるCSVファイルのリンクとその周辺にあるデータ名称の2つ組がCSV形式で出力される。

```
root@op:/work/opdutil# opdlist https://www.city.bunkyo.lg.jp/kusejoho/opendata/portalsite.html
https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/07metadata/metadata.csv,メタデータ（Excelファイル; 19KB）|（CSVファイル; 9KB）
https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/01shukaishisetsu/syukaishisetsu.csv,集会施設 （Excelファイル; 16KB）|（CSVファイル; 4KB）
...
```

この出力結果を加工し`dim install`に渡すことですべて`dim`に登録することができる。なお、この例では名前の後ろに余計な文字列(ExcelファイルとかCSVファイルとか)がついているため`sed`で削除しておく。

```
root@op:/work/opdutil# opdlist https://www.city.bunkyo.lg.jp/kusejoho/opendata/portalsite.html | sed 's/[(（].*[)）]//; s/,/ -n /' | while read line; do sh -c -x "dim install $line"; done
```

ここで`dim`の実行ログを見ると、1件だけ「The name already exists」で失敗。直前のものと同じデータセット名になっていることがわかる。`dim`はデータセット名(`-n`オプション)が重複した場合は登録できない。

```
...
+ dim install https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/02b-guru/b-guru-sendagi-komagome.csv -n コミュニティバス「Bーぐる」の時刻表
Installed to ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/02b-guru/b-guru-sendagi-komagome.csv
+ dim install https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/02b-guru/b-guru-mezirodai-kohinata.csv -n コミュニティバス「Bーぐる」の時刻表
The name already exists.
...
```

そこで、失敗したCSVのデータセット名を変更して再登録する。

```
root@op:/work/opdutil# dim install https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/02b-guru/b-guru-mezirodai-kohinata.csv -n コミュニティバス「Bーぐる」の時刻表（目白台・小日向）
```

`dim`にすべて登録されたことを確認できた。(`dim`はシンプルだが強力なツール!)

```
root@op:/work/opdutil# dim list -s
メタデータ https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/07metadata/metadata.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/07metadata/metadata.csv
集会施設 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/01shukaishisetsu/syukaishisetsu.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/01shukaishisetsu/syukaishisetsu.csv
コミュニティバス「Bーぐる」の時刻表 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/02b-guru/b-guru-sendagi-komagome.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/02b-guru/b-guru-sendagi-komagome.csv
自転車駐車場 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/03zitensya/zitensyatyusyajo.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/03zitensya/zitensyatyusyajo.csv
公衆無線LAN設置場所 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/04kosyumusenlan/kosyumusenlan.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/04kosyumusenlan/kosyumusenlan.csv
ごみと資源の収集日 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/05syusyubi/syusyubi.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/05syusyubi/syusyubi.csv
ごみと資源の分別品目 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/06bunbetuhinmoku/bunbetuhinmoku.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/06bunbetuhinmoku/bunbetuhinmoku.csv
資源の回収拠点 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/07kaisyukyoten/kaisyukyoten.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/07kaisyukyoten/kaisyukyoten.csv
区内サイクルポート一覧 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/10kunaisaikurupotoitiran/eriabetupotomap.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/10kunaisaikurupotoitiran/eriabetupotomap.csv
保育園 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/02kosodate-kyouiku/02hoikuen/hoikuen.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/02kosodate-kyouiku/02hoikuen/hoikuen.csv
幼稚園 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/02kosodate-kyouiku/01yotien/yotien.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/02kosodate-kyouiku/01yotien/yotien.csv
区立小学校 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/02kosodate-kyouiku/03syogakko/syogakko.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/02kosodate-kyouiku/03syogakko/syogakko.csv
区立中学校 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/02kosodate-kyouiku/04tyugakko/tyugakko.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/02kosodate-kyouiku/04tyugakko/tyugakko.csv
育成室 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/02kosodate-kyouiku/05ikuseishitsu/ikuseishitsu.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/02kosodate-kyouiku/05ikuseishitsu/ikuseishitsu.csv
児童館 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/02kosodate-kyouiku/06zidokan/zidokan.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/02kosodate-kyouiku/06zidokan/zidokan.csv
地域子育て支援拠点 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/02kosodate-kyouiku/07chiikikosodatesienkyoten/chiikikosodatesienkyoten.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/02kosodate-kyouiku/07chiikikosodatesienkyoten/chiikikosodatesienkyoten.csv
キッズルーム https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/02kosodate-kyouiku/08kidsroom/kidsroom.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/02kosodate-kyouiku/08kidsroom/kidsroom.csv
子ども・乳幼児ショートステイ https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/02kosodate-kyouiku/09shortstay/shortstay.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/02kosodate-kyouiku/09shortstay/shortstay.csv
病児・病後児保育ルーム https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/02kosodate-kyouiku/10byojihoikuroom/byojihoikuroom.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/02kosodate-kyouiku/10byojihoikuroom/byojihoikuroom.csv
文化・スポーツ施設 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/03bunka-kankou-sports/01bunka-sports/bunka-sports.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/03bunka-kankou-sports/01bunka-sports/bunka-sports.csv
区立図書館 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/03bunka-kankou-sports/02tosyokan/tosyokan.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/03bunka-kankou-sports/02tosyokan/tosyokan.csv
博物館一覧 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/03bunka-kankou-sports/03hakubutsukanitiran/hakubutsukan.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/03bunka-kankou-sports/03hakubutsukanitiran/hakubutsukan.csv
診療所 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/04hoken-fukushi/01shinryojo/shinryojo.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/04hoken-fukushi/01shinryojo/shinryojo.csv
歯科診療所 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/04hoken-fukushi/02shikashinryojo/shikashinryojo.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/04hoken-fukushi/02shikashinryojo/shikashinryojo.csv
施術所 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/04hoken-fukushi/03sejutsusyo/sejutsusyo.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/04hoken-fukushi/03sejutsusyo/sejutsusyo.csv
薬局 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/04hoken-fukushi/04yakkyoku/yakkyoku.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/04hoken-fukushi/04yakkyoku/yakkyoku.csv
高齢者あんしん相談センター https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/04hoken-fukushi/05koreisyaanshinsodancenter/koreisyaanshinsodancenter.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/04hoken-fukushi/05koreisyaanshinsodancenter/koreisyaanshinsodancenter.csv
地域密着型サービス事業所 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/04hoken-fukushi/06tiikimittyakugataservicezigyosyo/tiikimittyakugataservicezigyosyo.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/04hoken-fukushi/06tiikimittyakugataservicezigyosyo/tiikimittyakugataservicezigyosyo.csv
文京福祉センター https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/04hoken-fukushi/07bunkyofukushicenter/bunkyofukushicenter.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/04hoken-fukushi/07bunkyofukushicenter/bunkyofukushicenter.csv
緊急避難場所・避難所 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/05bousai-macidukuri-kankyou/01kinkyuhinanbasyo-hinanjo/kinkyuhinanbasyo-hinanjo.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/05bousai-macidukuri-kankyou/01kinkyuhinanbasyo-hinanjo/kinkyuhinanbasyo-hinanjo.csv
土のうステーション https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/05bousai-macidukuri-kankyou/02donostation/donostation.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/05bousai-macidukuri-kankyou/02donostation/donostation.csv
区立公園・児童遊園・遊び場 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/05bousai-macidukuri-kankyou/03kouen-asobiba/kuritsukouen-jidouyuuen-asobiba.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/05bousai-macidukuri-kankyou/03kouen-asobiba/kuritsukouen-jidouyuuen-asobiba.csv
区内消防署 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/05bousai-macidukuri-kankyou/05kunaisyoubousyoichiran/kunaisyoubousyo.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/05bousai-macidukuri-kankyou/05kunaisyoubousyoichiran/kunaisyoubousyo.csv
交通事故の年別推移 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/05bousai-macidukuri-kankyou/08kotsujikokensunonenbetsusuii/kotsujikononenbetsusuii.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/05bousai-macidukuri-kankyou/08kotsujikokensunonenbetsusuii/kotsujikononenbetsusuii.csv
文京区管理橋梁 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/05bousai-macidukuri-kankyou/09bunkyokukanrikyoryo/bunkyokukanrikyoryo.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/05bousai-macidukuri-kankyou/09bunkyokukanrikyoryo/bunkyokukanrikyoryo.csv
区内公衆便所 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/05bousai-macidukuri-kankyou/10kunaikosyubenjyo/kunaikosyubenjyo.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/05bousai-macidukuri-kankyou/10kunaikosyubenjyo/kunaikosyubenjyo.csv
AED設置箇所一覧 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/05bousai-macidukuri-kankyou/11aedsettikasyoitiran/aedsettikasyoitiran.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/05bousai-macidukuri-kankyou/11aedsettikasyoitiran/aedsettikasyoitiran.csv
区設貯水槽一覧表 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/05bousai-macidukuri-kankyou/12kusetutyosuisoitiranhyo/tyosuiso.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/05bousai-macidukuri-kankyou/12kusetutyosuisoitiranhyo/tyosuiso.csv
投票場所 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/06kuseijyouhou/06tohyobasyo/tohyobasyo.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/06kuseijyouhou/06tohyobasyo/tohyobasyo.csv
イベント一覧 https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/10sonota/01event/131059_event.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/10sonota/01event/131059_event.csv
コミュニティバス「Bーぐる」の時刻表（目白台・小日向） https://www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/02b-guru/b-guru-mezirodai-kohinata.csv ./data_files/www.city.bunkyo.lg.jp/library/opendata-bunkyo/01tetsuduki-kurashi/02b-guru/b-guru-mezirodai-kohinata.csv
```

## (参考)`dim list`のつかいかた

以下では`dim list`を頻繁に使用するため、イディオム的な使い方を説明する。

* 登録されているデータセットの名前を列挙

```
$ dim list -s | cut -d' ' -f 1
```

* 名前に「小学校」を含むデータセットのCSVファイルパスを取り出す(パイプで後続のファイルに渡す典型的な使い方)

```
$ dim list -s | grep 小学校 | cut -d' ' -f 3
```

* 全データセットから「文京シビックセンター」という文字列のある行を取り出し、ファイル名(basename)を添えて表示

```
$ for csvpath in `dim list -s | cut -d' ' -f 3`; do cat $csvpath | iconv -f cp932 -t utf-8 | grep -n 文京シビックセンター | sed "s#^#`basename -s .csv $csvpath`::#"; done
```

なお、最後の例にあるように、一般的にオープンデータCSVファイルの文字コードはWindowsに親和性が高いいわゆるシフトJISであることが多くlinuxでは扱いづらいため、必要に応じてutf-8に変換する。この場合、変換元ファイルの文字コード名はShift_JISではなくCP932が適切であることが多い。(CP932とShift_JISは一部の環境依存文字が異なる)文字コードについては`dim`で登録するときにコマンドラインオプションで指定して変換しておいてもよい。

## 4つ組(名称、住所、緯度、経度)の取り出しとStarSeeker用ファイルの生成

今回はStarSeekerの地図に表示するため、`opddetect`と`opdselect`を使い、文京区のデータから(名称、住所、緯度、経度)の4つ組を取り出す。元となるCSVファイルパスとデータセット名を標準入力から渡せるため、`dim`からのパイプで渡す。
  * `opddetect`の機能: コマンドラインオプション`--hint`で指定した正規表現群にマッチする行(CSVヘッダ行)を探し、それぞれのデータを格納したカラム番号を特定する。
  * `opdselect`の機能: コマンドラインオプション`--hint`で指定した正規表現群にマッチする行で特定したカラムについて、元のファイルからカラム選択して出力する。

### ヘッダの推定と該当カラムの特定(`opddetect`)

まずは「名」「住所」「緯度」「経度」をもつ行をヘッダと推定し、カラムを特定してみる。

```
root@op:/work/opdutil# dim list -s | cut -d' ' -f 1,3 | opddetect --encoding cp932 --hint '名,住所,緯度,経度' --csv 2>/dev/null
syukaishisetsu.csv,1,A:施設名,D:住所,E:緯度,F:経度
kosyumusenlan.csv,1,B:名称,C:住所,D:緯度,E:経度
hoikuen.csv,1,B:施設名,E:住所,F:緯度,G:経度
yotien.csv,1,B:施設名,E:住所,F:緯度,G:経度
syogakko.csv,1,B:施設名,E:住所,F:緯度,G:経度
tyugakko.csv,1,B:施設名,E:住所,F:緯度,G:経度
ikuseishitsu.csv,1,A:育成室名称,D:住所,E:緯度,F:経度
zidokan.csv,1,A:施設名,D:住所,E:緯度,F:経度
chiikikosodatesienkyoten.csv,1,A:施設名,E:住所,F:緯度,G:経度
kidsroom.csv,1,A:施設名,D:住所,E:緯度,F:経度
shortstay.csv,1,A:事業名,D:住所,E:緯度,F:経度
byojihoikuroom.csv,1,A:施設名,D:住所,E:緯度,F:経度
bunka-sports.csv,1,A:施設名,D:住所,E:緯度,F:経度
tosyokan.csv,1,A:施設名,D:住所,E:緯度,F:経度
hakubutsukan.csv,1,C:都道府県名,H:住所,I:緯度,J:経度
koreisyaanshinsodancenter.csv,1,A:施設名,D:住所,E:緯度,F:経度
tiikimittyakugataservicezigyosyo.csv,2,B:名　　称,E:住所,F:緯度,G:経度
bunkyofukushicenter.csv,1,A:施設名,D:住所,E:緯度,F:経度
kinkyuhinanbasyo-hinanjo.csv,1,A:施設名,C:住所,D:緯度,E:経度
kuritsukouen-jidouyuuen-asobiba.csv,1,A:施設名,C:住所,D:緯度,E:経度
kunaisyoubousyo.csv,1,A:名称,B:住所,C:緯度,D:経度
kunaikosyubenjyo.csv,1,A:公衆便所名,B:住所,C:緯度,D:経度
aedsettikasyoitiran.csv,1,C:都道府県名,F:住所,G:緯度,H:経度
tyosuiso.csv,1,C:都道府県名,F:住所,H:緯度,I:経度
131059_event.csv,1,C:都道府県名,T:住所,U:緯度,V:経度
```

ここで、たとえば以下の例は、`syukaishisetsu.csv`(集会施設)の`1`行目がマッチし、Excel形式のカラム番号`A`、`D`、`E`、`F`がそれぞれ「施設名」、「住所」、「緯度」、「経度」という値であることを示している。
  > syukaishisetsu.csv,1,A:施設名,D:住所,E:緯度,F:経度

ヘッダを特定できなかったcsvについてはログ(標準エラー出力)に「No records like hints」と出力されていることが確認できる。

```
root@op:/work/opdutil# dim list -s | cut -d' ' -f 1,3 | opddetect --encoding cp932 --hint '名,住所,緯度,経度' --csv 2>&1 | grep 'like hints'
metadata.csv: No records like hints
b-guru-sendagi-komagome.csv: No records like hints
zitensyatyusyajo.csv: No records like hints
syusyubi.csv: No records like hints
bunbetuhinmoku.csv: No records like hints
kaisyukyoten.csv: No records like hints
eriabetupotomap.csv: No records like hints
shinryojo.csv: No records like hints
shikashinryojo.csv: No records like hints
sejutsusyo.csv: No records like hints
yakkyoku.csv: No records like hints
donostation.csv: No records like hints
kotsujikononenbetsusuii.csv: No records like hints
bunkyokukanrikyoryo.csv: No records like hints
tohyobasyo.csv: No records like hints
b-guru-mezirodai-kohinata.csv: No records like hints
```

これらの失敗例の中にも緯度経度をもつものがあるためデータを見ながら試行錯誤を経た結果、緯度経度を含むオープンデータについて以下のコマンドですべてカラム取得できることを確認した。(`tyosuiso.csv` (貯水槽)はなんと「備考」に名前が入っていた!?)

```
root@op:/work/opdutil# dim list -s | cut -d' ' -f 1,3 | opddetect --encoding cp932 --hint '名　*称|施設名|事業名|設置場所|橋梁名|投票所|便所名|イベント名|備考,住所|位置|地,緯度,経度' --csv 2>/dev/null
syukaishisetsu.csv,1,A:施設名,D:住所,E:緯度,F:経度
zitensyatyusyajo.csv,1,A:名称,B:位置,C:緯度,D:経度
kosyumusenlan.csv,1,B:名称,C:住所,D:緯度,E:経度
hoikuen.csv,1,B:施設名,E:住所,F:緯度,G:経度
yotien.csv,1,B:施設名,E:住所,F:緯度,G:経度
syogakko.csv,1,B:施設名,E:住所,F:緯度,G:経度
tyugakko.csv,1,B:施設名,E:住所,F:緯度,G:経度
ikuseishitsu.csv,1,A:育成室名称,D:住所,E:緯度,F:経度
zidokan.csv,1,A:施設名,D:住所,E:緯度,F:経度
chiikikosodatesienkyoten.csv,1,A:施設名,E:住所,F:緯度,G:経度
kidsroom.csv,1,A:施設名,D:住所,E:緯度,F:経度
shortstay.csv,1,A:事業名,D:住所,E:緯度,F:経度
byojihoikuroom.csv,1,A:施設名,D:住所,E:緯度,F:経度
bunka-sports.csv,1,A:施設名,D:住所,E:緯度,F:経度
tosyokan.csv,1,A:施設名,D:住所,E:緯度,F:経度
hakubutsukan.csv,1,E:名称,H:住所,I:緯度,J:経度
koreisyaanshinsodancenter.csv,1,A:施設名,D:住所,E:緯度,F:経度
tiikimittyakugataservicezigyosyo.csv,2,B:名　　称,E:住所,F:緯度,G:経度
bunkyofukushicenter.csv,1,A:施設名,D:住所,E:緯度,F:経度
kinkyuhinanbasyo-hinanjo.csv,1,A:施設名,C:住所,D:緯度,E:経度
donostation.csv,1,A:設置場所,B:住所,C:緯度,D:経度
kuritsukouen-jidouyuuen-asobiba.csv,1,A:施設名,C:住所,D:緯度,E:経度
kunaisyoubousyo.csv,1,A:名称,B:住所,C:緯度,D:経度
bunkyokukanrikyoryo.csv,1,A:橋梁名,C:所在地,D:所在地（緯度）,E:所在地（経度）
kunaikosyubenjyo.csv,1,A:公衆便所名,B:住所,C:緯度,D:経度
aedsettikasyoitiran.csv,1,E:名称,F:住所,G:緯度,H:経度
tyosuiso.csv,1,K:備考,F:住所,H:緯度,I:経度
tohyobasyo.csv,1,C:投票所,D:所在地,E:緯度,F:経度
131059_event.csv,1,E:イベント名,T:住所,U:緯度,V:経度
```

### カラムの選択(`opdselect`)

次に、`opddetect`の代わりに`opdselect`に同じコマンドラインオプションを渡して、マッチしたカラムのみ選択してみる。(これはまさにSQLの`select`のようなもの)
なお、コマンドラインオプション`--csv`を省略すると`opdselect`は同じ結果をjsonで出力する。

```
root@op:/work/opdutil# dim list -s | cut -d' ' -f 1,3 | opdselect --encoding cp932 --hint '名　*称|施設名|事業名|設置場所|橋梁名|投票所|便所名|イベント名|備考,住所|位置|地,緯度,経度' --csv 2>/dev/null
syukaishisetsu,syukaishisetsu-00000001,施設名,住所,緯度,経度
syukaishisetsu,syukaishisetsu-00000002,礫川地域活動センター,東京都文京区小石川２丁目１８番１８号,35.711995,139.750389
syukaishisetsu,syukaishisetsu-00000003,大原地域活動センター,東京都文京区千石１丁目４番３号,35.724469,139.743379
syukaishisetsu,syukaishisetsu-00000004,大塚地域活動センター,東京都文京区大塚１丁目５番１７号,35.718634,139.734637
...
```

ここで、ヘッダ行そのものは不要であるため削除する。具体的には、`opdselect`のコマンドラインオプション`--filter`で緯度、経度が実数でないものを取り除く。また、ヘッダ以外にも作成年月日だけの行などデータとしては無意味なものも含まれるため、コマンドラインオプション`--strict`で空カラムのある行も取り除く。

```
root@op:/work/opdutil# dim list -s | cut -d' ' -f 1,3 | opdselect --encoding cp932 --hint '名　*称|施設名|事業名|設置場所|橋梁名|投票所|便所名|イベント名|備考,住所|位置|地,緯度,経度' --filter ',,float,float' --strict --csv 2>/dev/null
syukaishisetsu,syukaishisetsu-00000002,礫川地域活動センター,東京都文京区小石川２丁目１８番１８号,35.711995,139.750389
syukaishisetsu,syukaishisetsu-00000003,大原地域活動センター,東京都文京区千石１丁目４番３号,35.724469,139.743379
syukaishisetsu,syukaishisetsu-00000004,大塚地域活動センター,東京都文京区大塚１丁目５番１７号,35.718634,139.734637
...
```

### StarSeeker用ファイルの生成

最後に、これらの選択されたデータをもとにStarSeeker用のファイルを生成する。

```
root@op:/work/opdutil# dim list -s | cut -d' ' -f 1,3 | opdselect --encoding cp932 --hint '名　*称|施設名|事業名|設置場所|橋梁名|投票所|便所名|イベント名|備考,住所|位置|地,緯度,経度' --filter ',,float,float' --strict --post-process starseeker --post-process-args base=50000 name=文京区オープンデータ color=white attributes='address:住所,locationName(text):施設名,location(geo:point),time(DateTime)' 2>/dev/null
root@op:/work/opdutil# ls -l *.csv
-rw-r--r-- 1 root root     51 Feb 28 00:30 category.csv
-rw-r--r-- 1 root root 241043 Feb 28 00:30 data.csv
-rw-r--r-- 1 root root   4229 Feb 28 00:30 dataset.csv
```

# StarSeekerへのデータ登録

オープンデータのCSVを作成することができたので、StarSeekerにデータを登録する。

## 初期状態

いったんStarSeekerの作業ディレクトリに戻り、初期xlsxからcsvを取り出す。このうち`tables.csv`だけはそのまま使い、まずはStarSeekerの管理テーブルを作成する。

```
root@op:/work/opdutil# cd /work
root@op:/work# ./xlsx2csv-all.sh
root@op:/work# ls -l *.csv
-rw-r--r-- 1 root root    140 Feb 28 00:36 category.csv
-rw-r--r-- 1 root root   1637 Feb 28 00:36 point.csv
-rw-r--r-- 1 root root  47319 Feb 28 00:36 point_data.csv
-rw-r--r-- 1 root root    879 Feb 28 00:36 surface.csv
-rw-r--r-- 1 root root 735332 Feb 28 00:37 surface_data.csv
-rw-r--r-- 1 root root   2182 Feb 28 00:37 tables.csv
root@op:/work# cd opdutil
root@op:/work/opdutil# ss_conductor table create ../tables.csv --send $DSN
```

ここで画面を呼び出すと以下のように地図が表示される。

![文京区.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21963/e4ee6d8d-73dd-099d-4d73-7515f386c3ae.png)

## データセット一覧の作成

文京区の`category.csv`からPostgresのデータセットカテゴリ定義DMLを生成する。

```
root@op:/work/opdutil# ss_conductor category create ../tables.csv ./category.csv
insert into t_category (category_id,category_name,category_color,display_order,enabled) values (50000,'文京区オープンデータ','white',10,TRUE);
```

Postgresにデータセットカテゴリ定義DMLを投入する。なお、環境変数`$DSN`はStarSeekerにより自動的に設定されている。

```
root@op:/work/opdutil# ss_conductor category create ../tables.csv ./category.csv --send $DSN
```

Postgresのデータセット定義DMLを生成する。

```
root@op:/work/opdutil# ss_conductor dataset create --name point ../tables.csv ./dataset.csv
insert into t_point_dataset (point_dataset_id,category_id,point_dataset_name,point_color_code,entity_type,coordinates_attr_name,register_time_attr_name,enabled) values (50001,50000,'syukaishisetsu','green','syukaishisetsu','location','time',TRUE);
insert into t_point_detail (point_detail_id,point_dataset_id,item_attr_name,data_type,display_title,display_order,enabled) values (50002,50001,'address',0,'住所',1,TRUE);
insert into t_point_detail (point_detail_id,point_dataset_id,item_attr_name,data_type,display_title,display_order,enabled) values (50003,50001,'locationName',0,'施設名',2,TRUE);
insert into t_point_dataset (point_dataset_id,category_id,point_dataset_name,point_color_code,entity_type,coordinates_attr_name,register_time_attr_name,enabled) values (50024,50000,'zitensyatyusyajo','#7c8869','zitensyatyusyajo','location','time',TRUE);
...
```

Postgresにデータセット定義DMLを投入する。

```
root@op:/work/opdutil# ss_conductor dataset create --name point ../tables.csv ./dataset.csv --send $DSN
```

DML投入後にはデータセット一覧を確認することができる。Orionにデータを投入していないため、データセットを選択してもデータは表示されない。

![文京区データセット一覧.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21963/133e4c8d-6c42-1554-fe5b-7eaf797a7df1.png)

## Orionへのデータ投入

Orionに投入するエンティティJSONを生成する。

```
root@op:/work/opdutil# ss_conductor data create ../tables.csv ./data.csv
POST {'id': '50004', 'type': 'syukaishisetsu', 'address': {'type': 'text', 'value': '礫川地域活動センター'}, 'locationName': {'type': 'text', 'value': '東京都文京区小石川２丁目１８番１８号'}, 'location': {'type': 'geo:point', 'value': '35.711995, 139.750389'}, 'time': {'type': 'datetime', 'value': '2022-02-28T00:30:37'}}
POST {'id': '50005', 'type': 'syukaishisetsu', 'address': {'type': 'text', 'value': '大原地域活動センター'}, 'locationName': {'type': 'text', 'value': '東京都文京区千石１丁目４番３号'}, 'location': {'type': 'geo:point', 'value': '35.724469, 139.743379'}, 'time': {'type': 'datetime', 'value': '2022-02-28T00:30:37'}}
POST {'id': '50006', 'type': 'syukaishisetsu', 'address': {'type': 'text', 'value': '大塚地域活動センター'}, 'locationName': {'type': 'text', 'value': '東京都文京区大塚１丁目５番１７号'}, 'location': {'type': 'geo:point', 'value': '35.718634, 139.734637'}, 'time': {'type': 'datetime', 'value': '2022-02-28T00:30:37'}}
...
```

OrionにエンティティJSONを投入する。ここでも環境変数`$BROKER`はStarSeeker起動時に自動的に設定されているものを使う。ログの3つ組の意味は、それぞれ、投入したエンティティのID、HTTPレスポンスコード、追加情報である。Orionの場合、HTTPレスポンスコードが201であれば投入に成功している。

```
root@op:/work/opdutil# ss_conductor data create ../tables.csv ./data.csv --send $BROKER
50004 201  http://orion:1026/v2/entities
50005 201  http://orion:1026/v2/entities
50006 201  http://orion:1026/v2/entities
...
```

Orionへのエンティティ投入ログを見ると1件だけ`BadRequest`となっているが、一見すると緯度が異常値のように見える。

```
50242 400 BadRequest: geo coordinates format error [see Orion user manual]: 35727055, 139.746606 http://orion:1026/v2/entities
```

ログに出ているIDを参考に`opdselect`の出力したファイルである`data.csv`を確認すると、「hoikuen」(保育園)データセットの「キッズラボ千石園」であることがわかる。

```
root@op:/work/opdutil# ss_conductor data create ../tables.csv ./data.csv | grep 50242
POST {'id': '50242', 'type': 'hoikuen', 'address': {'type': 'text', 'value': 'キッズラボ千石園'}, 'locationName': {'type': 'text', 'value': '東京都文京区本駒込2丁目9番8号'}, 'location': {'type': 'geo:point', 'value': '35727055, 139.746606'}, 'time': {'type': 'datetime', 'value': '2022-02-28T00:30:37'}}
```

`dim`に戻ってソースCSVを確認すると文京区から取得したデータそのものに間違いがあることが判明した。(文京区さん修正お願いします!)

```
root@op:/work/opdutil# iconv -f cp932 -t utf-8 <`dim list -s | grep hoikuen | cut -d' ' -f3` | grep キッズラボ千石園
101,キッズラボ千石園,,113-0022,東京都文京区本駒込2丁目9番8号,35727055,139.746606,03-5981-8677,私立保育園,認可保育園,03-5981-8626,76,6,10,12,16,32,,
```

## 結果の確認

StarSeekerはOrionからデータを取得して地図に表示することができた。(緊急避難場所を表示している例)

![文京区緊急避難場所.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21963/c966376a-b775-e00c-a318-5b273f2fba2b.png)

StarSeekerではOrionのポートも公開していおり、直接NGSIでデータを取得することもできる。ただし、素のOrionをインターネットに晒すことは、データの登録・更新もできてしまうことになるため、実際には、Orionの手前にAPI GatewayやReverse Proxyなどを配置し、アプリケーションレイヤーでのアクセス制御を行うことが望ましい。

```
$ curl -s http://ホスト:1026/v2/entities?limit=5 | jq .
[
  {
    "id": "50886",
    "type": "aedsettikasyoitiran",
    "address": {
      "type": "text",
      "value": "こひなた保育園",
      "metadata": {}
    },
    "location": {
      "type": "geo:point",
      "value": "35.714013, 139.736125",
      "metadata": {}
    },
    "locationName": {
      "type": "text",
      "value": "小日向1-21-1",
      "metadata": {}
    },
    "time": {
      "type": "datetime",
      "value": "2022-02-28T00:30:37",
      "metadata": {}
    }
  },
  {
    "id": "50887",
    "type": "aedsettikasyoitiran",
    "address": {
      "type": "text",
      "value": "駒込保育園",
      "metadata": {}
    },
    "location": {
      "type": "geo:point",
      "value": "35.729655, 139.76063",
      "metadata": {}
    },
    "locationName": {
      "type": "text",
      "value": "千駄木3-19-17",
      "metadata": {}
    },
    "time": {
      "type": "datetime",
      "value": "2022-02-28T00:30:37",
      "metadata": {}
    }
  },
  {
    "id": "50888",
    "type": "aedsettikasyoitiran",
    "address": {
      "type": "text",
      "value": "さしがや保育園",
      "metadata": {}
    },
    "location": {
      "type": "geo:point",
      "value": "35.718104, 139.749759",
      "metadata": {}
    },
    "locationName": {
      "type": "text",
      "value": "白山2-32-6",
      "metadata": {}
    },
    "time": {
      "type": "datetime",
      "value": "2022-02-28T00:30:37",
      "metadata": {}
    }
  },
  {
    "id": "50918",
    "type": "tyosuiso",
    "address": {
      "type": "text",
      "value": "千駄木三丁目第二児童遊園",
      "metadata": {}
    },
    "location": {
      "type": "geo:point",
      "value": "35.728166, 139.762126",
      "metadata": {}
    },
    "locationName": {
      "type": "text",
      "value": "千駄木3丁目12",
      "metadata": {}
    },
    "time": {
      "type": "datetime",
      "value": "2022-02-28T00:30:37",
      "metadata": {}
    }
  },
  {
    "id": "50919",
    "type": "tyosuiso",
    "address": {
      "type": "text",
      "value": "西片二丁目児童遊園",
      "metadata": {}
    },
    "location": {
      "type": "geo:point",
      "value": "35.716273, 139.756792",
      "metadata": {}
    },
    "locationName": {
      "type": "text",
      "value": "西片2丁目19",
      "metadata": {}
    },
    "time": {
      "type": "datetime",
      "value": "2022-02-28T00:30:37",
      "metadata": {}
    }
  }
]
```

# 結果と考察

* 文京区オープンデータ(CSV)で緯度、経度情報をもつデータセットを一括して取得し、以下の機能を実装することができた。
  * 地図によるデータ公開
  * API (NGSI)によるデータ公開
* このままでは以下の課題があり、ひきつづき検討していきたい。
  * APIアクセス制御: インターネットとのあいだにAPI Gateway、Reverse Proxyなどをおく
  * 住所はあるが緯度、経度のないオープンデータの地図上への表示：たとえば[IMI Tool](https://github.com/IMI-Tool-Project/imi-enrichment-address)の利用
  * データカタログとの連携
  * APIカタログとの連携
  * データセットごとの全情報の表示
  * NGSI PATCH/PUTを容易にするしくみ