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