Skip to content
Permalink
Browse files

Improve QgsDistanceArea API to make it easier to determine units

for returned distances, add unit tests
  • Loading branch information
nyalldawson committed Feb 5, 2016
1 parent cb7d0fb commit 99cef42eef712f6281ebc00b68b41e302e52581b
@@ -50,7 +50,15 @@ class QgsDistanceArea
void setSourceAuthId( const QString& authid );

//! returns source spatial reference system
long sourceCrs() const;
//! @deprecated use sourceCrsId() instead
long sourceCrs() const /Deprecated/;

/** Returns the QgsCoordinateReferenceSystem::srsid() for the CRS used during calculations.
* @see setSourceCrs()
* @note added in QGIS 2.14
*/
long sourceCrsId() const;

//! What sort of coordinate system is being used?
bool geographic() const;

@@ -105,22 +113,46 @@ class QgsDistanceArea

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

//! measures perimeter of polygon
double measurePerimeter( const QgsGeometry* geometry ) const;
/** Measures the perimeter of a polygon geometry.
* @param geometry geometry to measure
* @returns perimeter of geometry. For geometry collections, any non-polygon geometries will be ignored. The units for the
* returned perimeter can be retrieved by calling lengthUnits().
* @note added in QGIS 2.12
* @see lengthUnits()
* @see measureArea()
* @see measurePerimeter()
*/
double measurePerimeter( const QgsGeometry *geometry ) const;

//! measures line
/** Measures the length of a line with multiple segments.
* @param points list of points in line
* @returns length of line. The units for the returned length can be retrieved by calling lengthUnits().
* @see lengthUnits()
*/
double measureLine( const QList<QgsPoint>& points ) const;

//! measures line with one segment
/** Measures length of a line with one segment.
* @param p1 start of line
* @param p2 end of line
* @returns distance between points. The units for the returned distance can be retrieved by calling lengthUnits().
* @see lengthUnits()
*/
double measureLine( const QgsPoint& p1, const QgsPoint& p2 ) const;

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

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

@@ -132,12 +164,21 @@ class QgsDistanceArea
//! Helper for conversion between physical units
void convertMeasurement( double &measure /In,Out/, QGis::UnitType &measureUnits /In,Out/, QGis::UnitType displayUnits, bool isArea ) const;

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

protected:
//! measures line distance, line points are extracted from WKB
// @note available in python bindings
// const unsigned char* measureLine( const unsigned char* feature, double* area, bool hasZptr = false ) const;
//! measures polygon area and perimeter, vertices are extracted from WKB
// @note available in python bindings
// @note not available in python bindings
// const unsigned char* measurePolygon( const unsigned char* feature, double* area, double* perimeter, bool hasZptr = false ) const;

