市区町村名を県名に変換する辞書を PowerShell でつくる

# tl;dr

PowerShell と Heartrails Geo API を使って、市の名前をキー、県の名前を値に取る JSON を作ります

```powershell
# まず 県→市の辞書を作る

$PrefRaw = wget "https://geoapi.heartrails.com/api/json?method=getPrefectures"
$PrefData = $PrefRaw.Content | ConvertFrom-Json
$Prefs = $PrefData.response.prefecture

$out = [System.Collections.Generic.Dictionary[String, PSObject]]::new()
foreach ($Pref in $Prefs) { # 各県について
  $CityRaw = wget "https://geoapi.heartrails.com/api/json?method=getCities&prefecture=$Pref" # 市を取ってきて
  $Data = $CityRaw.Content | ConvertFrom-Json # JSON→Dictionaryにして
  $out.Add($Pref, $Data.response.location) # 追加する
  echo $Data.response.location
  Start-Sleep -m 1000 # 負荷防止
}
# 県→市
echo $out | ConvertTo-Json | Out-File .\out.json

# 市→県の構造に変換
$rawJson = Get-Content .\out.json  # 県→市
$data = $rawJson | ConvertFrom-Json
$cityToPref = [System.Collections.Generic.Dictionary[String, PSObject]]::new() # 辞書を作って
# 件名を取得, Name をキーにしてぜんぶのメンバを取る
$Prefs = $data | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name

foreach ($Pref in $Prefs) {
  $Cities = $data.$Pref.value # ある県から
  foreach ($City in $Cities) { # すべての市に対し
    $cityToPref.Add($City.city, $Pref) # 市→県の辞書に追加
  }
}

$JData = $cityToPref | ConvertTo-Json  # JSON にして
echo $JData | Out-File .\cityToPref.json # 書き出す
```

上を実行すると得られる `cityToPref.json` は以下のようになります。

```json
{
    "津市":  "三重県",
    "四日市市":  "三重県",
    "伊勢市":  "三重県",
    "松阪市":  "三重県",
    ...
}
```


# イントロ

あるデータ分析の都合上、"市"の名前からそれが属する"県"の名前を取得する API が欲しかったので、そのベースとなる辞書を作ってみました。

https://geoapi.heartrails.com/

の API から、日本に存在する県名、県に存在する市町村のリストなどいろんなのが取得できます。

## もうちょっと詳しいモチベーション

> 総務省統計局, 家計調査（二人以上の世帯）　品目別都道府県庁所在市及び政令指定都市（※）ランキング
https://www.stat.go.jp/data/kakei/5.htm

を分析して消費額を地図にしようと思ったとき、総務省統計局のデータは「政令指定都市のデータ」が記載され、政令指定都市名(e.g. "仙台市", "堺市")がキーとなっていました。
さて、このデータを Microsoft Excel でWebマップを使って可視化するとなるとWebマップに与えるキーを県名にする必要があったので、市->県に逆引きするデータベースが欲しくなりました。

Excel は JSON をサポートしているので、
``{市区町村名: "県名"}``
の構造のデータなら、あとは Power Query や VLOOKUP 関数を組み合わせると簡易的に市->県への変換ができます。

# 解説、いきさつ

## 県→市 に変換

さて、PowerShellを使って

```powershell
$PrefRaw = wget "<https://geoapi.heartrails.com/api/json?method=getPrefectures>"

```

とすると、以下の出力が得られます。

```plaintext
StatusCode        : 200
StatusDescription : OK
Content           : {"response":{"prefecture":["\\u5317\\u6d77\\u9053","\\u9752\\u68ee\\u770c","\\u5ca9\\u624b\\u770c","\\u5bae\\
                    u57ce\\u770c","\\u79cb\\u7530\\u770c","\\u5c71\\u5f62\\u770c","\\u798f\\u5cf6\\u770c","\\u8328\\u57ce\\u770c","
                    \\u68...
RawContent        : HTTP/1.1 200 OK
...以下省略

```

`$PrefRaw` はいわゆる**オブジェクト**で

```powershell
echo $PrefRaw.Content

```

のように プロパティ `Content` を読むと

```json
{"response":{"prefecture":["\\u5317\\u6d77\\u9053","\\u9752\\u68ee\\u770c", ...]}}

```

のような出力が得られます。

JSON の `(root)."response"."prefecture"` が、県名のリストを含んでいるので、まずJSON(のテキスト)をオブジェクトに変換しましょう。PowerShell だと `ConvertFrom-Json` コマンドを使えば一発で変換できます。
(今回の場合、変換先がオブジェクトになりました)

```powershell
$Resp = $PrefRaw.Content | ConvertFrom-Json
$Prefs = $Resp.response.prefecture

```

ここで

```powershell
echo $Prefs
```

とすると

```
北海道
青森県
岩手県
宮城県
秋田県
山形県
福島県
茨城県
栃木県
...以下略

```

