Noodl×Node-REDでペルソナ５のイセカイナビを再現する

12/27 ナビの画面でPOST送信しても画面遷移しないようにしました

#概要
Noodl Advent Calender23日目ですね。なんと私これがQiita人生初投稿です:tada:
勢いに任せてQiitaの登録からやり始めたのですが、何を投稿するか、投稿できるのか…と不安でしたが、なんとか投稿できました。
今年Node-RED、Noodlを使い始めてできたプロトタイプに関するネタを１つ、非プログラマー向けに投稿したいと思います。

#イセカイナビとは
TVゲーム、ペルソナ５上に出てくる特に欲望が強く歪んだ大人が持つ認知上の世界、パレスへのナビゲーションを行うアプリ。
>[イセカイナビについて](https://dic.pixiv.net/a/%E3%82%A4%E3%82%BB%E3%82%AB%E3%82%A4%E3%83%8A%E3%83%93)

#構想
イセカイナビをNoodl×Node-REDで**できるだけコードを書かず**に再現したい。ただそれだけです。
基本ベースはナビゲーション機能を持たせるためにGoogleMapsAPIを使い、特定の単語(ゲーム中でのパレス発見に使用するキーワード）を記入するときのみパレスへのナビゲーションを示唆するアクションがTTSで実行されるようにする。

#構成
APIのノードが豊富なNode-REDと、UIの表現がしやすいNoodlを連携させてます。

#解説
できることは以下となります。
・現在地(パレス)、目的地(パレスの場所)に地名を入れると普通にナビとして使える
・タップするとモルガナカーが出てくる
・鴨志田、金城や、学校などを地名に入れるとTTSが反応する


##Noodlフロー
Noodlにもhtml、Javascript、cssを扱うノードがあります。公開プロジェクトのMapSampleにGoogleMapsを使用した例もありましたが、設定が難しすぎ私のプログラミングレベルでは理解が困難でした。
Node-REDのlibraryにもGoogleMapsを使えるノードがありますが、ルート表示をできる設定がどうもできないようで、Node-REDの方にMap表示のhtml、Javascript、cssを記述しNoodl側にはiframeで表示させてます。


結果として、両者のフローが非常にシンプルになっているのではないでしょうか。

Mainフロー
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542127/043a86cf-6786-735a-7982-0cd25b04ea83.png)

Maprender
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542127/132f866c-fa98-4528-0e09-beb79ebf9ead.png)

navienter
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542127/7f5789fe-b051-40e8-5370-afc7a7ad13d4.png)

toppage
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542127/e6430c40-dfd4-91dd-38b0-b6405ab937b6.png)


##Node-REDフロー
最初は積極的にAPI用のノードを使用していたのですが、GoogleDirectionsノードはルート表示のオプション(google.maps.DirectionsRenderer)が使えないため、templateノードに書くことにしました。
1週間前までほとんどhtml/css/javascriptは書いたことがなかったのですが、Javascriptは電子工作含め用途が拡大してきているのでこれを機に勉強しました。
以下のhtml/css/javascriptではコメントアウトして機能を切った部分も敢えて含めています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542127/07af2140-33d6-3a0a-62cd-bea5a6b14135.png)

htmlノードの中身

