#
#  gpsman --- GPS Manager: a manager for GPS receiver data
#
# Copyright (c) 1998-2008 Miguel Filgueiras mig@ncc.up.pt Universidade do Porto
#
#    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 2 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: files.tcl
#  Last change:  30 August 2008
#
# See also files_foreign.tcl
#  and metadata.tcl for properties of known file formats
#
# Includes contributions by
#   - Brian Baulch (baulchb _AT_ onthenet.com.au) marked "BSB contribution"
#   - Matt Martin (matt.martin _AT_ ieee.org) marked "MGM contribution"
#   - Valere Robin (valere.robin _AT_ wanadoo.fr) marked "VR contribution"
#

array set FCOMMAND {
    format  "!Format:"
    datum   "!Datum:"
    pformat "!Position:"
    pfdatum "!PFDatum:"
    dates   "!Creation:"
    0   no
    1   yes
    WP   "!W:"
    RT   "!R:"
    RS   "!RS:"
    TR   "!T:"
    TS   "!TS:"
    LN   "!LN:"
    LS   "!LS:"
    LAP  "!LAP:"
    GR   "!G:"
    GRWP   "!GW:"
    GRRT   "!GR:"
    GRTR   "!GT:"
    GRLN   "!GL:"
    GRLAP  "!GLAP:"
    GRGR   "!GG:"
    nb   "!NB:"
    comment   "%"
    mapback    "!Image:"
    mapproj    "!Projection:"
    maptransf  "!Transf:"
    mapscale   "!Scale:"
    mapbackat  "!Image at:"
    mapbackcs  "!Image coords:"
    maporigin  "!Origin:"
    mapinfo    "!Map:"
}

set FCOMMARGS {RT RS TR LN LAP GR GRWP GRRT GRTR GRLN GRLAP GRGR nb format
               pformat pfdatum datum dates mapback mapproj maptransf
               maporigin mapscale mapbackat mapbackcs}

  # attribute-value pairs:
  #    name of attribute, data array name, var for default value, and proc
  #    to check value (Ignore if appropriate; proc should return 1 on error
  #    and will be called with the value as argument)
array set FATTRPAIRS {
    WP {{symbol WPSymbol DEFAULTSYMBOL BadSymbol}
        {dispopt WPDispOpt DEFAULTDISPOPT BadDispOpt}
        {alt WPAlt EMPTYSTR BadAltitude}
	{mapbak WPMBack EMPTYSTR Ignore}}
    RT {{width RTWidth DEFTRTWIDTH BadWidth}
        {colour RTColour DEFTRTCOLOUR BadColour}
	{mapbak RTMBack EMPTYSTR Ignore}}
    TR {{width TRWidth DEFTTRWIDTH BadWidth}
        {colour TRColour DEFTTRCOLOUR BadColour}
	{mapbak TRMBack EMPTYSTR Ignore}}
    LN {{width LNWidth DEFTLNWIDTH BadWidth}
        {colour LNColour DEFTLNCOLOUR BadColour}
	{mapbak LNMBack EMPTYSTR Ignore}}
    LAP {}
}

# needing a global variable (not array) for default colours
set DEFTRTCOLOUR $MAPCOLOUR(RT)
set DEFTTRCOLOUR $MAPCOLOUR(TR)
set DEFTLNCOLOUR $MAPCOLOUR(LN)

##### saving

proc WriteHeader {file pformt} {
    # write header to file in GPSMan format, using $pformt for positions
    global TimeOffset Datum FCOMMAND CREATIONDATE \
	    SFilePFrmt SFileDatum SFileHeader MESS

    if { $SFileHeader($file) } {
	if { $SFilePFrmt($file) != $pformt } {
	    WriteChgPFormat $file $pformt
	}
	if { $SFileDatum($file) != $Datum } {
	    WriteChgDatum $file $Datum
	}	
    } else {
	puts $file "$FCOMMAND(comment) $MESS(written) GPSManager [NowTZ]"
	puts $file "$FCOMMAND(comment) $MESS(editrisk)"
	puts $file ""
	puts $file "$FCOMMAND(format) $pformt $TimeOffset $Datum"
	puts $file "$FCOMMAND(dates) $FCOMMAND($CREATIONDATE)"
	puts $file ""
	set SFilePFrmt($file) $pformt
	set SFileDatum($file) $Datum
	set SFileHeader($file) 1
    }
    return
}

proc WriteChgPFormat {file pformt} {
    # write command for changing position format to file in GPSMan format
    global FCOMMAND SFilePFrmt

    puts $file "$FCOMMAND(pformat) $pformt"
    set SFilePFrmt($file) $pformt
    return
}

proc WriteChgDatum {file datum} {
    # write command for changing datum to file in GPSMan format
    global FCOMMAND SFilePFrmt SFileDatum

    puts $file "$FCOMMAND(datum) $datum"
    set SFileDatum($file) $datum
    return
}

proc FormatPosn {posn type} {
    # from position to string under given format type

    switch $type {
	latlong { return [format "%s\t%s" [lindex $posn 2] [lindex $posn 3]] }
	utm {
	    return [format "%s\t%s\t%s\t%s" [lindex $posn 2] \
		      [lindex $posn 3] [lindex $posn 4] [lindex $posn 5]] 
	}
	grid {
	    return [format "%s\t%s\t%s" [lindex $posn 2] \
		      [lindex $posn 3] [lindex $posn 4]]
	}
	nzgrid {
	    return [format "%s\t%s" [lindex $posn 2] [lindex $posn 3]]
	}
	mh {
	    return [lindex $posn 2]
	}
    }
}

proc SaveNB {file obs} {
    # write NB data to file in GPSMan format
    global FCOMMAND

    if { $obs != "" } {
	puts $file "$FCOMMAND(nb)\t$obs"
	puts $file ""
    }
    return
}

proc WriteAttrPairs {file always wh ix hidden} {
    # write attribute-value pairs and hidden information for item
    #  with index $ix
    #  $wh in {WP, RT, TR, LN}
    #  $always is a flag set when the pairs must be always written out,
    #      otherwise they will be written only if they differ from their
    #      default value
    # no newline is written!
    global FATTRPAIRS
    
    foreach fd $FATTRPAIRS($wh) {
	set an [lindex $fd 1] ; set dv [lindex $fd 2]
	global $an $dv
	set x [set [set an]($ix)]
	if { $always || $x != [set $dv] } {
	    puts -nonewline $file "\t[lindex $fd 0]=$x"
	}
    }
    foreach h $hidden {
	puts -nonewline $file "\t$h"
    }
    return
}

proc WriteWPsRSs {file ixs stages} {
    # write WPs with indices in list $ixs and RT $stages (may be void) to
    #  file in GPSMan format
    global WPName WPCommt WPObs WPPFrmt WPPosn WPDatum WPDate WPHidden \
	    CREATIONDATE SFilePFrmt SFileDatum POSTYPE

    foreach i $ixs st $stages {
	if { [SlowOpAborted] } { return }
	if { $i != -1 } {
	    if { $WPPFrmt($i) != $SFilePFrmt($file) } {
		WriteChgPFormat $file $WPPFrmt($i)
	    }
	    if { $WPDatum($i) != $SFileDatum($file) } {
		WriteChgDatum $file $WPDatum($i)
	    }
	    set p [FormatPosn $WPPosn($i) $POSTYPE($SFilePFrmt($file))]
	    if { $CREATIONDATE } {
		puts -nonewline $file \
			"$WPName($i)\t$WPCommt($i)\t$WPDate($i)\t$p"
	    } else {
		puts -nonewline $file "$WPName($i)\t$WPCommt($i)\t$p"
	    }
	    WriteAttrPairs $file 0 WP $i $WPHidden($i)
	    puts $file ""
	    SaveNB $file $WPObs($i)
	} else {
	    puts $file "UNDEF"
	}
	if { $st != "" } {
	    WriteRTStage $file $st
	}
    }
    puts $file ""
    return
}

proc WriteRTStage {file stage} {
    # write a non-empty RT stage to file in GPSMan format
    global FCOMMAND DataIndex

    set c [lindex $stage $DataIndex(RScommt)]
    set l [lindex $stage $DataIndex(RSlabel)]
    puts -nonewline $file "$FCOMMAND(RS)\t$c\t$l"
    foreach h [lindex $stage $DataIndex(RShidden)] {
	puts -nonewline $file "\t$h"
    }
    puts $file ""
    return
}

proc JustfLeft {n string} {
    # justify string left to get a $n-characters string 
    set k [expr $n-[string length $string]]
    if { $k > 0 } {
	set sp [format "%${k}s" " "]
	return [append string $sp]
    }
    return $string
}

proc SaveWP {file ixs} {
    # save data for WPs with indices in list $ixs to file in GPSMan format
    global FCOMMAND PositionFormat

    WriteHeader $file $PositionFormat
    puts $file $FCOMMAND(WP)
    WriteWPsRSs $file $ixs ""
    return
}

proc SaveRT {file ixs} {
    # save data for RTs with indices in list $ixs to file in GPSMan format
    global FCOMMAND RTIdNumber RTCommt RTObs RTWPoints RTStages \
	    PositionFormat MESS

    WriteHeader $file $PositionFormat
    foreach i $ixs {
	if { [SlowOpAborted] } { return }
	set wpixs [Apply "$RTWPoints($i)" IndexNamed WP]
	if { [Undefined $wpixs] } {
	    GMMessage [format $MESS(undefWP) $RTIdNumber($i)]
	} else {
	    puts -nonewline $file \
		    "$FCOMMAND(RT)\t$RTIdNumber($i)\t$RTCommt($i)"
	    WriteAttrPairs $file 1 RT $i ""
	    puts $file ""
	    SaveNB $file $RTObs($i)
	    WriteWPsRSs $file $wpixs $RTStages($i)
	}
    }
    return
}

proc SaveTR {file ixs} {
    # save data for TRs with indices in list $ixs to file in GPSMan format
    global FCOMMAND TRName TRObs TRDatum TRTPoints TRSegStarts TRHidden \
	    DataIndex SFileDatum

    WriteHeader $file DMS
    set ilt $DataIndex(TPlatDMS) ; set ilg $DataIndex(TPlongDMS)
    set idt $DataIndex(TPdate)
    set ial $DataIndex(TPalt) ; set idp $DataIndex(TPdepth)
    foreach i $ixs {
	if { [SlowOpAborted] } { return }
	if { $TRDatum($i) != $SFileDatum($file) } {
	    WriteChgDatum $file $TRDatum($i)
	}
	puts -nonewline $file [format "%s\t%s" $FCOMMAND(TR) $TRName($i)]
	WriteAttrPairs $file 1 TR $i $TRHidden($i)
	puts $file ""
	SaveNB $file $TRObs($i)
	set ssts $TRSegStarts($i)
	set tpn 0 ; set nsst [lindex $ssts 0]
	foreach tp $TRTPoints($i) {
	    if { [SlowOpAborted] } { return }
	    if { $nsst == $tpn } {
		puts $file $FCOMMAND(TS)
		set ssts [lreplace $ssts 0 0]
		set nsst [lindex $ssts 0]
	    }
	    incr tpn
	    set alt [lindex $tp $ial] ; set dep [lindex $tp $idp]
	    if { $dep == "" } {
		if { $alt == "" } {
		    puts $file [format "\t%s\t%s\t%s" [lindex $tp $idt] \
			    [lindex $tp $ilt] [lindex $tp $ilg]]
		} else {
		    puts $file [format "\t%s\t%s\t%s\t%s" [lindex $tp $idt] \
			    [lindex $tp $ilt] [lindex $tp $ilg] $alt]
		}
	    } else { 
		puts $file [format "\t%s\t%s\t%s\t%s\t%s" [lindex $tp $idt] \
			[lindex $tp $ilt] [lindex $tp $ilg] $alt $dep]
	    }
	}
	puts $file ""
    }
    return
}

