Coverage Report

Created: 2025-06-13 06:18

/src/gdal/gnm/gnmgraph.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  GDAL/OGR Geography Network support (Geographic Network Model)
4
 * Purpose:  GNM graph implementation.
5
 * Authors:  Mikhail Gusev (gusevmihs at gmail dot com)
6
 *           Dmitry Baryshnikov, polimax@mail.ru
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 2014, Mikhail Gusev
10
 * Copyright (c) 2014-2015, NextGIS <info@nextgis.com>
11
 *
12
 * Permission is hereby granted, free of charge, to any person obtaining a
13
 * copy of this software and associated documentation files (the "Software"),
14
 * to deal in the Software without restriction, including without limitation
15
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16
 * and/or sell copies of the Software, and to permit persons to whom the
17
 * Software is furnished to do so, subject to the following conditions:
18
 *
19
 * The above copyright notice and this permission notice shall be included
20
 * in all copies or substantial portions of the Software.
21
 *
22
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
23
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
25
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28
 * DEALINGS IN THE SOFTWARE.
29
 ****************************************************************************/
30
31
#include "gnmgraph.h"
32
#include "gnm_priv.h"
33
#include <algorithm>
34
#include <limits>
35
#include <set>
36
37
//! @cond Doxygen_Suppress
38
GNMGraph::GNMGraph()
39
0
{
40
0
}
41
42
GNMGraph::~GNMGraph()
43
0
{
44
0
}
45
46
void GNMGraph::AddVertex(GNMGFID nFID)
47
0
{
48
0
    if (m_mstVertices.find(nFID) != m_mstVertices.end())
49
0
        return;
50
51
0
    GNMStdVertex stVertex;
52
0
    stVertex.bIsBlocked = false;
53
0
    m_mstVertices[nFID] = std::move(stVertex);
54
0
}
55
56
void GNMGraph::DeleteVertex(GNMGFID nFID)
57
0
{
58
0
    m_mstVertices.erase(nFID);
59
60
    // remove all edges with this vertex
61
0
    std::vector<GNMGFID> aoIdsToErase;
62
0
    for (std::map<GNMGFID, GNMStdEdge>::iterator it = m_mstEdges.begin();
63
0
         it != m_mstEdges.end(); ++it)
64
0
    {
65
0
        if (it->second.nSrcVertexFID == nFID ||
66
0
            it->second.nTgtVertexFID == nFID)
67
0
            aoIdsToErase.push_back(it->first);
68
0
    }
69
0
    for (size_t i = 0; i < aoIdsToErase.size(); i++)
70
0
        m_mstEdges.erase(aoIdsToErase[i]);
71
0
}
72
73
void GNMGraph::AddEdge(GNMGFID nConFID, GNMGFID nSrcFID, GNMGFID nTgtFID,
74
                       bool bIsBidir, double dfCost, double dfInvCost)
