Skip to content

Commit dfdcec8

Browse files
committed
Fix project unit confusion (pt 3): add area unit settings with a
ton of available area units (eg m2, km2, mi2, ft2, yd2, ha, ac, etc) Adds a new option in both the QGIS setting and project properties to set the units used for area measurements. Just like the distance setting, this defaults to the units set in QGIS options, but can then be overridden for specific projects. The setting is respected for area calculations in: - Attribute table field update bar - Field calculator calculations - Identify tool derived length and perimeter values Also adds unit tests to ensure that area calculated by attribute table update bar, field calculator and identify tool are consistent wrt ellipsoidal calculations and area units. TODO: make measure tool respect area setting (refs #13209, #4252 and fixes #12939, #2402, #4857)
1 parent 35c2d18 commit dfdcec8

26 files changed

+862
-145
lines changed

python/core/qgsdistancearea.sip

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,12 @@ class QgsDistanceArea
105105

106106
/** Measures the area of a geometry.
107107
* @param geometry geometry to measure
108-
* @returns area of geometry. For geometry collections, non surface geometries will be ignored
108+
* @returns area of geometry. For geometry collections, non surface geometries will be ignored. The units for the
109+
* returned area can be retrieved by calling areaUnits().
109110
* @note added in QGIS 2.12
110111
* @see measureLength()
111112
* @see measurePerimeter()
113+
* @see areaUnits()
112114
*/
113115
double measureArea( const QgsGeometry* geometry ) const;
114116

@@ -151,30 +153,73 @@ class QgsDistanceArea
151153

152154
/** Returns the units of distance for length calculations made by this object.
153155
* @note added in QGIS 2.14
156+
* @see areaUnits()
154157
*/
155158
QGis::UnitType lengthUnits() const;
156159

160+
/** Returns the units of area for areal calculations made by this object.
161+
* @note added in QGIS 2.14
162+
* @see lengthUnits()
163+
*/
164+
QgsUnitTypes::AreaUnit areaUnits() const;
165+
157166
//! measures polygon area
158167
double measurePolygon( const QList<QgsPoint>& points ) const;
159168

160169
//! compute bearing - in radians
161170
double bearing( const QgsPoint& p1, const QgsPoint& p2 ) const;
162171

172+
/** Returns a measurement formatted as a friendly string
173+
* @param value value of measurement
174+
* @param decimals number of decimal places to show
175+
* @param u unit of measurement
176+
* @param isArea set to true if measurement is an area measurement
177+
* @param keepBaseUnit set to false to allow conversion of large distances to more suitable units, eg meters
178+
* to kilometers
179+
* @return formatted measurement string
180+
* @see formatArea()
181+
*/
182+
//TODO QGIS 3.0 - remove isArea parameter (use AreaUnit variant instead), rename to formatDistance
163183
static QString textUnit( double value, int decimals, QGis::UnitType u, bool isArea, bool keepBaseUnit = false );
164184

185+
/** Returns an area formatted as a friendly string.
186+
* @param area area to format
187+
* @param decimals number of decimal places to show
188+
* @param unit unit of area
189+
* @param keepBaseUnit set to false to allow conversion of large areas to more suitable units, eg square meters to
190+
* square kilometers
191+
* @returns formatted area string
192+
* @note added in QGIS 2.14
193+
* @see textUnit()
194+
*/
195+
static QString formatArea( double area, int decimals, QgsUnitTypes::AreaUnit unit, bool keepBaseUnit = false );
196+
165197
//! Helper for conversion between physical units
166198
// TODO QGIS 3.0 - remove this method, as its behaviour is non-intuitive.
167199
void convertMeasurement( double &measure /In,Out/, QGis::UnitType &measureUnits /In,Out/, QGis::UnitType displayUnits, bool isArea ) const;
168200

169201
/** Takes a length measurement calculated by this QgsDistanceArea object and converts it to a
170202
* different distance unit.
171203
* @param length length value calculated by this class to convert. It is assumed that the length
172-
* was calculated by this class, ie that its unit of length is equal lengthUnits().
204+
* was calculated by this class, ie that its unit of length is equal to lengthUnits().
173205
* @param toUnits distance unit to convert measurement to
174206
* @returns converted distance
207+
* @see convertAreaMeasurement()
208+
* @note added in QGIS 2.14
175209
*/
176210
double convertLengthMeasurement( double length, QGis::UnitType toUnits ) const;
177211

212+
/** Takes an area measurement calculated by this QgsDistanceArea object and converts it to a
213+
* different areal unit.
214+
* @param area area value calculated by this class to convert. It is assumed that the area
215+
* was calculated by this class, ie that its unit of area is equal to areaUnits().
216+
* @param toUnits area unit to convert measurement to
217+
* @returns converted area
218+
* @see convertLengthMeasurement()
219+
* @note added in QGIS 2.14
220+
*/
221+
double convertAreaMeasurement( double area, QgsUnitTypes::AreaUnit toUnits ) const;
222+
178223
protected:
179224
//! measures polygon area and perimeter, vertices are extracted from WKB
180225
// @note not available in python bindings

python/core/qgsproject.sip

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,16 @@ class QgsProject : QObject
250250

251251
/** Convenience function to query default distance measurement units for project.
252252
* @note added in QGIS 2.14
253+
* @see areaUnits()
253254
*/
254255
QGis::UnitType distanceUnits() const;
255256

257+
/** Convenience function to query default area measurement units for project.
258+
* @note added in QGIS 2.14
259+
* @see distanceUnits()
260+
*/
261+
QgsUnitTypes::AreaUnit areaUnits() const;
262+
256263
/** Return project's home path
257264
@return home path of project (or QString::null if not set) */
258265
QString homePath() const;

python/gui/qgsmaptoolidentify.sip

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,23 @@ class QgsMapToolIdentify : QgsMapTool
111111
private:
112112

113113
//! Private helper
114-
virtual void convertMeasurement( QgsDistanceArea &calc, double &measure, QGis::UnitType &u, bool isArea );
114+
//! @deprecated use displayDistanceUnits() and displayAreaUnits() instead
115+
virtual void convertMeasurement( QgsDistanceArea &calc, double &measure, QGis::UnitType &u, bool isArea ) /Deprecated/;
115116

116-
/** Transforms the measurements of derived attributes in the desired units*/
117-
virtual QGis::UnitType displayUnits();
117+
/** Transforms the measurements of derived attributes in the desired units
118+
* @deprecated use displayDistanceUnits() and displayAreaUnits() instead
119+
*/
120+
virtual QGis::UnitType displayUnits() /Deprecated/;
118121

119122
/** Desired units for distance display.
120123
* @note added in QGIS 2.14
124+
* @see displayAreaUnits()
121125
*/
122-
virtual QGis::UnitType displayDistanceUnits();
126+
virtual QGis::UnitType displayDistanceUnits() const;
127+
128+
/** Desired units for area display.
129+
* @note added in QGIS 2.14
130+
* @see displayDistanceUnits()
131+
*/
132+
virtual QgsUnitTypes::AreaUnit displayAreaUnits() const;
123133
};