proc SaveLN {file ixs} {
    # save data for LNs with indices in list $ixs to file in GPSMan format
    global FCOMMAND LNName LNObs LNDatum LNPFrmt LNLPoints LNSegStarts \
	    LNWidth LNColour DataIndex SFileDatum SFilePFrmt

    WriteHeader $file $LNPFrmt([lindex $ixs 0])
    set iposn $DataIndex(LPposn) ; set ialt $DataIndex(LPalt)
    foreach i $ixs {
	if { [SlowOpAborted] } { return }
	if { $LNDatum($i) != $SFileDatum($file) } {
	    WriteChgDatum $file $LNDatum($i)
	}
	if { $LNPFrmt($i) != $SFilePFrmt($file) } {
	    WriteChgPFormat $file $LNPFrmt($i)
	}
	puts -nonewline $file [format "%s\t%s" $FCOMMAND(LN) $LNName($i)]
	WriteAttrPairs $file 1 LN $i ""
	puts $file ""
	SaveNB $file $LNObs($i)
	set ssts $LNSegStarts($i)
	set lpn 0 ; set nsst [lindex $ssts 0]
	foreach lp $LNLPoints($i) {
	    if { [SlowOpAborted] } { return }
	    if { $nsst == $lpn } {
		puts $file $FCOMMAND(LS)
		set ssts [lreplace $ssts 0 0]
		set nsst [lindex $ssts 0]
	    }
	    incr lpn
	    puts $file "\t[lindex $lp $iposn]\t[lindex $lp $ialt]"
	}
	puts $file ""
    }
    return
}

proc SaveLAP {file ixs} {
    # save data for LAPs with indices in list $ixs to file in GPSMan format
    global FCOMMAND LAPName LAPObs LAPDur LAPDist LAPBegPosn LAPEndPosn \
	LAPCals LAPTRIx LAPPFrmt LAPDatum SFileDatum SFilePFrmt

    WriteHeader $file $LAPPFrmt([lindex $ixs 0])
    foreach i $ixs {
	if { [SlowOpAborted] } { return }
	if { $LAPDatum($i) != $SFileDatum($file) } {
	    WriteChgDatum $file $LAPDatum($i)
	}
	if { $LAPPFrmt($i) != $SFilePFrmt($file) } {
	    WriteChgPFormat $file $LAPPFrmt($i)
	}
	puts -nonewline $file [format "%s\t%s\t" $FCOMMAND(LAP) $LAPName($i)]
	puts -nonewline $file "$LAPDur($i)\t$LAPDist($i)\t"
	puts -nonewline $file "[lrange $LAPBegPosn($i) 2 end]\t"
	puts -nonewline $file "[lrange $LAPEndPosn($i) 2 end]\t"
	puts $file "$LAPCals($i)\t$LAPTRIx($i)"
	SaveNB $file $LAPObs($i)
    }
    return
}

proc SaveGR {file ixs} {
    # save data for GRs with indices in list $ixs to file in GPSMan format
    global SFileHeader TYPES SUPPORTLAPS FCOMMAND MESS GRName GRObs GRConts

    set gtypes $TYPES
    if { $SUPPORTLAPS } { lappend gtypes LAP }
    foreach wh $gtypes {
	set l [GRsElements $ixs 1 $wh]
	if { $wh != "GR" } {
	    if { $l != "" } {
		Save$wh $file $l
		if { [SlowOpAborted] } { return }
	    }
	} else { set nixs $l }
    }
    if { ! $SFileHeader($file) } {
	puts $file "$FCOMMAND(comment) $MESS(written) GPSManager [NowTZ]"
	puts $file ""
    }
    foreach i $nixs {
	puts $file [format "%s\t%s" $FCOMMAND(GR) $GRName($i)]
	SaveNB $file $GRObs($i)
	foreach p $GRConts($i) {
	    if { [SlowOpAborted] } { return }
	    set c $FCOMMAND(GR[lindex $p 0])
	    foreach e [lindex $p 1] {
		puts $file [format "%s\t%s" $c "$e"]
		set c ""
	    }
	}
	puts $file ""
    }
    return
}

proc SaveWPDistBear {file w} {
    # save distances and bearings between WPs from window $w

    puts $file [$w.fr.fromto cget -text]
    puts $file ""
    puts $file [$w.fr.fr1.dist cget -text]
    puts $file [$w.fr.fr1.bear cget -text]
    puts $file ""
    return
}

proc SaveWPNearest {file w} {
    # save nearest WPs information from window $w
    global MESS

    puts $file [$w.fr.from cget -text]
    puts $file ""
    puts $file $MESS(WPNearest)
    set fb $w.fr.fr1.frbx
    foreach n [$fb.bxn get 0 end] d [$fb.bxd get 0 end] b [$fb.bxb get 0 end] {
	if { [SlowOpAborted] } { return }
	puts $file "$n\t$d\t$b"
    }
    puts $file ""   
    return
}

proc SaveRTComp {file w} {
    # save results of RT computation from window $w
    global MESS

    puts $file [$w.fr.fr1.ntitle cget -text]
    puts $file $MESS(RTcomp)
    set w $w.fr.fr3.fr31
    foreach n [$w.frbx.bxn get 0 end] \
	    p [$w.frbx.box get 0 end] \
	    d [$w.frbx.bxd get 0 end] \
	    b [$w.frbx.bxb get 0 end] \
	    da [$w.frbx.bxda get 0 end] \
	    sc [$w.frbx.bxsc get 0 end] \
	    sl [$w.frbx.bxsl get 0 end] {
	if { [SlowOpAborted] } { return }
	puts $file "$n\t$p\t$d\t$b\t$da\t$sc\t$sl"
    }
    puts $file [$w.frt.tt cget -text]
    puts $file ""
    return
}

proc SaveTRComp {file w} {
    # save results of TR computation from window $w
    global MESS

    puts $file [$w.fr.fr1.ntitle cget -text]
    puts $file $MESS(TRcomp)
    set w $w.fr.fr3
    foreach n [$w.frbx.bxn get 0 end] \
	    d [$w.frbx.bxd get 0 end] \
	    la [$w.frbx.bxlat get 0 end] \
	    lo [$w.frbx.bxlong get 0 end] \
	    a [$w.frbx.bal get 0 end] \
	    l [$w.frbx.bxl get 0 end] \
	    tl [$w.frbx.btl get 0 end] \
	    dt [$w.frbx.bdt get 0 end] \
	    s [$w.frbx.bsp get 0 end] \
	    b [$w.frbx.bbg get 0 end] {
	if { [SlowOpAborted] } { return }
	puts $file "$n\t$d\t$la\t$lo\t$a\t$l\t$tl\t$dt\t$s\t$b"
    }
    foreach k "frt.td frt.tt" {
	puts $file [$w.$k cget -text]
    }
    if { [winfo exists $w.frtng] } {
	foreach k "frtng.td frtng.tt" {
	    puts $file [$w.$k cget -text]
	}
    }
    foreach k "frsp.avg frsp.max frsp.min" {
	puts $file [$w.$k cget -text]
    }
    if { [winfo exists $w.frmxnalt] } {
	foreach k "frmxnalt.mx frmxnalt.mn frcad.cumul frcad.cumuld" {
	    puts $file [$w.$k cget -text]
	}
    }
    puts $file "[$w.frd0.toend cget -text] [$w.frd0.max cget -text]"
    puts $file ""
    return
}

proc SavePVTData {file args} {
    # save real-time log (Garmin PVT) information
    global TXT

    puts $file $TXT(realtimelog)
    set fx .pvt.fri.frtbx
    foreach bname "d lat long alt fix epe eph epv velx vely velz trk" {
	puts -nonewline $file "[$fx.tit$bname cget -text]\t"
	set $bname [$fx.bx$bname get 0 end]
    }
    puts $file "" ; puts $file ""
    foreach xd $d xlat $lat xlong $long xalt $alt xfix $fix xepe $epe \
	    xeph $eph xepv $epv xvelx $velx xvely $vely xvelz $velz xtrk $trk {
	if { [SlowOpAborted] } { return }
	puts $file "$xd\t$xlat\t$xlong\t$xalt\t$xfix\t$xepe\t$xeph\t$xepv\t$xvelx\t$xvely\t$xvelz\t$xtrk"
    }
    puts $file ""
    return
}

proc SaveMapBkInfo {file args} {
    # save map background information
    #  $args: list with path to image file, projection data, transformation
    #    data, scale, and data on subsidary images
    # projection and transformation data are lists with a name and a sequence
    #  of attribute=value pairs
    # data on subsidary images consists of 2 lists of pairs: one with grid
    #  coordinates and path, and the other one with canvas coordinates of NW
    #  corner and path
    global Datum FCOMMAND MESS

    set args [lindex $args 0]
    puts $file "$FCOMMAND(comment) $MESS(written) GPSManager [NowTZ]"
    puts $file "$FCOMMAND(comment) $MESS(edityourrisk)"
    puts $file ""
    puts $file "$FCOMMAND(mapback) [lindex $args 0]"
    puts $file "$FCOMMAND(datum) $Datum"
    set pd [lindex $args 1]
    puts -nonewline $file "$FCOMMAND(mapproj) [lindex $pd 0]"
    foreach p [lrange $pd 1 end] {
	puts -nonewline $file "\t$p"
    }
    puts $file ""
    set pt [lindex $args 2]
    puts -nonewline $file "$FCOMMAND(maptransf) [lindex $pt 0]"
    foreach p [lrange $pt 1 end] {
	puts -nonewline $file "\t$p"
    }
    puts $file ""
    puts $file "$FCOMMAND(mapscale) [lindex $args 3]"
    puts $file ""
    foreach ixsp [lindex $args 4] {
	puts $file "$FCOMMAND(mapbackat) [lindex $ixsp 0] [lindex $ixsp 1]"
    }
    puts $file ""
    foreach csp [lindex $args 5] {
	puts $file "$FCOMMAND(mapbackcs) [lindex $csp 0] [lindex $csp 1]"
    }
    puts $file ""
    return
}

proc SaveMapInfo {file args} {
    # save map datum, projection, transformation, grid and scale
    #  $args is list with projection data, transformation data, position
    #    format of coordinates, datum for coordinates and scale
    # projection and transformation data are lists with a name and a sequence
    #  of attribute=value pairs
    global Datum FCOMMAND MESS

    set args [lindex $args 0]
    puts $file "$FCOMMAND(comment) $MESS(written) GPSManager [NowTZ]"
    puts $file "$FCOMMAND(comment) $MESS(edityourrisk)"
    puts $file ""
    puts $file $FCOMMAND(mapinfo)
    puts $file "$FCOMMAND(datum) $Datum"
    set pd [lindex $args 0]
    puts -nonewline $file "$FCOMMAND(mapproj) [lindex $pd 0]"
    foreach p [lrange $pd 1 end] {
	puts -nonewline $file "\t$p"
    }
    puts $file ""
    set pt [lindex $args 1]
    puts -nonewline $file "$FCOMMAND(maptransf) [lindex $pt 0]"
    foreach p [lrange $pt 1 end] {
	puts -nonewline $file "\t$p"
    }
    puts $file ""
    puts $file "$FCOMMAND(pformat) [lindex $args 2]"
    puts $file "$FCOMMAND(pfdatum) [lindex $args 3]"
    puts $file "$FCOMMAND(mapscale) [lindex $args 4]"
    puts $file ""
    return
}

proc SaveGREls {how args} {
    # save elements of group to file in GPSMan format
    # if file != "stdout" file will be closed at the end
    #  $how==all: all elements in all groups
    #  $how==select: groups and element types chosen from list
    #  $args not used but in call-back
    global GRName SFilePFrmt SFileDatum SFileHeader TYPES SUPPORTLAPS MESS TXT

    set gtypes $TYPES
    if { $SUPPORTLAPS } { lappend gtypes LAP }
    switch $how {
	all {
	    set ixs [array names GRName]
	    set whs $gtypes
	}
	select {
	    if { [set ixs [ChooseItems GR]] == "" } { return }
	    set ts ""
	    foreach k $gtypes { lappend ts $TXT(name$k) }
	    if { [set whs \
		    [GMChooseFrom many $MESS(putwhat) 6 $ts $gtypes]] == "" } {
	       return
	   }
	}
    }
    set f [GMOpenFile $TXT(saveto) GR w]
    if { $f != ".." } {
	SlowOpWindow $TXT(save)
	set SFileHeader($f) 0
	if { [set i [lsearch -exact $whs GR]] != -1 } {
	    set whs [lreplace $whs $i $i]
	    set r 1
	} else { set r 0 }
	foreach wh $whs {
	    if { [set l [GRsElements $ixs $r $wh]] != "" } {
		Save${wh} $f $l
	    }
	}
	catch {
	    unset SFilePFrmt($f) ; unset SFileDatum($f)
	    unset SFileHeader($f)
	}
	if { $f != "stdout" } { close $f }
	SlowOpFinish ""
    }
    return
}

