Skip to content

Commit ddbdcf8

Browse files
committed
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)
1 parent 17a29f9 commit ddbdcf8

20 files changed

+611
-41
lines changed

python/core/qgsproject.sip

+5
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,11 @@ class QgsProject : QObject
248248
/** Convenience function to query topological editing status */
249249
bool topologicalEditing() const;
250250

251+
/** Convenience function to query default distance measurement units for project.
252+
* @note added in QGIS 2.14
253+
*/
254+
QGis::UnitType distanceUnits() const;
255+
251256
/** Return project's home path
252257
@return home path of project (or QString::null if not set) */
253258
QString homePath() const;

python/gui/qgsmaptoolidentify.sip

+13
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,17 @@ class QgsMapToolIdentify : QgsMapTool
107107

108108
bool identifyRasterLayer( QList<QgsMapToolIdentify::IdentifyResult> *results, QgsRasterLayer *layer, QgsPoint point, const QgsRectangle& viewExtent, double mapUnitsPerPixel );
109109
bool identifyVectorLayer( QList<QgsMapToolIdentify::IdentifyResult> *results, QgsVectorLayer *layer, const QgsPoint& point );
110+
111+
private:
112+
113+
//! Private helper
114+
virtual void convertMeasurement( QgsDistanceArea &calc, double &measure, QGis::UnitType &u, bool isArea );
115+
116+
/** Transforms the measurements of derived attributes in the desired units*/
117+
virtual QGis::UnitType displayUnits();
118+
119+
/** Desired units for distance display.
120+
* @note added in QGIS 2.14
121+
*/
122+
virtual QGis::UnitType displayDistanceUnits();
110123
};

src/app/qgsattributetabledialog.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ void QgsAttributeTableDialog::runFieldCalculation( QgsVectorLayer* layer, const
366366

367367
QgsExpression exp( expression );
368368
exp.setGeomCalculator( *myDa );
369+
exp.setDistanceUnits( QgsProject::instance()->distanceUnits() );
369370
bool useGeometry = exp.needsGeometry();
370371

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

818819
filterExpression.setGeomCalculator( myDa );
820+
filterExpression.setDistanceUnits( QgsProject::instance()->distanceUnits() );
819821
QgsFeatureRequest request( mMainView->masterModel()->request() );
820822
request.setSubsetOfAttributes( filterExpression.referencedColumns(), mLayer->fields() );
821823
if ( !fetchGeom )

src/app/qgsattributetabledialog.h

+2
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ class APP_EXPORT QgsAttributeTableDialog : public QDialog, private Ui::QgsAttrib
223223

224224
QgsRubberBand* mRubberBand;
225225
QgsSearchWidgetWrapper* mCurrentSearchWidgetWrapper;
226+
227+
friend class TestQgsAttributeTable;
226228
};
227229

228230

src/app/qgsfieldcalculator.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ void QgsFieldCalculator::accept()
164164
QString calcString = builder->expressionText();
165165
QgsExpression exp( calcString );
166166
exp.setGeomCalculator( myDa );
167+
exp.setDistanceUnits( QgsProject::instance()->distanceUnits() );
167168

168169
QgsExpressionContext expContext;
169170
expContext << QgsExpressionContextUtils::globalScope()

src/app/qgsfieldcalculator.h

+2
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ class APP_EXPORT QgsFieldCalculator: public QDialog, private Ui::QgsFieldCalcula
7171

7272
/** Idx of changed attribute*/
7373
int mAttributeId;
74+
75+
friend class TestQgsFieldCalculator;
7476
};
7577

7678
#endif // QGSFIELDCALCULATOR_H

src/app/qgsmaptoolidentifyaction.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,11 @@ QGis::UnitType QgsMapToolIdentifyAction::displayUnits()
193193
return ok ? unit : QGis::Meters;
194194
}
195195

196+
QGis::UnitType QgsMapToolIdentifyAction::displayDistanceUnits()
197+
{
198+
return QgsProject::instance()->distanceUnits();
199+
}
200+
196201
void QgsMapToolIdentifyAction::handleCopyToClipboard( QgsFeatureStore & featureStore )
197202
{
198203
QgsDebugMsg( QString( "features count = %1" ).arg( featureStore.features().size() ) );

src/app/qgsmaptoolidentifyaction.h

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class APP_EXPORT QgsMapToolIdentifyAction : public QgsMapToolIdentify
8181
QgsIdentifyResultsDialog *resultsDialog();
8282

8383
virtual QGis::UnitType displayUnits() override;
84+
virtual QGis::UnitType displayDistanceUnits() override;
8485

8586
};
8687

src/app/qgsmeasuredialog.cpp

