frontend/src/models/UnitMeasurement.ts

260 lines
8.1 KiB
TypeScript

import { cloneDeep } from "lodash";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import { User } from "models";
import moment from "moment";
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
import { or } from "utils";
export interface MeasurementsFor {
type: quack.MeasurementType;
label: string;
values: number[];
unit: string;
colour: string;
error: boolean;
}
export interface convertedUnitMeasurement {
timestamp: string;
measurementsFor: MeasurementsFor[];
}
export class UnitMeasurement {
public componentId: string = "";
public type: quack.MeasurementType = 0;
public label: string = "";
public values: pond.ValueArray[] = [];
public timestamps: string[] = [];
public colour: string = "";
public unit: string = "";
public static create(pb?: pond.UnitMeasurementsForComponent, user?: User): UnitMeasurement {
let my = new UnitMeasurement();
if (pb) {
let describer = describeMeasurement(pb.type, pb.componentType);
let values = pb.values;
if (user) {
values = unitConversion(pb.values, pb.type, user);
}
my.componentId = pb.componentId;
my.type = pb.type;
my.values = values;
my.timestamps = pb.timestamps;
my.colour = describer.colour();
my.unit = describer.GetUnit();
my.label = describer.label();
}
return my;
}
public static clone(other?: UnitMeasurement): UnitMeasurement {
if (other) {
return UnitMeasurement.create(
pond.UnitMeasurementsForComponent.fromObject(
cloneDeep({
componentId: other.componentId,
label: other.label,
type: other.type,
values: other.values,
timestamps: other.timestamps,
colour: other.colour,
unit: other.unit
})
)
);
}
return UnitMeasurement.create();
}
public static any(data: any, user?: User): UnitMeasurement {
let my = UnitMeasurement.create(
pond.UnitMeasurementsForComponent.fromObject(cloneDeep(data)),
user
);
return my;
}
/**
* takes in the unitMeasurementsForComponents from list and re-structures them into an array of objects that have the timestamp
* and all measurements at that timestamp in one object
* the converted measurement is meant to be used with the measurement summary
* @param toConvert
* @returns
*/
public static convertMeasurements(toConvert: UnitMeasurement[]): convertedUnitMeasurement[] {
let map: Map<string, convertedUnitMeasurement> = new Map<string, convertedUnitMeasurement>();
let converted: convertedUnitMeasurement[] = [];
toConvert.forEach((um: UnitMeasurement) => {
um.values.forEach((vals, i) => {
let current = map.get(um.timestamps[i]);
//checks if there is a converted measurement with that timestamp yet, if there is add the new one to it
if (current) {
current.measurementsFor.push({
type: um.type,
label: um.label,
values: vals.values,
colour: um.colour,
unit: um.unit,
error: vals.error
});
// if there is not then create a new one and put it in the map
} else {
let converted: convertedUnitMeasurement = {
timestamp: um.timestamps[i],
measurementsFor: [
{
type: um.type,
label: um.label,
values: vals.values,
colour: um.colour,
unit: um.unit,
error: vals.error
}
]
};
map.set(um.timestamps[i], converted);
}
});
});
converted = Array.from(map.values());
return converted;
}
//similar to the above however it only returns the converted measurement with the most recent timestamp
public static convertLastMeasurement(toConvert: UnitMeasurement[]): convertedUnitMeasurement {
let converted: convertedUnitMeasurement = {
measurementsFor: [],
timestamp: ""
};
toConvert.forEach((um: UnitMeasurement) => {
if (um.values.length > 0 && um.timestamps.length > 0) {
let recentPos = 0;
let timeVal = 0;
um.timestamps.forEach((timestamp, pos) => {
if (moment(timestamp).valueOf() > timeVal) {
recentPos = pos;
timeVal = moment(timestamp).valueOf();
}
});
converted.timestamp = um.timestamps[recentPos];
converted.measurementsFor.push({
type: um.type,
label: um.label,
values: um.values[recentPos].values,
colour: um.colour,
unit: um.unit,
error: um.values[recentPos].error
});
}
});
return converted;
}
/**
* will return a converted unit measurement where the values in the the measurementsFor array only contain the number for the requested node,
* if the requested node is outside the array (component doesn't have that node) the array will simply be empty
* @param toConvert
* @param node
* @returns
*/
public static convertLastMeasurementByNode(
toConvert: UnitMeasurement[],
node: number
): convertedUnitMeasurement {
let converted: convertedUnitMeasurement = {
measurementsFor: [],
timestamp: ""
};
toConvert.forEach((um: UnitMeasurement) => {
if (um.values.length > 0 && um.timestamps.length > 0) {
let recentPos = 0;
let timeVal = 0;
um.timestamps.forEach((timestamp, pos) => {
if (moment(timestamp).valueOf() > timeVal) {
recentPos = pos;
timeVal = moment(timestamp).valueOf();
}
});
converted.timestamp = um.timestamps[recentPos];
converted.measurementsFor.push({
type: um.type,
label: um.label,
values: or([um.values[recentPos].values[node]], []),
colour: um.colour,
unit: um.unit,
error: um.values[recentPos].error
});
}
});
return converted;
}
public getRecentForNode(node: number): number | undefined {
if (this.values.length > 0) {
if (this.values[this.values.length - 1].values[node]) {
return this.values[this.values.length - 1].values[node];
}
}
return undefined;
}
}
/**
* for handling the conversion of values based on user preferences, this is run for each unit measurement when it is created from the data from protobuf
* @param values
* @param type
* @param user
* @returns
*/
function unitConversion(
values: pond.ValueArray[],
type: quack.MeasurementType,
user: User
): pond.ValueArray[] {
let newVals = values;
if (
type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE &&
user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
) {
newVals.forEach((val, i) => {
val.values.forEach((v, j) => {
newVals[i].values[j] = Math.round((v * 1.8 + 32) * 100) / 100;
});
});
}
if (type === quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE) {
if (user.settings.pressureUnit === pond.PressureUnit.PRESSURE_UNIT_KILOPASCALS) {
newVals.forEach((val, i) => {
val.values.forEach((v, j) => {
newVals[i].values[j] = v / 1000;
});
});
//} else if (user.settings.pressureUnit === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER) {
} else {
//by default if there is nothing set use iwg
newVals.forEach((val, i) => {
val.values.forEach((v, j) => {
newVals[i].values[j] = Math.round((v / 248.8) * 100) / 100;
});
});
}
}
if (type === quack.MeasurementType.MEASUREMENT_TYPE_DISTANCE_CM) {
if (user.settings.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
newVals.forEach((val, i) => {
val.values.forEach((v, j) => {
newVals[i].values[j] = Math.round((v / 30.48) * 100) / 100;
});
});
} else {
//use meters as the default for users that have none set yet
newVals.forEach((val, i) => {
val.values.forEach((v, j) => {
newVals[i].values[j] = v / 100;
});
});
}
}
return newVals;
}