75
0
{
76
    // We do not add edge if an edge with the same id already exist
77
    // because each edge must have only one source and one target vertex.
78
0
    std::map<GNMGFID, GNMStdEdge>::iterator it = m_mstEdges.find(nConFID);
79
0
    if (it != m_mstEdges.end())
80
0
    {
81
0
        CPLError(CE_Failure, CPLE_AppDefined, "The edge already exist.");
82
0
        return;
83
0
    }
84
85
0
    AddVertex(nSrcFID);
86
0
    AddVertex(nTgtFID);
87
88
0
    auto itSrs = m_mstVertices.find(nSrcFID);
89
0
    auto itTgt = m_mstVertices.find(nTgtFID);
90
0
    if (itSrs == m_mstVertices.end() || itTgt == m_mstVertices.end())
91
0
    {
92
0
        CPLAssert(false);
93
0
        return;
94
0
    }
95
96
    // Insert edge to the array of edges.
97
0
    GNMStdEdge stEdge;
98
0
    stEdge.nSrcVertexFID = nSrcFID;
99
0
    stEdge.nTgtVertexFID = nTgtFID;
100
0
    stEdge.bIsBidir = bIsBidir;
101
0
    stEdge.dfDirCost = dfCost;
102
0
    stEdge.dfInvCost = dfInvCost;
103
0
    stEdge.bIsBlocked = false;
104
105
0
    m_mstEdges[nConFID] = stEdge;
106
107
0
    if (bIsBidir)
108
0
    {
109
0
        itSrs->second.anOutEdgeFIDs.push_back(nConFID);
110
0
        itTgt->second.anOutEdgeFIDs.push_back(nConFID);
111
0
    }
112
0
    else
113
0
    {
114
0
        itSrs->second.anOutEdgeFIDs.push_back(nConFID);
115
0
    }
116
0
}
117
118
void GNMGraph::DeleteEdge(GNMGFID nConFID)
119
0
{
120
0
    m_mstEdges.erase(nConFID);
121
122
    // remove edge from all vertices anOutEdgeFIDs
123
0
    for (auto &it : m_mstVertices)
124
0
    {
125
0
        it.second.anOutEdgeFIDs.erase(
126
0
            std::remove(it.second.anOutEdgeFIDs.begin(),
127
0
                        it.second.anOutEdgeFIDs.end(), nConFID),
128
0
            it.second.anOutEdgeFIDs.end());
129
0
    }
130
0
}
131
132
void GNMGraph::ChangeEdge(GNMGFID nFID, double dfCost, double dfInvCost)
133
0
{
134
0
    std::map<GNMGFID, GNMStdEdge>::iterator it = m_mstEdges.find(nFID);
135
0
    if (it != m_mstEdges.end())
136
0
    {
137
0
        it->second.dfDirCost = dfCost;
138
0
        it->second.dfInvCost = dfInvCost;
139
0
    }
140
0
}
141
142
void GNMGraph::ChangeBlockState(GNMGFID nFID, bool bBlock)
143
0
{
144
    // check vertices
145
0
    std::map<GNMGFID, GNMStdVertex>::iterator itv = m_mstVertices.find(nFID);
146
0
    if (itv != m_mstVertices.end())
147
0
    {
148
0
        itv->second.bIsBlocked = bBlock;
149
0
        return;
150
0
    }
151
152
    // check edges
153
0
    std::map<GNMGFID, GNMStdEdge>::iterator ite = m_mstEdges.find(nFID);
154
0
    if (ite != m_mstEdges.end())
155
0
    {
156
0
        ite->second.bIsBlocked = bBlock;
157
0
    }
158
0
}
159
160
bool GNMGraph::CheckVertexBlocked(GNMGFID nFID) const
161
0
{
162
0
    std::map<GNMGFID, GNMStdVertex>::const_iterator it =
163
0
        m_mstVertices.find(nFID);
164
0
    if (it != m_mstVertices.end())
165
0
        return it->second.bIsBlocked;
166
0
    return false;
167
0
}
168
169
void GNMGraph::ChangeAllBlockState(bool bBlock)
170
0
{
171
0
    for (std::map<GNMGFID, GNMStdVertex>::iterator itv = m_mstVertices.begin();
172
0
         itv != m_mstVertices.end(); ++itv)
173
0
    {
174
0
        itv->second.bIsBlocked = bBlock;
175
0
    }
176
177
0
    for (std::map<GNMGFID, GNMStdEdge>::iterator ite = m_mstEdges.begin();
178
0
         ite != m_mstEdges.end(); ++ite)
179
0
    {
180
0
        ite->second.bIsBlocked = bBlock;
181
0
    }
182
0
}
183
184
GNMPATH
185
GNMGraph::DijkstraShortestPath(GNMGFID nStartFID, GNMGFID nEndFID,
186
                               const std::map<GNMGFID, GNMStdEdge> &mstEdges)
