Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add QgsCoordinateReferenceSystem::operation() for retrieving details
of the associated proj operation for a CRS

Allows callers to determine the projection used for CRS objects and
their details.

Also add QgsCoordinateReferenceSystemRegistry::projOperations() for
retrieving a complete list of all available PROJ operations
  • Loading branch information
nyalldawson committed May 11, 2021
1 parent 7edc0f6 commit b6f60f6
Show file tree
Hide file tree
Showing 12 changed files with 274 additions and 0 deletions.
Expand Up @@ -890,6 +890,14 @@ The function also calculates the partial derivatives of the given coordinate.

Internally uses the proj library proj_factors API to calculate the factors.

.. versionadded:: 3.20
%End

QgsProjOperation operation() const;
%Docstring
Returns information about the PROJ operation associated with the coordinate reference system, for example
the projection method used by the CRS.

.. versionadded:: 3.20
%End

Expand Down
Expand Up @@ -111,6 +111,15 @@ Removes the existing user CRS with matching ``id``.
Returns ``False`` if the CRS could not be removed.

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

QMap< QString, QgsProjOperation > projOperations() const;
%Docstring
Returns a map of all valid PROJ operations.

The map keys correspond to PROJ operation IDs.

.. versionadded:: 3.20
%End

QList< QgsCelestialBody > celestialBodies() const;
Expand Down
67 changes: 67 additions & 0 deletions python/core/auto_generated/proj/qgsprojoperation.sip.in
@@ -0,0 +1,67 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/proj/qgsprojoperation.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/



class QgsProjOperation
{
%Docstring(signature="appended")
Contains information about a PROJ operation.

.. versionadded:: 3.20
%End

%TypeHeaderCode
#include "qgsprojoperation.h"
%End
public:

bool isValid() const;
%Docstring
Returns ``True`` if the body is a valid object, or ``False`` if it is a null/invalid
object.
%End

QString id() const;
%Docstring
ID of operation.
%End

QString description() const;
%Docstring
Description.
%End

QString details() const;
%Docstring
Additional details.
%End

SIP_PYOBJECT __repr__();
%MethodCode
QString str;
if ( !sipCpp->isValid() )
{
str = QStringLiteral( "<QgsProjOperation: invalid>" );
}
else
{
str = QStringLiteral( "<QgsProjOperation: %1>" ).arg( sipCpp->id() );
}
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
%End

};

/************************************************************************
* This file has been generated automatically from *
* *
* src/core/proj/qgsprojoperation.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
1 change: 1 addition & 0 deletions python/core/core_auto.sip
Expand Up @@ -436,6 +436,7 @@
%Include auto_generated/proj/qgsdatums.sip
%Include auto_generated/proj/qgsdatumtransform.sip
%Include auto_generated/proj/qgsellipsoidutils.sip
%Include auto_generated/proj/qgsprojoperation.sip
%Include auto_generated/proj/qgsprojutils.sip
%Include auto_generated/proj/qgsprojectionfactors.sip
%Include auto_generated/metadata/qgsabstractmetadatabase.sip
Expand Down
1 change: 1 addition & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -1365,6 +1365,7 @@ set(QGIS_CORE_HDRS
proj/qgsdatums.h
proj/qgsdatumtransform.h
proj/qgsellipsoidutils.h
proj/qgsprojoperation.h
proj/qgsprojutils.h
proj/qgsprojectionfactors.h

Expand Down
23 changes: 23 additions & 0 deletions src/core/proj/qgscoordinatereferencesystem.cpp
Expand Up @@ -42,6 +42,7 @@
#include "qgsogrutils.h"
#include "qgsdatums.h"
#include "qgsprojectionfactors.h"
#include "qgsprojoperation.h"

#include <sqlite3.h>
#include "qgsprojutils.h"
Expand Down Expand Up @@ -1327,6 +1328,28 @@ QgsProjectionFactors QgsCoordinateReferenceSystem::factors( const QgsPoint &poin
return res;
}

QgsProjOperation QgsCoordinateReferenceSystem::operation() const
{
QgsProjOperation res;

// we have to make a transformation object corresponding to the crs
QString projString = toProj();
projString.replace( QStringLiteral( "+type=crs" ), QString() );

QgsProjUtils::proj_pj_unique_ptr transformation( proj_create( QgsProjContext::get(), projString.toUtf8().constData() ) );
if ( !transformation )
return res;

PJ_PROJ_INFO info = proj_pj_info( transformation.get() );

if ( info.id )
{
return QgsApplication::coordinateReferenceSystemRegistry()->projOperations().value( QString( info.id ) );
}

return res;
}

QgsUnitTypes::DistanceUnit QgsCoordinateReferenceSystem::mapUnits() const
{
if ( !d->mIsValid )
Expand Down
9 changes: 9 additions & 0 deletions src/core/proj/qgscoordinatereferencesystem.h
Expand Up @@ -41,6 +41,7 @@ class QDomDocument;
class QgsCoordinateReferenceSystemPrivate;
class QgsDatumEnsemble;
class QgsProjectionFactors;
class QgsProjOperation;

#ifndef SIP_RUN
struct PJconsts;
Expand Down Expand Up @@ -822,6 +823,14 @@ class CORE_EXPORT QgsCoordinateReferenceSystem
*/
QgsProjectionFactors factors( const QgsPoint &point ) const;

