加古川市オープンデータ API 利用チュートリアル

## はじめに

加古川市オープンデータ API は、いわゆる REST API になっていて、追加で OAuth2 を使います。ちょうど facebook API や twitter API などと同じような構成です。利用する際の処理の流れは大きく分けて、OAuth2 の部分と、REST API の部分の二つにわかれます。

### OAuth2
OAuth2 は少し難しく見えます（そして実際ややこしいです）が、OAuth2 の処理が終わった後、最終的には「アクセストークン」が発行されます。

なにはともあれ OAuth2 の処理を行うには、決められた手順を踏まないとできません。OAuth2 で使用する様々な文字列は、「利用申請兼通知書」で申請すると得られます。「利用申請兼通知書」ですが、「アカウントの登録」と「アプリの登録」の二つのパートがあります。初回に「アカウント発行」のみ行い、後で「アプリの登録」を行うということもできるはずです。とりあえずデータを閲覧したいというだけであれば、「アカウントの発行」のみで大丈夫です。どうやればいいのか十分わかっている場合は、両方同時に申請してもよいでしょう。
https://opendata-api-kakogawa.jp/odp/contact/

「利用申請兼通知書」のExcelシートの送付ですが、これはウェブのフォームで送付することができます（もちろん書面で送付することもできます）。必要な項目を記入して、加古川市の「スマイルメール」の送付フォームで添付ファイルとして送信してください。スマイルメールの件名は「加古川市オープンデータAPI 利用申請書兼通知書」と記入するとよいでしょう。
https://www.smilemail-kakogawa.jp/form/index.php?section_cd=1006

OAuth2 は様々な用途に汎用的に使えるように整備されてきたため、登場人物がたくさん出てきます。Authorization Code Grant が推奨される方法とされていますが、それが唯一の正解というわけではありません。自分が思い描いている形がどこに着地するかは、一度考えてみるとよいでしょう。

OAuth2 のガイドとしては「データ利活用基盤サービス（FIWARE）アプリケーション開発ガイド 認証認可編」が参考になります（注1）。チュートリアルでは「アクセストークン」が得られた後の処理方法について説明します。


## データの取得

`curl` でデータを取得してみます。ここでは「アクセストークン」を仮に `16b1f828-cbb2-4bde-a066-946594e01dc5` としています。実際に得られたものに置き換えて試してみてください。

```bash
curl -v "https://api.opendata-api-kakogawa.jp/orion/v1.0/queryContext" \
  -X POST \
  --header "Authorization: Bearer 16b1f828-cbb2-4bde-a066-946594e01dc5" \
  --header "Content-type: application/json" \
  --data '{"entities":[{"isPattern":"true","id":".*"}]}'
```

加古川市オープンデータ API で採用されている fiware-orion の NGSI v1 は少し変わった流儀をしていて、問い合わせのクエリで条件を request body で POST 送信します（v2 では GET に改められています）（注2）。

上の例は最小の例です。条件を追加すると絞り込みができます。`isPattern` が true の場合は、`id` に正規表現を指定します。fiware orion では、この正規表現は POSIX 正規表現で、glibc の拡張正規表現が使えます。つまり `.*` で全てにマッチします。

`jq` コマンドを通して整形すると結果が見やすくなります。ID だけ並べてみましょう。

```bash
$ curl -s https://api.opendata-api-kakogawa.jp/orion/v1.0/queryContext \
  -X POST \
  --header "Authorization: Bearer 16b1f828-cbb2-4bde-a066-946594e01dc5" \
  --header "Content-type: application/json" \
  --data '{"entities":[{"isPattern":"true","id":".*"}]}' \
 | jq .contextResponses[].contextElement.id
"jp.opendata-api-kakogawa.AEDFacilities.Shikanren.1"
"jp.opendata-api-kakogawa.Transportaion.KakobusHigashikakogawa.1"
"jp.dev-necjfiware.PublicFacilities.Churinjo.1"
"jp.dev-necjfiware.Tourism.KanbeYukari.1"
"jp.dev-necjfiware.ChildcareCenter.1"
"jp.dev-necjfiware.BarrierFreeFacilities.1"
"jp.opendata-api-kakogawa.DisasterManagementFacilities.KinkyuHinanbashoHinanjo.1"
"jp.dev-necjfiware.CareOffices.Fukugou.1"
"jp.opendata-api-kakogawa.CareOffices.Fukugou.1"
"jp.opendata-api-kakogawa.ChildcareCenter.1"
"jp.opendata-api-kakogawa.BarrierFreeFacilities.1"
"jp.opendata-api-kakogawa.PublicFacilities.Churinjo.1"
"jp.opendata-api-kakogawa.Tourism.KanbeYukari.1"
"jp.opendata-api-kakogawa.FloodHazardMap.ShinsuiJisseki.1"
"jp.opendata-api-kakogawa.LandslideHazardMap.KyukeisyaHigaiSoutei.1"
"jp.opendata-api-kakogawa.LandslideAreaHazardMap.Sanpuku.1"
"jp.opendata-api-kakogawa.PondHazardMap.Tameike.1"
"jp.opendata-api-kakogawa.DisasterManagementFacilities.Tsunami.1"
"jp.opendata-api-kakogawa.DisasterManagementFacilities.Hinanjo.1"
"jp.opendata-api-kakogawa.DisasterManagementFacilities.KinkyuHinanbasho.1"
```

