diff --git a/python/core/auto_generated/elevation/qgsprofilerequest.sip.in b/python/core/auto_generated/elevation/qgsprofilerequest.sip.in new file mode 100644 index 00000000000..83a5476edc3 --- /dev/null +++ b/python/core/auto_generated/elevation/qgsprofilerequest.sip.in @@ -0,0 +1,131 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/elevation/qgsprofilerequest.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + +class QgsProfileRequest +{ +%Docstring(signature="appended") +Encapsulates properties and constraints relating to fetching elevation profiles from different sources. + +.. versionadded:: 3.26 +%End + +%TypeHeaderCode +#include "qgsprofilerequest.h" +%End + public: + + QgsProfileRequest( QgsCurve *curve /Transfer/ ); +%Docstring +Constructor for QgsProfileRequest. + +The ``curve`` argument specifies the line along which the profile should be generated. Ownership is transferred +to the request. +%End + + QgsProfileRequest( const QgsProfileRequest &other ); +%Docstring +Copy constructor. +%End + + ~QgsProfileRequest(); + + + bool operator==( const QgsProfileRequest &other ) const; + bool operator!=( const QgsProfileRequest &other ) const; + + QgsProfileRequest &setProfileCurve( QgsCurve *curve /Transfer/ ); +%Docstring +Sets the cross section profile ``curve``, which represents the line along which the profile should be generated. + +Ownership of ``curve`` is transferred to the request. + +The coordinate reference system of the ``curve`` is set via :py:func:`~QgsProfileRequest.setCrs`. + +.. seealso:: :py:func:`profileCurve` +%End + + QgsCurve *profileCurve(); +%Docstring +Returns the cross section profile curve, which represents the line along which the profile should be generated. + +The coordinate reference system of the curve is retrieved via :py:func:`~QgsProfileRequest.crs`. + +.. seealso:: :py:func:`setProfileCurve` +%End + + QgsProfileRequest &setCrs( const QgsCoordinateReferenceSystem &crs ); +%Docstring +Sets the desired Coordinate Reference System (``crs``) for the profile. + +This also represents the CRS associated with the :py:func:`~QgsProfileRequest.profileCurve`. + +.. seealso:: :py:func:`crs` +%End + + QgsCoordinateReferenceSystem crs() const; +%Docstring +Returns the desired Coordinate Reference System for the profile. + +This also represents the CRS associated with the :py:func:`~QgsProfileRequest.profileCurve`. + +.. seealso:: :py:func:`setCrs` +%End + + QgsCoordinateTransformContext transformContext() const; +%Docstring +Returns the transform context, for use when transforming coordinates from a source +to the request's :py:func:`~QgsProfileRequest.crs` + +.. seealso:: :py:func:`setTransformContext` +%End + + QgsProfileRequest &setTransformContext( const QgsCoordinateTransformContext &context ); +%Docstring +Sets the transform ``context``, for use when transforming coordinates from a source +to the request's :py:func:`~QgsProfileRequest.crs` + +.. seealso:: :py:func:`transformContext` +%End + + QgsProfileRequest &setTolerance( double tolerance ); +%Docstring +Sets the tolerance of the request (in :py:func:`~QgsProfileRequest.crs` units). + +This value determines how far from the :py:func:`~QgsProfileRequest.profileCurve` is appropriate for inclusion of results. For instance, +when a profile is generated for a point vector layer this tolerance distance will dictate how far from the +actual profile curve a point can reside within to be included in the results. Other sources may completely +ignore this tolerance if it is not appropriate for the particular source. + +.. seealso:: :py:func:`tolerance` +%End + + double tolerance() const; +%Docstring +Returns the tolerance of the request (in :py:func:`~QgsProfileRequest.crs` units). + +This value determines how far from the :py:func:`~QgsProfileRequest.profileCurve` is appropriate for inclusion of results. For instance, +when a profile is generated for a point vector layer this tolerance distance will dictate how far from the +actual profile curve a point can reside within to be included in the results. Other sources may completely +ignore this tolerance if it is not appropriate for the particular source. + +.. seealso:: :py:func:`setTolerance` +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/elevation/qgsprofilerequest.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index b5840dd1f4d..a678a76aa9e 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -268,6 +268,7 @@ %Include auto_generated/editform/qgsattributeeditorrelation.sip %Include auto_generated/editform/qgsattributeeditorhtmlelement.sip %Include auto_generated/editform/qgsattributeeditorqmlelement.sip +%Include auto_generated/elevation/qgsprofilerequest.sip %Include auto_generated/elevation/qgsterrainprovider.sip %Include auto_generated/externalstorage/qgsexternalstorage.sip %Include auto_generated/externalstorage/qgsexternalstorageregistry.sip diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index d90660e27ad..ed7a533404a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -44,6 +44,7 @@ set(QGIS_CORE_SRCS classification/qgsclassificationstandarddeviation.cpp classification/qgsclassificationlogarithmic.cpp + elevation/qgsprofilerequest.cpp elevation/qgsterrainprovider.cpp geocoding/qgsabstractgeocoderlocatorfilter.cpp @@ -1260,6 +1261,7 @@ set(QGIS_CORE_HDRS editform/qgsattributeeditorhtmlelement.h editform/qgsattributeeditorqmlelement.h + elevation/qgsprofilerequest.h elevation/qgsterrainprovider.h externalstorage/qgsexternalstorage.h diff --git a/src/core/elevation/qgsprofilerequest.cpp b/src/core/elevation/qgsprofilerequest.cpp new file mode 100644 index 00000000000..f903f0094e1 --- /dev/null +++ b/src/core/elevation/qgsprofilerequest.cpp @@ -0,0 +1,110 @@ +/*************************************************************************** + qgsprofilerequest.cpp + --------------- + begin : February 2022 + copyright : (C) 2022 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 "qgsprofilerequest.h" +#include "qgscurve.h" + +QgsProfileRequest::QgsProfileRequest( QgsCurve *curve ) + : mCurve( curve ) +{ + +} + +QgsProfileRequest::~QgsProfileRequest() = default; + +QgsProfileRequest::QgsProfileRequest( const QgsProfileRequest &other ) + : mCurve( other.mCurve ? other.mCurve->clone() : nullptr ) + , mCrs( other.mCrs ) + , mTransformContext( other.mTransformContext ) + , mTolerance( other.mTolerance ) +{ + +} + +QgsProfileRequest &QgsProfileRequest::operator=( const QgsProfileRequest &other ) +{ + mCurve.reset( other.mCurve ? other.mCurve->clone() : nullptr ); + mCrs = other.mCrs; + mTransformContext = other.mTransformContext; + mTolerance = other.mTolerance; + return *this; +} + +bool QgsProfileRequest::operator==( const QgsProfileRequest &other ) const +{ + if ( !qgsDoubleNear( mTolerance, other.mTolerance ) + || mCrs != other.mCrs + || !( mTransformContext == other.mTransformContext ) ) + return false; + + if ( ( !mCurve && other.mCurve ) + || ( mCurve && !other.mCurve ) ) + { + return false; + } + else if ( mCurve && other.mCurve ) + { + if ( !mCurve->equals( *other.mCurve ) ) + return false; + } + + return true; +} + +bool QgsProfileRequest::operator!=( const QgsProfileRequest &other ) const +{ + return !( *this == other ); +} + +QgsProfileRequest &QgsProfileRequest::setProfileCurve( QgsCurve *curve ) +{ + mCurve.reset( curve ); + return *this; +} + +QgsCurve *QgsProfileRequest::profileCurve() +{ + return mCurve.get(); +} + +QgsProfileRequest &QgsProfileRequest::setCrs( const QgsCoordinateReferenceSystem &crs ) +{ + mCrs = crs; + return *this; +} + +QgsCoordinateReferenceSystem QgsProfileRequest::crs() const +{ + return mCrs; +} + +QgsCoordinateTransformContext QgsProfileRequest::transformContext() const +{ + return mTransformContext; +} + +QgsProfileRequest &QgsProfileRequest::setTransformContext( const QgsCoordinateTransformContext &context ) +{ + mTransformContext = context; + return *this; +} + +QgsProfileRequest &QgsProfileRequest::setTolerance( double tolerance ) +{ + mTolerance = tolerance; + return *this; +} + diff --git a/src/core/elevation/qgsprofilerequest.h b/src/core/elevation/qgsprofilerequest.h new file mode 100644 index 00000000000..3c9faf7e8e4 --- /dev/null +++ b/src/core/elevation/qgsprofilerequest.h @@ -0,0 +1,149 @@ +/*************************************************************************** + qgsprofilerequest.h + --------------- + begin : February 2022 + copyright : (C) 2022 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 QGSPROFILEREQUEST_H +#define QGSPROFILEREQUEST_H + +#include "qgis_core.h" +#include "qgscoordinatereferencesystem.h" +#include "qgscoordinatetransformcontext.h" + +#include + +class QgsCurve; + +/** + * \brief Encapsulates properties and constraints relating to fetching elevation profiles from different sources. + * + * \ingroup core + * \since QGIS 3.26 + */ +class CORE_EXPORT QgsProfileRequest +{ + + public: + + /** + * Constructor for QgsProfileRequest. + * + * The \a curve argument specifies the line along which the profile should be generated. Ownership is transferred + * to the request. + */ + QgsProfileRequest( QgsCurve *curve SIP_TRANSFER ); + + /** + * Copy constructor. + */ + QgsProfileRequest( const QgsProfileRequest &other ); + + ~QgsProfileRequest(); + + /** + * Assignment operator + */ + QgsProfileRequest &operator=( const QgsProfileRequest &other ); + + bool operator==( const QgsProfileRequest &other ) const; + bool operator!=( const QgsProfileRequest &other ) const; + + /** + * Sets the cross section profile \a curve, which represents the line along which the profile should be generated. + * + * Ownership of \a curve is transferred to the request. + * + * The coordinate reference system of the \a curve is set via setCrs(). + * + * \see profileCurve() + */ + QgsProfileRequest &setProfileCurve( QgsCurve *curve SIP_TRANSFER ); + + /** + * Returns the cross section profile curve, which represents the line along which the profile should be generated. + * + * The coordinate reference system of the curve is retrieved via crs(). + * + * \see setProfileCurve() + */ + QgsCurve *profileCurve(); + + /** + * Sets the desired Coordinate Reference System (\a crs) for the profile. + * + * This also represents the CRS associated with the profileCurve(). + * + * \see crs() + */ + QgsProfileRequest &setCrs( const QgsCoordinateReferenceSystem &crs ); + + /** + * Returns the desired Coordinate Reference System for the profile. + * + * This also represents the CRS associated with the profileCurve(). + * + * \see setCrs() + */ + QgsCoordinateReferenceSystem crs() const; + + /** + * Returns the transform context, for use when transforming coordinates from a source + * to the request's crs() + * + * \see setTransformContext() + */ + QgsCoordinateTransformContext transformContext() const; + + /** + * Sets the transform \a context, for use when transforming coordinates from a source + * to the request's crs() + * + * \see transformContext() + */ + QgsProfileRequest &setTransformContext( const QgsCoordinateTransformContext &context ); + + /** + * Sets the tolerance of the request (in crs() units). + * + * This value determines how far from the profileCurve() is appropriate for inclusion of results. For instance, + * when a profile is generated for a point vector layer this tolerance distance will dictate how far from the + * actual profile curve a point can reside within to be included in the results. Other sources may completely + * ignore this tolerance if it is not appropriate for the particular source. + * + * \see tolerance() + */ + QgsProfileRequest &setTolerance( double tolerance ); + + /** + * Returns the tolerance of the request (in crs() units). + * + * This value determines how far from the profileCurve() is appropriate for inclusion of results. For instance, + * when a profile is generated for a point vector layer this tolerance distance will dictate how far from the + * actual profile curve a point can reside within to be included in the results. Other sources may completely + * ignore this tolerance if it is not appropriate for the particular source. + * + * \see setTolerance() + */ + double tolerance() const { return mTolerance; } + + private: + + std::unique_ptr< QgsCurve> mCurve; + QgsCoordinateReferenceSystem mCrs; + QgsCoordinateTransformContext mTransformContext; + + double mTolerance = 0; +}; + +#endif // QGSPROFILEREQUEST_H diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index c9cd6a74924..a42b830be18 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -253,6 +253,7 @@ ADD_PYTHON_TEST(PyQgsProcessingAlgDecorator test_processing_alg_decorator.py) ADD_PYTHON_TEST(PyQgsProcessingBatch test_qgsprocessingbatch.py) ADD_PYTHON_TEST(PyQgsProcessingParameters test_qgsprocessingparameters.py) ADD_PYTHON_TEST(PyQgsProcessingUtils test_qgsprocessingutils.py) +ADD_PYTHON_TEST(PyQgsProfileRequest test_qgsprofilerequest.py) ADD_PYTHON_TEST(PyQgsProjectionSelectionWidgets test_qgsprojectionselectionwidgets.py) ADD_PYTHON_TEST(PyQgsProjectElevationProperties test_qgsprojectelevationproperties.py) ADD_PYTHON_TEST(PyQgsProjectMetadata test_qgsprojectmetadata.py) diff --git a/tests/src/python/test_qgsprofilerequest.py b/tests/src/python/test_qgsprofilerequest.py new file mode 100644 index 00000000000..ad91d204e01 --- /dev/null +++ b/tests/src/python/test_qgsprofilerequest.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsProfileRequest + +.. note:: 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__ = 'Nyall Dawson' +__date__ = '18/03/2022' +__copyright__ = 'Copyright 2022, The QGIS Project' + +import os + +import qgis # NOQA + +from qgis.PyQt.QtCore import QTemporaryDir + +from qgis.core import ( + QgsLineString, + QgsProfileRequest, + QgsCoordinateReferenceSystem, + QgsCoordinateTransformContext +) + +from qgis.PyQt.QtXml import QDomDocument + +from qgis.testing import start_app, unittest +from utilities import unitTestDataPath + +start_app() + + +class TestQgsProfileRequest(unittest.TestCase): + + def testBasic(self): + req = QgsProfileRequest(QgsLineString([[1, 2], [3, 4]])) + self.assertEqual(req.profileCurve().asWkt(), 'LineString (1 2, 3 4)') + + req.setCrs(QgsCoordinateReferenceSystem('EPSG:3857')).setTolerance(5) + self.assertEqual(req.crs().authid(), 'EPSG:3857') + self.assertEqual(req.tolerance(), 5) + + proj_string = '+proj=pipeline +step +inv +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1' + transform_context = QgsCoordinateTransformContext() + transform_context.addCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:3111'), + QgsCoordinateReferenceSystem('EPSG:4283'), proj_string) + req.setTransformContext(transform_context) + self.assertEqual(req.transformContext().calculateCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:3111'), + QgsCoordinateReferenceSystem('EPSG:4283')), proj_string) + + copy = QgsProfileRequest(req) + self.assertEqual(copy.profileCurve().asWkt(), 'LineString (1 2, 3 4)') + self.assertEqual(copy.crs().authid(), 'EPSG:3857') + self.assertEqual(copy.tolerance(), 5) + self.assertEqual(copy.transformContext().calculateCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:3111'), + QgsCoordinateReferenceSystem('EPSG:4283')), proj_string) + + def testEquality(self): + """ + Test equality operator + """ + req = QgsProfileRequest(None) + req2 = QgsProfileRequest(None) + self.assertEqual(req, req2) + + req.setProfileCurve(QgsLineString([[1, 2], [3, 4]])) + self.assertNotEqual(req, req2) + + req2.setProfileCurve(QgsLineString([[1, 2], [3, 5]])) + self.assertNotEqual(req, req2) + + req.setProfileCurve(None) + self.assertNotEqual(req, req2) + + req.setProfileCurve(QgsLineString([[1, 2], [3, 5]])) + self.assertEqual(req, req2) + + req.setCrs(QgsCoordinateReferenceSystem('EPSG:3857')) + self.assertNotEqual(req, req2) + req2.setCrs(QgsCoordinateReferenceSystem('EPSG:3857')) + self.assertEqual(req, req2) + + proj_string = '+proj=pipeline +step +inv +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1' + transform_context = QgsCoordinateTransformContext() + transform_context.addCoordinateOperation(QgsCoordinateReferenceSystem('EPSG:3111'), + QgsCoordinateReferenceSystem('EPSG:4283'), proj_string) + + req.setTransformContext(transform_context) + self.assertNotEqual(req, req2) + req2.setTransformContext(transform_context) + self.assertEqual(req, req2) + + req.setTolerance(5) + self.assertNotEqual(req, req2) + req2.setTolerance(5) + self.assertEqual(req, req2) + + +if __name__ == '__main__': + unittest.main()