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)
This commit is contained in:
Nyall Dawson 2016-02-15 07:25:32 +11:00
parent 35c2d185fb
commit dfdcec8922
26 changed files with 886 additions and 169 deletions

View File

@ -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,17 +153,47 @@ 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;
@ -169,12 +201,25 @@ class QgsDistanceArea
/** 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

View File

@ -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;

View File

@ -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;
};

View File

@ -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 )

View File

@ -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()

View File

@ -184,20 +184,16 @@ void QgsMapToolIdentifyAction::deactivate()
QgsMapTool::deactivate();
}
QGis::UnitType QgsMapToolIdentifyAction::displayUnits()
{
// 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;
}
QGis::UnitType QgsMapToolIdentifyAction::displayDistanceUnits()
QGis::UnitType QgsMapToolIdentifyAction::displayDistanceUnits() const
{
return QgsProject::instance()->distanceUnits();
}
QgsUnitTypes::AreaUnit QgsMapToolIdentifyAction::displayAreaUnits() const
{
return QgsProject::instance()->areaUnits();
}
void QgsMapToolIdentifyAction::handleCopyToClipboard( QgsFeatureStore & featureStore )
{
QgsDebugMsg( QString( "features count = %1" ).arg( featureStore.features().size() ) );

View File

@ -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;
};

View File

@ -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() )
{

View File

@ -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()

View File

@ -539,6 +539,12 @@ QGis::UnitType QgsDistanceArea::lengthUnits() const
return willUseEllipsoid() ? QGis::Meters : mCoordTransform->sourceCrs().mapUnits();
}
QgsUnitTypes::AreaUnit QgsDistanceArea::areaUnits() const
{
return willUseEllipsoid() ? QgsUnitTypes::SquareMeters :
QgsUnitTypes::distanceToAreaUnit( mCoordTransform->sourceCrs().mapUnits() );
}
QgsConstWkbPtr QgsDistanceArea::measurePolygon( QgsConstWkbPtr wkbPtr, double* area, double* perimeter, bool hasZptr ) const
{
if ( !wkbPtr )
@ -1091,6 +1097,141 @@ QString QgsDistanceArea::textUnit( double value, int decimals, QGis::UnitType u,
return QLocale::system().toString( value, 'f', decimals ) + unitLabel;
}
QString QgsDistanceArea::formatArea( double area, int decimals, QgsUnitTypes::AreaUnit unit, bool keepBaseUnit )
{
QString unitLabel;
switch ( unit )
{
case QgsUnitTypes::SquareMeters:
{
if ( keepBaseUnit )
{
unitLabel = QObject::trUtf8( "" );
}
else if ( qAbs( area ) > QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::SquareKilometers, QgsUnitTypes::SquareMeters ) )
{
unitLabel = QObject::trUtf8( " km²" );
area = area * QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::SquareMeters, QgsUnitTypes::SquareKilometers );
}
else if ( qAbs( area ) > QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::Hectares, QgsUnitTypes::SquareMeters ) )
{
unitLabel = QObject::tr( " ha" );
area = area * QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::SquareMeters, QgsUnitTypes::Hectares );
}
else
{
unitLabel = QObject::trUtf8( "" );
}
break;
}
case QgsUnitTypes::SquareKilometers:
{
unitLabel = QObject::trUtf8( " km²" );
break;
}
case QgsUnitTypes::SquareFeet:
{
if ( keepBaseUnit )
{
unitLabel = QObject::trUtf8( " ft²" );
}
else if ( qAbs( area ) > QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::SquareMiles, QgsUnitTypes::SquareFeet ) )
{
unitLabel = QObject::trUtf8( " mi²" );
area = area * QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::SquareFeet, QgsUnitTypes::SquareMiles );
}
else
{
unitLabel = QObject::trUtf8( " ft²" );
}
break;
}
case QgsUnitTypes::SquareYards:
{
if ( keepBaseUnit )
{
unitLabel = QObject::trUtf8( " yd²" );
}
else if ( qAbs( area ) > QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::SquareMiles, QgsUnitTypes::SquareYards ) )
{
unitLabel = QObject::trUtf8( " mi²" );
area = area * QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::SquareYards, QgsUnitTypes::SquareMiles );
}
else
{
unitLabel = QObject::trUtf8( " yd²" );
}
break;
}
case QgsUnitTypes::SquareMiles:
{
unitLabel = QObject::trUtf8( " mi²" );
break;
}
case QgsUnitTypes::Hectares:
{
if ( keepBaseUnit )
{
unitLabel = QObject::trUtf8( " ha" );
}
else if ( qAbs( area ) > QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::SquareKilometers, QgsUnitTypes::Hectares ) )
{
unitLabel = QObject::trUtf8( " km²" );
area = area * QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::Hectares, QgsUnitTypes::SquareKilometers );
}
else
{
unitLabel = QObject::trUtf8( " ha" );
}
break;
}
case QgsUnitTypes::Acres:
{
if ( keepBaseUnit )
{
unitLabel = QObject::trUtf8( " ac" );
}
else if ( qAbs( area ) > QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::SquareMiles, QgsUnitTypes::Acres ) )
{
unitLabel = QObject::trUtf8( " mi²" );
area = area * QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::Acres, QgsUnitTypes::SquareMiles );
}
else
{
unitLabel = QObject::trUtf8( " ac" );
}
break;
}
case QgsUnitTypes::SquareNauticalMiles:
{
unitLabel = QObject::trUtf8( " nm²" );
break;
}
case QgsUnitTypes::SquareDegrees:
{
unitLabel = QObject::tr( " sq.deg." );
break;
}
case QgsUnitTypes::UnknownAreaUnit:
{
unitLabel.clear();
break;
}
}
return QLocale::system().toString( area, 'f', decimals ) + unitLabel;
}
void QgsDistanceArea::convertMeasurement( double &measure, QGis::UnitType &measureUnits, QGis::UnitType displayUnits, bool isArea ) const
{
// Helper for converting between meters and feet and degrees and NauticalMiles...
@ -1136,3 +1277,16 @@ double QgsDistanceArea::convertLengthMeasurement( double length, QGis::UnitType
return result;
}
double QgsDistanceArea::convertAreaMeasurement( double area, QgsUnitTypes::AreaUnit toUnits ) const
{
// get the conversion factor between the specified units
QgsUnitTypes::AreaUnit measureUnits = areaUnits();
double factorUnits = QgsUnitTypes::fromUnitToUnitFactor( measureUnits, toUnits );
double result = area * factorUnits;
QgsDebugMsg( QString( "Converted area of %1 %2 to %3 %4" ).arg( area )
.arg( QgsUnitTypes::toString( measureUnits ) )
.arg( result )
.arg( QgsUnitTypes::toString( toUnits ) ) );
return result;
}

View File

@ -19,6 +19,7 @@
#include <QList>
#include "qgscoordinatetransform.h"
#include "qgswkbptr.h"
#include "qgsunittypes.h"
class QgsGeometry;
class QgsAbstractGeometryV2;
@ -139,10 +140,12 @@ class CORE_EXPORT 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;
@ -194,17 +197,47 @@ class CORE_EXPORT 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, QGis::UnitType &measureUnits, QGis::UnitType displayUnits, bool isArea ) const;
@ -212,12 +245,25 @@ class CORE_EXPORT QgsDistanceArea
/** 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

View File

@ -1662,7 +1662,9 @@ static QVariant fcnGeomArea( const QVariantList&, const QgsExpressionContext* co
QgsDistanceArea* calc = parent->geomCalculator();
if ( calc )
{
return QVariant( calc->measureArea( f.constGeometry() ) );
double area = calc->measureArea( f.constGeometry() );
area = calc->convertAreaMeasurement( area, parent->areaUnits() );
return QVariant( area );
}
else
{
@ -3427,6 +3429,16 @@ void QgsExpression::setDistanceUnits( QGis::UnitType unit )
d->mDistanceUnit = unit;
}
QgsUnitTypes::AreaUnit QgsExpression::areaUnits() const
{
return d->mAreaUnit;
}
void QgsExpression::setAreaUnits( QgsUnitTypes::AreaUnit unit )
{
d->mAreaUnit = unit;
}
void QgsExpression::acceptVisitor( QgsExpression::Visitor& v ) const
{
if ( d->mRootNode )

View File

@ -24,7 +24,7 @@
#include <QCoreApplication>
#include "qgis.h"
#include "qgsunittypes.h"
class QgsFeature;
class QgsGeometry;
@ -268,6 +268,7 @@ class CORE_EXPORT QgsExpression
* (used by $length, $area and $perimeter functions only)
* @see setGeomCalculator()
* @see distanceUnits()
* @see areaUnits()
*/
QgsDistanceArea *geomCalculator();
@ -284,6 +285,7 @@ class CORE_EXPORT QgsExpression
* @note distances are only converted when a geomCalculator() has been set
* @note added in QGIS 2.14
* @see setDistanceUnits()
* @see areaUnits()
*/
QGis::UnitType distanceUnits() const;
@ -291,9 +293,26 @@ class CORE_EXPORT QgsExpression
* @note distances are only converted when a geomCalculator() has been set
* @note added in QGIS 2.14
* @see distanceUnits()
* @see setAreaUnits()
*/
void setDistanceUnits( QGis::UnitType unit );
/** Returns the desired areal units for calculations involving geomCalculator(), eg "$area".
* @note areas are only converted when a geomCalculator() has been set
* @note added in QGIS 2.14
* @see setAreaUnits()
* @see distanceUnits()
*/
QgsUnitTypes::AreaUnit areaUnits() const;
/** Sets the desired areal units for calculations involving geomCalculator(), eg "$area".
* @note areas are only converted when a geomCalculator() has been set
* @note added in QGIS 2.14
* @see areaUnits()
* @see setDistanceUnits()
*/
void setAreaUnits( QgsUnitTypes::AreaUnit unit );
/** This function currently replaces each expression between [% and %]
* in the string with the result of its evaluation on the feature
* passed as argument.

View File

@ -22,6 +22,7 @@
#include "qgsexpression.h"
#include "qgsdistancearea.h"
#include "qgsunittypes.h"
///@cond
/**
@ -39,6 +40,7 @@ class QgsExpressionPrivate
, mScale( 0 )
, mCalc( nullptr )
, mDistanceUnit( QGis::UnknownUnit )
, mAreaUnit( QgsUnitTypes::UnknownAreaUnit )
{}
QgsExpressionPrivate( const QgsExpressionPrivate& other )
@ -50,6 +52,7 @@ class QgsExpressionPrivate
, mExp( other.mExp )
, mCalc( other.mCalc )
, mDistanceUnit( other.mDistanceUnit )
, mAreaUnit( other.mAreaUnit )
{}
~QgsExpressionPrivate()
@ -70,6 +73,7 @@ class QgsExpressionPrivate
QSharedPointer<QgsDistanceArea> mCalc;
QGis::UnitType mDistanceUnit;
QgsUnitTypes::AreaUnit mAreaUnit;
};
///@endcond

View File

@ -454,9 +454,10 @@ void QgsProject::clear()
writeEntry( "PositionPrecision", "/DecimalPlaces", 2 );
writeEntry( "Paths", "/Absolute", false );
//copy default distance units to project
//copy default units to project
QSettings s;
writeEntry( "Measurement", "/DistanceUnits", s.value( "/qgis/measure/displayunits" ).toString() );
writeEntry( "Measurement", "/AreaUnits", s.value( "/qgis/measure/areaunits" ).toString() );
setDirty( false );
}
@ -2077,6 +2078,19 @@ QGis::UnitType QgsProject::distanceUnits() const
return ok ? type : QGis::Meters;
}
QgsUnitTypes::AreaUnit QgsProject::areaUnits() const
{
QString areaUnitString = QgsProject::instance()->readEntry( "Measurement", "/AreaUnits", QString() );
if ( !areaUnitString.isEmpty() )
return QgsUnitTypes::decodeAreaUnit( areaUnitString );
//fallback to QGIS default area unit
QSettings s;
bool ok = false;
QgsUnitTypes::AreaUnit type = QgsUnitTypes::decodeAreaUnit( s.value( "/qgis/measure/areaunits" ).toString(), &ok );
return ok ? type : QgsUnitTypes::SquareMeters;
}
void QgsProjectBadLayerDefaultHandler::handleBadLayers( const QList<QDomNode>& /*layers*/, const QDomDocument& /*projectDom*/ )
{
// just ignore any bad layers

View File

@ -32,6 +32,7 @@
//for the snap settings
#include "qgssnapper.h"
#include "qgstolerance.h"
#include "qgsunittypes.h"
//#include <QDomDocument>
@ -295,9 +296,16 @@ class CORE_EXPORT QgsProject : public 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;

View File

@ -398,10 +398,8 @@ QMap< QString, QString > QgsMapToolIdentify::featureDerivedAttributes( QgsFeatur
else if ( geometryType == QGis::Polygon )
{
double area = calc.measureArea( feature->constGeometry() );
QGis::UnitType myDisplayUnits;
convertMeasurement( calc, area, myDisplayUnits, true ); // area and myDisplayUnits are out params
QString str = calc.textUnit( area, 3, myDisplayUnits, true );
area = calc.convertAreaMeasurement( area, displayAreaUnits() );
QString str = formatArea( area );
derivedAttributes.insert( tr( "Area" ), str );
double perimeter = calc.measurePerimeter( feature->constGeometry() );
@ -647,8 +645,10 @@ void QgsMapToolIdentify::convertMeasurement( QgsDistanceArea &calc, double &meas
// Get the canvas units
QGis::UnitType myUnits = mCanvas->mapUnits();
Q_NOWARN_DEPRECATED_PUSH
calc.convertMeasurement( measure, myUnits, displayUnits(), isArea );
u = displayUnits();
Q_NOWARN_DEPRECATED_POP
}
QGis::UnitType QgsMapToolIdentify::displayUnits()
@ -656,12 +656,17 @@ QGis::UnitType QgsMapToolIdentify::displayUnits()
return mCanvas->mapUnits();
}
QGis::UnitType QgsMapToolIdentify::displayDistanceUnits()
QGis::UnitType QgsMapToolIdentify::displayDistanceUnits() const
{
return mCanvas->mapUnits();
}
QString QgsMapToolIdentify::formatDistance( double distance )
QgsUnitTypes::AreaUnit QgsMapToolIdentify::displayAreaUnits() const
{
return QgsUnitTypes::distanceToAreaUnit( mCanvas->mapUnits() );
}
QString QgsMapToolIdentify::formatDistance( double distance ) const
{
QSettings settings;
bool baseUnit = settings.value( "/qgis/measure/keepbaseunit", false ).toBool();
@ -669,6 +674,14 @@ QString QgsMapToolIdentify::formatDistance( double distance )
return QgsDistanceArea::textUnit( distance, 3, displayDistanceUnits(), false, baseUnit );
}
QString QgsMapToolIdentify::formatArea( double area ) const
{
QSettings settings;
bool baseUnit = settings.value( "/qgis/measure/keepbaseunit", false ).toBool();
return QgsDistanceArea::formatArea( area, 3, displayAreaUnits(), baseUnit );
}
void QgsMapToolIdentify::formatChanged( QgsRasterLayer *layer )
{
QgsDebugMsg( "Entered" );

View File

@ -158,20 +158,37 @@ class GUI_EXPORT QgsMapToolIdentify : public QgsMapTool
private:
//! Private helper
virtual void convertMeasurement( QgsDistanceArea &calc, double &measure, QGis::UnitType &u, bool isArea );
//! @deprecated use displayDistanceUnits() and displayAreaUnits() instead
Q_DECL_DEPRECATED 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();
/** Transforms the measurements of derived attributes in the desired units
* @deprecated use displayDistanceUnits() and displayAreaUnits() instead
*/
Q_DECL_DEPRECATED virtual QGis::UnitType displayUnits();
/** 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;
/** Format a distance into a suitable string for display to the user
* @note added in QGIS 2.14
* @see formatArea()
*/
QString formatDistance( double distance );
QString formatDistance( double distance ) const;
/** Format a distance into a suitable string for display to the user
* @note added in QGIS 2.14
* @see formatDistance()
*/
QString formatArea( double area ) const;
QMap< QString, QString > featureDerivedAttributes( QgsFeature *feature, QgsMapLayer *layer, const QgsPoint& layerPoint = QgsPoint() );

View File

@ -308,7 +308,7 @@
<item>
<widget class="QStackedWidget" name="mOptionsStackedWidget">
<property name="currentIndex">
<number>0</number>
<number>6</number>
</property>
<widget class="QWidget" name="mOptionsPageGeneral">
<layout class="QVBoxLayout" name="verticalLayout_3">
@ -1825,7 +1825,7 @@
<property name="geometry">
<rect>
<x>0</x>
<y>-224</y>
<y>0</y>
<width>949</width>
<height>802</height>
</rect>
@ -2988,9 +2988,9 @@
<property name="geometry">
<rect>
<x>0</x>
<y>-24</y>
<y>0</y>
<width>949</width>
<height>602</height>
<height>635</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_30">
@ -3141,34 +3141,6 @@
<string>Measure tool</string>
</property>
<layout class="QGridLayout" name="gridLayout_21">
<item row="2" column="0">
<widget class="QLabel" name="textLabel1_10">
<property name="text">
<string>Rubberband color</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="textLabel1_11">
<property name="text">
<string>Preferred measurements units</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QRadioButton" name="radMeters">
<property name="text">
<string>&amp;Meters</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QRadioButton" name="radFeet">
<property name="text">
<string>Feet</string>
</property>
</widget>
</item>
<item row="5" column="3">
<widget class="QRadioButton" name="radNautical">
<property name="text">
@ -3176,6 +3148,13 @@
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QRadioButton" name="mDegreesRadioButton">
<property name="text">
<string>Degrees</string>
</property>
</widget>
</item>
<item row="5" column="4">
<widget class="QRadioButton" name="radDegrees">
<property name="text">
@ -3183,74 +3162,23 @@
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="mAngleUnitsLabel">
<property name="text">
<string>Preferred angle units</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QRadioButton" name="mDegreesRadioButton">
<property name="text">
<string>Degrees</string>
</property>
</widget>
</item>
<item row="6" column="2">
<item row="7" column="2">
<widget class="QRadioButton" name="mRadiansRadioButton">
<property name="text">
<string>Radians</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_12">
<item row="7" column="0">
<widget class="QLabel" name="mAngleUnitsLabel">
<property name="text">
<string>Decimal places</string>
<string>Preferred angle units</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="mDecimalPlacesSpinBox"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_13">
<property name="toolTip">
<string>If unchecked large numbers will be converted from m. to km. and from ft. to miles</string>
</property>
<property name="text">
<string>Keep base unit</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="mKeepBaseUnitCheckBox">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="6" column="3">
<widget class="QRadioButton" name="mGonRadioButton">
<property name="text">
<string>Gon</string>
</property>
</widget>
</item>
<item row="2" column="2" colspan="3">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>191</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="1">
<widget class="QgsColorButtonV2" name="pbnMeasureColor">
<property name="sizePolicy">
@ -3276,6 +3204,88 @@
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QRadioButton" name="radMeters">
<property name="text">
<string>&amp;Meters</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QRadioButton" name="radFeet">
<property name="text">
<string>Feet</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_13">
<property name="toolTip">
<string>If unchecked large numbers will be converted from m. to km. and from ft. to miles</string>
</property>
<property name="text">
<string>Keep base unit</string>
</property>
</widget>
</item>
<item row="7" column="3">
<widget class="QRadioButton" name="mGonRadioButton">
<property name="text">
<string>Gon</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="textLabel1_10">
<property name="text">
<string>Rubberband color</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="textLabel1_11">
<property name="text">
<string>Preferred measurements units</string>
</property>
</widget>
</item>
<item row="2" column="2" colspan="3">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>191</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Decimal places</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="mKeepBaseUnitCheckBox">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="textLabel1_14">
<property name="text">
<string>Preferred area units</string>
</property>
</widget>
</item>
<item row="6" column="1" colspan="4">
<widget class="QComboBox" name="mAreaUnitsComboBox"/>
</item>
</layout>
</widget>
</item>
@ -3484,8 +3494,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>965</width>
<height>578</height>
<width>514</width>
<height>307</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_39">
@ -3688,8 +3698,8 @@
<property name="geometry">
<rect>
<x>0</x>
<y>-90</y>
<width>949</width>
<y>0</y>
<width>570</width>
<height>668</height>
</rect>
</property>
@ -4234,8 +4244,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>965</width>
<height>578</height>
<width>474</width>
<height>372</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
@ -4372,8 +4382,8 @@
<property name="geometry">
<rect>
<x>0</x>
<y>-69</y>
<width>949</width>
<y>0</y>
<width>574</width>
<height>647</height>
</rect>
</property>
@ -4619,8 +4629,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>965</width>
<height>578</height>
<width>305</width>
<height>226</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_32">
@ -4727,8 +4737,8 @@
<property name="geometry">
<rect>
<x>0</x>
<y>-162</y>
<width>949</width>
<y>0</y>
<width>542</width>
<height>740</height>
</rect>
</property>
@ -5207,12 +5217,6 @@
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>QgsColorButtonV2</class>
<extends>QToolButton</extends>
<header>qgscolorbuttonv2.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsCollapsibleGroupBox</class>
<extends>QGroupBox</extends>
@ -5220,9 +5224,9 @@
<container>1</container>
</customwidget>
<customwidget>
<class>QgsProjectionSelectionWidget</class>
<extends>QWidget</extends>
<header location="global">qgsprojectionselectionwidget.h</header>
<class>QgsColorButtonV2</class>
<extends>QToolButton</extends>
<header>qgscolorbuttonv2.h</header>
<container>1</container>
</customwidget>
<customwidget>
@ -5231,17 +5235,23 @@
<header location="global">qgscolorschemelist.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsScaleComboBox</class>
<extends>QComboBox</extends>
<header>qgsscalecombobox.h</header>
</customwidget>
<customwidget>
<class>QgsVariableEditorWidget</class>
<extends>QWidget</extends>
<header location="global">qgsvariableeditorwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsProjectionSelectionWidget</class>
<extends>QWidget</extends>
<header location="global">qgsprojectionselectionwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsScaleComboBox</class>
<extends>QComboBox</extends>
<header>qgsscalecombobox.h</header>
</customwidget>
<customwidget>
<class>QgsSettingsTree</class>
<extends>QWidget</extends>
@ -5370,6 +5380,7 @@
<tabstop>radFeet</tabstop>
<tabstop>radNautical</tabstop>
<tabstop>radDegrees</tabstop>
<tabstop>mAreaUnitsComboBox</tabstop>
<tabstop>mDegreesRadioButton</tabstop>
<tabstop>mRadiansRadioButton</tabstop>
<tabstop>mGonRadioButton</tabstop>

View File

@ -485,25 +485,9 @@
</property>
</widget>
</item>
<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">
@ -511,6 +495,22 @@
</property>
</widget>
</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="0" column="1" colspan="4">
<widget class="QComboBox" name="cmbEllipsoid"/>
</item>
<item row="1" column="4">
<widget class="QLineEdit" name="leSemiMinor"/>
</item>
<item row="1" column="2">
<widget class="QLineEdit" name="leSemiMajor"/>
</item>
<item row="1" column="3">
<widget class="QLabel" name="label_42">
<property name="text">
@ -518,6 +518,16 @@
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_29">
<property name="text">
<string>Units for area measurement</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="4">
<widget class="QComboBox" name="mAreaUnitsCombo"/>
</item>
</layout>
</widget>
</item>
@ -880,7 +890,7 @@
<x>0</x>
<y>0</y>
<width>379</width>
<height>562</height>
<height>582</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_12">
@ -2539,6 +2549,7 @@
<tabstop>leSemiMajor</tabstop>
<tabstop>leSemiMinor</tabstop>
<tabstop>mDistanceUnitsCombo</tabstop>
<tabstop>mAreaUnitsCombo</tabstop>
<tabstop>mCoordinateDisplayComboBox</tabstop>
<tabstop>radAutomatic</tabstop>
<tabstop>radManual</tabstop>

View File

@ -39,6 +39,7 @@ class TestQgsAttributeTable : public QObject
void init() {} // will be called before each testfunction is executed.
void cleanup() {} // will be called after every testfunction.
void testFieldCalculation();
void testFieldCalculationArea();
private:
QgisApp * mQgisApp;
@ -115,5 +116,57 @@ void TestQgsAttributeTable::testFieldCalculation()
QVERIFY( qgsDoubleNear( f.attribute( "col1" ).toDouble(), expected, 0.001 ) );
}
void TestQgsAttributeTable::testFieldCalculationArea()
{
//test $area field calculation
//create a temporary layer
QScopedPointer< QgsVectorLayer> tempLayer( new QgsVectorLayer( "Polygon?crs=epsg:3111&field=pk:int&field=col1:double", "vl", "memory" ) );
QVERIFY( tempLayer->isValid() );
QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
f1.setAttribute( "pk", 1 );
f1.setAttribute( "col1", 0.0 );
QgsPolyline polygonRing3111;
polygonRing3111 << QgsPoint( 2484588, 2425722 ) << QgsPoint( 2482767, 2398853 ) << QgsPoint( 2520109, 2397715 ) << QgsPoint( 2520792, 2425494 ) << QgsPoint( 2484588, 2425722 );
QgsPolygon polygon3111;
polygon3111 << polygonRing3111;
f1.setGeometry( QgsGeometry::fromPolygon( polygon3111 ) );
tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 );
// set project CRS and ellipsoid
QgisApp::instance()->mapCanvas()->setCrsTransformEnabled( true );
QgsCoordinateReferenceSystem srs( 3111, QgsCoordinateReferenceSystem::EpsgCrsId );
QgsProject::instance()->writeEntry( "SpatialRefSys", "/ProjectCRSProj4String", srs.toProj4() );
QgsProject::instance()->writeEntry( "SpatialRefSys", "/ProjectCRSID", ( int ) srs.srsid() );
QgsProject::instance()->writeEntry( "SpatialRefSys", "/ProjectCrs", srs.authid() );
QgsProject::instance()->writeEntry( "Measure", "/Ellipsoid", QString( "WGS84" ) );
QgsProject::instance()->writeEntry( "Measurement", "/AreaUnits", QgsUnitTypes::encodeUnit( QgsUnitTypes::SquareMeters ) );
// run area calculation
QScopedPointer< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.data() ) );
tempLayer->startEditing();
dlg->runFieldCalculation( tempLayer.data(), "col1", "$area" );
tempLayer->commitChanges();
// check result
QgsFeatureIterator fit = tempLayer->dataProvider()->getFeatures();
QgsFeature f;
QVERIFY( fit.nextFeature( f ) );
double expected = 1009089817.0;
QVERIFY( qgsDoubleNear( f.attribute( "col1" ).toDouble(), expected, 1.0 ) );
// change project area unit, check calculation respects unit
QgsProject::instance()->writeEntry( "Measurement", "/AreaUnits", QgsUnitTypes::encodeUnit( QgsUnitTypes::SquareMiles ) );
QScopedPointer< QgsAttributeTableDialog > dlg2( new QgsAttributeTableDialog( tempLayer.data() ) );
tempLayer->startEditing();
dlg2->runFieldCalculation( tempLayer.data(), "col1", "$area" );
tempLayer->commitChanges();
// check result
fit = tempLayer->dataProvider()->getFeatures();
QVERIFY( fit.nextFeature( f ) );
expected = 389.6117565069;
QVERIFY( qgsDoubleNear( f.attribute( "col1" ).toDouble(), expected, 0.001 ) );
}
QTEST_MAIN( TestQgsAttributeTable )
#include "testqgsattributetable.moc"

