UNPKG

@openhps/core

Version:

Open Hybrid Positioning System - Core component

371 lines 12.4 kB
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 };