Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vertical transformation of features in iterators #58079

Merged
merged 9 commits into from
Jul 17, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ Copy constructor

~QgsCoordinateTransform();

bool operator==( const QgsCoordinateTransform &other ) const;
bool operator!=( const QgsCoordinateTransform &other ) const;

static bool isTransformationPossible( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination );
%Docstring
Returns ``True`` if it is theoretically possible to transform between ``source`` and ``destination`` CRSes.
Expand Down Expand Up @@ -346,6 +349,14 @@ otherwise points are transformed from destination to source CRS.
bool isShortCircuited() const;
%Docstring
Returns ``True`` if the transform short circuits because the source and destination are equivalent.
%End

bool hasVerticalComponent() const;
%Docstring
Returns ``True`` if the transform includes a vertical component, i.e. if both the :py:func:`~QgsCoordinateTransform.sourceCrs`
and :py:func:`~QgsCoordinateTransform.destinationCrs` have a vertical axis.

.. versionadded:: 3.40
%End

QString coordinateOperation() const;
Expand Down
75 changes: 74 additions & 1 deletion python/PyQt6/core/auto_generated/qgsfeaturerequest.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@




class QgsFeatureRequest
{
%Docstring(signature="appended")
Expand Down Expand Up @@ -714,6 +713,20 @@ Set a simplification method for geometries that will be fetched.
Returns the simplification method for geometries that will be fetched.

.. seealso:: :py:func:`setSimplifyMethod`
%End

QgsCoordinateTransform coordinateTransform() const;
%Docstring
Returns the coordinate transform which will be used to transform
the feature's geometries.

If this transform is valid then it will always be used to transform
features, regardless of the :py:func:`~QgsFeatureRequest.destinationCrs` setting or the underlying
feature source's actual CRS.

.. seealso:: :py:func:`setCoordinateTransform`

.. versionadded:: 3.40
%End

QgsCoordinateReferenceSystem destinationCrs() const;
Expand All @@ -722,6 +735,14 @@ Returns the destination coordinate reference system for feature's geometries,
or an invalid :py:class:`QgsCoordinateReferenceSystem` if no reprojection will be done
and all features will be left with their original geometry.

.. warning::

if :py:func:`~QgsFeatureRequest.coordinateTransform` returns a valid transform then the
:py:func:`~QgsFeatureRequest.destinationCrs` will have no effect, and the :py:func:`~QgsFeatureRequest.coordinateTransform` will
always be used to transform features.

.. seealso:: :py:func:`calculateTransform`

.. seealso:: :py:func:`setDestinationCrs`

.. seealso:: :py:func:`transformContext`
Expand All @@ -735,6 +756,52 @@ and reprojection is required
.. seealso:: :py:func:`setDestinationCrs`

.. seealso:: :py:func:`destinationCrs`
%End

QgsCoordinateTransform calculateTransform( const QgsCoordinateReferenceSystem &sourceCrs ) const;
%Docstring
Calculates the coordinate transform to use to transform geometries
when they are originally in ``sourceCrs``.

This method will return :py:func:`~QgsFeatureRequest.coordinateTransform` if it is set (ignoring ``sourceCrs``), otherwise
it will calculate an appriopriate transform from ``sourceCrs`` to :py:func:`~QgsFeatureRequest.destinationCrs`.

.. versionadded:: 3.40
%End

QgsFeatureRequest &setCoordinateTransform( const QgsCoordinateTransform &transform );
%Docstring
Sets the coordinate ``transform`` which will be used to transform
the feature's geometries.

If this transform is valid then it will always be used to transform
features, regardless of the :py:func:`~QgsFeatureRequest.destinationCrs` setting or the underlying
feature source's actual CRS.

When a ``transform`` is set using :py:func:`~QgsFeatureRequest.setCoordinateTransform`, then any :py:func:`~QgsFeatureRequest.filterRect`
or :py:func:`~QgsFeatureRequest.referenceGeometry` set on the request is expected to be in the
same CRS as the destination CRS for the ``transform``.

The feature geometry transformation is performed
after all filter expressions are tested and any virtual fields are
calculated. Accordingly, any geometric expressions used in
:py:func:`~QgsFeatureRequest.filterExpression` will be performed in the original
source CRS. This ensures consistent results are returned regardless of the
destination CRS. Similarly, virtual field values will be calculated using the
original geometry in the source CRS, so these values are not affected by
any destination CRS transform present in the feature request.

.. warning::

This method should be used with caution, and it is recommended
to use the high-level :py:func:`~QgsFeatureRequest.setDestinationCrs` method instead. Setting a specific
transform should only be done when there is a requirement to use a particular
transform.

.. seealso:: :py:func:`coordinateTransform`


.. versionadded:: 3.40
%End

QgsFeatureRequest &setDestinationCrs( const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context );
Expand All @@ -758,6 +825,12 @@ destination CRS. Similarly, virtual field values will be calculated using the
original geometry in the source CRS, so these values are not affected by
any destination CRS transform present in the feature request.

.. warning::

if :py:func:`~QgsFeatureRequest.coordinateTransform` returns a valid transform then the
:py:func:`~QgsFeatureRequest.destinationCrs` will have no effect, and the :py:func:`~QgsFeatureRequest.coordinateTransform` will
always be used to transform features.

.. seealso:: :py:func:`destinationCrs`
%End

Expand Down
11 changes: 11 additions & 0 deletions python/core/auto_generated/proj/qgscoordinatetransform.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ Copy constructor

~QgsCoordinateTransform();

bool operator==( const QgsCoordinateTransform &other ) const;
bool operator!=( const QgsCoordinateTransform &other ) const;

static bool isTransformationPossible( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination );
%Docstring
Returns ``True`` if it is theoretically possible to transform between ``source`` and ``destination`` CRSes.
Expand Down Expand Up @@ -346,6 +349,14 @@ otherwise points are transformed from destination to source CRS.
bool isShortCircuited() const;
%Docstring
Returns ``True`` if the transform short circuits because the source and destination are equivalent.
%End

bool hasVerticalComponent() const;
%Docstring
Returns ``True`` if the transform includes a vertical component, i.e. if both the :py:func:`~QgsCoordinateTransform.sourceCrs`
and :py:func:`~QgsCoordinateTransform.destinationCrs` have a vertical axis.

.. versionadded:: 3.40
%End

QString coordinateOperation() const;
Expand Down
75 changes: 74 additions & 1 deletion python/core/auto_generated/qgsfeaturerequest.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@




class QgsFeatureRequest
{
%Docstring(signature="appended")
Expand Down Expand Up @@ -714,6 +713,20 @@ Set a simplification method for geometries that will be fetched.
Returns the simplification method for geometries that will be fetched.

.. seealso:: :py:func:`setSimplifyMethod`
%End

QgsCoordinateTransform coordinateTransform() const;
%Docstring
Returns the coordinate transform which will be used to transform
the feature's geometries.

If this transform is valid then it will always be used to transform
features, regardless of the :py:func:`~QgsFeatureRequest.destinationCrs` setting or the underlying
feature source's actual CRS.

.. seealso:: :py:func:`setCoordinateTransform`

.. versionadded:: 3.40
%End

QgsCoordinateReferenceSystem destinationCrs() const;
Expand All @@ -722,6 +735,14 @@ Returns the destination coordinate reference system for feature's geometries,
or an invalid :py:class:`QgsCoordinateReferenceSystem` if no reprojection will be done
and all features will be left with their original geometry.

.. warning::

if :py:func:`~QgsFeatureRequest.coordinateTransform` returns a valid transform then the
:py:func:`~QgsFeatureRequest.destinationCrs` will have no effect, and the :py:func:`~QgsFeatureRequest.coordinateTransform` will
always be used to transform features.

.. seealso:: :py:func:`calculateTransform`

.. seealso:: :py:func:`setDestinationCrs`

.. seealso:: :py:func:`transformContext`
Expand All @@ -735,6 +756,52 @@ and reprojection is required
.. seealso:: :py:func:`setDestinationCrs`

.. seealso:: :py:func:`destinationCrs`
%End

QgsCoordinateTransform calculateTransform( const QgsCoordinateReferenceSystem &sourceCrs ) const;
%Docstring
Calculates the coordinate transform to use to transform geometries
when they are originally in ``sourceCrs``.

This method will return :py:func:`~QgsFeatureRequest.coordinateTransform` if it is set (ignoring ``sourceCrs``), otherwise
it will calculate an appriopriate transform from ``sourceCrs`` to :py:func:`~QgsFeatureRequest.destinationCrs`.

.. versionadded:: 3.40
%End

QgsFeatureRequest &setCoordinateTransform( const QgsCoordinateTransform &transform );
%Docstring
Sets the coordinate ``transform`` which will be used to transform
the feature's geometries.

If this transform is valid then it will always be used to transform
features, regardless of the :py:func:`~QgsFeatureRequest.destinationCrs` setting or the underlying
feature source's actual CRS.

When a ``transform`` is set using :py:func:`~QgsFeatureRequest.setCoordinateTransform`, then any :py:func:`~QgsFeatureRequest.filterRect`
or :py:func:`~QgsFeatureRequest.referenceGeometry` set on the request is expected to be in the
same CRS as the destination CRS for the ``transform``.

The feature geometry transformation is performed
after all filter expressions are tested and any virtual fields are
calculated. Accordingly, any geometric expressions used in
:py:func:`~QgsFeatureRequest.filterExpression` will be performed in the original
source CRS. This ensures consistent results are returned regardless of the
destination CRS. Similarly, virtual field values will be calculated using the
original geometry in the source CRS, so these values are not affected by
any destination CRS transform present in the feature request.

.. warning::

This method should be used with caution, and it is recommended
to use the high-level :py:func:`~QgsFeatureRequest.setDestinationCrs` method instead. Setting a specific
transform should only be done when there is a requirement to use a particular
transform.

.. seealso:: :py:func:`coordinateTransform`


.. versionadded:: 3.40
%End

QgsFeatureRequest &setDestinationCrs( const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context );
Expand All @@ -758,6 +825,12 @@ destination CRS. Similarly, virtual field values will be calculated using the
original geometry in the source CRS, so these values are not affected by
any destination CRS transform present in the feature request.

.. warning::

if :py:func:`~QgsFeatureRequest.coordinateTransform` returns a valid transform then the
:py:func:`~QgsFeatureRequest.destinationCrs` will have no effect, and the :py:func:`~QgsFeatureRequest.coordinateTransform` will
always be used to transform features.

.. seealso:: :py:func:`destinationCrs`
%End

Expand Down
19 changes: 19 additions & 0 deletions src/core/proj/qgscoordinatetransform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,20 @@ QgsCoordinateTransform &QgsCoordinateTransform::operator=( const QgsCoordinateTr

QgsCoordinateTransform::~QgsCoordinateTransform() {} //NOLINT

bool QgsCoordinateTransform::operator==( const QgsCoordinateTransform &other ) const
{
return d->mSourceCRS == other.d->mSourceCRS
&& d->mDestCRS == other.d->mDestCRS
&& mBallparkTransformsAreAppropriate == other.mBallparkTransformsAreAppropriate
&& d->mProjCoordinateOperation == other.d->mProjCoordinateOperation
&& instantiatedCoordinateOperationDetails().proj == other.instantiatedCoordinateOperationDetails().proj;
}

bool QgsCoordinateTransform::operator!=( const QgsCoordinateTransform &other ) const
{
return !( *this == other );
}

bool QgsCoordinateTransform::isTransformationPossible( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination )
{
if ( !source.isValid() || !destination.isValid() )
Expand Down Expand Up @@ -921,6 +935,11 @@ bool QgsCoordinateTransform::isShortCircuited() const
return !d->mIsValid || d->mShortCircuit;
}

bool QgsCoordinateTransform::hasVerticalComponent() const
{
return d->mIsValid && d->mHasVerticalComponent;
}

QString QgsCoordinateTransform::coordinateOperation() const
{
return d->mProjCoordinateOperation;
Expand Down
11 changes: 11 additions & 0 deletions src/core/proj/qgscoordinatetransform.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ class CORE_EXPORT QgsCoordinateTransform

~QgsCoordinateTransform();

bool operator==( const QgsCoordinateTransform &other ) const;
bool operator!=( const QgsCoordinateTransform &other ) const;

/**
* Returns TRUE if it is theoretically possible to transform between \a source and \a destination CRSes.
*
Expand Down Expand Up @@ -378,6 +381,14 @@ class CORE_EXPORT QgsCoordinateTransform
*/
bool isShortCircuited() const;

/**
* Returns TRUE if the transform includes a vertical component, i.e. if both the sourceCrs()
* and destinationCrs() have a vertical axis.
*
* \since QGIS 3.40
*/
bool hasVerticalComponent() const;

/**
* Returns a Proj string representing the coordinate operation which will be used to transform
* coordinates.
Expand Down
3 changes: 3 additions & 0 deletions src/core/proj/qgscoordinatetransform_p.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinat
, mIsValid( other.mIsValid )
, mShortCircuit( other.mShortCircuit )
, mGeographicToWebMercator( other.mGeographicToWebMercator )
, mHasVerticalComponent( other.mHasVerticalComponent )
, mSourceCRS( other.mSourceCRS )
, mDestCRS( other.mDestCRS )
, mSourceDatumTransform( other.mSourceDatumTransform )
Expand Down Expand Up @@ -163,6 +164,8 @@ bool QgsCoordinateTransformPrivate::initialize()
mSourceCRS.isGeographic() &&
mDestCRS.authid() == QLatin1String( "EPSG:3857" );

mHasVerticalComponent = mSourceCRS.hasVerticalAxis() && mDestCRS.hasVerticalAxis();

mSourceIsDynamic = mSourceCRS.isDynamic();
mSourceCoordinateEpoch = mSourceCRS.coordinateEpoch();
mDestIsDynamic = mDestCRS.isDynamic();
Expand Down
3 changes: 3 additions & 0 deletions src/core/proj/qgscoordinatetransform_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ class QgsCoordinateTransformPrivate : public QSharedData
//! Flag to indicate EPSG:4326 to EPSG:3857 reprojection
bool mGeographicToWebMercator = false;

//! Flag to indicate whether the transform has a vertical component
bool mHasVerticalComponent = false;

//! QgsCoordinateReferenceSystem of the source (layer) coordinate system
QgsCoordinateReferenceSystem mSourceCRS;

Expand Down
6 changes: 2 additions & 4 deletions src/core/providers/memory/qgsmemoryfeatureiterator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,8 @@
QgsMemoryFeatureIterator::QgsMemoryFeatureIterator( QgsMemoryFeatureSource *source, bool ownSource, const QgsFeatureRequest &request )
: QgsAbstractFeatureIteratorFromSource<QgsMemoryFeatureSource>( source, ownSource, request )
{
if ( mRequest.destinationCrs().isValid() && mRequest.destinationCrs() != mSource->mCrs )
{
mTransform = QgsCoordinateTransform( mSource->mCrs, mRequest.destinationCrs(), mRequest.transformContext() );
}
mTransform = mRequest.calculateTransform( mSource->mCrs );

try
{
mFilterRect = filterRectToSourceCrs( mTransform );
Expand Down
5 changes: 1 addition & 4 deletions src/core/providers/ogr/qgsogrfeatureiterator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,7 @@ QgsOgrFeatureIterator::QgsOgrFeatureIterator( QgsOgrFeatureSource *source, bool
}
QMutexLocker locker( mSharedDS ? &mSharedDS->mutex() : nullptr );

if ( mRequest.destinationCrs().isValid() && mRequest.destinationCrs() != mSource->mCrs )
{
mTransform = QgsCoordinateTransform( mSource->mCrs, mRequest.destinationCrs(), mRequest.transformContext() );
}
mTransform = mRequest.calculateTransform( mSource->mCrs );
try
{
mFilterRect = filterRectToSourceCrs( mTransform );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,7 @@ QgsSensorThingsFeatureIterator::QgsSensorThingsFeatureIterator( QgsSensorThingsF
: QgsAbstractFeatureIteratorFromSource<QgsSensorThingsFeatureSource>( source, ownSource, request )
, mInterruptionChecker( request.feedback() )
{
if ( mRequest.destinationCrs().isValid() && mRequest.destinationCrs() != mSource->sharedData()->crs() )
{
mTransform = QgsCoordinateTransform( mSource->sharedData()->crs(), mRequest.destinationCrs(), mRequest.transformContext() );
}
mTransform = mRequest.calculateTransform( mSource->sharedData()->crs() );
try
{
mFilterRect = filterRectToSourceCrs( mTransform );
Expand Down
Loading
Loading