Fix project unit confusion (pt 2): add project distance unit setting

Adds a new option in project properties to set the units used for
distance measurements. This setting defaults to the units set in
QGIS options, but can then be overridden for specific projects.

The setting is respected for length and perimeter calculations in:
- Attribute table field update bar
- Field calculator calculations
- Identify tool derived length and perimeter values
- Default unit shown in measure dialog

Also adds unit tests to ensure that length and perimeter calculated
by attribute table update bar, field calculator and identify tool
are consistent wrt ellipsoidal calculations and distance units.

(refs #13209, #12939, #2402, #4857, #4252)
This commit is contained in:
Nyall Dawson 2016-02-14 21:04:41 +11:00
parent 17a29f90d6
commit ddbdcf8ab1
20 changed files with 611 additions and 41 deletions

View File

@ -248,6 +248,11 @@ class QgsProject : QObject
/** Convenience function to query topological editing status */
bool topologicalEditing() const;
/** Convenience function to query default distance measurement units for project.
* @note added in QGIS 2.14
*/
QGis::UnitType distanceUnits() const;
/** Return project's home path
@return home path of project (or QString::null if not set) */
QString homePath() const;

View File

@ -107,4 +107,17 @@ class QgsMapToolIdentify : QgsMapTool
bool identifyRasterLayer( QList<QgsMapToolIdentify::IdentifyResult> *results, QgsRasterLayer *layer, QgsPoint point, const QgsRectangle& viewExtent, double mapUnitsPerPixel );
bool identifyVectorLayer( QList<QgsMapToolIdentify::IdentifyResult> *results, QgsVectorLayer *layer, const QgsPoint& point );
private:
//! Private helper
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();
/** Desired units for distance display.
* @note added in QGIS 2.14
*/
virtual QGis::UnitType displayDistanceUnits();
};

View File

@ -366,6 +366,7 @@ void QgsAttributeTableDialog::runFieldCalculation( QgsVectorLayer* layer, const
QgsExpression exp( expression );
exp.setGeomCalculator( *myDa );
exp.setDistanceUnits( QgsProject::instance()->distanceUnits() );
bool useGeometry = exp.needsGeometry();
QgsFeatureRequest request( mMainView->masterModel()->request() );
@ -816,6 +817,7 @@ void QgsAttributeTableDialog::setFilterExpression( const QString& filterString )
QApplication::setOverrideCursor( Qt::WaitCursor );
filterExpression.setGeomCalculator( myDa );
filterExpression.setDistanceUnits( QgsProject::instance()->distanceUnits() );
QgsFeatureRequest request( mMainView->masterModel()->request() );
request.setSubsetOfAttributes( filterExpression.referencedColumns(), mLayer->fields() );
if ( !fetchGeom )

View File

@ -223,6 +223,8 @@ class APP_EXPORT QgsAttributeTableDialog : public QDialog, private Ui::QgsAttrib
QgsRubberBand* mRubberBand;
QgsSearchWidgetWrapper* mCurrentSearchWidgetWrapper;
friend class TestQgsAttributeTable;
};

View File

@ -164,6 +164,7 @@ void QgsFieldCalculator::accept()
QString calcString = builder->expressionText();
QgsExpression exp( calcString );
exp.setGeomCalculator( myDa );
exp.setDistanceUnits( QgsProject::instance()->distanceUnits() );
QgsExpressionContext expContext;
expContext << QgsExpressionContextUtils::globalScope()

View File

@ -71,6 +71,8 @@ class APP_EXPORT QgsFieldCalculator: public QDialog, private Ui::QgsFieldCalcula
/** Idx of changed attribute*/
int mAttributeId;
friend class TestQgsFieldCalculator;
};
#endif // QGSFIELDCALCULATOR_H

View File

@ -193,6 +193,11 @@ QGis::UnitType QgsMapToolIdentifyAction::displayUnits()
return ok ? unit : QGis::Meters;
}
QGis::UnitType QgsMapToolIdentifyAction::displayDistanceUnits()
{
return QgsProject::instance()->distanceUnits();
}
void QgsMapToolIdentifyAction::handleCopyToClipboard( QgsFeatureStore & featureStore )
{
QgsDebugMsg( QString( "features count = %1" ).arg( featureStore.features().size() ) );

View File

@ -81,6 +81,7 @@ class APP_EXPORT QgsMapToolIdentifyAction : public QgsMapToolIdentify
QgsIdentifyResultsDialog *resultsDialog();
virtual QGis::UnitType displayUnits() override;
virtual QGis::UnitType displayDistanceUnits() override;
};