proc SaveFile {how what args} {
    # save information to file in GPSMan format
    #  $how==comp: computation results;
    #            $what in {WPDistBear, WPNearest, RTComp, TRComp, PVTData}
    #            $args: normally the window containing the information; see
    #              procs Save$what
    #  $how==all: all items of type $what
    #            $what in $TYPES or $what=="LAP" or $what=="Data"
    #  $how==state: as "all" but without slow operation dialog
    #  $how==select: items of type $what chosen from list
    #            $what in $TYPES or $what=="LAP"
    #  $how==mapback: map background image information
    #            (used when saving state: no slow operation dialog)
    #            $what: MapBkInfo
    #            $args: see proc SaveMapBkInfo
    #  $how==mapinfo: map projection, transformation, position format and scale
    #            (used when saving state: no slow operation dialog)
    #            $what: MapInfo
    #            $args: see proc SaveMapInfo

    eval [list SaveFileTo {} $how $what $args]
    return
}

proc SaveFileTo {f how what args} {
    # save information to file $f in GPSMan format
    # if $f=="" ask user to select output file
    # if $f!="stdout" file will be closed at the end
    # see proc SaveFile for the description of the arguments
    global SFilePFrmt SFileDatum SFileHeader TXT TYPES SUPPORTLAPS File

    set stypes $TYPES
    if { $SUPPORTLAPS } { lappend stypes LAP }
    if { $how == "state" } {
	set slow 0
    } else { set slow 1 }
    switch $how {
	all -  state {
	    set lp [AllIndicesForType $what $stypes]
	}
	select {
	    if { [set ixs [ChooseItems $what]] == "" } { return }
	    set lp [list [list $what $ixs]]
	}
	comp { }
	mapinfo -  mapback { set slow 0 }
    }
    set oldf $f
    if { $f != "" || [set f [GMOpenFile $TXT(saveto) $what w]] != ".." } {
	if { $slow } {
	    SlowOpWindow $TXT(save)
	} else { SetCursor . watch }
	set SFileHeader($f) 0
	switch $how {
	    comp -  mapinfo {
		Save${what} $f $args
	    }
	    mapback {
		Save${what} $f $args
		if { $oldf == "" } {
		    Define backgrnd "" $File(MapBkInfo) ""
		}
	    }
	    default {
		foreach p $lp {
		    Save[lindex $p 0] $f [lindex $p 1]
		}
	    }
	}
	catch {
	    unset SFilePFrmt($f) ; unset SFileDatum($f)
	    unset SFileHeader($f)
	}
	if { $f != "stdout" } { close $f }
	if { $slow } {
	    SlowOpFinish ""
	} else { ResetCursor . }
    }
    return
}

## saving/restoring state

proc SaveState {} {
    # save current state of interface and current data
    # slow operation dialog not used
    global USvState USvData USvMap MESS Number MapEmpty Map TYPES EdWindow \
	    MapPFormat MapPFDatum MapPFNeedsDatum MapFont Travelling Travel \
	    GMEd SUPPORTLAPS File

    set stypes $TYPES
    if { $SUPPORTLAPS } { lappend stypes LAP }
    set todispl "" ; set toopen ""
    if { $Number(Data) != 0 } {
	if { [catch {set f [open $USvData w]}] } {
	    GMMessage [format $MESS(cantwrtsstate) $USvData]
	    return
	}
	set saveddata $USvData
	SaveFileTo $f state Data
	foreach wh $stypes {
	    global ${wh}Displ

	    set ns "" ; set os ""
	    foreach ix [array names ${wh}Displ] {
		set name [NameOf $wh $ix]
		if { [set [set wh]Displ($ix)] } {
		    lappend ns $name
		}
		if { [winfo exists .gm${wh}sh$ix] } {
		    lappend os $name
		}
	    }
	    if { [winfo exists $EdWindow($wh)] && $GMEd($wh,Index) != -1 } {
		set os [linsert $os 0 [NameOf $wh $GMEd($wh,Index)]]
	    }
	    if { $ns != "" } { lappend todispl [list $wh $ns] }
	    if { $os != "" } { lappend toopen [list $wh $os] }
	}
    } else { set saveddata "" }
    if { ! $MapEmpty } {
	if { [catch {set f [open $USvMap w]}] } {
	    GMMessage [format $MESS(cantwrtsstate) $USvMap]
	    return
	}
	set savedmap $USvMap
	if { [$Map find withtag mapimage] != "" } {
	    set mapfile Back
	    SaveMapBack $f
	} else {
	    set mapfile Params
	    SaveMapParams $f
	}
    } else { set savedmap "" }
    if { [catch {set f [open $USvState w]}] } {
	GMMessage [format $MESS(cantwrtsstate) $USvState]
	return
    }
    puts $f ""
    puts $f "# $MESS(written) GPSManager [NowTZ]"
    puts $f "# $MESS(editrisk)"
    puts $f ""
    puts $f "set saveddata \"$saveddata\""
    puts $f "set todispl \{$todispl\}"
    puts $f "set toopen \{$toopen\}"
    puts $f ""
    puts $f "set savedmap \"$savedmap\""
    if { $savedmap != "" } {
	puts $f "set mapfile \"$mapfile\""
	puts $f "set mappformat \"$MapPFormat\""
	puts $f "set mappfdatum \"$MapPFDatum\""
	puts $f "set mappfndatum \"$MapPFNeedsDatum\""
	puts $f "set mapfont \"$MapFont\""
    }
    puts $f ""
    puts $f "set Travelling \"$Travelling\""
    puts $f "array set Travel \{"
    foreach p $Travel(tosave) {
	foreach n [array names Travel $p] {
	    puts $f "   $n \{$Travel($n)\}"
	}
    }
    puts $f "\}"
    puts $f ""
    puts $f "array set File \{"
    foreach n [array names File] {
	puts $f "   $n \{$File($n)\}"
    }
    puts $f "\}"
    puts $f ""
    # must be the last instruction:
    puts $f "if \{ \[catch \{cd \"[pwd]\"\}\] \} \{"
    puts $f "  set saved \"[pwd]\""
    puts $f "\} else \{ set saved 1 \}"
    puts $f ""
    close $f
    return
}

proc RestoreState {} {
    # restore saved state and clear it if the user agrees
    global USvState MESS CREATIONDATE Proc DELSTATE MapPFormat Map MapFont \
	    Travelling Travel MapEmpty ASKPROJPARAMS Datum MapPFDatum \
	    MapPFNeedsDatum File

    if { [file size $USvState] == 0 } { return }
    # assumed to be in system encoding
    source $USvState
    if { [catch {set saved}] } {
	GMMessage [format $MESS(corruptsstate) $USvState]
	return
    }
    if { $saved != 1 } {
	GMMessage [format $MESS(couldntcd) $saved]
    }
    if { $DELSTATE == "always" || \
	    ( $DELSTATE == "ask" && [GMConfirm $MESS(delsstate)] ) } {
	set del 1
    } else { set del 0 }
    if { $savedmap != "" } {
	if { $mapfile == "Back" } {
	    set r [LoadMapFixedBk $savedmap]
	    if { $r == -1 } { return }
	    if { [lindex $r 0] != 1 } {
		GMMessage "$MESS(badfile): $savedmap"
		return
	    }
	    set MapEmpty 0
	    eval LoadMapBackGeoRef [lrange $r 1 end]
	} else {
	    # $mapfile == "Params"
	    set r [LoadMapInfo $savedmap]
	    if { $r == -1 } { return }
	    eval LoadMapParams $r
	}
	set MapPFormat $mappformat
	# compatibility with 6.1.2 and older versions
	if { [catch {set mappfdatum}] } {
	    set MapPFDatum $Datum
	    ChangeMapPFormat $MapPFormat
	} else {
	    set MapPFDatum $mappfdatum ; set MapPFNeedsDatum $mappfndatum
	}
	if { ! [catch {set mapfont}] } { set MapFont $mapfont }
	if { $del } { file delete -- $savedmap }
    }
    if { $saveddata != "" } {
	if { [catch {set f [open $saveddata r]}] } {
	    GMMessage [format $MESS(cantrdsstate) $saveddata]
	    return
	}
	if { [LoadFileFrom $f Data] == 0 } { return }
	if { $del } { file delete -- $saveddata }
    }
    set a $ASKPROJPARAMS ; set ASKPROJPARAMS 0
    foreach p $todispl {
	set wh [lindex $p 0]
	foreach n [lindex $p 1] {
	    if { [set ix [IndexNamed $wh $n]] != -1 } {
		PutMap $wh $ix
	    }
	}
    }
    set ASKPROJPARAMS $a
    foreach p $toopen {
	set wh [lindex $p 0]
	if { $wh == "LAP" } {
	    set opts "change revert forget cancel"
	} else { set opts "change revert create forget cancel" }
	foreach n [lindex $p 1] {
	    if { [set ix [IndexNamed $wh $n]] != -1 } {
		$Proc($wh) $ix $opts [ItemData $wh $ix]
		set opts ""
	    }
	}
    }
    if { $del } { file delete -- $USvState }
    # delete logo
    $Map delete dummy
    if { $Travelling } {
	set Travelling 0
	ToTravel
    }
    return
}

## saving canvas to Postscript or other format

proc SaveCanvas {cv bounds fmt dest} {
    # save canvas $c to file or printer under format $fmt
    #  $bounds is bounding box of visible part of canvas
    #    (should be set to "" if there is no need for it)
    #  $fmt is either PS, or in $ImgOutFormats (if the Img library is loaded)
    #    and probably will be PS if $dest==printer
    #  $dest in {file, printer}
    # items with tag "temp" can be created in and deleted from $cv
    # if $cv==$Map inclusion of map scale is an option
    # when printing, a temporary file named "print.tmp" is created in the
    #  user GPSMan-data directory
    global PRINTCMD PAPERSIZE PAGEWIDTH PAGEHEIGHT GFMode GFRot \
	GFPaper GFLegend GFVisible GFScale GFPrintOption MESS TXT MAPCOLOUR \
	Map MpW MAPSCLENGTH USERDIR MapFont FixedFont SHOWFILEITEMS

    set leg "=$TXT(legend)"
    set cms [list $TXT(colour) $TXT(grey) $TXT(mono)]
    set GFVisible $SHOWFILEITEMS ; set GFMode $TXT(colour)
    switch $fmt {
	PS {
	    set vs "GFLegend GFMode GFRot GFPaper"
	    set pps [array names PAGEWIDTH]
	    if { [set i [lsearch -exact $pps $PAPERSIZE]] != 0 } {
		set pps [linsert [lreplace $pps $i $i] 0 $PAPERSIZE]
	    }
	    set ds [list $leg $cms [list $TXT(portr) $TXT(landsc)] $pps]
	}
	jpeg {
	    set vs "GFLegend GFMode"
	    set ds [list $leg [lreplace $cms 2 2]]
	}
	default {
	    set vs "GFLegend"
	    set ds [list $leg]
	}
    }
    if { $bounds != "" && $fmt == "PS" } {
	set vs [linsert $vs 1 GFVisible]
	set ds [linsert $ds 1 "@$TXT(psvisible)"]
    }
    if { $cv == $Map } {
	set GFScale 1
	set vs [linsert $vs 1 GFScale]
	set ds [linsert $ds 1 "@$TXT(incscale)"]
	set font $MapFont
    } else {
	set GFScale 0
	set font $FixedFont
    }
    switch $dest {
	file {
	    set fn [GMGetFileName $TXT(saveto) Plot w $vs $ds]
	}
	printer {
	    if { $PRINTCMD == "" } {
		GMMessage $MESS(noprintcmd)
		return
	    }
	    lappend vs GFPrintOption
	    lappend ds "=$TXT(printopt)"
	    if { ! [GMChooseParams $TXT(print) $vs $ds] } { return }
	    set fn [file join $USERDIR print.tmp]
	}
    }
    if { $fn != ".." } {
	SetCursor . watch
	foreach m $cms tm "color gray mono" {
	    if { $GFMode == $m } {
		set GFMode $tm
		break
	    }
	}
	if { $GFVisible } {
	    set mbs $bounds
	} else { set mbs [$cv bbox all] }
	foreach "x0 y0 xf yf" $mbs { break }
	set sm [expr ($xf+$x0)/2.0]
	if { $GFScale } {
	    # only applies when $cv == $Map
	    set m $MAPSCLENGTH ; set m2 [expr $m/2.0]
	    scan [$MpW.frm.frmap3.fr3.cv.val cget -text] "%f %s" sc su
	    if { [set xs0 [expr $sm-$m2]] < $x0 } {
		set m $m2
		if { [set xs0 [expr $sm-$m/2.0]] < $x0 } {
		    set x0 $xs0
		}
		set sc [expr $sc/2.0]
		if { int($sc) != $sc } {
		    set sc [format "%.1f" $sc]
		} else { set sc [expr int($sc)] }
	    }
	    if { [set xsf [expr $xs0+$m]] > $xf } {
		set xf $xsf
	    }
	    if { $GFVisible } {
		set ys [expr $yf-100]
	    } else { set ys [expr $yf+60] ; set yf [expr $ys+10] }
	    set yd [expr $ys-7.5] ; set ylf [expr $ys+7.5]
	    $Map create line $xs0 $ys $xsf $ys -arrow both \
		    -fill $MAPCOLOUR(mapsel) -tags temp
	    $Map create line $xs0 $yd $xs0 $ylf -fill $MAPCOLOUR(mapsel) \
		    -tags temp
	    $Map create line $xsf $yd $xsf $ylf -fill $MAPCOLOUR(mapsel) \
		    -tags temp
            $Map create text $sm [expr $ys-8] -font $font \
		    -fill $MAPCOLOUR(mapsel) -text "$sc $su" -tags temp
	}
	if { $GFLegend != "" } {
	    if { $GFVisible } {
		set yt [expr $yf-60]
	    } else {
		set yt [expr $yf+20] ; set yf [expr $yt+5]
	    }
	    $cv create text $sm $yt -font $font \
		    -fill $MAPCOLOUR(mapleg) -text $GFLegend -tags temp
	}
	switch $fmt {
	    PS {
		set rot [expr [string compare $GFRot $TXT(portr)]!=0]
		set h [expr $yf-$y0+100] ; set w [expr $xf-$x0+100]
		if { $h > $w } {
		    set sc "-pageheight"
		    if { $rot } {
			set sca $PAGEWIDTH($GFPaper)
		    } else { set sca $PAGEHEIGHT($GFPaper) }
		} else {
		    set sc "-pagewidth"
		    if { $rot } {
			set sca $PAGEHEIGHT($GFPaper)
		    } else { set sca $PAGEWIDTH($GFPaper) }
		}
		if { $GFVisible } {
		    $cv postscript -file $fn -colormode $GFMode $sc $sca \
			    -rotate $rot
		} else {
		    $cv postscript -file $fn -width $w -height $h \
			    -colormode $GFMode $sc $sca -rotate $rot \
			    -x [expr $x0-50] -y [expr $y0-50]
		}
	    }
	    default {
		raise [winfo toplevel $cv]
		update
		after 500
		while { [catch {set im [image create photo -format window \
			                             -data $Map]}] } {
		    if { ! [GMConfirm $MESS(unobscmap)] } {
			ResetCursor .
			return
		    }
		    after 2000
		}
		if { $fmt == "jpeg" && $GFMode == "gray" } {
		    if { [catch {$im write $fn -format jpeg -grayscale}] } {
			GMMessage [format $MESS(cantwrtimg) $fmt]
		    }
		} else {
		    if { [catch {$im write $fn -format $fmt}] } {
			GMMessage [format $MESS(cantwrtimg) $fmt]
		    }
		}
		image delete $im
	    }
	}
	$cv delete temp
	if { $dest == "printer" } {
	    if { $GFPrintOption != "" } {
		exec $PRINTCMD $GFPrintOption [glob $fn]
	    } else { exec $PRINTCMD [glob $fn] }
	    file delete -- $fn
	}
	ResetCursor .
    }
    return
}

