Skip to content

Commit

Permalink
Show "Unknown CRS" entry in crs selector widget when the source CRS
Browse files Browse the repository at this point in the history
of a layer or project is unknown

Because:
1. It clearly indicates that the current CRS for the object is unknown
2. It lets users see the full WKT/proj definition of the CRS, and allows
them to copy this definition
3. Avoids loss of CRS information when just opening and accepting the dialog

Refs #33458
  • Loading branch information
nyalldawson committed Feb 4, 2020
1 parent 76808f6 commit fac9970
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 50 deletions.
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ Returns whether the bounds preview map is shown.
Returns ``True`` if the current selection in the widget is a valid choice. Valid Returns ``True`` if the current selection in the widget is a valid choice. Valid
selections include any projection and also the "no/invalid projection" option selections include any projection and also the "no/invalid projection" option
(if setShowNoProjection() was called). Invalid selections are the group (if setShowNoProjection() was called). Invalid selections are the group
headers (such as "Geographic Coordinate Systems" headers (such as "Geographic Coordinate Systems")
%End %End


public slots: public slots:
Expand Down
104 changes: 61 additions & 43 deletions src/gui/qgsprojectionselectiontreewidget.cpp
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -282,7 +282,11 @@ void QgsProjectionSelectionTreeWidget::setCrs( const QgsCoordinateReferenceSyste
mBlockSignals = true; mBlockSignals = true;
mCheckBoxNoProjection->setChecked( false ); mCheckBoxNoProjection->setChecked( false );
mBlockSignals = false; mBlockSignals = false;
applySelection( AuthidColumn, crs.authid() );
if ( !crs.authid().isEmpty() )
applySelection( AuthidColumn, crs.authid() );
else
loadUnknownCrs( crs );
if ( changed ) if ( changed )
{ {
emit crsSelected(); emit crsSelected();
Expand All @@ -300,35 +304,6 @@ QgsRectangle QgsProjectionSelectionTreeWidget::previewRect() const
return mAreaCanvas->canvasRect(); return mAreaCanvas->canvasRect();
} }


// Returns the whole proj4 string for the selected projection node
QString QgsProjectionSelectionTreeWidget::selectedProj4String()
{
// Only return the projection if there is a node in the tree
// selected that has an srid. This prevents error if the user
// selects a top-level node rather than an actual coordinate
// system
//
// Get the selected node
QTreeWidgetItem *item = lstCoordinateSystems->currentItem();
if ( !item || item->text( QgisCrsIdColumn ).isEmpty() )
return QString();

long srsId = item->text( QgisCrsIdColumn ).toLong();
QgsCoordinateReferenceSystem crs = QgsCoordinateReferenceSystem::fromSrsId( srsId );
return crs.toProj();
}

QString QgsProjectionSelectionTreeWidget::selectedWktString()
{
QTreeWidgetItem *item = lstCoordinateSystems->currentItem();
if ( !item || item->text( QgisCrsIdColumn ).isEmpty() )
return QString();

long srsId = item->text( QgisCrsIdColumn ).toLong();
QgsCoordinateReferenceSystem crs = QgsCoordinateReferenceSystem::fromSrsId( srsId );
return crs.toWkt( QgsCoordinateReferenceSystem::WKT2_2015, true );
}

QString QgsProjectionSelectionTreeWidget::getSelectedExpression( const QString &expression ) const QString QgsProjectionSelectionTreeWidget::getSelectedExpression( const QString &expression ) const
{ {
// Only return the attribute if there is a node in the tree // Only return the attribute if there is a node in the tree
Expand Down Expand Up @@ -409,11 +384,26 @@ QgsCoordinateReferenceSystem QgsProjectionSelectionTreeWidget::crs() const
if ( !mInitialized && mDeferredLoadCrs.isValid() ) if ( !mInitialized && mDeferredLoadCrs.isValid() )
return mDeferredLoadCrs; return mDeferredLoadCrs;


int srid = getSelectedExpression( QStringLiteral( "srs_id" ) ).toLong(); const QString srsIdString = getSelectedExpression( QStringLiteral( "srs_id" ) );
if ( srid >= USER_CRS_START_ID ) if ( !srsIdString.isEmpty() )
return QgsCoordinateReferenceSystem::fromOgcWmsCrs( QStringLiteral( "USER:%1" ).arg( srid ) ); {
int srid = srsIdString.toLong();
if ( srid >= USER_CRS_START_ID )
return QgsCoordinateReferenceSystem::fromOgcWmsCrs( QStringLiteral( "USER:%1" ).arg( srid ) );
else
return QgsCoordinateReferenceSystem::fromOgcWmsCrs( getSelectedExpression( QStringLiteral( "upper(auth_name||':'||auth_id)" ) ) );
}
else else
return QgsCoordinateReferenceSystem::fromOgcWmsCrs( getSelectedExpression( QStringLiteral( "upper(auth_name||':'||auth_id)" ) ) ); {
// custom CRS
QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
if ( lvi && lvi->data( 0, RoleWkt ).isValid() )
return QgsCoordinateReferenceSystem::fromWkt( lvi->data( 0, RoleWkt ).toString() );
else if ( lvi && lvi->data( 0, RoleProj ).isValid() )
return QgsCoordinateReferenceSystem::fromProj( lvi->data( 0, RoleProj ).toString() );
else
return QgsCoordinateReferenceSystem();
}
} }


void QgsProjectionSelectionTreeWidget::setShowNoProjection( bool show ) void QgsProjectionSelectionTreeWidget::setShowNoProjection( bool show )
Expand Down Expand Up @@ -450,7 +440,7 @@ bool QgsProjectionSelectionTreeWidget::hasValidSelection() const
else if ( !mInitialized && mDeferredLoadCrs.isValid() ) else if ( !mInitialized && mDeferredLoadCrs.isValid() )
return true; return true;
else else
return item && !item->text( QgisCrsIdColumn ).isEmpty(); return item && ( !item->text( QgisCrsIdColumn ).isEmpty() || item->data( 0, RoleWkt ).isValid() );
} }


