agmission/Development/server/helpers/work_record.js

357 lines
13 KiB
JavaScript

const utils = require('./utils'),
RecTypes = require('./constants').RecTypes;
class DataUtil {
constructor() { }
static readAgnBinary(buf, offset, recType = RecTypes.AGN_BIN_LQD) {
switch (buf[offset + 2]) {
case 8:
case 5:
case RecTypes.AGN_BIN_LQD:
return DataUtil._readAgnBinary(buf, offset, recType);
case 6:
return DataUtil._readAmsRpm(buf, offset, recType);
}
return null;
}
static _readAgnBinary(buf, offset, recType = RecTypes.AGN_BIN_LQD) {
const header02 = buf[offset + 2];
try {
const rec = new WorkRecord(RecTypes.AGN_BIN_LQD);
offset += 3;
// Hundredths of seconds since midnight of the record day originally but will be converted to seconds (after decoded)
rec.gpsTime = buf.readInt32LE(offset); offset += 4;
rec.lat = buf.readInt32LE(offset); offset += 4;
rec.lon = buf.readInt32LE(offset); offset += 4;
rec.tslu = buf.readUInt8(offset); offset += 1;
rec.llnum = buf.readUInt16LE(offset); offset += 2;
rec.xTrack = buf.readInt32LE(offset); offset += 4;
rec.grSpeed = buf.readUInt16LE(offset); offset += 2;
rec.alt = buf.readInt16LE(offset); offset += 2;
rec.timeAdv = buf.readInt16LE(offset); offset += 2;
rec.utmX = utils.fixedTo(buf.readInt32LE(offset), 2); offset += 4;
rec.utmY = utils.fixedTo(buf.readInt32LE(offset), 2); offset += 4;
rec.swath = buf.readInt16LE(offset); offset += 2;
rec.noAC = buf.readUInt8(offset); offset += 1;
rec.sprayStat = buf.readUInt8(offset); offset += 1;
rec.head = buf.readUInt8(offset); offset += 1;
rec.stdHdop = buf.readUInt8(offset); offset += 1;
rec.satsIn = buf.readUInt8(offset); offset += 1;
rec.lminApp = buf.readUInt16LE(offset); offset += 2; // Values from files: LIQUID: L/Min * 100, DRY: KG/HA * 1 => L/Min or Kg/Ha
rec.lminReq = buf.readUInt16LE(offset); offset += 2; // Values from files: LIQUID: L/Min * 100, DRY: KG/HA * 1 => L/Min or Kg/Ha
if (recType === RecTypes.AGN_BIN_LQD) {
if (header02 === 5) {
rec.haPerMin = buf.readUInt8(offset); offset += 1;
// Decode it immediately so following ones can use its value in ha/min
if (rec.haPerMin > 0)
rec.haPerMin = rec.haPerMin * 1E-1; // Sprayed ha * 10/minutes => ha/min
rec.calSens = buf.readUInt8(offset); offset += 1;
} else {
rec.lhaReq = buf.readUInt16LE(offset); offset += 2;
}
rec.calcodeFreq = buf.readUInt16LE(offset); offset += 2;
rec.sprayHeight = buf.readInt16LE(offset); offset += 2;
rec.windSpd = buf.readInt16LE(offset); offset += 2;
rec.windDir = buf.readInt16LE(offset); offset += 2;
rec.temp = buf.readInt16LE(offset); offset += 2;
rec.humid = buf.readUInt8(offset); offset += 1;
rec.driftX = buf.readInt16LE(offset); offset += 2;
rec.driftY = buf.readInt16LE(offset); offset += 2;
rec.depositX = buf.readInt16LE(offset); offset += 2;
rec.depositY = buf.readInt16LE(offset); offset += 2;
// Decode
if (header02 === 5) {
rec.lhaReq = rec.haPerMin ? (rec.lminReq / rec.haPerMin) : 0;
}
else if (header02 === 8) {
// Important: apply precision rule for these field with record 08 only (when not using AgFlow)
if (rec.lminReq > 0) rec.lminReq = rec.lminReq * 1E-2;
if (rec.lminApp > 0) rec.lminApp = rec.lminApp * 1E-2;
if (rec.lhaReq > 0) rec.lhaReq = rec.lhaReq * 1E-2;
}
}
else {
rec.avgRpm = buf.readUInt8(offset); offset += 1;
rec.calSens = buf.readUInt8(offset); offset += 1;
rec.calcodeFreq = buf.readUInt16LE(offset); offset += 2;
rec.sprayHeight = buf.readInt16LE(offset); offset += 2;
rec.windSpd = buf.readInt16LE(offset); offset += 2;
rec.windDir = buf.readInt16LE(offset); offset += 2;
rec.temp = buf.readInt16LE(offset); offset += 2;
rec.humid = buf.readUInt8(offset); offset += 1;
rec.driftX = buf.readInt16LE(offset); offset += 2;
rec.driftY = buf.readInt16LE(offset); offset += 2;
rec.realXT = buf.readInt16LE(offset); offset += 2;
rec.valvePos = buf.readInt16LE(offset); offset += 2;
// Decode
}
// Common decode
if (rec.lat !== 0)
rec.lat = rec.lat * 1E-6;
if (rec.lon !== 0)
rec.lon = rec.lon * 1E-6;
if (rec.grSpeed > 0) rec.grSpeed = rec.grSpeed * 1E-2; // cm/s => m/s
if (rec.timeAdv > 0) rec.timeAdv = rec.timeAdv * 1E-2;
if (header02 === 8 || header02 === 5) {
rec.utmX = rec.utmX * 1E-1;
rec.utmY = rec.utmY * 1E-1;
if (rec.swath > 0) rec.swath = rec.swath * 1E-1; // dm => m
}
if (rec.head > 0) {
// Convert back to degree = val * 360/256, before (when write) from deg to 1 byte: deg * 256/360;
rec.head = utils.fixedTo(rec.head * 1.40625, 1);
}
if (rec.stdHdop > 100) rec.stdHdop = rec.stdHdop - 100;
if (rec.windSpd > 0) rec.windSpd = rec.windSpd * 1E-1;
if (rec.windDir > 0) rec.windDir = rec.windDir * 1E-1;
if (utils.isNumber(rec.temp)) rec.temp = rec.temp * 1E-1;
if (rec.sprayHeight > 0) rec.sprayHeight = rec.sprayHeight * 1E-1;
rec.gpsTime = rec.gpsTime * 1E-2; // Convert to seconds from hundredths of seconds
// Decimal format truncate
rec.lminReq = utils.fixedTo(rec.lminReq, 2);
rec.lminApp = utils.fixedTo(rec.lminApp, 2);
rec.lhaReq = utils.fixedTo(rec.lhaReq, 2);
rec.gpsTime = utils.fixedTo(rec.gpsTime, 2);
return rec;
} catch (error) {
return null;
}
}
static _readAmsRpm(buf, offset, matType = RecTypes.AGN_BIN_LQD) {
if (!buf)
return null;
try {
const rec = new WorkRecord(RecTypes.AGN_AMS);
offset += 3; // Headers, GPTime, Swath, lminApp, lminReq, lhaReq
rec.gpsTime = buf.readInt32LE(offset); offset += 4; // GPSTime * 100
rec.swath = buf.readInt16LE(offset); offset += 2; // in cm
rec.lminApp = buf.readUInt16LE(offset); offset += 2; // LIQUID: L/Min * 100, DRY: KG/HA * 100
rec.lminReq = buf.readUInt16LE(offset); offset += 2; // LIQUID: L/Min * 100, DRY: KG/HA * 100
rec.applicRate = (buf.readUInt16LE(offset) || 0); offset += 2;
rec.rpm = [];
rec.rpm[0] = (buf.readUInt16LE(offset) || 0); offset += 2;
rec.rpm[1] = (buf.readUInt16LE(offset) || 0); offset += 2;
rec.rpm[2] = (buf.readUInt16LE(offset) || 0); offset += 2;
rec.rpm[3] = (buf.readUInt16LE(offset) || 0); offset += 2;
rec.rpm[4] = (buf.readUInt16LE(offset) || 0); offset += 2;
rec.rpm[5] = (buf.readUInt16LE(offset) || 0); offset += 2;
rec.rpm[6] = (buf.readUInt16LE(offset) || 0); offset += 2;
rec.rpm[7] = (buf.readUInt16LE(offset) || 0); offset += 2;
rec.rpm[8] = (buf.readUInt16LE(offset) || 0); offset += 2;
rec.rpm[9] = (buf.readUInt16LE(offset) || 0); offset += 2;
rec.rpm[10] = (buf.readUInt16LE(offset) || 0); offset += 2;
rec.rpm[11] = (buf.readUInt16LE(offset) || 0); offset += 2;
rec.psi = buf.readUInt16LE(offset); offset += 2;
rec.gpsAlt = buf.readInt16LE(offset) || 0; offset += 2;
rec.radarAlt = buf.readInt16LE(offset) || 0; offset += 2;
rec.raserAlt = buf.readInt16LE(offset) || 0; offset += 2;
if (matType === RecTypes.AGN_BIN_DRY) {
rec.xTrack = buf.readInt32LE(offset); offset += 4; // cm
rec.weight = buf.readUInt16LE(offset); offset += 2; // Kg
// Decode
if (rec.xTrack != 0) rec.xTrack = rec.xTrack * 1E-2; // => cm => m
if (rec.rpm[4]) rec.rpm[4] *= 1E-2; // VOLTIN1 volt * 100
if (rec.rpm[5]) rec.rpm[5] *= 1E-2; // VOLTIN2 volt * 100
if (rec.rpm[6]) rec.rpm[6] *= 1E-2; // CALIB1 T/Kg * 100
if (rec.rpm[7]) rec.rpm[7] *= 1E-2; // CALIB2 T/Kg * 100
if (rec.rpm[8]) rec.rpm[8] *= 1E-2; // AMP1 Amp * 100
if (rec.rpm[9]) rec.rpm[9] *= 1E-2; // AMP2 Amp * 100
}
// Common decode
if (rec.gpsTime) rec.gpsTime *= 1E-2;
if (rec.swath) rec.swath *= 1E-2;
if (rec.lminApp) rec.lminApp *= 1E-2;
if (rec.lminReq) rec.lminReq *= 1E-2;
if (rec.applicRate) rec.applicRate *= 1E-2;
if (rec.raserAlt) rec.raserAlt *= 1E-1;
if (rec.psi) rec.psi *= 1E-1;
if (rec.gpsAlt) rec.gpsAlt *= 1E-1;
if (rec.radarAlt) rec.radarAlt *= 1E-1;
// Decimal format
rec.gpsTime = utils.fixedTo(rec.gpsTime, 2);
rec.gpsAlt = utils.fixedTo(rec.gpsAlt, 2);
rec.radarAlt = utils.fixedTo(rec.radarAlt, 2);
rec.raserAlt = utils.fixedTo(rec.raserAlt, 2);
return rec;
} catch (error) {
return null;
}
}
/**
* Determine whether the binary buffer contains readable Agnav binary data
*/
static isValidAgn(buf, fromIdx = 0) {
return (buf && buf.length) && (buf[fromIdx] === 251 && buf[fromIdx + 1] === 251 && (buf[fromIdx + 2] === 8 || buf[fromIdx + 2] === 5 || buf[fromIdx + 2] === 6));
};
static readShpRecord(shpRec) {
const header02 = utils.getProp(shpRec, 'FM_ID', 0);
if (!shpRec || header02 == 0)
return null;
try {
const rec = new WorkRecord(RecTypes.AGN_SHP);
// Total seconds since midnight of the day
rec.gpsTime = Number(utils.getProp(shpRec, 'GPSTIME', 0));
rec.lat = utils.getProp(shpRec, 'LATITUDE', 0);
rec.lon = utils.getProp(shpRec, 'LONGITUDE', 0);
rec.tslu = utils.getProp(shpRec, 'TSLU', 0);
rec.llnum = utils.getProp(shpRec, 'LLNUM', 0);
rec.xTrack = utils.getProp(shpRec, 'XTRACK', 0);
rec.grSpeed = utils.getProp(shpRec, 'GRNDSPEED', 0);
rec.alt = utils.getProp(shpRec, 'ALTITUDE', 0);
rec.timeAdv = utils.getProp(shpRec, 'TIMEADV', 0);
rec.utmX = utils.getProp(shpRec, 'UTMX', 0);
rec.utmY = utils.getProp(shpRec, 'UTMY', 0);
rec.swath = utils.getProp(shpRec, 'SWATHWIDTH', 0);
rec.noAC = 0;
rec.sprayStat = utils.getProp(shpRec, 'SPRAYSTAT', 0);
rec.head = utils.getProp(shpRec, 'HEADING', 0);
rec.stdHdop = utils.getProp(shpRec, 'STDHDOP', 0);
rec.satsIn = utils.getProp(shpRec, 'SATINSIDE', 0);
rec.lminApp = utils.getProp(shpRec, 'LPMA', 0);
rec.lminReq = utils.getProp(shpRec, 'LPMD', 0);
rec.lhaReq = utils.getProp(shpRec, 'LPH', 0);
rec.sens = utils.getProp(shpRec, 'SENS', 0);
rec.calcodeFreq = utils.getProp(shpRec, 'OFFSET', 0);
rec.sprayHeight = utils.getProp(shpRec, 'SPHT', 0);
rec.windSpd = utils.getProp(shpRec, 'WSP', 0);
rec.windDir = utils.getProp(shpRec, 'WDIR', 0);
rec.temp = utils.getProp(shpRec, 'TEMP', 0);
rec.humid = utils.getProp(shpRec, 'HUMID', 0);
rec.driftX = utils.getProp(shpRec, 'DRFX', 0);
rec.driftY = utils.getProp(shpRec, 'DRFY', 0);
rec.depositX = utils.getProp(shpRec, 'DEPX', 0);
rec.depositY = utils.getProp(shpRec, 'DEPY', 0);
// RPM
rec.rpm = [
utils.getProp(shpRec, 'APPRPM1', 0),
utils.getProp(shpRec, 'APPRPM2', 0),
utils.getProp(shpRec, 'TARRPM1', 0),
utils.getProp(shpRec, 'TARRPM2', 0),
utils.getProp(shpRec, 'VOLT', 0) * 1E-2,
0, // 6??
utils.getProp(shpRec, 'T/KG1', 0) * 1E-2,
utils.getProp(shpRec, 'T/KG2', 0) * 1E-2,
utils.getProp(shpRec, 'AMP1', 0) * 1E-2,
utils.getProp(shpRec, 'AMP2', 0) * 1E-2,
0, // 11?
0. // 12?
];
// Decode
if (rec.grSpeed > 0) rec.grSpeed = rec.grSpeed * 0.277778; // km/h => m/s
if (header02 === 8) {
// Important: apply precision rule for these field with record 08 only (when not using AgFlow)
if (rec.lminReq > 0) rec.lminReq = rec.lminReq * 1E-2;
if (rec.lminApp > 0) rec.lminApp = rec.lminApp * 1E-2;
if (rec.lhaReq > 0) rec.lhaReq = rec.lhaReq * 1E-2;
}
if (rec.windSpd > 0) rec.windSpd = rec.windSpd * 0.277778; // km/h => m/s
rec.gpsTime = utils.fixedTo(rec.gpsTime, 2);
rec.grSpeed = utils.fixedTo(rec.grSpeed, 2);
return rec;
} catch (error) {
return null;
}
}
static readSLAscRecord(slRec) {
if (!slRec) return null;
try {
const rec = new WorkRecord(RecTypes.SATLOG);
rec.gpsTime = Number(utils.getProp(slRec, 'Time', ''));
if (!rec.gpsTime) return null;
rec['Time'] = utils.getProp(slRec, 'Time', '');
rec['Date'] = utils.getProp(slRec, 'Date');
rec.lat = utils.getPropNum(slRec, 'Lat', 0);
rec.lon = utils.getPropNum(slRec, 'Lon', 0);
rec.head = utils.getPropNum(slRec, 'Hdg', 0);
rec.alt = utils.getPropNum(slRec, 'Alt', 0);
rec.xTrack = utils.getPropNum(slRec, 'X-Track', 0);
rec.sprayStat = utils.getPropNum(slRec, 'Spray', 0);
rec.grSpeed = utils.getPropNum(slRec, 'Speed', 0);
rec.temp = utils.getPropNum(slRec, 'Temperature', 0);
rec.head = utils.getPropNum(slRec, 'Hdg', 0);
rec.humid = utils.getPropNum(slRec, 'RHumi', 0);
rec.satsIn = utils.getPropNum(slRec, 'SU', 0);
rec.stdHdop = utils.getPropNum(slRec, 'DOP', 0);
// Decode
rec.gpsTime = utils.fixedTo(Number(utils.timeToSeconds(rec.gpsTime)), 2);
return rec;
} catch (error) {
return null;
}
}
static mergeAgnAms(agn, ams) {
const excludes = ['swath', 'type'];
if (!agn || !(agn instanceof WorkRecord) || !(ams instanceof WorkRecord))
return null;
for (let [key, value] of Object.entries(ams)) {
if (!excludes.includes(key) && undefined != value) {
agn[key] = value;
}
}
return agn;
}
}
class WorkRecord {
// Notes: gpsTime must be in string of decimal value to ensure mongo sort by this field properly
constructor(type = RecTypes.UNKNOWN) {
this.type = type;
}
/**
* Change GPS time value
*/
adjustGpsTime(value) {
if (value !== 0) {
this.gpsTime = Number(this.gpsTime) + value;
this.gpsTime = utils.fixedTo(this.gpsTime, 2);
}
}
}
module.exports = { RecTypes, WorkRecord, DataUtil };