#
# This file is part of:
#
#  gpsman --- GPS Manager: a manager for GPS receiver data
#
# Copyright (c) 1998-2013 Miguel Filgueiras migfilg@t-online.de
#
#    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 3 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.
#
#  File: realtime.tcl
#  Last change:  6 October 2013
#

#### real time logging control

proc GPSRealTimeLogOnOff {} {
    # start or stop the real time log
    # change the button label accordingly
    global TXT RealTimeLogOn WConf

    if { $RealTimeLogOn } {
	StopRealTimeLog
	set txt $TXT(getlog)
    } else {
	if { ! [StartRealTimeLog 2] } { return }
	set txt $TXT(stop)
    }
    foreach w $WConf(realtime) type $WConf(realtimetype) {
	switch $type {
	    button {
		$w configure -text $txt
	    }
	    menuentry {
		[lindex $w 0] entryconfigure [lindex $w 1] -label $txt
	    }
	}
    }
    return 
}

proc StartRealTimeLog {interval} {
    # start real time logging with given $interval in seconds
    # assume it is currently off
    # return 0 on failure
    global GPSProtocol RealTimeLogIntv RealTimeLogOn RealTimeLogLast \
	    Travelling MESS MYGPS

    set RealTimeLogIntv $interval
    switch $GPSProtocol {
	garmin -  garmin_usb {
	    if { ! [GarminStartPVT] } { return 0 }
	}
	stext {
	    if { ! [StartSimpleText] } { return 0 }
	}
	simul {
	    SimulPVTOn
	}
	drivesim {
	    SimulDriveOn
	}
	nmea {
	    if { $MYGPS == "Lowrance" } {
		NMEASetupWindow
	    } elseif { ! [GarminStartNMEA rec] } { return 0 }
	}
	lowrance {
	    NMEASetupWindow
	}
	default {
	    GMMessage $MESS(rltmnotsupp)
	    return 0
	}
    }
    if { $Travelling } {
	TravelRealTimeOn
    }
    set RealTimeLogLast 0
    set RealTimeLogOn 1
    return 1
}

proc StopRealTimeLog {} {
    # stop real time logging
    # assume it is currently on
    global GPSProtocol RealTimeLogOn RealTimeLogAnim Travelling MYGPS

    switch $GPSProtocol {
	garmin -  garmin_usb {
	    GarminStopPVT
	}
	lowrance -
	nmea {
	    if { $MYGPS == "Lowrance" } {
		NMEAOff
	    } else { GarminStopNMEA }
	}
	stext {
	    StopSimpleText
	}
	simul {
	    SimulPVTOff
	}
	drivesim {
	    SimulDriveOff
	}
    }
    if { $RealTimeLogAnim } {
	AnimStopRealTime
	set RealTimeLogAnim 0
    }
    if { $Travelling } {
	TravelRealTimeOff
    }
    set RealTimeLogOn 0
    return
}

##### real time log window and hook to animation procs

