From f6cae1f8784f593227fc56cb699cb370f275f9af Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 16 Jul 2023 09:01:22 +1000 Subject: [PATCH] Move QgsOrientedBoundingBox out to own file --- .../geometry/qgsorientedboundingbox.sip.in | 93 +++++++++++++++ .../tiledmesh/qgscesiumutils.sip.in | 72 ------------ python/core/core_auto.sip | 1 + src/core/CMakeLists.txt | 2 + src/core/geometry/qgsorientedboundingbox.cpp | 74 ++++++++++++ src/core/geometry/qgsorientedboundingbox.h | 107 ++++++++++++++++++ .../tiledmesh/qgscesiumtilesdataprovider.cpp | 3 +- src/core/tiledmesh/qgscesiumutils.cpp | 100 ++++------------ src/core/tiledmesh/qgscesiumutils.h | 82 +------------- tests/src/python/CMakeLists.txt | 1 + tests/src/python/test_qgscesiumutils.py | 51 +-------- .../src/python/test_qgsorientedboundingbox.py | 91 +++++++++++++++ 12 files changed, 392 insertions(+), 285 deletions(-) create mode 100644 python/core/auto_generated/geometry/qgsorientedboundingbox.sip.in create mode 100644 src/core/geometry/qgsorientedboundingbox.cpp create mode 100644 src/core/geometry/qgsorientedboundingbox.h create mode 100644 tests/src/python/test_qgsorientedboundingbox.py diff --git a/python/core/auto_generated/geometry/qgsorientedboundingbox.sip.in b/python/core/auto_generated/geometry/qgsorientedboundingbox.sip.in new file mode 100644 index 000000000000..5ed493f8ade1 --- /dev/null +++ b/python/core/auto_generated/geometry/qgsorientedboundingbox.sip.in @@ -0,0 +1,93 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/geometry/qgsorientedboundingbox.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + + +class QgsOrientedBoundingBox +{ +%Docstring(signature="appended") +Represents a oriented (rotated) bounding box in 3 dimensions. + +.. warning:: + + Non-stable API, exposed to Python for unit testing only. + +.. versionadded:: 3.34 +%End + +%TypeHeaderCode +#include "qgsorientedboundingbox.h" +%End + public: + + QgsOrientedBoundingBox(); +%Docstring +Constructor for a null bounding box. +%End + + QgsOrientedBoundingBox( const QList ¢er, QList< double > &halfAxes ); +%Docstring +Constructor for a oriented bounding box, with a specified center and half axes matrix. +%End + + bool isNull() const; +%Docstring +Returns ``True`` if the box is a null bounding box. +%End + + double centerX() const; +%Docstring +Returns the center x-coordinate. + +.. seealso:: :py:func:`centerY` + +.. seealso:: :py:func:`centerZ` +%End + + double centerY() const; +%Docstring +Returns the center y-coordinate. + +.. seealso:: :py:func:`centerX` + +.. seealso:: :py:func:`centerZ` +%End + + double centerZ() const; +%Docstring +Returns the center z-coordinate. + +.. seealso:: :py:func:`centerX` + +.. seealso:: :py:func:`centerY` +%End + + + QList< double > halfAxesList() const /PyName=halfAxes/; +%Docstring +Returns the half axes matrix; +%End + + QgsBox3d extent() const; +%Docstring +Returns the overall bounding box of the object. +%End + +}; + + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/geometry/qgsorientedboundingbox.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/auto_generated/tiledmesh/qgscesiumutils.sip.in b/python/core/auto_generated/tiledmesh/qgscesiumutils.sip.in index 4d6d879ef371..31a4489c5b7d 100644 --- a/python/core/auto_generated/tiledmesh/qgscesiumutils.sip.in +++ b/python/core/auto_generated/tiledmesh/qgscesiumutils.sip.in @@ -11,78 +11,6 @@ -class QgsOrientedBoundingBox -{ -%Docstring(signature="appended") -Represents a oriented (rotated) bounding box in 3 dimensions. - -.. warning:: - - Non-stable API, exposed to Python for unit testing only. - -.. versionadded:: 3.34 -%End - -%TypeHeaderCode -#include "qgscesiumutils.h" -%End - public: - - QgsOrientedBoundingBox(); -%Docstring -Constructor for a null bounding box. -%End - - QgsOrientedBoundingBox( const QList ¢er, QList< double > &halfAxes ); -%Docstring -Constructor for a oriented bounding box, with a specified center and half axes matrix. -%End - - bool isNull() const; -%Docstring -Returns ``True`` if the box is a null bounding box. -%End - - double centerX() const; -%Docstring -Returns the center x-coordinate. - -.. seealso:: :py:func:`centerY` - -.. seealso:: :py:func:`centerZ` -%End - - double centerY() const; -%Docstring -Returns the center y-coordinate. - -.. seealso:: :py:func:`centerX` - -.. seealso:: :py:func:`centerZ` -%End - - double centerZ() const; -%Docstring -Returns the center z-coordinate. - -.. seealso:: :py:func:`centerX` - -.. seealso:: :py:func:`centerY` -%End - - - QList< double > halfAxesList() const /PyName=halfAxes/; -%Docstring -Returns the half axes matrix; -%End - - QgsBox3d extent() const; -%Docstring -Returns the overall bounding box of the object. -%End - -}; - class QgsCesiumUtils { %Docstring(signature="appended") diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index c833d000cf19..fc54db2f0628 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -345,6 +345,7 @@ %Include auto_generated/geometry/qgsmultipoint.sip %Include auto_generated/geometry/qgsmultipolygon.sip %Include auto_generated/geometry/qgsmultisurface.sip +%Include auto_generated/geometry/qgsorientedboundingbox.sip %Include auto_generated/geometry/qgspoint.sip %Include auto_generated/geometry/qgspolygon.sip %Include auto_generated/geometry/qgsquadrilateral.sip diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c950a464a5cd..80c119ac96ae 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -847,6 +847,7 @@ set(QGIS_CORE_SRCS geometry/qgsmultipoint.cpp geometry/qgsmultipolygon.cpp geometry/qgsmultisurface.cpp + geometry/qgsorientedboundingbox.cpp geometry/qgspoint.cpp geometry/qgspolygon.cpp geometry/qgsquadrilateral.cpp @@ -1431,6 +1432,7 @@ set(QGIS_CORE_HDRS geometry/qgsmultipoint.h geometry/qgsmultipolygon.h geometry/qgsmultisurface.h + geometry/qgsorientedboundingbox.h geometry/qgspoint.h geometry/qgspolygon.h geometry/qgsquadrilateral.h diff --git a/src/core/geometry/qgsorientedboundingbox.cpp b/src/core/geometry/qgsorientedboundingbox.cpp new file mode 100644 index 000000000000..a8434476a0eb --- /dev/null +++ b/src/core/geometry/qgsorientedboundingbox.cpp @@ -0,0 +1,74 @@ +/*************************************************************************** + qgsorientedboundingbox.cpp + -------------------- + begin : July 2023 + copyright : (C) 2023 by Nyall Dawson + email : nyall dot dawson at gmail dot com + ****************************************************************** + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsorientedboundingbox.h" +#include "qgsbox3d.h" + +QgsOrientedBoundingBox::QgsOrientedBoundingBox() = default; + +QgsOrientedBoundingBox::QgsOrientedBoundingBox( const QList ¢er, QList &halfAxes ) +{ + if ( center.size() == 3 ) + { + mCenter[0] = center.at( 0 ); + mCenter[1] = center.at( 1 ); + mCenter[2] = center.at( 2 ); + } + if ( halfAxes.size() == 9 ) + { + for ( int i = 0; i < 9; ++i ) + { + mHalfAxes[i] = halfAxes.at( i ); + } + } +} + +bool QgsOrientedBoundingBox::isNull() const +{ + return std::isnan( mCenter[0] ) || std::isnan( mCenter[1] ) || std::isnan( mCenter[2] ); +} + +QList< double > QgsOrientedBoundingBox::halfAxesList() const +{ + QList< double > res; + res.reserve( 9 ); + for ( int i = 0; i < 9; ++i ) + { + res.append( mHalfAxes[i] ); + } + return res; +} + +QgsBox3d QgsOrientedBoundingBox::extent() const +{ + const double extent[3] + { + std::fabs( mHalfAxes[0] ) + std::fabs( mHalfAxes[3] ) + std::fabs( mHalfAxes[6] ), + std::fabs( mHalfAxes[1] ) + std::fabs( mHalfAxes[4] ) + std::fabs( mHalfAxes[7] ), + std::fabs( mHalfAxes[2] ) + std::fabs( mHalfAxes[5] ) + std::fabs( mHalfAxes[8] ), + }; + + const double minX = mCenter[0] - extent[0]; + const double maxX = mCenter[0] + extent[0]; + const double minY = mCenter[1] - extent[1]; + const double maxY = mCenter[1] + extent[1]; + const double minZ = mCenter[2] - extent[2]; + const double maxZ = mCenter[2] + extent[2]; + + return QgsBox3d( minX, minY, minZ, maxX, maxY, maxZ ); +} diff --git a/src/core/geometry/qgsorientedboundingbox.h b/src/core/geometry/qgsorientedboundingbox.h new file mode 100644 index 000000000000..914639917332 --- /dev/null +++ b/src/core/geometry/qgsorientedboundingbox.h @@ -0,0 +1,107 @@ +/*************************************************************************** + qgsorientedboundingbox.h + -------------------- + begin : July 2023 + copyright : (C) 2023 by Nyall Dawson + email : nyall dot dawson at gmail dot com + ****************************************************************** + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSORIENTEDBOUNDINGBOX_H +#define QGSORIENTEDBOUNDINGBOX_H + +#include "qgis_core.h" +#include "qgis_sip.h" + +#include +#include + +class QgsBox3d; + +/** + * \brief Represents a oriented (rotated) bounding box in 3 dimensions. + * + * \ingroup core + * + * \warning Non-stable API, exposed to Python for unit testing only. + * + * \since QGIS 3.34 + */ +class CORE_EXPORT QgsOrientedBoundingBox +{ + public: + + /** + * Constructor for a null bounding box. + */ + QgsOrientedBoundingBox(); + + /** + * Constructor for a oriented bounding box, with a specified center and half axes matrix. + */ + QgsOrientedBoundingBox( const QList ¢er, QList< double > &halfAxes ); + + /** + * Returns TRUE if the box is a null bounding box. + */ + bool isNull() const; + + /** + * Returns the center x-coordinate. + * + * \see centerY() + * \see centerZ() + */ + double centerX() const { return mCenter[0]; } + + /** + * Returns the center y-coordinate. + + * \see centerX() + * \see centerZ() + */ + double centerY() const { return mCenter[1]; } + + /** + * Returns the center z-coordinate. + * + * \see centerX() + * \see centerY() + */ + double centerZ() const { return mCenter[2]; } + + /** + * Returns the half axes matrix; + */ + const double *halfAxes() const SIP_SKIP { return mHalfAxes; } + + /** + * Returns the half axes matrix; + */ + QList< double > halfAxesList() const SIP_PYNAME( halfAxes ); + + /** + * Returns the overall bounding box of the object. + */ + QgsBox3d extent() const; + + private: + + double mCenter[ 3 ] { std::numeric_limits< double >::quiet_NaN(), std::numeric_limits< double >::quiet_NaN(), std::numeric_limits< double >::quiet_NaN() }; + double mHalfAxes[9] { 1, 0, 0, 0, 1, 0, 0, 0, 1 }; + + friend class QgsCesiumUtils; + +}; + + +#endif // QGSORIENTEDBOUNDINGBOX_H diff --git a/src/core/tiledmesh/qgscesiumtilesdataprovider.cpp b/src/core/tiledmesh/qgscesiumtilesdataprovider.cpp index 6855a68ed4aa..f53b7a0e1d04 100644 --- a/src/core/tiledmesh/qgscesiumtilesdataprovider.cpp +++ b/src/core/tiledmesh/qgscesiumtilesdataprovider.cpp @@ -25,6 +25,7 @@ #include "qgscesiumutils.h" #include "qgssphere.h" #include "qgslogger.h" +#include "qgsorientedboundingbox.h" #include #include @@ -72,7 +73,7 @@ void QgsCesiumTilesDataProviderSharedData::setTilesetContent( const QString &til } else if ( rootBoundingVolume.contains( "box" ) ) { - const QgsOrientedBoundingBox bbox = QgsOrientedBoundingBox::fromJson( rootBoundingVolume["box"] ); + const QgsOrientedBoundingBox bbox = QgsCesiumUtils::parseBox( rootBoundingVolume["box"] ); if ( !bbox.isNull() ) { const QgsBox3d rootRegion = bbox.extent(); diff --git a/src/core/tiledmesh/qgscesiumutils.cpp b/src/core/tiledmesh/qgscesiumutils.cpp index 974439391f63..0d53d1d05e9b 100644 --- a/src/core/tiledmesh/qgscesiumutils.cpp +++ b/src/core/tiledmesh/qgscesiumutils.cpp @@ -20,6 +20,7 @@ #include "nlohmann/json.hpp" #include "qgsjsonutils.h" #include "qgssphere.h" +#include "qgsorientedboundingbox.h" QgsBox3d QgsCesiumUtils::parseRegion( const json ®ion ) { @@ -52,7 +53,23 @@ QgsOrientedBoundingBox QgsCesiumUtils::parseBox( const json &box ) if ( box.size() != 12 ) return QgsOrientedBoundingBox(); - return QgsOrientedBoundingBox::fromJson( box ); + try + { + QgsOrientedBoundingBox res; + for ( int i = 0; i < 3; ++i ) + { + res.mCenter[i] = box[i].get(); + } + for ( int i = 0; i < 9; ++i ) + { + res.mHalfAxes[i] = box[i + 3].get(); + } + return res; + } + catch ( nlohmann::json::exception & ) + { + return QgsOrientedBoundingBox(); + } } QgsOrientedBoundingBox QgsCesiumUtils::parseBox( const QVariantList &box ) @@ -60,7 +77,7 @@ QgsOrientedBoundingBox QgsCesiumUtils::parseBox( const QVariantList &box ) if ( box.size() != 12 ) return QgsOrientedBoundingBox(); - return QgsOrientedBoundingBox::fromJson( QgsJsonUtils::jsonFromVariant( box ) ); + return parseBox( QgsJsonUtils::jsonFromVariant( box ) ); } QgsSphere QgsCesiumUtils::parseSphere( const json &sphere ) @@ -89,82 +106,3 @@ QgsSphere QgsCesiumUtils::parseSphere( const QVariantList &sphere ) return parseSphere( QgsJsonUtils::jsonFromVariant( sphere ) ); } - -// -// QgsOrientedBoundingBox -// - -QgsOrientedBoundingBox QgsOrientedBoundingBox::fromJson( const json &json ) -{ - try - { - QgsOrientedBoundingBox res; - for ( int i = 0; i < 3; ++i ) - { - res.mCenter[i] = json[i].get(); - } - for ( int i = 0; i < 9; ++i ) - { - res.mHalfAxes[i] = json[i + 3].get(); - } - return res; - } - catch ( nlohmann::json::exception & ) - { - return QgsOrientedBoundingBox(); - } -} - -QgsOrientedBoundingBox::QgsOrientedBoundingBox() = default; - -QgsOrientedBoundingBox::QgsOrientedBoundingBox( const QList ¢er, QList &halfAxes ) -{ - if ( center.size() == 3 ) - { - mCenter[0] = center.at( 0 ); - mCenter[1] = center.at( 1 ); - mCenter[2] = center.at( 2 ); - } - if ( halfAxes.size() == 9 ) - { - for ( int i = 0; i < 9; ++i ) - { - mHalfAxes[i] = halfAxes.at( i ); - } - } -} - -bool QgsOrientedBoundingBox::isNull() const -{ - return std::isnan( mCenter[0] ) || std::isnan( mCenter[1] ) || std::isnan( mCenter[2] ); -} - -QList< double > QgsOrientedBoundingBox::halfAxesList() const -{ - QList< double > res; - res.reserve( 9 ); - for ( int i = 0; i < 9; ++i ) - { - res.append( mHalfAxes[i] ); - } - return res; -} - -QgsBox3d QgsOrientedBoundingBox::extent() const -{ - const double extent[3] - { - std::fabs( mHalfAxes[0] ) + std::fabs( mHalfAxes[3] ) + std::fabs( mHalfAxes[6] ), - std::fabs( mHalfAxes[1] ) + std::fabs( mHalfAxes[4] ) + std::fabs( mHalfAxes[7] ), - std::fabs( mHalfAxes[2] ) + std::fabs( mHalfAxes[5] ) + std::fabs( mHalfAxes[8] ), - }; - - const double minX = mCenter[0] - extent[0]; - const double maxX = mCenter[0] + extent[0]; - const double minY = mCenter[1] - extent[1]; - const double maxY = mCenter[1] + extent[1]; - const double minZ = mCenter[2] - extent[2]; - const double maxZ = mCenter[2] + extent[2]; - - return QgsBox3d( minX, minY, minZ, maxX, maxY, maxZ ); -} diff --git a/src/core/tiledmesh/qgscesiumutils.h b/src/core/tiledmesh/qgscesiumutils.h index dcd0f63a2944..4fd9e01555a5 100644 --- a/src/core/tiledmesh/qgscesiumutils.h +++ b/src/core/tiledmesh/qgscesiumutils.h @@ -29,87 +29,7 @@ using namespace nlohmann; #endif class QgsSphere; - -/** - * \brief Represents a oriented (rotated) bounding box in 3 dimensions. - * - * \ingroup core - * - * \warning Non-stable API, exposed to Python for unit testing only. - * - * \since QGIS 3.34 - */ -class CORE_EXPORT QgsOrientedBoundingBox -{ - public: -#ifndef SIP_RUN - - /** - * Creates an oriented bounding box from a Cesium json object. - */ - static QgsOrientedBoundingBox fromJson( const json &json ); -#endif - - /** - * Constructor for a null bounding box. - */ - QgsOrientedBoundingBox(); - - /** - * Constructor for a oriented bounding box, with a specified center and half axes matrix. - */ - QgsOrientedBoundingBox( const QList ¢er, QList< double > &halfAxes ); - - /** - * Returns TRUE if the box is a null bounding box. - */ - bool isNull() const; - - /** - * Returns the center x-coordinate. - * - * \see centerY() - * \see centerZ() - */ - double centerX() const { return mCenter[0]; } - - /** - * Returns the center y-coordinate. - - * \see centerX() - * \see centerZ() - */ - double centerY() const { return mCenter[1]; } - - /** - * Returns the center z-coordinate. - * - * \see centerX() - * \see centerY() - */ - double centerZ() const { return mCenter[2]; } - - /** - * Returns the half axes matrix; - */ - const double *halfAxes() const SIP_SKIP { return mHalfAxes; } - - /** - * Returns the half axes matrix; - */ - QList< double > halfAxesList() const SIP_PYNAME( halfAxes ); - - /** - * Returns the overall bounding box of the object. - */ - QgsBox3d extent() const; - - private: - - double mCenter[ 3 ] { std::numeric_limits< double >::quiet_NaN(), std::numeric_limits< double >::quiet_NaN(), std::numeric_limits< double >::quiet_NaN() }; - double mHalfAxes[9] { 1, 0, 0, 0, 1, 0, 0, 0, 1 }; - -}; +class QgsOrientedBoundingBox; /** * \brief Contains utilities for working with Cesium data. diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index d78478a0e0be..1849f913d882 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -252,6 +252,7 @@ ADD_PYTHON_TEST(PyQgsOGRProviderGpkg test_provider_ogr_gpkg.py) ADD_PYTHON_TEST(PyQgsOGRProviderSqlite test_provider_ogr_sqlite.py) ADD_PYTHON_TEST(PyQgsOpacityWidget test_qgsopacitywidget.py) ADD_PYTHON_TEST(PyQgsOptional test_qgsoptional.py) +ADD_PYTHON_TEST(PyQgsOrientedBoundingBox test_qgsorientedboundingbox.py) ADD_PYTHON_TEST(PyQgsOwsConnection test_qgsowsconnection.py) ADD_PYTHON_TEST(PyQgsPalLabelingBase test_qgspallabeling_base.py) ADD_PYTHON_TEST(PyQgsPalLabelingCanvas test_qgspallabeling_canvas.py) diff --git a/tests/src/python/test_qgscesiumutils.py b/tests/src/python/test_qgscesiumutils.py index 4b3e5005743f..27fc4e3648cf 100644 --- a/tests/src/python/test_qgscesiumutils.py +++ b/tests/src/python/test_qgscesiumutils.py @@ -14,7 +14,6 @@ import math import qgis # NOQA from qgis.core import ( - QgsOrientedBoundingBox, QgsCesiumUtils ) import unittest @@ -46,7 +45,7 @@ def test_parse_region(self): self.assertEqual(box.zMinimum(), 5.5) self.assertEqual(box.zMaximum(), 6.0) - def test_oriented_bounding_box(self): + def test_parse_oriented_bounding_box(self): box = QgsCesiumUtils.parseBox([]) self.assertTrue(box.isNull()) @@ -63,54 +62,6 @@ def test_oriented_bounding_box(self): self.assertEqual(box.centerZ(), 3) self.assertEqual(box.halfAxes(), [10.0, 0.0, 0.0, 0.0, 20.0, 0.0, 0.0, 0.0, 30.0]) - box = QgsCesiumUtils.parseBox([1, 2, 3, 1, 0, 0, 0, 1, 0, 0, 0, 1]) - self.assertEqual(box.centerX(), 1) - self.assertEqual(box.centerY(), 2) - self.assertEqual(box.centerZ(), 3) - self.assertEqual(box.halfAxes(), [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]) - - # 45 degree y axis rotation - box = QgsCesiumUtils.parseBox([1, 2, 3, - math.cos(math.pi / 4), 0, math.sin(math.pi / 4), - 0, 1, 0, - -math.sin(math.pi / 4), 0, math.cos(math.pi / 4)]) - self.assertEqual(box.centerX(), 1) - self.assertEqual(box.centerY(), 2) - self.assertEqual(box.centerZ(), 3) - self.assertEqual(box.halfAxes(), [0.7071067811865476, 0.0, 0.7071067811865475, 0.0, 1.0, 0.0, -0.7071067811865475, 0.0, 0.7071067811865476]) - - def test_box_extent(self): - box = QgsCesiumUtils.parseBox([1, 2, 3, 10, 0, 0, 0, 20, 0, 0, 0, 30]) - bounds = box.extent() - self.assertEqual(bounds.xMinimum(), -9) - self.assertEqual(bounds.xMaximum(), 11) - self.assertEqual(bounds.yMinimum(), -18) - self.assertEqual(bounds.yMaximum(), 22) - self.assertEqual(bounds.zMinimum(), -27) - self.assertEqual(bounds.zMaximum(), 33) - - box = QgsCesiumUtils.parseBox([1, 2, 3, 1, 0, 0, 0, 1, 0, 0, 0, 1]) - bounds = box.extent() - self.assertEqual(bounds.xMinimum(), 0) - self.assertEqual(bounds.xMaximum(), 2) - self.assertEqual(bounds.yMinimum(), 1) - self.assertEqual(bounds.yMaximum(), 3) - self.assertEqual(bounds.zMinimum(), 2) - self.assertEqual(bounds.zMaximum(), 4) - - # 45 degree y axis rotation - box = QgsCesiumUtils.parseBox([1, 2, 3, - math.cos(math.pi / 4), 0, math.sin(math.pi / 4), - 0, 1, 0, - -math.sin(math.pi / 4), 0, math.cos(math.pi / 4)]) - bounds = box.extent() - self.assertAlmostEqual(bounds.xMinimum(), 1 - math.sqrt(2), 5) - self.assertAlmostEqual(bounds.xMaximum(), 1 + math.sqrt(2), 5) - self.assertAlmostEqual(bounds.yMinimum(), 1, 5) - self.assertAlmostEqual(bounds.yMaximum(), 3, 5) - self.assertAlmostEqual(bounds.zMinimum(), 3 - math.sqrt(2), 5) - self.assertAlmostEqual(bounds.zMaximum(), 3 + math.sqrt(2), 5) - def test_parse_sphere(self): sphere = QgsCesiumUtils.parseSphere([]) self.assertTrue(sphere.isNull()) diff --git a/tests/src/python/test_qgsorientedboundingbox.py b/tests/src/python/test_qgsorientedboundingbox.py new file mode 100644 index 000000000000..e87b3679c1a4 --- /dev/null +++ b/tests/src/python/test_qgsorientedboundingbox.py @@ -0,0 +1,91 @@ +"""QGIS Unit tests for QgsOrientedBoundingBox + +From build dir, run: ctest -R QgsOrientedBoundingBox -V + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" +__author__ = '(C) 2023 by Nyall Dawson' +__date__ = '10/07/2023' +__copyright__ = 'Copyright 2023, The QGIS Project' + +import math +import qgis # NOQA +from qgis.core import ( + QgsOrientedBoundingBox +) +import unittest +from qgis.testing import start_app, QgisTestCase + +from utilities import unitTestDataPath + +start_app() +TEST_DATA_DIR = unitTestDataPath() + + +class TestQgsOrientedBoundingBox(QgisTestCase): + + def test_oriented_bounding_box(self): + box = QgsOrientedBoundingBox() + self.assertTrue(box.isNull()) + + # valid + box = QgsOrientedBoundingBox([1, 2, 3], [10, 0, 0, 0, 20, 0, 0, 0, 30]) + self.assertEqual(box.centerX(), 1) + self.assertEqual(box.centerY(), 2) + self.assertEqual(box.centerZ(), 3) + self.assertEqual(box.halfAxes(), [10.0, 0.0, 0.0, 0.0, 20.0, 0.0, 0.0, 0.0, 30.0]) + + box = QgsOrientedBoundingBox([1, 2, 3], [1, 0, 0, 0, 1, 0, 0, 0, 1]) + self.assertEqual(box.centerX(), 1) + self.assertEqual(box.centerY(), 2) + self.assertEqual(box.centerZ(), 3) + self.assertEqual(box.halfAxes(), [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]) + + # 45 degree y axis rotation + box = QgsOrientedBoundingBox([1, 2, 3], + [math.cos(math.pi / 4), 0, math.sin(math.pi / 4), + 0, 1, 0, + -math.sin(math.pi / 4), 0, math.cos(math.pi / 4)]) + self.assertEqual(box.centerX(), 1) + self.assertEqual(box.centerY(), 2) + self.assertEqual(box.centerZ(), 3) + self.assertEqual(box.halfAxes(), [0.7071067811865476, 0.0, 0.7071067811865475, 0.0, 1.0, 0.0, -0.7071067811865475, 0.0, 0.7071067811865476]) + + def test_box_extent(self): + box = QgsOrientedBoundingBox([1, 2, 3], [10, 0, 0, 0, 20, 0, 0, 0, 30]) + bounds = box.extent() + self.assertEqual(bounds.xMinimum(), -9) + self.assertEqual(bounds.xMaximum(), 11) + self.assertEqual(bounds.yMinimum(), -18) + self.assertEqual(bounds.yMaximum(), 22) + self.assertEqual(bounds.zMinimum(), -27) + self.assertEqual(bounds.zMaximum(), 33) + + box = QgsOrientedBoundingBox([1, 2, 3], [1, 0, 0, 0, 1, 0, 0, 0, 1]) + bounds = box.extent() + self.assertEqual(bounds.xMinimum(), 0) + self.assertEqual(bounds.xMaximum(), 2) + self.assertEqual(bounds.yMinimum(), 1) + self.assertEqual(bounds.yMaximum(), 3) + self.assertEqual(bounds.zMinimum(), 2) + self.assertEqual(bounds.zMaximum(), 4) + + # 45 degree y axis rotation + box = QgsOrientedBoundingBox([1, 2, 3], + [math.cos(math.pi / 4), 0, math.sin(math.pi / 4), + 0, 1, 0, + -math.sin(math.pi / 4), 0, math.cos(math.pi / 4)]) + bounds = box.extent() + self.assertAlmostEqual(bounds.xMinimum(), 1 - math.sqrt(2), 5) + self.assertAlmostEqual(bounds.xMaximum(), 1 + math.sqrt(2), 5) + self.assertAlmostEqual(bounds.yMinimum(), 1, 5) + self.assertAlmostEqual(bounds.yMaximum(), 3, 5) + self.assertAlmostEqual(bounds.zMinimum(), 3 - math.sqrt(2), 5) + self.assertAlmostEqual(bounds.zMaximum(), 3 + math.sqrt(2), 5) + + +if __name__ == '__main__': + unittest.main()