# -*- perl -*- # # $Id: Core.pm,v 1.63 2005/12/10 23:46:27 eserte Exp $ # # Copyright (c) 1995-2003 Slaven Rezic. All rights reserved. # This is free software; you can redistribute it and/or modify it under the # terms of the GNU General Public License, see the file COPYING. # # Mail: slaven@rezic.de # WWW: http://bbbike.sourceforge.net # package Strassen::Core; package Strassen; use strict; use BBBikeUtil; #require StrassenNetz; # AUTOLOAD: activate #require MultiStrassen; # AUTOLOAD: activate #require Strassen::Util; # AUTOLOAD: activate #require Strasse; # AUTOLOAD: activate #use AutoLoader 'AUTOLOAD'; use vars qw(@datadirs $OLD_AGREP $VERBOSE $VERSION $can_strassen_storable %directive_aliases ); use enum qw(NAME COORDS CAT); use constant LAST => CAT; $VERSION = sprintf("%d.%02d", q$Revision: 1.63 $ =~ /(\d+)\.(\d+)/); if (defined $ENV{BBBIKE_DATADIR}) { require Config; push @datadirs, split /$Config::Config{'path_sep'}/o, $ENV{BBBIKE_DATADIR}; } else { push @datadirs, ("$FindBin::RealBin/data", './data') if defined $FindBin::RealBin; foreach (@INC) { push @datadirs, "$_/data"; } # XXX push @datadirs, "http://www/~eserte/bbbike/root/data"; } $OLD_AGREP = 0 if !defined $OLD_AGREP; %directive_aliases = (attrs => "attributes"); #eval 'require Strassen::Storable; $can_strassen_storable = 1';warn $@ if $@; # static method to set the datadirs variable according to the used geography # object sub set_geography { my $geo = shift; @datadirs = $geo->datadir; } # XXX The Autoloader does not work for inherited methods... see # MultiStrassen.pm for a non-comprehensive list of problematic methods. use vars qw($AUTOLOAD); sub AUTOLOAD { warn "Loading Strassen::CoreHeavy for $AUTOLOAD ...\n" if $VERBOSE; require Strassen::CoreHeavy; if (defined &$AUTOLOAD) { goto &$AUTOLOAD; } else { die "Cannot find $AUTOLOAD in ". __PACKAGE__; } } # Arguments: # NoRead # PreserveLineInfo # PreserveComments # UseLocalDirectives # CustomPush (only for MapInfo) sub new { my($class, $filename, %args) = @_; if (defined $filename) { if ($filename =~ /\.(dbf|sbn|sbx|shp|shx)$/) { require Strassen::ESRI; return Strassen::ESRI->new($filename, %args); } elsif ($filename =~ /\.(mif|mid)$/i) { require Strassen::MapInfo; return Strassen::MapInfo->new($filename, %args); } elsif ($filename =~ /\.e00$/i) { require Strassen::E00; return Strassen::E00->new($filename, %args); } elsif ($filename =~ /\.(wpt|trk|rte)$/) { require Strassen::Gpsman; return Strassen::Gpsman->new($filename, %args); } elsif ($filename =~ /waypoint\.txt$/) { require Strassen::WaypointPlus; return Strassen::WaypointPlus->new($filename, %args); } elsif ($filename =~ /\.(mps|gpx|g7t)$/i) { if ($filename =~ /\.gpx$/ && eval { require Strassen::GPX; 1 }) { return Strassen::GPX->new($filename, %args); } else { require Strassen::FromRoute; return Strassen::FromRoute->new($filename, %args); } } } my(@filenames); if (defined $filename) { if (!file_name_is_absolute($filename)) { push @filenames, map { $_ . "/$filename" } @datadirs; } # relative filenames to end push @filenames, $filename; } my $self = { Data => [], Directives => [], GlobalDirectives => {}, }; bless $self, $class; if (@filenames) { TRY: { if ($filename eq '-') { $self->{File} = "-"; last TRY; } my $file; foreach $file (@filenames) { # if (!$args{NoStorable} and $can_strassen_storable and -f "$file.st" and -r _) { # my $obj = Strassen::Storable->new("$file.st"); # return $obj if $obj; # } if (-f $file and -r _) { $self->{File} = $file; if ($file =~ /\.gz$/) { $self->{IsGzipped} = 1; } last TRY; } my $gzfile = "$file.gz"; if (-f $gzfile and -r _) { $self->{File} = $gzfile; $self->{IsGzipped} = 1; last TRY; } } # XXX 2. versuch mit internet für minimale Funktionsfähigkeit # mit bbbike.ppl if (0) { foreach $file (@filenames) { eval q{ use lib "/home/e/eserte/src/perl/Hyper"; require Hyper; my $cachefile = Hyper::hypercopy($file); $self->{File} = $cachefile; }; last TRY if $self->{File}; } } # XXX end require Carp; Carp::confess("Can't open ", join(", ", @filenames)); } unless ($args{NoRead}) { $self->read_data(PreserveLineInfo => $args{PreserveLineInfo}, UseLocalDirectives => $args{UseLocalDirectives}, PreserveComments => $args{PreserveComments}, ); } } $self->{Pos} = 0; $self; } sub open_file { my($self, %args) = @_; my $file = $self->{File}; if ($self->{IsGzipped}) { die "Can't execute zcat $file" if !open(FILE, "gzip -dc $file |"); } else { die "Can't open $file" if !open(FILE, $file); } warn "Read Strassen file $file...\n" if ($VERBOSE && $VERBOSE > 1); $self->{Modtime} = (stat($file))[STAT_MODTIME]; binmode FILE; \*FILE; } sub read_data { my($self, %args) = @_; my $fh = $self->open_file(%args); $self->read_from_fh($fh, %args); } sub read_from_fh { my($self, $fh, %args) = @_; my @data; my @directives; my $read_only_global_directives = $args{ReadOnlyGlobalDirectives}; my $use_local_directives = $args{UseLocalDirectives}; my $has_tie_ixhash = 0; if ($use_local_directives) { $has_tie_ixhash = eval { require Tie::IxHash; 1 }; } use constant DIR_STAGE_LOCAL => 0; use constant DIR_STAGE_GLOBAL => 1; my $directives_stage = DIR_STAGE_LOCAL; my %global_directives; my %line_directive; if ($has_tie_ixhash) { tie %line_directive, "Tie::IxHash"; tie %global_directives, "Tie::IxHash"; } my @block_directives; my $preserve_line_info = $args{PreserveLineInfo} || 0; my $preserve_comments = $args{PreserveComments} || 0; while (<$fh>) { if (/^\#:\s*([^\s:]+):?\s*(.*)$/) { my($directive, $value_and_marker) = ($1, $2); $directive = $directive_aliases{$directive} if exists $directive_aliases{$directive}; my($value, $is_block_begin, $is_block_end); if ($value_and_marker =~ /^\^+\s*$/) { $is_block_end = 1; $value = ""; } else { $value_and_marker =~ /(.*?)(\s*vvv+\s*)?$/; if ($2) { $is_block_begin = 1; } $value = $1; } if ($. == 1) { $directives_stage = DIR_STAGE_GLOBAL; } elsif ($directives_stage eq DIR_STAGE_GLOBAL && $_ =~ /^\#:$/) { $directives_stage = DIR_STAGE_LOCAL; } if ($directives_stage eq DIR_STAGE_GLOBAL) { push @{ $global_directives{$directive} }, $value; } elsif ($use_local_directives) { if ($is_block_begin) { push @block_directives, [$directive => $value]; } elsif ($is_block_end) { pop @block_directives; } else { push @{ $line_directive{$directive} }, $value; } } next; } $directives_stage = DIR_STAGE_LOCAL if $directives_stage eq DIR_STAGE_GLOBAL; last if ($read_only_global_directives); if ($preserve_comments) { next if m{^\#:}; # directives already handled } else { next if m{^(\#|\s*$)}; } push @data, $_; if ($preserve_line_info) { $self->{LineInfo}[$#data] = $.; } if (keys %line_directive || @block_directives) { while(my($directive,$values) = each %line_directive) { if ($has_tie_ixhash && !$directives[$#data]) { tie %{ $directives[$#data] }, 'Tie::IxHash'; } push @{ $directives[$#data]{$directive} }, @$values; } for (@block_directives) { my($directive, $value) = @$_; if ($has_tie_ixhash && !$directives[$#data]) { tie %{ $directives[$#data] }, 'Tie::IxHash'; } push @{ $directives[$#data]{$directive} }, $value; } if (keys %line_directive) { %line_directive = (); } } } if (@block_directives) { die "The following block directives were not closed: `" . join(" ", map { "@$_" } @block_directives) . "'\n"; } if (keys %line_directive) { die "Stray line directive `@{[ keys %line_directive ]}' at end of file\n"; } warn "... done\n" if ($VERBOSE && $VERBOSE > 1); close $fh; $self->{Data} = \@data; $self->{Directives} = \@directives; $self->{GlobalDirectives} = \%global_directives; } # Return true if there is no data loaded. ### AutoLoad Sub sub has_data { $_[0]->{Data} && @{$_[0]->{Data}} } # new_from_data can't handle directives: ### AutoLoad Sub sub new_from_data { my($class, @data) = @_; $class->new_from_data_ref(\@data); } # new_from_data_ref can't handle directives: ### AutoLoad Sub sub new_from_data_ref { my($class, $data_ref) = @_; my $self = {}; $self->{Data} = $data_ref; $self->{Pos} = 0; bless $self, $class; } ### AutoLoad Sub sub new_from_data_string { my($class, $string, %args) = @_; my $self = { Pos => 0 }; bless $self, $class; my $fh; if ($] >= 5.008) { eval 'open($fh, "<", \$string)'; } else { require IO::String; # XXX add as prereq_pm for <5.008 $fh = IO::String->new($string); } $self->read_from_fh($fh, %args); $self; } # Erzeugt ein neues Strassen-Objekt mit Restriktionen # -restrictions => \@cats: do not copy records with these categories # -grep => \@cats: do only copy records with these categories (only if set) # -callback => sub { my($record) = shift; ... }: copy only if the callback # returns a true value for the given record ### AutoLoad Sub sub new_copy_restricted { my($class, $old_s, %args) = @_; my %restrictions; my %grep; my $callback; if ($args{-restrictions}) { %restrictions = map { ($_ => 1) } @{ $args{-restrictions} }; } if ($args{-grep}) { %grep = map { ($_ => 1) } @{ $args{-grep} }; } $callback = delete $args{-callback}; my $res = $class->new; $old_s->init; while(1) { my $ret = $old_s->next; last if !@{$ret->[COORDS]}; next if (keys %grep && !exists $grep{$ret->[CAT]}); next if exists $restrictions{$ret->[CAT]}; next if ($callback && !$callback->($ret)); $res->push($ret); } $res->{File} = $old_s->file; $res->{Id} = $old_s->id . "_restr_" . join("_", keys %restrictions); $res; } # Erzeugt aus dem Objekt eine Hash-Referenz mit erster Koordinate als Key # und dem Namen als Value. Ist nur für ein-Punkt-Daten geeignet. # init()/next() wird verwendet! ### AutoLoad Sub sub get_hashref { my($self) = @_; my $hash = {}; $self->init; while(1) { my $ret = $self->next; last if !@{$ret->[COORDS]}; $hash->{$ret->[COORDS][0]} = $ret->[NAME]; } $hash; } # Wie get_hashref, nur ist hier die Kategorie der Value. # init()/next() wird verwendet! ### AutoLoad Sub sub get_hashref_by_cat { my($self) = @_; my $hash = {}; $self->init; while(1) { my $ret = $self->next; last if !@{$ret->[COORDS]}; $hash->{$ret->[COORDS][0]} = $ret->[CAT]; } $hash; } # Erzeugt ein Hash Name => [Positions] im Data-Array. Optional kann ein # CODE ref angegeben werden, um den Hash-Key zu ändern. # init()/next() wird verwendet! ### AutoLoad Sub sub get_hashref_name_to_pos { my($self, $sub) = @_; my $hash = {}; $self->init; while(1) { my $ret = $self->next; last if !@{$ret->[COORDS]}; my $name = $sub ? $sub->($ret->[NAME]) : $ret->[NAME]; push @{$hash->{$name}}, $self->pos; } $hash; } # Ausgabe des Source-Files sub file { shift->{File} } sub dependent_files { my $self = shift; if ($self->{DependentFiles}) { @{ $self->{DependentFiles} }; } else { defined $self->file ? $self->file : (); } } # ID (für Caching) sub id { my $self = shift; if (defined $self->{Id}) { return $self->{Id}; } my @depfiles = $self->dependent_files; if (@depfiles) { require File::Basename; my $basedir = File::Basename::basename(File::Basename::dirname($depfiles[0])); $basedir = ($basedir eq "data" ? "" : $basedir . "_"); $basedir . join("_", map { File::Basename::basename($_) } @depfiles); } else { undef; } } ### AutoLoad Sub sub as_string { my($self, %args) = @_; my $s = ""; my $maybe_need_directive_separator = 1; if (!$args{IgnoreDirectives} && $self->{GlobalDirectives} && keys %{$self->{GlobalDirectives}}) { $s = ""; while(my($k,$v) = each %{ $self->{GlobalDirectives} }) { $s .= join("\n", map { "#: $k: $_" } @$v) . "\n"; } $s .= "#:\n"; # end global directives $maybe_need_directive_separator = 0; } if (!$args{IgnoreDirectives} && $self->{Directives}) { if ($maybe_need_directive_separator && $self->{Directives}[0]) { $s .= "#:\n"; } my %current_block_directives; for my $pos (0 .. $#{$self->{Data}}) { my $close_blocks = ""; if ($self->{Directives}[$pos]) { while(my($directive,$values) = each %{ $self->{Directives}[$pos] }) { for my $value (@$values) { my $continuing_to_next_line = 0; if ($pos < $#{$self->{Data}}) { if ($self->{Directives}[$pos+1] && exists $self->{Directives}[$pos+1]{$directive} && grep { $_ eq $value } @{ $self->{Directives}[$pos+1]{$directive} }) { $continuing_to_next_line = 1; } } if ($continuing_to_next_line && !$current_block_directives{$directive}{$value}) { $s .= "#: $directive: $value vvv\n"; $current_block_directives{$directive}{$value} = 1; } elsif ($continuing_to_next_line && $current_block_directives{$directive}{$value}) { # do nothing } elsif (!$continuing_to_next_line && $current_block_directives{$directive}{$value}) { $close_blocks .= "#: $directive: ^^^\n"; delete $current_block_directives{$directive}{$value}; } else { $s .= "#: $directive: $value\n"; } } } } $s .= $self->{Data}[$pos]; $s .= $close_blocks; } $s; } else { $s . join "", @{ $self->{Data} }; } } ### AutoLoad Sub sub write { my($self, $filename, %args) = @_; if (!defined $filename) { $filename = $self->file; } if (!defined $filename) { warn "No filename specified"; return 0; } if (open(COPY, ">$filename")) { binmode COPY; print COPY $self->as_string(%args); close COPY; 1; } else { warn "Can't write to $filename: $!" if $VERBOSE; 0; } } ### AutoLoad Sub sub append { my($self, $filename, %args) = @_; open(COPY, ">>$filename") or die "Can't append to $filename: $!"; binmode COPY; print COPY $self->as_string(%args); close COPY; } sub get { my($self, $pos) = @_; return [undef, [], undef] if $pos < 0; my $line = $self->{Data}->[$pos]; parse($line); } sub get_directive { my($self, $pos) = @_; $pos = $self->{Pos} if !defined $pos; return {} if !$self->{Directives}; $self->{Directives}[$pos] || {}; } sub get_directive_for_iterator { my($self, $iterator) = @_; my $pos = $self->{"Pos_Iterator_$iterator"}; $self->get_directive($pos); } # Returns a list of all elements in the streets database # Warning: this method resets the iterator! ### AutoLoad Sub sub get_all { my $self = shift; my @res; $self->init; while(1) { my $r = $self->next; return @res if !@{ $r->[COORDS] }; push @res, $r; } } # Für den angegebenen Namen wird die erste gefundene Zeile im selben Format # wie bei get(), next() und parse() zurückgegeben. # Achtung: da mit init() und next() gearbeitet wird, wird durch diese Methode # eine laufende Schleife aus dem Konzept gebracht! # If $rxcmp is true, then a regexp match is done. ### AutoLoad Sub sub get_by_name { my($self, $name, $rxcmp) = @_; $self->init; while(1) { my $ret = $self->next; return undef if !@{$ret->[COORDS]}; return $ret if ((!$rxcmp && $ret->[NAME] eq $name) || ( $rxcmp && $ret->[NAME] =~ /$name/)); } } # Like get_by_name, but return all matching streets in a list. sub get_all_by_name { my($self, $name, $rxcmp) = @_; my @res; $self->init; while(1) { my $ret = $self->next; last if !@{$ret->[COORDS]}; push @res, $ret if ((!$rxcmp && $ret->[NAME] eq $name) || ( $rxcmp && $ret->[NAME] =~ /$name/)); } @res; } # XXX Die zwei verschiedenen Aufrufarten für das Koordinatenargument in # set und push ist unbefriedigend. ### AutoLoad Sub sub set { my($self, $index, $arg) = @_; $self->{Data}[$index] = arr2line($arg); } sub set_current { # funktioniert in init/next-Schleifen my($self, $arg) = @_; $self->set($self->{Pos}, $arg); } # Arguments: [name, [xy1, xy2, ...], cat], # which is the same as the return value of next(). sub push { my($self, $arg) = @_; my $x = [$arg->[NAME], join(" ", @{$arg->[COORDS]}), $arg->[CAT]]; push @{$self->{Data}}, arr2line($x); } # Push with directives sub push_ext { my($self, $arg, $dir) = @_; if ($dir) { my $pos = @{$self->{Data}} || 0; $self->{Directives}[$pos] = $dir; } $self->push($arg); } sub delete_current { # funktioniert in init/next-Schleifen my($self) = @_; return if $self->{Pos} < 0; splice @{ $self->{Data} }, $self->{Pos}, 1; for my $member (qw(Directives LineInfo)) { if ($self->{$member}) { splice @{ $self->{$member} }, $self->{Pos}, 1; } } $self->{Pos}--; # XXX invalidate get_hashref_name_to_pos result # XXX invalidate all_crossings result } # wandelt eine Array-Referenz ["name", $Koordinaten, "cat"] in # einen String zum Abspeichern um # Achtung: das Koordinaten-Argument ist hier anders als beim Rückgabewert von # parse()! Siehe arr2line2(). # Tabs werden aus dem Namen entfernt # Achtung: ein "\n" wird angehängt ### AutoLoad Sub sub arr2line { my $arg = shift; (my $name = $arg->[NAME]) =~ s/\t/ /; "$name\t$arg->[CAT] $arg->[COORDS]\n" } # wie arr2line, aber ohne Newline # Tabs werden aus dem Namen entfernt ### AutoLoad Sub sub _arr2line { my $arg = shift; (my $name = $arg->[NAME]) =~ s/\t/ /; "$name\t$arg->[CAT] $arg->[COORDS]" } # Wie _arr2line, aber das COORDS-Argument ist eine Array-Referenz wie # beim Rückgabewert von parse(). # Tabs werden aus dem Namen entfernt. # Ein Newline fehlt hier und muss manuell angefügt werden, falls der Datensatz # in $self->{Data} geschrieben werden soll. ### AutoLoad Sub sub arr2line2 { my $arg = shift; (my $name = $arg->[NAME]) =~ s/\t/ /; "$name\t$arg->[CAT] " . join(" ", @{ $arg->[COORDS] }); } # This is a static method sub parse { # $_[0] is $line # my $_[0] = shift; return [undef, [], undef] if !$_[0]; my $tab_inx = index($_[0], "\t"); if ($tab_inx < 0) { warn "Probably tab character is missing (line <$_[0]>)\n" if $VERBOSE; [$_[0]]; } else { my @s = split /\s+/, substr($_[0], $tab_inx+1); my $category = shift @s; [substr($_[0], 0, $tab_inx), \@s, $category]; } } ### AutoLoad Sub sub get_obj { my($self, $pos) = @_; Strasse->new($self->get($pos)); } # initialisiert für next() und gibt *keinen* Wert zurück sub init { my $self = shift; $self->{Pos} = -1; } # Like init(), but use a private iterator sub init_for_iterator { my($self, $iterator) = @_; $self->{"Pos_Iterator_$iterator"} = -1; } # Setzt den Index auf den angegeben Wert (jedenfalls so, dass ein # anschließendes next() das richtige zurückgibt). sub set_index { $_[0]->{Pos} = $_[1] - 1; } sub set_last { $_[0]->{Pos} = scalar @{$_[0]->{Data}} - 1; } # initialisiert für next() und gibt den ersten Wert zurück ### AutoLoad Sub sub first { my $self = shift; $self->{Pos} = 0; $self->get(0); } # Return the next record and increment the iterator sub next { my $self = shift; $self->get(++($self->{Pos})); } # Return the next record without incrementing the iterator sub peek { my $self = shift; $self->get($self->{Pos}+1); } # Like next(), but use a private iterator sub next_for_iterator { my($self, $iterator) = @_; $self->get(++($self->{"Pos_Iterator_$iterator"})); } sub prev { my $self = shift; $self->get(--($self->{Pos})); } sub next_obj { my $self = shift; $self->get_obj(++($self->{Pos})); } #del? # # XXX wird das hier verwendet? Schönerer Ersatz für !@{$ret->[COORDS]} ? # sub at_end { # my $self = shift; # $self->{Pos} >= $#{$self->{Data}}; # } sub count { my $self = shift; scalar @{$self->{Data}}; } # gibt die aktuelle Position zurück sub pos { shift->{Pos} } sub line { my $self = shift; $self->{LineInfo}[$self->{Pos}]; } # Accessor for Data (but it's OK to use {Data}) sub data { shift->{Data} } # Gibt die Positionen (als Array) für einen bestimmten Namen zurück # Achtung: eine laufende init/next-Schleife wird hiermit zurückgesetzt! ### AutoLoad Sub sub pos_from_name { my($self, $name) = @_; my @res; my $found = 0; $self->init; while(1) { my $ret = $self->next; last if !@{$ret->[COORDS]}; if ($ret->[NAME] eq $name) { CORE::push(@res, $self->pos); $found++; } elsif ($found) { last; } } @res; } # for Object::Iterate *__init__ = \&init; sub __more__ { $_[0]->{Pos} < $#{$_[0]->{Data}} } *__next__ = \&next; # Statische Methode. # Wandelt die Indices aus dem Ergebnis von get() (2. Element) in # Koordinaten um (Format des Arguments: ["x1,y1", "x2,y2", ...]) # Gibt eine Referenz auf ein Array zurück: [[x1,y1], [x2,y2] ...] sub to_koord_slow { my($resref) = @_; my @res; foreach (@$resref) { if (/^(-?\d+),(-?\d+)$/) { CORE::push(@res, [$1, $2]); } elsif (/(-?\d+),(-?\d+)$/) { # ignore prefix XXX CORE::push(@res, [$1, $2]); } elsif ($_ eq '*') { CORE::push(@res, $_); } elsif (/(-?\d+(?:\.\d*)?),(-?\d+(?:\.\d*)?)$/) { # float numbers CORE::push(@res, [$1, $2]); } else { warn "Unrecognized reference in <@$resref>: <$_>"; return []; } } \@res; } # Statische Methode. # wie to_koord, nur für einen Punkt # XXX Koordinaten der Form prefix(x,y) bearbeiten sub to_koord1_slow { my($s) = @_; if ($s =~ /^(-?\d+),(-?\d+)$/) { [$1, $2]; } elsif ($s =~ /^((:[^:]*:)?([A-Za-z])?)?(-?\d+),(-?\d+)$/) { # Ausgabe: x, y, coordsystem, bahnhof [$4, $5, $3, $2]; } elsif (/(-?\d+(?:\.\d*)?),(-?\d+(?:\.\d*)?)$/) { # float numbers [$1, $2]; } else { warn "Unrecognized string: $s..."; [undef, undef]; # XXX } } *to_koord = \&to_koord_slow; *to_koord1 = \&to_koord1_slow; # Return crossings as an array or hash reference. # Argumente: # RetType: hash, hashpos, array (default) oder arraypos # Bei den ...pos-Varianten wird statt des Straßennamens die # Position im Strassen-Objekt zurückgegeben. # UseCache: gibt an, ob vom Cache gelesen und ein Cache geschrieben werden # soll # Kurvenpunkte: bei TRUE werden auch die Kurvenpunkte zurückgegeben # AllPoints: synonym for KurvenPunkte # # See below for the output forms. ### AutoLoad Sub sub all_crossings { my($self, %args) = @_; my $rettype = $args{RetType}; my $use_cache = $args{UseCache}; my $all_points = $args{AllPoints} || $args{Kurvenpunkte}; my $min_strassen = ($all_points ? 1 : 2); if (!defined $rettype) { $rettype = 'array' } if ($rettype !~ /^(array|hash)(pos)?$/) { die "Wrong RetType $rettype"; } my $basename = $self->id; my $cachefile = "all_crossings_${basename}_$rettype"; if ($all_points) { $cachefile .= "_kurvenp"; } if ($self->{Inaccessible}) { $cachefile .= "_inacc"; } if ($use_cache && $rettype =~ /^hash/) { require Strassen::Util; my $hashref = Strassen::Util::get_from_cache($cachefile, [$self->dependent_files]); if (defined $hashref) { warn "Using cache for $cachefile\n" if $VERBOSE; return $hashref; } } my $inacc; if ($self->{Inaccessible}) { require Strassen::Kreuzungen; my $cr = Kreuzungen->new_from_strassen (WantPos => 1, Strassen => $self->{Inaccessible}, ); $inacc = $cr->{Hash}; } # RetType ...pos: Positionen statt Straßennamen speichern my $store_pos = ($rettype =~ /pos$/); my %crossings; my %crossing_name; $self->init(); while(1) { my $ret = $self->next(); my @kreuzungen = @{$ret->[COORDS]}; last if @kreuzungen == 0; my $store = ($store_pos ? $self->pos : $ret->[NAME]); for my $xy (@kreuzungen) { next if $inacc && exists $inacc->{$xy}; $crossings{$xy}++; TEST: { for my $test (@{$crossing_name{$xy}}) { last TEST if ($test eq $store); } CORE::push(@{$crossing_name{$xy}}, $store); } } } if ($rettype =~ /^hash/) { # Rückgabewert: "x,y" => [name1,name2 ...] my @to_del; while(my($k, $v) = each %crossings) { if ($v < $min_strassen) { CORE::push(@to_del, $k); } else { $crossings{$k} = $crossing_name{$k}; } } foreach (@to_del) { delete $crossings{$_}; } if ($use_cache) { require Strassen::Util; if (Strassen::Util::write_cache(\%crossings, $cachefile)) { warn "Wrote cache ($cachefile)\n" if $VERBOSE; } } \%crossings; } else { # Rückgabewert: [x, y, "name1/name2/..."] my @crossings; while(my($k, $v) = each %crossings) { if ($v >= $min_strassen) { my($x, $y) = split(/,/, $k); CORE::push(@crossings, [$x, $y, join("/", @{$crossing_name{$k}})]); } } \@crossings; } } ### AutoLoad Sub sub strip_bezirk { require Strassen::Strasse; Strasse::strip_bezirk(@_) } # Für Orte: trennt den Namen vom Zusatz (z.B. ("Frankfurt", "Oder") ### AutoLoad Sub sub split_ort { split /\|/, $_[0], 2; } # Arguments (hash-style): # UseCache: use cache # Exact: use "exact" algorithm # GridHeight, GridWidth: grid extents (by default 1000) # With -rebuild => 1 the grid will be build again. # Uses the private Strassen::Core iterator "make_grid". ### AutoLoad Sub sub make_grid { my($self, %args) = @_; if ($args{-rebuild} && $self->{Grid}) { %args = (GridWidth => $self->{GridWidth}, GridHeight => $self->{GridHeight}, Exact => $self->{GridIsExact}, UseCache => $self->{GridUseCache}, ); } my $use_cache = $args{UseCache}; my $use_exact = $args{Exact}||0; $self->{GridWidth} = (defined $args{GridWidth} ? $args{GridWidth} : 1000); $self->{GridHeight} = (defined $args{GridHeight} ? $args{GridHeight} : $self->{GridWidth}); my $cachefile = "grid" . ($use_exact ? "x" : "") . "_" . $self->id . "_" . $self->{GridWidth}."x".$self->{GridHeight}; if ($use_cache) { require Strassen::Util; my $hashref = Strassen::Util::get_from_cache($cachefile, [$self->dependent_files]); if (defined $hashref) { warn "Using grid cache for $cachefile\n" if $VERBOSE; $self->{Grid} = $hashref; return; } } $self->{Grid} = {}; $self->{GridIsExact} = $use_exact; $self->{GridUseCache} = $use_cache; my $grid_build = ($use_exact ? $self->_make_grid_exact : $self->_make_grid_fast); while(my($g, $v) = each %$grid_build) { $self->{Grid}{$g} = [keys %$v]; } if ($use_cache) { require Strassen::Util; if (Strassen::Util::write_cache($self->{Grid}, $cachefile)) { warn "Wrote cache ($cachefile)\n" if $VERBOSE; } } } ### AutoLoad Sub sub _make_grid_fast { my $self = shift; my %grid_build; $self->init_for_iterator("make_grid"); my $strpos = 0; while(1) { my $r = $self->next_for_iterator("make_grid"); last if !@{$r->[COORDS]}; foreach my $c (@{$r->[COORDS]}) { $grid_build{join(",",$self->grid(split(/,/, $c)))}->{$strpos}++; } $strpos++; } \%grid_build; } ### AutoLoad Sub sub _make_grid_exact { my $self = shift; if (!eval { require VectorUtil; 1 }) { warn "Can't load VectorUtil.pm, fallback to _make_grid_fast"; return $self->_make_grid_fast; } eval { require VectorUtil::InlineDist; }; if ($@ && $VERBOSE) { warn $@ } my %grid_build; $self->init_for_iterator("make_grid"); my $strpos = 0; while(1) { my $r = $self->next_for_iterator("make_grid"); last if !@{$r->[COORDS]}; if (@{ $r->[COORDS] } == 1) { $grid_build{join(",",$self->grid(split(/,/, $r->[COORDS][0])))}->{$strpos}++; } else { for my $i (0 .. $#{$r->[COORDS]}-1) { my($x1, $y1) = split(',', $r->[COORDS][$i]); my($x2, $y2) = split(',', $r->[COORDS][$i+1]); my($from_grid_x, $from_grid_y) = $self->grid($x1,$y1); my($to_grid_x, $to_grid_y) = $self->grid($x2,$y2); ($from_grid_x, $to_grid_x) = ($to_grid_x, $from_grid_x) if $to_grid_x < $from_grid_x; ($from_grid_y, $to_grid_y) = ($to_grid_y, $from_grid_y) if $to_grid_y < $from_grid_y; for my $grid_x ($from_grid_x .. $to_grid_x) { for my $grid_y ($from_grid_y .. $to_grid_y) { my $grid_xy = join(",", $grid_x, $grid_y); next if $grid_build{$grid_xy}->{$strpos}; $grid_build{$grid_xy}->{$strpos}++ if VectorUtil::vector_in_grid($x1,$y1,$x2,$y2, $grid_x*$self->{GridWidth}, $grid_y*$self->{GridHeight}, ($grid_x+1)*$self->{GridWidth}, ($grid_y+1)*$self->{GridHeight}); } } } } $strpos++; } \%grid_build; } ### AutoLoad Sub sub grid { my($self, $x, $y) = @_; my($gx,$gy) = (int($x/$self->{GridWidth}), int($y/$self->{GridHeight})); $gx-- if $x < 0; $gy-- if $y < 0; ($gx,$gy); } # Gibt eine Liste mit den neuen Gitterquadranten für die # Koordinateneckpunte aus. Mit dem Argument KnownGrids können bereits # bekannte Quadranten aus der Liste ausgeschlossen werden. ### AutoLoad Sub sub get_new_grids { my($self, $x1, $y1, $x2, $y2, %args) = @_; if ($x2 < $x1) { ($x2, $x1) = ($x1, $x2) } if ($y2 < $y1) { ($y2, $y1) = ($y1, $y2) } my $known_grids = {}; if (exists $args{'KnownGrids'} and ref $args{'KnownGrids'} eq 'HASH') { $known_grids = $args{'KnownGrids'}; } my @new_grids; my($x,$ybeg) = $self->grid($x1,$y1); my($xend,$yend) = $self->grid($x2,$y2); while ($x <= $xend) { my $y = $ybeg; while ($y <= $yend) { my $xy = "$x,$y"; if (!$known_grids->{$xy}) { CORE::push(@new_grids, $xy); $known_grids->{$xy}++; } $y++; } $x++; } @new_grids; } # Checks if the coordinate is present in the Strassen data, so there is no # need to create a $net. The coord is in the form "$x,$y". # Warning: Initializes the iterator! sub reachable { my($self, $coord) = @_; $self->init; while(1) { my $ret = $self->next; return 0 if !@{ $ret->[Strassen::COORDS] }; foreach my $c (@{ $ret->[Strassen::COORDS] }) { return 1 if ($c eq $coord); } } } # Get the nearest point at a street for the given point. # Further arguments: # FullReturn: return all information instead only the returned point # AllReturn: return an array reference with the data for all nearest points, # not just the first one # The returned object contains: # StreetObj: the street object (result of Strassen::get) # N: the index of the street object in Strassen->{Data} # CoordIndex: the index of Coord in the Strassen::COORDS array # Dist: the distance from the given point to Coord # Coord: the nearest coordinate to the given point # Uses the private iterator "make_grid" sub nearest_point { my($s, $xy, %args) = @_; my($x,$y) = split /,/, $xy; require Strassen::Util; my $mindist = Strassen::Util::infinity(); my @line; if (!defined &VectorUtil::distance_point_line) { require VectorUtil; eval { require VectorUtil::InlineDist; }; if ($@ && $VERBOSE) { warn $@ } } $s->make_grid(UseCache => 1, Exact => 1) unless $s->{Grid}; my($grx,$gry) = $s->grid($x,$y); my %seen; for my $xx ($grx-1 .. $grx+1) { for my $yy ($gry-1 .. $gry+1) { # prevent autovivify (bad for CDB_File) next unless (exists $s->{Grid}{"$xx,$yy"}); foreach my $n (@{ $s->{Grid}{"$xx,$yy"} }) { next if $seen{$n}; $seen{$n}++; my $r = $s->get($n); my @p; foreach (@{ $r->[Strassen::COORDS] }) { CORE::push(@p, split /,/, $_); } if (@p == 2) { # point my $new_mindist = sqrt(sqr($x-$p[0])+sqr($y-$p[1])); if ($mindist >= $new_mindist) { my $line = {StreetObj => $r, N => $n, CoordIndex => 0, Dist => $new_mindist, Coords => \@p, }; if ($mindist == $new_mindist) { CORE::push(@line, $line); } else { @line = $line; } $mindist = $new_mindist; } } else { # line for(my $i=0; $i<$#p-1; $i+=2) { my $new_mindist = VectorUtil::distance_point_line($x,$y,@p[$i..$i+3]); if ($mindist >= $new_mindist) { my $line = {StreetObj => $r, N => $n, CoordIndex => $i/2, Dist => $new_mindist, Coords => [@p[$i..$i+3]], }; if ($mindist == $new_mindist) { CORE::push(@line, $line); } else { @line = $line; } $mindist = $new_mindist; } } } } } } if (@line) { for my $line (@line) { my($s0x,$s0y,$s1x,$s1y) = @{$line->{Coords}}; if (!defined $s1x) { # point $line->{Coord} = "$s0x,$s0y"; } else { my $dist0 = sqrt(sqr($s0x-$x)+sqr($s0y-$y)); my $dist1 = sqrt(sqr($s1x-$x)+sqr($s1y-$y)); if ($dist0 < $dist1) { $line->{Coord} = "$s0x,$s0y"; } else { $line->{Coord} = "$s1x,$s1y"; } } } if ($args{FullReturn}) { $args{AllReturn} ? \@line : $line[0]; } else { $args{AllReturn} ? [map { $_->{Coord} } @line] : $line[0]->{Coord}; } } else { undef; } } # See also get_anti_conversion sub get_conversion { my($self, %args) = @_; my $convsub; my $frommap = $self->{GlobalDirectives}{map} || $args{Map}; if ($frommap) { require Karte; Karte::preload(":all"); # Can't preload specific maps, because $map is a token, not a map module name my $tomap = $args{-tomap} || "standard"; return if $frommap eq $tomap; # no conversion needed if ($tomap ne "standard") { $convsub = sub { join ",", $Karte::map{$frommap}->map2map($Karte::map{$tomap}, split /,/, $_[0]); }; } else { $convsub = sub { join ",", $Karte::map{$frommap}->map2standard(split /,/, $_[0]); }; } } $convsub; } # set all $VERBOSE vars in this file sub set_verbose { my $verbose = shift; $StrassenNetz::VERBOSE = $verbose; $Strassen::VERBOSE = $verbose; $Strassen::Util::VERBOSE = $verbose; $Kreuzungen::VERBOSE = $verbose; $StrassenNetz::CNetFile::VERBOSE = $verbose; } sub get_global_directives { my $self = shift; if (ref $self && UNIVERSAL::isa($self, "Strassen")) { $self->{GlobalDirectives}; } else { my $file = shift; my $tmp_s = $self->new($file, NoRead => 1); $tmp_s->read_data(ReadOnlyGlobalDirectives => 1); $tmp_s->{GlobalDirectives}; } } sub DESTROY { } if (0) { # peacify -w $Kreuzungen::VERBOSE = $Kreuzungen::VERBOSE; $StrassenNetz::VERBOSE = $StrassenNetz::VERBOSE; $StrassenNetz::CNetFile::VERBOSE = $StrassenNetz::CNetFile::VERBOSE; $Strassen::Util::VERBOSE = $Strassen::Util::VERBOSE; *to_koord = *to_koord; *to_koord1 = *to_koord1; } 1; __END__ =head1 NAME Strassen::Core - the main Strassen object for bbd data =head1 SYNOPSIS use Strassen::Core; $s = Strassen->new($bbdfile); $s->init; while(1) { my $ret = $s->next; last if !@{ $ret->[Strassen::COORDS] }; print "Name: $ret->[Strassen::NAME]\n"; print "Category: $ret->[Strassen::CAT]\n"; print "Coordinates: " . join(" ", @{ $ret->[Strassen::COORDS] }) . "\n"; } =head1 DESCRIPTION See SYNOPSIS. =head1 SEE ALSO L, L.