Add new class QgsSphere for representing 3d spheres

This commit is contained in:
Nyall Dawson 2023-07-14 11:49:58 +10:00
parent 015fff4d39
commit 5ab122bc62
7 changed files with 563 additions and 0 deletions

View File

@ -0,0 +1,182 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/geometry/qgssphere.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsSphere
{
%Docstring(signature="appended")
A spherical geometry object.
Represents a simple 3-dimensional sphere.
.. versionadded:: 3.34
%End
%TypeHeaderCode
#include "qgssphere.h"
%End
public:
QgsSphere() /HoldGIL/;
%Docstring
Constructor for an invalid QgsSphere.
%End
QgsSphere( double x, double y, double z, double radius ) /HoldGIL/;
%Docstring
Constructor for QgsSphere with the specified center (``x``, ``y``, ``z``) and ``radius``.
%End
bool operator==( const QgsSphere &other ) const;
bool operator!=( const QgsSphere &other ) const;
bool isNull() const /HoldGIL/;
%Docstring
Returns ``True`` if the sphere is a null (default constructed) sphere.
%End
bool isEmpty() const /HoldGIL/;
%Docstring
Returns ``True`` if the sphere is considered empty, i.e. it has a radius of 0.
%End
QgsPoint center() const /HoldGIL/;
%Docstring
Returns the center point of the sphere.
.. seealso:: :py:func:`centerX`
.. seealso:: :py:func:`centerY`
.. seealso:: :py:func:`centerZ`
.. seealso:: :py:func:`setCenter`
%End
double centerX() const;
%Docstring
Returns the x-coordinate of the center of the sphere.
.. seealso:: :py:func:`center`
.. seealso:: :py:func:`centerY`
.. seealso:: :py:func:`centerZ`
.. seealso:: :py:func:`setCenter`
%End
double centerY() const;
%Docstring
Returns the y-coordinate of the center of the sphere.
.. seealso:: :py:func:`center`
.. seealso:: :py:func:`centerX`
.. seealso:: :py:func:`centerZ`
.. seealso:: :py:func:`setCenter`
%End
double centerZ() const;
%Docstring
Returns the z-coordinate of the center of the sphere.
.. seealso:: :py:func:`center`
.. seealso:: :py:func:`centerX`
.. seealso:: :py:func:`centerY`
.. seealso:: :py:func:`setCenter`
%End
void setCenter( const QgsPoint &center ) /HoldGIL/;
%Docstring
Sets the center point of the sphere.
.. seealso:: :py:func:`center`
%End
void setCenter( double x, double y, double z ) /HoldGIL/;
%Docstring
Sets the center point of the sphere to (``x``, ``y``, ``z``).
.. seealso:: :py:func:`center`
%End
double radius() const /HoldGIL/;
%Docstring
Returns the radius of the sphere.
.. seealso:: :py:func:`setRadius`
.. seealso:: :py:func:`diameter`
%End
void setRadius( double radius ) /HoldGIL/;
%Docstring
Sets the ``radius`` of the sphere.
.. seealso:: :py:func:`radius`
%End
double diameter() const /HoldGIL/;
%Docstring
Returns the diameter of the sphere.
.. seealso:: :py:func:`radius`
%End
double volume() const /HoldGIL/;
%Docstring
Returns the volume of the sphere.
%End
double surfaceArea() const /HoldGIL/;
%Docstring
Returns the surface area of the sphere.
%End
QgsCircle toCircle() const /HoldGIL/;
%Docstring
Converts the sphere to a 2-dimensional circle.
%End
QgsBox3d boundingBox() const /HoldGIL/;
%Docstring
Returns the 3-dimensional bounding box containing the sphere.
%End
SIP_PYOBJECT __repr__();
%MethodCode
QString str;
if ( sipCpp->isNull() )
{
str = QStringLiteral( "<QgsSphere: null>" ).arg( sipCpp->centerX() ).arg( sipCpp->centerY() ).arg( sipCpp->centerZ() ).arg( sipCpp->radius() );
}
else
{
str = QStringLiteral( "<QgsSphere: (%1, %2, %3) radius %4>" ).arg( sipCpp->centerX() ).arg( sipCpp->centerY() ).arg( sipCpp->centerZ() ).arg( sipCpp->radius() );
}
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/geometry/qgssphere.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -351,6 +351,7 @@
%Include auto_generated/geometry/qgsrectangle.sip
%Include auto_generated/geometry/qgsreferencedgeometry.sip
%Include auto_generated/geometry/qgsregularpolygon.sip
%Include auto_generated/geometry/qgssphere.sip
%Include auto_generated/geometry/qgssurface.sip
%Include auto_generated/geometry/qgstriangle.sip
%Include auto_generated/geometry/qgsvertexid.sip

View File

@ -853,6 +853,7 @@ set(QGIS_CORE_SRCS
geometry/qgsrectangle.cpp
geometry/qgsreferencedgeometry.cpp
geometry/qgsregularpolygon.cpp
geometry/qgssphere.cpp
geometry/qgssurface.cpp
geometry/qgstriangle.cpp
geometry/qgsvertexid.cpp
@ -1436,6 +1437,7 @@ set(QGIS_CORE_HDRS
geometry/qgsrectangle.h
geometry/qgsreferencedgeometry.h
geometry/qgsregularpolygon.h
geometry/qgssphere.h
geometry/qgssurface.h
geometry/qgstriangle.h
geometry/qgsvertexid.h

View File

@ -0,0 +1,80 @@
/***************************************************************************
qgssphere.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 "qgssphere.h"
#include "qgspoint.h"
#include "qgscircle.h"
#include "qgsbox3d.h"
QgsSphere::QgsSphere( double x, double y, double z, double radius )
: mCenterX( x )
, mCenterY( y )
, mCenterZ( z )
, mRadius( radius )
{
}
bool QgsSphere::isNull() const
{
return std::isnan( mCenterX ) || std::isnan( mCenterY ) || std::isnan( mCenterZ );
}
bool QgsSphere::isEmpty() const
{
return qgsDoubleNear( mRadius, 0 );
}
QgsPoint QgsSphere::center() const
{
return QgsPoint( mCenterX, mCenterY, mCenterZ );
}
void QgsSphere::setCenter( const QgsPoint &center )
{
mCenterX = center.x();
mCenterY = center.y();
mCenterZ = center.z();
}
double QgsSphere::volume() const
{
return 4.0 / 3.0 * M_PI * std::pow( mRadius, 3 );
}
double QgsSphere::surfaceArea() const
{
return 4.0 * M_PI * std::pow( mRadius, 2 );
}
QgsCircle QgsSphere::toCircle() const
{
if ( isNull() )
return QgsCircle();
return QgsCircle( QgsPoint( mCenterX, mCenterY ), mRadius );
}
QgsBox3d QgsSphere::boundingBox() const
{
if ( isNull() )
return QgsBox3d();
return QgsBox3d( mCenterX - mRadius, mCenterY - mRadius, mCenterZ - mRadius,
mCenterX + mRadius, mCenterY + mRadius, mCenterZ + mRadius );
}

View File

@ -0,0 +1,188 @@
/***************************************************************************
qgssphere.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 QGSSPHERE_H
#define QGSSPHERE_H
#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgis.h"
#include <limits>
class QgsPoint;
class QgsCircle;
class QgsBox3d;
/**
* \ingroup core
* \class QgsSphere
* \brief A spherical geometry object.
*
* Represents a simple 3-dimensional sphere.
*
* \since QGIS 3.34
*/
class CORE_EXPORT QgsSphere
{
public:
/**
* Constructor for an invalid QgsSphere.
*/
QgsSphere() SIP_HOLDGIL = default;
/**
* Constructor for QgsSphere with the specified center (\a x, \a y, \a z) and \a radius.
*/
QgsSphere( double x, double y, double z, double radius ) SIP_HOLDGIL;
bool operator==( const QgsSphere &other ) const
{
return qgsDoubleNear( mCenterX, other.mCenterX ) && qgsDoubleNear( mCenterY, other.mCenterY ) && qgsDoubleNear( mCenterZ, other.mCenterZ ) && qgsDoubleNear( mRadius, other.mRadius );
}
bool operator!=( const QgsSphere &other ) const { return !( *this == other ); }
/**
* Returns TRUE if the sphere is a null (default constructed) sphere.
*/
bool isNull() const SIP_HOLDGIL;
/**
* Returns TRUE if the sphere is considered empty, i.e. it has a radius of 0.
*/
bool isEmpty() const SIP_HOLDGIL;
/**
* Returns the center point of the sphere.
*
* \see centerX()
* \see centerY()
* \see centerZ()
* \see setCenter()
*/
QgsPoint center() const SIP_HOLDGIL;
/**
* Returns the x-coordinate of the center of the sphere.
*
* \see center()
* \see centerY()
* \see centerZ()
* \see setCenter()
*/
double centerX() const { return mCenterX; }
/**
* Returns the y-coordinate of the center of the sphere.
*
* \see center()
* \see centerX()
* \see centerZ()
* \see setCenter()
*/
double centerY() const { return mCenterY; }
/**
* Returns the z-coordinate of the center of the sphere.
*
* \see center()
* \see centerX()
* \see centerY()
* \see setCenter()
*/
double centerZ() const { return mCenterZ; }
/**
* Sets the center point of the sphere.
* \see center()
*/
void setCenter( const QgsPoint &center ) SIP_HOLDGIL;
/**
* Sets the center point of the sphere to (\a x, \a y, \a z).
* \see center()
*/
void setCenter( double x, double y, double z ) SIP_HOLDGIL { mCenterX = x; mCenterY = y; mCenterZ = z; }
/**
* Returns the radius of the sphere.
*
* \see setRadius()
* \see diameter()
*/
double radius() const SIP_HOLDGIL { return mRadius; }
/**
* Sets the \a radius of the sphere.
*
* \see radius()
*/
void setRadius( double radius ) SIP_HOLDGIL{ mRadius = radius; }
/**
* Returns the diameter of the sphere.
*
* \see radius()
*/
double diameter() const SIP_HOLDGIL { return mRadius * 2; }
/**
* Returns the volume of the sphere.
*/
double volume() const SIP_HOLDGIL;
/**
* Returns the surface area of the sphere.
*/
double surfaceArea() const SIP_HOLDGIL;
/**
* Converts the sphere to a 2-dimensional circle.
*/
QgsCircle toCircle() const SIP_HOLDGIL;
/**
* Returns the 3-dimensional bounding box containing the sphere.
*/
QgsBox3d boundingBox() const SIP_HOLDGIL;
#ifdef SIP_RUN
SIP_PYOBJECT __repr__();
% MethodCode
QString str;
if ( sipCpp->isNull() )
{
str = QStringLiteral( "<QgsSphere: null>" ).arg( sipCpp->centerX() ).arg( sipCpp->centerY() ).arg( sipCpp->centerZ() ).arg( sipCpp->radius() );
}
else
{
str = QStringLiteral( "<QgsSphere: (%1, %2, %3) radius %4>" ).arg( sipCpp->centerX() ).arg( sipCpp->centerY() ).arg( sipCpp->centerZ() ).arg( sipCpp->radius() );
}
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
% End
#endif
private:
double mCenterX = std::numeric_limits< double >::quiet_NaN();
double mCenterY = std::numeric_limits< double >::quiet_NaN();
double mCenterZ = std::numeric_limits< double >::quiet_NaN();
double mRadius = 1;
};
#endif // QGSSPHERE_H

View File

@ -347,6 +347,7 @@ ADD_PYTHON_TEST(PyQgsScaleCalculator test_qgsscalecalculator.py)
ADD_PYTHON_TEST(PyQgsScaleWidget test_qgsscalewidget.py)
ADD_PYTHON_TEST(PyQgsSingleSymbolRenderer test_qgssinglesymbolrenderer.py)
ADD_PYTHON_TEST(PyQgsShapefileProvider test_provider_shapefile.py)
ADD_PYTHON_TEST(PyQgsSphere test_qgssphere.py)
ADD_PYTHON_TEST(PyQgsSvgCache test_qgssvgcache.py)
ADD_PYTHON_TEST(PyQgsSymbolButton test_qgssymbolbutton.py)
ADD_PYTHON_TEST(PyQgsSymbolLayerRegistry test_qgssymbollayerregistry.py)

View File

@ -0,0 +1,109 @@
"""QGIS Unit tests for QgsSphere
From build dir, run: ctest -R QgsSphere -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__ = '14/07/2023'
__copyright__ = 'Copyright 2023, The QGIS Project'
import math
import qgis # NOQA
from qgis.core import (
QgsSphere,
QgsPoint,
QgsCircle,
QgsBox3d
)
import unittest
from qgis.testing import start_app, QgisTestCase
from utilities import unitTestDataPath
start_app()
TEST_DATA_DIR = unitTestDataPath()
class TestQgsSphere(QgisTestCase):
def test_null(self):
sphere = QgsSphere()
self.assertTrue(sphere.isNull())
self.assertEqual(str(sphere), '<QgsSphere: null>')
def test_sphere(self):
sphere = QgsSphere(1, 2, 3, 4)
self.assertFalse(sphere.isNull())
self.assertEqual(sphere.centerX(), 1)
self.assertEqual(sphere.centerY(), 2)
self.assertEqual(sphere.centerZ(), 3)
self.assertEqual(sphere.center(), QgsPoint(1, 2, 3))
self.assertEqual(sphere.radius(), 4)
self.assertEqual(str(sphere), '<QgsSphere: (1, 2, 3) radius 4>')
def test_setters(self):
sphere = QgsSphere(1, 2, 3, 4)
sphere.setCenter(11, 12, 13)
self.assertEqual(sphere.centerX(), 11)
self.assertEqual(sphere.centerY(), 12)
self.assertEqual(sphere.centerZ(), 13)
self.assertEqual(sphere.radius(), 4)
sphere.setCenter(QgsPoint(21, 22, 23))
self.assertEqual(sphere.centerX(), 21)
self.assertEqual(sphere.centerY(), 22)
self.assertEqual(sphere.centerZ(), 23)
self.assertEqual(sphere.radius(), 4)
sphere.setRadius(5)
self.assertEqual(sphere.centerX(), 21)
self.assertEqual(sphere.centerY(), 22)
self.assertEqual(sphere.centerZ(), 23)
self.assertEqual(sphere.radius(), 5)
def test_empty(self):
sphere = QgsSphere(1, 2, 3, 4)
self.assertFalse(sphere.isEmpty())
sphere = QgsSphere(1, 2, 3, 0)
self.assertTrue(sphere.isEmpty())
def test_equality(self):
self.assertEqual(QgsSphere(), QgsSphere())
self.assertNotEqual(QgsSphere(1, 2, 3, 4), QgsSphere())
self.assertNotEqual(QgsSphere(), QgsSphere(1, 2, 3, 4))
self.assertEqual(QgsSphere(1, 2, 3, 4), QgsSphere(1, 2, 3, 4))
self.assertNotEqual(QgsSphere(1, 2, 3, 4), QgsSphere(11, 2, 3, 4))
self.assertNotEqual(QgsSphere(1, 2, 3, 4), QgsSphere(1, 12, 3, 4))
self.assertNotEqual(QgsSphere(1, 2, 3, 4), QgsSphere(1, 2, 13, 4))
self.assertNotEqual(QgsSphere(1, 2, 3, 4), QgsSphere(1, 2, 3, 14))
def test_volume(self):
self.assertEqual(QgsSphere(1, 1, 1, 3).volume(), 113.09733552923254)
self.assertEqual(QgsSphere(1, 1, 1, 0).volume(), 0)
def test_surface_area(self):
self.assertEqual(QgsSphere(1, 1, 1, 7).surfaceArea(), 615.7521601035994)
self.assertEqual(QgsSphere(1, 1, 1, 0).surfaceArea(), 0)
def test_to_circle(self):
circle = QgsSphere().toCircle()
self.assertEqual(circle, QgsCircle())
circle = QgsSphere(1, 2, 3, 4).toCircle()
self.assertEqual(circle, QgsCircle(QgsPoint(1, 2), 4))
def test_bounding_box(self):
box = QgsSphere().boundingBox()
self.assertTrue(box.isNull())
box = QgsSphere(1, 2, 3, 4).boundingBox()
self.assertEqual(box.xMinimum(), -3)
self.assertEqual(box.yMinimum(), -2)
self.assertEqual(box.zMinimum(), -1)
self.assertEqual(box.xMaximum(), 5)
self.assertEqual(box.yMaximum(), 6)
self.assertEqual(box.zMaximum(), 7)
if __name__ == '__main__':
unittest.main()