Initial implementation of scale dependent map conversion for IFC4X3

This commit is contained in:
Dion Moult
2023-08-04 14:42:31 +10:00
parent e88e2f5d9c
commit 945836f578
2 changed files with 169 additions and 2 deletions
@@ -54,6 +54,27 @@ def xyz2enh(x, y, z, eastings, northings, orthogonal_height, x_axis_abscissa, x_
return (eastings, northings, height)
def xyz2enh_ifc4x3(
x,
y,
z,
eastings,
northings,
orthogonal_height,
x_axis_abscissa,
x_axis_ordinate,
scale=1.0,
factor_x=1.0,
factor_y=1.0,
factor_z=1.0,
):
theta = math.atan2(x_axis_ordinate, x_axis_abscissa)
eastings = (scale * factor_x * math.cos(theta) * x) - (scale * factor_y * math.sin(theta) * y) + eastings
northings = (scale * factor_x * math.sin(theta) * x) + (scale * factor_y * math.cos(theta) * y) + northings
height = (scale * factor_z * z) + orthogonal_height
return (eastings, northings, height)
def auto_z2e(ifc_file, z):
"""Convert a Z coordinate to an elevation using model georeferencing data
@@ -78,6 +99,9 @@ def auto_z2e(ifc_file, z):
h = conversion.OrthogonalHeight
map_unit = conversion.TargetCRS.MapUnit
if map_unit:
# Warning! This definition has changed in IFC4X3 such that map_unit no
# longer affects unit conversion, only the Scale attribute affects unit
# conversion. TODO: consolidate once IFC4X3 confirmed.
project_unit = ifcopenshell.util.unit.get_project_unit(ifc_file, "LENGTHUNIT")
h = ifcopenshell.util.unit.convert(
h,
@@ -128,6 +152,46 @@ def local2global(matrix, eastings, northings, orthogonal_height, x_axis_abscissa
return intermediate
def local2global_ifc4x3(
matrix,
eastings,
northings,
orthogonal_height,
x_axis_abscissa,
x_axis_ordinate,
scale=1.0,
factor_x=1.0,
factor_y=1.0,
factor_z=1.0,
):
# Matrix is a 4x4 matrix typically describing the object placement of an element.
theta = math.atan2(x_axis_ordinate, x_axis_abscissa)
scale_and_factor_matrix = np.array(
[
[scale * factor_x, 0, 0, 0],
[0, scale * factor_y, 0, 0],
[0, 0, scale * factor_z, 0],
[0, 0, 0, 1],
]
)
rotation_matrix = np.array(
[
[math.cos(theta), -math.sin(theta), 0, 0],
[math.sin(theta), math.cos(theta), 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
]
)
result = rotation_matrix @ scale_and_factor_matrix @ matrix
result[:, 0][0:3] /= np.linalg.norm(result[:, 0][0:3])
result[:, 1][0:3] /= np.linalg.norm(result[:, 1][0:3])
result[:, 2][0:3] /= np.linalg.norm(result[:, 2][0:3])
result[0][3] += eastings
result[1][3] += northings
result[2][3] += orthogonal_height
return result
def global2local(matrix, eastings, northings, orthogonal_height, x_axis_abscissa, x_axis_ordinate, scale=None):
if scale is None:
scale = 1.0
@@ -193,12 +257,13 @@ def get_true_north(ifc_file):
def angle2xaxis(angle):
angle_rad = math.radians(angle)
x = math.cos(angle_rad)
y = - math.sin(angle_rad)
y = -math.sin(angle_rad)
return x, y
# Used for converting True North angle as seen in CAD (relative to +Y)
def angle2yaxis(angle):
angle_rad = math.radians(angle)
x = - math.sin(angle_rad)
x = -math.sin(angle_rad)
y = math.cos(angle_rad)
return x, y
@@ -0,0 +1,102 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import pytest
import numpy as np
import test.bootstrap
import ifcopenshell.util.geolocation as subject
class TestXYZ2ENH(test.bootstrap.IFC4):
def test_converting_from_a_local_xyz_point_to_a_global_easting_northing_height(self):
assert subject.xyz2enh(0, 0, 0, 0, 0, 0, 1, 0) == (0, 0, 0)
assert subject.xyz2enh(0, 0, 0, 1, 2, 3, 1, 0) == (1, 2, 3)
assert subject.xyz2enh(0, 0, 0, 1, 2, 3, 0, 1) == (1, 2, 3)
assert np.allclose(subject.xyz2enh(1, 1, 0, 1, 2, 3, 1, 0), (2, 3, 3))
assert np.allclose(subject.xyz2enh(1, 1, 0, 1, 2, 3, 1, 0, 2), (3, 4, 3))
assert np.allclose(subject.xyz2enh(1, 1, 0, 1, 2, 3, 0, 1), (0, 3, 3))
class TestXYZ2ENHIfc4X3(test.bootstrap.IFC4):
def test_converting_from_a_local_xyz_point_to_a_global_easting_northing_height(self):
assert subject.xyz2enh_ifc4x3(0, 0, 0, 0, 0, 0, 1, 0) == (0, 0, 0)
assert subject.xyz2enh_ifc4x3(0, 0, 0, 1, 2, 3, 1, 0) == (1, 2, 3)
assert subject.xyz2enh_ifc4x3(0, 0, 0, 1, 2, 3, 0, 1) == (1, 2, 3)
assert np.allclose(subject.xyz2enh_ifc4x3(1, 1, 0, 1, 2, 3, 1, 0), (2, 3, 3))
assert np.allclose(subject.xyz2enh_ifc4x3(1, 1, 0, 1, 2, 3, 1, 0, 2), (3, 4, 3))
assert np.allclose(subject.xyz2enh_ifc4x3(1, 1, 1, 1, 2, 3, 1, 0, 2, 2, 3, 4), (5, 8, 11))
assert np.allclose(subject.xyz2enh_ifc4x3(1, 1, 0, 1, 2, 3, 0, 1), (0, 3, 3))
class TestLocal2Global(test.bootstrap.IFC4):
def test_converting_from_a_local_matrix_to_a_global_matrix(self):
m = np.eye(4)
m2 = np.eye(4)
assert np.allclose(subject.local2global(m, 0, 0, 0, 1.0, 0.0), m2)
m2[:, 3][0:3] = [1, 2, 3]
assert np.allclose(subject.local2global(m, 1, 2, 3, 1.0, 0.0), m2)
m2[:, 0][0:3] = [0, 1, 0]
m2[:, 1][0:3] = [-1, 0, 0]
assert np.allclose(subject.local2global(m, 1, 2, 3, 0.0, 1.0), m2)
m[:, 3][0:3] = [1, 1, 0]
m2 = np.eye(4)
m2[:, 3][0:3] = [2, 3, 3]
assert np.allclose(subject.local2global(m, 1, 2, 3, 1.0, 0.0), m2)
m2[:, 3][0:3] = [3, 4, 3]
assert np.allclose(subject.local2global(m, 1, 2, 3, 1.0, 0.0, 2), m2)
m2[:, 0][0:3] = [0, 1, 0]
m2[:, 1][0:3] = [-1, 0, 0]
m2[:, 3][0:3] = [0, 3, 3]
assert np.allclose(subject.local2global(m, 1, 2, 3, 0.0, 1.0), m2)
class TestLocal2GlobalIfc4X3(test.bootstrap.IFC4):
def test_converting_from_a_local_matrix_to_a_global_matrix(self):
m = np.eye(4)
m2 = np.eye(4)
assert np.allclose(subject.local2global_ifc4x3(m, 0, 0, 0, 1.0, 0.0), m2)
m2[:, 3][0:3] = [1, 2, 3]
assert np.allclose(subject.local2global_ifc4x3(m, 1, 2, 3, 1.0, 0.0), m2)
m2[:, 0][0:3] = [0, 1, 0]
m2[:, 1][0:3] = [-1, 0, 0]
assert np.allclose(subject.local2global_ifc4x3(m, 1, 2, 3, 0.0, 1.0), m2)
m[:, 3][0:3] = [1, 1, 0]
m2 = np.eye(4)
m2[:, 3][0:3] = [2, 3, 3]
assert np.allclose(subject.local2global_ifc4x3(m, 1, 2, 3, 1.0, 0.0), m2)
m2[:, 3][0:3] = [3, 4, 3]
assert np.allclose(subject.local2global_ifc4x3(m, 1, 2, 3, 1.0, 0.0, 2), m2)
m2[:, 0][0:3] = [0, 1, 0]
m2[:, 1][0:3] = [-1, 0, 0]
m2[:, 3][0:3] = [0, 3, 3]
assert np.allclose(subject.local2global_ifc4x3(m, 1, 2, 3, 0.0, 1.0), m2)
m[:, 3][0:3] = [1, 1, 1]
m2 = np.eye(4)
m2[:, 3][0:3] = [5, 8, 11]
assert np.allclose(subject.local2global_ifc4x3(m, 1, 2, 3, 1.0, 0.0, 2, 2, 3, 4), m2)