Create a QAbstractItemModel and proxy model for displaying tree

view of coordinate reference systems
This commit is contained in:
Nyall Dawson 2023-07-10 11:38:44 +10:00
parent ce95f9a522
commit 731123a425
10 changed files with 1861 additions and 4 deletions

View File

@ -0,0 +1,3 @@
# The following has been generated automatically from src/gui/qgscoordinatereferencesystemmodel.h
QgsCoordinateReferenceSystemProxyModel.Filters.baseClass = QgsCoordinateReferenceSystemProxyModel
Filters = QgsCoordinateReferenceSystemProxyModel # dirty hack since SIP seems to introduce the flags in module

View File

@ -0,0 +1,172 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgscoordinatereferencesystemmodel.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsCoordinateReferenceSystemModel : QAbstractItemModel
{
%Docstring(signature="appended")
A tree model for display of known coordinate reference systems.
.. versionadded:: 3.34
%End
%TypeHeaderCode
#include "qgscoordinatereferencesystemmodel.h"
%End
public:
enum Roles
{
RoleNodeType,
RoleName,
RoleAuthId,
RoleDeprecated,
RoleType,
RoleGroupId,
RoleWkt,
RoleProj,
};
QgsCoordinateReferenceSystemModel( QObject *parent = 0 );
virtual Qt::ItemFlags flags( const QModelIndex &index ) const;
virtual QVariant data( const QModelIndex &index, int role ) const;
virtual QVariant headerData( int section, Qt::Orientation orientation, int role ) const;
virtual int rowCount( const QModelIndex &parent = QModelIndex() ) const;
virtual int columnCount( const QModelIndex & = QModelIndex() ) const;
virtual QModelIndex index( int row, int column, const QModelIndex &parent = QModelIndex() ) const;
virtual QModelIndex parent( const QModelIndex &index ) const;
void addCustomCrs( const QgsCoordinateReferenceSystem &crs );
%Docstring
Adds a custom ``crs`` to the model.
This method can be used to add CRS which aren't present in either the standard PROJ SRS database or the
user's custom CRS database to the model.
%End
};
class QgsCoordinateReferenceSystemProxyModel: QSortFilterProxyModel
{
%Docstring(signature="appended")
A sort/filter proxy model for coordinate reference systems.
.. versionadded:: 3.34
%End
%TypeHeaderCode
#include "qgscoordinatereferencesystemmodel.h"
%End
public:
enum Filter
{
FilterHorizontal,
FilterVertical,
FilterCompound,
};
typedef QFlags<QgsCoordinateReferenceSystemProxyModel::Filter> Filters;
explicit QgsCoordinateReferenceSystemProxyModel( QObject *parent /TransferThis/ = 0 );
%Docstring
Constructor for QgsCoordinateReferenceSystemProxyModel, with the given ``parent`` object.
%End
QgsCoordinateReferenceSystemModel *coordinateReferenceSystemModel();
%Docstring
Returns the underlying source model.
%End
void setFilters( QgsCoordinateReferenceSystemProxyModel::Filters filters );
%Docstring
Set ``filters`` that affect how CRS are filtered.
.. seealso:: :py:func:`filters`
%End
Filters filters() const;
%Docstring
Returns any filters that affect how CRS are filtered.
.. seealso:: :py:func:`setFilters`
%End
void setFilterString( const QString &filter );
%Docstring
Sets a ``filter`` string, such that only coordinate reference systems matching the
specified string will be shown.
.. seealso:: :py:func:`filterString`
%End
QString filterString() const;
%Docstring
Returns the current filter string, if set.
.. seealso:: :py:func:`setFilterString`
%End
void setFilterAuthIds( const QStringList &filter );
%Docstring
Sets a ``filter`` list of CRS auth ID strings, such that only coordinate reference systems matching the
specified auth IDs will be shown.
.. seealso:: :py:func:`filterAuthIds`
%End
QStringList filterAuthIds() const;
%Docstring
Returns the current filter list of auth ID strings, if set.
.. seealso:: :py:func:`setFilterString`
%End
void setFilterDeprecated( bool filter );
%Docstring
Sets whether deprecated CRS should be filtered from the results.
.. seealso:: :py:func:`filterDeprecated`
%End
bool filterDeprecated() const;
%Docstring
Returns whether deprecated CRS will be filtered from the results.
.. seealso:: :py:func:`setFilterDeprecated`
%End
virtual bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const;
virtual bool lessThan( const QModelIndex &left, const QModelIndex &right ) const;
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgscoordinatereferencesystemmodel.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -38,6 +38,7 @@
%Include auto_generated/qgsconfigureshortcutsdialog.sip %Include auto_generated/qgsconfigureshortcutsdialog.sip
%Include auto_generated/qgscoordinateboundspreviewmapwidget.sip %Include auto_generated/qgscoordinateboundspreviewmapwidget.sip
%Include auto_generated/qgscoordinateoperationwidget.sip %Include auto_generated/qgscoordinateoperationwidget.sip
%Include auto_generated/qgscoordinatereferencesystemmodel.sip
%Include auto_generated/qgscredentialdialog.sip %Include auto_generated/qgscredentialdialog.sip
%Include auto_generated/qgscrsdefinitionwidget.sip %Include auto_generated/qgscrsdefinitionwidget.sip
%Include auto_generated/qgscurveeditorwidget.sip %Include auto_generated/qgscurveeditorwidget.sip

View File

@ -235,7 +235,7 @@ bool QgsCoordinateReferenceSystemRegistry::updateUserCrs( long id, const QgsCoor
if ( res ) if ( res )
{ {
emit userCrsChanged( crs.d->mAuthId ); emit userCrsChanged( QStringLiteral( "USER:%1" ).arg( id ) );
emit crsDefinitionsChanged(); emit crsDefinitionsChanged();
} }
@ -446,7 +446,7 @@ QList<QgsCrsDbRecord> QgsCoordinateReferenceSystemRegistry::crsDbRecords() const
int result = database.open_v2( srsDatabaseFileName, SQLITE_OPEN_READONLY, nullptr ); int result = database.open_v2( srsDatabaseFileName, SQLITE_OPEN_READONLY, nullptr );
if ( result == SQLITE_OK ) if ( result == SQLITE_OK )
{ {
const QString sql = QStringLiteral( "select description, srs_id, auth_name, auth_id, name, deprecated, srs_type from vw_srs" ); const QString sql = QStringLiteral( "select description, srs_id, auth_name, auth_id, projection_acronym, deprecated, srs_type from tbl_srs" );
sqlite3_statement_unique_ptr preparedStatement = database.prepare( sql, result ); sqlite3_statement_unique_ptr preparedStatement = database.prepare( sql, result );
if ( result == SQLITE_OK ) if ( result == SQLITE_OK )
{ {
@ -457,7 +457,7 @@ QList<QgsCrsDbRecord> QgsCoordinateReferenceSystemRegistry::crsDbRecords() const
record.srsId = preparedStatement.columnAsText( 1 ); record.srsId = preparedStatement.columnAsText( 1 );
record.authName = preparedStatement.columnAsText( 2 ); record.authName = preparedStatement.columnAsText( 2 );
record.authId = preparedStatement.columnAsText( 3 ); record.authId = preparedStatement.columnAsText( 3 );
record.name = preparedStatement.columnAsText( 4 ); record.projectionAcronym = preparedStatement.columnAsText( 4 );
record.deprecated = preparedStatement.columnAsText( 5 ).toInt(); record.deprecated = preparedStatement.columnAsText( 5 ).toInt();
record.type = qgsEnumKeyToValue( preparedStatement.columnAsText( 6 ), Qgis::CrsType::Unknown ); record.type = qgsEnumKeyToValue( preparedStatement.columnAsText( 6 ), Qgis::CrsType::Unknown );
mCrsDbRecords.append( record ); mCrsDbRecords.append( record );

View File

@ -41,7 +41,7 @@ class QgsProjOperation;
struct CORE_EXPORT QgsCrsDbRecord struct CORE_EXPORT QgsCrsDbRecord
{ {
QString description; QString description;
QString name; QString projectionAcronym;
QString srsId; QString srsId;
QString authName; QString authName;
QString authId; QString authId;

View File

@ -515,6 +515,7 @@ set(QGIS_GUI_SRCS
qgsconfigureshortcutsdialog.cpp qgsconfigureshortcutsdialog.cpp
qgscoordinateboundspreviewmapwidget.cpp qgscoordinateboundspreviewmapwidget.cpp
qgscoordinateoperationwidget.cpp qgscoordinateoperationwidget.cpp
qgscoordinatereferencesystemmodel.cpp
qgscrsdefinitionwidget.cpp qgscrsdefinitionwidget.cpp
qgscredentialdialog.cpp qgscredentialdialog.cpp
qgscustomdrophandler.cpp qgscustomdrophandler.cpp
@ -786,6 +787,7 @@ set(QGIS_GUI_HDRS
qgsconfigureshortcutsdialog.h qgsconfigureshortcutsdialog.h
qgscoordinateboundspreviewmapwidget.h qgscoordinateboundspreviewmapwidget.h
qgscoordinateoperationwidget.h qgscoordinateoperationwidget.h
qgscoordinatereferencesystemmodel.h
qgscredentialdialog.h qgscredentialdialog.h
qgscrsdefinitionwidget.h qgscrsdefinitionwidget.h
qgscurveeditorwidget.h qgscurveeditorwidget.h

View File

@ -0,0 +1,739 @@
/***************************************************************************
qgscoordinatereferencesystemmodel.h
-------------------
begin : July 2023
copyright : (C) 2023 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 "qgscoordinatereferencesystemmodel.h"
#include "qgscoordinatereferencesystemutils.h"
#include "qgsapplication.h"
#include <QFont>
QgsCoordinateReferenceSystemModel::QgsCoordinateReferenceSystemModel( QObject *parent )
: QAbstractItemModel( parent )
, mRootNode( std::make_unique< QgsCoordinateReferenceSystemModelGroupNode >( QString(), QIcon(), QString() ) )
{
mCrsDbRecords = QgsApplication::coordinateReferenceSystemRegistry()->crsDbRecords();
rebuild();
connect( QgsApplication::coordinateReferenceSystemRegistry(), &QgsCoordinateReferenceSystemRegistry::userCrsAdded, this, &QgsCoordinateReferenceSystemModel::userCrsAdded );
connect( QgsApplication::coordinateReferenceSystemRegistry(), &QgsCoordinateReferenceSystemRegistry::userCrsRemoved, this, &QgsCoordinateReferenceSystemModel::userCrsRemoved );
connect( QgsApplication::coordinateReferenceSystemRegistry(), &QgsCoordinateReferenceSystemRegistry::userCrsChanged, this, &QgsCoordinateReferenceSystemModel::userCrsChanged );
}
Qt::ItemFlags QgsCoordinateReferenceSystemModel::flags( const QModelIndex &index ) const
{
if ( !index.isValid() )
{
return Qt::ItemFlags();
}
QgsCoordinateReferenceSystemModelNode *n = index2node( index );
if ( !n )
return Qt::ItemFlags();
switch ( n->nodeType() )
{
case QgsCoordinateReferenceSystemModelNode::NodeGroup:
return index.column() == 0 ? Qt::ItemIsEnabled : Qt::ItemFlags();
case QgsCoordinateReferenceSystemModelNode::NodeCrs:
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}
BUILTIN_UNREACHABLE
}
QVariant QgsCoordinateReferenceSystemModel::data( const QModelIndex &index, int role ) const
{
if ( !index.isValid() )
return QVariant();
QgsCoordinateReferenceSystemModelNode *n = index2node( index );
if ( !n )
return QVariant();
if ( role == RoleNodeType )
return n->nodeType();
switch ( n->nodeType() )
{
case QgsCoordinateReferenceSystemModelNode::NodeGroup:
{
QgsCoordinateReferenceSystemModelGroupNode *groupNode = qgis::down_cast< QgsCoordinateReferenceSystemModelGroupNode * >( n );
switch ( role )
{
case Qt::DecorationRole:
switch ( index.column() )
{
case 0:
return groupNode->icon();
default:
break;
}
break;
case Qt::DisplayRole:
case Qt::ToolTipRole:
switch ( index.column() )
{
case 0:
return groupNode->name();
default:
break;
}
break;
case Qt::FontRole:
{
QFont font;
font.setItalic( true );
if ( groupNode->parent() == mRootNode.get() )
{
font.setBold( true );
}
return font;
}
case RoleGroupId:
return groupNode->id();
}
return QVariant();
}
case QgsCoordinateReferenceSystemModelNode::NodeCrs:
{
QgsCoordinateReferenceSystemModelCrsNode *crsNode = qgis::down_cast< QgsCoordinateReferenceSystemModelCrsNode * >( n );
switch ( role )
{
case Qt::DisplayRole:
case Qt::ToolTipRole:
switch ( index.column() )
{
case 0:
return crsNode->record().description;
case 1:
{
if ( crsNode->record().authName == QLatin1String( "CUSTOM" ) )
return QString();
return QStringLiteral( "%1:%2" ).arg( crsNode->record().authName, crsNode->record().authId );
}
default:
break;
}
break;
case RoleName:
return crsNode->record().description;
case RoleAuthId:
return QStringLiteral( "%1:%2" ).arg( crsNode->record().authName, crsNode->record().authId );
case RoleDeprecated:
return crsNode->record().deprecated;
case RoleType:
return QVariant::fromValue( crsNode->record().type );
case RoleWkt:
return crsNode->wkt();
case RoleProj:
return crsNode->proj();
default:
break;
}
}
}
return QVariant();
}
QVariant QgsCoordinateReferenceSystemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal )
{
switch ( role )
{
case Qt::DisplayRole:
switch ( section )
{
case 0:
return tr( "Coordinate Reference System" );
case 1:
return tr( "Authority ID" );
default:
break;
}
break;
default:
break;
}
}
return QVariant();
}
int QgsCoordinateReferenceSystemModel::rowCount( const QModelIndex &parent ) const
{
QgsCoordinateReferenceSystemModelNode *n = index2node( parent );
if ( !n )
return 0;
return n->children().count();
}
int QgsCoordinateReferenceSystemModel::columnCount( const QModelIndex & ) const
{
return 2;
}
QModelIndex QgsCoordinateReferenceSystemModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( !hasIndex( row, column, parent ) )
return QModelIndex();
QgsCoordinateReferenceSystemModelNode *n = index2node( parent );
if ( !n )
return QModelIndex(); // have no children
return createIndex( row, column, n->children().at( row ) );
}
QModelIndex QgsCoordinateReferenceSystemModel::parent( const QModelIndex &child ) const
{
if ( !child.isValid() )
return QModelIndex();
if ( QgsCoordinateReferenceSystemModelNode *n = index2node( child ) )
{
return indexOfParentTreeNode( n->parent() ); // must not be null
}
else
{
Q_ASSERT( false ); // no other node types!
return QModelIndex();
}
}
void QgsCoordinateReferenceSystemModel::rebuild()
{
beginResetModel();
mRootNode->deleteChildren();
for ( const QgsCrsDbRecord &record : std::as_const( mCrsDbRecords ) )
{
addRecord( record );
}
const QList<QgsCoordinateReferenceSystemRegistry::UserCrsDetails> userCrsList = QgsApplication::coordinateReferenceSystemRegistry()->userCrsList();
for ( const QgsCoordinateReferenceSystemRegistry::UserCrsDetails &details : userCrsList )
{
QgsCrsDbRecord userRecord;
userRecord.authName = QStringLiteral( "USER" );
userRecord.authId = QString::number( details.id );
userRecord.description = details.name;
addRecord( userRecord );
}
endResetModel();
}
void QgsCoordinateReferenceSystemModel::userCrsAdded( const QString &id )
{
const QList<QgsCoordinateReferenceSystemRegistry::UserCrsDetails> userCrsList = QgsApplication::coordinateReferenceSystemRegistry()->userCrsList();
for ( const QgsCoordinateReferenceSystemRegistry::UserCrsDetails &details : userCrsList )
{
if ( QStringLiteral( "USER:%1" ).arg( details.id ) == id )
{
QgsCrsDbRecord userRecord;
userRecord.authName = QStringLiteral( "USER" );
userRecord.authId = QString::number( details.id );
userRecord.description = details.name;
QgsCoordinateReferenceSystemModelGroupNode *group = mRootNode->getChildGroupNode( QStringLiteral( "USER" ) );
if ( !group )
{
std::unique_ptr< QgsCoordinateReferenceSystemModelGroupNode > newGroup = std::make_unique< QgsCoordinateReferenceSystemModelGroupNode >(
tr( "User Defined Coordinate Systems" ),
QgsApplication::getThemeIcon( QStringLiteral( "/user.svg" ) ), QStringLiteral( "USER" ) );
beginInsertRows( QModelIndex(), mRootNode->children().length(), mRootNode->children().length() );
mRootNode->addChildNode( newGroup.get() );
endInsertRows();
group = newGroup.release();
}
const QModelIndex parentGroupIndex = node2index( group );
beginInsertRows( parentGroupIndex, group->children().size(), group->children().size() );
QgsCoordinateReferenceSystemModelCrsNode *crsNode = addRecord( userRecord );
crsNode->setProj( details.proj );
crsNode->setWkt( details.wkt );
endInsertRows();
break;
}
}
}
void QgsCoordinateReferenceSystemModel::userCrsRemoved( long id )
{
QgsCoordinateReferenceSystemModelGroupNode *group = mRootNode->getChildGroupNode( QStringLiteral( "USER" ) );
if ( group )
{
for ( int row = 0; row < group->children().size(); ++row )
{
if ( QgsCoordinateReferenceSystemModelCrsNode *crsNode = dynamic_cast< QgsCoordinateReferenceSystemModelCrsNode * >( group->children().at( row ) ) )
{
if ( crsNode->record().authId == QString::number( id ) )
{
const QModelIndex parentIndex = node2index( group );
beginRemoveRows( parentIndex, row, row );
delete group->takeChild( crsNode );
endRemoveRows();
return;
}
}
}
}
}
void QgsCoordinateReferenceSystemModel::userCrsChanged( const QString &id )
{
QgsCoordinateReferenceSystemModelGroupNode *group = mRootNode->getChildGroupNode( QStringLiteral( "USER" ) );
if ( group )
{
for ( int row = 0; row < group->children().size(); ++row )
{
if ( QgsCoordinateReferenceSystemModelCrsNode *crsNode = dynamic_cast< QgsCoordinateReferenceSystemModelCrsNode * >( group->children().at( row ) ) )
{
if ( QStringLiteral( "USER:%1" ).arg( crsNode->record().authId ) == id )
{
// treat a change as a remove + add operation
const QModelIndex parentIndex = node2index( group );
beginRemoveRows( parentIndex, row, row );
delete group->takeChild( crsNode );
endRemoveRows();
userCrsAdded( id );
return;
}
}
}
}
}
QgsCoordinateReferenceSystemModelCrsNode *QgsCoordinateReferenceSystemModel::addRecord( const QgsCrsDbRecord &record )
{
QgsCoordinateReferenceSystemModelGroupNode *parentNode = mRootNode.get();
std::unique_ptr< QgsCoordinateReferenceSystemModelCrsNode > crsNode = std::make_unique< QgsCoordinateReferenceSystemModelCrsNode>( record );
QString groupName;
QString groupId;
QIcon groupIcon;
if ( record.authName == QLatin1String( "USER" ) )
{
groupName = tr( "User Defined Coordinate Systems" );
groupId = QStringLiteral( "USER" );
groupIcon = QgsApplication::getThemeIcon( QStringLiteral( "/user.svg" ) );
}
else if ( record.authName == QLatin1String( "CUSTOM" ) )
{
// the group is guaranteed to exist at this point
groupId = QStringLiteral( "CUSTOM" );
}
else
{
groupId = qgsEnumValueToKey( record.type );
switch ( record.type )
{
case Qgis::CrsType::Unknown:
break;
case Qgis::CrsType::Geodetic:
groupName = tr( "Geodetic" );
groupIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconProjectionEnabled.svg" ) );
break;
case Qgis::CrsType::Geocentric:
groupName = tr( "Geocentric" );
groupIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconProjectionEnabled.svg" ) );
break;
case Qgis::CrsType::Geographic2d:
groupName = tr( "Geographic (2D)" );
groupIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconProjectionEnabled.svg" ) );
break;
case Qgis::CrsType::Geographic3d:
groupName = tr( "Geographic (3D)" );
groupIcon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconProjectionEnabled.svg" ) );
break;
case Qgis::CrsType::Vertical:
groupName = tr( "Vertical" );
break;
case Qgis::CrsType::Projected:
case Qgis::CrsType::DerivedProjected:
groupName = tr( "Projected" );
groupIcon = QgsApplication::getThemeIcon( QStringLiteral( "/transformed.svg" ) );
break;
case Qgis::CrsType::Compound:
groupName = tr( "Compound" );
break;
case Qgis::CrsType::Temporal:
groupName = tr( "Temporal" );
break;
case Qgis::CrsType::Engineering:
groupName = tr( "Engineering" );
break;
case Qgis::CrsType::Bound:
groupName = tr( "Bound" );
break;
case Qgis::CrsType::Other:
groupName = tr( "Other" );
break;
}
}
if ( QgsCoordinateReferenceSystemModelGroupNode *group = parentNode->getChildGroupNode( groupId ) )
{
parentNode = group;
}
else
{
std::unique_ptr< QgsCoordinateReferenceSystemModelGroupNode > newGroup = std::make_unique< QgsCoordinateReferenceSystemModelGroupNode >( groupName, groupIcon, groupId );
parentNode->addChildNode( newGroup.get() );
parentNode = newGroup.release();
}
if ( ( record.authName != QLatin1String( "USER" ) && record.authName != QLatin1String( "CUSTOM" ) ) && ( record.type == Qgis::CrsType::Projected || record.type == Qgis::CrsType::DerivedProjected ) )
{
QString projectionName = QgsCoordinateReferenceSystemUtils::translateProjection( record.projectionAcronym );
if ( projectionName.isEmpty() )
projectionName = tr( "Other" );
if ( QgsCoordinateReferenceSystemModelGroupNode *group = parentNode->getChildGroupNode( record.projectionAcronym ) )
{
parentNode = group;
}
else
{
std::unique_ptr< QgsCoordinateReferenceSystemModelGroupNode > newGroup = std::make_unique< QgsCoordinateReferenceSystemModelGroupNode >( projectionName, QIcon(), record.projectionAcronym );
parentNode->addChildNode( newGroup.get() );
parentNode = newGroup.release();
}
}
parentNode->addChildNode( crsNode.get() );
return crsNode.release();
}
void QgsCoordinateReferenceSystemModel::addCustomCrs( const QgsCoordinateReferenceSystem &crs )
{
QgsCrsDbRecord userRecord;
userRecord.authName = QStringLiteral( "CUSTOM" );
userRecord.description = crs.description().isEmpty() ? tr( "Custom CRS" ) : crs.description();
userRecord.type = crs.type();
QgsCoordinateReferenceSystemModelGroupNode *group = mRootNode->getChildGroupNode( QStringLiteral( "CUSTOM" ) );
if ( !group )
{
std::unique_ptr< QgsCoordinateReferenceSystemModelGroupNode > newGroup = std::make_unique< QgsCoordinateReferenceSystemModelGroupNode >(
tr( "Custom Coordinate Systems" ),
QgsApplication::getThemeIcon( QStringLiteral( "/user.svg" ) ), QStringLiteral( "CUSTOM" ) );
beginInsertRows( QModelIndex(), mRootNode->children().length(), mRootNode->children().length() );
mRootNode->addChildNode( newGroup.get() );
endInsertRows();
group = newGroup.release();
}
const QModelIndex parentGroupIndex = node2index( group );
beginInsertRows( parentGroupIndex, group->children().size(), group->children().size() );
QgsCoordinateReferenceSystemModelCrsNode *node = addRecord( userRecord );
node->setWkt( crs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ) );
node->setProj( crs.toProj() );
endInsertRows();
}
QgsCoordinateReferenceSystemModelNode *QgsCoordinateReferenceSystemModel::index2node( const QModelIndex &index ) const
{
if ( !index.isValid() )
return mRootNode.get();
return reinterpret_cast<QgsCoordinateReferenceSystemModelNode *>( index.internalPointer() );
}
QModelIndex QgsCoordinateReferenceSystemModel::node2index( QgsCoordinateReferenceSystemModelNode *node ) const
{
if ( !node || !node->parent() )
return QModelIndex(); // this is the only root item -> invalid index
QModelIndex parentIndex = node2index( node->parent() );
int row = node->parent()->children().indexOf( node );
Q_ASSERT( row >= 0 );
return index( row, 0, parentIndex );
}
QModelIndex QgsCoordinateReferenceSystemModel::indexOfParentTreeNode( QgsCoordinateReferenceSystemModelNode *parentNode ) const
{
Q_ASSERT( parentNode );
QgsCoordinateReferenceSystemModelNode *grandParentNode = parentNode->parent();
if ( !grandParentNode )
return QModelIndex(); // root node -> invalid index
int row = grandParentNode->children().indexOf( parentNode );
Q_ASSERT( row >= 0 );
return createIndex( row, 0, parentNode );
}
///@cond PRIVATE
QgsCoordinateReferenceSystemModelNode::~QgsCoordinateReferenceSystemModelNode() = default;
QgsCoordinateReferenceSystemModelNode *QgsCoordinateReferenceSystemModelNode::takeChild( QgsCoordinateReferenceSystemModelNode *node )
{
return mChildren.takeAt( mChildren.indexOf( node ) );
}
void QgsCoordinateReferenceSystemModelNode::addChildNode( QgsCoordinateReferenceSystemModelNode *node )
{
if ( !node )
return;
Q_ASSERT( !node->mParent );
node->mParent = this;
mChildren.append( node );
}
void QgsCoordinateReferenceSystemModelNode::deleteChildren()
{
qDeleteAll( mChildren );
mChildren.clear();
}
QgsCoordinateReferenceSystemModelGroupNode *QgsCoordinateReferenceSystemModelNode::getChildGroupNode( const QString &id )
{
for ( QgsCoordinateReferenceSystemModelNode *node : std::as_const( mChildren ) )
{
if ( node->nodeType() == NodeGroup )
{
QgsCoordinateReferenceSystemModelGroupNode *groupNode = qgis::down_cast< QgsCoordinateReferenceSystemModelGroupNode * >( node );
if ( groupNode && groupNode->id() == id )
return groupNode;
}
}
return nullptr;
}
QgsCoordinateReferenceSystemModelGroupNode::QgsCoordinateReferenceSystemModelGroupNode( const QString &name, const QIcon &icon, const QString &id )
: mId( id )
, mName( name )
, mIcon( icon )
{
}
QgsCoordinateReferenceSystemModelCrsNode::QgsCoordinateReferenceSystemModelCrsNode( const QgsCrsDbRecord &record )
: mRecord( record )
{
}
///@endcond
//
// QgsCoordinateReferenceSystemProxyModel
//
QgsCoordinateReferenceSystemProxyModel::QgsCoordinateReferenceSystemProxyModel( QObject *parent )
: QSortFilterProxyModel( parent )
, mModel( new QgsCoordinateReferenceSystemModel( this ) )
{
setSourceModel( mModel );
setDynamicSortFilter( true );
setSortLocaleAware( true );
setFilterCaseSensitivity( Qt::CaseInsensitive );
setRecursiveFilteringEnabled( true );
sort( 0 );
}
QgsCoordinateReferenceSystemModel *QgsCoordinateReferenceSystemProxyModel::coordinateReferenceSystemModel()
{
return mModel;
}
const QgsCoordinateReferenceSystemModel *QgsCoordinateReferenceSystemProxyModel::coordinateReferenceSystemModel() const
{
return mModel;
}
void QgsCoordinateReferenceSystemProxyModel::setFilters( QgsCoordinateReferenceSystemProxyModel::Filters filters )
{
if ( mFilters == filters )
return;
mFilters = filters;
invalidateFilter();
}
void QgsCoordinateReferenceSystemProxyModel::setFilterString( const QString &filter )
{
mFilterString = filter;
invalidateFilter();
}
void QgsCoordinateReferenceSystemProxyModel::setFilterAuthIds( const QStringList &filter )
{
if ( mFilterAuthIds == filter )
return;
mFilterAuthIds.clear();
mFilterAuthIds.reserve( filter.size() );
for ( const QString &id : filter )
{
mFilterAuthIds.insert( id.toUpper() );
}
invalidateFilter();
}
void QgsCoordinateReferenceSystemProxyModel::setFilterDeprecated( bool filter )
{
if ( mFilterDeprecated == filter )
return;
mFilterDeprecated = filter;
invalidateFilter();
}
bool QgsCoordinateReferenceSystemProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const
{
if ( mFilterString.trimmed().isEmpty() && !mFilters && !mFilterDeprecated && mFilterAuthIds.isEmpty() )
return true;
const QModelIndex sourceIndex = mModel->index( sourceRow, 0, sourceParent );
const QgsCoordinateReferenceSystemModelNode::NodeType nodeType = static_cast< QgsCoordinateReferenceSystemModelNode::NodeType >( sourceModel()->data( sourceIndex, QgsCoordinateReferenceSystemModel::RoleNodeType ).toInt() );
switch ( nodeType )
{
case QgsCoordinateReferenceSystemModelNode::NodeGroup:
return false;
case QgsCoordinateReferenceSystemModelNode::NodeCrs:
break;
}
const bool deprecated = sourceModel()->data( sourceIndex, QgsCoordinateReferenceSystemModel::RoleDeprecated ).toBool();
if ( mFilterDeprecated && deprecated )
return false;
if ( mFilters )
{
const Qgis::CrsType type = sourceModel()->data( sourceIndex, QgsCoordinateReferenceSystemModel::RoleType ).value< Qgis::CrsType >();
switch ( type )
{
case Qgis::CrsType::Unknown:
case Qgis::CrsType::Other:
break;
case Qgis::CrsType::Geodetic:
case Qgis::CrsType::Geocentric:
case Qgis::CrsType::Geographic2d:
case Qgis::CrsType::Geographic3d:
case Qgis::CrsType::Projected:
case Qgis::CrsType::Temporal:
case Qgis::CrsType::Engineering:
case Qgis::CrsType::Bound:
case Qgis::CrsType::DerivedProjected:
if ( !mFilters.testFlag( Filter::FilterHorizontal ) )
return false;
break;
case Qgis::CrsType::Vertical:
if ( !mFilters.testFlag( Filter::FilterVertical ) )
return false;
break;
case Qgis::CrsType::Compound:
if ( !mFilters.testFlag( Filter::FilterCompound ) )
return false;
break;
}
}
const QString authid = sourceModel()->data( sourceIndex, QgsCoordinateReferenceSystemModel::RoleAuthId ).toString();
if ( !mFilterAuthIds.isEmpty() )
{
if ( !mFilterAuthIds.contains( authid.toUpper() ) )
return false;
}
if ( !mFilterString.trimmed().isEmpty() )
{
const QString name = sourceModel()->data( sourceIndex, QgsCoordinateReferenceSystemModel::RoleName ).toString();
if ( !( name.contains( mFilterString, Qt::CaseInsensitive )
|| authid.contains( mFilterString, Qt::CaseInsensitive ) ) )
return false;
}
return true;
}
bool QgsCoordinateReferenceSystemProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
{
QgsCoordinateReferenceSystemModelNode::NodeType leftType = static_cast< QgsCoordinateReferenceSystemModelNode::NodeType >( sourceModel()->data( left, QgsCoordinateReferenceSystemModel::RoleNodeType ).toInt() );
QgsCoordinateReferenceSystemModelNode::NodeType rightType = static_cast< QgsCoordinateReferenceSystemModelNode::NodeType >( sourceModel()->data( right, QgsCoordinateReferenceSystemModel::RoleNodeType ).toInt() );
if ( leftType != rightType )
{
if ( leftType == QgsCoordinateReferenceSystemModelNode::NodeGroup )
return true;
else if ( rightType == QgsCoordinateReferenceSystemModelNode::NodeGroup )
return false;
}
const QString leftStr = sourceModel()->data( left ).toString().toLower();
const QString rightStr = sourceModel()->data( right ).toString().toLower();
if ( leftType == QgsCoordinateReferenceSystemModelNode::NodeGroup )
{
// both are groups -- ensure USER group comes last, and CUSTOM group comes first
const QString leftGroupId = sourceModel()->data( left, QgsCoordinateReferenceSystemModel::RoleGroupId ).toString();
const QString rightGroupId = sourceModel()->data( left, QgsCoordinateReferenceSystemModel::RoleGroupId ).toString();
if ( leftGroupId == QLatin1String( "USER" ) )
return false;
if ( rightGroupId == QLatin1String( "USER" ) )
return true;
if ( leftGroupId == QLatin1String( "CUSTOM" ) )
return true;
if ( rightGroupId == QLatin1String( "CUSTOM" ) )
return false;
}
// default sort is alphabetical order
return QString::localeAwareCompare( leftStr, rightStr ) < 0;
}

View File

@ -0,0 +1,391 @@
/***************************************************************************
qgscoordinatereferencesystemmodel.h
-------------------
begin : July 2023
copyright : (C) 2023 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 QGSCOORDINATEREFERENCESYSTEMMODEL_H
#define QGSCOORDINATEREFERENCESYSTEMMODEL_H
#include "qgis_gui.h"
#include "qgis_sip.h"
#include "qgis.h"
#include "qgscoordinatereferencesystemregistry.h"
#include <QAbstractItemModel>
#include <QSortFilterProxyModel>
#include <QIcon>
class QgsCoordinateReferenceSystem;
class QgsCoordinateReferenceSystemModelGroupNode;
///@cond PRIVATE
#ifndef SIP_RUN
/**
* \brief Abstract base class for nodes contained within a QgsCoordinateReferenceSystemModel.
* \warning Not part of stable API and may change in future QGIS releases.
* \ingroup gui
* \since QGIS 3.34
*/
class GUI_EXPORT QgsCoordinateReferenceSystemModelNode
{
public:
//! Enumeration of possible model node types
enum NodeType
{
NodeGroup, //!< Group node
NodeCrs, //!< CRS node
};
virtual ~QgsCoordinateReferenceSystemModelNode();
/**
* Returns the node's type.
*/
virtual NodeType nodeType() const = 0;
/**
* Returns the node's parent. If the node's parent is NULLPTR, then the node is a root node.
*/
QgsCoordinateReferenceSystemModelNode *parent() { return mParent; }
/**
* Returns a list of children belonging to the node.
*/
QList<QgsCoordinateReferenceSystemModelNode *> children() { return mChildren; }
/**
* Returns a list of children belonging to the node.
*/
QList<QgsCoordinateReferenceSystemModelNode *> children() const { return mChildren; }
/**
* Removes the specified \a node from this node's children, and gives
* ownership back to the caller.
*/
QgsCoordinateReferenceSystemModelNode *takeChild( QgsCoordinateReferenceSystemModelNode *node );
/**
* Adds a child \a node to this node, transferring ownership of the node
* to this node.
*/
void addChildNode( QgsCoordinateReferenceSystemModelNode *node );
/**
* Deletes all child nodes from this node.
*/
void deleteChildren();
/**
* Tries to find a child node belonging to this node, which corresponds to
* a group node with the given group \a id. Returns NULLPTR if no matching
* child group node was found.
*/
QgsCoordinateReferenceSystemModelGroupNode *getChildGroupNode( const QString &id );
private:
QgsCoordinateReferenceSystemModelNode *mParent = nullptr;
QList<QgsCoordinateReferenceSystemModelNode *> mChildren;
};
/**
* \brief Coordinate reference system model node corresponding to a group
* \ingroup gui
* \warning Not available in Python bindings
* \since QGIS 3.34
*/
class GUI_EXPORT QgsCoordinateReferenceSystemModelGroupNode : public QgsCoordinateReferenceSystemModelNode
{
public:
/**
* Constructor for QgsCoordinateReferenceSystemModelGroupNode.
*/
QgsCoordinateReferenceSystemModelGroupNode( const QString &name, const QIcon &icon, const QString &id );
/**
* Returns the group's ID, which is non-translated.
*/
QString id() const { return mId; }
/**
* Returns the group's name, which is translated and user-visible.
*/
QString name() const { return mName; }
/**
* Returns the group's icon.
*/
QIcon icon() const { return mIcon; }
NodeType nodeType() const override { return NodeGroup; }
private:
QString mId;
QString mName;
QIcon mIcon;
};
/**
* \brief Coordinate reference system model node corresponding to a CRS.
* \ingroup gui
* \warning Not available in Python bindings.
* \since QGIS 3.44
*/
class GUI_EXPORT QgsCoordinateReferenceSystemModelCrsNode : public QgsCoordinateReferenceSystemModelNode
{
public:
/**
* Constructor for QgsCoordinateReferenceSystemModelCrsNode, associated
* with the specified \a record.
*/
QgsCoordinateReferenceSystemModelCrsNode( const QgsCrsDbRecord &record );
NodeType nodeType() const override { return NodeCrs; }
/**
* Returns the record associated with this node.
*/
const QgsCrsDbRecord &record() const { return mRecord; }
/**
* Sets the \a wkt representation of the CRS.
*
* This is only used for non-standard CRS (i.e. those not present in the database).
*
* \see wkt()
*/
void setWkt( const QString &wkt ) { mWkt = wkt; }
/**
* Returns the WKT representation of the CRS.
*
* This is only used for non-standard CRS (i.e. those not present in the database).
*
* \see setWkt()
*/
QString wkt() const { return mWkt; }
/**
* Sets the \a proj representation of the CRS.
*
* This is only used for non-standard CRS (i.e. those not present in the database).
*
* \see proj()
*/
void setProj( const QString &proj ) { mProj = proj; }
/**
* Returns the PROJ representation of the CRS.
*
* This is only used for non-standard CRS (i.e. those not present in the database).
*
* \see setProj()
*/
QString proj() const { return mProj; }
private:
const QgsCrsDbRecord mRecord;
QString mWkt;
QString mProj;
};
#endif
///@endcond
/**
* \class QgsCoordinateReferenceSystemModel
* \ingroup core
* \brief A tree model for display of known coordinate reference systems.
* \since QGIS 3.34
*/
class GUI_EXPORT QgsCoordinateReferenceSystemModel : public QAbstractItemModel
{
Q_OBJECT
public:
//! Custom roles used by the model
enum Roles
{
RoleNodeType = Qt::UserRole, //!< Corresponds to the node's type
RoleName = Qt::UserRole + 1, //!< The coordinate reference system name
RoleAuthId = Qt::UserRole + 2, //!< The coordinate reference system authority name and id
RoleDeprecated = Qt::UserRole + 3, //!< TRUE if the CRS is deprecated
RoleType = Qt::UserRole + 4, //!< The coordinate reference system type
RoleGroupId = Qt::UserRole + 5, //!< The node ID (for group nodes)
RoleWkt = Qt::UserRole + 6, //!< The coordinate reference system's WKT representation. This is only used for non-standard CRS (i.e. those not present in the database).
RoleProj = Qt::UserRole + 7, //!< The coordinate reference system's PROJ representation. This is only used for non-standard CRS (i.e. those not present in the database).
};
QgsCoordinateReferenceSystemModel( QObject *parent = nullptr );
Qt::ItemFlags flags( const QModelIndex &index ) const override;
QVariant data( const QModelIndex &index, int role ) const override;
QVariant headerData( int section, Qt::Orientation orientation, int role ) const override;
int rowCount( const QModelIndex &parent = QModelIndex() ) const override;
int columnCount( const QModelIndex & = QModelIndex() ) const override;
QModelIndex index( int row, int column, const QModelIndex &parent = QModelIndex() ) const override;
QModelIndex parent( const QModelIndex &index ) const override;
/**
* Adds a custom \a crs to the model.
*
* This method can be used to add CRS which aren't present in either the standard PROJ SRS database or the
* user's custom CRS database to the model.
*/
void addCustomCrs( const QgsCoordinateReferenceSystem &crs );
private slots:
void rebuild();
void userCrsAdded( const QString &id );
void userCrsRemoved( long id );
void userCrsChanged( const QString &id );
private:
QgsCoordinateReferenceSystemModelCrsNode *addRecord( const QgsCrsDbRecord &record );
QgsCoordinateReferenceSystemModelNode *index2node( const QModelIndex &index ) const;
QModelIndex node2index( QgsCoordinateReferenceSystemModelNode *node ) const;
QModelIndex indexOfParentTreeNode( QgsCoordinateReferenceSystemModelNode *parentNode ) const;
std::unique_ptr< QgsCoordinateReferenceSystemModelGroupNode > mRootNode;
QList< QgsCrsDbRecord > mCrsDbRecords;
};
/**
* \brief A sort/filter proxy model for coordinate reference systems.
*
* \ingroup gui
* \since QGIS 3.34
*/
class GUI_EXPORT QgsCoordinateReferenceSystemProxyModel: public QSortFilterProxyModel
{
Q_OBJECT
public:
//! Available filter flags for filtering the model
enum Filter
{
FilterHorizontal = 1 << 1, //!< Include horizontal CRS (excludes compound CRS containing a horizontal component)
FilterVertical = 1 << 2, //!< Include vertical CRS (excludes compound CRS containing a vertical component)
FilterCompound = 1 << 3, //!< Include compound CRS
};
Q_DECLARE_FLAGS( Filters, Filter )
Q_FLAG( Filters )
/**
* Constructor for QgsCoordinateReferenceSystemProxyModel, with the given \a parent object.
*/
explicit QgsCoordinateReferenceSystemProxyModel( QObject *parent SIP_TRANSFERTHIS = nullptr );
/**
* Returns the underlying source model.
*/
QgsCoordinateReferenceSystemModel *coordinateReferenceSystemModel();
/**
* Returns the underlying source model.
* \note Not available in Python bindings
*/
const QgsCoordinateReferenceSystemModel *coordinateReferenceSystemModel() const SIP_SKIP;
/**
* Set \a filters that affect how CRS are filtered.
* \see filters()
*/
void setFilters( QgsCoordinateReferenceSystemProxyModel::Filters filters );
/**
* Returns any filters that affect how CRS are filtered.
* \see setFilters()
*/
Filters filters() const { return mFilters; }
/**
* Sets a \a filter string, such that only coordinate reference systems matching the
* specified string will be shown.
*
* \see filterString()
*/
void setFilterString( const QString &filter );
/**
* Returns the current filter string, if set.
*
* \see setFilterString()
*/
QString filterString() const { return mFilterString; }
/**
* Sets a \a filter list of CRS auth ID strings, such that only coordinate reference systems matching the
* specified auth IDs will be shown.
*
* \see filterAuthIds()
*/
void setFilterAuthIds( const QStringList &filter );
/**
* Returns the current filter list of auth ID strings, if set.
*
* \see setFilterString()
*/
QStringList filterAuthIds() const { return mFilterAuthIds; }
/**
* Sets whether deprecated CRS should be filtered from the results.
*
* \see filterDeprecated()
*/
void setFilterDeprecated( bool filter );
/**
* Returns whether deprecated CRS will be filtered from the results.
*
* \see setFilterDeprecated()
*/
bool filterDeprecated() const { return mFilterDeprecated; }
bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const override;
bool lessThan( const QModelIndex &left, const QModelIndex &right ) const override;
private:
QgsCoordinateReferenceSystemModel *mModel = nullptr;
QString mFilterString;
QStringList mFilterAuthIds;
bool mFilterDeprecated = false;
Filters mFilters = Filters();
};
#endif // QGSCOORDINATEREFERENCESYSTEMMODEL_H

View File

@ -63,6 +63,7 @@ ADD_PYTHON_TEST(PyQgsCoordinateFormatter test_qgscoordinateformatter.py)
ADD_PYTHON_TEST(PyQgsCoordinateOperationWidget test_qgscoordinateoperationwidget.py) ADD_PYTHON_TEST(PyQgsCoordinateOperationWidget test_qgscoordinateoperationwidget.py)
ADD_PYTHON_TEST(PyQgsConditionalFormatWidgets test_qgsconditionalformatwidgets.py) ADD_PYTHON_TEST(PyQgsConditionalFormatWidgets test_qgsconditionalformatwidgets.py)
ADD_PYTHON_TEST(PyQgsCoordinateReferenceSystem test_qgscoordinatereferencesystem.py) ADD_PYTHON_TEST(PyQgsCoordinateReferenceSystem test_qgscoordinatereferencesystem.py)
ADD_PYTHON_TEST(PyQgsCoordinateReferenceSystemModel test_qgscoordinatereferencesystemmodel.py)
ADD_PYTHON_TEST(PyQgsCoordinateReferenceSystemUtils test_qgscoordinatereferencesystemutils.py) ADD_PYTHON_TEST(PyQgsCoordinateReferenceSystemUtils test_qgscoordinatereferencesystemutils.py)
ADD_PYTHON_TEST(PyQgsConditionalStyle test_qgsconditionalstyle.py) ADD_PYTHON_TEST(PyQgsConditionalStyle test_qgsconditionalstyle.py)
ADD_PYTHON_TEST(PyQgsConnectionRegistry test_qgsconnectionregistry.py) ADD_PYTHON_TEST(PyQgsConnectionRegistry test_qgsconnectionregistry.py)

View File

@ -0,0 +1,548 @@
"""QGIS Unit tests for QgsCoordinateReferenceSystemModel.
.. 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__ = '(C) 2022 by Nyall Dawson'
__date__ = '12/07/2023'
__copyright__ = 'Copyright 2023, The QGIS Project'
import qgis # NOQA
from qgis.PyQt.QtCore import (
Qt,
QModelIndex
)
from qgis.core import (
Qgis,
QgsApplication,
QgsCoordinateReferenceSystem,
QgsCoordinateReferenceSystemUtils,
)
from qgis.gui import (
QgsCoordinateReferenceSystemModel,
QgsCoordinateReferenceSystemProxyModel
)
import unittest
from qgis.testing import start_app, QgisTestCase
start_app()
class TestQgsCoordinateReferenceSystemModel(QgisTestCase):
def test_model(self):
model = QgsCoordinateReferenceSystemModel()
# top level items -- we expect to find Projected, Geographic (2D), Geographic (3D),
# Compound, Vertical amongst others
self.assertGreaterEqual(model.rowCount(QModelIndex()), 5)
top_level_items = [
model.data(model.index(row, 0, QModelIndex()), Qt.DisplayRole) for row in range(model.rowCount(QModelIndex()))
]
self.assertIn('Projected', top_level_items)
self.assertIn('Geographic (2D)', top_level_items)
self.assertIn('Geographic (3D)', top_level_items)
self.assertIn('Compound', top_level_items)
self.assertIn('Vertical', top_level_items)
# projection methods should not be at top level
self.assertNotIn('Cassini', top_level_items)
# user and custom groups should not be created until required
self.assertNotIn("User Defined Coordinate Systems", top_level_items)
self.assertNotIn("Custom Coordinate Systems", top_level_items)
# check group ids
top_level_item_group_ids = [
model.data(model.index(row, 0, QModelIndex()), QgsCoordinateReferenceSystemModel.RoleGroupId) for row in range(model.rowCount(QModelIndex()))
]
self.assertIn('Projected', top_level_item_group_ids)
self.assertIn('Geographic2d', top_level_item_group_ids)
self.assertIn('Geographic3d', top_level_item_group_ids)
self.assertIn('Compound', top_level_item_group_ids)
self.assertIn('Vertical', top_level_item_group_ids)
# find WGS84 in Geographic2d group
geographic_2d_index = [
model.index(row, 0, QModelIndex())
for row in range(model.rowCount(QModelIndex()))
if model.data(model.index(row, 0, QModelIndex()),
QgsCoordinateReferenceSystemModel.RoleGroupId) == 'Geographic2d'
][0]
# for proj 9, there's > 1300 crs in this group
self.assertGreaterEqual(model.rowCount(geographic_2d_index), 1000)
wgs84_index = [
model.index(row, 0, geographic_2d_index)
for row in range(model.rowCount(geographic_2d_index))
if model.data(model.index(row, 0, geographic_2d_index),
QgsCoordinateReferenceSystemModel.RoleAuthId) == 'EPSG:4326'
][0]
# test model roles
self.assertEqual(model.data(wgs84_index, Qt.DisplayRole), 'WGS 84')
self.assertEqual(model.data(model.index(wgs84_index.row(), 1, wgs84_index.parent()), Qt.DisplayRole), 'EPSG:4326')
self.assertEqual(model.data(wgs84_index, QgsCoordinateReferenceSystemModel.RoleName), 'WGS 84')
self.assertFalse(model.data(wgs84_index,
QgsCoordinateReferenceSystemModel.RoleDeprecated))
# the proj and wkt roles are only available for non-standard CRS
self.assertFalse(model.data(wgs84_index,
QgsCoordinateReferenceSystemModel.RoleWkt))
self.assertFalse(model.data(wgs84_index,
QgsCoordinateReferenceSystemModel.RoleProj))
# check that same result is returned by authIdToIndex
self.assertEqual(model.authIdToIndex('EPSG:4326'), wgs84_index)
# find EPSG:4329 in Geographic3d group (also tests a deprecated CRS)
geographic_3d_index = [
model.index(row, 0, QModelIndex())
for row in range(model.rowCount(QModelIndex()))
if model.data(model.index(row, 0, QModelIndex()),
QgsCoordinateReferenceSystemModel.RoleGroupId) == 'Geographic3d'
][0]
# for proj 9, there's > 200 crs in this group
self.assertGreaterEqual(model.rowCount(geographic_3d_index), 200)
epsg_4329_index = [
model.index(row, 0, geographic_3d_index)
for row in range(model.rowCount(geographic_3d_index))
if model.data(model.index(row, 0, geographic_3d_index),
QgsCoordinateReferenceSystemModel.RoleAuthId) == 'EPSG:4329'
][0]
# test model roles
self.assertEqual(model.data(epsg_4329_index, Qt.DisplayRole), 'WGS 84 (3D)')
self.assertEqual(
model.data(model.index(epsg_4329_index.row(), 1, epsg_4329_index.parent()),
Qt.DisplayRole), 'EPSG:4329')
self.assertEqual(model.data(epsg_4329_index,
QgsCoordinateReferenceSystemModel.RoleName),
'WGS 84 (3D)')
self.assertTrue(model.data(epsg_4329_index,
QgsCoordinateReferenceSystemModel.RoleDeprecated))
# the proj and wkt roles are only available for non-standard CRS
self.assertFalse(model.data(epsg_4329_index,
QgsCoordinateReferenceSystemModel.RoleWkt))
self.assertFalse(model.data(epsg_4329_index,
QgsCoordinateReferenceSystemModel.RoleProj))
# check that same result is returned by authIdToIndex
self.assertEqual(model.authIdToIndex('EPSG:4329'), epsg_4329_index)
# find a vertical crs
vertical_index = [
model.index(row, 0, QModelIndex())
for row in range(model.rowCount(QModelIndex()))
if model.data(model.index(row, 0, QModelIndex()),
QgsCoordinateReferenceSystemModel.RoleGroupId) == 'Vertical'
][0]
# for proj 9, there's > 400 crs in this group
self.assertGreaterEqual(model.rowCount(vertical_index), 400)
ahd_index = [
model.index(row, 0, vertical_index)
for row in range(model.rowCount(vertical_index))
if model.data(model.index(row, 0, vertical_index),
QgsCoordinateReferenceSystemModel.RoleAuthId) == 'EPSG:5711'
][0]
# test model roles
self.assertEqual(model.data(ahd_index, Qt.DisplayRole), 'AHD height')
self.assertEqual(
model.data(model.index(ahd_index.row(), 1, ahd_index.parent()),
Qt.DisplayRole), 'EPSG:5711')
self.assertEqual(model.data(ahd_index,
QgsCoordinateReferenceSystemModel.RoleName),
'AHD height')
self.assertFalse(model.data(ahd_index,
QgsCoordinateReferenceSystemModel.RoleDeprecated))
# the proj and wkt roles are only available for non-standard CRS
self.assertFalse(model.data(ahd_index,
QgsCoordinateReferenceSystemModel.RoleWkt))
self.assertFalse(model.data(ahd_index,
QgsCoordinateReferenceSystemModel.RoleProj))
# check that same result is returned by authIdToIndex
self.assertEqual(model.authIdToIndex('EPSG:5711'), ahd_index)
# check projected group
projected_index = [
model.index(row, 0, QModelIndex())
for row in range(model.rowCount(QModelIndex()))
if model.data(model.index(row, 0, QModelIndex()),
QgsCoordinateReferenceSystemModel.RoleGroupId) == 'Projected'
][0]
# for proj 9, there's > 50 projection methods in this group
self.assertGreaterEqual(model.rowCount(projected_index), 50)
# find Albers equal area group
aea_group_index = [
model.index(row, 0, projected_index)
for row in range(model.rowCount(projected_index))
if model.data(model.index(row, 0, projected_index),
QgsCoordinateReferenceSystemModel.RoleGroupId) == 'aea'
][0]
# for proj 9, there's > 100 crs in this group
self.assertGreaterEqual(model.rowCount(aea_group_index), 100)
# find epsg:3577 in this group
epsg_3577_index = [
model.index(row, 0, aea_group_index)
for row in range(model.rowCount(aea_group_index))
if model.data(model.index(row, 0, aea_group_index),
QgsCoordinateReferenceSystemModel.RoleAuthId) == 'EPSG:3577'
][0]
# test model roles
self.assertEqual(model.data(epsg_3577_index, Qt.DisplayRole), 'GDA94 / Australian Albers')
self.assertEqual(
model.data(model.index(epsg_3577_index.row(), 1, epsg_3577_index.parent()),
Qt.DisplayRole), 'EPSG:3577')
self.assertEqual(model.data(epsg_3577_index,
QgsCoordinateReferenceSystemModel.RoleName),
'GDA94 / Australian Albers')
self.assertFalse(model.data(epsg_3577_index,
QgsCoordinateReferenceSystemModel.RoleDeprecated))
# the proj and wkt roles are only available for non-standard CRS
self.assertFalse(model.data(epsg_3577_index,
QgsCoordinateReferenceSystemModel.RoleWkt))
self.assertFalse(model.data(epsg_3577_index,
QgsCoordinateReferenceSystemModel.RoleProj))
# check that same result is returned by authIdToIndex
self.assertEqual(model.authIdToIndex('EPSG:3577'), epsg_3577_index)
# now add a custom crs and ensure it appears in the model
prev_top_level_count = model.rowCount(QModelIndex())
registry = QgsApplication.coordinateReferenceSystemRegistry()
crs = QgsCoordinateReferenceSystem.fromProj("+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")
res = registry.addUserCrs(crs, 'my custom crs')
self.assertEqual(res, 100000)
self.assertEqual(model.rowCount(QModelIndex()), prev_top_level_count + 1)
top_level_items = [
model.data(model.index(row, 0, QModelIndex()), Qt.DisplayRole) for row in range(model.rowCount(QModelIndex()))
]
self.assertIn('User Defined Coordinate Systems', top_level_items)
self.assertNotIn("Custom Coordinate Systems", top_level_items)
# check group ids
top_level_item_group_ids = [
model.data(model.index(row, 0, QModelIndex()), QgsCoordinateReferenceSystemModel.RoleGroupId) for row in range(model.rowCount(QModelIndex()))
]
self.assertIn('USER', top_level_item_group_ids)
# find user crs
user_index = [
model.index(row, 0, QModelIndex())
for row in range(model.rowCount(QModelIndex()))
if model.data(model.index(row, 0, QModelIndex()),
QgsCoordinateReferenceSystemModel.RoleGroupId) == 'USER'
][0]
self.assertEqual(model.rowCount(user_index), 1)
user_crs_index = model.index(0, 0, user_index)
# test model roles
self.assertEqual(model.data(user_crs_index, Qt.DisplayRole), 'my custom crs')
self.assertEqual(
model.data(model.index(user_crs_index.row(), 1, user_crs_index.parent()),
Qt.DisplayRole), 'USER:100000')
self.assertEqual(model.data(user_crs_index,
QgsCoordinateReferenceSystemModel.RoleName),
'my custom crs')
self.assertFalse(model.data(user_crs_index,
QgsCoordinateReferenceSystemModel.RoleDeprecated))
# the proj and wkt roles are only available for non-standard CRS
self.assertEqual(model.data(user_crs_index,
QgsCoordinateReferenceSystemModel.RoleWkt)[:8], 'PROJCRS[')
self.assertEqual(model.data(user_crs_index,
QgsCoordinateReferenceSystemModel.RoleProj), "+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")
# check that same result is returned by authIdToIndex
self.assertEqual(model.authIdToIndex('USER:100000'), user_crs_index)
# modify user crs
crs = QgsCoordinateReferenceSystem.fromProj("+proj=aea +lat_1=21 +lat_2=-23 +lat_0=4 +lon_0=29 +x_0=10 +y_0=3 +datum=WGS84 +units=m +no_defs")
self.assertTrue(registry.updateUserCrs(100000, crs, 'my custom crs rev 2'))
user_index = [
model.index(row, 0, QModelIndex())
for row in range(model.rowCount(QModelIndex()))
if model.data(model.index(row, 0, QModelIndex()),
QgsCoordinateReferenceSystemModel.RoleGroupId) == 'USER'
][0]
self.assertEqual(model.rowCount(user_index), 1)
user_crs_index = model.index(0, 0, user_index)
# test model roles
self.assertEqual(model.data(user_crs_index, Qt.DisplayRole), 'my custom crs rev 2')
self.assertEqual(
model.data(model.index(user_crs_index.row(), 1, user_crs_index.parent()),
Qt.DisplayRole), 'USER:100000')
self.assertEqual(model.data(user_crs_index,
QgsCoordinateReferenceSystemModel.RoleName),
'my custom crs rev 2')
self.assertFalse(model.data(user_crs_index,
QgsCoordinateReferenceSystemModel.RoleDeprecated))
# the proj and wkt roles are only available for non-standard CRS
self.assertEqual(model.data(user_crs_index,
QgsCoordinateReferenceSystemModel.RoleWkt)[:8], 'PROJCRS[')
self.assertEqual(model.data(user_crs_index,
QgsCoordinateReferenceSystemModel.RoleProj), "+proj=aea +lat_1=21 +lat_2=-23 +lat_0=4 +lon_0=29 +x_0=10 +y_0=3 +datum=WGS84 +units=m +no_defs")
# remove
registry.removeUserCrs(100000)
self.assertEqual(model.rowCount(user_index), 0)
# add a non-standard crs (does not correspond to any db entry)
prev_top_level_count = model.rowCount(QModelIndex())
crs = QgsCoordinateReferenceSystem.fromProj(
"+proj=aea +lat_1=1.5 +lat_2=-23 +lat_0=4 +lon_0=29 +x_0=10 +y_0=3 +datum=WGS84 +units=m +no_defs")
model.addCustomCrs(crs)
self.assertEqual(model.rowCount(QModelIndex()), prev_top_level_count + 1)
top_level_items = [
model.data(model.index(row, 0, QModelIndex()), Qt.DisplayRole) for row in range(model.rowCount(QModelIndex()))
]
self.assertIn("Custom Coordinate Systems", top_level_items)
# check group ids
top_level_item_group_ids = [
model.data(model.index(row, 0, QModelIndex()), QgsCoordinateReferenceSystemModel.RoleGroupId) for row in range(model.rowCount(QModelIndex()))
]
self.assertIn('CUSTOM', top_level_item_group_ids)
custom_index = [
model.index(row, 0, QModelIndex())
for row in range(model.rowCount(QModelIndex()))
if model.data(model.index(row, 0, QModelIndex()),
QgsCoordinateReferenceSystemModel.RoleGroupId) == 'CUSTOM'
][0]
self.assertEqual(model.rowCount(custom_index), 1)
custom_crs_index = model.index(0, 0, custom_index)
# test model roles
self.assertEqual(model.data(custom_crs_index, Qt.DisplayRole), 'Custom CRS')
self.assertFalse(
model.data(model.index(custom_crs_index.row(), 1, custom_crs_index.parent()),
Qt.DisplayRole))
self.assertEqual(model.data(custom_crs_index,
QgsCoordinateReferenceSystemModel.RoleName), 'Custom CRS')
self.assertFalse(model.data(custom_crs_index,
QgsCoordinateReferenceSystemModel.RoleDeprecated))
# the proj and wkt roles are only available for non-standard CRS
self.assertEqual(model.data(custom_crs_index,
QgsCoordinateReferenceSystemModel.RoleWkt)[:8], 'PROJCRS[')
self.assertEqual(model.data(custom_crs_index,
QgsCoordinateReferenceSystemModel.RoleProj), "+proj=aea +lat_1=1.5 +lat_2=-23 +lat_0=4 +lon_0=29 +x_0=10 +y_0=3 +datum=WGS84 +units=m +no_defs")
def test_proxy_model(self):
model = QgsCoordinateReferenceSystemProxyModel()
# top level items -- we expect to find Projected, Geographic (2D), Geographic (3D),
# Compound, Vertical amongst others
self.assertGreaterEqual(model.rowCount(QModelIndex()), 5)
top_level_items = [
model.data(model.index(row, 0, QModelIndex()), Qt.DisplayRole) for row in range(model.rowCount(QModelIndex()))
]
self.assertIn('Projected', top_level_items)
self.assertIn('Geographic (2D)', top_level_items)
self.assertIn('Geographic (3D)', top_level_items)
self.assertIn('Compound', top_level_items)
self.assertIn('Vertical', top_level_items)
# filter by type
model.setFilters(QgsCoordinateReferenceSystemProxyModel.FilterHorizontal)
top_level_items = [
model.data(model.index(row, 0, QModelIndex()), Qt.DisplayRole) for
row in range(model.rowCount(QModelIndex()))
]
self.assertIn('Projected', top_level_items)
self.assertIn('Geographic (2D)', top_level_items)
self.assertIn('Geographic (3D)', top_level_items)
self.assertNotIn('Compound', top_level_items)
self.assertNotIn('Vertical', top_level_items)
model.setFilters(QgsCoordinateReferenceSystemProxyModel.FilterVertical)
top_level_items = [
model.data(model.index(row, 0, QModelIndex()), Qt.DisplayRole) for
row in range(model.rowCount(QModelIndex()))
]
self.assertNotIn('Projected', top_level_items)
self.assertNotIn('Geographic (2D)', top_level_items)
self.assertNotIn('Geographic (3D)', top_level_items)
self.assertNotIn('Compound', top_level_items)
self.assertIn('Vertical', top_level_items)
model.setFilters(QgsCoordinateReferenceSystemProxyModel.FilterCompound)
top_level_items = [
model.data(model.index(row, 0, QModelIndex()), Qt.DisplayRole) for
row in range(model.rowCount(QModelIndex()))
]
self.assertNotIn('Projected', top_level_items)
self.assertNotIn('Geographic (2D)', top_level_items)
self.assertNotIn('Geographic (3D)', top_level_items)
self.assertIn('Compound', top_level_items)
self.assertNotIn('Vertical', top_level_items)
model.setFilters(QgsCoordinateReferenceSystemProxyModel.Filters(QgsCoordinateReferenceSystemProxyModel.FilterCompound
| QgsCoordinateReferenceSystemProxyModel.FilterVertical))
top_level_items = [
model.data(model.index(row, 0, QModelIndex()), Qt.DisplayRole) for
row in range(model.rowCount(QModelIndex()))
]
self.assertNotIn('Projected', top_level_items)
self.assertNotIn('Geographic (2D)', top_level_items)
self.assertNotIn('Geographic (3D)', top_level_items)
self.assertIn('Compound', top_level_items)
self.assertIn('Vertical', top_level_items)
model.setFilters(QgsCoordinateReferenceSystemProxyModel.Filters())
# find WGS84 in Geographic2d group
geographic_2d_index = [
model.index(row, 0, QModelIndex())
for row in range(model.rowCount(QModelIndex()))
if model.data(model.index(row, 0, QModelIndex()),
QgsCoordinateReferenceSystemModel.RoleGroupId) == 'Geographic2d'
][0]
wgs84_index = [
model.index(row, 0, geographic_2d_index)
for row in range(model.rowCount(geographic_2d_index))
if model.data(model.index(row, 0, geographic_2d_index),
QgsCoordinateReferenceSystemModel.RoleAuthId) == 'EPSG:4326'
][0]
# test model roles
self.assertEqual(model.data(wgs84_index, Qt.DisplayRole), 'WGS 84')
self.assertEqual(model.data(model.index(wgs84_index.row(), 1, wgs84_index.parent()), Qt.DisplayRole), 'EPSG:4326')
self.assertEqual(model.data(wgs84_index, QgsCoordinateReferenceSystemModel.RoleName), 'WGS 84')
self.assertFalse(model.data(wgs84_index,
QgsCoordinateReferenceSystemModel.RoleDeprecated))
# the proj and wkt roles are only available for non-standard CRS
self.assertFalse(model.data(wgs84_index,
QgsCoordinateReferenceSystemModel.RoleWkt))
self.assertFalse(model.data(wgs84_index,
QgsCoordinateReferenceSystemModel.RoleProj))
# find EPSG:4329 in Geographic3d group (also tests a deprecated CRS)
geographic_3d_index = [
model.index(row, 0, QModelIndex())
for row in range(model.rowCount(QModelIndex()))
if model.data(model.index(row, 0, QModelIndex()),
QgsCoordinateReferenceSystemModel.RoleGroupId) == 'Geographic3d'
][0]
epsg_4329_index = [
model.index(row, 0, geographic_3d_index)
for row in range(model.rowCount(geographic_3d_index))
if model.data(model.index(row, 0, geographic_3d_index),
QgsCoordinateReferenceSystemModel.RoleAuthId) == 'EPSG:4329'
][0]
# test model roles
self.assertEqual(model.data(epsg_4329_index, Qt.DisplayRole), 'WGS 84 (3D)')
self.assertEqual(
model.data(model.index(epsg_4329_index.row(), 1, epsg_4329_index.parent()),
Qt.DisplayRole), 'EPSG:4329')
self.assertEqual(model.data(epsg_4329_index,
QgsCoordinateReferenceSystemModel.RoleName),
'WGS 84 (3D)')
self.assertTrue(model.data(epsg_4329_index,
QgsCoordinateReferenceSystemModel.RoleDeprecated))
# the proj and wkt roles are only available for non-standard CRS
self.assertFalse(model.data(epsg_4329_index,
QgsCoordinateReferenceSystemModel.RoleWkt))
self.assertFalse(model.data(epsg_4329_index,
QgsCoordinateReferenceSystemModel.RoleProj))
model.setFilterDeprecated(True)
geographic_3d_index = [
model.index(row, 0, QModelIndex())
for row in range(model.rowCount(QModelIndex()))
if model.data(model.index(row, 0, QModelIndex()),
QgsCoordinateReferenceSystemModel.RoleGroupId) == 'Geographic3d'
][0]
self.assertFalse([
model.index(row, 0, geographic_3d_index)
for row in range(model.rowCount(geographic_3d_index))
if model.data(model.index(row, 0, geographic_3d_index),
QgsCoordinateReferenceSystemModel.RoleAuthId) == 'EPSG:4329'
])
model.setFilterDeprecated(False)
geographic_3d_index = [
model.index(row, 0, QModelIndex())
for row in range(model.rowCount(QModelIndex()))
if model.data(model.index(row, 0, QModelIndex()),
QgsCoordinateReferenceSystemModel.RoleGroupId) == 'Geographic3d'
][0]
epsg_4329_index = [
model.index(row, 0, geographic_3d_index)
for row in range(model.rowCount(geographic_3d_index))
if model.data(model.index(row, 0, geographic_3d_index),
QgsCoordinateReferenceSystemModel.RoleAuthId) == 'EPSG:4329'
][0]
self.assertTrue(epsg_4329_index.isValid())
# filter by string
model.setFilterString('GDA94')
geographic_3d_index = [
model.index(row, 0, QModelIndex())
for row in range(model.rowCount(QModelIndex()))
if model.data(model.index(row, 0, QModelIndex()),
QgsCoordinateReferenceSystemModel.RoleGroupId) == 'Geographic3d'
][0]
self.assertFalse([
model.index(row, 0, geographic_3d_index)
for row in range(model.rowCount(geographic_3d_index))
if model.data(model.index(row, 0, geographic_3d_index),
QgsCoordinateReferenceSystemModel.RoleAuthId) == 'EPSG:4329'
])
epsg_4939_index = [
model.index(row, 0, geographic_3d_index)
for row in range(model.rowCount(geographic_3d_index))
if model.data(model.index(row, 0, geographic_3d_index),
QgsCoordinateReferenceSystemModel.RoleAuthId) == 'EPSG:4939'
][0]
self.assertTrue(epsg_4939_index.isValid())
epsg_4347_index = [
model.index(row, 0, geographic_3d_index)
for row in range(model.rowCount(geographic_3d_index))
if model.data(model.index(row, 0, geographic_3d_index),
QgsCoordinateReferenceSystemModel.RoleAuthId) == 'EPSG:4347'
][0]
self.assertTrue(epsg_4347_index.isValid())
model.setFilterString('')
# set filtered list of crs to show
model.setFilterAuthIds({'epsg:4289', 'EPSG:4196'})
geographic_2d_index = [
model.index(row, 0, QModelIndex())
for row in range(model.rowCount(QModelIndex()))
if model.data(model.index(row, 0, QModelIndex()),
QgsCoordinateReferenceSystemModel.RoleGroupId) == 'Geographic2d'
][0]
self.assertFalse([
model.index(row, 0, geographic_2d_index)
for row in range(model.rowCount(geographic_2d_index))
if model.data(model.index(row, 0, geographic_2d_index),
QgsCoordinateReferenceSystemModel.RoleAuthId) == 'EPSG:4169'
])
epsg_4289_index = [
model.index(row, 0, geographic_2d_index)
for row in range(model.rowCount(geographic_2d_index))
if model.data(model.index(row, 0, geographic_2d_index),
QgsCoordinateReferenceSystemModel.RoleAuthId) == 'EPSG:4289'
][0]
self.assertTrue(epsg_4289_index.isValid())
epsg_4196_index = [
model.index(row, 0, geographic_2d_index)
for row in range(model.rowCount(geographic_2d_index))
if model.data(model.index(row, 0, geographic_2d_index),
QgsCoordinateReferenceSystemModel.RoleAuthId) == 'EPSG:4196'
][0]
self.assertTrue(epsg_4196_index.isValid())
if __name__ == '__main__':
unittest.main()