################################################################## ## OsmReader.pm - Library for reading OSM files ## ## By Martijn van Oosterhout ## ## ## ## Package that reads both OSM file format files. ## ## The user creates the parse with a callback and the ## ## loader will call the callback for each detected object. ## ## Licence: LGPL ## ################################################################## package Geo::OSM::OsmReader; use strict; use warnings; use Utils::File; use Utils::Math; use Utils::Debug; use XML::Parser; use Carp; use Geo::OSM::EntitiesV5; use constant STATE_INIT => 1; use constant STATE_EXPECT_ENTITY => 3; use constant STATE_EXPECT_BODY => 4; # With this initialiser, your process will get called with instantiated objects sub init { my $obj = bless{}, shift; my $proc = shift; my $prog = shift; if( ref $proc ne "CODE" ) { die "init Geo::OSM::OsmReader requires a sub as argument\n" } $obj->{newproc} = $proc; if( defined $prog ) { $obj->{progress} = $prog } return $obj; } sub _process { my($self, $entity, $attr, $tags, $members) = @_; my $ent; if( $entity eq "node" ) { $ent = new Geo::OSM::Node( $attr, $tags ); } if( $entity eq "relation" ) { $ent = new Geo::OSM::Relation( $attr, $tags, $members ); } if( $entity eq "way" ) { $ent = new Geo::OSM::Way( $attr, $tags, $members ); } croak "Unknown entity '$entity'" if not defined $ent; return $self->{newproc}->( $ent ); } sub load($) { my ($self, $file_name) = @_; $self->{state} = STATE_INIT; my $start_time = time(); my $P = new XML::Parser(Handlers => {Start => sub{ DoStart( $self, @_ )}, End => sub { DoEnd( $self, @_ )}}); my $fh = data_open($file_name); die "Cannot open OSM File $file_name\n" unless $fh; $self->{input_length} = -s $fh; $self->{count}=0; eval { $P->parse($fh); }; print "\n" if $DEBUG || $VERBOSE; if ( $VERBOSE) { printf "Read and parsed $file_name in %.0f sec\n",time()-$start_time; } if ( $@ ) { warn "$@Error while parsing\n $file_name\n"; return; } if (not $P) { warn "WARNING: Could not parse osm data\n"; return; } } sub parse($) { my ($self, $string) = @_; $self->{state} = STATE_INIT; my $start_time = time(); my $P = new XML::Parser(Handlers => {Start => sub{ DoStart( $self, @_ )}, End => sub { DoEnd( $self, @_ )}}); $self->{input_length} = length($string); $self->{count}=0; eval { $P->parse($string); }; print "\n" if $DEBUG || $VERBOSE; if ( $VERBOSE) { printf "Read and parsed string in %.0f sec\n",time()-$start_time; } if ( $@ ) { warn "$@Error while parsing\n [$string]\n"; return; } if (not $P) { warn "WARNING: Could not parse osm data\n"; return; } } # Function is called whenever an XML tag is started sub DoStart { #print @_,"\n"; my ($self, $Expat, $Name, %Attr) = @_; if( $self->{state} == STATE_INIT ) { if($Name eq "osm"){ $self->{state} = STATE_EXPECT_ENTITY; if( $Attr{version} ne "0.5" ) { die "OsmReaderV5 can only read 0.5 files, found '$Attr{version}'\n" } } else { die "Expected 'osm' tag, got '$Name'\n"; } } elsif( $self->{state} == STATE_EXPECT_ENTITY ) { # Pick up the origin attribute from the bound tag if( $Name eq "bound" ) { if( exists $Attr{origin} ) { $self->{origin} = $Attr{origin}; } return; } if($Name eq "node" or $Name eq "relation" or $Name eq "way"){ $self->{entity} = $Name; $self->{attr} = {%Attr}; $self->{tags} = []; $self->{members} = ($Name ne "node") ? [] : undef; $self->{state} = STATE_EXPECT_BODY; } else { die "Expected entity\n"; } } elsif( $self->{state} == STATE_EXPECT_BODY ) { if($Name eq "tag"){ push @{$self->{tags}}, $Attr{"k"}, $Attr{"v"}; } if($Name eq "nd"){ push @{$self->{members}}, $Attr{"ref"}; } if($Name eq "member"){ push @{$self->{members}}, new Geo::OSM::Relation::Member( \%Attr ); } } } # Function is called whenever an XML tag is ended sub DoEnd { my ($self, $Expat, $Name) = @_; if( $self->{state} == STATE_EXPECT_BODY ) { if( $Name eq $self->{entity} ) { $self->_process( $self->{entity}, $self->{attr}, $self->{tags}, $self->{members} ); $self->{count}++; if( $self->{progress} and ($self->{count}%11) == 1) { $self->{progress}->($self->{count}, $Expat->current_byte()/$self->{input_length} ); } $self->{state} = STATE_EXPECT_ENTITY; } return; } elsif( $self->{state} == STATE_EXPECT_ENTITY ) { return if $Name eq "bound"; if( $Name eq "osm" ) { $self->{state} = STATE_INIT; } else {die} return; } } 1; __END__ =head1 NAME OsmReaderV5 - Module for reading OpenStreetMap V5 XML data files =head1 SYNOPSIS my $OSM = new Geo::OSM::OsmReader(\&process); $OSM->load("Data/changes.osc"); sub process { my($OSM, $entity) = @_; print "Read $entity ".$entity->id."\n"; my $tags = $entity->tags; while( my($k,$v) = splice @{$tags}, 0, 2 ) { print " $k: $v\n" } if( $entity->type eq "way" ) { print " Nodes: ", join(", ",@{$entity->nodes}),"\n"; } } =head1 AUTHOR Martijn van Oosterhout based on OsmXML.pm written by: Oliver White (oliver.white@blibbleblobble.co.uk) =head1 COPYRIGHT Copyright 2007, Martijn van Oosterhout Copyright 2006, Oliver White This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. =cut