Statistics
| Revision:

root / trunk / extensions / extGraph_predes / src / com / iver / cit / gvsig / graph / solvers / ShortestPathSolverAStar.java @ 8523

History | View | Annotate | Download (17.5 KB)

1 8499 fjp
/* gvSIG. Sistema de Informaci?n Geogr?fica de la Generalitat Valenciana
2
 *
3
 * Copyright (C) 2004 IVER T.I. and Generalitat Valenciana.
4
 *
5
 * This program is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU General Public License
7
 * as published by the Free Software Foundation; either version 2
8
 * of the License, or (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,USA.
18
 *
19
 * For more information, contact:
20
 *
21
 *  Generalitat Valenciana
22
 *   Conselleria d'Infraestructures i Transport
23
 *   Av. Blasco Ib??ez, 50
24
 *   46010 VALENCIA
25
 *   SPAIN
26
 *
27
 *      +34 963862235
28
 *   gvsig@gva.es
29
 *      www.gvsig.gva.es
30
 *
31
 *    or
32
 *
33
 *   IVER T.I. S.A
34
 *   Salamanca 50
35
 *   46005 Valencia
36
 *   Spain
37
 *
38
 *   +34 963163400
39
 *   dac@iver.es
40
 */
41
package com.iver.cit.gvsig.graph.solvers;
42
43
import java.util.ArrayList;
44
import java.util.Stack;
45
46
import com.iver.cit.gvsig.fmap.DriverException;
47
import com.iver.cit.gvsig.fmap.core.IFeature;
48
import com.iver.cit.gvsig.fmap.core.IGeometry;
49
import com.iver.cit.gvsig.fmap.core.v02.FConverter;
50
import com.iver.cit.gvsig.fmap.layers.VectorialAdapter;
51
import com.iver.cit.gvsig.graph.GraphException;
52
import com.iver.cit.gvsig.graph.core.AbstractNetSolver;
53
import com.iver.cit.gvsig.graph.core.GvEdge;
54
import com.iver.cit.gvsig.graph.core.GvFlag;
55
import com.iver.cit.gvsig.graph.core.GvNode;
56
import com.iver.cit.gvsig.graph.core.GvTurn;
57
import com.iver.cit.gvsig.graph.core.IGraph;
58
import com.vividsolutions.jts.geom.Coordinate;
59
import com.vividsolutions.jts.geom.Geometry;
60
import com.vividsolutions.jts.geom.GeometryFactory;
61
import com.vividsolutions.jts.geom.LineString;
62
import com.vividsolutions.jts.geom.MultiLineString;
63
64
/**
65
 * @author fjp
66
 * Este es ?til solo cuando podemos calcular la distancia estimada entre
67
 * 2 nodos. (Es decir, para temas de cartograf?a). Para analizar relaciones
68
 * creo que no servir?a).
69
 */