src/app/qgsattributetabledialog.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ void QgsAttributeTableDialog::runFieldCalculation( QgsVectorLayer* layer, const
367367
QgsExpression exp( expression );
368368
exp.setGeomCalculator( *myDa );
369369
exp.setDistanceUnits( QgsProject::instance()->distanceUnits() );
370+
exp.setAreaUnits( QgsProject::instance()->areaUnits() );
370371
bool useGeometry = exp.needsGeometry();
371372

372373
QgsFeatureRequest request( mMainView->masterModel()->request() );
@@ -818,6 +819,7 @@ void QgsAttributeTableDialog::setFilterExpression( const QString& filterString )
818819

819820
filterExpression.setGeomCalculator( myDa );
820821
filterExpression.setDistanceUnits( QgsProject::instance()->distanceUnits() );
822+
filterExpression.setAreaUnits( QgsProject::instance()->areaUnits() );
821823
QgsFeatureRequest request( mMainView->masterModel()->request() );
822824
request.setSubsetOfAttributes( filterExpression.referencedColumns(), mLayer->fields() );
823825
if ( !fetchGeom )

src/app/qgsfieldcalculator.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ void QgsFieldCalculator::accept()
165165
QgsExpression exp( calcString );
166166
exp.setGeomCalculator( myDa );
167167
exp.setDistanceUnits( QgsProject::instance()->distanceUnits() );
168+
exp.setAreaUnits( QgsProject::instance()->areaUnits() );
168169

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

src/app/qgsmaptoolidentifyaction.cpp

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -184,18 +184,14 @@ void QgsMapToolIdentifyAction::deactivate()
184184
QgsMapTool::deactivate();
185185
}
186186