/**
* Returns information about the PROJ operation associated with the coordinate reference system, for example
* the projection method used by the CRS.
*
* \since QGIS 3.20
*/
QgsProjOperation operation() const;

/**
* Returns whether axis is inverted (e.g., for WMS 1.3) for the CRS.
* \returns TRUE if CRS axis is inverted
Expand Down
30 changes: 30 additions & 0 deletions src/core/proj/qgscoordinatereferencesystemregistry.cpp
Expand Up @@ -26,6 +26,7 @@
#include "qgsprojutils.h"
#include "qgsruntimeprofiler.h"
#include "qgsexception.h"
#include "qgsprojoperation.h"

#include <sqlite3.h>
#include <mutex>
Expand Down Expand Up @@ -290,6 +291,7 @@ bool QgsCoordinateReferenceSystemRegistry::removeUserCrs( long id )
return res;
}


bool QgsCoordinateReferenceSystemRegistry::insertProjection( const QString &projectionAcronym )
{
sqlite3_database_unique_ptr database;
Expand Down Expand Up @@ -344,6 +346,34 @@ bool QgsCoordinateReferenceSystemRegistry::insertProjection( const QString &proj
return true;
}

QMap<QString, QgsProjOperation> QgsCoordinateReferenceSystemRegistry::projOperations() const
{
static std::once_flag initialized;
std::call_once( initialized, [ = ]
{
QgsScopedRuntimeProfile profile( QObject::tr( "Initialize PROJ operations" ) );

const PJ_OPERATIONS *operation = proj_list_operations();
while ( operation && operation->id )
{
QgsProjOperation value;
value.mValid = true;
value.mId = QString( operation->id );

const QString description( *operation->descr );
const QStringList descriptionParts = description.split( QStringLiteral( "\n\t" ) );
value.mDescription = descriptionParts.value( 0 );
value.mDetails = descriptionParts.mid( 1 ).join( '\n' );

mProjOperations.insert( value.id(), value );

operation++;
}
} );

return mProjOperations;
}

QList< QgsCelestialBody> QgsCoordinateReferenceSystemRegistry::celestialBodies() const
{
#if PROJ_VERSION_MAJOR>8 || (PROJ_VERSION_MAJOR==8 && PROJ_VERSION_MINOR>=1)
Expand Down
12 changes: 12 additions & 0 deletions src/core/proj/qgscoordinatereferencesystemregistry.h
Expand Up @@ -19,9 +19,11 @@
#define QGSCOORDINATEREFERENCESYSTEMREGISTRY_H

#include <QObject>
#include <QMap>
#include "qgscoordinatereferencesystem.h"

class QgsCelestialBody;
class QgsProjOperation;

/**
* \class QgsCoordinateReferenceSystemRegistry
Expand Down Expand Up @@ -123,6 +125,15 @@ class CORE_EXPORT QgsCoordinateReferenceSystemRegistry : public QObject
*/
bool removeUserCrs( long id );

