Add screen split capability

This commit is contained in:
Dave Conway-Jones 2018-10-22 21:01:24 +01:00
parent 4600491b0a
commit 0e7484263c
No known key found for this signature in database
GPG Key ID: 9E7F9C73F5168CD4
8 changed files with 562 additions and 31 deletions

View File

@ -1,5 +1,6 @@
### Change Log for Node-RED Worldmap
- v1.5.4 - allow remote update of the split position via `msg.command.split`
- v1.5.3 - Add side by side mode (via `msg.command` only).
- v1.5.2 - Make manually added icons moveable by default.
- v1.5.0 - Add multi-map capability - can now have multiple map endpoints. Issue #40 PR #51

View File

@ -9,6 +9,7 @@ map web page for plotting "things" on.
### Updates
- v1.5.4 - allow remote update of the split position via `msg.command.split`
- v1.5.3 - Add side by side mode (via `msg.command` only).
- v1.5.2 - Make manually added icons moveable by default.
- v1.5.0 - Add multi-map capability - can now have multiple map endpoints.
@ -247,6 +248,7 @@ Optional properties include
- **showlayer** - show the named overlay - `{command:{showlayer:"foo"}}`
- **hidelayer** - hide the named overlay - `{command:{hidelayer:"bar"}}`
- **side** - add a second map alongside with slide between them. Use the name of the *baselayer* to add - or "none" to remove the control. - `{command:{side:"Esri Satellite"}}`
- **split** - once you have split the screen - the split value is the % across the screen of the split line. - `{command:{split:50}}`
- **map** - Object containing details of a new map layer:
- **name** - name of the map base layer OR **overlay** - name of overlay layer
- **url** - url of the map layer

View File

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

View File

