154 changes: 154 additions & 0 deletions src/core/qgsdistancearea.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down Expand Up @@ -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...
Expand Down Expand Up @@ -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;
}
50 changes: 48 additions & 2 deletions src/core/qgsdistancearea.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <QList>
#include "qgscoordinatetransform.h"
#include "qgswkbptr.h"
#include "qgsunittypes.h"

class QgsGeometry;
class QgsAbstractGeometryV2;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -194,30 +197,73 @@ 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;

/** 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
Expand Down
14 changes: 13 additions & 1 deletion src/core/qgsexpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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 )
Expand Down
21 changes: 20 additions & 1 deletion src/core/qgsexpression.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
#include <QCoreApplication>

#include "qgis.h"

#include "qgsunittypes.h"

class QgsFeature;
class QgsGeometry;
Expand Down Expand Up @@ -268,6 +268,7 @@ class CORE_EXPORT QgsExpression
* (used by $length, $area and $perimeter functions only)
* @see setGeomCalculator()
* @see distanceUnits()
* @see areaUnits()
*/
QgsDistanceArea *geomCalculator();

Expand All @@ -284,16 +285,34 @@ 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;

/** Sets the desired distance units for calculations involving geomCalculator(), eg "$length" and "$perimeter".
* @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.
Expand Down
4 changes: 4 additions & 0 deletions src/core/qgsexpressionprivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include "qgsexpression.h"
#include "qgsdistancearea.h"
#include "qgsunittypes.h"

///@cond
/**
Expand All @@ -39,6 +40,7 @@ class QgsExpressionPrivate
, mScale( 0 )
, mCalc( nullptr )
, mDistanceUnit( QGis::UnknownUnit )
, mAreaUnit( QgsUnitTypes::UnknownAreaUnit )
{}

QgsExpressionPrivate( const QgsExpressionPrivate& other )
Expand All @@ -50,6 +52,7 @@ class QgsExpressionPrivate
, mExp( other.mExp )
, mCalc( other.mCalc )
, mDistanceUnit( other.mDistanceUnit )
, mAreaUnit( other.mAreaUnit )
{}

~QgsExpressionPrivate()
Expand All @@ -70,6 +73,7 @@ class QgsExpressionPrivate

QSharedPointer<QgsDistanceArea> mCalc;
QGis::UnitType mDistanceUnit;
QgsUnitTypes::AreaUnit mAreaUnit;
};
///@endcond

Expand Down
16 changes: 15 additions & 1 deletion src/core/qgsproject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions src/core/qgsproject.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
//for the snap settings
#include "qgssnapper.h"
#include "qgstolerance.h"
#include "qgsunittypes.h"

//#include <QDomDocument>

Expand Down Expand Up @@ -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;
Expand Down
25 changes: 19 additions & 6 deletions src/gui/qgsmaptoolidentify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() );
Expand Down Expand Up @@ -647,28 +645,43 @@ 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()
{
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();

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" );
Expand Down
27 changes: 22 additions & 5 deletions src/gui/qgsmaptoolidentify.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) const;

/** Format a distance into a suitable string for display to the user
* @note added in QGIS 2.14
* @see formatDistance()
*/
QString formatDistance( double distance );
QString formatArea( double area ) const;

QMap< QString, QString > featureDerivedAttributes( QgsFeature *feature, QgsMapLayer *layer, const QgsPoint& layerPoint = QgsPoint() );

