Skip to content

Commit

Permalink
Fix project unit confusion (pt 2): add project distance unit setting
Browse files Browse the repository at this point in the history
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 ddbdcf8
Show file tree
Hide file tree
Showing 20 changed files with 611 additions and 41 deletions.
5 changes: 5 additions & 0 deletions python/core/qgsproject.sip
Expand Up @@ -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;
Expand Down
13 changes: 13 additions & 0 deletions python/gui/qgsmaptoolidentify.sip
Expand Up @@ -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();
};
2 changes: 2 additions & 0 deletions src/app/qgsattributetabledialog.cpp
Expand Up @@ -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() );
Expand Down Expand Up @@ -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 )
Expand Down
2 changes: 2 additions & 0 deletions src/app/qgsattributetabledialog.h
Expand Up @@ -223,6 +223,8 @@ class APP_EXPORT QgsAttributeTableDialog : public QDialog, private Ui::QgsAttrib

QgsRubberBand* mRubberBand;
QgsSearchWidgetWrapper* mCurrentSearchWidgetWrapper;

friend class TestQgsAttributeTable;
};


Expand Down
1 change: 1 addition & 0 deletions src/app/qgsfieldcalculator.cpp
Expand Up @@ -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()
Expand Down
2 changes: 2 additions & 0 deletions src/app/qgsfieldcalculator.h
Expand Up @@ -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
5 changes: 5 additions & 0 deletions src/app/qgsmaptoolidentifyaction.cpp
Expand Up @@ -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() ) );
Expand Down
1 change: 1 addition & 0 deletions src/app/qgsmaptoolidentifyaction.h
Expand Up @@ -81,6 +81,7 @@ class APP_EXPORT QgsMapToolIdentifyAction : public QgsMapToolIdentify
QgsIdentifyResultsDialog *resultsDialog();

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

};

Expand Down
6 changes: 2 additions & 4 deletions src/app/qgsmeasuredialog.cpp
Expand Up @@ -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();

Expand All @@ -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.
Expand Down
19 changes: 13 additions & 6 deletions src/app/qgsprojectproperties.cpp
Expand Up @@ -54,6 +54,7 @@
#include "qgslayertreegroup.h"
#include "qgslayertreelayer.h"
#include "qgslayertreemodel.h"
#include "qgsunittypes.h"

#include "qgsmessagelog.h"

Expand Down Expand Up @@ -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() ) );
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -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" ) )
Expand Down Expand Up @@ -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
Expand All @@ -1162,8 +1173,6 @@ void QgsProjectProperties::on_cbxProjectionEnabled_toggled( bool onFlyEnabled )

// unset ellipsoid
mEllipsoidIndex = 0;

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

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

srIdUpdated();
Expand Down Expand Up @@ -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 );
Expand Down
19 changes: 19 additions & 0 deletions src/core/qgsproject.cpp
Expand Up @@ -36,6 +36,7 @@
#include "qgsvectorlayer.h"
#include "qgsvisibilitypresetcollection.h"
#include "qgslayerdefinition.h"
#include "qgsunittypes.h"

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

#ifdef Q_OS_UNIX
#include <utime.h>
Expand Down Expand Up @@ -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 );
}

Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/core/qgsproject.h
Expand Up @@ -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;
Expand Down
28 changes: 21 additions & 7 deletions src/gui/qgsmaptoolidentify.cpp
Expand Up @@ -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() );
Expand Down Expand Up @@ -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() );
Expand Down Expand Up @@ -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
Expand All @@ -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" );
Expand Down
10 changes: 10 additions & 0 deletions src/gui/qgsmaptoolidentify.h
Expand Up @@ -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
Expand Down
37 changes: 24 additions & 13 deletions src/ui/qgsprojectpropertiesbase.ui
Expand Up @@ -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>
Expand Down Expand Up @@ -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>
Expand Down

0 comments on commit ddbdcf8

Please sign in to comment.