Skip to content

Commit

Permalink
Added Points to lines algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
uclaros authored and nyalldawson committed Jan 18, 2021
1 parent 6387a42 commit 280d2e5
Show file tree
Hide file tree
Showing 4 changed files with 283 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/analysis/CMakeLists.txt
Expand Up @@ -123,6 +123,7 @@ set(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmpointtolayer.cpp
processing/qgsalgorithmpointsalonggeometry.cpp
processing/qgsalgorithmpointslayerfromtable.cpp
processing/qgsalgorithmpointstolines.cpp
processing/qgsalgorithmpoleofinaccessibility.cpp
processing/qgsalgorithmpolygonize.cpp
processing/qgsalgorithmprojectpointcartesian.cpp
Expand Down
226 changes: 226 additions & 0 deletions src/analysis/processing/qgsalgorithmpointstolines.cpp
@@ -0,0 +1,226 @@
/***************************************************************************
qgsalgorithmdpointstolines.cpp
---------------------
begin : November 2020
copyright : (C) 2020 by Stefanos Natsis
email : uclaros 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 "qgsalgorithmpointstolines.h"
#include "qgsvectorlayer.h"

///@cond PRIVATE

QString QgsPointsToLinesAlgorithm::name() const
{
return QStringLiteral( "pointstolines" );
}

QString QgsPointsToLinesAlgorithm::displayName() const
{
return QObject::tr( "Points to lines" );
}

QString QgsPointsToLinesAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm takes a point layer and connects its features creating a new line layer.\n\n"
"An attribute or expression may be specified to define the order the points should be connected. "
"If no order expression is specified, the fid is used.\n\n"
"A natural sort can be used when sorting by a string attribute "
"or expression (ie. place 'a9' before 'a10').\n\n"
"An attribute can be selected to group points having the same value into the same resulting line." );
}

QStringList QgsPointsToLinesAlgorithm::tags() const
{
return QObject::tr( "create,lines,points,connect,convert,join" ).split( ',' );
}

QString QgsPointsToLinesAlgorithm::group() const
{
return QObject::tr( "Vector geometry" );
}

QString QgsPointsToLinesAlgorithm::groupId() const
{
return QStringLiteral( "vectorgeometry" );
}

void QgsPointsToLinesAlgorithm::initAlgorithm( const QVariantMap & )
{
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ),
QObject::tr( "Input layer" ), QList< int >() << QgsProcessing::TypeVectorPoint ) );
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "CLOSE_LINES" ),
QObject::tr( "Create closed lines" ), false, true ) );
addParameter( new QgsProcessingParameterExpression( QStringLiteral( "ORDER_EXPRESSION" ),
QObject::tr( "Order expression" ), QVariant(), QStringLiteral( "INPUT" ), true ) );
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "NATURAL_SORT" ),
QObject::tr( "Sort text containing numbers naturally" ), false, true ) );
addParameter( new QgsProcessingParameterField( QStringLiteral( "GROUP_FIELD" ),
QObject::tr( "Line group field" ), QVariant(), QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any, false, true ) );
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ),
QObject::tr( "Lines" ), QgsProcessing::TypeVectorLine ) );
addOutput( new QgsProcessingOutputNumber( QStringLiteral( "NUM_LINES" ), QObject::tr( "Number of lines" ) ) );
}

QgsPointsToLinesAlgorithm *QgsPointsToLinesAlgorithm::createInstance() const
{
return new QgsPointsToLinesAlgorithm();
}

QVariantMap QgsPointsToLinesAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
std::unique_ptr< QgsProcessingFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
if ( !source )
throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );

const bool closeLines = parameterAsBool( parameters, QStringLiteral( "CLOSE_LINES" ), context );

QString orderExpression = parameterAsString( parameters, QStringLiteral( "ORDER_EXPRESSION" ), context );
// If no order expression is given, default to the fid
if ( orderExpression.isEmpty() )
orderExpression = QString( "$id" );
QgsExpressionContext expressionContext = createExpressionContext( parameters, context, source.get() );
QgsExpression expression = QgsExpression( orderExpression );
if ( expression.hasParserError() )
throw QgsProcessingException( expression.parserErrorString() );

QStringList requiredFields = QStringList( expression.referencedColumns().values() );
expression.prepare( &expressionContext );

QgsFields outputFields = QgsFields();
const QString groupField = parameterAsString( parameters, QStringLiteral( "GROUP_FIELD" ), context );
if ( ! groupField.isEmpty() )
{
const QgsField field = source->fields().field( groupField );
requiredFields.append( field.name() );
outputFields.append( field );
}

const bool naturalSort = parameterAsBool( parameters, QStringLiteral( "NATURAL_SORT" ), context );
QCollator collator;
collator.setNumericMode( true );

QgsWkbTypes::Type wkbType = QgsWkbTypes::LineString;
if ( QgsWkbTypes::hasM( source->wkbType() ) )
wkbType = QgsWkbTypes::addM( wkbType );
if ( QgsWkbTypes::hasZ( source->wkbType() ) )
wkbType = QgsWkbTypes::addZ( wkbType );

QString dest;
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outputFields, wkbType, source->sourceCrs() ) );
if ( !sink )
throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );

// Store the points in a hash with the group identifier as the key
QHash< QVariant, QVector< QPair< QVariant, const QgsPoint * > > > allPoints;

QgsFeatureRequest request = QgsFeatureRequest().setSubsetOfAttributes( requiredFields, source->fields() );
QgsFeatureIterator fit = source->getFeatures( request, QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks );
QgsFeature f;
const double totalPoints = source->featureCount() > 0 ? 100.0 / source->featureCount() : 0;
long currentPoint = 0;
feedback->setProgressText( QObject::tr( "Loading points…" ) );
while ( fit.nextFeature( f ) )
{
if ( feedback->isCanceled() )
{
break;
}
feedback->setProgress( currentPoint * totalPoints );

if ( f.hasGeometry() && ! f.geometry().isNull() )
{
expressionContext.setFeature( f );
const QVariant orderValue = expression.evaluate( &expressionContext );

// If no group field is specified then an invalid QVariant is returned, we're ok with that as our only group
const QVariant groupValue = f.attribute( groupField );
if ( ! allPoints.keys().contains( groupValue ) )
allPoints[ groupValue ] = QVector< QPair< QVariant, const QgsPoint * > >();
const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( f.geometry().constGet()->clone() );
allPoints[ groupValue ] << QPair< QVariant, const QgsPoint *>( orderValue, point );
}
++currentPoint;
}

int lineCount = 0;
currentPoint = 0;
QHashIterator< QVariant, QVector< QPair< QVariant, const QgsPoint * > > > hit( allPoints );
feedback->setProgressText( QObject::tr( "Creating lines…" ) );
while ( hit.hasNext() )
{
hit.next();
if ( feedback->isCanceled() )
{
break;
}
auto pairs = hit.value();

if ( naturalSort )
{
std::sort( pairs.begin(),
pairs.end(),
[&collator]( const QPair< const QVariant, const QgsPoint * > &pair1,
const QPair< const QVariant, const QgsPoint * > &pair2 )
{
return collator.compare( pair1.first.toString(), pair2.first.toString() ) < 0;
} );
}
else
{
std::sort( pairs.begin(),
pairs.end(),
[]( const QPair< const QVariant, const QgsPoint * > &pair1,
const QPair< const QVariant, const QgsPoint * > &pair2 )
{
return pair1.first < pair2.first;
} );
}


QVector<QgsPoint> linePoints;
for ( const auto pair : pairs )
{
if ( feedback->isCanceled() )
{
break;
}
feedback->setProgress( currentPoint * totalPoints );
linePoints.append( *pair.second );
++currentPoint;
}
if ( linePoints.size() < 2 )
{
feedback->pushInfo( QObject::tr( "Skipping line with group %1 : insufficient vertices" ).arg( hit.key().toString() ) );
continue;
}
if ( closeLines && linePoints.size() > 2 && linePoints.first() != linePoints.last() )
linePoints.append( linePoints.first() );

QgsFeature outputFeature;
QgsAttributes attrs;
attrs.append( hit.key() );
outputFeature.setGeometry( QgsGeometry::fromPolyline( linePoints ) );
outputFeature.setAttributes( attrs );
sink->addFeature( outputFeature, QgsFeatureSink::FastInsert );
++lineCount;
}


QVariantMap outputs;
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
outputs.insert( QStringLiteral( "NUM_LINESS" ), lineCount );
return outputs;
}

///@endcond
54 changes: 54 additions & 0 deletions src/analysis/processing/qgsalgorithmpointstolines.h
@@ -0,0 +1,54 @@
/***************************************************************************
qgsalgorithmpointstolines.h
---------------------
begin : November 2020
copyright : (C) 2020 by Stefanos Natsis
email : uclaros 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 QGSALGORITHMPOINTSTOLINES_H
#define QGSALGORITHMPOINTSTOLINES_H

#define SIP_NO_FILE

#include "qgis_sip.h"
#include "qgsprocessingalgorithm.h"

///@cond PRIVATE

/**
* Native points to lines algorithm.
*/
class QgsPointsToLinesAlgorithm : public QgsProcessingAlgorithm
{

public:

QgsPointsToLinesAlgorithm() = default;
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
QString name() const override;
QString displayName() const override;
QStringList tags() const override;
QString group() const override;
QString groupId() const override;
QString shortHelpString() const override;
QgsPointsToLinesAlgorithm *createInstance() const override SIP_FACTORY;

protected:

QVariantMap processAlgorithm( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
};

///@endcond PRIVATE

#endif // QGSALGORITHMPOINTSTOLINES_H
2 changes: 2 additions & 0 deletions src/analysis/processing/qgsnativealgorithms.cpp
Expand Up @@ -123,6 +123,7 @@
#include "qgsalgorithmpointtolayer.h"
#include "qgsalgorithmpointsalonggeometry.h"
#include "qgsalgorithmpointslayerfromtable.h"
#include "qgsalgorithmpointstolines.h"
#include "qgsalgorithmpoleofinaccessibility.h"
#include "qgsalgorithmpolygonize.h"
#include "qgsalgorithmprojectpointcartesian.h"
Expand Down Expand Up @@ -374,6 +375,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsPointToLayerAlgorithm() );
addAlgorithm( new QgsPointsAlongGeometryAlgorithm() );
addAlgorithm( new QgsPointsLayerFromTableAlgorithm() );
addAlgorithm( new QgsPointsToLinesAlgorithm() );
addAlgorithm( new QgsPoleOfInaccessibilityAlgorithm() );
addAlgorithm( new QgsPolygonizeAlgorithm() );
addAlgorithm( new QgsProjectPointCartesianAlgorithm() );
Expand Down

0 comments on commit 280d2e5

Please sign in to comment.