が得られます(47都道府県あることを `$Prefs.length`で確認するとよいです)

今回使ったAPIは 県->市 の変換ができるので、とりあえず正直に 県->市 に変換できる辞書を作ってみます。

面倒なことは **辞書型** (Dictionary) でなんとかします

```powershell
$out = [System.Collections.Generic.Dictionary[String, PSObject]]::new()
foreach ($Pref in $Prefs) {
  # API から市データを取得
  $CityRaw = wget "<https://geoapi.heartrails.com/api/json?method=getCities&prefecture=$Pref>"
  $Data = $CityRaw.Content | ConvertFrom-Json
  $out.Add($Pref, $Data.response.location)
  #echo $Data.response.location
  # APIへの過剰な負荷を防止するため
  Start-Sleep -m 1000
}

```

市データは、たとえば

```powershell
wget "<https://geoapi.heartrails.com/api/json?method=getCities&prefecture=秋田県>"

```

とすれば

```
StatusCode        : 200
StatusDescription : OK
Content           : {"response":{"location":[{"city":"\\u79cb\\u7530\\u5e02","city_kana":"\\u3042\\u304d\\u305f\\u3057"},{"ci
                    ty":"\\u80fd\\u4ee3\\u5e02","city_kana":"\\u306e\\u3057\\u308d\\u3057"},{"city":"\\u6a2a\\u624b\\u5e02","cit
                    y_ka...
RawContent        : HTTP/1.1 200 OK

```

のように、JSONの `(root)."response"."location"` に、

```json
{
  "city": "string"
  "city_kana": "string"
}

```

で表されるオブジェクトが配列になって返ります。
そこで、辞書型配列に

キー: `$Pref` (県名)
値: `$Data.response.location` (市、オブジェクト)

のペアを格納しています。

最後に、`$out` の内容を JSON に書き出しましょう。
ファイル `.\\out.json` に書き出すなら、

```powershell
echo $out | ConvertTo-Json | Out-File .\\out.json

```

でOKです。

```json
{
  "北海道": {
    "value": [
      {
        "city": "札幌市中央区",
        "city_kana": "さっぽろしちゅうおうく"
      },
      {
        "city": "札幌市北区",
        "city_kana": "さっぽろしきたく"
      },
      ...
    ],
    "Count": 188
  },
  "青森県": {
    "value": [
      {
        "city": "青森市",
        "city_kana": "あおもりし"
      },
      {
        "city": "弘前市",
        "city_kana": "ひろさきし"
      },
      ...
    ]
  }
  ...
}
```

"Count" という不要なプロパティが出てきましたが、加工時に破棄するので、ひとまずはこのままで続行します。

# 市→県 に変換

続いて、これを 市->県名 の形式に書き換えます。いったん `.\\Out.json` に書き出したものを再び読み込んで作業します。

```powershell
$rawJson = Get-Content .\\Out.json
$data = $rawJson | ConvertFrom-Json

$cityToPref = [System.Collections.Generic.Dictionary[String, PSObject]]::new()
```

県名は `$data` (辞書型配列) のキーにあたりますが、それを取得するのがちょっと難しいです (C# みたいに `$data.Keys`  で取得できれば便利なのですが)
`$data` のプロパティを全て読んで、その中の `Name` と名のつくプロパティだけをすべて抽出します。

```powershell
$Prefs = $data | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name
```

こうしたら、後は `$cityToPref` (辞書型配列) を、市区町村名をキー、県名を値にして作成しま
す。

```powershell
foreach ($Pref in $Prefs) {
  $Cities = $data.$Pref.value
  foreach ($City in $Cities) {
    $cityToPref.Add($City.city, $Pref)
  }
}
```

最後に JSON に変換してファイルに書き出します。

```powershell
$JData = $cityToPref | ConvertTo-Json  # JSON にして
echo $JData | Out-File .\cityToPref.json # 書き出す
```

あとはこれを Excel の [データ] -> データの取得 -> [ファイルから] -> [JSON] とたどって、 Power Query 経由で落とし込めば目標を達成できそうです。
無事に市区町村名->県名に変換する辞書ができました！

```json
{
    "津市":  "三重県",
    "四日市市":  "三重県",
    "伊勢市":  "三重県",
    "松阪市":  "三重県",
    ...
}
```

# あとがきと注意

ちょっとした問題として、日本には市区町村名がまったく同じ場所が存在します。
この場合、辞書型配列のキーはユニーク(他とかぶらない)である必要があるので、既に存在するキーを追加しようとすると弾かれます。
この場合、市->県の逆引きは2つ以上の検索結果を持ちうることになるため、なにかと判定が難しかったりします…。

今回は政令指定都市->県名 に変換するだけだったので影響は小さいなと判断し無視しましたが、すべての市区町村に対応するにはこの構造は適していません。