Skip to content

Commit

Permalink
Move responsibility for retrieving lists of custom user CRSes to
Browse files Browse the repository at this point in the history
QgsCoordinateReferenceSystemRegistry

This was the last bit of untested, direct sql database access relating
to custom projections. Now everything is nice and central and protected
by unit tests.
  • Loading branch information
nyalldawson committed Jan 27, 2021
1 parent 8e26132 commit ddf6245
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 53 deletions.
Expand Up @@ -29,6 +29,34 @@ QgsCoordinateReferenceSystemRegistry is not usually directly created, but rather
explicit QgsCoordinateReferenceSystemRegistry( QObject *parent = 0 );
%Docstring
Constructor for QgsCoordinateReferenceSystemRegistry, with the specified ``parent`` object.
%End

class UserCrsDetails
{
%Docstring
Contains details of a custom (user defined) CRS
%End

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

long id;

QString name;

QString proj;

QString wkt;

QgsCoordinateReferenceSystem crs;
};

QList< QgsCoordinateReferenceSystemRegistry::UserCrsDetails > userCrsList() const;
%Docstring
Returns a list containing the details of all registered
custom (user-defined) CRSes.
%End

long addUserCrs( const QgsCoordinateReferenceSystem &crs, const QString &name, QgsCoordinateReferenceSystem::Format nativeFormat = QgsCoordinateReferenceSystem::FormatWkt );
Expand Down
67 changes: 16 additions & 51 deletions src/app/qgscustomprojectiondialog.cpp
Expand Up @@ -25,7 +25,6 @@
#include "qgslogger.h"
#include "qgsprojectionselectiondialog.h"
#include "qgssettings.h"
#include "qgssqliteutils.h"
#include "qgsgui.h"
#include "qgscoordinatereferencesystemregistry.h"

Expand All @@ -34,10 +33,6 @@
#include <QMessageBox>
#include <QLocale>

//stdc++ includes
#include <fstream>
#include <sqlite3.h>

//proj4 includes
#if PROJ_VERSION_MAJOR>=6
#include "qgsprojutils.h"
Expand Down Expand Up @@ -102,55 +97,25 @@ QgsCustomProjectionDialog::QgsCustomProjectionDialog( QWidget *parent, Qt::Windo

void QgsCustomProjectionDialog::populateList()
{
//Setup connection to the existing custom CRS database:
sqlite3_database_unique_ptr database;
sqlite3_statement_unique_ptr preparedStatement;
//check the db is available
int result = database.open_v2( QgsApplication::qgisUserDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
if ( result != SQLITE_OK )
{
QgsDebugMsg( QStringLiteral( "Can't open database: %1" ).arg( database.errorMessage() ) );
// XXX This will likely never happen since on open, sqlite creates the
// database if it does not exist.
Q_ASSERT( result == SQLITE_OK );
}
QString sql = QStringLiteral( "select srs_id,description,parameters, wkt from tbl_srs" );
QgsDebugMsgLevel( QStringLiteral( "Query to populate existing list:%1" ).arg( sql ), 4 );
preparedStatement = database.prepare( sql, result );
if ( result == SQLITE_OK )
{
QgsCoordinateReferenceSystem crs;
while ( preparedStatement.step() == SQLITE_ROW )
{
const QString id = preparedStatement.columnAsText( 0 );
const QString name = preparedStatement.columnAsText( 1 );
const QString parameters = preparedStatement.columnAsText( 2 );
const QString wkt = preparedStatement.columnAsText( 3 );
const QList< QgsCoordinateReferenceSystemRegistry::UserCrsDetails > userCrsList = QgsApplication::coordinateReferenceSystemRegistry()->userCrsList();

if ( !wkt.isEmpty() )
crs.createFromWkt( wkt );
else
crs.createFromProj( parameters );

mExistingCRSnames[id] = name;
const QString actualWkt = crs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED, false );
const QString actualWktFormatted = crs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED, true );
const QString actualProj = crs.toProj();
mExistingCRSwkt[id] = wkt.isEmpty() ? QString() : actualWkt;
mExistingCRSproj[id] = wkt.isEmpty() ? actualProj : QString();

QTreeWidgetItem *newItem = new QTreeWidgetItem( leNameList, QStringList() );
newItem->setText( QgisCrsNameColumn, name );
newItem->setText( QgisCrsIdColumn, id );
newItem->setText( QgisCrsParametersColumn, wkt.isEmpty() ? actualProj : actualWkt );
newItem->setData( 0, FormattedWktRole, actualWktFormatted );
}
}
else
for ( const QgsCoordinateReferenceSystemRegistry::UserCrsDetails &details : userCrsList )
{
QgsDebugMsg( QStringLiteral( "Populate list query failed: %1" ).arg( sql ) );
const QString id = QString::number( details.id );

mExistingCRSnames[id] = details.name;
const QString actualWkt = details.crs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED, false );
const QString actualWktFormatted = details.crs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED, true );
const QString actualProj = details.crs.toProj();
mExistingCRSwkt[id] = details.wkt.isEmpty() ? QString() : actualWkt;
mExistingCRSproj[id] = details.wkt.isEmpty() ? actualProj : QString();

