source: subversion/utils/osm2pgsql/osm2pgsql.c @ 1718

Last change on this file since 1718 was 1718, checked in by jonb, 14 years ago

Improved version of osm2pgsql. Adds 'natural' attribute. Some alogorithm improvments to reduce run time. Optional duplicate way detection (at expense of RAM usage).

File size: 18.0 KB
Line 
1/*
2#-----------------------------------------------------------------------------
3# osm2pgsql - converts planet.osm file into PostgreSQL
4# compatible output suitable to be rendered by mapnik
5# Use: osm2pgsql planet.osm > planet.sql
6#-----------------------------------------------------------------------------
7# Original Python implementation by unnkown author
8# Re-implementation by Jon Burgess, Copyright 2006
9#
10# This program is free software; you can redistribute it and/or
11# modify it under the terms of the GNU General Public License
12# as published by the Free Software Foundation; either version 2
13# of the License, or (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 General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program; if not, write to the Free Software
22# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
23#-----------------------------------------------------------------------------
24*/
25
26#define _GNU_SOURCE
27
28#include <stdio.h>
29#include <unistd.h>
30#include <stdlib.h>
31#include <string.h>
32#include <assert.h>
33
34#include <libxml/xmlstring.h>
35#include <libxml/xmlreader.h>
36
37#include "bst.h"
38#include "avl.h"
39
40#define WKT_MAX 128000
41#define SQL_MAX 140000
42
43#if 0
44#define DEBUG printf
45#else
46#define DEBUG(x, ...)
47#endif
48
49struct tagDesc {
50        const char *name;
51        const char *type;
52}; 
53
54static struct tagDesc exportTags[] = {
55        {"name","text"},
56        {"place","text"},
57        {"landuse","text"},
58        {"leisure","text"},
59        {"natural","text"},
60        {"waterway","text"},
61        {"highway","text"},
62        {"railway","text"},
63        {"amenity","text"},
64        {"tourism","text"},
65        {"learning","text"}
66};
67
68static const char *table_name = "planet_osm";
69char fieldNames[128];
70
71#define MAX_ID_NODE (25000000)
72#define MAX_ID_SEGMENT (25000000)
73
74struct osmNode {
75        unsigned int id;
76        double lon;
77        double lat;
78};
79
80struct osmSegment {
81        unsigned int id;
82        unsigned int from;
83        unsigned int to;
84};
85
86struct osmWay {
87        unsigned int id;
88        char *values;
89        char *wkt;     
90};
91
92static struct osmNode    nodes[MAX_ID_NODE+1];
93static struct osmSegment segments[MAX_ID_SEGMENT+1];
94
95struct bst_table *node_positions;
96struct avl_table *segment_unique;
97struct avl_table *way_tree;
98
99static int count_node, count_all_node, count_dupe_node;
100static int count_segment, count_all_segment, count_dupe_segment;
101static int count_way, count_all_way, count_dupe_way;
102
103// Enable this to suppress duplicate ways in the output
104// This is useful on the planet-061128.osm dump and earlier
105// to remove lots of redundant data in the US Tiger import.
106// Note: This approximately doubles the RAM usage!
107static int suppress_dupes=0;
108
109struct keyval;
110
111struct keyval {
112        char *key;
113        char *value;
114        struct keyval *next;
115        struct keyval *prev;
116};
117
118static struct keyval keys, tags, segs;
119
120void usage(const char *arg0)
121{
122        fprintf(stderr, "Usage error:\n\t%s planet.osm  > planet.sql\n", arg0);
123        fprintf(stderr, "or\n\tgzip -dc planet.osm.gz | %s - | gzip -c > planet.sql.gz\n", arg0);
124}
125
126void initList(struct keyval *head)
127{
128        head->next = head;
129        head->prev = head;
130        head->key = NULL;
131        head->value = NULL;
132}
133
134void freeItem(struct keyval *p)
135{
136        free(p->key);
137        free(p->value);
138        free(p);
139}
140
141
142unsigned int countList(struct keyval *head) 
143{
144        struct keyval *p = head->next;
145        unsigned int count = 0; 
146
147        while(p != head) {
148                count++;
149                p = p->next;
150        }
151        return count;
152}
153
154int listHasData(struct keyval *head) 
155{
156        return (head->next != head);
157}
158
159
160char *getItem(struct keyval *head, const char *name)
161{
162        struct keyval *p = head->next;
163        while(p != head) {
164                if (!strcmp(p->key, name))
165                        return p->value;
166                p = p->next;
167        }
168        return NULL;
169}       
170
171
172struct keyval *popItem(struct keyval *head)
173{
174        struct keyval *p = head->next;
175        if (p == head)
176                return NULL;
177
178        head->next = p->next;
179        p->next->prev = head;
180
181        p->next = NULL;
182        p->prev = NULL;
183
184        return p;
185}       
186
187
188void pushItem(struct keyval *head, struct keyval *item)
189{
190        item->next = head;
191        item->prev = head->prev;
192        head->prev->next = item;
193        head->prev = item;
194}       
195
196void addItem(struct keyval *head, const char *name, const char *value)
197{
198        struct keyval *item = malloc(sizeof(struct keyval));
199       
200        if (!item) {
201                fprintf(stderr, "Error allocating keyval\n");
202                return;
203        }
204        item->key   = strdup(name);
205        item->value = strdup(value);
206
207        item->next = head->next;
208        item->prev = head;
209        head->next->prev = item;
210        head->next = item;
211}
212
213void resetList(struct keyval *head) 
214{
215        struct keyval *item;
216       
217        while((item = popItem(head))) 
218                freeItem(item);
219}
220
221void WKT(char *wkt, int polygon)
222{
223        double start_x=0, start_y=0, end_x=0, end_y=0;
224        int first = 1;
225        char tmpwkt[WKT_MAX];
226        unsigned int i = countList(&segs); 
227        unsigned int max = i * i;
228        wkt[0] = '\0';
229        static struct keyval triedSegs, *q;
230
231        initList(&triedSegs);   
232        i = 0;
233
234        while (listHasData(&segs) && i++ < max) {
235                struct keyval *p;
236                unsigned int id, to, from;
237                double x0, y0, x1, y1;
238
239                p = popItem(&segs);
240                id = strtoul(p->value, NULL, 10);
241
242                from = segments[id].from;
243                to   = segments[id].to; 
244
245                x0 = nodes[from].lon;
246                y0 = nodes[from].lat;
247                x1 = nodes[to].lon;
248                y1 = nodes[to].lat;
249
250                if (first) {
251                        first = 0;
252                        start_x = x0;
253                        start_y = y0;
254                        end_x = x1;
255                        end_y = y1;
256                        snprintf(wkt, WKT_MAX-1, "%.15g %.15g,%.15g %.15g", x0, y0, x1, y1);
257                } else {
258                        if (start_x == x0 && start_y == y0)  {
259                                start_x = x1;
260                                start_y = y1;
261                                snprintf(tmpwkt, WKT_MAX-1, "%.15g %.15g,%s", x1, y1, wkt);
262                        } else if (start_x == x1 && start_y == y1)  {
263                                start_x = x0;
264                                start_y = y0;
265                                snprintf(tmpwkt, WKT_MAX-1, "%.15g %.15g,%s", x0, y0, wkt);
266                        } else if (end_x == x0 && end_y == y0)  {
267                                end_x = x1;
268                                end_y = y1;
269                                snprintf(tmpwkt, WKT_MAX-1, "%s,%.15g %.15g", wkt, x1, y1);
270                        } else if (end_x == x1 && end_y == y1)  {
271                                end_x = x0;
272                                end_y = y0;
273                                snprintf(tmpwkt, WKT_MAX-1, "%s,%.15g %.15g", wkt, x0, y0);
274                        } else {
275                                pushItem(&triedSegs, p);
276                                continue;
277                        }
278                        strcpy(wkt, tmpwkt);
279                }
280                freeItem(p);
281                // Need to reconsider all previously tried segments again
282                while ((q = popItem(&triedSegs))) 
283                        pushItem(&segs, q);
284        }
285
286        if (strlen(wkt)) {
287                strcpy(tmpwkt, wkt);
288                if (polygon) 
289                        snprintf(wkt, WKT_MAX-1, "POLYGON((%s,%.15g %.15g))", tmpwkt, start_x, start_y);
290                else 
291                        snprintf(wkt, WKT_MAX-1, "LINESTRING(%s)", tmpwkt);
292        }
293
294        // Push any unattached segments back in the list for next time
295        while ((q = popItem(&triedSegs))) 
296                pushItem(&segs, q);
297}
298
299void StartElement(xmlTextReaderPtr reader, const xmlChar *name)
300{
301        xmlChar *xid, *xlat, *xlon, *xfrom, *xto, *xk, *xv;
302        unsigned int id, to, from;
303        double lon, lat;
304        char *k;
305
306        if (xmlStrEqual(name, BAD_CAST "node")) {
307                struct osmNode *node, *dupe;
308                xid  = xmlTextReaderGetAttribute(reader, BAD_CAST "id");
309                xlon = xmlTextReaderGetAttribute(reader, BAD_CAST "lon");
310                xlat = xmlTextReaderGetAttribute(reader, BAD_CAST "lat");
311                assert(xid); assert(xlon); assert(xlat);
312                id  = strtoul((char *)xid, NULL, 10);
313                lon = strtod((char *)xlon, NULL);
314                lat = strtod((char *)xlat, NULL);
315
316                assert(id > 0 && id < MAX_ID_NODE);
317                count_all_node++;
318                if (count_all_node%10000 == 0) 
319                        fprintf(stderr, "\rProcessing: Node(%dk)", count_all_node/1000);
320
321                node = &nodes[id];
322                node->id  = id;
323                node->lon = lon;
324                node->lat = lat;
325
326                dupe = suppress_dupes ? bst_insert(node_positions, (void *)node) : NULL;
327               
328                if (!dupe) {
329                        DEBUG("NODE(%d) %f %f\n", id, lon, lat);
330                } else {
331                        node->id = dupe->id;
332                        count_dupe_node++;
333                        DEBUG("NODE(%d) %f %f - dupe %d\n", id, lon, lat, dupe->id);
334                }
335                addItem(&keys, "id", (char *)xid);
336
337                xmlFree(xid);
338                xmlFree(xlon);
339                xmlFree(xlat);
340        } else if (xmlStrEqual(name, BAD_CAST "segment")) {
341                xid   = xmlTextReaderGetAttribute(reader, BAD_CAST "id");
342                xfrom = xmlTextReaderGetAttribute(reader, BAD_CAST "from");
343                xto   = xmlTextReaderGetAttribute(reader, BAD_CAST "to");
344                assert(xid); assert(xfrom); assert(xto);
345                id   = strtoul((char *)xid, NULL, 10);
346                from = strtoul((char *)xfrom, NULL, 10);
347                to   = strtoul((char *)xto, NULL, 10);
348
349                assert(id > 0 && id < MAX_ID_SEGMENT);
350                if (count_all_segment == 0) {
351                        //fprintf(stderr, "\nBalancing node tree\n");
352                        bst_balance(node_positions);
353                        fprintf(stderr, "\n");
354                }
355
356                count_all_segment++;
357                if (count_all_segment%10000 == 0) 
358                        fprintf(stderr, "\rProcessing: Segment(%dk)", count_all_segment/1000);
359
360                if (!nodes[to].id) {
361                        DEBUG("SEGMENT(%d), NODE(%d) is missing\n", id, to);
362                } else if (!nodes[from].id) {
363                        DEBUG("SEGMENT(%d), NODE(%d) is missing\n", id, from);
364                } else {
365                        from = nodes[from].id;
366                        to   = nodes[to].id;
367                        if (from != to) {
368                                struct osmSegment *segment, *dupe;
369                                segment = &segments[id];
370                                segment->id   = id;
371                                segment->to   = to;
372                                segment->from = from;
373
374                                dupe = suppress_dupes ? avl_insert(segment_unique, (void *)segment) : NULL;
375
376                                if (!dupe) {
377                                        count_segment++;
378                                        DEBUG("SEGMENT(%d) %d, %d\n", id, from, to);
379                                } else {
380                                        count_dupe_segment++;
381                                        segment->id = dupe->id;
382                                        DEBUG("SEGMENT(%d) %d, %d - dupe %d\n", id, from, to, dupe->id);
383                                }
384                        }
385                }
386
387                xmlFree(xid);
388                xmlFree(xfrom);
389                xmlFree(xto);
390        } else if (xmlStrEqual(name, BAD_CAST "tag")) {
391                char *p;
392                xk = xmlTextReaderGetAttribute(reader, BAD_CAST "k");
393                xv = xmlTextReaderGetAttribute(reader, BAD_CAST "v");
394                assert(xk); assert(xv);
395                k  = (char *)xmlStrdup(xk);
396                /* FIXME: This does not look safe on UTF-8 data */
397                while ((p = strchr(k, ':')))
398                        *p = '_';
399                while ((p = strchr(k, ' ')))
400                        *p = '_';
401                addItem(&tags, k, (char *)xv);
402                DEBUG("\t%s = %s\n", xk, xv);
403                xmlFree(k);
404                xmlFree(xk);
405                xmlFree(xv);
406        } else if (xmlStrEqual(name, BAD_CAST "way")) {
407                xid  = xmlTextReaderGetAttribute(reader, BAD_CAST "id");
408                assert(xid);
409                addItem(&keys, "id", (char *)xid);
410                DEBUG("WAY(%s)\n", xid);
411
412                if (count_all_way == 0)
413                        fprintf(stderr, "\n");
414               
415                count_all_way++;
416                if (count_all_way%1000 == 0) 
417                        fprintf(stderr, "\rProcessing: Way(%dk)", count_all_way/1000);
418
419                xmlFree(xid);
420        } else if (xmlStrEqual(name, BAD_CAST "seg")) {
421                xid  = xmlTextReaderGetAttribute(reader, BAD_CAST "id");
422                assert(xid);
423                id   = strtoul((char *)xid, NULL, 10);
424                if (!id || (id > MAX_ID_SEGMENT))
425                        DEBUG("\tSEG(%s) - invalid segment ID\n", xid);
426                else if (!segments[id].id)
427                        DEBUG("\tSEG(%s) - missing segment\n", xid);
428                else {
429                        char *tmp;
430                        // Find unique segment
431                        id = segments[id].id;
432                        asprintf(&tmp, "%d", id);
433                        addItem(&segs, "id", tmp);
434                        DEBUG("\tSEG(%s)\n", xid);
435                        free(tmp);
436                }
437                xmlFree(xid);
438        } else if (xmlStrEqual(name, BAD_CAST "osm")) {
439                /* ignore */
440        } else {
441                fprintf(stderr, "%s: Unknown element name: %s\n", __FUNCTION__, name);
442        }
443}
444
445void EndElement(xmlTextReaderPtr reader, const xmlChar *name)
446{
447        unsigned int id;
448        char *v;
449
450        DEBUG("%s: %s\n", __FUNCTION__, name);
451
452        if (xmlStrEqual(name, BAD_CAST "node")) {
453                int i, count = 0; 
454                char *values = NULL;
455                char *osm_id = getItem(&keys, "id");
456                if (!osm_id) {
457                        fprintf(stderr, "%s: Node ID not in keys\n", __FUNCTION__);
458                        resetList(&keys);
459                        resetList(&tags);
460                        return;
461                }
462                id  = strtoul(osm_id, NULL, 10);
463                assert(nodes[id].id);
464#if 0
465                if (id != nodes[id].id) {
466                        // TODO: Consider dropping all duplicate nodes or compare tags?
467                        // Don't really want to store all node attributes for comparison.
468                        resetList(&keys);
469                        resetList(&tags);
470                        return;
471                }
472#endif
473                for (i=0; i < sizeof(exportTags) / sizeof(exportTags[0]); i++) {
474                        char *oldval = values;
475                        int width = strcmp(exportTags[i].name, "name")?32:64;
476                        if ((v = getItem(&tags, exportTags[i].name)))
477                                count++;
478                        else
479                                v = "";
480
481                        if (oldval)                             
482                                asprintf(&values, "%s,$$%.*s$$", oldval, width, v);
483                        else
484                                asprintf(&values, "$$%.*s$$", width, v);
485
486                        free(oldval);
487                }
488                if (count) {
489                        char wkt[WKT_MAX];
490                        count_node++;
491                        snprintf(wkt, sizeof(wkt)-1, 
492                                "POINT(%.15g %.15g)", nodes[id].lon, nodes[id].lat);
493                        wkt[sizeof(wkt)-1] = '\0';
494                        printf("insert into %s (osm_id,%s,way) values (%s,%s,GeomFromText('%s',4326));\n", table_name,fieldNames,osm_id,values,wkt);
495                }
496                resetList(&keys);
497                resetList(&tags);
498                free(values);
499        } else if (xmlStrEqual(name, BAD_CAST "segment")) {
500                resetList(&tags);
501        } else if (xmlStrEqual(name, BAD_CAST "tag")) {
502                /* Separate tag list so tag stack unused */
503        } else if (xmlStrEqual(name, BAD_CAST "way")) {
504                int i, polygon = 0; 
505                char *values = NULL;
506                char wkt[WKT_MAX];
507                char *osm_id = getItem(&keys, "id");
508
509                if (!osm_id) {
510                        fprintf(stderr, "%s: WAY ID not in keys\n", __FUNCTION__);
511                        resetList(&keys);
512                        resetList(&tags);
513                        resetList(&segs);
514                        return;
515                }
516
517                if (!listHasData(&segs)) {
518                        DEBUG("%s: WAY(%s) has no segments\n", __FUNCTION__, osm_id);
519                        resetList(&keys);
520                        resetList(&tags);
521                        resetList(&segs);
522                        return;
523                }
524                id  = strtoul(osm_id, NULL, 10);
525
526                for (i=0; i < sizeof(exportTags) / sizeof(exportTags[0]); i++) {
527                        char *oldval = values;
528                        const char *name = exportTags[i].name;
529                        if ((v = getItem(&tags, name))) {
530                                if (!strcmp(name, "landuse") || !strcmp(name, "leisure")
531                                   || !strcmp(name, "amenity") || !strcmp(name, "natural"))
532                                        polygon = 1;
533                        } else
534                                v = "";                 
535
536                        if (oldval)                             
537                                asprintf(&values, "%s,$$%s$$", oldval, v);
538                        else
539                                asprintf(&values, "$$%s$$", v);
540
541                        free(oldval);
542                }
543
544                do {
545                        WKT(wkt, polygon); 
546                        if (strlen(wkt)) {
547                                struct osmWay *dupe = NULL;
548
549                                if (suppress_dupes) {
550                                        struct osmWay *way = malloc(sizeof(struct osmWay));
551                                        assert(way);
552                                        way->id = id;
553                                        way->values = strdup(values);
554                                        way->wkt    = strdup(wkt);
555                                        assert(way->values);
556                                        assert(way->wkt);
557                                        dupe = avl_insert(way_tree, (void *)way);
558                                        if (dupe) {
559                                                DEBUG("WAY(%d) - duplicate of %d\n", id, dupe->id);
560                                                count_dupe_way++;
561                                                free(way->values);
562                                                free(way->wkt);
563                                                free(way);
564                                        }
565                                } 
566                                if (!dupe) {
567                                        printf("insert into %s (osm_id,%s,way) values (%s,%s,GeomFromText('%s',4326));\n", table_name,fieldNames,osm_id,values,wkt);
568                                        count_way++;   
569                                }
570                        }
571                } while (listHasData(&segs));
572                resetList(&keys);
573                resetList(&tags);
574                resetList(&segs);
575                free(values);
576        } else if (xmlStrEqual(name, BAD_CAST "seg")) {
577                /* ignore */
578        } else if (xmlStrEqual(name, BAD_CAST "osm")) {
579                /* ignore */
580        } else {
581                fprintf(stderr, "%s: Unknown element name: %s\n", __FUNCTION__, name);
582        }
583}
584
585static void processNode(xmlTextReaderPtr reader) {
586        xmlChar *name;
587        name = xmlTextReaderName(reader);
588        if (name == NULL)
589                name = xmlStrdup(BAD_CAST "--");
590       
591        switch(xmlTextReaderNodeType(reader)) {
592                case XML_READER_TYPE_ELEMENT:
593                        StartElement(reader, name);     
594                        if (xmlTextReaderIsEmptyElement(reader))
595                                EndElement(reader, name); /* No end_element for self closing tags! */
596                        break;
597                case XML_READER_TYPE_END_ELEMENT:
598                        EndElement(reader, name);
599                        break;
600                case XML_READER_TYPE_SIGNIFICANT_WHITESPACE:
601                        /* Ignore */
602                        break;
603                default:
604                        fprintf(stderr, "Unknown node type %d\n", xmlTextReaderNodeType(reader));
605                        break;
606        }
607       
608        xmlFree(name);
609}
610
611void streamFile(char *filename) {
612        xmlTextReaderPtr reader;
613        int ret;
614       
615        reader = xmlNewTextReaderFilename(filename);
616        if (reader != NULL) {
617                ret = xmlTextReaderRead(reader);
618                while (ret == 1) {
619                        processNode(reader);
620                        ret = xmlTextReaderRead(reader);
621                }
622       
623                if (ret != 0) {
624                        fprintf(stderr, "%s : failed to parse\n", filename);
625                        return;
626                }
627       
628                xmlFreeTextReader(reader);
629        } else {
630                fprintf(stderr, "Unable to open %s\n", filename);
631        }
632}
633
634int compare_node(const void *bst_a, const void *bst_b, void *bst_param)
635{
636        const struct osmNode *nA = bst_a;
637        const struct osmNode *nB = bst_b;
638
639        if (nA == nB) return 0;
640        if (nA->id == nB->id) return 0;
641
642        if (nA->lon < nB->lon)
643                return -1;
644        else if (nA->lon > nB->lon)
645                return +1;
646       
647        if (nA->lat < nB->lat)
648                return -1;
649        else if (nA->lat > nB->lat)
650                return +1;
651
652        return 0; 
653}
654
655int compare_segment(const void *avl_a, const void *avl_b, void *avl_param)
656{
657        const struct osmSegment *sA = avl_a;
658        const struct osmSegment *sB = avl_b;
659
660        if (sA == sB) return 0;
661        if (sA->id == sB->id) return 0;
662
663        if (sA->from < sB->from)
664                return -1;
665        else if (sA->from > sB->from)
666                return +1;
667
668        if (sA->to < sB->to)
669                return -1;
670        else if (sA->to > sB->to)
671                return +1;
672        return 0;
673}
674
675int compare_way(const void *avl_a, const void *avl_b, void *avl_param)
676{
677        const struct osmWay *wA = avl_a;
678        const struct osmWay *wB = avl_b;
679        int c;
680
681        if (wA == wB) return 0;
682        if (wA->id == wB->id) return 0;
683
684        // TODO: Maybe keeping a hash of WKT would be better?
685        c = strcmp(wA->wkt, wB->wkt);
686        if (c) return c;
687
688        return strcmp(wA->values, wB->values);
689}
690
691
692int main(int argc, char *argv[])
693{
694        int i;
695
696        if (argc != 2) {
697                usage(argv[0]);
698                exit(1);
699        }
700 
701        node_positions = bst_create(compare_node, NULL, NULL);
702        assert(node_positions);
703        segment_unique = avl_create(compare_segment, NULL, NULL);
704        assert(segment_unique);
705        way_tree = avl_create(compare_way, NULL, NULL);
706        assert(way_tree);
707
708        initList(&keys);
709        initList(&tags);
710        initList(&segs);
711
712        LIBXML_TEST_VERSION
713       
714        printf("drop table %s ;\n", table_name);
715        printf("create table %s ( osm_id int4",table_name);
716        fieldNames[0] = '\0';
717        for (i=0; i < sizeof(exportTags) / sizeof(exportTags[0]); i++) {
718                char tmp[32];
719                sprintf(tmp, i?",\"%s\"":"\"%s\"", exportTags[i].name);
720                strcat(fieldNames, tmp);
721                printf(",\"%s\" %s", exportTags[i].name, exportTags[i].type);
722        }       
723        printf(" );\n");
724        printf("select AddGeometryColumn('%s', 'way', 4326, 'GEOMETRY', 2 );\n", table_name);
725        printf("begin;\n");
726       
727        streamFile(argv[1]);
728       
729        printf("commit;\n");
730        printf("vacuum analyze %s;\n", table_name);
731        printf("CREATE INDEX way_index ON %s USING GIST (way GIST_GEOMETRY_OPS);\n", table_name);
732        printf("vacuum analyze %s;\n", table_name);
733       
734        xmlCleanupParser();
735        xmlMemoryDump();
736       
737        fprintf(stderr, "\n");
738       
739        if (count_all_node) {
740        fprintf(stderr, "Node stats: out(%d), dupe(%d) (%.1f%%), total(%d)\n",
741                count_node, count_dupe_node, 100.0 * count_dupe_node / count_all_node, count_all_node);
742        }
743        if (count_all_segment) {
744        fprintf(stderr, "Segment stats: out(%d), dupe(%d) (%.1f%%), total(%d)\n",
745                count_segment, count_dupe_segment, 100.0 * count_dupe_segment / count_all_segment, count_all_segment);
746        }
747        if (count_all_way) {
748        fprintf(stderr, "Way stats: out(%d), dupe(%d) (%.1f%%), total(%d)\n",
749                count_way, count_dupe_way, 100.0 * count_dupe_way / count_all_way, count_all_way);
750        }
751
752        return 0;
753}
Note: See TracBrowser for help on using the repository browser.