source: subversion/applications/utils/osm-extract/planetosm-excerpt-tags.pl @ 9137

Last change on this file since 9137 was 9137, checked in by frederik, 11 years ago

support negative ids, fix usage instructions

  • Property svn:executable set to *
File size: 9.7 KB
Line 
1#!/usr/bin/perl
2# Takes a planet.osm, and extracts just the bits that have certain tags
3#
4# Requires several passes over the file, so can't work with a stream from
5#  STDIN. Normally run on an area excerpt, or data downloaded from the API
6#
7# For now, all configuration is done in the code. In future, we'll want to
8#  split this out into a rules file
9#
10# Nick Burch
11#     v0.01   01/11/2006
12
13use strict;
14use warnings;
15
16###########################################################################
17#                BEGIN USER CONFIGURATION BLOCK                           #
18###########################################################################
19
20# With these, give a tag name, and optionally a tag value
21# If you only want to match on name, not value, put in undef for the value
22
23# We will get all Nodes required by Ways and Relations
24# We can optionally also get other Nodes, based on their tags
25my @node_sel_tags = (
26        ['place','town'], 
27    ['place','city'],
28        ['railway',undef],
29);
30
31# We will get all Ways required by Relations
32# We can optionally also get other Ways, based on their tags
33my @way_sel_tags = (
34        ['railway',undef],
35        ['highway','motorway'],
36#       ['waterway','river'],
37#       ['natural','coastline'], # Gives really huge .osm files
38);
39
40# Specify which Relations to get, based on their tags
41my @rel_sel_tags = (
42#       ['type','multipolygon'],
43);
44
45###########################################################################
46#               END OF USER CONFIGURATION BLOCK                           #
47###########################################################################
48
49
50
51BEGIN {
52    my $dir = $0;
53    $dir =~s,[^/]+/[^/]+$,,;
54    unshift(@INC,"$dir/perl_lib");
55
56    unshift(@INC,"./perl_lib");
57    unshift(@INC,"../perl_lib");
58    unshift(@INC,"~/svn.openstreetmap.org/utils/perl_lib");
59    unshift(@INC,"$ENV{HOME}/svn.openstreetmap.org/utils/perl_lib");
60}
61
62use Getopt::Long;
63
64use Geo::OSM::Planet;
65use Pod::Usage;
66
67# We need Bit::Vector, as perl hashes can't handle the sort of data we need
68use Bit::Vector;
69
70our $man=0;
71our $help=0;
72my $bbox_opts='';
73
74my $VERBOSE;
75
76Getopt::Long::Configure('no_ignore_case');
77GetOptions ( 
78             'verbose+'         => \$VERBOSE,
79             'v+'               => \$VERBOSE,
80             'MAN'              => \$man, 
81             'man'              => \$man, 
82             'h|help|x'         => \$help, 
83             ) or pod2usage(1);
84
85pod2usage(1) if $help;
86pod2usage(-verbose=>2) if $man;
87
88
89# Grab the filename
90my $xml = shift||'';
91pod2usage(1) unless $xml;
92
93# Check we can load the file
94if($xml eq "-") {
95        die("Sorry, reading from stdin is not supported, as we have to make several passes\n");
96}
97unless( -f $xml) {
98        die("Planet.osm file '$xml' could not be found\n");
99}
100
101unless( -s $xml ) {
102    die " $xml has 0 size\n";
103}
104
105
106# We assume IDs to be up to 250 million
107my $wanted_nodes = Bit::Vector->new( 500 * 1000 * 1000 );
108my $wanted_ways  = Bit::Vector->new( 250 * 1000 * 1000 );
109
110
111# Sub to open xml
112sub openXML {
113        open(XML, "<$xml") or die("$!");
114        #open(XML, "<:utf8","$xml") or die("$!");
115}
116# Sub to close xml
117sub closeXML {
118        close XML;
119}
120
121# Sub to build sub to do tag matching
122sub buildTagMatcher {
123        my @rules = @_;
124        return sub {
125                my @tagsToTest = @_;
126                foreach my $tagToTest (@tagsToTest) {
127                        my ($name,$value) = @$tagToTest;
128                        foreach my $r (@rules) {
129                                my ($rname,$rvalue) = @$r;
130                                if($rvalue) {
131                                        # Check the rule name+value with the supplied name+value
132                                        if($rname eq $name && $rvalue eq $value) { return 1; }
133                                } else {
134                                        # Check the rule name with the supplied name
135                                        if($rname eq $name) { return 1; }
136                                }
137                        }
138                }
139                # No match on any of the tags
140                return 0;
141        };
142}
143
144# To print out a series of tags as xml
145sub printTags {
146        my @tags = @_;
147        foreach my $tagSet (@tags) {
148                print "    <tag k=\"$tagSet->[0]\" v=\"$tagSet->[1]\" />\n";
149        }
150}
151
152
153# Sub to process the file, against a bunch of helper subroutines
154my $pass = 0;
155sub processXML {
156        my ($nodeH, $wayH, $relH) = @_;
157        openXML();
158        $pass++;
159
160        # Process the file, giving tags to the helpers that like them
161
162        # Hold the main line, tags and segs of the tag
163        my $main_line;
164        my $main_type;
165        my $wanted;
166        my @tags;
167        my @nodes;
168        my @rel_ways;
169        my @rel_nodes;
170
171        my $startNewTag = sub{
172                $wanted = 0;
173                @tags = ();
174                @nodes = ();
175                @rel_ways = ();
176                @rel_nodes = ();
177        };
178
179        while(my $line = <XML>) {
180                if($line =~ /^\s*<node/) {
181                        $main_line = $line;
182                        $main_type = "node";
183                        &$startNewTag();
184                        unless($line =~ /\/>\s*$/) { next; }
185                }
186                elsif($line =~ /^\s*\<way/) {
187                        $main_line = $line;
188                        $main_type = "way";
189                        &$startNewTag();
190                        unless($line =~ /\/>\s*$/) { next; }
191                }
192                elsif($line =~ /^\s*<relation/) {
193                        $main_line = $line;
194                        $main_type = "relation";
195                        &$startNewTag();
196                        unless($line =~ /\/>\s*$/) { next; }
197                }
198
199                if($line =~ /^\s*\<tag/) {
200                        my ($name,$value) = ($line =~ /^\s*\<tag k=[\'\"](.*?)[\'\"] v=[\'\"](.*?)[\'\"]/);
201                        unless($name) { 
202                                unless($line =~ /k="\s*" v="\s*"/) {
203                                        warn "Invalid line '$line'"; 
204                                }
205                                next; 
206                        }
207                        my @tag = ($name,$value);
208                        push @tags, \@tag;
209                }
210                elsif($line =~ /^\s*\<nd /) {
211                        my ($ref) = ($line =~ /^\s*\<nd ref=[\'\"](-?\d+)[\'\"]/);
212                        unless($main_type eq "way") { warn "Got nd when in $main_type\n"; next; }
213                        unless($ref) { warn "Invalid line '$line'"; next; }
214                        push @nodes, $ref;
215                }
216                elsif($line =~ /^\s*\<member /) {
217                        my ($type,$ref,$role) = ($line =~ /^\s*\<member type=[\'\"](.*?)[\'\"] ref=[\'\"](\d+)[\'\"] role=[\'\"](.*)[\'\"]/);
218                        unless($main_type eq "relation") { warn "Got member when in $main_type\n"; next; }
219                        unless($type && $ref) { warn "Invalid line '$line'"; next; }
220
221                        my %m;
222                        $m{'type'} = $type;
223                        $m{'ref'} = $ref;
224                        $m{'role'} = $role;
225                        if($type eq "node") {
226                                push @rel_nodes, \%m;
227                        } elsif($type eq "way") {
228                                push @rel_ways, \%m;
229                        } else {
230                # FIXME doesn't support nested relations...
231                                warn("Got unknown member type '$type' in '$line'"); next;
232                        }
233                }
234
235                # Do the decisions when closing tags - can be self closing
236                elsif($line =~ /^\s*<\/?node/) {
237                        my ($id,$lat,$long) = ($main_line =~ /^\s*<node.*id=['"](-?\d+)['"].*lat=['"]?(\-?[\d\.]+)['"]?.*lon=['"]?(\-?[\d\.]+e?\-?\d*)['"]?/);
238
239                        unless($id) { warn "Invalid node line '$main_line'"; next; }
240                        unless($main_type eq "node") { warn "$main_type ended with $line"; next; }
241                        if($nodeH) {
242                                &$nodeH($id,$lat,$long,\@tags,$main_line,$line);
243                        }
244                }
245                elsif($line =~ /^\s*\<\/?way/) {
246                        my ($id) = ($main_line =~ /^\s*\<way id=[\'\"](-?\d+)[\'\"]/);
247
248                        unless($id) { warn "Invalid way line '$main_line'"; next; }
249                        unless($main_type eq "way") { warn "$main_type ended with $line"; next; }
250                        if($wayH) {
251                                &$wayH($id,\@tags,\@nodes,$main_line,$line);
252                        }
253                }
254                elsif($line =~ /^\s*<\/?relation/) {
255                        my ($id) = ($main_line =~ /^\s*\<relation id=[\'\"](\d+)[\'\"]/);
256
257                        unless($id) { warn "Invalid relation line '$main_line'"; next; }
258                        unless($main_type eq "relation") { warn "$main_type ended with $line"; next; }
259                        if($relH) {
260                                &$relH($id,\@tags,\@rel_nodes,\@rel_ways,$main_line,$line);
261                        }
262                }
263                elsif($line =~ /^\s*\<\?xml/) {
264                        if($pass == 1) {
265                                print $line;
266                        }
267                }
268                elsif($line =~ /^\s*\<osm /) {
269                        if($pass == 1) {
270                                print $line;
271                        }
272                }
273                elsif($line =~ /^\s*\<\/osm\>/ ) {
274                        if($pass == 3) {
275                                print $line;
276                        }
277                }
278                else {
279                        print STDERR "Unknown line $line\n";
280                };
281        }
282
283        # All done
284        closeXML();
285}
286
287
288# First up, call for relations
289my $relTagHelper = &buildTagMatcher(@rel_sel_tags);
290processXML(undef, undef, sub {
291        my ($id,$tagsRef,$relNodesRef,$relWaysRef,$main_line,$line) = @_;
292
293        # Test the tags, to see if we want this
294        if(&$relTagHelper(@$tagsRef)) {
295                # Bingo, matched
296                # Record the ways and nodes we want to get
297                foreach my $wref (@$relWaysRef) {
298                        $wanted_ways->Bit_On($wref->{'ref'});
299                }
300                foreach my $nref (@$relNodesRef) {
301                        $wanted_nodes->Bit_On($nref->{'ref'});
302                }
303
304                # Output
305                print $main_line;
306                foreach my $wref (@$relWaysRef) {
307                        print "    <member type=\"$wref->{'type'}\" ref=\"$wref->{'ref'}\" role=\"$wref->{'role'}\" />\n";
308                }
309                foreach my $nref (@$relNodesRef) {
310                        print "    <member type=\"$nref->{'type'}\" ref=\"$nref->{'ref'}\" role=\"$nref->{'role'}\" />\n";
311                }
312                &printTags(@$tagsRef);
313                print $line;
314        } else {
315                # Not wanted, skip
316        }
317});
318
319# Now for ways
320my $wayTagHelper = &buildTagMatcher(@way_sel_tags);
321processXML(undef, sub {
322        my ($id,$tagsRef,$nodesRef,$main_line,$line) = @_;
323        my $wanted = 0;
324
325        # Test the tags, to see if we want this
326        if(&$wayTagHelper(@$tagsRef)) {
327                # Bingo, matched
328                $wanted = 1;
329        } else {
330                # Does a relation want it?
331                if($wanted_ways->contains($id)) {
332                        # A relation wants it
333                        $wanted = 1;
334                }
335        }
336
337        if($wanted) {
338                # Record the nodes we want to get
339                foreach my $node (@$nodesRef) {
340                        $wanted_nodes->Bit_On($node);
341                }
342
343                # Output
344                print $main_line;
345                foreach my $node (@$nodesRef) {
346                        print "    <nd ref=\"$node\" />\n";
347                }
348                &printTags(@$tagsRef);
349                print $line;
350        } else {
351                # Not wanted, skip
352        }
353}, undef);
354
355# Now for nodes
356my $nodeTagHelper = &buildTagMatcher(@node_sel_tags);
357processXML(sub {
358        my ($id,$lat,$long,$tagsRef,$main_line,$line) = @_;
359        my $wanted = 0;
360
361        # Test the tags, to see if we want this
362        if(&$nodeTagHelper(@$tagsRef)) {
363                # Bingo, matched
364                $wanted = 1;
365        } else {
366                # Does a relation or way want it?
367                if($wanted_nodes->contains($id)) {
368                        # Something wants it
369                        $wanted = 1;
370                }
371        }
372
373        if($wanted) {
374                # Output
375                print $main_line;
376                unless($main_line =~ /\/>/) {
377                        &printTags(@$tagsRef);
378                        print $line;
379                }
380        } else {
381                # Not wanted, skip
382        }
383}, undef, undef);
384
385
386# All done
387
388##################################################################
389# Usage/manual
390
391__END__
392
393=head1 NAME
394
395B<planetosm-excerpt-tags.pl>
396
397=head1 DESCRIPTION
398
399=head1 SYNOPSIS
400
401B<Common usages:>
402
403
404B<planertosm-excerpt-tags.pl> planet.osm.xml> > excerpt.osm
405
406parse an excerpted planet.osm file, and output the parts that match certain
407tags.
408
409=head1 AUTHOR
410
411=head1 COPYRIGHT
412
413
414=head1 SEE ALSO
415
416http://www.openstreetmap.org/
417
418=cut
Note: See TracBrowser for help on using the repository browser.