proc UseRealTimeData {data} {
    # use position, velocity and time data
    #  UTC time - as a list with y m d h mn s
    #  position - as a list with latd, longd (for datum WGS 84)
    #  pos fix  - in {error, _, 2D, 3D, 2D-diff, 3D-diff, GPS, DGPS, Auto,
    #                 simul}
    #  pos error - as a list with
    #              estimated position error EPE in metres
    #              estimated horizontal error EPH in metres
    #              estimated vertical error in metres
    #  altitude - signed number in metres
    #  velocity vector - as list with vlat vlong valt, m/s
    #  course over ground - degrees
    #  number of satellites in view
    #  HDOP - horizontal dilution of precision
    #  speed - km/h
    # any value may be "_" or have a "_" to mean non-available data
    global TXT DateFormat RealTimeLogIntv RealTimeLogLast RealTimeLogAnim \
	    PVTLast PVTState PVTElapsed PVTZero PVTPosns PVTSecs PVTInterval \
	    PVTDFile GPSProtocol MYGPS Travelling CMDLINE

    set date [lindex $data 0]
    if { $date == "_" } {
	if { [string first "garmin" $GPSProtocol] == 0 || \
		($MYGPS == "Garmin" && $GPSProtocol == "nmea") } {
	    Log "UPVTD> bad date: $date"
	}
	if { $Travelling } { TravelBadDate }
	return
    }
    set date [eval LocalTimeAndUTC $date UTC]
    set secs [eval DateToSecs $date]
    set PVTInterval $RealTimeLogIntv
    if { [llength $data] < 10 } { return }
    foreach "x rpos fix err alt velv trk nsat hdop speed" $data { break }
    if { $rpos == "_" || $secs-$RealTimeLogLast < $RealTimeLogIntv } {
	return
    }
    set RealTimeLogLast $secs
    foreach "epe eph epv" $err {}
    foreach "velx vely velz" $velv {}
    if { $RealTimeLogAnim } { AnimNextRealTime $rpos "WGS 84" $secs }
    set posn [eval FormatLatLong $rpos DMS]
    set flat [lindex $posn 2] ; set flong [lindex $posn 3]
    if { $Travelling } {
	TravelData [lrange $date 3 5] $posn $flat $flong $fix $alt \
		   $velx $vely $velz $trk $speed
    }
    if { $PVTState != "on" } { return }
    # recording
    if { ! [winfo exists .pvt] } { RealTimeLogWindow }
    if { $PVTZero < 0 } { set PVTZero $secs }
    set PVTElapsed "    [FormatTime [expr $secs-$PVTZero]]"
    lappend PVTSecs $secs
    lappend PVTPosns $posn
    set vals [list [format "%4d." [incr PVTLast]] \
	    [eval FormatDate $DateFormat $date] \
	    $flat $flong \
	    [RTimeFormat $alt %7.1f] \
	    $TXT(posfix$fix) \
	    [RTimeFormat $epe %5.1f] \
	    [RTimeFormat $eph %5.1f] \
	    [RTimeFormat $epv %5.1f] \
	    [RTimeFormat $velx %7.1f] \
	    [RTimeFormat $vely %7.1f] \
	    [RTimeFormat $velz %7.1f] \
	    [RTimeFormat $trk %3.1f]]
    if { $PVTDFile != "" } { RTimeDumpLine $vals }

    if { ! $CMDLINE } {
	set fx .pvt.fri.frtbx ; set col 0

	foreach b "n d lat long alt fix epe eph epv velx vely velz trk" \
		v $vals {
	    if { [$fx.bx$b size] == 0 && $v == "" } {
		CollapseColumn "$fx.tit$b $fx.bx$b" $col \
			[lindex $TXT(PVTflds) $col] \
			menubtentry .pvt.frs.frbt.show.m .pvt.frs.frbt.show
	    }
	    incr col
	    $fx.bx$b insert end $v ; $fx.bx$b see end
	}
    }
    update
    return
}

