From 7c275a99a280a7f9d59da0de93938527c6b08bdf Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 2 Feb 2023 23:01:12 +1100 Subject: [PATCH] New utility functions for automatic local to global elevation conversions and easier unit conversions --- .../ifcopenshell/util/geolocation.py | 41 +++++++++++++++ .../ifcopenshell/util/unit.py | 50 +++++++++++++++++-- 2 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/util/geolocation.py b/src/ifcopenshell-python/ifcopenshell/util/geolocation.py index 85a23b6120..141b2e8a06 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/geolocation.py +++ b/src/ifcopenshell-python/ifcopenshell/util/geolocation.py @@ -18,6 +18,8 @@ import math import numpy as np +import ifcopenshell +import ifcopenshell.util.unit def dms2dd(degrees, minutes, seconds, ms=0): @@ -52,6 +54,45 @@ def xyz2enh(x, y, z, eastings, northings, orthogonal_height, x_axis_abscissa, x_ return (eastings, northings, height) +def auto_z2e(ifc_file, z): + """Convert a Z coordinate to an elevation using model georeferencing data + + The necessary georeferencing map conversion is automatically detected from + the IFC map conversion parameters present in the IFC model. If no map + conversion is present, then the Z coordinate is returned unchanged. + + :param ifc_file: The IFC file + :type ifc_file: ifcopenshell.file.file + :param z: The Z local engineering coordinate provided in project length units. + :type z: float + :return: The elevation in project length units. + :rtype: float + """ + try: + conversion = ifc_file.by_type("IfcMapConversion") + except: + return z + if not conversion or not conversion[0].OrthogonalHeight: + return z + conversion = conversion[0] + h = conversion.OrthogonalHeight + map_unit = conversion.TargetCRS.MapUnit + if map_unit: + project_unit = ifcopenshell.util.unit.get_project_unit(ifc_file, "LENGTHUNIT") + h = ifcopenshell.util.unit.convert( + h, + getattr(map_unit, "Prefix", None), + map_unit.Name, + getattr(project_unit, "Prefix", None), + project_unit.Name, + ) + return z2e(z, h) + + +def z2e(z, h): + return z + h + + def enh2xyz(e, n, h, eastings, northings, orthogonal_height, x_axis_abscissa, x_axis_ordinate, scale=None): if scale is None: scale = 1.0 diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py index c527f31b86..25aa251776 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/unit.py +++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py @@ -325,6 +325,25 @@ def get_unit_assignment(ifc_file): return unit_assignments[0] +def get_project_unit(ifc_file, unit_type): + """Get the default project unit of a particular unit type + + :param ifc_file: The IFC file. + :type ifc_file: ifcopenshell.file.file + :param unit_type: The type of unit, taken from the list of IFC unit types, + such as "LENGTHUNIT". + :type unit_type: str + :return: The IFC unit entity, or nothing if there is no default project unit + defined. + :rtype: ifcopenshell.entity_instance,None + """ + unit_assignment = get_unit_assignment(ifc_file) + if unit_assignment: + for unit in unit_assignment.Units or []: + if getattr(unit, "UnitType", None) == unit_type: + return unit + + def get_property_unit(prop, ifc_file): unit = getattr(prop, "Unit", None) if unit: @@ -431,19 +450,42 @@ def get_unit_symbol(unit): return "?" +def convert_unit(value, from_unit, to_unit): + """Convert from one unit to another unit + + :param value: The numeric value you want to convert + :type value: float + :param from_unit: The IfcNamedUnit to confirm from. + :type from_unit: ifcopenshell.entity_instance.entity_instance + :param to_unit: The IfcNamedUnit to confirm from. + :type to_unit: ifcopenshell.entity_instance.entity_instance + :return: The converted value. + :rtype: float + """ + return convert( + value, getattr(from_unit, "Prefix", None), from_unit.Name, getattr(to_unit, "Prefix", None), to_unit.Name + ) + + def convert(value, from_prefix, from_unit, to_prefix, to_unit): """Converts between length, area, and volume units + In this case, you manually specify the names and (optionally) prefixes to + convert to and from. In case you want to automatically convert to units + already available as IFC entities, consider using convert_unit() instead. + :param value: The numeric value you want to convert :type value: float :param from_prefix: A prefix from IfcSIPrefix. Can be None. - :type from_prefix: string + :type from_prefix: str,optional :param from_unit: IfcSIUnitName or IfcConversionBasedUnit.Name - :type from_unit: string + :type from_unit: str :param to_prefix: A prefix from IfcSIPrefix. Can be None. - :type to_prefix: string + :type to_prefix: str,optional :param to_unit: IfcSIUnitName or IfcConversionBasedUnit.Name - :type to_unit: string + :type to_unit: str + :return: The converted value. + :rtype: float """ if from_unit in si_conversions: value *= si_conversions[from_unit]