source: subversion/applications/utils/export/osm2csv/osm2csv.pl @ 4426

Last change on this file since 4426 was 3447, checked in by joerg, 13 years ago

adapt include directory for modules

  • Property svn:executable set to *
File size: 15.1 KB
Line 
1#!/usr/bin/perl
2
3BEGIN {
4    my $dir = $0;
5    $dir =~s,[^/]+/[^/]+$,,;
6    unshift(@INC,"$dir/../perl_lib");
7
8    unshift(@INC,"../perl_perl_lib");
9    unshift(@INC,"~/svn.openstreetmap.org/applications/utils/perl_lib");
10    unshift(@INC,"$ENV{HOME}/svn.openstreetmap.org/applications/utils/perl_lib");
11}
12
13
14use strict;
15use warnings;
16
17use XML::Parser;
18use Getopt::Long;
19use Storable ();
20use IO::File;
21use Pod::Usage;
22use Data::Dumper;
23
24use Geo::Filter::Area;
25use Geo::OSM::Planet;
26use Utils::Debug;
27use Utils::File;
28use Utils::LWP::Utils;
29use Utils::Math;
30use File::Slurp;
31
32sub output_osm($); # {}
33sub output_statistic($); # {}
34sub parse_planet($$); # {}
35
36our $man=0;
37our $help=0;
38my $areas_todo;
39my $do_list_areas=0;
40my $do_update_only=0;
41my $tie_nodes_hash=undef;
42my $Filename;
43
44my @needed_segment_tags=qw( name amenity class highway);
45
46Getopt::Long::Configure('no_ignore_case');
47GetOptions ( 
48             'debug+'              => \$DEBUG,     
49             'd+'                  => \$DEBUG,     
50             'verbose+'            => \$VERBOSE,
51             'MAN'                 => \$man, 
52             'man'                 => \$man, 
53             'h|help|x'            => \$help, 
54
55             'tie-nodes-hash'      => \$tie_nodes_hash,
56             'no-mirror'           => \$Utils::LWP::Utils::NO_MIRROR,
57             'proxy=s'             => \$Utils::LWP::Utils::PROXY,
58             'osm=s'               => \$Filename,
59             'area=s'              => \$areas_todo,
60             'list-areas'          => \$do_list_areas,
61             'update-only'         => \$do_update_only,
62             )
63    or pod2usage(1);
64
65$areas_todo ||= 'germany';
66$areas_todo=lc($areas_todo);
67
68# See if we'll have to tie the Nodes Hash to a File
69# This is at least 10 times slower, but we have less problems with
70# running out of memory
71if ( ! defined $tie_nodes_hash ) {
72    my $max_ram=mem_info("MemTotal");
73    $max_ram =~ s/MB//;
74    my $estimated_memory = {
75        africa     => 2500,
76        france     =>  192,
77        europe     => 3000,
78        germany    =>  500,
79        uk         =>  660,
80        world      => 4000,
81        world_east => 4000,
82        world_west => 4000,
83    };
84    for my $area ( split(",",$areas_todo )){
85        $tie_nodes_hash=1
86            if $estimated_memory->{$area} > $max_ram;
87     }
88}
89
90pod2usage(1) if $help;
91pod2usage(-verbose=>2) if $man;
92
93if ( $do_list_areas ) {
94    print Geo::Filter::Area->list_areas()."\n";
95    exit;
96}
97
98# TODO:
99# if the input filename is not planet*osm* we have to change the output filename too.
100$Filename ||= shift();
101unless ( $Filename && -s $Filename ) {
102    $Filename = mirror_planet();
103};
104if ( ! -s $Filename ) {
105    die "Cannot read $Filename\n";
106}
107
108pod2usage(1) unless $Filename;
109
110our $READ_FH=undef;
111our $OK_POS=0;
112
113
114our (%MainAttr,$Type,%Tags, @WaySegments);
115# Stats
116our %AllTags;
117# Stored data
118our (%Nodes, %Segments, %Stats);
119our $AREA_FILTER;
120our $PARSING_START_TIME=0;
121our $PARSING_DISPLAY_TIME=0;
122
123my $fn_named_points;
124my $fh_named_points;
125
126
127my $data_dir=planet_dir()."/csv";
128mkdir_if_needed( $data_dir );
129
130for my $area_name ( split(",",$areas_todo) ) {
131    if ( $do_update_only ) {
132        my $needs_update=0;
133        $needs_update ||= file_needs_re_generation($Filename,"$data_dir/osm-$area_name.csv");
134        $needs_update ||= file_needs_re_generation($Filename,"$data_dir/points-$area_name.csv");
135        $needs_update ||= file_needs_re_generation($Filename,"$data_dir/stats-$area_name.txt");
136        next unless $needs_update;
137        print STDERR "Update needed. One of the files is old or non existent\n" if $VERBOSE;
138    }
139    # -----------------------------------------------------------------------------
140    # Temporary data
141    $fn_named_points="$data_dir/points-$area_name.csv";
142    #unlink $fn_named_points if -s  $fn_named_points;
143    $fh_named_points = IO::File->new(">$fn_named_points.part");
144    $fh_named_points->binmode(':utf8');
145
146    (%MainAttr,%Tags)=((),());
147    $Type='';
148    @WaySegments = ();
149    (%AllTags,%Nodes, %Segments, %Stats)=((),(),(),(),());
150
151    # Currently active Area Filter
152    $PARSING_START_TIME=0;
153    # Estimated Number of elements to show progress while reading in percent
154    for my $type ( qw( tag node segment way )) {
155        $Stats{"${type}s estim"} = estimated_max_count($type);
156    }
157    $Stats{"tags"}     = 0;
158    $Stats{"nodes"}    = 0;
159    $Stats{"segments"} = 0;
160    $Stats{"ways"}     = 0;
161
162    #----------------------------------------------
163    # Processing stage
164    #----------------------------------------------
165
166    print STDERR "creating $data_dir/osm-$area_name.csv\n" if $VERBOSE;
167
168    my $base_filename="$data_dir/osm-$area_name";
169
170    if ( $tie_nodes_hash ) {
171        # maybe we should move this file to /tmp
172        # and lock it, and delete it in an END {} -Block
173        print STDERR "Tie-ing Nodes Hash to '$base_filename-Nodes.db'\n";
174        dbmopen(%Nodes,"$base_filename-Nodes.db",0666) 
175            or die "Could not open DBM File '$base_filename-Nodes.db': $!";
176    }
177    $Stats{"Tie Nodes_hash"} = $tie_nodes_hash;
178
179    parse_planet($Filename,$area_name);
180
181    printf STDERR "Creating output files\n";
182    die "No Area Name defined\n"
183        unless $area_name;
184
185    # close and rename poi list
186    $fh_named_points->close();
187    rename("$fn_named_points.part",$fn_named_points)
188        if -s "$fn_named_points.part";
189
190    output_osm("$data_dir/osm-$area_name.csv");
191    output_statistic("$data_dir/stats-$area_name.txt");
192    printf STDERR "$area_name Done\n";
193}
194exit;
195
196#----------------------------------------------
197# Parsing planet.osm File
198#----------------------------------------------
199sub parse_planet($$){
200    my $Filename = shift;
201    my $area_name = shift;
202
203    print STDERR "Reading and Parsing XML from $Filename for $area_name\n" if $DEBUG|| $VERBOSE;
204
205    $AREA_FILTER = Geo::Filter::Area->new( area => $area_name );
206
207    $PARSING_START_TIME=time();
208    $READ_FH = data_open($Filename);
209    my $P = new XML::Parser( Handlers => {
210        Start => \&DoStart, 
211        End => \&DoEnd, 
212        Char => \&DoChar});
213    eval {
214        $P->parse($READ_FH);
215        $READ_FH->close();
216    };
217    if ( $VERBOSE || $DEBUG )  {
218        print STDERR "\n";
219    }
220
221    # Print out not parsed lines
222    my $count=20;
223    $READ_FH->setpos($OK_POS);
224    while ( ($count--) && (my $line = $READ_FH->getline() )) {
225        print "REST: $line";
226    }
227
228    if ($@) {
229        print STDERR "WARNING: Could not parse osm data $Filename\n";
230        print STDERR "ERROR: $@\n";
231        return;
232    }
233    if (not $P) {
234        print STDERR "WARNING: Could not parse osm data $Filename\n";
235        return;
236    }
237    $Stats{"time parsing"} = time()-$PARSING_START_TIME;
238    printf("osm2csv: Parsing Osm-Data in %.0f sec\n",time()-$PARSING_START_TIME )
239        if $DEBUG || $VERBOSE;
240
241}
242
243#----------------------------------------------
244# Main output (segments)
245#----------------------------------------------
246sub output_osm($){
247    my $filename = shift;
248
249    $PARSING_START_TIME=time();
250
251    print STDERR "Writing Segments to $filename\n" if $DEBUG || $VERBOSE;
252    if(! open(OSM,">$filename")) {
253        warn "output_osm: Cannot write to $filename\n";
254        return;
255    }
256    binmode(OSM,":utf8");
257    foreach my $id (keys %Segments){
258        my $Segment = $Segments{$id};
259        next unless defined $Segment;
260        my $From = $Segment->{"from"};
261        my $To = $Segment->{"to"};
262        unless ( $From && $To ) {
263            $Stats{"segments without endpoints"}++;
264            next;
265        }
266        unless ( defined($Nodes{$From}) && defined($Nodes{$To}) ) {
267            $Stats{"segments without endpoint nodes defined"}++;
268            next;
269        }
270
271        printf OSM "%s,%s,",$Nodes{$From},$Nodes{$To};
272        for  my $tag ( @needed_segment_tags ) {
273            my $v='';
274            $v = $Segment->{$tag} if defined $Segment->{$tag};
275            print OSM "$v,";
276        }
277        foreach my $k ( keys %{$Segment} ){
278            #next if $k =~ m/^(class|name|highway)$/;
279            next if $k =~ m/^(from|to|segments)$/;
280            my $v = ($Segment->{$k}||'');
281            printf OSM ",%s=%s",$k,$v
282        }
283        printf OSM "\n",
284    }
285    close OSM;
286    $Stats{"time output"} = time()-$PARSING_START_TIME;
287}
288
289#----------------------------------------------
290# output (named point)
291#----------------------------------------------
292sub write_named_point($$){
293    my $fh_named_points = shift;
294    my $Node = shift;
295    return unless defined $Node;
296    $Stats{"Nodes with zero lat/long"}++ 
297        if($Node->{"lat"} == 0 and $Node->{"lon"} == 0);
298   
299    my $result = '';
300    if($Node->{"name"} || 
301       $Node->{"amenity"} || 
302       $Node->{"class"} || 
303       $Node->{"abutters"}){
304        $result = sprintf( "%f,%f",$Node->{"lat"},$Node->{"lon"});
305        for  my $tag ( @needed_segment_tags ) {
306            $result .= sprintf( ",%s", ($Node->{$tag}||''));
307        };
308        foreach my $k ( keys %{$Node} ) {
309            next if $k =~ m/created_by/;
310            next if $k =~ m/converted_by/;
311            next if $k =~ m/source/;
312            next if $k =~ m/time/;
313            next if $k =~ m/^(lat|lon|name|amenity|class|highway)$/;
314            my $v = $Node->{$k};
315            $result .= sprintf ",%s=%s",$k,$v;
316        }           
317        print $fh_named_points "$result\n";
318    }
319}
320
321#----------------------------------------------
322# Statistics output
323#----------------------------------------------
324sub output_statistic($){
325    my $filename = shift;
326    print STDERR "Statistics output $filename\n" if $DEBUG;
327    if(! open(STATS,">$filename")) {
328        warn "output_osm: Cannot write to $filename\n";
329        return;
330    }
331    binmode(STATS,":utf8");
332
333    foreach(sort {$AllTags{$b} <=> $AllTags{$a}} keys(%AllTags)){
334        printf STATS "* %d %s\n", $AllTags{$_}, $_;
335    }
336    printf STATS "\n\nStats:\n";
337    for my $k ( keys(%Stats) ){
338        printf STATS "* %5d %s\n", $Stats{$k}, ($k||'');
339    }
340}
341
342# Function is called whenever an XML tag is started
343#----------------------------------------------
344sub DoStart()
345{
346    my ($Expat, $Name, %Attr) = @_;
347   
348    if($Name eq "node"){
349        undef %Tags;
350        %MainAttr = %Attr;
351        $Type = "n";
352    }
353    if($Name eq "segment"){
354        undef %Tags;
355        %MainAttr = %Attr;
356        $Type = "s";
357    }
358    if($Name eq "way"){
359        undef %Tags;
360        undef @WaySegments;
361        %MainAttr = %Attr;
362        $Type = "w";
363    }
364    if($Name eq "tag"){
365        # TODO: protect against id,from,to,lat,long,etc. being used as tags
366        $Tags{$Attr{"k"}} = $Attr{"v"};
367        $AllTags{$Attr{"k"}}++;
368        $Stats{"tags"}++;
369    }
370    if($Name eq "seg"){
371        my $id = $Attr{"id"};
372        if ( defined ( $Segments{$id} ) ) {
373            push(@WaySegments, $id);
374        }
375    }
376}
377
378# Function is called whenever an XML tag is ended
379#----------------------------------------------
380sub DoEnd(){
381    my ($Expat, $Element) = @_;
382    my $ID = $MainAttr{"id"};
383    $Stats{"${Element}s read"}++;
384    $Stats{"tags read"}++;
385    if ( defined( $Stats{"${Element}s read"} )
386         &&( $Stats{"${Element}s read"}== 1 ) ){
387        $Stats{"memory at 1st $Element rss"} = sprintf("%.0f",mem_usage('rss'));
388        $Stats{"memory at 1st $Element vsz"} = sprintf("%.0f",mem_usage('vsz'));
389        if ( $DEBUG >1 || $VERBOSE >1) {
390            print STDERR "\n";
391        }
392    }
393   
394    if($Element eq "node"){
395        my $node={};
396        $node->{"lat"} = $MainAttr{"lat"};
397        $node->{"lon"} = $MainAttr{"lon"};
398       
399        if ( $AREA_FILTER->inside($node) ) {
400            $Nodes{$ID} = sprintf("%f,%f",$MainAttr{lat}, $MainAttr{lon});
401            foreach(keys(%Tags)){
402                $node->{$_} = $Tags{$_};
403            }
404            write_named_point($fh_named_points,$node);
405            $Stats{"nodes named"}++ if($node->{"name"});
406            $Stats{"nodes tagged "}++ if($MainAttr{"tags"});
407            $Stats{"nodes"}++;
408        }
409    }
410
411    if($Element eq "segment"){
412        my $from = $MainAttr{"from"};
413        my $to   = $MainAttr{"to"};
414        if ( defined($Nodes{$from}) && defined($Nodes{$to}) ) {
415            $Segments{$ID}{"from"} = $from;
416            $Segments{$ID}{"to"} = $to;
417
418            for ( @needed_segment_tags ) {
419                $Segments{$ID}{$_} = $Tags{$_};
420            }
421            $Stats{"segments tagged"}++ if $MainAttr{"tags"};
422            $Stats{"segments"}++;
423        }
424    }
425
426    if($Element eq "way"){
427        if ( @WaySegments ) {
428            foreach my $seg_id( @WaySegments ){ # we only have the needed ones in here
429                for my $tag ( @needed_segment_tags ) {
430                    if ( defined($MainAttr{$tag}) && $MainAttr{$tag} ) {
431                        $Segments{$seg_id}{$tag} = $MainAttr{$tag};
432                    }
433                    if ( defined($Tags{$tag}) && $Tags{$tag} ) {
434                        $Segments{$seg_id}{$tag} = $Tags{$tag};
435                    }
436                }
437            }
438            $Stats{"ways"}++;
439        }
440    }
441
442    if ( ( $VERBOSE || $DEBUG ) &&
443#        ! ( $Stats{"tags read"} % 10000 ) &&
444         ( time()-$PARSING_DISPLAY_TIME >0.9)
445
446         )  {
447        $PARSING_DISPLAY_TIME= time();
448        print STDERR "\r";
449        print STDERR "Read(".$AREA_FILTER->name()."): ";
450        for my $k ( qw(tags nodes segments ways ) ) {
451            next if $k =~ m/( estim| read| named| rss| vsz)$/;
452            if ( $DEBUG>6 || $VERBOSE>6) {
453                print STDERR $k;
454            } else {
455                print STDERR substr($k,0,1);
456            }
457            print STDERR ":".$Stats{$k};
458            printf STDERR "=%.0f%%",(100*$Stats{"$k"}/$Stats{"$k read"})
459                if $Stats{"$k read"};
460            printf STDERR " named:%d ",($Stats{"$k named"})
461                if $Stats{"$k named"} && ($VERBOSE >4);
462            printf STDERR "(%d",($Stats{"$k read"}||0);
463            if ( $Stats{"$k read"} && defined($Stats{"$k estim"}) ) {
464                printf STDERR "=%.0f%%",(100*($Stats{"$k read"}||0)/$Stats{"$k estim"});
465            }
466            print STDERR ") ";
467        }
468       
469        my $rss = sprintf("%.0f",mem_usage('rss'));
470        $Stats{"max rss"} = max($Stats{"max rss"},$rss) if $rss;
471        printf STDERR "max-rss %d" ,($Stats{"max rss"}) if $Stats{"max rss"} >$rss*1.3;
472        my $vsz = sprintf("%.0f",mem_usage('vsz'));
473        $Stats{"max vsz"} = max($Stats{"max vsz"},$vsz) if $vsz;
474        printf STDERR "max-vsz %d" ,($Stats{"max vsz"}) if $Stats{"max vsz"} >$vsz*1.3;
475
476        print STDERR mem_usage();
477        print STDERR time_estimate($PARSING_START_TIME,$Stats{"tags read"},$Stats{"tags estim"});
478        print STDERR "\r";
479    }
480}
481
482# Function is called whenever text is encountered in the XML file
483#----------------------------------------------
484sub DoChar(){
485    my ($Expat, $String) = @_;
486}
487
488##################################################################
489# Usage/manual
490
491__END__
492
493=head1 NAME
494
495B<osm2csv.pl> Version 0.02
496
497=head1 DESCRIPTION
498
499B<osm2csv.pl> is a program to convert osm-data from xml format to
500a plain text file in cvs form.
501This format then is normally used by osmpdfatlas.pl
502
503=head1 SYNOPSIS
504
505B<Common usages:>
506
507osm2csv.pl [-d] [-v] [-h] [--no-mirror] [--proxy=<proxy:port>] [--list-areas] <planet_filename.osm>
508
509=head1 OPTIONS
510
511=over 2
512
513=item B<--man> Complete documentation
514
515Complete documentation
516
517=item B<--proxy=<proxy:port>>
518
519Use proxy Server to get the newest planet.osm File
520
521=item B<--no-mirror>
522
523do not try to get the newest planet.osm first
524
525=item B<--osm=filename>
526
527Source File in OSM Format
528
529=item B<--area=germany> Area Filter
530
531Only read area for processing
532
533=item B<--list-areas>
534
535print all areas possible
536
537=item B<--tie-nodes-hash>
538
539if set we will tie the Nodes Hash to a File
540This is at least 10 times slower, but we have less problems with
541running out of memory.
542We have an internal list of estimated memory use and we'll try
543automgically to tie it if you don't have enough memory for a
544specified region.
545
546=item B<planet_filename.osm>
547
548the file to read from
549
550=back
551
552=head1 COPYRIGHT
553
554Copyright 2006, OJW
555
556This program is free software; you can redistribute it and/or
557modify it under the terms of the GNU General Public License
558as published by the Free Software Foundation; either version 2
559of the License, or (at your option) any later version.
560
561This program is distributed in the hope that it will be useful,
562but WITHOUT ANY WARRANTY; without even the implied warranty of
563MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
564GNU General Public License for more details.
565
566You should have received a copy of the GNU General Public License
567along with this program; if not, write to the Free Software
568Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
569
570=head1 AUTHOR
571
572OJW <streetmap@blibbleblobble.co.uk>
573Jörg Ostertag (osm2csv-for-openstreetmap@ostertag.name)
574
575=head1 SEE ALSO
576
577http://www.openstreetmap.org/
578
579=cut
Note: See TracBrowser for help on using the repository browser.