プログラマーのメモ書き

伊勢在住のプログラマーが気になることを気ままにメモったブログです

Overpass API を触ってみた

OpenStreetMap で作成した要素を調べてみたいと思いたって Overpass API (Overpass turbo) を使ってみましたので、その際のメモです。

Overpass API って?

OpenStreetMap のデータを抽出してくれる便利なサービスとのことです。XMLまたは Overpass QL という形式のクエリを投げて、結果を取得するそうです。詳しくは、下記URLを参照してください。

https://wiki.openstreetmap.org/wiki/JA:Overpass_API

実際に使えるサービスを提供してくれてるのがこちら。

https://overpass-api.de/

これを Web から使えるようにしてくれてるサービスが Overpass turbo になります。詳しい説明は下記を参照してください。

https://wiki.openstreetmap.org/wiki/JA:Overpass_turbo

Overpass turbo を触ってみる

まずは地図上で結果を確認できる Overpass turbo をさっそく触ってみました。

Overpass turbo のサイトにアクセスすると、下記のような入力画面と地図が表示されます(最初、地図の中心はイタリアかどこかですが、下記は伊勢を中心に表示するように移動後のものになります)。

f:id:junichim:20190222210522p:plain

で、このまま、画面上部の『実行』ボタンを押すと、

f:id:junichim:20190222205805p:plain

のように右画面の地図で表示している範囲内の水飲み場を示してくれます。

これは便利そうです。 ということで、左画面に書く Overpass QL について調べてみることにします。

Overpass QL クエリの書き方

中途半端な説明をするよりも、言語ガイドOverpass QL のページを見てもらうほうがいいのですが、 それだと、あまりに内容があれなんで、ちょっと戸惑った点とかとも合わせて、自分なりの理解の元、基本的な感じを書いておきます。間違ってる可能性も大いにありますので、変なところ見つけた方ぜひご指摘ください。

最初のサンプルとして、下記のものを考えます。

node
  (34.1316, 136.3006,34.6774, 136.9621)
  (user:"junichim");
out;

抽出結果はこちら。

f:id:junichim:20190222221514p:plain

伊勢市を中心に、私(ユーザー名 junichim)が編集したノードを表示しています。

クエリ(フィルタ)ステートメント

取得する要素を表す種類指定子(node, way ,relation (rel) , area) と、それらに続くフィルタ条件から成ります。

node で始まる行が、OpenStreetMapのノード(緯度経度を持つ地点)を取得するステートメントです。 続く (34.1316, 136.3006,34.6774, 136.9621) が対象となる領域を指定するフィルタ条件になります。ここでは、(南の緯度, 西の経度, 北の緯度, 東の経度)という形で指定します。 最後の (user:"junichim") がそのノードを作成したユーザーを指定するフィルタになります。

これで1つの命令(ステートメント)になり、最後はセミコロンになります。 表記は、1行で書いても、空白なしでもありでもいけるようです。

フィルタの種類はいろいろありますが、tag のキーバリューを指定する

node
  (34.1316, 136.3006,34.6774, 136.9621)
  ["name" ~ "商店$"];
out;

などもよく使うと思います。この場合は name キー が正規表現 ( ~ で指定) で『xx商店』で終わる ($) ものを指定しています。

アクション

上記の例の最後の out がアクションになります。 out アクションにより、クエリ結果を出力することになるようなので、ほぼ書くことになると思います。

out に引き続いて、出力の詳細さなどを記述できます。

out; // out body と同じ
out body; // デフォルト
out ids; // id のみ    
out skel; // body より簡潔
out meta; // 一番詳しい
結果の結合

複数のフィルタ結果を統合(和集合)を取りたいときに使います。

(
  node
    (34.1316, 136.3006,34.6774, 136.9621)
    ["name" ~ "商店$"];
  node
    (34.1316, 136.3006,34.6774, 136.9621)
    ["name" ~ "駅$"];
);
out;

()内に書かれた個々のフィルタを or でつなげたものになります。 この例だと、『xx商店』のノードと『〇〇駅』を取得します。

結果の絞り込み

実は一番動作がよくわからなかったのが、この結果の絞り込みです。

例えば、フィルタステートメントを連続して書くと、『伊勢』で始まり、『郵便局』で終わるものを指定できるような印象がありますが、

node
  (34.1316, 136.3006,34.6774, 136.9621)
  ["name" ~ "^伊勢"];
node
  (34.1316, 136.3006,34.6774, 136.9621)
  ["name" ~ "郵便局$"];
out;

のように書くと、

f:id:junichim:20190222223603p:plain

あれ?167か所も選ばれています。明らかに伊勢とは関係なさそうなところにもプロットがあります。

この例だと、一つのステートメントで書けば、両方の条件を and で指定できます。

node
  (34.1316, 136.3006,34.6774, 136.9621)
  ["name" ~ "^伊勢"]
  ["name" ~ "郵便局$"];
out;

結果も

f:id:junichim:20190222224805p:plain

となり、16か所、伊勢市近辺の郵便局だけになりました。

