Skip to content

Commit 1bada06

Browse files
committed
[FEATURE][processing] Add 'Project points (cartesian)' algorithm
Projects points from an input point layer by a specified distance and bearing (azimuth). Supports dynamic parameters for the distance and bearing so that they can use field values or expressions.
1 parent 84c5089 commit 1bada06

9 files changed

+425
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://ogr.maptools.org/ projected_multipoints.xsd"
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>-0.5</gml:X><gml:Y>-5</gml:Y></gml:coord>
10+
<gml:coord><gml:X>7.5</gml:X><gml:Y>3</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:projected_multipoints fid="points.9">
16+
<ogr:d>5</ogr:d>
17+
</ogr:projected_multipoints>
18+
</gml:featureMember>
19+
<gml:featureMember>
20+
<ogr:projected_multipoints fid="points.0">
21+
<ogr:geometryProperty><gml:MultiPoint srsName="EPSG:4326"><gml:pointMember><gml:Point><gml:coordinates>0.5,1.0</gml:coordinates></gml:Point></gml:pointMember><gml:pointMember><gml:Point><gml:coordinates>1.5,2.0</gml:coordinates></gml:Point></gml:pointMember><gml:pointMember><gml:Point><gml:coordinates>2.5,3.0</gml:coordinates></gml:Point></gml:pointMember></gml:MultiPoint></ogr:geometryProperty>
22+
<ogr:d>1</ogr:d>
23+
</ogr:projected_multipoints>
24+
</gml:featureMember>
25+
<gml:featureMember>
26+
<ogr:projected_multipoints fid="points.3">
27+
<ogr:geometryProperty><gml:MultiPoint srsName="EPSG:4326"><gml:pointMember><gml:Point><gml:coordinates>4.5,2.0</gml:coordinates></gml:Point></gml:pointMember><gml:pointMember><gml:Point><gml:coordinates>3.5,1.0</gml:coordinates></gml:Point></gml:pointMember></gml:MultiPoint></ogr:geometryProperty>
28+
<ogr:d>2</ogr:d>
29+
</ogr:projected_multipoints>
30+
</gml:featureMember>
31+
<gml:featureMember>
32+
<ogr:projected_multipoints fid="points.5">
33+
<ogr:geometryProperty><gml:MultiPoint srsName="EPSG:4326"><gml:pointMember><gml:Point><gml:coordinates>-0.5,-5.0</gml:coordinates></gml:Point></gml:pointMember><gml:pointMember><gml:Point><gml:coordinates>7.5,-1.0</gml:coordinates></gml:Point></gml:pointMember></gml:MultiPoint></ogr:geometryProperty>
34+
<ogr:d>3</ogr:d>
35+
</ogr:projected_multipoints>
36+
</gml:featureMember>
37+
<gml:featureMember>
38+
<ogr:projected_multipoints fid="points.7">
39+
<ogr:geometryProperty><gml:MultiPoint srsName="EPSG:4326"><gml:pointMember><gml:Point><gml:coordinates>6.5,-1.0</gml:coordinates></gml:Point></gml:pointMember><gml:pointMember><gml:Point><gml:coordinates>-0.5,-1.0</gml:coordinates></gml:Point></gml:pointMember></gml:MultiPoint></ogr:geometryProperty>
40+
<ogr:d>4</ogr:d>
41+
</ogr:projected_multipoints>
42+
</gml:featureMember>
43+
</ogr:FeatureCollection>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
3+
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
4+
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
5+
<xs:complexType name="FeatureCollectionType">
6+
<xs:complexContent>
7+
<xs:extension base="gml:AbstractFeatureCollectionType">
8+
<xs:attribute name="lockId" type="xs:string" use="optional"/>
9+
<xs:attribute name="scope" type="xs:string" use="optional"/>
10+
</xs:extension>
11+
</xs:complexContent>
12+
</xs:complexType>
13+
<xs:element name="projected_multipoints" type="ogr:projected_multipoints_Type" substitutionGroup="gml:_Feature"/>
14+
<xs:complexType name="projected_multipoints_Type">
15+
<xs:complexContent>
16+
<xs:extension base="gml:AbstractFeatureType">
17+
<xs:sequence>
18+
<xs:element name="geometryProperty" type="gml:MultiPointPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
19+
<xs:element name="d" nillable="true" minOccurs="0" maxOccurs="1">
20+
<xs:simpleType>
21+
<xs:restriction base="xs:integer">
22+
<xs:totalDigits value="10"/>
23+
</xs:restriction>
24+
</xs:simpleType>
25+
</xs:element>
26+
</xs:sequence>
27+
</xs:extension>
28+
</xs:complexContent>
29+
</xs:complexType>
30+
</xs:schema>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://ogr.maptools.org/ projected_points.xsd"
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>1</gml:X><gml:Y>-5</gml:Y></gml:coord>
10+
<gml:coord><gml:X>9</gml:X><gml:Y>3</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:projected_points fid="points.0">
16+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>2,1</gml:coordinates></gml:Point></ogr:geometryProperty>
17+
<ogr:id>1</ogr:id>
18+
<ogr:id2>2</ogr:id2>
19+
</ogr:projected_points>
20+
</gml:featureMember>
21+
<gml:featureMember>
22+
<ogr:projected_points fid="points.1">
23+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>4,3</gml:coordinates></gml:Point></ogr:geometryProperty>
24+
<ogr:id>2</ogr:id>
25+
<ogr:id2>1</ogr:id2>
26+
</ogr:projected_points>
27+
</gml:featureMember>
28+
<gml:featureMember>
29+
<ogr:projected_points fid="points.2">
30+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,2</gml:coordinates></gml:Point></ogr:geometryProperty>
31+
<ogr:id>3</ogr:id>
32+
<ogr:id2>0</ogr:id2>
33+
</ogr:projected_points>
34+
</gml:featureMember>
35+
<gml:featureMember>
36+
<ogr:projected_points fid="points.3">
37+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>6,2</gml:coordinates></gml:Point></ogr:geometryProperty>
38+
<ogr:id>4</ogr:id>
39+
<ogr:id2>2</ogr:id2>
40+
</ogr:projected_points>
41+
</gml:featureMember>
42+
<gml:featureMember>
43+
<ogr:projected_points fid="points.4">
44+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>5,1</gml:coordinates></gml:Point></ogr:geometryProperty>
45+
<ogr:id>5</ogr:id>
46+
<ogr:id2>1</ogr:id2>
47+
</ogr:projected_points>
48+
</gml:featureMember>
49+
<gml:featureMember>
50+
<ogr:projected_points fid="points.5">
51+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1,-5</gml:coordinates></gml:Point></ogr:geometryProperty>
52+
<ogr:id>6</ogr:id>
53+
<ogr:id2>0</ogr:id2>
54+
</ogr:projected_points>
55+
</gml:featureMember>
56+
<gml:featureMember>
57+
<ogr:projected_points fid="points.6">
58+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>9.0,-1.0</gml:coordinates></gml:Point></ogr:geometryProperty>
59+
<ogr:id>7</ogr:id>
60+
<ogr:id2>0</ogr:id2>
61+
</ogr:projected_points>
62+
</gml:featureMember>
63+
<gml:featureMember>
64+
<ogr:projected_points fid="points.7">
65+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>8.0,-1.0</gml:coordinates></gml:Point></ogr:geometryProperty>
66+
<ogr:id>8</ogr:id>
67+
<ogr:id2>0</ogr:id2>
68+
</ogr:projected_points>
69+
</gml:featureMember>
70+
<gml:featureMember>
71+
<ogr:projected_points fid="points.8">
72+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1.0,-1.0</gml:coordinates></gml:Point></ogr:geometryProperty>
73+
<ogr:id>9</ogr:id>
74+
<ogr:id2>0</ogr:id2>
75+
</ogr:projected_points>
76+
</gml:featureMember>
77+
</ogr:FeatureCollection>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
3+
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
4+
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
5+
<xs:complexType name="FeatureCollectionType">
6+
<xs:complexContent>
7+
<xs:extension base="gml:AbstractFeatureCollectionType">
8+
<xs:attribute name="lockId" type="xs:string" use="optional"/>
9+
<xs:attribute name="scope" type="xs:string" use="optional"/>
10+
</xs:extension>
11+
</xs:complexContent>
12+
</xs:complexType>
13+
<xs:element name="projected_points" type="ogr:projected_points_Type" substitutionGroup="gml:_Feature"/>
14+
<xs:complexType name="projected_points_Type">
15+
<xs:complexContent>
16+
<xs:extension base="gml:AbstractFeatureType">
17+
<xs:sequence>
18+
<xs:element name="geometryProperty" type="gml:PointPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
19+
<xs:element name="id" nillable="true" minOccurs="0" maxOccurs="1">
20+
<xs:simpleType>
21+
<xs:restriction base="xs:integer">
22+
<xs:totalDigits value="10"/>
23+
</xs:restriction>
24+
</xs:simpleType>
25+
</xs:element>
26+
<xs:element name="id2" nillable="true" minOccurs="0" maxOccurs="1">
27+
<xs:simpleType>
28+
<xs:restriction base="xs:integer">
29+
<xs:totalDigits value="10"/>
30+
</xs:restriction>
31+
</xs:simpleType>
32+
</xs:element>
33+
</xs:sequence>
34+
</xs:extension>
35+
</xs:complexContent>
36+
</xs:complexType>
37+
</xs:schema>

