# -*- perl -*- # # $Id: DirectGarmin.pm,v 1.26 2005/12/07 22:26:18 eserte Exp $ # Author: Slaven Rezic # # Copyright (C) 2002,2003,2004 Slaven Rezic. All rights reserved. # This package is free software; you can redistribute it and/or # modify it under the same terms as Perl itself. # # Mail: slaven@rezic.de # WWW: http://bbbike.sourceforge.net/ # package GPS::DirectGarmin; require GPS; push @ISA, 'GPS'; BEGIN { # XXX This should go away some day... if (-e "/home/e/eserte/work/perl-GPS") { eval 'use blib "/home/e/eserte/work/perl-GPS"'; warn $@ if $@; } eval 'use GPS::Garmin 0.13'; die $@ if $@; } BEGIN { if (!eval ' use Msg qw(frommain); 1; ') { warn $@ if $@; eval 'sub M ($) { $_[0] }'; eval 'sub Mfmt { sprintf(shift, @_) }'; } } use strict; use vars qw($DEBUG %waypoints); $DEBUG = 1 if !defined $DEBUG; sub transfer_to_file { 0 } sub transfer { my($self, %args) = @_; my $res = $args{-res} or die "-res argument is missing"; my($gps, $data) = @$res; my %maxdist_ret; if ($DEBUG && $self->{'debugdata'}) { %maxdist_ret = $self->dump; } if ($args{-test}) { require Data::Dumper; print STDERR Data::Dumper->new([$data],["gps_data"])->Useqq(1)->Dump . "\n"; my $wpt = 0; foreach (@$data) { $wpt++ if ($_->[0] eq $gps->GRMN_RTE_WPT_DATA); } my $mess = Mfmt("%s Waypoints in der Route.\n", $wpt); if ($maxdist_ret{'maxdist_mess'}) { $mess .= $maxdist_ret{'maxdist_mess'}; } if ($args{-top}) { my $can_plot = $maxdist_ret{'streets_bbd'} && defined &main::next_free_layer && defined &main::plot; require Tk::Dialog; my $d = $args{-top}->Dialog (-title => 'Simulate transfer', -bitmap => 'info', -text => $mess, -default_button => 'OK', -buttons => ["OK", ($can_plot ? 'Plot' : ())], ); my $ans = $d->Show; if ($ans =~ /plot/i) { my $abk = main::next_free_layer(); $main::str_file{$abk} = $maxdist_ret{'streets_bbd'}; $main::str_draw{$abk} = 1; main::plot('str',$abk); my $abk_p = main::next_free_layer(); $main::p_file{$abk_p} = $maxdist_ret{'points_bbd'}; $main::p_draw{$abk_p} = 1; main::plot('p',$abk_p); # XXX the delay is hackish $main::c->after(1000, sub { $main::c->raise($abk); $main::c->raise($abk_p."-fg"); }); } } else { print STDERR $mess; } } else { $gps->upload_data($data); } } # XXX die Berechnung der Maximalabweichung sollte nicht hier passieren... sub dump { my($self) = @_; my $debugdata = $self->{'debugdata'}; my $tmpdir = $main::tmpdir || "/tmp"; my $destfile_p = "$tmpdir/gpsdump-points.bbd"; my $destfile_s = "$tmpdir/gpsdump-streets.bbd"; require Karte; Karte::preload(qw(Polar Standard)); warn "Writing waypoint data to $destfile_p\n"; open(DEST, ">$destfile_p") or die $!; my @points; foreach (@$debugdata) { my $ident = $_->{'ident'}; my($x,$y) = map { int } $Karte::map{'polar'}->map2standard(@$_{qw(lon lat)}); push @points, "$x,$y"; print DEST "$ident\t#00b000 $x,$y\n"; } close DEST; require VectorUtil; my $maxdist = 0; my $maxdist_i = 0; my $data_i = 0; my $path_i = 0; foreach my $xy (@{ $self->{'origpath'} }) { my($x,$y) = @$xy; my($origx1,$origy1) = @{$debugdata->[$data_i]}{qw(origlon origlat)}; my($origx2,$origy2) = @{$debugdata->[$data_i+1]}{qw(origlon origlat)}; my $dist = VectorUtil::distance_point_line($x,$y,$origx1,$origy1,$origx2,$origy2); #warn "$dist: ($x,$y,$origx1,$origy1,$origx2,$origy2)"; if ($dist > $maxdist) { $maxdist = $dist; $maxdist_i = $data_i; } if ($origx2 == $x && $origy2 == $y) { $data_i++; } } continue { $path_i++ } my $maxdist_ident = $debugdata->[$maxdist_i]{'ident'}; my $maxdist_mess = Mfmt("Maximalabweichung: %d m an GPS-Routenpunkt <%s>\n", $maxdist, $maxdist_ident); my %ret = (maxdist => $maxdist, maxdist_ident => $maxdist_ident, maxdist_mess => $maxdist_mess, streets_bbd => $destfile_s, points_bbd => $destfile_p, ); warn $maxdist_mess; warn Mfmt("Schreibe Routendaten in die Datei %s\n", $destfile_s); open(DEST2, ">$destfile_s") or die $!; print DEST2 "GPS route\t#000080 " . join(" ", @points) . "\n"; close DEST2; %ret; } # XXX del $old_route_info_number use vars qw($old_route_info_name $old_route_info_wpt_suffix $old_route_info_wpt_suffix_existing); $old_route_info_wpt_suffix_existing=1; sub tk_interface { my($self, %args) = @_; #XXX return 1 if $args{-test}; # comment out if also testing wptsuffix my $top = $args{-top} or die "-top arg is missing"; my $gps_route_info = $args{-gpsrouteinfo} or die "-gpsrouteinfo arg is missing"; $gps_route_info->{Name} ||= $old_route_info_name if defined $old_route_info_name; #XXX del $gps_route_info->{Number} ||= $old_route_info_number if defined $old_route_info_number; $gps_route_info->{WptSuffix} ||= $old_route_info_wpt_suffix if defined $old_route_info_wpt_suffix; $gps_route_info->{WptSuffixExisting} ||= $old_route_info_wpt_suffix_existing if defined $old_route_info_wpt_suffix_existing; my $t = $top->Toplevel(-title => "GPS"); $t->transient($top) if $main::transient; Tk::grid($t->Label(-text => M"Name der Route"), my $e = $t->Entry(-textvariable => \$gps_route_info->{Name}, -validate => 'all', -vcmd => sub { length $_[0] <= 13 }), -sticky => "w"); $e->focus; ## Can be deleted, as the routenumber is ignored XXX # my $NumEntry = 'Entry'; # my @NumEntryArgs = (); # if (eval { require Tk::NumEntry }) { # $NumEntry = "NumEntry"; # @NumEntryArgs = (-minvalue => 1, -maxvalue => 20); # } # Tk::grid($t->Label(-text => M"Routennummer"), # $t->$NumEntry(-textvariable => \$gps_route_info->{Number}, # @NumEntryArgs, # -validate => 'all', # -vcmd => sub { $_[0] =~ /^\d*$/ }), # -sticky => "w"); Tk::grid($t->Label(-text => M"Waypoint-Suffix"), $t->Entry(-textvariable => \$gps_route_info->{WptSuffix}), -sticky => "w"); Tk::grid($t->Checkbutton(-text => M"Suffix nur bei vorhandenen Waypoints verwenden", -variable => \$gps_route_info->{WptSuffixExisting}), -sticky => "w", -columnspan => 2); Tk::grid($t->Button(-text => M"Waypoints-Cache zurücksetzen", -command => sub { %waypoints = (); }), -sticky => "w", -columnspan => 2); if (defined &main::optedit) { Tk::grid($t->Button(-text => M"GPS-Einstellungen", -command => sub { main::optedit(-page => M"GPS"); }), -sticky => "w", -columnspan => 2); } my $weiter = 0; { my $f = $t->Frame->grid(-columnspan => 2, -sticky => "ew"); Tk::grid($f->Button(-text => ($args{-test} ? M("Upload zum Garmin simulieren") : M("Upload zum Garmin")), -command => sub { $weiter = 1 }), $f->Button(Name => "cancel", -text => M"Abbruch", -command => sub { $weiter = -1 }), ); } $t->gridColumnconfigure($_, -weight => 1) for (0..1); $t->OnDestroy(sub { $weiter = -1 }); $t->waitVariable(\$weiter); $t->afterIdle(sub { if (Tk::Exists($t)) { $t->destroy } }); if ($weiter == 1) { $old_route_info_name = $gps_route_info->{Name}; #XXX del $old_route_info_number = $gps_route_info->{Number}; $old_route_info_wpt_suffix = $gps_route_info->{WptSuffix}; $old_route_info_wpt_suffix_existing = $gps_route_info->{WptSuffixExisting}; } return undef if $weiter == -1; 1; } ### this should load a route or track from the GPS device and ### convert it to bbd/bbr # sub convert_to_route { # my($self, $file, %args) = @_; # my($fh, $lines_ref) = $self->overread_trash($file, %args); # die "File $file does not match" unless $fh; # require Karte::Polar; # my $obj = $Karte::Polar::obj; # my @res; # my $check = sub { # my $line = shift; # chomp; # if (m|^W\s+(?:\w+)\s+([NS])(\d+)\s+([\d.]+)\s+([EW])(\d+)\s+([\d.]+)|) { # my $breite = $2; # my $laenge = $5; # my $breite_min = $3/60; # my $laenge_min = $6/60; # $breite += $breite_min; # $laenge += $laenge_min; # if ($1 eq 'S') { $breite = -$breite } # if ($3 eq 'W') { $laenge = -$laenge } # my($x,$y) = $obj->map2standard($laenge, $breite); # if (!@res || ($x != $res[-1]->[0] || # $y != $res[-1]->[1])) { # push @res, [$x, $y]; # } # } # }; # $check->($_) foreach @$lines_ref; # while(<$fh>) { # $check->($_); # } # close $fh; # @res; # } # der eTrex kann 50 Punkte pro Route aufzeichnen # XXX # ideas: # wichtigkeitspunkte für jeden (möglichen) punkt vergeben: # - viele punkte für große winkel # - punkte für straßennamenswechsel # - evtl. minuspunkte für kleine entfernungen vom vorherigen+nächsten punkt #XXX mit GpsmanData und den anderen Modulen mergen!!! sub convert_from_route { my($self, $route, %args) = @_; no locale; # for scalar localtime require Karte::Polar; require Strassen; my $obj = $Karte::Polar::obj; my $routename = sprintf("%-8s", $args{-routename} || "TRACBACK"); my $routenumber = $args{-routenumber} || 1; my $str = $args{-streetobj}; my $net = $args{-netobj}; my $wptsuffix = $args{-wptsuffix} || ""; my $wptsuffixexisting = $args{-wptsuffixexisting} || 0; my $convmeth = $args{-convmeth} || sub { $obj->standard2map(@_); }; my $gps_device = $args{-gpsdevice} || "/dev/cuaa0"; my %crossings; if ($str) { %crossings = %{ $str->all_crossings(RetType => 'hash', UseCache => 1) }; } my $now = scalar localtime; my $ident_counter = 0; my %idents; use constant MAX_COMMENT => 45; use constant IDENT_LENGTH => 10; # XXX make configurable use constant DISPLAY_SYMBOL_BIG => 8196; # zwei kleine Füße (sym_trcbck) use constant DISPLAY_SYMBOL_SMALL => 18; # viereckiger Punkt, also allgemeiner Wegepunkt (waypoint dot) use constant SHOW_SYMBOL => 1; use constant SHOW_SYMBOL_AND_NAME => 4; # XXX ? ja ? use constant SHOW_SYMBOL_AND_COMMENT => 5; my $gps; if (!$args{-test}) { $gps = new GPS::Garmin( 'Port' => $gps_device, 'Baud' => 9600, # XXX don't hardcode #verbose => 1, ) or die Mfmt("Verbindung zum GPS-Gerät <%s> fehlgeschlagen", $gps_device); } else { $gps = bless {}, 'GPS::Garmin::Dummy'; { package GPS::Garmin::Dummy; *handler = sub { bless {}, 'GPS::Garmin::Dummy' }; sub dummy { 0 } sub AUTOLOAD { goto &dummy } # more dummy definition sub GRMN_RTE_LINK_DATA { 1 } sub GRMN_RTE_WPT_DATA { 2 } } } my @d; my $handler = $gps->handler; push @d, [$gps->GRMN_RTE_HDR, $handler->pack_Rte_hdr({nmbr => $routenumber, cmnt => $routename})]; if ($DEBUG) { $self->{'debugdata'} = []; } my @path; my $obj_type; if ($args{-routetoname}) { @path = map { $route->path->[$_->[&StrassenNetz::ROUTE_ARRAYINX][0]] } @{$args{-routetoname}}; push @path, $route->path->[-1]; # add goal node $obj_type = 'routetoname'; } else { if ($net && $args{-simplify}) { my $max_waypoints = $args{-maxwaypoints} || 50; @path = $route->path_list_max($net, $max_waypoints); } else { @path = $route->path_list; } $obj_type = 'route'; } my $n = 0; foreach my $xy (@path) { my $xy_string = join ",", @$xy; my($lon, $lat) = $convmeth->(@$xy); # XXX del: $obj->standard2map(@$xy); #warn "lon=$lon lat=$lat\n"; # create comment and point number my $comment = ""; my $ident; my @cross_streets; if ($str && exists $crossings{$xy_string}) { @cross_streets = @{ $crossings{$xy_string} }; } if ($obj_type eq 'routetoname') { my $main_street = $args{-routetoname}->[$n][&StrassenNetz::ROUTE_NAME]; my $prev_street = $args{-routetoname}->[$n-1][&StrassenNetz::ROUTE_NAME] if $n > 0; # test for simplify_route_to_name output: if (ref $main_street eq 'ARRAY') { $main_street = $main_street->[0]; } if (ref $prev_street eq 'ARRAY') { $prev_street = $prev_street->[-1]; } # This condition is hacky: because landstrassen may or may not be # transformed from "A - B" to "(A -) B" or "(B -) A" the # $prev_street vs. $main_street comparison below does not # work. The workaround is to remove the (A -) part. This # would be removed anyway in the short_landstrasse function. if ($main_street =~ m/\s*\([^\)]+-\)\s*/) { # XXX (... -) ... @cross_streets = $main_street; } # no crossing => use at least the current street if (!@cross_streets) { @cross_streets = $main_street; } # if the main street is still the same, then use the # "+crossingstreet" syntax # XXX theoretisch kann "+crossingstreet+mainstreet" passieren, # was nicht so schön wäre ... aber dazu müsste crossingstreet # superkurz oder IDENT_LENGTH superlang sein # XXX die Abfrage im map scheint teilweise überflüssig zu sein # (main_street eq prev_street!) elsif (defined $prev_street && $prev_street eq $main_street && @cross_streets > 1) { @cross_streets = ("", map { $_->[0] } sort { $b->[1] <=> $a->[1] } map { [$_, $_ eq $main_street ? -99 : (defined $prev_street && $_ eq $prev_street ? -100 : 0) ] } @cross_streets); } # Sort the crossing streets so, that the current street # is first and the previous street (if any) is last. else { if (defined $prev_street && $prev_street eq $main_street) { undef $prev_street; } @cross_streets = (map { $_->[0] } sort { $b->[1] <=> $a->[1] } map { [$_, $_ eq $main_street ? 100 : (defined $prev_street && $_ eq $prev_street ? -100 : 0) ] } @cross_streets); } } if (@cross_streets) { # try to shorten street names if ($n < $#path) { $cross_streets[0] = short_landstrasse($cross_streets[0], $net, $xy_string, join(",",@{ $path[$n+1] })); } my $short_crossing; my $level = 0; while($level <= 3) { # XXX the "+" character is not supported by all Garmin devices $short_crossing = join("+", map { s/\s+\(.*\)\s*$//; Strasse::short($_, $level, "nodot") } @cross_streets); $short_crossing = _eliminate_umlauts($short_crossing); last # if (length($short_crossing) + length($comment) <= MAX_COMMENT); if (length($short_crossing) + length($comment) <= IDENT_LENGTH); $level++; } $comment .= $short_crossing; my $short_name; my $suffix_in_use = 0; my $create_short_name = sub { my($suffix,$name) = @_; $name = substr($name.(" "x IDENT_LENGTH), 0, IDENT_LENGTH); if ($suffix ne "") { substr($name, IDENT_LENGTH-length($suffix), length($suffix), $suffix); $suffix_in_use = $suffix; } else { $suffix_in_use = ""; } uc($name); # Garmin etrex venture supports only uppercase chars }; TRY: { if ($wptsuffix ne "" && $wptsuffixexisting) { $short_name = $create_short_name->("",$short_crossing); last TRY if (!exists $waypoints{$short_name}); } $short_name = $create_short_name->($wptsuffix,$short_crossing); } $ident = $short_name; my $local_ident_counter = ord("0")-1; while (exists $idents{$ident}) { # || # ($wptsuffixexisting && $wptsuffix ne "" && exists $waypoints{$ident})) { $local_ident_counter++; if ($local_ident_counter > ord("Z")) { last; # give up } elsif ($local_ident_counter > ord("9") && $local_ident_counter < ord("A")) { $local_ident_counter = ord("A"); } substr($ident,IDENT_LENGTH-1-length($suffix_in_use),1) = chr($local_ident_counter); } if (length($comment) > MAX_COMMENT) { $comment = substr($comment, 0, MAX_COMMENT); } } if (!defined $ident || $ident =~ /^\s*$/) { if ($n == 0) { $ident = "START $routenumber"; # no $wptsuffix needed } elsif ($n == $#path) { $ident = "GOAL $routenumber"; } else { $ident = $wptsuffix."T". ($ident_counter++); # don't bother with wptsuffixexisting here, and with suffix used as a prefix here } } print STDERR $ident, "\n"; $idents{$ident}++; $waypoints{$ident}++; if ($n > 0) { push @d, [$gps->GRMN_RTE_LINK_DATA, $handler->pack_Rte_link_data]; } my $wptdata = {lat => $lat, lon => $lon, ident => $ident, smbl => 8246}; # summit symbol XXX push @d, [$gps->GRMN_RTE_WPT_DATA, $handler->pack_Rte_wpt_data($wptdata)]; if ($DEBUG) { push @{$self->{'debugdata'}}, {%$wptdata, origlon => $xy->[0], origlat => $xy->[1]}; } } continue { $n++; } $self->{'origpath'} = $route->path; #use Data::Dumper; print STDERR "Line " . __LINE__ . ", File: " . __FILE__ . "\n" . Data::Dumper->Dumpxs([\@d],[]); # XXX return [$gps, \@d]; } # ... and more # XXX use version from GPS::Util? sub _eliminate_umlauts { my $s = shift; $s =~ tr/äöüÄÖÜéè/aouAOUee/; $s =~ s/ß/ss/g; $s =~ s/[\200-\377]/_/g; $s =~ s/[\(\)]//g; $s =~ s/str\./str/g; $s =~ s/\./ /g; $s; } sub short_landstrasse { my($s, $net, $xy1, $xy2) = @_; $s = Strasse::beautify_landstrasse($s, $net->street_is_backwards($xy1, $xy2)); $s =~ s/:\s+/ /g; $s =~ s/\s*\([^\)]+-\)\s*/-/g; $s; } 1; __END__