Skip to content
Permalink
Browse files
Fix project unit confusion (pt 2): add project distance unit setting
Adds a new option in project properties to set the units used for
distance measurements. This setting defaults to the units set in
QGIS options, but can then be overridden for specific projects.

The setting is respected for length and perimeter calculations in:
- Attribute table field update bar
- Field calculator calculations
- Identify tool derived length and perimeter values
- Default unit shown in measure dialog

Also adds unit tests to ensure that length and perimeter calculated
by attribute table update bar, field calculator and identify tool
are consistent wrt ellipsoidal calculations and distance units.

(refs #13209, #12939, #2402, #4857, #4252)
  • Loading branch information
nyalldawson committed Feb 14, 2016
1 parent 17a29f9 commit ddbdcf8ab1196e859642b1083e43a1bcd2ae2e14
@@ -248,6 +248,11 @@ class QgsProject : QObject
/** Convenience function to query topological editing status */
bool topologicalEditing() const;

/** Convenience function to query default distance measurement units for project.
* @note added in QGIS 2.14
*/
QGis::UnitType distanceUnits() const;

/** Return project's home path
@return home path of project (or QString::null if not set) */
QString homePath() const;
@@ -107,4 +107,17 @@ class QgsMapToolIdentify : QgsMapTool

bool identifyRasterLayer( QList<QgsMapToolIdentify::IdentifyResult> *results, QgsRasterLayer *layer, QgsPoint point, const QgsRectangle& viewExtent, double mapUnitsPerPixel );
bool identifyVectorLayer( QList<QgsMapToolIdentify::IdentifyResult> *results, QgsVectorLayer *layer, const QgsPoint& point );

private:

//! Private helper
virtual void convertMeasurement( QgsDistanceArea &calc, double &measure, QGis::UnitType &u, bool isArea );

/** Transforms the measurements of derived attributes in the desired units*/
virtual QGis::UnitType displayUnits();

/** Desired units for distance display.
* @note added in QGIS 2.14
*/
virtual QGis::UnitType displayDistanceUnits();
};
@@ -366,6 +366,7 @@ void QgsAttributeTableDialog::runFieldCalculation( QgsVectorLayer* layer, const

QgsExpression exp( expression );
exp.setGeomCalculator( *myDa );
exp.setDistanceUnits( QgsProject::instance()->distanceUnits() );
bool useGeometry = exp.needsGeometry();

QgsFeatureRequest request( mMainView->masterModel()->request() );
@@ -816,6 +817,7 @@ void QgsAttributeTableDialog::setFilterExpression( const QString& filterString )
QApplication::setOverrideCursor( Qt::WaitCursor );

filterExpression.setGeomCalculator( myDa );
filterExpression.setDistanceUnits( QgsProject::instance()->distanceUnits() );
QgsFeatureRequest request( mMainView->masterModel()->request() );
request.setSubsetOfAttributes( filterExpression.referencedColumns(), mLayer->fields() );
if ( !fetchGeom )
@@ -223,6 +223,8 @@ class APP_EXPORT QgsAttributeTableDialog : public QDialog, private Ui::QgsAttrib

QgsRubberBand* mRubberBand;
QgsSearchWidgetWrapper* mCurrentSearchWidgetWrapper;

friend class TestQgsAttributeTable;
};


@@ -164,6 +164,7 @@ void QgsFieldCalculator::accept()
QString calcString = builder->expressionText();
QgsExpression exp( calcString );
exp.setGeomCalculator( myDa );
exp.setDistanceUnits( QgsProject::instance()->distanceUnits() );

QgsExpressionContext expContext;
expContext << QgsExpressionContextUtils::globalScope()
@@ -71,6 +71,8 @@ class APP_EXPORT QgsFieldCalculator: public QDialog, private Ui::QgsFieldCalcula

/** Idx of changed attribute*/
int mAttributeId;

friend class TestQgsFieldCalculator;
};

#endif // QGSFIELDCALCULATOR_H
@@ -193,6 +193,11 @@ QGis::UnitType QgsMapToolIdentifyAction::displayUnits()
return ok ? unit : QGis::Meters;
}

QGis::UnitType QgsMapToolIdentifyAction::displayDistanceUnits()
{
return QgsProject::instance()->distanceUnits();
}

void QgsMapToolIdentifyAction::handleCopyToClipboard( QgsFeatureStore & featureStore )
{
QgsDebugMsg( QString( "features count = %1" ).arg( featureStore.features().size() ) );
@@ -81,6 +81,7 @@ class APP_EXPORT QgsMapToolIdentifyAction : public QgsMapToolIdentify
QgsIdentifyResultsDialog *resultsDialog();

virtual QGis::UnitType displayUnits() override;
virtual QGis::UnitType displayDistanceUnits() override;

};

