Skip to content

Commit

Permalink
[feature] Add optional (not on by default) layer tree warning icon
Browse files Browse the repository at this point in the history
for layers with CRS inaccuracies

If the new "Show CRS accuracy warnings for layers in project legend"
is checked by a user, then any layers with a CRS with accuracy
issues (i.e. a dynamic crs with no coordinate epoch available,
or a crs based on a datum ensemble with accuracy exceeding the
user-set limit) will have a new warning icon reflecting that
the layer is a low-accuracy layer.

This is entirely opt-in, and designed for use in engineering/BIM/...
industries where inaccuracies of meter/submeter level are very
dangerous.
  • Loading branch information
nyalldawson committed May 13, 2021
1 parent 93e48cf commit eb5fd48
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 10 deletions.
1 change: 1 addition & 0 deletions images/images.qrc
Expand Up @@ -915,6 +915,7 @@
<file>themes/default/mActionStreamingDigitize.svg</file>
<file>themes/default/mActionEditHtml.svg</file>
<file>themes/default/mIndicatorNotes.svg</file>
<file>themes/default/mIndicatorLowAccuracy.svg</file>
</qresource>
<qresource prefix="/images/tips">
<file alias="symbol_levels.png">qgis_tips/symbol_levels.png</file>
Expand Down
1 change: 1 addition & 0 deletions images/themes/default/mIndicatorLowAccuracy.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions resources/qgis_global_settings.ini
Expand Up @@ -91,6 +91,10 @@ projections\unknownCrsBehavior=NoAction
# CRS if higher positional accuracy is required
projections\crsAccuracyWarningThreshold=0.0

# If set to true, a warning icon will be shown next to any layer where the layer's CRS exceeds
# the accuracy warning threshold value (see crsAccuracyWarningThreshold)
projections\crsAccuracyIndicator=false

