@itwin/ecschema-metadata
Version:
ECObjects core concepts in typescript
276 lines • 15.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SchemaUnitProvider = void 0;
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
const core_bentley_1 = require("@itwin/core-bentley");
const core_quantity_1 = require("@itwin/core-quantity");
const Context_1 = require("../Context");
const SchemaItem_1 = require("../Metadata/SchemaItem");
const SchemaKey_1 = require("../SchemaKey");
const Unit_1 = require("../Metadata/Unit");
const ECObjects_1 = require("../ECObjects");
const UnitConverter_1 = require("../UnitConversion/UnitConverter");
const InvertedUnit_1 = require("../Metadata/InvertedUnit");
/**
* Class used to find Units in SchemaContext by attributes such as Phenomenon and DisplayLabel.
* @beta
*/
class SchemaUnitProvider {
_unitExtraData;
_unitConverter;
_context;
/**
*
* @param contextOrLocater The SchemaContext or a different ISchemaLocater implementation used to retrieve the schema. The SchemaContext
* class implements the ISchemaLocater interface. If the provided locater is not a SchemaContext instance a new SchemaContext will be
* created and the locater will be added.
* @param _unitExtraData Additional data like alternate display label not found in Units Schema to match with Units; Defaults to empty array.
*/
constructor(contextOrLocater, _unitExtraData = []) {
this._unitExtraData = _unitExtraData;
if (contextOrLocater instanceof Context_1.SchemaContext) {
this._context = contextOrLocater;
}
else {
this._context = new Context_1.SchemaContext();
this._context.addLocater(contextOrLocater);
}
this._unitConverter = new UnitConverter_1.UnitConverter(this._context);
}
/**
* Find unit in a schema that has unitName.
* @param unitName Full name of unit.
* @returns UnitProps interface from @itwin/core-quantity whose name matches unitName.
*/
async findUnitByName(unitName) {
// Check if schema exists and unit exists in schema
const [schemaName, schemaItemName] = SchemaItem_1.SchemaItem.parseFullName(unitName);
const schemaKey = new SchemaKey_1.SchemaKey(schemaName);
const schema = await this._context.getSchema(schemaKey);
if (!schema) {
return new core_quantity_1.BadUnit(); // return BadUnit if schema does not exist
}
const itemKey = new SchemaKey_1.SchemaItemKey(schemaItemName, schema.schemaKey);
const unit = await this._context.getSchemaItem(itemKey, Unit_1.Unit);
if (unit && unit.schemaItemType === ECObjects_1.SchemaItemType.Unit)
return this.getUnitsProps(unit);
const invertedUnit = await this._context.getSchemaItem(itemKey, InvertedUnit_1.InvertedUnit);
if (invertedUnit && invertedUnit.schemaItemType === ECObjects_1.SchemaItemType.InvertedUnit) {
return this.getUnitsProps(invertedUnit);
}
return new core_quantity_1.BadUnit();
}
/**
* Find all units in context that belongs to given phenomenon.
* @param phenomenon Full name of phenomenon.
* @returns Array of UnitProps (from @itwin/core-quantity) interface objects whose name matches phenomenon param.
*/
async getUnitsByFamily(phenomenon) {
// Check if schema exists and phenomenon exists in schema
const [schemaName, schemaItemName] = SchemaItem_1.SchemaItem.parseFullName(phenomenon);
const schemaKey = new SchemaKey_1.SchemaKey(schemaName);
const schema = await this._context.getSchema(schemaKey);
if (!schema) {
throw new core_bentley_1.BentleyError(core_bentley_1.BentleyStatus.ERROR, "Cannot find schema for phenomenon", () => {
return { phenomenon, schema: schemaName };
});
}
const itemKey = new SchemaKey_1.SchemaItemKey(schemaItemName, schema.schemaKey);
const phenom = await this._context.getSchemaItem(itemKey);
if (!phenom)
throw new core_bentley_1.BentleyError(core_bentley_1.BentleyStatus.ERROR, "Cannot find schema item/phenomenon", () => {
return { item: schemaItemName, schema: schemaName };
});
if (phenom.schemaItemType !== ECObjects_1.SchemaItemType.Phenomenon)
throw new core_bentley_1.BentleyError(core_bentley_1.BentleyStatus.ERROR, "Item is not a phenomenon", () => {
return { itemType: phenom.key.fullName };
});
// Find units' full name that match given phenomenon param.
const filteredUnits = [];
const schemaItems = this._context.getSchemaItems();
for (const value of schemaItems) {
if (Unit_1.Unit.isUnit(value)) {
const foundPhenomenon = await value.phenomenon;
if (foundPhenomenon && foundPhenomenon.key.matchesFullName(phenomenon)) {
const unitProps = await this.getUnitsProps(value);
filteredUnits.push(unitProps);
}
}
else if (InvertedUnit_1.InvertedUnit.isInvertedUnit(value) && value.invertsUnit) {
const invertsUnit = await value.invertsUnit;
if (invertsUnit) {
const foundPhenomenon = await invertsUnit.phenomenon;
if (foundPhenomenon && foundPhenomenon.key.matchesFullName(phenomenon)) {
const unitProps = await this.getUnitsProps(value);
filteredUnits.push(unitProps);
}
}
}
}
return filteredUnits;
}
/**
* Find alternate display labels associated with unitName, if any.
* @param unitName Full name of Unit.
*/
getAlternateDisplayLabels(unitName) {
let alternateLabels = [];
for (const entry of this._unitExtraData) {
if (entry.name.toLowerCase() === unitName.toLowerCase()) {
alternateLabels = entry.altDisplayLabels;
}
}
return alternateLabels;
}
/**
* Finds Unit by unitLabel, which could be a display label in the schema or alternate an display label defined in
* this._unitExtraData. If there are duplicates of the same display label in the context or teh same alternate display
* labels, specify schemaName, phenomenon, or unitSystem to get a specific unit.
*
* @param unitLabel Display label or alternate display label to query unit by.
* @param schemaName Ensure Unit with unitLabel belongs to Schema with schemaName.
* @param phenomenon Full name of phenomenon that Unit belongs to.
* @param unitSystem Full name of unitSystem that Unit belongs to.
* @returns The UnitProps interface from the @itwin/core-quantity package.
*/
async findUnit(unitLabel, schemaName, phenomenon, unitSystem) {
const findLabel = unitLabel.toLowerCase();
const findSchema = schemaName ? schemaName.toLowerCase() : undefined;
const findPhenomenon = phenomenon ? phenomenon.toLowerCase() : undefined;
const findUnitSystem = unitSystem ? unitSystem.toLowerCase() : undefined;
const foundUnit = await this.findUnitByDisplayLabel(findLabel, findSchema, findPhenomenon, findUnitSystem);
if (foundUnit.isValid)
return foundUnit;
// If there is no Unit with display label that matches label, then check for alternate display labels that may match
return this.findUnitByAltDisplayLabel(findLabel, findSchema, findPhenomenon, findUnitSystem);
}
/**
* Gets the @itwin/core-quantity UnitConversionProps for the given fromUnit and toUnit.
* @param fromUnit The UnitProps of the 'from' unit.
* @param toUnit The UnitProps of the 'to' unit.
* @returns The UnitConversionProps interface from the @itwin/core-quantity package.
*/
async getConversion(fromUnit, toUnit) {
// need to check if either side is an inverted unit. The UnitConverter can only handle Units
if (!fromUnit.isValid || !toUnit.isValid)
throw new core_bentley_1.BentleyError(core_bentley_1.BentleyStatus.ERROR, "Both provided units must be valid.", () => {
return { fromUnit, toUnit };
});
const { unitName: fromUnitName, isInverted: fromIsInverted } = await this.checkUnitPropsForConversion(fromUnit, this._context);
const { unitName: toUnitName, isInverted: toIsInverted } = await this.checkUnitPropsForConversion(toUnit, this._context);
const conversion = await this._unitConverter.calculateConversion(fromUnitName, toUnitName);
const result = {
factor: conversion.factor,
offset: conversion.offset,
};
if (fromIsInverted && !toIsInverted)
result.inversion = core_quantity_1.UnitConversionInvert.InvertPreConversion;
else if (!fromIsInverted && toIsInverted)
result.inversion = core_quantity_1.UnitConversionInvert.InvertPostConversion;
return result;
}
async checkUnitPropsForConversion(input, context) {
const [schemaName, schemaItemName] = SchemaItem_1.SchemaItem.parseFullName(input.name);
const schemaKey = new SchemaKey_1.SchemaKey(schemaName);
const schema = await context.getSchema(schemaKey);
if (!schema) {
throw new core_bentley_1.BentleyError(core_bentley_1.BentleyStatus.ERROR, "Could not obtain schema for unit.", () => {
return { name: input.name };
});
}
const itemKey = new SchemaKey_1.SchemaItemKey(schemaItemName, schema.schemaKey);
const invertedUnit = await context.getSchemaItem(itemKey, InvertedUnit_1.InvertedUnit);
// Check if we found an item, the item is an inverted unit, and it has its invertsUnit property set
if (invertedUnit && InvertedUnit_1.InvertedUnit.isInvertedUnit(invertedUnit) && invertedUnit.invertsUnit) {
return { unitName: invertedUnit.invertsUnit.fullName, isInverted: true };
}
return { unitName: input.name, isInverted: false };
}
/**
* Gets the @itwin/core UnitProps for the given Unit.
* @param unit The Unit to convert.
* @returns UnitProps interface from @itwin/core.
*/
async getUnitsProps(unit) {
if (Unit_1.Unit.isUnit(unit)) {
return {
name: unit.fullName,
label: unit.label ?? "",
phenomenon: unit.phenomenon ? unit.phenomenon.fullName : "",
isValid: true,
system: unit.unitSystem === undefined ? "" : unit.unitSystem.fullName,
};
}
const invertsUnit = await unit.invertsUnit;
if (!invertsUnit)
return new core_quantity_1.BadUnit();
return {
name: unit.fullName,
label: unit.label ?? "",
phenomenon: invertsUnit.phenomenon ? invertsUnit.phenomenon.fullName : "",
isValid: true,
system: unit.unitSystem === undefined ? "" : unit.unitSystem.fullName,
};
}
/**
* Finds Unit by displayLabel and that it belongs to schemaName, phenomenon, and unitSystem if defined.
* @internal
*/
async findUnitByDisplayLabel(displayLabel, schemaName, phenomenon, unitSystem) {
// TODO: Known bug: This only looks through loaded schemas. If schema name is provided, we can attempt to load that schema
const schemaItems = this._context.getSchemaItems();
for (const value of schemaItems) {
if (Unit_1.Unit.isUnit(value) && value.label?.toLowerCase() === displayLabel) {
// TODO: this can be optimized. We don't have to await these if we don't want to check for them
const currPhenomenon = await value.phenomenon;
const currUnitSystem = await value.unitSystem;
if (!schemaName || value.schema.name.toLowerCase() === schemaName)
if (!phenomenon || (currPhenomenon && currPhenomenon.key.matchesFullName(phenomenon)))
if (!unitSystem || (currUnitSystem && currUnitSystem.key.matchesFullName(unitSystem)))
return this.getUnitsProps(value);
}
else if (InvertedUnit_1.InvertedUnit.isInvertedUnit(value) && value.label?.toLowerCase() === displayLabel && value.invertsUnit) {
const invertsUnit = await value.invertsUnit;
if (invertsUnit) {
const currPhenomenon = await invertsUnit.phenomenon;
const currUnitSystem = await invertsUnit.unitSystem;
if (!schemaName || value.schema.name.toLowerCase() === schemaName)
if (!phenomenon || (currPhenomenon && currPhenomenon.key.matchesFullName(phenomenon)))
if (!unitSystem || (currUnitSystem && currUnitSystem.key.matchesFullName(unitSystem)))
return this.getUnitsProps(value);
}
}
}
return new core_quantity_1.BadUnit();
}
/**
* Finds Unit by altDisplayLabel and that it belongs to schemaName, phenomenon, and unitSystem if defined.
* @internal
*/
async findUnitByAltDisplayLabel(altDisplayLabel, schemaName, phenomenon, unitSystem) {
for (const entry of this._unitExtraData) {
if (entry.altDisplayLabels && entry.altDisplayLabels.length > 0) {
if (entry.altDisplayLabels.findIndex((ref) => ref.toLowerCase() === altDisplayLabel) !== -1) {
// Found altDisplayLabel that matches label to find
const unitProps = await this.findUnitByName(entry.name);
// If no schemaName, phenomenon, or unitSystem are provided, return unitProps
if (!schemaName && !phenomenon && !unitSystem)
return unitProps;
// Check if the provided values match unitProps
const schemaNameMatches = !schemaName || unitProps.name.toLowerCase().startsWith(schemaName);
const phenomenonMatches = !phenomenon || unitProps.phenomenon.toLowerCase() === phenomenon;
const unitSystemMatches = !unitSystem || unitProps.system.toLowerCase() === unitSystem;
// If all provided values match, return unitProps
if (schemaNameMatches && phenomenonMatches && unitSystemMatches)
return unitProps;
}
}
}
return new core_quantity_1.BadUnit();
}
}
exports.SchemaUnitProvider = SchemaUnitProvider;
//# sourceMappingURL=SchemaUnitProvider.js.map