いやいや、言語ガイドにある例だと、指定したnameの近くにある別のnameを持つもの なんてのが堂々と載ってます。

node["name"="Bonn"];
node
  (around:1000)
  ["name"="Gielgen"];
out body;

引用元はこちら

これって、さっきだめだった奴と同じ書き方なのに and 条件になってんじゃないの?

・・・これ調べてみると、実に曲者でして、リファレンスをよく読むと、フィルタステートメントのいくつかは、前のステートメントの結果を受け取らない、とあります。

でも、aroundフィルタでは受け取るんですね。なので、上記の言語ガイドの例は間違いなく and 条件として動いています。

他に、結果セットを明示的に示すフィルタ(入力集合によるフィルタ) も受け取ることができます。

なので、この場合は、

node
  (34.1316, 136.3006,34.6774, 136.9621)
  ["name" ~ "^伊勢"];
node._
  (34.1316, 136.3006,34.6774, 136.9621)
  ["name" ~ "郵便局$"];
out;

のようにデフォルトの結果セット (_) を指定してやれば、問題なく and 条件を作れます。

f:id:junichim:20190222230807p:plain

これは、はまりそうです。

なお、結果セットについては、 Overpass QL のページの概要と集合の部分が詳しいです。

Overpass turbo 使用上の注意

Overpass turbo はクエリを書けば、すぐに地図上で結果を確認できるので非常に便利なのですが、取得したデータ量が過大だった場合、下記のような警告が表示されます。

f:id:junichim:20190222205618p:plain

あまりにデータ量が多い時は条件を考え直したほうがいいかもしれませんね。

Overpass API として使ってみる

最後に、Overpass turbo 経由ではなく、スクリプト等で Overpass API を使いたい場合も試しました。 この場合は、下記のエンドポイントに対して data= パラメータで Overpass QL を URL エンコードした文字列を渡せばOKです。

https://overpass-api.de/api/interpreter

POSTでも投げられるそうです。詳しくは下記のページをご覧ください。

http://www.overpass-api.de/command_line.html

ただ、取得する結果のフォーマットをjsonとかにしたいときは、

[out:json];
node
  (34.1316, 136.3006, 34.6774, 136.9621);
out meta;

のように、先頭に出力形式を指定する必要があります(デフォルトはXML)。

試しに、指定範囲内で過去に編集したユーザー一覧を作ってみようと思いやってみました。

指定範囲内のノードをすべて取得する Overpass QL を作成します。

[out:json];
node
  (34.1316, 136.3006, 34.6774, 136.9621);
out meta;

この時、出力は meta を指定して、作成ユーザーの情報が載るようにします。

これを先ほどのエンドポイントに向かって投げます。

curl https://overpass-api.de/api/interpreter?data=%5bout%3ajson%5d%3b%0d%0anode%0d%0a%20%20%2834%2e1316%2c%20136%2e3006%2c%2034%2e6774%2c%20136%2e9621%29%3b%0d%0aout%20meta%3b > test.json

こんな感じで、Overpass API でデータ(約180Mあったのでご注意を)を取得後、

jq ' .elements[] | select(.timestamp >= "2018-01-01T00:00:00Z") | .user ' test.json | sort | uniq

のようにして、 jqで処理すれば簡単に取れますね。

こっちはこっちでいろいろと使えそうです。

参考

qiita.com

medium.com

www.nofuture.tv

Twilio 電話を受けたら録音して、メールを飛ばす

以前、こちらの記事で、 Twilio を使って受付用留守番電話を作るというのを試しました。 せっかく受付用留守電を作っても、このままだといちいち Twilio のコンソールを見ないとわからないので現実的ではないです。

ということで、こちらの記事で試したFAX受信時のメール送信と同様に、留守電に録音されたらメールを飛ばすようにしたいと思います。

留守番電話が録音されたら、メールを飛ばす

基本的な考え方は、FAXを受信したらメールを飛ばすのと同じです。

メール送信用 Function の作成

まずは、留守電があったら呼び出される Functions を作成しておきます。 内容的には、FAX送信時と大して変わりませんが、メールの添付ファイルとして指定するのが、FAXの文面ではなく、録音した音声データになります。

サンプルコードは、こちらのGist, 下記修正前のリビジョン を参照してください。

メール送信用 Function の呼び出し設定

Functions を作成したら、次は、メール送信 Function の呼び出し設定を行います。

これは、TwiML の Record 動詞recordingStatusCallback 属性を使えば、録音が利用可能となったタイミングで指定されたURLを呼び出すことで実現できます。

<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say language="ja-JP">録音します。録音が終了したらシャープを押してください。</Say>
  <Record action="https://handler.twilio.com/twiml/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 
          recordingStatusCallback="https://xxxxxxxxxxxxxxxxxx.twil.io/function名" />
</Response>

こうすることで、録音が終わり、そのデータが利用可能になった時点で、recordingStatusCallback で指定されたURLが呼び出されます。 なお、以前の記事と同様に、actionで指定されたURLは録音後間違いなく電話が切れるように、hugup動詞のみのTwiMLを指定します。

