1 /***************************************************************************
2 qgs3dmeasuredialog.cpp
3 --------------------------------------
4 Date : Jun 2019
5 Copyright : (C) 2019 by Ismail Sunni
6 Email : imajimatika at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16 #include <QCloseEvent>
17 #include <QPushButton>
18
19 #include "qgsproject.h"
20 #include "qgs3dmeasuredialog.h"
21 #include "qgs3dmaptoolmeasureline.h"
22 #include "qgsmapcanvas.h"
23 #include "qgisapp.h"
24 #include "qgs3dmapsettings.h"
25
26
Qgs3DMeasureDialog(Qgs3DMapToolMeasureLine * tool,Qt::WindowFlags f)27 Qgs3DMeasureDialog::Qgs3DMeasureDialog( Qgs3DMapToolMeasureLine *tool, Qt::WindowFlags f )
28 : QDialog( tool->canvas()->topLevelWidget(), f )
29 , mTool( tool )
30 {
31 setupUi( this );
32
33 setWindowTitle( tr( " 3D Measurement Tool" ) );
34
35 // New button
36 QPushButton *newButton = new QPushButton( tr( "&New" ) );
37 buttonBox->addButton( newButton, QDialogButtonBox::ActionRole );
38 connect( newButton, &QAbstractButton::clicked, this, &Qgs3DMeasureDialog::restart );
39
40 // Remove/Hide unused features/options from 2D line measurement
41 // Only support for Cartesian
42 mCartesian->setChecked( true );
43 // Hide ellipsoidal and Cartesian radio button (not needed)
44 mCartesian->hide();
45 mEllipsoidal->hide();
46 groupBox->hide();
47
48 // Update text for 3D specific
49 totalDistanceLabel->setText( tr( "Total 3D Distance" ) );
50
51 // Initialize unit combo box
52 // Add a configuration button
53 QPushButton *cb = new QPushButton( tr( "&Configuration" ) );
54 buttonBox->addButton( cb, QDialogButtonBox::ActionRole );
55 connect( cb, &QAbstractButton::clicked, this, &Qgs3DMeasureDialog::openConfigTab );
56
57 repopulateComboBoxUnits();
58
59 // Remove Help button until we have proper documentation for it
60 buttonBox->removeButton( buttonBox->button( QDialogButtonBox::Help ) );
61
62 connect( buttonBox, &QDialogButtonBox::rejected, this, &Qgs3DMeasureDialog::reject );
63 connect( mUnitsCombo, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &Qgs3DMeasureDialog::unitsChanged );
64 }
65
saveWindowLocation()66 void Qgs3DMeasureDialog::saveWindowLocation()
67 {
68 QgsSettings settings;
69 settings.setValue( QStringLiteral( "Windows/3DMeasure/geometry" ), saveGeometry() );
70 const QString &key = "/Windows/3DMeasure/h";
71 settings.setValue( key, height() );
72 }
73
restorePosition()74 void Qgs3DMeasureDialog::restorePosition()
75 {
76 const QgsSettings settings;
77 restoreGeometry( settings.value( QStringLiteral( "Windows/3DMeasure/geometry" ) ).toByteArray() );
78 const int wh = settings.value( QStringLiteral( "Windows/3DMeasure/h" ), 200 ).toInt();
79 resize( width(), wh );
80 }
81
addPoint()82 void Qgs3DMeasureDialog::addPoint()
83 {
84 const int numPoints = mTool->points().size();
85 if ( numPoints > 1 )
86 {
87 if ( !mTool->done() )
88 {
89 // Add new entry in the table
90 addMeasurement( lastDistance(), lastVerticalDistance(), lastHorizontalDistance() );
91 mTotal += lastDistance();
92 mHorizontalTotal += lastHorizontalDistance();
93 updateTotal();
94 }
95 }
96 else
97 {
98 updateTotal();
99 }
100 }
101
lastDistance()102 double Qgs3DMeasureDialog::lastDistance()
103 {
104 const QgsPoint lastPoint = mTool->points().rbegin()[0];
105 const QgsPoint secondLastPoint = mTool->points().rbegin()[1];
106 return lastPoint.distance3D( secondLastPoint );
107 }
108
lastVerticalDistance()109 double Qgs3DMeasureDialog::lastVerticalDistance()
110 {
111 const QgsPoint lastPoint = mTool->points().rbegin()[0];
112 const QgsPoint secondLastPoint = mTool->points().rbegin()[1];
113 return lastPoint.z() - secondLastPoint.z();
114 }
115
lastHorizontalDistance()116 double Qgs3DMeasureDialog::lastHorizontalDistance()
117 {
118 const QgsPoint lastPoint = mTool->points().rbegin()[0];
119 const QgsPoint secondLastPoint = mTool->points().rbegin()[1];
120 return lastPoint.distance( secondLastPoint );
121 }
122
repopulateComboBoxUnits()123 void Qgs3DMeasureDialog::repopulateComboBoxUnits()
124 {
125 mUnitsCombo->addItem( QgsUnitTypes::toString( QgsUnitTypes::DistanceMeters ), QgsUnitTypes::DistanceMeters );
126 mUnitsCombo->addItem( QgsUnitTypes::toString( QgsUnitTypes::DistanceKilometers ), QgsUnitTypes::DistanceKilometers );
127 mUnitsCombo->addItem( QgsUnitTypes::toString( QgsUnitTypes::DistanceFeet ), QgsUnitTypes::DistanceFeet );
128 mUnitsCombo->addItem( QgsUnitTypes::toString( QgsUnitTypes::DistanceYards ), QgsUnitTypes::DistanceYards );
129 mUnitsCombo->addItem( QgsUnitTypes::toString( QgsUnitTypes::DistanceMiles ), QgsUnitTypes::DistanceMiles );
130 mUnitsCombo->addItem( QgsUnitTypes::toString( QgsUnitTypes::DistanceNauticalMiles ), QgsUnitTypes::DistanceNauticalMiles );
131 mUnitsCombo->addItem( QgsUnitTypes::toString( QgsUnitTypes::DistanceCentimeters ), QgsUnitTypes::DistanceCentimeters );
132 mUnitsCombo->addItem( QgsUnitTypes::toString( QgsUnitTypes::DistanceMillimeters ), QgsUnitTypes::DistanceMillimeters );
133 mUnitsCombo->addItem( QgsUnitTypes::toString( QgsUnitTypes::DistanceDegrees ), QgsUnitTypes::DistanceDegrees );
134 mUnitsCombo->addItem( tr( "map units" ), QgsUnitTypes::DistanceUnknownUnit );
135 }
136
removeLastPoint()137 void Qgs3DMeasureDialog::removeLastPoint()
138 {
139 const int numPoints = mTool->points().size();
140 if ( numPoints >= 1 )
141 {
142 // Remove final row
143 delete mTable->takeTopLevelItem( mTable->topLevelItemCount() - 1 );
144 // Update total distance
145 const QgsLineString measureLine( mTool->points() );
146 mTotal = measureLine.length3D();
147 mHorizontalTotal = measureLine.length();
148 updateTotal();
149 }
150 }
151
reject()152 void Qgs3DMeasureDialog::reject()
153 {
154 saveWindowLocation();
155 restart();
156 QDialog::close();
157 }
158
restart()159 void Qgs3DMeasureDialog::restart()
160 {
161 mTool->restart();
162 resetTable();
163 }
164
closeEvent(QCloseEvent * e)165 void Qgs3DMeasureDialog::closeEvent( QCloseEvent *e )
166 {
167 reject();
168 e->accept();
169 }
170
updateSettings()171 void Qgs3DMeasureDialog::updateSettings()
172 {
173 const QgsSettings settings;
174
175 mDecimalPlaces = settings.value( QStringLiteral( "qgis/measure/decimalplaces" ), "3" ).toInt();
176 mMapDistanceUnit = mTool->canvas()->map()->crs().mapUnits();
177 mDisplayedDistanceUnit = QgsUnitTypes::decodeDistanceUnit(
178 settings.value( QStringLiteral( "qgis/measure/displayunits" ),
179 QgsUnitTypes::encodeUnit( QgsUnitTypes::DistanceUnknownUnit ) ).toString() );
180 setupTableHeader();
181 mUnitsCombo->setCurrentIndex( mUnitsCombo->findData( mDisplayedDistanceUnit ) );
182 }
183
unitsChanged(int index)184 void Qgs3DMeasureDialog::unitsChanged( int index )
185 {
186 mDisplayedDistanceUnit = static_cast< QgsUnitTypes::DistanceUnit >( mUnitsCombo->itemData( index ).toInt() );
187 updateTable();
188 updateTotal();
189 }
190
convertLength(double length,QgsUnitTypes::DistanceUnit toUnit) const191 double Qgs3DMeasureDialog::convertLength( double length, QgsUnitTypes::DistanceUnit toUnit ) const
192 {
193 const double factorUnits = QgsUnitTypes::fromUnitToUnitFactor( mMapDistanceUnit, toUnit );
194 return length * factorUnits;
195 }
196
formatDistance(double distance) const197 QString Qgs3DMeasureDialog::formatDistance( double distance ) const
198 {
199 const QgsSettings settings;
200 const bool baseUnit = settings.value( QStringLiteral( "qgis/measure/keepbaseunit" ), true ).toBool();
201 return QgsUnitTypes::formatDistance( distance, mDecimalPlaces, mDisplayedDistanceUnit, baseUnit );
202 }
203
showHelp()204 void Qgs3DMeasureDialog::showHelp()
205 {
206 QgsHelp::openHelp( QStringLiteral( "introduction/general_tools.html#measuring" ) );
207 }
208
openConfigTab()209 void Qgs3DMeasureDialog::openConfigTab()
210 {
211 QgisApp::instance()->showOptionsDialog( this, QStringLiteral( "mOptionsPageMapTools" ) );
212 }
213
setupTableHeader()214 void Qgs3DMeasureDialog::setupTableHeader()
215 {
216 // Set the table header to show displayed unit
217 QStringList headers;
218 headers << tr( "Horizontal Distance" );
219 headers << tr( "Vertical Distance" );
220 headers << tr( "3D Distance" );
221
222 QTreeWidgetItem *headerItem = new QTreeWidgetItem( headers );
223 for ( int i = 0; i < headers.count(); ++i )
224 {
225 headerItem->setTextAlignment( i, Qt::AlignRight );
226 }
227 mTable->setHeaderItem( headerItem );
228 for ( int i = 0; i < headers.count(); ++i )
229 {
230 mTable->resizeColumnToContents( i );
231 }
232 }
233
addMeasurement(double distance,double verticalDistance,double horizontalDistance)234 void Qgs3DMeasureDialog::addMeasurement( double distance, double verticalDistance, double horizontalDistance )
235 {
236 QStringList content;
237 content << QLocale().toString( convertLength( horizontalDistance, mDisplayedDistanceUnit ), 'f', mDecimalPlaces );
238 content << QLocale().toString( convertLength( verticalDistance, mDisplayedDistanceUnit ), 'f', mDecimalPlaces );
239 content << QLocale().toString( convertLength( distance, mDisplayedDistanceUnit ), 'f', mDecimalPlaces );
240 QTreeWidgetItem *item = new QTreeWidgetItem( content );
241 for ( int i = 0; i < content.count(); ++i )
242 {
243 item->setTextAlignment( i, Qt::AlignRight );
244 }
245 mTable->addTopLevelItem( item );
246 mTable->scrollToItem( item );
247 }
248
updateTotal()249 void Qgs3DMeasureDialog::updateTotal()
250 {
251 // Update total with new displayed unit
252 editTotal->setText( formatDistance( convertLength( mTotal, mDisplayedDistanceUnit ) ) );
253 editHorizontalTotal->setText( formatDistance( convertLength( mHorizontalTotal, mDisplayedDistanceUnit ) ) );
254 }
255
updateTable()256 void Qgs3DMeasureDialog::updateTable()
257 {
258 setupTableHeader();
259
260 // Reset table
261 mTable->clear();
262
263 // Repopulate the table based on new displayed unit
264 QVector<QgsPoint>::const_iterator it;
265 bool isFirstPoint = true; // first point
266 QgsPoint p1, p2;
267 const QVector< QgsPoint > tmpPoints = mTool->points();
268 for ( it = tmpPoints.constBegin(); it != tmpPoints.constEnd(); ++it )
269 {
270 p2 = *it;
271 if ( !isFirstPoint )
272 {
273 const double distance = p1.distance3D( p2 );
274 const double verticalDistance = p2.z() - p1.z();
275 const double horizontalDistance = p1.distance( p2 );
276 addMeasurement( distance, verticalDistance, horizontalDistance );
277 }
278 p1 = p2;
279 isFirstPoint = false;
280 }
281 }
282
resetTable()283 void Qgs3DMeasureDialog::resetTable()
284 {
285 mTable->clear();
286 mTotal = 0.;
287 mHorizontalTotal = 0.;
288 updateTotal();
289 }
290