add new ui_worldmap node for displaying map on Node-RED Dashboard (#112)
* add new ui_worldmap node for displaying map on Node-RED Dashboard * update according to comments
This commit is contained in:
parent
16b8ce050b
commit
cfb9f7feda
219
worldmap.html
219
worldmap.html
@ -150,6 +150,153 @@ then by default <code>⌘⇧m</code> - <code>ctrl-shift-m</code> will load the m
|
||||
to <b>control</b> the map remotely, including how to use a local map server.</p>
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/x-red" data-template-name="ui_worldmap">
|
||||
<div class="form-row" id="template-row-group">
|
||||
<label for="node-input-group"><i class="fa fa-table"></i> Group</span></label>
|
||||
<input type="text" id="node-input-group">
|
||||
</div>
|
||||
<div class="form-row" id="template-row-size">
|
||||
<label><i class="fa fa-object-group"></i> Size</span></label>
|
||||
<input type="hidden" id="node-input-width">
|
||||
<input type="hidden" id="node-input-height">
|
||||
<button class="editor-button" id="node-input-size"></button>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<table border="0" width="96%">
|
||||
<tr><td width="100px"><i class="fa fa-globe"></i> Start<td>Latitude</td><td>Longitude</td><td width="60px">Zoom</td></tr>
|
||||
<tr><td> </td>
|
||||
<td><input type="text" id="node-input-lat" style="width:90px;"></td>
|
||||
<td><input type="text" id="node-input-lon" style="width:90px;"></td>
|
||||
<td><input type="text" id="node-input-zoom" style="width:60px;" placeholder="1 - 18"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-layer"><i class="fa fa-map"></i> Base map</label>
|
||||
<select id="node-input-layer">
|
||||
<option value="OSM grey">OpenStreetMap Greyscale</option>
|
||||
<option value="OSM">OpenStreetMap</option>
|
||||
<option value="Esri">ESRI Streetmap</option>
|
||||
<option value="Esri Satellite">ESRI Satellite</option>
|
||||
<option value="Esri Topography">ESRI Topography</option>
|
||||
// <option value="Esri Terrain">ESRI Terrain</option>
|
||||
<option value="Esri Dark Grey">ESRI Dark Grey</option>
|
||||
<option value="Esri Ocean">ESRI Ocean</option>
|
||||
<option value="Nat Geo">National Geographic</option>
|
||||
<option value="UK OS Opendata">UK OS Opendata</option>
|
||||
<option value="Hike Bike">Hike Bike OSM</option>
|
||||
<option value="Terrain">Terrain</option>
|
||||
<option value="Watercolor">Stamen Watercolor</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-cluster"><i class="fa fa-dot-circle-o"></i>Cluster when</label>
|
||||
zoom level is less than <input type="text" id="node-input-cluster" placeholder="0 (0,off - 19)" style="width:100px;">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-maxage"><i class="fa fa-clock-o"></i> Max age</label>
|
||||
Remove markers after <input type="text" id="node-input-maxage" placeholder="600" style="width:67px;"> seconds
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-usermenu"><i class="fa fa-user"></i> User menu</label>
|
||||
<select id="node-input-usermenu" style="width:64px;">
|
||||
<option value="show">Show</option>
|
||||
<option value="hide">Hide</option>
|
||||
</select>
|
||||
<i class="fa fa-bars" style="margin-left:30px;"></i> Layer menu
|
||||
<select id="node-input-layers" style="width:64px;">
|
||||
<option value="show">Show</option>
|
||||
<option value="hide">Hide</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-panlock"><i class="fa fa-lock"></i> Lock map</label>
|
||||
<select id="node-input-panlock" style="width:64px;">
|
||||
<option value="false">False</option>
|
||||
<option value="true">True</option>
|
||||
</select>
|
||||
<i class="fa fa-lock" style="margin-left:30px;"></i> Lock zoom
|
||||
<select id="node-input-zoomlock" style="width:64px;">
|
||||
<option value="false">False</option>
|
||||
<option value="true">True</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-panit"><i class="fa fa-arrows-alt"></i> Auto-pan</label>
|
||||
<select id="node-input-panit" style="width:72px;">
|
||||
<option value="true">Enable</option>
|
||||
<option value="false">Disable</option>
|
||||
</select>
|
||||
<i class="fa fa-hand-o-right" style="margin-left:22px;"></i> Right click
|
||||
<select id="node-input-hiderightclick" style="width:72px;">
|
||||
<option value="false">Enable</option>
|
||||
<option value="true">Disable</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-coords"><i class="fa fa-compass"></i> Co-ordinates</label>
|
||||
<select id="node-input-coords" style="width:95px;">
|
||||
<option value="none">Not shown</option>
|
||||
<option value="deg">Degrees</option>
|
||||
<option value="dms">D.M.S</option>
|
||||
</select>
|
||||
<i class="fa fa-th" style="margin-left:22px;"></i> Graticule
|
||||
<select id="node-input-showgrid" style="width:95px;">
|
||||
<option value="false">Not shown</option>
|
||||
<option value="true">Visible</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-path"><i class="fa fa-globe"></i> Web Path</label>
|
||||
<input type="text" id="node-input-path" placeholder="worldmap">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-file"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="name">
|
||||
</div>
|
||||
<div class="form-tips">Set <i>Cluster when</i> to 0 to disable clustering of points.<br/>If <i>Path</i> is left empty,
|
||||
then by default <code>⌘⇧m</code> - <code>ctrl-shift-m</code> will load the map in a new tab.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="ui_worldmap">
|
||||
<p>Plots "things" on a map in Node-RED Dashboard. Needs an internet connection.</p>
|
||||
<p>It is possible to instantiate multiple worldmap nodes but each one must be given a unique
|
||||
path. Worldmap-in nodes are matched to worldmap nodes by having exactly the same path.
|
||||
<p>Shortcut - <code>⌘⇧m</code> - ctrl-shift-m to jump to the default Map (<tt>/worldmap</tt>).</p>
|
||||
<p>The minimum <code>msg.payload</code> must contain <code>name</code>, <code>lat</code> and <code>lon</code> properties, e.g.</p>
|
||||
<pre>{"name":"Joe", "lat":51, "lon":-1.05}</pre>
|
||||
<p><code>name</code> must be a unique identifier.</p>
|
||||
<p>Optional properties include</p>
|
||||
<ul>
|
||||
<li><code>layer</code> : specify a layer on the map to add marker to.</li>
|
||||
<li><code>speed</code> : combined with bearing, draws a vector.</li>
|
||||
<li><code>bearing</code> : combined with speed, draws a vector.</li>
|
||||
<li><code>accuracy</code> : combined with bearing, draws a polygon of possible direction.</li>
|
||||
<li><code>icon</code> : <a href="https://fontawesome.com/v4.7.0/icons/" target="_new">font awesome</a> icon name or <a href="https://github.com/dceejay/RedMap/blob/master/emojilist.md" target="_new">:emoji name:</a>, or url of icon image.</li>
|
||||
<li><code>iconColor</code> : standard CSS color name or #rrggbb hex value.</li>
|
||||
<li><code>SIDC</code> : NATO symbology code (instead of icon).</li>
|
||||
<li><code>label</code> : permanent label next to marker, or</li>
|
||||
<li><code>tooltip</code> : hover over text for marker. (alternative to label)</li>
|
||||
<li><code>bulding</code> : OSMBuildings GeoJSON object.</li>
|
||||
<li><code>ttl</code> : time to live of an individual marker before deletion.</li>
|
||||
<li><code>photoUrl</code> : adds an image pointed at by the url to the popup box.</li>
|
||||
<li><code>videoUrl</code> : adds an mp4 (320x240) pointed at by the url to Popup box</li>
|
||||
<li><code>weblink</code> : link to an external web page.</li>
|
||||
<li><code>deleted</code> : set to <i>true</i> to remove the named marker. (default false)</li>
|
||||
</ul>
|
||||
<p>Any other sub-properties of <code>msg.payload</code> will be added to the marker popup text box as extra information.</p>
|
||||
<p>Icons of type <i>plane</i>, <i>ship</i>, <i>car</i>, <i>uav</i> or <i>arrow</i> will use built in SVG icons that align to the
|
||||
<code>bearing</code> value.</p>
|
||||
<p>Font Awesome (<a href="https://fontawesome.com/v4.7.0/icons/" target="_new">fa-icons 4.7</a>) can also be used, as can
|
||||
NATO symbology codes (SIDC), or <a href="https://github.com/dceejay/RedMap/blob/master/emojilist.md" target="_new">:emoji name:</a>,
|
||||
or the url of a small icon image (32x32)</p>
|
||||
<p>See the <a href="https://www.npmjs.com/package/node-red-contrib-web-worldmap" target="_new">README</a> for further
|
||||
details and examples of icons and commands for drawing <b>lines</b> and <b>areas</b>, and to <b>add layers</b> and
|
||||
to <b>control</b> the map remotely, including how to use a local map server.</p>
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
var lnk = document.location.host+RED.settings.httpNodeRoot+"/worldmap";
|
||||
lnk = lnk.replace(new RegExp('\/{1,}','g'),'/');
|
||||
@ -211,6 +358,78 @@ then by default <code>⌘⇧m</code> - <code>ctrl-shift-m</code> will load the m
|
||||
$("#node-input-cluster").spinner({min:0, max:19});
|
||||
}
|
||||
});
|
||||
|
||||
RED.nodes.registerType('ui_worldmap',{
|
||||
category: 'dashboard',
|
||||
color: 'rgb( 63, 173, 181)',
|
||||
defaults: {
|
||||
group: {type: 'ui_group', required:true},
|
||||
order: {value: 0},
|
||||
width: {
|
||||
value: 0,
|
||||
validate: function(v) {
|
||||
var valid = true
|
||||
var width = v||0;
|
||||
var currentGroup = $('#node-input-group').val()|| this.group;
|
||||
var groupNode = RED.nodes.node(currentGroup);
|
||||
valid = !groupNode || +width <= +groupNode.width;
|
||||
$("#node-input-size").toggleClass("input-error",!valid);
|
||||
return valid;
|
||||
}},
|
||||
height: {value: 0},
|
||||
name: {value:""},
|
||||
lat: {value:""},
|
||||
lon: {value:""},
|
||||
zoom: {value:""},
|
||||
layer: {value:""},
|
||||
cluster: {value:""},
|
||||
maxage: {value:""},
|
||||
usermenu: {value:"hide"},
|
||||
layers: {value:"hide"},
|
||||
panit: {value:"false"},
|
||||
panlock: {value:"false"},
|
||||
zoomlock: {value:"false"},
|
||||
hiderightclick: {value:"false"},
|
||||
coords: {value:"false"},
|
||||
showgrid: {value:"false"},
|
||||
path: {value:"/worldmap"}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "white-globe.png",
|
||||
align: "right",
|
||||
paletteLabel: "worldmap",
|
||||
label: function() {
|
||||
return this.name||(this.path.charAt(0)==='/'?this.path.substr(1):this.path)||"world map";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
info: function() {
|
||||
return 'The map can also be found [here]('+RED.settings.httpNodeRoot.slice(0,-1)+this.path+').';
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$("#node-input-size").elementSizer({
|
||||
width: "#node-input-width",
|
||||
height: "#node-input-height",
|
||||
group: "#node-input-group"
|
||||
});
|
||||
if ($("#node-input-path").val() === undefined) {
|
||||
$("#node-input-path").val("worldmap");
|
||||
this.path = "worldmap";
|
||||
}
|
||||
if ($("#node-input-hiderightclick").val() === null) {
|
||||
$("#node-input-hiderightclick").val("false");
|
||||
this.hiderightclick = "false";
|
||||
}
|
||||
if ($("#node-input-coords").val() === null) {
|
||||
$("#node-input-coords").val("none");
|
||||
this.coords = "none";
|
||||
}
|
||||
$("#node-input-zoom").spinner({min:0, max:18});
|
||||
$("#node-input-cluster").spinner({min:0, max:19});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="worldmap in">
|
||||
|
142
worldmap.js
142
worldmap.js
@ -25,34 +25,33 @@ module.exports = function(RED) {
|
||||
// add the cgi module for serving local maps....
|
||||
RED.httpNode.use("/cgi-bin/mapserv", require('cgi')(__dirname + '/mapserv'));
|
||||
|
||||
var WorldMap = function(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.lat = n.lat || "";
|
||||
this.lon = n.lon || "";
|
||||
this.zoom = n.zoom || "";
|
||||
this.layer = n.layer || "";
|
||||
this.cluster = n.cluster || "";
|
||||
this.maxage = n.maxage || "";
|
||||
if (n.maxage == 0) { this.maxage = "0"; }
|
||||
this.showmenu = n.usermenu || "show";
|
||||
this.layers = n.layers || "show";
|
||||
this.panlock = n.panlock || "false";
|
||||
this.zoomlock = n.zoomlock || "false";
|
||||
this.panit = n.panit || "false";
|
||||
this.hiderightclick = n.hiderightclick || "false";
|
||||
this.coords = n.coords || "none";
|
||||
this.showgrid = n.showgrid || "false";
|
||||
this.path = n.path || "/worldmap";
|
||||
if (this.path.charAt(0) != "/") { this.path = "/" + this.path; }
|
||||
if (!sockets[this.path]) {
|
||||
var libPath = path.posix.join(RED.settings.httpNodeRoot, this.path, 'leaflet', 'sockjs.min.js');
|
||||
var sockPath = path.posix.join(RED.settings.httpNodeRoot,this.path,'socket');
|
||||
sockets[this.path] = sockjs.createServer({prefix:sockPath, sockjs_url:libPath, log:function() { return; }});
|
||||
sockets[this.path].installHandlers(RED.server);
|
||||
function worldMap(node, n) {
|
||||
RED.nodes.createNode(node,n);
|
||||
node.lat = n.lat || "";
|
||||
node.lon = n.lon || "";
|
||||
node.zoom = n.zoom || "";
|
||||
node.layer = n.layer || "";
|
||||
node.cluster = n.cluster || "";
|
||||
node.maxage = n.maxage || "";
|
||||
if (n.maxage == 0) { node.maxage = "0"; }
|
||||
node.showmenu = n.usermenu || "show";
|
||||
node.layers = n.layers || "show";
|
||||
node.panlock = n.panlock || "false";
|
||||
node.zoomlock = n.zoomlock || "false";
|
||||
node.panit = n.panit || "false";
|
||||
node.hiderightclick = n.hiderightclick || "false";
|
||||
node.coords = n.coords || "none";
|
||||
node.showgrid = n.showgrid || "false";
|
||||
node.path = n.path || "/worldmap";
|
||||
if (node.path.charAt(0) != "/") { noed.path = "/" + node.path; }
|
||||
if (!sockets[node.path]) {
|
||||
var libPath = path.posix.join(RED.settings.httpNodeRoot, node.path, 'leaflet', 'sockjs.min.js');
|
||||
var sockPath = path.posix.join(RED.settings.httpNodeRoot,node.path,'socket');
|
||||
sockets[node.path] = sockjs.createServer({prefix:sockPath, sockjs_url:libPath, log:function() { return; }});
|
||||
sockets[node.path].installHandlers(RED.server);
|
||||
}
|
||||
//this.log("Serving "+__dirname+" as "+this.path);
|
||||
this.log("started at "+this.path);
|
||||
var node = this;
|
||||
//node.log("Serving "+__dirname+" as "+node.path);
|
||||
node.log("started at "+node.path);
|
||||
var clients = {};
|
||||
RED.httpNode.use(compression());
|
||||
RED.httpNode.use(node.path, express.static(__dirname + '/worldmap'));
|
||||
@ -108,7 +107,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
}
|
||||
clients = {};
|
||||
sockets[this.path].removeListener('connection', callback);
|
||||
sockets[node.path].removeListener('connection', callback);
|
||||
for (var i=0; i < RED.httpNode._router.stack.length; i++) {
|
||||
var r = RED.httpNode._router.stack[i];
|
||||
if ((r.name === "serveStatic") && (r.regexp.test(node.path))) {
|
||||
@ -117,11 +116,95 @@ module.exports = function(RED) {
|
||||
}
|
||||
node.status({});
|
||||
});
|
||||
sockets[this.path].on('connection', callback);
|
||||
sockets[node.path].on('connection', callback);
|
||||
}
|
||||
|
||||
var WorldMap = function(n) {
|
||||
worldMap(this, n);
|
||||
}
|
||||
RED.nodes.registerType("worldmap",WorldMap);
|
||||
|
||||
|
||||
function HTML(ui, config) {
|
||||
var width = config.width;
|
||||
if (width == 0) {
|
||||
var group = RED.nodes.getNode(config.group);
|
||||
if (group) {
|
||||
width = group.config.width;
|
||||
}
|
||||
}
|
||||
var height = config.height;
|
||||
if (height == 0) {
|
||||
height = 10;
|
||||
}
|
||||
var size = ui.getSizes();
|
||||
var frameWidth = (size.sx +size.cx) *width -size.cx -10;
|
||||
var frameHeight = (size.sy +size.cy) *height -size.cy -10;
|
||||
var url = encodeURI(config.path);
|
||||
var html = `
|
||||
<div>
|
||||
<iframe src="${url}" width="${frameWidth}px" height="${frameHeight}px" style="border: none;"></iframe>
|
||||
</div>
|
||||
`;
|
||||
return html;
|
||||
}
|
||||
|
||||
function checkConfig(node, conf) {
|
||||
if (!conf || !conf.hasOwnProperty("group")) {
|
||||
node.error("no group");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
var ui = undefined;
|
||||
|
||||
function UIWorldMap(config) {
|
||||
try {
|
||||
var node = this;
|
||||
if(ui === undefined) {
|
||||
ui = RED.require("node-red-dashboard")(RED);
|
||||
}
|
||||
worldMap(node, config);
|
||||
var done = null;
|
||||
if (checkConfig(node, config)) {
|
||||
var html = HTML(ui, config);
|
||||
done = ui.addWidget({
|
||||
node: node,
|
||||
order: config.order,
|
||||
group: config.group,
|
||||
width: config.width,
|
||||
height: config.height,
|
||||
format: html,
|
||||
templateScope: "local",
|
||||
emitOnlyNewValues: false,
|
||||
forwardInputMessages: false,
|
||||
storeFrontEndInputAsState: false,
|
||||
convertBack: function (value) {
|
||||
return value;
|
||||
},
|
||||
beforeEmit: function(msg, value) {
|
||||
return { msg: { items: value } };
|
||||
},
|
||||
beforeSend: function (msg, orig) {
|
||||
if (orig) { return orig.msg; }
|
||||
},
|
||||
initController: function($scope, events) {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
node.on("close", function() {
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("ui_worldmap", UIWorldMap);
|
||||
|
||||
var WorldMapIn = function(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.path = n.path || "/worldmap";
|
||||
@ -164,7 +247,6 @@ module.exports = function(RED) {
|
||||
}
|
||||
RED.nodes.registerType("worldmap in",WorldMapIn);
|
||||
|
||||
|
||||
var WorldMapTracks = function(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.depth = parseInt(Number(n.depth) || 20);
|
||||
|
Loading…
Reference in New Issue
Block a user