GeoNLPで自由テキスト文から位置情報を抽出する

GeoNLPを利用して、テキスト文から地理情報を抜き出すというのにトライします。

# GeoNLPとは
国立情報学研究所が公開しているOSSサービスです。
自然言語文を自然言語処理・地理情報処理し、文中にある「地名」を抽出してくれます。
さらに（事前に登録しておけば）抽出した地名の経度緯度等の付随情報も吐き出してくれるスグレモノ。
特徴的なのは、抽出したい地名の辞書を作成して取り込めば、任意の地名・拠点名でも解決可能というところでしょう。
今回はこちらを実際に使ってみて、どれくらい使えるかというのを検証しました。

# セットアップ
長くなるので[別記事](https://qiita.com/Aptenodytes9/items/993108baec4f1a22c76f)にしました。
[公式ページ](https://geonlp.ex.nii.ac.jp/docs/system_developers/software/install.html)にインストール方法が説明されているので基本これに従えばOKですが、C++系に慣れていないときついかもしれません。

そしてセットアップしたあとに気づいたのですが、すでに有志の方によりDockerが配布されていました…笑
https://hub.docker.com/r/youheinakagawa/centos-geonlp

# 検証1：まずは短い文で
GeoNLPはコマンドライン・WEB API経由どちらでも呼び出し可能ですが、今回はコマンドラインで検証を進めました。

実際に、簡単な例文でどう解析されるのか確認します。

```
echo '{"method":"geonlp.parse","params":["新橋駅で雨が降っています",{"geojson":true}], "id":1}' | geonlp_api
```

レスポンスは下記のとおりです。

```
{
  "error": null,
  "id": 1,
  "result": {
    "features": [
      {
        "geometry": {
          "coordinates": [
            139.759017,
            35.664915
          ],
          "type": "Point"
        },
        "properties": {
          "body": "新橋",
          "body_kana": "シンバシ",
          "dictionary_id": 31,
          "entry_id": "eb03_6325",
          "geonlp_id": "3YCzY4",
          "hypernym": [
            "東京都",
            "浅草線"
          ],
          "kana": "シンバシエキ",
          "latitude": "35.664915",
          "longitude": "139.7590175",
          "name": "新 橋駅",
          "ne_class": "鉄道施設/地下鉄駅",
          "railway_type": "12",
          "service_provider_type": "3",
          "suffix": [
            "駅",
            ""
          ],
          "suffix_kana": [
            "エキ",
            ""
          ],
          "surface": "新橋駅"
        },
        "type": "Feature"
      },
      {
        "geometry": null,
        "properties": {
          "surface": "で雨が降って います"
        },
        "type": "Feature"
      }
    ],
    "type": "FeatureCollection"
  }
}
```
result>features以下がミソです。形態素解析の後、文章はいくつかのまとまり（feature）に分けられます。
一般的にfeatureの中から地名語を抽出できなければ「"geometry": null」となり、
抽出できれば「geometry」「properties」配下に各種解析結果を出力するようです。
上記例では1つ目のfeatureで地名語抽出に成功しており、
「geometry」以下に経度緯度が、「properties>surface」に地名語として認識できた単語（「新橋駅」）などが入っています。

# 検証2：複数文を同時に
複数の文を同時に解析にかける時は、「params」属性につっこみます。

```
echo '{"method":"geonlp.parse","params":[["新橋駅で雨が降っています","豊洲市場で朝ごはんを食べます"],{"geojson":true}], "id":1}' | geonlp_api
```

```
{
  "error": null,
  "id": 1,
  "result": {
    "features": [
      [
        {
          "geo": {
            "geometry": {
              "coordinates": [
                139.759017,
                35.664915
              ],
              "type": "Point"
            },
            "properties": {
              "body": "新橋",
              "body_kana": "シンバシ",
              "dictionary_id": 31,
              "entry_id": "eb03_6325",
              "geonlp_id": "3YCzY4",
              "hypernym": [
                "東京都",
                "浅草線"
              ],
              "kana": "シンバシエキ",
              "latitude": "35.664915",
              "longitude": "139.7590175",
              "name": "新橋駅",
              "ne_class": "鉄道施設/地下鉄駅",
              "railway_type": "12",
              "service_provider_type": "3",
              "suffix": [
                "駅",
                ""
              ],
              "suffix_kana": [
                "エキ",
                ""
              ]
            },
            "type": "Feature"
          },
          "surface": "新橋駅"
        },
        {
          "surface": "で雨が降っています"
        }
      ],
      [
        {
          "geo": {
            "geometry": {
              "coordinates": [
                139.795435,
                35.653705
              ],
              "type": "Point"
            },
            "properties": {
              "body": "豊洲",
              "body_kana": "トヨス",
              "dictionary_id": 31,
              "entry_id": "eb03_18",
              "geonlp_id": "IIj76T",
              "hypernym": [
                "ゆりかもめ",
                "東京臨海新交通臨海線"
              ],
              "kana": "トヨスエキ",
              "latitude": "35.653705",
              "longitude": "139.795435",
              "name": "豊洲駅",
              "ne_class": "鉄道施設/鉄道駅",
              "railway_type": "24",
              "service_provider_type": "5",
              "suffix": [
                "駅",
                ""
              ],
              "suffix_kana": [
                "エキ",
                ""
              ]
            },
            "type": "Feature"
          },
          "surface": "豊洲"
        },
        {
          "geo": {
            "geometry": {
              "coordinates": [
                134.944565,
                34.82431
              ],
              "type": "Point"
            },
            "properties": {
              "body": " 市場",
              "body_kana": "シジョウ",
              "dictionary_id": 31,
              "entry_id": "eb03_3337",
              "geonlp_id": "FCtT7u",
              "hypernym": [
                "神戸電 鉄",
                "粟生線"
              ],
              "kana": "シジョウエキ",
              "latitude": "34.82431",
              "longitude": "134.944565",
              "name": "市場駅",
              "ne_class": " 鉄道施設/鉄道駅",
              "railway_type": "12",
              "service_provider_type": "4",
              "suffix": [
                "駅",
                ""
              ],
              "suffix_kana": [
                "エキ",
                ""
              ]
            },
            "type": "Feature"
          },
          "surface": "市場"
        },
        {
          "surface": "で朝ごはんを食べます"
        }
      ]
    ],
    "type": "FeatureCollection"
  }
}
```

# 地名語辞書について
GeoNLPでは、地名とそれに付随する経度緯度・地名の種類（駅・都市・施設等）をまとめて「地名語」というエントリとして扱っています。
この「地名語」をCSV形式等でまとめたものを「地名解析辞書」とよび、GeoNLPに登録すれば任意の地名語を新しく追加・解析可能とすることができます。
通常、GeoNLPをインストールすると代表的な辞書はすでにインストールされていますので、初めにこれを確認します。

インストール済みの辞書を確認

```
cd /usr/local/src/geonlp-1.2.0/geonlp-dic-util
php geonlp-dic-util.php list

GeoNLP サーバ 'https://geonlp.ex.nii.ac.jp/api/dictionary' から最新の辞書一覧を取得しています．
ローカルキャッシュファイル '/usr/local/src/geonlp-1.2.0/geonlp_dic/.geonlp-dic-util/dictionary_list.json' から辞書一覧を取得しています．
未登録  geonlp/world_country    世界の国・地域（2013年9月）
最新    geonlp/japan_area       日本の地方・地域（2013年）
最新    geonlp/japan_pref       日本の都道府県（2010年4月）
最新    geonlp/japan_city       日本の郡・市区町村（2013年9月）
未登録  geonlp/japan_oaza       日本の大字（2012年）
最新    geonlp/japan_station    日本の鉄道駅（2012年）
最新    geonlp/japan_airport    日本の空港（2012年）
未登録  geonlp/japan_river      日本の河川・湖沼（2009年）
未登録  geonlp/japan_mountain   日本の山（2012年）
未登録  kitamoto/evacuation     全国の避難施設（2012年）
未登録    kitamoto/hospital       全国の医療機関（2010年9月）
…
```

「最新」と表示されていればインストール済みですが「未登録」であれば下記のようにしてインストールすることが可能です。
ここでは上記最下段で「未登録」となっている「全国の医療機関（2010年9月）」をインストールします。

```
# 辞書「全国の医療機関（2010年9月）」（辞書コード：kitamoto/hospital）をダウンロード
php geonlp-dic-util.php add kitamoto/hospital
# コンパイル
php geonlp-dic-util.php compile
# インストール
php geonlp-dic-util.php install
```

# インストールした地名語辞書の検証
インストールした結果、解析できる地名語が増えているか確認します。

インストール前

```
echo '今日は虎ノ門病院に行きました。' | geonlp_ma

今日    名詞,副詞可能,*,*,*,*,今日,キョウ,キョー
は      助詞,係助詞,*,*,*,*,は,ハ,ワ
虎ノ門  名詞,固有名詞,地名語,VXoGyC:虎ノ門駅,名詞-固有名詞-地名修飾語,*,虎ノ門,トラノモン,トラノモン
病院    名詞,一般,*,*,*,*,病院,ビョウイン,ビョーイン
に      助詞,格助詞,一般,*,*,*,に,ニ,ニ
行き    動詞,自立,*,*,五段・カ行促音便,連用形,行く,イキ,イキ
まし    助動詞,*,*,*,特殊・マス,連用形,ます,マシ,マシ
た      助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。      記号,句点,*,*,*,*,。,。,。
```

「虎ノ門病院」という単語が含まれる文章ですが、「虎ノ門」「病院」のふたつに別れており、一つの単語としては認識されませんでした。
そこで上記に従い医療機関の地名語辞書をインストールして、再度解析してみます。

インストール後

```
今日    名詞,副詞可能,*,*,*,*,今日,キョウ,キョー
は      助詞,係助詞,*,*,*,*,は,ハ,ワ
虎ノ門病院      名詞,固有名詞,地名語,7JR1g2:虎の門病院,*,*,虎の門病院,,
に      助詞,格助詞,一般,*,*,*,に,ニ,ニ
行き    動詞,自立,*,*,五段・カ行促音便,連用形,行く,イキ,イキ
まし    助動詞,*,*,*,特殊・マス,連用形,ます,マシ,マシ
た      助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。      記号,句点,*,*,*,*,。,。,。
```

無事、「虎ノ門病院」という一つの単語として形態素解析されました。


# 自作辞書のインポート
最後に、自作辞書を作成して、インポートしてみます。
[公式ページ](https://geonlp.ex.nii.ac.jp/docs/dic_developers/tutorial/index.html)にも、辞書作成→サーバーアップロード→インポートの方法が記載されていますが、
ここではサーバーアップロードはせず、作成した辞書を直接自環境にインポートしてみます。

```
#本体サーバールートディレクトリに自作辞書「testdic.csv」を配置
#自作辞書のインポート
$ cd /usr/local/src/geonlp-1.2.0/geonlp-dic-util
$ php geonlp-dic-util.php import testdic /usr/local/src/geonlp-1.2.0/geonlp_dic/testdic.csv
$ rm /usr/local/src/geonlp-1.2.0/geonlp_dic/testdic.csv
```
インポートに成功したかいなかを、先と同様にlistコマンドを使って確認します

```
# php /usr/local/src/geonlp-1.2.0/geonlp-dic-util/geonlp-dic-util.php list

未登録  geonlp/world_country    世界の国・地域（2013年9月）
最新    geonlp/japan_area       日本の地方・地域（2013年）
最新    geonlp/japan_pref       日本の都道府県（2010年4月）
最新    geonlp/japan_city       日本の郡・市区町村（2013年9月）
未登録  geonlp/japan_oaza       日本の大字（2012年）
最新    geonlp/japan_station    日本の鉄道駅（2012年）
最新    geonlp/japan_airport    日本の空港（2012年）
未登録  geonlp/japan_river      日本の河川・湖沼（2009年）
未登録  geonlp/japan_mountain   日本の山（2012年）
未登録  kitamoto/evacuation     全国の避難施設（2012年）
最新    kitamoto/hospital       全国の医療機関（2010年9月）
...
ローカル        local/testdic testdic
```

自由に地名語を追加できるのはたいへんありがたいです。


# 解析に使用する地名語辞書を限定する
上記では、一つのGeoNLP環境で数多くの辞書が有効化されていましたが、あえて使用する辞書を絞り、特定の地名語辞書に登録されている地名語だけを抽出することもできます。
例えば、「駅名の情報だけが記載されている（はずの）データベースから、効率よく地名語を抽出したい」という場合があるとします。
この時「東京駅」「札幌駅」といった単語を抽出して、駅の経度緯度を抽出したいわけですが、辞書を限定しないと「東京」「札幌」という単語のみを抽出して、東京や札幌の代表的地点（市庁舎等）の経度緯度を出力してしまう場合があります。
これを防ぐためには「 日本の郡・市区町村（2013年9月）」のような辞書を使用せず、
「日本の鉄道駅（2012年）」のような辞書のみを使用すればよいです。

コマンドラインからGeoNLPをコールする際、parseオプションのset-dicパラメータを利用すると使用する辞書を限定できます。

```
echo '{"method":"geonlp.parse","params":["JR本八幡駅",{"geojson":true, "set-dic":[31]}], "id":"a061000001VxwMmAAJ"}' | geonlp_api
```

上記例ですと、「"set-dic":[31]」としており、ID31の辞書のみを限定して利用しています。

なお肝心の辞書IDの特定方法ですが、公式サイトの辞書検索システムを参照すると判明します。
上記の例ですと「日本の鉄道駅（2012年）」は下記ページでID＝31だとわかります。
https://geonlp.ex.nii.ac.jp/dictionary/geonlp/japan_station