[processing] Port mean coordinates to c++

And remove final use of inefficient vector.extractPoints function
This commit is contained in:
Nyall Dawson 2017-09-23 09:25:18 +10:00
parent 10d6ef06e8
commit 0dd434c183
7 changed files with 246 additions and 216 deletions

View File

@ -294,14 +294,6 @@ qgis:listuniquevalues: >
qgis:meanandstandarddeviationplot:
qgis:meancoordinates: >
This algorithm computes a point layer with the center of mass of geometries in an input layer.
An attribute can be specified as containing weights to be applied to each feature when computing the center of mass.
If an attribute is selected in the <Unique ID field> parameters, features will be grouped according to values in this field. Instead of a single point with the center of mass of the whole layer, the output layer will contain a center of mass for the features in each category.
qgis:mergevectorlayers: >
This algorithm combines multiple vector layers of the same geometry type into a single one.

View File

@ -1,177 +0,0 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
MeanCoords.py
---------------------
Date : August 2012
Copyright : (C) 2012 by Victor Olaya
Email : volayaf 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. *
* *
***************************************************************************
"""
from builtins import str
__author__ = 'Victor Olaya'
__date__ = 'August 2012'
__copyright__ = '(C) 2012, Victor Olaya'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import os
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtCore import QVariant
from qgis.core import (QgsField,
QgsFeature,
QgsGeometry,
QgsPointXY,
QgsWkbTypes,
QgsFeatureRequest,
QgsFeatureSink,
QgsFields,
QgsProcessing,
QgsProcessingParameterFeatureSink,
QgsProcessingParameterField,
QgsProcessingParameterFeatureSource,
QgsProcessingException)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.tools import vector
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
class MeanCoords(QgisAlgorithm):
INPUT = 'INPUT'
WEIGHT = 'WEIGHT'
OUTPUT = 'OUTPUT'
UID = 'UID'
WEIGHT = 'WEIGHT'
def icon(self):
return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'mean.png'))
def group(self):
return self.tr('Vector analysis')
def __init__(self):
super().__init__()
def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
self.tr('Input layer')))
self.addParameter(QgsProcessingParameterField(self.WEIGHT, self.tr('Weight field'),
parentLayerParameterName=MeanCoords.INPUT,
type=QgsProcessingParameterField.Numeric,
optional=True))
self.addParameter(QgsProcessingParameterField(self.UID,
self.tr('Unique ID field'),
parentLayerParameterName=MeanCoords.INPUT,
optional=True))
self.addParameter(QgsProcessingParameterFeatureSink(MeanCoords.OUTPUT, self.tr('Mean coordinates'),
QgsProcessing.TypeVectorPoint))
def name(self):
return 'meancoordinates'
def displayName(self):
return self.tr('Mean coordinate(s)')
def processAlgorithm(self, parameters, context, feedback):
source = self.parameterAsSource(parameters, self.INPUT, context)
weight_field = self.parameterAsString(parameters, self.WEIGHT, context)
unique_field = self.parameterAsString(parameters, self.UID, context)
attributes = []
if not weight_field:
weight_index = -1
else:
weight_index = source.fields().lookupField(weight_field)
if weight_index >= 0:
attributes.append(weight_index)
if not unique_field:
unique_index = -1
else:
unique_index = source.fields().lookupField(unique_field)
if unique_index >= 0:
attributes.append(unique_index)
field_list = QgsFields()
field_list.append(QgsField('MEAN_X', QVariant.Double, '', 24, 15))
field_list.append(QgsField('MEAN_Y', QVariant.Double, '', 24, 15))
if unique_index >= 0:
field_list.append(QgsField('UID', QVariant.String, '', 255))
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
field_list, QgsWkbTypes.Point, source.sourceCrs())
features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes(attributes))
total = 100.0 / source.featureCount() if source.featureCount() else 0
means = {}
for current, feat in enumerate(features):
if feedback.isCanceled():
break
feedback.setProgress(int(current * total))
if unique_index == -1:
clazz = "Single class"
else:
clazz = str(feat.attributes()[unique_index]).strip()
if weight_index == -1:
weight = 1.00
else:
try:
weight = float(feat.attributes()[weight_index])
except:
weight = 1.00
if weight < 0:
raise QgsProcessingException(
self.tr('Negative weight value found. Please fix your data and try again.'))
if clazz not in means:
means[clazz] = (0, 0, 0)
(cx, cy, totalweight) = means[clazz]
geom = QgsGeometry(feat.geometry())
geom = vector.extractPoints(geom)
for i in geom:
cx += i.x() * weight
cy += i.y() * weight
totalweight += weight
means[clazz] = (cx, cy, totalweight)
current = 0
total = 100.0 / len(means) if means else 1
for (clazz, values) in list(means.items()):
if feedback.isCanceled():
break
outFeat = QgsFeature()
cx = values[0] / values[2]
cy = values[1] / values[2]
meanPoint = QgsPointXY(cx, cy)
outFeat.setGeometry(QgsGeometry.fromPoint(meanPoint))
attributes = [cx, cy]
if unique_index >= 0:
attributes.append(clazz)
outFeat.setAttributes(attributes)
sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
current += 1
feedback.setProgress(int(current * total))
return {self.OUTPUT: dest_id}

View File

@ -92,7 +92,6 @@ from .ImportIntoSpatialite import ImportIntoSpatialite
from .Intersection import Intersection
from .JoinAttributes import JoinAttributes
from .LinesToPolygons import LinesToPolygons
from .MeanCoords import MeanCoords
from .Merge import Merge
from .MinimumBoundingGeometry import MinimumBoundingGeometry
from .NearestNeighbourAnalysis import NearestNeighbourAnalysis
@ -226,7 +225,6 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
Intersection(),
JoinAttributes(),
LinesToPolygons(),
MeanCoords(),
Merge(),
MinimumBoundingGeometry(),
NearestNeighbourAnalysis(),

View File

@ -2665,7 +2665,7 @@ tests:
name: expected/points_along_lines.gml
type: vector
- algorithm: qgis:meancoordinates
- algorithm: native:meancoordinates
name: standard mean coordinates
params:
INPUT:
@ -2676,6 +2676,53 @@ tests:
name: expected/mean_coordinates.gml
type: vector
- algorithm: native:meancoordinates
name: Mean coordinates, multiple grouped
params:
INPUT:
name: points.gml
type: vector
UID: id2
results:
OUTPUT:
name: expected/mean_coordinates_unique_grouped.gml
type: vector
pk: id2
compare:
fields:
fid: skip
- algorithm: native:meancoordinates
name: Mean coordinates, unique field
params:
INPUT:
name: points.gml
type: vector
UID: id
results:
OUTPUT:
name: expected/mean_coordinates_unique_grouped_2.gml
type: vector
pk: id
compare:
fields:
fid: skip
- algorithm: native:meancoordinates
name: Mean coordinates, weighted
params:
INPUT:
name: points.gml
type: vector
WEIGHT: id
results:
OUTPUT:
name: expected/unique_coordinates_weight.gml
type: vector
compare:
fields:
fid: skip
- algorithm: native:collect
name: single part to multipart
params:

View File

@ -90,34 +90,6 @@ def values(source, *attributes):
return ret
def extractPoints(geom):
points = []
if geom.type() == QgsWkbTypes.PointGeometry:
if geom.isMultipart():
points = geom.asMultiPoint()
else:
points.append(geom.asPoint())
elif geom.type() == QgsWkbTypes.LineGeometry:
if geom.isMultipart():
lines = geom.asMultiPolyline()
for line in lines:
points.extend(line)
else:
points = geom.asPolyline()
elif geom.type() == QgsWkbTypes.PolygonGeometry:
if geom.isMultipart():
polygons = geom.asMultiPolygon()
for poly in polygons:
for line in poly:
points.extend(line)
else:
polygon = geom.asPolygon()
for line in polygon:
points.extend(line)
return points
def checkMinDistance(point, index, distance, points):
"""Check if distance from given point to all other points is greater
than given value.