proc RealTimeLogWindow {} {
    # create window for displaying real-time log
    # columns with empty first values will be collapsed
    global RealTimeLogIntv RealTimeLogAnim DATEWIDTH TXT MPOSX MPOSY COLOUR \
	    PVTLast PVTState PVTStateExt PVTElapsed PVTZero PVTPosns \
	    PVTSecs PVTInterval PVTDumping PVTDFile CMDLINE FixedFont

    set PVTLast 0
    set PVTElapsed "" ; set PVTZero -1
    set PVTPosns "" ; set PVTSecs ""
    if { $CMDLINE } { return }

    set fx .pvt.fri.frtbx

    set PVTInterval $RealTimeLogIntv
    set PVTStateExt $TXT(animon)
    set PVTDumping 0 ; set PVTDFile ""

    trace variable PVTState w RTimeStateChange
    trace variable PVTInterval w RTimeIntervalChange

    GMToplevel .pvt realtimelog +[expr $MPOSX+10]+[expr $MPOSY+80] {} \
        {WM_DELETE_WINDOW RTimeCancel} {}

    label .pvt.tit -text $TXT(realtimelog)

    frame .pvt.fri -relief flat -borderwidth 5 -bg $COLOUR(messbg)

    # see above for initialization of $fx
    frame $fx -relief flat -borderwidth 0
    # see below for creation of $fb and $show
    set fb .pvt.frs.frbt
    set show $fb.show ; set mn $show.m
    set col 0
    foreach b "n d lat long alt fix epe eph epv velx vely velz trk" \
	    m "5 $DATEWIDTH 10 10 4 4 4 4 4 6 6 6 4" \
	    t $TXT(PVTflds) {
	button $fx.tit$b -width $m -text $t -font $FixedFont \
		-command [list CollapseColumn "$fx.tit$b $fx.bx$b" $col \
		                               $t menubtentry $mn $show]
	BalloonBindings $fx.tit$b hide
	listbox $fx.bx$b -height 15 -width $m -relief flat \
		-yscrollcommand "$fx.bscr set" \
		-selectmode single -exportselection false
	bind $fx.bx$b <Button-1> {
	    MultSelect [winfo parent %W] [%W nearest %y] \
		    "bxn bxd bxlat bxlong bxalt bxfix bxepe bxeph bxepv \
		    bxvelx bxvely bxvelz bxtrk"
	}
	bind $fx.bx$b <Double-1> {
	    set i [%W nearest %y] ; set n [%W get $i]
	    set ww [winfo toplevel %W]
	    MultSelect [winfo parent %W] $i \
		    "bxn bxd bxlat bxlong bxalt bxfix bxepe bxeph bxepv \
		    bxvelx bxvely bxvelz bxtrk"
	    if { $n != "" } {
		MarkPoint PVT $ww $i
	    }
	}
	grid configure $fx.tit$b -column $col -row 0 -sticky news
	grid configure $fx.bx$b -column $col -row 1 -sticky news
	incr col
    }
    set boxes [list $fx.bxd $fx.bxn $fx.bxlat $fx.bxlong \
	    $fx.bxalt $fx.bxfix $fx.bxepe $fx.bxeph $fx.bxepv \
	    $fx.bxvelx $fx.bxvely $fx.bxvelz $fx.bxtrk]
    scrollbar $fx.bscr -command [list ScrollMany $boxes]
    Mscroll $boxes
    grid configure $fx.bscr -column $col -row 1 -sticky news

    frame .pvt.frs -relief flat -borderwidth 0
    # $fb == .pvt.frs.frbt
    frame $fb -relief flat -borderwidth 0
    button $fb.rest -text $TXT(restart) \
	    -command "RTimeRestart ; $fb.rest configure -state normal"
    button $fb.pause -text $TXT(pause) \
	    -command "RTimePause ; $fb.pause configure -state normal"
    menubutton $show -text $TXT(show) -relief raised \
	    -direction below -menu $show.m -state disabled
    menu $show.m -tearoff 0
    set fb1 ${fb}1
    frame $fb1 -relief flat -borderwidth 0
    button $fb1.stan -text $TXT(animation) \
	    -command "set RealTimeLogAnim 1 ; \
	    $fb1.stan configure -state normal"
    button $fb1.totr -text $TXT(mkTR) \
	    -command "RTimeToTR ; $fb1.totr configure -state normal"
    set mn $fb1.save.m
    menubutton $fb1.save -text $TXT(save) -relief raised \
	    -direction below -menu $mn
    menu $mn -tearoff 0
    $mn add command -label $TXT(exstglog) \
	    -command { SaveFile comp PVTData }
    $mn add checkbutton -label $TXT(contnsly) -selectcolor $COLOUR(check) \
	    -variable PVTDumping -command RTimeDump

    frame .pvt.frs.int -relief flat -borderwidth 0
    set fs .pvt.frs.int
    label $fs.sl -text $TXT(slow) -width 6
    scale $fs.sp -orient horizontal -from 1 -to 101  -showvalue 1 \
	    -width 8 -length 150 -variable PVTInterval \
	    -label "$TXT(rtimelogintv) (s):"
    label $fs.fst -text $TXT(fast) -width 6
    grid configure $fs.fst -column 0 -row 0 -sticky news
    grid configure $fs.sp -column 1 -row 0 -sticky news
    grid configure $fs.sl -column 2 -row 0 -sticky news

    frame .pvt.frs.st -relief flat -borderwidth 0
    set ft .pvt.frs.st
    label $ft.tit -text $TXT(state):
    label $ft.st -width 10 -textvariable PVTStateExt
    label $ft.tm -width 15 -textvariable PVTElapsed

    button .pvt.cnc -text $TXT(cancel) -command RTimeCancel

    pack $fb.rest $fb.pause $show -side top -pady 3 -fill x
    pack $fb1.stan $fb1.totr $fb1.save -side top -pady 3 -fill x
    pack $fx
    pack $fs $ft $fb $fb1 -side left -padx 10
    pack $ft.tit $ft.st $ft.tm -side left
    pack .pvt.tit .pvt.fri .pvt.frs .pvt.cnc -side top -pady 5
    return
}

