mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-27 00:33:48 -05:00
Merge pull request #5780 from pblottiere/bugfix_reshape2
[bugfix] Do not add binding line in both side in reshape map tool
This commit is contained in:
commit
14e6df450a
@ -151,7 +151,7 @@ Removes the last vertex from mRubberBand and mCaptureList
|
||||
:rtype: int
|
||||
%End
|
||||
|
||||
QVector<QgsPointXY> points();
|
||||
QVector<QgsPointXY> points() const;
|
||||
%Docstring
|
||||
List of digitized points
|
||||
:return: List of points
|
||||
|
@ -74,74 +74,116 @@ void QgsMapToolReshape::cadCanvasReleaseEvent( QgsMapMouseEvent *e )
|
||||
stopCapturing();
|
||||
return;
|
||||
}
|
||||
QgsPointXY firstPoint = points().at( 0 );
|
||||
QgsRectangle bbox( firstPoint.x(), firstPoint.y(), firstPoint.x(), firstPoint.y() );
|
||||
for ( int i = 1; i < size(); ++i )
|
||||
{
|
||||
bbox.combineExtentWith( points().at( i ).x(), points().at( i ).y() );
|
||||
}
|
||||
|
||||
QgsLineString reshapeLineString( points() );
|
||||
if ( QgsWkbTypes::hasZ( vlayer->wkbType() ) )
|
||||
reshapeLineString.addZValue( defaultZValue() );
|
||||
|
||||
//query all the features that intersect bounding box of capture line
|
||||
QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( bbox ).setSubsetOfAttributes( QgsAttributeList() ) );
|
||||
QgsFeature f;
|
||||
int reshapeReturn;
|
||||
bool reshapeDone = false;
|
||||
|
||||
vlayer->beginEditCommand( tr( "Reshape" ) );
|
||||
while ( fit.nextFeature( f ) )
|
||||
{
|
||||
//query geometry
|
||||
//call geometry->reshape(mCaptureList)
|
||||
//register changed geometry in vector layer
|
||||
QgsGeometry geom = f.geometry();
|
||||
if ( !geom.isNull() )
|
||||
{
|
||||
reshapeReturn = geom.reshapeGeometry( reshapeLineString );
|
||||
if ( reshapeReturn == 0 )
|
||||
{
|
||||
//avoid intersections on polygon layers
|
||||
if ( vlayer->geometryType() == QgsWkbTypes::PolygonGeometry )
|
||||
{
|
||||
//ignore all current layer features as they should be reshaped too
|
||||
QHash<QgsVectorLayer *, QSet<QgsFeatureId> > ignoreFeatures;
|
||||
ignoreFeatures.insert( vlayer, vlayer->allFeatureIds() );
|
||||
|
||||
if ( geom.avoidIntersections( QgsProject::instance()->avoidIntersectionsLayers(), ignoreFeatures ) != 0 )
|
||||
{
|
||||
emit messageEmitted( tr( "An error was reported during intersection removal" ), QgsMessageBar::CRITICAL );
|
||||
vlayer->destroyEditCommand();
|
||||
stopCapturing();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( geom.isEmpty() ) //intersection removal might have removed the whole geometry
|
||||
{
|
||||
emit messageEmitted( tr( "The feature cannot be reshaped because the resulting geometry is empty" ), QgsMessageBar::CRITICAL );
|
||||
vlayer->destroyEditCommand();
|
||||
stopCapturing();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
vlayer->changeGeometry( f.id(), geom );
|
||||
reshapeDone = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( reshapeDone )
|
||||
{
|
||||
vlayer->endEditCommand();
|
||||
}
|
||||
else
|
||||
{
|
||||
vlayer->destroyEditCommand();
|
||||
}
|
||||
reshape( vlayer );
|
||||
|
||||
stopCapturing();
|
||||
}
|
||||
}
|
||||
|
||||
bool QgsMapToolReshape::isBindingLine( QgsVectorLayer *vlayer, const QgsRectangle &bbox ) const
|
||||
{
|
||||
if ( vlayer->geometryType() != QgsWkbTypes::LineGeometry )
|
||||
return false;
|
||||
|
||||
bool begin = false;
|
||||
bool end = false;
|
||||
const QgsPointXY beginPoint = points().first();
|
||||
const QgsPointXY endPoint = points().last();
|
||||
|
||||
QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( bbox ).setSubsetOfAttributes( QgsAttributeList() ) );
|
||||
QgsFeature f;
|
||||
|
||||
// check that extremities of the new line are contained by features
|
||||
while ( fit.nextFeature( f ) )
|
||||
{
|
||||
const QgsGeometry geom = f.geometry();
|
||||
if ( !geom.isNull() )
|
||||
{
|
||||
const QgsPolylineXY line = geom.asPolyline();
|
||||
|
||||
if ( line.contains( beginPoint ) )
|
||||
begin = true;
|
||||
else if ( line.contains( endPoint ) )
|
||||
end = true;
|
||||
}
|
||||
}
|
||||
|
||||
return end && begin;
|
||||
}
|
||||
|
||||
void QgsMapToolReshape::reshape( QgsVectorLayer *vlayer )
|
||||
{
|
||||
QgsPointXY firstPoint = points().at( 0 );
|
||||
QgsRectangle bbox( firstPoint.x(), firstPoint.y(), firstPoint.x(), firstPoint.y() );
|
||||
for ( int i = 1; i < size(); ++i )
|
||||
{
|
||||
bbox.combineExtentWith( points().at( i ).x(), points().at( i ).y() );
|
||||
}
|
||||
|
||||
QgsLineString reshapeLineString( points() );
|
||||
if ( QgsWkbTypes::hasZ( vlayer->wkbType() ) )
|
||||
reshapeLineString.addZValue( defaultZValue() );
|
||||
|
||||
//query all the features that intersect bounding box of capture line
|
||||
QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( bbox ).setSubsetOfAttributes( QgsAttributeList() ) );
|
||||
QgsFeature f;
|
||||
int reshapeReturn;
|
||||
bool reshapeDone = false;
|
||||
bool isBinding = isBindingLine( vlayer, bbox );
|
||||
|
||||
vlayer->beginEditCommand( tr( "Reshape" ) );
|
||||
while ( fit.nextFeature( f ) )
|
||||
{
|
||||
//query geometry
|
||||
//call geometry->reshape(mCaptureList)
|
||||
//register changed geometry in vector layer
|
||||
QgsGeometry geom = f.geometry();
|
||||
if ( !geom.isNull() )
|
||||
{
|
||||
// in case of a binding line, we just want to update the line from
|
||||
// the starting point and not both side
|
||||
if ( isBinding && !geom.asPolyline().contains( points().first() ) )
|
||||
continue;
|
||||
|
||||
reshapeReturn = geom.reshapeGeometry( reshapeLineString );
|
||||
if ( reshapeReturn == 0 )
|
||||
{
|
||||
//avoid intersections on polygon layers
|
||||
if ( vlayer->geometryType() == QgsWkbTypes::PolygonGeometry )
|
||||
{
|
||||
//ignore all current layer features as they should be reshaped too
|
||||
QHash<QgsVectorLayer *, QSet<QgsFeatureId> > ignoreFeatures;
|
||||
ignoreFeatures.insert( vlayer, vlayer->allFeatureIds() );
|
||||
|
||||
if ( geom.avoidIntersections( QgsProject::instance()->avoidIntersectionsLayers(), ignoreFeatures ) != 0 )
|
||||
{
|
||||
emit messageEmitted( tr( "An error was reported during intersection removal" ), QgsMessageBar::CRITICAL );
|
||||
vlayer->destroyEditCommand();
|
||||
stopCapturing();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( geom.isEmpty() ) //intersection removal might have removed the whole geometry
|
||||
{
|
||||
emit messageEmitted( tr( "The feature cannot be reshaped because the resulting geometry is empty" ), QgsMessageBar::CRITICAL );
|
||||
vlayer->destroyEditCommand();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
vlayer->changeGeometry( f.id(), geom );
|
||||
reshapeDone = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( reshapeDone )
|
||||
{
|
||||
vlayer->endEditCommand();
|
||||
}
|
||||
else
|
||||
{
|
||||
vlayer->destroyEditCommand();
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,13 @@ class APP_EXPORT QgsMapToolReshape: public QgsMapToolCapture
|
||||
public:
|
||||
QgsMapToolReshape( QgsMapCanvas *canvas );
|
||||
void cadCanvasReleaseEvent( QgsMapMouseEvent *e ) override;
|
||||
|
||||
private:
|
||||
void reshape( QgsVectorLayer *vlayer );
|
||||
|
||||
bool isBindingLine( QgsVectorLayer *vlayer, const QgsRectangle &bbox ) const;
|
||||
|
||||
friend class TestQgsMapToolReshape;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -763,7 +763,7 @@ int QgsMapToolCapture::size()
|
||||
return mCaptureCurve.numPoints();
|
||||
}
|
||||
|
||||
QVector<QgsPointXY> QgsMapToolCapture::points()
|
||||
QVector<QgsPointXY> QgsMapToolCapture::points() const
|
||||
{
|
||||
QgsPointSequence pts;
|
||||
QVector<QgsPointXY> points;
|
||||
|
@ -192,7 +192,7 @@ class GUI_EXPORT QgsMapToolCapture : public QgsMapToolAdvancedDigitizing
|
||||
* List of digitized points
|
||||
* \returns List of points
|
||||
*/
|
||||
QVector<QgsPointXY> points();
|
||||
QVector<QgsPointXY> points() const;
|
||||
|
||||
/**
|
||||
* Set the points on which to work
|
||||
@ -259,6 +259,8 @@ class GUI_EXPORT QgsMapToolCapture : public QgsMapToolAdvancedDigitizing
|
||||
*/
|
||||
QgsPointXY mTracingStartPoint;
|
||||
|
||||
friend class TestQgsMapToolReshape;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
int mSkipNextContextMenuEvent;
|
||||
#endif
|
||||
|
@ -98,6 +98,7 @@ ADD_QGIS_TEST(fieldcalculatortest testqgsfieldcalculator.cpp)
|
||||
ADD_QGIS_TEST(maptooladdfeature testqgsmaptooladdfeature.cpp)
|
||||
ADD_QGIS_TEST(maptoolidentifyaction testqgsmaptoolidentifyaction.cpp)
|
||||
ADD_QGIS_TEST(maptoolselect testqgsmaptoolselect.cpp)
|
||||
ADD_QGIS_TEST(maptoolreshape testqgsmaptoolreshape.cpp)
|
||||
ADD_QGIS_TEST(measuretool testqgsmeasuretool.cpp)
|
||||
ADD_QGIS_TEST(nodetool testqgsnodetool.cpp)
|
||||
ADD_QGIS_TEST(vectorlayersaveasdialogtest testqgsvectorlayersaveasdialog.cpp)
|
||||
|
143
tests/src/app/testqgsmaptoolreshape.cpp
Normal file
143
tests/src/app/testqgsmaptoolreshape.cpp
Normal file
@ -0,0 +1,143 @@
|
||||
/***************************************************************************
|
||||
testqgsmaptoolreshape.cpp
|
||||
--------------------------------
|
||||
Date : 2017-12-1
|
||||
Copyright : (C) 2017 by Paul Blottiere
|
||||
Email : paul.blottiere@oslandia.com
|
||||
***************************************************************************
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "qgstest.h"
|
||||
#include "qgsapplication.h"
|
||||
#include "qgsmapcanvas.h"
|
||||
#include "qgsvectorlayer.h"
|
||||
#include "qgslinestring.h"
|
||||
#include "qgsmaptoolreshape.h"
|
||||
#include "qgisapp.h"
|
||||
|
||||
class TestQgsMapToolReshape : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TestQgsMapToolReshape() = default;
|
||||
|
||||
private slots:
|
||||
void initTestCase(); // will be called before the first testfunction is executed.
|
||||
void cleanupTestCase(); // will be called after the last testfunction was executed.
|
||||
void init(); // will be called before each testfunction is executed.
|
||||
void cleanup(); // will be called after every testfunction.
|
||||
|
||||
void reshapeWithBindingLine();
|
||||
|
||||
private:
|
||||
QgisApp *mQgisApp = nullptr;
|
||||
};
|
||||
|
||||
void TestQgsMapToolReshape::initTestCase()
|
||||
{
|
||||
QgsApplication::init();
|
||||
QgsApplication::initQgis();
|
||||
|
||||
// Set up the QgsSettings environment
|
||||
QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
|
||||
QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
|
||||
QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) );
|
||||
|
||||
QgsApplication::showSettings();
|
||||
|
||||
// enforce C locale because the tests expect it
|
||||
// (decimal separators / thousand separators)
|
||||
QLocale::setDefault( QLocale::c() );
|
||||
|
||||
mQgisApp = new QgisApp();
|
||||
}
|
||||
|
||||
void TestQgsMapToolReshape::cleanupTestCase()
|
||||
{
|
||||
QgsApplication::exitQgis();
|
||||
}
|
||||
|
||||
void TestQgsMapToolReshape::init()
|
||||
{
|
||||
}
|
||||
|
||||
void TestQgsMapToolReshape::cleanup()
|
||||
{
|
||||
}
|
||||
|
||||
void TestQgsMapToolReshape::reshapeWithBindingLine()
|
||||
{
|
||||
// prepare vector layer
|
||||
std::unique_ptr<QgsVectorLayer> vl;
|
||||
vl.reset( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:4326&field=name:string(20)" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
|
||||
|
||||
QgsGeometry g0 = QgsGeometry::fromWkt( "LineString (0 0, 1 1, 1 2)" );
|
||||
QgsFeature f0;
|
||||
f0.setGeometry( g0 );
|
||||
f0.setAttribute( 0, "polyline0" );
|
||||
|
||||
QgsGeometry g1 = QgsGeometry::fromWkt( "LineString (2 1, 3 2, 3 3, 2 2)" );
|
||||
QgsFeature f1;
|
||||
f1.setGeometry( g1 );
|
||||
f1.setAttribute( 0, "polyline1" );
|
||||
|
||||
vl->dataProvider()->addFeatures( QgsFeatureList() << f0 << f1 );
|
||||
|
||||
// prepare canvas
|
||||
QList<QgsMapLayer *> layers;
|
||||
layers.append( vl.get() );
|
||||
|
||||
QgsCoordinateReferenceSystem srs( 4326, QgsCoordinateReferenceSystem::EpsgCrsId );
|
||||
mQgisApp->mapCanvas()->setDestinationCrs( srs );
|
||||
mQgisApp->mapCanvas()->setLayers( layers );
|
||||
mQgisApp->mapCanvas()->setCurrentLayer( vl.get() );
|
||||
|
||||
// reshape to add line to polyline0
|
||||
QgsLineString cl0;
|
||||
cl0.setPoints( QgsPointSequence() << QgsPoint( 1, 2 ) << QgsPoint( 2, 1 ) );
|
||||
|
||||
QgsCompoundCurve curve0( *cl0.toCurveType() );
|
||||
|
||||
QgsMapToolReshape tool0( mQgisApp->mapCanvas() );
|
||||
tool0.mCaptureCurve = curve0;
|
||||
|
||||
vl->startEditing();
|
||||
tool0.reshape( vl.get() );
|
||||
|
||||
f0 = vl->getFeature( 1 );
|
||||
QCOMPARE( f0.geometry().asWkt(), QStringLiteral( "LineString (0 0, 1 1, 1 2, 2 1)" ) );
|
||||
|
||||
f1 = vl->getFeature( 2 );
|
||||
QCOMPARE( f1.geometry().asWkt(), QStringLiteral( "LineString (2 1, 3 2, 3 3, 2 2)" ) );
|
||||
|
||||
vl->rollBack();
|
||||
|
||||
// reshape to add line to polyline1
|
||||
QgsLineString cl1;
|
||||
cl1.setPoints( QgsPointSequence() << QgsPoint( 2, 1 ) << QgsPoint( 1, 2 ) );
|
||||
|
||||
QgsCompoundCurve curve1( *cl1.toCurveType() );
|
||||
|
||||
QgsMapToolReshape tool1( mQgisApp->mapCanvas() );
|
||||
tool1.mCaptureCurve = curve1;
|
||||
|
||||
vl->startEditing();
|
||||
tool1.reshape( vl.get() );
|
||||
|
||||
f0 = vl->getFeature( 1 );
|
||||
QCOMPARE( f0.geometry().asWkt(), QStringLiteral( "LineString (0 0, 1 1, 1 2)" ) );
|
||||
|
||||
f1 = vl->getFeature( 2 );
|
||||
QCOMPARE( f1.geometry().asWkt(), QStringLiteral( "LineString (1 2, 2 1, 3 2, 3 3, 2 2)" ) );
|
||||
|
||||
vl->rollBack();
|
||||
}
|
||||
|
||||
QGSTEST_MAIN( TestQgsMapToolReshape )
|
||||
#include "testqgsmaptoolreshape.moc"
|
Loading…
x
Reference in New Issue
Block a user