dji_srt_parser
Advanced tools
Comparing version 1.0.30 to 1.0.31
534
index.js
function DJI_SRT_Parser() { | ||
this.fileName = ""; | ||
this.fileName = ''; | ||
this.metadata = {}; | ||
@@ -9,29 +9,48 @@ this.rawMetadata = []; | ||
DJI_SRT_Parser.prototype.srtToObject = function(srt) {//convert SRT strings file into array of objects | ||
DJI_SRT_Parser.prototype.srtToObject = function(srt) { | ||
//convert SRT strings file into array of objects | ||
let converted = []; | ||
const timecodeRegEx = /(\d{2}:\d{2}:\d{2},\d{3})\s-->\s/; | ||
const packetRegEx = /^\d+$/; | ||
const arrayRegEx = /\b([A-Za-z]+)\(([-\+\w.,/]+)\)/g; | ||
const valueRegEx = /\b([A-Za-z]+)\s?:[\s\[a-zA-Z\]]?([-\+\d./]+)\w{0,3}\b/g; | ||
const arrayRegEx = /\b([A-Z_a-z]+)\(([-\+\w.,/]+)\)/g; | ||
const valueRegEx = /\b([A-Z_a-z]+)\s?:[\s\[a-z_A-Z\]]?([-\+\d./]+)\w{0,3}\b/g; | ||
const dateRegEx = /\d{4}[-.]\d{1,2}[-.]\d{1,2} \d{1,2}:\d{2}:\d{2}/; | ||
const accurateDateRegex = /(\d{4}[-.]\d{1,2}[-.]\d{1,2} \d{1,2}:\d{2}:\d{2}),(\w{3}),(\w{3})/g; | ||
srt = srt.split(/[\n\r]/); | ||
//Split difficult Phantom4Pro format | ||
srt = srt | ||
.replace(/.*-->.*/g, match => match.replace(/,/g, ':separator:')) | ||
.replace(/\(.*\)/g, match => match.replace(/,/g, ':separator:').replace(/\s/g, '')) | ||
.replace(/,/g, '') | ||
.replace(/\:separator\:/g, ','); | ||
//Split others | ||
srt = srt | ||
.split(/[\n\r]/) | ||
.map(l => l.trim()) | ||
.map(l => | ||
l | ||
.replace(/([a-zA-Z])\s(\d)/g, '$1:$2') | ||
.replace(/([a-zA-Z])\s\(/g, '$1(') | ||
.replace(/([a-zA-Z])\.([a-zA-Z])/g, '$1_$2') | ||
.replace(/([a-zA-Z])\/(\d)/g, '$1:$2') | ||
) | ||
.filter(l => l.length); | ||
srt.forEach(line => { | ||
let match; | ||
if (packetRegEx.test(line)) {//new packet | ||
if (packetRegEx.test(line)) { | ||
//new packet | ||
converted.push({}); | ||
} else if (match = timecodeRegEx.exec(line)) { | ||
converted[converted.length-1].TIMECODE = match[1]; | ||
} else if ((match = timecodeRegEx.exec(line))) { | ||
converted[converted.length - 1].TIMECODE = match[1]; | ||
} else { | ||
while (match = arrayRegEx.exec(line) ) { | ||
converted[converted.length-1][match[1]] = match[2].split(","); | ||
while ((match = arrayRegEx.exec(line))) { | ||
converted[converted.length - 1][match[1]] = match[2].split(','); | ||
} | ||
while (match = valueRegEx.exec(line)) { | ||
converted[converted.length-1][match[1]] = match[2]; | ||
while ((match = valueRegEx.exec(line))) { | ||
converted[converted.length - 1][match[1]] = match[2]; | ||
} | ||
if (match = accurateDateRegex.exec(line)) { | ||
converted[converted.length-1].DATE = match[1]+":"+match[2]+"."+match[3]; | ||
} else if (match = dateRegEx.exec(line)) { | ||
converted[converted.length-1].DATE = match[0]; | ||
if ((match = accurateDateRegex.exec(line))) { | ||
converted[converted.length - 1].DATE = match[1] + ':' + match[2] + '.' + match[3]; | ||
} else if ((match = dateRegEx.exec(line))) { | ||
converted[converted.length - 1].DATE = match[0]; | ||
} | ||
@@ -41,46 +60,60 @@ } | ||
if (converted.length < 1) { | ||
console.log("Error converting object"); | ||
console.log('Error converting object'); | ||
return null; | ||
} | ||
return converted; | ||
} | ||
}; | ||
DJI_SRT_Parser.prototype.interpretMetadata = function(arr,smooth) { | ||
let computeSpeed = function(arr) {//computes 3 types of speed in km/h | ||
let computed = JSON.parse(JSON.stringify(arr)); | ||
let measure = function(lat1, lon1, lat2, lon2){ // generally used geo measurement function. Source: https://stackoverflow.com/questions/639695/how-to-convert-latitude-or-longitude-to-meters | ||
if ([lat1, lon1, lat2, lon2].reduce(((acc,val) => !isNum(val) ? true:acc),false)) { | ||
return 0;//set distance to 0 if there are null or nans in positions | ||
DJI_SRT_Parser.prototype.interpretMetadata = function(arr, smooth) { | ||
let computeSpeed = function(arr) { | ||
//computes 3 types of speed in km/h | ||
let computed = JSON.parse(JSON.stringify(arr)); | ||
let measure = function(lat1, lon1, lat2, lon2) { | ||
// generally used geo measurement function. Source: https://stackoverflow.com/questions/639695/how-to-convert-latitude-or-longitude-to-meters | ||
if ([lat1, lon1, lat2, lon2].reduce((acc, val) => (!isNum(val) ? true : acc), false)) { | ||
return 0; //set distance to 0 if there are null or nans in positions | ||
} | ||
var R = 6378.137; // Radius of earth in KM | ||
var dLat = lat2 * Math.PI / 180 - lat1 * Math.PI / 180; | ||
var dLon = lon2 * Math.PI / 180 - lon1 * Math.PI / 180; | ||
var a = Math.sin(dLat/2) * Math.sin(dLat/2) + | ||
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * | ||
Math.sin(dLon/2) * Math.sin(dLon/2); | ||
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); | ||
var dLat = (lat2 * Math.PI) / 180 - (lat1 * Math.PI) / 180; | ||
var dLon = (lon2 * Math.PI) / 180 - (lon1 * Math.PI) / 180; | ||
var a = | ||
Math.sin(dLat / 2) * Math.sin(dLat / 2) + | ||
Math.cos((lat1 * Math.PI) / 180) * Math.cos((lat2 * Math.PI) / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2); | ||
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); | ||
var d = R * c; | ||
return d * 1000; // meters | ||
} | ||
}; | ||
let accDistance = 0; | ||
computed = computed.map((pck,i,cmp) => { | ||
computed = computed.map((pck, i, cmp) => { | ||
let result = JSON.parse(JSON.stringify(pck)); | ||
result.SPEED = { | ||
TWOD:0, | ||
VERTICAL:0, | ||
THREED:0 | ||
TWOD: 0, | ||
VERTICAL: 0, | ||
THREED: 0 | ||
}; | ||
result.DISTANCE = 0; | ||
if (i>0) { | ||
let origin2d = [cmp[i-1].GPS.LATITUDE,cmp[i-1].GPS.LATITUDE]; | ||
let destin2d = [pck.GPS.LATITUDE,pck.GPS.LATITUDE]; | ||
let distance2D = measure(origin2d[0],origin2d[1],destin2d[0],destin2d[1]); | ||
distance2D /= 1000;; | ||
let distanceVert = getElevation(pck) - getElevation(cmp[i-1]); | ||
if (i > 0) { | ||
let origin2d = [cmp[i - 1].GPS.LATITUDE, cmp[i - 1].GPS.LATITUDE]; | ||
let destin2d = [pck.GPS.LATITUDE, pck.GPS.LATITUDE]; | ||
let distance2D = measure(origin2d[0], origin2d[1], destin2d[0], destin2d[1]); | ||
distance2D /= 1000; | ||
let distanceVert = getElevation(pck) - getElevation(cmp[i - 1]); | ||
distanceVert /= 1000; | ||
let distance3D = Math.hypot(distance2D,distanceVert); | ||
let time = (new Date(pck.DATE).getTime()-new Date(cmp[i-1].DATE).getTime())/1000.0;//seconds | ||
time = time == 0 ? 1 : time;//make sure we are not dividing by zero, not sure why sometimes two packets have the same timestamp | ||
time /= 60*60; | ||
accDistance += distance3D*1000; | ||
let distance3D = Math.hypot(distance2D, distanceVert); | ||
let time = 1; //Fallback time, 1 second | ||
if (pck.DATE) { | ||
time = (new Date(pck.DATE).getTime() - new Date(cmp[i - 1].DATE).getTime()) / 1000.0; //seconds | ||
} else if (pck.TIMECODE) { | ||
const parseTC = /(\d\d):(\d\d):(\d\d),(\d{3})/; | ||
let match = pck.TIMECODE.match(parseTC); | ||
const useDate = new Date(0, 0, 0, match[1], match[2], match[3], match[4]); | ||
match = cmp[i - 1].TIMECODE.match(parseTC); | ||
const prevDate = new Date(0, 0, 0, match[1], match[2], match[3], match[4]); | ||
time = (new Date(useDate).getTime() - new Date(prevDate).getTime()) / 1000.0; //seconds | ||
} | ||
time = time == 0 ? 1 : time; //make sure we are not dividing by zero, not sure why sometimes two packets have the same timestamp | ||
time /= 60 * 60; | ||
accDistance += distance3D * 1000; | ||
result.DISTANCE = accDistance; | ||
@@ -94,5 +127,5 @@ result.SPEED.TWOD = distance2D / time; | ||
return computed; | ||
} | ||
}; | ||
let computeStats = function (arr) { | ||
let computeStats = function(arr) { | ||
let statsObject = function(obj) { | ||
@@ -102,11 +135,12 @@ if (obj.constructor === Object && Object.keys(obj).length === 0) return null; | ||
for (let elt in obj) { | ||
if (elt !== "TIMECODE") {//IGNORE TIMECODES FOR STATS | ||
if (typeof obj[elt] === "object" && obj[elt] != null) { | ||
if (elt !== 'TIMECODE') { | ||
//IGNORE TIMECODES FOR STATS | ||
if (typeof obj[elt] === 'object' && obj[elt] != null) { | ||
result[elt] = statsObject(obj[elt]); | ||
} else { | ||
result[elt] = { | ||
min:0, | ||
max:0, | ||
avg:0 | ||
} | ||
min: 0, | ||
max: 0, | ||
avg: 0 | ||
}; | ||
} | ||
@@ -116,101 +150,107 @@ } | ||
return result; | ||
} | ||
}; | ||
let recursiveStatsExtraction = function(res, arr) { | ||
let deepEqual = function(a, b) { | ||
if (a === b) return true; | ||
if (a == null || typeof a != "object" || | ||
b == null || typeof b != "object") | ||
return false; | ||
var propsInA = 0, propsInB = 0; | ||
for (var prop in a) | ||
propsInA += 1; | ||
if (a == null || typeof a != 'object' || b == null || typeof b != 'object') return false; | ||
var propsInA = 0, | ||
propsInB = 0; | ||
for (var prop in a) propsInA += 1; | ||
for (var prop in b) { | ||
propsInB += 1; | ||
if (!(prop in a) || !deepEqual(a[prop], b[prop])) | ||
return false; | ||
if (!(prop in a) || !deepEqual(a[prop], b[prop])) return false; | ||
} | ||
return propsInA == propsInB; | ||
} | ||
}; | ||
let result = res; | ||
for (let elt in result) { | ||
let select = arr.map(pck => pck[elt]); | ||
if (elt === "HOME") { //fill fields that do not use standard stats | ||
if (elt === 'HOME') { | ||
//fill fields that do not use standard stats | ||
let allHomes = []; | ||
select.reduce((acc,val) => {//save different homes if present | ||
if (!deepEqual(val,acc)) allHomes.push(val); | ||
select.reduce((acc, val) => { | ||
//save different homes if present | ||
if (!deepEqual(val, acc)) allHomes.push(val); | ||
return val; | ||
},[]); | ||
}, []); | ||
result[elt] = allHomes; | ||
} else if (elt === "DATE") { | ||
} else if (elt === 'DATE') { | ||
result[elt] = select[0]; | ||
} else if (elt === "TIMECODE") { | ||
} else if (elt === 'TIMECODE') { | ||
//DO NOTHING | ||
} else if (typeof select[0] === "object" && select[0] != null) { | ||
} else if (typeof select[0] === 'object' && select[0] != null) { | ||
recursiveStatsExtraction(result[elt], select); | ||
} else { | ||
result[elt].min = select.reduce((acc,val) => val < acc && isNum(val) ? val : acc,Infinity); | ||
result[elt].max = select.reduce((acc,val) => val > acc && isNum(val) ? val : acc,-Infinity); | ||
result[elt].avg = select.reduce((acc,val) => isNum(val) ? acc+val : acc ,0)/select.length; | ||
result[elt].min = select.reduce((acc, val) => (val < acc && isNum(val) ? val : acc), Infinity); | ||
result[elt].max = select.reduce((acc, val) => (val > acc && isNum(val) ? val : acc), -Infinity); | ||
result[elt].avg = select.reduce((acc, val) => (isNum(val) ? acc + val : acc), 0) / select.length; | ||
} | ||
} | ||
return result; | ||
} | ||
}; | ||
let result = statsObject(arr[0]); | ||
if (result.constructor === Object && Object.keys(result).length === 0) return null; | ||
result = recursiveStatsExtraction(result,arr); | ||
if (arr[arr.length-1].DIFFTIME != undefined) { | ||
result.DURATION = arr[arr.length-1].DIFFTIME; //duration of video in milliseconds | ||
} else if (arr[arr.length-1].DATE != undefined) { | ||
result.DURATION = (new Date(arr[arr.length-1].DATE) - new Date(arr[0].DATE)); //duration of video in milliseconds | ||
result = recursiveStatsExtraction(result, arr); | ||
if (arr[arr.length - 1].DIFFTIME != undefined) { | ||
result.DURATION = arr[arr.length - 1].DIFFTIME; //duration of video in milliseconds | ||
} else if (arr[arr.length - 1].DATE != undefined) { | ||
result.DURATION = new Date(arr[arr.length - 1].DATE) - new Date(arr[0].DATE); //duration of video in milliseconds | ||
} | ||
if (arr[arr.length-1].DISTANCE) { | ||
result.DISTANCE = arr[arr.length-1].DISTANCE ; //dsitance of flight in meters | ||
if (arr[arr.length - 1].DISTANCE != null) { | ||
result.DISTANCE = arr[arr.length - 1].DISTANCE; //dsitance of flight in meters | ||
} | ||
return result; | ||
} | ||
let interpretPacket = function (pck) { | ||
let interpretItem = function (key,datum) {//interprets known values to most useful data type | ||
}; | ||
let interpretPacket = function(pck) { | ||
let interpretItem = function(key, datum) { | ||
//interprets known values to most useful data type | ||
let interpretedI = {}; | ||
if (key.toUpperCase() === "GPS") { | ||
if (key.toUpperCase() === 'GPS') { | ||
interpretedI = { | ||
LATITUDE:isNum(datum[1]) ? Number(datum[1]) : "n/a", | ||
LONGITUDE:isNum(datum[0]) ? Number(datum[0]) : "n/a", | ||
ALTITUDE:isNum(datum[2]) ? Number(datum[2]) : "n/a" | ||
LATITUDE: isNum(datum[1]) ? Number(datum[1]) : 'n/a', | ||
LONGITUDE: isNum(datum[0]) ? Number(datum[0]) : 'n/a', | ||
ALTITUDE: isNum(datum[2]) ? Number(datum[2]) : 'n/a' | ||
}; | ||
} else if (key.toUpperCase() === "HOME") { | ||
} else if (key.toUpperCase() === 'HOME') { | ||
interpretedI = { | ||
LATITUDE:Number(datum[1]), | ||
LONGITUDE:Number(datum[0]) | ||
LATITUDE: Number(datum[1]), | ||
LONGITUDE: Number(datum[0]) | ||
}; | ||
} else if (key.toUpperCase() === "TIMECODE"){ | ||
} else if (key.toUpperCase() === 'TIMECODE') { | ||
interpretedI = datum; | ||
} else if (key.toUpperCase() === "DATE") { | ||
} else if (key.toUpperCase() === 'DATE') { | ||
const isoDateRegex = /[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{3})?Z/; | ||
let date = datum; | ||
if (!isoDateRegex.exec(datum)) date = datum.replace(/\./g,"-").replace(" ","T").replace(/-([0-9](\b|[a-zA-Z]))/g,"-0$1").replace(/:(\w{3})-(\w{3})$/g,".$1"); | ||
if (!isoDateRegex.exec(datum)) | ||
date = datum | ||
.replace(/\./g, '-') | ||
.replace(' ', 'T') | ||
.replace(/-([0-9](\b|[a-zA-Z]))/g, '-0$1') | ||
.replace(/:(\w{3})-(\w{3})$/g, '.$1'); | ||
interpretedI = new Date(date).getTime(); | ||
} else if (key.toUpperCase() === "EV") { | ||
} else if (key.toUpperCase() === 'EV') { | ||
interpretedI = eval(datum); | ||
} else if (key.toUpperCase() === "SHUTTER") { | ||
interpretedI = Number(datum.replace("1/", "")); | ||
} else if (key.toUpperCase() === "FNUM" && Number(datum) > 50) { //convert f numbers represented like 280 | ||
} else if (key.toUpperCase() === 'SHUTTER') { | ||
interpretedI = Number(datum.replace('1/', '')); | ||
} else if (key.toUpperCase() === 'FNUM' && Number(datum) > 50) { | ||
//convert f numbers represented like 280 | ||
interpretedI = Number(datum) / 100; | ||
} else { | ||
interpretedI = Number(datum.replace(/[a-zA-Z]/g, "")); | ||
interpretedI = Number(datum.replace(/[a-zA-Z]/g, '')); | ||
} | ||
if (interpretedI.constructor === Object && Object.keys(interpretedI).length === 0) return null; | ||
return interpretedI; | ||
} | ||
}; | ||
let fillMissingFields = function(pckt) { | ||
let replaceKey = function(o,old_key,new_key) { | ||
let replaceKey = function(o, old_key, new_key) { | ||
if (old_key !== new_key) { | ||
Object.defineProperty(o, new_key, | ||
Object.getOwnPropertyDescriptor(o, old_key)); | ||
delete o[old_key]; | ||
Object.defineProperty(o, new_key, Object.getOwnPropertyDescriptor(o, old_key)); | ||
delete o[old_key]; | ||
} | ||
} | ||
let references = {//translate keys form various formats | ||
SHUTTER:["TV"], | ||
FNUM:["IR"] | ||
}; | ||
let references = { | ||
//translate keys form various formats | ||
SHUTTER: ['TV', 'SS'], | ||
FNUM: ['IR', 'F'] | ||
}; | ||
for (let key in references) { | ||
@@ -220,3 +260,3 @@ if (pckt[key] == undefined) { | ||
if (pckt[match] != undefined) { | ||
replaceKey(pckt,match,key); | ||
replaceKey(pckt, match, key); | ||
} | ||
@@ -226,12 +266,12 @@ }); | ||
} | ||
let latitude = pckt["LATITUDE"]; //Mavic 2 style | ||
let longitude = pckt["LONGITUDE"] || pckt["LONGTITUDE"]; | ||
let altitude = pckt["ALTITUDE"] || pckt["BAROMETER"]; | ||
if (latitude != undefined && longitude != undefined ) { | ||
let longitude = pckt["LONGITUDE"] || pckt["LONGTITUDE"]; | ||
let latitude = pckt['LATITUDE']; //Mavic 2 style | ||
let longitude = pckt['LONGITUDE'] || pckt['LONGTITUDE']; | ||
let altitude = pckt['ALTITUDE'] || pckt['BAROMETER']; | ||
if (latitude != undefined && longitude != undefined) { | ||
let longitude = pckt['LONGITUDE'] || pckt['LONGTITUDE']; | ||
pckt.GPS = { | ||
LONGITUDE:longitude, | ||
LATITUDE:latitude | ||
} | ||
if (altitude != undefined ) { | ||
LONGITUDE: longitude, | ||
LATITUDE: latitude | ||
}; | ||
if (altitude != undefined) { | ||
pckt.GPS.ALTITUDE = altitude; | ||
@@ -243,6 +283,6 @@ } else { | ||
return pckt; | ||
} | ||
}; | ||
let interpretedP = {}; | ||
for (let item in pck) { | ||
interpretedP[item.toUpperCase()] = interpretItem(item,pck[item]); | ||
interpretedP[item.toUpperCase()] = interpretItem(item, pck[item]); | ||
} | ||
@@ -252,12 +292,13 @@ interpretedP = fillMissingFields(interpretedP); | ||
return interpretedP; | ||
} | ||
let smoothenGPS = function(arr,amount) { //averages positions with the specified surrounding seconds. Necessary due to DJI's SRT logs low precision | ||
}; | ||
let smoothenGPS = function(arr, amount) { | ||
//averages positions with the specified surrounding seconds. Necessary due to DJI's SRT logs low precision | ||
let smoothArr = JSON.parse(JSON.stringify(arr)); | ||
for (let i=0; i<arr.length; i++) { | ||
let start = parseInt(i-amount); | ||
let end = parseInt(i+amount); | ||
for (let i = 0; i < arr.length; i++) { | ||
let start = parseInt(i - amount); | ||
let end = parseInt(i + amount); | ||
let sums = { | ||
LATITUDE:0, | ||
LONGITUDE:0, | ||
ALTITUDE:0 | ||
LATITUDE: 0, | ||
LONGITUDE: 0, | ||
ALTITUDE: 0 | ||
}; | ||
@@ -267,9 +308,9 @@ let latSkips = 0; | ||
let altSkips = 0; | ||
for (let j=start; j<end; j++) { | ||
let k = Math.max(Math.min(j,arr.length-1),0); | ||
for (let j = start; j < end; j++) { | ||
let k = Math.max(Math.min(j, arr.length - 1), 0); | ||
if (isNum(arr[k].GPS.LATITUDE)) { | ||
sums.LATITUDE += arr[k].GPS.LATITUDE | ||
sums.LATITUDE += arr[k].GPS.LATITUDE; | ||
} else { | ||
latSkips++; | ||
}; | ||
} | ||
if (isNum(arr[k].GPS.LONGITUDE)) { | ||
@@ -286,21 +327,22 @@ sums.LONGITUDE += arr[k].GPS.LONGITUDE; | ||
} | ||
smoothArr[i].GPS.LATITUDE = sums.LATITUDE/(amount*2-latSkips); | ||
smoothArr[i].GPS.LONGITUDE = sums.LONGITUDE/(amount*2-lonSkips); | ||
smoothArr[i].GPS.ALTITUDE = sums.ALTITUDE/(amount*2-altSkips); | ||
smoothArr[i].GPS.LATITUDE = sums.LATITUDE / (amount * 2 - latSkips); | ||
smoothArr[i].GPS.LONGITUDE = sums.LONGITUDE / (amount * 2 - lonSkips); | ||
smoothArr[i].GPS.ALTITUDE = sums.ALTITUDE / (amount * 2 - altSkips); | ||
} | ||
return smoothArr; | ||
} | ||
}; | ||
let newArr = arr.map(pck => interpretPacket(pck)); | ||
for (let i = 1; i<newArr.length; i++) {//loop back and forth to fill missing gps data with neighbours | ||
for (let i = 1; i < newArr.length; i++) { | ||
//loop back and forth to fill missing gps data with neighbours | ||
if (newArr[i].GPS) { | ||
if (!isNum(newArr[i].GPS.LATITUDE)) newArr[i].GPS.LATITUDE = newArr[i-1].GPS.LATITUDE; | ||
if (!isNum(newArr[i].GPS.LONGITUDE)) newArr[i].GPS.LONGITUDE = newArr[i-1].GPS.LONGITUDE; | ||
if (newArr[i].GPS.ALTITUDE && !isNum(newArr[i].GPS.ALTITUDE)) ewArr[i].GPS.ALTITUDE = newArr[i-1].GPS.ALTITUDE; | ||
if (!isNum(newArr[i].GPS.LATITUDE)) newArr[i].GPS.LATITUDE = newArr[i - 1].GPS.LATITUDE; | ||
if (!isNum(newArr[i].GPS.LONGITUDE)) newArr[i].GPS.LONGITUDE = newArr[i - 1].GPS.LONGITUDE; | ||
if (newArr[i].GPS.ALTITUDE && !isNum(newArr[i].GPS.ALTITUDE)) ewArr[i].GPS.ALTITUDE = newArr[i - 1].GPS.ALTITUDE; | ||
} | ||
} | ||
for (let i = newArr.length-2; i>=0; i--) { | ||
for (let i = newArr.length - 2; i >= 0; i--) { | ||
if (newArr[i].GPS) { | ||
if (!isNum(newArr[i].GPS.LATITUDE)) newArr[i+1].GPS.LATITUDE; | ||
if (!isNum(newArr[i].GPS.LONGITUDE)) newArr[i+1].GPS.LONGITUDE; | ||
if (newArr[i].GPS.ALTITUDE != null && !isNum(newArr[i].GPS.ALTITUDE)) newArr[i+1].GPS.ALTITUDE; | ||
if (!isNum(newArr[i].GPS.LATITUDE)) newArr[i + 1].GPS.LATITUDE; | ||
if (!isNum(newArr[i].GPS.LONGITUDE)) newArr[i + 1].GPS.LONGITUDE; | ||
if (newArr[i].GPS.ALTITUDE != null && !isNum(newArr[i].GPS.ALTITUDE)) newArr[i + 1].GPS.ALTITUDE; | ||
} | ||
@@ -311,4 +353,4 @@ } | ||
if (newArr[0].GPS) { | ||
if (smoothing !== 0) { | ||
newArr = smoothenGPS(newArr,smoothing); | ||
if (smoothing !== 0) { | ||
newArr = smoothenGPS(newArr, smoothing); | ||
} | ||
@@ -320,12 +362,13 @@ this.smoothened = smoothing; | ||
if (newArr.length < 1) { | ||
console.log("Error intrerpreting metadata"); | ||
console.log('Error intrerpreting metadata'); | ||
return null; | ||
} | ||
return { | ||
packets:newArr, | ||
stats:stats | ||
} | ||
} | ||
packets: newArr, | ||
stats: stats | ||
}; | ||
}; | ||
function getElevation(src) {//GPS elevation data is almost useless, so we replace with barometer if available | ||
function getElevation(src) { | ||
//GPS elevation data is almost useless, so we replace with barometer if available | ||
if (src.BAROMETER != undefined) { | ||
@@ -344,48 +387,48 @@ return src.BAROMETER; | ||
DJI_SRT_Parser.prototype.createCSV = function(raw) { | ||
let csvExtract = function(obj,pre,val) { | ||
let prefix = pre ? pre+"_" : ""; | ||
let csvExtract = function(obj, pre, val) { | ||
let prefix = pre ? pre + '_' : ''; | ||
let results = []; | ||
for (let elt in obj) { | ||
if (typeof obj[elt] === "object" && obj[elt] != null) { | ||
let children = csvExtract(obj[elt],prefix+elt,val); | ||
children.forEach(child => results.push(child)); | ||
} else if (val) { | ||
results.push(JSON.stringify(obj[elt])); | ||
} else { | ||
results.push(prefix+elt); | ||
} | ||
if (typeof obj[elt] === 'object' && obj[elt] != null) { | ||
let children = csvExtract(obj[elt], prefix + elt, val); | ||
children.forEach(child => results.push(child)); | ||
} else if (val) { | ||
results.push(JSON.stringify(obj[elt])); | ||
} else { | ||
results.push(prefix + elt); | ||
} | ||
} | ||
return results.length ? results : null; | ||
} | ||
}; | ||
let rows = []; | ||
let array = raw ? this.rawMetadata : this.metadata.packets; | ||
rows.push(csvExtract(array[0])); | ||
array.forEach(pck => rows.push(csvExtract(pck,"",true))); | ||
if (rows.length <1) return null; | ||
array.forEach(pck => rows.push(csvExtract(pck, '', true))); | ||
if (rows.length < 1) return null; | ||
let csvContent; | ||
csvContent = rows.reduce((acc,rowArray) => { | ||
let row = rowArray.join(","); | ||
return acc + row + "\r\n"; | ||
},""); | ||
csvContent = rows.reduce((acc, rowArray) => { | ||
let row = rowArray.join(','); | ||
return acc + row + '\r\n'; | ||
}, ''); | ||
if (!csvContent) { | ||
console.log("Error creating CSV"); | ||
console.log('Error creating CSV'); | ||
return null; | ||
} | ||
return csvContent; | ||
} | ||
}; | ||
DJI_SRT_Parser.prototype.createGeoJSON = function(raw) { | ||
function GeoJSONExtract(obj,raw) { | ||
let extractProps = function(childObj,pre) { | ||
function GeoJSONExtract(obj, raw) { | ||
let extractProps = function(childObj, pre) { | ||
let results = []; | ||
for (let child in childObj) { | ||
if (typeof childObj[child] === "object" && childObj[child] != null ) { | ||
let children = extractProps(childObj[child],pre + "_" + child); | ||
if (typeof childObj[child] === 'object' && childObj[child] != null) { | ||
let children = extractProps(childObj[child], pre + '_' + child); | ||
children.forEach(child => results.push(child)); | ||
} else { | ||
results.push({name: pre + "_" + child, value: childObj[child]}); | ||
results.push({ name: pre + '_' + child, value: childObj[child] }); | ||
} | ||
} | ||
return results; | ||
} | ||
}; | ||
let extractCoordinates = function(coordsObj) { | ||
@@ -403,7 +446,7 @@ let coordResult = []; | ||
return coordResult; | ||
} | ||
}; | ||
let result = { | ||
type: "Feature", | ||
type: 'Feature', | ||
geometry: { | ||
type: "Point", | ||
type: 'Point', | ||
coordinates: [] | ||
@@ -415,16 +458,16 @@ }, | ||
for (let elt in obj) { | ||
if (elt === "DATE") { | ||
if (elt === 'DATE') { | ||
result.properties.timestamp = obj[elt]; | ||
} else if (elt === "GPS") { | ||
} else if (elt === 'GPS') { | ||
result.geometry.coordinates = extractCoordinates(obj[elt]); | ||
// } else if (elt === "HOME") {//no, readers don't understand it | ||
// result.properties.HOME = { | ||
// type: "Feature", | ||
// geometry: { | ||
// type: "Point", | ||
// coordinates: extractCoordinates(obj[elt]) | ||
// }, | ||
// }; | ||
} else if (typeof obj[elt] === "object" && obj[elt] != null) { | ||
let children = extractProps(obj[elt],elt); | ||
// } else if (elt === "HOME") {//no, readers don't understand it | ||
// result.properties.HOME = { | ||
// type: "Feature", | ||
// geometry: { | ||
// type: "Point", | ||
// coordinates: extractCoordinates(obj[elt]) | ||
// }, | ||
// }; | ||
} else if (typeof obj[elt] === 'object' && obj[elt] != null) { | ||
let children = extractProps(obj[elt], elt); | ||
children.forEach(child => { | ||
@@ -438,3 +481,4 @@ result.properties[child.name] = child.value; | ||
if (!raw) {//only modify elevation if user does want interpreted data, not raw | ||
if (!raw) { | ||
//only modify elevation if user does want interpreted data, not raw | ||
let bestElevation = getElevation(obj); | ||
@@ -449,18 +493,17 @@ if (bestElevation != null) { | ||
let GeoJSONContent = { | ||
type: "FeatureCollection", | ||
type: 'FeatureCollection', | ||
features: [] | ||
} | ||
}; | ||
let array = raw ? this.rawMetadata : this.metadata.packets; | ||
array.forEach(pck => GeoJSONContent.features.push(GeoJSONExtract(pck,raw))); | ||
array.forEach(pck => GeoJSONContent.features.push(GeoJSONExtract(pck, raw))); | ||
let createLinestring = function(features) { | ||
let result = { | ||
type: "Feature", | ||
type: 'Feature', | ||
properties: { | ||
"source": "dji-srt-parser", | ||
timestamp: [], | ||
source: 'dji-srt-parser', | ||
timestamp: [] | ||
}, | ||
geometry: { | ||
type: "LineString", | ||
type: 'LineString', | ||
coordinates: [] | ||
@@ -472,3 +515,17 @@ } | ||
for (let prop in props) { | ||
if (!["DATE","TIMECODE","GPS","timestamp","BAROMETER","DISTANCE","SPEED_THREED","SPEED_TWOD","SPEED_VERTICAL","HB","HS"].includes(prop)) { | ||
if ( | ||
![ | ||
'DATE', | ||
'TIMECODE', | ||
'GPS', | ||
'timestamp', | ||
'BAROMETER', | ||
'DISTANCE', | ||
'SPEED_THREED', | ||
'SPEED_TWOD', | ||
'SPEED_VERTICAL', | ||
'HB', | ||
'HS' | ||
].includes(prop) | ||
) { | ||
result.properties[prop] = props[prop]; | ||
@@ -483,3 +540,3 @@ } | ||
return result; | ||
} | ||
}; | ||
@@ -489,3 +546,3 @@ GeoJSONContent.features.push(createLinestring(GeoJSONContent.features)); | ||
if (!GeoJSONContent.features) { | ||
console.log("Error creating GeoJSON"); | ||
console.log('Error creating GeoJSON'); | ||
return null; | ||
@@ -495,5 +552,5 @@ } | ||
return JSON.stringify(GeoJSONContent); | ||
} | ||
}; | ||
DJI_SRT_Parser.prototype.loadFile = function(data,fileName,preparedData) { | ||
DJI_SRT_Parser.prototype.loadFile = function(data, fileName, preparedData) { | ||
let context = this; | ||
@@ -503,14 +560,17 @@ this.fileName = fileName; | ||
let decode = function(d) { | ||
if (typeof d ==="string" && d.split(",")[0].includes("base64")) { | ||
return atob(d.split(",")[1]); | ||
if (typeof d === 'string' && d.split(',')[0].includes('base64')) { | ||
return atob(d.split(',')[1]); | ||
} else { | ||
return d; | ||
} | ||
} | ||
this.flow(decode(data),preparedData); | ||
} | ||
}; | ||
this.flow( | ||
decode(data), | ||
preparedData | ||
); | ||
}; | ||
DJI_SRT_Parser.prototype.flow = function(data,preparedData) { | ||
DJI_SRT_Parser.prototype.flow = function(data, preparedData) { | ||
if (preparedData) { | ||
this.rawMetadata = JSON.parse(data); | ||
this.rawMetadata = JSON.parse(preparedData); | ||
} else { | ||
@@ -521,6 +581,6 @@ this.rawMetadata = this.srtToObject(data); | ||
this.loaded = true; | ||
} | ||
}; | ||
function notReady() { | ||
console.log("Data not ready"); | ||
console.log('Data not ready'); | ||
return null; | ||
@@ -533,20 +593,38 @@ } | ||
function toExport(context,file,fileName,preparedData) { | ||
context.loadFile(file,fileName,preparedData); | ||
function toExport(context, file, fileName, preparedData) { | ||
context.loadFile(file, fileName, preparedData); | ||
return { | ||
getSmoothing:function() {return context.loaded ? context.smoothened : notReady()}, | ||
setSmoothing:function(smooth) {if (context.loaded) {context.metadata = context.interpretMetadata(context.rawMetadata,smooth)} else {notReady()}}, | ||
rawMetadata:function() {return context.loaded ? context.rawMetadata : notReady()}, | ||
metadata:function() {return context.loaded ? context.metadata : notReady()}, | ||
toCSV:function(raw) {return context.loaded ? context.createCSV(raw) : notReady()}, | ||
toGeoJSON:function(raw) {return context.loaded ? context.createGeoJSON(raw) : notReady()}, | ||
getFileName:function() {return context.loaded ? context.fileName : notReady()} | ||
} | ||
getSmoothing: function() { | ||
return context.loaded ? context.smoothened : notReady(); | ||
}, | ||
setSmoothing: function(smooth) { | ||
if (context.loaded) { | ||
context.metadata = context.interpretMetadata(context.rawMetadata, smooth); | ||
} else { | ||
notReady(); | ||
} | ||
}, | ||
rawMetadata: function() { | ||
return context.loaded ? context.rawMetadata : notReady(); | ||
}, | ||
metadata: function() { | ||
return context.loaded ? context.metadata : notReady(); | ||
}, | ||
toCSV: function(raw) { | ||
return context.loaded ? context.createCSV(raw) : notReady(); | ||
}, | ||
toGeoJSON: function(raw) { | ||
return context.loaded ? context.createGeoJSON(raw) : notReady(); | ||
}, | ||
getFileName: function() { | ||
return context.loaded ? context.fileName : notReady(); | ||
} | ||
}; | ||
} | ||
function create_DJI_SRT_Parser(file,fileName,preparedData) { | ||
function create_DJI_SRT_Parser(file, fileName, preparedData) { | ||
try { | ||
var instance = new DJI_SRT_Parser(); | ||
return toExport(instance,file,fileName,preparedData); | ||
return toExport(instance, file, fileName, preparedData); | ||
} catch (err) { | ||
@@ -553,0 +631,0 @@ console.log(err); |
@@ -104,1 +104,13 @@ function preload(file) { | ||
}); | ||
//p4p attempt | ||
data = preload(`./samples/p4p_sample.SRT`); | ||
let p4p = DJISRTParser(data, 'p4p_sample.SRT'); | ||
test('p4p Format result should contain metadata', () => { | ||
expect(p4p.metadata()).toBeDefined(); | ||
}); | ||
test('The max altitude should be readable from the ALTITUDE field', () => { | ||
expect(p4p.metadata().stats.GPS.ALTITUDE.max).toBe(18); | ||
}); | ||
test('We should be able to read the aperture in the p4p format', () => { | ||
expect(p4p.metadata().packets[0].FNUM).toBe(3.2); | ||
}); |
{ | ||
"name": "dji_srt_parser", | ||
"version": "1.0.30", | ||
"version": "1.0.31", | ||
"description": "Parses and interprets DJI's drones SRT metadata", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -7,3 +7,2 @@ 'use_strict'; | ||
let rawFile = new XMLHttpRequest(); | ||
let allText; | ||
rawFile.open('GET', file, true); | ||
@@ -39,3 +38,3 @@ rawFile.onreadystatechange = function() { | ||
let files = ['mavic_pro', 'mavic_air', 'old_format', 'mavic_pro_buggy', 'mavic_2_style']; | ||
let files = ['mavic_pro', 'mavic_air', 'old_format', 'mavic_pro_buggy', 'mavic_2_style', 'p4p_sample']; | ||
let DJISRTParser = require('../index'); | ||
@@ -63,2 +62,3 @@ let i = 0; | ||
let elevation; | ||
if (DJIfile.metadata().stats.BAROMETER) { | ||
@@ -65,0 +65,0 @@ elevation = DJIfile.metadata().stats.BAROMETER.max; |
331085
13
810