mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-13 00:03:09 -04:00
[processing] Port mean coordinates to c++
And remove final use of inefficient vector.extractPoints function
This commit is contained in:
parent
10d6ef06e8
commit
0dd434c183
@ -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.
|
||||
|
||||
|
@ -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}
|
@ -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(),
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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 ¶meters, 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
|
||||
|
||||
|
||||
|
@ -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 ¶meters,
|
||||
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
|
||||
|
||||
};
|
||||
|
||||
///@endcond PRIVATE
|
||||
|
||||
#endif // QGSNATIVEALGORITHMS_H
|
||||
|
Loading…
x
Reference in New Issue
Block a user