isoxml-angular
Version:
JavaScript library to parse and generate ISOXML (ISO11783-10) files
284 lines (283 loc) • 13.7 kB
JavaScript
;
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);