View File

@ -39,6 +39,7 @@ class TestQgsFieldCalculator : public QObject
void init() {} // will be called before each testfunction is executed.
void cleanup() {} // will be called after every testfunction.
void testLengthCalculations();
void testAreaCalculations();
private:
QgisApp * mQgisApp;
@ -126,5 +127,67 @@ void TestQgsFieldCalculator::testLengthCalculations()
}
void TestQgsFieldCalculator::testAreaCalculations()
{
//test area calculation respects ellipsoid and project area units
//create a temporary layer
QScopedPointer< QgsVectorLayer> tempLayer( new QgsVectorLayer( "Polygon?crs=epsg:3111&field=pk:int&field=col1:double", "vl", "memory" ) );
QVERIFY( tempLayer->isValid() );
QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
f1.setAttribute( "pk", 1 );
f1.setAttribute( "col1", 0.0 );
QgsPolyline polygonRing3111;
polygonRing3111 << QgsPoint( 2484588, 2425722 ) << QgsPoint( 2482767, 2398853 ) << QgsPoint( 2520109, 2397715 ) << QgsPoint( 2520792, 2425494 ) << QgsPoint( 2484588, 2425722 );
QgsPolygon polygon3111;
polygon3111 << polygonRing3111;
f1.setGeometry( QgsGeometry::fromPolygon( polygon3111 ) );
tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 );
// set project CRS and ellipsoid
QgisApp::instance()->mapCanvas()->setCrsTransformEnabled( true );
QgsCoordinateReferenceSystem srs( 3111, QgsCoordinateReferenceSystem::EpsgCrsId );
QgsProject::instance()->writeEntry( "SpatialRefSys", "/ProjectCRSProj4String", srs.toProj4() );
QgsProject::instance()->writeEntry( "SpatialRefSys", "/ProjectCRSID", ( int ) srs.srsid() );
QgsProject::instance()->writeEntry( "SpatialRefSys", "/ProjectCrs", srs.authid() );
QgsProject::instance()->writeEntry( "Measure", "/Ellipsoid", QString( "WGS84" ) );
QgsProject::instance()->writeEntry( "Measurement", "/AreaUnits", QgsUnitTypes::encodeUnit( QgsUnitTypes::SquareMeters ) );
// run area calculation
tempLayer->startEditing();
QScopedPointer< QgsFieldCalculator > calc( new QgsFieldCalculator( tempLayer.data() ) );
// this next part is fragile, and may need to be modified if the dialog changes:
calc->mUpdateExistingGroupBox->setChecked( true );
calc->mExistingFieldComboBox->setCurrentIndex( 1 );
calc->builder->setExpressionText( "$area" );
calc->accept();
tempLayer->commitChanges();
// check result
QgsFeatureIterator fit = tempLayer->dataProvider()->getFeatures();
QgsFeature f;
QVERIFY( fit.nextFeature( f ) );
double expected = 1009089817.0;
QVERIFY( qgsDoubleNear( f.attribute( "col1" ).toDouble(), expected, 1.0 ) );
// change project area unit, check calculation respects unit
QgsProject::instance()->writeEntry( "Measurement", "/AreaUnits", QgsUnitTypes::encodeUnit( QgsUnitTypes::SquareMiles ) );
tempLayer->startEditing();
QScopedPointer< QgsFieldCalculator > calc2( new QgsFieldCalculator( tempLayer.data() ) );
calc2->mUpdateExistingGroupBox->setChecked( true );
calc2->mExistingFieldComboBox->setCurrentIndex( 1 );
calc2->builder->setExpressionText( "$area" );
calc2->accept();
tempLayer->commitChanges();
// check result
fit = tempLayer->dataProvider()->getFeatures();
QVERIFY( fit.nextFeature( f ) );
expected = 389.6117565069;
QVERIFY( qgsDoubleNear( f.attribute( "col1" ).toDouble(), expected, 0.001 ) );
}
QTEST_MAIN( TestQgsFieldCalculator )
#include "testqgsfieldcalculator.moc"

