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 };