意外と少ないですね。実は一度に取得できる件数はデフォルトで 20 件に絞られています。query string に `detail=on` を追加すると全体の件数も分かるようです。

```bash
$ curl -s https://api.opendata-api-kakogawa.jp/orion/v1.0/queryContext \
  -X POST \
  --header "Authorization: Bearer 16b1f828-cbb2-4bde-a066-946594e01dc5" \
  --header "Content-type: application/json" \
  --data '{"entities":[{"isPattern":"true","id":".*"}]}' \
 | jq .errorCode.details
"Count: 1649"
```

このような処理は一般的に「ページネーション」と呼ばれます。fiware-orion のドキュメントもありますので、適宜ページを繰って読み出してください（注３）。
https://fiware-orion.readthedocs.io/en/master/user/pagination/index.html#pagination-using-ngsiv1

## データの中身

NGSI ではデータの形が決まっていて、「あるデータ」は「ID で識別され、「属性を複数持つ」という形です。試しに 1 つだけ取り出してみましょう（説明のため内容を若干編集しています）。

```bash
$ curl -s "https://api.opendata-api-kakogawa.jp/orion/v1.0/queryContext?limit=1" \
  -X POST \
  --header "Authorization: Bearer 16b1f828-cbb2-4bde-a066-946594e01dc5" \
  --header "Content-type: application/json" \
  --data '{"entities":[{"isPattern":"true","id":".*"}]}' 
 | jq
{
  "contextResponses": [
    {
      "contextElement": {
        "type": "AEDFacilities",
        "isPattern": "false",
        "id": "jp.opendata-api-kakogawa.AEDFacilities.Shikanren.1",
        "attributes": [
          {
            "name": "住所",
            "type": "xsd:string",
            "value": "加古川町本町194"
          },
          {
            "name": "名称",
            "type": "xsd:string",
            "value": "中央消防署"
          }
        ]
      },
      "statusCode": {
        "code": "200",
        "reasonPhrase": "OK"
      }
    }
  ]
}
```

上記の例では `jp.opendata-api-kakogawa.AEDFacilities.Shikanren.1` が ID で、「住所」と「名称」という属性を持っています。

加古川オープンデータのサイトには複数のデータセットが登録されていますが、これらは同列に混ぜて取り出すことができるようになっています。取得の際に条件を追加して、例えば `{"type":"AEDFacilities"}` を追加するときれいに 1 つのデータセットを抜き出せます。

https://opendata-api-kakogawa.jp/ckan/dataset?res_format=ngsi10


### 用語 : Broker と context

fiware orion は data broker つまり、データを右から左に伝搬させる通知機能を持っています。一般的に言われる MQTT, AMQP や kafka で登場する broker と同じものです。NGSI では、通知を受け取るには通知を受け取る HTTP エンドポイントを subscribe します。NGSI-10 の `notifyContext` を使います。fiware orion はデータ登録先の機能を持ちますし、データの更新をトリガーとして通知を伝搬するブローカーとしても動作します。

これを試すには通知を受け取るサーバが必要になります。GCP の無料インスタンスなどを使って実験するとよいでしょう。次に subscribe と unsubscribe の例を示します。

