# -*- perl -*- # # $Id: Kreuzungen.pm,v 1.15 2004/12/05 21:54:41 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. # # Author: Slaven Rezic (eserte@cs.tu-berlin.de) # package Strassen::Kreuzungen; $VERSION = sprintf("%d.%02d", q$Revision: 1.15 $ =~ /(\d+)\.(\d+)/); package Kreuzungen; use strict; use vars qw($VERBOSE); #use AutoLoader 'AUTOLOAD'; # Argumente: entweder "Hash" oder "Strassen" # "Hash" ist der Rückgabewert von Strassen::all_crossings (bei # Verwendung von "hash" oder "hashpos" als RetType). Array wird noch nicht # verwendet. # Bei "Strassen" werden die Kreuzungen automatisch per all_crossings erstellt. sub new { my($class, %args) = @_; my $self = {}; my $rettype = 'hash'; my $usecache = defined $args{UseCache} ? $args{UseCache} : 1; my $all_points = $args{AllPoints} || $args{Kurvenpunkte} || 0; my @config = ($all_points ? "kurvenp" : ()); if (!exists $args{Hash}) { die "Missing arg for new Kreuzungen: either Hash or Strassen" if !exists $args{Strassen}; if ($args{WantPos}) { $rettype .= "pos"; } my($kreuzungen_ref) = $args{Strassen}->all_crossings (RetType => $rettype, AllPoints => $all_points, UseCache => $usecache); $args{Hash} = $kreuzungen_ref; if ($args{Strassen}->{Inaccessible}) { push @config, "inacc"; } } $self->{Hash} = $args{Hash}; $self->{Array} = $args{Array}; $self->{IsPos} = $args{WantPos}; $self->{Strassen} = $args{Strassen}; $self->{Config} = \@config; bless $self, $class; } # Nimmt an. dass alle Punkte aus der Strassen-Datei Kreuzungen (z.B. Orte) # darstellen. # Das Argument "Kurvenpunkte" wird hier ignoriert. ### AutoLoad Sub sub new_from_strassen { my($class, %args) = @_; my $want_pos = $args{WantPos}; my $self = {}; $self->{Hash} = {}; $self->{Config} = [ ]; my $str = $args{Strassen}; if (!$str) { die "Missing arg for new_from_strassen: Strassen"; } $str->init; my $pos = 0; while(1) { my $ret = $str->next; last if !@{ $ret->[Strassen::COORDS()] }; foreach my $p (@{ $ret->[Strassen::COORDS()] }) { $self->{Hash}{$p} = [($want_pos ? $pos : $ret->[Strassen::NAME()])]; } $pos++; } if ($want_pos) { $self->{IsPos} = 1; $self->{Strassen} = $str; } bless $self, $class; } ### Autoload Sub sub id { my $self = shift; if ($self->{Strassen}) { $self->{Strassen}->id; } else { undef; } } ### Autoload Sub sub file { my $self = shift; if ($self->{Strassen}) { $self->{Strassen}->file; } else { undef; } } ### Autoload Sub sub dependent_files { my $self = shift; if ($self->{Strassen}) { $self->{Strassen}->dependent_files; } else { return; # undef or () } } ### Autoload Sub sub add { my($self, %args) = @_; if (!exists $args{Hash}) { die "Missing argument Hash"; } my $h = $self->{Hash}; while(my($k,$v) = each %{ $args{Hash} }) { $h->{$k} = $v; } } # Return true if the given $coord is a crossing ### AutoLoad Sub sub crossing_exists { my($self, $coord) = @_; exists $self->{Hash}{$coord}; } # Return an array reference of street names of this coord ### AutoLoad Sub sub get { my($self, $coord) = @_; if (exists $self->{Hash}{$coord}) { if ($self->{IsPos}) { [ map { $self->{Strassen}->get($_)->[Strassen::NAME()] } @{ $self->{Hash}{$coord} } ]; } else { $self->{Hash}{$coord} } } else { die "Coordinate $coord does not exist in Hash. The Hash has @{[ scalar keys %{ $self->{Hash} } ]} entries."; } } ### AutoLoad Sub sub get_first { shift->get(@_)->[0] } # Args: Width => $grid_width (optional, default 1000m) # Height => $grid_height (optional, default $args{Width} # UseCache => $bool (optional, default 0) ### AutoLoad Sub sub make_grid { my($self, %args) = @_; $self->{GridWidth} = (defined $args{Width} ? $args{Width} : 1000); $self->{GridHeight} = (defined $args{Height} ? $args{Height} : $self->{GridWidth}); my $cachefile; TRY_CACHE: { if ($args{UseCache}) { my $id = $self->id; if (!defined $id) { warn "Can't create cache: missing id" if $VERBOSE; last TRY_CACHE; } $cachefile = "kreuzungen_grid_" . $id . "_" . $self->{GridWidth} . "x" . $self->{GridHeight}; if ($self->{Config} && @{ $self->{Config} }) { $cachefile .= "_" . join("_", @{ $self->{Config} }); } 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; $self->{Grid} = $hashref; return $hashref; } } } $self->{Grid} = {}; keys %{$self->{Hash}}; # reset iterator while(defined(my $k = each %{$self->{Hash}})) { my $grid = join(",", $self->grid(split(/,/, $k))); push @{$self->{Grid}{$grid}}, $k; } if ($args{UseCache} && defined $cachefile) { require Strassen::Util; if (Strassen::Util::write_cache($self->{Grid}, $cachefile)) { warn "Wrote cache ($cachefile)\n" if $VERBOSE; } } } ### 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); } # Argumente: # $x, $y # IncludeDistance: Rückgabewert ist ein Feld mit den Elementen ["x,y", dist] # Grids (default 1): Anzahl der Grids, die vom Mittelpunkt abweichen dürfen # BestOnly: return only the best match, otherwise return all in grid # Ansonsten wird ein Feld mit den Elementen "x,y" zurückgegeben. In beiden # Fällen ist das Feld nach der Entfernung von $x und $y sortiert (kürzeste # zuerst). ### AutoLoad Sub sub nearest { my($self, $x, $y, %args) = @_; my $use_cache = delete $args{UseCache}; my $best_only = delete $args{BestOnly}; $self->make_grid(UseCache => $use_cache) if (!$self->{Grid}); my $xy = "$x,$y"; my $grids = $args{Grids} || 1; # my $distance = $args{Distance}; # XXX distance wird noch ignoriert # if (!defined $distance) { $distance = 1000 } my($gridx, $gridy) = $self->grid($x, $y); my @res; my %seen_combination; for my $grids_i (0 .. $grids) { my @grid_sequence = (0); push @grid_sequence, map { ($_, -$_) } (1 .. $grids_i); for my $xx (@grid_sequence) { for my $yy (@grid_sequence) { next if (exists $seen_combination{"$xx,$yy"}); $seen_combination{"$xx,$yy"} = 1; my $s = ($gridx+$xx) . "," . ($gridy+$yy); if (defined $self->{Grid}{$s}) { push @res, @{$self->{Grid}{$s}}; } } } last if $best_only && @res && $grids_i > 0; } if ($args{IncludeDistance}) { foreach (@res) { my $dist = Strassen::Util::strecke_s($_, $xy); $_ = [$_, $dist]; } @res = sort { $a->[1] <=> $b->[1] } @res; } else { @res = map { $_->[0] } sort { $a->[1] <=> $b->[1] } map { [$_, Strassen::Util::strecke_s($_, $xy)] } @res; } if ($best_only) { $res[0]; } else { @res; } } # Incrementally try nearest with Grids => 1 to Grids => 5 # 5 can be replaced by MaxGrids parameter sub nearest_loop { my($self, $x, $y, %args) = @_; my $max_grids = delete $args{MaxGrids} || 5; return $self->nearest($x, $y, %args, Grids => $max_grids); } # wie nearest, nur wird hier "x,y" als ein Argument übergeben ### AutoLoad Sub sub nearest_coord { my($self, $xy, %args) = @_; my($x, $y) = split(/,/, $xy); $self->nearest($x, $y, %args); } # wie nearest_loop, nur wird hier "x,y" als ein Argument übergeben ### AutoLoad Sub sub nearest_loop_coord { my($self, $xy, %args) = @_; my($x, $y) = split(/,/, $xy); $self->nearest_loop($x, $y, %args); } # Zeichnet die Kreuzungen, z.B. zum Debuggen. ### AutoLoad Sub sub draw { my($self, $canvas, $transpose_sub) = @_; $canvas->delete("crossings"); while(my $crossing = each %{ $self->{Hash} }) { my($x,$y) = $transpose_sub->(split /,/, $crossing); $canvas->createLine($x, $y, $x, $y, -tags => 'crossings', -fill => 'DeepPink', -capstyle => "round", # XXX see exceed bug in main -width => 4, ); } } 1;