source: subversion/applications/utils/osm-extract/history-excerpt.pl

Last change on this file was 24769, checked in by frederik, 9 years ago

new history excerpt script

  • Property svn:executable set to *
File size: 8.7 KB
Line 
1#!/usr/bin/perl
2#
3# Takes a history planet file and excerpts an area from it.
4#
5# Written by Frederik Ramm <frederik@remote.org> - public domain.
6#
7# This is loosely based on planetosm-excerpt-area, written by Nick Burch in 2006.
8#
9# Input is read from stdin, so the history planet may be decompressed and
10# piped in. The output of this script is a valid OSM XML file which differs
11# from normal OSM XML files in that it may have more than one version of the
12# same object.
13#
14# This script does not use a proper XML parser, and is geared specifically
15# to the format currently produced by the history dump writer. Most imporantly,
16# that writer puts the whole file in one long line with no whitespace between
17# XML tags.
18#
19# The bbox is specified osmosis-style on the command line. Example:
20#
21# bzcat history-planet.osm.bz2 | perl history-excerpt.pl left=10 right=11.5 top=49 bottom=48 > excerpt.osm
22#
23# Criteria for inclusion:
24#
25# * A node is included if it falls inside the specified bounding box.
26#
27#   - Once a node has been included, all future versions will be included
28#     even if they move outside the bounding box; but previous versions
29#     will not be included.
30#
31# * A way is included if it references a node of which at least one version
32#   has been included. Temporality is not taken into account, i.e. the way
33#   version is included even if the referenced node only moved into the
34#   area of interest later.
35#
36#   - If ways reference nodes that have not been included, these references
37#     will be removed from the way.
38#   - If less than two nodes remain in a way version, it will not be written.
39#     This means that it is possible for a way to be present e.g. only in
40#     versions 1,6,7.
41#
42# * Relations are treated the same as ways, but cascading relations are not
43#   supported - all members of type "relation" will be dropped from relations.
44
45use strict;
46use warnings;
47
48use Getopt::Long;
49use Bit::Vector;
50use POSIX;
51
52my $left;
53my $right;
54my $top;
55my $bottom;
56
57my $indent = "  ";
58my $buffersize = 1024 * 1024 * 1024;
59
60while(my $arg = shift)
61{
62    if ($arg =~ /^(left|right|top|bottom)=(-?\d*(\.?\d+)?)$/)
63    {
64        eval('$'.$1.'='.$2);
65    }
66    else
67    {
68        usage();
69    }
70}
71
72usage() if (!defined($left) or $left < -180 or $left > 180);
73usage() if (!defined($right) or $right < -180 or $right > 180 or $right <= $left);
74usage() if (!defined($bottom) or $bottom < -90 or $bottom > 90);
75usage() if (!defined($top) or $top < -90 or $top > 90 or $top <= $bottom);
76
77# Counts of the numbers handled
78my $node_count = 0;
79my $way_count = 0;
80my $rel_count = 0;
81my $tag_count = 0;
82
83# We assume IDs to be up to 1500 million for nodes, 250 million for ways
84my $nodes = Bit::Vector->new( 1500 * 1000 * 1000 );
85my $ways = Bit::Vector->new( 250 * 1000 * 1000 );
86
87# Hold the id and type of the last valid main tag
88my $last_id = -1;
89my $last_type;
90my $last_copied_id = 0;
91
92# Hold the node and tags list for a way
93my $way_line;
94my @way_tags;
95my @way_nodes;
96# Something similar for relations
97my $rel_line;
98my @rel_tags;
99my @rel_members;
100
101# loop over the input file data
102
103my $buffer = "";
104my $eof = 0;
105my $currentpos = 0;
106
107while(1)
108{
109    my $lt = index($buffer, '><', $currentpos);
110    last if ($lt == -1 && $eof);
111    while ($lt == -1)
112    {
113        $buffer = substr($buffer, $currentpos);
114        $currentpos = 0;
115        my $br = sysread(STDIN, $buffer, $buffersize, length($buffer));
116        if ($br == 0)
117        {
118            $eof = 1;
119            $buffer .= '<';
120        }
121        $lt = index($buffer, '><');
122        $lt = length($buffer) - 2 if ($eof && $lt==-1);
123    }
124    my $tag = substr($buffer, $currentpos, $lt + 1 - $currentpos);
125    my $twolet = substr($tag, 0, 3);
126    $currentpos = $lt + 1;
127
128        $tag_count++;
129
130        # process the tag
131        if ($twolet eq "<no")
132    {
133                my ($id, $lat, $lon) = ($tag =~ /^<node.*\sid=['"](\d+)['"].+lat=['"]?(\-?[\d\.]+)['"]?.+lon=['"]?(\-?[\d\.]+e?\-?\d*)['"]?/);
134        if ($id != $last_id)
135        {
136            $last_id = -1; # In case it has tags we need to exclude
137            $last_type = "node";
138
139            unless($id) { warn "Invalid tag '$tag'"; next; }
140
141            # Do we need to exclude this node?
142            if ($lon < $left || $lon > $right || $lat < $bottom || $lat > $top)
143            {
144                next;
145            }
146        }
147
148                # Output the node
149                print "$tag\n";
150
151                $nodes->Bit_On($id);
152                $last_id = $id;
153
154                $node_count++;
155        }
156        elsif ($twolet eq "<wa") 
157    {
158                my ($id) = ($tag =~ /^<way.*\sid=[\'\"](\d+)[\'\"]/);
159
160        $last_id = -1; # In case it has tags we need to exclude
161        $last_type = "way";
162
163        unless($id) { warn "Invalid line '$tag'"; next; }
164
165        if (($tag =~ /\/>\s*$/) && $id == $last_copied_id)
166        {
167            print $tag."\n";
168            next;
169        }
170
171        # Save ID and line, will add later
172        $last_id = $id;
173        $way_line = $tag;
174
175                $way_count++;
176
177                # Blank way children lists
178                @way_tags = ();
179                @way_nodes = ();
180        }
181        elsif ($twolet eq "</w")
182    {
183                my $way_id = $last_id;
184                $last_id = -1;
185
186                next unless($way_id);
187                next if(scalar(@way_nodes)<2);
188
189                # Record this id
190                $ways->Bit_On($way_id);
191
192                # Output way
193                print $way_line."\n";
194
195                # Output way nodes
196                foreach my $wn (@way_nodes) 
197        {
198                        print $indent.$wn->{line};
199                }
200
201                # Add way tags
202                foreach my $wt (@way_tags) 
203        {
204                        print $indent.$wt->{line};
205                }
206
207                # Finish way
208                print $tag."\n";
209        $last_copied_id = $way_id;
210        }
211        elsif ($twolet eq "<nd")
212    {
213                my ($ref) = ($tag =~ /^<nd ref=[\'\"](\d+)[\'\"]/);
214                next unless($last_id > 0);
215                unless($ref) { warn "Invalid line '$tag'"; next; }
216                next unless($nodes->contains($ref));
217
218                # save, only add later
219                my %wn; 
220                $wn{'line'} = $tag."\n";
221                $wn{'ref'} = $ref;
222
223                push (@way_nodes,\%wn);
224        }
225        elsif ($twolet eq "<re")
226    {
227                my ($id) = ($tag =~ /^<relation.*\sid=[\'\"](\d+)[\'\"]/);
228                $last_id = -1; # In case it has tags we need to exclude
229                $last_type = "relation";
230
231                unless($id) { warn "Invalid line '$tag'"; next; }
232
233        if (($tag =~ /\/>\s*$/) && $id == $last_copied_id)
234        {
235            print $tag."\n";
236            next;
237        }
238
239                # save ID and line, will add later
240                $last_id = $id;
241                $rel_line = $tag;
242
243                $rel_count++;
244
245                # blank relation children lists
246                @rel_tags = ();
247                @rel_members = ();
248        }
249        elsif ($twolet eq "</r")
250    {
251                my $rel_id = $last_id;
252                $last_id = -1;
253
254                next unless($rel_id);
255                next unless(@rel_members);
256
257                # Output relation
258                print $rel_line."\n";
259
260                # Output ways and nodes
261                foreach my $rm (@rel_members) 
262        {
263                        print $indent.$rm->{line};
264                }
265
266                # Add relation tags
267                foreach my $rt (@rel_tags) 
268        {
269                        print $indent.$rt->{line};
270                }
271
272                # Finish relation
273                print $tag."\n";
274        $last_copied_id = $rel_id;
275        }
276        elsif ($twolet eq "<me")
277    {
278                my ($type,$ref,$role) = ($tag =~ /^<member type=[\'\"](.*?)[\'\"] ref=[\'\"](\d+)[\'\"] role=[\'\"](.*?)[\'\"]/);
279                next unless($last_id > 0);
280                unless($type && $ref) { warn "Invalid line '$tag'"; next; }
281
282                if ($type eq "node") 
283        {
284                        next unless($nodes->contains($ref));
285                } 
286        elsif($type eq "way") 
287        {
288                        next unless($ways->contains($ref));
289                } 
290        else 
291        {
292                        warn("Skipping unknown type '$type' for line '$tag'");
293                        next;
294                }
295
296                # Save, only add later
297                my %rm; 
298                $rm{'line'} = $tag."\n";
299                $rm{'type'} = $type;
300                $rm{'ref'} = $ref;
301                $rm{'role'} = $role;
302
303                push (@rel_members,\%rm);
304        }
305        elsif ($twolet eq "<ta")
306    {
307                my ($name,$value) = ($tag =~ /^<tag k=[\'\"](.*?)[\'\"] v=[\'\"](.*?)[\'\"]/);
308                unless($name) 
309        { 
310                        unless($tag =~ /k="" v=""/) 
311            {
312                                warn "Invalid line '$tag'"; 
313                        }
314                        next; 
315                }
316                if ($name =~ /^\s+$/) { warn "Skipping invalid tag line '$tag'"; next; }
317
318                # If last_id isn't there, the element we're attached to was invalid
319                next unless($last_id > 0);
320
321                if ($last_type eq "node") 
322        {
323                        print $indent.$tag."\n";
324                } 
325        elsif ($last_type eq "way") 
326        {
327                        # Save, only add if way has nodes
328                        my %wt; 
329                        $wt{'line'} = $tag."\n";
330                        $wt{'name'} = $name;
331                        $wt{'value'} = $value;
332                        push (@way_tags,\%wt);
333                } 
334        elsif ($last_type eq "relation") 
335        {
336                        # Save, only add if relation has nodes/ways
337                        my %rt; 
338                        $rt{'line'} = $tag."\n";
339                        $rt{'name'} = $name;
340                        $rt{'value'} = $value;
341                        push (@rel_tags,\%rt);
342                }
343        }       
344        elsif ($twolet eq "<?x")
345    {
346                print $tag."\n";
347        }
348        elsif ($twolet eq "<os" || $twolet eq "</o")
349    {
350                print $tag."\n";
351        }
352        elsif ($twolet eq "</n")
353    {
354                print $tag."\n" if ($last_id > 0);
355        }
356        elsif ($twolet eq "<ch" or $twolet eq "</c")
357    {
358                # ignore
359        }
360        else
361    {
362            print STDERR "Unknown line $tag\n";
363        };
364}
365
366sub usage
367{
368    print STDERR "usage: $0 filename.osm left=x bottom=x right=x top=x\n\n";
369    print STDERR "  excerpts the given bounding box from the given history file, and\n";
370    print STDERR "  writes the result to standard output.\n";
371    exit;
372}
Note: See TracBrowser for help on using the repository browser.