1 /***************************************************************************
2      TestQgsMapToolTrimExtendFeature.cpp
3      --------------------------------
4     Date                 : October 2018
5     Copyright            : (C) 2018 by Loïc Bartoletti
6     Email                : loic dot bartoletti at oslandia 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 "qgstest.h"
17 #include <QObject>
18 #include <QString>
19 
20 #include "qgsapplication.h"
21 #include "qgsvectorlayer.h"
22 #include "qgsvectordataprovider.h"
23 #include "qgsgeometry.h"
24 #include "qgsproject.h"
25 #include "qgssnappingutils.h"
26 #include "qgssnappingconfig.h"
27 #include "qgscategorizedsymbolrenderer.h"
28 #include "qgssettings.h"
29 #include "qgsmapcanvas.h"
30 #include "qgsmapmouseevent.h"
31 #include "qgsmapcanvassnappingutils.h"
32 #include "qgisapp.h"
33 
34 #include "qgsmaptooltrimextendfeature.h"
35 
36 
37 class TestQgsMapToolTrimExtendFeature : public QObject
38 {
39     Q_OBJECT
40 
41   public:
42     TestQgsMapToolTrimExtendFeature() = default;
43 
44   private:
45     std::unique_ptr<QgsVectorLayer> vlPolygon, vlMultiLine, vlLineZ, vlTopoEdit, vlTopoLimit;
46     QgsFeature f1, f2;
47     QgsMapCanvas *mCanvas = nullptr;
48 
49   private slots:
50 
initTestCase()51     void initTestCase()
52     {
53       QgsApplication::init();
54       QgsApplication::initQgis();
55       // Will make sure the settings dir with the style file for color ramp is created
56       QgsApplication::createDatabase();
57       QgsApplication::showSettings();
58 
59 
60       // vector layer with a triangle in a rectangle:
61       // (0,3) +-------------------+ (3,3)
62       //       | (1,2) +---+ (2,2) |
63       //       |        \  |       |
64       //       |         \ |       |
65       //       |          \|       |
66       //       |           + (2,1) |
67       // (0,0) +-------------------+ (3,0)
68       vlPolygon.reset( new QgsVectorLayer( QStringLiteral( "MultiPolygon?field=fld:int" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) ) );
69       const int idx = vlPolygon->fields().indexFromName( QStringLiteral( "fld" ) );
70       QVERIFY( idx != -1 );
71       f1.initAttributes( 1 );
72       f2.initAttributes( 1 );
73 
74       QgsPolygonXY polygon;
75       QgsPolylineXY polyline;
76       polyline << QgsPointXY( 1, 2 ) << QgsPointXY( 2, 1 ) << QgsPointXY( 2, 2 ) << QgsPointXY( 1, 2 );
77       polygon << polyline;
78       const QgsGeometry polygonGeom = QgsGeometry::fromPolygonXY( polygon );
79       f1.setGeometry( polygonGeom );
80       f1.setAttribute( idx, QVariant( 1 ) );
81       QgsFeatureList flist;
82       flist << f1;
83       vlPolygon->dataProvider()->addFeatures( flist );
84 
85       QgsPolygonXY polygon2;
86       QgsPolylineXY polyline2;
87       polyline2 << QgsPointXY( 0, 0 ) << QgsPointXY( 3, 0 ) << QgsPointXY( 3, 3 ) << QgsPointXY( 0, 3 ) << QgsPointXY( 0, 0 );
88       polygon2 << polyline2;
89       const QgsGeometry polygonGeom2 = QgsGeometry::fromPolygonXY( polygon2 );
90       f2.setGeometry( polygonGeom2 );
91       f2.setAttribute( idx, QVariant( 2 ) );
92       QgsFeatureList flist2;
93       flist2 << f2;
94       vlPolygon->dataProvider()->addFeatures( flist2 );
95 
96 
97       /*
98        *      |
99        *  |   |
100        *      |
101        * -----|
102        *      |
103        */
104 
105       vlMultiLine.reset( new QgsVectorLayer( QStringLiteral( "MultiLineString?crs=EPSG:3946&field=pk:int" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
106       QVERIFY( vlMultiLine->isValid() );
107       QgsFeature multi( vlMultiLine->dataProvider()->fields(), 1 );
108       multi.setAttribute( QStringLiteral( "pk" ), 1 );
109       multi.setGeometry( QgsGeometry::fromWkt( QStringLiteral(
110                            "MultiLineString ((10 0, 14 0),(11 1, 11 0.5),(14 -2, 14 2))" ) ) );
111 
112       vlMultiLine->dataProvider()->addFeatures( QgsFeatureList() << multi );
113 
114 
115       /*     (3 8 200)
116              /
117             /  (2 6 10)
118            /  \
119       (0 5 100)      (3 5 5)
120       */
121       vlLineZ.reset( new QgsVectorLayer( QStringLiteral( "LineStringZ?crs=EPSG:3946&field=pk:int" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
122       QVERIFY( vlLineZ->isValid() );
123       QgsFeature linez( vlLineZ->dataProvider()->fields(), 1 );
124       linez.setAttribute( QStringLiteral( "pk" ), 1 );
125       linez.setGeometry( QgsGeometry::fromWkt( QStringLiteral(
126                            "LineStringZ (3 5 5, 2 6 10)" ) ) );
127       QgsFeature linez2( vlLineZ->dataProvider()->fields(), 2 );
128       linez2.setAttribute( QStringLiteral( "pk" ), 2 );
129       linez2.setGeometry( QgsGeometry::fromWkt( QStringLiteral(
130                             "LineStringZ (0 5 100, 3 8 200)" ) ) );
131 
132       vlLineZ->dataProvider()->addFeatures( QgsFeatureList() << linez << linez2 );
133 
134 
135       /*
136        *       |
137        * ----  |
138        *       |
139        *
140        */
141       vlTopoEdit.reset( new QgsVectorLayer( QStringLiteral( "LineString?crs=EPSG:3946&field=pk:int" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
142       QVERIFY( vlTopoEdit->isValid() );
143       vlTopoLimit.reset( new QgsVectorLayer( QStringLiteral( "LineString?crs=EPSG:3946&field=pk:int" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
144       QVERIFY( vlTopoLimit->isValid() );
145       QgsFeature lineEdit( vlTopoEdit->dataProvider()->fields(), 1 );
146       lineEdit.setAttribute( QStringLiteral( "pk" ), 1 );
147       lineEdit.setGeometry( QgsGeometry::fromWkt( QStringLiteral( " LineString (20 15, 25 15) " ) ) );
148       QgsFeature lineLimit( vlTopoLimit->dataProvider()->fields(), 1 );
149       lineLimit.setAttribute( QStringLiteral( "pk" ), 1 );
150       lineLimit.setGeometry( QgsGeometry::fromWkt( QStringLiteral( " LineString (30 0, 30 30) " ) ) );
151 
152       vlTopoEdit->dataProvider()->addFeatures( QgsFeatureList() << lineEdit );
153       vlTopoLimit->dataProvider()->addFeatures( QgsFeatureList() << lineLimit );
154 
155 
156       mCanvas = new QgsMapCanvas();
157       mCanvas->setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3946" ) ) );
158       mCanvas->setLayers( QList<QgsMapLayer *>() << vlPolygon.get() << vlMultiLine.get() << vlLineZ.get() << vlTopoEdit.get() << vlTopoLimit.get() );
159 
160       QgsMapSettings mapSettings;
161       mapSettings.setOutputSize( QSize( 50, 50 ) );
162       mapSettings.setExtent( QgsRectangle( -1, -1, 4, 4 ) );
163       QVERIFY( mapSettings.hasValidSettings() );
164 
165       mapSettings.setLayers( QList<QgsMapLayer *>() << vlPolygon.get() << vlMultiLine.get() << vlLineZ.get() << vlTopoEdit.get() << vlTopoLimit.get() );
166 
167       QgsSnappingUtils *mSnappingUtils = new QgsMapCanvasSnappingUtils( mCanvas, this );
168       QgsSnappingConfig snappingConfig = mSnappingUtils->config();
169       mSnappingUtils->setMapSettings( mapSettings );
170       snappingConfig.setEnabled( true );
171       snappingConfig.setTolerance( 100 );
172       snappingConfig.setTypeFlag( static_cast<QgsSnappingConfig::SnappingTypeFlag>( QgsSnappingConfig::VertexFlag | QgsSnappingConfig::SegmentFlag ) );
173       snappingConfig.setUnits( QgsTolerance::Pixels );
174       snappingConfig.setMode( QgsSnappingConfig::AllLayers );
175       mSnappingUtils->setConfig( snappingConfig );
176 
177       mSnappingUtils->locatorForLayer( vlPolygon.get() )->init();
178       mSnappingUtils->locatorForLayer( vlMultiLine.get() )->init();
179       mSnappingUtils->locatorForLayer( vlLineZ.get() )->init();
180       mSnappingUtils->locatorForLayer( vlTopoEdit.get() )->init();
181       mSnappingUtils->locatorForLayer( vlTopoLimit.get() )->init();
182 
183       mCanvas->setSnappingUtils( mSnappingUtils );
184     }
185 
cleanupTestCase()186     void cleanupTestCase()
187     {
188       QgsApplication::exitQgis();
189     }
190 
191 
192 
testPolygon()193     void testPolygon()
194     {
195 
196       // vector layer with a triangle in a rectangle:
197       // (0,3) +-------------------+ (3,3)
198       //       | (1,2) +---+ (2,2) |
199       //       |        \  |       |
200       //       |         \ |       |
201       //       |          \|       |
202       //       |           + (2,1) |
203       // (0,0) +-------------------+ (3,0)
204       mCanvas->setCurrentLayer( vlPolygon.get() );
205       std::unique_ptr< QgsMapToolTrimExtendFeature > tool( new QgsMapToolTrimExtendFeature( mCanvas ) );
206 
207       vlPolygon->startEditing();
208       // Limit
209       QgsPointXY pt;
210       pt = tool->canvas()->mapSettings().mapToPixel().transform( 0, 0 );
211       std::unique_ptr< QgsMapMouseEvent > event( new QgsMapMouseEvent(
212             mCanvas,
213             QEvent::MouseMove,
214             QPoint( std::round( pt.x() ), std::round( pt.y() ) )
215           ) );
216       tool->canvasMoveEvent( event.get() );
217       event.reset( new QgsMapMouseEvent(
218                      mCanvas,
219                      QEvent::MouseButtonRelease,
220                      QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
221                      Qt::LeftButton
222                    ) );
223       tool->canvasReleaseEvent( event.get() );
224 
225       // Extend
226       pt = tool->canvas()->mapSettings().mapToPixel().transform( 1, 1.5 );
227       event.reset( new QgsMapMouseEvent(
228                      mCanvas,
229                      QEvent::MouseMove,
230                      QPoint( std::round( pt.x() ), std::round( pt.y() ) )
231                    ) );
232       tool->canvasMoveEvent( event.get() );
233       event.reset( new QgsMapMouseEvent(
234                      mCanvas,
235                      QEvent::MouseButtonRelease,
236                      QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
237                      Qt::LeftButton
238                    ) );
239       tool->canvasReleaseEvent( event.get() );
240 
241 
242       // vector layer with a trianglev in a rectangle:
243       // (0,3) +-------------------+ (3,3)
244       //       | (1,2) +---+ (2,2) |
245       //       |        \   \      |
246       //       |           \  \    |
247       //       |               \ \ |
248       //       |                  \|
249       // (0,0) +-------------------+ (3,0)
250       const QgsFeature f = vlPolygon->getFeature( 1 );
251 
252       const QString wkt = "Polygon ((1 2, 3 0, 2 2, 1 2))";
253       QCOMPARE( f.geometry().asWkt(), wkt );
254 
255       vlPolygon->rollBack();
256     }
257 
258 
testMultiLine()259     void testMultiLine()
260     {
261       /*
262        *      |
263        *  |   |
264        *      |
265        * -----|
266        *      |
267        */
268       mCanvas->setCurrentLayer( vlMultiLine.get() );
269       std::unique_ptr< QgsMapToolTrimExtendFeature > tool( new QgsMapToolTrimExtendFeature( mCanvas ) );
270 
271       vlMultiLine->startEditing();
272       // Limit
273       QgsPointXY pt;
274       pt = tool->canvas()->mapSettings().mapToPixel().transform( 12, 0 );
275       std::unique_ptr< QgsMapMouseEvent > event( new QgsMapMouseEvent(
276             mCanvas,
277             QEvent::MouseMove,
278             QPoint( std::round( pt.x() ), std::round( pt.y() ) )
279           ) );
280       tool->canvasMoveEvent( event.get() );
281       event.reset( new QgsMapMouseEvent(
282                      mCanvas,
283                      QEvent::MouseButtonRelease,
284                      QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
285                      Qt::LeftButton
286                    ) );
287       tool->canvasReleaseEvent( event.get() );
288 
289       // Extend
290       pt = tool->canvas()->mapSettings().mapToPixel().transform( 11, 0.8 );
291       event.reset( new QgsMapMouseEvent(
292                      mCanvas,
293                      QEvent::MouseMove,
294                      QPoint( std::round( pt.x() ), std::round( pt.y() ) )
295                    ) );
296       tool->canvasMoveEvent( event.get() );
297       event.reset( new QgsMapMouseEvent(
298                      mCanvas,
299                      QEvent::MouseButtonRelease,
300                      QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
301                      Qt::LeftButton
302                    ) );
303       tool->canvasReleaseEvent( event.get() );
304 
305       /*
306        *(11 1)|
307        *  |   |
308        *  |   |
309        * -----|
310        *(11 0)|
311        */
312       QgsFeature f = vlMultiLine->getFeature( 1 );
313 
314       QString wkt = "MultiLineString ((10 0, 14 0),(11 1, 11 0),(14 -2, 14 2))";
315       QCOMPARE( f.geometry().asWkt(), wkt );
316 
317 
318       // Limit
319       pt = tool->canvas()->mapSettings().mapToPixel().transform( 12, 0 );
320       event.reset( new QgsMapMouseEvent(
321                      mCanvas,
322                      QEvent::MouseMove,
323                      QPoint( std::round( pt.x() ), std::round( pt.y() ) )
324                    ) );
325       tool->canvasMoveEvent( event.get() );
326       event.reset( new QgsMapMouseEvent(
327                      mCanvas,
328                      QEvent::MouseButtonRelease,
329                      QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
330                      Qt::LeftButton
331                    ) );
332       tool->canvasReleaseEvent( event.get() );
333 
334       // Extend
335       pt = tool->canvas()->mapSettings().mapToPixel().transform( 14, 1 );
336 
337       event.reset( new QgsMapMouseEvent(
338                      mCanvas,
339                      QEvent::MouseMove,
340                      QPoint( std::round( pt.x() ), std::round( pt.y() ) )
341                    ) );
342       tool->canvasMoveEvent( event.get() );
343       event.reset( new QgsMapMouseEvent(
344                      mCanvas,
345                      QEvent::MouseButtonRelease,
346                      QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
347                      Qt::LeftButton
348                    ) );
349       tool->canvasReleaseEvent( event.get() );
350 
351       /*
352        *
353        *  |
354        *  |
355        * ------ (14 0)
356        *      |
357        *        (14 -2)
358        */
359       f = vlMultiLine->getFeature( 1 );
360       wkt = "MultiLineString ((10 0, 14 0),(11 1, 11 0),(14 -2, 14 0))";
361       QCOMPARE( f.geometry().asWkt(), wkt );
362 
363       vlMultiLine->rollBack();
364 
365 
366       vlMultiLine->startEditing();
367       // Limit
368       pt = tool->canvas()->mapSettings().mapToPixel().transform( 12, 0 );
369       event.reset( new QgsMapMouseEvent(
370                      mCanvas,
371                      QEvent::MouseMove,
372                      QPoint( std::round( pt.x() ), std::round( pt.y() ) )
373                    ) );
374       tool->canvasMoveEvent( event.get() );
375       event.reset( new QgsMapMouseEvent(
376                      mCanvas,
377                      QEvent::MouseButtonRelease,
378                      QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
379                      Qt::LeftButton
380                    ) );
381       tool->canvasReleaseEvent( event.get() );
382 
383       // Extend
384       pt = tool->canvas()->mapSettings().mapToPixel().transform( 14, -1 );
385       event.reset( new QgsMapMouseEvent(
386                      mCanvas,
387                      QEvent::MouseMove,
388                      QPoint( std::round( pt.x() ), std::round( pt.y() ) )
389                    ) );
390       tool->canvasMoveEvent( event.get() );
391       event.reset( new QgsMapMouseEvent(
392                      mCanvas,
393                      QEvent::MouseButtonRelease,
394                      QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
395                      Qt::LeftButton
396                    ) );
397       tool->canvasReleaseEvent( event.get() );
398 
399       /*
400        *      | (14 2)
401        *  |   |
402        *      |
403        * ------ (14 0)
404        *(10 0)
405        */
406       f = vlMultiLine->getFeature( 1 );
407 
408       wkt = "MultiLineString ((10 0, 14 0),(11 1, 11 0.5),(14 0, 14 2))";
409       QCOMPARE( f.geometry().asWkt(), wkt );
410     }
411 
testLineZ()412     void testLineZ()
413     {
414 
415       /*     (3 8 200)
416              /
417             /  (2 6 10)
418            /  \
419       (0 5 100)      (3 5 5)
420       */
421       mCanvas->setCurrentLayer( vlLineZ.get() );
422       std::unique_ptr< QgsMapToolTrimExtendFeature > tool( new QgsMapToolTrimExtendFeature( mCanvas ) );
423 
424       vlLineZ->startEditing();
425       // Limit
426       QgsPointXY pt;
427       pt = tool->canvas()->mapSettings().mapToPixel().transform( 0, 5 );
428       std::unique_ptr< QgsMapMouseEvent > event( new QgsMapMouseEvent(
429             mCanvas,
430             QEvent::MouseMove,
431             QPoint( std::round( pt.x() ), std::round( pt.y() ) )
432           ) );
433       tool->canvasMoveEvent( event.get() );
434       event.reset( new QgsMapMouseEvent(
435                      mCanvas,
436                      QEvent::MouseButtonRelease,
437                      QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
438                      Qt::LeftButton
439                    ) );
440       tool->canvasReleaseEvent( event.get() );
441 
442       // Extend
443       pt = tool->canvas()->mapSettings().mapToPixel().transform( 3, 5 );
444       event.reset( new QgsMapMouseEvent(
445                      mCanvas,
446                      QEvent::MouseMove,
447                      QPoint( std::round( pt.x() ), std::round( pt.y() ) )
448                    ) );
449       tool->canvasMoveEvent( event.get() );
450       event.reset( new QgsMapMouseEvent(
451                      mCanvas,
452                      QEvent::MouseButtonRelease,
453                      QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
454                      Qt::LeftButton
455                    ) );
456       tool->canvasReleaseEvent( event.get() );
457 
458 
459 
460       /*     (3 8 200)
461              /
462             /\ (1.5 6.5 150)
463            /  \
464       (0 5 100)      (3 5 5)
465       */
466       const QgsFeature f = vlLineZ->getFeature( 1 );
467 
468       const QString wkt = "LineStringZ (3 5 5, 1.5 6.5 150)";
469       QCOMPARE( f.geometry().asWkt(), wkt );
470 
471       vlLineZ->rollBack();
472     }
473 
testTopologicalPoints()474     void testTopologicalPoints()
475     {
476       const bool topologicalEditing = QgsProject::instance()->topologicalEditing();
477       QgsProject::instance()->setTopologicalEditing( true );
478 
479       mCanvas->setCurrentLayer( vlTopoEdit.get() );
480       std::unique_ptr< QgsMapToolTrimExtendFeature > tool( new QgsMapToolTrimExtendFeature( mCanvas ) );
481 
482       vlTopoLimit->startEditing();
483       vlTopoEdit->startEditing();
484       // Limit
485       QgsPointXY pt;
486       pt = tool->canvas()->mapSettings().mapToPixel().transform( 30, 15 );
487       std::unique_ptr< QgsMapMouseEvent > event( new QgsMapMouseEvent(
488             mCanvas,
489             QEvent::MouseMove,
490             QPoint( std::round( pt.x() ), std::round( pt.y() ) )
491           ) );
492       tool->canvasMoveEvent( event.get() );
493       event.reset( new QgsMapMouseEvent(
494                      mCanvas,
495                      QEvent::MouseButtonRelease,
496                      QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
497                      Qt::LeftButton
498                    ) );
499       tool->canvasReleaseEvent( event.get() );
500 
501       // Extend
502       pt = tool->canvas()->mapSettings().mapToPixel().transform( 22, 15 );
503       event.reset( new QgsMapMouseEvent(
504                      mCanvas,
505                      QEvent::MouseMove,
506                      QPoint( std::round( pt.x() ), std::round( pt.y() ) )
507                    ) );
508       tool->canvasMoveEvent( event.get() );
509       event.reset( new QgsMapMouseEvent(
510                      mCanvas,
511                      QEvent::MouseButtonRelease,
512                      QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
513                      Qt::LeftButton
514                    ) );
515       tool->canvasReleaseEvent( event.get() );
516 
517       const QgsFeature fEdit = vlTopoEdit->getFeature( 1 );
518       const QgsFeature fLimit = vlTopoLimit->getFeature( 1 );
519 
520       const QString wktEdit = "LineString (20 15, 30 15)";
521       const QString wktLimit = "LineString (30 0, 30 15, 30 30)";
522       QCOMPARE( fEdit.geometry().asWkt(), wktEdit );
523       QCOMPARE( fLimit.geometry().asWkt(), wktLimit );
524 
525       vlTopoEdit->rollBack();
526 
527       QgsProject::instance()->setTopologicalEditing( topologicalEditing );
528     }
529 
530 };
531 
532 QGSTEST_MAIN( TestQgsMapToolTrimExtendFeature )
533 #include "testqgsmaptooltrimextendfeature.moc"
534