先日、Node-RED UG
のイベントが名古屋にて行われ、それにスピーカーとして出席しました。
イベント名は「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のサイト
日本の地図
名古屋市近郊の地図
地図表示を行う拡張ノードを導入する
今回導入する拡張ノードnode-red-contrib-web-worldmap
は地図処理を行うメジャーな拡張ノードです。
では、こちらを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ノード
のプロパティを開き、中心となる緯度・経度の数値を入力していきます。またここで、表示のベースとなるマップの指定も行います。デフォルトではグレー階調のマップでちょっと見栄えがしないので、カラーに変更します。
緯度・経度に関しては都度調べることになるので、例えばある都市の中心にしたいときには以下のサイトで調べるとよいでしょう。
今回は長久手市と入力してみます。
これで、特定した場所の緯度経度の数値がわかるので、この値をプロパティに入力していきます。表示のベース地図もプルダウンから変更します。
- 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
は以下のようなデータです。
「標準的なバス情報フォーマット」は、バス事業者と経路検索等の情報利用者との情報の受渡しのための共通フォーマットです。国土交通省により2016年から標準化が進められています。公共交通データのデファクトスタンダートであるGTFSを元に作られた、交通事業者と開発者の双方に優しいフォーマットです。 (GTFS.JP より引用)
国土交通省のページ
探している長久手市のGTFSデータは以下からダウンロードできます。
ダウンロードしたGTFS
データにはバス停の位置情報のCSVファイルが含まれていますが、それはstops.txt
という形式になっています。このファイルを使用していきますが、CSVであることがわかりにくいので今回はstops_utf8_crlf.csv
という名前に変更して以降は使用していきます。文字コードをUTF-8
、改行コードをCRLF(0x0D 0x0A)
という意味です。
実際のファイルの内容は以下のようになっています。
CSVの処理を行う
(2024.05.30追記) CSVファイルはNode-REDから参照できるところにおいてください。以下の例ではローカル環境に配置した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エントごとに表題行の値をKey
、CSVデータを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においておきます。(途中の作成過程も含めて)
(2024.05.30追記) CSVファイルはNode-REDから参照できるところにおいてください。以下の例ではローカル環境に配置したCSVファイルを使用しています。そのままでは動きませんのでパスは必ず変更してください。
おわりに
今回はイベントで登壇した内容をエントリにまとめてみました。node-red-contrib-web-worldmap
はピンを立てる以外にもピンの周囲に円を描くようなことを行うこともできますし(イベントでは紹介されていました)、ピン形状の変更も行えます。また、ポイント間に線を引くといった処理をおこなうこともできるので、地図を使用するにはこれなしには考えられないのではないかとすら思えます。
やはり感じたのはCSVファイルというメジャーでありながら方言の多い形式があるデータフォーマットは何とかならないのかなと思うところではあります。
とはいえ、今回は初めて地図を使用したフローを簡単に使用することができました。今後の興味ある点としては、GPS機能のあるものをを持ち歩いてリアルタイムにこのピン立てができるようになると面白くなるのかもと想像しています。