View File

@ -39,6 +39,7 @@ class TestQgsMapToolIdentifyAction : public QObject
void cleanup(); // will be called after every testfunction.
void lengthCalculation(); //test calculation of derived length attributes
void perimeterCalculation(); //test calculation of derived perimeter attribute
void areaCalculation(); //test calculation of derived area attribute
private:
QgsMapCanvas* canvas;
@ -179,6 +180,63 @@ void TestQgsMapToolIdentifyAction::perimeterCalculation()
QVERIFY( qgsDoubleNear( perimeter, 79.715, 0.001 ) );
}
void TestQgsMapToolIdentifyAction::areaCalculation()
{
QSettings s;
s.setValue( "/qgis/measure/keepbaseunit", true );
//create a temporary layer
QScopedPointer< QgsVectorLayer> tempLayer( new QgsVectorLayer( "Polygon?crs=epsg:3111&field=pk:int&field=col1:double", "vl", "memory" ) );
QVERIFY( tempLayer->isValid() );
QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
f1.setAttribute( "pk", 1 );
f1.setAttribute( "col1", 0.0 );
QgsPolyline polygonRing3111;
polygonRing3111 << QgsPoint( 2484588, 2425722 ) << QgsPoint( 2482767, 2398853 ) << QgsPoint( 2520109, 2397715 ) << QgsPoint( 2520792, 2425494 ) << QgsPoint( 2484588, 2425722 );
QgsPolygon polygon3111;
polygon3111 << polygonRing3111;
f1.setGeometry( QgsGeometry::fromPolygon( polygon3111 ) );
tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 );
// set project CRS and ellipsoid
QgsCoordinateReferenceSystem srs( 3111, QgsCoordinateReferenceSystem::EpsgCrsId );
canvas->setCrsTransformEnabled( true );
canvas->setDestinationCrs( srs );
canvas->setExtent( f1.geometry()->boundingBox() );
QgsProject::instance()->writeEntry( "SpatialRefSys", "/ProjectCRSProj4String", srs.toProj4() );
QgsProject::instance()->writeEntry( "SpatialRefSys", "/ProjectCRSID", ( int ) srs.srsid() );
QgsProject::instance()->writeEntry( "SpatialRefSys", "/ProjectCrs", srs.authid() );
QgsProject::instance()->writeEntry( "Measure", "/Ellipsoid", QString( "WGS84" ) );
QgsProject::instance()->writeEntry( "Measurement", "/AreaUnits", QgsUnitTypes::encodeUnit( QgsUnitTypes::SquareMeters ) );
QgsPoint mapPoint = canvas->getCoordinateTransform()->transform( 2484588, 2425722 );
QScopedPointer< QgsMapToolIdentifyAction > action( new QgsMapToolIdentifyAction( canvas ) );
QList<QgsMapToolIdentify::IdentifyResult> result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer*>() << tempLayer.data() );
QCOMPARE( result.length(), 1 );
QString derivedArea = result.at( 0 ).mDerivedAttributes[tr( "Area" )];
double area = derivedArea.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
QVERIFY( qgsDoubleNear( area, 1009089817.0, 1.0 ) );
//check that project units are respected
QgsProject::instance()->writeEntry( "Measurement", "/AreaUnits", QgsUnitTypes::encodeUnit( QgsUnitTypes::SquareMiles ) );
result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer*>() << tempLayer.data() );
QCOMPARE( result.length(), 1 );
derivedArea = result.at( 0 ).mDerivedAttributes[tr( "Area" )];
area = derivedArea.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
QVERIFY( qgsDoubleNear( area, 389.6117, 0.001 ) );
//test unchecked "keep base units" setting
s.setValue( "/qgis/measure/keepbaseunit", false );
QgsProject::instance()->writeEntry( "Measurement", "/AreaUnits", QgsUnitTypes::encodeUnit( QgsUnitTypes::SquareFeet ) );
result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer*>() << tempLayer.data() );
QCOMPARE( result.length(), 1 );
derivedArea = result.at( 0 ).mDerivedAttributes[tr( "Area" )];
area = derivedArea.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
QVERIFY( qgsDoubleNear( area, 389.6117, 0.001 ) );
}
QTEST_MAIN( TestQgsMapToolIdentifyAction )
#include "testqgsmaptoolidentifyaction.moc"

