multimap (#51)

* fix fa-icons links

* first go at multimaps

* Handle cleaning up old static routes

Not great but seems to be the best way...
Issue #40

* add some colour buttons

* Add topojson countries layer for disconnected use
This commit is contained in:
Dave Conway-Jones 2018-10-12 23:58:20 +01:00 committed by GitHub
parent 5f3812a368
commit 3992e05cdc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 106 additions and 47 deletions

View File

@ -55,7 +55,7 @@ Optional properties include
- **speed** : combined with bearing, draws a vector. - **speed** : combined with bearing, draws a vector.
- **bearing** : combined with speed, draws a vector. - **bearing** : combined with speed, draws a vector.
- **accuracy** : combined with bearing, draws a polygon of possible direction. - **accuracy** : combined with bearing, draws a polygon of possible direction.
- **icon** : <a href="http://fortawesome.github.io/Font-Awesome/icons/" target="mapinfo">font awesome</a> icon name. - **icon** : <a href="https://fontawesome.com/v4.7.0/icons/" target="mapinfo">font awesome</a> icon name.
- **iconColor** : Standard CSS colour name or #rrggbb hex value. - **iconColor** : Standard CSS colour name or #rrggbb hex value.
- **SIDC** : NATO symbology code (instead of icon). See below. - **SIDC** : NATO symbology code (instead of icon). See below.
- **building** : OSMbulding GeoJSON feature set to add 2.5D buildings to buildings layer. See below. - **building** : OSMbulding GeoJSON feature set to add 2.5D buildings to buildings layer. See below.
@ -72,7 +72,7 @@ Any other `msg.payload` properties will be added to the icon popup text box.
### Icons ### Icons
You may select any of the Font Awesome set of [icons](http://fortawesome.github.io/Font-Awesome/icons/). You may select any of the Font Awesome set of [icons](https://fontawesome.com/v4.7.0/icons/).
If you use the name without the fa- prefix (eg `male`) you will get the icon inside a generic marker shape. If you use the fa- prefix (eg `fa-male`) you will get the icon on its own. If you use the name without the fa- prefix (eg `male`) you will get the icon inside a generic marker shape. If you use the fa- prefix (eg `fa-male`) you will get the icon on its own.
There are also several special icons... There are also several special icons...

View File

@ -1,6 +1,6 @@
{ {
"name": "node-red-contrib-web-worldmap", "name": "node-red-contrib-web-worldmap",
"version": "1.4.6", "version": "1.4.7",
"description": "A Node-RED node to provide a web page of a world map for plotting things on.", "description": "A Node-RED node to provide a web page of a world map for plotting things on.",
"dependencies": { "dependencies": {
"cgi": "0.3.1", "cgi": "0.3.1",

View File

@ -35,10 +35,10 @@
<option value="Esri Satellite">ESRI Satellite</option> <option value="Esri Satellite">ESRI Satellite</option>
<option value="Esri Terrain">ESRI Terrain</option> <option value="Esri Terrain">ESRI Terrain</option>
<option value="Esri Ocean">ESRI Ocean</option> <option value="Esri Ocean">ESRI Ocean</option>
<option value="Mapsurfer">Mapsurfer</option>
<option value="MapQuest OSM">MapQuest OSM</option>
<option value="Nat Geo">National Geographic</option> <option value="Nat Geo">National Geographic</option>
<option value="UK OS Opendata">UK OS Opendata</option> <option value="UK OS Opendata">UK OS Opendata</option>
<option value="Hike Bike">Hike Bike OSM</option>
<option value="Terrain">Terrain</option>
</select> </select>
</div> </div>
<div class="form-row"> <div class="form-row">
@ -68,16 +68,20 @@
<option value="true">True</option> <option value="true">True</option>
</select> </select>
</div> </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"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-file"></i> Name</label> <label for="node-input-name"><i class="fa fa-file"></i> Name</label>
<input type="text" id="node-input-name" placeholder="name"> <input type="text" id="node-input-name" placeholder="name">
</div> </div>
<div class="form-tips">Tip: By default <code>⌘⇧m</code> - <code>ctrl-shift-m</code> will load the map in a new tab.</div> <!-- <div class="form-tips">Tip: By default <code>⌘⇧m</code> - <code>ctrl-shift-m</code> will load the map in a new tab.</div>-->
</script> </script>
<script type="text/x-red" data-help-name="worldmap"> <script type="text/x-red" data-help-name="worldmap">
<p>Plots "things" on a web map. Needs an internet connection.</p> <p>Plots "things" on a web map. Needs an internet connection.</p>
<p>Shortcut - <code>⌘⇧m</code> - ctrl-shift-m to jump to Map.</p> <!-- <p>Shortcut - <code>⌘⇧m</code> - ctrl-shift-m to jump to Map.</p> -->
<p>The minimum <code>msg.payload</code> must contain <code>name</code>, <code>lat</code> and <code>lon</code> properties, e.g.</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> <pre>{name:"Joe", lat:51, lon:-1.05}</pre>
<p><code>name</code> must be a unique identifier.</p> <p><code>name</code> must be a unique identifier.</p>
@ -108,8 +112,7 @@
<script type="text/javascript"> <script type="text/javascript">
var lnk = document.location.host+RED.settings.httpNodeRoot+"/worldmap"; var lnk = document.location.host+RED.settings.httpNodeRoot+"/worldmap";
var re = new RegExp('\/{1,}','g'); lnk = lnk.replace(new RegExp('\/{1,}','g'),'/');
lnk = lnk.replace(re,'/');
if (!RED.hasOwnProperty("actions")) { if (!RED.hasOwnProperty("actions")) {
RED.keyboard.add("*",/* m */ 77,{ctrl:true, shift:true},function() { window.open(document.location.protocol+"//"+lnk) }); RED.keyboard.add("*",/* m */ 77,{ctrl:true, shift:true},function() { window.open(document.location.protocol+"//"+lnk) });
} }
@ -130,7 +133,8 @@
maxage: {value:""}, maxage: {value:""},
usermenu: {value:"show"}, usermenu: {value:"show"},
layers: {value:"show"}, layers: {value:"show"},
panit: {value:"false"} panit: {value:"false"},
path: {value:"/worldmap"}
}, },
inputs:1, inputs:1,
outputs:0, outputs:0,
@ -143,16 +147,24 @@
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
}, },
info: function() { info: function() {
return 'The map can be found [here]('+RED.settings.httpNodeRoot.slice(0,-1)+'/worldmap).'; return 'The map can be found [here]('+RED.settings.httpNodeRoot.slice(0,-1)+this.path+').';
}, },
oneditprepare: function() { oneditprepare: function() {
$( "#node-input-zoom" ).spinner({min:0, max:18}); if ($("#node-input-path").val() === undefined) {
$( "#node-input-cluster" ).spinner({min:0, max:19}); $("#node-input-path").val("worldmap");
this.path = "worldmap";
}
$("#node-input-zoom").spinner({min:0, max:18});
$("#node-input-cluster").spinner({min:0, max:19});
} }
}); });
</script> </script>
<script type="text/x-red" data-template-name="worldmap in"> <script type="text/x-red" data-template-name="worldmap in">
<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"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-file"></i> Name</label> <label for="node-input-name"><i class="fa fa-file"></i> Name</label>
<input type="text" id="node-input-name" placeholder="name"> <input type="text" id="node-input-name" placeholder="name">
@ -168,7 +180,8 @@
category: 'location', category: 'location',
color:"darksalmon", color:"darksalmon",
defaults: { defaults: {
name: {value:""} name: {value:""},
path: {value:"/worldmap"}
}, },
inputs:0, inputs:0,
outputs:1, outputs:1,
@ -180,7 +193,7 @@
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
}, },
info: function() { info: function() {
return 'The map can be found [here]('+RED.settings.httpNodeRoot.slice(0,-1)+'/worldmap).'; return 'The map can be found [here]('+RED.settings.httpNodeRoot.slice(0,-1)+this.path+').';
} }
}); });
</script> </script>

