Add support for IfcRigidOperation and IfcMapConversionScaled in API

This commit is contained in:
Dion Moult
2024-06-22 21:59:09 +10:00
parent 177d43c30e
commit 3c48cfbc19
3 changed files with 141 additions and 34 deletions
@@ -21,7 +21,7 @@ import ifcopenshell.api.pset
import ifcopenshell.util.element import ifcopenshell.util.element
def add_georeferencing(file: ifcopenshell.file) -> None: def add_georeferencing(file: ifcopenshell.file, ifc_class: str = "IfcMapConversion") -> None:
"""Add empty georeferencing entities to a model """Add empty georeferencing entities to a model
By default, models are not georeferenced. Georeferencing requires two By default, models are not georeferenced. Georeferencing requires two
@@ -35,8 +35,8 @@ def add_georeferencing(file: ifcopenshell.file) -> None:
georeferencing parameters. See georeferencing parameters. See
ifcopenshell.api.georeference.edit_georeferencing. ifcopenshell.api.georeference.edit_georeferencing.
:return: None :param ifc_class: A type of IfcCoordinateOperation. For IFC2X3, this has no
:rtype: None impact and only uses ePSet_MapConversion.
Example: Example:
@@ -73,6 +73,27 @@ def add_georeferencing(file: ifcopenshell.file) -> None:
if not source_crs: if not source_crs:
return return
projected_crs = file.create_entity("IfcProjectedCRS", Name="") projected_crs = file.create_entity("IfcProjectedCRS", Name="")
file.create_entity( if ifc_class == "IfcMapConversion":
"IfcMapConversion", SourceCRS=source_crs, TargetCRS=projected_crs, Eastings=0, Northings=0, OrthogonalHeight=0 file.create_entity(
) ifc_class, SourceCRS=source_crs, TargetCRS=projected_crs, Eastings=0, Northings=0, OrthogonalHeight=0
)
elif ifc_class == "IfcMapConversionScaled":
file.create_entity(
ifc_class,
SourceCRS=source_crs,
TargetCRS=projected_crs,
Eastings=0,
Northings=0,
OrthogonalHeight=0,
FactorX=1,
FactorY=1,
FactorZ=1,
)
elif ifc_class == "IfcRigidOperation":
file.create_entity(
ifc_class,
SourceCRS=source_crs,
TargetCRS=projected_crs,
FirstCoordinate=file.createIfcLengthMeasure(0),
SecondCoordinate=file.createIfcLengthMeasure(0),
)
@@ -22,7 +22,7 @@ from typing import Optional, Any
def edit_georeferencing( def edit_georeferencing(
file: ifcopenshell.file, file: ifcopenshell.file,
map_conversion: Optional[dict[str, Any]] = None, coordinate_operation: Optional[dict[str, Any]] = None,
projected_crs: Optional[dict[str, Any]] = None, projected_crs: Optional[dict[str, Any]] = None,
true_north: Optional[tuple[float, float]] = None, true_north: Optional[tuple[float, float]] = None,
) -> None: ) -> None:
@@ -48,9 +48,9 @@ def edit_georeferencing(
See ifcopenshell.util.geolocation for more utilities to convert to and See ifcopenshell.util.geolocation for more utilities to convert to and
from local and map coordinates to check your results. from local and map coordinates to check your results.
:param map_conversion: The IfcMapConversion dictionary of attribute :param coordinate_operation: The dictionary of attribute names and values
names and values you want to edit. you want to edit.
:type map_conversion: dict, optional :type coordinate_operation: dict, optional
:param projected_crs: The IfcProjectedCRS dictionary of attribute :param projected_crs: The IfcProjectedCRS dictionary of attribute
names and values you want to edit. names and values you want to edit.
:type projected_crs: dict, optional :type projected_crs: dict, optional
@@ -78,7 +78,7 @@ def edit_georeferencing(
# Ordinate. # Ordinate.
ifcopenshell.api.run("georeference.edit_georeferencing", model, ifcopenshell.api.run("georeference.edit_georeferencing", model,
projected_crs={"Name": "EPSG:7856"}, projected_crs={"Name": "EPSG:7856"},
map_conversion={ coordinate_operation={
"Eastings": 335087.17, # The architect nominates a false origin "Eastings": 335087.17, # The architect nominates a false origin
"Northings": 6251635.41, # The architect nominates a false origin "Northings": 6251635.41, # The architect nominates a false origin
# Note: this is the angle difference between Project North # Note: this is the angle difference between Project North
@@ -91,7 +91,7 @@ def edit_georeferencing(
usecase = Usecase() usecase = Usecase()
usecase.file = file usecase.file = file
usecase.settings = { usecase.settings = {
"map_conversion": map_conversion or {}, "coordinate_operation": coordinate_operation or {},
"projected_crs": projected_crs or {}, "projected_crs": projected_crs or {},
"true_north": true_north or [], "true_north": true_north or [],
} }
@@ -117,17 +117,17 @@ class Usecase:
ifcopenshell.api.pset.edit_pset(self.file, crs, properties=self.settings["projected_crs"]) ifcopenshell.api.pset.edit_pset(self.file, crs, properties=self.settings["projected_crs"])
if (conversion := ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion")): if (conversion := ifcopenshell.util.element.get_pset(project, "ePSet_MapConversion")):
conversion = self.file.by_id(conversion["id"]) conversion = self.file.by_id(conversion["id"])
for k, v in self.settings["map_conversion"].items(): for k, v in self.settings["coordinate_operation"].items():
if k in ("XAxisAbscissa", "XAxisOrdinate", "Scale"): if k in ("XAxisAbscissa", "XAxisOrdinate", "Scale"):
v = self.file.createIfcReal(v) v = self.file.createIfcReal(v)
else: else:
v = self.file.createIfcLengthMeasure(v) v = self.file.createIfcLengthMeasure(v)
ifcopenshell.api.pset.edit_pset(self.file, conversion, properties=self.settings["map_conversion"]) ifcopenshell.api.pset.edit_pset(self.file, conversion, properties=self.settings["coordinate_operation"])
return return
map_conversion = self.file.by_type("IfcMapConversion")[0] coordinate_operation = self.file.by_type("IfcCoordinateOperation")[0]
projected_crs = self.file.by_type("IfcProjectedCRS")[0] projected_crs = self.file.by_type("IfcProjectedCRS")[0]
for name, value in self.settings["map_conversion"].items(): for name, value in self.settings["coordinate_operation"].items():
setattr(map_conversion, name, value) setattr(coordinate_operation, name, value)
for name, value in self.settings["projected_crs"].items(): for name, value in self.settings["projected_crs"].items():
setattr(projected_crs, name, value) setattr(projected_crs, name, value)
@@ -56,7 +56,7 @@ class TestZ2E(test.bootstrap.IFC4):
assert np.isclose(subject.z2e(1000, 2, 0.001, 0.9), 2.9) assert np.isclose(subject.z2e(1000, 2, 0.001, 0.9), 2.9)
class TestAutoXYZ2ENH(test.bootstrap.IFC4): class TestAutoXYZ2ENH(test.bootstrap.IFC4X3):
def test_no_georeferencing(self): def test_no_georeferencing(self):
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject") ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
assert subject.auto_xyz2enh(self.file, 0, 0, 0) == (0, 0, 0) assert subject.auto_xyz2enh(self.file, 0, 0, 0) == (0, 0, 0)
@@ -69,20 +69,63 @@ class TestAutoXYZ2ENH(test.bootstrap.IFC4):
ifcopenshell.api.georeference.edit_georeferencing( ifcopenshell.api.georeference.edit_georeferencing(
self.file, self.file,
projected_crs={"Name": "EPSG:7856"}, projected_crs={"Name": "EPSG:7856"},
map_conversion={"Eastings": 1, "Northings": 2, "OrthogonalHeight": 3}, coordinate_operation={"Eastings": 1, "Northings": 2, "OrthogonalHeight": 3},
) )
assert subject.auto_xyz2enh(self.file, 0, 0, 0) == (1, 2, 3) assert subject.auto_xyz2enh(self.file, 0, 0, 0) == (1, 2, 3)
assert subject.auto_xyz2enh(self.file, 1, 3, 5) == (2, 5, 8) assert subject.auto_xyz2enh(self.file, 1, 3, 5) == (2, 5, 8)
ifcopenshell.api.georeference.edit_georeferencing(self.file, map_conversion={"Scale": 0.001}) ifcopenshell.api.georeference.edit_georeferencing(self.file, coordinate_operation={"Scale": 0.001})
assert subject.auto_xyz2enh(self.file, 1000, 1000, 0) == (2, 3, 3) assert subject.auto_xyz2enh(self.file, 1000, 1000, 0) == (2, 3, 3)
assert subject.auto_xyz2enh(self.file, 1000, 1000, 0, should_return_in_map_units=False) == (2000, 3000, 3000) assert subject.auto_xyz2enh(self.file, 1000, 1000, 0, should_return_in_map_units=False) == (2000, 3000, 3000)
ifcopenshell.api.georeference.edit_georeferencing( ifcopenshell.api.georeference.edit_georeferencing(
self.file, map_conversion={"XAxisAbscissa": 0, "XAxisOrdinate": 1} self.file, coordinate_operation={"XAxisAbscissa": 0, "XAxisOrdinate": 1}
) )
assert np.allclose(subject.auto_xyz2enh(self.file, 1000, 1000, 0), (0, 3, 3)) assert np.allclose(subject.auto_xyz2enh(self.file, 1000, 1000, 0), (0, 3, 3))
def test_map_conversion_scaled(self):
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
ifcopenshell.api.context.add_context(self.file, "Model")
ifcopenshell.api.georeference.add_georeferencing(self.file, ifc_class="IfcMapConversionScaled")
ifcopenshell.api.georeference.edit_georeferencing(
self.file,
projected_crs={"Name": "EPSG:7856"},
coordinate_operation={"Eastings": 1, "Northings": 2, "OrthogonalHeight": 3},
)
assert subject.auto_xyz2enh(self.file, 0, 0, 0) == (1, 2, 3)
assert subject.auto_xyz2enh(self.file, 1, 3, 5) == (2, 5, 8)
ifcopenshell.api.georeference.edit_georeferencing(self.file, coordinate_operation={"Scale": 0.001})
assert subject.auto_xyz2enh(self.file, 1000, 1000, 0) == (2, 3, 3)
ifcopenshell.api.georeference.edit_georeferencing(
self.file, coordinate_operation={"FactorX": 0.9, "FactorY": 0.9}
)
assert np.allclose(subject.auto_xyz2enh(self.file, 1000, 1000, 0), (1.9, 2.9, 3))
class TestAutoENH2XYZ(test.bootstrap.IFC4): def test_rigid_operation(self):
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
ifcopenshell.api.context.add_context(self.file, "Model")
ifcopenshell.api.georeference.add_georeferencing(self.file, ifc_class="IfcRigidOperation")
ifcopenshell.api.georeference.edit_georeferencing(
self.file,
projected_crs={"Name": "EPSG:4258"},
coordinate_operation={
"FirstCoordinate": self.file.createIfcPlaneAngleMeasure(1),
"SecondCoordinate": self.file.createIfcPlaneAngleMeasure(2),
"Height": 3,
},
)
assert subject.auto_xyz2enh(self.file, 0, 0, 0) == (1, 2, 3)
assert subject.auto_xyz2enh(self.file, 1, 3, 5) == (2, 5, 8)
ifcopenshell.api.georeference.edit_georeferencing(
self.file,
coordinate_operation={
"FirstCoordinate": self.file.createIfcLengthMeasure(1),
"SecondCoordinate": self.file.createIfcLengthMeasure(2),
},
)
assert subject.auto_xyz2enh(self.file, 0, 0, 0) == (1, 2, 3)
assert subject.auto_xyz2enh(self.file, 1, 3, 5) == (2, 5, 8)
class TestAutoENH2XYZ(test.bootstrap.IFC4X3):
def test_no_georeferencing(self): def test_no_georeferencing(self):
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject") ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
assert subject.auto_enh2xyz(self.file, 0, 0, 0) == (0, 0, 0) assert subject.auto_enh2xyz(self.file, 0, 0, 0) == (0, 0, 0)
@@ -95,20 +138,63 @@ class TestAutoENH2XYZ(test.bootstrap.IFC4):
ifcopenshell.api.georeference.edit_georeferencing( ifcopenshell.api.georeference.edit_georeferencing(
self.file, self.file,
projected_crs={"Name": "EPSG:7856"}, projected_crs={"Name": "EPSG:7856"},
map_conversion={"Eastings": 1, "Northings": 2, "OrthogonalHeight": 3}, coordinate_operation={"Eastings": 1, "Northings": 2, "OrthogonalHeight": 3},
) )
assert subject.auto_enh2xyz(self.file, 1, 2, 3) == (0, 0, 0) assert subject.auto_enh2xyz(self.file, 1, 2, 3) == (0, 0, 0)
assert subject.auto_enh2xyz(self.file, 2, 5, 8) == (1, 3, 5) assert subject.auto_enh2xyz(self.file, 2, 5, 8) == (1, 3, 5)
ifcopenshell.api.georeference.edit_georeferencing(self.file, map_conversion={"Scale": 0.001}) ifcopenshell.api.georeference.edit_georeferencing(self.file, coordinate_operation={"Scale": 0.001})
assert np.allclose(subject.auto_enh2xyz(self.file, 2, 3, 3), (1000, 1000, 0)) assert np.allclose(subject.auto_enh2xyz(self.file, 2, 3, 3), (1000, 1000, 0))
assert np.allclose( assert np.allclose(
subject.auto_enh2xyz(self.file, 2000, 3000, 3000, is_specified_in_map_units=False), (1000, 1000, 0) subject.auto_enh2xyz(self.file, 2000, 3000, 3000, is_specified_in_map_units=False), (1000, 1000, 0)
) )
ifcopenshell.api.georeference.edit_georeferencing( ifcopenshell.api.georeference.edit_georeferencing(
self.file, map_conversion={"XAxisAbscissa": 0, "XAxisOrdinate": 1} self.file, coordinate_operation={"XAxisAbscissa": 0, "XAxisOrdinate": 1}
) )
assert np.allclose(subject.auto_enh2xyz(self.file, 0, 3, 3), (1000, 1000, 0)) assert np.allclose(subject.auto_enh2xyz(self.file, 0, 3, 3), (1000, 1000, 0))
def test_map_conversion_scaled(self):
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
ifcopenshell.api.context.add_context(self.file, "Model")
ifcopenshell.api.georeference.add_georeferencing(self.file, ifc_class="IfcMapConversionScaled")
ifcopenshell.api.georeference.edit_georeferencing(
self.file,
projected_crs={"Name": "EPSG:7856"},
coordinate_operation={"Eastings": 1, "Northings": 2, "OrthogonalHeight": 3},
)
assert subject.auto_enh2xyz(self.file, 1, 2, 3) == (0, 0, 0)
assert subject.auto_enh2xyz(self.file, 2, 5, 8) == (1, 3, 5)
ifcopenshell.api.georeference.edit_georeferencing(self.file, coordinate_operation={"Scale": 0.001})
assert subject.auto_enh2xyz(self.file, 2, 3, 3) == (1000, 1000, 0)
ifcopenshell.api.georeference.edit_georeferencing(
self.file, coordinate_operation={"FactorX": 0.9, "FactorY": 0.9}
)
assert np.allclose(subject.auto_enh2xyz(self.file, 1.9, 2.9, 3), (1000, 1000, 0))
def test_rigid_operation(self):
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
ifcopenshell.api.context.add_context(self.file, "Model")
ifcopenshell.api.georeference.add_georeferencing(self.file, ifc_class="IfcRigidOperation")
ifcopenshell.api.georeference.edit_georeferencing(
self.file,
projected_crs={"Name": "EPSG:4258"},
coordinate_operation={
"FirstCoordinate": self.file.createIfcPlaneAngleMeasure(1),
"SecondCoordinate": self.file.createIfcPlaneAngleMeasure(2),
"Height": 3,
},
)
assert subject.auto_enh2xyz(self.file, 1, 2, 3) == (0, 0, 0)
assert subject.auto_enh2xyz(self.file, 2, 5, 8) == (1, 3, 5)
ifcopenshell.api.georeference.edit_georeferencing(
self.file,
coordinate_operation={
"FirstCoordinate": self.file.createIfcLengthMeasure(1),
"SecondCoordinate": self.file.createIfcLengthMeasure(2),
},
)
assert subject.auto_enh2xyz(self.file, 1, 2, 3) == (0, 0, 0)
assert subject.auto_enh2xyz(self.file, 2, 5, 8) == (1, 3, 5)
class TestAutoZ2E(test.bootstrap.IFC4): class TestAutoZ2E(test.bootstrap.IFC4):
def test_no_georeferencing(self): def test_no_georeferencing(self):
@@ -123,15 +209,15 @@ class TestAutoZ2E(test.bootstrap.IFC4):
ifcopenshell.api.georeference.edit_georeferencing( ifcopenshell.api.georeference.edit_georeferencing(
self.file, self.file,
projected_crs={"Name": "EPSG:7856"}, projected_crs={"Name": "EPSG:7856"},
map_conversion={"Eastings": 1, "Northings": 2, "OrthogonalHeight": 3}, coordinate_operation={"Eastings": 1, "Northings": 2, "OrthogonalHeight": 3},
) )
assert subject.auto_z2e(self.file, 0) == 3 assert subject.auto_z2e(self.file, 0) == 3
assert subject.auto_z2e(self.file, 5) == 8 assert subject.auto_z2e(self.file, 5) == 8
ifcopenshell.api.georeference.edit_georeferencing(self.file, map_conversion={"Scale": 0.001}) ifcopenshell.api.georeference.edit_georeferencing(self.file, coordinate_operation={"Scale": 0.001})
assert np.isclose(subject.auto_z2e(self.file, 0), 3) assert np.isclose(subject.auto_z2e(self.file, 0), 3)
assert np.isclose(subject.auto_z2e(self.file, 0, should_return_in_map_units=False), 3000) assert np.isclose(subject.auto_z2e(self.file, 0, should_return_in_map_units=False), 3000)
ifcopenshell.api.georeference.edit_georeferencing( ifcopenshell.api.georeference.edit_georeferencing(
self.file, map_conversion={"XAxisAbscissa": 0, "XAxisOrdinate": 1} self.file, coordinate_operation={"XAxisAbscissa": 0, "XAxisOrdinate": 1}
) )
assert np.isclose(subject.auto_z2e(self.file, 0), 3) assert np.isclose(subject.auto_z2e(self.file, 0), 3)
@@ -221,16 +307,16 @@ class TestAutoLocal2Global(test.bootstrap.IFC4):
ifcopenshell.api.georeference.edit_georeferencing( ifcopenshell.api.georeference.edit_georeferencing(
self.file, self.file,
projected_crs={"Name": "EPSG:7856"}, projected_crs={"Name": "EPSG:7856"},
map_conversion={"Eastings": 1, "Northings": 2, "OrthogonalHeight": 3}, coordinate_operation={"Eastings": 1, "Northings": 2, "OrthogonalHeight": 3},
) )
m2[:, 3][0:3] = [1, 2, 3] m2[:, 3][0:3] = [1, 2, 3]
assert np.allclose(subject.auto_local2global(self.file, m), m2) assert np.allclose(subject.auto_local2global(self.file, m), m2)
ifcopenshell.api.georeference.edit_georeferencing(self.file, map_conversion={"Scale": 0.001}) ifcopenshell.api.georeference.edit_georeferencing(self.file, coordinate_operation={"Scale": 0.001})
assert np.allclose(subject.auto_local2global(self.file, m), m2) assert np.allclose(subject.auto_local2global(self.file, m), m2)
m2[:, 3][0:3] = [1000, 2000, 3000] m2[:, 3][0:3] = [1000, 2000, 3000]
assert np.allclose(subject.auto_local2global(self.file, m, should_return_in_map_units=False), m2) assert np.allclose(subject.auto_local2global(self.file, m, should_return_in_map_units=False), m2)
ifcopenshell.api.georeference.edit_georeferencing( ifcopenshell.api.georeference.edit_georeferencing(
self.file, map_conversion={"XAxisAbscissa": 0, "XAxisOrdinate": 1} self.file, coordinate_operation={"XAxisAbscissa": 0, "XAxisOrdinate": 1}
) )
m[:, 3][0:3] = [1000, 1000, 0] m[:, 3][0:3] = [1000, 1000, 0]
m2[:, 0][0:3] = [0, 1, 0] m2[:, 0][0:3] = [0, 1, 0]
@@ -256,16 +342,16 @@ class TestAutoGlobal2Local(test.bootstrap.IFC4):
ifcopenshell.api.georeference.edit_georeferencing( ifcopenshell.api.georeference.edit_georeferencing(
self.file, self.file,
projected_crs={"Name": "EPSG:7856"}, projected_crs={"Name": "EPSG:7856"},
map_conversion={"Eastings": 1, "Northings": 2, "OrthogonalHeight": 3}, coordinate_operation={"Eastings": 1, "Northings": 2, "OrthogonalHeight": 3},
) )
m2[:, 3][0:3] = [1, 2, 3] m2[:, 3][0:3] = [1, 2, 3]
assert np.allclose(subject.auto_global2local(self.file, m2), m) assert np.allclose(subject.auto_global2local(self.file, m2), m)
ifcopenshell.api.georeference.edit_georeferencing(self.file, map_conversion={"Scale": 0.001}) ifcopenshell.api.georeference.edit_georeferencing(self.file, coordinate_operation={"Scale": 0.001})
assert np.allclose(subject.auto_global2local(self.file, m2), m) assert np.allclose(subject.auto_global2local(self.file, m2), m)
m2[:, 3][0:3] = [1000, 2000, 3000] m2[:, 3][0:3] = [1000, 2000, 3000]
assert np.allclose(subject.auto_global2local(self.file, m2, is_specified_in_map_units=False), m) assert np.allclose(subject.auto_global2local(self.file, m2, is_specified_in_map_units=False), m)
ifcopenshell.api.georeference.edit_georeferencing( ifcopenshell.api.georeference.edit_georeferencing(
self.file, map_conversion={"XAxisAbscissa": 0, "XAxisOrdinate": 1} self.file, coordinate_operation={"XAxisAbscissa": 0, "XAxisOrdinate": 1}
) )
m[:, 3][0:3] = [1000, 1000, 0] m[:, 3][0:3] = [1000, 1000, 0]
m2[:, 0][0:3] = [0, 1, 0] m2[:, 0][0:3] = [0, 1, 0]