# Specifies a manual bearing correction to apply to bearings reported by a GPS
# device, for use when a map canvas is set to match rotation to the GPS bearing
# or when showing the GPS bearing line
Expand Down
1 change: 1 addition & 0 deletions src/app/CMakeLists.txt
Expand Up @@ -48,6 +48,7 @@ set(QGIS_APP_SRCS
qgslayertreeviewindicatorprovider.cpp
qgslayertreeviewembeddedindicator.cpp
qgslayertreeviewfilterindicator.cpp
qgslayertreeviewlowaccuracyindicator.cpp
qgslayertreeviewmemoryindicator.cpp
qgslayertreeviewnocrsindicator.cpp
qgslayertreeviewnonremovableindicator.cpp
Expand Down
4 changes: 4 additions & 0 deletions src/app/options/qgsoptions.cpp
Expand Up @@ -527,6 +527,9 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl, const QList<QgsOpti
mCrsAccuracySpin->setClearValue( 0.0, tr( "Always show" ) );
mCrsAccuracySpin->setValue( crsAccuracyWarningThreshold );

const bool crsAccuracyIndicator = mSettings->value( QStringLiteral( "/projections/crsAccuracyIndicator" ), false, QgsSettings::App ).toBool();
mCrsAccuracyIndicatorCheck->setChecked( crsAccuracyIndicator );

mShowDatumTransformDialogCheckBox->setChecked( mSettings->value( QStringLiteral( "/projections/promptWhenMultipleTransformsExist" ), false, QgsSettings::App ).toBool() );

// Datum transforms
Expand Down Expand Up @@ -1706,6 +1709,7 @@ void QgsOptions::saveOptions()
mSettings->setEnumValue( QStringLiteral( "/projections/newProjectCrsBehavior" ), radProjectUseCrsOfFirstLayer->isChecked() ? QgsGui::UseCrsOfFirstLayerAdded : QgsGui::UsePresetCrs, QgsSettings::App );
mSettings->setValue( QStringLiteral( "/projections/promptWhenMultipleTransformsExist" ), mShowDatumTransformDialogCheckBox->isChecked(), QgsSettings::App );
mSettings->setValue( QStringLiteral( "/projections/crsAccuracyWarningThreshold" ), mCrsAccuracySpin->value(), QgsSettings::App );
mSettings->setValue( QStringLiteral( "/projections/crsAccuracyIndicator" ), mCrsAccuracyIndicatorCheck->isChecked(), QgsSettings::App );

//measurement settings
mSettings->setValue( QStringLiteral( "measure/planimetric" ), mPlanimetricMeasurementsComboBox->isChecked(), QgsSettings::Core );
Expand Down
2 changes: 2 additions & 0 deletions src/app/qgisapp.cpp
Expand Up @@ -251,6 +251,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
#include "qgslayertreeviewdefaultactions.h"
#include "qgslayertreeviewembeddedindicator.h"
#include "qgslayertreeviewfilterindicator.h"
#include "qgslayertreeviewlowaccuracyindicator.h"
#include "qgslayertreeviewmemoryindicator.h"
#include "qgslayertreeviewbadlayerindicator.h"
#include "qgslayertreeviewnonremovableindicator.h"
Expand Down Expand Up @@ -4634,6 +4635,7 @@ void QgisApp::initLayerTreeView()
new QgsLayerTreeViewTemporalIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
new QgsLayerTreeViewNoCrsIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
new QgsLayerTreeViewOfflineIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
new QgsLayerTreeViewLowAccuracyIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
QgsLayerTreeViewBadLayerIndicatorProvider *badLayerIndicatorProvider = new QgsLayerTreeViewBadLayerIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
connect( badLayerIndicatorProvider, &QgsLayerTreeViewBadLayerIndicatorProvider::requestChangeDataSource, this, &QgisApp::changeDataSource );
new QgsLayerTreeViewNonRemovableIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
Expand Down
136 changes: 136 additions & 0 deletions src/app/qgslayertreeviewlowaccuracyindicator.cpp
@@ -0,0 +1,136 @@
/***************************************************************************
qgslayertreeviewlowaccuracyindicator.cpp
--------------------------------------
Date : May 2021
Copyright : (C) 2021 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 "qgslayertreeviewlowaccuracyindicator.h"
#include "qgsdatums.h"
#include "qgssettings.h"
#include "qgsgui.h"

QgsLayerTreeViewLowAccuracyIndicatorProvider::QgsLayerTreeViewLowAccuracyIndicatorProvider( QgsLayerTreeView *view )
: QgsLayerTreeViewIndicatorProvider( view )
{
}

void QgsLayerTreeViewLowAccuracyIndicatorProvider::connectSignals( QgsMapLayer *layer )
{
QgsLayerTreeViewIndicatorProvider::connectSignals( layer );
connect( layer, &QgsMapLayer::crsChanged, this, &QgsLayerTreeViewLowAccuracyIndicatorProvider::onLayerChanged );
}

void QgsLayerTreeViewLowAccuracyIndicatorProvider::disconnectSignals( QgsMapLayer *layer )
{
QgsLayerTreeViewIndicatorProvider::disconnectSignals( layer );
disconnect( layer, &QgsMapLayer::crsChanged, this, &QgsLayerTreeViewLowAccuracyIndicatorProvider::onLayerChanged );
}

void QgsLayerTreeViewLowAccuracyIndicatorProvider::onIndicatorClicked( const QModelIndex & )
{

}

QString QgsLayerTreeViewLowAccuracyIndicatorProvider::iconName( QgsMapLayer * )
{
return QStringLiteral( "/mIndicatorLowAccuracy.svg" );
}

QString QgsLayerTreeViewLowAccuracyIndicatorProvider::tooltipText( QgsMapLayer *layer )
{
if ( !layer )
return QString();

const QgsCoordinateReferenceSystem crs = layer->crs();
if ( !crs.isValid() )
return QString();

// based on datum ensemble?
try
{
const QgsDatumEnsemble ensemble = crs.datumEnsemble();
if ( ensemble.isValid() )
{
QString id;
if ( !ensemble.code().isEmpty() )
id = QStringLiteral( "<i>%1</i> (%2:%3)" ).arg( ensemble.name(), ensemble.authority(), ensemble.code() );
else
id = QStringLiteral( "<i>%</i>”" ).arg( ensemble.name() );

if ( ensemble.accuracy() > 0 )
{
return tr( "Based on %1, which has a limited accuracy of <b>at best %2 meters</b>." ).arg( id ).arg( ensemble.accuracy() );
}
else
{
return tr( "Based on %1, which has a limited accuracy." ).arg( id );
}
}
}
catch ( QgsNotSupportedException & )
{

}

// dynamic crs with no epoch?
if ( crs.isDynamic() && std::isnan( crs.coordinateEpoch() ) )
{
return tr( "%1 is a dynamic CRS, but no coordinate epoch is set. Coordinates are ambiguous and of limited accuracy." ).arg( crs.userFriendlyIdentifier() );
}

return QString();
}

bool QgsLayerTreeViewLowAccuracyIndicatorProvider::acceptLayer( QgsMapLayer *layer )
{
QgsSettings settings;
if ( !settings.value( QStringLiteral( "/projections/crsAccuracyIndicator" ), false, QgsSettings::App ).toBool() )
return false;

if ( !layer->isValid() )
return false;

const QgsCoordinateReferenceSystem crs = layer->crs();
if ( !crs.isValid() )
return false;

// dynamic crs with no epoch?
if ( crs.isDynamic() && std::isnan( crs.coordinateEpoch() ) )
{
return true;
}

// based on datum ensemble?
try
{
const QgsDatumEnsemble ensemble = crs.datumEnsemble();
if ( ensemble.isValid() )
{
if ( ensemble.accuracy() > 0 )
{
if ( ensemble.accuracy() >= settings.value( QStringLiteral( "/projections/crsAccuracyWarningThreshold" ), 0.0, QgsSettings::App ).toDouble() )
return true;
}
else
{
// unknown accuracy, always show warning
return true;
}
}
}
catch ( QgsNotSupportedException & )
{

}

return false;
}
46 changes: 46 additions & 0 deletions src/app/qgslayertreeviewlowaccuracyindicator.h
@@ -0,0 +1,46 @@
/***************************************************************************
qgslayertreeviewlowaccuracyindicator.h
--------------------------------------
Date : May 2021
Copyright : (C) 2021 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 QGSLAYERTREEVIEWLOWACCURACYINDICATOR_H
#define QGSLAYERTREEVIEWLOWACCURACYINDICATOR_H

#include "qgslayertreeviewindicatorprovider.h"
#include "qgsmaplayer.h"

#include <QObject>
#include <QPointer>

//! Indicators for low accuracy layers
class QgsLayerTreeViewLowAccuracyIndicatorProvider : public QgsLayerTreeViewIndicatorProvider
{
Q_OBJECT

public:
explicit QgsLayerTreeViewLowAccuracyIndicatorProvider( QgsLayerTreeView *view );

protected:
void connectSignals( QgsMapLayer *layer ) override ;
void disconnectSignals( QgsMapLayer *layer ) override;

protected slots:
void onIndicatorClicked( const QModelIndex &index ) override;

private:
QString iconName( QgsMapLayer *layer ) override;
QString tooltipText( QgsMapLayer *layer ) override;
bool acceptLayer( QgsMapLayer *layer ) override;

};

#endif // QGSLAYERTREEVIEWLOWACCURACYINDICATOR_H
30 changes: 20 additions & 10 deletions src/ui/qgsoptionsbase.ui
Expand Up @@ -1837,6 +1837,19 @@
<string>Accuracy Warnings</string>
</property>
<layout class="QGridLayout" name="gridLayout_37" columnstretch="0,1,2">
<item row="0" column="2">
<spacer name="horizontalSpacer_23">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<widget class="QgsDoubleSpinBox" name="mCrsAccuracySpin">
<property name="toolTip">
Expand All @@ -1857,18 +1870,15 @@
</property>
</widget>
</item>
<item row="0" column="2">
<spacer name="horizontalSpacer_23">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<item row="1" column="0" colspan="3">
<widget class="QCheckBox" name="mCrsAccuracyIndicatorCheck">
<property name="toolTip">
<string>If checked, a warning icon will show next to any map layers with CRS accuracy warnings</string>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
<property name="text">
<string>Show CRS accuracy warnings for layers in project legend</string>
</property>
</spacer>
</widget>
</item>
</layout>
</widget>
Expand Down

0 comments on commit eb5fd48

Please sign in to comment.