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

Last change on this file since 6294 was 4893, checked in by nickburch, 12 years ago

Finish converting to support the 0.5 API

  • Property svn:executable set to *
File size: 9.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        ['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( 250 * 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                                warn("Got unknown member type '$type' in '$line'"); next;
231                        }
232                }
233
234                # Do the decisions when closing tags - can be self closing
235                elsif($line =~ /^\s*<\/?node/) {
236                        my ($id,$lat,$long) = ($main_line =~ /^\s*<node id=['"](\d+)['"] lat=['"]?(\-?[\d\.]+)['"]? lon=['"]?(\-?[\d\.]+e?\-?\d*)['"]?/);
237
238                        unless($id) { warn "Invalid node line '$main_line'"; next; }
239                        unless($main_type eq "node") { warn "$main_type ended with $line"; next; }
240                        if($nodeH) {
241                                &$nodeH($id,$lat,$long,\@tags,$main_line,$line);
242                        }
243                }
244                elsif($line =~ /^\s*\<\/?way/) {
245                        my ($id) = ($main_line =~ /^\s*\<way id=[\'\"](\d+)[\'\"]/);
246
247                        unless($id) { warn "Invalid way line '$main_line'"; next; }
248                        unless($main_type eq "way") { warn "$main_type ended with $line"; next; }
249                        if($wayH) {
250                                &$wayH($id,\@tags,\@nodes,$main_line,$line);
251                        }
252                }
253                elsif($line =~ /^\s*<\/?relation/) {
254                        my ($id) = ($main_line =~ /^\s*\<relation id=[\'\"](\d+)[\'\"]/);
255
256                        unless($id) { warn "Invalid relation line '$main_line'"; next; }
257                        unless($main_type eq "relation") { warn "$main_type ended with $line"; next; }
258                        if($relH) {
259                                &$relH($id,\@tags,\@rel_nodes,\@rel_ways,$main_line,$line);
260                        }
261                }
262                elsif($line =~ /^\s*\<\?xml/) {
263                        if($pass == 1) {
264                                print $line;
265                        }
266                }
267                elsif($line =~ /^\s*\<osm /) {
268                        if($pass == 1) {
269                                print $line;
270                        }
271                }
272                elsif($line =~ /^\s*\<\/osm\>/ ) {
273                        if($pass == 3) {
274                                print $line;
275                        }
276                }
277                else {
278                        print STDERR "Unknown line $line\n";
279                };
280        }
281
282        # All done
283        closeXML();
284}
285
286
287# First up, call for relations
288my $relTagHelper = &buildTagMatcher(@rel_sel_tags);
289processXML(undef, undef, sub {
290        my ($id,$tagsRef,$relNodesRef,$relWaysRef,$main_line,$line) = @_;
291
292        # Test the tags, to see if we want this
293        if(&$relTagHelper(@$tagsRef)) {
294                # Bingo, matched
295                # Record the ways and nodes we want to get
296                foreach my $wref (@$relWaysRef) {
297                        $wanted_ways->Bit_On($wref->{'ref'});
298                }
299                foreach my $nref (@$relNodesRef) {
300                        $wanted_nodes->Bit_On($nref->{'ref'});
301                }
302
303                # Output
304                print $main_line;
305                foreach my $wref (@$relWaysRef) {
306                        print "    <member type=\"$wref->{'type'}\" ref=\"$wref->{'ref'}\" role=\"$wref->{'role'}\" />\n";
307                }
308                foreach my $nref (@$relNodesRef) {
309                        print "    <member type=\"$nref->{'type'}\" ref=\"$nref->{'ref'}\" role=\"$nref->{'role'}\" />\n";
310                }
311                &printTags(@$tagsRef);
312                print $line;
313        } else {
314                # Not wanted, skip
315        }
316});
317
318# Now for ways
319my $wayTagHelper = &buildTagMatcher(@way_sel_tags);
320processXML(undef, sub {
321        my ($id,$tagsRef,$nodesRef,$main_line,$line) = @_;
322        my $wanted = 0;
323
324        # Test the tags, to see if we want this
325        if(&$wayTagHelper(@$tagsRef)) {
326                # Bingo, matched
327                $wanted = 1;
328        } else {
329                # Does a relation want it?
330                if($wanted_ways->contains($id)) {
331                        # A relation wants it
332                        $wanted = 1;
333                }
334        }
335
336        if($wanted) {
337                # Record the nodes we want to get
338                foreach my $node (@$nodesRef) {
339                        $wanted_nodes->Bit_On($node);
340                }
341
342                # Output
343                print $main_line;
344                foreach my $node (@$nodesRef) {
345                        print "    <nd ref=\"$node\" />\n";
346                }
347                &printTags(@$tagsRef);
348                print $line;
349        } else {
350                # Not wanted, skip
351        }
352}, undef);
353
354# Now for nodes
355my $nodeTagHelper = &buildTagMatcher(@node_sel_tags);
356processXML(sub {
357        my ($id,$lat,$long,$tagsRef,$main_line,$line) = @_;
358        my $wanted = 0;
359
360        # Test the tags, to see if we want this
361        if(&$nodeTagHelper(@$tagsRef)) {
362                # Bingo, matched
363                $wanted = 1;
364        } else {
365                # Does a relation or way want it?
366                if($wanted_nodes->contains($id)) {
367                        # Something wants it
368                        $wanted = 1;
369                }
370        }
371
372        if($wanted) {
373                # Output
374                print $main_line;
375                unless($main_line =~ /\/>/) {
376                        &printTags(@$tagsRef);
377                        print $line;
378                }
379        } else {
380                # Not wanted, skip
381        }
382}, undef, undef);
383
384
385# All done
386
387##################################################################
388# Usage/manual
389
390__END__
391
392=head1 NAME
393
394B<planetosm-excerpt-tags.pl>
395
396=head1 DESCRIPTION
397
398=head1 SYNOPSIS
399
400B<Common usages:>
401
402
403B<planertosm-excerpt-tags.pl> <planet.osm.xml> > excerpt.osm
404
405parse an excerpted planet.osm file, and output the parts that match certain
406tags.
407
408=head1 AUTHOR
409
410=head1 COPYRIGHT
411
412
413=head1 SEE ALSO
414
415http://www.openstreetmap.org/
416
417=cut
Note: See TracBrowser for help on using the repository browser.