@@ -54,9 +54,7 @@ QgsMeasureDialog::QgsMeasureDialog( QgsMeasureTool* tool, Qt::WindowFlags f )
mUnitsCombo->addItem( QgsUnitTypes::toString( QGis::Feet ), QGis::Feet );
mUnitsCombo->addItem( QgsUnitTypes::toString( QGis::Degrees ), QGis::Degrees );
mUnitsCombo->addItem( QgsUnitTypes::toString( QGis::NauticalMiles ), QGis::NauticalMiles );
QSettings settings;
QString units = settings.value( "/qgis/measure/displayunits", QgsUnitTypes::encodeUnit( QGis::Meters ) ).toString();
mUnitsCombo->setCurrentIndex( mUnitsCombo->findData( QgsUnitTypes::decodeDistanceUnit( units ) ) );
mUnitsCombo->setCurrentIndex( mUnitsCombo->findData( QgsProject::instance()->distanceUnits() ) );

updateSettings();

@@ -78,7 +76,7 @@ void QgsMeasureDialog::updateSettings()
mDecimalPlaces = settings.value( "/qgis/measure/decimalplaces", "3" ).toInt();
mCanvasUnits = mTool->canvas()->mapUnits();
// Configure QgsDistanceArea
mDisplayUnits = static_cast< QGis::UnitType >( mUnitsCombo->itemData( mUnitsCombo->currentIndex() ).toInt() );
mDisplayUnits = QgsProject::instance()->distanceUnits();
mDa.setSourceCrs( mTool->canvas()->mapSettings().destinationCrs().srsid() );
mDa.setEllipsoid( QgsProject::instance()->readEntry( "Measure", "/Ellipsoid", GEO_NONE ) );
// Only use ellipsoidal calculation when project wide transformation is enabled.
@@ -54,6 +54,7 @@
#include "qgslayertreegroup.h"
#include "qgslayertreelayer.h"
#include "qgslayertreemodel.h"
#include "qgsunittypes.h"

#include "qgsmessagelog.h"

@@ -84,6 +85,12 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas* mapCanvas, QWidget *pa
mCoordinateDisplayComboBox->addItem( tr( "Degrees, minutes" ), DegreesMinutes );
mCoordinateDisplayComboBox->addItem( tr( "Degrees, minutes, seconds" ), DegreesMinutesSeconds );

mDistanceUnitsCombo->addItem( tr( "Meters" ), QGis::Meters );
mDistanceUnitsCombo->addItem( tr( "Feet" ), QGis::Feet );
mDistanceUnitsCombo->addItem( tr( "Nautical miles" ), QGis::NauticalMiles );
mDistanceUnitsCombo->addItem( tr( "Degrees" ), QGis::Degrees );
mDistanceUnitsCombo->addItem( tr( "Map units" ), QGis::UnknownUnit );

connect( buttonBox->button( QDialogButtonBox::Apply ), SIGNAL( clicked() ), this, SLOT( apply() ) );
connect( this, SIGNAL( accepted() ), this, SLOT( apply() ) );
connect( projectionSelector, SIGNAL( sridSelected( QString ) ), this, SLOT( srIdUpdated() ) );
@@ -157,6 +164,8 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas* mapCanvas, QWidget *pa
else
mCoordinateDisplayComboBox->setCurrentIndex( mCoordinateDisplayComboBox->findData( DecimalDegrees ) );

mDistanceUnitsCombo->setCurrentIndex( mDistanceUnitsCombo->findData( QgsProject::instance()->distanceUnits() ) );

//get the color selections and set the button color accordingly
int myRedInt = QgsProject::instance()->readNumEntry( "Gui", "/SelectionColorRedPart", 255 );
int myGreenInt = QgsProject::instance()->readNumEntry( "Gui", "/SelectionColorGreenPart", 255 );
@@ -779,6 +788,9 @@ void QgsProjectProperties::apply()
// Announce that we may have a new display precision setting
emit displayPrecisionChanged();

QGis::UnitType distanceUnits = static_cast< QGis::UnitType >( mDistanceUnitsCombo->itemData( mDistanceUnitsCombo->currentIndex() ).toInt() );
QgsProject::instance()->writeEntry( "Measurement", "/DistanceUnits", QgsUnitTypes::encodeUnit( distanceUnits ) );

QgsProject::instance()->writeEntry( "Paths", "/Absolute", cbxAbsolutePath->currentIndex() == 0 );

if ( mEllipsoidList.at( mEllipsoidIndex ).acronym.startsWith( "PARAMETER" ) )
@@ -1139,7 +1151,6 @@ void QgsProjectProperties::showProjectionsTab()

