source: subversion/applications/utils/export/osm2pgsql/osm2pgsql.c @ 7345

Last change on this file since 7345 was 7345, checked in by martinvoosterhout, 12 years ago

Add a caching level to the slim-mode with configurable size, so it actually
has decent performance. It is implemented as a lossy sparse array with a
priority queue tracking how much of each block is used to ensure we maximize
the number of nodes we fit in the given amount of memory.

Also rearrange some header definitions.

  • Property svn:keywords set to Rev
File size: 18.5 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.bz2
6#-----------------------------------------------------------------------------
7# Original Python implementation by Artem Pavlenko
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#include <stdio.h>
28#include <unistd.h>
29#include <stdlib.h>
30#include <string.h>
31#include <assert.h>
32#include <getopt.h>
33
34#include <libpq-fe.h>
35
36#include <libxml/xmlstring.h>
37#include <libxml/xmlreader.h>
38
39#include "osmtypes.h"
40#include "build_geometry.h"
41#include "keyvals.h"
42#include "middle-pgsql.h"
43#include "middle-ram.h"
44#include "output-pgsql.h"
45#include "sanitizer.h"
46#include "reprojection.h"
47#include "text-tree.h"
48#include "input.h"
49#include "sprompt.h"
50
51static int count_node,    max_node;
52static int count_way,     max_way;
53static int count_rel,     max_rel;
54
55struct output_t *out;
56
57/* Since {node,way} elements are not nested we can guarantee the
58   values in an end tag must match those of the corresponding
59   start tag and can therefore be cached.
60*/
61static double node_lon, node_lat;
62static struct keyval tags;
63static int *nds;
64static int nd_count, nd_max;
65static struct member *members;
66static int member_count, member_max;
67static int osm_id;
68
69#define INIT_MAX_MEMBERS 64
70#define INIT_MAX_NODES  4096
71
72int verbose;
73static void realloc_nodes();
74static void realloc_members();
75
76// Bounding box to filter imported data
77const char *bbox = NULL;
78static double minlon, minlat, maxlon, maxlat;
79
80static void printStatus(void)
81{
82    fprintf(stderr, "\rProcessing: Node(%dk) Way(%dk) Relation(%dk)",
83            count_node/1000, count_way/1000, count_rel/1000);
84}
85
86static int parse_bbox(void)
87{
88    int n;
89
90    if (!bbox)
91        return 0;
92
93    n = sscanf(bbox, "%lf,%lf,%lf,%lf", &minlon, &minlat, &maxlon, &maxlat);
94    if (n != 4) {
95        fprintf(stderr, "Bounding box must be specified like: minlon,minlat,maxlon,maxlat\n");
96        return 1;
97    }
98    if (maxlon <= minlon) {
99        fprintf(stderr, "Bounding box failed due to maxlon <= minlon\n");
100        return 1;
101    }
102    if (maxlat <= minlat) {
103        fprintf(stderr, "Bounding box failed due to maxlat <= minlat\n");
104        return 1;
105    }
106    printf("Applying Bounding box: %f,%f to %f,%f\n", minlon,minlat,maxlon,maxlat);
107    return 0;
108}
109
110static int node_wanted(double lat, double lon)
111{
112    if (!bbox)
113        return 1;
114
115    if (lat < minlat || lat > maxlat)
116        return 0;
117    if (lon < minlon || lon > maxlon)
118        return 0;
119    return 1;
120}
121
122void StartElement(xmlTextReaderPtr reader, const xmlChar *name)
123{
124    xmlChar *xid, *xlat, *xlon, *xk, *xv, *xrole, *xtype;
125    char *k;
126
127    if (xmlStrEqual(name, BAD_CAST "node")) {
128        xid  = xmlTextReaderGetAttribute(reader, BAD_CAST "id");
129        xlon = xmlTextReaderGetAttribute(reader, BAD_CAST "lon");
130        xlat = xmlTextReaderGetAttribute(reader, BAD_CAST "lat");
131        assert(xid); assert(xlon); assert(xlat);
132
133        osm_id  = strtol((char *)xid, NULL, 10);
134        node_lon = strtod((char *)xlon, NULL);
135        node_lat = strtod((char *)xlat, NULL);
136
137        if (osm_id > max_node)
138            max_node = osm_id;
139
140        count_node++;
141        if (count_node%10000 == 0)
142            printStatus();
143
144        xmlFree(xid);
145        xmlFree(xlon);
146        xmlFree(xlat);
147    } else if (xmlStrEqual(name, BAD_CAST "tag")) {
148        xk = xmlTextReaderGetAttribute(reader, BAD_CAST "k");
149        assert(xk);
150
151        /* 'created_by' and 'source' are common and not interesting to mapnik renderer */
152        if (strcmp((char *)xk, "created_by") && strcmp((char *)xk, "source")) {
153            char *p;
154            xv = xmlTextReaderGetAttribute(reader, BAD_CAST "v");
155            assert(xv);
156            k  = (char *)xmlStrdup(xk);
157            while ((p = strchr(k, ' ')))
158                *p = '_';
159
160            addItem(&tags, k, (char *)xv, 0);
161            xmlFree(k);
162            xmlFree(xv);
163        }
164        xmlFree(xk);
165    } else if (xmlStrEqual(name, BAD_CAST "way")) {
166        xid  = xmlTextReaderGetAttribute(reader, BAD_CAST "id");
167        assert(xid);
168        osm_id   = strtol((char *)xid, NULL, 10);
169
170        if (osm_id > max_way)
171            max_way = osm_id;
172
173        count_way++;
174        if (count_way%1000 == 0)
175            printStatus();
176
177        nd_count = 0;
178        xmlFree(xid);
179    } else if (xmlStrEqual(name, BAD_CAST "nd")) {
180        xid  = xmlTextReaderGetAttribute(reader, BAD_CAST "ref");
181        assert(xid);
182
183        nds[nd_count++] = strtol( (char *)xid, NULL, 10 );
184
185        if( nd_count >= nd_max )
186          realloc_nodes();
187        xmlFree(xid);
188    } else if (xmlStrEqual(name, BAD_CAST "relation")) {
189        xid  = xmlTextReaderGetAttribute(reader, BAD_CAST "id");
190        assert(xid);
191        osm_id   = strtol((char *)xid, NULL, 10);
192
193        if (osm_id > max_rel)
194            max_rel = osm_id;
195
196        count_rel++;
197        if (count_rel%1000 == 0)
198            printStatus();
199
200        member_count = 0;
201        xmlFree(xid);
202    } else if (xmlStrEqual(name, BAD_CAST "member")) {
203        xrole = xmlTextReaderGetAttribute(reader, BAD_CAST "role");
204        assert(xrole);
205
206        xtype = xmlTextReaderGetAttribute(reader, BAD_CAST "type");
207        assert(xtype);
208
209        xid  = xmlTextReaderGetAttribute(reader, BAD_CAST "ref");
210        assert(xid);
211
212        members[member_count].id   = strtol( (char *)xid, NULL, 0 );
213        members[member_count].role = strdup( (char *)xrole );
214       
215        /* Currently we are only interested in 'way' members since these form polygons with holes */
216        if (xmlStrEqual(xtype, BAD_CAST "way"))
217            members[member_count].type = OSMTYPE_WAY;
218        if (xmlStrEqual(xtype, BAD_CAST "node"))
219            members[member_count].type = OSMTYPE_NODE;
220        if (xmlStrEqual(xtype, BAD_CAST "relation"))
221            members[member_count].type = OSMTYPE_RELATION;
222        member_count++;
223
224        if( member_count >= member_max )
225          realloc_members();
226        xmlFree(xid);
227        xmlFree(xrole);
228        xmlFree(xtype);
229    } else if (xmlStrEqual(name, BAD_CAST "osm")) {
230        /* ignore */
231    } else if (xmlStrEqual(name, BAD_CAST "bound")) {
232        /* ignore */
233    } else {
234        fprintf(stderr, "%s: Unknown element name: %s\n", __FUNCTION__, name);
235    }
236}
237
238static void resetMembers()
239{
240  int i;
241  for(i=0; i<member_count; i++ )
242    free( members[i].role );
243}
244
245void EndElement(const xmlChar *name)
246{
247    if (xmlStrEqual(name, BAD_CAST "node")) {
248        if (node_wanted(node_lat, node_lon)) {
249            reproject(&node_lat, &node_lon);
250            out->node_add(osm_id, node_lat, node_lon, &tags);
251        }
252        resetList(&tags);
253    } else if (xmlStrEqual(name, BAD_CAST "way")) {
254        out->way_add(osm_id, nds, nd_count, &tags );
255        resetList(&tags);
256    } else if (xmlStrEqual(name, BAD_CAST "relation")) {
257        out->relation_add(osm_id, members, member_count, &tags);
258        resetList(&tags);
259        resetMembers();
260    } else if (xmlStrEqual(name, BAD_CAST "tag")) {
261        /* ignore */
262    } else if (xmlStrEqual(name, BAD_CAST "nd")) {
263        /* ignore */
264    } else if (xmlStrEqual(name, BAD_CAST "member")) {
265        /* ignore */
266    } else if (xmlStrEqual(name, BAD_CAST "osm")) {
267        printStatus();
268    } else if (xmlStrEqual(name, BAD_CAST "bound")) {
269        /* ignore */
270    } else {
271        fprintf(stderr, "%s: Unknown element name: %s\n", __FUNCTION__, name);
272    }
273}
274
275static void processNode(xmlTextReaderPtr reader) {
276    xmlChar *name;
277    name = xmlTextReaderName(reader);
278    if (name == NULL)
279        name = xmlStrdup(BAD_CAST "--");
280       
281    switch(xmlTextReaderNodeType(reader)) {
282        case XML_READER_TYPE_ELEMENT:
283            StartElement(reader, name);
284            if (xmlTextReaderIsEmptyElement(reader))
285                EndElement(name); /* No end_element for self closing tags! */
286            break;
287        case XML_READER_TYPE_END_ELEMENT:
288            EndElement(name);
289            break;
290        case XML_READER_TYPE_SIGNIFICANT_WHITESPACE:
291            /* Ignore */
292            break;
293        default:
294            fprintf(stderr, "Unknown node type %d\n", xmlTextReaderNodeType(reader));
295            break;
296    }
297
298    xmlFree(name);
299}
300
301static int streamFile(char *filename, int sanitize) {
302    xmlTextReaderPtr reader;
303    int ret = 0;
304
305    if (sanitize)
306        reader = sanitizerOpen(filename);
307    else
308        reader = inputUTF8(filename);
309
310    if (reader != NULL) {
311        ret = xmlTextReaderRead(reader);
312        while (ret == 1) {
313            processNode(reader);
314            ret = xmlTextReaderRead(reader);
315        }
316
317        if (ret != 0) {
318            fprintf(stderr, "%s : failed to parse\n", filename);
319            return ret;
320        }
321
322        xmlFreeTextReader(reader);
323    } else {
324        fprintf(stderr, "Unable to open %s\n", filename);
325        return 1;
326    }
327    return 0;
328}
329
330void exit_nicely(void)
331{
332    fprintf(stderr, "Error occurred, cleaning up\n");
333    out->cleanup();
334    exit(1);
335}
336 
337static void usage(const char *arg0)
338{
339    int i;
340    const char *name = basename(arg0);
341
342    fprintf(stderr, "Usage:\n");
343    fprintf(stderr, "\t%s [options] planet.osm\n", name);
344    fprintf(stderr, "\t%s [options] planet.osm.{gz,bz2}\n", name);
345    fprintf(stderr, "\t%s [options] file1.osm file2.osm file3.osm\n", name);
346    fprintf(stderr, "\nThis will import the data from the OSM file(s) into a PostgreSQL database\n");
347    fprintf(stderr, "suitable for use by the Mapnik renderer\n");
348    fprintf(stderr, "\nOptions:\n");
349    fprintf(stderr, "   -a|--append\t\tAdd the OSM file into the database without removing\n");
350    fprintf(stderr, "              \t\texisting data.\n");
351    fprintf(stderr, "   -b|--bbox\t\tApply a bounding box filter on the imported data\n");
352    fprintf(stderr, "              \t\tMust be specified as: minlon,minlat,maxlon,maxlat\n");
353    fprintf(stderr, "              \t\te.g. --bbox -0.5,51.25,0.5,51.75\n");
354    fprintf(stderr, "   -c|--create\t\tRemove existing data from the database. This is the \n");
355    fprintf(stderr, "              \t\tdefault if --append is not specified.\n");
356    fprintf(stderr, "   -d|--database\tThe name of the PostgreSQL database to connect\n");
357    fprintf(stderr, "                \tto (default: gis).\n");
358    fprintf(stderr, "   -l|--latlong\t\tStore data in degrees of latitude & longitude.\n");
359    fprintf(stderr, "   -m|--merc\t\tStore data in proper spherical mercator, not OSM merc\n");
360    fprintf(stderr, "   -E|--proj num\tUse projection EPSG:num\n");
361    fprintf(stderr, "   -u|--utf8-sanitize\tRepair bad UTF8 input data (present in planet\n");
362    fprintf(stderr, "                \tdumps prior to August 2007). Adds about 10%% overhead.\n");
363    fprintf(stderr, "   -p|--prefix\t\tPrefix for table names (default planet_osm)\n");
364//#ifdef BROKEN_SLIM
365    fprintf(stderr, "   -s|--slim\t\tStore temporary data in the database. This greatly\n");
366    fprintf(stderr, "            \t\treduces the RAM usage but is much slower.\n");
367    fprintf(stderr, "   -C|--cache\t\tOnly for slim mode: Use upto this many MB for caching nodes\n");
368    fprintf(stderr, "             \t\tDefault is 800\n");
369//#endif
370    fprintf(stderr, "   -U|--username\tPostgresql user name.\n");
371    fprintf(stderr, "   -W|--password\tForce password prompt.\n");
372    fprintf(stderr, "   -H|--host\t\tDatabase server hostname or socket location.\n");
373    fprintf(stderr, "   -P|--port\t\tDatabase server port.\n");
374    fprintf(stderr, "   -h|--help\t\tHelp information.\n");
375    fprintf(stderr, "   -v|--verbose\t\tVerbose output.\n");
376    fprintf(stderr, "\n");
377    if(!verbose)
378    {
379        fprintf(stderr, "Add -v to display supported projections.\n");
380        fprintf(stderr, "Use -E to access any espg projections (usually in /usr/share/proj/epsg)\n" );
381    }
382    else
383    {
384        fprintf(stderr, "Supported projections:\n" );
385        for(i=0; i<PROJ_COUNT; i++ )
386        {
387            fprintf( stderr, "%-20s(%2s) SRS:%6d %s\n", 
388                    Projection_Info[i].descr, Projection_Info[i].option, Projection_Info[i].srs, Projection_Info[i].proj4text);
389        }
390    }
391}
392
393const char *build_conninfo(const char *db, const char *username, const char *password, const char *host, const char *port)
394{
395    static char conninfo[1024];
396
397    conninfo[0]='\0';
398    strcat(conninfo, "dbname='");
399    strcat(conninfo, db);
400    strcat(conninfo, "'");
401
402    if (username) {
403        strcat(conninfo, " user='");
404        strcat(conninfo, username);
405        strcat(conninfo, "'");
406    }
407    if (password) {
408        strcat(conninfo, " password='");
409        strcat(conninfo, password);
410        strcat(conninfo, "'");
411    }
412    if (host) {
413        strcat(conninfo, " host='");
414        strcat(conninfo, host);
415        strcat(conninfo, "'");
416    }
417    if (port) {
418        strcat(conninfo, " port='");
419        strcat(conninfo, port);
420        strcat(conninfo, "'");
421    }
422
423    return conninfo;
424}
425
426static void realloc_nodes()
427{
428  if( nd_max == 0 )
429    nd_max = INIT_MAX_NODES;
430  else
431    nd_max <<= 1;
432   
433  nds = realloc( nds, nd_max * sizeof( nds[0] ) );
434  if( !nds )
435  {
436    fprintf( stderr, "Failed to expand node list to %d\n", nd_max );
437    exit_nicely();
438  }
439}
440
441static void realloc_members()
442{
443  if( member_max == 0 )
444    member_max = INIT_MAX_NODES;
445  else
446    member_max <<= 1;
447   
448  members = realloc( members, member_max * sizeof( members[0] ) );
449  if( !members )
450  {
451    fprintf( stderr, "Failed to expand member list to %d\n", member_max );
452    exit_nicely();
453  }
454}
455
456int main(int argc, char *argv[])
457{
458    int append=0;
459    int create=0;
460    int slim=0;
461    int sanitize=0;
462    int pass_prompt=0;
463    int projection = PROJ_MERC;
464    const char *db = "gis";
465    const char *username=NULL;
466    const char *host=NULL;
467    const char *password=NULL;
468    const char *port = "5432";
469    const char *conninfo = NULL;
470    const char *prefix = "planet_osm";
471    int cache = 800;
472    struct output_options options;
473    PGconn *sql_conn;
474
475    fprintf(stderr, "osm2pgsql SVN version %s $Rev: 7345 $ \n\n", VERSION);
476
477    while (1) {
478        int c, option_index = 0;
479        static struct option long_options[] = {
480            {"append",   0, 0, 'a'},
481            {"bbox",     1, 0, 'b'},
482            {"create",   0, 0, 'c'},
483            {"database", 1, 0, 'd'},
484            {"latlong",  0, 0, 'l'},
485            {"verbose",  0, 0, 'v'},
486//#ifdef BROKEN_SLIM
487            {"slim",     0, 0, 's'},
488//#endif
489            {"prefix",   1, 0, 'p'},
490            {"proj",     1, 0, 'E'},
491            {"merc",     0, 0, 'm'},
492            {"utf8-sanitize", 0, 0, 'u'},
493            {"cache",    1, 0, 'C'},
494            {"username", 1, 0, 'U'},
495            {"password", 0, 0, 'W'},
496            {"host",     1, 0, 'H'},
497            {"port",     1, 0, 'P'},
498            {"help",     0, 0, 'h'},
499            {0, 0, 0, 0}
500        };
501
502        c = getopt_long (argc, argv, "ab:cd:hlmp:suvU:WH:P:E:", long_options, &option_index);
503        if (c == -1)
504            break;
505
506        switch (c) {
507            case 'a': append=1;   break;
508            case 'b': bbox=optarg; break;
509            case 'c': create=1;   break;
510            case 'v': verbose=1;  break;
511//#ifdef BROKEN_SLIM
512            case 's': slim=1;     break;
513//#endif
514            case 'u': sanitize=1; break;
515            case 'l': projection=PROJ_LATLONG;  break;
516            case 'm': projection=PROJ_SPHERE_MERC; break;
517            case 'E': projection=-atoi(optarg); break;
518            case 'p': prefix=optarg; break;
519            case 'd': db=optarg;  break;
520            case 'C': cache = atoi(optarg); break;
521            case 'U': username=optarg; break;
522            case 'W': pass_prompt=1; break;
523            case 'H': host=optarg; break;
524            case 'P': port=optarg; break;
525
526            case 'h':
527            case '?':
528            default:
529                usage(argv[0]);
530                exit(EXIT_FAILURE);
531        }
532    }
533
534    if (argc == optind) {  // No non-switch arguments
535        usage(argv[0]);
536        exit(EXIT_FAILURE);
537    }
538
539    if (append && create) {
540        fprintf(stderr, "Error: --append and --create options can not be used at the same time!\n");
541        exit(EXIT_FAILURE);
542    }
543   
544    if( cache < 0 ) cache = 0;
545
546    if (username || pass_prompt)
547        password = simple_prompt("Password:", 100, 0);
548
549    conninfo = build_conninfo(db, username, password, host, port);
550    sql_conn = PQconnectdb(conninfo);
551    if (PQstatus(sql_conn) != CONNECTION_OK) {
552        fprintf(stderr, "Connection to database failed: %s\n", PQerrorMessage(sql_conn));
553        exit(EXIT_FAILURE);
554    }
555    PQfinish(sql_conn);
556
557    text_init();
558    initList(&tags);
559
560    count_node = max_node = 0;
561    count_way = max_way = 0;
562    count_rel = max_rel = 0;
563
564    LIBXML_TEST_VERSION
565
566    project_init(projection);
567    fprintf(stderr, "Using projection SRS %d (%s)\n", 
568        project_getprojinfo()->srs, project_getprojinfo()->descr );
569
570    if (parse_bbox())
571        return 1;
572
573    options.conninfo = conninfo;
574    options.prefix = prefix;
575    options.append = append;
576    options.slim = slim;
577    options.projection = project_getprojinfo()->srs;
578    options.scale = (projection==PROJ_LATLONG)?10000000:100;
579    options.mid = slim ? &mid_pgsql : &mid_ram;
580    options.cache = cache;
581    out = &out_pgsql;
582
583    out->start(&options);
584
585    realloc_nodes();
586    realloc_members();
587
588    while (optind < argc) {
589        fprintf(stderr, "\nReading in file: %s\n", argv[optind]);
590        if (streamFile(argv[optind], sanitize) != 0)
591            exit_nicely();
592        optind++;
593    }
594
595    xmlCleanupParser();
596    xmlMemoryDump();
597   
598    if (count_node || count_way || count_rel) {
599        fprintf(stderr, "\n");
600        fprintf(stderr, "Node stats: total(%d), max(%d)\n", count_node, max_node);
601        fprintf(stderr, "Way stats: total(%d), max(%d)\n", count_way, max_way);
602        fprintf(stderr, "Relation stats: total(%d), max(%d)\n", count_rel, max_rel);
603    }
604    out->stop();
605   
606    free(nds);
607    free(members);
608
609    project_exit();
610    text_exit();
611    fprintf(stderr, "\n");
612
613    return 0;
614}
Note: See TracBrowser for help on using the repository browser.