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/nodesx.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1317 - (show annotations) (download) (as text)
Sun May 12 18:00:25 2013 UTC (11 years, 10 months ago) by amb
File MIME type: text/x-csrc
File size: 18784 byte(s)
Add functions to process the binary error log file and convert it into a
geographically searchable database.

1 /***************************************
2 Extented Node data type functions.
3
4 Part of the Routino routing software.
5 ******************/ /******************
6 This file Copyright 2008-2013 Andrew M. Bishop
7
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU Affero General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU Affero General Public License for more details.
17
18 You should have received a copy of the GNU Affero General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ***************************************/
21
22
23 #include <stdlib.h>
24 #include <string.h>
25
26 #include "types.h"
27 #include "nodes.h"
28
29 #include "typesx.h"
30 #include "nodesx.h"
31 #include "segmentsx.h"
32 #include "waysx.h"
33
34 #include "files.h"
35 #include "logging.h"
36 #include "sorting.h"
37
38
39 /* Global variables */
40
41 /*+ The command line '--tmpdir' option or its default value. +*/
42 extern char *option_tmpdirname;
43
44 /* Local variables */
45
46 /*+ Temporary file-local variables for use by the sort functions. +*/
47 static NodesX *sortnodesx;
48 static latlong_t lat_min,lat_max,lon_min,lon_max;
49
50 /* Local functions */
51
52 static int sort_by_id(NodeX *a,NodeX *b);
53 static int deduplicate_and_index_by_id(NodeX *nodex,index_t index);
54
55 static int sort_by_lat_long(NodeX *a,NodeX *b);
56 static int index_by_lat_long(NodeX *nodex,index_t index);
57
58
59 /*++++++++++++++++++++++++++++++++++++++
60 Allocate a new node list (create a new file or open an existing one).
61
62 NodesX *NewNodeList Returns a pointer to the node list.
63
64 int append Set to 1 if the file is to be opened for appending.
65
66 int readonly Set to 1 if the file is to be opened for reading.
67 ++++++++++++++++++++++++++++++++++++++*/
68
69 NodesX *NewNodeList(int append,int readonly)
70 {
71 NodesX *nodesx;
72
73 nodesx=(NodesX*)calloc(1,sizeof(NodesX));
74
75 logassert(nodesx,"Failed to allocate memory (try using slim mode?)"); /* Check calloc() worked */
76
77 nodesx->filename =(char*)malloc(strlen(option_tmpdirname)+32);
78 nodesx->filename_tmp=(char*)malloc(strlen(option_tmpdirname)+32);
79
80 sprintf(nodesx->filename ,"%s/nodesx.parsed.mem",option_tmpdirname);
81 sprintf(nodesx->filename_tmp,"%s/nodesx.%p.tmp" ,option_tmpdirname,(void*)nodesx);
82
83 if(append || readonly)
84 if(ExistsFile(nodesx->filename))
85 {
86 off_t size;
87
88 size=SizeFile(nodesx->filename);
89
90 nodesx->number=size/sizeof(NodeX);
91
92 RenameFile(nodesx->filename,nodesx->filename_tmp);
93 }
94
95 if(append)
96 nodesx->fd=OpenFileAppend(nodesx->filename_tmp);
97 else if(!readonly)
98 nodesx->fd=OpenFileNew(nodesx->filename_tmp);
99 else
100 nodesx->fd=-1;
101
102 #if SLIM
103 nodesx->cache=NewNodeXCache();
104 #endif
105
106 return(nodesx);
107 }
108
109
110 /*++++++++++++++++++++++++++++++++++++++
111 Free a node list.
112
113 NodesX *nodesx The set of nodes to be freed.
114
115 int keep If set then the results file is to be kept.
116 ++++++++++++++++++++++++++++++++++++++*/
117
118 void FreeNodeList(NodesX *nodesx,int keep)
119 {
120 if(keep)
121 RenameFile(nodesx->filename_tmp,nodesx->filename);
122 else
123 DeleteFile(nodesx->filename_tmp);
124
125 free(nodesx->filename);
126 free(nodesx->filename_tmp);
127
128 if(nodesx->idata)
129 free(nodesx->idata);
130
131 if(nodesx->gdata)
132 free(nodesx->gdata);
133
134 if(nodesx->pdata)
135 free(nodesx->pdata);
136
137 if(nodesx->super)
138 free(nodesx->super);
139
140 #if SLIM
141 DeleteNodeXCache(nodesx->cache);
142 #endif
143
144 free(nodesx);
145 }
146
147
148 /*++++++++++++++++++++++++++++++++++++++
149 Append a single node to an unsorted node list.
150
151 NodesX *nodesx The set of nodes to modify.
152
153 node_t id The node identifier from the original OSM data.
154
155 double latitude The latitude of the node.
156
157 double longitude The longitude of the node.
158
159 transports_t allow The allowed traffic types through the node.
160
161 nodeflags_t flags The flags to set for this node.
162 ++++++++++++++++++++++++++++++++++++++*/
163
164 void AppendNodeList(NodesX *nodesx,node_t id,double latitude,double longitude,transports_t allow,nodeflags_t flags)
165 {
166 NodeX nodex;
167
168 nodex.id=id;
169 nodex.latitude =radians_to_latlong(latitude);
170 nodex.longitude=radians_to_latlong(longitude);
171 nodex.allow=allow;
172 nodex.flags=flags;
173
174 WriteFile(nodesx->fd,&nodex,sizeof(NodeX));
175
176 nodesx->number++;
177
178 logassert(nodesx->number<NODE_FAKE,"Too many nodes (change index_t to 64-bits?)"); /* NODE_FAKE marks the high-water mark for real nodes. */
179 }
180
181
182 /*++++++++++++++++++++++++++++++++++++++
183 Finish appending nodes and change the filename over.
184
185 NodesX *nodesx The nodes that have been appended.
186 ++++++++++++++++++++++++++++++++++++++*/
187
188 void FinishNodeList(NodesX *nodesx)
189 {
190 if(nodesx->fd!=-1)
191 nodesx->fd=CloseFile(nodesx->fd);
192 }
193
194
195 /*++++++++++++++++++++++++++++++++++++++
196 Find a particular node index.
197
198 index_t IndexNodeX Returns the index of the extended node with the specified id.
199
200 NodesX *nodesx The set of nodes to use.
201
202 node_t id The node id to look for.
203 ++++++++++++++++++++++++++++++++++++++*/
204
205 index_t IndexNodeX(NodesX *nodesx,node_t id)
206 {
207 index_t start=0;
208 index_t end=nodesx->number-1;
209 index_t mid;
210
211 if(nodesx->number==0) /* No nodes */
212 return(NO_NODE);
213
214 if(id<nodesx->idata[start]) /* Key is before start */
215 return(NO_NODE);
216
217 if(id>nodesx->idata[end]) /* Key is after end */
218 return(NO_NODE);
219
220 /* Binary search - search key exact match only is required.
221 *
222 * # <- start | Check mid and move start or end if it doesn't match
223 * # |
224 * # | Since an exact match is wanted we can set end=mid-1
225 * # <- mid | or start=mid+1 because we know that mid doesn't match.
226 * # |
227 * # | Eventually either end=start or end=start+1 and one of
228 * # <- end | start or end is the wanted one.
229 */
230
231 do
232 {
233 mid=(start+end)/2; /* Choose mid point */
234
235 if(nodesx->idata[mid]<id) /* Mid point is too low */
236 start=mid+1;
237 else if(nodesx->idata[mid]>id) /* Mid point is too high */
238 end=mid?(mid-1):mid;
239 else /* Mid point is correct */
240 return(mid);
241 }
242 while((end-start)>1);
243
244 if(nodesx->idata[start]==id) /* Start is correct */
245 return(start);
246
247 if(nodesx->idata[end]==id) /* End is correct */
248 return(end);
249
250 return(NO_NODE);
251 }
252
253
254 /*++++++++++++++++++++++++++++++++++++++
255 Sort the node list.
256
257 NodesX *nodesx The set of nodes to modify.
258 ++++++++++++++++++++++++++++++++++++++*/
259
260 void SortNodeList(NodesX *nodesx)
261 {
262 int fd;
263 index_t xnumber;
264
265 /* Print the start message */
266
267 printf_first("Sorting Nodes");
268
269 /* Re-open the file read-only and a new file writeable */
270
271 nodesx->fd=ReOpenFile(nodesx->filename_tmp);
272
273 DeleteFile(nodesx->filename_tmp);
274
275 fd=OpenFileNew(nodesx->filename_tmp);
276
277 /* Allocate the array of indexes */
278
279 nodesx->idata=(node_t*)malloc(nodesx->number*sizeof(node_t));
280
281 logassert(nodesx->idata,"Failed to allocate memory (try using slim mode?)"); /* Check malloc() worked */
282
283 /* Sort the nodes by ID and index them */
284
285 xnumber=nodesx->number;
286
287 sortnodesx=nodesx;
288
289 nodesx->number=filesort_fixed(nodesx->fd,fd,sizeof(NodeX),NULL,
290 (int (*)(const void*,const void*))sort_by_id,
291 (int (*)(void*,index_t))deduplicate_and_index_by_id);
292
293 /* Close the files */
294
295 nodesx->fd=CloseFile(nodesx->fd);
296 CloseFile(fd);
297
298 /* Print the final message */
299
300 printf_last("Sorted Nodes: Nodes=%"Pindex_t" Duplicates=%"Pindex_t,xnumber,xnumber-nodesx->number);
301 }
302
303
304 /*++++++++++++++++++++++++++++++++++++++
305 Sort the nodes into id order.
306
307 int sort_by_id Returns the comparison of the id fields.
308
309 NodeX *a The first extended node.
310
311 NodeX *b The second extended node.
312 ++++++++++++++++++++++++++++++++++++++*/
313
314 static int sort_by_id(NodeX *a,NodeX *b)
315 {
316 node_t a_id=a->id;
317 node_t b_id=b->id;
318
319 if(a_id<b_id)
320 return(-1);
321 else if(a_id>b_id)
322 return(1);
323 else
324 return(-FILESORT_PRESERVE_ORDER(a,b)); /* latest version first */
325 }
326
327
328 /*++++++++++++++++++++++++++++++++++++++
329 Create the index of identifiers and discard duplicate nodes.
330
331 int deduplicate_and_index_by_id Return 1 if the value is to be kept, otherwise 0.
332
333 NodeX *nodex The extended node.
334
335 index_t index The number of sorted nodes that have already been written to the output file.
336 ++++++++++++++++++++++++++++++++++++++*/
337
338 static int deduplicate_and_index_by_id(NodeX *nodex,index_t index)
339 {
340 static node_t previd=NO_NODE_ID;
341
342 if(nodex->id!=previd)
343 {
344 previd=nodex->id;
345
346 if(nodex->flags&NODE_DELETED)
347 return(0);
348 else
349 {
350 sortnodesx->idata[index]=nodex->id;
351
352 return(1);
353 }
354 }
355 else
356 return(0);
357 }
358
359
360 /*++++++++++++++++++++++++++++++++++++++
361 Remove any nodes that are not part of a highway.
362
363 NodesX *nodesx The set of nodes to modify.
364
365 SegmentsX *segmentsx The set of segments to use.
366
367 int keep If set to 1 then keep the old data file otherwise delete it.
368 ++++++++++++++++++++++++++++++++++++++*/
369
370 void RemoveNonHighwayNodes(NodesX *nodesx,SegmentsX *segmentsx,int keep)
371 {
372 NodeX nodex;
373 index_t total=0,highway=0,nothighway=0;
374 int fd;
375
376 /* Print the start message */
377
378 printf_first("Checking Nodes: Nodes=0");
379
380 /* Re-open the file read-only and a new file writeable */
381
382 nodesx->fd=ReOpenFile(nodesx->filename_tmp);
383
384 if(keep)
385 RenameFile(nodesx->filename_tmp,nodesx->filename);
386 else
387 DeleteFile(nodesx->filename_tmp);
388
389 nodesx->knumber=nodesx->number;
390
391 fd=OpenFileNew(nodesx->filename_tmp);
392
393 /* Modify the on-disk image */
394
395 while(!ReadFile(nodesx->fd,&nodex,sizeof(NodeX)))
396 {
397 if(!IsBitSet(segmentsx->usednode,total))
398 nothighway++;
399 else
400 {
401 nodex.id=highway;
402 nodesx->idata[highway]=nodesx->idata[total];
403
404 WriteFile(fd,&nodex,sizeof(NodeX));
405
406 highway++;
407 }
408
409 total++;
410
411 if(!(total%10000))
412 printf_middle("Checking Nodes: Nodes=%"Pindex_t" Highway=%"Pindex_t" not-Highway=%"Pindex_t,total,highway,nothighway);
413 }
414
415 nodesx->number=highway;
416
417 /* Close the files */
418
419 nodesx->fd=CloseFile(nodesx->fd);
420 CloseFile(fd);
421
422 /* Free the now-unneeded index */
423
424 free(segmentsx->usednode);
425 segmentsx->usednode=NULL;
426
427 /* Print the final message */
428
429 printf_last("Checked Nodes: Nodes=%"Pindex_t" Highway=%"Pindex_t" not-Highway=%"Pindex_t,total,highway,nothighway);
430 }
431
432
433 /*++++++++++++++++++++++++++++++++++++++
434 Remove any nodes that have been pruned.
435
436 NodesX *nodesx The set of nodes to prune.
437
438 SegmentsX *segmentsx The set of segments to use.
439 ++++++++++++++++++++++++++++++++++++++*/
440
441 void RemovePrunedNodes(NodesX *nodesx,SegmentsX *segmentsx)
442 {
443 NodeX nodex;
444 index_t total=0,pruned=0,notpruned=0;
445 int fd;
446
447 if(nodesx->number==0)
448 return;
449
450 /* Print the start message */
451
452 printf_first("Deleting Pruned Nodes: Nodes=0 Pruned=0");
453
454 /* Allocate the array of indexes */
455
456 nodesx->pdata=(index_t*)malloc(nodesx->number*sizeof(index_t));
457
458 logassert(nodesx->pdata,"Failed to allocate memory (try using slim mode?)"); /* Check malloc() worked */
459
460 /* Re-open the file read-only and a new file writeable */
461
462 nodesx->fd=ReOpenFile(nodesx->filename_tmp);
463
464 DeleteFile(nodesx->filename_tmp);
465
466 fd=OpenFileNew(nodesx->filename_tmp);
467
468 /* Modify the on-disk image */
469
470 while(!ReadFile(nodesx->fd,&nodex,sizeof(NodeX)))
471 {
472 if(segmentsx->firstnode[total]==NO_SEGMENT)
473 {
474 pruned++;
475
476 nodesx->pdata[total]=NO_NODE;
477 }
478 else
479 {
480 nodex.id=notpruned;
481 nodesx->pdata[total]=notpruned;
482
483 WriteFile(fd,&nodex,sizeof(NodeX));
484
485 notpruned++;
486 }
487
488 total++;
489
490 if(!(total%10000))
491 printf_middle("Deleting Pruned Nodes: Nodes=%"Pindex_t" Pruned=%"Pindex_t,total,pruned);
492 }
493
494 nodesx->number=notpruned;
495
496 /* Close the files */
497
498 nodesx->fd=CloseFile(nodesx->fd);
499 CloseFile(fd);
500
501 /* Print the final message */
502
503 printf_last("Deleted Pruned Nodes: Nodes=%"Pindex_t" Pruned=%"Pindex_t,total,pruned);
504 }
505
506
507 /*++++++++++++++++++++++++++++++++++++++
508 Sort the node list geographically.
509
510 NodesX *nodesx The set of nodes to modify.
511 ++++++++++++++++++++++++++++++++++++++*/
512
513 void SortNodeListGeographically(NodesX *nodesx)
514 {
515 int fd;
516 ll_bin_t lat_min_bin,lat_max_bin,lon_min_bin,lon_max_bin;
517
518 if(nodesx->number==0)
519 return;
520
521 /* While we are here we can work out the range of data */
522
523 lat_min=radians_to_latlong( 2);
524 lat_max=radians_to_latlong(-2);
525 lon_min=radians_to_latlong( 4);
526 lon_max=radians_to_latlong(-4);
527
528 /* Print the start message */
529
530 printf_first("Sorting Nodes Geographically");
531
532 /* Allocate the memory for the geographical index array */
533
534 nodesx->gdata=(index_t*)malloc(nodesx->number*sizeof(index_t));
535
536 logassert(nodesx->gdata,"Failed to allocate memory (try using slim mode?)"); /* Check malloc() worked */
537
538 /* Re-open the file read-only and a new file writeable */
539
540 nodesx->fd=ReOpenFile(nodesx->filename_tmp);
541
542 DeleteFile(nodesx->filename_tmp);
543
544 fd=OpenFileNew(nodesx->filename_tmp);
545
546 /* Sort nodes geographically and index them */
547
548 sortnodesx=nodesx;
549
550 filesort_fixed(nodesx->fd,fd,sizeof(NodeX),NULL,
551 (int (*)(const void*,const void*))sort_by_lat_long,
552 (int (*)(void*,index_t))index_by_lat_long);
553
554 /* Close the files */
555
556 nodesx->fd=CloseFile(nodesx->fd);
557 CloseFile(fd);
558
559 /* Free the memory */
560
561 free(nodesx->super);
562 nodesx->super=NULL;
563
564 /* Work out the number of bins */
565
566 lat_min_bin=latlong_to_bin(lat_min);
567 lon_min_bin=latlong_to_bin(lon_min);
568 lat_max_bin=latlong_to_bin(lat_max);
569 lon_max_bin=latlong_to_bin(lon_max);
570
571 nodesx->latzero=lat_min_bin;
572 nodesx->lonzero=lon_min_bin;
573
574 nodesx->latbins=(lat_max_bin-lat_min_bin)+1;
575 nodesx->lonbins=(lon_max_bin-lon_min_bin)+1;
576
577 /* Print the final message */
578
579 printf_last("Sorted Nodes Geographically: Nodes=%"Pindex_t,nodesx->number);
580 }
581
582
583 /*++++++++++++++++++++++++++++++++++++++
584 Sort the nodes into latitude and longitude order (first by longitude bin
585 number, then by latitude bin number and then by exact longitude and then by
586 exact latitude).
587
588 int sort_by_lat_long Returns the comparison of the latitude and longitude fields.
589
590 NodeX *a The first extended node.
591
592 NodeX *b The second extended node.
593 ++++++++++++++++++++++++++++++++++++++*/
594
595 static int sort_by_lat_long(NodeX *a,NodeX *b)
596 {
597 ll_bin_t a_lon=latlong_to_bin(a->longitude);
598 ll_bin_t b_lon=latlong_to_bin(b->longitude);
599
600 if(a_lon<b_lon)
601 return(-1);
602 else if(a_lon>b_lon)
603 return(1);
604 else
605 {
606 ll_bin_t a_lat=latlong_to_bin(a->latitude);
607 ll_bin_t b_lat=latlong_to_bin(b->latitude);
608
609 if(a_lat<b_lat)
610 return(-1);
611 else if(a_lat>b_lat)
612 return(1);
613 else
614 {
615 if(a->longitude<b->longitude)
616 return(-1);
617 else if(a->longitude>b->longitude)
618 return(1);
619 else
620 {
621 if(a->latitude<b->latitude)
622 return(-1);
623 else if(a->latitude>b->latitude)
624 return(1);
625 }
626
627 return(FILESORT_PRESERVE_ORDER(a,b));
628 }
629 }
630 }
631
632
633 /*++++++++++++++++++++++++++++++++++++++
634 Create the index between the sorted and unsorted nodes.
635
636 int index_by_lat_long Return 1 if the value is to be kept, otherwise 0.
637
638 NodeX *nodex The extended node.
639
640 index_t index The number of sorted nodes that have already been written to the output file.
641 ++++++++++++++++++++++++++++++++++++++*/
642
643 static int index_by_lat_long(NodeX *nodex,index_t index)
644 {
645 sortnodesx->gdata[nodex->id]=index;
646
647 if(IsBitSet(sortnodesx->super,nodex->id))
648 nodex->flags|=NODE_SUPER;
649
650 if(nodex->latitude<lat_min)
651 lat_min=nodex->latitude;
652 if(nodex->latitude>lat_max)
653 lat_max=nodex->latitude;
654 if(nodex->longitude<lon_min)
655 lon_min=nodex->longitude;
656 if(nodex->longitude>lon_max)
657 lon_max=nodex->longitude;
658
659 return(1);
660 }
661
662
663 /*++++++++++++++++++++++++++++++++++++++
664 Save the final node list database to a file.
665
666 NodesX *nodesx The set of nodes to save.
667
668 const char *filename The name of the file to save.
669
670 SegmentsX *segmentsx The set of segments to use.
671 ++++++++++++++++++++++++++++++++++++++*/
672
673 void SaveNodeList(NodesX *nodesx,const char *filename,SegmentsX *segmentsx)
674 {
675 index_t i;
676 int fd;
677 NodesFile nodesfile={0};
678 index_t super_number=0;
679 ll_bin2_t latlonbin=0,maxlatlonbins;
680 index_t *offsets;
681
682 /* Print the start message */
683
684 printf_first("Writing Nodes: Nodes=0");
685
686 /* Allocate the memory for the geographical offsets array */
687
688 offsets=(index_t*)malloc((nodesx->latbins*nodesx->lonbins+1)*sizeof(index_t));
689
690 logassert(offsets,"Failed to allocate memory (try using slim mode?)"); /* Check malloc() worked */
691
692 latlonbin=0;
693
694 /* Re-open the file */
695
696 nodesx->fd=ReOpenFile(nodesx->filename_tmp);
697
698 /* Write out the nodes data */
699
700 fd=OpenFileNew(filename);
701
702 SeekFile(fd,sizeof(NodesFile)+(nodesx->latbins*nodesx->lonbins+1)*sizeof(index_t));
703
704 for(i=0;i<nodesx->number;i++)
705 {
706 NodeX nodex;
707 Node node={0};
708 ll_bin_t latbin,lonbin;
709 ll_bin2_t llbin;
710
711 ReadFile(nodesx->fd,&nodex,sizeof(NodeX));
712
713 /* Create the Node */
714
715 node.latoffset=latlong_to_off(nodex.latitude);
716 node.lonoffset=latlong_to_off(nodex.longitude);
717 node.firstseg=segmentsx->firstnode[nodesx->gdata[nodex.id]];
718 node.allow=nodex.allow;
719 node.flags=nodex.flags;
720
721 if(node.flags&NODE_SUPER)
722 super_number++;
723
724 /* Work out the offsets */
725
726 latbin=latlong_to_bin(nodex.latitude )-nodesx->latzero;
727 lonbin=latlong_to_bin(nodex.longitude)-nodesx->lonzero;
728 llbin=lonbin*nodesx->latbins+latbin;
729
730 for(;latlonbin<=llbin;latlonbin++)
731 offsets[latlonbin]=i;
732
733 /* Write the data */
734
735 WriteFile(fd,&node,sizeof(Node));
736
737 if(!((i+1)%10000))
738 printf_middle("Writing Nodes: Nodes=%"Pindex_t,i+1);
739 }
740
741 /* Close the file */
742
743 nodesx->fd=CloseFile(nodesx->fd);
744
745 /* Finish off the offset indexing and write them out */
746
747 maxlatlonbins=nodesx->latbins*nodesx->lonbins;
748
749 for(;latlonbin<=maxlatlonbins;latlonbin++)
750 offsets[latlonbin]=nodesx->number;
751
752 SeekFile(fd,sizeof(NodesFile));
753 WriteFile(fd,offsets,(nodesx->latbins*nodesx->lonbins+1)*sizeof(index_t));
754
755 free(offsets);
756
757 /* Write out the header structure */
758
759 nodesfile.number=nodesx->number;
760 nodesfile.snumber=super_number;
761
762 nodesfile.latbins=nodesx->latbins;
763 nodesfile.lonbins=nodesx->lonbins;
764
765 nodesfile.latzero=nodesx->latzero;
766 nodesfile.lonzero=nodesx->lonzero;
767
768 SeekFile(fd,0);
769 WriteFile(fd,&nodesfile,sizeof(NodesFile));
770
771 CloseFile(fd);
772
773 /* Print the final message */
774
775 printf_last("Wrote Nodes: Nodes=%"Pindex_t,nodesx->number);
776 }

Properties

Name Value
cvs:description Extended nodes functions.