1 /***************************************************************************
2     qgsmeasuretool.cpp  -  map tool for measuring distances and areas
3     ---------------------
4     begin                : April 2007
5     copyright            : (C) 2007 by Martin Dobias
6     email                : wonder.sk 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 "qgsdistancearea.h"
17 #include "qgslogger.h"
18 #include "qgsmapcanvas.h"
19 #include "qgsmaptopixel.h"
20 #include "qgsrubberband.h"
21 #include "qgsvectorlayer.h"
22 #include "qgssnappingutils.h"
23 #include "qgstolerance.h"
24 #include "qgsexception.h"
25 #include "qgsmeasuredialog.h"
26 #include "qgsmeasuretool.h"
27 #include "qgsmessagelog.h"
28 #include "qgssettings.h"
29 #include "qgsproject.h"
30 #include "qgssnapindicator.h"
31 #include "qgsmapmouseevent.h"
32 
33 #include <QMessageBox>
34 
35 
QgsMeasureTool(QgsMapCanvas * canvas,bool measureArea)36 QgsMeasureTool::QgsMeasureTool( QgsMapCanvas *canvas, bool measureArea )
37   : QgsMapTool( canvas )
38   , mMeasureArea( measureArea )
39   , mSnapIndicator( new QgsSnapIndicator( canvas ) )
40 {
41   mRubberBand = new QgsRubberBand( canvas, mMeasureArea ? QgsWkbTypes::PolygonGeometry : QgsWkbTypes::LineGeometry );
42   mRubberBandPoints = new QgsRubberBand( canvas, QgsWkbTypes::PointGeometry );
43 
44   // Append point we will move
45   mPoints.append( QgsPointXY( 0, 0 ) );
46   mDestinationCrs = canvas->mapSettings().destinationCrs();
47 
48   mDialog = new QgsMeasureDialog( this );
49   mDialog->setWindowFlags( mDialog->windowFlags() | Qt::Tool );
50   mDialog->restorePosition();
51 
52   connect( canvas, &QgsMapCanvas::destinationCrsChanged, this, &QgsMeasureTool::updateSettings );
53 }
54 
~QgsMeasureTool()55 QgsMeasureTool::~QgsMeasureTool()
56 {
57   // important - dialog is not parented to this tool (it's parented to the main window)
58   // but we want to clean it up now
59   delete mDialog;
60 }
61 
points() const62 QVector<QgsPointXY> QgsMeasureTool::points() const
63 {
64   return mPoints;
65 }
66 
67 
activate()68 void QgsMeasureTool::activate()
69 {
70   mDialog->show();
71   mRubberBand->show();
72   mRubberBandPoints->show();
73   QgsMapTool::activate();
74 
75   // ensure that we have correct settings
76   updateSettings();
77 
78   // If we suspect that they have data that is projected, yet the
79   // map CRS is set to a geographic one, warn them.
80   if ( mCanvas->mapSettings().destinationCrs().isValid() &&
81        mCanvas->mapSettings().destinationCrs().isGeographic() &&
82        ( mCanvas->extent().height() > 360 ||
83          mCanvas->extent().width() > 720 ) )
84   {
85     QMessageBox::warning( nullptr, tr( "Incorrect Measure Results" ),
86                           tr( "<p>This map is defined with a geographic coordinate system "
87                               "(latitude/longitude) "
88                               "but the map extents suggests that it is actually a projected "
89                               "coordinate system (e.g., Mercator). "
90                               "If so, the results from line or area measurements will be "
91                               "incorrect.</p>"
92                               "<p>To fix this, explicitly set an appropriate map coordinate "
93                               "system using the <tt>Settings:Project Properties</tt> menu." ) );
94     mWrongProjectProjection = true;
95   }
96 }
97 
deactivate()98 void QgsMeasureTool::deactivate()
99 {
100   mSnapIndicator->setMatch( QgsPointLocator::Match() );
101 
102   mDialog->hide();
103   mRubberBand->hide();
104   mRubberBandPoints->hide();
105   QgsMapTool::deactivate();
106 }
107 
restart()108 void QgsMeasureTool::restart()
109 {
110   mPoints.clear();
111 
112   mRubberBand->reset( mMeasureArea ? QgsWkbTypes::PolygonGeometry : QgsWkbTypes::LineGeometry );
113   mRubberBandPoints->reset( QgsWkbTypes::PointGeometry );
114 
115   mDone = true;
116   mWrongProjectProjection = false;
117 }
118 
updateSettings()119 void QgsMeasureTool::updateSettings()
120 {
121   QgsSettings settings;
122 
123   int myRed = settings.value( QStringLiteral( "qgis/default_measure_color_red" ), 222 ).toInt();
124   int myGreen = settings.value( QStringLiteral( "qgis/default_measure_color_green" ), 155 ).toInt();
125   int myBlue = settings.value( QStringLiteral( "qgis/default_measure_color_blue" ), 67 ).toInt();
126   mRubberBand->setColor( QColor( myRed, myGreen, myBlue, 100 ) );
127   mRubberBand->setWidth( 3 );
128   mRubberBandPoints->setIcon( QgsRubberBand::ICON_CIRCLE );
129   mRubberBandPoints->setIconSize( 10 );
130   mRubberBandPoints->setColor( QColor( myRed, myGreen, myBlue, 150 ) );
131 
132   // Reproject the points to the new destination CoordinateReferenceSystem
133   if ( mRubberBand->size() > 0 && mDestinationCrs != mCanvas->mapSettings().destinationCrs() && mCanvas->mapSettings().destinationCrs().isValid() )
134   {
135     QVector<QgsPointXY> points = mPoints;
136     bool lastDone = mDone;
137 
138     mDialog->restart();
139     mDone = lastDone;
140     QgsCoordinateTransform ct( mDestinationCrs, mCanvas->mapSettings().destinationCrs(), QgsProject::instance() );
141 
142     const auto constPoints = points;
143     for ( const QgsPointXY &previousPoint : constPoints )
144     {
145       try
146       {
147         QgsPointXY point = ct.transform( previousPoint );
148 
149         mPoints.append( point );
150         mRubberBand->addPoint( point, false );
151         mRubberBandPoints->addPoint( point, false );
152       }
153       catch ( QgsCsException &cse )
154       {
155         QgsMessageLog::logMessage( tr( "Transform error caught at the MeasureTool: %1" ).arg( cse.what() ) );
156       }
157     }
158 
159     mRubberBand->updatePosition();
160     mRubberBand->update();
161     mRubberBandPoints->updatePosition();
162     mRubberBandPoints->update();
163   }
164   mDestinationCrs = mCanvas->mapSettings().destinationCrs();
165 
166   mDialog->updateSettings();
167 
168   if ( !mDone && mRubberBand->size() > 0 )
169   {
170     mRubberBand->addPoint( mPoints.last() );
171     mDialog->addPoint();
172   }
173   if ( mRubberBand->size() > 0 )
174   {
175     mRubberBand->setVisible( true );
176     mRubberBandPoints->setVisible( true );
177   }
178 }
179 
180 //////////////////////////
181 
canvasPressEvent(QgsMapMouseEvent * e)182 void QgsMeasureTool::canvasPressEvent( QgsMapMouseEvent *e )
183 {
184   Q_UNUSED( e )
185 }
186 
canvasMoveEvent(QgsMapMouseEvent * e)187 void QgsMeasureTool::canvasMoveEvent( QgsMapMouseEvent *e )
188 {
189   QgsPointXY point = e->snapPoint();
190   mSnapIndicator->setMatch( e->mapPointMatch() );
191 
192   if ( ! mDone )
193   {
194     mRubberBand->movePoint( point );
195     mDialog->mouseMove( point );
196   }
197 }
198 
199 
canvasReleaseEvent(QgsMapMouseEvent * e)200 void QgsMeasureTool::canvasReleaseEvent( QgsMapMouseEvent *e )
201 {
202   QgsPointXY point = e->snapPoint();
203 
204   if ( mDone ) // if we have stopped measuring any mouse click restart measuring
205   {
206     mDialog->restart();
207   }
208 
209   if ( e->button() == Qt::RightButton ) // if we clicked the right button we stop measuring
210   {
211     mDone = true;
212     mRubberBand->removeLastPoint();
213     mDialog->removeLastPoint();
214   }
215   else if ( e->button() == Qt::LeftButton )
216   {
217     mDone = false;
218     addPoint( point );
219   }
220 
221   mDialog->show();
222 
223 }
224 
undo()225 void QgsMeasureTool::undo()
226 {
227   if ( mRubberBand )
228   {
229     if ( mPoints.empty() )
230     {
231       return;
232     }
233 
234     if ( mPoints.size() == 1 )
235     {
236       //removing first point, so restart everything
237       restart();
238       mDialog->restart();
239     }
240     else
241     {
242       //remove second last point from line band, and last point from points band
243       mRubberBand->removePoint( -2, true );
244       mRubberBandPoints->removePoint( -1, true );
245       mPoints.removeLast();
246 
247       mDialog->removeLastPoint();
248     }
249 
250   }
251 }
252 
keyPressEvent(QKeyEvent * e)253 void QgsMeasureTool::keyPressEvent( QKeyEvent *e )
254 {
255   if ( ( e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete ) )
256   {
257     if ( !mDone )
258     {
259       undo();
260     }
261 
262     // Override default shortcut management in MapCanvas
263     e->ignore();
264   }
265 }
266 
267 
addPoint(const QgsPointXY & point)268 void QgsMeasureTool::addPoint( const QgsPointXY &point )
269 {
270   QgsDebugMsg( "point=" + point.toString() );
271 
272   // don't add points with the same coordinates
273   if ( !mPoints.isEmpty() && mPoints.last() == point )
274   {
275     return;
276   }
277 
278   QgsPointXY pnt( point );
279   // Append point that we will be moving.
280   mPoints.append( pnt );
281 
282   mRubberBand->addPoint( point );
283   mRubberBandPoints->addPoint( point );
284   if ( ! mDone )    // Prevent the insertion of a new item in segments measure table
285   {
286     mDialog->addPoint();
287   }
288 }
289