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 1166 - (show annotations) (download) (as text)
Tue Nov 20 16:12:08 2012 UTC (12 years, 3 months ago) by amb
File MIME type: text/x-csrc
File size: 18919 byte(s)
Replace all assert statements with a custom error message that explains the
cause and suggests a solution.

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

Properties

Name Value
cvs:description Extended nodes functions.