```ruby:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
    <title>イセカイナビ ルート検索</title>
    <script src="http://maps.google.com/maps/api/js?key=[your api key]"></script>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
      <script type="text/javascript">
            {{{payload.javascript}}}
      </script>
      <style type="text/css">
            {{{payload.css}}}
      </style>
</head>
<body onload="initialize();">
    <div id="container">
        <h1>イセカイナビ ルート検索</h1>
        <!--<p>テキストフィールドに起点と終点の座標を入力すると、ルート検索を行います。</p>
        <p>※座標を入れてください。(例：35.6895967,139.6982244)</p>-->
       
            <form method="post" action="{{url}}">
            <table>
            <tr><th>パレス：</th><td><input type="text" name="start" id="start" size="20" maxlength="100"></td></tr>
            <tr><th>パレスの場所：</th><td><input type="text" name="end" id="end" size="20" maxlength="100"></td></tr>
            <tr><th>  <input type="button" value="検索" onclick="searchRoute()">
              <input type="submit" value="送信" ></tr></th>
           </table>   
            </form>
     
        <p>
            
          <!--  <input type="button" value="検索マーカーすべて非表示" onclick="invisibleMarkers()">
            <input type="button" value="検索マーカーすべて表示" onclick="visibleMarkers()">
        </p>
        <p>Googleマップをクリックし、マーカーを設置してください。</p>
        <p>
            <input type="button" value="マーカー削除" onclick="deleteMarkers()">
            <input type="button" value="座標出力" onclick="csvOutput()">
            <input type="button" value="座標初期化" onclick="csvDelete()">-->
        </p>
    </div>
    <div id="gmap">
        <div id="map_canvas" style="width:800px; height:400px;"></div>
        <!--<div id="csv" style="width: 30%; height: 400px; border: 1px solid Gray; overflow: auto;"></div>-->
    </div>
    <div id="dpanel">
        <div id="directionsPanel" style="width:100%; height:640px;"></div>
    </div>
</body>

</html>
```

cssノードの中身

```ruby:index.css
html { 
    height: 100%;
    font-size:0.8em;
}
body { 
    height: 100%; 
    margin: 0; 
    padding: 0;
    background-color:rgb(190, 17, 17);
    font-family:
      "ヒラギノ角ゴ Pro W3",
       "Hiragino Kaku Gothic Pro", 
       "メイリオ", 
       Meiryo, 
       Osaka, 
       "ＭＳ Ｐゴシック", 
       "MS PGothic", 
       sans-serif;     
}
#container {
    margin: 5px;
    padding-bottom: 5px;
    
}
#gmap {
    margin: 5px;
    padding-bottom: 5px;
    display: flex;
}
#dpanel {
    margin: 5px;
    padding-bottom: 5px;
    
  }
h1 {
    font-size:1.2em;
    background-color:rgb(3, 4, 5);
    color:#fff;
    padding:10px;
}
#map_canvas { 
    height: 100%;
}
#directionsPanel {
    background-color:#fff;
}
```

javascriptノードの中身