View File

@ -1325,17 +1325,38 @@ class TestQgsExpression: public QObject
// test area without geomCalculator
QgsExpression expArea( "$area" );
QVariant vArea = expArea.evaluate( &context );
QCOMPARE( vArea.toDouble(), 1005640568.0 );
double expected = 1005640568.0;
QVERIFY( qgsDoubleNear( vArea.toDouble(), expected, 1.0 ) );
// units should not be converted if no geometry calculator set
expArea.setAreaUnits( QgsUnitTypes::SquareFeet );
vArea = expArea.evaluate( &context );
QVERIFY( qgsDoubleNear( vArea.toDouble(), expected, 1.0 ) );
expArea.setAreaUnits( QgsUnitTypes::SquareNauticalMiles );
vArea = expArea.evaluate( &context );
QVERIFY( qgsDoubleNear( vArea.toDouble(), expected, 1.0 ) );
// test area with geomCalculator
expArea.setGeomCalculator( da );
vArea = expArea.evaluate( &context );
QVERIFY( qgsDoubleNear( vArea.toDouble(), 1009089817.0, 1.0 ) );
QgsExpression expArea2( "$area" );
expArea2.setGeomCalculator( da );
vArea = expArea2.evaluate( &context );
expected = 1009089817.0;
QVERIFY( qgsDoubleNear( vArea.toDouble(), expected, 1.0 ) );
// test unit conversion
expArea2.setAreaUnits( QgsUnitTypes::SquareMeters ); //default units should be square meters
vArea = expArea2.evaluate( &context );
QVERIFY( qgsDoubleNear( vArea.toDouble(), expected, 1.0 ) );
expArea2.setAreaUnits( QgsUnitTypes::UnknownAreaUnit ); //unknown units should not be converted
vArea = expArea2.evaluate( &context );
QVERIFY( qgsDoubleNear( vArea.toDouble(), expected, 1.0 ) );
expArea2.setAreaUnits( QgsUnitTypes::SquareMiles );
expected = 389.6117565069;
vArea = expArea2.evaluate( &context );
QVERIFY( qgsDoubleNear( vArea.toDouble(), expected, 0.001 ) );
// test perimeter without geomCalculator
QgsExpression expPerimeter( "$perimeter" );
QVariant vPerimeter = expPerimeter.evaluate( &context );
double expected = 128282.086;
expected = 128282.086;
QVERIFY( qgsDoubleNear( vPerimeter.toDouble(), expected, 0.001 ) );
// units should not be converted if no geometry calculator set
expPerimeter.setDistanceUnits( QGis::Feet );