long QgsProjectionSelectionTreeWidget::selectedCrsId() long QgsProjectionSelectionTreeWidget::selectedCrsId()
Expand Down Expand Up @@ -693,6 +683,25 @@ void QgsProjectionSelectionTreeWidget::loadCrsList( QSet<QString> *crsFilter )
mProjListDone = true; mProjListDone = true;
} }


void QgsProjectionSelectionTreeWidget::loadUnknownCrs( const QgsCoordinateReferenceSystem &crs )
{
if ( !mUnknownList )
{
mUnknownList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "Unknown Coordinate Systems" ) ) );
QFont fontTemp = mUnknownList->font( 0 );
fontTemp.setItalic( true );
fontTemp.setBold( true );
mUnknownList->setFont( 0, fontTemp );
mUnknownList->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/user.svg" ) ) );
}

QTreeWidgetItem *newItem = new QTreeWidgetItem( mUnknownList, QStringList( QObject::tr( "Unknown CRS" ) ) );
newItem->setData( 0, RoleWkt, crs.toWkt( QgsCoordinateReferenceSystem::WKT2_2018 ) );
newItem->setData( 0, RoleProj, crs.toProj() );

lstCoordinateSystems->setCurrentItem( newItem );
}

// New coordinate system selected from the list // New coordinate system selected from the list
void QgsProjectionSelectionTreeWidget::lstCoordinateSystems_currentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem * ) void QgsProjectionSelectionTreeWidget::lstCoordinateSystems_currentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem * )
{ {
Expand All @@ -716,15 +725,24 @@ void QgsProjectionSelectionTreeWidget::lstCoordinateSystems_currentItemChanged(


updateBoundsPreview(); updateBoundsPreview();


QList<QTreeWidgetItem *> nodes = lstRecent->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly, QgisCrsIdColumn ); const QString crsId = current->text( QgisCrsIdColumn );
if ( !nodes.isEmpty() ) if ( !crsId.isEmpty() )
{ {
QgsDebugMsgLevel( QStringLiteral( "found srs %1 in recent" ).arg( current->text( QgisCrsIdColumn ) ), 4 ); QList<QTreeWidgetItem *> nodes = lstRecent->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly, QgisCrsIdColumn );
lstRecent->setCurrentItem( nodes.first() ); if ( !nodes.isEmpty() )
{
QgsDebugMsgLevel( QStringLiteral( "found srs %1 in recent" ).arg( current->text( QgisCrsIdColumn ) ), 4 );
lstRecent->setCurrentItem( nodes.first() );
}
else
{
QgsDebugMsgLevel( QStringLiteral( "srs %1 not recent" ).arg( current->text( QgisCrsIdColumn ) ), 4 );
lstRecent->clearSelection();
lstCoordinateSystems->setFocus( Qt::OtherFocusReason );
}
} }
else else
{ {
QgsDebugMsgLevel( QStringLiteral( "srs %1 not recent" ).arg( current->text( QgisCrsIdColumn ) ), 4 );
lstRecent->clearSelection(); lstRecent->clearSelection();
lstCoordinateSystems->setFocus( Qt::OtherFocusReason ); lstCoordinateSystems->setFocus( Qt::OtherFocusReason );
} }
Expand Down Expand Up @@ -927,7 +945,7 @@ long QgsProjectionSelectionTreeWidget::getLargestCrsIdMatch( const QString &sql
void QgsProjectionSelectionTreeWidget::updateBoundsPreview() void QgsProjectionSelectionTreeWidget::updateBoundsPreview()
{ {
QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem(); QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
if ( !lvi || lvi->text( QgisCrsIdColumn ).isEmpty() ) if ( !lvi || ( lvi->text( QgisCrsIdColumn ).isEmpty() && !lvi->data( 0, RoleWkt ).isValid() ) )
return; return;


QgsCoordinateReferenceSystem currentCrs = crs(); QgsCoordinateReferenceSystem currentCrs = crs();
Expand All @@ -947,8 +965,8 @@ void QgsProjectionSelectionTreeWidget::updateBoundsPreview()
} }


const QString extentHtml = QStringLiteral( "<dt><b>%1</b></dt><dd>%2</dd>" ).arg( tr( "Extent" ), extentString ); const QString extentHtml = QStringLiteral( "<dt><b>%1</b></dt><dd>%2</dd>" ).arg( tr( "Extent" ), extentString );
const QString wktString = tr( "<dt><b>%1</b></dt><dd><code>%2</code></dd>" ).arg( tr( "WKT" ), selectedWktString().replace( '\n', QStringLiteral( "<br>" ) ).replace( ' ', QStringLiteral( "&nbsp;" ) ) ); const QString wktString = tr( "<dt><b>%1</b></dt><dd><code>%2</code></dd>" ).arg( tr( "WKT" ), currentCrs.toWkt( QgsCoordinateReferenceSystem::WKT2_2018, true ).replace( '\n', QStringLiteral( "<br>" ) ).replace( ' ', QStringLiteral( "&nbsp;" ) ) );
const QString proj4String = tr( "<dt><b>%1</b></dt><dd><code>%2</code></dd>" ).arg( tr( "Proj4" ), selectedProj4String() ); const QString proj4String = tr( "<dt><b>%1</b></dt><dd><code>%2</code></dd>" ).arg( tr( "Proj4" ), currentCrs.toProj() );


#ifdef Q_OS_WIN #ifdef Q_OS_WIN
const int smallerPointSize = std::max( font().pointSize() - 1, 8 ); // bit less on windows, due to poor rendering of small point sizes const int smallerPointSize = std::max( font().pointSize() - 1, 8 ); // bit less on windows, due to poor rendering of small point sizes
Expand Down
13 changes: 8 additions & 5 deletions src/gui/qgsprojectionselectiontreewidget.h
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class GUI_EXPORT QgsProjectionSelectionTreeWidget : public QWidget, private Ui::
* Returns TRUE if the current selection in the widget is a valid choice. Valid * Returns TRUE if the current selection in the widget is a valid choice. Valid
* selections include any projection and also the "no/invalid projection" option * selections include any projection and also the "no/invalid projection" option
* (if setShowNoProjection() was called). Invalid selections are the group * (if setShowNoProjection() was called). Invalid selections are the group
* headers (such as "Geographic Coordinate Systems" * headers (such as "Geographic Coordinate Systems")
*/ */
bool hasValidSelection() const; bool hasValidSelection() const;


Expand Down Expand Up @@ -182,6 +182,9 @@ class GUI_EXPORT QgsProjectionSelectionTreeWidget : public QWidget, private Ui::
*/ */
void loadCrsList( QSet<QString> *crsFilter = nullptr ); void loadCrsList( QSet<QString> *crsFilter = nullptr );



void loadUnknownCrs( const QgsCoordinateReferenceSystem &crs );

/** /**
* \brief Makes a \a string safe for use in SQL statements. * \brief Makes a \a string safe for use in SQL statements.
* This involves escaping single quotes, double quotes, backslashes, * This involves escaping single quotes, double quotes, backslashes,
Expand Down Expand Up @@ -226,10 +229,6 @@ class GUI_EXPORT QgsProjectionSelectionTreeWidget : public QWidget, private Ui::


QString selectedName(); QString selectedName();


QString selectedProj4String();

QString selectedWktString();

//! Gets the current QGIS projection identfier //! Gets the current QGIS projection identfier
long selectedCrsId(); long selectedCrsId();


Expand All @@ -239,6 +238,8 @@ class GUI_EXPORT QgsProjectionSelectionTreeWidget : public QWidget, private Ui::
enum Roles enum Roles
{ {
RoleDeprecated = Qt::UserRole, RoleDeprecated = Qt::UserRole,
RoleWkt,
RoleProj
}; };


// List view nodes for the tree view of projections // List view nodes for the tree view of projections
Expand All @@ -249,6 +250,8 @@ class GUI_EXPORT QgsProjectionSelectionTreeWidget : public QWidget, private Ui::
//! PROJCS node //! PROJCS node
QTreeWidgetItem *mProjList = nullptr; QTreeWidgetItem *mProjList = nullptr;


QTreeWidgetItem *mUnknownList = nullptr;

//! Users custom coordinate system file //! Users custom coordinate system file
QString mCustomCsFile; QString mCustomCsFile;
//! File name of the sqlite3 database //! File name of the sqlite3 database
Expand Down
13 changes: 12 additions & 1 deletion tests/src/python/test_qgsprojectionselectionwidgets.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from qgis.gui import (QgsProjectionSelectionWidget, from qgis.gui import (QgsProjectionSelectionWidget,
QgsProjectionSelectionTreeWidget, QgsProjectionSelectionTreeWidget,
QgsProjectionSelectionDialog) QgsProjectionSelectionDialog)
from qgis.core import QgsCoordinateReferenceSystem, QgsProject from qgis.core import QgsCoordinateReferenceSystem, QgsProject, QgsProjUtils
from qgis.testing import start_app, unittest from qgis.testing import start_app, unittest




Expand Down Expand Up @@ -134,6 +134,17 @@ def testTreeWidgetGettersSetters(self):
self.assertEqual(w.crs().authid(), 'EPSG:3111') self.assertEqual(w.crs().authid(), 'EPSG:3111')
self.assertTrue(w.hasValidSelection()) self.assertTrue(w.hasValidSelection())


@unittest.skipIf(QgsProjUtils.projVersionMajor() < 6, 'Not a proj6 build')
def testTreeWidgetUnknownCrs(self):
w = QgsProjectionSelectionTreeWidget()
w.show()
self.assertFalse(w.hasValidSelection())
w.setCrs(QgsCoordinateReferenceSystem.fromWkt('GEOGCS["WGS 84",DATUM["unknown",SPHEROID["WGS84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]]'))
self.assertTrue(w.crs().isValid())
self.assertFalse(w.crs().authid())
self.assertTrue(w.hasValidSelection())
self.assertEqual(w.crs().toWkt(QgsCoordinateReferenceSystem.WKT2_2018), 'GEOGCRS["WGS 84",DATUM["unknown",ELLIPSOID["WGS84",6378137,298.257223563,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["longitude",east,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["latitude",north,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]]]')

def testTreeWidgetNotSetOption(self): def testTreeWidgetNotSetOption(self):
""" test allowing no projection option for QgsProjectionSelectionTreeWidget """ """ test allowing no projection option for QgsProjectionSelectionTreeWidget """
w = QgsProjectionSelectionTreeWidget() w = QgsProjectionSelectionTreeWidget()
Expand Down

0 comments on commit fac9970

Please sign in to comment.