Skip to content

Commit

Permalink
More intuitive QgsPoint python constructors
Browse files Browse the repository at this point in the history
In python, the wkb type of a QgsPoint will by default be determined from
the provided parameters, where Z and M will be added as required if the
wkbType is Undefined.

    QgsPoint(x, y, z=nan, m=nan, wkbType=QgsWkbTypes.Undefined)

Thanks to the python API support of named parameters, it's also
straightforward to specify z, m and wkbType in any desired combination.

On the other hand, on C++ side it's often preferable to use

    QgsPoint(QgsWkbTypes::WkbType wkbType, double x, double y, double z, double m);

due to the lack of named parameters which make it harder to specify a
specific type and the advantage of typesafety that makes it possible to
verload the first constructor with this one.
  • Loading branch information
m-kuhn committed Jun 14, 2017
1 parent 4db671e commit 1808dc9
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 75 deletions.
48 changes: 23 additions & 25 deletions python/core/geometry/qgspoint.sip
Expand Up @@ -22,12 +22,31 @@ class QgsPoint: QgsAbstractGeometry
%End
public:

QgsPoint( double x = 0.0, double y = 0.0, SIP_PYOBJECT z = Py_None, SIP_PYOBJECT m = Py_None ) [( double x = 0.0, double y = 0.0, double z = 0.0, double m = 0.0 )];
QgsPoint( double x = 0.0, double y = 0.0, SIP_PYOBJECT z = Py_None, SIP_PYOBJECT m = Py_None, QgsWkbTypes::Type wkbType = QgsWkbTypes::Unknown ) [( double x = 0.0, double y = 0.0, double z = 0.0, double m = 0.0, QgsWkbTypes::Type wkbType = QgsWkbTypes::Unknown )];
%Docstring
Construct a point with the provided initial coordinate values.

If only z and m are not specified, the type will be a 2D point.
If any or both of the others are specified, the Z and M values will be added accordingly.
If ``wkbType`` is set to `QgsWkbTypes.Point`, `QgsWkbTypes.PointZ`, `QgsWkbTypes.PointM` or `QgsWkbTypes.PointZM`
the type will be set accordingly. If it is left to the default `QgsWkbTypes.Unknown`, the type will be set
based on the following rules:
- If only x and y are specified, the type will be a 2D point.
- If any or both of the Z and M are specified, the appropriate type will be created.

\code{.py}
pt = QgsPoint(43.4, 5.3)
pt.exportToWkt() # Point(43.4 5.3)

pt_z = QgsPoint(120, 343, 77)
pt.exportToWkt() # PointZ(120 343 77)

pt_m = QgsPoint(33, 88, m=5)
pt_m.m() # 5
pt_m.wkbType() # QgsWkbTypes.PointM

pt = QgsPoint(30, 40, wkbType=QgsWkbTypes.PointZ)
pt.z() # nan
pt.wkbType() # QgsWkbTypes.PointZ
\endcode
%End
%MethodCode
double z;
Expand All @@ -51,7 +70,7 @@ class QgsPoint: QgsAbstractGeometry
m = PyFloat_AsDouble( a3 );
}

sipCpp = new sipQgsPoint( a0, a1, z, m );
sipCpp = new sipQgsPoint( a0, a1, z, m, a4 );
%End

explicit QgsPoint( const QgsPointXY &p );
Expand All @@ -64,27 +83,6 @@ class QgsPoint: QgsAbstractGeometry
Construct a QgsPoint from a QPointF
%End

QgsPoint( QgsWkbTypes::Type type, double x = 0.0, double y = 0.0, double z = 0.0, double m = 0.0 );
%Docstring
Construct a point with a specified type (e.g., PointZ, PointM) and initial x, y, z, and m values.
\param type point type
\param x x-coordinate of point
\param y y-coordinate of point
\param z z-coordinate of point, for PointZ or PointZM types
\param m m-value of point, for PointM or PointZM types
%End
%MethodCode
if ( QgsWkbTypes::flatType( a0 ) != QgsWkbTypes::Point )
{
PyErr_SetString( PyExc_ValueError,
QString( "%1 is not a valid WKB type for point geometries" ).arg( QgsWkbTypes::displayString( a0 ) ).toUtf8().constData() );
sipIsErr = 1;
}
else
{
sipCpp = new sipQgsPoint( a0, a1, a2, a3, a4 );
}
%End

bool operator==( const QgsPoint &pt ) const;
bool operator!=( const QgsPoint &pt ) const;
Expand Down
21 changes: 13 additions & 8 deletions src/core/geometry/qgspoint.cpp
Expand Up @@ -31,14 +31,19 @@
* See details in QEP #17
****************************************************************************/