View File

@ -54,9 +54,7 @@ QgsMeasureDialog::QgsMeasureDialog( QgsMeasureTool* tool, Qt::WindowFlags f )
mUnitsCombo->addItem( QgsUnitTypes::toString( QGis::Feet ), QGis::Feet );
mUnitsCombo->addItem( QgsUnitTypes::toString( QGis::Degrees ), QGis::Degrees );
mUnitsCombo->addItem( QgsUnitTypes::toString( QGis::NauticalMiles ), QGis::NauticalMiles );
QSettings settings;
QString units = settings.value( "/qgis/measure/displayunits", QgsUnitTypes::encodeUnit( QGis::Meters ) ).toString();
mUnitsCombo->setCurrentIndex( mUnitsCombo->findData( QgsUnitTypes::decodeDistanceUnit( units ) ) );
mUnitsCombo->setCurrentIndex( mUnitsCombo->findData( QgsProject::instance()->distanceUnits() ) );
updateSettings();
@ -78,7 +76,7 @@ void QgsMeasureDialog::updateSettings()
mDecimalPlaces = settings.value( "/qgis/measure/decimalplaces", "3" ).toInt();
mCanvasUnits = mTool->canvas()->mapUnits();
// Configure QgsDistanceArea
mDisplayUnits = static_cast< QGis::UnitType >( mUnitsCombo->itemData( mUnitsCombo->currentIndex() ).toInt() );
mDisplayUnits = QgsProject::instance()->distanceUnits();
mDa.setSourceCrs( mTool->canvas()->mapSettings().destinationCrs().srsid() );
mDa.setEllipsoid( QgsProject::instance()->readEntry( "Measure", "/Ellipsoid", GEO_NONE ) );
// Only use ellipsoidal calculation when project wide transformation is enabled.

View File

