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

Last change on this file since 1918 was 1918, checked in by steve, 13 years ago

up the max seg ids

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