##### loading

proc OpenInputFileFails {what fmt} {
    # open file for loading/importing and set initial values
    #  $what in $TYPES or $what=="LAP" or $what=="Data"
    #  $fmt is such that "in" is in $FILEFORMAT($fmt,mode)
    # return 0 unless operation is to be cancelled
    global CREATIONDATE GFVisible TXT MESS FILEFORMAT SHOWFILEITEMS

    set params "" ; set parwidgts ""
    if { ! [catch {set pars $FILEFORMAT($fmt,in,params)}] } {
	set vrcvct "" ; set dvs $FILEFORMAT($fmt,in,pdefts)
	foreach p $pars dv $dvs {
	    if { [regexp {^global:(.+)$} $dv m dvv] } {
		global $dvv
		set dv [set $dvv]
	    }
	    set ff $FILEFORMAT($fmt,param,$p)
	    if { [llength $ff] < 4 } { BUG bad FILEFORMAT param }
	    foreach "var cv ct widgt" $FILEFORMAT($fmt,param,$p) { break }
	    global $var
	    set $var $dv
	    lappend params $var
	    # this needs the global TXT
	    lappend parwidgts [subst $widgt]
	    lappend vrcvct $var $cv $ct
	}
    } else { set pars "" }
    switch $fmt {
	GPSMan {
	    set for loadfrm ; set cr $CREATIONDATE
	}
	GPStrans {
	    # some code for loading GPSMan is used for importing GPStrans
	    set for importfrm ; set cr 1
	}
	default {
	    # incorporates MGM contribution
	    # incorporates VR contribution
	    set for importfrm ; set cr 0
	}
    }
    # mapping items is always an option
    set GFVisible $SHOWFILEITEMS
    lappend params GFVisible
    lappend parwidgts "@$TXT(mapitems)"
    while 1 {
	set f [GMOpenFileParms $TXT($for) $what r $params $parwidgts]
	if { $f == ".." } { return 1 }
	set ok 1
	if { $pars != "" } {
	    set vrctvl ""
	    foreach p $pars dv $dvs "var cv ct" $vrcvct {
		set val [set $var]
		if { [catch {eval $cv}] } {
		    set ms badconv ; set ok 0
		    break
		}
		lappend vrctvl $var $ct $val
	    }
	    if { $ok } {
		foreach p $pars dv $dvs "var ct val" $vrctvl {
		    if { [catch {set r [eval $ct]}] || $r == 0 } {
			set ms badprmval ; set ok 0
			break
		    }
		    set $var $val
		}
	    }
	}
	if { $ok } {
	    break
	}
	GMMessage [format $MESS($ms) $p $val] wait
	set $var $dv
    }
    InitInputFileVars $f $what $cr $GFVisible
    return 0
}

proc InitInputFileVars {f what cr vis} {
    # initialise input file $f global variables
    #  $what in $TYPES or $what=="LAP" or $what=="Data"
    #  $cr set if creation date is in use
    #  $vis set if must display items read in
    global LFileLNo LFileCreat LFileBuffFull LFileEOF LFileDefs LFileVisible \
	    LFileTimeOff LChannel TXT

    SlowOpWindow $TXT(fread)
    set LChannel($what) $f
    set LFileLNo($f) 0 ; set LFileCreat($f) $cr ; set LFileTimeOff($f) ""
    set LFileBuffFull($f) 0 ; set LFileEOF($f) 0
    set LFileDefs($f) 0 ; set LFileVisible($f) $vis
    return 0
}

proc CloseInputFile {what} {
    # close file opened for loading/importing and unset some global variables
    # if file is "stdin" it will not be closed
    #  $what in $TYPES or $what=="LAP"
    global LFilePFrmt LFileDatum LFileLNo LFileCreat \
	    LFileId LFileOther LFileBuff LFileBuffFull LFileEOF LFileDefs \
	    LFileVisible LFileTimeOff LChannel

    set f $LChannel($what)
    if { $f != "stdin" } { close $f }
    foreach v "LFilePFrmt($f) LFileDatum($f) LFileTimeOff($f) \
	    LFileLNo($f) LFileCreat($f) LFileDefs($f) LFileId($f) \
	    LFileOther($f) LFileBuff($f) LFileBuffFull($f) \
	    LFileEOF($f) LFileVisible($f) LChannel($what)" {
	catch { unset $v }
    }
    SlowOpFinish ""
    return
}

# encodings in Tcl

proc TclEncodingName {enc} {
    # tries to convert $enc to a Tcl encoding name
    global MESS

    set lenc [string tolower $enc]
    switch -glob -- $lenc {
	iso* {
	    regsub {^iso[-_]} $lenc iso enc
	}
	utf8 {
	    return utf-8
	}
	utf16* {
	    # not supported by Tcl 8.4
	    set enc utf-16
	}
	latin1 -  us-ascii {
	    return iso8859-1
	}
	latin[2-4] {
	    set enc iso8859-[string index $lenc 5]
	}
	latin[56] {
	    set enc iso8859-[expr [string index $lenc 5]+4]
	}
	latin[89] {
	    set enc iso8859-[expr [string index $lenc 5]+6]
	}
	latin10 {
	    set enc iso8859-16
	}
	mac* -  x11controlchars {
	    # keep the original name for uppercase letters
	}
	default {
	    set enc $lenc
	}
    }
    if { [lsearch -exact [encoding names] $enc] == -1 } {
	GMMessage [format $MESS(unknownenc) $enc]
    }
    return $enc
}

# buffered reading

proc ReadFileNL {file} {
    # buffered read from file a line (may be empty) at a time
    # uses and sets:
    #   buffer LFileBuff, flag LFileBuffFull set if buffer contents not used
    #   flag LFileEOF, line counter LFileLNo
    global LFileBuffFull LFileBuff LFileLNo LFileEOF

    if { $LFileEOF($file) } { return "" }
    if { $LFileBuffFull($file) } {
	set LFileBuffFull($file) 0
    } else {
	if { [eof $file] } {
	    set LFileEOF($file) 1
	    return ""
	}
	gets $file LFileBuff($file) ; incr LFileLNo($file)
    }
    return $LFileBuff($file)
}

proc ReadFile {file} {
    # buffered read from file a line at a time skipping empty lines
    # uses and sets:
    #   buffer LFileBuff, flag LFileBuffFull set if buffer contents not used
    #   flag LFileEOF, line counter LFileLNo
    global LFileBuffFull LFileBuff LFileLNo LFileEOF

    if { $LFileEOF($file) } { return "" }
    if { $LFileBuffFull($file) } {
	set LFileBuffFull($file) 0
    } else {
	if { [eof $file] } {
	    set LFileEOF($file) 1
	    return ""
	}
	gets $file LFileBuff($file) ; incr LFileLNo($file)
	while { $LFileBuff($file) == "" } {
	    if { [eof $file] } { 
		set LFileEOF($file) 1
		return ""
	    }
	    gets $file LFileBuff($file) ; incr LFileLNo($file)
	}
    }
    return $LFileBuff($file)
}

# reading utilities

proc ChopInString {m s} {
   # deletes prefix of length $m and leading spaces and tabs from string $s

   return [string trimleft [string range "$s" [string length "$m"] end] " \t"]
}

proc CleanLine {m importing} {
    # deletes leading/trailing blanks, and, if not importing, comments
    global FCOMMAND

    set m [string trim $m " "]
    if { ! $importing && [string first $FCOMMAND(comment) $m] == 0 } {
	return ""
    }
    return $m
}

# parsing

proc MakeComment {commt} {
    # try to change comment to fit to acceptable character set and length;
    #  on failure return ""
    global COMMENTLENGTH NOLOWERCASE

    if { ! [CheckChars Ignore $commt] } {
	if { $NOLOWERCASE } {
	    set commt [string toupper $commt]
	}
	regsub -all {:\.} $commt "" commt
    }
    if { ! [CheckChars Ignore $commt] } {
	return ""
    }
    if { [string length $commt] > $COMMENTLENGTH } {
	set commt [string trim $commt "\ \t"]
    }
    if { [string length $commt] > $COMMENTLENGTH } {
	set commt [string range $commt 0 [expr $COMMENTLENGTH-1]]
    }
    return $commt
}

proc FindPFormat {line} {
    # parse position format; position format names cannot have blanks
    global NONGRIDFMTS GRIDS

    if { [regexp {^([^ ]+)} $line x pf] } {
	foreach f $NONGRIDFMTS {
	    if { $f == $pf } { return $f }
	}
	foreach i $GRIDS {
	    if { $i == $pf } { return $i }
	}
    }
    return BAD
}

proc BadFilePTArgs {file name list params f cf} {
    # parse arguments of projection or transformation command
    #  $f in {Proj, Transf}
    #  $cf in {PROJ, TRANSF}
    # LFile${f}($file) is set to list with name and list of pairs with
    #  parameter name and value
    # return 0 if correct
    global LFile${f} MESS

    set l ""
    foreach av [lrange $list 1 end] {
	set fp [split $av =]
	if { [llength $fp] != 2 } {
	    GMMessage "$MESS(badfield): $f"
	    return 1
	}
	set fn [lindex $fp 0]
	if { [set i [lsearch -exact $params $fn]] == -1 } {
	    if { $name == "UTM" && $fn == "long0" } {
		# parameter name changed after version 6.0.1
		set fn m_0
		set i [lsearch -exact $params $fn]
	    } else {
		GMMessage "$MESS(badattr): $fn"
		return 1
	    }
	}
	set params [lreplace $params $i $i]
	lappend l [list $fn [lindex $fp 1]]
    }
    if { $params != "" } {
	GMMessage "$MESS(missattrs): $params"
	return 1
    }
    set LFile${f}($file) [list $name $l]
    return 0
}