void QgsProjectProperties::on_cbxProjectionEnabled_toggled( bool onFlyEnabled )
{
QString measureOnFlyState = tr( "Measure tool (CRS transformation: %1)" );
if ( !onFlyEnabled )
{
// reset projection to default
@@ -1162,8 +1173,6 @@ void QgsProjectProperties::on_cbxProjectionEnabled_toggled( bool onFlyEnabled )

// unset ellipsoid
mEllipsoidIndex = 0;

btnGrpMeasureEllipsoid->setTitle( measureOnFlyState.arg( tr( "OFF" ) ) );
}
else
{
@@ -1172,8 +1181,6 @@ void QgsProjectProperties::on_cbxProjectionEnabled_toggled( bool onFlyEnabled )
mLayerSrsId = projectionSelector->selectedCrsId();
}
projectionSelector->setSelectedCrsId( mProjectSrsId );

btnGrpMeasureEllipsoid->setTitle( measureOnFlyState.arg( tr( "ON" ) ) );
}

srIdUpdated();
@@ -1230,7 +1237,7 @@ void QgsProjectProperties::updateGuiForMapUnits( QGis::UnitType units )
else
{
//make sure map units option is shown in coordinate display combo
QString mapUnitString = tr( "Map units (%1)" ).arg( QGis::tr( units ) );
QString mapUnitString = tr( "Map units (%1)" ).arg( QgsUnitTypes::toString( units ) );
if ( idx < 0 )
{
mCoordinateDisplayComboBox->insertItem( 0, mapUnitString, MapUnits );
@@ -36,6 +36,7 @@
#include "qgsvectorlayer.h"
#include "qgsvisibilitypresetcollection.h"
#include "qgslayerdefinition.h"
#include "qgsunittypes.h"

#include <QApplication>
#include <QFileInfo>
@@ -45,6 +46,7 @@
#include <QTemporaryFile>
#include <QDir>
#include <QUrl>
#include <QSettings>

#ifdef Q_OS_UNIX
#include <utime.h>
@@ -452,6 +454,10 @@ void QgsProject::clear()
writeEntry( "PositionPrecision", "/DecimalPlaces", 2 );
writeEntry( "Paths", "/Absolute", false );

//copy default distance units to project
QSettings s;
writeEntry( "Measurement", "/DistanceUnits", s.value( "/qgis/measure/displayunits" ).toString() );

setDirty( false );
}

@@ -2058,6 +2064,19 @@ bool QgsProject::topologicalEditing() const
return ( QgsProject::instance()->readNumEntry( "Digitizing", "/TopologicalEditing", 0 ) > 0 );
}

QGis::UnitType QgsProject::distanceUnits() const
{
QString distanceUnitString = QgsProject::instance()->readEntry( "Measurement", "/DistanceUnits", QString() );
if ( !distanceUnitString.isEmpty() )
return QgsUnitTypes::decodeDistanceUnit( distanceUnitString );

//fallback to QGIS default measurement unit
QSettings s;
bool ok = false;
QGis::UnitType type = QgsUnitTypes::decodeDistanceUnit( s.value( "/qgis/measure/displayunits" ).toString(), &ok );
return ok ? type : QGis::Meters;
}

