[Python] pygeonlpでgeocoding


自前でジオコーディングをする必要が出てきたときにpygeonlpというライブラリが便利です。
自然言語から地理情報を認識できるのでドキュメントからの地理情報抽出みたいにも使えます。


https://github.com/geonlp-platform/pygeonlp


インストール
---
Docker環境で構築します。
Dockerfileの例は[こちら](https://github.com/geonlp-platform/pygeonlp/blob/main/docs/source/install_docker.rst)を参考にしたほうが間違いないかもです。

```dockerfile
FROM python:3.12-slim-bookworm

RUN apt-get update && apt-get install -y \
    libmecab-dev  \
    mecab-ipadic-utf8 \
    libboost-all-dev \
    libsqlite3-dev \
    g++

RUN pip install pygeonlp
```


動かしてみる
---

```sh
$ docker build -t pygeonlp_image:latest .
$ docker run --name pygeonlp --rm -it pygeonlp_image:latest bash
```

```sh
root@6237d3352127:/# pygeonlp geoparse
/root/geonlp/db に基本辞書セットをインストールしますか? (y/n):y
完了しました。
渋谷じゃなくて新宿に行こう。
渋谷    名詞,固有名詞,地名語,0ugqSw:渋谷駅,*,,渋谷,,    鉄道施設/鉄道駅,0ugqSw,渋谷駅,139.699863,35.65855066666666
じゃ    助詞,副助詞,*,*,*,*,じゃ,ジャ,ジャ
なく    助動詞,*,*,*,連用テ接続,特殊・ナイ,ない,ナク,ナク
て      助詞,接続助詞,*,*,*,*,て,テ,テ
新宿    名詞,固有名詞,地名語,SGGWYh:新宿駅,*,,新宿,,    鉄道施設/鉄道駅,SGGWYh,新宿駅,139.69921,35.689985
に      助詞,格助詞,一般,*,*,*,に,ニ,ニ
行こ    動詞,自立,*,*,未然ウ接続,五段・カ行促音便,行く,イコ,イコ
う      助動詞,*,*,*,基本形,不変化型,う,ウ,ウ
。      記号,句点,*,*,*,*,。,。,。
EOS
```

入力した文字列「渋谷じゃなくて新宿に行こう。」から「渋谷」と「新宿」の駅が認識されていることが確認できました。

ユーザー辞書の追加
---

pygeonlpはMeCabの解析結果に一部依存しているようです。そのため分かち書きが失敗していると地名表現の認識と上手くいかないようです。

```sh
$ echo '新高円寺' | pygeonlp geoparse
新      接頭詞,名詞接続,*,*,*,*,新,シン,シン
高円寺  名詞,固有名詞,地名語,WyG8eJ:高円寺駅,*,,高円寺,,        鉄道施設/鉄道駅,WyG8eJ,高円寺駅,139.6497175,35.705325
```
新高円寺駅が「新」と「高円寺」に分かれてしまっています。

sqlite3に格納された地名辞書には新高円寺の情報は入っているため、分かち書きが起因していそうです。
```sh
$ sqlite3 root/geonlp/db/geodic.sq3 
sqlite> select * from geoword where json like '%新高円寺%';
748sY7|3|Ekl8j3|{"body":"新高円寺","dictionary_id":3,"entry_id":"Ekl8j3","geonlp_id":"748sY7","hypernym":["東京地下鉄","4号線丸ノ内線"],"institution_type":"民営鉄道","latitude":"35.69786","longitude":"139.64851","ne_class":"鉄道施設\/鉄道駅","railway_class":"普通鉄道","suffix":["駅",""]}
```

そのため下記のようにユーザー辞書を追加してやります。
```csv:userdic.csv
新高円寺,1288,1288,3000,名詞,固有名詞,一般,*,*,新高円寺,*,*
```

辞書をコンパイルします
辞書DBが存在するディレクトリにmecabusr.dicとしておくと認識されるようです。
```sh
# $ apt-get install -y mecab
$ /usr/lib/mecab/mecab-dict-index -d /usr/share/mecab/dic/ipadic -f utf-8 -t utf-8 -u /root/geonlp/db/mecabusr.dic userdic.csv
```

```sh
$ echo '新高円寺' | pygeonlp geoparse
新高円寺        名詞,固有名詞,一般,*,*,名詞-固有名詞-地名語,新高円寺,,
EOS
```
認識されました。ただなぜか上手く行っておらず、「新高円寺駅」で再度試すと
```sh
$ echo '新高円寺駅' | pygeonlp geoparse
新高円寺駅      名詞,固有名詞,地名語,748sY7:新高円寺駅,*,*,新高円寺駅,, 鉄道施設/鉄道駅,748sY7,新高円寺駅,139.64851,35.69786
EOS
```
うまく認識されました！

カスタム属性の追加
---
地名や、その地名に紐づく情報を追加したい場合があります。
pygeonlpの駅データには緯度経度や路線情報が入っていますが、例えば別称や高度情報を追加したいとします。

そのときはpygeonlpのbasedataとなるcsvを編集することで実現できます。
例として`custom_column`を追加して、0~9の乱数を割り当てています。

```csv:ksj-station-N02.csv
geolod_id,entry_id,body,suffix,hypernym,ne_class,latitude,longitude,railway_class,institution_type,custom_column
xw0uiX,CTbeYV,二月田,駅/,九州旅客鉄道/指宿枕崎線,鉄道施設/鉄道駅,31.25432,130.6301,普通鉄道JR,JR在来線,0
AIgI8G,CbsmTH,古島,駅/,沖縄都市モノレール/沖縄都市モノレール線,鉄道施設/鉄道駅,26.23064,127.70294,跨座式モノレール,第三セクター,9
```

```sh
# $ rm -rf geonlp/db
$ python -m pygeonlp.api setup /usr/local/pygeonlp_basedata/
$ pygeonlp search '三鷹' | jq
```

```json
{
  "TYq6IZ": {
    "": "7530",
    "body": "三鷹",
    "custom_column": "4",
    "dictionary_id": 3,
    "entry_id": "8xkwGr",
    "geolod_id": "TYq6IZ",
    "hypernym": [
      "東日本旅客鉄道",
      "中央線"
    ],
    "institution_type": "JR在来線",
    "latitude": "35.702694",
    "longitude": "139.560708",
    "ne_class": "鉄道施設/鉄道駅",
    "railway_class": "普通鉄道JR",
    "suffix": [
      "駅",
      ""
    ],
    "dictionary_identifier": "geonlp:ksj-station-N02"
  }
}
```

custom_columnが追加されています

pythonから扱う場合は
```python
import pygeonlp.api as api
api.init()

pprint.pprint(api.analyze('三鷹')[0][1].as_dict())

# output
{'geometry': {'coordinates': [139.560708, 35.702694], 'type': 'Point'},
 'morphemes': {'conjugated_form': '名詞-固有名詞-一般',
               'conjugation_type': '*',
               'original_form': '三鷹',
               'pos': '名詞',
               'prononciation': '',
               'subclass1': '固有名詞',
               'subclass2': '地名語',
               'subclass3': 'TYq6IZ:三鷹駅',
               'surface': '三鷹',
               'yomi': ''},
 'node_type': 'GEOWORD',
 'prop': {'': '7530',
          'body': '三鷹',
          'custom_column': '4',
          'dictionary_id': 3,
          'dictionary_identifier': 'geonlp:ksj-station-N02',
          'entry_id': '8xkwGr',
          'geolod_id': 'TYq6IZ',
          'hypernym': ['東日本旅客鉄道', '中央線'],
          'institution_type': 'JR在来線',
          'latitude': '35.702694',
          'longitude': '139.560708',
          'ne_class': '鉄道施設/鉄道駅',
          'railway_class': '普通鉄道JR',
          'suffix': ['駅', '']},
 'surface': '三鷹'}
```

その他機能
---
jageocoderライブラリと連携することで日本の住所情報を利用したり逆ジオコーディングも可能です。2024年現在住所データベースのメンテナンスは適切にされているように見えます。

https://t-sagara.github.io/jageocoder/

他にも時空間フィルターやスコアリングのカスタマイズ機能も存在しており、自前でカスタマイズが必要なユースケースでは利用がおすすめです。