View File

@ -19,15 +19,12 @@ module.exports = function(RED) {
var path = require("path"); var path = require("path");
var express = require("express"); var express = require("express");
var sockjs = require('sockjs'); var sockjs = require('sockjs');
var socket; var sockets = {};
// add the cgi module for serving local maps....
RED.httpNode.use("/cgi-bin/mapserv", require('cgi')(__dirname + '/mapserv'));
var WorldMap = function(n) { var WorldMap = function(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
if (!socket) {
var fullPath = path.posix.join(RED.settings.httpNodeRoot, 'worldmap', 'leaflet', 'sockjs.min.js');
socket = sockjs.createServer({sockjs_url:fullPath, log:function() {}, transports:"xhr-polling"});
socket.installHandlers(RED.server, {prefix:path.posix.join(RED.settings.httpNodeRoot,'/worldmap/socket')});
}
this.lat = n.lat || ""; this.lat = n.lat || "";
this.lon = n.lon || ""; this.lon = n.lon || "";
this.zoom = n.zoom || ""; this.zoom = n.zoom || "";
@ -37,12 +34,17 @@ module.exports = function(RED) {
this.showmenu = n.usermenu || "show"; this.showmenu = n.usermenu || "show";
this.panit = n.panit || "false"; this.panit = n.panit || "false";
this.layers = n.layers || "show"; this.layers = n.layers || "show";
this.path = n.path || "/worldmap";
if (!sockets[this.path]) {
var fullPath = path.posix.join(RED.settings.httpNodeRoot, this.path, 'leaflet', 'sockjs.min.js');
sockets[this.path] = sockjs.createServer({sockjs_url:fullPath, log:function() {}, transports:"xhr-polling"});
sockets[this.path].installHandlers(RED.server, {prefix:path.posix.join(RED.settings.httpNodeRoot,this.path,'socket')});
}
var node = this; var node = this;
var clients = {}; var clients = {};
//node.log("Serving map from "+__dirname+" as "+RED.settings.httpNodeRoot.slice(0,-1)+"/worldmap"); //node.log("Serving map from "+__dirname+" as "+RED.settings.httpNodeRoot.slice(0,-1)+node;path);
RED.httpNode.use("/worldmap", express.static(__dirname + '/worldmap')); RED.httpNode.use(node.path, express.static(__dirname + '/worldmap'));
// add the cgi module for serving local maps....
RED.httpNode.use("/cgi-bin/mapserv", require('cgi')(__dirname + '/mapserv'));
var callback = function(client) { var callback = function(client) {
//client.setMaxListeners(0); //client.setMaxListeners(0);
@ -89,20 +91,27 @@ module.exports = function(RED) {
clients[c].end(); clients[c].end();
} }
} }
socket.removeListener('connection', callback); sockets[this.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))) {
RED.httpNode._router.stack.splice(i, 1)
}
}
node.status({}); node.status({});
}); });
socket.on('connection', callback); sockets[this.path].on('connection', callback);
} }
RED.nodes.registerType("worldmap",WorldMap); RED.nodes.registerType("worldmap",WorldMap);
var WorldMapIn = function(n) { var WorldMapIn = function(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
if (!socket) { this.path = n.path || "/worldmap";
var fullPath = path.posix.join(RED.settings.httpNodeRoot, 'worldmap', 'leaflet', 'sockjs.min.js'); if (!sockets[this.path]) {
socket = sockjs.createServer({sockjs_url:fullPath, prefix:path.posix.join(RED.settings.httpNodeRoot,'/worldmap/socket')}); var fullPath = path.posix.join(RED.settings.httpNodeRoot, this.path, 'leaflet', 'sockjs.min.js');
socket.installHandlers(RED.server); sockets[this.path] = sockjs.createServer({sockjs_url:fullPath, prefix:path.posix.join(RED.settings.httpNodeRoot,this.path,'socket')});
sockets[this.path].installHandlers(RED.server);
} }
var node = this; var node = this;
var clients = {}; var clients = {};
@ -113,7 +122,7 @@ module.exports = function(RED) {
node.status({fill:"green",shape:"dot",text:"connected "+Object.keys(clients).length}); node.status({fill:"green",shape:"dot",text:"connected "+Object.keys(clients).length});
client.on('data', function(message) { client.on('data', function(message) {
message = JSON.parse(message); message = JSON.parse(message);
node.send({payload:message, topic:"worldmap", _sessionid:client.id}); node.send({payload:message, topic:node.path.substr(1), _sessionid:client.id});
}); });
client.on('close', function() { client.on('close', function() {
delete clients[client.id]; delete clients[client.id];
@ -128,10 +137,10 @@ module.exports = function(RED) {
clients[c].end(); clients[c].end();
} }
} }
socket.removeListener('connection', callback); sockets[this.path].removeListener('connection', callback);
node.status({}); node.status({});
}); });
socket.on('connection', callback); sockets[this.path].on('connection', callback);
} }
RED.nodes.registerType("worldmap in",WorldMapIn); RED.nodes.registerType("worldmap in",WorldMapIn);