/**
* Returns a map of all valid PROJ operations.
*
* The map keys correspond to PROJ operation IDs.
*
* \since QGIS 3.20
*/
QMap< QString, QgsProjOperation > projOperations() const;

/**
* Returns a list of all known celestial bodies.
*
Expand Down Expand Up @@ -179,6 +190,7 @@ class CORE_EXPORT QgsCoordinateReferenceSystemRegistry : public QObject
bool insertProjection( const QString &projectionAcronym );

mutable QList< QgsCelestialBody > mCelestialBodies;
mutable QMap< QString, QgsProjOperation > mProjOperations;

};

Expand Down
82 changes: 82 additions & 0 deletions src/core/proj/qgsprojoperation.h
@@ -0,0 +1,82 @@
/***************************************************************************
qgsprojoperation.h
------------------------
begin : May 2021
copyright : (C) 2021 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 QGSPROJOPERATION_H
#define QGSPROJOPERATION_H

#include "qgis_core.h"
#include "qgis_sip.h"
#include <QString>

/**
* \ingroup core
* \brief Contains information about a PROJ operation.
*
* \since QGIS 3.20
*/
class CORE_EXPORT QgsProjOperation
{
public:

/**
* Returns TRUE if the body is a valid object, or FALSE if it is a null/invalid
* object.
*/
bool isValid() const { return mValid; }

/**
* ID of operation.
*/
QString id() const { return mId; }

/**
* Description.
*/
QString description() const { return mDescription; }

/**
* Additional details.
*/
QString details() const { return mDetails; }

#ifdef SIP_RUN
SIP_PYOBJECT __repr__();
% MethodCode
QString str;
if ( !sipCpp->isValid() )
{
str = QStringLiteral( "<QgsProjOperation: invalid>" );
}
else
{
str = QStringLiteral( "<QgsProjOperation: %1>" ).arg( sipCpp->id() );
}
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
% End
#endif

private:

bool mValid = false;
QString mId;
QString mDescription;
QString mDetails;

friend class QgsCoordinateReferenceSystemRegistry;
friend class QgsCoordinateReferenceSystem;
};

#endif // QGSCELESTIALBODY_H
19 changes: 19 additions & 0 deletions tests/src/core/testqgscoordinatereferencesystem.cpp
Expand Up @@ -26,6 +26,7 @@ Email : sherman at mrcc dot com
#include "qgsproject.h"
#include "qgsprojutils.h"
#include "qgsprojectionfactors.h"
#include "qgsprojoperation.h"
#include <proj.h>
#include <gdal.h>
#include <cpl_conv.h>
Expand Down Expand Up @@ -80,6 +81,7 @@ class TestQgsCoordinateReferenceSystem: public QObject
void mapUnits();
void isDynamic();
void celestialBody();
void operation();
void setValidationHint();
void hasAxisInverted();
void createFromProjInvalid();
Expand Down Expand Up @@ -1348,6 +1350,23 @@ void TestQgsCoordinateReferenceSystem::celestialBody()
#endif
}

void TestQgsCoordinateReferenceSystem::operation()
{
QgsCoordinateReferenceSystem crs;
QVERIFY( !crs.operation().isValid() );

crs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) );
QVERIFY( crs.operation().isValid() );
QCOMPARE( crs.operation().id(), QStringLiteral( "longlat" ) );
QCOMPARE( crs.operation().description(), QStringLiteral( "Lat/long (Geodetic alias)" ) );

crs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:28356" ) );
QVERIFY( crs.operation().isValid() );
QCOMPARE( crs.operation().id(), QStringLiteral( "utm" ) );
QCOMPARE( crs.operation().description(), QStringLiteral( "Universal Transverse Mercator (UTM)" ) );
QVERIFY( crs.operation().details().contains( QStringLiteral( "south approx" ) ) );
}

void TestQgsCoordinateReferenceSystem::setValidationHint()
{
QgsCoordinateReferenceSystem myCrs;
Expand Down

0 comments on commit b6f60f6

Please sign in to comment.