proc RTimeFormat {f fmt} {

    if { $f == "_" } { return "" }
    return [format $fmt $f]
}

proc RTimeCancel {} {
    global PVTState PVTDFile

    set PVTState abort
    if { $PVTDFile != "" } {
	close $PVTDFile
    }
    trace vdelete PVTState w RTimeStateChange
    trace vdelete PVTInterval w RTimeIntervalChange
    destroy .pvt
    return
}

proc RTimeToTR {} {
    # make a TR from the PVT data
    global PVTSecs PVTPosns

    set fx .pvt.fri.frtbx
    set tpfs "latd longd latDMS longDMS date secs alt"
    set i 0 ; set tps ""
    foreach p $PVTPosns s $PVTSecs {
	if { "$p" != "" } {
	    lappend p [$fx.bxd get $i] $s [$fx.bxalt get $i]
	    lappend tps [FormData TP $tpfs $p]
	}
	incr i
    }
    if { "$tps" == "" } { bell ; return }
    set opts "create revert cancel"
    GMTrack -1 $opts [FormData TR "Datum TPoints" [list "WGS 84" $tps]]
    return
}

proc RTimeRestart {} {
    # continue after a pause or restart logging
    # clear information in PVT window if restarting
    global RealTimeLogLast PVTSecs PVTPosns PVTLast PVTState PVTElapsed PVTZero

    if { "$PVTState" == "pause" } {
	set PVTState on
    } else {
	set PVTSecs "" ; set PVTPosns ""
	set RealTimeLogLast 0
	set PVTLast 0 ; set PVTState on ; set PVTElapsed "" ; set PVTZero -1
	set fx .pvt.fri.frtbx
	foreach b "n d lat long alt fix epe eph epv velx vely velz trk" {
	    $fx.bx$b delete 0 end
	}
    }
    return
}

proc RTimePause {} {
    # pause or continue after a pause
    global PVTState

    if { "$PVTState" == "pause" } {
	set PVTState on
    } else { set PVTState pause }
    return
}

proc RTimeStateChange {n no op} {
    # called by trace when $PVTState has been changed
    global PVTState PVTStateExt TXT

    set PVTStateExt $TXT(anim$PVTState)
    return
}

proc RTimeIntervalChange {n no op} {
    # called by trace when $PVTInterval has been changed
    # if there is an animation window for the log set Anim(speed,-1)
    #  that has a trace on it; otherwise set RealTimeLogIntv directly
    global PVTInterval RealTimeLogIntv Anim

    if { [winfo exists .anim-1] } {
	after 0 "set Anim(speed,-1) [expr $PVTInterval-51]"
    } else {
	set RealTimeLogIntv $PVTInterval
    }
    return
}

proc RTimeDump {} {
    # start/stop dumping continuously
    global PVTDFile PVTDumping TXT

    if { $PVTDFile == "" } {
	if { [set PVTDFile [GMOpenFile $TXT(saveto) PVTData wapp]] == ".." } {
	    set PVTDumping 0 ; set PVTDFile ""
	} else {
	    SavePVTData $PVTDFile
	}
    } else {
	close $PVTDFile
	set PVTDFile ""
    }
    return
}

proc RTimeDumpLine {vals} {
    # dump PVT data (except line number) unless only one fix is to be
    #  got (in command-line mode) in which case set the appropriate
    #  global to the PVT data
    global PVTDFile RealTimeGettingFix

    if { $RealTimeGettingFix != 0 } {
	if { $RealTimeGettingFix == 1 } { set RealTimeGettingFix $vals }
	return
    }
    set sep ""
    foreach v [lreplace $vals 0 0] {
	puts -nonewline $PVTDFile "${sep}$v"
	set sep "\t"
    }
    puts $PVTDFile ""
    flush $PVTDFile
    return
}

##### driving simulator