70
public class ShortestPathSolverAStar extends AbstractNetSolver {
71
72
        private GeometryFactory geomFactory = new GeometryFactory();
73
74
        protected Route route = new Route();
75
        private int fieldIndexStreetName;
76
77
        private class InfoShp {
78
                public int idArc;
79
                public double pct;
80
                public double cost;
81
                public double distance;
82
                public int direction;
83
                public boolean bFlip;
84
85
        }
86
87
        public void setFielStreetName(String name) {
88
                try {
89
                        int aux = net.getLayer().getRecordset().getFieldIndexByName(name);
90
                        if (aux == -1)
91
                                throw new RuntimeException("Field " + name + " not found.");
92
                        fieldIndexStreetName = aux;
93
                } catch (com.hardcode.gdbms.engine.data.driver.DriverException e) {
94
                        // TODO Auto-generated catch block
95
                        e.printStackTrace();
96
                } catch (DriverException e) {
97
                        // TODO Auto-generated catch block
98
                        e.printStackTrace();
99
                }
100
101
        }
102
103
        /**
104
         * @return a list of features
105
         * @throws GraphException
106
         */
107
        public Route calculateRoute() throws GraphException {
108
                GvFlag[] flags = net.getFlags();
109
                if (flags.length == 0)
110
                        throw new RuntimeException("Please, add flags before");
111
                int desde = 0;
112
                int hasta = 1;
113
                double elCoste1 = 0;
114
                route = new Route();
115
                for (int i = 0; i < flags.length - 1; i++) {
116
                        GvFlag fFrom = flags[desde];
117
                        GvFlag fTo = flags[hasta];
118
119
                        if (fFrom != fTo) {
120
                                int idStart = net.creaArcosVirtuales(fFrom);
121
                                int idStop = net.creaArcosVirtuales(fTo);
122
123 8522 fjp
                                double newCost = AStar(idStart, idStop);
124 8499 fjp
                                elCoste1 += newCost;
125
126
                                if (newCost != Double.MAX_VALUE)
127
                                {
128
                                        try {
129
                                                populateRoute(fFrom, fTo, idStart, idStop);
130
                                        } catch (DriverException e) {
131
                                                e.printStackTrace();
132
                                                throw new GraphException(e);
133
                                        }
134
                                }
135
                                else
136
                                {
137
                                        // No way
138
                                }
139
140
                                net.reconstruyeTramo(fFrom.getIdArc());
141
                                net.reconstruyeTramo(fTo.getIdArc());
142
                                desde = hasta;
143
                        } // if son puntos distintos
144
                        hasta++;
145
                }
146
147
                return route;
148
        }
149
150
        private void populateRouteSimple(int idStart, int idEnd) throws DriverException {
151
                int idEnlace;
152
                GvNode node;
153
                GvEdge link;
154
                double costeEntrada;
155
156
                // Trazar el camino desde idEnd hasta idStart hacia atr?s marcando los Enlaces
157
                double Coste = 0;
158
                double Coste2 = 0;
159
                IGraph graph = net.getGraph();
160
                node = graph.getNodeByID(idEnd);
161
                int from_link = node.getFromLink();
162
                VectorialAdapter va = (VectorialAdapter) net.getLayer().getSource();
163
                while (node.getIdNode() != idStart)
164
                {
165
                        if (from_link == -1)
166
                        {
167
                                throw new RuntimeException("Fallo al recorrer de manera inversa la soluci?n. Encontrado arco con -1");
168
                                // break; // FALLO!!
169
                        }
170
                        link = graph.getEdgeByID(from_link);
171
                        IFeature feat = va.getFeature(link.getIdArc());
172
                        route.addRouteFeature(feat.getGeometry(), link.getIdArc(),
173
                                        link.getWeight(), link.getDistance(), feat.getAttribute(getFieldIndexStreetName()).toString());
174
175
                        node = graph.getNodeByID(link.getIdNodeOrig());
176
177
                        Coste = Coste + link.getWeight();
178
                        Coste2 = Coste2 + link.getDistance();
179
180
                        // TODO:
181
                        // from_link = node.get_from_link(idEnlace, &costeEntrada);
182
                        from_link = node.getFromLink();
183
184
            }
185
                System.out.println("Salgo con node = " + node.getIdNode());
186
        }
187
188
        private void populateRoute(GvFlag origin, GvFlag dest, int idStart, int idEnd) throws DriverException {
189
                int idEnlace;
190
                GvNode node;
191
                GvEdge link;
192
                double costeEntrada;
193
194
                // Trazar el camino desde idEnd hasta idStart hacia atr?s marcando los Enlaces
195
                IGraph graph = net.getGraph();
196
                node = graph.getNodeByID(idEnd);
197
                int from_link = node.getFromLink();
198
                VectorialAdapter va = (VectorialAdapter) net.getLayer().getSource();
199
200
                /*        Miramos los nodos de los tramos inicio y final, y cogemos el nodo que tenga el from_link rellenado. E IGUAL A NUMSOLUCGLOBAL!!!!
201
                        A partir de ah?, recorremos hacia atr?s y tenemos el cuerpo principal del shape. Luego, para
202
                        las puntas hay que tener en cuenta los porcentajes, viendo el trozo que est? pegando a los nodos
203
                        que sabemos que est?n en el path
204
                */
205
206
                /* 22/9/2003 Corregimos el fallo que hab?a de escribir el shape de atr?s adelante.
207
                *  Guardamos lo que necesitamos en listaShapes y recorremos esa lista guardando los tramos
208
                *        con el sentido adecuado.
209
                */
210
211
                /* 3/Febrero/2005 Limpieza de c?digo superfluo y correci?n de un fallo raro. Ahora es m?s simple y parece
212
                * que no falla. A ver si dura. IDEA: quiz?s se necesite meter el porcentaje en los arcos partidos.
213
                */
214
215
                // Define a template class for a vector of IDs.
216
                InfoShp infoShp;
217
                Stack pilaShapes = new Stack();
218
//                typedef stack<CInfoShp> PILAINFO;
219
//                PILAINFO pilaShapes;
220
221
                double costeTramoFinal=-1;
222
                GvNode nodeEnd = graph.getNodeByID(idEnd);
223
                GvEdge finalEdge = graph.getEdgeByID(nodeEnd.getFromLink());
224
                costeTramoFinal = finalEdge.getWeight();
225
226
                GvNode pNodo;
227
                GvEdge pEnlace;
228
229
                boolean bFlipearShape = false;
230
231
                double pctMax, pctMin;
232
233
234
235
                //////////////////////////////////////////////////////////////////////////////////////
236
                // Trozo del final
237
                // El shape va de idStop1 a idStop2, y el porcentaje viene en ese sentido.
238
                // Si el idEnd es idStop1, quiere decir que el tramo que falta va en ese sentido tambi?n,
239
                // as? que recorremos ese shape desde idStop1 hasta que rebasemos o igualemos ese porcentaje.
240
                // Si no, hemos pasado por idStop2 y la parte que hay que meter es desde el pto interior a idStop2
241
                ///////////////////////////////////////////////////////////////////////////////////////
242
                IFeature feat = va.getFeature(finalEdge.getIdArc());
243
                MultiLineString jtsGeom = (MultiLineString) feat.getGeometry().toJTSGeometry();
244
//                CoordinateFilter removeDuplicates = new UniqueCoordinateArrayFilter();
245
//                jtsGeom.apply(removeDuplicates);
246
//                jtsGeom.geometryChanged();
247
248
249
250
                // SI ESTAMOS SOBRE EL MISMO TRAMO, CASO PARTICULAR
251
                // y el sentido de circulaci?n es correcto
252
                if ((origin.getIdArc() == dest.getIdArc()) && (nodeEnd.getBestCost() <= costeTramoFinal))
253
                {
254
                        if (dest.getPct() > origin.getPct())
255
                        {
256
                                pctMax = dest.getPct();
257
                                pctMin = origin.getPct();
258
                        }
259
                        else
260
                        {
261
                                pctMax = origin.getPct();
262
                                pctMin = dest.getPct();
263
                                bFlipearShape = true;
264
                        }
265
266
                        LineString line = SituaSobreTramo(jtsGeom,dest.getIdArc(), dest.getPct(),1);
267
268
                        pctMin = pctMin / pctMax; // Porque ha cambiado la longitud del shape
269
270
                        line = SituaSobreTramo(line,dest.getIdArc(), pctMin,0);
271
272
                        if (bFlipearShape)
273
                                line = line.reverse();
274
275
                        IGeometry geom = FConverter.jts_to_igeometry(line);
276
                        // TODO: Calcular bien el length de este arco,
277
                        // basandonos en el porcentaje costeTramoFinal / costeOriginal
278
                        route.addRouteFeature(geom, origin.getIdArc(),
279
                                        costeTramoFinal, line.getLength(), feat.getAttribute(getFieldIndexStreetName()).toString());
280
281
282
                        return ; // Deber?a sacar el coste
283
                }
284
285
286
287
                // Trazar el camino desde idEnd hasta idStart hacia atr?s marcando los Enlaces
288
                pNodo = graph.getNodeByID(idEnd);
289
290
                while ((pNodo.getIdNode() != idStart))
291
                {
292
                        idEnlace = pNodo.getFromLink();
293
                        pEnlace = graph.getEdgeByID(idEnlace);
294
295
                        pNodo = graph.getNodeByID(pEnlace.getIdNodeOrig());
296
297
                        infoShp = new InfoShp();
298
                        infoShp.distance = pEnlace.getDistance();
299
                        infoShp.cost = pEnlace.getWeight();
300
301
                        if ((pEnlace.getIdArc() == origin.getIdArc()) || (pEnlace.getIdArc() == dest.getIdArc()))
302
                        {
303
                                if (pEnlace.getIdArc() == origin.getIdArc())
304
                                {
305
                                        infoShp.pct = origin.getPct();
306
                                        infoShp.idArc = origin.getIdArc();
307
                                        if (pEnlace.getDirec() == 0)
308
                                        {
309
                                                infoShp.direction = 1;
310
                                                infoShp.bFlip = true;
311
                                        }
312
                                        else // Hemos pasado por el 2
313
                                        {
314
                                                infoShp.direction = 0;
315
                                                infoShp.bFlip = false;
316
                                        } // if else */
317
                                }
318
                                else
319
                                {
320
                                        infoShp.pct = dest.getPct();
321
                                        infoShp.idArc = dest.getIdArc();
322
                                        if (pEnlace.getDirec() == 0)
323
                                        {
324
                                                infoShp.direction = 0;
325
                                                infoShp.bFlip = true;
326
                                        }
327
                                        else
328
                                        {
329
                                                infoShp.direction = 1;
330
                                                infoShp.bFlip = false;
331
                                        } // if else */
332
                                }
333
                        }
334
                        else
335
                        {
336
                                infoShp.pct = 1.0;
337
                                infoShp.idArc = pEnlace.getIdArc();
338
339
                                infoShp.direction =1;
340
                                if (pEnlace.getDirec() == 1)
341
                                        infoShp.bFlip = false;
342
                                else
343
                                        infoShp.bFlip = true;
344
                        }
345
346
                        pilaShapes.push(infoShp);
347
                }
348
349
                // Y ahora recorremos hacia atr?s el vector y escribimos los shapes.
350
                // VECTORINFO::iterator theIterator;
351
                int auxC = 0;
352
353
                while (!pilaShapes.empty())
354
                {
355
                        infoShp = (InfoShp) pilaShapes.peek();
356
                        feat = va.getFeature(infoShp.idArc);
357
                        MultiLineString line = (MultiLineString) feat.getGeometry().toJTSGeometry();
358
//                        line.apply(removeDuplicates);
359
//                        line.geometryChanged();
360
361
                        LineString aux = null;
362
                        if (infoShp.pct < 1.0)
363
                                aux = SituaSobreTramo(line,infoShp.idArc, infoShp.pct, infoShp.direction);
364
365
                        IGeometry geom = null;
366
                        if (aux == null)
367
                        {
368
                                if (infoShp.bFlip)
369
                                        line = line.reverse();
370
                                geom = FConverter.jts_to_igeometry(line);
371
                        }
372
                        else
373
                        {
374
                                if (infoShp.bFlip)
375
                                        aux = aux.reverse();
376
                                geom = FConverter.jts_to_igeometry(aux);
377
                        }
378
379
380
                        route.addRouteFeature(geom, infoShp.idArc,
381
                                        infoShp.cost, infoShp.distance, feat.getAttribute(getFieldIndexStreetName()).toString());
382
383
384
                        pilaShapes.pop();
385
                        auxC++;
386
387
                }
388
389
                return;
390
391
392
        }
393
394
        LineString SituaSobreTramo(Geometry geom, int idArc, double pct, int parte)
395
//         Si parte vale cero, los v?lidos son los primeros. Si no, los segundos.
396
        {
397
                int j, numVertices;
398
                double longAcum, longReal, longBuscada, distSobre, miniPorcentaje;
399
                double nuevaX, nuevaY; // Por cuestiones de claridad al programar
400
                double dist=0;
401
402
                longAcum = 0;
403
                longReal = geom.getLength();
404
                longBuscada = longReal * pct;
405
                Coordinate[] coords = geom.getCoordinates();
406
                Coordinate c1 = null, c2 = null;
407
                ArrayList savedCoords = new ArrayList();
408
409
                 if (parte > 0) // Hemos entrado por el 1 hacia el 2 (al 2 no llegamos)
410
                {
411
                        for( j = 0; j < coords.length-1; j++ )
412
                        {
413
                                c1 = coords[j];
414
                                c2 = coords[j+1];
415
                                dist = c1.distance(c2);
416
                                longAcum += dist;
417
                                savedCoords.add(c1);
418
                                if (longAcum >= longBuscada)
419
                                {
420
                                        // Hasta aqu?. Ahora ahi que poner el punto sobre el tramo
421
                                        distSobre = dist - (longAcum - longBuscada);
422
                                        miniPorcentaje = distSobre/dist;
423
424
                                        nuevaX = c1.x + (c2.x-c1.x) * miniPorcentaje;
425
                                        nuevaY = c1.y + (c2.y-c1.y) * miniPorcentaje;
426
427
                                        savedCoords.add(new Coordinate(nuevaX, nuevaY));
428
                                        break;
429
                                } // if longAcum >= longBuscada
430
                        } // for j
431
432
                }
433
                else // Hemos entrado por el 2 hacia el 1
434
                {
435
                        numVertices = 0;
436
                        for( j = 0; j < coords.length; j++ )
437
                        {
438
                                ////////////////////////////////////////////////////////////////
439
                                // 13_ene_2005: Si el ?ltimo punto es el ?ltimo punto no
440
                                // podemos acceder al elemento j+1 porque nos salimos del shape
441
                                /////////////////////////////////////////////////////////////////
442
                                c1 = coords[j];
443
                                if (j < coords.length -1)
444
                                {
445
                                        c2 = coords[j+1];
446
447
                                        dist = c1.distance(c2);
448
                                        longAcum += dist;
449
                                }
450
451
                                if (longAcum >= longBuscada)
452
                                {
453
                                        // Hasta aqu?. Empezamos a meter puntos
454
455
                                        if (numVertices == 0)
456
                                        {
457
                                                distSobre = dist - (longAcum - longBuscada);
458
                                                miniPorcentaje = distSobre/dist;
459
                                                nuevaX = c1.x + (c2.x-c1.x) * miniPorcentaje;
460
                                                nuevaY = c1.y + (c2.y-c1.y) * miniPorcentaje;
461
462
                                                savedCoords.add(new Coordinate(nuevaX, nuevaY));
463
                                        }
464
                                        else
465
                                        {
466
                                                savedCoords.add(c2);
467
                                        }
468
                                        numVertices ++;
469
                                        // break;
470
                                } // if longAcum >= longBuscada
471
                        } // for j
472
473
                        // savedCoords.add(c2);
474
475
                } // if else
476
477
478
                 return geomFactory.createLineString((Coordinate[] )savedCoords.toArray(new Coordinate[0]));
479
        }
480
481
        private int getFieldIndexStreetName() {
482
                return fieldIndexStreetName;
483
        }
484
485
        private double AStar(int idStart, int idStop) {
486
                int nodeNum;
487
                int linkNum;
488
                double newCost;
489
                int idSiguienteNodo;
490
                GvNode node, toNode, finalNode, bestNode; // , *pNodoProv;
491
                GvEdge link;
492
                boolean bExit = false;
493
494
                boolean bGiroProhibido;
495
                ArrayList candidatos = new ArrayList();
496
497
                // char Mensaje[200];
498
499
                IGraph graph = net.getGraph();
500
501
                // NUEVO: 27-6-2003
502
                // Cada nodo y cada arco llevan un nuemero de soluci?n. Se supone
503
                // que si lo del nodo y el arco no coincide con
504
                // este numero, todav?a no ha sido inicializado y hay que hacerlo.
505
                // Para evitar coincidencias cuando de la vuelta el contador, cada
506
                // 65000 peticiones (por ejemplo), repasamos toda
507
                // la red y ponemos numSolucGlobal a -1
508
                if (numSolucGlobal > 65000) {
509
                        numSolucGlobal = -1;
510
511
                        for (nodeNum = 0; nodeNum < graph.numVertices(); nodeNum++) {
512
                                node = graph.getNodeByID(nodeNum);
513
                                node.initialize();
514
                        } // for nodeNum */
515
516
                }
517
                numSolucGlobal++;
518
519
                candidatos.clear();
520
                // A?adimos el Start Node a la lista de candidatosSTL
521
                // Nodo final
522
                finalNode = graph.getNodeByID(idStop);
523
                finalNode.initialize();
524
525
                node = graph.getNodeByID(idStart);
526
                node.initialize();
527
                bestNode = node;
528
529
                candidatos.add(node);
530
                node.setBestCost(0);
531
                node.setStatus(GvNode.statNowInList);
532 8523 fjp
                node.calculateStimation(finalNode, 0);
533 8522 fjp
534 8499 fjp
                // Mientras que la lista de candidatosSTL no est? vac?a, procesamos
535
                // Nodos
536 8522 fjp
                double bestStimation;
537 8499 fjp
538
                while ((!bExit) && (candidatos.size() > 0)) {
539
                        // Buscamos el nodo con m?nimo coste
540
                        node = (GvNode) candidatos.get(0);
541
                        bestNode = node;
542 8522 fjp
                        bestStimation = node.getStimation();
543 8499 fjp
                        for (nodeNum = 1; nodeNum < candidatos.size(); nodeNum++) {
544
                                node = (GvNode) candidatos.get(nodeNum);
545 8522 fjp
                                if (node.getStimation() < bestStimation) {
546
                                        bestStimation = node.getStimation();
547 8499 fjp
                                        bestNode = node;
548
                                }
549
                        } // for nodeNum candidatosSTL
550
551
                        node = bestNode;
552
                        // Borramos el mejor nodo de la lista de candidatosSTL
553
                        node.setStatus(GvNode.statWasInList);
554
                        // TODO: BORRAR POR INDEX, NO AS?. ES M?S LENTO QUE SI BORRAMOS EL i-?simo.
555
                        candidatos.remove(node);
556
                        // System.out.println("LINK " + link.getIdArc() + " from ");
557
                        // System.out.println("from " + idStart + " to " + finalNode.getIdNode() + ". node=" + node.getIdNode());
558
                        // Miramos si hemos llegado donde quer?amos
559
                        if (bestNode.getIdNode() == idStop) {
560
                                bExit = true;
561
                                break;
562
                        }
563
564
                        // sprintf(Mensaje,"Enlaces en el nodo %ld:
565
                        // %ld.",pNodo->idNodo,pNodo->Enlaces.GetSize());
566
                        // AfxMessageBox(Mensaje);
567
568
                        // A?adimos a la lista de candidatosSTL los vecinos del nodo que
569
                        // acabamos de borrar
570
                        // HAY Arcos QUE SALEN Y Arcos QUE LLEGAN. SOLO MIRAMOS LOS QUE
571
                        // SALEN.
572
                        for (linkNum = 0; linkNum < node.getEnlaces().size(); linkNum++) {
573
                                // Pillamos el nodo vecino
574
                                link = (GvEdge) node.getEnlaces().get(linkNum);
575
                                idSiguienteNodo = link.getIdNodeEnd();
576
577
                                toNode = graph.getNodeByID(idSiguienteNodo);
578
579
                                // 27_5_2004
580
                                // Si un arco tiene coste negativo, lo ignoramos
581
                                if (link.getWeight() < 0)
582
                                        continue;
583
584
                                // Fin arco con coste negativo
585
586
                                // NUEVO: 26-7-2003: Comprobamos si est? inicializado
587
                                if (toNode.getNumSoluc() != numSolucGlobal) {
588
                                        toNode.initialize();
589
                                }
590
                                else
591
                                {
592
                                        // System.out.println("Nodo ya inicializado");
593
                                }
594
595 8522 fjp
                                // Miramos a ver si podemos mejorar su best_cost
596
                                newCost = node.getBestCost() + link.getWeight();
597 8499 fjp
598 8522 fjp
                                if (newCost < toNode.getBestCost()) {
599
                                        // Es una mejora, as? que actualizamos el vecino y
600
                                        // lo a?adimos a los candidatosSTL
601
                                        toNode.setBestCost(newCost);
602
                                        toNode.setFromLink(link.getIdEdge());
603
604 8523 fjp
                                        toNode.calculateStimation(finalNode, newCost);
605 8499 fjp
606 8522 fjp
                                        if (toNode.getStatus() != GvNode.statNowInList) {
607
                                                toNode.setStatus(GvNode.statNowInList);
608
                                                candidatos.add(toNode);
609
                                        }
610
                                } // Si hay mejora
611 8499 fjp
612
                        } // for linkNum
613
                } // while candidatosSTL
614
615
                newCost = finalNode.getBestCost();
616
617
                return newCost;
618
        }
619
620
}