Expand Down
197 changes: 104 additions & 93 deletions src/ui/qgsoptionsbase.ui
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down Expand Up @@ -1825,7 +1825,7 @@
<property name="geometry">
<rect>
<x>0</x>
<y>-224</y>
<y>0</y>
<width>949</width>
<height>802</height>
</rect>
Expand Down Expand Up @@ -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">
Expand Down Expand Up @@ -3141,79 +3141,83 @@
<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">
<item row="5" column="3">
<widget class="QRadioButton" name="radNautical">
<property name="text">
<string>&amp;Meters</string>
<string>Nautical Miles</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QRadioButton" name="radFeet">
<item row="7" column="1">
<widget class="QRadioButton" name="mDegreesRadioButton">
<property name="text">
<string>Feet</string>
<string>Degrees</string>
</property>
</widget>
</item>
<item row="5" column="3">
<widget class="QRadioButton" name="radNautical">
<item row="5" column="4">
<widget class="QRadioButton" name="radDegrees">
<property name="text">
<string>Nautical Miles</string>
<string>Degrees</string>
</property>
</widget>
</item>
<item row="5" column="4">
<widget class="QRadioButton" name="radDegrees">
<item row="7" column="2">
<widget class="QRadioButton" name="mRadiansRadioButton">
<property name="text">
<string>Degrees</string>
<string>Radians</string>
</property>
</widget>
</item>
<item row="6" column="0">
<item row="7" 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">
<item row="3" column="1">
<widget class="QSpinBox" name="mDecimalPlacesSpinBox"/>
</item>
<item row="2" column="1">
<widget class="QgsColorButtonV2" name="pbnMeasureColor">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Degrees</string>
<string/>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QRadioButton" name="mRadiansRadioButton">
<item row="5" column="1">
<widget class="QRadioButton" name="radMeters">
<property name="text">
<string>Radians</string>
<string>&amp;Meters</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_12">
<item row="5" column="2">
<widget class="QRadioButton" name="radFeet">
<property name="text">
<string>Decimal places</string>
<string>Feet</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">
Expand All @@ -3224,17 +3228,24 @@
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="mKeepBaseUnitCheckBox">
<item row="7" column="3">
<widget class="QRadioButton" name="mGonRadioButton">
<property name="text">
<string/>
<string>Gon</string>
</property>
</widget>
</item>
<item row="6" column="3">
<widget class="QRadioButton" name="mGonRadioButton">
<item row="2" column="0">
<widget class="QLabel" name="textLabel1_10">
<property name="text">
<string>Gon</string>
<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>
Expand All @@ -3251,31 +3262,30 @@
</property>
</spacer>
</item>
<item row="2" column="1">
<widget class="QgsColorButtonV2" name="pbnMeasureColor">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
<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>
Expand Down Expand Up @@ -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">
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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">
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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">
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -5207,41 +5217,41 @@
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>QgsCollapsibleGroupBox</class>
<extends>QGroupBox</extends>
<header>qgscollapsiblegroupbox.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsColorButtonV2</class>
<extends>QToolButton</extends>
<header>qgscolorbuttonv2.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsCollapsibleGroupBox</class>
<extends>QGroupBox</extends>
<header>qgscollapsiblegroupbox.h</header>
<class>QgsColorSchemeList</class>
<extends>QWidget</extends>
<header location="global">qgscolorschemelist.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsProjectionSelectionWidget</class>
<class>QgsVariableEditorWidget</class>
<extends>QWidget</extends>
<header location="global">qgsprojectionselectionwidget.h</header>
<header location="global">qgsvariableeditorwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsColorSchemeList</class>
<class>QgsProjectionSelectionWidget</class>
<extends>QWidget</extends>
<header location="global">qgscolorschemelist.h</header>
<header location="global">qgsprojectionselectionwidget.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>QgsSettingsTree</class>
<extends>QWidget</extends>
Expand Down Expand Up @@ -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>
Expand Down
41 changes: 26 additions & 15 deletions src/ui/qgsprojectpropertiesbase.ui
Original file line number Diff line number Diff line change
Expand Up @@ -485,17 +485,15 @@
</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 row="1" column="1">
<widget class="QLabel" name="label_41">
<property name="text">
<string>Semi-major</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_28">
Expand All @@ -504,12 +502,14 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_41">
<property name="text">
<string>Semi-major</string>
</property>
</widget>
<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">
Expand All @@ -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>
Expand Down Expand Up @@ -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">
Expand Down Expand Up @@ -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>
Expand Down
53 changes: 53 additions & 0 deletions tests/src/app/testqgsattributetable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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"
63 changes: 63 additions & 0 deletions tests/src/app/testqgsfieldcalculator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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"
58 changes: 58 additions & 0 deletions tests/src/app/testqgsmaptoolidentifyaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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"
Expand Down
31 changes: 26 additions & 5 deletions tests/src/core/testqgsexpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down
19 changes: 19 additions & 0 deletions tests/src/core/testqgsproject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) );
Expand All @@ -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 );
}


Expand Down
85 changes: 85 additions & 0 deletions tests/src/python/test_qgsdistancearea.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ def testLengthMeasureAndUnits(self):
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)
Expand All @@ -201,6 +205,10 @@ def testLengthMeasureAndUnits(self):
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))
Expand All @@ -209,6 +217,83 @@ def testLengthMeasureAndUnits(self):
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()