Skip to content
Permalink
Browse files
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)
  • Loading branch information
nyalldawson committed Feb 15, 2016
1 parent 35c2d18 commit dfdcec89223d4167850c7ebbb0af1d65c21135f8
@@ -105,10 +105,12 @@ class QgsDistanceArea

/** Measures the area of a geometry.
* @param geometry geometry to measure
* @returns area of geometry. For geometry collections, non surface geometries will be ignored
* @returns area of geometry. For geometry collections, non surface geometries will be ignored. The units for the
* returned area can be retrieved by calling areaUnits().
* @note added in QGIS 2.12
* @see measureLength()
* @see measurePerimeter()
* @see areaUnits()
*/
double measureArea( const QgsGeometry* geometry ) const;

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

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

/** Returns the units of area for areal calculations made by this object.
* @note added in QGIS 2.14
* @see lengthUnits()
*/
QgsUnitTypes::AreaUnit areaUnits() const;

//! measures polygon area
double measurePolygon( const QList<QgsPoint>& points ) const;

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

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

/** Returns an area formatted as a friendly string.
* @param area area to format
* @param decimals number of decimal places to show
* @param unit unit of area
* @param keepBaseUnit set to false to allow conversion of large areas to more suitable units, eg square meters to
* square kilometers
* @returns formatted area string
* @note added in QGIS 2.14
* @see textUnit()
*/
static QString formatArea( double area, int decimals, QgsUnitTypes::AreaUnit unit, bool keepBaseUnit = false );

//! Helper for conversion between physical units
// TODO QGIS 3.0 - remove this method, as its behaviour is non-intuitive.
void convertMeasurement( double &measure /In,Out/, QGis::UnitType &measureUnits /In,Out/, QGis::UnitType displayUnits, bool isArea ) const;

/** Takes a length measurement calculated by this QgsDistanceArea object and converts it to a
* different distance unit.
* @param length length value calculated by this class to convert. It is assumed that the length
* was calculated by this class, ie that its unit of length is equal lengthUnits().
* was calculated by this class, ie that its unit of length is equal to lengthUnits().
* @param toUnits distance unit to convert measurement to
* @returns converted distance
* @see convertAreaMeasurement()
* @note added in QGIS 2.14
*/
double convertLengthMeasurement( double length, QGis::UnitType toUnits ) const;

/** Takes an area measurement calculated by this QgsDistanceArea object and converts it to a
* different areal unit.
* @param area area value calculated by this class to convert. It is assumed that the area
* was calculated by this class, ie that its unit of area is equal to areaUnits().
* @param toUnits area unit to convert measurement to
* @returns converted area
* @see convertLengthMeasurement()
* @note added in QGIS 2.14
*/
double convertAreaMeasurement( double area, QgsUnitTypes::AreaUnit toUnits ) const;

protected:
//! measures polygon area and perimeter, vertices are extracted from WKB
// @note not available in python bindings
@@ -250,9 +250,16 @@ class QgsProject : QObject

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

/** Convenience function to query default area measurement units for project.
* @note added in QGIS 2.14
* @see distanceUnits()
*/
QgsUnitTypes::AreaUnit areaUnits() const;

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

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

/** Transforms the measurements of derived attributes in the desired units*/
virtual QGis::UnitType displayUnits();
/** Transforms the measurements of derived attributes in the desired units
* @deprecated use displayDistanceUnits() and displayAreaUnits() instead
*/
virtual QGis::UnitType displayUnits() /Deprecated/;

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

/** Desired units for area display.
* @note added in QGIS 2.14
* @see displayDistanceUnits()
*/
virtual QgsUnitTypes::AreaUnit displayAreaUnits() const;
};
@@ -367,6 +367,7 @@ void QgsAttributeTableDialog::runFieldCalculation( QgsVectorLayer* layer, const
QgsExpression exp( expression );
exp.setGeomCalculator( *myDa );
exp.setDistanceUnits( QgsProject::instance()->distanceUnits() );
exp.setAreaUnits( QgsProject::instance()->areaUnits() );
bool useGeometry = exp.needsGeometry();

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

filterExpression.setGeomCalculator( myDa );
filterExpression.setDistanceUnits( QgsProject::instance()->distanceUnits() );
filterExpression.setAreaUnits( QgsProject::instance()->areaUnits() );
QgsFeatureRequest request( mMainView->masterModel()->request() );
request.setSubsetOfAttributes( filterExpression.referencedColumns(), mLayer->fields() );
if ( !fetchGeom )
@@ -165,6 +165,7 @@ void QgsFieldCalculator::accept()
QgsExpression exp( calcString );
exp.setGeomCalculator( myDa );
exp.setDistanceUnits( QgsProject::instance()->distanceUnits() );
exp.setAreaUnits( QgsProject::instance()->areaUnits() );

QgsExpressionContext expContext;
expContext << QgsExpressionContextUtils::globalScope()
@@ -184,18 +184,14 @@ void QgsMapToolIdentifyAction::deactivate()
QgsMapTool::deactivate();
}

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

QGis::UnitType QgsMapToolIdentifyAction::displayDistanceUnits()
QgsUnitTypes::AreaUnit QgsMapToolIdentifyAction::displayAreaUnits() const
{
return QgsProject::instance()->distanceUnits();
return QgsProject::instance()->areaUnits();
}

void QgsMapToolIdentifyAction::handleCopyToClipboard( QgsFeatureStore & featureStore )
@@ -80,8 +80,8 @@ class APP_EXPORT QgsMapToolIdentifyAction : public QgsMapToolIdentify

QgsIdentifyResultsDialog *resultsDialog();

virtual QGis::UnitType displayUnits() override;
virtual QGis::UnitType displayDistanceUnits() override;
virtual QGis::UnitType displayDistanceUnits() const override;
virtual QgsUnitTypes::AreaUnit displayAreaUnits() const override;

};

