つりはっく。
バチ抜けとは
釣り用語の一つです。
バチとはイソメやゴカイなどの環形動物のことを指し、バチ抜けとは春先の大潮の夜に海底に潜んでいるバチが一気に浮いてくる現象です。抜けたバチは種々の魚にとってはごちそうであって、バチを求めて魚が寄ってくるというわけです。バチ抜けは地域によって発生する時期がまちまちで、更に魚が寄ってくるかもわからない自然のイベントとなっているわけで、いつどのへんでバチ抜けが起こったのかを知りたいのは釣り人の性みたいなもの。
今回は、Twitterで位置情報とともにつぶやかれている情報をもとに、バチ抜けなどの情報を地図上にマッピングしてみようと思います。
使うのはNode-RED
今回もNode-REDで実装していきます。フローはこんな感じ。
ポイントとなるノードはこちら。
Twitterノード
これでTwitter上の情報をほぼリアルタイムにスキャンして、指定したハッシュタグのツイートを引っ張ってきます。実際にツイートが見つかると、おそらくTwitter本家のTweet Objectの形式でデータが取得できます。
ただ、仕様が複雑(笑)今回は、次のFunctionノードでテキトーに位置情報をパースします。
Functionノード
ここで、Twitterノードから取得したデータから位置情報をパースします。
if(msg.tweet && msg.tweet.place && msg.tweet.place.bounding_box && msg.tweet.place.bounding_box.coordinates){ var longitude = 0; var latitude = 0; var array = msg.tweet.place.bounding_box.coordinates[0]; var tweet = msg.payload.replace(/\r?\n/g,""); var link = 'https://twitter.com/' + msg.tweet.user.screen_name + '/status/' + msg.tweet.id_str; array.forEach(value => { longitude += value[0]; latitude += value[1]; }); longitude /= array.length; latitude /= array.length; msg = {}; msg.has_location = true; msg.payload = { "lat" : latitude, "lon" : longitude, "name" : tweet, "weblink" : link, }; } return msg;
tweet.place.bounding_box.coordinates
内の座標の平均値をとって、それを緯度経度として採用しています。後段の処理でCSVファイルに保存するため、Tweet本文は改行を削除してから保存しています。
CSVノード・Fileノード
前段の処理で、位置情報がパースできたTweetだけをCSVとして一旦保存しておきます。
WorldMapノード
今回の要。
node-red-contrib-web-worldmap
をインストールして、地図を表示できるようになります。フロー中にこのWorldMapノードがあると、特定のURLにアクセスすると地図が表示されるようになります。このノードに対して緯度経度などをくべると、地図上にピンが表示されるようになります。今回は、WorldMapにアクセスがあったときに発火するイベントを契機に、CSVを読み込み、そこに書かれた位置情報をすべてWorldMapにマッピングします。
これをつかって地図表示させてみました
こんな感じ。
まだまだTwitterの監視をし始めたばかりなのでピンはすくなめですが、ちゃんと可視化できています。
ちょっとずつピンが増えてくると楽しいですねぇ。
フローはこちら
[ { "id": "add2a0f2.b28d2", "type": "twitter in", "z": "52e43921.021f08", "twitter": "", "tags": "バチ抜け,シーバス,#バチ抜け,#シーバス", "user": "false", "name": "バチ抜けツイート", "inputs": 0, "x": 130, "y": 300, "wires": [ [ "643d563.f2d9fa8", "1e92e179.29a17f" ] ] }, { "id": "efe29703.79f498", "type": "comment", "z": "52e43921.021f08", "name": "ツイート監視", "info": "", "x": 110, "y": 260, "wires": [] }, { "id": "643d563.f2d9fa8", "type": "function", "z": "52e43921.021f08", "name": "位置情報パース", "func": "if(msg.tweet && msg.tweet.place && msg.tweet.place.bounding_box && msg.tweet.place.bounding_box.coordinates){\n var longitude = 0;\n var latitude = 0;\n var array = msg.tweet.place.bounding_box.coordinates[0];\n var tweet = msg.payload.replace(/\\r?\\n/g,\"\");\n var link = 'https://twitter.com/' + msg.tweet.user.screen_name + '/status/' + msg.tweet.id_str;\n\n array.forEach(value => {\n longitude += value[0];\n latitude += value[1];\n });\n \n longitude /= array.length;\n latitude /= array.length;\n \n msg = {};\n msg.has_location = true;\n msg.payload = {\n \"lat\" : latitude,\n \"lon\" : longitude,\n \"name\" : tweet,\n \"weblink\" : link,\n };\n}\nreturn msg;", "outputs": 1, "noerr": 0, "x": 340, "y": 300, "wires": [ [ "8649b0d9.a5f98" ] ] }, { "id": "dddeedd9.5a74b", "type": "worldmap", "z": "52e43921.021f08", "name": "", "lat": "", "lon": "", "zoom": "", "layer": "", "cluster": "", "maxage": "", "usermenu": "show", "layers": "show", "panit": "false", "panlock": "false", "zoomlock": "false", "hiderightclick": "false", "coords": "none", "showgrid": "false", "allowFileDrop": "false", "path": "/worldmap", "x": 1060, "y": 460, "wires": [] }, { "id": "8649b0d9.a5f98", "type": "switch", "z": "52e43921.021f08", "name": "位置情報があるときだけ動作", "property": "has_location", "propertyType": "msg", "rules": [ { "t": "true" } ], "checkall": "true", "repair": false, "outputs": 1, "x": 580, "y": 300, "wires": [ [ "6d46063b.e4bce8" ] ] }, { "id": "433fb432.0e49fc", "type": "function", "z": "52e43921.021f08", "name": "ズーム", "func": "msg.payload = {\n command : {\n lat : msg.payload.lat,\n lon : msg.payload.lon,\n zoom : 6,\n }\n}\nreturn msg;", "outputs": 1, "noerr": 0, "x": 930, "y": 480, "wires": [ [ "dddeedd9.5a74b" ] ] }, { "id": "75d4e561.cf315c", "type": "worldmap in", "z": "52e43921.021f08", "name": "", "path": "/worldmap", "events": "all", "x": 100, "y": 460, "wires": [ [ "493cf6af.c9d1d8" ] ] }, { "id": "493cf6af.c9d1d8", "type": "switch", "z": "52e43921.021f08", "name": "", "property": "payload.action", "propertyType": "msg", "rules": [ { "t": "eq", "v": "connected", "vt": "str" } ], "checkall": "true", "repair": false, "outputs": 1, "x": 250, "y": 460, "wires": [ [ "6381668a.b712c8" ] ] }, { "id": "6d46063b.e4bce8", "type": "csv", "z": "52e43921.021f08", "name": "CSVに変換", "sep": ",", "hdrin": "", "hdrout": "", "multi": "one", "ret": "\\n", "temp": "lat,lon,name,weblink", "skip": "0", "strings": false, "x": 810, "y": 300, "wires": [ [ "96084250.d8c06", "23921cd8.841d44" ] ] }, { "id": "96084250.d8c06", "type": "file", "z": "52e43921.021f08", "name": "history.csvに保存", "filename": "history.csv", "appendNewline": true, "createDir": false, "overwriteFile": "false", "encoding": "none", "x": 1010, "y": 300, "wires": [ [] ] }, { "id": "23921cd8.841d44", "type": "debug", "z": "52e43921.021f08", "name": "", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "true", "targetType": "full", "x": 970, "y": 340, "wires": [] }, { "id": "3b137fe3.26fa", "type": "csv", "z": "52e43921.021f08", "name": "", "sep": ",", "hdrin": "", "hdrout": "", "multi": "one", "ret": "\\n", "temp": "lat,lon,name,weblink", "skip": "0", "strings": true, "x": 630, "y": 460, "wires": [ [ "5786ae6a.d8e1" ] ] }, { "id": "6381668a.b712c8", "type": "file in", "z": "52e43921.021f08", "name": "history.csvを読み込み", "filename": "history.csv", "format": "utf8", "chunk": false, "sendError": false, "encoding": "none", "x": 440, "y": 460, "wires": [ [ "3b137fe3.26fa" ] ] }, { "id": "5786ae6a.d8e1", "type": "function", "z": "52e43921.021f08", "name": "weblink作成", "func": "const weblink = {\n \"name\" : \"Twitter\",\n \"url\" : msg.payload.weblink,\n \"target\":\"_new\"\n}\n\nmsg.payload.weblink = weblink;\nreturn msg;", "outputs": 1, "noerr": 0, "x": 790, "y": 460, "wires": [ [ "dddeedd9.5a74b", "433fb432.0e49fc" ] ] }, { "id": "cddaa0e7.caea7", "type": "comment", "z": "52e43921.021f08", "name": "Mapにアクセスがあったとき", "info": "", "x": 160, "y": 420, "wires": [] }, { "id": "1e92e179.29a17f", "type": "debug", "z": "52e43921.021f08", "name": "", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "true", "targetType": "full", "x": 310, "y": 340, "wires": [] } ]