proc FilePTComm {file f cf line} {
    # parse projection or transformation command
    #  $f in {Proj, Transf}
    #  $cf in {PROJ, TRANSF}
    # LFile${f}($file) is set to parsed data here or by proc BadFilePTArgs
    # return 1 if correct
    global MAPKNOWN${cf}S MAP${cf}DATA MESS LFile${f}

    set l [split $line \t]
    set p [lindex $l 0]
    if { [lsearch -exact [set MAPKNOWN${cf}S] $p] == -1 } {
	GMMessage "$MESS(unkn$f): $p"
	return 0
    }
    if { [catch {set MAP${cf}DATA($p)}] } {
	# for particular cases of projections with fixed parameters
	set LFile${f}($file) $p
	return 1
    }
    if { [BadFilePTArgs $file $p $l [set MAP${cf}DATA($p)] $f $cf] } {
	GMMessage $MESS(bad${f}args)
	return 0
    }
    return 1
}

proc FindArgs {commd line file} {
    # parse arguments of command when reading file in GPSMan format
    global LFilePFrmt LFileDatum LFileLNo LFileCreat LFileId LFileOther \
	LFileEOF LFileDefs LFilePath LFileOrigin LFileScale LFileIxPath \
	LFileTimeOff MESS FromTimeOffset

    switch $commd {
	RT {
	    if { [set tix [string first "\t" $line]] != -1 } {
		set LFileId($file) [string range $line 0 [expr $tix-1]]
		# comment and attributes
		set LFileOther($file) [string range $line [expr $tix+1] end]
	    } else {
		set LFileId($file) $line
		set LFileOther($file) ""
	    }
	    return RT
	}
	RS {
	    set l [split $line \t]
	    if { [llength $l] >= 2 } {
		set d [list [lindex $l 0] [lindex $l 1] [lrange $l 2 end]]
		set LFileOther($file) [FormData RS "commt label hidden" $d]
		return RS
	    }
	}
	TR -  LN -  LAP {
	    set l [split $line \t]
	    set LFileId($file) [lindex $l 0]
	    # attributes or other fields
	    set LFileOther($file) [lreplace $l 0 0]
	    return $commd
	}
	GR -  GRWP -  GRRT -  GRTR -  GRLN -  GRLAP -
	GRGR {
	    set LFileId($file) [lindex [split $line \t] 0]
	    return $commd
	}
	nb {
	    set t $line
	    while { [set line [ReadFileNL $file]] != "" && \
		    ! $LFileEOF($file) } {
		set t [format "%s\n%s" $t $line]
	    }
	    set LFileOther($file) [string trim $t " \n\t"]
	    return nb
	}
	format {
	    if { [set pf [FindPFormat $line]] != "BAD" } {
		set line [ChopInString $pf $line]
		if { [scan $line "%f" off] == 1 && abs($off) < 14.5 } {
		    if { $LFileTimeOff($file) != "" && \
			     $off != $LFileTimeOff($file) } {
			GMMessage $MESS(twotimeoffsets)
		    } else {
			regsub {[+-]?[0-9]+\.?[0-9]*} $line "" line
			set line [string trimleft $line "\ \t"]
			if { [DatumRefId $line] != -1 } {
			    set LFilePFrmt($file) $pf
			    if { $FromTimeOffset != "" } {
				set LFileTimeOff($file) $FromTimeOffset
			    } else { set LFileTimeOff($file) $off }
			    set LFileDatum($file) $line
			    set LFileDefs($file) 7
			    return format
			}
			GMMessage "$MESS(unkndatum): $line"
		    }
		}
	    }
	}
	dates {
	    switch $line {
		yes { set LFileCreat($file) 1 ; return dates }
		no { set LFileCreat($file) 0 ; return dates }
	    }
	}	
	pformat {
	    if { [set pf [FindPFormat $line]] != "BAD" } {
		set LFilePFrmt($file) $pf
		set LFileDefs($file) [expr $LFileDefs($file)|6]
		return pformat
	    }
	}
	datum -  pfdatum {
	    if { [DatumRefId $line] != -1 } {
		set LFileDatum($file) $line
		set LFileDefs($file) [expr $LFileDefs($file)|1]
		return $commd
	    }
	    GMMessage "$MESS(unkndatum): $line"
	}
	mapback {
	    if { $line != "" } {
		set LFilePath($file) $line
		return mapback
	    }
	}
	mapproj {
	    if { [FilePTComm $file Proj PROJ $line] } {
		# LFileProj set by proc FilePTComm
		return mapproj
	    }
	}
	maptransf {
	    if { [FilePTComm $file Transf TRANSF $line] } {
		# LFileTransf set by proc FilePTComm
		return maptransf
	    }
	}
	maporigin {
	    GMMessage $MESS(oldfilefmt)
	    return BADC
	}
	mapscale {
	    if { [scan $line %f s] == 1 && $s > 0 } {
		set LFileScale($file) $s
		return mapscale
	    }
	}
	mapbackat -  mapbackcs {
	    if { [scan $line %d,%d%s x y p] == 3 } {
		set LFileIxPath($file) [list $x $y $p]
		return $commd
	    }
	    GMMessage "$MESS(mbkbadat): $line"
	}
    }
    return BADC
}

proc ProcCommand {line file} {
    # parse command when reading file in GPSMan format
    global FCOMMAND FCOMMARGS

    if { [string first ! $line] } { return BAD }
    foreach t "WP TS LS mapinfo" {
	if { $line == $FCOMMAND($t) } { return $t }
    }
    foreach c $FCOMMARGS {
	if { [string first $FCOMMAND($c) $line] == 0 } {
	    if { $c != "RS" } {
		set line [ChopInString $FCOMMAND($c) $line]
	    } else {
		# first 2 fields may be empty: delete only command and 1st tab
		set line [string range "$line" \
			    [string length "$FCOMMAND($c)"] end]
		regsub {[ ]*\t} $line "" line
	    }
	    return [FindArgs $c $line $file]
	}
    }
    return BADC
}

proc FindCommand {file} {
    # parse command when reading from file in GPSMan format
    global LFileEOF

    while { 1 } {
	set m [ReadFile $file]
	if { $LFileEOF($file) } { return EOF }
	set m [string trim [CleanLine $m 0] "\t"]
	if { [string length $m]>0 && ! [regexp {^%.*} $m] } {
	    return [ProcCommand $m $file]
	}
    }
}

proc ReadNB {file} {
    # parse NB when reading from file in GPSMan format
    global LFileBuffFull LFileOther

    if { [FindCommand $file] != "nb" } {
	set LFileBuffFull($file) 1
	return ""
    }
    return $LFileOther($file)
}

proc FindHeader {file} {
    # parse commands at the beginning of file in GPSMan format;
    #  succeed if "format" command found, which may be preceded by
    #  "dates", "position format" or "datum" commands; fail otherwise

    while { 1 } {
	switch [FindCommand $file] {
	    format { return 1 }
	    dates -		
	    pformat -
	    datum { }
	    default { return 0 }
	}
    }
}