+2-4
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,7 @@ QgsMeasureDialog::QgsMeasureDialog( QgsMeasureTool* tool, Qt::WindowFlags f )
5454
mUnitsCombo->addItem( QgsUnitTypes::toString( QGis::Feet ), QGis::Feet );
5555
mUnitsCombo->addItem( QgsUnitTypes::toString( QGis::Degrees ), QGis::Degrees );
5656
mUnitsCombo->addItem( QgsUnitTypes::toString( QGis::NauticalMiles ), QGis::NauticalMiles );
57-
QSettings settings;
58-
QString units = settings.value( "/qgis/measure/displayunits", QgsUnitTypes::encodeUnit( QGis::Meters ) ).toString();
59-
mUnitsCombo->setCurrentIndex( mUnitsCombo->findData( QgsUnitTypes::decodeDistanceUnit( units ) ) );
57+
mUnitsCombo->setCurrentIndex( mUnitsCombo->findData( QgsProject::instance()->distanceUnits() ) );
6058

6159
updateSettings();
6260

@@ -78,7 +76,7 @@ void QgsMeasureDialog::updateSettings()
7876
mDecimalPlaces = settings.value( "/qgis/measure/decimalplaces", "3" ).toInt();
7977
mCanvasUnits = mTool->canvas()->mapUnits();
8078
// Configure QgsDistanceArea
81-
mDisplayUnits = static_cast< QGis::UnitType >( mUnitsCombo->itemData( mUnitsCombo->currentIndex() ).toInt() );
79+
mDisplayUnits = QgsProject::instance()->distanceUnits();
8280
mDa.setSourceCrs( mTool->canvas()->mapSettings().destinationCrs().srsid() );
8381
mDa.setEllipsoid( QgsProject::instance()->readEntry( "Measure", "/Ellipsoid", GEO_NONE ) );
8482
// Only use ellipsoidal calculation when project wide transformation is enabled.

src/app/qgsprojectproperties.cpp

+13-6
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
#include "qgslayertreegroup.h"
5555
#include "qgslayertreelayer.h"
5656
#include "qgslayertreemodel.h"
57+
#include "qgsunittypes.h"
5758

5859
#include "qgsmessagelog.h"
5960

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

88+
mDistanceUnitsCombo->addItem( tr( "Meters" ), QGis::Meters );
89+
mDistanceUnitsCombo->addItem( tr( "Feet" ), QGis::Feet );
90+
mDistanceUnitsCombo->addItem( tr( "Nautical miles" ), QGis::NauticalMiles );
91+
mDistanceUnitsCombo->addItem( tr( "Degrees" ), QGis::Degrees );
92+
mDistanceUnitsCombo->addItem( tr( "Map units" ), QGis::UnknownUnit );
93+
8794
connect( buttonBox->button( QDialogButtonBox::Apply ), SIGNAL( clicked() ), this, SLOT( apply() ) );
8895
connect( this, SIGNAL( accepted() ), this, SLOT( apply() ) );
8996
connect( projectionSelector, SIGNAL( sridSelected( QString ) ), this, SLOT( srIdUpdated() ) );
@@ -157,6 +164,8 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas* mapCanvas, QWidget *pa
157164
else
158165
mCoordinateDisplayComboBox->setCurrentIndex( mCoordinateDisplayComboBox->findData( DecimalDegrees ) );
159166

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

791+
QGis::UnitType distanceUnits = static_cast< QGis::UnitType >( mDistanceUnitsCombo->itemData( mDistanceUnitsCombo->currentIndex() ).toInt() );
792+
QgsProject::instance()->writeEntry( "Measurement", "/DistanceUnits", QgsUnitTypes::encodeUnit( distanceUnits ) );
793+
782794
QgsProject::instance()->writeEntry( "Paths", "/Absolute", cbxAbsolutePath->currentIndex() == 0 );
783795

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

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

11631174
// unset ellipsoid
11641175
mEllipsoidIndex = 0;
1165-
1166-
btnGrpMeasureEllipsoid->setTitle( measureOnFlyState.arg( tr( "OFF" ) ) );
11671176
}
11681177
else
11691178
{
@@ -1172,8 +1181,6 @@ void QgsProjectProperties::on_cbxProjectionEnabled_toggled( bool onFlyEnabled )
11721181
mLayerSrsId = projectionSelector->selectedCrsId();
11731182
}
11741183
projectionSelector->setSelectedCrsId( mProjectSrsId );
1175-
1176-
btnGrpMeasureEllipsoid->setTitle( measureOnFlyState.arg( tr( "ON" ) ) );
11771184
}
11781185