187
0
{
188
0
    std::map<GNMGFID, GNMGFID> mnShortestTree;
189
0
    DijkstraShortestPathTree(nStartFID, mstEdges, mnShortestTree);
190
191
    // We search for a path in the resulting tree, starting from end point to
192
    // start point.
193
194
0
    GNMPATH aoShortestPath;
195
0
    GNMGFID nNextVertexId = nEndFID;
196
0
    std::map<GNMGFID, GNMGFID>::iterator it;
197
0
    EDGEVERTEXPAIR buf;
198
199
0
    while (true)
200
0
    {
201
0
        it = mnShortestTree.find(nNextVertexId);
202
203
0
        if (it == mnShortestTree.end())
204
0
        {
205
            // We haven't found the start vertex - there is no path between
206
            // to given vertices in a shortest-path tree.
207
0
            break;
208
0
        }
209
0
        else if (it->first == nStartFID)
210
0
        {
211
            // We've reached the start vertex and return an array.
212
0
            aoShortestPath.push_back(std::make_pair(nNextVertexId, -1));
213
214
            // Revert array because the first vertex is now the last in path.
215
0
            int size = static_cast<int>(aoShortestPath.size());
216
0
            for (int i = 0; i < size / 2; ++i)
217
0
            {
218
0
                buf = aoShortestPath[i];
219
0
                aoShortestPath[i] = aoShortestPath[size - i - 1];
220
0
                aoShortestPath[size - i - 1] = buf;
221
0
            }
222
0
            return aoShortestPath;
223
0
        }
224
0
        else
225
0
        {
226
            // There is only one edge which leads to this vertex, because we
227
            // analyse a tree. We add this edge with its target vertex into
228
            // final array.
229
0
            aoShortestPath.push_back(std::make_pair(nNextVertexId, it->second));
230
231
            // An edge has only two vertices, so we get the opposite one to the
232
            // current vertex in order to continue search backwards.
233
0
            nNextVertexId = GetOppositVertex(it->second, it->first);
234
0
        }
235
0
    }
236
237
    // return empty array
238
0
    GNMPATH oRet;
239
0
    return oRet;
240
0
}
241
242
GNMPATH GNMGraph::DijkstraShortestPath(GNMGFID nStartFID, GNMGFID nEndFID)
243
0
{
244
0
    return DijkstraShortestPath(nStartFID, nEndFID, m_mstEdges);
245
0
}
246
247
std::vector<GNMPATH> GNMGraph::KShortestPaths(GNMGFID nStartFID,
248
                                              GNMGFID nEndFID, size_t nK)