View File

@ -171,3 +171,9 @@ a {
-o-transform-origin:0 100%; -o-transform-origin:0 100%;
transform-origin:0 100%; transform-origin:0 100%;
} }
.wm-red { color: #F56361; }
.wm-blue { color: #96CCE1; }
.wm-green { color: #ADD5A6; }
.wm-yellow { color: #F5EF91; }
.wm-black { color: #444444; }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -212,7 +212,15 @@ if ( window.localStorage.hasOwnProperty("maxage") ) {
// Create the Initial Map object. // Create the Initial Map object.
map = new L.map('map').setView(startpos, startzoom); map = new L.map('map').setView(startpos, startzoom);
var menuButton = L.easyButton( 'fa-bars fa-lg', function() { toggleMenu(); }, "Toggle menu", "topright"); var menuButton = L.easyButton({states:[{icon:'fa-bars fa-lg', onClick:function() { toggleMenu(); }, title:'Toggle menu'}], position:"topright"});
//var colorPickButton = L.easyButton({states:[{icon:'fa-tint fa-lg', onClick:function() { console.log("PICK"); }, title:'Pick Colour'}]});
var redButton = L.easyButton('fa-square wm-red', function(btn) { console.log("RED",btn); })
var blueButton = L.easyButton('fa-square wm-blue', function(btn) { console.log("BLUE",btn); })
var greenButton = L.easyButton('fa-square wm-green', function(btn) { console.log("GREEN",btn); })
var yellowButton = L.easyButton('fa-square wm-yellow', function(btn) { console.log("YELLOW",btn); })
var blackButton = L.easyButton('fa-square wm-black', function(btn) { console.log("BLACK",btn); })
var colorControl = L.easyBar([redButton,blueButton,greenButton,yellowButton,blackButton]);
// Move some bits around if in an iframe // Move some bits around if in an iframe
if (window.self !== window.top) { if (window.self !== window.top) {
@ -264,6 +272,7 @@ if (!inIframe) {
} }
else { if (showUserMenu) { menuButton.addTo(map); } } else { if (showUserMenu) { menuButton.addTo(map); } }
// Handle the dialog for popup help // Handle the dialog for popup help
var dialog = document.querySelector('dialog'); var dialog = document.querySelector('dialog');
dialogPolyfill.registerDialog(dialog); dialogPolyfill.registerDialog(dialog);
@ -436,8 +445,11 @@ map.on('overlayadd', function(e) {
layers["_daynight"].addLayer(L.terminator()); layers["_daynight"].addLayer(L.terminator());
} }
if (e.name == "drawing") { if (e.name == "drawing") {
map.addControl(drawControl);
overlays["drawing"].bringToFront(); overlays["drawing"].bringToFront();
// And the actual draw controls
map.addControl(drawControl);
// Add the color change button
//colorControl.addTo(map);
} }
ws.send(JSON.stringify({action:"addlayer", name:e.name})); ws.send(JSON.stringify({action:"addlayer", name:e.name}));
}); });
@ -452,6 +464,7 @@ map.on('overlayremove', function(e) {
layers["_daynight"].clearLayers(); layers["_daynight"].clearLayers();
} }
if (e.name == "drawing") { if (e.name == "drawing") {
map.removeControl(colorControl);
map.removeControl(drawControl); map.removeControl(drawControl);
} }
//else console.log("layer del :",e.name); //else console.log("layer del :",e.name);
@ -623,7 +636,6 @@ var NLS_OS_opendata = L.tileLayer('https://geo.nls.uk/maps/opendata/{z}/{x}/{y}.
}); });
basemaps["UK OS Opendata"] = NLS_OS_opendata; basemaps["UK OS Opendata"] = NLS_OS_opendata;
var HikeBike_HikeBike = L.tileLayer('http://{s}.tiles.wmflabs.org/hikebike/{z}/{x}/{y}.png', { var HikeBike_HikeBike = L.tileLayer('http://{s}.tiles.wmflabs.org/hikebike/{z}/{x}/{y}.png', {
maxZoom: 19, maxZoom: 19,
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>' attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
@ -658,8 +670,14 @@ basemaps["Terrain"] = new L.StamenTileLayer('terrain');
// Nice watercolour based maps by Stamen Design // Nice watercolour based maps by Stamen Design
basemaps["Watercolor"] = new L.StamenTileLayer('watercolor'); basemaps["Watercolor"] = new L.StamenTileLayer('watercolor');
// Now add the overlays // Now add the overlays
// Add the countries (world-110m) for offline use
var customTopoLayer = L.geoJson(null, {style: {color:"blue", weight:2, fillColor:"#cf6", fillOpacity:0.04}});
layers["_countries"] = omnivore.topojson('images/world-50m-flat.json',null,customTopoLayer);
overlays["countries"] = layers["_countries"];
// Add the day/night overlay // Add the day/night overlay
layers["_daynight"] = new L.LayerGroup(); layers["_daynight"] = new L.LayerGroup();
overlays["day/night"] = layers["_daynight"]; overlays["day/night"] = layers["_daynight"];

View File

@ -111,19 +111,22 @@ L.Control.EasyButton = L.Control.extend({
// icon: 'fa-circle', // wrapped with <a> // icon: 'fa-circle', // wrapped with <a>
// } // }
leafletClasses: true // use leaflet styles for the button leafletClasses: true, // use leaflet styles for the button
tagName: 'button',
}, },
initialize: function(icon, onClick, title, position){ initialize: function(icon, onClick, title, id){
// Added easy position input - DCJ
this.options.position = position || 'topleft';
// clear the states manually // clear the states manually
this.options.states = []; this.options.states = [];
// add id to options
if(id != null){
this.options.id = id;
}
// storage between state functions // storage between state functions
this.storage = {}; this.storage = {};
@ -164,18 +167,25 @@ L.Control.EasyButton = L.Control.extend({
_buildButton: function(){ _buildButton: function(){
this.button = L.DomUtil.create('button', ''); this.button = L.DomUtil.create(this.options.tagName, '');
// the next three if statements should be collapsed into the options
// when it's time for breaking changes.
if (this.tagName === 'button') {
this.button.type = 'button';
}
if (this.options.id ){ if (this.options.id ){
this.button.id = this.options.id; this.button.id = this.options.id;
} }
if (this.options.leafletClasses){ if (this.options.leafletClasses){
L.DomUtil.addClass(this.button, 'easy-button-button leaflet-bar-part'); L.DomUtil.addClass(this.button, 'easy-button-button leaflet-bar-part leaflet-interactive');
} }
// don't let double clicks get to the map // don't let double clicks and mousedown get to the map
L.DomEvent.addListener(this.button, 'dblclick', L.DomEvent.stop); L.DomEvent.addListener(this.button, 'dblclick', L.DomEvent.stop);
L.DomEvent.addListener(this.button, 'mousedown', L.DomEvent.stop);
// take care of normal clicks // take care of normal clicks
L.DomEvent.addListener(this.button,'click', function(e){ L.DomEvent.addListener(this.button,'click', function(e){
@ -309,7 +319,7 @@ L.Control.EasyButton = L.Control.extend({
}); });
L.easyButton = function(/* args will pass automatically */){ L.easyButton = function(/* args will pass automatically */){
var args = Array.prototype.concat.apply([L.Control.EasyButton],arguments) var args = Array.prototype.concat.apply([L.Control.EasyButton],arguments);
return new (Function.prototype.bind.apply(L.Control.EasyButton, args)); return new (Function.prototype.bind.apply(L.Control.EasyButton, args));
}; };

View File

@ -1,10 +1,11 @@
CACHE MANIFEST CACHE MANIFEST
# date: Oct 3rd 2018 - v1.4.6 # date: Oct 12th 2018 - v1.5.0
CACHE: CACHE:
index.html index.html
favicon.ico favicon.ico
images/node-red.png images/node-red.png
images/world-50m-flat.json
css/map.css css/map.css
leaflet/L.Terminator.js leaflet/L.Terminator.js
leaflet/Leaflet.fullscreen.min.js leaflet/Leaflet.fullscreen.min.js