mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-15 00:07:25 -05:00
Merge pull request #60945 from alexbruy/processing-export-geometry-info-cpp
port Add geometry attributes algorithm to C++
This commit is contained in:
commit
b5019cbfb0
@ -57,11 +57,6 @@ qgis:executesql: >
|
||||
|
||||
The result of the query will be added as a new layer.
|
||||
|
||||
qgis:exportaddgeometrycolumns: >
|
||||
This algorithm computes geometric properties of the features in a vector layer. It generates a new vector layer with the same content as the input one, but with additional attributes in its attributes table, containing geometric measurements.
|
||||
|
||||
Depending on the geometry type of the vector layer, the attributes added to the table will be different.
|
||||
|
||||
qgis:findprojection: >
|
||||
This algorithm allows creation of a shortlist of possible candidate coordinate reference systems for a layer with an unknown projection.
|
||||
|
||||
|
||||
@ -1,266 +0,0 @@
|
||||
"""
|
||||
***************************************************************************
|
||||
ExportGeometryInfo.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. *
|
||||
* *
|
||||
***************************************************************************
|
||||
"""
|
||||
|
||||
__author__ = "Victor Olaya"
|
||||
__date__ = "August 2012"
|
||||
__copyright__ = "(C) 2012, Victor Olaya"
|
||||
|
||||
import os
|
||||
import math
|
||||
|
||||
from qgis.PyQt.QtGui import QIcon
|
||||
from qgis.PyQt.QtCore import QMetaType
|
||||
|
||||
from qgis.core import (
|
||||
NULL,
|
||||
Qgis,
|
||||
QgsApplication,
|
||||
QgsCoordinateTransform,
|
||||
QgsField,
|
||||
QgsFields,
|
||||
QgsWkbTypes,
|
||||
QgsPointXY,
|
||||
QgsFeatureSink,
|
||||
QgsDistanceArea,
|
||||
QgsProcessingUtils,
|
||||
QgsProcessingException,
|
||||
QgsProcessingParameterFeatureSource,
|
||||
QgsProcessingParameterEnum,
|
||||
QgsProcessingParameterFeatureSink,
|
||||
QgsUnitTypes,
|
||||
)
|
||||
|
||||
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
|
||||
|
||||
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
|
||||
|
||||
|
||||
class ExportGeometryInfo(QgisAlgorithm):
|
||||
INPUT = "INPUT"
|
||||
METHOD = "CALC_METHOD"
|
||||
OUTPUT = "OUTPUT"
|
||||
|
||||
def icon(self):
|
||||
return QgsApplication.getThemeIcon(
|
||||
"/algorithms/mAlgorithmAddGeometryAttributes.svg"
|
||||
)
|
||||
|
||||
def svgIconPath(self):
|
||||
return QgsApplication.iconPath(
|
||||
"/algorithms/mAlgorithmAddGeometryAttributes.svg"
|
||||
)
|
||||
|
||||
def tags(self):
|
||||
return self.tr(
|
||||
"export,add,information,measurements,areas,lengths,perimeters,latitudes,longitudes,x,y,z,extract,points,lines,polygons,sinuosity,fields"
|
||||
).split(",")
|
||||
|
||||
def group(self):
|
||||
return self.tr("Vector geometry")
|
||||
|
||||
def groupId(self):
|
||||
return "vectorgeometry"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.export_z = False
|
||||
self.export_m = False
|
||||
self.distance_area = None
|
||||
self.distance_conversion_factor = 1
|
||||
self.area_conversion_factor = 1
|
||||
self.calc_methods = [
|
||||
self.tr("Layer CRS"),
|
||||
self.tr("Project CRS"),
|
||||
self.tr("Ellipsoidal"),
|
||||
]
|
||||
|
||||
def initAlgorithm(self, config=None):
|
||||
self.addParameter(
|
||||
QgsProcessingParameterFeatureSource(self.INPUT, self.tr("Input layer"))
|
||||
)
|
||||
self.addParameter(
|
||||
QgsProcessingParameterEnum(
|
||||
self.METHOD,
|
||||
self.tr("Calculate using"),
|
||||
options=self.calc_methods,
|
||||
defaultValue=0,
|
||||
)
|
||||
)
|
||||
self.addParameter(
|
||||
QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr("Added geom info"))
|
||||
)
|
||||
|
||||
def name(self):
|
||||
return "exportaddgeometrycolumns"
|
||||
|
||||
def displayName(self):
|
||||
return self.tr("Add geometry attributes")
|
||||
|
||||
def processAlgorithm(self, parameters, context, feedback):
|
||||
source = self.parameterAsSource(parameters, self.INPUT, context)
|
||||
if source is None:
|
||||
raise QgsProcessingException(
|
||||
self.invalidSourceError(parameters, self.INPUT)
|
||||
)
|
||||
|
||||
method = self.parameterAsEnum(parameters, self.METHOD, context)
|
||||
|
||||
wkb_type = source.wkbType()
|
||||
fields = source.fields()
|
||||
|
||||
new_fields = QgsFields()
|
||||
if (
|
||||
QgsWkbTypes.geometryType(wkb_type)
|
||||
== QgsWkbTypes.GeometryType.PolygonGeometry
|
||||
):
|
||||
new_fields.append(QgsField("area", QMetaType.Type.Double))
|
||||
new_fields.append(QgsField("perimeter", QMetaType.Type.Double))
|
||||
elif (
|
||||
QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.GeometryType.LineGeometry
|
||||
):
|
||||
new_fields.append(QgsField("length", QMetaType.Type.Double))
|
||||
if not QgsWkbTypes.isMultiType(source.wkbType()):
|
||||
new_fields.append(QgsField("straightdis", QMetaType.Type.Double))
|
||||
new_fields.append(QgsField("sinuosity", QMetaType.Type.Double))
|
||||
else:
|
||||
if QgsWkbTypes.isMultiType(source.wkbType()):
|
||||
new_fields.append(QgsField("numparts", QMetaType.Type.Int))
|
||||
else:
|
||||
new_fields.append(QgsField("xcoord", QMetaType.Type.Double))
|
||||
new_fields.append(QgsField("ycoord", QMetaType.Type.Double))
|
||||
if QgsWkbTypes.hasZ(source.wkbType()):
|
||||
self.export_z = True
|
||||
new_fields.append(QgsField("zcoord", QMetaType.Type.Double))
|
||||
if QgsWkbTypes.hasM(source.wkbType()):
|
||||
self.export_m = True
|
||||
new_fields.append(QgsField("mvalue", QMetaType.Type.Double))
|
||||
|
||||
fields = QgsProcessingUtils.combineFields(fields, new_fields)
|
||||
(sink, dest_id) = self.parameterAsSink(
|
||||
parameters, self.OUTPUT, context, fields, wkb_type, source.sourceCrs()
|
||||
)
|
||||
if sink is None:
|
||||
raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))
|
||||
|
||||
coordTransform = None
|
||||
|
||||
# Calculate with:
|
||||
# 0 - layer CRS
|
||||
# 1 - project CRS
|
||||
# 2 - ellipsoidal
|
||||
|
||||
self.distance_area = QgsDistanceArea()
|
||||
if method == 2:
|
||||
self.distance_area.setSourceCrs(
|
||||
source.sourceCrs(), context.transformContext()
|
||||
)
|
||||
self.distance_area.setEllipsoid(context.ellipsoid())
|
||||
|
||||
self.distance_conversion_factor = QgsUnitTypes.fromUnitToUnitFactor(
|
||||
self.distance_area.lengthUnits(), context.distanceUnit()
|
||||
)
|
||||
|
||||
self.area_conversion_factor = QgsUnitTypes.fromUnitToUnitFactor(
|
||||
self.distance_area.areaUnits(), context.areaUnit()
|
||||
)
|
||||
|
||||
elif method == 1:
|
||||
if not context.project():
|
||||
raise QgsProcessingException(
|
||||
self.tr("No project is available in this context")
|
||||
)
|
||||
coordTransform = QgsCoordinateTransform(
|
||||
source.sourceCrs(), context.project().crs(), context.project()
|
||||
)
|
||||
|
||||
features = source.getFeatures()
|
||||
total = 100.0 / source.featureCount() if source.featureCount() else 0
|
||||
for current, f in enumerate(features):
|
||||
if feedback.isCanceled():
|
||||
break
|
||||
|
||||
outFeat = f
|
||||
attrs = f.attributes()
|
||||
inGeom = f.geometry()
|
||||
if inGeom:
|
||||
if coordTransform is not None:
|
||||
inGeom.transform(coordTransform)
|
||||
|
||||
if inGeom.type() == QgsWkbTypes.GeometryType.PointGeometry:
|
||||
attrs.extend(self.point_attributes(inGeom))
|
||||
elif inGeom.type() == QgsWkbTypes.GeometryType.PolygonGeometry:
|
||||
attrs.extend(self.polygon_attributes(inGeom))
|
||||
else:
|
||||
attrs.extend(self.line_attributes(inGeom))
|
||||
|
||||
# ensure consistent count of attributes - otherwise null
|
||||
# geometry features will have incorrect attribute length
|
||||
# and provider may reject them
|
||||
if len(attrs) < len(fields):
|
||||
attrs += [NULL] * (len(fields) - len(attrs))
|
||||
|
||||
outFeat.setAttributes(attrs)
|
||||
sink.addFeature(outFeat, QgsFeatureSink.Flag.FastInsert)
|
||||
|
||||
feedback.setProgress(int(current * total))
|
||||
|
||||
sink.finalize()
|
||||
return {self.OUTPUT: dest_id}
|
||||
|
||||
def point_attributes(self, geometry):
|
||||
attrs = []
|
||||
if not geometry.isMultipart():
|
||||
pt = geometry.constGet()
|
||||
attrs.append(pt.x())
|
||||
attrs.append(pt.y())
|
||||
# add point z/m
|
||||
if self.export_z:
|
||||
attrs.append(pt.z())
|
||||
if self.export_m:
|
||||
attrs.append(pt.m())
|
||||
else:
|
||||
attrs = [geometry.constGet().numGeometries()]
|
||||
return attrs
|
||||
|
||||
def line_attributes(self, geometry):
|
||||
if geometry.isMultipart():
|
||||
return [self.distance_area.measureLength(geometry)]
|
||||
else:
|
||||
curve = geometry.constGet()
|
||||
p1 = curve.startPoint()
|
||||
p2 = curve.endPoint()
|
||||
straight_distance = (
|
||||
self.distance_conversion_factor
|
||||
* self.distance_area.measureLine(QgsPointXY(p1), QgsPointXY(p2))
|
||||
)
|
||||
sinuosity = curve.sinuosity()
|
||||
if math.isnan(sinuosity):
|
||||
sinuosity = NULL
|
||||
return [
|
||||
self.distance_conversion_factor
|
||||
* self.distance_area.measureLength(geometry),
|
||||
straight_distance,
|
||||
sinuosity,
|
||||
]
|
||||
|
||||
def polygon_attributes(self, geometry):
|
||||
area = self.area_conversion_factor * self.distance_area.measureArea(geometry)
|
||||
perimeter = (
|
||||
self.distance_conversion_factor
|
||||
* self.distance_area.measurePerimeter(geometry)
|
||||
)
|
||||
return [area, perimeter]
|
||||
@ -31,7 +31,6 @@ from .CheckValidity import CheckValidity
|
||||
from .Climb import Climb
|
||||
from .EliminateSelection import EliminateSelection
|
||||
from .ExecuteSQL import ExecuteSQL
|
||||
from .ExportGeometryInfo import ExportGeometryInfo
|
||||
from .FieldPyculator import FieldsPyculator
|
||||
from .FindProjection import FindProjection
|
||||
from .GeometryConvert import GeometryConvert
|
||||
@ -95,7 +94,6 @@ class QgisAlgorithmProvider(QgsProcessingProvider):
|
||||
Climb(),
|
||||
EliminateSelection(),
|
||||
ExecuteSQL(),
|
||||
ExportGeometryInfo(),
|
||||
FieldsPyculator(),
|
||||
FindProjection(),
|
||||
GeometryConvert(),
|
||||
|
||||
@ -100,7 +100,7 @@ def initMenusAndToolbars():
|
||||
defaultMenuEntries.update(
|
||||
{
|
||||
"qgis:checkvalidity": geometryToolsMenu,
|
||||
"qgis:exportaddgeometrycolumns": geometryToolsMenu,
|
||||
"native:exportaddgeometrycolumns": geometryToolsMenu,
|
||||
"native:centroids": geometryToolsMenu,
|
||||
"native:delaunaytriangulation": geometryToolsMenu,
|
||||
"native:voronoipolygons": geometryToolsMenu,
|
||||
|
||||
@ -2242,7 +2242,7 @@ tests:
|
||||
name: expected/smoothed_lines_max_angle.gml
|
||||
type: vector
|
||||
|
||||
- algorithm: qgis:exportaddgeometrycolumns
|
||||
- algorithm: native:exportaddgeometrycolumns
|
||||
name: Add Geometry PointZ
|
||||
params:
|
||||
CALC_METHOD: '0'
|
||||
@ -2254,7 +2254,7 @@ tests:
|
||||
name: expected/add_geometry_pointz.gml
|
||||
type: vector
|
||||
|
||||
- algorithm: qgis:exportaddgeometrycolumns
|
||||
- algorithm: native:exportaddgeometrycolumns
|
||||
name: Export line info
|
||||
params:
|
||||
CALC_METHOD: 0
|
||||
@ -2266,7 +2266,7 @@ tests:
|
||||
name: expected/export_line_info.gml
|
||||
type: vector
|
||||
|
||||
- algorithm: qgis:exportaddgeometrycolumns
|
||||
- algorithm: native:exportaddgeometrycolumns
|
||||
name: Export multiline info
|
||||
params:
|
||||
CALC_METHOD: 0
|
||||
@ -2278,7 +2278,7 @@ tests:
|
||||
name: expected/export_multiline_info.gml
|
||||
type: vector
|
||||
|
||||
- algorithm: qgis:exportaddgeometrycolumns
|
||||
- algorithm: native:exportaddgeometrycolumns
|
||||
name: Export multipoint info (GEOS < 3.9)
|
||||
condition:
|
||||
geos:
|
||||
@ -2293,7 +2293,7 @@ tests:
|
||||
name: expected/add_geometry_info_multipoint.gml
|
||||
type: vector
|
||||
|
||||
- algorithm: qgis:exportaddgeometrycolumns
|
||||
- algorithm: native:exportaddgeometrycolumns
|
||||
name: Export multipoint info (GEOS >= 3.9)
|
||||
condition:
|
||||
geos:
|
||||
|
||||
@ -100,6 +100,7 @@ set(QGIS_ANALYSIS_SRCS
|
||||
processing/qgsalgorithmexecutespatialitequeryregistered.cpp
|
||||
processing/qgsalgorithmexplode.cpp
|
||||
processing/qgsalgorithmexplodehstore.cpp
|
||||
processing/qgsalgorithmexportgeometryattributes.cpp
|
||||
processing/qgsalgorithmexportlayersinformation.cpp
|
||||
processing/qgsalgorithmexportmesh.cpp
|
||||
processing/qgsalgorithmexporttopostgresql.cpp
|
||||
|
||||
288
src/analysis/processing/qgsalgorithmexportgeometryattributes.cpp
Normal file
288
src/analysis/processing/qgsalgorithmexportgeometryattributes.cpp
Normal file
@ -0,0 +1,288 @@
|
||||
/***************************************************************************
|
||||
qgsalgorithmexportgeometryattributes.cpp
|
||||
---------------------
|
||||
begin : February 2025
|
||||
copyright : (C) 2025 by Alexander Bruy
|
||||
email : alexander dot bruy 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 "qgsalgorithmexportgeometryattributes.h"
|
||||
#include "qgsunittypes.h"
|
||||
#include "qgsgeometrycollection.h"
|
||||
#include "qgscurve.h"
|
||||
|
||||
///@cond PRIVATE
|
||||
|
||||
QString QgsExportGeometryAttributesAlgorithm::name() const
|
||||
{
|
||||
return QStringLiteral( "exportaddgeometrycolumns" );
|
||||
}
|
||||
|
||||
QString QgsExportGeometryAttributesAlgorithm::displayName() const
|
||||
{
|
||||
return QObject::tr( "Add geometry attributes" );
|
||||
}
|
||||
|
||||
QStringList QgsExportGeometryAttributesAlgorithm::tags() const
|
||||
{
|
||||
return QObject::tr( "export,add,information,measurements,areas,lengths,perimeters,latitudes,longitudes,x,y,z,extract,points,lines,polygons,sinuosity,fields" ).split( ',' );
|
||||
}
|
||||
|
||||
QString QgsExportGeometryAttributesAlgorithm::group() const
|
||||
{
|
||||
return QObject::tr( "Vector geometry" );
|
||||
}
|
||||
|
||||
QString QgsExportGeometryAttributesAlgorithm::groupId() const
|
||||
{
|
||||
return QStringLiteral( "vectorgeometry" );
|
||||
}
|
||||
|
||||
QString QgsExportGeometryAttributesAlgorithm::shortHelpString() const
|
||||
{
|
||||
return QObject::tr( "Computes geometric properties of the features in a vector layer. Algorithm generates a new "
|
||||
"vector layer with the same content as the input one, but with additional attributes in its "
|
||||
"attributes table, containing geometric measurements.\n\n"
|
||||
"Depending on the geometry type of the vector layer, the attributes added to the table will "
|
||||
"be different." );
|
||||
}
|
||||
|
||||
QgsExportGeometryAttributesAlgorithm *QgsExportGeometryAttributesAlgorithm::createInstance() const
|
||||
{
|
||||
return new QgsExportGeometryAttributesAlgorithm();
|
||||
}
|
||||
|
||||
void QgsExportGeometryAttributesAlgorithm::initAlgorithm( const QVariantMap & )
|
||||
{
|
||||
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorAnyGeometry ) ) );
|
||||
|
||||
const QStringList options = QStringList()
|
||||
<< QObject::tr( "Cartesian Calculations in Layer's CRS" )
|
||||
<< QObject::tr( "Cartesian Calculations in Project's CRS" )
|
||||
<< QObject::tr( "Ellipsoidal Calculations" );
|
||||
addParameter( new QgsProcessingParameterEnum( QStringLiteral( "METHOD" ), QObject::tr( "Calculate using" ), options, false, 0 ) );
|
||||
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Added geometry info" ) ) );
|
||||
}
|
||||
|
||||
bool QgsExportGeometryAttributesAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * )
|
||||
{
|
||||
Q_UNUSED( parameters );
|
||||
|
||||
mProjectCrs = context.project()->crs();
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariantMap QgsExportGeometryAttributesAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
||||
{
|
||||
std::unique_ptr<QgsProcessingFeatureSource> source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
|
||||
if ( !source )
|
||||
throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
|
||||
|
||||
const int method = parameterAsEnum( parameters, QStringLiteral( "METHOD" ), context );
|
||||
|
||||
const Qgis::WkbType wkbType = source->wkbType();
|
||||
QgsFields fields = source->fields();
|
||||
QgsFields newFields;
|
||||
|
||||
bool exportZ = false;
|
||||
bool exportM = false;
|
||||
if ( QgsWkbTypes::geometryType( wkbType ) == Qgis::GeometryType::Polygon )
|
||||
{
|
||||
newFields.append( QgsField( QStringLiteral( "area" ), QMetaType::Type::Double ) );
|
||||
newFields.append( QgsField( QStringLiteral( "perimeter" ), QMetaType::Type::Double ) );
|
||||
}
|
||||
else if ( QgsWkbTypes::geometryType( wkbType ) == Qgis::GeometryType::Line )
|
||||
{
|
||||
newFields.append( QgsField( QStringLiteral( "length" ), QMetaType::Type::Double ) );
|
||||
if ( !QgsWkbTypes::isMultiType( wkbType ) )
|
||||
{
|
||||
newFields.append( QgsField( QStringLiteral( "straightdis" ), QMetaType::Type::Double ) );
|
||||
newFields.append( QgsField( QStringLiteral( "sinuosity" ), QMetaType::Type::Double ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( QgsWkbTypes::isMultiType( wkbType ) )
|
||||
{
|
||||
newFields.append( QgsField( QStringLiteral( "numparts" ), QMetaType::Type::Int ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
newFields.append( QgsField( QStringLiteral( "xcoord" ), QMetaType::Type::Double ) );
|
||||
newFields.append( QgsField( QStringLiteral( "ycoord" ), QMetaType::Type::Double ) );
|
||||
if ( QgsWkbTypes::hasZ( wkbType ) )
|
||||
{
|
||||
newFields.append( QgsField( QStringLiteral( "zcoord" ), QMetaType::Type::Double ) );
|
||||
exportZ = true;
|
||||
}
|
||||
if ( QgsWkbTypes::hasM( wkbType ) )
|
||||
{
|
||||
newFields.append( QgsField( QStringLiteral( "mvalue" ), QMetaType::Type::Double ) );
|
||||
exportM = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fields = QgsProcessingUtils::combineFields( fields, newFields );
|
||||
|
||||
QString dest;
|
||||
std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields, wkbType, source->sourceCrs() ) );
|
||||
if ( !sink )
|
||||
{
|
||||
throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
|
||||
}
|
||||
|
||||
QgsCoordinateTransform transform;
|
||||
mDa = QgsDistanceArea();
|
||||
|
||||
if ( method == 2 )
|
||||
{
|
||||
mDa.setSourceCrs( source->sourceCrs(), context.transformContext() );
|
||||
mDa.setEllipsoid( context.ellipsoid() );
|
||||
mDistanceConversionFactor = QgsUnitTypes::fromUnitToUnitFactor( mDa.lengthUnits(), context.distanceUnit() );
|
||||
mAreaConversionFactor = QgsUnitTypes::fromUnitToUnitFactor( mDa.areaUnits(), context.areaUnit() );
|
||||
}
|
||||
else if ( method == 1 )
|
||||
{
|
||||
if ( !context.project() )
|
||||
{
|
||||
throw QgsProcessingException( QObject::tr( "No project is available in this context" ) );
|
||||
}
|
||||
transform = QgsCoordinateTransform( source->sourceCrs(), mProjectCrs, context.transformContext() );
|
||||
}
|
||||
|
||||
QgsFeatureIterator it = source->getFeatures();
|
||||
const double step = source->featureCount() > 0 ? 100.0 / source->featureCount() : 0;
|
||||
long i = 0;
|
||||
QgsFeature f;
|
||||
|
||||
while ( it.nextFeature( f ) )
|
||||
{
|
||||
if ( feedback->isCanceled() )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
QgsFeature outputFeature( f );
|
||||
QgsAttributes attrs = f.attributes();
|
||||
QgsGeometry geom = f.geometry();
|
||||
|
||||
if ( !geom.isNull() )
|
||||
{
|
||||
if ( transform.isValid() )
|
||||
{
|
||||
try
|
||||
{
|
||||
geom.transform( transform );
|
||||
}
|
||||
catch ( QgsCsException &e )
|
||||
{
|
||||
throw QgsProcessingException( QObject::tr( "Could not transform feature to project's CRS: %1" ).arg( e.what() ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( geom.type() == Qgis::GeometryType::Point )
|
||||
{
|
||||
attrs << pointAttributes( geom, exportZ, exportM );
|
||||
}
|
||||
else if ( geom.type() == Qgis::GeometryType::Polygon )
|
||||
{
|
||||
attrs << polygonAttributes( geom );
|
||||
}
|
||||
else
|
||||
{
|
||||
attrs << lineAttributes( geom );
|
||||
}
|
||||
}
|
||||
|
||||
// ensure consistent count of attributes - otherwise null geometry features will have incorrect attribute
|
||||
// length and provider may reject them
|
||||
while ( attrs.size() < fields.size() )
|
||||
{
|
||||
attrs.append( QVariant() );
|
||||
}
|
||||
|
||||
outputFeature.setAttributes( attrs );
|
||||
if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
|
||||
{
|
||||
throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
|
||||
}
|
||||
|
||||
i++;
|
||||
feedback->setProgress( i * step );
|
||||
}
|
||||
|
||||
sink->finalize();
|
||||
|
||||
QVariantMap results;
|
||||
results.insert( QStringLiteral( "OUTPUT" ), dest );
|
||||
return results;
|
||||
}
|
||||
|
||||
QgsAttributes QgsExportGeometryAttributesAlgorithm::pointAttributes( const QgsGeometry &geom, const bool exportZ, const bool exportM )
|
||||
{
|
||||
QgsAttributes attrs;
|
||||
|
||||
if ( !geom.isMultipart() )
|
||||
{
|
||||
auto point = qgsgeometry_cast<const QgsPoint *>( geom.constGet() );
|
||||
attrs.append( point->x() );
|
||||
attrs.append( point->y() );
|
||||
// add point Z/M
|
||||
if ( exportZ )
|
||||
{
|
||||
attrs.append( point->z() );
|
||||
}
|
||||
if ( exportM )
|
||||
{
|
||||
attrs.append( point->m() );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
attrs.append( qgsgeometry_cast<const QgsGeometryCollection *>( geom.constGet() )->numGeometries() );
|
||||
}
|
||||
return attrs;
|
||||
}
|
||||
|
||||
QgsAttributes QgsExportGeometryAttributesAlgorithm::lineAttributes( const QgsGeometry &geom )
|
||||
{
|
||||
QgsAttributes attrs;
|
||||
|
||||
if ( geom.isMultipart() )
|
||||
{
|
||||
attrs.append( mDistanceConversionFactor * mDa.measureLength( geom ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
auto curve = qgsgeometry_cast<const QgsCurve *>( geom.constGet() );
|
||||
const QgsPoint p1 = curve->startPoint();
|
||||
const QgsPoint p2 = curve->endPoint();
|
||||
const double straightDistance = mDistanceConversionFactor * mDa.measureLine( QgsPointXY( p1 ), QgsPointXY( p2 ) );
|
||||
const double sinuosity = curve->sinuosity();
|
||||
attrs.append( mDistanceConversionFactor * mDa.measureLength( geom ) );
|
||||
attrs.append( straightDistance );
|
||||
attrs.append( std::isnan( sinuosity ) ? QVariant() : sinuosity );
|
||||
}
|
||||
|
||||
return attrs;
|
||||
}
|
||||
|
||||
QgsAttributes QgsExportGeometryAttributesAlgorithm::polygonAttributes( const QgsGeometry &geom )
|
||||
{
|
||||
const double area = mAreaConversionFactor * mDa.measureArea( geom );
|
||||
const double perimeter = mDistanceConversionFactor * mDa.measurePerimeter( geom );
|
||||
|
||||
return QgsAttributes() << area << perimeter;
|
||||
}
|
||||
|
||||
///@endcond
|
||||
@ -0,0 +1,65 @@
|
||||
/***************************************************************************
|
||||
qgsalgorithmexportgeometryattributes.h
|
||||
---------------------
|
||||
begin : February 2025
|
||||
copyright : (C) 2025 by Alexander Bruy
|
||||
email : alexander dot bruy 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 QGSALGORITHMEXPORTGEOMETRYATTRIBUTES_H
|
||||
#define QGSALGORITHMEXPORTGEOMETRYATTRIBUTES_H
|
||||
|
||||
#define SIP_NO_FILE
|
||||
|
||||
#include "qgis_sip.h"
|
||||
#include "qgsprocessingalgorithm.h"
|
||||
#include "qgsapplication.h"
|
||||
|
||||
///@cond PRIVATE
|
||||
|
||||
/**
|
||||
* Native export geometry attributes algorithm.
|
||||
*/
|
||||
class QgsExportGeometryAttributesAlgorithm : public QgsProcessingAlgorithm
|
||||
{
|
||||
public:
|
||||
QgsExportGeometryAttributesAlgorithm() = default;
|
||||
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
|
||||
QIcon icon() const override { return QgsApplication::getThemeIcon( QStringLiteral( "/algorithms/mAlgorithmAddGeometryAttributes.svg" ) ); }
|
||||
QString svgIconPath() const override { return QgsApplication::iconPath( QStringLiteral( "/algorithms/mAlgorithmAddGeometryAttributes.svg" ) ); }
|
||||
QString name() const override;
|
||||
QString displayName() const override;
|
||||
QStringList tags() const override;
|
||||
QString group() const override;
|
||||
QString groupId() const override;
|
||||
QString shortHelpString() const override;
|
||||
QgsExportGeometryAttributesAlgorithm *createInstance() const override SIP_FACTORY;
|
||||
|
||||
protected:
|
||||
bool prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
|
||||
QVariantMap processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
|
||||
|
||||
private:
|
||||
QgsAttributes pointAttributes( const QgsGeometry &geom, const bool exportZ, const bool exportM );
|
||||
QgsAttributes polygonAttributes( const QgsGeometry &geom );
|
||||
QgsAttributes lineAttributes( const QgsGeometry &geom );
|
||||
|
||||
QgsDistanceArea mDa;
|
||||
QgsCoordinateReferenceSystem mProjectCrs;
|
||||
double mDistanceConversionFactor = 1;
|
||||
double mAreaConversionFactor = 1;
|
||||
};
|
||||
|
||||
///@endcond PRIVATE
|
||||
|
||||
#endif // QGSALGORITHMEXPORTGEOMETRYATTRIBUTES_H
|
||||
@ -83,6 +83,7 @@
|
||||
#include "qgsalgorithmexporttospreadsheet.h"
|
||||
#include "qgsalgorithmexplode.h"
|
||||
#include "qgsalgorithmexplodehstore.h"
|
||||
#include "qgsalgorithmexportgeometryattributes.h"
|
||||
#include "qgsalgorithmexportlayersinformation.h"
|
||||
#include "qgsalgorithmexporttopostgresql.h"
|
||||
#include "qgsalgorithmextendlines.h"
|
||||
@ -369,6 +370,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
|
||||
addAlgorithm( new QgsExecuteSpatialiteQueryAlgorithm() );
|
||||
addAlgorithm( new QgsExplodeAlgorithm() );
|
||||
addAlgorithm( new QgsExplodeHstoreAlgorithm() );
|
||||
addAlgorithm( new QgsExportGeometryAttributesAlgorithm() );
|
||||
addAlgorithm( new QgsExportLayersInformationAlgorithm() );
|
||||
addAlgorithm( new QgsExportLayerMetadataAlgorithm() );
|
||||
addAlgorithm( new QgsExportMeshVerticesAlgorithm );
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user