QTreeWidgetItem *newItem = new QTreeWidgetItem( leNameList, QStringList() );
newItem->setText( QgisCrsNameColumn, details.name );
newItem->setText( QgisCrsIdColumn, id );
newItem->setText( QgisCrsParametersColumn, details.wkt.isEmpty() ? actualProj : actualWkt );
newItem->setData( 0, FormattedWktRole, actualWktFormatted );
}
preparedStatement.reset();

leNameList->sortByColumn( QgisCrsNameColumn, Qt::AscendingOrder );

Expand Down
39 changes: 39 additions & 0 deletions src/core/qgscoordinatereferencesystemregistry.cpp
Expand Up @@ -31,6 +31,45 @@ QgsCoordinateReferenceSystemRegistry::QgsCoordinateReferenceSystemRegistry( QObj

}

QList<QgsCoordinateReferenceSystemRegistry::UserCrsDetails> QgsCoordinateReferenceSystemRegistry::userCrsList() const
{
QList<QgsCoordinateReferenceSystemRegistry::UserCrsDetails> res;

//Setup connection to the existing custom CRS database:
sqlite3_database_unique_ptr database;
//check the db is available
int result = database.open_v2( QgsApplication::qgisUserDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
if ( result != SQLITE_OK )
{
QgsDebugMsg( QStringLiteral( "Can't open database: %1" ).arg( database.errorMessage() ) );
return res;
}

const QString sql = QStringLiteral( "select srs_id,description,parameters, wkt from tbl_srs" );
QgsDebugMsgLevel( QStringLiteral( "Query to populate existing list:%1" ).arg( sql ), 4 );
sqlite3_statement_unique_ptr preparedStatement = database.prepare( sql, result );
if ( result == SQLITE_OK )
{
QgsCoordinateReferenceSystem crs;
while ( preparedStatement.step() == SQLITE_ROW )
{
UserCrsDetails details;
details.id = preparedStatement.columnAsText( 0 ).toLong();
details.name = preparedStatement.columnAsText( 1 );
details.proj = preparedStatement.columnAsText( 2 );
details.wkt = preparedStatement.columnAsText( 3 );

if ( !details.wkt.isEmpty() )
details.crs.createFromWkt( details.wkt );
else
details.crs.createFromProj( details.proj );

res << details;
}
}
return res;
}

long QgsCoordinateReferenceSystemRegistry::addUserCrs( const QgsCoordinateReferenceSystem &crs, const QString &name, QgsCoordinateReferenceSystem::Format nativeFormat )
{
if ( !crs.isValid() )
Expand Down
32 changes: 32 additions & 0 deletions src/core/qgscoordinatereferencesystemregistry.h
Expand Up @@ -42,6 +42,38 @@ class CORE_EXPORT QgsCoordinateReferenceSystemRegistry : public QObject
*/
explicit QgsCoordinateReferenceSystemRegistry( QObject *parent = nullptr );

/**
* Contains details of a custom (user defined) CRS
*/
class UserCrsDetails
{
public:

//! CRS ID
long id = -1;

//! CRS name (or description)
QString name;

//! PROJ string definition of CRS
QString proj;

/**
* WKT definition of CRS. This will be empty for custom CRSes
* which were defined using a PROJ string only.
*/
QString wkt;

//! QgsCoordinateReferenceSystem object representing the user-defined CRS.
QgsCoordinateReferenceSystem crs;
};

/**
* Returns a list containing the details of all registered
* custom (user-defined) CRSes.
*/
QList< QgsCoordinateReferenceSystemRegistry::UserCrsDetails > userCrsList() const;

/**
* Adds a new \a crs definition as a custom ("USER") CRS.
*
Expand Down
70 changes: 68 additions & 2 deletions tests/src/core/testqgscoordinatereferencesystemregistry.cpp
Expand Up @@ -72,6 +72,8 @@ void TestQgsCoordinateReferenceSystemRegistry::addUserCrs()
{
QgsCoordinateReferenceSystemRegistry *registry = QgsApplication::coordinateReferenceSystemRegistry();

QVERIFY( registry->userCrsList().isEmpty() );

QString madeUpProjection = QStringLiteral( "+proj=aea +lat_1=20 +lat_2=-23 +lat_0=4 +lon_0=29 +x_0=10 +y_0=3 +datum=WGS84 +units=m +no_defs" );
QgsCoordinateReferenceSystem userCrs = QgsCoordinateReferenceSystem::fromProj( madeUpProjection );
QVERIFY( userCrs.isValid() );
Expand All @@ -88,6 +90,7 @@ void TestQgsCoordinateReferenceSystemRegistry::addUserCrs()
QCOMPARE( spyAdded.length(), 0 );
QCOMPARE( spyChanged.length(), 0 );
QCOMPARE( spyCrsDefsChanged.length(), 0 );
QVERIFY( registry->userCrsList().isEmpty() );

// valid new crs
res = registry->addUserCrs( userCrs, QStringLiteral( "test" ) );
Expand All @@ -100,8 +103,15 @@ void TestQgsCoordinateReferenceSystemRegistry::addUserCrs()
QCOMPARE( userCrs.authid(), QStringLiteral( "USER:100000" ) );
QCOMPARE( userCrs.description(), QStringLiteral( "test" ) );

QCOMPARE( registry->userCrsList().count(), 1 );
QCOMPARE( registry->userCrsList().at( 0 ).id, 100000L );
QCOMPARE( registry->userCrsList().at( 0 ).name, QStringLiteral( "test" ) );
QCOMPARE( registry->userCrsList().at( 0 ).proj, madeUpProjection );
QVERIFY( !registry->userCrsList().at( 0 ).wkt.isEmpty() );
QCOMPARE( registry->userCrsList().at( 0 ).crs, userCrs );

// try adding again, should be assigned a new ID because we are calling the "add" method
res = registry->addUserCrs( userCrs, QStringLiteral( "test2" ) );
res = registry->addUserCrs( userCrs, QStringLiteral( "test2" ), QgsCoordinateReferenceSystem::FormatProj );
QCOMPARE( res, 100001L );
QCOMPARE( spyAdded.length(), 2 );
QCOMPARE( spyAdded.at( 1 ).at( 0 ).toString(), QStringLiteral( "USER:100001" ) );
Expand All @@ -110,6 +120,18 @@ void TestQgsCoordinateReferenceSystemRegistry::addUserCrs()
QCOMPARE( userCrs.srsid(), 100001L );
QCOMPARE( userCrs.authid(), QStringLiteral( "USER:100001" ) );
QCOMPARE( userCrs.description(), QStringLiteral( "test2" ) );

QCOMPARE( registry->userCrsList().count(), 2 );
QCOMPARE( registry->userCrsList().at( 0 ).id, 100000L );
QCOMPARE( registry->userCrsList().at( 0 ).name, QStringLiteral( "test" ) );
QCOMPARE( registry->userCrsList().at( 0 ).proj, madeUpProjection );
QVERIFY( !registry->userCrsList().at( 0 ).wkt.isEmpty() );
QCOMPARE( registry->userCrsList().at( 0 ).crs.toProj(), madeUpProjection );
QCOMPARE( registry->userCrsList().at( 1 ).id, 100001L );
QCOMPARE( registry->userCrsList().at( 1 ).name, QStringLiteral( "test2" ) );
QCOMPARE( registry->userCrsList().at( 1 ).proj, madeUpProjection );
QVERIFY( registry->userCrsList().at( 1 ).wkt.isEmpty() );
QCOMPARE( registry->userCrsList().at( 1 ).crs, userCrs );
}

void TestQgsCoordinateReferenceSystemRegistry::changeUserCrs()
Expand All @@ -132,12 +154,14 @@ void TestQgsCoordinateReferenceSystemRegistry::changeUserCrs()
QCOMPARE( spyAdded.length(), 0 );
QCOMPARE( spyChanged.length(), 0 );
QCOMPARE( spyCrsDefsChanged.length(), 0 );
QCOMPARE( registry->userCrsList().count(), 2 );
// non-existing crs - should be rejected
res = registry->updateUserCrs( 100100, userCrs, QStringLiteral( "test" ) );
QVERIFY( !res );
QCOMPARE( spyAdded.length(), 0 );
QCOMPARE( spyChanged.length(), 0 );
QCOMPARE( spyCrsDefsChanged.length(), 0 );
QCOMPARE( registry->userCrsList().count(), 2 );

// add valid new user crs
const long id = registry->addUserCrs( userCrs, QStringLiteral( "test" ) );
Expand All @@ -147,14 +171,15 @@ void TestQgsCoordinateReferenceSystemRegistry::changeUserCrs()
QCOMPARE( spyAdded.at( 0 ).at( 0 ).toString(), authid );
QCOMPARE( spyChanged.length(), 0 );
QCOMPARE( spyCrsDefsChanged.length(), 1 );
QCOMPARE( registry->userCrsList().count(), 3 );

// now try changing it
QgsCoordinateReferenceSystem crs2( authid );
QCOMPARE( crs2.toProj(), madeUpProjection );
const QString madeUpProjection2 = QStringLiteral( "+proj=aea +lat_1=22.5 +lat_2=-24.5 +lat_0=4 +lon_0=29 +x_0=10 +y_0=3 +datum=WGS84 +units=m +no_defs" );
crs2.createFromProj( madeUpProjection2 );

connect( registry, &QgsCoordinateReferenceSystemRegistry::userCrsChanged, this, [&]
QMetaObject::Connection conn1 = connect( registry, &QgsCoordinateReferenceSystemRegistry::userCrsChanged, this, [&]
{
// make sure that caches are invalidated before the signals are emitted, so that slots will
// get the new definition!
Expand All @@ -174,9 +199,31 @@ void TestQgsCoordinateReferenceSystemRegistry::changeUserCrs()
QCOMPARE( spyChanged.at( 0 ).at( 0 ).toString(), authid );
QCOMPARE( spyCrsDefsChanged.length(), 2 );

QCOMPARE( registry->userCrsList().count(), 3 );
QCOMPARE( registry->userCrsList().at( 2 ).id, 100002L );
QCOMPARE( registry->userCrsList().at( 2 ).name, QStringLiteral( "test 2" ) );
QCOMPARE( registry->userCrsList().at( 2 ).proj, madeUpProjection2 );
QVERIFY( !registry->userCrsList().at( 2 ).wkt.isEmpty() );
QCOMPARE( registry->userCrsList().at( 2 ).crs.toProj(), madeUpProjection2 );

// newly created crs should get new definition, not old
QgsCoordinateReferenceSystem crs3( authid );
QCOMPARE( crs3.toProj(), madeUpProjection2 );

// with proj native format
QObject::disconnect( conn1 );
QVERIFY( registry->updateUserCrs( id, crs2, QStringLiteral( "test 2" ), QgsCoordinateReferenceSystem::FormatProj ) );

QCOMPARE( spyAdded.length(), 1 );
QCOMPARE( spyChanged.length(), 2 );
QCOMPARE( spyCrsDefsChanged.length(), 3 );

QCOMPARE( registry->userCrsList().count(), 3 );
QCOMPARE( registry->userCrsList().at( 2 ).id, 100002L );
QCOMPARE( registry->userCrsList().at( 2 ).name, QStringLiteral( "test 2" ) );
QCOMPARE( registry->userCrsList().at( 2 ).proj, madeUpProjection2 );
QVERIFY( registry->userCrsList().at( 2 ).wkt.isEmpty() );
QCOMPARE( registry->userCrsList().at( 2 ).crs.toProj(), madeUpProjection2 );
}

void TestQgsCoordinateReferenceSystemRegistry::removeUserCrs()
Expand All @@ -194,13 +241,15 @@ void TestQgsCoordinateReferenceSystemRegistry::removeUserCrs()
QSignalSpy spyRemoved( registry, &QgsCoordinateReferenceSystemRegistry::userCrsRemoved );
QSignalSpy spyCrsDefsChanged( registry, &QgsCoordinateReferenceSystemRegistry::crsDefinitionsChanged );

QCOMPARE( registry->userCrsList().count(), 3 );
// non-existing crs - should be rejected
bool res = registry->removeUserCrs( 100100 );
QVERIFY( !res );
QCOMPARE( spyAdded.length(), 0 );
QCOMPARE( spyChanged.length(), 0 );
QCOMPARE( spyRemoved.length(), 0 );
QCOMPARE( spyCrsDefsChanged.length(), 0 );
QCOMPARE( registry->userCrsList().count(), 3 );

// add valid new user crs
const long id = registry->addUserCrs( userCrs, QStringLiteral( "test" ) );
Expand All @@ -211,6 +260,7 @@ void TestQgsCoordinateReferenceSystemRegistry::removeUserCrs()
QCOMPARE( spyChanged.length(), 0 );
QCOMPARE( spyRemoved.length(), 0 );
QCOMPARE( spyCrsDefsChanged.length(), 1 );
QCOMPARE( registry->userCrsList().count(), 4 );

QgsCoordinateReferenceSystem crs2( authid );
QVERIFY( crs2.isValid() );
Expand All @@ -230,6 +280,22 @@ void TestQgsCoordinateReferenceSystemRegistry::removeUserCrs()
QCOMPARE( spyRemoved.at( 0 ).at( 0 ).toLongLong(), static_cast< long long >( id ) );
QCOMPARE( spyCrsDefsChanged.length(), 2 );

QCOMPARE( registry->userCrsList().count(), 3 );
QCOMPARE( registry->userCrsList().at( 0 ).id, 100000L );
QCOMPARE( registry->userCrsList().at( 0 ).name, QStringLiteral( "test" ) );
QCOMPARE( registry->userCrsList().at( 0 ).proj, QStringLiteral( "+proj=aea +lat_1=20 +lat_2=-23 +lat_0=4 +lon_0=29 +x_0=10 +y_0=3 +datum=WGS84 +units=m +no_defs" ) );
QVERIFY( !registry->userCrsList().at( 0 ).wkt.isEmpty() );
QCOMPARE( registry->userCrsList().at( 0 ).crs.toProj(), QStringLiteral( "+proj=aea +lat_1=20 +lat_2=-23 +lat_0=4 +lon_0=29 +x_0=10 +y_0=3 +datum=WGS84 +units=m +no_defs" ) );
QCOMPARE( registry->userCrsList().at( 1 ).id, 100001L );
QCOMPARE( registry->userCrsList().at( 1 ).name, QStringLiteral( "test2" ) );
QCOMPARE( registry->userCrsList().at( 1 ).proj, QStringLiteral( "+proj=aea +lat_1=20 +lat_2=-23 +lat_0=4 +lon_0=29 +x_0=10 +y_0=3 +datum=WGS84 +units=m +no_defs" ) );
QVERIFY( registry->userCrsList().at( 1 ).wkt.isEmpty() );
QCOMPARE( registry->userCrsList().at( 1 ).crs.toProj(), QStringLiteral( "+proj=aea +lat_1=20 +lat_2=-23 +lat_0=4 +lon_0=29 +x_0=10 +y_0=3 +datum=WGS84 +units=m +no_defs" ) );
QCOMPARE( registry->userCrsList().at( 2 ).id, 100002L );
QCOMPARE( registry->userCrsList().at( 2 ).name, QStringLiteral( "test 2" ) );
QCOMPARE( registry->userCrsList().at( 2 ).proj, QStringLiteral( "+proj=aea +lat_1=22.5 +lat_2=-24.5 +lat_0=4 +lon_0=29 +x_0=10 +y_0=3 +datum=WGS84 +units=m +no_defs" ) );
QVERIFY( registry->userCrsList().at( 2 ).wkt.isEmpty() );

// doesn't exist anymore...
QgsCoordinateReferenceSystem crs3( authid );
QVERIFY( !crs3.isValid() );
Expand Down

0 comments on commit ddf6245

Please sign in to comment.