@@ -482,6 +482,22 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl ) :
radMeters->setChecked( true );
}

mAreaUnitsComboBox->addItem( tr( "Square meters" ), QgsUnitTypes::SquareMeters );
mAreaUnitsComboBox->addItem( tr( "Square kilometers" ), QgsUnitTypes::SquareKilometers );
mAreaUnitsComboBox->addItem( tr( "Square feet" ), QgsUnitTypes::SquareFeet );
mAreaUnitsComboBox->addItem( tr( "Square yards" ), QgsUnitTypes::SquareYards );
mAreaUnitsComboBox->addItem( tr( "Square miles" ), QgsUnitTypes::SquareMiles );
mAreaUnitsComboBox->addItem( tr( "Hectares" ), QgsUnitTypes::Hectares );
mAreaUnitsComboBox->addItem( tr( "Acres" ), QgsUnitTypes::Acres );
mAreaUnitsComboBox->addItem( tr( "Square nautical miles" ), QgsUnitTypes::SquareNauticalMiles );
mAreaUnitsComboBox->addItem( tr( "Square degrees" ), QgsUnitTypes::SquareDegrees );
mAreaUnitsComboBox->addItem( tr( "Map units" ), QgsUnitTypes::UnknownAreaUnit );

QgsUnitTypes::AreaUnit areaUnits = QgsUnitTypes::decodeAreaUnit( mSettings->value( "/qgis/measure/areaunits" ).toString(), &ok );
if ( !ok )
areaUnits = QgsUnitTypes::SquareMeters;
mAreaUnitsComboBox->setCurrentIndex( mAreaUnitsComboBox->findData( areaUnits ) );

QButtonGroup* angleButtonGroup = new QButtonGroup( this );
angleButtonGroup->addButton( mDegreesRadioButton );
angleButtonGroup->addButton( mRadiansRadioButton );
@@ -1263,6 +1279,9 @@ void QgsOptions::saveOptions()
mSettings->setValue( "/qgis/measure/displayunits", QgsUnitTypes::encodeUnit( QGis::Meters ) );
}

QgsUnitTypes::AreaUnit areaUnit = static_cast< QgsUnitTypes::AreaUnit >( mAreaUnitsComboBox->itemData( mAreaUnitsComboBox->currentIndex() ).toInt() );
mSettings->setValue( "/qgis/measure/areaunits", QgsUnitTypes::encodeUnit( areaUnit ) );

QString angleUnitString = "degrees";
if ( mRadiansRadioButton->isChecked() )
{
@@ -91,6 +91,17 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas* mapCanvas, QWidget *pa
mDistanceUnitsCombo->addItem( tr( "Degrees" ), QGis::Degrees );
mDistanceUnitsCombo->addItem( tr( "Map units" ), QGis::UnknownUnit );

mAreaUnitsCombo->addItem( tr( "Square meters" ), QgsUnitTypes::SquareMeters );
mAreaUnitsCombo->addItem( tr( "Square kilometers" ), QgsUnitTypes::SquareKilometers );
mAreaUnitsCombo->addItem( tr( "Square feet" ), QgsUnitTypes::SquareFeet );
mAreaUnitsCombo->addItem( tr( "Square yards" ), QgsUnitTypes::SquareYards );
mAreaUnitsCombo->addItem( tr( "Square miles" ), QgsUnitTypes::SquareMiles );
mAreaUnitsCombo->addItem( tr( "Hectares" ), QgsUnitTypes::Hectares );
mAreaUnitsCombo->addItem( tr( "Acres" ), QgsUnitTypes::Acres );
mAreaUnitsCombo->addItem( tr( "Square nautical miles" ), QgsUnitTypes::SquareNauticalMiles );
mAreaUnitsCombo->addItem( tr( "Square degrees" ), QgsUnitTypes::SquareDegrees );
mAreaUnitsCombo->addItem( tr( "Map units" ), QgsUnitTypes::UnknownAreaUnit );

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() ) );
@@ -165,6 +176,7 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas* mapCanvas, QWidget *pa
mCoordinateDisplayComboBox->setCurrentIndex( mCoordinateDisplayComboBox->findData( DecimalDegrees ) );

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

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

QgsUnitTypes::AreaUnit areaUnits = static_cast< QgsUnitTypes::AreaUnit >( mAreaUnitsCombo->itemData( mAreaUnitsCombo->currentIndex() ).toInt() );
QgsProject::instance()->writeEntry( "Measurement", "/AreaUnits", QgsUnitTypes::encodeUnit( areaUnits ) );

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

if ( mEllipsoidList.at( mEllipsoidIndex ).acronym.startsWith( "PARAMETER" ) )
@@ -1247,6 +1262,20 @@ void QgsProjectProperties::updateGuiForMapUnits( QGis::UnitType units )
mCoordinateDisplayComboBox->setItemText( idx, mapUnitString );
}
}

//also update unit combo boxes
idx = mDistanceUnitsCombo->findData( QGis::UnknownUnit );
if ( idx >= 0 )
{
QString mapUnitString = tr( "Map units (%1)" ).arg( QgsUnitTypes::toString( units ) );
mDistanceUnitsCombo->setItemText( idx, mapUnitString );
}
idx = mAreaUnitsCombo->findData( QgsUnitTypes::UnknownAreaUnit );
if ( idx >= 0 )
{
QString mapUnitString = tr( "Map units (%1)" ).arg( QgsUnitTypes::toString( QgsUnitTypes::distanceToAreaUnit( units ) ) );
mAreaUnitsCombo->setItemText( idx, mapUnitString );
}
}

void QgsProjectProperties::srIdUpdated()

0 comments on commit dfdcec8

Please sign in to comment.