Node-REDで地図上に可視化する!(node-red-contrib-web-worldmap)

先日、Node-RED UGのイベントが名古屋にて行われ、それにスピーカーとして出席しました。

node-red.connpass.com

イベント名は「Node-RED Flow 大喜利 in 名古屋」でしたが、内容としては1つのテーマからそれに関連するNode-REDのフローを作成して、そのフローについてフリートークをしていくというものです。今回は開催地が愛知県ということもあり、交通・地図というような内容で行われました。

自分は、Node-REDのイベントでLTはたまにしていたのですが、こういうタイプのものは初めてだったので、かなり緊張していました。というのも自分が作ったのはかなり初歩的なものだったという理由もあります。

Node-REDで地図を使用するには?

今回のお題は地図と交通データだったので、Node-REDで地図を表示したり、それにピンを立てる方法などを調べてみました。

Node-REDで地図を扱うには拡張ノードであるnode-red-contrib-web-worldmapを使用することが一般的のようです。また、今回はオープンデータであるGTFSのデータ群にあるバス停データを使用しています。当然ながらバス停位置データは経度・緯度データを含んだCSVデータになっているので、CSVデータを処理するCSVノード(デフォルト)を使用しています。

自分は愛知県(全国?)の中でも一番世帯年齢が若いといわれる長久手市名古屋市の東側に隣接)のバス停データを使用し、地図(OpenStreetMap:以降OSM)にピンを立てるフローの作成を行っています。選んだ理由としては、地方は自家用車文化なのでバス停がどのように点在しているのかというのに興味があったからです。多分あんまりないんじゃないかなと…予想。

OSMの詳細に関しては以下を参考にしてください。

OSMのサイト

openstreetmap.jp

日本の地図

名古屋市近郊の地図

地図表示を行う拡張ノードを導入する

今回導入する拡張ノードnode-red-contrib-web-worldmapは地図処理を行うメジャーな拡張ノードです。

flows.nodered.org

では、こちらをNode-REDに導入していきます。

導入方法

拡張ノードの追加は簡単にしておきます。

【三】をクリックしてメニューから【パレット管理】を選択します。

設定ダイアログの【ノード追加】タブをクリックします。

検索ボックスにworldmapを入力し、node-red-contrib-web-worldmapの【ノードを追加】ボタンをクリックします。

これで拡張ノードのインストールは完了です。

導入後の確認

導入後にパレットには以下の4つのノードが追加されます。

地図を表示して、ピンを打っていくという内容であればworldmapノード使用のみで問題ありません。では早速使用してみます。

地図を表示する

地図を表示するだけであれば、worldmapノードをフローエディタに配置するだけです。フローエディタに配置後、【デプロイ】ボタンをクリックします。デプロイが成功したら、 以下のアドレスにブラウザからアクセスを行います。アクセス先はNode-REDが動作しているURLに/worldmapと追加したリンクとなります。ホスト名を使用せず直接IPアドレスに変更してアクセスを行っても大丈夫です。(今回は/worldmapとなっていますが、worldmapノードにURLを指定するプロパティ値があるので変更も可能です。)

http://localhost:1880/worldmap

アクセスするとブラウザ上に地図(OSM)が表示されます。

表示パラメータがデフォルト値になっているので、あとは表示したい地図形式、場所の緯度・経度、縮尺を指定していくことになります。

地図の中心の緯度経度を設定する

まずは表示される地図の中心を設定してみます。地図の世界では緯度はLatitude、経度はlongitudeと表記されますが、こちらを使用して中心座標を設定します。worldmapノードのプロパティを開き、中心となる緯度・経度の数値を入力していきます。またここで、表示のベースとなるマップの指定も行います。デフォルトではグレー階調のマップでちょっと見栄えがしないので、カラーに変更します。

緯度・経度に関しては都度調べることになるので、例えばある都市の中心にしたいときには以下のサイトで調べるとよいでしょう。

www.geocoding.jp

今回は長久手市と入力してみます。

これで、特定した場所の緯度経度の数値がわかるので、この値をプロパティに入力していきます。表示のベース地図もプルダウンから変更します。

  • Latitude 35.184
  • longitude 137.0487
  • Base map OpenStreetMap

入力後、デプロイして再度先ほどのURLを開く(リロード可)と以下のようなマップが表示されます。

http://localhost:1880/worldmap

でも、まだ表示の縮尺があっていないので中心合わせただけです。

地図の縮尺を設定する

続いては地図の縮尺を設定していきます。地図の縮尺の設定はノードのプロパティのzoomに該当します。この数値は地図に依存しますが、OSMでは1~20まで設定を行うことができます。

今回はこの値を14に設定し、

【デプロイ】ボタンをクリックして、改めて先ほどのリンクへアクセスを行います。

http://localhost:1880/worldmap

設定した位置、縮尺で地図を表示することができるようになりました。これで基本的な動作の確認ができました。

地図にピンを立てる方法

あとは、表示した地図にピンを立てることができれば、ほとんど終わったも同然です。

地図にピンと立てる場合にはworldmapノードに特定の形式のJSONデータを持つmsg.payloadデータを送り込めば処理が行われます。 ピンを立てるだけのもっとも簡単なデータは以下のような形式のものになります。

msg.payload = { "name":"Jason", "lat":51.05, "lon":-1.35 }

今回はinjectノードfunctionノードを追加してフローを作成しています。

追加したfunctionノードに先程のJSONデータを埋め込み出力を行うようにします。ピンにnameで名前をつけて、あとは緯度(lat)経度(lon)のパラメータをJSONデータ(数値)として格納しています。ピンの形状も変更できますが、指定しなければデフォルトの形状になります。