249
0
{
250
    // Resulting array with paths.
251
    // A will be sorted by the path costs' descending.
252
0
    std::vector<GNMPATH> A;
253
254
0
    if (nK == 0)
255
0
        return A;  // return empty array if K is incorrect.
256
257
    // Temporary array for storing paths-candidates.
258
    // B will be automatically sorted by the cost descending order. We
259
    // need multimap because there can be physically different paths but
260
    // with the same costs.
261
0
    std::multimap<double, GNMPATH> B;
262
263
    // Firstly get the very shortest path.
264
    // Note, that it is important to obtain the path from DijkstraShortestPath()
265
    // as vector, rather than the map, because we need the correct order of the
266
    // path segments in the Yen's algorithm iterations.
267
0
    GNMPATH aoFirstPath = DijkstraShortestPath(nStartFID, nEndFID);
268
0
    if (aoFirstPath.empty())
269
0
        return A;  // return empty array if there is no path between points.
270
271
0
    A.push_back(std::move(aoFirstPath));
272
273
0
    size_t i, k, l;
274
0
    GNMPATH::iterator itAk, tempIt, itR;
275
0
    std::vector<GNMPATH>::iterator itA;
276
0
    std::map<GNMGFID, double>::iterator itDel;
277
0
    GNMPATH aoRootPath, aoRootPathOther, aoSpurPath;
278
0
    GNMGFID nSpurNode, nVertexToDel, nEdgeToDel;
279
0
    double dfSumCost;
280
281
0
    std::map<GNMGFID, GNMStdEdge> mstEdges = m_mstEdges;
282
283
0
    for (k = 0; k < nK - 1; ++k)  // -1 because we have already found one
284
0
    {
285
0
        std::map<GNMGFID, double>
286
0
            mDeletedEdges;  // for infinity costs assignment
287
0
        itAk = A[k].begin();
288
289
0
        for (i = 0; i < A[k].size() - 1; ++i)  // avoid end node
290
0
        {
291
            // Get the current node.
292
0
            nSpurNode = A[k][i].first;
293
294
            // Get the root path from the 0 to the current node.
295
296
            // Equivalent to A[k][i]
297
            // because we will use std::vector::assign, which assigns [..)
298
            // range, not [..]
299
0
            ++itAk;
300
301
0
            aoRootPath.assign(A[k].begin(), itAk);
302
303
            // Remove old incidence edges of all other best paths.
304
            // i.e. if the spur vertex can be reached in already found best
305
            // paths we must remove the following edge after the end of root
306
            // path from the graph in order not to take in account these already
307
            // seen best paths.
308
            // i.e. it ensures that the spur path will be different.
309
0
            for (itA = A.begin(); itA != A.end(); ++itA)
310
0
            {
311
                // check if the number of node exceed the number of last node in
312
                // the path array (i.e. if one of the A paths has less amount of
313
                // segments than the current candidate path)
314
0
                if (i >= itA->size())
315
0
                    continue;
316
317
                // + 1, because we will use std::vector::assign, which assigns
318
                // [..) range, not [..]
319
0
                aoRootPathOther.assign(itA->begin(), itA->begin() + i + 1);
320
321
                // Get the edge which follows the spur node for current path
322
                // and delete it.
323
                //
324
                // NOTE: we do not delete edges due to performance reasons,
325
                // because the deletion of edge and all its GFIDs in vertex
326
                // records is slower than the infinity cost assignment.
327
328
                // also check if node number exceed the number of the last node
329
                // in root array.
330
0
                if ((aoRootPath == aoRootPathOther) &&
331
0
                    (i < aoRootPathOther.size()))
332
0
                {
333
0
                    tempIt = itA->begin() + i + 1;
334
0
                    mDeletedEdges.insert(std::make_pair(
335
0
                        tempIt->second, mstEdges[tempIt->second].dfDirCost));
336
0
                    mstEdges[tempIt->second].dfDirCost =
337
0
                        std::numeric_limits<double>::infinity();
338
0
                }
339
0
            }
340
341
            // Remove root path nodes from the graph. If we do not delete them
342
            // the path will be found backwards and some parts of the path will
343
            // duplicate the parts of old paths.
344
            // Note: we "delete" all the incidence to the root nodes edges, so
345
            // to restore them in a common way.
346
347
            // end()-1, because we should not remove the spur node
348
0
            for (itR = aoRootPath.begin(); itR != aoRootPath.end() - 1; ++itR)
349
0
            {
350
0
                nVertexToDel = itR->first;
351
0
                for (l = 0;
352
0
                     l < m_mstVertices[nVertexToDel].anOutEdgeFIDs.size(); ++l)
353
0
                {
354
0
                    nEdgeToDel = m_mstVertices[nVertexToDel].anOutEdgeFIDs[l];
355
0
                    mDeletedEdges.insert(std::make_pair(
356
0
                        nEdgeToDel, mstEdges[nEdgeToDel].dfDirCost));
357
0
                    mstEdges[nEdgeToDel].dfDirCost =
358
0
                        std::numeric_limits<double>::infinity();
359
0
                }
360
0
            }
361
362
            // Find the new best path in the modified graph.
363
0
            aoSpurPath = DijkstraShortestPath(nSpurNode, nEndFID, mstEdges);
364
365
            // Firstly, restore deleted edges in order to calculate the summary
366
            // cost of the path correctly later, because the costs will be
367
            // gathered from the initial graph.
368
            // We must do it here, after each edge removing, because the later
369
            // Dijkstra searches must consider these edges.
370
0
            for (itDel = mDeletedEdges.begin(); itDel != mDeletedEdges.end();
371
0
                 ++itDel)
372
0
            {
373
0
                mstEdges[itDel->first].dfDirCost = itDel->second;
374
0
            }
375
376
0
            mDeletedEdges.clear();
377
378
            // If the part of a new best path has been found we form a full one
379
            // and add it to the candidates array.
380
0
            if (!aoSpurPath.empty())
381
0
            {
382
                // + 1 so not to consider the first node in the found path,
383
                // which is already the last node in the root path
384
0
                aoRootPath.insert(aoRootPath.end(), aoSpurPath.begin() + 1,
385
0
                                  aoSpurPath.end());
386
                // Calculate the summary cost of the path.
387
                // TODO: get the summary cost from the Dejkstra method?
388
0
                dfSumCost = 0.0;
389
0
                for (itR = aoRootPath.begin(); itR != aoRootPath.end(); ++itR)
390
0
                {
391
                    // TODO: check: Note, that here the current cost can not be
392
                    // infinity, because every time we assign infinity costs for
393
                    // edges of old paths, we anyway have the alternative edges
394
                    // with non-infinity costs.
395
0
                    dfSumCost += mstEdges[itR->second].dfDirCost;
396
0
                }
397
398
0
                B.insert(std::make_pair(dfSumCost, aoRootPath));
399
0
            }
400
0
        }
401
402
0
        if (B.empty())
403
0
            break;
404
405
        // The best path is the first, because the map is sorted accordingly.
406
        // Note, that here we won't clear the path candidates array and select
407
        // the best path from all of the rest paths, even from those which were
408
        // found on previous iterations. That's why we need k iterations at all.
409
        // Note, that if there were two paths with the same costs and it is the
410
        // LAST iteration the first occurred path will be added, rather than
411
        // random.
412
0
        A.push_back(B.begin()->second);
413
414
        // Sometimes B contains fully duplicate paths. Such duplicates have been
415
        // formed during the search of alternative for almost the same paths
416
        // which were already in A.
417
        // We allowed to add them into B so here we must delete all duplicates.
418
0
        while (!B.empty() && B.begin()->second == A.back())
419
0
        {
420
0
            B.erase(B.begin());
421
0
        }
422
0
    }
423
424
0
    return A;
425
0
}
426
427
GNMPATH GNMGraph::ConnectedComponents(const GNMVECTOR &anEmittersIDs)
428
0
{
429
0
    GNMPATH anConnectedIDs;
430
431
0
    if (anEmittersIDs.empty())
432
0
    {
433
0
        CPLError(CE_Failure, CPLE_IllegalArg, "Emitters list is empty.");
434
0
        return anConnectedIDs;
435
0
    }
436
0
    std::set<GNMGFID> anMarkedVertIDs;
437
438
0
    std::queue<GNMGFID> anStartQueue;
439
0
    GNMVECTOR::const_iterator it;
440
0
    for (it = anEmittersIDs.begin(); it != anEmittersIDs.end(); ++it)
441
0
    {
442
0
        anStartQueue.push(*it);
443
0
    }
444
445
    // Begin the iterations of the Breadth-first search.
446
0
    TraceTargets(anStartQueue, anMarkedVertIDs, anConnectedIDs);
447
448
0
    return anConnectedIDs;
449
0
}
450
451
void GNMGraph::Clear()
452
0
{
453
0
    m_mstVertices.clear();
454
0
    m_mstEdges.clear();
455
0
}
456
457
void GNMGraph::DijkstraShortestPathTree(
458
    GNMGFID nFID, const std::map<GNMGFID, GNMStdEdge> &mstEdges,
459
    std::map<GNMGFID, GNMGFID> &mnPathTree)
