@openhps/core
Version:
Open Hybrid Positioning System - Core component
371 lines • 12.4 kB
JavaScript
var Unit_1;
import { __decorate, __metadata } from "tslib";
import 'reflect-metadata';
import { SerializableObject, SerializableMember } from '../../data/decorators';
import { UnitPrefix } from './UnitPrefix';
import { Vector3 } from '../math/Vector3';
/**
* Unit
*
* ## Usage
* ### Creation
* ```typescript
* const myUnit = new Unit("meter", {
* baseName: "length",
* aliases: ["m", "meters"],
* prefixes: 'decimal'
* })
* ```
*
* ### Specifiers
* You can specify the prefix using the ```specifier(...)``` function.
* ```typescript
* const nanoUnit = myUnit.specifier(UnitPrefix.NANO);
* ```
* @category Unit
*/
let Unit = Unit_1 = class Unit {
/**
* Create a new unit
* @param {string} name Unit name
* @param {UnitOptions} options Unit options
*/
constructor(name, options) {
this._definitions = new Map();
this._prefixType = 'none';
this._aliases = [];
const config = options || {
baseName: undefined
};
config.aliases = config.aliases || [];
config.prefixes = config.prefixes || 'none';
config.definitions = config.definitions || [];
// Unit config
this._name = name || config.name;
this._baseName = config.baseName;
this._aliases = config.aliases;
this._prefixType = config.prefixes;
// Unit definitions
config.definitions.forEach(this._initDefinition.bind(this));
if (this.name) {
Unit_1.registerUnit(this, config.override);
}
}
/**
* Get a unit from JSON
* @param {any} json JSON object
* @returns {Unit} Unit if found
*/
static fromJSON(json) {
if (json.name !== undefined) {
const unit = Unit_1.findByName(json.name);
if (!unit) {
throw new Error(`Unit with name '${json.name}' not found! Unable to deserialize!`);
}
return unit;
} else {
throw new Error(`Unit does not define a serialization name! Unable to deserialize!`);
}
}
_initDefinition(definition) {
const referenceUnit = Unit_1.findByName(definition.unit, this.baseName);
const unitName = referenceUnit ? referenceUnit.name : definition.unit;
if ('toUnit' in definition) {
// UnitFunctionDefinition
this._initFunctionDefinition(definition, unitName);
} else {
// UnitBasicDefinition
this._initBasicDefinition(definition, unitName);
}
}
_initFunctionDefinition(definition, unitName) {
const functionDefinition = definition;
this._definitions.set(unitName, functionDefinition);
}
_initBasicDefinition(definition, unitName) {
const definitionKeys = Object.keys(definition);
const basicDefinition = definition;
const magnitudeOrder = definitionKeys.indexOf('magnitude');
const offsetOrder = definitionKeys.indexOf('offset');
const magnitude = basicDefinition.magnitude || 1;
const offset = basicDefinition.offset !== undefined ? basicDefinition.offset : 0;
const offsetPriority = magnitudeOrder === -1 ? true : offsetOrder < magnitudeOrder;
let toUnitFn;
let fromUnitFn;
if (offsetPriority) {
toUnitFn = value => (value + offset) * magnitude;
fromUnitFn = value => value / magnitude - offset;
} else {
toUnitFn = value => value * magnitude + offset;
fromUnitFn = value => (value - offset) / magnitude;
}
this._definitions.set(unitName, {
unit: basicDefinition.unit,
toUnit: toUnitFn,
fromUnit: fromUnitFn
});
}
/**
* Unit name
* @returns {string} Name
*/
get name() {
return this._name;
}
set name(name) {
this._name = name;
const existingUnit = Unit_1.findByName(name);
if (existingUnit) {
this._baseName = existingUnit.baseName;
this._definitions = existingUnit._definitions;
this._prefixType = existingUnit._prefixType;
this._aliases = existingUnit._aliases;
}
}
/**
* Unit aliases
* @returns {string[]} Alias names as array
*/
get aliases() {
return this._aliases;
}
get baseName() {
return this._baseName;
}
get prefixType() {
return this._prefixType;
}
get definitions() {
return Array.from(this._definitions.values());
}
get prefixes() {
switch (this._prefixType) {
case 'decimal':
return UnitPrefix.DECIMAL;
case 'none':
return [];
}
}
/**
* Get or create a definition from this unit to the base
* @returns {UnitFunctionDefinition} Definition to base
*/
createBaseDefinition() {
let newDefinition;
// Get base unit
const baseUnitName = Unit_1.UNIT_BASES.get(this.baseName);
if (this._definitions.has(baseUnitName)) {
const definition = this._definitions.get(baseUnitName);
newDefinition = definition;
} else {
this._definitions.forEach(definition => {
const unit = Unit_1.findByName(definition.unit, this.baseName);
const baseDefinition = unit.createBaseDefinition();
if (baseDefinition) {
newDefinition = {
unit: baseDefinition.unit,
toUnit: value => baseDefinition.toUnit(definition.toUnit(value)),
fromUnit: value => definition.fromUnit(baseDefinition.fromUnit(value))
};
return;
}
});
}
return newDefinition;
}
createDefinition(targetUnit) {
let newDefinition;
// Get base unit
const baseUnitName = Unit_1.UNIT_BASES.get(this.baseName);
const baseUnit = Unit_1.findByName(baseUnitName);
if (this._definitions.has(targetUnit.name)) {
// Direct conversion
const definition = this._definitions.get(targetUnit.name);
newDefinition = definition;
} else if (targetUnit._definitions.has(this.name)) {
// Reverse conversion
const definition = targetUnit._definitions.get(this.name);
newDefinition = {
inputType: definition.inputType,
outputType: definition.outputType,
unit: targetUnit.name,
toUnit: definition.fromUnit,
fromUnit: definition.toUnit
};
this._definitions.set(targetUnit.name, newDefinition);
} else if (baseUnit.name !== this.name) {
// No direct conversion found, convert to base unit
const currentToBase = this._definitions.get(baseUnitName);
const baseToTarget = baseUnit.createDefinition(targetUnit);
// Convert unit if definitions are found
if (currentToBase && baseToTarget) {
newDefinition = {
inputType: currentToBase.inputType,
outputType: currentToBase.outputType,
unit: targetUnit.name,
toUnit: value => baseToTarget.toUnit(currentToBase.toUnit(value)),
fromUnit: value => currentToBase.fromUnit(baseToTarget.fromUnit(value))
};
this._definitions.set(targetUnit.name, newDefinition);
}
}
return newDefinition;
}
/**
* Get the unit specifier
* @param {UnitPrefix} prefix Unit prefix
* @returns {Unit} Unit with specifier
*/
specifier(prefix) {
// Check if the unit already exists
const unitName = `${prefix.name}${this.name}`;
if (Unit_1.UNITS.has(unitName)) {
return Unit_1.UNITS.get(unitName);
}
// Confirm that the prefix is allowed
if (!this.prefixes.includes(prefix)) throw new Error(`Prefix '${prefix.name}' is not allowed for this unit!`);
// Get the unit constructor of the extended class. This allows
// serializing of units that are extended (e.g. LengthUnit)
const UnitConstructor = Object.getPrototypeOf(this).constructor;
const unit = new UnitConstructor();
unit._name = unitName;
unit._baseName = this.baseName;
const aliases = [];
this.aliases.forEach(alias => {
aliases.push(`${prefix.name}${alias}`);
aliases.push(`${prefix.abbrevation}${alias}`);
});
unit._aliases = aliases;
unit._definitions.set(this.name, {
unit: this.name,
toUnit: value => value * prefix.magnitude,
fromUnit: value => value / prefix.magnitude
});
return Unit_1.registerUnit(unit);
}
/**
* Find unit specifier by name or alias
* @param {string} name Unit name
* @returns {Unit | undefined} Unit if found
*/
findByName(name) {
// Check all aliases in those units
for (const alias of this.aliases.concat(this.name)) {
if (name === alias) {
// Exact match with alias
return this;
} else if (name.endsWith(alias)) {
// Unit that we are looking for ends with the alias
// confirm that there is a prefix match
for (const prefix of this.prefixes) {
if (name.match(prefix.abbrevationPattern) || name.match(prefix.namePattern)) {
return this.specifier(prefix);
}
}
}
}
return undefined;
}
/**
* Find a unit by its name
* @param {string} name Unit name
* @param {string} baseName Optional base name to specific result
* @returns {Unit | undefined} Unit if found
*/
static findByName(name, baseName) {
if (name === undefined) {
return undefined;
} else if (Unit_1.UNITS.has(name)) {
return Unit_1.UNITS.get(name);
} else {
// Check all units
for (const [, unit] of Unit_1.UNITS) {
if (baseName ? baseName !== unit.baseName : false) {
continue;
}
// Check all aliases in those units
const result = unit.findByName(name);
if (result) {
return result;
}
}
return undefined;
}
}
/**
* Convert a value in the current unit to a target unit
* @param {UnitValueType} value Value to convert
* @param {string | Unit} target Target unit
* @returns {number} Converted unit
*/
convert(value, target) {
const targetUnit = target instanceof Unit_1 ? target : Unit_1.findByName(target, this.baseName);
// Do not convert if target unit is the same or undefined
if (!targetUnit || targetUnit.name === this.name) {
return value;
}
const definition = this.createDefinition(targetUnit);
if (!definition) {
throw new Error(`No conversion definition found from '${this.name}' to '${targetUnit.name}'!`);
} else {
if (value instanceof Vector3 && definition.inputType !== Vector3) {
// Convert vector individually when definition only supports atomic data
return value.clone().fromArray([definition.toUnit(value.x), definition.toUnit(value.y), definition.toUnit(value.z)]);
} else {
return definition.toUnit(value);
}
}
}
/**
* Convert a value from a specific unit to a target unit
* @param {UnitValueType} value Value to convert
* @param {string | Unit} from Source unit
* @param {string | Unit} to Target unit
* @returns {UnitValueType} Converted unit
*/
static convert(value, from, to) {
const fromUnit = typeof from === 'string' ? Unit_1.findByName(from) : from;
return fromUnit.convert(value, to);
}
/**
* Register a new unit
* @param {Unit} unit Unit to register
* @param {boolean} override Override an existing unit with the same name
* @returns {Unit} Registered unit
*/
static registerUnit(unit, override = false) {
if (!unit.name) {
return unit;
}
// Register unit if it does not exist yet
if (!Unit_1.UNITS.has(unit.name) || override) {
Unit_1.UNITS.set(unit.name, unit);
}
// Check if the unit is a new base unit
const baseName = unit.baseName ? unit.baseName : unit.name;
const baseUnitName = Unit_1.UNIT_BASES.get(baseName);
if (!baseUnitName) {
Unit_1.UNIT_BASES.set(baseName, unit.name);
} else {
// Confirm that the unit can be converted to a base unit
const baseUnit = Unit_1.findByName(baseUnitName, baseName);
const fromBase = baseUnit.createDefinition(unit);
const toBase = unit.createBaseDefinition();
if (!fromBase) {
// No conversion definition
unit._definitions.set(baseUnitName, toBase);
}
}
return unit;
}
};
// Unit bases (e.g. length, time, velocity, ...)
Unit.UNIT_BASES = new Map();
// Units (e.g. second, meter, ...)
Unit.UNITS = new Map();
Unit.UNKNOWN = new Unit_1('unknown');
__decorate([SerializableMember(), __metadata("design:type", String), __metadata("design:paramtypes", [String])], Unit.prototype, "name", null);
Unit = Unit_1 = __decorate([SerializableObject({
initializer: Unit.fromJSON
}), __metadata("design:paramtypes", [String, Object])], Unit);
export { Unit };