/**
@@ -141,9 +141,7 @@ void QgsMeasureDialog::mouseMove( QgsPoint &point )

editTotal->setText( formatDistance( mTotal + d ) );

QGis::UnitType displayUnits;
// Meters or feet?
convertMeasurement( d, displayUnits, false );
d = convertLength( d, mDisplayUnits );

// Set moving
QTreeWidgetItem *item = mTable->topLevelItem( mTable->topLevelItemCount() - 1 );
@@ -211,9 +209,7 @@ void QgsMeasureDialog::removeLastPoint()
mTotal = mDa.measureLine( mTool->points() );
editTotal->setText( formatDistance( mTotal + d ) );

QGis::UnitType displayUnits;
// Meters or feet?
convertMeasurement( d, displayUnits, false );
d = convertLength( d, mDisplayUnits );

QTreeWidgetItem *item = mTable->topLevelItem( mTable->topLevelItemCount() - 1 );
item->setText( 0, QLocale::system().toString( d, 'f', mDecimalPlaces ) );
@@ -252,9 +248,8 @@ QString QgsMeasureDialog::formatDistance( double distance )
QSettings settings;
bool baseUnit = settings.value( "/qgis/measure/keepbaseunit", false ).toBool();

QGis::UnitType newDisplayUnits;
convertMeasurement( distance, newDisplayUnits, false );
return QgsDistanceArea::textUnit( distance, mDecimalPlaces, newDisplayUnits, false, baseUnit );
distance = convertLength( distance, mDisplayUnits );
return QgsDistanceArea::textUnit( distance, mDecimalPlaces, mDisplayUnits, false, baseUnit );
}

QString QgsMeasureDialog::formatArea( double area )
@@ -329,9 +324,8 @@ void QgsMeasureDialog::updateUi()
p2 = *it;
if ( !b )
{
double d = mDa.measureLine( p1, p2 );
QGis::UnitType dummyUnits;
convertMeasurement( d, dummyUnits, false );
double d = mDa.measureLine( p1, p2 );
d = convertLength( d, mDisplayUnits );

QTreeWidgetItem *item = new QTreeWidgetItem( QStringList( QLocale::system().toString( d, 'f', mDecimalPlaces ) ) );
item->setTextAlignment( 0, Qt::AlignRight );
@@ -361,6 +355,11 @@ void QgsMeasureDialog::convertMeasurement( double &measure, QGis::UnitType &u, b
u = myUnits;
}

double QgsMeasureDialog::convertLength( double length, QGis::UnitType toUnit )
{
return mDa.convertLengthMeasurement( length, toUnit );
}


void QgsMeasureDialog::reject()
{
@@ -85,6 +85,8 @@ class APP_EXPORT QgsMeasureDialog : public QDialog, private Ui::QgsMeasureBase
//! Converts the measurement, depending on settings in options and current transformation
void convertMeasurement( double &measure, QGis::UnitType &u, bool isArea );

double convertLength( double length, QGis::UnitType toUnit );

double mTotal;

//! indicates whether we're measuring distances or areas
@@ -534,6 +534,10 @@ double QgsDistanceArea::measureLine( const QgsPoint& p1, const QgsPoint& p2, QGi
return result;
}

QGis::UnitType QgsDistanceArea::lengthUnits() const
{
return willUseEllipsoid() ? QGis::Meters : mCoordTransform->sourceCrs().mapUnits();
}

const unsigned char *QgsDistanceArea::measurePolygon( const unsigned char* feature, double* area, double* perimeter, bool hasZptr ) const
{
@@ -1118,3 +1122,17 @@ void QgsDistanceArea::convertMeasurement( double &measure, QGis::UnitType &measu
measureUnits = displayUnits;
}

double QgsDistanceArea::convertLengthMeasurement( double length, QGis::UnitType toUnits ) const
{
// get the conversion factor between the specified units
QGis::UnitType measureUnits = lengthUnits();
double factorUnits = QgsUnitTypes::fromUnitToUnitFactor( measureUnits, toUnits );

double result = length * factorUnits;
QgsDebugMsg( QString( "Converted length of %1 %2 to %3 %4" ).arg( length )
.arg( QgsUnitTypes::toString( measureUnits ) )
.arg( result )
.arg( QgsUnitTypes::toString( toUnits ) ) );
return result;
}

@@ -83,7 +83,16 @@ class CORE_EXPORT QgsDistanceArea
void setSourceAuthId( const QString& authid );

//! returns source spatial reference system
long sourceCrs() const { return mCoordTransform->sourceCrs().srsid(); }
//! @deprecated use sourceCrsId() instead
// TODO QGIS 3.0 - make sourceCrs() return QgsCoordinateReferenceSystem
Q_DECL_DEPRECATED long sourceCrs() const { return mCoordTransform->sourceCrs().srsid(); }

/** Returns the QgsCoordinateReferenceSystem::srsid() for the CRS used during calculations.
* @see setSourceCrs()
* @note added in QGIS 2.14
*/
long sourceCrsId() const { return mCoordTransform->sourceCrs().srsid(); }

//! What sort of coordinate system is being used?
bool geographic() const { return mCoordTransform->sourceCrs().geographicFlag(); }

@@ -138,23 +147,38 @@ class CORE_EXPORT QgsDistanceArea

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

//! measures perimeter of polygon
/** Measures the perimeter of a polygon geometry.
* @param geometry geometry to measure
* @returns perimeter of geometry. For geometry collections, any non-polygon geometries will be ignored. The units for the
* returned perimeter can be retrieved by calling lengthUnits().
* @note added in QGIS 2.12
* @see lengthUnits()
* @see measureArea()
* @see measurePerimeter()
*/
double measurePerimeter( const QgsGeometry *geometry ) const;

//! measures line
/** Measures the length of a line with multiple segments.
* @param points list of points in line
* @returns length of line. The units for the returned length can be retrieved by calling lengthUnits().
* @see lengthUnits()
*/
double measureLine( const QList<QgsPoint>& points ) const;

/** Measures length of line with one segment
/** Measures length of a line with one segment.
* @param p1 start of line
* @param p2 end of line
* @returns distance in meters, or map units if cartesian calculation was performed
* @returns distance between points. The units for the returned distance can be retrieved by calling lengthUnits().
* @see lengthUnits()
*/
double measureLine( const QgsPoint& p1, const QgsPoint& p2 ) const;

@@ -167,6 +191,11 @@ class CORE_EXPORT QgsDistanceArea
*/
double measureLine( const QgsPoint& p1, const QgsPoint& p2, QGis::UnitType& units ) const;

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

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

