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

Last change on this file since 29338 was 21687, checked in by mrness, 9 years ago

dd support for nested relations.
Fix regular expressions.

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