```bash
$ curl "https://api.opendata-api-kakogawa.jp/orion/v1.0/subscribeContext" \
  -X POST \
  --header "Authorization: Bearer 16b1f828-cbb2-4bde-a066-946594e01dc5" \
  --header "Content-type: application/json" \
  --data '{"entities":[{"isPattern":"true","id":".*"}],
    "reference":"http://example.com/v1/notify"}'
 | jq
{
  "subscribeResponse": {
    "subscriptionId": "5b35ee3b0c736c3c77d0df75",
    "duration": "PT24H"
  }
}
```

```bash
$ curl "https://api.opendata-api-kakogawa.jp/orion/v1.0/unsubscribeContext" \
  -X POST \
  --header "Authorization: Bearer 16b1f828-cbb2-4bde-a066-946594e01dc5" \
  --header "Content-type: application/json" \
  --data '{"subscriptionId":"5b35ee3b0c736c3c77d0df75"}'
 | jq
{
  "subscriptionId": "5b35ee3b0c736c3c77d0df75",
  "statusCode": {
    "code": "200",
    "reasonPhrase": "OK"
  }
}
```

上記の例では、有効期間を省略しました。デフォルトでは `PT24H` の24時間になるようです。期限が切れる前に定期的にアップデートしましょう。この期間が都合が悪い時は、subscribe 時に `duration` パラメータを指定しましょう。ISO8601 の表記方法なので、例えば P1M は `P` = period, `1M` = 1 month となります（注3）。

fiware orion へのデータ更新は、単なる登録データの更新のように見えます。fiware orion から離れて、もう少し視点を高く持って NGSI の概念をもう少し広く見ると、データの登録をスイッチのオンオフのように考えることもできます。テレビを指し示すデータに "ON" を登録すると、実際のスイッチにも伝わってテレビが "ON" になる、といった具合です。このため NGSI の用語では、このデータのことを単なるデータとは呼ばずに、意識的に「コンテキスト」と呼んでいます。「コンテキストに値をセットする」と言えば、理解がしやすくなります。対比のためにいくつか例を挙げると、「（コンピュータ用語で）レジスタに値をセットする」だったり「（ネットワーク機器用語で）snmpで値をセットする」だったりします。


## 認証
実は OAuth2 自体は認証の仕組みではありません。一般的に言われている認証は、内部が複数の機能に分割されて Authentication, Authorization, Accounting の 3 つに分割されます。Authentication は「誰であるか」を特定する認証、Authorization は「許可を与える」承認、Accounting は「利用状態の管理」です。OAuth2 自体はこのうちの Authorization の枠組みです。

加古川オープンデータの申請書では AAA のすべてが入っていますが、これをそのままエンドユーザに提供する必要はありません。アプリケーションの用途に応じて、追加で AAA の仕組みを設計して使うとよいでしょう。


## 環境構築

Windows 10 では次の手順が簡単です

- Windows Store から Ubuntu をインストール
- apt-get update
- apt-get install -y curl jq



## 注釈

### 注1
加古川市で公開している開発ガイドはとても役に立ちます。 http://www.city.kakogawa.lg.jp/soshikikarasagasu/kikakubu/jouhouseisakuka/opendata/1528499081092.html

OAuth2 の原典は RFC 6749 を参照してください。https://tools.ietf.org/html/rfc6749

### 注2
残念ながら十分に記述された仕様は存在しませんが、次のドキュメントが手掛かりになります。

- 「データ利活用基盤サービス（FIWARE）アプリケーション開発ガイド データ収集蓄積編」
    - 第6章 Context Consumer 開発者向けガイド
    - 7.2.1 NGSI-10 インタフェース
- OMA "NGSI Context Management"
    - OMA-TS=NGSI_Context_Management-V1_0-20120529-A
    - 5.4.1
- https://fiware-orion.readthedocs.io/en/master/user/walkthrough_apiv1/index.html#query-context-operation

### 注3
- https://ja.wikipedia.org/wiki/ISO_8601#%E7%B6%99%E7%B6%9A%E6%99%82%E9%96%93
- https://www.iso.org/iso-8601-date-and-time-format.html
- https://www.iso.org/standard/40874.html

# 追記
6/30 土曜日に申請書送付して、7/4 水曜日にアカウント発行されました。おおよその目安として。添付ファイルをパスワード付き zip ファイルにくるんでメール、パスワードを別途送信、というフローを使っている様子なので、備考欄にメールアドレス以外の連絡手段を書いておくとよいのかも……？