mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-16 00:05:45 -04:00
Add API to remove vertices or edges from QgsGraph
Useful when you've built a graph and want to perform multiple different analysis on it after excluding routes without having to rebuild the whole graph again Eg. find the shortest path between two vertices, then remove all these edge from this path and repeat to try to find the second-shortest path which doesn't use any of the same edges from the shortest path
This commit is contained in:
parent
9b02c301a5
commit
c0b253a69b
@ -161,6 +161,30 @@ Returns the vertex at the given index.
|
||||
}
|
||||
%End
|
||||
|
||||
|
||||
void removeVertex( int index ) const;
|
||||
%Docstring
|
||||
Removes the vertex at specified ``index``.
|
||||
|
||||
All edges which are incoming or outgoing edges for the vertex will also be removed.
|
||||
|
||||
:raises IndexError: if the vertex is not found.
|
||||
|
||||
.. versionadded:: 3.24
|
||||
%End
|
||||
%MethodCode
|
||||
auto it = sipCpp->mGraphVertices.constFind( a0 );
|
||||
if ( it != sipCpp->mGraphVertices.constEnd() )
|
||||
{
|
||||
sipCpp->removeVertex( a0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
|
||||
sipIsErr = 1;
|
||||
}
|
||||
%End
|
||||
|
||||
int edgeCount() const;
|
||||
%Docstring
|
||||
Returns number of graph edges
|
||||
@ -186,6 +210,33 @@ Returns the edge at the given index.
|
||||
}
|
||||
%End
|
||||
|
||||
|
||||
|
||||
void removeEdge( int index ) const;
|
||||
%Docstring
|
||||
Removes the edge at specified ``index``.
|
||||
|
||||
The incoming and outgoing edges for all graph vertices will be updated accordingly. Vertices which
|
||||
no longer have any incoming or outgoing edges as a result will be removed from the graph automatically.
|
||||
|
||||
:raises IndexError: if the vertex is not found.
|
||||
|
||||
.. versionadded:: 3.24
|
||||
%End
|
||||
%MethodCode
|
||||
auto it = sipCpp->mGraphEdges.constFind( a0 );
|
||||
if ( it != sipCpp->mGraphEdges.constEnd() )
|
||||
{
|
||||
sipCpp->removeEdge( a0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
|
||||
sipIsErr = 1;
|
||||
}
|
||||
%End
|
||||
|
||||
|
||||
int findVertex( const QgsPointXY &pt ) const;
|
||||
%Docstring
|
||||
Find vertex by associated point
|
||||
|
@ -19,6 +19,7 @@
|
||||
*/
|
||||
|
||||
#include "qgsgraph.h"
|
||||
#include <QSet>
|
||||
|
||||
int QgsGraph::addVertex( const QgsPointXY &pt )
|
||||
{
|
||||
@ -51,6 +52,24 @@ const QgsGraphVertex &QgsGraph::vertex( int idx ) const
|
||||
Q_ASSERT_X( false, "QgsGraph::vertex()", "Invalid vertex ID" );
|
||||
}
|
||||
|
||||
void QgsGraph::removeVertex( int index )
|
||||
{
|
||||
auto it = mGraphVertices.constFind( index );
|
||||
if ( it != mGraphVertices.constEnd() )
|
||||
{
|
||||
QSet< int > affectedEdges = qgis::listToSet( it->incomingEdges() );
|
||||
affectedEdges.unite( qgis::listToSet( it->outgoingEdges() ) );
|
||||
|
||||
mGraphVertices.erase( it );
|
||||
|
||||
// remove affected edges
|
||||
for ( int edgeId : std::as_const( affectedEdges ) )
|
||||
{
|
||||
mGraphEdges.remove( edgeId );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const QgsGraphEdge &QgsGraph::edge( int idx ) const
|
||||
{
|
||||
auto it = mGraphEdges.constFind( idx );
|
||||
@ -59,6 +78,34 @@ const QgsGraphEdge &QgsGraph::edge( int idx ) const
|
||||
Q_ASSERT_X( false, "QgsGraph::edge()", "Invalid edge ID" );
|
||||
}
|
||||
|
||||
void QgsGraph::removeEdge( int index )
|
||||
{
|
||||
auto it = mGraphEdges.constFind( index );
|
||||
if ( it != mGraphEdges.constEnd() )
|
||||
{
|
||||
const int fromVertex = it->fromVertex();
|
||||
const int toVertex = it->toVertex();
|
||||
mGraphEdges.erase( it );
|
||||
|
||||
// clean up affected vertices
|
||||
auto vertexIt = mGraphVertices.find( fromVertex );
|
||||
if ( vertexIt != mGraphVertices.end() )
|
||||
{
|
||||
vertexIt->mOutgoingEdges.removeAll( index );
|
||||
if ( vertexIt->mOutgoingEdges.empty() && vertexIt->mIncomingEdges.empty() )
|
||||
mGraphVertices.erase( vertexIt );
|
||||
}
|
||||
|
||||
vertexIt = mGraphVertices.find( toVertex );
|
||||
if ( vertexIt != mGraphVertices.end() )
|
||||
{
|
||||
vertexIt->mIncomingEdges.removeAll( index );
|
||||
if ( vertexIt->mOutgoingEdges.empty() && vertexIt->mIncomingEdges.empty() )
|
||||
mGraphVertices.erase( vertexIt );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int QgsGraph::vertexCount() const
|
||||
{
|
||||
return mGraphVertices.size();
|
||||
|
@ -193,6 +193,41 @@ class ANALYSIS_EXPORT QgsGraph
|
||||
% End
|
||||
#endif
|
||||
|
||||
#ifndef SIP_RUN
|
||||
|
||||
/**
|
||||
* Removes the vertex at specified \a index.
|
||||
*
|
||||
* All edges which are incoming or outgoing edges for the vertex will also be removed.
|
||||
*
|
||||
* \since QGIS 3.24
|
||||
*/
|
||||
void removeVertex( int index );
|
||||
#else
|
||||
|
||||
/**
|
||||
* Removes the vertex at specified \a index.
|
||||
*
|
||||
* All edges which are incoming or outgoing edges for the vertex will also be removed.
|
||||
*
|
||||
* \throws IndexError if the vertex is not found.
|
||||
* \since QGIS 3.24
|
||||
*/
|
||||
void removeVertex( int index ) const;
|
||||
% MethodCode
|
||||
auto it = sipCpp->mGraphVertices.constFind( a0 );
|
||||
if ( it != sipCpp->mGraphVertices.constEnd() )
|
||||
{
|
||||
sipCpp->removeVertex( a0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
|
||||
sipIsErr = 1;
|
||||
}
|
||||
% End
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Returns number of graph edges
|
||||
*/
|
||||
@ -226,6 +261,45 @@ class ANALYSIS_EXPORT QgsGraph
|
||||
% End
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef SIP_RUN
|
||||
|
||||
/**
|
||||
* Removes the edge at specified \a index.
|
||||
*
|
||||
* The incoming and outgoing edges for all graph vertices will be updated accordingly. Vertices which
|
||||
* no longer have any incoming or outgoing edges as a result will be removed from the graph automatically.
|
||||
*
|
||||
* \since QGIS 3.24
|
||||
*/
|
||||
void removeEdge( int index );
|
||||
#else
|
||||
|
||||
/**
|
||||
* Removes the edge at specified \a index.
|
||||
*
|
||||
* The incoming and outgoing edges for all graph vertices will be updated accordingly. Vertices which
|
||||
* no longer have any incoming or outgoing edges as a result will be removed from the graph automatically.
|
||||
*
|
||||
* \throws IndexError if the vertex is not found.
|
||||
* \since QGIS 3.24
|
||||
*/
|
||||
void removeEdge( int index ) const;
|
||||
% MethodCode
|
||||
auto it = sipCpp->mGraphEdges.constFind( a0 );
|
||||
if ( it != sipCpp->mGraphEdges.constEnd() )
|
||||
{
|
||||
sipCpp->removeEdge( a0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
PyErr_SetString( PyExc_IndexError, QByteArray::number( a0 ) );
|
||||
sipIsErr = 1;
|
||||
}
|
||||
% End
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* Find vertex by associated point
|
||||
* \returns vertex index
|
||||
|
@ -121,6 +121,191 @@ class TestQgsGraph(unittest.TestCase):
|
||||
with self.assertRaises(IndexError):
|
||||
graph.edge(3)
|
||||
|
||||
def test_remove_vertex(self):
|
||||
graph = QgsGraph()
|
||||
|
||||
with self.assertRaises(IndexError):
|
||||
graph.removeVertex(0)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.removeVertex(-1)
|
||||
|
||||
v1 = graph.addVertex(QgsPointXY(1, 1))
|
||||
v2 = graph.addVertex(QgsPointXY(2, 2))
|
||||
v3 = graph.addVertex(QgsPointXY(3, 3))
|
||||
v4 = graph.addVertex(QgsPointXY(4, 4))
|
||||
edge_1 = graph.addEdge(v1, v2, [1])
|
||||
edge_2 = graph.addEdge(v2, v1, [1])
|
||||
edge_3 = graph.addEdge(v2, v3, [1])
|
||||
edge_4 = graph.addEdge(v2, v4, [1])
|
||||
edge_5 = graph.addEdge(v3, v4, [1])
|
||||
|
||||
self.assertEqual(graph.vertexCount(), 4)
|
||||
self.assertEqual(graph.edgeCount(), 5)
|
||||
|
||||
with self.assertRaises(IndexError):
|
||||
graph.removeVertex(5)
|
||||
|
||||
# remove a vertex
|
||||
graph.removeVertex(v3)
|
||||
self.assertEqual(graph.vertexCount(), 3)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.vertex(v3)
|
||||
self.assertEqual(graph.edgeCount(), 3)
|
||||
self.assertEqual(graph.edge(edge_1).fromVertex(), v1)
|
||||
self.assertEqual(graph.edge(edge_2).fromVertex(), v2)
|
||||
self.assertEqual(graph.edge(edge_4).fromVertex(), v2)
|
||||
|
||||
# edges 3 and 5 must be removed
|
||||
with self.assertRaises(IndexError):
|
||||
graph.edge(edge_3)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.edge(edge_5)
|
||||
|
||||
with self.assertRaises(IndexError):
|
||||
graph.removeVertex(v3)
|
||||
|
||||
# remove another vertex
|
||||
graph.removeVertex(v1)
|
||||
self.assertEqual(graph.vertexCount(), 2)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.vertex(v1)
|
||||
self.assertEqual(graph.edgeCount(), 1)
|
||||
self.assertEqual(graph.edge(edge_4).fromVertex(), v2)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.edge(edge_1)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.edge(edge_2)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.edge(edge_3)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.edge(edge_5)
|
||||
|
||||
with self.assertRaises(IndexError):
|
||||
graph.removeVertex(v1)
|
||||
|
||||
# remove another vertex
|
||||
graph.removeVertex(v4)
|
||||
self.assertEqual(graph.vertexCount(), 1)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.vertex(v4)
|
||||
self.assertEqual(graph.edgeCount(), 0)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.edge(edge_1)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.edge(edge_2)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.edge(edge_3)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.edge(edge_4)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.edge(edge_5)
|
||||
|
||||
with self.assertRaises(IndexError):
|
||||
graph.removeVertex(v4)
|
||||
|
||||
# remove last vertex
|
||||
graph.removeVertex(v2)
|
||||
self.assertEqual(graph.vertexCount(), 0)
|
||||
self.assertEqual(graph.edgeCount(), 0)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.vertex(v2)
|
||||
|
||||
with self.assertRaises(IndexError):
|
||||
graph.removeVertex(v2)
|
||||
|
||||
def test_remove_edge(self):
|
||||
graph = QgsGraph()
|
||||
|
||||
with self.assertRaises(IndexError):
|
||||
graph.removeEdge(0)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.removeEdge(-1)
|
||||
|
||||
v1 = graph.addVertex(QgsPointXY(1, 1))
|
||||
v2 = graph.addVertex(QgsPointXY(2, 2))
|
||||
v3 = graph.addVertex(QgsPointXY(3, 3))
|
||||
v4 = graph.addVertex(QgsPointXY(4, 4))
|
||||
edge_1 = graph.addEdge(v1, v2, [1])
|
||||
edge_2 = graph.addEdge(v2, v1, [1])
|
||||
edge_3 = graph.addEdge(v2, v3, [1])
|
||||
edge_4 = graph.addEdge(v2, v4, [1])
|
||||
edge_5 = graph.addEdge(v3, v4, [1])
|
||||
|
||||
self.assertEqual(graph.vertexCount(), 4)
|
||||
self.assertEqual(graph.edgeCount(), 5)
|
||||
|
||||
graph.removeEdge(edge_1)
|
||||
self.assertEqual(graph.vertexCount(), 4)
|
||||
self.assertEqual(graph.edgeCount(), 4)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.edge(edge_1)
|
||||
|
||||
# make sure vertices are updated accordingly
|
||||
self.assertEqual(graph.vertex(v1).incomingEdges(), [edge_2])
|
||||
self.assertFalse(graph.vertex(v1).outgoingEdges())
|
||||
self.assertFalse(graph.vertex(v2).incomingEdges())
|
||||
self.assertCountEqual(graph.vertex(v2).outgoingEdges(), [edge_2, edge_3, edge_4])
|
||||
|
||||
with self.assertRaises(IndexError):
|
||||
graph.removeEdge(edge_1)
|
||||
|
||||
# remove another edge
|
||||
graph.removeEdge(edge_2)
|
||||
self.assertEqual(graph.vertexCount(), 3)
|
||||
self.assertEqual(graph.edgeCount(), 3)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.edge(edge_2)
|
||||
|
||||
# make sure vertices are updated accordingly
|
||||
# vertex 1 should be removed -- no incoming or outgoing edges remain
|
||||
with self.assertRaises(IndexError):
|
||||
graph.vertex(v1)
|
||||
self.assertFalse(graph.vertex(v2).incomingEdges())
|
||||
self.assertCountEqual(graph.vertex(v2).outgoingEdges(), [edge_3, edge_4])
|
||||
|
||||
with self.assertRaises(IndexError):
|
||||
graph.removeEdge(edge_2)
|
||||
|
||||
graph.removeEdge(edge_4)
|
||||
self.assertEqual(graph.vertexCount(), 3)
|
||||
self.assertEqual(graph.edgeCount(), 2)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.edge(edge_4)
|
||||
self.assertFalse(graph.vertex(v2).incomingEdges())
|
||||
self.assertEqual(graph.vertex(v2).outgoingEdges(), [edge_3])
|
||||
self.assertEqual(graph.vertex(v4).incomingEdges(), [edge_5])
|
||||
self.assertFalse(graph.vertex(v4).outgoingEdges())
|
||||
|
||||
with self.assertRaises(IndexError):
|
||||
graph.removeEdge(edge_4)
|
||||
|
||||
graph.removeEdge(edge_3)
|
||||
self.assertEqual(graph.vertexCount(), 2)
|
||||
self.assertEqual(graph.edgeCount(), 1)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.edge(edge_3)
|
||||
# v2 should be removed
|
||||
with self.assertRaises(IndexError):
|
||||
graph.vertex(v2)
|
||||
self.assertFalse(graph.vertex(v3).incomingEdges())
|
||||
self.assertEqual(graph.vertex(v3).outgoingEdges(), [edge_5])
|
||||
|
||||
with self.assertRaises(IndexError):
|
||||
graph.removeEdge(edge_3)
|
||||
|
||||
graph.removeEdge(edge_5)
|
||||
self.assertEqual(graph.vertexCount(), 0)
|
||||
self.assertEqual(graph.edgeCount(), 0)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.edge(edge_5)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.vertex(v3)
|
||||
with self.assertRaises(IndexError):
|
||||
graph.vertex(v4)
|
||||
|
||||
with self.assertRaises(IndexError):
|
||||
graph.removeEdge(edge_5)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
Loading…
x
Reference in New Issue
Block a user