QgsPoint::QgsPoint( double x, double y, double z, double m )
QgsPoint::QgsPoint( double x, double y, double z, double m, QgsWkbTypes::Type wkbType )
: QgsAbstractGeometry()
, mX( x )
, mY( y )
, mZ( z )
, mM( m )
{
if ( qIsNaN( z ) )
if ( wkbType != QgsWkbTypes::Unknown )
{
Q_ASSERT( QgsWkbTypes::flatType( wkbType ) == QgsWkbTypes::Point );
mWkbType = wkbType;
}
else if ( qIsNaN( z ) )
{
if ( qIsNaN( m ) )
mWkbType = QgsWkbTypes::Point;
Expand Down Expand Up @@ -71,15 +76,15 @@ QgsPoint::QgsPoint( QPointF p )
mWkbType = QgsWkbTypes::Point;
}

QgsPoint::QgsPoint( QgsWkbTypes::Type type, double x, double y, double z, double m )
: mX( x )
QgsPoint::QgsPoint( QgsWkbTypes::Type wkbType, double x, double y, double z, double m )
: QgsAbstractGeometry()
, mX( x )
, mY( y )
, mZ( z )
, mM( m )
{
//protect against non-point WKB types
Q_ASSERT( QgsWkbTypes::flatType( type ) == QgsWkbTypes::Point );
mWkbType = type;
Q_ASSERT( QgsWkbTypes::flatType( wkbType ) == QgsWkbTypes::Point );
mWkbType = wkbType;
}

/***************************************************************************
Expand Down Expand Up @@ -535,5 +540,5 @@ QgsPoint QgsPoint::project( double distance, double azimuth, double inclination
pType = QgsWkbTypes::addM( pType );
}

return QgsPoint( pType, mX + dx, mY + dy, mZ + dz, mM );
return QgsPoint( mX + dx, mY + dy, mZ + dz, mM, pType );
}
55 changes: 29 additions & 26 deletions src/core/geometry/qgspoint.h
Expand Up @@ -47,13 +47,32 @@ class CORE_EXPORT QgsPoint: public QgsAbstractGeometry
/**
* Construct a point with the provided initial coordinate values.
*
* If only z and m are not specified, the type will be a 2D point.
* If any or both of the others are specified, the Z and M values will be added accordingly.
* If \a wkbType is set to `QgsWkbTypes::Point`, `QgsWkbTypes::PointZ`, `QgsWkbTypes::PointM` or `QgsWkbTypes::PointZM`
* the type will be set accordingly. If it is left to the default `QgsWkbTypes::Unknown`, the type will be set
* based on the following rules:
* - If only x and y are specified, the type will be a 2D point.
* - If any or both of the Z and M are specified, the appropriate type will be created.
*
* \code{.py}
* pt = QgsPoint(43.4, 5.3)
* pt.exportToWkt() # Point(43.4 5.3)
*
* pt_z = QgsPoint(120, 343, 77)
* pt.exportToWkt() # PointZ(120 343 77)
*
* pt_m = QgsPoint(33, 88, m=5)
* pt_m.m() # 5
* pt_m.wkbType() # QgsWkbTypes.PointM
*
* pt = QgsPoint(30, 40, wkbType=QgsWkbTypes.PointZ)
* pt.z() # nan
* pt.wkbType() # QgsWkbTypes.PointZ
* \endcode
*/
#ifndef SIP_RUN
QgsPoint( double x = 0.0, double y = 0.0, double z = std::numeric_limits<double>::quiet_NaN(), double m = std::numeric_limits<double>::quiet_NaN() ) SIP_SKIP;
QgsPoint( double x = 0.0, double y = 0.0, double z = std::numeric_limits<double>::quiet_NaN(), double m = std::numeric_limits<double>::quiet_NaN(), QgsWkbTypes::Type wkbType = QgsWkbTypes::Unknown ) SIP_SKIP;
#else
QgsPoint( double x = 0.0, double y = 0.0, SIP_PYOBJECT z = Py_None, SIP_PYOBJECT m = Py_None ) [( double x = 0.0, double y = 0.0, double z = 0.0, double m = 0.0 )];
QgsPoint( double x = 0.0, double y = 0.0, SIP_PYOBJECT z = Py_None, SIP_PYOBJECT m = Py_None, QgsWkbTypes::Type wkbType = QgsWkbTypes::Unknown ) [( double x = 0.0, double y = 0.0, double z = 0.0, double m = 0.0, QgsWkbTypes::Type wkbType = QgsWkbTypes::Unknown )];
% MethodCode
double z;
double m;
Expand All @@ -76,7 +95,7 @@ class CORE_EXPORT QgsPoint: public QgsAbstractGeometry
m = PyFloat_AsDouble( a3 );
}

sipCpp = new sipQgsPoint( a0, a1, z, m );
sipCpp = new sipQgsPoint( a0, a1, z, m, a4 );
% End
#endif