187-
QGis::UnitType QgsMapToolIdentifyAction::displayUnits()
187+
QGis::UnitType QgsMapToolIdentifyAction::displayDistanceUnits() const
188188
{
189-
// Get the units for display
190-
QSettings settings;
191-
bool ok = false;
192-
QGis::UnitType unit = QgsUnitTypes::decodeDistanceUnit( settings.value( "/qgis/measure/displayunits" ).toString(), &ok );
193-
return ok ? unit : QGis::Meters;
189+
return QgsProject::instance()->distanceUnits();
194190
}
195191

196-
QGis::UnitType QgsMapToolIdentifyAction::displayDistanceUnits()
192+
QgsUnitTypes::AreaUnit QgsMapToolIdentifyAction::displayAreaUnits() const
197193
{
198-
return QgsProject::instance()->distanceUnits();
194+
return QgsProject::instance()->areaUnits();
199195
}
200196

201197
void QgsMapToolIdentifyAction::handleCopyToClipboard( QgsFeatureStore & featureStore )

src/app/qgsmaptoolidentifyaction.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ class APP_EXPORT QgsMapToolIdentifyAction : public QgsMapToolIdentify
8080

8181
QgsIdentifyResultsDialog *resultsDialog();
8282

83-
virtual QGis::UnitType displayUnits() override;
84-
virtual QGis::UnitType displayDistanceUnits() override;
83+
virtual QGis::UnitType displayDistanceUnits() const override;
84+
virtual QgsUnitTypes::AreaUnit displayAreaUnits() const override;
8585

8686
};
8787

src/app/qgsoptions.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,22 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl ) :
482482
radMeters->setChecked( true );
483483
}
484484

485+
mAreaUnitsComboBox->addItem( tr( "Square meters" ), QgsUnitTypes::SquareMeters );
486+
mAreaUnitsComboBox->addItem( tr( "Square kilometers" ), QgsUnitTypes::SquareKilometers );
487+
mAreaUnitsComboBox->addItem( tr( "Square feet" ), QgsUnitTypes::SquareFeet );
488+
mAreaUnitsComboBox->addItem( tr( "Square yards" ), QgsUnitTypes::SquareYards );
489+
mAreaUnitsComboBox->addItem( tr( "Square miles" ), QgsUnitTypes::SquareMiles );
490+
mAreaUnitsComboBox->addItem( tr( "Hectares" ), QgsUnitTypes::Hectares );
491+
mAreaUnitsComboBox->addItem( tr( "Acres" ), QgsUnitTypes::Acres );
492+
mAreaUnitsComboBox->addItem( tr( "Square nautical miles" ), QgsUnitTypes::SquareNauticalMiles );
493+
mAreaUnitsComboBox->addItem( tr( "Square degrees" ), QgsUnitTypes::SquareDegrees );
494+
mAreaUnitsComboBox->addItem( tr( "Map units" ), QgsUnitTypes::UnknownAreaUnit );
495+
496+
QgsUnitTypes::AreaUnit areaUnits = QgsUnitTypes::decodeAreaUnit( mSettings->value( "/qgis/measure/areaunits" ).toString(), &ok );
497+
if ( !ok )
498+
areaUnits = QgsUnitTypes::SquareMeters;
499+
mAreaUnitsComboBox->setCurrentIndex( mAreaUnitsComboBox->findData( areaUnits ) );
500+
485501
QButtonGroup* angleButtonGroup = new QButtonGroup( this );
486502
angleButtonGroup->addButton( mDegreesRadioButton );
487503
angleButtonGroup->addButton( mRadiansRadioButton );
@@ -1263,6 +1279,9 @@ void QgsOptions::saveOptions()
12631279
mSettings->setValue( "/qgis/measure/displayunits", QgsUnitTypes::encodeUnit( QGis::Meters ) );
12641280
}
12651281

