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