@ -29,6 +29,7 @@
<link rel="stylesheet",type="text/css" href="leaflet/leaflet.measurecontrol.css">
<link rel="stylesheet",type="text/css" href="leaflet/easy-button.css">
<link rel="stylesheet",type="text/css" href="leaflet/leaflet.fullscreen.css">
<link rel="stylesheet",type="text/css" href="leaflet/Leaflet.Dialog.css">
<link rel="stylesheet",type="text/css" href="leaflet/leaflet-velocity.min.css">
<link rel="shortcut icon" type="image/ico" href="favicon.ico"/>
@ -45,6 +46,7 @@
<script type="text/javascript" src="leaflet/leaflet.measurecontrol.js"></script>
<script type="text/javascript" src="leaflet/easy-button.js"></script>
<script type="text/javascript" src="leaflet/Leaflet.fullscreen.min.js"></script>
<script type="text/javascript" src="leaflet/Leaflet.Dialog.js"></script>
<script type="text/javascript" src="leaflet/l.ellipse.min.js"></script>
<script type="text/javascript" src="leaflet/milsymbol.js"></script>
<script type="text/javascript" src="leaflet/nvg.js"></script>
@ -167,7 +169,7 @@ var connect = function() {
};
ws.onmessage = function(e) {
var data = JSON.parse(e.data);
//console.log("DATA",data);
//console.log("DATA",typeof data,data);
if (Array.isArray(data)) {
//console.log("ARRAY");
for (var prop in data) {
@ -179,7 +181,12 @@ var connect = function() {
else {
if (data.command) { doCommand(data.command); delete data.command; }
if (data.hasOwnProperty("name")) { setMarker(data); }
else { console.log("SKIP",data); }
else if (data.hasOwnProperty("type")) { doGeojson(data); }
else {
console.log("SKIP",data);
// if (typeof data === "string") { doDialog(data); }
// else { console.log("SKIP",data); }
}
}
};
}
@ -223,7 +230,6 @@ var yellowButton = L.easyButton('fa-square wm-yellow', function(btn) { console.l
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
if (window.self !== window.top) {
console.log("IN an iframe");
@ -345,6 +351,18 @@ function doTidyUp(l) {
}
}
// Call tidyup every {maxage} seconds - default 10 mins
var stale = null;
function setMaxAge() {
maxage = document.getElementById('maxage').value;
if (stale) { clearInterval(stale); }
//if (maxage > 0) {
stale = setInterval( function() { doTidyUp() }, 20000); // every 20 secs
//} //every minute
//console.log("Stale time set :",maxage+"s");
}
setMaxAge();
// move the daylight / nighttime boundary (if enabled) every minute
function moveTerminator() { // if terminator line plotted move it every minute
if (layers["_daynight"].getLayers().length > 0) {
@ -352,26 +370,13 @@ function moveTerminator() { // if terminator line plotted move it every minute
layers["_daynight"].addLayer(L.terminator());
}
}
setInterval( function() {moveTerminator()}, 60000 );
// Call tidyup every {maxage} seconds - default 10 mins
var stale = null;
function setMaxAge() {
maxage = document.getElementById('maxage').value;
if (stale) { clearInterval(stale); }
//if (maxage > 0) {
stale = setInterval( function() {doTidyUp()}, 20000); // every 20 secs
//} //every minute
//console.log("Stale time set :",maxage+"s");
}
setMaxAge();
setInterval( function() { moveTerminator() }, 60000 );
function setCluster(v) {
clusterAt = v || 0;
console.log("clusterAt set:",clusterAt);
showMapCurrentZoom();
}
//setCluster();
// Search for markers with names of ....
function doSearch() {
@ -439,7 +444,6 @@ map.on('overlayadd', function(e) {
overlays["satellite"].bringToBack();
}
if (e.name == "heatmap") { // show heatmap button when it's layer is added.
//document.getElementById("heat").style.display = 'block';
clrHeat.addTo(map);
}
if (e.name == "day/night") {
@ -458,7 +462,6 @@ map.on('overlayadd', function(e) {
map.on('overlayremove', function(e) {
if (e.name == "heatmap") { // hide heatmap button when it's layer is removed.
//document.getElementById("heat").style.display = 'none';
clrHeat.removeFrom(map);
}
if (e.name == "day/night") {
@ -515,12 +518,12 @@ map.on('zoomend', function() {
// ws.send(JSON.stringify({action:"rightclick", lat:e.latlng.lat.toFixed(5), lon:e.latlng.lng.toFixed(5)}));
//});
// single right click to add a marker
var rightmenuMap = L.popup({keepInView:true, minWidth:250}).setContent("<b>Add marker</b><br><input type='text' id='rinput' onkeydown='if (event.keyCode == 13) addThing();' placeholder='name (,icon, layer, colour)'/>");
var rclk;
var addThing = function() {
var thing = document.getElementById('rinput').value;
//console.log(thing);
map.closePopup();
var bits = thing.split(",");
var icon = (bits[1] || "circle").trim();
@ -528,22 +531,22 @@ var addThing = function() {
var colo = (bits[3] || "#910000").trim();
var drag = true;
var regi = /^[S,G,E,I,O][A-Z]{4}.*/; // if it looks like a SIDC code
var d;
var d = {action:"point", name:bits[0].trim(), layer:lay, draggable:drag, lat:rclk.lat, lon:rclk.lng};
if (regi.test(icon)) {
icon = (icon+"------------").substr(0,12);
d = {name:bits[0].trim(),layer:lay,SIDC:icon,draggable:drag,lat:rclk.lat,lon:rclk.lng};
d.SIDC = (icon+"------------").substr(0,12);
}
else {
d = {name:bits[0].trim(),layer:lay,icon:icon,iconColor:colo,draggable:drag,lat:rclk.lat,lon:rclk.lng};
d.icon = icon;
d.iconColor = colo;
}
ws.send(JSON.stringify(d));
delete d.action;
setMarker(d);
map.addLayer(layers[lay]);
d.action = "point";
ws.send(JSON.stringify(d));
}
// allow double right click to zoom out
// single right click opens a message window that sends to the socket.
// single right click opens a message window that adds a marker
var rclicked = false;
var rtout = null;
map.on('contextmenu', function(e) {
@ -769,6 +772,17 @@ layercontrol = L.control.layers(basemaps, overlays);
if (!inIframe) { layercontrol.addTo(map); }
else { showLayerMenu = false;}
// Add the dialog box for messages
var dialogue = L.control.dialog().addTo(map);
dialogue.freeze();
dialogue.close();
var doDialog = function(d) {
//console.log("DIALOGUE",d);
dialogue.setContent(d);
dialogue.open();
}
// Delete a marker (and notify websocket)
var delMarker = function(dname) {
if (typeof polygons[dname] != "undefined") {
@ -1233,6 +1247,7 @@ function setMarker(data) {
}
}
// handle any incoming COMMANDS to control the map remotely
function doCommand(cmd) {
// console.log("COMMAND",cmd);
// ignore server side initial command if client position already saved.
@ -1478,19 +1493,19 @@ function doCommand(cmd) {
basemaps[baselayername].addTo(map);
}
// Add side by side control
if (cmd.side) { console.log("SIDE",cmd.side); }
if (cmd.side && (cmd.side === "none")) {
sidebyside.remove();
map.removeLayer(basemaps[sidebyside.lay]);
sidebyside = undefined;
}
if (cmd.side && basemaps.hasOwnProperty(cmd.side)) {
if (sidebyside) { sidebyside.removeFrom(map); }
if (sidebyside) { sidebyside.remove(); map.removeLayer(basemaps[sidebyside.lay]); }
basemaps[cmd.side].addTo(map);
sidebyside = L.Control.sideBySide(basemaps[baselayername], basemaps[cmd.side]);
sidebyside.addTo(map);
sidebyside.lay = cmd.side;
}
if (cmd.split && sidebyside && (cmd.split <= 100) && (cmd.split >= 0)) { console.log("DING",cmd.split); sidebyside.setSplit(cmd.split); }
// Turn on an existing overlay
if (cmd.hasOwnProperty("showlayer") && overlays.hasOwnProperty(cmd.showlayer)) {
map.addLayer(overlays[cmd.showlayer]);
@ -1520,6 +1535,30 @@ function doCommand(cmd) {
}
map.setView([clat,clon],czoom);
}
// handle any incoming GEOJSON directly - may style badly
function doGeojson(g) {
console.log("GEOJSON",g);
if (!basemaps["geojson"]) {
var opt = { style: function(feature) {
var st = { stroke:true, color:"#910000", weight:2, fill:true, fillColor:"#910000", fillOpacity:0.3 };
if (feature.hasOwnProperty("properties")) {
console.log("GPROPS", feature.properties)
}
if (feature.hasOwnProperty("style")) {
console.log("GSTYLE", feature.style)
}
return st;
}
}
opt.onEachFeature = function (f,l) {
if (f.properties) { l.bindPopup('<pre>'+JSON.stringify(f.properties,null,' ').replace(/[\{\}"]/g,'')+'</pre>'); }
}
overlays["geojson"] = L.geoJson(g,opt);
layercontrol.addOverlay(overlays["geojson"],"geojson");
}
overlays["geojson"].addData(g);
}
</script>
</body>
</html>

View File

@ -0,0 +1,108 @@
.leaflet-control-dialog {
position: absolute;
background-color: #fff;
padding: 0px;
text-align: left;
border-radius: 4px;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65);
display: flex;
flex-direction: column;
}
.leaflet-control-dialog .leaflet-control-dialog-inner {
position: relative;
box-sizing: border-box;
float: left;
width: 100%;
height: 100%;
padding: 16px 0px;
}
.leaflet-control-dialog
.leaflet-control-dialog-inner
.leaflet-control-dialog-grabber {
position: absolute;
box-sizing: border-box;
width: 20px;
height: 20px;
top: 0px;
left: 0px;
padding: 3px;
font-size: 15px;
line-height: 15px;
color: #ccc;
}
.leaflet-control-dialog
.leaflet-control-dialog-inner
.leaflet-control-dialog-grabber:hover {
cursor: grab;
cursor: -webkit-grab;
cursor: -moz-grab;
}
.leaflet-control-dialog
.leaflet-control-dialog-inner
.leaflet-control-dialog-close {
position: absolute;
box-sizing: border-box;
width: 20px;
height: 20px;
top: 0px;
right: 0px;
padding: 2px;
font-size: 16px;
line-height: 16px;
color: #666;
}
.leaflet-control-dialog
.leaflet-control-dialog-inner
.leaflet-control-dialog-close:hover {
cursor: pointer;
}
.leaflet-control-dialog
.leaflet-control-dialog-inner
.leaflet-control-dialog-contents {
position: relative;
float: left;
width: 100%;
height: 100%;
margin: 0px;
padding: 0 14px;
min-height: 50px;
overflow: auto;
box-sizing: border-box;
}
.leaflet-control-dialog
.leaflet-control-dialog-inner
.leaflet-control-dialog-resizer {
position: absolute;
box-sizing: border-box;
width: 20px;
height: 20px;
bottom: 0px;
right: 0px;
padding: 2px;
font-size: 16px;
line-height: 16px;
color: #ccc;
}
.leaflet-control-dialog
.leaflet-control-dialog-inner
.leaflet-control-dialog-resizer:hover {
cursor: grab;
cursor: -webkit-grab;
cursor: -moz-grab;
}
.fa-rotate-45 {
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}

View File

@ -0,0 +1,374 @@
L.Control.Dialog = L.Control.extend({
options: {
size: [ 200, 160 ],
minSize: [ 200, 100 ],
maxSize: [ 400, 800 ],
anchor: [ 44, -165 ],
position: "topright",
initOpen: true
},
initialize: function(options) {
this.options = JSON.parse(JSON.stringify(this.options));
L.setOptions(this, options);
this._attributions = {};
},
onAdd: function(map) {
this._initLayout();
this._map = map;
this.update();
if (!this.options.initOpen) {
this.close();
}
return this._container;
},
open: function() {
if (!this._map) {
return;
}
this._container.style.visibility = "";
this._map.fire("dialog:opened", this);
return this;
},
close: function() {
this._container.style.visibility = "hidden";
this._map.fire("dialog:closed", this);
return this;
},
destroy: function() {
if (!this._map) {
return this;
}
this._map.fire("dialog:destroyed", this);
this.remove();
if (this.onRemove) {
this.onRemove(this._map);
}
return this;
},
setLocation: function(location) {
location = location || [ 250, 250 ];
this.options.anchor[0] = 0;
this.options.anchor[1] = 0;
this._oldMousePos.x = 0;
this._oldMousePos.y = 0;
this._move(location[1], location[0]);
return this;
},
setSize: function(size) {
size = size || [ 300, 300 ];
this.options.size[0] = 0;
this.options.size[1] = 0;
this._oldMousePos.x = 0;
this._oldMousePos.y = 0;
this._resize(size[0], size[1]);
return this;
},
lock: function() {
this._resizerNode.style.visibility = "hidden";
this._grabberNode.style.visibility = "hidden";
this._closeNode.style.visibility = "hidden";
this._map.fire("dialog:locked", this);
return this;
},
unlock: function() {
this._resizerNode.style.visibility = "";
this._grabberNode.style.visibility = "";
this._closeNode.style.visibility = "";
this._map.fire("dialog:unlocked", this);
return this;
},
freeze: function() {
this._resizerNode.style.visibility = "hidden";
this._grabberNode.style.visibility = "hidden";
this._map.fire("dialog:frozen", this);
return this;
},
unfreeze: function() {
this._resizerNode.style.visibility = "";
this._grabberNode.style.visibility = "";
this._map.fire("dialog:unfrozen", this);
return this;
},
hideClose: function() {
this._closeNode.style.visibility = "hidden";
this._map.fire("dialog:closehidden", this);
return this;
},
showClose: function() {
this._closeNode.style.visibility = "";
this._map.fire("dialog:closeshown", this);
return this;
},
hideResize: function() {
this._resizerNode.style.visibility = "hidden";
this._map.fire("dialog:resizehidden", this);
return this;
},
showResize: function() {
this._resizerNode.style.visibility = "";
this._map.fire("dialog:resizeshown", this);
return this;
},
setContent: function(content) {
this._content = content;
this.update();
return this;
},
getContent: function() {
return this._content;
},
getElement: function() {
return this._container;
},
update: function() {
if (!this._map) {
return;
}
this._container.style.visibility = "hidden";
this._updateContent();
this._updateLayout();
this._container.style.visibility = "";
this._map.fire("dialog:updated", this);
},
_initLayout: function() {
var className = "leaflet-control-dialog";
var container = (this._container = L.DomUtil.create("div", className));
container.style.width = this.options.size[0] + "px";
//container.style.height = this.options.size[1] + "px";
container.style.top = this.options.anchor[0] + "px";
// container.style.left = this.options.anchor[1] + "px";
container.style.right = "0px";
// container.style.display = "flex";
// container.style["flex-direction"] = "column";
var stop = L.DomEvent.stopPropagation;
L.DomEvent.on(container, "click", stop)
.on(container, "mousedown", stop)
.on(container, "touchstart", stop)
.on(container, "dblclick", stop)
.on(container, "mousewheel", stop)
.on(container, "contextmenu", stop)
.on(container, "MozMousePixelScroll", stop);
var innerContainer = (this._innerContainer = L.DomUtil.create(
"div",
className + "-inner"
));
var grabberNode = (this._grabberNode = L.DomUtil.create(
"div",
className + "-grabber"
));
var grabberIcon = L.DomUtil.create("i", "fa fa-arrows");
grabberNode.appendChild(grabberIcon);
L.DomEvent.on(grabberNode, "mousedown", this._handleMoveStart, this);
var closeNode = (this._closeNode = L.DomUtil.create(
"div",
className + "-close"
));
var closeIcon = L.DomUtil.create("i", "fa fa-times");
closeNode.appendChild(closeIcon);
L.DomEvent.on(closeNode, "click", this._handleClose, this);
var resizerNode = (this._resizerNode = L.DomUtil.create(
"div",
className + "-resizer"
));
var resizeIcon = L.DomUtil.create("i", "fa fa-arrows-h fa-rotate-45");
resizerNode.appendChild(resizeIcon);
L.DomEvent.on(resizerNode, "mousedown", this._handleResizeStart, this);
var contentNode = (this._contentNode = L.DomUtil.create(
"div",
className + "-contents"
));
container.appendChild(innerContainer);
innerContainer.appendChild(contentNode);
innerContainer.appendChild(grabberNode);
innerContainer.appendChild(closeNode);
innerContainer.appendChild(resizerNode);
this._oldMousePos = { x: 0, y: 0 };
},
_handleClose: function() {
this.close();
},
_handleResizeStart: function(e) {
this._oldMousePos.x = e.clientX;
this._oldMousePos.y = e.clientY;
L.DomEvent.on(this._map, "mousemove", this._handleMouseMove, this);
L.DomEvent.on(this._map, "mouseup", this._handleMouseUp, this);
this._map.fire("dialog:resizestart", this);
this._resizing = true;
},
_handleMoveStart: function(e) {
this._oldMousePos.x = e.clientX;
this._oldMousePos.y = e.clientY;
L.DomEvent.on(this._map, "mousemove", this._handleMouseMove, this);
L.DomEvent.on(this._map, "mouseup", this._handleMouseUp, this);
this._map.fire("dialog:movestart", this);
this._moving = true;
},
_handleMouseMove: function(e) {
var diffX = e.originalEvent.clientX - this._oldMousePos.x,
diffY = e.originalEvent.clientY - this._oldMousePos.y;
// this helps prevent accidental highlighting on drag:
if (e.originalEvent.stopPropagation) {
e.originalEvent.stopPropagation();
}
if (e.originalEvent.preventDefault) {
e.originalEvent.preventDefault();
}
if (this._resizing) {
this._resize(diffX, diffY);
}
if (this._moving) {
this._move(diffX, diffY);
}
},
_handleMouseUp: function() {
L.DomEvent.off(this._map, "mousemove", this._handleMouseMove, this);
L.DomEvent.off(this._map, "mouseup", this._handleMouseUp, this);
if (this._resizing) {
this._resizing = false;
this._map.fire("dialog:resizeend", this);
}
if (this._moving) {
this._moving = false;
this._map.fire("dialog:moveend", this);
}
},
_move: function(diffX, diffY) {
var newY = this.options.anchor[0] + diffY;
var newX = this.options.anchor[1] + diffX;
this.options.anchor[0] = newY;
this.options.anchor[1] = newX;
this._container.style.top = this.options.anchor[0] + "px";
this._container.style.left = this.options.anchor[1] + "px";
this._map.fire("dialog:moving", this);
this._oldMousePos.y += diffY;
this._oldMousePos.x += diffX;
},
_resize: function(diffX, diffY) {
var newX = this.options.size[0] + diffX;
var newY = this.options.size[1] + diffY;
if (newX <= this.options.maxSize[0] && newX >= this.options.minSize[0]) {
this.options.size[0] = newX;
this._container.style.width = this.options.size[0] + "px";
this._oldMousePos.x += diffX;
}
if (newY <= this.options.maxSize[1] && newY >= this.options.minSize[1]) {
this.options.size[1] = newY;
this._container.style.height = this.options.size[1] + "px";
this._oldMousePos.y += diffY;
}
this._map.fire("dialog:resizing", this);
},
_updateContent: function() {
if (!this._content) {
return;
}
var node = this._contentNode;
var content =
typeof this._content === "function" ? this._content(this) : this._content;
if (typeof content === "string") {
node.innerHTML = content;
} else {
while (node.hasChildNodes()) {
node.removeChild(node.firstChild);
}
node.appendChild(content);
}
},
_updateLayout: function() {
this._container.style.width = this.options.size[0] + "px";
//this._container.style.height = this.options.size[1] + "px";
this._container.style.top = this.options.anchor[0] + "px";
//this._container.style.left = this.options.anchor[1] + "px";
this._container.style.right = "0px";
}
});
L.control.dialog = function(options) {
return new L.Control.Dialog(options);
};

View File

@ -78,6 +78,11 @@ L.Control.SideBySide = L.Control.extend({
return this
},
setSplit: function(s) {
this._range.value = s/100;
this._updateClip();
},
_updateClip: function () {
var map = this._map
var rangeValue = this._range.value

View File

@ -1,5 +1,5 @@
CACHE MANIFEST
# date: Oct 15th 2018 - v1.5.3
# date: Oct 17th 2018 - v1.5.4
CACHE:
index.html
@ -8,6 +8,8 @@ images/node-red.png
images/world-50m-flat.json
css/map.css
leaflet/L.Terminator.js
leaflet/Leaflet.Dialog.css
leaflet/Leaflet.Dialog.js
leaflet/Leaflet.fullscreen.min.js
leaflet/Leaflet.vector-markers.css
leaflet/Leaflet.vector-markers.min.js