Skip to content

Commit 548461a

Browse files
lbartolettinyalldawson
authored andcommitted
[FEATURE] QgsPointV2 add project with 3D support
Allows projection of a point with inclination to return a 3d point. Expression "project" function has been extended to support a new inclination parameter.
1 parent 290758a commit 548461a

File tree

6 files changed

+151
-6
lines changed

6 files changed

+151
-6
lines changed

python/core/geometry/qgspointv2.sip

+29
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,35 @@ class QgsPointV2: public QgsAbstractGeometry
179179
*/
180180
double azimuth( const QgsPointV2& other ) const;
181181

182+
/** Returns a new point which correspond to this point projected by a specified distance
183+
* with specified angles (azimuth and inclination).
184+
* M value is preserved.
185+
* @param distance distance to project
186+
* @param azimuth angle to project in X Y, clockwise in degrees starting from north
187+
* @param inclination angle to project in Z (3D)
188+
* @return The point projected. If a 2D point is projected a 3D point will be returned except if
189+
* inclination is 90. A 3D point is always returned if a 3D point is projected.
190+
* Example:
191+
* \code{.py}
192+
* p = QgsPointV2( 1, 2 ) # 2D point
193+
* pr = p.project ( 1, 0 )
194+
* # pr is a 2D point: 'Point (1 3)'
195+
* pr = p.project ( 1, 0, 90 )
196+
* # pr is a 2D point: 'Point (1 3)'
197+
* pr = p.project (1, 0, 0 )
198+
* # pr is a 3D point: 'PointZ (1 2 1)'
199+
* p = QgsPointV2( QgsWkbTypes.PointZ, 1, 2, 2 ) # 3D point
200+
* pr = p.project ( 1, 0 )
201+
* # pr is a 3D point: 'PointZ (1 3 2)'
202+
* pr = p.project ( 1, 0, 90 )
203+
* # pr is a 3D point: 'PointZ (1 3 2)'
204+
* pr = p.project (1, 0, 0 )
205+
* # pr is a 3D point: 'PointZ (1 2 3)'
206+
* \endcode
207+
* @note added in QGIS 3.0
208+
*/
209+
QgsPointV2 project( double distance, double azimuth, double inclination = 90.0 ) const;
210+
182211
/**
183212
* Calculates the vector obtained by subtracting a point from this point.
184213
* @note added in QGIS 3.0

src/core/geometry/qgspointv2.cpp

+32
Original file line numberDiff line numberDiff line change
@@ -462,3 +462,35 @@ double QgsPointV2::azimuth( const QgsPointV2& other ) const
462462
double dy = other.y() - mY;
463463
return ( atan2( dx, dy ) * 180.0 / M_PI );
464464
}
465+
466+
467+
QgsPointV2 QgsPointV2::project( double distance, double azimuth, double inclination ) const
468+
{
469+
QgsWkbTypes::Type pType(QgsWkbTypes::Point);
470+
471+
double rads_xy = azimuth * M_PI / 180.0;
472+
double dx = 0.0, dy = 0.0, dz = 0.0;
473+
474+
inclination = fmod( inclination, 360.0 );
475+
476+
if ( !is3D() && qgsDoubleNear(inclination, 90.0) )
477+
{
478+
dx = distance * sin( rads_xy );
479+
dy = distance * cos( rads_xy );
480+
}
481+
else
482+
{
483+
pType = QgsWkbTypes::addZ( pType );
484+
double rads_z = inclination * M_PI / 180.0;
485+
dx = distance * sin( rads_z ) * sin( rads_xy );
486+
dy = distance * sin( rads_z ) * cos( rads_xy );
487+
dz = distance * cos( rads_z );
488+
}
489+
490+
if ( isMeasure() )
491+
{
492+
pType = QgsWkbTypes::addM( pType );
493+
}
494+
495+
return QgsPointV2( pType, mX + dx, mY + dy, mZ + dz, mM );
496+
}

src/core/geometry/qgspointv2.h

+29
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,35 @@ class CORE_EXPORT QgsPointV2: public QgsAbstractGeometry
193193
*/
194194
double azimuth( const QgsPointV2& other ) const;
195195

