# -*- perl -*- # # $Id: CoreHeavy.pm,v 1.29 2005/10/24 22:32:48 eserte Exp $ # # Copyright (c) 1995-2001 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::CoreHeavy; package Strassen; use strict; # Gibt die Positionsnummern aller Straßen aus $str_ref als Liste aus. # $str_ref ist eine Liste von [Straßenname, Bezirk]-Elementen # Falls eine Straße durch mehrere Bezirke führt, wird nur _eine_ Position # zurückgegeben. ### AutoLoad Sub sub union { my($self, $str_ref, %args) = @_; my $uniq = !$args{Nouniq}; my %str; foreach (@$str_ref) { $str{$_->[0]}->{$_->[1]}++; } my %res; $self->init; my $last; while(1) { my $ret = $self->next; last if !@{$ret->[COORDS]}; my $name = $ret->[NAME]; if ($uniq) { if (defined $last && $last eq $name) { next; } else { $last = $name; } } my @bez; if ($name =~ /^(.*)\s+\((.*)\)$/) { $name = $1; @bez = split(/,\s*/, $2); } if (exists $str{$name}) { if (@bez) { foreach my $bez (@bez) { if (exists $str{$name}->{$bez}) { $res{$self->pos}++; last; } } } else { $res{$self->pos}++; } } } keys %res; } # Create a new Strassen object from $self and remove points specified # in Strassen object $to_remove. sub new_with_removed_points { my($self, $to_remove, %args) = @_; my $new_s = Strassen->new; require Strassen::Kreuzungen; my $kr = Kreuzungen->new_from_strassen(Strassen => $to_remove); my $h = $kr->{Hash}; $self->init; while(1) { my $r = $self->next; last if !@{ $r->[COORDS] }; my @newcoords = ([]); for my $p (@{ $r->[COORDS] }) { if (!exists $h->{$p}) { CORE::push @{$newcoords[-1]}, $p; } else { CORE::push @newcoords, [] if @{$newcoords[-1]} != 0; } } pop @newcoords if @{$newcoords[-1]} == 0; for my $new_c (@newcoords) { $new_s->push([$r->[NAME], $new_c, $r->[CAT]]); } } #$new_s->{Id} = $self->id . "_removed_" . $to_remove->id; $new_s->{DependentFiles} = [$self->dependent_files, $to_remove->dependent_files]; $new_s; } ### AutoLoad Sub sub agrep { my($self, $pattern, %arg) = @_; agrep_file($pattern, $self->{File}, %arg); } # XXX make gzip-aware # XXX does not work for MultiStrassen # %arg: # NoDot: keine Ausgabe von "...", wenn zu viele Matches existieren # ErrorDef: Angabe der Reihenfolge (match begin, match errors) # Agrep: maximale Anzahl von erlaubten Fehlern # Return value: Array with matched street names ### AutoLoad Sub sub agrep_file { my($pattern, $file, %arg) = @_; my @paths; my @files; if (ref $file eq 'ARRAY') { @files = @$file; } else { CORE::push(@files, $file); } foreach my $file (@files) { my $path; if (-r $file) { $path = $file; } else { foreach (@datadirs) { if (-r "$_/$file") { $path = "$_/$file"; last; } } } if (!defined $path) { warn "File $file not found in @datadirs.\n"; return undef; } CORE::push(@paths, $path); } my $grep_type; my @data; if (!$OLD_AGREP && is_in_path('agrep')) { $grep_type = 'agrep'; $pattern =~ s/(.)/\\$1/g; } else { foreach my $path (@paths) { open(F, $path) or die "Can't open $path: $!"; my @file_data; chomp(@file_data = ); CORE::push(@data, @file_data); close F; } eval { local $SIG{'__DIE__'}; require String::Approx; String::Approx->VERSION(2.7); }; if (!$@) { $grep_type = 'approx'; } else { $grep_type = 'perl'; } } my @def; if ($arg{ErrorDef}) { @def = @{$arg{ErrorDef}}; } else { @def = ([1, 0], [1, 1], [1, 2], [0, 0], [0, 1], [0, 2], [0, 3], ); } for my $def (@def) { my($begin, $err, @extra) = @$def; next if (exists $arg{Agrep} && $err > $arg{Agrep}); my @this_res; my $grep_pattern = $pattern; if (grep($_ eq 'strasse', @extra)) { next if ($grep_pattern !~ s/(s)traße$/$1tr./i); } if ($grep_type eq 'agrep') { my @args = '-i'; $grep_pattern = ($begin ? "^$grep_pattern" : $grep_pattern); if ($err > 0) { CORE::push(@args, "-$err") } open(AGREP, "-|") or exec 'agrep', @args, $grep_pattern, @paths or die "Can't exec program: $!"; chomp(@this_res = ); close AGREP; } elsif ($grep_type eq 'approx' && $err) { next if $begin || $err > 2; # Bug bei $err == 3 $grep_pattern =~ s/[()]/./g; # String::Approx-Bug? @this_res = String::Approx::amatch ($grep_pattern, ['i', $err], @data); } else { # weder agrep noch String::Approx $grep_pattern = ($begin ? "^$grep_pattern" : $grep_pattern); if ($err == 0) { @this_res = grep(/\Q$grep_pattern\E/i, @data); } elsif ($err == 1) { # metacharacter erlauben @this_res = grep(/$grep_pattern/i, @data); } else { next; } } if (@this_res == 1) { return parse($this_res[0])->[NAME]; } elsif (@this_res) { my(@res1, @res2, @res3); my $i = 0; my $last_name; foreach (@this_res) { $i++; my $name = parse($_)->[NAME]; if (defined $last_name && $last_name eq $name) { next; } else { $last_name = $name; } if ($name eq $pattern) { return $name; } elsif ($name =~ /^\Q$pattern\E/i) { CORE::push(@res1, $name); } elsif ($i < 20) { CORE::push(@res2, $name); } elsif ($i == 20) { CORE::push(@res3, "...") unless $arg{NoDot}; } } @res1 = sort @res1; @res2 = sort @res2; return @res1, @res2, @res3; } } (); } # Sucht Straße anhand des Bezirkes. # $bezirk may be in the form "citypart1, citypart2, ..." ### AutoLoad Sub sub choose_street { my($str, $strasse, $bezirk, %args) = @_; my @bezirk = split /\s*,\s*/, $bezirk; my @pos; $str->init; while(1) { my $ret = $str->next; last if !@{$ret->[COORDS]}; my $check_strasse = $ret->[NAME]; if (substr($check_strasse, 0, length($strasse)) eq $strasse) { # if ($check_strasse =~ /^$strasse/) { my %bez; if ($check_strasse =~ /(.*)\s+\((.*)\)/) { $check_strasse = $1; foreach (split(/\s*,\s*/, $2)) { $bez{$_}++; } for my $bezirk (@bezirk) { if (exists $bez{$bezirk}) { if (wantarray) { CORE::push(@pos, $str->pos); } else { return $str->pos; } last; } } } elsif ($check_strasse eq $strasse) { if (wantarray) { CORE::push(@pos, $str->pos); } else { return $str->pos; } } } } if (wantarray) { @pos; } else { undef; } } sub copy_orig { my $self = shift; require Strassen::Util; if (! -d $Strassen::Util::tmpdir) { warn "$Strassen::Util::tmpdir does not exist" if $VERBOSE; return; } my $origdir = $self->get_diff_orig_dir; return if !$origdir; my @file = $self->file; if (!@file) { warn "File not defined" if $VERBOSE; return; } foreach (@file) { if (!-f $_) { warn "<$_> does not exist" if $VERBOSE; return; } } my $dest = $self->get_diff_file_name; if ($self->write($dest, IgnoreDirectives => 1)) { $self->{OrigFile} = $dest; 1; } else { delete $self->{OrigFile}; 0; } } sub get_diff_orig_dir { # ignore $self my $origdir = "$Strassen::Util::tmpdir/orig"; if (! -d $origdir) { mkdir $origdir, 0700; if (! -d $origdir) { warn "Can't create $origdir: $!" if $VERBOSE; return; } } $origdir; } sub get_diff_file_name { my($self) = @_; my @file = $self->file; my $origdir = get_diff_orig_dir; require File::Basename; my $dest = "$origdir/" . join("_", map { File::Basename::basename($_) } @file); $dest; } # Erzeugt die Differenz aus dem aktuellen Strassen-Objekt und der # letzten Version, die (evtl.) in $origdir abgelegt ist. # Rückgabe: (Strassen-Objekt mit neuen Straßen, zu löschenden Indices) # Argumente: -clonefile => 1: das File-Argument wird in das neue Objekt # kopiert ### AutoLoad Sub sub diff_orig { my($self, %args) = @_; require File::Basename; require Strassen::Util; my $origdir = $self->get_diff_orig_dir; my $first_file = $self->get_diff_file_name; if (!defined $self->{OrigFile}) { $self->{OrigFile} = "$origdir/" . File::Basename::basename($first_file); } if (! -f $self->{OrigFile}) { warn "<$self->{OrigFile}> does not exist" if $VERBOSE; delete $self->{OrigFile}; return; } if (!is_in_path("diff")) { warn "diff not found in path" if $VERBOSE; return; } my $dest = "$origdir/" . File::Basename::basename($first_file) . ".new"; return unless $self->write($dest, IgnoreDirectives => 1); my $old_line = 1; my $new_line = 1; my(@del, @add, %index_mapping); my $diff_cmd = "diff -u $self->{OrigFile} $dest |"; #warn $diff_cmd; open(DIFF, $diff_cmd); scalar ; scalar ; # overread header while() { chomp; if (/^\@\@\s*-(\d+).*\+(\d+)/) { $old_line = $1; $new_line = $2; } elsif (/^\+(.*)/) { CORE::push(@add, "$1\n"); $index_mapping{$#add} = $new_line-1; $new_line++; } elsif (/^-/) { CORE::push(@del, $old_line-1); # warum -1? $old_line++; } elsif (!/^[ \\]/) { warn "Unknown diff line: $_"; } else { $old_line++; $new_line++; } } close DIFF; unlink $dest; my $new_s = new_from_data Strassen @add; if ($args{-clonefile}) { $new_s->{File} = $self->{File}; } ($new_s, \@del, \%index_mapping); } # Create array reference from Data property: # [[$name, $category, ["$x1,$y1", "$x2,$y2" ...]], # [$name2, ...] # ] # Warning: this method resets any init/next loop! ### AutoLoad Sub sub as_array { my $self = shift; my $ret = []; $self->init; while(1) { my $r = $self->next; last if !@{$r->[COORDS]}; my $new_item = [$r->[NAME], $r->[CAT], $r->[COORDS]]; CORE::push(@$ret, $new_item); } $ret; } # Create a reverse hash pointing from a point to a list of streets # containing this point: # { "$x1,$y1" => [$streetname1, $streetname2 ...], ... } # Warning: this method resets any init/next loop! ### AutoLoad Sub sub as_reverse_hash { my $self = shift; my $rev_hash = {}; $self->init; while(1) { my $r = $self->next; last if !@{$r->[COORDS]}; foreach my $c (@{$r->[COORDS]}) { if (exists $rev_hash->{$c}) { CORE::push(@{ $rev_hash->{$c} }, $r->[NAME]); } else { $rev_hash->{$c} = [$r->[NAME]]; } } } $rev_hash; } # Given a Strassen file and a position, return the linenumber (starting # at 1). This function will skip all comment lines. ### AutoLoad Sub sub get_linenumber { my($strfile, $pos) = @_; my $orig_pos = $pos; my $linenumber = 0; open(STR, $strfile) or die "Can't open $strfile: $!"; while() { $linenumber++; next if /^( \# | \s*$ )/x; if ($pos == 0) { close STR; return $linenumber; } $pos--; } close STR; warn "Can't find position $orig_pos in file $strfile"; undef; } # Resets iterator ### AutoLoad Sub sub filter_region { my($s, $type, $x1,$y1, $x2,$y2) = @_; my $new_s = Strassen->new; $s->init; while(1) { my $r = $s->next; last if !@{ $r->[COORDS] }; my $ret; if ($type eq 'enclosed') { # XXX works only for one point my($x,$y) = split /,/, $r->[COORDS][0]; $ret = ($x1 <= $x && $x2 >= $x && $y1 <= $y && $y2 >= $y); } else { die "XXX type $type NYI"; } if ($ret) { $new_s->push($r); } } $new_s; } # Resets iterator # Arguments: -date (optional, default is today) # -negpos (optional, default is 0=negative, matches are deleted) ### AutoLoad Sub sub filter_date { my($s, %args) = @_; my $date = $args{-date}; if (!defined $date) { my @l = localtime; $date = sprintf "%04d-%02d-%02d", $l[5]+1900, $l[4]+1, $l[3]; } my $neg_pos = $args{-negpos} || 0; my $new_s = Strassen->new; $s->init; while(1) { my $r = $s->next; last if !@{ $r->[COORDS] }; my $hit; if ($r->[NAME] =~ /(\d{4}-\d{2}-\d{2})\s*(?:-|bis)\s*(\d{4}-\d{2}-\d{2})/ && ($date lt $1 || $date gt $2)) { if ($neg_pos == 0) { next; } else { $hit = 1; } } elsif ($r->[NAME] =~ /(?:-|bis)\s*(\d{4}-\d{2}-\d{2})/ && $date le $1) { if ($neg_pos == 0) { next; } else { $hit = 1; } } elsif ($r->[NAME] =~ /(\d{4}-\d{2}-\d{2})\s*(?:-|bis)/ && $date ge $1) { if ($neg_pos == 0) { next; } else { $hit = 1; } } if ($neg_pos == 0 || $hit) { $new_s->push($r); } } $new_s; } # XXX german/multilingual labels? # use as: $mw->getOpenFile(-filetypes => [Strassen->filetypes]) sub filetypes { (['bbd Files' => '.bbd'], ['Compressed bbd Files' => '.bbd.gz'], ['All Files' => '*']); } # Create a hash reference "x1,y1_x2,y2" => [position,...] in data array. # Optional $restrict should hold a callback returning 0 if the record # should be ignored, 1 for normal processing and 2 for using both # directions. # Warning: this method resets any init/next loop! sub make_coord_to_pos { my($s, $restrict) = @_; my $hash = {}; $s->init; while(1) { my $r = $s->next; last if !@{$r->[COORDS]}; my $restrict = $restrict->($r); next if !$restrict; for my $i (1 .. $#{$r->[COORDS]}) { CORE::push @{$hash->{$r->[COORDS]->[$i-1]."_".$r->[COORDS]->[$i]}}, $s->{Pos}; if ($restrict == 2) { CORE::push @{$hash->{$r->[COORDS]->[$i]."_".$r->[COORDS]->[$i-1]}}, $s->{Pos}; } } } $hash; } # Read/write bounding box file # Ack: resets the iterator if writing! ### AutoLoad Sub sub bboxes { my($self) = @_; return $self->{BBoxes} if $self->{BBoxes}; my @bboxes; $self->init; while(1) { my $r = $self->next; last if !@{ $r->[Strassen::COORDS] }; my @p; foreach (@{ $r->[Strassen::COORDS] }) { CORE::push(@p, split /,/, $_); } my(@bbox) = ($p[0], $p[1], $p[0], $p[1]); for(my $i=2; $i<$#p-1; $i+=2) { $bbox[0] = $p[$i] if ($p[$i] < $bbox[0]); $bbox[2] = $p[$i] if ($p[$i] > $bbox[2]); $bbox[1] = $p[$i+1] if ($p[$i+1] < $bbox[1]); $bbox[3] = $p[$i+1] if ($p[$i+1] > $bbox[3]); } CORE::push @bboxes, \@bbox; } $self->{BBoxes} = \@bboxes; \@bboxes; } # Return the bounding box of the file # Ack: resets the iterator sub bbox { my($self) = @_; $self->init; my($x1,$y1,$x2,$y2); while(1) { my $r = $self->next; last if !@{ $r->[Strassen::COORDS] }; for (@{ $r->[Strassen::COORDS] }) { my($x,$y) = split /,/; $x1 = $x if !defined $x1 || $x1 > $x; $x2 = $x if !defined $x2 || $x2 < $x; $y1 = $y if !defined $y1 || $y1 > $y; $y2 = $y if !defined $y2 || $y2 < $y; } } ($x1,$y1,$x2,$y2); } # $catref is either a hash reference of category => level mapping or a # an array reference of categories. Lower categories should be first. sub sort_by_cat { my($self, $catref, %args) = @_; $catref = $self->default_cat_stack_mapping if !$catref; my %catval; if (ref $catref eq 'HASH') { %catval = %$catref; } else { my $i = 0; $catval{$_} = $i++ foreach (@$catref); } my %ignore; %ignore = map { ($_,1) } @{ $args{-ignore} } if $args{-ignore}; @{ $self->{Data} } = map { $_->[1] } sort { if (exists $ignore{$a->[2][CAT]} || exists $ignore{$b->[2][CAT]}) { 0; } else { $a->[0] <=> $b->[0]; } } map { my $l = parse($_); [exists $catval{$l->[CAT]} ? $catval{$l->[CAT]} : 9999, $_, $l ] } @{ $self->{Data} }; } sub sort_records_by_cat { my($self, $records, $catref, %args) = @_; $catref = $self->default_cat_stack_mapping if !$catref; return map { $_->[1] } sort { $a->[0] <=> $b->[0] } map { [(exists $catref->{$_->[CAT]} ? $catref->{$_->[CAT]} : 9999), $_ ] } @$records; } sub default_cat_stack_mapping { return {'F:W' => 3, # Gewässer 'F:W1' => 3, # Gewässer 'W' => 3, 'W1' => 3, 'W2' => 3, 'F:I' => 6, # Insel 'F:P' => 15, # Parks # XXX This should be changed to real categories 'F:#c08080' => 10, # bebaute Flächen 'F:violet' => 20, # Industrie (alt) 'F:Industrial' => 20, # Industrie 'F:DarkViolet' => 21, # Hafen oder Industrie 'F:#46b47b' => 13, # Wald (alt) 'F:Woods' => 13, # Wald 'F:Orchard' => 13, 'F:Sport' => 13, 'F:Green' => 13, 'BAB' => 21, 'B' => 20, 'HH' => 15, 'H' => 10, 'N' => 5, 'NN' => 1, 'Pl' => 0, # Orte 6 => 6, 5 => 5, 4 => 4, 3 => 3, 2 => 2, 1 => 1, 0 => 0, }; } sub is_current { my($self) = @_; my @dependent_files; if ($self->dependent_files) { @dependent_files = $self->dependent_files; } elsif (defined $self->file) { @dependent_files = $self->file; } return 1 if !@dependent_files; return 0 if !defined $self->{Modtime}; for my $f (@dependent_files) { my $now_modtime = (stat($f))[STAT_MODTIME]; return 0 if $self->{Modtime} < $now_modtime; } return 1; } sub reload { my($self) = @_; return if $self->is_current; if ($self->{RebuildCode}) { $self->{RebuildCode}->(); } else { warn "Reload " . $self->file . "...\n"; $self->read_data; } if ($self->{Grid}) { warn "Rebuild grid ...\n"; $self->make_grid(-rebuild => 1); } } # See also get_conversion sub get_anti_conversion { my($self, %args) = @_; my $convsub; my $tomap = $self->{GlobalDirectives}{map} || $args{Map}; if ($tomap) { require Karte; Karte::preload(":all"); # Can't preload specific maps, because $map is a token, not a map module name my $frommap = $args{-frommap} || "standard"; return if $tomap eq $frommap; # no conversion needed if ($frommap ne "standard") { $convsub = sub { join ",", $Karte::map{$frommap}->map2map($Karte::map{$tomap}, split /,/, $_[0]); }; } else { $convsub = sub { join ",", $Karte::map{$tomap}->standard2map(split /,/, $_[0]); }; } } $convsub; } # Filter by a subroutine. # Return a new Strassen object. # This method uses the "grepstreets" iterator (use this for # get_directive_for_iterator) # Arguments: # -idadd => $string add this string to the id of the created object # -preservedir => $bool preserve directives sub grepstreets { my($s, $sub, %args) = @_; my $new_s = Strassen->new; $new_s->{DependentFiles} = [ $s->dependent_files ]; if ($args{-idadd}) { my $id = $new_s->id; $new_s->{Id} = $id . "_" . $args{-idadd}; } my $preserve_dir = $args{-preservedir} || 0; $s->init_for_iterator("grepstreets"); while(1) { my $r = $s->next_for_iterator("grepstreets"); last if !@{$r->[Strassen::COORDS]}; local $_ = $r; next if !&$sub; if ($preserve_dir) { $new_s->push_ext($r, $s->get_directive_for_iterator("grepstreets")); } else { $new_s->push($r); } } $new_s; } 1; __END__