python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml

+26
Original file line numberDiff line numberDiff line change
@@ -4664,6 +4664,32 @@ tests:
46644664
name: expected/difference.gml
46654665
type: vector
46664666

4667+
- algorithm: native:projectpointcartesian
4668+
name: Project points cartesian
4669+
params:
4670+
BEARING: 90.0
4671+
DISTANCE: 1.0
4672+
INPUT:
4673+
name: points.gml
4674+
type: vector
4675+
results:
4676+
OUTPUT:
4677+
name: expected/projected_points.gml
4678+
type: vector
4679+
4680+
- algorithm: native:projectpointcartesian
4681+
name: Project multipoints cartesian
4682+
params:
4683+
BEARING: -90.0
4684+
DISTANCE: 0.5
4685+
INPUT:
4686+
name: multipoints.gml
4687+
type: vector
4688+
results:
4689+
OUTPUT:
4690+
name: expected/projected_multipoints.gml
4691+
type: vector
4692+
46674693
- algorithm: native:removeduplicatevertices
46684694
name: Remove duplicate vertices from lines
46694695
params:

src/analysis/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ SET(QGIS_ANALYSIS_SRCS
5050
processing/qgsalgorithmorderbyexpression.cpp
5151
processing/qgsalgorithmorientedminimumboundingbox.cpp
5252
processing/qgsalgorithmpackage.cpp
53+
processing/qgsalgorithmprojectpointcartesian.cpp
5354
processing/qgsalgorithmpromotetomultipart.cpp
5455
processing/qgsalgorithmrasterlayeruniquevalues.cpp
5556
processing/qgsalgorithmremoveduplicatevertices.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/***************************************************************************
2+
qgsalgorithmprojectpointcartesian.cpp
3+
---------------------
4+
begin : April 2017
5+
copyright : (C) 2017 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#include "qgsalgorithmprojectpointcartesian.h"
19+
#include "qgsmultipoint.h"
20+
21+
///@cond PRIVATE
22+
23+
QString QgsProjectPointCartesianAlgorithm::name() const
24+
{
25+
return QStringLiteral( "projectpointcartesian" );
26+
}
27+
28+
QString QgsProjectPointCartesianAlgorithm::displayName() const
29+
{
30+
return QObject::tr( "Project points (Cartesian)" );
31+
}
32+
33+
QStringList QgsProjectPointCartesianAlgorithm::tags() const
34+
{
35+
return QObject::tr( "bearing,azimuth,distance,angle" ).split( ',' );
36+
}
37+
38+
QString QgsProjectPointCartesianAlgorithm::group() const
39+
{
40+
return QObject::tr( "Vector geometry" );
41+
}
42+
43+
QString QgsProjectPointCartesianAlgorithm::groupId() const
44+
{
45+
return QStringLiteral( "vectorgeometry" );
46+
}
47+
48+
QString QgsProjectPointCartesianAlgorithm::outputName() const
49+
{
50+
return QObject::tr( "Projected" );
51+
}
52+
53+
QString QgsProjectPointCartesianAlgorithm::shortHelpString() const
54+
{
55+
return QObject::tr( "This algorithm projects point geometries by a specified distance and bearing (azimuth), creating a new point layer with the projected points.\n\n"
56+
"The distance is specified in layer units, and the bearing in degrees clockwise from North." );
57+
}
58+
59+
QList<int> QgsProjectPointCartesianAlgorithm::inputLayerTypes() const
60+
{
61+
return QList<int>() << QgsProcessing::TypeVectorPoint;
62+
}
63+
64+
QgsProcessing::SourceType QgsProjectPointCartesianAlgorithm::outputLayerType() const
65+
{
66+
return QgsProcessing::TypeVectorPoint;
67+
}
68+
69+
QgsProjectPointCartesianAlgorithm *QgsProjectPointCartesianAlgorithm::createInstance() const
70+
{
71+
return new QgsProjectPointCartesianAlgorithm();
72+
}
73+
74+
void QgsProjectPointCartesianAlgorithm::initParameters( const QVariantMap & )
75+
{
76+
std::unique_ptr< QgsProcessingParameterNumber > bearing = qgis::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "BEARING" ), QObject::tr( "Bearing (degrees from North)" ), QgsProcessingParameterNumber::Double, false );
77+
bearing->setIsDynamic( true );
78+
bearing->setDynamicPropertyDefinition( QgsPropertyDefinition( QStringLiteral( "Bearing" ), QObject::tr( "Bearing (degrees from North)" ), QgsPropertyDefinition::Double ) );
79+
bearing->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) );
80+
addParameter( bearing.release() );
81+
82+
std::unique_ptr< QgsProcessingParameterNumber > distance = qgis::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "DISTANCE" ), QObject::tr( "Distance" ), QgsProcessingParameterNumber::Double, false );
83+
distance->setIsDynamic( true );
84+
distance->setDynamicPropertyDefinition( QgsPropertyDefinition( QStringLiteral( "Distance" ), QObject::tr( "Projection distance" ), QgsPropertyDefinition::Double ) );
85+
distance->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) );
86+
addParameter( distance.release() );
87+
}
88+
89+
bool QgsProjectPointCartesianAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
90+
{
91+
mBearing = parameterAsDouble( parameters, QStringLiteral( "BEARING" ), context );
92+
mDynamicBearing = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "BEARING" ) );
93+
if ( mDynamicBearing )
94+
mBearingProperty = parameters.value( QStringLiteral( "BEARING" ) ).value< QgsProperty >();
95+
96+
mDistance = parameterAsDouble( parameters, QStringLiteral( "DISTANCE" ), context );
97+
mDynamicDistance = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "DISTANCE" ) );
98+
if ( mDynamicDistance )
99+
mDistanceProperty = parameters.value( QStringLiteral( "DISTANCE" ) ).value< QgsProperty >();
100+
101+
return true;
102+
}
103+
104+
QgsFeatureList QgsProjectPointCartesianAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingContext &context, QgsProcessingFeedback * )
105+
{
106+
QgsFeature f = feature;
107+
if ( f.hasGeometry() && QgsWkbTypes::geometryType( f.geometry().wkbType() ) == QgsWkbTypes::PointGeometry )
108+
{
109+
double distance = mDistance;
110+
if ( mDynamicDistance )
111+
distance = mDistanceProperty.valueAsDouble( context.expressionContext(), distance );
112+
double bearing = mBearing;
113+
if ( mDynamicBearing )
114+
bearing = mBearingProperty.valueAsDouble( context.expressionContext(), bearing );
115+
116+
QgsGeometry g = f.geometry();
117+
if ( QgsWkbTypes::isMultiType( g.wkbType() ) )
118+
{
119+
const QgsMultiPoint *mp = static_cast< const QgsMultiPoint * >( g.constGet() );
120+
std::unique_ptr< QgsMultiPoint > result = qgis::make_unique< QgsMultiPoint >();
121+
for ( int i = 0; i < mp->numGeometries(); ++i )
122+
{
123+
const QgsPoint *p = static_cast< const QgsPoint * >( mp->geometryN( i ) );
124+
result->addGeometry( p->project( distance, bearing ).clone() );
125+
}
126+
f.setGeometry( QgsGeometry( std::move( result ) ) );
127+
}
128+
else
129+
{
130+
const QgsPoint *p = static_cast< const QgsPoint * >( g.constGet() );
131+
QgsPoint result = p->project( distance, bearing );
132+
f.setGeometry( QgsGeometry( result.clone() ) );
133+
}
134+
}
135+
136+
return QgsFeatureList() << f;
137+
}
138+
139+
///@endcond
140+

0 commit comments

Comments
 (0)