Enable File drop on map

This commit is contained in:
Dave Conway-Jones 2020-12-06 23:53:47 +00:00
parent ab7bbbe34d
commit 821bedca20
No known key found for this signature in database
GPG Key ID: 88BA2B8A411BE9FF
6 changed files with 144 additions and 55 deletions

View File

@ -1,5 +1,7 @@
### Change Log for Node-RED Worldmap
- v2.7.0 - Allow track and image files to be dragged onto the map, if enabled
- v2.6.1 - Better fit for worldmap when in ui_template
- v2.6.0 - Add route capability to draw line when online
- v2.5.9 - Fix handling of multiple hulls, tidy contextmenu handling
- v2.5.8 - Let node name be the full page map title

View File

@ -11,6 +11,8 @@ map web page for plotting "things" on.
### Updates
- v2.7.0 - Allow track and image files to be dragged onto the map, if enabled
- v2.6.1 - Better fit for worldmap when in ui_template
- v2.6.0 - Add route capability to draw line when online
- v2.5.9 - Fix handling of multiple hulls, tidy contextmenu handling
- v2.5.8 - Let node name be the full page map title
@ -24,13 +26,8 @@ map web page for plotting "things" on.
- v2.5.0 - Add minimap capability.
- v2.4.2 - Fix editing injected shapes.
- v2.4.1 - Add convex-hull node for grouping objects.
- v2.3.16 - Add heading to default addMarker, allow custom http icon size.
- v2.3.13 - Fix geoson feature properties fill color, and better marker handling
- v2.3.11 - Better editing of drawing layer, add OpenTopoMap, and better Esri satellite
- v2.3.10 - Improve geojson layer and name handling.
- v2.3.8 - Fix fa-marker offset to improve accuracy.
- see [CHANGELOG](https://github.com/dceejay/RedMap/blob/master/CHANGELOG.md) for full list.
- see [CHANGELOG](https://github.com/dceejay/RedMap/blob/master/CHANGELOG.md) for full list of changes.
## Install
@ -342,10 +339,14 @@ The **worldmap in** node can be used to receive various events from the map. Exa
{ "action": "addlayer", "name": "myLayer" } // when a new map layer is added
{ "action": "dellayer", "name": "myLayer" } // when a new map layer is deleted
{ "action": "file", "name": "filename", "lat":51, "lon":-1, "content":"....."} // when a file is dropped on the map - see below.
{ "action": "button", "name": "My Fancy Button" } // when a user defined button is clicked
{ "action": "feedback", "name": "some name", "value": "some value", "lat":51, "lon":0, "layer":"unknown" } // when a user calls the feedback function - see below
If File Drop is enabled - then the map can accept files of type gpx, kml, nvg, png and jpg. Image contents will be base64 encoded for transfer. The lat, lon of the cursor drop point will be included. Tracks will be locally rendered on the map. The node-red-node-exif node can be used to extract lcoation information from a jpeg image and then geolocate it back on the map.
All actions also include a `msg._sessionid` property that indicates which client session they came from. Any msg sent out that includes this property will ONLY be sent to that session - so you can target map updates to specific sessions if required.

View File

@ -1,6 +1,6 @@
{
"name": "node-red-contrib-web-worldmap",
"version": "2.6.0",
"version": "2.7.0",
"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

@ -103,7 +103,12 @@
</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">
<input type="text" id="node-input-path" placeholder="worldmap" style="width:30%;">
<i class="fa fa-hand-paper-o" style="margin-left:22px;"></i> File Drop
<select id="node-input-allowFileDrop" style="width:80px;">
<option value="false">Disable</option>
<option value="true">Enable</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-file"></i> Name</label>
@ -250,7 +255,12 @@ then by default <code>⌘⇧m</code> - <code>ctrl-shift-m</code> will load the m
</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">
<input type="text" id="node-input-path" placeholder="worldmap" style="width:30%;">
<i class="fa fa-hand-paper-o" style="margin-left:22px;"></i> File Drop
<select id="node-input-allowFileDrop" style="width:80px;">
<option value="false">Disable</option>
<option value="true">Enable</option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-file"></i> Name</label>
@ -297,7 +307,6 @@ 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/javascript">
var lnk = document.location.host+RED.settings.httpNodeRoot+"/worldmap";
lnk = lnk.replace(new RegExp('\/{1,}','g'),'/');
@ -327,6 +336,7 @@ then by default <code>⌘⇧m</code> - <code>ctrl-shift-m</code> will load the m
hiderightclick: {value:"false"},
coords: {value:"false"},
showgrid: {value:"false"},
allowFileDrop: {value:"false"},
path: {value:"/worldmap"}
},
inputs:1,
@ -395,6 +405,7 @@ then by default <code>⌘⇧m</code> - <code>ctrl-shift-m</code> will load the m
hiderightclick: {value:"true"},
coords: {value:"false"},
showgrid: {value:"false"},
allowFileDrop: {value:"false"},
path: {value:"/worldmap"}
},
inputs:1,
@ -446,7 +457,8 @@ then by default <code>⌘⇧m</code> - <code>ctrl-shift-m</code> will load the m
<label for="node-input-events"><i class="fa fa-sign-out"></i> Output</label>
<select id="node-input-events" style="width:70%;">
<option value="all">All action events</option>
<option value="">Connect event only</option>
<option value="connect">Connect events only</option>
<option value="files">File events only</option>
</select>
</div>
<div class="form-row">

View File

@ -42,6 +42,7 @@ module.exports = function(RED) {
node.hiderightclick = n.hiderightclick || "false";
node.coords = n.coords || "none";
node.showgrid = n.showgrid || "false";
node.allowFileDrop = n.allowFileDrop || "false";
node.path = n.path || "/worldmap";
if (node.path.charAt(0) != "/") { node.path = "/" + node.path; }
if (!sockets[node.path]) {
@ -78,6 +79,7 @@ module.exports = function(RED) {
c.showlayers = node.layers;
c.grid = {showgrid:node.showgrid};
c.hiderightclick = node.hiderightclick;
c.allowFileDrop = node.allowFileDrop;
c.coords = node.coords;
c.toptitle = node.name;
client.write(JSON.stringify({command:c}));
@ -139,7 +141,7 @@ module.exports = function(RED) {
var frameWidth = (size.sx +size.cx) *width - size.cx - 1;
var frameHeight = (size.sy +size.cy) *height - size.cy - 2;
var url = encodeURI(config.path);
var html = `<div style="overflow:hidden;">
var html = `<style>.nr-dashboard-ui_worldmap{padding:0;}</style><div style="overflow:hidden;">
<iframe src="${url}" width="${frameWidth}px" height="${frameHeight}px" style="border:none;"></iframe>
</div>
`;
@ -224,11 +226,14 @@ module.exports = function(RED) {
node.status({fill:"green",shape:"dot",text:"connected "+Object.keys(clients).length,_sessionid:client.id});
client.on('data', function(message) {
message = JSON.parse(message);
if (node.events !== "connect") {
if (message.hasOwnProperty("action")) {
if ((node.events === "files") && (message.action === "file")) {
setImmediate(function() {node.send({payload:message, topic:node.path.substr(1), _sessionid:client.id})});
}
else {
if (message.hasOwnProperty("action") && (message.action === "connected")) {
else if ((node.events === "connect") && (message.action === "connected")) {
setImmediate(function() {node.send({payload:message, topic:node.path.substr(1), _sessionid:client.id})});
}
else if (node.events === "all") {
setImmediate(function() {node.send({payload:message, topic:node.path.substr(1), _sessionid:client.id})});
}
}
@ -236,7 +241,7 @@ module.exports = function(RED) {
client.on('close', function() {
delete clients[client.id];
node.status({fill:"green",shape:"ring",text:"connected "+Object.keys(clients).length,_sessionid:client.id});
if (node.events !== "connect") {
if (node.events !== "files") {
node.send({payload:{action:"disconnect", clients:Object.keys(clients).length}, topic:node.path.substr(1), _sessionid:client.id});
}
});
@ -367,7 +372,6 @@ module.exports = function(RED) {
}
RED.nodes.registerType("worldmap-tracks",WorldMapTracks);
var WorldMapHull = function(n) {
RED.nodes.createNode(this,n);
this.prop = n.prop || "layer";

View File

@ -23,6 +23,7 @@ var inIframe = false;
var showUserMenu = true;
var showLayerMenu = true;
var showMouseCoords = false;
var allowFileDrop = false;
var minimap;
var sidebyside;
var layercontrol;
@ -94,7 +95,12 @@ var connect = function() {
ws.onmessage = function(e) {
var data = JSON.parse(e.data);
// console.log("DATA",typeof data,data);
if (data) {
if (data) { handleData(data); }
};
}
console.log("CONNECT TO",location.pathname + 'socket');
var handleData = function(data) {
if (Array.isArray(data)) {
//console.log("ARRAY");
// map.closePopup();
@ -117,6 +123,10 @@ var connect = function() {
else if (data.indexOf("<kml") != -1) {
data = {command:{map:{overlay:"KML", kml:data}}};
}
else if (data.indexOf("<gpx") != -1) {
console.log("GPX")
data = {command:{map:{overlay:"GPX", gpx:data}}};
}
}
if (data.command) { doCommand(data.command); delete data.command; }
if (data.hasOwnProperty("name")) { setMarker(data); }
@ -128,9 +138,6 @@ var connect = function() {
}
}
}
};
}
console.log("CONNECT TO",location.pathname + 'socket');
window.onunload = function() { if (ws) ws.close(); }
@ -153,6 +160,66 @@ document.addEventListener ("keydown", function (ev) {
// Create the Initial Map object.
map = new L.map('map').setView(startpos, startzoom);
var droplatlng;
var target = document.getElementById("map")
target.ondragover = function (ev) {
ev.preventDefault()
ev.dataTransfer.dropEffect = "move"
}
target.ondrop = function (ev) {
if (allowFileDrop === true) {
ev.preventDefault();
droplatlng = map.mouseEventToLatLng(ev);
handleFiles(ev.dataTransfer.files);
}
}
var handleFiles = function(files) {
([...files]).forEach(readFile);
}
var readFile = function(file) {
console.log("FILE",file)
// Check if the file is text or kml
if (file.type &&
file.type.indexOf('text') === -1 &&
file.type.indexOf('kml') === -1 &&
file.type.indexOf('jpeg') === -1 &&
file.type.indexOf('png') === -1) {
console.log('File is not text, kml, jpeg or png.', file.type, file);
return;
}
const reader = new FileReader();
reader.addEventListener('load', (event) => {
var content = event.target.result;
var data;
if (content.indexOf("base64") !== -1) {
if (content.indexOf("image") === -1) {
data = atob(content.split("base64,")[1]);
if (data.indexOf("<gpx") !== -1) {
doCommand({map:{overlay:file.name, gpx:data}});
}
else if (data.indexOf("<kml") !== -1) {
doCommand({map:{overlay:file.name, kml:data}});
}
else if (data.indexOf("<nvg") !== -1) {
doCommand({map:{overlay:file.name, nvg:data}});
}
else {
handleData(data);
}
}
ws.send(JSON.stringify({action:"file", name:file.name, content:content, lat:droplatlng.lat, lon:droplatlng.lng}));
}
else {
console.log("NOT SURE WHAT THIS IS?",content)
}
});
reader.readAsDataURL(file);
}
// Create some buttons
var menuButton = L.easyButton({states:[{icon:'fa-bars fa-lg', onClick:function() { toggleMenu(); }, title:'Toggle menu'}], position:"topright"});
var fullscreenButton = L.control.fullscreen();
@ -1600,7 +1667,6 @@ function setMarker(data) {
var b = marker.getPopup().getContent().split("bearing : ");
if (b.length === 2) { b = parseFloat(b[1].split("<br")[0]); }
else { b = undefined; }
console.log("P",marker.getPopup().getContent())
ws.send(JSON.stringify({action:"move",name:marker.name,layer:marker.lay,icon:marker.icon,iconColor:marker.iconColor,SIDC:marker.SIDC,draggable:true,lat:parseFloat(marker.getLatLng().lat.toFixed(6)),lon:parseFloat(marker.getLatLng().lng.toFixed(6)),bearing:b }));
});
}
@ -1850,6 +1916,11 @@ function doCommand(cmd) {
rightmenuMap.setContent(addmenu);
}
}
if (cmd.hasOwnProperty("allowFileDrop")) {
if (typeof cmd.allowFileDrop === "string") {
allowFileDrop = cmd.allowFileDrop === "false" ? false : true;
}
}
if (cmd.hasOwnProperty("coords")) {
try { coords.removeFrom(map); }
catch(e) {}
@ -1985,7 +2056,6 @@ function doCommand(cmd) {
}
var parser = new NVG(cmd.map.nvg);
var geoj = parser.toGeoJSON();
overlays[cmd.map.overlay] = L.geoJson(geoj,{
style: function(feature) {
var st = { stroke:true, color:"black", weight:2, fill:true };