```index.js
var map;
var marker;
var markers = [];
var infowindow;
var directionsDisplay;
var directionsService;
var pinImage_red = new google.maps.MarkerImage("C:/Users/user/morgana.png");
var pinImage_blue = new google.maps.MarkerImage("http://maps.google.com/mapfiles/ms/icons/blue-dot.png");
var MarkerArray = new google.maps.MVCArray();

//initialize関数
function initialize() {
    var latlng = new google.maps.LatLng(35.681382,139.766084);//東京駅
    var myOptions = {
        zoom:14,
        center:latlng,
        mapTypeId:google.maps.MapTypeId.ROADMAP
    };
    map = new google.maps.Map(document.getElementById("map_canvas"),myOptions);

    directionsDisplay = new google.maps.DirectionsRenderer({
        polylineOptions: {
            //strokeColor: '#FF0000',
            strokeColor: '#1e90ff',
            strokeWeight: 8,
            strokeOpacity: 0.6
        }
    });
    directionsDisplay.setMap(map);

    // createMarker関数の呼び出し
    createMarker();
}
//searchRoute関数
function searchRoute() {
    var start = document.getElementById("start").value;
    var end = document.getElementById("end").value;
    
var rendererOptions = {
        draggable: true,
        preserveViewport:false
    };

    directionsService = new google.maps.DirectionsService();
    var request = {
        origin: start,
        destination: end,
        travelMode: google.maps.DirectionsTravelMode.DRIVING, // 自動車でのルート
        unitSystem: google.maps.DirectionsUnitSystem.METRIC, // 単位km表示
        optimizeWaypoints: true, // 最適化された最短距離にする
        avoidHighways: true, // 高速道路を除外
        avoidTolls: true // 有料道路を除外
    };
    directionsService.route(request, function(response, status){
        if (status == google.maps.DirectionsStatus.OK){
            directionsDisplay.setDirections(response);
            directionsDisplay.setPanel(document.getElementById("directionsPanel"));
        }
        // overview_pathを表示する
       /* for (var i = 0; i < response.routes.length; i++) {
            var r = response.routes[i];
            for (var j = 0; j < r.overview_path.length; j++) {
                var latlng = r.overview_path[j];
                marker = new google.maps.Marker({
                    position: latlng,
                    icon: pinImage_blue,
                    map: map
                });
                //markers.push(marker);
            }
        }*/
    });
    map = new google.maps.Map(document.getElementById("map_canvas"),response);
    directionsDisplay.setMap(map);
    
    msg.payload = document.getElementById("start").value;
}
//createMarker関数
function createMarker() {
    // mapをクリックしたときのイベントを設定
    google.maps.event.addListener(map, 'click', maplistener);

    // mapをクリックしたときの処理
    function maplistener(event) {
        // marker作成
        marker = new google.maps.Marker({
            map: map,
            draggable: true, // ドラッグを可能にする
            icon: {
              url: "C:/Users/user/morgana.png",           //アイコンのURL
              scaledSize: new google.maps.Size(50, 50) //サイズ
            }
        });
        // markerの位置を設定
        // event.latLng.lat()でクリックしたところの緯度を取得
        marker.setPosition(new google.maps.LatLng(event.latLng.lat(), event.latLng.lng()));
        MarkerArray.push(marker);
    }
}
//deleteMarkers関数
function deleteMarkers() {
    MarkerArray.forEach(function (marker, idx) { marker.setMap(null); });
    MarkerArray = []; // marker配列の初期化
}
//csvOutput関数
function csvOutput() {
    MarkerArray.forEach(function (marker, idx) {
        var pos = marker.getPosition();
        var lat = pos.lat();
        var lng = pos.lng();
        $("#csv").append(lat + "," + lng  + "<br />");
    });
}
//csvDelete関数
function csvDelete() {
    $("#csv").empty();
}
//invisibleMarkers関数
function invisibleMarkers() {
    markers.forEach(function (marker, idx) { marker.setVisible(false); });
}
//visibleMarkers関数
function visibleMarkers() {
    markers.forEach(function (marker, idx) { marker.setVisible(true); });
}

```

<details><summary>全体フロー</summary><div>