460
0
{
461
    // Initialize all vertices in graph with infinity mark.
462
0
    double dfInfinity = std::numeric_limits<double>::infinity();
463
464
0
    std::map<GNMGFID, double> mMarks;
465
0
    std::map<GNMGFID, GNMStdVertex>::iterator itv;
466
0
    for (itv = m_mstVertices.begin(); itv != m_mstVertices.end(); ++itv)
467
0
    {
468
0
        mMarks[itv->first] = dfInfinity;
469
0
    }
470
471
0
    mMarks[nFID] = 0.0;
472
0
    mnPathTree[nFID] = -1;
473
474
    // Initialize all vertices as unseen (there are no seen vertices).
475
0
    std::set<GNMGFID> snSeen;
476
477
    // We use multimap to maintain the ascending order of costs and because
478
    // there can be different vertices with the equal cost.
479
0
    std::multimap<double, GNMGFID> to_see;
480
0
    std::multimap<double, GNMGFID>::iterator it;
481
0
    to_see.insert(std::pair<double, GNMGFID>(0.0, nFID));
482
0
    LPGNMCONSTVECTOR panOutcomeEdgeId;
483
484
0
    size_t i;
485
0
    GNMGFID nCurrentVertId, nCurrentEdgeId, nTargetVertId;
486
0
    double dfCurrentEdgeCost, dfCurrentVertMark, dfNewVertexMark;
487
0
    std::map<GNMGFID, GNMStdEdge>::const_iterator ite;
488
489
    // Continue iterations while there are some vertices to see.
490
0
    while (!to_see.empty())
491
0
    {
492
        // We must see vertices with minimal costs at first.
493
        // In multimap the first cost is the minimal.
494
0
        it = to_see.begin();
495
496
0
        nCurrentVertId = it->second;
497
0
        dfCurrentVertMark = it->first;
498
0
        snSeen.insert(it->second);
499
0
        to_see.erase(it);
500
501
        // For all neighbours for the current vertex.
502
0
        panOutcomeEdgeId = GetOutEdges(nCurrentVertId);
503
0
        if (nullptr == panOutcomeEdgeId)
504
0
            continue;
505
506
0
        for (i = 0; i < panOutcomeEdgeId->size(); ++i)
507
0
        {
508
0
            nCurrentEdgeId = panOutcomeEdgeId->operator[](i);
509
510
0
            ite = mstEdges.find(nCurrentEdgeId);
511
0
            if (ite == mstEdges.end() || ite->second.bIsBlocked)
512
0
                continue;
513
514
            // We go in any edge from source to target so we take only
515
            // direct cost (even if an edge is bi-directed).
516
0
            dfCurrentEdgeCost = ite->second.dfDirCost;
517
518
            // While we see outcome edges of current vertex id we definitely
519
            // know that target vertex id will be target for current edge id.
520
0
            nTargetVertId = GetOppositVertex(nCurrentEdgeId, nCurrentVertId);
521
522
            // Calculate a new mark assuming the full path cost (mark of the
523
            // current vertex) to this vertex.
524
0
            dfNewVertexMark = dfCurrentVertMark + dfCurrentEdgeCost;
525
526
            // Update mark of the vertex if needed.
527
0
            if (snSeen.find(nTargetVertId) == snSeen.end() &&
528
0
                dfNewVertexMark < mMarks[nTargetVertId] &&
529
0
                !CheckVertexBlocked(nTargetVertId))
530
0
            {
531
0
                mMarks[nTargetVertId] = dfNewVertexMark;
532
0
                mnPathTree[nTargetVertId] = nCurrentEdgeId;
533
534
                // The vertex with minimal cost will be inserted to the
535
                // beginning.
536
0
                to_see.insert(
537
0
                    std::pair<double, GNMGFID>(dfNewVertexMark, nTargetVertId));
538
0
            }
539
0
        }
540
0
    }
541
0
}
542
543
LPGNMCONSTVECTOR GNMGraph::GetOutEdges(GNMGFID nFID) const
544
0
{
545
0
    std::map<GNMGFID, GNMStdVertex>::const_iterator it =
546
0
        m_mstVertices.find(nFID);
547
0
    if (it != m_mstVertices.end())
548
0
        return &it->second.anOutEdgeFIDs;
549
0
    return nullptr;
550
0
}
551
552
GNMGFID GNMGraph::GetOppositVertex(GNMGFID nEdgeFID, GNMGFID nVertexFID) const
553
0
{
554
0
    std::map<GNMGFID, GNMStdEdge>::const_iterator it =
555
0
        m_mstEdges.find(nEdgeFID);
556
0
    if (it != m_mstEdges.end())
557
0
    {
558
0
        if (nVertexFID == it->second.nSrcVertexFID)
559
0
        {
560
0
            return it->second.nTgtVertexFID;
561
0
        }
562
0
        else if (nVertexFID == it->second.nTgtVertexFID)
563
0
        {
564
0
            return it->second.nSrcVertexFID;
565
0
        }
566
0
    }
567
0
    return -1;
568
0
}
569
570
void GNMGraph::TraceTargets(std::queue<GNMGFID> &vertexQueue,
571
                            std::set<GNMGFID> &markedVertIds,
572
                            GNMPATH &connectedIds)
