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