Skip to content
Permalink
Browse files
[feature] Add optional (not on by default) layer tree warning icon
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 eb5fd484fff1f5952db9811e3cd67518826be6b9
@@ -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>
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"><path d="M 1.5213216,8.5 H 13.521322 v 4 H 1.5213216 Z" fill="none" stroke="#9f0000" stroke-linecap="square" stroke-opacity=".705882"/><path d="m 4.5213201,9 v 1" stroke="#9f0000" fill="#9f0000" fill-opacity=".705882" stroke-width=".999997" stroke-opacity=".705882"/><path d="m 7.5213186,9 v 2" stroke="#9f0000" fill="#9f0000" fill-opacity=".705882" stroke-width=".999994" stroke-opacity=".705882"/><path d="m 10.521321,9 v 1" stroke="#9f0000" fill="#9f0000" fill-opacity=".705882" stroke-width=".999997" stroke-opacity=".705882"/><path d="M1.4999975 3.0016277l.0000067 2.9972465v0M13 4.5H2M13.5 3l.000007 2.9999977v0" fill="none" stroke="#9f0000" stroke-width="1px" stroke-opacity=".70588237"/></svg>
@@ -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
@@ -48,6 +48,7 @@ set(QGIS_APP_SRCS
qgslayertreeviewindicatorprovider.cpp
qgslayertreeviewembeddedindicator.cpp
qgslayertreeviewfilterindicator.cpp
qgslayertreeviewlowaccuracyindicator.cpp
qgslayertreeviewmemoryindicator.cpp
qgslayertreeviewnocrsindicator.cpp
qgslayertreeviewnonremovableindicator.cpp
@@ -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
@@ -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 );
@@ -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"
@@ -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
@@ -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;
}
@@ -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
@@ -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">
@@ -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>

0 comments on commit eb5fd48

Please sign in to comment.