1282+
QgsUnitTypes::AreaUnit areaUnit = static_cast< QgsUnitTypes::AreaUnit >( mAreaUnitsComboBox->itemData( mAreaUnitsComboBox->currentIndex() ).toInt() );
1283+
mSettings->setValue( "/qgis/measure/areaunits", QgsUnitTypes::encodeUnit( areaUnit ) );
1284+
12661285
QString angleUnitString = "degrees";
12671286
if ( mRadiansRadioButton->isChecked() )
12681287
{

src/app/qgsprojectproperties.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,17 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas* mapCanvas, QWidget *pa
9191
mDistanceUnitsCombo->addItem( tr( "Degrees" ), QGis::Degrees );
9292
mDistanceUnitsCombo->addItem( tr( "Map units" ), QGis::UnknownUnit );
9393

94+
mAreaUnitsCombo->addItem( tr( "Square meters" ), QgsUnitTypes::SquareMeters );
95+
mAreaUnitsCombo->addItem( tr( "Square kilometers" ), QgsUnitTypes::SquareKilometers );
96+
mAreaUnitsCombo->addItem( tr( "Square feet" ), QgsUnitTypes::SquareFeet );
97+
mAreaUnitsCombo->addItem( tr( "Square yards" ), QgsUnitTypes::SquareYards );
98+
mAreaUnitsCombo->addItem( tr( "Square miles" ), QgsUnitTypes::SquareMiles );
99+
mAreaUnitsCombo->addItem( tr( "Hectares" ), QgsUnitTypes::Hectares );
100+
mAreaUnitsCombo->addItem( tr( "Acres" ), QgsUnitTypes::Acres );
101+
mAreaUnitsCombo->addItem( tr( "Square nautical miles" ), QgsUnitTypes::SquareNauticalMiles );
102+
mAreaUnitsCombo->addItem( tr( "Square degrees" ), QgsUnitTypes::SquareDegrees );
103+
mAreaUnitsCombo->addItem( tr( "Map units" ), QgsUnitTypes::UnknownAreaUnit );
104+
94105
connect( buttonBox->button( QDialogButtonBox::Apply ), SIGNAL( clicked() ), this, SLOT( apply() ) );
95106
connect( this, SIGNAL( accepted() ), this, SLOT( apply() ) );
96107
connect( projectionSelector, SIGNAL( sridSelected( QString ) ), this, SLOT( srIdUpdated() ) );
@@ -165,6 +176,7 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas* mapCanvas, QWidget *pa
165176
mCoordinateDisplayComboBox->setCurrentIndex( mCoordinateDisplayComboBox->findData( DecimalDegrees ) );
166177

167178
mDistanceUnitsCombo->setCurrentIndex( mDistanceUnitsCombo->findData( QgsProject::instance()->distanceUnits() ) );
179+
mAreaUnitsCombo->setCurrentIndex( mAreaUnitsCombo->findData( QgsProject::instance()->areaUnits() ) );
168180

169181
//get the color selections and set the button color accordingly
170182
int myRedInt = QgsProject::instance()->readNumEntry( "Gui", "/SelectionColorRedPart", 255 );
@@ -791,6 +803,9 @@ void QgsProjectProperties::apply()
791803
QGis::UnitType distanceUnits = static_cast< QGis::UnitType >( mDistanceUnitsCombo->itemData( mDistanceUnitsCombo->currentIndex() ).toInt() );
792804
QgsProject::instance()->writeEntry( "Measurement", "/DistanceUnits", QgsUnitTypes::encodeUnit( distanceUnits ) );
793805

806+
QgsUnitTypes::AreaUnit areaUnits = static_cast< QgsUnitTypes::AreaUnit >( mAreaUnitsCombo->itemData( mAreaUnitsCombo->currentIndex() ).toInt() );
807+
QgsProject::instance()->writeEntry( "Measurement", "/AreaUnits", QgsUnitTypes::encodeUnit( areaUnits ) );
808+
794809
QgsProject::instance()->writeEntry( "Paths", "/Absolute", cbxAbsolutePath->currentIndex() == 0 );
795810

796811
if ( mEllipsoidList.at( mEllipsoidIndex ).acronym.startsWith( "PARAMETER" ) )
@@ -1247,6 +1262,20 @@ void QgsProjectProperties::updateGuiForMapUnits( QGis::UnitType units )
12471262
mCoordinateDisplayComboBox->setItemText( idx, mapUnitString );
12481263
}
12491264
}
1265+
1266+
//also update unit combo boxes
1267+
idx = mDistanceUnitsCombo->findData( QGis::UnknownUnit );
1268+
if ( idx >= 0 )
1269+
{
1270+
QString mapUnitString = tr( "Map units (%1)" ).arg( QgsUnitTypes::toString( units ) );
1271+
mDistanceUnitsCombo->setItemText( idx, mapUnitString );
1272+
}
1273+
idx = mAreaUnitsCombo->findData( QgsUnitTypes::UnknownAreaUnit );
1274+
if ( idx >= 0 )
1275+
{
1276+
QString mapUnitString = tr( "Map units (%1)" ).arg( QgsUnitTypes::toString( QgsUnitTypes::distanceToAreaUnit( units ) ) );
1277+
mAreaUnitsCombo->setItemText( idx, mapUnitString );
1278+
}
12501279
}
12511280

12521281
void QgsProjectProperties::srIdUpdated()

0 commit comments

Comments
 (0)