proc SimulDriveOn {} {
    # simulate PVT by generating fake PVT data lists and calls to
    #  proc UseRealTimeData
    # the data lists are computed from user actions in a driving window
    global SimulDrive COLOUR TXT EPOSX EPOSY SPUNIT MESS

    set w .simdrive
    if { [winfo exists $w] } { Raise $w ; return }
    set SimulDrive(data) ""
    set SimulDrive(mess) ""

    GMToplevel $w drivesim +$EPOSX+$EPOSY {} \
        {WM_DELETE_WINDOW {GMMessage $MESS(chgrecprot)}} \
	[list <Key-Up> {SimulDriveChange speed 5} \
	     <Key-Down> {SimulDriveChange speed -5} \
	     <Key-Left> {SimulDriveChange turn 1} \
	     <Key-Right> {SimulDriveChange turn -1} \
	     <Key-space> {SimulDriveChange turnval 0} \
	     <Leave> {SimulDriveControl 0} \
	     <Enter> "focus $w ; SimulDriveControl 1"]

    frame $w.fr -borderwidth 2 -bg $COLOUR(messbg)
    frame $w.fr.frsw
    # steering wheel
    set swwd 74 ; set swht 74
    set cwl $w.fr.frsw.c_whl
    canvas $cwl -width $swwd -height $swht -relief flat -bg $COLOUR(dialbg)
    if { $swwd >= $swht } { set sl $swht } else { set sl $swwd }
    set sl [expr int($sl*0.8)]
    set x0 [expr ($swwd-$sl)/2] ; set xe [expr $x0+$sl]
    set y0 [expr ($swht-$sl)/2] ; set ye [expr $y0+$sl]
    $cwl create oval $x0 $y0 $xe $ye -fill $COLOUR(dialbg) -width 4 \
		-outline green
    set xm [expr $swwd/2] ; set sl [expr $sl*0.5]
    set fx 2 ; set fy 2
    set d [expr $sl/6] ; set d1 [expr $sl/10]
    set gx 5 ; set gy 3
    set d2 [expr $sl/4]
    set ps [list [expr $xm-$sl+$fx] [expr $xm-$fy] \
	          [expr $xm-$sl+$d] [expr $xm-$d1] \
		  [expr $xm+$sl-$d] [expr $xm-$d1] \
		  [expr $xm+$sl-$fx] [expr $xm-$fy] \
		  [expr $xm+$sl-$gx] [expr $xm+$gy] \
		  [expr $xm+$d2] [expr $xm+$d1] \
		  [expr $xm+$d] [expr $xm+$d]  [expr $xm-$d] [expr $xm+$d] \
		  [expr $xm-$d2] [expr $xm+$d1] \
		  [expr $xm-$sl+$gx] [expr $xm+$gy] \
		  [expr $xm-$sl+$fx] [expr $xm-$fy]]
    eval $cwl create polygon $ps -fill green -tags wheel
    set cs ""
    foreach "x y" $ps {
	lappend cs [expr $x-$xm] [expr $y-$xm]
    }
    set d 3 ; set m [expr $xm+$d] ; set n [expr $xm-$d]
    $cwl create oval $n $n $m $m -fill black
    set SimulDrive(wheel) [list $xm $xm wheel $cwl $cs]
    # speed bar
    scale $w.fr.frsw.speed -length $swht -from 500 -to 0 -showvalue 1 \
	    -width 20 -label $SPUNIT -variable SimulDrive(speed) \
	    -command SimulDriveSpeedChange
    # help
    BalloonButton $w.fr.frsw.bhlp rthlpdsim
    # steering buttons
    set i -1
    foreach b "left straight right" op "turn turnval turn" v "1 0 -1" {
	button $w.fr.frsw.b$b -text $TXT($b) -width 6 \
		-command "$w.fr.frsw.b$b configure -state normal ; \
		          SimulDriveChange $op $v"
	grid $w.fr.frsw.b$b -row 1 -column [incr i] -sticky ew
    }
    grid $w.fr.frsw.bhlp -column 0 -row 0 -sticky es
    grid $cwl -column 1 -row 0
    grid $w.fr.frsw.speed -column 2 -row 0 -sticky wns

    # message
    label $w.fr.mess -textvariable SimulDrive(mess) -width 15 -fg red

    frame $w.fr.frbs
    button $w.fr.frbs.start -text $TXT(startfrom) -command SimulDriveStart
    grid $w.fr.frbs.start

    foreach x "frsw mess" {
	grid $w.fr.$x
    }
    grid $w.fr.frbs -pady 5
    grid $w.fr
    RaiseWindow $w
    update idletasks
    return
}

proc SimulDriveControl {flag} {
    # pointer left from/returned to window
    global SimulDrive TXT

    if { $SimulDrive(data) != "" && ! $flag } {
	set SimulDrive(mess) $TXT(outofctrl)
    } else { set SimulDrive(mess) "" }
    return
}