11791186
srIdUpdated();
@@ -1230,7 +1237,7 @@ void QgsProjectProperties::updateGuiForMapUnits( QGis::UnitType units )
12301237
else
12311238
{
12321239
//make sure map units option is shown in coordinate display combo
1233-
QString mapUnitString = tr( "Map units (%1)" ).arg( QGis::tr( units ) );
1240+
QString mapUnitString = tr( "Map units (%1)" ).arg( QgsUnitTypes::toString( units ) );
12341241
if ( idx < 0 )
12351242
{
12361243
mCoordinateDisplayComboBox->insertItem( 0, mapUnitString, MapUnits );

src/core/qgsproject.cpp

+19
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "qgsvectorlayer.h"
3737
#include "qgsvisibilitypresetcollection.h"
3838
#include "qgslayerdefinition.h"
39+
#include "qgsunittypes.h"
3940

4041
#include <QApplication>
4142
#include <QFileInfo>
@@ -45,6 +46,7 @@
4546
#include <QTemporaryFile>
4647
#include <QDir>
4748
#include <QUrl>
49+
#include <QSettings>
4850

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

457+
//copy default distance units to project
458+
QSettings s;
459+
writeEntry( "Measurement", "/DistanceUnits", s.value( "/qgis/measure/displayunits" ).toString() );
460+
455461
setDirty( false );
456462
}
457463

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

2067+
QGis::UnitType QgsProject::distanceUnits() const
2068+
{
2069+
QString distanceUnitString = QgsProject::instance()->readEntry( "Measurement", "/DistanceUnits", QString() );
2070+
if ( !distanceUnitString.isEmpty() )
2071+
return QgsUnitTypes::decodeDistanceUnit( distanceUnitString );
2072+
2073+
//fallback to QGIS default measurement unit
2074+
QSettings s;
2075+
bool ok = false;
2076+
QGis::UnitType type = QgsUnitTypes::decodeDistanceUnit( s.value( "/qgis/measure/displayunits" ).toString(), &ok );
2077+
return ok ? type : QGis::Meters;
2078+
}
2079+
20612080
void QgsProjectBadLayerDefaultHandler::handleBadLayers( const QList<QDomNode>& /*layers*/, const QDomDocument& /*projectDom*/ )
20622081
{
20632082
// just ignore any bad layers

src/core/qgsproject.h

+5
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,11 @@ class CORE_EXPORT QgsProject : public QObject
293293
/** Convenience function to query topological editing status */
294294
bool topologicalEditing() const;
295295

296+
/** Convenience function to query default distance measurement units for project.
297+
* @note added in QGIS 2.14
298+
*/
299+
QGis::UnitType distanceUnits() const;
300+
296301
/** Return project's home path
297302
@return home path of project (or QString::null if not set) */
298303
QString homePath() const;

src/gui/qgsmaptoolidentify.cpp

+21-7
Original file line numberDiff line numberDiff line change
@@ -369,9 +369,8 @@ QMap< QString, QString > QgsMapToolIdentify::featureDerivedAttributes( QgsFeatur
369369
if ( geometryType == QGis::Line )
370370
{
371371
double dist = calc.measureLength( feature->constGeometry() );
372-
QGis::UnitType myDisplayUnits;
373-
convertMeasurement( calc, dist, myDisplayUnits, false );
374-
QString str = calc.textUnit( dist, 3, myDisplayUnits, false ); // dist and myDisplayUnits are out params
372+
dist = calc.convertLengthMeasurement( dist, displayDistanceUnits() );
373+
QString str = formatDistance( dist );
375374
derivedAttributes.insert( tr( "Length" ), str );
376375

377376
const QgsCurveV2* curve = dynamic_cast< const QgsCurveV2* >( feature->constGeometry()->geometry() );
@@ -399,13 +398,15 @@ QMap< QString, QString > QgsMapToolIdentify::featureDerivedAttributes( QgsFeatur
399398
else if ( geometryType == QGis::Polygon )
400399
{
401400
double area = calc.measureArea( feature->constGeometry() );
402-
double perimeter = calc.measurePerimeter( feature->constGeometry() );
401+
403402
QGis::UnitType myDisplayUnits;
404403
convertMeasurement( calc, area, myDisplayUnits, true ); // area and myDisplayUnits are out params
405404
QString str = calc.textUnit( area, 3, myDisplayUnits, true );
406405
derivedAttributes.insert( tr( "Area" ), str );
407-
convertMeasurement( calc, perimeter, myDisplayUnits, false ); // perimeter and myDisplayUnits are out params
408-
str = calc.textUnit( perimeter, 3, myDisplayUnits, false );
406+
407+
double perimeter = calc.measurePerimeter( feature->constGeometry() );
408+
perimeter = calc.convertLengthMeasurement( perimeter, displayDistanceUnits() );
409+
str = formatDistance( perimeter );
409410
derivedAttributes.insert( tr( "Perimeter" ), str );
410411

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

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

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

659+
QGis::UnitType QgsMapToolIdentify::displayDistanceUnits()
660+
{
661+
return mCanvas->mapUnits();
662+
}
663+
664+
QString QgsMapToolIdentify::formatDistance( double distance )
665+
{
666+
QSettings settings;
667+
bool baseUnit = settings.value( "/qgis/measure/keepbaseunit", false ).toBool();
668+
669+
return QgsDistanceArea::textUnit( distance, 3, displayDistanceUnits(), false, baseUnit );
670+
}
671+
658672
void QgsMapToolIdentify::formatChanged( QgsRasterLayer *layer )
659673
{
660674
QgsDebugMsg( "Entered" );

src/gui/qgsmaptoolidentify.h

+10
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,16 @@ class GUI_EXPORT QgsMapToolIdentify : public QgsMapTool
163163
/** Transforms the measurements of derived attributes in the desired units*/
164164
virtual QGis::UnitType displayUnits();
165165

166+
/** Desired units for distance display.
167+
* @note added in QGIS 2.14
168+
*/
169+
virtual QGis::UnitType displayDistanceUnits();
170+
171+
/** Format a distance into a suitable string for display to the user
172+
* @note added in QGIS 2.14
173+
*/
174+
QString formatDistance( double distance );
175+
166176
QMap< QString, QString > featureDerivedAttributes( QgsFeature *feature, QgsMapLayer *layer, const QgsPoint& layerPoint = QgsPoint() );
167177

168178
/** Adds details of the closest vertex to derived attributes

src/ui/qgsprojectpropertiesbase.ui

+24-13
Original file line numberDiff line numberDiff line change
@@ -471,43 +471,53 @@
471471
<item>
472472
<widget class="QgsCollapsibleGroupBox" name="btnGrpMeasureEllipsoid">
473473
<property name="title">
474-
<string>Measure tool</string>
474+
<string>Measurements</string>
475475
</property>
476476
<property name="syncGroup" stdset="0">
477477
<string notr="true">projgeneral</string>
478478
</property>
479479
<layout class="QGridLayout" name="gridLayoutMeasureTool">
480-
<item row="1" column="0">
480+
<item row="0" column="0">
481481
<widget class="QLabel" name="textLabel1_8">
482482
<property name="text">
483483
<string>Ellipsoid
484484
(for distance calculations)</string>
485485
</property>
486486
</widget>
487487
</item>
488-
<item row="2" column="1">
488+
<item row="1" column="4">
489+
<widget class="QLineEdit" name="leSemiMinor"/>
490+
</item>
491+
<item row="2" column="1" colspan="4">
492+
<widget class="QComboBox" name="mDistanceUnitsCombo"/>
493+
</item>
494+
<item row="1" column="2">
495+
<widget class="QLineEdit" name="leSemiMajor"/>
496+
</item>
497+
<item row="0" column="1" colspan="4">
498+
<widget class="QComboBox" name="cmbEllipsoid"/>
499+
</item>
500+
<item row="2" column="0">
501+
<widget class="QLabel" name="label_28">
502+
<property name="text">
503+
<string>Units for distance measurement</string>
504+
</property>
505+
</widget>
506+
</item>
507+
<item row="1" column="1">
489508
<widget class="QLabel" name="label_41">
490509
<property name="text">
491510
<string>Semi-major</string>
492511
</property>
493512
</widget>
494513
</item>
495-
<item row="2" column="2">
496-
<widget class="QLineEdit" name="leSemiMajor"/>
497-
</item>
498-
<item row="2" column="4">
499-
<widget class="QLineEdit" name="leSemiMinor"/>
500-
</item>
501-
<item row="2" column="3">
514+
<item row="1" column="3">
502515
<widget class="QLabel" name="label_42">
503516
<property name="text">
504517
<string>Semi-minor</string>
505518
</property>
506519
</widget>
507520
</item>
508-
<item row="1" column="1" colspan="4">
509-
<widget class="QComboBox" name="cmbEllipsoid"/>
510-
</item>
511521
</layout>
512522
</widget>
513523
</item>
@@ -2528,6 +2538,7 @@
25282538
<tabstop>cmbEllipsoid</tabstop>
25292539
<tabstop>leSemiMajor</tabstop>
25302540
<tabstop>leSemiMinor</tabstop>
2541+
<tabstop>mDistanceUnitsCombo</tabstop>
25312542
<tabstop>mCoordinateDisplayComboBox</tabstop>
25322543
<tabstop>radAutomatic</tabstop>
25332544
<tabstop>radManual</tabstop>

0 commit comments

Comments
 (0)