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