Skip to content

Commit

Permalink
Fixes reshape tool topo points
Browse files Browse the repository at this point in the history
  • Loading branch information
troopa81 committed Sep 28, 2023
1 parent 4676af8 commit 847350b
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 133 deletions.
1 change: 1 addition & 0 deletions src/app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ set(QGIS_APP_SRCS
locator/qgslocatoroptionswidget.cpp

maptools/qgsappmaptools.cpp
maptools/qgsavoidintersectionsoperation.cpp
maptools/qgsmaptoolsdigitizingtechniquemanager.cpp

maptools/qgsmaptoolshapecircleabstract.cpp
Expand Down
136 changes: 136 additions & 0 deletions src/app/maptools/qgsavoidintersectionsoperation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/***************************************************************************
qgsavoidintersectionsoperation.cpp
---------------------
begin : 2023/09/20
copyright : (C) 2023 by Julien Cabieces
email : julien dot cabieces at oslandia dot 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 <QPushButton>

#include "qgis.h"
#include "qgisapp.h"
#include "qgsavoidintersectionsoperation.h"
#include "qgsmessagebar.h"
#include "qgsmessagebaritem.h"
#include "qgsproject.h"
#include "qgsvectorlayer.h"

Qgis::GeometryOperationResult QgsAvoidIntersectionsOperation::apply( QgsVectorLayer *layer, QgsFeatureId fid, QgsGeometry &geom,
const QHash<QgsVectorLayer *, QSet<QgsFeatureId> > &ignoreFeatures )
{
QList<QgsVectorLayer *> avoidIntersectionsLayers;
switch ( QgsProject::instance()->avoidIntersectionsMode() )
{
case Qgis::AvoidIntersectionsMode::AvoidIntersectionsCurrentLayer:
avoidIntersectionsLayers.append( layer );
break;
case Qgis::AvoidIntersectionsMode::AvoidIntersectionsLayers:
avoidIntersectionsLayers = QgsProject::instance()->avoidIntersectionsLayers();
break;
case Qgis::AvoidIntersectionsMode::AllowIntersections:
break;
}

if ( avoidIntersectionsLayers.isEmpty() )
return Qgis::GeometryOperationResult::NothingHappened;

Qgis::GeometryOperationResult avoidIntersectionsReturn = geom.avoidIntersectionsV2( avoidIntersectionsLayers, ignoreFeatures );
bool geomHasChanged = false;
switch ( avoidIntersectionsReturn )
{
case Qgis::GeometryOperationResult::GeometryTypeHasChanged: // Geometry type was changed, let's try our best to make it compatible with the target layer
{
geomHasChanged = true;
const QVector<QgsGeometry> newGeoms = geom.coerceToType( layer->wkbType() );
if ( newGeoms.count() == 1 )
{
geom = newGeoms.at( 0 );
avoidIntersectionsReturn = Qgis::GeometryOperationResult::Success;
}
else // handle multi geometries
{
QgsFeatureList removedFeatures;
double largest = 0;
QgsFeature originalFeature = layer->getFeature( fid );
int largestPartIndex = -1;
for ( int i = 0; i < newGeoms.size(); ++i )
{
QgsGeometry currentPart = newGeoms.at( i );
const double currentPartSize = layer->geometryType() == Qgis::GeometryType::Polygon ? currentPart.area() : currentPart.length();

QgsFeature partFeature( layer->fields() );
partFeature.setAttributes( originalFeature.attributes() );
partFeature.setGeometry( currentPart );
removedFeatures.append( partFeature );
if ( currentPartSize > largest )
{
geom = currentPart;
largestPartIndex = i;
largest = currentPartSize;
}
}
removedFeatures.removeAt( largestPartIndex );
QgsMessageBarItem *messageBarItem = QgisApp::instance()->messageBar()->createMessage( tr( "Avoid overlaps" ), tr( "Only the largest of multiple created geometries was preserved." ) );
QPushButton *restoreButton = new QPushButton( tr( "Restore others" ) );
QPointer<QgsVectorLayer> layerPtr( layer );
connect( restoreButton, &QPushButton::clicked, restoreButton, [ = ]
{
if ( !layerPtr )
return;
layerPtr->beginEditCommand( tr( "Restored geometry parts removed by avoid overlaps" ) );
QgsFeatureList unconstFeatures = removedFeatures;
QgisApp::instance()->pasteFeatures( layerPtr.data(), 0, removedFeatures.size(), unconstFeatures );
} );
messageBarItem->layout()->addWidget( restoreButton );
QgisApp::instance()->messageBar()->pushWidget( messageBarItem, Qgis::MessageLevel::Info, 15 );
}
break;
}

case Qgis::GeometryOperationResult::InvalidBaseGeometry:
geomHasChanged = true;
emit messageEmitted( tr( "At least one geometry intersected is invalid. These geometries must be manually repaired." ), Qgis::MessageLevel::Warning );
break;

case Qgis::GeometryOperationResult::Success:
geomHasChanged = true;
break;

case Qgis::GeometryOperationResult::NothingHappened:
case Qgis::GeometryOperationResult::InvalidInputGeometryType:
case Qgis::GeometryOperationResult::SelectionIsEmpty:
case Qgis::GeometryOperationResult::SelectionIsGreaterThanOne:
case Qgis::GeometryOperationResult::GeometryEngineError:
case Qgis::GeometryOperationResult::LayerNotEditable:
case Qgis::GeometryOperationResult::AddPartSelectedGeometryNotFound:
case Qgis::GeometryOperationResult::AddPartNotMultiGeometry:
case Qgis::GeometryOperationResult::AddRingNotClosed:
case Qgis::GeometryOperationResult::AddRingNotValid:
case Qgis::GeometryOperationResult::AddRingCrossesExistingRings:
case Qgis::GeometryOperationResult::AddRingNotInExistingFeature:
case Qgis::GeometryOperationResult::SplitCannotSplitPoint:
break;
}

if ( QgsProject::instance()->topologicalEditing() && geomHasChanged )
{
// then add the new points generated by avoidIntersections
QgsGeometry oldGeom = layer->getGeometry( fid ).convertToType( Qgis::GeometryType::Point, true );
QgsGeometry difference = geom.convertToType( Qgis::GeometryType::Point, true ).difference( oldGeom );
for ( auto it = difference.vertices_begin(); it != difference.vertices_end(); ++it )
for ( QgsVectorLayer *targetLayer : avoidIntersectionsLayers )
{
targetLayer->addTopologicalPoints( *it );
}
}

return avoidIntersectionsReturn;
}
59 changes: 59 additions & 0 deletions src/app/maptools/qgsavoidintersectionsoperation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/***************************************************************************
qgsavoidintersectionsoperation.h
---------------------
begin : 2023/09/20
copyright : (C) 2023 by Julien Cabieces
email : julien dot cabieces at oslandia dot 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. *
* *
***************************************************************************/

#ifndef QGSAVOIDINTERSECTIONSOPERATION_H
#define QGSAVOIDINTERSECTIONSOPERATION_H

#include <QObject>

#include "qgis.h"
#include "qgis_gui.h"
#include "qgsfeatureid.h"
#include "qgspoint.h"

class QgsVectorLayer;
class QgsGeometry;
class QgsMessageBar;

/**
* \ingroup gui
* \brief Helper class to apply the avoid intersection operation on a geometry and treat resulting issues.
* \since QGIS 3.34
*/
class GUI_EXPORT QgsAvoidIntersectionsOperation : public QObject
{
Q_OBJECT

public:

/**
* Contructor
*/
QgsAvoidIntersectionsOperation() = default;

// TODO cartouche
// TODO don't return int, return enum
Qgis::GeometryOperationResult apply( QgsVectorLayer *layer, QgsFeatureId fid, QgsGeometry &geom,
const QHash<QgsVectorLayer *, QSet<QgsFeatureId> > &ignoreFeatures = ( QHash<QgsVectorLayer *, QSet<QgsFeatureId> >() ) );

signals:

/**
* emmit a \a message with corresponding \a level
*/
void messageEmitted( const QString &message, Qgis::MessageLevel = Qgis::MessageLevel::Info );
};

#endif
62 changes: 26 additions & 36 deletions src/app/qgsmaptoolreshape.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* *
***************************************************************************/

#include "qgsavoidintersectionsoperation.h"
#include "qgsmaptoolreshape.h"
#include "qgsfeatureiterator.h"
#include "qgsgeometry.h"
Expand Down Expand Up @@ -157,6 +158,8 @@ void QgsMapToolReshape::reshape( QgsVectorLayer *vlayer )
Qgis::GeometryOperationResult reshapeReturn = Qgis::GeometryOperationResult::Success;
bool reshapeDone = false;
const bool isBinding = isBindingLine( vlayer, bbox );
QgsAvoidIntersectionsOperation avoidIntersections;
connect( &avoidIntersections, &QgsAvoidIntersectionsOperation::messageEmitted, this, &QgsMapTool::messageEmitted );

vlayer->beginEditCommand( tr( "Reshape" ) );
while ( fit.nextFeature( f ) )
Expand All @@ -182,41 +185,27 @@ void QgsMapToolReshape::reshape( QgsVectorLayer *vlayer )
QHash<QgsVectorLayer *, QSet<QgsFeatureId> > ignoreFeatures;
ignoreFeatures.insert( vlayer, vlayer->allFeatureIds() );

QList<QgsVectorLayer *> avoidIntersectionsLayers;
switch ( QgsProject::instance()->avoidIntersectionsMode() )
{
case Qgis::AvoidIntersectionsMode::AvoidIntersectionsCurrentLayer:
avoidIntersectionsLayers.append( vlayer );
break;
case Qgis::AvoidIntersectionsMode::AvoidIntersectionsLayers:
avoidIntersectionsLayers = QgsProject::instance()->avoidIntersectionsLayers();
break;
case Qgis::AvoidIntersectionsMode::AllowIntersections:
break;
}
Qgis::GeometryOperationResult res = Qgis::GeometryOperationResult::NothingHappened;
if ( avoidIntersectionsLayers.size() > 0 )
{
res = geom.avoidIntersectionsV2( QgsProject::instance()->avoidIntersectionsLayers(), ignoreFeatures );
if ( res == Qgis::GeometryOperationResult::InvalidInputGeometryType )
{
emit messageEmitted( tr( "An error was reported during intersection removal" ), Qgis::MessageLevel::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" ), Qgis::MessageLevel::Critical );
vlayer->destroyEditCommand();
return;
}
if ( res == Qgis::GeometryOperationResult::InvalidBaseGeometry )
{
emit messageEmitted( tr( "At least one geometry intersected is invalid. These geometries must be manually repaired." ), Qgis::MessageLevel::Warning );
}
avoidIntersections.apply( vlayer, f.id(), geom, ignoreFeatures );

// TODO what do we do with those messages
// if ( avoidIntersectionsLayers.size() > 0 )
// {
// res = geom.avoidIntersections( QgsProject::instance()->avoidIntersectionsLayers(), ignoreFeatures );
// if ( res == 1 )
// {
// emit messageEmitted( tr( "An error was reported during intersection removal" ), Qgis::MessageLevel::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" ), Qgis::MessageLevel::Critical );
// vlayer->destroyEditCommand();
// return;
// }
}

vlayer->changeGeometry( f.id(), geom );
Expand All @@ -227,7 +216,7 @@ void QgsMapToolReshape::reshape( QgsVectorLayer *vlayer )

if ( reshapeDone )
{
// Add topological points
// Add topological points due to snapping
if ( QgsProject::instance()->topologicalEditing() )
{
const QList<QgsPointLocator::Match> sm = snappingMatches();
Expand All @@ -240,6 +229,7 @@ void QgsMapToolReshape::reshape( QgsVectorLayer *vlayer )
}
}
}

vlayer->endEditCommand();
}
else
Expand Down
Loading

0 comments on commit 847350b

Please sign in to comment.