1 /***************************************************************************
2                          qgsalgorithmtransect.cpp
3                          -------------------------
4     begin                : October 2017
5     copyright            : (C) 2017 by Loïc Bartoletti
6     email                : lbartoletti at tuxfamily dot org
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 "qgsalgorithmtransect.h"
19 #include "qgsmultilinestring.h"
20 #include "qgslinestring.h"
21 
22 ///@cond PRIVATE
23 
name() const24 QString QgsTransectAlgorithm::name() const
25 {
26   return QStringLiteral( "transect" );
27 }
28 
displayName() const29 QString QgsTransectAlgorithm::displayName() const
30 {
31   return QObject::tr( "Transect" );
32 }
33 
tags() const34 QStringList QgsTransectAlgorithm::tags() const
35 {
36   return QObject::tr( "transect,station,lines,extend," ).split( ',' );
37 }
38 
group() const39 QString QgsTransectAlgorithm::group() const
40 {
41   return QObject::tr( "Vector geometry" );
42 }
43 
groupId() const44 QString QgsTransectAlgorithm::groupId() const
45 {
46   return QStringLiteral( "vectorgeometry" );
47 }
48 
initAlgorithm(const QVariantMap &)49 void QgsTransectAlgorithm::initAlgorithm( const QVariantMap & )
50 {
51   addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ),
52                 QObject::tr( "Input layer" ), QList< int >() << QgsProcessing::TypeVectorLine ) );
53   std::unique_ptr< QgsProcessingParameterDistance > length = std::make_unique< QgsProcessingParameterDistance >( QStringLiteral( "LENGTH" ), QObject::tr( "Length of the transect" ),
54       5.0, QStringLiteral( "INPUT" ), false, 0 );
55   length->setIsDynamic( true );
56   length->setDynamicPropertyDefinition( QgsPropertyDefinition( QStringLiteral( "LENGTH" ), QObject::tr( "Length of the transect" ), QgsPropertyDefinition::DoublePositive ) );
57   length->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) );
58   addParameter( length.release() );
59 
60   std::unique_ptr< QgsProcessingParameterNumber > angle = std::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "ANGLE" ), QObject::tr( "Angle in degrees from the original line at the vertices" ), QgsProcessingParameterNumber::Double,
61       90.0, false, 0, 360 );
62   angle->setIsDynamic( true );
63   angle->setDynamicPropertyDefinition( QgsPropertyDefinition( QStringLiteral( "ANGLE" ), QObject::tr( "Angle in degrees" ), QgsPropertyDefinition::Double ) );
64   angle->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) );
65   addParameter( angle.release() );
66 
67   addParameter( new QgsProcessingParameterEnum( QStringLiteral( "SIDE" ), QObject::tr( "Side to create the transects" ), QStringList() << QObject::tr( "Left" ) << QObject::tr( "Right" ) << QObject::tr( "Both" ), false ) );
68   addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Transect" ), QgsProcessing::TypeVectorLine ) );
69 
70 }
71 
shortHelpString() const72 QString QgsTransectAlgorithm::shortHelpString() const
73 {
74 
75   return QObject::tr( "This algorithm creates transects on vertices for (multi)linestring.\n" ) +
76          QObject::tr( "A transect is a line oriented from an angle (by default perpendicular) to the input polylines (at vertices)." ) +
77          QStringLiteral( "\n\n" )  +
78          QObject::tr( "Field(s) from feature(s) are returned in the transect with these new fields:\n" ) +
79          QObject::tr( "- TR_FID: ID of the original feature\n" ) +
80          QObject::tr( "- TR_ID: ID of the transect. Each transect have an unique ID\n" ) +
81          QObject::tr( "- TR_SEGMENT: ID of the segment of the linestring\n" ) +
82          QObject::tr( "- TR_ANGLE: Angle in degrees from the original line at the vertex\n" ) +
83          QObject::tr( "- TR_LENGTH: Total length of the transect returned\n" ) +
84          QObject::tr( "- TR_ORIENT: Side of the transect (only on the left or right of the line, or both side)\n" );
85 
86 }
87 
createInstance() const88 QgsTransectAlgorithm *QgsTransectAlgorithm::createInstance() const
89 {
90   return new QgsTransectAlgorithm();
91 }
92 
processAlgorithm(const QVariantMap & parameters,QgsProcessingContext & context,QgsProcessingFeedback * feedback)93 QVariantMap QgsTransectAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
94 {
95   const Side orientation = static_cast< QgsTransectAlgorithm::Side >( parameterAsInt( parameters, QStringLiteral( "SIDE" ), context ) );
96   const double angle = fabs( parameterAsDouble( parameters, QStringLiteral( "ANGLE" ), context ) );
97   const bool dynamicAngle = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "ANGLE" ) );
98   QgsProperty angleProperty;
99   if ( dynamicAngle )
100     angleProperty = parameters.value( QStringLiteral( "ANGLE" ) ).value< QgsProperty >();
101 
102   double length = parameterAsDouble( parameters, QStringLiteral( "LENGTH" ), context );
103   const bool dynamicLength = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "LENGTH" ) );
104   QgsProperty lengthProperty;
105   if ( dynamicLength )
106     lengthProperty = parameters.value( QStringLiteral( "LENGTH" ) ).value< QgsProperty >();
107 
108   if ( orientation == QgsTransectAlgorithm::Both )
109     length /= 2.0;
110 
111   std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
112   if ( !source )
113     throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
114 
115   QgsExpressionContext expressionContext = createExpressionContext( parameters, context, dynamic_cast< QgsProcessingFeatureSource * >( source.get() ) );
116 
117   QgsFields fields = source->fields();
118 
119   fields.append( QgsField( QStringLiteral( "TR_FID" ), QVariant::Int, QString(), 20 ) );
120   fields.append( QgsField( QStringLiteral( "TR_ID" ), QVariant::Int, QString(), 20 ) );
121   fields.append( QgsField( QStringLiteral( "TR_SEGMENT" ), QVariant::Int, QString(), 20 ) );
122   fields.append( QgsField( QStringLiteral( "TR_ANGLE" ), QVariant::Double, QString(), 5, 2 ) );
123   fields.append( QgsField( QStringLiteral( "TR_LENGTH" ), QVariant::Double, QString(), 20, 6 ) );
124   fields.append( QgsField( QStringLiteral( "TR_ORIENT" ), QVariant::Int, QString(), 1 ) );
125 
126   QgsWkbTypes::Type outputWkb = QgsWkbTypes::LineString;
127   if ( QgsWkbTypes::hasZ( source->wkbType() ) )
128     outputWkb = QgsWkbTypes::addZ( outputWkb );
129   if ( QgsWkbTypes::hasM( source->wkbType() ) )
130     outputWkb = QgsWkbTypes::addM( outputWkb );
131 
132   QString dest;
133   std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields,
134                                           outputWkb, source->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) );
135   if ( !sink )
136     throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
137 
138   QgsFeatureIterator features = source->getFeatures( );
139 
140   int current = -1;
141   int number = 0;
142   const double step =  source->featureCount() > 0 ? 100.0 / source->featureCount() : 1;
143   QgsFeature feat;
144 
145 
146   while ( features.nextFeature( feat ) )
147   {
148     current++;
149     if ( feedback->isCanceled() )
150     {
151       break;
152     }
153 
154     feedback->setProgress( current * step );
155     if ( !feat.hasGeometry() )
156       continue;
157 
158     QgsGeometry inputGeometry = feat.geometry();
159 
160     if ( dynamicLength || dynamicAngle )
161     {
162       expressionContext.setFeature( feat );
163     }
164 
165     double evaluatedLength = length;
166     if ( dynamicLength )
167       evaluatedLength = lengthProperty.valueAsDouble( context.expressionContext(), length );
168     double evaluatedAngle = angle;
169     if ( dynamicAngle )
170       evaluatedAngle = angleProperty.valueAsDouble( context.expressionContext(), angle );
171 
172     inputGeometry.convertToMultiType();
173     const QgsMultiLineString *multiLine = static_cast< const QgsMultiLineString *  >( inputGeometry.constGet() );
174     for ( int id = 0; id < multiLine->numGeometries(); ++id )
175     {
176       const QgsLineString *line = multiLine->lineStringN( id );
177       QgsAbstractGeometry::vertex_iterator it = line->vertices_begin();
178       while ( it != line->vertices_end() )
179       {
180         const QgsVertexId vertexId = it.vertexId();
181         const int i = vertexId.vertex;
182         QgsFeature outFeat;
183         QgsAttributes attrs = feat.attributes();
184         attrs << current << number << i + 1 << evaluatedAngle <<
185               ( ( orientation == QgsTransectAlgorithm::Both ) ? evaluatedLength * 2 : evaluatedLength ) <<
186               orientation;
187         outFeat.setAttributes( attrs );
188         const double angleAtVertex = line->vertexAngle( vertexId );
189         outFeat.setGeometry( calcTransect( *it, angleAtVertex, evaluatedLength, orientation, evaluatedAngle ) );
190         if ( !sink->addFeature( outFeat, QgsFeatureSink::FastInsert ) )
191           throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
192         number++;
193         it++;
194       }
195     }
196   }
197 
198   QVariantMap outputs;
199   outputs.insert( QStringLiteral( "OUTPUT" ), dest );
200   return outputs;
201 }
202 
203 
calcTransect(const QgsPoint & point,const double angleAtVertex,const double length,const QgsTransectAlgorithm::Side orientation,const double angle)204 QgsGeometry QgsTransectAlgorithm::calcTransect( const QgsPoint &point, const double angleAtVertex, const double length, const QgsTransectAlgorithm::Side orientation, const double angle )
205 {
206   QgsPoint pLeft; // left point of the line
207   QgsPoint pRight; // right point of the line
208 
209   QgsPolyline line;
210 
211   if ( ( orientation == QgsTransectAlgorithm::Right ) || ( orientation == QgsTransectAlgorithm::Both ) )
212   {
213     pLeft = point.project( length, angle + 180.0 / M_PI * angleAtVertex );
214     if ( orientation != QgsTransectAlgorithm::Both )
215       pRight = point;
216   }
217 
218   if ( ( orientation == QgsTransectAlgorithm::Left ) || ( orientation == QgsTransectAlgorithm::Both ) )
219   {
220     pRight = point.project( -length, angle + 180.0 / M_PI * angleAtVertex );
221     if ( orientation != QgsTransectAlgorithm::Both )
222       pLeft = point;
223   }
224 
225   line.append( pLeft );
226   line.append( pRight );
227 
228   return QgsGeometry::fromPolyline( line );
229 }
230 
231 ///@endcond
232