proc Clean {coord} {
    # replace degree, minute and second characters by space in coordinate

    regsub -all {['"]} $coord "\ " nc
    # replace degree symbol
    while { [set i [string first \xb0 $nc]] != -1 } {
	set nc \
	  "[string range $nc 0 [expr $i-1]] [string range $nc [expr $i+1] end]"
    }
    return $nc
}

proc ReadPosn {fs i file} {
    # parse position when reading file in GPSMan format
    #  $fs is list of fields, and position starts at index $i
    # sets $PosnLength to number of fields
    global LFilePFrmt LFileDatum PosnLength MESS TXT POSTYPE

    set pformt $LFilePFrmt($file)
    switch [set ptype $POSTYPE($pformt)] {
	latlong {
	    set PosnLength 2
	    set lat [Clean [lindex $fs $i]]
	    set long [Clean [lindex $fs [expr $i+1]]]
	    if { ! [CheckLat GMMessage $lat $pformt] || \
		 ! [CheckLong GMMessage $long $pformt] } {
	       return -1
	    }
	    set latd [Coord $pformt $lat S]
	    set longd [Coord $pformt $long W]
	    return [list $latd $longd $lat $long]
	}
	utm {
	    set PosnLength 4
	    set ze [lindex $fs $i] ; incr i
	    set zn [lindex $fs $i] ; incr i
	    set eng [lindex $fs $i] ; incr i
	    set nng [lindex $fs $i]
	    if { ! [CheckZE GMMessage $ze] || \
		 ! [CheckZN GMMessage $zn] || \
		 ! [CheckNumber GMMessage $eng] || \
		 ! [CheckNumber GMMessage $nng] } {
	       return -1
	    }
	    set pd [UTMToDegrees $ze $zn $eng $nng $LFileDatum($file)]
	    return [lappend pd $ze $zn $eng $nng]
	}
	grid -
	nzgrid {
	    if { $ptype == "grid" } {
		set gr 1
		set zone [lindex $fs $i] ; incr i
		if { ! [CheckZone GMMessage $zone $pformt] } { return -1 }
	    } else { set gr 0 ; set zone "" }
	    set eng [lindex $fs $i] ; incr i
	    set nng [lindex $fs $i]
	    if { ! [CheckFloat GMMessage $eng] || \
		 ! [CheckFloat GMMessage $nng] } {
	       return -1
	    }
	    if { [BadDatumFor $pformt $LFileDatum($file) GMMessage] != 0 } {
		return -1
	    }
	    set pd [GridToDegrees $pformt $zone $eng $nng $LFileDatum($file)]
	    if { $pd == 0 } {
		GMMessage $MESS(outofgrid)
		return -1
	    }
	    if { $gr } {
		set PosnLength 3
		return [lappend pd $zone $eng $nng]
	    } else {
		set PosnLength 2
		return [lappend pd $eng $nng]
	    }
	}
	mh {
	    set PosnLength 1
	    set mhl [lindex $fs $i]
	    if { ! [CheckMHLocator GMMessage $mhl] } { return -1 }
	    return [linsert [MHLocToDegrees $mhl] end $mhl]
	}
    }
}

proc LoadAttrPairs {wh fields ix hidden} {
    # parse attribute-value pairs in list $fields from index $ix
    #  $wh in {WP, RT, TR, LN}
    #  $hidden is 0 if no hidden attributes are allowed
    # return pair with info on open attributes and list of hidden attributes
    #  or "" on error
    global FATTRPAIRS MESS DataIndex

    set pairs $FATTRPAIRS($wh)
    set os "" ; set hs ""
    foreach f [lrange $fields $ix end] {
	set fp [split $f =] ; set l [llength $fp]
	set fn [lindex $fp 0]
	if { [set i [lsearch -glob $pairs "$fn *"]] == -1 } {
	    if { $hidden } {
		lappend hs $f
	    } else {
		GMMessage [format $MESS(badattr) $wh $f]
		return ""
	    }
	} elseif { $l > 2 } {
	    GMMessage [format $MESS(badattr) $wh $f]
	    return ""
	} else {
	    set fd [lindex $pairs $i]
	    set fv [lindex $fp 1]
	    if { [[lindex $fd 3] $fv] } {
		GMMessage [format $MESS(badattrval) $wh $f]
		return ""
	    }
	    lappend os [list $DataIndex([lindex $fd 1]) $fv]
	}
    }
    return [list $os $hs]
}

proc InsertAttrPairs {as data} {
    # insert information from attribute-value pairs into data list

    foreach p $as {
	set k [lindex $p 0]
	set data [lreplace $data $k $k [lindex $p 1]]
    }
    return $data
}

proc LoadWPs {file outRT importing how} {
    # parse WPs data from file
    #  $outRT set if not in the context of reading a RT
    #  $importing set if reading from file in GPStrans instead of GPSMan format
    #  $how in {normal, inGR, inRTGR, void} meaning: keep all data, keep
    #    data for WPs with indices in $LFileIxs($file,WP), save data to
    #    LFileWPs($file) for possible use in RTs in GRs, discard data
    # if ! $outRT && ! $importing assume $LFileId($file) has the identifier
    #  of the RT being loaded
    global LFileLNo LFileBuffFull LFileCreat LFilePFrmt \
	LFileOther LFileDatum LFileEOF LFileVisible LFileWPs LFileIxs \
	LFileRTStages LFileId LFileTimeOff PosnLength DEFAULTSYMBOL \
	DEFAULTDISPOPT CREATIONDATE MESS DataIndex TimeOffset DateFormat


    if { ! $importing } {
	if { $LFileCreat($file) && $LFileTimeOff($file) == "" } {
	    GMMessage $MESS(notimeoffset)
	    set LFileTimeOff($file) 0
	}
	set adjtimeoff [expr $TimeOffset != $LFileTimeOff($file)]
	set adjsecs [expr int(3600*($LFileTimeOff($file)-$TimeOffset))]
    } else { set adjtimeoff 0 }
    
    set wps "" ; set ix -1 ; set count 0
    while { 1 } {
	if { [SlowOpAborted] } { return "" }
	set m [ReadFile $file]
	if { $LFileEOF($file) } { return $wps }
	if { [set m [CleanLine $m $importing]] == "" } { continue }
	if { ! $importing && [string first ! $m] == 0 } {
	    switch -glob [ProcCommand $m $file] {
		WP -  RT -  TR -  LN -  LAP -
		GR {
		    set LFileBuffFull($file) 1
		    return $wps
		}
		RS {
		    if { $outRT } {
			GMMessage "$MESS(badRS) $LFileLNo($file)"
			return ""
		    }
		    lappend LFileRTStages($file) \
			    [list $count $LFileOther($file)]
		    incr count -1
		}
		nb {
		    if { $how != "void" } {
			GMMessage "$MESS(badcommdWP) $LFileLNo($file)"
			return ""
		    }
		}
		GR?? -
		map* -
		BADC {
		    GMMessage "$MESS(badcommdWP) $LFileLNo($file)"
		    return ""
		}
		default {
		    if { $count > 0 } { incr count -1 }
		}
	    }
	    continue
	}
	if { $how == "void" } { continue }
	if { $importing } {
	    if { [regexp {^[RT].*} $m] } {
		set LFileBuffFull($file) 1
		return $wps
	    }
	    if { [string first W $m] != 0 && [string first \t $m] != 1 } {
		GMMessage "$MESS(notabsWP) $LFileLNo($file)"
		return ""
	    }
	    set m [string range $m 2 end]
	}
	set as [split $m \t]
	set n [llength $as]
	if { $n == 1 } {
	    if { $importing || $outRT } {
		set m notabsWP
	    } else {
		# WP name only, in a RT in GPSMan format
		#  this is no longer supported
		set m RTnoWPname
	    }
	    GMMessage "$MESS($m) $LFileLNo($file)"
	    return ""
	}
	if { $n < [expr 3+$LFileCreat($file)] } {
	    GMMessage "$MESS(nofieldsWP) $LFileLNo($file)"
	    return ""
	}
	set name [string trim [lindex $as 0] " "]
	if { ! [CheckName Ignore $name] } {
	    if { [set nname [AskForName $name]] == "" } { return "" }
	    set chgdname $name
	    set name $nname
	} else { set chgdname "" }
	set commt [string trim [lindex $as 1] " "]
	if { ! [CheckComment Ignore $commt] } {
	    set commt [MakeComment $commt]
	}
	set i 2
	if { $LFileCreat($file) && [incr i] && \
		 [set date [lindex $as 2]] != "" } {
	    if { [set sd [ScanDate $date]] == "" } {
		GMMessage [format $MESS(baddateas) $date]
		return ""
	    }
	    if { $adjtimeoff } {
		set secs [expr [eval DateToSecs $sd]+$adjsecs]
		set sd [DateIntsFromSecs $secs]
		set date [eval FormatDate $DateFormat $sd]
	    }
	} else { set date [Now] }
	set posn [ReadPosn $as $i $file]
	if { $posn == -1 } { return "" }
	incr i $PosnLength
	set hs ""
	if { $n > $i } {
	    if { $importing } {
		GMMessage "$MESS(excfieldsWP) $LFileLNo($file)"
		return ""
	    }
	    # optional attribute-value pairs
	    if { [set as [LoadAttrPairs WP $as $i 1]] == "" } { return "" }
	    set hs [lindex $as 1] ; set as [lindex $as 0]
	} else { set as "" }
	set fs "Name Commt PFrmt Posn Datum Date Hidden"
	set data [FormData WP $fs [list $name $commt $LFilePFrmt($file) $posn \
			             $LFileDatum($file) $date $hs]]
	if { $chgdname != "" } {
	    set data [AddOldNameToObs WP $data $chgdname]
	}
	set data [InsertAttrPairs $as $data]
	# check for a NB
	if { ! $importing } {
	    set m [CleanLine [ReadFile $file] 0]
	    while { ! $LFileEOF($file) && [string length $m]==0 } {
		set m [CleanLine [ReadFile $file] 0]
	    }
	    if { ! $LFileEOF($file) && [string first ! $m]==0 && \
		    [ProcCommand $m $file] == "nb" } {
		set i $DataIndex(WPObs)
		set data [lreplace $data $i $i $LFileOther($file)]
	    } else {
		set LFileBuffFull($file) 1
	    }
	}
	set ix [IndexNamed WP $name]
	switch $how {
	    normal {
		set name [StoreWP $ix $name $data $LFileVisible($file)]
		lappend wps $name
	    }
	    inGR {
		if { [lsearch -exact $LFileIxs($file,WP) $ix] != -1 } {
		    set name [StoreWP $ix $name $data $LFileVisible($file)]
		    lappend wps $name
		}
	    }
	    inRTGR {
		lappend LFileWPs($file) [list $name $ix $data]
		lappend wps $name
	    }
	}
	incr count
    }
    # not used
    return
}

proc BadRTRead {file importing how} {
    # parse RTs data from file in GPSMan or GPStrans format
    #  $how in {normal, inGR, void}: keep data on all RTs, keep data for RTs
    #    with indices in $LFileIxs($file,RT), discard data
    global LFileLNo LFileId LFileOther LFileVisible LFileIxs LFileWPs \
	    LFileRTStages MAXWPINROUTE MESS

    # this global is used in LoadWPs
    set id $LFileId($file)
    set as [split $LFileOther($file) "\t"]
    set commt [lindex $as 0] ; set as [lreplace $as 0 0]
    if { ! [CheckComment Ignore $commt] } { set commt [MakeComment $commt] }
    if { $as != "" } {
	if { [set as [LoadAttrPairs RT $as 0 0]] == "" } { return 1 }
	set as [lindex $as 0]
    }
    if { $importing } {
	set obs ""
    } else { set obs [ReadNB $file] }
    set LFileRTStages($file) ""
    switch $how {
	normal {
	    set wpshow normal
	}
	inGR {
	    set wpshow inRTGR
	}
	void {
	    LoadWPs $file 0 $importing void
	    return 0
	}
    }
    set wps [LoadWPs $file 0 $importing $wpshow]
    if { [SlowOpAborted] } { return 1 }
    set cstages $LFileRTStages($file)
    set n [llength $wps]
    if { $n == 0 } {
	GMMessage "$MESS(badWPsRT) $LFileLNo($file)"
	return 1
    }
    if { $n > $MAXWPINROUTE } {
	GMMessage [format $MESS(toomuchWPs) $MAXWPINROUTE]
    }
    set stages "" ; set i 1
    foreach cst $cstages {
	if { [SlowOpAborted] } { return 1 }
	if { [set ct [lindex $cst 0]] > $n || $ct == 0 } {
	    GMMessage "$MESS(badWPsRSs) $LFileLNo($file)"
	    return 1
	}
	while { $ct > $i } {
	    lappend stages ""  ;  incr i
	}
	lappend stages [lindex $cst 1] ; incr i
    }
    set ix [IndexNamed RT $id]
    if { $how == "inGR" } {
	if { [lsearch -exact $LFileIxs($file,RT) $ix] == -1 } { return 0 }
	set nwps ""
	foreach n $wps {
	    if { [SlowOpAborted] } { return 1 }
	    # that there is an entry for the WP was checked in LoadWPs
	    foreach e $LFileWPs($file) {
		if { [lindex $e 0] == $n } {
		    set name [StoreWP [lindex $e 1] $n [lindex $e 2] \
			                            $LFileVisible($file)]
		    lappend nwps $name
		    break
		}
	    }
	}
	set wps $nwps
    }
    set data [FormData RT "IdNumber Commt Obs WPoints Stages" \
	               [list $id $commt $obs $wps $stages]]
    set data [InsertAttrPairs $as $data]
    StoreRT $ix $id $data $wps $LFileVisible($file)
    return 0
}

proc LoadTPs {file importing e0} {
    # parse TR points data from file in either GPSMan or GPStrans format
    #  $e0 is the expected contents of first field (either "" or "T")
    # return list with list of TR points and list of indices (!=0) of TPs
    #  starting segments (always "" when importing)
    global LFileLNo LFileBuffFull LFilePFrmt LFileDatum LFileEOF LFileTimeOff \
	TimeOffset DateFormat MESS

    if { ! $importing } {
	if { $LFileTimeOff($file) == "" } {
	    GMMessage $MESS(notimeoffset)
	    set LFileTimeOff($file) 0
	}
	set adjtimeoff [expr $TimeOffset != $LFileTimeOff($file)]
	set adjsecs [expr int(3600*($LFileTimeOff($file)-$TimeOffset))]
    } else { set adjtimeoff 0 }

    set tps ""
    set tpn 0 ; set segsts ""
    while { 1 } {
	if { [SlowOpAborted] } { return "" }
	if { $importing } {
	    set line [ReadFileNL $file]
	    if { $LFileEOF($file) || $line == "" } { return [list $tps ""] }
	} else {
	    set line [ReadFile $file]
	    if { $LFileEOF($file) } { return [list $tps $segsts] }
	}
	if { [set line [CleanLine $line $importing]] == "" } { continue }
	if { ! $importing && [string first ! $line] == 0 } {
	    switch [ProcCommand $line $file] {
		format -  datum -  pformat -  dates - 
		WP -  RT -  TR -  LN -  LAP -
		GR {
		    set LFileBuffFull($file) 1
		    return [list $tps $segsts]
		}
		comment { }
		TS {
		    if { $tpn != 0 } { lappend segsts $tpn }
		}
		default {
		    GMMessage "$MESS(badcommdTP) $LFileLNo($file)"
		    return ""
		}
	    }
	    continue
	}
	incr tpn
	set fs [split $line \t]
	if { [lindex $fs 0] != $e0 } {
	    GMMessage "$MESS(badTP) $LFileLNo($file)"
	    return ""
	}
	set ad error ; set dep ""
	if { [set l [llength $fs]] == 4 } {
	    set ad ""
	} elseif { ! $importing } {
	    if { $l == 5 } {
		set ad [lindex $fs 4]
		if { [BadAltitude $ad] } { set ad error }
	    } elseif { $l == 6 } {
		# "?"s used in 5.0, 5.1
		if { [set ad [lindex $fs 4]] == "?" } {
		    set ad ""
		} elseif { [BadAltitude $ad] } { set ad error }
		set dep [lindex $fs 5]
		if { $dep != "?" && [BadAltitude $dep] } { set ad error }
	    }
	}
	if { $ad == "" } {
	    if { $dep == "" } {
		set ns "latd longd latDMS longDMS date secs"
	    } else {
		set ad $dep
		set ns "latd longd latDMS longDMS date secs depth"
	    }
	} elseif { $ad == "error" } {
	    GMMessage "$MESS(badTP) $LFileLNo($file)"
	    return ""
	} elseif { $dep == "" } {
	    set ns "latd longd latDMS longDMS date secs alt"
	} else {
	    set ad [list $ad $dep]
	    set ns "latd longd latDMS longDMS date secs alt depth"
	}
	if { [set sd [ScanDate [string trim [lindex $fs 1] " "]]] == "" } {
	    GMMessage [format $MESS(baddateas) [lindex $fs 1]]
	    return ""
	}
	set secs [eval DateToSecs $sd]
	if { $adjtimeoff } {
	    set secs [expr $secs+$adjsecs]
	    set sd [DateIntsFromSecs $secs]
	}
	set date [list [eval FormatDate $DateFormat $sd] $secs]
	set posn [ReadPosn $fs 2 $file]
	if { $posn == -1 } { return "" }
	if { $LFilePFrmt($file) != "DMS" } {
	    set posn [FormatLatLong [lindex $posn 0] [lindex $posn 1] DMS]
	}
	set tp [FormData TP $ns [concat $posn $date $ad]]
	lappend tps $tp
    }
    # not used
    return
}

proc BadTRRead {file importing how} {
    # parse TRs data from file in either GPSMan or GPStrans format
    #  $how in {normal, inGR, void}: keep data on all TRs, keep data for TRs
    #    with indices in $LFileIxs($file,TR), discard data
    global LFileLNo LFileDatum LFileId LFileOther LFileVisible LFileIxs MESS \
	    MYGPS

    set hs ""
    if { $importing } {
	set e0 T ; set obs "" ; set as ""
	after 1000 ; set id [Now]
    } else {
	set e0 ""
	set id $LFileId($file)
	if { [set as $LFileOther($file)] != "" } {
	    if { [set as [LoadAttrPairs TR $as 0 1]] == "" } {
		return 1
	    }
	    set hs [lindex $as 1] ; set as [lindex $as 0]
	    if { $hs != "" && $MYGPS == "Garmin" } {
		foreach "as hs" [HiddenRecover TR $as $hs] {}
	    }
	}
	set obs [ReadNB $file]
    }
    # save current datum: a new one may appear at the end of the track
    set datum $LFileDatum($file)
    set ltps [LoadTPs $file $importing $e0]
    if { [SlowOpAborted] } { return 1 }
    set tps [lindex $ltps 0]
    if { $tps != "" } {
	if { $how == "void" } { return 0 }
	set ix [IndexNamed TR $id]
	if { $how != "inGR" || \
		[lsearch -exact $LFileIxs($file,TR) $ix] != -1 } {
	    set data [FormData TR "Name Obs Datum TPoints SegStarts Hidden" \
		             [list $id $obs $datum $tps [lindex $ltps 1] $hs]]
	    set data [InsertAttrPairs $as $data]
	    StoreTR $ix $id $data $LFileVisible($file)
	}
	return 0
    }
    GMMessage "$MESS(badTPsTR) $LFileLNo($file)"
    return 1
}

proc LoadLPs {file} {
    # parse LN points data from file in GPSMan format
    # return list with list of LN points and list of indices (!=0) of LPs
    #  starting segments
    global LFileLNo LFileBuffFull LFilePFrmt LFileDatum LFileEOF MESS

    set lps ""
    set lpn 0 ; set segsts ""
    while { 1 } {
	if { [SlowOpAborted] } { return "" }
	set line [ReadFile $file]
	if { $LFileEOF($file) } { return [list $lps $segsts] }
	if { [set line [CleanLine $line 0]] == "" } { continue }
	if { [string first ! $line]==0 } {
	    switch [ProcCommand $line $file] {
		format -  datum -  pformat -  dates - 
		WP -  RT -  TR -  LN -  LAP -
		GR {
		    set LFileBuffFull($file) 1
		    return [list $lps $segsts]
		}
		comment { }
		LS {
		    if { $lpn != 0 } { lappend segsts $lpn }
		}
		default {
		    GMMessage "$MESS(badcommdLP) $LFileLNo($file)"
		    return ""
		}
	    }
	    continue
	}
	incr lpn
	set fs [split $line \t]
	foreach "b posn alt" $fs { break }
	foreach "latd longd" $posn { break }
	if { $b != "" || [llength $fs] != 3 || \
		! [CheckLat GMMessage $latd DDD] || \
		! [CheckLong GMMessage $longd DDD] || \
		($alt != "" && [BadAltitude $alt]) } {
	    GMMessage "$MESS(badLP) $LFileLNo($file)"
	    return ""
	}
	# no check on user visible position, for speed
	lappend lps [FormData LP "posn alt" [list $posn $alt]]
    }
    # not used
    return
}

proc BadLNRead {file importing how} {
    # parse LNs data from file in GPSMan format
    #  $how in {normal, inGR, void}: keep data on all LNs, keep data for LNs
    #    with indices in $LFileIxs($file,LN), discard data
    global LFileLNo LFileDatum LFileId LFileOther LFileVisible LFileIxs \
	    LFilePFrmt MESS

    set id $LFileId($file)
    if { [set as [LoadAttrPairs LN $LFileOther($file) 0 0]] == "" } {
	return 1
    }
    set as [lindex $as 0]
    set obs [ReadNB $file]
    # save current datum: a new one may appear at the end of the line
    set datum $LFileDatum($file)
    if { [SlowOpAborted] } { return 1 }
    set lpss [LoadLPs $file]
    set lps [lindex $lpss 0]
    if { $lps != "" } {
	if { $how == "void" } { return 0 }
	set ix [IndexNamed LN $id]
	if { $how != "inGR" || \
		[lsearch -exact $LFileIxs($file,LN) $ix] != -1 } {
	    set pfrmt $LFilePFrmt($file)
	    set data [FormData LN "Name Obs Datum PFrmt LPoints SegStarts" \
  		    [list $id $obs $datum $pfrmt $lps [lindex $lpss 1]]]
	    set data [InsertAttrPairs $as $data]
	    StoreLN $ix $id $data $LFileVisible($file)
	}
	return 0
    }
    GMMessage "$MESS(badLPsLN) $LFileLNo($file)"
    return 1
}

proc BadLAPRead {file importing how} {
    # parse LAP data from file in GPSMan format
    #  $how in {normal, inGR}: keep data on all LAPs, keep data for LAPs
    #    with indices in $LFileIxs($file,LAP)
    global LFileLNo LFileDatum LFileId LFileOther LFilePFrmt LFileIxs \
	LFileTimeOff TimeOffset DateFormat MESS SUPPORTLAPS

    if { ! $SUPPORTLAPS } {
	GMMessage $MESS(lapncnsdrd)
	return 0
    }
    set id $LFileId($file)
    if { $LFileTimeOff($file) == "" } {
	GMMessage $MESS(notimeoffset)
	set LFileTimeOff($file) 0
    }
    if { [set sd [ScanDate $id]] == "" } {
	GMMessage [format $MESS(baddateas) $id]
	return ""
    }
    set secs [eval DateToSecs $sd]
    if { $TimeOffset != $LFileTimeOff($file) } {
	set secs [expr $secs+int(3600*($LFileTimeOff($file)-$TimeOffset))]
	set id [eval FormatDate $DateFormat [DateIntsFromSecs $secs]]
    }
    set start [list $id $secs]
    set n [llength [set as $LFileOther($file)]]
    foreach "dur dist begp endp cals trix" $as { break }
    if { $n != 6 || ! [CheckTime GMMessage $dur] || \
	     ( $dist != "" && ! [CheckFloat GMMessage $dist] ) || \
	     ( $begp != "" && [set begp [ReadPosn $begp 0 $file]] == -1 ) || \
	     ( $endp != "" && [set endp [ReadPosn $endp 0 $file]] == -1 ) || \
	     ( $cals != "" && ! [CheckNumber GMMessage $cals] ) || \
	     ( $trix != "" && \
		   ( ! [CheckNumber GMMessage $trix] || $trix > 255 )) } {
	GMMessage "$MESS(badLAP) $LFileLNo($file)"
	return 1
    }
    set ix [IndexNamed LAP $id]
    if { $how != "inGR" || \
	     [lsearch -exact $LFileIxs($file,LAP) $ix] != -1 } {
	set pfrmt $LFilePFrmt($file) ; set datum $LFileDatum($file)
	set obs [ReadNB $file]
	set data [FormData LAP \
	      "Name Obs Start Dur Dist BegPosn EndPosn Cals TRIx PFrmt Datum" \
		  [list $id $obs $start $dur $dist $begp $endp $cals $trix \
		       $pfrmt $datum]]
	StoreLAP $ix $id $data
    }
    return 0
}

proc LoadGRElements {file id} {
    # parse GR elements from file in GPSMan format
    #  $id is name of GR
    global LFileEOF LFileLNo LFileId LFileBuffFull TYPES SUPPORTLAPS MESS

    set gtypes [linsert $TYPES end LAP]
    foreach wh $gtypes { set l($wh) "" }
    set wh 0
    while 1 {
	if { [SlowOpAborted] } { return "" }
	set line [ReadFile $file]
	if { $LFileEOF($file) } { break }
	set line [CleanLine $line 0]
	if { [string length $line] > 0 } {
	    if { [string first ! $line]==0 } {
		switch -glob [set c [ProcCommand $line $file]] {
		    format -  datum -  pformat -  dates - 
		    WP -  RT -  TR -  LN -  LAP -
		    GR {
			set LFileBuffFull($file) 1
			break
		    }
		    GRWP {
			set wh WP
			set n [GetReplacedName $LFileId($file) GR $id]
			if { ! [CheckName Ignore $n] } {
			    GMMessage "$MESS(badGRel) $LFileLNo($file)"
			    return ""
			}
			lappend l(WP) $n
		    }
		    GR* {
			regsub GR $c "" wh
			lappend l($wh) $LFileId($file)
		    }
		    RS -
		    map* -
		    BADC {
			GMMessage "$MESS(badcommdGRel) $LFileLNo($file)"
			return ""
		    }
		}
	    } else {
		if { $wh == 0 } {
		    GMMessage "$MESS(notypeforGRel) $LFileLNo($file)"
		    return ""
		}
		set fs [split $line \t]
		if { [lindex $fs 0] != "" || [llength $fs]!=2 } {
		    GMMessage "$MESS(badGRel) $LFileLNo($file)"
		    return ""
		}
		set n [lindex $fs 1]
		if { $wh == "WP" } {
		    set n [GetReplacedName $n GR $id]
		    if { ! [CheckName Ignore $n] } {
			GMMessage "$MESS(badGRel) $LFileLNo($file)"
			return ""
		    }
		}
		lappend l($wh) $n
	    }
	}
    }
    set els ""
    foreach wh $gtypes {
	if { $l($wh) != "" && \
	     ( $wh != "LAP" || $SUPPORTLAPS ) } {
	    lappend els [list $wh $l($wh)]
	}
    }
    return $els
}

proc BadGRRead {file keep} {
    # parse GRs data from file in GPSMan format
    #  $keep is true if data is to be kept
    global LFileLNo LFileId LFileVisible MESS

    set id $LFileId($file) ; set obs [ReadNB $file]
    set els [LoadGRElements $file $id]
    if { [SlowOpAborted] } { return 1 }
    if { $els != "" } {
	if { $keep } {
	    set data [FormData GR "Name Obs Conts" [list $id $obs $els]]
	    StoreGR [IndexNamed GR $id] $id $data $LFileVisible($file)
	}
	return 0
    }
    GMMessage "$MESS(badGRels) $LFileLNo($file)"
    return 1
}

proc LoadAll {file} {
    # load all items from file in GPSMan format
    # return 0 on error
    global LFileLNo LFileDefs MESS FCOMMAND

    set r 0
    while { 1 } {
	if { [SlowOpAborted] } { return 0 }
	switch [set wh [FindCommand $file]] {
	    WP {
		if { $LFileDefs($file) != 7 } {
		    GMMessage $MESS(noheader)
		    break
		}
		LoadWPs $file 1 0 normal
		set r 1
	    }
	    RT -  TR -  LN -  LAP {
		if { $LFileDefs($file) != 7 } {
		    GMMessage $MESS(noheader)
		    break
		}
		if { [Bad${wh}Read $file 0 normal] } { break }
		set r 1
	    }
	    GR {
		if { [BadGRRead $file 1] } { break }
		set r 1
	    }
	    GRWP -  GRRT -  GRTR -  GRLN -  GRLAP -
	    GRGR {
		GMMessage "$MESS(GRelout) $LFileLNo($file)"
	    }
	    format -
	    dates -
	    pformat -
	    datum { }
	    EOF { set r 1 ; break }
	    pfdatum -  NBC -
	    BAD {
		GMMessage "$MESS(loaderr) $LFileLNo($file)"
		break 
	    }
	    BADC {
		GMMessage "$MESS(unkncommd) $LFileLNo($file)"
		break 
	    }
	}
    }
    return $r
}

proc LoadFile {what} {
    # open file in GPSMan format and load all items in it
    #  $what in $TYPES or $what=="LAP" or $what==Data identifies menu
    #  (see proc LoadAll)
    # return 0 on error
    global LChannel

    InitWPRenaming
    if { [OpenInputFileFails $what GPSMan] } { return }
    set r [LoadAll $LChannel($what)]
    CloseInputFile $what
    EndWPRenaming
    return $r
}

proc LoadFileFrom {f what} {
    # open file in GPSMan format and load all items in it
    #  $what in $TYPES or $what=="LAP" or $what==Data
    #  (see proc LoadAll)
    # return 0 on error
    global LChannel CREATIONDATE SHOWFILEITEMS

    InitWPRenaming
    InitInputFileVars $f $what $CREATIONDATE $SHOWFILEITEMS
    set r [LoadAll $f]
    CloseInputFile $what
    EndWPRenaming
    return $r
}

proc LoadGRElsIn {types lixs fmt} {
    # load items of $types (part of $TYPES except GR) to replace
    #  items with given indices, with -1 meaning new items should be accepted
    #  $lixs is list of lists of indices (and maybe -1) aligned with $types
    # $fmt is "GPSMan"
    global LChannel LFileLNo LFileDefs LFileWPs LFileIxs MESS FCOMMAND TYPES

    set file $LChannel(GR)
    foreach wh $TYPES {
	set how$wh void
    }
    foreach wh $types ixs $lixs {
	set LFileIxs($file,$wh) $ixs
	set how$wh inGR
    }
    while { 1 } {
	switch [set wh [FindCommand $file]] {
	    WP {
		if { $LFileDefs($file) != 7 } {
		    GMMessage $MESS(noheader)
		    break
		}
		LoadWPs $file 1 0 $howWP
	    }
	    RT -  TR -  LN -  LAP {
		if { $LFileDefs($file) != 7 } {
		    GMMessage $MESS(noheader)
		    break
		}
		if { [Bad${wh}Read $file 0 $howRT] } { break }
	    }
	    GR {
		if { [BadGRRead $file 0] } { break }
	    }
	    GRWP -  GRRT -  GRTR -  GRLN -  GRLAP -
	    GRGR {
		GMMessage "$MESS(GRelout) $LFileLNo($file)"
	    }
	    format -
	    dates -
	    pformat -
	    datum { }
	    EOF { break }
	    pfdatum -  NBC -
	    BAD {
		GMMessage "$MESS(loaderr) $LFileLNo($file)"
		break
	    }
	    BADC {
		GMMessage "$MESS(unkncommd) $LFileLNo($file)"
		break
	    }
	}
    }
    foreach wh $types {
	catch { unset LFileIxs($file,$wh) }
    }
    catch { unset LFileWPs($file) }
    return
}

proc LoadGREls {what} {
    # load data from file in GPSMan format according to contents of GR(s)
    #  $what in $TYPES identifies menu, not being used
    global TYPES

    InputToGR $TYPES "" OpenInputFileFails LoadGRElsIn CloseInputFile GPSMan
    return
}

proc LoadMapFixedBk {path} {
    # try to load map background information
    # if file does not start with !Image command, return 0
    #  otherwise, return -1 on error or abort, or on success:
    #  "1 path datum pd td scale lixp lcsp" if new format
    #    Before release 5.3 this returned also
    #  "2 path datum origin scale lixp" if old format with !Origin: command
    #    but this command is not accepted anymore by proc FindArgs
    #  where
    #    pd, pt are pairs with name and list of pairs with parameter
    #     name and value
    #    lixp is list of grid indices and path for each subsidiary image in
    #     grid
    #    lcsp is list of canvas coordinates (NW) and path for each subsidiary
    #     image not in grid
    global LFileLNo LFileBuffFull LFileEOF LFilePath LFileDatum LFileDefs \
	LFileProj LFileTransf LFileOrigin LFileScale LFileIxPath LFileTimeOff \
	MESS TXT

    if { $path == "" } {
	set f [GMOpenFile $TXT(loadfrm) MapBkInfo r]
    } else {
	if { [catch {set f [open $path r]}] } {
	    GMMessage "$MESS(cantread) $path"
	    return -1
	}
    }
    set r -1
    if { $f != ".." } {
	set LFileLNo($f) 0 ; set LFileBuffFull($f) 0 ; set LFileEOF($f) 0
	set LFileTimeOff($f) ""
	if { [FindCommand $f] != "mapback" } {
	    set r 0
	} else {
	    SetCursor . watch
	    set LFileDefs($f) 0
	    if { [FindCommand $f] != "datum" } {
		GMMessage $MESS(mbkbaddatum)
	    } else {
		switch [FindCommand $f] {
		    maporigin {
			set r [list 2 $LFilePath($f) $LFileDatum($f) \
				$LFileOrigin($f)]
		    }
		    mapproj {
			if { [FindCommand $f] != "maptransf" } {
			    GMMessage $MESS(mbkbadtransf)
			} else {
			    set r [list 1 $LFilePath($f) $LFileDatum($f) \
				    $LFileProj($f) $LFileTransf($f)]
			}
		    }
		    default {
			GMMessage $MESS(mbkbadproj)
		    }
		}
		if { $r != -1 } {
		    if { [FindCommand $f] != "mapscale" } {
			GMMessage $MESS(mbkbadscale)
			set r -1
		    } else {
			set lg "" ; set lc ""
			while 1 {
			    switch [FindCommand $f] {
				mapbackat {
				    lappend lg $LFileIxPath($f)
				}
				mapbackcs {
				    lappend lc $LFileIxPath($f)
				}
				default {
				    break
				}
			    }
			}
			lappend r $LFileScale($f) $lg $lc
		    }
		}
	    }
	    ResetCursor .
	}
	foreach v "LFileLNo($f) LFileBuffFull($f) LFileEOF($f) LFilePath($f) \
		LFileDefs($f) LFileDatum($f) LFileProj($f) LFileTransf($f) \
		LFileOrigin($f) LFileScale($f) LFileIxPath($f) \
                LFileTimeOff($f)" {
	    catch { unset $v }
	}
	close $f
    }
    return $r
}

proc LoadMapInfo {path} {
    # try to load map information
    # if file does not start with !Map command, return 0
    #  otherwise, return -1 on error or abort, or on success a list with
    #    datum pd td pformat pfdatum scale 
    #  where
    #    pd, pt are pairs with name and list of pairs with parameter
    #     name and value for projection and transformation
    # for files generated by 6.1.2 or older $pfdatum defaults to $datum
    global LFileLNo LFileBuffFull LFileEOF LFileDatum LFileDefs \
	    LFileProj LFileTransf LFilePFrmt LFileScale LFileTimeOff MESS TXT

    if { $path == "" } {
	set f [GMOpenFile $TXT(loadfrm) MapInfo r]
    } else {
	if { [catch {set f [open $path r]}] } {
	    GMMessage "MESS(cantread) $path"
	    return -1
	}
    }
    set r -1
    if { $f != ".." } {
	set LFileLNo($f) 0 ; set LFileBuffFull($f) 0 ; set LFileEOF($f) 0
	set LFileTimeOff($f) ""
	if { [FindCommand $f] != "mapinfo" } {
	    GMMessage $MESS(badmapinfo)
	} else {
	    SetCursor . watch
	    set LFileDefs($f) 0
	    if { [FindCommand $f] != "datum" } {
		GMMessage $MESS(mbkbaddatum)
	    } elseif { [FindCommand $f] != "mapproj" || \
			   [FindCommand $f] != "maptransf" || \
			   [FindCommand $f] != "pformat" } {
		GMMessage $MESS(badmapinfo)
	    } else {
		set datum $LFileDatum($f)
		if { [set c [FindCommand $f]] != "pfdatum" } {
		    # file by 6.1.2 or older
		    set LFileDatum($f) $datum
		} else { set c [FindCommand $f] }
		if { $c != "mapscale" } {
		    GMMessage $MESS(badmapinfo)
		} else {
		    set r [list $datum $LFileProj($f) $LFileTransf($f) \
			$LFilePFrmt($f) $LFileDatum($f) $LFileScale($f)]
		}
	    }
	}
	ResetCursor .
	foreach v "LFileLNo($f) LFileBuffFull($f) LFileEOF($f) \
		LFileDefs($f) LFileDatum($f) LFileProj($f) LFileTransf($f) \
		LFilePFrmt($f) LFileScale($f) LFileTimeOff($f)" {
	    catch { unset $v }
	}
	close $f
    }
    return $r
}

array set LSqIStates {
    start       {format datum pformat}
    start,nxt   {point pformat datum}
    pformat     {pformat}
    pformat,nxt {datum}
    datum       {datum}
    datum,nxt   {pformat}
    point       {}
}

proc LoadLeastSquaresInfo {} {
    # read geodetic and pixel coordinates of points for geo-referencing
    #  a map image using a least squares fit
    # file starts with optional datum and position format commands, or a
    #  format command, (defaults are "WGS 84" and DDD), followed by a line
    #  for each point with fields separated by tabulations; such lines
    #  either start with a $FCOMMAND(WP) field, followed by fields with
    #  an existing WP name and the pixel coordinates (x, y, with y
    #  increasing downwards), or have a position, in the selected/default
    #  datum and format, and the pixel coordinates (as before)
    # the minimum number of points is given by the global
    #    $MAPTRANSFNPTS(LeastSquares) defined in metadata.tcl
    # return 0 on failure, or a list with list of latd, longd and datum,
    #  a list of pairs with pixel coordinates (x, y, in Tcl sign convention
    #  with origin at upper left corner), a list with waypoints names (empty
    #  for explicit positions), and the input file path
    global LSqIStates LFileLNo LFileEOF LFileBuffFull LFileDatum LFileDefs \
	LFileDatum LFilePFrmt PosnLength TXT MESS MAPTRANSFNPTS FCOMMAND File \
	WPPosn WPDatum GFShowInfo GFVisible SHOWFILEITEMS

    set GFVisible $SHOWFILEITEMS
    set prms {GFShowInfo GFVisible}
    set pws [list "@$TXT(showfitinfo)" "@$TXT(mapfitWPs)"]
    set f [GMOpenFileParms $TXT(loadfrm) MapBkInfo r $prms $pws]
    if { $f == ".." } {
	return 0
    }
    set fpath $File(MapBkInfo)
    set LFileLNo($f) 0 ; set LFileBuffFull($f) 0 ; set LFileEOF($f) 0
    set LFileDefs($f) 0
    # defaults
    set LFileDatum($f) "WGS 84" ; set LFilePFrmt($f) DDD
    set state start ; set err 0
    set ldds "" ; set xys "" ; set names ""
    while { ! $LFileEOF($f) } {
	if { [set line [CleanLine [ReadFile $f] 0]] == "" } { continue }
	if { [string first ! $line] == 0 && \
		 [string first $FCOMMAND(WP) $line] != 0 } {
	    set c [ProcCommand $line $f]
	    if { [set ix [lsearch -exact $LSqIStates($state) $c]] == -1 } {
		incr err ; break
	    }
	    set state [lindex $LSqIStates($state,nxt) $ix]
	} else {
	    set state point
	    set fs [split $line \t]
	    if { [lindex $fs 0] == $FCOMMAND(WP) } {
		set name [string trim [lindex $fs 1]]
		if { ! [CheckName GMMessage $name] } { incr err ; break }
		if { [set ix [IndexNamed WP $name]] == -1 } {
		    GMMessage [format $MESS(undefinedWP) $name]
		    incr err ; break
		}
		set p $WPPosn($ix)
		set datum $WPDatum($ix)
		set fix 2
	    } elseif { [set p [ReadPosn $fs 0 $f]] != -1 } {
		set name ""
		set datum  $LFileDatum($f)
		set fix $PosnLength
	    } else { incr err ; break }
	    set x [lindex $fs $fix]
	    set y [lindex $fs [expr $fix+1]]
	    if { ! [CheckSignedFloat GMMessage $x] || \
		     ! [CheckSignedFloat GMMessage $y] } {
		incr err ; break
	    }
	    lappend ldds [list [lindex $p 0] [lindex $p 1] $datum]
	    lappend xys [list $x $y]
	    lappend names $name
	}
    }
    close $f
    if { $err } {
	GMMessage "$MESS(loaderr) $LFileLNo($f)"
	set r 0
    } elseif { [regexp {^>=([0-9]+)$} $MAPTRANSFNPTS(LeastSquares) x n] && \
		   [lindex $xys [expr $n-1]] == "" } {
	GMMessage [format $MESS(needNpoints) $n]
	set r 0
    } else {
	set r [list $ldds $xys $names $fpath]
    }
    foreach v "LNo EOF BuffFull Datum Defs Datum PFrmt" {
	catch { unset LFile${v}($f) }
    }
    return $r
}

## BSB contribution
proc LoadAutoMapInfo {} {
    # Load index of mapfiles and bounds

    global ImageIndex MESS TXT

    set f [GMOpenFile $TXT(loadfrm) MapBkInfo r]
    if { $f != ".." } {
	set index -1
	while {! [eof $f]} {
	    incr index
	    set line [gets $f]
	    set ImageIndex($index) [split $line "\t"]
	}
    }
    return
}