196+
/** Returns a new point which correspond to this point projected by a specified distance
197+
* with specified angles (azimuth and inclination).
198+
* M value is preserved.
199+
* @param distance distance to project
200+
* @param azimuth angle to project in X Y, clockwise in degrees starting from north
201+
* @param inclination angle to project in Z (3D)
202+
* @return The point projected. If a 2D point is projected a 3D point will be returned except if
203+
* inclination is 90. A 3D point is always returned if a 3D point is projected.
204+
* Example:
205+
* \code{.py}
206+
* p = QgsPointV2( 1, 2 ) # 2D point
207+
* pr = p.project ( 1, 0 )
208+
* # pr is a 2D point: 'Point (1 3)'
209+
* pr = p.project ( 1, 0, 90 )
210+
* # pr is a 2D point: 'Point (1 3)'
211+
* pr = p.project (1, 0, 0 )
212+
* # pr is a 3D point: 'PointZ (1 2 1)'
213+
* p = QgsPointV2( QgsWkbTypes.PointZ, 1, 2, 2 ) # 3D point
214+
* pr = p.project ( 1, 0 )
215+
* # pr is a 3D point: 'PointZ (1 3 2)'
216+
* pr = p.project ( 1, 0, 90 )
217+
* # pr is a 3D point: 'PointZ (1 3 2)'
218+
* pr = p.project (1, 0, 0 )
219+
* # pr is a 3D point: 'PointZ (1 2 3)'
220+
* \endcode
221+
* @note added in QGIS 3.0
222+
*/
223+
QgsPointV2 project(double distance, double azimuth, double inclination = 90.0 ) const;
224+
196225
/**
197226
* Calculates the vector obtained by subtracting a point from this point.
198227
* @note added in QGIS 3.0

src/core/qgsexpression.cpp

+6-5
Original file line numberDiff line numberDiff line change
@@ -2762,12 +2762,13 @@ static QVariant fcnProject( const QVariantList& values, const QgsExpressionConte
27622762
}
27632763

27642764
double distance = getDoubleValue( values.at( 1 ), parent );
2765-
double bearing = getDoubleValue( values.at( 2 ), parent );
2765+
double azimuth = getDoubleValue( values.at( 2 ), parent );
2766+
double inclination = getDoubleValue( values.at( 3 ), parent );
27662767

2767-
QgsPoint p = geom.asPoint();
2768-
QgsPoint newPoint = p.project( distance, ( 180 * bearing ) / M_PI );
2768+
const QgsPointV2* p = dynamic_cast<const QgsPointV2*>( geom.geometry() );
2769+
QgsPointV2 newPoint = p->project( distance, 180.0 * azimuth / M_PI, 180.0 * inclination / M_PI );
27692770

2770-
return QVariant::fromValue( QgsGeometry( new QgsPointV2( newPoint.x(), newPoint.y() ) ) );
2771+
return QVariant::fromValue( QgsGeometry( new QgsPointV2( newPoint) ) );
27712772
}
27722773

27732774
static QVariant fcnExtrude( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
@@ -3765,7 +3766,7 @@ const QList<QgsExpression::Function*>& QgsExpression::Functions()
37653766
<< new StaticFunction( QStringLiteral( "radians" ), ParameterList() << Parameter( QStringLiteral( "degrees" ) ), fcnRadians, QStringLiteral( "Math" ) )
37663767
<< new StaticFunction( QStringLiteral( "degrees" ), ParameterList() << Parameter( QStringLiteral( "radians" ) ), fcnDegrees, QStringLiteral( "Math" ) )
37673768
<< new StaticFunction( QStringLiteral( "azimuth" ), ParameterList() << Parameter( QStringLiteral( "point_a" ) ) << Parameter( QStringLiteral( "point_b" ) ), fcnAzimuth, QStringList() << QStringLiteral( "Math" ) << QStringLiteral( "GeometryGroup" ) )
3768-
<< new StaticFunction( QStringLiteral( "project" ), ParameterList() << Parameter( QStringLiteral( "point" ) ) << Parameter( QStringLiteral( "distance" ) ) << Parameter( QStringLiteral( "bearing" ) ), fcnProject, QStringLiteral( "GeometryGroup" ) )
3769+
<< new StaticFunction( QStringLiteral( "project" ), ParameterList() << Parameter( QStringLiteral( "point" ) ) << Parameter( QStringLiteral( "distance" ) ) << Parameter( QStringLiteral( "azimuth" ) ) << Parameter( QStringLiteral( "elevation" ), true, M_PI / 2 ), fcnProject, QStringLiteral( "GeometryGroup" ) )
37693770
<< new StaticFunction( QStringLiteral( "abs" ), ParameterList() << Parameter( QStringLiteral( "value" ) ), fcnAbs, QStringLiteral( "Math" ) )
37703771
<< new StaticFunction( QStringLiteral( "cos" ), ParameterList() << Parameter( QStringLiteral( "angle" ) ), fcnCos, QStringLiteral( "Math" ) )
37713772
<< new StaticFunction( QStringLiteral( "sin" ), ParameterList() << Parameter( QStringLiteral( "angle" ) ), fcnSin, QStringLiteral( "Math" ) )

tests/src/core/testqgsexpression.cpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,10 @@ class TestQgsExpression: public QObject
786786
QTest::newRow( "project not geom" ) << "project( 'asd', 1, 2 )" << true << QVariant();
787787
QTest::newRow( "project not point" ) << "project( geom_from_wkt('LINESTRING(2 0,2 2, 3 2, 3 0)'), 1, 2 )" << true << QVariant();
788788
QTest::newRow( "project x" ) << "toint(x(project( make_point( 1, 2 ), 3, radians(270)))*1000000)" << false << QVariant( -2 * 1000000 );
789-
QTest::newRow( "project y" ) << "toint(y(project( point:=make_point( 1, 2 ), distance:=3, bearing:=radians(270)))*1000000)" << false << QVariant( 2 * 1000000 );
789+
QTest::newRow( "project y" ) << "toint(y(project( point:=make_point( 1, 2 ), distance:=3, azimuth:=radians(270)))*1000000)" << false << QVariant( 2 * 1000000 );
790+
QTest::newRow( "project m value preserved" ) << "geom_to_wkt(project( make_point( 1, 2, 2, 5), 1, 0.0, 0.0 ) )" << false << QVariant( "PointZM (1 2 3 5)" );
791+
QTest::newRow( "project 2D Point" ) << "geom_to_wkt(project( point:=make_point( 1, 2), distance:=1, azimuth:=radians(0), elevation:=0 ) )" << false << QVariant( "PointZ (1 2 1)" );
792+
QTest::newRow( "project 3D Point" ) << "geom_to_wkt(project( make_point( 1, 2, 2), 5, radians(450), radians (450) ) )" << false << QVariant( "PointZ (6 2 2)" );
790793
QTest::newRow( "extrude geom" ) << "geom_to_wkt(extrude( geom_from_wkt('LineString( 1 2, 3 2, 4 3)'),1,2))" << false << QVariant( "Polygon ((1 2, 3 2, 4 3, 5 5, 4 4, 2 4, 1 2))" );
791794
QTest::newRow( "extrude not geom" ) << "extrude('g',5,6)" << true << QVariant();
792795
QTest::newRow( "extrude null" ) << "extrude(NULL,5,6)" << false << QVariant();

tests/src/core/testqgsgeometry.cpp

+51
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,57 @@ void TestQgsGeometry::point()
839839
QCOMPARE( p31 - QgsVector( 3, 5 ), QgsPointV2( 1 , 2 ) );
840840
p31 -= QgsVector( 3, 5 );
841841
QCOMPARE( p31, QgsPointV2( 1, 2 ) );
842+
843+
// test projecting a point
844+
// 2D
845+
QgsPointV2 p33 = QgsPointV2( 1, 2 );
846+
QCOMPARE( p33.project( 1, 0 ), QgsPointV2( 1, 3 ) );
847+
QCOMPARE( p33.project( 1, 0, 0 ), QgsPointV2( QgsWkbTypes::PointZ, 1, 2, 1 ) );
848+
QCOMPARE( p33.project( 1.5, 90 ), QgsPointV2( 2.5, 2 ) );
849+
QCOMPARE( p33.project( 1.5, 90, 90 ), QgsPointV2( 2.5, 2 ) ); // stay QgsWkbTypes::Point
850+
QCOMPARE( p33.project( 2, 180 ), QgsPointV2( 1, 0 ) );
851+
QCOMPARE( p33.project( 2, 180, 180 ), QgsPointV2( QgsWkbTypes::PointZ, 1, 2, -2 ) );
852+
QCOMPARE( p33.project( 5, 270 ), QgsPointV2( -4, 2 ) );
853+
QCOMPARE( p33.project( 5, 270, 270 ), QgsPointV2( QgsWkbTypes::PointZ, 6, 2, 0 ) );
854+
QCOMPARE( p33.project( 6, 360 ), QgsPointV2( 1, 8 ) );
855+
QCOMPARE( p33.project( 6, 360, 360 ), QgsPointV2( QgsWkbTypes::PointZ, 1, 2, 6 ) );
856+
QCOMPARE( p33.project( 5, 450 ), QgsPointV2( 6, 2 ) );
857+
QCOMPARE( p33.project( 5, 450, 450 ), QgsPointV2( 6, 2 ) ); // stay QgsWkbTypes::Point
858+
QCOMPARE( p33.project( -1, 0 ), QgsPointV2( 1, 1 ) );
859+
QCOMPARE( p33.project( -1, 0, 0 ), QgsPointV2( QgsWkbTypes::PointZ, 1, 2, -1 ) );
860+
QCOMPARE( p33.project( 1.5, -90 ), QgsPointV2( -0.5, 2 ) );
861+
QCOMPARE( p33.project( 1.5, -90, -90 ), QgsPointV2( QgsWkbTypes::PointZ, 2.5, 2, 0 ) );
862+
// PointM
863+
p33.addMValue( 5.0 );
864+
QCOMPARE( p33.project( 1, 0 ), QgsPointV2( QgsWkbTypes::PointM, 1, 3, 0, 5 ) );
865+
QCOMPARE( p33.project( 1, 0, 0 ), QgsPointV2( QgsWkbTypes::PointZM, 1, 2, 1, 5 ) );
866+
QCOMPARE( p33.project( 5, 450, 450 ), QgsPointV2( QgsWkbTypes::PointM, 6, 2, 0, 5 ) );
867+
868+
// 3D
869+
QgsPointV2 p34 = QgsPointV2( QgsWkbTypes::PointZ, 1, 2, 2 );
870+
QCOMPARE( p34.project( 1, 0 ), QgsPointV2(QgsWkbTypes::PointZ, 1, 3, 2 ) );
871+
QCOMPARE( p34.project( 1, 0, 0 ), QgsPointV2(QgsWkbTypes::PointZ, 1, 2, 3 ) );
872+
QCOMPARE( p34.project( 1.5, 90 ), QgsPointV2(QgsWkbTypes::PointZ, 2.5, 2, 2 ) );
873+
QCOMPARE( p34.project( 1.5, 90, 90 ), QgsPointV2(QgsWkbTypes::PointZ, 2.5, 2, 2 ) );
874+
QCOMPARE( p34.project( 2, 180 ), QgsPointV2(QgsWkbTypes::PointZ, 1, 0, 2 ) );
875+
QCOMPARE( p34.project( 2, 180, 180 ), QgsPointV2(QgsWkbTypes::PointZ, 1, 2, 0 ) );
876+
QCOMPARE( p34.project( 5, 270 ), QgsPointV2(QgsWkbTypes::PointZ, -4, 2, 2 ) );
877+
QCOMPARE( p34.project( 5, 270, 270 ), QgsPointV2(QgsWkbTypes::PointZ, 6, 2, 2 ) );
878+
QCOMPARE( p34.project( 6, 360 ), QgsPointV2(QgsWkbTypes::PointZ, 1, 8, 2 ) );
879+
QCOMPARE( p34.project( 6, 360, 360 ), QgsPointV2(QgsWkbTypes::PointZ, 1, 2, 8 ) );
880+
QCOMPARE( p34.project( 5, 450 ), QgsPointV2(QgsWkbTypes::PointZ, 6, 2, 2 ) );
881+
QCOMPARE( p34.project( 5, 450, 450 ), QgsPointV2(QgsWkbTypes::PointZ, 6, 2, 2 ) );
882+
QCOMPARE( p34.project( -1, 0 ), QgsPointV2(QgsWkbTypes::PointZ, 1, 1, 2 ) );
883+
QCOMPARE( p34.project( -1, 0, 0 ), QgsPointV2(QgsWkbTypes::PointZ, 1, 2, 1 ) );
884+
QCOMPARE( p34.project( 1.5, -90 ), QgsPointV2(QgsWkbTypes::PointZ, -0.5, 2, 2 ) );
885+
QCOMPARE( p34.project( 1.5, -90, -90 ), QgsPointV2(QgsWkbTypes::PointZ, 2.5, 2, 2 ) );
886+
// PointM
887+
p34.addMValue( 5.0 );
888+
QCOMPARE( p34.project( 1, 0 ), QgsPointV2(QgsWkbTypes::PointZM, 1, 3, 2, 5 ) );
889+
QCOMPARE( p34.project( 1, 0, 0 ), QgsPointV2(QgsWkbTypes::PointZM, 1, 2, 3, 5 ) );
890+
QCOMPARE( p34.project( 5, 450 ), QgsPointV2(QgsWkbTypes::PointZM, 6, 2, 2, 5 ) );
891+
QCOMPARE( p34.project( 5, 450, 450 ), QgsPointV2(QgsWkbTypes::PointZM, 6, 2, 2, 5 ) );
892+
842893
}
843894

844895
void TestQgsGeometry::lineString()

0 commit comments

Comments
 (0)