```rb
[{"id":"ee85b10c.04e36","type":"tab","label":"フロー 1","disabled":false,"info":""},{"id":"78b876a.8434f88","type":"function","z":"ee85b10c.04e36","name":"","func":"msg.payload = msg.speech;\nreturn msg;","outputs":1,"noerr":0,"x":970,"y":100,"wires":[["8719688d.a64188"]]},{"id":"8719688d.a64188","type":"play audio","z":"ee85b10c.04e36","name":"","voice":"28","x":1150,"y":100,"wires":[]},{"id":"8850973e.fbf978","type":"watson-text-to-speech","z":"ee85b10c.04e36","name":"test","lang":"ja-JP","langhidden":"ja-JP","langcustom":"NoCustomisationSetting","langcustomhidden":"","voice":"ja-JP_EmiV3Voice","voicehidden":"ja-JP_EmiV3Voice","format":"audio/wav","password":"[yourapikey]","apikey":"[yourapikey]","payload-response":false,"default-endpoint":true,"service-endpoint":"https://stream.watsonplatform.net/text-to-speech/api ","x":830,"y":100,"wires":[["78b876a.8434f88"]]},{"id":"d1589e1d.04b38","type":"switch","z":"ee85b10c.04e36","name":"","property":"payload","propertyType":"msg","rules":[{"t":"cont","v":"鴨志田","vt":"str"},{"t":"cont","v":"斑目","vt":"str"},{"t":"cont","v":"金城","vt":"str"},{"t":"cont","v":"双葉","vt":"str"},{"t":"cont","v":"飯島","vt":"str"},{"t":"cont","v":"獅童","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":7,"x":470,"y":100,"wires":[["4d37f48f.21e5fc"],["4d37f48f.21e5fc"],["4d37f48f.21e5fc"],["4d37f48f.21e5fc"],["4d37f48f.21e5fc"],["4d37f48f.21e5fc"],[]]},{"id":"4d37f48f.21e5fc","type":"trigger","z":"ee85b10c.04e36","op1":"ヒットしました","op2":"　","op1type":"str","op2type":"str","duration":"50","extend":false,"units":"ms","reset":"","bytopic":"all","name":"","x":650,"y":100,"wires":[["8850973e.fbf978"]]},{"id":"2d07b5f3.1876fa","type":"switch","z":"ee85b10c.04e36","name":"","property":"payload","propertyType":"msg","rules":[{"t":"cont","v":"学校","vt":"str"},{"t":"cont","v":"家","vt":"str"},{"t":"cont","v":"渋谷","vt":"str"},{"t":"cont","v":"部屋","vt":"str"},{"t":"cont","v":"裁判所","vt":"str"},{"t":"cont","v":"日本","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":7,"x":450,"y":240,"wires":[["e9ffab36.653948"],["e9ffab36.653948"],["e9ffab36.653948"],["e9ffab36.653948"],["e9ffab36.653948"],["e9ffab36.653948"],[]]},{"id":"e9ffab36.653948","type":"trigger","z":"ee85b10c.04e36","op1":"候補が見つかりました","op2":"　","op1type":"str","op2type":"str","duration":"50","extend":false,"units":"ms","reset":"","bytopic":"all","name":"","x":650,"y":240,"wires":[["8850973e.fbf978"]]},{"id":"a9507e54.00fad","type":"http in","z":"ee85b10c.04e36","name":"","url":"/test","method":"get","upload":false,"swaggerDoc":"","x":140,"y":440,"wires":[["fc596815.8d6548"]]},{"id":"23f44fc3.55d97","type":"http response","z":"ee85b10c.04e36","name":"","statusCode":"","headers":{},"x":990,"y":520,"wires":[]},{"id":"d5e50cbe.ad0bd","type":"template","z":"ee85b10c.04e36","name":"html","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<!DOCTYPE html>\n<html lang=\"ja\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"initial-scale=1.0, user-scalable=no\">\n    <title>イセカイナビ ルート検索</title>\n    <script src=\"http://maps.google.com/maps/api/js?key=[yourapikey]\"></script>\n    <script type=\"text/javascript\" src=\"http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js\"></script>\n      <script type=\"text/javascript\">\n            {{{payload.javascript}}}\n      </script>\n      <style type=\"text/css\">\n            {{{payload.css}}}\n      </style>\n</head>\n<body onload=\"initialize();\">\n    <div id=\"container\">\n        <h1>イセカイナビ ルート検索</h1>\n        <!--<p>テキストフィールドに起点と終点の座標を入力すると、ルート検索を行います。</p>\n        <p>※座標を入れてください。(例：35.6895967,139.6982244)</p>-->\n       \n            <form method=\"post\" action=\"{{url}}\">\n            <table>\n            <tr><th>パレス：</th><td><input type=\"text\" name=\"start\" id=\"start\" size=\"20\" maxlength=\"100\"></td></tr>\n            <tr><th>パレスの場所：</th><td><input type=\"text\" name=\"end\" id=\"end\" size=\"20\" maxlength=\"100\"></td></tr>\n            <tr><th>  <input type=\"button\" value=\"検索\" onclick=\"searchRoute()\">\n              <input type=\"submit\" value=\"送信\"></tr></th>\n              \n           </table>   \n            </form>\n            \n            <form method=\"post\" id=\"form\" action=\"/api/upload\" target=\"sendPhoto\">\n             <input type=\"file\" capture=\"camera\" accept=\"image/*\" id=\"file-image\">\n             <button class=\"btn-send\">送信</button>\n            </form>\n\n            <iframe name=\"sendPhoto\" style=\"width:0px;height:0px;border:0px;\"></iframe>\n     \n        <p>\n            \n          <!--  <input type=\"button\" value=\"検索マーカーすべて非表示\" onclick=\"invisibleMarkers()\">\n            <input type=\"button\" value=\"検索マーカーすべて表示\" onclick=\"visibleMarkers()\">\n        </p>\n        <p>Googleマップをクリックし、マーカーを設置してください。</p>\n        <p>\n            <input type=\"button\" value=\"マーカー削除\" onclick=\"deleteMarkers()\">\n            <input type=\"button\" value=\"座標出力\" onclick=\"csvOutput()\">\n            <input type=\"button\" value=\"座標初期化\" onclick=\"csvDelete()\">-->\n        </p>\n    </div>\n    <div id=\"gmap\">\n        <div id=\"map_canvas\" style=\"width:800px; height:400px;\"></div>\n        <!--<div id=\"csv\" style=\"width: 30%; height: 400px; border: 1px solid Gray; overflow: auto;\"></div>-->\n    </div>\n    <div id=\"dpanel\">\n        <div id=\"directionsPanel\" style=\"width:100%; height:640px;\"></div>\n    </div>\n</body>\n\n</html>","output":"str","x":770,"y":520,"wires":[["23f44fc3.55d97"]]},{"id":"c71c133f.75499","type":"template","z":"ee85b10c.04e36","name":"css","field":"payload.css","fieldType":"msg","format":"css","syntax":"mustache","template":"html { \n    height: 100%;\n    font-size:0.8em;\n}\nbody { \n    height: 100%; \n    margin: 0; \n    padding: 0;\n    background-color:rgb(190, 17, 17);\n    font-family:\n      \"ヒラギノ角ゴ Pro W3\",\n       \"Hiragino Kaku Gothic Pro\", \n       \"メイリオ\", \n       Meiryo, \n       Osaka, \n       \"ＭＳ Ｐゴシック\", \n       \"MS PGothic\", \n       sans-serif;     \n}\n#container {\n    margin: 5px;\n    padding-bottom: 5px;\n    \n}\n#gmap {\n    margin: 5px;\n    padding-bottom: 5px;\n    display: flex;\n}\n#dpanel {\n    margin: 5px;\n    padding-bottom: 5px;\n    \n  }\nh1 {\n    font-size:1.2em;\n    background-color:rgb(3, 4, 5);\n    color:#fff;\n    padding:10px;\n}\n#map_canvas { \n    height: 100%;\n}\n#directionsPanel {\n    background-color:#fff;\n}","output":"str","x":590,"y":520,"wires":[["d5e50cbe.ad0bd"]]},{"id":"9ce5b942.ae73f8","type":"template","z":"ee85b10c.04e36","name":"javascript","field":"payload.javascript","fieldType":"msg","format":"javascript","syntax":"plain","template":"var map;\nvar marker;\nvar markers = [];\nvar infowindow;\nvar directionsDisplay;\nvar directionsService;\nvar pinImage_red = new google.maps.MarkerImage(\"C:/Users/user/morgana.png\");\nvar pinImage_blue = new google.maps.MarkerImage(\"http://maps.google.com/mapfiles/ms/icons/blue-dot.png\");\nvar MarkerArray = new google.maps.MVCArray();\n\n//initialize関数\nfunction initialize() {\n    var latlng = new google.maps.LatLng(35.681382,139.766084);//東京駅\n    var myOptions = {\n        zoom:14,\n        center:latlng,\n        mapTypeId:google.maps.MapTypeId.ROADMAP\n    };\n    map = new google.maps.Map(document.getElementById(\"map_canvas\"),myOptions);\n\n    directionsDisplay = new google.maps.DirectionsRenderer({\n        polylineOptions: {\n            //strokeColor: '#FF0000',\n            strokeColor: '#1e90ff',\n            strokeWeight: 8,\n            strokeOpacity: 0.6\n        }\n    });\n    directionsDisplay.setMap(map);\n\n    // createMarker関数の呼び出し\n    createMarker();\n}\n//searchRoute関数\nfunction searchRoute() {\n    var start = document.getElementById(\"start\").value;\n    var end = document.getElementById(\"end\").value;\n    \nvar rendererOptions = {\n        draggable: true,\n        preserveViewport:false\n    };\n\n    directionsService = new google.maps.DirectionsService();\n    var request = {\n        origin: start,\n        destination: end,\n        travelMode: google.maps.DirectionsTravelMode.DRIVING, // 自動車でのルート\n        unitSystem: google.maps.DirectionsUnitSystem.METRIC, // 単位km表示\n        optimizeWaypoints: true, // 最適化された最短距離にする\n        avoidHighways: true, // 高速道路を除外\n        avoidTolls: true // 有料道路を除外\n    };\n    directionsService.route(request, function(response, status){\n        if (status == google.maps.DirectionsStatus.OK){\n            directionsDisplay.setDirections(response);\n            directionsDisplay.setPanel(document.getElementById(\"directionsPanel\"));\n        }\n        // overview_pathを表示する\n       /* for (var i = 0; i < response.routes.length; i++) {\n            var r = response.routes[i];\n            for (var j = 0; j < r.overview_path.length; j++) {\n                var latlng = r.overview_path[j];\n                marker = new google.maps.Marker({\n                    position: latlng,\n                    icon: pinImage_blue,\n                    map: map\n                });\n                //markers.push(marker);\n            }\n        }*/\n    });\n    map = new google.maps.Map(document.getElementById(\"map_canvas\"),response);\n    directionsDisplay.setMap(map);\n    \n    msg.payload = document.getElementById(\"start\").value;\n}\n//createMarker関数\nfunction createMarker() {\n    // mapをクリックしたときのイベントを設定\n    google.maps.event.addListener(map, 'click', maplistener);\n\n    // mapをクリックしたときの処理\n    function maplistener(event) {\n        // marker作成\n        marker = new google.maps.Marker({\n            map: map,\n            draggable: true, // ドラッグを可能にする\n            icon: {\n              url: \"C:/Users/user/morgana.png\",           //アイコンのURL\n              scaledSize: new google.maps.Size(50, 50) //サイズ\n            }\n        });\n        // markerの位置を設定\n        // event.latLng.lat()でクリックしたところの緯度を取得\n        marker.setPosition(new google.maps.LatLng(event.latLng.lat(), event.latLng.lng()));\n        MarkerArray.push(marker);\n    }\n}\n//deleteMarkers関数\nfunction deleteMarkers() {\n    MarkerArray.forEach(function (marker, idx) { marker.setMap(null); });\n    MarkerArray = []; // marker配列の初期化\n}\n//csvOutput関数\nfunction csvOutput() {\n    MarkerArray.forEach(function (marker, idx) {\n        var pos = marker.getPosition();\n        var lat = pos.lat();\n        var lng = pos.lng();\n        $(\"#csv\").append(lat + \",\" + lng  + \"<br />\");\n    });\n}\n//csvDelete関数\nfunction csvDelete() {\n    $(\"#csv\").empty();\n}\n//invisibleMarkers関数\nfunction invisibleMarkers() {\n    markers.forEach(function (marker, idx) { marker.setVisible(false); });\n}\n//visibleMarkers関数\nfunction visibleMarkers() {\n    markers.forEach(function (marker, idx) { marker.setVisible(true); });\n}\n\n\n\n\n","output":"str","x":400,"y":520,"wires":[["c71c133f.75499"]]},{"id":"614d8fb5.931c9","type":"http in","z":"ee85b10c.04e36","name":"","url":"/testpost","method":"post","upload":false,"swaggerDoc":"31bf6a0b.69e446","x":160,"y":620,"wires":[["1a7369f1.b04786","fe5df434.913d78","ff120caa.7ce93","7ad6e0c9.9946b"]]},{"id":"1a7369f1.b04786","type":"debug","z":"ee85b10c.04e36","name":"testpost","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":500,"y":680,"wires":[]},{"id":"c8ce9274.efad7","type":"http response","z":"ee85b10c.04e36","name":"","x":1050,"y":620,"wires":[]},{"id":"2cd4d9fd.a5c146","type":"function","z":"ee85b10c.04e36","name":"return msg.payload to client","func":"msg.payload = 'The following data was submitted and available in the msg.payload: '+msg.payload;\nreturn msg;","outputs":1,"noerr":0,"x":700,"y":620,"wires":[["8dcec1a.317904"]]},{"id":"fe5df434.913d78","type":"json","z":"ee85b10c.04e36","name":"","x":450,"y":620,"wires":[["2cd4d9fd.a5c146"]]},{"id":"fc596815.8d6548","type":"function","z":"ee85b10c.04e36","name":"msg.url = \"testpost\";","func":"msg.url = \"testpost\";\nreturn msg;","outputs":1,"noerr":0,"x":380,"y":440,"wires":[["9ce5b942.ae73f8"]]},{"id":"ff120caa.7ce93","type":"split","z":"ee85b10c.04e36","name":"","splt":":","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":290,"y":100,"wires":[["d1589e1d.04b38"]]},{"id":"7ad6e0c9.9946b","type":"split","z":"ee85b10c.04e36","name":"","splt":":","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":310,"y":260,"wires":[["2d07b5f3.1876fa"]]},{"id":"8dcec1a.317904","type":"template","z":"ee85b10c.04e36","name":"html","field":"payload","fieldType":"msg","format":"html","syntax":"mustache","template":"<!--<A HREF=\"javascript:history.back()\">前のページに戻る</A>-->\n\n<script>\n//document.write(’<a href=\"’+document.referrer+’\">前のページに戻る</a>’);\nwindow.location.href = '/test'; \n</script>","output":"str","x":910,"y":620,"wires":[["c8ce9274.efad7"]]},{"id":"31bf6a0b.69e446","type":"swagger-doc","z":"","summary":"","description":"","tags":"","consumes":"","produces":"","parameters":[],"responses":{},"deprecated":false}]
```
</div></details>