Expand All @@ -88,28 +107,12 @@ class CORE_EXPORT QgsPoint: public QgsAbstractGeometry
*/
explicit QgsPoint( QPointF p );

/** Construct a point with a specified type (e.g., PointZ, PointM) and initial x, y, z, and m values.
* \param type point type
* \param x x-coordinate of point
* \param y y-coordinate of point
* \param z z-coordinate of point, for PointZ or PointZM types
* \param m m-value of point, for PointM or PointZM types
/**
* Create a new point with the given wkbtype and values.
*
* \note Not available in Python bindings
*/
QgsPoint( QgsWkbTypes::Type type, double x = 0.0, double y = 0.0, double z = 0.0, double m = 0.0 );
#ifdef SIP_RUN
% MethodCode
if ( QgsWkbTypes::flatType( a0 ) != QgsWkbTypes::Point )
{
PyErr_SetString( PyExc_ValueError,
QString( "%1 is not a valid WKB type for point geometries" ).arg( QgsWkbTypes::displayString( a0 ) ).toUtf8().constData() );
sipIsErr = 1;
}
else
{
sipCpp = new sipQgsPoint( a0, a1, a2, a3, a4 );
}
% End
#endif
explicit QgsPoint( QgsWkbTypes::Type wkbType, double x, double y, double z = std::numeric_limits<double>::quiet_NaN(), double m = std::numeric_limits<double>::quiet_NaN() ) SIP_SKIP;

bool operator==( const QgsPoint &pt ) const;
bool operator!=( const QgsPoint &pt ) const;
Expand Down
18 changes: 9 additions & 9 deletions tests/src/python/test_qgsbox3d.py
Expand Up @@ -35,7 +35,7 @@ def testCtor(self):
self.assertEqual(box.yMaximum(), 11.0)
self.assertEqual(box.zMaximum(), 12.0)

box = QgsBox3d(QgsPoint(QgsWkbTypes.PointZ, 5, 6, 7), QgsPoint(QgsWkbTypes.PointZ, 10, 11, 12))
box = QgsBox3d(QgsPoint(5, 6, 7), QgsPoint(10, 11, 12))
self.assertEqual(box.xMinimum(), 5.0)
self.assertEqual(box.yMinimum(), 6.0)
self.assertEqual(box.zMinimum(), 7.0)
Expand All @@ -44,7 +44,7 @@ def testCtor(self):
self.assertEqual(box.zMaximum(), 12.0)

# point constructor should normalize
box = QgsBox3d(QgsPoint(QgsWkbTypes.PointZ, 10, 11, 12), QgsPoint(QgsWkbTypes.PointZ, 5, 6, 7))
box = QgsBox3d(QgsPoint(10, 11, 12), QgsPoint(5, 6, 7))
self.assertEqual(box.xMinimum(), 5.0)
self.assertEqual(box.yMinimum(), 6.0)
self.assertEqual(box.zMinimum(), 7.0)
Expand Down Expand Up @@ -137,14 +137,14 @@ def testContains(self):

def testContainsPoint(self):
box = QgsBox3d(5.0, 6.0, 7.0, 11.0, 13.0, 15.0)
self.assertTrue(box.contains(QgsPoint(QgsWkbTypes.PointZ, 6, 7, 8)))
self.assertFalse(box.contains(QgsPoint(QgsWkbTypes.PointZ, 16, 7, 8)))
self.assertFalse(box.contains(QgsPoint(QgsWkbTypes.PointZ, 6, 17, 8)))
self.assertFalse(box.contains(QgsPoint(QgsWkbTypes.PointZ, 6, 7, 18)))
self.assertTrue(box.contains(QgsPoint(6, 7, 8)))
self.assertFalse(box.contains(QgsPoint(16, 7, 8)))
self.assertFalse(box.contains(QgsPoint(6, 17, 8)))
self.assertFalse(box.contains(QgsPoint(6, 7, 18)))
# 2d containment
self.assertTrue(box.contains(QgsPoint(QgsWkbTypes.Point, 6, 7)))
self.assertFalse(box.contains(QgsPoint(QgsWkbTypes.Point, 16, 7)))
self.assertFalse(box.contains(QgsPoint(QgsWkbTypes.Point, 6, 17)))
self.assertTrue(box.contains(QgsPoint(6, 7)))
self.assertFalse(box.contains(QgsPoint(16, 7)))
self.assertFalse(box.contains(QgsPoint(6, 17)))