View File

@ -87,6 +87,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsExtentToLayerAlgorithm() );
addAlgorithm( new QgsLineIntersectionAlgorithm() );
addAlgorithm( new QgsSplitWithLinesAlgorithm() );
addAlgorithm( new QgsMeanCoordinatesAlgorithm() );
}
void QgsSaveSelectedFeatures::initAlgorithm( const QVariantMap & )
@ -2412,6 +2413,179 @@ QVariantMap QgsSplitWithLinesAlgorithm::processAlgorithm( const QVariantMap &par
}
void QgsMeanCoordinatesAlgorithm::initAlgorithm( const QVariantMap & )
{
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ),
QObject::tr( "Input layer" ), QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );
addParameter( new QgsProcessingParameterField( QStringLiteral( "WEIGHT" ), QObject::tr( "Weight field" ),
QVariant(), QStringLiteral( "INPUT" ),
QgsProcessingParameterField::Numeric, false, true ) );
addParameter( new QgsProcessingParameterField( QStringLiteral( "UID" ),
QObject::tr( "Unique ID field" ), QVariant(),
QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any, false, true ) );
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Mean coordinates" ), QgsProcessing::TypeVectorPoint ) );
}
QString QgsMeanCoordinatesAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm computes a point layer with the center of mass of geometries in an input layer.\n\n"
"An attribute can be specified as containing weights to be applied to each feature when computing the center of mass.\n\n"
"If an attribute is selected in the <Unique ID field> parameter, features will be grouped according "
"to values in this field. Instead of a single point with the center of mass of the whole layer, "
"the output layer will contain a center of mass for the features in each category." );
}
QgsMeanCoordinatesAlgorithm *QgsMeanCoordinatesAlgorithm::createInstance() const
{
return new QgsMeanCoordinatesAlgorithm();
}
QVariantMap QgsMeanCoordinatesAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
if ( !source )
return QVariantMap();
QString weightFieldName = parameterAsString( parameters, QStringLiteral( "WEIGHT" ), context );
QString uniqueFieldName = parameterAsString( parameters, QStringLiteral( "UID" ), context );
QgsAttributeList attributes;
int weightIndex = -1;
if ( !weightFieldName.isEmpty() )
{
weightIndex = source->fields().lookupField( weightFieldName );
if ( weightIndex >= 0 )
attributes.append( weightIndex );
}
int uniqueFieldIndex = -1;
if ( !uniqueFieldName.isEmpty() )
{
uniqueFieldIndex = source->fields().lookupField( uniqueFieldName );
if ( uniqueFieldIndex >= 0 )
attributes.append( uniqueFieldIndex );
}
QgsFields fields;
fields.append( QgsField( QStringLiteral( "MEAN_X" ), QVariant::Double, QString(), 24, 15 ) );
fields.append( QgsField( QStringLiteral( "MEAN_Y" ), QVariant::Double, QString(), 24, 15 ) );
if ( uniqueFieldIndex >= 0 )
{
QgsField uniqueField = source->fields().at( uniqueFieldIndex );
fields.append( uniqueField );
}
QString dest;
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields,
QgsWkbTypes::Point, source->sourceCrs() ) );
if ( !sink )
return QVariantMap();
QgsFeatureIterator features = source->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( attributes ) );
double step = source->featureCount() > 0 ? 50.0 / source->featureCount() : 1;
int i = 0;
QgsFeature feat;
QHash< QVariant, QList< double > > means;
while ( features.nextFeature( feat ) )
{
i++;
if ( feedback->isCanceled() )
{
break;
}
feedback->setProgress( i * step );
if ( !feat.hasGeometry() )
continue;
QVariant featureClass;
if ( uniqueFieldIndex >= 0 )
{
featureClass = feat.attribute( uniqueFieldIndex );
}
else
{
featureClass = QStringLiteral( "#####singleclass#####" );
}
double weight = 1;
if ( weightIndex >= 0 )
{
bool ok = false;
weight = feat.attribute( weightIndex ).toDouble( &ok );
if ( !ok )
weight = 1.0;
}
if ( weight < 0 )
{
throw QgsProcessingException( QObject::tr( "Negative weight value found. Please fix your data and try again." ) );
}
QList< double > values = means.value( featureClass );
double cx = 0;
double cy = 0;
double totalWeight = 0;
if ( !values.empty() )
{
cx = values.at( 0 );
cy = values.at( 1 );
totalWeight = values.at( 2 );
}
QgsVertexId vid;
QgsPoint pt;
const QgsAbstractGeometry *g = feat.geometry().geometry();
// NOTE - should this be including the duplicate nodes for closed rings? currently it is,
// but I suspect that the expected behavior would be to NOT include these
while ( g->nextVertex( vid, pt ) )
{
cx += pt.x() * weight;
cy += pt.y() * weight;
totalWeight += weight;
}
means[featureClass] = QList< double >() << cx << cy << totalWeight;
}
i = 0;
step = !means.empty() ? 50.0 / means.count() : 1;
for ( auto it = means.constBegin(); it != means.constEnd(); ++it )
{
i++;
if ( feedback->isCanceled() )
{
break;
}
feedback->setProgress( 50 + i * step );
if ( qgsDoubleNear( it.value().at( 2 ), 0 ) )
continue;
QgsFeature outFeat;
double cx = it.value().at( 0 ) / it.value().at( 2 );
double cy = it.value().at( 1 ) / it.value().at( 2 );
QgsPointXY meanPoint( cx, cy );
outFeat.setGeometry( QgsGeometry::fromPoint( meanPoint ) );
QgsAttributes attributes;
attributes << cx << cy;
if ( uniqueFieldIndex >= 0 )
attributes.append( it.key() );
outFeat.setAttributes( attributes );
sink->addFeature( outFeat, QgsFeatureSink::FastInsert );
}
QVariantMap outputs;
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
return outputs;
}
///@endcond

View File

@ -786,6 +786,30 @@ class QgsSplitWithLinesAlgorithm : public QgsProcessingAlgorithm
};
/**
* Native mean coordinates algorithm.
*/
class QgsMeanCoordinatesAlgorithm : public QgsProcessingAlgorithm
{
public:
QgsMeanCoordinatesAlgorithm() = default;
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
QString name() const override { return QStringLiteral( "meancoordinates" ); }
QString displayName() const override { return QObject::tr( "Mean coordinate(s)" ); }
virtual QStringList tags() const override { return QObject::tr( "mean,average,coordinate" ).split( ',' ); }
QString group() const override { return QObject::tr( "Vector analysis" ); }
QString shortHelpString() const override;
QgsMeanCoordinatesAlgorithm *createInstance() const override SIP_FACTORY;
protected:
virtual QVariantMap processAlgorithm( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
};
///@endcond PRIVATE
#endif // QGSNATIVEALGORITHMS_H