diff --git a/python/core/geometry/qgsgeometry.sip b/python/core/geometry/qgsgeometry.sip index d80c4a4162b..9ac033732e8 100644 --- a/python/core/geometry/qgsgeometry.sip +++ b/python/core/geometry/qgsgeometry.sip @@ -433,9 +433,12 @@ class QgsGeometry /** Returns a geometry representing the points making up this geometry that do not make up other. */ QgsGeometry* difference( const QgsGeometry* geometry ) const /Factory/; - /** Returns a Geometry representing the points making up this Geometry that do not make up other. */ + /** Returns a Geometry representing the points making up this geometry that do not make up other. */ QgsGeometry* symDifference( const QgsGeometry* geometry ) const /Factory/; + /** Returns an extruded version of this geometry. */ + QgsGeometry extrude( double x, double y ); + /** Exports the geometry to WKT * @note precision parameter added in 2.4 * @return true in case of success and false else diff --git a/resources/function_help/json/extrude b/resources/function_help/json/extrude new file mode 100644 index 00000000000..1cc21b9df6e --- /dev/null +++ b/resources/function_help/json/extrude @@ -0,0 +1,20 @@ +{ + "name": "extrude", + "type": "function", + "description": "Returns an extruded version of the input (Multi-)Curve or (Multi-)Linestring geometry with an extension specified by x and y.", + "arguments": [ + {"arg":"geom","description":"a polygon geometry"}, + {"arg":"x","description":"x extension, numeric value"}, + {"arg":"y","description":"y extension, numeric value"} + ], + "examples": [ + { + "expression":"extrude(geom_from_wkt('LineString(1 2, 3 2, 4 3)'), 1, 2)", + "returns":"Polygon ((1 2, 3 2, 4 3, 5 5, 4 4, 2 4, 1 2))" + }, + { + "expression":"extrude(geom_from_wkt('MultiLineString((1 2, 3 2), (4 3, 8 3)'), 1, 2)", + "returns":"MultiPolygon (((1 2, 3 2, 4 4, 2 4, 1 2)),((4 3, 8 3, 9 5, 5 5, 4 3)))" + } + ] +} diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index fc74076db36..7c60a78e0b2 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -320,6 +320,7 @@ SET(QGIS_CORE_SRCS geometry/qgsgeometry.cpp geometry/qgsgeometrycollectionv2.cpp geometry/qgsgeometryeditutils.cpp + geometry/qgsinternalgeometryengine.cpp geometry/qgsgeometryfactory.cpp geometry/qgsgeometryutils.cpp geometry/qgsgeos.cpp @@ -788,6 +789,7 @@ SET(QGIS_CORE_HDRS layertree/qgslayertreeutils.h geometry/qgsgeometry.h + geometry/qgsinternalgeometryengine.h geometry/qgsabstractgeometryv2.h geometry/qgswkbtypes.h geometry/qgspointv2.h diff --git a/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp index 01ef6ee144d..b55dccad247 100644 --- a/src/core/geometry/qgsgeometry.cpp +++ b/src/core/geometry/qgsgeometry.cpp @@ -23,6 +23,7 @@ email : morb at ozemail dot com dot au #include "qgsgeometryeditutils.h" #include "qgsgeometryfactory.h" #include "qgsgeometryutils.h" +#include "qgsinternalgeometryengine.h" #include "qgsgeos.h" #include "qgsapplication.h" #include "qgslogger.h" @@ -1404,6 +1405,13 @@ QgsGeometry* QgsGeometry::symDifference( const QgsGeometry* geometry ) const return new QgsGeometry( resultGeom ); } +QgsGeometry QgsGeometry::extrude( double x, double y ) +{ + QgsInternalGeometryEngine engine( *this ); + + return engine.extrude( x, y ); +} + QList QgsGeometry::asGeometryCollection() const { QList geometryList; diff --git a/src/core/geometry/qgsgeometry.h b/src/core/geometry/qgsgeometry.h index 6f6f75a8f1c..fabffae530c 100644 --- a/src/core/geometry/qgsgeometry.h +++ b/src/core/geometry/qgsgeometry.h @@ -480,9 +480,12 @@ class CORE_EXPORT QgsGeometry /** Returns a geometry representing the points making up this geometry that do not make up other. */ QgsGeometry* difference( const QgsGeometry* geometry ) const; - /** Returns a Geometry representing the points making up this Geometry that do not make up other. */ + /** Returns a geometry representing the points making up this geometry that do not make up other. */ QgsGeometry* symDifference( const QgsGeometry* geometry ) const; + /** Returns an extruded version of this geometry. */ + QgsGeometry extrude( double x, double y ); + /** Exports the geometry to WKT * @note precision parameter added in 2.4 * @return true in case of success and false else diff --git a/src/core/geometry/qgsgeometryengine.h b/src/core/geometry/qgsgeometryengine.h index 14a208b7432..bcabcd9aa01 100644 --- a/src/core/geometry/qgsgeometryengine.h +++ b/src/core/geometry/qgsgeometryengine.h @@ -13,8 +13,8 @@ email : marco.hugentobler at sourcepole dot com * * ***************************************************************************/ -#ifndef QGSVECTORTOPOLOGY_H -#define QGSVECTORTOPOLOGY_H +#ifndef QGSGEOMETRYENGINE_H +#define QGSGEOMETRYENGINE_H #include "qgspointv2.h" #include "qgslinestringv2.h" @@ -106,4 +106,4 @@ class CORE_EXPORT QgsGeometryEngine QgsGeometryEngine(); }; -#endif // QGSVECTORTOPOLOGY_H +#endif // QGSGEOMETRYENGINE_H diff --git a/src/core/geometry/qgsinternalgeometryengine.cpp b/src/core/geometry/qgsinternalgeometryengine.cpp new file mode 100644 index 00000000000..bfcf5e4da92 --- /dev/null +++ b/src/core/geometry/qgsinternalgeometryengine.cpp @@ -0,0 +1,88 @@ +/*************************************************************************** + qgsinternalgeometryengine.cpp - QgsInternalGeometryEngine + + --------------------- + begin : 13.1.2016 + copyright : (C) 2016 by Matthias Kuhn + email : matthias@opengis.ch + *************************************************************************** + * * + * 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 "qgsinternalgeometryengine.h" + +#include "qgslinestringv2.h" +#include "qgsmultipolygonv2.h" +#include "qgspolygonv2.h" +#include "qgsmulticurvev2.h" + +#include + +QgsInternalGeometryEngine::QgsInternalGeometryEngine( const QgsGeometry& geometry ) + : mGeometry( geometry.geometry() ) +{ + +} + +/*************************************************************************** + * This class is considered CRITICAL and any change MUST be accompanied with + * full unit tests. + * See details in QEP #17 + ****************************************************************************/ + +QgsGeometry QgsInternalGeometryEngine::extrude( double x, double y ) +{ + QList linesToProcess; + + const QgsMultiCurveV2* multiCurve = dynamic_cast< const QgsMultiCurveV2* >( mGeometry ); + if ( multiCurve ) + { + for ( int i = 0; i < multiCurve->partCount(); ++i ) + { + linesToProcess << static_cast( multiCurve->geometryN( i )->clone() ); + } + } + + const QgsCurveV2* curve = dynamic_cast< const QgsCurveV2* >( mGeometry ); + if ( curve ) + { + linesToProcess << static_cast( curve->segmentize() ); + } + + QgsMultiPolygonV2* multipolygon = linesToProcess.size() > 1 ? new QgsMultiPolygonV2() : nullptr; + QgsPolygonV2* polygon; + + if ( !linesToProcess.empty() ) + { + Q_FOREACH ( QgsLineStringV2* line, linesToProcess ) + { + QTransform transform = QTransform::fromTranslate( x, y ); + + QgsLineStringV2* secondline = line->reversed(); + secondline->transform( transform ); + + line->append( secondline ); + line->addVertex( line->pointN( 0 ) ); + + polygon = new QgsPolygonV2(); + polygon->setExteriorRing( line ); + + if ( multipolygon ) + multipolygon->addGeometry( polygon ); + + delete secondline; + } + + if ( multipolygon ) + return QgsGeometry( multipolygon ); + else + return QgsGeometry( polygon ); + } + + return QgsGeometry(); +} diff --git a/src/core/geometry/qgsinternalgeometryengine.h b/src/core/geometry/qgsinternalgeometryengine.h new file mode 100644 index 00000000000..31352d60dab --- /dev/null +++ b/src/core/geometry/qgsinternalgeometryengine.h @@ -0,0 +1,54 @@ +/*************************************************************************** + qgsinternalgeometryengine.h - QgsInternalGeometryEngine + + --------------------- + begin : 13.1.2016 + copyright : (C) 2016 by Matthias Kuhn + email : matthias@opengis.ch + *************************************************************************** + * * + * 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 QGSINTERNALGEOMETRYENGINE_H +#define QGSINTERNALGEOMETRYENGINE_H + +#include "qgsgeometry.h" + +/** + * This class offers geometry processing methods. + * + * The methods are available via QgsGeometry::[geometryfunction] + * and therefore this does not need to be accessed directly. + * + * @note not available in Python bindings + */ + +class QgsInternalGeometryEngine +{ + public: + /** + * The caller is responsible that the geometry is available and unchanged + * for the whole lifetime of this object. + * @param geometry + */ + QgsInternalGeometryEngine( const QgsGeometry& geometry ); + + /** + * Will extrude a line or (segmentized) curve by a given offset and return a polygon + * representation of it. + * + * @param x offset in x direction + * @param y offset in y direction + * @return an extruded polygon + */ + QgsGeometry extrude( double x, double y ); + + private: + const QgsAbstractGeometryV2* mGeometry; +}; + +#endif // QGSINTERNALGEOMETRYENGINE_H diff --git a/src/core/qgsexpression.cpp b/src/core/qgsexpression.cpp index 57d78392eaf..a3d235c39f4 100644 --- a/src/core/qgsexpression.cpp +++ b/src/core/qgsexpression.cpp @@ -2153,6 +2153,21 @@ static QVariant fcnAzimuth( const QVariantList& values, const QgsExpressionConte return QVariant(); } +static QVariant fcnExtrude( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) +{ + if ( values.length() != 3 ) + return QVariant(); + + QgsGeometry fGeom = getGeometry( values.at( 0 ), parent ); + double x = getDoubleValue( values.at( 1 ), parent ); + double y = getDoubleValue( values.at( 2 ), parent ); + + QgsGeometry geom = fGeom.extrude( x, y ); + + QVariant result = geom.geometry() ? QVariant::fromValue( geom ) : QVariant(); + return result; +} + static QVariant fcnRound( const QVariantList& values, const QgsExpressionContext *, QgsExpression* parent ) { if ( values.length() == 2 ) @@ -2915,6 +2930,7 @@ const QList& QgsExpression::Functions() << new StaticFunction( "geom_to_wkt", -1, fcnGeomToWKT, "GeometryGroup", QString(), false, QStringList(), false, QStringList() << "geomToWKT" ) << new StaticFunction( "geometry", 1, fcnGetGeometry, "GeometryGroup", QString(), true ) << new StaticFunction( "transform", 3, fcnTransformGeometry, "GeometryGroup" ) + << new StaticFunction( "extrude", 3, fcnExtrude, "GeometryGroup", QString() ) << new StaticFunction( "$rownum", 0, fcnRowNumber, "deprecated" ) << new StaticFunction( "$id", 0, fcnFeatureId, "Record" ) << new StaticFunction( "$currentfeature", 0, fcnFeature, "Record" ) diff --git a/src/core/qgsexpression.h b/src/core/qgsexpression.h index b76b57ac18e..d4df9892a2e 100644 --- a/src/core/qgsexpression.h +++ b/src/core/qgsexpression.h @@ -280,7 +280,7 @@ class CORE_EXPORT QgsExpression QgsDistanceArea* geomCalculator(); //! Sets the geometry calculator used in evaluation of expressions, - // instead of the default. + //! instead of the default. void setGeomCalculator( const QgsDistanceArea &calc ); /** This function currently replaces each expression between [% and %] diff --git a/tests/src/python/test_qgsgeometry.py b/tests/src/python/test_qgsgeometry.py index 9777bffbd01..2005210ef88 100644 --- a/tests/src/python/test_qgsgeometry.py +++ b/tests/src/python/test_qgsgeometry.py @@ -345,7 +345,7 @@ class TestQgsGeometry(TestCase): 'simplify_error.wkt'), 'rt') myWKT = myWKTFile.readline() myWKTFile.close() - print myWKT + # print myWKT myGeometry = QgsGeometry().fromWkt(myWKT) assert myGeometry is not None myStartLength = len(myWKT) @@ -1191,6 +1191,17 @@ class TestQgsGeometry(TestCase): expwkt = "MultiPolygon (((0 0, 1 0, 1 1, 2 1, 2 2, 0 2, 0 0)),((4 0, 5 0, 5 2, 3 2, 3 1, 4 1, 4 0)))" wkt = polygon.exportToWkt() + def testExtrude(self): + points = [QgsPoint(1, 2), QgsPoint(3, 2), QgsPoint(4, 3)] + line = QgsGeometry.fromPolyline(points) + expected = QgsGeometry.fromWkt('Polygon ((1 2, 3 2, 4 3, 5 5, 4 4, 2 4, 1 2))') + self.assertEqual(line.extrude(1, 2).exportToWkt(), expected.exportToWkt()) + + points2 = [[QgsPoint(1, 2), QgsPoint(3, 2)], [QgsPoint(4, 3), QgsPoint(8, 3)]] + multiline = QgsGeometry.fromMultiPolyline(points2) + expected = QgsGeometry.fromWkt('MultiPolygon (((1 2, 3 2, 4 4, 2 4, 1 2)),((4 3, 8 3, 9 5, 5 5, 4 3)))') + self.assertEqual(multiline.extrude(1, 2).exportToWkt(), expected.exportToWkt()) + def testBoundingBox(self): # 2-+-+-+-+-3 # | |