diff --git a/python/plugins/processing/tests/testdata/expected/split_lines_by_length.gml b/python/plugins/processing/tests/testdata/expected/split_lines_by_length.gml new file mode 100644 index 00000000000..1fc2c849979 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/split_lines_by_length.gml @@ -0,0 +1,83 @@ + + + + + -1-3 + 115 + + + + + + 6,2 8.2,2.0 + + + + + 8.2,2.0 9,2 9,3 9.28284271247462,3.28284271247462 + + + + + 9.28284271247462,3.28284271247462 10.838477631085,4.83847763108502 + + + + + 10.838477631085,4.83847763108502 11,5 + + + + + -1,-1 1,-1 + + + + + 2,0 2,2 2.2,2.0 + + + + + 2.2,2.0 3,2 3,3 + + + + + 3,1 5,1 + + + + + 7,-3 9.2,-3.0 + + + + + 9.2,-3.0 10,-3 + + + + + 6,-3 7.55563491861041,-1.4443650813896 + + + + + 7.55563491861041,-1.4443650813896 9.11126983722081,0.111269837220809 + + + + + 9.11126983722081,0.111269837220809 10,1 + + + + + + + diff --git a/python/plugins/processing/tests/testdata/expected/split_lines_by_length.xsd b/python/plugins/processing/tests/testdata/expected/split_lines_by_length.xsd new file mode 100644 index 00000000000..93aaed6f635 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/split_lines_by_length.xsd @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/plugins/processing/tests/testdata/expected/split_linez_by_length.dbf b/python/plugins/processing/tests/testdata/expected/split_linez_by_length.dbf new file mode 100644 index 00000000000..41b6f927905 Binary files /dev/null and b/python/plugins/processing/tests/testdata/expected/split_linez_by_length.dbf differ diff --git a/python/plugins/processing/tests/testdata/expected/split_linez_by_length.prj b/python/plugins/processing/tests/testdata/expected/split_linez_by_length.prj new file mode 100644 index 00000000000..a30c00a55de --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/split_linez_by_length.prj @@ -0,0 +1 @@ +GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file diff --git a/python/plugins/processing/tests/testdata/expected/split_linez_by_length.qpj b/python/plugins/processing/tests/testdata/expected/split_linez_by_length.qpj new file mode 100644 index 00000000000..5fbc831e743 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/split_linez_by_length.qpj @@ -0,0 +1 @@ +GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] diff --git a/python/plugins/processing/tests/testdata/expected/split_linez_by_length.shp b/python/plugins/processing/tests/testdata/expected/split_linez_by_length.shp new file mode 100644 index 00000000000..e6cc5496886 Binary files /dev/null and b/python/plugins/processing/tests/testdata/expected/split_linez_by_length.shp differ diff --git a/python/plugins/processing/tests/testdata/expected/split_linez_by_length.shx b/python/plugins/processing/tests/testdata/expected/split_linez_by_length.shx new file mode 100644 index 00000000000..001e17fa573 Binary files /dev/null and b/python/plugins/processing/tests/testdata/expected/split_linez_by_length.shx differ diff --git a/python/plugins/processing/tests/testdata/expected/split_multiline_by_length.gml b/python/plugins/processing/tests/testdata/expected/split_multiline_by_length.gml new file mode 100644 index 00000000000..a4a9e778318 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/split_multiline_by_length.gml @@ -0,0 +1,98 @@ + + + + + -1-1 + 5.580422264875244.119769673704415 + + + + + + -1,-1 0.1,-1.0 + + + + + 0.1,-1.0 1,-1 + + + + + 3,1 4.1,1.0 + + + + + 4.1,1.0 5,1 + + + + + 5.02418426103647,2.4147792706334 5.00538358886158,1.3149399484023 + + + + + 5.00538358886158,1.3149399484023 5,1 + + + + + + + + + 2,0 2.0,1.1 + + + + + 2.0,1.1 2,2 2.2,2.0 + + + + + 2.2,2.0 3,2 3.0,2.3 + + + + + 3.0,2.3 3,3 + + + + + 2.94433781190019,4.04721689059501 4.04388044198829,4.07893446646294 + + + + + 4.04388044198829,4.07893446646294 5.1434230720764,4.11065204233086 + + + + + 5.1434230720764,4.11065204233086 5.4595009596929,4.11976967370441 + + + + + 3,3 4.09976658596272,2.97734042366024 + + + + + 4.09976658596272,2.97734042366024 5.19953317192545,2.95468084732049 + + + + + 5.19953317192545,2.95468084732049 5.58042226487524,2.9468330134357 + + + diff --git a/python/plugins/processing/tests/testdata/expected/split_multiline_by_length.xsd b/python/plugins/processing/tests/testdata/expected/split_multiline_by_length.xsd new file mode 100644 index 00000000000..e8b95138b8b --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/split_multiline_by_length.xsd @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 7ebacd0ba6d..1ef6a52172c 100755 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -6360,4 +6360,41 @@ tests: name: expected/force_rhr_multipolys.gml type: vector + - algorithm: native:splitlinesbylength + name: Split multilines by length + params: + INPUT: + name: multilines.gml|layername=multilines + type: vector + LENGTH: 1.1 + results: + OUTPUT: + name: expected/split_multiline_by_length.gml + type: vector + + - algorithm: native:splitlinesbylength + name: Split lines by length + params: + INPUT: + name: lines.gml|layername=lines + type: vector + LENGTH: 2.2 + results: + OUTPUT: + name: expected/split_lines_by_length.gml + type: vector + + - algorithm: native:splitlinesbylength + name: Split linesz by length + params: + INPUT: + name: lines_z.shp + type: vector + LENGTH: 3.1 + results: + OUTPUT: + name: expected/split_linez_by_length.shp + type: vector + + # See ../README.md for a description of the file format diff --git a/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt index de4b521655a..6937ae26fac 100644 --- a/src/analysis/CMakeLists.txt +++ b/src/analysis/CMakeLists.txt @@ -92,6 +92,7 @@ SET(QGIS_ANALYSIS_SRCS processing/qgsalgorithmsimplify.cpp processing/qgsalgorithmsmooth.cpp processing/qgsalgorithmsnaptogrid.cpp + processing/qgsalgorithmsplitlinesbylength.cpp processing/qgsalgorithmsplitwithlines.cpp processing/qgsalgorithmstringconcatenation.cpp processing/qgsalgorithmswapxy.cpp diff --git a/src/analysis/processing/qgsalgorithmsplitlinesbylength.cpp b/src/analysis/processing/qgsalgorithmsplitlinesbylength.cpp new file mode 100644 index 00000000000..8ff44b18472 --- /dev/null +++ b/src/analysis/processing/qgsalgorithmsplitlinesbylength.cpp @@ -0,0 +1,151 @@ +/*************************************************************************** + qgsalgorithmsplitlinesbylength.cpp + --------------------- + begin : December 2018 + copyright : (C) 2018 by Nyall Dawson + email : nyall dot dawson 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 "qgsalgorithmsplitlinesbylength.h" +#include "qgscurve.h" +#include "qgslinestring.h" +#include "qgscircularstring.h" +#include "qgscompoundcurve.h" +#include "qgsgeometrycollection.h" + +///@cond PRIVATE + +QString QgsSplitLinesByLengthAlgorithm::name() const +{ + return QStringLiteral( "splitlinesbylength" ); +} + +QString QgsSplitLinesByLengthAlgorithm::displayName() const +{ + return QObject::tr( "Split lines by maximum length" ); +} + +QStringList QgsSplitLinesByLengthAlgorithm::tags() const +{ + return QObject::tr( "segments,parts,distance,cut,chop" ).split( ',' ); +} + +QString QgsSplitLinesByLengthAlgorithm::group() const +{ + return QObject::tr( "Vector geometry" ); +} + +QString QgsSplitLinesByLengthAlgorithm::groupId() const +{ + return QStringLiteral( "vectorgeometry" ); +} + +QString QgsSplitLinesByLengthAlgorithm::shortHelpString() const +{ + return QObject::tr( "This algorithm takes a line (or curve) layer and splits each feature into multiple parts, " + "where each part is of a specified maximum length.\n\n" + "Z and M values at the start and end of the new line substrings are linearly interpolated from existing values." ); +} + +QString QgsSplitLinesByLengthAlgorithm::shortDescription() const +{ + return QObject::tr( "Splits lines into parts which are no longer than a specified length." ); +} + +QList QgsSplitLinesByLengthAlgorithm::inputLayerTypes() const +{ + return QList() << QgsProcessing::TypeVectorLine; +} + +QgsProcessing::SourceType QgsSplitLinesByLengthAlgorithm::outputLayerType() const +{ + return QgsProcessing::TypeVectorLine; +} + +QgsSplitLinesByLengthAlgorithm *QgsSplitLinesByLengthAlgorithm::createInstance() const +{ + return new QgsSplitLinesByLengthAlgorithm(); +} + +void QgsSplitLinesByLengthAlgorithm::initParameters( const QVariantMap & ) +{ + std::unique_ptr< QgsProcessingParameterDistance > length = qgis::make_unique< QgsProcessingParameterDistance >( QStringLiteral( "LENGTH" ), + QObject::tr( "Maximum line length" ), 10, QStringLiteral( "INPUT" ), false, 0 ); + length->setIsDynamic( true ); + length->setDynamicPropertyDefinition( QgsPropertyDefinition( QStringLiteral( "LENGTH" ), QObject::tr( "Maximum length" ), QgsPropertyDefinition::DoublePositive ) ); + length->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) ); + addParameter( length.release() ); +} + +bool QgsSplitLinesByLengthAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) +{ + mLength = parameterAsDouble( parameters, QStringLiteral( "LENGTH" ), context ); + mDynamicLength = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "LENGTH" ) ); + if ( mDynamicLength ) + mLengthProperty = parameters.value( QStringLiteral( "LENGTH" ) ).value< QgsProperty >(); + + return true; +} + +QString QgsSplitLinesByLengthAlgorithm::outputName() const +{ + return QObject::tr( "Split" ); +} + +QgsWkbTypes::Type QgsSplitLinesByLengthAlgorithm::outputWkbType( QgsWkbTypes::Type inputWkbType ) const +{ + return QgsWkbTypes::singleType( inputWkbType ); +} + +QgsFeatureList QgsSplitLinesByLengthAlgorithm::processFeature( const QgsFeature &f, QgsProcessingContext &context, QgsProcessingFeedback * ) +{ + if ( !f.hasGeometry() ) + { + return QgsFeatureList() << f; + } + else + { + double distance = mLength; + if ( mDynamicLength ) + distance = mLengthProperty.valueAsDouble( context.expressionContext(), distance ); + + QgsFeature outputFeature; + outputFeature.setAttributes( f.attributes() ); + QgsFeatureList features; + const QgsGeometry inputGeom = f.geometry(); + for ( auto it = inputGeom.const_parts_begin(); it != inputGeom.const_parts_end(); ++it ) + { + const QgsCurve *part = qgsgeometry_cast< const QgsCurve * >( *it ); + if ( !part ) + continue; + + double start = 0.0; + double end = distance; + const double length = part->length(); + while ( start < length ) + { + outputFeature.setGeometry( QgsGeometry( part->curveSubstring( start, end ) ) ); + start += distance; + end += distance; + features << outputFeature; + } + + } + return features; + } +} + + +///@endcond + + + diff --git a/src/analysis/processing/qgsalgorithmsplitlinesbylength.h b/src/analysis/processing/qgsalgorithmsplitlinesbylength.h new file mode 100644 index 00000000000..97384a29039 --- /dev/null +++ b/src/analysis/processing/qgsalgorithmsplitlinesbylength.h @@ -0,0 +1,67 @@ +/*************************************************************************** + qgsalgorithmsplitlinesbylength.h + --------------------- + begin : December 2018 + copyright : (C) 2018 by Nyall Dawson + email : nyall dot dawson 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 QGSALGORITHMSPLITLINESBYLENGTH_H +#define QGSALGORITHMSPLITLINESBYLENGTH_H + +#define SIP_NO_FILE + +#include "qgis.h" +#include "qgsprocessingalgorithm.h" + +///@cond PRIVATE + +/** + * Native split lines by maximum length algorithm. + */ +class QgsSplitLinesByLengthAlgorithm : public QgsProcessingFeatureBasedAlgorithm +{ + + public: + + QgsSplitLinesByLengthAlgorithm() = default; + QString name() const override; + QString displayName() const override; + QStringList tags() const override; + QString group() const override; + QString groupId() const override; + QString shortHelpString() const override; + QString shortDescription() const override; + QList inputLayerTypes() const override; + QgsProcessing::SourceType outputLayerType() const override; + QgsSplitLinesByLengthAlgorithm *createInstance() const override SIP_FACTORY; + void initParameters( const QVariantMap &configuration = QVariantMap() ) override; + bool prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + + protected: + QString outputName() const override; + QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type inputWkbType ) const override; + QgsFeatureList processFeature( const QgsFeature &feature, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + + private: + + double mLength = 0.0; + bool mDynamicLength = false; + QgsProperty mLengthProperty; + +}; + +///@endcond PRIVATE + +#endif // QGSALGORITHMSPLITLINESBYLENGTH_H + + diff --git a/src/analysis/processing/qgsnativealgorithms.cpp b/src/analysis/processing/qgsnativealgorithms.cpp index cf4f53fa9e6..cc62f226a4e 100644 --- a/src/analysis/processing/qgsnativealgorithms.cpp +++ b/src/analysis/processing/qgsnativealgorithms.cpp @@ -87,6 +87,7 @@ #include "qgsalgorithmsimplify.h" #include "qgsalgorithmsmooth.h" #include "qgsalgorithmsnaptogrid.h" +#include "qgsalgorithmsplitlinesbylength.h" #include "qgsalgorithmsplitwithlines.h" #include "qgsalgorithmstringconcatenation.h" #include "qgsalgorithmsubdivide.h" @@ -221,6 +222,7 @@ void QgsNativeAlgorithms::loadAlgorithms() addAlgorithm( new QgsSimplifyAlgorithm() ); addAlgorithm( new QgsSmoothAlgorithm() ); addAlgorithm( new QgsSnapToGridAlgorithm() ); + addAlgorithm( new QgsSplitLinesByLengthAlgorithm() ); addAlgorithm( new QgsSplitWithLinesAlgorithm() ); addAlgorithm( new QgsStringConcatenationAlgorithm() ); addAlgorithm( new QgsSubdivideAlgorithm() ); diff --git a/tests/src/python/test_qgsprocessinginplace.py b/tests/src/python/test_qgsprocessinginplace.py index 8b9c7ba57a6..5e1ee479670 100644 --- a/tests/src/python/test_qgsprocessinginplace.py +++ b/tests/src/python/test_qgsprocessinginplace.py @@ -175,6 +175,7 @@ class TestQgsProcessingInPlace(unittest.TestCase): self._support_inplace_edit_tester('native:difference', GEOMETRY_ONLY) self._support_inplace_edit_tester('native:dropgeometries', ALL) self._support_inplace_edit_tester('native:splitwithlines', LINESTRING_AND_POLYGON_ONLY) + self._support_inplace_edit_tester('native:splitlinesbylength', LINESTRING_ONLY) self._support_inplace_edit_tester('native:buffer', POLYGON_ONLY_NOT_M_NOT_Z) def _make_compatible_tester(self, feature_wkt, layer_wkb_name, attrs=[1]):