void QgsProjectBadLayerDefaultHandler::handleBadLayers( const QList<QDomNode>& /*layers*/, const QDomDocument& /*projectDom*/ )
{
// just ignore any bad layers
@@ -293,6 +293,11 @@ class CORE_EXPORT QgsProject : public QObject
/** Convenience function to query topological editing status */
bool topologicalEditing() const;

/** Convenience function to query default distance measurement units for project.
* @note added in QGIS 2.14
*/
QGis::UnitType distanceUnits() const;

/** Return project's home path
@return home path of project (or QString::null if not set) */
QString homePath() const;
@@ -369,9 +369,8 @@ QMap< QString, QString > QgsMapToolIdentify::featureDerivedAttributes( QgsFeatur
if ( geometryType == QGis::Line )
{
double dist = calc.measureLength( feature->constGeometry() );
QGis::UnitType myDisplayUnits;
convertMeasurement( calc, dist, myDisplayUnits, false );
QString str = calc.textUnit( dist, 3, myDisplayUnits, false ); // dist and myDisplayUnits are out params
dist = calc.convertLengthMeasurement( dist, displayDistanceUnits() );
QString str = formatDistance( dist );
derivedAttributes.insert( tr( "Length" ), str );

const QgsCurveV2* curve = dynamic_cast< const QgsCurveV2* >( feature->constGeometry()->geometry() );
@@ -399,13 +398,15 @@ QMap< QString, QString > QgsMapToolIdentify::featureDerivedAttributes( QgsFeatur
else if ( geometryType == QGis::Polygon )
{
double area = calc.measureArea( feature->constGeometry() );
double perimeter = calc.measurePerimeter( feature->constGeometry() );

QGis::UnitType myDisplayUnits;
convertMeasurement( calc, area, myDisplayUnits, true ); // area and myDisplayUnits are out params
QString str = calc.textUnit( area, 3, myDisplayUnits, true );
derivedAttributes.insert( tr( "Area" ), str );
convertMeasurement( calc, perimeter, myDisplayUnits, false ); // perimeter and myDisplayUnits are out params
str = calc.textUnit( perimeter, 3, myDisplayUnits, false );

double perimeter = calc.measurePerimeter( feature->constGeometry() );
perimeter = calc.convertLengthMeasurement( perimeter, displayDistanceUnits() );
str = formatDistance( perimeter );
derivedAttributes.insert( tr( "Perimeter" ), str );

str = QLocale::system().toString( feature->constGeometry()->geometry()->nCoordinates() );
@@ -640,7 +641,7 @@ bool QgsMapToolIdentify::identifyRasterLayer( QList<IdentifyResult> *results, Qg

void QgsMapToolIdentify::convertMeasurement( QgsDistanceArea &calc, double &measure, QGis::UnitType &u, bool isArea )
{
// Helper for converting between meters and feet
// Helper for converting between units
// The parameter &u is out only...

// Get the canvas units
@@ -655,6 +656,19 @@ QGis::UnitType QgsMapToolIdentify::displayUnits()
return mCanvas->mapUnits();
}

QGis::UnitType QgsMapToolIdentify::displayDistanceUnits()
{
return mCanvas->mapUnits();
}

QString QgsMapToolIdentify::formatDistance( double distance )
{
QSettings settings;
bool baseUnit = settings.value( "/qgis/measure/keepbaseunit", false ).toBool();

return QgsDistanceArea::textUnit( distance, 3, displayDistanceUnits(), false, baseUnit );
}

void QgsMapToolIdentify::formatChanged( QgsRasterLayer *layer )
{
QgsDebugMsg( "Entered" );
@@ -163,6 +163,16 @@ class GUI_EXPORT QgsMapToolIdentify : public QgsMapTool
/** Transforms the measurements of derived attributes in the desired units*/
virtual QGis::UnitType displayUnits();

/** Desired units for distance display.
* @note added in QGIS 2.14
*/
virtual QGis::UnitType displayDistanceUnits();

/** Format a distance into a suitable string for display to the user
* @note added in QGIS 2.14
*/
QString formatDistance( double distance );

QMap< QString, QString > featureDerivedAttributes( QgsFeature *feature, QgsMapLayer *layer, const QgsPoint& layerPoint = QgsPoint() );

/** Adds details of the closest vertex to derived attributes
@@ -471,43 +471,53 @@
<item>
<widget class="QgsCollapsibleGroupBox" name="btnGrpMeasureEllipsoid">
<property name="title">
<string>Measure tool</string>
<string>Measurements</string>
</property>
<property name="syncGroup" stdset="0">
<string notr="true">projgeneral</string>
</property>
<layout class="QGridLayout" name="gridLayoutMeasureTool">
<item row="1" column="0">
<item row="0" column="0">
<widget class="QLabel" name="textLabel1_8">
<property name="text">
<string>Ellipsoid
(for distance calculations)</string>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="1" column="4">
<widget class="QLineEdit" name="leSemiMinor"/>
</item>
<item row="2" column="1" colspan="4">
<widget class="QComboBox" name="mDistanceUnitsCombo"/>
</item>
<item row="1" column="2">
<widget class="QLineEdit" name="leSemiMajor"/>
</item>
<item row="0" column="1" colspan="4">
<widget class="QComboBox" name="cmbEllipsoid"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_28">
<property name="text">
<string>Units for distance measurement</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_41">
<property name="text">
<string>Semi-major</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLineEdit" name="leSemiMajor"/>
</item>
<item row="2" column="4">
<widget class="QLineEdit" name="leSemiMinor"/>
</item>
<item row="2" column="3">
<item row="1" column="3">
<widget class="QLabel" name="label_42">
<property name="text">
<string>Semi-minor</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="4">
<widget class="QComboBox" name="cmbEllipsoid"/>
</item>
</layout>
</widget>
</item>
@@ -2528,6 +2538,7 @@
<tabstop>cmbEllipsoid</tabstop>
<tabstop>leSemiMajor</tabstop>
<tabstop>leSemiMinor</tabstop>
<tabstop>mDistanceUnitsCombo</tabstop>
<tabstop>mCoordinateDisplayComboBox</tabstop>
<tabstop>radAutomatic</tabstop>
<tabstop>radManual</tabstop>

0 comments on commit ddbdcf8

Please sign in to comment.