Routino SVN Repository Browser

Check out the latest version of Routino: svn co http://routino.org/svn/trunk routino

ViewVC logotype

Contents of /trunk/src/optimiser.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 435 - (show annotations) (download) (as text)
Tue Jul 6 19:28:27 2010 UTC (14 years, 9 months ago) by amb
File MIME type: text/x-csrc
File size: 22816 byte(s)
Don't crash if the middle part of the route can't be found but exit cleanly.

1 /***************************************
2 $Header: /home/amb/CVS/routino/src/optimiser.c,v 1.85 2010-07-06 19:28:27 amb Exp $
3
4 Routing optimiser.
5
6 Part of the Routino routing software.
7 ******************/ /******************
8 This file Copyright 2008-2010 Andrew M. Bishop
9
10 This program is free software: you can redistribute it and/or modify
11 it under the terms of the GNU Affero General Public License as published by
12 the Free Software Foundation, either version 3 of the License, or
13 (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU Affero General Public License for more details.
19
20 You should have received a copy of the GNU Affero General Public License
21 along with this program. If not, see <http://www.gnu.org/licenses/>.
22 ***************************************/
23
24
25 #include <stdio.h>
26
27 #include "types.h"
28 #include "functions.h"
29 #include "nodes.h"
30 #include "segments.h"
31 #include "ways.h"
32 #include "results.h"
33
34
35 /*+ The option not to print any progress information. +*/
36 extern int option_quiet;
37
38 /*+ The option to calculate the quickest route insted of the shortest. +*/
39 extern int option_quickest;
40
41
42 /*++++++++++++++++++++++++++++++++++++++
43 Find the optimum route between two nodes not passing through a super-node.
44
45 Results *FindNormalRoute Returns a set of results.
46
47 Nodes *nodes The set of nodes to use.
48
49 Segments *segments The set of segments to use.
50
51 Ways *ways The set of ways to use.
52
53 index_t start The start node.
54
55 index_t finish The finish node.
56
57 Profile *profile The profile containing the transport type, speeds and allowed highways.
58 ++++++++++++++++++++++++++++++++++++++*/
59
60 Results *FindNormalRoute(Nodes *nodes,Segments *segments,Ways *ways,index_t start,index_t finish,Profile *profile)
61 {
62 Results *results;
63 Queue *queue;
64 index_t node1,node2;
65 score_t finish_score;
66 double finish_lat,finish_lon;
67 Result *result1,*result2;
68 Segment *segment;
69 Way *way;
70
71 /* Set up the finish conditions */
72
73 finish_score=INF_SCORE;
74
75 if(IsFakeNode(finish))
76 GetFakeLatLong(finish,&finish_lat,&finish_lon);
77 else
78 GetLatLong(nodes,finish,&finish_lat,&finish_lon);
79
80 /* Create the list of results and insert the first node into the queue */
81
82 results=NewResultsList(8);
83
84 results->start=start;
85 results->finish=finish;
86
87 result1=InsertResult(results,start);
88
89 ZeroResult(result1);
90
91 queue=NewQueueList();
92
93 InsertInQueue(queue,result1);
94
95 /* Loop across all nodes in the queue */
96
97 while((result1=PopFromQueue(queue)))
98 {
99 if(result1->score>finish_score)
100 continue;
101
102 node1=result1->node;
103
104 if(IsFakeNode(node1))
105 segment=FirstFakeSegment(node1);
106 else
107 segment=FirstSegment(segments,nodes,node1);
108
109 while(segment)
110 {
111 score_t segment_pref,segment_score,cumulative_score;
112 int i;
113
114 node2=OtherNode(segment,node1); /* need this here because we use node2 later */
115
116 if(!IsNormalSegment(segment))
117 goto endloop;
118
119 if(profile->oneway && IsOnewayTo(segment,node1))
120 goto endloop;
121
122 if(result1->prev==node2)
123 goto endloop;
124
125 if(node2!=finish && !IsFakeNode(node2) && IsSuperNode(nodes,node2))
126 goto endloop;
127
128 way=LookupWay(ways,segment->way);
129
130 if(!(way->allow&profile->allow))
131 goto endloop;
132
133 if(!profile->highway[HIGHWAY(way->type)])
134 goto endloop;
135
136 if(way->weight && way->weight<profile->weight)
137 goto endloop;
138
139 if((way->height && way->height<profile->height) ||
140 (way->width && way->width <profile->width ) ||
141 (way->length && way->length<profile->length))
142 goto endloop;
143
144 segment_pref=profile->highway[HIGHWAY(way->type)];
145
146 for(i=1;i<Property_Count;i++)
147 if(ways->props & PROPERTIES(i))
148 {
149 if(way->props & PROPERTIES(i))
150 segment_pref*=profile->props_yes[i];
151 else
152 segment_pref*=profile->props_no[i];
153 }
154
155 if(segment_pref==0)
156 goto endloop;
157
158 if(option_quickest==0)
159 segment_score=(score_t)DISTANCE(segment->distance)/segment_pref;
160 else
161 segment_score=(score_t)Duration(segment,way,profile)/segment_pref;
162
163 cumulative_score=result1->score+segment_score;
164
165 if(cumulative_score>finish_score)
166 goto endloop;
167
168 result2=FindResult(results,node2);
169
170 if(!result2) /* New end node */
171 {
172 result2=InsertResult(results,node2);
173 result2->prev=node1;
174 result2->next=NO_NODE;
175 result2->score=cumulative_score;
176 result2->segment=segment;
177
178 if(node2==finish)
179 {
180 finish_score=cumulative_score;
181 }
182 else
183 {
184 result2->sortby=result2->score;
185
186 InsertInQueue(queue,result2);
187 }
188 }
189 else if(cumulative_score<result2->score) /* New end node is better */
190 {
191 result2->prev=node1;
192 result2->score=cumulative_score;
193 result2->segment=segment;
194
195 if(node2==finish)
196 {
197 finish_score=cumulative_score;
198 }
199 else
200 {
201 result2->sortby=result2->score;
202
203 if(result2->score<finish_score)
204 InsertInQueue(queue,result2);
205 }
206 }
207
208 endloop:
209
210 if(IsFakeNode(node1))
211 segment=NextFakeSegment(segment,node1);
212 else if(IsFakeNode(node2))
213 segment=NULL; /* cannot call NextSegment() with a fake segment */
214 else
215 {
216 segment=NextSegment(segments,segment,node1);
217
218 if(!segment && IsFakeNode(finish))
219 segment=ExtraFakeSegment(node1,finish);
220 }
221 }
222 }
223
224 FreeQueueList(queue);
225
226 /* Check it worked */
227
228 if(!FindResult(results,finish))
229 {
230 FreeResultsList(results);
231 return(NULL);
232 }
233
234 FixForwardRoute(results,finish);
235
236 return(results);
237 }
238
239
240 /*++++++++++++++++++++++++++++++++++++++
241 Find the optimum route between two nodes where the start and end are a set of pre-routed super-nodes.
242
243 Results *FindMiddleRoute Returns a set of results.
244
245 Nodes *nodes The set of nodes to use.
246
247 Segments *segments The set of segments to use.
248
249 Ways *ways The set of ways to use.
250
251 Results *begin The initial portion of the route.
252
253 Results *end The final portion of the route.
254
255 Profile *profile The profile containing the transport type, speeds and allowed highways.
256 ++++++++++++++++++++++++++++++++++++++*/
257
258 Results *FindMiddleRoute(Nodes *nodes,Segments *segments,Ways *ways,Results *begin,Results *end,Profile *profile)
259 {
260 Results *results;
261 Queue *queue;
262 index_t node1,node2;
263 index_t end_prev;
264 score_t finish_score;
265 double finish_lat,finish_lon;
266 Result *result1,*result2,*result3;
267 Segment *segment;
268 Way *way;
269
270 if(!option_quiet)
271 {
272 printf("Routing: Super-Nodes checked = 0");
273 fflush(stdout);
274 }
275
276 /* Set up the finish conditions */
277
278 finish_score=INF_DISTANCE;
279 end_prev=NO_NODE;
280
281 if(IsFakeNode(end->finish))
282 GetFakeLatLong(end->finish,&finish_lat,&finish_lon);
283 else
284 GetLatLong(nodes,end->finish,&finish_lat,&finish_lon);
285
286 /* Create the list of results and insert the first node into the queue */
287
288 results=NewResultsList(65536);
289
290 results->start=begin->start;
291 results->finish=end->finish;
292
293 result1=InsertResult(results,begin->start);
294 result3=FindResult(begin,begin->start);
295
296 *result1=*result3;
297
298 queue=NewQueueList();
299
300 /* Insert the finish points of the beginning part of the path into the queue */
301
302 result3=FirstResult(begin);
303
304 while(result3)
305 {
306 if(result3->node!=begin->start && !IsFakeNode(result3->node) && IsSuperNode(nodes,result3->node))
307 {
308 result2=InsertResult(results,result3->node);
309
310 *result2=*result3;
311
312 result2->prev=begin->start;
313
314 result2->sortby=result2->score;
315
316 InsertInQueue(queue,result2);
317 }
318
319 result3=NextResult(begin,result3);
320 }
321
322 if(begin->number==1)
323 InsertInQueue(queue,result1);
324
325 /* Loop across all nodes in the queue */
326
327 while((result1=PopFromQueue(queue)))
328 {
329 if(result1->score>finish_score)
330 continue;
331
332 node1=result1->node;
333
334 segment=FirstSegment(segments,nodes,node1);
335
336 while(segment)
337 {
338 score_t segment_pref,segment_score,cumulative_score;
339 int i;
340
341 if(!IsSuperSegment(segment))
342 goto endloop;
343
344 if(profile->oneway && IsOnewayTo(segment,node1))
345 goto endloop;
346
347 node2=OtherNode(segment,node1);
348
349 if(result1->prev==node2)
350 goto endloop;
351
352 way=LookupWay(ways,segment->way);
353
354 if(!(way->allow&profile->allow))
355 goto endloop;
356
357 if(!profile->highway[HIGHWAY(way->type)])
358 goto endloop;
359
360 if(way->weight && way->weight<profile->weight)
361 goto endloop;
362
363 if((way->height && way->height<profile->height) ||
364 (way->width && way->width <profile->width ) ||
365 (way->length && way->length<profile->length))
366 goto endloop;
367
368 segment_pref=profile->highway[HIGHWAY(way->type)];
369
370 for(i=1;i<Property_Count;i++)
371 if(ways->props & PROPERTIES(i))
372 {
373 if(way->props & PROPERTIES(i))
374 segment_pref*=profile->props_yes[i];
375 else
376 segment_pref*=profile->props_no[i];
377 }
378
379 if(segment_pref==0)
380 goto endloop;
381
382 if(option_quickest==0)
383 segment_score=(score_t)DISTANCE(segment->distance)/segment_pref;
384 else
385 segment_score=(score_t)Duration(segment,way,profile)/segment_pref;
386
387 cumulative_score=result1->score+segment_score;
388
389 if(cumulative_score>finish_score)
390 goto endloop;
391
392 result2=FindResult(results,node2);
393
394 if(!result2) /* New end node */
395 {
396 result2=InsertResult(results,node2);
397 result2->prev=node1;
398 result2->next=NO_NODE;
399 result2->score=cumulative_score;
400 result2->segment=segment;
401
402 if((result3=FindResult(end,node2)))
403 {
404 finish_score=result2->score+result3->score;
405 end_prev=node2;
406 }
407 else
408 {
409 double lat,lon;
410 distance_t direct;
411
412 GetLatLong(nodes,node2,&lat,&lon);
413 direct=Distance(lat,lon,finish_lat,finish_lon);
414
415 if(option_quickest==0)
416 result2->sortby=result2->score+(score_t)direct/profile->max_pref;
417 else
418 result2->sortby=result2->score+(score_t)distance_speed_to_duration(direct,profile->max_speed)/profile->max_pref;
419
420 InsertInQueue(queue,result2);
421 }
422 }
423 else if(cumulative_score<result2->score) /* New end node is better */
424 {
425 result2->prev=node1;
426 result2->score=cumulative_score;
427 result2->segment=segment;
428
429 if((result3=FindResult(end,node2)))
430 {
431 if((result2->score+result3->score)<finish_score)
432 {
433 finish_score=result2->score+result3->score;
434 end_prev=node2;
435 }
436 }
437 else if(result2->score<finish_score)
438 {
439 double lat,lon;
440 distance_t direct;
441
442 GetLatLong(nodes,node2,&lat,&lon);
443 direct=Distance(lat,lon,finish_lat,finish_lon);
444
445 if(option_quickest==0)
446 result2->sortby=result2->score+(score_t)direct/profile->max_pref;
447 else
448 result2->sortby=result2->score+(score_t)distance_speed_to_duration(direct,profile->max_speed)/profile->max_pref;
449
450 InsertInQueue(queue,result2);
451 }
452 }
453
454 endloop:
455
456 if(!option_quiet && !(results->number%10000))
457 {
458 printf("\rRouting: Super-Nodes checked = %d",results->number);
459 fflush(stdout);
460 }
461
462 segment=NextSegment(segments,segment,node1);
463 }
464 }
465
466 if(!option_quiet)
467 {
468 printf("\rRouting: Super-Nodes checked = %d\n",results->number);
469 fflush(stdout);
470 }
471
472 /* Finish off the end part of the route. */
473
474 if(!FindResult(results,end->finish) && end_prev!=NO_NODE)
475 {
476 result2=InsertResult(results,end->finish);
477 result3=FindResult(end,end->finish);
478
479 *result2=*result3;
480
481 result2->prev=end_prev;
482 result2->score=finish_score;
483 }
484
485 FreeQueueList(queue);
486
487 /* Check it worked */
488
489 if(end_prev==NO_NODE)
490 {
491 FreeResultsList(results);
492 return(NULL);
493 }
494
495 FixForwardRoute(results,end->finish);
496
497 return(results);
498 }
499
500
501 /*++++++++++++++++++++++++++++++++++++++
502 Find all routes from a specified node to any super-node.
503
504 Results *FindStartRoutes Returns a set of results.
505
506 Nodes *nodes The set of nodes to use.
507
508 Segments *segments The set of segments to use.
509
510 Ways *ways The set of ways to use.
511
512 index_t start The start node.
513
514 Profile *profile The profile containing the transport type, speeds and allowed highways.
515 ++++++++++++++++++++++++++++++++++++++*/
516
517 Results *FindStartRoutes(Nodes *nodes,Segments *segments,Ways *ways,index_t start,Profile *profile)
518 {
519 Results *results;
520 Queue *queue;
521 index_t node1,node2;
522 Result *result1,*result2;
523 Segment *segment;
524 Way *way;
525
526 /* Insert the first node into the queue */
527
528 results=NewResultsList(8);
529
530 results->start=start;
531
532 result1=InsertResult(results,start);
533
534 ZeroResult(result1);
535
536 queue=NewQueueList();
537
538 InsertInQueue(queue,result1);
539
540 /* Loop across all nodes in the queue */
541
542 while((result1=PopFromQueue(queue)))
543 {
544 node1=result1->node;
545
546 if(IsFakeNode(node1))
547 segment=FirstFakeSegment(node1);
548 else
549 segment=FirstSegment(segments,nodes,node1);
550
551 while(segment)
552 {
553 score_t segment_pref,segment_score,cumulative_score;
554 int i;
555
556 if(!IsNormalSegment(segment))
557 goto endloop;
558
559 if(profile->oneway && IsOnewayTo(segment,node1))
560 goto endloop;
561
562 node2=OtherNode(segment,node1);
563
564 if(result1->prev==node2)
565 goto endloop;
566
567 way=LookupWay(ways,segment->way);
568
569 if(!(way->allow&profile->allow))
570 goto endloop;
571
572 if(!profile->highway[HIGHWAY(way->type)])
573 goto endloop;
574
575 if(way->weight && way->weight<profile->weight)
576 goto endloop;
577
578 if((way->height && way->height<profile->height) ||
579 (way->width && way->width <profile->width ) ||
580 (way->length && way->length<profile->length))
581 goto endloop;
582
583 segment_pref=profile->highway[HIGHWAY(way->type)];
584
585 for(i=1;i<Property_Count;i++)
586 if(ways->props & PROPERTIES(i))
587 {
588 if(way->props & PROPERTIES(i))
589 segment_pref*=profile->props_yes[i];
590 else
591 segment_pref*=profile->props_no[i];
592 }
593
594 if(segment_pref==0)
595 goto endloop;
596
597 if(option_quickest==0)
598 segment_score=(score_t)DISTANCE(segment->distance)/segment_pref;
599 else
600 segment_score=(score_t)Duration(segment,way,profile)/segment_pref;
601
602 cumulative_score=result1->score+segment_score;
603
604 result2=FindResult(results,node2);
605
606 if(!result2) /* New end node */
607 {
608 result2=InsertResult(results,node2);
609 result2->prev=node1;
610 result2->next=NO_NODE;
611 result2->score=cumulative_score;
612 result2->segment=segment;
613
614 if(!IsFakeNode(node2) && !IsSuperNode(nodes,node2))
615 {
616 result2->sortby=result2->score;
617 InsertInQueue(queue,result2);
618 }
619 }
620 else if(cumulative_score<result2->score) /* New end node is better */
621 {
622 result2->prev=node1;
623 result2->score=cumulative_score;
624 result2->segment=segment;
625
626 if(!IsFakeNode(node2) && !IsSuperNode(nodes,node2))
627 {
628 result2->sortby=result2->score;
629 InsertInQueue(queue,result2);
630 }
631 }
632
633 endloop:
634
635 if(IsFakeNode(node1))
636 segment=NextFakeSegment(segment,node1);
637 else
638 segment=NextSegment(segments,segment,node1);
639 }
640 }
641
642 FreeQueueList(queue);
643
644 /* Check it worked */
645
646 if(results->number==1)
647 {
648 FreeResultsList(results);
649 return(NULL);
650 }
651
652 return(results);
653 }
654
655
656 /*++++++++++++++++++++++++++++++++++++++
657 Find all routes from any super-node to a specific node.
658
659 Results *FindFinishRoutes Returns a set of results.
660
661 Nodes *nodes The set of nodes to use.
662
663 Segments *segments The set of segments to use.
664
665 Ways *ways The set of ways to use.
666
667 index_t finish The finishing node.
668
669 Profile *profile The profile containing the transport type, speeds and allowed highways.
670 ++++++++++++++++++++++++++++++++++++++*/
671
672 Results *FindFinishRoutes(Nodes *nodes,Segments *segments,Ways *ways,index_t finish,Profile *profile)
673 {
674 Results *results;
675 Queue *queue;
676 index_t node1,node2;
677 Result *result1,*result2;
678 Segment *segment;
679 Way *way;
680
681 /* Insert the first node into the queue */
682
683 results=NewResultsList(8);
684
685 results->finish=finish;
686
687 result1=InsertResult(results,finish);
688
689 ZeroResult(result1);
690
691 queue=NewQueueList();
692
693 InsertInQueue(queue,result1);
694
695 /* Loop across all nodes in the queue */
696
697 while((result1=PopFromQueue(queue)))
698 {
699 node1=result1->node;
700
701 if(IsFakeNode(node1))
702 segment=FirstFakeSegment(node1);
703 else
704 segment=FirstSegment(segments,nodes,node1);
705
706 while(segment)
707 {
708 score_t segment_pref,segment_score,cumulative_score;
709 int i;
710
711 if(!IsNormalSegment(segment))
712 goto endloop;
713
714 if(profile->oneway && IsOnewayFrom(segment,node1))
715 goto endloop;
716
717 node2=OtherNode(segment,node1);
718
719 if(result1->next==node2)
720 goto endloop;
721
722 way=LookupWay(ways,segment->way);
723
724 if(!(way->allow&profile->allow))
725 goto endloop;
726
727 if(!profile->highway[HIGHWAY(way->type)])
728 goto endloop;
729
730 if(way->weight && way->weight<profile->weight)
731 goto endloop;
732
733 if((way->height && way->height<profile->height) ||
734 (way->width && way->width <profile->width ) ||
735 (way->length && way->length<profile->length))
736 goto endloop;
737
738 segment_pref=profile->highway[HIGHWAY(way->type)];
739
740 for(i=1;i<Property_Count;i++)
741 if(ways->props & PROPERTIES(i))
742 {
743 if(way->props & PROPERTIES(i))
744 segment_pref*=profile->props_yes[i];
745 else
746 segment_pref*=profile->props_no[i];
747 }
748
749 if(segment_pref==0)
750 goto endloop;
751
752 if(option_quickest==0)
753 segment_score=(score_t)DISTANCE(segment->distance)/segment_pref;
754 else
755 segment_score=(score_t)Duration(segment,way,profile)/segment_pref;
756
757 cumulative_score=result1->score+segment_score;
758
759 result2=FindResult(results,node2);
760
761 if(!result2) /* New end node */
762 {
763 result2=InsertResult(results,node2);
764 result2->prev=NO_NODE;
765 result2->next=node1;
766 result2->score=cumulative_score;
767 result2->segment=segment;
768
769 if(!IsFakeNode(node2) && !IsSuperNode(nodes,node2))
770 {
771 result2->sortby=result2->score;
772 InsertInQueue(queue,result2);
773 }
774 }
775 else if(cumulative_score<result2->score) /* New end node is better */
776 {
777 result2->next=node1;
778 result2->score=cumulative_score;
779 result2->segment=segment;
780
781 if(!IsFakeNode(node2) && !IsSuperNode(nodes,node2))
782 {
783 result2->sortby=result2->score;
784 InsertInQueue(queue,result2);
785 }
786 }
787
788 endloop:
789
790 if(IsFakeNode(node1))
791 segment=NextFakeSegment(segment,node1);
792 else
793 segment=NextSegment(segments,segment,node1);
794 }
795 }
796
797 FreeQueueList(queue);
798
799 /* Check it worked */
800
801 if(results->number==1)
802 {
803 FreeResultsList(results);
804 return(NULL);
805 }
806
807 return(results);
808 }
809
810
811 /*++++++++++++++++++++++++++++++++++++++
812 Create an optimum route given the set of super-nodes to follow.
813
814 Results *CombineRoutes Returns the results from joining the super-nodes.
815
816 Results *results The set of results from the super-nodes.
817
818 Nodes *nodes The list of nodes.
819
820 Segments *segments The set of segments to use.
821
822 Ways *ways The list of ways.
823
824 Profile *profile The profile containing the transport type, speeds and allowed highways.
825 ++++++++++++++++++++++++++++++++++++++*/
826
827 Results *CombineRoutes(Results *results,Nodes *nodes,Segments *segments,Ways *ways,Profile *profile)
828 {
829 Result *result1,*result2,*result3,*result4;
830 Results *combined;
831
832 combined=NewResultsList(64);
833
834 combined->start=results->start;
835 combined->finish=results->finish;
836
837 /* Sort out the combined route */
838
839 result1=FindResult(results,results->start);
840
841 result3=InsertResult(combined,results->start);
842
843 ZeroResult(result3);
844
845 do
846 {
847 if(result1->next!=NO_NODE)
848 {
849 Results *results2=FindNormalRoute(nodes,segments,ways,result1->node,result1->next,profile);
850
851 result2=FindResult(results2,result1->node);
852
853 result3->next=result2->next;
854
855 result2=FindResult(results2,result2->next);
856
857 do
858 {
859 result4=InsertResult(combined,result2->node);
860
861 *result4=*result2;
862 result4->score+=result3->score;
863
864 if(result2->next!=NO_NODE)
865 result2=FindResult(results2,result2->next);
866 else
867 result2=NULL;
868 }
869 while(result2);
870
871 FreeResultsList(results2);
872
873 result1=FindResult(results,result1->next);
874
875 result3=result4;
876 }
877 else
878 result1=NULL;
879 }
880 while(result1);
881
882 return(combined);
883 }
884
885
886 /*++++++++++++++++++++++++++++++++++++++
887 Fx the forward route (i.e. setup next nodes for forward path from prev nodes on reverse path).
888
889 Results *results The set of results to update.
890
891 index_t finish The finish point.
892 ++++++++++++++++++++++++++++++++++++++*/
893
894 void FixForwardRoute(Results *results,index_t finish)
895 {
896 Result *result2=FindResult(results,finish);
897 Result *result1;
898
899 /* Create the forward links for the optimum path */
900
901 do
902 {
903 if(result2->prev!=NO_NODE)
904 {
905 index_t node1=result2->prev;
906
907 result1=FindResult(results,node1);
908
909 result1->next=result2->node;
910
911 result2=result1;
912 }
913 else
914 result2=NULL;
915 }
916 while(result2);
917
918 results->finish=finish;
919 }

Properties

Name Value
cvs:description Route optimiser.