573
0
{
574
0
    std::queue<GNMGFID> neighbours_queue;
575
576
    // See all given vertices except thouse that have been already seen.
577
0
    while (!vertexQueue.empty())
578
0
    {
579
0
        GNMGFID nCurVertID = vertexQueue.front();
580
581
        // There may be duplicate unmarked vertices in a current queue. Check
582
        // it.
583
0
        if (markedVertIds.find(nCurVertID) == markedVertIds.end())
584
0
        {
585
0
            markedVertIds.insert(nCurVertID);
586
587
            // See all outcome edges, add them to connected and than see the
588
            // target vertex of each edge. Add it to the queue, which will be
589
            // recursively seen the same way on the next iteration.
590
0
            LPGNMCONSTVECTOR panOutcomeEdgeIDs = GetOutEdges(nCurVertID);
591
0
            if (nullptr != panOutcomeEdgeIDs)
592
0
            {
593
0
                for (const GNMGFID nCurEdgeID : *panOutcomeEdgeIDs)
594
0
                {
595
                    // ISSUE: think about to return a sequence of vertices and
596
                    // edges (which is more universal), as now we are going to
597
                    // return only sequence of edges.
598
0
                    connectedIds.push_back(
599
0
                        std::make_pair(nCurVertID, nCurEdgeID));
600
601
                    // Get the only target vertex of this edge. If edge is
602
                    // bidirected get not that vertex that with nCurVertID.
603
0
                    GNMGFID nTargetVertID;
604
                    /*
605
                    std::vector<GNMGFID> target_vert_ids =
606
                    _getTgtVert(cur_edge_id); std::vector<GNMGFID>::iterator
607
                    itt; for (itt = target_vert_ids.begin(); itt !=
608
                    target_vert_ids.end(); ++itt)
609
                    {
610
                        if ((*itt) != cur_vert_id)
611
                        {
612
                            target_vert_id = *itt;
613
                            break;
614
                        }
615
                    }
616
                    */
617
0
                    nTargetVertID = GetOppositVertex(nCurEdgeID, nCurVertID);
618
619
                    // Avoid marked or blocked vertices.
620
0
                    if ((markedVertIds.find(nTargetVertID) ==
621
0
                         markedVertIds.end()) &&
622
0
                        (!CheckVertexBlocked(nTargetVertID)))
623
0
                        neighbours_queue.push(nTargetVertID);
624
0
                }
625
0
            }
626
0
        }
627
628
0
        vertexQueue.pop();
629
0
    }
630
631
0
    if (!neighbours_queue.empty())
632
0
        TraceTargets(neighbours_queue, markedVertIds, connectedIds);
633
0
}
634
635
//! @endcond