あとは地図のリンクへアクセスして確かめます。(※injectノードのボタンをクリックしないとfunctionノードが動作しないので必ず押してください

http://localhost:1880/worldmap

これで、1つピンを立てることができました。このフローを何回も繰り返すことによって多くのピンを立てていくことができます。

複数のピンを立てていく

先ほどはピンを1つ立てるフローでしたが、オープンデータで公開されているものはCSVなどのフォーマットになっているものが多くあります。 そこで、CSVデータ1行毎にピンデータのJSONデータを生成し、ピンを順次立てていくようなことを考えていくことになります。 今回はバス停データはGTFSで公開されているものを使用します。

GTFSとは?

GTFSは以下のようなデータです。

www.gtfs.jp

「標準的なバス情報フォーマット」は、バス事業者と経路検索等の情報利用者との情報の受渡しのための共通フォーマットです。国土交通省により2016年から標準化が進められています。公共交通データのデファクトスタンダートであるGTFSを元に作られた、交通事業者と開発者の双方に優しいフォーマットです。 (GTFS.JP より引用)

国土交通省のページ

www.mlit.go.jp

探している長久手市のGTFSデータは以下からダウンロードできます。

tshimada291.sakura.ne.jp

ダウンロードしたGTFSデータにはバス停の位置情報のCSVファイルが含まれていますが、それはstops.txtという形式になっています。このファイルを使用していきますが、CSVであることがわかりにくいので今回はstops_utf8_crlf.csvという名前に変更して以降は使用していきます。文字コードUTF-8、改行コードをCRLF(0x0D 0x0A)という意味です。

実際のファイルの内容は以下のようになっています。

CSVの処理を行う

CSVは簡単で扱いやすい反面、フォーマットの方言が多く、一般化する処理が面倒という物凄く矛盾したデータ形式だと思います。そのため、可能な限りオリジナルの処理ではなく、デフォルトのノードの機能をうまく使用するのがおすすめです。今回はこの処理はNode-REDのデフォルトのノードであるcsvノードを使用していきます。

このノードの良い点はCSVファイルの1行(1エントリ)毎に後続のフローに出力してくれる点です。この仕組みであれば先ほどのフローの考え方を大きく変えることなく処理を作成することができます。

注意点としてはcsvノードは実はデータのパースを行うことだけしかできないので、ファイルの読み込みはread fileノードと組み合わせることになります。

では、CSVファイルを開くフローを作成していきます。以下のノードを使用してフローを作成していきます。

  • injectノード
  • read fileノード
  • csvノード
  • debugノード

これらのノードを以下のように接続します。

あとは各ノードのプロパティを編集していきます。といっても編集するのはread fileノードcsvノードの2つだけです。

read fileノードのプロパティではファイル名をパス込みで入力を行います。

これで、指定したファイルの開くことができるようになります。

続いて、csvノードのプロパティを以下のように編集します。ここでのポイントは「1行目に列名を含むになっている」と「行毎にメッセージを分割」指定することです。

これで設定は終了しました。

画面の【デプロイ】ボタンをクリックしてエラーが発生していなければOKです。injectノードのボタンをクリックするとCSVファイルが読み込まれます。デバックの画面を開くと以下のように1エントごとに表題行の値をKeyCSVデータをValueとしたJSON形式が後段のフローに出力されていることがわかります。

あとは先ほど作成していたworldmapのフローと結合を行っていくことになります。

フローを完成させる

では、これまでに作成した2つのフローを結合させていきます。単純に結合させるだけでは動作しないので、データ形式JSON)インタフェースとなるノード、templateノードを挟み2つのフローを結合させていきます。

地図表示側のフローのinjectノードfunctionノードを削除し、代わりにtemplateノードを入れて、2つのフローを結合して以下のようなフローに編集します。

今回ピンを立てるのに必要となるCSVデータの属性(Key)は以下となります。

  • stop_name
  • stop_lat
  • stop_lon

このCSVデータの値をJSONデータのKeyとして以下のように変換して、新しいJSONデータを生成してmsg.payloadに格納し出力を行います。

  • stop_name➞name
  • stop_lat➞lat
  • stop_lon➞lon

では、templateノードのプロパティを変更してきます。

【テンプレート】の内容※ダブルクォーテーションがわかりにくいので注意です

{
    "name" : "{{payload.stop_name}}",
    "lat" : {{payload.stop_lat}} ,
    "lon" : {{payload.stop_lon}}
}

【出力形式】はJSONを選択します。

編集後に【デプロイ】ボタンをクリックし、injectノードのボタンをクリックした後、地図のURLを表示すると以下のようにピンが立った地図が表示されます。

完成版のフロー

完成したフローをGitHubにおいておきます。(途中の作成過程も含めて)

github.com

おわりに

今回はイベントで登壇した内容をエントリにまとめてみました。node-red-contrib-web-worldmapはピンを立てる以外にもピンの周囲に円を描くようなことを行うこともできますし(イベントでは紹介されていました)、ピン形状の変更も行えます。また、ポイント間に線を引くといった処理をおこなうこともできるので、地図を使用するにはこれなしには考えられないのではないかとすら思えます。

やはり感じたのはCSVファイルというメジャーでありながら方言の多い形式があるデータフォーマットは何とかならないのかなと思うところではあります。

とはいえ、今回は初めて地図を使用したフローを簡単に使用することができました。今後の興味ある点としては、GPS機能のあるものをを持ち歩いてリアルタイムにこのピン立てができるようになると面白くなるのかもと想像しています。