UNPKG

isoxml-angular

Version:

JavaScript library to parse and generate ISOXML (ISO11783-10) files

284 lines (283 loc) 13.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExtendedTimeLog = void 0; const BufferReader_1 = __importDefault(require("./BufferReader")); const baseEntities_1 = require("../../baseEntities"); const classRegistry_1 = require("../../classRegistry"); const ISOXMLManager_1 = require("../../ISOXMLManager"); const xmlManager_1 = require("../../xmlManager"); const utils_1 = require("../../utils"); class ExtendedTimeLog extends baseEntities_1.TimeLog { constructor(attributes, isoxmlManager) { super(attributes, isoxmlManager); this.tag = "TLG" /* TAGS.TimeLog */; this.parsingErrors = []; this.parsedTimeLog = null; } static fromXML(xml, isoxmlManager, internalId) { return __awaiter(this, void 0, void 0, function* () { const entity = yield baseEntities_1.TimeLog.fromXML(xml, isoxmlManager, internalId, ExtendedTimeLog); const xmlFilename = `${entity.attributes.Filename}.xml`; const binFilename = `${entity.attributes.Filename}.bin`; entity.binaryData = yield isoxmlManager.getParsedFile(binFilename, true); if (!entity.binaryData) { isoxmlManager.addWarning(`[${internalId}] TimeLog binary file "${binFilename}" is missing`); } const xmlData = yield isoxmlManager.getParsedFile(xmlFilename, false); if (!xmlData) { isoxmlManager.addWarning(`[${internalId}] TimeLog header file "${xmlFilename}" is missing`); return entity; } const xmlTimelog = (0, xmlManager_1.xml2js)(xmlData); const timeLogIsoxmlManager = new ISOXMLManager_1.ISOXMLManager({ realm: 'timelog' }); entity.timeLogHeader = (yield baseEntities_1.TimelogTime.fromXML(xmlTimelog["TIM" /* TAGS.Time */][0], timeLogIsoxmlManager, `${xmlFilename}->${"TIM" /* TAGS.Time */}[0]`)); timeLogIsoxmlManager.getWarnings().forEach(warning => { isoxmlManager.addWarning(warning); }); return entity; }); } getDLVDeviceElement(dataLogValue) { var _a; const detId = (_a = dataLogValue.attributes.DeviceElementIdRef) === null || _a === void 0 ? void 0 : _a.xmlId; if (!detId) { return null; } return this.isoxmlManager.getEntityByXmlId(detId); } toXML() { const json = { ["TIM" /* TAGS.Time */]: this.timeLogHeader.toXML() }; const xmlData = (0, xmlManager_1.js2xml)(json); this.isoxmlManager.addFileToSave(xmlData, `${this.attributes.Filename}.xml`); this.isoxmlManager.addFileToSave(this.binaryData, `${this.attributes.Filename}.bin`); return super.toXML(); } parseBinaryFile() { var _a, _b; if (this.parsedTimeLog) { return this.parsedTimeLog; } const records = []; const reader = new BufferReader_1.default(this.binaryData.buffer); const headerPos = (_b = (_a = this.timeLogHeader.attributes.Position) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.attributes; const minPoint = [Infinity, Infinity]; const maxPoint = [-Infinity, -Infinity]; const ranges = {}; while (reader.tell() < this.binaryData.length) { const record = {}; let isValidPosition = false; // skip items with lat = lng = 0 const position = {}; try { const ms = reader.nextUint32(); const days = reader.nextUint16(); record.time = new Date(1000 * 60 * 60 * 24 * days + ms); //TODO: timezone ? if (headerPos) { if (headerPos.PositionNorth === null) { position.PositionNorth = reader.nextInt32() / 10000000; } else { position.PositionNorth = headerPos.PositionNorth; } if (headerPos.PositionEast === null) { position.PositionEast = reader.nextInt32() / 10000000; } else { position.PositionEast = headerPos.PositionEast; } if (headerPos.PositionUp === null) { position.PositionUp = reader.nextInt32(); } else { position.PositionUp = headerPos.PositionUp; } if (headerPos.PositionStatus === '') { position.PositionStatus = reader.nextUint8().toString(); } else { position.PositionStatus = headerPos.PositionStatus; } if (headerPos.PDOP === null) { position.PDOP = reader.nextUint16() / 10; } else { position.PDOP = headerPos.PDOP; } if (headerPos.HDOP === null) { position.HDOP = reader.nextUint16() / 10; } else { position.HDOP = headerPos.HDOP; } if (headerPos.NumberOfSatellites === null) { position.NumberOfSatellites = reader.nextUint8(); } else { position.NumberOfSatellites = headerPos.NumberOfSatellites; } if (headerPos.GpsUtcTime === null) { position.GpsUtcTime = reader.nextUint32(); } else { position.GpsUtcTime = headerPos.GpsUtcTime; } if (headerPos.GpsUtcDate === null) { position.GpsUtcDate = reader.nextUint16(); } else { position.GpsUtcDate = headerPos.GpsUtcDate; } isValidPosition = position.PositionEast !== 0 || position.PositionNorth !== 0; record.position = position; } const count = reader.nextUint8(); const values = {}; for (let dlv = 0; dlv < count; dlv++) { const dlvIdx = reader.nextUint8(); const ddi = this.timeLogHeader.attributes.DataLogValue[dlvIdx].attributes.ProcessDataDDI; const detId = this.timeLogHeader.attributes.DataLogValue[dlvIdx].attributes.DeviceElementIdRef.xmlId; const value = reader.nextInt32(); const key = `${ddi}_${detId}`; if (isValidPosition) { if (!ranges[key]) { ranges[key] = { min: value, max: value }; } else { ranges[key] = { min: Math.min(ranges[key].min, value), max: Math.max(ranges[key].max, value) }; } } values[key] = value; } record.values = values; record.isValidPosition = isValidPosition; records.push(record); } catch (error) { this.parsingErrors.push(`Can't parse timelog record #${records.length}: not enough bytes in binary file`); break; } // update bbox only after parsing the whole record to exclude invalid records if (isValidPosition) { minPoint[0] = Math.min(minPoint[0], position.PositionEast); minPoint[1] = Math.min(minPoint[1], position.PositionNorth); maxPoint[0] = Math.max(maxPoint[0], position.PositionEast); maxPoint[1] = Math.max(maxPoint[1], position.PositionNorth); } } const bbox = [...minPoint, ...maxPoint]; const valuesInfo = (this.timeLogHeader.attributes.DataLogValue || []).map(dlv => { var _a, _b; const ddi = dlv.attributes.ProcessDataDDI; const deviceElement = this.getDLVDeviceElement(dlv); const vpn = deviceElement === null || deviceElement === void 0 ? void 0 : deviceElement.getValuePresentation(ddi); const dpd = deviceElement === null || deviceElement === void 0 ? void 0 : deviceElement.getDataProcess(ddi); const info = (0, utils_1.constructValueInformation)(ddi, vpn, dpd); const detId = dlv.attributes.DeviceElementIdRef.xmlId; const device = deviceElement === null || deviceElement === void 0 ? void 0 : deviceElement.getParentDevice(); const key = `${ddi}_${detId}`; const deviceName = (_a = device === null || device === void 0 ? void 0 : device.attributes.DeviceDesignator) !== null && _a !== void 0 ? _a : ''; const deviceElementName = (_b = deviceElement === null || deviceElement === void 0 ? void 0 : deviceElement.attributes.DeviceElementDesignator) !== null && _b !== void 0 ? _b : ''; info.deviceElementId = detId; info.deviceElementDesignator = [deviceName, deviceElementName].filter(e => e).join(' - '); info.valueKey = key; if (key in ranges) { info.minValue = ranges[key].min; info.maxValue = ranges[key].max; } return info; }); this.parsedTimeLog = { bbox, valuesInfo, timeLogs: records }; return this.parsedTimeLog; } // This is a modified and a more conservative version of the Boxplot algorithm // It doesn't work well in all the cases, but sometimes it can be useful rangesWithoutOutliers() { const parsedTimeLog = this.parseBinaryFile(); const uniqueValues = {}; parsedTimeLog.valuesInfo.forEach(valueInfo => { uniqueValues[valueInfo.valueKey] = new Set(); }); parsedTimeLog.timeLogs.forEach(item => { Object.keys(item.values).forEach(valueKey => { uniqueValues[valueKey].add(item.values[valueKey]); }); }); const newValuesInfo = parsedTimeLog.valuesInfo.map(valueInfo => { const values = [...uniqueValues[valueInfo.valueKey]]; if (values.length < 8) { return { minValue: valueInfo.minValue, maxValue: valueInfo.maxValue }; } values.sort((a, b) => a - b); const q1 = values[values.length >> 2]; const q3 = values[(values.length * 3) >> 2]; const iqr = q3 - q1; const upperBoundary = q3 + 2.5 * iqr; const lowerBoundary = q1 - 2.5 * iqr; // console.log(values, q1, q3, lowerBoundary, upperBoundary) let minValue; let maxValue; for (let i = 0; i < values.length; i++) { if (values[i] >= lowerBoundary) { minValue = values[i]; break; } } for (let i = values.length - 1; i >= 0; i--) { if (values[i] <= upperBoundary) { maxValue = values[i]; break; } } return { minValue, maxValue }; }); return newValuesInfo; } /** Filles missing values with the latest value */ getFilledTimeLogs() { const timeLogInfo = this.parseBinaryFile(); const sortedTimeLogs = timeLogInfo.timeLogs.sort((a, b) => +a.time - +b.time); const latestValues = {}; return sortedTimeLogs.map(timeLog => { const filledValues = {}; timeLogInfo.valuesInfo.forEach(valueInfo => { const valueKey = valueInfo.valueKey; if (valueKey in timeLog.values) { filledValues[valueKey] = timeLog.values[valueKey]; latestValues[valueKey] = timeLog.values[valueKey]; } else if (valueKey in latestValues) { filledValues[valueKey] = latestValues[valueKey]; } }); return Object.assign(Object.assign({}, timeLog), { values: filledValues }); }); } } exports.ExtendedTimeLog = ExtendedTimeLog; (0, classRegistry_1.registerEntityClass)('main', "TLG" /* TAGS.TimeLog */, ExtendedTimeLog);