2014-11-04 16:07:32 +01:00
|
|
|
|
|
|
|
module.exports = function(RED) {
|
|
|
|
"use strict";
|
|
|
|
var ExifImage = require('exif').ExifImage;
|
2015-01-12 20:14:04 +01:00
|
|
|
|
2014-11-04 16:07:32 +01:00
|
|
|
function convertDegreesMinutesSecondsToDecimals(degrees, minutes, seconds) {
|
|
|
|
var result;
|
|
|
|
result = degrees + (minutes / 60) + (seconds / 3600);
|
|
|
|
return result;
|
|
|
|
}
|
2015-01-12 20:14:04 +01:00
|
|
|
|
2015-03-31 14:18:25 +02:00
|
|
|
function ExifNode(n) {
|
|
|
|
RED.nodes.createNode(this,n);
|
2020-12-07 18:34:44 +01:00
|
|
|
this.mode = n.mode || "normal";
|
|
|
|
if (this.mode === "worldmap") { this.property = "payload.content"; }
|
|
|
|
else { this.property = n.property || "payload"; }
|
2015-03-31 14:18:25 +02:00
|
|
|
var node = this;
|
|
|
|
|
|
|
|
/***
|
|
|
|
* Extracts GPS location information from Exif data. If enough information is
|
|
|
|
* provided, convert the Exif data into a pair of single floating point number
|
|
|
|
* latitude/longitude data pairs. Populates msg.location with these.
|
|
|
|
* Assumes that the msg object will always have exifData available as msg.exif.
|
|
|
|
* Assume that the GPS data saved into Exif provides a valid value
|
|
|
|
*/
|
2020-12-06 18:34:27 +01:00
|
|
|
function addMsgLocationDataFromExifGPSData(msg,val) {
|
2014-11-04 16:07:32 +01:00
|
|
|
var gpsData = msg.exif.gps; // declaring variable purely to make checks more readable
|
2015-05-10 20:27:35 +02:00
|
|
|
if (gpsData.GPSAltitude) {
|
|
|
|
/* istanbul ignore else */
|
|
|
|
if (!msg.location) { msg.location = {}; }
|
2014-11-04 16:07:32 +01:00
|
|
|
msg.location.alt = gpsData.GPSAltitude;
|
|
|
|
}
|
2015-05-10 20:27:35 +02:00
|
|
|
if (gpsData.GPSLatitudeRef && gpsData.GPSLatitude && gpsData.GPSLongitudeRef && gpsData.GPSLongitude) { // location can be determined, OK
|
2014-11-04 16:07:32 +01:00
|
|
|
// The data provided in Exif is in degrees, minutes, seconds, this is to be converted into a single floating point degree
|
2015-05-10 20:27:35 +02:00
|
|
|
if (gpsData.GPSLatitude.length === 3) { // OK to convert latitude
|
|
|
|
if (gpsData.GPSLongitude.length === 3) { // OK to convert longitude
|
2015-03-31 14:18:25 +02:00
|
|
|
var latitude = convertDegreesMinutesSecondsToDecimals(gpsData.GPSLatitude[0], gpsData.GPSLatitude[1], gpsData.GPSLatitude[2]);
|
|
|
|
latitude = Math.round(latitude * 100000)/100000; // 5dp is approx 1m resolution...
|
|
|
|
// (N)orth means positive latitude, (S)outh means negative latitude
|
|
|
|
if (gpsData.GPSLatitudeRef.toString() === 'S' || gpsData.GPSLatitudeRef.toString() === 's') {
|
2014-11-04 16:07:32 +01:00
|
|
|
latitude = latitude * -1;
|
|
|
|
}
|
|
|
|
var longitude = convertDegreesMinutesSecondsToDecimals(gpsData.GPSLongitude[0], gpsData.GPSLongitude[1], gpsData.GPSLongitude[2]);
|
2015-03-31 14:18:25 +02:00
|
|
|
longitude = Math.round(longitude * 100000)/100000; // 5dp is approx 1m resolution...
|
|
|
|
// (E)ast means positive longitude, (W)est means negative longitude
|
|
|
|
if (gpsData.GPSLongitudeRef.toString() === 'W' || gpsData.GPSLongitudeRef.toString() === 'w') {
|
2014-11-04 16:07:32 +01:00
|
|
|
longitude = longitude * -1;
|
|
|
|
}
|
2015-03-31 14:18:25 +02:00
|
|
|
// Create location property if not exists
|
|
|
|
if (!msg.location) { msg.location = {}; }
|
2014-11-04 16:07:32 +01:00
|
|
|
msg.location.lat = latitude;
|
|
|
|
msg.location.lon = longitude;
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
2014-11-04 16:07:32 +01:00
|
|
|
node.log("Invalid longitude data, no location information has been added to the message.");
|
|
|
|
}
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
2014-11-04 16:07:32 +01:00
|
|
|
node.log("Invalid latitude data, no location information has been added to the message.");
|
|
|
|
}
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
2014-11-04 16:07:32 +01:00
|
|
|
node.log("The location of this image cannot be determined safely so no location information has been added to the message.");
|
|
|
|
}
|
2020-12-06 18:34:27 +01:00
|
|
|
msg.location.arc = {
|
|
|
|
ranges: [500,1000,2000],
|
|
|
|
pan: gpsData.GPSImgDirection,
|
|
|
|
fov: (2 * Math.atan(36 / (2 * msg.exif.exif.FocalLengthIn35mmFormat)) * 180 / Math.PI),
|
|
|
|
color: '#910000'
|
|
|
|
}
|
|
|
|
msg.location.icon = "fa-camera";
|
|
|
|
var na;
|
|
|
|
if (val.hasOwnProperty("name")) { na = val.name; }
|
|
|
|
else if (msg.hasOwnProperty("filename")) { na = msg.filename.split('/').pop(); }
|
|
|
|
else { na = msg.exif.image.Make+"_"+msg.exif.image.ModifyDate; }
|
|
|
|
msg.location.name = na;
|
2020-12-07 18:34:44 +01:00
|
|
|
msg.location.layer = "Images";
|
2020-12-06 18:34:27 +01:00
|
|
|
msg.location.popup = '<img width="280" src="data:image/jpeg;base64,'+val.toString("base64")+'"/>'
|
2014-11-04 16:07:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
this.on("input", function(msg) {
|
2020-12-07 18:34:44 +01:00
|
|
|
if (node.mode === "worldmap" && (msg.payload.action !== "file" || msg.payload.type.indexOf("image") === -1)) { return; } // in case worldmap-in not filtered.
|
2014-11-04 16:07:32 +01:00
|
|
|
try {
|
2020-12-06 18:34:27 +01:00
|
|
|
var value = RED.util.getMessageProperty(msg,node.property);
|
|
|
|
if (value !== undefined) {
|
|
|
|
if (typeof value === "string") { // it must be a base64 encoded inline image type
|
|
|
|
if (value.indexOf('data:image') !== -1) {
|
|
|
|
value = new Buffer.from(value.replace(/^data:image\/[a-z]+;base64,/, ""), 'base64');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (Buffer.isBuffer(value)) { // or a proper jpg buffer
|
|
|
|
new ExifImage({ image:value }, function (error, exifData) {
|
2015-01-12 20:14:04 +01:00
|
|
|
if (error) {
|
2020-12-07 18:34:44 +01:00
|
|
|
if (node.mode !== "worldmap") {
|
|
|
|
node.log(error.toString());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
msg.location = {name:msg.payload.name, lat:msg.payload.lat, lon:msg.payload.lon, layer:"Images", icon:"fa-camera", draggable:true};
|
|
|
|
msg.location.popup = '<img width="280" src="data:image\/png;base64,'+msg.payload.content.toString('base64')+'"/><br/>';
|
|
|
|
}
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
2018-01-07 16:26:57 +01:00
|
|
|
if (exifData) {
|
2015-01-12 20:14:04 +01:00
|
|
|
msg.exif = exifData;
|
2019-08-11 14:37:15 +02:00
|
|
|
if ((exifData.hasOwnProperty("gps")) && (Object.keys(exifData.gps).length !== 0)) {
|
2020-12-06 18:34:27 +01:00
|
|
|
addMsgLocationDataFromExifGPSData(msg,value);
|
2018-01-07 16:26:57 +01:00
|
|
|
}
|
2018-01-07 16:43:37 +01:00
|
|
|
//else { node.log("The incoming image did not contain Exif GPS data."); }
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
2018-01-07 16:43:37 +01:00
|
|
|
node.warn("The incoming image did not contain any Exif data, nothing to do.");
|
2014-11-04 16:07:32 +01:00
|
|
|
}
|
2015-01-12 20:14:04 +01:00
|
|
|
}
|
2020-12-07 18:34:44 +01:00
|
|
|
if (node.mode === "worldmap") {
|
|
|
|
msg.payload = msg.location;
|
|
|
|
delete msg.location;
|
|
|
|
}
|
2015-01-12 20:14:04 +01:00
|
|
|
node.send(msg);
|
|
|
|
});
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
2018-01-07 16:43:37 +01:00
|
|
|
node.error("Invalid payload received, the Exif node cannot proceed, no messages sent.",msg);
|
2014-11-04 16:07:32 +01:00
|
|
|
return;
|
|
|
|
}
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
else {
|
2020-12-06 18:34:27 +01:00
|
|
|
node.warn("No input received, the Exif node cannot proceed, no messages sent.",msg);
|
2014-11-04 16:07:32 +01:00
|
|
|
return;
|
|
|
|
}
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
catch (error) {
|
2018-01-07 16:43:37 +01:00
|
|
|
node.error("An error occurred while extracting Exif information. Please check the log for details.",msg);
|
2014-11-04 16:07:32 +01:00
|
|
|
node.log('Error: '+error.message);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
RED.nodes.registerType("exif",ExifNode);
|
|
|
|
}
|