View File

@ -87,6 +87,8 @@ void TestQgsProject::testProjectUnits()
{
//test setting and retrieving project units
// DISTANCE
//first set a default QGIS distance unit
QSettings s;
s.setValue( "/qgis/measure/displayunits", QgsUnitTypes::encodeUnit( QGis::Feet ) );
@ -103,6 +105,23 @@ void TestQgsProject::testProjectUnits()
//test setting new units for project
prj->writeEntry( "Measurement", "/DistanceUnits", QgsUnitTypes::encodeUnit( QGis::NauticalMiles ) );
QCOMPARE( prj->distanceUnits(), QGis::NauticalMiles );
// AREA
//first set a default QGIS area unit
s.setValue( "/qgis/measure/areaunits", QgsUnitTypes::encodeUnit( QgsUnitTypes::SquareYards ) );
// new project should inherit QGIS default area unit
prj->clear();
QCOMPARE( prj->areaUnits(), QgsUnitTypes::SquareYards );
//changing default QGIS unit should not affect existing project
s.setValue( "/qgis/measure/areaunits", QgsUnitTypes::encodeUnit( QgsUnitTypes::Acres ) );
QCOMPARE( prj->areaUnits(), QgsUnitTypes::SquareYards );
//test setting new units for project
prj->writeEntry( "Measurement", "/AreaUnits", QgsUnitTypes::encodeUnit( QgsUnitTypes::Acres ) );
QCOMPARE( prj->areaUnits(), QgsUnitTypes::Acres );
}