###使用API用のlibrary
watson-text-to-speech(node-red library)
Google Maps Api(node-redのhtml内で使用) 


#動作
以下のtweetのような動作になります。

[注意]TTSの音声が動画後半で出ます。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">よっしゃーできたぁー👍<a href="https://twitter.com/hashtag/Noodl?src=hash&amp;ref_src=twsrc%5Etfw">#Noodl</a> <a href="https://twitter.com/hashtag/%E3%82%A4%E3%82%BB%E3%82%AB%E3%82%A4%E3%83%8A%E3%83%93?src=hash&amp;ref_src=twsrc%5Etfw">#イセカイナビ</a> <a href="https://t.co/Vdj5KGyH4P">pic.twitter.com/Vdj5KGyH4P</a></p>&mdash; サクヤtypeR Noodl勉強 (@typeR_Anonymous) <a href="https://twitter.com/typeR_Anonymous/status/1209028757534597120?ref_src=twsrc%5Etfw">December 23, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

最後の画面はnode-REDをスマホから起動させれば普通にナビとして使えますよ！


#おわりに
　Noodl×Node-REDでペルソナ５のイセカイナビを再現してみました。Noodlは開発されてまだまだ日が浅いのでlibraryが少なく自由度高いことをやろうとすると、どうしてもJavascriptノードに頼ることになります。Node-REDではfunctionノードにJavascriptで記述していくのは可読性が下がるためアンチデザインパターンとされています(ノンコーディングでなくなる)。
　ともあれ便利なツールであることは確かなので、積極的に公開してノードのlibraryを増やしていきたいですね！


##参考
GoogleMapsjavascriptApiについては@wakusyanさんの記事を参考とさせていただきました。
https://qiita.com/wakusyan/items/baa5dbac7fcdb0fa42f5