1 |
##################################################################
|
2 |
## OsmChangeReader.pm - Library for reading OSM change files ##
|
3 |
## By Martijn van Oosterhout <kleptog@svana.org> ##
|
4 |
## ##
|
5 |
## Package that reads both osmChange and JOSM file format change##
|
6 |
## files. The user creates the parse with a callback and the ##
|
7 |
## loader will call the callback for each detected change. Note ##
|
8 |
## that for JOSM file entires that are not changes are ignored. ##
|
9 |
## ##
|
10 |
## Licence: LGPL ##
|
11 |
##################################################################
|
12 |
package Geo::OSM::OsmChangeReader;
|
13 |
|
14 |
use strict;
|
15 |
use warnings;
|
16 |
|
17 |
use Utils::File;
|
18 |
use Utils::Math;
|
19 |
use Utils::Debug;
|
20 |
use XML::Parser;
|
21 |
use Carp;
|
22 |
|
23 |
use Geo::OSM::EntitiesV3;
|
24 |
|
25 |
use constant STATE_INIT => 1;
|
26 |
use constant STATE_EXPECT_COMMAND => 2;
|
27 |
use constant STATE_EXPECT_ENTITY => 3;
|
28 |
use constant STATE_EXPECT_BODY => 4;
|
29 |
|
30 |
use constant FILETYPE_UNKNOWN => 0;
|
31 |
use constant FILETYPE_OSMCHANGE => 1;
|
32 |
use constant FILETYPE_OSM => 2;
|
33 |
|
34 |
sub new
|
35 |
{
|
36 |
my $obj = bless{}, shift;
|
37 |
my $proc = shift;
|
38 |
my $prog = shift;
|
39 |
if( ref $proc ne "CODE" )
|
40 |
{ die "new Geo::OSM::OsmChangeReader requires a sub as argument\n" }
|
41 |
$obj->{oldproc} = $proc;
|
42 |
if( defined $prog )
|
43 |
{ $obj->{progress} = $prog }
|
44 |
return $obj;
|
45 |
}
|
46 |
|
47 |
# With this initialiser, your process will get called with instantiated objects rather than useless details
|
48 |
sub init
|
49 |
{
|
50 |
my $obj = bless{}, shift;
|
51 |
my $proc = shift;
|
52 |
my $prog = shift;
|
53 |
if( ref $proc ne "CODE" )
|
54 |
{ die "init Geo::OSM::OsmChangeReader requires a sub as argument\n" }
|
55 |
$obj->{newproc} = $proc;
|
56 |
if( defined $prog )
|
57 |
{ $obj->{progress} = $prog }
|
58 |
return $obj;
|
59 |
}
|
60 |
|
61 |
sub _process
|
62 |
{
|
63 |
my($self, $command, $entity, $attr, $tags, $segs) = @_;
|
64 |
|
65 |
if( defined $self->{oldproc} )
|
66 |
{
|
67 |
return $self->{oldproc}->($command, $entity, $attr, $tags, $segs);
|
68 |
}
|
69 |
|
70 |
my $ent;
|
71 |
if( $entity eq "node" )
|
72 |
{
|
73 |
$ent = new Geo::OSM::Node( $attr, $tags );
|
74 |
}
|
75 |
if( $entity eq "segment" )
|
76 |
{
|
77 |
$ent = new Geo::OSM::Segment( $attr, $tags );
|
78 |
}
|
79 |
if( $entity eq "way" )
|
80 |
{
|
81 |
$ent = new Geo::OSM::Way( $attr, $tags, $segs );
|
82 |
}
|
83 |
croak "Unknown entity '$entity'" if not defined $ent;
|
84 |
|
85 |
return $self->{newproc}->($command, $ent );
|
86 |
}
|
87 |
|
88 |
sub load{
|
89 |
my ($self, $file_name) = @_;
|
90 |
|
91 |
$self->{filetype} = FILETYPE_UNKNOWN;
|
92 |
$self->{state} = STATE_INIT;
|
93 |
|
94 |
my $start_time = time();
|
95 |
my $P = new XML::Parser(Handlers => {Start => sub{ DoStart( $self, @_ )}, End => sub { DoEnd( $self, @_ )}});
|
96 |
my $fh = data_open($file_name);
|
97 |
die "Cannot open OSM File $file_name\n" unless $fh;
|
98 |
$self->{input_length} = -s $fh;
|
99 |
$self->{count}=0;
|
100 |
eval {
|
101 |
$P->parse($fh);
|
102 |
};
|
103 |
print "\n" if $DEBUG || $VERBOSE;
|
104 |
if ( $VERBOSE) {
|
105 |
printf "Read and parsed $file_name in %.0f sec\n",time()-$start_time;
|
106 |
}
|
107 |
if ( $@ ) {
|
108 |
warn "$@Error while parsing\n $file_name\n";
|
109 |
return;
|
110 |
}
|
111 |
if (not $P) {
|
112 |
warn "WARNING: Could not parse osm data\n";
|
113 |
return;
|
114 |
}
|
115 |
|
116 |
}
|
117 |
|
118 |
sub parse($)
|
119 |
{
|
120 |
my ($self, $string) = @_;
|
121 |
|
122 |
$self->{state} = STATE_INIT;
|
123 |
|
124 |
my $start_time = time();
|
125 |
my $P = new XML::Parser(Handlers => {Start => sub{ DoStart( $self, @_ )}, End => sub { DoEnd( $self, @_ )}});
|
126 |
$self->{input_length} = length($string);
|
127 |
$self->{count}=0;
|
128 |
eval {
|
129 |
$P->parse($string);
|
130 |
};
|
131 |
print "\n" if $DEBUG || $VERBOSE;
|
132 |
if ( $VERBOSE) {
|
133 |
printf "Read and parsed string in %.0f sec\n",time()-$start_time;
|
134 |
}
|
135 |
if ( $@ ) {
|
136 |
warn "$@Error while parsing\n [$string]\n";
|
137 |
return;
|
138 |
}
|
139 |
if (not $P) {
|
140 |
warn "WARNING: Could not parse osm data\n";
|
141 |
return;
|
142 |
}
|
143 |
}
|
144 |
|
145 |
# Function is called whenever an XML tag is started
|
146 |
sub DoStart
|
147 |
{
|
148 |
#print @_,"\n";
|
149 |
my ($self, $Expat, $Name, %Attr) = @_;
|
150 |
|
151 |
if( $self->{filetype} == FILETYPE_UNKNOWN )
|
152 |
{
|
153 |
if( $self->{state} == STATE_INIT )
|
154 |
{
|
155 |
if($Name eq "osmChange"){
|
156 |
$self->{state} = STATE_EXPECT_COMMAND;
|
157 |
$self->{filetype} = FILETYPE_OSMCHANGE;
|
158 |
|
159 |
if( $Attr{version} ne "0.3" and $Attr{version} ne "0.4" )
|
160 |
{ die "OsmChangeReaderV3 can only read 0.3 and 0.4 files, found '$Attr{version}'\n" }
|
161 |
} elsif($Name eq "osm"){
|
162 |
$self->{state} = STATE_EXPECT_ENTITY;
|
163 |
$self->{filetype} = FILETYPE_OSM;
|
164 |
|
165 |
if( $Attr{version} ne "0.3" and $Attr{version} ne "0.4" )
|
166 |
{ die "OsmChangeReaderV3 can only read 0.3 and 0.4 files, found '$Attr{version}'\n" }
|
167 |
} else {
|
168 |
die "Expected 'osmChange' tag, got '$Name'\n";
|
169 |
}
|
170 |
}
|
171 |
}
|
172 |
elsif( $self->{state} == STATE_EXPECT_COMMAND )
|
173 |
{
|
174 |
if($Name eq 'create' or $Name eq 'modify' or $Name eq 'delete'){
|
175 |
$self->{command} = $Name;
|
176 |
$self->{state} = STATE_EXPECT_ENTITY;
|
177 |
} else {
|
178 |
die "Expected command\n";
|
179 |
}
|
180 |
}
|
181 |
elsif( $self->{state} == STATE_EXPECT_ENTITY )
|
182 |
{
|
183 |
# Pick up the origin attribute from the bound tag
|
184 |
if( $Name eq "bound" )
|
185 |
{
|
186 |
if( exists $Attr{origin} )
|
187 |
{
|
188 |
$self->{origin} = $Attr{origin};
|
189 |
}
|
190 |
return;
|
191 |
}
|
192 |
if($Name eq "node" or $Name eq "segment" or $Name eq "way"){
|
193 |
$self->{entity} = $Name;
|
194 |
$self->{attr} = {%Attr};
|
195 |
$self->{tags} = [];
|
196 |
$self->{segs} = ($Name eq "way") ? [] : undef;
|
197 |
$self->{state} = STATE_EXPECT_BODY;
|
198 |
} else {
|
199 |
die "Expected entity\n";
|
200 |
}
|
201 |
}
|
202 |
elsif( $self->{state} == STATE_EXPECT_BODY )
|
203 |
{
|
204 |
if($Name eq "tag"){
|
205 |
push @{$self->{tags}}, $Attr{"k"}, $Attr{"v"};
|
206 |
}
|
207 |
if($Name eq "seg"){
|
208 |
push @{$self->{segs}}, $Attr{"id"};
|
209 |
}
|
210 |
}
|
211 |
}
|
212 |
|
213 |
# Function is called whenever an XML tag is ended
|
214 |
sub DoEnd
|
215 |
{
|
216 |
my ($self, $Expat, $Name) = @_;
|
217 |
if( $self->{state} == STATE_EXPECT_BODY )
|
218 |
{
|
219 |
if( $Name eq $self->{entity} )
|
220 |
{
|
221 |
if( $self->{filetype} == FILETYPE_OSMCHANGE )
|
222 |
{
|
223 |
$self->_process( $self->{command}, $self->{entity}, $self->{attr}, $self->{tags}, $self->{segs} );
|
224 |
}
|
225 |
else # FILETYPE_OSM
|
226 |
{
|
227 |
# Only entities with a modify tag are interesting, or if they have a negative ID (that's create)
|
228 |
if( exists $self->{attr}->{action} )
|
229 |
{
|
230 |
$self->_process( $self->{attr}->{action}, $self->{entity}, $self->{attr}, $self->{tags}, $self->{segs} );
|
231 |
}
|
232 |
elsif( $self->{attr}{id} < 0 )
|
233 |
{
|
234 |
$self->_process( "create", $self->{entity}, $self->{attr}, $self->{tags}, $self->{segs} );
|
235 |
}
|
236 |
}
|
237 |
$self->{count}++;
|
238 |
if( $self->{progress} and ($self->{count}%11) == 1)
|
239 |
{
|
240 |
$self->{progress}->($self->{count}, $Expat->current_byte()/$self->{input_length} );
|
241 |
}
|
242 |
$self->{state} = STATE_EXPECT_ENTITY;
|
243 |
}
|
244 |
return;
|
245 |
}
|
246 |
elsif( $self->{state} == STATE_EXPECT_ENTITY )
|
247 |
{
|
248 |
return if $Name eq "bound";
|
249 |
if( $self->{filetype} == FILETYPE_OSMCHANGE )
|
250 |
{
|
251 |
if( $Name eq $self->{command} )
|
252 |
{
|
253 |
$self->{state} = STATE_EXPECT_COMMAND;
|
254 |
}else {die}
|
255 |
}
|
256 |
else # FILETYPE_OSM
|
257 |
{
|
258 |
if( $Name eq "osm" )
|
259 |
{
|
260 |
$self->{state} = STATE_INIT;
|
261 |
} else {die}
|
262 |
}
|
263 |
return;
|
264 |
}
|
265 |
elsif( $self->{state} == STATE_EXPECT_COMMAND )
|
266 |
{
|
267 |
if( $Name eq "osmChange" )
|
268 |
{
|
269 |
$self->{state} = STATE_INIT;
|
270 |
}else {die}
|
271 |
return;
|
272 |
}
|
273 |
}
|
274 |
|
275 |
1;
|
276 |
|
277 |
__END__
|
278 |
|
279 |
=head1 NAME
|
280 |
|
281 |
OsmChangeReaderV3 - Module for reading OpenStreetMap V3 Change XML data files
|
282 |
|
283 |
=head1 SYNOPSIS
|
284 |
|
285 |
my $OSM = new Geo::OSM::OsmChangeReader(\&process);
|
286 |
$OSM->load("Data/changes.osc");
|
287 |
|
288 |
sub process
|
289 |
{
|
290 |
my($OSM, $command, $entity, $attr, $tags, $segs) = @_;
|
291 |
print "Doing a $command on a $entity $attr->{id}\n";
|
292 |
while( my($k,$v) = splice @{$tags}, 0, 2 )
|
293 |
{ print " $k: $v\n" }
|
294 |
if( defined $segs )
|
295 |
{ print " Segs: ", join(", ",@$segs),"\n"; }
|
296 |
}
|
297 |
|
298 |
=head1 AUTHOR
|
299 |
|
300 |
Martijn van Oosterhout <kleptog@svana.org>
|
301 |
based on OsmXML.pm written by:
|
302 |
Oliver White (oliver.white@blibbleblobble.co.uk)
|
303 |
|
304 |
=head1 COPYRIGHT
|
305 |
|
306 |
Copyright 2007, Martijn van Oosterhout
|
307 |
Copyright 2006, Oliver White
|
308 |
|
309 |
This program is free software; you can redistribute it and/or
|
310 |
modify it under the terms of the GNU General Public License
|
311 |
as published by the Free Software Foundation; either version 2
|
312 |
of the License, or (at your option) any later version.
|
313 |
|
314 |
This program is distributed in the hope that it will be useful,
|
315 |
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
316 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
317 |
GNU General Public License for more details.
|
318 |
|
319 |
You should have received a copy of the GNU General Public License
|
320 |
along with this program; if not, write to the Free Software
|
321 |
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
322 |
|
323 |
=cut
|