@ -54,6 +54,7 @@
#include "qgslayertreegroup.h"
#include "qgslayertreelayer.h"
#include "qgslayertreemodel.h"
#include "qgsunittypes.h"
#include "qgsmessagelog.h"
@ -84,6 +85,12 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas* mapCanvas, QWidget *pa
mCoordinateDisplayComboBox->addItem( tr( "Degrees, minutes" ), DegreesMinutes );
mCoordinateDisplayComboBox->addItem( tr( "Degrees, minutes, seconds" ), DegreesMinutesSeconds );
mDistanceUnitsCombo->addItem( tr( "Meters" ), QGis::Meters );
mDistanceUnitsCombo->addItem( tr( "Feet" ), QGis::Feet );
mDistanceUnitsCombo->addItem( tr( "Nautical miles" ), QGis::NauticalMiles );
mDistanceUnitsCombo->addItem( tr( "Degrees" ), QGis::Degrees );
mDistanceUnitsCombo->addItem( tr( "Map units" ), QGis::UnknownUnit );
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() ) );
@ -157,6 +164,8 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas* mapCanvas, QWidget *pa
else
mCoordinateDisplayComboBox->setCurrentIndex( mCoordinateDisplayComboBox->findData( DecimalDegrees ) );
mDistanceUnitsCombo->setCurrentIndex( mDistanceUnitsCombo->findData( QgsProject::instance()->distanceUnits() ) );
//get the color selections and set the button color accordingly
int myRedInt = QgsProject::instance()->readNumEntry( "Gui", "/SelectionColorRedPart", 255 );
int myGreenInt = QgsProject::instance()->readNumEntry( "Gui", "/SelectionColorGreenPart", 255 );
@ -779,6 +788,9 @@ void QgsProjectProperties::apply()
// Announce that we may have a new display precision setting
emit displayPrecisionChanged();
QGis::UnitType distanceUnits = static_cast< QGis::UnitType >( mDistanceUnitsCombo->itemData( mDistanceUnitsCombo->currentIndex() ).toInt() );
QgsProject::instance()->writeEntry( "Measurement", "/DistanceUnits", QgsUnitTypes::encodeUnit( distanceUnits ) );
QgsProject::instance()->writeEntry( "Paths", "/Absolute", cbxAbsolutePath->currentIndex() == 0 );
if ( mEllipsoidList.at( mEllipsoidIndex ).acronym.startsWith( "PARAMETER" ) )
@ -1139,7 +1151,6 @@ void QgsProjectProperties::showProjectionsTab()
void QgsProjectProperties::on_cbxProjectionEnabled_toggled( bool onFlyEnabled )
{
QString measureOnFlyState = tr( "Measure tool (CRS transformation: %1)" );
if ( !onFlyEnabled )
{
// reset projection to default
@ -1162,8 +1173,6 @@ void QgsProjectProperties::on_cbxProjectionEnabled_toggled( bool onFlyEnabled )
// unset ellipsoid
mEllipsoidIndex = 0;
btnGrpMeasureEllipsoid->setTitle( measureOnFlyState.arg( tr( "OFF" ) ) );
}
else
{
@ -1172,8 +1181,6 @@ void QgsProjectProperties::on_cbxProjectionEnabled_toggled( bool onFlyEnabled )
mLayerSrsId = projectionSelector->selectedCrsId();
}
projectionSelector->setSelectedCrsId( mProjectSrsId );
btnGrpMeasureEllipsoid->setTitle( measureOnFlyState.arg( tr( "ON" ) ) );
}
srIdUpdated();
@ -1230,7 +1237,7 @@ void QgsProjectProperties::updateGuiForMapUnits( QGis::UnitType units )
else
{
//make sure map units option is shown in coordinate display combo
QString mapUnitString = tr( "Map units (%1)" ).arg( QGis::tr( units ) );
QString mapUnitString = tr( "Map units (%1)" ).arg( QgsUnitTypes::toString( units ) );
if ( idx < 0 )
{
mCoordinateDisplayComboBox->insertItem( 0, mapUnitString, MapUnits );

View File

@ -36,6 +36,7 @@
#include "qgsvectorlayer.h"
#include "qgsvisibilitypresetcollection.h"
#include "qgslayerdefinition.h"
#include "qgsunittypes.h"
#include <QApplication>
#include <QFileInfo>
@ -45,6 +46,7 @@
#include <QTemporaryFile>
#include <QDir>
#include <QUrl>
#include <QSettings>
#ifdef Q_OS_UNIX
#include <utime.h>
@ -452,6 +454,10 @@ void QgsProject::clear()
writeEntry( "PositionPrecision", "/DecimalPlaces", 2 );
writeEntry( "Paths", "/Absolute", false );
//copy default distance units to project
QSettings s;
writeEntry( "Measurement", "/DistanceUnits", s.value( "/qgis/measure/displayunits" ).toString() );
setDirty( false );
}
@ -2058,6 +2064,19 @@ bool QgsProject::topologicalEditing() const
return ( QgsProject::instance()->readNumEntry( "Digitizing", "/TopologicalEditing", 0 ) > 0 );
}
QGis::UnitType QgsProject::distanceUnits() const
{
QString distanceUnitString = QgsProject::instance()->readEntry( "Measurement", "/DistanceUnits", QString() );
if ( !distanceUnitString.isEmpty() )
return QgsUnitTypes::decodeDistanceUnit( distanceUnitString );
//fallback to QGIS default measurement unit
QSettings s;
bool ok = false;
QGis::UnitType type = QgsUnitTypes::decodeDistanceUnit( s.value( "/qgis/measure/displayunits" ).toString(), &ok );
return ok ? type : QGis::Meters;
}
void QgsProjectBadLayerDefaultHandler::handleBadLayers( const QList<QDomNode>& /*layers*/, const QDomDocument& /*projectDom*/ )
{
// just ignore any bad layers

View File

@ -293,6 +293,11 @@ class CORE_EXPORT QgsProject : public QObject
/** Convenience function to query topological editing status */
bool topologicalEditing() const;
/** Convenience function to query default distance measurement units for project.
* @note added in QGIS 2.14
*/
QGis::UnitType distanceUnits() const;
/** Return project's home path
@return home path of project (or QString::null if not set) */
QString homePath() const;

View File

@ -369,9 +369,8 @@ QMap< QString, QString > QgsMapToolIdentify::featureDerivedAttributes( QgsFeatur
if ( geometryType == QGis::Line )
{
double dist = calc.measureLength( feature->constGeometry() );
QGis::UnitType myDisplayUnits;
convertMeasurement( calc, dist, myDisplayUnits, false );
QString str = calc.textUnit( dist, 3, myDisplayUnits, false ); // dist and myDisplayUnits are out params
dist = calc.convertLengthMeasurement( dist, displayDistanceUnits() );
QString str = formatDistance( dist );
derivedAttributes.insert( tr( "Length" ), str );
const QgsCurveV2* curve = dynamic_cast< const QgsCurveV2* >( feature->constGeometry()->geometry() );
@ -399,13 +398,15 @@ QMap< QString, QString > QgsMapToolIdentify::featureDerivedAttributes( QgsFeatur
else if ( geometryType == QGis::Polygon )
{
double area = calc.measureArea( feature->constGeometry() );
double perimeter = calc.measurePerimeter( feature->constGeometry() );
QGis::UnitType myDisplayUnits;
convertMeasurement( calc, area, myDisplayUnits, true ); // area and myDisplayUnits are out params
QString str = calc.textUnit( area, 3, myDisplayUnits, true );
derivedAttributes.insert( tr( "Area" ), str );
convertMeasurement( calc, perimeter, myDisplayUnits, false ); // perimeter and myDisplayUnits are out params
str = calc.textUnit( perimeter, 3, myDisplayUnits, false );
double perimeter = calc.measurePerimeter( feature->constGeometry() );
perimeter = calc.convertLengthMeasurement( perimeter, displayDistanceUnits() );
str = formatDistance( perimeter );
derivedAttributes.insert( tr( "Perimeter" ), str );
str = QLocale::system().toString( feature->constGeometry()->geometry()->nCoordinates() );
@ -640,7 +641,7 @@ bool QgsMapToolIdentify::identifyRasterLayer( QList<IdentifyResult> *results, Qg
void QgsMapToolIdentify::convertMeasurement( QgsDistanceArea &calc, double &measure, QGis::UnitType &u, bool isArea )
{
// Helper for converting between meters and feet
// Helper for converting between units
// The parameter &u is out only...
// Get the canvas units
@ -655,6 +656,19 @@ QGis::UnitType QgsMapToolIdentify::displayUnits()
return mCanvas->mapUnits();
}
QGis::UnitType QgsMapToolIdentify::displayDistanceUnits()
{
return mCanvas->mapUnits();
}
QString QgsMapToolIdentify::formatDistance( double distance )
{
QSettings settings;
bool baseUnit = settings.value( "/qgis/measure/keepbaseunit", false ).toBool();
return QgsDistanceArea::textUnit( distance, 3, displayDistanceUnits(), false, baseUnit );
}
void QgsMapToolIdentify::formatChanged( QgsRasterLayer *layer )
{
QgsDebugMsg( "Entered" );

View File

@ -163,6 +163,16 @@ class GUI_EXPORT QgsMapToolIdentify : public QgsMapTool
/** Transforms the measurements of derived attributes in the desired units*/
virtual QGis::UnitType displayUnits();
/** Desired units for distance display.
* @note added in QGIS 2.14
*/
virtual QGis::UnitType displayDistanceUnits();
/** Format a distance into a suitable string for display to the user
* @note added in QGIS 2.14
*/
QString formatDistance( double distance );
QMap< QString, QString > featureDerivedAttributes( QgsFeature *feature, QgsMapLayer *layer, const QgsPoint& layerPoint = QgsPoint() );
/** Adds details of the closest vertex to derived attributes

View File

@ -471,13 +471,13 @@
<item>
<widget class="QgsCollapsibleGroupBox" name="btnGrpMeasureEllipsoid">
<property name="title">
<string>Measure tool</string>
<string>Measurements</string>
</property>
<property name="syncGroup" stdset="0">
<string notr="true">projgeneral</string>
</property>
<layout class="QGridLayout" name="gridLayoutMeasureTool">
<item row="1" column="0">
<item row="0" column="0">
<widget class="QLabel" name="textLabel1_8">
<property name="text">
<string>Ellipsoid
@ -485,29 +485,39 @@
</property>
</widget>
</item>
<item row="2" column="1">
<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">
<string>Semi-major</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLineEdit" name="leSemiMajor"/>
</item>
<item row="2" column="4">
<widget class="QLineEdit" name="leSemiMinor"/>
</item>
<item row="2" column="3">
<item row="1" column="3">
<widget class="QLabel" name="label_42">
<property name="text">
<string>Semi-minor</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="4">
<widget class="QComboBox" name="cmbEllipsoid"/>
</item>
</layout>
</widget>
</item>
@ -2528,6 +2538,7 @@
<tabstop>cmbEllipsoid</tabstop>
<tabstop>leSemiMajor</tabstop>
<tabstop>leSemiMinor</tabstop>
<tabstop>mDistanceUnitsCombo</tabstop>
<tabstop>mCoordinateDisplayComboBox</tabstop>
<tabstop>radAutomatic</tabstop>
<tabstop>radManual</tabstop>

View File

@ -3,17 +3,23 @@
# the UI file won't be wrapped!
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_SOURCE_DIR}/src/core
${CMAKE_SOURCE_DIR}/src/core/auth
${CMAKE_SOURCE_DIR}/src/core/composer
${CMAKE_SOURCE_DIR}/src/core/geometry
${CMAKE_SOURCE_DIR}/src/core/raster
${CMAKE_SOURCE_DIR}/src/core/symbology-ng
${CMAKE_BINARY_DIR}/src/ui
${CMAKE_SOURCE_DIR}/src/gui
${CMAKE_SOURCE_DIR}/src/python
${CMAKE_SOURCE_DIR}/src/app
${CMAKE_SOURCE_DIR}/src/app/pluginmanager
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/core
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/core/auth
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/core/composer
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/core/geometry
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/core/raster
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/core/symbology-ng
${CMAKE_CURRENT_BINARY_DIR}/../../../src/ui
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/gui
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/gui/editorwidgets
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/gui/editorwidgets/core
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/gui/attributetable
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/gui/symbology-ng
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/gui/raster
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/python
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/app
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/app/pluginmanager
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/ui
)
INCLUDE_DIRECTORIES(SYSTEM
${QT_INCLUDE_DIR}
@ -96,3 +102,6 @@ ENDMACRO (ADD_QGIS_TEST)
# Tests:
ADD_QGIS_TEST(qgisappclipboard testqgisappclipboard.cpp)
ADD_QGIS_TEST(attributetabletest testqgsattributetable.cpp)
ADD_QGIS_TEST(fieldcalculatortest testqgsfieldcalculator.cpp)
ADD_QGIS_TEST(maptoolidentifyaction testqgsmaptoolidentifyaction.cpp)

View File

@ -0,0 +1,119 @@
/***************************************************************************
testqgsattributetable.cpp
-------------------------
Date : 2016-02-14
Copyright : (C) 2016 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include <QtTest/QtTest>
#include "qgisapp.h"
#include "qgsapplication.h"
#include "qgsvectorlayer.h"
#include "qgsfeature.h"
#include "qgsgeometry.h"
#include "qgsvectordataprovider.h"
#include "qgsattributetabledialog.h"
#include "qgsproject.h"
#include "qgsmapcanvas.h"
#include "qgsunittypes.h"
/** \ingroup UnitTests
* This is a unit test for the attribute table dialog
*/
class TestQgsAttributeTable : public QObject
{
Q_OBJECT
public:
TestQgsAttributeTable();
private slots:
void initTestCase();// will be called before the first testfunction is executed.
void cleanupTestCase();// will be called after the last testfunction was executed.
void init() {} // will be called before each testfunction is executed.
void cleanup() {} // will be called after every testfunction.
void testFieldCalculation();
private:
QgisApp * mQgisApp;
};
TestQgsAttributeTable::TestQgsAttributeTable()
: mQgisApp( nullptr )
{
}
//runs before all tests
void TestQgsAttributeTable::initTestCase()
{
qDebug() << "TestQgisAppClipboard::initTestCase()";
// init QGIS's paths - true means that all path will be inited from prefix
QgsApplication::init();
QgsApplication::initQgis();
mQgisApp = new QgisApp();
}
//runs after all tests
void TestQgsAttributeTable::cleanupTestCase()
{
QgsApplication::exitQgis();
}
void TestQgsAttributeTable::testFieldCalculation()
{
//test field calculation
//create a temporary layer
QScopedPointer< QgsVectorLayer> tempLayer( new QgsVectorLayer( "LineString?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 line3111;
line3111 << QgsPoint( 2484588, 2425722 ) << QgsPoint( 2482767, 2398853 );
f1.setGeometry( QgsGeometry::fromPolyline( line3111 ) );
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", "/DistanceUnits", QgsUnitTypes::encodeUnit( QGis::Meters ) );
// run length calculation
QScopedPointer< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.data() ) );
tempLayer->startEditing();
dlg->runFieldCalculation( tempLayer.data(), "col1", "$length" );
tempLayer->commitChanges();
// check result
QgsFeatureIterator fit = tempLayer->dataProvider()->getFeatures();
QgsFeature f;
QVERIFY( fit.nextFeature( f ) );
double expected = 26932.156;
QVERIFY( qgsDoubleNear( f.attribute( "col1" ).toDouble(), expected, 0.001 ) );
// change project length unit, check calculation respects unit
QgsProject::instance()->writeEntry( "Measurement", "/DistanceUnits", QgsUnitTypes::encodeUnit( QGis::Feet ) );
QScopedPointer< QgsAttributeTableDialog > dlg2( new QgsAttributeTableDialog( tempLayer.data() ) );
tempLayer->startEditing();
dlg2->runFieldCalculation( tempLayer.data(), "col1", "$length" );
tempLayer->commitChanges();
// check result
fit = tempLayer->dataProvider()->getFeatures();
QVERIFY( fit.nextFeature( f ) );
expected = 88360.0918635;
QVERIFY( qgsDoubleNear( f.attribute( "col1" ).toDouble(), expected, 0.001 ) );
}
QTEST_MAIN( TestQgsAttributeTable )
#include "testqgsattributetable.moc"

View File

@ -0,0 +1,130 @@
/***************************************************************************
testqgsfieldcalculator.cpp
--------------------------
Date : 2016-02-14
Copyright : (C) 2016 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include <QtTest/QtTest>
#include "qgisapp.h"
#include "qgsapplication.h"
#include "qgsvectorlayer.h"
#include "qgsfeature.h"
#include "qgsgeometry.h"
#include "qgsvectordataprovider.h"
#include "qgsfieldcalculator.h"
#include "qgsproject.h"
#include "qgsmapcanvas.h"
#include "qgsunittypes.h"
/** \ingroup UnitTests
* This is a unit test for the field calculator
*/
class TestQgsFieldCalculator : public QObject
{
Q_OBJECT
public:
TestQgsFieldCalculator();
private slots:
void initTestCase();// will be called before the first testfunction is executed.
void cleanupTestCase();// will be called after the last testfunction was executed.
void init() {} // will be called before each testfunction is executed.
void cleanup() {} // will be called after every testfunction.
void testLengthCalculations();
private:
QgisApp * mQgisApp;
};
TestQgsFieldCalculator::TestQgsFieldCalculator()
: mQgisApp( nullptr )
{
}
//runs before all tests
void TestQgsFieldCalculator::initTestCase()
{
qDebug() << "TestQgisAppClipboard::initTestCase()";
// init QGIS's paths - true means that all path will be inited from prefix
QgsApplication::init();
QgsApplication::initQgis();
mQgisApp = new QgisApp();
}
//runs after all tests
void TestQgsFieldCalculator::cleanupTestCase()
{
QgsApplication::exitQgis();
}
void TestQgsFieldCalculator::testLengthCalculations()
{
//test length calculation respects ellipsoid and project distance units
//create a temporary layer
QScopedPointer< QgsVectorLayer> tempLayer( new QgsVectorLayer( "LineString?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 line3111;
line3111 << QgsPoint( 2484588, 2425722 ) << QgsPoint( 2482767, 2398853 );
f1.setGeometry( QgsGeometry::fromPolyline( line3111 ) );
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", "/DistanceUnits", QgsUnitTypes::encodeUnit( QGis::Meters ) );
// run length 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( "$length" );
calc->accept();
tempLayer->commitChanges();
// check result
QgsFeatureIterator fit = tempLayer->dataProvider()->getFeatures();
QgsFeature f;
QVERIFY( fit.nextFeature( f ) );
double expected = 26932.156;
QVERIFY( qgsDoubleNear( f.attribute( "col1" ).toDouble(), expected, 0.001 ) );
// change project length unit, check calculation respects unit
QgsProject::instance()->writeEntry( "Measurement", "/DistanceUnits", QgsUnitTypes::encodeUnit( QGis::Feet ) );
tempLayer->startEditing();
QScopedPointer< QgsFieldCalculator > calc2( new QgsFieldCalculator( tempLayer.data() ) );
calc2->mUpdateExistingGroupBox->setChecked( true );
calc2->mExistingFieldComboBox->setCurrentIndex( 1 );
calc2->builder->setExpressionText( "$length" );
calc2->accept();
tempLayer->commitChanges();
// check result
fit = tempLayer->dataProvider()->getFeatures();
QVERIFY( fit.nextFeature( f ) );
expected = 88360.0918635;
QVERIFY( qgsDoubleNear( f.attribute( "col1" ).toDouble(), expected, 0.001 ) );
}
QTEST_MAIN( TestQgsFieldCalculator )
#include "testqgsfieldcalculator.moc"

View File

@ -0,0 +1,188 @@
/***************************************************************************
testqgsmaptoolidentifyaction.cpp
--------------------------------
Date : 2016-02-14
Copyright : (C) 2016 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include <QtTest/QtTest>
#include "qgsapplication.h"
#include "qgsvectorlayer.h"
#include "qgsfeature.h"
#include "qgsgeometry.h"
#include "qgsvectordataprovider.h"
#include "qgsproject.h"
#include "qgsmapcanvas.h"
#include "qgsunittypes.h"
#include "qgsmaptoolidentifyaction.h"
class TestQgsMapToolIdentifyAction : public QObject
{
Q_OBJECT
public:
TestQgsMapToolIdentifyAction()
: canvas( 0 )
{}
private slots:
void initTestCase(); // will be called before the first testfunction is executed.
void cleanupTestCase(); // will be called after the last testfunction was executed.
void init(); // will be called before each testfunction is executed.
void cleanup(); // will be called after every testfunction.
void lengthCalculation(); //test calculation of derived length attributes
void perimeterCalculation(); //test calculation of derived perimeter attribute
private:
QgsMapCanvas* canvas;
};
void TestQgsMapToolIdentifyAction::initTestCase()
{
QgsApplication::init();
QgsApplication::initQgis();
// Set up the QSettings environment
QCoreApplication::setOrganizationName( "QGIS" );
QCoreApplication::setOrganizationDomain( "qgis.org" );
QCoreApplication::setApplicationName( "QGIS-TEST" );
QgsApplication::showSettings();
}
void TestQgsMapToolIdentifyAction::cleanupTestCase()
{
QgsApplication::exitQgis();
}
void TestQgsMapToolIdentifyAction::init()
{
canvas = new QgsMapCanvas();
}
void TestQgsMapToolIdentifyAction::cleanup()
{
delete canvas;
}
void TestQgsMapToolIdentifyAction::lengthCalculation()
{
QSettings s;
s.setValue( "/qgis/measure/keepbaseunit", true );
//create a temporary layer
QScopedPointer< QgsVectorLayer> tempLayer( new QgsVectorLayer( "LineString?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 line3111;
line3111 << QgsPoint( 2484588, 2425722 ) << QgsPoint( 2482767, 2398853 );
f1.setGeometry( QgsGeometry::fromPolyline( line3111 ) );
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", "/DistanceUnits", QgsUnitTypes::encodeUnit( QGis::Meters ) );
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 derivedLength = result.at( 0 ).mDerivedAttributes[tr( "Length" )];
double length = derivedLength.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
QVERIFY( qgsDoubleNear( length, 26932.2, 0.1 ) );
//check that project units are respected
QgsProject::instance()->writeEntry( "Measurement", "/DistanceUnits", QgsUnitTypes::encodeUnit( QGis::Feet ) );
result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer*>() << tempLayer.data() );
QCOMPARE( result.length(), 1 );
derivedLength = result.at( 0 ).mDerivedAttributes[tr( "Length" )];
length = derivedLength.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
QVERIFY( qgsDoubleNear( length, 88360.1, 0.1 ) );
//test unchecked "keep base units" setting
s.setValue( "/qgis/measure/keepbaseunit", false );
result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer*>() << tempLayer.data() );
QCOMPARE( result.length(), 1 );
derivedLength = result.at( 0 ).mDerivedAttributes[tr( "Length" )];
length = derivedLength.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
QVERIFY( qgsDoubleNear( length, 16.735, 0.001 ) );
}
void TestQgsMapToolIdentifyAction::perimeterCalculation()
{
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", "/DistanceUnits", QgsUnitTypes::encodeUnit( QGis::Meters ) );
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 derivedPerimeter = result.at( 0 ).mDerivedAttributes[tr( "Perimeter" )];
double perimeter = derivedPerimeter.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
QCOMPARE( perimeter, 128289.074 );
//check that project units are respected
QgsProject::instance()->writeEntry( "Measurement", "/DistanceUnits", QgsUnitTypes::encodeUnit( QGis::Feet ) );
result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer*>() << tempLayer.data() );
QCOMPARE( result.length(), 1 );
derivedPerimeter = result.at( 0 ).mDerivedAttributes[tr( "Perimeter" )];
perimeter = derivedPerimeter.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
QVERIFY( qgsDoubleNear( perimeter, 420896.0, 0.1 ) );
//test unchecked "keep base units" setting
s.setValue( "/qgis/measure/keepbaseunit", false );
result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer*>() << tempLayer.data() );
QCOMPARE( result.length(), 1 );
derivedPerimeter = result.at( 0 ).mDerivedAttributes[tr( "Perimeter" )];
perimeter = derivedPerimeter.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
QVERIFY( qgsDoubleNear( perimeter, 79.715, 0.001 ) );
}
QTEST_MAIN( TestQgsMapToolIdentifyAction )
#include "testqgsmaptoolidentifyaction.moc"

View File

@ -17,6 +17,7 @@
#include <qgsapplication.h>
#include <qgsproject.h>
#include "qgsunittypes.h"
class TestQgsProject : public QObject
@ -29,6 +30,7 @@ class TestQgsProject : public QObject
void cleanup();// will be called after every testfunction.
void testReadPath();
void testProjectUnits();
};
void TestQgsProject::init()
@ -43,6 +45,11 @@ void TestQgsProject::cleanup()
void TestQgsProject::initTestCase()
{
// Runs once before any tests are run
// Set up the QSettings environment
QCoreApplication::setOrganizationName( "QGIS" );
QCoreApplication::setOrganizationDomain( "qgis.org" );
QCoreApplication::setApplicationName( "QGIS-TEST" );
}
@ -76,6 +83,28 @@ void TestQgsProject::testReadPath()
}
void TestQgsProject::testProjectUnits()
{
//test setting and retrieving project units
//first set a default QGIS distance unit
QSettings s;
s.setValue( "/qgis/measure/displayunits", QgsUnitTypes::encodeUnit( QGis::Feet ) );
QgsProject* prj = QgsProject::instance();
// new project should inherit QGIS default distance unit
prj->clear();
QCOMPARE( prj->distanceUnits(), QGis::Feet );
//changing default QGIS unit should not affect existing project
s.setValue( "/qgis/measure/displayunits", QgsUnitTypes::encodeUnit( QGis::NauticalMiles ) );
QCOMPARE( prj->distanceUnits(), QGis::Feet );
//test setting new units for project
prj->writeEntry( "Measurement", "/DistanceUnits", QgsUnitTypes::encodeUnit( QGis::NauticalMiles ) );
QCOMPARE( prj->distanceUnits(), QGis::NauticalMiles );
}
QTEST_MAIN( TestQgsProject )
#include "testqgsproject.moc"