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.
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
selections include any projection and also the "no/invalid projection" option
(if setShowNoProjection() was called). Invalid selections are the group
headers (such as "Geographic Coordinate Systems"
headers (such as "Geographic Coordinate Systems")
%End

public slots:
Expand Down
104 changes: 61 additions & 43 deletions src/gui/qgsprojectionselectiontreewidget.cpp
Expand Up @@ -282,7 +282,11 @@ void QgsProjectionSelectionTreeWidget::setCrs( const QgsCoordinateReferenceSyste
mBlockSignals = true;
mCheckBoxNoProjection->setChecked( false );
mBlockSignals = false;
applySelection( AuthidColumn, crs.authid() );

if ( !crs.authid().isEmpty() )
applySelection( AuthidColumn, crs.authid() );
else
loadUnknownCrs( crs );
if ( changed )
{
emit crsSelected();
Expand All @@ -300,35 +304,6 @@ QgsRectangle QgsProjectionSelectionTreeWidget::previewRect() const
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
{
// 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() )
return mDeferredLoadCrs;

int srid = getSelectedExpression( QStringLiteral( "srs_id" ) ).toLong();
if ( srid >= USER_CRS_START_ID )
return QgsCoordinateReferenceSystem::fromOgcWmsCrs( QStringLiteral( "USER:%1" ).arg( srid ) );
const QString srsIdString = getSelectedExpression( QStringLiteral( "srs_id" ) );
if ( !srsIdString.isEmpty() )
{
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
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 )
Expand Down Expand Up @@ -450,7 +440,7 @@ bool QgsProjectionSelectionTreeWidget::hasValidSelection() const
else if ( !mInitialized && mDeferredLoadCrs.isValid() )
return true;
else
return item && !item->text( QgisCrsIdColumn ).isEmpty();
return item && ( !item->text( QgisCrsIdColumn ).isEmpty() || item->data( 0, RoleWkt ).isValid() );
}

long QgsProjectionSelectionTreeWidget::selectedCrsId()
Expand Down Expand Up @@ -693,6 +683,25 @@ void QgsProjectionSelectionTreeWidget::loadCrsList( QSet<QString> *crsFilter )
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
void QgsProjectionSelectionTreeWidget::lstCoordinateSystems_currentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem * )
{
Expand All @@ -716,15 +725,24 @@ void QgsProjectionSelectionTreeWidget::lstCoordinateSystems_currentItemChanged(

updateBoundsPreview();

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

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 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 proj4String = tr( "<dt><b>%1</b></dt><dd><code>%2</code></dd>" ).arg( tr( "Proj4" ), selectedProj4String() );
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" ), currentCrs.toProj() );

#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
Expand Down
13 changes: 8 additions & 5 deletions src/gui/qgsprojectionselectiontreewidget.h
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
* selections include any projection and also the "no/invalid projection" option
* (if setShowNoProjection() was called). Invalid selections are the group
* headers (such as "Geographic Coordinate Systems"
* headers (such as "Geographic Coordinate Systems")
*/
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 loadUnknownCrs( const QgsCoordinateReferenceSystem &crs );

/**
* \brief Makes a \a string safe for use in SQL statements.
* 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 selectedProj4String();

QString selectedWktString();

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

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

// 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
QTreeWidgetItem *mProjList = nullptr;

QTreeWidgetItem *mUnknownList = nullptr;

//! Users custom coordinate system file
QString mCustomCsFile;
//! File name of the sqlite3 database
Expand Down
13 changes: 12 additions & 1 deletion tests/src/python/test_qgsprojectionselectionwidgets.py
Expand Up @@ -16,7 +16,7 @@
from qgis.gui import (QgsProjectionSelectionWidget,
QgsProjectionSelectionTreeWidget,
QgsProjectionSelectionDialog)
from qgis.core import QgsCoordinateReferenceSystem, QgsProject
from qgis.core import QgsCoordinateReferenceSystem, QgsProject, QgsProjUtils
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.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):
""" test allowing no projection option for QgsProjectionSelectionTreeWidget """
w = QgsProjectionSelectionTreeWidget()
Expand Down

0 comments on commit fac9970

Please sign in to comment.