1 /***************************************************************************
2     testqgsrubberband.cpp
3      --------------------------------------
4     Date                 : 28.4.2013
5     Copyright            : (C) 2013 Vinayan Parameswaran
6     Email                : vinayan123 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 
17 #include "qgstest.h"
18 #include <QObject>
19 #include <QString>
20 #include <QCoreApplication>
21 #include <QWidget>
22 
23 #include <qgsapplication.h>
24 #include <qgsmapcanvas.h>
25 #include <qgsvectorlayer.h>
26 #include <qgsrubberband.h>
27 #include <qgslogger.h>
28 #include "qgssymbol.h"
29 #include "qgsrenderchecker.h"
30 #include "qgslinesymbol.h"
31 #include "qgsfillsymbol.h"
32 
33 class TestQgsRubberband : public QObject
34 {
35     Q_OBJECT
36   public:
37     TestQgsRubberband() = default;
38 
39   private slots:
40     void initTestCase(); // will be called before the first testfunction is executed.
41     void cleanupTestCase(); // will be called after the last testfunction was executed.
42     void init(); // will be called before each testfunction is executed.
43     void cleanup(); // will be called after every testfunction.
44 
45     void testAddSingleMultiGeometries(); //test for #7728
46     void pointGeometryAddPoints();
47     void pointGeometrySetGeometry();
48     void lineGeometryAddPoints();
49     void copyPointsFrom();
50     void testBoundingRect(); //test for #12392
51     void testVisibility(); //test for 12486
52     void testClose(); //test closing geometry
53     void testLineSymbolRender();
54     void testFillSymbolRender();
55 
56   private:
57     QgsMapCanvas *mCanvas = nullptr;
58     QgsVectorLayer *mPolygonLayer = nullptr;
59     QString mTestDataDir;
60     QgsRubberBand *mRubberband = nullptr;
61     QString mReport;
62 };
63 
initTestCase()64 void TestQgsRubberband::initTestCase()
65 {
66   QgsApplication::init();
67   QgsApplication::initQgis();
68   QgsApplication::showSettings();
69 
70   // Setup a map canvas with a vector layer loaded...
71   const QString myDataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt
72   mTestDataDir = myDataDir + '/';
73 
74   //
75   // load a vector layer
76   //
77   const QString myPolygonFileName = mTestDataDir + "polys.shp";
78   const QFileInfo myPolygonFileInfo( myPolygonFileName );
79   mPolygonLayer = new QgsVectorLayer( myPolygonFileInfo.filePath(),
80                                       myPolygonFileInfo.completeBaseName(), QStringLiteral( "ogr" ) );
81 
82   mCanvas = new QgsMapCanvas();
83   mCanvas->setFrameStyle( QFrame::NoFrame );
84   mCanvas->resize( 512, 512 );
85   mCanvas->show(); // to make the canvas resize
86   mCanvas->hide();
87 
88   mRubberband = nullptr;
89   mReport += QLatin1String( "<h1>Rubberband Tests</h1>\n" );
90 }
91 
cleanupTestCase()92 void TestQgsRubberband::cleanupTestCase()
93 {
94   delete mRubberband;
95   delete mPolygonLayer;
96   delete mCanvas;
97 
98   const QString myReportFile = QDir::tempPath() + "/qgistest.html";
99   QFile myFile( myReportFile );
100   if ( myFile.open( QIODevice::WriteOnly | QIODevice::Append ) )
101   {
102     QTextStream myQTextStream( &myFile );
103     myQTextStream << mReport;
104     myFile.close();
105   }
106 
107   QgsApplication::exitQgis();
108 }
109 
init()110 void TestQgsRubberband::init()
111 {
112 
113 }
114 
cleanup()115 void TestQgsRubberband::cleanup()
116 {
117 
118 }
119 
testAddSingleMultiGeometries()120 void TestQgsRubberband::testAddSingleMultiGeometries()
121 {
122   mRubberband = new QgsRubberBand( mCanvas, mPolygonLayer->geometryType() );
123   const QgsGeometry geomSinglePart( QgsGeometry::fromWkt( QStringLiteral( "POLYGON((-0.00022418 -0.00000279,-0.0001039 0.00002395,-0.00008677 -0.00005313,-0.00020705 -0.00007987,-0.00022418 -0.00000279))" ) ) );
124   const QgsGeometry geomMultiPart( QgsGeometry::fromWkt( QStringLiteral( "MULTIPOLYGON(((-0.00018203 0.00012178,-0.00009444 0.00014125,-0.00007861 0.00007001,-0.00016619 0.00005054,-0.00018203 0.00012178)),((-0.00030957 0.00009464,-0.00021849 0.00011489,-0.00020447 0.00005184,-0.00029555 0.00003158,-0.00030957 0.00009464)))" ) ) );
125 
126   mCanvas->setExtent( QgsRectangle( -1e-3, -1e-3, 1e-3, 1e-3 ) ); // otherwise point cannot be converted to canvas coord
127 
128   mRubberband->addGeometry( geomSinglePart, mPolygonLayer );
129   mRubberband->addGeometry( geomMultiPart, mPolygonLayer );
130   QVERIFY( mRubberband->numberOfVertices() == 15 );
131 }
132 
pointGeometryAddPoints()133 void TestQgsRubberband::pointGeometryAddPoints()
134 {
135   // point geometry
136   std::unique_ptr< QgsMapCanvas > canvas = std::make_unique< QgsMapCanvas >();
137   QgsRubberBand r1( canvas.get(), QgsWkbTypes::PointGeometry );
138   QVERIFY( r1.asGeometry().isEmpty() );
139   r1.addPoint( QgsPointXY( 1, 2 ) );
140   QCOMPARE( r1.asGeometry().asWkt(), QStringLiteral( "MultiPoint ((1 2))" ) );
141   r1.addPoint( QgsPointXY( 2, 3 ) );
142   QCOMPARE( r1.asGeometry().asWkt(), QStringLiteral( "MultiPoint ((1 2),(2 3))" ) );
143   r1.addPoint( QgsPointXY( 3, 4 ) );
144   QCOMPARE( r1.asGeometry().asWkt(), QStringLiteral( "MultiPoint ((1 2),(2 3),(3 4))" ) );
145   r1.reset( QgsWkbTypes::PointGeometry );
146   QVERIFY( r1.asGeometry().isEmpty() );
147   r1.addPoint( QgsPointXY( 1, 2 ) );
148   QCOMPARE( r1.asGeometry().asWkt(), QStringLiteral( "MultiPoint ((1 2))" ) );
149 }
150 
pointGeometrySetGeometry()151 void TestQgsRubberband::pointGeometrySetGeometry()
152 {
153   // point geometry, set using setToGeometry
154   std::unique_ptr< QgsMapCanvas > canvas = std::make_unique< QgsMapCanvas >();
155   QgsRubberBand r1( canvas.get(), QgsWkbTypes::PointGeometry );
156   QVERIFY( r1.asGeometry().isEmpty() );
157   r1.setToGeometry( QgsGeometry::fromPointXY( QgsPointXY( 1, 2 ) ) );
158   QCOMPARE( r1.asGeometry().asWkt(), QStringLiteral( "MultiPoint ((1 2))" ) );
159   r1.setToGeometry( QgsGeometry::fromPointXY( QgsPointXY( 2, 3 ) ) );
160   QCOMPARE( r1.asGeometry().asWkt(), QStringLiteral( "MultiPoint ((2 3))" ) );
161   r1.addGeometry( QgsGeometry::fromPointXY( QgsPointXY( 5, 6 ) ) );
162   QCOMPARE( r1.asGeometry().asWkt(), QStringLiteral( "MultiPoint ((2 3),(5 6))" ) );
163   r1.setToGeometry( QgsGeometry::fromMultiPointXY( {QgsPointXY( 1, 2 ), QgsPointXY( 3, 4 ) } ) );
164   QCOMPARE( r1.asGeometry().asWkt(), QStringLiteral( "MultiPoint ((1 2),(3 4))" ) );
165   r1.addGeometry( QgsGeometry::fromPointXY( QgsPointXY( 5, 7 ) ) );
166   QCOMPARE( r1.asGeometry().asWkt(), QStringLiteral( "MultiPoint ((1 2),(3 4),(5 7))" ) );
167   r1.addGeometry( QgsGeometry::fromMultiPointXY( { QgsPointXY( 7, 8 ), QgsPointXY( 9, 10 )} ) );
168   QCOMPARE( r1.asGeometry().asWkt(), QStringLiteral( "MultiPoint ((1 2),(3 4),(5 7),(7 8),(9 10))" ) );
169   r1.reset( QgsWkbTypes::PointGeometry );
170   r1.addGeometry( QgsGeometry::fromMultiPointXY( { QgsPointXY( 7, 8 ), QgsPointXY( 9, 10 )} ) );
171   QCOMPARE( r1.asGeometry().asWkt(), QStringLiteral( "MultiPoint ((7 8),(9 10))" ) );
172 }
173 
lineGeometryAddPoints()174 void TestQgsRubberband::lineGeometryAddPoints()
175 {
176   std::unique_ptr< QgsMapCanvas > canvas = std::make_unique< QgsMapCanvas >();
177   QgsRubberBand r1( canvas.get(), QgsWkbTypes::LineGeometry );
178   QVERIFY( r1.asGeometry().isEmpty() );
179   r1.addPoint( QgsPointXY( 1, 2 ) );
180   QCOMPARE( r1.asGeometry().asWkt(), QStringLiteral( "LineString (1 2, 1 2)" ) );
181   r1.addPoint( QgsPointXY( 2, 3 ) );
182   QCOMPARE( r1.asGeometry().asWkt(), QStringLiteral( "LineString (1 2, 2 3)" ) );
183   r1.addPoint( QgsPointXY( 3, 4 ) );
184   QCOMPARE( r1.asGeometry().asWkt(), QStringLiteral( "LineString (1 2, 2 3, 3 4)" ) );
185   r1.reset( QgsWkbTypes::LineGeometry );
186   QVERIFY( r1.asGeometry().isEmpty() );
187   r1.addPoint( QgsPointXY( 1, 2 ) );
188   QCOMPARE( r1.asGeometry().asWkt(), QStringLiteral( "LineString (1 2, 1 2)" ) );
189 }
190 
copyPointsFrom()191 void TestQgsRubberband::copyPointsFrom()
192 {
193   std::unique_ptr< QgsMapCanvas > canvas = std::make_unique< QgsMapCanvas >();
194   QgsRubberBand r1( canvas.get(), QgsWkbTypes::PointGeometry );
195   r1.addPoint( QgsPointXY( 1, 2 ) );
196   r1.addPoint( QgsPointXY( 3, 4 ) );
197   QCOMPARE( r1.asGeometry().asWkt(), QStringLiteral( "MultiPoint ((1 2),(3 4))" ) );
198 
199   QgsRubberBand r2( canvas.get(), QgsWkbTypes::LineGeometry );
200   r2.copyPointsFrom( &r1 );
201   QCOMPARE( r2.asGeometry().asWkt(), QStringLiteral( "MultiPoint ((1 2),(3 4))" ) );
202 
203   // line geometry band
204   r1.reset( QgsWkbTypes::LineGeometry );
205   r1.addPoint( QgsPointXY( 1, 2 ) );
206   r1.addPoint( QgsPointXY( 2, 3 ) );
207   r1.addPoint( QgsPointXY( 3, 4 ) );
208   QCOMPARE( r1.asGeometry().asWkt(), QStringLiteral( "LineString (1 2, 2 3, 3 4)" ) );
209 
210   r2.copyPointsFrom( &r1 );
211   QCOMPARE( r2.asGeometry().asWkt(), QStringLiteral( "LineString (1 2, 2 3, 3 4)" ) );
212 }
213 
testBoundingRect()214 void TestQgsRubberband::testBoundingRect()
215 {
216   // Set extent to match canvas size.
217   // This is to ensure a 1:1 scale
218   mCanvas->setExtent( QgsRectangle( 0, 0, 512, 512 ) );
219   QCOMPARE( mCanvas->mapUnitsPerPixel(), 1.0 );
220 
221   // Polygon extent is 10,10 to 30,30
222   const QgsGeometry geom( QgsGeometry::fromWkt(
223                             QStringLiteral( "POLYGON((10 10,10 30,30 30,30 10,10 10))" )
224                           ) );
225   mRubberband = new QgsRubberBand( mCanvas, mPolygonLayer->geometryType() );
226   mRubberband->setIconSize( 5 ); // default, but better be explicit
227   mRubberband->setWidth( 1 );    // default, but better be explicit
228   mRubberband->addGeometry( geom, mPolygonLayer );
229 
230   // 20 pixels for the extent + 3 for pen & icon per side + 2 of extra padding from setRect()
231   QCOMPARE( mRubberband->boundingRect(), QRectF( QPointF( -1, -1 ), QSizeF( 28, 28 ) ) );
232   QCOMPARE( mRubberband->pos(), QPointF(
233               // 10 for extent minx - 3 for pen & icon
234               10 - 3,
235               // 30 for extent maxy - 3 for pen & icon
236               512 - 30 - 3
237             ) );
238 
239   mCanvas->setExtent( QgsRectangle( 0, 0, 256, 256 ) );
240 
241   // 40 pixels for the extent + 3 for pen & icon per side + 2 of extra padding from setRect()
242   QCOMPARE( mRubberband->boundingRect(), QRectF( QPointF( -1, -1 ), QSizeF( 48, 48 ) ) );
243   QCOMPARE( mRubberband->pos(), QPointF(
244               // 10 for extent minx - 3 for pen & icon
245               10 * 2 - 3,
246               // 30 for extent maxy - 3 for pen & icon
247               512 - 30 * 2 - 3
248             ) );
249 
250 }
251 
testVisibility()252 void TestQgsRubberband::testVisibility()
253 {
254   mRubberband = new QgsRubberBand( mCanvas, mPolygonLayer->geometryType() );
255 
256   // Visibility is set to false by default
257   QCOMPARE( mRubberband->isVisible(), false );
258 
259   // Check visibility after setting to empty geometry
260   const std::shared_ptr<QgsGeometry> emptyGeom( new QgsGeometry );
261   mRubberband->setToGeometry( *emptyGeom.get(), mPolygonLayer );
262   QCOMPARE( mRubberband->isVisible(), false );
263 
264   // Check that visibility changes
265   mRubberband->setVisible( true );
266   mRubberband->setToGeometry( *emptyGeom.get(), mPolygonLayer );
267   QCOMPARE( mRubberband->isVisible(), false );
268 
269   // Check visibility after setting to valid geometry
270   const QgsGeometry geom( QgsGeometry::fromWkt(
271                             QStringLiteral( "POLYGON((10 10,10 30,30 30,30 10,10 10))" )
272                           ) );
273   mRubberband->setToGeometry( geom, mPolygonLayer );
274   QCOMPARE( mRubberband->isVisible(), true );
275 
276   // Add point without update
277   mRubberband->reset( QgsWkbTypes::PolygonGeometry );
278   mRubberband->addPoint( QgsPointXY( 10, 10 ), false );
279   QCOMPARE( mRubberband->isVisible(), false );
280 
281   // Add point with update
282   mRubberband->addPoint( QgsPointXY( 20, 20 ), true );
283   QCOMPARE( mRubberband->isVisible(), true );
284 
285   // Check visibility after zoom (should not be changed)
286   mRubberband->setVisible( false );
287   mCanvas->zoomIn();
288   QCOMPARE( mRubberband->isVisible(), false );
289 
290 }
291 
testClose()292 void TestQgsRubberband::testClose()
293 {
294   QgsRubberBand r( mCanvas, QgsWkbTypes::PolygonGeometry );
295 
296   // try closing empty rubber band, don't want to crash
297   r.closePoints();
298   QCOMPARE( r.partSize( 0 ), 0 );
299 
300   r.addPoint( QgsPointXY( 1, 2 ) );
301   r.addPoint( QgsPointXY( 1, 3 ) );
302   r.addPoint( QgsPointXY( 2, 3 ) );
303   QCOMPARE( r.partSize( 0 ), 3 );
304 
305   // test with some bad geometry indexes - don't want to crash!
306   r.closePoints( true, -1 );
307   QCOMPARE( r.partSize( 0 ), 3 );
308   r.closePoints( true, 100 );
309   QCOMPARE( r.partSize( 0 ), 3 );
310 
311   // valid close
312   r.closePoints();
313   QCOMPARE( r.partSize( 0 ), 4 );
314 
315   // close already closed polygon, should be no change
316   r.closePoints();
317   QCOMPARE( r.partSize( 0 ), 4 );
318 }
319 
testLineSymbolRender()320 void TestQgsRubberband::testLineSymbolRender()
321 {
322   std::unique_ptr< QgsMapCanvas > canvas = std::make_unique< QgsMapCanvas >();
323   canvas->setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
324   canvas->setFrameStyle( 0 );
325   canvas->resize( 600, 400 );
326   canvas->setExtent( QgsRectangle( 10, 30, 20, 35 ) );
327   canvas->show();
328 
329   QgsRubberBand r( canvas.get(), QgsWkbTypes::LineGeometry );
330   r.addGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString( 12 32, 18 33)" ) ) );
331 
332   std::unique_ptr< QgsLineSymbol > lineSymbol( QgsLineSymbol::createSimple(
333   {
334     { QStringLiteral( "line_color" ), QStringLiteral( "#0000ff" ) },
335     { QStringLiteral( "line_width" ), QStringLiteral( "3" )},
336     { QStringLiteral( "capstyle" ), QStringLiteral( "round" )}
337   } ) );
338   r.setSymbol( lineSymbol.release() );
339 
340   QPixmap pixmap( canvas->size() );
341   QPainter painter( &pixmap );
342   canvas->render( &painter );
343   painter.end();
344   const QString destFile = QDir::tempPath() + QStringLiteral( "/rubberband_line_symbol.png" );
345   pixmap.save( destFile );
346 
347   QgsRenderChecker checker;
348   checker.setControlPathPrefix( QStringLiteral( "rubberband" ) );
349   checker.setControlName( QStringLiteral( "expected_line_symbol" ) );
350   checker.setRenderedImage( destFile );
351   const bool result = checker.compareImages( QStringLiteral( "expected_line_symbol" ) );
352   mReport += checker.report();
353   QVERIFY( result );
354 }
355 
testFillSymbolRender()356 void TestQgsRubberband::testFillSymbolRender()
357 {
358   std::unique_ptr< QgsMapCanvas > canvas = std::make_unique< QgsMapCanvas >();
359   canvas->setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
360   canvas->setFrameStyle( 0 );
361   canvas->resize( 600, 400 );
362   canvas->setExtent( QgsRectangle( 10, 30, 20, 35 ) );
363   canvas->show();
364 
365   QgsRubberBand r( canvas.get(), QgsWkbTypes::LineGeometry );
366   r.addGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon((12 32, 12 35, 18 35, 12 32))" ) ) );
367 
368   std::unique_ptr< QgsFillSymbol > fillSymbol( QgsFillSymbol::createSimple(
369   {
370     { QStringLiteral( "color" ), QStringLiteral( "#ff00ff" ) },
371     { QStringLiteral( "line_color" ), QStringLiteral( "#0000ff" ) },
372     { QStringLiteral( "line_width" ), QStringLiteral( "3" )},
373     { QStringLiteral( "joinstyle" ), QStringLiteral( "round" )}
374   } ) );
375   r.setSymbol( fillSymbol.release() );
376 
377   QPixmap pixmap( canvas->size() );
378   QPainter painter( &pixmap );
379   canvas->render( &painter );
380   painter.end();
381   const QString destFile = QDir::tempPath() + QStringLiteral( "/rubberband_fill_symbol.png" );
382   pixmap.save( destFile );
383 
384   QgsRenderChecker checker;
385   checker.setControlPathPrefix( QStringLiteral( "rubberband" ) );
386   checker.setControlName( QStringLiteral( "expected_fill_symbol" ) );
387   checker.setRenderedImage( destFile );
388   const bool result = checker.compareImages( QStringLiteral( "expected_fill_symbol" ) );
389   mReport += checker.report();
390   QVERIFY( result );
391 }
392 
393 
394 QGSTEST_MAIN( TestQgsRubberband )
395 #include "testqgsrubberband.moc"
396 
397 
398