def testVolume(self):
box = QgsBox3d(5.0, 6.0, 7.0, 11.0, 13.0, 15.0)
Expand Down
44 changes: 37 additions & 7 deletions tests/src/python/test_qgsgeometry.py
Expand Up @@ -1409,7 +1409,7 @@ def testAddPart(self):
# test adding a part with Z values
point = QgsGeometry.fromPoint(points[0])
point.geometry().addZValue(4.0)
self.assertEqual(point.addPointsV2([QgsPoint(QgsWkbTypes.PointZ, points[1][0], points[1][1], 3.0)]), 0)
self.assertEqual(point.addPointsV2([QgsPoint(points[1][0], points[1][1], 3.0, wkbType=QgsWkbTypes.PointZ)]), 0)
expwkt = "MultiPointZ ((0 0 4), (1 0 3))"
wkt = point.exportToWkt()
assert compareWkt(expwkt, wkt), "Expected:\n%s\nGot:\n%s\n" % (expwkt, wkt)
Expand Down Expand Up @@ -1438,7 +1438,7 @@ def testAddPart(self):
# test adding a part with Z values
polyline = QgsGeometry.fromPolyline(points[0])
polyline.geometry().addZValue(4.0)
points2 = [QgsPoint(QgsWkbTypes.PointZ, p[0], p[1], 3.0) for p in points[1]]
points2 = [QgsPoint(p[0], p[1], 3.0, wkbType=QgsWkbTypes.PointZ) for p in points[1]]
self.assertEqual(polyline.addPointsV2(points2), 0)
expwkt = "MultiLineStringZ ((0 0 4, 1 0 4, 1 1 4, 2 1 4, 2 0 4),(3 0 3, 3 1 3, 5 1 3, 5 0 3, 6 0 3))"
wkt = polyline.exportToWkt()
Expand Down Expand Up @@ -1482,7 +1482,7 @@ def testAddPart(self):
# test adding a part with Z values
polygon = QgsGeometry.fromPolygon(points[0])
polygon.geometry().addZValue(4.0)
points2 = [QgsPoint(QgsWkbTypes.PointZ, pi[0], pi[1], 3.0) for pi in points[1][0]]
points2 = [QgsPoint(pi[0], pi[1], 3.0, wkbType=QgsWkbTypes.PointZ) for pi in points[1][0]]
self.assertEqual(polygon.addPointsV2(points2), 0)
expwkt = "MultiPolygonZ (((0 0 4, 1 0 4, 1 1 4, 2 1 4, 2 2 4, 0 2 4, 0 0 4)),((4 0 3, 5 0 3, 5 2 3, 3 2 3, 3 1 3, 4 1 3, 4 0 3)))"
wkt = polygon.exportToWkt()
Expand Down Expand Up @@ -4130,10 +4130,40 @@ def testCompare(self):
self.assertTrue(QgsGeometry.compare(lp, lp2, 1e-6))

def testPoint(self):
self.assertEqual(QgsPoint(1, 2).wkbType(), QgsWkbTypes.Point)
self.assertEqual(QgsPoint(1, 2, 3).wkbType(), QgsWkbTypes.PointZ)
self.assertEqual(QgsPoint(1, 2, m=3).wkbType(), QgsWkbTypes.PointM)
self.assertEqual(QgsPoint(1, 2, 3, 4).wkbType(), QgsWkbTypes.PointZM)
point = QgsPoint(1, 2)
self.assertEqual(point.wkbType(), QgsWkbTypes.Point)
self.assertEqual(point.x(), 1)
self.assertEqual(point.y(), 2)

point = QgsPoint(1, 2, wkbType=QgsWkbTypes.Point)
self.assertEqual(point.wkbType(), QgsWkbTypes.Point)
self.assertEqual(point.x(), 1)
self.assertEqual(point.y(), 2)

point_z = QgsPoint(1, 2, 3)
self.assertEqual(point_z.wkbType(), QgsWkbTypes.PointZ)
self.assertEqual(point.x(), 1)
self.assertEqual(point.y(), 2)
self.assertEqual(point.z(), 3)

point_z = QgsPoint(1, 2, 3, 4, wkbType=QgsWkbTypes.PointZ)
self.assertEqual(point_z.wkbType(), QgsWkbTypes.PointZ)
self.assertEqual(point.x(), 1)
self.assertEqual(point.y(), 2)
self.assertEqual(point.z(), 3)

point_m = QgsPoint(1, 2, m=3)
self.assertEqual(point_m.wkbType(), QgsWkbTypes.PointM)
self.assertEqual(point.x(), 1)
self.assertEqual(point.y(), 2)
self.assertEqual(point.m(), 3)

point_zm = QgsPoint(1, 2, 3, 4)
self.assertEqual(point_zm.wkbType(), QgsWkbTypes.PointZM)
self.assertEqual(point.x(), 1)
self.assertEqual(point.y(), 2)
self.assertEqual(point.z(), 3)
self.assertEqual(point.m(), 4)


if __name__ == '__main__':
Expand Down

0 comments on commit 1808dc9

Please sign in to comment.