テスト

さあ、試しましょう。 電話をかけて、適当に録音して、メールが来ているか確認します。

メールを確認すると、

f:id:junichim:20190221145307p:plain

問題なく受信できてました。と思いきや、電話をかけてきた相手の電話番号が表示されていません。

あれ?どうなっているんや?

ということで、リファレンスを調べてみると、どうも recordingStatusCallback で送る時は、発信元の電話番号とかの情報は送られていないようです。

これってどうしたらいいんだろうか?としばし考えたのですが、よくわからなかったので、Twilio のサポートに聞いてみると、CallSid を使って通話の情報を取得してください、とのことでした。 ま、考えてみりゃそういうことか。

発信元電話番号の取得

ということで、メール送信前に、発信元電話番号を取得して、それをメールのタイトルに入れるようにします。

まず、Function が呼ばれた時に、どういう情報が送られているのかを確認します。

debug:context: {
     "ACCOUNT_SID":"ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    ,"DOMAIN_NAME":"yyyyyyyy-yyyyyyyy-yyyyyyyyy.twil.io"
}
debug:event: {
     "RecordingSource":"RecordVerb"
    ,"RecordingSid":"RExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    ,"RecordingUrl":"https://api.twilio.com/2010-04-01/Accounts/ACxxxxxxxxxxxxxxxxxxxxxxx/Recordings/RExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    ,"RecordingStatus":"completed"
    ,"RecordingChannels":"1"
    ,"ErrorCode":"0"
    ,"CallSid":"CAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    ,"RecordingStartTime":"Sat, 16 Feb 2019 07:42:24 +0000"
    ,"AccountSid":"ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    ,"RecordingDuration":"3"
}

(頭の debug:event 等はconsole.logに埋め込んだ文字です) 引数の event の中身を確認すろと、先ほど見た、 recordingStatusCallback のリファレンスの通りになっています。 そこで、この CallSid が通話を識別するSidなので、これを使って Call Resource を取得して、表示させれば良さそうです。

具体的には、Fetch a Call Resource APIを呼びます。

このAPIのリファレンスのサンプル(例えば、node.js)を見るとわかるように、このAPIを呼ぶためには、Twilio client が必要なのですが、Twilio client を生成するには AUTH_TOKEN が必要です。 なお、先ほど Function が呼ばれた際のパラメータを見てもわかるように AUTH_TOKEN は返してくれません。

そこで、 Function の引数にAUTH_TOKEN を渡すようにするために、Functions の設定を変更して、 AUTH_TOKEN を渡すようにします。

f:id:junichim:20190221150452p:plain

設定変更後、渡されたパラメータを確認すると、

debug:context:{
     "AUTH_TOKEN":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    ,"ACCOUNT_SID":"ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    ,"DOMAIN_NAME":"xxxxxxxxxxxxxxxxxxxxx"
}

のように AUTH_TOKENが渡ってきているのが確認できました。

ただし、この設定は、そのアカウント(上記の場合はサブアカウント)で管理しているすべての Function について共通のようなので、その点は少し注意が必要かもしれません。

AUTH_TOKEN が取得できるようになったので、リファレンスと同様に電話番号を取得します。 あとは、これとメール送信を Promise でつなげれば終わりです。

最終的に こちらのGist のようにして、発信元の電話番号を取得します。

確認

もう一度試します。 電話をかけて、適当に録音して、メールを確認すると、

f:id:junichim:20190221151611p:plain

こんどは、電話をかけてきた相手の電話番号も無事表示されています。

さいごに

基本的な動作は FAX の時と同じでしたが、処理の呼び出し時に受け取れるパラメータの内容が微妙に異なっているのに少しはまりました。 でも、これで、留守電も使えるようになったので、とりあえずは満足です。

QNAP Backup Station などでの『最大ダウンロード速度』の設定について

ちょっと前に設定方法がわからなかったことがあったので、今更ですがメモっと来ます。

  • 機種:QNAP TS-231P
  • QTS : 正確には覚えてませんが、たぶん 4.3.5.0728 だったはず

QNAP の Backup Station の設定で Rsync サーバーのところに 『最大ダウンロード速度を有効にする』という項目があります。

QNAP Turbo NAS Software User Manual

いまのバックアップ体制はLAN内のみなので、速度制限なんて不要だと思い、マニュアル通り0を入れたら、エラーになります。

f:id:junichim:20181218093833p:plain

不審に思って、サポートに問い合わせてみたら、

  • チェックを入れるとダウンロード速度制限が有効になる
  • ダウンロード速度制限を有効にしたくなかったら、チェックを入れるな

とのことでした。

そう言われてみれば画面の表記も機能のオン・オフのように受け取れますね。でも、ここはちょっと、マニュアル表記も見直してほしいところです(いずれにせよ、0は入力できないので)。

ともあれ、WAN経由でバックアップするとかじゃない限り、チェックなしでよさそうです。