@@ -176,11 +205,21 @@ class CORE_EXPORT QgsDistanceArea
static QString textUnit( double value, int decimals, QGis::UnitType u, bool isArea, 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().
* @param toUnits distance unit to convert measurement to
* @returns converted distance
*/
double convertLengthMeasurement( double length, QGis::UnitType toUnits ) const;

protected:
//! measures polygon area and perimeter, vertices are extracted from WKB
// @note available in python bindings
// @note not available in python bindings
const unsigned char* measurePolygon( const unsigned char* feature, double* area, double* perimeter, bool hasZptr = false ) const;

/**
@@ -625,7 +625,7 @@ void QgsMapToolIdentify::convertMeasurement( QgsDistanceArea &calc, double &meas
QGis::UnitType myUnits = mCanvas->mapUnits();

calc.convertMeasurement( measure, myUnits, displayUnits(), isArea );
u = myUnits;
u = displayUnits();
}

QGis::UnitType QgsMapToolIdentify::displayUnits()
@@ -162,7 +162,9 @@ void TestQgsDistanceArea::unit_conversions()
//outputUnit = QGis::Meters;

// First, convert from sq.meter to sq.feet
Q_NOWARN_DEPRECATED_PUSH
myDa.convertMeasurement( inputValue, inputUnit, outputUnit, true );
Q_NOWARN_DEPRECATED_POP
QVERIFY( qAbs( inputValue - 107639.1041671 ) <= 0.0000001 );

// The print a text unit. This is i18n, so we should ignore the unit
@@ -16,7 +16,10 @@

from qgis.core import (QgsGeometry,
QgsPoint,
QgsDistanceArea
QgsDistanceArea,
QgsCoordinateReferenceSystem,
QGis,
QgsUnitTypes
)

from qgis.testing import (start_app,
@@ -30,6 +33,19 @@

class TestQgsDistanceArea(unittest.TestCase):

def testCrs(self):
# test setting/getting the source CRS
da = QgsDistanceArea()

# try setting using a crs id
da.setSourceCrs(3452)
self.assertEqual(da.sourceCrsId(), 3452)

# try setting using a CRS object
crs = QgsCoordinateReferenceSystem(3111, QgsCoordinateReferenceSystem.EpsgCrsId)
da.setSourceCrs(crs)
self.assertEqual(da.sourceCrsId(), crs.srsid())

def testMeasureLine(self):
# +-+
# | |
@@ -137,6 +153,62 @@ def testWillUseEllipsoid(self):
da.setEllipsoidalMode(False)
self.assertFalse(da.willUseEllipsoid())

def testLengthMeasureAndUnits(self):
"""Test a variety of length measurements in different CRS and ellipsoid modes, to check that the
calculated lengths and units are always consistent
"""

da = QgsDistanceArea()
da.setSourceCrs(3452)
da.setEllipsoidalMode(False)
da.setEllipsoid("NONE")
daCRS = QgsCoordinateReferenceSystem()
daCRS.createFromSrsId(da.sourceCrs())

# We check both the measured length AND the units, in case the logic regarding
# ellipsoids and units changes in future
distance = da.measureLine(QgsPoint(1, 1), QgsPoint(2, 3))
units = da.lengthUnits()

print "measured {} in {}".format(distance, QgsUnitTypes.toString(units))
assert ((abs(distance - 2.23606797) < 0.00000001 and units == QGis.Degrees) or
(abs(distance - 248.52) < 0.01 and units == QGis.Meters))

da.setEllipsoid("WGS84")
distance = da.measureLine(QgsPoint(1, 1), QgsPoint(2, 3))
units = da.lengthUnits()

print "measured {} in {}".format(distance, QgsUnitTypes.toString(units))
assert ((abs(distance - 2.23606797) < 0.00000001 and units == QGis.Degrees) or
(abs(distance - 248.52) < 0.01 and units == QGis.Meters))

da.setEllipsoidalMode(True)
distance = da.measureLine(QgsPoint(1, 1), QgsPoint(2, 3))
units = da.lengthUnits()

print "measured {} in {}".format(distance, QgsUnitTypes.toString(units))
# should always be in Meters
self.assertAlmostEqual(distance, 247555.57, delta=0.01)
self.assertEqual(units, QGis.Meters)

# now try with a source CRS which is in feet
da.setSourceCrs(27469)
da.setEllipsoidalMode(False)
# measurement should be in feet
distance = da.measureLine(QgsPoint(1, 1), QgsPoint(2, 3))
units = da.lengthUnits()
print "measured {} in {}".format(distance, QgsUnitTypes.toString(units))
self.assertAlmostEqual(distance, 2.23606797, delta=0.000001)
self.assertEqual(units, QGis.Feet)

da.setEllipsoidalMode(True)
# now should be in Meters again
distance = da.measureLine(QgsPoint(1, 1), QgsPoint(2, 3))
units = da.lengthUnits()
print "measured {} in {}".format(distance, QgsUnitTypes.toString(units))
self.assertAlmostEqual(distance, 0.67953772, delta=0.000001)
self.assertEqual(units, QGis.Meters)


if __name__ == '__main__':
unittest.main()

0 comments on commit 99cef42

Please sign in to comment.
You can’t perform that action at this time.