proc SimulDriveSpeedChange {val} {
    # change in speed scale: start if needs be
    global SimulDrive MESS

    if { $SimulDrive(data) == "" } {
	GMMessage $MESS(drvsimoff)
    }
    return
}

proc SimulDriveChange {what d} {
    # $what in {speed, turn, turnval}
    # $d is change in value unless $what==turnval in which case is new value
    global SimulDrive MESS

    if { $SimulDrive(data) == "" } {
	GMMessage $MESS(drvsimoff)
    } elseif { $what == "turnval" } {
	set SimulDrive(turn) $d
	TurnObject $d $SimulDrive(wheel)
    } else {
	incr SimulDrive($what) $d
	if { $what == "turn" } {
	    if { $SimulDrive(turn) > 180 } {
		set SimulDrive(turn) 180
	    } elseif { $SimulDrive(turn) < -180 } {
		set SimulDrive(turn) -180
	    }
	    TurnObject $SimulDrive(turn) $SimulDrive(wheel)
	}
    }
    return
}

proc SimulDriveStart {} {
    # fix the initial point and start the simulation
    global SimulDrive Number WPPosn WPDatum RealTimeLogOn MESS

    if { $Number(WP) == 0 } {
	GMMessage $MESS(needWP)
	return
    }
    if { [set ix [ChooseItems WP]] == "" } { return }
    set ix [lindex $ix 0]
    set p $WPPosn($ix)
    if { $WPDatum($ix) != "WGS 84" } {
	set p [ToDatum [lindex $p 0] [lindex $p 1] \
		   $WPDatum($ix) "WGS 84"]
    } else { set p [lrange $p 0 1] }
    foreach x "trk turn speed" { set SimulDrive($x) 0 }
    TurnObject 0 $SimulDrive(wheel)
    set SimulDrive(secs) [clock seconds]
    set date [clock format $SimulDrive(secs) -format "%Y %m %d %H %M %S"]
    regsub -all { 0([0-9])} $date { \1} date
    set SimulDrive(data) [list $date $p simul {_ _ _} _ {0 0 0} _ _ _ 0]
    if { $RealTimeLogOn } { UseRealTimeData $SimulDrive(data) }
    after 1000 SimulDrivePVT
    return
}

proc SimulDrivePVT {} {
    # generate a fake PVT data list and a call to proc UseRealTimeData
    # the previous data list is changed in answer to the user actions
    global SimulDrive RealTimeLogOn DSCALE

    if { $SimulDrive(data) != "" } {
	foreach "date pos fix errv alt velv" $SimulDrive(data) { break }
	set s [clock seconds]
	set dt [expr $s-$SimulDrive(secs)]
	set SimulDrive(secs) $s
	set date [clock format $s -format "%Y %m %d %H %M %S"]
	regsub -all { 0([0-9])} $date { \1} date
	if { $SimulDrive(speed) == 0 } {
	    # update time and velocity vector only
	    set SimulDrive(data) [lreplace $SimulDrive(data) 0 0 $date]
	    set SimulDrive(data) [lreplace $SimulDrive(data) 5 5 {0 0 0}]
	} else {
	    foreach "lat long" $pos { break }
	    foreach "vx vy vz" $velv {}
	    set SimulDrive(trk) \
		    [expr round($SimulDrive(trk)-$SimulDrive(turn)/6.0)%360]
	    set a [expr $SimulDrive(trk)*0.01745329251994329576]
	    # m/s
	    set sp [expr $SimulDrive(speed)/$DSCALE/3.6]
	    set vx [expr $sp*sin($a)] ; set vy [expr $sp*cos($a)]
	    # plane sailing formulas
	    set dlat [expr $vy*$dt/111120.0]
	    set coslat [expr cos($lat*0.01745329251994329576)]
	    if { $coslat < 1e-15 } {
		set dlong 0
	    } else { set dlong [expr $vx*$dt/111120.0/$coslat] }
	    set SimulDrive(data) [list $date \
		    [list [expr $lat+$dlat] [expr $long+$dlong]] simul $errv \
		    $alt [list $vx $vy $vz] $SimulDrive(trk) _ _ _]
	}
	if { $RealTimeLogOn } { UseRealTimeData $SimulDrive(data) }
	after 1000 SimulDrivePVT
    }
    return
}

proc SimulDriveOff {} {
    global SimulDrive

    destroy .simdrive
    set SimulDrive(data) ""
    return
}