View File

@ -191,6 +191,10 @@ class TestQgsDistanceArea(unittest.TestCase):
self.assertAlmostEqual(distance, 247555.57, delta=0.01)
self.assertEqual(units, QGis.Meters)
# test converting the resultant length
distance = da.convertLengthMeasurement(distance, QGis.NauticalMiles)
self.assertAlmostEqual(distance, 133.669, delta=0.01)
# now try with a source CRS which is in feet
da.setSourceCrs(27469)
da.setEllipsoidalMode(False)
@ -201,6 +205,10 @@ class TestQgsDistanceArea(unittest.TestCase):
self.assertAlmostEqual(distance, 2.23606797, delta=0.000001)
self.assertEqual(units, QGis.Feet)
# test converting the resultant length
distance = da.convertLengthMeasurement(distance, QGis.Meters)
self.assertAlmostEqual(distance, 0.6815, delta=0.001)
da.setEllipsoidalMode(True)
# now should be in Meters again
distance = da.measureLine(QgsPoint(1, 1), QgsPoint(2, 3))
@ -209,6 +217,83 @@ class TestQgsDistanceArea(unittest.TestCase):
self.assertAlmostEqual(distance, 0.67953772, delta=0.000001)
self.assertEqual(units, QGis.Meters)
# test converting the resultant length
distance = da.convertLengthMeasurement(distance, QGis.Feet)
self.assertAlmostEqual(distance, 2.2294, delta=0.001)
def testAreaMeasureAndUnits(self):
"""Test a variety of area measurements in different CRS and ellipsoid modes, to check that the
calculated areas and units are always consistent
"""
da = QgsDistanceArea()
da.setSourceCrs(3452)
da.setEllipsoidalMode(False)
da.setEllipsoid("NONE")
daCRS = QgsCoordinateReferenceSystem()
daCRS.createFromSrsId(da.sourceCrs())
polygon = QgsGeometry.fromPolygon(
[[
QgsPoint(0, 0), QgsPoint(1, 0), QgsPoint(1, 1), QgsPoint(2, 1), QgsPoint(2, 2), QgsPoint(0, 2), QgsPoint(0, 0),
]]
)
# We check both the measured area AND the units, in case the logic regarding
# ellipsoids and units changes in future
area = da.measureArea(polygon)
units = da.areaUnits()
print "measured {} in {}".format(area, QgsUnitTypes.toString(units))
assert ((abs(area - 3.0) < 0.00000001 and units == QgsUnitTypes.SquareDegrees) or
(abs(area - 37176087091.5) < 0.1 and units == QgsUnitTypes.SquareMeters))
da.setEllipsoid("WGS84")
area = da.measureArea(polygon)
units = da.areaUnits()
print "measured {} in {}".format(area, QgsUnitTypes.toString(units))
assert ((abs(area - 3.0) < 0.00000001 and units == QgsUnitTypes.SquareDegrees) or
(abs(area - 37176087091.5) < 0.1 and units == QgsUnitTypes.SquareMeters))
da.setEllipsoidalMode(True)
area = da.measureArea(polygon)
units = da.areaUnits()
print "measured {} in {}".format(area, QgsUnitTypes.toString(units))
# should always be in Meters Squared
self.assertAlmostEqual(area, 37416879192.9, delta=0.1)
self.assertEqual(units, QgsUnitTypes.SquareMeters)
# test converting the resultant area
area = da.convertAreaMeasurement(area, QgsUnitTypes.SquareMiles)
self.assertAlmostEqual(area, 14446.7378, delta=0.001)
# now try with a source CRS which is in feet
da.setSourceCrs(27469)
da.setEllipsoidalMode(False)
# measurement should be in square feet
area = da.measureArea(polygon)
units = da.areaUnits()
print "measured {} in {}".format(area, QgsUnitTypes.toString(units))
self.assertAlmostEqual(area, 3.0, delta=0.000001)
self.assertEqual(units, QgsUnitTypes.SquareFeet)
# test converting the resultant area
area = da.convertAreaMeasurement(area, QgsUnitTypes.SquareYards)
self.assertAlmostEqual(area, 0.333333, delta=0.001)
da.setEllipsoidalMode(True)
# now should be in Square Meters again
area = da.measureArea(polygon)
units = da.areaUnits()
print "measured {} in {}".format(area, QgsUnitTypes.toString(units))
self.assertAlmostEqual(area, 0.256102704082, delta=0.000001)
self.assertEqual(units, QgsUnitTypes.SquareMeters)
# test converting the resultant area
area = da.convertAreaMeasurement(area, QgsUnitTypes.SquareYards)
self.assertAlmostEqual(area, 0.30629, delta=0.0001)
if __name__ == '__main__':
unittest.main()